@agentrific/server 0.6.0 → 0.7.1

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.
@@ -0,0 +1,2682 @@
1
+ // ../../node_modules/pi-claude-agent-sdk/src/index.ts
2
+ import { createAssistantMessageEventStream } from "@earendil-works/pi-ai";
3
+
4
+ import { getModels } from "@earendil-works/pi-ai/compat";
5
+
6
+ import { query } from "@anthropic-ai/claude-agent-sdk";
7
+
8
+ import { createSession, deleteSession, openSession, repairToolPairing } from "cc-session-io";
9
+
10
+ import { appendFileSync, mkdirSync as mkdirSync2, realpathSync, statSync as statSync2 } from "fs";
11
+
12
+ import { dirname as dirname3, join as join4 } from "path";
13
+
14
+
15
+ // ../../node_modules/pi-claude-agent-sdk/src/convert.ts
16
+ import { pascalCase } from "change-case";
17
+
18
+ import { createHash } from "node:crypto";
19
+
20
+
21
+ // ../../node_modules/pi-claude-agent-sdk/src/skills.ts
22
+ import { formatSkillsForPrompt } from "@earendil-works/pi-coding-agent";
23
+
24
+
25
+ // ../../node_modules/pi-claude-agent-sdk/src/log-paths.ts
26
+ import { getAgentDir } from "@earendil-works/pi-coding-agent";
27
+
28
+ import { join } from "path";
29
+
30
+
31
+ // ../../node_modules/pi-claude-agent-sdk/src/session-verify.ts
32
+ import { statSync, readFileSync } from "fs";
33
+
34
+
35
+ // ../../node_modules/pi-claude-agent-sdk/src/config.ts
36
+ import { CONFIG_DIR_NAME, getAgentDir as getAgentDir2 } from "@earendil-works/pi-coding-agent";
37
+
38
+ import { existsSync, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
39
+
40
+ import { dirname, join as join2 } from "path";
41
+
42
+
43
+ // ../../node_modules/pi-claude-agent-sdk/src/mcp-server.ts
44
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
45
+
46
+ import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
47
+
48
+
49
+ // ../../node_modules/pi-claude-agent-sdk/src/claude-executable.ts
50
+ import { spawnSync } from "node:child_process";
51
+
52
+ import { accessSync, constants, readFileSync as readFileSync3 } from "node:fs";
53
+
54
+ import { createRequire } from "node:module";
55
+
56
+ import { delimiter, dirname as dirname2, join as join3 } from "node:path";
57
+
58
+
59
+ // ../../node_modules/pi-claude-agent-sdk/src/transcript.ts
60
+ import {
61
+ contentText,
62
+ getCurrentSystemMessage,
63
+ getCurrentTools
64
+ } from "@earendil-works/pi-ai";
65
+
66
+
67
+ // ../../node_modules/pi-claude-agent-sdk/src/usage.ts
68
+ import { calculateCost } from "@earendil-works/pi-ai";
69
+ export function createClaudeExtension(queryImplementation) {
70
+ const bridgeGlobals = {};
71
+
72
+ var MCP_SERVER_NAME = "custom-tools";
73
+
74
+ var MCP_TOOL_PREFIX = `mcp__${MCP_SERVER_NAME}__`;
75
+
76
+ function renderSkillsBlock(skills, readTool) {
77
+ if (readTool === "none" || skills.length === 0) return void 0;
78
+ const block = formatSkillsForPrompt(skills).trim();
79
+ if (!block) return void 0;
80
+ return readTool === "mcp" ? rewriteSkillsBlock(block) : block;
81
+ }
82
+
83
+ function rewriteSkillsBlock(skillsBlock) {
84
+ return skillsBlock.replace(
85
+ "Use the read tool to load a skill's file",
86
+ `Use the read tool (mcp__${MCP_SERVER_NAME}__read) to load a skill's file`
87
+ );
88
+ }
89
+
90
+
91
+ // ../../node_modules/pi-claude-agent-sdk/src/convert.ts
92
+ var PROVIDER_ID = "claude-bridge";
93
+
94
+ var PI_TO_SDK_TOOL_NAME = {
95
+ read: "Read",
96
+ write: "Write",
97
+ edit: "Edit",
98
+ bash: "Bash"
99
+ };
100
+
101
+ var MAX_ANTHROPIC_TOOL_ID_LENGTH = 64;
102
+
103
+ function sanitizeToolId(id, cache) {
104
+ const existing = cache.get(id);
105
+ if (existing) return existing;
106
+ const sanitized = id.replace(/[^a-zA-Z0-9_-]/g, "_") || "tool";
107
+ let clean = sanitized.slice(0, MAX_ANTHROPIC_TOOL_ID_LENGTH);
108
+ const occupied = new Set(cache.values());
109
+ if (occupied.has(clean)) {
110
+ const suffix = `_${createHash("sha256").update(id).digest("hex").slice(0, 12)}`;
111
+ clean = `${sanitized.slice(0, MAX_ANTHROPIC_TOOL_ID_LENGTH - suffix.length)}${suffix}`;
112
+ let attempt = 2;
113
+ while (occupied.has(clean)) {
114
+ const numberedSuffix = `${suffix}_${attempt++}`;
115
+ clean = `${sanitized.slice(0, MAX_ANTHROPIC_TOOL_ID_LENGTH - numberedSuffix.length)}${numberedSuffix}`;
116
+ }
117
+ }
118
+ cache.set(id, clean);
119
+ return clean;
120
+ }
121
+
122
+ function mapPiToolNameToSdk(name, customToolNameToSdk) {
123
+ if (!name) return "";
124
+ const normalized = name.toLowerCase();
125
+ if (normalized.startsWith(MCP_TOOL_PREFIX)) {
126
+ throw new Error(`mapPiToolNameToSdk: "${name}" is already an SDK tool name \u2014 pi history holds pi tool names`);
127
+ }
128
+ if (!customToolNameToSdk) return PI_TO_SDK_TOOL_NAME[normalized] ?? pascalCase(name);
129
+ return customToolNameToSdk.get(name) ?? customToolNameToSdk.get(normalized) ?? `${MCP_TOOL_PREFIX}${name}`;
130
+ }
131
+
132
+ function messageContentToText(content) {
133
+ if (typeof content === "string") return content;
134
+ if (!Array.isArray(content)) return "";
135
+ const parts = [];
136
+ let hasText = false;
137
+ for (const block of content) {
138
+ if (block.type === "text" && block.text) {
139
+ parts.push(block.text);
140
+ hasText = true;
141
+ } else if (block.type !== "text" && block.type !== "image") {
142
+ parts.push(`[${block.type}]`);
143
+ }
144
+ }
145
+ return hasText ? parts.join("\n") : "";
146
+ }
147
+
148
+ function toolResultContent(content) {
149
+ if (typeof content === "string" || !Array.isArray(content)) return messageContentToText(content) || "";
150
+ const images = content.filter((b) => b.type === "image" && b.data && b.mimeType);
151
+ if (!images.length) return messageContentToText(content) || "";
152
+ const blocks = [];
153
+ for (const block of content) {
154
+ if (block.type === "text" && block.text) blocks.push({ type: "text", text: block.text });
155
+ else if (block.type === "image" && block.data && block.mimeType) {
156
+ blocks.push({ type: "image", source: { type: "base64", media_type: block.mimeType, data: block.data } });
157
+ } else if (block.type !== "text" && block.type !== "image") {
158
+ blocks.push({ type: "text", text: `[${block.type}]` });
159
+ }
160
+ }
161
+ return blocks;
162
+ }
163
+
164
+ function convertPiMessages(messages, customToolNameToSdk, dropThinking = false) {
165
+ const anthropicMessages = [];
166
+ const sanitizedIds = /* @__PURE__ */ new Map();
167
+ const dropped = { thinking: 0, abortedTurns: 0, providers: /* @__PURE__ */ new Set(), other: /* @__PURE__ */ new Map() };
168
+ let turnResults = null;
169
+ let turnAssistantIdx = null;
170
+ for (const msg of messages) {
171
+ if (msg.role === "user") {
172
+ if (typeof msg.content === "string") {
173
+ anthropicMessages.push({ role: "user", content: msg.content || "[empty]" });
174
+ } else if (Array.isArray(msg.content)) {
175
+ const parts = [];
176
+ for (const block of msg.content) {
177
+ if (block.type === "text" && block.text) parts.push({ type: "text", text: block.text });
178
+ else if (block.type === "image" && block.data && block.mimeType) {
179
+ parts.push({ type: "image", source: { type: "base64", media_type: block.mimeType, data: block.data } });
180
+ }
181
+ }
182
+ anthropicMessages.push({ role: "user", content: parts.length ? parts : "[image]" });
183
+ } else {
184
+ anthropicMessages.push({ role: "user", content: "[empty]" });
185
+ }
186
+ } else if (msg.role === "assistant") {
187
+ const content = Array.isArray(msg.content) ? msg.content : [];
188
+ const blocks = [];
189
+ for (const block of content) {
190
+ if (block.type === "text" && block.text) {
191
+ blocks.push({ type: "text", text: block.text });
192
+ } else if (block.type === "thinking") {
193
+ const sig = block.thinkingSignature;
194
+ if (!dropThinking && msg.provider === PROVIDER_ID && sig) {
195
+ blocks.push({ type: "thinking", thinking: block.thinking ?? "", signature: sig });
196
+ } else {
197
+ dropped.thinking++;
198
+ dropped.providers.add(msg.provider ?? "unknown");
199
+ }
200
+ } else if (block.type === "toolCall") {
201
+ const toolName = mapPiToolNameToSdk(block.name, customToolNameToSdk);
202
+ blocks.push({ type: "tool_use", id: sanitizeToolId(block.id, sanitizedIds), name: toolName, input: block.arguments ?? {} });
203
+ } else {
204
+ dropped.other.set(block.type, (dropped.other.get(block.type) ?? 0) + 1);
205
+ }
206
+ }
207
+ if (!content.length) {
208
+ dropped.abortedTurns++;
209
+ continue;
210
+ }
211
+ if (!blocks.length) blocks.push({ type: "text", text: "[incompatible content omitted]" });
212
+ turnResults = null;
213
+ turnAssistantIdx = anthropicMessages.length;
214
+ anthropicMessages.push({ role: "assistant", content: blocks });
215
+ } else if (msg.role === "toolResult") {
216
+ const block = { type: "tool_result", tool_use_id: sanitizeToolId(msg.toolCallId, sanitizedIds), content: toolResultContent(msg.content), is_error: msg.isError };
217
+ if (turnResults) {
218
+ turnResults.content.push(block);
219
+ } else {
220
+ turnResults = { role: "user", content: [block] };
221
+ anthropicMessages.splice(turnAssistantIdx === null ? anthropicMessages.length : turnAssistantIdx + 1, 0, turnResults);
222
+ }
223
+ }
224
+ }
225
+ return { anthropicMessages, sanitizedIds, dropped };
226
+ }
227
+
228
+ var DEBUG_LOG_PATH = process.env.CLAUDE_BRIDGE_DEBUG_PATH || join(getAgentDir(), "claude-bridge.log");
229
+
230
+ var DIAG_LOG_PATH = join(getAgentDir(), "claude-bridge-diag.log");
231
+
232
+
233
+ // ../../node_modules/pi-claude-agent-sdk/src/models.ts
234
+ var TWO_HUNDRED_K_CONTEXT = 2e5;
235
+
236
+ var ONE_M_CONTEXT = 1e6;
237
+
238
+ function isDatedAlias(id) {
239
+ return /-20\d{6}$/.test(id);
240
+ }
241
+
242
+ var FAMILY_ORDER = ["fable", "opus", "sonnet", "haiku"];
243
+
244
+ function versionRank(id) {
245
+ const [, family, major, minor] = id.split("-");
246
+ return { family, tuple: [Number(major) || 0, Number(minor) || 0] };
247
+ }
248
+
249
+ function buildModels(piAiModels) {
250
+ return piAiModels.filter((m) => typeof m.id === "string" && !isDatedAlias(m.id)).sort((a, b) => {
251
+ const fa = FAMILY_ORDER.indexOf(versionRank(a.id).family);
252
+ const fb = FAMILY_ORDER.indexOf(versionRank(b.id).family);
253
+ const ta = fa === -1 ? FAMILY_ORDER.length : fa;
254
+ const tb = fb === -1 ? FAMILY_ORDER.length : fb;
255
+ if (ta !== tb) return ta - tb;
256
+ const ra = versionRank(a.id).tuple;
257
+ const rb = versionRank(b.id).tuple;
258
+ if (ra[0] !== rb[0]) return rb[0] - ra[0];
259
+ if (ra[1] !== rb[1]) return rb[1] - ra[1];
260
+ return a.id.localeCompare(b.id);
261
+ }).map(({ id, name, reasoning, input, contextWindow, maxTokens, thinkingLevelMap }) => ({
262
+ id,
263
+ name,
264
+ reasoning,
265
+ input,
266
+ contextWindow,
267
+ maxTokens,
268
+ thinkingLevelMap,
269
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }
270
+ }));
271
+ }
272
+
273
+ var MEASURED_ONE_M = /* @__PURE__ */ new Set([
274
+ "claude-fable-5",
275
+ "claude-fable-5-1",
276
+ "claude-opus-5-5",
277
+ "claude-opus-5",
278
+ "claude-opus-4-8",
279
+ "claude-opus-4-7",
280
+ "claude-sonnet-5",
281
+ "claude-sonnet-5-5"
282
+ ]);
283
+
284
+ var PLAN_GATED_ONE_M = {
285
+ // [1m] measured 1M on Max plan / extra usage; 429 on Pro without it.
286
+ "claude-opus-4-6": (settings) => settings.plan === "max" || settings.longContextExtraUsage,
287
+ // [1m] measured 1M with extra usage only.
288
+ "claude-sonnet-4-6": (settings) => settings.longContextExtraUsage
289
+ };
290
+
291
+ function resolveClaudeCodeRuntimeModel(model, settings) {
292
+ const modelId = model.id;
293
+ if (settings.forceTwoHundredK?.includes(modelId)) {
294
+ return { cliModelId: modelId, contextWindow: TWO_HUNDRED_K_CONTEXT };
295
+ }
296
+ if (MEASURED_ONE_M.has(modelId)) {
297
+ return { cliModelId: `${modelId}[1m]`, contextWindow: ONE_M_CONTEXT };
298
+ }
299
+ const planGate = PLAN_GATED_ONE_M[modelId];
300
+ if (planGate) {
301
+ const useOneM = planGate(settings);
302
+ return {
303
+ cliModelId: useOneM ? `${modelId}[1m]` : modelId,
304
+ contextWindow: useOneM ? ONE_M_CONTEXT : TWO_HUNDRED_K_CONTEXT
305
+ };
306
+ }
307
+ return { cliModelId: modelId, contextWindow: TWO_HUNDRED_K_CONTEXT };
308
+ }
309
+
310
+ function claudeCodeModelId(model, settings) {
311
+ return resolveClaudeCodeRuntimeModel(model, settings).cliModelId;
312
+ }
313
+
314
+ function adaptiveThinkingAlwaysOn(modelId) {
315
+ return modelId === "claude-fable-5-1" || modelId.startsWith("claude-fable-5-1[") || modelId === "claude-fable-5" || modelId.startsWith("claude-fable-5[");
316
+ }
317
+
318
+ function thinkingBoundToPrefix(modelId) {
319
+ return modelId === "claude-fable-5-1" || modelId.startsWith("claude-fable-5-1[");
320
+ }
321
+
322
+ function minClaudeCodeVersionForModel(modelId) {
323
+ if (modelId === "claude-fable-5-1" || modelId.startsWith("claude-fable-5-1[")) return "2.1.251";
324
+ return void 0;
325
+ }
326
+
327
+ function applyLongContext(models, settings) {
328
+ return models.map((m) => {
329
+ const { contextWindow } = resolveClaudeCodeRuntimeModel(m, settings);
330
+ const name = contextWindow > TWO_HUNDRED_K_CONTEXT && !/\b1M\b/i.test(m.name) ? `${m.name} 1M` : m.name;
331
+ return contextWindow === m.contextWindow && name === m.name ? m : { ...m, contextWindow, name };
332
+ });
333
+ }
334
+
335
+ function verifyWrittenSession(jsonlPath, expectedSessionId, expectedRecordCount) {
336
+ const warnings = [];
337
+ let st;
338
+ try {
339
+ st = statSync(jsonlPath);
340
+ } catch (e) {
341
+ warnings.push(`file missing after save \u2014 path=${jsonlPath} err=${e.message}`);
342
+ return warnings;
343
+ }
344
+ let content;
345
+ try {
346
+ content = readFileSync(jsonlPath, "utf8");
347
+ } catch (e) {
348
+ warnings.push(`file unreadable \u2014 path=${jsonlPath} size=${st.size} err=${e.message}`);
349
+ return warnings;
350
+ }
351
+ const lines = content.split("\n").filter((l) => l.trim().length > 0);
352
+ if (lines.length !== expectedRecordCount) {
353
+ warnings.push(`record count mismatch \u2014 expected=${expectedRecordCount} actual=${lines.length} path=${jsonlPath} bytes=${content.length}`);
354
+ return warnings;
355
+ }
356
+ try {
357
+ const firstRec = JSON.parse(lines[0]);
358
+ const lastRec = JSON.parse(lines[lines.length - 1]);
359
+ if (firstRec.sessionId !== expectedSessionId || lastRec.sessionId !== expectedSessionId) {
360
+ warnings.push(`sessionId drift \u2014 expected=${expectedSessionId} first=${firstRec.sessionId} last=${lastRec.sessionId}`);
361
+ }
362
+ } catch (e) {
363
+ warnings.push(`malformed JSONL \u2014 path=${jsonlPath} err=${e.message}`);
364
+ }
365
+ return warnings;
366
+ }
367
+
368
+
369
+ // ../../node_modules/pi-claude-agent-sdk/src/extract-tool-results.ts
370
+ function toolResultToMcpContent(content) {
371
+ if (typeof content === "string") return [{ type: "text", text: content || "" }];
372
+ if (!Array.isArray(content)) return [{ type: "text", text: "" }];
373
+ const blocks = [];
374
+ for (const block of content) {
375
+ if (block.type === "text" && block.text) blocks.push({ type: "text", text: block.text });
376
+ else if (block.type === "image" && block.data && block.mimeType) blocks.push({ type: "image", data: block.data, mimeType: block.mimeType });
377
+ }
378
+ return blocks.length ? blocks : [{ type: "text", text: "" }];
379
+ }
380
+
381
+ function extractAllToolResults(messages) {
382
+ const results = [];
383
+ let stopIdx = -1;
384
+ for (let i = messages.length - 1; i >= 0; i--) {
385
+ const msg = messages[i];
386
+ if (msg.role === "toolResult") {
387
+ results.unshift({ content: toolResultToMcpContent(msg.content), isError: msg.isError, toolCallId: msg.toolCallId });
388
+ } else if (msg.role === "assistant") {
389
+ stopIdx = i;
390
+ break;
391
+ }
392
+ }
393
+ return { results, stopIdx };
394
+ }
395
+
396
+
397
+ // ../../node_modules/pi-claude-agent-sdk/src/query-state.ts
398
+ var QueryContext = class {
399
+ // Query-scoped (fully isolated per query)
400
+ activeQuery = null;
401
+ currentPiStream = null;
402
+ latestCursor = 0;
403
+ pendingToolCalls = /* @__PURE__ */ new Map();
404
+ pendingResults = /* @__PURE__ */ new Map();
405
+ /** tool_use ids emitted this turn. Sole purpose is routing a delivered result
406
+ * to the owning query when several queries are in flight — pairing a result
407
+ * to its call is done by id from Claude's tools/call _meta, not from here. */
408
+ turnToolCallIds = [];
409
+ /** Streaming-input handle for the active query — how steers reach CC mid-turn. */
410
+ promptStream = null;
411
+ /** Last rate-limit rejection seen on this query. Claude Code sends it just before the
412
+ * failure it caused, which is the only thing tying the two together. */
413
+ rateLimitRejection = null;
414
+ /** Highest 5% utilization bucket we notified for, so repeat rate_limit_event spam is suppressed. */
415
+ lastRateLimitWarnStep = null;
416
+ lastRateLimitWarnThreshold;
417
+ /** pi session this query serves, from SimpleStreamOptions.sessionId at fresh-query
418
+ * setup. A bridge process serves several pi sessions at once (subagents run their
419
+ * own AgentSessions), and history rewrites must only discard the rewriting
420
+ * session's parked queries — this is the match key. Null when the host did not
421
+ * supply an id.
422
+ */
423
+ piSessionId = null;
424
+ /** pi rewrote the history this query was built from (session_compact,
425
+ * session_tree in its own pi session). Set by markRebuildForSession, consumed
426
+ * by the tool-result delivery that discards the query. Not session-wide state:
427
+ * it dies with the context it belongs to, so it cannot leak into later turns
428
+ * the way a module flag does.
429
+ */
430
+ historyStale = false;
431
+ /** A steer never reached CC. A first query has no session mirror yet, so
432
+ * completion must carry this into the mirror it creates. */
433
+ missedSteer = false;
434
+ // Per-turn (reset together)
435
+ turnOutput = null;
436
+ turnStarted = false;
437
+ turnSawStreamEvent = false;
438
+ turnSawToolCall = false;
439
+ /** API message id from the last message_start, and whether its message_stop has
440
+ * arrived. An `assistant` message under a different id while the stream is still
441
+ * open is Claude Code's non-streaming fallback for a stalled stream. */
442
+ turnStreamMessageId;
443
+ turnStreamOpen = false;
444
+ /** turnBlocks length at that message_start: where an abandoned attempt's blocks begin. */
445
+ turnStreamBlockStart = 0;
446
+ get turnBlocks() {
447
+ if (!this.turnOutput) throw new Error("turnBlocks accessed before resetTurnState");
448
+ return this.turnOutput.content;
449
+ }
450
+ /** Answer every parked MCP handler with `reason` and forget the turn's queued
451
+ * results. Called when the query it belongs to is going away (abort, error,
452
+ * normal end). Handlers must be *resolved*, not rejected: an error reply is
453
+ * still a reply, and a handler left awaiting a subprocess that is gone keeps
454
+ * CC's tools/call open forever, which wedges pi's turn behind it. */
455
+ releasePendingToolCalls(reason) {
456
+ for (const pending of this.pendingToolCalls.values()) pending.resolve({ content: [{ type: "text", text: reason }] });
457
+ this.pendingToolCalls.clear();
458
+ this.pendingResults.clear();
459
+ }
460
+ resetTurnState(model) {
461
+ this.turnOutput = {
462
+ role: "assistant",
463
+ content: [],
464
+ api: model.api,
465
+ provider: model.provider,
466
+ model: model.id,
467
+ usage: {
468
+ input: 0,
469
+ output: 0,
470
+ cacheRead: 0,
471
+ cacheWrite: 0,
472
+ totalTokens: 0,
473
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }
474
+ },
475
+ stopReason: "stop",
476
+ timestamp: Date.now()
477
+ };
478
+ this.turnStarted = false;
479
+ this.turnSawStreamEvent = false;
480
+ this.turnSawToolCall = false;
481
+ this.turnStreamMessageId = void 0;
482
+ this.turnStreamOpen = false;
483
+ this.turnStreamBlockStart = 0;
484
+ }
485
+ };
486
+
487
+ var _ctx = new QueryContext();
488
+
489
+ function ctx() {
490
+ return _ctx;
491
+ }
492
+
493
+
494
+ // ../../node_modules/pi-claude-agent-sdk/src/prompt-stream.ts
495
+ function makePromptStream() {
496
+ const queue = [];
497
+ let inflight = null;
498
+ let wake = null;
499
+ let done = false;
500
+ let failure = null;
501
+ const kick = () => {
502
+ wake?.();
503
+ wake = null;
504
+ };
505
+ async function* gen() {
506
+ try {
507
+ while (true) {
508
+ while (queue.length === 0 && !done && !failure) {
509
+ await new Promise((resolve) => {
510
+ wake = resolve;
511
+ });
512
+ }
513
+ if (failure) throw failure;
514
+ const item = queue.shift();
515
+ if (!item) return;
516
+ inflight = item;
517
+ try {
518
+ yield item.msg;
519
+ item.resolve();
520
+ } finally {
521
+ item.reject(new Error("prompt stream closed"));
522
+ inflight = null;
523
+ }
524
+ }
525
+ } finally {
526
+ done = true;
527
+ }
528
+ }
529
+ return {
530
+ stream: gen(),
531
+ push: (msg) => failure || done ? Promise.reject(failure ?? new Error("prompt stream closed")) : new Promise((resolve, reject) => {
532
+ queue.push({ msg, resolve, reject });
533
+ kick();
534
+ }),
535
+ end: () => {
536
+ done = true;
537
+ kick();
538
+ },
539
+ fail: (error) => {
540
+ if (failure) return;
541
+ failure = error;
542
+ queue.splice(0).forEach((item) => item.reject(error));
543
+ inflight?.reject(error);
544
+ kick();
545
+ }
546
+ };
547
+ }
548
+
549
+ function userMessage(content, priority) {
550
+ return {
551
+ type: "user",
552
+ message: { role: "user", content },
553
+ parent_tool_use_id: null,
554
+ ...priority ? { priority } : {}
555
+ };
556
+ }
557
+
558
+ function tryParseJson(path) {
559
+ if (!existsSync(path)) return {};
560
+ try {
561
+ return JSON.parse(readFileSync2(path, "utf-8"));
562
+ } catch (e) {
563
+ console.error(`claude-bridge: failed to parse ${path}: ${e}`);
564
+ return {};
565
+ }
566
+ }
567
+
568
+ function claudeCodeSettings(provider = {}) {
569
+ return { autoMemoryEnabled: provider.autoMemoryEnabled ?? false };
570
+ }
571
+
572
+ function globalConfigPath() {
573
+ return join2(getAgentDir2(), "claude-bridge.json");
574
+ }
575
+
576
+ function markStartupNoticeShown() {
577
+ const path = globalConfigPath();
578
+ let existing = {};
579
+ if (existsSync(path)) {
580
+ try {
581
+ existing = JSON.parse(readFileSync2(path, "utf-8"));
582
+ } catch (e) {
583
+ console.error(`claude-bridge: leaving ${path} alone, it does not parse: ${e}`);
584
+ return path;
585
+ }
586
+ }
587
+ const next = { ...existing, startupNoticeShown: (/* @__PURE__ */ new Date()).toLocaleDateString("en-CA") };
588
+ mkdirSync(dirname(path), { recursive: true });
589
+ writeFileSync(path, `${JSON.stringify(next, null, 2)}
590
+ `);
591
+ return path;
592
+ }
593
+
594
+ function loadConfig(cwd) {
595
+ const global = tryParseJson(globalConfigPath());
596
+ const project = tryParseJson(join2(cwd, CONFIG_DIR_NAME, "claude-bridge.json"));
597
+ return {
598
+ startupNoticeShown: project.startupNoticeShown ?? global.startupNoticeShown,
599
+ provider: { ...global.provider, ...project.provider }
600
+ };
601
+ }
602
+
603
+
604
+ // ../../node_modules/pi-claude-agent-sdk/src/agents-md.ts
605
+ function formatProjectContext(contextFiles) {
606
+ if (contextFiles.length === 0) return void 0;
607
+ let prompt = "<project_context>\n\nProject-specific instructions and guidelines:\n\n";
608
+ for (const { path, content } of contextFiles) {
609
+ prompt += `<project_instructions path="${path}">
610
+ ${content}
611
+ </project_instructions>
612
+
613
+ `;
614
+ }
615
+ return `${prompt}</project_context>`;
616
+ }
617
+
618
+
619
+ // ../../node_modules/pi-claude-agent-sdk/src/prompt-capture.ts
620
+ var PromptCaptures = class {
621
+ /** Pi rebuilds prompts when tools change, so retain only recent lookup keys.
622
+ * Inheritance edges hold direct references and survive key eviction.
623
+ *
624
+ * Set well above any plausible working set because the costs are lopsided: a
625
+ * capture is tens of KB, while evicting one that is still live fails the turn.
626
+ * A parent that fans out to more distinct sub-agent prompts than this before its
627
+ * own next turn would be evicted despite being in use. The bound exists only to
628
+ * cap an extension that rebuilds the prompt every turn, which would otherwise
629
+ * grow keys without limit. */
630
+ constructor(limit = 256, onDiagnose) {
631
+ this.limit = limit;
632
+ this.onDiagnose = onDiagnose ?? (() => {
633
+ });
634
+ }
635
+ limit;
636
+ captures = /* @__PURE__ */ new Map();
637
+ /** Invoked with everything that would otherwise be lost when resolution throws,
638
+ * so the bridge can write it to its debug log. Kept off the throw path itself:
639
+ * the resolver is hot and the caller may own a faster sink than string-building.
640
+ *
641
+ * Set by the bridge on the shared instance; tests that want the diagnostic can
642
+ * pass one per instance. */
643
+ onDiagnose;
644
+ record(systemPrompt, input, source) {
645
+ const existing = this.captures.get(systemPrompt);
646
+ const customChanged = existing?.custom !== input.custom;
647
+ const capture = existing ?? {
648
+ ...input,
649
+ assembledPrompt: systemPrompt,
650
+ contextFiles: [],
651
+ skills: [],
652
+ inherited: []
653
+ };
654
+ capture.custom = input.custom;
655
+ capture.append = input.append;
656
+ capture.contextFiles = input.contextFiles.map((file) => ({ ...file }));
657
+ capture.skills = [...input.skills];
658
+ capture.source = source;
659
+ if (!existing || customChanged) {
660
+ capture.inherited = this.findInheritedPrompts(systemPrompt, input.custom);
661
+ }
662
+ this.touch(systemPrompt, capture);
663
+ }
664
+ /** Exact lookup only. Callers serving a query want `resolveOrDerive`. */
665
+ resolve(systemPrompt) {
666
+ if (!systemPrompt) return void 0;
667
+ const capture = this.captures.get(systemPrompt);
668
+ if (capture) this.touch(systemPrompt, capture);
669
+ return capture;
670
+ }
671
+ /** Recency is by use, not just by record. A parent agent records its prompt once
672
+ * and then only ever resolves it, so counting writes alone ages it out behind the
673
+ * sub-agent prompts churning past it — observed in a real 135-message session,
674
+ * where the parent's own prompt was evicted and its next turn resolved to
675
+ * nothing. */
676
+ touch(systemPrompt, capture) {
677
+ this.captures.delete(systemPrompt);
678
+ this.captures.set(systemPrompt, capture);
679
+ for (const key of this.captures.keys()) {
680
+ if (this.captures.size <= this.limit) break;
681
+ this.captures.delete(key);
682
+ }
683
+ }
684
+ /**
685
+ * The capture to project for one provider query.
686
+ *
687
+ * An exact key is the normal case. A prompt that only *embeds* known prompts —
688
+ * anything that wrapped what Pi assembled after we recorded it — resolves to a
689
+ * transient descendant over the whole prompt, so projection swaps each embedded
690
+ * capture for its portable parts and carries everything around them through
691
+ * unchanged. That surrounding text belongs to whatever did the wrapping, and
692
+ * dropping it would be exactly the silent instruction loss this exists to
693
+ * prevent. The descendant is not retained — its key is not ours to own.
694
+ *
695
+ * Throws when a prompt can be accounted for by neither route. Returning an empty
696
+ * capture instead would hand Claude Code a turn with none of the user's context
697
+ * files, skills, custom prompt or append text, and say so only in a debug line —
698
+ * silently discarding policy the user wrote down. A failed turn is recoverable;
699
+ * a turn that quietly ignored its instructions is not.
700
+ */
701
+ resolveOrDerive(systemPrompt) {
702
+ if (!systemPrompt) return void 0;
703
+ const exact = this.captures.get(systemPrompt);
704
+ if (exact) {
705
+ this.touch(systemPrompt, exact);
706
+ return exact;
707
+ }
708
+ const revived = this.reachableCaptures().find((node) => node.assembledPrompt === systemPrompt);
709
+ if (revived) {
710
+ this.touch(systemPrompt, revived);
711
+ return revived;
712
+ }
713
+ const embedded = this.findInheritedPrompts(systemPrompt, systemPrompt);
714
+ if (embedded.length === 0) {
715
+ const matches = this.closestKnown(systemPrompt);
716
+ this.onDiagnose({ systemPrompt, matches });
717
+ const truncated = matches.find(
718
+ (m) => m.firstDivergent === systemPrompt.length && m.key.length > systemPrompt.length
719
+ );
720
+ if (truncated) {
721
+ throw new Error(
722
+ `prompt-capture: this ${systemPrompt.length}-char prompt is the ${truncated.key.length}-char capture recorded at ${truncated.source ?? "an unknown boundary"} with its final ${truncated.key.length - systemPrompt.length} chars missing. That shape matches pi#5581: an extension-triggered idle turn (sendMessage with triggerTurn) skips before_agent_start, so the prompt lacks that turn's extension additions. The turn fails; the next user-typed turn resolves normally. Workaround: when idle, send a user message instead of triggerTurn.`
723
+ );
724
+ }
725
+ throw new Error(
726
+ `prompt-capture: no capture for this ${systemPrompt.length}-char system prompt, and it embeds none of the ${this.captures.size} known. Closest known match diverges at offset ${matches[0]?.firstDivergent ?? "?"} (${matches.length ? matches[0].key.length : 0}-char key${matches[0]?.source ? `, last recorded at ${matches[0].source}` : ""}). Claude Code would receive none of this turn's context files, skills or custom instructions. The usual cause is an extension loaded after claude-bridge that rewrites the system prompt from before_agent_start \u2014 one that wraps it is fine, one that rebuilds or strips it leaves nothing to match. (Also possible: pi rebuilt the prompt outside before_agent_start \u2014 a late-registered tool or fresh resource discovery.)`
727
+ );
728
+ }
729
+ return { assembledPrompt: systemPrompt, custom: systemPrompt, contextFiles: [], skills: [], inherited: embedded };
730
+ }
731
+ get size() {
732
+ return this.captures.size;
733
+ }
734
+ /** Longest shared-prefix matches, best first, for the throw diagnostic. */
735
+ closestKnown(systemPrompt) {
736
+ let shared = 0;
737
+ const matches = [];
738
+ for (const [key, capture] of this.captures.entries()) {
739
+ const limit = Math.min(key.length, systemPrompt.length);
740
+ let i = 0;
741
+ while (i < limit && key.charCodeAt(i) === systemPrompt.charCodeAt(i)) i++;
742
+ if (i >= shared) {
743
+ if (i > shared) {
744
+ shared = i;
745
+ matches.length = 0;
746
+ }
747
+ matches.push({ key, firstDivergent: i, source: capture.source });
748
+ }
749
+ }
750
+ return matches;
751
+ }
752
+ findInheritedPrompts(systemPrompt, custom) {
753
+ if (!custom) return [];
754
+ const candidates = [];
755
+ for (const parent of this.reachableCaptures()) {
756
+ const key = parent.assembledPrompt;
757
+ if (key === systemPrompt || key.length === 0) continue;
758
+ for (let start = custom.indexOf(key); start !== -1; start = custom.indexOf(key, start + key.length)) {
759
+ candidates.push({ start, end: start + key.length, length: key.length, parent });
760
+ }
761
+ }
762
+ candidates.sort((a, b) => b.length - a.length || a.start - b.start);
763
+ const selected = [];
764
+ for (const candidate of candidates) {
765
+ if (selected.some((edge) => candidate.start < edge.end && candidate.end > edge.start)) continue;
766
+ selected.push({ start: candidate.start, end: candidate.end, parent: candidate.parent });
767
+ }
768
+ return selected.sort((a, b) => a.start - b.start);
769
+ }
770
+ reachableCaptures() {
771
+ const result = [];
772
+ const seen = /* @__PURE__ */ new Set();
773
+ const visit = (capture) => {
774
+ if (seen.has(capture)) return;
775
+ seen.add(capture);
776
+ result.push(capture);
777
+ for (const edge of capture.inherited) visit(edge.parent);
778
+ };
779
+ for (const capture of this.captures.values()) visit(capture);
780
+ return result;
781
+ }
782
+ };
783
+
784
+ var PI_PREAMBLE = "You are an expert coding assistant operating inside pi";
785
+
786
+ var ANTHROPIC_THIRD_PARTY_TRIGGERS = ["docs/custom-provider.md", "docs/packages.md"];
787
+
788
+ var SHARED_CAPTURES_KEY = /* @__PURE__ */ Symbol.for("claude-bridge:promptCaptures");
789
+
790
+ function sharedPromptCaptures(onDiagnose) {
791
+ const globals = globalThis;
792
+ return globals[SHARED_CAPTURES_KEY] ??= new PromptCaptures(256, onDiagnose);
793
+ }
794
+
795
+ function projectPromptCapture(capture, options) {
796
+ return projectCapture(capture, options, /* @__PURE__ */ new Set());
797
+ }
798
+
799
+ function collectPromptSkills(capture) {
800
+ const result = [];
801
+ const seenPaths = /* @__PURE__ */ new Set();
802
+ const visited = /* @__PURE__ */ new Set();
803
+ const visiting = /* @__PURE__ */ new Set();
804
+ const visit = (node) => {
805
+ if (visited.has(node)) return;
806
+ if (visiting.has(node)) throw new Error("Cyclic prompt inheritance");
807
+ visiting.add(node);
808
+ for (const edge of node.inherited) visit(edge.parent);
809
+ for (const skill of node.skills) {
810
+ if (skill.disableModelInvocation || seenPaths.has(skill.filePath)) continue;
811
+ seenPaths.add(skill.filePath);
812
+ result.push(skill);
813
+ }
814
+ visiting.delete(node);
815
+ visited.add(node);
816
+ };
817
+ visit(capture);
818
+ return result;
819
+ }
820
+
821
+ function projectCapture(capture, options, visiting) {
822
+ if (visiting.has(capture)) throw new Error("Cyclic prompt inheritance");
823
+ visiting.add(capture);
824
+ try {
825
+ const inheritedSkillPaths = new Set(
826
+ capture.inherited.flatMap((edge) => collectPromptSkills(edge.parent).map((skill) => skill.filePath))
827
+ );
828
+ const ownSkillPaths = /* @__PURE__ */ new Set();
829
+ const ownSkills = capture.skills.filter((skill) => {
830
+ if (skill.disableModelInvocation || inheritedSkillPaths.has(skill.filePath) || ownSkillPaths.has(skill.filePath)) {
831
+ return false;
832
+ }
833
+ ownSkillPaths.add(skill.filePath);
834
+ return true;
835
+ });
836
+ const custom = projectCustom(capture, options, visiting);
837
+ const parts = [];
838
+ const context = formatProjectContext(capture.contextFiles);
839
+ if (context) parts.push({ label: "the project context block", text: context });
840
+ const skills = renderSkillsBlock(ownSkills, options.skillReadTool);
841
+ if (skills) parts.push({ label: "the skills block", text: skills });
842
+ if (custom) parts.push({ label: "the custom prompt", text: custom });
843
+ if (capture.append) parts.push({ label: "the appended instructions", text: capture.append });
844
+ assertSendablePrompt(parts, capture);
845
+ return parts.length > 0 ? parts.map((part) => part.text).join("\n\n") : void 0;
846
+ } finally {
847
+ visiting.delete(capture);
848
+ }
849
+ }
850
+
851
+ function assertSendablePrompt(parts, capture) {
852
+ const findings = [];
853
+ for (const { label, text } of parts) {
854
+ const offset = preambleAtLineStart(text);
855
+ if (offset !== -1) {
856
+ findings.push(`pi's preamble ("${PI_PREAMBLE}") in ${label}, at offset ${offset} of ${text.length} chars`);
857
+ }
858
+ if (ANTHROPIC_THIRD_PARTY_TRIGGERS.every((trigger) => text.includes(trigger))) {
859
+ findings.push(`${ANTHROPIC_THIRD_PARTY_TRIGGERS.join(" and ")} in ${label}`);
860
+ }
861
+ }
862
+ if (findings.length === 0) return;
863
+ throw new Error([
864
+ "prompt-capture: refusing to send this prompt. Claude Code's Anthropic path reads a request",
865
+ " carrying pi's harness, or the phrase pair its subscription gate rejects, as a third-party",
866
+ " app: it fails with 400 or is billed as extra usage.",
867
+ ...findings.map((finding) => ` Found: ${finding}.`),
868
+ ` Capture: ${capture.source ?? "unknown"}, ${capture.inherited.length} inherited capture(s) substituted.`,
869
+ ' If this came from an inherited pi prompt, see README "Compatibility with other extensions".',
870
+ " If it is your own text, reword or remove it. CLAUDE_BRIDGE_DEBUG=1 writes the full prompt to",
871
+ ` ${DEBUG_LOG_PATH}.`
872
+ ].join("\n"));
873
+ }
874
+
875
+ function preambleAtLineStart(text) {
876
+ for (let offset = text.indexOf(PI_PREAMBLE); offset !== -1; offset = text.indexOf(PI_PREAMBLE, offset + PI_PREAMBLE.length)) {
877
+ if (offset === 0 || text[offset - 1] === "\n") return offset;
878
+ }
879
+ return -1;
880
+ }
881
+
882
+ function projectCustom(capture, options, visiting) {
883
+ if (!capture.custom || capture.inherited.length === 0) return capture.custom;
884
+ let result = "";
885
+ let cursor = 0;
886
+ for (const edge of capture.inherited) {
887
+ result += capture.custom.slice(cursor, edge.start);
888
+ result += projectCapture(edge.parent, options, visiting) ?? "";
889
+ cursor = edge.end;
890
+ }
891
+ return result + capture.custom.slice(cursor);
892
+ }
893
+
894
+
895
+ // ../../node_modules/pi-claude-agent-sdk/src/attachments.ts
896
+ var CONTENT_BEARING = /* @__PURE__ */ new Set(["file"]);
897
+
898
+ function userPromptText(record) {
899
+ if (record.type !== "user") return void 0;
900
+ const content = record.message?.content;
901
+ if (Array.isArray(content) && content.some((b) => b?.type === "tool_result")) return void 0;
902
+ const text = messageContentToText(content);
903
+ return text ? text : void 0;
904
+ }
905
+
906
+ function collectCarriedAttachments(records) {
907
+ const ordinalOf = /* @__PURE__ */ new Map();
908
+ const textOf = /* @__PURE__ */ new Map();
909
+ let ordinal = 0;
910
+ const carried = [];
911
+ for (const raw of records) {
912
+ const record = raw;
913
+ const prompt = userPromptText(record);
914
+ if (prompt !== void 0) {
915
+ ordinalOf.set(record.uuid, ordinal++);
916
+ textOf.set(record.uuid, prompt);
917
+ continue;
918
+ }
919
+ if (record.type !== "attachment") continue;
920
+ const parent = record.parentUuid;
921
+ if (parent === null || !ordinalOf.has(parent)) continue;
922
+ const inherited = ordinalOf.get(parent);
923
+ ordinalOf.set(record.uuid, inherited);
924
+ textOf.set(record.uuid, textOf.get(parent));
925
+ const attachment = record.attachment;
926
+ if (!attachment || !CONTENT_BEARING.has(attachment.type)) continue;
927
+ carried.push({ attachment, userOrdinal: inherited, parentText: textOf.get(parent) });
928
+ }
929
+ return carried;
930
+ }
931
+
932
+ function placeCarriedAttachments(carried, messages) {
933
+ const prompts = [];
934
+ messages.forEach((msg, index) => {
935
+ if (msg.role !== "user") return;
936
+ if (Array.isArray(msg.content) && msg.content.some((b) => b?.type === "tool_result")) return;
937
+ const text = messageContentToText(msg.content);
938
+ if (text) prompts.push({ index, text });
939
+ });
940
+ const attachments = [];
941
+ const skipped = [];
942
+ for (const item of carried) {
943
+ const name = String(item.attachment.filename ?? item.attachment.type);
944
+ const candidate = prompts[item.userOrdinal];
945
+ if (!candidate) {
946
+ skipped.push(`${name}: prompt #${item.userOrdinal} is no longer in history`);
947
+ continue;
948
+ }
949
+ if (candidate.text !== item.parentText) {
950
+ skipped.push(`${name}: prompt #${item.userOrdinal} changed`);
951
+ continue;
952
+ }
953
+ attachments.push({ afterIndex: candidate.index, attachment: item.attachment });
954
+ }
955
+ return { attachments, skipped };
956
+ }
957
+
958
+ var TOOL_USE_ID_META = "claudecode/toolUseId";
959
+
960
+ function assertObjectSchema(tool) {
961
+ const schema = tool.inputSchema;
962
+ if (!schema || schema.type !== "object") {
963
+ throw new Error(`${tool.name}: MCP tool parameters must be an object schema, got ${JSON.stringify(schema)}`);
964
+ }
965
+ }
966
+
967
+ function createToolServer(name, tools) {
968
+ const server = new McpServer({ name, version: "1.0.0" }, { capabilities: { tools: {} } });
969
+ const byName = new Map(tools.map((tool) => [tool.name, tool]));
970
+ for (const tool of tools) assertObjectSchema(tool);
971
+ server.server.setRequestHandler(ListToolsRequestSchema, () => ({
972
+ tools: tools.map((tool) => ({
973
+ name: tool.name,
974
+ description: tool.description,
975
+ inputSchema: tool.inputSchema
976
+ }))
977
+ }));
978
+ server.server.setRequestHandler(CallToolRequestSchema, async (request) => {
979
+ const tool = byName.get(request.params.name);
980
+ if (!tool) throw new Error(`Unknown tool: ${request.params.name}`);
981
+ const toolCallId = request.params._meta?.[TOOL_USE_ID_META];
982
+ if (typeof toolCallId !== "string") {
983
+ throw new Error(`${tool.name}: tools/call is missing _meta["${TOOL_USE_ID_META}"] \u2014 cannot pair the result with its tool call`);
984
+ }
985
+ const { content, isError } = await tool.handler(toolCallId);
986
+ return { content, isError };
987
+ });
988
+ return { type: "sdk", name, instance: server };
989
+ }
990
+
991
+
992
+ // ../../node_modules/pi-claude-agent-sdk/src/child-env.ts
993
+ var CC_CHILD_ENV = {
994
+ ENABLE_CLAUDEAI_MCP_SERVERS: "0",
995
+ DISABLE_AUTO_COMPACT: "1"
996
+ };
997
+
998
+ function buildClaudeChildEnv(base, resolved) {
999
+ const env = { ...base, ...resolved?.env ?? {}, ...CC_CHILD_ENV };
1000
+ for (const key of [
1001
+ "ANTHROPIC_API_KEY",
1002
+ "ANTHROPIC_AUTH_TOKEN",
1003
+ "ANTHROPIC_OAUTH_TOKEN",
1004
+ "ANTHROPIC_IDENTITY_TOKEN",
1005
+ "ANTHROPIC_IDENTITY_TOKEN_FILE",
1006
+ "ANTHROPIC_BASE_URL",
1007
+ "ANTHROPIC_CUSTOM_HEADERS",
1008
+ "CLAUDE_CODE_OAUTH_TOKEN",
1009
+ "CLAUDE_CODE_CUSTOM_OAUTH_URL",
1010
+ "CLAUDE_CODE_OAUTH_CLIENT_ID",
1011
+ "CLAUDE_CODE_USE_BEDROCK",
1012
+ "CLAUDE_CODE_USE_FOUNDRY",
1013
+ "CLAUDE_CODE_USE_VERTEX"
1014
+ ]) delete env[key];
1015
+ const headers = Object.entries(resolved?.auth.headers ?? {}).filter((entry) => entry[1] != null);
1016
+ const authorization = headers.find(([name]) => name.toLowerCase() === "authorization")?.[1];
1017
+ const bearerToken = authorization?.match(/^Bearer\s+(.+)$/i)?.[1];
1018
+ const headerApiKey = headers.find(([name]) => name.toLowerCase() === "x-api-key")?.[1];
1019
+ const credential = resolved?.auth.apiKey ?? headerApiKey;
1020
+ if (!credential && !bearerToken) {
1021
+ throw new Error("No Anthropic credential is configured in Pi. Configure Anthropic authentication in Pi before using claude-bridge.");
1022
+ }
1023
+ if (resolved?.auth.baseUrl) env.ANTHROPIC_BASE_URL = resolved.auth.baseUrl;
1024
+ const customHeaders = headers.filter(([name]) => !["authorization", "x-api-key"].includes(name.toLowerCase()));
1025
+ if (customHeaders.length > 0) {
1026
+ env.ANTHROPIC_CUSTOM_HEADERS = customHeaders.map(([name, value]) => `${name}: ${value}`).join("\n");
1027
+ }
1028
+ if (bearerToken) {
1029
+ env.ANTHROPIC_AUTH_TOKEN = bearerToken;
1030
+ } else if (resolved?.source?.toLowerCase().includes("oauth")) {
1031
+ env.CLAUDE_CODE_OAUTH_TOKEN = credential;
1032
+ } else {
1033
+ env.ANTHROPIC_API_KEY = credential;
1034
+ }
1035
+ return env;
1036
+ }
1037
+
1038
+ var CHILD_OAUTH_MIN_VALIDITY_MS = 2 * 60 * 60 * 1e3;
1039
+
1040
+ var minValidityUnsatisfiable = false;
1041
+
1042
+ async function resolveAnthropicAuth(registry) {
1043
+ const runtime = registry.runtime;
1044
+ if (!minValidityUnsatisfiable && typeof runtime?.getAuth === "function") {
1045
+ try {
1046
+ return await runtime.getAuth("anthropic", { minOAuthValidityMs: CHILD_OAUTH_MIN_VALIDITY_MS });
1047
+ } catch (err) {
1048
+ if (/expires too soon/i.test(err instanceof Error ? err.message : String(err))) minValidityUnsatisfiable = true;
1049
+ }
1050
+ }
1051
+ return registry.getProviderAuth("anthropic");
1052
+ }
1053
+
1054
+ async function resolveClaudeChildEnv(registry, base = process.env) {
1055
+ return { ...base, ...CC_CHILD_ENV };
1056
+ const resolved = registry ? await resolveAnthropicAuth(registry) : void 0;
1057
+ return buildClaudeChildEnv(base, resolved);
1058
+ }
1059
+
1060
+ var require2 = createRequire(import.meta.url);
1061
+
1062
+ var versionCache = /* @__PURE__ */ new Map();
1063
+
1064
+ function parseClaudeVersion(text) {
1065
+ const match = text.match(/(\d+)\.(\d+)\.(\d+)/);
1066
+ if (!match) return void 0;
1067
+ return { major: Number(match[1]), minor: Number(match[2]), patch: Number(match[3]) };
1068
+ }
1069
+
1070
+ function formatClaudeVersion(version) {
1071
+ return `${version.major}.${version.minor}.${version.patch}`;
1072
+ }
1073
+
1074
+ function compareClaudeVersion(a, b) {
1075
+ return a.major - b.major || a.minor - b.minor || a.patch - b.patch;
1076
+ }
1077
+
1078
+ function readBundledClaudeCodeVersion() {
1079
+ try {
1080
+ const pkgPath = join3(dirname2(require2.resolve("@anthropic-ai/claude-agent-sdk")), "package.json");
1081
+ const pkg = JSON.parse(readFileSync3(pkgPath, "utf8"));
1082
+ return parseClaudeVersion(pkg.claudeCodeVersion ?? "");
1083
+ } catch {
1084
+ return void 0;
1085
+ }
1086
+ }
1087
+
1088
+ function findExecutableOnPath(name, env = process.env) {
1089
+ const pathEnv = env.PATH ?? env.Path;
1090
+ if (!pathEnv) return void 0;
1091
+ const exts = process.platform === "win32" ? (env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";").filter(Boolean) : [""];
1092
+ const names = process.platform === "win32" && !exts.some((ext) => name.toUpperCase().endsWith(ext.toUpperCase())) ? [name, ...exts.map((ext) => name + ext)] : [name];
1093
+ for (const dir of pathEnv.split(delimiter)) {
1094
+ if (!dir) continue;
1095
+ for (const candidateName of names) {
1096
+ const candidate = join3(dir, candidateName);
1097
+ try {
1098
+ accessSync(candidate, constants.X_OK);
1099
+ return candidate;
1100
+ } catch {
1101
+ }
1102
+ }
1103
+ }
1104
+ return void 0;
1105
+ }
1106
+
1107
+ function readClaudeCliVersion(executable) {
1108
+ if (versionCache.has(executable)) return versionCache.get(executable);
1109
+ try {
1110
+ const result = spawnSync(executable, ["--version"], {
1111
+ encoding: "utf8",
1112
+ timeout: 8e3,
1113
+ env: process.env,
1114
+ windowsHide: true
1115
+ });
1116
+ const version = parseClaudeVersion(`${result.stdout ?? ""}
1117
+ ${result.stderr ?? ""}`);
1118
+ versionCache.set(executable, version);
1119
+ return version;
1120
+ } catch {
1121
+ versionCache.set(executable, void 0);
1122
+ return void 0;
1123
+ }
1124
+ }
1125
+
1126
+ function tooOldError(modelId, min, found) {
1127
+ return `${modelId} requires Claude Code ${formatClaudeVersion(min)} or newer (${found}). Install a current claude on PATH, or set provider.pathToClaudeCodeExecutable in ~/.pi/agent/claude-bridge.json.`;
1128
+ }
1129
+
1130
+ function resolveClaudeCodeExecutable(modelId, configured, deps = {}) {
1131
+ const bundledVersion = deps.bundledVersion ?? readBundledClaudeCodeVersion;
1132
+ const findOnPath = deps.findOnPath ?? (() => findExecutableOnPath("claude"));
1133
+ const readVersion = deps.readVersion ?? readClaudeCliVersion;
1134
+ const minText = minClaudeCodeVersionForModel(modelId);
1135
+ const min = minText ? parseClaudeVersion(minText) : void 0;
1136
+ if (configured) {
1137
+ if (min) {
1138
+ const version = readVersion(configured);
1139
+ if (version && compareClaudeVersion(version, min) < 0) {
1140
+ return {
1141
+ source: "configured",
1142
+ error: tooOldError(modelId, min, `provider.pathToClaudeCodeExecutable is ${formatClaudeVersion(version)} at ${configured}`)
1143
+ };
1144
+ }
1145
+ }
1146
+ return { path: configured, source: "configured" };
1147
+ }
1148
+ if (!min) return { source: "bundled" };
1149
+ const bundled = bundledVersion();
1150
+ if (bundled && compareClaudeVersion(bundled, min) >= 0) {
1151
+ return { source: "bundled" };
1152
+ }
1153
+ const pathClaude = findOnPath();
1154
+ if (pathClaude) {
1155
+ const version = readVersion(pathClaude);
1156
+ if (version && compareClaudeVersion(version, min) >= 0) {
1157
+ return { path: pathClaude, source: "path" };
1158
+ }
1159
+ }
1160
+ if (bundled) {
1161
+ return { source: "bundled", error: tooOldError(modelId, min, `bundled CLI is ${formatClaudeVersion(bundled)}`) };
1162
+ }
1163
+ return { source: "bundled" };
1164
+ }
1165
+
1166
+ var SECTION_RANK = /* @__PURE__ */ new Map([
1167
+ ["preamble", 0],
1168
+ ["tools", 1],
1169
+ ["rules", 2],
1170
+ ["docs", 3],
1171
+ ["addendum", 4],
1172
+ ["project_context", 5],
1173
+ ["skills", 6],
1174
+ ["cwd", 7]
1175
+ ]);
1176
+
1177
+ function stableRanked(sections, ranks) {
1178
+ let predecessorRank = -1;
1179
+ const ranked = [...sections].map(([name, value]) => {
1180
+ const rank = ranks.get(name) ?? predecessorRank;
1181
+ predecessorRank = rank;
1182
+ return { name, value, rank };
1183
+ });
1184
+ ranked.sort((a, b) => a.rank - b.rank);
1185
+ return new Map(ranked.map(({ name, value }) => [name, value]));
1186
+ }
1187
+
1188
+ function canonicalSystemPrompt(message) {
1189
+ if (!message) return void 0;
1190
+ const sections = new Map(
1191
+ Object.entries(message.sections ?? {}).filter((entry) => entry[1] !== null)
1192
+ );
1193
+ const parts = [contentText(message.content), ...stableRanked(sections, SECTION_RANK).values()].filter((part) => part.length > 0);
1194
+ return parts.length > 0 ? parts.join("\n\n") : void 0;
1195
+ }
1196
+
1197
+ function toBridgeContext(context) {
1198
+ if (!context.messages.some((message) => message.role === "system")) return context;
1199
+ const tools = getCurrentTools(context.messages);
1200
+ return {
1201
+ ...context,
1202
+ systemPrompt: canonicalSystemPrompt(getCurrentSystemMessage(context.messages)),
1203
+ tools: tools.length > 0 ? tools : void 0,
1204
+ messages: nonSystemMessages(context.messages)
1205
+ };
1206
+ }
1207
+
1208
+ function nonSystemMessages(messages) {
1209
+ return messages.filter((message) => message.role !== "system");
1210
+ }
1211
+
1212
+ function updateUsage(output, usage, model) {
1213
+ if (usage.input_tokens != null) output.usage.input = usage.input_tokens;
1214
+ if (usage.output_tokens != null) output.usage.output = usage.output_tokens;
1215
+ if (usage.cache_read_input_tokens != null) output.usage.cacheRead = usage.cache_read_input_tokens;
1216
+ if (usage.cache_creation_input_tokens != null) output.usage.cacheWrite = usage.cache_creation_input_tokens;
1217
+ const reasoning = usage.output_tokens_details?.thinking_tokens;
1218
+ if (reasoning != null) output.usage.reasoning = reasoning;
1219
+ output.usage.totalTokens = output.usage.input + output.usage.output + output.usage.cacheRead + output.usage.cacheWrite;
1220
+ calculateCost(model, output.usage);
1221
+ const promptTokens = output.usage.input + output.usage.cacheRead + output.usage.cacheWrite;
1222
+ const report = { cachePct: promptTokens > 0 ? Math.round(output.usage.cacheRead / promptTokens * 100) : 0 };
1223
+ if (reasoning != null) report.reasoning = reasoning;
1224
+ return report;
1225
+ }
1226
+
1227
+
1228
+ // ../../node_modules/pi-claude-agent-sdk/src/index.ts
1229
+ var DEBUG = process.env.CLAUDE_BRIDGE_DEBUG === "1";
1230
+
1231
+ var RECORD_STREAM_PATH = process.env.CLAUDE_BRIDGE_RECORD_STREAM;
1232
+
1233
+ var CLAUDE_MD_EXCLUDES = ["**/CLAUDE.md", "**/.claude/rules/**"];
1234
+
1235
+ if (DEBUG) {
1236
+ try {
1237
+ mkdirSync2(dirname3(DEBUG_LOG_PATH), { recursive: true });
1238
+ } catch {
1239
+ }
1240
+ }
1241
+
1242
+ var moduleInstanceId = Math.random().toString(36).slice(2, 8);
1243
+
1244
+ function debug(...args) {
1245
+ if (!DEBUG) return;
1246
+ const ts = (/* @__PURE__ */ new Date()).toISOString();
1247
+ const fmt = (a) => {
1248
+ if (typeof a === "string") return a;
1249
+ if (a instanceof Error) return `${a.name}: ${a.message}${a.stack ? "\n" + a.stack : ""}`;
1250
+ return JSON.stringify(a);
1251
+ };
1252
+ const msg = args.map(fmt).join(" ");
1253
+ appendFileSync(DEBUG_LOG_PATH, `[${ts}] [${moduleInstanceId}] ${msg}
1254
+ `);
1255
+ }
1256
+
1257
+ var nextCliDebugSeq = 1;
1258
+
1259
+ function makeCliDebugOptions(tag) {
1260
+ if (!DEBUG) return {};
1261
+ const seq = nextCliDebugSeq++;
1262
+ const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
1263
+ const logDir = join4(dirname3(DEBUG_LOG_PATH), "cc-cli-logs");
1264
+ try {
1265
+ mkdirSync2(logDir, { recursive: true });
1266
+ } catch {
1267
+ }
1268
+ const debugFile = join4(logDir, `${ts}-${tag}-${seq}.log`);
1269
+ debug(`cli-debug: ${tag} #${seq} \u2192 ${debugFile}`);
1270
+ return {
1271
+ debug: true,
1272
+ debugFile,
1273
+ stderr: (data) => {
1274
+ for (const line of data.split(/\r?\n/)) {
1275
+ if (line) debug(`[cli-stderr ${tag}#${seq}] ${line}`);
1276
+ }
1277
+ }
1278
+ };
1279
+ }
1280
+
1281
+ function diagDump(label, data) {
1282
+ const ts = (/* @__PURE__ */ new Date()).toISOString();
1283
+ const entry = { ts, moduleInstanceId, label, ...data };
1284
+ mkdirSync2(dirname3(DIAG_LOG_PATH), { recursive: true });
1285
+ appendFileSync(DIAG_LOG_PATH, JSON.stringify(entry) + "\n");
1286
+ debug(`DIAG: ${label} (see ${DIAG_LOG_PATH})`);
1287
+ }
1288
+
1289
+ var ACTIVE_STREAM_SIMPLE_KEY = /* @__PURE__ */ Symbol("claude-bridge:activeStreamSimple");
1290
+
1291
+ var MODELS = buildModels(getModels("anthropic"));
1292
+
1293
+ var providerSettings = {};
1294
+
1295
+ var longContextSettings = { plan: "max", longContextExtraUsage: false };
1296
+
1297
+ function errorMessage(err) {
1298
+ if (err instanceof Error) return err.message;
1299
+ if (err && typeof err === "object") {
1300
+ const obj = err;
1301
+ if (typeof obj.message === "string") return obj.message;
1302
+ if (typeof obj.error === "string") return obj.error;
1303
+ try {
1304
+ return JSON.stringify(err);
1305
+ } catch {
1306
+ }
1307
+ }
1308
+ return String(err);
1309
+ }
1310
+
1311
+ function readCarriedAttachments(sessionId, cwd) {
1312
+ try {
1313
+ const previous = openSession({ sessionId, projectPath: cwd, claudeDir: process.env.CLAUDE_CONFIG_DIR });
1314
+ return collectCarriedAttachments(previous.records);
1315
+ } catch (error) {
1316
+ debug(`WARNING: could not read attachments from session ${sessionId.slice(0, 8)}:`, error);
1317
+ return [];
1318
+ }
1319
+ }
1320
+
1321
+ function sessionKey(piSessionId) {
1322
+ return piSessionId ?? "(none)";
1323
+ }
1324
+
1325
+ var sharedSessions = /* @__PURE__ */ new Map();
1326
+
1327
+ function sessionStateFor(piSessionId) {
1328
+ return sharedSessions.get(sessionKey(piSessionId)) ?? null;
1329
+ }
1330
+
1331
+ function setSessionStateFor(piSessionId, state) {
1332
+ if (state === null) sharedSessions.delete(sessionKey(piSessionId));
1333
+ else sharedSessions.set(sessionKey(piSessionId), state);
1334
+ }
1335
+
1336
+ var historyRewrittenBySession = /* @__PURE__ */ new Set();
1337
+
1338
+ var MARK_REBUILD_HOOKS_KEY = /* @__PURE__ */ Symbol.for("claude-bridge:markRebuildHooks");
1339
+
1340
+ var markRebuildHooks = globalThis[MARK_REBUILD_HOOKS_KEY] ?? /* @__PURE__ */ new Set();
1341
+
1342
+ globalThis[MARK_REBUILD_HOOKS_KEY] = markRebuildHooks;
1343
+
1344
+ function markRebuildForSession(piSession, event) {
1345
+ const key = sessionKey(piSession);
1346
+ const state = sharedSessions.get(key);
1347
+ if (!state) {
1348
+ debug(`${event}: history rewritten, no session to mark yet`);
1349
+ } else {
1350
+ sharedSessions.set(key, { ...state, needsRebuild: true });
1351
+ debug(`${event}: marking needsRebuild on session ${state.sessionId.slice(0, 8)}`);
1352
+ }
1353
+ if (piSession) historyRewrittenBySession.add(piSession);
1354
+ armStaleContexts();
1355
+ }
1356
+
1357
+ function armStaleContexts() {
1358
+ for (const c of activeQueryContexts) {
1359
+ if (c.piSessionId && historyRewrittenBySession.has(c.piSessionId)) c.historyStale = true;
1360
+ }
1361
+ }
1362
+
1363
+ markRebuildHooks.add(markRebuildForSession);
1364
+
1365
+ function sponsorMarkRebuildForSession(piSession, event) {
1366
+ for (const hook of markRebuildHooks) hook(piSession, event);
1367
+ }
1368
+
1369
+ function convertAndImportMessages(session, messages, customToolNameToSdk, carried, dropThinking = false) {
1370
+ const { anthropicMessages, sanitizedIds, dropped } = convertPiMessages(messages, customToolNameToSdk, dropThinking);
1371
+ debug(`convertAndImportMessages: ${messages.length} pi msgs \u2192 ${anthropicMessages.length} anthropic msgs`);
1372
+ debug(`convertAndImportMessages: imported roles:`, anthropicMessages.map((m, i) => {
1373
+ const c = m.content;
1374
+ if (typeof c === "string") return `[${i}]${m.role}:text`;
1375
+ if (Array.isArray(c)) return `[${i}]${m.role}:${c.map((b) => b.type).join("+")}`;
1376
+ return `[${i}]${m.role}:?`;
1377
+ }).join(" "));
1378
+ const droppedParts = [
1379
+ dropped.thinking ? `${dropped.thinking} thinking (${[...dropped.providers].sort().join(", ")})` : "",
1380
+ dropped.abortedTurns ? `${dropped.abortedTurns} aborted turn(s)` : "",
1381
+ ...[...dropped.other].map(([type, n]) => `${n} ${type}`)
1382
+ ].filter(Boolean);
1383
+ if (droppedParts.length > 0) {
1384
+ debug(`convertAndImportMessages: dropped ${droppedParts.join(", ")}`);
1385
+ }
1386
+ if (sanitizedIds.size > 0) {
1387
+ debug(
1388
+ `convertAndImportMessages: sanitized ${sanitizedIds.size} tool IDs:`,
1389
+ [...sanitizedIds.entries()].map(([orig, clean]) => orig === clean ? orig : `${orig}\u2192${clean}`).join(", ")
1390
+ );
1391
+ }
1392
+ const repaired = repairToolPairing(anthropicMessages);
1393
+ if (repaired.length !== anthropicMessages.length) {
1394
+ debug(`convertAndImportMessages: repairToolPairing ${anthropicMessages.length} \u2192 ${repaired.length} msgs`);
1395
+ }
1396
+ const placed = carried?.length ? placeCarriedAttachments(carried, repaired) : void 0;
1397
+ if (placed?.skipped.length) {
1398
+ debug(`convertAndImportMessages: dropped ${placed.skipped.length} carried attachment(s): ${placed.skipped.join("; ")}`);
1399
+ }
1400
+ if (placed?.attachments.length) {
1401
+ debug(`convertAndImportMessages: carrying ${placed.attachments.length} attachment(s) across the rebuild`);
1402
+ }
1403
+ if (repaired.length) {
1404
+ session.importMessages(repaired, placed?.attachments.length ? { attachments: placed.attachments } : void 0);
1405
+ }
1406
+ }
1407
+
1408
+ function extractAllToolResults2(context) {
1409
+ const { results, stopIdx } = extractAllToolResults(context.messages);
1410
+ debug(`extractAllToolResults: ${results.length} results from ${context.messages.length} msgs, stopped at index ${stopIdx}`);
1411
+ debug(`extractAllToolResults: all msg roles:`, context.messages.map((m, i) => `[${i}]${m.role}`).join(" "));
1412
+ for (let r = 0; r < results.length; r++) {
1413
+ debug(`extractAllToolResults: result[${r}] id=${results[r].toolCallId}${results[r].isError ? " ERROR" : ""} preview:`, JSON.stringify(results[r].content).slice(0, 150));
1414
+ }
1415
+ return results;
1416
+ }
1417
+
1418
+ function turnStart(messages) {
1419
+ let i = messages.length;
1420
+ while (i > 0 && messages[i - 1].role === "user") i--;
1421
+ return i;
1422
+ }
1423
+
1424
+ function extractUserPrompt(messages) {
1425
+ const turn = messages.slice(turnStart(messages));
1426
+ if (turn.length === 0) return null;
1427
+ return turn.map((m) => typeof m.content === "string" ? m.content : messageContentToText(m.content)).filter((text) => text).join("\n");
1428
+ }
1429
+
1430
+ function extractUserPromptBlocks(messages) {
1431
+ const turn = messages.slice(turnStart(messages));
1432
+ if (turn.length === 0) return null;
1433
+ let hasImage = false;
1434
+ const blocks = [];
1435
+ for (const message of turn) {
1436
+ const content = typeof message.content === "string" ? [{ type: "text", text: message.content }] : message.content;
1437
+ if (!Array.isArray(content)) {
1438
+ throw new Error(
1439
+ `extractUserPromptBlocks: user message content must be a string or block array, got ${typeof content} \u2014 likely a malformed message from another extension`
1440
+ );
1441
+ }
1442
+ for (const block of content) {
1443
+ if (block.type === "text" && block.text) {
1444
+ blocks.push({ type: "text", text: block.text });
1445
+ } else if (block.type === "image") {
1446
+ if (!block.data || !block.mimeType) {
1447
+ debug(`image block missing data or mimeType, skipping: keys=${Object.keys(block).join(",")}`);
1448
+ continue;
1449
+ }
1450
+ debug(`image block: mimeType=${block.mimeType}, data length=${block.data.length}`);
1451
+ hasImage = true;
1452
+ blocks.push({
1453
+ type: "image",
1454
+ source: {
1455
+ type: "base64",
1456
+ media_type: block.mimeType,
1457
+ data: block.data
1458
+ }
1459
+ });
1460
+ }
1461
+ }
1462
+ }
1463
+ debug(`extractUserPromptBlocks: ${turn.length} msgs in turn, ${blocks.length} blocks, types=${blocks.map((b) => b.type).join(",")}`);
1464
+ return hasImage ? blocks : null;
1465
+ }
1466
+
1467
+ function newAssistantOutput(model, text, stopReason, errorMessage2) {
1468
+ return {
1469
+ role: "assistant",
1470
+ content: text ? [{ type: "text", text }] : [],
1471
+ api: model.api,
1472
+ provider: model.provider,
1473
+ model: model.id,
1474
+ usage: {
1475
+ input: 0,
1476
+ output: 0,
1477
+ cacheRead: 0,
1478
+ cacheWrite: 0,
1479
+ totalTokens: 0,
1480
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }
1481
+ },
1482
+ stopReason,
1483
+ ...errorMessage2 ? { errorMessage: errorMessage2 } : {},
1484
+ timestamp: Date.now()
1485
+ };
1486
+ }
1487
+
1488
+ function extractStandalonePrompt(context) {
1489
+ if (context.tools?.length) {
1490
+ throw new Error(`standalone provider requests do not support tools (got ${context.tools.length})`);
1491
+ }
1492
+ const messages = context.messages;
1493
+ if (messages.length !== 1 || messages[0].role !== "user") {
1494
+ throw new Error(
1495
+ `standalone provider request expected exactly 1 user message, got ${messages.length} (${messages.map((m) => m.role).join(",")})`
1496
+ );
1497
+ }
1498
+ const promptText = extractUserPrompt(messages);
1499
+ if (!promptText) throw new Error("standalone provider request prompt is empty");
1500
+ return promptText;
1501
+ }
1502
+
1503
+ function isStandaloneRequest(context, options) {
1504
+ return options?.cacheRetention === "none" && context.tools === void 0 && context.messages.length === 1 && context.messages[0]?.role === "user";
1505
+ }
1506
+
1507
+ function resultErrorText(message) {
1508
+ const result = message;
1509
+ if (result.subtype === "success") return result.is_error ? result.result || "Claude Code reported an error" : void 0;
1510
+ if (Array.isArray(result.errors) && result.errors.length) return result.errors.map(String).join("\n");
1511
+ if (typeof result.error === "string") return result.error;
1512
+ return `Claude Code failed: ${result.subtype ?? "unknown result"}`;
1513
+ }
1514
+
1515
+ function describeRateLimitFailure(rejection, failure) {
1516
+ const kind = rejection.rateLimitType ? ` (${rejection.rateLimitType})` : "";
1517
+ const resets = rejection.resetsAt ? ` \u2014 resets ${new Date(rejection.resetsAt * 1e3).toLocaleTimeString()}` : "";
1518
+ return `Claude rate limit${kind}${resets}: ${failure}`;
1519
+ }
1520
+
1521
+ function standaloneStreamFn(model, context, options) {
1522
+ const stream = createAssistantMessageEventStream();
1523
+ void runStandaloneRequest(model, context, options, stream);
1524
+ return stream;
1525
+ }
1526
+
1527
+ async function runStandaloneRequest(model, context, options, stream) {
1528
+ let sdkQuery;
1529
+ let wasAborted = false;
1530
+ const onAbort = () => {
1531
+ wasAborted = true;
1532
+ void sdkQuery?.interrupt().catch(() => {
1533
+ });
1534
+ try {
1535
+ sdkQuery?.close();
1536
+ } catch {
1537
+ }
1538
+ };
1539
+ try {
1540
+ const promptText = extractStandalonePrompt(context);
1541
+ const cwd = options?.cwd ?? process.cwd();
1542
+ const standaloneProviderSettings = loadConfig(cwd).provider;
1543
+ const claudeExecutableResolution = resolveClaudeCodeExecutable(model.id, standaloneProviderSettings?.pathToClaudeCodeExecutable);
1544
+ if (claudeExecutableResolution.error) throw new Error(claudeExecutableResolution.error);
1545
+ const claudeExecutable = claudeExecutableResolution.path;
1546
+ const cliModel = claudeCodeModelId(model, longContextSettings);
1547
+ const mapped = options?.reasoning ? model.thinkingLevelMap?.[options.reasoning] : void 0;
1548
+ const effort = options?.reasoning ? mapped === void 0 ? REASONING_TO_EFFORT[options.reasoning] : VALID_EFFORTS.has(mapped) ? mapped : void 0 : void 0;
1549
+ const extraArgs = {};
1550
+ if (effort || adaptiveThinkingAlwaysOn(model.id)) extraArgs["thinking-display"] = "summarized";
1551
+ debug(`standalone: spawn model=${cliModel} registeredModel=${model.id} promptLen=${promptText.length} effort=${effort ?? "default"}`);
1552
+ const childEnv = await resolveClaudeChildEnv(piModelRegistry);
1553
+ sdkQuery = query({
1554
+ prompt: promptText,
1555
+ options: {
1556
+ cwd,
1557
+ env: childEnv,
1558
+ settings: { autoMemoryEnabled: false },
1559
+ tools: [],
1560
+ strictMcpConfig: true,
1561
+ settingSources: [],
1562
+ skills: [],
1563
+ persistSession: false,
1564
+ systemPrompt: context.systemPrompt,
1565
+ model: cliModel,
1566
+ maxTurns: 1,
1567
+ extraArgs,
1568
+ ...effort ? { effort } : {},
1569
+ ...claudeExecutable ? { pathToClaudeCodeExecutable: claudeExecutable } : {},
1570
+ ...makeCliDebugOptions("standalone")
1571
+ }
1572
+ });
1573
+ if (options?.signal) {
1574
+ if (options.signal.aborted) onAbort();
1575
+ else options.signal.addEventListener("abort", onAbort, { once: true });
1576
+ }
1577
+ let assistantText = "";
1578
+ let finalText = "";
1579
+ let errorText;
1580
+ let resultUsage;
1581
+ let firstEventLogged = false;
1582
+ for await (const message of sdkQuery) {
1583
+ if (!firstEventLogged) {
1584
+ debug(`standalone: first event type=${message.type}`);
1585
+ firstEventLogged = true;
1586
+ }
1587
+ if (wasAborted) break;
1588
+ if (message.type === "assistant") {
1589
+ for (const block of message.message?.content ?? []) {
1590
+ if (block.type === "text" && typeof block.text === "string") assistantText += block.text;
1591
+ }
1592
+ } else if (message.type === "result") {
1593
+ logServedContextWindow("standalone", message, model);
1594
+ errorText = resultErrorText(message);
1595
+ resultUsage = message.usage;
1596
+ if (!errorText && message.subtype === "success") finalText = message.result || assistantText;
1597
+ }
1598
+ }
1599
+ if (wasAborted) {
1600
+ const output2 = newAssistantOutput(model, "", "aborted", "Operation aborted");
1601
+ debug("standalone: aborted");
1602
+ stream.push({ type: "error", reason: "aborted", error: output2 });
1603
+ stream.end();
1604
+ return;
1605
+ }
1606
+ const text = finalText || assistantText;
1607
+ if (errorText || !text.trim()) {
1608
+ const msg = errorText ?? "Claude Code standalone request returned empty text";
1609
+ debug(`standalone: error ${msg}`);
1610
+ stream.push({ type: "error", reason: "error", error: newAssistantOutput(model, "", "error", msg) });
1611
+ stream.end();
1612
+ return;
1613
+ }
1614
+ const output = newAssistantOutput(model, text, "stop");
1615
+ if (resultUsage) recordUsage(output, resultUsage, model);
1616
+ debug(`standalone: done textLen=${text.length}`);
1617
+ stream.push({ type: "done", reason: "stop", message: output });
1618
+ stream.end();
1619
+ } catch (err) {
1620
+ const msg = errorMessage(err);
1621
+ debug("runStandaloneRequest threw; pushing terminal error", err);
1622
+ stream.push({ type: "error", reason: "error", error: newAssistantOutput(model, "", "error", msg) });
1623
+ stream.end();
1624
+ } finally {
1625
+ options?.signal?.removeEventListener("abort", onAbort);
1626
+ try {
1627
+ sdkQuery?.close();
1628
+ } catch {
1629
+ }
1630
+ }
1631
+ }
1632
+
1633
+ function verifyWrittenSession2(jsonlPath, expectedSessionId, expectedRecordCount, cwd) {
1634
+ const warnings = verifyWrittenSession(jsonlPath, expectedSessionId, expectedRecordCount);
1635
+ for (const msg of warnings) {
1636
+ debug(`WARNING session verify: ${msg}`);
1637
+ piUI?.notify(
1638
+ `Session file issue: ${msg}
1639
+ cwd=${cwd} realpath=${safeRealpath(cwd)} CLAUDE_CONFIG_DIR=${process.env.CLAUDE_CONFIG_DIR ?? "(unset)"}
1640
+ Please copy and paste this message into a new issue at https://github.com/pi-pod/pi-claude-agent-sdk/issues/new` + (DEBUG ? ` and attach ${DEBUG_LOG_PATH}` : ` (rerun with CLAUDE_BRIDGE_DEBUG=1 to capture a debug log)`),
1641
+ "warning"
1642
+ );
1643
+ diagDump("session_verify_fail", { msg, jsonlPath, cwd, realpath: safeRealpath(cwd), claudeConfigDir: process.env.CLAUDE_CONFIG_DIR ?? null });
1644
+ }
1645
+ }
1646
+
1647
+ function safeRealpath(p) {
1648
+ try {
1649
+ return realpathSync(p);
1650
+ } catch (e) {
1651
+ return `<failed: ${e.message}>`;
1652
+ }
1653
+ }
1654
+
1655
+ function debugSessionPaths(label, cwd, jsonlPath) {
1656
+ const realCwd = safeRealpath(cwd);
1657
+ let fileSize = null;
1658
+ let fileExists = false;
1659
+ try {
1660
+ const st = statSync2(jsonlPath);
1661
+ fileExists = true;
1662
+ fileSize = st.size;
1663
+ } catch {
1664
+ }
1665
+ debug(`${label}: cwd=${cwd}`);
1666
+ if (realCwd !== cwd) debug(`${label}: realpath(cwd)=${realCwd} (DIFFERS \u2014 symlink-resolved path is what CC SDK uses)`);
1667
+ debug(`${label}: jsonlPath=${jsonlPath}`);
1668
+ debug(`${label}: fileExists=${fileExists}${fileSize != null ? ` size=${fileSize}` : ""}`);
1669
+ debug(`${label}: env.CLAUDE_CONFIG_DIR=${process.env.CLAUDE_CONFIG_DIR ?? "(unset)"} HOME=${process.env.HOME ?? "(unset)"}`);
1670
+ }
1671
+
1672
+ function syncSharedSession(messages, cwd, customToolNameToSdk, modelId, piSessionId) {
1673
+ const sharedSession = sessionStateFor(piSessionId);
1674
+ const history = nonSystemMessages(messages);
1675
+ const priorMessages = history.slice(0, turnStart(history));
1676
+ if (sharedSession && !sharedSession.needsRebuild && priorMessages.length >= sharedSession.cursor) {
1677
+ const missed = priorMessages.slice(sharedSession.cursor);
1678
+ const trailingAssistantOnly = missed.length === 1 && missed[0].role === "assistant";
1679
+ if (missed.length === 0 || trailingAssistantOnly) {
1680
+ if (trailingAssistantOnly) {
1681
+ setSessionStateFor(piSessionId, { ...sharedSession, cursor: priorMessages.length, cwd });
1682
+ debug(`Case 3: advanced cursor past trailing assistant, resuming session ${sharedSession.sessionId.slice(0, 8)}, cursor=${priorMessages.length}`);
1683
+ } else {
1684
+ debug(`Case 3: resuming session ${sharedSession.sessionId.slice(0, 8)}, cursor=${sharedSession.cursor}`);
1685
+ }
1686
+ debug(`syncResult: path=reuse sessionId=${sharedSession.sessionId} cursor=${sharedSession?.cursor}`);
1687
+ return { sessionId: sharedSession.sessionId };
1688
+ }
1689
+ }
1690
+ if (sharedSession && !sharedSession.needsRebuild && priorMessages.length < sharedSession.cursor) {
1691
+ debug(`Case 1 synthetic: clean start for shorter context, preserving shared session ${sharedSession.sessionId.slice(0, 8)}, cursor=${sharedSession.cursor}`);
1692
+ debug(`syncResult: path=clean-start preserve-shared sessionId=${sharedSession.sessionId} cursor=${sharedSession.cursor}`);
1693
+ return { sessionId: null, preserveSharedSession: true };
1694
+ }
1695
+ if (priorMessages.length === 0) {
1696
+ debug(`Case 1: clean start, ${history.length} total messages`);
1697
+ debug(`syncResult: path=clean-start`);
1698
+ return { sessionId: null };
1699
+ }
1700
+ const previousSessionId = sharedSession?.sessionId;
1701
+ const previousCursor = sharedSession?.cursor ?? 0;
1702
+ const preserveId = previousSessionId !== void 0 && !sharedSession?.forceRotate;
1703
+ const carried = previousSessionId !== void 0 ? readCarriedAttachments(previousSessionId, cwd) : [];
1704
+ if (preserveId) {
1705
+ deleteSession(previousSessionId, cwd, process.env.CLAUDE_CONFIG_DIR);
1706
+ }
1707
+ const session = createSession({
1708
+ projectPath: cwd,
1709
+ claudeDir: process.env.CLAUDE_CONFIG_DIR,
1710
+ ...preserveId ? { sessionId: previousSessionId } : {},
1711
+ ...modelId ? { model: modelId } : {}
1712
+ });
1713
+ convertAndImportMessages(session, priorMessages, customToolNameToSdk, carried, thinkingBoundToPrefix(modelId ?? ""));
1714
+ session.save();
1715
+ verifyWrittenSession2(session.jsonlPath, session.sessionId, session.records.length, cwd);
1716
+ setSessionStateFor(piSessionId, { sessionId: session.sessionId, cursor: priorMessages.length, cwd, piSessionId: piSessionId ?? void 0 });
1717
+ if (previousSessionId === void 0) {
1718
+ debug(`Case 2: first turn with ${priorMessages.length} prior messages \u2192 session ${session.sessionId.slice(0, 8)}, ${session.records.length} records`);
1719
+ } else if (preserveId) {
1720
+ const missedCount = priorMessages.length - previousCursor;
1721
+ debug(`Case 4: ${missedCount} missed messages, ${priorMessages.length} total \u2192 rewrote session ${session.sessionId.slice(0, 8)} (same id), ${session.records.length} records`);
1722
+ } else {
1723
+ debug(`Case 4 post-abort: ${priorMessages.length} total \u2192 new session ${session.sessionId.slice(0, 8)} (was ${previousSessionId.slice(0, 8)}, rotated to avoid race with orphan writer), ${session.records.length} records`);
1724
+ }
1725
+ debugSessionPaths(`${session.sessionId.slice(0, 8)}`, cwd, session.jsonlPath);
1726
+ debug(`syncResult: path=rebuild sessionId=${session.sessionId} priors=${priorMessages.length} ${previousSessionId === void 0 ? "first" : preserveId ? "preserved" : "rotated-post-abort"}`);
1727
+ return { sessionId: session.sessionId };
1728
+ }
1729
+
1730
+ var queryImpl = query;
1731
+
1732
+ var __test = {
1733
+ setQuery(fn) {
1734
+ queryImpl = fn ?? query;
1735
+ },
1736
+ resetSharedSession(piSessionId) {
1737
+ if (piSessionId === void 0) sharedSessions.clear();
1738
+ else setSessionStateFor(piSessionId, null);
1739
+ historyRewrittenBySession.clear();
1740
+ },
1741
+ markRebuildForSession,
1742
+ getHistoryRewritten: () => historyRewrittenBySession.size > 0,
1743
+ historyRewrittenBySession,
1744
+ armStaleContexts,
1745
+ discardRewrittenQuery,
1746
+ contextForToolResults,
1747
+ isQueryAbandoned: (q) => abandonedQueries.has(q),
1748
+ get activeQueryContexts() {
1749
+ return activeQueryContexts;
1750
+ },
1751
+ setSharedSession(piSessionId, state) {
1752
+ setSessionStateFor(piSessionId, state);
1753
+ },
1754
+ getSharedSession(piSessionId = null) {
1755
+ return sessionStateFor(piSessionId);
1756
+ },
1757
+ setPiModelRegistry(registry) {
1758
+ piModelRegistry = registry;
1759
+ },
1760
+ setPiUI(ui) {
1761
+ piUI = ui;
1762
+ },
1763
+ toBridgeContext,
1764
+ syncSharedSession,
1765
+ extractUserPromptBlocks,
1766
+ consumeQuery,
1767
+ finalizeCurrentStream,
1768
+ resultErrorText,
1769
+ deliverToolResults,
1770
+ drainForAbort,
1771
+ CC_CHILD_ENV,
1772
+ buildMcpServers,
1773
+ isStandaloneRequest,
1774
+ extractStandalonePrompt,
1775
+ get promptCaptures() {
1776
+ return promptCaptures;
1777
+ }
1778
+ };
1779
+
1780
+ function piToolNameFor(name, customToolNameToPi) {
1781
+ return customToolNameToPi.get(name) ?? customToolNameToPi.get(name.toLowerCase());
1782
+ }
1783
+
1784
+ var SDK_KEY_RENAMES = {
1785
+ read: { file_path: "path" },
1786
+ write: { file_path: "path" },
1787
+ edit: { file_path: "path", old_string: "oldText", new_string: "newText", old_text: "oldText", new_text: "newText" }
1788
+ };
1789
+
1790
+ function mapToolArgs(toolName, args) {
1791
+ const input = args ?? {};
1792
+ const renames = SDK_KEY_RENAMES[toolName.toLowerCase()];
1793
+ const result = {};
1794
+ for (const [key, value] of Object.entries(input)) {
1795
+ const piKey = renames?.[key] ?? key;
1796
+ if (!(piKey in result)) result[piKey] = value;
1797
+ }
1798
+ if (toolName.toLowerCase() === "bash" && result.timeout == null) {
1799
+ result.timeout = 120;
1800
+ }
1801
+ return result;
1802
+ }
1803
+
1804
+ var piUI = null;
1805
+
1806
+ var piMode = null;
1807
+
1808
+ var piModelRegistry = null;
1809
+
1810
+ var activeQueryContexts = /* @__PURE__ */ new Set();
1811
+
1812
+ var pendingNotices = [];
1813
+
1814
+ function showStartupNoticeOnce() {
1815
+ if (pendingNotices.length === 0 || piMode !== "tui") return;
1816
+ const notices = pendingNotices;
1817
+ pendingNotices = [];
1818
+ const path = markStartupNoticeShown();
1819
+ const title = `\x1B[33mWelcome to pi-claude-agent-sdk\x1B[39m \u2014 settings live in ${path}`;
1820
+ const bullets = [...notices, "This message only appears once. See README.md for more."].map((n) => `\u2022 ${n}`);
1821
+ piUI?.notify([title, ...bullets, "\u2500".repeat(64)].join("\n"), "info");
1822
+ }
1823
+
1824
+ var promptCaptures = sharedPromptCaptures((diagnostic) => {
1825
+ const first = diagnostic.matches[0];
1826
+ debug(
1827
+ `prompt-capture: no match for ${diagnostic.systemPrompt.length}-char system prompt. ` + (first ? `closest known (${first.key.length}-char) shares its first ${first.firstDivergent} chars and diverges at offset ${first.firstDivergent}: ` + JSON.stringify(diagnostic.systemPrompt.slice(first.firstDivergent - 40, first.firstDivergent + 60)) : "no known captures to compare against.") + ` known keys=${diagnostic.matches.length}`
1828
+ );
1829
+ });
1830
+
1831
+ function reportLeaks(label) {
1832
+ const pendingCalls = [...activeQueryContexts].reduce((n, c) => n + c.pendingToolCalls.size, 0);
1833
+ const liveStreams = [...activeQueryContexts].filter((c) => c.promptStream !== null).length;
1834
+ if (activeQueryContexts.size === 0 && pendingCalls === 0 && liveStreams === 0) return;
1835
+ debug(
1836
+ `WARNING: ${label} left state behind \u2014 contexts=${activeQueryContexts.size} pendingToolCalls=${pendingCalls} promptStreams=${liveStreams}`
1837
+ );
1838
+ }
1839
+
1840
+ function contextForToolResults(results) {
1841
+ for (const result of results) {
1842
+ const id = result.toolCallId;
1843
+ if (!id) continue;
1844
+ for (const queryCtx of activeQueryContexts) {
1845
+ if (queryCtx.pendingToolCalls.has(id) || queryCtx.pendingResults.has(id) || queryCtx.turnToolCallIds.includes(id)) {
1846
+ return queryCtx;
1847
+ }
1848
+ }
1849
+ }
1850
+ return void 0;
1851
+ }
1852
+
1853
+ function resolveMcpTools(context) {
1854
+ const mcpTools = [];
1855
+ const customToolNameToSdk = /* @__PURE__ */ new Map();
1856
+ const customToolNameToPi = /* @__PURE__ */ new Map();
1857
+ if (!context.tools) return { mcpTools, customToolNameToSdk, customToolNameToPi };
1858
+ for (const tool of context.tools) {
1859
+ const sdkName = `${MCP_TOOL_PREFIX}${tool.name}`;
1860
+ mcpTools.push(tool);
1861
+ customToolNameToSdk.set(tool.name, sdkName);
1862
+ customToolNameToSdk.set(tool.name.toLowerCase(), sdkName);
1863
+ customToolNameToPi.set(sdkName, tool.name);
1864
+ customToolNameToPi.set(sdkName.toLowerCase(), tool.name);
1865
+ }
1866
+ return { mcpTools, customToolNameToSdk, customToolNameToPi };
1867
+ }
1868
+
1869
+ function buildMcpServers(tools, queryCtx) {
1870
+ if (!tools.length) return void 0;
1871
+ const mcpTools = tools.map((tool) => ({
1872
+ name: tool.name,
1873
+ description: tool.description,
1874
+ inputSchema: tool.parameters,
1875
+ handler: async (toolCallId) => {
1876
+ if (queryCtx.pendingResults.has(toolCallId)) {
1877
+ const result = queryCtx.pendingResults.get(toolCallId);
1878
+ queryCtx.pendingResults.delete(toolCallId);
1879
+ debug(`mcp handler: ${tool.name} [${toolCallId}] \u2192 resolved from queue (${queryCtx.pendingResults.size} remaining)`);
1880
+ return result;
1881
+ }
1882
+ debug(`mcp handler: ${tool.name} [${toolCallId}] \u2192 waiting`);
1883
+ return new Promise((resolve) => {
1884
+ queryCtx.pendingToolCalls.set(toolCallId, { toolName: tool.name, resolve });
1885
+ });
1886
+ }
1887
+ }));
1888
+ return { [MCP_SERVER_NAME]: createToolServer(MCP_SERVER_NAME, mcpTools) };
1889
+ }
1890
+
1891
+ function recordUsage(output, usage, model) {
1892
+ const { cachePct, reasoning } = updateUsage(output, usage, model);
1893
+ const reasoningText = reasoning == null ? "" : ` reasoning=${reasoning}`;
1894
+ debug(`usage: in=${output.usage.input} out=${output.usage.output} cacheRead=${output.usage.cacheRead} cacheWrite=${output.usage.cacheWrite} total=${output.usage.totalTokens}${reasoningText} cachePct=${cachePct}% model=${model.id}`);
1895
+ }
1896
+
1897
+ function logServedContextWindow(label, message, model) {
1898
+ const modelUsage = message.modelUsage;
1899
+ if (!modelUsage) return;
1900
+ for (const [k, v] of Object.entries(modelUsage)) {
1901
+ debug(`${label}: served contextWindow=${v.contextWindow ?? "?"} maxOutputTokens=${v.maxOutputTokens ?? "?"} servedModel=${k} registered=${model.contextWindow}`);
1902
+ }
1903
+ }
1904
+
1905
+ var REASONING_TO_EFFORT = {
1906
+ minimal: "low",
1907
+ low: "low",
1908
+ medium: "medium",
1909
+ high: "high",
1910
+ xhigh: "max"
1911
+ };
1912
+
1913
+ var VALID_EFFORTS = /* @__PURE__ */ new Set(["low", "medium", "high", "xhigh", "max"]);
1914
+
1915
+ function mapStopReason(reason) {
1916
+ switch (reason) {
1917
+ case "tool_use":
1918
+ return "toolUse";
1919
+ case "max_tokens":
1920
+ return "length";
1921
+ case "end_turn":
1922
+ default:
1923
+ return "stop";
1924
+ }
1925
+ }
1926
+
1927
+ function parsePartialJson(input, fallback) {
1928
+ if (!input) return fallback;
1929
+ try {
1930
+ return JSON.parse(input);
1931
+ } catch {
1932
+ return fallback;
1933
+ }
1934
+ }
1935
+
1936
+ var completedStreams = /* @__PURE__ */ new WeakSet();
1937
+
1938
+ function markStreamComplete(stream) {
1939
+ if (stream) completedStreams.add(stream);
1940
+ }
1941
+
1942
+ function claimCurrentPiStream(stream, label, c) {
1943
+ if (c.currentPiStream && !completedStreams.has(c.currentPiStream)) {
1944
+ debug(`WARNING: currentPiStream overwritten before terminal event (${label}); activeQuery=${Boolean(c.activeQuery)} pendingHandlers=${c.pendingToolCalls.size}`);
1945
+ }
1946
+ c.currentPiStream = stream;
1947
+ }
1948
+
1949
+ function ensureTurnStarted(c) {
1950
+ if (!c.turnStarted && c.currentPiStream && c.turnOutput) {
1951
+ c.currentPiStream.push({ type: "start", partial: c.turnOutput });
1952
+ c.turnStarted = true;
1953
+ }
1954
+ }
1955
+
1956
+ function finalizeCurrentStream(c, stopReason) {
1957
+ if (!c.currentPiStream || !c.turnOutput) return;
1958
+ debug(`provider: finalizeCurrentStream called, stopReason=${stopReason}, turnOutput=${JSON.stringify({ stopReason: c.turnOutput.stopReason, error: c.turnOutput.errorMessage })}`);
1959
+ if (!c.turnStarted) ensureTurnStarted(c);
1960
+ const stream = c.currentPiStream;
1961
+ if (c.turnOutput.stopReason === "error") {
1962
+ stream.push({ type: "error", reason: "error", error: c.turnOutput });
1963
+ } else {
1964
+ const reason = stopReason === "length" ? "length" : "stop";
1965
+ stream.push({ type: "done", reason, message: c.turnOutput });
1966
+ }
1967
+ markStreamComplete(stream);
1968
+ stream.end();
1969
+ c.currentPiStream = null;
1970
+ }
1971
+
1972
+ function processStreamEvent(message, customToolNameToPi, model, c) {
1973
+ if (!c.currentPiStream || !c.turnOutput) return;
1974
+ c.turnSawStreamEvent = true;
1975
+ const event = message.event;
1976
+ if (event?.type === "message_start") {
1977
+ if (c.turnStreamOpen) dropAbandonedStreamBlocks(c, `restreamed as ${event.message?.id}`);
1978
+ c.turnToolCallIds = [];
1979
+ c.turnStreamMessageId = event.message?.id;
1980
+ c.turnStreamOpen = true;
1981
+ c.turnStreamBlockStart = c.turnBlocks.length;
1982
+ if (event.message?.usage) recordUsage(c.turnOutput, event.message.usage, model);
1983
+ return;
1984
+ }
1985
+ if (event?.type === "content_block_start") {
1986
+ ensureTurnStarted(c);
1987
+ if (event.content_block?.type === "text") {
1988
+ c.turnBlocks.push({ type: "text", text: "", index: event.index });
1989
+ c.currentPiStream.push({ type: "text_start", contentIndex: c.turnBlocks.length - 1, partial: c.turnOutput });
1990
+ } else if (event.content_block?.type === "thinking") {
1991
+ c.turnBlocks.push({ type: "thinking", thinking: "", thinkingSignature: "", index: event.index });
1992
+ c.currentPiStream.push({ type: "thinking_start", contentIndex: c.turnBlocks.length - 1, partial: c.turnOutput });
1993
+ } else if (event.content_block?.type === "tool_use") {
1994
+ const piName = piToolNameFor(event.content_block.name, customToolNameToPi);
1995
+ if (!piName) {
1996
+ debug(`processStreamEvent: skipping tool_use for unserved tool ${event.content_block.name} [${event.content_block.id}] \u2014 CC rejects it and retries`);
1997
+ return;
1998
+ }
1999
+ c.turnSawToolCall = true;
2000
+ c.turnToolCallIds.push(event.content_block.id);
2001
+ c.turnBlocks.push({
2002
+ type: "toolCall",
2003
+ id: event.content_block.id,
2004
+ name: piName,
2005
+ arguments: event.content_block.input ?? {},
2006
+ partialJson: "",
2007
+ index: event.index
2008
+ });
2009
+ c.currentPiStream.push({ type: "toolcall_start", contentIndex: c.turnBlocks.length - 1, partial: c.turnOutput });
2010
+ } else {
2011
+ debug("processStreamEvent: unhandled content_block_start type", event.content_block?.type);
2012
+ }
2013
+ return;
2014
+ }
2015
+ if (event?.type === "content_block_delta") {
2016
+ const index = c.turnBlocks.findIndex((b) => b.index === event.index);
2017
+ const block = c.turnBlocks[index];
2018
+ if (!block) return;
2019
+ if (event.delta?.type === "text_delta" && block.type === "text") {
2020
+ block.text += event.delta.text;
2021
+ c.currentPiStream.push({ type: "text_delta", contentIndex: index, delta: event.delta.text, partial: c.turnOutput });
2022
+ } else if (event.delta?.type === "thinking_delta" && block.type === "thinking") {
2023
+ block.thinking += event.delta.thinking;
2024
+ c.currentPiStream.push({ type: "thinking_delta", contentIndex: index, delta: event.delta.thinking, partial: c.turnOutput });
2025
+ } else if (event.delta?.type === "input_json_delta" && block.type === "toolCall") {
2026
+ block.partialJson += event.delta.partial_json;
2027
+ block.arguments = parsePartialJson(block.partialJson, block.arguments);
2028
+ c.currentPiStream.push({ type: "toolcall_delta", contentIndex: index, delta: event.delta.partial_json, partial: c.turnOutput });
2029
+ } else if (event.delta?.type === "signature_delta" && block.type === "thinking") {
2030
+ block.thinkingSignature = (block.thinkingSignature ?? "") + event.delta.signature;
2031
+ } else {
2032
+ debug("processStreamEvent: unhandled content_block_delta type", event.delta?.type);
2033
+ }
2034
+ return;
2035
+ }
2036
+ if (event?.type === "content_block_stop") {
2037
+ const index = c.turnBlocks.findIndex((b) => b.index === event.index);
2038
+ const block = c.turnBlocks[index];
2039
+ if (!block) return;
2040
+ delete block.index;
2041
+ if (block.type === "text") {
2042
+ c.currentPiStream.push({ type: "text_end", contentIndex: index, content: block.text, partial: c.turnOutput });
2043
+ } else if (block.type === "thinking") {
2044
+ c.currentPiStream.push({ type: "thinking_end", contentIndex: index, content: block.thinking, partial: c.turnOutput });
2045
+ } else if (block.type === "toolCall") {
2046
+ c.turnSawToolCall = true;
2047
+ block.arguments = mapToolArgs(
2048
+ block.name,
2049
+ parsePartialJson(block.partialJson, block.arguments)
2050
+ );
2051
+ delete block.partialJson;
2052
+ c.currentPiStream.push({ type: "toolcall_end", contentIndex: index, toolCall: block, partial: c.turnOutput });
2053
+ }
2054
+ return;
2055
+ }
2056
+ if (event?.type === "message_delta") {
2057
+ c.turnOutput.stopReason = mapStopReason(event.delta?.stop_reason);
2058
+ if (event.usage) recordUsage(c.turnOutput, event.usage, model);
2059
+ return;
2060
+ }
2061
+ if (event?.type === "message_stop") c.turnStreamOpen = false;
2062
+ if (event?.type === "message_stop" && c.turnSawToolCall) {
2063
+ c.turnOutput.stopReason = "toolUse";
2064
+ const stream = c.currentPiStream;
2065
+ stream.push({ type: "done", reason: "toolUse", message: c.turnOutput });
2066
+ markStreamComplete(stream);
2067
+ stream.end();
2068
+ c.currentPiStream = null;
2069
+ return;
2070
+ }
2071
+ if (event?.type !== "message_stop" && event?.type !== "ping") {
2072
+ debug("processStreamEvent: unhandled event type", event?.type);
2073
+ }
2074
+ }
2075
+
2076
+ function dropAbandonedStreamBlocks(c, why) {
2077
+ const dropped = c.turnBlocks.splice(c.turnStreamBlockStart);
2078
+ debug(`dropAbandonedStreamBlocks: ${why}; dropped ${dropped.length} blocks from ${c.turnStreamMessageId} types=${dropped.map((b) => b.type).join(",")}`);
2079
+ c.turnToolCallIds = [];
2080
+ c.turnSawToolCall = c.turnBlocks.some((b) => b.type === "toolCall");
2081
+ c.turnStreamOpen = false;
2082
+ }
2083
+
2084
+ function processAssistantMessage(message, model, customToolNameToPi, c) {
2085
+ const assistantMsg = message.message;
2086
+ if (!assistantMsg?.content) return;
2087
+ if (c.turnSawStreamEvent) {
2088
+ const id = assistantMsg.id;
2089
+ if (!id || !c.turnStreamMessageId || id === c.turnStreamMessageId) return;
2090
+ if (c.turnStreamOpen) dropAbandonedStreamBlocks(c, `non-streaming fallback ${id}`);
2091
+ }
2092
+ c.turnToolCallIds = [];
2093
+ debug(`processAssistantMessage fallback: ${assistantMsg.content.length} blocks, types=${assistantMsg.content.map((b) => b.type).join(",")}`);
2094
+ for (const block of assistantMsg.content) {
2095
+ if (block.type === "text" && block.text) {
2096
+ ensureTurnStarted(c);
2097
+ c.turnBlocks.push({ type: "text", text: block.text });
2098
+ const idx = c.turnBlocks.length - 1;
2099
+ c.currentPiStream?.push({ type: "text_start", contentIndex: idx, partial: c.turnOutput });
2100
+ c.currentPiStream?.push({ type: "text_delta", contentIndex: idx, delta: block.text, partial: c.turnOutput });
2101
+ c.currentPiStream?.push({ type: "text_end", contentIndex: idx, content: block.text, partial: c.turnOutput });
2102
+ } else if (block.type === "thinking") {
2103
+ ensureTurnStarted(c);
2104
+ c.turnBlocks.push({ type: "thinking", thinking: block.thinking ?? "", thinkingSignature: block.signature ?? "" });
2105
+ const idx = c.turnBlocks.length - 1;
2106
+ c.currentPiStream?.push({ type: "thinking_start", contentIndex: idx, partial: c.turnOutput });
2107
+ if (block.thinking) c.currentPiStream?.push({ type: "thinking_delta", contentIndex: idx, delta: block.thinking, partial: c.turnOutput });
2108
+ c.currentPiStream?.push({ type: "thinking_end", contentIndex: idx, content: block.thinking ?? "", partial: c.turnOutput });
2109
+ } else if (block.type === "tool_use") {
2110
+ const piName = piToolNameFor(block.name, customToolNameToPi);
2111
+ if (!piName) {
2112
+ debug(`processAssistantMessage: skipping tool_use for unserved tool ${block.name} [${block.id}] \u2014 CC rejects it and retries`);
2113
+ continue;
2114
+ }
2115
+ ensureTurnStarted(c);
2116
+ c.turnSawToolCall = true;
2117
+ c.turnToolCallIds.push(block.id);
2118
+ c.turnBlocks.push({
2119
+ type: "toolCall",
2120
+ id: block.id,
2121
+ name: piName,
2122
+ arguments: mapToolArgs(piName, block.input)
2123
+ });
2124
+ const idx = c.turnBlocks.length - 1;
2125
+ const toolBlock = c.turnBlocks[idx];
2126
+ c.currentPiStream?.push({ type: "toolcall_start", contentIndex: idx, partial: c.turnOutput });
2127
+ c.currentPiStream?.push({ type: "toolcall_end", contentIndex: idx, toolCall: toolBlock, partial: c.turnOutput });
2128
+ } else {
2129
+ debug("processAssistantMessage: unhandled block type", block.type);
2130
+ }
2131
+ }
2132
+ if (assistantMsg.usage && c.turnOutput) recordUsage(c.turnOutput, assistantMsg.usage, model);
2133
+ if (c.turnSawToolCall && c.currentPiStream && c.turnOutput) {
2134
+ c.turnOutput.stopReason = "toolUse";
2135
+ const stream = c.currentPiStream;
2136
+ stream.push({ type: "done", reason: "toolUse", message: c.turnOutput });
2137
+ markStreamComplete(stream);
2138
+ stream.end();
2139
+ c.currentPiStream = null;
2140
+ }
2141
+ }
2142
+
2143
+ async function consumeQuery(sdkQuery, customToolNameToPi, model, wasAborted, queryCtx) {
2144
+ let capturedSessionId;
2145
+ for await (const message of sdkQuery) {
2146
+ if (RECORD_STREAM_PATH) appendFileSync(RECORD_STREAM_PATH, `${JSON.stringify(message)}
2147
+ `);
2148
+ if (wasAborted()) break;
2149
+ let resultError;
2150
+ if (message.type === "result") {
2151
+ queryCtx.promptStream?.end();
2152
+ logServedContextWindow("result", message, model);
2153
+ resultError = resultErrorText(message);
2154
+ if (resultError !== void 0) {
2155
+ if (queryCtx.rateLimitRejection) {
2156
+ resultError = describeRateLimitFailure(queryCtx.rateLimitRejection, resultError);
2157
+ queryCtx.rateLimitRejection = null;
2158
+ }
2159
+ debug(`consumeQuery: error result, subtype=${message.subtype}, error=${resultError}`);
2160
+ if (queryCtx.turnOutput) {
2161
+ queryCtx.turnOutput.stopReason = "error";
2162
+ queryCtx.turnOutput.errorMessage = resultError;
2163
+ }
2164
+ }
2165
+ }
2166
+ if (message.type === "rate_limit_event") {
2167
+ const info = message.rate_limit_info;
2168
+ debug("consumeQuery: rate_limit_event", JSON.stringify(info).slice(0, 300));
2169
+ if (info?.status === "rejected") {
2170
+ queryCtx.rateLimitRejection = info;
2171
+ queryCtx.lastRateLimitWarnStep = null;
2172
+ queryCtx.lastRateLimitWarnThreshold = void 0;
2173
+ const resetsAt = info.resetsAt ? new Date(info.resetsAt * 1e3).toLocaleTimeString() : "unknown";
2174
+ piUI?.notify(`Claude rate limited (${info.rateLimitType ?? "unknown"}) \u2014 resets at ${resetsAt}`, "warning");
2175
+ } else if (info?.status === "allowed") {
2176
+ queryCtx.lastRateLimitWarnStep = null;
2177
+ queryCtx.lastRateLimitWarnThreshold = void 0;
2178
+ } else if (info?.status === "allowed_warning") {
2179
+ const percent = Math.round((info.utilization ?? 0) * 100);
2180
+ const step = Math.floor(percent / 5);
2181
+ const rose = queryCtx.lastRateLimitWarnStep === null || step > queryCtx.lastRateLimitWarnStep;
2182
+ if (rose || info.surpassedThreshold !== queryCtx.lastRateLimitWarnThreshold) {
2183
+ queryCtx.lastRateLimitWarnStep = step;
2184
+ queryCtx.lastRateLimitWarnThreshold = info.surpassedThreshold;
2185
+ piUI?.notify(`Claude rate limit warning: ${percent}% used (${info.rateLimitType ?? ""})`, "warning");
2186
+ }
2187
+ }
2188
+ continue;
2189
+ }
2190
+ if (!queryCtx.currentPiStream || !queryCtx.turnOutput) continue;
2191
+ switch (message.type) {
2192
+ case "stream_event":
2193
+ processStreamEvent(message, customToolNameToPi, model, queryCtx);
2194
+ break;
2195
+ case "assistant":
2196
+ processAssistantMessage(message, model, customToolNameToPi, queryCtx);
2197
+ break;
2198
+ case "result": {
2199
+ if (resultError === void 0 && !queryCtx.turnSawStreamEvent && message.subtype === "success") {
2200
+ ensureTurnStarted(queryCtx);
2201
+ const text = message.result || "";
2202
+ queryCtx.turnBlocks.push({ type: "text", text });
2203
+ const idx = queryCtx.turnBlocks.length - 1;
2204
+ queryCtx.currentPiStream?.push({ type: "text_start", contentIndex: idx, partial: queryCtx.turnOutput });
2205
+ queryCtx.currentPiStream?.push({ type: "text_delta", contentIndex: idx, delta: text, partial: queryCtx.turnOutput });
2206
+ queryCtx.currentPiStream?.push({ type: "text_end", contentIndex: idx, content: text, partial: queryCtx.turnOutput });
2207
+ }
2208
+ break;
2209
+ }
2210
+ case "system":
2211
+ if (message.subtype === "init" && message.session_id) {
2212
+ capturedSessionId = message.session_id;
2213
+ }
2214
+ break;
2215
+ case "user":
2216
+ break;
2217
+ default:
2218
+ debug("consumeQuery: unhandled SDK message type", message.type);
2219
+ break;
2220
+ }
2221
+ }
2222
+ debug(`consumeQuery: for-await loop exited, wasAborted=${wasAborted()}, capturedSessionId=${capturedSessionId?.slice(0, 8) ?? "none"}`);
2223
+ return { capturedSessionId };
2224
+ }
2225
+
2226
+ function steerBlocks(messages) {
2227
+ const blocks = extractUserPromptBlocks(messages);
2228
+ if (blocks) return blocks;
2229
+ const text = extractUserPrompt(messages);
2230
+ return text ? [{ type: "text", text }] : null;
2231
+ }
2232
+
2233
+ function steerMissedSession(c, text) {
2234
+ c.missedSteer = true;
2235
+ const state = sessionStateFor(c.piSessionId);
2236
+ if (state) setSessionStateFor(c.piSessionId, { ...state, needsRebuild: true });
2237
+ debug(`provider: steer never reached CC, marked query for rebuild: ${text.slice(0, 60)}`);
2238
+ }
2239
+
2240
+ async function deliverToolResults(c, results, steer, contextLength) {
2241
+ if (steer) {
2242
+ const text = steer.map((b) => b.type === "text" ? b.text : "[image]").join("\n");
2243
+ if (!c.promptStream) {
2244
+ debug(`WARNING: steer with no prompt stream, dropping: ${text.slice(0, 60)}`);
2245
+ steerMissedSession(c, text);
2246
+ } else {
2247
+ try {
2248
+ await c.promptStream.push(userMessage(steer, "next"));
2249
+ debug(`provider: steer written to CC stdin before tool result: ${text.slice(0, 60)}`);
2250
+ } catch (error) {
2251
+ debug(`provider: steer push rejected, delivering tool result anyway:`, error);
2252
+ steerMissedSession(c, text);
2253
+ }
2254
+ }
2255
+ }
2256
+ debug(`provider: tool results, ${results.length} results, ${c.pendingToolCalls.size} waiting handlers, ctx.msgs=${contextLength}`);
2257
+ for (const result of results) {
2258
+ const id = result.toolCallId;
2259
+ if (id && c.pendingToolCalls.has(id)) {
2260
+ const pending = c.pendingToolCalls.get(id);
2261
+ c.pendingToolCalls.delete(id);
2262
+ debug(`provider: resolving ${pending.toolName} [${id}]${result.isError ? " (error)" : ""}`, JSON.stringify(result.content).slice(0, 200));
2263
+ pending.resolve(result);
2264
+ } else if (id) {
2265
+ c.pendingResults.set(id, result);
2266
+ debug(`provider: queued result [${id}] (${c.pendingResults.size} pending)`);
2267
+ } else {
2268
+ debug(`WARNING: tool result without toolCallId, cannot match`);
2269
+ }
2270
+ if (c.pendingToolCalls.size > 0 && c.pendingResults.size > 0) {
2271
+ debug(`BUG: both maps non-empty! handlers=${c.pendingToolCalls.size} results=${c.pendingResults.size}`);
2272
+ }
2273
+ }
2274
+ if (c.pendingToolCalls.size > 0) {
2275
+ debug(`WARNING: ${c.pendingToolCalls.size} MCP handlers still waiting after delivering ${results.length} results`);
2276
+ piUI?.notify(`pi-claude-agent-sdk: ${c.pendingToolCalls.size} tool handler(s) still waiting \u2014 provider may be stuck`, "warning");
2277
+ }
2278
+ }
2279
+
2280
+ function drainForAbort(c, promptStream) {
2281
+ promptStream.fail(new Error("Operation aborted"));
2282
+ c.releasePendingToolCalls("Operation aborted");
2283
+ }
2284
+
2285
+ var abandonedQueries = /* @__PURE__ */ new WeakSet();
2286
+
2287
+ var CONTINUE_AFTER_REWRITE_PROMPT = "[Your context was compacted. What precedes this is a summary plus the most recent messages, ending with the tool result you were waiting for. Continue the task from there.]";
2288
+
2289
+ function discardRewrittenQuery(c) {
2290
+ const discarded = c.activeQuery;
2291
+ if (discarded) abandonedQueries.add(discarded);
2292
+ c.activeQuery = null;
2293
+ activeQueryContexts.delete(c);
2294
+ c.turnToolCallIds = [];
2295
+ c.promptStream?.fail(new Error("conversation rewritten"));
2296
+ c.promptStream = null;
2297
+ c.releasePendingToolCalls("Context was compacted; this query was discarded.");
2298
+ void discarded?.interrupt?.().catch(() => {
2299
+ });
2300
+ try {
2301
+ discarded?.close?.();
2302
+ } catch {
2303
+ }
2304
+ const state = sessionStateFor(c.piSessionId);
2305
+ if (state) setSessionStateFor(c.piSessionId, { ...state, needsRebuild: true, forceRotate: true });
2306
+ if (c.piSessionId) historyRewrittenBySession.delete(c.piSessionId);
2307
+ debug("provider: history rewritten under a parked query \u2014 discarded it, rebuilding from current history");
2308
+ }
2309
+
2310
+ function streamClaudeAgentSdk(model, context, options) {
2311
+ context = toBridgeContext(context);
2312
+ if (isStandaloneRequest(context, options)) {
2313
+ debug("provider: routing standalone cacheRetention=none request to isolated subprocess");
2314
+ return standaloneStreamFn(model, context, options);
2315
+ }
2316
+ showStartupNoticeOnce();
2317
+ const stream = createAssistantMessageEventStream();
2318
+ const lastMsgRole = context.messages[context.messages.length - 1]?.role;
2319
+ debug(`provider: streamClaudeAgentSdk called, activeQuery=${!!ctx().activeQuery}, lastMsgRole=${lastMsgRole}, isReentrant=${ctx().activeQuery !== null}`);
2320
+ let activeQuery = ctx().activeQuery !== null;
2321
+ const allResults = activeQueryContexts.size > 0 ? extractAllToolResults2(context) : [];
2322
+ let resultCtx = allResults.length > 0 ? contextForToolResults(allResults) : void 0;
2323
+ const rewrittenUnderQuery = Boolean(resultCtx?.historyStale);
2324
+ if (resultCtx && rewrittenUnderQuery) {
2325
+ discardRewrittenQuery(resultCtx);
2326
+ resultCtx = void 0;
2327
+ activeQuery = ctx().activeQuery !== null;
2328
+ }
2329
+ const isReentrantUserQuery = activeQuery && lastMsgRole === "user" && allResults.length === 0;
2330
+ if (isReentrantUserQuery) {
2331
+ debug(`provider: active query user-only call treated as reentrant fresh query, waitingHandlers=${ctx().pendingToolCalls.size}, ctx.msgs=${context.messages.length}`);
2332
+ }
2333
+ if (resultCtx) {
2334
+ claimCurrentPiStream(stream, "tool-result", resultCtx);
2335
+ resultCtx.resetTurnState(model);
2336
+ if (!resultCtx.historyStale && resultCtx.piSessionId && historyRewrittenBySession.has(resultCtx.piSessionId)) {
2337
+ resultCtx.historyStale = true;
2338
+ }
2339
+ const steer = lastMsgRole === "user" ? steerBlocks(context.messages) : null;
2340
+ void deliverToolResults(resultCtx, allResults, steer, context.messages.length);
2341
+ const state = sessionStateFor(resultCtx.piSessionId);
2342
+ if (state) state.cursor = context.messages.length;
2343
+ resultCtx.latestCursor = Math.max(resultCtx.latestCursor, context.messages.length);
2344
+ return stream;
2345
+ }
2346
+ const lastMsg = context.messages[context.messages.length - 1];
2347
+ if (lastMsg?.role === "toolResult" && !rewrittenUnderQuery) {
2348
+ debug(`provider: orphaned tool result after abort, emitting end_turn`);
2349
+ const orphanState = sessionStateFor(options?.sessionId ?? null);
2350
+ if (orphanState && activeQueryContexts.size === 0) orphanState.cursor = context.messages.length;
2351
+ const c = new QueryContext();
2352
+ c.resetTurnState(model);
2353
+ queueMicrotask(() => {
2354
+ stream.push({ type: "done", reason: "stop", message: c.turnOutput });
2355
+ markStreamComplete(stream);
2356
+ stream.end();
2357
+ });
2358
+ return stream;
2359
+ }
2360
+ const isReentrant = activeQuery;
2361
+ const queryCtx = isReentrant ? new QueryContext() : ctx();
2362
+ debug(`provider: fresh query setup, isReentrant=${isReentrant}, activeContexts=${activeQueryContexts.size}`);
2363
+ const claudeExecutableResolution = resolveClaudeCodeExecutable(model.id, providerSettings.pathToClaudeCodeExecutable);
2364
+ if (claudeExecutableResolution.error) {
2365
+ stream.push({ type: "error", reason: "error", error: newAssistantOutput(model, "", "error", claudeExecutableResolution.error) });
2366
+ stream.end();
2367
+ return stream;
2368
+ }
2369
+ const claudeExecutable = claudeExecutableResolution.path;
2370
+ const { mcpTools, customToolNameToSdk, customToolNameToPi } = resolveMcpTools(context);
2371
+ let promptCapture;
2372
+ let systemPromptAppend;
2373
+ try {
2374
+ promptCapture = promptCaptures.resolveOrDerive(context.systemPrompt);
2375
+ systemPromptAppend = promptCapture ? projectPromptCapture(promptCapture, {
2376
+ skillReadTool: mcpTools.some((tool) => tool.name === "read") ? "mcp" : "none"
2377
+ }) : void 0;
2378
+ } catch (err) {
2379
+ const output = newAssistantOutput(model, "", "error", errorMessage(err));
2380
+ queueMicrotask(() => {
2381
+ stream.push({ type: "error", reason: "error", error: output });
2382
+ markStreamComplete(stream);
2383
+ stream.end();
2384
+ });
2385
+ diagDump("prompt_capture_unresolved", {
2386
+ promptChars: context.systemPrompt?.length ?? 0,
2387
+ knownKeys: promptCaptures.size,
2388
+ reentrantUserQuery: isReentrantUserQuery,
2389
+ error: errorMessage(err)
2390
+ });
2391
+ return stream;
2392
+ }
2393
+ claimCurrentPiStream(stream, "fresh-query", queryCtx);
2394
+ queryCtx.pendingToolCalls.clear();
2395
+ queryCtx.pendingResults.clear();
2396
+ queryCtx.turnToolCallIds = [];
2397
+ queryCtx.resetTurnState(model);
2398
+ queryCtx.latestCursor = 0;
2399
+ queryCtx.piSessionId = options?.sessionId ?? null;
2400
+ queryCtx.historyStale = false;
2401
+ queryCtx.missedSteer = false;
2402
+ const cwd = options?.cwd ?? process.cwd();
2403
+ const cliModel = claudeCodeModelId(model, longContextSettings);
2404
+ const piSessionId = options?.sessionId ?? null;
2405
+ const syncResult = syncSharedSession(context.messages, cwd, customToolNameToSdk, cliModel, piSessionId);
2406
+ if (piSessionId) historyRewrittenBySession.delete(piSessionId);
2407
+ const { sessionId: resumeSessionId } = syncResult;
2408
+ const promptBlocks = extractUserPromptBlocks(context.messages);
2409
+ let promptText = extractUserPrompt(context.messages) ?? "";
2410
+ if (rewrittenUnderQuery && !promptText && !promptBlocks) {
2411
+ promptText = CONTINUE_AFTER_REWRITE_PROMPT;
2412
+ debug(`provider: continuing the turn after a rewritten history, ${context.messages.length} msgs rebuilt`);
2413
+ }
2414
+ if (!promptText && !promptBlocks) {
2415
+ diagDump("empty_prompt", {
2416
+ contextLength: context.messages.length,
2417
+ lastMsgRole: lastMsg?.role,
2418
+ isReentrant,
2419
+ activeQueryContexts: activeQueryContexts.size,
2420
+ activeQueryExists: queryCtx.activeQuery !== null,
2421
+ sharedSession: sessionStateFor(piSessionId) ? { sessionId: sessionStateFor(piSessionId).sessionId.slice(0, 8), cursor: sessionStateFor(piSessionId).cursor } : sessionStateFor(null) ? { sessionId: sessionStateFor(null).sessionId.slice(0, 8), cursor: sessionStateFor(null).cursor } : null,
2422
+ messageRoles: context.messages.map((m, i) => `[${i}]${m.role}`).join(" ")
2423
+ });
2424
+ promptText = "[continue]";
2425
+ }
2426
+ const promptStream = makePromptStream();
2427
+ void promptStream.push(userMessage(promptBlocks ?? [{ type: "text", text: promptText }])).catch((error) => debug(`provider: initial prompt push rejected:`, error));
2428
+ queryCtx.promptStream = promptStream;
2429
+ const mcpServers = buildMcpServers(mcpTools, queryCtx);
2430
+ const strictMcpConfigEnabled = providerSettings.strictMcpConfig !== false;
2431
+ const mapped = options?.reasoning ? model.thinkingLevelMap?.[options.reasoning] : void 0;
2432
+ const effort = options?.reasoning ? mapped === void 0 ? REASONING_TO_EFFORT[options.reasoning] : VALID_EFFORTS.has(mapped) ? mapped : void 0 : void 0;
2433
+ const extraArgs = { model: cliModel };
2434
+ if (strictMcpConfigEnabled) extraArgs["strict-mcp-config"] = null;
2435
+ if (effort || adaptiveThinkingAlwaysOn(model.id)) extraArgs["thinking-display"] = "summarized";
2436
+ const queryOptions = {
2437
+ cwd,
2438
+ tools: [],
2439
+ permissionMode: "bypassPermissions",
2440
+ includePartialMessages: true,
2441
+ // includeGitInstructions:false drops the gitStatus block from the preset.
2442
+ // That block is the trailing suffix of the cached system block, and a
2443
+ // git-state transition (new file, staging, commit) rewrites it — busting
2444
+ // the prompt cache for the whole conversation from there on (see
2445
+ // diag/probe-git-cache.mjs). The bridge re-invokes CC per turn, so this
2446
+ // hit on every transition. Cost here is nil: the setting also strips
2447
+ // CC's git-workflow guidance from its Bash tool prompt, but the provider
2448
+ // path runs CC with `tools: []`, so those definitions never ship.
2449
+ settings: {
2450
+ ...claudeCodeSettings(providerSettings),
2451
+ claudeMdExcludes: CLAUDE_MD_EXCLUDES,
2452
+ includeGitInstructions: false
2453
+ },
2454
+ systemPrompt: {
2455
+ type: "preset",
2456
+ preset: "claude_code",
2457
+ append: systemPromptAppend ? systemPromptAppend : void 0
2458
+ },
2459
+ extraArgs,
2460
+ ...effort ? { effort } : {},
2461
+ ...mcpServers ? { mcpServers } : {},
2462
+ ...resumeSessionId ? { resume: resumeSessionId } : {},
2463
+ ...claudeExecutable ? { pathToClaudeCodeExecutable: claudeExecutable } : {},
2464
+ ...makeCliDebugOptions("provider")
2465
+ };
2466
+ debug(
2467
+ "provider: fresh query",
2468
+ `model=${cliModel} msgs=${context.messages.length} tools=${mcpTools.length}`,
2469
+ `resume=${resumeSessionId?.slice(0, 8) ?? "none"} effort=${effort ?? "default"}`,
2470
+ `ctxFiles=${promptCapture?.contextFiles.length ?? 0} strictMcp=${strictMcpConfigEnabled}`,
2471
+ `prompt=${promptText.slice(0, 60)}${promptBlocks ? " [+images]" : ""}`
2472
+ );
2473
+ let wasAborted = false;
2474
+ let sdkQuery = null;
2475
+ const authPending = { kind: "claude-auth-pending" };
2476
+ queryCtx.activeQuery = authPending;
2477
+ const abortCtx = queryCtx;
2478
+ const requestAbort = () => {
2479
+ void sdkQuery?.interrupt().catch(() => {
2480
+ });
2481
+ try {
2482
+ sdkQuery?.close();
2483
+ } catch {
2484
+ }
2485
+ };
2486
+ const onAbort = () => {
2487
+ wasAborted = true;
2488
+ drainForAbort(abortCtx, promptStream);
2489
+ requestAbort();
2490
+ };
2491
+ if (options?.signal) {
2492
+ if (options.signal.aborted) onAbort();
2493
+ else options.signal.addEventListener("abort", onAbort, { once: true });
2494
+ }
2495
+ void (async () => {
2496
+ queryOptions.env = await resolveClaudeChildEnv(piModelRegistry);
2497
+ if (wasAborted || options?.signal?.aborted) throw new Error("Operation aborted");
2498
+ const startedQuery = queryImpl({ prompt: promptStream.stream, options: queryOptions });
2499
+ sdkQuery = startedQuery;
2500
+ queryCtx.activeQuery = startedQuery;
2501
+ activeQueryContexts.add(queryCtx);
2502
+ if (wasAborted || options?.signal?.aborted) {
2503
+ requestAbort();
2504
+ throw new Error("Operation aborted");
2505
+ }
2506
+ const { capturedSessionId } = await consumeQuery(startedQuery, customToolNameToPi, model, () => wasAborted, queryCtx);
2507
+ debug(`provider: consumeQuery completed, stopReason=${queryCtx.turnOutput?.stopReason}, error=${queryCtx.turnOutput?.errorMessage}, aborted=${wasAborted}`);
2508
+ if (abandonedQueries.has(sdkQuery)) {
2509
+ debug("provider: discarded query completed, leaving session and stream to its replacement");
2510
+ return;
2511
+ }
2512
+ if (wasAborted || options?.signal?.aborted) {
2513
+ const state = sessionStateFor(queryCtx.piSessionId);
2514
+ if (state) setSessionStateFor(queryCtx.piSessionId, { ...state, needsRebuild: true, forceRotate: true });
2515
+ debug(`provider: abort detected, marked sharedSession needsRebuild + forceRotate`);
2516
+ if (queryCtx.turnOutput) {
2517
+ queryCtx.turnOutput.stopReason = "aborted";
2518
+ queryCtx.turnOutput.errorMessage = "Operation aborted";
2519
+ }
2520
+ const stream2 = queryCtx.currentPiStream;
2521
+ stream2?.push({ type: "error", reason: "aborted", error: queryCtx.turnOutput });
2522
+ markStreamComplete(stream2);
2523
+ stream2?.end();
2524
+ queryCtx.currentPiStream = null;
2525
+ return;
2526
+ }
2527
+ if (syncResult.preserveSharedSession) {
2528
+ const state = sessionStateFor(queryCtx.piSessionId);
2529
+ if (capturedSessionId && capturedSessionId !== state?.sessionId) {
2530
+ deleteSession(capturedSessionId, cwd, process.env.CLAUDE_CONFIG_DIR);
2531
+ debug(`provider: query done, deleted ephemeral session ${capturedSessionId.slice(0, 8)} to preserve shared session`);
2532
+ }
2533
+ debug(`provider: query done, ignoring captured session ${capturedSessionId?.slice(0, 8) ?? "none"} to preserve shared session`);
2534
+ } else {
2535
+ const state = sessionStateFor(queryCtx.piSessionId);
2536
+ const sessionId = capturedSessionId ?? state?.sessionId;
2537
+ if (sessionId) {
2538
+ const cursor = Math.max(context.messages.length, queryCtx.latestCursor, state?.cursor ?? 0);
2539
+ debug(`provider: query done, session=${sessionId.slice(0, 8)}, cursor=${cursor}`);
2540
+ setSessionStateFor(queryCtx.piSessionId, { ...state, sessionId, cursor, cwd, piSessionId: queryCtx.piSessionId ?? void 0, needsRebuild: queryCtx.missedSteer || state?.needsRebuild });
2541
+ }
2542
+ }
2543
+ if (!isReentrant && queryCtx.activeQuery === sdkQuery) {
2544
+ debug("provider: clearing activeQuery before final stream completion");
2545
+ queryCtx.activeQuery = null;
2546
+ }
2547
+ finalizeCurrentStream(queryCtx, queryCtx.turnOutput?.stopReason);
2548
+ })().catch((error) => {
2549
+ debug(`provider: query error, model=${cliModel}, aborted=${Boolean(options?.signal?.aborted)}, error=`, error);
2550
+ if (sdkQuery && abandonedQueries.has(sdkQuery)) {
2551
+ debug("provider: discarded query ended in error, leaving session and stream to its replacement");
2552
+ return;
2553
+ }
2554
+ if (wasAborted || options?.signal?.aborted) {
2555
+ const state = sessionStateFor(queryCtx.piSessionId);
2556
+ if (state) setSessionStateFor(queryCtx.piSessionId, { ...state, needsRebuild: true, forceRotate: true });
2557
+ } else if (sdkQuery) {
2558
+ setSessionStateFor(queryCtx.piSessionId, null);
2559
+ }
2560
+ promptStream.fail(error instanceof Error ? error : new Error(String(error)));
2561
+ if (queryCtx.turnOutput) {
2562
+ queryCtx.turnOutput.stopReason = options?.signal?.aborted ? "aborted" : "error";
2563
+ queryCtx.turnOutput.errorMessage ??= error instanceof Error ? error.message : String(error);
2564
+ }
2565
+ if (!isReentrant && queryCtx.activeQuery === sdkQuery) {
2566
+ queryCtx.releasePendingToolCalls("Query ended");
2567
+ debug("provider: clearing activeQuery before error stream completion");
2568
+ queryCtx.activeQuery = null;
2569
+ }
2570
+ const stream2 = queryCtx.currentPiStream;
2571
+ stream2?.push({ type: "error", reason: queryCtx.turnOutput?.stopReason ?? "error", error: queryCtx.turnOutput });
2572
+ markStreamComplete(stream2);
2573
+ stream2?.end();
2574
+ queryCtx.currentPiStream = null;
2575
+ }).finally(() => {
2576
+ if (options?.signal) options.signal.removeEventListener("abort", onAbort);
2577
+ promptStream.fail(new Error("query ended"));
2578
+ if (queryCtx.promptStream === promptStream) queryCtx.promptStream = null;
2579
+ if (queryCtx.activeQuery === authPending || queryCtx.activeQuery === sdkQuery || queryCtx.activeQuery === null) {
2580
+ queryCtx.releasePendingToolCalls("Query ended");
2581
+ queryCtx.activeQuery = null;
2582
+ activeQueryContexts.delete(queryCtx);
2583
+ }
2584
+ try {
2585
+ sdkQuery?.close();
2586
+ } catch {
2587
+ }
2588
+ });
2589
+ return stream;
2590
+ }
2591
+
2592
+ function index_default(pi) {
2593
+ process.env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC = "1";
2594
+ const config = loadConfig(process.cwd());
2595
+ debug("loadConfig:", JSON.stringify(config));
2596
+ providerSettings = config.provider ?? {};
2597
+ const forceTwoHundredK = Array.isArray(providerSettings.forceTwoHundredK) ? providerSettings.forceTwoHundredK.filter((id) => typeof id === "string") : void 0;
2598
+ longContextSettings = {
2599
+ plan: providerSettings.plan ?? "max",
2600
+ longContextExtraUsage: providerSettings.longContextExtraUsage ?? false,
2601
+ forceTwoHundredK
2602
+ };
2603
+ const registeredModels = applyLongContext(MODELS, longContextSettings);
2604
+ if (registeredModels.length === 0) {
2605
+ console.error("claude-bridge: no models available from pi-ai's anthropic catalog \u2014 update @earendil-works/pi-ai (requires >=0.86.1)");
2606
+ }
2607
+ if (!config.startupNoticeShown) {
2608
+ if (config.provider?.plan === void 0) pendingNotices.push('Assuming a Max plan. On Pro, set provider.plan to "pro" so Opus 4.6 stays at 200K context.');
2609
+ }
2610
+ const clearSession = (event) => {
2611
+ debug(`${event}: clearing ${sharedSessions.size} shared session${sharedSessions.size === 1 ? "" : "s"}`);
2612
+ sharedSessions.clear();
2613
+ historyRewrittenBySession.clear();
2614
+ const g2 = bridgeGlobals;
2615
+ if (g2[ACTIVE_STREAM_SIMPLE_KEY] === streamClaudeAgentSdk) {
2616
+ debug(`${event}: clearing ACTIVE_STREAM_SIMPLE_KEY`);
2617
+ g2[ACTIVE_STREAM_SIMPLE_KEY] = void 0;
2618
+ }
2619
+ };
2620
+ pi.on("session_start", (event, ctx2) => {
2621
+ piUI = ctx2.ui;
2622
+ piMode = ctx2.mode;
2623
+ piModelRegistry = ctx2.modelRegistry;
2624
+ if (event.reason === "new" || event.reason === "resume" || event.reason === "fork") {
2625
+ clearSession(`session_start:${event.reason}`);
2626
+ }
2627
+ });
2628
+ let lastSystemPromptOptions;
2629
+ function recordSystemPrompt(source, systemPrompt, options) {
2630
+ if (!systemPrompt) return;
2631
+ const hasRead = !options?.selectedTools || options.selectedTools.includes("read");
2632
+ promptCaptures.record(systemPrompt, {
2633
+ custom: options?.customPrompt,
2634
+ append: options?.appendSystemPrompt,
2635
+ contextFiles: options?.contextFiles ?? [],
2636
+ skills: hasRead ? options?.skills ?? [] : []
2637
+ }, source);
2638
+ }
2639
+ pi.on("before_agent_start", (event) => {
2640
+ lastSystemPromptOptions = event.systemPromptOptions;
2641
+ recordSystemPrompt("before_agent_start", event.systemPrompt, event.systemPromptOptions);
2642
+ });
2643
+ pi.on("agent_start", (_event, ctx2) => {
2644
+ recordSystemPrompt("agent_start", ctx2.getSystemPrompt(), lastSystemPromptOptions);
2645
+ });
2646
+ pi.on("turn_start", (_event, ctx2) => {
2647
+ recordSystemPrompt("turn_start", ctx2.getSystemPrompt(), lastSystemPromptOptions);
2648
+ });
2649
+ pi.on("session_shutdown", () => {
2650
+ reportLeaks("session_shutdown");
2651
+ clearSession("session_shutdown");
2652
+ });
2653
+ pi.on("session_compact", (event, ctx2) => sponsorMarkRebuildForSession(ctx2.sessionManager.getSessionId(), `session_compact:${event.reason}:willRetry=${event.willRetry}`));
2654
+ pi.on("session_tree", (_event, ctx2) => sponsorMarkRebuildForSession(ctx2.sessionManager.getSessionId(), "session_tree"));
2655
+ const g = bridgeGlobals;
2656
+ const providerConfig = {
2657
+ baseUrl: "claude-bridge",
2658
+ apiKey: "not-used",
2659
+ api: "claude-bridge",
2660
+ models: registeredModels,
2661
+ // Cast: the Provider interface passes a TranscriptContext; the bridge takes plain
2662
+ // Context models (toBridgeContext normalizes at the stream entry points).
2663
+ streamSimple: streamClaudeAgentSdk
2664
+ };
2665
+ if (!g[ACTIVE_STREAM_SIMPLE_KEY]) {
2666
+ g[ACTIVE_STREAM_SIMPLE_KEY] = streamClaudeAgentSdk;
2667
+ pi.registerProvider(PROVIDER_ID, providerConfig);
2668
+ } else {
2669
+ debug(`provider: deferring registration decision to session_start (module=${moduleInstanceId})`);
2670
+ pi.on("session_start", (_event, ctx2) => {
2671
+ if (ctx2.modelRegistry.getProvider(PROVIDER_ID)) {
2672
+ debug(`provider: registry already has ${PROVIDER_ID}, skipping registration (module=${moduleInstanceId})`);
2673
+ return;
2674
+ }
2675
+ debug(`provider: registry lacks ${PROVIDER_ID}, registering (module=${moduleInstanceId})`);
2676
+ pi.registerProvider(PROVIDER_ID, providerConfig);
2677
+ });
2678
+ }
2679
+ }
2680
+ if (queryImplementation) __test.setQuery(queryImplementation);
2681
+ return index_default;
2682
+ }