@happyvertical/smrt-chat 0.57.36 → 0.57.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/agents/tool-loop.md +60 -6
  2. package/dist/assistant-turn-events.d.ts +12 -4
  3. package/dist/assistant-turn-events.d.ts.map +1 -1
  4. package/dist/assistant-turn.d.ts +29 -1
  5. package/dist/assistant-turn.d.ts.map +1 -1
  6. package/dist/chunks/{ChatService-D0XuiCTk.js → ChatService-BGBAqmZq.js} +14 -3
  7. package/dist/chunks/ChatService-BGBAqmZq.js.map +1 -0
  8. package/dist/chunks/assistant-turn-events-BbZUf-Ju.js.map +1 -1
  9. package/dist/chunks/chat-stream-DK67O_WB.js +2689 -0
  10. package/dist/chunks/chat-stream-DK67O_WB.js.map +1 -0
  11. package/dist/collections/ChatMessageCollection.d.ts +5 -1
  12. package/dist/collections/ChatMessageCollection.d.ts.map +1 -1
  13. package/dist/index.d.ts +2 -2
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +12 -5
  16. package/dist/index.js.map +1 -1
  17. package/dist/internal/agent-runtime.js +1 -1
  18. package/dist/manifest.json +2 -1
  19. package/dist/persona-conversation.d.ts +5 -0
  20. package/dist/persona-conversation.d.ts.map +1 -1
  21. package/dist/services/ChatService.d.ts +12 -1
  22. package/dist/services/ChatService.d.ts.map +1 -1
  23. package/dist/smrt-knowledge.json +5 -5
  24. package/dist/svelte/components/assistant/create-assistant-dock-controller.svelte.d.ts +6 -2
  25. package/dist/svelte/components/assistant/create-assistant-dock-controller.svelte.d.ts.map +1 -1
  26. package/dist/svelte/components/assistant/create-assistant-dock-controller.svelte.js +4 -1
  27. package/dist/sveltekit.d.ts +13 -2
  28. package/dist/sveltekit.d.ts.map +1 -1
  29. package/dist/sveltekit.js +7 -3
  30. package/dist/sveltekit.js.map +1 -1
  31. package/dist/tool-loop.d.ts +47 -9
  32. package/dist/tool-loop.d.ts.map +1 -1
  33. package/dist/voice.d.ts +4 -0
  34. package/dist/voice.d.ts.map +1 -1
  35. package/package.json +21 -21
  36. package/dist/chunks/ChatService-D0XuiCTk.js.map +0 -1
  37. package/dist/chunks/chat-stream-D6B3InMe.js +0 -2601
  38. package/dist/chunks/chat-stream-D6B3InMe.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"sveltekit.js","names":[],"sources":["../src/sveltekit.ts"],"sourcesContent":["/**\n * Mountable SvelteKit routes for the AssistantDock (#3368).\n *\n * One call serves everything the dock's HTTP transport\n * (`createAssistantHttpTransport` / `createSmrtAssistantTransport`) and HTTP\n * action client (`createAssistantHttpActionClient`) call:\n *\n * ```ts\n * // src/routes/api/assistant/[...path]/+server.ts\n * import { mountAssistantRoutes } from '@happyvertical/smrt-chat/sveltekit';\n * import { runtime } from '$lib/server/smrt';\n * export const { GET, POST } = mountAssistantRoutes({\n * allowedTools: ['notes.read', 'notes.create'],\n * runtime,\n * });\n * ```\n *\n * `ai` defaults to the `smrt.config` `ai` block (resolved per turn through\n * `@happyvertical/smrt-config`); `allowedTools` alone offers the manifest\n * operations it names; `runtime` supplies the per-request database and the\n * streamed turn's own database lifetime (`db` may instead be a per-request\n * resolver).\n *\n * GET threads member-scoped thread list\n * POST threads { title } → { thread }\n * GET threads/:id/messages member-scoped, chronological\n * POST threads/:id/messages { content, clientRequestId, ... } → SSE turn\n * POST threads/:id/resume { continuationId, results, ... } → SSE turn\n * POST attachments multipart `file` (host storage only)\n * POST actions/preview | actions/apply DataSurfaceActionWireRequest → { result }\n *\n * Trust boundary: the principal comes from SvelteKit `event.locals` (as\n * populated by `createSessionHandler` from `@happyvertical/smrt-users/sveltekit`)\n * or an injected resolver — never from a header, hostname or request body.\n * Every actor gets ONE assistant `AgentSession` per tenant (keyed, so it never\n * reuses another feature's session); threads are readable and writable only\n * when they live in that session's room. Reads never create anything. Model\n * tools run under `executeAsPrincipal` (inside `runToolLoop`) with a\n * fail-closed allow-list: no `allowedTools` means no tools.\n *\n * Server-only: imports no Svelte or browser code.\n *\n * @packageDocumentation\n */\n\nimport './__smrt-register__.js';\n\nimport type { AIInterface, AIMessage } from '@happyvertical/ai';\nimport type {\n PrincipalAuditSink,\n PrincipalTool,\n} from '@happyvertical/smrt-agents';\nimport type { DataSurfaceActionAdapter } from '@happyvertical/smrt-agents/server';\nimport {\n getConfig,\n loadConfig,\n resolveConfiguredAIProvider,\n toAIClientOptions,\n} from '@happyvertical/smrt-config';\nimport type { SmrtClassOptions } from '@happyvertical/smrt-core';\nimport {\n getCurrentSessionPermissionContext,\n getRequestScopedDatabase,\n} from '@happyvertical/smrt-users';\nimport {\n ASSISTANT_TURN_GENERIC_ERROR,\n type AssistantContinuationStore,\n type AssistantTurnErrorLogger,\n type AssistantTurnState,\n type AuthoredToolReply,\n createAssistantTurnResponse,\n createSessionContinuationStore,\n DEFAULT_CONTINUATION_TTL_MS,\n runAssistantTurn,\n} from './assistant-turn.js';\nimport type { AssistantTurnEvent } from './assistant-turn-events.js';\nimport { MAX_CHAT_STREAM_CONTENT_LENGTH } from './chat-stream.js';\nimport type { AgentSession } from './models/AgentSession.js';\nimport type { ChatMessage } from './models/ChatMessage.js';\nimport type { ChatThread } from './models/ChatThread.js';\nimport {\n ChatClientRequestConflictError,\n type ChatClientRequestOutcome,\n type ChatMessageAttachment,\n ChatService,\n clientRequestMessageId,\n} from './services/ChatService.js';\nimport {\n buildManifestToolCatalog,\n type ClientToolResultInput,\n FALLBACK_HISTORY_PLACEHOLDER,\n isFallbackReply,\n MAX_CLIENT_TOOL_RESULT_CHARS,\n MAX_CLIENT_TOOLS,\n type ManifestTool,\n sanitizeClientToolDeclarations,\n type ToolInvocation,\n type ToolLoopUsage,\n} from './tool-loop.js';\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/** The slice of a SvelteKit `RequestEvent` these handlers read. */\nexport interface AssistantRouteEvent {\n request: Request;\n url: URL;\n params?: Partial<Record<string, string>>;\n locals?: unknown;\n}\n\n/** A SvelteKit `+server.ts` handler with no `@sveltejs/kit` dependency. */\nexport type AssistantRouteHandler = (\n event: AssistantRouteEvent,\n) => Promise<Response>;\n\n/** The authenticated caller, resolved server-side. */\nexport interface AssistantPrincipal {\n /** The user whose live permissions bound every tool call (`runAsUserId`). */\n userId: string;\n /** The user's Profile: the chat actor that owns the assistant session. */\n profileId: string;\n /** The active, session-authorized tenant. */\n tenantId: string;\n /** Optional permission snapshot; omitted resolves live permissions. */\n permissions?: string[];\n}\n\n/**\n * Resolves the caller. Return `null` for an unauthenticated request (401);\n * throw {@link AssistantRouteError} for any other refusal.\n */\nexport type AssistantPrincipalResolver = (\n event: AssistantRouteEvent,\n) =>\n | AssistantPrincipal\n | null\n | undefined\n | Promise<AssistantPrincipal | null | undefined>;\n\n/** What every per-request option callback receives. */\nexport interface AssistantRequestContext {\n event: AssistantRouteEvent;\n principal: AssistantPrincipal;\n /**\n * The client-supplied `context` object of a send or resume body (for\n * example the surfaces the page has mounted), validated to be a plain JSON\n * object of at most {@link MAX_ASSISTANT_REQUEST_CONTEXT_BYTES} bytes.\n * Present only on the callbacks of a send or resume; absent when the client\n * sent none. It is untrusted request input: use it to narrow, never to\n * grant.\n */\n requestContext?: Readonly<Record<string, unknown>>;\n}\n\n/** A value, or a per-request callback producing it. */\nexport type AssistantRouteValue<T> =\n | T\n | ((context: AssistantRequestContext) => T | Promise<T>);\n\n/** A stored attachment, as the host's storage reports it. */\nexport interface AssistantAttachmentRecord {\n id: string;\n name: string;\n contentType: string;\n size: number;\n url?: string;\n}\n\n/**\n * Host-owned attachment storage. Without it, uploads and sends with\n * attachments are refused: the chat package stores no files.\n */\nexport interface AssistantAttachmentStorage {\n /** Store an upload for the principal and describe it. */\n upload(\n file: File,\n context: AssistantRequestContext,\n ): Promise<AssistantAttachmentRecord>;\n /**\n * Check that the references a send names belong to the principal and\n * return the records to store on the message, or `null` to refuse. The\n * references are request input: never trust their `url`/`size` as given.\n */\n verify(\n references: unknown[],\n context: AssistantRequestContext,\n ): Promise<AssistantAttachmentRecord[] | null>;\n /** Largest accepted upload, bytes. Default 10 MiB. */\n maxBytes?: number;\n}\n\n/** The two adapter calls the action routes make. */\nexport type AssistantActionAdapter = Pick<\n DataSurfaceActionAdapter,\n 'preview' | 'apply'\n>;\n\n/** Selectable model, the same shape the dock's `ModelPicker` uses. */\nexport interface AssistantRouteModel {\n id: string;\n label?: string;\n}\n\n/**\n * The two application-runtime calls the routes use; the runtime from\n * `@happyvertical/smrt-app-runtime/sveltekit` satisfies it as is.\n */\nexport interface AssistantRouteRuntime {\n /** The request's database (its RLS transaction inside one). */\n databaseConfig(): SmrtClassOptions['db'];\n /**\n * Run `fn` as a principal: under `database-rls` in a fresh transaction\n * publishing that user, tenant and live permissions (capped to `scopes`;\n * omitted means no cap), which `databaseConfig()` returns inside `fn`.\n * `fn` receives the bound principal, whose `scopes` are the effective set.\n */\n runAsPrincipal<T>(\n principal: {\n readonly id: string;\n readonly tenantId: string;\n readonly scopes?: readonly string[];\n },\n fn: (bound: AssistantRouteBoundPrincipal) => Promise<T>,\n ): Promise<T>;\n}\n\n/** What {@link AssistantRouteRuntime.runAsPrincipal} hands its callback. */\nexport interface AssistantRouteBoundPrincipal {\n /** The effective permissions: live at bind time, capped to the scopes. */\n readonly scopes: readonly string[];\n}\n\n/** Options for {@link mountAssistantRoutes}. */\nexport interface MountAssistantRoutesOptions {\n /**\n * The AI client, or a factory called once per turn. Omitted, each turn\n * builds a client from the `smrt.config` `ai` block through the shared\n * resolver (`resolveConfiguredAIProvider` → `toAIClientOptions` →\n * `getAI`), loading the config file first if nothing has. A factory that\n * throws (including `AIProviderNotConfiguredError` when nothing is\n * configured) answers 503 before anything is written; the error goes to\n * `onError` only. Passing `ai` explicitly as `undefined`/`null` is a mount\n * error, not a fallback.\n */\n ai?:\n | AIInterface\n | ((\n context: AssistantRequestContext & { model?: string },\n ) => AIInterface | Promise<AIInterface>);\n /**\n * Database for chat persistence, the tool catalog and the principal run:\n * a fixed value, or a resolver called once per request (and once more\n * inside a streamed turn's own database lifetime, see `runtime`). Default\n * with `runtime`: `() => runtime.databaseConfig()`.\n */\n db?: AssistantRouteValue<SmrtClassOptions['db']>;\n /**\n * The application runtime (`runtime` from\n * `@happyvertical/smrt-app-runtime/sveltekit`). A request inside an RLS\n * transaction (`database-rls`) ends, committing that transaction, when the\n * handler returns its streaming response, so a turn never keeps it: with\n * `runtime`, a streamed turn waits for the request's transaction to end and\n * then runs, persists its reply and records its outcome in its own\n * transaction for the same principal (`runtime.runAsPrincipal`). Without\n * it, such a turn runs to completion before the response is returned (the\n * events arrive at once, not incrementally). Outside an RLS transaction\n * (SQLite, the local profile) the turn streams as before either way.\n */\n runtime?: AssistantRouteRuntime;\n /** Default: {@link resolveAssistantPrincipalFromLocals}. */\n resolvePrincipal?: AssistantPrincipalResolver;\n /** The assistant's agent id (its `bot` profile slug). Default `smrt-assistant`. */\n agentId?: string;\n /**\n * Session key of the dock's `AgentSession`. Default\n * {@link ASSISTANT_DOCK_SESSION_KEY} (`assistant-dock`). A host whose\n * assistant threads already live in another session passes that session's\n * key to list and continue them: `null` adopts the person's unkeyed session\n * (what `ChatService.createAgentSession` opens without a `sessionKey`), a\n * string adopts the session stored under that key. Sessions are matched on\n * the key exactly, so a host changing it from the default stops seeing the\n * threads of the old key.\n */\n sessionKey?: string | null;\n systemPrompt?: AssistantRouteValue<string | undefined>;\n /**\n * Server tool allow-list, fail-closed: absent or empty offers NO tools.\n * Gates `extraTools`, `tools` and the data-surface action adapter. Without\n * `tools`, the route offers the manifest operations named here\n * (`buildManifestToolCatalog`), except names an `extraTools` entry serves.\n * A name nothing provides is a configuration error: at mount when its\n * collection is already registered, otherwise the turn answers 503 (the\n * check is skipped when `actions` is set, since action tools may be named).\n */\n allowedTools?: AssistantRouteValue<readonly string[]>;\n /** Server tools (e.g. `createDataSurfaceTools()`), narrowed by `allowedTools`. */\n extraTools?: AssistantRouteValue<readonly PrincipalTool[]>;\n /**\n * Manifest tools, narrowed by `allowedTools`. Omitted, they are built from\n * `allowedTools`; pass a value (even `[]`) to supply them yourself.\n */\n tools?: AssistantRouteValue<readonly ManifestTool[]>;\n /**\n * Browser tools the page may declare (exact names or `prefix*`). Empty\n * (the default) admits none.\n */\n clientToolAllowList?: readonly string[];\n /**\n * Where suspended turns wait. Default: the actor's assistant session\n * context (`createSessionContinuationStore`), re-read before every write.\n * A shared store must keep keys private to the session it is given.\n */\n continuations?: (\n context: AssistantRequestContext & { session: AgentSession },\n ) => AssistantContinuationStore;\n /** When set, a requested model must be listed; otherwise it is ignored. */\n models?: readonly AssistantRouteModel[];\n /** Model used when the request names none. */\n defaultModel?: string;\n maxSteps?: number;\n maxTurnTokens?: number;\n maxTurnMs?: number;\n /** Per-model-call timeout in ms (see `AssistantTurnOptions`, #4379). */\n requestTimeoutMs?: number;\n /** Reply stored when the turn produced no answer (#4379). */\n emptyAnswerFallback?: string;\n maxTokens?: number;\n temperature?: number;\n /** Earlier messages sent with a new turn. Default 20. */\n historyLimit?: number;\n /**\n * A stored send with no reply and no recorded outcome (e.g. its process\n * died) counts as failed after this long. Default 15 minutes.\n */\n abandonedTurnMs?: number;\n /**\n * How long a streamed turn waits for its request's RLS transaction to end\n * before it reports an error instead of running (see `runtime`). It then\n * keeps watching, up to `abandonedTurnMs`, and settles a send that\n * committed late as `failed`. Default 60 s.\n */\n turnStartTimeoutMs?: number;\n describeTool?: (name: string, args?: Record<string, unknown>) => string;\n /**\n * Persist a server tool invocation into the thread (default: none). Its\n * tool name must be on `allowedTools`, which is mirrored onto the session.\n */\n authorInvocation?: (invocation: ToolInvocation) => AuthoredToolReply | null;\n onUsage?: (usage: ToolLoopUsage, context: AssistantRequestContext) => void;\n audit?: PrincipalAuditSink;\n postgresRls?: boolean;\n /** Host attachment storage; absent refuses attachments. */\n attachments?: AssistantAttachmentStorage;\n /** Data-surface action adapter; absent answers the action routes 404. */\n actions?: {\n adapter: AssistantRouteValue<AssistantActionAdapter>;\n };\n /**\n * Refuse mutating requests whose `Origin` (or `Sec-Fetch-Site`/`Referer`)\n * is not this origin. Default `true`.\n */\n checkOrigin?: boolean;\n /** Extra origins accepted by the origin check (e.g. behind a proxy). */\n trustedOrigins?: readonly string[];\n /** Largest JSON body, bytes. Default 1 MiB. */\n maxBodyBytes?: number;\n /** Longest user message, characters. Default 12,000. */\n maxContentLength?: number;\n /** SSE keep-alive interval. */\n heartbeatMs?: number;\n /** Server-side log for failures withheld from the browser. */\n onError?: AssistantTurnErrorLogger;\n /** Rest parameter name of the mounting route. Default `path`. */\n paramName?: string;\n /** Used when the route has no rest parameter: the prefix to strip. */\n basePath?: string;\n}\n\n/** The handlers {@link mountAssistantRoutes} returns. */\nexport interface AssistantRoutes {\n GET: AssistantRouteHandler;\n POST: AssistantRouteHandler;\n /** Method-agnostic entry point (GET and POST both delegate here). */\n handle: AssistantRouteHandler;\n}\n\n/** A refusal with an HTTP status and a stable, user-safe code. */\nexport class AssistantRouteError extends Error {\n constructor(\n readonly status: number,\n readonly code: string,\n message: string,\n ) {\n super(message);\n this.name = 'AssistantRouteError';\n }\n}\n\n// ---------------------------------------------------------------------------\n// Wire shapes (mirrors `AssistantThreadSummary` / `AssistantMessage`)\n// ---------------------------------------------------------------------------\n\n/** Thread summary as the routes send it. */\nexport interface AssistantThreadWire {\n id: string;\n title: string;\n isResolved: boolean;\n messageCount: number;\n lastMessageAt: string | null;\n}\n\n/** Message as the routes send it (chronological in lists). */\nexport interface AssistantMessageWire {\n id: string;\n threadId: string;\n content: string;\n role: 'user' | 'assistant' | 'system' | 'tool';\n createdAt: string;\n attachments?: AssistantAttachmentRecord[];\n toolCallData?: Record<string, unknown>;\n clientRequestId?: string;\n}\n\n/**\n * JSON answer to a send whose `clientRequestId` is already stored for this\n * thread and actor. `outcome`: `completed` (a reply follows), `cancelled`\n * (stopped, no reply), or `in_progress` (still running, or waiting on\n * browser tools). A failed or abandoned turn answers 409 `turn_failed`.\n */\nexport interface AssistantDuplicateSendWire {\n duplicate: true;\n inProgress: boolean;\n outcome: 'completed' | 'cancelled' | 'in_progress';\n userMessage: AssistantMessageWire;\n assistantMessage?: AssistantMessageWire;\n messages: AssistantMessageWire[];\n}\n\n// ---------------------------------------------------------------------------\n// Defaults and helpers\n// ---------------------------------------------------------------------------\n\n/** Default assistant agent id. */\nexport const DEFAULT_ASSISTANT_AGENT_ID = 'smrt-assistant';\n/** Session key isolating the dock's session from other uses of the agent id. */\nexport const ASSISTANT_DOCK_SESSION_KEY = 'assistant-dock';\n/** Largest serialized `context` object a send or resume body may carry. */\nexport const MAX_ASSISTANT_REQUEST_CONTEXT_BYTES = 8 * 1024;\n/** Default JSON body cap. */\nexport const DEFAULT_ASSISTANT_MAX_BODY_BYTES = 1024 * 1024;\n/** Default attachment cap. */\nexport const DEFAULT_ASSISTANT_MAX_ATTACHMENT_BYTES = 10 * 1024 * 1024;\nconst MAX_TITLE_LENGTH = 200;\nconst MAX_ATTACHMENTS_PER_MESSAGE = 10;\nconst MAX_ID_LENGTH = 128;\nconst CLIENT_REQUEST_ID = /^[A-Za-z0-9_.:-]{1,128}$/;\n/** Default age after which an unanswered, unsettled send counts as failed. */\nexport const DEFAULT_ASSISTANT_ABANDONED_TURN_MS = 15 * 60 * 1000;\nconst GENERIC_ERROR = 'The assistant request failed.';\n\n/**\n * The default principal resolver: SvelteKit `event.locals` as populated by\n * `createSessionHandler` (`user`, `tenantId`). No user → `null` (401); a user\n * without a Profile or an active tenant is refused with 403.\n */\nexport function resolveAssistantPrincipalFromLocals(\n locals: unknown,\n): AssistantPrincipal | null {\n const record = (locals ?? {}) as {\n user?: { id?: unknown; profileId?: unknown } | null;\n tenantId?: unknown;\n };\n const user = record.user;\n const userId = nonEmpty(user?.id);\n if (!user || !userId) return null;\n const profileId = nonEmpty(user.profileId);\n if (!profileId) {\n throw new AssistantRouteError(\n 403,\n 'profile_required',\n 'Your account has no profile, so it cannot use the assistant.',\n );\n }\n const tenantId = nonEmpty(record.tenantId);\n if (!tenantId) {\n throw new AssistantRouteError(\n 403,\n 'tenant_required',\n 'Select an organization to use the assistant.',\n );\n }\n return { userId, profileId, tenantId };\n}\n\nfunction nonEmpty(value: unknown): string | null {\n return typeof value === 'string' && value.trim().length > 0 ? value : null;\n}\n\nfunction json(\n body: unknown,\n status = 200,\n headers?: Record<string, string>,\n): Response {\n const out = new Headers(headers);\n out.set('content-type', 'application/json; charset=utf-8');\n out.set('cache-control', 'no-store');\n return new Response(JSON.stringify(body), { status, headers: out });\n}\n\nfunction errorResponse(error: AssistantRouteError): Response {\n return json({ error: error.message, code: error.code }, error.status);\n}\n\nconst notFound = () =>\n new AssistantRouteError(404, 'not_found', 'Conversation not found.');\nconst badRequest = (message: string, code = 'invalid_request') =>\n new AssistantRouteError(400, code, message);\n\nasync function resolveValue<T>(\n value: AssistantRouteValue<T> | undefined,\n context: AssistantRequestContext,\n): Promise<T | undefined> {\n if (typeof value === 'function') {\n return (value as (c: AssistantRequestContext) => T | Promise<T>)(context);\n }\n return value;\n}\n\nlet configLoad: Promise<unknown> | undefined;\n\n/**\n * The default AI factory: the `smrt.config` `ai` block (plus its key\n * variable) through the shared resolver. Loads the config file once when no\n * one has; throws `AIProviderNotConfiguredError` (variable names only, never\n * values) when nothing is configured.\n */\nasync function configuredAssistantAI(): Promise<AIInterface> {\n if (getConfig() === null) {\n configLoad ??= loadConfig().catch((error: unknown) => {\n configLoad = undefined;\n throw error;\n });\n await configLoad;\n }\n const options = toAIClientOptions(resolveConfiguredAIProvider());\n // Imported per turn, as the dev routes do: mounting (and SvelteKit's\n // build-time route analysis) never loads a provider SDK.\n const { getAI } = await import('@happyvertical/ai');\n return getAI(options);\n}\n\n/**\n * Allow-listed names that neither `extraTools` nor the manifest catalog\n * provides. With `registeredOnly`, only names whose collection the registry\n * already knows: a class may not be registered yet when a route module is\n * imported (e.g. during SvelteKit's build analysis), so only those prove a\n * typo at mount.\n */\nfunction unprovidedToolNames(\n requested: readonly string[],\n offered: readonly ManifestTool[],\n registeredOnly: boolean,\n): string[] {\n const provided = new Set(offered.map((tool) => tool.slug));\n const missing = requested.filter((name) => !provided.has(name));\n if (!registeredOnly || missing.length === 0) return missing;\n // `all: true` only lists collections for this check; nothing it returns is\n // ever offered to a model.\n const collections = new Set(\n buildManifestToolCatalog({ all: true }).map((tool) => tool.collection),\n );\n return missing.filter((name) => {\n const dot = name.lastIndexOf('.');\n return dot > 0 && collections.has(name.slice(0, dot));\n });\n}\n\nasync function readBounded(request: Request, max: number): Promise<Uint8Array> {\n const declared = Number(request.headers.get('content-length'));\n const tooLarge = () =>\n new AssistantRouteError(\n 413,\n 'payload_too_large',\n 'The request is too large.',\n );\n if (Number.isFinite(declared) && declared > max) throw tooLarge();\n if (!request.body) return new Uint8Array();\n const reader = request.body.getReader();\n const chunks: Uint8Array[] = [];\n let total = 0;\n for (;;) {\n const { value, done } = await reader.read();\n if (done) break;\n total += value.byteLength;\n if (total > max) {\n await reader.cancel().catch(() => undefined);\n throw tooLarge();\n }\n chunks.push(value);\n }\n const out = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks) {\n out.set(chunk, offset);\n offset += chunk.byteLength;\n }\n return out;\n}\n\nasync function readJsonObject(\n request: Request,\n max: number,\n): Promise<Record<string, unknown>> {\n const type = request.headers.get('content-type') ?? '';\n if (!/^application\\/json\\b/i.test(type)) {\n throw new AssistantRouteError(415, 'unsupported_media_type', 'Send JSON.');\n }\n const bytes = await readBounded(request, max);\n let parsed: unknown;\n try {\n parsed = JSON.parse(new TextDecoder().decode(bytes));\n } catch {\n throw badRequest('The request body is not valid JSON.');\n }\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n throw badRequest('The request body must be a JSON object.');\n }\n return parsed as Record<string, unknown>;\n}\n\nfunction requireId(value: unknown, label: string): string {\n if (\n typeof value !== 'string' ||\n value.length === 0 ||\n value.length > MAX_ID_LENGTH\n ) {\n throw badRequest(`${label} is missing or invalid.`);\n }\n return value;\n}\n\nfunction originAllowed(\n request: Request,\n url: URL,\n trusted: readonly string[],\n): boolean {\n const allowed = new Set([url.origin, ...trusted]);\n const origin = request.headers.get('origin');\n if (origin) return allowed.has(origin);\n const site = request.headers.get('sec-fetch-site')?.toLowerCase();\n if (site) return site === 'same-origin';\n const referer = request.headers.get('referer');\n if (referer) {\n try {\n return allowed.has(new URL(referer).origin);\n } catch {\n return false;\n }\n }\n return false;\n}\n\nfunction toIso(value: unknown): string | null {\n if (value == null || value === '') return null;\n const date = value instanceof Date ? value : new Date(String(value));\n return Number.isNaN(date.getTime()) ? null : date.toISOString();\n}\n\nfunction parseObject(value: unknown): Record<string, unknown> | null {\n if (value && typeof value === 'object' && !Array.isArray(value)) {\n return value as Record<string, unknown>;\n }\n if (typeof value !== 'string' || value.length === 0) return null;\n try {\n const parsed = JSON.parse(value) as unknown;\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed)\n ? (parsed as Record<string, unknown>)\n : null;\n } catch {\n return null;\n }\n}\n\nfunction parseArray(value: unknown): unknown[] {\n if (Array.isArray(value)) return value;\n if (typeof value !== 'string' || value.length === 0) return [];\n try {\n const parsed = JSON.parse(value) as unknown;\n return Array.isArray(parsed) ? parsed : [];\n } catch {\n return [];\n }\n}\n\n/** Map a persisted thread to the wire. */\nexport function toAssistantThreadWire(thread: ChatThread): AssistantThreadWire {\n return {\n id: String(thread.id),\n title: thread.title ?? '',\n isResolved: Boolean(thread.isResolved),\n messageCount: Number(thread.messageCount) || 0,\n lastMessageAt: toIso(thread.lastMessageAt),\n };\n}\n\n/** Map a persisted message to the wire. */\nexport function toAssistantMessageWire(\n message: ChatMessage,\n): AssistantMessageWire {\n const metadata = parseObject(message.metadata);\n const toolCallData = parseObject(message.toolCallData);\n const attachments = parseArray(message.attachments)\n .map((entry): AssistantAttachmentRecord | null => {\n const a = entry as Record<string, unknown> | null;\n const id = nonEmpty(a?.id);\n const name = nonEmpty(a?.filename) ?? nonEmpty(a?.name);\n if (!id || !name) return null;\n return {\n id,\n name,\n contentType: nonEmpty(a?.contentType) ?? 'application/octet-stream',\n size: Number(a?.size) || 0,\n ...(nonEmpty(a?.url) ? { url: a?.url as string } : {}),\n };\n })\n .filter((a): a is AssistantAttachmentRecord => a !== null);\n const clientRequestId = nonEmpty(metadata?.clientRequestId);\n return {\n id: String(message.id),\n threadId: message.threadId ?? '',\n content: message.content ?? '',\n role: message.role,\n createdAt: toIso(message.created_at) ?? new Date(0).toISOString(),\n ...(attachments.length > 0 ? { attachments } : {}),\n ...(toolCallData ? { toolCallData } : {}),\n ...(clientRequestId ? { clientRequestId } : {}),\n };\n}\n\nfunction isAuthorizationFailure(error: unknown): boolean {\n const message = error instanceof Error ? error.message : '';\n return /authorization denied|not found|not active/i.test(message);\n}\n\n// ---------------------------------------------------------------------------\n// mountAssistantRoutes\n// ---------------------------------------------------------------------------\n\ntype RouteMatch =\n | { route: 'threads' }\n | { route: 'thread-messages'; threadId: string }\n | { route: 'thread-resume'; threadId: string }\n | { route: 'attachments' }\n | { route: 'actions'; phase: 'preview' | 'apply' };\n\nconst ROUTE_METHODS: Record<RouteMatch['route'], readonly string[]> = {\n threads: ['GET', 'POST'],\n 'thread-messages': ['GET', 'POST'],\n 'thread-resume': ['POST'],\n attachments: ['POST'],\n actions: ['POST'],\n};\n\nfunction matchRoute(segments: string[]): RouteMatch | null {\n const [a, b, c, ...rest] = segments;\n if (rest.length > 0) return null;\n if (a === 'threads' && b === undefined) return { route: 'threads' };\n if (a === 'threads' && b && c === 'messages') {\n return { route: 'thread-messages', threadId: b };\n }\n if (a === 'threads' && b && c === 'resume') {\n return { route: 'thread-resume', threadId: b };\n }\n if (a === 'attachments' && b === undefined) return { route: 'attachments' };\n if (a === 'actions' && (b === 'preview' || b === 'apply') && !c) {\n return { route: 'actions', phase: b };\n }\n return null;\n}\n\n/**\n * Build the AssistantDock's server routes for one SvelteKit rest route\n * (e.g. `src/routes/api/assistant/[...path]/+server.ts`).\n */\nexport function mountAssistantRoutes(\n options: MountAssistantRoutesOptions = {},\n): AssistantRoutes {\n if ('ai' in options && options.ai == null) {\n throw new Error(\n 'mountAssistantRoutes: `ai` was passed but is empty. Pass an AI client ' +\n 'or a factory, or omit `ai` to use the smrt.config `ai` block.',\n );\n }\n const aiSource = options.ai ?? configuredAssistantAI;\n if (\n options.tools === undefined &&\n !options.actions &&\n Array.isArray(options.allowedTools) &&\n typeof options.extraTools !== 'function'\n ) {\n // Fail fast on a name nothing can provide (a typo), when knowable now.\n const extra = new Set(\n ((options.extraTools ?? []) as readonly PrincipalTool[]).map(\n (tool) => tool.slug,\n ),\n );\n const requested = (options.allowedTools as readonly unknown[]).filter(\n (name): name is string =>\n typeof name === 'string' && name.length > 0 && !extra.has(name),\n );\n if (requested.length > 0) {\n const unknown = unprovidedToolNames(\n requested,\n buildManifestToolCatalog({ allowedTools: requested }),\n true,\n );\n if (unknown.length > 0) {\n throw new Error(\n `mountAssistantRoutes: allowedTools names ${unknown.join(', ')}, ` +\n 'which no manifest operation or extraTools entry provides.',\n );\n }\n }\n }\n const agentId = options.agentId ?? DEFAULT_ASSISTANT_AGENT_ID;\n const dockSessionKey =\n options.sessionKey === undefined\n ? ASSISTANT_DOCK_SESSION_KEY\n : options.sessionKey;\n if (dockSessionKey !== null && !dockSessionKey.trim()) {\n throw new Error(\n 'mountAssistantRoutes: sessionKey must be a non-empty string or null.',\n );\n }\n const paramName = options.paramName ?? 'path';\n const maxBody = options.maxBodyBytes ?? DEFAULT_ASSISTANT_MAX_BODY_BYTES;\n const maxContent = options.maxContentLength ?? MAX_CHAT_STREAM_CONTENT_LENGTH;\n const historyLimit = Math.max(0, options.historyLimit ?? 20);\n const checkOrigin = options.checkOrigin ?? true;\n const trustedOrigins = options.trustedOrigins ?? [];\n const resolvePrincipal: AssistantPrincipalResolver =\n options.resolvePrincipal ??\n ((event) => resolveAssistantPrincipalFromLocals(event.locals));\n const logError: AssistantTurnErrorLogger =\n options.onError ??\n ((error) => {\n // biome-ignore lint/suspicious/noConsole: server-side default for detail withheld from the browser; hosts pass `onError`\n console.error('[smrt-chat] assistant route failed:', error);\n });\n const safeLog = (error: unknown) => {\n try {\n logError(error);\n } catch {\n // Logging never breaks a response.\n }\n };\n const abandonedTurnMs =\n options.abandonedTurnMs ?? DEFAULT_ASSISTANT_ABANDONED_TURN_MS;\n const turnStartTimeoutMs =\n options.turnStartTimeoutMs ?? DEFAULT_ASSISTANT_TURN_START_TIMEOUT_MS;\n\n const segmentsOf = (event: AssistantRouteEvent): string[] | null => {\n const param = event.params?.[paramName];\n let path: string | undefined =\n typeof param === 'string' ? param : undefined;\n if (path === undefined && options.basePath) {\n const base = options.basePath.replace(/\\/+$/, '');\n const pathname = event.url.pathname;\n if (pathname === base || pathname.startsWith(`${base}/`)) {\n path = pathname.slice(base.length);\n }\n }\n if (path === undefined) return null;\n return path.split('/').filter((segment) => segment.length > 0);\n };\n\n const runtime = options.runtime;\n const dbOption: AssistantRouteValue<SmrtClassOptions['db']> | undefined =\n options.db !== undefined || !runtime\n ? options.db\n : () => runtime.databaseConfig();\n\n // The database is resolved once per context (a resolver may return the\n // request's own RLS transaction) and never shared across requests. A\n // streamed turn's own lifetime gets a fresh context, so it resolves anew.\n const requestDbs = new WeakMap<\n AssistantRequestContext,\n Promise<SmrtClassOptions['db'] | undefined>\n >();\n const dbFor = (context: AssistantRequestContext) => {\n let db = requestDbs.get(context);\n if (!db) {\n db = resolveValue(dbOption, context);\n requestDbs.set(context, db);\n }\n return db;\n };\n\n const chatFor = async (context: AssistantRequestContext) => {\n const db = await dbFor(context);\n const chat = await ChatService.create({\n tenantId: context.principal.tenantId,\n ...(db ? { db } : {}),\n } as Parameters<typeof ChatService.create>[0]);\n await chat.initialize();\n return chat;\n };\n\n const findSession = async (\n chat: ChatService,\n principal: AssistantPrincipal,\n ): Promise<AgentSession | null> => {\n const sessions = await chat.findActiveAgentSessions({\n tenantId: principal.tenantId,\n agentId,\n participantProfileId: principal.profileId,\n });\n return (\n sessions.find(\n (session) =>\n session.getSessionKey() === dockSessionKey &&\n session.participantProfileId === principal.profileId &&\n session.tenantId === principal.tenantId &&\n Boolean(session.chatRoomId),\n ) ?? null\n );\n };\n\n const requireOwnThread = async (\n chat: ChatService,\n principal: AssistantPrincipal,\n session: AgentSession | null,\n threadId: string,\n ): Promise<ChatThread> => {\n if (!session?.chatRoomId) throw notFound();\n const thread = await chat.getThread({\n threadId,\n tenantId: principal.tenantId,\n });\n if (\n !thread ||\n thread.roomId !== session.chatRoomId ||\n thread.tenantId !== principal.tenantId\n ) {\n throw notFound();\n }\n return thread;\n };\n\n const allowedToolsFor = async (context: AssistantRequestContext) => {\n const list = await resolveValue(options.allowedTools, context);\n return Array.isArray(list)\n ? list.filter(\n (tool): tool is string => typeof tool === 'string' && tool.length > 0,\n )\n : [];\n };\n\n const resolveModel = (requested: unknown): string | undefined => {\n if (requested === undefined || requested === null || requested === '') {\n return options.defaultModel;\n }\n if (typeof requested !== 'string' || requested.length > MAX_ID_LENGTH) {\n throw badRequest('The model is invalid.', 'model_not_allowed');\n }\n if (!options.models || options.models.length === 0) {\n return options.defaultModel;\n }\n if (!options.models.some((model) => model.id === requested)) {\n throw badRequest('That model is not available.', 'model_not_allowed');\n }\n return requested;\n };\n\n const resolveAI = async (\n context: AssistantRequestContext,\n model: string | undefined,\n ): Promise<AIInterface> => {\n try {\n const ai =\n typeof aiSource === 'function'\n ? await aiSource({ ...context, ...(model ? { model } : {}) })\n : aiSource;\n if (!ai || typeof ai.chat !== 'function') {\n throw new Error('The AI factory returned no client.');\n }\n return ai;\n } catch (error) {\n safeLog(error);\n throw new AssistantRouteError(\n 503,\n 'assistant_unavailable',\n 'The assistant is unavailable right now. Please try again later.',\n );\n }\n };\n\n /**\n * The manifest tools a turn is offered: `tools` when given, otherwise the\n * catalog entries for the allow-listed names `extraTools` does not serve.\n * A name nothing provides refuses the turn (503, detail to `onError`)\n * unless `actions` is set (action tools are allow-listed too).\n */\n const manifestToolsFor = async (\n context: AssistantRequestContext,\n allowedTools: readonly string[],\n extraTools: readonly PrincipalTool[],\n db: SmrtClassOptions['db'] | undefined,\n ): Promise<readonly ManifestTool[]> => {\n if (options.tools !== undefined) {\n return (await resolveValue(options.tools, context)) ?? [];\n }\n const extra = new Set(extraTools.map((tool) => tool.slug));\n const requested = allowedTools.filter((name) => !extra.has(name));\n if (requested.length === 0) return [];\n const tools = buildManifestToolCatalog({\n ...(db ? { db } : {}),\n allowedTools: requested,\n });\n if (!options.actions) {\n const unknown = unprovidedToolNames(requested, tools, false);\n if (unknown.length > 0) {\n safeLog(\n new Error(\n `mountAssistantRoutes: allowedTools names ${unknown.join(', ')}, ` +\n 'which no manifest operation or extraTools entry provides.',\n ),\n );\n throw new AssistantRouteError(\n 503,\n 'assistant_unavailable',\n 'The assistant is unavailable right now. Please try again later.',\n );\n }\n }\n return tools;\n };\n\n /** The store suspended turns of this actor's session wait in. */\n const continuationStoreFor = (\n context: AssistantRequestContext,\n chat: ChatService,\n session: AgentSession,\n ): AssistantContinuationStore => {\n if (options.continuations) {\n return options.continuations({ ...context, session });\n }\n const { principal } = context;\n return createSessionContinuationStore(async () => {\n const current = await chat.getAgentSession({\n agentSessionId: String(session.id),\n tenantId: principal.tenantId,\n });\n return current &&\n current.participantProfileId === principal.profileId &&\n current.isActive()\n ? current\n : null;\n });\n };\n\n /**\n * Attach a body's optional `context` object to the request context: a plain\n * JSON object (not an array) of bounded size, else 400.\n */\n const withRequestContext = (\n context: AssistantRequestContext,\n value: unknown,\n ): AssistantRequestContext => {\n if (value === undefined || value === null) return context;\n if (typeof value !== 'object' || Array.isArray(value)) {\n throw badRequest('context must be an object.', 'invalid_context');\n }\n if (\n new TextEncoder().encode(JSON.stringify(value)).length >\n MAX_ASSISTANT_REQUEST_CONTEXT_BYTES\n ) {\n throw badRequest(\n `context is limited to ${MAX_ASSISTANT_REQUEST_CONTEXT_BYTES} bytes.`,\n 'invalid_context',\n );\n }\n return {\n ...context,\n requestContext: value as Readonly<Record<string, unknown>>,\n };\n };\n\n /**\n * The request-bound part of a turn (model, allow-list, AI client, abort),\n * resolved once per request before anything is stored.\n */\n const prepareTurn = async (\n context: AssistantRequestContext,\n body: Record<string, unknown>,\n ) => {\n const model = resolveModel(body.model);\n const allowedTools = await allowedToolsFor(context);\n const clientTools = sanitizeClientToolDeclarations(\n body.clientTools,\n options.clientToolAllowList ?? [],\n );\n const ai = await resolveAI(context, model);\n // The turn stops when the request is aborted OR the response body is\n // cancelled (a client that left), whichever the adapter reports.\n const abort = new AbortController();\n const requestSignal = context.event.request.signal;\n if (requestSignal.aborted) abort.abort();\n else {\n requestSignal.addEventListener('abort', () => abort.abort(), {\n once: true,\n });\n }\n return { abort, ai, model, allowedTools, clientTools };\n };\n type PreparedTurn = Awaited<ReturnType<typeof prepareTurn>>;\n\n /**\n * Everything a turn needs that is bound to a database: built for the\n * request (where every refusal happens), and again inside a streamed\n * turn's own database lifetime (`mirror: false`: the request already\n * mirrored the allow-list).\n */\n const turnSetup = async (\n context: AssistantRequestContext,\n chat: ChatService,\n session: AgentSession,\n thread: ChatThread,\n prepared: PreparedTurn,\n mirror = true,\n ) => {\n const { principal } = context;\n const { abort, ai, model, allowedTools, clientTools } = prepared;\n const [extraTools, systemPrompt, db] = await Promise.all([\n resolveValue(options.extraTools, context),\n resolveValue(options.systemPrompt, context),\n dbFor(context),\n ]);\n const tools = await manifestToolsFor(\n context,\n allowedTools,\n extraTools ?? [],\n db,\n );\n // Mirror the allow-list onto the session so the authoring gate\n // (`sendAgentReply`) agrees with the loop's offer gate.\n if (options.authorInvocation && mirror) {\n const current = session.getAllowedTools();\n if (\n current.length !== allowedTools.length ||\n current.some((tool, index) => tool !== allowedTools[index])\n ) {\n await chat.updateAgentSessionConfig({\n agentSessionId: String(session.id),\n actorProfileId: principal.profileId,\n tenantId: principal.tenantId,\n allowedTools,\n });\n }\n }\n const continuations = continuationStoreFor(context, chat, session);\n return {\n abort,\n ai,\n model,\n clientTools,\n continuations,\n continuationKey: String(thread.id),\n turn: {\n ai,\n principal: {\n runAsUserId: principal.userId,\n tenantId: principal.tenantId,\n allowedTools,\n },\n ...(db ? { db } : {}),\n ...(systemPrompt ? { systemPrompt } : {}),\n extraTools: [...(extraTools ?? [])],\n tools: [...tools],\n clientTools,\n continuations,\n continuationKey: String(thread.id),\n ...(model ? { model } : {}),\n ...(options.maxSteps !== undefined\n ? { maxSteps: options.maxSteps }\n : {}),\n ...(options.maxTurnTokens !== undefined\n ? { maxTurnTokens: options.maxTurnTokens }\n : {}),\n ...(options.maxTurnMs !== undefined\n ? { maxTurnMs: options.maxTurnMs }\n : {}),\n ...(options.requestTimeoutMs !== undefined\n ? { requestTimeoutMs: options.requestTimeoutMs }\n : {}),\n ...(options.emptyAnswerFallback !== undefined\n ? { emptyAnswerFallback: options.emptyAnswerFallback }\n : {}),\n ...(options.maxTokens !== undefined\n ? { maxTokens: options.maxTokens }\n : {}),\n ...(options.temperature !== undefined\n ? { temperature: options.temperature }\n : {}),\n ...(principal.permissions\n ? { permissions: principal.permissions }\n : {}),\n ...(options.audit ? { audit: options.audit } : {}),\n ...(options.postgresRls !== undefined\n ? { postgresRls: options.postgresRls }\n : {}),\n onBehalfOfUserId: principal.userId,\n signal: abort.signal,\n ...(options.onUsage\n ? {\n onUsage: (usage: ToolLoopUsage) => {\n try {\n options.onUsage?.(usage, context);\n } catch (error) {\n safeLog(error);\n }\n },\n }\n : {}),\n ...(options.describeTool ? { describeTool: options.describeTool } : {}),\n onError: safeLog,\n serializeMessage: (message: unknown) =>\n toAssistantMessageWire(message as ChatMessage),\n author: {\n chatService: chat,\n agentSessionId: String(session.id),\n tenantId: principal.tenantId,\n threadId: String(thread.id),\n ...(options.authorInvocation\n ? { authorInvocation: options.authorInvocation }\n : {}),\n },\n },\n };\n };\n\n const stream = (\n events: AsyncGenerator<AssistantTurnEvent<AssistantMessageWire>, unknown>,\n abort: AbortController,\n ) =>\n createAssistantTurnResponse(events, {\n ...(options.heartbeatMs !== undefined\n ? { heartbeatMs: options.heartbeatMs }\n : {}),\n onError: safeLog,\n // A cancelled body cancels the turn, even when the adapter never\n // aborts the request signal on disconnect.\n onCancel: () => abort.abort(),\n });\n\n type TurnSetup = Awaited<ReturnType<typeof turnSetup>>;\n type TurnEvent = AssistantTurnEvent<AssistantMessageWire>;\n\n /**\n * Answer with a turn's events. `start` runs the turn on a chat service\n * and setup bound to the database it may use. Outside an RLS request\n * transaction that is the request's own, and the turn is detached. Inside\n * one, the transaction commits when the handler returns, so the turn never\n * keeps it: with `runtime` it runs, once that transaction has ended, in its\n * own principal-bound transaction (rebinding chat, tools and continuations\n * there, and first checking that `durableSend`, the stored user message,\n * committed); without `runtime` it runs to completion before returning.\n */\n const respondWithTurn = async (\n context: AssistantRequestContext,\n chat: ChatService,\n session: AgentSession,\n thread: ChatThread,\n prepared: PreparedTurn,\n setup: TurnSetup,\n first: TurnEvent | null,\n start: (\n chat: ChatService,\n setup: TurnSetup,\n ) => AsyncGenerator<TurnEvent, unknown>,\n durableSend?: string,\n ): Promise<Response> => {\n const transaction = requestTransaction();\n if (!transaction) {\n return stream(\n detached(first, pump(start(chat, setup))).events,\n prepared.abort,\n );\n }\n const isActive = transaction.isActive;\n if (!runtime || typeof isActive !== 'function') {\n const turn = detached(first, pump(start(chat, setup)));\n await turn.settled;\n return stream(turn.events, prepared.abort);\n }\n const { principal } = context;\n // The request's authority, frozen before the response returns (an empty\n // set stays empty): the principal's snapshot, else the permissions the\n // request's RLS context published. Always the cap, so the turn runs with\n // (this snapshot ∩ live at bind) and never gains a later grant.\n const bound = {\n id: principal.userId,\n tenantId: principal.tenantId,\n scopes: Object.freeze([\n ...(principal.permissions ??\n getCurrentSessionPermissionContext()?.permissions ??\n []),\n ]),\n };\n /** Record the stored send as failed, in a fresh lifetime. */\n const settleFailed = async (messageId: string, onlyIfStored = false) => {\n try {\n await runtime.runAsPrincipal(bound, async () => {\n const settleChat = await chatFor({ event: context.event, principal });\n if (onlyIfStored) {\n const stored = await settleChat.getThreadMessageReplies({\n threadId: String(thread.id),\n messageId,\n actorProfileId: principal.profileId,\n tenantId: principal.tenantId,\n });\n if (!stored?.message) return;\n }\n await settleOutcome(\n settleChat,\n principal,\n String(thread.id),\n messageId,\n )('failed');\n });\n } catch (settleError) {\n safeLog(settleError);\n }\n };\n /** After a timed-out start: settle the send once its request ends. */\n const settleLateSend = async (\n requestActive: () => boolean,\n messageId: string,\n ) => {\n try {\n if (await requestEnded(requestActive, abandonedTurnMs)) {\n await settleFailed(messageId, true);\n } else {\n safeLog(\n new Error(\n 'mountAssistantRoutes: the request transaction never ended; its send was not settled.',\n ),\n );\n }\n } catch (error) {\n safeLog(error);\n }\n };\n const ownLifetime = async (emit: (event: TurnEvent) => void) => {\n // Events after which the browser acts on stored state (resume, retry,\n // reload) wait until this lifetime has committed, with all that follow.\n const held: TurnEvent[] = [];\n let sendConfirmed = false;\n const requestActive = () => isActive.call(transaction) as boolean;\n try {\n if (!(await requestEnded(requestActive, turnStartTimeoutMs))) {\n // Never abandon the send: once the request's transaction ends,\n // settle it if it committed (a rollback stored nothing).\n if (durableSend) void settleLateSend(requestActive, durableSend);\n throw new Error(\n 'mountAssistantRoutes: the request transaction did not end; the turn did not run.',\n );\n }\n await runtime.runAsPrincipal(bound, async (granted) => {\n // The turn's authority is the bound principal's effective set, as\n // narrowed at bind time; the pre-bind snapshot never reaches it.\n const turnContext: AssistantRequestContext = {\n event: context.event,\n ...(context.requestContext\n ? { requestContext: context.requestContext }\n : {}),\n principal: {\n ...principal,\n permissions: effectiveScopes(granted, bound.scopes),\n },\n };\n const turnChat = await chatFor(turnContext);\n if (durableSend) {\n const stored = await turnChat.getThreadMessageReplies({\n threadId: String(thread.id),\n messageId: durableSend,\n actorProfileId: principal.profileId,\n tenantId: principal.tenantId,\n });\n if (!stored?.message) {\n throw new Error(\n 'mountAssistantRoutes: the send was not stored; the turn did not run.',\n );\n }\n sendConfirmed = true;\n }\n const turnSetupInLifetime = await turnSetup(\n turnContext,\n turnChat,\n session,\n thread,\n prepared,\n false,\n );\n for await (const event of start(turnChat, turnSetupInLifetime)) {\n if (held.length > 0 || HELD_UNTIL_COMMIT.has(event.type)) {\n held.push(event);\n } else {\n emit(event);\n }\n }\n });\n for (const event of held) emit(event);\n } catch (error) {\n // Nothing the turn wrote committed: settle the send as failed (in a\n // fresh lifetime) so it does not wait out `abandonedTurnMs`.\n safeLog(error);\n if (durableSend && sendConfirmed) await settleFailed(durableSend);\n emit({\n type: 'error',\n error: ASSISTANT_TURN_GENERIC_ERROR,\n code: 'internal_error',\n });\n emit({\n type: 'status',\n status: { state: 'error', label: ASSISTANT_TURN_GENERIC_ERROR },\n });\n }\n };\n return stream(detached(first, ownLifetime).events, prepared.abort);\n };\n\n // ---- route bodies ------------------------------------------------------\n\n const listThreads = async (context: AssistantRequestContext) => {\n const chat = await chatFor(context);\n const session = await findSession(chat, context.principal);\n if (!session?.chatRoomId) return json({ items: [] });\n const threads = await chat.listRoomThreads({\n roomId: session.chatRoomId,\n actorProfileId: context.principal.profileId,\n tenantId: context.principal.tenantId,\n });\n return json({ items: threads.map(toAssistantThreadWire) });\n };\n\n const createThread = async (context: AssistantRequestContext) => {\n const body = await readJsonObject(context.event.request, maxBody);\n const title = typeof body.title === 'string' ? body.title.trim() : '';\n if (!title || title.length > MAX_TITLE_LENGTH) {\n throw badRequest(\n `A title of 1 to ${MAX_TITLE_LENGTH} characters is required.`,\n );\n }\n const { principal } = context;\n const chat = await chatFor(context);\n const allowedTools = await allowedToolsFor(context);\n const { session } = await chat.createAgentSession({\n tenantId: principal.tenantId,\n agentId,\n actorProfileId: principal.profileId,\n allowedTools,\n sessionKey: dockSessionKey,\n });\n if (!session.chatRoomId) throw new Error('Assistant session has no room');\n const thread = await chat.startThread({\n tenantId: principal.tenantId,\n roomId: session.chatRoomId,\n actorProfileId: principal.profileId,\n title,\n });\n return json({ thread: toAssistantThreadWire(thread) }, 201);\n };\n\n const loadMessages = async (\n context: AssistantRequestContext,\n threadId: string,\n ) => {\n const { principal } = context;\n const chat = await chatFor(context);\n const session = await findSession(chat, principal);\n await requireOwnThread(chat, principal, session, threadId);\n const messages = await chat.getThreadMessages({\n threadId,\n actorProfileId: principal.profileId,\n tenantId: principal.tenantId,\n limit: 200,\n });\n return json({ items: messages.map(toAssistantMessageWire) });\n };\n\n /**\n * The answer for a send whose reservation (its deterministic user-message\n * id) is already stored, or `null` when it is not. Replies are the\n * messages linked to the send (`replyToMessageId`), never \"whatever\n * follows it\", so overlapping sends in one thread keep their own replies.\n * Legacy replies written without the link are not attributed.\n */\n const reservationAnswer = async (\n context: AssistantRequestContext,\n chat: ChatService,\n session: AgentSession,\n threadId: string,\n messageId: string,\n ): Promise<Response | null> => {\n const { principal } = context;\n const stored = await chat.getThreadMessageReplies({\n threadId,\n messageId,\n actorProfileId: principal.profileId,\n tenantId: principal.tenantId,\n });\n const anchor = stored?.message;\n if (\n !stored ||\n anchor?.role !== 'user' ||\n anchor.senderProfileId !== principal.profileId\n ) {\n return null;\n }\n const userMessage = toAssistantMessageWire(anchor);\n const after = stored.replies.map(toAssistantMessageWire);\n const assistantMessage = [...after]\n .reverse()\n .find((message) => message.role === 'assistant');\n const answer = (\n outcome: AssistantDuplicateSendWire['outcome'],\n ): Response => {\n const body: AssistantDuplicateSendWire = {\n duplicate: true,\n inProgress: outcome === 'in_progress',\n outcome,\n userMessage,\n ...(assistantMessage ? { assistantMessage } : {}),\n messages: after,\n };\n return json(body, 200);\n };\n const turnFailed = () =>\n new AssistantRouteError(\n 409,\n 'turn_failed',\n 'The assistant could not answer this message. Send it again.',\n );\n const metadata = parseObject(anchor.metadata) ?? {};\n const recorded = metadata.turnOutcome;\n // A linked assistant reply is the turn's final answer (the runner\n // stores exactly one, when the turn finishes; tool results are role\n // `tool`; a suspension stores none), so it is completion evidence even\n // if the outcome marker is stale or its write was lost.\n if (assistantMessage) return answer('completed');\n if (recorded === 'failed') throw turnFailed();\n if (recorded === 'cancelled') return answer('cancelled');\n if (recorded === 'completed') return answer('completed');\n if (recorded === 'suspended') {\n // Waiting on the browser: alive exactly as long as its continuation.\n const continuationId = nonEmpty(metadata.turnContinuationId);\n if (!continuationId) throw turnFailed();\n const store = continuationStoreFor(context, chat, session);\n const alive = store.has\n ? await store.has(threadId, continuationId)\n : Date.now() - new Date(String(metadata.turnSettledAt)).getTime() <=\n DEFAULT_CONTINUATION_TTL_MS;\n if (!alive) throw turnFailed();\n return answer('in_progress');\n }\n // Not settled yet (no marker), or a resumed leg running: abandoned once\n // older than `abandonedTurnMs`, measured from when it last started.\n const since =\n recorded === 'running' ? metadata.turnSettledAt : anchor.created_at;\n const age = Date.now() - new Date(String(since)).getTime();\n if (Number.isFinite(age) && age > abandonedTurnMs) throw turnFailed();\n return answer('in_progress');\n };\n\n /** Record the turn's outcome on its send; never fails the response. */\n const settleOutcome =\n (\n chat: ChatService,\n principal: AssistantPrincipal,\n threadId: string,\n messageId: string,\n ) =>\n async (\n outcome: ChatClientRequestOutcome,\n settings: { quiet?: boolean } = {},\n ) => {\n try {\n await chat.recordClientRequestOutcome({\n tenantId: principal.tenantId,\n threadId,\n messageId,\n actorProfileId: principal.profileId,\n outcome,\n resumedFrom: null,\n });\n } catch (error) {\n // `quiet`: the send may never have been stored.\n if (!settings.quiet) safeLog(error);\n }\n };\n\n /**\n * The turn runner's own lifecycle reports (`onState`), recorded on the send\n * they belong to. The send comes from the runner: the user message for a\n * first leg, the consumed continuation for a resumed one — never from the\n * request. Writes are monotonic compare-and-set (`ChatService`).\n */\n const recordTurnState =\n (chat: ChatService, principal: AssistantPrincipal, threadId: string) =>\n async (state: AssistantTurnState): Promise<boolean> => {\n // A continuation without an origin has no send to record against.\n if (!state.originMessageId) return true;\n return chat.recordClientRequestOutcome({\n tenantId: principal.tenantId,\n threadId,\n messageId: state.originMessageId,\n actorProfileId: principal.profileId,\n outcome: state.state,\n resumedFrom: state.resumedFrom,\n continuationId: state.continuationId ?? null,\n });\n };\n\n const sendMessage = async (\n baseContext: AssistantRequestContext,\n threadId: string,\n ) => {\n const body = await readJsonObject(baseContext.event.request, maxBody);\n const context = withRequestContext(baseContext, body.context);\n const content = typeof body.content === 'string' ? body.content.trim() : '';\n if (!content) throw badRequest('The message is empty.', 'empty_message');\n if (content.length > maxContent) {\n throw new AssistantRouteError(\n 413,\n 'message_too_long',\n `Messages are limited to ${maxContent} characters.`,\n );\n }\n const clientRequestId = body.clientRequestId;\n if (\n typeof clientRequestId !== 'string' ||\n !CLIENT_REQUEST_ID.test(clientRequestId)\n ) {\n throw badRequest('clientRequestId is missing or invalid.');\n }\n const references = body.attachments;\n if (\n references !== undefined &&\n references !== null &&\n (!Array.isArray(references) ||\n references.length > MAX_ATTACHMENTS_PER_MESSAGE)\n ) {\n throw badRequest('attachments must be a short list.');\n }\n const hasAttachments = Array.isArray(references) && references.length > 0;\n if (hasAttachments && !options.attachments) {\n throw badRequest(\n 'This assistant does not accept attachments.',\n 'attachments_unsupported',\n );\n }\n // Validate the model before any write.\n resolveModel(body.model);\n\n const { principal } = context;\n const chat = await chatFor(context);\n const session = await findSession(chat, principal);\n const thread = await requireOwnThread(chat, principal, session, threadId);\n if (!session?.chatRoomId) throw notFound();\n\n // The reservation is the user message's deterministic primary key, scoped\n // to tenant, room, thread, actor and clientRequestId: the database's\n // primary-key uniqueness makes it atomic and durable across replicas.\n const reservationId = clientRequestMessageId({\n tenantId: principal.tenantId,\n roomId: session.chatRoomId,\n threadId,\n actorProfileId: principal.profileId,\n clientRequestId,\n });\n const known = await reservationAnswer(\n context,\n chat,\n session,\n threadId,\n reservationId,\n );\n if (known) return known;\n\n let attachments: ChatMessageAttachment[] | null = null;\n if (hasAttachments && options.attachments) {\n const verified = await options.attachments.verify(\n references as unknown[],\n context,\n );\n if (!verified || verified.length !== (references as unknown[]).length) {\n throw badRequest(\n 'An attachment could not be found.',\n 'attachment_not_found',\n );\n }\n attachments = verified.map((record) => ({\n id: record.id,\n filename: record.name,\n contentType: record.contentType,\n size: record.size,\n ...(record.url ? { url: record.url } : {}),\n }));\n }\n\n // Everything that can refuse (the AI factory's 503 included) runs before\n // the reservation, so a refused send stores nothing.\n const prepared = await prepareTurn(context, body);\n const setup = await turnSetup(context, chat, session, thread, prepared);\n\n // A failure after the reservation may have been stored settles it as\n // failed (a no-op when nothing was stored), so a retry is told so at\n // once instead of waiting out `abandonedTurnMs`.\n const recordFailure = settleOutcome(\n chat,\n principal,\n threadId,\n reservationId,\n );\n let userMessage: ChatMessage;\n try {\n userMessage = await chat.sendMessage({\n tenantId: principal.tenantId,\n roomId: session.chatRoomId,\n threadId,\n actorProfileId: principal.profileId,\n agentSessionId: String(session.id),\n content,\n clientRequestId,\n attachments,\n });\n } catch (error) {\n if (error instanceof ChatClientRequestConflictError) {\n // Another request (any replica) took this reservation first.\n const answer = await reservationAnswer(\n context,\n chat,\n session,\n threadId,\n reservationId,\n );\n if (answer) return answer;\n } else {\n await recordFailure('failed', { quiet: true });\n }\n throw error;\n }\n const userWire = toAssistantMessageWire(userMessage);\n\n try {\n const history: AIMessage[] = [];\n if (historyLimit > 0) {\n const earlier = await chat.getThreadMessages({\n threadId,\n actorProfileId: principal.profileId,\n tenantId: principal.tenantId,\n limit: historyLimit + 1,\n });\n for (const message of earlier) {\n if (String(message.id) === userWire.id) continue;\n if (message.role !== 'user' && message.role !== 'assistant') {\n continue;\n }\n if (!message.content) continue;\n // A fallback reply is not a model answer: never replay it as one\n // (#4379); a fixed marker keeps the roles alternating.\n history.push({\n role: message.role,\n content:\n message.role === 'assistant' && isFallbackReply(message)\n ? FALLBACK_HISTORY_PLACEHOLDER\n : message.content,\n });\n }\n }\n\n const transcript = history.slice(-historyLimit);\n return await respondWithTurn(\n context,\n chat,\n session,\n thread,\n prepared,\n setup,\n { type: 'message', message: userWire },\n (turnChat, turnSetupFor) =>\n runAssistantTurn<AssistantMessageWire>({\n ...turnSetupFor.turn,\n history: transcript,\n userMessage: content,\n // Every reply links to this send, and a suspension keeps it.\n originMessageId: userWire.id,\n onState: recordTurnState(turnChat, principal, threadId),\n }),\n userWire.id,\n );\n } catch (error) {\n await recordFailure('failed');\n throw error;\n }\n };\n\n const resumeTurn = async (\n baseContext: AssistantRequestContext,\n threadId: string,\n ) => {\n const body = await readJsonObject(baseContext.event.request, maxBody);\n const context = withRequestContext(baseContext, body.context);\n const continuationId = requireId(body.continuationId, 'continuationId');\n if (\n !Array.isArray(body.results) ||\n body.results.length > MAX_CLIENT_TOOLS\n ) {\n throw badRequest('results must be a list of tool results.');\n }\n const results: ClientToolResultInput[] = body.results.map((raw) => {\n const entry = raw as Record<string, unknown> | null;\n if (!entry || typeof entry !== 'object') {\n throw badRequest('Each result must be an object.');\n }\n const id = requireId(entry.id, 'result id');\n if (typeof entry.ok !== 'boolean') {\n throw badRequest('Each result needs an ok flag.');\n }\n const result =\n typeof entry.result === 'string'\n ? entry.result.slice(0, MAX_CLIENT_TOOL_RESULT_CHARS)\n : undefined;\n const error =\n typeof entry.error === 'string' ? entry.error.slice(0, 500) : undefined;\n return {\n id,\n ok: entry.ok,\n ...(result !== undefined ? { result } : {}),\n ...(error !== undefined ? { error } : {}),\n };\n });\n resolveModel(body.model);\n\n const { principal } = context;\n const chat = await chatFor(context);\n const session = await findSession(chat, principal);\n const thread = await requireOwnThread(chat, principal, session, threadId);\n if (!session) throw notFound();\n const prepared = await prepareTurn(context, body);\n const setup = await turnSetup(context, chat, session, thread, prepared);\n // The send this leg belongs to comes only from the continuation it\n // consumes (stored server-side when the turn suspended), reported by the\n // runner; a request's `clientRequestId` is ignored here. A missing,\n // foreign or expired continuation is never consumed, so it changes no\n // send.\n return respondWithTurn(\n context,\n chat,\n session,\n thread,\n prepared,\n setup,\n null,\n (turnChat, turnSetupFor) =>\n runAssistantTurn<AssistantMessageWire>({\n ...turnSetupFor.turn,\n resume: { continuationId, results },\n onState: recordTurnState(turnChat, principal, threadId),\n }),\n );\n };\n\n const uploadAttachment = async (context: AssistantRequestContext) => {\n const storage = options.attachments;\n if (!storage) {\n throw new AssistantRouteError(\n 404,\n 'attachments_unsupported',\n 'This assistant does not accept attachments.',\n );\n }\n const request = context.event.request;\n const type = request.headers.get('content-type') ?? '';\n if (!/^multipart\\/form-data\\b/i.test(type)) {\n throw new AssistantRouteError(\n 415,\n 'unsupported_media_type',\n 'Upload a file as multipart/form-data.',\n );\n }\n const maxBytes = storage.maxBytes ?? DEFAULT_ASSISTANT_MAX_ATTACHMENT_BYTES;\n const bytes = await readBounded(request, maxBytes + 64 * 1024);\n let form: FormData;\n try {\n form = await new Response(\n bytes as unknown as ConstructorParameters<typeof Response>[0],\n {\n headers: { 'content-type': type },\n },\n ).formData();\n } catch {\n throw badRequest('The upload could not be read.');\n }\n const file = form.get('file');\n if (!file || typeof file === 'string') {\n throw badRequest('Attach the upload as the `file` field.');\n }\n if (file.size > maxBytes) {\n throw new AssistantRouteError(\n 413,\n 'payload_too_large',\n 'The file is too large.',\n );\n }\n const record = await storage.upload(file as File, context);\n return json(\n {\n attachment: {\n id: record.id,\n name: record.name,\n contentType: record.contentType,\n size: record.size,\n ...(record.url ? { url: record.url } : {}),\n },\n },\n 201,\n );\n };\n\n const runAction = async (\n context: AssistantRequestContext,\n phase: 'preview' | 'apply',\n ) => {\n if (!options.actions) {\n throw new AssistantRouteError(\n 404,\n 'actions_unsupported',\n 'This assistant has no actions.',\n );\n }\n const body = await readJsonObject(context.event.request, maxBody);\n if (body.phase !== phase) {\n throw badRequest(`Expected a ${phase} request.`);\n }\n const adapter = await resolveValue(options.actions.adapter, context);\n if (!adapter) throw new Error('No data-surface action adapter resolved.');\n const { principal } = context;\n const allowedTools = await allowedToolsFor(context);\n const db = await dbFor(context);\n const actionContext = {\n principal: {\n ...(db ? { db } : {}),\n principal: {\n runAsUserId: principal.userId,\n tenantId: principal.tenantId,\n allowedTools,\n },\n onBehalfOfUserId: principal.userId,\n ...(principal.permissions\n ? { permissions: principal.permissions }\n : {}),\n ...(options.audit ? { audit: options.audit } : {}),\n ...(options.postgresRls !== undefined\n ? { postgresRls: options.postgresRls }\n : {}),\n },\n } as Parameters<AssistantActionAdapter['preview']>[1];\n const request = body as unknown as Parameters<\n AssistantActionAdapter['preview']\n >[0];\n try {\n const result =\n phase === 'preview'\n ? await adapter.preview(request, actionContext)\n : await adapter.apply(request, actionContext);\n return json({ result });\n } catch (error) {\n safeLog(error);\n return json(\n {\n error:\n phase === 'apply'\n ? 'The change may or may not have been applied. Check again.'\n : GENERIC_ERROR,\n code: phase === 'apply' ? 'outcome_unknown' : 'internal_error',\n },\n 500,\n );\n }\n };\n\n // ---- dispatch ----------------------------------------------------------\n\n const handle: AssistantRouteHandler = async (event) => {\n try {\n const segments = segmentsOf(event);\n const match = segments ? matchRoute(segments) : null;\n if (!match) {\n throw new AssistantRouteError(404, 'not_found', 'Not found.');\n }\n const method = event.request.method.toUpperCase();\n const allowed = ROUTE_METHODS[match.route];\n if (!allowed.includes(method)) {\n return json(\n { error: 'Method not allowed.', code: 'method_not_allowed' },\n 405,\n { allow: allowed.join(', ') },\n );\n }\n if (\n method !== 'GET' &&\n checkOrigin &&\n !originAllowed(event.request, event.url, trustedOrigins)\n ) {\n throw new AssistantRouteError(\n 403,\n 'invalid_origin',\n 'Invalid request origin.',\n );\n }\n const principal = await resolvePrincipal(event);\n if (!principal) {\n throw new AssistantRouteError(\n 401,\n 'unauthenticated',\n 'Sign in to use the assistant.',\n );\n }\n if (\n !nonEmpty(principal.userId) ||\n !nonEmpty(principal.profileId) ||\n !nonEmpty(principal.tenantId)\n ) {\n throw new AssistantRouteError(\n 403,\n 'principal_incomplete',\n 'Your session cannot use the assistant.',\n );\n }\n const context: AssistantRequestContext = { event, principal };\n switch (match.route) {\n case 'threads':\n return method === 'GET'\n ? await listThreads(context)\n : await createThread(context);\n case 'thread-messages': {\n const threadId = requireId(match.threadId, 'thread id');\n return method === 'GET'\n ? await loadMessages(context, threadId)\n : await sendMessage(context, threadId);\n }\n case 'thread-resume':\n return await resumeTurn(\n context,\n requireId(match.threadId, 'thread id'),\n );\n case 'attachments':\n return await uploadAttachment(context);\n case 'actions':\n return await runAction(context, match.phase);\n }\n } catch (error) {\n if (error instanceof AssistantRouteError) return errorResponse(error);\n if (isAuthorizationFailure(error)) return errorResponse(notFound());\n safeLog(error);\n return json({ error: GENERIC_ERROR, code: 'internal_error' }, 500);\n }\n };\n\n return { GET: handle, POST: handle, handle };\n}\n\n/**\n * Events after which the browser acts on stored state (resumes a\n * suspension, retries, reloads the thread): a turn in its own database\n * lifetime holds them, and every event after them, until it commits.\n */\nconst HELD_UNTIL_COMMIT: ReadonlySet<string> = new Set([\n 'client_tool_calls',\n 'done',\n 'error',\n]);\n\n/** Default for `turnStartTimeoutMs`. */\nexport const DEFAULT_ASSISTANT_TURN_START_TIMEOUT_MS = 60_000;\n\n/**\n * The RLS request transaction this call runs in (`database-rls`), when\n * there is one. It commits when the request's handler chain returns.\n */\nfunction requestTransaction(): { isActive?: unknown } | null {\n if (getCurrentSessionPermissionContext()?.postgresRls !== true) return null;\n return (\n (getRequestScopedDatabase() as { isActive?: unknown } | undefined) ?? null\n );\n}\n\n/**\n * The permissions a turn in its own lifetime runs with: the bound\n * principal's effective `scopes` (live at bind time, capped by the runtime),\n * kept within `cap` as well so a runtime that ignored the cap cannot widen\n * them. Malformed scopes refuse the turn.\n */\nfunction effectiveScopes(\n granted: AssistantRouteBoundPrincipal | undefined,\n cap: readonly string[] | undefined,\n): string[] {\n const scopes = granted?.scopes;\n if (\n !Array.isArray(scopes) ||\n !scopes.every((scope) => typeof scope === 'string')\n ) {\n throw new Error(\n 'mountAssistantRoutes: runtime.runAsPrincipal bound no effective scopes.',\n );\n }\n const allowed = cap ? new Set(cap) : undefined;\n return scopes.filter((scope) => !allowed || allowed.has(scope));\n}\n\n/** Resolves `true` once `isActive()` is false, `false` after `timeoutMs`. */\nasync function requestEnded(\n isActive: () => boolean,\n timeoutMs: number,\n): Promise<boolean> {\n const deadline = Date.now() + timeoutMs;\n let delay = 2;\n while (isActive()) {\n if (Date.now() >= deadline) return false;\n await new Promise((resolve) => setTimeout(resolve, delay));\n delay = Math.min(delay * 2, 50);\n }\n return true;\n}\n\n/** A producer that forwards a turn's events. */\nfunction pump<M>(\n events: AsyncGenerator<AssistantTurnEvent<M>, unknown>,\n): (emit: (event: AssistantTurnEvent<M>) => void) => Promise<void> {\n return async (emit) => {\n for await (const event of events) emit(event);\n };\n}\n\n/**\n * Run a turn to its end regardless of the reader. `produce` starts at once\n * and its events go into a buffer the response reads from (after `first`,\n * when given); a reader that leaves only stops reading, and `settled`\n * resolves when the producer has finished. The turn itself records its\n * outcome (`onState`), so nothing here depends on how far the reader got. A\n * client disconnect cancels the turn — through the request's abort signal,\n * or the response body's cancellation when the adapter does not abort the\n * request — as the dock's Stop does; the runner then records `cancelled`\n * (or `completed`, if the reply was already stored).\n */\nfunction detached<M>(\n first: AssistantTurnEvent<M> | null,\n produce: (emit: (event: AssistantTurnEvent<M>) => void) => Promise<void>,\n): {\n events: AsyncGenerator<AssistantTurnEvent<M>, void>;\n settled: Promise<void>;\n} {\n const buffer: AssistantTurnEvent<M>[] = first ? [first] : [];\n let finished = false;\n let notify: (() => void) | null = null;\n const wake = () => {\n const resume = notify;\n notify = null;\n resume?.();\n };\n const settled = (async () => {\n try {\n await produce((event) => {\n buffer.push(event);\n wake();\n });\n } catch {\n // Producers report failures in-band and do not throw.\n } finally {\n finished = true;\n wake();\n }\n })();\n const events = (async function* () {\n for (;;) {\n if (buffer.length > 0) {\n yield buffer.shift() as AssistantTurnEvent<M>;\n continue;\n }\n if (finished) return;\n await new Promise<void>((resolve) => {\n notify = resolve;\n });\n }\n })();\n return { events, settled };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"sveltekit.js","names":[],"sources":["../src/sveltekit.ts"],"sourcesContent":["/**\n * Mountable SvelteKit routes for the AssistantDock (#3368).\n *\n * One call serves everything the dock's HTTP transport\n * (`createAssistantHttpTransport` / `createSmrtAssistantTransport`) and HTTP\n * action client (`createAssistantHttpActionClient`) call:\n *\n * ```ts\n * // src/routes/api/assistant/[...path]/+server.ts\n * import { mountAssistantRoutes } from '@happyvertical/smrt-chat/sveltekit';\n * import { runtime } from '$lib/server/smrt';\n * export const { GET, POST } = mountAssistantRoutes({\n * allowedTools: ['notes.read', 'notes.create'],\n * runtime,\n * });\n * ```\n *\n * `ai` defaults to the `smrt.config` `ai` block (resolved per turn through\n * `@happyvertical/smrt-config`); `allowedTools` alone offers the manifest\n * operations it names; `runtime` supplies the per-request database and the\n * streamed turn's own database lifetime (`db` may instead be a per-request\n * resolver).\n *\n * GET threads member-scoped thread list\n * POST threads { title } → { thread }\n * GET threads/:id/messages member-scoped, chronological\n * POST threads/:id/messages { content, clientRequestId, ... } → SSE turn\n * POST threads/:id/resume { continuationId, results, ... } → SSE turn\n * POST attachments multipart `file` (host storage only)\n * POST actions/preview | actions/apply DataSurfaceActionWireRequest → { result }\n *\n * Trust boundary: the principal comes from SvelteKit `event.locals` (as\n * populated by `createSessionHandler` from `@happyvertical/smrt-users/sveltekit`)\n * or an injected resolver — never from a header, hostname or request body.\n * Every actor gets ONE assistant `AgentSession` per tenant (keyed, so it never\n * reuses another feature's session); threads are readable and writable only\n * when they live in that session's room. Reads never create anything. Model\n * tools run under `executeAsPrincipal` (inside `runToolLoop`) with a\n * fail-closed allow-list: no `allowedTools` means no tools.\n *\n * Server-only: imports no Svelte or browser code.\n *\n * @packageDocumentation\n */\n\nimport './__smrt-register__.js';\n\nimport type { AIInterface, AIMessage } from '@happyvertical/ai';\nimport type {\n PrincipalAuditSink,\n PrincipalTool,\n} from '@happyvertical/smrt-agents';\nimport type { DataSurfaceActionAdapter } from '@happyvertical/smrt-agents/server';\nimport {\n getConfig,\n loadConfig,\n resolveConfiguredAIProvider,\n toAIClientOptions,\n} from '@happyvertical/smrt-config';\nimport type { SmrtClassOptions } from '@happyvertical/smrt-core';\nimport {\n getCurrentSessionPermissionContext,\n getRequestScopedDatabase,\n} from '@happyvertical/smrt-users';\nimport {\n ASSISTANT_TURN_GENERIC_ERROR,\n type AssistantContinuationStore,\n type AssistantTurnErrorLogger,\n type AssistantTurnState,\n type AuthoredToolReply,\n createAssistantTurnResponse,\n createSessionContinuationStore,\n DEFAULT_CONTINUATION_TTL_MS,\n type ProviderErrorFallback,\n runAssistantTurn,\n} from './assistant-turn.js';\nimport type { AssistantTurnEvent } from './assistant-turn-events.js';\nimport { MAX_CHAT_STREAM_CONTENT_LENGTH } from './chat-stream.js';\nimport type { AgentSession } from './models/AgentSession.js';\nimport type { ChatMessage } from './models/ChatMessage.js';\nimport type { ChatThread } from './models/ChatThread.js';\nimport {\n ChatClientRequestConflictError,\n type ChatClientRequestOutcome,\n type ChatMessageAttachment,\n ChatService,\n clientRequestMessageId,\n} from './services/ChatService.js';\nimport {\n buildManifestToolCatalog,\n type ClientToolResultInput,\n FALLBACK_HISTORY_PLACEHOLDER,\n isFallbackReply,\n MAX_CLIENT_TOOL_RESULT_CHARS,\n MAX_CLIENT_TOOLS,\n type ManifestTool,\n sanitizeClientToolDeclarations,\n type ToolInvocation,\n type ToolLoopUsage,\n} from './tool-loop.js';\nimport type { ChatMessageRole } from './types.js';\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/** The slice of a SvelteKit `RequestEvent` these handlers read. */\nexport interface AssistantRouteEvent {\n request: Request;\n url: URL;\n params?: Partial<Record<string, string>>;\n locals?: unknown;\n}\n\n/** A SvelteKit `+server.ts` handler with no `@sveltejs/kit` dependency. */\nexport type AssistantRouteHandler = (\n event: AssistantRouteEvent,\n) => Promise<Response>;\n\n/** The authenticated caller, resolved server-side. */\nexport interface AssistantPrincipal {\n /** The user whose live permissions bound every tool call (`runAsUserId`). */\n userId: string;\n /** The user's Profile: the chat actor that owns the assistant session. */\n profileId: string;\n /** The active, session-authorized tenant. */\n tenantId: string;\n /** Optional permission snapshot; omitted resolves live permissions. */\n permissions?: string[];\n}\n\n/**\n * Resolves the caller. Return `null` for an unauthenticated request (401);\n * throw {@link AssistantRouteError} for any other refusal.\n */\nexport type AssistantPrincipalResolver = (\n event: AssistantRouteEvent,\n) =>\n | AssistantPrincipal\n | null\n | undefined\n | Promise<AssistantPrincipal | null | undefined>;\n\n/** What every per-request option callback receives. */\nexport interface AssistantRequestContext {\n event: AssistantRouteEvent;\n principal: AssistantPrincipal;\n /**\n * The client-supplied `context` object of a send or resume body (for\n * example the surfaces the page has mounted), validated to be a plain JSON\n * object of at most {@link MAX_ASSISTANT_REQUEST_CONTEXT_BYTES} bytes.\n * Present only on the callbacks of a send or resume; absent when the client\n * sent none. It is untrusted request input: use it to narrow, never to\n * grant.\n */\n requestContext?: Readonly<Record<string, unknown>>;\n}\n\n/** A value, or a per-request callback producing it. */\nexport type AssistantRouteValue<T> =\n | T\n | ((context: AssistantRequestContext) => T | Promise<T>);\n\n/** A stored attachment, as the host's storage reports it. */\nexport interface AssistantAttachmentRecord {\n id: string;\n name: string;\n contentType: string;\n size: number;\n url?: string;\n}\n\n/**\n * Host-owned attachment storage. Without it, uploads and sends with\n * attachments are refused: the chat package stores no files.\n */\nexport interface AssistantAttachmentStorage {\n /** Store an upload for the principal and describe it. */\n upload(\n file: File,\n context: AssistantRequestContext,\n ): Promise<AssistantAttachmentRecord>;\n /**\n * Check that the references a send names belong to the principal and\n * return the records to store on the message, or `null` to refuse. The\n * references are request input: never trust their `url`/`size` as given.\n */\n verify(\n references: unknown[],\n context: AssistantRequestContext,\n ): Promise<AssistantAttachmentRecord[] | null>;\n /** Largest accepted upload, bytes. Default 10 MiB. */\n maxBytes?: number;\n}\n\n/** The two adapter calls the action routes make. */\nexport type AssistantActionAdapter = Pick<\n DataSurfaceActionAdapter,\n 'preview' | 'apply'\n>;\n\n/** Selectable model, the same shape the dock's `ModelPicker` uses. */\nexport interface AssistantRouteModel {\n id: string;\n label?: string;\n}\n\n/**\n * The two application-runtime calls the routes use; the runtime from\n * `@happyvertical/smrt-app-runtime/sveltekit` satisfies it as is.\n */\nexport interface AssistantRouteRuntime {\n /** The request's database (its RLS transaction inside one). */\n databaseConfig(): SmrtClassOptions['db'];\n /**\n * Run `fn` as a principal: under `database-rls` in a fresh transaction\n * publishing that user, tenant and live permissions (capped to `scopes`;\n * omitted means no cap), which `databaseConfig()` returns inside `fn`.\n * `fn` receives the bound principal, whose `scopes` are the effective set.\n */\n runAsPrincipal<T>(\n principal: {\n readonly id: string;\n readonly tenantId: string;\n readonly scopes?: readonly string[];\n },\n fn: (bound: AssistantRouteBoundPrincipal) => Promise<T>,\n ): Promise<T>;\n}\n\n/** What {@link AssistantRouteRuntime.runAsPrincipal} hands its callback. */\nexport interface AssistantRouteBoundPrincipal {\n /** The effective permissions: live at bind time, capped to the scopes. */\n readonly scopes: readonly string[];\n}\n\n/** Options for {@link mountAssistantRoutes}. */\nexport interface MountAssistantRoutesOptions {\n /**\n * The AI client, or a factory called once per turn. Omitted, each turn\n * builds a client from the `smrt.config` `ai` block through the shared\n * resolver (`resolveConfiguredAIProvider` → `toAIClientOptions` →\n * `getAI`), loading the config file first if nothing has. A factory that\n * throws (including `AIProviderNotConfiguredError` when nothing is\n * configured) answers 503 before anything is written; the error goes to\n * `onError` only. Passing `ai` explicitly as `undefined`/`null` is a mount\n * error, not a fallback.\n */\n ai?:\n | AIInterface\n | ((\n context: AssistantRequestContext & { model?: string },\n ) => AIInterface | Promise<AIInterface>);\n /**\n * Database for chat persistence, the tool catalog and the principal run:\n * a fixed value, or a resolver called once per request (and once more\n * inside a streamed turn's own database lifetime, see `runtime`). Default\n * with `runtime`: `() => runtime.databaseConfig()`.\n */\n db?: AssistantRouteValue<SmrtClassOptions['db']>;\n /**\n * The application runtime (`runtime` from\n * `@happyvertical/smrt-app-runtime/sveltekit`). A request inside an RLS\n * transaction (`database-rls`) ends, committing that transaction, when the\n * handler returns its streaming response, so a turn never keeps it: with\n * `runtime`, a streamed turn waits for the request's transaction to end and\n * then runs, persists its reply and records its outcome in its own\n * transaction for the same principal (`runtime.runAsPrincipal`). Without\n * it, such a turn runs to completion before the response is returned (the\n * events arrive at once, not incrementally). Outside an RLS transaction\n * (SQLite, the local profile) the turn streams as before either way.\n */\n runtime?: AssistantRouteRuntime;\n /** Default: {@link resolveAssistantPrincipalFromLocals}. */\n resolvePrincipal?: AssistantPrincipalResolver;\n /** The assistant's agent id (its `bot` profile slug). Default `smrt-assistant`. */\n agentId?: string;\n /**\n * Session key of the dock's `AgentSession`. Default\n * {@link ASSISTANT_DOCK_SESSION_KEY} (`assistant-dock`). A host whose\n * assistant threads already live in another session passes that session's\n * key to list and continue them: `null` adopts the person's unkeyed session\n * (what `ChatService.createAgentSession` opens without a `sessionKey`), a\n * string adopts the session stored under that key. Sessions are matched on\n * the key exactly, so a host changing it from the default stops seeing the\n * threads of the old key.\n */\n sessionKey?: string | null;\n systemPrompt?: AssistantRouteValue<string | undefined>;\n /**\n * Server tool allow-list, fail-closed: absent or empty offers NO tools.\n * Gates `extraTools`, `tools` and the data-surface action adapter. Without\n * `tools`, the route offers the manifest operations named here\n * (`buildManifestToolCatalog`), except names an `extraTools` entry serves.\n * A name nothing provides is a configuration error: at mount when its\n * collection is already registered, otherwise the turn answers 503 (the\n * check is skipped when `actions` is set, since action tools may be named).\n */\n allowedTools?: AssistantRouteValue<readonly string[]>;\n /** Server tools (e.g. `createDataSurfaceTools()`), narrowed by `allowedTools`. */\n extraTools?: AssistantRouteValue<readonly PrincipalTool[]>;\n /**\n * Manifest tools, narrowed by `allowedTools`. Omitted, they are built from\n * `allowedTools`; pass a value (even `[]`) to supply them yourself.\n */\n tools?: AssistantRouteValue<readonly ManifestTool[]>;\n /**\n * Browser tools the page may declare (exact names or `prefix*`). Empty\n * (the default) admits none.\n */\n clientToolAllowList?: readonly string[];\n /**\n * Where suspended turns wait. Default: the actor's assistant session\n * context (`createSessionContinuationStore`), re-read before every write.\n * A shared store must keep keys private to the session it is given.\n */\n continuations?: (\n context: AssistantRequestContext & { session: AgentSession },\n ) => AssistantContinuationStore;\n /** When set, a requested model must be listed; otherwise it is ignored. */\n models?: readonly AssistantRouteModel[];\n /** Model used when the request names none. */\n defaultModel?: string;\n maxSteps?: number;\n /** Repeated-call stop (see `AssistantTurnOptions`, #4391). */\n maxRepeatedCalls?: number;\n maxTurnTokens?: number;\n maxTurnMs?: number;\n /** Per-model-call timeout in ms (see `AssistantTurnOptions`, #4379). */\n requestTimeoutMs?: number;\n /** Reply stored when the turn produced no answer (#4379). */\n emptyAnswerFallback?: string;\n /**\n * Reply stored when a model call throws, as text or a function of the\n * error `code` (see `AssistantTurnOptions`, #4454). Unset: an error event\n * and nothing stored.\n */\n providerErrorFallback?: ProviderErrorFallback;\n maxTokens?: number;\n temperature?: number;\n /**\n * Earlier user and assistant turns sent with a new turn; persisted tool\n * rows do not count (#4458). Default 20.\n */\n historyLimit?: number;\n /**\n * A stored send with no reply and no recorded outcome (e.g. its process\n * died) counts as failed after this long. Default 15 minutes.\n */\n abandonedTurnMs?: number;\n /**\n * How long a streamed turn waits for its request's RLS transaction to end\n * before it reports an error instead of running (see `runtime`). It then\n * keeps watching, up to `abandonedTurnMs`, and settles a send that\n * committed late as `failed`. Default 60 s.\n */\n turnStartTimeoutMs?: number;\n describeTool?: (name: string, args?: Record<string, unknown>) => string;\n /**\n * Persist a server tool invocation into the thread (default: none). Its\n * tool name must be on `allowedTools`, which is mirrored onto the session.\n */\n authorInvocation?: (invocation: ToolInvocation) => AuthoredToolReply | null;\n onUsage?: (usage: ToolLoopUsage, context: AssistantRequestContext) => void;\n audit?: PrincipalAuditSink;\n postgresRls?: boolean;\n /** Host attachment storage; absent refuses attachments. */\n attachments?: AssistantAttachmentStorage;\n /** Data-surface action adapter; absent answers the action routes 404. */\n actions?: {\n adapter: AssistantRouteValue<AssistantActionAdapter>;\n };\n /**\n * Refuse mutating requests whose `Origin` (or `Sec-Fetch-Site`/`Referer`)\n * is not this origin. Default `true`.\n */\n checkOrigin?: boolean;\n /** Extra origins accepted by the origin check (e.g. behind a proxy). */\n trustedOrigins?: readonly string[];\n /** Largest JSON body, bytes. Default 1 MiB. */\n maxBodyBytes?: number;\n /** Longest user message, characters. Default 12,000. */\n maxContentLength?: number;\n /** SSE keep-alive interval. */\n heartbeatMs?: number;\n /** Server-side log for failures withheld from the browser. */\n onError?: AssistantTurnErrorLogger;\n /** Rest parameter name of the mounting route. Default `path`. */\n paramName?: string;\n /** Used when the route has no rest parameter: the prefix to strip. */\n basePath?: string;\n}\n\n/** The handlers {@link mountAssistantRoutes} returns. */\nexport interface AssistantRoutes {\n GET: AssistantRouteHandler;\n POST: AssistantRouteHandler;\n /** Method-agnostic entry point (GET and POST both delegate here). */\n handle: AssistantRouteHandler;\n}\n\n/** A refusal with an HTTP status and a stable, user-safe code. */\nexport class AssistantRouteError extends Error {\n constructor(\n readonly status: number,\n readonly code: string,\n message: string,\n ) {\n super(message);\n this.name = 'AssistantRouteError';\n }\n}\n\n// ---------------------------------------------------------------------------\n// Wire shapes (mirrors `AssistantThreadSummary` / `AssistantMessage`)\n// ---------------------------------------------------------------------------\n\n/** Thread summary as the routes send it. */\nexport interface AssistantThreadWire {\n id: string;\n title: string;\n isResolved: boolean;\n messageCount: number;\n lastMessageAt: string | null;\n}\n\n/** Message as the routes send it (chronological in lists). */\nexport interface AssistantMessageWire {\n id: string;\n threadId: string;\n content: string;\n role: 'user' | 'assistant' | 'system' | 'tool';\n createdAt: string;\n attachments?: AssistantAttachmentRecord[];\n toolCallData?: Record<string, unknown>;\n clientRequestId?: string;\n}\n\n/**\n * JSON answer to a send whose `clientRequestId` is already stored for this\n * thread and actor. `outcome`: `completed` (a reply follows), `cancelled`\n * (stopped, no reply), or `in_progress` (still running, or waiting on\n * browser tools). A failed or abandoned turn answers 409 `turn_failed`.\n */\nexport interface AssistantDuplicateSendWire {\n duplicate: true;\n inProgress: boolean;\n outcome: 'completed' | 'cancelled' | 'in_progress';\n userMessage: AssistantMessageWire;\n assistantMessage?: AssistantMessageWire;\n messages: AssistantMessageWire[];\n}\n\n// ---------------------------------------------------------------------------\n// Defaults and helpers\n// ---------------------------------------------------------------------------\n\n/** Default assistant agent id. */\nexport const DEFAULT_ASSISTANT_AGENT_ID = 'smrt-assistant';\n\n/** The roles the assistant route replays as history (#4458). */\nconst HISTORY_REPLAY_ROLES: readonly ChatMessageRole[] = ['user', 'assistant'];\n/** Session key isolating the dock's session from other uses of the agent id. */\nexport const ASSISTANT_DOCK_SESSION_KEY = 'assistant-dock';\n/** Largest serialized `context` object a send or resume body may carry. */\nexport const MAX_ASSISTANT_REQUEST_CONTEXT_BYTES = 8 * 1024;\n/** Default JSON body cap. */\nexport const DEFAULT_ASSISTANT_MAX_BODY_BYTES = 1024 * 1024;\n/** Default attachment cap. */\nexport const DEFAULT_ASSISTANT_MAX_ATTACHMENT_BYTES = 10 * 1024 * 1024;\nconst MAX_TITLE_LENGTH = 200;\nconst MAX_ATTACHMENTS_PER_MESSAGE = 10;\nconst MAX_ID_LENGTH = 128;\nconst CLIENT_REQUEST_ID = /^[A-Za-z0-9_.:-]{1,128}$/;\n/** Default age after which an unanswered, unsettled send counts as failed. */\nexport const DEFAULT_ASSISTANT_ABANDONED_TURN_MS = 15 * 60 * 1000;\nconst GENERIC_ERROR = 'The assistant request failed.';\n\n/**\n * The default principal resolver: SvelteKit `event.locals` as populated by\n * `createSessionHandler` (`user`, `tenantId`). No user → `null` (401); a user\n * without a Profile or an active tenant is refused with 403.\n */\nexport function resolveAssistantPrincipalFromLocals(\n locals: unknown,\n): AssistantPrincipal | null {\n const record = (locals ?? {}) as {\n user?: { id?: unknown; profileId?: unknown } | null;\n tenantId?: unknown;\n };\n const user = record.user;\n const userId = nonEmpty(user?.id);\n if (!user || !userId) return null;\n const profileId = nonEmpty(user.profileId);\n if (!profileId) {\n throw new AssistantRouteError(\n 403,\n 'profile_required',\n 'Your account has no profile, so it cannot use the assistant.',\n );\n }\n const tenantId = nonEmpty(record.tenantId);\n if (!tenantId) {\n throw new AssistantRouteError(\n 403,\n 'tenant_required',\n 'Select an organization to use the assistant.',\n );\n }\n return { userId, profileId, tenantId };\n}\n\nfunction nonEmpty(value: unknown): string | null {\n return typeof value === 'string' && value.trim().length > 0 ? value : null;\n}\n\nfunction json(\n body: unknown,\n status = 200,\n headers?: Record<string, string>,\n): Response {\n const out = new Headers(headers);\n out.set('content-type', 'application/json; charset=utf-8');\n out.set('cache-control', 'no-store');\n return new Response(JSON.stringify(body), { status, headers: out });\n}\n\nfunction errorResponse(error: AssistantRouteError): Response {\n return json({ error: error.message, code: error.code }, error.status);\n}\n\nconst notFound = () =>\n new AssistantRouteError(404, 'not_found', 'Conversation not found.');\nconst badRequest = (message: string, code = 'invalid_request') =>\n new AssistantRouteError(400, code, message);\n\nasync function resolveValue<T>(\n value: AssistantRouteValue<T> | undefined,\n context: AssistantRequestContext,\n): Promise<T | undefined> {\n if (typeof value === 'function') {\n return (value as (c: AssistantRequestContext) => T | Promise<T>)(context);\n }\n return value;\n}\n\nlet configLoad: Promise<unknown> | undefined;\n\n/**\n * The default AI factory: the `smrt.config` `ai` block (plus its key\n * variable) through the shared resolver. Loads the config file once when no\n * one has; throws `AIProviderNotConfiguredError` (variable names only, never\n * values) when nothing is configured.\n */\nasync function configuredAssistantAI(): Promise<AIInterface> {\n if (getConfig() === null) {\n configLoad ??= loadConfig().catch((error: unknown) => {\n configLoad = undefined;\n throw error;\n });\n await configLoad;\n }\n const options = toAIClientOptions(resolveConfiguredAIProvider());\n // Imported per turn, as the dev routes do: mounting (and SvelteKit's\n // build-time route analysis) never loads a provider SDK.\n const { getAI } = await import('@happyvertical/ai');\n return getAI(options);\n}\n\n/**\n * Allow-listed names that neither `extraTools` nor the manifest catalog\n * provides. With `registeredOnly`, only names whose collection the registry\n * already knows: a class may not be registered yet when a route module is\n * imported (e.g. during SvelteKit's build analysis), so only those prove a\n * typo at mount.\n */\nfunction unprovidedToolNames(\n requested: readonly string[],\n offered: readonly ManifestTool[],\n registeredOnly: boolean,\n): string[] {\n const provided = new Set(offered.map((tool) => tool.slug));\n const missing = requested.filter((name) => !provided.has(name));\n if (!registeredOnly || missing.length === 0) return missing;\n // `all: true` only lists collections for this check; nothing it returns is\n // ever offered to a model.\n const collections = new Set(\n buildManifestToolCatalog({ all: true }).map((tool) => tool.collection),\n );\n return missing.filter((name) => {\n const dot = name.lastIndexOf('.');\n return dot > 0 && collections.has(name.slice(0, dot));\n });\n}\n\nasync function readBounded(request: Request, max: number): Promise<Uint8Array> {\n const declared = Number(request.headers.get('content-length'));\n const tooLarge = () =>\n new AssistantRouteError(\n 413,\n 'payload_too_large',\n 'The request is too large.',\n );\n if (Number.isFinite(declared) && declared > max) throw tooLarge();\n if (!request.body) return new Uint8Array();\n const reader = request.body.getReader();\n const chunks: Uint8Array[] = [];\n let total = 0;\n for (;;) {\n const { value, done } = await reader.read();\n if (done) break;\n total += value.byteLength;\n if (total > max) {\n await reader.cancel().catch(() => undefined);\n throw tooLarge();\n }\n chunks.push(value);\n }\n const out = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks) {\n out.set(chunk, offset);\n offset += chunk.byteLength;\n }\n return out;\n}\n\nasync function readJsonObject(\n request: Request,\n max: number,\n): Promise<Record<string, unknown>> {\n const type = request.headers.get('content-type') ?? '';\n if (!/^application\\/json\\b/i.test(type)) {\n throw new AssistantRouteError(415, 'unsupported_media_type', 'Send JSON.');\n }\n const bytes = await readBounded(request, max);\n let parsed: unknown;\n try {\n parsed = JSON.parse(new TextDecoder().decode(bytes));\n } catch {\n throw badRequest('The request body is not valid JSON.');\n }\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n throw badRequest('The request body must be a JSON object.');\n }\n return parsed as Record<string, unknown>;\n}\n\nfunction requireId(value: unknown, label: string): string {\n if (\n typeof value !== 'string' ||\n value.length === 0 ||\n value.length > MAX_ID_LENGTH\n ) {\n throw badRequest(`${label} is missing or invalid.`);\n }\n return value;\n}\n\nfunction originAllowed(\n request: Request,\n url: URL,\n trusted: readonly string[],\n): boolean {\n const allowed = new Set([url.origin, ...trusted]);\n const origin = request.headers.get('origin');\n if (origin) return allowed.has(origin);\n const site = request.headers.get('sec-fetch-site')?.toLowerCase();\n if (site) return site === 'same-origin';\n const referer = request.headers.get('referer');\n if (referer) {\n try {\n return allowed.has(new URL(referer).origin);\n } catch {\n return false;\n }\n }\n return false;\n}\n\nfunction toIso(value: unknown): string | null {\n if (value == null || value === '') return null;\n const date = value instanceof Date ? value : new Date(String(value));\n return Number.isNaN(date.getTime()) ? null : date.toISOString();\n}\n\nfunction parseObject(value: unknown): Record<string, unknown> | null {\n if (value && typeof value === 'object' && !Array.isArray(value)) {\n return value as Record<string, unknown>;\n }\n if (typeof value !== 'string' || value.length === 0) return null;\n try {\n const parsed = JSON.parse(value) as unknown;\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed)\n ? (parsed as Record<string, unknown>)\n : null;\n } catch {\n return null;\n }\n}\n\nfunction parseArray(value: unknown): unknown[] {\n if (Array.isArray(value)) return value;\n if (typeof value !== 'string' || value.length === 0) return [];\n try {\n const parsed = JSON.parse(value) as unknown;\n return Array.isArray(parsed) ? parsed : [];\n } catch {\n return [];\n }\n}\n\n/** Map a persisted thread to the wire. */\nexport function toAssistantThreadWire(thread: ChatThread): AssistantThreadWire {\n return {\n id: String(thread.id),\n title: thread.title ?? '',\n isResolved: Boolean(thread.isResolved),\n messageCount: Number(thread.messageCount) || 0,\n lastMessageAt: toIso(thread.lastMessageAt),\n };\n}\n\n/** Map a persisted message to the wire. */\nexport function toAssistantMessageWire(\n message: ChatMessage,\n): AssistantMessageWire {\n const metadata = parseObject(message.metadata);\n const toolCallData = parseObject(message.toolCallData);\n const attachments = parseArray(message.attachments)\n .map((entry): AssistantAttachmentRecord | null => {\n const a = entry as Record<string, unknown> | null;\n const id = nonEmpty(a?.id);\n const name = nonEmpty(a?.filename) ?? nonEmpty(a?.name);\n if (!id || !name) return null;\n return {\n id,\n name,\n contentType: nonEmpty(a?.contentType) ?? 'application/octet-stream',\n size: Number(a?.size) || 0,\n ...(nonEmpty(a?.url) ? { url: a?.url as string } : {}),\n };\n })\n .filter((a): a is AssistantAttachmentRecord => a !== null);\n const clientRequestId = nonEmpty(metadata?.clientRequestId);\n return {\n id: String(message.id),\n threadId: message.threadId ?? '',\n content: message.content ?? '',\n role: message.role,\n createdAt: toIso(message.created_at) ?? new Date(0).toISOString(),\n ...(attachments.length > 0 ? { attachments } : {}),\n ...(toolCallData ? { toolCallData } : {}),\n ...(clientRequestId ? { clientRequestId } : {}),\n };\n}\n\nfunction isAuthorizationFailure(error: unknown): boolean {\n const message = error instanceof Error ? error.message : '';\n return /authorization denied|not found|not active/i.test(message);\n}\n\n// ---------------------------------------------------------------------------\n// mountAssistantRoutes\n// ---------------------------------------------------------------------------\n\ntype RouteMatch =\n | { route: 'threads' }\n | { route: 'thread-messages'; threadId: string }\n | { route: 'thread-resume'; threadId: string }\n | { route: 'attachments' }\n | { route: 'actions'; phase: 'preview' | 'apply' };\n\nconst ROUTE_METHODS: Record<RouteMatch['route'], readonly string[]> = {\n threads: ['GET', 'POST'],\n 'thread-messages': ['GET', 'POST'],\n 'thread-resume': ['POST'],\n attachments: ['POST'],\n actions: ['POST'],\n};\n\nfunction matchRoute(segments: string[]): RouteMatch | null {\n const [a, b, c, ...rest] = segments;\n if (rest.length > 0) return null;\n if (a === 'threads' && b === undefined) return { route: 'threads' };\n if (a === 'threads' && b && c === 'messages') {\n return { route: 'thread-messages', threadId: b };\n }\n if (a === 'threads' && b && c === 'resume') {\n return { route: 'thread-resume', threadId: b };\n }\n if (a === 'attachments' && b === undefined) return { route: 'attachments' };\n if (a === 'actions' && (b === 'preview' || b === 'apply') && !c) {\n return { route: 'actions', phase: b };\n }\n return null;\n}\n\n/**\n * Build the AssistantDock's server routes for one SvelteKit rest route\n * (e.g. `src/routes/api/assistant/[...path]/+server.ts`).\n */\nexport function mountAssistantRoutes(\n options: MountAssistantRoutesOptions = {},\n): AssistantRoutes {\n if ('ai' in options && options.ai == null) {\n throw new Error(\n 'mountAssistantRoutes: `ai` was passed but is empty. Pass an AI client ' +\n 'or a factory, or omit `ai` to use the smrt.config `ai` block.',\n );\n }\n const aiSource = options.ai ?? configuredAssistantAI;\n if (\n options.tools === undefined &&\n !options.actions &&\n Array.isArray(options.allowedTools) &&\n typeof options.extraTools !== 'function'\n ) {\n // Fail fast on a name nothing can provide (a typo), when knowable now.\n const extra = new Set(\n ((options.extraTools ?? []) as readonly PrincipalTool[]).map(\n (tool) => tool.slug,\n ),\n );\n const requested = (options.allowedTools as readonly unknown[]).filter(\n (name): name is string =>\n typeof name === 'string' && name.length > 0 && !extra.has(name),\n );\n if (requested.length > 0) {\n const unknown = unprovidedToolNames(\n requested,\n buildManifestToolCatalog({ allowedTools: requested }),\n true,\n );\n if (unknown.length > 0) {\n throw new Error(\n `mountAssistantRoutes: allowedTools names ${unknown.join(', ')}, ` +\n 'which no manifest operation or extraTools entry provides.',\n );\n }\n }\n }\n const agentId = options.agentId ?? DEFAULT_ASSISTANT_AGENT_ID;\n const dockSessionKey =\n options.sessionKey === undefined\n ? ASSISTANT_DOCK_SESSION_KEY\n : options.sessionKey;\n if (dockSessionKey !== null && !dockSessionKey.trim()) {\n throw new Error(\n 'mountAssistantRoutes: sessionKey must be a non-empty string or null.',\n );\n }\n const paramName = options.paramName ?? 'path';\n const maxBody = options.maxBodyBytes ?? DEFAULT_ASSISTANT_MAX_BODY_BYTES;\n const maxContent = options.maxContentLength ?? MAX_CHAT_STREAM_CONTENT_LENGTH;\n const historyLimit = Math.max(0, options.historyLimit ?? 20);\n const checkOrigin = options.checkOrigin ?? true;\n const trustedOrigins = options.trustedOrigins ?? [];\n const resolvePrincipal: AssistantPrincipalResolver =\n options.resolvePrincipal ??\n ((event) => resolveAssistantPrincipalFromLocals(event.locals));\n const logError: AssistantTurnErrorLogger =\n options.onError ??\n ((error) => {\n // biome-ignore lint/suspicious/noConsole: server-side default for detail withheld from the browser; hosts pass `onError`\n console.error('[smrt-chat] assistant route failed:', error);\n });\n const safeLog = (error: unknown) => {\n try {\n logError(error);\n } catch {\n // Logging never breaks a response.\n }\n };\n const abandonedTurnMs =\n options.abandonedTurnMs ?? DEFAULT_ASSISTANT_ABANDONED_TURN_MS;\n const turnStartTimeoutMs =\n options.turnStartTimeoutMs ?? DEFAULT_ASSISTANT_TURN_START_TIMEOUT_MS;\n\n const segmentsOf = (event: AssistantRouteEvent): string[] | null => {\n const param = event.params?.[paramName];\n let path: string | undefined =\n typeof param === 'string' ? param : undefined;\n if (path === undefined && options.basePath) {\n const base = options.basePath.replace(/\\/+$/, '');\n const pathname = event.url.pathname;\n if (pathname === base || pathname.startsWith(`${base}/`)) {\n path = pathname.slice(base.length);\n }\n }\n if (path === undefined) return null;\n return path.split('/').filter((segment) => segment.length > 0);\n };\n\n const runtime = options.runtime;\n const dbOption: AssistantRouteValue<SmrtClassOptions['db']> | undefined =\n options.db !== undefined || !runtime\n ? options.db\n : () => runtime.databaseConfig();\n\n // The database is resolved once per context (a resolver may return the\n // request's own RLS transaction) and never shared across requests. A\n // streamed turn's own lifetime gets a fresh context, so it resolves anew.\n const requestDbs = new WeakMap<\n AssistantRequestContext,\n Promise<SmrtClassOptions['db'] | undefined>\n >();\n const dbFor = (context: AssistantRequestContext) => {\n let db = requestDbs.get(context);\n if (!db) {\n db = resolveValue(dbOption, context);\n requestDbs.set(context, db);\n }\n return db;\n };\n\n const chatFor = async (context: AssistantRequestContext) => {\n const db = await dbFor(context);\n const chat = await ChatService.create({\n tenantId: context.principal.tenantId,\n ...(db ? { db } : {}),\n } as Parameters<typeof ChatService.create>[0]);\n await chat.initialize();\n return chat;\n };\n\n const findSession = async (\n chat: ChatService,\n principal: AssistantPrincipal,\n ): Promise<AgentSession | null> => {\n const sessions = await chat.findActiveAgentSessions({\n tenantId: principal.tenantId,\n agentId,\n participantProfileId: principal.profileId,\n });\n return (\n sessions.find(\n (session) =>\n session.getSessionKey() === dockSessionKey &&\n session.participantProfileId === principal.profileId &&\n session.tenantId === principal.tenantId &&\n Boolean(session.chatRoomId),\n ) ?? null\n );\n };\n\n const requireOwnThread = async (\n chat: ChatService,\n principal: AssistantPrincipal,\n session: AgentSession | null,\n threadId: string,\n ): Promise<ChatThread> => {\n if (!session?.chatRoomId) throw notFound();\n const thread = await chat.getThread({\n threadId,\n tenantId: principal.tenantId,\n });\n if (\n !thread ||\n thread.roomId !== session.chatRoomId ||\n thread.tenantId !== principal.tenantId\n ) {\n throw notFound();\n }\n return thread;\n };\n\n const allowedToolsFor = async (context: AssistantRequestContext) => {\n const list = await resolveValue(options.allowedTools, context);\n return Array.isArray(list)\n ? list.filter(\n (tool): tool is string => typeof tool === 'string' && tool.length > 0,\n )\n : [];\n };\n\n const resolveModel = (requested: unknown): string | undefined => {\n if (requested === undefined || requested === null || requested === '') {\n return options.defaultModel;\n }\n if (typeof requested !== 'string' || requested.length > MAX_ID_LENGTH) {\n throw badRequest('The model is invalid.', 'model_not_allowed');\n }\n if (!options.models || options.models.length === 0) {\n return options.defaultModel;\n }\n if (!options.models.some((model) => model.id === requested)) {\n throw badRequest('That model is not available.', 'model_not_allowed');\n }\n return requested;\n };\n\n const resolveAI = async (\n context: AssistantRequestContext,\n model: string | undefined,\n ): Promise<AIInterface> => {\n try {\n const ai =\n typeof aiSource === 'function'\n ? await aiSource({ ...context, ...(model ? { model } : {}) })\n : aiSource;\n if (!ai || typeof ai.chat !== 'function') {\n throw new Error('The AI factory returned no client.');\n }\n return ai;\n } catch (error) {\n safeLog(error);\n throw new AssistantRouteError(\n 503,\n 'assistant_unavailable',\n 'The assistant is unavailable right now. Please try again later.',\n );\n }\n };\n\n /**\n * The manifest tools a turn is offered: `tools` when given, otherwise the\n * catalog entries for the allow-listed names `extraTools` does not serve.\n * A name nothing provides refuses the turn (503, detail to `onError`)\n * unless `actions` is set (action tools are allow-listed too).\n */\n const manifestToolsFor = async (\n context: AssistantRequestContext,\n allowedTools: readonly string[],\n extraTools: readonly PrincipalTool[],\n db: SmrtClassOptions['db'] | undefined,\n ): Promise<readonly ManifestTool[]> => {\n if (options.tools !== undefined) {\n return (await resolveValue(options.tools, context)) ?? [];\n }\n const extra = new Set(extraTools.map((tool) => tool.slug));\n const requested = allowedTools.filter((name) => !extra.has(name));\n if (requested.length === 0) return [];\n const tools = buildManifestToolCatalog({\n ...(db ? { db } : {}),\n allowedTools: requested,\n });\n if (!options.actions) {\n const unknown = unprovidedToolNames(requested, tools, false);\n if (unknown.length > 0) {\n safeLog(\n new Error(\n `mountAssistantRoutes: allowedTools names ${unknown.join(', ')}, ` +\n 'which no manifest operation or extraTools entry provides.',\n ),\n );\n throw new AssistantRouteError(\n 503,\n 'assistant_unavailable',\n 'The assistant is unavailable right now. Please try again later.',\n );\n }\n }\n return tools;\n };\n\n /** The store suspended turns of this actor's session wait in. */\n const continuationStoreFor = (\n context: AssistantRequestContext,\n chat: ChatService,\n session: AgentSession,\n ): AssistantContinuationStore => {\n if (options.continuations) {\n return options.continuations({ ...context, session });\n }\n const { principal } = context;\n return createSessionContinuationStore(async () => {\n const current = await chat.getAgentSession({\n agentSessionId: String(session.id),\n tenantId: principal.tenantId,\n });\n return current &&\n current.participantProfileId === principal.profileId &&\n current.isActive()\n ? current\n : null;\n });\n };\n\n /**\n * Attach a body's optional `context` object to the request context: a plain\n * JSON object (not an array) of bounded size, else 400.\n */\n const withRequestContext = (\n context: AssistantRequestContext,\n value: unknown,\n ): AssistantRequestContext => {\n if (value === undefined || value === null) return context;\n if (typeof value !== 'object' || Array.isArray(value)) {\n throw badRequest('context must be an object.', 'invalid_context');\n }\n if (\n new TextEncoder().encode(JSON.stringify(value)).length >\n MAX_ASSISTANT_REQUEST_CONTEXT_BYTES\n ) {\n throw badRequest(\n `context is limited to ${MAX_ASSISTANT_REQUEST_CONTEXT_BYTES} bytes.`,\n 'invalid_context',\n );\n }\n return {\n ...context,\n requestContext: value as Readonly<Record<string, unknown>>,\n };\n };\n\n /**\n * The request-bound part of a turn (model, allow-list, AI client, abort),\n * resolved once per request before anything is stored.\n */\n const prepareTurn = async (\n context: AssistantRequestContext,\n body: Record<string, unknown>,\n ) => {\n const model = resolveModel(body.model);\n const allowedTools = await allowedToolsFor(context);\n const clientTools = sanitizeClientToolDeclarations(\n body.clientTools,\n options.clientToolAllowList ?? [],\n );\n const ai = await resolveAI(context, model);\n // The turn stops when the request is aborted OR the response body is\n // cancelled (a client that left), whichever the adapter reports.\n const abort = new AbortController();\n const requestSignal = context.event.request.signal;\n if (requestSignal.aborted) abort.abort();\n else {\n requestSignal.addEventListener('abort', () => abort.abort(), {\n once: true,\n });\n }\n return { abort, ai, model, allowedTools, clientTools };\n };\n type PreparedTurn = Awaited<ReturnType<typeof prepareTurn>>;\n\n /**\n * Everything a turn needs that is bound to a database: built for the\n * request (where every refusal happens), and again inside a streamed\n * turn's own database lifetime (`mirror: false`: the request already\n * mirrored the allow-list).\n */\n const turnSetup = async (\n context: AssistantRequestContext,\n chat: ChatService,\n session: AgentSession,\n thread: ChatThread,\n prepared: PreparedTurn,\n mirror = true,\n ) => {\n const { principal } = context;\n const { abort, ai, model, allowedTools, clientTools } = prepared;\n const [extraTools, systemPrompt, db] = await Promise.all([\n resolveValue(options.extraTools, context),\n resolveValue(options.systemPrompt, context),\n dbFor(context),\n ]);\n const tools = await manifestToolsFor(\n context,\n allowedTools,\n extraTools ?? [],\n db,\n );\n // Mirror the allow-list onto the session so the authoring gate\n // (`sendAgentReply`) agrees with the loop's offer gate.\n if (options.authorInvocation && mirror) {\n const current = session.getAllowedTools();\n if (\n current.length !== allowedTools.length ||\n current.some((tool, index) => tool !== allowedTools[index])\n ) {\n await chat.updateAgentSessionConfig({\n agentSessionId: String(session.id),\n actorProfileId: principal.profileId,\n tenantId: principal.tenantId,\n allowedTools,\n });\n }\n }\n const continuations = continuationStoreFor(context, chat, session);\n return {\n abort,\n ai,\n model,\n clientTools,\n continuations,\n continuationKey: String(thread.id),\n turn: {\n ai,\n principal: {\n runAsUserId: principal.userId,\n tenantId: principal.tenantId,\n allowedTools,\n },\n ...(db ? { db } : {}),\n ...(systemPrompt ? { systemPrompt } : {}),\n extraTools: [...(extraTools ?? [])],\n tools: [...tools],\n clientTools,\n continuations,\n continuationKey: String(thread.id),\n ...(model ? { model } : {}),\n ...(options.maxSteps !== undefined\n ? { maxSteps: options.maxSteps }\n : {}),\n ...(options.maxRepeatedCalls !== undefined\n ? { maxRepeatedCalls: options.maxRepeatedCalls }\n : {}),\n ...(options.maxTurnTokens !== undefined\n ? { maxTurnTokens: options.maxTurnTokens }\n : {}),\n ...(options.maxTurnMs !== undefined\n ? { maxTurnMs: options.maxTurnMs }\n : {}),\n ...(options.requestTimeoutMs !== undefined\n ? { requestTimeoutMs: options.requestTimeoutMs }\n : {}),\n ...(options.emptyAnswerFallback !== undefined\n ? { emptyAnswerFallback: options.emptyAnswerFallback }\n : {}),\n ...(options.providerErrorFallback !== undefined\n ? { providerErrorFallback: options.providerErrorFallback }\n : {}),\n ...(options.maxTokens !== undefined\n ? { maxTokens: options.maxTokens }\n : {}),\n ...(options.temperature !== undefined\n ? { temperature: options.temperature }\n : {}),\n ...(principal.permissions\n ? { permissions: principal.permissions }\n : {}),\n ...(options.audit ? { audit: options.audit } : {}),\n ...(options.postgresRls !== undefined\n ? { postgresRls: options.postgresRls }\n : {}),\n onBehalfOfUserId: principal.userId,\n signal: abort.signal,\n ...(options.onUsage\n ? {\n onUsage: (usage: ToolLoopUsage) => {\n try {\n options.onUsage?.(usage, context);\n } catch (error) {\n safeLog(error);\n }\n },\n }\n : {}),\n ...(options.describeTool ? { describeTool: options.describeTool } : {}),\n onError: safeLog,\n serializeMessage: (message: unknown) =>\n toAssistantMessageWire(message as ChatMessage),\n author: {\n chatService: chat,\n agentSessionId: String(session.id),\n tenantId: principal.tenantId,\n threadId: String(thread.id),\n ...(options.authorInvocation\n ? { authorInvocation: options.authorInvocation }\n : {}),\n },\n },\n };\n };\n\n const stream = (\n events: AsyncGenerator<AssistantTurnEvent<AssistantMessageWire>, unknown>,\n abort: AbortController,\n ) =>\n createAssistantTurnResponse(events, {\n ...(options.heartbeatMs !== undefined\n ? { heartbeatMs: options.heartbeatMs }\n : {}),\n onError: safeLog,\n // A cancelled body cancels the turn, even when the adapter never\n // aborts the request signal on disconnect.\n onCancel: () => abort.abort(),\n });\n\n type TurnSetup = Awaited<ReturnType<typeof turnSetup>>;\n type TurnEvent = AssistantTurnEvent<AssistantMessageWire>;\n\n /**\n * Answer with a turn's events. `start` runs the turn on a chat service\n * and setup bound to the database it may use. Outside an RLS request\n * transaction that is the request's own, and the turn is detached. Inside\n * one, the transaction commits when the handler returns, so the turn never\n * keeps it: with `runtime` it runs, once that transaction has ended, in its\n * own principal-bound transaction (rebinding chat, tools and continuations\n * there, and first checking that `durableSend`, the stored user message,\n * committed); without `runtime` it runs to completion before returning.\n */\n const respondWithTurn = async (\n context: AssistantRequestContext,\n chat: ChatService,\n session: AgentSession,\n thread: ChatThread,\n prepared: PreparedTurn,\n setup: TurnSetup,\n first: TurnEvent | null,\n start: (\n chat: ChatService,\n setup: TurnSetup,\n ) => AsyncGenerator<TurnEvent, unknown>,\n durableSend?: string,\n ): Promise<Response> => {\n const transaction = requestTransaction();\n if (!transaction) {\n return stream(\n detached(first, pump(start(chat, setup))).events,\n prepared.abort,\n );\n }\n const isActive = transaction.isActive;\n if (!runtime || typeof isActive !== 'function') {\n const turn = detached(first, pump(start(chat, setup)));\n await turn.settled;\n return stream(turn.events, prepared.abort);\n }\n const { principal } = context;\n // The request's authority, frozen before the response returns (an empty\n // set stays empty): the principal's snapshot, else the permissions the\n // request's RLS context published. Always the cap, so the turn runs with\n // (this snapshot ∩ live at bind) and never gains a later grant.\n const bound = {\n id: principal.userId,\n tenantId: principal.tenantId,\n scopes: Object.freeze([\n ...(principal.permissions ??\n getCurrentSessionPermissionContext()?.permissions ??\n []),\n ]),\n };\n /** Record the stored send as failed, in a fresh lifetime. */\n const settleFailed = async (messageId: string, onlyIfStored = false) => {\n try {\n await runtime.runAsPrincipal(bound, async () => {\n const settleChat = await chatFor({ event: context.event, principal });\n if (onlyIfStored) {\n const stored = await settleChat.getThreadMessageReplies({\n threadId: String(thread.id),\n messageId,\n actorProfileId: principal.profileId,\n tenantId: principal.tenantId,\n });\n if (!stored?.message) return;\n }\n await settleOutcome(\n settleChat,\n principal,\n String(thread.id),\n messageId,\n )('failed');\n });\n } catch (settleError) {\n safeLog(settleError);\n }\n };\n /** After a timed-out start: settle the send once its request ends. */\n const settleLateSend = async (\n requestActive: () => boolean,\n messageId: string,\n ) => {\n try {\n if (await requestEnded(requestActive, abandonedTurnMs)) {\n await settleFailed(messageId, true);\n } else {\n safeLog(\n new Error(\n 'mountAssistantRoutes: the request transaction never ended; its send was not settled.',\n ),\n );\n }\n } catch (error) {\n safeLog(error);\n }\n };\n const ownLifetime = async (emit: (event: TurnEvent) => void) => {\n // Events after which the browser acts on stored state (resume, retry,\n // reload) wait until this lifetime has committed, with all that follow.\n const held: TurnEvent[] = [];\n let sendConfirmed = false;\n const requestActive = () => isActive.call(transaction) as boolean;\n try {\n if (!(await requestEnded(requestActive, turnStartTimeoutMs))) {\n // Never abandon the send: once the request's transaction ends,\n // settle it if it committed (a rollback stored nothing).\n if (durableSend) void settleLateSend(requestActive, durableSend);\n throw new Error(\n 'mountAssistantRoutes: the request transaction did not end; the turn did not run.',\n );\n }\n await runtime.runAsPrincipal(bound, async (granted) => {\n // The turn's authority is the bound principal's effective set, as\n // narrowed at bind time; the pre-bind snapshot never reaches it.\n const turnContext: AssistantRequestContext = {\n event: context.event,\n ...(context.requestContext\n ? { requestContext: context.requestContext }\n : {}),\n principal: {\n ...principal,\n permissions: effectiveScopes(granted, bound.scopes),\n },\n };\n const turnChat = await chatFor(turnContext);\n if (durableSend) {\n const stored = await turnChat.getThreadMessageReplies({\n threadId: String(thread.id),\n messageId: durableSend,\n actorProfileId: principal.profileId,\n tenantId: principal.tenantId,\n });\n if (!stored?.message) {\n throw new Error(\n 'mountAssistantRoutes: the send was not stored; the turn did not run.',\n );\n }\n sendConfirmed = true;\n }\n const turnSetupInLifetime = await turnSetup(\n turnContext,\n turnChat,\n session,\n thread,\n prepared,\n false,\n );\n for await (const event of start(turnChat, turnSetupInLifetime)) {\n if (held.length > 0 || HELD_UNTIL_COMMIT.has(event.type)) {\n held.push(event);\n } else {\n emit(event);\n }\n }\n });\n for (const event of held) emit(event);\n } catch (error) {\n // Nothing the turn wrote committed: settle the send as failed (in a\n // fresh lifetime) so it does not wait out `abandonedTurnMs`.\n safeLog(error);\n if (durableSend && sendConfirmed) await settleFailed(durableSend);\n emit({\n type: 'error',\n error: ASSISTANT_TURN_GENERIC_ERROR,\n code: 'internal_error',\n });\n emit({\n type: 'status',\n status: { state: 'error', label: ASSISTANT_TURN_GENERIC_ERROR },\n });\n }\n };\n return stream(detached(first, ownLifetime).events, prepared.abort);\n };\n\n // ---- route bodies ------------------------------------------------------\n\n const listThreads = async (context: AssistantRequestContext) => {\n const chat = await chatFor(context);\n const session = await findSession(chat, context.principal);\n if (!session?.chatRoomId) return json({ items: [] });\n const threads = await chat.listRoomThreads({\n roomId: session.chatRoomId,\n actorProfileId: context.principal.profileId,\n tenantId: context.principal.tenantId,\n });\n return json({ items: threads.map(toAssistantThreadWire) });\n };\n\n const createThread = async (context: AssistantRequestContext) => {\n const body = await readJsonObject(context.event.request, maxBody);\n const title = typeof body.title === 'string' ? body.title.trim() : '';\n if (!title || title.length > MAX_TITLE_LENGTH) {\n throw badRequest(\n `A title of 1 to ${MAX_TITLE_LENGTH} characters is required.`,\n );\n }\n const { principal } = context;\n const chat = await chatFor(context);\n const allowedTools = await allowedToolsFor(context);\n const { session } = await chat.createAgentSession({\n tenantId: principal.tenantId,\n agentId,\n actorProfileId: principal.profileId,\n allowedTools,\n sessionKey: dockSessionKey,\n });\n if (!session.chatRoomId) throw new Error('Assistant session has no room');\n const thread = await chat.startThread({\n tenantId: principal.tenantId,\n roomId: session.chatRoomId,\n actorProfileId: principal.profileId,\n title,\n });\n return json({ thread: toAssistantThreadWire(thread) }, 201);\n };\n\n const loadMessages = async (\n context: AssistantRequestContext,\n threadId: string,\n ) => {\n const { principal } = context;\n const chat = await chatFor(context);\n const session = await findSession(chat, principal);\n await requireOwnThread(chat, principal, session, threadId);\n const messages = await chat.getThreadMessages({\n threadId,\n actorProfileId: principal.profileId,\n tenantId: principal.tenantId,\n limit: 200,\n });\n return json({ items: messages.map(toAssistantMessageWire) });\n };\n\n /**\n * The answer for a send whose reservation (its deterministic user-message\n * id) is already stored, or `null` when it is not. Replies are the\n * messages linked to the send (`replyToMessageId`), never \"whatever\n * follows it\", so overlapping sends in one thread keep their own replies.\n * Legacy replies written without the link are not attributed.\n */\n const reservationAnswer = async (\n context: AssistantRequestContext,\n chat: ChatService,\n session: AgentSession,\n threadId: string,\n messageId: string,\n ): Promise<Response | null> => {\n const { principal } = context;\n const stored = await chat.getThreadMessageReplies({\n threadId,\n messageId,\n actorProfileId: principal.profileId,\n tenantId: principal.tenantId,\n });\n const anchor = stored?.message;\n if (\n !stored ||\n anchor?.role !== 'user' ||\n anchor.senderProfileId !== principal.profileId\n ) {\n return null;\n }\n const userMessage = toAssistantMessageWire(anchor);\n const after = stored.replies.map(toAssistantMessageWire);\n const assistantMessage = [...after]\n .reverse()\n .find((message) => message.role === 'assistant');\n const answer = (\n outcome: AssistantDuplicateSendWire['outcome'],\n ): Response => {\n const body: AssistantDuplicateSendWire = {\n duplicate: true,\n inProgress: outcome === 'in_progress',\n outcome,\n userMessage,\n ...(assistantMessage ? { assistantMessage } : {}),\n messages: after,\n };\n return json(body, 200);\n };\n const turnFailed = () =>\n new AssistantRouteError(\n 409,\n 'turn_failed',\n 'The assistant could not answer this message. Send it again.',\n );\n const metadata = parseObject(anchor.metadata) ?? {};\n const recorded = metadata.turnOutcome;\n // A linked assistant reply is the turn's final answer (the runner\n // stores exactly one, when the turn finishes; tool results are role\n // `tool`; a suspension stores none), so it is completion evidence even\n // if the outcome marker is stale or its write was lost.\n if (assistantMessage) return answer('completed');\n if (recorded === 'failed') throw turnFailed();\n if (recorded === 'cancelled') return answer('cancelled');\n if (recorded === 'completed') return answer('completed');\n if (recorded === 'suspended') {\n // Waiting on the browser: alive exactly as long as its continuation.\n const continuationId = nonEmpty(metadata.turnContinuationId);\n if (!continuationId) throw turnFailed();\n const store = continuationStoreFor(context, chat, session);\n const alive = store.has\n ? await store.has(threadId, continuationId)\n : Date.now() - new Date(String(metadata.turnSettledAt)).getTime() <=\n DEFAULT_CONTINUATION_TTL_MS;\n if (!alive) throw turnFailed();\n return answer('in_progress');\n }\n // Not settled yet (no marker), or a resumed leg running: abandoned once\n // older than `abandonedTurnMs`, measured from when it last started.\n const since =\n recorded === 'running' ? metadata.turnSettledAt : anchor.created_at;\n const age = Date.now() - new Date(String(since)).getTime();\n if (Number.isFinite(age) && age > abandonedTurnMs) throw turnFailed();\n return answer('in_progress');\n };\n\n /** Record the turn's outcome on its send; never fails the response. */\n const settleOutcome =\n (\n chat: ChatService,\n principal: AssistantPrincipal,\n threadId: string,\n messageId: string,\n ) =>\n async (\n outcome: ChatClientRequestOutcome,\n settings: { quiet?: boolean } = {},\n ) => {\n try {\n await chat.recordClientRequestOutcome({\n tenantId: principal.tenantId,\n threadId,\n messageId,\n actorProfileId: principal.profileId,\n outcome,\n resumedFrom: null,\n });\n } catch (error) {\n // `quiet`: the send may never have been stored.\n if (!settings.quiet) safeLog(error);\n }\n };\n\n /**\n * The turn runner's own lifecycle reports (`onState`), recorded on the send\n * they belong to. The send comes from the runner: the user message for a\n * first leg, the consumed continuation for a resumed one — never from the\n * request. Writes are monotonic compare-and-set (`ChatService`).\n */\n const recordTurnState =\n (chat: ChatService, principal: AssistantPrincipal, threadId: string) =>\n async (state: AssistantTurnState): Promise<boolean> => {\n // A continuation without an origin has no send to record against.\n if (!state.originMessageId) return true;\n return chat.recordClientRequestOutcome({\n tenantId: principal.tenantId,\n threadId,\n messageId: state.originMessageId,\n actorProfileId: principal.profileId,\n outcome: state.state,\n resumedFrom: state.resumedFrom,\n continuationId: state.continuationId ?? null,\n });\n };\n\n const sendMessage = async (\n baseContext: AssistantRequestContext,\n threadId: string,\n ) => {\n const body = await readJsonObject(baseContext.event.request, maxBody);\n const context = withRequestContext(baseContext, body.context);\n const content = typeof body.content === 'string' ? body.content.trim() : '';\n if (!content) throw badRequest('The message is empty.', 'empty_message');\n if (content.length > maxContent) {\n throw new AssistantRouteError(\n 413,\n 'message_too_long',\n `Messages are limited to ${maxContent} characters.`,\n );\n }\n const clientRequestId = body.clientRequestId;\n if (\n typeof clientRequestId !== 'string' ||\n !CLIENT_REQUEST_ID.test(clientRequestId)\n ) {\n throw badRequest('clientRequestId is missing or invalid.');\n }\n const references = body.attachments;\n if (\n references !== undefined &&\n references !== null &&\n (!Array.isArray(references) ||\n references.length > MAX_ATTACHMENTS_PER_MESSAGE)\n ) {\n throw badRequest('attachments must be a short list.');\n }\n const hasAttachments = Array.isArray(references) && references.length > 0;\n if (hasAttachments && !options.attachments) {\n throw badRequest(\n 'This assistant does not accept attachments.',\n 'attachments_unsupported',\n );\n }\n // Validate the model before any write.\n resolveModel(body.model);\n\n const { principal } = context;\n const chat = await chatFor(context);\n const session = await findSession(chat, principal);\n const thread = await requireOwnThread(chat, principal, session, threadId);\n if (!session?.chatRoomId) throw notFound();\n\n // The reservation is the user message's deterministic primary key, scoped\n // to tenant, room, thread, actor and clientRequestId: the database's\n // primary-key uniqueness makes it atomic and durable across replicas.\n const reservationId = clientRequestMessageId({\n tenantId: principal.tenantId,\n roomId: session.chatRoomId,\n threadId,\n actorProfileId: principal.profileId,\n clientRequestId,\n });\n const known = await reservationAnswer(\n context,\n chat,\n session,\n threadId,\n reservationId,\n );\n if (known) return known;\n\n let attachments: ChatMessageAttachment[] | null = null;\n if (hasAttachments && options.attachments) {\n const verified = await options.attachments.verify(\n references as unknown[],\n context,\n );\n if (!verified || verified.length !== (references as unknown[]).length) {\n throw badRequest(\n 'An attachment could not be found.',\n 'attachment_not_found',\n );\n }\n attachments = verified.map((record) => ({\n id: record.id,\n filename: record.name,\n contentType: record.contentType,\n size: record.size,\n ...(record.url ? { url: record.url } : {}),\n }));\n }\n\n // Everything that can refuse (the AI factory's 503 included) runs before\n // the reservation, so a refused send stores nothing.\n const prepared = await prepareTurn(context, body);\n const setup = await turnSetup(context, chat, session, thread, prepared);\n\n // A failure after the reservation may have been stored settles it as\n // failed (a no-op when nothing was stored), so a retry is told so at\n // once instead of waiting out `abandonedTurnMs`.\n const recordFailure = settleOutcome(\n chat,\n principal,\n threadId,\n reservationId,\n );\n let userMessage: ChatMessage;\n try {\n userMessage = await chat.sendMessage({\n tenantId: principal.tenantId,\n roomId: session.chatRoomId,\n threadId,\n actorProfileId: principal.profileId,\n agentSessionId: String(session.id),\n content,\n clientRequestId,\n attachments,\n });\n } catch (error) {\n if (error instanceof ChatClientRequestConflictError) {\n // Another request (any replica) took this reservation first.\n const answer = await reservationAnswer(\n context,\n chat,\n session,\n threadId,\n reservationId,\n );\n if (answer) return answer;\n } else {\n await recordFailure('failed', { quiet: true });\n }\n throw error;\n }\n const userWire = toAssistantMessageWire(userMessage);\n\n try {\n const history: AIMessage[] = [];\n if (historyLimit > 0) {\n // `historyLimit` counts replayed turns, not raw rows (#4458): the\n // role filter runs in SQL, so persisted tool rows never take a\n // slot. `+ 1` covers this send, which is already stored. The read\n // is bounded by the limit whatever the thread holds.\n const earlier = await chat.getThreadMessages({\n threadId,\n actorProfileId: principal.profileId,\n tenantId: principal.tenantId,\n limit: historyLimit + 1,\n roles: HISTORY_REPLAY_ROLES,\n });\n for (const message of earlier) {\n if (String(message.id) === userWire.id) continue;\n if (message.role !== 'user' && message.role !== 'assistant') {\n continue;\n }\n if (!message.content) continue;\n // A fallback reply is not a model answer: never replay it as one\n // (#4379); a fixed marker keeps the roles alternating.\n history.push({\n role: message.role,\n content:\n message.role === 'assistant' && isFallbackReply(message)\n ? FALLBACK_HISTORY_PLACEHOLDER\n : message.content,\n });\n }\n }\n\n const transcript = history.slice(-historyLimit);\n return await respondWithTurn(\n context,\n chat,\n session,\n thread,\n prepared,\n setup,\n { type: 'message', message: userWire },\n (turnChat, turnSetupFor) =>\n runAssistantTurn<AssistantMessageWire>({\n ...turnSetupFor.turn,\n history: transcript,\n userMessage: content,\n // Every reply links to this send, and a suspension keeps it.\n originMessageId: userWire.id,\n onState: recordTurnState(turnChat, principal, threadId),\n }),\n userWire.id,\n );\n } catch (error) {\n await recordFailure('failed');\n throw error;\n }\n };\n\n const resumeTurn = async (\n baseContext: AssistantRequestContext,\n threadId: string,\n ) => {\n const body = await readJsonObject(baseContext.event.request, maxBody);\n const context = withRequestContext(baseContext, body.context);\n const continuationId = requireId(body.continuationId, 'continuationId');\n if (\n !Array.isArray(body.results) ||\n body.results.length > MAX_CLIENT_TOOLS\n ) {\n throw badRequest('results must be a list of tool results.');\n }\n const results: ClientToolResultInput[] = body.results.map((raw) => {\n const entry = raw as Record<string, unknown> | null;\n if (!entry || typeof entry !== 'object') {\n throw badRequest('Each result must be an object.');\n }\n const id = requireId(entry.id, 'result id');\n if (typeof entry.ok !== 'boolean') {\n throw badRequest('Each result needs an ok flag.');\n }\n const result =\n typeof entry.result === 'string'\n ? entry.result.slice(0, MAX_CLIENT_TOOL_RESULT_CHARS)\n : undefined;\n const error =\n typeof entry.error === 'string' ? entry.error.slice(0, 500) : undefined;\n return {\n id,\n ok: entry.ok,\n ...(result !== undefined ? { result } : {}),\n ...(error !== undefined ? { error } : {}),\n };\n });\n resolveModel(body.model);\n\n const { principal } = context;\n const chat = await chatFor(context);\n const session = await findSession(chat, principal);\n const thread = await requireOwnThread(chat, principal, session, threadId);\n if (!session) throw notFound();\n const prepared = await prepareTurn(context, body);\n const setup = await turnSetup(context, chat, session, thread, prepared);\n // The send this leg belongs to comes only from the continuation it\n // consumes (stored server-side when the turn suspended), reported by the\n // runner; a request's `clientRequestId` is ignored here. A missing,\n // foreign or expired continuation is never consumed, so it changes no\n // send.\n return respondWithTurn(\n context,\n chat,\n session,\n thread,\n prepared,\n setup,\n null,\n (turnChat, turnSetupFor) =>\n runAssistantTurn<AssistantMessageWire>({\n ...turnSetupFor.turn,\n resume: { continuationId, results },\n onState: recordTurnState(turnChat, principal, threadId),\n }),\n );\n };\n\n const uploadAttachment = async (context: AssistantRequestContext) => {\n const storage = options.attachments;\n if (!storage) {\n throw new AssistantRouteError(\n 404,\n 'attachments_unsupported',\n 'This assistant does not accept attachments.',\n );\n }\n const request = context.event.request;\n const type = request.headers.get('content-type') ?? '';\n if (!/^multipart\\/form-data\\b/i.test(type)) {\n throw new AssistantRouteError(\n 415,\n 'unsupported_media_type',\n 'Upload a file as multipart/form-data.',\n );\n }\n const maxBytes = storage.maxBytes ?? DEFAULT_ASSISTANT_MAX_ATTACHMENT_BYTES;\n const bytes = await readBounded(request, maxBytes + 64 * 1024);\n let form: FormData;\n try {\n form = await new Response(\n bytes as unknown as ConstructorParameters<typeof Response>[0],\n {\n headers: { 'content-type': type },\n },\n ).formData();\n } catch {\n throw badRequest('The upload could not be read.');\n }\n const file = form.get('file');\n if (!file || typeof file === 'string') {\n throw badRequest('Attach the upload as the `file` field.');\n }\n if (file.size > maxBytes) {\n throw new AssistantRouteError(\n 413,\n 'payload_too_large',\n 'The file is too large.',\n );\n }\n const record = await storage.upload(file as File, context);\n return json(\n {\n attachment: {\n id: record.id,\n name: record.name,\n contentType: record.contentType,\n size: record.size,\n ...(record.url ? { url: record.url } : {}),\n },\n },\n 201,\n );\n };\n\n const runAction = async (\n context: AssistantRequestContext,\n phase: 'preview' | 'apply',\n ) => {\n if (!options.actions) {\n throw new AssistantRouteError(\n 404,\n 'actions_unsupported',\n 'This assistant has no actions.',\n );\n }\n const body = await readJsonObject(context.event.request, maxBody);\n if (body.phase !== phase) {\n throw badRequest(`Expected a ${phase} request.`);\n }\n const adapter = await resolveValue(options.actions.adapter, context);\n if (!adapter) throw new Error('No data-surface action adapter resolved.');\n const { principal } = context;\n const allowedTools = await allowedToolsFor(context);\n const db = await dbFor(context);\n const actionContext = {\n principal: {\n ...(db ? { db } : {}),\n principal: {\n runAsUserId: principal.userId,\n tenantId: principal.tenantId,\n allowedTools,\n },\n onBehalfOfUserId: principal.userId,\n ...(principal.permissions\n ? { permissions: principal.permissions }\n : {}),\n ...(options.audit ? { audit: options.audit } : {}),\n ...(options.postgresRls !== undefined\n ? { postgresRls: options.postgresRls }\n : {}),\n },\n } as Parameters<AssistantActionAdapter['preview']>[1];\n const request = body as unknown as Parameters<\n AssistantActionAdapter['preview']\n >[0];\n try {\n const result =\n phase === 'preview'\n ? await adapter.preview(request, actionContext)\n : await adapter.apply(request, actionContext);\n return json({ result });\n } catch (error) {\n safeLog(error);\n return json(\n {\n error:\n phase === 'apply'\n ? 'The change may or may not have been applied. Check again.'\n : GENERIC_ERROR,\n code: phase === 'apply' ? 'outcome_unknown' : 'internal_error',\n },\n 500,\n );\n }\n };\n\n // ---- dispatch ----------------------------------------------------------\n\n const handle: AssistantRouteHandler = async (event) => {\n try {\n const segments = segmentsOf(event);\n const match = segments ? matchRoute(segments) : null;\n if (!match) {\n throw new AssistantRouteError(404, 'not_found', 'Not found.');\n }\n const method = event.request.method.toUpperCase();\n const allowed = ROUTE_METHODS[match.route];\n if (!allowed.includes(method)) {\n return json(\n { error: 'Method not allowed.', code: 'method_not_allowed' },\n 405,\n { allow: allowed.join(', ') },\n );\n }\n if (\n method !== 'GET' &&\n checkOrigin &&\n !originAllowed(event.request, event.url, trustedOrigins)\n ) {\n throw new AssistantRouteError(\n 403,\n 'invalid_origin',\n 'Invalid request origin.',\n );\n }\n const principal = await resolvePrincipal(event);\n if (!principal) {\n throw new AssistantRouteError(\n 401,\n 'unauthenticated',\n 'Sign in to use the assistant.',\n );\n }\n if (\n !nonEmpty(principal.userId) ||\n !nonEmpty(principal.profileId) ||\n !nonEmpty(principal.tenantId)\n ) {\n throw new AssistantRouteError(\n 403,\n 'principal_incomplete',\n 'Your session cannot use the assistant.',\n );\n }\n const context: AssistantRequestContext = { event, principal };\n switch (match.route) {\n case 'threads':\n return method === 'GET'\n ? await listThreads(context)\n : await createThread(context);\n case 'thread-messages': {\n const threadId = requireId(match.threadId, 'thread id');\n return method === 'GET'\n ? await loadMessages(context, threadId)\n : await sendMessage(context, threadId);\n }\n case 'thread-resume':\n return await resumeTurn(\n context,\n requireId(match.threadId, 'thread id'),\n );\n case 'attachments':\n return await uploadAttachment(context);\n case 'actions':\n return await runAction(context, match.phase);\n }\n } catch (error) {\n if (error instanceof AssistantRouteError) return errorResponse(error);\n if (isAuthorizationFailure(error)) return errorResponse(notFound());\n safeLog(error);\n return json({ error: GENERIC_ERROR, code: 'internal_error' }, 500);\n }\n };\n\n return { GET: handle, POST: handle, handle };\n}\n\n/**\n * Events after which the browser acts on stored state (resumes a\n * suspension, retries, reloads the thread): a turn in its own database\n * lifetime holds them, and every event after them, until it commits.\n */\nconst HELD_UNTIL_COMMIT: ReadonlySet<string> = new Set([\n 'client_tool_calls',\n 'done',\n 'error',\n]);\n\n/** Default for `turnStartTimeoutMs`. */\nexport const DEFAULT_ASSISTANT_TURN_START_TIMEOUT_MS = 60_000;\n\n/**\n * The RLS request transaction this call runs in (`database-rls`), when\n * there is one. It commits when the request's handler chain returns.\n */\nfunction requestTransaction(): { isActive?: unknown } | null {\n if (getCurrentSessionPermissionContext()?.postgresRls !== true) return null;\n return (\n (getRequestScopedDatabase() as { isActive?: unknown } | undefined) ?? null\n );\n}\n\n/**\n * The permissions a turn in its own lifetime runs with: the bound\n * principal's effective `scopes` (live at bind time, capped by the runtime),\n * kept within `cap` as well so a runtime that ignored the cap cannot widen\n * them. Malformed scopes refuse the turn.\n */\nfunction effectiveScopes(\n granted: AssistantRouteBoundPrincipal | undefined,\n cap: readonly string[] | undefined,\n): string[] {\n const scopes = granted?.scopes;\n if (\n !Array.isArray(scopes) ||\n !scopes.every((scope) => typeof scope === 'string')\n ) {\n throw new Error(\n 'mountAssistantRoutes: runtime.runAsPrincipal bound no effective scopes.',\n );\n }\n const allowed = cap ? new Set(cap) : undefined;\n return scopes.filter((scope) => !allowed || allowed.has(scope));\n}\n\n/** Resolves `true` once `isActive()` is false, `false` after `timeoutMs`. */\nasync function requestEnded(\n isActive: () => boolean,\n timeoutMs: number,\n): Promise<boolean> {\n const deadline = Date.now() + timeoutMs;\n let delay = 2;\n while (isActive()) {\n if (Date.now() >= deadline) return false;\n await new Promise((resolve) => setTimeout(resolve, delay));\n delay = Math.min(delay * 2, 50);\n }\n return true;\n}\n\n/** A producer that forwards a turn's events. */\nfunction pump<M>(\n events: AsyncGenerator<AssistantTurnEvent<M>, unknown>,\n): (emit: (event: AssistantTurnEvent<M>) => void) => Promise<void> {\n return async (emit) => {\n for await (const event of events) emit(event);\n };\n}\n\n/**\n * Run a turn to its end regardless of the reader. `produce` starts at once\n * and its events go into a buffer the response reads from (after `first`,\n * when given); a reader that leaves only stops reading, and `settled`\n * resolves when the producer has finished. The turn itself records its\n * outcome (`onState`), so nothing here depends on how far the reader got. A\n * client disconnect cancels the turn — through the request's abort signal,\n * or the response body's cancellation when the adapter does not abort the\n * request — as the dock's Stop does; the runner then records `cancelled`\n * (or `completed`, if the reply was already stored).\n */\nfunction detached<M>(\n first: AssistantTurnEvent<M> | null,\n produce: (emit: (event: AssistantTurnEvent<M>) => void) => Promise<void>,\n): {\n events: AsyncGenerator<AssistantTurnEvent<M>, void>;\n settled: Promise<void>;\n} {\n const buffer: AssistantTurnEvent<M>[] = first ? [first] : [];\n let finished = false;\n let notify: (() => void) | null = null;\n const wake = () => {\n const resume = notify;\n notify = null;\n resume?.();\n };\n const settled = (async () => {\n try {\n await produce((event) => {\n buffer.push(event);\n wake();\n });\n } catch {\n // Producers report failures in-band and do not throw.\n } finally {\n finished = true;\n wake();\n }\n })();\n const events = (async function* () {\n for (;;) {\n if (buffer.length > 0) {\n yield buffer.shift() as AssistantTurnEvent<M>;\n continue;\n }\n if (finished) return;\n await new Promise<void>((resolve) => {\n notify = resolve;\n });\n }\n })();\n return { events, settled };\n}\n"],"mappings":"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"}