@tanstack/ai-claude-code 0.2.3 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,306 +1,281 @@
1
- import { normalizeSystemPrompts, EventType } from "@tanstack/ai";
2
- import { toRunErrorRawEvent } from "@tanstack/ai/adapter-internals";
3
- import { BaseTextAdapter } from "@tanstack/ai/adapters";
4
- import { SandboxCapability, getSandbox, approvalId, resolveApproval, buildApprovalRequestedEvent, createBridgeEventChannel, getWorkspaceProjection, getSandboxPolicy, getToolBridgeProvisioner, nodeHttpBridgeProvisioner, spawnNdjson, mergeChunkStreams } from "@tanstack/ai-sandbox";
5
1
  import { buildPrompt } from "../messages/prompt.js";
6
2
  import { translateSdkStream } from "../stream/translate.js";
7
3
  import { mapPolicyToClaudeFlags } from "./policy-map.js";
8
4
  import { projectClaudeWorkspace } from "./projection.js";
9
- const DEFAULT_WORKDIR = "/workspace";
5
+ import { EventType, normalizeSystemPrompts } from "@tanstack/ai";
6
+ import { toRunErrorRawEvent } from "@tanstack/ai/adapter-internals";
7
+ import { BaseTextAdapter } from "@tanstack/ai/adapters";
8
+ import { SandboxCapability, alignedIfAttaching, approvalId, buildApprovalRequestedEvent, createBridgeEventChannel, createRunScopedIdGen, encodeRunId, getSandbox, getSandboxDurability, getSandboxPolicy, getToolBridgeProvisioner, getWorkspaceProjection, journalOptionsFor, mergeChunkStreams, nodeHttpBridgeProvisioner, resolveApproval, resolveDurableRunId, resolveDurableThreadId, spawnNdjson } from "@tanstack/ai-sandbox";
9
+ //#region src/adapters/text.ts
10
+ var DEFAULT_WORKDIR = "/workspace";
11
+ /** POSIX single-quote escape for embedding values in the `claude …` command. */
10
12
  function q(value) {
11
- return `'${value.replace(/'/g, `'\\''`)}'`;
13
+ return `'${value.replace(/'/g, `'\\''`)}'`;
12
14
  }
