@tanstack/ai-claude-code 0.3.1 → 0.4.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.
@@ -0,0 +1,14 @@
1
+ /** Placeholder swapped for the schema JSON after the runner reads the files. */
2
+ export declare const CLAUDE_JSON_SCHEMA_PLACEHOLDER = "__TANSTACK_SCHEMA__";
3
+ /**
4
+ * Written into the sandbox and run with `node`.
5
+ *
6
+ * The shell only sees two filenames. The runner reads the argv array and the
7
+ * schema JSON from those files, then spawn()s claude so `--json-schema` is a
8
+ * real argv value (the CLI rejects a file path).
9
+ *
10
+ * On Windows, `claude` is often a `.cmd` shim. `spawn(cmd, args)` without a
11
+ * shell cannot find that shim, so we go through `sh` with `"$0"` / `"$@"`
12
+ * (git-bash is already on PATH in the local-process sandbox).
13
+ */
14
+ export declare const CLAUDE_RUNNER_SOURCE: string;
@@ -0,0 +1,70 @@
1
+ //#region src/adapters/claude-run-source.ts
2
+ /** Placeholder swapped for the schema JSON after the runner reads the files. */
3
+ var CLAUDE_JSON_SCHEMA_PLACEHOLDER = "__TANSTACK_SCHEMA__";
4
+ /**
5
+ * Written into the sandbox and run with `node`.
6
+ *
7
+ * The shell only sees two filenames. The runner reads the argv array and the
8
+ * schema JSON from those files, then spawn()s claude so `--json-schema` is a
9
+ * real argv value (the CLI rejects a file path).
10
+ *
11
+ * On Windows, `claude` is often a `.cmd` shim. `spawn(cmd, args)` without a
12
+ * shell cannot find that shim, so we go through `sh` with `"$0"` / `"$@"`
13
+ * (git-bash is already on PATH in the local-process sandbox).
14
+ */
15
+ var CLAUDE_RUNNER_SOURCE = `import { spawn } from 'node:child_process'
16
+ import { readFileSync } from 'node:fs'
17
+
18
+ const argvFile = process.argv[2]
19
+ if (!argvFile) {
20
+ console.error('tanstack-claude-run: missing argv file')
21
+ process.exit(1)
22
+ }
23
+
24
+ const argv = JSON.parse(readFileSync(argvFile, 'utf8'))
25
+ if (!Array.isArray(argv)) {
26
+ console.error('tanstack-claude-run: argv file must be a JSON array')
27
+ process.exit(1)
28
+ }
29
+
30
+ const schemaFile = process.argv[3]
31
+ if (schemaFile) {
32
+ const schema = readFileSync(schemaFile, 'utf8')
33
+ for (let i = 0; i < argv.length; i++) {
34
+ if (argv[i] === ${JSON.stringify(CLAUDE_JSON_SCHEMA_PLACEHOLDER)}) argv[i] = schema
35
+ }
36
+ }
37
+
38
+ const [cmd, ...args] = argv
39
+ if (typeof cmd !== 'string' || cmd === '') {
40
+ console.error('tanstack-claude-run: missing command')
41
+ process.exit(1)
42
+ }
43
+
44
+ const opts = {
45
+ stdio: ['pipe', 'pipe', 'pipe'],
46
+ env: process.env,
47
+ windowsHide: true,
48
+ }
49
+
50
+ const child =
51
+ process.platform === 'win32'
52
+ ? spawn('sh', ['-c', 'exec "$0" "$@"', cmd, ...args], opts)
53
+ : spawn(cmd, args, opts)
54
+
55
+ process.stdin.pipe(child.stdin)
56
+ child.stdout.pipe(process.stdout)
57
+ child.stderr.pipe(process.stderr)
58
+ child.on('error', (error) => {
59
+ console.error(error)
60
+ process.exit(1)
61
+ })
62
+ child.on('exit', (code, signal) => {
63
+ if (signal) process.exit(1)
64
+ process.exit(code ?? 1)
65
+ })
66
+ `;
67
+ //#endregion
68
+ export { CLAUDE_JSON_SCHEMA_PLACEHOLDER, CLAUDE_RUNNER_SOURCE };
69
+
70
+ //# sourceMappingURL=claude-run-source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-run-source.js","names":[],"sources":["../../../src/adapters/claude-run-source.ts"],"sourcesContent":["/** Placeholder swapped for the schema JSON after the runner reads the files. */\nexport const CLAUDE_JSON_SCHEMA_PLACEHOLDER = '__TANSTACK_SCHEMA__'\n\n/**\n * Written into the sandbox and run with `node`.\n *\n * The shell only sees two filenames. The runner reads the argv array and the\n * schema JSON from those files, then spawn()s claude so `--json-schema` is a\n * real argv value (the CLI rejects a file path).\n *\n * On Windows, `claude` is often a `.cmd` shim. `spawn(cmd, args)` without a\n * shell cannot find that shim, so we go through `sh` with `\"$0\"` / `\"$@\"`\n * (git-bash is already on PATH in the local-process sandbox).\n */\nexport const CLAUDE_RUNNER_SOURCE = `import { spawn } from 'node:child_process'\nimport { readFileSync } from 'node:fs'\n\nconst argvFile = process.argv[2]\nif (!argvFile) {\n console.error('tanstack-claude-run: missing argv file')\n process.exit(1)\n}\n\nconst argv = JSON.parse(readFileSync(argvFile, 'utf8'))\nif (!Array.isArray(argv)) {\n console.error('tanstack-claude-run: argv file must be a JSON array')\n process.exit(1)\n}\n\nconst schemaFile = process.argv[3]\nif (schemaFile) {\n const schema = readFileSync(schemaFile, 'utf8')\n for (let i = 0; i < argv.length; i++) {\n if (argv[i] === ${JSON.stringify(CLAUDE_JSON_SCHEMA_PLACEHOLDER)}) argv[i] = schema\n }\n}\n\nconst [cmd, ...args] = argv\nif (typeof cmd !== 'string' || cmd === '') {\n console.error('tanstack-claude-run: missing command')\n process.exit(1)\n}\n\nconst opts = {\n stdio: ['pipe', 'pipe', 'pipe'],\n env: process.env,\n windowsHide: true,\n}\n\nconst child =\n process.platform === 'win32'\n ? spawn('sh', ['-c', 'exec \"$0\" \"$@\"', cmd, ...args], opts)\n : spawn(cmd, args, opts)\n\nprocess.stdin.pipe(child.stdin)\nchild.stdout.pipe(process.stdout)\nchild.stderr.pipe(process.stderr)\nchild.on('error', (error) => {\n console.error(error)\n process.exit(1)\n})\nchild.on('exit', (code, signal) => {\n if (signal) process.exit(1)\n process.exit(code ?? 1)\n})\n`\n"],"mappings":";;AACA,IAAa,iCAAiC;;;;;;;;;;;;AAa9C,IAAa,uBAAuB;;;;;;;;;;;;;;;;;;;sBAmBd,KAAK,UAAU,8BAA8B,EAAE"}
@@ -1,4 +1,4 @@
1
- import { isSecretRef, resolveGitSkillDir } from "@tanstack/ai-sandbox";
1
+ import { discoverSkillDirs, isSecretRef, resolveGitSkillDir, resolveHarnessCwd } from "@tanstack/ai-sandbox";
2
2
  //#region src/adapters/projection.ts
3
3
  /**
4
4
  * Claude Code workspace projector — the reference implementation the other
@@ -30,11 +30,6 @@ import { isSecretRef, resolveGitSkillDir } from "@tanstack/ai-sandbox";
30
30
  function shellQuote(value) {
31
31
  return `'${value.replace(/'/g, `'\\''`)}'`;
32
32
  }
33
- /** Last path segment of a `gitSkill` clone dir, used as the skills-dir name. */
34
- function basenameOf(path) {
35
- const segments = path.split("/").filter((segment) => segment !== "");
36
- return segments[segments.length - 1] ?? path;
37
- }
38
33
  /** True when `value` is a `bearer(ref)` marker created by `@tanstack/ai-sandbox`. */
39
34
  function isBearerMarker(value) {
40
35
  return typeof value === "object" && value !== null && isSecretRef(value.__bearerRef);
@@ -102,12 +97,16 @@ async function projectGitSkills(handle, projection) {
102
97
  madeDir = true;
103
98
  }
104
99
  const source = skill.into ?? resolveGitSkillDir(projection.root, skill);
105
- const target = `${skillsDir}/${basenameOf(source)}`;
106
- const lnCmd = `ln -s ${shellQuote(source)} ${shellQuote(target)}`;
107
- if ((await handle.process.exec(lnCmd, { cwd: projection.root })).exitCode !== 0) {
108
- const cpCmd = `cp -r ${shellQuote(source)} ${shellQuote(target)}`;
109
- const copied = await handle.process.exec(cpCmd, { cwd: projection.root });
110
- if (copied.exitCode !== 0) console.warn(`[claude-code] failed to link gitSkill "${skill.repo}" into ${target}: ${copied.stderr.trim()}`);
100
+ const discovered = await discoverSkillDirs(handle, source);
101
+ for (const { name, dir } of discovered) {
102
+ const target = resolveHarnessCwd(handle, `${skillsDir}/${name}`);
103
+ const realDir = resolveHarnessCwd(handle, dir);
104
+ const lnCmd = `ln -s ${shellQuote(realDir)} ${shellQuote(target)}`;
105
+ if ((await handle.process.exec(lnCmd, { cwd: projection.root })).exitCode !== 0) {
106
+ const cpCmd = `cp -r ${shellQuote(realDir)} ${shellQuote(target)}`;
107
+ const copied = await handle.process.exec(cpCmd, { cwd: projection.root });
108
+ if (copied.exitCode !== 0) console.warn(`[claude-code] failed to link gitSkill "${skill.repo}" into ${target}: ${copied.stderr.trim()}`);
109
+ }
111
110
  }
112
111
  }
113
112
  }
