@tanstack/ai-acp 0.3.3 → 0.3.5

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.
@@ -1,6 +1,6 @@
1
1
  import { BaseTextAdapter, StructuredOutputOptions, StructuredOutputResult } from '@tanstack/ai/adapters';
2
2
  import { SandboxHandle } from '@tanstack/ai-sandbox';
3
- import { DefaultMessageMetadataByModality, Modality, ModelMessage, StreamChunk, TextOptions } from '@tanstack/ai';
3
+ import { DefaultMessageMetadataByModality, Modality, ModelMessage, AdapterYieldChunk, TextOptions } from '@tanstack/ai';
4
4
  import { AcpSessionTransport } from '../transport/types.js';
5
5
  import { AcpPermissionMode, PermissionHandler } from '../types/acp-types.js';
6
6
  import { BuiltAcpPrompt } from '../messages/prompt.js';
@@ -162,7 +162,7 @@ export declare class AcpCompatibleTextAdapter<TModel extends string, TModelOptio
162
162
  private buildPrompt;
163
163
  private applySystemPrompts;
164
164
  private makePermissionHandler;
165
- chatStream(options: TextOptions<ResolvedOptions<TModelOptions>>): AsyncIterable<StreamChunk>;
165
+ chatStream(options: TextOptions<ResolvedOptions<TModelOptions>>): AsyncIterable<AdapterYieldChunk>;
166
166
  private openStdioTransport;
167
167
  private emitDiffChunks;
168
168
  private emitParsedStructuredOutput;
