import { AssistantMode, AssistantPromptPart, AssistantRawMessage, AssistantSandboxAuthContext, AssistantArtifact, AssistantSessionInfo, DownloadedAssistantArtifact, StreamAccess } from './types'; /** * Does a proxied request into a sandbox of this mode carry the signed-in user's * own tokens (`forwardedAuthToken` / `forwardedWorkspaceToken`)? * * ★★ Only Ask (`assistant`). It is the one mode that authenticates the * in-sandbox agent with the user's OWN identity (read-only, enforced by * AI_ASSISTANT_READONLY — see `buildAssistantSandboxEnv`). Every other mode must * never receive them: * - `api-calls` (Take action) acts through a delegated per-workspace app; the * sandbox service mints and sends those credentials itself and never reads the * forwarded tokens. * - `vibe-plugins`, `general` and every PRODUCT-declared mode (see * `declareAssistantMode`) make no platform GraphQL calls at all. Their agent * has bash, so a user token handed to one is a token a prompt injection can * read and replay as the user (BOFF-7304). A product cannot opt a mode into * tokens: the test below is equality with the built-in id, not a capability. * * ★ Fails closed: an unknown, empty or absent mode forwards nothing. The sandbox * service applies the same rule server-side from the sandbox's recorded mode * (wspace-sandbox-svc#87); this is the client half, so the tokens never leave * the browser for a sandbox that has no use for them. */ export declare function forwardsUserTokensToSandbox(mode: AssistantMode | null | undefined): boolean; export declare function findExistingSandbox(workspaceId: string, product: string, mode: AssistantMode, isProd: boolean, authContext: AssistantSandboxAuthContext, excludedSandboxIds?: ReadonlySet): Promise; export interface ExistingAssistantSandbox { id: string; name: string; status: string; metadata: Record | null; template: string; createdBy: string; } /** Pure reuse selector, kept separate so quarantine behavior is regression-testable. */ export declare function selectExistingAssistantSandbox(sandboxes: ExistingAssistantSandbox[], product: string, mode: AssistantMode, _isProd: boolean, authContext: Pick, excludedSandboxIds?: ReadonlySet): string | null; /** * Does this mode write through the gateway, and therefore need the * confirm-before-write gate? * * Single source of truth: the env builder, the metadata stamped at create time * and the reuse check all derive from this, so they cannot drift into a state * where a sandbox is write-capable but not gated. */ export declare function modeRequiresConfirmWrites(mode: AssistantMode): boolean; /** * Environment for an assistant sandbox container. * * Extracted and exported so the authority coupling below is unit-testable: * a mode that can write must arrive with its brake on, and that invariant is * too easy to break silently from inside an async network call. */ export declare function buildAssistantSandboxEnv(product: string, mode: AssistantMode, workspaceId: string, authContext: Pick): Array<{ name: string; value: string; }>; export declare function createAssistantSandbox(product: string, mode: AssistantMode, workspaceId: string, isProd: boolean, ttlSeconds: number, authContext: AssistantSandboxAuthContext): Promise; /** * Wait for RUNNING. Prefers the svc long-poll (waitForSandboxRunning); on an * older gateway that field errors → falls back to 2s getSandbox polling. Keeps * the same terminal-error strings either way. */ export declare function waitForSandboxRunning(sandboxId: string, workspaceId: string, authContext: AssistantSandboxAuthContext, maxWaitMs?: number): Promise; export declare function waitForAssistantServiceReady(sandboxId: string, workspaceId: string, mode: AssistantMode, authContext: AssistantSandboxAuthContext, maxWaitMs?: number): Promise; export declare function createAssistantSession(sandboxId: string, workspaceId: string, mode: AssistantMode, authContext: AssistantSandboxAuthContext): Promise<{ sessionId: string; }>; /** * Tell the sandbox the user approved a pending write. * * ★★ This is the frontend half of the write gate. The sandbox no longer puts * the confirmation token in anything the model can read — the refusal carries a * reference that NAMES the pending change and authorises nothing — so the only * way a refused write becomes executable is this call, made because a person * pressed a button. The model is not in this path. * * ★ The reference is not a secret and is validated as hex by the parser before * it reaches here, so it can safely sit in the URL. * * ★ Throws on failure rather than resolving quietly. A 404 means the pending * write expired or was already spent, and the user must see that their approval * did NOT land — silently swallowing it would leave them believing a change was * authorised when it was not. */ export declare function approvePendingWrite(sandboxId: string, workspaceId: string, reference: string, mode: AssistantMode, authContext: AssistantSandboxAuthContext): Promise; /** * Our own assistant sandboxes in this workspace that are for a DIFFERENT mode. * * ★★ Pure and exported so the ownership rules below are testable without a * network. Getting these wrong stops somebody else's container. */ export declare function selectSupersededAssistantSandboxes(sandboxes: readonly ExistingAssistantSandbox[], product: string, mode: AssistantMode, _isProd: boolean, authContext: AssistantSandboxAuthContext, /** * Sandboxes proven dead this session. * * ★ Same parameter, same position and same default as the reuse selector, so * the two keep identical shapes — which is what makes "superseded is the * complement of reusable" checkable by reading them side by side. */ excludedSandboxIds?: ReadonlySet): string[]; /** * Free the workspace's sandbox slot before provisioning a different mode. * * ★★ THE BUG THIS FIXES. Sandbox reuse keys on mode and image tag, so "Ask" and * "Take action" need DIFFERENT containers — but a Free workspace holds exactly * ONE concurrent sandbox, and nothing ever released it. Every session provisions * a read-only Ask sandbox, the panel heartbeat then extends its TTL for as long * as the panel is open, and switching to "Take action" asks for a second * container against a quota of one. It fails, and the failure surfaces as * "You've reached your sandbox limit on the current plan" — which reads like a * billing problem and is actually us holding our own slot. * * So: before provisioning, stop the assistant sandboxes that can no longer be * reused. This is not an optimisation, it is the difference between "Take * action" working and not working on the plan almost every workspace is on. * * ★ Best-effort by design. A failure to stop is not a failure to answer: the * provision that follows may still succeed (a paid workspace has room), and if * it does not, the quota error is the right thing for the user to see. Throwing * here would replace a specific, actionable error with a vaguer one. * * ★ Consequence worth stating: with a one-slot quota, a second tab in the other * mode loses its sandbox. That is forced by the quota rather than chosen here — * the alternative is that the second mode never works at all — and the tab * recovers by provisioning again on its next turn. */ export declare function releaseSupersededAssistantSandboxes(workspaceId: string, product: string, mode: AssistantMode, isProd: boolean, authContext: AssistantSandboxAuthContext, /** Sandboxes proven dead this session — released even if they still match. */ excludedSandboxIds?: ReadonlySet): Promise; /** * Terminalize a sandbox whose Cloudflare data plane was reaped while its DB * record remained RUNNING. Invoked only for Cloudflare's exact connection- * refused signature before provisioning the replacement. */ export declare function stopReapedAssistantSandbox(sandboxId: string, workspaceId: string, authContext: AssistantSandboxAuthContext): Promise; /** * Assemble the `/sessions/:id/prompt-async` request body. Pure + exported so the * backward-compatibility contract is unit-testable without touching the network. * * The contract, deliberately additive: * - `prompt` is ALWAYS present. A bridge that predates multimodal ignores the * unknown `parts` key and answers from `prompt` — degradation, not failure. * - `parts` appears ONLY when there is at least one. A text-only turn therefore * produces a byte-identical body to the pre-multimodal client. * - When a bridge does understand `parts`, `parts` wins and `prompt` is the * text-only fallback. */ export declare function buildPromptRequestBody(args: { mode: AssistantMode; prompt: string; pageContext: Record | undefined; parts?: AssistantPromptPart[]; }): Record; export interface AssistantPromptGatewayRequest { sandboxId: string; workspaceId: string; sessionId: string; mode: AssistantMode; prompt: string; pageContext: Record | undefined; authContext: AssistantSandboxAuthContext; parts?: AssistantPromptPart[]; } /** The actual unbatched GraphQL body, including context and forwarded-token overhead. */ export declare function buildPromptGatewayRequest(args: AssistantPromptGatewayRequest): { query: string; operationName: string; variables: Record; }; export declare function promptGatewayRequestBytes(args: AssistantPromptGatewayRequest): number; export declare function sendAssistantPromptAsync(sandboxId: string, workspaceId: string, sessionId: string, prompt: string, mode: AssistantMode, pageContext: Record | undefined, authContext: AssistantSandboxAuthContext, /** Multimodal extras. Optional + trailing, so every existing call site is unaffected. */ options?: { parts?: AssistantPromptPart[]; }): Promise<{ messageID?: string; }>; export declare function getAssistantMessages(sandboxId: string, workspaceId: string, sessionId: string, mode: AssistantMode, authContext: AssistantSandboxAuthContext, opts?: { limit?: number; sinceMs?: number; }): Promise; /** * Ask the svc to mint a signed SSE stream URL. Returns null when unavailable * (older backend / preview data plane off) → caller falls back to polling. */ export declare function getAssistantStreamAccess(sandboxId: string, workspaceId: string, sessionId: string, mode: AssistantMode, authContext: AssistantSandboxAuthContext): Promise; export declare function extendAssistantSandboxTTL(sandboxId: string, workspaceId: string, additionalSeconds: number, authContext: AssistantSandboxAuthContext): Promise; export declare function sendHeartbeat(sandboxId: string, workspaceId: string, sessionId: string, mode: AssistantMode, authContext: AssistantSandboxAuthContext): Promise; /** * Cancel the turn currently running in this sandbox session. * * ★ The sandbox has exposed `POST /sessions/:id/abort` since the original * server, and it does more than drop the reply: it calls `abandonSession` * before `session.abort`, so the provider-failover chain stops too and a * cancelled turn is not quietly re-answered by the next provider. Nothing in * this package called it — every other route here (`prompt-async`, `messages`, * `stream-url`, `heartbeat`, `artifacts`) was wired and this one was not, which * is why "nothing cancels the model" has been written into several comments as * though it were a property of the system rather than a gap in the client. * * ★★ Do NOT call this directly from an interrupt handler. Abort is scoped to * the SESSION, which outlives the turn, so a late request cancels whatever is * running when it arrives. Gate every call on `planTurnAbort`. * * Never throws: a failed cancellation must not take the UI down with it. The * boolean is the sandbox's own `{ aborted: true }`, so a caller can tell * "cancelled" from "asked, and something went wrong". */ export declare function abortAssistantTurn(sandboxId: string, workspaceId: string, sessionId: string, mode: AssistantMode, authContext: AssistantSandboxAuthContext): Promise; export declare function getAssistantArtifacts(sandboxId: string, workspaceId: string, sessionId: string, mode: AssistantMode, authContext: AssistantSandboxAuthContext): Promise; export declare function getAssistantSession(sandboxId: string, workspaceId: string, sessionId: string, mode: AssistantMode, authContext: AssistantSandboxAuthContext): Promise; export declare function downloadAssistantArtifact(sandboxId: string, workspaceId: string, sessionId: string, artifactPath: string, /** * ★ `undefined` is accepted, and forwards no user tokens. A message persisted * before its metadata carried `mode` cannot say which sandbox made it. * Artifacts are only ever collected for a mode that `producesArtifacts` — * `vibe-plugins` or a product-declared mode — and none of those needs a user * token; the download is authorised by the sandbox's proxy token. */ mode: AssistantMode | undefined, authContext: AssistantSandboxAuthContext): Promise; export declare function checkAssistantAiCredits(workspaceId: string, authContext: AssistantSandboxAuthContext): Promise; export declare function reportAssistantAiTokenUsage(workspaceId: string, sessionId: string, messageId: string, model: string | undefined, tokens: { input: number; output: number; }, authContext: AssistantSandboxAuthContext): Promise; //# sourceMappingURL=api.d.ts.map