Notifications follow the conversation's audience (users/access.audience):
whoever started it and the people it's shared with, plus the owner once they
follow a guest's conversation (writing in it, or PUT /api/conversation-follow),
minus whoever muted it (meta.follow). Pushes and asks fan out to each of them
on their own channels — the owner on the webhooks, a guest in-app + email —
and the feeds (/api/notifications, /api/notify-log, the phone's /unread)
list only what the caller hears, links rebased to the reader's host. Forms
carry forYou so "waiting for feedback" is per user.
/api/users carries spentUsd (this month), totalSpentUsd and conversations per
guest (users/runs.spend_by_user, the same sum the budget is checked against).
POST /api/users/{id}/impersonate opens a 4-hour guest session for the owner's
browser on the admin host (imp on the record: never extended, not counted in
the guest's sessions); /api/me says impersonating; logout ends it. A spawn
while impersonating skips the "guest started" ping.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
135 lines
3.7 KiB
Python
135 lines
3.7 KiB
Python
"""Guests: ``/api/me``, the owner's user admin, conversation sharing."""
|
|
from __future__ import annotations
|
|
|
|
from typing import Optional
|
|
|
|
from .base import Schema
|
|
|
|
__all__ = [
|
|
"ProjectGrant", "NotifyPrefs", "Me", "GuestUser", "UsersResponse",
|
|
"LoginLink", "ShareCandidate", "ConversationShare", "GuestShortcut",
|
|
"GuestDefaults", "GuestSkill", "GuestSkillsResponse",
|
|
]
|
|
|
|
|
|
class ProjectGrant(Schema):
|
|
slug: str
|
|
level: str # "view" | "chat" | "ship"
|
|
|
|
|
|
class NotifyPrefs(Schema):
|
|
channels: list[str] # "in_app" | "email" | "webpush"
|
|
|
|
|
|
class Me(Schema):
|
|
"""Who the browser is: the owner, or a guest with their grants."""
|
|
id: str
|
|
role: str # "owner" | "guest"
|
|
name: str
|
|
email: Optional[str] = None
|
|
readonly: bool = False
|
|
projects: list[ProjectGrant]
|
|
models: list[str]
|
|
budgetUsd: Optional[float] = None
|
|
spentUsd: Optional[float] = None
|
|
notify: Optional[NotifyPrefs] = None
|
|
mail: bool = False # outbound mail configured (login links can be mailed)
|
|
surface: Optional[str] = None # "admin" | "guest"
|
|
# A guest's starting prompt shortcuts (what Reset goes back to); None for
|
|
# the owner, whose defaults are built into the frontend.
|
|
defaultShortcuts: Optional[list["GuestShortcut"]] = None
|
|
# The owner looking through this guest's eyes (Settings → Users →
|
|
# Impersonate): the PWA shows a "back to admin" bar.
|
|
impersonating: bool = False
|
|
|
|
|
|
class GuestUser(Schema):
|
|
id: str
|
|
email: str
|
|
name: str
|
|
role: str
|
|
projects: dict[str, str]
|
|
models: list[str]
|
|
budgetUsd: float
|
|
notify: NotifyPrefs
|
|
disabled: bool
|
|
createdAt: str
|
|
lastLoginAt: Optional[str] = None
|
|
sessions: int = 0
|
|
# What their conversations cost: this month (what the budget is checked
|
|
# against) and all time, and how many conversations they started.
|
|
spentUsd: float = 0
|
|
totalSpentUsd: float = 0
|
|
conversations: int = 0
|
|
|
|
|
|
class UsersResponse(Schema):
|
|
users: list[GuestUser]
|
|
levels: list[str]
|
|
channels: list[str]
|
|
defaultModels: list[str]
|
|
mail: bool
|
|
guestUrl: Optional[str] = None
|
|
|
|
|
|
class LoginLink(Schema):
|
|
url: str
|
|
sent: bool
|
|
expiresInS: int
|
|
|
|
|
|
class ShareCandidate(Schema):
|
|
id: str
|
|
name: str
|
|
level: Optional[str] = None
|
|
|
|
|
|
class AudienceMember(Schema):
|
|
id: str # "owner" or a guest's users.json id
|
|
name: str
|
|
|
|
|
|
class ConversationShare(Schema):
|
|
sessionId: str
|
|
owner: Optional[str] = None
|
|
project: Optional[str] = None
|
|
participants: list[str]
|
|
candidates: list[ShareCandidate]
|
|
# Does the caller hear about this conversation (its pushes, asks, forms)?
|
|
notifying: bool = False
|
|
# Everyone who does — who started it, who it's shared with, the owner if
|
|
# they follow it, minus whoever muted it.
|
|
audience: list[AudienceMember] = []
|
|
|
|
|
|
class GuestShortcut(Schema):
|
|
"""A prompt shortcut in the frontend's ``PromptShortcut`` shape."""
|
|
id: str
|
|
label: str
|
|
icon: str = "Sparkles"
|
|
guideline: str = ""
|
|
defaultOn: bool = False
|
|
labelOff: Optional[str] = None
|
|
guidelineOff: Optional[str] = None
|
|
children: Optional[list[str]] = None
|
|
triggers: Optional[list[str]] = None
|
|
shared: Optional[bool] = None
|
|
|
|
|
|
class GuestDefaults(Schema):
|
|
"""What the owner sets for every guest at once (Settings → Users)."""
|
|
promptShortcuts: list[GuestShortcut]
|
|
sharedSkills: list[str] # `name` (repo) | `<project>/<name>`
|
|
|
|
|
|
class GuestSkill(Schema):
|
|
"""A skill a guest's run can use — the composer's `/` mentions."""
|
|
name: str # the invocation name (`shared:<name>` when shared)
|
|
description: str = ""
|
|
triggerWords: list[str] = []
|
|
source: str # "shared" | <project slug>
|
|
|
|
|
|
class GuestSkillsResponse(Schema):
|
|
skills: list[GuestSkill]
|