@@ -1 +1 @@
1
- {"version":3,"file":"projection.js","names":[],"sources":["../../../src/adapters/projection.ts"],"sourcesContent":["/**\n * Claude Code workspace projector — the reference implementation the other\n * harness projectors (codex, opencode) mirror.\n *\n * `withSandbox` surfaces a portable `WorkspaceProjection` (skills, plugins, a\n * secret resolver, and a one-time marker path) via a capability. Each harness\n * adapter reads it in its `chatStream` setup and projects those inputs into the\n * CLI's native format. For Claude Code that means:\n *\n * - MCP servers → a project-scoped `.mcp.json` at the workspace root.\n * - gitSkill repos → linked under `.claude/skills/<basename>`.\n * - agentSkill → no reliable claude primitive pulls a public skill by\n * bare name, so we warn and skip rather than invent one.\n * - plugins → `claude plugin install <name>` (best-effort).\n *\n * The secret-bearing `.mcp.json` is (re)written on EVERY call, re-resolving\n * secrets each time, so claude always reads current values and a snapshot can\n * never serve a stale or rotated secret. Only the safe, idempotent, non-secret\n * operations (gitSkill links, plugin installs, agentSkill handling) are guarded\n * by a one-time marker file under the workspace.\n *\n * External-convention caveat: the `.mcp.json` location/shape, the skills dir,\n * and the plugin-install command are verified against the installed `claude`\n * CLI. Where claude has no clean primitive (agentSkill by bare name) we no-op\n * with a warning instead of fabricating a command.\n */\nimport { isSecretRef, resolveGitSkillDir } from '@tanstack/ai-sandbox'\nimport type {\n BearerRef,\n SandboxHandle,\n SecretRef,\n WorkspaceProjection,\n WorkspaceSkill,\n} from '@tanstack/ai-sandbox'\n\n/** POSIX single-quote escape for embedding a value in a shell command. */\nfunction shellQuote(value: string): string {\n return `'${value.replace(/'/g, `'\\\\''`)}'`\n}\n\n/** Last path segment of a `gitSkill` clone dir, used as the skills-dir name. */\nfunction basenameOf(path: string): string {\n const segments = path.split('/').filter((segment) => segment !== '')\n return segments[segments.length - 1] ?? path\n}\n\n/** True when `value` is a `bearer(ref)` marker created by `@tanstack/ai-sandbox`. */\nfunction isBearerMarker(value: unknown): value is BearerRef {\n return (\n typeof value === 'object' &&\n value !== null &&\n isSecretRef((value as { __bearerRef?: unknown }).__bearerRef)\n )\n}\n\n/**\n * Resolve a single MCP header value: a `SecretRef` resolves to its plaintext, a\n * `bearer(ref)` marker resolves to `Bearer <plaintext>`, and a plain string is\n * passed through unchanged.\n */\nfunction resolveHeaderValue(\n value: string | SecretRef | BearerRef,\n resolveSecret: (ref: SecretRef) => string,\n): string {\n if (isSecretRef(value)) return resolveSecret(value)\n if (isBearerMarker(value)) return `Bearer ${resolveSecret(value.__bearerRef)}`\n return value\n}\n\n/** A claude-format HTTP MCP server entry. */\ninterface ClaudeMcpServer {\n type: 'http'\n url: string\n headers: Record<string, string>\n}\n\n/**\n * Build claude's project-scoped MCP config from the `{ kind: 'mcp' }` skills,\n * resolving every header value (SecretRef / bearer / string). Returns\n * `undefined` when there are no MCP skills so the caller can skip the write.\n */\nfunction buildMcpConfig(\n skills: Array<WorkspaceSkill>,\n resolveSecret: (ref: SecretRef) => string,\n): { mcpServers: Record<string, ClaudeMcpServer> } | undefined {\n const mcpServers: Record<string, ClaudeMcpServer> = {}\n let count = 0\n for (const skill of skills) {\n if (skill.kind !== 'mcp') continue\n count += 1\n const headers: Record<string, string> = {}\n const rawHeaders = skill.config.headers ?? {}\n for (const [name, value] of Object.entries(rawHeaders)) {\n headers[name] = resolveHeaderValue(value, resolveSecret)\n }\n const rawUrl = skill.config['url']\n const url = typeof rawUrl === 'string' ? rawUrl : ''\n mcpServers[skill.name] = { type: 'http', url, headers }\n }\n return count > 0 ? { mcpServers } : undefined\n}\n\n/**\n * Write the project-scoped `.mcp.json`, re-resolving every secret. This runs on\n * EVERY projection call (never gated by the marker) so claude always reads the\n * current secret values and a snapshot can never serve a stale or rotated one.\n * With `snapshot:'after-run'` the file may still be captured in the image, so\n * secret-bearing MCP material is best used with the default `after-setup`\n * strategy. When there are no MCP skills the write is skipped.\n */\nasync function projectMcpServers(\n handle: SandboxHandle,\n projection: WorkspaceProjection,\n): Promise<void> {\n const config = buildMcpConfig(projection.skills, projection.resolveSecret)\n if (config === undefined) return\n const target = `${projection.root}/.mcp.json`\n await handle.fs.write(target, JSON.stringify(config, null, 2))\n}\n\n/**\n * Ensure each cloned `gitSkill` repo is available under claude's project skills\n * dir (`<root>/.claude/skills/<basename>`) via a symlink, falling back to a\n * recursive copy on platforms without `ln -s`.\n */\nasync function projectGitSkills(\n handle: SandboxHandle,\n projection: WorkspaceProjection,\n): Promise<void> {\n const skillsDir = `${projection.root}/.claude/skills`\n let madeDir = false\n for (const skill of projection.skills) {\n if (skill.kind !== 'git') continue\n if (!madeDir) {\n await handle.fs.mkdir(skillsDir)\n madeDir = true\n }\n const source = skill.into ?? resolveGitSkillDir(projection.root, skill)\n const target = `${skillsDir}/${basenameOf(source)}`\n const lnCmd = `ln -s ${shellQuote(source)} ${shellQuote(target)}`\n const result = await handle.process.exec(lnCmd, { cwd: projection.root })\n if (result.exitCode !== 0) {\n const cpCmd = `cp -r ${shellQuote(source)} ${shellQuote(target)}`\n const copied = await handle.process.exec(cpCmd, { cwd: projection.root })\n if (copied.exitCode !== 0) {\n console.warn(\n `[claude-code] failed to link gitSkill \"${skill.repo}\" into ${target}: ${copied.stderr.trim()}`,\n )\n }\n }\n }\n}\n\n/**\n * `agentSkill` references a public skill by bare name. Claude Code has no\n * primitive to fetch a skill from a bare name (skills resolve from local\n * `.claude/skills/` dirs and plugin marketplaces), so we warn and skip rather\n * than fabricate a command.\n */\nfunction projectAgentSkills(projection: WorkspaceProjection): void {\n for (const skill of projection.skills) {\n if (skill.kind !== 'agent-skill') continue\n console.warn(\n `[claude-code] agentSkill \"${skill.name}\" cannot be projected: Claude Code has ` +\n 'no command to install a public skill by bare name. Provide it as a gitSkill ' +\n 'or a plugin instead. Skipping.',\n )\n }\n}\n\n/**\n * Install each declared plugin via `claude plugin install <name>`. Plugin\n * installs are best-effort: a failure (no marketplace, network, …) warns but\n * never throws, so a missing plugin can't break the run.\n */\nasync function projectPlugins(\n handle: SandboxHandle,\n projection: WorkspaceProjection,\n): Promise<void> {\n for (const name of projection.plugins) {\n const cmd = `claude plugin install ${shellQuote(name)}`\n try {\n const result = await handle.process.exec(cmd, { cwd: projection.root })\n if (result.exitCode !== 0) {\n console.warn(\n `[claude-code] \"claude plugin install ${name}\" exited ${result.exitCode}: ${result.stderr.trim()}`,\n )\n }\n } catch (error: unknown) {\n const message = error instanceof Error ? error.message : String(error)\n console.warn(\n `[claude-code] failed to install plugin \"${name}\": ${message}`,\n )\n }\n }\n}\n\n/**\n * Project a `WorkspaceProjection` into the Claude Code sandbox. Safe to call on\n * every `chatStream`. The secret-bearing `.mcp.json` is (re)written on every\n * call, re-resolving secrets, so claude always reads current values and a\n * snapshot can never serve a stale or rotated secret. The safe, idempotent,\n * non-secret operations (gitSkill links, plugin installs, agentSkill handling)\n * are guarded by a one-time marker so they run only on the first call after\n * create/restore.\n *\n * @param handle - The sandbox handle (`fs` + `process`).\n * @param projection - The portable workspace inputs from `withSandbox`.\n */\nexport async function projectClaudeWorkspace(\n handle: SandboxHandle,\n projection: WorkspaceProjection,\n): Promise<void> {\n // Always re-resolve and rewrite the secret-bearing MCP config so rotated\n // secrets re-apply and snapshots can't serve stale values.\n await projectMcpServers(handle, projection)\n\n // Gate only the safe, idempotent, non-secret operations on the marker.\n if (await handle.fs.exists(projection.markerPath)) return\n\n await projectGitSkills(handle, projection)\n projectAgentSkills(projection)\n await projectPlugins(handle, projection)\n\n await handle.fs.write(projection.markerPath, '')\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"projection.js","names":[],"sources":["../../../src/adapters/projection.ts"],"sourcesContent":["/**\n * Claude Code workspace projector — the reference implementation the other\n * harness projectors (codex, opencode) mirror.\n *\n * `withSandbox` surfaces a portable `WorkspaceProjection` (skills, plugins, a\n * secret resolver, and a one-time marker path) via a capability. Each harness\n * adapter reads it in its `chatStream` setup and projects those inputs into the\n * CLI's native format. For Claude Code that means:\n *\n * - MCP servers → a project-scoped `.mcp.json` at the workspace root.\n * - gitSkill repos → linked under `.claude/skills/<basename>`.\n * - agentSkill → no reliable claude primitive pulls a public skill by\n * bare name, so we warn and skip rather than invent one.\n * - plugins → `claude plugin install <name>` (best-effort).\n *\n * The secret-bearing `.mcp.json` is (re)written on EVERY call, re-resolving\n * secrets each time, so claude always reads current values and a snapshot can\n * never serve a stale or rotated secret. Only the safe, idempotent, non-secret\n * operations (gitSkill links, plugin installs, agentSkill handling) are guarded\n * by a one-time marker file under the workspace.\n *\n * External-convention caveat: the `.mcp.json` location/shape, the skills dir,\n * and the plugin-install command are verified against the installed `claude`\n * CLI. Where claude has no clean primitive (agentSkill by bare name) we no-op\n * with a warning instead of fabricating a command.\n */\nimport {\n discoverSkillDirs,\n isSecretRef,\n resolveGitSkillDir,\n resolveHarnessCwd,\n} from '@tanstack/ai-sandbox'\nimport type {\n BearerRef,\n SandboxHandle,\n SecretRef,\n WorkspaceProjection,\n WorkspaceSkill,\n} from '@tanstack/ai-sandbox'\n\n/** POSIX single-quote escape for embedding a value in a shell command. */\nfunction shellQuote(value: string): string {\n return `'${value.replace(/'/g, `'\\\\''`)}'`\n}\n\n/** True when `value` is a `bearer(ref)` marker created by `@tanstack/ai-sandbox`. */\nfunction isBearerMarker(value: unknown): value is BearerRef {\n return (\n typeof value === 'object' &&\n value !== null &&\n isSecretRef((value as { __bearerRef?: unknown }).__bearerRef)\n )\n}\n\n/**\n * Resolve a single MCP header value: a `SecretRef` resolves to its plaintext, a\n * `bearer(ref)` marker resolves to `Bearer <plaintext>`, and a plain string is\n * passed through unchanged.\n */\nfunction resolveHeaderValue(\n value: string | SecretRef | BearerRef,\n resolveSecret: (ref: SecretRef) => string,\n): string {\n if (isSecretRef(value)) return resolveSecret(value)\n if (isBearerMarker(value)) return `Bearer ${resolveSecret(value.__bearerRef)}`\n return value\n}\n\n/** A claude-format HTTP MCP server entry. */\ninterface ClaudeMcpServer {\n type: 'http'\n url: string\n headers: Record<string, string>\n}\n\n/**\n * Build claude's project-scoped MCP config from the `{ kind: 'mcp' }` skills,\n * resolving every header value (SecretRef / bearer / string). Returns\n * `undefined` when there are no MCP skills so the caller can skip the write.\n */\nfunction buildMcpConfig(\n skills: Array<WorkspaceSkill>,\n resolveSecret: (ref: SecretRef) => string,\n): { mcpServers: Record<string, ClaudeMcpServer> } | undefined {\n const mcpServers: Record<string, ClaudeMcpServer> = {}\n let count = 0\n for (const skill of skills) {\n if (skill.kind !== 'mcp') continue\n count += 1\n const headers: Record<string, string> = {}\n const rawHeaders = skill.config.headers ?? {}\n for (const [name, value] of Object.entries(rawHeaders)) {\n headers[name] = resolveHeaderValue(value, resolveSecret)\n }\n const rawUrl = skill.config['url']\n const url = typeof rawUrl === 'string' ? rawUrl : ''\n mcpServers[skill.name] = { type: 'http', url, headers }\n }\n return count > 0 ? { mcpServers } : undefined\n}\n\n/**\n * Write the project-scoped `.mcp.json`, re-resolving every secret. This runs on\n * EVERY projection call (never gated by the marker) so claude always reads the\n * current secret values and a snapshot can never serve a stale or rotated one.\n * With `snapshot:'after-run'` the file may still be captured in the image, so\n * secret-bearing MCP material is best used with the default `after-setup`\n * strategy. When there are no MCP skills the write is skipped.\n */\nasync function projectMcpServers(\n handle: SandboxHandle,\n projection: WorkspaceProjection,\n): Promise<void> {\n const config = buildMcpConfig(projection.skills, projection.resolveSecret)\n if (config === undefined) return\n const target = `${projection.root}/.mcp.json`\n await handle.fs.write(target, JSON.stringify(config, null, 2))\n}\n\n/**\n * Ensure each cloned `gitSkill` repo is available under claude's project skills\n * dir (`<root>/.claude/skills/<basename>`) via a symlink, falling back to a\n * recursive copy on platforms without `ln -s`.\n */\nasync function projectGitSkills(\n handle: SandboxHandle,\n projection: WorkspaceProjection,\n): Promise<void> {\n const skillsDir = `${projection.root}/.claude/skills`\n let madeDir = false\n for (const skill of projection.skills) {\n if (skill.kind !== 'git') continue\n if (!madeDir) {\n await handle.fs.mkdir(skillsDir)\n madeDir = true\n }\n // Discover over virtual `/workspace` paths (handle.fs remaps). Remap only\n // for shell `ln`/`cp`, where absolute paths must match the real workdir.\n const source = skill.into ?? resolveGitSkillDir(projection.root, skill)\n const discovered = await discoverSkillDirs(handle, source)\n for (const { name, dir } of discovered) {\n const target = resolveHarnessCwd(handle, `${skillsDir}/${name}`)\n const realDir = resolveHarnessCwd(handle, dir)\n const lnCmd = `ln -s ${shellQuote(realDir)} ${shellQuote(target)}`\n const result = await handle.process.exec(lnCmd, { cwd: projection.root })\n if (result.exitCode !== 0) {\n const cpCmd = `cp -r ${shellQuote(realDir)} ${shellQuote(target)}`\n const copied = await handle.process.exec(cpCmd, {\n cwd: projection.root,\n })\n if (copied.exitCode !== 0) {\n console.warn(\n `[claude-code] failed to link gitSkill \"${skill.repo}\" into ${target}: ${copied.stderr.trim()}`,\n )\n }\n }\n }\n }\n}\n\n/**\n * `agentSkill` references a public skill by bare name. Claude Code has no\n * primitive to fetch a skill from a bare name (skills resolve from local\n * `.claude/skills/` dirs and plugin marketplaces), so we warn and skip rather\n * than fabricate a command.\n */\nfunction projectAgentSkills(projection: WorkspaceProjection): void {\n for (const skill of projection.skills) {\n if (skill.kind !== 'agent-skill') continue\n console.warn(\n `[claude-code] agentSkill \"${skill.name}\" cannot be projected: Claude Code has ` +\n 'no command to install a public skill by bare name. Provide it as a gitSkill ' +\n 'or a plugin instead. Skipping.',\n )\n }\n}\n\n/**\n * Install each declared plugin via `claude plugin install <name>`. Plugin\n * installs are best-effort: a failure (no marketplace, network, …) warns but\n * never throws, so a missing plugin can't break the run.\n */\nasync function projectPlugins(\n handle: SandboxHandle,\n projection: WorkspaceProjection,\n): Promise<void> {\n for (const name of projection.plugins) {\n const cmd = `claude plugin install ${shellQuote(name)}`\n try {\n const result = await handle.process.exec(cmd, { cwd: projection.root })\n if (result.exitCode !== 0) {\n console.warn(\n `[claude-code] \"claude plugin install ${name}\" exited ${result.exitCode}: ${result.stderr.trim()}`,\n )\n }\n } catch (error: unknown) {\n const message = error instanceof Error ? error.message : String(error)\n console.warn(\n `[claude-code] failed to install plugin \"${name}\": ${message}`,\n )\n }\n }\n}\n\n/**\n * Project a `WorkspaceProjection` into the Claude Code sandbox. Safe to call on\n * every `chatStream`. The secret-bearing `.mcp.json` is (re)written on every\n * call, re-resolving secrets, so claude always reads current values and a\n * snapshot can never serve a stale or rotated secret. The safe, idempotent,\n * non-secret operations (gitSkill links, plugin installs, agentSkill handling)\n * are guarded by a one-time marker so they run only on the first call after\n * create/restore.\n *\n * @param handle - The sandbox handle (`fs` + `process`).\n * @param projection - The portable workspace inputs from `withSandbox`.\n */\nexport async function projectClaudeWorkspace(\n handle: SandboxHandle,\n projection: WorkspaceProjection,\n): Promise<void> {\n // Always re-resolve and rewrite the secret-bearing MCP config so rotated\n // secrets re-apply and snapshots can't serve stale values.\n await projectMcpServers(handle, projection)\n\n // Gate only the safe, idempotent, non-secret operations on the marker.\n if (await handle.fs.exists(projection.markerPath)) return\n\n await projectGitSkills(handle, projection)\n projectAgentSkills(projection)\n await projectPlugins(handle, projection)\n\n await handle.fs.write(projection.markerPath, '')\n}\n"],"mappings":"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"}
@@ -37,6 +37,12 @@ export interface ClaudeCodeTextConfig {
37
37
  streamPartials?: boolean;
38
38
  /** Extra environment variables for the claude process inside the sandbox. */
39
39
  env?: Record<string, string>;
40
+ /**
41
+ * `'api-key'` (default) injects `ANTHROPIC_API_KEY`.
42
+ * `'host'` uses `claude login` and does not inject the key.
43
+ * Not inferred from the sandbox.
44
+ */
45
+ authMode?: 'host' | 'api-key';
40
46
  /** Emit a `file.changed` CUSTOM event with the git diff after the run (default true). */
41
47
  emitDiff?: boolean;
42
48
  }
@@ -47,8 +53,10 @@ export declare class ClaudeCodeTextAdapter<TModel extends ClaudeCodeModel> exten
47
53
  constructor(config: ClaudeCodeTextConfig, model: TModel);
48
54
  private sandboxFrom;
49
55
  private workdir;
56
+ supportsCombinedToolsAndSchema(): boolean;
57
+ combinedStructuredOutputSource(): 'event';
50
58
  /** Build the `claude` command line (prompt goes via stdin, not argv). */
51
- private buildCommand;
59
+ private buildArgv;
52
60
  /**
53
61
  * Build the permission-prompt resolver the host MCP bridge exposes to claude
54
62
  * (`--permission-prompt-tool`). Maps claude's permission request onto the
@@ -2,8 +2,9 @@ import { buildPrompt } from "../messages/prompt.js";
2
2
  import { translateSdkStream } from "../stream/translate.js";
3
3
  import { mapPolicyToClaudeFlags } from "./policy-map.js";
4
4
  import { projectClaudeWorkspace } from "./projection.js";
5
+ import { CLAUDE_JSON_SCHEMA_PLACEHOLDER, CLAUDE_RUNNER_SOURCE } from "./claude-run-source.js";
5
6
  import { EventType, normalizeSystemPrompts } from "@tanstack/ai";
6
- import { toRunErrorRawEvent } from "@tanstack/ai/adapter-internals";
7
+ import { appendOutputSchemaInstruction, toRunErrorRawEvent } from "@tanstack/ai/adapter-internals";
7
8
  import { BaseTextAdapter } from "@tanstack/ai/adapters";
8
9
  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
10
  //#region src/adapters/text.ts
@@ -12,6 +13,25 @@ var DEFAULT_WORKDIR = "/workspace";
12
13
  function q(value) {
13
14
  return `'${value.replace(/'/g, `'\\''`)}'`;
14
15
  }
16
+ /** Copy host Anthropic auth into the sandbox process. Docker `exec` Env replaces the container env, so a key set only at create time can vanish. */
17
+ function hostClaudeAuthEnv() {
18
+ const env = {};
19
+ const apiKey = process.env.ANTHROPIC_API_KEY;
20
+ if (apiKey) env.ANTHROPIC_API_KEY = apiKey;
21
+ const authToken = process.env.ANTHROPIC_AUTH_TOKEN;
22
+ if (authToken) env.ANTHROPIC_AUTH_TOKEN = authToken;
23
+ return env;
24
+ }
25
+ /**
26
+ * Windows Node often has USERPROFILE but no HOME. Claude then cannot find
27
+ * `~/.claude.json` (the `claude login` file) and prints "Not logged in".
28
+ */
29
+ function localProcessHomeEnv(provider) {
30
+ if (provider !== "local-process") return {};
31
+ if (process.env.HOME) return {};
32
+ const home = process.env.USERPROFILE;
33
+ return home ? { HOME: home } : {};
34
+ }
15
35
  /** Format a host tool-bridge as claude's `--mcp-config` JSON. */
16
36
  function bridgeToMcpConfig(bridge) {
17
37
  return JSON.stringify({ mcpServers: { [bridge.name]: {
@@ -36,39 +56,48 @@ var ClaudeCodeTextAdapter = class extends BaseTextAdapter {
36
56
  workdir(options) {
37
57
  return options.modelOptions?.cwd ?? this.adapterConfig.cwd ?? DEFAULT_WORKDIR;
38
58
  }
59
+ supportsCombinedToolsAndSchema() {
60
+ return true;
61
+ }
62
+ combinedStructuredOutputSource() {
63
+ return "event";
64
+ }
39
65
  /** Build the `claude` command line (prompt goes via stdin, not argv). */
40
- buildCommand(options, resume, policyFlags, mcpConfigPath, permissionPromptTool) {
66
+ buildArgv(options, resume, policyFlags, mcpConfigPath, permissionPromptTool, hasJsonSchema) {
41
67
  const config = this.adapterConfig;
42
68
  const modelOptions = options.modelOptions;
43
- const exe = config.claudeExecutable ?? "claude";
44
69
  const args = [
70
+ ...(config.claudeExecutable ?? "claude").split(" "),
71
+ "--setting-sources",
72
+ "user",
45
73
  "-p",
46
74
  "--output-format",
47
75
  "stream-json",
48
76
  "--verbose",
49
77
  "--model",
50
- q(this.model)
78
+ this.model
51
79
  ];
52
80
  if (config.streamPartials !== false) args.push("--include-partial-messages");
53
- if (resume !== void 0) args.push("--resume", q(resume));
81
+ if (resume !== void 0) args.push("--resume", resume);
54
82
  const permissionMode = modelOptions?.permissionMode ?? config.permissionMode ?? policyFlags.permissionMode ?? "bypassPermissions";
55
- args.push("--permission-mode", q(permissionMode));
83
+ args.push("--permission-mode", permissionMode);
56
84
  const maxTurns = modelOptions?.maxTurns ?? config.maxTurns;
57
85
  if (maxTurns !== void 0) args.push("--max-turns", String(maxTurns));
58
- for (const dir of config.addDirs ?? []) args.push("--add-dir", q(dir));
86
+ for (const dir of config.addDirs ?? []) args.push("--add-dir", dir);
59
87
  const allowedTools = [...modelOptions?.allowedTools ?? config.allowedTools ?? [], ...policyFlags.allowedTools];
60
- if (allowedTools.length > 0) args.push("--allowedTools", q([...new Set(allowedTools)].join(",")));
88
+ if (allowedTools.length > 0) args.push("--allowedTools", [...new Set(allowedTools)].join(","));
61
89
  const disallowedTools = [...modelOptions?.disallowedTools ?? config.disallowedTools ?? [], ...policyFlags.disallowedTools];
62
- if (disallowedTools.length > 0) args.push("--disallowedTools", q([...new Set(disallowedTools)].join(",")));
90
+ if (disallowedTools.length > 0) args.push("--disallowedTools", [...new Set(disallowedTools)].join(","));
63
91
  const systemPrompts = normalizeSystemPrompts(options.systemPrompts).map((prompt) => prompt.content).filter((content) => content.trim() !== "");
64
92
  if (systemPrompts.length > 0) {
65
93
  const joined = systemPrompts.join("\n\n");
66
94
  const flag = config.systemPromptMode === "replace" ? "--system-prompt" : "--append-system-prompt";
67
- args.push(flag, q(joined));
95
+ args.push(flag, joined);
68
96
  }
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(" ")}`;
97
+ if (mcpConfigPath !== void 0) args.push("--mcp-config", mcpConfigPath);
98
+ if (hasJsonSchema) args.push("--json-schema", CLAUDE_JSON_SCHEMA_PLACEHOLDER);
99
+ if (permissionPromptTool !== void 0) args.push("--permission-prompt-tool", permissionPromptTool);
100
+ return args;
72
101
  }
73
102
  /**
74
103
  * Build the permission-prompt resolver the host MCP bridge exposes to claude
@@ -166,7 +195,9 @@ var ClaudeCodeTextAdapter = class extends BaseTextAdapter {
166
195
  ...permission !== void 0 ? { permission } : {},
167
196
  ...options.abortController?.signal ? { signal: options.abortController.signal } : {}
168
197
  });
169
- const { prompt, resume } = buildPrompt(options.messages, options.modelOptions?.sessionId);
198
+ const built = buildPrompt(options.messages, options.modelOptions?.sessionId);
199
+ const resume = built.resume;
200
+ const prompt = options.outputSchema !== void 0 ? appendOutputSchemaInstruction(built.prompt, options.outputSchema) : built.prompt;
170
201
  const runIdSegment = encodeRunId(runId);
171
202
  let mcpConfigArg;
172
203
  if (bridge) {
@@ -176,7 +207,21 @@ var ClaudeCodeTextAdapter = class extends BaseTextAdapter {
176
207
  tempFiles.push(mcpConfigPath);
177
208
  mcpConfigArg = mcpConfigFile;
178
209
  }
179
- const command = this.buildCommand(options, resume, mapPolicyToClaudeFlags(policy), mcpConfigArg, bridge && permission ? `mcp__${bridge.name}__${permission.toolName}` : void 0);
210
+ let jsonSchemaFile;
211
+ if (options.outputSchema !== void 0) {
212
+ jsonSchemaFile = `tanstack-output-schema-${runIdSegment}.json`;
213
+ const schemaPath = `${cwd}/${jsonSchemaFile}`;
214
+ await sandbox.fs.write(schemaPath, JSON.stringify(options.outputSchema));
215
+ tempFiles.push(schemaPath);
216
+ }
217
+ const runnerFile = `tanstack-claude-run-${runIdSegment}.mjs`;
218
+ await sandbox.fs.write(`${cwd}/${runnerFile}`, CLAUDE_RUNNER_SOURCE);
219
+ tempFiles.push(`${cwd}/${runnerFile}`);
220
+ const argv = this.buildArgv(options, resume, mapPolicyToClaudeFlags(policy), mcpConfigArg, bridge && permission ? `mcp__${bridge.name}__${permission.toolName}` : void 0, jsonSchemaFile !== void 0);
221
+ const argvFile = `tanstack-claude-argv-${runIdSegment}.json`;
222
+ await sandbox.fs.write(`${cwd}/${argvFile}`, JSON.stringify(argv));
223
+ tempFiles.push(`${cwd}/${argvFile}`);
224
+ const command = jsonSchemaFile === void 0 ? `node ${q(runnerFile)} ${q(argvFile)}` : `node ${q(runnerFile)} ${q(argvFile)} ${q(jsonSchemaFile)}`;
180
225
  let runCommand = command;
181
226
  let stdinInput = prompt;
182
227
  if (sandbox.capabilities.writableStdin === false) {
@@ -190,12 +235,18 @@ var ClaudeCodeTextAdapter = class extends BaseTextAdapter {
190
235
  provider: "claude-code",
191
236
  model: this.model
192
237
  });
238
+ const injectApiKey = (options.modelOptions?.authMode ?? this.adapterConfig.authMode ?? "api-key") === "api-key";
193
239
  const journalOptions = journalOptionsFor(durability, runId);
194
240
  const rawEvents = spawnNdjson(sandbox, runCommand, {
195
241
  cwd,
196
242
  ...stdinInput !== void 0 ? { input: stdinInput } : {},
197
243
  env: {
198
- IS_SANDBOX: "1",
244
+ ...sandbox.provider === "local-process" ? {} : {
245
+ IS_SANDBOX: "1",
246
+ CLAUDE_CODE_SANDBOXED: "1"
247
+ },
248
+ ...injectApiKey ? hostClaudeAuthEnv() : {},
249
+ ...localProcessHomeEnv(sandbox.provider),
199
250
  ...this.adapterConfig.env
200
251
  },
201
252
  ...options.abortController?.signal ? { signal: options.abortController.signal } : options.request?.signal ? { signal: options.request.signal } : {},
@@ -211,6 +262,7 @@ var ClaudeCodeTextAdapter = class extends BaseTextAdapter {
211
262
  threadId,
212
263
  ...options.parentRunId !== void 0 && { parentRunId: options.parentRunId },
213
264
  genId: createRunScopedIdGen(runId),
265
+ ...options.outputSchema ? { expectStructuredOutput: true } : {},
214
266
  onSdkMessage: (message) => logger.provider(`provider=claude-code type=${message.type}`, { chunk: message })
215
267
  }), channel.stream), durability, logger);
216
268
  if (this.adapterConfig.emitDiff !== false) try {
@@ -256,7 +308,7 @@ var ClaudeCodeTextAdapter = class extends BaseTextAdapter {
256
308
  }
257
309
  }
258
310
  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."));
311
+ return Promise.reject(/* @__PURE__ */ new Error("This harness honors outputSchema on chat() in the same turn. Pass outputSchema to chat(), or use a model adapter for a one-shot extract."));
260
312
  }
261
313
  };
262
314
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"text.js","names":[],"sources":["../../../src/adapters/text.ts"],"sourcesContent":["import { EventType, normalizeSystemPrompts } from '@tanstack/ai'\nimport { toRunErrorRawEvent } from '@tanstack/ai/adapter-internals'\nimport { BaseTextAdapter } from '@tanstack/ai/adapters'\nimport {\n SandboxCapability,\n alignedIfAttaching,\n approvalId,\n buildApprovalRequestedEvent,\n createBridgeEventChannel,\n createRunScopedIdGen,\n encodeRunId,\n getSandbox,\n getSandboxDurability,\n getSandboxPolicy,\n getToolBridgeProvisioner,\n getWorkspaceProjection,\n journalOptionsFor,\n mergeChunkStreams,\n nodeHttpBridgeProvisioner,\n resolveApproval,\n resolveDurableRunId,\n resolveDurableThreadId,\n spawnNdjson,\n} from '@tanstack/ai-sandbox'\nimport { buildPrompt } from '../messages/prompt'\nimport { translateSdkStream } from '../stream/translate'\nimport { mapPolicyToClaudeFlags } from './policy-map'\nimport { projectClaudeWorkspace } from './projection'\nimport type { ClaudePolicyFlags } from './policy-map'\nimport type {\n BridgeEventChannel,\n HostToolBridge,\n PermissionToolResult,\n SandboxHandle,\n SandboxPolicy,\n} from '@tanstack/ai-sandbox'\nimport type {\n StructuredOutputOptions,\n StructuredOutputResult,\n} from '@tanstack/ai/adapters'\nimport type {\n DefaultMessageMetadataByModality,\n Modality,\n StreamChunk,\n TextOptions,\n} from '@tanstack/ai'\nimport type { ClaudeCodeModel } from '../model-meta'\nimport type { ClaudeCodeTextProviderOptions } from '../provider-options'\nimport type { AgentSdkMessage } from '../stream/sdk-types'\n\nexport type ClaudeCodePermissionMode =\n | 'default'\n | 'acceptEdits'\n | 'bypassPermissions'\n | 'plan'\n\nconst DEFAULT_WORKDIR = '/workspace'\n\nexport interface ClaudeCodeTextConfig {\n /**\n * Working directory inside the sandbox where `claude` runs. Defaults to\n * `/workspace` (the conventional sandbox workspace root).\n */\n cwd?: string\n /**\n * Claude Code permission mode passed via `--permission-mode`. Defaults to\n * `'bypassPermissions'` — a sandbox is isolated, so the agent is allowed to\n * edit files and run commands without prompting. Tighten via `defineSandboxPolicy`\n * / this option for less autonomy.\n */\n permissionMode?: ClaudeCodePermissionMode\n /** Built-in tools the harness may use (`--allowedTools`). */\n allowedTools?: Array<string>\n /** Built-in tools removed from the harness (`--disallowedTools`). */\n disallowedTools?: Array<string>\n /** Extra directories the agent may access (`--add-dir`). */\n addDirs?: Array<string>\n /** Maximum harness-internal turns (`--max-turns`). */\n maxTurns?: number\n /**\n * How `systemPrompts` from `chat()` are applied:\n * - `'append'` (default): `--append-system-prompt` on top of the preset.\n * - `'replace'`: `--system-prompt` as the entire system prompt.\n */\n systemPromptMode?: 'append' | 'replace'\n /** Path/name of the claude executable inside the sandbox. Defaults to `claude`. */\n claudeExecutable?: string\n /** Emit token-level deltas via `--include-partial-messages` (default true). */\n streamPartials?: boolean\n /** Extra environment variables for the claude process inside the sandbox. */\n env?: Record<string, string>\n /** Emit a `file.changed` CUSTOM event with the git diff after the run (default true). */\n emitDiff?: boolean\n}\n\n/** POSIX single-quote escape for embedding values in the `claude …` command. */\nfunction q(value: string): string {\n return `'${value.replace(/'/g, `'\\\\''`)}'`\n}\n\n/** Format a host tool-bridge as claude's `--mcp-config` JSON. */\nfunction bridgeToMcpConfig(bridge: HostToolBridge): string {\n return JSON.stringify({\n mcpServers: {\n [bridge.name]: {\n type: 'http',\n url: bridge.url,\n headers: { Authorization: `Bearer ${bridge.token}` },\n },\n },\n })\n}\n\nexport class ClaudeCodeTextAdapter<\n TModel extends ClaudeCodeModel,\n> extends BaseTextAdapter<\n TModel,\n ClaudeCodeTextProviderOptions,\n ReadonlyArray<Modality> & readonly ['text'],\n DefaultMessageMetadataByModality,\n ReadonlyArray<string>,\n unknown,\n never\n> {\n readonly name = 'claude-code' as const\n\n // Harness adapter: requires a sandbox to run the agent CLI inside.\n override readonly requires = [SandboxCapability] as const\n\n private readonly adapterConfig: ClaudeCodeTextConfig\n\n constructor(config: ClaudeCodeTextConfig, model: TModel) {\n super({}, model)\n this.adapterConfig = config\n }\n\n private sandboxFrom(\n options: TextOptions<ClaudeCodeTextProviderOptions>,\n ): SandboxHandle {\n const ctx = options.capabilities\n if (!ctx) {\n throw new Error(\n 'Adapter \"claude-code\" requires a sandbox. Add withSandbox(defineSandbox({ ... })) ' +\n 'to chat() middleware (e.g. with the local-process or docker provider).',\n )\n }\n return getSandbox(ctx)\n }\n\n private workdir(options: TextOptions<ClaudeCodeTextProviderOptions>): string {\n return (\n options.modelOptions?.cwd ?? this.adapterConfig.cwd ?? DEFAULT_WORKDIR\n )\n }\n\n /** Build the `claude` command line (prompt goes via stdin, not argv). */\n private buildCommand(\n options: TextOptions<ClaudeCodeTextProviderOptions>,\n resume: string | undefined,\n policyFlags: ClaudePolicyFlags,\n mcpConfigPath: string | undefined,\n permissionPromptTool: string | undefined,\n ): string {\n const config = this.adapterConfig\n const modelOptions = options.modelOptions\n const exe = config.claudeExecutable ?? 'claude'\n\n const args: Array<string> = [\n '-p',\n '--output-format',\n 'stream-json',\n '--verbose',\n '--model',\n q(this.model),\n ]\n\n if (config.streamPartials !== false) args.push('--include-partial-messages')\n if (resume !== undefined) args.push('--resume', q(resume))\n\n // Precedence: per-call modelOptions > adapter config > policy > sandbox default.\n const permissionMode =\n modelOptions?.permissionMode ??\n config.permissionMode ??\n policyFlags.permissionMode ??\n 'bypassPermissions'\n args.push('--permission-mode', q(permissionMode))\n\n const maxTurns = modelOptions?.maxTurns ?? config.maxTurns\n if (maxTurns !== undefined) args.push('--max-turns', String(maxTurns))\n\n for (const dir of config.addDirs ?? []) args.push('--add-dir', q(dir))\n\n const allowedTools = [\n ...(modelOptions?.allowedTools ?? config.allowedTools ?? []),\n ...policyFlags.allowedTools,\n ]\n if (allowedTools.length > 0) {\n args.push('--allowedTools', q([...new Set(allowedTools)].join(',')))\n }\n const disallowedTools = [\n ...(modelOptions?.disallowedTools ?? config.disallowedTools ?? []),\n ...policyFlags.disallowedTools,\n ]\n if (disallowedTools.length > 0) {\n args.push('--disallowedTools', q([...new Set(disallowedTools)].join(',')))\n }\n\n const systemPrompts = normalizeSystemPrompts(options.systemPrompts)\n .map((prompt) => prompt.content)\n .filter((content) => content.trim() !== '')\n if (systemPrompts.length > 0) {\n const joined = systemPrompts.join('\\n\\n')\n const flag =\n config.systemPromptMode === 'replace'\n ? '--system-prompt'\n : '--append-system-prompt'\n args.push(flag, q(joined))\n }\n\n if (mcpConfigPath !== undefined) args.push('--mcp-config', q(mcpConfigPath))\n if (permissionPromptTool !== undefined) {\n args.push('--permission-prompt-tool', q(permissionPromptTool))\n }\n\n return `${exe} ${args.join(' ')}`\n }\n\n /**\n * Build the permission-prompt resolver the host MCP bridge exposes to claude\n * (`--permission-prompt-tool`). Maps claude's permission request onto the\n * sandbox policy + client approvals; on an `ask` action with no decision yet,\n * records an approval-requested event and denies (the client re-runs to grant).\n */\n private buildPermissionResolver(\n policy: SandboxPolicy | undefined,\n approvals: ReadonlyMap<string, boolean> | undefined,\n scripts: Record<string, string> | undefined,\n sink: Array<StreamChunk>,\n threadId: string,\n runId: string,\n ): (input: { tool_name?: string; input?: unknown }) => PermissionToolResult {\n const writeTools = new Set(['Write', 'Edit', 'MultiEdit', 'NotebookEdit'])\n const networkTools = new Set(['WebFetch', 'WebSearch'])\n return (request) => {\n const toolName = request.tool_name ?? 'tool'\n const cmdInput = request.input\n const command =\n toolName === 'Bash' &&\n cmdInput !== null &&\n typeof cmdInput === 'object' &&\n 'command' in cmdInput &&\n typeof (cmdInput as { command?: unknown }).command === 'string'\n ? (cmdInput as { command: string }).command\n : undefined\n const capability = writeTools.has(toolName)\n ? 'fileWrite'\n : networkTools.has(toolName)\n ? 'network'\n : undefined\n const id = approvalId({\n provider: 'claude-code',\n kind: command !== undefined ? 'command' : (capability ?? 'tool'),\n target: command ?? toolName,\n })\n const outcome = resolveApproval({\n policy,\n approvals,\n id,\n scripts,\n ...(command !== undefined ? { command } : {}),\n ...(capability !== undefined ? { capability } : {}),\n })\n if (outcome.needsApproval) {\n sink.push(\n buildApprovalRequestedEvent({\n approvalId: id,\n title: `Approve ${toolName}${command !== undefined ? `: ${command}` : ''}`,\n threadId,\n runId,\n detail: { provider: 'claude-code', toolName },\n }),\n )\n return {\n behavior: 'deny',\n message:\n 'Awaiting client approval. Approve in the UI and re-run to continue.',\n }\n }\n return outcome.decision === 'allow'\n ? { behavior: 'allow' }\n : { behavior: 'deny', message: 'Denied by sandbox policy.' }\n }\n }\n\n async *chatStream(\n options: TextOptions<ClaudeCodeTextProviderOptions>,\n ): AsyncIterable<StreamChunk> {\n const { logger } = options\n let bridge: HostToolBridge | undefined\n let channel: BridgeEventChannel | undefined\n const approvalRequests: Array<StreamChunk> = []\n // Temp files written for the run (bridge MCP config, redirected prompt) that\n // carry the bearer token / prompt; removed in `finally` so they don't linger\n // in the sandbox after the run.\n let cleanupSandbox: SandboxHandle | undefined\n const tempFiles: Array<string> = []\n try {\n const sandbox = this.sandboxFrom(options)\n cleanupSandbox = sandbox\n const cwd = this.workdir(options)\n // Durability comes from `withSandbox(sandbox, { runs, durability })`, read\n // back off the capability bus. Absent it, everything below resolves to\n // exactly today's behavior (no journal option, no alignment, and a\n // generated `runId` when the caller didn't supply one).\n const durability = options.capabilities\n ? getSandboxDurability(options.capabilities, { optional: true })\n : undefined\n // The journaled path below derives its journal path and its\n // message-id generator from `runId`. A resuming host must recompute\n // the same `runId` to find the same journal file and reproduce the\n // same translated ids — that's only possible when the caller supplies\n // `runId` explicitly. `resolveDurableRunId` enforces that loudly (via\n // `DurableRunIdRequiredError`) whenever durability is wired, and falls\n // back to a fresh `this.generateId()` otherwise, preserving today's\n // behavior for non-durable runs.\n const runId = resolveDurableRunId(options.runId, {\n durable: durability !== undefined,\n adapter: 'claude-code',\n fallback: () => this.generateId(),\n })\n // `threadId` is stamped on every chunk `translateSdkStream` emits, so an\n // ATTACHING run that mints a fresh one replays a stream the stored log\n // cannot match at index 0. `resolveDurableThreadId` refuses that up front\n // instead of letting alignment discover it mid-stream; a durable FRESH run\n // and a non-durable run both keep the generated fallback untouched.\n const threadId = resolveDurableThreadId(options.threadId, {\n durable: durability !== undefined,\n attaching: durability?.attach === true,\n adapter: 'claude-code',\n fallback: () => this.generateId(),\n })\n // Surfaces custom events from bridged tools (e.g. code mode console logs)\n // on this run's live output stream.\n channel = createBridgeEventChannel({ model: this.model, threadId, runId })\n\n // Idempotently project workspace skills/plugins/MCP into the sandbox in\n // claude's native format (guarded by the projection marker file).\n const projection = options.capabilities\n ? getWorkspaceProjection(options.capabilities, { optional: true })\n : undefined\n if (projection) await projectClaudeWorkspace(sandbox, projection)\n\n const policy = options.capabilities\n ? getSandboxPolicy(options.capabilities, { optional: true })\n : undefined\n\n // A permission-prompt tool gates the agent's native tools when a policy\n // can `ask`/`deny` (interactive approvals).\n const permission =\n policy !== undefined\n ? {\n toolName: 'approval_prompt',\n resolve: this.buildPermissionResolver(\n policy,\n options.approvals,\n projection?.scripts,\n approvalRequests,\n threadId,\n runId,\n ),\n }\n : undefined\n\n // Bridge chat()-provided server tools (and/or the permission tool) into\n // the sandbox over MCP.\n const hasTools = options.tools !== undefined && options.tools.length > 0\n if (hasTools || permission !== undefined) {\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 ...(permission !== undefined ? { permission } : {}),\n ...(options.abortController?.signal\n ? { signal: options.abortController.signal }\n : {}),\n })\n }\n\n const { prompt, resume } = buildPrompt(\n options.messages,\n options.modelOptions?.sessionId,\n )\n // Both files below name themselves after `runId`, and durability makes\n // `runId` CALLER-chosen. Raw, a `/` in it would silently turn each basename\n // into a nested path (writing outside the intended directory, or failing on\n // one that does not exist), `..` would climb out of that directory, and a\n // long id would fail the spawn with `ENAMETOOLONG`. `encodeRunId` collapses\n // any id to one bounded, injective path segment — the same encoder\n // `journalPaths` uses, so these files and the journal agree on how a given\n // id spells. Computed once so the two filenames cannot drift apart.\n const runIdSegment = encodeRunId(runId)\n\n // The bridge MCP config carries the per-run bearer token. Write it to a\n // file and pass claude the PATH, so the token never appears in argv (where\n // any process in the sandbox could read it via `ps` / `/proc/<pid>/cmdline`).\n let mcpConfigArg: string | undefined\n if (bridge) {\n // Pass claude a path RELATIVE to its cwd (the real workdir the handle\n // runs the process in). An absolute VIRTUAL path like `/workspace/…` is\n // wrong wherever claude runs outside a sandbox that literally uses\n // `/workspace` — e.g. local-process on Windows, where git-bash resolves\n // `/workspace` to `C:\\Program Files\\Git\\workspace` and the file is \"not\n // found\". The bare filename resolves correctly on every provider.\n const mcpConfigFile = `.tanstack-mcp-bridge-${runIdSegment}.json`\n const mcpConfigPath = `${cwd}/${mcpConfigFile}`\n await sandbox.fs.write(mcpConfigPath, bridgeToMcpConfig(bridge))\n tempFiles.push(mcpConfigPath)\n mcpConfigArg = mcpConfigFile\n }\n const command = this.buildCommand(\n options,\n resume,\n mapPolicyToClaudeFlags(policy),\n mcpConfigArg,\n bridge && permission\n ? `mcp__${bridge.name}__${permission.toolName}`\n : undefined,\n )\n\n // Deliver the prompt. The default feeds it over stdin (keeps it out of\n // argv). Providers without a writable host→process stdin (e.g. Cloudflare)\n // can't accept that write, so write the prompt to a file and redirect the\n // CLI's stdin from it in-shell (`claude -p … < file`) — still out of argv.\n let runCommand = command\n let stdinInput: string | undefined = prompt\n if (sandbox.capabilities.writableStdin === false) {\n const promptPath = `/tmp/tanstack-claude-prompt-${runIdSegment}`\n await sandbox.fs.write(promptPath, prompt)\n tempFiles.push(promptPath)\n runCommand = `${command} < ${q(promptPath)}`\n stdinInput = undefined\n }\n\n logger.request(\n `activity=chat provider=claude-code model=${this.model} sandbox=${sandbox.provider} messages=${options.messages.length} resume=${resume ?? 'none'}`,\n { provider: 'claude-code', model: this.model },\n )\n\n const journalOptions = journalOptionsFor(durability, runId)\n const rawEvents = spawnNdjson(sandbox, runCommand, {\n cwd,\n ...(stdinInput !== undefined ? { input: stdinInput } : {}),\n // claude maps `bypassPermissions` to `--dangerously-skip-permissions`,\n // which it refuses to run as root. Sandbox containers routinely run as\n // root (Docker / Cloudflare), so set `IS_SANDBOX=1` — claude's\n // documented escape hatch for skip-permissions in an isolated\n // environment — merged over the sandbox env (a caller-provided value\n // wins). Safe to set unconditionally; it is a no-op for stricter modes.\n env: { IS_SANDBOX: '1', ...this.adapterConfig.env },\n ...(options.abortController?.signal\n ? { signal: options.abortController.signal }\n : options.request?.signal\n ? { signal: options.request.signal }\n : {}),\n onNonJsonLine: (line) =>\n logger.provider(`provider=claude-code non-json line: ${line}`, {\n chunk: line,\n }),\n // Journal + attach both come from the sandbox durability capability, so\n // the attach route configures takeover by passing `attach: true` to\n // `withSandbox` — `chat()` stays free of sandbox vocabulary. Omitted\n // entirely (not passed as `undefined`) when the run isn't durable, so\n // `spawnNdjson` takes its original, unjournaled path byte-for-byte.\n ...(journalOptions === undefined ? {} : { journal: journalOptions }),\n })\n\n async function* asMessages(): AsyncIterable<AgentSdkMessage> {\n for await (const event of rawEvents) yield event as AgentSdkMessage\n }\n\n // `mergeChunkStreams` splices `channel.stream` (host-tool-bridge CUSTOM\n // events from LIVE tool execution) into the deterministic translator\n // output. Those events do not occur on a replay, so a takeover's replay is\n // NOT chunk-for-chunk identical to what the log holds. `alignedIfAttaching`\n // handles it: alignment skips stored out-of-band CUSTOM entries within a\n // bounded window (see `align.ts`), so a bridged-tool run can be taken over\n // without a spurious `JournalReplayDivergedError` — while a genuine\n // determinism regression still throws. The wrap goes OUTSIDE the merge:\n // the stored log holds the previous host's merged output, so aligning the\n // pre-merge translated stream alone would compare against a log it never\n // produced. On a non-attaching run this is a pure passthrough (see\n // `alignedIfAttaching`'s `attach` guard).\n yield* alignedIfAttaching(\n mergeChunkStreams(\n translateSdkStream(asMessages(), {\n model: this.model,\n runId,\n threadId,\n ...(options.parentRunId !== undefined && {\n parentRunId: options.parentRunId,\n }),\n // Deterministic on the journaled path: two translations of the same\n // journal prefix from a fresh generator seeded with the same\n // `runId` mint the same message ids (unlike `this.generateId()`,\n // which mixes in `Date.now()` / `Math.random()`). See\n // `createRunScopedIdGen` in `@tanstack/ai-sandbox`.\n genId: createRunScopedIdGen(runId),\n onSdkMessage: (message) =>\n logger.provider(`provider=claude-code type=${message.type}`, {\n chunk: message,\n }),\n }),\n channel.stream,\n ),\n durability,\n logger,\n )\n\n // Surface the working-tree diff so UIs can render what the agent changed.\n if (this.adapterConfig.emitDiff !== false) {\n try {\n const diff = await sandbox.process.exec(`git -C ${q(cwd)} diff`, {\n cwd,\n })\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 // not a git repo / git unavailable — skip the diff event\n }\n }\n\n // Surface any pending approval requests (policy `ask` actions awaiting a\n // client decision); the client approves and re-runs to continue.\n for (const event of approvalRequests) yield event\n } catch (error: unknown) {\n const err = error as Error & { code?: string }\n const rawEvent = toRunErrorRawEvent(error)\n logger.errors('claude-code.chatStream fatal', {\n error,\n source: 'claude-code.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 channel?.close()\n if (bridge) await bridge.close()\n // Remove the per-run token/prompt files. Best-effort: a cleanup failure\n // must not mask the run's own outcome.\n if (cleanupSandbox) {\n for (const path of tempFiles) {\n try {\n await cleanupSandbox.fs.remove(path)\n } catch {\n // file already gone / sandbox torn down — nothing to clean up\n }\n }\n }\n }\n }\n\n structuredOutput(\n _options: StructuredOutputOptions<ClaudeCodeTextProviderOptions>,\n ): Promise<StructuredOutputResult<unknown>> {\n return Promise.reject(\n new Error(\n 'Structured output is not yet supported by the in-sandbox Claude Code adapter. ' +\n 'Use a model adapter (e.g. anthropic) for structured output, or omit outputSchema.',\n ),\n )\n }\n}\n\n/**\n * Creates a Claude Code harness adapter that runs **inside a sandbox**.\n *\n * Unlike HTTP provider adapters, this is a *harness* adapter: it spawns the\n * `claude` CLI inside the sandbox provided by `withSandbox(...)` (the adapter\n * declares `requires: [SandboxCapability]`), streams its `stream-json` stdout\n * back as AG-UI events, and lets Claude Code run its own agent loop and native\n * tools (Bash, file edits, search, …) against the sandbox workspace. The\n * sandbox image must provide the `claude` executable and `ANTHROPIC_API_KEY`\n * in its environment (e.g. via `workspace.secrets`). The session id is\n * surfaced via a CUSTOM `claude-code.session-id` event so follow-up calls can\n * resume through `modelOptions.sessionId`.\n */\nexport function claudeCodeText<TModel extends ClaudeCodeModel>(\n model: TModel,\n config: ClaudeCodeTextConfig = {},\n): ClaudeCodeTextAdapter<TModel> {\n return new ClaudeCodeTextAdapter(config, model)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"text.js","names":[],"sources":["../../../src/adapters/text.ts"],"sourcesContent":["import { EventType, normalizeSystemPrompts } from '@tanstack/ai'\nimport {\n appendOutputSchemaInstruction,\n toRunErrorRawEvent,\n} from '@tanstack/ai/adapter-internals'\nimport { BaseTextAdapter } from '@tanstack/ai/adapters'\nimport {\n SandboxCapability,\n alignedIfAttaching,\n approvalId,\n buildApprovalRequestedEvent,\n createBridgeEventChannel,\n createRunScopedIdGen,\n encodeRunId,\n getSandbox,\n getSandboxDurability,\n getSandboxPolicy,\n getToolBridgeProvisioner,\n getWorkspaceProjection,\n journalOptionsFor,\n mergeChunkStreams,\n nodeHttpBridgeProvisioner,\n resolveApproval,\n resolveDurableRunId,\n resolveDurableThreadId,\n spawnNdjson,\n} from '@tanstack/ai-sandbox'\nimport { buildPrompt } from '../messages/prompt'\nimport { translateSdkStream } from '../stream/translate'\nimport { mapPolicyToClaudeFlags } from './policy-map'\nimport { projectClaudeWorkspace } from './projection'\nimport {\n CLAUDE_JSON_SCHEMA_PLACEHOLDER,\n CLAUDE_RUNNER_SOURCE,\n} from './claude-run-source'\n\nimport type { ClaudePolicyFlags } from './policy-map'\nimport type {\n BridgeEventChannel,\n HostToolBridge,\n PermissionToolResult,\n SandboxHandle,\n SandboxPolicy,\n} from '@tanstack/ai-sandbox'\nimport type {\n StructuredOutputOptions,\n StructuredOutputResult,\n} from '@tanstack/ai/adapters'\nimport type {\n DefaultMessageMetadataByModality,\n Modality,\n StreamChunk,\n TextOptions,\n} from '@tanstack/ai'\nimport type { ClaudeCodeModel } from '../model-meta'\nimport type { ClaudeCodeTextProviderOptions } from '../provider-options'\nimport type { AgentSdkMessage } from '../stream/sdk-types'\n\nexport type ClaudeCodePermissionMode =\n | 'default'\n | 'acceptEdits'\n | 'bypassPermissions'\n | 'plan'\n\nconst DEFAULT_WORKDIR = '/workspace'\n\nexport interface ClaudeCodeTextConfig {\n /**\n * Working directory inside the sandbox where `claude` runs. Defaults to\n * `/workspace` (the conventional sandbox workspace root).\n */\n cwd?: string\n /**\n * Claude Code permission mode passed via `--permission-mode`. Defaults to\n * `'bypassPermissions'` — a sandbox is isolated, so the agent is allowed to\n * edit files and run commands without prompting. Tighten via `defineSandboxPolicy`\n * / this option for less autonomy.\n */\n permissionMode?: ClaudeCodePermissionMode\n /** Built-in tools the harness may use (`--allowedTools`). */\n allowedTools?: Array<string>\n /** Built-in tools removed from the harness (`--disallowedTools`). */\n disallowedTools?: Array<string>\n /** Extra directories the agent may access (`--add-dir`). */\n addDirs?: Array<string>\n /** Maximum harness-internal turns (`--max-turns`). */\n maxTurns?: number\n /**\n * How `systemPrompts` from `chat()` are applied:\n * - `'append'` (default): `--append-system-prompt` on top of the preset.\n * - `'replace'`: `--system-prompt` as the entire system prompt.\n */\n systemPromptMode?: 'append' | 'replace'\n /** Path/name of the claude executable inside the sandbox. Defaults to `claude`. */\n claudeExecutable?: string\n /** Emit token-level deltas via `--include-partial-messages` (default true). */\n streamPartials?: boolean\n /** Extra environment variables for the claude process inside the sandbox. */\n env?: Record<string, string>\n /**\n * `'api-key'` (default) injects `ANTHROPIC_API_KEY`.\n * `'host'` uses `claude login` and does not inject the key.\n * Not inferred from the sandbox.\n */\n authMode?: 'host' | 'api-key'\n /** Emit a `file.changed` CUSTOM event with the git diff after the run (default true). */\n emitDiff?: boolean\n}\n\n/** POSIX single-quote escape for embedding values in the `claude …` command. */\nfunction q(value: string): string {\n return `'${value.replace(/'/g, `'\\\\''`)}'`\n}\n\n/** Copy host Anthropic auth into the sandbox process. Docker `exec` Env replaces the container env, so a key set only at create time can vanish. */\nfunction hostClaudeAuthEnv(): Record<string, string> {\n const env: Record<string, string> = {}\n const apiKey = process.env.ANTHROPIC_API_KEY\n if (apiKey) env.ANTHROPIC_API_KEY = apiKey\n const authToken = process.env.ANTHROPIC_AUTH_TOKEN\n if (authToken) env.ANTHROPIC_AUTH_TOKEN = authToken\n return env\n}\n\n/**\n * Windows Node often has USERPROFILE but no HOME. Claude then cannot find\n * `~/.claude.json` (the `claude login` file) and prints \"Not logged in\".\n */\nfunction localProcessHomeEnv(provider: string): Record<string, string> {\n if (provider !== 'local-process') return {}\n if (process.env.HOME) return {}\n const home = process.env.USERPROFILE\n return home ? { HOME: home } : {}\n}\n\n/** Format a host tool-bridge as claude's `--mcp-config` JSON. */\nfunction bridgeToMcpConfig(bridge: HostToolBridge): string {\n return JSON.stringify({\n mcpServers: {\n [bridge.name]: {\n type: 'http',\n url: bridge.url,\n headers: { Authorization: `Bearer ${bridge.token}` },\n },\n },\n })\n}\n\nexport class ClaudeCodeTextAdapter<\n TModel extends ClaudeCodeModel,\n> extends BaseTextAdapter<\n TModel,\n ClaudeCodeTextProviderOptions,\n ReadonlyArray<Modality> & readonly ['text'],\n DefaultMessageMetadataByModality,\n ReadonlyArray<string>,\n unknown,\n never\n> {\n readonly name = 'claude-code' as const\n\n // Harness adapter: requires a sandbox to run the agent CLI inside.\n override readonly requires = [SandboxCapability] as const\n\n private readonly adapterConfig: ClaudeCodeTextConfig\n\n constructor(config: ClaudeCodeTextConfig, model: TModel) {\n super({}, model)\n this.adapterConfig = config\n }\n\n private sandboxFrom(\n options: TextOptions<ClaudeCodeTextProviderOptions>,\n ): SandboxHandle {\n const ctx = options.capabilities\n if (!ctx) {\n throw new Error(\n 'Adapter \"claude-code\" requires a sandbox. Add withSandbox(defineSandbox({ ... })) ' +\n 'to chat() middleware (e.g. with the local-process or docker provider).',\n )\n }\n return getSandbox(ctx)\n }\n\n private workdir(options: TextOptions<ClaudeCodeTextProviderOptions>): string {\n return (\n options.modelOptions?.cwd ?? this.adapterConfig.cwd ?? DEFAULT_WORKDIR\n )\n }\n\n supportsCombinedToolsAndSchema(): boolean {\n return true\n }\n\n combinedStructuredOutputSource(): 'event' {\n return 'event'\n }\n\n /** Build the `claude` command line (prompt goes via stdin, not argv). */\n private buildArgv(\n options: TextOptions<ClaudeCodeTextProviderOptions>,\n resume: string | undefined,\n policyFlags: ClaudePolicyFlags,\n mcpConfigPath: string | undefined,\n permissionPromptTool: string | undefined,\n hasJsonSchema: boolean,\n ): Array<string> {\n const config = this.adapterConfig\n const modelOptions = options.modelOptions\n const exeParts = (config.claudeExecutable ?? 'claude').split(' ')\n\n // `--setting-sources user` before `-p`. Do not pass `--bare`: that flag\n // skips stored `claude login` credentials and prints\n // \"Not logged in · Please run /login\" (claude-code#51047).\n // `-p` can take the next token as the prompt, so these flags stay first.\n const args: Array<string> = [\n ...exeParts,\n '--setting-sources',\n 'user',\n '-p',\n '--output-format',\n 'stream-json',\n '--verbose',\n '--model',\n this.model,\n ]\n\n if (config.streamPartials !== false) args.push('--include-partial-messages')\n if (resume !== undefined) args.push('--resume', resume)\n\n const permissionMode =\n modelOptions?.permissionMode ??\n config.permissionMode ??\n policyFlags.permissionMode ??\n 'bypassPermissions'\n args.push('--permission-mode', permissionMode)\n\n const maxTurns = modelOptions?.maxTurns ?? config.maxTurns\n if (maxTurns !== undefined) args.push('--max-turns', String(maxTurns))\n\n for (const dir of config.addDirs ?? []) args.push('--add-dir', dir)\n\n const allowedTools = [\n ...(modelOptions?.allowedTools ?? config.allowedTools ?? []),\n ...policyFlags.allowedTools,\n ]\n if (allowedTools.length > 0) {\n args.push('--allowedTools', [...new Set(allowedTools)].join(','))\n }\n const disallowedTools = [\n ...(modelOptions?.disallowedTools ?? config.disallowedTools ?? []),\n ...policyFlags.disallowedTools,\n ]\n if (disallowedTools.length > 0) {\n args.push('--disallowedTools', [...new Set(disallowedTools)].join(','))\n }\n\n const systemPrompts = normalizeSystemPrompts(options.systemPrompts)\n .map((prompt) => prompt.content)\n .filter((content) => content.trim() !== '')\n if (systemPrompts.length > 0) {\n const joined = systemPrompts.join('\\n\\n')\n const flag =\n config.systemPromptMode === 'replace'\n ? '--system-prompt'\n : '--append-system-prompt'\n args.push(flag, joined)\n }\n\n if (mcpConfigPath !== undefined) args.push('--mcp-config', mcpConfigPath)\n if (hasJsonSchema) {\n args.push('--json-schema', CLAUDE_JSON_SCHEMA_PLACEHOLDER)\n }\n if (permissionPromptTool !== undefined) {\n args.push('--permission-prompt-tool', permissionPromptTool)\n }\n\n return args\n }\n\n /**\n * Build the permission-prompt resolver the host MCP bridge exposes to claude\n * (`--permission-prompt-tool`). Maps claude's permission request onto the\n * sandbox policy + client approvals; on an `ask` action with no decision yet,\n * records an approval-requested event and denies (the client re-runs to grant).\n */\n private buildPermissionResolver(\n policy: SandboxPolicy | undefined,\n approvals: ReadonlyMap<string, boolean> | undefined,\n scripts: Record<string, string> | undefined,\n sink: Array<StreamChunk>,\n threadId: string,\n runId: string,\n ): (input: { tool_name?: string; input?: unknown }) => PermissionToolResult {\n const writeTools = new Set(['Write', 'Edit', 'MultiEdit', 'NotebookEdit'])\n const networkTools = new Set(['WebFetch', 'WebSearch'])\n return (request) => {\n const toolName = request.tool_name ?? 'tool'\n const cmdInput = request.input\n const command =\n toolName === 'Bash' &&\n cmdInput !== null &&\n typeof cmdInput === 'object' &&\n 'command' in cmdInput &&\n typeof (cmdInput as { command?: unknown }).command === 'string'\n ? (cmdInput as { command: string }).command\n : undefined\n const capability = writeTools.has(toolName)\n ? 'fileWrite'\n : networkTools.has(toolName)\n ? 'network'\n : undefined\n const id = approvalId({\n provider: 'claude-code',\n kind: command !== undefined ? 'command' : (capability ?? 'tool'),\n target: command ?? toolName,\n })\n const outcome = resolveApproval({\n policy,\n approvals,\n id,\n scripts,\n ...(command !== undefined ? { command } : {}),\n ...(capability !== undefined ? { capability } : {}),\n })\n if (outcome.needsApproval) {\n sink.push(\n buildApprovalRequestedEvent({\n approvalId: id,\n title: `Approve ${toolName}${command !== undefined ? `: ${command}` : ''}`,\n threadId,\n runId,\n detail: { provider: 'claude-code', toolName },\n }),\n )\n return {\n behavior: 'deny',\n message:\n 'Awaiting client approval. Approve in the UI and re-run to continue.',\n }\n }\n return outcome.decision === 'allow'\n ? { behavior: 'allow' }\n : { behavior: 'deny', message: 'Denied by sandbox policy.' }\n }\n }\n\n async *chatStream(\n options: TextOptions<ClaudeCodeTextProviderOptions>,\n ): AsyncIterable<StreamChunk> {\n const { logger } = options\n let bridge: HostToolBridge | undefined\n let channel: BridgeEventChannel | undefined\n const approvalRequests: Array<StreamChunk> = []\n // Temp files written for the run (bridge MCP config, redirected prompt) that\n // carry the bearer token / prompt; removed in `finally` so they don't linger\n // in the sandbox after the run.\n let cleanupSandbox: SandboxHandle | undefined\n const tempFiles: Array<string> = []\n try {\n const sandbox = this.sandboxFrom(options)\n cleanupSandbox = sandbox\n const cwd = this.workdir(options)\n\n // Durability comes from `withSandbox(sandbox, { runs, durability })`, read\n // back off the capability bus. Absent it, everything below resolves to\n // exactly today's behavior (no journal option, no alignment, and a\n // generated `runId` when the caller didn't supply one).\n const durability = options.capabilities\n ? getSandboxDurability(options.capabilities, { optional: true })\n : undefined\n // The journaled path below derives its journal path and its\n // message-id generator from `runId`. A resuming host must recompute\n // the same `runId` to find the same journal file and reproduce the\n // same translated ids — that's only possible when the caller supplies\n // `runId` explicitly. `resolveDurableRunId` enforces that loudly (via\n // `DurableRunIdRequiredError`) whenever durability is wired, and falls\n // back to a fresh `this.generateId()` otherwise, preserving today's\n // behavior for non-durable runs.\n const runId = resolveDurableRunId(options.runId, {\n durable: durability !== undefined,\n adapter: 'claude-code',\n fallback: () => this.generateId(),\n })\n // `threadId` is stamped on every chunk `translateSdkStream` emits, so an\n // ATTACHING run that mints a fresh one replays a stream the stored log\n // cannot match at index 0. `resolveDurableThreadId` refuses that up front\n // instead of letting alignment discover it mid-stream; a durable FRESH run\n // and a non-durable run both keep the generated fallback untouched.\n const threadId = resolveDurableThreadId(options.threadId, {\n durable: durability !== undefined,\n attaching: durability?.attach === true,\n adapter: 'claude-code',\n fallback: () => this.generateId(),\n })\n // Surfaces custom events from bridged tools (e.g. code mode console logs)\n // on this run's live output stream.\n channel = createBridgeEventChannel({ model: this.model, threadId, runId })\n\n // Idempotently project workspace skills/plugins/MCP into the sandbox in\n // claude's native format (guarded by the projection marker file).\n const projection = options.capabilities\n ? getWorkspaceProjection(options.capabilities, { optional: true })\n : undefined\n if (projection) await projectClaudeWorkspace(sandbox, projection)\n\n const policy = options.capabilities\n ? getSandboxPolicy(options.capabilities, { optional: true })\n : undefined\n\n // A permission-prompt tool gates the agent's native tools when a policy\n // can `ask`/`deny` (interactive approvals).\n const permission =\n policy !== undefined\n ? {\n toolName: 'approval_prompt',\n resolve: this.buildPermissionResolver(\n policy,\n options.approvals,\n projection?.scripts,\n approvalRequests,\n threadId,\n runId,\n ),\n }\n : undefined\n\n // Bridge chat()-provided server tools (and/or the permission tool) into\n // the sandbox over MCP.\n const hasTools = options.tools !== undefined && options.tools.length > 0\n if (hasTools || permission !== undefined) {\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 ...(permission !== undefined ? { permission } : {}),\n ...(options.abortController?.signal\n ? { signal: options.abortController.signal }\n : {}),\n })\n }\n\n const built = buildPrompt(\n options.messages,\n options.modelOptions?.sessionId,\n )\n const resume = built.resume\n const prompt =\n options.outputSchema !== undefined\n ? appendOutputSchemaInstruction(built.prompt, options.outputSchema)\n : built.prompt\n // Both files below name themselves after `runId`, and durability makes\n // `runId` CALLER-chosen. Raw, a `/` in it would silently turn each basename\n // into a nested path (writing outside the intended directory, or failing on\n // one that does not exist), `..` would climb out of that directory, and a\n // long id would fail the spawn with `ENAMETOOLONG`. `encodeRunId` collapses\n // any id to one bounded, injective path segment — the same encoder\n // `journalPaths` uses, so these files and the journal agree on how a given\n // id spells. Computed once so the two filenames cannot drift apart.\n const runIdSegment = encodeRunId(runId)\n\n // The bridge MCP config carries the per-run bearer token. Write it to a\n // file and pass claude the PATH, so the token never appears in argv (where\n // any process in the sandbox could read it via `ps` / `/proc/<pid>/cmdline`).\n let mcpConfigArg: string | undefined\n if (bridge) {\n // Pass claude a path RELATIVE to its cwd (the real workdir the handle\n // runs the process in). An absolute VIRTUAL path like `/workspace/…` is\n // wrong wherever claude runs outside a sandbox that literally uses\n // `/workspace` — e.g. local-process on Windows, where git-bash resolves\n // `/workspace` to `C:\\Program Files\\Git\\workspace` and the file is \"not\n // found\". The bare filename resolves correctly on every provider.\n const mcpConfigFile = `.tanstack-mcp-bridge-${runIdSegment}.json`\n const mcpConfigPath = `${cwd}/${mcpConfigFile}`\n await sandbox.fs.write(mcpConfigPath, bridgeToMcpConfig(bridge))\n tempFiles.push(mcpConfigPath)\n mcpConfigArg = mcpConfigFile\n }\n let jsonSchemaFile: string | undefined\n if (options.outputSchema !== undefined) {\n jsonSchemaFile = `tanstack-output-schema-${runIdSegment}.json`\n const schemaPath = `${cwd}/${jsonSchemaFile}`\n await sandbox.fs.write(schemaPath, JSON.stringify(options.outputSchema))\n tempFiles.push(schemaPath)\n }\n const runnerFile = `tanstack-claude-run-${runIdSegment}.mjs`\n await sandbox.fs.write(`${cwd}/${runnerFile}`, CLAUDE_RUNNER_SOURCE)\n tempFiles.push(`${cwd}/${runnerFile}`)\n const argv = this.buildArgv(\n options,\n resume,\n mapPolicyToClaudeFlags(policy),\n mcpConfigArg,\n bridge && permission\n ? `mcp__${bridge.name}__${permission.toolName}`\n : undefined,\n jsonSchemaFile !== undefined,\n )\n // Filenames only on the shell line. JSON (schema, system prompt, MCP\n // config path) lives in the argv file so git-bash cannot retokenize it.\n const argvFile = `tanstack-claude-argv-${runIdSegment}.json`\n await sandbox.fs.write(`${cwd}/${argvFile}`, JSON.stringify(argv))\n tempFiles.push(`${cwd}/${argvFile}`)\n const command =\n jsonSchemaFile === undefined\n ? `node ${q(runnerFile)} ${q(argvFile)}`\n : `node ${q(runnerFile)} ${q(argvFile)} ${q(jsonSchemaFile)}`\n\n // Deliver the prompt. The default feeds it over stdin (keeps it out of\n // argv). Providers without a writable host→process stdin (e.g. Cloudflare)\n // can't accept that write, so write the prompt to a file and redirect the\n // CLI's stdin from it in-shell (`claude -p … < file`) — still out of argv.\n let runCommand = command\n let stdinInput: string | undefined = prompt\n if (sandbox.capabilities.writableStdin === false) {\n const promptPath = `/tmp/tanstack-claude-prompt-${runIdSegment}`\n await sandbox.fs.write(promptPath, prompt)\n tempFiles.push(promptPath)\n runCommand = `${command} < ${q(promptPath)}`\n stdinInput = undefined\n }\n\n logger.request(\n `activity=chat provider=claude-code model=${this.model} sandbox=${sandbox.provider} messages=${options.messages.length} resume=${resume ?? 'none'}`,\n { provider: 'claude-code', model: this.model },\n )\n\n const authMode =\n options.modelOptions?.authMode ??\n this.adapterConfig.authMode ??\n 'api-key'\n const injectApiKey = authMode === 'api-key'\n\n const journalOptions = journalOptionsFor(durability, runId)\n const rawEvents = spawnNdjson(sandbox, runCommand, {\n cwd,\n ...(stdinInput !== undefined ? { input: stdinInput } : {}),\n // Isolated sandboxes often run as root. Claude refuses\n // `--dangerously-skip-permissions` as root unless IS_SANDBOX=1.\n // CLAUDE_CODE_SANDBOXED marks a real isolation boundary. Do not set\n // either on local-process: that provider runs on the host.\n env: {\n ...(sandbox.provider === 'local-process'\n ? {}\n : {\n IS_SANDBOX: '1',\n CLAUDE_CODE_SANDBOXED: '1',\n }),\n ...(injectApiKey ? hostClaudeAuthEnv() : {}),\n ...localProcessHomeEnv(sandbox.provider),\n ...this.adapterConfig.env,\n },\n ...(options.abortController?.signal\n ? { signal: options.abortController.signal }\n : options.request?.signal\n ? { signal: options.request.signal }\n : {}),\n onNonJsonLine: (line) =>\n logger.provider(`provider=claude-code non-json line: ${line}`, {\n chunk: line,\n }),\n // Journal + attach both come from the sandbox durability capability, so\n // the attach route configures takeover by passing `attach: true` to\n // `withSandbox` — `chat()` stays free of sandbox vocabulary. Omitted\n // entirely (not passed as `undefined`) when the run isn't durable, so\n // `spawnNdjson` takes its original, unjournaled path byte-for-byte.\n ...(journalOptions === undefined ? {} : { journal: journalOptions }),\n })\n\n async function* asMessages(): AsyncIterable<AgentSdkMessage> {\n for await (const event of rawEvents) yield event as AgentSdkMessage\n }\n\n // `mergeChunkStreams` splices `channel.stream` (host-tool-bridge CUSTOM\n // events from LIVE tool execution) into the deterministic translator\n // output. Those events do not occur on a replay, so a takeover's replay is\n // NOT chunk-for-chunk identical to what the log holds. `alignedIfAttaching`\n // handles it: alignment skips stored out-of-band CUSTOM entries within a\n // bounded window (see `align.ts`), so a bridged-tool run can be taken over\n // without a spurious `JournalReplayDivergedError` — while a genuine\n // determinism regression still throws. The wrap goes OUTSIDE the merge:\n // the stored log holds the previous host's merged output, so aligning the\n // pre-merge translated stream alone would compare against a log it never\n // produced. On a non-attaching run this is a pure passthrough (see\n // `alignedIfAttaching`'s `attach` guard).\n yield* alignedIfAttaching(\n mergeChunkStreams(\n translateSdkStream(asMessages(), {\n model: this.model,\n runId,\n threadId,\n ...(options.parentRunId !== undefined && {\n parentRunId: options.parentRunId,\n }),\n // Deterministic on the journaled path: two translations of the same\n // journal prefix from a fresh generator seeded with the same\n // `runId` mint the same message ids (unlike `this.generateId()`,\n // which mixes in `Date.now()` / `Math.random()`). See\n // `createRunScopedIdGen` in `@tanstack/ai-sandbox`.\n genId: createRunScopedIdGen(runId),\n ...(options.outputSchema ? { expectStructuredOutput: true } : {}),\n onSdkMessage: (message) =>\n logger.provider(`provider=claude-code type=${message.type}`, {\n chunk: message,\n }),\n }),\n channel.stream,\n ),\n durability,\n logger,\n )\n\n // Surface the working-tree diff so UIs can render what the agent changed.\n if (this.adapterConfig.emitDiff !== false) {\n try {\n const diff = await sandbox.process.exec(`git -C ${q(cwd)} diff`, {\n cwd,\n })\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 // not a git repo / git unavailable — skip the diff event\n }\n }\n\n // Surface any pending approval requests (policy `ask` actions awaiting a\n // client decision); the client approves and re-runs to continue.\n for (const event of approvalRequests) yield event\n } catch (error: unknown) {\n const err = error as Error & { code?: string }\n const rawEvent = toRunErrorRawEvent(error)\n logger.errors('claude-code.chatStream fatal', {\n error,\n source: 'claude-code.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 channel?.close()\n if (bridge) await bridge.close()\n // Remove the per-run token/prompt files. Best-effort: a cleanup failure\n // must not mask the run's own outcome.\n if (cleanupSandbox) {\n for (const path of tempFiles) {\n try {\n await cleanupSandbox.fs.remove(path)\n } catch {\n // file already gone / sandbox torn down — nothing to clean up\n }\n }\n }\n }\n }\n\n structuredOutput(\n _options: StructuredOutputOptions<ClaudeCodeTextProviderOptions>,\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 * Creates a Claude Code harness adapter that runs **inside a sandbox**.\n *\n * Unlike HTTP provider adapters, this is a *harness* adapter: it spawns the\n * `claude` CLI inside the sandbox provided by `withSandbox(...)` (the adapter\n * declares `requires: [SandboxCapability]`), streams its `stream-json` stdout\n * back as AG-UI events, and lets Claude Code run its own agent loop and native\n * tools (Bash, file edits, search, …) against the sandbox workspace. The\n * sandbox image must provide the `claude` executable and `ANTHROPIC_API_KEY`\n * in its environment (e.g. via `workspace.secrets`). The session id is\n * surfaced via a CUSTOM `claude-code.session-id` event so follow-up calls can\n * resume through `modelOptions.sessionId`.\n */\nexport function claudeCodeText<TModel extends ClaudeCodeModel>(\n model: TModel,\n config: ClaudeCodeTextConfig = {},\n): ClaudeCodeTextAdapter<TModel> {\n return new ClaudeCodeTextAdapter(config, model)\n}\n"],"mappings":"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"}
@@ -26,5 +26,11 @@ export interface ClaudeCodeTextProviderOptions {
26
26
  disallowedTools?: Array<string>;
27
27
  /** Per-call override of the harness working directory. */
28
28
  cwd?: string;
29
+ /**
30
+ * `'api-key'` (default) injects `ANTHROPIC_API_KEY`.
31
+ * `'host'` uses `claude login`.
32
+ * Not inferred from the sandbox.
33
+ */
34
+ authMode?: 'host' | 'api-key';
29
35
  }
30
36
  export {};
@@ -71,6 +71,9 @@ export type SdkRawStreamEvent = {
71
71
  index: number;
72
72
  content_block: {
73
73
  type: string;
74
+ id?: string;
75
+ name?: string;
76
+ input?: unknown;
74
77
  };
75
78
  } | {
76
79
  type: 'content_block_delta';
@@ -79,6 +82,7 @@ export type SdkRawStreamEvent = {
79
82
  type: string;
80
83
  text?: string;
81
84
  thinking?: string;
85
+ partial_json?: string;
82
86
  };
83
87
  } | {
84
88
  type: 'content_block_stop';
@@ -4,6 +4,12 @@ import { AgentSdkMessage } from './sdk-types.js';
4
4
  export declare const SESSION_ID_EVENT = "claude-code.session-id";
5
5
  /** Server name used for bridged TanStack tools (model sees `mcp__tanstack__<name>`). */
6
6
  export declare const BRIDGED_MCP_SERVER_NAME = "tanstack";
7
+ /**
8
+ * Claude Code `--json-schema` injects a fake tool named StructuredOutput.
9
+ * The model "calls" it with the schema JSON. The result message may omit
10
+ * `structured_output`; harvest the tool input in that case.
11
+ */
12
+ export declare const SYNTHETIC_STRUCTURED_OUTPUT_TOOL = "StructuredOutput";
7
13
  /** Claude Code-specific usage details attached to RUN_FINISHED usage. */
8
14
  export type ClaudeCodeProviderUsageDetails = {
9
15
  /** Total cost of the harness run in USD, as reported by Claude Code. */
@@ -19,6 +25,8 @@ export interface TranslateContext {
19
25
  onSessionId?: (sessionId: string) => void;
20
26
  /** Called for each raw SDK message, for logging. */
21
27
  onSdkMessage?: (message: AgentSdkMessage) => void;
28
+ /** Emit structured-output events from `result.structured_output`. */
29
+ expectStructuredOutput?: boolean;
22
30
  }
23
31
  /**
24
32
  * Strip the bridged MCP server prefix so tool-call events match the TanStack