import { randomUUID } from "node:crypto"; import { ATTEMPT_TOOL_CATALOG_VERSION, ATTEMPT_TOOL_CATALOG_MAX_BYTES, AttemptToolCall, AttemptToolCatalog, AttemptToolResult, CodemodeCallSubmission, CodemodeOperation, CodemodeDispatchAck, CodemodeDispatchRequest, type AttemptToolCall as AttemptToolCallValue, AttemptToolCaller, type AttemptToolCatalog as AttemptToolCatalogValue, type AttemptToolCatalogEntry as AttemptToolCatalogEntryValue, type AttemptToolIdentity, type AttemptToolResult as AttemptToolResultValue, type CodemodeDispatchAck as CodemodeDispatchAckValue, type CodemodeDispatchRequest as CodemodeDispatchRequestValue, type CodemodeOperation as CodemodeOperationValue, } from "@opengeni/contracts"; import { ToolGateway, ToolGatewayApprovalRequiredError as AttemptToolApprovalRequiredError, ToolGatewayCatalogIntegrityError as AttemptToolCatalogIntegrityError, ToolGatewayCatalogStaleError as AttemptToolCatalogStaleError, ToolGatewayCatalogTooLargeError as AttemptToolCatalogTooLargeError, ToolGatewayInputValidationError as AttemptToolInputValidationError, ToolGatewayOutputValidationError as AttemptToolOutputValidationError, ToolGatewayPathCollisionError as AttemptToolPathCollisionError, ToolGatewayToolNotFoundError as AttemptToolNotFoundError, digestCanonicalJson, digestToolGatewayDefinition, prepareToolGatewayDefinitions, type PreparedToolGatewayCall, type ToolGatewayArgumentResolver, type ToolGatewayCallLifecycle, type ToolGatewayDefinition, } from "@opengeni/tool-gateway"; import { siteHostWorkspacePath } from "@opengeni/contracts/site-session-http"; import { materializeCodemodeImages } from "./images"; export type { AttemptToolCatalog, AttemptToolCatalogEntry } from "@opengeni/contracts"; export type AttemptToolScope = Pick< AttemptToolCatalogValue, "accountId" | "workspaceId" | "sessionId" | "turnId" | "attemptId" | "executionGeneration" >; export type AttemptToolExecutionContext = { operationId: string; caller: AttemptToolCaller; /** Trusted in-process SDK correlation; not operation identity or approval authority. */ sourceCallId?: string; /** In-process transport metadata; never part of catalog identity or digest. */ transportMeta?: Record | null; signal?: AbortSignal; }; export type AttemptToolDefinition = Omit & { /** Optional human-readable path. Unsafe/colliding segments are normalized. */ codemodePath?: readonly string[]; /** In-process execution lifecycle shared by model MCP and Codemode. */ lifecycle?: ToolGatewayCallLifecycle; execute: ( args: Record, context: AttemptToolExecutionContext, ) => Promise | AttemptToolResultValue; }; export type AttemptToolAuthorization = (input: { call: AttemptToolCallValue; entry: AttemptToolCatalogEntryValue; }) => Promise | void; export type CreateAttemptToolEnvironmentInput = { scope: AttemptToolScope; generation: number; /** Trusted host's accepted native permission set, never caller input. */ firstPartyMcpPermissions?: AttemptToolCatalogValue["firstPartyMcpPermissions"]; definitions: readonly AttemptToolDefinition[]; createdAt?: Date; authorize?: AttemptToolAuthorization; /** Host-only approval projection for one exact model invocation. */ confirmModelApproval?: (input: { modelName: string; subjectId: string }) => boolean; /** Trusted secret-reference resolution; runs after approval, before execution. */ resolveArguments?: ToolGatewayArgumentResolver; }; export type ModelAttemptToolCall = { operationId?: string; /** Exact SDK call id supplied by the host, never inferred from arguments or metadata. */ sourceCallId?: string; modelName: string; arguments: Record; subjectId: string; transportMeta?: Record | null; signal?: AbortSignal; }; export { AttemptToolCatalogStaleError, AttemptToolNotFoundError, AttemptToolApprovalRequiredError, AttemptToolCatalogIntegrityError, AttemptToolCatalogTooLargeError, AttemptToolInputValidationError, AttemptToolOutputValidationError, AttemptToolPathCollisionError, }; export class CodemodeTransportError extends Error { readonly code = "codemode_transport_error"; readonly remoteCode: string | null; readonly retryable: boolean | null; readonly outcomeUnknown: boolean | null; readonly details: Readonly> | null; constructor( message: string, readonly status: number | null = null, options: { code?: string; retryable?: boolean; outcomeUnknown?: boolean; details?: Readonly>; } = {}, ) { super(message); this.name = "CodemodeTransportError"; this.remoteCode = options.code ?? null; this.retryable = options.retryable ?? null; this.outcomeUnknown = options.outcomeUnknown ?? null; this.details = options.details ?? null; } } export class CodemodeOperationError extends Error { constructor( readonly operation: CodemodeOperationValue, readonly code: string, ) { super(operation.errorMessage ?? `Codemode operation ${operation.state}`); this.name = "CodemodeOperationError"; // Preserve the public operation property without dumping protected arguments into error logs. Object.defineProperty(this, "operation", { value: operation, enumerable: false }); } } /** Compact receipt: payloads remain in the protected operation journal. */ export class CodemodeApprovalPendingError extends Error { readonly code = "codemode_approval_pending"; readonly state = "waiting_for_approval"; constructor( readonly operationId: string, readonly approvalRequestId: string | undefined, ) { super( `Operation ${operationId} is waiting for approval. After review, read or resume this handle; do not submit its arguments again.`, ); this.name = "CodemodeApprovalPendingError"; } } export type CodemodeTokenProvider = string | (() => string | Promise); export type CodemodeClientOptions = { /** `/v1/workspaces/:workspaceId/codemode` base URL. */ baseUrl: string; token: CodemodeTokenProvider; fetch?: typeof globalThis.fetch; pollIntervalMs?: number; timeoutMs?: number; }; export type CodemodeCallOptions = { operationId?: string; /** Optional public entry pin, checked again after a pre-submission stale refresh. */ expectedDefinitionDigest?: string; /** Stops client observation only; it never cancels an already-created server operation. */ signal?: AbortSignal; timeoutMs?: number; }; export type CodemodeToolFunction = ( argumentsValue?: Record, options?: CodemodeCallOptions, ) => Promise; export type CodemodeToolResult = AttemptToolResultValue; export class CodemodeToolCallError extends Error { readonly code: string; readonly retryable: boolean; constructor(readonly result: AttemptToolResultValue) { const error = structuredToolError(result); super(error.message); this.name = "CodemodeToolCallError"; this.code = error.code; this.retryable = error.retryable; } } export class CodemodeToolContractError extends Error { readonly code = "invalid_tool_result"; constructor(message: string) { super(message); this.name = "CodemodeToolContractError"; } } export interface CodemodeTools { [key: string]: CodemodeTools | CodemodeToolFunction; } /** Persistent, idempotent client for one exact sandbox attempt bearer. */ export class CodemodeClient { private readonly baseUrl: string; private readonly fetchImpl: typeof globalThis.fetch; private readonly pollIntervalMs: number; private readonly timeoutMs: number; private catalogSnapshot: AttemptToolCatalogValue | null = null; constructor(private readonly options: CodemodeClientOptions) { this.baseUrl = options.baseUrl.replace(/\/+$/u, ""); if (!/^https?:\/\//u.test(this.baseUrl)) { throw new Error("Codemode baseUrl must be an absolute HTTP(S) URL"); } this.fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis); this.pollIntervalMs = boundedPositiveInteger(options.pollIntervalMs ?? 500, 50, 30_000); this.timeoutMs = boundedPositiveInteger(options.timeoutMs ?? 10 * 60_000, 1_000, 60 * 60_000); } async catalog( options: { refresh?: boolean; signal?: AbortSignal } = {}, ): Promise { if (this.catalogSnapshot && !options.refresh) return this.catalogSnapshot; const response = await this.request("/catalog", { method: "GET", ...(options.signal ? { signal: options.signal } : {}), }); const catalog = parseVerifiedAttemptToolCatalog(await response.json()); this.catalogSnapshot = catalog; return catalog; } async tools(options: { refresh?: boolean; signal?: AbortSignal } = {}): Promise { return compileCodemodeTools(await this.catalog(options), this); } async call( identity: AttemptToolIdentity, argumentsValue: Record = {}, options: CodemodeCallOptions = {}, ): Promise { return ( await this.callResolved( (catalog) => catalog.entries.find( (entry) => entry.identity.serverId === identity.serverId && entry.identity.toolName === identity.toolName, ) ?? null, argumentsValue, options, ) ).result; } private async callResolved( resolveEntry: (catalog: AttemptToolCatalogValue) => AttemptToolCatalogEntryValue | null, argumentsValue: Record, options: CodemodeCallOptions, ): Promise<{ result: AttemptToolResultValue; entry: AttemptToolCatalogEntryValue }> { let catalog = await this.catalog(options.signal ? { signal: options.signal } : {}); let entry = resolveEntry(catalog); if (!entry) throw new AttemptToolNotFoundError(); const assertDefinition = () => { if ( options.expectedDefinitionDigest !== undefined && options.expectedDefinitionDigest !== digestToolGatewayDefinition(catalog, entry!) ) throw new CodemodeTransportError("The tool definition changed before execution", 409, { code: "tool_definition_stale", retryable: true, outcomeUnknown: false, }); }; assertDefinition(); const operationId = options.operationId ?? randomUUID(); const deadline = Date.now() + boundedPositiveInteger(options.timeoutMs ?? this.timeoutMs, 1_000, 60 * 60_000); let submitted = false; let staleRefreshAttempted = false; let operation: CodemodeOperationValue | null = null; let nextNotifyAt = 0; while (true) { throwIfAborted(options.signal); if (Date.now() >= deadline) { throw new CodemodeTransportError( `Codemode operation ${operationId} did not settle before the client deadline`, ); } const shouldNotify = !submitted || ((operation?.state === "queued" || operation?.state === "running") && Date.now() >= nextNotifyAt); if (shouldNotify) { submitted = true; nextNotifyAt = Date.now() + 2_000; try { operation = await this.submit( operationId, catalog.digest, entry.identity, argumentsValue, options.signal, ); } catch (error) { if ( operation === null && !staleRefreshAttempted && error instanceof CodemodeTransportError && error.outcomeUnknown !== true && error.retryable !== false && error.remoteCode === "codemode_catalog_stale" ) { staleRefreshAttempted = true; catalog = await this.catalog({ refresh: true, ...(options.signal ? { signal: options.signal } : {}), }); entry = resolveEntry(catalog); if (!entry) throw new AttemptToolNotFoundError(); assertDefinition(); submitted = false; nextNotifyAt = 0; continue; } if (options.signal?.aborted) throw error; if (operation === null && !canReconcileCodemodeSubmission(error)) throw error; // The POST may have committed before its response was lost, or an // already-bound operation may have settled while a later wake // notification failed deterministically. The caller-owned id is the // recovery handle: read and re-prove the exact binding before // deciding that another side effect is necessary. let recovered: CodemodeOperationValue; try { recovered = await this.read(operationId, options.signal); } catch (recoveryError) { if (options.signal?.aborted) throw recoveryError; throw new CodemodeTransportError( `Codemode operation ${operationId} could not be reconciled after its submission response failed`, null, { code: "codemode_operation_recovery_unavailable", retryable: true, outcomeUnknown: true, details: { operationId }, }, ); } assertRecoveredCodemodeOperation(recovered, { operationId, catalog, identity: entry.identity, arguments: argumentsValue, }); operation = recovered; } } else { operation = await this.read(operationId, options.signal); } if (operation.state === "waiting_for_approval") { throw new CodemodeApprovalPendingError(operation.operationId, operation.approvalRequestId); } if (operation.state === "completed") { return { result: AttemptToolResult.parse(operation.result), entry }; } if (["failed", "outcome_unknown", "cancelled"].includes(operation.state)) { throw new CodemodeOperationError( operation, operation.errorCode ?? `codemode_${operation.state}`, ); } await abortableDelay(this.pollIntervalMs, options.signal); } } /** Resolve and call one exact generated namespace path without parsing a wire name. */ async callPath( path: readonly string[], argumentsValue: Record = {}, options: CodemodeCallOptions = {}, ): Promise { if (path.length < 2 || path.some((segment) => segment.length === 0)) { throw new AttemptToolNotFoundError(); } return ( await this.callResolved( (catalog) => catalogEntryForPath(catalog, path), argumentsValue, options, ) ).result; } /** Return structured content, or a local-image wrapper for typed multimodal results. */ async callPathValue( path: readonly string[], argumentsValue: Record = {}, options: CodemodeCallOptions = {}, ): Promise { if (path.length < 2 || path.some((segment) => segment.length === 0)) { throw new AttemptToolNotFoundError(); } const { result, entry } = await this.callResolved( (catalog) => catalogEntryForPath(catalog, path), argumentsValue, options, ); if (!entry.outputSchema) return result; if (result.isError) throw new CodemodeToolCallError(result); if (!result.structuredContent) { throw new CodemodeToolContractError( `Codemode tool ${path.join(".")} declared outputSchema but returned no structured content`, ); } if (!result.content.some((content) => content.type === "image")) { return result.structuredContent; } const images = await materializeCodemodeImages(result); return { structuredContent: result.structuredContent, images, otherContent: result.content.filter((content) => content.type !== "image"), }; } private async submit( operationId: string, catalogDigest: string, identity: AttemptToolIdentity, argumentsValue: Record, signal?: AbortSignal, ): Promise { // Submission is idempotent by the caller-owned operation id: identical // bytes replay the same journal row, so a known-outcome transient server // failure (for example a database contention 503) is resubmitted as is. return await retryTransientCodemodeRequest(signal, async () => { const response = await this.request("/calls", { method: "POST", ...(signal ? { signal } : {}), headers: { "content-type": "application/json" }, body: JSON.stringify({ operationId, catalogDigest, identity, arguments: argumentsValue, durableApproval: true, }), }); return CodemodeCallSubmission.parse(await response.json()).operation; }); } /** Observe a durable handle using the current attempt, without resubmitting its payload. */ async status(operationId: string, signal?: AbortSignal) { const operation = await this.read(operationId, signal); return { operationId: operation.operationId, state: operation.state, approvalRequestId: operation.approvalRequestId, result: operation.result, errorCode: operation.errorCode, errorMessage: operation.errorMessage, }; } /** Observe continuation after review. Human approval wakes the worker; this never grants permission. */ async resume( operationId: string, options: Omit = {}, ): Promise { const deadline = Date.now() + boundedPositiveInteger(options.timeoutMs ?? this.timeoutMs, 1_000, 60 * 60_000); while (true) { throwIfAborted(options.signal); const operation = await this.read(operationId, options.signal); if (operation.state === "waiting_for_approval") throw new CodemodeApprovalPendingError(operation.operationId, operation.approvalRequestId); if (operation.state === "completed") return AttemptToolResult.parse(operation.result); if (["failed", "outcome_unknown", "cancelled"].includes(operation.state)) throw new CodemodeOperationError( operation, operation.errorCode ?? `codemode_${operation.state}`, ); if (Date.now() >= deadline) throw new CodemodeTransportError( `Codemode operation ${operationId} did not settle before the client deadline`, ); await abortableDelay(this.pollIntervalMs, options.signal); } } private async read(operationId: string, signal?: AbortSignal): Promise { return await retryTransientCodemodeRequest(signal, async () => { const response = await this.request(`/calls/${operationId}`, { method: "GET", ...(signal ? { signal } : {}), }); return CodemodeOperation.parse(await response.json()); }); } /** The agent's own SDK requests through the Codemode SDK proxy (and the * transport a local Site preview forwards through). The attempt bearer never * enters a page. Validate tenant routing here; ordinary API handlers * authorize the operation under the existing agent proxy permission ceiling. * Requests marked as Site frames are held to the Site operation allowlist * by the preview handler and again by the API. */ async sessionRequest(path: string, init: RequestInit): Promise { siteHostWorkspacePath(path, "site-host"); return this.request(`/sdk${path}`, init, false); } private async request(path: string, init: RequestInit, throwOnError = true): Promise { const token = typeof this.options.token === "function" ? await this.options.token() : this.options.token; const response = await this.fetchImpl(`${this.baseUrl}${path}`, { ...init, headers: { ...Object.fromEntries(new Headers(init.headers).entries()), authorization: `Bearer ${token}`, "x-opengeni-codemode-capabilities": "durable-approval-v1", }, }); if (!response.ok && throwOnError) { let message = `Codemode request failed with HTTP ${response.status}`; let errorOptions: { code?: string; retryable?: boolean; outcomeUnknown?: boolean; details?: Readonly>; } = {}; try { const error = parseCodemodeApiError(await response.json()); if (error?.message) message = error.message; if (error) { errorOptions = { ...(error.code ? { code: error.code } : {}), ...(error.retryable === undefined ? {} : { retryable: error.retryable }), ...(error.outcomeUnknown === undefined ? {} : { outcomeUnknown: error.outcomeUnknown }), ...(error.details ? { details: error.details } : {}), }; } } catch { // The status is sufficient; never echo an unbounded provider body. } throw new CodemodeTransportError(message, response.status, errorOptions); } return response; } } function canReconcileCodemodeSubmission(error: unknown): boolean { return !(error instanceof CodemodeTransportError) || error.outcomeUnknown === true; } function assertRecoveredCodemodeOperation( operation: CodemodeOperationValue, expected: { operationId: string; catalog: AttemptToolCatalogValue; identity: AttemptToolIdentity; arguments: Record; }, ): void { const matches = operation.operationId === expected.operationId && operation.accountId === expected.catalog.accountId && operation.workspaceId === expected.catalog.workspaceId && operation.sessionId === expected.catalog.sessionId && operation.turnId === expected.catalog.turnId && operation.attemptId === expected.catalog.attemptId && operation.executionGeneration === expected.catalog.executionGeneration && operation.catalogDigest === expected.catalog.digest && operation.identity.serverId === expected.identity.serverId && operation.identity.toolName === expected.identity.toolName && digestCanonicalJson(operation.arguments) === digestCanonicalJson(expected.arguments); if (matches) return; throw new CodemodeTransportError( "Codemode operation id is already bound to a different request", 409, { code: "codemode_operation_conflict", retryable: false, outcomeUnknown: false, }, ); } export function compileCodemodeTools( catalog: AttemptToolCatalogValue, client: CodemodeClient, ): CodemodeTools { const verified = parseVerifiedAttemptToolCatalog(catalog); const root: CodemodeTools = Object.create(null) as CodemodeTools; for (const entry of verified.entries) { let cursor = root; for (const segment of entry.codemodePath.slice(0, -1)) { let existing = cursor[segment]; if (typeof existing === "function") throw new Error("Codemode path collides with a tool"); if (!existing) { existing = Object.create(null) as CodemodeTools; cursor[segment] = existing; } cursor = existing; } const leaf = entry.codemodePath.at(-1)!; const invoke: CodemodeToolFunction = async (args = {}, options = {}) => await client.callPathValue(entry.codemodePath, args, options); Object.defineProperty(invoke, "entry", { value: entry, enumerable: false }); cursor[leaf] = invoke; } return root; } export class AttemptToolEnvironment { constructor( readonly catalog: AttemptToolCatalogValue, private readonly gateway: ToolGateway, ) {} async call( input: AttemptToolCallValue, context: { transportMeta?: Record | null; signal?: AbortSignal; } = {}, ): Promise { return await (await this.prepareCall(input, context)).execute(); } async prepareCall( input: AttemptToolCallValue, context: { transportMeta?: Record | null; signal?: AbortSignal; } = {}, ): Promise { const call = AttemptToolCall.parse(input); return await this.gateway.prepareCall(call, context); } effectDigest(identity: AttemptToolIdentity): string { return this.gateway.effectDigest(identity); } async callModel(input: ModelAttemptToolCall): Promise { return await this.gateway.callModel(input); } } export function createAttemptToolEnvironment( input: CreateAttemptToolEnvironmentInput, ): AttemptToolEnvironment { const createdAt = (input.createdAt ?? new Date()).toISOString(); const prepared = prepareToolGatewayDefinitions( input.definitions.map( (definition): ToolGatewayDefinition => ({ ...definition, execute: async (argumentsValue, context) => await definition.execute(argumentsValue, { ...context, caller: AttemptToolCaller.parse(context.caller), }), }), ), ); const unsigned = { version: ATTEMPT_TOOL_CATALOG_VERSION, ...input.scope, generation: input.generation, createdAt, ...(input.firstPartyMcpPermissions !== undefined ? { firstPartyMcpPermissions: [...input.firstPartyMcpPermissions] } : {}), entries: [...prepared.entries], }; const catalog = immutableVerifiedCatalog({ ...unsigned, digest: digestAttemptToolCatalog(unsigned), }); return new AttemptToolEnvironment( catalog, prepared.create({ catalogDigest: catalog.digest, requireApproval: (entry, caller) => caller.kind === "codemode" && entry.approval === "human", ...(input.confirmModelApproval ? { confirmModelApproval: ({ entry, subjectId }) => input.confirmModelApproval!({ modelName: entry.modelName, subjectId, }), } : {}), ...(input.authorize ? { authorize: async ({ call, entry }) => await input.authorize!({ call: AttemptToolCall.parse(call), entry, }), } : {}), ...(input.resolveArguments ? { resolveArguments: input.resolveArguments } : {}), }), ); } export function digestAttemptToolCatalog(catalog: Omit): string { const { createdAt: _createdAt, ...authoritative } = catalog; return digestCanonicalJson(authoritative); } export function digestCodemodeOperationRequest( input: Pick, ): string { return digestCanonicalJson(input); } export function codemodeDispatchSubject(workspaceId: string, attemptId: string): string { if (!UUID_PATTERN.test(workspaceId) || !UUID_PATTERN.test(attemptId)) { throw new Error("Codemode dispatch subject requires UUID workspace and attempt ids"); } return `codemode.${workspaceId}.${attemptId}.dispatch`; } export function encodeCodemodeDispatchRequest(input: CodemodeDispatchRequestValue): Uint8Array { return new TextEncoder().encode(JSON.stringify(CodemodeDispatchRequest.parse(input))); } export function decodeCodemodeDispatchRequest(input: Uint8Array): CodemodeDispatchRequestValue { return CodemodeDispatchRequest.parse(JSON.parse(new TextDecoder().decode(input))); } export function encodeCodemodeDispatchAck(input: CodemodeDispatchAckValue): Uint8Array { return new TextEncoder().encode(JSON.stringify(CodemodeDispatchAck.parse(input))); } export function decodeCodemodeDispatchAck(input: Uint8Array): CodemodeDispatchAckValue { return CodemodeDispatchAck.parse(JSON.parse(new TextDecoder().decode(input))); } export function parseVerifiedAttemptToolCatalog(input: unknown): AttemptToolCatalogValue { return verifyAttemptToolCatalog(input).catalog; } // Only snapshots constructed here enter this identity cache. A digest supplied // by a caller, a frozen object, or a JSON copy is not evidence of verification. // Weak keys retain no catalog beyond the lifetime of its ordinary consumers. const immutableCatalogVerifications = new WeakMap< object, { catalog: AttemptToolCatalogValue; byteLength: number } >(); function immutableVerifiedCatalog(input: unknown): AttemptToolCatalogValue { // JSON-schema records can retain caller-owned nested values after parsing. // Detach before freezing so neither side can mutate the other's snapshot. const verified = verifyAttemptToolCatalog(structuredClone(input)); if (isPlainCatalogValue(verified.catalog)) { freezeCatalogValue(verified.catalog); immutableCatalogVerifications.set(verified.catalog, verified); } return verified.catalog; } function isPlainCatalogValue(value: unknown): boolean { if (value === null || typeof value !== "object") return true; // Freezing Date/Map/etc. does not freeze their internal mutable state. const prototype = Object.getPrototypeOf(value); if (!Array.isArray(value) && prototype !== Object.prototype && prototype !== null) return false; for (const child of Object.values(value)) { if (!isPlainCatalogValue(child)) return false; } return true; } function freezeCatalogValue(value: unknown): void { if (value === null || typeof value !== "object") return; for (const child of Object.values(value)) freezeCatalogValue(child); Object.freeze(value); } /** Verifies a catalog and also reports its serialized UTF-8 size. */ export function verifyAttemptToolCatalog(input: unknown): { catalog: AttemptToolCatalogValue; byteLength: number; } { if (input !== null && typeof input === "object") { const verified = immutableCatalogVerifications.get(input); if (verified) return { ...verified }; } const catalog = AttemptToolCatalog.parse(input); const byteLength = assertCatalogSize(catalog); const { digest, ...unsigned } = catalog; if (digestAttemptToolCatalog(unsigned) !== digest) { throw new AttemptToolCatalogIntegrityError(); } return { catalog, byteLength }; } function assertCatalogSize(catalog: AttemptToolCatalogValue): number { const byteLength = new TextEncoder().encode(JSON.stringify(catalog)).byteLength; if (byteLength > ATTEMPT_TOOL_CATALOG_MAX_BYTES) { throw new AttemptToolCatalogTooLargeError(); } return byteLength; } const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu; function boundedPositiveInteger(value: number, minimum: number, maximum: number): number { if (!Number.isFinite(value)) return minimum; return Math.max(minimum, Math.min(maximum, Math.floor(value))); } function throwIfAborted(signal?: AbortSignal): void { if (signal?.aborted) throw signal.reason ?? new DOMException("Aborted", "AbortError"); } function structuredToolError(result: AttemptToolResultValue): { code: string; message: string; retryable: boolean; } { const structured = result.structuredContent as | { error?: { code?: unknown; message?: unknown; retryable?: unknown } } | undefined; const error = structured?.error; return { code: typeof error?.code === "string" ? error.code : "tool_error", message: typeof error?.message === "string" ? error.message : "Codemode tool failed", retryable: error?.retryable === true, }; } function catalogEntryForPath( catalog: AttemptToolCatalogValue, path: readonly string[], ): AttemptToolCatalogEntryValue | null { const matches = catalog.entries.filter( (entry) => entry.codemodePath.length === path.length && entry.codemodePath.every((segment, index) => segment === path[index]), ); return matches.length === 1 ? matches[0]! : null; } function parseCodemodeApiError(input: unknown): { message?: string; code?: string; retryable?: boolean; outcomeUnknown?: boolean; details?: Readonly>; } | null { if (!input || typeof input !== "object" || Array.isArray(input)) return null; const root = input as Record; const nested = root.error && typeof root.error === "object" && !Array.isArray(root.error) ? (root.error as Record) : root; const details = nested.details && typeof nested.details === "object" && !Array.isArray(nested.details) ? (nested.details as Readonly>) : undefined; const detailCode = details?.code; const code = typeof detailCode === "string" && /^[a-z0-9_]{1,128}$/u.test(detailCode) ? detailCode : undefined; const message = typeof nested.message === "string" ? nested.message : undefined; const retryable = typeof nested.retryable === "boolean" ? nested.retryable : undefined; const outcomeUnknown = typeof nested.outcomeUnknown === "boolean" ? nested.outcomeUnknown : undefined; return message || code || retryable !== undefined || outcomeUnknown !== undefined || details ? { ...(message ? { message } : {}), ...(code ? { code } : {}), ...(retryable === undefined ? {} : { retryable }), ...(outcomeUnknown === undefined ? {} : { outcomeUnknown }), ...(details ? { details } : {}), } : null; } const CODEMODE_TRANSIENT_RETRY_DELAYS_MS = [100, 250, 500, 1_000, 2_000] as const; /** A server-declared transient failure whose outcome is known: nothing was applied. */ export function isTransientCodemodeTransportError(error: unknown): boolean { return ( error instanceof CodemodeTransportError && error.status !== null && error.status >= 500 && error.retryable === true && error.outcomeUnknown !== true ); } /** Bounded retry for idempotent journal requests (submit by operation id, read). */ async function retryTransientCodemodeRequest( signal: AbortSignal | undefined, request: () => Promise, ): Promise { for (let attempt = 0; ; attempt += 1) { try { return await request(); } catch (error) { const delayMs = CODEMODE_TRANSIENT_RETRY_DELAYS_MS[attempt]; if (delayMs === undefined || signal?.aborted || !isTransientCodemodeTransportError(error)) { throw error; } await abortableDelay(delayMs + Math.floor(Math.random() * delayMs), signal); } } } async function abortableDelay(delayMs: number, signal?: AbortSignal): Promise { if (!signal) { await new Promise((resolve) => setTimeout(resolve, delayMs)); return; } throwIfAborted(signal); await new Promise((resolve, reject) => { const timer = setTimeout(() => { signal.removeEventListener("abort", onAbort); resolve(); }, delayMs); const onAbort = () => { clearTimeout(timer); reject(signal.reason ?? new DOMException("Aborted", "AbortError")); }; signal.addEventListener("abort", onAbort, { once: true }); }); } export * from "./environment"; export * from "./interaction"; export * from "./images"; export * from "./artifacts"; export * from "./structured"; export * from "./declarations"; export * from "./site"; export * from "./pagination";