pi-claude-agent-sdk 0.8.5 → 0.8.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/index.ts +59 -120
package/README.md
CHANGED
|
@@ -66,7 +66,7 @@ Integration tests spawn real `pi` and Claude Code subprocesses, so they need wri
|
|
|
66
66
|
Set `CLAUDE_BRIDGE_DEBUG=1` to enable debug output:
|
|
67
67
|
|
|
68
68
|
- **Bridge log** at `~/.pi/agent/claude-bridge.log` — every provider call, session sync decision, tool result delivery, and CC's stderr. Override location with `CLAUDE_BRIDGE_DEBUG_PATH`.
|
|
69
|
-
- **Per-query Claude Code CLI logs** at `~/.pi/agent/cc-cli-logs/<timestamp>-<tag>-<seq>.log` — the CC subprocess's own debug stream, one file per `query()` call. Tags are `provider` (main turn) or `
|
|
69
|
+
- **Per-query Claude Code CLI logs** at `~/.pi/agent/cc-cli-logs/<timestamp>-<tag>-<seq>.log` — the CC subprocess's own debug stream, one file per `query()` call. Tags are `provider` (resumable main turn) or `standalone` (compaction, branch summary, and tool-free extension-owned no-cache completion). Useful when a resume fails or CC misbehaves internally — shows the CLI's own view of session loading, API requests, and tool calls.
|
|
70
70
|
|
|
71
71
|
When filing a bug about a session-resume failure (e.g. "No conversation found"), the most useful attachments are the `syncResult:` lines from the bridge log plus the matching `cc-cli-logs/` file for the failing query.
|
|
72
72
|
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { calculateCost, type AssistantMessage, type AssistantMessageEventStream, type Context, type ImageContent, type Model, type SimpleStreamOptions, type TextContent, type Tool, type UserMessage } from "@earendil-works/pi-ai";
|
|
2
2
|
import * as piAi from "@earendil-works/pi-ai";
|
|
3
3
|
import { getModels } from "@earendil-works/pi-ai/compat";
|
|
4
|
-
import {
|
|
4
|
+
import { type ExtensionAPI, type ExtensionContext, type ExtensionUIContext } from "@earendil-works/pi-coding-agent";
|
|
5
5
|
import { query, type EffortLevel, type SDKMessage, type SettingSource } from "@anthropic-ai/claude-agent-sdk";
|
|
6
6
|
import type { Base64ImageSource, ContentBlockParam } from "@anthropic-ai/sdk/resources";
|
|
7
7
|
import { createSession, deleteSession, openSession, repairToolPairing } from "cc-session-io";
|
|
@@ -366,18 +366,33 @@ function newAssistantOutput(model: Model<any>, text: string, stopReason: Assista
|
|
|
366
366
|
};
|
|
367
367
|
}
|
|
368
368
|
|
|
369
|
-
function
|
|
369
|
+
function extractStandalonePrompt(context: Context): string {
|
|
370
|
+
if (context.tools?.length) {
|
|
371
|
+
throw new Error(`standalone provider requests do not support tools (got ${context.tools.length})`);
|
|
372
|
+
}
|
|
373
|
+
const messages = context.messages;
|
|
370
374
|
if (messages.length !== 1 || messages[0].role !== "user") {
|
|
371
375
|
throw new Error(
|
|
372
|
-
`
|
|
376
|
+
`standalone provider request expected exactly 1 user message, got ${messages.length} ` +
|
|
373
377
|
`(${messages.map((m) => m.role).join(",")})`,
|
|
374
378
|
);
|
|
375
379
|
}
|
|
376
380
|
const promptText = extractUserPrompt(messages);
|
|
377
|
-
if (!promptText) throw new Error("
|
|
381
|
+
if (!promptText) throw new Error("standalone provider request prompt is empty");
|
|
378
382
|
return promptText;
|
|
379
383
|
}
|
|
380
384
|
|
|
385
|
+
/** Pi marks summaries and extension-owned nested completions as no-cache,
|
|
386
|
+
* standalone requests. They must never enter the resumable provider path: its
|
|
387
|
+
* prompt capture and shared Claude Code session belong to the interactive
|
|
388
|
+
* agent, not to an unrelated planner or summarizer. */
|
|
389
|
+
function isStandaloneRequest(context: Context, options?: SimpleStreamOptions): boolean {
|
|
390
|
+
return options?.cacheRetention === "none"
|
|
391
|
+
&& context.tools === undefined
|
|
392
|
+
&& context.messages.length === 1
|
|
393
|
+
&& context.messages[0]?.role === "user";
|
|
394
|
+
}
|
|
395
|
+
|
|
381
396
|
/** Failure text for an SDK result, or undefined when it succeeded. CC reports API failures
|
|
382
397
|
* (429 capacity, overload, prompt-too-long) with `is_error` on an otherwise success-shaped
|
|
383
398
|
* result; the dedicated error subtypes carry `errors` instead. */
|
|
@@ -407,13 +422,13 @@ function describeRateLimitFailure(rejection: { rateLimitType?: string; resetsAt?
|
|
|
407
422
|
return `Claude rate limit${kind}${resets}: ${failure}`;
|
|
408
423
|
}
|
|
409
424
|
|
|
410
|
-
function
|
|
425
|
+
function standaloneStreamFn(model: Model<any>, context: Context, options?: SimpleStreamOptions): AssistantMessageEventStream {
|
|
411
426
|
const stream = newAssistantMessageEventStream();
|
|
412
|
-
void
|
|
427
|
+
void runStandaloneRequest(model, context, options, stream);
|
|
413
428
|
return stream;
|
|
414
429
|
}
|
|
415
430
|
|
|
416
|
-
async function
|
|
431
|
+
async function runStandaloneRequest(
|
|
417
432
|
model: Model<any>,
|
|
418
433
|
context: Context,
|
|
419
434
|
options: SimpleStreamOptions | undefined,
|
|
@@ -428,14 +443,20 @@ async function runIsolatedSummary(
|
|
|
428
443
|
};
|
|
429
444
|
|
|
430
445
|
try {
|
|
431
|
-
const promptText =
|
|
446
|
+
const promptText = extractStandalonePrompt(context);
|
|
432
447
|
const cwd = (options as { cwd?: string } | undefined)?.cwd ?? process.cwd();
|
|
433
|
-
const
|
|
434
|
-
const claudeExecutableResolution = resolveClaudeCodeExecutable(model.id,
|
|
448
|
+
const standaloneProviderSettings = loadConfig(cwd).provider;
|
|
449
|
+
const claudeExecutableResolution = resolveClaudeCodeExecutable(model.id, standaloneProviderSettings?.pathToClaudeCodeExecutable);
|
|
435
450
|
if (claudeExecutableResolution.error) throw new Error(claudeExecutableResolution.error);
|
|
436
451
|
const claudeExecutable = claudeExecutableResolution.path;
|
|
437
452
|
const cliModel = claudeCodeModelId(model, longContextSettings);
|
|
438
|
-
|
|
453
|
+
const effort = options?.reasoning
|
|
454
|
+
? ((model as any).thinkingLevelMap?.[options.reasoning] as EffortLevel | undefined)
|
|
455
|
+
?? REASONING_TO_EFFORT[options.reasoning]
|
|
456
|
+
: undefined;
|
|
457
|
+
const extraArgs: Record<string, string | null> = {};
|
|
458
|
+
if (effort || adaptiveThinkingAlwaysOn(model.id)) extraArgs["thinking-display"] = "summarized";
|
|
459
|
+
debug(`standalone: spawn model=${cliModel} registeredModel=${model.id} promptLen=${promptText.length} effort=${effort ?? "default"}`);
|
|
439
460
|
const childEnv = await resolveClaudeChildEnv(piModelRegistry);
|
|
440
461
|
|
|
441
462
|
sdkQuery = query({
|
|
@@ -452,8 +473,10 @@ async function runIsolatedSummary(
|
|
|
452
473
|
systemPrompt: context.systemPrompt,
|
|
453
474
|
model: cliModel,
|
|
454
475
|
maxTurns: 1,
|
|
476
|
+
extraArgs,
|
|
477
|
+
...(effort ? { effort } : {}),
|
|
455
478
|
...(claudeExecutable ? { pathToClaudeCodeExecutable: claudeExecutable } : {}),
|
|
456
|
-
...makeCliDebugOptions("
|
|
479
|
+
...makeCliDebugOptions("standalone"),
|
|
457
480
|
},
|
|
458
481
|
});
|
|
459
482
|
|
|
@@ -465,11 +488,12 @@ async function runIsolatedSummary(
|
|
|
465
488
|
let assistantText = "";
|
|
466
489
|
let finalText = "";
|
|
467
490
|
let errorText: string | undefined;
|
|
491
|
+
let resultUsage: Record<string, number | undefined> | undefined;
|
|
468
492
|
let firstEventLogged = false;
|
|
469
493
|
|
|
470
494
|
for await (const message of sdkQuery) {
|
|
471
495
|
if (!firstEventLogged) {
|
|
472
|
-
debug(`
|
|
496
|
+
debug(`standalone: first event type=${message.type}`);
|
|
473
497
|
firstEventLogged = true;
|
|
474
498
|
}
|
|
475
499
|
if (wasAborted) break;
|
|
@@ -479,15 +503,16 @@ async function runIsolatedSummary(
|
|
|
479
503
|
if (block.type === "text" && typeof block.text === "string") assistantText += block.text;
|
|
480
504
|
}
|
|
481
505
|
} else if (message.type === "result") {
|
|
482
|
-
logServedContextWindow("
|
|
506
|
+
logServedContextWindow("standalone", message, model);
|
|
483
507
|
errorText = resultErrorText(message);
|
|
508
|
+
resultUsage = (message as SDKMessage & { usage?: Record<string, number | undefined> }).usage;
|
|
484
509
|
if (!errorText && message.subtype === "success") finalText = message.result || assistantText;
|
|
485
510
|
}
|
|
486
511
|
}
|
|
487
512
|
|
|
488
513
|
if (wasAborted) {
|
|
489
514
|
const output = newAssistantOutput(model, "", "aborted", "Operation aborted");
|
|
490
|
-
debug("
|
|
515
|
+
debug("standalone: aborted");
|
|
491
516
|
stream.push({ type: "error", reason: "aborted", error: output });
|
|
492
517
|
stream.end();
|
|
493
518
|
return;
|
|
@@ -495,19 +520,21 @@ async function runIsolatedSummary(
|
|
|
495
520
|
|
|
496
521
|
const text = finalText || assistantText;
|
|
497
522
|
if (errorText || !text.trim()) {
|
|
498
|
-
const msg = errorText ?? "Claude Code
|
|
499
|
-
debug(`
|
|
523
|
+
const msg = errorText ?? "Claude Code standalone request returned empty text";
|
|
524
|
+
debug(`standalone: error ${msg}`);
|
|
500
525
|
stream.push({ type: "error", reason: "error", error: newAssistantOutput(model, "", "error", msg) });
|
|
501
526
|
stream.end();
|
|
502
527
|
return;
|
|
503
528
|
}
|
|
504
529
|
|
|
505
|
-
|
|
506
|
-
|
|
530
|
+
const output = newAssistantOutput(model, text, "stop");
|
|
531
|
+
if (resultUsage) updateUsage(output, resultUsage, model);
|
|
532
|
+
debug(`standalone: done textLen=${text.length}`);
|
|
533
|
+
stream.push({ type: "done", reason: "stop", message: output });
|
|
507
534
|
stream.end();
|
|
508
535
|
} catch (err) {
|
|
509
536
|
const msg = errorMessage(err);
|
|
510
|
-
debug("
|
|
537
|
+
debug("runStandaloneRequest threw; pushing terminal error", err);
|
|
511
538
|
stream.push({ type: "error", reason: "error", error: newAssistantOutput(model, "", "error", msg) });
|
|
512
539
|
stream.end();
|
|
513
540
|
} finally {
|
|
@@ -516,17 +543,6 @@ async function runIsolatedSummary(
|
|
|
516
543
|
}
|
|
517
544
|
}
|
|
518
545
|
|
|
519
|
-
function reinjectPriorCompactionFileOps(branchEntries: Array<{ type: string; details?: unknown }>, preparation: { fileOps: { read: Set<string>; edited: Set<string> } }): void {
|
|
520
|
-
const prior = [...branchEntries]
|
|
521
|
-
.reverse()
|
|
522
|
-
.find((entry): entry is CompactionEntry => entry.type === "compaction");
|
|
523
|
-
const details = prior?.details as { readFiles?: unknown; modifiedFiles?: unknown } | undefined;
|
|
524
|
-
if (!Array.isArray(details?.readFiles) || !Array.isArray(details?.modifiedFiles)) return;
|
|
525
|
-
for (const file of details.readFiles) preparation.fileOps.read.add(String(file));
|
|
526
|
-
for (const file of details.modifiedFiles) preparation.fileOps.edited.add(String(file));
|
|
527
|
-
debug(`compact takeover: re-injected prior file ops read=${details.readFiles.length} modified=${details.modifiedFiles.length}`);
|
|
528
|
-
}
|
|
529
|
-
|
|
530
546
|
interface SyncResult {
|
|
531
547
|
sessionId: string | null;
|
|
532
548
|
preserveSharedSession?: boolean;
|
|
@@ -643,8 +659,8 @@ function syncSharedSession(
|
|
|
643
659
|
// the completion handler). Remove this branch and a subagent resumes — then
|
|
644
660
|
// overwrites — the parent's session.
|
|
645
661
|
//
|
|
646
|
-
// It is
|
|
647
|
-
//
|
|
662
|
+
// It is not the standalone no-cache path: runStandaloneRequest never calls
|
|
663
|
+
// syncSharedSession at all.
|
|
648
664
|
//
|
|
649
665
|
// Only reachable when needsRebuild is false — user-facing history rewrites
|
|
650
666
|
// (/compact, session_tree, /new, fork) always set needsRebuild or clear
|
|
@@ -722,7 +738,8 @@ export const __test = {
|
|
|
722
738
|
drainForAbort,
|
|
723
739
|
CC_CHILD_ENV,
|
|
724
740
|
buildMcpServers,
|
|
725
|
-
|
|
741
|
+
isStandaloneRequest,
|
|
742
|
+
extractStandalonePrompt,
|
|
726
743
|
};
|
|
727
744
|
|
|
728
745
|
// --- Provider helpers: tool name mapping ---
|
|
@@ -833,25 +850,6 @@ function reportLeaks(label: string): void {
|
|
|
833
850
|
);
|
|
834
851
|
}
|
|
835
852
|
|
|
836
|
-
/** What pi's branch summary means for the navigation it was asked for.
|
|
837
|
-
*
|
|
838
|
-
* Cancelling on failure matches pi's own path, which rethrows a summary error out
|
|
839
|
-
* of the navigation rather than moving without one. Separated from the event
|
|
840
|
-
* handler so this decision is testable without a Claude Code subprocess — driving
|
|
841
|
-
* `generateBranchSummary` itself would only be testing pi. */
|
|
842
|
-
function branchSummaryOutcome(result: BranchSummaryResult): { cancel: true } | { summary: { summary: string; details: unknown; usage?: BranchSummaryResult["usage"] } } {
|
|
843
|
-
if (result.aborted) return { cancel: true };
|
|
844
|
-
if (result.error) throw new Error(result.error);
|
|
845
|
-
debug(`session_before_tree: takeover complete summaryLen=${result.summary?.length ?? 0}`);
|
|
846
|
-
return {
|
|
847
|
-
summary: {
|
|
848
|
-
summary: result.summary ?? "",
|
|
849
|
-
details: { readFiles: result.readFiles ?? [], modifiedFiles: result.modifiedFiles ?? [] },
|
|
850
|
-
usage: result.usage,
|
|
851
|
-
},
|
|
852
|
-
};
|
|
853
|
-
}
|
|
854
|
-
|
|
855
853
|
function contextForToolResults(results: McpResult[]): QueryContext | undefined {
|
|
856
854
|
for (const result of results) {
|
|
857
855
|
const id = result.toolCallId;
|
|
@@ -1424,9 +1422,15 @@ function drainForAbort(c: QueryContext, promptStream: PromptStream): void {
|
|
|
1424
1422
|
c.releasePendingToolCalls("Operation aborted");
|
|
1425
1423
|
}
|
|
1426
1424
|
|
|
1427
|
-
/** Provider entry point. Pi calls this for
|
|
1428
|
-
*
|
|
1425
|
+
/** Provider entry point. Pi calls this for normal agent turns, but also for
|
|
1426
|
+
* standalone no-cache completions made by compaction and extensions. Route the
|
|
1427
|
+
* latter before touching prompt captures or resumable-session state. */
|
|
1429
1428
|
function streamClaudeAgentSdk(model: Model<any>, context: Context, options?: SimpleStreamOptions): AssistantMessageEventStream {
|
|
1429
|
+
if (isStandaloneRequest(context, options)) {
|
|
1430
|
+
debug(`provider: routing standalone cacheRetention=none request to isolated subprocess`);
|
|
1431
|
+
return standaloneStreamFn(model, context, options);
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1430
1434
|
showStartupNoticeOnce();
|
|
1431
1435
|
const stream = newAssistantMessageEventStream();
|
|
1432
1436
|
|
|
@@ -1828,39 +1832,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
1828
1832
|
clearSession("session_shutdown");
|
|
1829
1833
|
});
|
|
1830
1834
|
|
|
1831
|
-
pi.on("session_before_compact", async (event, ctx) => {
|
|
1832
|
-
if (ctx.model?.baseUrl !== "claude-bridge") return undefined;
|
|
1833
|
-
debug(
|
|
1834
|
-
`session_before_compact: takeover reason=${event.reason} willRetry=${event.willRetry} ` +
|
|
1835
|
-
`isSplitTurn=${event.preparation.isSplitTurn} messages=${event.preparation.messagesToSummarize.length} ` +
|
|
1836
|
-
`turnPrefix=${event.preparation.turnPrefixMessages.length}`,
|
|
1837
|
-
);
|
|
1838
|
-
try {
|
|
1839
|
-
reinjectPriorCompactionFileOps(event.branchEntries, event.preparation);
|
|
1840
|
-
const compaction = await compact(
|
|
1841
|
-
event.preparation,
|
|
1842
|
-
ctx.model,
|
|
1843
|
-
undefined,
|
|
1844
|
-
undefined,
|
|
1845
|
-
event.customInstructions,
|
|
1846
|
-
event.signal,
|
|
1847
|
-
undefined,
|
|
1848
|
-
isolatedStreamFn,
|
|
1849
|
-
undefined,
|
|
1850
|
-
);
|
|
1851
|
-
debug(`session_before_compact: takeover complete summaryLen=${compaction.summary.length}`);
|
|
1852
|
-
return { compaction };
|
|
1853
|
-
} catch (err) {
|
|
1854
|
-
const msg = errorMessage(err);
|
|
1855
|
-
debug("session_before_compact: takeover failed; cancelling to avoid native compact fallback", err);
|
|
1856
|
-
ctx.ui?.notify?.(
|
|
1857
|
-
`pi-claude-agent-sdk compact failed (${msg}); cancelled to avoid known hang. Retry, switch model, or reduce context.`,
|
|
1858
|
-
"error",
|
|
1859
|
-
);
|
|
1860
|
-
return { cancel: true };
|
|
1861
|
-
}
|
|
1862
|
-
});
|
|
1863
|
-
|
|
1864
1835
|
// pi /compact and session-tree navigation (rewind / fork-at-point /
|
|
1865
1836
|
// branch switch) both mutate pi's messages array out from under the
|
|
1866
1837
|
// bridge. syncSharedSession's REUSE check would otherwise see
|
|
@@ -1877,38 +1848,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
1877
1848
|
pi.on("session_compact", (event) => markRebuild(`session_compact:${event.reason}:willRetry=${event.willRetry}`));
|
|
1878
1849
|
pi.on("session_tree", () => markRebuild("session_tree"));
|
|
1879
1850
|
|
|
1880
|
-
// Branch summarization — rewind or fork-at-point with "summarize" — is the other
|
|
1881
|
-
// place pi asks the model for a summary, and unlike compaction it runs through
|
|
1882
|
-
// the *agent's* stream function (agent-session passes `streamFn:
|
|
1883
|
-
// this.agent.streamFunction`). On a bridge model that reaches this provider
|
|
1884
|
-
// carrying pi's internal summarization prompt, which no `before_agent_start`
|
|
1885
|
-
// ever recorded, so the prompt-capture resolver has nothing to resolve it to.
|
|
1886
|
-
// Take it over the way compaction is taken over: the summary runs as its own
|
|
1887
|
-
// Claude Code subprocess, never touching the live session or the resolver.
|
|
1888
|
-
pi.on("session_before_tree", async (event, ctx) => {
|
|
1889
|
-
if (ctx.model?.baseUrl !== "claude-bridge") return undefined;
|
|
1890
|
-
const { entriesToSummarize, userWantsSummary, customInstructions, replaceInstructions } = event.preparation;
|
|
1891
|
-
if (!userWantsSummary || entriesToSummarize.length === 0) return undefined;
|
|
1892
|
-
debug(`session_before_tree: takeover entries=${entriesToSummarize.length} target=${event.preparation.targetId.slice(0, 8)}`);
|
|
1893
|
-
try {
|
|
1894
|
-
const result = await generateBranchSummary(entriesToSummarize, {
|
|
1895
|
-
model: ctx.model,
|
|
1896
|
-
signal: event.signal,
|
|
1897
|
-
customInstructions,
|
|
1898
|
-
replaceInstructions,
|
|
1899
|
-
streamFn: isolatedStreamFn,
|
|
1900
|
-
});
|
|
1901
|
-
return branchSummaryOutcome(result);
|
|
1902
|
-
} catch (err) {
|
|
1903
|
-
debug("session_before_tree: takeover failed; cancelling navigation", err);
|
|
1904
|
-
ctx.ui?.notify?.(
|
|
1905
|
-
`pi-claude-agent-sdk branch summary failed (${errorMessage(err)}); navigation cancelled.`,
|
|
1906
|
-
"error",
|
|
1907
|
-
);
|
|
1908
|
-
return { cancel: true };
|
|
1909
|
-
}
|
|
1910
|
-
});
|
|
1911
|
-
|
|
1912
1851
|
// --- Provider ---
|
|
1913
1852
|
//
|
|
1914
1853
|
// Guard against re-registration when the module is loaded multiple times
|