/** * Shared AI-assistant transport types (Layer 1, fe-libs). * * One implementation of the sandbox-backed assistant transport for ALL products. * Previously each `microfe-` shipped a near-identical ~600-900 line copy * of this logic; this module centralizes it behind a config-driven factory. The * only genuine per-product inputs are the product id and its `gatherPageContext`. */ /** * The modes fe-libs itself defines — and the only ones whose AUTHORITY it has * audited. Every authority decision in `api.ts` (user tokens for `assistant`, * the confirm-before-write gate for `api-calls`) is an equality test against one * of these ids, so nothing outside this union can ever be handed a user token or * a write path. */ export type BuiltInAssistantMode = 'general' | 'api-calls' | 'vibe-plugins' | 'assistant'; declare const productAssistantModeBrand: unique symbol; /** * A backend mode id a PRODUCT declares through its seam (the `modes` it gives * `AssistantProductProvider`): a persona the sandbox service and the agent image * know, which shared code deliberately does not name. * * ★★ Branded, so the only way to hold one is `declareAssistantMode(id)`, which * validates it. The id lands in a sandbox name, its metadata and an i18n key, so * an unchecked string is not acceptable there — and a typo of a built-in id must * not compile silently as a "new" mode. * * ★ Why fe-libs does not keep a list of these: the sandbox service is the * authority on which mode ids exist (it answers an unknown one with * `Unsupported assistant mode`), and a second hard-coded list is exactly what * once rejected a mode after it had shipped in the agent image. Shared code also * names no product (`noProductNames.test.ts`), and a product's mode id usually * carries the product's name. */ export type ProductAssistantMode = string & { readonly [productAssistantModeBrand]: true; }; export type AssistantMode = BuiltInAssistantMode | ProductAssistantMode; /** Local mirror of a product's PageContext; the transport forwards a subset. */ export interface PageContext { currentUrl?: string; currentPage?: string; entityType?: string; entityId?: string; platform?: string; version?: string; viewportSize?: string; [key: string]: unknown; } export interface AssistantSandboxAuthContext { accessToken?: string | null; workspaceToken?: string | null; userId?: string | null; organizationId?: string | null; } export interface AssistantRawMessagePart { type?: string; text?: string; tool?: string; state?: { status?: string; input?: unknown; output?: unknown; raw?: unknown; }; /** * Inbound media (an image or file the agent produced). Present only once the * `ai-assistant-*` bridge emits non-text parts; today's agent returns * `text | tool` only, so these stay undefined and every reader degrades to the * text path. Typed here so consumers can render media without a fe-libs bump * on the day the bridge starts sending it. */ mimeType?: string; url?: string; /** Inline bytes as bare base64 (no `data:` prefix) — mirrors the outbound shape. */ data?: string; filename?: string; } /** * One piece of a multimodal prompt (outbound). The external `ai-assistant-*` * bridge translates these into native `opencode` message parts. * * Carry the bytes either by reference (`url`) or inline (`data`, bare base64 with * no `data:` prefix). Inline is the reliable option for images: a browser-issued * storage URL is frequently cookie- or CF-gated and therefore unreachable from * inside the sandbox container, whereas inline bytes need no egress at all. */ export type AssistantPromptPart = { type: 'text'; text: string; } | { type: 'image'; mimeType: string; url?: string; data?: string; name?: string; } | { type: 'file'; mimeType: string; url?: string; data?: string; name?: string; }; export interface AssistantRawMessage { info?: { id?: string; role?: string; providerID?: string; modelID?: string; tokens?: { input: number; output: number; }; time?: { created?: number; completed?: number; }; }; parts?: AssistantRawMessagePart[]; } export interface AssistantArtifact { name: string; path: string; type: string; size: number; downloadPath: string; } export interface AssistantWidgetMessage { id: string; role: 'user' | 'assistant'; content: string; pending?: boolean; } /** What a product passes to build its transport. Only `product` is required. */ export interface SandboxAssistantConfig { /** AI_ASSISTANT_PRODUCT for the sandbox container (drives per-product docs). */ product: string; /** * Per-product page-context gatherer; a minimal window-based default is used if * absent. Typed as `() => object` (not the exported `PageContext`) so a * product's own `PageContext` interface — which lacks an index signature — is * accepted without friction. The transport reads it as a plain record. */ gatherPageContext?: () => object; /** Session mode. Default 'assistant' (combined docs + api-calls). */ mode?: AssistantMode; /** Enable AI-credit metering (preflight + post-turn report). Fails open. */ enableCredits?: boolean; /** Overall per-turn deadline. Default 420_000ms. */ streamBudgetMs?: number; /** * Report the whole run's narration, joined oldest first, instead of only its * newest message (BC1, BOFF-7334). Default false. * * ★★ The widget's counterpart to `AssistantModeConfig.accumulatesNarration`, * declared here for the same reason `streamBudgetMs` is: this transport takes * its per-mode behaviour from the product's config rather than resolving * capabilities itself. Off by default because latest-only is what every * product wrapping this transport renders today — and because it changes what * is STORED as the turn's answer, not merely what is shown. */ accumulateNarration?: boolean; /** Sandbox TTL at create (retries at 600 on validation error). Default 3600. */ ttlSeconds?: number; /** Optional prod-runtime detector for image-tag pinning (hostname-based). */ isProdRuntime?: () => boolean; /** Called when a quota error is detected (widget/panel surfaces it). */ onQuotaExhausted?: (error: unknown) => void; /** Called after a successful post-turn credit charge. */ onCreditsUpdated?: () => void; } export interface AssistantSendArgs { /** * The turn as plain text. ALWAYS sent, even alongside `parts` — an * un-upgraded bridge ignores what it does not understand and still answers * from this, so a multimodal client degrades to text instead of failing. */ prompt: string; /** * Optional multimodal turn. When present it is AUTHORITATIVE for a bridge * that understands it, and `prompt` is the text-only fallback. Omit entirely * for text-only turns — the wire body then looks exactly as it always has. */ parts?: AssistantPromptPart[]; onProgress: (partialText: string) => void; signal: AbortSignal; } export type PrewarmLevel = 'probe' | 'full'; /** The transport contract consumed by the fe-libs AssistantWidget. */ export interface SandboxAssistantTransport { sendPrompt: (args: AssistantSendArgs) => Promise<{ text: string; }>; /** Fire-and-forget warm-up (single-flighted). 'probe' never creates a sandbox. */ prewarm: (level?: PrewarmLevel) => void; /** Stop the keep-warm heartbeat (widget closed / tab hidden). */ suspend: () => void; } export interface StreamAccess { url: string; expiresAt: number; } export interface AssistantSessionInfo { id: string; mode: string; } export interface DownloadedAssistantArtifact { filename: string; mimeType: string; contentBase64: string; } export {}; //# sourceMappingURL=types.d.ts.map