copperhead 0.5.0 → 0.7.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/README.md +34 -1
- package/dist/agent/loop.js +130 -15
- package/dist/agent/loop.js.map +1 -1
- package/dist/agent/prompts.js +2 -1
- package/dist/agent/prompts.js.map +1 -1
- package/dist/agent/providers/claude-code.js +466 -0
- package/dist/agent/providers/claude-code.js.map +1 -0
- package/dist/agent/providers/openai.js +30 -10
- package/dist/agent/providers/openai.js.map +1 -1
- package/dist/agent/recovery.js +148 -0
- package/dist/agent/recovery.js.map +1 -0
- package/dist/agent/render.js +17 -2
- package/dist/agent/render.js.map +1 -1
- package/dist/agent/response-cache.js +81 -0
- package/dist/agent/response-cache.js.map +1 -0
- package/dist/agent/tools.js +61 -4
- package/dist/agent/tools.js.map +1 -1
- package/dist/agent/transcript.js.map +1 -1
- package/dist/cli.js +47 -2
- package/dist/cli.js.map +1 -1
- package/dist/commands/create.js +486 -35
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/export.js +90 -0
- package/dist/commands/export.js.map +1 -0
- package/dist/config.js +33 -6
- package/dist/config.js.map +1 -1
- package/dist/kicad/bom-export.js +240 -0
- package/dist/kicad/bom-export.js.map +1 -0
- package/dist/kicad/bootstrap.js +166 -0
- package/dist/kicad/bootstrap.js.map +1 -0
- package/dist/kicad/fab.js +94 -0
- package/dist/kicad/fab.js.map +1 -0
- package/dist/kicad/spice.js +306 -0
- package/dist/kicad/spice.js.map +1 -0
- package/dist/kicad/symlib.js +228 -0
- package/dist/kicad/symlib.js.map +1 -0
- package/dist/memory/bom-table.js +232 -0
- package/dist/memory/bom-table.js.map +1 -0
- package/dist/memory/drift.js +33 -27
- package/dist/memory/drift.js.map +1 -1
- package/dist/util/git.js +37 -1
- package/dist/util/git.js.map +1 -1
- package/dist/util/preflight.js +37 -0
- package/dist/util/preflight.js.map +1 -1
- package/dist/util/retry.js +23 -0
- package/dist/util/retry.js.map +1 -1
- package/dist/util/tmp.js +119 -0
- package/dist/util/tmp.js.map +1 -0
- package/package.json +6 -2
- package/src/agent/loop.ts +148 -15
- package/src/agent/prompts.ts +2 -1
- package/src/agent/providers/claude-code.ts +550 -0
- package/src/agent/providers/openai.ts +33 -16
- package/src/agent/recovery.ts +162 -0
- package/src/agent/render.ts +28 -1
- package/src/agent/response-cache.ts +80 -0
- package/src/agent/tools.ts +62 -4
- package/src/agent/transcript.ts +1 -0
- package/src/agent/types.ts +18 -0
- package/src/cli.ts +52 -2
- package/src/commands/create.ts +543 -38
- package/src/commands/export.ts +117 -0
- package/src/config.ts +54 -6
- package/src/kicad/bom-export.ts +321 -0
- package/src/kicad/bootstrap.ts +181 -0
- package/src/kicad/fab.ts +121 -0
- package/src/kicad/spice.ts +399 -0
- package/src/kicad/symlib.ts +248 -0
- package/src/memory/bom-table.ts +249 -0
- package/src/memory/drift.ts +42 -32
- package/src/util/git.ts +37 -1
- package/src/util/preflight.ts +44 -0
- package/src/util/retry.ts +29 -0
- package/src/util/tmp.ts +113 -0
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
import { mkdtemp, rm, utimes } from 'node:fs/promises';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
/** Explicit deny list, belt-and-suspenders on top of the empty `tools` allowlist.
|
|
5
|
+
* `'*'` is the documented wildcard; the named entries stay for clarity and in
|
|
6
|
+
* case a given SDK build does not honor the wildcard. */
|
|
7
|
+
const DISALLOWED_BUILTINS = [
|
|
8
|
+
'*',
|
|
9
|
+
'Bash',
|
|
10
|
+
'Edit',
|
|
11
|
+
'MultiEdit',
|
|
12
|
+
'Write',
|
|
13
|
+
'Read',
|
|
14
|
+
'Glob',
|
|
15
|
+
'Grep',
|
|
16
|
+
'NotebookEdit',
|
|
17
|
+
'WebFetch',
|
|
18
|
+
'WebSearch',
|
|
19
|
+
'Task',
|
|
20
|
+
'TodoWrite',
|
|
21
|
+
];
|
|
22
|
+
export class ClaudeCodeProvider {
|
|
23
|
+
model;
|
|
24
|
+
injectedQuery;
|
|
25
|
+
importSdk;
|
|
26
|
+
sessionResume;
|
|
27
|
+
name = 'claude-code';
|
|
28
|
+
callSeq = 0;
|
|
29
|
+
cwdPromise;
|
|
30
|
+
/** In-flight query aborters, so close() (called by the turn watchdog on a
|
|
31
|
+
* hung turn) can tear down the live subprocess, not just delete its cwd. */
|
|
32
|
+
inFlight = new Set();
|
|
33
|
+
/** Session-resume state (1.1). `sessionId` is the last session the SDK reported;
|
|
34
|
+
* `sentCount` is how many `messages` we have already handed it, so a resumed
|
|
35
|
+
* turn sends only the delta. Unused unless `sessionResume` is on. */
|
|
36
|
+
sessionId;
|
|
37
|
+
sentCount = 0;
|
|
38
|
+
constructor(model, injectedQuery, importSdk = (specifier) => import(specifier),
|
|
39
|
+
/**
|
|
40
|
+
* Opt-in: resume one SDK session across turns and send only new messages,
|
|
41
|
+
* instead of flattening and re-sending the entire conversation every turn
|
|
42
|
+
* (1.1). Cuts the ~quadratic history re-send that dominates long-stage cost.
|
|
43
|
+
* OFF by default and deliberately mutually exclusive with the response cache:
|
|
44
|
+
* the cache replays turns the resumed session never saw, so mixing them would
|
|
45
|
+
* desync the session. `makeProvider` enables it only when the cache is off.
|
|
46
|
+
*/
|
|
47
|
+
sessionResume = false) {
|
|
48
|
+
this.model = model;
|
|
49
|
+
this.injectedQuery = injectedQuery;
|
|
50
|
+
this.importSdk = importSdk;
|
|
51
|
+
this.sessionResume = sessionResume;
|
|
52
|
+
}
|
|
53
|
+
// `opts.maxTokens` is intentionally ignored: the Agent SDK drives the Claude
|
|
54
|
+
// Code subprocess and exposes no per-call max-tokens knob. `opts.onStream` is
|
|
55
|
+
// honored: this provider streams, so it reports cumulative streamed-text length
|
|
56
|
+
// as blocks arrive, which the loop turns into a liveness heartbeat (5.1).
|
|
57
|
+
async chat(messages, tools, opts = {}) {
|
|
58
|
+
const query = await this.resolveQuery();
|
|
59
|
+
const system = messages
|
|
60
|
+
.filter((m) => m.role === 'system')
|
|
61
|
+
.map((m) => m.content)
|
|
62
|
+
.join('\n\n');
|
|
63
|
+
const systemPrompt = [system, renderToolProtocol(tools)].filter(Boolean).join('\n\n');
|
|
64
|
+
// Session-resume mode (1.1): once the SDK has given us a session id, resume it
|
|
65
|
+
// and send only the messages added since our last turn — the subprocess still
|
|
66
|
+
// holds the earlier conversation, so re-sending it would just re-bill it. The
|
|
67
|
+
// first turn (no session id yet) sends the full flattened history as usual.
|
|
68
|
+
const resume = this.sessionResume ? this.sessionId : undefined;
|
|
69
|
+
const prompt = resume ? renderDelta(messages, this.sentCount) : renderConversation(messages);
|
|
70
|
+
const catalog = new Set(tools.map((t) => t.name));
|
|
71
|
+
const cwd = await this.ensureCwd();
|
|
72
|
+
let text = null;
|
|
73
|
+
let inputTokens = 0;
|
|
74
|
+
let outputTokens = 0;
|
|
75
|
+
// One aborter per turn: close() aborts it to kill a hung subprocess.
|
|
76
|
+
const aborter = new AbortController();
|
|
77
|
+
this.inFlight.add(aborter);
|
|
78
|
+
try {
|
|
79
|
+
for await (const msg of query({
|
|
80
|
+
prompt,
|
|
81
|
+
options: {
|
|
82
|
+
systemPrompt,
|
|
83
|
+
...(this.model ? { model: this.model } : {}),
|
|
84
|
+
abortController: aborter,
|
|
85
|
+
// Layered "the SDK executes nothing" defense (D1/D5):
|
|
86
|
+
// 1. `tools: []` disables ALL built-in tools (Agent SDK 0.3.x docs:
|
|
87
|
+
// "[] (empty array) - Disable all built-in tools").
|
|
88
|
+
// 2. `disallowedTools` denies by name, with a wildcard, as a backstop.
|
|
89
|
+
// 3. `canUseTool` denies every tool BEFORE it runs — the permission
|
|
90
|
+
// analog to Codex's read-only sandbox — so even an unrecognized
|
|
91
|
+
// future tool cannot execute.
|
|
92
|
+
// 4. The tool_use tripwire below fails the run loudly if one is
|
|
93
|
+
// emitted anyway. Any single layer failing is caught by the next.
|
|
94
|
+
tools: [],
|
|
95
|
+
...(resume ? { resume } : {}),
|
|
96
|
+
disallowedTools: DISALLOWED_BUILTINS,
|
|
97
|
+
canUseTool: async (toolName) => ({
|
|
98
|
+
behavior: 'deny',
|
|
99
|
+
message: `copperhead claude-code is reasoning-only; the SDK must not execute tools (blocked ${toolName}).`,
|
|
100
|
+
interrupt: true,
|
|
101
|
+
}),
|
|
102
|
+
cwd,
|
|
103
|
+
// The SDK's `env` REPLACES the subprocess environment entirely, so
|
|
104
|
+
// inherit process.env and strip the billed API keys: a claude-code run
|
|
105
|
+
// must use the saved login and never silently a paid ANTHROPIC_API_KEY
|
|
106
|
+
// / OPENAI_API_KEY, even when one is also set (D2).
|
|
107
|
+
env: { ...process.env, ANTHROPIC_API_KEY: undefined, OPENAI_API_KEY: undefined },
|
|
108
|
+
maxTurns: 1,
|
|
109
|
+
},
|
|
110
|
+
})) {
|
|
111
|
+
if (msg.type === 'assistant') {
|
|
112
|
+
for (const block of msg.message?.content ?? []) {
|
|
113
|
+
if (block.type === 'text' && block.text) {
|
|
114
|
+
text = (text ?? '') + block.text;
|
|
115
|
+
// Report progress so the loop's heartbeat shows this turn is alive
|
|
116
|
+
// and streaming, not hung, during a multi-minute large-output turn.
|
|
117
|
+
opts.onStream?.(text.length);
|
|
118
|
+
}
|
|
119
|
+
else if (block.type === 'tool_use') {
|
|
120
|
+
// Load-bearing invariant (D1): the SDK must execute nothing, so it
|
|
121
|
+
// must never emit a tool_use block. If it does, `tools: []` was not
|
|
122
|
+
// honored — fail loudly rather than let an edit bypass copperhead's
|
|
123
|
+
// snapshot / verify / commit gates.
|
|
124
|
+
throw new Error('claude-code: the Agent SDK emitted a tool_use block, but its tools are ' +
|
|
125
|
+
'disabled — the reasoning-only invariant was violated (SDK option drift?). ' +
|
|
126
|
+
'Refusing to continue.');
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
else if (msg.type === 'result') {
|
|
131
|
+
if (typeof msg.usage?.input_tokens === 'number')
|
|
132
|
+
inputTokens = msg.usage.input_tokens;
|
|
133
|
+
if (typeof msg.usage?.output_tokens === 'number')
|
|
134
|
+
outputTokens = msg.usage.output_tokens;
|
|
135
|
+
}
|
|
136
|
+
// The session id can arrive on any message (init/system/result); keep the
|
|
137
|
+
// latest so the next turn can resume it (1.1). No-op unless resume is on.
|
|
138
|
+
if (this.sessionResume && typeof msg.session_id === 'string')
|
|
139
|
+
this.sessionId = msg.session_id;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
// Auth failures get an actionable message (non-retryable); everything else
|
|
144
|
+
// — crucially a 429 — is re-thrown untouched so its status survives for
|
|
145
|
+
// withRetry/isRateLimit (D4). We never fall back to a keyed provider: the
|
|
146
|
+
// distinct `name` makes otherProvider() return null for us.
|
|
147
|
+
if (isAuthError(err))
|
|
148
|
+
throw new Error(authHint(err.message));
|
|
149
|
+
throw err;
|
|
150
|
+
}
|
|
151
|
+
finally {
|
|
152
|
+
this.inFlight.delete(aborter);
|
|
153
|
+
}
|
|
154
|
+
// Only advance the high-water mark on a turn that completed: a thrown turn
|
|
155
|
+
// (rate limit, timeout) is retried, and must re-send the same delta so no
|
|
156
|
+
// message is lost from the resumed session (1.1).
|
|
157
|
+
if (this.sessionResume)
|
|
158
|
+
this.sentCount = messages.length;
|
|
159
|
+
const parsed = parseToolCalls(text, () => `cc-${++this.callSeq}`, catalog);
|
|
160
|
+
return {
|
|
161
|
+
text: parsed.text,
|
|
162
|
+
toolCalls: parsed.toolCalls,
|
|
163
|
+
usage: { inputTokens, outputTokens },
|
|
164
|
+
nudge: parsed.nudge,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
/** Tear down in-flight work and remove the scratch cwd. Called by the turn
|
|
168
|
+
* watchdog on a hung turn (via withTimeout's onTimeout) AND once per run in a
|
|
169
|
+
* finally. Aborting first kills the `claude` subprocess a hung turn spawned —
|
|
170
|
+
* without it the process is orphaned and keeps writing to its temp cwd, which
|
|
171
|
+
* (with KiCad local history) was a source of the disk-fill halt (2.2/4.1, I8).
|
|
172
|
+
* A leftover empty dir in the OS tmpdir is harmless; the startup sweep reclaims
|
|
173
|
+
* any that a hard SIGKILL bypassed this cleanup for. */
|
|
174
|
+
async close() {
|
|
175
|
+
for (const aborter of this.inFlight) {
|
|
176
|
+
try {
|
|
177
|
+
aborter.abort();
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
// best effort: a controller that already settled throws nothing useful
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
this.inFlight.clear();
|
|
184
|
+
const pending = this.cwdPromise;
|
|
185
|
+
this.cwdPromise = undefined;
|
|
186
|
+
if (!pending)
|
|
187
|
+
return;
|
|
188
|
+
try {
|
|
189
|
+
await rm(await pending, { recursive: true, force: true });
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
// best effort: a leftover empty dir in the OS tmpdir is harmless
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
/** One isolated scratch cwd per provider instance, created once and reused
|
|
196
|
+
* across turns so a long run does not leak a temp dir per turn. Even with
|
|
197
|
+
* tools disabled this guarantees the SDK has no path into the repo (D5). */
|
|
198
|
+
async ensureCwd() {
|
|
199
|
+
if (!this.cwdPromise)
|
|
200
|
+
this.cwdPromise = mkdtemp(path.join(os.tmpdir(), 'copperhead-cc-'));
|
|
201
|
+
const cwd = await this.cwdPromise;
|
|
202
|
+
// Keep this reused scratch dir's mtime fresh on every turn. It is the only
|
|
203
|
+
// long-lived temp dir a run holds (kicad-cli dirs are per-call), so a
|
|
204
|
+
// multi-hour run would otherwise leave it with a stale mtime and a concurrent
|
|
205
|
+
// run's startup sweep (sweepStaleTempDirs, age-gated) could delete it out from
|
|
206
|
+
// under the live process (F4). Best-effort: a touch failure is harmless.
|
|
207
|
+
const now = new Date();
|
|
208
|
+
await utimes(cwd, now, now).catch(() => { });
|
|
209
|
+
return cwd;
|
|
210
|
+
}
|
|
211
|
+
async resolveQuery() {
|
|
212
|
+
if (this.injectedQuery)
|
|
213
|
+
return this.injectedQuery;
|
|
214
|
+
let mod;
|
|
215
|
+
try {
|
|
216
|
+
// importSdk defaults to a non-literal `import()`, so tsc never resolves the
|
|
217
|
+
// optional dependency at build time and it may legitimately be absent (D3).
|
|
218
|
+
mod = (await this.importSdk('@anthropic-ai/claude-agent-sdk'));
|
|
219
|
+
}
|
|
220
|
+
catch (err) {
|
|
221
|
+
// Only a genuinely-absent module gets the "install it" message; a present
|
|
222
|
+
// but broken install surfaces its real error rather than being mislabeled.
|
|
223
|
+
const code = err.code;
|
|
224
|
+
if (code === 'ERR_MODULE_NOT_FOUND' || code === 'MODULE_NOT_FOUND') {
|
|
225
|
+
throw new Error('the claude-code provider needs the optional dependency @anthropic-ai/claude-agent-sdk; ' +
|
|
226
|
+
'install it with `npm i @anthropic-ai/claude-agent-sdk`');
|
|
227
|
+
}
|
|
228
|
+
throw err;
|
|
229
|
+
}
|
|
230
|
+
const query = mod.query ?? mod.default?.query;
|
|
231
|
+
if (!query) {
|
|
232
|
+
throw new Error('@anthropic-ai/claude-agent-sdk did not export `query`; the installed version may be incompatible');
|
|
233
|
+
}
|
|
234
|
+
return query;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
function renderToolProtocol(tools) {
|
|
238
|
+
if (!tools.length)
|
|
239
|
+
return '';
|
|
240
|
+
const lines = [
|
|
241
|
+
'# Tool protocol',
|
|
242
|
+
'',
|
|
243
|
+
'You are the reasoning half of a tool-driven workflow; you cannot run anything yourself.',
|
|
244
|
+
'To take an action, reply with EXACTLY ONE JSON object and nothing else, wrapped in a',
|
|
245
|
+
'```json fenced code block:',
|
|
246
|
+
'',
|
|
247
|
+
'```json',
|
|
248
|
+
'{"tool": "<tool_name>", "args": { ... }}',
|
|
249
|
+
'```',
|
|
250
|
+
'',
|
|
251
|
+
'Use only the tools listed below, with `args` matching the tool\'s JSON Schema. If you have',
|
|
252
|
+
'no tool to call and only want to say something, reply with plain prose and no JSON block.',
|
|
253
|
+
'',
|
|
254
|
+
'## Available tools',
|
|
255
|
+
];
|
|
256
|
+
for (const t of tools) {
|
|
257
|
+
lines.push('', `### ${t.name}`, t.description, `Parameters (JSON Schema): ${JSON.stringify(t.parameters)}`);
|
|
258
|
+
}
|
|
259
|
+
return lines.join('\n');
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* The prompt for a *resumed* turn (1.1): only the messages added since the last
|
|
263
|
+
* turn we sent, and only the ones the resumed session does not already hold. The
|
|
264
|
+
* subprocess already has every prior turn plus its own assistant replies, so we
|
|
265
|
+
* send just the new user nudges and tool results — that delta is what advances
|
|
266
|
+
* the conversation. Falls back to the full render (via the caller) when there is
|
|
267
|
+
* no session yet.
|
|
268
|
+
*/
|
|
269
|
+
function renderDelta(messages, from) {
|
|
270
|
+
const idToName = new Map();
|
|
271
|
+
for (const m of messages) {
|
|
272
|
+
if (m.role === 'assistant')
|
|
273
|
+
for (const call of m.toolCalls ?? [])
|
|
274
|
+
idToName.set(call.id, call.name);
|
|
275
|
+
}
|
|
276
|
+
const parts = [];
|
|
277
|
+
for (const m of messages.slice(Math.max(0, from))) {
|
|
278
|
+
if (m.role === 'user') {
|
|
279
|
+
parts.push(`[user]\n${m.content}`);
|
|
280
|
+
}
|
|
281
|
+
else if (m.role === 'tool') {
|
|
282
|
+
const name = idToName.get(m.toolCallId) ?? m.toolCallId;
|
|
283
|
+
parts.push(`[result of ${name}]\n${m.content}`);
|
|
284
|
+
}
|
|
285
|
+
// assistant/system messages are already in the resumed session — skip them.
|
|
286
|
+
}
|
|
287
|
+
return parts.join('\n\n');
|
|
288
|
+
}
|
|
289
|
+
function renderConversation(messages) {
|
|
290
|
+
const idToName = new Map();
|
|
291
|
+
const parts = [];
|
|
292
|
+
for (const m of messages) {
|
|
293
|
+
if (m.role === 'system')
|
|
294
|
+
continue;
|
|
295
|
+
if (m.role === 'user') {
|
|
296
|
+
parts.push(`[user]\n${m.content}`);
|
|
297
|
+
}
|
|
298
|
+
else if (m.role === 'assistant') {
|
|
299
|
+
if (m.content)
|
|
300
|
+
parts.push(`[assistant]\n${m.content}`);
|
|
301
|
+
for (const call of m.toolCalls ?? []) {
|
|
302
|
+
idToName.set(call.id, call.name);
|
|
303
|
+
parts.push(`[assistant tool call]\n\`\`\`json\n${JSON.stringify({ tool: call.name, args: call.args })}\n\`\`\``);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
const name = idToName.get(m.toolCallId) ?? m.toolCallId;
|
|
308
|
+
parts.push(`[result of ${name}]\n${m.content}`);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return parts.join('\n\n');
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Detect a malformed-but-intended tool call in a turn that dispatched none
|
|
315
|
+
* (#I10). The signature is machine-recognizable: the text contains
|
|
316
|
+
* `"tool":"<name>"` naming a tool in the current catalog, yet nothing parsed.
|
|
317
|
+
* That is the exact case where the tolerant extractor's silence misleads the
|
|
318
|
+
* model — the JSON was near-miss malformed (a brace short, or the outer object
|
|
319
|
+
* split so only an inner `{args}` with no `tool` key balanced), not the tool
|
|
320
|
+
* being broken. Returns a one-line steer to re-emit it, or undefined when the
|
|
321
|
+
* absence of a call is genuine (plain prose, no tool named).
|
|
322
|
+
*/
|
|
323
|
+
function detectMalformedCall(text, catalog) {
|
|
324
|
+
const re = /"tool"\s*:\s*"([^"]+)"/g;
|
|
325
|
+
let m;
|
|
326
|
+
while ((m = re.exec(text)) !== null) {
|
|
327
|
+
const name = m[1];
|
|
328
|
+
if (catalog.has(name)) {
|
|
329
|
+
return (`A tool call for "${name}" looks malformed — it named the tool but did not parse as ` +
|
|
330
|
+
'valid JSON (likely unbalanced braces or a missing closing brace), so no call ran. ' +
|
|
331
|
+
'Re-emit it as exactly one complete JSON object: {"tool": "...", "args": { ... }}.');
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return undefined;
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Extract tool-call JSON from the model's reply. Tolerant by design (D1):
|
|
338
|
+
* unparseable output is returned as plain text with no tool calls rather than
|
|
339
|
+
* throwing, so a non-conforming turn degrades to the loop's stall/nudge path.
|
|
340
|
+
* A parsed block only counts as a tool call when its name is in the current
|
|
341
|
+
* turn's catalog (`availableTools(ctx)`): a hallucinated or locked tool name is
|
|
342
|
+
* left as prose so the loop nudges, rather than dispatching a bogus call.
|
|
343
|
+
*/
|
|
344
|
+
function parseToolCalls(text, nextId, catalog) {
|
|
345
|
+
if (!text)
|
|
346
|
+
return { text: null, toolCalls: [] };
|
|
347
|
+
const toolCalls = [];
|
|
348
|
+
const matched = [];
|
|
349
|
+
// Extract tool calls by scanning for complete JSON objects, NOT by matching
|
|
350
|
+
// ``` fences. A tool call's `content`/`args` can hold a full markdown doc that
|
|
351
|
+
// itself contains ``` code fences; a fence regex truncates the JSON at the
|
|
352
|
+
// first inner fence, JSON.parse fails, and the call is silently dropped (the
|
|
353
|
+
// model then assumes it wrote a file it never did). The brace scan is
|
|
354
|
+
// string-aware, so braces and backticks inside JSON string values are ignored.
|
|
355
|
+
let searchFrom = 0;
|
|
356
|
+
while (searchFrom < text.length) {
|
|
357
|
+
const braceAt = text.indexOf('{', searchFrom);
|
|
358
|
+
if (braceAt < 0)
|
|
359
|
+
break;
|
|
360
|
+
const span = scanJsonObject(text, braceAt);
|
|
361
|
+
if (!span) {
|
|
362
|
+
// Unbalanced '{' (stray brace in prose): retry from the next candidate so
|
|
363
|
+
// one bad brace can't hide a well-formed call later in the reply.
|
|
364
|
+
searchFrom = braceAt + 1;
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
const call = toToolCall(text.slice(span.start, span.end), nextId, catalog);
|
|
368
|
+
if (call) {
|
|
369
|
+
toolCalls.push(call);
|
|
370
|
+
matched.push([span.start, span.end]);
|
|
371
|
+
}
|
|
372
|
+
searchFrom = span.end;
|
|
373
|
+
}
|
|
374
|
+
if (!toolCalls.length) {
|
|
375
|
+
// No call dispatched — but did the model clearly *intend* one? A fenced
|
|
376
|
+
// ```json block that names a catalog tool yet produced zero calls is a
|
|
377
|
+
// malformed near-miss (unbalanced braces, a missing `}`, or an inner object
|
|
378
|
+
// with no `tool` key). Silently dropping it gives the model no signal, so it
|
|
379
|
+
// misreads "no result" as "this tool is broken" and can bake that false
|
|
380
|
+
// conclusion into a committed summary (#I10). Surface a nudge instead.
|
|
381
|
+
return { text: text.trim() ? text : null, toolCalls, nudge: detectMalformedCall(text, catalog) };
|
|
382
|
+
}
|
|
383
|
+
// Prose is whatever survives once the tool-call objects (and any now-empty
|
|
384
|
+
// ```json fences around them) are removed.
|
|
385
|
+
let prose = '';
|
|
386
|
+
let cursor = 0;
|
|
387
|
+
for (const [start, end] of matched) {
|
|
388
|
+
prose += text.slice(cursor, start);
|
|
389
|
+
cursor = end;
|
|
390
|
+
}
|
|
391
|
+
prose += text.slice(cursor);
|
|
392
|
+
prose = prose.replace(/```(?:json)?\s*```/gi, '').replace(/```(?:json)?\s*$/gi, '').trim();
|
|
393
|
+
return { text: prose.length ? prose : null, toolCalls };
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Find the first complete, brace-balanced JSON object at or after `from`,
|
|
397
|
+
* respecting JSON string quoting/escaping so braces or backticks inside string
|
|
398
|
+
* values do not end the scan. Returns its `[start, end)` bounds or null.
|
|
399
|
+
*/
|
|
400
|
+
function scanJsonObject(text, from) {
|
|
401
|
+
const start = text.indexOf('{', from);
|
|
402
|
+
if (start < 0)
|
|
403
|
+
return null;
|
|
404
|
+
let depth = 0;
|
|
405
|
+
let inStr = false;
|
|
406
|
+
let esc = false;
|
|
407
|
+
for (let i = start; i < text.length; i++) {
|
|
408
|
+
const ch = text[i];
|
|
409
|
+
if (inStr) {
|
|
410
|
+
if (esc)
|
|
411
|
+
esc = false;
|
|
412
|
+
else if (ch === '\\')
|
|
413
|
+
esc = true;
|
|
414
|
+
else if (ch === '"')
|
|
415
|
+
inStr = false;
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
if (ch === '"')
|
|
419
|
+
inStr = true;
|
|
420
|
+
else if (ch === '{')
|
|
421
|
+
depth++;
|
|
422
|
+
else if (ch === '}' && --depth === 0)
|
|
423
|
+
return { start, end: i + 1 };
|
|
424
|
+
}
|
|
425
|
+
return null;
|
|
426
|
+
}
|
|
427
|
+
function toToolCall(raw, nextId, catalog) {
|
|
428
|
+
if (!raw)
|
|
429
|
+
return null;
|
|
430
|
+
let obj;
|
|
431
|
+
try {
|
|
432
|
+
obj = JSON.parse(raw.trim());
|
|
433
|
+
}
|
|
434
|
+
catch {
|
|
435
|
+
return null;
|
|
436
|
+
}
|
|
437
|
+
if (!obj || typeof obj !== 'object')
|
|
438
|
+
return null;
|
|
439
|
+
const rec = obj;
|
|
440
|
+
if (typeof rec.tool !== 'string')
|
|
441
|
+
return null;
|
|
442
|
+
// Only accept names the turn actually advertised. An empty catalog means the
|
|
443
|
+
// turn offered no tools, so nothing parses as a call.
|
|
444
|
+
if (!catalog.has(rec.tool))
|
|
445
|
+
return null;
|
|
446
|
+
const args = rec.args && typeof rec.args === 'object' ? rec.args : {};
|
|
447
|
+
return { id: nextId(), name: rec.tool, args };
|
|
448
|
+
}
|
|
449
|
+
function isAuthError(err) {
|
|
450
|
+
const status = err?.status
|
|
451
|
+
?? err?.statusCode;
|
|
452
|
+
// A present status is authoritative: only 401/403 are auth failures, so a 429
|
|
453
|
+
// (or anything else) is NOT treated as auth and is re-thrown untouched — its
|
|
454
|
+
// status must survive for withRetry/isRateLimit even if the message mentions
|
|
455
|
+
// "oauth token". Only when there is no status do we fall back to a narrow
|
|
456
|
+
// message heuristic.
|
|
457
|
+
if (typeof status === 'number')
|
|
458
|
+
return status === 401 || status === 403;
|
|
459
|
+
const m = (err?.message ?? '').toLowerCase();
|
|
460
|
+
return /unauthenticat|unauthoriz|not logged in|please log in|invalid api key|oauth token|setup-token/.test(m);
|
|
461
|
+
}
|
|
462
|
+
function authHint(detail) {
|
|
463
|
+
return ('claude-code is not authenticated: log in to Claude Code, or run `claude setup-token` and ' +
|
|
464
|
+
`set CLAUDE_CODE_OAUTH_TOKEN. copperhead never reads your credential itself (original error: ${detail})`);
|
|
465
|
+
}
|
|
466
|
+
//# sourceMappingURL=claude-code.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-code.js","sourceRoot":"","sources":["../../../src/agent/providers/claude-code.ts"],"names":[],"mappings":"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"}
|
|
@@ -26,11 +26,7 @@ export class OpenAIProvider {
|
|
|
26
26
|
content: m.content,
|
|
27
27
|
...(m.toolCalls?.length
|
|
28
28
|
? {
|
|
29
|
-
tool_calls: m.toolCalls.map(
|
|
30
|
-
id: t.id,
|
|
31
|
-
type: 'function',
|
|
32
|
-
function: { name: t.name, arguments: JSON.stringify(t.args) },
|
|
33
|
-
})),
|
|
29
|
+
tool_calls: m.toolCalls.map(serializeToolCall),
|
|
34
30
|
}
|
|
35
31
|
: {}),
|
|
36
32
|
};
|
|
@@ -48,11 +44,10 @@ export class OpenAIProvider {
|
|
|
48
44
|
: {}),
|
|
49
45
|
});
|
|
50
46
|
const choice = res.choices[0];
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}));
|
|
47
|
+
// Capture any non-standard properties returned by the API (e.g. Gemini thought
|
|
48
|
+
// signatures) so they can be echoed back on subsequent turns. Dropping them
|
|
49
|
+
// causes reasoning-model backends to reject the follow-up request with 400.
|
|
50
|
+
const toolCalls = (choice?.message.tool_calls ?? []).map(parseToolCall);
|
|
56
51
|
return {
|
|
57
52
|
text: choice?.message.content ?? null,
|
|
58
53
|
toolCalls,
|
|
@@ -71,4 +66,29 @@ function safeParse(s) {
|
|
|
71
66
|
return { _raw: s };
|
|
72
67
|
}
|
|
73
68
|
}
|
|
69
|
+
export function serializeToolCall(t) {
|
|
70
|
+
return {
|
|
71
|
+
id: t.id,
|
|
72
|
+
type: 'function',
|
|
73
|
+
function: { name: t.name, arguments: JSON.stringify(t.args) },
|
|
74
|
+
// Preserve vendor-specific tool-call fields (e.g. Gemini thought signatures).
|
|
75
|
+
// Dropping them makes the next turn's request 400.
|
|
76
|
+
...(t.extra || {}),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
export function parseToolCall(t) {
|
|
80
|
+
const extra = {};
|
|
81
|
+
for (const [k, v] of Object.entries(t)) {
|
|
82
|
+
if (k !== 'id' && k !== 'type' && k !== 'function') {
|
|
83
|
+
extra[k] = v;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const fn = t.function;
|
|
87
|
+
return {
|
|
88
|
+
id: t.id,
|
|
89
|
+
name: fn.name,
|
|
90
|
+
args: safeParse(fn.arguments),
|
|
91
|
+
...(Object.keys(extra).length ? { extra } : {}),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
74
94
|
//# sourceMappingURL=openai.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openai.js","sourceRoot":"","sources":["../../../src/agent/providers/openai.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"openai.js","sourceRoot":"","sources":["../../../src/agent/providers/openai.ts"],"names":[],"mappings":"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"}
|