@@ -1 +1 @@
1
- {"version":3,"file":"compatible.js","names":[],"sources":["../../../src/adapters/compatible.ts"],"sourcesContent":["import { EventType, normalizeSystemPrompts } from '@tanstack/ai'\nimport {\n appendOutputSchemaInstruction,\n parseJsonFromAssistantText,\n structuredOutputCompleteChunk,\n structuredOutputStartChunk,\n toRunErrorRawEvent,\n} from '@tanstack/ai/adapter-internals'\nimport { BaseTextAdapter } from '@tanstack/ai/adapters'\nimport {\n DurableAttachNotSupportedError,\n SandboxCapability,\n buildApprovalRequestedEvent,\n createBridgeEventChannel,\n getSandbox,\n getSandboxDurability,\n getToolBridgeProvisioner,\n getWorkspaceProjection,\n mergeChunkStreams,\n nodeHttpBridgeProvisioner,\n resolveDurableRunId,\n resolveHarnessCwd,\n} from '@tanstack/ai-sandbox'\nimport { AsyncQueue } from '../stream/queue'\nimport { startAcpSession } from '../session/acp-client'\nimport { translateAcpStream } from '../stream/translate'\nimport { resolveInteractivePermission, resolvePermission } from '../permissions'\nimport { buildAcpPrompt } from '../messages/prompt'\nimport { projectAcpWorkspace, workspaceMcpServers } from './projection'\nimport type { AcpMcpServer } from './projection'\nimport type { HostToolBridge, SandboxHandle } from '@tanstack/ai-sandbox'\nimport type {\n StructuredOutputOptions,\n StructuredOutputResult,\n} from '@tanstack/ai/adapters'\nimport type {\n DefaultMessageMetadataByModality,\n Modality,\n ModelMessage,\n StreamChunk,\n TextOptions,\n} from '@tanstack/ai'\nimport type { AcpSessionHandle } from '../session/acp-client'\nimport type { AcpStreamEvent } from '../stream/translate'\nimport type { AcpSessionTransport } from '../transport/types'\nimport type {\n AcpPermissionMode,\n AcpSessionUpdate,\n PermissionHandler,\n} from '../types/acp-types'\nimport type { BuiltAcpPrompt } from '../messages/prompt'\n\nconst DEFAULT_WORKDIR = '/workspace'\n\n/**\n * Everything a harness needs to know to launch its ACP server inside the\n * sandbox. Passed to {@link AcpCompatibleConfig.command} /\n * {@link AcpCompatibleConfig.openTransport}.\n */\nexport interface AcpHarnessContext<\n TModelOptions extends Record<string, any> = AcpCompatibleProviderOptions,\n> {\n /** The sandbox the harness runs in (from `withSandbox(...)` middleware). */\n sandbox: SandboxHandle\n /** The selected model id. */\n model: string\n /** Virtual cwd for `sandbox.process.spawn` (the provider maps `/workspace`). */\n cwd: string\n /** Literal cwd for the harness's own `--cwd` flag / ACP `newSession`. */\n harnessCwd: string\n /** Extra env vars configured for the harness process. */\n env: Record<string, string> | undefined\n /**\n * Per-call options from `chat({ modelOptions })` — the base ACP options plus\n * whatever you declared via {@link AcpCompatibleConfig.modelOptions}. Read\n * these to turn options into CLI flags / transport choices.\n */\n modelOptions: TModelOptions | undefined\n /** Abort signal for the run, when one was provided. */\n signal: AbortSignal | undefined\n}\n\n/** Union of selectable model names from a `models` tuple (any string if omitted). */\nexport type AcpModelNameOf<TModels extends ReadonlyArray<string>> =\n TModels[number]\n\nexport interface AcpCompatibleConfig<\n TModels extends ReadonlyArray<string> = ReadonlyArray<string>,\n TModelOptions extends Record<string, any> = AcpCompatibleProviderOptions,\n> {\n /**\n * Harness name. Used as the provider label, the log prefix, and the CUSTOM\n * session-id event name (`<name>.session-id`).\n */\n name: string\n /**\n * The models this harness accepts. Declaring them makes the returned factory\n * type-safe — `harness('known-model')` is checked, unknown ids are rejected.\n * Omit to accept any string.\n */\n models?: TModels\n /**\n * Type-only brand for the per-call options accepted via `chat({ modelOptions })`.\n * Declare your harness's options here with `{} as { ... }` (the value is unused\n * at runtime); they are merged with the base {@link AcpCompatibleProviderOptions}\n * and exposed on {@link AcpHarnessContext.modelOptions} so `command` /\n * `openTransport` can turn them into CLI flags.\n *\n * @example modelOptions: {} as { reasoningEffort?: 'low' | 'high' }\n */\n modelOptions?: TModelOptions\n /**\n * Build the shell command that launches the harness's ACP server over\n * **stdio** inside the sandbox (e.g. `` `pi --acp -m ${model}` ``). Required\n * unless {@link openTransport} is provided.\n */\n command?: (\n ctx: AcpHarnessContext<AcpCompatibleProviderOptions & TModelOptions>,\n ) => string\n /**\n * Full transport escape hatch — open any {@link AcpSessionTransport} yourself\n * (e.g. boot a `serve` process and connect over WebSocket, as Grok Build\n * does). Overrides {@link command}. Put ALL teardown in the returned\n * transport's `dispose` (stream) / process (stdio); it is disposed when the\n * session ends.\n */\n openTransport?: (\n ctx: AcpHarnessContext<AcpCompatibleProviderOptions & TModelOptions>,\n ) => Promise<AcpSessionTransport> | AcpSessionTransport\n /** Working directory inside the sandbox. Defaults to `/workspace`. */\n cwd?: string\n /**\n * The harness's skills directory, relative to the workspace root (e.g.\n * `'.pi/skills'`) — its native convention for where it auto-discovers skills,\n * the way Claude Code uses `.claude/skills`. When set, `withSandbox` workspace\n * `gitSkill`s are linked here. MCP skills don't need this: they're passed to\n * the agent over ACP natively. Omit and `gitSkill`s are left unlinked (warned).\n */\n skillsDir?: string\n /** Extra environment variables for the harness process. */\n env?: Record<string, string>\n /**\n * `'api-key'` (default) uses {@link authMethodId} (or `modelOptions.authMethodId`).\n * `'host'` skips ACP authenticate (use the CLI login on the machine).\n * Not inferred from the sandbox.\n */\n authMode?: 'host' | 'api-key'\n /**\n * ACP auth method to select before the session starts, when the harness\n * advertises one (e.g. `'pi-api-key'`). Overridable per call via\n * `modelOptions.authMethodId`. Ignored when {@link authMode} is `'host'`.\n */\n authMethodId?: string\n /** ACP permission policy. Defaults to `'bypassPermissions'`. */\n permissionMode?: AcpPermissionMode\n /**\n * Permission strategy:\n * - `'headless'` (default) — auto-resolve via {@link permissionMode}; the\n * sandbox is the boundary, so the agent runs without prompting.\n * - `'interactive'` — same policy, but `ask`-style prompts emit an\n * approval-requested event so a client can approve and re-run.\n */\n permissions?: 'headless' | 'interactive'\n /** Custom permission handler; overrides {@link permissions}/{@link permissionMode}. */\n onPermissionRequest?: PermissionHandler\n /** Message used for `RUN_ERROR` when the harness refuses a request. */\n refusalMessage?: string\n /** Emit ACP `plan` updates as a CUSTOM event under this name (off by default). */\n planEventName?: string\n /**\n * After the run, emit the `git diff` of the working dir as a `file.changed`\n * CUSTOM event. Requires a git repo at `cwd`. Off by default.\n */\n emitDiff?: boolean\n /**\n * Harness-specific JSON-RPC notifications (vendor `_x/...` extensions). Must\n * return without throwing — unknown extensions must not tear down the session.\n */\n onExtNotification?: (method: string, params: Record<string, unknown>) => void\n /**\n * Convert chat history into the harness prompt + resume inputs. Defaults to\n * {@link buildAcpPrompt} (trailing user message + flattened transcript).\n */\n buildPrompt?: (\n messages: Array<ModelMessage>,\n sessionId: string | undefined,\n ) => BuiltAcpPrompt\n}\n\n/** Per-call provider options, passed via `modelOptions` on `chat()`. */\nexport interface AcpCompatibleProviderOptions {\n /**\n * Resume an existing harness session. The adapter emits the session id of\n * every run via a CUSTOM `<name>.session-id` event; thread it back here to\n * continue (only the trailing user message is sent).\n */\n sessionId?: string\n /** Per-call override of the harness working directory. */\n cwd?: string\n /**\n * `'api-key'` (default) uses {@link authMethodId}.\n * `'host'` skips ACP authenticate.\n * Not inferred from the sandbox.\n */\n authMode?: 'host' | 'api-key'\n /** Per-call override of the ACP auth method. Ignored when authMode is `'host'`. */\n authMethodId?: string\n /** Per-call override of the ACP permission policy. */\n permissionMode?: AcpPermissionMode\n}\n\n/** Per-call options the adapter sees: the base ACP options + the harness's own. */\ntype ResolvedOptions<TModelOptions extends Record<string, any>> =\n AcpCompatibleProviderOptions & TModelOptions\n\nfunction q(value: string): string {\n return `'${value.replace(/'/g, `'\\\\''`)}'`\n}\n\nasync function disposeTransport(transport: AcpSessionTransport): Promise<void> {\n if (transport.kind === 'stdio') {\n await transport.process.kill()\n return\n }\n await transport.dispose()\n}\n\n/**\n * A generic ACP harness adapter built from {@link AcpCompatibleConfig}. Runs the\n * configured coding-agent CLI inside the sandbox provided by `withSandbox(...)`\n * and translates its ACP session into AG-UI `StreamChunk`s.\n */\nexport class AcpCompatibleTextAdapter<\n TModel extends string,\n TModelOptions extends Record<string, any> = AcpCompatibleProviderOptions,\n> extends BaseTextAdapter<\n TModel,\n ResolvedOptions<TModelOptions>,\n ReadonlyArray<Modality> & readonly ['text'],\n DefaultMessageMetadataByModality,\n ReadonlyArray<string>,\n unknown,\n never\n> {\n override readonly name: string\n\n override readonly requires = [SandboxCapability] as const\n\n private readonly harness: AcpCompatibleConfig<\n ReadonlyArray<string>,\n TModelOptions\n >\n\n constructor(\n config: AcpCompatibleConfig<ReadonlyArray<string>, TModelOptions>,\n model: TModel,\n ) {\n super({}, model)\n if (config.command === undefined && config.openTransport === undefined) {\n throw new Error(\n `acpCompatible(\"${config.name}\") needs either a \"command\" or an \"openTransport\".`,\n )\n }\n this.harness = config\n this.name = config.name\n }\n\n supportsCombinedToolsAndSchema(): boolean {\n return true\n }\n\n combinedStructuredOutputSource(): 'event' {\n return 'event'\n }\n\n private sandboxFrom(\n options: TextOptions<ResolvedOptions<TModelOptions>>,\n ): SandboxHandle {\n const ctx = options.capabilities\n if (!ctx) {\n throw new Error(\n `Adapter \"${this.name}\" requires a sandbox. Add withSandbox(defineSandbox({ ... })) to chat() middleware.`,\n )\n }\n return getSandbox(ctx)\n }\n\n private buildPrompt(\n messages: Array<ModelMessage>,\n sessionId: string | undefined,\n ): BuiltAcpPrompt {\n return this.harness.buildPrompt\n ? this.harness.buildPrompt(messages, sessionId)\n : buildAcpPrompt(messages, sessionId, this.name)\n }\n\n private applySystemPrompts(\n systemPrompts: Array<string>,\n prompt: string,\n ): string {\n if (systemPrompts.length === 0) return prompt\n return `${systemPrompts.join('\\n\\n')}\\n\\n${prompt}`\n }\n\n private makePermissionHandler(input: {\n mode: AcpPermissionMode\n bridgedToolNames: ReadonlySet<string>\n approvals: ReadonlyMap<string, boolean> | undefined\n approvalRequests: Array<StreamChunk>\n threadId: string\n runId: string\n }): PermissionHandler {\n if (this.harness.onPermissionRequest)\n return this.harness.onPermissionRequest\n\n if (this.harness.permissions === 'interactive') {\n return (request) => {\n const result = resolveInteractivePermission(\n request,\n input.mode,\n input.bridgedToolNames,\n input.approvals,\n this.name,\n )\n if (result.approvalId !== undefined) {\n input.approvalRequests.push(\n buildApprovalRequestedEvent({\n approvalId: result.approvalId,\n title:\n result.title ??\n request.toolCall.title ??\n request.toolCall.toolCallId,\n threadId: input.threadId,\n runId: input.runId,\n detail: { provider: this.name },\n }),\n )\n }\n return result.outcome\n }\n }\n\n return (request) =>\n resolvePermission(request, input.mode, input.bridgedToolNames)\n }\n\n async *chatStream(\n options: TextOptions<ResolvedOptions<TModelOptions>>,\n ): AsyncIterable<StreamChunk> {\n const { logger } = options\n let handle: AcpSessionHandle | undefined\n let bridge: HostToolBridge | undefined\n let transport: AcpSessionTransport | undefined\n const externalSignal =\n options.abortController?.signal ?? options.request?.signal ?? undefined\n let onAbort: (() => void) | undefined\n\n try {\n const sandbox = this.sandboxFrom(options)\n const modelOptions = options.modelOptions\n const cwd = modelOptions?.cwd ?? this.harness.cwd ?? DEFAULT_WORKDIR\n const harnessCwd = resolveHarnessCwd(sandbox, cwd)\n // This adapter does not journal yet, so a generated id is still fine.\n // Routed through the helper anyway so that whenever it gains journaling\n // it inherits the caller-supplied-runId requirement instead of\n // re-deriving it (see `packages/ai-sandbox/src/durability.ts`).\n const runId = resolveDurableRunId(options.runId, {\n durable: false,\n adapter: 'acp',\n fallback: () => this.generateId(),\n })\n const threadId = options.threadId ?? this.generateId()\n\n // Durability wired onto a path that cannot deliver it. Two outcomes, split\n // by whether a first attempt has already run.\n //\n // ATTACH is fatal. `sandboxRunDriver`'s `drive()` sets `attach: true` only\n // when a previous host was already streaming this run, so continuing past\n // here reaches `startAcpSession` + `session.prompt(...)` and re-runs the\n // agent from scratch against the workspace that attempt already mutated,\n // appending its whole output to a log that still holds the first\n // attempt's. This adapter has no journal to tail, no\n // `awaitAttachableJournal` to refuse the attach up front, and no\n // `alignedIfAttaching` to suppress the already-delivered prefix — so there\n // is nothing between here and that corruption except this throw.\n //\n // A FRESH durable run only fails to be recoverable LATER, which an app may\n // knowingly accept (it can wire `withSandbox({ runs, durability })` once at\n // the middleware level and still route some runs through this adapter). So\n // that is a warn, not a throw: audible, not fatal. Once per run, not per\n // chunk — a per-chunk warning would be worse than none. Mirrors\n // `ai-grok-build`'s `chatStreamAcp`.\n const durability = options.capabilities\n ? getSandboxDurability(options.capabilities, { optional: true })\n : undefined\n if (durability !== undefined) {\n if (durability.attach) {\n throw new DurableAttachNotSupportedError(\n 'acp',\n 'this adapter drives the harness over a bidirectional ACP ' +\n 'connection and does not journal',\n )\n }\n logger.warn(\n 'acp: sandbox durability is wired but this adapter never journals — ' +\n 'this run will not be recoverable on reconnect. Use a journaling ' +\n 'harness adapter for runs that must survive a host restart, or drop ' +\n 'durability if these runs are not meant to.',\n { runId, adapter: 'acp' },\n )\n }\n\n const channel = createBridgeEventChannel({\n model: this.model,\n threadId,\n runId,\n })\n\n const sessionId = modelOptions?.sessionId\n const { prompt: resumePrompt } = this.buildPrompt(\n options.messages,\n sessionId,\n )\n\n // Bridge chat()-provided tools into the agent over MCP (ACP http server).\n const bridgedToolNames = new Set(\n (options.tools ?? []).map((tool) => tool.name),\n )\n if (options.tools && options.tools.length > 0) {\n const provisioner =\n (options.capabilities\n ? getToolBridgeProvisioner(options.capabilities, { optional: true })\n : undefined) ?? nodeHttpBridgeProvisioner\n bridge = await provisioner.provision(options.tools, {\n provider: sandbox.provider,\n context: options.context,\n emitCustomEvent: channel.emitCustomEvent,\n ...(externalSignal ? { signal: externalSignal } : {}),\n })\n }\n\n // Project workspace skills declared via withSandbox. MCP skills ride ACP's\n // native `mcpServers` (below); gitSkills are linked into `skillsDir`.\n let workspaceServers: Array<AcpMcpServer> = []\n const projection = options.capabilities\n ? getWorkspaceProjection(options.capabilities, { optional: true })\n : undefined\n if (projection !== undefined) {\n await projectAcpWorkspace(sandbox, projection, {\n ...(this.harness.skillsDir !== undefined && {\n skillsDir: this.harness.skillsDir,\n }),\n harnessName: this.name,\n })\n workspaceServers = workspaceMcpServers(projection)\n }\n\n const ctx: AcpHarnessContext<ResolvedOptions<TModelOptions>> = {\n sandbox,\n model: this.model,\n cwd,\n harnessCwd,\n env: this.harness.env,\n modelOptions,\n signal: externalSignal,\n }\n transport = this.harness.openTransport\n ? await this.harness.openTransport(ctx)\n : await this.openStdioTransport(ctx)\n\n const mode =\n modelOptions?.permissionMode ??\n this.harness.permissionMode ??\n 'bypassPermissions'\n const authMode =\n modelOptions?.authMode ?? this.harness.authMode ?? 'api-key'\n const authMethodId =\n authMode === 'host'\n ? undefined\n : (modelOptions?.authMethodId ?? this.harness.authMethodId)\n\n const approvalRequests: Array<StreamChunk> = []\n const permissionHandler = this.makePermissionHandler({\n mode,\n bridgedToolNames,\n approvals: options.approvals,\n approvalRequests,\n threadId,\n runId,\n })\n\n const queue = new AsyncQueue<AcpStreamEvent>()\n\n logger.request(\n `activity=chat provider=${this.name} model=${this.model} sandbox=${sandbox.provider} messages=${options.messages.length} resume=${sessionId ?? 'none'}`,\n { provider: this.name, model: this.model },\n )\n\n // The host tool-bridge (chat() tools) + workspace MCP skills, both over\n // ACP's native MCP channel.\n const mcpServers: Array<AcpMcpServer> = [\n ...(bridge !== undefined\n ? [\n {\n name: bridge.name,\n url: bridge.url,\n headers: [\n { name: 'Authorization', value: `Bearer ${bridge.token}` },\n ],\n },\n ]\n : []),\n ...workspaceServers,\n ]\n\n const onAcpUpdate = (update: AcpSessionUpdate) =>\n queue.push({ kind: 'update', update })\n handle = await startAcpSession({\n transport,\n cwd: harnessCwd,\n ...(authMethodId !== undefined && { authMethodId }),\n ...(sessionId !== undefined && { resumeSessionId: sessionId }),\n ...(mcpServers.length > 0 && { mcpServers }),\n onUpdate: onAcpUpdate,\n ...(this.harness.onExtNotification && {\n onExtNotification: this.harness.onExtNotification,\n }),\n onPermissionRequest: permissionHandler,\n })\n const session = handle\n\n if (externalSignal !== undefined) {\n onAbort = () => void session.cancel().catch(() => undefined)\n if (externalSignal.aborted) onAbort()\n else externalSignal.addEventListener('abort', onAbort, { once: true })\n }\n\n queue.push({ kind: 'session', sessionId: session.sessionId })\n\n const systemPrompts = normalizeSystemPrompts(options.systemPrompts)\n .map((p) => p.content)\n .filter((c) => c.trim() !== '')\n let promptText = this.applySystemPrompts(\n systemPrompts,\n session.resumed || sessionId === undefined\n ? resumePrompt\n : this.buildPrompt(options.messages, undefined).prompt,\n )\n if (options.outputSchema) {\n promptText = appendOutputSchemaInstruction(\n promptText,\n options.outputSchema,\n )\n }\n\n session\n .prompt(promptText)\n .then(({ stopReason, usage }) => {\n queue.push({\n kind: 'done',\n stopReason,\n ...(usage !== undefined && { usage }),\n })\n queue.end()\n })\n .catch((error: unknown) => queue.fail(error))\n\n const wantsStructured = options.outputSchema !== undefined\n let lastAssistantText = ''\n let lastTextMessageId: string | undefined\n let heldFinished: StreamChunk | undefined\n for await (const chunk of mergeChunkStreams(\n translateAcpStream(queue, {\n model: this.model,\n runId,\n threadId,\n ...(options.parentRunId !== undefined && {\n parentRunId: options.parentRunId,\n }),\n genId: () => this.generateId(),\n bridgedToolNames,\n labels: {\n sessionIdEvent: `${this.name}.session-id`,\n // Surface non-text agent content (image/audio/resource) instead of\n // dropping it — emitted as a CUSTOM `<name>.message-content` event.\n contentEvent: `${this.name}.message-content`,\n ...(this.harness.planEventName !== undefined && {\n planEvent: this.harness.planEventName,\n }),\n ...(this.harness.refusalMessage !== undefined && {\n refusalMessage: this.harness.refusalMessage,\n }),\n },\n onAcpEvent: (event) =>\n logger.provider(`provider=${this.name} kind=${event.kind}`, {\n chunk: event,\n }),\n }),\n channel.stream,\n )) {\n if (wantsStructured && chunk.type === EventType.RUN_FINISHED) {\n heldFinished = chunk\n continue\n }\n if (wantsStructured) {\n if (chunk.type === EventType.TEXT_MESSAGE_START) {\n lastAssistantText = ''\n if (typeof chunk.messageId === 'string' && chunk.messageId !== '') {\n lastTextMessageId = chunk.messageId\n }\n } else if (\n chunk.type === EventType.TEXT_MESSAGE_CONTENT &&\n typeof chunk.delta === 'string'\n ) {\n lastAssistantText += chunk.delta\n }\n }\n yield chunk\n }\n\n if (options.outputSchema) {\n yield* this.emitParsedStructuredOutput(\n lastAssistantText,\n threadId,\n runId,\n lastTextMessageId,\n )\n }\n if (heldFinished) yield heldFinished\n\n // Surface any pending approval requests (interactive ask-policy actions\n // awaiting a client decision); the client approves and re-runs to continue.\n for (const event of approvalRequests) yield event\n\n if (this.harness.emitDiff) {\n yield* this.emitDiffChunks(sandbox, cwd, threadId, runId)\n }\n } catch (error: unknown) {\n const err = error as Error & { code?: string }\n const rawEvent = toRunErrorRawEvent(error)\n logger.errors(`${this.name}.chatStream fatal`, {\n error,\n source: `${this.name}.chatStream`,\n })\n yield {\n type: EventType.RUN_ERROR,\n model: options.model,\n timestamp: Date.now(),\n message: err.message || 'Unknown error occurred',\n ...(err.code !== undefined && { code: err.code }),\n ...(rawEvent !== undefined && { rawEvent }),\n error: {\n message: err.message || 'Unknown error occurred',\n ...(err.code !== undefined && { code: err.code }),\n },\n }\n } finally {\n if (externalSignal !== undefined && onAbort !== undefined) {\n externalSignal.removeEventListener('abort', onAbort)\n }\n // startAcpSession owns transport teardown once a handle exists (and tears\n // it down itself on a failed init). Only dispose here if we opened a\n // transport but never reached a session.\n if (handle !== undefined) await handle.dispose()\n else if (transport !== undefined) await disposeTransport(transport)\n await bridge?.close()\n }\n }\n\n private async openStdioTransport(\n ctx: AcpHarnessContext<ResolvedOptions<TModelOptions>>,\n ): Promise<AcpSessionTransport> {\n const build = this.harness.command\n if (build === undefined) {\n // Unreachable — the constructor requires `command` or `openTransport`,\n // and this path only runs when `openTransport` is absent.\n throw new Error(\n `acpCompatible(\"${this.name}\") has no \"command\" to launch over stdio.`,\n )\n }\n const command = build(ctx)\n const proc = await ctx.sandbox.process.spawn(command, {\n cwd: ctx.cwd,\n ...(this.harness.env ? { env: this.harness.env } : {}),\n ...(ctx.signal ? { signal: ctx.signal } : {}),\n })\n return { kind: 'stdio', process: proc }\n }\n\n private async *emitDiffChunks(\n sandbox: SandboxHandle,\n cwd: string,\n threadId: string,\n runId: string,\n ): AsyncIterable<StreamChunk> {\n try {\n const diff = await sandbox.process.exec(`git -C ${q(cwd)} diff`, { cwd })\n if (diff.exitCode === 0 && diff.stdout.trim() !== '') {\n yield {\n type: EventType.CUSTOM,\n name: 'file.changed',\n value: { path: '.', diff: diff.stdout },\n timestamp: Date.now(),\n threadId,\n runId,\n }\n }\n } catch {\n // ignore — diff is best-effort\n }\n }\n\n private *emitParsedStructuredOutput(\n raw: string,\n threadId: string,\n runId: string,\n messageId = this.generateId(),\n ): Generator<StreamChunk> {\n try {\n const object = parseJsonFromAssistantText(raw)\n yield structuredOutputStartChunk({\n messageId,\n model: this.model,\n threadId,\n runId,\n })\n yield structuredOutputCompleteChunk({\n messageId,\n model: this.model,\n threadId,\n runId,\n object,\n raw,\n })\n } catch (error: unknown) {\n const parserMessage =\n error instanceof Error\n ? error.message\n : 'Failed to parse structured output'\n const preview = raw.trim().slice(0, 200)\n const message =\n preview === '' ? parserMessage : `${parserMessage} Content: ${preview}`\n yield {\n type: EventType.RUN_ERROR,\n model: this.model,\n timestamp: Date.now(),\n message,\n code: 'structured-output-parse-failed',\n error: { message, code: 'structured-output-parse-failed' },\n }\n }\n }\n\n structuredOutput(\n _options: StructuredOutputOptions<ResolvedOptions<TModelOptions>>,\n ): Promise<StructuredOutputResult<unknown>> {\n return Promise.reject(\n new Error(\n 'This harness honors outputSchema on chat() in the same turn. ' +\n 'Pass outputSchema to chat(), or use a model adapter for a one-shot extract.',\n ),\n )\n }\n}\n\n/**\n * Configure an ACP-compatible harness once, then select a model per call.\n *\n * Mirrors `openaiCompatible`: it lets you plug ANY Agent Client Protocol agent\n * into a TanStack AI sandbox without a dedicated adapter package.\n *\n * @example\n * ```ts\n * import { acpCompatible } from '@tanstack/ai-acp'\n * import { chat } from '@tanstack/ai'\n * import { defineSandbox, withSandbox } from '@tanstack/ai-sandbox'\n *\n * const pi = acpCompatible({\n * name: 'pi',\n * // declaring `models` makes pi('…') type-safe; omit to accept any string\n * models: ['pi-fast', 'pi-pro'],\n * // declare per-call options; merged with the base ACP options and exposed\n * // on ctx.modelOptions inside `command` / `openTransport`\n * modelOptions: {} as { reasoningEffort?: 'low' | 'high' },\n * command: ({ model, harnessCwd, modelOptions }) =>\n * `pi --acp -m ${model} --cwd ${harnessCwd}` +\n * (modelOptions?.reasoningEffort ? ` --effort ${modelOptions.reasoningEffort}` : ''),\n * authMethodId: 'pi-api-key',\n * })\n *\n * chat({\n * adapter: pi('pi-pro'),\n * modelOptions: { reasoningEffort: 'high' }, // typed\n * messages,\n * middleware: [withSandbox(defineSandbox({ /* provider, install pi *\\/ }))],\n * })\n * ```\n */\nexport function acpCompatible<\n const TModels extends ReadonlyArray<string> = ReadonlyArray<string>,\n TModelOptions extends Record<string, any> = AcpCompatibleProviderOptions,\n>(config: AcpCompatibleConfig<TModels, TModelOptions>) {\n return <TModel extends AcpModelNameOf<TModels>>(\n model: TModel,\n overrides?: Partial<AcpCompatibleConfig<TModels, TModelOptions>>,\n ): AcpCompatibleTextAdapter<TModel, TModelOptions> =>\n new AcpCompatibleTextAdapter<TModel, TModelOptions>(\n overrides ? { ...config, ...overrides } : config,\n model,\n )\n}\n\n/**\n * One-shot helper: build a single-model ACP-compatible harness adapter inline.\n *\n * @example\n * ```ts\n * chat({\n * adapter: acpCompatibleText('pi-fast', {\n * name: 'pi',\n * command: ({ model }) => `pi --acp -m ${model}`,\n * }),\n * messages,\n * middleware: [withSandbox(defineSandbox({ ... }))],\n * })\n * ```\n */\nexport function acpCompatibleText<\n TModel extends string,\n TModelOptions extends Record<string, any> = AcpCompatibleProviderOptions,\n>(\n model: TModel,\n config: AcpCompatibleConfig<ReadonlyArray<string>, TModelOptions>,\n): AcpCompatibleTextAdapter<TModel, TModelOptions> {\n return new AcpCompatibleTextAdapter<TModel, TModelOptions>(config, model)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"compatible.js","names":[],"sources":["../../../src/adapters/compatible.ts"],"sourcesContent":["import { EventType, normalizeSystemPrompts } from '@tanstack/ai'\nimport {\n appendOutputSchemaInstruction,\n parseJsonFromAssistantText,\n structuredOutputCompleteChunk,\n structuredOutputStartChunk,\n toRunErrorRawEvent,\n} from '@tanstack/ai/adapter-internals'\nimport { BaseTextAdapter } from '@tanstack/ai/adapters'\nimport {\n DurableAttachNotSupportedError,\n SandboxCapability,\n buildApprovalRequestedEvent,\n createBridgeEventChannel,\n getSandbox,\n getSandboxDurability,\n getToolBridgeProvisioner,\n getWorkspaceProjection,\n mergeChunkStreams,\n nodeHttpBridgeProvisioner,\n resolveDurableRunId,\n resolveHarnessCwd,\n} from '@tanstack/ai-sandbox'\nimport { AsyncQueue } from '../stream/queue'\nimport { startAcpSession } from '../session/acp-client'\nimport { translateAcpStream } from '../stream/translate'\nimport { resolveInteractivePermission, resolvePermission } from '../permissions'\nimport { buildAcpPrompt } from '../messages/prompt'\nimport { projectAcpWorkspace, workspaceMcpServers } from './projection'\nimport type { AcpMcpServer } from './projection'\nimport type { HostToolBridge, SandboxHandle } from '@tanstack/ai-sandbox'\nimport type {\n StructuredOutputOptions,\n StructuredOutputResult,\n} from '@tanstack/ai/adapters'\nimport type {\n DefaultMessageMetadataByModality,\n Modality,\n ModelMessage,\n AdapterYieldChunk,\n TextOptions,\n} from '@tanstack/ai'\nimport type { AcpSessionHandle } from '../session/acp-client'\nimport type { AcpStreamEvent } from '../stream/translate'\nimport type { AcpSessionTransport } from '../transport/types'\nimport type {\n AcpPermissionMode,\n AcpSessionUpdate,\n PermissionHandler,\n} from '../types/acp-types'\nimport type { BuiltAcpPrompt } from '../messages/prompt'\n\nconst DEFAULT_WORKDIR = '/workspace'\n\n/**\n * Everything a harness needs to know to launch its ACP server inside the\n * sandbox. Passed to {@link AcpCompatibleConfig.command} /\n * {@link AcpCompatibleConfig.openTransport}.\n */\nexport interface AcpHarnessContext<\n TModelOptions extends Record<string, any> = AcpCompatibleProviderOptions,\n> {\n /** The sandbox the harness runs in (from `withSandbox(...)` middleware). */\n sandbox: SandboxHandle\n /** The selected model id. */\n model: string\n /** Virtual cwd for `sandbox.process.spawn` (the provider maps `/workspace`). */\n cwd: string\n /** Literal cwd for the harness's own `--cwd` flag / ACP `newSession`. */\n harnessCwd: string\n /** Extra env vars configured for the harness process. */\n env: Record<string, string> | undefined\n /**\n * Per-call options from `chat({ modelOptions })` — the base ACP options plus\n * whatever you declared via {@link AcpCompatibleConfig.modelOptions}. Read\n * these to turn options into CLI flags / transport choices.\n */\n modelOptions: TModelOptions | undefined\n /** Abort signal for the run, when one was provided. */\n signal: AbortSignal | undefined\n}\n\n/** Union of selectable model names from a `models` tuple (any string if omitted). */\nexport type AcpModelNameOf<TModels extends ReadonlyArray<string>> =\n TModels[number]\n\nexport interface AcpCompatibleConfig<\n TModels extends ReadonlyArray<string> = ReadonlyArray<string>,\n TModelOptions extends Record<string, any> = AcpCompatibleProviderOptions,\n> {\n /**\n * Harness name. Used as the provider label, the log prefix, and the CUSTOM\n * session-id event name (`<name>.session-id`).\n */\n name: string\n /**\n * The models this harness accepts. Declaring them makes the returned factory\n * type-safe — `harness('known-model')` is checked, unknown ids are rejected.\n * Omit to accept any string.\n */\n models?: TModels\n /**\n * Type-only brand for the per-call options accepted via `chat({ modelOptions })`.\n * Declare your harness's options here with `{} as { ... }` (the value is unused\n * at runtime); they are merged with the base {@link AcpCompatibleProviderOptions}\n * and exposed on {@link AcpHarnessContext.modelOptions} so `command` /\n * `openTransport` can turn them into CLI flags.\n *\n * @example modelOptions: {} as { reasoningEffort?: 'low' | 'high' }\n */\n modelOptions?: TModelOptions\n /**\n * Build the shell command that launches the harness's ACP server over\n * **stdio** inside the sandbox (e.g. `` `pi --acp -m ${model}` ``). Required\n * unless {@link openTransport} is provided.\n */\n command?: (\n ctx: AcpHarnessContext<AcpCompatibleProviderOptions & TModelOptions>,\n ) => string\n /**\n * Full transport escape hatch — open any {@link AcpSessionTransport} yourself\n * (e.g. boot a `serve` process and connect over WebSocket, as Grok Build\n * does). Overrides {@link command}. Put ALL teardown in the returned\n * transport's `dispose` (stream) / process (stdio); it is disposed when the\n * session ends.\n */\n openTransport?: (\n ctx: AcpHarnessContext<AcpCompatibleProviderOptions & TModelOptions>,\n ) => Promise<AcpSessionTransport> | AcpSessionTransport\n /** Working directory inside the sandbox. Defaults to `/workspace`. */\n cwd?: string\n /**\n * The harness's skills directory, relative to the workspace root (e.g.\n * `'.pi/skills'`) — its native convention for where it auto-discovers skills,\n * the way Claude Code uses `.claude/skills`. When set, `withSandbox` workspace\n * `gitSkill`s are linked here. MCP skills don't need this: they're passed to\n * the agent over ACP natively. Omit and `gitSkill`s are left unlinked (warned).\n */\n skillsDir?: string\n /** Extra environment variables for the harness process. */\n env?: Record<string, string>\n /**\n * `'api-key'` (default) uses {@link authMethodId} (or `modelOptions.authMethodId`).\n * `'host'` skips ACP authenticate (use the CLI login on the machine).\n * Not inferred from the sandbox.\n */\n authMode?: 'host' | 'api-key'\n /**\n * ACP auth method to select before the session starts, when the harness\n * advertises one (e.g. `'pi-api-key'`). Overridable per call via\n * `modelOptions.authMethodId`. Ignored when {@link authMode} is `'host'`.\n */\n authMethodId?: string\n /** ACP permission policy. Defaults to `'bypassPermissions'`. */\n permissionMode?: AcpPermissionMode\n /**\n * Permission strategy:\n * - `'headless'` (default) — auto-resolve via {@link permissionMode}; the\n * sandbox is the boundary, so the agent runs without prompting.\n * - `'interactive'` — same policy, but `ask`-style prompts emit an\n * approval-requested event so a client can approve and re-run.\n */\n permissions?: 'headless' | 'interactive'\n /** Custom permission handler; overrides {@link permissions}/{@link permissionMode}. */\n onPermissionRequest?: PermissionHandler\n /** Message used for `RUN_ERROR` when the harness refuses a request. */\n refusalMessage?: string\n /** Emit ACP `plan` updates as a CUSTOM event under this name (off by default). */\n planEventName?: string\n /**\n * After the run, emit the `git diff` of the working dir as a `file.changed`\n * CUSTOM event. Requires a git repo at `cwd`. Off by default.\n */\n emitDiff?: boolean\n /**\n * Harness-specific JSON-RPC notifications (vendor `_x/...` extensions). Must\n * return without throwing — unknown extensions must not tear down the session.\n */\n onExtNotification?: (method: string, params: Record<string, unknown>) => void\n /**\n * Convert chat history into the harness prompt + resume inputs. Defaults to\n * {@link buildAcpPrompt} (trailing user message + flattened transcript).\n */\n buildPrompt?: (\n messages: Array<ModelMessage>,\n sessionId: string | undefined,\n ) => BuiltAcpPrompt\n}\n\n/** Per-call provider options, passed via `modelOptions` on `chat()`. */\nexport interface AcpCompatibleProviderOptions {\n /**\n * Resume an existing harness session. The adapter emits the session id of\n * every run via a CUSTOM `<name>.session-id` event; thread it back here to\n * continue (only the trailing user message is sent).\n */\n sessionId?: string\n /** Per-call override of the harness working directory. */\n cwd?: string\n /**\n * `'api-key'` (default) uses {@link authMethodId}.\n * `'host'` skips ACP authenticate.\n * Not inferred from the sandbox.\n */\n authMode?: 'host' | 'api-key'\n /** Per-call override of the ACP auth method. Ignored when authMode is `'host'`. */\n authMethodId?: string\n /** Per-call override of the ACP permission policy. */\n permissionMode?: AcpPermissionMode\n}\n\n/** Per-call options the adapter sees: the base ACP options + the harness's own. */\ntype ResolvedOptions<TModelOptions extends Record<string, any>> =\n AcpCompatibleProviderOptions & TModelOptions\n\nfunction q(value: string): string {\n return `'${value.replace(/'/g, `'\\\\''`)}'`\n}\n\nasync function disposeTransport(transport: AcpSessionTransport): Promise<void> {\n if (transport.kind === 'stdio') {\n await transport.process.kill()\n return\n }\n await transport.dispose()\n}\n\n/**\n * A generic ACP harness adapter built from {@link AcpCompatibleConfig}. Runs the\n * configured coding-agent CLI inside the sandbox provided by `withSandbox(...)`\n * and translates its ACP session into AG-UI `StreamChunk`s.\n */\nexport class AcpCompatibleTextAdapter<\n TModel extends string,\n TModelOptions extends Record<string, any> = AcpCompatibleProviderOptions,\n> extends BaseTextAdapter<\n TModel,\n ResolvedOptions<TModelOptions>,\n ReadonlyArray<Modality> & readonly ['text'],\n DefaultMessageMetadataByModality,\n ReadonlyArray<string>,\n unknown,\n never\n> {\n override readonly name: string\n\n override readonly requires = [SandboxCapability] as const\n\n private readonly harness: AcpCompatibleConfig<\n ReadonlyArray<string>,\n TModelOptions\n >\n\n constructor(\n config: AcpCompatibleConfig<ReadonlyArray<string>, TModelOptions>,\n model: TModel,\n ) {\n super({}, model)\n if (config.command === undefined && config.openTransport === undefined) {\n throw new Error(\n `acpCompatible(\"${config.name}\") needs either a \"command\" or an \"openTransport\".`,\n )\n }\n this.harness = config\n this.name = config.name\n }\n\n supportsCombinedToolsAndSchema(): boolean {\n return true\n }\n\n combinedStructuredOutputSource(): 'event' {\n return 'event'\n }\n\n private sandboxFrom(\n options: TextOptions<ResolvedOptions<TModelOptions>>,\n ): SandboxHandle {\n const ctx = options.capabilities\n if (!ctx) {\n throw new Error(\n `Adapter \"${this.name}\" requires a sandbox. Add withSandbox(defineSandbox({ ... })) to chat() middleware.`,\n )\n }\n return getSandbox(ctx)\n }\n\n private buildPrompt(\n messages: Array<ModelMessage>,\n sessionId: string | undefined,\n ): BuiltAcpPrompt {\n return this.harness.buildPrompt\n ? this.harness.buildPrompt(messages, sessionId)\n : buildAcpPrompt(messages, sessionId, this.name)\n }\n\n private applySystemPrompts(\n systemPrompts: Array<string>,\n prompt: string,\n ): string {\n if (systemPrompts.length === 0) return prompt\n return `${systemPrompts.join('\\n\\n')}\\n\\n${prompt}`\n }\n\n private makePermissionHandler(input: {\n mode: AcpPermissionMode\n bridgedToolNames: ReadonlySet<string>\n approvals: ReadonlyMap<string, boolean> | undefined\n approvalRequests: Array<AdapterYieldChunk>\n threadId: string\n runId: string\n }): PermissionHandler {\n if (this.harness.onPermissionRequest)\n return this.harness.onPermissionRequest\n\n if (this.harness.permissions === 'interactive') {\n return (request) => {\n const result = resolveInteractivePermission(\n request,\n input.mode,\n input.bridgedToolNames,\n input.approvals,\n this.name,\n )\n if (result.approvalId !== undefined) {\n input.approvalRequests.push(\n buildApprovalRequestedEvent({\n approvalId: result.approvalId,\n title:\n result.title ??\n request.toolCall.title ??\n request.toolCall.toolCallId,\n threadId: input.threadId,\n runId: input.runId,\n detail: { provider: this.name },\n }),\n )\n }\n return result.outcome\n }\n }\n\n return (request) =>\n resolvePermission(request, input.mode, input.bridgedToolNames)\n }\n\n async *chatStream(\n options: TextOptions<ResolvedOptions<TModelOptions>>,\n ): AsyncIterable<AdapterYieldChunk> {\n const { logger } = options\n let handle: AcpSessionHandle | undefined\n let bridge: HostToolBridge | undefined\n let transport: AcpSessionTransport | undefined\n const externalSignal =\n options.abortController?.signal ?? options.request?.signal ?? undefined\n let onAbort: (() => void) | undefined\n\n try {\n const sandbox = this.sandboxFrom(options)\n const modelOptions = options.modelOptions\n const cwd = modelOptions?.cwd ?? this.harness.cwd ?? DEFAULT_WORKDIR\n const harnessCwd = resolveHarnessCwd(sandbox, cwd)\n // This adapter does not journal yet, so a generated id is still fine.\n // Routed through the helper anyway so that whenever it gains journaling\n // it inherits the caller-supplied-runId requirement instead of\n // re-deriving it (see `packages/ai-sandbox/src/durability.ts`).\n const runId = resolveDurableRunId(options.runId, {\n durable: false,\n adapter: 'acp',\n fallback: () => this.generateId(),\n })\n const threadId = options.threadId ?? this.generateId()\n\n // Durability wired onto a path that cannot deliver it. Two outcomes, split\n // by whether a first attempt has already run.\n //\n // ATTACH is fatal. `sandboxRunDriver`'s `drive()` sets `attach: true` only\n // when a previous host was already streaming this run, so continuing past\n // here reaches `startAcpSession` + `session.prompt(...)` and re-runs the\n // agent from scratch against the workspace that attempt already mutated,\n // appending its whole output to a log that still holds the first\n // attempt's. This adapter has no journal to tail, no\n // `awaitAttachableJournal` to refuse the attach up front, and no\n // `alignedIfAttaching` to suppress the already-delivered prefix — so there\n // is nothing between here and that corruption except this throw.\n //\n // A FRESH durable run only fails to be recoverable LATER, which an app may\n // knowingly accept (it can wire `withSandbox({ runs, durability })` once at\n // the middleware level and still route some runs through this adapter). So\n // that is a warn, not a throw: audible, not fatal. Once per run, not per\n // chunk — a per-chunk warning would be worse than none. Mirrors\n // `ai-grok-build`'s `chatStreamAcp`.\n const durability = options.capabilities\n ? getSandboxDurability(options.capabilities, { optional: true })\n : undefined\n if (durability !== undefined) {\n if (durability.attach) {\n throw new DurableAttachNotSupportedError(\n 'acp',\n 'this adapter drives the harness over a bidirectional ACP ' +\n 'connection and does not journal',\n )\n }\n logger.warn(\n 'acp: sandbox durability is wired but this adapter never journals — ' +\n 'this run will not be recoverable on reconnect. Use a journaling ' +\n 'harness adapter for runs that must survive a host restart, or drop ' +\n 'durability if these runs are not meant to.',\n { runId, adapter: 'acp' },\n )\n }\n\n const channel = createBridgeEventChannel({\n model: this.model,\n threadId,\n runId,\n })\n\n const sessionId = modelOptions?.sessionId\n const { prompt: resumePrompt } = this.buildPrompt(\n options.messages,\n sessionId,\n )\n\n // Bridge chat()-provided tools into the agent over MCP (ACP http server).\n const bridgedToolNames = new Set(\n (options.tools ?? []).map((tool) => tool.name),\n )\n if (options.tools && options.tools.length > 0) {\n const provisioner =\n (options.capabilities\n ? getToolBridgeProvisioner(options.capabilities, { optional: true })\n : undefined) ?? nodeHttpBridgeProvisioner\n bridge = await provisioner.provision(options.tools, {\n provider: sandbox.provider,\n context: options.context,\n emitCustomEvent: channel.emitCustomEvent,\n ...(externalSignal ? { signal: externalSignal } : {}),\n })\n }\n\n // Project workspace skills declared via withSandbox. MCP skills ride ACP's\n // native `mcpServers` (below); gitSkills are linked into `skillsDir`.\n let workspaceServers: Array<AcpMcpServer> = []\n const projection = options.capabilities\n ? getWorkspaceProjection(options.capabilities, { optional: true })\n : undefined\n if (projection !== undefined) {\n await projectAcpWorkspace(sandbox, projection, {\n ...(this.harness.skillsDir !== undefined && {\n skillsDir: this.harness.skillsDir,\n }),\n harnessName: this.name,\n })\n workspaceServers = workspaceMcpServers(projection)\n }\n\n const ctx: AcpHarnessContext<ResolvedOptions<TModelOptions>> = {\n sandbox,\n model: this.model,\n cwd,\n harnessCwd,\n env: this.harness.env,\n modelOptions,\n signal: externalSignal,\n }\n transport = this.harness.openTransport\n ? await this.harness.openTransport(ctx)\n : await this.openStdioTransport(ctx)\n\n const mode =\n modelOptions?.permissionMode ??\n this.harness.permissionMode ??\n 'bypassPermissions'\n const authMode =\n modelOptions?.authMode ?? this.harness.authMode ?? 'api-key'\n const authMethodId =\n authMode === 'host'\n ? undefined\n : (modelOptions?.authMethodId ?? this.harness.authMethodId)\n\n const approvalRequests: Array<AdapterYieldChunk> = []\n const permissionHandler = this.makePermissionHandler({\n mode,\n bridgedToolNames,\n approvals: options.approvals,\n approvalRequests,\n threadId,\n runId,\n })\n\n const queue = new AsyncQueue<AcpStreamEvent>()\n\n logger.request(\n `activity=chat provider=${this.name} model=${this.model} sandbox=${sandbox.provider} messages=${options.messages.length} resume=${sessionId ?? 'none'}`,\n { provider: this.name, model: this.model },\n )\n\n // The host tool-bridge (chat() tools) + workspace MCP skills, both over\n // ACP's native MCP channel.\n const mcpServers: Array<AcpMcpServer> = [\n ...(bridge !== undefined\n ? [\n {\n name: bridge.name,\n url: bridge.url,\n headers: [\n { name: 'Authorization', value: `Bearer ${bridge.token}` },\n ],\n },\n ]\n : []),\n ...workspaceServers,\n ]\n\n const onAcpUpdate = (update: AcpSessionUpdate) =>\n queue.push({ kind: 'update', update })\n handle = await startAcpSession({\n transport,\n cwd: harnessCwd,\n ...(authMethodId !== undefined && { authMethodId }),\n ...(sessionId !== undefined && { resumeSessionId: sessionId }),\n ...(mcpServers.length > 0 && { mcpServers }),\n onUpdate: onAcpUpdate,\n ...(this.harness.onExtNotification && {\n onExtNotification: this.harness.onExtNotification,\n }),\n onPermissionRequest: permissionHandler,\n })\n const session = handle\n\n if (externalSignal !== undefined) {\n onAbort = () => void session.cancel().catch(() => undefined)\n if (externalSignal.aborted) onAbort()\n else externalSignal.addEventListener('abort', onAbort, { once: true })\n }\n\n queue.push({ kind: 'session', sessionId: session.sessionId })\n\n const systemPrompts = normalizeSystemPrompts(options.systemPrompts)\n .map((p) => p.content)\n .filter((c) => c.trim() !== '')\n let promptText = this.applySystemPrompts(\n systemPrompts,\n session.resumed || sessionId === undefined\n ? resumePrompt\n : this.buildPrompt(options.messages, undefined).prompt,\n )\n if (options.outputSchema) {\n promptText = appendOutputSchemaInstruction(\n promptText,\n options.outputSchema,\n )\n }\n\n session\n .prompt(promptText)\n .then(({ stopReason, usage }) => {\n queue.push({\n kind: 'done',\n stopReason,\n ...(usage !== undefined && { usage }),\n })\n queue.end()\n })\n .catch((error: unknown) => queue.fail(error))\n\n const wantsStructured = options.outputSchema !== undefined\n let lastAssistantText = ''\n let lastTextMessageId: string | undefined\n let heldFinished: AdapterYieldChunk | undefined\n for await (const chunk of mergeChunkStreams(\n translateAcpStream(queue, {\n model: this.model,\n runId,\n threadId,\n ...(options.parentRunId !== undefined && {\n parentRunId: options.parentRunId,\n }),\n genId: () => this.generateId(),\n bridgedToolNames,\n labels: {\n sessionIdEvent: `${this.name}.session-id`,\n // Surface non-text agent content (image/audio/resource) instead of\n // dropping it — emitted as a CUSTOM `<name>.message-content` event.\n contentEvent: `${this.name}.message-content`,\n ...(this.harness.planEventName !== undefined && {\n planEvent: this.harness.planEventName,\n }),\n ...(this.harness.refusalMessage !== undefined && {\n refusalMessage: this.harness.refusalMessage,\n }),\n },\n onAcpEvent: (event) =>\n logger.provider(`provider=${this.name} kind=${event.kind}`, {\n chunk: event,\n }),\n }),\n channel.stream,\n )) {\n if (wantsStructured && chunk.type === EventType.RUN_FINISHED) {\n heldFinished = chunk\n continue\n }\n if (wantsStructured) {\n if (chunk.type === EventType.TEXT_MESSAGE_START) {\n lastAssistantText = ''\n if (typeof chunk.messageId === 'string' && chunk.messageId !== '') {\n lastTextMessageId = chunk.messageId\n }\n } else if (\n chunk.type === EventType.TEXT_MESSAGE_CONTENT &&\n typeof chunk.delta === 'string'\n ) {\n lastAssistantText += chunk.delta\n }\n }\n yield chunk\n }\n\n if (options.outputSchema) {\n yield* this.emitParsedStructuredOutput(\n lastAssistantText,\n threadId,\n runId,\n lastTextMessageId,\n )\n }\n if (heldFinished) yield heldFinished\n\n // Surface any pending approval requests (interactive ask-policy actions\n // awaiting a client decision); the client approves and re-runs to continue.\n for (const event of approvalRequests) yield event\n\n if (this.harness.emitDiff) {\n yield* this.emitDiffChunks(sandbox, cwd, threadId, runId)\n }\n } catch (error: unknown) {\n const err = error as Error & { code?: string }\n const rawEvent = toRunErrorRawEvent(error)\n logger.errors(`${this.name}.chatStream fatal`, {\n error,\n source: `${this.name}.chatStream`,\n })\n yield {\n type: EventType.RUN_ERROR,\n model: options.model,\n timestamp: Date.now(),\n message: err.message || 'Unknown error occurred',\n ...(err.code !== undefined && { code: err.code }),\n ...(rawEvent !== undefined && { rawEvent }),\n error: {\n message: err.message || 'Unknown error occurred',\n ...(err.code !== undefined && { code: err.code }),\n },\n }\n } finally {\n if (externalSignal !== undefined && onAbort !== undefined) {\n externalSignal.removeEventListener('abort', onAbort)\n }\n // startAcpSession owns transport teardown once a handle exists (and tears\n // it down itself on a failed init). Only dispose here if we opened a\n // transport but never reached a session.\n if (handle !== undefined) await handle.dispose()\n else if (transport !== undefined) await disposeTransport(transport)\n await bridge?.close()\n }\n }\n\n private async openStdioTransport(\n ctx: AcpHarnessContext<ResolvedOptions<TModelOptions>>,\n ): Promise<AcpSessionTransport> {\n const build = this.harness.command\n if (build === undefined) {\n // Unreachable — the constructor requires `command` or `openTransport`,\n // and this path only runs when `openTransport` is absent.\n throw new Error(\n `acpCompatible(\"${this.name}\") has no \"command\" to launch over stdio.`,\n )\n }\n const command = build(ctx)\n const proc = await ctx.sandbox.process.spawn(command, {\n cwd: ctx.cwd,\n ...(this.harness.env ? { env: this.harness.env } : {}),\n ...(ctx.signal ? { signal: ctx.signal } : {}),\n })\n return { kind: 'stdio', process: proc }\n }\n\n private async *emitDiffChunks(\n sandbox: SandboxHandle,\n cwd: string,\n threadId: string,\n runId: string,\n ): AsyncIterable<AdapterYieldChunk> {\n try {\n const diff = await sandbox.process.exec(`git -C ${q(cwd)} diff`, { cwd })\n if (diff.exitCode === 0 && diff.stdout.trim() !== '') {\n yield {\n type: EventType.CUSTOM,\n name: 'file.changed',\n value: { path: '.', diff: diff.stdout },\n timestamp: Date.now(),\n threadId,\n runId,\n }\n }\n } catch {\n // ignore — diff is best-effort\n }\n }\n\n private *emitParsedStructuredOutput(\n raw: string,\n threadId: string,\n runId: string,\n messageId = this.generateId(),\n ): Generator<AdapterYieldChunk> {\n try {\n const object = parseJsonFromAssistantText(raw)\n yield structuredOutputStartChunk({\n messageId,\n model: this.model,\n threadId,\n runId,\n })\n yield structuredOutputCompleteChunk({\n messageId,\n model: this.model,\n threadId,\n runId,\n object,\n raw,\n })\n } catch (error: unknown) {\n const parserMessage =\n error instanceof Error\n ? error.message\n : 'Failed to parse structured output'\n const preview = raw.trim().slice(0, 200)\n const message =\n preview === '' ? parserMessage : `${parserMessage} Content: ${preview}`\n yield {\n type: EventType.RUN_ERROR,\n model: this.model,\n timestamp: Date.now(),\n message,\n code: 'structured-output-parse-failed',\n error: { message, code: 'structured-output-parse-failed' },\n }\n }\n }\n\n structuredOutput(\n _options: StructuredOutputOptions<ResolvedOptions<TModelOptions>>,\n ): Promise<StructuredOutputResult<unknown>> {\n return Promise.reject(\n new Error(\n 'This harness honors outputSchema on chat() in the same turn. ' +\n 'Pass outputSchema to chat(), or use a model adapter for a one-shot extract.',\n ),\n )\n }\n}\n\n/**\n * Configure an ACP-compatible harness once, then select a model per call.\n *\n * Mirrors `openaiCompatible`: it lets you plug ANY Agent Client Protocol agent\n * into a TanStack AI sandbox without a dedicated adapter package.\n *\n * @example\n * ```ts\n * import { acpCompatible } from '@tanstack/ai-acp'\n * import { chat } from '@tanstack/ai'\n * import { defineSandbox, withSandbox } from '@tanstack/ai-sandbox'\n *\n * const pi = acpCompatible({\n * name: 'pi',\n * // declaring `models` makes pi('…') type-safe; omit to accept any string\n * models: ['pi-fast', 'pi-pro'],\n * // declare per-call options; merged with the base ACP options and exposed\n * // on ctx.modelOptions inside `command` / `openTransport`\n * modelOptions: {} as { reasoningEffort?: 'low' | 'high' },\n * command: ({ model, harnessCwd, modelOptions }) =>\n * `pi --acp -m ${model} --cwd ${harnessCwd}` +\n * (modelOptions?.reasoningEffort ? ` --effort ${modelOptions.reasoningEffort}` : ''),\n * authMethodId: 'pi-api-key',\n * })\n *\n * chat({\n * adapter: pi('pi-pro'),\n * modelOptions: { reasoningEffort: 'high' }, // typed\n * messages,\n * middleware: [withSandbox(defineSandbox({ /* provider, install pi *\\/ }))],\n * })\n * ```\n */\nexport function acpCompatible<\n const TModels extends ReadonlyArray<string> = ReadonlyArray<string>,\n TModelOptions extends Record<string, any> = AcpCompatibleProviderOptions,\n>(config: AcpCompatibleConfig<TModels, TModelOptions>) {\n return <TModel extends AcpModelNameOf<TModels>>(\n model: TModel,\n overrides?: Partial<AcpCompatibleConfig<TModels, TModelOptions>>,\n ): AcpCompatibleTextAdapter<TModel, TModelOptions> =>\n new AcpCompatibleTextAdapter<TModel, TModelOptions>(\n overrides ? { ...config, ...overrides } : config,\n model,\n )\n}\n\n/**\n * One-shot helper: build a single-model ACP-compatible harness adapter inline.\n *\n * @example\n * ```ts\n * chat({\n * adapter: acpCompatibleText('pi-fast', {\n * name: 'pi',\n * command: ({ model }) => `pi --acp -m ${model}`,\n * }),\n * messages,\n * middleware: [withSandbox(defineSandbox({ ... }))],\n * })\n * ```\n */\nexport function acpCompatibleText<\n TModel extends string,\n TModelOptions extends Record<string, any> = AcpCompatibleProviderOptions,\n>(\n model: TModel,\n config: AcpCompatibleConfig<ReadonlyArray<string>, TModelOptions>,\n): AcpCompatibleTextAdapter<TModel, TModelOptions> {\n return new AcpCompatibleTextAdapter<TModel, TModelOptions>(config, model)\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { StreamChunk } from '@tanstack/ai';
1
+ import { AdapterYieldChunk } from '@tanstack/ai';
2
2
  import { AcpSessionUpdate, AcpStopReason, AcpUsage } from '../types/acp-types.js';
3
3
  export declare const BRIDGED_MCP_SERVER_NAME = "tanstack";
4
4
  export type AcpStreamEvent = {
@@ -35,4 +35,4 @@ export interface TranslateContext {
35
35
  onAcpEvent?: (event: AcpStreamEvent) => void;
36
36
  }
37
37
  export declare function matchBridgedToolName(title: string | null | undefined, bridgedToolNames: ReadonlySet<string> | undefined): string | undefined;
38
- export declare function translateAcpStream(events: AsyncIterable<AcpStreamEvent>, ctx: TranslateContext): AsyncIterable<StreamChunk>;
38
+ export declare function translateAcpStream(events: AsyncIterable<AcpStreamEvent>, ctx: TranslateContext): AsyncIterable<AdapterYieldChunk>;
@@ -1 +1 @@
1
- {"version":3,"file":"translate.js","names":[],"sources":["../../../src/stream/translate.ts"],"sourcesContent":["import { EventType, buildBaseUsage } from '@tanstack/ai'\nimport type { StreamChunk, TokenUsage } from '@tanstack/ai'\nimport type {\n AcpSessionUpdate,\n AcpStopReason,\n AcpToolCallUpdate,\n AcpUsage,\n} from '../types/acp-types'\n\nexport const BRIDGED_MCP_SERVER_NAME = 'tanstack'\n\nexport type AcpStreamEvent =\n | { kind: 'session'; sessionId: string }\n | { kind: 'update'; update: AcpSessionUpdate }\n | { kind: 'done'; stopReason: AcpStopReason; usage?: AcpUsage }\n\nexport interface AcpTranslateLabels {\n sessionIdEvent: string\n planEvent?: string\n refusalMessage?: string\n /**\n * When set, non-text agent message content (image / audio / resource /\n * resource_link blocks) is surfaced as a CUSTOM event under this name instead\n * of being dropped. Each event's `value` is `{ content: <ACP content block> }`.\n * Omit to keep the text-only behavior.\n */\n contentEvent?: string\n}\n\nexport interface TranslateContext {\n model: string\n runId: string\n threadId: string\n parentRunId?: string\n genId: () => string\n labels: AcpTranslateLabels\n bridgedToolNames?: ReadonlySet<string>\n onAcpEvent?: (event: AcpStreamEvent) => void\n}\n\nexport function matchBridgedToolName(\n title: string | null | undefined,\n bridgedToolNames: ReadonlySet<string> | undefined,\n): string | undefined {\n if (!title || !bridgedToolNames) return undefined\n if (bridgedToolNames.has(title)) return title\n for (const name of bridgedToolNames) {\n if (title.startsWith(`${name} (`)) return name\n }\n return undefined\n}\n\nfunction resolveToolName(\n update: AcpToolCallUpdate,\n bridgedToolNames: ReadonlySet<string> | undefined,\n): string {\n return (\n matchBridgedToolName(update.title, bridgedToolNames) ??\n update.kind ??\n 'tool'\n )\n}\n\nfunction stringifyToolOutput(update: AcpToolCallUpdate): string {\n if (update.rawOutput !== undefined) {\n return typeof update.rawOutput === 'string'\n ? update.rawOutput\n : JSON.stringify(update.rawOutput)\n }\n const blocks = update.content ?? []\n const text = blocks\n .map((block) =>\n block.content && typeof block.content.text === 'string'\n ? block.content.text\n : '',\n )\n .join('')\n // Preserve non-text tool content (diff / terminal / image / resource blocks)\n // by serializing the structured array, rather than collapsing to a stub.\n const hasNonText = blocks.some(\n (block) =>\n block.type !== 'content' ||\n (block.content !== undefined && block.content.type !== 'text'),\n )\n if (hasNonText) return JSON.stringify(blocks)\n if (text !== '') return text\n return JSON.stringify({ status: update.status ?? 'completed' })\n}\n\nfunction buildUsage(usage: AcpUsage | undefined): TokenUsage | undefined {\n if (!usage) return undefined\n const promptTokens = usage.inputTokens ?? 0\n const completionTokens = usage.outputTokens ?? 0\n const result = buildBaseUsage({\n promptTokens,\n completionTokens,\n totalTokens: usage.totalTokens ?? promptTokens + completionTokens,\n })\n if (usage.cachedReadTokens) {\n result.promptTokensDetails = { cachedTokens: usage.cachedReadTokens }\n }\n if (usage.thoughtTokens) {\n result.completionTokensDetails = { reasoningTokens: usage.thoughtTokens }\n }\n return result\n}\n\nexport async function* translateAcpStream(\n events: AsyncIterable<AcpStreamEvent>,\n ctx: TranslateContext,\n): AsyncIterable<StreamChunk> {\n const { model, runId, threadId, genId, labels } = ctx\n const now = () => Date.now()\n\n let runStarted = false\n const unresolvedToolCalls = new Set<string>()\n const knownToolCalls = new Set<string>()\n\n let textMessageId: string | null = null\n let textContent = ''\n let reasoningId: string | null = null\n\n function* startRun(): Generator<StreamChunk> {\n if (runStarted) return\n runStarted = true\n yield {\n type: EventType.RUN_STARTED,\n runId,\n threadId,\n model,\n timestamp: now(),\n ...(ctx.parentRunId !== undefined && { parentRunId: ctx.parentRunId }),\n }\n }\n\n function* closeText(): Generator<StreamChunk> {\n if (textMessageId !== null) {\n yield {\n type: EventType.TEXT_MESSAGE_END,\n messageId: textMessageId,\n model,\n timestamp: now(),\n }\n }\n textMessageId = null\n textContent = ''\n }\n\n function* closeReasoning(): Generator<StreamChunk> {\n if (reasoningId !== null) {\n yield {\n type: EventType.REASONING_MESSAGE_END,\n messageId: reasoningId,\n model,\n timestamp: now(),\n }\n yield {\n type: EventType.REASONING_END,\n messageId: reasoningId,\n model,\n timestamp: now(),\n }\n }\n reasoningId = null\n }\n\n function* synthesizeUnresolvedResults(): Generator<StreamChunk> {\n for (const toolCallId of unresolvedToolCalls) {\n yield {\n type: EventType.TOOL_CALL_RESULT,\n toolCallId,\n messageId: genId(),\n model,\n timestamp: now(),\n content: JSON.stringify({ status: 'interrupted' }),\n }\n }\n unresolvedToolCalls.clear()\n }\n\n function* openToolCall(update: AcpToolCallUpdate): Generator<StreamChunk> {\n if (knownToolCalls.has(update.toolCallId)) return\n knownToolCalls.add(update.toolCallId)\n const toolCallName = resolveToolName(update, ctx.bridgedToolNames)\n const input = {\n ...(update.title != null && { title: update.title }),\n ...(update.rawInput !== undefined && update.rawInput !== null\n ? typeof update.rawInput === 'object'\n ? (update.rawInput as Record<string, unknown>)\n : { input: update.rawInput }\n : {}),\n }\n const args = JSON.stringify(input)\n yield {\n type: EventType.TOOL_CALL_START,\n toolCallId: update.toolCallId,\n toolCallName,\n toolName: toolCallName,\n model,\n timestamp: now(),\n }\n yield {\n type: EventType.TOOL_CALL_ARGS,\n toolCallId: update.toolCallId,\n model,\n timestamp: now(),\n delta: args,\n args,\n }\n yield {\n type: EventType.TOOL_CALL_END,\n toolCallId: update.toolCallId,\n toolCallName,\n toolName: toolCallName,\n model,\n timestamp: now(),\n input,\n }\n unresolvedToolCalls.add(update.toolCallId)\n }\n\n function* resolveToolCall(update: AcpToolCallUpdate): Generator<StreamChunk> {\n yield* openToolCall(update)\n unresolvedToolCalls.delete(update.toolCallId)\n yield {\n type: EventType.TOOL_CALL_RESULT,\n toolCallId: update.toolCallId,\n messageId: genId(),\n model,\n timestamp: now(),\n content: stringifyToolOutput(update),\n ...(update.status === 'failed' && { state: 'output-error' as const }),\n }\n }\n\n function* handleUpdate(update: AcpSessionUpdate): Generator<StreamChunk> {\n if (update.sessionUpdate === 'agent_message_chunk') {\n yield* closeReasoning()\n // Non-text content (image / audio / resource / resource_link): surface it\n // as a CUSTOM event when the harness opted in, instead of dropping it.\n if (update.content.type !== 'text') {\n if (labels.contentEvent !== undefined) {\n yield* closeText()\n yield {\n type: EventType.CUSTOM,\n model,\n timestamp: now(),\n name: labels.contentEvent,\n value: { content: update.content },\n }\n }\n return\n }\n const text =\n typeof update.content.text === 'string' ? update.content.text : ''\n if (text === '') return\n if (textMessageId === null) {\n textMessageId = genId()\n yield {\n type: EventType.TEXT_MESSAGE_START,\n messageId: textMessageId,\n model,\n timestamp: now(),\n role: 'assistant',\n }\n }\n textContent += text\n yield {\n type: EventType.TEXT_MESSAGE_CONTENT,\n messageId: textMessageId,\n model,\n timestamp: now(),\n delta: text,\n content: textContent,\n }\n } else if (update.sessionUpdate === 'agent_thought_chunk') {\n yield* closeText()\n const thought =\n typeof update.content.text === 'string' ? update.content.text : ''\n if (thought === '') return\n if (reasoningId === null) {\n reasoningId = genId()\n yield {\n type: EventType.REASONING_START,\n messageId: reasoningId,\n model,\n timestamp: now(),\n }\n yield {\n type: EventType.REASONING_MESSAGE_START,\n messageId: reasoningId,\n role: 'reasoning' as const,\n model,\n timestamp: now(),\n }\n }\n yield {\n type: EventType.REASONING_MESSAGE_CONTENT,\n messageId: reasoningId,\n delta: thought,\n model,\n timestamp: now(),\n }\n } else if (update.sessionUpdate === 'tool_call') {\n yield* closeText()\n yield* closeReasoning()\n yield* openToolCall(update)\n if (update.status === 'completed' || update.status === 'failed') {\n yield* resolveToolCall(update)\n }\n } else if (update.sessionUpdate === 'tool_call_update') {\n if (update.status === 'completed' || update.status === 'failed') {\n yield* resolveToolCall(update)\n } else if (\n update.status === 'in_progress' &&\n update.rawInput !== undefined\n ) {\n yield* closeText()\n yield* closeReasoning()\n if (!knownToolCalls.has(update.toolCallId)) {\n yield* openToolCall(update)\n } else {\n const input = {\n ...(update.title != null && { title: update.title }),\n ...(typeof update.rawInput === 'object' && update.rawInput !== null\n ? (update.rawInput as Record<string, unknown>)\n : { input: update.rawInput }),\n }\n const args = JSON.stringify(input)\n yield {\n type: EventType.TOOL_CALL_ARGS,\n toolCallId: update.toolCallId,\n model,\n timestamp: now(),\n delta: args,\n args,\n }\n }\n }\n } else if (\n update.sessionUpdate === 'plan' &&\n labels.planEvent !== undefined\n ) {\n yield {\n type: EventType.CUSTOM,\n model,\n timestamp: now(),\n name: labels.planEvent,\n value: { entries: update.entries },\n }\n }\n }\n\n try {\n for await (const event of events) {\n ctx.onAcpEvent?.(event)\n\n if (event.kind === 'session') {\n yield* startRun()\n yield {\n type: EventType.CUSTOM,\n model,\n timestamp: now(),\n name: labels.sessionIdEvent,\n value: { sessionId: event.sessionId },\n }\n } else if (event.kind === 'update') {\n yield* startRun()\n yield* handleUpdate(event.update)\n } else {\n yield* startRun()\n yield* closeText()\n yield* closeReasoning()\n yield* synthesizeUnresolvedResults()\n\n if (event.stopReason === 'refusal') {\n const message =\n labels.refusalMessage ?? 'The harness refused the request.'\n yield {\n type: EventType.RUN_ERROR,\n model,\n timestamp: now(),\n message,\n code: 'refusal',\n error: { message, code: 'refusal' },\n }\n } else {\n const usage = buildUsage(event.usage)\n const finishReason =\n event.stopReason === 'max_tokens' ||\n event.stopReason === 'max_turn_requests'\n ? ('length' as const)\n : ('stop' as const)\n yield {\n type: EventType.RUN_FINISHED,\n runId,\n threadId,\n model,\n timestamp: now(),\n finishReason,\n ...(usage !== undefined && { usage }),\n }\n }\n }\n }\n } catch (error) {\n yield* closeText()\n yield* closeReasoning()\n yield* synthesizeUnresolvedResults()\n throw error\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"translate.js","names":[],"sources":["../../../src/stream/translate.ts"],"sourcesContent":["import { EventType, buildBaseUsage } from '@tanstack/ai'\nimport type { AdapterYieldChunk, TokenUsage } from '@tanstack/ai'\nimport type {\n AcpSessionUpdate,\n AcpStopReason,\n AcpToolCallUpdate,\n AcpUsage,\n} from '../types/acp-types'\n\nexport const BRIDGED_MCP_SERVER_NAME = 'tanstack'\n\nexport type AcpStreamEvent =\n | { kind: 'session'; sessionId: string }\n | { kind: 'update'; update: AcpSessionUpdate }\n | { kind: 'done'; stopReason: AcpStopReason; usage?: AcpUsage }\n\nexport interface AcpTranslateLabels {\n sessionIdEvent: string\n planEvent?: string\n refusalMessage?: string\n /**\n * When set, non-text agent message content (image / audio / resource /\n * resource_link blocks) is surfaced as a CUSTOM event under this name instead\n * of being dropped. Each event's `value` is `{ content: <ACP content block> }`.\n * Omit to keep the text-only behavior.\n */\n contentEvent?: string\n}\n\nexport interface TranslateContext {\n model: string\n runId: string\n threadId: string\n parentRunId?: string\n genId: () => string\n labels: AcpTranslateLabels\n bridgedToolNames?: ReadonlySet<string>\n onAcpEvent?: (event: AcpStreamEvent) => void\n}\n\nexport function matchBridgedToolName(\n title: string | null | undefined,\n bridgedToolNames: ReadonlySet<string> | undefined,\n): string | undefined {\n if (!title || !bridgedToolNames) return undefined\n if (bridgedToolNames.has(title)) return title\n for (const name of bridgedToolNames) {\n if (title.startsWith(`${name} (`)) return name\n }\n return undefined\n}\n\nfunction resolveToolName(\n update: AcpToolCallUpdate,\n bridgedToolNames: ReadonlySet<string> | undefined,\n): string {\n return (\n matchBridgedToolName(update.title, bridgedToolNames) ??\n update.kind ??\n 'tool'\n )\n}\n\nfunction stringifyToolOutput(update: AcpToolCallUpdate): string {\n if (update.rawOutput !== undefined) {\n return typeof update.rawOutput === 'string'\n ? update.rawOutput\n : JSON.stringify(update.rawOutput)\n }\n const blocks = update.content ?? []\n const text = blocks\n .map((block) =>\n block.content && typeof block.content.text === 'string'\n ? block.content.text\n : '',\n )\n .join('')\n // Preserve non-text tool content (diff / terminal / image / resource blocks)\n // by serializing the structured array, rather than collapsing to a stub.\n const hasNonText = blocks.some(\n (block) =>\n block.type !== 'content' ||\n (block.content !== undefined && block.content.type !== 'text'),\n )\n if (hasNonText) return JSON.stringify(blocks)\n if (text !== '') return text\n return JSON.stringify({ status: update.status ?? 'completed' })\n}\n\nfunction buildUsage(usage: AcpUsage | undefined): TokenUsage | undefined {\n if (!usage) return undefined\n const promptTokens = usage.inputTokens ?? 0\n const completionTokens = usage.outputTokens ?? 0\n const result = buildBaseUsage({\n promptTokens,\n completionTokens,\n totalTokens: usage.totalTokens ?? promptTokens + completionTokens,\n })\n if (usage.cachedReadTokens) {\n result.promptTokensDetails = { cachedTokens: usage.cachedReadTokens }\n }\n if (usage.thoughtTokens) {\n result.completionTokensDetails = { reasoningTokens: usage.thoughtTokens }\n }\n return result\n}\n\nexport async function* translateAcpStream(\n events: AsyncIterable<AcpStreamEvent>,\n ctx: TranslateContext,\n): AsyncIterable<AdapterYieldChunk> {\n const { model, runId, threadId, genId, labels } = ctx\n const now = () => Date.now()\n\n let runStarted = false\n const unresolvedToolCalls = new Set<string>()\n const knownToolCalls = new Set<string>()\n\n let textMessageId: string | null = null\n let textContent = ''\n let reasoningId: string | null = null\n\n function* startRun(): Generator<AdapterYieldChunk> {\n if (runStarted) return\n runStarted = true\n yield {\n type: EventType.RUN_STARTED,\n runId,\n threadId,\n model,\n timestamp: now(),\n ...(ctx.parentRunId !== undefined && { parentRunId: ctx.parentRunId }),\n }\n }\n\n function* closeText(): Generator<AdapterYieldChunk> {\n if (textMessageId !== null) {\n yield {\n type: EventType.TEXT_MESSAGE_END,\n messageId: textMessageId,\n model,\n timestamp: now(),\n }\n }\n textMessageId = null\n textContent = ''\n }\n\n function* closeReasoning(): Generator<AdapterYieldChunk> {\n if (reasoningId !== null) {\n yield {\n type: EventType.REASONING_MESSAGE_END,\n messageId: reasoningId,\n model,\n timestamp: now(),\n }\n yield {\n type: EventType.REASONING_END,\n messageId: reasoningId,\n model,\n timestamp: now(),\n }\n }\n reasoningId = null\n }\n\n function* synthesizeUnresolvedResults(): Generator<AdapterYieldChunk> {\n for (const toolCallId of unresolvedToolCalls) {\n yield {\n type: EventType.TOOL_CALL_RESULT,\n toolCallId,\n messageId: genId(),\n model,\n timestamp: now(),\n content: JSON.stringify({ status: 'interrupted' }),\n }\n }\n unresolvedToolCalls.clear()\n }\n\n function* openToolCall(\n update: AcpToolCallUpdate,\n ): Generator<AdapterYieldChunk> {\n if (knownToolCalls.has(update.toolCallId)) return\n knownToolCalls.add(update.toolCallId)\n const toolCallName = resolveToolName(update, ctx.bridgedToolNames)\n const input = {\n ...(update.title != null && { title: update.title }),\n ...(update.rawInput !== undefined && update.rawInput !== null\n ? typeof update.rawInput === 'object'\n ? (update.rawInput as Record<string, unknown>)\n : { input: update.rawInput }\n : {}),\n }\n const args = JSON.stringify(input)\n yield {\n type: EventType.TOOL_CALL_START,\n toolCallId: update.toolCallId,\n toolCallName,\n toolName: toolCallName,\n model,\n timestamp: now(),\n }\n yield {\n type: EventType.TOOL_CALL_ARGS,\n toolCallId: update.toolCallId,\n model,\n timestamp: now(),\n delta: args,\n args,\n }\n yield {\n type: EventType.TOOL_CALL_END,\n toolCallId: update.toolCallId,\n toolCallName,\n toolName: toolCallName,\n model,\n timestamp: now(),\n input,\n }\n unresolvedToolCalls.add(update.toolCallId)\n }\n\n function* resolveToolCall(\n update: AcpToolCallUpdate,\n ): Generator<AdapterYieldChunk> {\n yield* openToolCall(update)\n unresolvedToolCalls.delete(update.toolCallId)\n yield {\n type: EventType.TOOL_CALL_RESULT,\n toolCallId: update.toolCallId,\n messageId: genId(),\n model,\n timestamp: now(),\n content: stringifyToolOutput(update),\n ...(update.status === 'failed' && { state: 'output-error' as const }),\n }\n }\n\n function* handleUpdate(\n update: AcpSessionUpdate,\n ): Generator<AdapterYieldChunk> {\n if (update.sessionUpdate === 'agent_message_chunk') {\n yield* closeReasoning()\n // Non-text content (image / audio / resource / resource_link): surface it\n // as a CUSTOM event when the harness opted in, instead of dropping it.\n if (update.content.type !== 'text') {\n if (labels.contentEvent !== undefined) {\n yield* closeText()\n yield {\n type: EventType.CUSTOM,\n model,\n timestamp: now(),\n name: labels.contentEvent,\n value: { content: update.content },\n }\n }\n return\n }\n const text =\n typeof update.content.text === 'string' ? update.content.text : ''\n if (text === '') return\n if (textMessageId === null) {\n textMessageId = genId()\n yield {\n type: EventType.TEXT_MESSAGE_START,\n messageId: textMessageId,\n model,\n timestamp: now(),\n role: 'assistant',\n }\n }\n textContent += text\n yield {\n type: EventType.TEXT_MESSAGE_CONTENT,\n messageId: textMessageId,\n model,\n timestamp: now(),\n delta: text,\n content: textContent,\n }\n } else if (update.sessionUpdate === 'agent_thought_chunk') {\n yield* closeText()\n const thought =\n typeof update.content.text === 'string' ? update.content.text : ''\n if (thought === '') return\n if (reasoningId === null) {\n reasoningId = genId()\n yield {\n type: EventType.REASONING_START,\n messageId: reasoningId,\n model,\n timestamp: now(),\n }\n yield {\n type: EventType.REASONING_MESSAGE_START,\n messageId: reasoningId,\n role: 'reasoning' as const,\n model,\n timestamp: now(),\n }\n }\n yield {\n type: EventType.REASONING_MESSAGE_CONTENT,\n messageId: reasoningId,\n delta: thought,\n model,\n timestamp: now(),\n }\n } else if (update.sessionUpdate === 'tool_call') {\n yield* closeText()\n yield* closeReasoning()\n yield* openToolCall(update)\n if (update.status === 'completed' || update.status === 'failed') {\n yield* resolveToolCall(update)\n }\n } else if (update.sessionUpdate === 'tool_call_update') {\n if (update.status === 'completed' || update.status === 'failed') {\n yield* resolveToolCall(update)\n } else if (\n update.status === 'in_progress' &&\n update.rawInput !== undefined\n ) {\n yield* closeText()\n yield* closeReasoning()\n if (!knownToolCalls.has(update.toolCallId)) {\n yield* openToolCall(update)\n } else {\n const input = {\n ...(update.title != null && { title: update.title }),\n ...(typeof update.rawInput === 'object' && update.rawInput !== null\n ? (update.rawInput as Record<string, unknown>)\n : { input: update.rawInput }),\n }\n const args = JSON.stringify(input)\n yield {\n type: EventType.TOOL_CALL_ARGS,\n toolCallId: update.toolCallId,\n model,\n timestamp: now(),\n delta: args,\n args,\n }\n }\n }\n } else if (\n update.sessionUpdate === 'plan' &&\n labels.planEvent !== undefined\n ) {\n yield {\n type: EventType.CUSTOM,\n model,\n timestamp: now(),\n name: labels.planEvent,\n value: { entries: update.entries },\n }\n }\n }\n\n try {\n for await (const event of events) {\n ctx.onAcpEvent?.(event)\n\n if (event.kind === 'session') {\n yield* startRun()\n yield {\n type: EventType.CUSTOM,\n model,\n timestamp: now(),\n name: labels.sessionIdEvent,\n value: { sessionId: event.sessionId },\n }\n } else if (event.kind === 'update') {\n yield* startRun()\n yield* handleUpdate(event.update)\n } else {\n yield* startRun()\n yield* closeText()\n yield* closeReasoning()\n yield* synthesizeUnresolvedResults()\n\n if (event.stopReason === 'refusal') {\n const message =\n labels.refusalMessage ?? 'The harness refused the request.'\n yield {\n type: EventType.RUN_ERROR,\n model,\n timestamp: now(),\n message,\n code: 'refusal',\n error: { message, code: 'refusal' },\n }\n } else {\n const usage = buildUsage(event.usage)\n const finishReason =\n event.stopReason === 'max_tokens' ||\n event.stopReason === 'max_turn_requests'\n ? ('length' as const)\n : ('stop' as const)\n yield {\n type: EventType.RUN_FINISHED,\n runId,\n threadId,\n model,\n timestamp: now(),\n finishReason,\n ...(usage !== undefined && { usage }),\n }\n }\n }\n }\n } catch (error) {\n yield* closeText()\n yield* closeReasoning()\n yield* synthesizeUnresolvedResults()\n throw error\n }\n}\n"],"mappings":";;AASA,IAAa,0BAA0B;AA+BvC,SAAgB,qBACd,OACA,kBACoB;CACpB,IAAI,CAAC,SAAS,CAAC,kBAAkB,OAAO,KAAA;CACxC,IAAI,iBAAiB,IAAI,KAAK,GAAG,OAAO;CACxC,KAAK,MAAM,QAAQ,kBACjB,IAAI,MAAM,WAAW,GAAG,KAAK,GAAG,GAAG,OAAO;AAG9C;AAEA,SAAS,gBACP,QACA,kBACQ;CACR,OACE,qBAAqB,OAAO,OAAO,gBAAgB,KACnD,OAAO,QACP;AAEJ;AAEA,SAAS,oBAAoB,QAAmC;CAC9D,IAAI,OAAO,cAAc,KAAA,GACvB,OAAO,OAAO,OAAO,cAAc,WAC/B,OAAO,YACP,KAAK,UAAU,OAAO,SAAS;CAErC,MAAM,SAAS,OAAO,WAAW,CAAC;CAClC,MAAM,OAAO,OACV,KAAK,UACJ,MAAM,WAAW,OAAO,MAAM,QAAQ,SAAS,WAC3C,MAAM,QAAQ,OACd,EACN,CAAC,CACA,KAAK,EAAE;CAQV,IALmB,OAAO,MACvB,UACC,MAAM,SAAS,aACd,MAAM,YAAY,KAAA,KAAa,MAAM,QAAQ,SAAS,MAEvD,GAAY,OAAO,KAAK,UAAU,MAAM;CAC5C,IAAI,SAAS,IAAI,OAAO;CACxB,OAAO,KAAK,UAAU,EAAE,QAAQ,OAAO,UAAU,YAAY,CAAC;AAChE;AAEA,SAAS,WAAW,OAAqD;CACvE,IAAI,CAAC,OAAO,OAAO,KAAA;CACnB,MAAM,eAAe,MAAM,eAAe;CAC1C,MAAM,mBAAmB,MAAM,gBAAgB;CAC/C,MAAM,SAAS,eAAe;EAC5B;EACA;EACA,aAAa,MAAM,eAAe,eAAe;CACnD,CAAC;CACD,IAAI,MAAM,kBACR,OAAO,sBAAsB,EAAE,cAAc,MAAM,iBAAiB;CAEtE,IAAI,MAAM,eACR,OAAO,0BAA0B,EAAE,iBAAiB,MAAM,cAAc;CAE1E,OAAO;AACT;AAEA,gBAAuB,mBACrB,QACA,KACkC;CAClC,MAAM,EAAE,OAAO,OAAO,UAAU,OAAO,WAAW;CAClD,MAAM,YAAY,KAAK,IAAI;CAE3B,IAAI,aAAa;CACjB,MAAM,sCAAsB,IAAI,IAAY;CAC5C,MAAM,iCAAiB,IAAI,IAAY;CAEvC,IAAI,gBAA+B;CACnC,IAAI,cAAc;CAClB,IAAI,cAA6B;CAEjC,UAAU,WAAyC;EACjD,IAAI,YAAY;EAChB,aAAa;EACb,MAAM;GACJ,MAAM,UAAU;GAChB;GACA;GACA;GACA,WAAW,IAAI;GACf,GAAI,IAAI,gBAAgB,KAAA,KAAa,EAAE,aAAa,IAAI,YAAY;EACtE;CACF;CAEA,UAAU,YAA0C;EAClD,IAAI,kBAAkB,MACpB,MAAM;GACJ,MAAM,UAAU;GAChB,WAAW;GACX;GACA,WAAW,IAAI;EACjB;EAEF,gBAAgB;EAChB,cAAc;CAChB;CAEA,UAAU,iBAA+C;EACvD,IAAI,gBAAgB,MAAM;GACxB,MAAM;IACJ,MAAM,UAAU;IAChB,WAAW;IACX;IACA,WAAW,IAAI;GACjB;GACA,MAAM;IACJ,MAAM,UAAU;IAChB,WAAW;IACX;IACA,WAAW,IAAI;GACjB;EACF;EACA,cAAc;CAChB;CAEA,UAAU,8BAA4D;EACpE,KAAK,MAAM,cAAc,qBACvB,MAAM;GACJ,MAAM,UAAU;GAChB;GACA,WAAW,MAAM;GACjB;GACA,WAAW,IAAI;GACf,SAAS,KAAK,UAAU,EAAE,QAAQ,cAAc,CAAC;EACnD;EAEF,oBAAoB,MAAM;CAC5B;CAEA,UAAU,aACR,QAC8B;EAC9B,IAAI,eAAe,IAAI,OAAO,UAAU,GAAG;EAC3C,eAAe,IAAI,OAAO,UAAU;EACpC,MAAM,eAAe,gBAAgB,QAAQ,IAAI,gBAAgB;EACjE,MAAM,QAAQ;GACZ,GAAI,OAAO,SAAS,QAAQ,EAAE,OAAO,OAAO,MAAM;GAClD,GAAI,OAAO,aAAa,KAAA,KAAa,OAAO,aAAa,OACrD,OAAO,OAAO,aAAa,WACxB,OAAO,WACR,EAAE,OAAO,OAAO,SAAS,IAC3B,CAAC;EACP;EACA,MAAM,OAAO,KAAK,UAAU,KAAK;EACjC,MAAM;GACJ,MAAM,UAAU;GAChB,YAAY,OAAO;GACnB;GACA,UAAU;GACV;GACA,WAAW,IAAI;EACjB;EACA,MAAM;GACJ,MAAM,UAAU;GAChB,YAAY,OAAO;GACnB;GACA,WAAW,IAAI;GACf,OAAO;GACP;EACF;EACA,MAAM;GACJ,MAAM,UAAU;GAChB,YAAY,OAAO;GACnB;GACA,UAAU;GACV;GACA,WAAW,IAAI;GACf;EACF;EACA,oBAAoB,IAAI,OAAO,UAAU;CAC3C;CAEA,UAAU,gBACR,QAC8B;EAC9B,OAAO,aAAa,MAAM;EAC1B,oBAAoB,OAAO,OAAO,UAAU;EAC5C,MAAM;GACJ,MAAM,UAAU;GAChB,YAAY,OAAO;GACnB,WAAW,MAAM;GACjB;GACA,WAAW,IAAI;GACf,SAAS,oBAAoB,MAAM;GACnC,GAAI,OAAO,WAAW,YAAY,EAAE,OAAO,eAAwB;EACrE;CACF;CAEA,UAAU,aACR,QAC8B;EAC9B,IAAI,OAAO,kBAAkB,uBAAuB;GAClD,OAAO,eAAe;GAGtB,IAAI,OAAO,QAAQ,SAAS,QAAQ;IAClC,IAAI,OAAO,iBAAiB,KAAA,GAAW;KACrC,OAAO,UAAU;KACjB,MAAM;MACJ,MAAM,UAAU;MAChB;MACA,WAAW,IAAI;MACf,MAAM,OAAO;MACb,OAAO,EAAE,SAAS,OAAO,QAAQ;KACnC;IACF;IACA;GACF;GACA,MAAM,OACJ,OAAO,OAAO,QAAQ,SAAS,WAAW,OAAO,QAAQ,OAAO;GAClE,IAAI,SAAS,IAAI;GACjB,IAAI,kBAAkB,MAAM;IAC1B,gBAAgB,MAAM;IACtB,MAAM;KACJ,MAAM,UAAU;KAChB,WAAW;KACX;KACA,WAAW,IAAI;KACf,MAAM;IACR;GACF;GACA,eAAe;GACf,MAAM;IACJ,MAAM,UAAU;IAChB,WAAW;IACX;IACA,WAAW,IAAI;IACf,OAAO;IACP,SAAS;GACX;EACF,OAAO,IAAI,OAAO,kBAAkB,uBAAuB;GACzD,OAAO,UAAU;GACjB,MAAM,UACJ,OAAO,OAAO,QAAQ,SAAS,WAAW,OAAO,QAAQ,OAAO;GAClE,IAAI,YAAY,IAAI;GACpB,IAAI,gBAAgB,MAAM;IACxB,cAAc,MAAM;IACpB,MAAM;KACJ,MAAM,UAAU;KAChB,WAAW;KACX;KACA,WAAW,IAAI;IACjB;IACA,MAAM;KACJ,MAAM,UAAU;KAChB,WAAW;KACX,MAAM;KACN;KACA,WAAW,IAAI;IACjB;GACF;GACA,MAAM;IACJ,MAAM,UAAU;IAChB,WAAW;IACX,OAAO;IACP;IACA,WAAW,IAAI;GACjB;EACF,OAAO,IAAI,OAAO,kBAAkB,aAAa;GAC/C,OAAO,UAAU;GACjB,OAAO,eAAe;GACtB,OAAO,aAAa,MAAM;GAC1B,IAAI,OAAO,WAAW,eAAe,OAAO,WAAW,UACrD,OAAO,gBAAgB,MAAM;EAEjC,OAAO,IAAI,OAAO,kBAAkB,oBAAoB;GACtD,IAAI,OAAO,WAAW,eAAe,OAAO,WAAW,UACrD,OAAO,gBAAgB,MAAM;QACxB,IACL,OAAO,WAAW,iBAClB,OAAO,aAAa,KAAA,GACpB;IACA,OAAO,UAAU;IACjB,OAAO,eAAe;IACtB,IAAI,CAAC,eAAe,IAAI,OAAO,UAAU,GACvC,OAAO,aAAa,MAAM;SACrB;KACL,MAAM,QAAQ;MACZ,GAAI,OAAO,SAAS,QAAQ,EAAE,OAAO,OAAO,MAAM;MAClD,GAAI,OAAO,OAAO,aAAa,YAAY,OAAO,aAAa,OAC1D,OAAO,WACR,EAAE,OAAO,OAAO,SAAS;KAC/B;KACA,MAAM,OAAO,KAAK,UAAU,KAAK;KACjC,MAAM;MACJ,MAAM,UAAU;MAChB,YAAY,OAAO;MACnB;MACA,WAAW,IAAI;MACf,OAAO;MACP;KACF;IACF;GACF;EACF,OAAO,IACL,OAAO,kBAAkB,UACzB,OAAO,cAAc,KAAA,GAErB,MAAM;GACJ,MAAM,UAAU;GAChB;GACA,WAAW,IAAI;GACf,MAAM,OAAO;GACb,OAAO,EAAE,SAAS,OAAO,QAAQ;EACnC;CAEJ;CAEA,IAAI;EACF,WAAW,MAAM,SAAS,QAAQ;GAChC,IAAI,aAAa,KAAK;GAEtB,IAAI,MAAM,SAAS,WAAW;IAC5B,OAAO,SAAS;IAChB,MAAM;KACJ,MAAM,UAAU;KAChB;KACA,WAAW,IAAI;KACf,MAAM,OAAO;KACb,OAAO,EAAE,WAAW,MAAM,UAAU;IACtC;GACF,OAAO,IAAI,MAAM,SAAS,UAAU;IAClC,OAAO,SAAS;IAChB,OAAO,aAAa,MAAM,MAAM;GAClC,OAAO;IACL,OAAO,SAAS;IAChB,OAAO,UAAU;IACjB,OAAO,eAAe;IACtB,OAAO,4BAA4B;IAEnC,IAAI,MAAM,eAAe,WAAW;KAClC,MAAM,UACJ,OAAO,kBAAkB;KAC3B,MAAM;MACJ,MAAM,UAAU;MAChB;MACA,WAAW,IAAI;MACf;MACA,MAAM;MACN,OAAO;OAAE;OAAS,MAAM;MAAU;KACpC;IACF,OAAO;KACL,MAAM,QAAQ,WAAW,MAAM,KAAK;KACpC,MAAM,eACJ,MAAM,eAAe,gBACrB,MAAM,eAAe,sBAChB,WACA;KACP,MAAM;MACJ,MAAM,UAAU;MAChB;MACA;MACA;MACA,WAAW,IAAI;MACf;MACA,GAAI,UAAU,KAAA,KAAa,EAAE,MAAM;KACrC;IACF;GACF;EACF;CACF,SAAS,OAAO;EACd,OAAO,UAAU;EACjB,OAAO,eAAe;EACtB,OAAO,4BAA4B;EACnC,MAAM;CACR;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/ai-acp",
3
- "version": "0.3.3",
3
+ "version": "0.3.5",
4
4
  "description": "Shared Agent Client Protocol (ACP) transport, session, and AG-UI translation for TanStack AI harness adapters.",
5
5
  "author": "",
6
6
  "license": "MIT",
@@ -36,14 +36,14 @@
36
36
  "@agentclientprotocol/sdk": "^0.25.0"
37
37
  },
38
38
  "peerDependencies": {
39
- "@tanstack/ai": "^0.47.1",
40
- "@tanstack/ai-sandbox": "^0.4.0"
39
+ "@tanstack/ai": "^0.49.1",
40
+ "@tanstack/ai-sandbox": "^0.5.1"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@vitest/coverage-v8": "4.1.10",
44
- "@tanstack/ai": "0.47.1",
45
- "@tanstack/ai-sandbox": "0.4.0",
46
- "@tanstack/ai-sandbox-local-process": "0.2.3"
44
+ "@tanstack/ai": "0.49.1",
45
+ "@tanstack/ai-sandbox": "0.5.1",
46
+ "@tanstack/ai-sandbox-local-process": "0.2.4"
47
47
  },
48
48
  "scripts": {
49
49
  "build": "vite build",
@@ -37,7 +37,7 @@ import type {
37
37
  DefaultMessageMetadataByModality,
38
38
  Modality,
39
39
  ModelMessage,
40
- StreamChunk,
40
+ AdapterYieldChunk,
41
41
  TextOptions,
42
42
  } from '@tanstack/ai'
43
43
  import type { AcpSessionHandle } from '../session/acp-client'
@@ -306,7 +306,7 @@ export class AcpCompatibleTextAdapter<
306
306
  mode: AcpPermissionMode
307
307
  bridgedToolNames: ReadonlySet<string>
308
308
  approvals: ReadonlyMap<string, boolean> | undefined
309
- approvalRequests: Array<StreamChunk>
309
+ approvalRequests: Array<AdapterYieldChunk>
310
310
  threadId: string
311
311
  runId: string
312
312
  }): PermissionHandler {
@@ -346,7 +346,7 @@ export class AcpCompatibleTextAdapter<
346
346
 
347
347
  async *chatStream(
348
348
  options: TextOptions<ResolvedOptions<TModelOptions>>,
349
- ): AsyncIterable<StreamChunk> {
349
+ ): AsyncIterable<AdapterYieldChunk> {
350
350
  const { logger } = options
351
351
  let handle: AcpSessionHandle | undefined
352
352
  let bridge: HostToolBridge | undefined
@@ -479,7 +479,7 @@ export class AcpCompatibleTextAdapter<
479
479
  ? undefined
480
480
  : (modelOptions?.authMethodId ?? this.harness.authMethodId)
481
481
 
482
- const approvalRequests: Array<StreamChunk> = []
482
+ const approvalRequests: Array<AdapterYieldChunk> = []
483
483
  const permissionHandler = this.makePermissionHandler({
484
484
  mode,
485
485
  bridgedToolNames,
@@ -568,7 +568,7 @@ export class AcpCompatibleTextAdapter<
568
568
  const wantsStructured = options.outputSchema !== undefined
569
569
  let lastAssistantText = ''
570
570
  let lastTextMessageId: string | undefined
571
- let heldFinished: StreamChunk | undefined
571
+ let heldFinished: AdapterYieldChunk | undefined
572
572
  for await (const chunk of mergeChunkStreams(
573
573
  translateAcpStream(queue, {
574
574
  model: this.model,
@@ -692,7 +692,7 @@ export class AcpCompatibleTextAdapter<
692
692
  cwd: string,
693
693
  threadId: string,
694
694
  runId: string,
695
- ): AsyncIterable<StreamChunk> {
695
+ ): AsyncIterable<AdapterYieldChunk> {
696
696
  try {
697
697
  const diff = await sandbox.process.exec(`git -C ${q(cwd)} diff`, { cwd })
698
698
  if (diff.exitCode === 0 && diff.stdout.trim() !== '') {
@@ -715,7 +715,7 @@ export class AcpCompatibleTextAdapter<
715
715
  threadId: string,
716
716
  runId: string,
717
717
  messageId = this.generateId(),
718
- ): Generator<StreamChunk> {
718
+ ): Generator<AdapterYieldChunk> {
719
719
  try {
720
720
  const object = parseJsonFromAssistantText(raw)
721
721
  yield structuredOutputStartChunk({
@@ -1,5 +1,5 @@
1
1
  import { EventType, buildBaseUsage } from '@tanstack/ai'
2
- import type { StreamChunk, TokenUsage } from '@tanstack/ai'
2
+ import type { AdapterYieldChunk, TokenUsage } from '@tanstack/ai'
3
3
  import type {
4
4
  AcpSessionUpdate,
5
5
  AcpStopReason,
@@ -108,7 +108,7 @@ function buildUsage(usage: AcpUsage | undefined): TokenUsage | undefined {
108
108
  export async function* translateAcpStream(
109
109
  events: AsyncIterable<AcpStreamEvent>,
110
110
  ctx: TranslateContext,
111
- ): AsyncIterable<StreamChunk> {
111
+ ): AsyncIterable<AdapterYieldChunk> {
112
112
  const { model, runId, threadId, genId, labels } = ctx
113
113
  const now = () => Date.now()
114
114
 
@@ -120,7 +120,7 @@ export async function* translateAcpStream(
120
120
  let textContent = ''
121
121
  let reasoningId: string | null = null
122
122
 
123
- function* startRun(): Generator<StreamChunk> {
123
+ function* startRun(): Generator<AdapterYieldChunk> {
124
124
  if (runStarted) return
125
125
  runStarted = true
126
126
  yield {
@@ -133,7 +133,7 @@ export async function* translateAcpStream(
133
133
  }
134
134
  }
135
135
 
136
- function* closeText(): Generator<StreamChunk> {
136
+ function* closeText(): Generator<AdapterYieldChunk> {
137
137
  if (textMessageId !== null) {
138
138
  yield {
139
139
  type: EventType.TEXT_MESSAGE_END,
@@ -146,7 +146,7 @@ export async function* translateAcpStream(
146
146
  textContent = ''
147
147
  }
148
148
 
149
- function* closeReasoning(): Generator<StreamChunk> {
149
+ function* closeReasoning(): Generator<AdapterYieldChunk> {
150
150
  if (reasoningId !== null) {
151
151
  yield {
152
152
  type: EventType.REASONING_MESSAGE_END,
@@ -164,7 +164,7 @@ export async function* translateAcpStream(
164
164
  reasoningId = null
165
165
  }
166
166
 
167
- function* synthesizeUnresolvedResults(): Generator<StreamChunk> {
167
+ function* synthesizeUnresolvedResults(): Generator<AdapterYieldChunk> {
168
168
  for (const toolCallId of unresolvedToolCalls) {
169
169
  yield {
170
170
  type: EventType.TOOL_CALL_RESULT,
@@ -178,7 +178,9 @@ export async function* translateAcpStream(
178
178
  unresolvedToolCalls.clear()
179
179
  }
180
180
 
181
- function* openToolCall(update: AcpToolCallUpdate): Generator<StreamChunk> {
181
+ function* openToolCall(
182
+ update: AcpToolCallUpdate,
183
+ ): Generator<AdapterYieldChunk> {
182
184
  if (knownToolCalls.has(update.toolCallId)) return
183
185
  knownToolCalls.add(update.toolCallId)
184
186
  const toolCallName = resolveToolName(update, ctx.bridgedToolNames)
@@ -219,7 +221,9 @@ export async function* translateAcpStream(
219
221
  unresolvedToolCalls.add(update.toolCallId)
220
222
  }
221
223
 
222
- function* resolveToolCall(update: AcpToolCallUpdate): Generator<StreamChunk> {
224
+ function* resolveToolCall(
225
+ update: AcpToolCallUpdate,
226
+ ): Generator<AdapterYieldChunk> {
223
227
  yield* openToolCall(update)
224
228
  unresolvedToolCalls.delete(update.toolCallId)
225
229
  yield {
@@ -233,7 +237,9 @@ export async function* translateAcpStream(
233
237
  }
234
238
  }
235
239
 
236
- function* handleUpdate(update: AcpSessionUpdate): Generator<StreamChunk> {
240
+ function* handleUpdate(
241
+ update: AcpSessionUpdate,
242
+ ): Generator<AdapterYieldChunk> {
237
243
  if (update.sessionUpdate === 'agent_message_chunk') {
238
244
  yield* closeReasoning()
239
245
  // Non-text content (image / audio / resource / resource_link): surface it