@atrvd/trigger 0.1.4
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 +138 -0
- package/dist/index.d.ts +138 -0
- package/dist/index.js +265 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +238 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +55 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
interface ClaudeCodeInput<T = unknown> {
|
|
4
|
+
prompt: string;
|
|
5
|
+
model?: string;
|
|
6
|
+
/** Timeout in milliseconds. Default: 300_000 (5 min) */
|
|
7
|
+
timeout?: number;
|
|
8
|
+
/**
|
|
9
|
+
* Maximum allowed stdout size in bytes. Default: 10_485_760 (10 MB).
|
|
10
|
+
* If claude output exceeds this limit the process is killed and the promise rejects.
|
|
11
|
+
*/
|
|
12
|
+
maxOutputBytes?: number;
|
|
13
|
+
/**
|
|
14
|
+
* Environment variables passed to the subprocess.
|
|
15
|
+
* Defaults to the full process.env so that the binary can access its auth
|
|
16
|
+
* tokens and config (ANTHROPIC_API_KEY, HOME, etc.).
|
|
17
|
+
* Override if you need to restrict or augment the environment.
|
|
18
|
+
*/
|
|
19
|
+
env?: NodeJS.ProcessEnv;
|
|
20
|
+
/**
|
|
21
|
+
* Binary to execute. Default: "ccs" (Claude Code Profile & Model Switcher).
|
|
22
|
+
* Use "claude" to invoke the Claude CLI directly without profile switching.
|
|
23
|
+
* Using `--print` (not `-p`) avoids the ccs delegation system while
|
|
24
|
+
* preserving the standard `--output-format json` envelope output.
|
|
25
|
+
*/
|
|
26
|
+
binary?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Optional Zod schema (requires Zod v4+). When provided:
|
|
29
|
+
* - Its JSON Schema representation is passed via `--json-schema` CLI flag
|
|
30
|
+
* (CLI-level enforcement — Claude physically cannot return non-JSON).
|
|
31
|
+
* - The response is validated with `schema.parse()`.
|
|
32
|
+
* - On failure, an auto-fix retry is sent to `fixModel`.
|
|
33
|
+
* - `result.parsed` is typed as `T` (the schema's inferred type).
|
|
34
|
+
* - Mutually exclusive with `jsonSchema`.
|
|
35
|
+
*/
|
|
36
|
+
schema?: z.ZodType<T>;
|
|
37
|
+
/**
|
|
38
|
+
* Model used for auto-fix retries. Default: "claude-haiku-4-5-20251001"
|
|
39
|
+
* (cheap and fast, good for JSON correction tasks).
|
|
40
|
+
*/
|
|
41
|
+
fixModel?: string;
|
|
42
|
+
/**
|
|
43
|
+
* Maximum number of auto-fix attempts on schema validation failure. Default: 1.
|
|
44
|
+
*/
|
|
45
|
+
maxFixes?: number;
|
|
46
|
+
/**
|
|
47
|
+
* Raw JSON Schema for CLI-level structured output enforcement.
|
|
48
|
+
* Adds `--json-schema <schema>` to spawn args.
|
|
49
|
+
* Claude returns structured_output in the envelope — guaranteed JSON.
|
|
50
|
+
* No runtime validation or auto-fix. `result.parsed` is `unknown`.
|
|
51
|
+
* Mutually exclusive with `schema`.
|
|
52
|
+
*/
|
|
53
|
+
jsonSchema?: Record<string, unknown>;
|
|
54
|
+
/**
|
|
55
|
+
* List of CCS account names to rotate through on `is_error: true` responses.
|
|
56
|
+
* Each account maps to `CLAUDE_CONFIG_DIR=~/.ccs/instances/<name>`.
|
|
57
|
+
* On `is_error`, the next account in the list is tried automatically.
|
|
58
|
+
* Non-is_error failures (timeouts, non-zero exit, etc.) are thrown immediately.
|
|
59
|
+
* If all accounts return `is_error`, throws with the last error message.
|
|
60
|
+
*/
|
|
61
|
+
ccsAccounts?: string[];
|
|
62
|
+
/**
|
|
63
|
+
* Path to an MCP config JSON file.
|
|
64
|
+
* When provided, adds `--mcp-config <path>` to the spawn args so the
|
|
65
|
+
* Claude CLI can load MCP servers (filesystem, supabase, etc.).
|
|
66
|
+
* The caller is responsible for writing the file before calling
|
|
67
|
+
* `runClaudeCode` and deleting it afterwards.
|
|
68
|
+
* If the file does not exist or is not accessible, the Claude CLI
|
|
69
|
+
* will exit with a non-zero code and the promise will reject.
|
|
70
|
+
*/
|
|
71
|
+
mcpConfigPath?: string;
|
|
72
|
+
/**
|
|
73
|
+
* Absolute path to the repository directory Claude should work in.
|
|
74
|
+
* Required — without it Claude would inherit the worker CWD, exposing
|
|
75
|
+
* worker source files, env vars, and credentials to the model.
|
|
76
|
+
* Adds `--add-dir <path>` and sets `cwd: addDir` on the spawned process
|
|
77
|
+
* so relative paths (e.g. Read("src/foo.ts")) resolve inside the repo.
|
|
78
|
+
* Throws if empty. If the directory does not exist, the Claude CLI will
|
|
79
|
+
* exit with a non-zero code and the promise will reject.
|
|
80
|
+
*/
|
|
81
|
+
addDir: string;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The JSON envelope that `claude --output-format json` wraps every response in.
|
|
85
|
+
* Only the fields we care about are typed; the rest are captured in the index signature.
|
|
86
|
+
*/
|
|
87
|
+
interface ClaudeEnvelope {
|
|
88
|
+
type: string;
|
|
89
|
+
subtype: string;
|
|
90
|
+
/** The actual text response from the model (plain text / raw JSON string) */
|
|
91
|
+
result: string;
|
|
92
|
+
is_error: boolean;
|
|
93
|
+
/**
|
|
94
|
+
* Present when `--json-schema` flag is used.
|
|
95
|
+
* Contains the structured output object — use this instead of `result`.
|
|
96
|
+
*/
|
|
97
|
+
structured_output?: unknown;
|
|
98
|
+
[key: string]: unknown;
|
|
99
|
+
}
|
|
100
|
+
interface ClaudeCodeResult<T = unknown> {
|
|
101
|
+
/** The actual text content of claude's response (extracted from envelope.result) */
|
|
102
|
+
text: string;
|
|
103
|
+
/**
|
|
104
|
+
* Parsed value. Typed as `T` when a schema is provided and validation passes.
|
|
105
|
+
* Without schema: `unknown` (will be `null` when the response is not valid JSON).
|
|
106
|
+
*/
|
|
107
|
+
parsed: T;
|
|
108
|
+
/** The full response envelope from `claude --output-format json` */
|
|
109
|
+
envelope: ClaudeEnvelope;
|
|
110
|
+
/** Number of auto-fix retries used. 0 means the first response was valid. */
|
|
111
|
+
fixAttempts: number;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Runs the configured binary (default: ccs) with --print flag as a subprocess.
|
|
115
|
+
* Command: `<binary> --print --output-format json --model <model> [--json-schema <schema>]`
|
|
116
|
+
* The prompt is written to the process stdin to avoid OS ARG_MAX limits (~2MB on Linux).
|
|
117
|
+
*
|
|
118
|
+
* When a `schema` is provided (Zod v4+):
|
|
119
|
+
* - Its JSON Schema is passed via `--json-schema` CLI flag (hard enforcement).
|
|
120
|
+
* - Claude returns structured_output in the envelope — guaranteed JSON.
|
|
121
|
+
* - The response is validated; on failure an auto-fix retry is sent to `fixModel`.
|
|
122
|
+
* - `result.fixAttempts` indicates how many retries were used (0 = first response valid).
|
|
123
|
+
* - `result.parsed` is typed as `T` (the schema's inferred type).
|
|
124
|
+
*
|
|
125
|
+
* When `jsonSchema` is provided:
|
|
126
|
+
* - Passed via `--json-schema` CLI flag (hard enforcement, no validation or retry).
|
|
127
|
+
* - `result.parsed` is `unknown`.
|
|
128
|
+
*
|
|
129
|
+
* Without schema: `result.parsed` is `unknown` (may be `null` for plain-text responses).
|
|
130
|
+
*
|
|
131
|
+
* Designed to be used as a plain async function inside a trigger.dev task `run()`.
|
|
132
|
+
*/
|
|
133
|
+
declare function runClaudeCode(input: ClaudeCodeInput): Promise<ClaudeCodeResult<unknown>>;
|
|
134
|
+
declare function runClaudeCode<T>(input: ClaudeCodeInput<T> & {
|
|
135
|
+
schema: z.ZodType<T>;
|
|
136
|
+
}): Promise<ClaudeCodeResult<T>>;
|
|
137
|
+
|
|
138
|
+
export { type ClaudeCodeInput, type ClaudeCodeResult, type ClaudeEnvelope, runClaudeCode };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
interface ClaudeCodeInput<T = unknown> {
|
|
4
|
+
prompt: string;
|
|
5
|
+
model?: string;
|
|
6
|
+
/** Timeout in milliseconds. Default: 300_000 (5 min) */
|
|
7
|
+
timeout?: number;
|
|
8
|
+
/**
|
|
9
|
+
* Maximum allowed stdout size in bytes. Default: 10_485_760 (10 MB).
|
|
10
|
+
* If claude output exceeds this limit the process is killed and the promise rejects.
|
|
11
|
+
*/
|
|
12
|
+
maxOutputBytes?: number;
|
|
13
|
+
/**
|
|
14
|
+
* Environment variables passed to the subprocess.
|
|
15
|
+
* Defaults to the full process.env so that the binary can access its auth
|
|
16
|
+
* tokens and config (ANTHROPIC_API_KEY, HOME, etc.).
|
|
17
|
+
* Override if you need to restrict or augment the environment.
|
|
18
|
+
*/
|
|
19
|
+
env?: NodeJS.ProcessEnv;
|
|
20
|
+
/**
|
|
21
|
+
* Binary to execute. Default: "ccs" (Claude Code Profile & Model Switcher).
|
|
22
|
+
* Use "claude" to invoke the Claude CLI directly without profile switching.
|
|
23
|
+
* Using `--print` (not `-p`) avoids the ccs delegation system while
|
|
24
|
+
* preserving the standard `--output-format json` envelope output.
|
|
25
|
+
*/
|
|
26
|
+
binary?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Optional Zod schema (requires Zod v4+). When provided:
|
|
29
|
+
* - Its JSON Schema representation is passed via `--json-schema` CLI flag
|
|
30
|
+
* (CLI-level enforcement — Claude physically cannot return non-JSON).
|
|
31
|
+
* - The response is validated with `schema.parse()`.
|
|
32
|
+
* - On failure, an auto-fix retry is sent to `fixModel`.
|
|
33
|
+
* - `result.parsed` is typed as `T` (the schema's inferred type).
|
|
34
|
+
* - Mutually exclusive with `jsonSchema`.
|
|
35
|
+
*/
|
|
36
|
+
schema?: z.ZodType<T>;
|
|
37
|
+
/**
|
|
38
|
+
* Model used for auto-fix retries. Default: "claude-haiku-4-5-20251001"
|
|
39
|
+
* (cheap and fast, good for JSON correction tasks).
|
|
40
|
+
*/
|
|
41
|
+
fixModel?: string;
|
|
42
|
+
/**
|
|
43
|
+
* Maximum number of auto-fix attempts on schema validation failure. Default: 1.
|
|
44
|
+
*/
|
|
45
|
+
maxFixes?: number;
|
|
46
|
+
/**
|
|
47
|
+
* Raw JSON Schema for CLI-level structured output enforcement.
|
|
48
|
+
* Adds `--json-schema <schema>` to spawn args.
|
|
49
|
+
* Claude returns structured_output in the envelope — guaranteed JSON.
|
|
50
|
+
* No runtime validation or auto-fix. `result.parsed` is `unknown`.
|
|
51
|
+
* Mutually exclusive with `schema`.
|
|
52
|
+
*/
|
|
53
|
+
jsonSchema?: Record<string, unknown>;
|
|
54
|
+
/**
|
|
55
|
+
* List of CCS account names to rotate through on `is_error: true` responses.
|
|
56
|
+
* Each account maps to `CLAUDE_CONFIG_DIR=~/.ccs/instances/<name>`.
|
|
57
|
+
* On `is_error`, the next account in the list is tried automatically.
|
|
58
|
+
* Non-is_error failures (timeouts, non-zero exit, etc.) are thrown immediately.
|
|
59
|
+
* If all accounts return `is_error`, throws with the last error message.
|
|
60
|
+
*/
|
|
61
|
+
ccsAccounts?: string[];
|
|
62
|
+
/**
|
|
63
|
+
* Path to an MCP config JSON file.
|
|
64
|
+
* When provided, adds `--mcp-config <path>` to the spawn args so the
|
|
65
|
+
* Claude CLI can load MCP servers (filesystem, supabase, etc.).
|
|
66
|
+
* The caller is responsible for writing the file before calling
|
|
67
|
+
* `runClaudeCode` and deleting it afterwards.
|
|
68
|
+
* If the file does not exist or is not accessible, the Claude CLI
|
|
69
|
+
* will exit with a non-zero code and the promise will reject.
|
|
70
|
+
*/
|
|
71
|
+
mcpConfigPath?: string;
|
|
72
|
+
/**
|
|
73
|
+
* Absolute path to the repository directory Claude should work in.
|
|
74
|
+
* Required — without it Claude would inherit the worker CWD, exposing
|
|
75
|
+
* worker source files, env vars, and credentials to the model.
|
|
76
|
+
* Adds `--add-dir <path>` and sets `cwd: addDir` on the spawned process
|
|
77
|
+
* so relative paths (e.g. Read("src/foo.ts")) resolve inside the repo.
|
|
78
|
+
* Throws if empty. If the directory does not exist, the Claude CLI will
|
|
79
|
+
* exit with a non-zero code and the promise will reject.
|
|
80
|
+
*/
|
|
81
|
+
addDir: string;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The JSON envelope that `claude --output-format json` wraps every response in.
|
|
85
|
+
* Only the fields we care about are typed; the rest are captured in the index signature.
|
|
86
|
+
*/
|
|
87
|
+
interface ClaudeEnvelope {
|
|
88
|
+
type: string;
|
|
89
|
+
subtype: string;
|
|
90
|
+
/** The actual text response from the model (plain text / raw JSON string) */
|
|
91
|
+
result: string;
|
|
92
|
+
is_error: boolean;
|
|
93
|
+
/**
|
|
94
|
+
* Present when `--json-schema` flag is used.
|
|
95
|
+
* Contains the structured output object — use this instead of `result`.
|
|
96
|
+
*/
|
|
97
|
+
structured_output?: unknown;
|
|
98
|
+
[key: string]: unknown;
|
|
99
|
+
}
|
|
100
|
+
interface ClaudeCodeResult<T = unknown> {
|
|
101
|
+
/** The actual text content of claude's response (extracted from envelope.result) */
|
|
102
|
+
text: string;
|
|
103
|
+
/**
|
|
104
|
+
* Parsed value. Typed as `T` when a schema is provided and validation passes.
|
|
105
|
+
* Without schema: `unknown` (will be `null` when the response is not valid JSON).
|
|
106
|
+
*/
|
|
107
|
+
parsed: T;
|
|
108
|
+
/** The full response envelope from `claude --output-format json` */
|
|
109
|
+
envelope: ClaudeEnvelope;
|
|
110
|
+
/** Number of auto-fix retries used. 0 means the first response was valid. */
|
|
111
|
+
fixAttempts: number;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Runs the configured binary (default: ccs) with --print flag as a subprocess.
|
|
115
|
+
* Command: `<binary> --print --output-format json --model <model> [--json-schema <schema>]`
|
|
116
|
+
* The prompt is written to the process stdin to avoid OS ARG_MAX limits (~2MB on Linux).
|
|
117
|
+
*
|
|
118
|
+
* When a `schema` is provided (Zod v4+):
|
|
119
|
+
* - Its JSON Schema is passed via `--json-schema` CLI flag (hard enforcement).
|
|
120
|
+
* - Claude returns structured_output in the envelope — guaranteed JSON.
|
|
121
|
+
* - The response is validated; on failure an auto-fix retry is sent to `fixModel`.
|
|
122
|
+
* - `result.fixAttempts` indicates how many retries were used (0 = first response valid).
|
|
123
|
+
* - `result.parsed` is typed as `T` (the schema's inferred type).
|
|
124
|
+
*
|
|
125
|
+
* When `jsonSchema` is provided:
|
|
126
|
+
* - Passed via `--json-schema` CLI flag (hard enforcement, no validation or retry).
|
|
127
|
+
* - `result.parsed` is `unknown`.
|
|
128
|
+
*
|
|
129
|
+
* Without schema: `result.parsed` is `unknown` (may be `null` for plain-text responses).
|
|
130
|
+
*
|
|
131
|
+
* Designed to be used as a plain async function inside a trigger.dev task `run()`.
|
|
132
|
+
*/
|
|
133
|
+
declare function runClaudeCode(input: ClaudeCodeInput): Promise<ClaudeCodeResult<unknown>>;
|
|
134
|
+
declare function runClaudeCode<T>(input: ClaudeCodeInput<T> & {
|
|
135
|
+
schema: z.ZodType<T>;
|
|
136
|
+
}): Promise<ClaudeCodeResult<T>>;
|
|
137
|
+
|
|
138
|
+
export { type ClaudeCodeInput, type ClaudeCodeResult, type ClaudeEnvelope, runClaudeCode };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
runClaudeCode: () => runClaudeCode
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(index_exports);
|
|
26
|
+
|
|
27
|
+
// src/runners/claude-code.ts
|
|
28
|
+
var import_node_child_process = require("child_process");
|
|
29
|
+
var import_node_os = require("os");
|
|
30
|
+
var import_zod = require("zod");
|
|
31
|
+
var SchemaValidationError = class extends Error {
|
|
32
|
+
constructor(message) {
|
|
33
|
+
super(message);
|
|
34
|
+
this.name = "SchemaValidationError";
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
var DEFAULT_MAX_OUTPUT_BYTES = 10 * 1024 * 1024;
|
|
38
|
+
function killGracefully(proc) {
|
|
39
|
+
proc.kill("SIGTERM");
|
|
40
|
+
setTimeout(() => {
|
|
41
|
+
proc.kill("SIGKILL");
|
|
42
|
+
}, 5e3).unref();
|
|
43
|
+
}
|
|
44
|
+
function spawnOnce(binary, prompt, model, timeout, maxOutputBytes, env, addDir, mcpConfigPath, jsonSchemaStr) {
|
|
45
|
+
const args = ["--print", "--output-format", "json", "--model", model, "--tools", "Read,Grep,Glob"];
|
|
46
|
+
if (mcpConfigPath) args.push("--mcp-config", mcpConfigPath);
|
|
47
|
+
args.push("--add-dir", addDir);
|
|
48
|
+
if (jsonSchemaStr) {
|
|
49
|
+
args.push("--json-schema", jsonSchemaStr);
|
|
50
|
+
}
|
|
51
|
+
return new Promise((resolve, reject) => {
|
|
52
|
+
const proc = (0, import_node_child_process.spawn)(binary, args, { env, cwd: addDir });
|
|
53
|
+
if (!proc.stdout || !proc.stderr || !proc.stdin) {
|
|
54
|
+
reject(new Error(`Failed to spawn ${binary}: stdio streams not available`));
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
proc.stdin.on("error", () => {
|
|
58
|
+
});
|
|
59
|
+
if (proc.stdin.write(prompt, "utf8")) {
|
|
60
|
+
proc.stdin.end();
|
|
61
|
+
} else {
|
|
62
|
+
proc.stdin.once("drain", () => proc.stdin.end());
|
|
63
|
+
}
|
|
64
|
+
let settled = false;
|
|
65
|
+
const stdoutChunks = [];
|
|
66
|
+
const stderrChunks = [];
|
|
67
|
+
let totalStdoutSize = 0;
|
|
68
|
+
const timer = setTimeout(() => {
|
|
69
|
+
settled = true;
|
|
70
|
+
killGracefully(proc);
|
|
71
|
+
reject(new Error(`${binary} process timed out after ${timeout}ms`));
|
|
72
|
+
}, timeout);
|
|
73
|
+
proc.stdout.on("data", (chunk) => {
|
|
74
|
+
totalStdoutSize += chunk.length;
|
|
75
|
+
if (totalStdoutSize > maxOutputBytes) {
|
|
76
|
+
if (settled) return;
|
|
77
|
+
settled = true;
|
|
78
|
+
clearTimeout(timer);
|
|
79
|
+
killGracefully(proc);
|
|
80
|
+
reject(new Error(`${binary} output exceeded ${maxOutputBytes} bytes`));
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
stdoutChunks.push(chunk);
|
|
84
|
+
});
|
|
85
|
+
proc.stderr.on("data", (chunk) => {
|
|
86
|
+
stderrChunks.push(chunk);
|
|
87
|
+
});
|
|
88
|
+
proc.on("close", (code) => {
|
|
89
|
+
if (settled) return;
|
|
90
|
+
settled = true;
|
|
91
|
+
clearTimeout(timer);
|
|
92
|
+
const stdout = Buffer.concat(stdoutChunks).toString();
|
|
93
|
+
const stderr = Buffer.concat(stderrChunks).toString();
|
|
94
|
+
if (code !== 0) {
|
|
95
|
+
reject(
|
|
96
|
+
new Error(
|
|
97
|
+
`${binary} exited with code ${code}${stderr ? `
|
|
98
|
+
stderr: ${stderr}` : ""}`
|
|
99
|
+
)
|
|
100
|
+
);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
let envelope;
|
|
104
|
+
try {
|
|
105
|
+
envelope = JSON.parse(stdout.trim());
|
|
106
|
+
} catch {
|
|
107
|
+
reject(new Error(`${binary} output is not valid JSON: ${stdout.trim().slice(0, 200)}`));
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (envelope.is_error) {
|
|
111
|
+
reject(new IsErrorResponse(envelope.result));
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (envelope.structured_output !== void 0) {
|
|
115
|
+
const text2 = JSON.stringify(envelope.structured_output);
|
|
116
|
+
resolve({ text: text2, parsed: envelope.structured_output, envelope });
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (typeof envelope.result !== "string") {
|
|
120
|
+
reject(new Error("Invalid envelope: result field is not a string"));
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const text = envelope.result;
|
|
124
|
+
let parsed = null;
|
|
125
|
+
try {
|
|
126
|
+
parsed = JSON.parse(text);
|
|
127
|
+
} catch {
|
|
128
|
+
}
|
|
129
|
+
resolve({ text, parsed, envelope });
|
|
130
|
+
});
|
|
131
|
+
proc.on("error", (err) => {
|
|
132
|
+
if (settled) return;
|
|
133
|
+
settled = true;
|
|
134
|
+
clearTimeout(timer);
|
|
135
|
+
reject(new Error(`Failed to spawn ${binary}: ${err.message}`));
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
var IsErrorResponse = class extends Error {
|
|
140
|
+
constructor(result) {
|
|
141
|
+
super(`returned is_error=true: ${result}`);
|
|
142
|
+
this.result = result;
|
|
143
|
+
this.name = "IsErrorResponse";
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
function spawnWithAccounts(binary, prompt, model, timeout, maxOutputBytes, env, accounts, addDir, mcpConfigPath, jsonSchemaStr) {
|
|
147
|
+
if (!accounts || accounts.length === 0) {
|
|
148
|
+
return spawnOnce(binary, prompt, model, timeout, maxOutputBytes, env, addDir, mcpConfigPath, jsonSchemaStr);
|
|
149
|
+
}
|
|
150
|
+
return (async () => {
|
|
151
|
+
let lastIsError;
|
|
152
|
+
for (const account of accounts) {
|
|
153
|
+
const accountEnv = {
|
|
154
|
+
...env,
|
|
155
|
+
CLAUDE_CONFIG_DIR: `${(0, import_node_os.homedir)()}/.ccs/instances/${account}`
|
|
156
|
+
};
|
|
157
|
+
try {
|
|
158
|
+
return await spawnOnce(binary, prompt, model, timeout, maxOutputBytes, accountEnv, addDir, mcpConfigPath, jsonSchemaStr);
|
|
159
|
+
} catch (err) {
|
|
160
|
+
if (err instanceof IsErrorResponse) {
|
|
161
|
+
lastIsError = err;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
throw err;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
throw new Error(lastIsError.result);
|
|
168
|
+
})();
|
|
169
|
+
}
|
|
170
|
+
function buildFixPrompt(invalidText, error) {
|
|
171
|
+
let errorMessage;
|
|
172
|
+
if (error instanceof import_zod.z.ZodError) {
|
|
173
|
+
errorMessage = error.issues.map((i) => `Path: ${i.path.length ? i.path.join(".") : "(root)"}, ${i.message}`).join("\n");
|
|
174
|
+
} else if (error instanceof Error) {
|
|
175
|
+
errorMessage = error.message;
|
|
176
|
+
} else {
|
|
177
|
+
errorMessage = String(error);
|
|
178
|
+
}
|
|
179
|
+
return `The following JSON failed schema validation.
|
|
180
|
+
|
|
181
|
+
Errors:
|
|
182
|
+
${errorMessage}
|
|
183
|
+
|
|
184
|
+
Invalid response:
|
|
185
|
+
${invalidText}
|
|
186
|
+
|
|
187
|
+
Output ONLY corrected JSON. No explanation, no markdown.`;
|
|
188
|
+
}
|
|
189
|
+
async function runClaudeCode(input) {
|
|
190
|
+
const {
|
|
191
|
+
prompt,
|
|
192
|
+
model = "claude-sonnet-4-6",
|
|
193
|
+
timeout = 3e5,
|
|
194
|
+
maxOutputBytes = DEFAULT_MAX_OUTPUT_BYTES,
|
|
195
|
+
env = process.env,
|
|
196
|
+
binary = "ccs",
|
|
197
|
+
schema,
|
|
198
|
+
fixModel = "claude-haiku-4-5-20251001",
|
|
199
|
+
maxFixes = 1,
|
|
200
|
+
jsonSchema,
|
|
201
|
+
ccsAccounts,
|
|
202
|
+
mcpConfigPath,
|
|
203
|
+
addDir
|
|
204
|
+
} = input;
|
|
205
|
+
if (!prompt || prompt.trim().length === 0) {
|
|
206
|
+
throw new Error("prompt cannot be empty");
|
|
207
|
+
}
|
|
208
|
+
if (!addDir || addDir.trim().length === 0) {
|
|
209
|
+
throw new Error("addDir is required: omitting it exposes the worker CWD to the model");
|
|
210
|
+
}
|
|
211
|
+
if (!binary || binary.trim().length === 0) {
|
|
212
|
+
throw new Error("binary cannot be empty");
|
|
213
|
+
}
|
|
214
|
+
if (jsonSchema && schema) {
|
|
215
|
+
throw new Error("cannot use jsonSchema with schema");
|
|
216
|
+
}
|
|
217
|
+
let jsonSchemaStr;
|
|
218
|
+
if (schema) {
|
|
219
|
+
try {
|
|
220
|
+
jsonSchemaStr = JSON.stringify(import_zod.z.toJSONSchema(schema));
|
|
221
|
+
} catch (err) {
|
|
222
|
+
throw new Error(
|
|
223
|
+
`Failed to convert Zod schema to JSON Schema (requires Zod v4+): ${String(err)}`
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
} else if (jsonSchema) {
|
|
227
|
+
try {
|
|
228
|
+
jsonSchemaStr = JSON.stringify(jsonSchema);
|
|
229
|
+
} catch (err) {
|
|
230
|
+
throw new Error(`Failed to serialize jsonSchema to JSON: ${String(err)}`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
const raw = await spawnWithAccounts(binary, prompt, model, timeout, maxOutputBytes, env, ccsAccounts, addDir, mcpConfigPath, jsonSchemaStr);
|
|
234
|
+
if (!schema) {
|
|
235
|
+
return { text: raw.text, parsed: raw.parsed, envelope: raw.envelope, fixAttempts: 0 };
|
|
236
|
+
}
|
|
237
|
+
let lastText = raw.text;
|
|
238
|
+
let lastError;
|
|
239
|
+
try {
|
|
240
|
+
const parsed = schema.parse(raw.parsed);
|
|
241
|
+
return { ...raw, parsed, fixAttempts: 0 };
|
|
242
|
+
} catch (err) {
|
|
243
|
+
lastError = err;
|
|
244
|
+
}
|
|
245
|
+
for (let attempt = 1; attempt <= maxFixes; attempt++) {
|
|
246
|
+
const fixPromptStr = buildFixPrompt(lastText, lastError);
|
|
247
|
+
const fixRaw = await spawnWithAccounts(binary, fixPromptStr, fixModel, timeout, maxOutputBytes, env, ccsAccounts, addDir, mcpConfigPath, jsonSchemaStr);
|
|
248
|
+
try {
|
|
249
|
+
const parsed = schema.parse(fixRaw.parsed);
|
|
250
|
+
return { ...fixRaw, parsed, fixAttempts: attempt };
|
|
251
|
+
} catch (err) {
|
|
252
|
+
lastText = fixRaw.text;
|
|
253
|
+
lastError = err;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
const errorMessage = lastError instanceof Error ? lastError.message : String(lastError);
|
|
257
|
+
throw new SchemaValidationError(
|
|
258
|
+
`SchemaValidationError: validation failed after ${maxFixes} fix attempt(s): ${errorMessage}`
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
262
|
+
0 && (module.exports = {
|
|
263
|
+
runClaudeCode
|
|
264
|
+
});
|
|
265
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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"]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
// src/runners/claude-code.ts
|
|
2
|
+
import { spawn } from "child_process";
|
|
3
|
+
import { homedir } from "os";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
var SchemaValidationError = class extends Error {
|
|
6
|
+
constructor(message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "SchemaValidationError";
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
var DEFAULT_MAX_OUTPUT_BYTES = 10 * 1024 * 1024;
|
|
12
|
+
function killGracefully(proc) {
|
|
13
|
+
proc.kill("SIGTERM");
|
|
14
|
+
setTimeout(() => {
|
|
15
|
+
proc.kill("SIGKILL");
|
|
16
|
+
}, 5e3).unref();
|
|
17
|
+
}
|
|
18
|
+
function spawnOnce(binary, prompt, model, timeout, maxOutputBytes, env, addDir, mcpConfigPath, jsonSchemaStr) {
|
|
19
|
+
const args = ["--print", "--output-format", "json", "--model", model, "--tools", "Read,Grep,Glob"];
|
|
20
|
+
if (mcpConfigPath) args.push("--mcp-config", mcpConfigPath);
|
|
21
|
+
args.push("--add-dir", addDir);
|
|
22
|
+
if (jsonSchemaStr) {
|
|
23
|
+
args.push("--json-schema", jsonSchemaStr);
|
|
24
|
+
}
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
const proc = spawn(binary, args, { env, cwd: addDir });
|
|
27
|
+
if (!proc.stdout || !proc.stderr || !proc.stdin) {
|
|
28
|
+
reject(new Error(`Failed to spawn ${binary}: stdio streams not available`));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
proc.stdin.on("error", () => {
|
|
32
|
+
});
|
|
33
|
+
if (proc.stdin.write(prompt, "utf8")) {
|
|
34
|
+
proc.stdin.end();
|
|
35
|
+
} else {
|
|
36
|
+
proc.stdin.once("drain", () => proc.stdin.end());
|
|
37
|
+
}
|
|
38
|
+
let settled = false;
|
|
39
|
+
const stdoutChunks = [];
|
|
40
|
+
const stderrChunks = [];
|
|
41
|
+
let totalStdoutSize = 0;
|
|
42
|
+
const timer = setTimeout(() => {
|
|
43
|
+
settled = true;
|
|
44
|
+
killGracefully(proc);
|
|
45
|
+
reject(new Error(`${binary} process timed out after ${timeout}ms`));
|
|
46
|
+
}, timeout);
|
|
47
|
+
proc.stdout.on("data", (chunk) => {
|
|
48
|
+
totalStdoutSize += chunk.length;
|
|
49
|
+
if (totalStdoutSize > maxOutputBytes) {
|
|
50
|
+
if (settled) return;
|
|
51
|
+
settled = true;
|
|
52
|
+
clearTimeout(timer);
|
|
53
|
+
killGracefully(proc);
|
|
54
|
+
reject(new Error(`${binary} output exceeded ${maxOutputBytes} bytes`));
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
stdoutChunks.push(chunk);
|
|
58
|
+
});
|
|
59
|
+
proc.stderr.on("data", (chunk) => {
|
|
60
|
+
stderrChunks.push(chunk);
|
|
61
|
+
});
|
|
62
|
+
proc.on("close", (code) => {
|
|
63
|
+
if (settled) return;
|
|
64
|
+
settled = true;
|
|
65
|
+
clearTimeout(timer);
|
|
66
|
+
const stdout = Buffer.concat(stdoutChunks).toString();
|
|
67
|
+
const stderr = Buffer.concat(stderrChunks).toString();
|
|
68
|
+
if (code !== 0) {
|
|
69
|
+
reject(
|
|
70
|
+
new Error(
|
|
71
|
+
`${binary} exited with code ${code}${stderr ? `
|
|
72
|
+
stderr: ${stderr}` : ""}`
|
|
73
|
+
)
|
|
74
|
+
);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
let envelope;
|
|
78
|
+
try {
|
|
79
|
+
envelope = JSON.parse(stdout.trim());
|
|
80
|
+
} catch {
|
|
81
|
+
reject(new Error(`${binary} output is not valid JSON: ${stdout.trim().slice(0, 200)}`));
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (envelope.is_error) {
|
|
85
|
+
reject(new IsErrorResponse(envelope.result));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (envelope.structured_output !== void 0) {
|
|
89
|
+
const text2 = JSON.stringify(envelope.structured_output);
|
|
90
|
+
resolve({ text: text2, parsed: envelope.structured_output, envelope });
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (typeof envelope.result !== "string") {
|
|
94
|
+
reject(new Error("Invalid envelope: result field is not a string"));
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const text = envelope.result;
|
|
98
|
+
let parsed = null;
|
|
99
|
+
try {
|
|
100
|
+
parsed = JSON.parse(text);
|
|
101
|
+
} catch {
|
|
102
|
+
}
|
|
103
|
+
resolve({ text, parsed, envelope });
|
|
104
|
+
});
|
|
105
|
+
proc.on("error", (err) => {
|
|
106
|
+
if (settled) return;
|
|
107
|
+
settled = true;
|
|
108
|
+
clearTimeout(timer);
|
|
109
|
+
reject(new Error(`Failed to spawn ${binary}: ${err.message}`));
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
var IsErrorResponse = class extends Error {
|
|
114
|
+
constructor(result) {
|
|
115
|
+
super(`returned is_error=true: ${result}`);
|
|
116
|
+
this.result = result;
|
|
117
|
+
this.name = "IsErrorResponse";
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
function spawnWithAccounts(binary, prompt, model, timeout, maxOutputBytes, env, accounts, addDir, mcpConfigPath, jsonSchemaStr) {
|
|
121
|
+
if (!accounts || accounts.length === 0) {
|
|
122
|
+
return spawnOnce(binary, prompt, model, timeout, maxOutputBytes, env, addDir, mcpConfigPath, jsonSchemaStr);
|
|
123
|
+
}
|
|
124
|
+
return (async () => {
|
|
125
|
+
let lastIsError;
|
|
126
|
+
for (const account of accounts) {
|
|
127
|
+
const accountEnv = {
|
|
128
|
+
...env,
|
|
129
|
+
CLAUDE_CONFIG_DIR: `${homedir()}/.ccs/instances/${account}`
|
|
130
|
+
};
|
|
131
|
+
try {
|
|
132
|
+
return await spawnOnce(binary, prompt, model, timeout, maxOutputBytes, accountEnv, addDir, mcpConfigPath, jsonSchemaStr);
|
|
133
|
+
} catch (err) {
|
|
134
|
+
if (err instanceof IsErrorResponse) {
|
|
135
|
+
lastIsError = err;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
throw err;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
throw new Error(lastIsError.result);
|
|
142
|
+
})();
|
|
143
|
+
}
|
|
144
|
+
function buildFixPrompt(invalidText, error) {
|
|
145
|
+
let errorMessage;
|
|
146
|
+
if (error instanceof z.ZodError) {
|
|
147
|
+
errorMessage = error.issues.map((i) => `Path: ${i.path.length ? i.path.join(".") : "(root)"}, ${i.message}`).join("\n");
|
|
148
|
+
} else if (error instanceof Error) {
|
|
149
|
+
errorMessage = error.message;
|
|
150
|
+
} else {
|
|
151
|
+
errorMessage = String(error);
|
|
152
|
+
}
|
|
153
|
+
return `The following JSON failed schema validation.
|
|
154
|
+
|
|
155
|
+
Errors:
|
|
156
|
+
${errorMessage}
|
|
157
|
+
|
|
158
|
+
Invalid response:
|
|
159
|
+
${invalidText}
|
|
160
|
+
|
|
161
|
+
Output ONLY corrected JSON. No explanation, no markdown.`;
|
|
162
|
+
}
|
|
163
|
+
async function runClaudeCode(input) {
|
|
164
|
+
const {
|
|
165
|
+
prompt,
|
|
166
|
+
model = "claude-sonnet-4-6",
|
|
167
|
+
timeout = 3e5,
|
|
168
|
+
maxOutputBytes = DEFAULT_MAX_OUTPUT_BYTES,
|
|
169
|
+
env = process.env,
|
|
170
|
+
binary = "ccs",
|
|
171
|
+
schema,
|
|
172
|
+
fixModel = "claude-haiku-4-5-20251001",
|
|
173
|
+
maxFixes = 1,
|
|
174
|
+
jsonSchema,
|
|
175
|
+
ccsAccounts,
|
|
176
|
+
mcpConfigPath,
|
|
177
|
+
addDir
|
|
178
|
+
} = input;
|
|
179
|
+
if (!prompt || prompt.trim().length === 0) {
|
|
180
|
+
throw new Error("prompt cannot be empty");
|
|
181
|
+
}
|
|
182
|
+
if (!addDir || addDir.trim().length === 0) {
|
|
183
|
+
throw new Error("addDir is required: omitting it exposes the worker CWD to the model");
|
|
184
|
+
}
|
|
185
|
+
if (!binary || binary.trim().length === 0) {
|
|
186
|
+
throw new Error("binary cannot be empty");
|
|
187
|
+
}
|
|
188
|
+
if (jsonSchema && schema) {
|
|
189
|
+
throw new Error("cannot use jsonSchema with schema");
|
|
190
|
+
}
|
|
191
|
+
let jsonSchemaStr;
|
|
192
|
+
if (schema) {
|
|
193
|
+
try {
|
|
194
|
+
jsonSchemaStr = JSON.stringify(z.toJSONSchema(schema));
|
|
195
|
+
} catch (err) {
|
|
196
|
+
throw new Error(
|
|
197
|
+
`Failed to convert Zod schema to JSON Schema (requires Zod v4+): ${String(err)}`
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
} else if (jsonSchema) {
|
|
201
|
+
try {
|
|
202
|
+
jsonSchemaStr = JSON.stringify(jsonSchema);
|
|
203
|
+
} catch (err) {
|
|
204
|
+
throw new Error(`Failed to serialize jsonSchema to JSON: ${String(err)}`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
const raw = await spawnWithAccounts(binary, prompt, model, timeout, maxOutputBytes, env, ccsAccounts, addDir, mcpConfigPath, jsonSchemaStr);
|
|
208
|
+
if (!schema) {
|
|
209
|
+
return { text: raw.text, parsed: raw.parsed, envelope: raw.envelope, fixAttempts: 0 };
|
|
210
|
+
}
|
|
211
|
+
let lastText = raw.text;
|
|
212
|
+
let lastError;
|
|
213
|
+
try {
|
|
214
|
+
const parsed = schema.parse(raw.parsed);
|
|
215
|
+
return { ...raw, parsed, fixAttempts: 0 };
|
|
216
|
+
} catch (err) {
|
|
217
|
+
lastError = err;
|
|
218
|
+
}
|
|
219
|
+
for (let attempt = 1; attempt <= maxFixes; attempt++) {
|
|
220
|
+
const fixPromptStr = buildFixPrompt(lastText, lastError);
|
|
221
|
+
const fixRaw = await spawnWithAccounts(binary, fixPromptStr, fixModel, timeout, maxOutputBytes, env, ccsAccounts, addDir, mcpConfigPath, jsonSchemaStr);
|
|
222
|
+
try {
|
|
223
|
+
const parsed = schema.parse(fixRaw.parsed);
|
|
224
|
+
return { ...fixRaw, parsed, fixAttempts: attempt };
|
|
225
|
+
} catch (err) {
|
|
226
|
+
lastText = fixRaw.text;
|
|
227
|
+
lastError = err;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const errorMessage = lastError instanceof Error ? lastError.message : String(lastError);
|
|
231
|
+
throw new SchemaValidationError(
|
|
232
|
+
`SchemaValidationError: validation failed after ${maxFixes} fix attempt(s): ${errorMessage}`
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
export {
|
|
236
|
+
runClaudeCode
|
|
237
|
+
};
|
|
238
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +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"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@atrvd/trigger",
|
|
3
|
+
"version": "0.1.4",
|
|
4
|
+
"description": "Shared run-functions for trigger.dev tasks",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "ATRVD",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"trigger.dev",
|
|
9
|
+
"claude",
|
|
10
|
+
"ai",
|
|
11
|
+
"automation"
|
|
12
|
+
],
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://github.com/atrvd/trigger.git"
|
|
16
|
+
},
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"registry": "https://registry.npmjs.org",
|
|
19
|
+
"access": "restricted"
|
|
20
|
+
},
|
|
21
|
+
"main": "./dist/index.js",
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"files": [
|
|
24
|
+
"dist"
|
|
25
|
+
],
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"import": "./dist/index.js",
|
|
29
|
+
"require": "./dist/index.cjs",
|
|
30
|
+
"types": "./dist/index.d.ts"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=18.0.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/node": "^22.19.15",
|
|
38
|
+
"@vitest/coverage-v8": "^2.1.9",
|
|
39
|
+
"tsup": "^8.0.0",
|
|
40
|
+
"typescript": "^5.5.0",
|
|
41
|
+
"vitest": "^2.0.0"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"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
|
+
}
|