15
+ /** Format a host tool-bridge as claude's `--mcp-config` JSON. */
13
16
  function bridgeToMcpConfig(bridge) {
14
- return JSON.stringify({
15
- mcpServers: {
16
- [bridge.name]: {
17
- type: "http",
18
- url: bridge.url,
19
- headers: { Authorization: `Bearer ${bridge.token}` }
20
- }
21
- }
22
- });
23
- }
24
- class ClaudeCodeTextAdapter extends BaseTextAdapter {
25
- name = "claude-code";
26
- // Harness adapter: requires a sandbox to run the agent CLI inside.
27
- requires = [SandboxCapability];
28
- adapterConfig;
29
- constructor(config, model) {
30
- super({}, model);
31
- this.adapterConfig = config;
32
- }
33
- sandboxFrom(options) {
34
- const ctx = options.capabilities;
35
- if (!ctx) {
36
- throw new Error(
37
- 'Adapter "claude-code" requires a sandbox. Add withSandbox(defineSandbox({ ... })) to chat() middleware (e.g. with the local-process or docker provider).'
38
- );
39
- }
40
- return getSandbox(ctx);
41
- }
42
- workdir(options) {
43
- return options.modelOptions?.cwd ?? this.adapterConfig.cwd ?? DEFAULT_WORKDIR;
44
- }
45
- /** Build the `claude` command line (prompt goes via stdin, not argv). */
46
- buildCommand(options, resume, policyFlags, mcpConfigPath, permissionPromptTool) {
47
- const config = this.adapterConfig;
48
- const modelOptions = options.modelOptions;
49
- const exe = config.claudeExecutable ?? "claude";
50
- const args = [
51
- "-p",
52
- "--output-format",
53
- "stream-json",
54
- "--verbose",
55
- "--model",
56
- q(this.model)
57
- ];
58
- if (config.streamPartials !== false) args.push("--include-partial-messages");
59
- if (resume !== void 0) args.push("--resume", q(resume));
60
- const permissionMode = modelOptions?.permissionMode ?? config.permissionMode ?? policyFlags.permissionMode ?? "bypassPermissions";
61
- args.push("--permission-mode", q(permissionMode));
62
- const maxTurns = modelOptions?.maxTurns ?? config.maxTurns;
63
- if (maxTurns !== void 0) args.push("--max-turns", String(maxTurns));
64
- for (const dir of config.addDirs ?? []) args.push("--add-dir", q(dir));
65
- const allowedTools = [
66
- ...modelOptions?.allowedTools ?? config.allowedTools ?? [],
67
- ...policyFlags.allowedTools
68
- ];
69
- if (allowedTools.length > 0) {
70
- args.push("--allowedTools", q([...new Set(allowedTools)].join(",")));
71
- }
72
- const disallowedTools = [
73
- ...modelOptions?.disallowedTools ?? config.disallowedTools ?? [],
74
- ...policyFlags.disallowedTools
75
- ];
76
- if (disallowedTools.length > 0) {
77
- args.push("--disallowedTools", q([...new Set(disallowedTools)].join(",")));
78
- }
79
- const systemPrompts = normalizeSystemPrompts(options.systemPrompts).map((prompt) => prompt.content).filter((content) => content.trim() !== "");
80
- if (systemPrompts.length > 0) {
81
- const joined = systemPrompts.join("\n\n");
82
- const flag = config.systemPromptMode === "replace" ? "--system-prompt" : "--append-system-prompt";
83
- args.push(flag, q(joined));
84
- }
85
- if (mcpConfigPath !== void 0) args.push("--mcp-config", q(mcpConfigPath));
86
- if (permissionPromptTool !== void 0) {
87
- args.push("--permission-prompt-tool", q(permissionPromptTool));
88
- }
89
- return `${exe} ${args.join(" ")}`;
90
- }
91
- /**
92
- * Build the permission-prompt resolver the host MCP bridge exposes to claude
93
- * (`--permission-prompt-tool`). Maps claude's permission request onto the
94
- * sandbox policy + client approvals; on an `ask` action with no decision yet,
95
- * records an approval-requested event and denies (the client re-runs to grant).
96
- */
97
- buildPermissionResolver(policy, approvals, scripts, sink, threadId, runId) {
98
- const writeTools = /* @__PURE__ */ new Set(["Write", "Edit", "MultiEdit", "NotebookEdit"]);
99
- const networkTools = /* @__PURE__ */ new Set(["WebFetch", "WebSearch"]);
100
- return (request) => {
101
- const toolName = request.tool_name ?? "tool";
102
- const cmdInput = request.input;
103
- const command = toolName === "Bash" && cmdInput !== null && typeof cmdInput === "object" && "command" in cmdInput && typeof cmdInput.command === "string" ? cmdInput.command : void 0;
104
- const capability = writeTools.has(toolName) ? "fileWrite" : networkTools.has(toolName) ? "network" : void 0;
105
- const id = approvalId({
106
- provider: "claude-code",
107
- kind: command !== void 0 ? "command" : capability ?? "tool",
108
- target: command ?? toolName
109
- });
110
- const outcome = resolveApproval({
111
- policy,
112
- approvals,
113
- id,
114
- scripts,
115
- ...command !== void 0 ? { command } : {},
116
- ...capability !== void 0 ? { capability } : {}
117
- });
118
- if (outcome.needsApproval) {
119
- sink.push(
120
- buildApprovalRequestedEvent({
121
- approvalId: id,
122
- title: `Approve ${toolName}${command !== void 0 ? `: ${command}` : ""}`,
123
- threadId,
124
- runId,
125
- detail: { provider: "claude-code", toolName }
126
- })
127
- );
128
- return {
129
- behavior: "deny",
130
- message: "Awaiting client approval. Approve in the UI and re-run to continue."
131
- };
132
- }
133
- return outcome.decision === "allow" ? { behavior: "allow" } : { behavior: "deny", message: "Denied by sandbox policy." };
134
- };
135
- }
136
- async *chatStream(options) {
137
- const { logger } = options;
138
- let bridge;
139
- let channel;
140
- const approvalRequests = [];
141
- let cleanupSandbox;
142
- const tempFiles = [];
143
- try {
144
- const sandbox = this.sandboxFrom(options);
145
- cleanupSandbox = sandbox;
146
- const cwd = this.workdir(options);
147
- const runId = options.runId ?? this.generateId();
148
- const threadId = options.threadId ?? this.generateId();
149
- channel = createBridgeEventChannel({ model: this.model, threadId, runId });
150
- const projection = options.capabilities ? getWorkspaceProjection(options.capabilities, { optional: true }) : void 0;
151
- if (projection) await projectClaudeWorkspace(sandbox, projection);
152
- const policy = options.capabilities ? getSandboxPolicy(options.capabilities, { optional: true }) : void 0;
153
- const permission = policy !== void 0 ? {
154
- toolName: "approval_prompt",
155
- resolve: this.buildPermissionResolver(
156
- policy,
157
- options.approvals,
158
- projection?.scripts,
159
- approvalRequests,
160
- threadId,
161
- runId
162
- )
163
- } : void 0;
164
- const hasTools = options.tools !== void 0 && options.tools.length > 0;
165
- if (hasTools || permission !== void 0) {
166
- const provisioner = (options.capabilities ? getToolBridgeProvisioner(options.capabilities, { optional: true }) : void 0) ?? nodeHttpBridgeProvisioner;
167
- bridge = await provisioner.provision(options.tools ?? [], {
168
- provider: sandbox.provider,
169
- context: options.context,
170
- emitCustomEvent: channel.emitCustomEvent,
171
- ...permission !== void 0 ? { permission } : {},
172
- ...options.abortController?.signal ? { signal: options.abortController.signal } : {}
173
- });
174
- }
175
- const { prompt, resume } = buildPrompt(
176
- options.messages,
177
- options.modelOptions?.sessionId
178
- );
179
- let mcpConfigArg;
180
- if (bridge) {
181
- const mcpConfigFile = `.tanstack-mcp-bridge-${runId}.json`;
182
- const mcpConfigPath = `${cwd}/${mcpConfigFile}`;
183
- await sandbox.fs.write(mcpConfigPath, bridgeToMcpConfig(bridge));
184
- tempFiles.push(mcpConfigPath);
185
- mcpConfigArg = mcpConfigFile;
186
- }
187
- const command = this.buildCommand(
188
- options,
189
- resume,
190
- mapPolicyToClaudeFlags(policy),
191
- mcpConfigArg,
192
- bridge && permission ? `mcp__${bridge.name}__${permission.toolName}` : void 0
193
- );
194
- let runCommand = command;
195
- let stdinInput = prompt;
196
- if (sandbox.capabilities.writableStdin === false) {
197
- const promptPath = `/tmp/tanstack-claude-prompt-${runId}`;
198
- await sandbox.fs.write(promptPath, prompt);
199
- tempFiles.push(promptPath);
200
- runCommand = `${command} < ${q(promptPath)}`;
201
- stdinInput = void 0;
202
- }
203
- logger.request(
204
- `activity=chat provider=claude-code model=${this.model} sandbox=${sandbox.provider} messages=${options.messages.length} resume=${resume ?? "none"}`,
205
- { provider: "claude-code", model: this.model }
206
- );
207
- const rawEvents = spawnNdjson(sandbox, runCommand, {
208
- cwd,
209
- ...stdinInput !== void 0 ? { input: stdinInput } : {},
210
- // claude maps `bypassPermissions` to `--dangerously-skip-permissions`,
211
- // which it refuses to run as root. Sandbox containers routinely run as
212
- // root (Docker / Cloudflare), so set `IS_SANDBOX=1` — claude's
213
- // documented escape hatch for skip-permissions in an isolated
214
- // environment — merged over the sandbox env (a caller-provided value
215
- // wins). Safe to set unconditionally; it is a no-op for stricter modes.
216
- env: { IS_SANDBOX: "1", ...this.adapterConfig.env },
217
- ...options.abortController?.signal ? { signal: options.abortController.signal } : options.request?.signal ? { signal: options.request.signal } : {},
218
- onNonJsonLine: (line) => logger.provider(`provider=claude-code non-json line: ${line}`, {
219
- chunk: line
220
- })
221
- });
222
- async function* asMessages() {
223
- for await (const event of rawEvents) yield event;
224
- }
225
- yield* mergeChunkStreams(
226
- translateSdkStream(asMessages(), {
227
- model: this.model,
228
- runId,
229
- threadId,
230
- ...options.parentRunId !== void 0 && {
231
- parentRunId: options.parentRunId
232
- },
233
- genId: () => this.generateId(),
234
- onSdkMessage: (message) => logger.provider(`provider=claude-code type=${message.type}`, {
235
- chunk: message
236
- })
237
- }),
238
- channel.stream
239
- );
240
- if (this.adapterConfig.emitDiff !== false) {
241
- try {
242
- const diff = await sandbox.process.exec(`git -C ${q(cwd)} diff`, {
243
- cwd
244
- });
245
- if (diff.exitCode === 0 && diff.stdout.trim() !== "") {
246
- yield {
247
- type: EventType.CUSTOM,
248
- name: "file.changed",
249
- value: { path: ".", diff: diff.stdout },
250
- timestamp: Date.now(),
251
- threadId,
252
- runId
253
- };
254
- }
255
- } catch {
256
- }
257
- }
258
- for (const event of approvalRequests) yield event;
259
- } catch (error) {
260
- const err = error;
261
- const rawEvent = toRunErrorRawEvent(error);
262
- logger.errors("claude-code.chatStream fatal", {
263
- error,
264
- source: "claude-code.chatStream"
265
- });
266
- yield {
267
- type: EventType.RUN_ERROR,
268
- model: options.model,
269
- timestamp: Date.now(),
270
- message: err.message || "Unknown error occurred",
271
- ...err.code !== void 0 && { code: err.code },
272
- ...rawEvent !== void 0 && { rawEvent },
273
- error: {
274
- message: err.message || "Unknown error occurred",
275
- ...err.code !== void 0 && { code: err.code }
276
- }
277
- };
278
- } finally {
279
- channel?.close();
280
- if (bridge) await bridge.close();
281
- if (cleanupSandbox) {
282
- for (const path of tempFiles) {
283
- try {
284
- await cleanupSandbox.fs.remove(path);
285
- } catch {
286
- }
287
- }
288
- }
289
- }
290
- }
291
- structuredOutput(_options) {
292
- return Promise.reject(
293
- new Error(
294
- "Structured output is not yet supported by the in-sandbox Claude Code adapter. Use a model adapter (e.g. anthropic) for structured output, or omit outputSchema."
295
- )
296
- );
297
- }
17
+ return JSON.stringify({ mcpServers: { [bridge.name]: {
18
+ type: "http",
19
+ url: bridge.url,
20
+ headers: { Authorization: `Bearer ${bridge.token}` }
21
+ } } });
298
22
  }
