@ai-sdk/harness-claude-code 0.0.0 → 1.0.0-beta.11
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/CHANGELOG.md +95 -0
- package/LICENSE +13 -0
- package/README.md +62 -0
- package/dist/bridge/index.mjs +1023 -0
- package/dist/bridge/index.mjs.map +1 -0
- package/dist/bridge/package.json +13 -0
- package/dist/bridge/pnpm-lock.yaml +1035 -0
- package/dist/index.d.ts +376 -0
- package/dist/index.js +1307 -0
- package/dist/index.js.map +1 -0
- package/package.json +74 -1
- package/src/bridge/claude-skills-option.ts +11 -0
- package/src/bridge/compaction-latch.ts +62 -0
- package/src/bridge/index.ts +881 -0
- package/src/bridge/package.json +13 -0
- package/src/bridge/pnpm-lock.yaml +1035 -0
- package/src/claude-code-auth.ts +131 -0
- package/src/claude-code-bridge-protocol.ts +38 -0
- package/src/claude-code-harness.ts +1586 -0
- package/src/index.ts +12 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
|
|
6
|
+
export type ClaudeCodeAuthOptions = {
|
|
7
|
+
readonly anthropic?: {
|
|
8
|
+
readonly apiKey?: string;
|
|
9
|
+
readonly authToken?: string;
|
|
10
|
+
readonly baseUrl?: string;
|
|
11
|
+
};
|
|
12
|
+
readonly gateway?: {
|
|
13
|
+
readonly apiKey?: string;
|
|
14
|
+
readonly baseUrl?: string;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Resolve the environment-variable blob the bridge needs to authenticate
|
|
20
|
+
* with Anthropic (directly or via the Vercel AI Gateway). Precedence:
|
|
21
|
+
*
|
|
22
|
+
* 1. Explicit `auth.anthropic` — pin to direct Anthropic auth.
|
|
23
|
+
* 2. Explicit `auth.gateway` — pin to gateway-routed auth.
|
|
24
|
+
* 3. Auto-detect from the host process env: gateway first
|
|
25
|
+
* (`AI_GATEWAY_API_KEY`), then direct (`ANTHROPIC_API_KEY` /
|
|
26
|
+
* `ANTHROPIC_AUTH_TOKEN`).
|
|
27
|
+
*/
|
|
28
|
+
export type ResolveClaudeCodeEnvOptions = {
|
|
29
|
+
/**
|
|
30
|
+
* Returns an API key from a custom source (e.g. a password manager).
|
|
31
|
+
* Used as the last-resort fallback in the auto-detect branch when no
|
|
32
|
+
* static env vars or explicit auth are configured. Defaults to running
|
|
33
|
+
* the `apiKeyHelper` command from `~/.claude/settings.json`, matching
|
|
34
|
+
* the `claude` CLI's own behaviour.
|
|
35
|
+
*/
|
|
36
|
+
readApiKeyHelper?: () => string | undefined;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export function resolveClaudeCodeEnv(
|
|
40
|
+
auth: ClaudeCodeAuthOptions | undefined,
|
|
41
|
+
processEnv: Record<string, string | undefined> = process.env,
|
|
42
|
+
options: ResolveClaudeCodeEnvOptions = {},
|
|
43
|
+
): Record<string, string> {
|
|
44
|
+
const readApiKey = options.readApiKeyHelper ?? readApiKeyHelper;
|
|
45
|
+
if (auth?.anthropic) {
|
|
46
|
+
return pickAnthropic({ explicit: auth.anthropic, processEnv, readApiKey });
|
|
47
|
+
}
|
|
48
|
+
if (auth?.gateway) {
|
|
49
|
+
return pickGateway(auth.gateway, processEnv);
|
|
50
|
+
}
|
|
51
|
+
if (processEnv.AI_GATEWAY_API_KEY) {
|
|
52
|
+
return pickGateway({}, processEnv);
|
|
53
|
+
}
|
|
54
|
+
return pickAnthropic({ processEnv, readApiKey });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function pickAnthropic({
|
|
58
|
+
explicit,
|
|
59
|
+
processEnv,
|
|
60
|
+
readApiKey,
|
|
61
|
+
}: {
|
|
62
|
+
explicit?: NonNullable<ClaudeCodeAuthOptions['anthropic']>;
|
|
63
|
+
processEnv: Record<string, string | undefined>;
|
|
64
|
+
readApiKey: () => string | undefined;
|
|
65
|
+
}): Record<string, string> {
|
|
66
|
+
const env: Record<string, string> = {};
|
|
67
|
+
const helperKey = explicit ? undefined : readApiKey();
|
|
68
|
+
const apiKey = explicit?.apiKey ?? processEnv.ANTHROPIC_API_KEY ?? helperKey;
|
|
69
|
+
const authToken =
|
|
70
|
+
explicit?.authToken ?? processEnv.ANTHROPIC_AUTH_TOKEN ?? helperKey;
|
|
71
|
+
if (apiKey) env.ANTHROPIC_API_KEY = apiKey;
|
|
72
|
+
if (authToken) env.ANTHROPIC_AUTH_TOKEN = authToken;
|
|
73
|
+
const baseUrl = explicit?.baseUrl ?? processEnv.ANTHROPIC_BASE_URL;
|
|
74
|
+
if (baseUrl) env.ANTHROPIC_BASE_URL = baseUrl;
|
|
75
|
+
return env;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Read the `apiKeyHelper` setting from `~/.claude/settings.json` and run
|
|
80
|
+
* it. The `claude` CLI uses this hook to fetch credentials from password
|
|
81
|
+
* managers and similar tools; mirroring it here lets users with that
|
|
82
|
+
* setup run the harness without having to set `ANTHROPIC_API_KEY`
|
|
83
|
+
* explicitly.
|
|
84
|
+
*/
|
|
85
|
+
function readApiKeyHelper(): string | undefined {
|
|
86
|
+
const home = homedir();
|
|
87
|
+
if (!home) return undefined;
|
|
88
|
+
let raw: string;
|
|
89
|
+
try {
|
|
90
|
+
raw = readFileSync(join(home, '.claude', 'settings.json'), 'utf8');
|
|
91
|
+
} catch {
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
let settings: { apiKeyHelper?: unknown };
|
|
95
|
+
try {
|
|
96
|
+
settings = JSON.parse(raw);
|
|
97
|
+
} catch {
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
const command = settings.apiKeyHelper;
|
|
101
|
+
if (typeof command !== 'string' || command.length === 0) return undefined;
|
|
102
|
+
try {
|
|
103
|
+
const output = execFileSync('sh', ['-c', command], {
|
|
104
|
+
encoding: 'utf8',
|
|
105
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
106
|
+
});
|
|
107
|
+
const trimmed = output.trim();
|
|
108
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
109
|
+
} catch {
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function pickGateway(
|
|
115
|
+
explicit: NonNullable<ClaudeCodeAuthOptions['gateway']>,
|
|
116
|
+
processEnv: Record<string, string | undefined>,
|
|
117
|
+
): Record<string, string> {
|
|
118
|
+
const apiKey = explicit.apiKey ?? processEnv.AI_GATEWAY_API_KEY;
|
|
119
|
+
const baseUrl =
|
|
120
|
+
explicit.baseUrl ??
|
|
121
|
+
processEnv.AI_GATEWAY_BASE_URL ??
|
|
122
|
+
'https://ai-gateway.vercel.sh';
|
|
123
|
+
const env: Record<string, string> = {};
|
|
124
|
+
if (apiKey) {
|
|
125
|
+
env.AI_GATEWAY_API_KEY = apiKey;
|
|
126
|
+
env.ANTHROPIC_API_KEY = apiKey;
|
|
127
|
+
}
|
|
128
|
+
env.AI_GATEWAY_BASE_URL = baseUrl;
|
|
129
|
+
env.ANTHROPIC_BASE_URL = baseUrl;
|
|
130
|
+
return env;
|
|
131
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import {
|
|
2
|
+
harnessV1BridgeInboundCommandSchemas,
|
|
3
|
+
harnessV1BridgeOutboundMessageSchema,
|
|
4
|
+
harnessV1BridgeReadySchema,
|
|
5
|
+
harnessV1BridgeStartBaseSchema,
|
|
6
|
+
} from '@ai-sdk/harness';
|
|
7
|
+
import { z } from 'zod/v4';
|
|
8
|
+
|
|
9
|
+
/*
|
|
10
|
+
* Claude Code's bridge wire protocol. The outbound events, transport frames,
|
|
11
|
+
* shared inbound commands, and `bridge-ready` line all come from the shared
|
|
12
|
+
* `@ai-sdk/harness` protocol — the only Claude-specific piece is the `start`
|
|
13
|
+
* payload, which carries Claude SDK configuration.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export const outboundMessageSchema = harnessV1BridgeOutboundMessageSchema;
|
|
17
|
+
export type OutboundMessage = z.infer<typeof outboundMessageSchema>;
|
|
18
|
+
|
|
19
|
+
export const startMessageSchema = harnessV1BridgeStartBaseSchema.extend({
|
|
20
|
+
thinking: z.enum(['off', 'on', 'adaptive']).optional(),
|
|
21
|
+
maxTurns: z.number().optional(),
|
|
22
|
+
skills: z.array(z.string()).optional(),
|
|
23
|
+
// Resume signal. When true, the bridge passes `{ continue: true }` to the
|
|
24
|
+
// Claude SDK so the in-workdir thread state is rehydrated. The host sets this
|
|
25
|
+
// on the first prompt after a cross-process resume.
|
|
26
|
+
continue: z.boolean().optional(),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export type StartMessage = z.infer<typeof startMessageSchema>;
|
|
30
|
+
|
|
31
|
+
export const inboundMessageSchema = z.discriminatedUnion('type', [
|
|
32
|
+
startMessageSchema,
|
|
33
|
+
...harnessV1BridgeInboundCommandSchemas,
|
|
34
|
+
]);
|
|
35
|
+
export type InboundMessage = z.infer<typeof inboundMessageSchema>;
|
|
36
|
+
|
|
37
|
+
export const bridgeReadySchema = harnessV1BridgeReadySchema;
|
|
38
|
+
export type BridgeReady = z.infer<typeof bridgeReadySchema>;
|