@titan-design/agent 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +127 -0
- package/dist/index.d.ts +188 -0
- package/dist/index.js +365 -0
- package/dist/index.js.map +1 -0
- package/package.json +47 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Henry Jewkes
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# @titan-design/agent
|
|
2
|
+
|
|
3
|
+
Run one headless Claude Code session and get back either a typed answer or a
|
|
4
|
+
typed failure. A thin wrapper over the Claude Agent SDK's `query()` that adds the
|
|
5
|
+
three things a production caller always has to add itself: a scrubbed child
|
|
6
|
+
environment, mandatory circuit breakers, and a failure taxonomy you can branch on.
|
|
7
|
+
|
|
8
|
+
Tier 1 of the titan-platform DAG (TP-11). Depends on
|
|
9
|
+
`@anthropic-ai/claude-agent-sdk`; `zod` is a peer (v4).
|
|
10
|
+
|
|
11
|
+
## Run an agent
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { z } from "zod";
|
|
15
|
+
import { runAgent } from "@titan-design/agent";
|
|
16
|
+
|
|
17
|
+
const result = await runAgent({
|
|
18
|
+
prompt: "Summarise the failing tests in this repo.",
|
|
19
|
+
cwd: "/path/to/worktree",
|
|
20
|
+
maxTurns: 12,
|
|
21
|
+
maxBudgetUsd: 2,
|
|
22
|
+
outputSchema: z.object({ failing: z.array(z.string()), likelyCause: z.string() }),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
if (result.ok) {
|
|
26
|
+
console.log(result.output.likelyCause, result.usage.totalCostUsd);
|
|
27
|
+
} else if (result.failure.kind === "rate_limited") {
|
|
28
|
+
scheduleRetry(result.failure.retryAt);
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`runAgent` never throws for anything the agent does. The only throws are caller
|
|
33
|
+
mistakes caught before the session starts: a missing or non-positive budget.
|
|
34
|
+
|
|
35
|
+
## Budgets are required, not defaulted
|
|
36
|
+
|
|
37
|
+
The SDK leaves `maxTurns` and `maxBudgetUsd` unlimited. Rather than pick a
|
|
38
|
+
default nobody would notice was wrong, both are required fields on
|
|
39
|
+
`AgentRunConfig` and `runAgent` throws a `TypeError` before it calls `query()`
|
|
40
|
+
if either is missing, zero, negative, or not finite.
|
|
41
|
+
|
|
42
|
+
## The environment scrub
|
|
43
|
+
|
|
44
|
+
`prepareEnv(env, { allowApiKeyBilling })` copies the environment and hands the
|
|
45
|
+
result to `options.env`. It never mutates its argument and never reads
|
|
46
|
+
`process.env` except as the default first argument.
|
|
47
|
+
|
|
48
|
+
| Variable | Action | Why |
|
|
49
|
+
|---|---|---|
|
|
50
|
+
| `CLAUDECODE`, `CLAUDE_CODE_SSE_PORT`, `CLAUDE_CODE_ENTRYPOINT` | strip | a child CLI that inherits these refuses to start inside a parent session |
|
|
51
|
+
| every other `CLAUDE_CODE_*` (e.g. `CLAUDE_CODE_EXECPATH`) | strip | describes the parent session and confuses the child's config inference |
|
|
52
|
+
| `CLAUDE_CODE_OAUTH_TOKEN` | keep | the subscription credential |
|
|
53
|
+
| `CLAUDE_CODE_USE_BEDROCK` / `_VERTEX` / `_FOUNDRY` | keep | a deliberate provider choice |
|
|
54
|
+
| `CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS` | keep | a deliberate opt-out |
|
|
55
|
+
| `CLAUDE_CODE_MAX_RETRIES` | keep, default `3` | the built-in default of 10 turns an outage into a long silent hang |
|
|
56
|
+
| `ANTHROPIC_API_KEY`, `ANTHROPIC_AUTH_TOKEN` | strip unless `allowApiKeyBilling` | either one silently outranks the OAuth token and bills the API account |
|
|
57
|
+
| `HTTP_PROXY`, `HTTPS_PROXY`, `ALL_PROXY` (both cases) | strip | leak from the parent shell and re-route the child's API traffic |
|
|
58
|
+
| everything else | keep | ordinary process environment |
|
|
59
|
+
|
|
60
|
+
Two guards sit either side of the run. `assertAuthEnvOk` is the pre-flight: it
|
|
61
|
+
refuses to start when the OAuth token is missing, or when a metered credential
|
|
62
|
+
survived the scrub. The post-flight reads `apiKeySource` off the first
|
|
63
|
+
`system/init` message and ends the run as `auth_misconfigured` if it names the
|
|
64
|
+
env API-key path. That check is a **blacklist**, not a whitelist: the SDK's
|
|
65
|
+
`ApiKeySource` union grows over time, and an unknown new member is far more
|
|
66
|
+
likely to be another benign route than a billing one.
|
|
67
|
+
|
|
68
|
+
## Failures
|
|
69
|
+
|
|
70
|
+
One `kind` per recovery strategy:
|
|
71
|
+
|
|
72
|
+
| kind | Means |
|
|
73
|
+
|---|---|
|
|
74
|
+
| `rate_limited` | usage or burst limit; carries `retryAt` when the stream or the message said when |
|
|
75
|
+
| `budget_exceeded` | `maxBudgetUsd` was reached |
|
|
76
|
+
| `max_turns` | `maxTurns` was reached |
|
|
77
|
+
| `schema_invalid` | the SDK exhausted its structured-output retries, or the answer failed the caller's zod schema |
|
|
78
|
+
| `refusal` | the model declined |
|
|
79
|
+
| `auth_misconfigured` | the pre-flight or the `apiKeySource` check failed |
|
|
80
|
+
| `runtime_error` | anything else, including a dead subprocess |
|
|
81
|
+
| `aborted` | the caller's `AbortSignal` fired |
|
|
82
|
+
| `inactivity_timeout` | no message arrived for `inactivityTimeoutMs` (default 600 000) |
|
|
83
|
+
|
|
84
|
+
`classifyResult(resultMessage, options?)` is exported so a product can map a
|
|
85
|
+
result message it captured itself.
|
|
86
|
+
|
|
87
|
+
## Structured output
|
|
88
|
+
|
|
89
|
+
Pass `outputSchema` and the SDK is asked for `outputFormat: { type: "json_schema" }`.
|
|
90
|
+
The SDK validates and retries on its own; this package re-parses
|
|
91
|
+
`structured_output` with the same zod schema so the value you receive is the one
|
|
92
|
+
TypeScript promises. `result.output` is the final assistant text when no schema
|
|
93
|
+
was given.
|
|
94
|
+
|
|
95
|
+
## Cost
|
|
96
|
+
|
|
97
|
+
`result.usage` is `{ totalCostUsd, modelUsage, turns, durationMs }`. These are
|
|
98
|
+
the SDK's **client-side estimates**, priced from a bundled table. Use them as a
|
|
99
|
+
budget signal, never as a billing statement. The SDK's cost fields are already
|
|
100
|
+
cumulative across a `query()` call, so this package reads the latest result
|
|
101
|
+
rather than summing; `modelUsage` is the fallback when a crash result arrives
|
|
102
|
+
with the total zeroed.
|
|
103
|
+
|
|
104
|
+
## Subagents and permissions
|
|
105
|
+
|
|
106
|
+
`agents`, `mcpServers`, `hooks`, `allowedTools`, `disallowedTools`, `model`,
|
|
107
|
+
`resumeSessionId` and `settingSources` pass straight through. Two defaults are
|
|
108
|
+
chosen for headless safety:
|
|
109
|
+
|
|
110
|
+
- `permissionMode` defaults to `"dontAsk"`, so nothing runs that was not
|
|
111
|
+
pre-approved.
|
|
112
|
+
- `settingSources` defaults to `[]`, so no CLAUDE.md, settings file, or
|
|
113
|
+
`.mcp.json` is picked up off the filesystem unless you ask for it.
|
|
114
|
+
|
|
115
|
+
**Gotcha:** a subagent *inherits* `bypassPermissions`, `acceptEdits` and `auto`
|
|
116
|
+
from the parent and cannot narrow them per-subagent. A parent running wide open
|
|
117
|
+
gives every subagent that same reach regardless of what its `AgentDefinition`
|
|
118
|
+
says. Prefer `allowedTools` wildcards over a permissive mode.
|
|
119
|
+
|
|
120
|
+
This package runs one session. It does not pool, schedule, or retry; that
|
|
121
|
+
belongs to the workflow tier.
|
|
122
|
+
|
|
123
|
+
## Ending a run
|
|
124
|
+
|
|
125
|
+
Every run ends cleanly. The inactivity watchdog resets on each streamed message
|
|
126
|
+
and, like the caller's `AbortSignal`, ends the run through the SDK's
|
|
127
|
+
`abortController` and then `query.close()`, so no CLI subprocess is left behind.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { PermissionMode, AgentDefinition, McpServerConfig, HookEvent, HookCallbackMatcher, SettingSource, SDKMessage, ModelUsage, query, SDKResultMessage } from '@anthropic-ai/claude-agent-sdk';
|
|
2
|
+
import { ZodError, ZodType } from 'zod';
|
|
3
|
+
|
|
4
|
+
/** Why a run ended without a usable answer. One kind per recovery strategy. */
|
|
5
|
+
type AgentFailure = {
|
|
6
|
+
kind: "rate_limited";
|
|
7
|
+
reason: string;
|
|
8
|
+
retryAt?: Date;
|
|
9
|
+
raw?: unknown;
|
|
10
|
+
} | {
|
|
11
|
+
kind: "budget_exceeded";
|
|
12
|
+
reason: string;
|
|
13
|
+
raw?: unknown;
|
|
14
|
+
} | {
|
|
15
|
+
kind: "schema_invalid";
|
|
16
|
+
reason: string;
|
|
17
|
+
error?: ZodError;
|
|
18
|
+
raw?: unknown;
|
|
19
|
+
} | {
|
|
20
|
+
kind: "max_turns";
|
|
21
|
+
reason: string;
|
|
22
|
+
raw?: unknown;
|
|
23
|
+
} | {
|
|
24
|
+
kind: "refusal";
|
|
25
|
+
reason: string;
|
|
26
|
+
raw?: unknown;
|
|
27
|
+
} | {
|
|
28
|
+
kind: "auth_misconfigured";
|
|
29
|
+
reason: string;
|
|
30
|
+
hint?: string;
|
|
31
|
+
raw?: unknown;
|
|
32
|
+
} | {
|
|
33
|
+
kind: "runtime_error";
|
|
34
|
+
reason: string;
|
|
35
|
+
raw?: unknown;
|
|
36
|
+
} | {
|
|
37
|
+
kind: "aborted";
|
|
38
|
+
reason: string;
|
|
39
|
+
} | {
|
|
40
|
+
kind: "inactivity_timeout";
|
|
41
|
+
reason: string;
|
|
42
|
+
timeoutMs: number;
|
|
43
|
+
};
|
|
44
|
+
type AgentFailureKind = AgentFailure["kind"];
|
|
45
|
+
/** What the first `system/init` message told us about the session we actually got. */
|
|
46
|
+
interface AgentInit {
|
|
47
|
+
apiKeySource: string | undefined;
|
|
48
|
+
model: string | undefined;
|
|
49
|
+
tools: string[];
|
|
50
|
+
permissionMode: string | undefined;
|
|
51
|
+
claudeCodeVersion: string | undefined;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Client-side estimates. The SDK prices from a bundled table, so treat these as
|
|
55
|
+
* a budget signal and never as a billing statement.
|
|
56
|
+
*/
|
|
57
|
+
interface AgentUsage {
|
|
58
|
+
totalCostUsd: number;
|
|
59
|
+
modelUsage: Record<string, ModelUsage>;
|
|
60
|
+
turns: number;
|
|
61
|
+
durationMs: number;
|
|
62
|
+
}
|
|
63
|
+
interface AgentRunConfig<T = string> {
|
|
64
|
+
prompt: string;
|
|
65
|
+
cwd: string;
|
|
66
|
+
/** Required: the SDK's own default is unlimited. Must be positive. */
|
|
67
|
+
maxTurns: number;
|
|
68
|
+
/** Required: the SDK's own default is unlimited. Must be positive. */
|
|
69
|
+
maxBudgetUsd: number;
|
|
70
|
+
model?: string;
|
|
71
|
+
/** Turns on the SDK's `json_schema` output format, which retries on its own until the answer validates. */
|
|
72
|
+
outputSchema?: ZodType<T>;
|
|
73
|
+
allowedTools?: string[];
|
|
74
|
+
disallowedTools?: string[];
|
|
75
|
+
/** Defaults to `dontAsk`: nothing is pre-approved, so nothing runs unprompted. */
|
|
76
|
+
permissionMode?: PermissionMode;
|
|
77
|
+
resumeSessionId?: string;
|
|
78
|
+
agents?: Record<string, AgentDefinition>;
|
|
79
|
+
mcpServers?: Record<string, McpServerConfig>;
|
|
80
|
+
hooks?: Partial<Record<HookEvent, HookCallbackMatcher[]>>;
|
|
81
|
+
signal?: AbortSignal;
|
|
82
|
+
/** Ends the run when no message arrives for this long. Defaults to 600_000. */
|
|
83
|
+
inactivityTimeoutMs?: number;
|
|
84
|
+
/** Keeps `ANTHROPIC_API_KEY` in the child env and bills the API account. */
|
|
85
|
+
allowApiKeyBilling?: boolean;
|
|
86
|
+
/** Defaults to `[]`: nothing is read off the filesystem unless asked for. */
|
|
87
|
+
settingSources?: SettingSource[];
|
|
88
|
+
onMessage?: (message: SDKMessage) => void;
|
|
89
|
+
}
|
|
90
|
+
type AgentRunResult<T = string> = {
|
|
91
|
+
ok: true;
|
|
92
|
+
output: T;
|
|
93
|
+
sessionId: string;
|
|
94
|
+
usage: AgentUsage;
|
|
95
|
+
init: AgentInit;
|
|
96
|
+
} | {
|
|
97
|
+
ok: false;
|
|
98
|
+
failure: AgentFailure;
|
|
99
|
+
sessionId?: string;
|
|
100
|
+
usage?: AgentUsage;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
declare const DEFAULT_INACTIVITY_MS = 600000;
|
|
104
|
+
interface AgentRunDeps {
|
|
105
|
+
/** Injection seam for tests; defaults to the SDK's own `query`. */
|
|
106
|
+
query?: typeof query;
|
|
107
|
+
/** Base environment to scrub; defaults to `process.env`. */
|
|
108
|
+
env?: NodeJS.ProcessEnv;
|
|
109
|
+
now?: () => number;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Run one headless Claude Code session to completion and return either its
|
|
113
|
+
* answer or a typed failure. Never throws for anything the agent does; the only
|
|
114
|
+
* throws are caller mistakes caught before the session starts.
|
|
115
|
+
*/
|
|
116
|
+
declare function runAgent<T = string>(config: AgentRunConfig<T>, deps?: AgentRunDeps): Promise<AgentRunResult<T>>;
|
|
117
|
+
|
|
118
|
+
interface ClassifyOptions {
|
|
119
|
+
/** Epoch millis used to turn a relative "retry after Ns" hint into an absolute date. */
|
|
120
|
+
now?: number;
|
|
121
|
+
/** Epoch millis from the stream's last `rate_limit_event`, which beats anything parsed from prose. */
|
|
122
|
+
rateLimitResetsAt?: number;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Map a terminal `result` message onto the failure taxonomy, or `undefined` when
|
|
126
|
+
* the run succeeded and the caller should read the output.
|
|
127
|
+
*/
|
|
128
|
+
declare function classifyResult(result: SDKResultMessage, options?: ClassifyOptions): AgentFailure | undefined;
|
|
129
|
+
/**
|
|
130
|
+
* The SDK's cost fields are already cumulative across a `query()` call, so the
|
|
131
|
+
* latest result carries the running total and summing them would double-count.
|
|
132
|
+
* `modelUsage` is the fallback because a crash result can arrive with
|
|
133
|
+
* `total_cost_usd` zeroed while the per-model breakdown survives.
|
|
134
|
+
*/
|
|
135
|
+
declare function usageFromResult(result: SDKResultMessage | undefined, durationMs: number): AgentUsage;
|
|
136
|
+
declare function sumModelCost(modelUsage: Record<string, ModelUsage>): number;
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* The only module in this package that reads `process.env`. A stray
|
|
140
|
+
* `ANTHROPIC_API_KEY` silently outranks `CLAUDE_CODE_OAUTH_TOKEN` and routes a
|
|
141
|
+
* headless run onto metered API billing; the invariants here exist because that
|
|
142
|
+
* cost real money once.
|
|
143
|
+
*/
|
|
144
|
+
/** Set by a parent Claude Code session; the child CLI refuses to start when it inherits them. */
|
|
145
|
+
declare const ANTI_NESTING_VARS: readonly ["CLAUDECODE", "CLAUDE_CODE_SSE_PORT", "CLAUDE_CODE_ENTRYPOINT"];
|
|
146
|
+
/** Metered-billing credentials, stripped unless the caller opts in explicitly. */
|
|
147
|
+
declare const STRIPPED_AUTH_VARS: readonly ["ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN"];
|
|
148
|
+
/**
|
|
149
|
+
* The only `CLAUDE_CODE_*` variables a child may inherit. Everything else in
|
|
150
|
+
* that namespace describes the *parent* session (exec path, terminal, entry
|
|
151
|
+
* point) and confuses the child's credential and config inference.
|
|
152
|
+
*/
|
|
153
|
+
declare const CLAUDE_CODE_PASSTHROUGH_VARS: readonly ["CLAUDE_CODE_OAUTH_TOKEN", "CLAUDE_CODE_MAX_RETRIES", "CLAUDE_CODE_USE_BEDROCK", "CLAUDE_CODE_USE_VERTEX", "CLAUDE_CODE_USE_FOUNDRY", "CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS"];
|
|
154
|
+
/** Proxy settings leak from the parent shell and silently re-route the child's API traffic. */
|
|
155
|
+
declare const STRIPPED_PROXY_VARS: readonly ["HTTP_PROXY", "http_proxy", "HTTPS_PROXY", "https_proxy", "ALL_PROXY", "all_proxy"];
|
|
156
|
+
/** The CLI retries 10 times by default, which turns an outage into a long silent hang. */
|
|
157
|
+
declare const DEFAULT_MAX_RETRIES = "3";
|
|
158
|
+
interface PrepareEnvOptions {
|
|
159
|
+
/** Leave `ANTHROPIC_API_KEY` / `ANTHROPIC_AUTH_TOKEN` in place and bill the API account. */
|
|
160
|
+
allowApiKeyBilling?: boolean;
|
|
161
|
+
}
|
|
162
|
+
declare class AuthMisconfiguredError extends Error {
|
|
163
|
+
readonly hint: string | undefined;
|
|
164
|
+
constructor(message: string, hint?: string);
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Copy `env` and scrub it into something safe to hand a child Claude Code
|
|
168
|
+
* process. Never mutates its argument, so the result can go straight to
|
|
169
|
+
* `options.env`.
|
|
170
|
+
*/
|
|
171
|
+
declare function prepareEnv(env?: NodeJS.ProcessEnv, options?: PrepareEnvOptions): Record<string, string>;
|
|
172
|
+
/**
|
|
173
|
+
* Pre-flight the scrubbed env so a misrouted run fails before it spends
|
|
174
|
+
* anything, rather than mid-stream with a surprise billing source.
|
|
175
|
+
*/
|
|
176
|
+
declare function assertAuthEnvOk(env: Record<string, string>, options?: PrepareEnvOptions): void;
|
|
177
|
+
/**
|
|
178
|
+
* Credential sources that mean the run went to metered API billing. A blacklist,
|
|
179
|
+
* not a whitelist: the SDK's `ApiKeySource` union grows, and an unrecognised new
|
|
180
|
+
* member is far more likely to be another benign OAuth-ish route than a billing
|
|
181
|
+
* one. `"env"` is the pre-0.3 spelling of `"ANTHROPIC_API_KEY"`, kept so an
|
|
182
|
+
* older CLI on the PATH still trips the guard.
|
|
183
|
+
*/
|
|
184
|
+
declare const FORBIDDEN_API_KEY_SOURCES: ReadonlySet<string>;
|
|
185
|
+
/** Post-flight on the first `system/init`, the earliest point the real auth route is observable. */
|
|
186
|
+
declare function assertApiKeySourceAllowed(observed: string | undefined): void;
|
|
187
|
+
|
|
188
|
+
export { ANTI_NESTING_VARS, type AgentFailure, type AgentFailureKind, type AgentInit, type AgentRunConfig, type AgentRunDeps, type AgentRunResult, type AgentUsage, AuthMisconfiguredError, CLAUDE_CODE_PASSTHROUGH_VARS, type ClassifyOptions, DEFAULT_INACTIVITY_MS, DEFAULT_MAX_RETRIES, FORBIDDEN_API_KEY_SOURCES, type PrepareEnvOptions, STRIPPED_AUTH_VARS, STRIPPED_PROXY_VARS, assertApiKeySourceAllowed, assertAuthEnvOk, classifyResult, prepareEnv, runAgent, sumModelCost, usageFromResult };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
// src/run.ts
|
|
2
|
+
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
3
|
+
import { toJSONSchema } from "zod";
|
|
4
|
+
|
|
5
|
+
// src/env.ts
|
|
6
|
+
var ANTI_NESTING_VARS = ["CLAUDECODE", "CLAUDE_CODE_SSE_PORT", "CLAUDE_CODE_ENTRYPOINT"];
|
|
7
|
+
var STRIPPED_AUTH_VARS = ["ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN"];
|
|
8
|
+
var CLAUDE_CODE_PASSTHROUGH_VARS = [
|
|
9
|
+
"CLAUDE_CODE_OAUTH_TOKEN",
|
|
10
|
+
"CLAUDE_CODE_MAX_RETRIES",
|
|
11
|
+
"CLAUDE_CODE_USE_BEDROCK",
|
|
12
|
+
"CLAUDE_CODE_USE_VERTEX",
|
|
13
|
+
"CLAUDE_CODE_USE_FOUNDRY",
|
|
14
|
+
"CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS"
|
|
15
|
+
];
|
|
16
|
+
var STRIPPED_PROXY_VARS = [
|
|
17
|
+
"HTTP_PROXY",
|
|
18
|
+
"http_proxy",
|
|
19
|
+
"HTTPS_PROXY",
|
|
20
|
+
"https_proxy",
|
|
21
|
+
"ALL_PROXY",
|
|
22
|
+
"all_proxy"
|
|
23
|
+
];
|
|
24
|
+
var DEFAULT_MAX_RETRIES = "3";
|
|
25
|
+
var AuthMisconfiguredError = class extends Error {
|
|
26
|
+
hint;
|
|
27
|
+
constructor(message, hint) {
|
|
28
|
+
super(message);
|
|
29
|
+
this.name = "AuthMisconfiguredError";
|
|
30
|
+
this.hint = hint;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
function prepareEnv(env = process.env, options = {}) {
|
|
34
|
+
const out = {};
|
|
35
|
+
const passthrough = new Set(CLAUDE_CODE_PASSTHROUGH_VARS);
|
|
36
|
+
for (const [key, value] of Object.entries(env)) {
|
|
37
|
+
if (value === void 0) continue;
|
|
38
|
+
if (isClaudeCodeVar(key) && !passthrough.has(key)) continue;
|
|
39
|
+
out[key] = value;
|
|
40
|
+
}
|
|
41
|
+
for (const key of ANTI_NESTING_VARS) delete out[key];
|
|
42
|
+
for (const key of STRIPPED_PROXY_VARS) delete out[key];
|
|
43
|
+
if (!options.allowApiKeyBilling) {
|
|
44
|
+
for (const key of STRIPPED_AUTH_VARS) delete out[key];
|
|
45
|
+
}
|
|
46
|
+
out.CLAUDE_CODE_MAX_RETRIES ??= DEFAULT_MAX_RETRIES;
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
function isClaudeCodeVar(key) {
|
|
50
|
+
return key === "CLAUDECODE" || key.startsWith("CLAUDE_CODE_");
|
|
51
|
+
}
|
|
52
|
+
function assertAuthEnvOk(env, options = {}) {
|
|
53
|
+
if (options.allowApiKeyBilling) {
|
|
54
|
+
if (!env.ANTHROPIC_API_KEY && !env.ANTHROPIC_AUTH_TOKEN) {
|
|
55
|
+
throw new AuthMisconfiguredError(
|
|
56
|
+
"allowApiKeyBilling is set but neither ANTHROPIC_API_KEY nor ANTHROPIC_AUTH_TOKEN is present"
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (env.ANTHROPIC_API_KEY || env.ANTHROPIC_AUTH_TOKEN) {
|
|
62
|
+
throw new AuthMisconfiguredError(
|
|
63
|
+
"ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN survived the scrub",
|
|
64
|
+
"pass the env through prepareEnv, or set allowApiKeyBilling to bill the API account on purpose"
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
if (!env.CLAUDE_CODE_OAUTH_TOKEN) {
|
|
68
|
+
throw new AuthMisconfiguredError(
|
|
69
|
+
"CLAUDE_CODE_OAUTH_TOKEN is not set; subscription auth will fail",
|
|
70
|
+
"run `claude setup-token` once and export the printed token"
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
var FORBIDDEN_API_KEY_SOURCES = /* @__PURE__ */ new Set(["env", "ANTHROPIC_API_KEY"]);
|
|
75
|
+
function assertApiKeySourceAllowed(observed) {
|
|
76
|
+
if (observed === void 0) {
|
|
77
|
+
throw new AuthMisconfiguredError("the init message carried no apiKeySource; the auth route cannot be verified");
|
|
78
|
+
}
|
|
79
|
+
if (FORBIDDEN_API_KEY_SOURCES.has(observed)) {
|
|
80
|
+
throw new AuthMisconfiguredError(
|
|
81
|
+
`apiKeySource '${observed}' means the run is on env-based API-key billing`,
|
|
82
|
+
"a stray ANTHROPIC_API_KEY most likely overrode CLAUDE_CODE_OAUTH_TOKEN"
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// src/failures.ts
|
|
88
|
+
var RATE_LIMIT_PATTERNS = [
|
|
89
|
+
/you'?ve hit your (session|weekly|opus|sonnet) limit/i,
|
|
90
|
+
/not your usage limit/i,
|
|
91
|
+
/\brate[ _-]?limit(ed|ing)?\b/i,
|
|
92
|
+
/\b429\b/,
|
|
93
|
+
/usage limit reached/i
|
|
94
|
+
];
|
|
95
|
+
var RETRY_AFTER_SECONDS = /retry(?:ing)?[- ](?:after|in)[ :]*(\d+)\s*(?:s|sec|secs|seconds)?\b/i;
|
|
96
|
+
var ISO_TIMESTAMP = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})/;
|
|
97
|
+
function classifyResult(result, options = {}) {
|
|
98
|
+
const text = errorText(result);
|
|
99
|
+
if (result.subtype === "success") {
|
|
100
|
+
if (result.stop_reason === "refusal") {
|
|
101
|
+
return { kind: "refusal", reason: text || "the model refused the request", raw: result };
|
|
102
|
+
}
|
|
103
|
+
return result.is_error ? rateLimitedOrRuntime(text, result, options) : void 0;
|
|
104
|
+
}
|
|
105
|
+
switch (result.subtype) {
|
|
106
|
+
case "error_max_budget_usd":
|
|
107
|
+
return { kind: "budget_exceeded", reason: text || "max budget reached", raw: result };
|
|
108
|
+
case "error_max_turns":
|
|
109
|
+
return { kind: "max_turns", reason: text || "max turns reached", raw: result };
|
|
110
|
+
case "error_max_structured_output_retries":
|
|
111
|
+
return { kind: "schema_invalid", reason: text || "structured-output retries exhausted", raw: result };
|
|
112
|
+
case "error_during_execution":
|
|
113
|
+
return rateLimitedOrRuntime(text, result, options);
|
|
114
|
+
default:
|
|
115
|
+
return { kind: "runtime_error", reason: `unknown result subtype: ${String(result["subtype"])}`, raw: result };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function rateLimitedOrRuntime(text, raw, options) {
|
|
119
|
+
if (!RATE_LIMIT_PATTERNS.some((p) => p.test(text))) {
|
|
120
|
+
return { kind: "runtime_error", reason: text || "the session ended with an unexplained error", raw };
|
|
121
|
+
}
|
|
122
|
+
const failure = { kind: "rate_limited", reason: text, raw };
|
|
123
|
+
const retryAt = parseRetryAt(text, options);
|
|
124
|
+
if (retryAt) failure.retryAt = retryAt;
|
|
125
|
+
return failure;
|
|
126
|
+
}
|
|
127
|
+
function parseRetryAt(text, options) {
|
|
128
|
+
if (options.rateLimitResetsAt !== void 0) return new Date(options.rateLimitResetsAt);
|
|
129
|
+
const seconds = RETRY_AFTER_SECONDS.exec(text);
|
|
130
|
+
if (seconds?.[1]) return new Date((options.now ?? Date.now()) + Number(seconds[1]) * 1e3);
|
|
131
|
+
const iso = ISO_TIMESTAMP.exec(text);
|
|
132
|
+
if (iso) return new Date(iso[0]);
|
|
133
|
+
return void 0;
|
|
134
|
+
}
|
|
135
|
+
function errorText(result) {
|
|
136
|
+
if (result.subtype === "success") return result.result ?? "";
|
|
137
|
+
return (result.errors ?? []).filter((e) => typeof e === "string" && e.length > 0).join("\n");
|
|
138
|
+
}
|
|
139
|
+
function usageFromResult(result, durationMs) {
|
|
140
|
+
const modelUsage = result?.modelUsage ?? {};
|
|
141
|
+
const reported = result?.total_cost_usd;
|
|
142
|
+
const totalCostUsd = typeof reported === "number" && reported > 0 ? reported : sumModelCost(modelUsage);
|
|
143
|
+
return { totalCostUsd, modelUsage, turns: result?.num_turns ?? 0, durationMs };
|
|
144
|
+
}
|
|
145
|
+
function sumModelCost(modelUsage) {
|
|
146
|
+
return Object.values(modelUsage).reduce((total, m) => total + (m.costUSD ?? 0), 0);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// src/run.ts
|
|
150
|
+
var DEFAULT_INACTIVITY_MS = 6e5;
|
|
151
|
+
async function runAgent(config, deps = {}) {
|
|
152
|
+
assertBudgets(config);
|
|
153
|
+
const now = deps.now ?? Date.now;
|
|
154
|
+
const startedAt = now();
|
|
155
|
+
const env = prepareEnv(deps.env ?? process.env, { allowApiKeyBilling: config.allowApiKeyBilling });
|
|
156
|
+
try {
|
|
157
|
+
assertAuthEnvOk(env, { allowApiKeyBilling: config.allowApiKeyBilling });
|
|
158
|
+
} catch (error) {
|
|
159
|
+
return { ok: false, failure: authFailure(error) };
|
|
160
|
+
}
|
|
161
|
+
const session = new Session(config, env, deps);
|
|
162
|
+
try {
|
|
163
|
+
return await session.run(startedAt);
|
|
164
|
+
} finally {
|
|
165
|
+
session.dispose();
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function assertBudgets(config) {
|
|
169
|
+
requirePositive("maxTurns", config.maxTurns);
|
|
170
|
+
requirePositive("maxBudgetUsd", config.maxBudgetUsd);
|
|
171
|
+
}
|
|
172
|
+
function requirePositive(field, value) {
|
|
173
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
174
|
+
throw new TypeError(`${field} is required and must be a positive finite number (received ${String(value)})`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
function authFailure(error) {
|
|
178
|
+
const failure = { kind: "auth_misconfigured", reason: messageOf(error) };
|
|
179
|
+
if (error instanceof AuthMisconfiguredError && error.hint) failure.hint = error.hint;
|
|
180
|
+
return failure;
|
|
181
|
+
}
|
|
182
|
+
function messageOf(error) {
|
|
183
|
+
return error instanceof Error ? error.message : String(error);
|
|
184
|
+
}
|
|
185
|
+
var Session = class {
|
|
186
|
+
constructor(config, env, deps) {
|
|
187
|
+
this.config = config;
|
|
188
|
+
this.env = env;
|
|
189
|
+
this.deps = deps;
|
|
190
|
+
this.inactivityMs = config.inactivityTimeoutMs ?? DEFAULT_INACTIVITY_MS;
|
|
191
|
+
}
|
|
192
|
+
config;
|
|
193
|
+
env;
|
|
194
|
+
deps;
|
|
195
|
+
controller = new AbortController();
|
|
196
|
+
inactivityMs;
|
|
197
|
+
watchdog;
|
|
198
|
+
stream;
|
|
199
|
+
endReason;
|
|
200
|
+
init;
|
|
201
|
+
sessionId;
|
|
202
|
+
result;
|
|
203
|
+
rateLimitResetsAt;
|
|
204
|
+
authError;
|
|
205
|
+
onExternalAbort = () => {
|
|
206
|
+
this.end({ kind: "aborted", reason: String(this.config.signal?.reason ?? "aborted by caller") });
|
|
207
|
+
};
|
|
208
|
+
async run(startedAt) {
|
|
209
|
+
const signal = this.config.signal;
|
|
210
|
+
if (signal?.aborted) this.onExternalAbort();
|
|
211
|
+
else signal?.addEventListener("abort", this.onExternalAbort);
|
|
212
|
+
this.resetWatchdog();
|
|
213
|
+
const run = this.deps.query ?? query;
|
|
214
|
+
const elapsed = () => (this.deps.now ?? Date.now)() - startedAt;
|
|
215
|
+
try {
|
|
216
|
+
this.stream = run({ prompt: this.config.prompt, options: this.buildOptions() });
|
|
217
|
+
for await (const message of this.stream) {
|
|
218
|
+
this.resetWatchdog();
|
|
219
|
+
this.config.onMessage?.(message);
|
|
220
|
+
this.absorb(message);
|
|
221
|
+
if (this.endReason || this.authError) break;
|
|
222
|
+
}
|
|
223
|
+
} catch (error) {
|
|
224
|
+
if (!this.endReason) return this.fail({ kind: "runtime_error", reason: messageOf(error), raw: error }, elapsed());
|
|
225
|
+
}
|
|
226
|
+
return this.settle(elapsed());
|
|
227
|
+
}
|
|
228
|
+
dispose() {
|
|
229
|
+
if (this.watchdog) clearTimeout(this.watchdog);
|
|
230
|
+
this.config.signal?.removeEventListener("abort", this.onExternalAbort);
|
|
231
|
+
if (typeof this.stream?.close === "function") this.stream.close();
|
|
232
|
+
}
|
|
233
|
+
buildOptions() {
|
|
234
|
+
const c = this.config;
|
|
235
|
+
const options = {
|
|
236
|
+
cwd: c.cwd,
|
|
237
|
+
env: this.env,
|
|
238
|
+
abortController: this.controller,
|
|
239
|
+
maxTurns: c.maxTurns,
|
|
240
|
+
maxBudgetUsd: c.maxBudgetUsd,
|
|
241
|
+
permissionMode: c.permissionMode ?? "dontAsk",
|
|
242
|
+
settingSources: c.settingSources ?? []
|
|
243
|
+
};
|
|
244
|
+
if (c.model) options.model = c.model;
|
|
245
|
+
if (c.allowedTools) options.allowedTools = c.allowedTools;
|
|
246
|
+
if (c.disallowedTools) options.disallowedTools = c.disallowedTools;
|
|
247
|
+
if (c.resumeSessionId) options.resume = c.resumeSessionId;
|
|
248
|
+
if (c.agents) options.agents = c.agents;
|
|
249
|
+
if (c.mcpServers) options.mcpServers = c.mcpServers;
|
|
250
|
+
if (c.hooks) options.hooks = c.hooks;
|
|
251
|
+
if (c.outputSchema) {
|
|
252
|
+
options.outputFormat = { type: "json_schema", schema: toJSONSchema(c.outputSchema) };
|
|
253
|
+
}
|
|
254
|
+
return options;
|
|
255
|
+
}
|
|
256
|
+
absorb(message) {
|
|
257
|
+
if ("session_id" in message && typeof message.session_id === "string") this.sessionId = message.session_id;
|
|
258
|
+
if (message.type === "system" && message.subtype === "init") {
|
|
259
|
+
this.init = {
|
|
260
|
+
apiKeySource: message.apiKeySource,
|
|
261
|
+
model: message.model,
|
|
262
|
+
tools: message.tools ?? [],
|
|
263
|
+
permissionMode: message.permissionMode,
|
|
264
|
+
claudeCodeVersion: message.claude_code_version
|
|
265
|
+
};
|
|
266
|
+
if (!this.config.allowApiKeyBilling) this.checkApiKeySource(message.apiKeySource);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
if (message.type === "rate_limit_event") this.rateLimitResetsAt = message.rate_limit_info.resetsAt;
|
|
270
|
+
if (message.type === "result") this.result = message;
|
|
271
|
+
}
|
|
272
|
+
checkApiKeySource(observed) {
|
|
273
|
+
try {
|
|
274
|
+
assertApiKeySourceAllowed(observed);
|
|
275
|
+
} catch (error) {
|
|
276
|
+
this.authError = error instanceof AuthMisconfiguredError ? error : new AuthMisconfiguredError(messageOf(error));
|
|
277
|
+
this.controller.abort(this.authError);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
end(reason) {
|
|
281
|
+
this.endReason ??= reason;
|
|
282
|
+
this.controller.abort(new Error(describeEnd(reason)));
|
|
283
|
+
}
|
|
284
|
+
resetWatchdog() {
|
|
285
|
+
if (this.watchdog) clearTimeout(this.watchdog);
|
|
286
|
+
this.watchdog = setTimeout(() => {
|
|
287
|
+
this.end({ kind: "inactivity_timeout", timeoutMs: this.inactivityMs });
|
|
288
|
+
}, this.inactivityMs);
|
|
289
|
+
this.watchdog.unref?.();
|
|
290
|
+
}
|
|
291
|
+
settle(durationMs) {
|
|
292
|
+
if (this.authError) return this.fail(authFailure(this.authError), durationMs);
|
|
293
|
+
if (this.endReason) return this.fail(endFailure(this.endReason), durationMs);
|
|
294
|
+
if (!this.result) {
|
|
295
|
+
const reason = "the session ended without emitting a result message";
|
|
296
|
+
return this.fail({ kind: "runtime_error", reason }, durationMs);
|
|
297
|
+
}
|
|
298
|
+
const options = {};
|
|
299
|
+
if (this.deps.now) options.now = this.deps.now();
|
|
300
|
+
if (this.rateLimitResetsAt !== void 0) options.rateLimitResetsAt = this.rateLimitResetsAt;
|
|
301
|
+
const failure = classifyResult(this.result, options);
|
|
302
|
+
if (failure) return this.fail(failure, durationMs);
|
|
303
|
+
return this.succeed(this.result, durationMs);
|
|
304
|
+
}
|
|
305
|
+
succeed(result, durationMs) {
|
|
306
|
+
const usage = usageFromResult(result, durationMs);
|
|
307
|
+
if (!this.init || !this.sessionId) {
|
|
308
|
+
const reason = "the session produced a result but never emitted an init message";
|
|
309
|
+
return { ok: false, failure: { kind: "runtime_error", reason, raw: result }, usage };
|
|
310
|
+
}
|
|
311
|
+
const output = this.readOutput(result);
|
|
312
|
+
if ("failure" in output) return { ok: false, failure: output.failure, sessionId: this.sessionId, usage };
|
|
313
|
+
return { ok: true, output: output.value, sessionId: this.sessionId, usage, init: this.init };
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* The SDK already validated `structured_output` against the same schema; we
|
|
317
|
+
* re-parse so the value the caller receives is the one TypeScript promises.
|
|
318
|
+
*/
|
|
319
|
+
readOutput(result) {
|
|
320
|
+
const schema = this.config.outputSchema;
|
|
321
|
+
if (!schema) return { value: result.subtype === "success" ? result.result : "" };
|
|
322
|
+
const raw = result.subtype === "success" ? result.structured_output : void 0;
|
|
323
|
+
const parsed = schema.safeParse(raw);
|
|
324
|
+
if (parsed.success) return { value: parsed.data };
|
|
325
|
+
return {
|
|
326
|
+
failure: {
|
|
327
|
+
kind: "schema_invalid",
|
|
328
|
+
reason: "structured output did not match the supplied schema",
|
|
329
|
+
error: parsed.error,
|
|
330
|
+
raw
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
fail(failure, durationMs) {
|
|
335
|
+
const out = { ok: false, failure };
|
|
336
|
+
if (this.sessionId) out.sessionId = this.sessionId;
|
|
337
|
+
if (this.result) out.usage = usageFromResult(this.result, durationMs);
|
|
338
|
+
return out;
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
function endFailure(reason) {
|
|
342
|
+
if (reason.kind === "aborted") return { kind: "aborted", reason: reason.reason };
|
|
343
|
+
return { kind: "inactivity_timeout", reason: describeEnd(reason), timeoutMs: reason.timeoutMs };
|
|
344
|
+
}
|
|
345
|
+
function describeEnd(reason) {
|
|
346
|
+
return reason.kind === "aborted" ? reason.reason : `no message for ${reason.timeoutMs}ms`;
|
|
347
|
+
}
|
|
348
|
+
export {
|
|
349
|
+
ANTI_NESTING_VARS,
|
|
350
|
+
AuthMisconfiguredError,
|
|
351
|
+
CLAUDE_CODE_PASSTHROUGH_VARS,
|
|
352
|
+
DEFAULT_INACTIVITY_MS,
|
|
353
|
+
DEFAULT_MAX_RETRIES,
|
|
354
|
+
FORBIDDEN_API_KEY_SOURCES,
|
|
355
|
+
STRIPPED_AUTH_VARS,
|
|
356
|
+
STRIPPED_PROXY_VARS,
|
|
357
|
+
assertApiKeySourceAllowed,
|
|
358
|
+
assertAuthEnvOk,
|
|
359
|
+
classifyResult,
|
|
360
|
+
prepareEnv,
|
|
361
|
+
runAgent,
|
|
362
|
+
sumModelCost,
|
|
363
|
+
usageFromResult
|
|
364
|
+
};
|
|
365
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/run.ts","../src/env.ts","../src/failures.ts"],"sourcesContent":["import { query, type Options, type Query, type SDKMessage, type SDKResultMessage } from \"@anthropic-ai/claude-agent-sdk\";\nimport { toJSONSchema } from \"zod\";\nimport { AuthMisconfiguredError, assertApiKeySourceAllowed, assertAuthEnvOk, prepareEnv } from \"./env.js\";\nimport { classifyResult, usageFromResult } from \"./failures.js\";\nimport type { AgentFailure, AgentInit, AgentRunConfig, AgentRunResult } from \"./types.js\";\n\nexport const DEFAULT_INACTIVITY_MS = 600_000;\n\nexport interface AgentRunDeps {\n /** Injection seam for tests; defaults to the SDK's own `query`. */\n query?: typeof query;\n /** Base environment to scrub; defaults to `process.env`. */\n env?: NodeJS.ProcessEnv;\n now?: () => number;\n}\n\n/**\n * Run one headless Claude Code session to completion and return either its\n * answer or a typed failure. Never throws for anything the agent does; the only\n * throws are caller mistakes caught before the session starts.\n */\nexport async function runAgent<T = string>(\n config: AgentRunConfig<T>,\n deps: AgentRunDeps = {},\n): Promise<AgentRunResult<T>> {\n assertBudgets(config);\n const now = deps.now ?? Date.now;\n const startedAt = now();\n\n const env = prepareEnv(deps.env ?? process.env, { allowApiKeyBilling: config.allowApiKeyBilling });\n try {\n assertAuthEnvOk(env, { allowApiKeyBilling: config.allowApiKeyBilling });\n } catch (error) {\n return { ok: false, failure: authFailure(error) };\n }\n\n const session = new Session(config, env, deps);\n try {\n return await session.run(startedAt);\n } finally {\n session.dispose();\n }\n}\n\n/** The SDK leaves both circuit breakers unlimited, so a missing one is a bug, not a default. */\nfunction assertBudgets(config: AgentRunConfig<unknown>): void {\n requirePositive(\"maxTurns\", config.maxTurns);\n requirePositive(\"maxBudgetUsd\", config.maxBudgetUsd);\n}\n\nfunction requirePositive(field: string, value: number): void {\n if (typeof value !== \"number\" || !Number.isFinite(value) || value <= 0) {\n throw new TypeError(`${field} is required and must be a positive finite number (received ${String(value)})`);\n }\n}\n\nfunction authFailure(error: unknown): AgentFailure {\n const failure: AgentFailure = { kind: \"auth_misconfigured\", reason: messageOf(error) };\n if (error instanceof AuthMisconfiguredError && error.hint) failure.hint = error.hint;\n return failure;\n}\n\nfunction messageOf(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\ntype EndReason = { kind: \"aborted\"; reason: string } | { kind: \"inactivity_timeout\"; timeoutMs: number };\n\n/** One `query()` call plus the watchdog, abort wiring and stream state it owns. */\nclass Session<T> {\n private readonly controller = new AbortController();\n private readonly inactivityMs: number;\n private watchdog: ReturnType<typeof setTimeout> | undefined;\n private stream: Query | undefined;\n private endReason: EndReason | undefined;\n private init: AgentInit | undefined;\n private sessionId: string | undefined;\n private result: SDKResultMessage | undefined;\n private rateLimitResetsAt: number | undefined;\n private authError: AuthMisconfiguredError | undefined;\n private readonly onExternalAbort = () => {\n this.end({ kind: \"aborted\", reason: String(this.config.signal?.reason ?? \"aborted by caller\") });\n };\n\n constructor(\n private readonly config: AgentRunConfig<T>,\n private readonly env: Record<string, string>,\n private readonly deps: AgentRunDeps,\n ) {\n this.inactivityMs = config.inactivityTimeoutMs ?? DEFAULT_INACTIVITY_MS;\n }\n\n async run(startedAt: number): Promise<AgentRunResult<T>> {\n const signal = this.config.signal;\n if (signal?.aborted) this.onExternalAbort();\n else signal?.addEventListener(\"abort\", this.onExternalAbort);\n\n this.resetWatchdog();\n const run = this.deps.query ?? query;\n const elapsed = () => (this.deps.now ?? Date.now)() - startedAt;\n\n try {\n this.stream = run({ prompt: this.config.prompt, options: this.buildOptions() });\n for await (const message of this.stream) {\n this.resetWatchdog();\n this.config.onMessage?.(message);\n this.absorb(message);\n if (this.endReason || this.authError) break;\n }\n } catch (error) {\n if (!this.endReason) return this.fail({ kind: \"runtime_error\", reason: messageOf(error), raw: error }, elapsed());\n }\n return this.settle(elapsed());\n }\n\n dispose(): void {\n if (this.watchdog) clearTimeout(this.watchdog);\n this.config.signal?.removeEventListener(\"abort\", this.onExternalAbort);\n // `close()` tears down the CLI subprocess; guarded because older SDKs lack it.\n if (typeof this.stream?.close === \"function\") this.stream.close();\n }\n\n private buildOptions(): Options {\n const c = this.config;\n const options: Options = {\n cwd: c.cwd,\n env: this.env,\n abortController: this.controller,\n maxTurns: c.maxTurns,\n maxBudgetUsd: c.maxBudgetUsd,\n permissionMode: c.permissionMode ?? \"dontAsk\",\n settingSources: c.settingSources ?? [],\n };\n if (c.model) options.model = c.model;\n if (c.allowedTools) options.allowedTools = c.allowedTools;\n if (c.disallowedTools) options.disallowedTools = c.disallowedTools;\n if (c.resumeSessionId) options.resume = c.resumeSessionId;\n if (c.agents) options.agents = c.agents;\n if (c.mcpServers) options.mcpServers = c.mcpServers;\n if (c.hooks) options.hooks = c.hooks;\n if (c.outputSchema) {\n options.outputFormat = { type: \"json_schema\", schema: toJSONSchema(c.outputSchema) as Record<string, unknown> };\n }\n return options;\n }\n\n private absorb(message: SDKMessage): void {\n if (\"session_id\" in message && typeof message.session_id === \"string\") this.sessionId = message.session_id;\n if (message.type === \"system\" && message.subtype === \"init\") {\n this.init = {\n apiKeySource: message.apiKeySource,\n model: message.model,\n tools: message.tools ?? [],\n permissionMode: message.permissionMode,\n claudeCodeVersion: message.claude_code_version,\n };\n if (!this.config.allowApiKeyBilling) this.checkApiKeySource(message.apiKeySource);\n return;\n }\n if (message.type === \"rate_limit_event\") this.rateLimitResetsAt = message.rate_limit_info.resetsAt;\n if (message.type === \"result\") this.result = message;\n }\n\n private checkApiKeySource(observed: string | undefined): void {\n try {\n assertApiKeySourceAllowed(observed);\n } catch (error) {\n // Stop the session here rather than let a mis-billed run continue spending.\n this.authError = error instanceof AuthMisconfiguredError ? error : new AuthMisconfiguredError(messageOf(error));\n this.controller.abort(this.authError);\n }\n }\n\n private end(reason: EndReason): void {\n this.endReason ??= reason;\n this.controller.abort(new Error(describeEnd(reason)));\n }\n\n private resetWatchdog(): void {\n if (this.watchdog) clearTimeout(this.watchdog);\n this.watchdog = setTimeout(() => {\n this.end({ kind: \"inactivity_timeout\", timeoutMs: this.inactivityMs });\n }, this.inactivityMs);\n this.watchdog.unref?.();\n }\n\n private settle(durationMs: number): AgentRunResult<T> {\n if (this.authError) return this.fail(authFailure(this.authError), durationMs);\n if (this.endReason) return this.fail(endFailure(this.endReason), durationMs);\n if (!this.result) {\n const reason = \"the session ended without emitting a result message\";\n return this.fail({ kind: \"runtime_error\", reason }, durationMs);\n }\n\n const options: { now?: number; rateLimitResetsAt?: number } = {};\n if (this.deps.now) options.now = this.deps.now();\n if (this.rateLimitResetsAt !== undefined) options.rateLimitResetsAt = this.rateLimitResetsAt;\n const failure = classifyResult(this.result, options);\n if (failure) return this.fail(failure, durationMs);\n\n return this.succeed(this.result, durationMs);\n }\n\n private succeed(result: SDKResultMessage, durationMs: number): AgentRunResult<T> {\n const usage = usageFromResult(result, durationMs);\n if (!this.init || !this.sessionId) {\n const reason = \"the session produced a result but never emitted an init message\";\n return { ok: false, failure: { kind: \"runtime_error\", reason, raw: result }, usage };\n }\n const output = this.readOutput(result);\n if (\"failure\" in output) return { ok: false, failure: output.failure, sessionId: this.sessionId, usage };\n return { ok: true, output: output.value, sessionId: this.sessionId, usage, init: this.init };\n }\n\n /**\n * The SDK already validated `structured_output` against the same schema; we\n * re-parse so the value the caller receives is the one TypeScript promises.\n */\n private readOutput(result: SDKResultMessage): { value: T } | { failure: AgentFailure } {\n const schema = this.config.outputSchema;\n if (!schema) return { value: (result.subtype === \"success\" ? result.result : \"\") as T };\n const raw = result.subtype === \"success\" ? result.structured_output : undefined;\n const parsed = schema.safeParse(raw);\n if (parsed.success) return { value: parsed.data };\n return {\n failure: {\n kind: \"schema_invalid\",\n reason: \"structured output did not match the supplied schema\",\n error: parsed.error,\n raw,\n },\n };\n }\n\n private fail(failure: AgentFailure, durationMs: number): AgentRunResult<T> {\n const out: AgentRunResult<T> = { ok: false, failure };\n if (this.sessionId) out.sessionId = this.sessionId;\n if (this.result) out.usage = usageFromResult(this.result, durationMs);\n return out;\n }\n}\n\nfunction endFailure(reason: EndReason): AgentFailure {\n if (reason.kind === \"aborted\") return { kind: \"aborted\", reason: reason.reason };\n return { kind: \"inactivity_timeout\", reason: describeEnd(reason), timeoutMs: reason.timeoutMs };\n}\n\nfunction describeEnd(reason: EndReason): string {\n return reason.kind === \"aborted\" ? reason.reason : `no message for ${reason.timeoutMs}ms`;\n}\n","/**\n * The only module in this package that reads `process.env`. A stray\n * `ANTHROPIC_API_KEY` silently outranks `CLAUDE_CODE_OAUTH_TOKEN` and routes a\n * headless run onto metered API billing; the invariants here exist because that\n * cost real money once.\n */\n\n/** Set by a parent Claude Code session; the child CLI refuses to start when it inherits them. */\nexport const ANTI_NESTING_VARS = [\"CLAUDECODE\", \"CLAUDE_CODE_SSE_PORT\", \"CLAUDE_CODE_ENTRYPOINT\"] as const;\n\n/** Metered-billing credentials, stripped unless the caller opts in explicitly. */\nexport const STRIPPED_AUTH_VARS = [\"ANTHROPIC_API_KEY\", \"ANTHROPIC_AUTH_TOKEN\"] as const;\n\n/**\n * The only `CLAUDE_CODE_*` variables a child may inherit. Everything else in\n * that namespace describes the *parent* session (exec path, terminal, entry\n * point) and confuses the child's credential and config inference.\n */\nexport const CLAUDE_CODE_PASSTHROUGH_VARS = [\n \"CLAUDE_CODE_OAUTH_TOKEN\",\n \"CLAUDE_CODE_MAX_RETRIES\",\n \"CLAUDE_CODE_USE_BEDROCK\",\n \"CLAUDE_CODE_USE_VERTEX\",\n \"CLAUDE_CODE_USE_FOUNDRY\",\n \"CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS\",\n] as const;\n\n/** Proxy settings leak from the parent shell and silently re-route the child's API traffic. */\nexport const STRIPPED_PROXY_VARS = [\n \"HTTP_PROXY\",\n \"http_proxy\",\n \"HTTPS_PROXY\",\n \"https_proxy\",\n \"ALL_PROXY\",\n \"all_proxy\",\n] as const;\n\n/** The CLI retries 10 times by default, which turns an outage into a long silent hang. */\nexport const DEFAULT_MAX_RETRIES = \"3\";\n\nexport interface PrepareEnvOptions {\n /** Leave `ANTHROPIC_API_KEY` / `ANTHROPIC_AUTH_TOKEN` in place and bill the API account. */\n allowApiKeyBilling?: boolean;\n}\n\nexport class AuthMisconfiguredError extends Error {\n readonly hint: string | undefined;\n\n constructor(message: string, hint?: string) {\n super(message);\n this.name = \"AuthMisconfiguredError\";\n this.hint = hint;\n }\n}\n\n/**\n * Copy `env` and scrub it into something safe to hand a child Claude Code\n * process. Never mutates its argument, so the result can go straight to\n * `options.env`.\n */\nexport function prepareEnv(\n env: NodeJS.ProcessEnv = process.env,\n options: PrepareEnvOptions = {},\n): Record<string, string> {\n const out: Record<string, string> = {};\n const passthrough = new Set<string>(CLAUDE_CODE_PASSTHROUGH_VARS);\n\n for (const [key, value] of Object.entries(env)) {\n if (value === undefined) continue;\n if (isClaudeCodeVar(key) && !passthrough.has(key)) continue;\n out[key] = value;\n }\n\n for (const key of ANTI_NESTING_VARS) delete out[key];\n for (const key of STRIPPED_PROXY_VARS) delete out[key];\n if (!options.allowApiKeyBilling) {\n for (const key of STRIPPED_AUTH_VARS) delete out[key];\n }\n\n out.CLAUDE_CODE_MAX_RETRIES ??= DEFAULT_MAX_RETRIES;\n return out;\n}\n\nfunction isClaudeCodeVar(key: string): boolean {\n return key === \"CLAUDECODE\" || key.startsWith(\"CLAUDE_CODE_\");\n}\n\n/**\n * Pre-flight the scrubbed env so a misrouted run fails before it spends\n * anything, rather than mid-stream with a surprise billing source.\n */\nexport function assertAuthEnvOk(env: Record<string, string>, options: PrepareEnvOptions = {}): void {\n if (options.allowApiKeyBilling) {\n if (!env.ANTHROPIC_API_KEY && !env.ANTHROPIC_AUTH_TOKEN) {\n throw new AuthMisconfiguredError(\n \"allowApiKeyBilling is set but neither ANTHROPIC_API_KEY nor ANTHROPIC_AUTH_TOKEN is present\",\n );\n }\n return;\n }\n\n if (env.ANTHROPIC_API_KEY || env.ANTHROPIC_AUTH_TOKEN) {\n throw new AuthMisconfiguredError(\n \"ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN survived the scrub\",\n \"pass the env through prepareEnv, or set allowApiKeyBilling to bill the API account on purpose\",\n );\n }\n\n if (!env.CLAUDE_CODE_OAUTH_TOKEN) {\n throw new AuthMisconfiguredError(\n \"CLAUDE_CODE_OAUTH_TOKEN is not set; subscription auth will fail\",\n \"run `claude setup-token` once and export the printed token\",\n );\n }\n}\n\n/**\n * Credential sources that mean the run went to metered API billing. A blacklist,\n * not a whitelist: the SDK's `ApiKeySource` union grows, and an unrecognised new\n * member is far more likely to be another benign OAuth-ish route than a billing\n * one. `\"env\"` is the pre-0.3 spelling of `\"ANTHROPIC_API_KEY\"`, kept so an\n * older CLI on the PATH still trips the guard.\n */\nexport const FORBIDDEN_API_KEY_SOURCES: ReadonlySet<string> = new Set([\"env\", \"ANTHROPIC_API_KEY\"]);\n\n/** Post-flight on the first `system/init`, the earliest point the real auth route is observable. */\nexport function assertApiKeySourceAllowed(observed: string | undefined): void {\n if (observed === undefined) {\n throw new AuthMisconfiguredError(\"the init message carried no apiKeySource; the auth route cannot be verified\");\n }\n if (FORBIDDEN_API_KEY_SOURCES.has(observed)) {\n throw new AuthMisconfiguredError(\n `apiKeySource '${observed}' means the run is on env-based API-key billing`,\n \"a stray ANTHROPIC_API_KEY most likely overrode CLAUDE_CODE_OAUTH_TOKEN\",\n );\n }\n}\n","import type { ModelUsage, SDKResultMessage } from \"@anthropic-ai/claude-agent-sdk\";\nimport type { AgentFailure, AgentUsage } from \"./types.js\";\n\n/**\n * Rate limiting reaches the caller as prose, not as a subtype, so the phrasing\n * is the only signal. Matching too eagerly would relabel ordinary runtime errors\n * as retryable, so each pattern is anchored on wording the CLI actually emits.\n */\nconst RATE_LIMIT_PATTERNS = [\n /you'?ve hit your (session|weekly|opus|sonnet) limit/i,\n /not your usage limit/i,\n /\\brate[ _-]?limit(ed|ing)?\\b/i,\n /\\b429\\b/,\n /usage limit reached/i,\n];\n\nconst RETRY_AFTER_SECONDS = /retry(?:ing)?[- ](?:after|in)[ :]*(\\d+)\\s*(?:s|sec|secs|seconds)?\\b/i;\nconst ISO_TIMESTAMP = /\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?(?:Z|[+-]\\d{2}:\\d{2})/;\n\nexport interface ClassifyOptions {\n /** Epoch millis used to turn a relative \"retry after Ns\" hint into an absolute date. */\n now?: number;\n /** Epoch millis from the stream's last `rate_limit_event`, which beats anything parsed from prose. */\n rateLimitResetsAt?: number;\n}\n\n/**\n * Map a terminal `result` message onto the failure taxonomy, or `undefined` when\n * the run succeeded and the caller should read the output.\n */\nexport function classifyResult(result: SDKResultMessage, options: ClassifyOptions = {}): AgentFailure | undefined {\n const text = errorText(result);\n\n if (result.subtype === \"success\") {\n if (result.stop_reason === \"refusal\") {\n return { kind: \"refusal\", reason: text || \"the model refused the request\", raw: result };\n }\n // A success subtype still carries `is_error` when the turn died on an API error.\n return result.is_error ? rateLimitedOrRuntime(text, result, options) : undefined;\n }\n\n switch (result.subtype) {\n case \"error_max_budget_usd\":\n return { kind: \"budget_exceeded\", reason: text || \"max budget reached\", raw: result };\n case \"error_max_turns\":\n return { kind: \"max_turns\", reason: text || \"max turns reached\", raw: result };\n case \"error_max_structured_output_retries\":\n return { kind: \"schema_invalid\", reason: text || \"structured-output retries exhausted\", raw: result };\n case \"error_during_execution\":\n return rateLimitedOrRuntime(text, result, options);\n default:\n // Unreachable per the SDK's union, but a newer CLI may add a subtype.\n return { kind: \"runtime_error\", reason: `unknown result subtype: ${String(result[\"subtype\"])}`, raw: result };\n }\n}\n\nfunction rateLimitedOrRuntime(text: string, raw: SDKResultMessage, options: ClassifyOptions): AgentFailure {\n if (!RATE_LIMIT_PATTERNS.some((p) => p.test(text))) {\n return { kind: \"runtime_error\", reason: text || \"the session ended with an unexplained error\", raw };\n }\n const failure: AgentFailure = { kind: \"rate_limited\", reason: text, raw };\n const retryAt = parseRetryAt(text, options);\n if (retryAt) failure.retryAt = retryAt;\n return failure;\n}\n\nfunction parseRetryAt(text: string, options: ClassifyOptions): Date | undefined {\n if (options.rateLimitResetsAt !== undefined) return new Date(options.rateLimitResetsAt);\n const seconds = RETRY_AFTER_SECONDS.exec(text);\n if (seconds?.[1]) return new Date((options.now ?? Date.now()) + Number(seconds[1]) * 1000);\n const iso = ISO_TIMESTAMP.exec(text);\n if (iso) return new Date(iso[0]);\n return undefined;\n}\n\n/** `result` carries the text on a success message, `errors` on every failure subtype. */\nfunction errorText(result: SDKResultMessage): string {\n if (result.subtype === \"success\") return result.result ?? \"\";\n return (result.errors ?? []).filter((e) => typeof e === \"string\" && e.length > 0).join(\"\\n\");\n}\n\n/**\n * The SDK's cost fields are already cumulative across a `query()` call, so the\n * latest result carries the running total and summing them would double-count.\n * `modelUsage` is the fallback because a crash result can arrive with\n * `total_cost_usd` zeroed while the per-model breakdown survives.\n */\nexport function usageFromResult(result: SDKResultMessage | undefined, durationMs: number): AgentUsage {\n const modelUsage = result?.modelUsage ?? {};\n const reported = result?.total_cost_usd;\n const totalCostUsd = typeof reported === \"number\" && reported > 0 ? reported : sumModelCost(modelUsage);\n return { totalCostUsd, modelUsage, turns: result?.num_turns ?? 0, durationMs };\n}\n\nexport function sumModelCost(modelUsage: Record<string, ModelUsage>): number {\n return Object.values(modelUsage).reduce((total, m) => total + (m.costUSD ?? 0), 0);\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@titan-design/agent",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Headless agent triggering over the Claude Agent SDK with env-scrub, failure taxonomy, and hard budgets",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"author": "Henry Jewkes",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"homepage": "https://github.com/HJewkes/titan-platform#readme",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/HJewkes/titan-platform.git",
|
|
24
|
+
"directory": "packages/agent"
|
|
25
|
+
},
|
|
26
|
+
"bugs": "https://github.com/HJewkes/titan-platform/issues",
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=20"
|
|
29
|
+
},
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@anthropic-ai/claude-agent-sdk": "^0.3.263"
|
|
35
|
+
},
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"zod": "^4.0.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"zod": "^4.4.3"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "tsup",
|
|
44
|
+
"typecheck": "tsc --noEmit",
|
|
45
|
+
"lint": "eslint src"
|
|
46
|
+
}
|
|
47
|
+
}
|