23
+ var ClaudeCodeTextAdapter = class extends BaseTextAdapter {
24
+ name = "claude-code";
25
+ requires = [SandboxCapability];
26
+ adapterConfig;
27
+ constructor(config, model) {
28
+ super({}, model);
29
+ this.adapterConfig = config;
30
+ }
31
+ sandboxFrom(options) {
32
+ const ctx = options.capabilities;
33
+ if (!ctx) throw new Error("Adapter \"claude-code\" requires a sandbox. Add withSandbox(defineSandbox({ ... })) to chat() middleware (e.g. with the local-process or docker provider).");
34
+ return getSandbox(ctx);
35
+ }
36
+ workdir(options) {
37
+ return options.modelOptions?.cwd ?? this.adapterConfig.cwd ?? DEFAULT_WORKDIR;
38
+ }
39
+ /** Build the `claude` command line (prompt goes via stdin, not argv). */
40
+ buildCommand(options, resume, policyFlags, mcpConfigPath, permissionPromptTool) {
41
+ const config = this.adapterConfig;
42
+ const modelOptions = options.modelOptions;
43
+ const exe = config.claudeExecutable ?? "claude";
44
+ const args = [
45
+ "-p",
46
+ "--output-format",
47
+ "stream-json",
48
+ "--verbose",
49
+ "--model",
50
+ q(this.model)
51
+ ];
52
+ if (config.streamPartials !== false) args.push("--include-partial-messages");
53
+ if (resume !== void 0) args.push("--resume", q(resume));
54
+ const permissionMode = modelOptions?.permissionMode ?? config.permissionMode ?? policyFlags.permissionMode ?? "bypassPermissions";
55
+ args.push("--permission-mode", q(permissionMode));
56
+ const maxTurns = modelOptions?.maxTurns ?? config.maxTurns;
57
+ if (maxTurns !== void 0) args.push("--max-turns", String(maxTurns));
58
+ for (const dir of config.addDirs ?? []) args.push("--add-dir", q(dir));
59
+ const allowedTools = [...modelOptions?.allowedTools ?? config.allowedTools ?? [], ...policyFlags.allowedTools];
60
+ if (allowedTools.length > 0) args.push("--allowedTools", q([...new Set(allowedTools)].join(",")));
61
+ const disallowedTools = [...modelOptions?.disallowedTools ?? config.disallowedTools ?? [], ...policyFlags.disallowedTools];
62
+ if (disallowedTools.length > 0) args.push("--disallowedTools", q([...new Set(disallowedTools)].join(",")));
63
+ const systemPrompts = normalizeSystemPrompts(options.systemPrompts).map((prompt) => prompt.content).filter((content) => content.trim() !== "");
64
+ if (systemPrompts.length > 0) {
65
+ const joined = systemPrompts.join("\n\n");
66
+ const flag = config.systemPromptMode === "replace" ? "--system-prompt" : "--append-system-prompt";
67
+ args.push(flag, q(joined));
68
+ }
69
+ if (mcpConfigPath !== void 0) args.push("--mcp-config", q(mcpConfigPath));
70
+ if (permissionPromptTool !== void 0) args.push("--permission-prompt-tool", q(permissionPromptTool));
71
+ return `${exe} ${args.join(" ")}`;
72
+ }
73
+ /**
74
+ * Build the permission-prompt resolver the host MCP bridge exposes to claude
75
+ * (`--permission-prompt-tool`). Maps claude's permission request onto the
76
+ * sandbox policy + client approvals; on an `ask` action with no decision yet,
77
+ * records an approval-requested event and denies (the client re-runs to grant).
78
+ */
79
+ buildPermissionResolver(policy, approvals, scripts, sink, threadId, runId) {
80
+ const writeTools = /* @__PURE__ */ new Set([
81
+ "Write",
82
+ "Edit",
83
+ "MultiEdit",
84
+ "NotebookEdit"
85
+ ]);
86
+ const networkTools = /* @__PURE__ */ new Set(["WebFetch", "WebSearch"]);
87
+ return (request) => {
88
+ const toolName = request.tool_name ?? "tool";
89
+ const cmdInput = request.input;
90
+ const command = toolName === "Bash" && cmdInput !== null && typeof cmdInput === "object" && "command" in cmdInput && typeof cmdInput.command === "string" ? cmdInput.command : void 0;
91
+ const capability = writeTools.has(toolName) ? "fileWrite" : networkTools.has(toolName) ? "network" : void 0;
92
+ const id = approvalId({
93
+ provider: "claude-code",
94
+ kind: command !== void 0 ? "command" : capability ?? "tool",
95
+ target: command ?? toolName
96
+ });
97
+ const outcome = resolveApproval({
98
+ policy,
99
+ approvals,
100
+ id,
101
+ scripts,
102
+ ...command !== void 0 ? { command } : {},
103
+ ...capability !== void 0 ? { capability } : {}
104
+ });
105
+ if (outcome.needsApproval) {
106
+ sink.push(buildApprovalRequestedEvent({
107
+ approvalId: id,
108
+ title: `Approve ${toolName}${command !== void 0 ? `: ${command}` : ""}`,
109
+ threadId,
110
+ runId,
111
+ detail: {
112
+ provider: "claude-code",
113
+ toolName
114
+ }
115
+ }));
116
+ return {
117
+ behavior: "deny",
118
+ message: "Awaiting client approval. Approve in the UI and re-run to continue."
119
+ };
120
+ }
121
+ return outcome.decision === "allow" ? { behavior: "allow" } : {
122
+ behavior: "deny",
123
+ message: "Denied by sandbox policy."
124
+ };
125
+ };
126
+ }
127
+ async *chatStream(options) {
128
+ const { logger } = options;
129
+ let bridge;
130
+ let channel;
131
+ const approvalRequests = [];
132
+ let cleanupSandbox;
133
+ const tempFiles = [];
134
+ try {
135
+ const sandbox = this.sandboxFrom(options);
136
+ cleanupSandbox = sandbox;
137
+ const cwd = this.workdir(options);
138
+ const durability = options.capabilities ? getSandboxDurability(options.capabilities, { optional: true }) : void 0;
139
+ const runId = resolveDurableRunId(options.runId, {
140
+ durable: durability !== void 0,
141
+ adapter: "claude-code",
142
+ fallback: () => this.generateId()
143
+ });
144
+ const threadId = resolveDurableThreadId(options.threadId, {
145
+ durable: durability !== void 0,
146
+ attaching: durability?.attach === true,
147
+ adapter: "claude-code",
148
+ fallback: () => this.generateId()
149
+ });
150
+ channel = createBridgeEventChannel({
151
+ model: this.model,
152
+ threadId,
153
+ runId
154
+ });
155
+ const projection = options.capabilities ? getWorkspaceProjection(options.capabilities, { optional: true }) : void 0;
156
+ if (projection) await projectClaudeWorkspace(sandbox, projection);
157
+ const policy = options.capabilities ? getSandboxPolicy(options.capabilities, { optional: true }) : void 0;
158
+ const permission = policy !== void 0 ? {
159
+ toolName: "approval_prompt",
160
+ resolve: this.buildPermissionResolver(policy, options.approvals, projection?.scripts, approvalRequests, threadId, runId)
161
+ } : void 0;
162
+ if (options.tools !== void 0 && options.tools.length > 0 || permission !== void 0) bridge = await ((options.capabilities ? getToolBridgeProvisioner(options.capabilities, { optional: true }) : void 0) ?? nodeHttpBridgeProvisioner).provision(options.tools ?? [], {
163
+ provider: sandbox.provider,
164
+ context: options.context,
165
+ emitCustomEvent: channel.emitCustomEvent,
166
+ ...permission !== void 0 ? { permission } : {},
167
+ ...options.abortController?.signal ? { signal: options.abortController.signal } : {}
168
+ });
169
+ const { prompt, resume } = buildPrompt(options.messages, options.modelOptions?.sessionId);
170
+ const runIdSegment = encodeRunId(runId);
171
+ let mcpConfigArg;
172
+ if (bridge) {
173
+ const mcpConfigFile = `.tanstack-mcp-bridge-${runIdSegment}.json`;
174
+ const mcpConfigPath = `${cwd}/${mcpConfigFile}`;
175
+ await sandbox.fs.write(mcpConfigPath, bridgeToMcpConfig(bridge));
176
+ tempFiles.push(mcpConfigPath);
177
+ mcpConfigArg = mcpConfigFile;
178
+ }
179
+ const command = this.buildCommand(options, resume, mapPolicyToClaudeFlags(policy), mcpConfigArg, bridge && permission ? `mcp__${bridge.name}__${permission.toolName}` : void 0);
180
+ let runCommand = command;
181
+ let stdinInput = prompt;
182
+ if (sandbox.capabilities.writableStdin === false) {
183
+ const promptPath = `/tmp/tanstack-claude-prompt-${runIdSegment}`;
184
+ await sandbox.fs.write(promptPath, prompt);
185
+ tempFiles.push(promptPath);
186
+ runCommand = `${command} < ${q(promptPath)}`;
187
+ stdinInput = void 0;
188
+ }
189
+ logger.request(`activity=chat provider=claude-code model=${this.model} sandbox=${sandbox.provider} messages=${options.messages.length} resume=${resume ?? "none"}`, {
190
+ provider: "claude-code",
191
+ model: this.model
192
+ });
193
+ const journalOptions = journalOptionsFor(durability, runId);
194
+ const rawEvents = spawnNdjson(sandbox, runCommand, {
195
+ cwd,
196
+ ...stdinInput !== void 0 ? { input: stdinInput } : {},
197
+ env: {
198
+ IS_SANDBOX: "1",
199
+ ...this.adapterConfig.env
200
+ },
201
+ ...options.abortController?.signal ? { signal: options.abortController.signal } : options.request?.signal ? { signal: options.request.signal } : {},
202
+ onNonJsonLine: (line) => logger.provider(`provider=claude-code non-json line: ${line}`, { chunk: line }),
203
+ ...journalOptions === void 0 ? {} : { journal: journalOptions }
204
+ });
205
+ async function* asMessages() {
206
+ for await (const event of rawEvents) yield event;
207
+ }
208
+ yield* alignedIfAttaching(mergeChunkStreams(translateSdkStream(asMessages(), {
209
+ model: this.model,
210
+ runId,
211
+ threadId,
212
+ ...options.parentRunId !== void 0 && { parentRunId: options.parentRunId },
213
+ genId: createRunScopedIdGen(runId),
214
+ onSdkMessage: (message) => logger.provider(`provider=claude-code type=${message.type}`, { chunk: message })
215
+ }), channel.stream), durability, logger);
216
+ if (this.adapterConfig.emitDiff !== false) try {
217
+ const diff = await sandbox.process.exec(`git -C ${q(cwd)} diff`, { cwd });
218
+ if (diff.exitCode === 0 && diff.stdout.trim() !== "") yield {
219
+ type: EventType.CUSTOM,
220
+ name: "file.changed",
221
+ value: {
222
+ path: ".",
223
+ diff: diff.stdout
224
+ },
225
+ timestamp: Date.now(),
226
+ threadId,
227
+ runId
228
+ };
229
+ } catch {}
230
+ for (const event of approvalRequests) yield event;
231
+ } catch (error) {
232
+ const err = error;
233
+ const rawEvent = toRunErrorRawEvent(error);
234
+ logger.errors("claude-code.chatStream fatal", {
235
+ error,
236
+ source: "claude-code.chatStream"
237
+ });
238
+ yield {
239
+ type: EventType.RUN_ERROR,
240
+ model: options.model,
241
+ timestamp: Date.now(),
242
+ message: err.message || "Unknown error occurred",
243
+ ...err.code !== void 0 && { code: err.code },
244
+ ...rawEvent !== void 0 && { rawEvent },
245
+ error: {
246
+ message: err.message || "Unknown error occurred",
247
+ ...err.code !== void 0 && { code: err.code }
248
+ }
249
+ };
250
+ } finally {
251
+ channel?.close();
252
+ if (bridge) await bridge.close();
253
+ if (cleanupSandbox) for (const path of tempFiles) try {
254
+ await cleanupSandbox.fs.remove(path);
255
+ } catch {}
256
+ }
257
+ }
258
+ structuredOutput(_options) {
259
+ return Promise.reject(/* @__PURE__ */ new Error("Structured output is not yet supported by the in-sandbox Claude Code adapter. Use a model adapter (e.g. anthropic) for structured output, or omit outputSchema."));
260
+ }
261
+ };
262
+ /**
263
+ * Creates a Claude Code harness adapter that runs **inside a sandbox**.
264
+ *
265
+ * Unlike HTTP provider adapters, this is a *harness* adapter: it spawns the
266
+ * `claude` CLI inside the sandbox provided by `withSandbox(...)` (the adapter
267
+ * declares `requires: [SandboxCapability]`), streams its `stream-json` stdout
268
+ * back as AG-UI events, and lets Claude Code run its own agent loop and native
269
+ * tools (Bash, file edits, search, …) against the sandbox workspace. The
270
+ * sandbox image must provide the `claude` executable and `ANTHROPIC_API_KEY`
271
+ * in its environment (e.g. via `workspace.secrets`). The session id is
272
+ * surfaced via a CUSTOM `claude-code.session-id` event so follow-up calls can
273
+ * resume through `modelOptions.sessionId`.
274
+ */
299
275
  function claudeCodeText(model, config = {}) {
300
- return new ClaudeCodeTextAdapter(config, model);
276
+ return new ClaudeCodeTextAdapter(config, model);
301
277
  }
302
- export {
303
- ClaudeCodeTextAdapter,
304
- claudeCodeText
305
- };
306
- //# sourceMappingURL=text.js.map
278
+ //#endregion
279
+ export { ClaudeCodeTextAdapter, claudeCodeText };
280
+
281
+ //# sourceMappingURL=text.js.map