@coder/ai-sdk-agent 0.11.8 → 0.12.0

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.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/agent/coder-agent.ts","../src/errors.ts","../src/ai-version.ts","../src/files.ts","../src/coder/types.ts","../src/coder/ws.ts","../src/transport-events.ts","../src/coder/client.ts","../src/coder/workspaces.ts","../src/model/prompt.ts","../src/model/session-stream.ts","../src/model/translate.ts","../src/model/language-model.ts","../src/model/history.ts"],"sourcesContent":["import {\n type Agent,\n type FilePart,\n isStepCount,\n type StopCondition,\n type SystemModelMessage,\n type ToolChoice,\n ToolLoopAgent,\n type ToolLoopAgentSettings,\n type ToolSet,\n} from \"ai\";\nimport { assertSupportedAiVersion } from \"../ai-version.js\";\nimport {\n type ChatFileInput,\n CoderChatClient,\n type CoderChatClientOptions,\n type UploadedChatFile,\n} from \"../coder/client.js\";\nimport type { ChatModelConfig, ReasoningEffort } from \"../coder/types.js\";\nimport {\n type PreviewShareLevel,\n resolveWorkspacePreview,\n shareWorkspacePreview,\n type WorkspaceApiConnection,\n} from \"../coder/workspaces.js\";\nimport { CoderAgentError, CoderApiError } from \"../errors.js\";\nimport type { FileContent } from \"../files.js\";\nimport type { TransportEventHandler } from \"../transport-events.js\";\nimport { CoderLanguageModel } from \"../model/language-model.js\";\nimport { CODER_PROVIDER_OPTIONS } from \"../model/prompt.js\";\nimport type { WorkspaceFileStore, WorkspacePlacement } from \"../workspace-files.js\";\n\ntype InnerAgent<TOOLS extends ToolSet> = ToolLoopAgent<never, TOOLS>;\n\n/** A handle to a file uploaded as a chat attachment (see {@link CoderAgent.attach}). */\nexport interface ChatAttachment extends UploadedChatFile {\n /**\n * A native AI SDK `file` part that references this upload by id. Drop it into\n * a user message's `content` to reuse the file across turns without\n * re-uploading — the agent recognizes the id and skips the upload.\n *\n * The reference travels in `providerOptions.coder.fileId`; if you persist the\n * part through a store that strips `providerOptions`, the reference is lost\n * (re-`attach()` instead of persisting the handle).\n */\n toFilePart(): FilePart;\n}\n\nfunction makeChatAttachment(file: UploadedChatFile): ChatAttachment {\n return {\n ...file,\n toFilePart: () => ({\n type: \"file\",\n // Bytes are unused: the reference travels in providerOptions.coder.fileId.\n data: \"\",\n mediaType: file.mediaType,\n ...(file.name ? { filename: file.name } : {}),\n providerOptions: { [CODER_PROVIDER_OPTIONS]: { fileId: file.id } },\n }),\n };\n}\n\n/** Default step ceiling. Each \"step\" is one client-tool round-trip; chatd caps\n * its own server-side loop at 1200 steps independently. */\nconst DEFAULT_STOP = isStepCount(64);\n\n/**\n * Bounded-cleanup defaults. An interrupted chat keeps winding down server-side\n * for a few seconds, during which archiving 409s — {@link CoderAgent.archive}\n * and `[Symbol.asyncDispose]` retry those 409s every ~`SETTLE_RETRY_DELAY_MS`\n * for up to ~`SETTLE_DEADLINE_MS` overall before giving up.\n */\nconst SETTLE_DEADLINE_MS = 15_000;\nconst SETTLE_RETRY_DELAY_MS = 1_000;\n\n/**\n * Clamps a user-supplied delay to what `AbortSignal.timeout`/`setTimeout`\n * accept: a non-negative integer no larger than the 32-bit timer cap.\n * Anything else (negative, fractional beyond flooring, `NaN`, `Infinity`)\n * falls back to the default rather than throwing at dispose time.\n */\nfunction sanitizeDelayMs(value: number | undefined, fallback: number): number {\n const MAX_TIMER_MS = 2 ** 31 - 1;\n if (value === undefined || Number.isNaN(value)) return fallback;\n if (value === Number.POSITIVE_INFINITY) return MAX_TIMER_MS;\n if (!Number.isFinite(value) || value < 0) return fallback;\n return Math.min(Math.floor(value), MAX_TIMER_MS);\n}\n\n/** Abort-aware sleep: resolves after `ms`, or rejects with the signal's reason. */\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(signal.reason);\n return;\n }\n const onAbort = () => {\n clearTimeout(timer);\n reject(signal?.reason);\n };\n const timer = setTimeout(() => {\n signal?.removeEventListener(\"abort\", onAbort);\n resolve();\n }, ms);\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n });\n}\n\n/**\n * Archive a chat, retrying while it is still settling. Interrupting a run does\n * not stop it instantly — chatd winds it down for a few more seconds and 409s\n * archive attempts in that window. Only those 409s are retried (other failures\n * and caller aborts rethrow immediately); once `deadlineMs` has elapsed the\n * last 409 is rethrown.\n */\nasync function archiveWhenSettled(\n client: CoderChatClient,\n chatId: string,\n opts: { deadlineMs: number; retryDelayMs: number; signal?: AbortSignal },\n): Promise<void> {\n // The deadline decides when to stop retrying; the timeout signal additionally\n // unsticks an archive request that hangs (both track the same clock).\n const timeout = AbortSignal.timeout(opts.deadlineMs);\n const signal = opts.signal ? AbortSignal.any([opts.signal, timeout]) : timeout;\n const deadline = Date.now() + opts.deadlineMs;\n for (;;) {\n try {\n await client.archiveChat(chatId, signal);\n return;\n } catch (err) {\n if (!(err instanceof CoderApiError) || err.status !== 409) throw err;\n // Stop when there is no budget left for another pause + attempt, surfacing\n // the 409 (the actionable error) rather than an opaque timeout.\n if (Date.now() + opts.retryDelayMs > deadline) throw err;\n // Pause on the caller's signal only — the deadline is enforced above.\n await sleep(opts.retryDelayMs, opts.signal);\n }\n }\n}\n\n/**\n * Result of {@link CoderAgent.interrupt}: `interrupted: false` means there was\n * no chat to interrupt (neither a live session nor a\n * {@link CoderAgent.lastKnownChatId}) and no server call was made.\n */\nexport type CoderInterruptResult =\n | { interrupted: true; chatId: string }\n | { interrupted: false; chatId?: undefined };\n\n/**\n * Result of {@link CoderAgent.archive}: `archived: false` means there was no\n * chat to archive (neither a live session nor a\n * {@link CoderAgent.lastKnownChatId}) and no server call was made. Failures\n * still throw — see {@link CoderAgent.archive}. `chatId` is the primary\n * target (the session's or last-known chat); `archivedChatIds` additionally\n * lists every chat archived by the call, oldest first — more than one when\n * earlier stranded chats ({@link CoderAgent.strandedChatIds}) were retired\n * alongside it.\n */\nexport type CoderArchiveResult =\n | { archived: true; chatId: string; archivedChatIds: string[] }\n | { archived: false; chatId?: undefined; archivedChatIds?: undefined };\nexport interface CoderAgentSettings<TOOLS extends ToolSet = {}> {\n // --- connection (provide a client, or baseUrl + token) ---\n /**\n * A pre-built {@link CoderChatClient}, used instead of constructing one from\n * `baseUrl`/`token`. The preview helpers ({@link CoderAgent.getPreview},\n * {@link CoderAgent.sharePreview}) call non-chat endpoints and therefore\n * still need credentials — pass `baseUrl` + `token` alongside `client` (or\n * set `CODER_URL` + `CODER_SESSION_TOKEN`) to enable them.\n */\n client?: CoderChatClient;\n /** Coder deployment base URL, e.g. `https://dogfood.cdr.dev`. Defaults to `CODER_URL`. */\n baseUrl?: string;\n /** Coder API/session token (sent as `Coder-Session-Token`). Defaults to `CODER_SESSION_TOKEN`. */\n token?: string;\n fetch?: CoderChatClientOptions[\"fetch\"];\n webSocketFactory?: CoderChatClientOptions[\"webSocketFactory\"];\n /**\n * Observability hook: receives typed transport events — HTTP request/\n * response timings, the per-chat stream's WebSocket lifecycle (dial/open/\n * events/close/redial), and turn-segment start/settle — so timing and\n * tracing need no `fetch`/`webSocketFactory` wrapping. Exceptions it throws\n * are swallowed; without it, no event objects are allocated. NOTE: when a\n * pre-built `client` is supplied, its own `onTransportEvent` (set at client\n * construction) emits the HTTP/WebSocket events — this setting then only\n * receives the `segment:*` events. See {@link CoderTransportEvent}.\n */\n onTransportEvent?: TransportEventHandler;\n\n // --- Coder chat configuration ---\n /** Organization UUID that owns the chat (required). */\n organizationId: string;\n /** Model hint: UUID, `provider:model`, model id, or display-name substring. */\n model?: string;\n /** Reasoning effort sent on chat creation and each user-message submission. */\n reasoningEffort?: ReasoningEffort;\n /** Bind the chat to a Coder workspace (enables workspace-scoped tools). */\n workspaceId?: string;\n /**\n * Adapter for writing files onto a workspace filesystem, enabling\n * {@link CoderAgent.uploadToWorkspace}. Supply one backed by a workspace\n * connection (e.g. a `CoderWorkspaceSession` from `@coder/ai-sdk-sandbox`).\n */\n workspaceFiles?: WorkspaceFileStore;\n /** chatd-side MCP servers to enable for this chat. */\n mcpServerIds?: string[];\n /** chatd plan mode. */\n planMode?: \"\" | \"plan\";\n /** Resume an existing chat (session) instead of creating a new one. */\n chatId?: string;\n /**\n * Resume cursor for `chatId`: the newest chat message id a previous\n * instance had already seen. Persist {@link CoderAgent.lastSeenMessageId}\n * alongside the chat id when a turn ends and supply both here — the resumed\n * turn then skips its pre-prompt seed GET (the single-message history probe\n * that would otherwise establish the cursor). Without it, behavior is\n * unchanged. Must be a positive integer obtained from the getter, and\n * requires `chatId`; anything else fails construction.\n */\n lastSeenMessageId?: number;\n\n // --- agent configuration ---\n /** Stable agent id (exposed as {@link CoderAgent.id}). */\n id?: string;\n /** System instructions → chatd `system_prompt`. */\n instructions?: string | SystemModelMessage | Array<SystemModelMessage>;\n /** Custom (client-executed) tools. Each should have an `execute` for scripting. */\n tools?: TOOLS;\n /**\n * Per-tool context values for tools that declare a `contextSchema`, keyed by\n * tool name (forwarded to the AI SDK loop). Required by the SDK at runtime\n * whenever a supplied tool declares contextual data.\n */\n toolsContext?: ToolLoopAgentSettings<never, TOOLS>[\"toolsContext\"];\n toolChoice?: ToolChoice<NoInfer<TOOLS>>;\n /** Stop condition(s) for the AI SDK loop. Default `isStepCount(64)`. */\n stopWhen?: StopCondition<NoInfer<TOOLS>> | Array<StopCondition<NoInfer<TOOLS>>>;\n /**\n * SDK-level retries. Default `0`: this agent owns server-side chat state, so\n * SDK retries could duplicate a turn. Override with care.\n */\n maxRetries?: number;\n /**\n * Per-segment time budget in milliseconds, applied to each model round-trip\n * (chat creation / message / tool-result submission plus the server run until\n * it settles). If a segment runs longer (e.g. the server is wedged or a\n * workspace can't be scheduled), the run is interrupted server-side and the\n * call rejects with a retryable {@link CoderChatError} (`kind: \"timeout\"`)\n * instead of hanging. A multi-step `generate()` driving client tools makes\n * several segments, so this bounds each one — to cap total wall-clock for the\n * whole call, pass `abortSignal: AbortSignal.timeout(ms)`. Unset or\n * non-positive means no limit.\n */\n requestTimeoutMs?: number;\n /**\n * Overall deadline in milliseconds for bounded cleanup: how long\n * {@link CoderAgent.archive} keeps retrying while a freshly interrupted chat\n * settles server-side, and how long `[Symbol.asyncDispose]` may take in\n * total. Default 15 000. Primarily a test/tuning knob.\n */\n settleDeadlineMs?: number;\n /**\n * Pause in milliseconds between {@link CoderAgent.archive} retries while the\n * chat settles. Default 1000. Primarily a test/tuning knob.\n */\n settleRetryDelayMs?: number;\n}\n\n/** Options for {@link CoderAgent.getPreview}. */\nexport interface WorkspacePreviewOptions {\n /** Workspace port the app listens on (4–5 digit ports parse most reliably). */\n port: number;\n /**\n * Agent that serves the port. Optional when the workspace has exactly one\n * agent; required when it has several (the error lists the candidates).\n */\n agentName?: string;\n /**\n * Protocol the app speaks *inside the workspace* on that port — `https` adds\n * the `s` suffix (`3000s--…`) so the proxy connects over TLS. This is not\n * the browser scheme, which follows the deployment's access URL.\n * Default `\"http\"`.\n */\n protocol?: \"http\" | \"https\";\n signal?: AbortSignal;\n}\n\n/** A resolved workspace preview URL (see {@link CoderAgent.getPreview}). */\nexport interface WorkspacePreview {\n /** Browser-openable subdomain app URL, e.g. `https://3000--main--dev--alice.apps.example.com`. */\n url: string;\n}\n\n/** Options for {@link CoderAgent.sharePreview}. */\nexport interface SharePreviewOptions extends WorkspacePreviewOptions {\n /**\n * Who may open the preview: `authenticated` (any logged-in user; the\n * default), `organization` (members of the workspace's organization;\n * requires a newer Coder server), or `public` (no auth at all — mind what\n * the port serves).\n */\n shareLevel?: PreviewShareLevel;\n}\n\n/** A shared workspace preview (see {@link CoderAgent.sharePreview}). */\nexport interface SharedWorkspacePreview extends WorkspacePreview {\n /** The share level now in effect for the port. */\n shareLevel: PreviewShareLevel;\n}\n\n/**\n * An AI SDK-compliant agent backed by a remote Coder `chatd` agent runtime.\n *\n * `new CoderAgent({ ... })` returns an object that implements the AI SDK\n * `Agent` interface (`generate()`/`stream()`), so it composes with the rest of\n * the AI SDK ecosystem. Internally it wraps a {@link ToolLoopAgent} whose model\n * is a {@link CoderLanguageModel}; chatd runs the actual agent loop server-side.\n *\n * One `CoderAgent` instance corresponds to one chatd chat (\"session\"): the chat\n * is created on the first `generate()`/`stream()` and reused for subsequent\n * turns. Use {@link CoderAgent.resetSession} to start a fresh chat.\n */\nexport class CoderAgent<TOOLS extends ToolSet = {}> implements Agent<never, TOOLS> {\n readonly version = \"agent-v1\" as const;\n\n readonly #client: CoderChatClient;\n readonly #model: CoderLanguageModel;\n readonly #inner: InnerAgent<TOOLS>;\n readonly #organizationId: string;\n readonly #workspaceFiles: WorkspaceFileStore | undefined;\n readonly #workspaceId: string | undefined;\n readonly #workspaceApi: WorkspaceApiConnection | undefined;\n readonly #settleDeadlineMs: number;\n readonly #settleRetryDelayMs: number;\n\n constructor(settings: CoderAgentSettings<TOOLS>) {\n // Fail fast on an incompatible AI SDK major (see peer dependency `ai@^7`).\n assertSupportedAiVersion();\n\n // Resolved once, up front, so every consumer of the settings credentials\n // (the client bootstrap below and the preview helpers' REST connection)\n // sees the same values. Explicit settings win over the environment,\n // matching the sandbox transports' convention. `process` is missing in\n // browser bundles (the injected-`webSocketFactory` path), so reach it via\n // `globalThis` — client-only construction must not throw a ReferenceError.\n const env = globalThis.process?.env;\n const baseUrl = settings.baseUrl ?? env?.CODER_URL;\n const token = settings.token ?? env?.CODER_SESSION_TOKEN;\n\n if (settings.client) {\n this.#client = settings.client;\n } else if (baseUrl && token) {\n this.#client = new CoderChatClient({\n baseUrl,\n token,\n fetch: settings.fetch,\n webSocketFactory: settings.webSocketFactory,\n onTransportEvent: settings.onTransportEvent,\n });\n } else {\n throw new CoderAgentError(\n \"CoderAgent requires either `client` or both `baseUrl` and `token` \" +\n \"(or the CODER_URL and CODER_SESSION_TOKEN environment variables).\",\n );\n }\n\n this.#organizationId = settings.organizationId;\n this.#workspaceFiles = settings.workspaceFiles;\n this.#workspaceId = settings.workspaceId;\n // The preview helpers call stable v2 endpoints that CoderChatClient (chat\n // scoped) does not expose, so they need the raw credentials when given.\n this.#workspaceApi = baseUrl && token ? { baseUrl, token, fetch: settings.fetch } : undefined;\n // Sanitized so `AbortSignal.timeout` (which rejects non-integer, negative,\n // and non-finite delays with a RangeError) can never make disposal throw.\n this.#settleDeadlineMs = sanitizeDelayMs(settings.settleDeadlineMs, SETTLE_DEADLINE_MS);\n this.#settleRetryDelayMs = sanitizeDelayMs(settings.settleRetryDelayMs, SETTLE_RETRY_DELAY_MS);\n\n // A file written to one workspace isn't visible to a chat bound to another.\n if (\n settings.workspaceFiles &&\n settings.workspaceId &&\n settings.workspaceFiles.workspaceId !== settings.workspaceId\n ) {\n throw new CoderAgentError(\n `workspaceFiles.workspaceId (${settings.workspaceFiles.workspaceId}) does not match the ` +\n `agent's workspaceId (${settings.workspaceId}); the chat's tools would not see uploaded files.`,\n );\n }\n\n this.#model = new CoderLanguageModel({\n client: this.#client,\n organizationId: settings.organizationId,\n model: settings.model,\n reasoningEffort: settings.reasoningEffort,\n workspaceId: settings.workspaceId,\n mcpServerIds: settings.mcpServerIds,\n planMode: settings.planMode,\n chatId: settings.chatId,\n lastSeenMessageId: settings.lastSeenMessageId,\n requestTimeoutMs: settings.requestTimeoutMs,\n onTransportEvent: settings.onTransportEvent,\n });\n\n // Whether `toolsContext` is required depends on the concrete TOOLS the\n // caller supplies (tools with a `contextSchema`), a conditional TypeScript\n // cannot resolve for a generic TOOLS — hence the cast. The field itself is\n // typed on CoderAgentSettings, so callers still get the checked shape.\n this.#inner = new ToolLoopAgent<never, TOOLS>({\n model: this.#model,\n id: settings.id,\n instructions: settings.instructions,\n tools: settings.tools,\n toolsContext: settings.toolsContext,\n toolChoice: settings.toolChoice,\n stopWhen: settings.stopWhen ?? DEFAULT_STOP,\n maxRetries: settings.maxRetries ?? 0,\n } as ToolLoopAgentSettings<never, TOOLS>);\n }\n\n get id(): string | undefined {\n return this.#inner.id;\n }\n\n get tools(): TOOLS {\n return this.#inner.tools;\n }\n\n /** The underlying chatd client. */\n get client(): CoderChatClient {\n return this.#client;\n }\n\n /** The current chatd chat id, once a turn has started. */\n get chatId(): string | undefined {\n return this.#model.chatId;\n }\n\n /**\n * The session's resume cursor: the newest chat message id observed, or\n * `undefined` before any turn has streamed. Persist it alongside\n * {@link chatId} when a turn ends and supply both to a future instance\n * ({@link CoderAgentSettings.lastSeenMessageId}) to skip the resumed turn's\n * pre-prompt seed GET. Read it between turns, not mid-generation.\n */\n get lastSeenMessageId(): number | undefined {\n return this.#model.lastSeenMessageId;\n }\n\n /**\n * The last chat id this agent created or attached to, retained for CLEANUP\n * even after the session was dropped — a fresh-chat stream failure discards\n * the session ({@link CoderAgent.chatId} becomes `undefined`, so a retry\n * starts fresh), but the failed chat still exists server-side.\n * {@link CoderAgent.archive} and {@link CoderAgent.interrupt} fall back to\n * this id; generation never does. Superseded when a later turn creates or\n * attaches to another chat; cleared once {@link CoderAgent.archive}\n * succeeds against it.\n */\n get lastKnownChatId(): string | undefined {\n return this.#model.lastKnownChatId;\n }\n\n /**\n * Chat ids stranded by automatic session discards and not yet cleaned up,\n * oldest first. Each retryable fresh-chat stream failure strands one chat,\n * so with `maxRetries` > 0 several can accumulate while only the final\n * attempt's error (and {@link CoderAgent.lastKnownChatId}) names the\n * newest. {@link CoderAgent.archive} retires all of them.\n */\n get strandedChatIds(): string[] {\n return this.#model.strandedChatIds;\n }\n\n generate(\n options: Parameters<InnerAgent<TOOLS>[\"generate\"]>[0],\n ): ReturnType<InnerAgent<TOOLS>[\"generate\"]> {\n return this.#inner.generate(options);\n }\n\n stream(\n options: Parameters<InnerAgent<TOOLS>[\"stream\"]>[0],\n ): ReturnType<InnerAgent<TOOLS>[\"stream\"]> {\n return this.#inner.stream(options);\n }\n\n // --- session helpers ------------------------------------------------------\n\n /**\n * List the model configs available to this agent's organization. Use this\n * to discover valid values for the `model` hint instead of guessing ids —\n * match on `id`, `provider`/`model`, or `display_name`.\n */\n listModels(signal?: AbortSignal): Promise<ChatModelConfig[]> {\n return this.#client.listModelConfigs(this.#organizationId, signal);\n }\n\n /**\n * Start a fresh chatd chat on the next turn. The dropped chat still exists\n * server-side: {@link CoderAgent.archive} / {@link CoderAgent.interrupt}\n * keep targeting it (via {@link CoderAgent.lastKnownChatId}) until a new\n * chat supersedes it or an archive succeeds against it.\n */\n resetSession(): void {\n this.#model.resetSession();\n }\n\n /**\n * Interrupt the in-flight generation, if any. Resolves as soon as the server\n * acknowledges the interrupt — the run keeps winding down asynchronously for\n * a few seconds afterwards (see {@link CoderAgent.archive}).\n *\n * Targets the session's chat, or — after the session was dropped (a\n * fresh-chat stream failure, or {@link CoderAgent.resetSession}) — the\n * {@link CoderAgent.lastKnownChatId}, whose run may still be winding down\n * server-side. With neither, there is nothing to interrupt and it resolves\n * `{ interrupted: false }` without a server call (inspect the result rather\n * than relying on a silent no-op).\n */\n async interrupt(opts?: { signal?: AbortSignal }): Promise<CoderInterruptResult> {\n const id = this.#model.chatId ?? this.#model.lastKnownChatId;\n if (!id) return { interrupted: false };\n // A stream paused for client tools dies with the run it was following:\n // close it FIRST so a later resume dials fresh instead of consuming the\n // interrupt's settle events as the resumed generation's, and so the\n // socket cannot outlive an explicit interrupt when the tool loop never\n // resumes. (A stream an active segment is reading is left to that\n // segment, which observes the settle and closes it.) Local and instant.\n await this.#model.closePausedStream();\n await this.#client.interruptChat(id, opts?.signal);\n return { interrupted: true, chatId: id };\n }\n\n /**\n * Archive the underlying chat (safe cleanup; hides it from listings).\n *\n * Targets the session's chat, or — after the session was dropped (a\n * fresh-chat stream failure, or {@link CoderAgent.resetSession}) — the\n * {@link CoderAgent.lastKnownChatId}, so the stranded chat is still cleaned\n * up; chats stranded by EARLIER automatic discards\n * ({@link CoderAgent.strandedChatIds} — `maxRetries` can accumulate several)\n * are retired in the same call. On success each archived id is cleared as a\n * cleanup target; with no id at all, there is nothing to archive and it\n * resolves `{ archived: false }` without a server call (inspect the result\n * rather than relying on a silent no-op).\n *\n * A freshly interrupted/settled chat can keep winding down server-side for a\n * few seconds, during which the server rejects archiving with a 409. Those\n * 409s are retried (~1s apart — see `settleRetryDelayMs`) inside ONE\n * `settleDeadlineMs` window (~15s) shared by the whole call — however many\n * chats it archives — and the last 409 is rethrown if the window closes\n * first; any other failure, including a caller abort, rethrows immediately.\n * Chats are archived oldest first, each id cleared as its archive succeeds —\n * a mid-list failure rethrows and leaves the remaining ids targetable by a\n * later retry.\n */\n async archive(opts?: { signal?: AbortSignal }): Promise<CoderArchiveResult> {\n const primary = this.#model.chatId ?? this.#model.lastKnownChatId;\n if (!primary) return { archived: false };\n // The guaranteed-cleanup path also releases a stream retained by a\n // client-tool pause the caller abandoned (e.g. `stopWhen` ended the tool\n // loop, or a tool had no execute handler) — the socket must not outlive\n // the archived chat. Local and instant; an actively-read stream is left\n // to its segment, which closes it when the run settles.\n await this.#model.closePausedStream();\n // Oldest stranded chats first, primary last (deduplicated — after a\n // single discard the primary IS the newest stranded id): earlier ids'\n // runs were interrupted attempts ago, so they archive without a settle\n // wait, and a failure part-way leaves the rest pending, not forgotten.\n const targets = [...new Set([...this.#model.strandedChatIds, primary])];\n // One settle window for the WHOLE batch, not per target, so archive()\n // keeps its documented ~settleDeadlineMs bound however many chats it\n // retires: each target gets only the remaining budget (floored at 1ms\n // for AbortSignal.timeout's sake — with the window already exhausted,\n // the attempt aborts at once and the id stays targetable later).\n const deadline = Date.now() + this.#settleDeadlineMs;\n for (const id of targets) {\n await archiveWhenSettled(this.#client, id, {\n deadlineMs: Math.max(deadline - Date.now(), 1),\n retryDelayMs: this.#settleRetryDelayMs,\n signal: opts?.signal,\n });\n this.#model.noteChatCleanedUp(id);\n }\n return { archived: true, chatId: primary, archivedChatIds: targets };\n }\n\n /**\n * Clean up the chat when the agent leaves an `await using` scope, so cleanup\n * rides scope exit instead of a separate call you have to remember in a\n * `finally`. Interrupts any in-flight server run, then archives the chat\n * (retrying while the interrupted run settles) — including a chat stranded\n * by a session discard (see {@link CoderAgent.lastKnownChatId}), which a\n * failed `generate()` would otherwise leak. Best-effort and bounded\n * (~15s overall): disposal errors are swallowed after the bounded attempts\n * so they can't mask the scope's own error — call {@link CoderAgent.archive}\n * directly when you need guaranteed cleanup.\n *\n * @example\n * ```ts\n * await using agent = new CoderAgent({ ... });\n * const { text } = await agent.generate({ prompt: \"…\" });\n * // agent.interrupt() + agent.archive() run automatically here.\n * ```\n */\n async [Symbol.asyncDispose](): Promise<void> {\n // Release the session's retained event stream first (issue #44): a turn\n // that paused for client tools, aborted, or timed out leaves its WebSocket\n // open for reuse, and scope exit is where it must be freed. Local-only and\n // instant; never throws.\n try {\n await this.#model[Symbol.asyncDispose]();\n } catch {\n /* best-effort cleanup */\n }\n // archive() only soft-hides the chat; it does not stop a generation. If the\n // scope exits mid-turn (e.g. generate() threw, or an early return), interrupt\n // first so chatd stops generating and releases the workspace, then archive.\n // One shared timeout bounds both calls so scope exit can never hang.\n const deadline = AbortSignal.timeout(this.#settleDeadlineMs);\n try {\n await this.interrupt({ signal: deadline });\n } catch {\n /* best-effort cleanup */\n }\n try {\n await this.archive({ signal: deadline });\n } catch {\n /* best-effort cleanup */\n }\n }\n\n // --- files ----------------------------------------------------------------\n\n /**\n * Upload a file as a chat attachment — content for the model to *read* (a PDF,\n * image, CSV, …). Returns a handle you can reference from a later turn via\n * {@link ChatAttachment.toFilePart} (no re-upload). Validated against the chat\n * allowlist and 10 MiB cap up front.\n *\n * You usually don't need this: a native AI SDK `file` part dropped into a\n * message's `content` is uploaded transparently. Reach for `attach()` to\n * pre-upload, to reuse one file across turns, or to validate before sending.\n *\n * For large or non-allowlisted files (zips, datasets, binaries) — material for\n * the agent to *operate on* — use {@link CoderAgent.uploadToWorkspace}.\n */\n async attach(file: ChatFileInput, signal?: AbortSignal): Promise<ChatAttachment> {\n const uploaded = await this.#client.uploadChatFile(this.#organizationId, file, signal);\n return makeChatAttachment(uploaded);\n }\n\n /**\n * Write a file onto the bound workspace's filesystem for the agent to operate\n * on (extract, build, read with its tools). Requires a {@link WorkspaceFileStore}\n * via the `workspaceFiles` setting. The file is written as-is — no unpacking;\n * instruct the agent to `unzip`/`tar -x`, or extract over your own connection.\n *\n * The chat should be bound to the same workspace (`workspaceId`) so the agent's\n * server-side tools can see the file.\n */\n async uploadToWorkspace(file: {\n content: FileContent;\n /** Destination path inside the workspace (the adapter may resolve relatives). */\n path: string;\n signal?: AbortSignal;\n }): Promise<WorkspacePlacement> {\n if (!this.#workspaceFiles) {\n throw new CoderAgentError(\n \"uploadToWorkspace requires a `workspaceFiles` adapter. Construct the agent with \" +\n \"`workspaceFiles` (e.g. backed by a @coder/ai-sdk-sandbox workspace session) to enable it.\",\n );\n }\n const { path } = await this.#workspaceFiles.write({\n content: file.content,\n path: file.path,\n signal: file.signal,\n });\n return { workspaceId: this.#workspaceFiles.workspaceId, path };\n }\n\n // --- previews ---------------------------------------------------------------\n\n /** The v2 connection + workspace id the preview helpers need, or a clear error. */\n #previewContext(method: string): { conn: WorkspaceApiConnection; workspaceId: string } {\n if (!this.#workspaceId) {\n throw new CoderAgentError(\n `${method}() requires the agent to be bound to a workspace — construct the CoderAgent ` +\n `with the \\`workspaceId\\` setting.`,\n );\n }\n if (!this.#workspaceApi) {\n throw new CoderAgentError(\n `${method}() needs the deployment's REST credentials — construct the CoderAgent with ` +\n `\\`baseUrl\\` and \\`token\\` (they can accompany \\`client\\`), or set the CODER_URL and ` +\n `CODER_SESSION_TOKEN environment variables.`,\n );\n }\n return { conn: this.#workspaceApi, workspaceId: this.#workspaceId };\n }\n\n /**\n * Resolve the browser URL where a port on the bound workspace can be\n * previewed, e.g. `https://3000--main--dev--alice.apps.example.com`.\n * Composed from stable v2 endpoints (workspace lookup + wildcard app host),\n * so it works against old Coder servers. Requires the `workspaceId` setting,\n * `baseUrl`/`token` credentials, and a deployment with a wildcard access URL\n * configured.\n *\n * The URL honors the port's current share level — private to the workspace\n * owner unless shared (see {@link CoderAgent.sharePreview}).\n */\n async getPreview(opts: WorkspacePreviewOptions): Promise<WorkspacePreview> {\n const { conn, workspaceId } = this.#previewContext(\"getPreview\");\n const { url } = await resolveWorkspacePreview(\n conn,\n { workspaceId, port: opts.port, agentName: opts.agentName, protocol: opts.protocol },\n opts.signal,\n );\n return { url };\n }\n\n /**\n * Share a port on the bound workspace and return its preview URL. Upserts\n * the port's share level — `authenticated` by default — so teammates (or,\n * with `public`, anyone) can open what the agent is serving; re-invoking\n * updates the level in place. On Coder servers that predate port sharing\n * (< v2.9) this fails with a 404 {@link CoderApiError} saying so.\n */\n async sharePreview(opts: SharePreviewOptions): Promise<SharedWorkspacePreview> {\n const { conn, workspaceId } = this.#previewContext(\"sharePreview\");\n return shareWorkspacePreview(\n conn,\n {\n workspaceId,\n port: opts.port,\n agentName: opts.agentName,\n protocol: opts.protocol,\n shareLevel: opts.shareLevel,\n },\n opts.signal,\n );\n }\n}\n","import { APICallError } from \"@ai-sdk/provider\";\nimport type { ChatErrorPayload } from \"./coder/types.js\";\n\n/** Base error for all `@coder/ai-sdk-agent` failures. */\nexport class CoderAgentError extends Error {\n override name = \"CoderAgentError\";\n constructor(message: string, options?: { cause?: unknown }) {\n super(message, options);\n }\n}\n\n/** An HTTP request to the Coder API failed. */\nexport class CoderApiError extends CoderAgentError {\n override name = \"CoderApiError\";\n readonly status: number;\n readonly detail: string | undefined;\n readonly method: string;\n readonly path: string;\n constructor(args: {\n status: number;\n method: string;\n path: string;\n message: string;\n detail?: string;\n }) {\n super(\n `Coder API ${args.method} ${args.path} failed (${args.status}): ${args.message}` +\n (args.detail ? ` — ${args.detail}` : \"\"),\n );\n this.status = args.status;\n this.detail = args.detail;\n this.method = args.method;\n this.path = args.path;\n }\n}\n\n/**\n * The per-chat event stream dropped and could not be re-established within its\n * redial budget (or could not be safely resumed — see `streamChatEvents` on\n * servers without delta coordinates). Extends the AI SDK's {@link APICallError} with\n * `isRetryable: true` because the `ai` package's retry machinery (`maxRetries`)\n * only honors `APICallError`/`GatewayError`-branded errors — plain `Error`\n * subclasses are never retried no matter what they claim. Note the retry\n * wrapper only sees errors that reject the model call itself (`generate()`);\n * a failure mid-`stream()` surfaces on the stream, past that wrapper. The last\n * underlying transport failure is preserved as `cause`.\n *\n * NOTE: this is deliberately NOT a {@link CoderAgentError} (single\n * inheritance); match it with `APICallError.isInstance(err)` or `err.name`.\n */\nexport class CoderStreamError extends APICallError {\n override name = \"CoderStreamError\";\n /**\n * The chatd chat id the failed turn had created or attached to, when known.\n * A fresh-chat stream failure discards the session so a retry starts on a\n * fresh chat (`CoderAgent.chatId` becomes `undefined`), but the failed chat\n * still exists server-side — this field names it so callers can clean it up\n * (`CoderAgent.archive()` also retains it as the last-known cleanup target).\n * `undefined` when the failure happened before any chat existed.\n */\n readonly chatId: string | undefined;\n constructor(args: {\n message: string;\n url: string;\n cause?: unknown;\n isRetryable?: boolean;\n chatId?: string;\n }) {\n super({\n message: args.message,\n url: args.url,\n requestBodyValues: undefined,\n // Retries re-invoke the model with the SAME prompt, so the model layer\n // downgrades this to false whenever the failed chat has prior state a\n // re-submission would corrupt, or server-side tooling (workspace/MCP)\n // whose effects a replay would duplicate (see #runTurn).\n isRetryable: args.isRetryable ?? true,\n cause: args.cause,\n });\n this.chatId = args.chatId;\n }\n}\n\n/** A chat generation ended in an error status. */\nexport class CoderChatError extends CoderAgentError {\n override name = \"CoderChatError\";\n readonly kind: string | undefined;\n readonly provider: string | undefined;\n readonly retryable: boolean;\n readonly statusCode: number | undefined;\n constructor(payload: ChatErrorPayload) {\n super(payload.detail ? `${payload.message}: ${payload.detail}` : payload.message);\n this.kind = payload.kind;\n this.provider = payload.provider;\n this.retryable = payload.retryable ?? false;\n this.statusCode = payload.status_code;\n }\n}\n","import { CoderAgentError } from \"./errors.js\";\n\n/** The `ai` peer-dependency major this package is built against (`ai@^7`). */\nconst SUPPORTED_AI_MAJOR = 7;\n\n/**\n * Decides whether a resolved `ai` package version is supported. Returns the\n * error message to raise for a known-incompatible major, or `undefined` to\n * proceed. Unparseable strings also return `undefined`: the guard fails open\n * rather than inventing a failure on exotic version formats.\n */\nexport function unsupportedAiVersionMessage(version: string): string | undefined {\n const major = Number(/^v?(\\d+)(?:[.+-]|$)/.exec(version.trim())?.[1]);\n if (!Number.isSafeInteger(major) || major === SUPPORTED_AI_MAJOR) return undefined;\n return (\n `@coder/ai-sdk-agent supports the \\`ai\\` package v${SUPPORTED_AI_MAJOR} only ` +\n `(peer dependency \\`ai@^${SUPPORTED_AI_MAJOR}\\`), but ai@${version} is installed. ` +\n `Other majors fail at runtime in confusing ways (brand-check TypeErrors, silently ` +\n `empty streams). Install a compatible version, e.g. \\`pnpm add ai@^${SUPPORTED_AI_MAJOR}\\`.`\n );\n}\n\n/**\n * Best-effort resolution of the installed `ai` package's version via\n * `createRequire(import.meta.url)(\"ai/package.json\")` (both v6 and v7 export\n * `./package.json`). Returns `undefined` whenever resolution isn't possible —\n * non-Node runtime, bundle without module resolution, `ai` not installed, … —\n * because the guard must never introduce a failure of its own. `node:module`\n * is reached through `process.getBuiltinModule` (Node ≥ 20.16) instead of a\n * static import, which would crash non-Node runtimes at module-load time.\n */\nfunction resolveInstalledAiVersion(): string | undefined {\n try {\n const nodeModule =\n typeof process !== \"undefined\" && typeof process.getBuiltinModule === \"function\"\n ? process.getBuiltinModule(\"node:module\")\n : undefined;\n const requireFromHere = nodeModule?.createRequire(import.meta.url);\n const pkg = requireFromHere?.(\"ai/package.json\") as { version?: unknown } | undefined;\n return typeof pkg?.version === \"string\" ? pkg.version : undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Memoized verdict: `undefined` = not yet computed, `null` = supported (or\n * unresolvable), a string = the message to throw on every construction.\n */\nlet cachedMessage: string | null | undefined;\n\n/**\n * Asserts that the installed `ai` major matches this package's peer range\n * (`ai@^7`). Called from the `CoderAgent`/`CoderLanguageModel` constructors so\n * an incompatible AI SDK fails fast with an actionable error instead of the\n * cryptic failures it produces mid-generation. Fails open when the installed\n * version cannot be resolved. The verdict is memoized across calls.\n *\n * @param resolveVersion Test seam; production callers use the default.\n */\nexport function assertSupportedAiVersion(\n resolveVersion: () => string | undefined = resolveInstalledAiVersion,\n): void {\n if (cachedMessage === undefined) {\n const version = resolveVersion();\n cachedMessage = version === undefined ? null : (unsupportedAiVersionMessage(version) ?? null);\n }\n if (cachedMessage !== null) throw new CoderAgentError(cachedMessage);\n}\n\n/** Clears the memoized verdict so resolution runs again (unit-test hook). */\nexport function resetAiVersionCheckForTests(): void {\n cachedMessage = undefined;\n}\n","import type { SharedV4FileData } from \"@ai-sdk/provider\";\nimport { CoderAgentError } from \"./errors.js\";\n\n/**\n * Bytes to upload, in any convenient shape.\n *\n * `Blob`/`File` is the recommended form: it is lazy (not forced fully into\n * memory), carries its own size (so the 10 MiB chat cap can fail fast and a\n * `Content-Length` can be sent), is re-readable (so a failed upload can be\n * retried), and works both in Node (`fs.openAsBlob(\"./x.pdf\")`) and the browser\n * (an `<input type=file>` yields a `File`). A `ReadableStream` is accepted for\n * genuinely unbounded sources, but its size is unknown up front — the chat size\n * cap can then only be enforced server-side.\n */\nexport type FileContent = Blob | ReadableStream<Uint8Array> | Uint8Array | ArrayBuffer;\n\n/** A {@link FileContent} normalized for upload: a fetch-ready body plus derived metadata. */\nexport interface ResolvedFile {\n /**\n * Body to hand to `fetch`. Raw bytes are wrapped in a `Blob` so the body is\n * always a clean `BodyInit` (and bytes get a `Content-Length` for free).\n */\n body: Blob | ReadableStream<Uint8Array>;\n /** Resolved IANA media type. */\n mediaType: string;\n /** Original filename, if known. */\n name: string | undefined;\n /** Byte length, if known up front (undefined for streams). */\n size: number | undefined;\n}\n\nfunction isBlob(v: unknown): v is Blob {\n return typeof Blob !== \"undefined\" && v instanceof Blob;\n}\nfunction isReadableStream(v: unknown): v is ReadableStream<Uint8Array> {\n return typeof ReadableStream !== \"undefined\" && v instanceof ReadableStream;\n}\n\n/**\n * Normalize {@link FileContent} into a fetch-ready body and derive its media\n * type, name, and (when known) size. `Blob`/`File` supply their own\n * `type`/`name`/`size`; for raw bytes or a stream, `mediaType` must be provided\n * by the caller. Explicit `opts.mediaType`/`opts.name` always win.\n */\nexport function resolveFileContent(\n content: FileContent,\n opts?: { mediaType?: string; name?: string },\n): ResolvedFile {\n let mediaType = opts?.mediaType;\n let name = opts?.name;\n let body: ResolvedFile[\"body\"];\n let size: number | undefined;\n\n if (isBlob(content)) {\n body = content;\n size = content.size;\n if (!mediaType && content.type) mediaType = content.type;\n // `File` extends `Blob` with a `name`; read it structurally so we don't\n // depend on the `File` global type existing.\n const maybeName = (content as { name?: unknown }).name;\n if (!name && typeof maybeName === \"string\") name = maybeName;\n } else if (isReadableStream(content)) {\n body = content;\n size = undefined;\n } else if (content instanceof Uint8Array || content instanceof ArrayBuffer) {\n size = content.byteLength;\n body = new Blob([content as BlobPart]);\n } else {\n throw new CoderAgentError(\n \"Unsupported file content: expected a Blob/File, ReadableStream, Uint8Array, or ArrayBuffer.\",\n );\n }\n\n if (!mediaType) {\n throw new CoderAgentError(\n \"Could not determine the file's media type. Pass `mediaType`, or provide a Blob/File that carries its own `type`.\",\n );\n }\n\n return { body, mediaType, name, size };\n}\n\n/** Decode a base64 string to bytes (Node `Buffer` or browser `atob`). */\nfunction base64ToBytes(b64: string): Uint8Array {\n // `Buffer` is already a `Uint8Array`; return it directly (no extra copy).\n if (typeof Buffer !== \"undefined\") return Buffer.from(b64, \"base64\");\n const bin = atob(b64);\n const bytes = new Uint8Array(bin.length);\n for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);\n return bytes;\n}\n\n/**\n * Convert an AI SDK file-part `data` value (a tagged {@link SharedV4FileData}\n * union) into {@link FileContent} for upload. The provider receives inline\n * bytes, a base64 string, or inline text. URLs are not expected: this model\n * declares no `supportedUrls`, so the SDK downloads them to bytes before\n * calling us — a URL (or an opaque provider reference) here is therefore an\n * error.\n */\nexport function dataContentToFileContent(data: SharedV4FileData): FileContent {\n switch (data.type) {\n case \"data\":\n return data.data instanceof Uint8Array ? data.data : base64ToBytes(data.data);\n case \"text\":\n return new TextEncoder().encode(data.text);\n default:\n throw new CoderAgentError(\n `File parts referencing a ${data.type === \"url\" ? \"URL\" : \"provider reference\"} are not supported; provide the file bytes instead.`,\n );\n }\n}\n","/**\n * Wire types for Coder's stable `chatd` chat API\n * (`/api/v2/chats`, promoted in Coder 2.37.0). These mirror the Go types in\n * `coder/coder`'s `codersdk/chats.go` (JSON shapes), restricted to the subset\n * this client uses. Field names use snake_case to match the wire format.\n */\n\n// ---------------------------------------------------------------------------\n// Input (client -> server)\n// ---------------------------------------------------------------------------\n\nexport type ChatInputPartType = \"text\" | \"file\" | \"file-reference\";\n\nexport interface ChatInputPart {\n type: ChatInputPartType;\n text?: string;\n file_id?: string;\n file_name?: string;\n start_line?: number;\n end_line?: number;\n content?: string;\n}\n\n/**\n * Media types accepted by the chat-file upload endpoint\n * (`POST /api/v2/chats/files`). The server allowlist is intentionally\n * narrow — anything else (zip, tarball, binaries, …) is not a chat attachment\n * and belongs on the workspace filesystem instead. Mirrors\n * `codersdk.AllChatAttachmentMediaTypes`.\n */\nexport type ChatAttachmentMediaType =\n | \"application/json\"\n | \"application/pdf\"\n | \"image/gif\"\n | \"image/jpeg\"\n | \"image/png\"\n | \"image/webp\"\n | \"text/csv\"\n | \"text/markdown\"\n | \"text/plain\";\n\n/** The chat-attachment media-type allowlist, as a runtime-checkable set. */\nexport const CHAT_ATTACHMENT_MEDIA_TYPES: ReadonlySet<string> = new Set<ChatAttachmentMediaType>([\n \"application/json\",\n \"application/pdf\",\n \"image/gif\",\n \"image/jpeg\",\n \"image/png\",\n \"image/webp\",\n \"text/csv\",\n \"text/markdown\",\n \"text/plain\",\n]);\n\n/** Per-file size cap enforced by the chat-file upload endpoint (10 MiB). */\nexport const MAX_CHAT_FILE_SIZE_BYTES = 10 * 1024 * 1024;\n\nexport type ChatClientType = \"ui\" | \"api\";\nexport type ChatPlanMode = \"\" | \"plan\";\nexport type ChatBusyBehavior = \"queue\" | \"interrupt\";\n\n/** Coder's global reasoning effort scale; accepts future server-defined values. */\nexport type ReasoningEffort =\n | \"none\"\n | \"minimal\"\n | \"low\"\n | \"medium\"\n | \"high\"\n | \"xhigh\"\n | \"max\"\n | (string & {});\n\n/**\n * A client-executed (\"dynamic\") tool definition. chatd never executes these;\n * when the model calls one, the chat enters `requires_action` and the client\n * must execute it and submit results via {@link SubmitToolResultsRequest}.\n *\n * NOTE: the JSON key is `input_schema` (snake_case), per chatd.\n */\nexport interface DynamicTool {\n name: string;\n description?: string;\n /** A JSON Schema object describing the tool input. */\n input_schema: unknown;\n}\n\nexport interface CreateChatRequest {\n organization_id: string;\n content: ChatInputPart[];\n system_prompt?: string;\n workspace_id?: string;\n model_config_id?: string;\n reasoning_effort?: ReasoningEffort;\n mcp_server_ids?: string[];\n labels?: Record<string, string>;\n unsafe_dynamic_tools?: DynamicTool[];\n plan_mode?: ChatPlanMode;\n client_type?: ChatClientType;\n}\n\nexport interface CreateChatMessageRequest {\n content: ChatInputPart[];\n model_config_id?: string;\n reasoning_effort?: ReasoningEffort;\n mcp_server_ids?: string[];\n busy_behavior?: ChatBusyBehavior;\n plan_mode?: ChatPlanMode;\n}\n\n/** Result of a single client-executed tool call. */\nexport interface ToolResult {\n tool_call_id: string;\n /** JSON-serializable tool output. */\n output: unknown;\n is_error?: boolean;\n}\n\nexport interface SubmitToolResultsRequest {\n results: ToolResult[];\n}\n\nexport interface UpdateChatRequest {\n title?: string;\n archived?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Output (server -> client)\n// ---------------------------------------------------------------------------\n\nexport type ChatStatus =\n | \"waiting\"\n | \"pending\"\n | \"running\"\n | \"paused\"\n | \"completed\"\n | \"error\"\n | \"requires_action\"\n | \"interrupting\";\n\nexport type ChatMessageRole = \"system\" | \"user\" | \"assistant\" | \"tool\";\n\nexport type ChatMessagePartType =\n | \"text\"\n | \"reasoning\"\n | \"tool-call\"\n | \"tool-result\"\n | \"source\"\n | \"file\"\n | \"file-reference\"\n | \"context-file\"\n | \"skill\";\n\n/**\n * A part of a chat message. chatd models this as a single flat struct with a\n * `type` discriminator and many optional fields (see `codersdk.ChatMessagePart`),\n * so we mirror that shape rather than a strict discriminated union.\n */\nexport interface ChatMessagePart {\n type: ChatMessagePartType;\n\n // text / reasoning\n text?: string;\n signature?: string;\n\n // tool-call / tool-result\n tool_call_id?: string;\n tool_name?: string;\n mcp_server_config_id?: string | null;\n /** tool-call: complete arguments (JSON value). */\n args?: unknown;\n /** tool-call: incremental argument text chunk during streaming. */\n args_delta?: string;\n parsed_commands?: string[][];\n /** tool-result: complete result (JSON value). */\n result?: unknown;\n /** tool-result: incremental result text chunk during streaming. */\n result_delta?: string;\n result_reset?: boolean;\n is_error?: boolean;\n is_media?: boolean;\n provider_executed?: boolean;\n created_at?: string;\n completed_at?: string;\n\n // source\n source_id?: string;\n url?: string;\n title?: string;\n\n // file\n media_type?: string;\n name?: string;\n file_id?: string | null;\n\n // file-reference\n file_name?: string;\n start_line?: number;\n end_line?: number;\n content?: string;\n\n // skill\n skill_name?: string;\n skill_description?: string;\n}\n\nexport interface ChatMessageUsage {\n input_tokens?: number;\n output_tokens?: number;\n total_tokens?: number;\n reasoning_tokens?: number;\n cache_creation_tokens?: number;\n cache_read_tokens?: number;\n context_limit?: number;\n /**\n * Cost in micro-USD, when the server prices usage. Optional: servers that\n * don't price usage (or predate cost reporting) omit it.\n */\n total_cost_micros?: number;\n /** Model runtime in milliseconds, when the server reports it. */\n total_runtime_ms?: number;\n}\n\nexport interface ChatMessage {\n id: number;\n chat_id: string;\n created_by?: string;\n model_config_id?: string;\n created_at: string;\n role: ChatMessageRole;\n content?: ChatMessagePart[];\n usage?: ChatMessageUsage;\n}\n\nexport interface ChatErrorPayload {\n message: string;\n detail?: string;\n kind?: string;\n provider?: string;\n retryable?: boolean;\n status_code?: number;\n}\n\nexport interface Chat {\n id: string;\n organization_id: string;\n owner_id: string;\n owner_username?: string;\n workspace_id?: string | null;\n agent_id?: string | null;\n parent_chat_id?: string | null;\n root_chat_id?: string | null;\n last_model_config_id?: string;\n last_reasoning_effort?: ReasoningEffort | null;\n title: string;\n status: ChatStatus;\n plan_mode?: ChatPlanMode;\n last_error?: ChatErrorPayload | null;\n created_at: string;\n updated_at: string;\n archived: boolean;\n mcp_server_ids?: string[];\n client_type?: ChatClientType;\n}\n\nexport interface ChatQueuedMessage {\n id: number;\n chat_id: string;\n content: ChatMessagePart[];\n created_at: string;\n}\n\nexport interface CreateChatMessageResponse {\n message?: ChatMessage;\n queued_message?: ChatQueuedMessage;\n queued: boolean;\n warnings?: string[];\n}\n\nexport interface ChatMessagesResponse {\n messages: ChatMessage[];\n queued_messages: ChatQueuedMessage[];\n has_more: boolean;\n}\n\n/** Response from `POST /api/v2/chats/files`: the uploaded file's id. */\nexport interface UploadChatFileResponse {\n id: string;\n}\n\n/**\n * A configured model, normalized to the shape of the legacy\n * `GET /api/experimental/chats/model-configs` listing (removed upstream in\n * coder/coder#28632). The client synthesizes this from the organization-scoped\n * {@link OrganizationChatModelsResponse} by joining each model's\n * `ai_provider_id` to its provider descriptor's `type` (→ `provider`).\n * Depending on the server version and how a config was created, `provider`,\n * `model`, and `display_name` may be absent on individual entries, so treat\n * them as optional and guard reads.\n */\nexport interface ChatModelConfig {\n id: string;\n provider?: string;\n ai_provider_id?: string;\n model?: string;\n display_name?: string;\n enabled?: boolean;\n is_default?: boolean;\n context_limit?: number;\n compression_threshold?: number;\n reasoning_efforts?: ReasoningEffort[];\n}\n\n/**\n * An organization-scoped chat model, as returned by\n * `GET /api/v2/organizations/{organization}/chats/models` (codersdk\n * `ChatModel`). The provider type string is not carried here — join\n * `ai_provider_id` against {@link ChatModelProviderDescriptor.id}. Fields\n * other than `id` are treated as optional to tolerate partial payloads from\n * older/newer servers.\n */\nexport interface ChatModel {\n id: string;\n organization_id?: string;\n /** FK into the response's `providers[].id`. */\n ai_provider_id?: string;\n model?: string;\n display_name?: string;\n enabled?: boolean;\n is_default?: boolean;\n context_limit?: number;\n compression_threshold?: number;\n reasoning_efforts?: ReasoningEffort[];\n created_at?: string;\n updated_at?: string;\n}\n\n/**\n * The redacted view of an AI provider carried on the organization-scoped\n * model collection (codersdk `ChatModelProviderDescriptor`) — capability\n * metadata only, no key material or base URLs. `type` is the provider type\n * string (e.g. `anthropic`) that the legacy listing carried per-model.\n */\nexport interface ChatModelProviderDescriptor {\n id: string;\n type?: string;\n display_name?: string;\n icon?: string;\n enabled?: boolean;\n available?: boolean;\n unavailable_reason?: string;\n}\n\n/** A configured provider type the server build does not support. */\nexport interface ChatUnsupportedProvider {\n provider?: string;\n display_name?: string;\n}\n\n/**\n * Response of `GET /api/v2/organizations/{organization}/chats/models`\n * (codersdk `OrganizationChatModelsResponse`): the org's model configs plus\n * the provider descriptors they reference.\n */\nexport interface OrganizationChatModelsResponse {\n models?: ChatModel[];\n providers?: ChatModelProviderDescriptor[];\n unsupported_providers?: ChatUnsupportedProvider[];\n}\n\n// ---------------------------------------------------------------------------\n// Stream events (WebSocket `/stream`)\n// ---------------------------------------------------------------------------\n\nexport type ChatStreamEventType =\n | \"message_part\"\n | \"message\"\n | \"status\"\n | \"error\"\n | \"queue_update\"\n | \"retry\"\n | \"action_required\"\n | \"preview_reset\"\n | \"history_reset\";\n\nexport interface ChatStreamMessagePart {\n role?: ChatMessageRole;\n part: ChatMessagePart;\n history_version?: number;\n generation_attempt?: number;\n seq?: number;\n}\n\nexport interface ChatStreamStatus {\n status: ChatStatus;\n}\n\nexport interface ChatStreamToolCall {\n tool_call_id: string;\n tool_name: string;\n /** JSON string of the tool-call arguments. */\n args: string;\n}\n\nexport interface ChatStreamActionRequired {\n tool_calls: ChatStreamToolCall[];\n}\n\nexport interface ChatStreamRetry {\n attempt: number;\n delay_ms: number;\n error: string;\n kind?: string;\n provider?: string;\n status_code?: number;\n retrying_at: string;\n}\n\nexport interface ChatStreamEvent {\n type: ChatStreamEventType;\n chat_id: string;\n message?: ChatMessage;\n message_part?: ChatStreamMessagePart;\n status?: ChatStreamStatus;\n error?: ChatErrorPayload;\n retry?: ChatStreamRetry;\n queued_messages?: ChatQueuedMessage[];\n action_required?: ChatStreamActionRequired;\n}\n\n/** Terminal statuses: the chat has stopped generating for this turn. */\nexport const TERMINAL_STATUSES: ReadonlySet<ChatStatus> = new Set<ChatStatus>([\n \"waiting\",\n \"completed\",\n \"error\",\n \"requires_action\",\n]);\n\n// ---------------------------------------------------------------------------\n// Watch events (WebSocket `/watch`)\n// ---------------------------------------------------------------------------\n\n/** Kinds of chat lifecycle events emitted on the per-user watch socket. */\nexport type ChatWatchEventKind =\n | \"status_change\"\n | \"summary_change\"\n | \"title_change\"\n | \"created\"\n | \"deleted\"\n | \"diff_status_change\"\n | \"action_required\"\n | \"context_dirty\";\n\n/**\n * An event from `GET /api/v2/chats/watch`, which covers every chat\n * visible to the authenticated user (not a single chat). Mirrors\n * `codersdk.ChatWatchEvent`.\n */\nexport interface ChatWatchEvent {\n kind: ChatWatchEventKind;\n chat: Chat;\n /** Pending client-executed tool calls; sent with `action_required` events. */\n tool_calls?: ChatStreamToolCall[];\n}\n","import NodeWebSocket from \"ws\";\nimport { CoderAgentError, CoderApiError, CoderStreamError } from \"../errors.js\";\nimport {\n safeTransportEmitter,\n type StreamCloseTransportEvent,\n type TransportEventHandler,\n} from \"../transport-events.js\";\nimport type { ChatStreamEvent, ChatWatchEvent } from \"./types.js\";\n\n/**\n * A minimal WebSocket factory abstraction so the stream reader can run on\n * Node (via the `ws` package, with header auth) or in environments that\n * provide a global `WebSocket` (browsers, where auth must go in the query\n * string). Node is the default target.\n */\nexport interface WebSocketLike {\n send(data: string): void;\n close(code?: number): void;\n addEventListener(type: \"open\", cb: () => void): void;\n addEventListener(type: \"message\", cb: (ev: { data: unknown }) => void): void;\n addEventListener(type: \"error\", cb: (ev: unknown) => void): void;\n addEventListener(type: \"close\", cb: (ev: { code?: number; reason?: string }) => void): void;\n removeEventListener(type: \"open\", cb: () => void): void;\n removeEventListener(type: \"message\", cb: (ev: { data: unknown }) => void): void;\n removeEventListener(type: \"error\", cb: (ev: unknown) => void): void;\n removeEventListener(type: \"close\", cb: (ev: { code?: number; reason?: string }) => void): void;\n}\n\nexport type WebSocketFactory = (\n url: string,\n protocols: { headers: Record<string, string> },\n) => WebSocketLike;\n\nconst defaultFactory: WebSocketFactory = (url, { headers }) => {\n // `ws` accepts custom handshake headers, which lets us authenticate with the\n // `Coder-Session-Token` header instead of leaking the token into the URL.\n return new NodeWebSocket(url, { headers }) as unknown as WebSocketLike;\n};\n\nfunction httpToWs(baseUrl: string): string {\n if (baseUrl.startsWith(\"https://\")) return `wss://${baseUrl.slice(\"https://\".length)}`;\n if (baseUrl.startsWith(\"http://\")) return `ws://${baseUrl.slice(\"http://\".length)}`;\n return baseUrl;\n}\n\nexport interface StreamChatEventsOptions {\n baseUrl: string;\n token: string;\n chatId: string;\n /** Only stream events for messages with id greater than this. */\n afterId?: number;\n signal?: AbortSignal;\n webSocketFactory?: WebSocketFactory;\n /** Resolve the client's route prefix before dialing, using the reader's cancellation signal. */\n resolveApiPrefix?: (signal: AbortSignal) => Promise<string>;\n /**\n * Observability hook: receives the reader's `ws:*` transport events (dial,\n * open, decoded events, close, error, redial). Exceptions it throws are\n * swallowed; without it, no event objects are allocated and no extra socket\n * listeners are registered. See {@link CoderTransportEvent}.\n */\n onTransportEvent?: TransportEventHandler;\n /**\n * Reader id stamped on this call's `ws:*` events, for callers that need to\n * know it (the model records it to stamp `segment:settle` with the reader\n * that served the segment). Must come from {@link nextStreamReaderId};\n * allocated automatically when absent.\n */\n reader?: number;\n}\n\n/**\n * Monotonic reader-id source for `ws:*` transport events (issue #94). One\n * module-global counter — deliberately not per client or model instance, so\n * `(chatId, reader, attempt)` stays unique for the lifetime of any subscriber,\n * even a handler shared across instances. Advanced on every `streamChatEvents`\n * call (one integer increment per turn — the #45 zero-overhead property is\n * about event objects and listeners), subscriber or not, so ids stay globally\n * consistent when only some clients are observed.\n */\nlet readerSeq = 0;\n\n/** Allocates the next reader id (see {@link StreamChatEventsOptions.reader}). */\nexport function nextStreamReaderId(): number {\n readerSeq += 1;\n return readerSeq;\n}\n\nconst STREAM_BACKOFF_INITIAL_MS = 1_000;\nconst STREAM_BACKOFF_CAP_MS = 30_000;\n/**\n * Consecutive progress-less connection endings tolerated before a dropped\n * per-chat stream gives up — counting the drop that starts an outage. Only\n * forward progress — a newly committed or revised message, or a new\n * (unsuppressed) delta — resets the count; chatd replays a `status` and the\n * in-progress episode on every reconnect, so counting mere receipt would let\n * a half-dead connection (connects, replays, drops) redial forever. With\n * 1s→2s→4s→8s backoff between the five endings this bounds a non-progressing\n * network to ~15s of redialing even without a caller signal or\n * `requestTimeoutMs`.\n */\nconst STREAM_MAX_CONSECUTIVE_FAILURES = 5;\n\n/**\n * Upgrade-rejection statuses that can succeed on a later attempt with the\n * exact same request: request timeout (408), too early (425), and rate\n * limiting (429). These consume the redial budget instead of failing fast;\n * every other 4xx (bad/expired token, deleted chat, …) is terminal.\n */\nconst TRANSIENT_UPGRADE_STATUSES: ReadonlySet<number> = new Set([408, 425, 429]);\n\n/**\n * Opens the chatd `/stream` WebSocket and yields decoded {@link ChatStreamEvent}s\n * as an async iterable. Each text frame from chatd is a JSON array of events;\n * we flatten them into a single stream.\n *\n * The chat stream is a live subscription that stays open after a turn settles,\n * so callers should `break` out of the loop once they observe a terminal status\n * — that triggers generator cleanup, which closes the socket.\n *\n * Dropped connections are redialed with exponential backoff (like\n * {@link watchChatEvents}), replaying from the turn's ORIGINAL `after_id`. The\n * cursor is deliberately NOT advanced past messages already yielded: chatd's\n * initial sync filters out ids at or below `after_id`, so an advanced cursor\n * would silently lose same-id REVISION snapshots (updated content, tool\n * results, usage) committed while the connection was down. Consumers must\n * dedupe repeated `message` snapshots — the wire contract demands that anyway,\n * since chatd re-sends full snapshots on revision bumps even without a\n * reconnect (TurnTranslator does). On reconnect chatd also replays the\n * in-progress generation attempt's `message_part` deltas from `seq` 1, so\n * parts of an episode\n * (`history_version`, `generation_attempt`) at or below the last yielded `seq`\n * are suppressed here rather than double-yielded. That episode filter runs at\n * frame arrival (not at consumption), so each frame's `ws:event` can carry\n * the reader's own verdict as `forwarded` (issue #111) while `timestamp`\n * keeps meaning arrival time. The iteration ends in five\n * ways: `signal` aborting (clean return), a non-transient 4xx upgrade\n * rejection (terminal {@link CoderApiError} — bad/expired token or a deleted\n * chat cannot succeed on retry, while 408/425/429 consume the redial budget),\n * an unparseable frame (terminal {@link CoderAgentError} — a redial would\n * replay the same frame forever), a drop while deltas WITHOUT episode\n * coordinates are outstanding (older chatd; a {@link CoderStreamError},\n * because a delta replay could not be deduped and would double-emit — a NEW\n * committed `message` finalizes them into dedupable snapshots and re-enables\n * redial), or the redial budget running out (also a {@link CoderStreamError}).\n */\nexport function streamChatEvents(\n options: StreamChatEventsOptions,\n): AsyncGenerator<ChatStreamEvent, void, void> {\n // Cancellation plumbing, mirroring watchChatEvents below: async-generator\n // `return()`/`throw()` queue behind a pending `next()`, and with redial a\n // pending read can now span a backoff sleep and a fresh socket — a bare\n // `return()` from a signal-less consumer would otherwise keep redialing\n // instead of tearing down. The wrapper aborts an internal controller first,\n // which wakes the pending read (and the backoff sleep), then delegates.\n const controller = new AbortController();\n const external = options.signal;\n const chain = (): void => controller.abort(external?.reason);\n if (external) {\n if (external.aborted) chain();\n else external.addEventListener(\"abort\", chain, { once: true });\n }\n // The reader id is fixed at call time (before the first dial), so every\n // `ws:*` event of this reader — across redials — carries the same id.\n const reader = options.reader ?? nextStreamReaderId();\n const inner = streamChatEventsLoop({ ...options, reader, signal: controller.signal });\n const detach = (): void => external?.removeEventListener(\"abort\", chain);\n const finish = async (): Promise<void> => {\n controller.abort();\n detach();\n try {\n await inner.return();\n } catch {\n /* the loop's own teardown errors are not the caller's problem */\n }\n };\n return {\n // Detach the signal chain once the loop settles for good (done, or a\n // terminal failure) — the iteration protocol never calls return() on a\n // failed iterator, and the {once} listener would otherwise pile up on a\n // long-lived signal across re-created readers.\n async next(): Promise<IteratorResult<ChatStreamEvent, void>> {\n try {\n const result = await inner.next();\n if (result.done) detach();\n return result;\n } catch (err) {\n detach();\n throw err;\n }\n },\n async return(): Promise<IteratorResult<ChatStreamEvent, void>> {\n await finish();\n return { done: true, value: undefined };\n },\n async throw(err: unknown): Promise<IteratorResult<ChatStreamEvent, void>> {\n await finish();\n throw err;\n },\n [Symbol.asyncIterator]() {\n return this;\n },\n // Native async generators are async-disposable; the wrapper must be too.\n async [Symbol.asyncDispose](): Promise<void> {\n await finish();\n },\n } as AsyncGenerator<ChatStreamEvent, void, void>;\n}\n\nasync function* streamChatEventsLoop(\n options: StreamChatEventsOptions & { reader: number; signal: AbortSignal },\n): AsyncGenerator<ChatStreamEvent, void, void> {\n const { baseUrl, token, chatId, signal, reader } = options;\n const factory = options.webSocketFactory ?? defaultFactory;\n // Exception-isolated observability emitter; undefined without a subscriber,\n // and every emit site below is guarded on that so the no-subscriber path\n // allocates no event objects and registers no extra listeners.\n const emit = safeTransportEmitter(options.onTransportEvent);\n // 1-based connection attempt for `ws:*` event correlation.\n let attempt = 0;\n\n const wsBase = httpToWs(baseUrl);\n if (signal.aborted) return;\n let prefix = \"/api/v2/chats\";\n try {\n if (options.resolveApiPrefix) prefix = await options.resolveApiPrefix(signal);\n } catch (err) {\n if (signal.aborted) return;\n throw err;\n }\n const path = `${prefix}/${chatId}/stream`;\n // The turn's original cursor, reused verbatim on every redial — see the doc\n // comment above for why it must not advance past yielded messages.\n const query = options.afterId !== undefined ? `?after_id=${options.afterId}` : \"\";\n const url = `${wsBase}${path}${query}`;\n\n // Highest committed message id yielded, for progress accounting only.\n let maxCommittedId = options.afterId;\n // Last-yielded serialized form of each committed message, so a same-id\n // REVISION snapshot (updated content, tool data, or usage on an\n // already-committed message) counts as forward progress while a\n // byte-identical replay does not. Revision numbers are not on the wire\n // type, so content equality is the discriminator (#59).\n const lastMessageJson = new Map<number, string>();\n // Last yielded delta position, for suppressing chatd's from-the-start replay\n // of the in-progress episode after a redial.\n let lastHistoryVersion: number | undefined;\n let lastGenerationAttempt: number | undefined;\n let lastSeq: number | undefined;\n // Deltas missing those coordinates (older servers) cannot be deduped on\n // replay: while any are outstanding, a drop is terminal instead of redialed\n // — the pre-redial behavior — because a replay would double-emit them. A\n // NEW committed `message` finalizes them (their content then arrives only as\n // snapshots, which consumers dedupe), making redial safe again.\n let sawUntrackableDelta = false;\n\n let backoffMs = STREAM_BACKOFF_INITIAL_MS;\n let failures = 0;\n let lastFailure: Error | undefined;\n\n while (!signal?.aborted) {\n // One connection attempt per iteration; the queue/wake plumbing mirrors\n // watchChatEventsLoop below.\n const queue: ChatStreamEvent[] = [];\n let resolveNext: (() => void) | undefined;\n let finished = false;\n let failure: Error | undefined;\n let parseFailure = false;\n // Whether this connection's `ws:close` has been emitted (via the close\n // listener); the teardown below emits a synthetic one otherwise, so every\n // dial gets exactly one close.\n let closeEmitted = false;\n\n const wake = () => {\n resolveNext?.();\n resolveNext = undefined;\n };\n\n attempt += 1;\n emit?.({ type: \"ws:dial\", chatId, reader, attempt, url, timestamp: Date.now() });\n let ws: WebSocketLike;\n try {\n ws = factory(url, { headers: { \"Coder-Session-Token\": token } });\n } catch (err) {\n // A synchronously-throwing factory is a terminal reader failure (as\n // before the hooks); keep the observability invariants on the way out —\n // the emitted dial still gets its error and exactly-one close.\n if (emit) {\n const message = err instanceof Error ? err.message : String(err);\n emit({ type: \"ws:error\", chatId, reader, attempt, message, timestamp: Date.now() });\n emit({ type: \"ws:close\", chatId, reader, attempt, timestamp: Date.now() });\n }\n throw err;\n }\n\n const onAbort = () => {\n finished = true;\n try {\n ws.close(1000);\n } catch {\n /* ignore */\n }\n wake();\n };\n let abortListenerAdded = false;\n if (signal) {\n if (signal.aborted) onAbort();\n else {\n signal.addEventListener(\"abort\", onAbort, { once: true });\n abortListenerAdded = true;\n }\n }\n\n // Applies the reader's replay dedup to one decoded frame at ARRIVAL: it\n // updates the episode/progress ledgers, emits the frame's `ws:event`\n // stamped with the verdict (`forwarded`, issue #111), and enqueues only\n // forwarded frames for the consumption path below. Running the filter here\n // instead of at consumption keeps the stamp truthful without splitting the\n // state machine in two, and is behaviorally equivalent: the queue is\n // drained in arrival order before any redial decision reads the ledgers,\n // so they advance through the same frame sequence either way. `timestamp`\n // keeps meaning arrival time, and the verdict is computed per-frame\n // subscriber or not, so the no-subscriber path stays allocation-free.\n const ingest = (next: ChatStreamEvent): void => {\n // Only forward progress resets the redial budget: a new delta or a\n // newly committed message. Replays (chatd re-sends a `status` and the\n // in-progress episode on every reconnect) don't count — otherwise a\n // half-dead connection that connects, replays, and drops would reset\n // the budget each round and redial forever.\n let progress = false;\n let forwarded = true;\n if (next.type === \"message_part\") {\n const mp = next.message_part;\n const hv = mp?.history_version;\n const ga = mp?.generation_attempt;\n const seq = mp?.seq;\n if (hv !== undefined && ga !== undefined && seq !== undefined) {\n if (\n hv === lastHistoryVersion &&\n ga === lastGenerationAttempt &&\n lastSeq !== undefined &&\n seq <= lastSeq\n ) {\n // An already-yielded delta, replayed after a redial: suppressed\n // from the consumption path (never enqueued below).\n forwarded = false;\n } else {\n lastHistoryVersion = hv;\n lastGenerationAttempt = ga;\n lastSeq = seq;\n progress = true;\n }\n } else {\n // No episode coordinates: cannot be deduped on replay, so it\n // must not count as progress and disables redial (see below).\n sawUntrackableDelta = true;\n }\n } else if (next.type === \"message\" && next.message) {\n // A message not seen in this exact form before — a new commit or\n // a same-id revision — is forward progress; a byte-identical\n // replay is not. Either way the snapshot is forwarded: snapshot\n // dedup is the consumer's job (the wire contract re-sends full\n // snapshots on revision bumps even without a reconnect).\n const serialized = JSON.stringify(next.message);\n if (lastMessageJson.get(next.message.id) !== serialized) {\n lastMessageJson.set(next.message.id, serialized);\n progress = true;\n }\n if (maxCommittedId === undefined || next.message.id > maxCommittedId) {\n maxCommittedId = next.message.id;\n // This commit finalizes the in-progress deltas: their content\n // now lives in committed snapshots, which consumers dedupe, so\n // a replay can no longer duplicate coordinate-less deltas.\n sawUntrackableDelta = false;\n }\n }\n if (progress) {\n failures = 0;\n backoffMs = STREAM_BACKOFF_INITIAL_MS;\n }\n // Observed at arrival — a redial's replayed episode is visible to\n // subscribers, with the duplicates the reader suppresses marked\n // `forwarded: false`.\n emit?.({\n type: \"ws:event\",\n chatId,\n reader,\n attempt,\n event: next,\n forwarded,\n timestamp: Date.now(),\n });\n if (forwarded) queue.push(next);\n };\n\n const onMessage = (ev: { data: unknown }): void => {\n if (finished) return;\n let batch: unknown;\n try {\n const data = typeof ev.data === \"string\" ? ev.data : String(ev.data);\n batch = JSON.parse(data);\n } catch (err) {\n emit?.({\n type: \"ws:error\",\n chatId,\n reader,\n attempt,\n message: \"failed to parse chat stream frame\",\n timestamp: Date.now(),\n });\n failure = new CoderAgentError(\"failed to parse chat stream frame\", { cause: err });\n parseFailure = true;\n finished = true;\n wake();\n return;\n }\n if (Array.isArray(batch)) {\n for (const e of batch) ingest(e as ChatStreamEvent);\n } else if (batch && typeof batch === \"object\") {\n ingest(batch as ChatStreamEvent);\n }\n wake();\n };\n const onError = (ev: unknown): void => {\n if (finished) return;\n const message =\n ev && typeof ev === \"object\" && \"message\" in ev\n ? String((ev as { message: unknown }).message)\n : \"chat stream socket error\";\n emit?.({ type: \"ws:error\", chatId, reader, attempt, message, timestamp: Date.now() });\n const status = upgradeStatus(message);\n failure =\n status !== undefined &&\n status >= 400 &&\n status < 500 &&\n !TRANSIENT_UPGRADE_STATUSES.has(status)\n ? new CoderApiError({ status, method: \"GET\", path, message })\n : new CoderAgentError(message);\n finished = true;\n wake();\n };\n const onClose = (ev?: { code?: number; reason?: string }): void => {\n if (emit && !closeEmitted) {\n closeEmitted = true;\n const closeEvent: StreamCloseTransportEvent = {\n type: \"ws:close\",\n chatId,\n reader,\n attempt,\n timestamp: Date.now(),\n };\n if (ev?.code !== undefined) closeEvent.code = ev.code;\n if (ev?.reason !== undefined && ev.reason !== \"\") closeEvent.reason = ev.reason;\n emit(closeEvent);\n }\n finished = true;\n wake();\n };\n // Registered only for observability — the reader itself never waits for\n // the handshake (a send-less consumer just reads whatever arrives).\n const onOpen = emit\n ? (): void => emit({ type: \"ws:open\", chatId, reader, attempt, timestamp: Date.now() })\n : undefined;\n if (onOpen) ws.addEventListener(\"open\", onOpen);\n ws.addEventListener(\"message\", onMessage);\n ws.addEventListener(\"error\", onError);\n ws.addEventListener(\"close\", onClose);\n\n try {\n while (true) {\n while (queue.length > 0) {\n // Frames were replay-filtered and progress-credited at arrival (see\n // `ingest` above); the consumption path only hands them over.\n yield queue.shift() as ChatStreamEvent;\n }\n if (finished) break;\n await new Promise<void>((resolve) => {\n resolveNext = resolve;\n });\n }\n } finally {\n finished = true;\n if (signal && abortListenerAdded) signal.removeEventListener(\"abort\", onAbort);\n if (onOpen) ws.removeEventListener(\"open\", onOpen);\n ws.removeEventListener(\"message\", onMessage);\n ws.removeEventListener(\"error\", onError);\n ws.removeEventListener(\"close\", onClose);\n try {\n ws.close(1000);\n } catch {\n /* ignore */\n }\n // The reader ended the connection itself (settle, teardown, abort, or an\n // error-triggered exit whose close event hadn't fired yet): emit the\n // close now — every dial gets exactly one `ws:close`, with `code`/\n // `reason` only when the server/network closed the socket.\n if (emit && !closeEmitted) {\n closeEmitted = true;\n emit({ type: \"ws:close\", chatId, reader, attempt, timestamp: Date.now() });\n }\n }\n\n if (signal?.aborted) return;\n // A rejected upgrade (non-transient 4xx) is terminal: retrying with the\n // same credentials against the same (possibly deleted) chat cannot\n // succeed. An unparseable frame is terminal too: the cursor cannot advance\n // past an event we cannot decode, so a redial would replay the exact same\n // frame forever.\n if (failure instanceof CoderApiError) throw failure;\n if (parseFailure && failure) throw failure;\n if (sawUntrackableDelta) {\n // A CoderStreamError (not a bare CoderAgentError) so the model layer\n // applies its retry-safety gates — otherwise the generic stream_closed\n // wrap would advertise a same-chat retry that could duplicate the turn.\n throw new CoderStreamError({\n message:\n \"chat stream dropped mid-turn and cannot be resumed: this server does not stamp deltas with history_version/generation_attempt/seq, so a redial would replay them\",\n url,\n cause: failure,\n chatId,\n });\n }\n if (failure) lastFailure = failure;\n // EVERY ended connection counts toward the budget — including the one\n // whose drop started this outage — so the give-up bound stays ~15s of\n // backoff on all paths; forward progress above resets the count.\n failures += 1;\n if (failures >= STREAM_MAX_CONSECUTIVE_FAILURES) {\n throw new CoderStreamError({\n message: `Coder chat stream ${path} could not be (re-)established after ${failures} consecutive connection failures without progress`,\n url,\n cause: lastFailure,\n chatId,\n });\n }\n emit?.({\n type: \"ws:redial\",\n chatId,\n reader,\n attempt,\n consecutiveFailures: failures,\n maxConsecutiveFailures: STREAM_MAX_CONSECUTIVE_FAILURES,\n backoffMs,\n timestamp: Date.now(),\n });\n await sleep(backoffMs, signal);\n if (signal?.aborted) return;\n backoffMs = Math.min(backoffMs * 2, STREAM_BACKOFF_CAP_MS);\n }\n}\n\nconst WATCH_BACKOFF_INITIAL_MS = 1_000;\nconst WATCH_BACKOFF_CAP_MS = 30_000;\n\n/** Abort-aware sleep; resolves early (without throwing) when the signal aborts. */\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve) => {\n if (signal?.aborted) {\n resolve();\n return;\n }\n const onAbort = (): void => {\n clearTimeout(timer);\n resolve();\n };\n const timer = setTimeout(() => {\n signal?.removeEventListener(\"abort\", onAbort);\n resolve();\n }, ms);\n // The timer stays ref'd on purpose: an actively consumed watch loop must\n // keep the process alive through a reconnect delay (abort clears it).\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n });\n}\n\n/**\n * Extract the HTTP status from a rejected WebSocket upgrade. The `ws` package\n * reports handshake failures as an error event whose message is\n * `Unexpected server response: <status>`; other factories that follow the\n * same convention are recognized too.\n */\nfunction upgradeStatus(message: string): number | undefined {\n const match = /unexpected server response: (\\d{3})$/i.exec(message.trim());\n return match ? Number(match[1]) : undefined;\n}\n\nexport interface WatchChatEventsOptions {\n baseUrl: string;\n token: string;\n signal?: AbortSignal;\n webSocketFactory?: WebSocketFactory;\n /** Resolve the client's route prefix before dialing, using the reader's cancellation signal. */\n resolveApiPrefix?: (signal: AbortSignal) => Promise<string>;\n}\n\n/**\n * Opens the chatd `/watch` WebSocket — lifecycle events for every chat visible\n * to the authenticated user — and yields decoded {@link ChatWatchEvent}s as an\n * async iterable.\n *\n * Unlike the per-chat `/stream`, this is a long-lived subscription, so dropped\n * connections are redialed automatically with exponential backoff (1s doubling\n * to a 30s cap, reset once an event arrives). It ends in exactly three ways:\n * `signal` aborting, the consumer ending iteration (`break` from `for await`,\n * `iterator.return()`) — both tear the socket down promptly even while a read\n * is pending — or the server rejecting the upgrade with a 4xx — bad/expired\n * token, or an older Coder server without the endpoint (404) — which throws a\n * terminal {@link CoderApiError} instead of retrying forever.\n */\nexport function watchChatEvents(\n options: WatchChatEventsOptions,\n): AsyncGenerator<ChatWatchEvent, void, void> {\n // Cancellation plumbing: async-generator `return()`/`throw()` queue behind a\n // pending `next()`, and on this rarely-eventing stream a pending read is the\n // steady state — a bare `return()` would otherwise hang (socket open) until\n // the next server event. The wrapper aborts an internal controller first,\n // which wakes the pending read, then delegates.\n const controller = new AbortController();\n const external = options.signal;\n const chain = (): void => controller.abort(external?.reason);\n if (external) {\n if (external.aborted) chain();\n else external.addEventListener(\"abort\", chain, { once: true });\n }\n const inner = watchChatEventsLoop({ ...options, signal: controller.signal });\n const detach = (): void => external?.removeEventListener(\"abort\", chain);\n const finish = async (): Promise<void> => {\n controller.abort();\n detach();\n try {\n await inner.return();\n } catch {\n /* the loop's own teardown errors are not the caller's problem */\n }\n };\n return {\n // Detach the signal chain once the loop settles for good (done, or the\n // terminal 4xx rejection) — the iteration protocol never calls return()\n // on a failed iterator, and the {once} listener would otherwise pile up\n // on a long-lived signal across re-created watchers.\n async next(): Promise<IteratorResult<ChatWatchEvent, void>> {\n try {\n const result = await inner.next();\n if (result.done) detach();\n return result;\n } catch (err) {\n detach();\n throw err;\n }\n },\n async return(): Promise<IteratorResult<ChatWatchEvent, void>> {\n await finish();\n return { done: true, value: undefined };\n },\n async throw(err: unknown): Promise<IteratorResult<ChatWatchEvent, void>> {\n await finish();\n throw err;\n },\n [Symbol.asyncIterator]() {\n return this;\n },\n // Native async generators are async-disposable; the wrapper must be too,\n // so `await using events = client.watchChats(…)` keeps tearing down.\n async [Symbol.asyncDispose](): Promise<void> {\n await finish();\n },\n } as AsyncGenerator<ChatWatchEvent, void, void>;\n}\n\nasync function* watchChatEventsLoop(\n options: WatchChatEventsOptions & { signal: AbortSignal },\n): AsyncGenerator<ChatWatchEvent, void, void> {\n const { baseUrl, token, signal } = options;\n const factory = options.webSocketFactory ?? defaultFactory;\n if (signal.aborted) return;\n let prefix = \"/api/v2/chats\";\n try {\n if (options.resolveApiPrefix) prefix = await options.resolveApiPrefix(signal);\n } catch (err) {\n if (signal.aborted) return;\n throw err;\n }\n const path = `${prefix}/watch`;\n const url = `${httpToWs(baseUrl)}${path}`;\n\n let backoffMs = WATCH_BACKOFF_INITIAL_MS;\n while (!signal?.aborted) {\n // One connection attempt per iteration; the queue/wake plumbing mirrors\n // streamChatEvents above.\n const queue: ChatWatchEvent[] = [];\n let resolveNext: (() => void) | undefined;\n let finished = false;\n let failure: Error | undefined;\n\n const wake = () => {\n resolveNext?.();\n resolveNext = undefined;\n };\n\n const ws = factory(url, { headers: { \"Coder-Session-Token\": token } });\n\n const onAbort = () => {\n finished = true;\n try {\n ws.close(1000);\n } catch {\n /* ignore */\n }\n wake();\n };\n let abortListenerAdded = false;\n if (signal) {\n if (signal.aborted) onAbort();\n else {\n signal.addEventListener(\"abort\", onAbort, { once: true });\n abortListenerAdded = true;\n }\n }\n\n const onMessage = (ev: { data: unknown }): void => {\n if (finished) return;\n let payload: unknown;\n try {\n const data = typeof ev.data === \"string\" ? ev.data : String(ev.data);\n payload = JSON.parse(data);\n } catch (err) {\n // A malformed frame means this connection is unusable; redial it.\n failure = new CoderAgentError(\"failed to parse chat watch frame\", { cause: err });\n finished = true;\n wake();\n return;\n }\n // chatd sends one event object per frame (wsjson); tolerate batches too.\n if (Array.isArray(payload)) {\n for (const e of payload) queue.push(e as ChatWatchEvent);\n } else if (payload && typeof payload === \"object\") {\n queue.push(payload as ChatWatchEvent);\n }\n wake();\n };\n const onError = (ev: unknown): void => {\n if (finished) return;\n const message =\n ev && typeof ev === \"object\" && \"message\" in ev\n ? String((ev as { message: unknown }).message)\n : \"chat watch socket error\";\n const status = upgradeStatus(message);\n failure =\n status !== undefined && status >= 400 && status < 500\n ? new CoderApiError({ status, method: \"GET\", path, message })\n : new CoderAgentError(message);\n finished = true;\n wake();\n };\n const onClose = (): void => {\n finished = true;\n wake();\n };\n ws.addEventListener(\"message\", onMessage);\n ws.addEventListener(\"error\", onError);\n ws.addEventListener(\"close\", onClose);\n\n try {\n while (true) {\n while (queue.length > 0) {\n const next = queue.shift() as ChatWatchEvent;\n // Receiving an event proves the connection works — reset the backoff.\n backoffMs = WATCH_BACKOFF_INITIAL_MS;\n yield next;\n }\n if (finished) break;\n await new Promise<void>((resolve) => {\n resolveNext = resolve;\n });\n }\n } finally {\n finished = true;\n if (signal && abortListenerAdded) signal.removeEventListener(\"abort\", onAbort);\n ws.removeEventListener(\"message\", onMessage);\n ws.removeEventListener(\"error\", onError);\n ws.removeEventListener(\"close\", onClose);\n try {\n ws.close(1000);\n } catch {\n /* ignore */\n }\n }\n\n if (signal?.aborted) return;\n // A rejected upgrade (4xx) is terminal: retrying with the same credentials\n // against the same server cannot succeed.\n if (failure instanceof CoderApiError) throw failure;\n await sleep(backoffMs, signal);\n if (signal?.aborted) return;\n backoffMs = Math.min(backoffMs * 2, WATCH_BACKOFF_CAP_MS);\n }\n}\n","import type { LanguageModelV4FinishReason } from \"@ai-sdk/provider\";\nimport type { ChatStatus, ChatStreamEvent } from \"./coder/types.js\";\n\n/**\n * Typed transport observability events (issue #45).\n *\n * A single optional `onTransportEvent` hook — on {@link CoderChatClientOptions},\n * {@link CoderLanguageModelConfig}, and {@link CoderAgentSettings} — receives\n * these events so consumers can build traces and timings from a stable event\n * vocabulary instead of wrapping `fetch`/`webSocketFactory` and re-parsing\n * every stream frame.\n *\n * Contract:\n * - **Isolation** — exceptions thrown by a subscriber are swallowed; they can\n * never alter transport behavior or a turn's outcome.\n * - **Zero overhead** — without a subscriber, no event objects are allocated\n * and no extra listeners are registered.\n * - **No secrets** — events carry no headers and no tokens. Authentication\n * travels exclusively in the `Coder-Session-Token` request header, which is\n * deliberately excluded; the `path`/`url` fields never contain credentials.\n * - **Observation, not interception** — events are emitted synchronously at\n * observation time (`timestamp` is `Date.now()`, comparable to server-side\n * timestamps such as a message's `created_at`); handlers cannot modify what\n * the transport does. Payloads referenced by an event (`ws:event`'s decoded\n * stream event) are shared with the pipeline — treat them as read-only.\n */\ninterface TransportEventBase {\n /** Wall-clock observation time in ms since epoch (`Date.now()`). */\n timestamp: number;\n}\n\n// --- HTTP (every REST call made by CoderChatClient) -------------------------\n\n/**\n * Names the `CoderChatClient` operation performing an HTTP exchange (issue\n * #112). The public client method names are the stable vocabulary, so\n * subscribers can classify per-operation timings without reverse-engineering\n * `path` (whose API prefix is private and may change). New client methods\n * extend this union; treat unrecognized values as \"some other operation\"\n * rather than matching exhaustively.\n */\nexport type CoderClientOperation =\n | \"listModelConfigs\"\n | \"resolveModelConfigId\"\n | \"createChat\"\n | \"getChat\"\n | \"createChatMessage\"\n | \"getMessages\"\n | \"submitToolResults\"\n | \"interruptChat\"\n | \"deleteQueuedMessage\"\n | \"updateChat\"\n | \"archiveChat\"\n | \"uploadChatFile\"\n | \"getChatFile\"\n | \"streamEvents\"\n | \"watchChats\";\n\ninterface HttpTransportEventBase extends TransportEventBase {\n /**\n * Correlates a request with its response/error. Monotonically increasing\n * per client instance.\n */\n id: number;\n /**\n * The client operation performing this exchange — the public\n * `CoderChatClient` method name. `archiveChat` stamps its own operation\n * even though it issues the same `PATCH` as `updateChat`.\n */\n op: CoderClientOperation;\n /** HTTP method, e.g. `POST`. */\n method: string;\n /**\n * Path + query relative to the client's `baseUrl`, e.g.\n * `/api/v2/chats/{id}/tool-results`. Never carries credentials.\n */\n path: string;\n}\n\n/** A request is about to be sent. */\nexport interface HttpRequestTransportEvent extends HttpTransportEventBase {\n type: \"http:request\";\n}\n\n/**\n * Response headers arrived (before the body is read). Emitted for every\n * completed exchange, including non-2xx responses (`ok: false`) that the\n * client subsequently surfaces as a `CoderApiError`.\n */\nexport interface HttpResponseTransportEvent extends HttpTransportEventBase {\n type: \"http:response\";\n status: number;\n ok: boolean;\n /** Time from request start to response headers, in milliseconds. */\n durationMs: number;\n}\n\n/** The fetch itself rejected (network failure, abort) — no response arrived. */\nexport interface HttpErrorTransportEvent extends HttpTransportEventBase {\n type: \"http:error\";\n message: string;\n durationMs: number;\n}\n\n// --- WebSocket (the per-chat `/stream` reader) -------------------------------\n\ninterface StreamTransportEventBase extends TransportEventBase {\n chatId: string;\n /**\n * Identifies the reader (one `streamChatEvents` call) this event belongs\n * to: monotonically increasing, allocated per call from one module-global\n * (process-wide) counter — NOT per model or client instance, so\n * `(chatId, reader, attempt)` stays unique for the lifetime of any\n * subscriber, even one shared across instances (issue #94). Readers on\n * other chats/clients advance the same counter, so ids are ordered but not\n * dense per chat. Use it to tell a superseded reader's late events apart\n * from the live reader's: a client-tool pause abandoned by the caller is\n * closed fire-and-forget when the next turn dials its replacement, and the\n * old reader's `ws:close` (or a raced-in frame) can emit after the new\n * reader's `ws:dial` — both would otherwise be ambiguous at `attempt` 1.\n */\n reader: number;\n /**\n * 1-based connection attempt within one reader (`streamChatEvents` call —\n * with stream retention (#44), one per turn). Increments on every redial\n * and restarts at 1 for each new reader.\n */\n attempt: number;\n}\n\n/** A connection attempt is starting (socket constructed). */\nexport interface StreamDialTransportEvent extends StreamTransportEventBase {\n type: \"ws:dial\";\n /** The `wss://…/stream[?after_id=N]` URL. Never carries credentials. */\n url: string;\n}\n\n/** The WebSocket handshake completed. */\nexport interface StreamOpenTransportEvent extends StreamTransportEventBase {\n type: \"ws:open\";\n}\n\n/**\n * A decoded stream event arrived on the wire. Emitted at arrival time for\n * every event in a frame batch — after a redial, chatd's replay of the\n * in-progress episode is still visible here, stamped with the reader's own\n * replay verdict (`forwarded`). `event` is the decoded object by reference\n * (no copy): do not mutate it.\n */\nexport interface StreamEventTransportEvent extends StreamTransportEventBase {\n type: \"ws:event\";\n event: ChatStreamEvent;\n /**\n * The reader's replay disposition for this frame (issue #111): `false`\n * exactly when the episode filter suppresses it from the consumption path —\n * a redial's replay of an in-progress delta at or below the last yielded\n * `seq` of the same episode — and `true` when the reader forwards it to its\n * consumer. It is a dedup verdict, not a delivery receipt: a consumer\n * tearing down mid-queue can still discard a forwarded frame.\n *\n * Snapshot dedup is deliberately NOT reflected here: repeated or revised\n * `message` snapshots are always `forwarded: true`, because reconciling\n * them is the consumer's job past the transport layer — TurnTranslator's\n * per-message emitted-content ledger decides what a revision re-emits.\n * That disposition is not stamped on transport frames; subscribers needing\n * content fidelity must consume model output (or TurnTranslator), not\n * `ws:event`.\n */\n forwarded: boolean;\n}\n\n/**\n * The connection ended. Exactly one per dial: carries `code`/`reason` when\n * the server or network closed the socket; both are absent when the reader\n * tore the connection down itself (turn settled, redial teardown, abort).\n */\nexport interface StreamCloseTransportEvent extends StreamTransportEventBase {\n type: \"ws:close\";\n code?: number;\n reason?: string;\n}\n\n/**\n * An error was observed on the connection: a socket `error` event (including\n * rejected upgrades) or an unparseable frame. The reader's own terminal\n * classification still surfaces through the turn (see `segment:settle`).\n */\nexport interface StreamErrorTransportEvent extends StreamTransportEventBase {\n type: \"ws:error\";\n message: string;\n}\n\n/**\n * A dropped connection is about to be redialed after `backoffMs`. Carries the\n * redial budget state: the reader gives up (failing the turn) when\n * `consecutiveFailures` — connection endings without forward progress —\n * reaches `maxConsecutiveFailures`; any forward progress resets the count.\n * `attempt` is the connection that just ended; the next dial is `attempt + 1`.\n */\nexport interface StreamRedialTransportEvent extends StreamTransportEventBase {\n type: \"ws:redial\";\n consecutiveFailures: number;\n maxConsecutiveFailures: number;\n backoffMs: number;\n}\n\n// --- Turn segments (one doGenerate/doStream call each) -----------------------\n\ninterface SegmentTransportEventBase extends TransportEventBase {\n /** 1-based segment counter per model instance. */\n segment: number;\n /** Undefined on a first turn's start until the chat is created. */\n chatId?: string;\n /**\n * The reader (`streamChatEvents` call — the `reader` on `ws:*` events) that\n * served this segment, correlating it with the connection's `ws:*` events.\n * Absent on `segment:start`, which is emitted before the segment acquires\n * its stream (a fresh turn dials only once the chat exists, and even a\n * stream retained by a client-tool pause can still be replaced — stamping a\n * predicted reader could lie), and on a settle when the segment failed\n * before acquiring one. Segments of one turn share the reader: the stream\n * is retained across `requires_action` pauses (#44).\n */\n reader?: number;\n}\n\n/**\n * A segment (one model round-trip: submission plus the server run until it\n * settles or pauses for a client tool) started. A turn that drives client\n * tools spans several segments.\n */\nexport interface SegmentStartTransportEvent extends SegmentTransportEventBase {\n type: \"segment:start\";\n}\n\n/**\n * The segment ended. Exactly one per start:\n * - settled cleanly → `status` is the turn's terminal chat status and\n * `finishReason` the unified AI SDK finish reason (`tool-calls` marks a\n * client-tool pause; the turn continues with a resume segment);\n * - failed (abort, timeout, transport failure, server error) → `error`,\n * plus `status` when the server run itself still settled terminally\n * (e.g. `status: \"error\"` streamed alongside the error);\n * - torn down before settling (consumer canceled the stream) → neither.\n */\nexport interface SegmentSettleTransportEvent extends SegmentTransportEventBase {\n type: \"segment:settle\";\n status?: ChatStatus;\n finishReason?: LanguageModelV4FinishReason[\"unified\"];\n error?: { name: string; message: string };\n durationMs: number;\n}\n\n/** Every transport observability event, discriminated on `type`. */\nexport type CoderTransportEvent =\n | HttpRequestTransportEvent\n | HttpResponseTransportEvent\n | HttpErrorTransportEvent\n | StreamDialTransportEvent\n | StreamOpenTransportEvent\n | StreamEventTransportEvent\n | StreamCloseTransportEvent\n | StreamErrorTransportEvent\n | StreamRedialTransportEvent\n | SegmentStartTransportEvent\n | SegmentSettleTransportEvent;\n\n/**\n * Subscriber for {@link CoderTransportEvent}s. Exceptions are swallowed —\n * including an `async` handler's rejection (a function returning a promise is\n * assignable to this void-returning signature, and its rejection must not\n * become an unhandled rejection either).\n */\nexport type TransportEventHandler = (event: CoderTransportEvent) => void;\n\n/**\n * Wraps a subscriber so its exceptions can never reach the transport or a\n * turn. Returns `undefined` when there is no subscriber — emit sites guard on\n * that, so no event objects are allocated without one (internal).\n */\nexport function safeTransportEmitter(\n handler: TransportEventHandler | undefined,\n): TransportEventHandler | undefined {\n if (!handler) return undefined;\n return (event) => {\n try {\n // The signature is void-returning, but TypeScript accepts an `async`\n // handler there — its rejection would land AFTER this try/catch and\n // become an unhandled rejection (killing the process on Node).\n // `Promise.resolve` adopts a returned thenable, so both a rejected\n // async handler and a sync throw (caught below, before the promise\n // wrapping) are silenced the same way. The handler still runs\n // synchronously; only its failure handling is deferred.\n void Promise.resolve(handler(event)).catch(() => {});\n } catch {\n // Observability must never alter turn outcomes: a throwing subscriber\n // is deliberately silenced rather than surfaced into the pipeline.\n }\n };\n}\n","import { CoderAgentError, CoderApiError } from \"../errors.js\";\nimport { type FileContent, resolveFileContent } from \"../files.js\";\nimport {\n type Chat,\n CHAT_ATTACHMENT_MEDIA_TYPES,\n type ChatMessagesResponse,\n type ChatModelConfig,\n type ChatStreamEvent,\n type ChatWatchEvent,\n type CreateChatMessageRequest,\n type CreateChatMessageResponse,\n type CreateChatRequest,\n MAX_CHAT_FILE_SIZE_BYTES,\n type OrganizationChatModelsResponse,\n type SubmitToolResultsRequest,\n type UpdateChatRequest,\n type UploadChatFileResponse,\n} from \"./types.js\";\nimport { streamChatEvents, watchChatEvents, type WebSocketFactory } from \"./ws.js\";\nimport {\n type CoderClientOperation,\n safeTransportEmitter,\n type TransportEventHandler,\n} from \"../transport-events.js\";\n\n/** A file to upload as a chat attachment. */\nexport interface ChatFileInput {\n content: FileContent;\n /**\n * Media type. Required unless `content` is a Blob/File with a non-empty `type`\n * — note `fs.openAsBlob()` returns a Blob with no type, so pass it explicitly there.\n */\n mediaType?: string;\n /** Original filename, surfaced to the model and UI. Defaults to a File's `name`. */\n name?: string;\n}\n\n/** A file that has been uploaded to chat-file storage. */\nexport interface UploadedChatFile {\n /** Server-assigned file id, referenced from message content via a `file` part. */\n id: string;\n mediaType: string;\n name?: string;\n}\n\nexport interface CoderChatClientOptions {\n /** Base URL of the Coder deployment, e.g. `https://dogfood.cdr.dev`. */\n baseUrl: string;\n /** Coder API token or session token (sent as `Coder-Session-Token`). */\n token: string;\n /** Custom fetch implementation (defaults to global `fetch`). */\n fetch?: typeof globalThis.fetch;\n /** Custom WebSocket factory (defaults to the `ws` package on Node). */\n webSocketFactory?: WebSocketFactory;\n /**\n * Observability hook: receives typed transport events (HTTP request/response\n * timings, per-chat stream WebSocket lifecycle). Exceptions it throws are\n * swallowed; without it, no event objects are allocated. See\n * {@link CoderTransportEvent}.\n */\n onTransportEvent?: TransportEventHandler;\n}\n\nconst API_PREFIX = \"/api/v2/chats\";\nconst LEGACY_API_PREFIX = \"/api/experimental/chats\";\n\n/**\n * A thin, typed client for Coder's chat API, using stable routes with a\n * per-instance experimental-prefix fallback for deployments before v2.37.0.\n */\nexport class CoderChatClient {\n readonly baseUrl: string;\n readonly #token: string;\n readonly #fetch: typeof globalThis.fetch;\n readonly #webSocketFactory: WebSocketFactory | undefined;\n /** The raw subscriber, forwarded to stream readers (which wrap it themselves). */\n readonly #onTransportEvent: TransportEventHandler | undefined;\n /** Exception-isolated emitter for this client's own HTTP events. */\n readonly #emitTransportEvent: TransportEventHandler | undefined;\n #httpSeq = 0;\n #apiPrefix: typeof API_PREFIX | typeof LEGACY_API_PREFIX | undefined;\n\n constructor(options: CoderChatClientOptions) {\n // Normalize: strip a single trailing slash.\n this.baseUrl = options.baseUrl.replace(/\\/$/, \"\");\n this.#token = options.token;\n this.#fetch = options.fetch ?? globalThis.fetch.bind(globalThis);\n this.#webSocketFactory = options.webSocketFactory;\n this.#onTransportEvent = options.onTransportEvent;\n this.#emitTransportEvent = safeTransportEmitter(options.onTransportEvent);\n }\n\n /**\n * Issue a request and return the raw `Response` on success (body unread). On a\n * non-2xx status the body is consumed to build a {@link CoderApiError}. Shared\n * by JSON requests and the raw upload/download endpoints. `op` names the\n * public client method on the exchange's `http:*` events (issue #112) — the\n * emit site is shared, so each call site threads its own literal.\n */\n async #sendOnce(\n op: CoderClientOperation,\n method: string,\n path: string,\n opts?: { body?: BodyInit; headers?: Record<string, string>; signal?: AbortSignal },\n ): Promise<Response> {\n const headers: Record<string, string> = {\n \"Coder-Session-Token\": this.#token,\n ...opts?.headers,\n };\n const init: RequestInit = { method, headers, body: opts?.body, signal: opts?.signal };\n // A streaming request body requires half-duplex mode on Node's fetch (undici).\n if (typeof ReadableStream !== \"undefined\" && opts?.body instanceof ReadableStream) {\n (init as RequestInit & { duplex?: \"half\" }).duplex = \"half\";\n }\n\n // Observability: request/response/error events, guarded so nothing is\n // allocated without a subscriber. NOTE: headers are deliberately excluded\n // from the events (the auth token travels in `Coder-Session-Token`).\n const emit = this.#emitTransportEvent;\n let requestId = 0;\n let startedAt = 0;\n if (emit) {\n requestId = ++this.#httpSeq;\n startedAt = performance.now();\n emit({ type: \"http:request\", id: requestId, op, method, path, timestamp: Date.now() });\n }\n let res: Response;\n try {\n res = await this.#fetch(`${this.baseUrl}${path}`, init);\n } catch (err) {\n emit?.({\n type: \"http:error\",\n id: requestId,\n op,\n method,\n path,\n message: err instanceof Error ? err.message : String(err),\n durationMs: performance.now() - startedAt,\n timestamp: Date.now(),\n });\n throw err;\n }\n emit?.({\n type: \"http:response\",\n id: requestId,\n op,\n method,\n path,\n status: res.status,\n ok: res.ok,\n durationMs: performance.now() - startedAt,\n timestamp: Date.now(),\n });\n if (!res.ok) {\n const errObj = (await this.#json<{ message?: string; detail?: string }>(res)) ?? {};\n throw new CoderApiError({\n status: res.status,\n method,\n path,\n message: errObj.message ?? res.statusText ?? \"request failed\",\n detail: errObj.detail,\n });\n }\n return res;\n }\n\n /**\n * Negotiate promoted chat routes from responses, not buildinfo versions:\n * v2.37.0 introduced /api/v2/chats (coder/coder#28496), while patched older\n * releases still only mount /api/experimental/chats. Retry a v2 404 once on\n * experimental; the first non-404 HTTP response locks the prefix for this\n * client. Network/abort failures cannot establish route support. Two 404s\n * preserve the original v2 error (possibly a missing resource) and leave\n * negotiation open for the next call. A locked client never falls back.\n *\n * Concurrent initial calls may probe in parallel; a late response cannot\n * overwrite the first lock. Model listings use separate routes/fallbacks\n * and must neither consult nor establish this chat-prefix lock.\n */\n async #send(\n op: CoderClientOperation,\n method: string,\n path: string,\n opts?: { body?: BodyInit; headers?: Record<string, string>; signal?: AbortSignal },\n ): Promise<Response> {\n if (path !== API_PREFIX && !path.startsWith(`${API_PREFIX}/`)) {\n return this.#sendOnce(op, method, path, opts);\n }\n const suffix = path.slice(API_PREFIX.length);\n if (this.#apiPrefix) {\n return this.#sendOnce(op, method, `${this.#apiPrefix}${suffix}`, opts);\n }\n try {\n const res = await this.#sendOnce(op, method, path, opts);\n this.#apiPrefix ??= API_PREFIX;\n return res;\n } catch (err) {\n if (!(err instanceof CoderApiError)) throw err;\n if (err.status !== 404) {\n this.#apiPrefix ??= API_PREFIX;\n throw err;\n }\n // Another initial request may have established v2 while this one ran.\n if (this.#apiPrefix === API_PREFIX) throw err;\n try {\n const res = await this.#sendOnce(op, method, `${LEGACY_API_PREFIX}${suffix}`, opts);\n this.#apiPrefix ??= LEGACY_API_PREFIX;\n return res;\n } catch (legacyErr) {\n if (legacyErr instanceof CoderApiError) {\n if (legacyErr.status === 404) throw err;\n this.#apiPrefix ??= LEGACY_API_PREFIX;\n }\n throw legacyErr;\n }\n }\n }\n\n /**\n * Resolve before a WebSocket dial or a non-replayable streaming upload. A\n * read-only listing avoids consuming the upload body and works even without\n * a chat id (/watch). The WS reader supplies its internal cancellation signal\n * so return()/throw()/async disposal can also cancel an in-flight probe.\n */\n async #resolveApiPrefix(op: CoderClientOperation, signal?: AbortSignal): Promise<string> {\n if (!this.#apiPrefix) {\n const res = await this.#send(op, \"GET\", API_PREFIX, { signal });\n await res.body?.cancel();\n }\n if (!this.#apiPrefix) throw new CoderAgentError(\"Chat route probe succeeded without a prefix\");\n return this.#apiPrefix;\n }\n\n /** Read a response body as JSON, tolerating an unreadable/empty/malformed body (→ undefined). */\n async #json<T>(res: Response): Promise<T | undefined> {\n const text = await res.text().catch(() => \"\");\n if (text.length === 0) return undefined;\n try {\n return JSON.parse(text) as T;\n } catch {\n return undefined;\n }\n }\n\n async #request<T>(\n op: CoderClientOperation,\n method: string,\n path: string,\n body?: unknown,\n signal?: AbortSignal,\n ): Promise<T> {\n const res = await this.#send(op, method, path, {\n body: body === undefined ? undefined : JSON.stringify(body),\n headers: body === undefined ? undefined : { \"Content-Type\": \"application/json\" },\n signal,\n });\n return (await this.#json<T>(res)) as T;\n }\n\n // --- REST -----------------------------------------------------------------\n\n /**\n * List the organization's model configs, normalized to\n * {@link ChatModelConfig} (the legacy per-model `provider` string is joined\n * from the response's provider descriptors). See {@link #fetchModelConfigs}\n * for the endpoint and version-skew fallback.\n */\n listModelConfigs(organizationId: string, signal?: AbortSignal): Promise<ChatModelConfig[]>;\n /**\n * @deprecated The deployment-scoped `GET /api/experimental/chats/model-configs`\n * route this overload calls was removed upstream (coder/coder#28632) and\n * returns 404 on current deployments — pass an `organizationId`.\n */\n listModelConfigs(signal?: AbortSignal): Promise<ChatModelConfig[]>;\n listModelConfigs(\n organizationIdOrSignal?: string | AbortSignal,\n maybeSignal?: AbortSignal,\n ): Promise<ChatModelConfig[]> {\n const organizationId =\n typeof organizationIdOrSignal === \"string\" ? organizationIdOrSignal : undefined;\n const signal =\n typeof organizationIdOrSignal === \"string\" ? maybeSignal : organizationIdOrSignal;\n return this.#fetchModelConfigs(\"listModelConfigs\", organizationId, signal);\n }\n\n createChat(req: CreateChatRequest, signal?: AbortSignal): Promise<Chat> {\n return this.#request<Chat>(\"createChat\", \"POST\", API_PREFIX, req, signal);\n }\n\n getChat(chatId: string, signal?: AbortSignal): Promise<Chat> {\n return this.#request<Chat>(\"getChat\", \"GET\", `${API_PREFIX}/${chatId}`, undefined, signal);\n }\n\n createChatMessage(\n chatId: string,\n req: CreateChatMessageRequest,\n signal?: AbortSignal,\n ): Promise<CreateChatMessageResponse> {\n return this.#request<CreateChatMessageResponse>(\n \"createChatMessage\",\n \"POST\",\n `${API_PREFIX}/${chatId}/messages`,\n req,\n signal,\n );\n }\n\n getMessages(\n chatId: string,\n opts?: { before_id?: number; after_id?: number; limit?: number },\n signal?: AbortSignal,\n ): Promise<ChatMessagesResponse> {\n const params = new URLSearchParams();\n if (opts?.before_id !== undefined) params.set(\"before_id\", String(opts.before_id));\n if (opts?.after_id !== undefined) params.set(\"after_id\", String(opts.after_id));\n if (opts?.limit !== undefined) params.set(\"limit\", String(opts.limit));\n const q = params.toString();\n return this.#request<ChatMessagesResponse>(\n \"getMessages\",\n \"GET\",\n `${API_PREFIX}/${chatId}/messages${q ? `?${q}` : \"\"}`,\n undefined,\n signal,\n );\n }\n\n submitToolResults(\n chatId: string,\n req: SubmitToolResultsRequest,\n signal?: AbortSignal,\n ): Promise<void> {\n return this.#request<void>(\n \"submitToolResults\",\n \"POST\",\n `${API_PREFIX}/${chatId}/tool-results`,\n req,\n signal,\n );\n }\n\n /**\n * Interrupt the chat's in-flight run (`POST /interrupt`). The server flips\n * an active run to `interrupting` and responds immediately with the updated\n * chat — it does not wait for the run to actually stop.\n *\n * Pass `{ wait: true }` to send `?wait=true`, asking the server to hold the\n * response until the run has stopped. Coder servers without that param\n * ignore the unknown query harmlessly and still return immediately, so\n * callers should be prepared to confirm completion via the event stream.\n *\n * The second parameter also accepts a bare `AbortSignal` (the historical\n * signature).\n */\n interruptChat(\n chatId: string,\n optsOrSignal?: AbortSignal | { wait?: boolean; signal?: AbortSignal },\n ): Promise<Chat> {\n const opts = optsOrSignal instanceof AbortSignal ? { signal: optsOrSignal } : optsOrSignal;\n const query = opts?.wait ? \"?wait=true\" : \"\";\n return this.#request<Chat>(\n \"interruptChat\",\n \"POST\",\n `${API_PREFIX}/${chatId}/interrupt${query}`,\n undefined,\n opts?.signal,\n );\n }\n\n /**\n * Withdraw a QUEUED (not yet materialized) message from the chat's\n * submission queue (`DELETE /queue/{id}`). `queuedMessageId` is the\n * queue-entry id from `CreateChatMessageResponse.queued_message` — a\n * different id space from committed chat message ids. Rejects with a\n * {@link CoderApiError}: 404 when the entry no longer exists (already\n * promoted into history, or deleted), 409 when the chat has no queue.\n */\n deleteQueuedMessage(\n chatId: string,\n queuedMessageId: number,\n signal?: AbortSignal,\n ): Promise<void> {\n return this.#request<void>(\n \"deleteQueuedMessage\",\n \"DELETE\",\n `${API_PREFIX}/${chatId}/queue/${queuedMessageId}`,\n undefined,\n signal,\n );\n }\n\n updateChat(chatId: string, req: UpdateChatRequest, signal?: AbortSignal): Promise<void> {\n return this.#request<void>(\"updateChat\", \"PATCH\", `${API_PREFIX}/${chatId}`, req, signal);\n }\n\n /** Convenience: archive a chat (soft-hide; safe for cleanup). */\n archiveChat(chatId: string, signal?: AbortSignal): Promise<void> {\n // Issues updateChat's PATCH directly so its `http:*` events carry its own\n // `op` — the point of the stamp is the caller's intent, not the wire shape.\n return this.#request<void>(\n \"archiveChat\",\n \"PATCH\",\n `${API_PREFIX}/${chatId}`,\n { archived: true } satisfies UpdateChatRequest,\n signal,\n );\n }\n\n // --- Files ----------------------------------------------------------------\n\n /**\n * Upload a file as a durable chat attachment, returning its id (referenced\n * from message content via a `file` input part). The server enforces a narrow\n * media-type allowlist and a 10 MiB cap; both are checked client-side first so\n * unsupported files fail fast with a clear error instead of an opaque 4xx.\n *\n * The body is sent raw (not multipart): `Content-Type` carries the media type\n * and `Content-Disposition` the filename, mirroring `codersdk`.\n */\n async uploadChatFile(\n organizationId: string,\n file: ChatFileInput,\n signal?: AbortSignal,\n ): Promise<UploadedChatFile> {\n const resolved = resolveFileContent(file.content, {\n mediaType: file.mediaType,\n name: file.name,\n });\n // Match the allowlist on the media type alone, ignoring parameters such as\n // `; charset=utf-8` that a Blob/File commonly carries.\n const mediaType = (resolved.mediaType.split(\";\")[0] ?? resolved.mediaType).trim().toLowerCase();\n if (!CHAT_ATTACHMENT_MEDIA_TYPES.has(mediaType)) {\n throw new CoderAgentError(\n `Media type \"${mediaType}\" is not allowed for chat attachments ` +\n `(allowed: ${[...CHAT_ATTACHMENT_MEDIA_TYPES].join(\", \")}). ` +\n `Write other file types to a workspace instead.`,\n );\n }\n if (resolved.size !== undefined && resolved.size > MAX_CHAT_FILE_SIZE_BYTES) {\n throw new CoderAgentError(\n `File is ${resolved.size} bytes, over the ${MAX_CHAT_FILE_SIZE_BYTES}-byte ` +\n `(10 MiB) chat attachment limit. Write large files to a workspace instead.`,\n );\n }\n\n const headers: Record<string, string> = { \"Content-Type\": mediaType };\n if (resolved.name) {\n // RFC 6266: a sanitized ASCII `filename` plus an RFC 5987 `filename*` that\n // preserves the exact name. Both are pure ASCII, so a non-Latin-1 name\n // (CJK, emoji, accents) can't trip fetch's ByteString header check, and\n // CR/LF/quote/backslash are encoded away rather than breaking the header.\n const ascii = resolved.name.replace(/[^\\x20-\\x7e]/g, \"_\").replace(/([\"\\\\])/g, \"\\\\$1\");\n const utf8 = encodeURIComponent(resolved.name).replace(\n /['()*]/g,\n (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`,\n );\n headers[\"Content-Disposition\"] = `attachment; filename=\"${ascii}\"; filename*=UTF-8''${utf8}`;\n }\n if (resolved.body instanceof ReadableStream) {\n await this.#resolveApiPrefix(\"uploadChatFile\", signal);\n }\n const res = await this.#send(\n \"uploadChatFile\",\n \"POST\",\n `${API_PREFIX}/files?organization=${encodeURIComponent(organizationId)}`,\n { body: resolved.body, headers, signal },\n );\n const parsed = await this.#json<UploadChatFileResponse>(res);\n if (!parsed?.id) {\n throw new CoderApiError({\n status: res.status,\n method: \"POST\",\n path: `${this.#apiPrefix}/files`,\n message: \"upload succeeded but the response contained no file id\",\n });\n }\n return { id: parsed.id, mediaType, name: resolved.name };\n }\n\n /** Download a chat file's bytes and media type by id. */\n async getChatFile(\n fileId: string,\n signal?: AbortSignal,\n ): Promise<{ bytes: Uint8Array; mediaType: string }> {\n const res = await this.#send(\"getChatFile\", \"GET\", `${API_PREFIX}/files/${fileId}`, {\n signal,\n });\n const buf = await res.arrayBuffer();\n return {\n bytes: new Uint8Array(buf),\n mediaType: res.headers.get(\"Content-Type\") ?? \"application/octet-stream\",\n };\n }\n\n // --- Streaming ------------------------------------------------------------\n\n streamEvents(\n chatId: string,\n opts?: {\n afterId?: number;\n signal?: AbortSignal;\n /** Pre-allocated reader id for `ws:*` events — see {@link streamChatEvents}. */\n reader?: number;\n },\n ): AsyncGenerator<ChatStreamEvent, void, void> {\n return streamChatEvents({\n baseUrl: this.baseUrl,\n token: this.#token,\n chatId,\n resolveApiPrefix: (signal) => this.#resolveApiPrefix(\"streamEvents\", signal),\n afterId: opts?.afterId,\n signal: opts?.signal,\n reader: opts?.reader,\n webSocketFactory: this.#webSocketFactory,\n onTransportEvent: this.#onTransportEvent,\n });\n }\n\n /**\n * Watch lifecycle events (status/title changes, creation, deletion, …) for\n * every chat visible to the authenticated user via the `/chats/watch`\n * WebSocket. An unresolved client first probes the REST chat listing;\n * probe failures propagate unless the reader was cancelled. Once connected,\n * dropped connections are redialed with exponential backoff. Aborting the\n * signal or ending iteration tears down the reader; a 4xx upgrade rejection\n * is a terminal {@link CoderApiError} (bad/expired token, or an older Coder\n * server without the endpoint → 404).\n */\n watchChats(opts?: { signal?: AbortSignal }): AsyncGenerator<ChatWatchEvent, void, void> {\n return watchChatEvents({\n baseUrl: this.baseUrl,\n token: this.#token,\n resolveApiPrefix: (signal) => this.#resolveApiPrefix(\"watchChats\", signal),\n signal: opts?.signal,\n webSocketFactory: this.#webSocketFactory,\n });\n }\n\n // --- Helpers --------------------------------------------------------------\n\n /**\n * Fetch the model listing for hint resolution / listing, tolerating version\n * skew in both directions:\n *\n * - Primary: `GET /api/v2/organizations/{organizationId}/chats/models` —\n * the organization-scoped route that replaced the legacy listing upstream\n * (coder/coder#28632); its response is normalized to the legacy\n * {@link ChatModelConfig} array by {@link joinOrgModels}.\n * - Newer SDK, older deployment: the org-scoped route does not exist yet,\n * so on a 404 this falls back once to the legacy\n * `GET /api/experimental/chats/model-configs`, whose body already is the\n * legacy array (parsed tolerantly: a non-array body yields no configs and\n * non-object entries are dropped). (Older SDK, newer deployment is the\n * reverse skew: the legacy route is a reserved 404 upstream, which is why\n * the org-scoped route is primary.)\n * - The deprecated no-`organizationId` overloads call the legacy route\n * directly, preserving their historical behavior.\n *\n * When both routes 404, the org-scoped error is rethrown — it names the\n * organization actually queried (e.g. a mistyped `organizationId`), while\n * the legacy route's generic \"Route not found\" would mask it.\n *\n * Both requests stamp the calling public method's `op` on the exchange's\n * `http:*` events — the stamp names the caller's intent, not the wire shape.\n */\n async #fetchModelConfigs(\n op: CoderClientOperation,\n organizationId: string | undefined,\n signal?: AbortSignal,\n ): Promise<ChatModelConfig[]> {\n const legacy = async (): Promise<ChatModelConfig[]> => {\n const raw: unknown = await this.#request<unknown>(\n op,\n \"GET\",\n `${LEGACY_API_PREFIX}/model-configs`,\n undefined,\n signal,\n );\n return (Array.isArray(raw) ? raw : []).filter(\n (c): c is ChatModelConfig => typeof c === \"object\" && c !== null,\n );\n };\n if (organizationId === undefined) return legacy();\n\n let res: unknown;\n try {\n res = await this.#request<unknown>(\n op,\n \"GET\",\n `/api/v2/organizations/${encodeURIComponent(organizationId)}/chats/models`,\n undefined,\n signal,\n );\n } catch (err) {\n if (!(err instanceof CoderApiError) || err.status !== 404) throw err;\n try {\n return await legacy();\n } catch (legacyErr) {\n if (legacyErr instanceof CoderApiError && legacyErr.status === 404) throw err;\n throw legacyErr;\n }\n }\n return joinOrgModels(res);\n }\n\n /**\n * Resolves a user-friendly model hint to a model-config UUID.\n *\n * Accepts: a config UUID (returned as-is), a `provider:model` id\n * (e.g. `anthropic:claude-haiku-4-5-20251001`), a bare model id, or a\n * display-name substring (case-insensitive). Returns `undefined` if no\n * match is found, in which case the caller should let chatd pick the default.\n *\n * Tolerates partial payloads from older/newer servers: a malformed listing\n * (empty body, non-array JSON, null entries) resolves as no match, and\n * entries missing `provider`, `model`, or `display_name` are matched on the\n * fields they do carry — an entry without `provider` still matches by its\n * model id, one without `model` only by display name.\n */\n resolveModelConfigId(\n hint: string,\n organizationId: string,\n signal?: AbortSignal,\n ): Promise<string | undefined>;\n /**\n * @deprecated The deployment-scoped `GET /api/experimental/chats/model-configs`\n * route this overload calls was removed upstream (coder/coder#28632) and\n * returns 404 on current deployments — pass an `organizationId`.\n */\n resolveModelConfigId(hint: string, signal?: AbortSignal): Promise<string | undefined>;\n async resolveModelConfigId(\n hint: string,\n organizationIdOrSignal?: string | AbortSignal,\n maybeSignal?: AbortSignal,\n ): Promise<string | undefined> {\n const organizationId =\n typeof organizationIdOrSignal === \"string\" ? organizationIdOrSignal : undefined;\n const signal =\n typeof organizationIdOrSignal === \"string\" ? maybeSignal : organizationIdOrSignal;\n const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n if (UUID_RE.test(hint)) return hint;\n\n const configs = await this.#fetchModelConfigs(\"resolveModelConfigId\", organizationId, signal);\n const lower = hint.toLowerCase();\n // `provider:model` form.\n const colon = hint.indexOf(\":\");\n const provider = colon >= 0 ? hint.slice(0, colon).toLowerCase() : undefined;\n const model = colon >= 0 ? hint.slice(colon + 1).toLowerCase() : lower;\n\n const candidates = configs.filter((c) => c.enabled !== false);\n const pool = candidates.length > 0 ? candidates : configs;\n\n // Guarded accessors: treat a missing/non-string field as absent.\n const modelOf = (c: ChatModelConfig) =>\n typeof c.model === \"string\" ? c.model.toLowerCase() : undefined;\n const providerOf = (c: ChatModelConfig) =>\n typeof c.provider === \"string\" ? c.provider.toLowerCase() : undefined;\n const displayNameOf = (c: ChatModelConfig) =>\n typeof c.display_name === \"string\" ? c.display_name.toLowerCase() : undefined;\n\n const exact = pool.find(\n (c) => modelOf(c) === model && (provider === undefined || providerOf(c) === provider),\n );\n if (exact) return exact.id;\n\n const byModel = pool.find((c) => modelOf(c) === model);\n if (byModel) return byModel.id;\n\n const byDisplay = pool.find((c) => displayNameOf(c)?.includes(lower));\n if (byDisplay) return byDisplay.id;\n\n const byModelSubstring = pool.find((c) => modelOf(c)?.includes(model));\n return byModelSubstring?.id;\n }\n}\n\n/**\n * Normalize an org-scoped models response to the legacy {@link ChatModelConfig}\n * array: join each model's `ai_provider_id` to its provider descriptor and\n * copy the descriptor's `type` onto the model as `provider`. Guarded against\n * partial payloads — non-object entries are dropped, and a model whose\n * provider cannot be joined keeps `provider` absent (hint matching then falls\n * back to its model id / display name).\n */\nfunction joinOrgModels(res: unknown): ChatModelConfig[] {\n const body = (\n typeof res === \"object\" && res !== null ? res : {}\n ) as OrganizationChatModelsResponse;\n const models = Array.isArray(body.models) ? body.models : [];\n const providers = Array.isArray(body.providers) ? body.providers : [];\n\n const providerTypeById = new Map<string, string>();\n for (const p of providers) {\n if (typeof p !== \"object\" || p === null) continue;\n if (typeof p.id === \"string\" && typeof p.type === \"string\") providerTypeById.set(p.id, p.type);\n }\n\n const configs: ChatModelConfig[] = [];\n for (const m of models) {\n if (typeof m !== \"object\" || m === null) continue;\n const config: ChatModelConfig = { ...m };\n if (m.reasoning_efforts !== undefined) config.reasoning_efforts = m.reasoning_efforts;\n const providerType =\n typeof m.ai_provider_id === \"string\" ? providerTypeById.get(m.ai_provider_id) : undefined;\n if (providerType !== undefined) config.provider = providerType;\n configs.push(config);\n }\n return configs;\n}\n","/**\n * Minimal helpers for the stable Coder v2 REST endpoints that the workspace\n * preview feature composes: workspace lookup, the wildcard app host, and port\n * sharing. Deliberately tiny — only the fields this package reads are modeled\n * — and mirroring `CoderChatClient`'s conventions (`Coder-Session-Token`\n * auth, `CoderApiError` on non-2xx). These are internal building blocks for\n * `CoderAgent.getPreview`/`sharePreview`; only their option/result types are\n * part of the public API.\n */\n\nimport { CoderAgentError, CoderApiError } from \"../errors.js\";\n\n/** Connection details for the stable v2 API (same credentials as the chat client). */\nexport interface WorkspaceApiConnection {\n /** Base URL of the Coder deployment, e.g. `https://dogfood.cdr.dev`. */\n baseUrl: string;\n /** Coder API/session token (sent as `Coder-Session-Token`). */\n token: string;\n /** Custom fetch implementation (defaults to global `fetch`). */\n fetch?: typeof globalThis.fetch;\n}\n\n/**\n * Subset of `codersdk.Workspace` (GET `/api/v2/workspaces/{workspace}`) that\n * the preview helpers read. Resources without agents omit the `agents` key\n * entirely, so the whole chain is treated as optional.\n */\nexport interface WorkspaceSummary {\n id: string;\n /** Username of the workspace owner (a subdomain URL segment). */\n owner_name: string;\n name: string;\n latest_build?: {\n resources?: { agents?: { name: string }[] }[];\n };\n}\n\n/** Response of GET `/api/v2/applications/host`; `host` is `\"\"` when subdomain apps are disabled. */\nexport interface AppHostResponse {\n /** Wildcard app host with a literal `*`, e.g. `*.apps.example.com`, possibly with a port. */\n host: string;\n}\n\n/** Share levels a port can be in; `owner` is the implicit default when no share exists. */\nexport type PortShareLevel = \"owner\" | \"authenticated\" | \"organization\" | \"public\";\n\n/**\n * Share levels the upsert endpoint accepts. `owner` is rejected server-side —\n * reverting to owner-only is done by deleting the share, not setting it.\n */\nexport type PreviewShareLevel = Exclude<PortShareLevel, \"owner\">;\n\n/** Protocol the workspace app speaks on the port (drives the `s` suffix in the URL). */\nexport type PortShareProtocol = \"http\" | \"https\";\n\n/** Body of POST `/api/v2/workspaces/{workspace}/port-share` (a true upsert on agent+port). */\nexport interface UpsertPortShareRequest {\n agent_name: string;\n port: number;\n share_level: PreviewShareLevel;\n protocol: PortShareProtocol;\n}\n\n/** A port-share row, mirroring `codersdk.WorkspaceAgentPortShare`. */\nexport interface WorkspaceAgentPortShare {\n workspace_id: string;\n agent_name: string;\n port: number;\n share_level: PortShareLevel;\n protocol: PortShareProtocol;\n}\n\n/**\n * Issue a JSON request against the v2 API. Mirrors `CoderChatClient`: the\n * session-token header, and a {@link CoderApiError} carrying the server's\n * `message`/`detail` on any non-2xx (body tolerated missing or malformed).\n */\nasync function request<T>(\n conn: WorkspaceApiConnection,\n method: string,\n path: string,\n body?: unknown,\n signal?: AbortSignal,\n): Promise<T> {\n const fetchFn = conn.fetch ?? globalThis.fetch.bind(globalThis);\n const headers: Record<string, string> = { \"Coder-Session-Token\": conn.token };\n if (body !== undefined) headers[\"Content-Type\"] = \"application/json\";\n\n const res = await fetchFn(`${conn.baseUrl.replace(/\\/$/, \"\")}${path}`, {\n method,\n headers,\n body: body === undefined ? undefined : JSON.stringify(body),\n signal,\n });\n const text = await res.text().catch(() => \"\");\n let parsed: unknown;\n try {\n parsed = text.length > 0 ? JSON.parse(text) : undefined;\n } catch {\n parsed = undefined;\n }\n if (!res.ok) {\n const errObj = (parsed ?? {}) as { message?: string; detail?: string };\n throw new CoderApiError({\n status: res.status,\n method,\n path,\n message: errObj.message ?? res.statusText ?? \"request failed\",\n detail: errObj.detail,\n });\n }\n return parsed as T;\n}\n\n/** Fetch a workspace (owner, name, agents) by UUID: GET `/api/v2/workspaces/{workspace}`. */\nexport function getWorkspace(\n conn: WorkspaceApiConnection,\n workspaceId: string,\n signal?: AbortSignal,\n): Promise<WorkspaceSummary> {\n return request<WorkspaceSummary>(\n conn,\n \"GET\",\n `/api/v2/workspaces/${workspaceId}`,\n undefined,\n signal,\n );\n}\n\n/**\n * Fetch the deployment's wildcard app host: GET `/api/v2/applications/host`.\n * The endpoint predates port sharing and exists on all supported servers;\n * an empty `host` means no wildcard access URL is configured.\n */\nexport function getAppsHost(\n conn: WorkspaceApiConnection,\n signal?: AbortSignal,\n): Promise<AppHostResponse> {\n return request<AppHostResponse>(conn, \"GET\", \"/api/v2/applications/host\", undefined, signal);\n}\n\n/**\n * Create or update a port share: POST `/api/v2/workspaces/{workspace}/port-share`\n * (an upsert keyed on agent+port; re-posting updates level/protocol in place).\n * A 404 is rethrown with a hint that the server may predate port sharing —\n * the whole route is absent on Coder servers older than ~v2.9.\n */\nexport async function upsertPortShare(\n conn: WorkspaceApiConnection,\n workspaceId: string,\n req: UpsertPortShareRequest,\n signal?: AbortSignal,\n): Promise<WorkspaceAgentPortShare> {\n const path = `/api/v2/workspaces/${workspaceId}/port-share`;\n try {\n return await request<WorkspaceAgentPortShare>(conn, \"POST\", path, req, signal);\n } catch (err) {\n if (err instanceof CoderApiError && err.status === 404) {\n throw new CoderApiError({\n status: 404,\n method: \"POST\",\n path,\n message: \"port sharing is unavailable\",\n detail:\n \"the workspace was not found, or this Coder server predates port sharing (added in Coder v2.9)\",\n });\n }\n throw err;\n }\n}\n\n/** Inputs for building a preview URL (see `CoderAgent.getPreview`). */\nexport interface ResolvePreviewOptions {\n workspaceId: string;\n /** Workspace port the app listens on. */\n port: number;\n /** Agent that serves the port; defaults to the workspace's only agent. */\n agentName?: string;\n /** Protocol the app speaks inside the workspace (not the browser scheme). Default `\"http\"`. */\n protocol?: PortShareProtocol;\n}\n\n/** The workspace's only agent's name, or a clear error naming the candidates. */\nfunction defaultAgentName(workspace: WorkspaceSummary): string {\n const agents = (workspace.latest_build?.resources ?? []).flatMap((r) => r.agents ?? []);\n const first = agents[0];\n if (agents.length === 1 && first) return first.name;\n if (agents.length === 0) {\n throw new CoderAgentError(\n `Workspace \"${workspace.name}\" has no agents in its latest build (is it running?); ` +\n `pass agentName explicitly.`,\n );\n }\n throw new CoderAgentError(\n `Workspace \"${workspace.name}\" has ${agents.length} agents ` +\n `(${agents.map((a) => a.name).join(\", \")}); pass agentName to pick one.`,\n );\n}\n\n/**\n * Ports must be integral, in TCP range, and — stricter than TCP — at least\n * 1000: Coder's subdomain app parser (`appurl.PortRegex`, `^\\d{4,5}s?$`) only\n * treats 4–5 digit labels as ports, so a URL for port 80 would be parsed as an\n * app named \"80\" and never resolve. Reject those up front instead of returning\n * a dead URL.\n */\nfunction assertValidPort(port: number): void {\n if (!Number.isInteger(port) || port < 1 || port > 65535) {\n throw new CoderAgentError(`Invalid port ${port}: expected an integer between 1 and 65535.`);\n }\n if (port < 1000) {\n throw new CoderAgentError(\n `Port ${port} cannot be previewed: Coder subdomain app URLs only encode 4-5 digit ports ` +\n `(1000-65535). Serve the preview on a higher port instead.`,\n );\n }\n}\n\n/**\n * Compose the subdomain preview URL for a workspace port the same way the\n * dashboard does: the label `{port}{s?}--{agent}--{workspace}--{owner}`\n * replaces the `*` in the deployment's wildcard app host, and the browser\n * scheme follows the deployment's own access URL. The trailing `s` (from\n * `protocol: \"https\"`) tells the proxy to speak TLS *to the workspace port*;\n * it does not affect the browser scheme.\n */\nexport async function resolveWorkspacePreview(\n conn: WorkspaceApiConnection,\n opts: ResolvePreviewOptions,\n signal?: AbortSignal,\n): Promise<{ url: string; agentName: string }> {\n assertValidPort(opts.port);\n const [workspace, appHost] = await Promise.all([\n getWorkspace(conn, opts.workspaceId, signal),\n getAppsHost(conn, signal),\n ]);\n if (!appHost.host) {\n throw new CoderAgentError(\n \"This Coder deployment has no wildcard app host configured (--wildcard-access-url), \" +\n \"so subdomain preview URLs are unavailable.\",\n );\n }\n const agentName = opts.agentName ?? defaultAgentName(workspace);\n const suffix = opts.protocol === \"https\" ? \"s\" : \"\";\n const label = `${opts.port}${suffix}--${agentName}--${workspace.name}--${workspace.owner_name}`;\n // The host keeps a literal `*` (possibly inside the first label, e.g.\n // `*--apps.example.com`) and may carry an explicit port on dev deployments —\n // substitute the label into the whole host string, dashboard-style.\n const scheme = new URL(conn.baseUrl).protocol;\n return { url: `${scheme}//${appHost.host.replace(/\\*/g, label)}`, agentName };\n}\n\n/** Inputs for sharing and previewing a workspace port (see `CoderAgent.sharePreview`). */\nexport interface SharePreviewRequest extends ResolvePreviewOptions {\n /** Who may open the port. Default `\"authenticated\"`. */\n shareLevel?: PreviewShareLevel;\n}\n\n/**\n * Resolve a port's preview URL, then upsert its share level so the URL is\n * reachable beyond the workspace owner. Returns the server-confirmed level\n * (falling back to the requested one when the server omits it).\n */\nexport async function shareWorkspacePreview(\n conn: WorkspaceApiConnection,\n opts: SharePreviewRequest,\n signal?: AbortSignal,\n): Promise<{ url: string; shareLevel: PreviewShareLevel }> {\n const requested = opts.shareLevel ?? \"authenticated\";\n const { url, agentName } = await resolveWorkspacePreview(conn, opts, signal);\n const share = await upsertPortShare(\n conn,\n opts.workspaceId,\n {\n agent_name: agentName,\n port: opts.port,\n share_level: requested,\n protocol: opts.protocol ?? \"http\",\n },\n signal,\n );\n // Old servers may answer with an empty/partial body; trust it when present.\n const level = share?.share_level && share.share_level !== \"owner\" ? share.share_level : requested;\n return { url, shareLevel: level };\n}\n","import type {\n LanguageModelV4CallOptions,\n LanguageModelV4Message,\n LanguageModelV4Prompt,\n LanguageModelV4ToolResultOutput,\n SharedV4FileData,\n} from \"@ai-sdk/provider\";\nimport type { ChatInputPart, DynamicTool, ToolResult } from \"../coder/types.js\";\n\n/** Namespace key for this package's options on AI SDK message-part `providerOptions`. */\nexport const CODER_PROVIDER_OPTIONS = \"coder\";\n\n/** Shape of this package's `providerOptions.coder` payload on a file part. */\nexport interface CoderFileProviderOptions {\n /** A pre-uploaded chat-file id to reuse instead of uploading the part's bytes. */\n fileId?: string;\n}\n\n/** Concatenated system messages, mapped to chatd's `system_prompt`. */\nexport function extractSystemPrompt(prompt: LanguageModelV4Prompt): string | undefined {\n const parts = prompt.filter(\n (m): m is Extract<LanguageModelV4Message, { role: \"system\" }> => m.role === \"system\",\n );\n if (parts.length === 0) return undefined;\n const joined = parts\n .map((m) => m.content)\n .join(\"\\n\\n\")\n .trim();\n return joined.length > 0 ? joined : undefined;\n}\n\n/** The content array of a user message (text and file parts). */\nexport type UserContent = Extract<LanguageModelV4Message, { role: \"user\" }>[\"content\"];\n\n/**\n * Uploads a file part's bytes and resolves to its chatd file id. Supplied by\n * the language model (which holds the client + organization id); kept abstract\n * here so {@link userContentToInputParts} stays pure and testable.\n */\nexport type FilePartUploader = (file: {\n data: SharedV4FileData;\n mediaType: string;\n filename?: string;\n}) => Promise<string>;\n\n/** Reads a pre-uploaded file id from a file part's `providerOptions.coder.fileId`. */\nfunction coderFileId(providerOptions: unknown): string | undefined {\n const ns = (providerOptions as Record<string, CoderFileProviderOptions> | undefined)?.[\n CODER_PROVIDER_OPTIONS\n ];\n // Require a non-empty id: an empty string is not a usable reference, so fall\n // back to uploading rather than emitting `file_id: \"\"`.\n return typeof ns?.fileId === \"string\" && ns.fileId.length > 0 ? ns.fileId : undefined;\n}\n\n/**\n * Maps a user message's content to chatd input parts. Text passes through;\n * file parts are turned into `file` parts referencing an uploaded file id —\n * either reused from `providerOptions.coder.fileId` (a pre-uploaded handle) or\n * uploaded on the fly via {@link FilePartUploader}.\n */\nexport async function userContentToInputParts(\n content: UserContent,\n uploadFile: FilePartUploader,\n): Promise<ChatInputPart[]> {\n // Uploads are independent, so run them concurrently; `map` preserves order.\n const parts = await Promise.all(\n content.map(async (part): Promise<ChatInputPart | null> => {\n if (part.type === \"text\") {\n return part.text.length > 0 ? { type: \"text\", text: part.text } : null;\n }\n if (part.type === \"file\") {\n const fileId =\n coderFileId(part.providerOptions) ??\n (await uploadFile({\n data: part.data,\n mediaType: part.mediaType,\n filename: part.filename,\n }));\n return { type: \"file\", file_id: fileId };\n }\n return null;\n }),\n );\n return parts.filter((p): p is ChatInputPart => p !== null);\n}\n\nfunction toolResultOutputToChatd(output: LanguageModelV4ToolResultOutput): {\n value: unknown;\n isError: boolean;\n} {\n switch (output.type) {\n case \"text\":\n return { value: output.value, isError: false };\n case \"json\":\n return { value: output.value, isError: false };\n case \"error-text\":\n return { value: output.value, isError: true };\n case \"error-json\":\n return { value: output.value, isError: true };\n case \"execution-denied\":\n return { value: { execution_denied: true, reason: output.reason }, isError: true };\n case \"content\":\n return { value: output.value, isError: false };\n default:\n return { value: output, isError: false };\n }\n}\n\nexport type TurnAction =\n | { kind: \"new-turn\"; content: UserContent }\n | { kind: \"resume\"; toolResults: ToolResult[] }\n | { kind: \"noop\" };\n\n/**\n * Decides what to do with a fresh `doStream`/`doGenerate` prompt:\n * - trailing `tool` message(s) → resume the in-flight chatd turn by submitting\n * those tool results (the AI SDK executed a client tool between steps);\n * - otherwise the trailing `user` message → a new turn.\n */\nexport function classifyTurnAction(prompt: LanguageModelV4Prompt): TurnAction {\n if (prompt.length === 0) return { kind: \"noop\" };\n const last = prompt[prompt.length - 1];\n if (!last) return { kind: \"noop\" };\n\n if (last.role === \"tool\") {\n const toolResults: ToolResult[] = [];\n // Collect from all trailing consecutive tool messages.\n for (let i = prompt.length - 1; i >= 0; i--) {\n const m = prompt[i];\n if (!m || m.role !== \"tool\") break;\n for (const part of m.content) {\n if (part.type !== \"tool-result\") continue;\n const { value, isError } = toolResultOutputToChatd(part.output);\n toolResults.unshift({ tool_call_id: part.toolCallId, output: value, is_error: isError });\n }\n }\n return { kind: \"resume\", toolResults };\n }\n\n if (last.role === \"user\") {\n return { kind: \"new-turn\", content: last.content };\n }\n\n return { kind: \"noop\" };\n}\n\n/** Maps AI SDK function tools to chatd client-executed (\"dynamic\") tools. */\nexport function toolsToDynamicTools(tools: LanguageModelV4CallOptions[\"tools\"]): DynamicTool[] {\n if (!tools) return [];\n const out: DynamicTool[] = [];\n for (const tool of tools) {\n if (tool.type !== \"function\") continue; // provider tools are not forwarded\n out.push({\n name: tool.name,\n description: tool.description,\n input_schema: tool.inputSchema,\n });\n }\n return out;\n}\n\n/** The set of tool names the AI SDK side owns (i.e. client-executed). */\nexport function dynamicToolNames(tools: LanguageModelV4CallOptions[\"tools\"]): Set<string> {\n const names = new Set<string>();\n for (const tool of tools ?? []) {\n if (tool.type === \"function\") names.add(tool.name);\n }\n return names;\n}\n","import { CoderAgentError } from \"../errors.js\";\nimport type { ChatStreamEvent } from \"../coder/types.js\";\n\n/**\n * A chat event stream retained across turn segments (issue #44).\n *\n * One `doGenerate`/`doStream` call is one *segment* of a chatd turn; a turn\n * that drives client tools spans several segments (each `requires_action`\n * pause hands control back to the AI SDK, which executes the tools and calls\n * the model again with the results). Before this wrapper existed each segment\n * dialed its own WebSocket and closed it on settle — one TLS + upgrade\n * handshake per tool round trip, plus a between-connections window where\n * events had to be recovered via `after_id` catch-up on the next dial.\n *\n * This class owns the turn-lifetime stream instead:\n *\n * - It wraps the self-redialing reader (`streamChatEvents` via\n * `CoderChatClient.streamEvents`), dialed lazily with the STREAM's own\n * lifetime signal — never a segment's abort signal, so a segment\n * abort/timeout cannot tear the socket down.\n * - Reads are memoized: at most one `next()` is outstanding on the underlying\n * generator, and a read that a detached segment abandoned (it lost a race\n * against its abort signal, a grace timer, or the REST recovery fetch) is\n * adopted by the next segment via {@link read} — the event it eventually\n * resolves with is delivered, never dropped.\n * - Lifecycle is a small state machine: `attached` (a segment is consuming) →\n * {@link pause} on a healthy `requires_action` settle (reusable by the\n * resume segment) → {@link attach} or {@link close}. Every other segment\n * exit closes the stream — an aborted/timed-out segment's stream could\n * never be reused (its partially consumed in-progress episode cannot be\n * safely re-read), so it is not kept. Closing is idempotent; the socket\n * also ends on session teardown (`resetSession()` /\n * `[Symbol.asyncDispose]()`) or replacement by the next turn's fresh\n * stream.\n *\n * The redial machinery inside the wrapped reader keeps working while paused:\n * {@link pause} leaves a read outstanding, so a socket drop during tool\n * execution is redialed (from the turn's original cursor) in the background\n * instead of adding reconnect latency to the resume segment.\n */\nexport class SessionChatStream {\n readonly chatId: string;\n /**\n * The reader id stamped on the wrapped reader's `ws:*` transport events —\n * the model stamps it on `segment:settle` so segments correlate with the\n * connection that served them (issue #94).\n */\n readonly reader: number;\n readonly #events: AsyncGenerator<ChatStreamEvent, void, void>;\n readonly #controller = new AbortController();\n\n #pending: Promise<IteratorResult<ChatStreamEvent, void>> | undefined;\n #attached = true;\n #reusable = false;\n #closePromise: Promise<void> | undefined;\n\n constructor(opts: {\n chatId: string;\n /** The reader id `open` passes to the underlying `streamChatEvents` call. */\n reader: number;\n /** Opens the underlying reader, bound to the stream's lifetime signal. */\n open: (signal: AbortSignal) => AsyncGenerator<ChatStreamEvent, void, void>;\n }) {\n this.chatId = opts.chatId;\n this.reader = opts.reader;\n this.#events = opts.open(this.#controller.signal);\n }\n\n get closed(): boolean {\n return this.#closePromise !== undefined;\n }\n\n /** Whether a segment is currently consuming this stream. */\n get attached(): boolean {\n return this.#attached;\n }\n\n /** Whether a resume segment for `chatId` may {@link attach}. */\n canAttach(chatId: string): boolean {\n return !this.closed && this.#reusable && this.chatId === chatId;\n }\n\n /**\n * The next event, memoized: repeated calls return the SAME promise until\n * {@link consumed} acknowledges delivery, so a read abandoned by one segment\n * is handed to the next instead of losing its event.\n */\n read(): Promise<IteratorResult<ChatStreamEvent, void>> {\n if (!this.#pending) {\n if (!this.#attached && !this.closed) {\n // Reads belong to segments; only the paused prefetch (issued by\n // pause() itself) may exist between segments.\n throw new CoderAgentError(\"SessionChatStream.read() without an attached segment\");\n }\n this.#pending = this.#events.next();\n // Tag as handled: a rejection can land while no segment is racing this\n // read (detached, or the segment lost a race); the next consumer still\n // observes it by awaiting read().\n this.#pending.catch(() => {});\n }\n return this.#pending;\n }\n\n /**\n * Acknowledge that the segment received `p`'s result and the next\n * {@link read} should issue a fresh read. A no-op unless `p` is the\n * outstanding read — a raced-out segment never consumes.\n */\n consumed(p: Promise<IteratorResult<ChatStreamEvent, void>>): void {\n if (this.#pending === p) this.#pending = undefined;\n }\n\n /** Attach the resume segment to a stream paused at `requires_action`. */\n attach(): void {\n if (!this.canAttach(this.chatId) || this.#attached) {\n throw new CoderAgentError(\"SessionChatStream.attach() on a non-reusable stream\");\n }\n this.#attached = true;\n this.#reusable = false;\n }\n\n /**\n * Segment settled at a healthy `requires_action` pause: keep the stream for\n * the tool-result resume, with a read outstanding so the reader's redial\n * machinery stays live while the client tools execute.\n */\n pause(): void {\n this.#attached = false;\n if (this.closed) return;\n this.#reusable = true;\n if (!this.#pending) {\n this.#pending = this.#events.next();\n this.#pending.catch(() => {});\n }\n }\n\n /**\n * Close the underlying reader (and socket). Idempotent; safe on a stream\n * whose generator already ended or threw.\n */\n close(): Promise<void> {\n this.#closePromise ??= (async () => {\n this.#attached = false;\n this.#reusable = false;\n this.#controller.abort();\n try {\n await this.#events.return(undefined);\n } catch {\n /* the reader's teardown errors are not the closer's problem */\n }\n })();\n return this.#closePromise;\n }\n}\n","import type {\n JSONObject,\n LanguageModelV4FinishReason,\n LanguageModelV4StreamPart,\n LanguageModelV4Usage,\n} from \"@ai-sdk/provider\";\nimport { CoderChatError } from \"../errors.js\";\nimport {\n type ChatErrorPayload,\n type ChatMessage,\n type ChatMessagePart,\n type ChatMessageUsage,\n type ChatStatus,\n type ChatStreamEvent,\n TERMINAL_STATUSES,\n} from \"../coder/types.js\";\n\n/** `a + b` where an undefined operand contributes nothing; undefined only when both are. */\nfunction addDefined(a: number | undefined, b: number | undefined): number | undefined {\n if (b === undefined) return a;\n return (a ?? 0) + b;\n}\n\n/**\n * How each known wire usage field aggregates across a segment's steps:\n * counters sum; `context_limit` is a property of the model rather than a\n * counter, so the newest step's value wins. Typed against\n * {@link ChatMessageUsage} so adding a wire field without classifying it\n * fails the build instead of silently vanishing from the totals. Fields this\n * SDK doesn't know yet (newer servers) pass through with newest-wins\n * semantics so `usage.raw` stays the escape hatch for them.\n */\nconst USAGE_FIELD_KINDS = {\n input_tokens: \"sum\",\n output_tokens: \"sum\",\n total_tokens: \"sum\",\n reasoning_tokens: \"sum\",\n cache_creation_tokens: \"sum\",\n cache_read_tokens: \"sum\",\n total_cost_micros: \"sum\",\n total_runtime_ms: \"sum\",\n context_limit: \"newest\",\n} as const satisfies Record<keyof ChatMessageUsage, \"sum\" | \"newest\">;\n\nfunction mapUsage(u: ChatMessageUsage | undefined): LanguageModelV4Usage {\n // chatd normalizes provider usage so `input_tokens` counts only the UNCACHED\n // prompt tokens — cache reads/writes are reported separately (both Anthropic\n // and OpenAI are normalized this way server-side). The V4 `inputTokens.total`\n // is the full prompt size, so the cache components must be added back; with\n // prompt caching active, `input_tokens` alone is near zero.\n return {\n inputTokens: {\n total: addDefined(\n addDefined(u?.input_tokens, u?.cache_read_tokens),\n u?.cache_creation_tokens,\n ),\n noCache: u?.input_tokens,\n cacheRead: u?.cache_read_tokens,\n cacheWrite: u?.cache_creation_tokens,\n },\n outputTokens: {\n total: u?.output_tokens,\n text: undefined,\n reasoning: u?.reasoning_tokens,\n },\n // Preserve the turn's wire usage (snake_case) so callers can reach fields\n // the normalized shape has no slot for (context_limit, cost, runtime, …).\n ...(u ? { raw: u as unknown as JSONObject } : {}),\n };\n}\n\n/**\n * Sums per-message wire usage into segment totals. chatd attaches usage to\n * every assistant message it commits — one per internal model step — so the\n * segment's consumption is the sum over all of them, not the last message's\n * (the AI SDK then sums segments into the turn total). Fields stay absent\n * unless at least one message reported them (old servers keep looking\n * unchanged); non-counter and unknown fields take the newest reporting\n * message's value (see {@link USAGE_FIELD_KINDS}).\n */\nfunction accumulateUsage(\n byMessageId: ReadonlyMap<number, ChatMessageUsage>,\n): ChatMessageUsage | undefined {\n if (byMessageId.size === 0) return undefined;\n // Wire usage is parsed JSON; newer servers may send fields beyond\n // ChatMessageUsage, so accumulate on the JSON shape and narrow at the end.\n const total: JSONObject = {};\n // Map insertion order is not id order after a revision re-set, so track the\n // reporting message id per newest-wins key explicitly.\n const newestIdByKey = new Map<string, number>();\n for (const [id, u] of byMessageId) {\n for (const [key, value] of Object.entries(u)) {\n if (value === undefined) continue;\n if (USAGE_FIELD_KINDS[key as keyof ChatMessageUsage] === \"sum\" && typeof value === \"number\") {\n total[key] = ((total[key] as number | undefined) ?? 0) + value;\n } else if (id > (newestIdByKey.get(key) ?? -1)) {\n // `context_limit`, malformed counters, and any field this SDK doesn't\n // know yet: not summable — the newest reporting message's value wins,\n // which keeps `usage.raw` a forward-compatible escape hatch.\n newestIdByKey.set(key, id);\n total[key] = value;\n }\n }\n }\n return total as ChatMessageUsage;\n}\n\nfunction jsonResult(value: unknown): NonNullable<unknown> {\n // chatd tool results are arbitrary JSON; the V4 tool-result `result` must be\n // a non-null JSON value.\n return (value ?? {}) as NonNullable<unknown>;\n}\n\n/** A snapshot's full content of one kind, in wire order. */\nfunction joinContent(content: readonly ChatMessagePart[], kind: \"text\" | \"reasoning\"): string {\n let full = \"\";\n for (const p of content) if (p.type === kind) full += p.text ?? \"\";\n return full;\n}\n\n/**\n * Canonical JSON of a decoded wire value: object keys sorted recursively, so\n * two decodings of the same server-side content compare equal regardless of\n * property order. Inputs are parsed JSON (plain objects/arrays/primitives —\n * never `undefined`, functions, or cycles), which keeps this total.\n */\nfunction canonicalJson(value: unknown): string {\n if (Array.isArray(value)) return `[${value.map(canonicalJson).join(\",\")}]`;\n if (value !== null && typeof value === \"object\") {\n const entries = Object.entries(value);\n entries.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));\n const body = entries.map(([k, v]) => `${JSON.stringify(k)}:${canonicalJson(v)}`);\n return `{${body.join(\",\")}}`;\n }\n return JSON.stringify(value) ?? \"null\";\n}\n\n/**\n * The attribution gate for a QUEUED submission (issue #114): the chat was\n * busy, so `createChatMessage` returned only a queue entry — no committed\n * message id — and everything on the stream between the pre-turn cursor and\n * the entry's materialization belongs to the chat's CURRENT (concurrent or\n * dying) run. While the gate is up, {@link TurnTranslator.ingest} attributes\n * NOTHING to this turn; it only scans for the turn's own user-message\n * snapshot, whose id then becomes the effective turn cursor.\n *\n * Correlation protocol (chatd has no wire-level queue-entry ↔ message id\n * link; the queue is strictly FIFO and both the queue entry and the\n * materialized message expose the same server-side content encoding):\n * - a `message` snapshot with role `user`, id past the cursor, and content\n * structurally equal to the queue entry's becomes the CANDIDATE anchor;\n * - the `queue_update` that chatd emits after every queue change confirms\n * it: our entry gone from the full queue list ⇒ the candidate is our\n * materialization (anchor there); our entry still listed ⇒ the candidate\n * was an EARLIER identical-content entry's materialization (drop it and\n * keep waiting). Message snapshots that arrive between candidate and\n * confirmation (a reconnect replays this turn's own commits before the\n * connect-time `queue_update`) are buffered and replayed on anchor;\n * - without a queue-entry id to confirm against (older wire shapes), the\n * first matching user message anchors directly.\n *\n * Dead ends fail fast instead of hanging until timeout: chatd does NOT\n * auto-promote the queue after an error settle, so an `error` event while\n * gated means the entry is stuck behind a dead chat; our entry vanishing from\n * `queue_update` without a candidate means it was deleted externally (or its\n * materialized form could not be correlated). Residual ambiguity — identical\n * concurrent submissions promoted within one replay window — needs a wire\n * correlation from chatd and is tracked as a follow-up.\n */\ninterface QueuedSubmissionGate {\n /** The queue-entry id from the submission response, when the wire sent one. */\n queuedId: number | undefined;\n /** Canonical content of the queue entry; undefined matches any user message. */\n expected: string | undefined;\n /** Latest matching user-message id awaiting `queue_update` confirmation. */\n candidateId: number | undefined;\n /** Message snapshots since the candidate, replayed through ingest on anchor. */\n buffered: ChatMessage[];\n /** Set when the wait can never complete; the model surfaces it as a throw. */\n deadEnd: ChatErrorPayload | undefined;\n}\n\n/** Constructor options for {@link TurnTranslator}. */\nexport interface TurnTranslatorOptions {\n dynamicToolNames: ReadonlySet<string>;\n turnCursor?: number;\n /**\n * Tool-call ids whose results the session already submitted. With the\n * stream retained across segments (and redialed with the turn's original\n * cursor), a reconnect can replay a previous segment's `action_required`\n * snapshot; re-emitting an already-answered call would make the AI SDK\n * execute the tool a second time. A live reference — the set grows as\n * later segments submit results.\n */\n submittedToolCallIds?: ReadonlySet<string>;\n /**\n * Present when the submission was QUEUED (`CreateChatMessageResponse`\n * carried no committed `message`): `turnCursor` is then only the PRE-TURN\n * cursor, and attribution is withheld until the queue entry materializes as\n * this turn's own user message — see {@link QueuedSubmissionGate} for the\n * correlation protocol. `id`/`content` come from the response's\n * `queued_message`; either may be absent on unexpected wire shapes, which\n * degrades matching (no confirmation / match-any) but never widens\n * attribution beyond the first observed user message.\n */\n queuedSubmission?: { id?: number; content?: readonly ChatMessagePart[] };\n}\n\n/**\n * Translates one chatd turn's {@link ChatStreamEvent} stream into a sequence of\n * `LanguageModelV4StreamPart`s for the AI SDK.\n *\n * Text/reasoning is reconciled against a per-message emitted-content ledger:\n * `message_part` deltas are emitted as they stream (they carry no message id,\n * so they accumulate as *pending* content), and each assistant `message`\n * snapshot attributes the pending content to its id and emits whatever suffix\n * of the snapshot's full content is still missing. That makes fast\n * snapshot-only messages (everything missing), trailing snapshots after deltas\n * (nothing missing), commit-during-disconnect snapshots (the tail the lost\n * deltas never delivered), and append-revisions of earlier messages (the\n * appended suffix) all the same operation — and a snapshot whose content does\n * not extend what was emitted (a byte-identical replay, or a rewrite that\n * cannot be expressed as deltas) is safely suppressed instead of\n * double-emitted.\n *\n * Client (custom) tool calls are emitted from the reliable `action_required`\n * event and left for the AI SDK to execute. chatd's own server-side tools are\n * surfaced best-effort as `providerExecuted` tool calls/results.\n *\n * A QUEUED submission additionally starts behind an attribution gate: until\n * the queue entry materializes as this turn's own user message, every event\n * belongs to the chat's concurrent run and is withheld (see\n * {@link QueuedSubmissionGate} and {@link TurnTranslatorOptions.queuedSubmission}).\n *\n * The mirror direction is the PROMOTION SETTLE BOUNDARY: a user-role\n * `message` snapshot past the turn's effective cursor settles this turn\n * mid-run and freezes the translator, because chatd starts the chat's next\n * run without ever emitting a terminal status for the finished one (see\n * {@link settledAtPromotionBoundary}, issue #120). Both behaviors key off\n * the SAME promoted user snapshot — it is the submitting turn's anchor\n * (turn start) and the finishing turn's boundary (turn end) — and stay\n * independent because each keys strictly off its OWN turn's cursor: the\n * anchor advances the submitter's cursor TO the snapshot's id, while the\n * boundary requires an id strictly PAST the finisher's cursor.\n */\nexport class TurnTranslator {\n readonly #dynamicToolNames: ReadonlySet<string>;\n readonly #submittedToolCallIds: ReadonlySet<string>;\n\n #seq = 0;\n // `pending` accumulates delta content that has been emitted but not yet\n // attributed to a message id (deltas carry none); the next assistant\n // snapshot claims it. It survives block closes — a closed block loses its\n // stream id, never the record of what was emitted.\n #text = { id: undefined as string | undefined, pending: \"\" };\n #reasoning = { id: undefined as string | undefined, pending: \"\" };\n #currentAssistantId: number | undefined;\n // Whether text/reasoning deltas arrived since the last assistant `message`\n // snapshot — i.e. the next assistant snapshot is the trailing snapshot of the\n // message those deltas streamed (deltas carry no message id of their own).\n #deltasSinceSnapshot = false;\n\n #serverToolCalls = new Set<string>();\n #serverToolResults = new Set<string>();\n #clientToolCalls = new Set<string>();\n #clientToolCallSeen = false;\n #sources = new Set<string>();\n\n // Per-message wire usage, keyed by message id so a re-streamed revision of\n // the same message replaces its earlier entry instead of double-counting\n // (chatd re-sends full snapshots on revision bumps and history resets).\n readonly #usageByMessageId = new Map<number, ChatMessageUsage>();\n // The emitted-content ledger: the text/reasoning this translator has emitted\n // for each processed assistant message id. A redial replays the turn from\n // its original cursor and chatd re-sends full snapshots on revision bumps,\n // so already-processed messages arrive again — the ledger is what lets a\n // byte-identical replay reconcile to a no-op while an APPENDING revision (or\n // a commit-during-disconnect snapshot) yields exactly its missing suffix\n // (see #ingestMessage). `substantive` records whether any processed snapshot\n // of the message carried content (text, reasoning, tools, or sources) —\n // false only for announce-style empty snapshots, whose in-flight deltas are\n // still claimable by a same-id commit.\n readonly #emittedByMessageId = new Map<\n number,\n { text: string; reasoning: string; substantive: boolean }\n >();\n // Same-id revision snapshots (their full wire content) that arrived while\n // the NEXT step's deltas were open (latest wins). chatd sends each revision\n // once on a healthy connection, so the suffix cannot wait for a replay — it\n // is reconciled as soon as the next step's snapshot settles ownership of\n // the pending deltas, or flushed by finish() when the segment ends without\n // one (see #78).\n readonly #deferredRevisions = new Map<number, readonly ChatMessagePart[]>();\n #error: ChatErrorPayload | undefined;\n #terminalStatus: ChatStatus | undefined;\n #maxMessageId = 0;\n\n /**\n * Messages with an id at or below this cursor belong to earlier turns (or\n * to a segment that already streamed) — they arrive only as replays: the\n * initial sync when resuming a chat, or a mid-turn `history_reset` re-send\n * of the full history. Ingest skips them entirely so a replay can neither\n * re-emit earlier turns' content nor inflate this turn's usage. Mutable for\n * exactly two transitions, both of which pin the turn's OWN user message as\n * the effective floor: a QUEUED submission starts at the pre-turn cursor\n * and advances to the materialized user message's id when the gate anchors\n * (see {@link QueuedSubmissionGate}), and a ZERO cursor (this turn created\n * the chat) adopts the replay's first user snapshot — this turn's own\n * prompt — as the cursor (see #ingestMessage). Either way, replayed\n * pre-cursor tail messages then stay filtered on every redial, which reuses\n * the turn's ORIGINAL `after_id` and therefore re-delivers them.\n */\n #turnCursor: number;\n\n /** The queued-submission attribution gate; undefined once anchored (#114). */\n #queuedGate: QueuedSubmissionGate | undefined;\n\n /**\n * Set when the turn settled at a PROMOTION BOUNDARY (issue #120): a\n * user-role `message` snapshot with an id past the turn's effective cursor\n * arrived mid-run. Once set, the translator is FROZEN — every later event\n * is the successor run's traffic and ingests as a no-op — so nothing past\n * the boundary can leak into this turn's content, usage, or settle state.\n * See {@link settledAtPromotionBoundary} for why the snapshot is reliable.\n */\n #promotionBoundary = false;\n\n constructor(opts: TurnTranslatorOptions) {\n this.#dynamicToolNames = opts.dynamicToolNames;\n this.#turnCursor = opts.turnCursor ?? 0;\n this.#submittedToolCallIds = opts.submittedToolCallIds ?? new Set();\n const queued = opts.queuedSubmission;\n if (queued) {\n this.#queuedGate = {\n queuedId: queued.id,\n expected: queued.content !== undefined ? canonicalJson(queued.content) : undefined,\n candidateId: undefined,\n buffered: [],\n deadEnd: undefined,\n };\n }\n }\n\n get terminalStatus(): ChatStatus | undefined {\n return this.#terminalStatus;\n }\n /** Whether a client (custom) tool call has been emitted this turn. */\n get clientToolCallSeen(): boolean {\n return this.#clientToolCallSeen;\n }\n get maxMessageId(): number {\n return this.#maxMessageId;\n }\n get error(): ChatErrorPayload | undefined {\n return this.#error;\n }\n /**\n * The turn's EFFECTIVE attribution cursor: the constructor's `turnCursor`\n * until a queued submission anchors (the materialized user message's id\n * after) or a zero-cursor turn adopts its own prompt's id (see\n * #ingestMessage). Distinct from the stream's redial cursor, which\n * deliberately stays the turn's original `after_id` (see `streamChatEvents`).\n */\n get turnCursor(): number {\n return this.#turnCursor;\n }\n /**\n * Whether a queued submission is still awaiting its own user-message\n * snapshot (#114). While true, nothing is attributed to this turn — no\n * content, usage, tool calls, or settle statuses — because the stream\n * window still belongs to the chat's concurrent run.\n */\n get awaitingQueuedSubmission(): boolean {\n return this.#queuedGate !== undefined;\n }\n /**\n * Set when a queued submission's wait can never complete (see\n * {@link QueuedSubmissionGate}); the model layer throws it so the turn\n * fails fast instead of hanging until timeout/abort.\n */\n get queuedSubmissionDeadEnd(): ChatErrorPayload | undefined {\n return this.#queuedGate?.deadEnd;\n }\n /**\n * Whether the turn settled at a PROMOTION BOUNDARY (issue #120): a\n * user-role `message` snapshot with an id past the turn's effective cursor\n * arrived mid-run. chatd commits a mid-run user message only when it starts\n * the chat's NEXT run — `FinishTurn`/`FinishInterruption` promote the queue\n * head atomically with the previous run's finish (the chat transitions\n * `running → running` and NO terminal status is ever emitted for the\n * finished run), and interrupting a `requires_action` pause queues and then\n * promotes the same way — so the snapshot is proof this turn's own run is\n * over even though no terminal status was (or ever will be) sent for it.\n * The segment loop settles the turn here instead of absorbing the promoted\n * run's content, usage, and settle events; the successor run stays alive\n * and belongs to whoever submitted it, so teardown must NOT interrupt it.\n */\n get settledAtPromotionBoundary(): boolean {\n return this.#promotionBoundary;\n }\n\n // --- block helpers --------------------------------------------------------\n\n #openText(out: LanguageModelV4StreamPart[]): void {\n if (this.#reasoning.id) this.#closeReasoning(out);\n if (!this.#text.id) {\n this.#text.id = `text-${++this.#seq}`;\n out.push({ type: \"text-start\", id: this.#text.id });\n }\n }\n #closeText(out: LanguageModelV4StreamPart[]): void {\n if (this.#text.id) {\n out.push({ type: \"text-end\", id: this.#text.id });\n this.#text.id = undefined;\n }\n }\n #openReasoning(out: LanguageModelV4StreamPart[]): void {\n if (this.#text.id) this.#closeText(out);\n if (!this.#reasoning.id) {\n this.#reasoning.id = `reasoning-${++this.#seq}`;\n out.push({ type: \"reasoning-start\", id: this.#reasoning.id });\n }\n }\n #closeReasoning(out: LanguageModelV4StreamPart[]): void {\n if (this.#reasoning.id) {\n out.push({ type: \"reasoning-end\", id: this.#reasoning.id });\n this.#reasoning.id = undefined;\n }\n }\n\n #emitTextDelta(out: LanguageModelV4StreamPart[], delta: string): void {\n this.#openText(out);\n out.push({ type: \"text-delta\", id: this.#text.id as string, delta });\n }\n #emitReasoningDelta(out: LanguageModelV4StreamPart[], delta: string): void {\n this.#openReasoning(out);\n out.push({ type: \"reasoning-delta\", id: this.#reasoning.id as string, delta });\n }\n\n /**\n * Reconciles a replayed/revised snapshot of an EARLIER (non-current)\n * assistant message against its ledger entry: an APPENDING revision emits\n * its suffix, anything else is a no-op. The suffix is bracketed in its own\n * block(s) — an open block belongs to the CURRENT step, and splicing another\n * message's content into it would corrupt both — and the interrupted step's\n * next delta simply opens a fresh block (its pending content is tracked\n * independently of blocks).\n */\n #reconcileRevision(\n out: LanguageModelV4StreamPart[],\n rec: { text: string; reasoning: string },\n content: readonly ChatMessagePart[],\n ): void {\n const fullText = joinContent(content, \"text\");\n const fullReasoning = joinContent(content, \"reasoning\");\n // Per-kind extension gates: a kind's suffix emits only when the revision\n // strictly extends what the ledger already emitted for it.\n const textOk = fullText.length > rec.text.length && fullText.startsWith(rec.text);\n const reasoningOk =\n fullReasoning.length > rec.reasoning.length && fullReasoning.startsWith(rec.reasoning);\n if (!textOk && !reasoningOk) return;\n this.#closeText(out);\n this.#closeReasoning(out);\n // Emit each kind's missing sub-span walking the snapshot in WIRE order —\n // the same walk as first-sight reconciliation (see #ingestMessage) — so a\n // revision appending both kinds surfaces its suffixes the way the model\n // produced them, not reasoning-first (#79).\n let seenText = 0;\n let seenReasoning = 0;\n for (const p of content) {\n if (p.type === \"text\") {\n const end = seenText + (p.text ?? \"\").length;\n const from = Math.max(seenText, rec.text.length);\n if (textOk && end > from) this.#emitTextDelta(out, fullText.slice(from, end));\n seenText = end;\n } else if (p.type === \"reasoning\") {\n const end = seenReasoning + (p.text ?? \"\").length;\n const from = Math.max(seenReasoning, rec.reasoning.length);\n if (reasoningOk && end > from)\n this.#emitReasoningDelta(out, fullReasoning.slice(from, end));\n seenReasoning = end;\n }\n }\n if (textOk) rec.text = fullText;\n if (reasoningOk) rec.reasoning = fullReasoning;\n this.#closeText(out);\n this.#closeReasoning(out);\n }\n\n // --- tool helpers ---------------------------------------------------------\n\n #isClientTool(name: string | undefined): boolean {\n return name !== undefined && this.#dynamicToolNames.has(name);\n }\n\n #emitServerToolCall(out: LanguageModelV4StreamPart[], part: ChatMessagePart): void {\n const id = part.tool_call_id;\n const name = part.tool_name;\n if (!id || !name) return;\n if (part.args === undefined) return; // wait for complete args (snapshot)\n if (this.#serverToolCalls.has(id)) return;\n this.#serverToolCalls.add(id);\n // `dynamic: true` is load-bearing: server tools are not in the client ToolSet, and\n // the AI SDK only tolerates unknown tool names on `providerExecuted && dynamic`\n // calls. Without it every server tool call is marked `invalid`, which injects a\n // phantom tool-error output and halts the tool loop on that step — stranding the\n // turn whenever a client tool call is pending in the same segment.\n out.push({\n type: \"tool-input-start\",\n id,\n toolName: name,\n providerExecuted: true,\n dynamic: true,\n });\n out.push({ type: \"tool-input-end\", id });\n out.push({\n type: \"tool-call\",\n toolCallId: id,\n toolName: name,\n input: typeof part.args === \"string\" ? part.args : JSON.stringify(part.args),\n providerExecuted: true,\n dynamic: true,\n });\n }\n\n #emitServerToolResult(out: LanguageModelV4StreamPart[], part: ChatMessagePart): void {\n const id = part.tool_call_id;\n const name = part.tool_name;\n if (!id || !name) return;\n if (part.result === undefined) return;\n if (this.#serverToolResults.has(id)) return;\n // Only pair with a call emitted in THIS segment. A result whose call streamed in\n // an earlier segment (chatd paused for a client tool in between, and the resume\n // segment starts past the assistant message) would reach the AI SDK call-less,\n // and generateText throws \"Tool call <id> not found.\" Drop the orphan — the\n // server-side transcript still has it.\n if (!this.#serverToolCalls.has(id)) return;\n this.#serverToolResults.add(id);\n out.push({\n type: \"tool-result\",\n toolCallId: id,\n toolName: name,\n result: jsonResult(part.result),\n isError: part.is_error ?? false,\n // Mirror the call's `dynamic: true` so call and result land in the same\n // bucket (steps[*].dynamicToolCalls / dynamicToolResults, UI streams).\n dynamic: true,\n });\n }\n\n /**\n * Emits a chatd `source` part as a standalone V4 url source (no text-block\n * bracketing needed). Deduped by the emitted id so a part streamed as a\n * `message_part` isn't re-emitted by its trailing `message` snapshot, while\n * snapshot-only turns still emit theirs.\n */\n #emitSource(out: LanguageModelV4StreamPart[], part: ChatMessagePart): void {\n const url = part.url;\n if (!url) return; // the V4 source part requires both id and url\n const id = part.source_id || url;\n if (this.#sources.has(id)) return;\n this.#sources.add(id);\n out.push({\n type: \"source\",\n sourceType: \"url\",\n id,\n url,\n ...(part.title !== undefined ? { title: part.title } : {}),\n });\n }\n\n // --- ingest ---------------------------------------------------------------\n\n ingest(ev: ChatStreamEvent): LanguageModelV4StreamPart[] {\n // Frozen at the promotion boundary (issue #120): everything after it —\n // the successor run's deltas, commits (usage included), statuses, errors,\n // pauses — is the NEXT turn's traffic and must not mutate this turn.\n if (this.#promotionBoundary) return [];\n const gate = this.#queuedGate;\n if (gate) return this.#ingestGated(gate, ev);\n const out: LanguageModelV4StreamPart[] = [];\n switch (ev.type) {\n case \"message_part\":\n this.#ingestMessagePart(out, ev);\n break;\n case \"message\":\n if (ev.message) this.#ingestMessage(out, ev.message);\n break;\n case \"action_required\":\n for (const tc of ev.action_required?.tool_calls ?? []) {\n if (this.#clientToolCalls.has(tc.tool_call_id)) continue;\n // A replayed pause snapshot for a call this session already\n // answered (see the constructor doc) — not this segment's work.\n if (this.#submittedToolCallIds.has(tc.tool_call_id)) continue;\n // A pause can follow a deferred revision without a settling\n // assistant snapshot; the revision arrived first, so its suffix\n // must precede the tool call to keep the stream's content order\n // (as the error branch already does). Replayed/answered calls\n // above skip this — a replay must stay a no-op.\n this.#flushDeferredRevisions(out);\n this.#closeText(out);\n this.#closeReasoning(out);\n this.#clientToolCalls.add(tc.tool_call_id);\n this.#clientToolCallSeen = true;\n out.push({ type: \"tool-input-start\", id: tc.tool_call_id, toolName: tc.tool_name });\n out.push({ type: \"tool-input-end\", id: tc.tool_call_id });\n out.push({\n type: \"tool-call\",\n toolCallId: tc.tool_call_id,\n toolName: tc.tool_name,\n input: tc.args,\n });\n }\n break;\n case \"error\":\n if (ev.error) {\n this.#error = ev.error;\n // doGenerate() throws on the error part and closes the generator\n // before finish() can run (issue #72) — parts yielded after it are\n // never pulled, so the deferred-revision flush must precede it.\n this.#flushDeferredRevisions(out);\n out.push({ type: \"error\", error: new CoderChatError(ev.error) });\n }\n break;\n case \"status\":\n if (ev.status) {\n if (TERMINAL_STATUSES.has(ev.status.status)) {\n this.#terminalStatus = ev.status.status;\n } else {\n // A non-terminal transition means the chat is generating again:\n // any previously recorded settle is stale. This matters for a\n // stream that outlives its segment — a redial during the\n // tool-result resume replays the connect-time status snapshot,\n // which can still say `requires_action` from the previous\n // segment's pause; without the clear, the segment loop would\n // treat every later event as post-settle and truncate the\n // resumed generation via its safety valve. Real terminal\n // statuses are unaffected: the segment breaks on them\n // immediately, before any transition could arrive.\n this.#terminalStatus = undefined;\n }\n }\n break;\n default:\n break; // retry / queue_update / preview_reset / history_reset\n }\n return out;\n }\n\n /**\n * Ingest while the queued-submission gate is up (#114): the stream window\n * still belongs to the chat's concurrent run, so nothing is attributed —\n * this only advances cursor bookkeeping, runs the anchor correlation\n * protocol (see {@link QueuedSubmissionGate}), and detects dead ends.\n * Returns parts only at the anchoring transition, when buffered snapshots\n * of this turn's own already-committed messages are replayed through the\n * normal path.\n */\n #ingestGated(gate: QueuedSubmissionGate, ev: ChatStreamEvent): LanguageModelV4StreamPart[] {\n switch (ev.type) {\n case \"message\": {\n const message = ev.message;\n if (!message) break;\n // Cursor bookkeeping stays live while gated: these are committed\n // messages the session has observed, and the NEXT turn's boundary\n // must advance past them whether or not this turn ever anchors.\n if (message.id > this.#maxMessageId) this.#maxMessageId = message.id;\n if (message.id <= this.#turnCursor) break; // pre-turn replay\n if (message.role === \"user\" && this.#matchesQueuedContent(gate, message)) {\n // Without a queue-entry id there is nothing to confirm against\n // (older wire shapes): anchor on the first match directly.\n if (gate.queuedId === undefined) return this.#anchorQueuedTurn(message.id);\n // Candidate — the LATEST match before confirmation wins: on a\n // reconnect replay, an identical-content entry queued AHEAD of\n // ours materialized first, and its user message replays before\n // ours does. Messages buffered for a superseded candidate belonged\n // to that earlier entry's run; drop them with it.\n gate.candidateId = message.id;\n gate.buffered = [];\n } else if (gate.candidateId !== undefined) {\n // Between candidate and confirmation only message snapshots can\n // arrive (chatd flushes commits before the queue update): they are\n // this turn's own already-committed messages when the candidate is\n // right, and the superseded run's when it is not — buffer them and\n // let the confirmation decide.\n gate.buffered.push(message);\n }\n break;\n }\n case \"queue_update\": {\n if (gate.queuedId === undefined) break;\n // A `queue_update` always carries the FULL queue, but the wire field\n // is `omitempty`: an EMPTY queue arrives with `queued_messages`\n // absent — the very shape the common single-entry promotion produces.\n // Absent therefore means empty, not \"no information\".\n const queue = ev.queued_messages ?? [];\n if (queue.some((entry) => entry.id === gate.queuedId)) {\n // Our entry is still queued, so any candidate materialized from an\n // EARLIER identical-content entry — not ours. Keep waiting.\n gate.candidateId = undefined;\n gate.buffered = [];\n break;\n }\n if (gate.candidateId !== undefined) {\n const buffered = gate.buffered;\n const out = this.#anchorQueuedTurn(gate.candidateId);\n for (const message of buffered) this.#ingestMessage(out, message);\n return out;\n }\n // Gone from the queue, yet no user message matched: deleted\n // externally, or the materialized form could not be correlated.\n // Either way this turn's window will never open — fail fast.\n gate.deadEnd = {\n message:\n \"The queued submission left the chat's queue without materializing as this turn's user message (deleted externally, or its materialized form could not be correlated); this turn never started.\",\n kind: \"queued_submission_lost\",\n retryable: false,\n };\n break;\n }\n case \"error\": {\n if (!ev.error) break;\n // The chat's CURRENT run failed while our entry sat in the queue.\n // chatd does not auto-promote the queue after an error settle — the\n // entry is stuck until an explicit user action — so waiting on the\n // stream can never complete. Fail fast, attributing the failure to\n // the wait rather than absorbing the other run's error as this\n // turn's own settle. Non-retryable: a resubmission to the errored\n // chat would promote the stuck entry AND queue a duplicate.\n gate.deadEnd = {\n message:\n \"The chat's active run failed while this turn's submission was queued behind it; the queued message never materialized, so this turn never started.\",\n detail: ev.error.detail ? `${ev.error.message}: ${ev.error.detail}` : ev.error.message,\n kind: \"queued_submission_lost\",\n retryable: false,\n };\n break;\n }\n default:\n // Deltas, statuses (chatd promotes the queue atomically with the\n // previous run's finish, so no terminal status separates the runs),\n // action_required (the PREVIOUS turn's pending calls — attributing\n // them would make the AI SDK execute another turn's tools), retries:\n // all the concurrent run's, all withheld.\n break;\n }\n return [];\n }\n\n /** Whether a user-message snapshot's content matches the queued entry's. */\n #matchesQueuedContent(gate: QueuedSubmissionGate, message: ChatMessage): boolean {\n if (gate.expected === undefined) return true;\n return canonicalJson(message.content ?? []) === gate.expected;\n }\n\n /**\n * Opens the turn's attribution window at `anchorId` — this turn's own user\n * message — and drops the gate. Replays of the pre-anchor tail (redials\n * reuse the turn's ORIGINAL cursor) stay filtered by the advanced cursor.\n */\n #anchorQueuedTurn(anchorId: number): LanguageModelV4StreamPart[] {\n this.#turnCursor = anchorId;\n this.#queuedGate = undefined;\n return [];\n }\n\n #ingestMessagePart(out: LanguageModelV4StreamPart[], ev: ChatStreamEvent): void {\n const mp = ev.message_part;\n if (!mp || (mp.role !== \"assistant\" && mp.role !== \"tool\")) return;\n const part = mp.part;\n switch (part.type) {\n case \"text\":\n this.#deltasSinceSnapshot = true;\n this.#openText(out);\n if (part.text) {\n out.push({ type: \"text-delta\", id: this.#text.id as string, delta: part.text });\n this.#text.pending += part.text;\n }\n break;\n case \"reasoning\":\n this.#deltasSinceSnapshot = true;\n this.#openReasoning(out);\n if (part.text) {\n out.push({ type: \"reasoning-delta\", id: this.#reasoning.id as string, delta: part.text });\n this.#reasoning.pending += part.text;\n }\n break;\n case \"tool-call\":\n this.#closeText(out);\n this.#closeReasoning(out);\n if (!this.#isClientTool(part.tool_name)) this.#emitServerToolCall(out, part);\n break;\n case \"tool-result\":\n if (!this.#isClientTool(part.tool_name)) this.#emitServerToolResult(out, part);\n break;\n case \"source\":\n this.#emitSource(out, part);\n break;\n default:\n break;\n }\n }\n\n #ingestMessage(out: LanguageModelV4StreamPart[], message: ChatMessage): void {\n // The anchor drain (#anchorQueuedTurn's caller replays buffered\n // snapshots through here directly) can cross the promotion boundary\n // mid-loop; the freeze applies to the rest of the buffer too.\n if (this.#promotionBoundary) return;\n if (message.id > this.#maxMessageId) this.#maxMessageId = message.id;\n // Replay of an earlier turn (or an already-streamed segment): skip it\n // entirely — see #turnCursor. In the normal path the server-side\n // `after_id` filter means such ids never arrive, so this only bites on\n // replays, where processing them would re-emit old content as this\n // turn's output and double-count its usage.\n if (message.id <= this.#turnCursor) return;\n // PROMOTION SETTLE BOUNDARY (issue #120). A user-role message with an id\n // past the turn's effective cursor can only be committed when chatd\n // starts the chat's NEXT run: `FinishTurn` and `FinishInterruption`\n // promote the queue head atomically with the previous run's finish (the\n // chat transitions `running → running` — no terminal status is ever\n // emitted for the finished run), and interrupting a `requires_action`\n // pause commits its cancellations as role \"tool\" and promotes the same\n // way. Verified against chatd source: every other user-role insert\n // happens only on an idle/terminal chat; the turn's own submission id is\n // at or below the effective cursor (a new turn's cursor IS its message\n // id; a queued turn's gate anchors the cursor AT its materialized\n // message); tool results commit as role \"tool\"; and compaction's\n // user-role summary has visibility \"model\" and never reaches the stream.\n // Redial replays cannot false-settle: message ids are strictly\n // increasing at commit time, so a replay re-delivers only ids at or\n // below the cursor, which the filter above drops. Settle here rather\n // than absorb the successor run's content, usage, and settle events —\n // the mirror of the #114 submission-side gate.\n if (message.role === \"user\") {\n // A ZERO cursor means this turn created the chat (or resumed an empty\n // one): there is no committed-message id to anchor on, the stream dials\n // without `after_id`, and the replay opens with this turn's OWN prompt —\n // the one user snapshot that is not a boundary. It is guaranteed first:\n // chatd commits the prompt in the chat-creation transaction, so any\n // foreign submission gets a strictly higher id, and both initial replay\n // and redials deliver messages in id order. Adopt its id as the\n // effective cursor — the same normalization every other turn shape gets\n // from its submission response or gate anchor — so replays of it stay\n // filtered above and any LATER user snapshot is a true boundary.\n if (this.#turnCursor === 0) {\n this.#turnCursor = message.id;\n return;\n }\n this.#promotionBoundary = true;\n // A recorded `requires_action` is stale by definition here — the pause\n // it announced was superseded by the interrupt that produced this\n // boundary. Clear it so finish() cannot classify the settled turn as\n // \"tool-calls\" and send the AI SDK to resume a run that no longer\n // exists. (Real terminal statuses never coexist with the boundary: the\n // segment loop breaks on them before another event is ingested.)\n this.#terminalStatus = undefined;\n return;\n }\n // chatd attaches usage to the assistant messages it commits (one per\n // internal model step); tool messages never carry usage, and under\n // summing, accepting a hypothetical mirrored copy would double-count.\n if (message.usage && message.role === \"assistant\") {\n this.#usageByMessageId.set(message.id, message.usage);\n }\n const content = message.content ?? [];\n\n if (message.role === \"assistant\") {\n const fullText = joinContent(content, \"text\");\n const fullReasoning = joinContent(content, \"reasoning\");\n const known = this.#emittedByMessageId.get(message.id);\n\n if (known && message.id !== this.#currentAssistantId) {\n // A replay or revision bump of an EARLIER message (a redial replays\n // the turn from its original cursor; chatd re-sends full snapshots on\n // revision bumps). Reconcile against the ledger: a byte-identical\n // replay is a no-op, an APPENDING revision yields its suffix, and a\n // rewrite stays suppressed (already-emitted text cannot be retracted\n // in a delta stream — suppression is the safe side). NOT taken for\n // the current message: deltas carry no message id, so while the next\n // step streams, #currentAssistantId still names the last committed\n // one and its pending deltas must join the reconciliation below.\n this.#reconcileRevision(out, known, content);\n } else if (known && this.#deltasSinceSnapshot && known.substantive) {\n // A same-id snapshot racing the NEXT step's open deltas. A message\n // whose earlier snapshot already carried content — text, reasoning,\n // or only tools/sources — never streams more deltas (content changes\n // to committed messages arrive only as revision snapshots), so the\n // pending deltas belong to the next, still-uncommitted message —\n // they must NOT be claimed here, even when the revised content\n // happens to share their prefix. Emit nothing and claim nothing yet:\n // the snapshot is DEFERRED and reconciled once the next step's\n // snapshot settles ownership of the pending deltas (see below).\n // Announce-style commits (the earlier snapshot was EMPTY) stay on\n // the attribution path below — their deltas ARE this message's\n // content.\n this.#deferredRevisions.set(message.id, content);\n } else {\n // First sight of this message, or a re-snapshot of the CURRENT one —\n // progressive snapshot-mode growth, or an announce-style commit\n // filling in content that (partially) streamed as deltas.\n //\n // New assistant message boundary: close the previous message's\n // blocks. Skipped when deltas arrived since the previous assistant\n // snapshot: deltas carry no message id, so they belong to the message\n // THIS snapshot finalizes, and their block is this message's block.\n if (\n this.#currentAssistantId !== undefined &&\n this.#currentAssistantId !== message.id &&\n !this.#deltasSinceSnapshot\n ) {\n this.#closeText(out);\n this.#closeReasoning(out);\n }\n this.#currentAssistantId = message.id;\n\n // What this snapshot's content must extend: everything already\n // attributed to this message id, plus the pending deltas it is about\n // to claim. The suffix beyond that is exactly the content this client\n // never received — nothing for a byte-identical trailing snapshot,\n // everything for a snapshot-only message, and the missing tail when\n // the message committed while a dropped stream was redialing (even if\n // the block already closed: the ledger, unlike a block cursor,\n // survives closes). A snapshot that does NOT extend it — a replay\n // racing the next step's open deltas, or a rewrite — attributes\n // nothing and emits nothing: the pending deltas may belong to the\n // next, still-uncommitted message, and a later snapshot naming them\n // settles the attribution instead.\n const rec = known ?? { text: \"\", reasoning: \"\", substantive: false };\n // Per-kind gates: a kind reconciles only when the snapshot's full\n // content extends what was already attributed + pending for it.\n const textAttributed = rec.text + this.#text.pending;\n const reasoningAttributed = rec.reasoning + this.#reasoning.pending;\n const textOk = fullText.startsWith(textAttributed);\n const reasoningOk = fullReasoning.startsWith(reasoningAttributed);\n // Emit each part's missing sub-span walking the snapshot in WIRE\n // order, so recovered blocks interleave the way the model produced\n // them (text→reasoning→text stays that way even when the drop left\n // the text block open) — opening a kind closes the other, and the\n // ledger, unlike a block cursor, loses nothing to the close.\n let seenText = 0;\n let seenReasoning = 0;\n for (const p of content) {\n if (p.type === \"text\") {\n const end = seenText + (p.text ?? \"\").length;\n const from = Math.max(seenText, textAttributed.length);\n if (textOk && end > from) this.#emitTextDelta(out, fullText.slice(from, end));\n seenText = end;\n } else if (p.type === \"reasoning\") {\n const end = seenReasoning + (p.text ?? \"\").length;\n const from = Math.max(seenReasoning, reasoningAttributed.length);\n if (reasoningOk && end > from)\n this.#emitReasoningDelta(out, fullReasoning.slice(from, end));\n seenReasoning = end;\n }\n }\n if (textOk) rec.text = fullText;\n if (reasoningOk) rec.reasoning = fullReasoning;\n // Reaching this path means THIS snapshot owns the pending deltas (the\n // race guard above intercepts the one ambiguous ordering), so they\n // are settled even when reconciliation failed: a rewrite-on-commit\n // stays suppressed, but its stale pending must not poison every later\n // message's reconciliation.\n this.#text.pending = \"\";\n this.#reasoning.pending = \"\";\n rec.substantive ||= content.some((p) =>\n p.type === \"text\" || p.type === \"reasoning\" ? (p.text ?? \"\").length > 0 : true,\n );\n this.#emittedByMessageId.set(message.id, rec);\n this.#deltasSinceSnapshot = false;\n // Delta ownership is settled: reconcile revisions that were deferred\n // while the race was open. They are now revisions of an EARLIER\n // message and get the same bracketed suffix-or-suppress treatment.\n for (const [id, deferred] of this.#deferredRevisions) {\n if (id === message.id) continue;\n const deferredRec = this.#emittedByMessageId.get(id);\n if (deferredRec) this.#reconcileRevision(out, deferredRec, deferred);\n this.#deferredRevisions.delete(id);\n }\n }\n // Tool calls/results and sources are id-deduped, so snapshots process them\n // unconditionally (even when the snapshot is a text/reasoning no-op).\n for (const part of content) {\n if (part.type === \"tool-call\" && !this.#isClientTool(part.tool_name))\n this.#emitServerToolCall(out, part);\n else if (part.type === \"tool-result\" && !this.#isClientTool(part.tool_name))\n this.#emitServerToolResult(out, part);\n else if (part.type === \"source\") this.#emitSource(out, part);\n }\n } else if (message.role === \"tool\") {\n for (const part of content) {\n if (part.type === \"tool-result\" && !this.#isClientTool(part.tool_name))\n this.#emitServerToolResult(out, part);\n else if (part.type === \"source\") this.#emitSource(out, part);\n }\n }\n }\n\n // --- finish ---------------------------------------------------------------\n\n /**\n * Flushes revisions still deferred behind a delta-ownership race that never\n * settled — the racing step ended terminally without committing a snapshot,\n * so the drain in #ingestMessage never ran. chatd sent each revision once;\n * this is the segment's last chance to emit its suffix (#78). Attribution\n * care: the pending deltas' owner never committed, so they attribute to no\n * one — reconciliation is against the ledger alone, and only\n * ledger-extension suffixes may emit. Runs at finish() and, because\n * doGenerate() throws on a streamed error part and closes the generator\n * before finish() (issue #72), ahead of the error part in ingest().\n */\n #flushDeferredRevisions(out: LanguageModelV4StreamPart[]): void {\n for (const [id, deferred] of this.#deferredRevisions) {\n const rec = this.#emittedByMessageId.get(id);\n if (rec) this.#reconcileRevision(out, rec, deferred);\n }\n this.#deferredRevisions.clear();\n }\n\n finish(): LanguageModelV4StreamPart[] {\n const out: LanguageModelV4StreamPart[] = [];\n this.#flushDeferredRevisions(out);\n this.#closeText(out);\n this.#closeReasoning(out);\n\n let unified: LanguageModelV4FinishReason[\"unified\"];\n if (this.#error || this.#terminalStatus === \"error\") unified = \"error\";\n else if (this.#promotionBoundary) {\n // The wire cannot say WHY a run ended at a promotion boundary:\n // \"completed, then promoted\" (FinishTurn) and \"interrupted, then\n // promoted\" (FinishInterruption, `busy_behavior: \"interrupt\"`) emit the\n // same events — promoted user message, queue_update, `running` — and no\n // terminal status for the finished run. \"stop\" is a deliberate\n // approximation covering both: completion is the common case (every\n // queued follow-up on a healthy run promotes through FinishTurn), and\n // no unified reason expresses \"interrupted\". It also outranks\n // \"tool-calls\": a pause superseded by the interrupt that produced this\n // boundary must not send the AI SDK to resume a run that no longer\n // exists. `raw` stays undefined — fabricating a terminal status the\n // server never sent would mislead observability (the segment:settle\n // event carries a finishReason but no `status` for this settle shape).\n // Exact classification needs a wire signal for the finished run;\n // issue #119 is the upstream ask.\n unified = \"stop\";\n } else if (this.#clientToolCallSeen || this.#terminalStatus === \"requires_action\")\n unified = \"tool-calls\";\n else unified = \"stop\";\n\n const usage = accumulateUsage(this.#usageByMessageId);\n\n // Surface server-side cost/runtime (sent by newer servers as extra usage\n // fields), summed over the segment's steps, under `providerMetadata.coder`.\n // Omitted entirely when the server sent neither, so old servers look\n // unchanged. NOTE: the AI SDK propagates only the FINAL segment's\n // providerMetadata to `result.providerMetadata` — whole-turn cost for a\n // client-tool turn is the sum over `result.steps[*].providerMetadata`.\n const coder: JSONObject = {};\n if (usage?.total_cost_micros !== undefined) coder.total_cost_micros = usage.total_cost_micros;\n if (usage?.total_runtime_ms !== undefined) coder.total_runtime_ms = usage.total_runtime_ms;\n\n out.push({\n type: \"finish\",\n usage: mapUsage(usage),\n finishReason: { unified, raw: this.#terminalStatus },\n ...(Object.keys(coder).length > 0 ? { providerMetadata: { coder } } : {}),\n });\n return out;\n }\n}\n","import type {\n LanguageModelV4,\n LanguageModelV4CallOptions,\n LanguageModelV4Content,\n LanguageModelV4GenerateResult,\n LanguageModelV4StreamPart,\n LanguageModelV4StreamResult,\n LanguageModelV4Usage,\n SharedV4Warning,\n} from \"@ai-sdk/provider\";\nimport { assertSupportedAiVersion } from \"../ai-version.js\";\nimport { CoderAgentError, CoderApiError, CoderChatError, CoderStreamError } from \"../errors.js\";\nimport { CoderChatClient } from \"../coder/client.js\";\nimport type {\n ChatInputPart,\n ChatQueuedMessage,\n ChatStatus,\n ChatStreamEvent,\n ChatStreamToolCall,\n CreateChatMessageRequest,\n CreateChatRequest,\n ReasoningEffort,\n} from \"../coder/types.js\";\nimport { dataContentToFileContent } from \"../files.js\";\nimport {\n classifyTurnAction,\n dynamicToolNames,\n extractSystemPrompt,\n type FilePartUploader,\n toolsToDynamicTools,\n type UserContent,\n userContentToInputParts,\n} from \"./prompt.js\";\nimport { nextStreamReaderId } from \"../coder/ws.js\";\nimport { SessionChatStream } from \"./session-stream.js\";\nimport { TurnTranslator, type TurnTranslatorOptions } from \"./translate.js\";\nimport {\n safeTransportEmitter,\n type SegmentSettleTransportEvent,\n type SegmentStartTransportEvent,\n type TransportEventHandler,\n} from \"../transport-events.js\";\n\n/**\n * How long a segment that has settled into `requires_action` waits for the\n * `action_required` event before recovering the pending tool calls from chat\n * history over REST (see `#recoverRequiresAction`). Long enough that the\n * normal fast path — the event follows the status on the same connection,\n * typically within milliseconds — never fires it; short enough to turn a lost\n * event from a `requestTimeoutMs`-sized hang into a ~2s hiccup.\n */\nconst ACTION_REQUIRED_GRACE_MS = 2_000;\n\n/** Sentinel resolved by the grace timer, distinguishable from a stream read. */\nconst GRACE_EXPIRED = Symbol(\"action-required-grace-expired\");\n\n/**\n * Sentinel resolved when the SEGMENT's abort signal fires. Segment reads race\n * this against the shared stream instead of wiring the signal into the stream\n * itself: the stream's fate is a session policy decided at segment exit (a\n * healthy client-tool pause survives its segment; every other exit closes the\n * stream), never a side effect of signal chaining.\n */\nconst SEGMENT_ABORTED = Symbol(\"segment-aborted\");\n\nfunction startGraceTimer(ms: number) {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const expired = new Promise<typeof GRACE_EXPIRED>((resolve) => {\n timer = setTimeout(() => resolve(GRACE_EXPIRED), ms);\n });\n return { expired, cancel: () => clearTimeout(timer) };\n}\n\n/**\n * Observability out-params threaded from the `segment:*` wrapper into\n * `#runTurnInner`, read by the wrapper's `finally` for settle classification\n * on paths where the inner generator is closed without delivering its normal\n * terminal parts:\n * - `chatId`: the segment's chat id once known, surviving a session discard\n * (`resetSession()`) that clears the instance field before the settle\n * event is emitted;\n * - `terminalStatus`: the translator's terminal chat status, for a settle\n * whose `finish` part never reached the wrapper — `doGenerate()` throws on\n * a streamed error part and closes the generator before `finish()` runs\n * (issue #72);\n * - `timeoutError`: set when the turn's internal `requestTimeoutMs` signal\n * fired. That signal is created inside the turn and is invisible to the\n * wrapper, so a `gen.return()` teardown (consumer cancel with no pull\n * pending) would otherwise misclassify the timeout as a consumer teardown\n * (issue #73);\n * - `reader`: the id of the reader (`streamChatEvents` call) serving the\n * segment, written at stream acquisition; the settle carries it so\n * segments correlate with the connection's `ws:*` events (issue #94).\n */\ninterface SegmentChatRef {\n chatId?: string;\n terminalStatus?: ChatStatus;\n timeoutError?: CoderChatError;\n reader?: number;\n}\n\nconst EMPTY_USAGE: LanguageModelV4Usage = {\n inputTokens: {\n total: undefined,\n noCache: undefined,\n cacheRead: undefined,\n cacheWrite: undefined,\n },\n outputTokens: { total: undefined, text: undefined, reasoning: undefined },\n};\n\nexport interface CoderLanguageModelConfig {\n client: CoderChatClient;\n organizationId: string;\n /** Model hint (UUID, `provider:model`, model id, or display-name substring). */\n model?: string;\n /** Reasoning effort sent on chat creation and each user-message submission. */\n reasoningEffort?: ReasoningEffort;\n /** Bind the chat to a Coder workspace (enables workspace tools). */\n workspaceId?: string;\n /** chatd-side MCP servers to enable. */\n mcpServerIds?: string[];\n planMode?: \"\" | \"plan\";\n /** Resume an existing chat instead of creating a new one. */\n chatId?: string;\n /**\n * Resume cursor for `chatId`: the newest chat message id a previous\n * instance had already seen — persist {@link CoderLanguageModel.lastSeenMessageId}\n * alongside the chat id and supply both here. Skips the resumed turn's\n * pre-prompt seed GET (the single-message history probe that would\n * otherwise establish the cursor); without it, behavior is unchanged and\n * the seed GET remains the fallback. Must be a positive integer obtained\n * from the getter, and requires `chatId` (the constructor rejects anything\n * else — a wrong cursor would silently misattribute replayed history).\n */\n lastSeenMessageId?: number;\n /**\n * Per-segment time budget in milliseconds, applied to each model round-trip\n * (one `doStream`/`doGenerate` call: chat creation or message/tool-result\n * submission, plus the server-side run until it settles or pauses for a client\n * tool). If exceeded, the run is interrupted server-side and the call rejects\n * with a retryable {@link CoderChatError} (`kind: \"timeout\"`). A multi-step\n * `generate()` that drives client tools makes several segments, so this bounds\n * each segment, not the whole call — to cap total wall-clock, pass\n * `abortSignal: AbortSignal.timeout(ms)`. Unset or non-positive means no limit.\n */\n requestTimeoutMs?: number;\n /**\n * Observability hook: receives this model's `segment:*` transport events\n * (turn-segment start/settle with terminal status and timing). HTTP and\n * WebSocket events are emitted by the {@link CoderChatClient} — pass the\n * hook to its options too (CoderAgent wires both from one setting).\n * Exceptions it throws are swallowed; without it, no event objects are\n * allocated. See {@link CoderTransportEvent}.\n */\n onTransportEvent?: TransportEventHandler;\n}\n\n/**\n * A {@link LanguageModelV4} that is backed by a remote Coder `chatd` agent\n * runtime instead of a raw LLM. One model instance owns one chatd chat\n * (a \"session\"): the chat is created lazily on the first turn and reused for\n * subsequent turns and for client-tool resume steps.\n *\n * The chatd server runs the agent loop (model calls, server-side tools,\n * compaction) itself. This model therefore represents *one chatd segment* per\n * `doStream` call — it advances the chat until it settles (`waiting`/\n * `completed`) or pauses for a client tool (`requires_action`). When the AI SDK\n * executes a client tool and calls `doStream` again with the tool result, this\n * model resumes the same chat. The two loops mesh at the client-tool boundary.\n *\n * NOTE: a single model instance is single-flight — do not run concurrent\n * generations against the same instance/session.\n *\n * The session retains ONE event stream per turn (issue #44): a segment that\n * pauses for client tools leaves the WebSocket open, and the tool-result\n * resume segment keeps reading it — no re-dial per tool round trip. The\n * socket closes when its segment ends any other way (terminal settle, error,\n * abort, timeout), when the next turn replaces it, or on session teardown\n * (`resetSession()` / `[Symbol.asyncDispose]()`). A turn that pauses for\n * client tools but never resumes leaves the socket open until teardown, so\n * dispose or reset sessions you abandon mid-turn.\n */\nexport class CoderLanguageModel implements LanguageModelV4 {\n readonly specificationVersion = \"v4\" as const;\n readonly provider = \"coder.chatd\";\n readonly modelId: string;\n readonly supportedUrls: Record<string, RegExp[]> = {};\n\n readonly #config: CoderLanguageModelConfig;\n #chatId: string | undefined;\n /**\n * The last chat id this model created or attached to, retained as a CLEANUP\n * target — see {@link lastKnownChatId}. Unlike `#chatId` it survives\n * {@link resetSession}, and is only replaced when a later turn's chat\n * supersedes it or cleared by {@link noteChatCleanedUp} once cleanup\n * actually succeeded against it.\n */\n #lastKnownChatId: string | undefined;\n /**\n * Every chat id stranded by an AUTOMATIC session discard (a stream failure\n * on a chat the failed turn itself created) and not yet cleaned up. A\n * single `#lastKnownChatId` slot is not enough here: each AI-SDK\n * `maxRetries` attempt strands its own fresh chat and only the final\n * attempt's error reaches the caller, so without this ledger every attempt\n * before the last would leak unnamed. Drained by {@link noteChatCleanedUp}.\n * Deliberate abandonment ({@link resetSession}) does NOT record here — the\n * caller chose to walk away and may want the chat kept.\n */\n readonly #strandedChatIds = new Set<string>();\n #lastSeenMessageId = 0;\n #resolvedModelConfigId: string | undefined;\n #modelResolved = false;\n readonly #submittedToolCallIds = new Set<string>();\n #inFlight = false;\n /** The turn-lifetime event stream, retained across client-tool pauses. */\n #retainedStream: SessionChatStream | undefined;\n /** Exception-isolated observability emitter for `segment:*` events. */\n readonly #emitTransportEvent: TransportEventHandler | undefined;\n #segmentSeq = 0;\n\n constructor(config: CoderLanguageModelConfig) {\n // Fail fast on an incompatible AI SDK major (see peer dependency `ai@^7`).\n assertSupportedAiVersion();\n this.#config = config;\n this.modelId = config.model ?? \"chatd\";\n this.#chatId = config.chatId;\n this.#lastKnownChatId = config.chatId;\n // The resume cursor is validated eagerly: internally 0 is the \"no cursor\n // yet\" sentinel (it triggers the seed GET), so a zero/negative/fractional\n // value from the caller must fail fast here rather than silently corrupt\n // the turn boundary — and a cursor without its chat is a caller bug (the\n // pair is persisted together; see the lastSeenMessageId getter).\n if (config.lastSeenMessageId !== undefined) {\n if (!Number.isSafeInteger(config.lastSeenMessageId) || config.lastSeenMessageId <= 0) {\n throw new CoderAgentError(\n `lastSeenMessageId must be a positive integer (got ${config.lastSeenMessageId}); ` +\n \"persist the value of the lastSeenMessageId getter and supply it verbatim.\",\n );\n }\n if (!config.chatId) {\n throw new CoderAgentError(\n \"lastSeenMessageId requires chatId: a resume cursor is meaningless without the chat it belongs to.\",\n );\n }\n this.#lastSeenMessageId = config.lastSeenMessageId;\n }\n this.#emitTransportEvent = safeTransportEmitter(config.onTransportEvent);\n }\n\n get chatId(): string | undefined {\n return this.#chatId;\n }\n\n /**\n * The resume cursor: the newest chat message id this session has observed,\n * or `undefined` before any turn has streamed (nothing to persist then —\n * a fresh instance seeds from history instead). Persist it alongside\n * {@link chatId} once a turn settles and supply both to a future instance\n * ({@link CoderLanguageModelConfig.lastSeenMessageId}) to skip the resumed\n * turn's pre-prompt seed GET. Read it between turns, not mid-generation:\n * the cursor advances when a segment ends.\n */\n get lastSeenMessageId(): number | undefined {\n return this.#lastSeenMessageId > 0 ? this.#lastSeenMessageId : undefined;\n }\n\n /**\n * The last chat id this model created or attached to, for CLEANUP targeting\n * only — generation always targets {@link chatId}. It survives\n * {@link resetSession} (including the automatic session discard after a\n * fresh-chat stream failure, which clears {@link chatId} so a retry starts\n * fresh while the failed chat still exists server-side), is superseded when\n * a later turn creates or attaches to another chat, and is cleared via\n * {@link noteChatCleanedUp} once cleanup actually succeeded against it.\n */\n get lastKnownChatId(): string | undefined {\n return this.#lastKnownChatId;\n }\n\n /**\n * Chat ids stranded by automatic session discards and not yet cleaned up,\n * oldest first. Each retryable fresh-chat stream failure strands one chat;\n * under AI-SDK `maxRetries` several can accumulate while only the final\n * attempt's error (and {@link lastKnownChatId}) names the newest —\n * `CoderAgent.archive()` retires all of them. Ids leave the list only via\n * {@link noteChatCleanedUp}.\n */\n get strandedChatIds(): string[] {\n return [...this.#strandedChatIds];\n }\n\n /**\n * Forgets `chatId` as a cleanup target — call it only after cleanup\n * actually succeeded against that chat (e.g. `CoderAgent.archive()`\n * archived it), so later cleanup calls don't re-target an already-archived\n * chat: removes it from {@link strandedChatIds} and, when it is the\n * last-known id, clears {@link lastKnownChatId} (match-guarded: a stale\n * completion must not erase a newer chat's id). The session field\n * ({@link chatId}) is deliberately untouched — archiving does not reset the\n * session.\n */\n noteChatCleanedUp(chatId: string): void {\n this.#strandedChatIds.delete(chatId);\n if (this.#lastKnownChatId === chatId) this.#lastKnownChatId = undefined;\n }\n\n /**\n * Drops the current session so the next turn creates a fresh chat. Also\n * closes the retained event stream, if any (fire-and-forget — a sync reset\n * must not wait on socket teardown, and `close()` never rejects).\n * {@link lastKnownChatId} deliberately survives the reset: the dropped chat\n * still exists server-side and cleanup must still be able to target it.\n */\n resetSession(): void {\n this.#chatId = undefined;\n this.#lastSeenMessageId = 0;\n this.#submittedToolCallIds.clear();\n const stream = this.#retainedStream;\n this.#retainedStream = undefined;\n if (stream) void stream.close();\n }\n\n /**\n * Closes the retained event stream, if any. The stream otherwise closes\n * when its turn settles or fails, or when the next turn replaces it — but a\n * turn that pauses for client tools and never resumes (e.g. the caller\n * stopped the tool loop, or aborted between segments) leaves the socket\n * open, so dispose (`await using`) or {@link resetSession} sessions you\n * abandon mid-turn.\n */\n async [Symbol.asyncDispose](): Promise<void> {\n const stream = this.#retainedStream;\n this.#retainedStream = undefined;\n if (stream) await stream.close();\n }\n\n /**\n * Closes a stream retained by a client-tool pause. Called on explicit\n * interrupts ({@link CoderAgent.interrupt}): an interrupted pause is dead —\n * its buffer would otherwise collect the interrupt's own settle events,\n * which a later tool-result resume would consume as if they were the\n * resumed generation's (truncating the turn), and if the tool loop never\n * resumes the socket would outlive the explicit interrupt. A stream a\n * segment is actively reading is left alone: that segment observes the\n * settle the interrupt produces and closes the stream itself.\n */\n async closePausedStream(): Promise<void> {\n const stream = this.#retainedStream;\n if (!stream || stream.attached) return;\n this.#retainedStream = undefined;\n await stream.close();\n }\n\n /**\n * The stream a segment reads: a resume segment re-attaches to the stream\n * its predecessor left paused at `requires_action` (the whole point of\n * retention — no re-dial per tool round trip); everything else — a new\n * turn, or a pause the caller abandoned — replaces it with a fresh dial,\n * because a stale buffer from a settle this client never observed must not\n * leak into the new turn.\n */\n #acquireStream(resume: boolean, chatId: string, afterId: number | undefined): SessionChatStream {\n const retained = this.#retainedStream;\n if (resume && retained?.canAttach(chatId)) {\n retained.attach();\n return retained;\n }\n this.#retainedStream = undefined;\n if (retained) void retained.close();\n // Allocated here (not inside streamChatEvents) so the model knows the id\n // the reader will stamp on its `ws:*` events — `segment:settle` carries\n // it to correlate segments with the connection that served them (#94).\n const reader = nextStreamReaderId();\n const created = new SessionChatStream({\n chatId,\n reader,\n open: (streamSignal) =>\n this.#config.client.streamEvents(chatId, { afterId, signal: streamSignal, reader }),\n });\n this.#retainedStream = created;\n return created;\n }\n\n /** Close `stream` and drop the retention slot if it still holds it. */\n async #closeStream(stream: SessionChatStream): Promise<void> {\n if (this.#retainedStream === stream) this.#retainedStream = undefined;\n await stream.close();\n }\n\n async #resolveModelConfigId(signal?: AbortSignal): Promise<string | undefined> {\n if (this.#modelResolved) return this.#resolvedModelConfigId;\n if (this.#config.model) {\n this.#resolvedModelConfigId = await this.#config.client.resolveModelConfigId(\n this.#config.model,\n this.#config.organizationId,\n signal,\n );\n }\n this.#modelResolved = true;\n return this.#resolvedModelConfigId;\n }\n\n /**\n * Resolve a user message's content to chatd input parts, uploading any file\n * parts to chat-file storage (the upload endpoint needs only the organization\n * id, so this runs before the chat exists). Pre-uploaded files carried via\n * `providerOptions.coder.fileId` are referenced without re-uploading.\n */\n #buildContent(\n content: UserContent,\n signal?: AbortSignal,\n onUpload?: () => void,\n ): Promise<ChatInputPart[]> {\n const uploadFile: FilePartUploader = async (f) => {\n const uploaded = await this.#config.client.uploadChatFile(\n this.#config.organizationId,\n { content: dataContentToFileContent(f.data), mediaType: f.mediaType, name: f.filename },\n signal,\n );\n onUpload?.();\n return uploaded.id;\n };\n return userContentToInputParts(content, uploadFile);\n }\n\n /**\n * REST fallback for a `requires_action` segment whose `action_required`\n * event never arrived: recover the turn's pending client tool calls from\n * committed chat history (`GET /chats/{id}/messages`) — the same history\n * chatd itself derives `action_required` from server-side. Returns synthetic\n * stream events for the translator: the turn's message snapshots first (so\n * text, usage, and the message cursor stay consistent even if the stream\n * also missed a commit — the translator's id-keyed dedup makes re-ingesting\n * already-delivered ones a no-op), then one `action_required` event carrying\n * the unresolved dynamic tool calls. Returns `[]` when history shows no\n * pending calls — nothing to recover, the caller keeps waiting on the\n * stream, bounded by `requestTimeoutMs`/abort.\n */\n async #recoverRequiresAction(\n chatId: string,\n dynamicNames: ReadonlySet<string>,\n /** The turn's EFFECTIVE cursor (`TurnTranslator.turnCursor`). */\n turnCursor: number,\n signal?: AbortSignal,\n ): Promise<ChatStreamEvent[]> {\n // Fetch WITHOUT an `after_id` cursor: cursored reads page ASCENDING\n // (oldest first), so a long turn would truncate away the newest messages\n // — exactly where the pending call lives. A cursor-less read pages\n // newest-first, which guarantees the last assistant message and\n // everything after it (the inputs of the derivation below) are on the\n // FIRST page; `has_more` then only refers to older history we don't\n // need. The turn filter is applied client-side instead.\n const { messages } = await this.#config.client.getMessages(chatId, { limit: 200 }, signal);\n // Restrict to this turn's messages (the translator would skip older ids\n // anyway, but the derivation must not see earlier turns), in id order.\n const turnMessages = messages.filter((m) => m.id > turnCursor).sort((a, b) => a.id - b.id);\n // Mirror chatd's own derivation (`actionRequiredFromHistory` →\n // `unresolvedToolCallsFromHistory`): the pending calls are the LAST\n // assistant message's non-provider-executed dynamic tool-call parts,\n // minus ids already handled by later messages' tool results.\n const lastAssistant = turnMessages.findLast((m) => m.role === \"assistant\");\n if (!lastAssistant) return [];\n const handled = new Set<string>();\n for (const message of turnMessages) {\n if (message.id <= lastAssistant.id) continue;\n for (const part of message.content ?? []) {\n if (part.type === \"tool-result\" && part.tool_call_id) handled.add(part.tool_call_id);\n }\n }\n const toolCalls: ChatStreamToolCall[] = [];\n for (const part of lastAssistant.content ?? []) {\n if (part.type !== \"tool-call\" || part.provider_executed) continue;\n if (!part.tool_call_id || !part.tool_name) continue;\n if (!dynamicNames.has(part.tool_name)) continue;\n if (handled.has(part.tool_call_id)) continue;\n // A call whose result this instance already submitted must not be\n // re-emitted — the AI SDK would execute the tool a second time.\n if (this.#submittedToolCallIds.has(part.tool_call_id)) continue;\n toolCalls.push({\n tool_call_id: part.tool_call_id,\n tool_name: part.tool_name,\n // `action_required` carries args as their raw JSON text (chatd sends\n // `string(part.Args)` of the history part's json.RawMessage);\n // re-encoding the snapshot's parsed JSON value reconstructs it. A\n // JSON `null` is a legitimate argument value and must survive; only\n // an ABSENT args field falls back to empty-object args.\n args: JSON.stringify(part.args === undefined ? {} : part.args),\n });\n }\n if (toolCalls.length === 0) return [];\n const events: ChatStreamEvent[] = turnMessages.map((message) => ({\n type: \"message\",\n chat_id: chatId,\n message,\n }));\n events.push({\n type: \"action_required\",\n chat_id: chatId,\n action_required: { tool_calls: toolCalls },\n });\n return events;\n }\n\n /**\n * Wraps {@link #runTurnInner} with `segment:start`/`segment:settle`\n * observability events. A pure pass-through without a subscriber — no\n * counters, clocks, or event objects on the unobserved path. The settle\n * event is emitted in a `finally`, so every started segment settles exactly\n * once: cleanly (with the terminal status carried by the `finish` part that\n * streamed through), with an `error` summary, or — when the consumer tears\n * the stream down mid-segment — with neither. Consumer teardown has two\n * shapes: a plain generator return (no catch runs), and a\n * `ReadableStream.cancel()` that {@link doStream} routes through an abort to\n * unblock a pending socket read — the latter surfaces here as an abort\n * throw, so `isConsumerCancel` (supplied by `doStream`) reclassifies it as\n * teardown rather than a failed segment.\n *\n * Two more classification wrinkles (review findings):\n * - a streamed server `error` part is captured BEFORE being yielded —\n * `doGenerate()` throws on receiving it and closes this generator without\n * another pull, so the catch below never sees that failure;\n * - the segment's chat id is tracked in `segmentChat` (written by\n * {@link #runTurnInner} once known) — a retry-safe stream failure on a\n * freshly created chat discards the session (clearing `this.#chatId`)\n * before the finally runs, and the settle must still name the chat that\n * the preceding `ws:*`/`http:*` events identified. The same out-param\n * carries the translator's terminal status and the internal-timeout\n * marker for generator-return exits (see {@link SegmentChatRef}).\n */\n async *#runTurn(\n options: LanguageModelV4CallOptions,\n isConsumerCancel?: () => boolean,\n ): AsyncGenerator<LanguageModelV4StreamPart, void, void> {\n const emit = this.#emitTransportEvent;\n if (!emit) {\n yield* this.#runTurnInner(options);\n return;\n }\n const segment = ++this.#segmentSeq;\n const startedAt = performance.now();\n const startEvent: SegmentStartTransportEvent = {\n type: \"segment:start\",\n segment,\n timestamp: Date.now(),\n };\n if (this.#chatId !== undefined) startEvent.chatId = this.#chatId;\n emit(startEvent);\n const segmentChat: SegmentChatRef = {};\n let finishReason: LanguageModelV4GenerateResult[\"finishReason\"] | undefined;\n let failure: unknown;\n let failed = false;\n try {\n for await (const part of this.#runTurnInner(options, segmentChat)) {\n if (part.type === \"finish\") finishReason = part.finishReason;\n else if (part.type === \"error\" && !failed) {\n failure = part.error;\n failed = true;\n }\n yield part;\n }\n } catch (err) {\n // An intentional consumer cancel is a teardown, not a failed segment —\n // even though it reaches the turn as an abort (see the doc comment).\n if (!isConsumerCancel?.()) {\n failure = err;\n failed = true;\n }\n throw err;\n } finally {\n const settleEvent: SegmentSettleTransportEvent = {\n type: \"segment:settle\",\n segment,\n durationMs: performance.now() - startedAt,\n timestamp: Date.now(),\n };\n const chatId = segmentChat.chatId ?? this.#chatId;\n if (chatId !== undefined) settleEvent.chatId = chatId;\n // Absent when the segment failed before acquiring a stream — never\n // guessed (a predicted reader could name a connection that never\n // served this segment; see the field's doc comment).\n if (segmentChat.reader !== undefined) settleEvent.reader = segmentChat.reader;\n // An abort or timeout can also end the segment through generator\n // return() while suspended at a yield (e.g. the consumer cancels after\n // the abort with no pull pending) — return() runs this finally WITHOUT\n // the catch above ever seeing an exception. If the segment would\n // otherwise look like a teardown, classify by termination source:\n // - the turn's internal requestTimeoutMs having fired wins (it already\n // interrupted the server run, and it is invisible to both the\n // caller's signal and the consumer-cancel probe — issue #73);\n // - otherwise a caller abort the consumer did not initiate settles as\n // a failure with the abort reason.\n if (!failed && finishReason === undefined) {\n if (segmentChat.timeoutError) {\n failure = segmentChat.timeoutError;\n failed = true;\n } else if (options.abortSignal?.aborted && !isConsumerCancel?.()) {\n failure = options.abortSignal.reason;\n failed = true;\n }\n }\n if (failed) {\n settleEvent.error = {\n name: failure instanceof Error ? failure.name : \"Error\",\n message: failure instanceof Error ? failure.message : String(failure),\n };\n }\n if (finishReason !== undefined) {\n // finishReason.raw is the translator's terminal chat status. An error\n // settle keeps it too when the server run itself settled terminally\n // (`status: \"error\"` streamed alongside the error part).\n if (finishReason.raw !== undefined) settleEvent.status = finishReason.raw as ChatStatus;\n if (!failed) settleEvent.finishReason = finishReason.unified;\n } else if (segmentChat.terminalStatus !== undefined) {\n // The finish part never reached this wrapper — doGenerate() throws on\n // a streamed error part and closes the generator without another pull\n // — but the translator still observed the terminal status batched\n // behind the error in the same frame (issue #72).\n settleEvent.status = segmentChat.terminalStatus;\n }\n emit(settleEvent);\n }\n }\n\n async *#runTurnInner(\n options: LanguageModelV4CallOptions,\n /** Observability out-param: the segment's chat id, written once known. */\n segmentChat?: SegmentChatRef,\n ): AsyncGenerator<LanguageModelV4StreamPart, void, void> {\n // A single model instance owns one chatd session's mutable state, so it is\n // single-flight: reject overlapping turns rather than silently corrupting\n // chatId / lastSeenMessageId / submitted tool-call tracking.\n if (this.#inFlight) {\n throw new CoderAgentError(\n \"A generation is already in flight on this CoderAgent (single-flight). Use a separate CoderAgent instance for concurrent sessions.\",\n );\n }\n this.#inFlight = true;\n\n // Combine the caller's abort signal with an optional per-turn timeout into a\n // single signal (the platform composes and cleans these up for us). When\n // there's neither a caller signal nor a timeout, `signal` stays undefined and\n // there is no per-turn setup. Keep a reference to our *own* timeout signal so\n // a timeout stays distinguishable from a caller abort — even when the caller's\n // own signal is itself an `AbortSignal.timeout` (whose reason is a TimeoutError\n // too, so reason-sniffing alone would misclassify it).\n const externalSignal = options.abortSignal;\n const timeoutMs = this.#config.requestTimeoutMs;\n const timeoutSignal =\n timeoutMs !== undefined && timeoutMs > 0 ? AbortSignal.timeout(timeoutMs) : undefined;\n const sources = [externalSignal, timeoutSignal].filter(\n (s): s is AbortSignal => s !== undefined,\n );\n const signal: AbortSignal | undefined =\n sources.length > 0 ? AbortSignal.any(sources) : undefined;\n\n // Declared here so `finally` can read whether the turn settled; constructed\n // only once the turn's starting message id is known (see `turnCursor`).\n let translator: TurnTranslator | undefined;\n\n // Whether this call took ownership of the session stream (#acquireStream\n // ran). Until then, a stream retained by the PREDECESSOR segment's pause\n // has no owner watching it — a REST-phase failure below must close it\n // (see the outer finally) rather than leave it retained: the turn is dead\n // (and the run interrupted), so a caller retry attaching to its buffer\n // would read interrupt-era events and end before the resumed generation.\n let streamAcquired = false;\n\n // Set when a stream failure kills a chat that THIS call created: the\n // `finally` then drops the session (after interrupting the dead run) so an\n // automatic whole-call retry replays the turn on a fresh chat.\n let discardSession = false;\n\n // Interrupting the *server* run (not just closing the WebSocket) is what frees\n // the chat's workspace/resources. Fire it at most once — on abort/timeout, and\n // on teardown of an unsettled turn (see `finally`), which also covers stream\n // `cancel()` and premature close, neither of which aborts the signal. A chat\n // whose id we never received (createChat aborted mid-flight) can't be reached.\n let interruptSent = false;\n // The turn's own chat id, captured once known: the interrupt must reach\n // THIS turn's run even after a concurrent resetSession() cleared the\n // session id mid-segment (which also closes the attached stream and fails\n // the segment — whose teardown then still stops the orphaned server run).\n let turnChatId: string | undefined;\n // Whether the submission was QUEUED (chat busy — see #114), and its queue\n // entry, once known. Set before the translator exists, so the teardown\n // below can classify a REST-phase failure after the queueing response.\n let queuedTurn = false;\n let queuedEntry: ChatQueuedMessage | undefined;\n const interrupt = (): void => {\n const id = turnChatId ?? this.#chatId;\n if (interruptSent || !id) return;\n // Settled at a promotion boundary (issue #120): the chat's live run\n // belongs to the promoted submission, not to this turn — a late\n // abort/timeout racing the settle must not kill someone else's turn.\n // (Terminal-status settles don't need this: their chat is idle and an\n // interrupt is a harmless 409.)\n if (translator?.settledAtPromotionBoundary) return;\n interruptSent = true;\n // A QUEUED turn that never anchored has no server run of its own: the\n // chat's active run belongs to a concurrent/predecessor submission, and\n // interrupting it would kill someone else's work — and, worse, make\n // chatd promote this turn's queue entry into a run nobody is watching.\n // Withdraw the entry instead (best-effort, like the interrupt): the\n // turn is dead, so its prompt must not run unattended later. If the\n // entry is already gone (promoted during the race, or deleted\n // externally), there is no reliable ownership evidence for whatever is\n // running now — leave it alone rather than risk killing an innocent\n // run. Without an entry id (unexpected wire shape) do nothing, for the\n // same reason.\n if (queuedTurn && (translator === undefined || translator.awaitingQueuedSubmission)) {\n const entryId = queuedEntry?.id;\n if (entryId !== undefined) {\n void this.#config.client.deleteQueuedMessage(id, entryId).catch(() => {});\n }\n return;\n }\n void this.#config.client.interruptChat(id).catch(() => {});\n };\n signal?.addEventListener(\"abort\", interrupt, { once: true });\n\n // Map an abort of the combined signal to the right error. Our own\n // `timeoutSignal` having fired means a per-turn timeout; otherwise it's a\n // caller abort, re-thrown as the caller's reason (preserving AbortError so the\n // AI SDK still recognizes the cancellation).\n const timeoutError = (): CoderChatError =>\n new CoderChatError({\n message: `Coder Agent turn exceeded its ${timeoutMs}ms requestTimeoutMs budget.`,\n kind: \"timeout\",\n retryable: true,\n });\n const throwIfAborted = (): void => {\n if (timeoutSignal?.aborted) {\n throw timeoutError();\n }\n if (signal?.aborted) {\n throw (\n externalSignal?.reason ?? new DOMException(\"The operation was aborted.\", \"AbortError\")\n );\n }\n };\n\n try {\n const { prompt } = options;\n\n // chatd does not constrain output to a JSON schema server-side, so a\n // `responseFormat: json` request can't be honored. Warn rather than\n // silently mislead — schema-constrained output should go through the\n // provider (@coder/ai-sdk-provider) instead.\n const warnings: SharedV4Warning[] = [];\n if (options.responseFormat?.type === \"json\") {\n warnings.push({\n type: \"unsupported\",\n feature: \"responseFormat\",\n details:\n \"Coder Agents does not enforce a JSON schema server-side, so structured output is best-effort (not schema-constrained). For reliable structured output, use @coder/ai-sdk-provider (createCoder) with generateObject / Output.object.\",\n });\n }\n yield { type: \"stream-start\", warnings };\n\n const action = classifyTurnAction(prompt);\n if (action.kind === \"noop\") {\n throw new CoderAgentError(\n \"CoderAgent received a prompt with no user message or tool results to act on.\",\n );\n }\n\n let afterId: number | undefined;\n // Whether THIS call created the chat — the one case where an automatic\n // whole-call retry after a stream failure is safe (see the\n // CoderStreamError handling below).\n let turnCreatedChat = false;\n // Whether this call uploaded inline file attachments — an external\n // effect a whole-call retry would repeat, creating redundant file\n // records (pre-uploaded `fileId` references don't set this).\n let uploadedAttachment = false;\n // Whether the chat ended up bound to a workspace — configured OR\n // assigned server-side by the deployment (createChat's response says).\n // Either way its tools can have non-idempotent effects a replay would\n // repeat.\n let chatHasWorkspace = Boolean(this.#config.workspaceId);\n // Same for chatd-side MCP servers: configured OR auto-attached by the\n // deployment (createChat's response `mcp_server_ids` says — issue #58).\n let chatHasMcpServers = Boolean(this.#config.mcpServerIds?.length);\n\n // A fresh instance resuming an existing chat (config.chatId) has no\n // message cursor yet. Without one, a queued submission or a tool-result\n // resume falls back to afterId 0: the server replays the chat's FULL\n // history and this turn would absorb every earlier turn's content and\n // usage. Seed the cursor from the chat's newest message instead (the\n // messages endpoint pages newest-first; an empty chat has nothing to\n // replay, so 0 stays correct there). Skipped when the caller supplied\n // the cursor (config.lastSeenMessageId, validated at construction) —\n // that is the same value a previous instance's turn established, minus\n // this round-trip (#115).\n if (this.#chatId && this.#lastSeenMessageId === 0) {\n const { messages } = await this.#config.client.getMessages(\n this.#chatId,\n { limit: 1 },\n signal,\n );\n this.#lastSeenMessageId = messages[0]?.id ?? 0;\n }\n\n if (action.kind === \"new-turn\") {\n // Resolve the model config and upload any file parts concurrently — they\n // are independent round-trips. (Uploads run before the chat exists and\n // resolve file parts to `file` input parts referencing their uploaded ids.)\n const [modelConfigId, content] = await Promise.all([\n this.#resolveModelConfigId(signal),\n this.#buildContent(action.content, signal, () => {\n uploadedAttachment = true;\n }),\n ]);\n if (!this.#chatId) {\n const req: CreateChatRequest = {\n organization_id: this.#config.organizationId,\n content,\n client_type: \"api\",\n };\n const system = extractSystemPrompt(prompt);\n if (system) req.system_prompt = system;\n const tools = toolsToDynamicTools(options.tools);\n if (tools.length > 0) req.unsafe_dynamic_tools = tools;\n if (modelConfigId) req.model_config_id = modelConfigId;\n if (this.#config.reasoningEffort !== undefined) {\n req.reasoning_effort = this.#config.reasoningEffort;\n }\n if (this.#config.workspaceId) req.workspace_id = this.#config.workspaceId;\n if (this.#config.mcpServerIds?.length) req.mcp_server_ids = this.#config.mcpServerIds;\n if (this.#config.planMode) req.plan_mode = this.#config.planMode;\n const chat = await this.#config.client.createChat(req, signal);\n this.#chatId = chat.id;\n turnCreatedChat = true;\n if (chat.workspace_id) chatHasWorkspace = true;\n if (chat.mcp_server_ids?.length) chatHasMcpServers = true;\n afterId = this.#lastSeenMessageId > 0 ? this.#lastSeenMessageId : undefined;\n } else {\n const req: CreateChatMessageRequest = { content };\n if (modelConfigId) req.model_config_id = modelConfigId;\n if (this.#config.reasoningEffort !== undefined) {\n req.reasoning_effort = this.#config.reasoningEffort;\n }\n const resp = await this.#config.client.createChatMessage(this.#chatId, req, signal);\n if (resp.message) {\n afterId = resp.message.id;\n } else if (resp.queued) {\n // QUEUED submission (#114): the chat was busy, so the response\n // carries only a queue entry — no committed message id. The\n // stream window from the pre-turn cursor up to the entry's\n // materialization belongs to the chat's CURRENT run, so the\n // translator withholds attribution until this turn's own\n // user-message snapshot is observed (`queuedSubmission` below).\n // The stream still dials from the pre-turn cursor: the\n // materialized id is unknown here, and a later cursor would skip\n // the very snapshot the gate anchors on.\n afterId = this.#lastSeenMessageId;\n queuedTurn = true;\n queuedEntry = resp.queued_message;\n } else {\n // Out-of-contract response (neither a committed message nor the\n // queued flag): keep the historical pre-turn-cursor fallback\n // rather than gating on a signal the wire never sent.\n afterId = this.#lastSeenMessageId;\n }\n }\n } else {\n // resume\n if (!this.#chatId)\n throw new CoderChatError({ message: \"cannot submit tool results before a chat exists\" });\n const fresh = action.toolResults.filter(\n (r) => !this.#submittedToolCallIds.has(r.tool_call_id),\n );\n if (fresh.length > 0) {\n await this.#config.client.submitToolResults(this.#chatId, { results: fresh }, signal);\n for (const r of fresh) this.#submittedToolCallIds.add(r.tool_call_id);\n }\n afterId = this.#lastSeenMessageId;\n }\n\n const chatId = this.#chatId as string;\n turnChatId = chatId;\n // The cleanup target outlives the session: a stream failure below can\n // discard the session (resetSession), but the chat persists server-side\n // and archive()/interrupt() must still be able to name it.\n this.#lastKnownChatId = chatId;\n if (segmentChat) segmentChat.chatId = chatId;\n // Only messages past the turn's starting cursor belong to this turn —\n // resuming a chat replays earlier turns' messages (usage included), and\n // a mid-turn history reset re-sends them again. Constructing the\n // translator with the cursor makes it impossible to ingest before the\n // boundary is known.\n const dynamicNames = dynamicToolNames(options.tools);\n const translatorOptions: TurnTranslatorOptions = {\n dynamicToolNames: dynamicNames,\n turnCursor: afterId ?? 0,\n // The retained stream redials with the TURN's original cursor, so a\n // reconnect can replay an earlier segment's `action_required`\n // snapshot; calls this session already answered must not run again.\n submittedToolCallIds: this.#submittedToolCallIds,\n };\n if (queuedTurn) {\n // For a queued submission the cursor above is only the PRE-turn\n // boundary: the translator additionally withholds attribution until\n // the queue entry materializes as this turn's own user message and\n // then advances its effective cursor to that message's id (#114).\n const queuedSubmission: NonNullable<TurnTranslatorOptions[\"queuedSubmission\"]> = {};\n if (queuedEntry?.id !== undefined) queuedSubmission.id = queuedEntry.id;\n if (queuedEntry?.content !== undefined) queuedSubmission.content = queuedEntry.content;\n translatorOptions.queuedSubmission = queuedSubmission;\n }\n translator = new TurnTranslator(translatorOptions);\n // chatd emits the `requires_action` status BEFORE the `action_required`\n // event that carries the pending tool calls, so for that status we keep\n // reading until the client tool calls have actually been emitted (bounded\n // by a safety counter, since the stream is a live subscription). The\n // event can also never arrive at all — consumed by a previous connection\n // across a reconnect race, or a server-side hiccup between the status\n // flip and the history-derived event — and on a quiet socket the read\n // would then block until requestTimeoutMs (minutes) and fail a turn\n // whose tool calls have been sitting fully committed in chat history\n // the whole time. So once `requires_action` settles without a client\n // tool call, a grace timer races the pending read; if it expires, the\n // tool calls are recovered from history over REST instead\n // (#recoverRequiresAction) — one shot per segment, never on the fast\n // path, still bounded by requestTimeoutMs/abort.\n let sinceRequiresAction = 0;\n let grace: ReturnType<typeof startGraceTimer> | undefined;\n // The in-flight recovery fetch. It races the pending stream read rather\n // than being awaited serially: a slow or hanging REST request must not\n // block a late stream event from completing the segment (with\n // requestTimeoutMs unset, a serial await could hang the turn on the\n // fetch indefinitely — the very failure mode this fallback removes).\n // Failures resolve to [] (best-effort): the segment then keeps waiting\n // on the stream, bounded by requestTimeoutMs/abort as before.\n let recovery: Promise<ChatStreamEvent[]> | undefined;\n let recoveryAbort: AbortController | undefined;\n let recoveryAttempted = false;\n // One stream per TURN, not per segment: a resume segment re-attaches to\n // the stream its predecessor left paused at `requires_action`; anything\n // else dials fresh (see #acquireStream). The stream is bound to its OWN\n // lifetime signal — the segment signal instead races reads below, so an\n // abort/timeout detaches this segment without killing the shared socket.\n const stream = this.#acquireStream(action.kind === \"resume\", chatId, afterId);\n streamAcquired = true;\n // A resume segment re-attaches to the retained stream, so this is the\n // ORIGINAL dialing reader's id — segments of one turn share it.\n if (segmentChat) segmentChat.reader = stream.reader;\n let onSegmentAbort: (() => void) | undefined;\n const segmentAborted: Promise<typeof SEGMENT_ABORTED> | undefined = signal\n ? new Promise((resolve) => {\n onSegmentAbort = () => resolve(SEGMENT_ABORTED);\n if (signal.aborted) onSegmentAbort();\n else signal.addEventListener(\"abort\", onSegmentAbort, { once: true });\n })\n : undefined;\n const raceSegment = <T>(p: Promise<T>): Promise<T | typeof SEGMENT_ABORTED> =>\n segmentAborted ? Promise.race([p, segmentAborted]) : p;\n // Whether the segment settled at a healthy client-tool pause — the one\n // exit that keeps the stream for the next segment (see the finally).\n let retainStream = false;\n try {\n for (;;) {\n // The stream memoizes its pending read: this either starts a fresh\n // read or adopts one an earlier iteration (or segment) raced out —\n // an event resolved while nobody was consuming is delivered here,\n // never dropped.\n const next = stream.read();\n const awaitingActionRequired =\n translator.terminalStatus === \"requires_action\" && !translator.clientToolCallSeen;\n if (!awaitingActionRequired && (grace !== undefined || recoveryAttempted)) {\n // The requires_action that armed this wait state was stale — a\n // replayed connect-time snapshot from a redial, cleared by a later\n // non-terminal transition (the chat is generating again). Re-arm\n // the one-shot fallback so a REAL pause later in this segment\n // gets its own grace timer and recovery fetch.\n grace?.cancel();\n grace = undefined;\n recoveryAbort?.abort();\n recoveryAbort = undefined;\n recovery = undefined;\n recoveryAttempted = false;\n sinceRequiresAction = 0;\n }\n let raced:\n | IteratorResult<ChatStreamEvent, void>\n | ChatStreamEvent[]\n | typeof GRACE_EXPIRED\n | typeof SEGMENT_ABORTED;\n if (awaitingActionRequired && !recoveryAttempted) {\n // One timer for the whole wait (not per event), so a server that\n // keeps sending unrelated events cannot postpone the fallback.\n grace ??= startGraceTimer(ACTION_REQUIRED_GRACE_MS);\n raced = await raceSegment(Promise.race([next, grace.expired]));\n if (raced === GRACE_EXPIRED) {\n recoveryAttempted = true;\n // Defensive: if the grace timer beat a concurrent abort,\n // classify before fetching.\n throwIfAborted();\n // A recovery-specific controller (chained to the turn signal)\n // so the losing fetch is canceled when the stream wins or the\n // turn tears down — a hung request must not outlive the\n // segment and leak its connection.\n recoveryAbort = new AbortController();\n recovery = this.#recoverRequiresAction(\n chatId,\n dynamicNames,\n // The turn's EFFECTIVE cursor: for a queued submission it has\n // advanced past the materialized user message (a pause can\n // only arm after the gate anchored), so the derivation never\n // sees the concurrent predecessor run's tail (#114).\n translator.turnCursor,\n signal ? AbortSignal.any([signal, recoveryAbort.signal]) : recoveryAbort.signal,\n ).catch(() => []);\n continue;\n }\n } else if (awaitingActionRequired && recovery) {\n raced = await raceSegment(Promise.race([next, recovery]));\n if (Array.isArray(raced)) {\n recovery = undefined;\n // An aborted fetch resolves [] through the catch above —\n // classify instead of silently waiting out the timeout.\n throwIfAborted();\n for (const ev of raced) for (const part of translator.ingest(ev)) yield part;\n // The losing read stays memoized on the stream — the resume\n // segment adopts it (read() is rejection-tagged, so a late\n // failure while paused cannot surface as unhandled).\n if (translator.clientToolCallSeen) break;\n continue; // nothing recovered — keep waiting on the stream\n }\n } else {\n raced = await raceSegment(next);\n }\n if (raced === SEGMENT_ABORTED) {\n throwIfAborted();\n // Unreachable: the sentinel only resolves once `signal` aborted.\n throw new CoderAgentError(\"segment abort sentinel resolved without an abort\");\n }\n stream.consumed(next);\n if (raced.done) break;\n const parts = translator.ingest(raced.value);\n // A queued submission whose wait can never complete — the chat\n // settled in error with the entry still queued (chatd does not\n // auto-promote the queue after an error), or the entry left the\n // queue without materializing as this turn's user message. Thrown\n // rather than streamed as an error part: the failure is this\n // client's WAIT, not a settle of this turn's run (which never\n // started), so no terminal status is recorded — which also routes\n // teardown to withdraw the stuck entry (see interrupt()).\n const deadEnd = translator.queuedSubmissionDeadEnd;\n if (deadEnd) throw new CoderChatError(deadEnd);\n if (raced.value.type === \"error\" && translator.error && !translator.terminalStatus) {\n // chatd batches the terminal `status: \"error\"` right behind the\n // `error` event in the same WS frame, but a doGenerate() consumer\n // throws on the error part yielded below and closes this\n // generator without another pull — the settle event would then\n // omit a terminal status that was already decoded (issue #72).\n // Peek ONE more event, bounded by a 0ms timer: an event buffered\n // from the same frame resolves in microtasks (the reader yields\n // batched events without awaiting in between), which always beats\n // the macrotask timer, while a quiet socket costs a single timer\n // tick on an already-failing turn. A peek that loses (or rejects)\n // stays memoized on the stream — the next iteration adopts it,\n // so no event or failure is ever dropped.\n const peek = stream.read();\n const bound = startGraceTimer(0);\n const peeked = await raceSegment(Promise.race([peek, bound.expired])).catch(\n (): typeof GRACE_EXPIRED => GRACE_EXPIRED,\n );\n bound.cancel();\n if (peeked !== GRACE_EXPIRED && peeked !== SEGMENT_ABORTED && !peeked.done) {\n stream.consumed(peek);\n parts.push(...translator.ingest(peeked.value));\n }\n }\n for (const part of parts) yield part;\n // Promotion settle boundary (issue #120): a user-role message past\n // the turn's cursor means the run is over on the wire even though\n // no terminal status was (or will be) emitted for it — chatd\n // started the chat's next run atomically with the finish. Settle\n // NOW: everything after this event is the successor run's.\n if (translator.settledAtPromotionBoundary) break;\n const status = translator.terminalStatus;\n if (status) {\n if (status !== \"requires_action\") break;\n if (translator.clientToolCallSeen) break;\n if (++sinceRequiresAction > 200) break;\n }\n }\n // A healthy pause — the turn continues with a tool-result resume, so\n // the stream is retained (the finally pauses it). Every other exit\n // (terminal settle, error settle, premature end) closes it. Never\n // retain past an error settle (the SDK ends the turn there), and\n // never past an abort — one can land while the generator is suspended\n // yielding the settle's tool-call parts, after which this segment\n // still rejects (post-loop throwIfAborted): its pause was never\n // delivered, so a retry must redial fresh and recover the pause from\n // the connect-time snapshot instead of attaching to a socket whose\n // already-consumed requires_action events will not be re-sent.\n retainStream =\n !signal?.aborted &&\n translator.terminalStatus === \"requires_action\" &&\n translator.clientToolCallSeen &&\n !translator.error;\n } catch (err) {\n // Abort surfaces here only if the reader threw instead of closing cleanly;\n // prefer the abort/timeout classification.\n throwIfAborted();\n // The reader redials dropped connections internally (with `after_id`\n // catch-up), so what escapes it is terminal: a CoderApiError (4xx\n // upgrade rejection, re-thrown as-is), a CoderStreamError (redial\n // budget exhausted, or undedupable old-server deltas; handled below),\n // or a bare CoderAgentError (unparseable frame). Surface the latter as\n // a retryable stream failure so a caller's `CoderChatError &&\n // retryable` retry path catches it instead of seeing a bare,\n // non-retryable error.\n //\n // An AI-SDK `maxRetries` retry re-invokes this model with the SAME\n // prompt, and `#runTurn` would submit it AGAIN as a new user turn.\n // That is only safe when BOTH hold: this very call created the chat\n // (discarding the dead session — its run is being interrupted anyway —\n // lets the retry replay the whole turn on a FRESH chat), and the chat\n // has no server-side effectful tooling (a bound workspace or MCP\n // servers), whose tools may have executed before the stream died and\n // would run AGAIN on a replay; the async interrupt cannot undo, or\n // even reliably outrace, such external effects. Client tools are the\n // SDK's own tool loop, which restarts cleanly with the fresh chat. A\n // chat with prior state — resumed sessions, later turns, tool-result\n // segments — would be corrupted by any re-submission. In the unsafe\n // cases the error is downgraded to non-retryable and the caller owns\n // the retry/resume decision.\n if (err instanceof CoderStreamError) {\n // The reader stamps the chat id it streamed, but a custom client's\n // stream source may not — stamp the turn's own id (they name the\n // same chat) so BOTH rethrow paths below keep the documented\n // guarantee that the error names the failed turn's chat. The field\n // is publicly readonly; the model layer owns the error en route.\n if (err.chatId === undefined && turnChatId !== undefined) {\n (err as { chatId: string | undefined }).chatId = turnChatId;\n }\n if (turnCreatedChat) {\n // Nothing worth keeping: the chat's only content is this failed,\n // interrupted turn, and a later manual generate() on this instance\n // must not attach to it either. Record it as stranded FIRST: an\n // automatic retry's fresh chat will supersede #lastKnownChatId,\n // and without the ledger every attempt before the last would leak\n // unnamed (only the final attempt's error reaches the caller).\n if (turnChatId) this.#strandedChatIds.add(turnChatId);\n discardSession = true;\n }\n const effectful = chatHasWorkspace || chatHasMcpServers || uploadedAttachment;\n if (turnCreatedChat && !effectful) throw err;\n throw new CoderStreamError({\n message: `${err.message}; automatic retry is disabled because ${\n turnCreatedChat\n ? \"the turn has server-side effects a replay would repeat (workspace/MCP tools that may have executed, or freshly uploaded attachments)\"\n : \"the chat has prior state (a retry would resubmit this turn's prompt as a new user turn)\"\n }`,\n url: err.url,\n cause: err,\n isRetryable: false,\n chatId: err.chatId,\n });\n }\n if (\n err instanceof CoderAgentError &&\n !(err instanceof CoderApiError) &&\n !(err instanceof CoderChatError)\n ) {\n throw new CoderChatError({\n message: `Coder chat stream failed mid-turn: ${err.message}`,\n kind: \"stream_closed\",\n retryable: true,\n });\n }\n throw err;\n } finally {\n grace?.cancel();\n // Cancel a still-pending (losing) recovery fetch: the segment's exit\n // must not leave its HTTP request alive. (Settled/consumed recovery\n // makes this a no-op; the fetch rejection resolves to [] above.)\n recoveryAbort?.abort();\n if (signal && onSegmentAbort) signal.removeEventListener(\"abort\", onSegmentAbort);\n // The stream's fate, by exit kind: a healthy client-tool pause\n // retains it (paused) for the resume segment, with a read outstanding\n // so a drop during tool execution redials in the background; every\n // other exit — terminal settle, error settle, fatal stream error,\n // premature end, consumer teardown mid-loop, abort/timeout — closes\n // it. An aborted segment's stream could never be reused anyway (its\n // half-consumed in-progress episode cannot be re-read), so leaving it\n // open would only leak the socket until session teardown. Note the\n // close is still a policy decision made HERE, not signal-chaining:\n // the segment's signal never reaches the shared stream, which is what\n // lets the healthy pause outlive its segment.\n if (retainStream && !signal?.aborted && !stream.closed) {\n stream.pause();\n } else {\n await this.#closeStream(stream);\n }\n }\n\n // An abort can slip past the loop (e.g. it settled a race the segment\n // never awaited again); classify before treating the end as normal.\n throwIfAborted();\n\n // No terminal status: the stream ended before the turn settled — with\n // the reader redialing drops internally, a clean `done` means the\n // session stream was closed underneath this segment (resetSession or\n // dispose mid-turn). If an `error` event arrived (without a trailing\n // `status: error`), fall through to finish() so the real error surfaces\n // (unified:\"error\" + the error part), consistent with the\n // `status: error` path; otherwise surface the premature close rather\n // than a clean (truncated) `stop`. A promotion-boundary settle is a\n // settled turn despite carrying no terminal status (issue #120) — the\n // server never emits one for a run that finished into a promotion.\n if (\n !translator.terminalStatus &&\n !translator.error &&\n !translator.settledAtPromotionBoundary\n ) {\n throw new CoderChatError({\n message:\n \"Coder chat stream ended before the turn settled (connection closed or the server ended the stream without a terminal status).\",\n kind: \"stream_closed\",\n retryable: true,\n });\n }\n\n for (const part of translator.finish()) yield part;\n } catch (err) {\n // A timeout/caller abort during the REST phase (createChat / message /\n // tool-results / model resolution) rejects the fetch before the stream loop;\n // reclassify so the documented retryable timeout/abort contract still holds.\n throwIfAborted();\n throw err;\n } finally {\n // Advance the cursor on every exit (success, abort, error) so resuming the\n // same chat doesn't re-read messages already streamed this turn.\n if (translator && translator.maxMessageId > this.#lastSeenMessageId)\n this.#lastSeenMessageId = translator.maxMessageId;\n // Settle-time observability out-params (see SegmentChatRef): the\n // segment wrapper's finally runs after this one and reads them for\n // classification on generator-return exits.\n if (segmentChat) {\n if (translator?.terminalStatus !== undefined) {\n segmentChat.terminalStatus = translator.terminalStatus;\n }\n if (timeoutSignal?.aborted) segmentChat.timeoutError = timeoutError();\n }\n signal?.removeEventListener(\"abort\", interrupt);\n // Teardown of an unsettled turn — stream cancel(), premature close, or an\n // abort the listener didn't cover — interrupt the server so it stops.\n // (A turn that failed before streaming has no translator and never\n // settled, so it interrupts too.) A transient transport drop never gets\n // here: the reader redials internally, so an unsettled exit means abort,\n // timeout, a terminal stream failure (4xx / redial budget exhausted /\n // unparseable frame), or a REST-phase error — all cases where the healthy\n // server run must not keep burning tokens on an audience of zero.\n // A promotion-boundary settle carries no terminal status but IS settled\n // (issue #120) — and the chat's live run now belongs to the promoted\n // submission, so interrupting here would kill someone else's turn.\n if (!translator?.terminalStatus && !translator?.settledAtPromotionBoundary) interrupt();\n // A failure before the stream section ran (REST phase: tool-result\n // submission, uploads, chat creation, cursor seeding) leaves the\n // predecessor segment's paused stream ownerless while its turn is now\n // dead — and the interrupt above pushes interrupt-era events into its\n // buffer. Close it: an abandoned call must not leak the socket, and a\n // caller retry must dial fresh instead of attaching to that buffer.\n if (!streamAcquired && this.#retainedStream) {\n const stale = this.#retainedStream;\n this.#retainedStream = undefined;\n void stale.close();\n }\n // Ordered after the interrupt (which needs the chat id) and after the\n // cursor advance above (which the reset zeroes out again).\n if (discardSession) this.resetSession();\n this.#inFlight = false;\n }\n }\n\n async doStream(options: LanguageModelV4CallOptions): Promise<LanguageModelV4StreamResult> {\n // A consumer can tear the stream down via ReadableStream.cancel() without\n // aborting options.abortSignal. Route cancel through an abort so the turn\n // interrupts the server run and the blocked stream reader unblocks — a bare\n // gen.return() would deadlock on a pending socket read and never reach the\n // interrupt, leaking the workspace.\n const cancelController = new AbortController();\n const abortSignal = options.abortSignal\n ? AbortSignal.any([options.abortSignal, cancelController.signal])\n : cancelController.signal;\n // Observability classification: a segment counts as consumer-cancelled\n // only when cancel() INITIATED the termination. A caller abort that fired\n // first is a failure settle even if the consumer also cancels its reader\n // before the rejection propagates — so record which came first, rather\n // than whether cancellation ever happened.\n let consumerCancelled = false;\n const gen = this.#runTurn({ ...options, abortSignal }, () => consumerCancelled);\n const stream = new ReadableStream<LanguageModelV4StreamPart>({\n async pull(controller) {\n try {\n const { value, done } = await gen.next();\n if (done) controller.close();\n else controller.enqueue(value);\n } catch (err) {\n // A consumer-initiated cancel aborts the turn (to interrupt the server\n // and unblock the reader); that surfaces here as the turn's AbortError,\n // but it's an intentional teardown, so end the stream cleanly rather\n // than erroring it. A caller's own abortSignal still errors as usual.\n if (cancelController.signal.aborted) controller.close();\n else controller.error(err);\n }\n },\n async cancel() {\n // Only a cancel that beat any caller abort is a teardown (see above).\n consumerCancelled = !options.abortSignal?.aborted;\n cancelController.abort();\n await gen.return().catch(() => {});\n },\n });\n return { stream };\n }\n\n async doGenerate(options: LanguageModelV4CallOptions): Promise<LanguageModelV4GenerateResult> {\n const content: LanguageModelV4Content[] = [];\n const textBuf = new Map<string, string>();\n const reasoningBuf = new Map<string, string>();\n let usage: LanguageModelV4Usage = EMPTY_USAGE;\n let finishReason: LanguageModelV4GenerateResult[\"finishReason\"] = {\n unified: \"stop\",\n raw: undefined,\n };\n let providerMetadata: LanguageModelV4GenerateResult[\"providerMetadata\"];\n const warnings: LanguageModelV4GenerateResult[\"warnings\"] = [];\n\n for await (const part of this.#runTurn(options)) {\n switch (part.type) {\n case \"stream-start\":\n warnings.push(...part.warnings);\n break;\n case \"text-start\":\n textBuf.set(part.id, \"\");\n break;\n case \"text-delta\":\n textBuf.set(part.id, (textBuf.get(part.id) ?? \"\") + part.delta);\n break;\n case \"text-end\": {\n const t = textBuf.get(part.id) ?? \"\";\n if (t.length > 0) content.push({ type: \"text\", text: t });\n textBuf.delete(part.id);\n break;\n }\n case \"reasoning-start\":\n reasoningBuf.set(part.id, \"\");\n break;\n case \"reasoning-delta\":\n reasoningBuf.set(part.id, (reasoningBuf.get(part.id) ?? \"\") + part.delta);\n break;\n case \"reasoning-end\": {\n const t = reasoningBuf.get(part.id) ?? \"\";\n if (t.length > 0) content.push({ type: \"reasoning\", text: t });\n reasoningBuf.delete(part.id);\n break;\n }\n case \"tool-call\":\n case \"tool-result\":\n case \"source\":\n case \"file\":\n content.push(part);\n break;\n case \"finish\":\n usage = part.usage;\n finishReason = part.finishReason;\n providerMetadata = part.providerMetadata;\n break;\n case \"error\":\n throw part.error instanceof Error\n ? part.error\n : new CoderChatError({ message: String(part.error) });\n default:\n break;\n }\n }\n\n return {\n content,\n finishReason,\n usage,\n warnings,\n ...(providerMetadata ? { providerMetadata } : {}),\n };\n }\n}\n","import type { DynamicToolUIPart, UIMessage } from \"ai\";\nimport type { ChatMessage, ChatMessagePart } from \"../coder/types.js\";\n\n/** Options for {@link chatMessagesToUIMessages}. */\nexport interface ChatMessagesToUIMessagesOptions {\n /**\n * Resolves a persisted `file` part to a URL for its `FileUIPart`. Persisted\n * file parts carry only a `file_id` (no bytes, and usually no `url`), so\n * without a URL there is nothing for the UI to render and the part is\n * skipped. Download the bytes with `CoderChatClient.getChatFile(fileId)`\n * and return a data:/object/proxy URL here to keep attachments visible.\n * Returning `undefined` falls back to the part's own `url`, if any.\n */\n fileUrl?: (part: ChatMessagePart) => string | undefined;\n}\n\nfunction toolErrorText(result: unknown): string {\n if (typeof result === \"string\") return result;\n if (result === undefined || result === null) return \"\";\n return JSON.stringify(result) ?? \"\";\n}\n\nfunction toDynamicToolPart(\n call: ChatMessagePart,\n result: ChatMessagePart | undefined,\n): DynamicToolUIPart | undefined {\n const toolCallId = call.tool_call_id;\n const toolName = call.tool_name;\n if (!toolCallId || !toolName) return undefined;\n const providerExecuted = call.provider_executed ?? result?.provider_executed;\n const base = {\n type: \"dynamic-tool\" as const,\n toolName,\n toolCallId,\n ...(providerExecuted !== undefined ? { providerExecuted } : {}),\n };\n if (!result) return { ...base, state: \"input-available\", input: call.args };\n if (result.is_error) {\n return {\n ...base,\n state: \"output-error\",\n input: call.args,\n errorText: toolErrorText(result.result),\n };\n }\n return { ...base, state: \"output-available\", input: call.args, output: result.result };\n}\n\nfunction toUIParts(\n content: ChatMessagePart[],\n resultsByCallId: ReadonlyMap<string, ChatMessagePart>,\n opts: ChatMessagesToUIMessagesOptions | undefined,\n): UIMessage[\"parts\"] {\n const parts: UIMessage[\"parts\"] = [];\n for (const part of content) {\n switch (part.type) {\n case \"text\":\n parts.push({ type: \"text\", text: part.text ?? \"\", state: \"done\" });\n break;\n case \"reasoning\":\n parts.push({ type: \"reasoning\", text: part.text ?? \"\", state: \"done\" });\n break;\n case \"tool-call\": {\n const result = part.tool_call_id ? resultsByCallId.get(part.tool_call_id) : undefined;\n const ui = toDynamicToolPart(part, result);\n if (ui) parts.push(ui);\n break;\n }\n case \"source\":\n // `sourceId` is required by the AI SDK; fall back to the URL when the\n // wire part has no `source_id`. Without a URL there is nothing to\n // link, so the part is skipped.\n if (part.url) {\n parts.push({\n type: \"source-url\",\n sourceId: part.source_id || part.url,\n url: part.url,\n ...(part.title ? { title: part.title } : {}),\n });\n }\n break;\n case \"file\": {\n const url = opts?.fileUrl?.(part) ?? part.url;\n if (url) {\n parts.push({\n type: \"file\",\n mediaType: part.media_type ?? \"application/octet-stream\",\n ...(part.name ? { filename: part.name } : {}),\n url,\n });\n }\n break;\n }\n case \"tool-result\":\n // Folded into the originating `dynamic-tool` part above.\n break;\n default:\n // file-reference / context-file / skill / future kinds: no UI\n // equivalent; skip so history written by newer Coder servers\n // degrades gracefully.\n break;\n }\n }\n return parts;\n}\n\n/**\n * Converts persisted chat history (`CoderChatClient.getMessages`) into AI SDK\n * v6 `UIMessage`s, e.g. to rehydrate `useChat({ messages })` for an existing\n * chat. Pure and side-effect free.\n *\n * The mapping mirrors what a live-streamed transcript of the same turn looks\n * like, with one caveat around client-tool typing (below):\n * - One `ChatMessage` becomes one `UIMessage` (ids stringified), sorted\n * chronologically by id regardless of input order — the messages endpoint\n * pages newest-first by default, while `useChat` needs oldest-first. The\n * exception is `role: \"tool\"` messages: their `tool-result` parts are\n * folded into the originating assistant `dynamic-tool` part and the message\n * itself is dropped.\n * - `text`/`reasoning` parts map to their UI counterparts with\n * `state: \"done\"` (history is complete, never streaming).\n * - Every `tool-call` part becomes a `dynamic-tool` part, in state\n * `\"output-available\"` once a result exists (`\"output-error\"` when it is\n * marked `is_error`), or `\"input-available\"` while no result has been\n * persisted yet. For server-executed tools this matches the live stream\n * (they stream with `dynamic: true`); client (`ToolSet`) tools stream live\n * as statically-typed `tool-{name}` parts, but persisted history does not\n * record which names were client tools, so they rehydrate as `dynamic-tool`\n * too. Render tools by name — ai's `isToolOrDynamicToolUIPart` +\n * `getToolOrDynamicToolName` — rather than exact `part.type` matches and\n * both shapes look identical.\n * - `source` parts become `source-url` parts; skipped when the wire part has\n * no `url`.\n * - `file` parts become `file` UI parts only when a URL is available (see\n * {@link ChatMessagesToUIMessagesOptions.fileUrl}): persisted file parts\n * carry only a `file_id`, and `FileUIPart` requires a `url`. Parts without\n * one are skipped.\n * - `file-reference` / `context-file` / `skill` / unknown part kinds have no\n * UI equivalent and are skipped silently (forward compatible).\n */\nexport function chatMessagesToUIMessages(\n messages: ChatMessage[],\n opts?: ChatMessagesToUIMessagesOptions,\n): UIMessage[] {\n // Normalize to chronological order (ids are per-chat sequential). The\n // messages endpoint pages newest-first by default (only `after_id` queries\n // return ascending), and `useChat` expects oldest-first — accepting any\n // input order makes both pagination shapes safe to pass straight in.\n const ordered = [...messages].sort((a, b) => a.id - b.id);\n\n // Pass 1: index tool results by call id. chatd persists results as separate\n // role:\"tool\" messages; scan every message so results inlined on assistant\n // snapshots fold identically.\n const resultsByCallId = new Map<string, ChatMessagePart>();\n for (const message of ordered) {\n for (const part of message.content ?? []) {\n if (part.type !== \"tool-result\" || !part.tool_call_id) continue;\n if (!resultsByCallId.has(part.tool_call_id)) resultsByCallId.set(part.tool_call_id, part);\n }\n }\n\n // Pass 2: one UIMessage per ChatMessage, chronological. role:\"tool\" messages\n // were folded above and produce none.\n const out: UIMessage[] = [];\n for (const message of ordered) {\n if (message.role === \"tool\") continue;\n out.push({\n id: String(message.id),\n role: message.role,\n parts: toUIParts(message.content ?? [], resultsByCallId, opts),\n });\n }\n return out;\n}\n"],"mappings":"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","names":["sleep"]}
1
+ {"version":3,"sources":["../src/agent/coder-agent.ts","../src/errors.ts","../src/ai-version.ts","../src/files.ts","../src/coder/types.ts","../src/coder/ws.ts","../src/transport-events.ts","../src/coder/client.ts","../src/coder/workspaces.ts","../src/model/prompt.ts","../src/model/session-stream.ts","../src/model/translate.ts","../src/model/language-model.ts","../src/model/history.ts"],"sourcesContent":["import {\n type Agent,\n type FilePart,\n isStepCount,\n type StopCondition,\n type SystemModelMessage,\n type ToolChoice,\n ToolLoopAgent,\n type ToolLoopAgentSettings,\n type ToolSet,\n} from \"ai\";\nimport { assertSupportedAiVersion } from \"../ai-version.js\";\nimport {\n type ChatFileInput,\n CoderChatClient,\n type CoderChatClientOptions,\n type UploadedChatFile,\n} from \"../coder/client.js\";\nimport type { ChatModelConfig, ReasoningEffort } from \"../coder/types.js\";\nimport {\n type PreviewShareLevel,\n resolveWorkspacePreview,\n shareWorkspacePreview,\n type WorkspaceApiConnection,\n} from \"../coder/workspaces.js\";\nimport { CoderAgentError, CoderApiError } from \"../errors.js\";\nimport type { FileContent } from \"../files.js\";\nimport type { TransportEventHandler } from \"../transport-events.js\";\nimport { CoderLanguageModel } from \"../model/language-model.js\";\nimport { CODER_PROVIDER_OPTIONS } from \"../model/prompt.js\";\nimport type { WorkspaceFileStore, WorkspacePlacement } from \"../workspace-files.js\";\n\ntype InnerAgent<TOOLS extends ToolSet> = ToolLoopAgent<never, TOOLS>;\n\n/** A handle to a file uploaded as a chat attachment (see {@link CoderAgent.attach}). */\nexport interface ChatAttachment extends UploadedChatFile {\n /**\n * A native AI SDK `file` part that references this upload by id. Drop it into\n * a user message's `content` to reuse the file across turns without\n * re-uploading — the agent recognizes the id and skips the upload.\n *\n * The reference travels in `providerOptions.coder.fileId`; if you persist the\n * part through a store that strips `providerOptions`, the reference is lost\n * (re-`attach()` instead of persisting the handle).\n */\n toFilePart(): FilePart;\n}\n\nfunction makeChatAttachment(file: UploadedChatFile): ChatAttachment {\n return {\n ...file,\n toFilePart: () => ({\n type: \"file\",\n // Bytes are unused: the reference travels in providerOptions.coder.fileId.\n data: \"\",\n mediaType: file.mediaType,\n ...(file.name ? { filename: file.name } : {}),\n providerOptions: { [CODER_PROVIDER_OPTIONS]: { fileId: file.id } },\n }),\n };\n}\n\n/** Default step ceiling. Each \"step\" is one client-tool round-trip; chatd caps\n * its own server-side loop at 1200 steps independently. */\nconst DEFAULT_STOP = isStepCount(64);\n\n/**\n * Bounded-cleanup defaults. An interrupted chat keeps winding down server-side\n * for a few seconds, during which archiving 409s — {@link CoderAgent.archive}\n * and `[Symbol.asyncDispose]` retry those 409s every ~`SETTLE_RETRY_DELAY_MS`\n * for up to ~`SETTLE_DEADLINE_MS` overall before giving up.\n */\nconst SETTLE_DEADLINE_MS = 15_000;\nconst SETTLE_RETRY_DELAY_MS = 1_000;\n\n/**\n * Clamps a user-supplied delay to what `AbortSignal.timeout`/`setTimeout`\n * accept: a non-negative integer no larger than the 32-bit timer cap.\n * Anything else (negative, fractional beyond flooring, `NaN`, `Infinity`)\n * falls back to the default rather than throwing at dispose time.\n */\nfunction sanitizeDelayMs(value: number | undefined, fallback: number): number {\n const MAX_TIMER_MS = 2 ** 31 - 1;\n if (value === undefined || Number.isNaN(value)) return fallback;\n if (value === Number.POSITIVE_INFINITY) return MAX_TIMER_MS;\n if (!Number.isFinite(value) || value < 0) return fallback;\n return Math.min(Math.floor(value), MAX_TIMER_MS);\n}\n\n/** Abort-aware sleep: resolves after `ms`, or rejects with the signal's reason. */\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(signal.reason);\n return;\n }\n const onAbort = () => {\n clearTimeout(timer);\n reject(signal?.reason);\n };\n const timer = setTimeout(() => {\n signal?.removeEventListener(\"abort\", onAbort);\n resolve();\n }, ms);\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n });\n}\n\n/**\n * Archive a chat, retrying while it is still settling. Interrupting a run does\n * not stop it instantly — chatd winds it down for a few more seconds and 409s\n * archive attempts in that window. Only those 409s are retried (other failures\n * and caller aborts rethrow immediately); once `deadlineMs` has elapsed the\n * last 409 is rethrown.\n */\nasync function archiveWhenSettled(\n client: CoderChatClient,\n chatId: string,\n opts: { deadlineMs: number; retryDelayMs: number; signal?: AbortSignal },\n): Promise<void> {\n // The deadline decides when to stop retrying; the timeout signal additionally\n // unsticks an archive request that hangs (both track the same clock).\n const timeout = AbortSignal.timeout(opts.deadlineMs);\n const signal = opts.signal ? AbortSignal.any([opts.signal, timeout]) : timeout;\n const deadline = Date.now() + opts.deadlineMs;\n for (;;) {\n try {\n await client.archiveChat(chatId, signal);\n return;\n } catch (err) {\n if (!(err instanceof CoderApiError) || err.status !== 409) throw err;\n // Stop when there is no budget left for another pause + attempt, surfacing\n // the 409 (the actionable error) rather than an opaque timeout.\n if (Date.now() + opts.retryDelayMs > deadline) throw err;\n // Pause on the caller's signal only — the deadline is enforced above.\n await sleep(opts.retryDelayMs, opts.signal);\n }\n }\n}\n\n/**\n * Result of {@link CoderAgent.interrupt}: `interrupted: false` means there was\n * no chat to interrupt (neither a live session nor a\n * {@link CoderAgent.lastKnownChatId}) and no server call was made.\n */\nexport type CoderInterruptResult =\n | { interrupted: true; chatId: string }\n | { interrupted: false; chatId?: undefined };\n\n/**\n * Result of {@link CoderAgent.archive}: `archived: false` means there was no\n * chat to archive (neither a live session nor a\n * {@link CoderAgent.lastKnownChatId}) and no server call was made. Failures\n * still throw — see {@link CoderAgent.archive}. `chatId` is the primary\n * target (the session's or last-known chat); `archivedChatIds` additionally\n * lists every chat archived by the call, oldest first — more than one when\n * earlier stranded chats ({@link CoderAgent.strandedChatIds}) were retired\n * alongside it.\n */\nexport type CoderArchiveResult =\n | { archived: true; chatId: string; archivedChatIds: string[] }\n | { archived: false; chatId?: undefined; archivedChatIds?: undefined };\nexport interface CoderAgentSettings<TOOLS extends ToolSet = {}> {\n // --- connection (provide a client, or baseUrl + token) ---\n /**\n * A pre-built {@link CoderChatClient}, used instead of constructing one from\n * `baseUrl`/`token`. The preview helpers ({@link CoderAgent.getPreview},\n * {@link CoderAgent.sharePreview}) call non-chat endpoints and therefore\n * still need credentials — pass `baseUrl` + `token` alongside `client` (or\n * set `CODER_URL` + `CODER_SESSION_TOKEN`) to enable them.\n */\n client?: CoderChatClient;\n /** Coder deployment base URL, e.g. `https://dogfood.cdr.dev`. Defaults to `CODER_URL`. */\n baseUrl?: string;\n /** Coder API/session token (sent as `Coder-Session-Token`). Defaults to `CODER_SESSION_TOKEN`. */\n token?: string;\n fetch?: CoderChatClientOptions[\"fetch\"];\n webSocketFactory?: CoderChatClientOptions[\"webSocketFactory\"];\n /**\n * Observability hook: receives typed transport events — HTTP request/\n * response timings, the per-chat stream's WebSocket lifecycle (dial/open/\n * events/close/redial), and turn-segment start/settle — so timing and\n * tracing need no `fetch`/`webSocketFactory` wrapping. Exceptions it throws\n * are swallowed; without it, no event objects are allocated. NOTE: when a\n * pre-built `client` is supplied, its own `onTransportEvent` (set at client\n * construction) emits the HTTP/WebSocket events — this setting then only\n * receives the `segment:*` events. See {@link CoderTransportEvent}.\n */\n onTransportEvent?: TransportEventHandler;\n\n // --- Coder chat configuration ---\n /** Organization UUID that owns the chat (required). */\n organizationId: string;\n /** Model hint: UUID, `provider:model`, model id, or display-name substring. */\n model?: string;\n /** Reasoning effort sent on chat creation and each user-message submission. */\n reasoningEffort?: ReasoningEffort;\n /** Bind the chat to a Coder workspace (enables workspace-scoped tools). */\n workspaceId?: string;\n /**\n * Adapter for writing files onto a workspace filesystem, enabling\n * {@link CoderAgent.uploadToWorkspace}. Supply one backed by a workspace\n * connection (e.g. a `CoderWorkspaceSession` from `@coder/ai-sdk-sandbox`).\n */\n workspaceFiles?: WorkspaceFileStore;\n /** chatd-side MCP servers to enable for this chat. */\n mcpServerIds?: string[];\n /** chatd plan mode. */\n planMode?: \"\" | \"plan\";\n /** Resume an existing chat (session) instead of creating a new one. */\n chatId?: string;\n /**\n * Resume cursor for `chatId`: the newest chat message id a previous\n * instance had already seen. Persist {@link CoderAgent.lastSeenMessageId}\n * alongside the chat id when a turn ends and supply both here — the resumed\n * turn then skips its pre-prompt seed GET (the single-message history probe\n * that would otherwise establish the cursor). Without it, behavior is\n * unchanged. Must be a positive integer obtained from the getter, and\n * requires `chatId`; anything else fails construction.\n */\n lastSeenMessageId?: number;\n\n // --- agent configuration ---\n /** Stable agent id (exposed as {@link CoderAgent.id}). */\n id?: string;\n /** System instructions → chatd `system_prompt`. */\n instructions?: string | SystemModelMessage | Array<SystemModelMessage>;\n /** Custom (client-executed) tools. Each should have an `execute` for scripting. */\n tools?: TOOLS;\n /**\n * Per-tool context values for tools that declare a `contextSchema`, keyed by\n * tool name (forwarded to the AI SDK loop). Required by the SDK at runtime\n * whenever a supplied tool declares contextual data.\n */\n toolsContext?: ToolLoopAgentSettings<never, TOOLS>[\"toolsContext\"];\n toolChoice?: ToolChoice<NoInfer<TOOLS>>;\n /** Stop condition(s) for the AI SDK loop. Default `isStepCount(64)`. */\n stopWhen?: StopCondition<NoInfer<TOOLS>> | Array<StopCondition<NoInfer<TOOLS>>>;\n /**\n * SDK-level retries. Default `0`: this agent owns server-side chat state, so\n * SDK retries could duplicate a turn. Override with care.\n */\n maxRetries?: number;\n /**\n * Per-segment time budget in milliseconds, applied to each model round-trip\n * (chat creation / message / tool-result submission plus the server run until\n * it settles). If a segment runs longer (e.g. the server is wedged or a\n * workspace can't be scheduled), the run is interrupted server-side and the\n * call rejects with a retryable {@link CoderChatError} (`kind: \"timeout\"`)\n * instead of hanging. A multi-step `generate()` driving client tools makes\n * several segments, so this bounds each one — to cap total wall-clock for the\n * whole call, pass `abortSignal: AbortSignal.timeout(ms)`. Unset or\n * non-positive means no limit.\n */\n requestTimeoutMs?: number;\n /**\n * Overall deadline in milliseconds for bounded cleanup: how long\n * {@link CoderAgent.archive} keeps retrying while a freshly interrupted chat\n * settles server-side, and how long `[Symbol.asyncDispose]` may take in\n * total. Default 15 000. Primarily a test/tuning knob.\n */\n settleDeadlineMs?: number;\n /**\n * Pause in milliseconds between {@link CoderAgent.archive} retries while the\n * chat settles. Default 1000. Primarily a test/tuning knob.\n */\n settleRetryDelayMs?: number;\n}\n\n/** Options for {@link CoderAgent.getPreview}. */\nexport interface WorkspacePreviewOptions {\n /** Workspace port the app listens on (4–5 digit ports parse most reliably). */\n port: number;\n /**\n * Agent that serves the port. Optional when the workspace has exactly one\n * agent; required when it has several (the error lists the candidates).\n */\n agentName?: string;\n /**\n * Protocol the app speaks *inside the workspace* on that port — `https` adds\n * the `s` suffix (`3000s--…`) so the proxy connects over TLS. This is not\n * the browser scheme, which follows the deployment's access URL.\n * Default `\"http\"`.\n */\n protocol?: \"http\" | \"https\";\n signal?: AbortSignal;\n}\n\n/** A resolved workspace preview URL (see {@link CoderAgent.getPreview}). */\nexport interface WorkspacePreview {\n /** Browser-openable subdomain app URL, e.g. `https://3000--main--dev--alice.apps.example.com`. */\n url: string;\n}\n\n/** Options for {@link CoderAgent.sharePreview}. */\nexport interface SharePreviewOptions extends WorkspacePreviewOptions {\n /**\n * Who may open the preview: `authenticated` (any logged-in user; the\n * default), `organization` (members of the workspace's organization;\n * requires a newer Coder server), or `public` (no auth at all — mind what\n * the port serves).\n */\n shareLevel?: PreviewShareLevel;\n}\n\n/** A shared workspace preview (see {@link CoderAgent.sharePreview}). */\nexport interface SharedWorkspacePreview extends WorkspacePreview {\n /** The share level now in effect for the port. */\n shareLevel: PreviewShareLevel;\n}\n\n/**\n * An AI SDK-compliant agent backed by a remote Coder `chatd` agent runtime.\n *\n * `new CoderAgent({ ... })` returns an object that implements the AI SDK\n * `Agent` interface (`generate()`/`stream()`), so it composes with the rest of\n * the AI SDK ecosystem. Internally it wraps a {@link ToolLoopAgent} whose model\n * is a {@link CoderLanguageModel}; chatd runs the actual agent loop server-side.\n *\n * One `CoderAgent` instance corresponds to one chatd chat (\"session\"): the chat\n * is created on the first `generate()`/`stream()` and reused for subsequent\n * turns. Use {@link CoderAgent.resetSession} to start a fresh chat.\n */\nexport class CoderAgent<TOOLS extends ToolSet = {}> implements Agent<never, TOOLS> {\n readonly version = \"agent-v1\" as const;\n\n readonly #client: CoderChatClient;\n readonly #model: CoderLanguageModel;\n readonly #inner: InnerAgent<TOOLS>;\n readonly #organizationId: string;\n readonly #workspaceFiles: WorkspaceFileStore | undefined;\n readonly #workspaceId: string | undefined;\n readonly #workspaceApi: WorkspaceApiConnection | undefined;\n readonly #settleDeadlineMs: number;\n readonly #settleRetryDelayMs: number;\n\n constructor(settings: CoderAgentSettings<TOOLS>) {\n // Fail fast on an incompatible AI SDK major (see peer dependency `ai@^7`).\n assertSupportedAiVersion();\n\n // Resolved once, up front, so every consumer of the settings credentials\n // (the client bootstrap below and the preview helpers' REST connection)\n // sees the same values. Explicit settings win over the environment,\n // matching the sandbox transports' convention. `process` is missing in\n // browser bundles (the injected-`webSocketFactory` path), so reach it via\n // `globalThis` — client-only construction must not throw a ReferenceError.\n const env = globalThis.process?.env;\n const baseUrl = settings.baseUrl ?? env?.CODER_URL;\n const token = settings.token ?? env?.CODER_SESSION_TOKEN;\n\n if (settings.client) {\n this.#client = settings.client;\n } else if (baseUrl && token) {\n this.#client = new CoderChatClient({\n baseUrl,\n token,\n fetch: settings.fetch,\n webSocketFactory: settings.webSocketFactory,\n onTransportEvent: settings.onTransportEvent,\n });\n } else {\n throw new CoderAgentError(\n \"CoderAgent requires either `client` or both `baseUrl` and `token` \" +\n \"(or the CODER_URL and CODER_SESSION_TOKEN environment variables).\",\n );\n }\n\n this.#organizationId = settings.organizationId;\n this.#workspaceFiles = settings.workspaceFiles;\n this.#workspaceId = settings.workspaceId;\n // The preview helpers call stable v2 endpoints that CoderChatClient (chat\n // scoped) does not expose, so they need the raw credentials when given.\n this.#workspaceApi = baseUrl && token ? { baseUrl, token, fetch: settings.fetch } : undefined;\n // Sanitized so `AbortSignal.timeout` (which rejects non-integer, negative,\n // and non-finite delays with a RangeError) can never make disposal throw.\n this.#settleDeadlineMs = sanitizeDelayMs(settings.settleDeadlineMs, SETTLE_DEADLINE_MS);\n this.#settleRetryDelayMs = sanitizeDelayMs(settings.settleRetryDelayMs, SETTLE_RETRY_DELAY_MS);\n\n // A file written to one workspace isn't visible to a chat bound to another.\n if (\n settings.workspaceFiles &&\n settings.workspaceId &&\n settings.workspaceFiles.workspaceId !== settings.workspaceId\n ) {\n throw new CoderAgentError(\n `workspaceFiles.workspaceId (${settings.workspaceFiles.workspaceId}) does not match the ` +\n `agent's workspaceId (${settings.workspaceId}); the chat's tools would not see uploaded files.`,\n );\n }\n\n this.#model = new CoderLanguageModel({\n client: this.#client,\n organizationId: settings.organizationId,\n model: settings.model,\n reasoningEffort: settings.reasoningEffort,\n workspaceId: settings.workspaceId,\n mcpServerIds: settings.mcpServerIds,\n planMode: settings.planMode,\n chatId: settings.chatId,\n lastSeenMessageId: settings.lastSeenMessageId,\n requestTimeoutMs: settings.requestTimeoutMs,\n onTransportEvent: settings.onTransportEvent,\n });\n\n // Whether `toolsContext` is required depends on the concrete TOOLS the\n // caller supplies (tools with a `contextSchema`), a conditional TypeScript\n // cannot resolve for a generic TOOLS — hence the cast. The field itself is\n // typed on CoderAgentSettings, so callers still get the checked shape.\n this.#inner = new ToolLoopAgent<never, TOOLS>({\n model: this.#model,\n id: settings.id,\n instructions: settings.instructions,\n tools: settings.tools,\n toolsContext: settings.toolsContext,\n toolChoice: settings.toolChoice,\n stopWhen: settings.stopWhen ?? DEFAULT_STOP,\n maxRetries: settings.maxRetries ?? 0,\n } as ToolLoopAgentSettings<never, TOOLS>);\n }\n\n get id(): string | undefined {\n return this.#inner.id;\n }\n\n get tools(): TOOLS {\n return this.#inner.tools;\n }\n\n /** The underlying chatd client. */\n get client(): CoderChatClient {\n return this.#client;\n }\n\n /** The current chatd chat id, once a turn has started. */\n get chatId(): string | undefined {\n return this.#model.chatId;\n }\n\n /**\n * The session's resume cursor: the newest chat message id observed, or\n * `undefined` before any turn has streamed. Persist it alongside\n * {@link chatId} when a turn ends and supply both to a future instance\n * ({@link CoderAgentSettings.lastSeenMessageId}) to skip the resumed turn's\n * pre-prompt seed GET. Read it between turns, not mid-generation.\n */\n get lastSeenMessageId(): number | undefined {\n return this.#model.lastSeenMessageId;\n }\n\n /**\n * The last chat id this agent created or attached to, retained for CLEANUP\n * even after the session was dropped — a fresh-chat stream failure discards\n * the session ({@link CoderAgent.chatId} becomes `undefined`, so a retry\n * starts fresh), but the failed chat still exists server-side.\n * {@link CoderAgent.archive} and {@link CoderAgent.interrupt} fall back to\n * this id; generation never does. Superseded when a later turn creates or\n * attaches to another chat; cleared once {@link CoderAgent.archive}\n * succeeds against it.\n */\n get lastKnownChatId(): string | undefined {\n return this.#model.lastKnownChatId;\n }\n\n /**\n * Chat ids stranded by automatic session discards and not yet cleaned up,\n * oldest first. Each retryable fresh-chat stream failure strands one chat,\n * so with `maxRetries` > 0 several can accumulate while only the final\n * attempt's error (and {@link CoderAgent.lastKnownChatId}) names the\n * newest. {@link CoderAgent.archive} retires all of them.\n */\n get strandedChatIds(): string[] {\n return this.#model.strandedChatIds;\n }\n\n generate(\n options: Parameters<InnerAgent<TOOLS>[\"generate\"]>[0],\n ): ReturnType<InnerAgent<TOOLS>[\"generate\"]> {\n return this.#inner.generate(options);\n }\n\n stream(\n options: Parameters<InnerAgent<TOOLS>[\"stream\"]>[0],\n ): ReturnType<InnerAgent<TOOLS>[\"stream\"]> {\n return this.#inner.stream(options);\n }\n\n // --- session helpers ------------------------------------------------------\n\n /**\n * List the model configs available to this agent's organization. Use this\n * to discover valid values for the `model` hint instead of guessing ids —\n * match on `id`, `provider`/`model`, or `display_name`.\n */\n listModels(signal?: AbortSignal): Promise<ChatModelConfig[]> {\n return this.#client.listModelConfigs(this.#organizationId, signal);\n }\n\n /**\n * Start a fresh chatd chat on the next turn. The dropped chat still exists\n * server-side: {@link CoderAgent.archive} / {@link CoderAgent.interrupt}\n * keep targeting it (via {@link CoderAgent.lastKnownChatId}) until a new\n * chat supersedes it or an archive succeeds against it.\n */\n resetSession(): void {\n this.#model.resetSession();\n }\n\n /**\n * Interrupt the in-flight generation, if any. Resolves as soon as the server\n * acknowledges the interrupt — the run keeps winding down asynchronously for\n * a few seconds afterwards (see {@link CoderAgent.archive}).\n *\n * Targets the session's chat, or — after the session was dropped (a\n * fresh-chat stream failure, or {@link CoderAgent.resetSession}) — the\n * {@link CoderAgent.lastKnownChatId}, whose run may still be winding down\n * server-side. With neither, there is nothing to interrupt and it resolves\n * `{ interrupted: false }` without a server call (inspect the result rather\n * than relying on a silent no-op).\n */\n async interrupt(opts?: { signal?: AbortSignal }): Promise<CoderInterruptResult> {\n const id = this.#model.chatId ?? this.#model.lastKnownChatId;\n if (!id) return { interrupted: false };\n // A stream paused for client tools dies with the run it was following:\n // close it FIRST so a later resume dials fresh instead of consuming the\n // interrupt's settle events as the resumed generation's, and so the\n // socket cannot outlive an explicit interrupt when the tool loop never\n // resumes. (A stream an active segment is reading is left to that\n // segment, which observes the settle and closes it.) Local and instant.\n await this.#model.closePausedStream();\n await this.#client.interruptChat(id, opts?.signal);\n return { interrupted: true, chatId: id };\n }\n\n /**\n * Archive the underlying chat (safe cleanup; hides it from listings).\n *\n * Targets the session's chat, or — after the session was dropped (a\n * fresh-chat stream failure, or {@link CoderAgent.resetSession}) — the\n * {@link CoderAgent.lastKnownChatId}, so the stranded chat is still cleaned\n * up; chats stranded by EARLIER automatic discards\n * ({@link CoderAgent.strandedChatIds} — `maxRetries` can accumulate several)\n * are retired in the same call. On success each archived id is cleared as a\n * cleanup target; with no id at all, there is nothing to archive and it\n * resolves `{ archived: false }` without a server call (inspect the result\n * rather than relying on a silent no-op).\n *\n * A freshly interrupted/settled chat can keep winding down server-side for a\n * few seconds, during which the server rejects archiving with a 409. Those\n * 409s are retried (~1s apart — see `settleRetryDelayMs`) inside ONE\n * `settleDeadlineMs` window (~15s) shared by the whole call — however many\n * chats it archives — and the last 409 is rethrown if the window closes\n * first; any other failure, including a caller abort, rethrows immediately.\n * Chats are archived oldest first, each id cleared as its archive succeeds —\n * a mid-list failure rethrows and leaves the remaining ids targetable by a\n * later retry.\n */\n async archive(opts?: { signal?: AbortSignal }): Promise<CoderArchiveResult> {\n const primary = this.#model.chatId ?? this.#model.lastKnownChatId;\n if (!primary) return { archived: false };\n // The guaranteed-cleanup path also releases a stream retained by a\n // client-tool pause the caller abandoned (e.g. `stopWhen` ended the tool\n // loop, or a tool had no execute handler) — the socket must not outlive\n // the archived chat. Local and instant; an actively-read stream is left\n // to its segment, which closes it when the run settles.\n await this.#model.closePausedStream();\n // Oldest stranded chats first, primary last (deduplicated — after a\n // single discard the primary IS the newest stranded id): earlier ids'\n // runs were interrupted attempts ago, so they archive without a settle\n // wait, and a failure part-way leaves the rest pending, not forgotten.\n const targets = [...new Set([...this.#model.strandedChatIds, primary])];\n // One settle window for the WHOLE batch, not per target, so archive()\n // keeps its documented ~settleDeadlineMs bound however many chats it\n // retires: each target gets only the remaining budget (floored at 1ms\n // for AbortSignal.timeout's sake — with the window already exhausted,\n // the attempt aborts at once and the id stays targetable later).\n const deadline = Date.now() + this.#settleDeadlineMs;\n for (const id of targets) {\n await archiveWhenSettled(this.#client, id, {\n deadlineMs: Math.max(deadline - Date.now(), 1),\n retryDelayMs: this.#settleRetryDelayMs,\n signal: opts?.signal,\n });\n this.#model.noteChatCleanedUp(id);\n }\n return { archived: true, chatId: primary, archivedChatIds: targets };\n }\n\n /**\n * Clean up the chat when the agent leaves an `await using` scope, so cleanup\n * rides scope exit instead of a separate call you have to remember in a\n * `finally`. Interrupts any in-flight server run, then archives the chat\n * (retrying while the interrupted run settles) — including a chat stranded\n * by a session discard (see {@link CoderAgent.lastKnownChatId}), which a\n * failed `generate()` would otherwise leak. Best-effort and bounded\n * (~15s overall): disposal errors are swallowed after the bounded attempts\n * so they can't mask the scope's own error — call {@link CoderAgent.archive}\n * directly when you need guaranteed cleanup.\n *\n * @example\n * ```ts\n * await using agent = new CoderAgent({ ... });\n * const { text } = await agent.generate({ prompt: \"…\" });\n * // agent.interrupt() + agent.archive() run automatically here.\n * ```\n */\n async [Symbol.asyncDispose](): Promise<void> {\n // Release the session's retained event stream first (issue #44): a turn\n // that paused for client tools, aborted, or timed out leaves its WebSocket\n // open for reuse, and scope exit is where it must be freed. Local-only and\n // instant; never throws.\n try {\n await this.#model[Symbol.asyncDispose]();\n } catch {\n /* best-effort cleanup */\n }\n // archive() only soft-hides the chat; it does not stop a generation. If the\n // scope exits mid-turn (e.g. generate() threw, or an early return), interrupt\n // first so chatd stops generating and releases the workspace, then archive.\n // One shared timeout bounds both calls so scope exit can never hang.\n const deadline = AbortSignal.timeout(this.#settleDeadlineMs);\n try {\n await this.interrupt({ signal: deadline });\n } catch {\n /* best-effort cleanup */\n }\n try {\n await this.archive({ signal: deadline });\n } catch {\n /* best-effort cleanup */\n }\n }\n\n // --- files ----------------------------------------------------------------\n\n /**\n * Upload a file as a chat attachment — content for the model to *read* (a PDF,\n * image, CSV, …). Returns a handle you can reference from a later turn via\n * {@link ChatAttachment.toFilePart} (no re-upload). Validated against the chat\n * allowlist and 10 MiB cap up front.\n *\n * You usually don't need this: a native AI SDK `file` part dropped into a\n * message's `content` is uploaded transparently. Reach for `attach()` to\n * pre-upload, to reuse one file across turns, or to validate before sending.\n *\n * For large or non-allowlisted files (zips, datasets, binaries) — material for\n * the agent to *operate on* — use {@link CoderAgent.uploadToWorkspace}.\n */\n async attach(file: ChatFileInput, signal?: AbortSignal): Promise<ChatAttachment> {\n const uploaded = await this.#client.uploadChatFile(this.#organizationId, file, signal);\n return makeChatAttachment(uploaded);\n }\n\n /**\n * Write a file onto the bound workspace's filesystem for the agent to operate\n * on (extract, build, read with its tools). Requires a {@link WorkspaceFileStore}\n * via the `workspaceFiles` setting. The file is written as-is — no unpacking;\n * instruct the agent to `unzip`/`tar -x`, or extract over your own connection.\n *\n * The chat should be bound to the same workspace (`workspaceId`) so the agent's\n * server-side tools can see the file.\n */\n async uploadToWorkspace(file: {\n content: FileContent;\n /** Destination path inside the workspace (the adapter may resolve relatives). */\n path: string;\n signal?: AbortSignal;\n }): Promise<WorkspacePlacement> {\n if (!this.#workspaceFiles) {\n throw new CoderAgentError(\n \"uploadToWorkspace requires a `workspaceFiles` adapter. Construct the agent with \" +\n \"`workspaceFiles` (e.g. backed by a @coder/ai-sdk-sandbox workspace session) to enable it.\",\n );\n }\n const { path } = await this.#workspaceFiles.write({\n content: file.content,\n path: file.path,\n signal: file.signal,\n });\n return { workspaceId: this.#workspaceFiles.workspaceId, path };\n }\n\n // --- previews ---------------------------------------------------------------\n\n /** The v2 connection + workspace id the preview helpers need, or a clear error. */\n #previewContext(method: string): { conn: WorkspaceApiConnection; workspaceId: string } {\n if (!this.#workspaceId) {\n throw new CoderAgentError(\n `${method}() requires the agent to be bound to a workspace — construct the CoderAgent ` +\n `with the \\`workspaceId\\` setting.`,\n );\n }\n if (!this.#workspaceApi) {\n throw new CoderAgentError(\n `${method}() needs the deployment's REST credentials — construct the CoderAgent with ` +\n `\\`baseUrl\\` and \\`token\\` (they can accompany \\`client\\`), or set the CODER_URL and ` +\n `CODER_SESSION_TOKEN environment variables.`,\n );\n }\n return { conn: this.#workspaceApi, workspaceId: this.#workspaceId };\n }\n\n /**\n * Resolve the browser URL where a port on the bound workspace can be\n * previewed, e.g. `https://3000--main--dev--alice.apps.example.com`.\n * Composed from stable v2 endpoints (workspace lookup + wildcard app host),\n * so it works against old Coder servers. Requires the `workspaceId` setting,\n * `baseUrl`/`token` credentials, and a deployment with a wildcard access URL\n * configured.\n *\n * The URL honors the port's current share level — private to the workspace\n * owner unless shared (see {@link CoderAgent.sharePreview}).\n */\n async getPreview(opts: WorkspacePreviewOptions): Promise<WorkspacePreview> {\n const { conn, workspaceId } = this.#previewContext(\"getPreview\");\n const { url } = await resolveWorkspacePreview(\n conn,\n { workspaceId, port: opts.port, agentName: opts.agentName, protocol: opts.protocol },\n opts.signal,\n );\n return { url };\n }\n\n /**\n * Share a port on the bound workspace and return its preview URL. Upserts\n * the port's share level — `authenticated` by default — so teammates (or,\n * with `public`, anyone) can open what the agent is serving; re-invoking\n * updates the level in place. On Coder servers that predate port sharing\n * (< v2.9) this fails with a 404 {@link CoderApiError} saying so.\n */\n async sharePreview(opts: SharePreviewOptions): Promise<SharedWorkspacePreview> {\n const { conn, workspaceId } = this.#previewContext(\"sharePreview\");\n return shareWorkspacePreview(\n conn,\n {\n workspaceId,\n port: opts.port,\n agentName: opts.agentName,\n protocol: opts.protocol,\n shareLevel: opts.shareLevel,\n },\n opts.signal,\n );\n }\n}\n","import { APICallError } from \"@ai-sdk/provider\";\nimport type { ChatErrorPayload } from \"./coder/types.js\";\n\n/** Base error for all `@coder/ai-sdk-agent` failures. */\nexport class CoderAgentError extends Error {\n override name = \"CoderAgentError\";\n constructor(message: string, options?: { cause?: unknown }) {\n super(message, options);\n }\n}\n\n/** An HTTP request to the Coder API failed. */\nexport class CoderApiError extends CoderAgentError {\n override name = \"CoderApiError\";\n readonly status: number;\n readonly detail: string | undefined;\n readonly method: string;\n readonly path: string;\n constructor(args: {\n status: number;\n method: string;\n path: string;\n message: string;\n detail?: string;\n }) {\n super(\n `Coder API ${args.method} ${args.path} failed (${args.status}): ${args.message}` +\n (args.detail ? ` — ${args.detail}` : \"\"),\n );\n this.status = args.status;\n this.detail = args.detail;\n this.method = args.method;\n this.path = args.path;\n }\n}\n\n/**\n * The per-chat event stream dropped and could not be re-established within its\n * redial budget (or could not be safely resumed — see `streamChatEvents` on\n * servers without delta coordinates). Extends the AI SDK's {@link APICallError} with\n * `isRetryable: true` because the `ai` package's retry machinery (`maxRetries`)\n * only honors `APICallError`/`GatewayError`-branded errors — plain `Error`\n * subclasses are never retried no matter what they claim. Note the retry\n * wrapper only sees errors that reject the model call itself (`generate()`);\n * a failure mid-`stream()` surfaces on the stream, past that wrapper. The last\n * underlying transport failure is preserved as `cause`.\n *\n * NOTE: this is deliberately NOT a {@link CoderAgentError} (single\n * inheritance); match it with `APICallError.isInstance(err)` or `err.name`.\n */\nexport class CoderStreamError extends APICallError {\n override name = \"CoderStreamError\";\n /**\n * The chatd chat id the failed turn had created or attached to, when known.\n * A fresh-chat stream failure discards the session so a retry starts on a\n * fresh chat (`CoderAgent.chatId` becomes `undefined`), but the failed chat\n * still exists server-side — this field names it so callers can clean it up\n * (`CoderAgent.archive()` also retains it as the last-known cleanup target).\n * `undefined` when the failure happened before any chat existed.\n */\n readonly chatId: string | undefined;\n constructor(args: {\n message: string;\n url: string;\n cause?: unknown;\n isRetryable?: boolean;\n chatId?: string;\n }) {\n super({\n message: args.message,\n url: args.url,\n requestBodyValues: undefined,\n // Retries re-invoke the model with the SAME prompt, so the model layer\n // downgrades this to false whenever the failed chat has prior state a\n // re-submission would corrupt, or server-side tooling (workspace/MCP)\n // whose effects a replay would duplicate (see #runTurn).\n isRetryable: args.isRetryable ?? true,\n cause: args.cause,\n });\n this.chatId = args.chatId;\n }\n}\n\n/** A chat generation ended in an error status. */\nexport class CoderChatError extends CoderAgentError {\n override name = \"CoderChatError\";\n readonly kind: string | undefined;\n readonly provider: string | undefined;\n readonly retryable: boolean;\n readonly statusCode: number | undefined;\n constructor(payload: ChatErrorPayload) {\n super(payload.detail ? `${payload.message}: ${payload.detail}` : payload.message);\n this.kind = payload.kind;\n this.provider = payload.provider;\n this.retryable = payload.retryable ?? false;\n this.statusCode = payload.status_code;\n }\n}\n","import { CoderAgentError } from \"./errors.js\";\n\n/** The `ai` peer-dependency major this package is built against (`ai@^7`). */\nconst SUPPORTED_AI_MAJOR = 7;\n\n/**\n * Decides whether a resolved `ai` package version is supported. Returns the\n * error message to raise for a known-incompatible major, or `undefined` to\n * proceed. Unparseable strings also return `undefined`: the guard fails open\n * rather than inventing a failure on exotic version formats.\n */\nexport function unsupportedAiVersionMessage(version: string): string | undefined {\n const major = Number(/^v?(\\d+)(?:[.+-]|$)/.exec(version.trim())?.[1]);\n if (!Number.isSafeInteger(major) || major === SUPPORTED_AI_MAJOR) return undefined;\n return (\n `@coder/ai-sdk-agent supports the \\`ai\\` package v${SUPPORTED_AI_MAJOR} only ` +\n `(peer dependency \\`ai@^${SUPPORTED_AI_MAJOR}\\`), but ai@${version} is installed. ` +\n `Other majors fail at runtime in confusing ways (brand-check TypeErrors, silently ` +\n `empty streams). Install a compatible version, e.g. \\`pnpm add ai@^${SUPPORTED_AI_MAJOR}\\`.`\n );\n}\n\n/**\n * Best-effort resolution of the installed `ai` package's version via\n * `createRequire(import.meta.url)(\"ai/package.json\")` (both v6 and v7 export\n * `./package.json`). Returns `undefined` whenever resolution isn't possible —\n * non-Node runtime, bundle without module resolution, `ai` not installed, … —\n * because the guard must never introduce a failure of its own. `node:module`\n * is reached through `process.getBuiltinModule` (Node ≥ 20.16) instead of a\n * static import, which would crash non-Node runtimes at module-load time.\n */\nfunction resolveInstalledAiVersion(): string | undefined {\n try {\n const nodeModule =\n typeof process !== \"undefined\" && typeof process.getBuiltinModule === \"function\"\n ? process.getBuiltinModule(\"node:module\")\n : undefined;\n const requireFromHere = nodeModule?.createRequire(import.meta.url);\n const pkg = requireFromHere?.(\"ai/package.json\") as { version?: unknown } | undefined;\n return typeof pkg?.version === \"string\" ? pkg.version : undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Memoized verdict: `undefined` = not yet computed, `null` = supported (or\n * unresolvable), a string = the message to throw on every construction.\n */\nlet cachedMessage: string | null | undefined;\n\n/**\n * Asserts that the installed `ai` major matches this package's peer range\n * (`ai@^7`). Called from the `CoderAgent`/`CoderLanguageModel` constructors so\n * an incompatible AI SDK fails fast with an actionable error instead of the\n * cryptic failures it produces mid-generation. Fails open when the installed\n * version cannot be resolved. The verdict is memoized across calls.\n *\n * @param resolveVersion Test seam; production callers use the default.\n */\nexport function assertSupportedAiVersion(\n resolveVersion: () => string | undefined = resolveInstalledAiVersion,\n): void {\n if (cachedMessage === undefined) {\n const version = resolveVersion();\n cachedMessage = version === undefined ? null : (unsupportedAiVersionMessage(version) ?? null);\n }\n if (cachedMessage !== null) throw new CoderAgentError(cachedMessage);\n}\n\n/** Clears the memoized verdict so resolution runs again (unit-test hook). */\nexport function resetAiVersionCheckForTests(): void {\n cachedMessage = undefined;\n}\n","import type { SharedV4FileData } from \"@ai-sdk/provider\";\nimport { CoderAgentError } from \"./errors.js\";\n\n/**\n * Bytes to upload, in any convenient shape.\n *\n * `Blob`/`File` is the recommended form: it is lazy (not forced fully into\n * memory), carries its own size (so the 10 MiB chat cap can fail fast and a\n * `Content-Length` can be sent), is re-readable (so a failed upload can be\n * retried), and works both in Node (`fs.openAsBlob(\"./x.pdf\")`) and the browser\n * (an `<input type=file>` yields a `File`). A `ReadableStream` is accepted for\n * genuinely unbounded sources, but its size is unknown up front — the chat size\n * cap can then only be enforced server-side.\n */\nexport type FileContent = Blob | ReadableStream<Uint8Array> | Uint8Array | ArrayBuffer;\n\n/** A {@link FileContent} normalized for upload: a fetch-ready body plus derived metadata. */\nexport interface ResolvedFile {\n /**\n * Body to hand to `fetch`. Raw bytes are wrapped in a `Blob` so the body is\n * always a clean `BodyInit` (and bytes get a `Content-Length` for free).\n */\n body: Blob | ReadableStream<Uint8Array>;\n /** Resolved IANA media type. */\n mediaType: string;\n /** Original filename, if known. */\n name: string | undefined;\n /** Byte length, if known up front (undefined for streams). */\n size: number | undefined;\n}\n\nfunction isBlob(v: unknown): v is Blob {\n return typeof Blob !== \"undefined\" && v instanceof Blob;\n}\nfunction isReadableStream(v: unknown): v is ReadableStream<Uint8Array> {\n return typeof ReadableStream !== \"undefined\" && v instanceof ReadableStream;\n}\n\n/**\n * Normalize {@link FileContent} into a fetch-ready body and derive its media\n * type, name, and (when known) size. `Blob`/`File` supply their own\n * `type`/`name`/`size`; for raw bytes or a stream, `mediaType` must be provided\n * by the caller. Explicit `opts.mediaType`/`opts.name` always win.\n */\nexport function resolveFileContent(\n content: FileContent,\n opts?: { mediaType?: string; name?: string },\n): ResolvedFile {\n let mediaType = opts?.mediaType;\n let name = opts?.name;\n let body: ResolvedFile[\"body\"];\n let size: number | undefined;\n\n if (isBlob(content)) {\n body = content;\n size = content.size;\n if (!mediaType && content.type) mediaType = content.type;\n // `File` extends `Blob` with a `name`; read it structurally so we don't\n // depend on the `File` global type existing.\n const maybeName = (content as { name?: unknown }).name;\n if (!name && typeof maybeName === \"string\") name = maybeName;\n } else if (isReadableStream(content)) {\n body = content;\n size = undefined;\n } else if (content instanceof Uint8Array || content instanceof ArrayBuffer) {\n size = content.byteLength;\n body = new Blob([content as BlobPart]);\n } else {\n throw new CoderAgentError(\n \"Unsupported file content: expected a Blob/File, ReadableStream, Uint8Array, or ArrayBuffer.\",\n );\n }\n\n if (!mediaType) {\n throw new CoderAgentError(\n \"Could not determine the file's media type. Pass `mediaType`, or provide a Blob/File that carries its own `type`.\",\n );\n }\n\n return { body, mediaType, name, size };\n}\n\n/** Decode a base64 string to bytes (Node `Buffer` or browser `atob`). */\nfunction base64ToBytes(b64: string): Uint8Array {\n // `Buffer` is already a `Uint8Array`; return it directly (no extra copy).\n if (typeof Buffer !== \"undefined\") return Buffer.from(b64, \"base64\");\n const bin = atob(b64);\n const bytes = new Uint8Array(bin.length);\n for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);\n return bytes;\n}\n\n/**\n * Convert an AI SDK file-part `data` value (a tagged {@link SharedV4FileData}\n * union) into {@link FileContent} for upload. The provider receives inline\n * bytes, a base64 string, or inline text. URLs are not expected: this model\n * declares no `supportedUrls`, so the SDK downloads them to bytes before\n * calling us — a URL (or an opaque provider reference) here is therefore an\n * error.\n */\nexport function dataContentToFileContent(data: SharedV4FileData): FileContent {\n switch (data.type) {\n case \"data\":\n return data.data instanceof Uint8Array ? data.data : base64ToBytes(data.data);\n case \"text\":\n return new TextEncoder().encode(data.text);\n default:\n throw new CoderAgentError(\n `File parts referencing a ${data.type === \"url\" ? \"URL\" : \"provider reference\"} are not supported; provide the file bytes instead.`,\n );\n }\n}\n","/**\n * Wire types for Coder's stable `chatd` chat API\n * (`/api/v2/chats`, promoted in Coder 2.37.0). These mirror the Go types in\n * `coder/coder`'s `codersdk/chats.go` (JSON shapes), restricted to the subset\n * this client uses. Field names use snake_case to match the wire format.\n */\n\n// ---------------------------------------------------------------------------\n// Input (client -> server)\n// ---------------------------------------------------------------------------\n\nexport type ChatInputPartType = \"text\" | \"file\" | \"file-reference\";\n\nexport interface ChatInputPart {\n type: ChatInputPartType;\n text?: string;\n file_id?: string;\n file_name?: string;\n start_line?: number;\n end_line?: number;\n content?: string;\n}\n\n/**\n * Media types accepted by the chat-file upload endpoint\n * (`POST /api/v2/chats/files`). The server allowlist is intentionally\n * narrow — anything else (zip, tarball, binaries, …) is not a chat attachment\n * and belongs on the workspace filesystem instead. Mirrors\n * `codersdk.AllChatAttachmentMediaTypes`.\n */\nexport type ChatAttachmentMediaType =\n | \"application/json\"\n | \"application/pdf\"\n | \"image/gif\"\n | \"image/jpeg\"\n | \"image/png\"\n | \"image/webp\"\n | \"text/csv\"\n | \"text/markdown\"\n | \"text/plain\";\n\n/** The chat-attachment media-type allowlist, as a runtime-checkable set. */\nexport const CHAT_ATTACHMENT_MEDIA_TYPES: ReadonlySet<string> = new Set<ChatAttachmentMediaType>([\n \"application/json\",\n \"application/pdf\",\n \"image/gif\",\n \"image/jpeg\",\n \"image/png\",\n \"image/webp\",\n \"text/csv\",\n \"text/markdown\",\n \"text/plain\",\n]);\n\n/** Per-file size cap enforced by the chat-file upload endpoint (10 MiB). */\nexport const MAX_CHAT_FILE_SIZE_BYTES = 10 * 1024 * 1024;\n\nexport type ChatClientType = \"ui\" | \"api\";\nexport type ChatPlanMode = \"\" | \"plan\";\nexport type ChatBusyBehavior = \"queue\" | \"interrupt\";\n\n/** Coder's global reasoning effort scale; accepts future server-defined values. */\nexport type ReasoningEffort =\n | \"none\"\n | \"minimal\"\n | \"low\"\n | \"medium\"\n | \"high\"\n | \"xhigh\"\n | \"max\"\n | (string & {});\n\n/**\n * A client-executed (\"dynamic\") tool definition. chatd never executes these;\n * when the model calls one, the chat enters `requires_action` and the client\n * must execute it and submit results via {@link SubmitToolResultsRequest}.\n *\n * NOTE: the JSON key is `input_schema` (snake_case), per chatd.\n */\nexport interface DynamicTool {\n name: string;\n description?: string;\n /** A JSON Schema object describing the tool input. */\n input_schema: unknown;\n}\n\nexport interface CreateChatRequest {\n organization_id: string;\n content: ChatInputPart[];\n system_prompt?: string;\n workspace_id?: string;\n model_config_id?: string;\n reasoning_effort?: ReasoningEffort;\n mcp_server_ids?: string[];\n labels?: Record<string, string>;\n unsafe_dynamic_tools?: DynamicTool[];\n plan_mode?: ChatPlanMode;\n client_type?: ChatClientType;\n}\n\nexport interface CreateChatMessageRequest {\n content: ChatInputPart[];\n model_config_id?: string;\n reasoning_effort?: ReasoningEffort;\n mcp_server_ids?: string[];\n busy_behavior?: ChatBusyBehavior;\n plan_mode?: ChatPlanMode;\n}\n\n/** Result of a single client-executed tool call. */\nexport interface ToolResult {\n tool_call_id: string;\n /** JSON-serializable tool output. */\n output: unknown;\n is_error?: boolean;\n}\n\nexport interface SubmitToolResultsRequest {\n results: ToolResult[];\n}\n\nexport interface UpdateChatRequest {\n title?: string;\n archived?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Output (server -> client)\n// ---------------------------------------------------------------------------\n\nexport type ChatStatus =\n | \"waiting\"\n | \"pending\"\n | \"running\"\n | \"paused\"\n | \"completed\"\n | \"error\"\n | \"requires_action\"\n | \"interrupting\";\n\nexport type ChatMessageRole = \"system\" | \"user\" | \"assistant\" | \"tool\";\n\nexport type ChatMessagePartType =\n | \"text\"\n | \"reasoning\"\n | \"tool-call\"\n | \"tool-result\"\n | \"source\"\n | \"file\"\n | \"file-reference\"\n | \"context-file\"\n | \"skill\";\n\n/**\n * A part of a chat message. chatd models this as a single flat struct with a\n * `type` discriminator and many optional fields (see `codersdk.ChatMessagePart`),\n * so we mirror that shape rather than a strict discriminated union.\n */\nexport interface ChatMessagePart {\n type: ChatMessagePartType;\n\n // text / reasoning\n text?: string;\n signature?: string;\n\n // tool-call / tool-result\n tool_call_id?: string;\n tool_name?: string;\n mcp_server_config_id?: string | null;\n /** tool-call: complete arguments (JSON value). */\n args?: unknown;\n /** tool-call: incremental argument text chunk during streaming. */\n args_delta?: string;\n parsed_commands?: string[][];\n /** tool-result: complete result (JSON value). */\n result?: unknown;\n /** tool-result: incremental result text chunk during streaming. */\n result_delta?: string;\n result_reset?: boolean;\n is_error?: boolean;\n is_media?: boolean;\n provider_executed?: boolean;\n created_at?: string;\n completed_at?: string;\n\n // source\n source_id?: string;\n url?: string;\n title?: string;\n\n // file\n media_type?: string;\n name?: string;\n file_id?: string | null;\n\n // file-reference\n file_name?: string;\n start_line?: number;\n end_line?: number;\n content?: string;\n\n // skill\n skill_name?: string;\n skill_description?: string;\n}\n\nexport interface ChatMessageUsage {\n input_tokens?: number;\n output_tokens?: number;\n total_tokens?: number;\n reasoning_tokens?: number;\n cache_creation_tokens?: number;\n cache_read_tokens?: number;\n context_limit?: number;\n /**\n * Cost in micro-USD, when the server prices usage. Optional: servers that\n * don't price usage (or predate cost reporting) omit it.\n */\n total_cost_micros?: number;\n /** Model runtime in milliseconds, when the server reports it. */\n total_runtime_ms?: number;\n}\n\nexport interface ChatMessage {\n id: number;\n chat_id: string;\n created_by?: string;\n model_config_id?: string;\n created_at: string;\n role: ChatMessageRole;\n content?: ChatMessagePart[];\n usage?: ChatMessageUsage;\n /**\n * The id of the queue entry ({@link ChatQueuedMessage.id}) this user\n * message was promoted from. Set only on messages promoted out of the chat's\n * queue (coder/coder#29859); omitted for direct sends, edits and synthetic\n * rows, and by older servers that don't record it — absent means unknown.\n */\n queued_message_id?: number;\n}\n\nexport interface ChatErrorPayload {\n message: string;\n detail?: string;\n kind?: string;\n provider?: string;\n retryable?: boolean;\n status_code?: number;\n}\n\nexport interface Chat {\n id: string;\n organization_id: string;\n owner_id: string;\n owner_username?: string;\n workspace_id?: string | null;\n agent_id?: string | null;\n parent_chat_id?: string | null;\n root_chat_id?: string | null;\n last_model_config_id?: string;\n last_reasoning_effort?: ReasoningEffort | null;\n title: string;\n status: ChatStatus;\n plan_mode?: ChatPlanMode;\n last_error?: ChatErrorPayload | null;\n created_at: string;\n updated_at: string;\n archived: boolean;\n mcp_server_ids?: string[];\n client_type?: ChatClientType;\n}\n\nexport interface ChatQueuedMessage {\n id: number;\n chat_id: string;\n content: ChatMessagePart[];\n created_at: string;\n}\n\nexport interface CreateChatMessageResponse {\n message?: ChatMessage;\n queued_message?: ChatQueuedMessage;\n queued: boolean;\n warnings?: string[];\n}\n\nexport interface ChatMessagesResponse {\n messages: ChatMessage[];\n queued_messages: ChatQueuedMessage[];\n has_more: boolean;\n}\n\n/** Response from `POST /api/v2/chats/files`: the uploaded file's id. */\nexport interface UploadChatFileResponse {\n id: string;\n}\n\n/**\n * A configured model, normalized to the shape of the legacy\n * `GET /api/experimental/chats/model-configs` listing (removed upstream in\n * coder/coder#28632). The client synthesizes this from the organization-scoped\n * {@link OrganizationChatModelsResponse} by joining each model's\n * `ai_provider_id` to its provider descriptor's `type` (→ `provider`).\n * Depending on the server version and how a config was created, `provider`,\n * `model`, and `display_name` may be absent on individual entries, so treat\n * them as optional and guard reads.\n */\nexport interface ChatModelConfig {\n id: string;\n provider?: string;\n ai_provider_id?: string;\n model?: string;\n display_name?: string;\n enabled?: boolean;\n is_default?: boolean;\n context_limit?: number;\n compression_threshold?: number;\n reasoning_efforts?: ReasoningEffort[];\n}\n\n/**\n * An organization-scoped chat model, as returned by\n * `GET /api/v2/organizations/{organization}/chats/models` (codersdk\n * `ChatModel`). The provider type string is not carried here — join\n * `ai_provider_id` against {@link ChatModelProviderDescriptor.id}. Fields\n * other than `id` are treated as optional to tolerate partial payloads from\n * older/newer servers.\n */\nexport interface ChatModel {\n id: string;\n organization_id?: string;\n /** FK into the response's `providers[].id`. */\n ai_provider_id?: string;\n model?: string;\n display_name?: string;\n enabled?: boolean;\n is_default?: boolean;\n context_limit?: number;\n compression_threshold?: number;\n reasoning_efforts?: ReasoningEffort[];\n created_at?: string;\n updated_at?: string;\n}\n\n/**\n * The redacted view of an AI provider carried on the organization-scoped\n * model collection (codersdk `ChatModelProviderDescriptor`) — capability\n * metadata only, no key material or base URLs. `type` is the provider type\n * string (e.g. `anthropic`) that the legacy listing carried per-model.\n */\nexport interface ChatModelProviderDescriptor {\n id: string;\n type?: string;\n display_name?: string;\n icon?: string;\n enabled?: boolean;\n available?: boolean;\n unavailable_reason?: string;\n}\n\n/** A configured provider type the server build does not support. */\nexport interface ChatUnsupportedProvider {\n provider?: string;\n display_name?: string;\n}\n\n/**\n * Response of `GET /api/v2/organizations/{organization}/chats/models`\n * (codersdk `OrganizationChatModelsResponse`): the org's model configs plus\n * the provider descriptors they reference.\n */\nexport interface OrganizationChatModelsResponse {\n models?: ChatModel[];\n providers?: ChatModelProviderDescriptor[];\n unsupported_providers?: ChatUnsupportedProvider[];\n}\n\n// ---------------------------------------------------------------------------\n// Stream events (WebSocket `/stream`)\n// ---------------------------------------------------------------------------\n\nexport type ChatStreamEventType =\n | \"message_part\"\n | \"message\"\n | \"status\"\n | \"error\"\n | \"queue_update\"\n | \"retry\"\n | \"action_required\"\n | \"preview_reset\"\n | \"history_reset\";\n\nexport interface ChatStreamMessagePart {\n role?: ChatMessageRole;\n part: ChatMessagePart;\n history_version?: number;\n generation_attempt?: number;\n seq?: number;\n}\n\nexport interface ChatStreamStatus {\n status: ChatStatus;\n}\n\nexport interface ChatStreamToolCall {\n tool_call_id: string;\n tool_name: string;\n /** JSON string of the tool-call arguments. */\n args: string;\n}\n\nexport interface ChatStreamActionRequired {\n tool_calls: ChatStreamToolCall[];\n}\n\nexport interface ChatStreamRetry {\n attempt: number;\n delay_ms: number;\n error: string;\n kind?: string;\n provider?: string;\n status_code?: number;\n retrying_at: string;\n}\n\nexport interface ChatStreamEvent {\n type: ChatStreamEventType;\n chat_id: string;\n message?: ChatMessage;\n message_part?: ChatStreamMessagePart;\n status?: ChatStreamStatus;\n error?: ChatErrorPayload;\n retry?: ChatStreamRetry;\n queued_messages?: ChatQueuedMessage[];\n action_required?: ChatStreamActionRequired;\n}\n\n/** Terminal statuses: the chat has stopped generating for this turn. */\nexport const TERMINAL_STATUSES: ReadonlySet<ChatStatus> = new Set<ChatStatus>([\n \"waiting\",\n \"completed\",\n \"error\",\n \"requires_action\",\n]);\n\n// ---------------------------------------------------------------------------\n// Watch events (WebSocket `/watch`)\n// ---------------------------------------------------------------------------\n\n/** Kinds of chat lifecycle events emitted on the per-user watch socket. */\nexport type ChatWatchEventKind =\n | \"status_change\"\n | \"summary_change\"\n | \"title_change\"\n | \"created\"\n | \"deleted\"\n | \"diff_status_change\"\n | \"action_required\"\n | \"context_dirty\";\n\n/**\n * An event from `GET /api/v2/chats/watch`, which covers every chat\n * visible to the authenticated user (not a single chat). Mirrors\n * `codersdk.ChatWatchEvent`.\n */\nexport interface ChatWatchEvent {\n kind: ChatWatchEventKind;\n chat: Chat;\n /** Pending client-executed tool calls; sent with `action_required` events. */\n tool_calls?: ChatStreamToolCall[];\n}\n","import NodeWebSocket from \"ws\";\nimport { CoderAgentError, CoderApiError, CoderStreamError } from \"../errors.js\";\nimport {\n safeTransportEmitter,\n type StreamCloseTransportEvent,\n type TransportEventHandler,\n} from \"../transport-events.js\";\nimport type { ChatStreamEvent, ChatWatchEvent } from \"./types.js\";\n\n/**\n * A minimal WebSocket factory abstraction so the stream reader can run on\n * Node (via the `ws` package, with header auth) or in environments that\n * provide a global `WebSocket` (browsers, where auth must go in the query\n * string). Node is the default target.\n */\nexport interface WebSocketLike {\n send(data: string): void;\n close(code?: number): void;\n addEventListener(type: \"open\", cb: () => void): void;\n addEventListener(type: \"message\", cb: (ev: { data: unknown }) => void): void;\n addEventListener(type: \"error\", cb: (ev: unknown) => void): void;\n addEventListener(type: \"close\", cb: (ev: { code?: number; reason?: string }) => void): void;\n removeEventListener(type: \"open\", cb: () => void): void;\n removeEventListener(type: \"message\", cb: (ev: { data: unknown }) => void): void;\n removeEventListener(type: \"error\", cb: (ev: unknown) => void): void;\n removeEventListener(type: \"close\", cb: (ev: { code?: number; reason?: string }) => void): void;\n}\n\nexport type WebSocketFactory = (\n url: string,\n protocols: { headers: Record<string, string> },\n) => WebSocketLike;\n\nconst defaultFactory: WebSocketFactory = (url, { headers }) => {\n // `ws` accepts custom handshake headers, which lets us authenticate with the\n // `Coder-Session-Token` header instead of leaking the token into the URL.\n return new NodeWebSocket(url, { headers }) as unknown as WebSocketLike;\n};\n\nfunction httpToWs(baseUrl: string): string {\n if (baseUrl.startsWith(\"https://\")) return `wss://${baseUrl.slice(\"https://\".length)}`;\n if (baseUrl.startsWith(\"http://\")) return `ws://${baseUrl.slice(\"http://\".length)}`;\n return baseUrl;\n}\n\nexport interface StreamChatEventsOptions {\n baseUrl: string;\n token: string;\n chatId: string;\n /** Only stream events for messages with id greater than this. */\n afterId?: number;\n signal?: AbortSignal;\n webSocketFactory?: WebSocketFactory;\n /** Resolve the client's route prefix before dialing, using the reader's cancellation signal. */\n resolveApiPrefix?: (signal: AbortSignal) => Promise<string>;\n /**\n * Observability hook: receives the reader's `ws:*` transport events (dial,\n * open, decoded events, close, error, redial). Exceptions it throws are\n * swallowed; without it, no event objects are allocated and no extra socket\n * listeners are registered. See {@link CoderTransportEvent}.\n */\n onTransportEvent?: TransportEventHandler;\n /**\n * Reader id stamped on this call's `ws:*` events, for callers that need to\n * know it (the model records it to stamp `segment:settle` with the reader\n * that served the segment). Must come from {@link nextStreamReaderId};\n * allocated automatically when absent.\n */\n reader?: number;\n}\n\n/**\n * Monotonic reader-id source for `ws:*` transport events (issue #94). One\n * module-global counter — deliberately not per client or model instance, so\n * `(chatId, reader, attempt)` stays unique for the lifetime of any subscriber,\n * even a handler shared across instances. Advanced on every `streamChatEvents`\n * call (one integer increment per turn — the #45 zero-overhead property is\n * about event objects and listeners), subscriber or not, so ids stay globally\n * consistent when only some clients are observed.\n */\nlet readerSeq = 0;\n\n/** Allocates the next reader id (see {@link StreamChatEventsOptions.reader}). */\nexport function nextStreamReaderId(): number {\n readerSeq += 1;\n return readerSeq;\n}\n\nconst STREAM_BACKOFF_INITIAL_MS = 1_000;\nconst STREAM_BACKOFF_CAP_MS = 30_000;\n/**\n * Consecutive progress-less connection endings tolerated before a dropped\n * per-chat stream gives up — counting the drop that starts an outage. Only\n * forward progress — a newly committed or revised message, or a new\n * (unsuppressed) delta — resets the count; chatd replays a `status` and the\n * in-progress episode on every reconnect, so counting mere receipt would let\n * a half-dead connection (connects, replays, drops) redial forever. With\n * 1s→2s→4s→8s backoff between the five endings this bounds a non-progressing\n * network to ~15s of redialing even without a caller signal or\n * `requestTimeoutMs`.\n */\nconst STREAM_MAX_CONSECUTIVE_FAILURES = 5;\n\n/**\n * Upgrade-rejection statuses that can succeed on a later attempt with the\n * exact same request: request timeout (408), too early (425), and rate\n * limiting (429). These consume the redial budget instead of failing fast;\n * every other 4xx (bad/expired token, deleted chat, …) is terminal.\n */\nconst TRANSIENT_UPGRADE_STATUSES: ReadonlySet<number> = new Set([408, 425, 429]);\n\n/**\n * Opens the chatd `/stream` WebSocket and yields decoded {@link ChatStreamEvent}s\n * as an async iterable. Each text frame from chatd is a JSON array of events;\n * we flatten them into a single stream.\n *\n * The chat stream is a live subscription that stays open after a turn settles,\n * so callers should `break` out of the loop once they observe a terminal status\n * — that triggers generator cleanup, which closes the socket.\n *\n * Dropped connections are redialed with exponential backoff (like\n * {@link watchChatEvents}), replaying from the turn's ORIGINAL `after_id`. The\n * cursor is deliberately NOT advanced past messages already yielded: chatd's\n * initial sync filters out ids at or below `after_id`, so an advanced cursor\n * would silently lose same-id REVISION snapshots (updated content, tool\n * results, usage) committed while the connection was down. Consumers must\n * dedupe repeated `message` snapshots — the wire contract demands that anyway,\n * since chatd re-sends full snapshots on revision bumps even without a\n * reconnect (TurnTranslator does). On reconnect chatd also replays the\n * in-progress generation attempt's `message_part` deltas from `seq` 1, so\n * parts of an episode\n * (`history_version`, `generation_attempt`) at or below the last yielded `seq`\n * are suppressed here rather than double-yielded. That episode filter runs at\n * frame arrival (not at consumption), so each frame's `ws:event` can carry\n * the reader's own verdict as `forwarded` (issue #111) while `timestamp`\n * keeps meaning arrival time. The iteration ends in five\n * ways: `signal` aborting (clean return), a non-transient 4xx upgrade\n * rejection (terminal {@link CoderApiError} — bad/expired token or a deleted\n * chat cannot succeed on retry, while 408/425/429 consume the redial budget),\n * an unparseable frame (terminal {@link CoderAgentError} — a redial would\n * replay the same frame forever), a drop while deltas WITHOUT episode\n * coordinates are outstanding (older chatd; a {@link CoderStreamError},\n * because a delta replay could not be deduped and would double-emit — a NEW\n * committed `message` finalizes them into dedupable snapshots and re-enables\n * redial), or the redial budget running out (also a {@link CoderStreamError}).\n */\nexport function streamChatEvents(\n options: StreamChatEventsOptions,\n): AsyncGenerator<ChatStreamEvent, void, void> {\n // Cancellation plumbing, mirroring watchChatEvents below: async-generator\n // `return()`/`throw()` queue behind a pending `next()`, and with redial a\n // pending read can now span a backoff sleep and a fresh socket — a bare\n // `return()` from a signal-less consumer would otherwise keep redialing\n // instead of tearing down. The wrapper aborts an internal controller first,\n // which wakes the pending read (and the backoff sleep), then delegates.\n const controller = new AbortController();\n const external = options.signal;\n const chain = (): void => controller.abort(external?.reason);\n if (external) {\n if (external.aborted) chain();\n else external.addEventListener(\"abort\", chain, { once: true });\n }\n // The reader id is fixed at call time (before the first dial), so every\n // `ws:*` event of this reader — across redials — carries the same id.\n const reader = options.reader ?? nextStreamReaderId();\n const inner = streamChatEventsLoop({ ...options, reader, signal: controller.signal });\n const detach = (): void => external?.removeEventListener(\"abort\", chain);\n const finish = async (): Promise<void> => {\n controller.abort();\n detach();\n try {\n await inner.return();\n } catch {\n /* the loop's own teardown errors are not the caller's problem */\n }\n };\n return {\n // Detach the signal chain once the loop settles for good (done, or a\n // terminal failure) — the iteration protocol never calls return() on a\n // failed iterator, and the {once} listener would otherwise pile up on a\n // long-lived signal across re-created readers.\n async next(): Promise<IteratorResult<ChatStreamEvent, void>> {\n try {\n const result = await inner.next();\n if (result.done) detach();\n return result;\n } catch (err) {\n detach();\n throw err;\n }\n },\n async return(): Promise<IteratorResult<ChatStreamEvent, void>> {\n await finish();\n return { done: true, value: undefined };\n },\n async throw(err: unknown): Promise<IteratorResult<ChatStreamEvent, void>> {\n await finish();\n throw err;\n },\n [Symbol.asyncIterator]() {\n return this;\n },\n // Native async generators are async-disposable; the wrapper must be too.\n async [Symbol.asyncDispose](): Promise<void> {\n await finish();\n },\n } as AsyncGenerator<ChatStreamEvent, void, void>;\n}\n\nasync function* streamChatEventsLoop(\n options: StreamChatEventsOptions & { reader: number; signal: AbortSignal },\n): AsyncGenerator<ChatStreamEvent, void, void> {\n const { baseUrl, token, chatId, signal, reader } = options;\n const factory = options.webSocketFactory ?? defaultFactory;\n // Exception-isolated observability emitter; undefined without a subscriber,\n // and every emit site below is guarded on that so the no-subscriber path\n // allocates no event objects and registers no extra listeners.\n const emit = safeTransportEmitter(options.onTransportEvent);\n // 1-based connection attempt for `ws:*` event correlation.\n let attempt = 0;\n\n const wsBase = httpToWs(baseUrl);\n if (signal.aborted) return;\n let prefix = \"/api/v2/chats\";\n try {\n if (options.resolveApiPrefix) prefix = await options.resolveApiPrefix(signal);\n } catch (err) {\n if (signal.aborted) return;\n throw err;\n }\n const path = `${prefix}/${chatId}/stream`;\n // The turn's original cursor, reused verbatim on every redial — see the doc\n // comment above for why it must not advance past yielded messages.\n const query = options.afterId !== undefined ? `?after_id=${options.afterId}` : \"\";\n const url = `${wsBase}${path}${query}`;\n\n // Highest committed message id yielded, for progress accounting only.\n let maxCommittedId = options.afterId;\n // Last-yielded serialized form of each committed message, so a same-id\n // REVISION snapshot (updated content, tool data, or usage on an\n // already-committed message) counts as forward progress while a\n // byte-identical replay does not. Revision numbers are not on the wire\n // type, so content equality is the discriminator (#59).\n const lastMessageJson = new Map<number, string>();\n // Last yielded delta position, for suppressing chatd's from-the-start replay\n // of the in-progress episode after a redial.\n let lastHistoryVersion: number | undefined;\n let lastGenerationAttempt: number | undefined;\n let lastSeq: number | undefined;\n // Deltas missing those coordinates (older servers) cannot be deduped on\n // replay: while any are outstanding, a drop is terminal instead of redialed\n // — the pre-redial behavior — because a replay would double-emit them. A\n // NEW committed `message` finalizes them (their content then arrives only as\n // snapshots, which consumers dedupe), making redial safe again.\n let sawUntrackableDelta = false;\n\n let backoffMs = STREAM_BACKOFF_INITIAL_MS;\n let failures = 0;\n let lastFailure: Error | undefined;\n\n while (!signal?.aborted) {\n // One connection attempt per iteration; the queue/wake plumbing mirrors\n // watchChatEventsLoop below.\n const queue: ChatStreamEvent[] = [];\n let resolveNext: (() => void) | undefined;\n let finished = false;\n let failure: Error | undefined;\n let parseFailure = false;\n // Whether this connection's `ws:close` has been emitted (via the close\n // listener); the teardown below emits a synthetic one otherwise, so every\n // dial gets exactly one close.\n let closeEmitted = false;\n\n const wake = () => {\n resolveNext?.();\n resolveNext = undefined;\n };\n\n attempt += 1;\n emit?.({ type: \"ws:dial\", chatId, reader, attempt, url, timestamp: Date.now() });\n let ws: WebSocketLike;\n try {\n ws = factory(url, { headers: { \"Coder-Session-Token\": token } });\n } catch (err) {\n // A synchronously-throwing factory is a terminal reader failure (as\n // before the hooks); keep the observability invariants on the way out —\n // the emitted dial still gets its error and exactly-one close.\n if (emit) {\n const message = err instanceof Error ? err.message : String(err);\n emit({ type: \"ws:error\", chatId, reader, attempt, message, timestamp: Date.now() });\n emit({ type: \"ws:close\", chatId, reader, attempt, timestamp: Date.now() });\n }\n throw err;\n }\n\n const onAbort = () => {\n finished = true;\n try {\n ws.close(1000);\n } catch {\n /* ignore */\n }\n wake();\n };\n let abortListenerAdded = false;\n if (signal) {\n if (signal.aborted) onAbort();\n else {\n signal.addEventListener(\"abort\", onAbort, { once: true });\n abortListenerAdded = true;\n }\n }\n\n // Applies the reader's replay dedup to one decoded frame at ARRIVAL: it\n // updates the episode/progress ledgers, emits the frame's `ws:event`\n // stamped with the verdict (`forwarded`, issue #111), and enqueues only\n // forwarded frames for the consumption path below. Running the filter here\n // instead of at consumption keeps the stamp truthful without splitting the\n // state machine in two, and is behaviorally equivalent: the queue is\n // drained in arrival order before any redial decision reads the ledgers,\n // so they advance through the same frame sequence either way. `timestamp`\n // keeps meaning arrival time, and the verdict is computed per-frame\n // subscriber or not, so the no-subscriber path stays allocation-free.\n const ingest = (next: ChatStreamEvent): void => {\n // Only forward progress resets the redial budget: a new delta or a\n // newly committed message. Replays (chatd re-sends a `status` and the\n // in-progress episode on every reconnect) don't count — otherwise a\n // half-dead connection that connects, replays, and drops would reset\n // the budget each round and redial forever.\n let progress = false;\n let forwarded = true;\n if (next.type === \"message_part\") {\n const mp = next.message_part;\n const hv = mp?.history_version;\n const ga = mp?.generation_attempt;\n const seq = mp?.seq;\n if (hv !== undefined && ga !== undefined && seq !== undefined) {\n if (\n hv === lastHistoryVersion &&\n ga === lastGenerationAttempt &&\n lastSeq !== undefined &&\n seq <= lastSeq\n ) {\n // An already-yielded delta, replayed after a redial: suppressed\n // from the consumption path (never enqueued below).\n forwarded = false;\n } else {\n lastHistoryVersion = hv;\n lastGenerationAttempt = ga;\n lastSeq = seq;\n progress = true;\n }\n } else {\n // No episode coordinates: cannot be deduped on replay, so it\n // must not count as progress and disables redial (see below).\n sawUntrackableDelta = true;\n }\n } else if (next.type === \"message\" && next.message) {\n // A message not seen in this exact form before — a new commit or\n // a same-id revision — is forward progress; a byte-identical\n // replay is not. Either way the snapshot is forwarded: snapshot\n // dedup is the consumer's job (the wire contract re-sends full\n // snapshots on revision bumps even without a reconnect).\n const serialized = JSON.stringify(next.message);\n if (lastMessageJson.get(next.message.id) !== serialized) {\n lastMessageJson.set(next.message.id, serialized);\n progress = true;\n }\n if (maxCommittedId === undefined || next.message.id > maxCommittedId) {\n maxCommittedId = next.message.id;\n // This commit finalizes the in-progress deltas: their content\n // now lives in committed snapshots, which consumers dedupe, so\n // a replay can no longer duplicate coordinate-less deltas.\n sawUntrackableDelta = false;\n }\n }\n if (progress) {\n failures = 0;\n backoffMs = STREAM_BACKOFF_INITIAL_MS;\n }\n // Observed at arrival — a redial's replayed episode is visible to\n // subscribers, with the duplicates the reader suppresses marked\n // `forwarded: false`.\n emit?.({\n type: \"ws:event\",\n chatId,\n reader,\n attempt,\n event: next,\n forwarded,\n timestamp: Date.now(),\n });\n if (forwarded) queue.push(next);\n };\n\n const onMessage = (ev: { data: unknown }): void => {\n if (finished) return;\n let batch: unknown;\n try {\n const data = typeof ev.data === \"string\" ? ev.data : String(ev.data);\n batch = JSON.parse(data);\n } catch (err) {\n emit?.({\n type: \"ws:error\",\n chatId,\n reader,\n attempt,\n message: \"failed to parse chat stream frame\",\n timestamp: Date.now(),\n });\n failure = new CoderAgentError(\"failed to parse chat stream frame\", { cause: err });\n parseFailure = true;\n finished = true;\n wake();\n return;\n }\n if (Array.isArray(batch)) {\n for (const e of batch) ingest(e as ChatStreamEvent);\n } else if (batch && typeof batch === \"object\") {\n ingest(batch as ChatStreamEvent);\n }\n wake();\n };\n const onError = (ev: unknown): void => {\n if (finished) return;\n const message =\n ev && typeof ev === \"object\" && \"message\" in ev\n ? String((ev as { message: unknown }).message)\n : \"chat stream socket error\";\n emit?.({ type: \"ws:error\", chatId, reader, attempt, message, timestamp: Date.now() });\n const status = upgradeStatus(message);\n failure =\n status !== undefined &&\n status >= 400 &&\n status < 500 &&\n !TRANSIENT_UPGRADE_STATUSES.has(status)\n ? new CoderApiError({ status, method: \"GET\", path, message })\n : new CoderAgentError(message);\n finished = true;\n wake();\n };\n const onClose = (ev?: { code?: number; reason?: string }): void => {\n if (emit && !closeEmitted) {\n closeEmitted = true;\n const closeEvent: StreamCloseTransportEvent = {\n type: \"ws:close\",\n chatId,\n reader,\n attempt,\n timestamp: Date.now(),\n };\n if (ev?.code !== undefined) closeEvent.code = ev.code;\n if (ev?.reason !== undefined && ev.reason !== \"\") closeEvent.reason = ev.reason;\n emit(closeEvent);\n }\n finished = true;\n wake();\n };\n // Registered only for observability — the reader itself never waits for\n // the handshake (a send-less consumer just reads whatever arrives).\n const onOpen = emit\n ? (): void => emit({ type: \"ws:open\", chatId, reader, attempt, timestamp: Date.now() })\n : undefined;\n if (onOpen) ws.addEventListener(\"open\", onOpen);\n ws.addEventListener(\"message\", onMessage);\n ws.addEventListener(\"error\", onError);\n ws.addEventListener(\"close\", onClose);\n\n try {\n while (true) {\n while (queue.length > 0) {\n // Frames were replay-filtered and progress-credited at arrival (see\n // `ingest` above); the consumption path only hands them over.\n yield queue.shift() as ChatStreamEvent;\n }\n if (finished) break;\n await new Promise<void>((resolve) => {\n resolveNext = resolve;\n });\n }\n } finally {\n finished = true;\n if (signal && abortListenerAdded) signal.removeEventListener(\"abort\", onAbort);\n if (onOpen) ws.removeEventListener(\"open\", onOpen);\n ws.removeEventListener(\"message\", onMessage);\n ws.removeEventListener(\"error\", onError);\n ws.removeEventListener(\"close\", onClose);\n try {\n ws.close(1000);\n } catch {\n /* ignore */\n }\n // The reader ended the connection itself (settle, teardown, abort, or an\n // error-triggered exit whose close event hadn't fired yet): emit the\n // close now — every dial gets exactly one `ws:close`, with `code`/\n // `reason` only when the server/network closed the socket.\n if (emit && !closeEmitted) {\n closeEmitted = true;\n emit({ type: \"ws:close\", chatId, reader, attempt, timestamp: Date.now() });\n }\n }\n\n if (signal?.aborted) return;\n // A rejected upgrade (non-transient 4xx) is terminal: retrying with the\n // same credentials against the same (possibly deleted) chat cannot\n // succeed. An unparseable frame is terminal too: the cursor cannot advance\n // past an event we cannot decode, so a redial would replay the exact same\n // frame forever.\n if (failure instanceof CoderApiError) throw failure;\n if (parseFailure && failure) throw failure;\n if (sawUntrackableDelta) {\n // A CoderStreamError (not a bare CoderAgentError) so the model layer\n // applies its retry-safety gates — otherwise the generic stream_closed\n // wrap would advertise a same-chat retry that could duplicate the turn.\n throw new CoderStreamError({\n message:\n \"chat stream dropped mid-turn and cannot be resumed: this server does not stamp deltas with history_version/generation_attempt/seq, so a redial would replay them\",\n url,\n cause: failure,\n chatId,\n });\n }\n if (failure) lastFailure = failure;\n // EVERY ended connection counts toward the budget — including the one\n // whose drop started this outage — so the give-up bound stays ~15s of\n // backoff on all paths; forward progress above resets the count.\n failures += 1;\n if (failures >= STREAM_MAX_CONSECUTIVE_FAILURES) {\n throw new CoderStreamError({\n message: `Coder chat stream ${path} could not be (re-)established after ${failures} consecutive connection failures without progress`,\n url,\n cause: lastFailure,\n chatId,\n });\n }\n emit?.({\n type: \"ws:redial\",\n chatId,\n reader,\n attempt,\n consecutiveFailures: failures,\n maxConsecutiveFailures: STREAM_MAX_CONSECUTIVE_FAILURES,\n backoffMs,\n timestamp: Date.now(),\n });\n await sleep(backoffMs, signal);\n if (signal?.aborted) return;\n backoffMs = Math.min(backoffMs * 2, STREAM_BACKOFF_CAP_MS);\n }\n}\n\nconst WATCH_BACKOFF_INITIAL_MS = 1_000;\nconst WATCH_BACKOFF_CAP_MS = 30_000;\n\n/** Abort-aware sleep; resolves early (without throwing) when the signal aborts. */\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve) => {\n if (signal?.aborted) {\n resolve();\n return;\n }\n const onAbort = (): void => {\n clearTimeout(timer);\n resolve();\n };\n const timer = setTimeout(() => {\n signal?.removeEventListener(\"abort\", onAbort);\n resolve();\n }, ms);\n // The timer stays ref'd on purpose: an actively consumed watch loop must\n // keep the process alive through a reconnect delay (abort clears it).\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n });\n}\n\n/**\n * Extract the HTTP status from a rejected WebSocket upgrade. The `ws` package\n * reports handshake failures as an error event whose message is\n * `Unexpected server response: <status>`; other factories that follow the\n * same convention are recognized too.\n */\nfunction upgradeStatus(message: string): number | undefined {\n const match = /unexpected server response: (\\d{3})$/i.exec(message.trim());\n return match ? Number(match[1]) : undefined;\n}\n\nexport interface WatchChatEventsOptions {\n baseUrl: string;\n token: string;\n signal?: AbortSignal;\n webSocketFactory?: WebSocketFactory;\n /** Resolve the client's route prefix before dialing, using the reader's cancellation signal. */\n resolveApiPrefix?: (signal: AbortSignal) => Promise<string>;\n}\n\n/**\n * Opens the chatd `/watch` WebSocket — lifecycle events for every chat visible\n * to the authenticated user — and yields decoded {@link ChatWatchEvent}s as an\n * async iterable.\n *\n * Unlike the per-chat `/stream`, this is a long-lived subscription, so dropped\n * connections are redialed automatically with exponential backoff (1s doubling\n * to a 30s cap, reset once an event arrives). It ends in exactly three ways:\n * `signal` aborting, the consumer ending iteration (`break` from `for await`,\n * `iterator.return()`) — both tear the socket down promptly even while a read\n * is pending — or the server rejecting the upgrade with a 4xx — bad/expired\n * token, or an older Coder server without the endpoint (404) — which throws a\n * terminal {@link CoderApiError} instead of retrying forever.\n */\nexport function watchChatEvents(\n options: WatchChatEventsOptions,\n): AsyncGenerator<ChatWatchEvent, void, void> {\n // Cancellation plumbing: async-generator `return()`/`throw()` queue behind a\n // pending `next()`, and on this rarely-eventing stream a pending read is the\n // steady state — a bare `return()` would otherwise hang (socket open) until\n // the next server event. The wrapper aborts an internal controller first,\n // which wakes the pending read, then delegates.\n const controller = new AbortController();\n const external = options.signal;\n const chain = (): void => controller.abort(external?.reason);\n if (external) {\n if (external.aborted) chain();\n else external.addEventListener(\"abort\", chain, { once: true });\n }\n const inner = watchChatEventsLoop({ ...options, signal: controller.signal });\n const detach = (): void => external?.removeEventListener(\"abort\", chain);\n const finish = async (): Promise<void> => {\n controller.abort();\n detach();\n try {\n await inner.return();\n } catch {\n /* the loop's own teardown errors are not the caller's problem */\n }\n };\n return {\n // Detach the signal chain once the loop settles for good (done, or the\n // terminal 4xx rejection) — the iteration protocol never calls return()\n // on a failed iterator, and the {once} listener would otherwise pile up\n // on a long-lived signal across re-created watchers.\n async next(): Promise<IteratorResult<ChatWatchEvent, void>> {\n try {\n const result = await inner.next();\n if (result.done) detach();\n return result;\n } catch (err) {\n detach();\n throw err;\n }\n },\n async return(): Promise<IteratorResult<ChatWatchEvent, void>> {\n await finish();\n return { done: true, value: undefined };\n },\n async throw(err: unknown): Promise<IteratorResult<ChatWatchEvent, void>> {\n await finish();\n throw err;\n },\n [Symbol.asyncIterator]() {\n return this;\n },\n // Native async generators are async-disposable; the wrapper must be too,\n // so `await using events = client.watchChats(…)` keeps tearing down.\n async [Symbol.asyncDispose](): Promise<void> {\n await finish();\n },\n } as AsyncGenerator<ChatWatchEvent, void, void>;\n}\n\nasync function* watchChatEventsLoop(\n options: WatchChatEventsOptions & { signal: AbortSignal },\n): AsyncGenerator<ChatWatchEvent, void, void> {\n const { baseUrl, token, signal } = options;\n const factory = options.webSocketFactory ?? defaultFactory;\n if (signal.aborted) return;\n let prefix = \"/api/v2/chats\";\n try {\n if (options.resolveApiPrefix) prefix = await options.resolveApiPrefix(signal);\n } catch (err) {\n if (signal.aborted) return;\n throw err;\n }\n const path = `${prefix}/watch`;\n const url = `${httpToWs(baseUrl)}${path}`;\n\n let backoffMs = WATCH_BACKOFF_INITIAL_MS;\n while (!signal?.aborted) {\n // One connection attempt per iteration; the queue/wake plumbing mirrors\n // streamChatEvents above.\n const queue: ChatWatchEvent[] = [];\n let resolveNext: (() => void) | undefined;\n let finished = false;\n let failure: Error | undefined;\n\n const wake = () => {\n resolveNext?.();\n resolveNext = undefined;\n };\n\n const ws = factory(url, { headers: { \"Coder-Session-Token\": token } });\n\n const onAbort = () => {\n finished = true;\n try {\n ws.close(1000);\n } catch {\n /* ignore */\n }\n wake();\n };\n let abortListenerAdded = false;\n if (signal) {\n if (signal.aborted) onAbort();\n else {\n signal.addEventListener(\"abort\", onAbort, { once: true });\n abortListenerAdded = true;\n }\n }\n\n const onMessage = (ev: { data: unknown }): void => {\n if (finished) return;\n let payload: unknown;\n try {\n const data = typeof ev.data === \"string\" ? ev.data : String(ev.data);\n payload = JSON.parse(data);\n } catch (err) {\n // A malformed frame means this connection is unusable; redial it.\n failure = new CoderAgentError(\"failed to parse chat watch frame\", { cause: err });\n finished = true;\n wake();\n return;\n }\n // chatd sends one event object per frame (wsjson); tolerate batches too.\n if (Array.isArray(payload)) {\n for (const e of payload) queue.push(e as ChatWatchEvent);\n } else if (payload && typeof payload === \"object\") {\n queue.push(payload as ChatWatchEvent);\n }\n wake();\n };\n const onError = (ev: unknown): void => {\n if (finished) return;\n const message =\n ev && typeof ev === \"object\" && \"message\" in ev\n ? String((ev as { message: unknown }).message)\n : \"chat watch socket error\";\n const status = upgradeStatus(message);\n failure =\n status !== undefined && status >= 400 && status < 500\n ? new CoderApiError({ status, method: \"GET\", path, message })\n : new CoderAgentError(message);\n finished = true;\n wake();\n };\n const onClose = (): void => {\n finished = true;\n wake();\n };\n ws.addEventListener(\"message\", onMessage);\n ws.addEventListener(\"error\", onError);\n ws.addEventListener(\"close\", onClose);\n\n try {\n while (true) {\n while (queue.length > 0) {\n const next = queue.shift() as ChatWatchEvent;\n // Receiving an event proves the connection works — reset the backoff.\n backoffMs = WATCH_BACKOFF_INITIAL_MS;\n yield next;\n }\n if (finished) break;\n await new Promise<void>((resolve) => {\n resolveNext = resolve;\n });\n }\n } finally {\n finished = true;\n if (signal && abortListenerAdded) signal.removeEventListener(\"abort\", onAbort);\n ws.removeEventListener(\"message\", onMessage);\n ws.removeEventListener(\"error\", onError);\n ws.removeEventListener(\"close\", onClose);\n try {\n ws.close(1000);\n } catch {\n /* ignore */\n }\n }\n\n if (signal?.aborted) return;\n // A rejected upgrade (4xx) is terminal: retrying with the same credentials\n // against the same server cannot succeed.\n if (failure instanceof CoderApiError) throw failure;\n await sleep(backoffMs, signal);\n if (signal?.aborted) return;\n backoffMs = Math.min(backoffMs * 2, WATCH_BACKOFF_CAP_MS);\n }\n}\n","import type { LanguageModelV4FinishReason } from \"@ai-sdk/provider\";\nimport type { ChatStatus, ChatStreamEvent } from \"./coder/types.js\";\n\n/**\n * Typed transport observability events (issue #45).\n *\n * A single optional `onTransportEvent` hook — on {@link CoderChatClientOptions},\n * {@link CoderLanguageModelConfig}, and {@link CoderAgentSettings} — receives\n * these events so consumers can build traces and timings from a stable event\n * vocabulary instead of wrapping `fetch`/`webSocketFactory` and re-parsing\n * every stream frame.\n *\n * Contract:\n * - **Isolation** — exceptions thrown by a subscriber are swallowed; they can\n * never alter transport behavior or a turn's outcome.\n * - **Zero overhead** — without a subscriber, no event objects are allocated\n * and no extra listeners are registered.\n * - **No secrets** — events carry no headers and no tokens. Authentication\n * travels exclusively in the `Coder-Session-Token` request header, which is\n * deliberately excluded; the `path`/`url` fields never contain credentials.\n * - **Observation, not interception** — events are emitted synchronously at\n * observation time (`timestamp` is `Date.now()`, comparable to server-side\n * timestamps such as a message's `created_at`); handlers cannot modify what\n * the transport does. Payloads referenced by an event (`ws:event`'s decoded\n * stream event) are shared with the pipeline — treat them as read-only.\n */\ninterface TransportEventBase {\n /** Wall-clock observation time in ms since epoch (`Date.now()`). */\n timestamp: number;\n}\n\n// --- HTTP (every REST call made by CoderChatClient) -------------------------\n\n/**\n * Names the `CoderChatClient` operation performing an HTTP exchange (issue\n * #112). The public client method names are the stable vocabulary, so\n * subscribers can classify per-operation timings without reverse-engineering\n * `path` (whose API prefix is private and may change). New client methods\n * extend this union; treat unrecognized values as \"some other operation\"\n * rather than matching exhaustively.\n */\nexport type CoderClientOperation =\n | \"listModelConfigs\"\n | \"resolveModelConfigId\"\n | \"createChat\"\n | \"getChat\"\n | \"createChatMessage\"\n | \"getMessages\"\n | \"submitToolResults\"\n | \"interruptChat\"\n | \"deleteQueuedMessage\"\n | \"updateChat\"\n | \"archiveChat\"\n | \"uploadChatFile\"\n | \"getChatFile\"\n | \"streamEvents\"\n | \"watchChats\";\n\ninterface HttpTransportEventBase extends TransportEventBase {\n /**\n * Correlates a request with its response/error. Monotonically increasing\n * per client instance.\n */\n id: number;\n /**\n * The client operation performing this exchange — the public\n * `CoderChatClient` method name. `archiveChat` stamps its own operation\n * even though it issues the same `PATCH` as `updateChat`.\n */\n op: CoderClientOperation;\n /** HTTP method, e.g. `POST`. */\n method: string;\n /**\n * Path + query relative to the client's `baseUrl`, e.g.\n * `/api/v2/chats/{id}/tool-results`. Never carries credentials.\n */\n path: string;\n}\n\n/** A request is about to be sent. */\nexport interface HttpRequestTransportEvent extends HttpTransportEventBase {\n type: \"http:request\";\n}\n\n/**\n * Response headers arrived (before the body is read). Emitted for every\n * completed exchange, including non-2xx responses (`ok: false`) that the\n * client subsequently surfaces as a `CoderApiError`.\n */\nexport interface HttpResponseTransportEvent extends HttpTransportEventBase {\n type: \"http:response\";\n status: number;\n ok: boolean;\n /** Time from request start to response headers, in milliseconds. */\n durationMs: number;\n}\n\n/** The fetch itself rejected (network failure, abort) — no response arrived. */\nexport interface HttpErrorTransportEvent extends HttpTransportEventBase {\n type: \"http:error\";\n message: string;\n durationMs: number;\n}\n\n// --- WebSocket (the per-chat `/stream` reader) -------------------------------\n\ninterface StreamTransportEventBase extends TransportEventBase {\n chatId: string;\n /**\n * Identifies the reader (one `streamChatEvents` call) this event belongs\n * to: monotonically increasing, allocated per call from one module-global\n * (process-wide) counter — NOT per model or client instance, so\n * `(chatId, reader, attempt)` stays unique for the lifetime of any\n * subscriber, even one shared across instances (issue #94). Readers on\n * other chats/clients advance the same counter, so ids are ordered but not\n * dense per chat. Use it to tell a superseded reader's late events apart\n * from the live reader's: a client-tool pause abandoned by the caller is\n * closed fire-and-forget when the next turn dials its replacement, and the\n * old reader's `ws:close` (or a raced-in frame) can emit after the new\n * reader's `ws:dial` — both would otherwise be ambiguous at `attempt` 1.\n */\n reader: number;\n /**\n * 1-based connection attempt within one reader (`streamChatEvents` call —\n * with stream retention (#44), one per turn). Increments on every redial\n * and restarts at 1 for each new reader.\n */\n attempt: number;\n}\n\n/** A connection attempt is starting (socket constructed). */\nexport interface StreamDialTransportEvent extends StreamTransportEventBase {\n type: \"ws:dial\";\n /** The `wss://…/stream[?after_id=N]` URL. Never carries credentials. */\n url: string;\n}\n\n/** The WebSocket handshake completed. */\nexport interface StreamOpenTransportEvent extends StreamTransportEventBase {\n type: \"ws:open\";\n}\n\n/**\n * A decoded stream event arrived on the wire. Emitted at arrival time for\n * every event in a frame batch — after a redial, chatd's replay of the\n * in-progress episode is still visible here, stamped with the reader's own\n * replay verdict (`forwarded`). `event` is the decoded object by reference\n * (no copy): do not mutate it.\n */\nexport interface StreamEventTransportEvent extends StreamTransportEventBase {\n type: \"ws:event\";\n event: ChatStreamEvent;\n /**\n * The reader's replay disposition for this frame (issue #111): `false`\n * exactly when the episode filter suppresses it from the consumption path —\n * a redial's replay of an in-progress delta at or below the last yielded\n * `seq` of the same episode — and `true` when the reader forwards it to its\n * consumer. It is a dedup verdict, not a delivery receipt: a consumer\n * tearing down mid-queue can still discard a forwarded frame.\n *\n * Snapshot dedup is deliberately NOT reflected here: repeated or revised\n * `message` snapshots are always `forwarded: true`, because reconciling\n * them is the consumer's job past the transport layer — TurnTranslator's\n * per-message emitted-content ledger decides what a revision re-emits.\n * That disposition is not stamped on transport frames; subscribers needing\n * content fidelity must consume model output (or TurnTranslator), not\n * `ws:event`.\n */\n forwarded: boolean;\n}\n\n/**\n * The connection ended. Exactly one per dial: carries `code`/`reason` when\n * the server or network closed the socket; both are absent when the reader\n * tore the connection down itself (turn settled, redial teardown, abort).\n */\nexport interface StreamCloseTransportEvent extends StreamTransportEventBase {\n type: \"ws:close\";\n code?: number;\n reason?: string;\n}\n\n/**\n * An error was observed on the connection: a socket `error` event (including\n * rejected upgrades) or an unparseable frame. The reader's own terminal\n * classification still surfaces through the turn (see `segment:settle`).\n */\nexport interface StreamErrorTransportEvent extends StreamTransportEventBase {\n type: \"ws:error\";\n message: string;\n}\n\n/**\n * A dropped connection is about to be redialed after `backoffMs`. Carries the\n * redial budget state: the reader gives up (failing the turn) when\n * `consecutiveFailures` — connection endings without forward progress —\n * reaches `maxConsecutiveFailures`; any forward progress resets the count.\n * `attempt` is the connection that just ended; the next dial is `attempt + 1`.\n */\nexport interface StreamRedialTransportEvent extends StreamTransportEventBase {\n type: \"ws:redial\";\n consecutiveFailures: number;\n maxConsecutiveFailures: number;\n backoffMs: number;\n}\n\n// --- Turn segments (one doGenerate/doStream call each) -----------------------\n\ninterface SegmentTransportEventBase extends TransportEventBase {\n /** 1-based segment counter per model instance. */\n segment: number;\n /** Undefined on a first turn's start until the chat is created. */\n chatId?: string;\n /**\n * The reader (`streamChatEvents` call — the `reader` on `ws:*` events) that\n * served this segment, correlating it with the connection's `ws:*` events.\n * Absent on `segment:start`, which is emitted before the segment acquires\n * its stream (a fresh turn dials only once the chat exists, and even a\n * stream retained by a client-tool pause can still be replaced — stamping a\n * predicted reader could lie), and on a settle when the segment failed\n * before acquiring one. Segments of one turn share the reader: the stream\n * is retained across `requires_action` pauses (#44).\n */\n reader?: number;\n}\n\n/**\n * A segment (one model round-trip: submission plus the server run until it\n * settles or pauses for a client tool) started. A turn that drives client\n * tools spans several segments.\n */\nexport interface SegmentStartTransportEvent extends SegmentTransportEventBase {\n type: \"segment:start\";\n}\n\n/**\n * The segment ended. Exactly one per start:\n * - settled cleanly → `status` is the turn's terminal chat status and\n * `finishReason` the unified AI SDK finish reason (`tool-calls` marks a\n * client-tool pause; the turn continues with a resume segment);\n * - failed (abort, timeout, transport failure, server error) → `error`,\n * plus `status` when the server run itself still settled terminally\n * (e.g. `status: \"error\"` streamed alongside the error);\n * - torn down before settling (consumer canceled the stream) → neither.\n */\nexport interface SegmentSettleTransportEvent extends SegmentTransportEventBase {\n type: \"segment:settle\";\n status?: ChatStatus;\n finishReason?: LanguageModelV4FinishReason[\"unified\"];\n error?: { name: string; message: string };\n durationMs: number;\n}\n\n/** Every transport observability event, discriminated on `type`. */\nexport type CoderTransportEvent =\n | HttpRequestTransportEvent\n | HttpResponseTransportEvent\n | HttpErrorTransportEvent\n | StreamDialTransportEvent\n | StreamOpenTransportEvent\n | StreamEventTransportEvent\n | StreamCloseTransportEvent\n | StreamErrorTransportEvent\n | StreamRedialTransportEvent\n | SegmentStartTransportEvent\n | SegmentSettleTransportEvent;\n\n/**\n * Subscriber for {@link CoderTransportEvent}s. Exceptions are swallowed —\n * including an `async` handler's rejection (a function returning a promise is\n * assignable to this void-returning signature, and its rejection must not\n * become an unhandled rejection either).\n */\nexport type TransportEventHandler = (event: CoderTransportEvent) => void;\n\n/**\n * Wraps a subscriber so its exceptions can never reach the transport or a\n * turn. Returns `undefined` when there is no subscriber — emit sites guard on\n * that, so no event objects are allocated without one (internal).\n */\nexport function safeTransportEmitter(\n handler: TransportEventHandler | undefined,\n): TransportEventHandler | undefined {\n if (!handler) return undefined;\n return (event) => {\n try {\n // The signature is void-returning, but TypeScript accepts an `async`\n // handler there — its rejection would land AFTER this try/catch and\n // become an unhandled rejection (killing the process on Node).\n // `Promise.resolve` adopts a returned thenable, so both a rejected\n // async handler and a sync throw (caught below, before the promise\n // wrapping) are silenced the same way. The handler still runs\n // synchronously; only its failure handling is deferred.\n void Promise.resolve(handler(event)).catch(() => {});\n } catch {\n // Observability must never alter turn outcomes: a throwing subscriber\n // is deliberately silenced rather than surfaced into the pipeline.\n }\n };\n}\n","import { CoderAgentError, CoderApiError } from \"../errors.js\";\nimport { type FileContent, resolveFileContent } from \"../files.js\";\nimport {\n type Chat,\n CHAT_ATTACHMENT_MEDIA_TYPES,\n type ChatMessagesResponse,\n type ChatModelConfig,\n type ChatStreamEvent,\n type ChatWatchEvent,\n type CreateChatMessageRequest,\n type CreateChatMessageResponse,\n type CreateChatRequest,\n MAX_CHAT_FILE_SIZE_BYTES,\n type OrganizationChatModelsResponse,\n type SubmitToolResultsRequest,\n type UpdateChatRequest,\n type UploadChatFileResponse,\n} from \"./types.js\";\nimport { streamChatEvents, watchChatEvents, type WebSocketFactory } from \"./ws.js\";\nimport {\n type CoderClientOperation,\n safeTransportEmitter,\n type TransportEventHandler,\n} from \"../transport-events.js\";\n\n/** A file to upload as a chat attachment. */\nexport interface ChatFileInput {\n content: FileContent;\n /**\n * Media type. Required unless `content` is a Blob/File with a non-empty `type`\n * — note `fs.openAsBlob()` returns a Blob with no type, so pass it explicitly there.\n */\n mediaType?: string;\n /** Original filename, surfaced to the model and UI. Defaults to a File's `name`. */\n name?: string;\n}\n\n/** A file that has been uploaded to chat-file storage. */\nexport interface UploadedChatFile {\n /** Server-assigned file id, referenced from message content via a `file` part. */\n id: string;\n mediaType: string;\n name?: string;\n}\n\nexport interface CoderChatClientOptions {\n /** Base URL of the Coder deployment, e.g. `https://dogfood.cdr.dev`. */\n baseUrl: string;\n /** Coder API token or session token (sent as `Coder-Session-Token`). */\n token: string;\n /** Custom fetch implementation (defaults to global `fetch`). */\n fetch?: typeof globalThis.fetch;\n /** Custom WebSocket factory (defaults to the `ws` package on Node). */\n webSocketFactory?: WebSocketFactory;\n /**\n * Observability hook: receives typed transport events (HTTP request/response\n * timings, per-chat stream WebSocket lifecycle). Exceptions it throws are\n * swallowed; without it, no event objects are allocated. See\n * {@link CoderTransportEvent}.\n */\n onTransportEvent?: TransportEventHandler;\n}\n\nconst API_PREFIX = \"/api/v2/chats\";\nconst LEGACY_API_PREFIX = \"/api/experimental/chats\";\n\n/**\n * A thin, typed client for Coder's chat API, using stable routes with a\n * per-instance experimental-prefix fallback for deployments before v2.37.0.\n */\nexport class CoderChatClient {\n readonly baseUrl: string;\n readonly #token: string;\n readonly #fetch: typeof globalThis.fetch;\n readonly #webSocketFactory: WebSocketFactory | undefined;\n /** The raw subscriber, forwarded to stream readers (which wrap it themselves). */\n readonly #onTransportEvent: TransportEventHandler | undefined;\n /** Exception-isolated emitter for this client's own HTTP events. */\n readonly #emitTransportEvent: TransportEventHandler | undefined;\n #httpSeq = 0;\n #apiPrefix: typeof API_PREFIX | typeof LEGACY_API_PREFIX | undefined;\n\n constructor(options: CoderChatClientOptions) {\n // Normalize: strip a single trailing slash.\n this.baseUrl = options.baseUrl.replace(/\\/$/, \"\");\n this.#token = options.token;\n this.#fetch = options.fetch ?? globalThis.fetch.bind(globalThis);\n this.#webSocketFactory = options.webSocketFactory;\n this.#onTransportEvent = options.onTransportEvent;\n this.#emitTransportEvent = safeTransportEmitter(options.onTransportEvent);\n }\n\n /**\n * Issue a request and return the raw `Response` on success (body unread). On a\n * non-2xx status the body is consumed to build a {@link CoderApiError}. Shared\n * by JSON requests and the raw upload/download endpoints. `op` names the\n * public client method on the exchange's `http:*` events (issue #112) — the\n * emit site is shared, so each call site threads its own literal.\n */\n async #sendOnce(\n op: CoderClientOperation,\n method: string,\n path: string,\n opts?: { body?: BodyInit; headers?: Record<string, string>; signal?: AbortSignal },\n ): Promise<Response> {\n const headers: Record<string, string> = {\n \"Coder-Session-Token\": this.#token,\n ...opts?.headers,\n };\n const init: RequestInit = { method, headers, body: opts?.body, signal: opts?.signal };\n // A streaming request body requires half-duplex mode on Node's fetch (undici).\n if (typeof ReadableStream !== \"undefined\" && opts?.body instanceof ReadableStream) {\n (init as RequestInit & { duplex?: \"half\" }).duplex = \"half\";\n }\n\n // Observability: request/response/error events, guarded so nothing is\n // allocated without a subscriber. NOTE: headers are deliberately excluded\n // from the events (the auth token travels in `Coder-Session-Token`).\n const emit = this.#emitTransportEvent;\n let requestId = 0;\n let startedAt = 0;\n if (emit) {\n requestId = ++this.#httpSeq;\n startedAt = performance.now();\n emit({ type: \"http:request\", id: requestId, op, method, path, timestamp: Date.now() });\n }\n let res: Response;\n try {\n res = await this.#fetch(`${this.baseUrl}${path}`, init);\n } catch (err) {\n emit?.({\n type: \"http:error\",\n id: requestId,\n op,\n method,\n path,\n message: err instanceof Error ? err.message : String(err),\n durationMs: performance.now() - startedAt,\n timestamp: Date.now(),\n });\n throw err;\n }\n emit?.({\n type: \"http:response\",\n id: requestId,\n op,\n method,\n path,\n status: res.status,\n ok: res.ok,\n durationMs: performance.now() - startedAt,\n timestamp: Date.now(),\n });\n if (!res.ok) {\n const errObj = (await this.#json<{ message?: string; detail?: string }>(res)) ?? {};\n throw new CoderApiError({\n status: res.status,\n method,\n path,\n message: errObj.message ?? res.statusText ?? \"request failed\",\n detail: errObj.detail,\n });\n }\n return res;\n }\n\n /**\n * Negotiate promoted chat routes from responses, not buildinfo versions:\n * v2.37.0 introduced /api/v2/chats (coder/coder#28496), while patched older\n * releases still only mount /api/experimental/chats. Retry a v2 404 once on\n * experimental; the first non-404 HTTP response locks the prefix for this\n * client. Network/abort failures cannot establish route support. Two 404s\n * preserve the original v2 error (possibly a missing resource) and leave\n * negotiation open for the next call. A locked client never falls back.\n *\n * Concurrent initial calls may probe in parallel; a late response cannot\n * overwrite the first lock. Model listings use separate routes/fallbacks\n * and must neither consult nor establish this chat-prefix lock.\n */\n async #send(\n op: CoderClientOperation,\n method: string,\n path: string,\n opts?: { body?: BodyInit; headers?: Record<string, string>; signal?: AbortSignal },\n ): Promise<Response> {\n if (path !== API_PREFIX && !path.startsWith(`${API_PREFIX}/`)) {\n return this.#sendOnce(op, method, path, opts);\n }\n const suffix = path.slice(API_PREFIX.length);\n if (this.#apiPrefix) {\n return this.#sendOnce(op, method, `${this.#apiPrefix}${suffix}`, opts);\n }\n try {\n const res = await this.#sendOnce(op, method, path, opts);\n this.#apiPrefix ??= API_PREFIX;\n return res;\n } catch (err) {\n if (!(err instanceof CoderApiError)) throw err;\n if (err.status !== 404) {\n this.#apiPrefix ??= API_PREFIX;\n throw err;\n }\n // Another initial request may have established v2 while this one ran.\n if (this.#apiPrefix === API_PREFIX) throw err;\n try {\n const res = await this.#sendOnce(op, method, `${LEGACY_API_PREFIX}${suffix}`, opts);\n this.#apiPrefix ??= LEGACY_API_PREFIX;\n return res;\n } catch (legacyErr) {\n if (legacyErr instanceof CoderApiError) {\n if (legacyErr.status === 404) throw err;\n this.#apiPrefix ??= LEGACY_API_PREFIX;\n }\n throw legacyErr;\n }\n }\n }\n\n /**\n * Resolve before a WebSocket dial or a non-replayable streaming upload. A\n * read-only listing avoids consuming the upload body and works even without\n * a chat id (/watch). The WS reader supplies its internal cancellation signal\n * so return()/throw()/async disposal can also cancel an in-flight probe.\n */\n async #resolveApiPrefix(op: CoderClientOperation, signal?: AbortSignal): Promise<string> {\n if (!this.#apiPrefix) {\n const res = await this.#send(op, \"GET\", API_PREFIX, { signal });\n await res.body?.cancel();\n }\n if (!this.#apiPrefix) throw new CoderAgentError(\"Chat route probe succeeded without a prefix\");\n return this.#apiPrefix;\n }\n\n /** Read a response body as JSON, tolerating an unreadable/empty/malformed body (→ undefined). */\n async #json<T>(res: Response): Promise<T | undefined> {\n const text = await res.text().catch(() => \"\");\n if (text.length === 0) return undefined;\n try {\n return JSON.parse(text) as T;\n } catch {\n return undefined;\n }\n }\n\n async #request<T>(\n op: CoderClientOperation,\n method: string,\n path: string,\n body?: unknown,\n signal?: AbortSignal,\n ): Promise<T> {\n const res = await this.#send(op, method, path, {\n body: body === undefined ? undefined : JSON.stringify(body),\n headers: body === undefined ? undefined : { \"Content-Type\": \"application/json\" },\n signal,\n });\n return (await this.#json<T>(res)) as T;\n }\n\n // --- REST -----------------------------------------------------------------\n\n /**\n * List the organization's model configs, normalized to\n * {@link ChatModelConfig} (the legacy per-model `provider` string is joined\n * from the response's provider descriptors). See {@link #fetchModelConfigs}\n * for the endpoint and version-skew fallback.\n */\n listModelConfigs(organizationId: string, signal?: AbortSignal): Promise<ChatModelConfig[]>;\n /**\n * @deprecated The deployment-scoped `GET /api/experimental/chats/model-configs`\n * route this overload calls was removed upstream (coder/coder#28632) and\n * returns 404 on current deployments — pass an `organizationId`.\n */\n listModelConfigs(signal?: AbortSignal): Promise<ChatModelConfig[]>;\n listModelConfigs(\n organizationIdOrSignal?: string | AbortSignal,\n maybeSignal?: AbortSignal,\n ): Promise<ChatModelConfig[]> {\n const organizationId =\n typeof organizationIdOrSignal === \"string\" ? organizationIdOrSignal : undefined;\n const signal =\n typeof organizationIdOrSignal === \"string\" ? maybeSignal : organizationIdOrSignal;\n return this.#fetchModelConfigs(\"listModelConfigs\", organizationId, signal);\n }\n\n createChat(req: CreateChatRequest, signal?: AbortSignal): Promise<Chat> {\n return this.#request<Chat>(\"createChat\", \"POST\", API_PREFIX, req, signal);\n }\n\n getChat(chatId: string, signal?: AbortSignal): Promise<Chat> {\n return this.#request<Chat>(\"getChat\", \"GET\", `${API_PREFIX}/${chatId}`, undefined, signal);\n }\n\n createChatMessage(\n chatId: string,\n req: CreateChatMessageRequest,\n signal?: AbortSignal,\n ): Promise<CreateChatMessageResponse> {\n return this.#request<CreateChatMessageResponse>(\n \"createChatMessage\",\n \"POST\",\n `${API_PREFIX}/${chatId}/messages`,\n req,\n signal,\n );\n }\n\n getMessages(\n chatId: string,\n opts?: { before_id?: number; after_id?: number; limit?: number },\n signal?: AbortSignal,\n ): Promise<ChatMessagesResponse> {\n const params = new URLSearchParams();\n if (opts?.before_id !== undefined) params.set(\"before_id\", String(opts.before_id));\n if (opts?.after_id !== undefined) params.set(\"after_id\", String(opts.after_id));\n if (opts?.limit !== undefined) params.set(\"limit\", String(opts.limit));\n const q = params.toString();\n return this.#request<ChatMessagesResponse>(\n \"getMessages\",\n \"GET\",\n `${API_PREFIX}/${chatId}/messages${q ? `?${q}` : \"\"}`,\n undefined,\n signal,\n );\n }\n\n submitToolResults(\n chatId: string,\n req: SubmitToolResultsRequest,\n signal?: AbortSignal,\n ): Promise<void> {\n return this.#request<void>(\n \"submitToolResults\",\n \"POST\",\n `${API_PREFIX}/${chatId}/tool-results`,\n req,\n signal,\n );\n }\n\n /**\n * Interrupt the chat's in-flight run (`POST /interrupt`). The server flips\n * an active run to `interrupting` and responds immediately with the updated\n * chat — it does not wait for the run to actually stop.\n *\n * Pass `{ wait: true }` to send `?wait=true`, asking the server to hold the\n * response until the run has stopped. Coder servers without that param\n * ignore the unknown query harmlessly and still return immediately, so\n * callers should be prepared to confirm completion via the event stream.\n *\n * The second parameter also accepts a bare `AbortSignal` (the historical\n * signature).\n */\n interruptChat(\n chatId: string,\n optsOrSignal?: AbortSignal | { wait?: boolean; signal?: AbortSignal },\n ): Promise<Chat> {\n const opts = optsOrSignal instanceof AbortSignal ? { signal: optsOrSignal } : optsOrSignal;\n const query = opts?.wait ? \"?wait=true\" : \"\";\n return this.#request<Chat>(\n \"interruptChat\",\n \"POST\",\n `${API_PREFIX}/${chatId}/interrupt${query}`,\n undefined,\n opts?.signal,\n );\n }\n\n /**\n * Withdraw a QUEUED (not yet materialized) message from the chat's\n * submission queue (`DELETE /queue/{id}`). `queuedMessageId` is the\n * queue-entry id from `CreateChatMessageResponse.queued_message` — a\n * different id space from committed chat message ids. Rejects with a\n * {@link CoderApiError}: 404 when the entry no longer exists (already\n * promoted into history, or deleted), 409 when the chat has no queue.\n */\n deleteQueuedMessage(\n chatId: string,\n queuedMessageId: number,\n signal?: AbortSignal,\n ): Promise<void> {\n return this.#request<void>(\n \"deleteQueuedMessage\",\n \"DELETE\",\n `${API_PREFIX}/${chatId}/queue/${queuedMessageId}`,\n undefined,\n signal,\n );\n }\n\n updateChat(chatId: string, req: UpdateChatRequest, signal?: AbortSignal): Promise<void> {\n return this.#request<void>(\"updateChat\", \"PATCH\", `${API_PREFIX}/${chatId}`, req, signal);\n }\n\n /** Convenience: archive a chat (soft-hide; safe for cleanup). */\n archiveChat(chatId: string, signal?: AbortSignal): Promise<void> {\n // Issues updateChat's PATCH directly so its `http:*` events carry its own\n // `op` — the point of the stamp is the caller's intent, not the wire shape.\n return this.#request<void>(\n \"archiveChat\",\n \"PATCH\",\n `${API_PREFIX}/${chatId}`,\n { archived: true } satisfies UpdateChatRequest,\n signal,\n );\n }\n\n // --- Files ----------------------------------------------------------------\n\n /**\n * Upload a file as a durable chat attachment, returning its id (referenced\n * from message content via a `file` input part). The server enforces a narrow\n * media-type allowlist and a 10 MiB cap; both are checked client-side first so\n * unsupported files fail fast with a clear error instead of an opaque 4xx.\n *\n * The body is sent raw (not multipart): `Content-Type` carries the media type\n * and `Content-Disposition` the filename, mirroring `codersdk`.\n */\n async uploadChatFile(\n organizationId: string,\n file: ChatFileInput,\n signal?: AbortSignal,\n ): Promise<UploadedChatFile> {\n const resolved = resolveFileContent(file.content, {\n mediaType: file.mediaType,\n name: file.name,\n });\n // Match the allowlist on the media type alone, ignoring parameters such as\n // `; charset=utf-8` that a Blob/File commonly carries.\n const mediaType = (resolved.mediaType.split(\";\")[0] ?? resolved.mediaType).trim().toLowerCase();\n if (!CHAT_ATTACHMENT_MEDIA_TYPES.has(mediaType)) {\n throw new CoderAgentError(\n `Media type \"${mediaType}\" is not allowed for chat attachments ` +\n `(allowed: ${[...CHAT_ATTACHMENT_MEDIA_TYPES].join(\", \")}). ` +\n `Write other file types to a workspace instead.`,\n );\n }\n if (resolved.size !== undefined && resolved.size > MAX_CHAT_FILE_SIZE_BYTES) {\n throw new CoderAgentError(\n `File is ${resolved.size} bytes, over the ${MAX_CHAT_FILE_SIZE_BYTES}-byte ` +\n `(10 MiB) chat attachment limit. Write large files to a workspace instead.`,\n );\n }\n\n const headers: Record<string, string> = { \"Content-Type\": mediaType };\n if (resolved.name) {\n // RFC 6266: a sanitized ASCII `filename` plus an RFC 5987 `filename*` that\n // preserves the exact name. Both are pure ASCII, so a non-Latin-1 name\n // (CJK, emoji, accents) can't trip fetch's ByteString header check, and\n // CR/LF/quote/backslash are encoded away rather than breaking the header.\n const ascii = resolved.name.replace(/[^\\x20-\\x7e]/g, \"_\").replace(/([\"\\\\])/g, \"\\\\$1\");\n const utf8 = encodeURIComponent(resolved.name).replace(\n /['()*]/g,\n (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`,\n );\n headers[\"Content-Disposition\"] = `attachment; filename=\"${ascii}\"; filename*=UTF-8''${utf8}`;\n }\n if (resolved.body instanceof ReadableStream) {\n await this.#resolveApiPrefix(\"uploadChatFile\", signal);\n }\n const res = await this.#send(\n \"uploadChatFile\",\n \"POST\",\n `${API_PREFIX}/files?organization=${encodeURIComponent(organizationId)}`,\n { body: resolved.body, headers, signal },\n );\n const parsed = await this.#json<UploadChatFileResponse>(res);\n if (!parsed?.id) {\n throw new CoderApiError({\n status: res.status,\n method: \"POST\",\n path: `${this.#apiPrefix}/files`,\n message: \"upload succeeded but the response contained no file id\",\n });\n }\n return { id: parsed.id, mediaType, name: resolved.name };\n }\n\n /** Download a chat file's bytes and media type by id. */\n async getChatFile(\n fileId: string,\n signal?: AbortSignal,\n ): Promise<{ bytes: Uint8Array; mediaType: string }> {\n const res = await this.#send(\"getChatFile\", \"GET\", `${API_PREFIX}/files/${fileId}`, {\n signal,\n });\n const buf = await res.arrayBuffer();\n return {\n bytes: new Uint8Array(buf),\n mediaType: res.headers.get(\"Content-Type\") ?? \"application/octet-stream\",\n };\n }\n\n // --- Streaming ------------------------------------------------------------\n\n streamEvents(\n chatId: string,\n opts?: {\n afterId?: number;\n signal?: AbortSignal;\n /** Pre-allocated reader id for `ws:*` events — see {@link streamChatEvents}. */\n reader?: number;\n },\n ): AsyncGenerator<ChatStreamEvent, void, void> {\n return streamChatEvents({\n baseUrl: this.baseUrl,\n token: this.#token,\n chatId,\n resolveApiPrefix: (signal) => this.#resolveApiPrefix(\"streamEvents\", signal),\n afterId: opts?.afterId,\n signal: opts?.signal,\n reader: opts?.reader,\n webSocketFactory: this.#webSocketFactory,\n onTransportEvent: this.#onTransportEvent,\n });\n }\n\n /**\n * Watch lifecycle events (status/title changes, creation, deletion, …) for\n * every chat visible to the authenticated user via the `/chats/watch`\n * WebSocket. An unresolved client first probes the REST chat listing;\n * probe failures propagate unless the reader was cancelled. Once connected,\n * dropped connections are redialed with exponential backoff. Aborting the\n * signal or ending iteration tears down the reader; a 4xx upgrade rejection\n * is a terminal {@link CoderApiError} (bad/expired token, or an older Coder\n * server without the endpoint → 404).\n */\n watchChats(opts?: { signal?: AbortSignal }): AsyncGenerator<ChatWatchEvent, void, void> {\n return watchChatEvents({\n baseUrl: this.baseUrl,\n token: this.#token,\n resolveApiPrefix: (signal) => this.#resolveApiPrefix(\"watchChats\", signal),\n signal: opts?.signal,\n webSocketFactory: this.#webSocketFactory,\n });\n }\n\n // --- Helpers --------------------------------------------------------------\n\n /**\n * Fetch the model listing for hint resolution / listing, tolerating version\n * skew in both directions:\n *\n * - Primary: `GET /api/v2/organizations/{organizationId}/chats/models` —\n * the organization-scoped route that replaced the legacy listing upstream\n * (coder/coder#28632); its response is normalized to the legacy\n * {@link ChatModelConfig} array by {@link joinOrgModels}.\n * - Newer SDK, older deployment: the org-scoped route does not exist yet,\n * so on a 404 this falls back once to the legacy\n * `GET /api/experimental/chats/model-configs`, whose body already is the\n * legacy array (parsed tolerantly: a non-array body yields no configs and\n * non-object entries are dropped). (Older SDK, newer deployment is the\n * reverse skew: the legacy route is a reserved 404 upstream, which is why\n * the org-scoped route is primary.)\n * - The deprecated no-`organizationId` overloads call the legacy route\n * directly, preserving their historical behavior.\n *\n * When both routes 404, the org-scoped error is rethrown — it names the\n * organization actually queried (e.g. a mistyped `organizationId`), while\n * the legacy route's generic \"Route not found\" would mask it.\n *\n * Both requests stamp the calling public method's `op` on the exchange's\n * `http:*` events — the stamp names the caller's intent, not the wire shape.\n */\n async #fetchModelConfigs(\n op: CoderClientOperation,\n organizationId: string | undefined,\n signal?: AbortSignal,\n ): Promise<ChatModelConfig[]> {\n const legacy = async (): Promise<ChatModelConfig[]> => {\n const raw: unknown = await this.#request<unknown>(\n op,\n \"GET\",\n `${LEGACY_API_PREFIX}/model-configs`,\n undefined,\n signal,\n );\n return (Array.isArray(raw) ? raw : []).filter(\n (c): c is ChatModelConfig => typeof c === \"object\" && c !== null,\n );\n };\n if (organizationId === undefined) return legacy();\n\n let res: unknown;\n try {\n res = await this.#request<unknown>(\n op,\n \"GET\",\n `/api/v2/organizations/${encodeURIComponent(organizationId)}/chats/models`,\n undefined,\n signal,\n );\n } catch (err) {\n if (!(err instanceof CoderApiError) || err.status !== 404) throw err;\n try {\n return await legacy();\n } catch (legacyErr) {\n if (legacyErr instanceof CoderApiError && legacyErr.status === 404) throw err;\n throw legacyErr;\n }\n }\n return joinOrgModels(res);\n }\n\n /**\n * Resolves a user-friendly model hint to a model-config UUID.\n *\n * Accepts: a config UUID (returned as-is), a `provider:model` id\n * (e.g. `anthropic:claude-haiku-4-5-20251001`), a bare model id, or a\n * display-name substring (case-insensitive). Returns `undefined` if no\n * match is found, in which case the caller should let chatd pick the default.\n *\n * Tolerates partial payloads from older/newer servers: a malformed listing\n * (empty body, non-array JSON, null entries) resolves as no match, and\n * entries missing `provider`, `model`, or `display_name` are matched on the\n * fields they do carry — an entry without `provider` still matches by its\n * model id, one without `model` only by display name.\n */\n resolveModelConfigId(\n hint: string,\n organizationId: string,\n signal?: AbortSignal,\n ): Promise<string | undefined>;\n /**\n * @deprecated The deployment-scoped `GET /api/experimental/chats/model-configs`\n * route this overload calls was removed upstream (coder/coder#28632) and\n * returns 404 on current deployments — pass an `organizationId`.\n */\n resolveModelConfigId(hint: string, signal?: AbortSignal): Promise<string | undefined>;\n async resolveModelConfigId(\n hint: string,\n organizationIdOrSignal?: string | AbortSignal,\n maybeSignal?: AbortSignal,\n ): Promise<string | undefined> {\n const organizationId =\n typeof organizationIdOrSignal === \"string\" ? organizationIdOrSignal : undefined;\n const signal =\n typeof organizationIdOrSignal === \"string\" ? maybeSignal : organizationIdOrSignal;\n const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n if (UUID_RE.test(hint)) return hint;\n\n const configs = await this.#fetchModelConfigs(\"resolveModelConfigId\", organizationId, signal);\n const lower = hint.toLowerCase();\n // `provider:model` form.\n const colon = hint.indexOf(\":\");\n const provider = colon >= 0 ? hint.slice(0, colon).toLowerCase() : undefined;\n const model = colon >= 0 ? hint.slice(colon + 1).toLowerCase() : lower;\n\n const candidates = configs.filter((c) => c.enabled !== false);\n const pool = candidates.length > 0 ? candidates : configs;\n\n // Guarded accessors: treat a missing/non-string field as absent.\n const modelOf = (c: ChatModelConfig) =>\n typeof c.model === \"string\" ? c.model.toLowerCase() : undefined;\n const providerOf = (c: ChatModelConfig) =>\n typeof c.provider === \"string\" ? c.provider.toLowerCase() : undefined;\n const displayNameOf = (c: ChatModelConfig) =>\n typeof c.display_name === \"string\" ? c.display_name.toLowerCase() : undefined;\n\n const exact = pool.find(\n (c) => modelOf(c) === model && (provider === undefined || providerOf(c) === provider),\n );\n if (exact) return exact.id;\n\n const byModel = pool.find((c) => modelOf(c) === model);\n if (byModel) return byModel.id;\n\n const byDisplay = pool.find((c) => displayNameOf(c)?.includes(lower));\n if (byDisplay) return byDisplay.id;\n\n const byModelSubstring = pool.find((c) => modelOf(c)?.includes(model));\n return byModelSubstring?.id;\n }\n}\n\n/**\n * Normalize an org-scoped models response to the legacy {@link ChatModelConfig}\n * array: join each model's `ai_provider_id` to its provider descriptor and\n * copy the descriptor's `type` onto the model as `provider`. Guarded against\n * partial payloads — non-object entries are dropped, and a model whose\n * provider cannot be joined keeps `provider` absent (hint matching then falls\n * back to its model id / display name).\n */\nfunction joinOrgModels(res: unknown): ChatModelConfig[] {\n const body = (\n typeof res === \"object\" && res !== null ? res : {}\n ) as OrganizationChatModelsResponse;\n const models = Array.isArray(body.models) ? body.models : [];\n const providers = Array.isArray(body.providers) ? body.providers : [];\n\n const providerTypeById = new Map<string, string>();\n for (const p of providers) {\n if (typeof p !== \"object\" || p === null) continue;\n if (typeof p.id === \"string\" && typeof p.type === \"string\") providerTypeById.set(p.id, p.type);\n }\n\n const configs: ChatModelConfig[] = [];\n for (const m of models) {\n if (typeof m !== \"object\" || m === null) continue;\n const config: ChatModelConfig = { ...m };\n if (m.reasoning_efforts !== undefined) config.reasoning_efforts = m.reasoning_efforts;\n const providerType =\n typeof m.ai_provider_id === \"string\" ? providerTypeById.get(m.ai_provider_id) : undefined;\n if (providerType !== undefined) config.provider = providerType;\n configs.push(config);\n }\n return configs;\n}\n","/**\n * Minimal helpers for the stable Coder v2 REST endpoints that the workspace\n * preview feature composes: workspace lookup, the wildcard app host, and port\n * sharing. Deliberately tiny — only the fields this package reads are modeled\n * — and mirroring `CoderChatClient`'s conventions (`Coder-Session-Token`\n * auth, `CoderApiError` on non-2xx). These are internal building blocks for\n * `CoderAgent.getPreview`/`sharePreview`; only their option/result types are\n * part of the public API.\n */\n\nimport { CoderAgentError, CoderApiError } from \"../errors.js\";\n\n/** Connection details for the stable v2 API (same credentials as the chat client). */\nexport interface WorkspaceApiConnection {\n /** Base URL of the Coder deployment, e.g. `https://dogfood.cdr.dev`. */\n baseUrl: string;\n /** Coder API/session token (sent as `Coder-Session-Token`). */\n token: string;\n /** Custom fetch implementation (defaults to global `fetch`). */\n fetch?: typeof globalThis.fetch;\n}\n\n/**\n * Subset of `codersdk.Workspace` (GET `/api/v2/workspaces/{workspace}`) that\n * the preview helpers read. Resources without agents omit the `agents` key\n * entirely, so the whole chain is treated as optional.\n */\nexport interface WorkspaceSummary {\n id: string;\n /** Username of the workspace owner (a subdomain URL segment). */\n owner_name: string;\n name: string;\n latest_build?: {\n resources?: { agents?: { name: string }[] }[];\n };\n}\n\n/** Response of GET `/api/v2/applications/host`; `host` is `\"\"` when subdomain apps are disabled. */\nexport interface AppHostResponse {\n /** Wildcard app host with a literal `*`, e.g. `*.apps.example.com`, possibly with a port. */\n host: string;\n}\n\n/** Share levels a port can be in; `owner` is the implicit default when no share exists. */\nexport type PortShareLevel = \"owner\" | \"authenticated\" | \"organization\" | \"public\";\n\n/**\n * Share levels the upsert endpoint accepts. `owner` is rejected server-side —\n * reverting to owner-only is done by deleting the share, not setting it.\n */\nexport type PreviewShareLevel = Exclude<PortShareLevel, \"owner\">;\n\n/** Protocol the workspace app speaks on the port (drives the `s` suffix in the URL). */\nexport type PortShareProtocol = \"http\" | \"https\";\n\n/** Body of POST `/api/v2/workspaces/{workspace}/port-share` (a true upsert on agent+port). */\nexport interface UpsertPortShareRequest {\n agent_name: string;\n port: number;\n share_level: PreviewShareLevel;\n protocol: PortShareProtocol;\n}\n\n/** A port-share row, mirroring `codersdk.WorkspaceAgentPortShare`. */\nexport interface WorkspaceAgentPortShare {\n workspace_id: string;\n agent_name: string;\n port: number;\n share_level: PortShareLevel;\n protocol: PortShareProtocol;\n}\n\n/**\n * Issue a JSON request against the v2 API. Mirrors `CoderChatClient`: the\n * session-token header, and a {@link CoderApiError} carrying the server's\n * `message`/`detail` on any non-2xx (body tolerated missing or malformed).\n */\nasync function request<T>(\n conn: WorkspaceApiConnection,\n method: string,\n path: string,\n body?: unknown,\n signal?: AbortSignal,\n): Promise<T> {\n const fetchFn = conn.fetch ?? globalThis.fetch.bind(globalThis);\n const headers: Record<string, string> = { \"Coder-Session-Token\": conn.token };\n if (body !== undefined) headers[\"Content-Type\"] = \"application/json\";\n\n const res = await fetchFn(`${conn.baseUrl.replace(/\\/$/, \"\")}${path}`, {\n method,\n headers,\n body: body === undefined ? undefined : JSON.stringify(body),\n signal,\n });\n const text = await res.text().catch(() => \"\");\n let parsed: unknown;\n try {\n parsed = text.length > 0 ? JSON.parse(text) : undefined;\n } catch {\n parsed = undefined;\n }\n if (!res.ok) {\n const errObj = (parsed ?? {}) as { message?: string; detail?: string };\n throw new CoderApiError({\n status: res.status,\n method,\n path,\n message: errObj.message ?? res.statusText ?? \"request failed\",\n detail: errObj.detail,\n });\n }\n return parsed as T;\n}\n\n/** Fetch a workspace (owner, name, agents) by UUID: GET `/api/v2/workspaces/{workspace}`. */\nexport function getWorkspace(\n conn: WorkspaceApiConnection,\n workspaceId: string,\n signal?: AbortSignal,\n): Promise<WorkspaceSummary> {\n return request<WorkspaceSummary>(\n conn,\n \"GET\",\n `/api/v2/workspaces/${workspaceId}`,\n undefined,\n signal,\n );\n}\n\n/**\n * Fetch the deployment's wildcard app host: GET `/api/v2/applications/host`.\n * The endpoint predates port sharing and exists on all supported servers;\n * an empty `host` means no wildcard access URL is configured.\n */\nexport function getAppsHost(\n conn: WorkspaceApiConnection,\n signal?: AbortSignal,\n): Promise<AppHostResponse> {\n return request<AppHostResponse>(conn, \"GET\", \"/api/v2/applications/host\", undefined, signal);\n}\n\n/**\n * Create or update a port share: POST `/api/v2/workspaces/{workspace}/port-share`\n * (an upsert keyed on agent+port; re-posting updates level/protocol in place).\n * A 404 is rethrown with a hint that the server may predate port sharing —\n * the whole route is absent on Coder servers older than ~v2.9.\n */\nexport async function upsertPortShare(\n conn: WorkspaceApiConnection,\n workspaceId: string,\n req: UpsertPortShareRequest,\n signal?: AbortSignal,\n): Promise<WorkspaceAgentPortShare> {\n const path = `/api/v2/workspaces/${workspaceId}/port-share`;\n try {\n return await request<WorkspaceAgentPortShare>(conn, \"POST\", path, req, signal);\n } catch (err) {\n if (err instanceof CoderApiError && err.status === 404) {\n throw new CoderApiError({\n status: 404,\n method: \"POST\",\n path,\n message: \"port sharing is unavailable\",\n detail:\n \"the workspace was not found, or this Coder server predates port sharing (added in Coder v2.9)\",\n });\n }\n throw err;\n }\n}\n\n/** Inputs for building a preview URL (see `CoderAgent.getPreview`). */\nexport interface ResolvePreviewOptions {\n workspaceId: string;\n /** Workspace port the app listens on. */\n port: number;\n /** Agent that serves the port; defaults to the workspace's only agent. */\n agentName?: string;\n /** Protocol the app speaks inside the workspace (not the browser scheme). Default `\"http\"`. */\n protocol?: PortShareProtocol;\n}\n\n/** The workspace's only agent's name, or a clear error naming the candidates. */\nfunction defaultAgentName(workspace: WorkspaceSummary): string {\n const agents = (workspace.latest_build?.resources ?? []).flatMap((r) => r.agents ?? []);\n const first = agents[0];\n if (agents.length === 1 && first) return first.name;\n if (agents.length === 0) {\n throw new CoderAgentError(\n `Workspace \"${workspace.name}\" has no agents in its latest build (is it running?); ` +\n `pass agentName explicitly.`,\n );\n }\n throw new CoderAgentError(\n `Workspace \"${workspace.name}\" has ${agents.length} agents ` +\n `(${agents.map((a) => a.name).join(\", \")}); pass agentName to pick one.`,\n );\n}\n\n/**\n * Ports must be integral, in TCP range, and — stricter than TCP — at least\n * 1000: Coder's subdomain app parser (`appurl.PortRegex`, `^\\d{4,5}s?$`) only\n * treats 4–5 digit labels as ports, so a URL for port 80 would be parsed as an\n * app named \"80\" and never resolve. Reject those up front instead of returning\n * a dead URL.\n */\nfunction assertValidPort(port: number): void {\n if (!Number.isInteger(port) || port < 1 || port > 65535) {\n throw new CoderAgentError(`Invalid port ${port}: expected an integer between 1 and 65535.`);\n }\n if (port < 1000) {\n throw new CoderAgentError(\n `Port ${port} cannot be previewed: Coder subdomain app URLs only encode 4-5 digit ports ` +\n `(1000-65535). Serve the preview on a higher port instead.`,\n );\n }\n}\n\n/**\n * Compose the subdomain preview URL for a workspace port the same way the\n * dashboard does: the label `{port}{s?}--{agent}--{workspace}--{owner}`\n * replaces the `*` in the deployment's wildcard app host, and the browser\n * scheme follows the deployment's own access URL. The trailing `s` (from\n * `protocol: \"https\"`) tells the proxy to speak TLS *to the workspace port*;\n * it does not affect the browser scheme.\n */\nexport async function resolveWorkspacePreview(\n conn: WorkspaceApiConnection,\n opts: ResolvePreviewOptions,\n signal?: AbortSignal,\n): Promise<{ url: string; agentName: string }> {\n assertValidPort(opts.port);\n const [workspace, appHost] = await Promise.all([\n getWorkspace(conn, opts.workspaceId, signal),\n getAppsHost(conn, signal),\n ]);\n if (!appHost.host) {\n throw new CoderAgentError(\n \"This Coder deployment has no wildcard app host configured (--wildcard-access-url), \" +\n \"so subdomain preview URLs are unavailable.\",\n );\n }\n const agentName = opts.agentName ?? defaultAgentName(workspace);\n const suffix = opts.protocol === \"https\" ? \"s\" : \"\";\n const label = `${opts.port}${suffix}--${agentName}--${workspace.name}--${workspace.owner_name}`;\n // The host keeps a literal `*` (possibly inside the first label, e.g.\n // `*--apps.example.com`) and may carry an explicit port on dev deployments —\n // substitute the label into the whole host string, dashboard-style.\n const scheme = new URL(conn.baseUrl).protocol;\n return { url: `${scheme}//${appHost.host.replace(/\\*/g, label)}`, agentName };\n}\n\n/** Inputs for sharing and previewing a workspace port (see `CoderAgent.sharePreview`). */\nexport interface SharePreviewRequest extends ResolvePreviewOptions {\n /** Who may open the port. Default `\"authenticated\"`. */\n shareLevel?: PreviewShareLevel;\n}\n\n/**\n * Resolve a port's preview URL, then upsert its share level so the URL is\n * reachable beyond the workspace owner. Returns the server-confirmed level\n * (falling back to the requested one when the server omits it).\n */\nexport async function shareWorkspacePreview(\n conn: WorkspaceApiConnection,\n opts: SharePreviewRequest,\n signal?: AbortSignal,\n): Promise<{ url: string; shareLevel: PreviewShareLevel }> {\n const requested = opts.shareLevel ?? \"authenticated\";\n const { url, agentName } = await resolveWorkspacePreview(conn, opts, signal);\n const share = await upsertPortShare(\n conn,\n opts.workspaceId,\n {\n agent_name: agentName,\n port: opts.port,\n share_level: requested,\n protocol: opts.protocol ?? \"http\",\n },\n signal,\n );\n // Old servers may answer with an empty/partial body; trust it when present.\n const level = share?.share_level && share.share_level !== \"owner\" ? share.share_level : requested;\n return { url, shareLevel: level };\n}\n","import type {\n LanguageModelV4CallOptions,\n LanguageModelV4Message,\n LanguageModelV4Prompt,\n LanguageModelV4ToolResultOutput,\n SharedV4FileData,\n} from \"@ai-sdk/provider\";\nimport type { ChatInputPart, DynamicTool, ToolResult } from \"../coder/types.js\";\n\n/** Namespace key for this package's options on AI SDK message-part `providerOptions`. */\nexport const CODER_PROVIDER_OPTIONS = \"coder\";\n\n/** Shape of this package's `providerOptions.coder` payload on a file part. */\nexport interface CoderFileProviderOptions {\n /** A pre-uploaded chat-file id to reuse instead of uploading the part's bytes. */\n fileId?: string;\n}\n\n/** Concatenated system messages, mapped to chatd's `system_prompt`. */\nexport function extractSystemPrompt(prompt: LanguageModelV4Prompt): string | undefined {\n const parts = prompt.filter(\n (m): m is Extract<LanguageModelV4Message, { role: \"system\" }> => m.role === \"system\",\n );\n if (parts.length === 0) return undefined;\n const joined = parts\n .map((m) => m.content)\n .join(\"\\n\\n\")\n .trim();\n return joined.length > 0 ? joined : undefined;\n}\n\n/** The content array of a user message (text and file parts). */\nexport type UserContent = Extract<LanguageModelV4Message, { role: \"user\" }>[\"content\"];\n\n/**\n * Uploads a file part's bytes and resolves to its chatd file id. Supplied by\n * the language model (which holds the client + organization id); kept abstract\n * here so {@link userContentToInputParts} stays pure and testable.\n */\nexport type FilePartUploader = (file: {\n data: SharedV4FileData;\n mediaType: string;\n filename?: string;\n}) => Promise<string>;\n\n/** Reads a pre-uploaded file id from a file part's `providerOptions.coder.fileId`. */\nfunction coderFileId(providerOptions: unknown): string | undefined {\n const ns = (providerOptions as Record<string, CoderFileProviderOptions> | undefined)?.[\n CODER_PROVIDER_OPTIONS\n ];\n // Require a non-empty id: an empty string is not a usable reference, so fall\n // back to uploading rather than emitting `file_id: \"\"`.\n return typeof ns?.fileId === \"string\" && ns.fileId.length > 0 ? ns.fileId : undefined;\n}\n\n/**\n * Maps a user message's content to chatd input parts. Text passes through;\n * file parts are turned into `file` parts referencing an uploaded file id —\n * either reused from `providerOptions.coder.fileId` (a pre-uploaded handle) or\n * uploaded on the fly via {@link FilePartUploader}.\n */\nexport async function userContentToInputParts(\n content: UserContent,\n uploadFile: FilePartUploader,\n): Promise<ChatInputPart[]> {\n // Uploads are independent, so run them concurrently; `map` preserves order.\n const parts = await Promise.all(\n content.map(async (part): Promise<ChatInputPart | null> => {\n if (part.type === \"text\") {\n return part.text.length > 0 ? { type: \"text\", text: part.text } : null;\n }\n if (part.type === \"file\") {\n const fileId =\n coderFileId(part.providerOptions) ??\n (await uploadFile({\n data: part.data,\n mediaType: part.mediaType,\n filename: part.filename,\n }));\n return { type: \"file\", file_id: fileId };\n }\n return null;\n }),\n );\n return parts.filter((p): p is ChatInputPart => p !== null);\n}\n\nfunction toolResultOutputToChatd(output: LanguageModelV4ToolResultOutput): {\n value: unknown;\n isError: boolean;\n} {\n switch (output.type) {\n case \"text\":\n return { value: output.value, isError: false };\n case \"json\":\n return { value: output.value, isError: false };\n case \"error-text\":\n return { value: output.value, isError: true };\n case \"error-json\":\n return { value: output.value, isError: true };\n case \"execution-denied\":\n return { value: { execution_denied: true, reason: output.reason }, isError: true };\n case \"content\":\n return { value: output.value, isError: false };\n default:\n return { value: output, isError: false };\n }\n}\n\nexport type TurnAction =\n | { kind: \"new-turn\"; content: UserContent }\n | { kind: \"resume\"; toolResults: ToolResult[] }\n | { kind: \"noop\" };\n\n/**\n * Decides what to do with a fresh `doStream`/`doGenerate` prompt:\n * - trailing `tool` message(s) → resume the in-flight chatd turn by submitting\n * those tool results (the AI SDK executed a client tool between steps);\n * - otherwise the trailing `user` message → a new turn.\n */\nexport function classifyTurnAction(prompt: LanguageModelV4Prompt): TurnAction {\n if (prompt.length === 0) return { kind: \"noop\" };\n const last = prompt[prompt.length - 1];\n if (!last) return { kind: \"noop\" };\n\n if (last.role === \"tool\") {\n const toolResults: ToolResult[] = [];\n // Collect from all trailing consecutive tool messages.\n for (let i = prompt.length - 1; i >= 0; i--) {\n const m = prompt[i];\n if (!m || m.role !== \"tool\") break;\n for (const part of m.content) {\n if (part.type !== \"tool-result\") continue;\n const { value, isError } = toolResultOutputToChatd(part.output);\n toolResults.unshift({ tool_call_id: part.toolCallId, output: value, is_error: isError });\n }\n }\n return { kind: \"resume\", toolResults };\n }\n\n if (last.role === \"user\") {\n return { kind: \"new-turn\", content: last.content };\n }\n\n return { kind: \"noop\" };\n}\n\n/** Maps AI SDK function tools to chatd client-executed (\"dynamic\") tools. */\nexport function toolsToDynamicTools(tools: LanguageModelV4CallOptions[\"tools\"]): DynamicTool[] {\n if (!tools) return [];\n const out: DynamicTool[] = [];\n for (const tool of tools) {\n if (tool.type !== \"function\") continue; // provider tools are not forwarded\n out.push({\n name: tool.name,\n description: tool.description,\n input_schema: tool.inputSchema,\n });\n }\n return out;\n}\n\n/** The set of tool names the AI SDK side owns (i.e. client-executed). */\nexport function dynamicToolNames(tools: LanguageModelV4CallOptions[\"tools\"]): Set<string> {\n const names = new Set<string>();\n for (const tool of tools ?? []) {\n if (tool.type === \"function\") names.add(tool.name);\n }\n return names;\n}\n","import { CoderAgentError } from \"../errors.js\";\nimport type { ChatStreamEvent } from \"../coder/types.js\";\n\n/**\n * A chat event stream retained across turn segments (issue #44).\n *\n * One `doGenerate`/`doStream` call is one *segment* of a chatd turn; a turn\n * that drives client tools spans several segments (each `requires_action`\n * pause hands control back to the AI SDK, which executes the tools and calls\n * the model again with the results). Before this wrapper existed each segment\n * dialed its own WebSocket and closed it on settle — one TLS + upgrade\n * handshake per tool round trip, plus a between-connections window where\n * events had to be recovered via `after_id` catch-up on the next dial.\n *\n * This class owns the turn-lifetime stream instead:\n *\n * - It wraps the self-redialing reader (`streamChatEvents` via\n * `CoderChatClient.streamEvents`), dialed lazily with the STREAM's own\n * lifetime signal — never a segment's abort signal, so a segment\n * abort/timeout cannot tear the socket down.\n * - Reads are memoized: at most one `next()` is outstanding on the underlying\n * generator, and a read that a detached segment abandoned (it lost a race\n * against its abort signal, a grace timer, or the REST recovery fetch) is\n * adopted by the next segment via {@link read} — the event it eventually\n * resolves with is delivered, never dropped.\n * - Lifecycle is a small state machine: `attached` (a segment is consuming) →\n * {@link pause} on a healthy `requires_action` settle (reusable by the\n * resume segment) → {@link attach} or {@link close}. Every other segment\n * exit closes the stream — an aborted/timed-out segment's stream could\n * never be reused (its partially consumed in-progress episode cannot be\n * safely re-read), so it is not kept. Closing is idempotent; the socket\n * also ends on session teardown (`resetSession()` /\n * `[Symbol.asyncDispose]()`) or replacement by the next turn's fresh\n * stream.\n *\n * The redial machinery inside the wrapped reader keeps working while paused:\n * {@link pause} leaves a read outstanding, so a socket drop during tool\n * execution is redialed (from the turn's original cursor) in the background\n * instead of adding reconnect latency to the resume segment.\n */\nexport class SessionChatStream {\n readonly chatId: string;\n /**\n * The reader id stamped on the wrapped reader's `ws:*` transport events —\n * the model stamps it on `segment:settle` so segments correlate with the\n * connection that served them (issue #94).\n */\n readonly reader: number;\n readonly #events: AsyncGenerator<ChatStreamEvent, void, void>;\n readonly #controller = new AbortController();\n\n #pending: Promise<IteratorResult<ChatStreamEvent, void>> | undefined;\n #attached = true;\n #reusable = false;\n #closePromise: Promise<void> | undefined;\n\n constructor(opts: {\n chatId: string;\n /** The reader id `open` passes to the underlying `streamChatEvents` call. */\n reader: number;\n /** Opens the underlying reader, bound to the stream's lifetime signal. */\n open: (signal: AbortSignal) => AsyncGenerator<ChatStreamEvent, void, void>;\n }) {\n this.chatId = opts.chatId;\n this.reader = opts.reader;\n this.#events = opts.open(this.#controller.signal);\n }\n\n get closed(): boolean {\n return this.#closePromise !== undefined;\n }\n\n /** Whether a segment is currently consuming this stream. */\n get attached(): boolean {\n return this.#attached;\n }\n\n /** Whether a resume segment for `chatId` may {@link attach}. */\n canAttach(chatId: string): boolean {\n return !this.closed && this.#reusable && this.chatId === chatId;\n }\n\n /**\n * The next event, memoized: repeated calls return the SAME promise until\n * {@link consumed} acknowledges delivery, so a read abandoned by one segment\n * is handed to the next instead of losing its event.\n */\n read(): Promise<IteratorResult<ChatStreamEvent, void>> {\n if (!this.#pending) {\n if (!this.#attached && !this.closed) {\n // Reads belong to segments; only the paused prefetch (issued by\n // pause() itself) may exist between segments.\n throw new CoderAgentError(\"SessionChatStream.read() without an attached segment\");\n }\n this.#pending = this.#events.next();\n // Tag as handled: a rejection can land while no segment is racing this\n // read (detached, or the segment lost a race); the next consumer still\n // observes it by awaiting read().\n this.#pending.catch(() => {});\n }\n return this.#pending;\n }\n\n /**\n * Acknowledge that the segment received `p`'s result and the next\n * {@link read} should issue a fresh read. A no-op unless `p` is the\n * outstanding read — a raced-out segment never consumes.\n */\n consumed(p: Promise<IteratorResult<ChatStreamEvent, void>>): void {\n if (this.#pending === p) this.#pending = undefined;\n }\n\n /** Attach the resume segment to a stream paused at `requires_action`. */\n attach(): void {\n if (!this.canAttach(this.chatId) || this.#attached) {\n throw new CoderAgentError(\"SessionChatStream.attach() on a non-reusable stream\");\n }\n this.#attached = true;\n this.#reusable = false;\n }\n\n /**\n * Segment settled at a healthy `requires_action` pause: keep the stream for\n * the tool-result resume, with a read outstanding so the reader's redial\n * machinery stays live while the client tools execute.\n */\n pause(): void {\n this.#attached = false;\n if (this.closed) return;\n this.#reusable = true;\n if (!this.#pending) {\n this.#pending = this.#events.next();\n this.#pending.catch(() => {});\n }\n }\n\n /**\n * Close the underlying reader (and socket). Idempotent; safe on a stream\n * whose generator already ended or threw.\n */\n close(): Promise<void> {\n this.#closePromise ??= (async () => {\n this.#attached = false;\n this.#reusable = false;\n this.#controller.abort();\n try {\n await this.#events.return(undefined);\n } catch {\n /* the reader's teardown errors are not the closer's problem */\n }\n })();\n return this.#closePromise;\n }\n}\n","import type {\n JSONObject,\n LanguageModelV4FinishReason,\n LanguageModelV4StreamPart,\n LanguageModelV4Usage,\n} from \"@ai-sdk/provider\";\nimport { CoderChatError } from \"../errors.js\";\nimport {\n type ChatErrorPayload,\n type ChatMessage,\n type ChatMessagePart,\n type ChatMessageUsage,\n type ChatStatus,\n type ChatStreamEvent,\n TERMINAL_STATUSES,\n} from \"../coder/types.js\";\n\n/** `a + b` where an undefined operand contributes nothing; undefined only when both are. */\nfunction addDefined(a: number | undefined, b: number | undefined): number | undefined {\n if (b === undefined) return a;\n return (a ?? 0) + b;\n}\n\n/**\n * How each known wire usage field aggregates across a segment's steps:\n * counters sum; `context_limit` is a property of the model rather than a\n * counter, so the newest step's value wins. Typed against\n * {@link ChatMessageUsage} so adding a wire field without classifying it\n * fails the build instead of silently vanishing from the totals. Fields this\n * SDK doesn't know yet (newer servers) pass through with newest-wins\n * semantics so `usage.raw` stays the escape hatch for them.\n */\nconst USAGE_FIELD_KINDS = {\n input_tokens: \"sum\",\n output_tokens: \"sum\",\n total_tokens: \"sum\",\n reasoning_tokens: \"sum\",\n cache_creation_tokens: \"sum\",\n cache_read_tokens: \"sum\",\n total_cost_micros: \"sum\",\n total_runtime_ms: \"sum\",\n context_limit: \"newest\",\n} as const satisfies Record<keyof ChatMessageUsage, \"sum\" | \"newest\">;\n\nfunction mapUsage(u: ChatMessageUsage | undefined): LanguageModelV4Usage {\n // chatd normalizes provider usage so `input_tokens` counts only the UNCACHED\n // prompt tokens — cache reads/writes are reported separately (both Anthropic\n // and OpenAI are normalized this way server-side). The V4 `inputTokens.total`\n // is the full prompt size, so the cache components must be added back; with\n // prompt caching active, `input_tokens` alone is near zero.\n return {\n inputTokens: {\n total: addDefined(\n addDefined(u?.input_tokens, u?.cache_read_tokens),\n u?.cache_creation_tokens,\n ),\n noCache: u?.input_tokens,\n cacheRead: u?.cache_read_tokens,\n cacheWrite: u?.cache_creation_tokens,\n },\n outputTokens: {\n total: u?.output_tokens,\n text: undefined,\n reasoning: u?.reasoning_tokens,\n },\n // Preserve the turn's wire usage (snake_case) so callers can reach fields\n // the normalized shape has no slot for (context_limit, cost, runtime, …).\n ...(u ? { raw: u as unknown as JSONObject } : {}),\n };\n}\n\n/**\n * Sums per-message wire usage into segment totals. chatd attaches usage to\n * every assistant message it commits — one per internal model step — so the\n * segment's consumption is the sum over all of them, not the last message's\n * (the AI SDK then sums segments into the turn total). Fields stay absent\n * unless at least one message reported them (old servers keep looking\n * unchanged); non-counter and unknown fields take the newest reporting\n * message's value (see {@link USAGE_FIELD_KINDS}).\n */\nfunction accumulateUsage(\n byMessageId: ReadonlyMap<number, ChatMessageUsage>,\n): ChatMessageUsage | undefined {\n if (byMessageId.size === 0) return undefined;\n // Wire usage is parsed JSON; newer servers may send fields beyond\n // ChatMessageUsage, so accumulate on the JSON shape and narrow at the end.\n const total: JSONObject = {};\n // Map insertion order is not id order after a revision re-set, so track the\n // reporting message id per newest-wins key explicitly.\n const newestIdByKey = new Map<string, number>();\n for (const [id, u] of byMessageId) {\n for (const [key, value] of Object.entries(u)) {\n if (value === undefined) continue;\n if (USAGE_FIELD_KINDS[key as keyof ChatMessageUsage] === \"sum\" && typeof value === \"number\") {\n total[key] = ((total[key] as number | undefined) ?? 0) + value;\n } else if (id > (newestIdByKey.get(key) ?? -1)) {\n // `context_limit`, malformed counters, and any field this SDK doesn't\n // know yet: not summable — the newest reporting message's value wins,\n // which keeps `usage.raw` a forward-compatible escape hatch.\n newestIdByKey.set(key, id);\n total[key] = value;\n }\n }\n }\n return total as ChatMessageUsage;\n}\n\nfunction jsonResult(value: unknown): NonNullable<unknown> {\n // chatd tool results are arbitrary JSON; the V4 tool-result `result` must be\n // a non-null JSON value.\n return (value ?? {}) as NonNullable<unknown>;\n}\n\n/** A snapshot's full content of one kind, in wire order. */\nfunction joinContent(content: readonly ChatMessagePart[], kind: \"text\" | \"reasoning\"): string {\n let full = \"\";\n for (const p of content) if (p.type === kind) full += p.text ?? \"\";\n return full;\n}\n\n/**\n * Canonical JSON of a decoded wire value: object keys sorted recursively, so\n * two decodings of the same server-side content compare equal regardless of\n * property order. Inputs are parsed JSON (plain objects/arrays/primitives —\n * never `undefined`, functions, or cycles), which keeps this total.\n */\nfunction canonicalJson(value: unknown): string {\n if (Array.isArray(value)) return `[${value.map(canonicalJson).join(\",\")}]`;\n if (value !== null && typeof value === \"object\") {\n const entries = Object.entries(value);\n entries.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));\n const body = entries.map(([k, v]) => `${JSON.stringify(k)}:${canonicalJson(v)}`);\n return `{${body.join(\",\")}}`;\n }\n return JSON.stringify(value) ?? \"null\";\n}\n\n/**\n * The attribution gate for a QUEUED submission (issue #114): the chat was\n * busy, so `createChatMessage` returned only a queue entry — no committed\n * message id — and everything on the stream between the pre-turn cursor and\n * the entry's materialization belongs to the chat's CURRENT (concurrent or\n * dying) run. While the gate is up, {@link TurnTranslator.ingest} attributes\n * NOTHING to this turn; it only scans for the turn's own user-message\n * snapshot, whose id then becomes the effective turn cursor.\n *\n * EXACT correlation (issue #119): chatd stamps a user message promoted out of\n * the queue with the entry's id as `queued_message_id` (coder/coder#29859),\n * on live events and on `after_id`/`history_reset` replays alike. When a\n * user snapshot past the cursor carries the stamp and the submission response\n * gave us the entry's id, the stamp decides alone: equal ⇒ it is this turn's\n * own message (anchor there, no `queue_update` confirmation needed); different\n * ⇒ it is another entry's promotion and is never a candidate, even with\n * byte-identical content — which closes the twin window described below.\n * Capability is detected per message by the stamp's presence, never by server\n * version.\n *\n * HEURISTIC fallback, used for every user snapshot WITHOUT the stamp (older\n * servers, which never write it) or when the submission response carried no\n * entry id. The queue is strictly FIFO and both the queue entry and the\n * materialized message expose the same server-side content encoding:\n * - a `message` snapshot with role `user`, id past the cursor, and content\n * structurally equal to the queue entry's becomes the CANDIDATE anchor;\n * - the `queue_update` that chatd emits after every queue change confirms\n * it: our entry gone from the full queue list ⇒ the candidate is our\n * materialization (anchor there); our entry still listed ⇒ the candidate\n * was an EARLIER identical-content entry's materialization (drop it and\n * keep waiting). Message snapshots that arrive between candidate and\n * confirmation (a reconnect replays this turn's own commits before the\n * connect-time `queue_update`) are buffered and replayed on anchor;\n * - without a queue-entry id to confirm against (older wire shapes), the\n * first matching user message anchors directly.\n *\n * Dead ends fail fast instead of hanging until timeout: chatd does NOT\n * auto-promote the queue after an error settle, so an `error` event while\n * gated means the entry is stuck behind a dead chat; our entry vanishing from\n * `queue_update` without a candidate means it was deleted externally (or its\n * materialized form could not be correlated). The heuristic's residual\n * ambiguity — byte-identical concurrent submissions promoted within one\n * replay window, whose per-promotion `queue_update`s collapse into one — is\n * what the exact stamp resolves; unstamped servers keep it.\n */\ninterface QueuedSubmissionGate {\n /**\n * The queue-entry id from the submission response, when the wire sent one:\n * the exact-anchor key and the heuristic's confirmation key.\n */\n queuedId: number | undefined;\n /** Canonical content of the queue entry; undefined matches any user message. */\n expected: string | undefined;\n /** Latest matching user-message id awaiting `queue_update` confirmation. */\n candidateId: number | undefined;\n /** Message snapshots since the candidate, replayed through ingest on anchor. */\n buffered: ChatMessage[];\n /** Set when the wait can never complete; the model surfaces it as a throw. */\n deadEnd: ChatErrorPayload | undefined;\n}\n\n/** Constructor options for {@link TurnTranslator}. */\nexport interface TurnTranslatorOptions {\n dynamicToolNames: ReadonlySet<string>;\n turnCursor?: number;\n /**\n * Tool-call ids whose results the session already submitted. With the\n * stream retained across segments (and redialed with the turn's original\n * cursor), a reconnect can replay a previous segment's `action_required`\n * snapshot; re-emitting an already-answered call would make the AI SDK\n * execute the tool a second time. A live reference — the set grows as\n * later segments submit results.\n */\n submittedToolCallIds?: ReadonlySet<string>;\n /**\n * Present when the submission was QUEUED (`CreateChatMessageResponse`\n * carried no committed `message`): `turnCursor` is then only the PRE-TURN\n * cursor, and attribution is withheld until the queue entry materializes as\n * this turn's own user message — see {@link QueuedSubmissionGate} for the\n * correlation protocol. `id`/`content` come from the response's\n * `queued_message`; either may be absent on unexpected wire shapes, which\n * degrades matching (no confirmation / match-any) but never widens\n * attribution beyond the first observed user message. Without `id`, exact\n * `queued_message_id` anchoring is unavailable and the heuristic applies.\n */\n queuedSubmission?: { id?: number; content?: readonly ChatMessagePart[] };\n}\n\n/**\n * Translates one chatd turn's {@link ChatStreamEvent} stream into a sequence of\n * `LanguageModelV4StreamPart`s for the AI SDK.\n *\n * Text/reasoning is reconciled against a per-message emitted-content ledger:\n * `message_part` deltas are emitted as they stream (they carry no message id,\n * so they accumulate as *pending* content), and each assistant `message`\n * snapshot attributes the pending content to its id and emits whatever suffix\n * of the snapshot's full content is still missing. That makes fast\n * snapshot-only messages (everything missing), trailing snapshots after deltas\n * (nothing missing), commit-during-disconnect snapshots (the tail the lost\n * deltas never delivered), and append-revisions of earlier messages (the\n * appended suffix) all the same operation — and a snapshot whose content does\n * not extend what was emitted (a byte-identical replay, or a rewrite that\n * cannot be expressed as deltas) is safely suppressed instead of\n * double-emitted.\n *\n * Client (custom) tool calls are emitted from the reliable `action_required`\n * event and left for the AI SDK to execute. chatd's own server-side tools are\n * surfaced best-effort as `providerExecuted` tool calls/results.\n *\n * A QUEUED submission additionally starts behind an attribution gate: until\n * the queue entry materializes as this turn's own user message, every event\n * belongs to the chat's concurrent run and is withheld (see\n * {@link QueuedSubmissionGate} and {@link TurnTranslatorOptions.queuedSubmission}).\n *\n * The mirror direction is the PROMOTION SETTLE BOUNDARY: a user-role\n * `message` snapshot past the turn's effective cursor settles this turn\n * mid-run and freezes the translator, because chatd starts the chat's next\n * run without ever emitting a terminal status for the finished one (see\n * {@link settledAtPromotionBoundary}, issue #120). Both behaviors key off\n * the SAME promoted user snapshot — it is the submitting turn's anchor\n * (turn start) and the finishing turn's boundary (turn end) — and stay\n * independent because each keys strictly off its OWN turn's cursor: the\n * anchor advances the submitter's cursor TO the snapshot's id, while the\n * boundary requires an id strictly PAST the finisher's cursor.\n */\nexport class TurnTranslator {\n readonly #dynamicToolNames: ReadonlySet<string>;\n readonly #submittedToolCallIds: ReadonlySet<string>;\n\n #seq = 0;\n // `pending` accumulates delta content that has been emitted but not yet\n // attributed to a message id (deltas carry none); the next assistant\n // snapshot claims it. It survives block closes — a closed block loses its\n // stream id, never the record of what was emitted.\n #text = { id: undefined as string | undefined, pending: \"\" };\n #reasoning = { id: undefined as string | undefined, pending: \"\" };\n #currentAssistantId: number | undefined;\n // Whether text/reasoning deltas arrived since the last assistant `message`\n // snapshot — i.e. the next assistant snapshot is the trailing snapshot of the\n // message those deltas streamed (deltas carry no message id of their own).\n #deltasSinceSnapshot = false;\n\n #serverToolCalls = new Set<string>();\n #serverToolResults = new Set<string>();\n #clientToolCalls = new Set<string>();\n #clientToolCallSeen = false;\n #sources = new Set<string>();\n\n // Per-message wire usage, keyed by message id so a re-streamed revision of\n // the same message replaces its earlier entry instead of double-counting\n // (chatd re-sends full snapshots on revision bumps and history resets).\n readonly #usageByMessageId = new Map<number, ChatMessageUsage>();\n // The emitted-content ledger: the text/reasoning this translator has emitted\n // for each processed assistant message id. A redial replays the turn from\n // its original cursor and chatd re-sends full snapshots on revision bumps,\n // so already-processed messages arrive again — the ledger is what lets a\n // byte-identical replay reconcile to a no-op while an APPENDING revision (or\n // a commit-during-disconnect snapshot) yields exactly its missing suffix\n // (see #ingestMessage). `substantive` records whether any processed snapshot\n // of the message carried content (text, reasoning, tools, or sources) —\n // false only for announce-style empty snapshots, whose in-flight deltas are\n // still claimable by a same-id commit.\n readonly #emittedByMessageId = new Map<\n number,\n { text: string; reasoning: string; substantive: boolean }\n >();\n // Same-id revision snapshots (their full wire content) that arrived while\n // the NEXT step's deltas were open (latest wins). chatd sends each revision\n // once on a healthy connection, so the suffix cannot wait for a replay — it\n // is reconciled as soon as the next step's snapshot settles ownership of\n // the pending deltas, or flushed by finish() when the segment ends without\n // one (see #78).\n readonly #deferredRevisions = new Map<number, readonly ChatMessagePart[]>();\n #error: ChatErrorPayload | undefined;\n #terminalStatus: ChatStatus | undefined;\n #maxMessageId = 0;\n\n /**\n * Messages with an id at or below this cursor belong to earlier turns (or\n * to a segment that already streamed) — they arrive only as replays: the\n * initial sync when resuming a chat, or a mid-turn `history_reset` re-send\n * of the full history. Ingest skips them entirely so a replay can neither\n * re-emit earlier turns' content nor inflate this turn's usage. Mutable for\n * exactly two transitions, both of which pin the turn's OWN user message as\n * the effective floor: a QUEUED submission starts at the pre-turn cursor\n * and advances to the materialized user message's id when the gate anchors\n * (see {@link QueuedSubmissionGate}), and a ZERO cursor (this turn created\n * the chat) adopts the replay's first user snapshot — this turn's own\n * prompt — as the cursor (see #ingestMessage). Either way, replayed\n * pre-cursor tail messages then stay filtered on every redial, which reuses\n * the turn's ORIGINAL `after_id` and therefore re-delivers them.\n */\n #turnCursor: number;\n\n /** The queued-submission attribution gate; undefined once anchored (#114). */\n #queuedGate: QueuedSubmissionGate | undefined;\n\n /**\n * Set when the turn settled at a PROMOTION BOUNDARY (issue #120): a\n * user-role `message` snapshot with an id past the turn's effective cursor\n * arrived mid-run. Once set, the translator is FROZEN — every later event\n * is the successor run's traffic and ingests as a no-op — so nothing past\n * the boundary can leak into this turn's content, usage, or settle state.\n * See {@link settledAtPromotionBoundary} for why the snapshot is reliable.\n */\n #promotionBoundary = false;\n\n constructor(opts: TurnTranslatorOptions) {\n this.#dynamicToolNames = opts.dynamicToolNames;\n this.#turnCursor = opts.turnCursor ?? 0;\n this.#submittedToolCallIds = opts.submittedToolCallIds ?? new Set();\n const queued = opts.queuedSubmission;\n if (queued) {\n this.#queuedGate = {\n queuedId: queued.id,\n expected: queued.content !== undefined ? canonicalJson(queued.content) : undefined,\n candidateId: undefined,\n buffered: [],\n deadEnd: undefined,\n };\n }\n }\n\n get terminalStatus(): ChatStatus | undefined {\n return this.#terminalStatus;\n }\n /** Whether a client (custom) tool call has been emitted this turn. */\n get clientToolCallSeen(): boolean {\n return this.#clientToolCallSeen;\n }\n get maxMessageId(): number {\n return this.#maxMessageId;\n }\n get error(): ChatErrorPayload | undefined {\n return this.#error;\n }\n /**\n * The turn's EFFECTIVE attribution cursor: the constructor's `turnCursor`\n * until a queued submission anchors (the materialized user message's id\n * after) or a zero-cursor turn adopts its own prompt's id (see\n * #ingestMessage). Distinct from the stream's redial cursor, which\n * deliberately stays the turn's original `after_id` (see `streamChatEvents`).\n */\n get turnCursor(): number {\n return this.#turnCursor;\n }\n /**\n * Whether a queued submission is still awaiting its own user-message\n * snapshot (#114). While true, nothing is attributed to this turn — no\n * content, usage, tool calls, or settle statuses — because the stream\n * window still belongs to the chat's concurrent run.\n */\n get awaitingQueuedSubmission(): boolean {\n return this.#queuedGate !== undefined;\n }\n /**\n * Set when a queued submission's wait can never complete (see\n * {@link QueuedSubmissionGate}); the model layer throws it so the turn\n * fails fast instead of hanging until timeout/abort.\n */\n get queuedSubmissionDeadEnd(): ChatErrorPayload | undefined {\n return this.#queuedGate?.deadEnd;\n }\n /**\n * Whether the turn settled at a PROMOTION BOUNDARY (issue #120): a\n * user-role `message` snapshot with an id past the turn's effective cursor\n * arrived mid-run. chatd commits a mid-run user message only when it starts\n * the chat's NEXT run — `FinishTurn`/`FinishInterruption` promote the queue\n * head atomically with the previous run's finish (the chat transitions\n * `running → running` and NO terminal status is ever emitted for the\n * finished run), and interrupting a `requires_action` pause queues and then\n * promotes the same way — so the snapshot is proof this turn's own run is\n * over even though no terminal status was (or ever will be) sent for it.\n * The segment loop settles the turn here instead of absorbing the promoted\n * run's content, usage, and settle events; the successor run stays alive\n * and belongs to whoever submitted it, so teardown must NOT interrupt it.\n */\n get settledAtPromotionBoundary(): boolean {\n return this.#promotionBoundary;\n }\n\n // --- block helpers --------------------------------------------------------\n\n #openText(out: LanguageModelV4StreamPart[]): void {\n if (this.#reasoning.id) this.#closeReasoning(out);\n if (!this.#text.id) {\n this.#text.id = `text-${++this.#seq}`;\n out.push({ type: \"text-start\", id: this.#text.id });\n }\n }\n #closeText(out: LanguageModelV4StreamPart[]): void {\n if (this.#text.id) {\n out.push({ type: \"text-end\", id: this.#text.id });\n this.#text.id = undefined;\n }\n }\n #openReasoning(out: LanguageModelV4StreamPart[]): void {\n if (this.#text.id) this.#closeText(out);\n if (!this.#reasoning.id) {\n this.#reasoning.id = `reasoning-${++this.#seq}`;\n out.push({ type: \"reasoning-start\", id: this.#reasoning.id });\n }\n }\n #closeReasoning(out: LanguageModelV4StreamPart[]): void {\n if (this.#reasoning.id) {\n out.push({ type: \"reasoning-end\", id: this.#reasoning.id });\n this.#reasoning.id = undefined;\n }\n }\n\n #emitTextDelta(out: LanguageModelV4StreamPart[], delta: string): void {\n this.#openText(out);\n out.push({ type: \"text-delta\", id: this.#text.id as string, delta });\n }\n #emitReasoningDelta(out: LanguageModelV4StreamPart[], delta: string): void {\n this.#openReasoning(out);\n out.push({ type: \"reasoning-delta\", id: this.#reasoning.id as string, delta });\n }\n\n /**\n * Reconciles a replayed/revised snapshot of an EARLIER (non-current)\n * assistant message against its ledger entry: an APPENDING revision emits\n * its suffix, anything else is a no-op. The suffix is bracketed in its own\n * block(s) — an open block belongs to the CURRENT step, and splicing another\n * message's content into it would corrupt both — and the interrupted step's\n * next delta simply opens a fresh block (its pending content is tracked\n * independently of blocks).\n */\n #reconcileRevision(\n out: LanguageModelV4StreamPart[],\n rec: { text: string; reasoning: string },\n content: readonly ChatMessagePart[],\n ): void {\n const fullText = joinContent(content, \"text\");\n const fullReasoning = joinContent(content, \"reasoning\");\n // Per-kind extension gates: a kind's suffix emits only when the revision\n // strictly extends what the ledger already emitted for it.\n const textOk = fullText.length > rec.text.length && fullText.startsWith(rec.text);\n const reasoningOk =\n fullReasoning.length > rec.reasoning.length && fullReasoning.startsWith(rec.reasoning);\n if (!textOk && !reasoningOk) return;\n this.#closeText(out);\n this.#closeReasoning(out);\n // Emit each kind's missing sub-span walking the snapshot in WIRE order —\n // the same walk as first-sight reconciliation (see #ingestMessage) — so a\n // revision appending both kinds surfaces its suffixes the way the model\n // produced them, not reasoning-first (#79).\n let seenText = 0;\n let seenReasoning = 0;\n for (const p of content) {\n if (p.type === \"text\") {\n const end = seenText + (p.text ?? \"\").length;\n const from = Math.max(seenText, rec.text.length);\n if (textOk && end > from) this.#emitTextDelta(out, fullText.slice(from, end));\n seenText = end;\n } else if (p.type === \"reasoning\") {\n const end = seenReasoning + (p.text ?? \"\").length;\n const from = Math.max(seenReasoning, rec.reasoning.length);\n if (reasoningOk && end > from)\n this.#emitReasoningDelta(out, fullReasoning.slice(from, end));\n seenReasoning = end;\n }\n }\n if (textOk) rec.text = fullText;\n if (reasoningOk) rec.reasoning = fullReasoning;\n this.#closeText(out);\n this.#closeReasoning(out);\n }\n\n // --- tool helpers ---------------------------------------------------------\n\n #isClientTool(name: string | undefined): boolean {\n return name !== undefined && this.#dynamicToolNames.has(name);\n }\n\n #emitServerToolCall(out: LanguageModelV4StreamPart[], part: ChatMessagePart): void {\n const id = part.tool_call_id;\n const name = part.tool_name;\n if (!id || !name) return;\n if (part.args === undefined) return; // wait for complete args (snapshot)\n if (this.#serverToolCalls.has(id)) return;\n this.#serverToolCalls.add(id);\n // `dynamic: true` is load-bearing: server tools are not in the client ToolSet, and\n // the AI SDK only tolerates unknown tool names on `providerExecuted && dynamic`\n // calls. Without it every server tool call is marked `invalid`, which injects a\n // phantom tool-error output and halts the tool loop on that step — stranding the\n // turn whenever a client tool call is pending in the same segment.\n out.push({\n type: \"tool-input-start\",\n id,\n toolName: name,\n providerExecuted: true,\n dynamic: true,\n });\n out.push({ type: \"tool-input-end\", id });\n out.push({\n type: \"tool-call\",\n toolCallId: id,\n toolName: name,\n input: typeof part.args === \"string\" ? part.args : JSON.stringify(part.args),\n providerExecuted: true,\n dynamic: true,\n });\n }\n\n #emitServerToolResult(out: LanguageModelV4StreamPart[], part: ChatMessagePart): void {\n const id = part.tool_call_id;\n const name = part.tool_name;\n if (!id || !name) return;\n if (part.result === undefined) return;\n if (this.#serverToolResults.has(id)) return;\n // Only pair with a call emitted in THIS segment. A result whose call streamed in\n // an earlier segment (chatd paused for a client tool in between, and the resume\n // segment starts past the assistant message) would reach the AI SDK call-less,\n // and generateText throws \"Tool call <id> not found.\" Drop the orphan — the\n // server-side transcript still has it.\n if (!this.#serverToolCalls.has(id)) return;\n this.#serverToolResults.add(id);\n out.push({\n type: \"tool-result\",\n toolCallId: id,\n toolName: name,\n result: jsonResult(part.result),\n isError: part.is_error ?? false,\n // Mirror the call's `dynamic: true` so call and result land in the same\n // bucket (steps[*].dynamicToolCalls / dynamicToolResults, UI streams).\n dynamic: true,\n });\n }\n\n /**\n * Emits a chatd `source` part as a standalone V4 url source (no text-block\n * bracketing needed). Deduped by the emitted id so a part streamed as a\n * `message_part` isn't re-emitted by its trailing `message` snapshot, while\n * snapshot-only turns still emit theirs.\n */\n #emitSource(out: LanguageModelV4StreamPart[], part: ChatMessagePart): void {\n const url = part.url;\n if (!url) return; // the V4 source part requires both id and url\n const id = part.source_id || url;\n if (this.#sources.has(id)) return;\n this.#sources.add(id);\n out.push({\n type: \"source\",\n sourceType: \"url\",\n id,\n url,\n ...(part.title !== undefined ? { title: part.title } : {}),\n });\n }\n\n // --- ingest ---------------------------------------------------------------\n\n ingest(ev: ChatStreamEvent): LanguageModelV4StreamPart[] {\n // Frozen at the promotion boundary (issue #120): everything after it —\n // the successor run's deltas, commits (usage included), statuses, errors,\n // pauses — is the NEXT turn's traffic and must not mutate this turn.\n if (this.#promotionBoundary) return [];\n const gate = this.#queuedGate;\n if (gate) return this.#ingestGated(gate, ev);\n const out: LanguageModelV4StreamPart[] = [];\n switch (ev.type) {\n case \"message_part\":\n this.#ingestMessagePart(out, ev);\n break;\n case \"message\":\n if (ev.message) this.#ingestMessage(out, ev.message);\n break;\n case \"action_required\":\n for (const tc of ev.action_required?.tool_calls ?? []) {\n if (this.#clientToolCalls.has(tc.tool_call_id)) continue;\n // A replayed pause snapshot for a call this session already\n // answered (see the constructor doc) — not this segment's work.\n if (this.#submittedToolCallIds.has(tc.tool_call_id)) continue;\n // A pause can follow a deferred revision without a settling\n // assistant snapshot; the revision arrived first, so its suffix\n // must precede the tool call to keep the stream's content order\n // (as the error branch already does). Replayed/answered calls\n // above skip this — a replay must stay a no-op.\n this.#flushDeferredRevisions(out);\n this.#closeText(out);\n this.#closeReasoning(out);\n this.#clientToolCalls.add(tc.tool_call_id);\n this.#clientToolCallSeen = true;\n out.push({ type: \"tool-input-start\", id: tc.tool_call_id, toolName: tc.tool_name });\n out.push({ type: \"tool-input-end\", id: tc.tool_call_id });\n out.push({\n type: \"tool-call\",\n toolCallId: tc.tool_call_id,\n toolName: tc.tool_name,\n input: tc.args,\n });\n }\n break;\n case \"error\":\n if (ev.error) {\n this.#error = ev.error;\n // doGenerate() throws on the error part and closes the generator\n // before finish() can run (issue #72) — parts yielded after it are\n // never pulled, so the deferred-revision flush must precede it.\n this.#flushDeferredRevisions(out);\n out.push({ type: \"error\", error: new CoderChatError(ev.error) });\n }\n break;\n case \"status\":\n if (ev.status) {\n if (TERMINAL_STATUSES.has(ev.status.status)) {\n this.#terminalStatus = ev.status.status;\n } else {\n // A non-terminal transition means the chat is generating again:\n // any previously recorded settle is stale. This matters for a\n // stream that outlives its segment — a redial during the\n // tool-result resume replays the connect-time status snapshot,\n // which can still say `requires_action` from the previous\n // segment's pause; without the clear, the segment loop would\n // treat every later event as post-settle and truncate the\n // resumed generation via its safety valve. Real terminal\n // statuses are unaffected: the segment breaks on them\n // immediately, before any transition could arrive.\n this.#terminalStatus = undefined;\n }\n }\n break;\n default:\n break; // retry / queue_update / preview_reset / history_reset\n }\n return out;\n }\n\n /**\n * Ingest while the queued-submission gate is up (#114): the stream window\n * still belongs to the chat's concurrent run, so nothing is attributed —\n * this only advances cursor bookkeeping, runs the anchor correlation\n * protocol (see {@link QueuedSubmissionGate}), and detects dead ends.\n * Returns parts only at the anchoring transition, when buffered snapshots\n * of this turn's own already-committed messages are replayed through the\n * normal path.\n */\n #ingestGated(gate: QueuedSubmissionGate, ev: ChatStreamEvent): LanguageModelV4StreamPart[] {\n switch (ev.type) {\n case \"message\": {\n const message = ev.message;\n if (!message) break;\n // Cursor bookkeeping stays live while gated: these are committed\n // messages the session has observed, and the NEXT turn's boundary\n // must advance past them whether or not this turn ever anchors.\n if (message.id > this.#maxMessageId) this.#maxMessageId = message.id;\n if (message.id <= this.#turnCursor) break; // pre-turn replay\n const exact = this.#exactQueuedMatch(gate, message);\n // Our own promotion, by the server's stamp: anchor now. Anchoring\n // drops the gate, so a heuristic candidate (and its buffer) from an\n // unstamped snapshot is discarded with it — the stamp proves that\n // candidate was not ours.\n if (exact === true) return this.#anchorQueuedTurn(message.id);\n // `exact === false`: another entry's promotion, never a candidate\n // however its content compares; it only falls through to buffering.\n if (\n exact === undefined &&\n message.role === \"user\" &&\n this.#matchesQueuedContent(gate, message)\n ) {\n // Without a queue-entry id there is nothing to confirm against\n // (older wire shapes): anchor on the first match directly.\n if (gate.queuedId === undefined) return this.#anchorQueuedTurn(message.id);\n // Candidate — the LATEST match before confirmation wins: on a\n // reconnect replay, an identical-content entry queued AHEAD of\n // ours materialized first, and its user message replays before\n // ours does. Messages buffered for a superseded candidate belonged\n // to that earlier entry's run; drop them with it.\n gate.candidateId = message.id;\n gate.buffered = [];\n } else if (gate.candidateId !== undefined) {\n // Between candidate and confirmation only message snapshots can\n // arrive (chatd flushes commits before the queue update): they are\n // this turn's own already-committed messages when the candidate is\n // right, and the superseded run's when it is not — buffer them and\n // let the confirmation decide.\n gate.buffered.push(message);\n }\n break;\n }\n case \"queue_update\": {\n if (gate.queuedId === undefined) break;\n // A `queue_update` always carries the FULL queue, but the wire field\n // is `omitempty`: an EMPTY queue arrives with `queued_messages`\n // absent — the very shape the common single-entry promotion produces.\n // Absent therefore means empty, not \"no information\".\n const queue = ev.queued_messages ?? [];\n if (queue.some((entry) => entry.id === gate.queuedId)) {\n // Our entry is still queued, so any candidate materialized from an\n // EARLIER identical-content entry — not ours. Keep waiting.\n gate.candidateId = undefined;\n gate.buffered = [];\n break;\n }\n if (gate.candidateId !== undefined) {\n const buffered = gate.buffered;\n const out = this.#anchorQueuedTurn(gate.candidateId);\n for (const message of buffered) this.#ingestMessage(out, message);\n return out;\n }\n // Gone from the queue, yet no user message matched: deleted\n // externally, or the materialized form could not be correlated.\n // Either way this turn's window will never open — fail fast.\n gate.deadEnd = {\n message:\n \"The queued submission left the chat's queue without materializing as this turn's user message (deleted externally, or its materialized form could not be correlated); this turn never started.\",\n kind: \"queued_submission_lost\",\n retryable: false,\n };\n break;\n }\n case \"error\": {\n if (!ev.error) break;\n // The chat's CURRENT run failed while our entry sat in the queue.\n // chatd does not auto-promote the queue after an error settle — the\n // entry is stuck until an explicit user action — so waiting on the\n // stream can never complete. Fail fast, attributing the failure to\n // the wait rather than absorbing the other run's error as this\n // turn's own settle. Non-retryable: a resubmission to the errored\n // chat would promote the stuck entry AND queue a duplicate.\n gate.deadEnd = {\n message:\n \"The chat's active run failed while this turn's submission was queued behind it; the queued message never materialized, so this turn never started.\",\n detail: ev.error.detail ? `${ev.error.message}: ${ev.error.detail}` : ev.error.message,\n kind: \"queued_submission_lost\",\n retryable: false,\n };\n break;\n }\n default:\n // Deltas, statuses (chatd promotes the queue atomically with the\n // previous run's finish, so no terminal status separates the runs),\n // action_required (the PREVIOUS turn's pending calls — attributing\n // them would make the AI SDK execute another turn's tools), retries:\n // all the concurrent run's, all withheld.\n break;\n }\n return [];\n }\n\n /**\n * The EXACT verdict for a snapshot (#119): `true`/`false` when it is a\n * user message stamped with `queued_message_id` and the submission response\n * gave us the entry id to compare; `undefined` (no exact evidence — use the\n * heuristic) otherwise. Only a numeric stamp counts, so an out-of-contract\n * `null` degrades to the heuristic instead of rejecting our own message.\n */\n #exactQueuedMatch(gate: QueuedSubmissionGate, message: ChatMessage): boolean | undefined {\n const stamp = message.queued_message_id;\n if (message.role !== \"user\" || typeof stamp !== \"number\") return undefined;\n if (gate.queuedId === undefined) return undefined;\n return stamp === gate.queuedId;\n }\n\n /** Whether a user-message snapshot's content matches the queued entry's. */\n #matchesQueuedContent(gate: QueuedSubmissionGate, message: ChatMessage): boolean {\n if (gate.expected === undefined) return true;\n return canonicalJson(message.content ?? []) === gate.expected;\n }\n\n /**\n * Opens the turn's attribution window at `anchorId` — this turn's own user\n * message — and drops the gate. Replays of the pre-anchor tail (redials\n * reuse the turn's ORIGINAL cursor) stay filtered by the advanced cursor.\n */\n #anchorQueuedTurn(anchorId: number): LanguageModelV4StreamPart[] {\n // Both callers pass an id that already cleared the gated cursor filter\n // (candidates are recorded only past it, and the cursor cannot move while\n // gated); anything else is a bug here, and anchoring on it would re-open\n // attribution for earlier turns' replays.\n if (anchorId <= this.#turnCursor) {\n throw new Error(\n `TurnTranslator invariant: queued anchor ${anchorId} is not past cursor ${this.#turnCursor}`,\n );\n }\n this.#turnCursor = anchorId;\n this.#queuedGate = undefined;\n return [];\n }\n\n #ingestMessagePart(out: LanguageModelV4StreamPart[], ev: ChatStreamEvent): void {\n const mp = ev.message_part;\n if (!mp || (mp.role !== \"assistant\" && mp.role !== \"tool\")) return;\n const part = mp.part;\n switch (part.type) {\n case \"text\":\n this.#deltasSinceSnapshot = true;\n this.#openText(out);\n if (part.text) {\n out.push({ type: \"text-delta\", id: this.#text.id as string, delta: part.text });\n this.#text.pending += part.text;\n }\n break;\n case \"reasoning\":\n this.#deltasSinceSnapshot = true;\n this.#openReasoning(out);\n if (part.text) {\n out.push({ type: \"reasoning-delta\", id: this.#reasoning.id as string, delta: part.text });\n this.#reasoning.pending += part.text;\n }\n break;\n case \"tool-call\":\n this.#closeText(out);\n this.#closeReasoning(out);\n if (!this.#isClientTool(part.tool_name)) this.#emitServerToolCall(out, part);\n break;\n case \"tool-result\":\n if (!this.#isClientTool(part.tool_name)) this.#emitServerToolResult(out, part);\n break;\n case \"source\":\n this.#emitSource(out, part);\n break;\n default:\n break;\n }\n }\n\n #ingestMessage(out: LanguageModelV4StreamPart[], message: ChatMessage): void {\n // The anchor drain (#anchorQueuedTurn's caller replays buffered\n // snapshots through here directly) can cross the promotion boundary\n // mid-loop; the freeze applies to the rest of the buffer too.\n if (this.#promotionBoundary) return;\n if (message.id > this.#maxMessageId) this.#maxMessageId = message.id;\n // Replay of an earlier turn (or an already-streamed segment): skip it\n // entirely — see #turnCursor. In the normal path the server-side\n // `after_id` filter means such ids never arrive, so this only bites on\n // replays, where processing them would re-emit old content as this\n // turn's output and double-count its usage.\n if (message.id <= this.#turnCursor) return;\n // PROMOTION SETTLE BOUNDARY (issue #120). A user-role message with an id\n // past the turn's effective cursor can only be committed when chatd\n // starts the chat's NEXT run: `FinishTurn` and `FinishInterruption`\n // promote the queue head atomically with the previous run's finish (the\n // chat transitions `running → running` — no terminal status is ever\n // emitted for the finished run), and interrupting a `requires_action`\n // pause commits its cancellations as role \"tool\" and promotes the same\n // way. Verified against chatd source: every other user-role insert\n // happens only on an idle/terminal chat; the turn's own submission id is\n // at or below the effective cursor (a new turn's cursor IS its message\n // id; a queued turn's gate anchors the cursor AT its materialized\n // message); tool results commit as role \"tool\"; and compaction's\n // user-role summary has visibility \"model\" and never reaches the stream.\n // Redial replays cannot false-settle: message ids are strictly\n // increasing at commit time, so a replay re-delivers only ids at or\n // below the cursor, which the filter above drops. Settle here rather\n // than absorb the successor run's content, usage, and settle events —\n // the mirror of the #114 submission-side gate.\n if (message.role === \"user\") {\n // A ZERO cursor means this turn created the chat (or resumed an empty\n // one): there is no committed-message id to anchor on, the stream dials\n // without `after_id`, and the replay opens with this turn's OWN prompt —\n // the one user snapshot that is not a boundary. It is guaranteed first:\n // chatd commits the prompt in the chat-creation transaction, so any\n // foreign submission gets a strictly higher id, and both initial replay\n // and redials deliver messages in id order. Adopt its id as the\n // effective cursor — the same normalization every other turn shape gets\n // from its submission response or gate anchor — so replays of it stay\n // filtered above and any LATER user snapshot is a true boundary.\n if (this.#turnCursor === 0) {\n this.#turnCursor = message.id;\n return;\n }\n this.#promotionBoundary = true;\n // A recorded `requires_action` is stale by definition here — the pause\n // it announced was superseded by the interrupt that produced this\n // boundary. Clear it so finish() cannot classify the settled turn as\n // \"tool-calls\" and send the AI SDK to resume a run that no longer\n // exists. (Real terminal statuses never coexist with the boundary: the\n // segment loop breaks on them before another event is ingested.)\n this.#terminalStatus = undefined;\n return;\n }\n // chatd attaches usage to the assistant messages it commits (one per\n // internal model step); tool messages never carry usage, and under\n // summing, accepting a hypothetical mirrored copy would double-count.\n if (message.usage && message.role === \"assistant\") {\n this.#usageByMessageId.set(message.id, message.usage);\n }\n const content = message.content ?? [];\n\n if (message.role === \"assistant\") {\n const fullText = joinContent(content, \"text\");\n const fullReasoning = joinContent(content, \"reasoning\");\n const known = this.#emittedByMessageId.get(message.id);\n\n if (known && message.id !== this.#currentAssistantId) {\n // A replay or revision bump of an EARLIER message (a redial replays\n // the turn from its original cursor; chatd re-sends full snapshots on\n // revision bumps). Reconcile against the ledger: a byte-identical\n // replay is a no-op, an APPENDING revision yields its suffix, and a\n // rewrite stays suppressed (already-emitted text cannot be retracted\n // in a delta stream — suppression is the safe side). NOT taken for\n // the current message: deltas carry no message id, so while the next\n // step streams, #currentAssistantId still names the last committed\n // one and its pending deltas must join the reconciliation below.\n this.#reconcileRevision(out, known, content);\n } else if (known && this.#deltasSinceSnapshot && known.substantive) {\n // A same-id snapshot racing the NEXT step's open deltas. A message\n // whose earlier snapshot already carried content — text, reasoning,\n // or only tools/sources — never streams more deltas (content changes\n // to committed messages arrive only as revision snapshots), so the\n // pending deltas belong to the next, still-uncommitted message —\n // they must NOT be claimed here, even when the revised content\n // happens to share their prefix. Emit nothing and claim nothing yet:\n // the snapshot is DEFERRED and reconciled once the next step's\n // snapshot settles ownership of the pending deltas (see below).\n // Announce-style commits (the earlier snapshot was EMPTY) stay on\n // the attribution path below — their deltas ARE this message's\n // content.\n this.#deferredRevisions.set(message.id, content);\n } else {\n // First sight of this message, or a re-snapshot of the CURRENT one —\n // progressive snapshot-mode growth, or an announce-style commit\n // filling in content that (partially) streamed as deltas.\n //\n // New assistant message boundary: close the previous message's\n // blocks. Skipped when deltas arrived since the previous assistant\n // snapshot: deltas carry no message id, so they belong to the message\n // THIS snapshot finalizes, and their block is this message's block.\n if (\n this.#currentAssistantId !== undefined &&\n this.#currentAssistantId !== message.id &&\n !this.#deltasSinceSnapshot\n ) {\n this.#closeText(out);\n this.#closeReasoning(out);\n }\n this.#currentAssistantId = message.id;\n\n // What this snapshot's content must extend: everything already\n // attributed to this message id, plus the pending deltas it is about\n // to claim. The suffix beyond that is exactly the content this client\n // never received — nothing for a byte-identical trailing snapshot,\n // everything for a snapshot-only message, and the missing tail when\n // the message committed while a dropped stream was redialing (even if\n // the block already closed: the ledger, unlike a block cursor,\n // survives closes). A snapshot that does NOT extend it — a replay\n // racing the next step's open deltas, or a rewrite — attributes\n // nothing and emits nothing: the pending deltas may belong to the\n // next, still-uncommitted message, and a later snapshot naming them\n // settles the attribution instead.\n const rec = known ?? { text: \"\", reasoning: \"\", substantive: false };\n // Per-kind gates: a kind reconciles only when the snapshot's full\n // content extends what was already attributed + pending for it.\n const textAttributed = rec.text + this.#text.pending;\n const reasoningAttributed = rec.reasoning + this.#reasoning.pending;\n const textOk = fullText.startsWith(textAttributed);\n const reasoningOk = fullReasoning.startsWith(reasoningAttributed);\n // Emit each part's missing sub-span walking the snapshot in WIRE\n // order, so recovered blocks interleave the way the model produced\n // them (text→reasoning→text stays that way even when the drop left\n // the text block open) — opening a kind closes the other, and the\n // ledger, unlike a block cursor, loses nothing to the close.\n let seenText = 0;\n let seenReasoning = 0;\n for (const p of content) {\n if (p.type === \"text\") {\n const end = seenText + (p.text ?? \"\").length;\n const from = Math.max(seenText, textAttributed.length);\n if (textOk && end > from) this.#emitTextDelta(out, fullText.slice(from, end));\n seenText = end;\n } else if (p.type === \"reasoning\") {\n const end = seenReasoning + (p.text ?? \"\").length;\n const from = Math.max(seenReasoning, reasoningAttributed.length);\n if (reasoningOk && end > from)\n this.#emitReasoningDelta(out, fullReasoning.slice(from, end));\n seenReasoning = end;\n }\n }\n if (textOk) rec.text = fullText;\n if (reasoningOk) rec.reasoning = fullReasoning;\n // Reaching this path means THIS snapshot owns the pending deltas (the\n // race guard above intercepts the one ambiguous ordering), so they\n // are settled even when reconciliation failed: a rewrite-on-commit\n // stays suppressed, but its stale pending must not poison every later\n // message's reconciliation.\n this.#text.pending = \"\";\n this.#reasoning.pending = \"\";\n rec.substantive ||= content.some((p) =>\n p.type === \"text\" || p.type === \"reasoning\" ? (p.text ?? \"\").length > 0 : true,\n );\n this.#emittedByMessageId.set(message.id, rec);\n this.#deltasSinceSnapshot = false;\n // Delta ownership is settled: reconcile revisions that were deferred\n // while the race was open. They are now revisions of an EARLIER\n // message and get the same bracketed suffix-or-suppress treatment.\n for (const [id, deferred] of this.#deferredRevisions) {\n if (id === message.id) continue;\n const deferredRec = this.#emittedByMessageId.get(id);\n if (deferredRec) this.#reconcileRevision(out, deferredRec, deferred);\n this.#deferredRevisions.delete(id);\n }\n }\n // Tool calls/results and sources are id-deduped, so snapshots process them\n // unconditionally (even when the snapshot is a text/reasoning no-op).\n for (const part of content) {\n if (part.type === \"tool-call\" && !this.#isClientTool(part.tool_name))\n this.#emitServerToolCall(out, part);\n else if (part.type === \"tool-result\" && !this.#isClientTool(part.tool_name))\n this.#emitServerToolResult(out, part);\n else if (part.type === \"source\") this.#emitSource(out, part);\n }\n } else if (message.role === \"tool\") {\n for (const part of content) {\n if (part.type === \"tool-result\" && !this.#isClientTool(part.tool_name))\n this.#emitServerToolResult(out, part);\n else if (part.type === \"source\") this.#emitSource(out, part);\n }\n }\n }\n\n // --- finish ---------------------------------------------------------------\n\n /**\n * Flushes revisions still deferred behind a delta-ownership race that never\n * settled — the racing step ended terminally without committing a snapshot,\n * so the drain in #ingestMessage never ran. chatd sent each revision once;\n * this is the segment's last chance to emit its suffix (#78). Attribution\n * care: the pending deltas' owner never committed, so they attribute to no\n * one — reconciliation is against the ledger alone, and only\n * ledger-extension suffixes may emit. Runs at finish() and, because\n * doGenerate() throws on a streamed error part and closes the generator\n * before finish() (issue #72), ahead of the error part in ingest().\n */\n #flushDeferredRevisions(out: LanguageModelV4StreamPart[]): void {\n for (const [id, deferred] of this.#deferredRevisions) {\n const rec = this.#emittedByMessageId.get(id);\n if (rec) this.#reconcileRevision(out, rec, deferred);\n }\n this.#deferredRevisions.clear();\n }\n\n finish(): LanguageModelV4StreamPart[] {\n const out: LanguageModelV4StreamPart[] = [];\n this.#flushDeferredRevisions(out);\n this.#closeText(out);\n this.#closeReasoning(out);\n\n let unified: LanguageModelV4FinishReason[\"unified\"];\n if (this.#error || this.#terminalStatus === \"error\") unified = \"error\";\n else if (this.#promotionBoundary) {\n // The wire cannot say WHY a run ended at a promotion boundary:\n // \"completed, then promoted\" (FinishTurn) and \"interrupted, then\n // promoted\" (FinishInterruption, `busy_behavior: \"interrupt\"`) emit the\n // same events — promoted user message, queue_update, `running` — and no\n // terminal status for the finished run. \"stop\" is a deliberate\n // approximation covering both: completion is the common case (every\n // queued follow-up on a healthy run promotes through FinishTurn), and\n // no unified reason expresses \"interrupted\". It also outranks\n // \"tool-calls\": a pause superseded by the interrupt that produced this\n // boundary must not send the AI SDK to resume a run that no longer\n // exists. `raw` stays undefined — fabricating a terminal status the\n // server never sent would mislead observability (the segment:settle\n // event carries a finishReason but no `status` for this settle shape).\n // Exact classification needs a wire signal for the finished run;\n // issue #119 is the upstream ask.\n unified = \"stop\";\n } else if (this.#clientToolCallSeen || this.#terminalStatus === \"requires_action\")\n unified = \"tool-calls\";\n else unified = \"stop\";\n\n const usage = accumulateUsage(this.#usageByMessageId);\n\n // Surface server-side cost/runtime (sent by newer servers as extra usage\n // fields), summed over the segment's steps, under `providerMetadata.coder`.\n // Omitted entirely when the server sent neither, so old servers look\n // unchanged. NOTE: the AI SDK propagates only the FINAL segment's\n // providerMetadata to `result.providerMetadata` — whole-turn cost for a\n // client-tool turn is the sum over `result.steps[*].providerMetadata`.\n const coder: JSONObject = {};\n if (usage?.total_cost_micros !== undefined) coder.total_cost_micros = usage.total_cost_micros;\n if (usage?.total_runtime_ms !== undefined) coder.total_runtime_ms = usage.total_runtime_ms;\n\n out.push({\n type: \"finish\",\n usage: mapUsage(usage),\n finishReason: { unified, raw: this.#terminalStatus },\n ...(Object.keys(coder).length > 0 ? { providerMetadata: { coder } } : {}),\n });\n return out;\n }\n}\n","import type {\n LanguageModelV4,\n LanguageModelV4CallOptions,\n LanguageModelV4Content,\n LanguageModelV4GenerateResult,\n LanguageModelV4StreamPart,\n LanguageModelV4StreamResult,\n LanguageModelV4Usage,\n SharedV4Warning,\n} from \"@ai-sdk/provider\";\nimport { assertSupportedAiVersion } from \"../ai-version.js\";\nimport { CoderAgentError, CoderApiError, CoderChatError, CoderStreamError } from \"../errors.js\";\nimport { CoderChatClient } from \"../coder/client.js\";\nimport type {\n ChatInputPart,\n ChatQueuedMessage,\n ChatStatus,\n ChatStreamEvent,\n ChatStreamToolCall,\n CreateChatMessageRequest,\n CreateChatRequest,\n ReasoningEffort,\n} from \"../coder/types.js\";\nimport { dataContentToFileContent } from \"../files.js\";\nimport {\n classifyTurnAction,\n dynamicToolNames,\n extractSystemPrompt,\n type FilePartUploader,\n toolsToDynamicTools,\n type UserContent,\n userContentToInputParts,\n} from \"./prompt.js\";\nimport { nextStreamReaderId } from \"../coder/ws.js\";\nimport { SessionChatStream } from \"./session-stream.js\";\nimport { TurnTranslator, type TurnTranslatorOptions } from \"./translate.js\";\nimport {\n safeTransportEmitter,\n type SegmentSettleTransportEvent,\n type SegmentStartTransportEvent,\n type TransportEventHandler,\n} from \"../transport-events.js\";\n\n/**\n * How long a segment that has settled into `requires_action` waits for the\n * `action_required` event before recovering the pending tool calls from chat\n * history over REST (see `#recoverRequiresAction`). Long enough that the\n * normal fast path — the event follows the status on the same connection,\n * typically within milliseconds — never fires it; short enough to turn a lost\n * event from a `requestTimeoutMs`-sized hang into a ~2s hiccup.\n */\nconst ACTION_REQUIRED_GRACE_MS = 2_000;\n\n/** Sentinel resolved by the grace timer, distinguishable from a stream read. */\nconst GRACE_EXPIRED = Symbol(\"action-required-grace-expired\");\n\n/**\n * Sentinel resolved when the SEGMENT's abort signal fires. Segment reads race\n * this against the shared stream instead of wiring the signal into the stream\n * itself: the stream's fate is a session policy decided at segment exit (a\n * healthy client-tool pause survives its segment; every other exit closes the\n * stream), never a side effect of signal chaining.\n */\nconst SEGMENT_ABORTED = Symbol(\"segment-aborted\");\n\nfunction startGraceTimer(ms: number) {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const expired = new Promise<typeof GRACE_EXPIRED>((resolve) => {\n timer = setTimeout(() => resolve(GRACE_EXPIRED), ms);\n });\n return { expired, cancel: () => clearTimeout(timer) };\n}\n\n/**\n * Observability out-params threaded from the `segment:*` wrapper into\n * `#runTurnInner`, read by the wrapper's `finally` for settle classification\n * on paths where the inner generator is closed without delivering its normal\n * terminal parts:\n * - `chatId`: the segment's chat id once known, surviving a session discard\n * (`resetSession()`) that clears the instance field before the settle\n * event is emitted;\n * - `terminalStatus`: the translator's terminal chat status, for a settle\n * whose `finish` part never reached the wrapper — `doGenerate()` throws on\n * a streamed error part and closes the generator before `finish()` runs\n * (issue #72);\n * - `timeoutError`: set when the turn's internal `requestTimeoutMs` signal\n * fired. That signal is created inside the turn and is invisible to the\n * wrapper, so a `gen.return()` teardown (consumer cancel with no pull\n * pending) would otherwise misclassify the timeout as a consumer teardown\n * (issue #73);\n * - `reader`: the id of the reader (`streamChatEvents` call) serving the\n * segment, written at stream acquisition; the settle carries it so\n * segments correlate with the connection's `ws:*` events (issue #94).\n */\ninterface SegmentChatRef {\n chatId?: string;\n terminalStatus?: ChatStatus;\n timeoutError?: CoderChatError;\n reader?: number;\n}\n\nconst EMPTY_USAGE: LanguageModelV4Usage = {\n inputTokens: {\n total: undefined,\n noCache: undefined,\n cacheRead: undefined,\n cacheWrite: undefined,\n },\n outputTokens: { total: undefined, text: undefined, reasoning: undefined },\n};\n\nexport interface CoderLanguageModelConfig {\n client: CoderChatClient;\n organizationId: string;\n /** Model hint (UUID, `provider:model`, model id, or display-name substring). */\n model?: string;\n /** Reasoning effort sent on chat creation and each user-message submission. */\n reasoningEffort?: ReasoningEffort;\n /** Bind the chat to a Coder workspace (enables workspace tools). */\n workspaceId?: string;\n /** chatd-side MCP servers to enable. */\n mcpServerIds?: string[];\n planMode?: \"\" | \"plan\";\n /** Resume an existing chat instead of creating a new one. */\n chatId?: string;\n /**\n * Resume cursor for `chatId`: the newest chat message id a previous\n * instance had already seen — persist {@link CoderLanguageModel.lastSeenMessageId}\n * alongside the chat id and supply both here. Skips the resumed turn's\n * pre-prompt seed GET (the single-message history probe that would\n * otherwise establish the cursor); without it, behavior is unchanged and\n * the seed GET remains the fallback. Must be a positive integer obtained\n * from the getter, and requires `chatId` (the constructor rejects anything\n * else — a wrong cursor would silently misattribute replayed history).\n */\n lastSeenMessageId?: number;\n /**\n * Per-segment time budget in milliseconds, applied to each model round-trip\n * (one `doStream`/`doGenerate` call: chat creation or message/tool-result\n * submission, plus the server-side run until it settles or pauses for a client\n * tool). If exceeded, the run is interrupted server-side and the call rejects\n * with a retryable {@link CoderChatError} (`kind: \"timeout\"`). A multi-step\n * `generate()` that drives client tools makes several segments, so this bounds\n * each segment, not the whole call — to cap total wall-clock, pass\n * `abortSignal: AbortSignal.timeout(ms)`. Unset or non-positive means no limit.\n */\n requestTimeoutMs?: number;\n /**\n * Observability hook: receives this model's `segment:*` transport events\n * (turn-segment start/settle with terminal status and timing). HTTP and\n * WebSocket events are emitted by the {@link CoderChatClient} — pass the\n * hook to its options too (CoderAgent wires both from one setting).\n * Exceptions it throws are swallowed; without it, no event objects are\n * allocated. See {@link CoderTransportEvent}.\n */\n onTransportEvent?: TransportEventHandler;\n}\n\n/**\n * A {@link LanguageModelV4} that is backed by a remote Coder `chatd` agent\n * runtime instead of a raw LLM. One model instance owns one chatd chat\n * (a \"session\"): the chat is created lazily on the first turn and reused for\n * subsequent turns and for client-tool resume steps.\n *\n * The chatd server runs the agent loop (model calls, server-side tools,\n * compaction) itself. This model therefore represents *one chatd segment* per\n * `doStream` call — it advances the chat until it settles (`waiting`/\n * `completed`) or pauses for a client tool (`requires_action`). When the AI SDK\n * executes a client tool and calls `doStream` again with the tool result, this\n * model resumes the same chat. The two loops mesh at the client-tool boundary.\n *\n * NOTE: a single model instance is single-flight — do not run concurrent\n * generations against the same instance/session.\n *\n * The session retains ONE event stream per turn (issue #44): a segment that\n * pauses for client tools leaves the WebSocket open, and the tool-result\n * resume segment keeps reading it — no re-dial per tool round trip. The\n * socket closes when its segment ends any other way (terminal settle, error,\n * abort, timeout), when the next turn replaces it, or on session teardown\n * (`resetSession()` / `[Symbol.asyncDispose]()`). A turn that pauses for\n * client tools but never resumes leaves the socket open until teardown, so\n * dispose or reset sessions you abandon mid-turn.\n */\nexport class CoderLanguageModel implements LanguageModelV4 {\n readonly specificationVersion = \"v4\" as const;\n readonly provider = \"coder.chatd\";\n readonly modelId: string;\n readonly supportedUrls: Record<string, RegExp[]> = {};\n\n readonly #config: CoderLanguageModelConfig;\n #chatId: string | undefined;\n /**\n * The last chat id this model created or attached to, retained as a CLEANUP\n * target — see {@link lastKnownChatId}. Unlike `#chatId` it survives\n * {@link resetSession}, and is only replaced when a later turn's chat\n * supersedes it or cleared by {@link noteChatCleanedUp} once cleanup\n * actually succeeded against it.\n */\n #lastKnownChatId: string | undefined;\n /**\n * Every chat id stranded by an AUTOMATIC session discard (a stream failure\n * on a chat the failed turn itself created) and not yet cleaned up. A\n * single `#lastKnownChatId` slot is not enough here: each AI-SDK\n * `maxRetries` attempt strands its own fresh chat and only the final\n * attempt's error reaches the caller, so without this ledger every attempt\n * before the last would leak unnamed. Drained by {@link noteChatCleanedUp}.\n * Deliberate abandonment ({@link resetSession}) does NOT record here — the\n * caller chose to walk away and may want the chat kept.\n */\n readonly #strandedChatIds = new Set<string>();\n #lastSeenMessageId = 0;\n #resolvedModelConfigId: string | undefined;\n #modelResolved = false;\n readonly #submittedToolCallIds = new Set<string>();\n #inFlight = false;\n /** The turn-lifetime event stream, retained across client-tool pauses. */\n #retainedStream: SessionChatStream | undefined;\n /** Exception-isolated observability emitter for `segment:*` events. */\n readonly #emitTransportEvent: TransportEventHandler | undefined;\n #segmentSeq = 0;\n\n constructor(config: CoderLanguageModelConfig) {\n // Fail fast on an incompatible AI SDK major (see peer dependency `ai@^7`).\n assertSupportedAiVersion();\n this.#config = config;\n this.modelId = config.model ?? \"chatd\";\n this.#chatId = config.chatId;\n this.#lastKnownChatId = config.chatId;\n // The resume cursor is validated eagerly: internally 0 is the \"no cursor\n // yet\" sentinel (it triggers the seed GET), so a zero/negative/fractional\n // value from the caller must fail fast here rather than silently corrupt\n // the turn boundary — and a cursor without its chat is a caller bug (the\n // pair is persisted together; see the lastSeenMessageId getter).\n if (config.lastSeenMessageId !== undefined) {\n if (!Number.isSafeInteger(config.lastSeenMessageId) || config.lastSeenMessageId <= 0) {\n throw new CoderAgentError(\n `lastSeenMessageId must be a positive integer (got ${config.lastSeenMessageId}); ` +\n \"persist the value of the lastSeenMessageId getter and supply it verbatim.\",\n );\n }\n if (!config.chatId) {\n throw new CoderAgentError(\n \"lastSeenMessageId requires chatId: a resume cursor is meaningless without the chat it belongs to.\",\n );\n }\n this.#lastSeenMessageId = config.lastSeenMessageId;\n }\n this.#emitTransportEvent = safeTransportEmitter(config.onTransportEvent);\n }\n\n get chatId(): string | undefined {\n return this.#chatId;\n }\n\n /**\n * The resume cursor: the newest chat message id this session has observed,\n * or `undefined` before any turn has streamed (nothing to persist then —\n * a fresh instance seeds from history instead). Persist it alongside\n * {@link chatId} once a turn settles and supply both to a future instance\n * ({@link CoderLanguageModelConfig.lastSeenMessageId}) to skip the resumed\n * turn's pre-prompt seed GET. Read it between turns, not mid-generation:\n * the cursor advances when a segment ends.\n */\n get lastSeenMessageId(): number | undefined {\n return this.#lastSeenMessageId > 0 ? this.#lastSeenMessageId : undefined;\n }\n\n /**\n * The last chat id this model created or attached to, for CLEANUP targeting\n * only — generation always targets {@link chatId}. It survives\n * {@link resetSession} (including the automatic session discard after a\n * fresh-chat stream failure, which clears {@link chatId} so a retry starts\n * fresh while the failed chat still exists server-side), is superseded when\n * a later turn creates or attaches to another chat, and is cleared via\n * {@link noteChatCleanedUp} once cleanup actually succeeded against it.\n */\n get lastKnownChatId(): string | undefined {\n return this.#lastKnownChatId;\n }\n\n /**\n * Chat ids stranded by automatic session discards and not yet cleaned up,\n * oldest first. Each retryable fresh-chat stream failure strands one chat;\n * under AI-SDK `maxRetries` several can accumulate while only the final\n * attempt's error (and {@link lastKnownChatId}) names the newest —\n * `CoderAgent.archive()` retires all of them. Ids leave the list only via\n * {@link noteChatCleanedUp}.\n */\n get strandedChatIds(): string[] {\n return [...this.#strandedChatIds];\n }\n\n /**\n * Forgets `chatId` as a cleanup target — call it only after cleanup\n * actually succeeded against that chat (e.g. `CoderAgent.archive()`\n * archived it), so later cleanup calls don't re-target an already-archived\n * chat: removes it from {@link strandedChatIds} and, when it is the\n * last-known id, clears {@link lastKnownChatId} (match-guarded: a stale\n * completion must not erase a newer chat's id). The session field\n * ({@link chatId}) is deliberately untouched — archiving does not reset the\n * session.\n */\n noteChatCleanedUp(chatId: string): void {\n this.#strandedChatIds.delete(chatId);\n if (this.#lastKnownChatId === chatId) this.#lastKnownChatId = undefined;\n }\n\n /**\n * Drops the current session so the next turn creates a fresh chat. Also\n * closes the retained event stream, if any (fire-and-forget — a sync reset\n * must not wait on socket teardown, and `close()` never rejects).\n * {@link lastKnownChatId} deliberately survives the reset: the dropped chat\n * still exists server-side and cleanup must still be able to target it.\n */\n resetSession(): void {\n this.#chatId = undefined;\n this.#lastSeenMessageId = 0;\n this.#submittedToolCallIds.clear();\n const stream = this.#retainedStream;\n this.#retainedStream = undefined;\n if (stream) void stream.close();\n }\n\n /**\n * Closes the retained event stream, if any. The stream otherwise closes\n * when its turn settles or fails, or when the next turn replaces it — but a\n * turn that pauses for client tools and never resumes (e.g. the caller\n * stopped the tool loop, or aborted between segments) leaves the socket\n * open, so dispose (`await using`) or {@link resetSession} sessions you\n * abandon mid-turn.\n */\n async [Symbol.asyncDispose](): Promise<void> {\n const stream = this.#retainedStream;\n this.#retainedStream = undefined;\n if (stream) await stream.close();\n }\n\n /**\n * Closes a stream retained by a client-tool pause. Called on explicit\n * interrupts ({@link CoderAgent.interrupt}): an interrupted pause is dead —\n * its buffer would otherwise collect the interrupt's own settle events,\n * which a later tool-result resume would consume as if they were the\n * resumed generation's (truncating the turn), and if the tool loop never\n * resumes the socket would outlive the explicit interrupt. A stream a\n * segment is actively reading is left alone: that segment observes the\n * settle the interrupt produces and closes the stream itself.\n */\n async closePausedStream(): Promise<void> {\n const stream = this.#retainedStream;\n if (!stream || stream.attached) return;\n this.#retainedStream = undefined;\n await stream.close();\n }\n\n /**\n * The stream a segment reads: a resume segment re-attaches to the stream\n * its predecessor left paused at `requires_action` (the whole point of\n * retention — no re-dial per tool round trip); everything else — a new\n * turn, or a pause the caller abandoned — replaces it with a fresh dial,\n * because a stale buffer from a settle this client never observed must not\n * leak into the new turn.\n */\n #acquireStream(resume: boolean, chatId: string, afterId: number | undefined): SessionChatStream {\n const retained = this.#retainedStream;\n if (resume && retained?.canAttach(chatId)) {\n retained.attach();\n return retained;\n }\n this.#retainedStream = undefined;\n if (retained) void retained.close();\n // Allocated here (not inside streamChatEvents) so the model knows the id\n // the reader will stamp on its `ws:*` events — `segment:settle` carries\n // it to correlate segments with the connection that served them (#94).\n const reader = nextStreamReaderId();\n const created = new SessionChatStream({\n chatId,\n reader,\n open: (streamSignal) =>\n this.#config.client.streamEvents(chatId, { afterId, signal: streamSignal, reader }),\n });\n this.#retainedStream = created;\n return created;\n }\n\n /** Close `stream` and drop the retention slot if it still holds it. */\n async #closeStream(stream: SessionChatStream): Promise<void> {\n if (this.#retainedStream === stream) this.#retainedStream = undefined;\n await stream.close();\n }\n\n async #resolveModelConfigId(signal?: AbortSignal): Promise<string | undefined> {\n if (this.#modelResolved) return this.#resolvedModelConfigId;\n if (this.#config.model) {\n this.#resolvedModelConfigId = await this.#config.client.resolveModelConfigId(\n this.#config.model,\n this.#config.organizationId,\n signal,\n );\n }\n this.#modelResolved = true;\n return this.#resolvedModelConfigId;\n }\n\n /**\n * Resolve a user message's content to chatd input parts, uploading any file\n * parts to chat-file storage (the upload endpoint needs only the organization\n * id, so this runs before the chat exists). Pre-uploaded files carried via\n * `providerOptions.coder.fileId` are referenced without re-uploading.\n */\n #buildContent(\n content: UserContent,\n signal?: AbortSignal,\n onUpload?: () => void,\n ): Promise<ChatInputPart[]> {\n const uploadFile: FilePartUploader = async (f) => {\n const uploaded = await this.#config.client.uploadChatFile(\n this.#config.organizationId,\n { content: dataContentToFileContent(f.data), mediaType: f.mediaType, name: f.filename },\n signal,\n );\n onUpload?.();\n return uploaded.id;\n };\n return userContentToInputParts(content, uploadFile);\n }\n\n /**\n * REST fallback for a `requires_action` segment whose `action_required`\n * event never arrived: recover the turn's pending client tool calls from\n * committed chat history (`GET /chats/{id}/messages`) — the same history\n * chatd itself derives `action_required` from server-side. Returns synthetic\n * stream events for the translator: the turn's message snapshots first (so\n * text, usage, and the message cursor stay consistent even if the stream\n * also missed a commit — the translator's id-keyed dedup makes re-ingesting\n * already-delivered ones a no-op), then one `action_required` event carrying\n * the unresolved dynamic tool calls. Returns `[]` when history shows no\n * pending calls — nothing to recover, the caller keeps waiting on the\n * stream, bounded by `requestTimeoutMs`/abort.\n */\n async #recoverRequiresAction(\n chatId: string,\n dynamicNames: ReadonlySet<string>,\n /** The turn's EFFECTIVE cursor (`TurnTranslator.turnCursor`). */\n turnCursor: number,\n signal?: AbortSignal,\n ): Promise<ChatStreamEvent[]> {\n // Fetch WITHOUT an `after_id` cursor: cursored reads page ASCENDING\n // (oldest first), so a long turn would truncate away the newest messages\n // — exactly where the pending call lives. A cursor-less read pages\n // newest-first, which guarantees the last assistant message and\n // everything after it (the inputs of the derivation below) are on the\n // FIRST page; `has_more` then only refers to older history we don't\n // need. The turn filter is applied client-side instead.\n const { messages } = await this.#config.client.getMessages(chatId, { limit: 200 }, signal);\n // Restrict to this turn's messages (the translator would skip older ids\n // anyway, but the derivation must not see earlier turns), in id order.\n const turnMessages = messages.filter((m) => m.id > turnCursor).sort((a, b) => a.id - b.id);\n // Mirror chatd's own derivation (`actionRequiredFromHistory` →\n // `unresolvedToolCallsFromHistory`): the pending calls are the LAST\n // assistant message's non-provider-executed dynamic tool-call parts,\n // minus ids already handled by later messages' tool results.\n const lastAssistant = turnMessages.findLast((m) => m.role === \"assistant\");\n if (!lastAssistant) return [];\n const handled = new Set<string>();\n for (const message of turnMessages) {\n if (message.id <= lastAssistant.id) continue;\n for (const part of message.content ?? []) {\n if (part.type === \"tool-result\" && part.tool_call_id) handled.add(part.tool_call_id);\n }\n }\n const toolCalls: ChatStreamToolCall[] = [];\n for (const part of lastAssistant.content ?? []) {\n if (part.type !== \"tool-call\" || part.provider_executed) continue;\n if (!part.tool_call_id || !part.tool_name) continue;\n if (!dynamicNames.has(part.tool_name)) continue;\n if (handled.has(part.tool_call_id)) continue;\n // A call whose result this instance already submitted must not be\n // re-emitted — the AI SDK would execute the tool a second time.\n if (this.#submittedToolCallIds.has(part.tool_call_id)) continue;\n toolCalls.push({\n tool_call_id: part.tool_call_id,\n tool_name: part.tool_name,\n // `action_required` carries args as their raw JSON text (chatd sends\n // `string(part.Args)` of the history part's json.RawMessage);\n // re-encoding the snapshot's parsed JSON value reconstructs it. A\n // JSON `null` is a legitimate argument value and must survive; only\n // an ABSENT args field falls back to empty-object args.\n args: JSON.stringify(part.args === undefined ? {} : part.args),\n });\n }\n if (toolCalls.length === 0) return [];\n const events: ChatStreamEvent[] = turnMessages.map((message) => ({\n type: \"message\",\n chat_id: chatId,\n message,\n }));\n events.push({\n type: \"action_required\",\n chat_id: chatId,\n action_required: { tool_calls: toolCalls },\n });\n return events;\n }\n\n /**\n * Wraps {@link #runTurnInner} with `segment:start`/`segment:settle`\n * observability events. A pure pass-through without a subscriber — no\n * counters, clocks, or event objects on the unobserved path. The settle\n * event is emitted in a `finally`, so every started segment settles exactly\n * once: cleanly (with the terminal status carried by the `finish` part that\n * streamed through), with an `error` summary, or — when the consumer tears\n * the stream down mid-segment — with neither. Consumer teardown has two\n * shapes: a plain generator return (no catch runs), and a\n * `ReadableStream.cancel()` that {@link doStream} routes through an abort to\n * unblock a pending socket read — the latter surfaces here as an abort\n * throw, so `isConsumerCancel` (supplied by `doStream`) reclassifies it as\n * teardown rather than a failed segment.\n *\n * Two more classification wrinkles (review findings):\n * - a streamed server `error` part is captured BEFORE being yielded —\n * `doGenerate()` throws on receiving it and closes this generator without\n * another pull, so the catch below never sees that failure;\n * - the segment's chat id is tracked in `segmentChat` (written by\n * {@link #runTurnInner} once known) — a retry-safe stream failure on a\n * freshly created chat discards the session (clearing `this.#chatId`)\n * before the finally runs, and the settle must still name the chat that\n * the preceding `ws:*`/`http:*` events identified. The same out-param\n * carries the translator's terminal status and the internal-timeout\n * marker for generator-return exits (see {@link SegmentChatRef}).\n */\n async *#runTurn(\n options: LanguageModelV4CallOptions,\n isConsumerCancel?: () => boolean,\n ): AsyncGenerator<LanguageModelV4StreamPart, void, void> {\n const emit = this.#emitTransportEvent;\n if (!emit) {\n yield* this.#runTurnInner(options);\n return;\n }\n const segment = ++this.#segmentSeq;\n const startedAt = performance.now();\n const startEvent: SegmentStartTransportEvent = {\n type: \"segment:start\",\n segment,\n timestamp: Date.now(),\n };\n if (this.#chatId !== undefined) startEvent.chatId = this.#chatId;\n emit(startEvent);\n const segmentChat: SegmentChatRef = {};\n let finishReason: LanguageModelV4GenerateResult[\"finishReason\"] | undefined;\n let failure: unknown;\n let failed = false;\n try {\n for await (const part of this.#runTurnInner(options, segmentChat)) {\n if (part.type === \"finish\") finishReason = part.finishReason;\n else if (part.type === \"error\" && !failed) {\n failure = part.error;\n failed = true;\n }\n yield part;\n }\n } catch (err) {\n // An intentional consumer cancel is a teardown, not a failed segment —\n // even though it reaches the turn as an abort (see the doc comment).\n if (!isConsumerCancel?.()) {\n failure = err;\n failed = true;\n }\n throw err;\n } finally {\n const settleEvent: SegmentSettleTransportEvent = {\n type: \"segment:settle\",\n segment,\n durationMs: performance.now() - startedAt,\n timestamp: Date.now(),\n };\n const chatId = segmentChat.chatId ?? this.#chatId;\n if (chatId !== undefined) settleEvent.chatId = chatId;\n // Absent when the segment failed before acquiring a stream — never\n // guessed (a predicted reader could name a connection that never\n // served this segment; see the field's doc comment).\n if (segmentChat.reader !== undefined) settleEvent.reader = segmentChat.reader;\n // An abort or timeout can also end the segment through generator\n // return() while suspended at a yield (e.g. the consumer cancels after\n // the abort with no pull pending) — return() runs this finally WITHOUT\n // the catch above ever seeing an exception. If the segment would\n // otherwise look like a teardown, classify by termination source:\n // - the turn's internal requestTimeoutMs having fired wins (it already\n // interrupted the server run, and it is invisible to both the\n // caller's signal and the consumer-cancel probe — issue #73);\n // - otherwise a caller abort the consumer did not initiate settles as\n // a failure with the abort reason.\n if (!failed && finishReason === undefined) {\n if (segmentChat.timeoutError) {\n failure = segmentChat.timeoutError;\n failed = true;\n } else if (options.abortSignal?.aborted && !isConsumerCancel?.()) {\n failure = options.abortSignal.reason;\n failed = true;\n }\n }\n if (failed) {\n settleEvent.error = {\n name: failure instanceof Error ? failure.name : \"Error\",\n message: failure instanceof Error ? failure.message : String(failure),\n };\n }\n if (finishReason !== undefined) {\n // finishReason.raw is the translator's terminal chat status. An error\n // settle keeps it too when the server run itself settled terminally\n // (`status: \"error\"` streamed alongside the error part).\n if (finishReason.raw !== undefined) settleEvent.status = finishReason.raw as ChatStatus;\n if (!failed) settleEvent.finishReason = finishReason.unified;\n } else if (segmentChat.terminalStatus !== undefined) {\n // The finish part never reached this wrapper — doGenerate() throws on\n // a streamed error part and closes the generator without another pull\n // — but the translator still observed the terminal status batched\n // behind the error in the same frame (issue #72).\n settleEvent.status = segmentChat.terminalStatus;\n }\n emit(settleEvent);\n }\n }\n\n async *#runTurnInner(\n options: LanguageModelV4CallOptions,\n /** Observability out-param: the segment's chat id, written once known. */\n segmentChat?: SegmentChatRef,\n ): AsyncGenerator<LanguageModelV4StreamPart, void, void> {\n // A single model instance owns one chatd session's mutable state, so it is\n // single-flight: reject overlapping turns rather than silently corrupting\n // chatId / lastSeenMessageId / submitted tool-call tracking.\n if (this.#inFlight) {\n throw new CoderAgentError(\n \"A generation is already in flight on this CoderAgent (single-flight). Use a separate CoderAgent instance for concurrent sessions.\",\n );\n }\n this.#inFlight = true;\n\n // Combine the caller's abort signal with an optional per-turn timeout into a\n // single signal (the platform composes and cleans these up for us). When\n // there's neither a caller signal nor a timeout, `signal` stays undefined and\n // there is no per-turn setup. Keep a reference to our *own* timeout signal so\n // a timeout stays distinguishable from a caller abort — even when the caller's\n // own signal is itself an `AbortSignal.timeout` (whose reason is a TimeoutError\n // too, so reason-sniffing alone would misclassify it).\n const externalSignal = options.abortSignal;\n const timeoutMs = this.#config.requestTimeoutMs;\n const timeoutSignal =\n timeoutMs !== undefined && timeoutMs > 0 ? AbortSignal.timeout(timeoutMs) : undefined;\n const sources = [externalSignal, timeoutSignal].filter(\n (s): s is AbortSignal => s !== undefined,\n );\n const signal: AbortSignal | undefined =\n sources.length > 0 ? AbortSignal.any(sources) : undefined;\n\n // Declared here so `finally` can read whether the turn settled; constructed\n // only once the turn's starting message id is known (see `turnCursor`).\n let translator: TurnTranslator | undefined;\n\n // Whether this call took ownership of the session stream (#acquireStream\n // ran). Until then, a stream retained by the PREDECESSOR segment's pause\n // has no owner watching it — a REST-phase failure below must close it\n // (see the outer finally) rather than leave it retained: the turn is dead\n // (and the run interrupted), so a caller retry attaching to its buffer\n // would read interrupt-era events and end before the resumed generation.\n let streamAcquired = false;\n\n // Set when a stream failure kills a chat that THIS call created: the\n // `finally` then drops the session (after interrupting the dead run) so an\n // automatic whole-call retry replays the turn on a fresh chat.\n let discardSession = false;\n\n // Interrupting the *server* run (not just closing the WebSocket) is what frees\n // the chat's workspace/resources. Fire it at most once — on abort/timeout, and\n // on teardown of an unsettled turn (see `finally`), which also covers stream\n // `cancel()` and premature close, neither of which aborts the signal. A chat\n // whose id we never received (createChat aborted mid-flight) can't be reached.\n let interruptSent = false;\n // The turn's own chat id, captured once known: the interrupt must reach\n // THIS turn's run even after a concurrent resetSession() cleared the\n // session id mid-segment (which also closes the attached stream and fails\n // the segment — whose teardown then still stops the orphaned server run).\n let turnChatId: string | undefined;\n // Whether the submission was QUEUED (chat busy — see #114), and its queue\n // entry, once known. Set before the translator exists, so the teardown\n // below can classify a REST-phase failure after the queueing response.\n let queuedTurn = false;\n let queuedEntry: ChatQueuedMessage | undefined;\n const interrupt = (): void => {\n const id = turnChatId ?? this.#chatId;\n if (interruptSent || !id) return;\n // Settled at a promotion boundary (issue #120): the chat's live run\n // belongs to the promoted submission, not to this turn — a late\n // abort/timeout racing the settle must not kill someone else's turn.\n // (Terminal-status settles don't need this: their chat is idle and an\n // interrupt is a harmless 409.)\n if (translator?.settledAtPromotionBoundary) return;\n interruptSent = true;\n // A QUEUED turn that never anchored has no server run of its own: the\n // chat's active run belongs to a concurrent/predecessor submission, and\n // interrupting it would kill someone else's work — and, worse, make\n // chatd promote this turn's queue entry into a run nobody is watching.\n // Withdraw the entry instead (best-effort, like the interrupt): the\n // turn is dead, so its prompt must not run unattended later. If the\n // entry is already gone (promoted during the race, or deleted\n // externally), there is no reliable ownership evidence for whatever is\n // running now — leave it alone rather than risk killing an innocent\n // run. Without an entry id (unexpected wire shape) do nothing, for the\n // same reason.\n if (queuedTurn && (translator === undefined || translator.awaitingQueuedSubmission)) {\n const entryId = queuedEntry?.id;\n if (entryId !== undefined) {\n void this.#config.client.deleteQueuedMessage(id, entryId).catch(() => {});\n }\n return;\n }\n void this.#config.client.interruptChat(id).catch(() => {});\n };\n signal?.addEventListener(\"abort\", interrupt, { once: true });\n\n // Map an abort of the combined signal to the right error. Our own\n // `timeoutSignal` having fired means a per-turn timeout; otherwise it's a\n // caller abort, re-thrown as the caller's reason (preserving AbortError so the\n // AI SDK still recognizes the cancellation).\n const timeoutError = (): CoderChatError =>\n new CoderChatError({\n message: `Coder Agent turn exceeded its ${timeoutMs}ms requestTimeoutMs budget.`,\n kind: \"timeout\",\n retryable: true,\n });\n const throwIfAborted = (): void => {\n if (timeoutSignal?.aborted) {\n throw timeoutError();\n }\n if (signal?.aborted) {\n throw (\n externalSignal?.reason ?? new DOMException(\"The operation was aborted.\", \"AbortError\")\n );\n }\n };\n\n try {\n const { prompt } = options;\n\n // chatd does not constrain output to a JSON schema server-side, so a\n // `responseFormat: json` request can't be honored. Warn rather than\n // silently mislead — schema-constrained output should go through the\n // provider (@coder/ai-sdk-provider) instead.\n const warnings: SharedV4Warning[] = [];\n if (options.responseFormat?.type === \"json\") {\n warnings.push({\n type: \"unsupported\",\n feature: \"responseFormat\",\n details:\n \"Coder Agents does not enforce a JSON schema server-side, so structured output is best-effort (not schema-constrained). For reliable structured output, use @coder/ai-sdk-provider (createCoder) with generateObject / Output.object.\",\n });\n }\n yield { type: \"stream-start\", warnings };\n\n const action = classifyTurnAction(prompt);\n if (action.kind === \"noop\") {\n throw new CoderAgentError(\n \"CoderAgent received a prompt with no user message or tool results to act on.\",\n );\n }\n\n let afterId: number | undefined;\n // Whether THIS call created the chat — the one case where an automatic\n // whole-call retry after a stream failure is safe (see the\n // CoderStreamError handling below).\n let turnCreatedChat = false;\n // Whether this call uploaded inline file attachments — an external\n // effect a whole-call retry would repeat, creating redundant file\n // records (pre-uploaded `fileId` references don't set this).\n let uploadedAttachment = false;\n // Whether the chat ended up bound to a workspace — configured OR\n // assigned server-side by the deployment (createChat's response says).\n // Either way its tools can have non-idempotent effects a replay would\n // repeat.\n let chatHasWorkspace = Boolean(this.#config.workspaceId);\n // Same for chatd-side MCP servers: configured OR auto-attached by the\n // deployment (createChat's response `mcp_server_ids` says — issue #58).\n let chatHasMcpServers = Boolean(this.#config.mcpServerIds?.length);\n\n // A fresh instance resuming an existing chat (config.chatId) has no\n // message cursor yet. Without one, a queued submission or a tool-result\n // resume falls back to afterId 0: the server replays the chat's FULL\n // history and this turn would absorb every earlier turn's content and\n // usage. Seed the cursor from the chat's newest message instead (the\n // messages endpoint pages newest-first; an empty chat has nothing to\n // replay, so 0 stays correct there). Skipped when the caller supplied\n // the cursor (config.lastSeenMessageId, validated at construction) —\n // that is the same value a previous instance's turn established, minus\n // this round-trip (#115).\n if (this.#chatId && this.#lastSeenMessageId === 0) {\n const { messages } = await this.#config.client.getMessages(\n this.#chatId,\n { limit: 1 },\n signal,\n );\n this.#lastSeenMessageId = messages[0]?.id ?? 0;\n }\n\n if (action.kind === \"new-turn\") {\n // Resolve the model config and upload any file parts concurrently — they\n // are independent round-trips. (Uploads run before the chat exists and\n // resolve file parts to `file` input parts referencing their uploaded ids.)\n const [modelConfigId, content] = await Promise.all([\n this.#resolveModelConfigId(signal),\n this.#buildContent(action.content, signal, () => {\n uploadedAttachment = true;\n }),\n ]);\n if (!this.#chatId) {\n const req: CreateChatRequest = {\n organization_id: this.#config.organizationId,\n content,\n client_type: \"api\",\n };\n const system = extractSystemPrompt(prompt);\n if (system) req.system_prompt = system;\n const tools = toolsToDynamicTools(options.tools);\n if (tools.length > 0) req.unsafe_dynamic_tools = tools;\n if (modelConfigId) req.model_config_id = modelConfigId;\n if (this.#config.reasoningEffort !== undefined) {\n req.reasoning_effort = this.#config.reasoningEffort;\n }\n if (this.#config.workspaceId) req.workspace_id = this.#config.workspaceId;\n if (this.#config.mcpServerIds?.length) req.mcp_server_ids = this.#config.mcpServerIds;\n if (this.#config.planMode) req.plan_mode = this.#config.planMode;\n const chat = await this.#config.client.createChat(req, signal);\n this.#chatId = chat.id;\n turnCreatedChat = true;\n if (chat.workspace_id) chatHasWorkspace = true;\n if (chat.mcp_server_ids?.length) chatHasMcpServers = true;\n afterId = this.#lastSeenMessageId > 0 ? this.#lastSeenMessageId : undefined;\n } else {\n const req: CreateChatMessageRequest = { content };\n if (modelConfigId) req.model_config_id = modelConfigId;\n if (this.#config.reasoningEffort !== undefined) {\n req.reasoning_effort = this.#config.reasoningEffort;\n }\n const resp = await this.#config.client.createChatMessage(this.#chatId, req, signal);\n if (resp.message) {\n afterId = resp.message.id;\n } else if (resp.queued) {\n // QUEUED submission (#114): the chat was busy, so the response\n // carries only a queue entry — no committed message id. The\n // stream window from the pre-turn cursor up to the entry's\n // materialization belongs to the chat's CURRENT run, so the\n // translator withholds attribution until this turn's own\n // user-message snapshot is observed (`queuedSubmission` below).\n // The stream still dials from the pre-turn cursor: the\n // materialized id is unknown here, and a later cursor would skip\n // the very snapshot the gate anchors on.\n afterId = this.#lastSeenMessageId;\n queuedTurn = true;\n queuedEntry = resp.queued_message;\n } else {\n // Out-of-contract response (neither a committed message nor the\n // queued flag): keep the historical pre-turn-cursor fallback\n // rather than gating on a signal the wire never sent.\n afterId = this.#lastSeenMessageId;\n }\n }\n } else {\n // resume\n if (!this.#chatId)\n throw new CoderChatError({ message: \"cannot submit tool results before a chat exists\" });\n const fresh = action.toolResults.filter(\n (r) => !this.#submittedToolCallIds.has(r.tool_call_id),\n );\n if (fresh.length > 0) {\n await this.#config.client.submitToolResults(this.#chatId, { results: fresh }, signal);\n for (const r of fresh) this.#submittedToolCallIds.add(r.tool_call_id);\n }\n afterId = this.#lastSeenMessageId;\n }\n\n const chatId = this.#chatId as string;\n turnChatId = chatId;\n // The cleanup target outlives the session: a stream failure below can\n // discard the session (resetSession), but the chat persists server-side\n // and archive()/interrupt() must still be able to name it.\n this.#lastKnownChatId = chatId;\n if (segmentChat) segmentChat.chatId = chatId;\n // Only messages past the turn's starting cursor belong to this turn —\n // resuming a chat replays earlier turns' messages (usage included), and\n // a mid-turn history reset re-sends them again. Constructing the\n // translator with the cursor makes it impossible to ingest before the\n // boundary is known.\n const dynamicNames = dynamicToolNames(options.tools);\n const translatorOptions: TurnTranslatorOptions = {\n dynamicToolNames: dynamicNames,\n turnCursor: afterId ?? 0,\n // The retained stream redials with the TURN's original cursor, so a\n // reconnect can replay an earlier segment's `action_required`\n // snapshot; calls this session already answered must not run again.\n submittedToolCallIds: this.#submittedToolCallIds,\n };\n if (queuedTurn) {\n // For a queued submission the cursor above is only the PRE-turn\n // boundary: the translator additionally withholds attribution until\n // the queue entry materializes as this turn's own user message and\n // then advances its effective cursor to that message's id (#114).\n const queuedSubmission: NonNullable<TurnTranslatorOptions[\"queuedSubmission\"]> = {};\n if (queuedEntry?.id !== undefined) queuedSubmission.id = queuedEntry.id;\n if (queuedEntry?.content !== undefined) queuedSubmission.content = queuedEntry.content;\n translatorOptions.queuedSubmission = queuedSubmission;\n }\n translator = new TurnTranslator(translatorOptions);\n // chatd emits the `requires_action` status BEFORE the `action_required`\n // event that carries the pending tool calls, so for that status we keep\n // reading until the client tool calls have actually been emitted (bounded\n // by a safety counter, since the stream is a live subscription). The\n // event can also never arrive at all — consumed by a previous connection\n // across a reconnect race, or a server-side hiccup between the status\n // flip and the history-derived event — and on a quiet socket the read\n // would then block until requestTimeoutMs (minutes) and fail a turn\n // whose tool calls have been sitting fully committed in chat history\n // the whole time. So once `requires_action` settles without a client\n // tool call, a grace timer races the pending read; if it expires, the\n // tool calls are recovered from history over REST instead\n // (#recoverRequiresAction) — one shot per segment, never on the fast\n // path, still bounded by requestTimeoutMs/abort.\n let sinceRequiresAction = 0;\n let grace: ReturnType<typeof startGraceTimer> | undefined;\n // The in-flight recovery fetch. It races the pending stream read rather\n // than being awaited serially: a slow or hanging REST request must not\n // block a late stream event from completing the segment (with\n // requestTimeoutMs unset, a serial await could hang the turn on the\n // fetch indefinitely — the very failure mode this fallback removes).\n // Failures resolve to [] (best-effort): the segment then keeps waiting\n // on the stream, bounded by requestTimeoutMs/abort as before.\n let recovery: Promise<ChatStreamEvent[]> | undefined;\n let recoveryAbort: AbortController | undefined;\n let recoveryAttempted = false;\n // One stream per TURN, not per segment: a resume segment re-attaches to\n // the stream its predecessor left paused at `requires_action`; anything\n // else dials fresh (see #acquireStream). The stream is bound to its OWN\n // lifetime signal — the segment signal instead races reads below, so an\n // abort/timeout detaches this segment without killing the shared socket.\n const stream = this.#acquireStream(action.kind === \"resume\", chatId, afterId);\n streamAcquired = true;\n // A resume segment re-attaches to the retained stream, so this is the\n // ORIGINAL dialing reader's id — segments of one turn share it.\n if (segmentChat) segmentChat.reader = stream.reader;\n let onSegmentAbort: (() => void) | undefined;\n const segmentAborted: Promise<typeof SEGMENT_ABORTED> | undefined = signal\n ? new Promise((resolve) => {\n onSegmentAbort = () => resolve(SEGMENT_ABORTED);\n if (signal.aborted) onSegmentAbort();\n else signal.addEventListener(\"abort\", onSegmentAbort, { once: true });\n })\n : undefined;\n const raceSegment = <T>(p: Promise<T>): Promise<T | typeof SEGMENT_ABORTED> =>\n segmentAborted ? Promise.race([p, segmentAborted]) : p;\n // Whether the segment settled at a healthy client-tool pause — the one\n // exit that keeps the stream for the next segment (see the finally).\n let retainStream = false;\n try {\n for (;;) {\n // The stream memoizes its pending read: this either starts a fresh\n // read or adopts one an earlier iteration (or segment) raced out —\n // an event resolved while nobody was consuming is delivered here,\n // never dropped.\n const next = stream.read();\n const awaitingActionRequired =\n translator.terminalStatus === \"requires_action\" && !translator.clientToolCallSeen;\n if (!awaitingActionRequired && (grace !== undefined || recoveryAttempted)) {\n // The requires_action that armed this wait state was stale — a\n // replayed connect-time snapshot from a redial, cleared by a later\n // non-terminal transition (the chat is generating again). Re-arm\n // the one-shot fallback so a REAL pause later in this segment\n // gets its own grace timer and recovery fetch.\n grace?.cancel();\n grace = undefined;\n recoveryAbort?.abort();\n recoveryAbort = undefined;\n recovery = undefined;\n recoveryAttempted = false;\n sinceRequiresAction = 0;\n }\n let raced:\n | IteratorResult<ChatStreamEvent, void>\n | ChatStreamEvent[]\n | typeof GRACE_EXPIRED\n | typeof SEGMENT_ABORTED;\n if (awaitingActionRequired && !recoveryAttempted) {\n // One timer for the whole wait (not per event), so a server that\n // keeps sending unrelated events cannot postpone the fallback.\n grace ??= startGraceTimer(ACTION_REQUIRED_GRACE_MS);\n raced = await raceSegment(Promise.race([next, grace.expired]));\n if (raced === GRACE_EXPIRED) {\n recoveryAttempted = true;\n // Defensive: if the grace timer beat a concurrent abort,\n // classify before fetching.\n throwIfAborted();\n // A recovery-specific controller (chained to the turn signal)\n // so the losing fetch is canceled when the stream wins or the\n // turn tears down — a hung request must not outlive the\n // segment and leak its connection.\n recoveryAbort = new AbortController();\n recovery = this.#recoverRequiresAction(\n chatId,\n dynamicNames,\n // The turn's EFFECTIVE cursor: for a queued submission it has\n // advanced past the materialized user message (a pause can\n // only arm after the gate anchored), so the derivation never\n // sees the concurrent predecessor run's tail (#114).\n translator.turnCursor,\n signal ? AbortSignal.any([signal, recoveryAbort.signal]) : recoveryAbort.signal,\n ).catch(() => []);\n continue;\n }\n } else if (awaitingActionRequired && recovery) {\n raced = await raceSegment(Promise.race([next, recovery]));\n if (Array.isArray(raced)) {\n recovery = undefined;\n // An aborted fetch resolves [] through the catch above —\n // classify instead of silently waiting out the timeout.\n throwIfAborted();\n for (const ev of raced) for (const part of translator.ingest(ev)) yield part;\n // The losing read stays memoized on the stream — the resume\n // segment adopts it (read() is rejection-tagged, so a late\n // failure while paused cannot surface as unhandled).\n if (translator.clientToolCallSeen) break;\n continue; // nothing recovered — keep waiting on the stream\n }\n } else {\n raced = await raceSegment(next);\n }\n if (raced === SEGMENT_ABORTED) {\n throwIfAborted();\n // Unreachable: the sentinel only resolves once `signal` aborted.\n throw new CoderAgentError(\"segment abort sentinel resolved without an abort\");\n }\n stream.consumed(next);\n if (raced.done) break;\n const parts = translator.ingest(raced.value);\n // A queued submission whose wait can never complete — the chat\n // settled in error with the entry still queued (chatd does not\n // auto-promote the queue after an error), or the entry left the\n // queue without materializing as this turn's user message. Thrown\n // rather than streamed as an error part: the failure is this\n // client's WAIT, not a settle of this turn's run (which never\n // started), so no terminal status is recorded — which also routes\n // teardown to withdraw the stuck entry (see interrupt()).\n const deadEnd = translator.queuedSubmissionDeadEnd;\n if (deadEnd) throw new CoderChatError(deadEnd);\n if (raced.value.type === \"error\" && translator.error && !translator.terminalStatus) {\n // chatd batches the terminal `status: \"error\"` right behind the\n // `error` event in the same WS frame, but a doGenerate() consumer\n // throws on the error part yielded below and closes this\n // generator without another pull — the settle event would then\n // omit a terminal status that was already decoded (issue #72).\n // Peek ONE more event, bounded by a 0ms timer: an event buffered\n // from the same frame resolves in microtasks (the reader yields\n // batched events without awaiting in between), which always beats\n // the macrotask timer, while a quiet socket costs a single timer\n // tick on an already-failing turn. A peek that loses (or rejects)\n // stays memoized on the stream — the next iteration adopts it,\n // so no event or failure is ever dropped.\n const peek = stream.read();\n const bound = startGraceTimer(0);\n const peeked = await raceSegment(Promise.race([peek, bound.expired])).catch(\n (): typeof GRACE_EXPIRED => GRACE_EXPIRED,\n );\n bound.cancel();\n if (peeked !== GRACE_EXPIRED && peeked !== SEGMENT_ABORTED && !peeked.done) {\n stream.consumed(peek);\n parts.push(...translator.ingest(peeked.value));\n }\n }\n for (const part of parts) yield part;\n // Promotion settle boundary (issue #120): a user-role message past\n // the turn's cursor means the run is over on the wire even though\n // no terminal status was (or will be) emitted for it — chatd\n // started the chat's next run atomically with the finish. Settle\n // NOW: everything after this event is the successor run's.\n if (translator.settledAtPromotionBoundary) break;\n const status = translator.terminalStatus;\n if (status) {\n if (status !== \"requires_action\") break;\n if (translator.clientToolCallSeen) break;\n if (++sinceRequiresAction > 200) break;\n }\n }\n // A healthy pause — the turn continues with a tool-result resume, so\n // the stream is retained (the finally pauses it). Every other exit\n // (terminal settle, error settle, premature end) closes it. Never\n // retain past an error settle (the SDK ends the turn there), and\n // never past an abort — one can land while the generator is suspended\n // yielding the settle's tool-call parts, after which this segment\n // still rejects (post-loop throwIfAborted): its pause was never\n // delivered, so a retry must redial fresh and recover the pause from\n // the connect-time snapshot instead of attaching to a socket whose\n // already-consumed requires_action events will not be re-sent.\n retainStream =\n !signal?.aborted &&\n translator.terminalStatus === \"requires_action\" &&\n translator.clientToolCallSeen &&\n !translator.error;\n } catch (err) {\n // Abort surfaces here only if the reader threw instead of closing cleanly;\n // prefer the abort/timeout classification.\n throwIfAborted();\n // The reader redials dropped connections internally (with `after_id`\n // catch-up), so what escapes it is terminal: a CoderApiError (4xx\n // upgrade rejection, re-thrown as-is), a CoderStreamError (redial\n // budget exhausted, or undedupable old-server deltas; handled below),\n // or a bare CoderAgentError (unparseable frame). Surface the latter as\n // a retryable stream failure so a caller's `CoderChatError &&\n // retryable` retry path catches it instead of seeing a bare,\n // non-retryable error.\n //\n // An AI-SDK `maxRetries` retry re-invokes this model with the SAME\n // prompt, and `#runTurn` would submit it AGAIN as a new user turn.\n // That is only safe when BOTH hold: this very call created the chat\n // (discarding the dead session — its run is being interrupted anyway —\n // lets the retry replay the whole turn on a FRESH chat), and the chat\n // has no server-side effectful tooling (a bound workspace or MCP\n // servers), whose tools may have executed before the stream died and\n // would run AGAIN on a replay; the async interrupt cannot undo, or\n // even reliably outrace, such external effects. Client tools are the\n // SDK's own tool loop, which restarts cleanly with the fresh chat. A\n // chat with prior state — resumed sessions, later turns, tool-result\n // segments — would be corrupted by any re-submission. In the unsafe\n // cases the error is downgraded to non-retryable and the caller owns\n // the retry/resume decision.\n if (err instanceof CoderStreamError) {\n // The reader stamps the chat id it streamed, but a custom client's\n // stream source may not — stamp the turn's own id (they name the\n // same chat) so BOTH rethrow paths below keep the documented\n // guarantee that the error names the failed turn's chat. The field\n // is publicly readonly; the model layer owns the error en route.\n if (err.chatId === undefined && turnChatId !== undefined) {\n (err as { chatId: string | undefined }).chatId = turnChatId;\n }\n if (turnCreatedChat) {\n // Nothing worth keeping: the chat's only content is this failed,\n // interrupted turn, and a later manual generate() on this instance\n // must not attach to it either. Record it as stranded FIRST: an\n // automatic retry's fresh chat will supersede #lastKnownChatId,\n // and without the ledger every attempt before the last would leak\n // unnamed (only the final attempt's error reaches the caller).\n if (turnChatId) this.#strandedChatIds.add(turnChatId);\n discardSession = true;\n }\n const effectful = chatHasWorkspace || chatHasMcpServers || uploadedAttachment;\n if (turnCreatedChat && !effectful) throw err;\n throw new CoderStreamError({\n message: `${err.message}; automatic retry is disabled because ${\n turnCreatedChat\n ? \"the turn has server-side effects a replay would repeat (workspace/MCP tools that may have executed, or freshly uploaded attachments)\"\n : \"the chat has prior state (a retry would resubmit this turn's prompt as a new user turn)\"\n }`,\n url: err.url,\n cause: err,\n isRetryable: false,\n chatId: err.chatId,\n });\n }\n if (\n err instanceof CoderAgentError &&\n !(err instanceof CoderApiError) &&\n !(err instanceof CoderChatError)\n ) {\n throw new CoderChatError({\n message: `Coder chat stream failed mid-turn: ${err.message}`,\n kind: \"stream_closed\",\n retryable: true,\n });\n }\n throw err;\n } finally {\n grace?.cancel();\n // Cancel a still-pending (losing) recovery fetch: the segment's exit\n // must not leave its HTTP request alive. (Settled/consumed recovery\n // makes this a no-op; the fetch rejection resolves to [] above.)\n recoveryAbort?.abort();\n if (signal && onSegmentAbort) signal.removeEventListener(\"abort\", onSegmentAbort);\n // The stream's fate, by exit kind: a healthy client-tool pause\n // retains it (paused) for the resume segment, with a read outstanding\n // so a drop during tool execution redials in the background; every\n // other exit — terminal settle, error settle, fatal stream error,\n // premature end, consumer teardown mid-loop, abort/timeout — closes\n // it. An aborted segment's stream could never be reused anyway (its\n // half-consumed in-progress episode cannot be re-read), so leaving it\n // open would only leak the socket until session teardown. Note the\n // close is still a policy decision made HERE, not signal-chaining:\n // the segment's signal never reaches the shared stream, which is what\n // lets the healthy pause outlive its segment.\n if (retainStream && !signal?.aborted && !stream.closed) {\n stream.pause();\n } else {\n await this.#closeStream(stream);\n }\n }\n\n // An abort can slip past the loop (e.g. it settled a race the segment\n // never awaited again); classify before treating the end as normal.\n throwIfAborted();\n\n // No terminal status: the stream ended before the turn settled — with\n // the reader redialing drops internally, a clean `done` means the\n // session stream was closed underneath this segment (resetSession or\n // dispose mid-turn). If an `error` event arrived (without a trailing\n // `status: error`), fall through to finish() so the real error surfaces\n // (unified:\"error\" + the error part), consistent with the\n // `status: error` path; otherwise surface the premature close rather\n // than a clean (truncated) `stop`. A promotion-boundary settle is a\n // settled turn despite carrying no terminal status (issue #120) — the\n // server never emits one for a run that finished into a promotion.\n if (\n !translator.terminalStatus &&\n !translator.error &&\n !translator.settledAtPromotionBoundary\n ) {\n throw new CoderChatError({\n message:\n \"Coder chat stream ended before the turn settled (connection closed or the server ended the stream without a terminal status).\",\n kind: \"stream_closed\",\n retryable: true,\n });\n }\n\n for (const part of translator.finish()) yield part;\n } catch (err) {\n // A timeout/caller abort during the REST phase (createChat / message /\n // tool-results / model resolution) rejects the fetch before the stream loop;\n // reclassify so the documented retryable timeout/abort contract still holds.\n throwIfAborted();\n throw err;\n } finally {\n // Advance the cursor on every exit (success, abort, error) so resuming the\n // same chat doesn't re-read messages already streamed this turn.\n if (translator && translator.maxMessageId > this.#lastSeenMessageId)\n this.#lastSeenMessageId = translator.maxMessageId;\n // Settle-time observability out-params (see SegmentChatRef): the\n // segment wrapper's finally runs after this one and reads them for\n // classification on generator-return exits.\n if (segmentChat) {\n if (translator?.terminalStatus !== undefined) {\n segmentChat.terminalStatus = translator.terminalStatus;\n }\n if (timeoutSignal?.aborted) segmentChat.timeoutError = timeoutError();\n }\n signal?.removeEventListener(\"abort\", interrupt);\n // Teardown of an unsettled turn — stream cancel(), premature close, or an\n // abort the listener didn't cover — interrupt the server so it stops.\n // (A turn that failed before streaming has no translator and never\n // settled, so it interrupts too.) A transient transport drop never gets\n // here: the reader redials internally, so an unsettled exit means abort,\n // timeout, a terminal stream failure (4xx / redial budget exhausted /\n // unparseable frame), or a REST-phase error — all cases where the healthy\n // server run must not keep burning tokens on an audience of zero.\n // A promotion-boundary settle carries no terminal status but IS settled\n // (issue #120) — and the chat's live run now belongs to the promoted\n // submission, so interrupting here would kill someone else's turn.\n if (!translator?.terminalStatus && !translator?.settledAtPromotionBoundary) interrupt();\n // A failure before the stream section ran (REST phase: tool-result\n // submission, uploads, chat creation, cursor seeding) leaves the\n // predecessor segment's paused stream ownerless while its turn is now\n // dead — and the interrupt above pushes interrupt-era events into its\n // buffer. Close it: an abandoned call must not leak the socket, and a\n // caller retry must dial fresh instead of attaching to that buffer.\n if (!streamAcquired && this.#retainedStream) {\n const stale = this.#retainedStream;\n this.#retainedStream = undefined;\n void stale.close();\n }\n // Ordered after the interrupt (which needs the chat id) and after the\n // cursor advance above (which the reset zeroes out again).\n if (discardSession) this.resetSession();\n this.#inFlight = false;\n }\n }\n\n async doStream(options: LanguageModelV4CallOptions): Promise<LanguageModelV4StreamResult> {\n // A consumer can tear the stream down via ReadableStream.cancel() without\n // aborting options.abortSignal. Route cancel through an abort so the turn\n // interrupts the server run and the blocked stream reader unblocks — a bare\n // gen.return() would deadlock on a pending socket read and never reach the\n // interrupt, leaking the workspace.\n const cancelController = new AbortController();\n const abortSignal = options.abortSignal\n ? AbortSignal.any([options.abortSignal, cancelController.signal])\n : cancelController.signal;\n // Observability classification: a segment counts as consumer-cancelled\n // only when cancel() INITIATED the termination. A caller abort that fired\n // first is a failure settle even if the consumer also cancels its reader\n // before the rejection propagates — so record which came first, rather\n // than whether cancellation ever happened.\n let consumerCancelled = false;\n const gen = this.#runTurn({ ...options, abortSignal }, () => consumerCancelled);\n const stream = new ReadableStream<LanguageModelV4StreamPart>({\n async pull(controller) {\n try {\n const { value, done } = await gen.next();\n if (done) controller.close();\n else controller.enqueue(value);\n } catch (err) {\n // A consumer-initiated cancel aborts the turn (to interrupt the server\n // and unblock the reader); that surfaces here as the turn's AbortError,\n // but it's an intentional teardown, so end the stream cleanly rather\n // than erroring it. A caller's own abortSignal still errors as usual.\n if (cancelController.signal.aborted) controller.close();\n else controller.error(err);\n }\n },\n async cancel() {\n // Only a cancel that beat any caller abort is a teardown (see above).\n consumerCancelled = !options.abortSignal?.aborted;\n cancelController.abort();\n await gen.return().catch(() => {});\n },\n });\n return { stream };\n }\n\n async doGenerate(options: LanguageModelV4CallOptions): Promise<LanguageModelV4GenerateResult> {\n const content: LanguageModelV4Content[] = [];\n const textBuf = new Map<string, string>();\n const reasoningBuf = new Map<string, string>();\n let usage: LanguageModelV4Usage = EMPTY_USAGE;\n let finishReason: LanguageModelV4GenerateResult[\"finishReason\"] = {\n unified: \"stop\",\n raw: undefined,\n };\n let providerMetadata: LanguageModelV4GenerateResult[\"providerMetadata\"];\n const warnings: LanguageModelV4GenerateResult[\"warnings\"] = [];\n\n for await (const part of this.#runTurn(options)) {\n switch (part.type) {\n case \"stream-start\":\n warnings.push(...part.warnings);\n break;\n case \"text-start\":\n textBuf.set(part.id, \"\");\n break;\n case \"text-delta\":\n textBuf.set(part.id, (textBuf.get(part.id) ?? \"\") + part.delta);\n break;\n case \"text-end\": {\n const t = textBuf.get(part.id) ?? \"\";\n if (t.length > 0) content.push({ type: \"text\", text: t });\n textBuf.delete(part.id);\n break;\n }\n case \"reasoning-start\":\n reasoningBuf.set(part.id, \"\");\n break;\n case \"reasoning-delta\":\n reasoningBuf.set(part.id, (reasoningBuf.get(part.id) ?? \"\") + part.delta);\n break;\n case \"reasoning-end\": {\n const t = reasoningBuf.get(part.id) ?? \"\";\n if (t.length > 0) content.push({ type: \"reasoning\", text: t });\n reasoningBuf.delete(part.id);\n break;\n }\n case \"tool-call\":\n case \"tool-result\":\n case \"source\":\n case \"file\":\n content.push(part);\n break;\n case \"finish\":\n usage = part.usage;\n finishReason = part.finishReason;\n providerMetadata = part.providerMetadata;\n break;\n case \"error\":\n throw part.error instanceof Error\n ? part.error\n : new CoderChatError({ message: String(part.error) });\n default:\n break;\n }\n }\n\n return {\n content,\n finishReason,\n usage,\n warnings,\n ...(providerMetadata ? { providerMetadata } : {}),\n };\n }\n}\n","import type { DynamicToolUIPart, UIMessage } from \"ai\";\nimport type { ChatMessage, ChatMessagePart } from \"../coder/types.js\";\n\n/** Options for {@link chatMessagesToUIMessages}. */\nexport interface ChatMessagesToUIMessagesOptions {\n /**\n * Resolves a persisted `file` part to a URL for its `FileUIPart`. Persisted\n * file parts carry only a `file_id` (no bytes, and usually no `url`), so\n * without a URL there is nothing for the UI to render and the part is\n * skipped. Download the bytes with `CoderChatClient.getChatFile(fileId)`\n * and return a data:/object/proxy URL here to keep attachments visible.\n * Returning `undefined` falls back to the part's own `url`, if any.\n */\n fileUrl?: (part: ChatMessagePart) => string | undefined;\n}\n\nfunction toolErrorText(result: unknown): string {\n if (typeof result === \"string\") return result;\n if (result === undefined || result === null) return \"\";\n return JSON.stringify(result) ?? \"\";\n}\n\nfunction toDynamicToolPart(\n call: ChatMessagePart,\n result: ChatMessagePart | undefined,\n): DynamicToolUIPart | undefined {\n const toolCallId = call.tool_call_id;\n const toolName = call.tool_name;\n if (!toolCallId || !toolName) return undefined;\n const providerExecuted = call.provider_executed ?? result?.provider_executed;\n const base = {\n type: \"dynamic-tool\" as const,\n toolName,\n toolCallId,\n ...(providerExecuted !== undefined ? { providerExecuted } : {}),\n };\n if (!result) return { ...base, state: \"input-available\", input: call.args };\n if (result.is_error) {\n return {\n ...base,\n state: \"output-error\",\n input: call.args,\n errorText: toolErrorText(result.result),\n };\n }\n return { ...base, state: \"output-available\", input: call.args, output: result.result };\n}\n\nfunction toUIParts(\n content: ChatMessagePart[],\n resultsByCallId: ReadonlyMap<string, ChatMessagePart>,\n opts: ChatMessagesToUIMessagesOptions | undefined,\n): UIMessage[\"parts\"] {\n const parts: UIMessage[\"parts\"] = [];\n for (const part of content) {\n switch (part.type) {\n case \"text\":\n parts.push({ type: \"text\", text: part.text ?? \"\", state: \"done\" });\n break;\n case \"reasoning\":\n parts.push({ type: \"reasoning\", text: part.text ?? \"\", state: \"done\" });\n break;\n case \"tool-call\": {\n const result = part.tool_call_id ? resultsByCallId.get(part.tool_call_id) : undefined;\n const ui = toDynamicToolPart(part, result);\n if (ui) parts.push(ui);\n break;\n }\n case \"source\":\n // `sourceId` is required by the AI SDK; fall back to the URL when the\n // wire part has no `source_id`. Without a URL there is nothing to\n // link, so the part is skipped.\n if (part.url) {\n parts.push({\n type: \"source-url\",\n sourceId: part.source_id || part.url,\n url: part.url,\n ...(part.title ? { title: part.title } : {}),\n });\n }\n break;\n case \"file\": {\n const url = opts?.fileUrl?.(part) ?? part.url;\n if (url) {\n parts.push({\n type: \"file\",\n mediaType: part.media_type ?? \"application/octet-stream\",\n ...(part.name ? { filename: part.name } : {}),\n url,\n });\n }\n break;\n }\n case \"tool-result\":\n // Folded into the originating `dynamic-tool` part above.\n break;\n default:\n // file-reference / context-file / skill / future kinds: no UI\n // equivalent; skip so history written by newer Coder servers\n // degrades gracefully.\n break;\n }\n }\n return parts;\n}\n\n/**\n * Converts persisted chat history (`CoderChatClient.getMessages`) into AI SDK\n * v6 `UIMessage`s, e.g. to rehydrate `useChat({ messages })` for an existing\n * chat. Pure and side-effect free.\n *\n * The mapping mirrors what a live-streamed transcript of the same turn looks\n * like, with one caveat around client-tool typing (below):\n * - One `ChatMessage` becomes one `UIMessage` (ids stringified), sorted\n * chronologically by id regardless of input order — the messages endpoint\n * pages newest-first by default, while `useChat` needs oldest-first. The\n * exception is `role: \"tool\"` messages: their `tool-result` parts are\n * folded into the originating assistant `dynamic-tool` part and the message\n * itself is dropped.\n * - `text`/`reasoning` parts map to their UI counterparts with\n * `state: \"done\"` (history is complete, never streaming).\n * - Every `tool-call` part becomes a `dynamic-tool` part, in state\n * `\"output-available\"` once a result exists (`\"output-error\"` when it is\n * marked `is_error`), or `\"input-available\"` while no result has been\n * persisted yet. For server-executed tools this matches the live stream\n * (they stream with `dynamic: true`); client (`ToolSet`) tools stream live\n * as statically-typed `tool-{name}` parts, but persisted history does not\n * record which names were client tools, so they rehydrate as `dynamic-tool`\n * too. Render tools by name — ai's `isToolOrDynamicToolUIPart` +\n * `getToolOrDynamicToolName` — rather than exact `part.type` matches and\n * both shapes look identical.\n * - `source` parts become `source-url` parts; skipped when the wire part has\n * no `url`.\n * - `file` parts become `file` UI parts only when a URL is available (see\n * {@link ChatMessagesToUIMessagesOptions.fileUrl}): persisted file parts\n * carry only a `file_id`, and `FileUIPart` requires a `url`. Parts without\n * one are skipped.\n * - `file-reference` / `context-file` / `skill` / unknown part kinds have no\n * UI equivalent and are skipped silently (forward compatible).\n */\nexport function chatMessagesToUIMessages(\n messages: ChatMessage[],\n opts?: ChatMessagesToUIMessagesOptions,\n): UIMessage[] {\n // Normalize to chronological order (ids are per-chat sequential). The\n // messages endpoint pages newest-first by default (only `after_id` queries\n // return ascending), and `useChat` expects oldest-first — accepting any\n // input order makes both pagination shapes safe to pass straight in.\n const ordered = [...messages].sort((a, b) => a.id - b.id);\n\n // Pass 1: index tool results by call id. chatd persists results as separate\n // role:\"tool\" messages; scan every message so results inlined on assistant\n // snapshots fold identically.\n const resultsByCallId = new Map<string, ChatMessagePart>();\n for (const message of ordered) {\n for (const part of message.content ?? []) {\n if (part.type !== \"tool-result\" || !part.tool_call_id) continue;\n if (!resultsByCallId.has(part.tool_call_id)) resultsByCallId.set(part.tool_call_id, part);\n }\n }\n\n // Pass 2: one UIMessage per ChatMessage, chronological. role:\"tool\" messages\n // were folded above and produce none.\n const out: UIMessage[] = [];\n for (const message of ordered) {\n if (message.role === \"tool\") continue;\n out.push({\n id: String(message.id),\n role: message.role,\n parts: toUIParts(message.content ?? [], resultsByCallId, opts),\n });\n }\n return out;\n}\n"],"mappings":"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","names":["sleep"]}