@atrvd/trigger 0.1.4 → 0.1.6
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.
- package/dist/index.d.mts +13 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +11 -7
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +11 -7
- package/dist/index.mjs.map +1 -1
- package/package.json +12 -11
package/dist/index.d.mts
CHANGED
|
@@ -51,6 +51,19 @@ interface ClaudeCodeInput<T = unknown> {
|
|
|
51
51
|
* Mutually exclusive with `schema`.
|
|
52
52
|
*/
|
|
53
53
|
jsonSchema?: Record<string, unknown>;
|
|
54
|
+
/**
|
|
55
|
+
* Override the default tool list passed via `--tools`.
|
|
56
|
+
* Default: `["Read", "Grep", "Glob"]` (read-only by design).
|
|
57
|
+
* Pass `["Bash", "Read", "Grep", "Glob"]` to also allow Bash execution.
|
|
58
|
+
*/
|
|
59
|
+
tools?: string[];
|
|
60
|
+
/**
|
|
61
|
+
* Restrict Bash commands via `--allowed-tools` flag.
|
|
62
|
+
* Each entry is a pattern like `"Bash(git log*)"`.
|
|
63
|
+
* Only meaningful when `tools` includes `"Bash"`.
|
|
64
|
+
* When omitted, no `--allowed-tools` flag is added.
|
|
65
|
+
*/
|
|
66
|
+
allowedTools?: string[];
|
|
54
67
|
/**
|
|
55
68
|
* List of CCS account names to rotate through on `is_error: true` responses.
|
|
56
69
|
* Each account maps to `CLAUDE_CONFIG_DIR=~/.ccs/instances/<name>`.
|
package/dist/index.d.ts
CHANGED
|
@@ -51,6 +51,19 @@ interface ClaudeCodeInput<T = unknown> {
|
|
|
51
51
|
* Mutually exclusive with `schema`.
|
|
52
52
|
*/
|
|
53
53
|
jsonSchema?: Record<string, unknown>;
|
|
54
|
+
/**
|
|
55
|
+
* Override the default tool list passed via `--tools`.
|
|
56
|
+
* Default: `["Read", "Grep", "Glob"]` (read-only by design).
|
|
57
|
+
* Pass `["Bash", "Read", "Grep", "Glob"]` to also allow Bash execution.
|
|
58
|
+
*/
|
|
59
|
+
tools?: string[];
|
|
60
|
+
/**
|
|
61
|
+
* Restrict Bash commands via `--allowed-tools` flag.
|
|
62
|
+
* Each entry is a pattern like `"Bash(git log*)"`.
|
|
63
|
+
* Only meaningful when `tools` includes `"Bash"`.
|
|
64
|
+
* When omitted, no `--allowed-tools` flag is added.
|
|
65
|
+
*/
|
|
66
|
+
allowedTools?: string[];
|
|
54
67
|
/**
|
|
55
68
|
* List of CCS account names to rotate through on `is_error: true` responses.
|
|
56
69
|
* Each account maps to `CLAUDE_CONFIG_DIR=~/.ccs/instances/<name>`.
|
package/dist/index.js
CHANGED
|
@@ -41,8 +41,10 @@ function killGracefully(proc) {
|
|
|
41
41
|
proc.kill("SIGKILL");
|
|
42
42
|
}, 5e3).unref();
|
|
43
43
|
}
|
|
44
|
-
function spawnOnce(binary, prompt, model, timeout, maxOutputBytes, env, addDir, mcpConfigPath, jsonSchemaStr) {
|
|
45
|
-
const
|
|
44
|
+
function spawnOnce(binary, prompt, model, timeout, maxOutputBytes, env, addDir, mcpConfigPath, jsonSchemaStr, tools, allowedTools) {
|
|
45
|
+
const toolsList = tools?.length ? tools.join(",") : "Read,Grep,Glob";
|
|
46
|
+
const args = ["--print", "--output-format", "json", "--model", model, "--tools", toolsList];
|
|
47
|
+
if (allowedTools?.length) args.push("--allowed-tools", ...allowedTools);
|
|
46
48
|
if (mcpConfigPath) args.push("--mcp-config", mcpConfigPath);
|
|
47
49
|
args.push("--add-dir", addDir);
|
|
48
50
|
if (jsonSchemaStr) {
|
|
@@ -143,9 +145,9 @@ var IsErrorResponse = class extends Error {
|
|
|
143
145
|
this.name = "IsErrorResponse";
|
|
144
146
|
}
|
|
145
147
|
};
|
|
146
|
-
function spawnWithAccounts(binary, prompt, model, timeout, maxOutputBytes, env, accounts, addDir, mcpConfigPath, jsonSchemaStr) {
|
|
148
|
+
function spawnWithAccounts(binary, prompt, model, timeout, maxOutputBytes, env, accounts, addDir, mcpConfigPath, jsonSchemaStr, tools, allowedTools) {
|
|
147
149
|
if (!accounts || accounts.length === 0) {
|
|
148
|
-
return spawnOnce(binary, prompt, model, timeout, maxOutputBytes, env, addDir, mcpConfigPath, jsonSchemaStr);
|
|
150
|
+
return spawnOnce(binary, prompt, model, timeout, maxOutputBytes, env, addDir, mcpConfigPath, jsonSchemaStr, tools, allowedTools);
|
|
149
151
|
}
|
|
150
152
|
return (async () => {
|
|
151
153
|
let lastIsError;
|
|
@@ -155,7 +157,7 @@ function spawnWithAccounts(binary, prompt, model, timeout, maxOutputBytes, env,
|
|
|
155
157
|
CLAUDE_CONFIG_DIR: `${(0, import_node_os.homedir)()}/.ccs/instances/${account}`
|
|
156
158
|
};
|
|
157
159
|
try {
|
|
158
|
-
return await spawnOnce(binary, prompt, model, timeout, maxOutputBytes, accountEnv, addDir, mcpConfigPath, jsonSchemaStr);
|
|
160
|
+
return await spawnOnce(binary, prompt, model, timeout, maxOutputBytes, accountEnv, addDir, mcpConfigPath, jsonSchemaStr, tools, allowedTools);
|
|
159
161
|
} catch (err) {
|
|
160
162
|
if (err instanceof IsErrorResponse) {
|
|
161
163
|
lastIsError = err;
|
|
@@ -200,7 +202,9 @@ async function runClaudeCode(input) {
|
|
|
200
202
|
jsonSchema,
|
|
201
203
|
ccsAccounts,
|
|
202
204
|
mcpConfigPath,
|
|
203
|
-
addDir
|
|
205
|
+
addDir,
|
|
206
|
+
tools,
|
|
207
|
+
allowedTools
|
|
204
208
|
} = input;
|
|
205
209
|
if (!prompt || prompt.trim().length === 0) {
|
|
206
210
|
throw new Error("prompt cannot be empty");
|
|
@@ -230,7 +234,7 @@ async function runClaudeCode(input) {
|
|
|
230
234
|
throw new Error(`Failed to serialize jsonSchema to JSON: ${String(err)}`);
|
|
231
235
|
}
|
|
232
236
|
}
|
|
233
|
-
const raw = await spawnWithAccounts(binary, prompt, model, timeout, maxOutputBytes, env, ccsAccounts, addDir, mcpConfigPath, jsonSchemaStr);
|
|
237
|
+
const raw = await spawnWithAccounts(binary, prompt, model, timeout, maxOutputBytes, env, ccsAccounts, addDir, mcpConfigPath, jsonSchemaStr, tools, allowedTools);
|
|
234
238
|
if (!schema) {
|
|
235
239
|
return { text: raw.text, parsed: raw.parsed, envelope: raw.envelope, fixAttempts: 0 };
|
|
236
240
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/runners/claude-code.ts"],"sourcesContent":["export { runClaudeCode } from \"./runners/claude-code\";\nexport type { ClaudeCodeInput, ClaudeCodeResult, ClaudeEnvelope } from \"./runners/claude-code\";\n","import { spawn } from \"node:child_process\";\nimport { homedir } from \"node:os\";\nimport { z } from \"zod\";\n\nexport interface ClaudeCodeInput<T = unknown> {\n prompt: string;\n model?: string;\n /** Timeout in milliseconds. Default: 300_000 (5 min) */\n timeout?: number;\n /**\n * Maximum allowed stdout size in bytes. Default: 10_485_760 (10 MB).\n * If claude output exceeds this limit the process is killed and the promise rejects.\n */\n maxOutputBytes?: number;\n /**\n * Environment variables passed to the subprocess.\n * Defaults to the full process.env so that the binary can access its auth\n * tokens and config (ANTHROPIC_API_KEY, HOME, etc.).\n * Override if you need to restrict or augment the environment.\n */\n env?: NodeJS.ProcessEnv;\n /**\n * Binary to execute. Default: \"ccs\" (Claude Code Profile & Model Switcher).\n * Use \"claude\" to invoke the Claude CLI directly without profile switching.\n * Using `--print` (not `-p`) avoids the ccs delegation system while\n * preserving the standard `--output-format json` envelope output.\n */\n binary?: string;\n /**\n * Optional Zod schema (requires Zod v4+). When provided:\n * - Its JSON Schema representation is passed via `--json-schema` CLI flag\n * (CLI-level enforcement — Claude physically cannot return non-JSON).\n * - The response is validated with `schema.parse()`.\n * - On failure, an auto-fix retry is sent to `fixModel`.\n * - `result.parsed` is typed as `T` (the schema's inferred type).\n * - Mutually exclusive with `jsonSchema`.\n */\n schema?: z.ZodType<T>;\n /**\n * Model used for auto-fix retries. Default: \"claude-haiku-4-5-20251001\"\n * (cheap and fast, good for JSON correction tasks).\n */\n fixModel?: string;\n /**\n * Maximum number of auto-fix attempts on schema validation failure. Default: 1.\n */\n maxFixes?: number;\n /**\n * Raw JSON Schema for CLI-level structured output enforcement.\n * Adds `--json-schema <schema>` to spawn args.\n * Claude returns structured_output in the envelope — guaranteed JSON.\n * No runtime validation or auto-fix. `result.parsed` is `unknown`.\n * Mutually exclusive with `schema`.\n */\n jsonSchema?: Record<string, unknown>;\n /**\n * List of CCS account names to rotate through on `is_error: true` responses.\n * Each account maps to `CLAUDE_CONFIG_DIR=~/.ccs/instances/<name>`.\n * On `is_error`, the next account in the list is tried automatically.\n * Non-is_error failures (timeouts, non-zero exit, etc.) are thrown immediately.\n * If all accounts return `is_error`, throws with the last error message.\n */\n ccsAccounts?: string[];\n /**\n * Path to an MCP config JSON file.\n * When provided, adds `--mcp-config <path>` to the spawn args so the\n * Claude CLI can load MCP servers (filesystem, supabase, etc.).\n * The caller is responsible for writing the file before calling\n * `runClaudeCode` and deleting it afterwards.\n * If the file does not exist or is not accessible, the Claude CLI\n * will exit with a non-zero code and the promise will reject.\n */\n mcpConfigPath?: string;\n /**\n * Absolute path to the repository directory Claude should work in.\n * Required — without it Claude would inherit the worker CWD, exposing\n * worker source files, env vars, and credentials to the model.\n * Adds `--add-dir <path>` and sets `cwd: addDir` on the spawned process\n * so relative paths (e.g. Read(\"src/foo.ts\")) resolve inside the repo.\n * Throws if empty. If the directory does not exist, the Claude CLI will\n * exit with a non-zero code and the promise will reject.\n */\n addDir: string;\n}\n\n/**\n * The JSON envelope that `claude --output-format json` wraps every response in.\n * Only the fields we care about are typed; the rest are captured in the index signature.\n */\nexport interface ClaudeEnvelope {\n type: string;\n subtype: string;\n /** The actual text response from the model (plain text / raw JSON string) */\n result: string;\n is_error: boolean;\n /**\n * Present when `--json-schema` flag is used.\n * Contains the structured output object — use this instead of `result`.\n */\n structured_output?: unknown;\n [key: string]: unknown;\n}\n\nexport interface ClaudeCodeResult<T = unknown> {\n /** The actual text content of claude's response (extracted from envelope.result) */\n text: string;\n /**\n * Parsed value. Typed as `T` when a schema is provided and validation passes.\n * Without schema: `unknown` (will be `null` when the response is not valid JSON).\n */\n parsed: T;\n /** The full response envelope from `claude --output-format json` */\n envelope: ClaudeEnvelope;\n /** Number of auto-fix retries used. 0 means the first response was valid. */\n fixAttempts: number;\n}\n\n/**\n * Thrown when schema validation fails after all auto-fix attempts are exhausted.\n */\nexport class SchemaValidationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"SchemaValidationError\";\n }\n}\n\nconst DEFAULT_MAX_OUTPUT_BYTES = 10 * 1024 * 1024; // 10 MB\n\n/**\n * Kills a process gracefully: SIGTERM first, SIGKILL after 5 seconds if still alive.\n */\nfunction killGracefully(proc: ReturnType<typeof spawn>): void {\n proc.kill(\"SIGTERM\");\n setTimeout(() => {\n proc.kill(\"SIGKILL\");\n }, 5000).unref();\n}\n\n/** Internal raw result from a single spawn cycle (before schema validation). */\ninterface RawResult {\n text: string;\n parsed: unknown | null;\n envelope: ClaudeEnvelope;\n}\n\n/**\n * Spawns the binary once and collects the JSON envelope result.\n * All validation/retry logic lives in `runClaudeCode`.\n */\nfunction spawnOnce(\n binary: string,\n prompt: string,\n model: string,\n timeout: number,\n maxOutputBytes: number,\n env: NodeJS.ProcessEnv,\n addDir: string,\n mcpConfigPath?: string,\n jsonSchemaStr?: string\n): Promise<RawResult> {\n // Use --print (not -p) so that ccs passes args through to claude without\n // triggering its delegation system. Prompt is the final positional argument.\n // Read,Grep,Glob is always enforced: runClaudeCode is read-only by design.\n const args = [\"--print\", \"--output-format\", \"json\", \"--model\", model, \"--tools\", \"Read,Grep,Glob\"];\n if (mcpConfigPath) args.push(\"--mcp-config\", mcpConfigPath);\n args.push(\"--add-dir\", addDir);\n if (jsonSchemaStr) {\n args.push(\"--json-schema\", jsonSchemaStr);\n }\n // Prompt is delivered via stdin — avoids OS ARG_MAX limits (~2MB on Linux)\n\n return new Promise((resolve, reject) => {\n const proc = spawn(binary, args, { env, cwd: addDir });\n\n if (!proc.stdout || !proc.stderr || !proc.stdin) {\n reject(new Error(`Failed to spawn ${binary}: stdio streams not available`));\n return;\n }\n\n // Suppress all stdin errors — EPIPE is expected when the process exits before reading\n // all of stdin; other stdin errors are extremely rare and non-fatal (process already spawned).\n proc.stdin.on(\"error\", () => {});\n // If the internal buffer is full (write returns false), wait for drain before closing.\n // This handles extremely large prompts where the OS pipe buffer fills up.\n if (proc.stdin.write(prompt, \"utf8\")) {\n proc.stdin.end();\n } else {\n proc.stdin.once(\"drain\", () => proc.stdin!.end());\n }\n\n // settled prevents double-resolve/reject between timeout and close handlers\n let settled = false;\n\n const stdoutChunks: Buffer[] = [];\n const stderrChunks: Buffer[] = [];\n let totalStdoutSize = 0;\n\n const timer = setTimeout(() => {\n settled = true;\n killGracefully(proc);\n reject(new Error(`${binary} process timed out after ${timeout}ms`));\n }, timeout);\n\n proc.stdout.on(\"data\", (chunk: Buffer) => {\n totalStdoutSize += chunk.length;\n if (totalStdoutSize > maxOutputBytes) {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n killGracefully(proc);\n reject(new Error(`${binary} output exceeded ${maxOutputBytes} bytes`));\n return;\n }\n stdoutChunks.push(chunk);\n });\n\n proc.stderr.on(\"data\", (chunk: Buffer) => {\n stderrChunks.push(chunk);\n });\n\n proc.on(\"close\", (code) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n\n const stdout = Buffer.concat(stdoutChunks).toString();\n const stderr = Buffer.concat(stderrChunks).toString();\n\n if (code !== 0) {\n reject(\n new Error(\n `${binary} exited with code ${code}${stderr ? `\\nstderr: ${stderr}` : \"\"}`\n )\n );\n return;\n }\n\n let envelope: ClaudeEnvelope;\n try {\n envelope = JSON.parse(stdout.trim()) as ClaudeEnvelope;\n } catch {\n reject(new Error(`${binary} output is not valid JSON: ${stdout.trim().slice(0, 200)}`));\n return;\n }\n\n if (envelope.is_error) {\n reject(new IsErrorResponse(envelope.result));\n return;\n }\n\n // --json-schema mode: result comes in structured_output, not result field\n if (envelope.structured_output !== undefined) {\n const text = JSON.stringify(envelope.structured_output);\n resolve({ text, parsed: envelope.structured_output, envelope });\n return;\n }\n\n if (typeof envelope.result !== \"string\") {\n reject(new Error(\"Invalid envelope: result field is not a string\"));\n return;\n }\n\n const text = envelope.result;\n\n let parsed: unknown | null = null;\n try {\n parsed = JSON.parse(text);\n } catch {\n // text is not JSON — that's fine\n }\n\n resolve({ text, parsed, envelope });\n });\n\n proc.on(\"error\", (err) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n reject(new Error(`Failed to spawn ${binary}: ${err.message}`));\n });\n });\n}\n\n/**\n * Thrown internally by spawnOnce when the Claude envelope contains is_error=true.\n * Caught by spawnWithAccounts to distinguish account-level errors from other failures.\n */\nclass IsErrorResponse extends Error {\n constructor(public readonly result: string) {\n super(`returned is_error=true: ${result}`);\n this.name = \"IsErrorResponse\";\n }\n}\n\n/**\n * Calls spawnOnce with optional CCS account rotation.\n * When `accounts` is provided, tries each account in order by setting\n * CLAUDE_CONFIG_DIR=~/.ccs/instances/<name>. Rotates only on is_error=true;\n * any other error (timeout, non-zero exit, ENOENT) is thrown immediately.\n * If all accounts return is_error, throws with the last error message.\n *\n * Implemented as a regular (non-async) function so the no-rotation path\n * returns spawnOnce's promise directly without adding an extra microtask tick.\n */\nfunction spawnWithAccounts(\n binary: string,\n prompt: string,\n model: string,\n timeout: number,\n maxOutputBytes: number,\n env: NodeJS.ProcessEnv,\n accounts: string[] | undefined,\n addDir: string,\n mcpConfigPath?: string,\n jsonSchemaStr?: string\n): Promise<RawResult> {\n // Fast path: return promise directly — no extra microtask overhead\n if (!accounts || accounts.length === 0) {\n return spawnOnce(binary, prompt, model, timeout, maxOutputBytes, env, addDir, mcpConfigPath, jsonSchemaStr);\n }\n\n // Rotation path: async IIFE to iterate through accounts\n return (async () => {\n let lastIsError: IsErrorResponse | undefined;\n for (const account of accounts) {\n const accountEnv: NodeJS.ProcessEnv = {\n ...env,\n CLAUDE_CONFIG_DIR: `${homedir()}/.ccs/instances/${account}`,\n };\n try {\n return await spawnOnce(binary, prompt, model, timeout, maxOutputBytes, accountEnv, addDir, mcpConfigPath, jsonSchemaStr);\n } catch (err) {\n if (err instanceof IsErrorResponse) {\n lastIsError = err;\n continue;\n }\n throw err;\n }\n }\n throw new Error(lastIsError!.result);\n })();\n}\n\n/**\n * Builds the auto-fix prompt sent to the fixModel when schema validation fails.\n * Includes structured ZodError issues (when available) and the original invalid\n * response so the model can self-correct.\n */\nfunction buildFixPrompt(invalidText: string, error: unknown): string {\n let errorMessage: string;\n if (error instanceof z.ZodError) {\n errorMessage = error.issues\n .map((i) => `Path: ${i.path.length ? i.path.join(\".\") : \"(root)\"}, ${i.message}`)\n .join(\"\\n\");\n } else if (error instanceof Error) {\n errorMessage = error.message;\n } else {\n errorMessage = String(error);\n }\n return (\n `The following JSON failed schema validation.\\n\\n` +\n `Errors:\\n${errorMessage}\\n\\n` +\n `Invalid response:\\n${invalidText}\\n\\n` +\n `Output ONLY corrected JSON. No explanation, no markdown.`\n );\n}\n\n/**\n * Runs the configured binary (default: ccs) with --print flag as a subprocess.\n * Command: `<binary> --print --output-format json --model <model> [--json-schema <schema>]`\n * The prompt is written to the process stdin to avoid OS ARG_MAX limits (~2MB on Linux).\n *\n * When a `schema` is provided (Zod v4+):\n * - Its JSON Schema is passed via `--json-schema` CLI flag (hard enforcement).\n * - Claude returns structured_output in the envelope — guaranteed JSON.\n * - The response is validated; on failure an auto-fix retry is sent to `fixModel`.\n * - `result.fixAttempts` indicates how many retries were used (0 = first response valid).\n * - `result.parsed` is typed as `T` (the schema's inferred type).\n *\n * When `jsonSchema` is provided:\n * - Passed via `--json-schema` CLI flag (hard enforcement, no validation or retry).\n * - `result.parsed` is `unknown`.\n *\n * Without schema: `result.parsed` is `unknown` (may be `null` for plain-text responses).\n *\n * Designed to be used as a plain async function inside a trigger.dev task `run()`.\n */\nexport function runClaudeCode(input: ClaudeCodeInput): Promise<ClaudeCodeResult<unknown>>;\nexport function runClaudeCode<T>(\n input: ClaudeCodeInput<T> & { schema: z.ZodType<T> }\n): Promise<ClaudeCodeResult<T>>;\nexport async function runClaudeCode<T = unknown>(\n input: ClaudeCodeInput<T>\n): Promise<ClaudeCodeResult<T | null>> {\n const {\n prompt,\n model = \"claude-sonnet-4-6\",\n timeout = 300_000,\n maxOutputBytes = DEFAULT_MAX_OUTPUT_BYTES,\n env = process.env,\n binary = \"ccs\",\n schema,\n fixModel = \"claude-haiku-4-5-20251001\",\n maxFixes = 1,\n jsonSchema,\n ccsAccounts,\n mcpConfigPath,\n addDir,\n } = input;\n\n if (!prompt || prompt.trim().length === 0) {\n throw new Error(\"prompt cannot be empty\");\n }\n\n if (!addDir || addDir.trim().length === 0) {\n throw new Error(\"addDir is required: omitting it exposes the worker CWD to the model\");\n }\n\n if (!binary || binary.trim().length === 0) {\n throw new Error(\"binary cannot be empty\");\n }\n\n if (jsonSchema && schema) {\n throw new Error(\"cannot use jsonSchema with schema\");\n }\n\n // Build jsonSchemaStr from Zod schema or raw jsonSchema — passed as CLI flag, not prompt text\n let jsonSchemaStr: string | undefined;\n if (schema) {\n try {\n jsonSchemaStr = JSON.stringify(z.toJSONSchema(schema));\n } catch (err) {\n throw new Error(\n `Failed to convert Zod schema to JSON Schema (requires Zod v4+): ${String(err)}`\n );\n }\n } else if (jsonSchema) {\n try {\n jsonSchemaStr = JSON.stringify(jsonSchema);\n } catch (err) {\n throw new Error(`Failed to serialize jsonSchema to JSON: ${String(err)}`);\n }\n }\n\n const raw = await spawnWithAccounts(binary, prompt, model, timeout, maxOutputBytes, env, ccsAccounts, addDir, mcpConfigPath, jsonSchemaStr);\n\n // No schema — return raw result. parsed may be null for plain-text responses.\n // Cast is safe: overload 1 constrains T = unknown, so T | null = unknown.\n if (!schema) {\n return { text: raw.text, parsed: raw.parsed as T | null, envelope: raw.envelope, fixAttempts: 0 };\n }\n\n // Try to validate the initial response.\n // Note: if raw.parsed is null (plain-text response), schema.parse(null) throws ZodError,\n // which correctly triggers the auto-fix flow.\n let lastText = raw.text;\n let lastError: unknown;\n try {\n const parsed = schema.parse(raw.parsed);\n return { ...raw, parsed, fixAttempts: 0 };\n } catch (err) {\n lastError = err;\n }\n\n // Auto-fix loop: send errors + original output back to fixModel for self-correction.\n // jsonSchemaStr is passed so the fix spawn also uses --json-schema enforcement.\n for (let attempt = 1; attempt <= maxFixes; attempt++) {\n const fixPromptStr = buildFixPrompt(lastText, lastError);\n const fixRaw = await spawnWithAccounts(binary, fixPromptStr, fixModel, timeout, maxOutputBytes, env, ccsAccounts, addDir, mcpConfigPath, jsonSchemaStr);\n try {\n const parsed = schema.parse(fixRaw.parsed);\n return { ...fixRaw, parsed, fixAttempts: attempt };\n } catch (err) {\n lastText = fixRaw.text;\n lastError = err;\n }\n }\n\n const errorMessage = lastError instanceof Error ? lastError.message : String(lastError);\n throw new SchemaValidationError(\n `SchemaValidationError: validation failed after ${maxFixes} fix attempt(s): ${errorMessage}`\n );\n}\n"],"mappings":"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","names":["text"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/runners/claude-code.ts"],"sourcesContent":["export { runClaudeCode } from \"./runners/claude-code\";\nexport type { ClaudeCodeInput, ClaudeCodeResult, ClaudeEnvelope } from \"./runners/claude-code\";\n","import { spawn } from \"node:child_process\";\nimport { homedir } from \"node:os\";\nimport { z } from \"zod\";\n\nexport interface ClaudeCodeInput<T = unknown> {\n prompt: string;\n model?: string;\n /** Timeout in milliseconds. Default: 300_000 (5 min) */\n timeout?: number;\n /**\n * Maximum allowed stdout size in bytes. Default: 10_485_760 (10 MB).\n * If claude output exceeds this limit the process is killed and the promise rejects.\n */\n maxOutputBytes?: number;\n /**\n * Environment variables passed to the subprocess.\n * Defaults to the full process.env so that the binary can access its auth\n * tokens and config (ANTHROPIC_API_KEY, HOME, etc.).\n * Override if you need to restrict or augment the environment.\n */\n env?: NodeJS.ProcessEnv;\n /**\n * Binary to execute. Default: \"ccs\" (Claude Code Profile & Model Switcher).\n * Use \"claude\" to invoke the Claude CLI directly without profile switching.\n * Using `--print` (not `-p`) avoids the ccs delegation system while\n * preserving the standard `--output-format json` envelope output.\n */\n binary?: string;\n /**\n * Optional Zod schema (requires Zod v4+). When provided:\n * - Its JSON Schema representation is passed via `--json-schema` CLI flag\n * (CLI-level enforcement — Claude physically cannot return non-JSON).\n * - The response is validated with `schema.parse()`.\n * - On failure, an auto-fix retry is sent to `fixModel`.\n * - `result.parsed` is typed as `T` (the schema's inferred type).\n * - Mutually exclusive with `jsonSchema`.\n */\n schema?: z.ZodType<T>;\n /**\n * Model used for auto-fix retries. Default: \"claude-haiku-4-5-20251001\"\n * (cheap and fast, good for JSON correction tasks).\n */\n fixModel?: string;\n /**\n * Maximum number of auto-fix attempts on schema validation failure. Default: 1.\n */\n maxFixes?: number;\n /**\n * Raw JSON Schema for CLI-level structured output enforcement.\n * Adds `--json-schema <schema>` to spawn args.\n * Claude returns structured_output in the envelope — guaranteed JSON.\n * No runtime validation or auto-fix. `result.parsed` is `unknown`.\n * Mutually exclusive with `schema`.\n */\n jsonSchema?: Record<string, unknown>;\n /**\n * Override the default tool list passed via `--tools`.\n * Default: `[\"Read\", \"Grep\", \"Glob\"]` (read-only by design).\n * Pass `[\"Bash\", \"Read\", \"Grep\", \"Glob\"]` to also allow Bash execution.\n */\n tools?: string[];\n /**\n * Restrict Bash commands via `--allowed-tools` flag.\n * Each entry is a pattern like `\"Bash(git log*)\"`.\n * Only meaningful when `tools` includes `\"Bash\"`.\n * When omitted, no `--allowed-tools` flag is added.\n */\n allowedTools?: string[];\n /**\n * List of CCS account names to rotate through on `is_error: true` responses.\n * Each account maps to `CLAUDE_CONFIG_DIR=~/.ccs/instances/<name>`.\n * On `is_error`, the next account in the list is tried automatically.\n * Non-is_error failures (timeouts, non-zero exit, etc.) are thrown immediately.\n * If all accounts return `is_error`, throws with the last error message.\n */\n ccsAccounts?: string[];\n /**\n * Path to an MCP config JSON file.\n * When provided, adds `--mcp-config <path>` to the spawn args so the\n * Claude CLI can load MCP servers (filesystem, supabase, etc.).\n * The caller is responsible for writing the file before calling\n * `runClaudeCode` and deleting it afterwards.\n * If the file does not exist or is not accessible, the Claude CLI\n * will exit with a non-zero code and the promise will reject.\n */\n mcpConfigPath?: string;\n /**\n * Absolute path to the repository directory Claude should work in.\n * Required — without it Claude would inherit the worker CWD, exposing\n * worker source files, env vars, and credentials to the model.\n * Adds `--add-dir <path>` and sets `cwd: addDir` on the spawned process\n * so relative paths (e.g. Read(\"src/foo.ts\")) resolve inside the repo.\n * Throws if empty. If the directory does not exist, the Claude CLI will\n * exit with a non-zero code and the promise will reject.\n */\n addDir: string;\n}\n\n/**\n * The JSON envelope that `claude --output-format json` wraps every response in.\n * Only the fields we care about are typed; the rest are captured in the index signature.\n */\nexport interface ClaudeEnvelope {\n type: string;\n subtype: string;\n /** The actual text response from the model (plain text / raw JSON string) */\n result: string;\n is_error: boolean;\n /**\n * Present when `--json-schema` flag is used.\n * Contains the structured output object — use this instead of `result`.\n */\n structured_output?: unknown;\n [key: string]: unknown;\n}\n\nexport interface ClaudeCodeResult<T = unknown> {\n /** The actual text content of claude's response (extracted from envelope.result) */\n text: string;\n /**\n * Parsed value. Typed as `T` when a schema is provided and validation passes.\n * Without schema: `unknown` (will be `null` when the response is not valid JSON).\n */\n parsed: T;\n /** The full response envelope from `claude --output-format json` */\n envelope: ClaudeEnvelope;\n /** Number of auto-fix retries used. 0 means the first response was valid. */\n fixAttempts: number;\n}\n\n/**\n * Thrown when schema validation fails after all auto-fix attempts are exhausted.\n */\nexport class SchemaValidationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"SchemaValidationError\";\n }\n}\n\nconst DEFAULT_MAX_OUTPUT_BYTES = 10 * 1024 * 1024; // 10 MB\n\n/**\n * Kills a process gracefully: SIGTERM first, SIGKILL after 5 seconds if still alive.\n */\nfunction killGracefully(proc: ReturnType<typeof spawn>): void {\n proc.kill(\"SIGTERM\");\n setTimeout(() => {\n proc.kill(\"SIGKILL\");\n }, 5000).unref();\n}\n\n/** Internal raw result from a single spawn cycle (before schema validation). */\ninterface RawResult {\n text: string;\n parsed: unknown | null;\n envelope: ClaudeEnvelope;\n}\n\n/**\n * Spawns the binary once and collects the JSON envelope result.\n * All validation/retry logic lives in `runClaudeCode`.\n */\nfunction spawnOnce(\n binary: string,\n prompt: string,\n model: string,\n timeout: number,\n maxOutputBytes: number,\n env: NodeJS.ProcessEnv,\n addDir: string,\n mcpConfigPath?: string,\n jsonSchemaStr?: string,\n tools?: string[],\n allowedTools?: string[]\n): Promise<RawResult> {\n // Use --print (not -p) so that ccs passes args through to claude without\n // triggering its delegation system. Prompt is the final positional argument.\n const toolsList = tools?.length ? tools.join(\",\") : \"Read,Grep,Glob\";\n const args = [\"--print\", \"--output-format\", \"json\", \"--model\", model, \"--tools\", toolsList];\n if (allowedTools?.length) args.push(\"--allowed-tools\", ...allowedTools);\n if (mcpConfigPath) args.push(\"--mcp-config\", mcpConfigPath);\n args.push(\"--add-dir\", addDir);\n if (jsonSchemaStr) {\n args.push(\"--json-schema\", jsonSchemaStr);\n }\n // Prompt is delivered via stdin — avoids OS ARG_MAX limits (~2MB on Linux)\n\n return new Promise((resolve, reject) => {\n const proc = spawn(binary, args, { env, cwd: addDir });\n\n if (!proc.stdout || !proc.stderr || !proc.stdin) {\n reject(new Error(`Failed to spawn ${binary}: stdio streams not available`));\n return;\n }\n\n // Suppress all stdin errors — EPIPE is expected when the process exits before reading\n // all of stdin; other stdin errors are extremely rare and non-fatal (process already spawned).\n proc.stdin.on(\"error\", () => {});\n // If the internal buffer is full (write returns false), wait for drain before closing.\n // This handles extremely large prompts where the OS pipe buffer fills up.\n if (proc.stdin.write(prompt, \"utf8\")) {\n proc.stdin.end();\n } else {\n proc.stdin.once(\"drain\", () => proc.stdin!.end());\n }\n\n // settled prevents double-resolve/reject between timeout and close handlers\n let settled = false;\n\n const stdoutChunks: Buffer[] = [];\n const stderrChunks: Buffer[] = [];\n let totalStdoutSize = 0;\n\n const timer = setTimeout(() => {\n settled = true;\n killGracefully(proc);\n reject(new Error(`${binary} process timed out after ${timeout}ms`));\n }, timeout);\n\n proc.stdout.on(\"data\", (chunk: Buffer) => {\n totalStdoutSize += chunk.length;\n if (totalStdoutSize > maxOutputBytes) {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n killGracefully(proc);\n reject(new Error(`${binary} output exceeded ${maxOutputBytes} bytes`));\n return;\n }\n stdoutChunks.push(chunk);\n });\n\n proc.stderr.on(\"data\", (chunk: Buffer) => {\n stderrChunks.push(chunk);\n });\n\n proc.on(\"close\", (code) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n\n const stdout = Buffer.concat(stdoutChunks).toString();\n const stderr = Buffer.concat(stderrChunks).toString();\n\n if (code !== 0) {\n reject(\n new Error(\n `${binary} exited with code ${code}${stderr ? `\\nstderr: ${stderr}` : \"\"}`\n )\n );\n return;\n }\n\n let envelope: ClaudeEnvelope;\n try {\n envelope = JSON.parse(stdout.trim()) as ClaudeEnvelope;\n } catch {\n reject(new Error(`${binary} output is not valid JSON: ${stdout.trim().slice(0, 200)}`));\n return;\n }\n\n if (envelope.is_error) {\n reject(new IsErrorResponse(envelope.result));\n return;\n }\n\n // --json-schema mode: result comes in structured_output, not result field\n if (envelope.structured_output !== undefined) {\n const text = JSON.stringify(envelope.structured_output);\n resolve({ text, parsed: envelope.structured_output, envelope });\n return;\n }\n\n if (typeof envelope.result !== \"string\") {\n reject(new Error(\"Invalid envelope: result field is not a string\"));\n return;\n }\n\n const text = envelope.result;\n\n let parsed: unknown | null = null;\n try {\n parsed = JSON.parse(text);\n } catch {\n // text is not JSON — that's fine\n }\n\n resolve({ text, parsed, envelope });\n });\n\n proc.on(\"error\", (err) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n reject(new Error(`Failed to spawn ${binary}: ${err.message}`));\n });\n });\n}\n\n/**\n * Thrown internally by spawnOnce when the Claude envelope contains is_error=true.\n * Caught by spawnWithAccounts to distinguish account-level errors from other failures.\n */\nclass IsErrorResponse extends Error {\n constructor(public readonly result: string) {\n super(`returned is_error=true: ${result}`);\n this.name = \"IsErrorResponse\";\n }\n}\n\n/**\n * Calls spawnOnce with optional CCS account rotation.\n * When `accounts` is provided, tries each account in order by setting\n * CLAUDE_CONFIG_DIR=~/.ccs/instances/<name>. Rotates only on is_error=true;\n * any other error (timeout, non-zero exit, ENOENT) is thrown immediately.\n * If all accounts return is_error, throws with the last error message.\n *\n * Implemented as a regular (non-async) function so the no-rotation path\n * returns spawnOnce's promise directly without adding an extra microtask tick.\n */\nfunction spawnWithAccounts(\n binary: string,\n prompt: string,\n model: string,\n timeout: number,\n maxOutputBytes: number,\n env: NodeJS.ProcessEnv,\n accounts: string[] | undefined,\n addDir: string,\n mcpConfigPath?: string,\n jsonSchemaStr?: string,\n tools?: string[],\n allowedTools?: string[]\n): Promise<RawResult> {\n // Fast path: return promise directly — no extra microtask overhead\n if (!accounts || accounts.length === 0) {\n return spawnOnce(binary, prompt, model, timeout, maxOutputBytes, env, addDir, mcpConfigPath, jsonSchemaStr, tools, allowedTools);\n }\n\n // Rotation path: async IIFE to iterate through accounts\n return (async () => {\n let lastIsError: IsErrorResponse | undefined;\n for (const account of accounts) {\n const accountEnv: NodeJS.ProcessEnv = {\n ...env,\n CLAUDE_CONFIG_DIR: `${homedir()}/.ccs/instances/${account}`,\n };\n try {\n return await spawnOnce(binary, prompt, model, timeout, maxOutputBytes, accountEnv, addDir, mcpConfigPath, jsonSchemaStr, tools, allowedTools);\n } catch (err) {\n if (err instanceof IsErrorResponse) {\n lastIsError = err;\n continue;\n }\n throw err;\n }\n }\n throw new Error(lastIsError!.result);\n })();\n}\n\n/**\n * Builds the auto-fix prompt sent to the fixModel when schema validation fails.\n * Includes structured ZodError issues (when available) and the original invalid\n * response so the model can self-correct.\n */\nfunction buildFixPrompt(invalidText: string, error: unknown): string {\n let errorMessage: string;\n if (error instanceof z.ZodError) {\n errorMessage = error.issues\n .map((i) => `Path: ${i.path.length ? i.path.join(\".\") : \"(root)\"}, ${i.message}`)\n .join(\"\\n\");\n } else if (error instanceof Error) {\n errorMessage = error.message;\n } else {\n errorMessage = String(error);\n }\n return (\n `The following JSON failed schema validation.\\n\\n` +\n `Errors:\\n${errorMessage}\\n\\n` +\n `Invalid response:\\n${invalidText}\\n\\n` +\n `Output ONLY corrected JSON. No explanation, no markdown.`\n );\n}\n\n/**\n * Runs the configured binary (default: ccs) with --print flag as a subprocess.\n * Command: `<binary> --print --output-format json --model <model> [--json-schema <schema>]`\n * The prompt is written to the process stdin to avoid OS ARG_MAX limits (~2MB on Linux).\n *\n * When a `schema` is provided (Zod v4+):\n * - Its JSON Schema is passed via `--json-schema` CLI flag (hard enforcement).\n * - Claude returns structured_output in the envelope — guaranteed JSON.\n * - The response is validated; on failure an auto-fix retry is sent to `fixModel`.\n * - `result.fixAttempts` indicates how many retries were used (0 = first response valid).\n * - `result.parsed` is typed as `T` (the schema's inferred type).\n *\n * When `jsonSchema` is provided:\n * - Passed via `--json-schema` CLI flag (hard enforcement, no validation or retry).\n * - `result.parsed` is `unknown`.\n *\n * Without schema: `result.parsed` is `unknown` (may be `null` for plain-text responses).\n *\n * Designed to be used as a plain async function inside a trigger.dev task `run()`.\n */\nexport function runClaudeCode(input: ClaudeCodeInput): Promise<ClaudeCodeResult<unknown>>;\nexport function runClaudeCode<T>(\n input: ClaudeCodeInput<T> & { schema: z.ZodType<T> }\n): Promise<ClaudeCodeResult<T>>;\nexport async function runClaudeCode<T = unknown>(\n input: ClaudeCodeInput<T>\n): Promise<ClaudeCodeResult<T | null>> {\n const {\n prompt,\n model = \"claude-sonnet-4-6\",\n timeout = 300_000,\n maxOutputBytes = DEFAULT_MAX_OUTPUT_BYTES,\n env = process.env,\n binary = \"ccs\",\n schema,\n fixModel = \"claude-haiku-4-5-20251001\",\n maxFixes = 1,\n jsonSchema,\n ccsAccounts,\n mcpConfigPath,\n addDir,\n tools,\n allowedTools,\n } = input;\n\n if (!prompt || prompt.trim().length === 0) {\n throw new Error(\"prompt cannot be empty\");\n }\n\n if (!addDir || addDir.trim().length === 0) {\n throw new Error(\"addDir is required: omitting it exposes the worker CWD to the model\");\n }\n\n if (!binary || binary.trim().length === 0) {\n throw new Error(\"binary cannot be empty\");\n }\n\n if (jsonSchema && schema) {\n throw new Error(\"cannot use jsonSchema with schema\");\n }\n\n // Build jsonSchemaStr from Zod schema or raw jsonSchema — passed as CLI flag, not prompt text\n let jsonSchemaStr: string | undefined;\n if (schema) {\n try {\n jsonSchemaStr = JSON.stringify(z.toJSONSchema(schema));\n } catch (err) {\n throw new Error(\n `Failed to convert Zod schema to JSON Schema (requires Zod v4+): ${String(err)}`\n );\n }\n } else if (jsonSchema) {\n try {\n jsonSchemaStr = JSON.stringify(jsonSchema);\n } catch (err) {\n throw new Error(`Failed to serialize jsonSchema to JSON: ${String(err)}`);\n }\n }\n\n const raw = await spawnWithAccounts(binary, prompt, model, timeout, maxOutputBytes, env, ccsAccounts, addDir, mcpConfigPath, jsonSchemaStr, tools, allowedTools);\n\n // No schema — return raw result. parsed may be null for plain-text responses.\n // Cast is safe: overload 1 constrains T = unknown, so T | null = unknown.\n if (!schema) {\n return { text: raw.text, parsed: raw.parsed as T | null, envelope: raw.envelope, fixAttempts: 0 };\n }\n\n // Try to validate the initial response.\n // Note: if raw.parsed is null (plain-text response), schema.parse(null) throws ZodError,\n // which correctly triggers the auto-fix flow.\n let lastText = raw.text;\n let lastError: unknown;\n try {\n const parsed = schema.parse(raw.parsed);\n return { ...raw, parsed, fixAttempts: 0 };\n } catch (err) {\n lastError = err;\n }\n\n // Auto-fix loop: send errors + original output back to fixModel for self-correction.\n // jsonSchemaStr is passed so the fix spawn also uses --json-schema enforcement.\n for (let attempt = 1; attempt <= maxFixes; attempt++) {\n const fixPromptStr = buildFixPrompt(lastText, lastError);\n const fixRaw = await spawnWithAccounts(binary, fixPromptStr, fixModel, timeout, maxOutputBytes, env, ccsAccounts, addDir, mcpConfigPath, jsonSchemaStr);\n try {\n const parsed = schema.parse(fixRaw.parsed);\n return { ...fixRaw, parsed, fixAttempts: attempt };\n } catch (err) {\n lastText = fixRaw.text;\n lastError = err;\n }\n }\n\n const errorMessage = lastError instanceof Error ? lastError.message : String(lastError);\n throw new SchemaValidationError(\n `SchemaValidationError: validation failed after ${maxFixes} fix attempt(s): ${errorMessage}`\n );\n}\n"],"mappings":"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","names":["text"]}
|
package/dist/index.mjs
CHANGED
|
@@ -15,8 +15,10 @@ function killGracefully(proc) {
|
|
|
15
15
|
proc.kill("SIGKILL");
|
|
16
16
|
}, 5e3).unref();
|
|
17
17
|
}
|
|
18
|
-
function spawnOnce(binary, prompt, model, timeout, maxOutputBytes, env, addDir, mcpConfigPath, jsonSchemaStr) {
|
|
19
|
-
const
|
|
18
|
+
function spawnOnce(binary, prompt, model, timeout, maxOutputBytes, env, addDir, mcpConfigPath, jsonSchemaStr, tools, allowedTools) {
|
|
19
|
+
const toolsList = tools?.length ? tools.join(",") : "Read,Grep,Glob";
|
|
20
|
+
const args = ["--print", "--output-format", "json", "--model", model, "--tools", toolsList];
|
|
21
|
+
if (allowedTools?.length) args.push("--allowed-tools", ...allowedTools);
|
|
20
22
|
if (mcpConfigPath) args.push("--mcp-config", mcpConfigPath);
|
|
21
23
|
args.push("--add-dir", addDir);
|
|
22
24
|
if (jsonSchemaStr) {
|
|
@@ -117,9 +119,9 @@ var IsErrorResponse = class extends Error {
|
|
|
117
119
|
this.name = "IsErrorResponse";
|
|
118
120
|
}
|
|
119
121
|
};
|
|
120
|
-
function spawnWithAccounts(binary, prompt, model, timeout, maxOutputBytes, env, accounts, addDir, mcpConfigPath, jsonSchemaStr) {
|
|
122
|
+
function spawnWithAccounts(binary, prompt, model, timeout, maxOutputBytes, env, accounts, addDir, mcpConfigPath, jsonSchemaStr, tools, allowedTools) {
|
|
121
123
|
if (!accounts || accounts.length === 0) {
|
|
122
|
-
return spawnOnce(binary, prompt, model, timeout, maxOutputBytes, env, addDir, mcpConfigPath, jsonSchemaStr);
|
|
124
|
+
return spawnOnce(binary, prompt, model, timeout, maxOutputBytes, env, addDir, mcpConfigPath, jsonSchemaStr, tools, allowedTools);
|
|
123
125
|
}
|
|
124
126
|
return (async () => {
|
|
125
127
|
let lastIsError;
|
|
@@ -129,7 +131,7 @@ function spawnWithAccounts(binary, prompt, model, timeout, maxOutputBytes, env,
|
|
|
129
131
|
CLAUDE_CONFIG_DIR: `${homedir()}/.ccs/instances/${account}`
|
|
130
132
|
};
|
|
131
133
|
try {
|
|
132
|
-
return await spawnOnce(binary, prompt, model, timeout, maxOutputBytes, accountEnv, addDir, mcpConfigPath, jsonSchemaStr);
|
|
134
|
+
return await spawnOnce(binary, prompt, model, timeout, maxOutputBytes, accountEnv, addDir, mcpConfigPath, jsonSchemaStr, tools, allowedTools);
|
|
133
135
|
} catch (err) {
|
|
134
136
|
if (err instanceof IsErrorResponse) {
|
|
135
137
|
lastIsError = err;
|
|
@@ -174,7 +176,9 @@ async function runClaudeCode(input) {
|
|
|
174
176
|
jsonSchema,
|
|
175
177
|
ccsAccounts,
|
|
176
178
|
mcpConfigPath,
|
|
177
|
-
addDir
|
|
179
|
+
addDir,
|
|
180
|
+
tools,
|
|
181
|
+
allowedTools
|
|
178
182
|
} = input;
|
|
179
183
|
if (!prompt || prompt.trim().length === 0) {
|
|
180
184
|
throw new Error("prompt cannot be empty");
|
|
@@ -204,7 +208,7 @@ async function runClaudeCode(input) {
|
|
|
204
208
|
throw new Error(`Failed to serialize jsonSchema to JSON: ${String(err)}`);
|
|
205
209
|
}
|
|
206
210
|
}
|
|
207
|
-
const raw = await spawnWithAccounts(binary, prompt, model, timeout, maxOutputBytes, env, ccsAccounts, addDir, mcpConfigPath, jsonSchemaStr);
|
|
211
|
+
const raw = await spawnWithAccounts(binary, prompt, model, timeout, maxOutputBytes, env, ccsAccounts, addDir, mcpConfigPath, jsonSchemaStr, tools, allowedTools);
|
|
208
212
|
if (!schema) {
|
|
209
213
|
return { text: raw.text, parsed: raw.parsed, envelope: raw.envelope, fixAttempts: 0 };
|
|
210
214
|
}
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/runners/claude-code.ts"],"sourcesContent":["import { spawn } from \"node:child_process\";\nimport { homedir } from \"node:os\";\nimport { z } from \"zod\";\n\nexport interface ClaudeCodeInput<T = unknown> {\n prompt: string;\n model?: string;\n /** Timeout in milliseconds. Default: 300_000 (5 min) */\n timeout?: number;\n /**\n * Maximum allowed stdout size in bytes. Default: 10_485_760 (10 MB).\n * If claude output exceeds this limit the process is killed and the promise rejects.\n */\n maxOutputBytes?: number;\n /**\n * Environment variables passed to the subprocess.\n * Defaults to the full process.env so that the binary can access its auth\n * tokens and config (ANTHROPIC_API_KEY, HOME, etc.).\n * Override if you need to restrict or augment the environment.\n */\n env?: NodeJS.ProcessEnv;\n /**\n * Binary to execute. Default: \"ccs\" (Claude Code Profile & Model Switcher).\n * Use \"claude\" to invoke the Claude CLI directly without profile switching.\n * Using `--print` (not `-p`) avoids the ccs delegation system while\n * preserving the standard `--output-format json` envelope output.\n */\n binary?: string;\n /**\n * Optional Zod schema (requires Zod v4+). When provided:\n * - Its JSON Schema representation is passed via `--json-schema` CLI flag\n * (CLI-level enforcement — Claude physically cannot return non-JSON).\n * - The response is validated with `schema.parse()`.\n * - On failure, an auto-fix retry is sent to `fixModel`.\n * - `result.parsed` is typed as `T` (the schema's inferred type).\n * - Mutually exclusive with `jsonSchema`.\n */\n schema?: z.ZodType<T>;\n /**\n * Model used for auto-fix retries. Default: \"claude-haiku-4-5-20251001\"\n * (cheap and fast, good for JSON correction tasks).\n */\n fixModel?: string;\n /**\n * Maximum number of auto-fix attempts on schema validation failure. Default: 1.\n */\n maxFixes?: number;\n /**\n * Raw JSON Schema for CLI-level structured output enforcement.\n * Adds `--json-schema <schema>` to spawn args.\n * Claude returns structured_output in the envelope — guaranteed JSON.\n * No runtime validation or auto-fix. `result.parsed` is `unknown`.\n * Mutually exclusive with `schema`.\n */\n jsonSchema?: Record<string, unknown>;\n /**\n * List of CCS account names to rotate through on `is_error: true` responses.\n * Each account maps to `CLAUDE_CONFIG_DIR=~/.ccs/instances/<name>`.\n * On `is_error`, the next account in the list is tried automatically.\n * Non-is_error failures (timeouts, non-zero exit, etc.) are thrown immediately.\n * If all accounts return `is_error`, throws with the last error message.\n */\n ccsAccounts?: string[];\n /**\n * Path to an MCP config JSON file.\n * When provided, adds `--mcp-config <path>` to the spawn args so the\n * Claude CLI can load MCP servers (filesystem, supabase, etc.).\n * The caller is responsible for writing the file before calling\n * `runClaudeCode` and deleting it afterwards.\n * If the file does not exist or is not accessible, the Claude CLI\n * will exit with a non-zero code and the promise will reject.\n */\n mcpConfigPath?: string;\n /**\n * Absolute path to the repository directory Claude should work in.\n * Required — without it Claude would inherit the worker CWD, exposing\n * worker source files, env vars, and credentials to the model.\n * Adds `--add-dir <path>` and sets `cwd: addDir` on the spawned process\n * so relative paths (e.g. Read(\"src/foo.ts\")) resolve inside the repo.\n * Throws if empty. If the directory does not exist, the Claude CLI will\n * exit with a non-zero code and the promise will reject.\n */\n addDir: string;\n}\n\n/**\n * The JSON envelope that `claude --output-format json` wraps every response in.\n * Only the fields we care about are typed; the rest are captured in the index signature.\n */\nexport interface ClaudeEnvelope {\n type: string;\n subtype: string;\n /** The actual text response from the model (plain text / raw JSON string) */\n result: string;\n is_error: boolean;\n /**\n * Present when `--json-schema` flag is used.\n * Contains the structured output object — use this instead of `result`.\n */\n structured_output?: unknown;\n [key: string]: unknown;\n}\n\nexport interface ClaudeCodeResult<T = unknown> {\n /** The actual text content of claude's response (extracted from envelope.result) */\n text: string;\n /**\n * Parsed value. Typed as `T` when a schema is provided and validation passes.\n * Without schema: `unknown` (will be `null` when the response is not valid JSON).\n */\n parsed: T;\n /** The full response envelope from `claude --output-format json` */\n envelope: ClaudeEnvelope;\n /** Number of auto-fix retries used. 0 means the first response was valid. */\n fixAttempts: number;\n}\n\n/**\n * Thrown when schema validation fails after all auto-fix attempts are exhausted.\n */\nexport class SchemaValidationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"SchemaValidationError\";\n }\n}\n\nconst DEFAULT_MAX_OUTPUT_BYTES = 10 * 1024 * 1024; // 10 MB\n\n/**\n * Kills a process gracefully: SIGTERM first, SIGKILL after 5 seconds if still alive.\n */\nfunction killGracefully(proc: ReturnType<typeof spawn>): void {\n proc.kill(\"SIGTERM\");\n setTimeout(() => {\n proc.kill(\"SIGKILL\");\n }, 5000).unref();\n}\n\n/** Internal raw result from a single spawn cycle (before schema validation). */\ninterface RawResult {\n text: string;\n parsed: unknown | null;\n envelope: ClaudeEnvelope;\n}\n\n/**\n * Spawns the binary once and collects the JSON envelope result.\n * All validation/retry logic lives in `runClaudeCode`.\n */\nfunction spawnOnce(\n binary: string,\n prompt: string,\n model: string,\n timeout: number,\n maxOutputBytes: number,\n env: NodeJS.ProcessEnv,\n addDir: string,\n mcpConfigPath?: string,\n jsonSchemaStr?: string\n): Promise<RawResult> {\n // Use --print (not -p) so that ccs passes args through to claude without\n // triggering its delegation system. Prompt is the final positional argument.\n // Read,Grep,Glob is always enforced: runClaudeCode is read-only by design.\n const args = [\"--print\", \"--output-format\", \"json\", \"--model\", model, \"--tools\", \"Read,Grep,Glob\"];\n if (mcpConfigPath) args.push(\"--mcp-config\", mcpConfigPath);\n args.push(\"--add-dir\", addDir);\n if (jsonSchemaStr) {\n args.push(\"--json-schema\", jsonSchemaStr);\n }\n // Prompt is delivered via stdin — avoids OS ARG_MAX limits (~2MB on Linux)\n\n return new Promise((resolve, reject) => {\n const proc = spawn(binary, args, { env, cwd: addDir });\n\n if (!proc.stdout || !proc.stderr || !proc.stdin) {\n reject(new Error(`Failed to spawn ${binary}: stdio streams not available`));\n return;\n }\n\n // Suppress all stdin errors — EPIPE is expected when the process exits before reading\n // all of stdin; other stdin errors are extremely rare and non-fatal (process already spawned).\n proc.stdin.on(\"error\", () => {});\n // If the internal buffer is full (write returns false), wait for drain before closing.\n // This handles extremely large prompts where the OS pipe buffer fills up.\n if (proc.stdin.write(prompt, \"utf8\")) {\n proc.stdin.end();\n } else {\n proc.stdin.once(\"drain\", () => proc.stdin!.end());\n }\n\n // settled prevents double-resolve/reject between timeout and close handlers\n let settled = false;\n\n const stdoutChunks: Buffer[] = [];\n const stderrChunks: Buffer[] = [];\n let totalStdoutSize = 0;\n\n const timer = setTimeout(() => {\n settled = true;\n killGracefully(proc);\n reject(new Error(`${binary} process timed out after ${timeout}ms`));\n }, timeout);\n\n proc.stdout.on(\"data\", (chunk: Buffer) => {\n totalStdoutSize += chunk.length;\n if (totalStdoutSize > maxOutputBytes) {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n killGracefully(proc);\n reject(new Error(`${binary} output exceeded ${maxOutputBytes} bytes`));\n return;\n }\n stdoutChunks.push(chunk);\n });\n\n proc.stderr.on(\"data\", (chunk: Buffer) => {\n stderrChunks.push(chunk);\n });\n\n proc.on(\"close\", (code) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n\n const stdout = Buffer.concat(stdoutChunks).toString();\n const stderr = Buffer.concat(stderrChunks).toString();\n\n if (code !== 0) {\n reject(\n new Error(\n `${binary} exited with code ${code}${stderr ? `\\nstderr: ${stderr}` : \"\"}`\n )\n );\n return;\n }\n\n let envelope: ClaudeEnvelope;\n try {\n envelope = JSON.parse(stdout.trim()) as ClaudeEnvelope;\n } catch {\n reject(new Error(`${binary} output is not valid JSON: ${stdout.trim().slice(0, 200)}`));\n return;\n }\n\n if (envelope.is_error) {\n reject(new IsErrorResponse(envelope.result));\n return;\n }\n\n // --json-schema mode: result comes in structured_output, not result field\n if (envelope.structured_output !== undefined) {\n const text = JSON.stringify(envelope.structured_output);\n resolve({ text, parsed: envelope.structured_output, envelope });\n return;\n }\n\n if (typeof envelope.result !== \"string\") {\n reject(new Error(\"Invalid envelope: result field is not a string\"));\n return;\n }\n\n const text = envelope.result;\n\n let parsed: unknown | null = null;\n try {\n parsed = JSON.parse(text);\n } catch {\n // text is not JSON — that's fine\n }\n\n resolve({ text, parsed, envelope });\n });\n\n proc.on(\"error\", (err) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n reject(new Error(`Failed to spawn ${binary}: ${err.message}`));\n });\n });\n}\n\n/**\n * Thrown internally by spawnOnce when the Claude envelope contains is_error=true.\n * Caught by spawnWithAccounts to distinguish account-level errors from other failures.\n */\nclass IsErrorResponse extends Error {\n constructor(public readonly result: string) {\n super(`returned is_error=true: ${result}`);\n this.name = \"IsErrorResponse\";\n }\n}\n\n/**\n * Calls spawnOnce with optional CCS account rotation.\n * When `accounts` is provided, tries each account in order by setting\n * CLAUDE_CONFIG_DIR=~/.ccs/instances/<name>. Rotates only on is_error=true;\n * any other error (timeout, non-zero exit, ENOENT) is thrown immediately.\n * If all accounts return is_error, throws with the last error message.\n *\n * Implemented as a regular (non-async) function so the no-rotation path\n * returns spawnOnce's promise directly without adding an extra microtask tick.\n */\nfunction spawnWithAccounts(\n binary: string,\n prompt: string,\n model: string,\n timeout: number,\n maxOutputBytes: number,\n env: NodeJS.ProcessEnv,\n accounts: string[] | undefined,\n addDir: string,\n mcpConfigPath?: string,\n jsonSchemaStr?: string\n): Promise<RawResult> {\n // Fast path: return promise directly — no extra microtask overhead\n if (!accounts || accounts.length === 0) {\n return spawnOnce(binary, prompt, model, timeout, maxOutputBytes, env, addDir, mcpConfigPath, jsonSchemaStr);\n }\n\n // Rotation path: async IIFE to iterate through accounts\n return (async () => {\n let lastIsError: IsErrorResponse | undefined;\n for (const account of accounts) {\n const accountEnv: NodeJS.ProcessEnv = {\n ...env,\n CLAUDE_CONFIG_DIR: `${homedir()}/.ccs/instances/${account}`,\n };\n try {\n return await spawnOnce(binary, prompt, model, timeout, maxOutputBytes, accountEnv, addDir, mcpConfigPath, jsonSchemaStr);\n } catch (err) {\n if (err instanceof IsErrorResponse) {\n lastIsError = err;\n continue;\n }\n throw err;\n }\n }\n throw new Error(lastIsError!.result);\n })();\n}\n\n/**\n * Builds the auto-fix prompt sent to the fixModel when schema validation fails.\n * Includes structured ZodError issues (when available) and the original invalid\n * response so the model can self-correct.\n */\nfunction buildFixPrompt(invalidText: string, error: unknown): string {\n let errorMessage: string;\n if (error instanceof z.ZodError) {\n errorMessage = error.issues\n .map((i) => `Path: ${i.path.length ? i.path.join(\".\") : \"(root)\"}, ${i.message}`)\n .join(\"\\n\");\n } else if (error instanceof Error) {\n errorMessage = error.message;\n } else {\n errorMessage = String(error);\n }\n return (\n `The following JSON failed schema validation.\\n\\n` +\n `Errors:\\n${errorMessage}\\n\\n` +\n `Invalid response:\\n${invalidText}\\n\\n` +\n `Output ONLY corrected JSON. No explanation, no markdown.`\n );\n}\n\n/**\n * Runs the configured binary (default: ccs) with --print flag as a subprocess.\n * Command: `<binary> --print --output-format json --model <model> [--json-schema <schema>]`\n * The prompt is written to the process stdin to avoid OS ARG_MAX limits (~2MB on Linux).\n *\n * When a `schema` is provided (Zod v4+):\n * - Its JSON Schema is passed via `--json-schema` CLI flag (hard enforcement).\n * - Claude returns structured_output in the envelope — guaranteed JSON.\n * - The response is validated; on failure an auto-fix retry is sent to `fixModel`.\n * - `result.fixAttempts` indicates how many retries were used (0 = first response valid).\n * - `result.parsed` is typed as `T` (the schema's inferred type).\n *\n * When `jsonSchema` is provided:\n * - Passed via `--json-schema` CLI flag (hard enforcement, no validation or retry).\n * - `result.parsed` is `unknown`.\n *\n * Without schema: `result.parsed` is `unknown` (may be `null` for plain-text responses).\n *\n * Designed to be used as a plain async function inside a trigger.dev task `run()`.\n */\nexport function runClaudeCode(input: ClaudeCodeInput): Promise<ClaudeCodeResult<unknown>>;\nexport function runClaudeCode<T>(\n input: ClaudeCodeInput<T> & { schema: z.ZodType<T> }\n): Promise<ClaudeCodeResult<T>>;\nexport async function runClaudeCode<T = unknown>(\n input: ClaudeCodeInput<T>\n): Promise<ClaudeCodeResult<T | null>> {\n const {\n prompt,\n model = \"claude-sonnet-4-6\",\n timeout = 300_000,\n maxOutputBytes = DEFAULT_MAX_OUTPUT_BYTES,\n env = process.env,\n binary = \"ccs\",\n schema,\n fixModel = \"claude-haiku-4-5-20251001\",\n maxFixes = 1,\n jsonSchema,\n ccsAccounts,\n mcpConfigPath,\n addDir,\n } = input;\n\n if (!prompt || prompt.trim().length === 0) {\n throw new Error(\"prompt cannot be empty\");\n }\n\n if (!addDir || addDir.trim().length === 0) {\n throw new Error(\"addDir is required: omitting it exposes the worker CWD to the model\");\n }\n\n if (!binary || binary.trim().length === 0) {\n throw new Error(\"binary cannot be empty\");\n }\n\n if (jsonSchema && schema) {\n throw new Error(\"cannot use jsonSchema with schema\");\n }\n\n // Build jsonSchemaStr from Zod schema or raw jsonSchema — passed as CLI flag, not prompt text\n let jsonSchemaStr: string | undefined;\n if (schema) {\n try {\n jsonSchemaStr = JSON.stringify(z.toJSONSchema(schema));\n } catch (err) {\n throw new Error(\n `Failed to convert Zod schema to JSON Schema (requires Zod v4+): ${String(err)}`\n );\n }\n } else if (jsonSchema) {\n try {\n jsonSchemaStr = JSON.stringify(jsonSchema);\n } catch (err) {\n throw new Error(`Failed to serialize jsonSchema to JSON: ${String(err)}`);\n }\n }\n\n const raw = await spawnWithAccounts(binary, prompt, model, timeout, maxOutputBytes, env, ccsAccounts, addDir, mcpConfigPath, jsonSchemaStr);\n\n // No schema — return raw result. parsed may be null for plain-text responses.\n // Cast is safe: overload 1 constrains T = unknown, so T | null = unknown.\n if (!schema) {\n return { text: raw.text, parsed: raw.parsed as T | null, envelope: raw.envelope, fixAttempts: 0 };\n }\n\n // Try to validate the initial response.\n // Note: if raw.parsed is null (plain-text response), schema.parse(null) throws ZodError,\n // which correctly triggers the auto-fix flow.\n let lastText = raw.text;\n let lastError: unknown;\n try {\n const parsed = schema.parse(raw.parsed);\n return { ...raw, parsed, fixAttempts: 0 };\n } catch (err) {\n lastError = err;\n }\n\n // Auto-fix loop: send errors + original output back to fixModel for self-correction.\n // jsonSchemaStr is passed so the fix spawn also uses --json-schema enforcement.\n for (let attempt = 1; attempt <= maxFixes; attempt++) {\n const fixPromptStr = buildFixPrompt(lastText, lastError);\n const fixRaw = await spawnWithAccounts(binary, fixPromptStr, fixModel, timeout, maxOutputBytes, env, ccsAccounts, addDir, mcpConfigPath, jsonSchemaStr);\n try {\n const parsed = schema.parse(fixRaw.parsed);\n return { ...fixRaw, parsed, fixAttempts: attempt };\n } catch (err) {\n lastText = fixRaw.text;\n lastError = err;\n }\n }\n\n const errorMessage = lastError instanceof Error ? lastError.message : String(lastError);\n throw new SchemaValidationError(\n `SchemaValidationError: validation failed after ${maxFixes} fix attempt(s): ${errorMessage}`\n );\n}\n"],"mappings":"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","names":["text"]}
|
|
1
|
+
{"version":3,"sources":["../src/runners/claude-code.ts"],"sourcesContent":["import { spawn } from \"node:child_process\";\nimport { homedir } from \"node:os\";\nimport { z } from \"zod\";\n\nexport interface ClaudeCodeInput<T = unknown> {\n prompt: string;\n model?: string;\n /** Timeout in milliseconds. Default: 300_000 (5 min) */\n timeout?: number;\n /**\n * Maximum allowed stdout size in bytes. Default: 10_485_760 (10 MB).\n * If claude output exceeds this limit the process is killed and the promise rejects.\n */\n maxOutputBytes?: number;\n /**\n * Environment variables passed to the subprocess.\n * Defaults to the full process.env so that the binary can access its auth\n * tokens and config (ANTHROPIC_API_KEY, HOME, etc.).\n * Override if you need to restrict or augment the environment.\n */\n env?: NodeJS.ProcessEnv;\n /**\n * Binary to execute. Default: \"ccs\" (Claude Code Profile & Model Switcher).\n * Use \"claude\" to invoke the Claude CLI directly without profile switching.\n * Using `--print` (not `-p`) avoids the ccs delegation system while\n * preserving the standard `--output-format json` envelope output.\n */\n binary?: string;\n /**\n * Optional Zod schema (requires Zod v4+). When provided:\n * - Its JSON Schema representation is passed via `--json-schema` CLI flag\n * (CLI-level enforcement — Claude physically cannot return non-JSON).\n * - The response is validated with `schema.parse()`.\n * - On failure, an auto-fix retry is sent to `fixModel`.\n * - `result.parsed` is typed as `T` (the schema's inferred type).\n * - Mutually exclusive with `jsonSchema`.\n */\n schema?: z.ZodType<T>;\n /**\n * Model used for auto-fix retries. Default: \"claude-haiku-4-5-20251001\"\n * (cheap and fast, good for JSON correction tasks).\n */\n fixModel?: string;\n /**\n * Maximum number of auto-fix attempts on schema validation failure. Default: 1.\n */\n maxFixes?: number;\n /**\n * Raw JSON Schema for CLI-level structured output enforcement.\n * Adds `--json-schema <schema>` to spawn args.\n * Claude returns structured_output in the envelope — guaranteed JSON.\n * No runtime validation or auto-fix. `result.parsed` is `unknown`.\n * Mutually exclusive with `schema`.\n */\n jsonSchema?: Record<string, unknown>;\n /**\n * Override the default tool list passed via `--tools`.\n * Default: `[\"Read\", \"Grep\", \"Glob\"]` (read-only by design).\n * Pass `[\"Bash\", \"Read\", \"Grep\", \"Glob\"]` to also allow Bash execution.\n */\n tools?: string[];\n /**\n * Restrict Bash commands via `--allowed-tools` flag.\n * Each entry is a pattern like `\"Bash(git log*)\"`.\n * Only meaningful when `tools` includes `\"Bash\"`.\n * When omitted, no `--allowed-tools` flag is added.\n */\n allowedTools?: string[];\n /**\n * List of CCS account names to rotate through on `is_error: true` responses.\n * Each account maps to `CLAUDE_CONFIG_DIR=~/.ccs/instances/<name>`.\n * On `is_error`, the next account in the list is tried automatically.\n * Non-is_error failures (timeouts, non-zero exit, etc.) are thrown immediately.\n * If all accounts return `is_error`, throws with the last error message.\n */\n ccsAccounts?: string[];\n /**\n * Path to an MCP config JSON file.\n * When provided, adds `--mcp-config <path>` to the spawn args so the\n * Claude CLI can load MCP servers (filesystem, supabase, etc.).\n * The caller is responsible for writing the file before calling\n * `runClaudeCode` and deleting it afterwards.\n * If the file does not exist or is not accessible, the Claude CLI\n * will exit with a non-zero code and the promise will reject.\n */\n mcpConfigPath?: string;\n /**\n * Absolute path to the repository directory Claude should work in.\n * Required — without it Claude would inherit the worker CWD, exposing\n * worker source files, env vars, and credentials to the model.\n * Adds `--add-dir <path>` and sets `cwd: addDir` on the spawned process\n * so relative paths (e.g. Read(\"src/foo.ts\")) resolve inside the repo.\n * Throws if empty. If the directory does not exist, the Claude CLI will\n * exit with a non-zero code and the promise will reject.\n */\n addDir: string;\n}\n\n/**\n * The JSON envelope that `claude --output-format json` wraps every response in.\n * Only the fields we care about are typed; the rest are captured in the index signature.\n */\nexport interface ClaudeEnvelope {\n type: string;\n subtype: string;\n /** The actual text response from the model (plain text / raw JSON string) */\n result: string;\n is_error: boolean;\n /**\n * Present when `--json-schema` flag is used.\n * Contains the structured output object — use this instead of `result`.\n */\n structured_output?: unknown;\n [key: string]: unknown;\n}\n\nexport interface ClaudeCodeResult<T = unknown> {\n /** The actual text content of claude's response (extracted from envelope.result) */\n text: string;\n /**\n * Parsed value. Typed as `T` when a schema is provided and validation passes.\n * Without schema: `unknown` (will be `null` when the response is not valid JSON).\n */\n parsed: T;\n /** The full response envelope from `claude --output-format json` */\n envelope: ClaudeEnvelope;\n /** Number of auto-fix retries used. 0 means the first response was valid. */\n fixAttempts: number;\n}\n\n/**\n * Thrown when schema validation fails after all auto-fix attempts are exhausted.\n */\nexport class SchemaValidationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"SchemaValidationError\";\n }\n}\n\nconst DEFAULT_MAX_OUTPUT_BYTES = 10 * 1024 * 1024; // 10 MB\n\n/**\n * Kills a process gracefully: SIGTERM first, SIGKILL after 5 seconds if still alive.\n */\nfunction killGracefully(proc: ReturnType<typeof spawn>): void {\n proc.kill(\"SIGTERM\");\n setTimeout(() => {\n proc.kill(\"SIGKILL\");\n }, 5000).unref();\n}\n\n/** Internal raw result from a single spawn cycle (before schema validation). */\ninterface RawResult {\n text: string;\n parsed: unknown | null;\n envelope: ClaudeEnvelope;\n}\n\n/**\n * Spawns the binary once and collects the JSON envelope result.\n * All validation/retry logic lives in `runClaudeCode`.\n */\nfunction spawnOnce(\n binary: string,\n prompt: string,\n model: string,\n timeout: number,\n maxOutputBytes: number,\n env: NodeJS.ProcessEnv,\n addDir: string,\n mcpConfigPath?: string,\n jsonSchemaStr?: string,\n tools?: string[],\n allowedTools?: string[]\n): Promise<RawResult> {\n // Use --print (not -p) so that ccs passes args through to claude without\n // triggering its delegation system. Prompt is the final positional argument.\n const toolsList = tools?.length ? tools.join(\",\") : \"Read,Grep,Glob\";\n const args = [\"--print\", \"--output-format\", \"json\", \"--model\", model, \"--tools\", toolsList];\n if (allowedTools?.length) args.push(\"--allowed-tools\", ...allowedTools);\n if (mcpConfigPath) args.push(\"--mcp-config\", mcpConfigPath);\n args.push(\"--add-dir\", addDir);\n if (jsonSchemaStr) {\n args.push(\"--json-schema\", jsonSchemaStr);\n }\n // Prompt is delivered via stdin — avoids OS ARG_MAX limits (~2MB on Linux)\n\n return new Promise((resolve, reject) => {\n const proc = spawn(binary, args, { env, cwd: addDir });\n\n if (!proc.stdout || !proc.stderr || !proc.stdin) {\n reject(new Error(`Failed to spawn ${binary}: stdio streams not available`));\n return;\n }\n\n // Suppress all stdin errors — EPIPE is expected when the process exits before reading\n // all of stdin; other stdin errors are extremely rare and non-fatal (process already spawned).\n proc.stdin.on(\"error\", () => {});\n // If the internal buffer is full (write returns false), wait for drain before closing.\n // This handles extremely large prompts where the OS pipe buffer fills up.\n if (proc.stdin.write(prompt, \"utf8\")) {\n proc.stdin.end();\n } else {\n proc.stdin.once(\"drain\", () => proc.stdin!.end());\n }\n\n // settled prevents double-resolve/reject between timeout and close handlers\n let settled = false;\n\n const stdoutChunks: Buffer[] = [];\n const stderrChunks: Buffer[] = [];\n let totalStdoutSize = 0;\n\n const timer = setTimeout(() => {\n settled = true;\n killGracefully(proc);\n reject(new Error(`${binary} process timed out after ${timeout}ms`));\n }, timeout);\n\n proc.stdout.on(\"data\", (chunk: Buffer) => {\n totalStdoutSize += chunk.length;\n if (totalStdoutSize > maxOutputBytes) {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n killGracefully(proc);\n reject(new Error(`${binary} output exceeded ${maxOutputBytes} bytes`));\n return;\n }\n stdoutChunks.push(chunk);\n });\n\n proc.stderr.on(\"data\", (chunk: Buffer) => {\n stderrChunks.push(chunk);\n });\n\n proc.on(\"close\", (code) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n\n const stdout = Buffer.concat(stdoutChunks).toString();\n const stderr = Buffer.concat(stderrChunks).toString();\n\n if (code !== 0) {\n reject(\n new Error(\n `${binary} exited with code ${code}${stderr ? `\\nstderr: ${stderr}` : \"\"}`\n )\n );\n return;\n }\n\n let envelope: ClaudeEnvelope;\n try {\n envelope = JSON.parse(stdout.trim()) as ClaudeEnvelope;\n } catch {\n reject(new Error(`${binary} output is not valid JSON: ${stdout.trim().slice(0, 200)}`));\n return;\n }\n\n if (envelope.is_error) {\n reject(new IsErrorResponse(envelope.result));\n return;\n }\n\n // --json-schema mode: result comes in structured_output, not result field\n if (envelope.structured_output !== undefined) {\n const text = JSON.stringify(envelope.structured_output);\n resolve({ text, parsed: envelope.structured_output, envelope });\n return;\n }\n\n if (typeof envelope.result !== \"string\") {\n reject(new Error(\"Invalid envelope: result field is not a string\"));\n return;\n }\n\n const text = envelope.result;\n\n let parsed: unknown | null = null;\n try {\n parsed = JSON.parse(text);\n } catch {\n // text is not JSON — that's fine\n }\n\n resolve({ text, parsed, envelope });\n });\n\n proc.on(\"error\", (err) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n reject(new Error(`Failed to spawn ${binary}: ${err.message}`));\n });\n });\n}\n\n/**\n * Thrown internally by spawnOnce when the Claude envelope contains is_error=true.\n * Caught by spawnWithAccounts to distinguish account-level errors from other failures.\n */\nclass IsErrorResponse extends Error {\n constructor(public readonly result: string) {\n super(`returned is_error=true: ${result}`);\n this.name = \"IsErrorResponse\";\n }\n}\n\n/**\n * Calls spawnOnce with optional CCS account rotation.\n * When `accounts` is provided, tries each account in order by setting\n * CLAUDE_CONFIG_DIR=~/.ccs/instances/<name>. Rotates only on is_error=true;\n * any other error (timeout, non-zero exit, ENOENT) is thrown immediately.\n * If all accounts return is_error, throws with the last error message.\n *\n * Implemented as a regular (non-async) function so the no-rotation path\n * returns spawnOnce's promise directly without adding an extra microtask tick.\n */\nfunction spawnWithAccounts(\n binary: string,\n prompt: string,\n model: string,\n timeout: number,\n maxOutputBytes: number,\n env: NodeJS.ProcessEnv,\n accounts: string[] | undefined,\n addDir: string,\n mcpConfigPath?: string,\n jsonSchemaStr?: string,\n tools?: string[],\n allowedTools?: string[]\n): Promise<RawResult> {\n // Fast path: return promise directly — no extra microtask overhead\n if (!accounts || accounts.length === 0) {\n return spawnOnce(binary, prompt, model, timeout, maxOutputBytes, env, addDir, mcpConfigPath, jsonSchemaStr, tools, allowedTools);\n }\n\n // Rotation path: async IIFE to iterate through accounts\n return (async () => {\n let lastIsError: IsErrorResponse | undefined;\n for (const account of accounts) {\n const accountEnv: NodeJS.ProcessEnv = {\n ...env,\n CLAUDE_CONFIG_DIR: `${homedir()}/.ccs/instances/${account}`,\n };\n try {\n return await spawnOnce(binary, prompt, model, timeout, maxOutputBytes, accountEnv, addDir, mcpConfigPath, jsonSchemaStr, tools, allowedTools);\n } catch (err) {\n if (err instanceof IsErrorResponse) {\n lastIsError = err;\n continue;\n }\n throw err;\n }\n }\n throw new Error(lastIsError!.result);\n })();\n}\n\n/**\n * Builds the auto-fix prompt sent to the fixModel when schema validation fails.\n * Includes structured ZodError issues (when available) and the original invalid\n * response so the model can self-correct.\n */\nfunction buildFixPrompt(invalidText: string, error: unknown): string {\n let errorMessage: string;\n if (error instanceof z.ZodError) {\n errorMessage = error.issues\n .map((i) => `Path: ${i.path.length ? i.path.join(\".\") : \"(root)\"}, ${i.message}`)\n .join(\"\\n\");\n } else if (error instanceof Error) {\n errorMessage = error.message;\n } else {\n errorMessage = String(error);\n }\n return (\n `The following JSON failed schema validation.\\n\\n` +\n `Errors:\\n${errorMessage}\\n\\n` +\n `Invalid response:\\n${invalidText}\\n\\n` +\n `Output ONLY corrected JSON. No explanation, no markdown.`\n );\n}\n\n/**\n * Runs the configured binary (default: ccs) with --print flag as a subprocess.\n * Command: `<binary> --print --output-format json --model <model> [--json-schema <schema>]`\n * The prompt is written to the process stdin to avoid OS ARG_MAX limits (~2MB on Linux).\n *\n * When a `schema` is provided (Zod v4+):\n * - Its JSON Schema is passed via `--json-schema` CLI flag (hard enforcement).\n * - Claude returns structured_output in the envelope — guaranteed JSON.\n * - The response is validated; on failure an auto-fix retry is sent to `fixModel`.\n * - `result.fixAttempts` indicates how many retries were used (0 = first response valid).\n * - `result.parsed` is typed as `T` (the schema's inferred type).\n *\n * When `jsonSchema` is provided:\n * - Passed via `--json-schema` CLI flag (hard enforcement, no validation or retry).\n * - `result.parsed` is `unknown`.\n *\n * Without schema: `result.parsed` is `unknown` (may be `null` for plain-text responses).\n *\n * Designed to be used as a plain async function inside a trigger.dev task `run()`.\n */\nexport function runClaudeCode(input: ClaudeCodeInput): Promise<ClaudeCodeResult<unknown>>;\nexport function runClaudeCode<T>(\n input: ClaudeCodeInput<T> & { schema: z.ZodType<T> }\n): Promise<ClaudeCodeResult<T>>;\nexport async function runClaudeCode<T = unknown>(\n input: ClaudeCodeInput<T>\n): Promise<ClaudeCodeResult<T | null>> {\n const {\n prompt,\n model = \"claude-sonnet-4-6\",\n timeout = 300_000,\n maxOutputBytes = DEFAULT_MAX_OUTPUT_BYTES,\n env = process.env,\n binary = \"ccs\",\n schema,\n fixModel = \"claude-haiku-4-5-20251001\",\n maxFixes = 1,\n jsonSchema,\n ccsAccounts,\n mcpConfigPath,\n addDir,\n tools,\n allowedTools,\n } = input;\n\n if (!prompt || prompt.trim().length === 0) {\n throw new Error(\"prompt cannot be empty\");\n }\n\n if (!addDir || addDir.trim().length === 0) {\n throw new Error(\"addDir is required: omitting it exposes the worker CWD to the model\");\n }\n\n if (!binary || binary.trim().length === 0) {\n throw new Error(\"binary cannot be empty\");\n }\n\n if (jsonSchema && schema) {\n throw new Error(\"cannot use jsonSchema with schema\");\n }\n\n // Build jsonSchemaStr from Zod schema or raw jsonSchema — passed as CLI flag, not prompt text\n let jsonSchemaStr: string | undefined;\n if (schema) {\n try {\n jsonSchemaStr = JSON.stringify(z.toJSONSchema(schema));\n } catch (err) {\n throw new Error(\n `Failed to convert Zod schema to JSON Schema (requires Zod v4+): ${String(err)}`\n );\n }\n } else if (jsonSchema) {\n try {\n jsonSchemaStr = JSON.stringify(jsonSchema);\n } catch (err) {\n throw new Error(`Failed to serialize jsonSchema to JSON: ${String(err)}`);\n }\n }\n\n const raw = await spawnWithAccounts(binary, prompt, model, timeout, maxOutputBytes, env, ccsAccounts, addDir, mcpConfigPath, jsonSchemaStr, tools, allowedTools);\n\n // No schema — return raw result. parsed may be null for plain-text responses.\n // Cast is safe: overload 1 constrains T = unknown, so T | null = unknown.\n if (!schema) {\n return { text: raw.text, parsed: raw.parsed as T | null, envelope: raw.envelope, fixAttempts: 0 };\n }\n\n // Try to validate the initial response.\n // Note: if raw.parsed is null (plain-text response), schema.parse(null) throws ZodError,\n // which correctly triggers the auto-fix flow.\n let lastText = raw.text;\n let lastError: unknown;\n try {\n const parsed = schema.parse(raw.parsed);\n return { ...raw, parsed, fixAttempts: 0 };\n } catch (err) {\n lastError = err;\n }\n\n // Auto-fix loop: send errors + original output back to fixModel for self-correction.\n // jsonSchemaStr is passed so the fix spawn also uses --json-schema enforcement.\n for (let attempt = 1; attempt <= maxFixes; attempt++) {\n const fixPromptStr = buildFixPrompt(lastText, lastError);\n const fixRaw = await spawnWithAccounts(binary, fixPromptStr, fixModel, timeout, maxOutputBytes, env, ccsAccounts, addDir, mcpConfigPath, jsonSchemaStr);\n try {\n const parsed = schema.parse(fixRaw.parsed);\n return { ...fixRaw, parsed, fixAttempts: attempt };\n } catch (err) {\n lastText = fixRaw.text;\n lastError = err;\n }\n }\n\n const errorMessage = lastError instanceof Error ? lastError.message : String(lastError);\n throw new SchemaValidationError(\n `SchemaValidationError: validation failed after ${maxFixes} fix attempt(s): ${errorMessage}`\n );\n}\n"],"mappings":"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","names":["text"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atrvd/trigger",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Shared run-functions for trigger.dev tasks",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "ATRVD",
|
|
@@ -30,6 +30,16 @@
|
|
|
30
30
|
"types": "./dist/index.d.ts"
|
|
31
31
|
}
|
|
32
32
|
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsup",
|
|
35
|
+
"prepare": "tsup",
|
|
36
|
+
"test": "vitest run",
|
|
37
|
+
"test:unit": "vitest run src/**/*.test.ts --exclude src/**/*.{integration,e2e}.test.ts",
|
|
38
|
+
"test:integration": "vitest run src/**/*.integration.test.ts",
|
|
39
|
+
"test:e2e": "CLAUDE_E2E=1 vitest run --testTimeout=120000 src/**/*.e2e.test.ts",
|
|
40
|
+
"test:watch": "vitest",
|
|
41
|
+
"typecheck": "tsc --noEmit"
|
|
42
|
+
},
|
|
33
43
|
"engines": {
|
|
34
44
|
"node": ">=18.0.0"
|
|
35
45
|
},
|
|
@@ -42,14 +52,5 @@
|
|
|
42
52
|
},
|
|
43
53
|
"dependencies": {
|
|
44
54
|
"zod": "^4.3.6"
|
|
45
|
-
},
|
|
46
|
-
"scripts": {
|
|
47
|
-
"build": "tsup",
|
|
48
|
-
"test": "vitest run",
|
|
49
|
-
"test:unit": "vitest run src/**/*.test.ts --exclude src/**/*.{integration,e2e}.test.ts",
|
|
50
|
-
"test:integration": "vitest run src/**/*.integration.test.ts",
|
|
51
|
-
"test:e2e": "CLAUDE_E2E=1 vitest run --testTimeout=120000 src/**/*.e2e.test.ts",
|
|
52
|
-
"test:watch": "vitest",
|
|
53
|
-
"typecheck": "tsc --noEmit"
|
|
54
55
|
}
|
|
55
|
-
}
|
|
56
|
+
}
|