@astralform/js 4.6.1 → 4.8.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.cjs +65 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +56 -1
- package/dist/index.d.ts +56 -1
- package/dist/index.js +65 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/utils.ts","../src/rate-limit.ts","../src/streaming.ts","../src/client.ts","../src/storage.ts","../src/tools.ts","../src/protocol-registry.ts","../src/translate.ts","../src/session.ts","../src/restore-plan.ts","../src/types.ts","../src/stream-manager.ts","../src/replay.ts","../src/embedded-resource.ts"],"sourcesContent":["// Core classes\nexport { AstralformClient } from \"./client.js\";\nexport { ChatSession, CONVERSATION_PAGE_SIZE } from \"./session.js\";\nexport { ToolRegistry, type ToolHandler } from \"./tools.js\";\nexport { InMemoryStorage, type ChatStorage } from \"./storage.js\";\n\n// Errors\nexport {\n AstralformError,\n AuthenticationError,\n RateLimitError,\n LLMNotConfiguredError,\n ServerError,\n ConnectionError,\n StreamAbortedError,\n} from \"./errors.js\";\nexport type { RateLimitErrorDetails } from \"./errors.js\";\n\n// Utilities\nexport { generateId } from \"./utils.js\";\n\n// Streaming\nexport { streamJobSSE } from \"./streaming.js\";\n\n// Stream manager\nexport { StreamManager } from \"./stream-manager.js\";\nexport type {\n StreamState,\n SendOptions,\n StreamManagerEvent,\n} from \"./stream-manager.js\";\n\n// Delta translator (shared between session and replay)\nexport { translateDelta } from \"./translate.js\";\n\n// Event replay (for restoring conversations from persisted events)\nexport { mapSseToChat, replayEvents } from \"./replay.js\";\nexport type { RawSseEvent } from \"./replay.js\";\n\n// Embedded resource detection (protocol-agnostic UI surface helper)\nexport {\n isEmbeddedResource,\n parseEmbeddedResource,\n} from \"./embedded-resource.js\";\nexport type { EmbeddedResource } from \"./embedded-resource.js\";\n\n// Protocol adapter registry — consumers register framework-specific\n// renderers for embedded resource MIME types.\nexport { ProtocolRegistry } from \"./protocol-registry.js\";\nexport type { ProtocolAdapter } from \"./protocol-registry.js\";\n\n// Event type constants\nexport { ChatEventType } from \"./types.js\";\nexport type { ChatEventTypeValue } from \"./types.js\";\n\n// High-level ChatEvent (SDK → consumer)\nexport type { ChatEvent, BlockDeltaPayload, TurnUsage } from \"./types.js\";\n\n// Custom event payload catalog\nexport type {\n AgentIdentity,\n TaskStatus,\n TodoItem,\n TodoUpdatePayload,\n TitleGeneratedPayload,\n SubagentStartPayload,\n SubagentStopPayload,\n ContextWarningPayload,\n ContextUpdatePayload,\n MemoryRecord,\n MemoryRecallPayload,\n MemoryUpdatePayload,\n DesktopStreamPayload,\n AttachmentStagedPayload,\n WorkspaceReadyPayload,\n AssetCreatedPayload,\n ToolApprovalRequestedPayload,\n ToolApprovalGrantedPayload,\n ToolPermissionDeniedPayload,\n ToolHarnessWarningPayload,\n UserUnavailablePayload,\n PromptSuggestionPayload,\n} from \"./custom-events.js\";\n\n// Wire protocol types (for consumers that want to parse the raw SSE\n// data themselves or write their own transport adapter)\nexport type {\n WireEvent,\n WireMessageStart,\n WireMessageStop,\n WireBlockStart,\n WireBlockDelta,\n WireBlockStop,\n WireStallWarning,\n WireRetryEvent,\n WireErrorEvent,\n WireKeepalive,\n WireCustomEvent,\n WireBlockKind,\n WireBlockStatus,\n WireStopReason,\n WireBlockDeltaPayload,\n WireTextDelta,\n WireThinkingDelta,\n WireSignatureDelta,\n WireInputDelta,\n WireInputArgDelta,\n WireOutputDelta,\n WireStatusDelta,\n} from \"./types.js\";\n\n// Config + domain models\nexport type {\n AstralformConfig,\n AstralformApiKeyConfig,\n AstralformUserTokenConfig,\n Conversation,\n Message,\n AgentStatus,\n AgentCapability,\n UIComponentsConfig,\n AgentInfo,\n SkillInfo,\n ModelOption,\n ModelChoiceOptions,\n TeamSummary,\n TeamAgentSummary,\n} from \"./types.js\";\n\n// Request / response types\nexport type {\n ChatStreamRequest,\n ReasoningEffort,\n ToolResultRequest,\n ToolResult,\n ToolDefinition,\n ToolApprovalRequest,\n ToolApprovalDecision,\n ToolApprovalScope,\n ToolGrant,\n MyToolGrantsPage,\n ToolCallRequest,\n JobCreateResponse,\n JobStatus,\n JobSummary,\n ActiveJob,\n FeedbackRequest,\n FeedbackResponse,\n ConversationAsset,\n StreamJobSSEOptions,\n ChatStreamEvent,\n ConversationEvent,\n} from \"./types.js\";\n","export interface RateLimitErrorDetails {\n retryAfterSec?: number;\n resetAt?: number;\n scope?: string;\n policyId?: string;\n limit?: number;\n remaining?: number;\n requestId?: string;\n}\n\nexport class AstralformError extends Error {\n constructor(\n message: string,\n public code: string,\n ) {\n super(message);\n this.name = \"AstralformError\";\n }\n}\n\nexport class AuthenticationError extends AstralformError {\n constructor(message = \"Invalid or missing API key\") {\n super(message, \"authentication_error\");\n this.name = \"AuthenticationError\";\n }\n}\n\nexport class RateLimitError extends AstralformError {\n declare readonly retryAfterSec?: number;\n declare readonly resetAt?: number;\n declare readonly scope?: string;\n declare readonly policyId?: string;\n declare readonly limit?: number;\n declare readonly remaining?: number;\n declare readonly requestId?: string;\n\n constructor(\n message = \"Rate limit exceeded\",\n details: RateLimitErrorDetails = {},\n ) {\n super(message, \"rate_limit_error\");\n this.name = \"RateLimitError\";\n Object.assign(this, details);\n }\n}\n\nexport class LLMNotConfiguredError extends AstralformError {\n constructor(message = \"LLM provider not configured for this agent\") {\n super(message, \"llm_not_configured\");\n this.name = \"LLMNotConfiguredError\";\n }\n}\n\nexport class ServerError extends AstralformError {\n constructor(message = \"Internal server error\") {\n super(message, \"server_error\");\n this.name = \"ServerError\";\n }\n}\n\nexport class ConnectionError extends AstralformError {\n constructor(message = \"Failed to connect to server\") {\n super(message, \"connection_error\");\n this.name = \"ConnectionError\";\n }\n}\n\nexport class StreamAbortedError extends AstralformError {\n constructor(message = \"Stream was aborted\") {\n super(message, \"stream_aborted\");\n this.name = \"StreamAbortedError\";\n }\n}\n","export function sanitizeErrorText(text: string): string {\n return text.slice(0, 500).replace(/Bearer\\s+\\S+/gi, \"Bearer [REDACTED]\");\n}\n\nexport function generateId(): string {\n if (typeof crypto !== \"undefined\" && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n // Fallback for environments without crypto.randomUUID\n return \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === \"x\" ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n}\n","import { RateLimitError, type RateLimitErrorDetails } from \"./errors.js\";\nimport { sanitizeErrorText } from \"./utils.js\";\n\nconst DEFAULT_MESSAGE = \"Rate limit exceeded\";\n\nfunction parseNumber(value: unknown): number | undefined {\n if (typeof value === \"number\" && Number.isFinite(value)) {\n return value;\n }\n if (typeof value === \"string\") {\n const trimmed = value.trim();\n if (!trimmed) {\n return undefined;\n }\n const parsed = Number(trimmed);\n if (Number.isFinite(parsed)) {\n return parsed;\n }\n }\n return undefined;\n}\n\nfunction parseString(value: unknown): string | undefined {\n if (typeof value !== \"string\") {\n return undefined;\n }\n const trimmed = value.trim();\n return trimmed ? trimmed : undefined;\n}\n\nfunction parseJsonObject(rawText: string): Record<string, unknown> | undefined {\n if (!rawText) {\n return undefined;\n }\n try {\n const parsed: unknown = JSON.parse(rawText);\n if (parsed && typeof parsed === \"object\") {\n return parsed as Record<string, unknown>;\n }\n } catch {\n // Ignore parse errors and fall back to text-only handling.\n }\n return undefined;\n}\n\nfunction parseRetryAfterHeader(value: string | null): number | undefined {\n if (!value) {\n return undefined;\n }\n\n const numeric = parseNumber(value);\n if (numeric !== undefined) {\n return Math.max(0, Math.ceil(numeric));\n }\n\n const asDate = Date.parse(value);\n if (Number.isFinite(asDate)) {\n const diffMs = asDate - Date.now();\n return Math.max(0, Math.ceil(diffMs / 1000));\n }\n\n return undefined;\n}\n\nfunction parseResetTimestamp(value: unknown): number | undefined {\n const numeric = parseNumber(value);\n if (numeric !== undefined) {\n if (numeric > 1_000_000_000_000) {\n return Math.floor(numeric);\n }\n return Math.floor(numeric * 1000);\n }\n\n const asString = parseString(value);\n if (!asString) {\n return undefined;\n }\n\n const asDate = Date.parse(asString);\n if (Number.isFinite(asDate)) {\n return asDate;\n }\n return undefined;\n}\n\nfunction pickFirst<T>(\n payload: Record<string, unknown>,\n keys: string[],\n parser: (value: unknown) => T | undefined,\n): T | undefined {\n for (const key of keys) {\n const parsed = parser(payload[key]);\n if (parsed !== undefined) {\n return parsed;\n }\n }\n return undefined;\n}\n\nfunction buildRateLimitDetails(\n payload: Record<string, unknown>,\n headers?: Headers,\n): RateLimitErrorDetails {\n const headerRetryAfter = headers\n ? parseRetryAfterHeader(headers.get(\"retry-after\"))\n : undefined;\n const bodyRetryAfter = pickFirst(\n payload,\n [\"retry_after\", \"retryAfter\", \"retry_after_sec\", \"retryAfterSec\"],\n parseNumber,\n );\n\n const retryAfterSec = bodyRetryAfter ?? headerRetryAfter;\n\n const headerReset = headers\n ? parseResetTimestamp(\n headers.get(\"x-ratelimit-reset\") ?? headers.get(\"x-ratelimit-reset-at\"),\n )\n : undefined;\n const bodyReset = parseResetTimestamp(\n payload.reset_at ?? payload.resetAt ?? payload.reset,\n );\n\n const resetAt =\n bodyReset ??\n headerReset ??\n (retryAfterSec !== undefined\n ? Date.now() + retryAfterSec * 1000\n : undefined);\n\n const limit =\n pickFirst(payload, [\"limit\", \"rate_limit\", \"max\"], parseNumber) ??\n (headers ? parseNumber(headers.get(\"x-ratelimit-limit\")) : undefined);\n\n const remaining =\n pickFirst(payload, [\"remaining\", \"rate_limit_remaining\"], parseNumber) ??\n (headers ? parseNumber(headers.get(\"x-ratelimit-remaining\")) : undefined);\n\n const scope =\n pickFirst(payload, [\"scope\", \"limit_scope\"], parseString) ??\n (headers ? parseString(headers.get(\"x-ratelimit-scope\")) : undefined);\n\n const policyId =\n pickFirst(payload, [\"policy_id\", \"policyId\", \"policy\"], parseString) ??\n (headers\n ? parseString(\n headers.get(\"x-ratelimit-policy\") ??\n headers.get(\"x-ratelimit-policy-id\"),\n )\n : undefined);\n\n const requestId =\n pickFirst(payload, [\"request_id\", \"requestId\"], parseString) ??\n (headers\n ? parseString(\n headers.get(\"x-request-id\") ?? headers.get(\"x-correlation-id\"),\n )\n : undefined);\n\n return {\n retryAfterSec,\n resetAt,\n scope,\n policyId,\n limit,\n remaining,\n requestId,\n };\n}\n\nexport function createRateLimitErrorFromPayload(\n payload: Record<string, unknown>,\n fallbackMessage = DEFAULT_MESSAGE,\n): RateLimitError {\n const message = pickFirst(\n payload,\n [\"message\", \"error_description\"],\n parseString,\n );\n return new RateLimitError(\n message ?? fallbackMessage,\n buildRateLimitDetails(payload),\n );\n}\n\nexport function createRateLimitErrorFromHttp(\n response: Response,\n rawText: string,\n): RateLimitError {\n const payload = parseJsonObject(rawText) ?? {};\n const details = buildRateLimitDetails(payload, response.headers);\n\n const sanitizedText = sanitizeErrorText(rawText);\n const message =\n pickFirst(payload, [\"message\", \"error_description\"], parseString) ??\n (sanitizedText || DEFAULT_MESSAGE);\n\n return new RateLimitError(message, details);\n}\n","import {\n AuthenticationError,\n ConnectionError,\n ServerError,\n StreamAbortedError,\n} from \"./errors.js\";\nimport { createRateLimitErrorFromHttp } from \"./rate-limit.js\";\nimport type { ChatStreamEvent, StreamJobSSEOptions } from \"./types.js\";\nimport { sanitizeErrorText } from \"./utils.js\";\n\n/**\n * GET-based SSE stream for job events.\n */\nexport async function* streamJobSSE(\n options: StreamJobSSEOptions,\n): AsyncGenerator<ChatStreamEvent> {\n const { url, headers, signal, fetchFn } = options;\n\n let response: Response;\n try {\n response = await fetchFn(url, {\n method: \"GET\",\n headers,\n signal,\n });\n } catch (err) {\n if (err instanceof DOMException && err.name === \"AbortError\") {\n throw new StreamAbortedError();\n }\n throw new ConnectionError(\n err instanceof Error ? err.message : \"Failed to connect\",\n );\n }\n\n if (!response.ok) {\n const rawText = await response.text().catch(() => \"\");\n const text = rawText ? sanitizeErrorText(rawText) : \"\";\n switch (response.status) {\n case 401:\n throw new AuthenticationError();\n case 429:\n throw createRateLimitErrorFromHttp(response, rawText);\n default:\n throw new ServerError(text || `HTTP ${response.status}`);\n }\n }\n\n if (!response.body) {\n throw new ConnectionError(\"Response body is null\");\n }\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n let currentEvent = \"\";\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split(\"\\n\");\n buffer = lines.pop() ?? \"\";\n\n for (const line of lines) {\n if (line.startsWith(\"event: \")) {\n currentEvent = line.slice(7).trim();\n } else if (line.startsWith(\"data: \")) {\n const data = line.slice(6);\n if (data === \"[DONE]\") {\n return;\n }\n yield { event: currentEvent || \"message\", data };\n }\n if (line === \"\") {\n currentEvent = \"\";\n }\n }\n }\n } catch (err) {\n if (err instanceof DOMException && err.name === \"AbortError\") {\n throw new StreamAbortedError();\n }\n throw err;\n } finally {\n reader.releaseLock();\n }\n}\n","import { AuthenticationError, ConnectionError, ServerError } from \"./errors.js\";\nimport { createRateLimitErrorFromHttp } from \"./rate-limit.js\";\nimport { streamJobSSE } from \"./streaming.js\";\nimport { sanitizeErrorText } from \"./utils.js\";\nimport type {\n ActiveJob,\n AgentInfo,\n AstralformApiKeyConfig,\n AstralformConfig,\n ChatStreamEvent,\n ChatStreamRequest,\n ConversationAsset,\n ConversationEvent,\n Conversation,\n FeedbackRequest,\n FeedbackResponse,\n JobCreateResponse,\n JobStatus,\n JobSummary,\n Message,\n ModelOption,\n MyToolGrantsPage,\n AgentStatus,\n TeamAgentSummary,\n SkillInfo,\n TeamSummary,\n ToolApprovalRequest,\n ToolResultRequest,\n} from \"./types.js\";\n\nconst DEFAULT_BASE_URL = \"https://api.astralform.ai\";\nconst DEFAULT_TIMEOUT_MS = 30_000;\n\nfunction validateBaseURL(url: string): string {\n const cleaned = url.replace(/\\/+$/, \"\");\n try {\n const parsed = new URL(cleaned);\n if (parsed.protocol !== \"https:\" && parsed.protocol !== \"http:\") {\n throw new Error(\n `Invalid baseURL protocol \"${parsed.protocol}\" - only http: and https: are allowed`,\n );\n }\n return parsed.origin + parsed.pathname.replace(/\\/+$/, \"\");\n } catch (err) {\n if (err instanceof Error && err.message.includes(\"Invalid baseURL\")) {\n throw err;\n }\n throw new Error(`Invalid baseURL: \"${cleaned}\" is not a valid URL`);\n }\n}\n\nfunction isApiKeyConfig(\n config: AstralformConfig,\n): config is AstralformApiKeyConfig {\n return \"apiKey\" in config;\n}\n\n/** Discriminates between the two auth modes the client supports. */\ntype AuthMode =\n | { kind: \"api_key\"; apiKey: string; userId: string }\n | {\n kind: \"user_token\";\n accessToken: string;\n /** Null until the user picks an agent; account-scoped calls still work. */\n agentId: string | null;\n /** Optional end-user override. When present, sent as X-End-User-ID. */\n endUserId: string | null;\n };\n\nexport class AstralformClient {\n private readonly baseURL: string;\n private readonly fetchFn: typeof globalThis.fetch;\n private readonly timeoutMs: number;\n /**\n * Auth state is mutable so callers can rotate access tokens or switch\n * agent context without re-instantiating the client. API-key mode is\n * effectively immutable in practice but uses the same shape for uniformity.\n */\n private auth: AuthMode;\n\n constructor(config: AstralformConfig) {\n if (isApiKeyConfig(config)) {\n if (!config.apiKey || typeof config.apiKey !== \"string\") {\n throw new Error(\"apiKey is required and must be a non-empty string\");\n }\n if (!config.userId || typeof config.userId !== \"string\") {\n throw new Error(\"userId is required in API-key mode\");\n }\n this.auth = {\n kind: \"api_key\",\n apiKey: config.apiKey,\n userId: config.userId,\n };\n } else {\n if (!config.accessToken || typeof config.accessToken !== \"string\") {\n throw new Error(\n \"accessToken is required and must be a non-empty string in user-token mode\",\n );\n }\n // agentId is optional — a pre-pick client (right after login) can\n // still hit account-scoped routes like listTeams(). Agent-scoped\n // routes will 4xx until one is set via updateAgentId().\n const agentId =\n typeof config.agentId === \"string\" && config.agentId.length > 0\n ? config.agentId\n : null;\n this.auth = {\n kind: \"user_token\",\n accessToken: config.accessToken,\n agentId,\n endUserId:\n typeof config.endUserId === \"string\" && config.endUserId.length > 0\n ? config.endUserId\n : null,\n };\n }\n\n this.baseURL = validateBaseURL(config.baseURL ?? DEFAULT_BASE_URL);\n this.fetchFn = config.fetch ?? globalThis.fetch.bind(globalThis);\n this.timeoutMs =\n typeof config.timeoutMs === \"number\" &&\n Number.isFinite(config.timeoutMs) &&\n config.timeoutMs > 0\n ? config.timeoutMs\n : DEFAULT_TIMEOUT_MS;\n }\n\n /**\n * Replace the current OIDC access token without reconstructing the client.\n * Use after refreshing via the host app's own token manager.\n * Throws if the client was created in API-key mode.\n */\n updateAccessToken(accessToken: string): void {\n if (this.auth.kind !== \"user_token\") {\n throw new Error(\"updateAccessToken is only valid in user-token mode\");\n }\n if (!accessToken || typeof accessToken !== \"string\") {\n throw new Error(\"accessToken must be a non-empty string\");\n }\n this.auth = { ...this.auth, accessToken };\n }\n\n /**\n * Swap the active agent for a user-token client. The backend verifies the\n * current developer has access to the new agent; a 403 comes back if not.\n */\n updateAgentId(agentId: string): void {\n if (this.auth.kind !== \"user_token\") {\n throw new Error(\"updateAgentId is only valid in user-token mode\");\n }\n if (!agentId || typeof agentId !== \"string\") {\n throw new Error(\"agentId must be a non-empty string\");\n }\n this.auth = { ...this.auth, agentId };\n }\n\n /**\n * Set (or clear) the end-user override for user-token mode.\n *\n * Pass `null` or an empty string to clear — subsequent requests go\n * back to scoping against the developer's own identity. Throws if\n * called in API-key mode, where end-user context already travels via\n * the constructor's `userId` field.\n */\n updateEndUserId(endUserId: string | null): void {\n if (this.auth.kind !== \"user_token\") {\n throw new Error(\"updateEndUserId is only valid in user-token mode\");\n }\n const normalized =\n typeof endUserId === \"string\" && endUserId.length > 0 ? endUserId : null;\n this.auth = { ...this.auth, endUserId: normalized };\n }\n\n /** Current end-user override in user-token mode, or `null` if unset. */\n get endUserId(): string | null {\n return this.auth.kind === \"user_token\" ? this.auth.endUserId : null;\n }\n\n /**\n * Active agent for user-token mode, or `null` if pre-pick (client\n * was constructed without one). For API-key mode the agent is baked\n * into the key, so this getter returns `null` there too — use\n * `authMode` to disambiguate.\n */\n get agentId(): string | null {\n return this.auth.kind === \"user_token\" ? this.auth.agentId : null;\n }\n\n /** Which auth mode this client was constructed with. */\n get authMode(): \"api_key\" | \"user_token\" {\n return this.auth.kind;\n }\n\n /**\n * Authorization + identity headers for the current auth mode, without\n * `Content-Type`. Suitable for JSON requests (paired with the JSON header\n * in the `headers` getter) and for multipart uploads where the browser\n * must set its own `Content-Type` boundary.\n */\n private get authHeaders(): Record<string, string> {\n if (this.auth.kind === \"api_key\") {\n return {\n Authorization: `Bearer ${this.auth.apiKey}`,\n \"X-End-User-ID\": this.auth.userId,\n };\n }\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.auth.accessToken}`,\n };\n if (this.auth.agentId) {\n headers[\"X-Agent-ID\"] = this.auth.agentId;\n }\n if (this.auth.endUserId) {\n headers[\"X-End-User-ID\"] = this.auth.endUserId;\n }\n return headers;\n }\n\n private get headers(): Record<string, string> {\n return {\n ...this.authHeaders,\n \"Content-Type\": \"application/json\",\n };\n }\n\n /**\n * Run one REST exchange under a single deadline covering connect, headers,\n * AND the body read. The body read is the part that matters: `json()` used\n * to sit outside every guard, so a response whose headers arrived but whose\n * body stalled hung forever — silently stranding callers that await it\n * (a stalled `getMessages` used to leave `StreamManager.restore()` parked\n * before it ever fetched the events it renders from).\n *\n * The controller is created per request and is deliberately NOT the\n * session's — that one means \"the user cancelled this turn\" and is null\n * outside a live turn. Aborting frees the socket; the race guarantees a\n * rejection even when an injected `fetch` ignores the signal.\n */\n private async withDeadline<T>(\n run: (signal: AbortSignal) => Promise<T>,\n ): Promise<T> {\n const controller = new AbortController();\n const timedOut = () =>\n new ConnectionError(`Request timed out after ${this.timeoutMs}ms`);\n let timer: ReturnType<typeof setTimeout> | undefined;\n const deadline = new Promise<never>((_resolve, reject) => {\n timer = setTimeout(() => {\n controller.abort();\n reject(timedOut());\n }, this.timeoutMs);\n });\n try {\n // Promise.race subscribes to both inputs, so the loser's later\n // rejection (the abort landing after the deadline won) counts as\n // handled and can't surface as an unhandled rejection.\n return await Promise.race([run(controller.signal), deadline]);\n } catch (err) {\n // A signal-honouring fetch rejects with AbortError before the race\n // settles — normalize it to the same timeout error either way.\n if (controller.signal.aborted) throw timedOut();\n throw err;\n } finally {\n clearTimeout(timer);\n }\n }\n\n private async request(\n method: string,\n path: string,\n body?: unknown,\n ): Promise<Response> {\n return this.withDeadline((signal) => this.send(method, path, body, signal));\n }\n\n /** Fetch + status handling. Always called inside `withDeadline`. */\n private async send(\n method: string,\n path: string,\n body: unknown,\n signal: AbortSignal,\n ): Promise<Response> {\n const response = await this.fetchFn(`${this.baseURL}${path}`, {\n method,\n headers: this.headers,\n body: body !== undefined ? JSON.stringify(body) : undefined,\n signal,\n }).catch((err) => {\n throw new ConnectionError(\n err instanceof Error ? err.message : \"Failed to connect\",\n );\n });\n await this.handleError(response);\n return response;\n }\n\n // DO NOT refactor these back into `request()` + `.json()`. Parsing the body\n // INSIDE the raced callback is the entire fix: `json()` outside the deadline\n // is the original bug (headers arrive, body stalls, caller hangs forever).\n // `request()` survives for `del()`, which never reads the body.\n\n async get<T>(path: string): Promise<T> {\n return this.withDeadline(async (signal) => {\n const response = await this.send(\"GET\", path, undefined, signal);\n return (await response.json()) as T;\n });\n }\n\n async post<T>(path: string, body: unknown): Promise<T> {\n return this.withDeadline(async (signal) => {\n const response = await this.send(\"POST\", path, body, signal);\n return (await response.json()) as T;\n });\n }\n\n private async del(path: string): Promise<void> {\n await this.request(\"DELETE\", path);\n }\n\n private async handleError(response: Response): Promise<void> {\n if (response.ok) return;\n const text = await response.text().catch(() => \"\");\n switch (response.status) {\n case 401:\n throw new AuthenticationError();\n case 429:\n throw createRateLimitErrorFromHttp(response, text);\n default: {\n const safeText = text ? sanitizeErrorText(text) : \"\";\n throw new ServerError(safeText || `HTTP ${response.status}`);\n }\n }\n }\n\n // --- REST Methods ---\n\n async getHealth(): Promise<{\n status: string;\n version: string;\n ollama_connected: boolean;\n }> {\n return this.get(\"/v1/health\");\n }\n\n // Agent readiness check, scoped to the client's active agent via X-Agent-ID.\n async getAgentStatus(): Promise<AgentStatus> {\n const raw = await this.get<{\n is_ready: boolean;\n llm_configured: boolean;\n llm_provider?: string;\n llm_model?: string;\n message: string;\n ui_components?: {\n enabled?: boolean;\n protocol?: string | null;\n mime_type?: string | null;\n };\n capabilities?: Array<{ key?: string; enabled?: boolean }>;\n }>(\"/v1/agent/status\");\n const ui = raw.ui_components ?? {};\n return {\n isReady: raw.is_ready,\n llmConfigured: raw.llm_configured,\n llmProvider: raw.llm_provider,\n llmModel: raw.llm_model,\n message: raw.message,\n // Defaults to [] rather than undefined so a caller can iterate without a\n // guard, and so a server that predates the field behaves as \"reports\n // nothing\" instead of throwing. Entries missing a key are dropped: a\n // capability with no name cannot be matched against and would render as\n // an unlabelled row.\n capabilities: (raw.capabilities ?? []).flatMap((c) =>\n typeof c?.key === \"string\" && c.key.length > 0\n ? [{ key: c.key, enabled: Boolean(c.enabled) }]\n : [],\n ),\n uiComponents: {\n enabled: Boolean(ui.enabled),\n protocol: ui.protocol ?? null,\n mimeType: ui.mime_type ?? null,\n },\n };\n }\n\n async getConversations(limit = 50, offset = 0): Promise<Conversation[]> {\n const safeLimit = Math.max(1, Math.min(200, Math.floor(Number(limit))));\n const safeOffset = Math.max(0, Math.floor(Number(offset)));\n const raw = await this.get<\n {\n id: string;\n title: string;\n message_count: number;\n created_at: string;\n updated_at: string;\n }[]\n >(`/v1/conversations?limit=${safeLimit}&offset=${safeOffset}`);\n return raw.map((c) => ({\n id: c.id,\n title: c.title,\n messageCount: c.message_count,\n createdAt: c.created_at,\n updatedAt: c.updated_at,\n }));\n }\n\n async getMessages(conversationId: string): Promise<Message[]> {\n const raw = await this.get<\n {\n id: string;\n conversation_id: string;\n role: \"user\" | \"assistant\" | \"system\";\n content: string;\n parent_id?: string;\n created_at: string;\n }[]\n >(`/v1/conversations/${encodeURIComponent(conversationId)}/messages`);\n return raw.map((m) => ({\n id: m.id,\n conversationId: m.conversation_id,\n role: m.role,\n content: m.content,\n parentId: m.parent_id,\n status: \"complete\" as const,\n createdAt: m.created_at,\n }));\n }\n\n async deleteConversation(id: string): Promise<void> {\n await this.del(`/v1/conversations/${encodeURIComponent(id)}`);\n }\n\n /**\n * List the AI personas (sub-agents) available INSIDE the client's active\n * agent workspace — orchestrator + specialists, addressed per message via\n * `ChatStreamRequest.agent_name`. Not to be confused with `listAgents()`,\n * which enumerates the team-level agents a signed-in user can open.\n */\n async getAgents(): Promise<AgentInfo[]> {\n const raw = await this.get<\n {\n name: string;\n display_name: string;\n description: string;\n is_orchestrator: boolean;\n is_enabled: boolean;\n avatar_url?: string;\n }[]\n >(\"/v1/agents\");\n return raw.map((a) => ({\n name: a.name,\n displayName: a.display_name,\n description: a.description,\n isOrchestrator: a.is_orchestrator,\n isEnabled: a.is_enabled,\n avatarUrl: a.avatar_url,\n }));\n }\n\n /**\n * List the models the caller may pick this turn — expanded from the curated\n * catalog of the providers the team has connected (client-side model\n * selection). Backs the composer's model picker. Scoped to the active agent\n * via X-Agent-ID, same as {@link getAgentStatus}.\n */\n async getModels(): Promise<ModelOption[]> {\n const raw = await this.get<\n {\n provider: string;\n provider_display: string;\n model: string;\n thinking: boolean;\n tools: boolean;\n vision: boolean;\n thinking_mode: string;\n supports_effort?: boolean;\n }[]\n >(\"/v1/models\");\n return raw.map((m) => ({\n provider: m.provider,\n providerDisplay: m.provider_display,\n model: m.model,\n thinking: m.thinking,\n tools: m.tools,\n vision: m.vision,\n thinkingMode: m.thinking_mode,\n // Coerce so the non-optional `supportsEffort: boolean` stays honest even\n // against an older backend that omits `supports_effort` (→ false = safe:\n // the effort control is hidden). Unlike the always-present siblings above,\n // this field can be absent, so it's the one that needs coercion.\n supportsEffort: Boolean(m.supports_effort),\n }));\n }\n\n async getSkills(): Promise<SkillInfo[]> {\n const raw = await this.get<\n {\n name: string;\n display_name: string;\n description: string;\n is_enabled: boolean;\n }[]\n >(\"/v1/skills\");\n return raw.map((s) => ({\n name: s.name,\n displayName: s.display_name,\n description: s.description,\n isEnabled: s.is_enabled,\n }));\n }\n\n async getConversationEvents(\n conversationId: string,\n jobId?: string,\n ): Promise<ConversationEvent[]> {\n let url = `/v1/conversations/${encodeURIComponent(conversationId)}/events`;\n if (jobId) url += `?job_id=${encodeURIComponent(jobId)}`;\n return this.get(url);\n }\n\n async submitToolResult(request: ToolResultRequest): Promise<void> {\n await this.post(\"/v1/tool-result\", request);\n }\n\n async submitToolApproval(request: ToolApprovalRequest): Promise<void> {\n await this.post(\"/v1/tool-approval\", request);\n }\n\n // --- End-user tool-permission self-service ---\n\n /**\n * List the current end user's own remembered tool-permission grants.\n * Only `conversation`/`always` grants exist (`once` is never persisted).\n * Paginated via `limit` (default 100, max 200) / `offset`; `total` lets you\n * page through all of them.\n */\n async getMyToolPermissions(options?: {\n limit?: number;\n offset?: number;\n }): Promise<MyToolGrantsPage> {\n const params = new URLSearchParams();\n if (options?.limit != null) {\n const safeLimit = Math.max(\n 1,\n Math.min(200, Math.floor(Number(options.limit))),\n );\n params.set(\"limit\", String(safeLimit));\n }\n if (options?.offset != null) {\n const safeOffset = Math.max(0, Math.floor(Number(options.offset)));\n params.set(\"offset\", String(safeOffset));\n }\n const qs = params.toString();\n const raw = await this.get<{\n grants: {\n id: string;\n tool_name: string;\n decision: \"allow\" | \"deny\";\n scope: \"conversation\" | \"always\";\n conversation_id: string | null;\n created_at: string;\n }[];\n total: number;\n limit: number;\n offset: number;\n }>(`/v1/me/tool-permissions${qs ? `?${qs}` : \"\"}`);\n return {\n grants: raw.grants.map((g) => ({\n id: g.id,\n toolName: g.tool_name,\n decision: g.decision,\n scope: g.scope,\n conversationId: g.conversation_id,\n createdAt: g.created_at,\n })),\n total: raw.total,\n limit: raw.limit,\n offset: raw.offset,\n };\n }\n\n /**\n * Revoke one of the current end user's remembered grants by id. The agent\n * will ask again the next time that tool is used.\n */\n async revokeToolPermission(id: string): Promise<void> {\n await this.del(`/v1/me/tool-permissions/${encodeURIComponent(id)}`);\n }\n\n // --- Conversation Assets ---\n\n private mapAsset(raw: Record<string, unknown>): ConversationAsset {\n return {\n id: raw.id as string,\n kind: raw.kind as \"upload\" | \"output\",\n originalName: raw.original_name as string,\n mediaType: raw.media_type as string,\n sizeBytes: raw.size_bytes as number,\n workspacePath: raw.workspace_path as string | undefined,\n sourceMessageId: raw.source_message_id as string | undefined,\n agentName: raw.agent_name as string | undefined,\n // The API serializes an unsigned asset as `url: null`; normalize to\n // undefined so it matches the `url?: string` type and consumers that\n // check `!== undefined` never receive a null.\n url: (raw.url as string | null) ?? undefined,\n createdAt: raw.created_at as string,\n };\n }\n\n async uploadFile(\n conversationId: string,\n file: Blob,\n filename?: string,\n ): Promise<ConversationAsset> {\n const formData = new FormData();\n formData.append(\"file\", file, filename);\n\n const response = await this.fetchFn(\n `${this.baseURL}/v1/conversations/${encodeURIComponent(conversationId)}/uploads`,\n {\n method: \"POST\",\n headers: this.authHeaders,\n body: formData,\n },\n ).catch((err) => {\n throw new ConnectionError(\n err instanceof Error ? err.message : \"Failed to connect\",\n );\n });\n await this.handleError(response);\n const raw = await response.json();\n return this.mapAsset(raw as Record<string, unknown>);\n }\n\n async listUploads(conversationId: string): Promise<ConversationAsset[]> {\n const raw = await this.get<Record<string, unknown>[]>(\n `/v1/conversations/${encodeURIComponent(conversationId)}/uploads`,\n );\n return raw.map((r) => this.mapAsset(r));\n }\n\n async listOutputs(conversationId: string): Promise<ConversationAsset[]> {\n const raw = await this.get<Record<string, unknown>[]>(\n `/v1/conversations/${encodeURIComponent(conversationId)}/outputs`,\n );\n return raw.map((r) => this.mapAsset(r));\n }\n\n // --- Account-scoped discovery (user-token mode) ---\n //\n // Lets a signed-in user pick which team/agent they want to act on.\n // Backend gates these on OIDC user context (no X-Agent-ID required) —\n // sending them in API-key mode yields 401.\n\n async listTeams(): Promise<TeamSummary[]> {\n const raw = await this.get<\n Array<{\n id: string;\n name: string;\n slug: string;\n is_default: boolean;\n role: string;\n }>\n >(\"/v1/teams\");\n return raw.map((t) => ({\n id: t.id,\n name: t.name,\n slug: t.slug,\n isDefault: t.is_default,\n role: t.role,\n }));\n }\n\n /**\n * List the team-level agents (formerly \"projects\") the signed-in user can\n * open — the pickable workspaces under a team. Not to be confused with\n * `getAgents()`, which lists the AI personas inside the active agent.\n */\n async listAgents(teamId: string): Promise<TeamAgentSummary[]> {\n const raw = await this.get<\n Array<{\n id: string;\n name: string;\n team_id: string;\n created_at: string;\n updated_at: string;\n }>\n >(`/v1/teams/${encodeURIComponent(teamId)}/agents`);\n return raw.map((a) => ({\n id: a.id,\n name: a.name,\n teamId: a.team_id,\n createdAt: a.created_at,\n updatedAt: a.updated_at,\n }));\n }\n\n // --- Jobs API ---\n\n async createJob(request: ChatStreamRequest): Promise<JobCreateResponse> {\n return this.post<JobCreateResponse>(\"/v1/jobs\", request);\n }\n\n async *streamJobEvents(\n jobId: string,\n afterSeq = -1,\n signal?: AbortSignal,\n ): AsyncGenerator<ChatStreamEvent> {\n const url = `${this.baseURL}/v1/jobs/${encodeURIComponent(jobId)}/events?after=${afterSeq}`;\n yield* streamJobSSE({\n url,\n headers: this.headers,\n signal,\n fetchFn: this.fetchFn,\n });\n }\n\n async cancelJob(jobId: string): Promise<void> {\n await this.post(`/v1/jobs/${encodeURIComponent(jobId)}/cancel`, {});\n }\n\n async getJob(jobId: string): Promise<JobStatus> {\n const raw = await this.get<{\n job_id: string;\n status: string;\n created_at?: string | null;\n started_at?: string | null;\n completed_at?: string | null;\n error_message?: string | null;\n input_tokens?: number;\n output_tokens?: number;\n }>(`/v1/jobs/${encodeURIComponent(jobId)}`);\n return {\n jobId: raw.job_id,\n status: raw.status,\n createdAt: raw.created_at ?? null,\n startedAt: raw.started_at ?? null,\n completedAt: raw.completed_at ?? null,\n errorMessage: raw.error_message ?? null,\n inputTokens: raw.input_tokens ?? 0,\n outputTokens: raw.output_tokens ?? 0,\n };\n }\n\n async submitFeedback(\n jobId: string,\n request: FeedbackRequest,\n ): Promise<FeedbackResponse> {\n const body: { rating: 1 | -1; comment?: string } = {\n rating: request.rating,\n };\n if (request.comment != null) body.comment = request.comment;\n const raw = await this.post<{\n id: string;\n job_id: string;\n rating: number;\n comment: string | null;\n created_at: string;\n }>(`/v1/jobs/${encodeURIComponent(jobId)}/feedback`, body);\n return {\n id: raw.id,\n jobId: raw.job_id,\n rating: raw.rating,\n comment: raw.comment,\n createdAt: raw.created_at,\n };\n }\n\n async getActiveJob(conversationId: string): Promise<ActiveJob> {\n const raw = await this.get<{\n job_id: string | null;\n status: string;\n }>(`/v1/conversations/${encodeURIComponent(conversationId)}/active-job`);\n return {\n jobId: raw.job_id ?? null,\n status: raw.status,\n };\n }\n\n async listJobs(conversationId: string): Promise<JobSummary[]> {\n const raw = await this.get<\n {\n job_id: string;\n status: string;\n replaces_job_id?: string | null;\n response_content?: Record<string, unknown> | null;\n metrics?: Record<string, unknown> | null;\n created_at?: string | null;\n }[]\n >(`/v1/conversations/${encodeURIComponent(conversationId)}/jobs`);\n return raw.map((j) => ({\n jobId: j.job_id,\n status: j.status,\n replacesJobId: j.replaces_job_id ?? null,\n responseContent: j.response_content ?? null,\n metrics: j.metrics ?? null,\n createdAt: j.created_at ?? null,\n }));\n }\n}\n","import type { Conversation, Message } from \"./types.js\";\n\nexport interface ChatStorage {\n fetchConversations(): Promise<Conversation[]>;\n fetchConversation(id: string): Promise<Conversation | null>;\n createConversation(id: string, title: string): Promise<Conversation>;\n updateConversationTitle(id: string, title: string): Promise<void>;\n deleteConversation(id: string): Promise<void>;\n fetchMessages(conversationId: string): Promise<Message[]>;\n addMessage(message: Message, conversationId: string): Promise<void>;\n updateMessageStatus(id: string, status: Message[\"status\"]): Promise<void>;\n deleteMessage(id: string): Promise<void>;\n}\n\nexport class InMemoryStorage implements ChatStorage {\n private conversations = new Map<string, Conversation>();\n private messages = new Map<string, Message[]>();\n\n async fetchConversations(): Promise<Conversation[]> {\n return Array.from(this.conversations.values()).sort(\n (a, b) =>\n new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime(),\n );\n }\n\n async fetchConversation(id: string): Promise<Conversation | null> {\n return this.conversations.get(id) ?? null;\n }\n\n async createConversation(id: string, title: string): Promise<Conversation> {\n const now = new Date().toISOString();\n const conversation: Conversation = {\n id,\n title,\n messageCount: 0,\n createdAt: now,\n updatedAt: now,\n };\n this.conversations.set(id, conversation);\n this.messages.set(id, []);\n return conversation;\n }\n\n async updateConversationTitle(id: string, title: string): Promise<void> {\n const conv = this.conversations.get(id);\n if (conv) {\n conv.title = title;\n conv.updatedAt = new Date().toISOString();\n }\n }\n\n async deleteConversation(id: string): Promise<void> {\n this.conversations.delete(id);\n this.messages.delete(id);\n }\n\n async fetchMessages(conversationId: string): Promise<Message[]> {\n return this.messages.get(conversationId) ?? [];\n }\n\n async addMessage(message: Message, conversationId: string): Promise<void> {\n const msgs = this.messages.get(conversationId) ?? [];\n msgs.push(message);\n this.messages.set(conversationId, msgs);\n\n const conv = this.conversations.get(conversationId);\n if (conv) {\n conv.messageCount = msgs.length;\n conv.updatedAt = new Date().toISOString();\n }\n }\n\n async updateMessageStatus(\n id: string,\n status: Message[\"status\"],\n ): Promise<void> {\n for (const msgs of this.messages.values()) {\n const msg = msgs.find((m) => m.id === id);\n if (msg) {\n msg.status = status;\n return;\n }\n }\n }\n\n async deleteMessage(id: string): Promise<void> {\n for (const [convId, msgs] of this.messages.entries()) {\n const idx = msgs.findIndex((m) => m.id === id);\n if (idx !== -1) {\n msgs.splice(idx, 1);\n const conv = this.conversations.get(convId);\n if (conv) {\n conv.messageCount = msgs.length;\n }\n return;\n }\n }\n }\n}\n","import type { ToolCallRequest, ToolDefinition, ToolResult } from \"./types.js\";\n\nexport type ToolHandler = (args: Record<string, unknown>) => Promise<string>;\n\ninterface RegisteredTool {\n name: string;\n description: string;\n inputSchema: Record<string, unknown>;\n handler: ToolHandler;\n}\n\n/** Validates tool name: alphanumeric, hyphens, underscores, dots only */\nconst TOOL_NAME_PATTERN = /^[a-zA-Z0-9_.\\-]+$/;\n\n/** Strips prototype-polluting keys from an object */\nfunction sanitizeArgs(args: Record<string, unknown>): Record<string, unknown> {\n const clean: Record<string, unknown> = Object.create(null);\n for (const key of Object.keys(args)) {\n if (key === \"__proto__\" || key === \"constructor\" || key === \"prototype\") {\n continue;\n }\n clean[key] = args[key];\n }\n return clean;\n}\n\nexport class ToolRegistry {\n private tools = new Map<string, RegisteredTool>();\n\n registerTool(\n name: string,\n description: string,\n inputSchema: Record<string, unknown>,\n handler: ToolHandler,\n ): void {\n if (!name || !TOOL_NAME_PATTERN.test(name)) {\n throw new Error(\n `Invalid tool name \"${name}\" - must match ${TOOL_NAME_PATTERN}`,\n );\n }\n if (name.length > 256) {\n throw new Error(\"Tool name must be 256 characters or fewer\");\n }\n this.tools.set(name, { name, description, inputSchema, handler });\n }\n\n unregisterTool(name: string): boolean {\n return this.tools.delete(name);\n }\n\n hasTool(name: string): boolean {\n return this.tools.has(name);\n }\n\n async executeTool(request: ToolCallRequest): Promise<ToolResult> {\n const tool = this.tools.get(request.toolName);\n if (!tool) {\n return {\n call_id: request.callId,\n tool_name: request.toolName,\n result: `Tool \"${request.toolName}\" not found`,\n is_error: true,\n };\n }\n\n try {\n const result = await tool.handler(sanitizeArgs(request.arguments));\n return {\n call_id: request.callId,\n tool_name: request.toolName,\n result,\n is_error: false,\n };\n } catch (err) {\n return {\n call_id: request.callId,\n tool_name: request.toolName,\n result: err instanceof Error ? err.message : String(err),\n is_error: true,\n };\n }\n }\n\n getManifest(): ToolDefinition[] {\n return Array.from(this.tools.values()).map((t) => ({\n name: t.name,\n description: t.description,\n parameters: t.inputSchema,\n }));\n }\n\n getToolNames(): string[] {\n return Array.from(this.tools.keys());\n }\n\n clear(): void {\n this.tools.clear();\n }\n}\n","// =============================================================================\n// Protocol adapter registry — pluggable UI protocol layer\n// =============================================================================\n//\n// The SDK is framework-agnostic: it never renders. But when the backend\n// emits an MCP-style embedded resource (A2UI today, other protocols\n// tomorrow) the consumer needs to hand the payload to a renderer. This\n// file defines the contract between those two sides:\n//\n// • `ProtocolAdapter` — an opaque, framework-specific handle that\n// claims a MIME type. The SDK stores it; the consumer narrows the\n// type when reading it back out.\n//\n// • `ProtocolRegistry` — a MIME-keyed map of adapters. One lives on\n// each `ChatSession` so its lifecycle matches the session: clearing\n// on disconnect, swapping on reconnect with a different agent.\n//\n// The consumer decides _whether_ to register an adapter — typically by\n// consulting `session.agentStatus.uiComponents` after `connect()`.\n// The SDK never auto-registers anything; it just stores what it's told.\n// =============================================================================\n\n/**\n * Minimal adapter contract. Frontends extend this with a `render()`\n * method (or equivalent) returning their framework's view type.\n */\nexport interface ProtocolAdapter {\n /** IANA-style MIME type this adapter handles (e.g. ``application/json+a2ui``). */\n readonly mimeType: string;\n}\n\n/**\n * MIME-keyed adapter map, generic on the adapter subtype so consumers\n * can register richer shapes without casting on every read.\n */\nexport class ProtocolRegistry<T extends ProtocolAdapter = ProtocolAdapter> {\n private adapters = new Map<string, T>();\n\n /** Register or replace the adapter for a MIME type. */\n register(adapter: T): void {\n this.adapters.set(adapter.mimeType, adapter);\n }\n\n /** Remove the adapter for a MIME type. No-op if not registered. */\n unregister(mimeType: string): void {\n this.adapters.delete(mimeType);\n }\n\n /** Returns the adapter for a MIME type, or ``null`` if none is registered. */\n get(mimeType: string): T | null {\n return this.adapters.get(mimeType) ?? null;\n }\n\n has(mimeType: string): boolean {\n return this.adapters.has(mimeType);\n }\n\n /** Drop every adapter. Called when a session disconnects. */\n clear(): void {\n this.adapters.clear();\n }\n\n listMimeTypes(): string[] {\n return Array.from(this.adapters.keys());\n }\n}\n","// =============================================================================\n// Wire → ChatEvent translation\n//\n// Single source of truth for the pure translation from backend wire types\n// (snake_case, mirroring Pydantic) to consumer-facing ChatEvents (camelCase).\n// Shared between the live session loop and the persisted-event replay path,\n// so adding a new event type only requires updating one place.\n// =============================================================================\n\nimport type {\n BlockDeltaPayload,\n ChatEvent,\n WireBlockDeltaPayload,\n WireEvent,\n} from \"./types.js\";\nimport type { MemoryRecord, TodoItem } from \"./custom-events.js\";\n\n// --- Delta channels ---\n\n/**\n * Translate a WireBlockDeltaPayload into the consumer-facing shape. Returns\n * ``null`` for unknown channels so forward-compatible backends can add new\n * ones without breaking old clients.\n */\nexport function translateDelta(\n wire: WireBlockDeltaPayload,\n): BlockDeltaPayload | null {\n switch (wire.channel) {\n case \"text\":\n return { channel: \"text\", text: wire.text };\n case \"thinking\":\n return { channel: \"thinking\", text: wire.text };\n case \"signature\":\n return { channel: \"signature\", signature: wire.signature };\n case \"input\":\n return { channel: \"input\", partialJson: wire.partial_json };\n case \"input_arg\":\n return {\n channel: \"inputArg\",\n argName: wire.arg_name,\n text: wire.text,\n };\n case \"output\":\n return { channel: \"output\", stream: wire.stream, chunk: wire.chunk };\n case \"status\":\n return {\n channel: \"status\",\n status: wire.status,\n note: wire.note,\n };\n default:\n return null;\n }\n}\n\n// --- Custom events ---\n\nfunction translateAgentIdentity(raw: Record<string, unknown>) {\n return {\n name: (raw.name as string) ?? \"\",\n displayName: (raw.display_name as string | null) ?? null,\n avatarUrl: (raw.avatar_url as string | null) ?? null,\n description: (raw.description as string | null) ?? null,\n };\n}\n\n/**\n * Translate a wire custom event (``{type: \"custom\", name, data}``) into a\n * typed ChatEvent. Unknown names fall through to the generic ``custom``\n * passthrough so consumers can still observe future backends.\n */\nexport function translateCustomEvent(\n name: string,\n data: Record<string, unknown>,\n): ChatEvent {\n switch (name) {\n case \"user_message\":\n return {\n type: \"user_message\",\n content: (data.content as string) ?? \"\",\n createdAt: data.created_at as number | undefined,\n };\n case \"title_generated\":\n return {\n type: \"title_generated\",\n title: (data.title as string) ?? \"\",\n };\n case \"todo_update\":\n return {\n type: \"todo_update\",\n todos: (data.todos as TodoItem[]) ?? [],\n };\n case \"context_update\":\n return {\n type: \"context_update\",\n context: (data.context as Record<string, unknown>) ?? {},\n phase: (data.phase as string | null) ?? null,\n updatedAt: (data.updated_at as number | null) ?? null,\n };\n case \"subagent_start\":\n return {\n type: \"subagent_start\",\n agent: translateAgentIdentity(\n (data.agent as Record<string, unknown>) ?? {},\n ),\n taskCallId: (data.task_call_id as string | null) ?? null,\n };\n case \"subagent_stop\":\n return {\n type: \"subagent_stop\",\n agent: translateAgentIdentity(\n (data.agent as Record<string, unknown>) ?? {},\n ),\n taskCallId: (data.task_call_id as string | null) ?? null,\n };\n case \"context_warning\":\n return {\n type: \"context_warning\",\n severity: (data.severity as string) ?? \"warning\",\n utilizationPct: (data.utilization_pct as number) ?? 0,\n remainingTokens: (data.remaining_tokens as number) ?? 0,\n windowTokens: (data.window_tokens as number) ?? 0,\n inputTokens: (data.input_tokens as number) ?? 0,\n message: (data.message as string) ?? \"\",\n };\n case \"memory_recall\":\n return {\n type: \"memory_recall\",\n memories: (data.memories as MemoryRecord[] | undefined) ?? [],\n };\n case \"memory_update\":\n return {\n type: \"memory_update\",\n action: (data.action as string) ?? \"\",\n memoryId: (data.memory_id as string | null) ?? null,\n key: (data.key as string | null) ?? null,\n namespace: (data.namespace as string | null) ?? null,\n };\n case \"desktop_stream\":\n return {\n type: \"desktop_stream\",\n url: (data.url as string) ?? \"\",\n sandboxId: (data.sandbox_id as string | null) ?? null,\n };\n case \"attachment_staged\":\n return {\n type: \"attachment_staged\",\n attachmentId: (data.attachment_id as string) ?? \"\",\n filename: (data.filename as string) ?? \"\",\n contentType: (data.content_type as string | null) ?? null,\n sizeBytes: (data.size_bytes as number | null) ?? null,\n };\n case \"workspace_ready\":\n return {\n type: \"workspace_ready\",\n sandboxId: (data.sandbox_id as string) ?? \"\",\n workspacePath: (data.workspace_path as string | null) ?? null,\n };\n case \"asset_created\":\n return {\n type: \"asset_created\",\n assetId: (data.asset_id as string) ?? \"\",\n filename: (data.filename as string) ?? \"\",\n url: (data.url as string | null) ?? null,\n contentType: (data.content_type as string | null) ?? null,\n };\n case \"tool_approval_requested\":\n return {\n type: \"tool_approval_requested\",\n toolName: (data.tool_name as string) ?? \"\",\n callId: (data.call_id as string) ?? \"\",\n arguments: (data.arguments as Record<string, unknown>) ?? {},\n riskLevel: (data.risk_level as string | null) ?? null,\n reason: (data.reason as string | null) ?? null,\n };\n case \"tool_approval_granted\":\n return {\n type: \"tool_approval_granted\",\n toolName: (data.tool_name as string) ?? \"\",\n callId: (data.call_id as string) ?? \"\",\n };\n case \"tool_permission_denied\":\n return {\n type: \"tool_permission_denied\",\n toolName: (data.tool_name as string) ?? \"\",\n callId: (data.call_id as string) ?? \"\",\n reason: (data.reason as string | null) ?? null,\n deniedBy: (data.denied_by as string | null) ?? null,\n };\n case \"tool_harness_warning\":\n return {\n type: \"tool_harness_warning\",\n toolName: (data.tool_name as string) ?? \"\",\n callId: (data.call_id as string) ?? \"\",\n message: (data.message as string | null) ?? null,\n details: (data.details as Record<string, unknown> | null) ?? null,\n };\n case \"user_unavailable\":\n return {\n type: \"user_unavailable\",\n consecutiveTimeouts: (data.consecutive_timeouts as number) ?? 0,\n toolName: (data.tool_name as string | null) ?? null,\n };\n case \"prompt_suggestion\":\n return {\n type: \"prompt_suggestion\",\n suggestions: (data.suggestions as string[]) ?? [],\n };\n case \"state_changed\":\n return {\n type: \"state_changed\",\n state: (data.state as string) ?? \"\",\n };\n default:\n return { type: \"custom\", name, data };\n }\n}\n\n// --- Top-level wire events ---\n\n/**\n * Legacy-transport payload extractor for events that aren't wrapped in a\n * ``CustomEvent`` envelope. The backend's ``writer.emit(name, data)`` path\n * spreads the payload fields at the top level of the wire object (see\n * ``backend/src/jobs/suggestion_hook.py`` for ``prompt_suggestion``), so\n * the wire object itself *is* the ``data`` dict for ``translateCustomEvent``.\n * This helper isolates the unsafe cast to one place.\n */\nfunction legacyCustomEventData(wire: WireEvent): Record<string, unknown> {\n return wire as unknown as Record<string, unknown>;\n}\n\n/**\n * Translate a full WireEvent into its typed ChatEvent counterpart. Returns\n * ``null`` when the wire payload is malformed (e.g. unknown delta channel)\n * so the caller can skip it without crashing the stream.\n */\nexport function translateWireEvent(wire: WireEvent): ChatEvent | null {\n // Legacy transport: ``prompt_suggestion`` is emitted via the raw\n // ``writer.emit()`` path rather than wrapped in a CustomEvent envelope,\n // so its ``type`` field is the event name itself. Route it through the\n // custom-event translator to keep the mapping in one place.\n if ((wire as { type: string }).type === \"prompt_suggestion\") {\n return translateCustomEvent(\n \"prompt_suggestion\",\n legacyCustomEventData(wire),\n );\n }\n switch (wire.type) {\n case \"message_start\":\n return {\n type: \"message_start\",\n turnId: wire.turn_id,\n model: wire.model,\n agentName: wire.agent_name,\n agentDisplayName: wire.agent_display_name,\n agentAvatarUrl: wire.agent_avatar_url,\n };\n case \"block_start\":\n return {\n type: \"block_start\",\n turnId: wire.turn_id,\n path: wire.path,\n parentPath: wire.parent_path ?? null,\n kind: wire.kind,\n metadata: wire.metadata,\n };\n case \"block_delta\": {\n const delta = translateDelta(wire.delta);\n if (!delta) return null;\n return {\n type: \"block_delta\",\n turnId: wire.turn_id,\n path: wire.path,\n delta,\n };\n }\n case \"block_stop\":\n return {\n type: \"block_stop\",\n turnId: wire.turn_id,\n path: wire.path,\n status: wire.status,\n final: wire.final,\n };\n case \"message_stop\":\n return {\n type: \"message_stop\",\n turnId: wire.turn_id,\n jobId: wire.job_id,\n stopReason: wire.stop_reason,\n usage: {\n inputTokens: wire.usage.input_tokens ?? 0,\n outputTokens: wire.usage.output_tokens ?? 0,\n cachedTokens: wire.usage.cached_tokens ?? 0,\n },\n ttfbMs: wire.ttfb_ms,\n totalMs: wire.total_ms,\n stallCount: wire.stall_count,\n };\n case \"stall\":\n return {\n type: \"stall\",\n sinceLastEventMs: wire.since_last_event_ms,\n stallCount: wire.stall_count,\n };\n case \"retry\":\n return {\n type: \"retry\",\n attempt: wire.attempt,\n reason: wire.reason,\n backoffMs: wire.backoff_ms,\n strategy: wire.strategy ?? null,\n maxAttempts: wire.max_attempts ?? null,\n contextRecovery: wire.context_recovery ?? null,\n };\n case \"error\":\n return {\n type: \"error\",\n code: wire.code,\n message: wire.message,\n blockPath: wire.block_path ?? null,\n };\n case \"keepalive\":\n return {\n type: \"keepalive\",\n sinceLastEventMs: wire.since_last_event_ms,\n };\n case \"custom\":\n return translateCustomEvent(wire.name, wire.data);\n default: {\n // Exhaustive guard — a new WireEvent variant should force this switch\n // to be updated at compile time.\n const _exhaustive: never = wire;\n void _exhaustive;\n return null;\n }\n }\n}\n","import { AstralformClient } from \"./client.js\";\nimport { InMemoryStorage, type ChatStorage } from \"./storage.js\";\nimport { ToolRegistry } from \"./tools.js\";\nimport { ProtocolRegistry } from \"./protocol-registry.js\";\nimport { translateWireEvent } from \"./translate.js\";\nimport type {\n AgentInfo,\n AstralformConfig,\n ChatEvent,\n ChatStreamRequest,\n Conversation,\n ConversationEvent,\n Message,\n AgentStatus,\n SendOptions,\n SkillInfo,\n ToolCallRequest,\n ToolResult,\n WireEvent,\n} from \"./types.js\";\nimport { generateId } from \"./utils.js\";\nimport {\n AuthenticationError,\n ConnectionError,\n RateLimitError,\n} from \"./errors.js\";\n\ntype ChatEventHandler = (event: ChatEvent) => void;\n\n/**\n * Bounded auto-reconnect for a live SSE stream that drops mid-turn (worker\n * restart, network blip). We resume from ``lastSeq`` — the backend replays\n * missed events (``?after=seq``) and, for a job that already died, back-fills a\n * terminal event — so the UI recovers without a manual page refresh. Backoff is\n * exponential and capped; total window (~17s over 6 tries) comfortably covers a\n * server restart without spinning forever if the job is genuinely gone.\n */\nconst SSE_MAX_RECONNECTS = 6;\n\n// Retries for the client-tool result POST itself, independent of the SSE\n// reconnect loop — reconnecting the *stream* can't recover a failed *result\n// submission*, and retrying the POST avoids re-executing a client tool.\nconst TOOL_RESULT_MAX_RETRIES = 3;\n\n/**\n * Conversations fetched per page, by ``connect`` and ``loadMoreConversations``\n * alike. The two must use the same size: the offset is derived from how many\n * rows the server has returned so far, so a first page of a different size\n * would leave the second page's offset pointing at the wrong row.\n */\nexport const CONVERSATION_PAGE_SIZE = 50;\n\nfunction sseReconnectDelayMs(attempt: number): number {\n return Math.min(500 * 2 ** (attempt - 1), 5000);\n}\n\nfunction pathEquals(a: number[], b: number[]): boolean {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (a[i] !== b[i]) return false;\n }\n return true;\n}\n\n/**\n * ChatSession — translates the backend wire protocol into typed ChatEvents\n * for consumers. Owns HTTP + SSE plumbing, conversation state, and the\n * client-tool round-trip. Does NOT own block construction — consumers\n * build their own block state from the typed events.\n */\nexport class ChatSession {\n readonly client: AstralformClient;\n readonly toolRegistry: ToolRegistry;\n readonly storage: ChatStorage;\n /**\n * Pluggable UI protocol adapters. Consumers register a framework-\n * specific adapter (e.g. React) for each MIME type they can render,\n * typically gated on ``session.agentStatus.uiComponents.protocol``.\n * ``ToolBlock``-style consumers look up the adapter for an incoming\n * embedded resource and hand off rendering.\n */\n readonly protocols = new ProtocolRegistry();\n\n // State\n conversationId: string | null = null;\n conversations: Conversation[] = [];\n /**\n * Whether another page of conversations may exist on the server.\n *\n * Inferred from the last page being full, since the list endpoint returns a\n * bare array with no total. A total that happens to be an exact multiple of\n * the page size therefore costs one extra empty request before this flips —\n * cheaper than adding a count query to every list call.\n */\n hasMoreConversations = false;\n /** True while ``loadMoreConversations`` is in flight. */\n isLoadingConversations = false;\n messages: Message[] = [];\n isStreaming = false;\n agentStatus: AgentStatus | null = null;\n agents: AgentInfo[] = [];\n skills: SkillInfo[] = [];\n enabledClientTools = new Set<string>();\n modelDisplayName: string | null = null;\n\n /**\n * Ids of conversations the SERVER has handed us, which is the paging offset.\n *\n * Deliberately not ``conversations.length``. That array also holds\n * conversations created locally and unshifted on top (``createNewConversation``,\n * and the auto-created conversation in ``consumeJobStream``), so using its\n * length as the offset would over-count and silently skip a row of real\n * history on the next page. Tracking ids rather than a counter also makes\n * deletion self-correcting: removing a server-sourced conversation shifts\n * every later page up by one, and dropping its id from this set is exactly\n * that shift — while deleting a purely local one correctly changes nothing.\n */\n private serverConversationIds = new Set<string>();\n\n /**\n * Bumped every time ``connect()`` re-seeds the conversation list.\n *\n * A ``loadMoreConversations`` request issued before a re-seed describes the\n * OLD paging state, so applying its response afterwards both appends the\n * wrong rows and corrupts the offset. Concretely: with 100 rows held, an\n * offset-100 response landing after a reconnect has reset to rows 0-49 would\n * append rows 100-149 — a 50-row hole — and leave the id set at 100, so every\n * later page re-requests offset 100 and never advances again. The generation\n * is captured before the await and rechecked after, so a superseded response\n * is discarded instead.\n */\n private conversationsGeneration = 0;\n\n // Minimal in-session accumulation for the assistant message record.\n // Only top-level ``text`` blocks contribute; subagent / tool output\n // is tracked by the consumer's own block store.\n private accumulatedText = \"\";\n private currentTextPath: number[] | null = null;\n\n private handlers: Set<ChatEventHandler> = new Set();\n private abortController: AbortController | null = null;\n\n constructor(config: AstralformConfig, storage?: ChatStorage) {\n this.client = new AstralformClient(config);\n this.toolRegistry = new ToolRegistry();\n this.storage = storage ?? new InMemoryStorage();\n }\n\n on(handler: ChatEventHandler): () => void {\n this.handlers.add(handler);\n return () => {\n this.handlers.delete(handler);\n };\n }\n\n private emit(event: ChatEvent): void {\n for (const handler of this.handlers) {\n try {\n handler(event);\n } catch {\n // Don't let handler errors crash the session\n }\n }\n }\n\n async connect(): Promise<void> {\n const [status, conversations, agents, skills] = await Promise.allSettled([\n this.client.getAgentStatus(),\n this.client.getConversations(CONVERSATION_PAGE_SIZE),\n this.client.getAgents().catch(() => [] as AgentInfo[]),\n this.client.getSkills().catch(() => [] as SkillInfo[]),\n ]);\n\n if (status.status === \"fulfilled\") {\n this.agentStatus = status.value;\n }\n if (conversations.status === \"fulfilled\") {\n // Reconnect re-seeds page 1, so reset the paging state with it rather\n // than letting a previous connection's offset carry over. Bumping the\n // generation here (and only here — a FAILED fetch leaves the list intact,\n // so an in-flight page is still valid against it) invalidates any page\n // request already in flight against the old offset.\n this.conversationsGeneration++;\n this.conversations = conversations.value;\n this.serverConversationIds = new Set(\n conversations.value.map((c) => c.id),\n );\n this.hasMoreConversations =\n conversations.value.length === CONVERSATION_PAGE_SIZE;\n }\n if (agents.status === \"fulfilled\") {\n this.agents = agents.value;\n }\n if (skills.status === \"fulfilled\") {\n this.skills = skills.value;\n }\n\n this.emit({ type: \"connected\" });\n }\n\n async send(content: string, options?: SendOptions): Promise<void> {\n if ((options?.provider == null) !== (options?.model == null)) {\n throw new Error(\n \"`provider` and `model` must be supplied together (client-side model selection).\",\n );\n }\n if (this.isStreaming) return;\n\n const conversationId =\n options?.conversationId ?? this.conversationId ?? undefined;\n\n const userMessage: Message = {\n id: generateId(),\n conversationId: conversationId ?? \"\",\n role: \"user\",\n content,\n status: \"complete\",\n createdAt: new Date().toISOString(),\n };\n if (conversationId) {\n await this.storage.addMessage(userMessage, conversationId);\n }\n this.messages.push(userMessage);\n\n const request: ChatStreamRequest = {\n message: content,\n conversation_id: conversationId,\n mcp_manifest: this.toolRegistry.getManifest(),\n enabled_mcp: Array.from(\n options?.enabledClientTools ?? this.enabledClientTools,\n ),\n upload_ids: options?.uploadIds,\n agent_name: options?.agentName,\n plan_mode: options?.planMode,\n image_mode: options?.imageMode,\n goal: options?.goal,\n // Per-request model choice (client-side model selection).\n provider: options?.provider,\n model: options?.model,\n reasoning_effort: options?.reasoningEffort,\n temperature: options?.temperature,\n };\n\n await this.processStream(request);\n }\n\n async resendFromCheckpoint(\n messageId: string,\n newContent: string,\n ): Promise<void> {\n if (this.isStreaming) return;\n\n const request: ChatStreamRequest = {\n message: newContent,\n conversation_id: this.conversationId ?? undefined,\n resend_from: messageId,\n mcp_manifest: this.toolRegistry.getManifest(),\n enabled_mcp: Array.from(this.enabledClientTools),\n };\n\n await this.processStream(request);\n }\n\n private resetStreamingState(): void {\n this.accumulatedText = \"\";\n this.currentTextPath = null;\n }\n\n private async processStream(request: ChatStreamRequest): Promise<void> {\n this.isStreaming = true;\n this.resetStreamingState();\n this.abortController = new AbortController();\n\n try {\n await this.consumeJobStream(request);\n } catch (err) {\n if (!(err instanceof DOMException && err.name === \"AbortError\")) {\n this.emit({\n type: \"error\",\n code: \"connection_error\",\n message: err instanceof Error ? err.message : String(err),\n blockPath: null,\n });\n }\n } finally {\n this.isStreaming = false;\n this.abortController = null;\n }\n }\n\n /** Last received sequence number for resumable reconnection */\n private lastSeq = -1;\n\n /**\n * Client-tool call_ids whose result was already submitted this turn. On a\n * reconnect the resumed stream can replay a tool request we already handled;\n * this dedups so each is executed + submitted at most once (but a request we\n * never submitted still runs). Cleared at the start of each turn.\n */\n private submittedToolCallIds = new Set<string>();\n\n /** Current job ID for cancellation */\n currentJobId: string | null = null;\n\n private async consumeJobStream(request: ChatStreamRequest): Promise<void> {\n const job = await this.client.createJob(request);\n this.currentJobId = job.job_id;\n\n const conversationId = job.conversation_id;\n if (!this.conversationId) {\n this.conversationId = conversationId;\n }\n // Ensure the conversation exists in both the local array and\n // ChatStorage so title_generated, completeStream, and fallback\n // reload all work for backend-created conversations.\n if (!this.conversations.some((c) => c.id === conversationId)) {\n const now = new Date().toISOString();\n const conv = {\n id: conversationId,\n title: \"\",\n messageCount: 0,\n createdAt: now,\n updatedAt: now,\n };\n this.conversations.unshift(conv);\n await this.storage.createConversation(conversationId, \"\").catch(() => {});\n }\n // Backfill the just-sent user message if send() ran before we knew the\n // conversation id (first turn of an auto-created conversation).\n const lastMsg = this.messages[this.messages.length - 1];\n if (lastMsg?.role === \"user\" && !lastMsg.conversationId) {\n lastMsg.conversationId = conversationId;\n await this.storage.addMessage(lastMsg, conversationId).catch(() => {});\n }\n const messageId = job.message_id;\n this.lastSeq = -1;\n this.submittedToolCallIds.clear();\n\n await this.consumeEventStream(\n job.job_id,\n conversationId,\n messageId,\n true, // executeClientTools\n );\n }\n\n /**\n * Shared event consumption loop. Parses each wire event, updates\n * minimal session state, and emits typed ChatEvents to consumers.\n */\n private async consumeEventStream(\n jobId: string,\n conversationId: string,\n messageId: string,\n executeClientTools: boolean,\n ): Promise<void> {\n // Capture the signal ONCE. detach()/disconnect() abort the controller and\n // then null it out synchronously, so re-reading this.abortController later\n // would lose the aborted state (?. → undefined → falsy) and the loop would\n // reconnect an unstoppable, signal-less stream. The AbortSignal stays valid\n // (and stays aborted) even after the controller is gone.\n const signal = this.abortController?.signal;\n\n for (let attempt = 0; ; attempt++) {\n const stream = this.client.streamJobEvents(jobId, this.lastSeq, signal);\n // Client tools stay enabled across reconnects; re-seen tool requests are\n // deduped by submitted call_id in dispatchWireEvent, so a tool whose\n // result we never posted (drop before submit) still runs on resume.\n let sawTerminal: boolean;\n try {\n sawTerminal = await this.pumpStream(\n stream,\n conversationId,\n messageId,\n executeClientTools,\n );\n } catch (err) {\n if (signal?.aborted) return; // user cancelled / detached\n // Auth failures and rate limits can't be fixed by reconnecting (and\n // hammering a 429 is harmful) — surface them immediately. Genuine\n // connectivity failures (incl. a 5xx from a restarting server) retry.\n if (\n err instanceof AuthenticationError ||\n err instanceof RateLimitError\n ) {\n throw err;\n }\n if (attempt >= SSE_MAX_RECONNECTS) throw err;\n await this.sleepUnlessAborted(sseReconnectDelayMs(attempt + 1), signal);\n continue; // resume from lastSeq\n }\n\n // A terminal event (message_stop / error) ends the turn — including the\n // backend's back-filled terminal for a job that died mid-stream.\n if (sawTerminal || signal?.aborted) return;\n\n // Stream ended WITHOUT a terminal event: the worker/connection dropped\n // mid-turn. Resume from lastSeq so the backend can replay missed events\n // (and back-fill a terminal for a dead job) rather than leave the UI\n // hanging on \"working\".\n if (attempt >= SSE_MAX_RECONNECTS) {\n throw new ConnectionError(\"Lost connection to the response stream.\");\n }\n await this.sleepUnlessAborted(sseReconnectDelayMs(attempt + 1), signal);\n }\n }\n\n /**\n * Consume a single SSE stream to exhaustion. Returns whether a terminal\n * event (``message_stop`` / ``error``) was seen, so the caller can decide\n * whether an ended stream means \"turn done\" vs \"dropped, reconnect\".\n */\n private async pumpStream(\n stream: AsyncGenerator<{ data: string }>,\n conversationId: string,\n messageId: string,\n executeClientTools: boolean,\n ): Promise<boolean> {\n let sawTerminal = false;\n for await (const raw of stream) {\n let parsed: WireEvent;\n try {\n const data = JSON.parse(raw.data);\n if (\n typeof data !== \"object\" ||\n data === null ||\n typeof data.type !== \"string\"\n ) {\n // Legacy \"done\" sentinel — backend still emits it for subscribers.\n // Silently consume; the new protocol uses message_stop for turn end.\n if (typeof (data as { seq?: unknown })?.seq === \"number\") {\n this.lastSeq = (data as { seq: number }).seq;\n }\n continue;\n }\n parsed = data as WireEvent;\n if (typeof (data as Record<string, unknown>).seq === \"number\") {\n this.lastSeq = (data as Record<string, unknown>).seq as number;\n }\n } catch {\n continue;\n }\n\n if (parsed.type === \"message_stop\" || parsed.type === \"error\") {\n sawTerminal = true;\n }\n\n await this.dispatchWireEvent(\n parsed,\n conversationId,\n messageId,\n executeClientTools,\n );\n }\n return sawTerminal;\n }\n\n /** Sleep for ``ms``, resolving early if the turn is aborted mid-backoff. */\n private sleepUnlessAborted(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve) => {\n if (signal?.aborted) return resolve();\n const timer = setTimeout(() => {\n signal?.removeEventListener(\"abort\", onAbort);\n resolve();\n }, ms);\n const onAbort = () => {\n clearTimeout(timer);\n resolve();\n };\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n });\n }\n\n /** POST a client-tool result, retrying transient failures a few times. */\n private async submitToolResultWithRetry(\n payload: Parameters<AstralformClient[\"submitToolResult\"]>[0],\n ): Promise<void> {\n const signal = this.abortController?.signal;\n for (let attempt = 0; ; attempt++) {\n try {\n await this.client.submitToolResult(payload);\n return;\n } catch (err) {\n if (signal?.aborted) throw err;\n if (\n err instanceof AuthenticationError ||\n err instanceof RateLimitError\n ) {\n throw err;\n }\n if (attempt >= TOOL_RESULT_MAX_RETRIES) throw err;\n await this.sleepUnlessAborted(sseReconnectDelayMs(attempt + 1), signal);\n }\n }\n }\n\n private async dispatchWireEvent(\n wire: WireEvent,\n conversationId: string,\n messageId: string,\n executeClientTools: boolean,\n ): Promise<void> {\n // Side effects that depend on mutable session state must run before the\n // ChatEvent is emitted so consumers see a consistent view.\n this.applyWireSideEffects(wire, conversationId, messageId);\n\n const event = translateWireEvent(wire);\n if (event) {\n this.emit(event);\n }\n\n // Client tool round-trip — deferred to block_stop with\n // status=awaiting_client_result, where the parsed input is in final.input.\n if (\n executeClientTools &&\n wire.type === \"block_stop\" &&\n wire.status === \"awaiting_client_result\" &&\n wire.final?.call_id\n ) {\n const f = wire.final;\n const callId = (f.call_id as string) ?? \"\";\n // Dedup across reconnects: a resumed stream can replay a tool request we\n // already handled. Execute + submit each call_id at most once — but DO\n // run requests not yet submitted (e.g. the drop happened before we could\n // post the result), rather than skipping client tools wholesale.\n if (callId && !this.submittedToolCallIds.has(callId)) {\n const request: ToolCallRequest = {\n callId,\n toolName: (f.tool_name as string) ?? \"\",\n arguments: (f.input as Record<string, unknown>) ?? {},\n isClientTool: true,\n };\n const results = await this.executeClientTools([request]);\n // Retry the POST itself before giving up: reconnecting the SSE stream\n // can't recover a failed result submission, and retrying here avoids\n // re-executing the tool on a transient network blip.\n await this.submitToolResultWithRetry({\n conversation_id: conversationId,\n message_id: messageId,\n tool_results: results,\n });\n // Marked only after a successful submit, so a drop mid-POST re-runs it.\n this.submittedToolCallIds.add(callId);\n }\n }\n }\n\n /**\n * Synchronous replay of a single stored wire event — the side-effect +\n * translate + emit core of ``dispatchWireEvent`` without the (live-only)\n * client-tool round-trip. Called in a tight synchronous loop during history\n * restore so the consumer's per-event store writes batch into ONE render\n * instead of re-typing the whole conversation event by event.\n */\n private replayWireEvent(wire: WireEvent, conversationId: string): void {\n this.applyWireSideEffects(wire, conversationId, \"\");\n const event = translateWireEvent(wire);\n if (event) {\n this.emit(event);\n }\n }\n\n /**\n * State mutations driven by wire events. Kept separate from translation so\n * the pure wire → ChatEvent mapping can live in translate.ts and be reused\n * by the replay path.\n *\n * ``messageId`` is the server-assigned assistant message id for the current\n * turn; empty in the reconnect and conversation-switch replay paths where\n * messages have already been loaded from REST and shouldn't be re-pushed.\n */\n private applyWireSideEffects(\n wire: WireEvent,\n conversationId: string,\n messageId: string,\n ): void {\n switch (wire.type) {\n case \"message_start\":\n // Reset per-turn accumulator so multi-turn replay doesn't concatenate\n // text from prior turns into the next assistant message.\n this.resetStreamingState();\n if (wire.model) {\n this.modelDisplayName = wire.model;\n }\n return;\n\n case \"block_start\":\n // Track the currently open top-level text block so we can accumulate\n // its content for the assistant Message record.\n if (\n wire.kind === \"text\" &&\n (!wire.parent_path || wire.parent_path.length === 0)\n ) {\n this.currentTextPath = wire.path;\n }\n return;\n\n case \"block_delta\":\n if (\n wire.delta.channel === \"text\" &&\n this.currentTextPath !== null &&\n pathEquals(this.currentTextPath, wire.path)\n ) {\n this.accumulatedText += wire.delta.text;\n }\n return;\n\n case \"block_stop\":\n if (\n this.currentTextPath !== null &&\n pathEquals(this.currentTextPath, wire.path)\n ) {\n this.currentTextPath = null;\n }\n return;\n\n case \"message_stop\":\n // Only record the assistant message when we have the server's\n // message id. Reconnect/replay paths load messages via REST instead.\n if (messageId) {\n const assistantMessage: Message = {\n id: messageId,\n conversationId,\n role: \"assistant\",\n content: this.accumulatedText,\n status: \"complete\",\n createdAt: new Date().toISOString(),\n };\n this.messages.push(assistantMessage);\n this.storage\n .addMessage(assistantMessage, conversationId)\n .catch(() => {});\n }\n this.isStreaming = false;\n this.currentJobId = null;\n return;\n\n case \"custom\":\n if (wire.name === \"title_generated\") {\n const title = (wire.data.title as string) ?? \"\";\n if (this.conversationId && title) {\n const conv = this.conversations.find(\n (c) => c.id === this.conversationId,\n );\n if (conv) {\n conv.title = title;\n }\n this.storage\n .updateConversationTitle(this.conversationId, title)\n .catch(() => {});\n }\n }\n return;\n\n default:\n return;\n }\n }\n\n private async executeClientTools(\n toolCalls: ToolCallRequest[],\n ): Promise<ToolResult[]> {\n const results: ToolResult[] = [];\n for (const call of toolCalls) {\n const result = await this.toolRegistry.executeTool(call);\n results.push(result);\n }\n return results;\n }\n\n /**\n * Load conversation context (messages) without replaying events.\n * Used before reconnectToJob — SSE replay handles event replay.\n */\n async loadConversation(id: string): Promise<void> {\n this.conversationId = id;\n this.resetStreamingState();\n this.messages = await this.client\n .getMessages(id)\n .catch(() => this.storage.fetchMessages(id));\n }\n\n /**\n * Reconnect to a running job's SSE stream (e.g. after page reload).\n * Replays all events from the beginning and continues live.\n */\n async reconnectToJob(jobId: string): Promise<void> {\n if (this.isStreaming) return;\n\n this.isStreaming = true;\n this.currentJobId = jobId;\n this.lastSeq = -1;\n this.submittedToolCallIds.clear();\n this.resetStreamingState();\n this.abortController = new AbortController();\n\n try {\n await this.consumeEventStream(\n jobId,\n this.conversationId ?? \"\",\n \"\",\n false, // don't execute client tools on reconnect\n );\n } catch (err) {\n this.emit({\n type: \"error\",\n code: \"connection_error\",\n message: err instanceof Error ? err.message : String(err),\n blockPath: null,\n });\n } finally {\n this.isStreaming = false;\n this.abortController = null;\n }\n }\n\n /** Detach from the SSE stream without cancelling the job. */\n detach(): void {\n this.abortController?.abort();\n this.abortController = null;\n this.isStreaming = false;\n this.resetStreamingState();\n this.emit({ type: \"disconnected\" });\n }\n\n /** Stop the job and disconnect (explicit user action). */\n disconnect(): void {\n if (this.currentJobId) {\n this.client.cancelJob(this.currentJobId).catch(() => {});\n }\n this.detach();\n this.currentJobId = null;\n // Drop all protocol adapters — lifecycle tied to the session.\n this.protocols.clear();\n }\n\n async createNewConversation(): Promise<string> {\n const id = generateId();\n const conversation = await this.storage.createConversation(\n id,\n \"New Conversation\",\n );\n this.conversations.unshift(conversation);\n this.conversationId = id;\n this.messages = [];\n return id;\n }\n\n /**\n * Replay one completed turn's already-fetched events, synchronously.\n *\n * Fetching is the caller's job (``StreamManager.restore`` loads every turn's\n * events in parallel and the message list once), so this is pure replay: no\n * awaits, so the whole restore runs in a single synchronous pass and the\n * consumer batches it into one render.\n *\n * ``userMessageContent`` is the prompt that triggered this turn. It's emitted\n * as a synthetic ``user_message`` BEFORE any of the turn's events: user\n * prompts aren't persisted in ``job_events``, and some events precede\n * ``message_start`` in the stream (e.g. ``memory_recall`` from prompt prep),\n * so leading with the prompt keeps the turn in order.\n */\n replayTurn(\n id: string,\n events: ConversationEvent[],\n userMessageContent?: string,\n userMessageId?: string,\n isSteer = false,\n ): void {\n this.conversationId = id;\n this.resetStreamingState();\n\n if (userMessageContent) {\n this.emit({\n type: \"user_message\",\n content: userMessageContent,\n ...(userMessageId ? { id: userMessageId } : {}),\n ...(isSteer ? { steer: true } : {}),\n });\n }\n\n // The data payload is authoritative for `type` (matching how\n // replay.ts#mapSseToChat reads it), with the SSE event name as a fallback\n // for pre-v2 rows.\n for (const ev of events) {\n const type = (ev.data.type as string) || ev.event;\n if (!type || type === \"done\") continue;\n\n const wire = { ...ev.data, type } as unknown as WireEvent;\n try {\n this.replayWireEvent(wire, id);\n } catch {\n // Skip malformed replay events\n }\n }\n }\n\n /**\n * Load a conversation's messages and replay its persisted history.\n *\n * Convenience for plain-``ChatSession`` consumers (the documented\n * conversation-management API). ``StreamManager`` drives restore itself —\n * loading messages once and replaying each turn in parallel — and does NOT\n * call this; it's kept so direct-Session usage doesn't break.\n *\n * Without ``jobId`` it replays the whole conversation; with one, just that\n * job's events.\n */\n async switchConversation(id: string, jobId?: string): Promise<void> {\n const [messagesResult, eventsResult] = await Promise.allSettled([\n this.client.getMessages(id).catch(() => this.storage.fetchMessages(id)),\n this.client.getConversationEvents(id, jobId),\n ]);\n this.messages =\n messagesResult.status === \"fulfilled\" ? messagesResult.value : [];\n this.replayTurn(\n id,\n eventsResult.status === \"fulfilled\" ? eventsResult.value : [],\n );\n }\n\n /**\n * Append the next page of conversation history to ``conversations``.\n *\n * The list is ordered ``updated_at DESC`` and paged by offset, so a\n * conversation bumped to the top mid-scroll can surface again in a later\n * page; ids already held are dropped rather than duplicated. Returns only\n * the conversations actually appended, which may be empty even on a full\n * page. Rejects on network failure with ``hasMoreConversations`` still true,\n * so the caller can retry.\n *\n * KNOWN LIMITATION — offset paging is only stable while the prefix already\n * consumed stays put. The offset tracking here corrects for perturbations\n * THIS session causes (local unshifts, ``deleteConversation``), but not for\n * ones it never sees:\n *\n * - a conversation this session hasn't loaded yet is bumped to the top (a\n * headless routine or another device posting to it), pushing the whole\n * list down — it lands inside the consumed prefix, which no later offset\n * revisits;\n * - a conversation is deleted from another tab/device, shrinking the list so\n * the next offset lands one row too far in.\n *\n * Each perturbation costs at most one conversation off the sidebar, and only\n * until the next ``connect()`` — that re-seeds page 1 and resets the paging\n * state, so a reload or reconnect always recovers it. Nothing is lost\n * server-side. Both cases are pinned by tests in\n * ``tests/conversation-paging.test.ts``.\n *\n * Closing the gap properly needs a stable server cursor (keyset paging on\n * ``(updated_at, id)``) rather than a raw offset, which is a backend change —\n * tracking ids client-side cannot discover a row that moved into a region\n * already scanned.\n */\n async loadMoreConversations(): Promise<Conversation[]> {\n // Scroll handlers fire far faster than the request completes; without this\n // guard every frame would refetch the same offset.\n if (this.isLoadingConversations || !this.hasMoreConversations) return [];\n this.isLoadingConversations = true;\n const generation = this.conversationsGeneration;\n try {\n const page = await this.client.getConversations(\n CONVERSATION_PAGE_SIZE,\n this.serverConversationIds.size,\n );\n // A reconnect re-seeded the list while this was in flight, so this page\n // describes a paging state that no longer exists. Drop it untouched —\n // connect() has already set conversations/hasMore for the new state, and\n // the caller's next call pages from there.\n if (generation !== this.conversationsGeneration) return [];\n this.hasMoreConversations = page.length === CONVERSATION_PAGE_SIZE;\n const fresh = page.filter((c) => !this.serverConversationIds.has(c.id));\n for (const c of page) this.serverConversationIds.add(c.id);\n // A locally-created conversation can already sit in the array from its\n // unshift; count it toward the offset (the server did return it) but\n // don't append a second copy.\n const known = new Set(this.conversations.map((c) => c.id));\n const appended = fresh.filter((c) => !known.has(c.id));\n this.conversations.push(...appended);\n return appended;\n } finally {\n this.isLoadingConversations = false;\n }\n }\n\n async deleteConversation(id: string): Promise<void> {\n try {\n await this.client.deleteConversation(id);\n } catch {\n // May already be deleted on backend\n }\n await this.storage.deleteConversation(id);\n // Shrinks the paging offset iff the server had handed us this one — every\n // later page now shifts up by one, and without this the next page would\n // skip a conversation. A purely local conversation isn't in the set, so\n // deleting it correctly leaves the offset alone.\n //\n // ``Set.delete`` reports whether it was there, which is exactly the\n // \"was this server-sourced?\" test. When it was, any page ALREADY in flight\n // is now stale for the same reason as a reconnect: its offset was computed\n // pre-delete but the server evaluates the query post-delete, so it starts\n // one row late and would skip that row for good. The adjustment above fixes\n // future requests and cannot rescue an outstanding one — so invalidate it.\n if (this.serverConversationIds.delete(id)) {\n this.conversationsGeneration++;\n }\n this.conversations = this.conversations.filter((c) => c.id !== id);\n if (this.conversationId === id) {\n this.conversationId = null;\n this.messages = [];\n }\n }\n\n toggleClientTool(name: string): boolean {\n if (this.enabledClientTools.has(name)) {\n this.enabledClientTools.delete(name);\n return false;\n }\n this.enabledClientTools.add(name);\n return true;\n }\n}\n","/**\n * Deciding which prompt started which turn, when restoring a conversation.\n *\n * Restore has to pair each completed job with the user message that triggered\n * it. It used to do that by POSITION — the N-th completed job to the N-th user\n * message — which is only sound while every user message starts exactly one\n * job. A mid-run steer (`POST /conversations/{id}/steer`) is a user message\n * that starts none, so it shifted every later turn onto the wrong prompt and\n * pushed the tail off the end of the loop entirely.\n *\n * The backend now stamps a turn's prompt with the same id the job records\n * (`job.message_id`), so the pairing can be exact. Four kinds of row show up in\n * the two lists, and the id is what tells them apart:\n *\n * | row | has message id | job links to it |\n * |----------------------|----------------|-----------------|\n * | current turn prompt | yes | yes |\n * | mid-run steer | yes | no |\n * | legacy prompt | no | n/a |\n * | goal-continuation | hidden from the message list entirely |\n *\n * Classification keys off whether a job's `message_id` actually MATCHES a\n * message — never off a field being present. Both are always populated in real\n * data: `jobs.message_id` is NOT NULL, and the messages endpoint substitutes a\n * positional index string when a row carries no id of its own. A presence check\n * therefore reads every pre-link conversation as linked and strips every prompt\n * bubble from it.\n *\n * So: a job whose id matches a message is a turn; a message no job claims is a\n * steer; a job past the cutover whose id matches nothing visible is a\n * continuation, replaying with no bubble (what the positional version did by\n * accident when it ran off the end of the message list).\n *\n * Jobs are the spine, so continuations keep their place in the transcript.\n * Steers are interleaved at the point they appear in the message list, which\n * puts them after the turn that was running when they were sent.\n *\n * A conversation predating the link has no `message_id` on any job; there is\n * nothing to pair with, and no backfill is possible — inferring which historic\n * prompt started which job is exactly the ambiguity the link removes, so\n * guessing would bake the bug into the data. Those fall back to the positional\n * walk, unchanged, and keep the old behaviour including its flaw.\n */\n\nexport interface RestoreJob {\n job_id: string;\n /** The prompt that started this turn. Absent on pre-link rows and on\n * goal-continuation jobs, whose seed is hidden from the message list. */\n message_id?: string | null;\n}\n\nexport interface RestoreMessage {\n /** Present once the backend tags prompts; absent on pre-link rows. */\n id?: string;\n content: string;\n}\n\nexport type ReplayStep =\n /** Replay a job's events, optionally preceded by the prompt bubble. */\n | { kind: \"turn\"; jobId: string; content?: string; messageId?: string }\n /** A user message that started no turn — render the bubble alone. */\n | { kind: \"steer\"; content: string; messageId?: string };\n\n/**\n * Order the replay: which jobs to play, with which prompts, and where the\n * steers go between them. Pure, so the ordering rules are testable without a\n * session, a network, or a fake event stream.\n */\nexport function planRestore(args: {\n completedJobs: RestoreJob[];\n userMessages: RestoreMessage[];\n}): ReplayStep[] {\n const { completedJobs, userMessages } = args;\n\n const byId = new Map<string, number>();\n userMessages.forEach((m, i) => {\n if (m.id) byId.set(m.id, i);\n });\n /** The message index this job's prompt lives at, if it is visible. */\n const linkOf = (j: RestoreJob): number | undefined =>\n j.message_id ? byId.get(j.message_id) : undefined;\n\n const positional = (jobs: RestoreJob[], msgs: RestoreMessage[]): ReplayStep[] =>\n jobs.map((job, i) => ({\n kind: \"turn\" as const,\n jobId: job.job_id,\n content: msgs[i]?.content,\n messageId: msgs[i]?.id,\n }));\n\n // Detection keys off whether a job's id actually MATCHES a message — never\n // off a field merely being present. Both fields are always populated in real\n // data (`jobs.message_id` is NOT NULL, and the messages endpoint substitutes\n // a positional index string when a row has no id of its own), so a\n // presence check silently classifies every pre-link conversation as linked\n // and strips every prompt bubble from it.\n const firstLinked = completedJobs.findIndex((j) => linkOf(j) !== undefined);\n if (firstLinked === -1) {\n // Nothing matches: the whole conversation predates the tagging.\n return positional(completedJobs, userMessages);\n }\n\n // The tagging started at a point in time, so a conversation spanning it\n // splits cleanly: everything before the first linked turn has no usable\n // link and falls back to position; everything after is exact.\n const cutover = linkOf(completedJobs[firstLinked]!)!;\n const steps: ReplayStep[] = positional(\n completedJobs.slice(0, firstLinked),\n userMessages.slice(0, cutover),\n );\n\n let cursor = cutover;\n const isSteer = (m: RestoreMessage | undefined): m is RestoreMessage =>\n !!m?.id && !completedJobs.some((j) => j.message_id === m.id);\n\n /** Emit every steer sitting before `stopAt`, and advance past it. */\n const drainTo = (stopAt: number) => {\n while (cursor < stopAt) {\n const m = userMessages[cursor++];\n if (isSteer(m)) {\n steps.push({ kind: \"steer\", content: m.content, messageId: m.id });\n }\n }\n cursor = stopAt + 1;\n };\n\n for (const job of completedJobs.slice(firstLinked)) {\n const at = linkOf(job);\n if (at !== undefined) {\n drainTo(at);\n const prompt = userMessages[at]!;\n steps.push({\n kind: \"turn\",\n jobId: job.job_id,\n content: prompt.content,\n messageId: prompt.id,\n });\n continue;\n }\n // Past the cutover, a job whose id matches nothing visible is a goal\n // continuation: its seed is deliberately hidden from the message list, so\n // it replays its events with no bubble — which is what the positional\n // version did by accident when it ran off the end of the messages.\n steps.push({ kind: \"turn\", jobId: job.job_id });\n }\n\n // Steers sent during the final turn sit past every prompt.\n for (let i = cursor; i < userMessages.length; i++) {\n const m = userMessages[i];\n if (isSteer(m)) {\n steps.push({ kind: \"steer\", content: m.content, messageId: m.id });\n }\n }\n\n return steps;\n}\n","// =============================================================================\n// Astralform SDK v2 type definitions\n// =============================================================================\n//\n// Wire protocol types mirror the Pydantic models in\n// `backend/src/stream/protocol.py` 1:1. The SDK forwards typed events to\n// the consumer; block construction happens on the consumer side.\n//\n// Custom event payloads live in `custom-events.ts`.\n\nimport type { AgentIdentity, MemoryRecord, TodoItem } from \"./custom-events.js\";\n\n// Re-export so consumers can import from types.ts or custom-events.ts\nexport type { AgentIdentity, MemoryRecord, TodoItem };\n\n// --- Configuration ---\n//\n// The SDK supports two authentication modes. TypeScript narrows the union by\n// property presence, so consumers can write:\n//\n// new AstralformClient({ apiKey, userId }) // API-key mode\n// new AstralformClient({ accessToken, agentId }) // user-token mode\n//\n// Pick based on who the caller represents:\n//\n// * API-key mode — A customer's backend (B2B2C). Agent scoping is baked\n// into the key; the end user is named per request via `X-End-User-ID`.\n//\n// * User-token mode — An app acting on behalf of an Astralform account\n// holder (AstralChat, future 3rd-party integrations). The OIDC access\n// token is issued by the Astralform Identity Provider (our OAuth 2.1\n// Server at auth.astralform.ai); agent scoping comes from the\n// `X-Agent-ID` header.\n\ninterface AstralformBaseConfig {\n /** Override the default API origin. Defaults to https://api.astralform.ai. */\n baseURL?: string;\n /** Supply a custom fetch (SSR, testing, custom interceptors). */\n fetch?: typeof globalThis.fetch;\n /**\n * Abort a REST request — connect, headers, AND body read — after this many\n * milliseconds. Defaults to 30_000. Does not apply to `uploadFile` (large\n * files on slow uplinks) or to SSE streaming, which is long-lived by design\n * and carries its own `AbortSignal`.\n *\n * Note that unlike axios and friends, `0` does NOT mean \"no timeout\": any\n * non-positive or non-finite value falls back to the default. REST calls\n * cannot opt out of the deadline — an unbounded REST request is the bug\n * this exists to prevent, and it fails silently (a stalled body strands the\n * caller forever with nothing to react to).\n */\n timeoutMs?: number;\n}\n\nexport interface AstralformApiKeyConfig extends AstralformBaseConfig {\n /** Agent API key (`sk_live_...` or `sk_test_...`). */\n apiKey: string;\n /**\n * The customer's own identifier for the end user making this call.\n * Sent as the `X-End-User-ID` header. Required in API-key mode.\n */\n userId: string;\n}\n\nexport interface AstralformUserTokenConfig extends AstralformBaseConfig {\n /**\n * OIDC access token issued by the Astralform Identity Provider.\n * Expect this to be short-lived; use `client.updateAccessToken()` after\n * refreshing to hot-swap without re-instantiating the client.\n */\n accessToken: string;\n /**\n * Active agent context. Sent as the `X-Agent-ID` header. The backend\n * verifies the token's developer has access to this agent on every\n * request; switching agents is a local `updateAgentId()` call.\n *\n * Optional so a pre-pick client (right after login, before the user has\n * chosen a team/agent) can still call account-scoped discovery routes\n * like `listTeams()` / `listAgents(teamId)`. Agent-scoped calls\n * (conversations, messages, chat) will error out until one is set.\n */\n agentId?: string;\n /**\n * Optional end-user override — lets a developer acting under a user\n * token impersonate a downstream end-user identity for testing\n * purposes. When set, sent alongside `X-Agent-ID` as `X-End-User-ID`\n * so memory, rate limits, and conversations scope to the specified\n * end-user rather than the developer themselves.\n *\n * Use `client.updateEndUserId()` to rotate at runtime.\n */\n endUserId?: string;\n}\n\nexport type AstralformConfig =\n AstralformApiKeyConfig | AstralformUserTokenConfig;\n\n// --- Event type constants (SDK public) ---\n//\n// These are the high-level ChatEvent kinds the SDK emits to its\n// consumer. The raw wire events below are translated into these\n// kinds at the session boundary.\n\nexport const ChatEventType = {\n // Connection lifecycle (SDK-local, not wire)\n Connected: \"connected\",\n Disconnected: \"disconnected\",\n\n // Turn lifecycle\n MessageStart: \"message_start\",\n MessageStop: \"message_stop\",\n\n // Block lifecycle\n BlockStart: \"block_start\",\n BlockDelta: \"block_delta\",\n BlockStop: \"block_stop\",\n\n // Reliability\n Stall: \"stall\",\n Retry: \"retry\",\n Error: \"error\",\n Keepalive: \"keepalive\",\n\n // Conversation-level (typed custom events)\n UserMessage: \"user_message\",\n TitleGenerated: \"title_generated\",\n TodoUpdate: \"todo_update\",\n ContextUpdate: \"context_update\",\n SubagentStart: \"subagent_start\",\n SubagentStop: \"subagent_stop\",\n ContextWarning: \"context_warning\",\n MemoryRecall: \"memory_recall\",\n MemoryUpdate: \"memory_update\",\n DesktopStream: \"desktop_stream\",\n AttachmentStaged: \"attachment_staged\",\n WorkspaceReady: \"workspace_ready\",\n AssetCreated: \"asset_created\",\n ToolApprovalRequested: \"tool_approval_requested\",\n ToolApprovalGranted: \"tool_approval_granted\",\n ToolPermissionDenied: \"tool_permission_denied\",\n ToolHarnessWarning: \"tool_harness_warning\",\n UserUnavailable: \"user_unavailable\",\n PromptSuggestion: \"prompt_suggestion\",\n StateChanged: \"state_changed\",\n\n // Generic fallthrough for unknown custom events\n Custom: \"custom\",\n} as const;\n\nexport type ChatEventTypeValue =\n (typeof ChatEventType)[keyof typeof ChatEventType];\n\n// =============================================================================\n// Wire protocol (matches backend Pydantic models 1:1, snake_case)\n// =============================================================================\n\nexport type WireBlockKind = \"text\" | \"thinking\" | \"tool_use\";\n\nexport type WireBlockStatus =\n | \"streaming\"\n | \"awaiting_client_result\"\n | \"ok\"\n | \"error\"\n | \"denied\"\n | \"cancelled\";\n\nexport type WireStopReason =\n \"end_turn\" | \"tool_use\" | \"max_tokens\" | \"context_overflow\" | \"error\";\n\n// --- BlockDelta payloads (discriminated on `channel`) ---\n\nexport interface WireTextDelta {\n channel: \"text\";\n text: string;\n}\n\nexport interface WireThinkingDelta {\n channel: \"thinking\";\n text: string;\n}\n\nexport interface WireSignatureDelta {\n channel: \"signature\";\n signature: string;\n}\n\nexport interface WireInputDelta {\n channel: \"input\";\n partial_json: string;\n}\n\nexport interface WireInputArgDelta {\n channel: \"input_arg\";\n arg_name: string;\n text: string;\n}\n\nexport interface WireOutputDelta {\n channel: \"output\";\n stream: \"stdout\" | \"stderr\" | \"progress\";\n chunk: string;\n}\n\nexport interface WireStatusDelta {\n channel: \"status\";\n status:\n \"executing\" | \"awaiting_client_result\" | \"awaiting_approval\" | \"denied\";\n note?: string;\n}\n\nexport type WireBlockDeltaPayload =\n | WireTextDelta\n | WireThinkingDelta\n | WireSignatureDelta\n | WireInputDelta\n | WireInputArgDelta\n | WireOutputDelta\n | WireStatusDelta;\n\n// --- Top-level wire events ---\n\ninterface WireEnvelope {\n seq: number;\n ts: number;\n job_id: string;\n}\n\nexport interface WireMessageStart extends WireEnvelope {\n type: \"message_start\";\n turn_id: string;\n model: string;\n agent_name?: string | null;\n agent_display_name?: string | null;\n agent_avatar_url?: string | null;\n}\n\nexport interface WireBlockStart extends WireEnvelope {\n type: \"block_start\";\n turn_id: string;\n path: number[];\n parent_path?: number[] | null;\n kind: WireBlockKind;\n metadata: Record<string, unknown>;\n}\n\nexport interface WireBlockDelta extends WireEnvelope {\n type: \"block_delta\";\n turn_id: string;\n path: number[];\n delta: WireBlockDeltaPayload;\n}\n\nexport interface WireBlockStop extends WireEnvelope {\n type: \"block_stop\";\n turn_id: string;\n path: number[];\n status: WireBlockStatus;\n final: Record<string, unknown>;\n}\n\nexport interface WireMessageStop extends WireEnvelope {\n type: \"message_stop\";\n turn_id: string;\n stop_reason: WireStopReason;\n usage: {\n input_tokens?: number;\n output_tokens?: number;\n cached_tokens?: number;\n };\n ttfb_ms?: number | null;\n total_ms: number;\n stall_count: number;\n}\n\nexport interface WireStallWarning extends WireEnvelope {\n type: \"stall\";\n since_last_event_ms: number;\n stall_count: number;\n}\n\nexport interface WireRetryEvent extends WireEnvelope {\n type: \"retry\";\n attempt: number;\n reason: string;\n backoff_ms: number;\n strategy?: string | null;\n max_attempts?: number | null;\n context_recovery?: Record<string, unknown> | null;\n}\n\nexport interface WireErrorEvent extends WireEnvelope {\n type: \"error\";\n code: string;\n message: string;\n block_path?: number[] | null;\n // Rate limit fields (carried when code == \"rate_limit_exceeded\")\n retry_after?: number;\n retry_after_sec?: number;\n reset_at?: number | string;\n scope?: string;\n policy_id?: string;\n limit?: number;\n remaining?: number;\n request_id?: string;\n}\n\nexport interface WireKeepalive extends WireEnvelope {\n type: \"keepalive\";\n since_last_event_ms: number;\n}\n\nexport interface WireCustomEvent extends WireEnvelope {\n type: \"custom\";\n name: string;\n data: Record<string, unknown>;\n}\n\nexport type WireEvent =\n | WireMessageStart\n | WireBlockStart\n | WireBlockDelta\n | WireBlockStop\n | WireMessageStop\n | WireStallWarning\n | WireRetryEvent\n | WireErrorEvent\n | WireKeepalive\n | WireCustomEvent;\n\n// =============================================================================\n// ChatEvent — high-level SDK events (camelCase, emitted to consumers)\n// =============================================================================\n\nexport interface TurnUsage {\n inputTokens: number;\n outputTokens: number;\n cachedTokens: number;\n}\n\nexport type BlockDeltaPayload =\n | { channel: \"text\"; text: string }\n | { channel: \"thinking\"; text: string }\n | { channel: \"signature\"; signature: string }\n | { channel: \"input\"; partialJson: string }\n | { channel: \"inputArg\"; argName: string; text: string }\n | {\n channel: \"output\";\n stream: \"stdout\" | \"stderr\" | \"progress\";\n chunk: string;\n }\n | {\n channel: \"status\";\n status:\n \"executing\" | \"awaiting_client_result\" | \"awaiting_approval\" | \"denied\";\n note?: string;\n };\n\nexport type ChatEvent =\n // Connection lifecycle\n | { type: \"connected\" }\n | { type: \"disconnected\" }\n\n // Turn lifecycle\n | {\n type: \"message_start\";\n turnId: string;\n model: string;\n agentName?: string | null;\n agentDisplayName?: string | null;\n agentAvatarUrl?: string | null;\n }\n | {\n type: \"message_stop\";\n turnId: string;\n jobId: string;\n stopReason: WireStopReason;\n usage: TurnUsage;\n ttfbMs?: number | null;\n totalMs: number;\n stallCount: number;\n }\n\n // Block lifecycle\n | {\n type: \"block_start\";\n turnId: string;\n path: number[];\n parentPath?: number[] | null;\n kind: WireBlockKind;\n metadata: Record<string, unknown>;\n }\n | {\n type: \"block_delta\";\n turnId: string;\n path: number[];\n delta: BlockDeltaPayload;\n }\n | {\n type: \"block_stop\";\n turnId: string;\n path: number[];\n status: WireBlockStatus;\n final: Record<string, unknown>;\n }\n\n // Reliability\n | {\n type: \"stall\";\n sinceLastEventMs: number;\n stallCount: number;\n }\n | {\n type: \"retry\";\n attempt: number;\n reason: string;\n backoffMs: number;\n strategy?: string | null;\n maxAttempts?: number | null;\n contextRecovery?: Record<string, unknown> | null;\n }\n | {\n type: \"error\";\n code: string;\n message: string;\n blockPath: number[] | null;\n }\n | {\n type: \"keepalive\";\n sinceLastEventMs: number;\n }\n\n // Conversation-level — typed custom events\n | {\n type: \"user_message\";\n content: string;\n createdAt?: number;\n id?: string;\n /** This message was steered into a run already in flight — it started\n * no turn of its own. Consumers that badge a steer (a \"waiting to be\n * read\" notice) key off this; without it a replayed steer is\n * indistinguishable from an ordinary prompt. */\n steer?: boolean;\n }\n | { type: \"title_generated\"; title: string }\n | { type: \"todo_update\"; todos: TodoItem[] }\n | {\n type: \"context_update\";\n context: Record<string, unknown>;\n phase?: string | null;\n updatedAt?: number | null;\n }\n | {\n type: \"subagent_start\";\n agent: AgentIdentity;\n taskCallId?: string | null;\n }\n | {\n type: \"subagent_stop\";\n agent: AgentIdentity;\n taskCallId?: string | null;\n }\n | {\n type: \"context_warning\";\n /** Known values: \"info\" | \"warning\" | \"critical\". Typed as string for forward compat. */\n severity: string;\n utilizationPct: number;\n remainingTokens: number;\n windowTokens: number;\n inputTokens: number;\n message: string;\n }\n | { type: \"memory_recall\"; memories: MemoryRecord[] }\n | {\n type: \"memory_update\";\n /** Known values: \"created\" | \"updated\" | \"deleted\". */\n action: string;\n memoryId?: string | null;\n key?: string | null;\n namespace?: string | null;\n }\n | { type: \"desktop_stream\"; url: string; sandboxId?: string | null }\n | {\n type: \"attachment_staged\";\n attachmentId: string;\n filename: string;\n contentType?: string | null;\n sizeBytes?: number | null;\n }\n | {\n type: \"workspace_ready\";\n sandboxId: string;\n workspacePath?: string | null;\n }\n | {\n type: \"asset_created\";\n assetId: string;\n filename: string;\n url?: string | null;\n contentType?: string | null;\n }\n | {\n type: \"tool_approval_requested\";\n toolName: string;\n callId: string;\n arguments: Record<string, unknown>;\n riskLevel?: string | null;\n reason?: string | null;\n }\n | {\n type: \"tool_approval_granted\";\n toolName: string;\n callId: string;\n }\n | {\n type: \"tool_permission_denied\";\n toolName: string;\n callId: string;\n reason?: string | null;\n /** Known values: \"hook\" | \"rule\" | \"user\" | \"timeout\" | \"circuit_breaker\". */\n deniedBy?: string | null;\n }\n | {\n type: \"tool_harness_warning\";\n toolName: string;\n callId: string;\n message?: string | null;\n details?: Record<string, unknown> | null;\n }\n | {\n type: \"user_unavailable\";\n consecutiveTimeouts: number;\n toolName?: string | null;\n }\n | {\n type: \"prompt_suggestion\";\n suggestions: string[];\n }\n | {\n type: \"state_changed\";\n /**\n * Job lifecycle state. Known values from the backend today include\n * \"queued\" | \"running\" | \"waiting_for_tool\" | \"completed\" | \"failed\".\n * Typed as string for forward compat.\n */\n state: string;\n }\n\n // Generic fallthrough for unknown custom events\n | {\n type: \"custom\";\n name: string;\n data: Record<string, unknown>;\n };\n\n// =============================================================================\n// Domain models (unchanged from previous SDK version)\n// =============================================================================\n\nexport interface Conversation {\n id: string;\n title: string;\n messageCount: number;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface Message {\n id: string;\n conversationId: string;\n role: \"user\" | \"assistant\" | \"system\";\n content: string;\n parentId?: string;\n status: \"sending\" | \"streaming\" | \"complete\" | \"error\";\n createdAt: string;\n toolCalls?: ToolCallRequest[];\n}\n\nexport interface UIComponentsConfig {\n enabled: boolean;\n /** Protocol slug (e.g. \"a2ui\"). Null when disabled. */\n protocol: string | null;\n /** MIME type to match against embedded resources (e.g. \"application/json+a2ui\"). */\n mimeType: string | null;\n}\n\n/** One capability the agent either has or does not, right now. */\nexport interface AgentCapability {\n /** Stable key, e.g. `\"image\"`, `\"web\"`, `\"skills\"`. */\n key: string;\n enabled: boolean;\n}\n\nexport interface AgentStatus {\n isReady: boolean;\n llmConfigured: boolean;\n llmProvider?: string;\n llmModel?: string;\n message: string;\n uiComponents: UIComponentsConfig;\n /**\n * What the agent can do right now, so a client can offer a capability only\n * where it will actually work rather than failing on send.\n *\n * Empty when the backend does not report it (older servers) or when the agent\n * is not ready — treat an ABSENT key as \"not reported\", never as disabled.\n * Only capabilities with a real per-agent gate appear; always-on ones do not,\n * because a constant tells a client nothing.\n */\n capabilities: AgentCapability[];\n}\n\nexport interface AgentInfo {\n name: string;\n displayName: string;\n description: string;\n isOrchestrator: boolean;\n isEnabled: boolean;\n avatarUrl?: string;\n}\n\n// --- Team / Agent discovery (OIDC user-token surface) ---\n\nexport interface TeamSummary {\n id: string;\n name: string;\n slug: string;\n isDefault: boolean;\n /** Caller's role in this team (e.g. \"owner\", \"admin\", \"member\"). */\n role: string;\n}\n\n/**\n * A team-level agent (formerly \"project\") — the workspace a user opens to\n * chat. Distinct from `AgentInfo`, which describes the AI personas available\n * INSIDE an agent workspace (orchestrator + specialists).\n */\nexport interface TeamAgentSummary {\n id: string;\n name: string;\n teamId: string;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface SkillInfo {\n name: string;\n displayName: string;\n description: string;\n isEnabled: boolean;\n}\n\n// TodoItem is imported from custom-events.ts and re-exported above.\n\n// =============================================================================\n// Job and request types\n// =============================================================================\n\nexport interface JobCreateResponse {\n job_id: string;\n conversation_id: string;\n message_id: string;\n status: string;\n}\n\nexport interface JobStatus {\n jobId: string;\n status: string;\n createdAt: string | null;\n startedAt: string | null;\n completedAt: string | null;\n errorMessage: string | null;\n inputTokens: number;\n outputTokens: number;\n}\n\nexport interface ActiveJob {\n jobId: string | null;\n status: string;\n}\n\nexport interface JobSummary {\n jobId: string;\n status: string;\n replacesJobId: string | null;\n responseContent: Record<string, unknown> | null;\n metrics: Record<string, unknown> | null;\n createdAt: string | null;\n}\n\nexport interface FeedbackRequest {\n /** 1 for thumbs up, -1 for thumbs down. */\n rating: 1 | -1;\n comment?: string | null;\n}\n\nexport interface FeedbackResponse {\n id: string;\n jobId: string;\n rating: number;\n comment: string | null;\n createdAt: string;\n}\n\n/** Reasoning effort a caller may request for a turn (client-side model selection). */\nexport type ReasoningEffort = \"low\" | \"medium\" | \"high\";\n\n/**\n * The per-request model choice (client-side model selection). `provider` and\n * `model` are paired — send both or neither; when omitted, the server reuses the\n * conversation's last model or a connected-provider default.\n */\nexport interface ModelChoiceOptions {\n provider?: string;\n model?: string;\n reasoningEffort?: ReasoningEffort;\n temperature?: number;\n}\n\nexport interface ChatStreamRequest {\n message?: string;\n conversation_id?: string;\n mcp_manifest?: ToolDefinition[];\n enabled_mcp?: string[];\n continue_from_message?: string;\n resend_from?: string;\n upload_ids?: string[];\n agent_name?: string;\n plan_mode?: boolean;\n image_mode?: boolean;\n /**\n * Start a durable long-horizon goal for this run (goal mode). The backend mints\n * an agent_goal from this text and drives the run under a budget until the\n * objective is genuinely complete. A blank/omitted value runs a normal turn.\n */\n goal?: string;\n /**\n * Per-request model choice (client-side model selection), wire shape.\n * `provider` and `model` are paired; omit to reuse the thread's last model.\n */\n provider?: string;\n model?: string;\n reasoning_effort?: ReasoningEffort;\n temperature?: number;\n}\n\nexport interface ToolResultRequest {\n conversation_id: string;\n message_id: string;\n tool_results: ToolResult[];\n}\n\nexport interface ToolResult {\n call_id: string;\n tool_name: string;\n result: string;\n is_error: boolean;\n}\n\nexport type ToolApprovalDecision = \"allow\" | \"deny\";\nexport type ToolApprovalScope = \"once\" | \"conversation\" | \"always\";\n\nexport interface ToolApprovalRequest {\n job_id: string;\n call_id: string;\n decision: ToolApprovalDecision;\n scope: ToolApprovalScope;\n}\n\n/**\n * A remembered tool-permission grant belonging to the current end user.\n * Only `conversation`/`always` grants are ever stored (`once` is consumed at\n * approval time and never persisted).\n */\nexport interface ToolGrant {\n id: string;\n toolName: string;\n decision: ToolApprovalDecision;\n scope: Exclude<ToolApprovalScope, \"once\">;\n /** Set for `conversation`-scoped grants; `null` for `always`. */\n conversationId: string | null;\n createdAt: string;\n}\n\n/** A page of the current end user's own tool grants. */\nexport interface MyToolGrantsPage {\n grants: ToolGrant[];\n total: number;\n limit: number;\n offset: number;\n}\n\nexport interface ToolDefinition {\n name: string;\n description: string;\n parameters: Record<string, unknown>;\n}\n\nexport interface ToolCallRequest {\n callId: string;\n toolName: string;\n displayName?: string;\n description?: string;\n arguments: Record<string, unknown>;\n isClientTool: boolean;\n toolCategory?: string;\n iconUrl?: string;\n}\n\n// =============================================================================\n// SSE transport / raw parsing\n// =============================================================================\n\nexport interface StreamJobSSEOptions {\n url: string;\n headers: Record<string, string>;\n signal?: AbortSignal;\n fetchFn: typeof globalThis.fetch;\n}\n\nexport interface ChatStreamEvent {\n event: string;\n data: string;\n}\n\nexport interface ConversationEvent {\n seq: number;\n event: string;\n data: Record<string, unknown>;\n}\n\n// =============================================================================\n// Send / session options\n// =============================================================================\n\nexport interface SendOptions extends ModelChoiceOptions {\n conversationId?: string;\n enabledClientTools?: string[];\n uploadIds?: string[];\n agentName?: string;\n planMode?: boolean;\n /**\n * Attach the image-generation tool to this turn.\n *\n * Off by default and per-message, because generating costs the developer real\n * money at a third-party provider — the agent does not get to decide on its\n * own that a picture would be nice. The tool is not attached at all unless\n * this is set, so an agent with image generation configured still cannot\n * generate one on an ordinary turn.\n *\n * Gate the affordance on `AgentStatus.capabilities` (Astralform ≥ 0.61.0) —\n * an agent with no provider configured will simply have no tool to call.\n * The `image_mode` field itself is accepted from Astralform ≥ 0.59.0.\n */\n imageMode?: boolean;\n /**\n * Start a durable long-horizon goal for this run (goal mode). The text becomes\n * the goal's objective; the backend keeps the agent working under a budget until\n * it's complete. Omit for a normal turn.\n */\n goal?: string;\n}\n\n/**\n * A selectable model for one of the team's connected providers, from\n * `GET /v1/models`. Backs the client-side model picker.\n */\nexport interface ModelOption {\n provider: string;\n providerDisplay: string;\n model: string;\n thinking: boolean;\n tools: boolean;\n vision: boolean;\n thinkingMode: string;\n /** whether the model accepts a configurable reasoning effort (low/medium/high); false for always-on-thinking / think-tags models where effort is a no-op */\n supportsEffort: boolean;\n}\n\n// =============================================================================\n// Conversation assets (unchanged)\n// =============================================================================\n\nexport interface ConversationAsset {\n id: string;\n kind: \"upload\" | \"output\";\n originalName: string;\n mediaType: string;\n sizeBytes: number;\n workspacePath?: string;\n sourceMessageId?: string;\n agentName?: string;\n /**\n * Freshly-signed download/preview URL. Minted per response by the backend\n * (private `workspaces` bucket), so it reflects the current signature rather\n * than a link baked in when the asset was created. May be absent if signing\n * failed or the asset has no stored object.\n */\n url?: string;\n createdAt: string;\n}\n","/**\n * StreamManager — high-level conversation lifecycle coordinator.\n *\n * Sits on top of ChatSession and manages the state machine for\n * multi-conversation SSE streaming. Framework-agnostic: emits\n * typed events to registered handlers. Block construction is NOT\n * the SDK's concern — consumers build their own block tree from\n * the forwarded ``ChatEvent`` instances.\n *\n * import { ChatSession, StreamManager } from \"@astralform/js\";\n * const session = new ChatSession({ ... });\n * const manager = new StreamManager(session);\n * manager.on((event) => {\n * if (event.type === \"event\") {\n * // event.event is a typed ChatEvent — dispatch to your reducer\n * }\n * });\n * await manager.send(\"Hello\");\n */\nimport { planRestore } from \"./restore-plan\";\n\nimport type { ChatEvent, ModelChoiceOptions } from \"./types.js\";\nimport { ChatEventType } from \"./types.js\";\nimport type { ChatSession } from \"./session.js\";\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport type StreamState = \"idle\" | \"streaming\" | \"restoring\" | \"detached\";\n\nexport interface SendOptions extends ModelChoiceOptions {\n agentName?: string;\n uploadIds?: string[];\n planMode?: boolean;\n /**\n * Attach the image-generation tool to this turn. Per-message and off by\n * default — generating costs the developer real money at a third-party\n * provider. Gate the affordance on `AgentStatus.capabilities`.\n */\n imageMode?: boolean;\n /**\n * Start a durable long-horizon goal for this run (goal mode) — the text is the\n * goal objective the backend drives to completion. Omit for a normal turn.\n */\n goal?: string;\n}\n\nexport type StreamManagerEvent =\n | { type: \"stateChange\"; state: StreamState; conversationId: string | null }\n | { type: \"conversationChanged\"; conversationId: string | null }\n | {\n type: \"backgroundJobsChanged\";\n jobs: ReadonlyMap<string, string>;\n }\n | { type: \"event\"; conversationId: string | null; event: ChatEvent }\n | { type: \"versionsReady\"; conversationId: string; count: number };\n\ntype EventHandler = (event: StreamManagerEvent) => void;\n\n// =============================================================================\n// StreamManager\n// =============================================================================\n\nexport class StreamManager {\n private session: ChatSession;\n private _state: StreamState = \"idle\";\n private _activeConversationId: string | null = null;\n private _backgroundJobs = new Map<string, string>();\n private handlers: EventHandler[] = [];\n private unsub: (() => void) | null = null;\n\n constructor(session: ChatSession) {\n this.session = session;\n this.attach();\n }\n\n // ── Public state ──────────────────────────────────────────────\n\n get state(): StreamState {\n return this._state;\n }\n\n get activeConversationId(): string | null {\n return this._activeConversationId;\n }\n\n get backgroundJobs(): ReadonlyMap<string, string> {\n return this._backgroundJobs;\n }\n\n // ── Event subscription ────────────────────────────────────────\n\n on(handler: EventHandler): () => void {\n this.handlers.push(handler);\n return () => {\n this.handlers = this.handlers.filter((h) => h !== handler);\n };\n }\n\n private emit(event: StreamManagerEvent): void {\n for (const handler of this.handlers) {\n try {\n handler(event);\n } catch {\n // Don't let handler errors crash the manager\n }\n }\n }\n\n private setState(state: StreamState): void {\n this._state = state;\n this.emit({\n type: \"stateChange\",\n state,\n conversationId: this._activeConversationId,\n });\n }\n\n // ── Session event wiring ──────────────────────────────────────\n\n private attach(): void {\n this.unsub = this.session.on((event: ChatEvent) => {\n this.onSessionEvent(event);\n });\n }\n\n private onSessionEvent(event: ChatEvent): void {\n const convId = this.session.conversationId;\n\n // Forward every event to subscribers as a typed envelope\n this.emit({\n type: \"event\",\n conversationId: convId,\n event,\n });\n\n // Handle completion — message_stop is the terminal turn event.\n if (event.type === ChatEventType.MessageStop) {\n if (this._state === \"streaming\") {\n this.setState(\"idle\");\n }\n }\n }\n\n // ── Send ──────────────────────────────────────────────────────\n\n async send(content: string, options?: SendOptions): Promise<void> {\n if ((options?.provider == null) !== (options?.model == null)) {\n throw new Error(\n \"`provider` and `model` must be supplied together (client-side model selection).\",\n );\n }\n if (this._state === \"streaming\") return;\n\n // Auto-create conversation if none active\n if (!this._activeConversationId) {\n const id = await this.session.createNewConversation();\n this.setActiveConversation(id);\n }\n\n this.setState(\"streaming\");\n\n try {\n await this.session.send(content, {\n agentName: options?.agentName,\n uploadIds: options?.uploadIds,\n planMode: options?.planMode,\n imageMode: options?.imageMode,\n goal: options?.goal,\n provider: options?.provider,\n model: options?.model,\n reasoningEffort: options?.reasoningEffort,\n temperature: options?.temperature,\n });\n } catch {\n // AbortError from detach is expected\n }\n\n this.finalizeStream();\n }\n\n // ── Regenerate ────────────────────────────────────────────────\n\n async regenerate(): Promise<void> {\n if (this._state === \"streaming\") return;\n\n const userMsgs = this.session.messages.filter(\n (m: { role: string }) => m.role === \"user\",\n );\n const lastUserMsg = userMsgs[userMsgs.length - 1];\n if (!lastUserMsg) return;\n\n this.setState(\"streaming\");\n\n try {\n await this.session.resendFromCheckpoint(\n lastUserMsg.id,\n lastUserMsg.content,\n );\n } catch {\n // AbortError from detach is expected\n }\n\n this.finalizeStream();\n }\n\n // ── Switch conversation ───────────────────────────────────────\n\n /**\n * Switch the active conversation.\n *\n * ``opts.skipHistoryReplay`` is a fast path for consumers that CACHE a\n * restored conversation's rendered blocks: it moves the active pointer and\n * loads the message list (needed for send / regenerate context) but skips\n * the expensive event fetch + replay, and never enters the ``restoring``\n * state, so a consumer that clears its block view on ``restoring`` keeps\n * showing the cached history with no flash of a spinner.\n *\n * It still confirms there is no live job before skipping: the in-memory\n * background-job map is empty on a fresh instance (page reload) and blind to\n * jobs started in another tab/device, so the fast path always asks the server\n * (``getActiveJob``) and falls through to a full reconnect if one is running.\n * That one small request is the only cost it doesn't skip, so passing the\n * flag whenever you hold cached blocks is safe.\n */\n async switchTo(\n conversationId: string,\n opts?: { skipHistoryReplay?: boolean },\n ): Promise<void> {\n if (conversationId === this._activeConversationId) return;\n\n // Capture BEFORE the delete below: a background job THIS instance detached\n // must reconnect, so it can never take the cached fast path.\n const targetHadBackgroundJob = this._backgroundJobs.has(conversationId);\n\n // If streaming, detach (job keeps running in background)\n if (this._state === \"streaming\") {\n const oldConvId = this._activeConversationId;\n const jobId = this.session.currentJobId;\n if (oldConvId && jobId) {\n this._backgroundJobs.set(oldConvId, jobId);\n this.emit({\n type: \"backgroundJobsChanged\",\n jobs: this._backgroundJobs,\n });\n }\n this.session.detach();\n }\n\n // Clear background job for target (we're viewing it now)\n if (this._backgroundJobs.has(conversationId)) {\n this._backgroundJobs.delete(conversationId);\n this.emit({\n type: \"backgroundJobsChanged\",\n jobs: this._backgroundJobs,\n });\n }\n\n this.setActiveConversation(conversationId);\n\n if (opts?.skipHistoryReplay && !targetHadBackgroundJob) {\n // Cached fast path — but a job started before this instance existed (page\n // reload) or in another tab/device won't be in _backgroundJobs, so confirm\n // with the server that nothing is live before skipping the reconnect.\n let activeJobId: string | null = null;\n try {\n activeJobId = (await this.session.client.getActiveJob(conversationId))\n .jobId;\n } catch {\n // Network error — treat as no active job (best-effort, matches restore()).\n }\n if (!activeJobId) {\n // Consumer already holds the rendered blocks. Load the message list so\n // send/regenerate have their context, but skip the fetch + replay and\n // stay out of the ``restoring`` state.\n await this.session.loadConversation(conversationId);\n this.setState(\"idle\");\n return;\n }\n // A live job is running — fall through to a full restore(), which\n // reconnects to its stream.\n }\n\n await this.restore(conversationId);\n }\n\n // ── Create / delete conversation ──────────────────────────────\n\n async createConversation(): Promise<string> {\n const id = await this.session.createNewConversation();\n this.setActiveConversation(id);\n return id;\n }\n\n async deleteConversation(id: string): Promise<void> {\n await this.session.deleteConversation(id);\n this._backgroundJobs.delete(id);\n if (this._activeConversationId === id) {\n this._activeConversationId = null;\n this.emit({ type: \"conversationChanged\", conversationId: null });\n }\n }\n\n // ── Stop (explicit cancel) ────────────────────────────────────\n\n stop(): void {\n this.session.disconnect();\n this.setState(\"idle\");\n }\n\n // ── Cleanup ───────────────────────────────────────────────────\n\n destroy(): void {\n if (this.unsub) {\n this.unsub();\n this.unsub = null;\n }\n this.handlers = [];\n }\n\n // ── Internal: helpers ──────────────────────────────────────────\n\n private finalizeStream(): void {\n if (this._state === \"streaming\") {\n this.setState(\"idle\");\n }\n }\n\n // ── Internal: restore ─────────────────────────────────────────\n\n private async restore(conversationId: string): Promise<void> {\n this.setState(\"restoring\");\n\n // Check for active job\n let activeJobId: string | null = null;\n try {\n const res = await this.session.client.getActiveJob(conversationId);\n activeJobId = res.jobId;\n } catch {\n // Network error — assume no active job\n }\n\n if (activeJobId) {\n // Active job: load messages, reconnect to live SSE\n await this.session.loadConversation(conversationId);\n this.setState(\"streaming\");\n try {\n await this.session.reconnectToJob(activeJobId);\n } catch {\n // Stream ended or aborted\n }\n if (this._state === \"streaming\") {\n this.setState(\"idle\");\n }\n } else {\n // Completed: load the final messages once, then replay each turn.\n await this.session.loadConversation(conversationId);\n\n try {\n const jobs = await this.session.client.get<\n {\n job_id: string;\n status: string;\n message_id?: string | null;\n metrics?: Record<string, unknown>;\n }[]\n >(`/v1/conversations/${encodeURIComponent(conversationId)}/jobs`);\n const completedJobs = jobs.filter(\n (j: { status: string }) => j.status === \"completed\",\n );\n\n // User prompts aren't persisted in job_events — they live in the\n // messages table, so each turn has to be paired with the message that\n // started it. `job.message_id` is that link; planRestore also decides\n // where mid-run steers (user messages that start no job) and goal\n // continuations (jobs with no visible prompt) belong. See\n // restore-plan.ts for why pairing by index was wrong.\n const userMessages = this.session.messages.filter(\n (m) => m.role === \"user\",\n );\n const plan = planRestore({\n completedJobs: completedJobs.map((j) => ({\n job_id: j.job_id,\n message_id: j.message_id,\n })),\n userMessages: userMessages.map((m) => ({\n id: m.id,\n content: m.content,\n })),\n });\n\n // Fetch every turn's events up front, in PARALLEL. The backend strips\n // live-only deltas from this path, so each response is small; parallel\n // fetch collapses N serial round-trips into one wave. We still fetch\n // per job (not the whole conversation in one call) so superseded\n // regeneration versions stay available for version navigation — the\n // whole-conversation endpoint drops them.\n const eventLists = await Promise.all(\n completedJobs.map((job: { job_id: string }) =>\n this.session.client\n .getConversationEvents(conversationId, job.job_id)\n .catch(() => []),\n ),\n );\n\n const eventsByJobId = new Map(\n completedJobs.map((job, i) => [job.job_id, eventLists[i] ?? []]),\n );\n\n // Replay every step in one SYNCHRONOUS pass (no awaits between events\n // or turns), so the consumer batches the whole history into a single\n // render instead of re-typing it event by event. A steer replays as a\n // turn with no events: the bubble, and nothing after it.\n for (const step of plan) {\n if (step.kind === \"steer\") {\n this.session.replayTurn(\n conversationId,\n [],\n step.content,\n step.messageId,\n true,\n );\n continue;\n }\n this.session.replayTurn(\n conversationId,\n eventsByJobId.get(step.jobId) ?? [],\n step.content,\n step.messageId,\n );\n }\n\n if (completedJobs.length > 0) {\n this.emit({\n type: \"versionsReady\",\n conversationId,\n count: completedJobs.length,\n });\n }\n } catch {\n // Version chain loading failed — non-blocking\n }\n\n this.setState(\"idle\");\n }\n }\n\n // ── Internal: set active conversation ─────────────────────────\n\n private setActiveConversation(id: string): void {\n this._activeConversationId = id;\n this.emit({ type: \"conversationChanged\", conversationId: id });\n }\n}\n","// =============================================================================\n// Event replay — translate persisted wire events into ChatEvent sequences\n//\n// The backend persists wire events in the `job_events` table. When a\n// consumer needs to restore a conversation (page refresh, conversation\n// switch), it fetches these raw events and replays them through the same\n// ChatEvent pipeline used during live streaming.\n//\n// All wire → ChatEvent translation lives in `translate.ts`. This file just\n// adapts the persisted envelope shape (`{seq, event, data}`) and handles\n// user-message interleaving, which the backend doesn't record.\n// =============================================================================\n\nimport { translateWireEvent } from \"./translate.js\";\nimport type { ChatEvent, WireEvent } from \"./types.js\";\n\n/**\n * Raw SSE event shape returned by GET /v1/conversations/{id}/events.\n * Mirrors what JobEventWriter persists to the job_events table.\n */\nexport interface RawSseEvent {\n seq: number;\n event: string;\n data: Record<string, unknown>;\n /** Epoch ms when the event was persisted (from job_events.created_at). */\n created_at?: number;\n}\n\n/**\n * Build a ``WireEvent`` from the persisted envelope. The data payload is\n * authoritative (and always carries ``type`` in practice); the SSE event\n * name is a fallback for older rows that pre-date the v2 protocol.\n */\nfunction toWireEvent(raw: RawSseEvent): WireEvent | null {\n const type = (raw.data.type as string) || raw.event;\n if (!type || type === \"done\") return null;\n return { ...raw.data, type } as unknown as WireEvent;\n}\n\n/**\n * Map a raw SSE event (persisted in job_events) into the SDK ChatEvent\n * format. Returns an array because some rows (malformed / ``done`` sentinels)\n * map to zero events.\n */\nexport function mapSseToChat(raw: RawSseEvent): ChatEvent[] {\n const wire = toWireEvent(raw);\n if (!wire) return [];\n const event = translateWireEvent(wire);\n return event ? [event] : [];\n}\n\n/**\n * Replay persisted SSE events through the provided handler, interleaving\n * user messages from session.messages at the START of each turn (user\n * messages aren't persisted in job_events).\n *\n * The turn boundary is a change of ``job_id``, NOT ``message_start`` or\n * ``message_stop``. A completed job maps to exactly one user turn — but a\n * single job can contain several ``message_start``/``message_stop`` pairs (a\n * tool-use loop: LLM call → tool result → LLM call again), so neither event\n * reliably delimits turns. And within a job some events precede the first\n * ``message_start`` (e.g. ``memory_recall``, emitted during prompt prep), so\n * gating the user block on ``message_start`` would replay them above the\n * user's own message. Keying off ``job_id`` injects the prompt once per job,\n * before its first event — matching how the restore path\n * (``stream-manager.ts#restore`` → ``session.ts#replayTurn``) leads each turn\n * with its own prompt.\n */\nexport function replayEvents(\n sseEvents: RawSseEvent[],\n userMessages: { role: string; content: string }[],\n handleEvent: (event: ChatEvent) => void,\n addBlock: (block: { type: \"user\"; id: string; content: string }) => void,\n): void {\n const userMsgs = userMessages.filter((m) => m.role === \"user\");\n let userIdx = 0;\n let currentJobId: string | null = null;\n\n for (const raw of sseEvents) {\n const type = (raw.data.type as string) || raw.event;\n if (!type || type === \"done\") continue;\n\n // A new job_id starts a new user turn — inject its prompt before the\n // job's first event. Events without a job_id stay within the current\n // turn (never a boundary), so a stray untagged event can't misfire.\n const jobId = (raw.data.job_id as string | undefined) ?? null;\n if (jobId !== null && jobId !== currentJobId) {\n currentJobId = jobId;\n if (userIdx < userMsgs.length) {\n addBlock({\n type: \"user\",\n id: `replay_user_${userIdx}`,\n content: userMsgs[userIdx]!.content,\n });\n userIdx++;\n }\n }\n\n for (const ce of mapSseToChat(raw)) {\n handleEvent(ce);\n }\n }\n}\n","// =============================================================================\n// Embedded Resource detection — protocol-agnostic\n// =============================================================================\n//\n// The backend can emit rich UI surfaces (A2UI, and future protocols) by\n// wrapping a tool result in an MCP-style embedded resource:\n//\n// {\n// \"_embedded_resource\": true,\n// \"mime_type\": \"application/json+a2ui\",\n// \"uri\": \"a2ui://surface/<id>\",\n// \"payload\": { ...protocol-specific... }\n// }\n//\n// The SDK stays protocol-agnostic: it exposes a detector/parser so\n// frontends can route the payload to a registered renderer for the\n// matching MIME type. The SDK itself never imports a renderer.\n// =============================================================================\n\n/**\n * Parsed shape of an MCP-style embedded resource, as emitted by the\n * backend's UI component tools (``render_surface``, ``update_surface``).\n */\nexport interface EmbeddedResource {\n /** IANA-style MIME type, e.g. \"application/json+a2ui\". */\n mimeType: string;\n /** Opaque URI (e.g. \"a2ui://surface/my-form\"). */\n uri: string;\n /** Protocol-specific payload — shape depends on ``mimeType``. */\n payload: Record<string, unknown>;\n}\n\n/**\n * Detect whether a value is an embedded resource wrapper. The check is\n * purposely loose — any object with ``_embedded_resource: true`` is\n * accepted, which matches the MCP convention and keeps the SDK\n * forward-compatible with future protocols.\n */\nexport function isEmbeddedResource(value: unknown): value is {\n _embedded_resource: true;\n mime_type?: string;\n uri?: string;\n payload?: Record<string, unknown>;\n} {\n return (\n typeof value === \"object\" &&\n value !== null &&\n (value as { _embedded_resource?: unknown })._embedded_resource === true\n );\n}\n\n/**\n * Parse an embedded resource from arbitrary tool output. Returns\n * ``null`` when the value isn't an embedded resource or is malformed.\n *\n * Accepts either an object (the preferred wire format) or a JSON\n * string containing one (defense in depth — some transport layers\n * historically serialized tool results before sending).\n */\nexport function parseEmbeddedResource(value: unknown): EmbeddedResource | null {\n let candidate: unknown = value;\n if (typeof candidate === \"string\") {\n // Only try JSON.parse if it plausibly looks like a JSON object\n // starting with `{`. Avoids pathological input.\n const trimmed = candidate.trim();\n if (!trimmed.startsWith(\"{\")) return null;\n try {\n candidate = JSON.parse(trimmed);\n } catch {\n return null;\n }\n }\n if (!isEmbeddedResource(candidate)) return null;\n const mimeType = candidate.mime_type;\n const uri = candidate.uri;\n const payload = candidate.payload;\n if (typeof mimeType !== \"string\" || !mimeType) return null;\n if (typeof uri !== \"string\" || !uri) return null;\n if (!payload || typeof payload !== \"object\") return null;\n return {\n mimeType,\n uri,\n payload: payload as Record<string, unknown>,\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/utils.ts","../src/rate-limit.ts","../src/streaming.ts","../src/client.ts","../src/storage.ts","../src/tools.ts","../src/protocol-registry.ts","../src/translate.ts","../src/session.ts","../src/restore-plan.ts","../src/types.ts","../src/stream-manager.ts","../src/replay.ts","../src/embedded-resource.ts"],"sourcesContent":["// Core classes\nexport { AstralformClient } from \"./client.js\";\nexport { ChatSession, CONVERSATION_PAGE_SIZE } from \"./session.js\";\nexport { ToolRegistry, type ToolHandler } from \"./tools.js\";\nexport { InMemoryStorage, type ChatStorage } from \"./storage.js\";\n\n// Errors\nexport {\n AstralformError,\n AuthenticationError,\n RateLimitError,\n LLMNotConfiguredError,\n ServerError,\n ConnectionError,\n StreamAbortedError,\n} from \"./errors.js\";\nexport type { RateLimitErrorDetails } from \"./errors.js\";\n\n// Utilities\nexport { generateId } from \"./utils.js\";\n\n// Streaming\nexport { streamJobSSE } from \"./streaming.js\";\n\n// Stream manager\nexport { StreamManager } from \"./stream-manager.js\";\nexport type {\n StreamState,\n SendOptions,\n StreamManagerEvent,\n} from \"./stream-manager.js\";\n\n// Delta translator (shared between session and replay)\nexport { translateDelta } from \"./translate.js\";\n\n// Event replay (for restoring conversations from persisted events)\nexport { mapSseToChat, replayEvents } from \"./replay.js\";\nexport type { RawSseEvent } from \"./replay.js\";\n\n// Embedded resource detection (protocol-agnostic UI surface helper)\nexport {\n isEmbeddedResource,\n parseEmbeddedResource,\n} from \"./embedded-resource.js\";\nexport type { EmbeddedResource } from \"./embedded-resource.js\";\n\n// Protocol adapter registry — consumers register framework-specific\n// renderers for embedded resource MIME types.\nexport { ProtocolRegistry } from \"./protocol-registry.js\";\nexport type { ProtocolAdapter } from \"./protocol-registry.js\";\n\n// Event type constants\nexport { ChatEventType } from \"./types.js\";\nexport type { ChatEventTypeValue } from \"./types.js\";\n\n// High-level ChatEvent (SDK → consumer)\nexport type { ChatEvent, BlockDeltaPayload, TurnUsage } from \"./types.js\";\n\n// Custom event payload catalog\nexport type {\n AgentIdentity,\n TaskStatus,\n TodoItem,\n TodoUpdatePayload,\n PlanUpdatePayload,\n NoteUpdatePayload,\n TitleGeneratedPayload,\n SubagentStartPayload,\n SubagentStopPayload,\n ContextWarningPayload,\n ContextUpdatePayload,\n MemoryRecord,\n MemoryRecallPayload,\n MemoryUpdatePayload,\n DesktopStreamPayload,\n AttachmentStagedPayload,\n WorkspaceReadyPayload,\n AssetCreatedPayload,\n ToolApprovalRequestedPayload,\n ToolApprovalGrantedPayload,\n ToolPermissionDeniedPayload,\n ToolHarnessWarningPayload,\n UserUnavailablePayload,\n PromptSuggestionPayload,\n} from \"./custom-events.js\";\n\n// Wire protocol types (for consumers that want to parse the raw SSE\n// data themselves or write their own transport adapter)\nexport type {\n WireEvent,\n WireMessageStart,\n WireMessageStop,\n WireBlockStart,\n WireBlockDelta,\n WireBlockStop,\n WireStallWarning,\n WireRetryEvent,\n WireErrorEvent,\n WireKeepalive,\n WireCustomEvent,\n WireBlockKind,\n WireBlockStatus,\n WireStopReason,\n WireBlockDeltaPayload,\n WireTextDelta,\n WireThinkingDelta,\n WireSignatureDelta,\n WireInputDelta,\n WireInputArgDelta,\n WireOutputDelta,\n WireStatusDelta,\n} from \"./types.js\";\n\n// Config + domain models\nexport type {\n AstralformConfig,\n AstralformApiKeyConfig,\n AstralformUserTokenConfig,\n Conversation,\n Message,\n AgentStatus,\n AgentCapability,\n UIComponentsConfig,\n AgentInfo,\n SkillInfo,\n ModelOption,\n ModelChoiceOptions,\n TeamSummary,\n TeamAgentSummary,\n} from \"./types.js\";\n\n// Request / response types\nexport type {\n ChatStreamRequest,\n ReasoningEffort,\n ToolResultRequest,\n ToolResult,\n ToolDefinition,\n ToolApprovalRequest,\n ToolApprovalDecision,\n ToolApprovalScope,\n ToolGrant,\n MyToolGrantsPage,\n ToolCallRequest,\n JobCreateResponse,\n JobStatus,\n JobSummary,\n ActiveJob,\n FeedbackRequest,\n FeedbackResponse,\n ConversationAsset,\n StreamJobSSEOptions,\n ChatStreamEvent,\n ConversationEvent,\n} from \"./types.js\";\n","export interface RateLimitErrorDetails {\n retryAfterSec?: number;\n resetAt?: number;\n scope?: string;\n policyId?: string;\n limit?: number;\n remaining?: number;\n requestId?: string;\n}\n\nexport class AstralformError extends Error {\n constructor(\n message: string,\n public code: string,\n ) {\n super(message);\n this.name = \"AstralformError\";\n }\n}\n\nexport class AuthenticationError extends AstralformError {\n constructor(message = \"Invalid or missing API key\") {\n super(message, \"authentication_error\");\n this.name = \"AuthenticationError\";\n }\n}\n\nexport class RateLimitError extends AstralformError {\n declare readonly retryAfterSec?: number;\n declare readonly resetAt?: number;\n declare readonly scope?: string;\n declare readonly policyId?: string;\n declare readonly limit?: number;\n declare readonly remaining?: number;\n declare readonly requestId?: string;\n\n constructor(\n message = \"Rate limit exceeded\",\n details: RateLimitErrorDetails = {},\n ) {\n super(message, \"rate_limit_error\");\n this.name = \"RateLimitError\";\n Object.assign(this, details);\n }\n}\n\nexport class LLMNotConfiguredError extends AstralformError {\n constructor(message = \"LLM provider not configured for this agent\") {\n super(message, \"llm_not_configured\");\n this.name = \"LLMNotConfiguredError\";\n }\n}\n\nexport class ServerError extends AstralformError {\n constructor(message = \"Internal server error\") {\n super(message, \"server_error\");\n this.name = \"ServerError\";\n }\n}\n\nexport class ConnectionError extends AstralformError {\n constructor(message = \"Failed to connect to server\") {\n super(message, \"connection_error\");\n this.name = \"ConnectionError\";\n }\n}\n\nexport class StreamAbortedError extends AstralformError {\n constructor(message = \"Stream was aborted\") {\n super(message, \"stream_aborted\");\n this.name = \"StreamAbortedError\";\n }\n}\n","export function sanitizeErrorText(text: string): string {\n return text.slice(0, 500).replace(/Bearer\\s+\\S+/gi, \"Bearer [REDACTED]\");\n}\n\nexport function generateId(): string {\n if (typeof crypto !== \"undefined\" && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n // Fallback for environments without crypto.randomUUID\n return \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === \"x\" ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n}\n","import { RateLimitError, type RateLimitErrorDetails } from \"./errors.js\";\nimport { sanitizeErrorText } from \"./utils.js\";\n\nconst DEFAULT_MESSAGE = \"Rate limit exceeded\";\n\nfunction parseNumber(value: unknown): number | undefined {\n if (typeof value === \"number\" && Number.isFinite(value)) {\n return value;\n }\n if (typeof value === \"string\") {\n const trimmed = value.trim();\n if (!trimmed) {\n return undefined;\n }\n const parsed = Number(trimmed);\n if (Number.isFinite(parsed)) {\n return parsed;\n }\n }\n return undefined;\n}\n\nfunction parseString(value: unknown): string | undefined {\n if (typeof value !== \"string\") {\n return undefined;\n }\n const trimmed = value.trim();\n return trimmed ? trimmed : undefined;\n}\n\nfunction parseJsonObject(rawText: string): Record<string, unknown> | undefined {\n if (!rawText) {\n return undefined;\n }\n try {\n const parsed: unknown = JSON.parse(rawText);\n if (parsed && typeof parsed === \"object\") {\n return parsed as Record<string, unknown>;\n }\n } catch {\n // Ignore parse errors and fall back to text-only handling.\n }\n return undefined;\n}\n\nfunction parseRetryAfterHeader(value: string | null): number | undefined {\n if (!value) {\n return undefined;\n }\n\n const numeric = parseNumber(value);\n if (numeric !== undefined) {\n return Math.max(0, Math.ceil(numeric));\n }\n\n const asDate = Date.parse(value);\n if (Number.isFinite(asDate)) {\n const diffMs = asDate - Date.now();\n return Math.max(0, Math.ceil(diffMs / 1000));\n }\n\n return undefined;\n}\n\nfunction parseResetTimestamp(value: unknown): number | undefined {\n const numeric = parseNumber(value);\n if (numeric !== undefined) {\n if (numeric > 1_000_000_000_000) {\n return Math.floor(numeric);\n }\n return Math.floor(numeric * 1000);\n }\n\n const asString = parseString(value);\n if (!asString) {\n return undefined;\n }\n\n const asDate = Date.parse(asString);\n if (Number.isFinite(asDate)) {\n return asDate;\n }\n return undefined;\n}\n\nfunction pickFirst<T>(\n payload: Record<string, unknown>,\n keys: string[],\n parser: (value: unknown) => T | undefined,\n): T | undefined {\n for (const key of keys) {\n const parsed = parser(payload[key]);\n if (parsed !== undefined) {\n return parsed;\n }\n }\n return undefined;\n}\n\nfunction buildRateLimitDetails(\n payload: Record<string, unknown>,\n headers?: Headers,\n): RateLimitErrorDetails {\n const headerRetryAfter = headers\n ? parseRetryAfterHeader(headers.get(\"retry-after\"))\n : undefined;\n const bodyRetryAfter = pickFirst(\n payload,\n [\"retry_after\", \"retryAfter\", \"retry_after_sec\", \"retryAfterSec\"],\n parseNumber,\n );\n\n const retryAfterSec = bodyRetryAfter ?? headerRetryAfter;\n\n const headerReset = headers\n ? parseResetTimestamp(\n headers.get(\"x-ratelimit-reset\") ?? headers.get(\"x-ratelimit-reset-at\"),\n )\n : undefined;\n const bodyReset = parseResetTimestamp(\n payload.reset_at ?? payload.resetAt ?? payload.reset,\n );\n\n const resetAt =\n bodyReset ??\n headerReset ??\n (retryAfterSec !== undefined\n ? Date.now() + retryAfterSec * 1000\n : undefined);\n\n const limit =\n pickFirst(payload, [\"limit\", \"rate_limit\", \"max\"], parseNumber) ??\n (headers ? parseNumber(headers.get(\"x-ratelimit-limit\")) : undefined);\n\n const remaining =\n pickFirst(payload, [\"remaining\", \"rate_limit_remaining\"], parseNumber) ??\n (headers ? parseNumber(headers.get(\"x-ratelimit-remaining\")) : undefined);\n\n const scope =\n pickFirst(payload, [\"scope\", \"limit_scope\"], parseString) ??\n (headers ? parseString(headers.get(\"x-ratelimit-scope\")) : undefined);\n\n const policyId =\n pickFirst(payload, [\"policy_id\", \"policyId\", \"policy\"], parseString) ??\n (headers\n ? parseString(\n headers.get(\"x-ratelimit-policy\") ??\n headers.get(\"x-ratelimit-policy-id\"),\n )\n : undefined);\n\n const requestId =\n pickFirst(payload, [\"request_id\", \"requestId\"], parseString) ??\n (headers\n ? parseString(\n headers.get(\"x-request-id\") ?? headers.get(\"x-correlation-id\"),\n )\n : undefined);\n\n return {\n retryAfterSec,\n resetAt,\n scope,\n policyId,\n limit,\n remaining,\n requestId,\n };\n}\n\nexport function createRateLimitErrorFromPayload(\n payload: Record<string, unknown>,\n fallbackMessage = DEFAULT_MESSAGE,\n): RateLimitError {\n const message = pickFirst(\n payload,\n [\"message\", \"error_description\"],\n parseString,\n );\n return new RateLimitError(\n message ?? fallbackMessage,\n buildRateLimitDetails(payload),\n );\n}\n\nexport function createRateLimitErrorFromHttp(\n response: Response,\n rawText: string,\n): RateLimitError {\n const payload = parseJsonObject(rawText) ?? {};\n const details = buildRateLimitDetails(payload, response.headers);\n\n const sanitizedText = sanitizeErrorText(rawText);\n const message =\n pickFirst(payload, [\"message\", \"error_description\"], parseString) ??\n (sanitizedText || DEFAULT_MESSAGE);\n\n return new RateLimitError(message, details);\n}\n","import {\n AuthenticationError,\n ConnectionError,\n ServerError,\n StreamAbortedError,\n} from \"./errors.js\";\nimport { createRateLimitErrorFromHttp } from \"./rate-limit.js\";\nimport type { ChatStreamEvent, StreamJobSSEOptions } from \"./types.js\";\nimport { sanitizeErrorText } from \"./utils.js\";\n\n/**\n * GET-based SSE stream for job events.\n */\nexport async function* streamJobSSE(\n options: StreamJobSSEOptions,\n): AsyncGenerator<ChatStreamEvent> {\n const { url, headers, signal, fetchFn } = options;\n\n let response: Response;\n try {\n response = await fetchFn(url, {\n method: \"GET\",\n headers,\n signal,\n });\n } catch (err) {\n if (err instanceof DOMException && err.name === \"AbortError\") {\n throw new StreamAbortedError();\n }\n throw new ConnectionError(\n err instanceof Error ? err.message : \"Failed to connect\",\n );\n }\n\n if (!response.ok) {\n const rawText = await response.text().catch(() => \"\");\n const text = rawText ? sanitizeErrorText(rawText) : \"\";\n switch (response.status) {\n case 401:\n throw new AuthenticationError();\n case 429:\n throw createRateLimitErrorFromHttp(response, rawText);\n default:\n throw new ServerError(text || `HTTP ${response.status}`);\n }\n }\n\n if (!response.body) {\n throw new ConnectionError(\"Response body is null\");\n }\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n let currentEvent = \"\";\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split(\"\\n\");\n buffer = lines.pop() ?? \"\";\n\n for (const line of lines) {\n if (line.startsWith(\"event: \")) {\n currentEvent = line.slice(7).trim();\n } else if (line.startsWith(\"data: \")) {\n const data = line.slice(6);\n if (data === \"[DONE]\") {\n return;\n }\n yield { event: currentEvent || \"message\", data };\n }\n if (line === \"\") {\n currentEvent = \"\";\n }\n }\n }\n } catch (err) {\n if (err instanceof DOMException && err.name === \"AbortError\") {\n throw new StreamAbortedError();\n }\n throw err;\n } finally {\n reader.releaseLock();\n }\n}\n","import { AuthenticationError, ConnectionError, ServerError } from \"./errors.js\";\nimport { createRateLimitErrorFromHttp } from \"./rate-limit.js\";\nimport { streamJobSSE } from \"./streaming.js\";\nimport { sanitizeErrorText } from \"./utils.js\";\nimport type {\n ActiveJob,\n AgentInfo,\n AstralformApiKeyConfig,\n AstralformConfig,\n ChatStreamEvent,\n ChatStreamRequest,\n ConversationAsset,\n ConversationEvent,\n Conversation,\n FeedbackRequest,\n FeedbackResponse,\n JobCreateResponse,\n JobStatus,\n JobSummary,\n Message,\n ModelOption,\n MyToolGrantsPage,\n AgentStatus,\n TeamAgentSummary,\n SkillInfo,\n TeamSummary,\n ToolApprovalRequest,\n ToolResultRequest,\n} from \"./types.js\";\n\nconst DEFAULT_BASE_URL = \"https://api.astralform.ai\";\nconst DEFAULT_TIMEOUT_MS = 30_000;\n\nfunction validateBaseURL(url: string): string {\n const cleaned = url.replace(/\\/+$/, \"\");\n try {\n const parsed = new URL(cleaned);\n if (parsed.protocol !== \"https:\" && parsed.protocol !== \"http:\") {\n throw new Error(\n `Invalid baseURL protocol \"${parsed.protocol}\" - only http: and https: are allowed`,\n );\n }\n return parsed.origin + parsed.pathname.replace(/\\/+$/, \"\");\n } catch (err) {\n if (err instanceof Error && err.message.includes(\"Invalid baseURL\")) {\n throw err;\n }\n throw new Error(`Invalid baseURL: \"${cleaned}\" is not a valid URL`);\n }\n}\n\nfunction isApiKeyConfig(\n config: AstralformConfig,\n): config is AstralformApiKeyConfig {\n return \"apiKey\" in config;\n}\n\n/** Discriminates between the two auth modes the client supports. */\ntype AuthMode =\n | { kind: \"api_key\"; apiKey: string; userId: string }\n | {\n kind: \"user_token\";\n accessToken: string;\n /** Null until the user picks an agent; account-scoped calls still work. */\n agentId: string | null;\n /** Optional end-user override. When present, sent as X-End-User-ID. */\n endUserId: string | null;\n };\n\nexport class AstralformClient {\n private readonly baseURL: string;\n private readonly fetchFn: typeof globalThis.fetch;\n private readonly timeoutMs: number;\n /**\n * Auth state is mutable so callers can rotate access tokens or switch\n * agent context without re-instantiating the client. API-key mode is\n * effectively immutable in practice but uses the same shape for uniformity.\n */\n private auth: AuthMode;\n\n constructor(config: AstralformConfig) {\n if (isApiKeyConfig(config)) {\n if (!config.apiKey || typeof config.apiKey !== \"string\") {\n throw new Error(\"apiKey is required and must be a non-empty string\");\n }\n if (!config.userId || typeof config.userId !== \"string\") {\n throw new Error(\"userId is required in API-key mode\");\n }\n this.auth = {\n kind: \"api_key\",\n apiKey: config.apiKey,\n userId: config.userId,\n };\n } else {\n if (!config.accessToken || typeof config.accessToken !== \"string\") {\n throw new Error(\n \"accessToken is required and must be a non-empty string in user-token mode\",\n );\n }\n // agentId is optional — a pre-pick client (right after login) can\n // still hit account-scoped routes like listTeams(). Agent-scoped\n // routes will 4xx until one is set via updateAgentId().\n const agentId =\n typeof config.agentId === \"string\" && config.agentId.length > 0\n ? config.agentId\n : null;\n this.auth = {\n kind: \"user_token\",\n accessToken: config.accessToken,\n agentId,\n endUserId:\n typeof config.endUserId === \"string\" && config.endUserId.length > 0\n ? config.endUserId\n : null,\n };\n }\n\n this.baseURL = validateBaseURL(config.baseURL ?? DEFAULT_BASE_URL);\n this.fetchFn = config.fetch ?? globalThis.fetch.bind(globalThis);\n this.timeoutMs =\n typeof config.timeoutMs === \"number\" &&\n Number.isFinite(config.timeoutMs) &&\n config.timeoutMs > 0\n ? config.timeoutMs\n : DEFAULT_TIMEOUT_MS;\n }\n\n /**\n * Replace the current OIDC access token without reconstructing the client.\n * Use after refreshing via the host app's own token manager.\n * Throws if the client was created in API-key mode.\n */\n updateAccessToken(accessToken: string): void {\n if (this.auth.kind !== \"user_token\") {\n throw new Error(\"updateAccessToken is only valid in user-token mode\");\n }\n if (!accessToken || typeof accessToken !== \"string\") {\n throw new Error(\"accessToken must be a non-empty string\");\n }\n this.auth = { ...this.auth, accessToken };\n }\n\n /**\n * Swap the active agent for a user-token client. The backend verifies the\n * current developer has access to the new agent; a 403 comes back if not.\n */\n updateAgentId(agentId: string): void {\n if (this.auth.kind !== \"user_token\") {\n throw new Error(\"updateAgentId is only valid in user-token mode\");\n }\n if (!agentId || typeof agentId !== \"string\") {\n throw new Error(\"agentId must be a non-empty string\");\n }\n this.auth = { ...this.auth, agentId };\n }\n\n /**\n * Set (or clear) the end-user override for user-token mode.\n *\n * Pass `null` or an empty string to clear — subsequent requests go\n * back to scoping against the developer's own identity. Throws if\n * called in API-key mode, where end-user context already travels via\n * the constructor's `userId` field.\n */\n updateEndUserId(endUserId: string | null): void {\n if (this.auth.kind !== \"user_token\") {\n throw new Error(\"updateEndUserId is only valid in user-token mode\");\n }\n const normalized =\n typeof endUserId === \"string\" && endUserId.length > 0 ? endUserId : null;\n this.auth = { ...this.auth, endUserId: normalized };\n }\n\n /** Current end-user override in user-token mode, or `null` if unset. */\n get endUserId(): string | null {\n return this.auth.kind === \"user_token\" ? this.auth.endUserId : null;\n }\n\n /**\n * Active agent for user-token mode, or `null` if pre-pick (client\n * was constructed without one). For API-key mode the agent is baked\n * into the key, so this getter returns `null` there too — use\n * `authMode` to disambiguate.\n */\n get agentId(): string | null {\n return this.auth.kind === \"user_token\" ? this.auth.agentId : null;\n }\n\n /** Which auth mode this client was constructed with. */\n get authMode(): \"api_key\" | \"user_token\" {\n return this.auth.kind;\n }\n\n /**\n * Authorization + identity headers for the current auth mode, without\n * `Content-Type`. Suitable for JSON requests (paired with the JSON header\n * in the `headers` getter) and for multipart uploads where the browser\n * must set its own `Content-Type` boundary.\n */\n private get authHeaders(): Record<string, string> {\n if (this.auth.kind === \"api_key\") {\n return {\n Authorization: `Bearer ${this.auth.apiKey}`,\n \"X-End-User-ID\": this.auth.userId,\n };\n }\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.auth.accessToken}`,\n };\n if (this.auth.agentId) {\n headers[\"X-Agent-ID\"] = this.auth.agentId;\n }\n if (this.auth.endUserId) {\n headers[\"X-End-User-ID\"] = this.auth.endUserId;\n }\n return headers;\n }\n\n private get headers(): Record<string, string> {\n return {\n ...this.authHeaders,\n \"Content-Type\": \"application/json\",\n };\n }\n\n /**\n * Run one REST exchange under a single deadline covering connect, headers,\n * AND the body read. The body read is the part that matters: `json()` used\n * to sit outside every guard, so a response whose headers arrived but whose\n * body stalled hung forever — silently stranding callers that await it\n * (a stalled `getMessages` used to leave `StreamManager.restore()` parked\n * before it ever fetched the events it renders from).\n *\n * The controller is created per request and is deliberately NOT the\n * session's — that one means \"the user cancelled this turn\" and is null\n * outside a live turn. Aborting frees the socket; the race guarantees a\n * rejection even when an injected `fetch` ignores the signal.\n */\n private async withDeadline<T>(\n run: (signal: AbortSignal) => Promise<T>,\n ): Promise<T> {\n const controller = new AbortController();\n const timedOut = () =>\n new ConnectionError(`Request timed out after ${this.timeoutMs}ms`);\n let timer: ReturnType<typeof setTimeout> | undefined;\n const deadline = new Promise<never>((_resolve, reject) => {\n timer = setTimeout(() => {\n controller.abort();\n reject(timedOut());\n }, this.timeoutMs);\n });\n try {\n // Promise.race subscribes to both inputs, so the loser's later\n // rejection (the abort landing after the deadline won) counts as\n // handled and can't surface as an unhandled rejection.\n return await Promise.race([run(controller.signal), deadline]);\n } catch (err) {\n // A signal-honouring fetch rejects with AbortError before the race\n // settles — normalize it to the same timeout error either way.\n if (controller.signal.aborted) throw timedOut();\n throw err;\n } finally {\n clearTimeout(timer);\n }\n }\n\n private async request(\n method: string,\n path: string,\n body?: unknown,\n ): Promise<Response> {\n return this.withDeadline((signal) => this.send(method, path, body, signal));\n }\n\n /** Fetch + status handling. Always called inside `withDeadline`. */\n private async send(\n method: string,\n path: string,\n body: unknown,\n signal: AbortSignal,\n ): Promise<Response> {\n const response = await this.fetchFn(`${this.baseURL}${path}`, {\n method,\n headers: this.headers,\n body: body !== undefined ? JSON.stringify(body) : undefined,\n signal,\n }).catch((err) => {\n throw new ConnectionError(\n err instanceof Error ? err.message : \"Failed to connect\",\n );\n });\n await this.handleError(response);\n return response;\n }\n\n // DO NOT refactor these back into `request()` + `.json()`. Parsing the body\n // INSIDE the raced callback is the entire fix: `json()` outside the deadline\n // is the original bug (headers arrive, body stalls, caller hangs forever).\n // `request()` survives for `del()`, which never reads the body.\n\n async get<T>(path: string): Promise<T> {\n return this.withDeadline(async (signal) => {\n const response = await this.send(\"GET\", path, undefined, signal);\n return (await response.json()) as T;\n });\n }\n\n async post<T>(path: string, body: unknown): Promise<T> {\n return this.withDeadline(async (signal) => {\n const response = await this.send(\"POST\", path, body, signal);\n return (await response.json()) as T;\n });\n }\n\n async patch<T>(path: string, body: unknown): Promise<T> {\n return this.withDeadline(async (signal) => {\n const response = await this.send(\"PATCH\", path, body, signal);\n return (await response.json()) as T;\n });\n }\n\n private async del(path: string): Promise<void> {\n await this.request(\"DELETE\", path);\n }\n\n private async handleError(response: Response): Promise<void> {\n if (response.ok) return;\n const text = await response.text().catch(() => \"\");\n switch (response.status) {\n case 401:\n throw new AuthenticationError();\n case 429:\n throw createRateLimitErrorFromHttp(response, text);\n default: {\n const safeText = text ? sanitizeErrorText(text) : \"\";\n throw new ServerError(safeText || `HTTP ${response.status}`);\n }\n }\n }\n\n // --- REST Methods ---\n\n async getHealth(): Promise<{\n status: string;\n version: string;\n ollama_connected: boolean;\n }> {\n return this.get(\"/v1/health\");\n }\n\n // Agent readiness check, scoped to the client's active agent via X-Agent-ID.\n async getAgentStatus(): Promise<AgentStatus> {\n const raw = await this.get<{\n is_ready: boolean;\n llm_configured: boolean;\n llm_provider?: string;\n llm_model?: string;\n message: string;\n ui_components?: {\n enabled?: boolean;\n protocol?: string | null;\n mime_type?: string | null;\n };\n capabilities?: Array<{ key?: string; enabled?: boolean }>;\n }>(\"/v1/agent/status\");\n const ui = raw.ui_components ?? {};\n return {\n isReady: raw.is_ready,\n llmConfigured: raw.llm_configured,\n llmProvider: raw.llm_provider,\n llmModel: raw.llm_model,\n message: raw.message,\n // Defaults to [] rather than undefined so a caller can iterate without a\n // guard, and so a server that predates the field behaves as \"reports\n // nothing\" instead of throwing. Entries missing a key are dropped: a\n // capability with no name cannot be matched against and would render as\n // an unlabelled row.\n capabilities: (raw.capabilities ?? []).flatMap((c) =>\n typeof c?.key === \"string\" && c.key.length > 0\n ? [{ key: c.key, enabled: Boolean(c.enabled) }]\n : [],\n ),\n uiComponents: {\n enabled: Boolean(ui.enabled),\n protocol: ui.protocol ?? null,\n mimeType: ui.mime_type ?? null,\n },\n };\n }\n\n async getConversations(limit = 50, offset = 0): Promise<Conversation[]> {\n const safeLimit = Math.max(1, Math.min(200, Math.floor(Number(limit))));\n const safeOffset = Math.max(0, Math.floor(Number(offset)));\n const raw = await this.get<\n {\n id: string;\n title: string;\n message_count: number;\n created_at: string;\n updated_at: string;\n }[]\n >(`/v1/conversations?limit=${safeLimit}&offset=${safeOffset}`);\n return raw.map((c) => ({\n id: c.id,\n title: c.title,\n messageCount: c.message_count,\n createdAt: c.created_at,\n updatedAt: c.updated_at,\n }));\n }\n\n async getMessages(conversationId: string): Promise<Message[]> {\n const raw = await this.get<\n {\n id: string;\n conversation_id: string;\n role: \"user\" | \"assistant\" | \"system\";\n content: string;\n parent_id?: string;\n created_at: string;\n }[]\n >(`/v1/conversations/${encodeURIComponent(conversationId)}/messages`);\n return raw.map((m) => ({\n id: m.id,\n conversationId: m.conversation_id,\n role: m.role,\n content: m.content,\n parentId: m.parent_id,\n status: \"complete\" as const,\n createdAt: m.created_at,\n }));\n }\n\n /**\n * Replace the title the server generated from the conversation's first turn.\n *\n * The server does NOT bump `updated_at` for a rename — conversations list\n * newest-updated first, and relabelling one is not activity — so the\n * timestamp coming back is the original. Callers should merge the response\n * rather than stamp their own, or they reintroduce the reordering the\n * server deliberately avoids.\n */\n async renameConversation(id: string, title: string): Promise<Conversation> {\n const c = await this.patch<{\n id: string;\n title: string;\n message_count: number;\n created_at: string;\n updated_at: string;\n }>(`/v1/conversations/${encodeURIComponent(id)}`, { title });\n return {\n id: c.id,\n title: c.title,\n messageCount: c.message_count,\n createdAt: c.created_at,\n updatedAt: c.updated_at,\n };\n }\n\n async deleteConversation(id: string): Promise<void> {\n await this.del(`/v1/conversations/${encodeURIComponent(id)}`);\n }\n\n /**\n * List the AI personas (sub-agents) available INSIDE the client's active\n * agent workspace — orchestrator + specialists, addressed per message via\n * `ChatStreamRequest.agent_name`. Not to be confused with `listAgents()`,\n * which enumerates the team-level agents a signed-in user can open.\n */\n async getAgents(): Promise<AgentInfo[]> {\n const raw = await this.get<\n {\n name: string;\n display_name: string;\n description: string;\n is_orchestrator: boolean;\n is_enabled: boolean;\n avatar_url?: string;\n }[]\n >(\"/v1/agents\");\n return raw.map((a) => ({\n name: a.name,\n displayName: a.display_name,\n description: a.description,\n isOrchestrator: a.is_orchestrator,\n isEnabled: a.is_enabled,\n avatarUrl: a.avatar_url,\n }));\n }\n\n /**\n * List the models the caller may pick this turn — expanded from the curated\n * catalog of the providers the team has connected (client-side model\n * selection). Backs the composer's model picker. Scoped to the active agent\n * via X-Agent-ID, same as {@link getAgentStatus}.\n */\n async getModels(): Promise<ModelOption[]> {\n const raw = await this.get<\n {\n provider: string;\n provider_display: string;\n model: string;\n thinking: boolean;\n tools: boolean;\n vision: boolean;\n thinking_mode: string;\n supports_effort?: boolean;\n }[]\n >(\"/v1/models\");\n return raw.map((m) => ({\n provider: m.provider,\n providerDisplay: m.provider_display,\n model: m.model,\n thinking: m.thinking,\n tools: m.tools,\n vision: m.vision,\n thinkingMode: m.thinking_mode,\n // Coerce so the non-optional `supportsEffort: boolean` stays honest even\n // against an older backend that omits `supports_effort` (→ false = safe:\n // the effort control is hidden). Unlike the always-present siblings above,\n // this field can be absent, so it's the one that needs coercion.\n supportsEffort: Boolean(m.supports_effort),\n }));\n }\n\n async getSkills(): Promise<SkillInfo[]> {\n const raw = await this.get<\n {\n name: string;\n display_name: string;\n description: string;\n is_enabled: boolean;\n }[]\n >(\"/v1/skills\");\n return raw.map((s) => ({\n name: s.name,\n displayName: s.display_name,\n description: s.description,\n isEnabled: s.is_enabled,\n }));\n }\n\n async getConversationEvents(\n conversationId: string,\n jobId?: string,\n ): Promise<ConversationEvent[]> {\n let url = `/v1/conversations/${encodeURIComponent(conversationId)}/events`;\n if (jobId) url += `?job_id=${encodeURIComponent(jobId)}`;\n return this.get(url);\n }\n\n async submitToolResult(request: ToolResultRequest): Promise<void> {\n await this.post(\"/v1/tool-result\", request);\n }\n\n async submitToolApproval(request: ToolApprovalRequest): Promise<void> {\n await this.post(\"/v1/tool-approval\", request);\n }\n\n // --- End-user tool-permission self-service ---\n\n /**\n * List the current end user's own remembered tool-permission grants.\n * Only `conversation`/`always` grants exist (`once` is never persisted).\n * Paginated via `limit` (default 100, max 200) / `offset`; `total` lets you\n * page through all of them.\n */\n async getMyToolPermissions(options?: {\n limit?: number;\n offset?: number;\n }): Promise<MyToolGrantsPage> {\n const params = new URLSearchParams();\n if (options?.limit != null) {\n const safeLimit = Math.max(\n 1,\n Math.min(200, Math.floor(Number(options.limit))),\n );\n params.set(\"limit\", String(safeLimit));\n }\n if (options?.offset != null) {\n const safeOffset = Math.max(0, Math.floor(Number(options.offset)));\n params.set(\"offset\", String(safeOffset));\n }\n const qs = params.toString();\n const raw = await this.get<{\n grants: {\n id: string;\n tool_name: string;\n decision: \"allow\" | \"deny\";\n scope: \"conversation\" | \"always\";\n conversation_id: string | null;\n created_at: string;\n }[];\n total: number;\n limit: number;\n offset: number;\n }>(`/v1/me/tool-permissions${qs ? `?${qs}` : \"\"}`);\n return {\n grants: raw.grants.map((g) => ({\n id: g.id,\n toolName: g.tool_name,\n decision: g.decision,\n scope: g.scope,\n conversationId: g.conversation_id,\n createdAt: g.created_at,\n })),\n total: raw.total,\n limit: raw.limit,\n offset: raw.offset,\n };\n }\n\n /**\n * Revoke one of the current end user's remembered grants by id. The agent\n * will ask again the next time that tool is used.\n */\n async revokeToolPermission(id: string): Promise<void> {\n await this.del(`/v1/me/tool-permissions/${encodeURIComponent(id)}`);\n }\n\n // --- Conversation Assets ---\n\n private mapAsset(raw: Record<string, unknown>): ConversationAsset {\n return {\n id: raw.id as string,\n kind: raw.kind as \"upload\" | \"output\",\n originalName: raw.original_name as string,\n mediaType: raw.media_type as string,\n sizeBytes: raw.size_bytes as number,\n workspacePath: raw.workspace_path as string | undefined,\n sourceMessageId: raw.source_message_id as string | undefined,\n agentName: raw.agent_name as string | undefined,\n // The API serializes an unsigned asset as `url: null`; normalize to\n // undefined so it matches the `url?: string` type and consumers that\n // check `!== undefined` never receive a null.\n url: (raw.url as string | null) ?? undefined,\n createdAt: raw.created_at as string,\n };\n }\n\n async uploadFile(\n conversationId: string,\n file: Blob,\n filename?: string,\n ): Promise<ConversationAsset> {\n const formData = new FormData();\n formData.append(\"file\", file, filename);\n\n const response = await this.fetchFn(\n `${this.baseURL}/v1/conversations/${encodeURIComponent(conversationId)}/uploads`,\n {\n method: \"POST\",\n headers: this.authHeaders,\n body: formData,\n },\n ).catch((err) => {\n throw new ConnectionError(\n err instanceof Error ? err.message : \"Failed to connect\",\n );\n });\n await this.handleError(response);\n const raw = await response.json();\n return this.mapAsset(raw as Record<string, unknown>);\n }\n\n async listUploads(conversationId: string): Promise<ConversationAsset[]> {\n const raw = await this.get<Record<string, unknown>[]>(\n `/v1/conversations/${encodeURIComponent(conversationId)}/uploads`,\n );\n return raw.map((r) => this.mapAsset(r));\n }\n\n async listOutputs(conversationId: string): Promise<ConversationAsset[]> {\n const raw = await this.get<Record<string, unknown>[]>(\n `/v1/conversations/${encodeURIComponent(conversationId)}/outputs`,\n );\n return raw.map((r) => this.mapAsset(r));\n }\n\n // --- Account-scoped discovery (user-token mode) ---\n //\n // Lets a signed-in user pick which team/agent they want to act on.\n // Backend gates these on OIDC user context (no X-Agent-ID required) —\n // sending them in API-key mode yields 401.\n\n async listTeams(): Promise<TeamSummary[]> {\n const raw = await this.get<\n Array<{\n id: string;\n name: string;\n slug: string;\n is_default: boolean;\n role: string;\n }>\n >(\"/v1/teams\");\n return raw.map((t) => ({\n id: t.id,\n name: t.name,\n slug: t.slug,\n isDefault: t.is_default,\n role: t.role,\n }));\n }\n\n /**\n * List the team-level agents (formerly \"projects\") the signed-in user can\n * open — the pickable workspaces under a team. Not to be confused with\n * `getAgents()`, which lists the AI personas inside the active agent.\n */\n async listAgents(teamId: string): Promise<TeamAgentSummary[]> {\n const raw = await this.get<\n Array<{\n id: string;\n name: string;\n team_id: string;\n created_at: string;\n updated_at: string;\n }>\n >(`/v1/teams/${encodeURIComponent(teamId)}/agents`);\n return raw.map((a) => ({\n id: a.id,\n name: a.name,\n teamId: a.team_id,\n createdAt: a.created_at,\n updatedAt: a.updated_at,\n }));\n }\n\n // --- Jobs API ---\n\n async createJob(request: ChatStreamRequest): Promise<JobCreateResponse> {\n return this.post<JobCreateResponse>(\"/v1/jobs\", request);\n }\n\n async *streamJobEvents(\n jobId: string,\n afterSeq = -1,\n signal?: AbortSignal,\n ): AsyncGenerator<ChatStreamEvent> {\n const url = `${this.baseURL}/v1/jobs/${encodeURIComponent(jobId)}/events?after=${afterSeq}`;\n yield* streamJobSSE({\n url,\n headers: this.headers,\n signal,\n fetchFn: this.fetchFn,\n });\n }\n\n async cancelJob(jobId: string): Promise<void> {\n await this.post(`/v1/jobs/${encodeURIComponent(jobId)}/cancel`, {});\n }\n\n async getJob(jobId: string): Promise<JobStatus> {\n const raw = await this.get<{\n job_id: string;\n status: string;\n created_at?: string | null;\n started_at?: string | null;\n completed_at?: string | null;\n error_message?: string | null;\n input_tokens?: number;\n output_tokens?: number;\n }>(`/v1/jobs/${encodeURIComponent(jobId)}`);\n return {\n jobId: raw.job_id,\n status: raw.status,\n createdAt: raw.created_at ?? null,\n startedAt: raw.started_at ?? null,\n completedAt: raw.completed_at ?? null,\n errorMessage: raw.error_message ?? null,\n inputTokens: raw.input_tokens ?? 0,\n outputTokens: raw.output_tokens ?? 0,\n };\n }\n\n async submitFeedback(\n jobId: string,\n request: FeedbackRequest,\n ): Promise<FeedbackResponse> {\n const body: { rating: 1 | -1; comment?: string } = {\n rating: request.rating,\n };\n if (request.comment != null) body.comment = request.comment;\n const raw = await this.post<{\n id: string;\n job_id: string;\n rating: number;\n comment: string | null;\n created_at: string;\n }>(`/v1/jobs/${encodeURIComponent(jobId)}/feedback`, body);\n return {\n id: raw.id,\n jobId: raw.job_id,\n rating: raw.rating,\n comment: raw.comment,\n createdAt: raw.created_at,\n };\n }\n\n async getActiveJob(conversationId: string): Promise<ActiveJob> {\n const raw = await this.get<{\n job_id: string | null;\n status: string;\n }>(`/v1/conversations/${encodeURIComponent(conversationId)}/active-job`);\n return {\n jobId: raw.job_id ?? null,\n status: raw.status,\n };\n }\n\n async listJobs(conversationId: string): Promise<JobSummary[]> {\n const raw = await this.get<\n {\n job_id: string;\n status: string;\n replaces_job_id?: string | null;\n response_content?: Record<string, unknown> | null;\n metrics?: Record<string, unknown> | null;\n created_at?: string | null;\n }[]\n >(`/v1/conversations/${encodeURIComponent(conversationId)}/jobs`);\n return raw.map((j) => ({\n jobId: j.job_id,\n status: j.status,\n replacesJobId: j.replaces_job_id ?? null,\n responseContent: j.response_content ?? null,\n metrics: j.metrics ?? null,\n createdAt: j.created_at ?? null,\n }));\n }\n}\n","import type { Conversation, Message } from \"./types.js\";\n\nexport interface ChatStorage {\n fetchConversations(): Promise<Conversation[]>;\n fetchConversation(id: string): Promise<Conversation | null>;\n createConversation(id: string, title: string): Promise<Conversation>;\n updateConversationTitle(id: string, title: string): Promise<void>;\n deleteConversation(id: string): Promise<void>;\n fetchMessages(conversationId: string): Promise<Message[]>;\n addMessage(message: Message, conversationId: string): Promise<void>;\n updateMessageStatus(id: string, status: Message[\"status\"]): Promise<void>;\n deleteMessage(id: string): Promise<void>;\n}\n\nexport class InMemoryStorage implements ChatStorage {\n private conversations = new Map<string, Conversation>();\n private messages = new Map<string, Message[]>();\n\n async fetchConversations(): Promise<Conversation[]> {\n return Array.from(this.conversations.values()).sort(\n (a, b) =>\n new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime(),\n );\n }\n\n async fetchConversation(id: string): Promise<Conversation | null> {\n return this.conversations.get(id) ?? null;\n }\n\n async createConversation(id: string, title: string): Promise<Conversation> {\n const now = new Date().toISOString();\n const conversation: Conversation = {\n id,\n title,\n messageCount: 0,\n createdAt: now,\n updatedAt: now,\n };\n this.conversations.set(id, conversation);\n this.messages.set(id, []);\n return conversation;\n }\n\n async updateConversationTitle(id: string, title: string): Promise<void> {\n const conv = this.conversations.get(id);\n if (conv) {\n conv.title = title;\n conv.updatedAt = new Date().toISOString();\n }\n }\n\n async deleteConversation(id: string): Promise<void> {\n this.conversations.delete(id);\n this.messages.delete(id);\n }\n\n async fetchMessages(conversationId: string): Promise<Message[]> {\n return this.messages.get(conversationId) ?? [];\n }\n\n async addMessage(message: Message, conversationId: string): Promise<void> {\n const msgs = this.messages.get(conversationId) ?? [];\n msgs.push(message);\n this.messages.set(conversationId, msgs);\n\n const conv = this.conversations.get(conversationId);\n if (conv) {\n conv.messageCount = msgs.length;\n conv.updatedAt = new Date().toISOString();\n }\n }\n\n async updateMessageStatus(\n id: string,\n status: Message[\"status\"],\n ): Promise<void> {\n for (const msgs of this.messages.values()) {\n const msg = msgs.find((m) => m.id === id);\n if (msg) {\n msg.status = status;\n return;\n }\n }\n }\n\n async deleteMessage(id: string): Promise<void> {\n for (const [convId, msgs] of this.messages.entries()) {\n const idx = msgs.findIndex((m) => m.id === id);\n if (idx !== -1) {\n msgs.splice(idx, 1);\n const conv = this.conversations.get(convId);\n if (conv) {\n conv.messageCount = msgs.length;\n }\n return;\n }\n }\n }\n}\n","import type { ToolCallRequest, ToolDefinition, ToolResult } from \"./types.js\";\n\nexport type ToolHandler = (args: Record<string, unknown>) => Promise<string>;\n\ninterface RegisteredTool {\n name: string;\n description: string;\n inputSchema: Record<string, unknown>;\n handler: ToolHandler;\n}\n\n/** Validates tool name: alphanumeric, hyphens, underscores, dots only */\nconst TOOL_NAME_PATTERN = /^[a-zA-Z0-9_.\\-]+$/;\n\n/** Strips prototype-polluting keys from an object */\nfunction sanitizeArgs(args: Record<string, unknown>): Record<string, unknown> {\n const clean: Record<string, unknown> = Object.create(null);\n for (const key of Object.keys(args)) {\n if (key === \"__proto__\" || key === \"constructor\" || key === \"prototype\") {\n continue;\n }\n clean[key] = args[key];\n }\n return clean;\n}\n\nexport class ToolRegistry {\n private tools = new Map<string, RegisteredTool>();\n\n registerTool(\n name: string,\n description: string,\n inputSchema: Record<string, unknown>,\n handler: ToolHandler,\n ): void {\n if (!name || !TOOL_NAME_PATTERN.test(name)) {\n throw new Error(\n `Invalid tool name \"${name}\" - must match ${TOOL_NAME_PATTERN}`,\n );\n }\n if (name.length > 256) {\n throw new Error(\"Tool name must be 256 characters or fewer\");\n }\n this.tools.set(name, { name, description, inputSchema, handler });\n }\n\n unregisterTool(name: string): boolean {\n return this.tools.delete(name);\n }\n\n hasTool(name: string): boolean {\n return this.tools.has(name);\n }\n\n async executeTool(request: ToolCallRequest): Promise<ToolResult> {\n const tool = this.tools.get(request.toolName);\n if (!tool) {\n return {\n call_id: request.callId,\n tool_name: request.toolName,\n result: `Tool \"${request.toolName}\" not found`,\n is_error: true,\n };\n }\n\n try {\n const result = await tool.handler(sanitizeArgs(request.arguments));\n return {\n call_id: request.callId,\n tool_name: request.toolName,\n result,\n is_error: false,\n };\n } catch (err) {\n return {\n call_id: request.callId,\n tool_name: request.toolName,\n result: err instanceof Error ? err.message : String(err),\n is_error: true,\n };\n }\n }\n\n getManifest(): ToolDefinition[] {\n return Array.from(this.tools.values()).map((t) => ({\n name: t.name,\n description: t.description,\n parameters: t.inputSchema,\n }));\n }\n\n getToolNames(): string[] {\n return Array.from(this.tools.keys());\n }\n\n clear(): void {\n this.tools.clear();\n }\n}\n","// =============================================================================\n// Protocol adapter registry — pluggable UI protocol layer\n// =============================================================================\n//\n// The SDK is framework-agnostic: it never renders. But when the backend\n// emits an MCP-style embedded resource (A2UI today, other protocols\n// tomorrow) the consumer needs to hand the payload to a renderer. This\n// file defines the contract between those two sides:\n//\n// • `ProtocolAdapter` — an opaque, framework-specific handle that\n// claims a MIME type. The SDK stores it; the consumer narrows the\n// type when reading it back out.\n//\n// • `ProtocolRegistry` — a MIME-keyed map of adapters. One lives on\n// each `ChatSession` so its lifecycle matches the session: clearing\n// on disconnect, swapping on reconnect with a different agent.\n//\n// The consumer decides _whether_ to register an adapter — typically by\n// consulting `session.agentStatus.uiComponents` after `connect()`.\n// The SDK never auto-registers anything; it just stores what it's told.\n// =============================================================================\n\n/**\n * Minimal adapter contract. Frontends extend this with a `render()`\n * method (or equivalent) returning their framework's view type.\n */\nexport interface ProtocolAdapter {\n /** IANA-style MIME type this adapter handles (e.g. ``application/json+a2ui``). */\n readonly mimeType: string;\n}\n\n/**\n * MIME-keyed adapter map, generic on the adapter subtype so consumers\n * can register richer shapes without casting on every read.\n */\nexport class ProtocolRegistry<T extends ProtocolAdapter = ProtocolAdapter> {\n private adapters = new Map<string, T>();\n\n /** Register or replace the adapter for a MIME type. */\n register(adapter: T): void {\n this.adapters.set(adapter.mimeType, adapter);\n }\n\n /** Remove the adapter for a MIME type. No-op if not registered. */\n unregister(mimeType: string): void {\n this.adapters.delete(mimeType);\n }\n\n /** Returns the adapter for a MIME type, or ``null`` if none is registered. */\n get(mimeType: string): T | null {\n return this.adapters.get(mimeType) ?? null;\n }\n\n has(mimeType: string): boolean {\n return this.adapters.has(mimeType);\n }\n\n /** Drop every adapter. Called when a session disconnects. */\n clear(): void {\n this.adapters.clear();\n }\n\n listMimeTypes(): string[] {\n return Array.from(this.adapters.keys());\n }\n}\n","// =============================================================================\n// Wire → ChatEvent translation\n//\n// Single source of truth for the pure translation from backend wire types\n// (snake_case, mirroring Pydantic) to consumer-facing ChatEvents (camelCase).\n// Shared between the live session loop and the persisted-event replay path,\n// so adding a new event type only requires updating one place.\n// =============================================================================\n\nimport type {\n BlockDeltaPayload,\n ChatEvent,\n WireBlockDeltaPayload,\n WireEvent,\n} from \"./types.js\";\nimport type { MemoryRecord, TodoItem } from \"./custom-events.js\";\n\n// --- Delta channels ---\n\n/**\n * Translate a WireBlockDeltaPayload into the consumer-facing shape. Returns\n * ``null`` for unknown channels so forward-compatible backends can add new\n * ones without breaking old clients.\n */\nexport function translateDelta(\n wire: WireBlockDeltaPayload,\n): BlockDeltaPayload | null {\n switch (wire.channel) {\n case \"text\":\n return { channel: \"text\", text: wire.text };\n case \"thinking\":\n return { channel: \"thinking\", text: wire.text };\n case \"signature\":\n return { channel: \"signature\", signature: wire.signature };\n case \"input\":\n return { channel: \"input\", partialJson: wire.partial_json };\n case \"input_arg\":\n return {\n channel: \"inputArg\",\n argName: wire.arg_name,\n text: wire.text,\n };\n case \"output\":\n return { channel: \"output\", stream: wire.stream, chunk: wire.chunk };\n case \"status\":\n return {\n channel: \"status\",\n status: wire.status,\n note: wire.note,\n };\n default:\n return null;\n }\n}\n\n// --- Custom events ---\n\nfunction translateAgentIdentity(raw: Record<string, unknown>) {\n return {\n name: (raw.name as string) ?? \"\",\n displayName: (raw.display_name as string | null) ?? null,\n avatarUrl: (raw.avatar_url as string | null) ?? null,\n description: (raw.description as string | null) ?? null,\n };\n}\n\n/**\n * Translate a wire custom event (``{type: \"custom\", name, data}``) into a\n * typed ChatEvent. Unknown names fall through to the generic ``custom``\n * passthrough so consumers can still observe future backends.\n */\nexport function translateCustomEvent(\n name: string,\n data: Record<string, unknown>,\n): ChatEvent {\n switch (name) {\n case \"user_message\":\n return {\n type: \"user_message\",\n content: (data.content as string) ?? \"\",\n createdAt: data.created_at as number | undefined,\n };\n case \"title_generated\":\n return {\n type: \"title_generated\",\n title: (data.title as string) ?? \"\",\n };\n case \"todo_update\":\n return {\n type: \"todo_update\",\n todos: (data.todos as TodoItem[]) ?? [],\n };\n case \"plan_update\":\n return {\n type: \"plan_update\",\n plan: (data.plan as string) ?? \"\",\n };\n case \"note_update\":\n return {\n type: \"note_update\",\n notes: (data.notes as string[]) ?? [],\n };\n case \"context_update\":\n return {\n type: \"context_update\",\n context: (data.context as Record<string, unknown>) ?? {},\n phase: (data.phase as string | null) ?? null,\n updatedAt: (data.updated_at as number | null) ?? null,\n };\n case \"subagent_start\":\n return {\n type: \"subagent_start\",\n agent: translateAgentIdentity(\n (data.agent as Record<string, unknown>) ?? {},\n ),\n taskCallId: (data.task_call_id as string | null) ?? null,\n };\n case \"subagent_stop\":\n return {\n type: \"subagent_stop\",\n agent: translateAgentIdentity(\n (data.agent as Record<string, unknown>) ?? {},\n ),\n taskCallId: (data.task_call_id as string | null) ?? null,\n };\n case \"context_warning\":\n return {\n type: \"context_warning\",\n severity: (data.severity as string) ?? \"warning\",\n utilizationPct: (data.utilization_pct as number) ?? 0,\n remainingTokens: (data.remaining_tokens as number) ?? 0,\n windowTokens: (data.window_tokens as number) ?? 0,\n inputTokens: (data.input_tokens as number) ?? 0,\n message: (data.message as string) ?? \"\",\n };\n case \"memory_recall\":\n return {\n type: \"memory_recall\",\n memories: (data.memories as MemoryRecord[] | undefined) ?? [],\n };\n case \"memory_update\":\n return {\n type: \"memory_update\",\n action: (data.action as string) ?? \"\",\n memoryId: (data.memory_id as string | null) ?? null,\n key: (data.key as string | null) ?? null,\n namespace: (data.namespace as string | null) ?? null,\n };\n case \"desktop_stream\":\n return {\n type: \"desktop_stream\",\n url: (data.url as string) ?? \"\",\n sandboxId: (data.sandbox_id as string | null) ?? null,\n };\n case \"attachment_staged\":\n return {\n type: \"attachment_staged\",\n attachmentId: (data.attachment_id as string) ?? \"\",\n filename: (data.filename as string) ?? \"\",\n contentType: (data.content_type as string | null) ?? null,\n sizeBytes: (data.size_bytes as number | null) ?? null,\n };\n case \"workspace_ready\":\n return {\n type: \"workspace_ready\",\n sandboxId: (data.sandbox_id as string) ?? \"\",\n workspacePath: (data.workspace_path as string | null) ?? null,\n };\n case \"asset_created\":\n return {\n type: \"asset_created\",\n assetId: (data.asset_id as string) ?? \"\",\n filename: (data.filename as string) ?? \"\",\n url: (data.url as string | null) ?? null,\n contentType: (data.content_type as string | null) ?? null,\n };\n case \"tool_approval_requested\":\n return {\n type: \"tool_approval_requested\",\n toolName: (data.tool_name as string) ?? \"\",\n callId: (data.call_id as string) ?? \"\",\n arguments: (data.arguments as Record<string, unknown>) ?? {},\n riskLevel: (data.risk_level as string | null) ?? null,\n reason: (data.reason as string | null) ?? null,\n };\n case \"tool_approval_granted\":\n return {\n type: \"tool_approval_granted\",\n toolName: (data.tool_name as string) ?? \"\",\n callId: (data.call_id as string) ?? \"\",\n };\n case \"tool_permission_denied\":\n return {\n type: \"tool_permission_denied\",\n toolName: (data.tool_name as string) ?? \"\",\n callId: (data.call_id as string) ?? \"\",\n reason: (data.reason as string | null) ?? null,\n deniedBy: (data.denied_by as string | null) ?? null,\n };\n case \"tool_harness_warning\":\n return {\n type: \"tool_harness_warning\",\n toolName: (data.tool_name as string) ?? \"\",\n callId: (data.call_id as string) ?? \"\",\n message: (data.message as string | null) ?? null,\n details: (data.details as Record<string, unknown> | null) ?? null,\n };\n case \"user_unavailable\":\n return {\n type: \"user_unavailable\",\n consecutiveTimeouts: (data.consecutive_timeouts as number) ?? 0,\n toolName: (data.tool_name as string | null) ?? null,\n };\n case \"prompt_suggestion\":\n return {\n type: \"prompt_suggestion\",\n suggestions: (data.suggestions as string[]) ?? [],\n };\n case \"state_changed\":\n return {\n type: \"state_changed\",\n state: (data.state as string) ?? \"\",\n };\n default:\n return { type: \"custom\", name, data };\n }\n}\n\n// --- Top-level wire events ---\n\n/**\n * Legacy-transport payload extractor for events that aren't wrapped in a\n * ``CustomEvent`` envelope. The backend's ``writer.emit(name, data)`` path\n * spreads the payload fields at the top level of the wire object (see\n * ``backend/src/jobs/suggestion_hook.py`` for ``prompt_suggestion``), so\n * the wire object itself *is* the ``data`` dict for ``translateCustomEvent``.\n * This helper isolates the unsafe cast to one place.\n */\nfunction legacyCustomEventData(wire: WireEvent): Record<string, unknown> {\n return wire as unknown as Record<string, unknown>;\n}\n\n/**\n * Translate a full WireEvent into its typed ChatEvent counterpart. Returns\n * ``null`` when the wire payload is malformed (e.g. unknown delta channel)\n * so the caller can skip it without crashing the stream.\n */\nexport function translateWireEvent(wire: WireEvent): ChatEvent | null {\n // Legacy transport: ``prompt_suggestion`` is emitted via the raw\n // ``writer.emit()`` path rather than wrapped in a CustomEvent envelope,\n // so its ``type`` field is the event name itself. Route it through the\n // custom-event translator to keep the mapping in one place.\n if ((wire as { type: string }).type === \"prompt_suggestion\") {\n return translateCustomEvent(\n \"prompt_suggestion\",\n legacyCustomEventData(wire),\n );\n }\n switch (wire.type) {\n case \"message_start\":\n return {\n type: \"message_start\",\n turnId: wire.turn_id,\n model: wire.model,\n agentName: wire.agent_name,\n agentDisplayName: wire.agent_display_name,\n agentAvatarUrl: wire.agent_avatar_url,\n };\n case \"block_start\":\n return {\n type: \"block_start\",\n turnId: wire.turn_id,\n path: wire.path,\n parentPath: wire.parent_path ?? null,\n kind: wire.kind,\n metadata: wire.metadata,\n };\n case \"block_delta\": {\n const delta = translateDelta(wire.delta);\n if (!delta) return null;\n return {\n type: \"block_delta\",\n turnId: wire.turn_id,\n path: wire.path,\n delta,\n };\n }\n case \"block_stop\":\n return {\n type: \"block_stop\",\n turnId: wire.turn_id,\n path: wire.path,\n status: wire.status,\n final: wire.final,\n };\n case \"message_stop\":\n return {\n type: \"message_stop\",\n turnId: wire.turn_id,\n jobId: wire.job_id,\n stopReason: wire.stop_reason,\n usage: {\n inputTokens: wire.usage.input_tokens ?? 0,\n outputTokens: wire.usage.output_tokens ?? 0,\n cachedTokens: wire.usage.cached_tokens ?? 0,\n },\n ttfbMs: wire.ttfb_ms,\n totalMs: wire.total_ms,\n stallCount: wire.stall_count,\n };\n case \"stall\":\n return {\n type: \"stall\",\n sinceLastEventMs: wire.since_last_event_ms,\n stallCount: wire.stall_count,\n };\n case \"retry\":\n return {\n type: \"retry\",\n attempt: wire.attempt,\n reason: wire.reason,\n backoffMs: wire.backoff_ms,\n strategy: wire.strategy ?? null,\n maxAttempts: wire.max_attempts ?? null,\n contextRecovery: wire.context_recovery ?? null,\n };\n case \"error\":\n return {\n type: \"error\",\n code: wire.code,\n message: wire.message,\n blockPath: wire.block_path ?? null,\n };\n case \"keepalive\":\n return {\n type: \"keepalive\",\n sinceLastEventMs: wire.since_last_event_ms,\n };\n case \"custom\":\n return translateCustomEvent(wire.name, wire.data);\n default: {\n // Exhaustive guard — a new WireEvent variant should force this switch\n // to be updated at compile time.\n const _exhaustive: never = wire;\n void _exhaustive;\n return null;\n }\n }\n}\n","import { AstralformClient } from \"./client.js\";\nimport { InMemoryStorage, type ChatStorage } from \"./storage.js\";\nimport { ToolRegistry } from \"./tools.js\";\nimport { ProtocolRegistry } from \"./protocol-registry.js\";\nimport { translateWireEvent } from \"./translate.js\";\nimport type {\n AgentInfo,\n AstralformConfig,\n ChatEvent,\n ChatStreamRequest,\n Conversation,\n ConversationEvent,\n Message,\n AgentStatus,\n SendOptions,\n SkillInfo,\n ToolCallRequest,\n ToolResult,\n WireEvent,\n} from \"./types.js\";\nimport { generateId } from \"./utils.js\";\nimport {\n AuthenticationError,\n ConnectionError,\n RateLimitError,\n} from \"./errors.js\";\n\ntype ChatEventHandler = (event: ChatEvent) => void;\n\n/**\n * Bounded auto-reconnect for a live SSE stream that drops mid-turn (worker\n * restart, network blip). We resume from ``lastSeq`` — the backend replays\n * missed events (``?after=seq``) and, for a job that already died, back-fills a\n * terminal event — so the UI recovers without a manual page refresh. Backoff is\n * exponential and capped; total window (~17s over 6 tries) comfortably covers a\n * server restart without spinning forever if the job is genuinely gone.\n */\nconst SSE_MAX_RECONNECTS = 6;\n\n// Retries for the client-tool result POST itself, independent of the SSE\n// reconnect loop — reconnecting the *stream* can't recover a failed *result\n// submission*, and retrying the POST avoids re-executing a client tool.\nconst TOOL_RESULT_MAX_RETRIES = 3;\n\n/**\n * Conversations fetched per page, by ``connect`` and ``loadMoreConversations``\n * alike. The two must use the same size: the offset is derived from how many\n * rows the server has returned so far, so a first page of a different size\n * would leave the second page's offset pointing at the wrong row.\n */\nexport const CONVERSATION_PAGE_SIZE = 50;\n\nfunction sseReconnectDelayMs(attempt: number): number {\n return Math.min(500 * 2 ** (attempt - 1), 5000);\n}\n\nfunction pathEquals(a: number[], b: number[]): boolean {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (a[i] !== b[i]) return false;\n }\n return true;\n}\n\n/**\n * ChatSession — translates the backend wire protocol into typed ChatEvents\n * for consumers. Owns HTTP + SSE plumbing, conversation state, and the\n * client-tool round-trip. Does NOT own block construction — consumers\n * build their own block state from the typed events.\n */\nexport class ChatSession {\n readonly client: AstralformClient;\n readonly toolRegistry: ToolRegistry;\n readonly storage: ChatStorage;\n /**\n * Pluggable UI protocol adapters. Consumers register a framework-\n * specific adapter (e.g. React) for each MIME type they can render,\n * typically gated on ``session.agentStatus.uiComponents.protocol``.\n * ``ToolBlock``-style consumers look up the adapter for an incoming\n * embedded resource and hand off rendering.\n */\n readonly protocols = new ProtocolRegistry();\n\n // State\n conversationId: string | null = null;\n conversations: Conversation[] = [];\n /**\n * Whether another page of conversations may exist on the server.\n *\n * Inferred from the last page being full, since the list endpoint returns a\n * bare array with no total. A total that happens to be an exact multiple of\n * the page size therefore costs one extra empty request before this flips —\n * cheaper than adding a count query to every list call.\n */\n hasMoreConversations = false;\n /** True while ``loadMoreConversations`` is in flight. */\n isLoadingConversations = false;\n messages: Message[] = [];\n isStreaming = false;\n agentStatus: AgentStatus | null = null;\n agents: AgentInfo[] = [];\n skills: SkillInfo[] = [];\n enabledClientTools = new Set<string>();\n modelDisplayName: string | null = null;\n\n /**\n * Ids of conversations the SERVER has handed us, which is the paging offset.\n *\n * Deliberately not ``conversations.length``. That array also holds\n * conversations created locally and unshifted on top (``createNewConversation``,\n * and the auto-created conversation in ``consumeJobStream``), so using its\n * length as the offset would over-count and silently skip a row of real\n * history on the next page. Tracking ids rather than a counter also makes\n * deletion self-correcting: removing a server-sourced conversation shifts\n * every later page up by one, and dropping its id from this set is exactly\n * that shift — while deleting a purely local one correctly changes nothing.\n */\n private serverConversationIds = new Set<string>();\n\n /**\n * Bumped every time ``connect()`` re-seeds the conversation list.\n *\n * A ``loadMoreConversations`` request issued before a re-seed describes the\n * OLD paging state, so applying its response afterwards both appends the\n * wrong rows and corrupts the offset. Concretely: with 100 rows held, an\n * offset-100 response landing after a reconnect has reset to rows 0-49 would\n * append rows 100-149 — a 50-row hole — and leave the id set at 100, so every\n * later page re-requests offset 100 and never advances again. The generation\n * is captured before the await and rechecked after, so a superseded response\n * is discarded instead.\n */\n private conversationsGeneration = 0;\n\n // Minimal in-session accumulation for the assistant message record.\n // Only top-level ``text`` blocks contribute; subagent / tool output\n // is tracked by the consumer's own block store.\n private accumulatedText = \"\";\n private currentTextPath: number[] | null = null;\n\n private handlers: Set<ChatEventHandler> = new Set();\n private abortController: AbortController | null = null;\n\n constructor(config: AstralformConfig, storage?: ChatStorage) {\n this.client = new AstralformClient(config);\n this.toolRegistry = new ToolRegistry();\n this.storage = storage ?? new InMemoryStorage();\n }\n\n on(handler: ChatEventHandler): () => void {\n this.handlers.add(handler);\n return () => {\n this.handlers.delete(handler);\n };\n }\n\n private emit(event: ChatEvent): void {\n for (const handler of this.handlers) {\n try {\n handler(event);\n } catch {\n // Don't let handler errors crash the session\n }\n }\n }\n\n async connect(): Promise<void> {\n const [status, conversations, agents, skills] = await Promise.allSettled([\n this.client.getAgentStatus(),\n this.client.getConversations(CONVERSATION_PAGE_SIZE),\n this.client.getAgents().catch(() => [] as AgentInfo[]),\n this.client.getSkills().catch(() => [] as SkillInfo[]),\n ]);\n\n if (status.status === \"fulfilled\") {\n this.agentStatus = status.value;\n }\n if (conversations.status === \"fulfilled\") {\n // Reconnect re-seeds page 1, so reset the paging state with it rather\n // than letting a previous connection's offset carry over. Bumping the\n // generation here (and only here — a FAILED fetch leaves the list intact,\n // so an in-flight page is still valid against it) invalidates any page\n // request already in flight against the old offset.\n this.conversationsGeneration++;\n this.conversations = conversations.value;\n this.serverConversationIds = new Set(\n conversations.value.map((c) => c.id),\n );\n this.hasMoreConversations =\n conversations.value.length === CONVERSATION_PAGE_SIZE;\n }\n if (agents.status === \"fulfilled\") {\n this.agents = agents.value;\n }\n if (skills.status === \"fulfilled\") {\n this.skills = skills.value;\n }\n\n this.emit({ type: \"connected\" });\n }\n\n async send(content: string, options?: SendOptions): Promise<void> {\n if ((options?.provider == null) !== (options?.model == null)) {\n throw new Error(\n \"`provider` and `model` must be supplied together (client-side model selection).\",\n );\n }\n if (this.isStreaming) return;\n\n const conversationId =\n options?.conversationId ?? this.conversationId ?? undefined;\n\n const userMessage: Message = {\n id: generateId(),\n conversationId: conversationId ?? \"\",\n role: \"user\",\n content,\n status: \"complete\",\n createdAt: new Date().toISOString(),\n };\n if (conversationId) {\n await this.storage.addMessage(userMessage, conversationId);\n }\n this.messages.push(userMessage);\n\n const request: ChatStreamRequest = {\n message: content,\n conversation_id: conversationId,\n mcp_manifest: this.toolRegistry.getManifest(),\n enabled_mcp: Array.from(\n options?.enabledClientTools ?? this.enabledClientTools,\n ),\n upload_ids: options?.uploadIds,\n agent_name: options?.agentName,\n plan_mode: options?.planMode,\n image_mode: options?.imageMode,\n goal: options?.goal,\n // Per-request model choice (client-side model selection).\n provider: options?.provider,\n model: options?.model,\n reasoning_effort: options?.reasoningEffort,\n temperature: options?.temperature,\n };\n\n await this.processStream(request);\n }\n\n async resendFromCheckpoint(\n messageId: string,\n newContent: string,\n ): Promise<void> {\n if (this.isStreaming) return;\n\n const request: ChatStreamRequest = {\n message: newContent,\n conversation_id: this.conversationId ?? undefined,\n resend_from: messageId,\n mcp_manifest: this.toolRegistry.getManifest(),\n enabled_mcp: Array.from(this.enabledClientTools),\n };\n\n await this.processStream(request);\n }\n\n private resetStreamingState(): void {\n this.accumulatedText = \"\";\n this.currentTextPath = null;\n }\n\n private async processStream(request: ChatStreamRequest): Promise<void> {\n this.isStreaming = true;\n this.resetStreamingState();\n this.abortController = new AbortController();\n\n try {\n await this.consumeJobStream(request);\n } catch (err) {\n if (!(err instanceof DOMException && err.name === \"AbortError\")) {\n this.emit({\n type: \"error\",\n code: \"connection_error\",\n message: err instanceof Error ? err.message : String(err),\n blockPath: null,\n });\n }\n } finally {\n this.isStreaming = false;\n this.abortController = null;\n }\n }\n\n /** Last received sequence number for resumable reconnection */\n private lastSeq = -1;\n\n /**\n * Client-tool call_ids whose result was already submitted this turn. On a\n * reconnect the resumed stream can replay a tool request we already handled;\n * this dedups so each is executed + submitted at most once (but a request we\n * never submitted still runs). Cleared at the start of each turn.\n */\n private submittedToolCallIds = new Set<string>();\n\n /** Current job ID for cancellation */\n currentJobId: string | null = null;\n\n private async consumeJobStream(request: ChatStreamRequest): Promise<void> {\n const job = await this.client.createJob(request);\n this.currentJobId = job.job_id;\n\n const conversationId = job.conversation_id;\n if (!this.conversationId) {\n this.conversationId = conversationId;\n }\n // Ensure the conversation exists in both the local array and\n // ChatStorage so title_generated, completeStream, and fallback\n // reload all work for backend-created conversations.\n if (!this.conversations.some((c) => c.id === conversationId)) {\n const now = new Date().toISOString();\n const conv = {\n id: conversationId,\n title: \"\",\n messageCount: 0,\n createdAt: now,\n updatedAt: now,\n };\n this.conversations.unshift(conv);\n await this.storage.createConversation(conversationId, \"\").catch(() => {});\n }\n // Backfill the just-sent user message if send() ran before we knew the\n // conversation id (first turn of an auto-created conversation).\n const lastMsg = this.messages[this.messages.length - 1];\n if (lastMsg?.role === \"user\" && !lastMsg.conversationId) {\n lastMsg.conversationId = conversationId;\n await this.storage.addMessage(lastMsg, conversationId).catch(() => {});\n }\n const messageId = job.message_id;\n this.lastSeq = -1;\n this.submittedToolCallIds.clear();\n\n await this.consumeEventStream(\n job.job_id,\n conversationId,\n messageId,\n true, // executeClientTools\n );\n }\n\n /**\n * Shared event consumption loop. Parses each wire event, updates\n * minimal session state, and emits typed ChatEvents to consumers.\n */\n private async consumeEventStream(\n jobId: string,\n conversationId: string,\n messageId: string,\n executeClientTools: boolean,\n ): Promise<void> {\n // Capture the signal ONCE. detach()/disconnect() abort the controller and\n // then null it out synchronously, so re-reading this.abortController later\n // would lose the aborted state (?. → undefined → falsy) and the loop would\n // reconnect an unstoppable, signal-less stream. The AbortSignal stays valid\n // (and stays aborted) even after the controller is gone.\n const signal = this.abortController?.signal;\n\n for (let attempt = 0; ; attempt++) {\n const stream = this.client.streamJobEvents(jobId, this.lastSeq, signal);\n // Client tools stay enabled across reconnects; re-seen tool requests are\n // deduped by submitted call_id in dispatchWireEvent, so a tool whose\n // result we never posted (drop before submit) still runs on resume.\n let sawTerminal: boolean;\n try {\n sawTerminal = await this.pumpStream(\n stream,\n conversationId,\n messageId,\n executeClientTools,\n );\n } catch (err) {\n if (signal?.aborted) return; // user cancelled / detached\n // Auth failures and rate limits can't be fixed by reconnecting (and\n // hammering a 429 is harmful) — surface them immediately. Genuine\n // connectivity failures (incl. a 5xx from a restarting server) retry.\n if (\n err instanceof AuthenticationError ||\n err instanceof RateLimitError\n ) {\n throw err;\n }\n if (attempt >= SSE_MAX_RECONNECTS) throw err;\n await this.sleepUnlessAborted(sseReconnectDelayMs(attempt + 1), signal);\n continue; // resume from lastSeq\n }\n\n // A terminal event (message_stop / error) ends the turn — including the\n // backend's back-filled terminal for a job that died mid-stream.\n if (sawTerminal || signal?.aborted) return;\n\n // Stream ended WITHOUT a terminal event: the worker/connection dropped\n // mid-turn. Resume from lastSeq so the backend can replay missed events\n // (and back-fill a terminal for a dead job) rather than leave the UI\n // hanging on \"working\".\n if (attempt >= SSE_MAX_RECONNECTS) {\n throw new ConnectionError(\"Lost connection to the response stream.\");\n }\n await this.sleepUnlessAborted(sseReconnectDelayMs(attempt + 1), signal);\n }\n }\n\n /**\n * Consume a single SSE stream to exhaustion. Returns whether a terminal\n * event (``message_stop`` / ``error``) was seen, so the caller can decide\n * whether an ended stream means \"turn done\" vs \"dropped, reconnect\".\n */\n private async pumpStream(\n stream: AsyncGenerator<{ data: string }>,\n conversationId: string,\n messageId: string,\n executeClientTools: boolean,\n ): Promise<boolean> {\n let sawTerminal = false;\n for await (const raw of stream) {\n let parsed: WireEvent;\n try {\n const data = JSON.parse(raw.data);\n if (\n typeof data !== \"object\" ||\n data === null ||\n typeof data.type !== \"string\"\n ) {\n // Legacy \"done\" sentinel — backend still emits it for subscribers.\n // Silently consume; the new protocol uses message_stop for turn end.\n if (typeof (data as { seq?: unknown })?.seq === \"number\") {\n this.lastSeq = (data as { seq: number }).seq;\n }\n continue;\n }\n parsed = data as WireEvent;\n if (typeof (data as Record<string, unknown>).seq === \"number\") {\n this.lastSeq = (data as Record<string, unknown>).seq as number;\n }\n } catch {\n continue;\n }\n\n if (parsed.type === \"message_stop\" || parsed.type === \"error\") {\n sawTerminal = true;\n }\n\n await this.dispatchWireEvent(\n parsed,\n conversationId,\n messageId,\n executeClientTools,\n );\n }\n return sawTerminal;\n }\n\n /** Sleep for ``ms``, resolving early if the turn is aborted mid-backoff. */\n private sleepUnlessAborted(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve) => {\n if (signal?.aborted) return resolve();\n const timer = setTimeout(() => {\n signal?.removeEventListener(\"abort\", onAbort);\n resolve();\n }, ms);\n const onAbort = () => {\n clearTimeout(timer);\n resolve();\n };\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n });\n }\n\n /** POST a client-tool result, retrying transient failures a few times. */\n private async submitToolResultWithRetry(\n payload: Parameters<AstralformClient[\"submitToolResult\"]>[0],\n ): Promise<void> {\n const signal = this.abortController?.signal;\n for (let attempt = 0; ; attempt++) {\n try {\n await this.client.submitToolResult(payload);\n return;\n } catch (err) {\n if (signal?.aborted) throw err;\n if (\n err instanceof AuthenticationError ||\n err instanceof RateLimitError\n ) {\n throw err;\n }\n if (attempt >= TOOL_RESULT_MAX_RETRIES) throw err;\n await this.sleepUnlessAborted(sseReconnectDelayMs(attempt + 1), signal);\n }\n }\n }\n\n private async dispatchWireEvent(\n wire: WireEvent,\n conversationId: string,\n messageId: string,\n executeClientTools: boolean,\n ): Promise<void> {\n // Side effects that depend on mutable session state must run before the\n // ChatEvent is emitted so consumers see a consistent view.\n this.applyWireSideEffects(wire, conversationId, messageId);\n\n const event = translateWireEvent(wire);\n if (event) {\n this.emit(event);\n }\n\n // Client tool round-trip — deferred to block_stop with\n // status=awaiting_client_result, where the parsed input is in final.input.\n if (\n executeClientTools &&\n wire.type === \"block_stop\" &&\n wire.status === \"awaiting_client_result\" &&\n wire.final?.call_id\n ) {\n const f = wire.final;\n const callId = (f.call_id as string) ?? \"\";\n // Dedup across reconnects: a resumed stream can replay a tool request we\n // already handled. Execute + submit each call_id at most once — but DO\n // run requests not yet submitted (e.g. the drop happened before we could\n // post the result), rather than skipping client tools wholesale.\n if (callId && !this.submittedToolCallIds.has(callId)) {\n const request: ToolCallRequest = {\n callId,\n toolName: (f.tool_name as string) ?? \"\",\n arguments: (f.input as Record<string, unknown>) ?? {},\n isClientTool: true,\n };\n const results = await this.executeClientTools([request]);\n // Retry the POST itself before giving up: reconnecting the SSE stream\n // can't recover a failed result submission, and retrying here avoids\n // re-executing the tool on a transient network blip.\n await this.submitToolResultWithRetry({\n conversation_id: conversationId,\n message_id: messageId,\n tool_results: results,\n });\n // Marked only after a successful submit, so a drop mid-POST re-runs it.\n this.submittedToolCallIds.add(callId);\n }\n }\n }\n\n /**\n * Synchronous replay of a single stored wire event — the side-effect +\n * translate + emit core of ``dispatchWireEvent`` without the (live-only)\n * client-tool round-trip. Called in a tight synchronous loop during history\n * restore so the consumer's per-event store writes batch into ONE render\n * instead of re-typing the whole conversation event by event.\n */\n private replayWireEvent(wire: WireEvent, conversationId: string): void {\n this.applyWireSideEffects(wire, conversationId, \"\");\n const event = translateWireEvent(wire);\n if (event) {\n this.emit(event);\n }\n }\n\n /**\n * State mutations driven by wire events. Kept separate from translation so\n * the pure wire → ChatEvent mapping can live in translate.ts and be reused\n * by the replay path.\n *\n * ``messageId`` is the server-assigned assistant message id for the current\n * turn; empty in the reconnect and conversation-switch replay paths where\n * messages have already been loaded from REST and shouldn't be re-pushed.\n */\n private applyWireSideEffects(\n wire: WireEvent,\n conversationId: string,\n messageId: string,\n ): void {\n switch (wire.type) {\n case \"message_start\":\n // Reset per-turn accumulator so multi-turn replay doesn't concatenate\n // text from prior turns into the next assistant message.\n this.resetStreamingState();\n if (wire.model) {\n this.modelDisplayName = wire.model;\n }\n return;\n\n case \"block_start\":\n // Track the currently open top-level text block so we can accumulate\n // its content for the assistant Message record.\n if (\n wire.kind === \"text\" &&\n (!wire.parent_path || wire.parent_path.length === 0)\n ) {\n this.currentTextPath = wire.path;\n }\n return;\n\n case \"block_delta\":\n if (\n wire.delta.channel === \"text\" &&\n this.currentTextPath !== null &&\n pathEquals(this.currentTextPath, wire.path)\n ) {\n this.accumulatedText += wire.delta.text;\n }\n return;\n\n case \"block_stop\":\n if (\n this.currentTextPath !== null &&\n pathEquals(this.currentTextPath, wire.path)\n ) {\n this.currentTextPath = null;\n }\n return;\n\n case \"message_stop\":\n // Only record the assistant message when we have the server's\n // message id. Reconnect/replay paths load messages via REST instead.\n if (messageId) {\n const assistantMessage: Message = {\n id: messageId,\n conversationId,\n role: \"assistant\",\n content: this.accumulatedText,\n status: \"complete\",\n createdAt: new Date().toISOString(),\n };\n this.messages.push(assistantMessage);\n this.storage\n .addMessage(assistantMessage, conversationId)\n .catch(() => {});\n }\n this.isStreaming = false;\n this.currentJobId = null;\n return;\n\n case \"custom\":\n if (wire.name === \"title_generated\") {\n const title = (wire.data.title as string) ?? \"\";\n if (this.conversationId && title) {\n const conv = this.conversations.find(\n (c) => c.id === this.conversationId,\n );\n if (conv) {\n conv.title = title;\n }\n this.storage\n .updateConversationTitle(this.conversationId, title)\n .catch(() => {});\n }\n }\n return;\n\n default:\n return;\n }\n }\n\n private async executeClientTools(\n toolCalls: ToolCallRequest[],\n ): Promise<ToolResult[]> {\n const results: ToolResult[] = [];\n for (const call of toolCalls) {\n const result = await this.toolRegistry.executeTool(call);\n results.push(result);\n }\n return results;\n }\n\n /**\n * Load conversation context (messages) without replaying events.\n * Used before reconnectToJob — SSE replay handles event replay.\n */\n async loadConversation(id: string): Promise<void> {\n this.conversationId = id;\n this.resetStreamingState();\n this.messages = await this.client\n .getMessages(id)\n .catch(() => this.storage.fetchMessages(id));\n }\n\n /**\n * Reconnect to a running job's SSE stream (e.g. after page reload).\n * Replays all events from the beginning and continues live.\n */\n async reconnectToJob(jobId: string): Promise<void> {\n if (this.isStreaming) return;\n\n this.isStreaming = true;\n this.currentJobId = jobId;\n this.lastSeq = -1;\n this.submittedToolCallIds.clear();\n this.resetStreamingState();\n this.abortController = new AbortController();\n\n try {\n await this.consumeEventStream(\n jobId,\n this.conversationId ?? \"\",\n \"\",\n false, // don't execute client tools on reconnect\n );\n } catch (err) {\n this.emit({\n type: \"error\",\n code: \"connection_error\",\n message: err instanceof Error ? err.message : String(err),\n blockPath: null,\n });\n } finally {\n this.isStreaming = false;\n this.abortController = null;\n }\n }\n\n /** Detach from the SSE stream without cancelling the job. */\n detach(): void {\n this.abortController?.abort();\n this.abortController = null;\n this.isStreaming = false;\n this.resetStreamingState();\n this.emit({ type: \"disconnected\" });\n }\n\n /** Stop the job and disconnect (explicit user action). */\n disconnect(): void {\n if (this.currentJobId) {\n this.client.cancelJob(this.currentJobId).catch(() => {});\n }\n this.detach();\n this.currentJobId = null;\n // Drop all protocol adapters — lifecycle tied to the session.\n this.protocols.clear();\n }\n\n async createNewConversation(): Promise<string> {\n const id = generateId();\n const conversation = await this.storage.createConversation(\n id,\n \"New Conversation\",\n );\n this.conversations.unshift(conversation);\n this.conversationId = id;\n this.messages = [];\n return id;\n }\n\n /**\n * Replay one completed turn's already-fetched events, synchronously.\n *\n * Fetching is the caller's job (``StreamManager.restore`` loads every turn's\n * events in parallel and the message list once), so this is pure replay: no\n * awaits, so the whole restore runs in a single synchronous pass and the\n * consumer batches it into one render.\n *\n * ``userMessageContent`` is the prompt that triggered this turn. It's emitted\n * as a synthetic ``user_message`` BEFORE any of the turn's events: user\n * prompts aren't persisted in ``job_events``, and some events precede\n * ``message_start`` in the stream (e.g. ``memory_recall`` from prompt prep),\n * so leading with the prompt keeps the turn in order.\n */\n replayTurn(\n id: string,\n events: ConversationEvent[],\n userMessageContent?: string,\n userMessageId?: string,\n isSteer = false,\n ): void {\n this.conversationId = id;\n this.resetStreamingState();\n\n if (userMessageContent) {\n this.emit({\n type: \"user_message\",\n content: userMessageContent,\n ...(userMessageId ? { id: userMessageId } : {}),\n ...(isSteer ? { steer: true } : {}),\n });\n }\n\n // The data payload is authoritative for `type` (matching how\n // replay.ts#mapSseToChat reads it), with the SSE event name as a fallback\n // for pre-v2 rows.\n for (const ev of events) {\n const type = (ev.data.type as string) || ev.event;\n if (!type || type === \"done\") continue;\n\n const wire = { ...ev.data, type } as unknown as WireEvent;\n try {\n this.replayWireEvent(wire, id);\n } catch {\n // Skip malformed replay events\n }\n }\n }\n\n /**\n * Load a conversation's messages and replay its persisted history.\n *\n * Convenience for plain-``ChatSession`` consumers (the documented\n * conversation-management API). ``StreamManager`` drives restore itself —\n * loading messages once and replaying each turn in parallel — and does NOT\n * call this; it's kept so direct-Session usage doesn't break.\n *\n * Without ``jobId`` it replays the whole conversation; with one, just that\n * job's events.\n */\n async switchConversation(id: string, jobId?: string): Promise<void> {\n const [messagesResult, eventsResult] = await Promise.allSettled([\n this.client.getMessages(id).catch(() => this.storage.fetchMessages(id)),\n this.client.getConversationEvents(id, jobId),\n ]);\n this.messages =\n messagesResult.status === \"fulfilled\" ? messagesResult.value : [];\n this.replayTurn(\n id,\n eventsResult.status === \"fulfilled\" ? eventsResult.value : [],\n );\n }\n\n /**\n * Append the next page of conversation history to ``conversations``.\n *\n * The list is ordered ``updated_at DESC`` and paged by offset, so a\n * conversation bumped to the top mid-scroll can surface again in a later\n * page; ids already held are dropped rather than duplicated. Returns only\n * the conversations actually appended, which may be empty even on a full\n * page. Rejects on network failure with ``hasMoreConversations`` still true,\n * so the caller can retry.\n *\n * KNOWN LIMITATION — offset paging is only stable while the prefix already\n * consumed stays put. The offset tracking here corrects for perturbations\n * THIS session causes (local unshifts, ``deleteConversation``), but not for\n * ones it never sees:\n *\n * - a conversation this session hasn't loaded yet is bumped to the top (a\n * headless routine or another device posting to it), pushing the whole\n * list down — it lands inside the consumed prefix, which no later offset\n * revisits;\n * - a conversation is deleted from another tab/device, shrinking the list so\n * the next offset lands one row too far in.\n *\n * Each perturbation costs at most one conversation off the sidebar, and only\n * until the next ``connect()`` — that re-seeds page 1 and resets the paging\n * state, so a reload or reconnect always recovers it. Nothing is lost\n * server-side. Both cases are pinned by tests in\n * ``tests/conversation-paging.test.ts``.\n *\n * Closing the gap properly needs a stable server cursor (keyset paging on\n * ``(updated_at, id)``) rather than a raw offset, which is a backend change —\n * tracking ids client-side cannot discover a row that moved into a region\n * already scanned.\n */\n async loadMoreConversations(): Promise<Conversation[]> {\n // Scroll handlers fire far faster than the request completes; without this\n // guard every frame would refetch the same offset.\n if (this.isLoadingConversations || !this.hasMoreConversations) return [];\n this.isLoadingConversations = true;\n const generation = this.conversationsGeneration;\n try {\n const page = await this.client.getConversations(\n CONVERSATION_PAGE_SIZE,\n this.serverConversationIds.size,\n );\n // A reconnect re-seeded the list while this was in flight, so this page\n // describes a paging state that no longer exists. Drop it untouched —\n // connect() has already set conversations/hasMore for the new state, and\n // the caller's next call pages from there.\n if (generation !== this.conversationsGeneration) return [];\n this.hasMoreConversations = page.length === CONVERSATION_PAGE_SIZE;\n const fresh = page.filter((c) => !this.serverConversationIds.has(c.id));\n for (const c of page) this.serverConversationIds.add(c.id);\n // A locally-created conversation can already sit in the array from its\n // unshift; count it toward the offset (the server did return it) but\n // don't append a second copy.\n const known = new Set(this.conversations.map((c) => c.id));\n const appended = fresh.filter((c) => !known.has(c.id));\n this.conversations.push(...appended);\n return appended;\n } finally {\n this.isLoadingConversations = false;\n }\n }\n\n /**\n * Rename a conversation, server first.\n *\n * Deliberately NOT optimistic, unlike the delete below. A failed delete is\n * self-correcting (the row is still there on the next page fetch), but a\n * failed rename that had already been written locally would leave the\n * sidebar showing a title the server never accepted — and nothing refetches\n * a conversation that is already in the loaded list.\n *\n * Mirrors the `title_generated` path: the entry in `conversations` is\n * mutated in place, which is what every consumer of the list reads.\n */\n async renameConversation(id: string, title: string): Promise<void> {\n const updated = await this.client.renameConversation(id, title);\n const conv = this.conversations.find((c) => c.id === id);\n if (conv) {\n // The server's title, not the caller's — it trims before storing.\n conv.title = updated.title;\n }\n await this.storage.updateConversationTitle(id, updated.title);\n }\n\n async deleteConversation(id: string): Promise<void> {\n try {\n await this.client.deleteConversation(id);\n } catch {\n // May already be deleted on backend\n }\n await this.storage.deleteConversation(id);\n // Shrinks the paging offset iff the server had handed us this one — every\n // later page now shifts up by one, and without this the next page would\n // skip a conversation. A purely local conversation isn't in the set, so\n // deleting it correctly leaves the offset alone.\n //\n // ``Set.delete`` reports whether it was there, which is exactly the\n // \"was this server-sourced?\" test. When it was, any page ALREADY in flight\n // is now stale for the same reason as a reconnect: its offset was computed\n // pre-delete but the server evaluates the query post-delete, so it starts\n // one row late and would skip that row for good. The adjustment above fixes\n // future requests and cannot rescue an outstanding one — so invalidate it.\n if (this.serverConversationIds.delete(id)) {\n this.conversationsGeneration++;\n }\n this.conversations = this.conversations.filter((c) => c.id !== id);\n if (this.conversationId === id) {\n this.conversationId = null;\n this.messages = [];\n }\n }\n\n toggleClientTool(name: string): boolean {\n if (this.enabledClientTools.has(name)) {\n this.enabledClientTools.delete(name);\n return false;\n }\n this.enabledClientTools.add(name);\n return true;\n }\n}\n","/**\n * Deciding which prompt started which turn, when restoring a conversation.\n *\n * Restore has to pair each completed job with the user message that triggered\n * it. It used to do that by POSITION — the N-th completed job to the N-th user\n * message — which is only sound while every user message starts exactly one\n * job. A mid-run steer (`POST /conversations/{id}/steer`) is a user message\n * that starts none, so it shifted every later turn onto the wrong prompt and\n * pushed the tail off the end of the loop entirely.\n *\n * The backend now stamps a turn's prompt with the same id the job records\n * (`job.message_id`), so the pairing can be exact. Four kinds of row show up in\n * the two lists, and the id is what tells them apart:\n *\n * | row | has message id | job links to it |\n * |----------------------|----------------|-----------------|\n * | current turn prompt | yes | yes |\n * | mid-run steer | yes | no |\n * | legacy prompt | no | n/a |\n * | goal-continuation | hidden from the message list entirely |\n *\n * Classification keys off whether a job's `message_id` actually MATCHES a\n * message — never off a field being present. Both are always populated in real\n * data: `jobs.message_id` is NOT NULL, and the messages endpoint substitutes a\n * positional index string when a row carries no id of its own. A presence check\n * therefore reads every pre-link conversation as linked and strips every prompt\n * bubble from it.\n *\n * So: a job whose id matches a message is a turn; a message no job claims is a\n * steer; a job past the cutover whose id matches nothing visible is a\n * continuation, replaying with no bubble (what the positional version did by\n * accident when it ran off the end of the message list).\n *\n * Jobs are the spine, so continuations keep their place in the transcript.\n * Steers are interleaved at the point they appear in the message list, which\n * puts them after the turn that was running when they were sent.\n *\n * A conversation predating the link has no `message_id` on any job; there is\n * nothing to pair with, and no backfill is possible — inferring which historic\n * prompt started which job is exactly the ambiguity the link removes, so\n * guessing would bake the bug into the data. Those fall back to the positional\n * walk, unchanged, and keep the old behaviour including its flaw.\n */\n\nexport interface RestoreJob {\n job_id: string;\n /** The prompt that started this turn. Absent on pre-link rows and on\n * goal-continuation jobs, whose seed is hidden from the message list. */\n message_id?: string | null;\n}\n\nexport interface RestoreMessage {\n /** Present once the backend tags prompts; absent on pre-link rows. */\n id?: string;\n content: string;\n}\n\nexport type ReplayStep =\n /** Replay a job's events, optionally preceded by the prompt bubble. */\n | { kind: \"turn\"; jobId: string; content?: string; messageId?: string }\n /** A user message that started no turn — render the bubble alone. */\n | { kind: \"steer\"; content: string; messageId?: string };\n\n/**\n * Order the replay: which jobs to play, with which prompts, and where the\n * steers go between them. Pure, so the ordering rules are testable without a\n * session, a network, or a fake event stream.\n */\nexport function planRestore(args: {\n completedJobs: RestoreJob[];\n userMessages: RestoreMessage[];\n}): ReplayStep[] {\n const { completedJobs, userMessages } = args;\n\n const byId = new Map<string, number>();\n userMessages.forEach((m, i) => {\n if (m.id) byId.set(m.id, i);\n });\n /** The message index this job's prompt lives at, if it is visible. */\n const linkOf = (j: RestoreJob): number | undefined =>\n j.message_id ? byId.get(j.message_id) : undefined;\n\n const positional = (jobs: RestoreJob[], msgs: RestoreMessage[]): ReplayStep[] =>\n jobs.map((job, i) => ({\n kind: \"turn\" as const,\n jobId: job.job_id,\n content: msgs[i]?.content,\n messageId: msgs[i]?.id,\n }));\n\n // Detection keys off whether a job's id actually MATCHES a message — never\n // off a field merely being present. Both fields are always populated in real\n // data (`jobs.message_id` is NOT NULL, and the messages endpoint substitutes\n // a positional index string when a row has no id of its own), so a\n // presence check silently classifies every pre-link conversation as linked\n // and strips every prompt bubble from it.\n const firstLinked = completedJobs.findIndex((j) => linkOf(j) !== undefined);\n if (firstLinked === -1) {\n // Nothing matches: the whole conversation predates the tagging.\n return positional(completedJobs, userMessages);\n }\n\n // The tagging started at a point in time, so a conversation spanning it\n // splits cleanly: everything before the first linked turn has no usable\n // link and falls back to position; everything after is exact.\n const cutover = linkOf(completedJobs[firstLinked]!)!;\n const steps: ReplayStep[] = positional(\n completedJobs.slice(0, firstLinked),\n userMessages.slice(0, cutover),\n );\n\n let cursor = cutover;\n const isSteer = (m: RestoreMessage | undefined): m is RestoreMessage =>\n !!m?.id && !completedJobs.some((j) => j.message_id === m.id);\n\n /** Emit every steer sitting before `stopAt`, and advance past it. */\n const drainTo = (stopAt: number) => {\n while (cursor < stopAt) {\n const m = userMessages[cursor++];\n if (isSteer(m)) {\n steps.push({ kind: \"steer\", content: m.content, messageId: m.id });\n }\n }\n cursor = stopAt + 1;\n };\n\n for (const job of completedJobs.slice(firstLinked)) {\n const at = linkOf(job);\n if (at !== undefined) {\n drainTo(at);\n const prompt = userMessages[at]!;\n steps.push({\n kind: \"turn\",\n jobId: job.job_id,\n content: prompt.content,\n messageId: prompt.id,\n });\n continue;\n }\n // Past the cutover, a job whose id matches nothing visible is a goal\n // continuation: its seed is deliberately hidden from the message list, so\n // it replays its events with no bubble — which is what the positional\n // version did by accident when it ran off the end of the messages.\n steps.push({ kind: \"turn\", jobId: job.job_id });\n }\n\n // Steers sent during the final turn sit past every prompt.\n for (let i = cursor; i < userMessages.length; i++) {\n const m = userMessages[i];\n if (isSteer(m)) {\n steps.push({ kind: \"steer\", content: m.content, messageId: m.id });\n }\n }\n\n return steps;\n}\n","// =============================================================================\n// Astralform SDK v2 type definitions\n// =============================================================================\n//\n// Wire protocol types mirror the Pydantic models in\n// `backend/src/stream/protocol.py` 1:1. The SDK forwards typed events to\n// the consumer; block construction happens on the consumer side.\n//\n// Custom event payloads live in `custom-events.ts`.\n\nimport type { AgentIdentity, MemoryRecord, TodoItem } from \"./custom-events.js\";\n\n// Re-export so consumers can import from types.ts or custom-events.ts\nexport type { AgentIdentity, MemoryRecord, TodoItem };\n\n// --- Configuration ---\n//\n// The SDK supports two authentication modes. TypeScript narrows the union by\n// property presence, so consumers can write:\n//\n// new AstralformClient({ apiKey, userId }) // API-key mode\n// new AstralformClient({ accessToken, agentId }) // user-token mode\n//\n// Pick based on who the caller represents:\n//\n// * API-key mode — A customer's backend (B2B2C). Agent scoping is baked\n// into the key; the end user is named per request via `X-End-User-ID`.\n//\n// * User-token mode — An app acting on behalf of an Astralform account\n// holder (AstralChat, future 3rd-party integrations). The OIDC access\n// token is issued by the Astralform Identity Provider (our OAuth 2.1\n// Server at auth.astralform.ai); agent scoping comes from the\n// `X-Agent-ID` header.\n\ninterface AstralformBaseConfig {\n /** Override the default API origin. Defaults to https://api.astralform.ai. */\n baseURL?: string;\n /** Supply a custom fetch (SSR, testing, custom interceptors). */\n fetch?: typeof globalThis.fetch;\n /**\n * Abort a REST request — connect, headers, AND body read — after this many\n * milliseconds. Defaults to 30_000. Does not apply to `uploadFile` (large\n * files on slow uplinks) or to SSE streaming, which is long-lived by design\n * and carries its own `AbortSignal`.\n *\n * Note that unlike axios and friends, `0` does NOT mean \"no timeout\": any\n * non-positive or non-finite value falls back to the default. REST calls\n * cannot opt out of the deadline — an unbounded REST request is the bug\n * this exists to prevent, and it fails silently (a stalled body strands the\n * caller forever with nothing to react to).\n */\n timeoutMs?: number;\n}\n\nexport interface AstralformApiKeyConfig extends AstralformBaseConfig {\n /** Agent API key (`sk_live_...` or `sk_test_...`). */\n apiKey: string;\n /**\n * The customer's own identifier for the end user making this call.\n * Sent as the `X-End-User-ID` header. Required in API-key mode.\n */\n userId: string;\n}\n\nexport interface AstralformUserTokenConfig extends AstralformBaseConfig {\n /**\n * OIDC access token issued by the Astralform Identity Provider.\n * Expect this to be short-lived; use `client.updateAccessToken()` after\n * refreshing to hot-swap without re-instantiating the client.\n */\n accessToken: string;\n /**\n * Active agent context. Sent as the `X-Agent-ID` header. The backend\n * verifies the token's developer has access to this agent on every\n * request; switching agents is a local `updateAgentId()` call.\n *\n * Optional so a pre-pick client (right after login, before the user has\n * chosen a team/agent) can still call account-scoped discovery routes\n * like `listTeams()` / `listAgents(teamId)`. Agent-scoped calls\n * (conversations, messages, chat) will error out until one is set.\n */\n agentId?: string;\n /**\n * Optional end-user override — lets a developer acting under a user\n * token impersonate a downstream end-user identity for testing\n * purposes. When set, sent alongside `X-Agent-ID` as `X-End-User-ID`\n * so memory, rate limits, and conversations scope to the specified\n * end-user rather than the developer themselves.\n *\n * Use `client.updateEndUserId()` to rotate at runtime.\n */\n endUserId?: string;\n}\n\nexport type AstralformConfig =\n AstralformApiKeyConfig | AstralformUserTokenConfig;\n\n// --- Event type constants (SDK public) ---\n//\n// These are the high-level ChatEvent kinds the SDK emits to its\n// consumer. The raw wire events below are translated into these\n// kinds at the session boundary.\n\nexport const ChatEventType = {\n // Connection lifecycle (SDK-local, not wire)\n Connected: \"connected\",\n Disconnected: \"disconnected\",\n\n // Turn lifecycle\n MessageStart: \"message_start\",\n MessageStop: \"message_stop\",\n\n // Block lifecycle\n BlockStart: \"block_start\",\n BlockDelta: \"block_delta\",\n BlockStop: \"block_stop\",\n\n // Reliability\n Stall: \"stall\",\n Retry: \"retry\",\n Error: \"error\",\n Keepalive: \"keepalive\",\n\n // Conversation-level (typed custom events)\n UserMessage: \"user_message\",\n TitleGenerated: \"title_generated\",\n TodoUpdate: \"todo_update\",\n PlanUpdate: \"plan_update\",\n NoteUpdate: \"note_update\",\n ContextUpdate: \"context_update\",\n SubagentStart: \"subagent_start\",\n SubagentStop: \"subagent_stop\",\n ContextWarning: \"context_warning\",\n MemoryRecall: \"memory_recall\",\n MemoryUpdate: \"memory_update\",\n DesktopStream: \"desktop_stream\",\n AttachmentStaged: \"attachment_staged\",\n WorkspaceReady: \"workspace_ready\",\n AssetCreated: \"asset_created\",\n ToolApprovalRequested: \"tool_approval_requested\",\n ToolApprovalGranted: \"tool_approval_granted\",\n ToolPermissionDenied: \"tool_permission_denied\",\n ToolHarnessWarning: \"tool_harness_warning\",\n UserUnavailable: \"user_unavailable\",\n PromptSuggestion: \"prompt_suggestion\",\n StateChanged: \"state_changed\",\n\n // Generic fallthrough for unknown custom events\n Custom: \"custom\",\n} as const;\n\nexport type ChatEventTypeValue =\n (typeof ChatEventType)[keyof typeof ChatEventType];\n\n// =============================================================================\n// Wire protocol (matches backend Pydantic models 1:1, snake_case)\n// =============================================================================\n\nexport type WireBlockKind = \"text\" | \"thinking\" | \"tool_use\";\n\nexport type WireBlockStatus =\n | \"streaming\"\n | \"awaiting_client_result\"\n | \"ok\"\n | \"error\"\n | \"denied\"\n | \"cancelled\";\n\nexport type WireStopReason =\n \"end_turn\" | \"tool_use\" | \"max_tokens\" | \"context_overflow\" | \"error\";\n\n// --- BlockDelta payloads (discriminated on `channel`) ---\n\nexport interface WireTextDelta {\n channel: \"text\";\n text: string;\n}\n\nexport interface WireThinkingDelta {\n channel: \"thinking\";\n text: string;\n}\n\nexport interface WireSignatureDelta {\n channel: \"signature\";\n signature: string;\n}\n\nexport interface WireInputDelta {\n channel: \"input\";\n partial_json: string;\n}\n\nexport interface WireInputArgDelta {\n channel: \"input_arg\";\n arg_name: string;\n text: string;\n}\n\nexport interface WireOutputDelta {\n channel: \"output\";\n stream: \"stdout\" | \"stderr\" | \"progress\";\n chunk: string;\n}\n\nexport interface WireStatusDelta {\n channel: \"status\";\n status:\n \"executing\" | \"awaiting_client_result\" | \"awaiting_approval\" | \"denied\";\n note?: string;\n}\n\nexport type WireBlockDeltaPayload =\n | WireTextDelta\n | WireThinkingDelta\n | WireSignatureDelta\n | WireInputDelta\n | WireInputArgDelta\n | WireOutputDelta\n | WireStatusDelta;\n\n// --- Top-level wire events ---\n\ninterface WireEnvelope {\n seq: number;\n ts: number;\n job_id: string;\n}\n\nexport interface WireMessageStart extends WireEnvelope {\n type: \"message_start\";\n turn_id: string;\n model: string;\n agent_name?: string | null;\n agent_display_name?: string | null;\n agent_avatar_url?: string | null;\n}\n\nexport interface WireBlockStart extends WireEnvelope {\n type: \"block_start\";\n turn_id: string;\n path: number[];\n parent_path?: number[] | null;\n kind: WireBlockKind;\n metadata: Record<string, unknown>;\n}\n\nexport interface WireBlockDelta extends WireEnvelope {\n type: \"block_delta\";\n turn_id: string;\n path: number[];\n delta: WireBlockDeltaPayload;\n}\n\nexport interface WireBlockStop extends WireEnvelope {\n type: \"block_stop\";\n turn_id: string;\n path: number[];\n status: WireBlockStatus;\n final: Record<string, unknown>;\n}\n\nexport interface WireMessageStop extends WireEnvelope {\n type: \"message_stop\";\n turn_id: string;\n stop_reason: WireStopReason;\n usage: {\n input_tokens?: number;\n output_tokens?: number;\n cached_tokens?: number;\n };\n ttfb_ms?: number | null;\n total_ms: number;\n stall_count: number;\n}\n\nexport interface WireStallWarning extends WireEnvelope {\n type: \"stall\";\n since_last_event_ms: number;\n stall_count: number;\n}\n\nexport interface WireRetryEvent extends WireEnvelope {\n type: \"retry\";\n attempt: number;\n reason: string;\n backoff_ms: number;\n strategy?: string | null;\n max_attempts?: number | null;\n context_recovery?: Record<string, unknown> | null;\n}\n\nexport interface WireErrorEvent extends WireEnvelope {\n type: \"error\";\n code: string;\n message: string;\n block_path?: number[] | null;\n // Rate limit fields (carried when code == \"rate_limit_exceeded\")\n retry_after?: number;\n retry_after_sec?: number;\n reset_at?: number | string;\n scope?: string;\n policy_id?: string;\n limit?: number;\n remaining?: number;\n request_id?: string;\n}\n\nexport interface WireKeepalive extends WireEnvelope {\n type: \"keepalive\";\n since_last_event_ms: number;\n}\n\nexport interface WireCustomEvent extends WireEnvelope {\n type: \"custom\";\n name: string;\n data: Record<string, unknown>;\n}\n\nexport type WireEvent =\n | WireMessageStart\n | WireBlockStart\n | WireBlockDelta\n | WireBlockStop\n | WireMessageStop\n | WireStallWarning\n | WireRetryEvent\n | WireErrorEvent\n | WireKeepalive\n | WireCustomEvent;\n\n// =============================================================================\n// ChatEvent — high-level SDK events (camelCase, emitted to consumers)\n// =============================================================================\n\nexport interface TurnUsage {\n inputTokens: number;\n outputTokens: number;\n cachedTokens: number;\n}\n\nexport type BlockDeltaPayload =\n | { channel: \"text\"; text: string }\n | { channel: \"thinking\"; text: string }\n | { channel: \"signature\"; signature: string }\n | { channel: \"input\"; partialJson: string }\n | { channel: \"inputArg\"; argName: string; text: string }\n | {\n channel: \"output\";\n stream: \"stdout\" | \"stderr\" | \"progress\";\n chunk: string;\n }\n | {\n channel: \"status\";\n status:\n \"executing\" | \"awaiting_client_result\" | \"awaiting_approval\" | \"denied\";\n note?: string;\n };\n\nexport type ChatEvent =\n // Connection lifecycle\n | { type: \"connected\" }\n | { type: \"disconnected\" }\n\n // Turn lifecycle\n | {\n type: \"message_start\";\n turnId: string;\n model: string;\n agentName?: string | null;\n agentDisplayName?: string | null;\n agentAvatarUrl?: string | null;\n }\n | {\n type: \"message_stop\";\n turnId: string;\n jobId: string;\n stopReason: WireStopReason;\n usage: TurnUsage;\n ttfbMs?: number | null;\n totalMs: number;\n stallCount: number;\n }\n\n // Block lifecycle\n | {\n type: \"block_start\";\n turnId: string;\n path: number[];\n parentPath?: number[] | null;\n kind: WireBlockKind;\n metadata: Record<string, unknown>;\n }\n | {\n type: \"block_delta\";\n turnId: string;\n path: number[];\n delta: BlockDeltaPayload;\n }\n | {\n type: \"block_stop\";\n turnId: string;\n path: number[];\n status: WireBlockStatus;\n final: Record<string, unknown>;\n }\n\n // Reliability\n | {\n type: \"stall\";\n sinceLastEventMs: number;\n stallCount: number;\n }\n | {\n type: \"retry\";\n attempt: number;\n reason: string;\n backoffMs: number;\n strategy?: string | null;\n maxAttempts?: number | null;\n contextRecovery?: Record<string, unknown> | null;\n }\n | {\n type: \"error\";\n code: string;\n message: string;\n blockPath: number[] | null;\n }\n | {\n type: \"keepalive\";\n sinceLastEventMs: number;\n }\n\n // Conversation-level — typed custom events\n | {\n type: \"user_message\";\n content: string;\n createdAt?: number;\n id?: string;\n /** This message was steered into a run already in flight — it started\n * no turn of its own. Consumers that badge a steer (a \"waiting to be\n * read\" notice) key off this; without it a replayed steer is\n * indistinguishable from an ordinary prompt. */\n steer?: boolean;\n }\n | { type: \"title_generated\"; title: string }\n | { type: \"todo_update\"; todos: TodoItem[] }\n /** The conversation's plan, as markdown, after the agent wrote or revised it.\n * Carries the FULL body rather than a diff: the backend replaces the plan\n * wholesale (`write_plan` — \"Replaces any existing plan\"), so a consumer that\n * merged deltas would drift from the stored document. */\n | { type: \"plan_update\"; plan: string }\n /** Names of the conversation's notes, after one was written or deleted. Names\n * only — bodies are unbounded and read on demand. */\n | { type: \"note_update\"; notes: string[] }\n | {\n type: \"context_update\";\n context: Record<string, unknown>;\n phase?: string | null;\n updatedAt?: number | null;\n }\n | {\n type: \"subagent_start\";\n agent: AgentIdentity;\n taskCallId?: string | null;\n }\n | {\n type: \"subagent_stop\";\n agent: AgentIdentity;\n taskCallId?: string | null;\n }\n | {\n type: \"context_warning\";\n /** Known values: \"info\" | \"warning\" | \"critical\". Typed as string for forward compat. */\n severity: string;\n utilizationPct: number;\n remainingTokens: number;\n windowTokens: number;\n inputTokens: number;\n message: string;\n }\n | { type: \"memory_recall\"; memories: MemoryRecord[] }\n | {\n type: \"memory_update\";\n /** Known values: \"created\" | \"updated\" | \"deleted\". */\n action: string;\n memoryId?: string | null;\n key?: string | null;\n namespace?: string | null;\n }\n | { type: \"desktop_stream\"; url: string; sandboxId?: string | null }\n | {\n type: \"attachment_staged\";\n attachmentId: string;\n filename: string;\n contentType?: string | null;\n sizeBytes?: number | null;\n }\n | {\n type: \"workspace_ready\";\n sandboxId: string;\n workspacePath?: string | null;\n }\n | {\n type: \"asset_created\";\n assetId: string;\n filename: string;\n url?: string | null;\n contentType?: string | null;\n }\n | {\n type: \"tool_approval_requested\";\n toolName: string;\n callId: string;\n arguments: Record<string, unknown>;\n riskLevel?: string | null;\n reason?: string | null;\n }\n | {\n type: \"tool_approval_granted\";\n toolName: string;\n callId: string;\n }\n | {\n type: \"tool_permission_denied\";\n toolName: string;\n callId: string;\n reason?: string | null;\n /** Known values: \"hook\" | \"rule\" | \"user\" | \"timeout\" | \"circuit_breaker\". */\n deniedBy?: string | null;\n }\n | {\n type: \"tool_harness_warning\";\n toolName: string;\n callId: string;\n message?: string | null;\n details?: Record<string, unknown> | null;\n }\n | {\n type: \"user_unavailable\";\n consecutiveTimeouts: number;\n toolName?: string | null;\n }\n | {\n type: \"prompt_suggestion\";\n suggestions: string[];\n }\n | {\n type: \"state_changed\";\n /**\n * Job lifecycle state. Known values from the backend today include\n * \"queued\" | \"running\" | \"waiting_for_tool\" | \"completed\" | \"failed\".\n * Typed as string for forward compat.\n */\n state: string;\n }\n\n // Generic fallthrough for unknown custom events\n | {\n type: \"custom\";\n name: string;\n data: Record<string, unknown>;\n };\n\n// =============================================================================\n// Domain models (unchanged from previous SDK version)\n// =============================================================================\n\nexport interface Conversation {\n id: string;\n title: string;\n messageCount: number;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface Message {\n id: string;\n conversationId: string;\n role: \"user\" | \"assistant\" | \"system\";\n content: string;\n parentId?: string;\n status: \"sending\" | \"streaming\" | \"complete\" | \"error\";\n createdAt: string;\n toolCalls?: ToolCallRequest[];\n}\n\nexport interface UIComponentsConfig {\n enabled: boolean;\n /** Protocol slug (e.g. \"a2ui\"). Null when disabled. */\n protocol: string | null;\n /** MIME type to match against embedded resources (e.g. \"application/json+a2ui\"). */\n mimeType: string | null;\n}\n\n/** One capability the agent either has or does not, right now. */\nexport interface AgentCapability {\n /** Stable key, e.g. `\"image\"`, `\"web\"`, `\"skills\"`. */\n key: string;\n enabled: boolean;\n}\n\nexport interface AgentStatus {\n isReady: boolean;\n llmConfigured: boolean;\n llmProvider?: string;\n llmModel?: string;\n message: string;\n uiComponents: UIComponentsConfig;\n /**\n * What the agent can do right now, so a client can offer a capability only\n * where it will actually work rather than failing on send.\n *\n * Empty when the backend does not report it (older servers) or when the agent\n * is not ready — treat an ABSENT key as \"not reported\", never as disabled.\n * Only capabilities with a real per-agent gate appear; always-on ones do not,\n * because a constant tells a client nothing.\n */\n capabilities: AgentCapability[];\n}\n\nexport interface AgentInfo {\n name: string;\n displayName: string;\n description: string;\n isOrchestrator: boolean;\n isEnabled: boolean;\n avatarUrl?: string;\n}\n\n// --- Team / Agent discovery (OIDC user-token surface) ---\n\nexport interface TeamSummary {\n id: string;\n name: string;\n slug: string;\n isDefault: boolean;\n /** Caller's role in this team (e.g. \"owner\", \"admin\", \"member\"). */\n role: string;\n}\n\n/**\n * A team-level agent (formerly \"project\") — the workspace a user opens to\n * chat. Distinct from `AgentInfo`, which describes the AI personas available\n * INSIDE an agent workspace (orchestrator + specialists).\n */\nexport interface TeamAgentSummary {\n id: string;\n name: string;\n teamId: string;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface SkillInfo {\n name: string;\n displayName: string;\n description: string;\n isEnabled: boolean;\n}\n\n// TodoItem is imported from custom-events.ts and re-exported above.\n\n// =============================================================================\n// Job and request types\n// =============================================================================\n\nexport interface JobCreateResponse {\n job_id: string;\n conversation_id: string;\n message_id: string;\n status: string;\n}\n\nexport interface JobStatus {\n jobId: string;\n status: string;\n createdAt: string | null;\n startedAt: string | null;\n completedAt: string | null;\n errorMessage: string | null;\n inputTokens: number;\n outputTokens: number;\n}\n\nexport interface ActiveJob {\n jobId: string | null;\n status: string;\n}\n\nexport interface JobSummary {\n jobId: string;\n status: string;\n replacesJobId: string | null;\n responseContent: Record<string, unknown> | null;\n metrics: Record<string, unknown> | null;\n createdAt: string | null;\n}\n\nexport interface FeedbackRequest {\n /** 1 for thumbs up, -1 for thumbs down. */\n rating: 1 | -1;\n comment?: string | null;\n}\n\nexport interface FeedbackResponse {\n id: string;\n jobId: string;\n rating: number;\n comment: string | null;\n createdAt: string;\n}\n\n/** Reasoning effort a caller may request for a turn (client-side model selection). */\nexport type ReasoningEffort = \"low\" | \"medium\" | \"high\";\n\n/**\n * The per-request model choice (client-side model selection). `provider` and\n * `model` are paired — send both or neither; when omitted, the server reuses the\n * conversation's last model or a connected-provider default.\n */\nexport interface ModelChoiceOptions {\n provider?: string;\n model?: string;\n reasoningEffort?: ReasoningEffort;\n temperature?: number;\n}\n\nexport interface ChatStreamRequest {\n message?: string;\n conversation_id?: string;\n mcp_manifest?: ToolDefinition[];\n enabled_mcp?: string[];\n continue_from_message?: string;\n resend_from?: string;\n upload_ids?: string[];\n agent_name?: string;\n plan_mode?: boolean;\n image_mode?: boolean;\n /**\n * Start a durable long-horizon goal for this run (goal mode). The backend mints\n * an agent_goal from this text and drives the run under a budget until the\n * objective is genuinely complete. A blank/omitted value runs a normal turn.\n */\n goal?: string;\n /**\n * Per-request model choice (client-side model selection), wire shape.\n * `provider` and `model` are paired; omit to reuse the thread's last model.\n */\n provider?: string;\n model?: string;\n reasoning_effort?: ReasoningEffort;\n temperature?: number;\n}\n\nexport interface ToolResultRequest {\n conversation_id: string;\n message_id: string;\n tool_results: ToolResult[];\n}\n\nexport interface ToolResult {\n call_id: string;\n tool_name: string;\n result: string;\n is_error: boolean;\n}\n\nexport type ToolApprovalDecision = \"allow\" | \"deny\";\nexport type ToolApprovalScope = \"once\" | \"conversation\" | \"always\";\n\nexport interface ToolApprovalRequest {\n job_id: string;\n call_id: string;\n decision: ToolApprovalDecision;\n scope: ToolApprovalScope;\n}\n\n/**\n * A remembered tool-permission grant belonging to the current end user.\n * Only `conversation`/`always` grants are ever stored (`once` is consumed at\n * approval time and never persisted).\n */\nexport interface ToolGrant {\n id: string;\n toolName: string;\n decision: ToolApprovalDecision;\n scope: Exclude<ToolApprovalScope, \"once\">;\n /** Set for `conversation`-scoped grants; `null` for `always`. */\n conversationId: string | null;\n createdAt: string;\n}\n\n/** A page of the current end user's own tool grants. */\nexport interface MyToolGrantsPage {\n grants: ToolGrant[];\n total: number;\n limit: number;\n offset: number;\n}\n\nexport interface ToolDefinition {\n name: string;\n description: string;\n parameters: Record<string, unknown>;\n}\n\nexport interface ToolCallRequest {\n callId: string;\n toolName: string;\n displayName?: string;\n description?: string;\n arguments: Record<string, unknown>;\n isClientTool: boolean;\n toolCategory?: string;\n iconUrl?: string;\n}\n\n// =============================================================================\n// SSE transport / raw parsing\n// =============================================================================\n\nexport interface StreamJobSSEOptions {\n url: string;\n headers: Record<string, string>;\n signal?: AbortSignal;\n fetchFn: typeof globalThis.fetch;\n}\n\nexport interface ChatStreamEvent {\n event: string;\n data: string;\n}\n\nexport interface ConversationEvent {\n seq: number;\n event: string;\n data: Record<string, unknown>;\n}\n\n// =============================================================================\n// Send / session options\n// =============================================================================\n\nexport interface SendOptions extends ModelChoiceOptions {\n conversationId?: string;\n enabledClientTools?: string[];\n uploadIds?: string[];\n agentName?: string;\n planMode?: boolean;\n /**\n * Attach the image-generation tool to this turn.\n *\n * Off by default and per-message, because generating costs the developer real\n * money at a third-party provider — the agent does not get to decide on its\n * own that a picture would be nice. The tool is not attached at all unless\n * this is set, so an agent with image generation configured still cannot\n * generate one on an ordinary turn.\n *\n * Gate the affordance on `AgentStatus.capabilities` (Astralform ≥ 0.61.0) —\n * an agent with no provider configured will simply have no tool to call.\n * The `image_mode` field itself is accepted from Astralform ≥ 0.59.0.\n */\n imageMode?: boolean;\n /**\n * Start a durable long-horizon goal for this run (goal mode). The text becomes\n * the goal's objective; the backend keeps the agent working under a budget until\n * it's complete. Omit for a normal turn.\n */\n goal?: string;\n}\n\n/**\n * A selectable model for one of the team's connected providers, from\n * `GET /v1/models`. Backs the client-side model picker.\n */\nexport interface ModelOption {\n provider: string;\n providerDisplay: string;\n model: string;\n thinking: boolean;\n tools: boolean;\n vision: boolean;\n thinkingMode: string;\n /** whether the model accepts a configurable reasoning effort (low/medium/high); false for always-on-thinking / think-tags models where effort is a no-op */\n supportsEffort: boolean;\n}\n\n// =============================================================================\n// Conversation assets (unchanged)\n// =============================================================================\n\nexport interface ConversationAsset {\n id: string;\n kind: \"upload\" | \"output\";\n originalName: string;\n mediaType: string;\n sizeBytes: number;\n workspacePath?: string;\n sourceMessageId?: string;\n agentName?: string;\n /**\n * Freshly-signed download/preview URL. Minted per response by the backend\n * (private `workspaces` bucket), so it reflects the current signature rather\n * than a link baked in when the asset was created. May be absent if signing\n * failed or the asset has no stored object.\n */\n url?: string;\n createdAt: string;\n}\n","/**\n * StreamManager — high-level conversation lifecycle coordinator.\n *\n * Sits on top of ChatSession and manages the state machine for\n * multi-conversation SSE streaming. Framework-agnostic: emits\n * typed events to registered handlers. Block construction is NOT\n * the SDK's concern — consumers build their own block tree from\n * the forwarded ``ChatEvent`` instances.\n *\n * import { ChatSession, StreamManager } from \"@astralform/js\";\n * const session = new ChatSession({ ... });\n * const manager = new StreamManager(session);\n * manager.on((event) => {\n * if (event.type === \"event\") {\n * // event.event is a typed ChatEvent — dispatch to your reducer\n * }\n * });\n * await manager.send(\"Hello\");\n */\nimport { planRestore } from \"./restore-plan\";\n\nimport type { ChatEvent, ModelChoiceOptions } from \"./types.js\";\nimport { ChatEventType } from \"./types.js\";\nimport type { ChatSession } from \"./session.js\";\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport type StreamState = \"idle\" | \"streaming\" | \"restoring\" | \"detached\";\n\nexport interface SendOptions extends ModelChoiceOptions {\n agentName?: string;\n uploadIds?: string[];\n planMode?: boolean;\n /**\n * Attach the image-generation tool to this turn. Per-message and off by\n * default — generating costs the developer real money at a third-party\n * provider. Gate the affordance on `AgentStatus.capabilities`.\n */\n imageMode?: boolean;\n /**\n * Start a durable long-horizon goal for this run (goal mode) — the text is the\n * goal objective the backend drives to completion. Omit for a normal turn.\n */\n goal?: string;\n}\n\nexport type StreamManagerEvent =\n | { type: \"stateChange\"; state: StreamState; conversationId: string | null }\n | { type: \"conversationChanged\"; conversationId: string | null }\n | {\n type: \"backgroundJobsChanged\";\n jobs: ReadonlyMap<string, string>;\n }\n | { type: \"event\"; conversationId: string | null; event: ChatEvent }\n | { type: \"versionsReady\"; conversationId: string; count: number };\n\ntype EventHandler = (event: StreamManagerEvent) => void;\n\n// =============================================================================\n// StreamManager\n// =============================================================================\n\nexport class StreamManager {\n private session: ChatSession;\n private _state: StreamState = \"idle\";\n private _activeConversationId: string | null = null;\n private _backgroundJobs = new Map<string, string>();\n private handlers: EventHandler[] = [];\n private unsub: (() => void) | null = null;\n\n constructor(session: ChatSession) {\n this.session = session;\n this.attach();\n }\n\n // ── Public state ──────────────────────────────────────────────\n\n get state(): StreamState {\n return this._state;\n }\n\n get activeConversationId(): string | null {\n return this._activeConversationId;\n }\n\n get backgroundJobs(): ReadonlyMap<string, string> {\n return this._backgroundJobs;\n }\n\n // ── Event subscription ────────────────────────────────────────\n\n on(handler: EventHandler): () => void {\n this.handlers.push(handler);\n return () => {\n this.handlers = this.handlers.filter((h) => h !== handler);\n };\n }\n\n private emit(event: StreamManagerEvent): void {\n for (const handler of this.handlers) {\n try {\n handler(event);\n } catch {\n // Don't let handler errors crash the manager\n }\n }\n }\n\n private setState(state: StreamState): void {\n this._state = state;\n this.emit({\n type: \"stateChange\",\n state,\n conversationId: this._activeConversationId,\n });\n }\n\n // ── Session event wiring ──────────────────────────────────────\n\n private attach(): void {\n this.unsub = this.session.on((event: ChatEvent) => {\n this.onSessionEvent(event);\n });\n }\n\n private onSessionEvent(event: ChatEvent): void {\n const convId = this.session.conversationId;\n\n // Forward every event to subscribers as a typed envelope\n this.emit({\n type: \"event\",\n conversationId: convId,\n event,\n });\n\n // Handle completion — message_stop is the terminal turn event.\n if (event.type === ChatEventType.MessageStop) {\n if (this._state === \"streaming\") {\n this.setState(\"idle\");\n }\n }\n }\n\n // ── Send ──────────────────────────────────────────────────────\n\n async send(content: string, options?: SendOptions): Promise<void> {\n if ((options?.provider == null) !== (options?.model == null)) {\n throw new Error(\n \"`provider` and `model` must be supplied together (client-side model selection).\",\n );\n }\n if (this._state === \"streaming\") return;\n\n // Auto-create conversation if none active\n if (!this._activeConversationId) {\n const id = await this.session.createNewConversation();\n this.setActiveConversation(id);\n }\n\n this.setState(\"streaming\");\n\n try {\n await this.session.send(content, {\n agentName: options?.agentName,\n uploadIds: options?.uploadIds,\n planMode: options?.planMode,\n imageMode: options?.imageMode,\n goal: options?.goal,\n provider: options?.provider,\n model: options?.model,\n reasoningEffort: options?.reasoningEffort,\n temperature: options?.temperature,\n });\n } catch {\n // AbortError from detach is expected\n }\n\n this.finalizeStream();\n }\n\n // ── Regenerate ────────────────────────────────────────────────\n\n async regenerate(): Promise<void> {\n if (this._state === \"streaming\") return;\n\n const userMsgs = this.session.messages.filter(\n (m: { role: string }) => m.role === \"user\",\n );\n const lastUserMsg = userMsgs[userMsgs.length - 1];\n if (!lastUserMsg) return;\n\n this.setState(\"streaming\");\n\n try {\n await this.session.resendFromCheckpoint(\n lastUserMsg.id,\n lastUserMsg.content,\n );\n } catch {\n // AbortError from detach is expected\n }\n\n this.finalizeStream();\n }\n\n // ── Switch conversation ───────────────────────────────────────\n\n /**\n * Switch the active conversation.\n *\n * ``opts.skipHistoryReplay`` is a fast path for consumers that CACHE a\n * restored conversation's rendered blocks: it moves the active pointer and\n * loads the message list (needed for send / regenerate context) but skips\n * the expensive event fetch + replay, and never enters the ``restoring``\n * state, so a consumer that clears its block view on ``restoring`` keeps\n * showing the cached history with no flash of a spinner.\n *\n * It still confirms there is no live job before skipping: the in-memory\n * background-job map is empty on a fresh instance (page reload) and blind to\n * jobs started in another tab/device, so the fast path always asks the server\n * (``getActiveJob``) and falls through to a full reconnect if one is running.\n * That one small request is the only cost it doesn't skip, so passing the\n * flag whenever you hold cached blocks is safe.\n */\n async switchTo(\n conversationId: string,\n opts?: { skipHistoryReplay?: boolean },\n ): Promise<void> {\n if (conversationId === this._activeConversationId) return;\n\n // Capture BEFORE the delete below: a background job THIS instance detached\n // must reconnect, so it can never take the cached fast path.\n const targetHadBackgroundJob = this._backgroundJobs.has(conversationId);\n\n // If streaming, detach (job keeps running in background)\n if (this._state === \"streaming\") {\n const oldConvId = this._activeConversationId;\n const jobId = this.session.currentJobId;\n if (oldConvId && jobId) {\n this._backgroundJobs.set(oldConvId, jobId);\n this.emit({\n type: \"backgroundJobsChanged\",\n jobs: this._backgroundJobs,\n });\n }\n this.session.detach();\n }\n\n // Clear background job for target (we're viewing it now)\n if (this._backgroundJobs.has(conversationId)) {\n this._backgroundJobs.delete(conversationId);\n this.emit({\n type: \"backgroundJobsChanged\",\n jobs: this._backgroundJobs,\n });\n }\n\n this.setActiveConversation(conversationId);\n\n if (opts?.skipHistoryReplay && !targetHadBackgroundJob) {\n // Cached fast path — but a job started before this instance existed (page\n // reload) or in another tab/device won't be in _backgroundJobs, so confirm\n // with the server that nothing is live before skipping the reconnect.\n let activeJobId: string | null = null;\n try {\n activeJobId = (await this.session.client.getActiveJob(conversationId))\n .jobId;\n } catch {\n // Network error — treat as no active job (best-effort, matches restore()).\n }\n if (!activeJobId) {\n // Consumer already holds the rendered blocks. Load the message list so\n // send/regenerate have their context, but skip the fetch + replay and\n // stay out of the ``restoring`` state.\n await this.session.loadConversation(conversationId);\n this.setState(\"idle\");\n return;\n }\n // A live job is running — fall through to a full restore(), which\n // reconnects to its stream.\n }\n\n await this.restore(conversationId);\n }\n\n // ── Create / rename / delete conversation ─────────────────────\n\n async createConversation(): Promise<string> {\n const id = await this.session.createNewConversation();\n this.setActiveConversation(id);\n return id;\n }\n\n /**\n * Rename a conversation. Purely a relabel — no active-conversation or\n * background-job bookkeeping to do, unlike delete, so this is a passthrough.\n */\n async renameConversation(id: string, title: string): Promise<void> {\n await this.session.renameConversation(id, title);\n }\n\n async deleteConversation(id: string): Promise<void> {\n await this.session.deleteConversation(id);\n this._backgroundJobs.delete(id);\n if (this._activeConversationId === id) {\n this._activeConversationId = null;\n this.emit({ type: \"conversationChanged\", conversationId: null });\n }\n }\n\n // ── Stop (explicit cancel) ────────────────────────────────────\n\n stop(): void {\n this.session.disconnect();\n this.setState(\"idle\");\n }\n\n // ── Cleanup ───────────────────────────────────────────────────\n\n destroy(): void {\n if (this.unsub) {\n this.unsub();\n this.unsub = null;\n }\n this.handlers = [];\n }\n\n // ── Internal: helpers ──────────────────────────────────────────\n\n private finalizeStream(): void {\n if (this._state === \"streaming\") {\n this.setState(\"idle\");\n }\n }\n\n // ── Internal: restore ─────────────────────────────────────────\n\n private async restore(conversationId: string): Promise<void> {\n this.setState(\"restoring\");\n\n // Check for active job\n let activeJobId: string | null = null;\n try {\n const res = await this.session.client.getActiveJob(conversationId);\n activeJobId = res.jobId;\n } catch {\n // Network error — assume no active job\n }\n\n if (activeJobId) {\n // Active job: load messages, reconnect to live SSE\n await this.session.loadConversation(conversationId);\n this.setState(\"streaming\");\n try {\n await this.session.reconnectToJob(activeJobId);\n } catch {\n // Stream ended or aborted\n }\n if (this._state === \"streaming\") {\n this.setState(\"idle\");\n }\n } else {\n // Completed: load the final messages once, then replay each turn.\n await this.session.loadConversation(conversationId);\n\n try {\n const jobs = await this.session.client.get<\n {\n job_id: string;\n status: string;\n message_id?: string | null;\n metrics?: Record<string, unknown>;\n }[]\n >(`/v1/conversations/${encodeURIComponent(conversationId)}/jobs`);\n const completedJobs = jobs.filter(\n (j: { status: string }) => j.status === \"completed\",\n );\n\n // User prompts aren't persisted in job_events — they live in the\n // messages table, so each turn has to be paired with the message that\n // started it. `job.message_id` is that link; planRestore also decides\n // where mid-run steers (user messages that start no job) and goal\n // continuations (jobs with no visible prompt) belong. See\n // restore-plan.ts for why pairing by index was wrong.\n const userMessages = this.session.messages.filter(\n (m) => m.role === \"user\",\n );\n const plan = planRestore({\n completedJobs: completedJobs.map((j) => ({\n job_id: j.job_id,\n message_id: j.message_id,\n })),\n userMessages: userMessages.map((m) => ({\n id: m.id,\n content: m.content,\n })),\n });\n\n // Fetch every turn's events up front, in PARALLEL. The backend strips\n // live-only deltas from this path, so each response is small; parallel\n // fetch collapses N serial round-trips into one wave. We still fetch\n // per job (not the whole conversation in one call) so superseded\n // regeneration versions stay available for version navigation — the\n // whole-conversation endpoint drops them.\n const eventLists = await Promise.all(\n completedJobs.map((job: { job_id: string }) =>\n this.session.client\n .getConversationEvents(conversationId, job.job_id)\n .catch(() => []),\n ),\n );\n\n const eventsByJobId = new Map(\n completedJobs.map((job, i) => [job.job_id, eventLists[i] ?? []]),\n );\n\n // Replay every step in one SYNCHRONOUS pass (no awaits between events\n // or turns), so the consumer batches the whole history into a single\n // render instead of re-typing it event by event. A steer replays as a\n // turn with no events: the bubble, and nothing after it.\n for (const step of plan) {\n if (step.kind === \"steer\") {\n this.session.replayTurn(\n conversationId,\n [],\n step.content,\n step.messageId,\n true,\n );\n continue;\n }\n this.session.replayTurn(\n conversationId,\n eventsByJobId.get(step.jobId) ?? [],\n step.content,\n step.messageId,\n );\n }\n\n if (completedJobs.length > 0) {\n this.emit({\n type: \"versionsReady\",\n conversationId,\n count: completedJobs.length,\n });\n }\n } catch {\n // Version chain loading failed — non-blocking\n }\n\n this.setState(\"idle\");\n }\n }\n\n // ── Internal: set active conversation ─────────────────────────\n\n private setActiveConversation(id: string): void {\n this._activeConversationId = id;\n this.emit({ type: \"conversationChanged\", conversationId: id });\n }\n}\n","// =============================================================================\n// Event replay — translate persisted wire events into ChatEvent sequences\n//\n// The backend persists wire events in the `job_events` table. When a\n// consumer needs to restore a conversation (page refresh, conversation\n// switch), it fetches these raw events and replays them through the same\n// ChatEvent pipeline used during live streaming.\n//\n// All wire → ChatEvent translation lives in `translate.ts`. This file just\n// adapts the persisted envelope shape (`{seq, event, data}`) and handles\n// user-message interleaving, which the backend doesn't record.\n// =============================================================================\n\nimport { translateWireEvent } from \"./translate.js\";\nimport type { ChatEvent, WireEvent } from \"./types.js\";\n\n/**\n * Raw SSE event shape returned by GET /v1/conversations/{id}/events.\n * Mirrors what JobEventWriter persists to the job_events table.\n */\nexport interface RawSseEvent {\n seq: number;\n event: string;\n data: Record<string, unknown>;\n /** Epoch ms when the event was persisted (from job_events.created_at). */\n created_at?: number;\n}\n\n/**\n * Build a ``WireEvent`` from the persisted envelope. The data payload is\n * authoritative (and always carries ``type`` in practice); the SSE event\n * name is a fallback for older rows that pre-date the v2 protocol.\n */\nfunction toWireEvent(raw: RawSseEvent): WireEvent | null {\n const type = (raw.data.type as string) || raw.event;\n if (!type || type === \"done\") return null;\n return { ...raw.data, type } as unknown as WireEvent;\n}\n\n/**\n * Map a raw SSE event (persisted in job_events) into the SDK ChatEvent\n * format. Returns an array because some rows (malformed / ``done`` sentinels)\n * map to zero events.\n */\nexport function mapSseToChat(raw: RawSseEvent): ChatEvent[] {\n const wire = toWireEvent(raw);\n if (!wire) return [];\n const event = translateWireEvent(wire);\n return event ? [event] : [];\n}\n\n/**\n * Replay persisted SSE events through the provided handler, interleaving\n * user messages from session.messages at the START of each turn (user\n * messages aren't persisted in job_events).\n *\n * The turn boundary is a change of ``job_id``, NOT ``message_start`` or\n * ``message_stop``. A completed job maps to exactly one user turn — but a\n * single job can contain several ``message_start``/``message_stop`` pairs (a\n * tool-use loop: LLM call → tool result → LLM call again), so neither event\n * reliably delimits turns. And within a job some events precede the first\n * ``message_start`` (e.g. ``memory_recall``, emitted during prompt prep), so\n * gating the user block on ``message_start`` would replay them above the\n * user's own message. Keying off ``job_id`` injects the prompt once per job,\n * before its first event — matching how the restore path\n * (``stream-manager.ts#restore`` → ``session.ts#replayTurn``) leads each turn\n * with its own prompt.\n */\nexport function replayEvents(\n sseEvents: RawSseEvent[],\n userMessages: { role: string; content: string }[],\n handleEvent: (event: ChatEvent) => void,\n addBlock: (block: { type: \"user\"; id: string; content: string }) => void,\n): void {\n const userMsgs = userMessages.filter((m) => m.role === \"user\");\n let userIdx = 0;\n let currentJobId: string | null = null;\n\n for (const raw of sseEvents) {\n const type = (raw.data.type as string) || raw.event;\n if (!type || type === \"done\") continue;\n\n // A new job_id starts a new user turn — inject its prompt before the\n // job's first event. Events without a job_id stay within the current\n // turn (never a boundary), so a stray untagged event can't misfire.\n const jobId = (raw.data.job_id as string | undefined) ?? null;\n if (jobId !== null && jobId !== currentJobId) {\n currentJobId = jobId;\n if (userIdx < userMsgs.length) {\n addBlock({\n type: \"user\",\n id: `replay_user_${userIdx}`,\n content: userMsgs[userIdx]!.content,\n });\n userIdx++;\n }\n }\n\n for (const ce of mapSseToChat(raw)) {\n handleEvent(ce);\n }\n }\n}\n","// =============================================================================\n// Embedded Resource detection — protocol-agnostic\n// =============================================================================\n//\n// The backend can emit rich UI surfaces (A2UI, and future protocols) by\n// wrapping a tool result in an MCP-style embedded resource:\n//\n// {\n// \"_embedded_resource\": true,\n// \"mime_type\": \"application/json+a2ui\",\n// \"uri\": \"a2ui://surface/<id>\",\n// \"payload\": { ...protocol-specific... }\n// }\n//\n// The SDK stays protocol-agnostic: it exposes a detector/parser so\n// frontends can route the payload to a registered renderer for the\n// matching MIME type. The SDK itself never imports a renderer.\n// =============================================================================\n\n/**\n * Parsed shape of an MCP-style embedded resource, as emitted by the\n * backend's UI component tools (``render_surface``, ``update_surface``).\n */\nexport interface EmbeddedResource {\n /** IANA-style MIME type, e.g. \"application/json+a2ui\". */\n mimeType: string;\n /** Opaque URI (e.g. \"a2ui://surface/my-form\"). */\n uri: string;\n /** Protocol-specific payload — shape depends on ``mimeType``. */\n payload: Record<string, unknown>;\n}\n\n/**\n * Detect whether a value is an embedded resource wrapper. The check is\n * purposely loose — any object with ``_embedded_resource: true`` is\n * accepted, which matches the MCP convention and keeps the SDK\n * forward-compatible with future protocols.\n */\nexport function isEmbeddedResource(value: unknown): value is {\n _embedded_resource: true;\n mime_type?: string;\n uri?: string;\n payload?: Record<string, unknown>;\n} {\n return (\n typeof value === \"object\" &&\n value !== null &&\n (value as { _embedded_resource?: unknown })._embedded_resource === true\n );\n}\n\n/**\n * Parse an embedded resource from arbitrary tool output. Returns\n * ``null`` when the value isn't an embedded resource or is malformed.\n *\n * Accepts either an object (the preferred wire format) or a JSON\n * string containing one (defense in depth — some transport layers\n * historically serialized tool results before sending).\n */\nexport function parseEmbeddedResource(value: unknown): EmbeddedResource | null {\n let candidate: unknown = value;\n if (typeof candidate === \"string\") {\n // Only try JSON.parse if it plausibly looks like a JSON object\n // starting with `{`. Avoids pathological input.\n const trimmed = candidate.trim();\n if (!trimmed.startsWith(\"{\")) return null;\n try {\n candidate = JSON.parse(trimmed);\n } catch {\n return null;\n }\n }\n if (!isEmbeddedResource(candidate)) return null;\n const mimeType = candidate.mime_type;\n const uri = candidate.uri;\n const payload = candidate.payload;\n if (typeof mimeType !== \"string\" || !mimeType) return null;\n if (typeof uri !== \"string\" || !uri) return null;\n if (!payload || typeof payload !== \"object\") return null;\n return {\n mimeType,\n uri,\n payload: payload as Record<string, unknown>,\n };\n}\n"],"mappings":"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","names":[]}
|