@psnext/slingcli 2.5.20260722-2 → 2.5.20260722-3
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/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js +9 -7
- package/node_modules/@earendil-works/pi-agent-core/dist/agent.js +25 -23
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/agent-harness.js +32 -19
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/branch-summarization.js +6 -7
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.js +75 -26
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/utils.js +6 -17
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/jsonl-storage.js +56 -4
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/memory-storage.js +56 -4
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/repo-utils.js +2 -2
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/session.js +30 -14
- package/node_modules/@earendil-works/pi-agent-core/dist/index.js +4 -2
- package/node_modules/@earendil-works/pi-agent-core/dist/stream-fn.js +17 -0
- package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-codex-responses.js +2 -7
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js +14 -2
- package/node_modules/@earendil-works/pi-ai/dist/api/pi-messages.js +2 -4
- package/node_modules/@earendil-works/pi-ai/dist/auth/helpers.js +3 -2
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/kimi-coding.js +249 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/load.js +5 -0
- package/node_modules/@earendil-works/pi-ai/dist/bun-oauth.js +2 -0
- package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.js +2 -0
- package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.js +60 -0
- package/node_modules/@earendil-works/pi-ai/dist/index.js +2 -0
- package/node_modules/@earendil-works/pi-ai/dist/models.generated.js +4 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/all.js +8 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.js +3 -2
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/cerebras.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/cloudflare-ai-gateway.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/cloudflare-workers-ai.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/fireworks.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/github-copilot.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/groq.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/huggingface.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/kimi-coding.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/moonshotai-cn.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/moonshotai.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/openai-codex.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/openai.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode-go.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/qwen-token-plan-cn.json +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/qwen-token-plan.json +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/together.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/xai.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/kimi-coding.js +10 -2
- package/node_modules/@earendil-works/pi-ai/dist/providers/opencode-go.js +2 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan-cn.js +15 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan-cn.models.js +5 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan.js +15 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan.models.js +5 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/radius-config.js +18 -14
- package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js +3 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js +79 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/text.js +10 -0
- package/node_modules/@earendil-works/{pi-agent-core/dist/harness/session → pi-ai/dist/utils}/uuid.js +4 -8
- package/node_modules/@earendil-works/pi-ai/package.json +8 -5
- package/node_modules/@earendil-works/pi-coding-agent/dist/cli/args.js +2 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.js +72 -57
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/compaction/branch-summarization.js +8 -11
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/compaction/compaction.js +63 -26
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/compaction/utils.js +6 -17
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/runner.js +5 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-resolver.js +2 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-runtime.js +18 -15
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/project-trust.js +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/remote-catalog-provider.js +26 -7
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/sdk.js +6 -2
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/session-manager.js +100 -24
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/usage-totals.js +52 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/extensions/llama/client.js +3 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/extensions/llama/index.js +3 -3
- package/node_modules/@earendil-works/pi-coding-agent/dist/index.js +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/main.js +5 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/footer.js +20 -21
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.js +69 -25
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-client.js +7 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-mode.js +4 -0
- package/node_modules/@earendil-works/pi-coding-agent/npm-shrinkwrap.json +16 -31
- package/node_modules/@earendil-works/pi-coding-agent/package.json +4 -4
- package/node_modules/@earendil-works/{pi-orchestrator → pi-server}/dist/cli.js +5 -5
- package/node_modules/@earendil-works/{pi-orchestrator → pi-server}/dist/config.js +9 -9
- package/node_modules/@earendil-works/{pi-orchestrator → pi-server}/dist/ipc/client.js +1 -1
- package/node_modules/@earendil-works/{pi-orchestrator → pi-server}/dist/ipc/server.js +1 -1
- package/node_modules/@earendil-works/{pi-orchestrator → pi-server}/dist/radius.js +8 -8
- package/node_modules/@earendil-works/{pi-orchestrator → pi-server}/dist/rpc-process.js +1 -1
- package/node_modules/@earendil-works/{pi-orchestrator → pi-server}/dist/serve.js +3 -3
- package/node_modules/@earendil-works/{pi-orchestrator → pi-server}/dist/storage.js +7 -7
- package/node_modules/@earendil-works/{pi-orchestrator → pi-server}/dist/supervisor.js +2 -2
- package/node_modules/@earendil-works/{pi-orchestrator → pi-server}/package.json +9 -6
- package/node_modules/@earendil-works/pi-tui/dist/components/editor.js +16 -10
- package/node_modules/@earendil-works/pi-tui/dist/tui.js +4 -2
- package/node_modules/@earendil-works/pi-tui/package.json +1 -1
- package/package.json +7 -7
- package/slingshot/index.js +253 -250
- /package/node_modules/@earendil-works/{pi-orchestrator → pi-server}/dist/handler.js +0 -0
- /package/node_modules/@earendil-works/{pi-orchestrator → pi-server}/dist/index.js +0 -0
- /package/node_modules/@earendil-works/{pi-orchestrator → pi-server}/dist/ipc/protocol.js +0 -0
- /package/node_modules/@earendil-works/{pi-orchestrator → pi-server}/dist/types.js +0 -0
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
16
16
|
import { basename, dirname } from "node:path";
|
|
17
|
+
import { contentText } from "@earendil-works/pi-ai";
|
|
17
18
|
import { clampThinkingLevel, cleanupSessionResources, getSupportedThinkingLevels, isContextOverflow, isRetryableAssistantError, modelsAreEqual, resetApiProviders, streamSimple, } from "@earendil-works/pi-ai/compat";
|
|
18
19
|
import { getThemeByName, theme } from "../modes/interactive/theme/theme.js";
|
|
19
20
|
import { stripFrontmatter } from "../utils/frontmatter.js";
|
|
@@ -35,6 +36,7 @@ import { buildSystemPrompt } from "./system-prompt.js";
|
|
|
35
36
|
import { createLocalBashOperations } from "./tools/bash.js";
|
|
36
37
|
import { createAllToolDefinitions } from "./tools/index.js";
|
|
37
38
|
import { createToolDefinitionFromAgentTool } from "./tools/tool-definition-wrapper.js";
|
|
39
|
+
import { addUsageToTotals, createUsageTotals } from "./usage-totals.js";
|
|
38
40
|
/**
|
|
39
41
|
* Parse a skill block from message text.
|
|
40
42
|
* Returns null if the text doesn't contain a skill block.
|
|
@@ -188,7 +190,7 @@ export class AgentSession {
|
|
|
188
190
|
throw new Error(formatNoApiKeyFoundMessage(model.provider));
|
|
189
191
|
}
|
|
190
192
|
async _getSummarizationRequestAuth(model) {
|
|
191
|
-
if (this.agent.
|
|
193
|
+
if (this.agent.streamFunction === streamSimple) {
|
|
192
194
|
return this._getRequiredRequestAuth(model);
|
|
193
195
|
}
|
|
194
196
|
try {
|
|
@@ -243,6 +245,7 @@ export class AgentSession {
|
|
|
243
245
|
content: result.content,
|
|
244
246
|
details: result.details,
|
|
245
247
|
isError,
|
|
248
|
+
usage: result.usage,
|
|
246
249
|
});
|
|
247
250
|
if (!hookResult) {
|
|
248
251
|
return undefined;
|
|
@@ -251,6 +254,7 @@ export class AgentSession {
|
|
|
251
254
|
content: hookResult.content,
|
|
252
255
|
details: hookResult.details,
|
|
253
256
|
isError: hookResult.isError ?? isError,
|
|
257
|
+
usage: hookResult.usage,
|
|
254
258
|
};
|
|
255
259
|
};
|
|
256
260
|
}
|
|
@@ -325,7 +329,7 @@ export class AgentSession {
|
|
|
325
329
|
// This ensures the UI sees the updated queue state
|
|
326
330
|
if (event.type === "message_start" && event.message.role === "user") {
|
|
327
331
|
this._overflowRecoveryAttempted = false;
|
|
328
|
-
const messageText =
|
|
332
|
+
const messageText = contentText(event.message.content, "");
|
|
329
333
|
if (messageText) {
|
|
330
334
|
// Check steering queue first
|
|
331
335
|
const steeringIndex = this._steeringMessages.indexOf(messageText);
|
|
@@ -394,16 +398,6 @@ export class AgentSession {
|
|
|
394
398
|
}
|
|
395
399
|
return false;
|
|
396
400
|
}
|
|
397
|
-
/** Extract text content from a message */
|
|
398
|
-
_getUserMessageText(message) {
|
|
399
|
-
if (message.role !== "user")
|
|
400
|
-
return "";
|
|
401
|
-
const content = message.content;
|
|
402
|
-
if (typeof content === "string")
|
|
403
|
-
return content;
|
|
404
|
-
const textBlocks = content.filter((c) => c.type === "text");
|
|
405
|
-
return textBlocks.map((c) => c.text).join("");
|
|
406
|
-
}
|
|
407
401
|
/** Find the last assistant message in agent state (including aborted ones) */
|
|
408
402
|
_findLastAssistantMessage() {
|
|
409
403
|
const messages = this.agent.state.messages;
|
|
@@ -570,7 +564,7 @@ export class AgentSession {
|
|
|
570
564
|
catch {
|
|
571
565
|
// Dispose must succeed even if an abort hook throws.
|
|
572
566
|
}
|
|
573
|
-
this._extensionRunner.invalidate("This extension ctx is stale after session replacement or reload. Do not use a captured
|
|
567
|
+
this._extensionRunner.invalidate("This extension ctx is stale after session replacement or reload. Do not use a captured pi or command ctx after ctx.newSession(), ctx.fork(), ctx.switchSession(), or ctx.reload(). For newSession, fork, and switchSession, move post-replacement work into withSession and use the ctx passed to withSession. For reload, do not use the old ctx after await ctx.reload().");
|
|
574
568
|
this._disconnectFromAgent();
|
|
575
569
|
this._eventListeners = [];
|
|
576
570
|
cleanupSessionResources(this.sessionId);
|
|
@@ -1414,26 +1408,29 @@ export class AgentSession {
|
|
|
1414
1408
|
let summary;
|
|
1415
1409
|
let firstKeptEntryId;
|
|
1416
1410
|
let tokensBefore;
|
|
1411
|
+
let usage;
|
|
1417
1412
|
let details;
|
|
1418
1413
|
if (extensionCompaction) {
|
|
1419
1414
|
// Extension provided compaction content
|
|
1420
1415
|
summary = extensionCompaction.summary;
|
|
1421
1416
|
firstKeptEntryId = extensionCompaction.firstKeptEntryId;
|
|
1422
1417
|
tokensBefore = extensionCompaction.tokensBefore;
|
|
1418
|
+
usage = extensionCompaction.usage;
|
|
1423
1419
|
details = extensionCompaction.details;
|
|
1424
1420
|
}
|
|
1425
1421
|
else {
|
|
1426
1422
|
// Generate compaction result
|
|
1427
|
-
const result = await compact(preparation, this.model, apiKey, headers, customInstructions, this._compactionAbortController.signal, this.thinkingLevel, this.agent.
|
|
1423
|
+
const result = await compact(preparation, this.model, apiKey, headers, customInstructions, this._compactionAbortController.signal, this.thinkingLevel, this.agent.streamFunction, env, this.settingsManager.getRetrySettings(), this._summarizationRetryCallbacks({ source: "compaction", reason: "manual" }));
|
|
1428
1424
|
summary = result.summary;
|
|
1429
1425
|
firstKeptEntryId = result.firstKeptEntryId;
|
|
1430
1426
|
tokensBefore = result.tokensBefore;
|
|
1427
|
+
usage = result.usage;
|
|
1431
1428
|
details = result.details;
|
|
1432
1429
|
}
|
|
1433
1430
|
if (this._compactionAbortController.signal.aborted) {
|
|
1434
1431
|
throw new Error("Compaction cancelled");
|
|
1435
1432
|
}
|
|
1436
|
-
this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromExtension);
|
|
1433
|
+
this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromExtension, usage);
|
|
1437
1434
|
const newEntries = this.sessionManager.getEntries();
|
|
1438
1435
|
const sessionContext = this.sessionManager.buildSessionContext();
|
|
1439
1436
|
this.agent.state.messages = sessionContext.messages;
|
|
@@ -1454,6 +1451,7 @@ export class AgentSession {
|
|
|
1454
1451
|
firstKeptEntryId,
|
|
1455
1452
|
tokensBefore,
|
|
1456
1453
|
estimatedTokensAfter,
|
|
1454
|
+
usage,
|
|
1457
1455
|
details,
|
|
1458
1456
|
};
|
|
1459
1457
|
this._emit({
|
|
@@ -1600,7 +1598,7 @@ export class AgentSession {
|
|
|
1600
1598
|
let apiKey;
|
|
1601
1599
|
let headers;
|
|
1602
1600
|
let env;
|
|
1603
|
-
if (this.agent.
|
|
1601
|
+
if (this.agent.streamFunction === streamSimple) {
|
|
1604
1602
|
const authResult = await this._modelRuntime.getAuth(this.model);
|
|
1605
1603
|
if (!authResult?.auth.apiKey)
|
|
1606
1604
|
return false;
|
|
@@ -1649,20 +1647,23 @@ export class AgentSession {
|
|
|
1649
1647
|
let summary;
|
|
1650
1648
|
let firstKeptEntryId;
|
|
1651
1649
|
let tokensBefore;
|
|
1650
|
+
let usage;
|
|
1652
1651
|
let details;
|
|
1653
1652
|
if (extensionCompaction) {
|
|
1654
1653
|
// Extension provided compaction content
|
|
1655
1654
|
summary = extensionCompaction.summary;
|
|
1656
1655
|
firstKeptEntryId = extensionCompaction.firstKeptEntryId;
|
|
1657
1656
|
tokensBefore = extensionCompaction.tokensBefore;
|
|
1657
|
+
usage = extensionCompaction.usage;
|
|
1658
1658
|
details = extensionCompaction.details;
|
|
1659
1659
|
}
|
|
1660
1660
|
else {
|
|
1661
1661
|
// Generate compaction result
|
|
1662
|
-
const compactResult = await compact(preparation, this.model, apiKey, headers, undefined, this._autoCompactionAbortController.signal, this.thinkingLevel, this.agent.
|
|
1662
|
+
const compactResult = await compact(preparation, this.model, apiKey, headers, undefined, this._autoCompactionAbortController.signal, this.thinkingLevel, this.agent.streamFunction, env, this.settingsManager.getRetrySettings(), this._summarizationRetryCallbacks({ source: "compaction", reason }));
|
|
1663
1663
|
summary = compactResult.summary;
|
|
1664
1664
|
firstKeptEntryId = compactResult.firstKeptEntryId;
|
|
1665
1665
|
tokensBefore = compactResult.tokensBefore;
|
|
1666
|
+
usage = compactResult.usage;
|
|
1666
1667
|
details = compactResult.details;
|
|
1667
1668
|
}
|
|
1668
1669
|
if (this._autoCompactionAbortController.signal.aborted) {
|
|
@@ -1675,7 +1676,7 @@ export class AgentSession {
|
|
|
1675
1676
|
});
|
|
1676
1677
|
return false;
|
|
1677
1678
|
}
|
|
1678
|
-
this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromExtension);
|
|
1679
|
+
this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromExtension, usage);
|
|
1679
1680
|
const newEntries = this.sessionManager.getEntries();
|
|
1680
1681
|
const sessionContext = this.sessionManager.buildSessionContext();
|
|
1681
1682
|
this.agent.state.messages = sessionContext.messages;
|
|
@@ -1696,6 +1697,7 @@ export class AgentSession {
|
|
|
1696
1697
|
firstKeptEntryId,
|
|
1697
1698
|
tokensBefore,
|
|
1698
1699
|
estimatedTokensAfter,
|
|
1700
|
+
usage,
|
|
1699
1701
|
details,
|
|
1700
1702
|
};
|
|
1701
1703
|
this._emit({ type: "compaction_end", reason, result, aborted: false, willRetry });
|
|
@@ -2086,6 +2088,34 @@ export class AgentSession {
|
|
|
2086
2088
|
return false;
|
|
2087
2089
|
return isRetryableAssistantError(message);
|
|
2088
2090
|
}
|
|
2091
|
+
/**
|
|
2092
|
+
* Retry policy + callbacks shared by compaction and branch-summary summarization calls.
|
|
2093
|
+
* Uses the same `settings.retry` budget/backoff as agent-turn retries so a single transient
|
|
2094
|
+
* stream drop no longer fails the whole operation. `source` carries the context
|
|
2095
|
+
* the TUI needs to render the retry and recreate the underlying indicator.
|
|
2096
|
+
*/
|
|
2097
|
+
_summarizationRetryCallbacks(source) {
|
|
2098
|
+
return {
|
|
2099
|
+
onRetryScheduled: (attempt, maxAttempts, delayMs, errorMessage) => {
|
|
2100
|
+
this._emit({
|
|
2101
|
+
type: "summarization_retry_scheduled",
|
|
2102
|
+
attempt,
|
|
2103
|
+
maxAttempts,
|
|
2104
|
+
delayMs,
|
|
2105
|
+
errorMessage,
|
|
2106
|
+
});
|
|
2107
|
+
},
|
|
2108
|
+
onRetryAttemptStart: () => {
|
|
2109
|
+
this._emit({
|
|
2110
|
+
type: "summarization_retry_attempt_start",
|
|
2111
|
+
...source,
|
|
2112
|
+
});
|
|
2113
|
+
},
|
|
2114
|
+
onRetryFinished: () => {
|
|
2115
|
+
this._emit({ type: "summarization_retry_finished" });
|
|
2116
|
+
},
|
|
2117
|
+
};
|
|
2118
|
+
}
|
|
2089
2119
|
/**
|
|
2090
2120
|
* Prepare a retryable error for continuation with exponential backoff.
|
|
2091
2121
|
* @returns true if the caller should continue the agent, false otherwise
|
|
@@ -2331,6 +2361,7 @@ export class AgentSession {
|
|
|
2331
2361
|
// Run default summarizer if needed
|
|
2332
2362
|
let summaryText;
|
|
2333
2363
|
let summaryDetails;
|
|
2364
|
+
let summaryUsage;
|
|
2334
2365
|
if (options.summarize && entriesToSummarize.length > 0 && !extensionSummary) {
|
|
2335
2366
|
const model = this.model;
|
|
2336
2367
|
const { apiKey, headers, env } = await this._getSummarizationRequestAuth(model);
|
|
@@ -2344,7 +2375,9 @@ export class AgentSession {
|
|
|
2344
2375
|
customInstructions,
|
|
2345
2376
|
replaceInstructions,
|
|
2346
2377
|
reserveTokens: branchSummarySettings.reserveTokens,
|
|
2347
|
-
streamFn: this.agent.
|
|
2378
|
+
streamFn: this.agent.streamFunction,
|
|
2379
|
+
retry: this.settingsManager.getRetrySettings(),
|
|
2380
|
+
callbacks: this._summarizationRetryCallbacks({ source: "branchSummary" }),
|
|
2348
2381
|
});
|
|
2349
2382
|
if (result.aborted) {
|
|
2350
2383
|
return { cancelled: true, aborted: true };
|
|
@@ -2353,6 +2386,7 @@ export class AgentSession {
|
|
|
2353
2386
|
throw new Error(result.error);
|
|
2354
2387
|
}
|
|
2355
2388
|
summaryText = result.summary;
|
|
2389
|
+
summaryUsage = result.usage;
|
|
2356
2390
|
summaryDetails = {
|
|
2357
2391
|
readFiles: result.readFiles || [],
|
|
2358
2392
|
modifiedFiles: result.modifiedFiles || [],
|
|
@@ -2361,6 +2395,7 @@ export class AgentSession {
|
|
|
2361
2395
|
else if (extensionSummary) {
|
|
2362
2396
|
summaryText = extensionSummary.summary;
|
|
2363
2397
|
summaryDetails = extensionSummary.details;
|
|
2398
|
+
summaryUsage = extensionSummary.usage;
|
|
2364
2399
|
}
|
|
2365
2400
|
// Determine the new leaf position based on target type
|
|
2366
2401
|
let newLeafId;
|
|
@@ -2368,18 +2403,12 @@ export class AgentSession {
|
|
|
2368
2403
|
if (targetEntry.type === "message" && targetEntry.message.role === "user") {
|
|
2369
2404
|
// User message: leaf = parent (null if root), text goes to editor
|
|
2370
2405
|
newLeafId = targetEntry.parentId;
|
|
2371
|
-
editorText =
|
|
2406
|
+
editorText = contentText(targetEntry.message.content, "");
|
|
2372
2407
|
}
|
|
2373
2408
|
else if (targetEntry.type === "custom_message") {
|
|
2374
2409
|
// Custom message: leaf = parent (null if root), text goes to editor
|
|
2375
2410
|
newLeafId = targetEntry.parentId;
|
|
2376
|
-
editorText =
|
|
2377
|
-
typeof targetEntry.content === "string"
|
|
2378
|
-
? targetEntry.content
|
|
2379
|
-
: targetEntry.content
|
|
2380
|
-
.filter((c) => c.type === "text")
|
|
2381
|
-
.map((c) => c.text)
|
|
2382
|
-
.join("");
|
|
2411
|
+
editorText = contentText(targetEntry.content, "");
|
|
2383
2412
|
}
|
|
2384
2413
|
else {
|
|
2385
2414
|
// Non-user message: leaf = selected node
|
|
@@ -2390,7 +2419,7 @@ export class AgentSession {
|
|
|
2390
2419
|
let summaryEntry;
|
|
2391
2420
|
if (summaryText) {
|
|
2392
2421
|
// Create summary at target position (can be null for root)
|
|
2393
|
-
const summaryId = this.sessionManager.branchWithSummary(newLeafId, summaryText, summaryDetails, fromExtension);
|
|
2422
|
+
const summaryId = this.sessionManager.branchWithSummary(newLeafId, summaryText, summaryDetails, fromExtension, summaryUsage);
|
|
2394
2423
|
summaryEntry = this.sessionManager.getEntry(summaryId);
|
|
2395
2424
|
// Attach label to the summary entry
|
|
2396
2425
|
if (label) {
|
|
@@ -2438,24 +2467,13 @@ export class AgentSession {
|
|
|
2438
2467
|
continue;
|
|
2439
2468
|
if (entry.message.role !== "user")
|
|
2440
2469
|
continue;
|
|
2441
|
-
const text =
|
|
2470
|
+
const text = contentText(entry.message.content, "");
|
|
2442
2471
|
if (text) {
|
|
2443
2472
|
result.push({ entryId: entry.id, text });
|
|
2444
2473
|
}
|
|
2445
2474
|
}
|
|
2446
2475
|
return result;
|
|
2447
2476
|
}
|
|
2448
|
-
_extractUserMessageText(content) {
|
|
2449
|
-
if (typeof content === "string")
|
|
2450
|
-
return content;
|
|
2451
|
-
if (Array.isArray(content)) {
|
|
2452
|
-
return content
|
|
2453
|
-
.filter((c) => c.type === "text")
|
|
2454
|
-
.map((c) => c.text)
|
|
2455
|
-
.join("");
|
|
2456
|
-
}
|
|
2457
|
-
return "";
|
|
2458
|
-
}
|
|
2459
2477
|
/**
|
|
2460
2478
|
* Get session statistics. Aggregates over ALL session entries (including
|
|
2461
2479
|
* history that was compacted away), so token/cost totals reflect what was
|
|
@@ -2467,12 +2485,11 @@ export class AgentSession {
|
|
|
2467
2485
|
let toolResults = 0;
|
|
2468
2486
|
let totalMessages = 0;
|
|
2469
2487
|
let toolCalls = 0;
|
|
2470
|
-
|
|
2471
|
-
let totalOutput = 0;
|
|
2472
|
-
let totalCacheRead = 0;
|
|
2473
|
-
let totalCacheWrite = 0;
|
|
2474
|
-
let totalCost = 0;
|
|
2488
|
+
const usageTotals = createUsageTotals();
|
|
2475
2489
|
for (const entry of this.sessionManager.getEntries()) {
|
|
2490
|
+
if ((entry.type === "branch_summary" || entry.type === "compaction") && entry.usage) {
|
|
2491
|
+
addUsageToTotals(usageTotals, entry.usage);
|
|
2492
|
+
}
|
|
2476
2493
|
if (entry.type !== "message")
|
|
2477
2494
|
continue;
|
|
2478
2495
|
totalMessages++;
|
|
@@ -2482,6 +2499,9 @@ export class AgentSession {
|
|
|
2482
2499
|
}
|
|
2483
2500
|
else if (message.role === "toolResult") {
|
|
2484
2501
|
toolResults++;
|
|
2502
|
+
if (message.usage) {
|
|
2503
|
+
addUsageToTotals(usageTotals, message.usage);
|
|
2504
|
+
}
|
|
2485
2505
|
}
|
|
2486
2506
|
else if (message.role === "assistant") {
|
|
2487
2507
|
assistantMessages++;
|
|
@@ -2489,12 +2509,7 @@ export class AgentSession {
|
|
|
2489
2509
|
if (Array.isArray(assistantMsg.content)) {
|
|
2490
2510
|
toolCalls += assistantMsg.content.filter((c) => c.type === "toolCall").length;
|
|
2491
2511
|
}
|
|
2492
|
-
|
|
2493
|
-
totalInput += usage.input;
|
|
2494
|
-
totalOutput += usage.output;
|
|
2495
|
-
totalCacheRead += usage.cacheRead;
|
|
2496
|
-
totalCacheWrite += usage.cacheWrite;
|
|
2497
|
-
totalCost += usage.cost.total;
|
|
2512
|
+
addUsageToTotals(usageTotals, assistantMsg.usage);
|
|
2498
2513
|
}
|
|
2499
2514
|
}
|
|
2500
2515
|
return {
|
|
@@ -2506,13 +2521,13 @@ export class AgentSession {
|
|
|
2506
2521
|
toolResults,
|
|
2507
2522
|
totalMessages,
|
|
2508
2523
|
tokens: {
|
|
2509
|
-
input:
|
|
2510
|
-
output:
|
|
2511
|
-
cacheRead:
|
|
2512
|
-
cacheWrite:
|
|
2513
|
-
total:
|
|
2524
|
+
input: usageTotals.input,
|
|
2525
|
+
output: usageTotals.output,
|
|
2526
|
+
cacheRead: usageTotals.cacheRead,
|
|
2527
|
+
cacheWrite: usageTotals.cacheWrite,
|
|
2528
|
+
total: usageTotals.input + usageTotals.output + usageTotals.cacheRead + usageTotals.cacheWrite,
|
|
2514
2529
|
},
|
|
2515
|
-
cost:
|
|
2530
|
+
cost: usageTotals.cost,
|
|
2516
2531
|
contextUsage: this.getContextUsage(),
|
|
2517
2532
|
};
|
|
2518
2533
|
}
|
package/node_modules/@earendil-works/pi-coding-agent/dist/core/compaction/branch-summarization.js
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* When navigating to a different point in the session tree, this generates
|
|
5
5
|
* a summary of the branch being left so context isn't lost.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
7
|
+
import { contentText } from "@earendil-works/pi-ai";
|
|
8
8
|
import { convertToLlm, createBranchSummaryMessage, createCompactionSummaryMessage, createCustomMessage, } from "../messages.js";
|
|
9
|
-
import { estimateTokens } from "./compaction.js";
|
|
9
|
+
import { completeSummarization, estimateTokens } from "./compaction.js";
|
|
10
10
|
import { computeFileLists, createFileOps, extractFileOpsFromMessage, formatFileOperations, SUMMARIZATION_SYSTEM_PROMPT, serializeConversation, } from "./utils.js";
|
|
11
11
|
// ============================================================================
|
|
12
12
|
// Entry Collection
|
|
@@ -185,7 +185,7 @@ Keep each section concise. Preserve exact file paths, function names, and error
|
|
|
185
185
|
* @param options - Generation options
|
|
186
186
|
*/
|
|
187
187
|
export async function generateBranchSummary(entries, options) {
|
|
188
|
-
const { model, apiKey, headers, env, signal, customInstructions, replaceInstructions, reserveTokens = 16384, streamFn, } = options;
|
|
188
|
+
const { model, apiKey, headers, env, signal, customInstructions, replaceInstructions, reserveTokens = 16384, streamFn, retry, callbacks, } = options;
|
|
189
189
|
// Token budget = context window minus reserved space for prompt + response
|
|
190
190
|
const contextWindow = model.contextWindow || 128000;
|
|
191
191
|
const tokenBudget = contextWindow - reserveTokens;
|
|
@@ -218,12 +218,11 @@ export async function generateBranchSummary(entries, options) {
|
|
|
218
218
|
];
|
|
219
219
|
// Call LLM for summarization. Prefer the session stream function so SDK
|
|
220
220
|
// request behavior (timeouts, retries, attribution headers) stays consistent
|
|
221
|
-
// without running through agent state/events.
|
|
221
|
+
// without running through agent state/events. Retried via completeSummarization
|
|
222
|
+
// so transient stream drops reuse the configured retry policy.
|
|
222
223
|
const context = { systemPrompt: SUMMARIZATION_SYSTEM_PROMPT, messages: summarizationMessages };
|
|
223
224
|
const requestOptions = { apiKey, headers, env, signal, maxTokens: 2048 };
|
|
224
|
-
const response = streamFn
|
|
225
|
-
? await (await streamFn(model, context, requestOptions)).result()
|
|
226
|
-
: await completeSimple(model, context, requestOptions);
|
|
225
|
+
const response = await completeSummarization(model, context, requestOptions, streamFn, retry, callbacks);
|
|
227
226
|
// Check if aborted or errored
|
|
228
227
|
if (response.stopReason === "aborted") {
|
|
229
228
|
return { aborted: true };
|
|
@@ -231,10 +230,7 @@ export async function generateBranchSummary(entries, options) {
|
|
|
231
230
|
if (response.stopReason === "error") {
|
|
232
231
|
return { error: response.errorMessage || "Summarization failed" };
|
|
233
232
|
}
|
|
234
|
-
let summary = response.content
|
|
235
|
-
.filter((c) => c.type === "text")
|
|
236
|
-
.map((c) => c.text)
|
|
237
|
-
.join("\n");
|
|
233
|
+
let summary = contentText(response.content);
|
|
238
234
|
// Prepend preamble to provide context about the branch summary
|
|
239
235
|
summary = BRANCH_SUMMARY_PREAMBLE + summary;
|
|
240
236
|
// Compute file lists and append to summary
|
|
@@ -242,6 +238,7 @@ export async function generateBranchSummary(entries, options) {
|
|
|
242
238
|
summary += formatFileOperations(readFiles, modifiedFiles);
|
|
243
239
|
return {
|
|
244
240
|
summary: summary || "No summary generated",
|
|
241
|
+
usage: response.usage,
|
|
245
242
|
readFiles,
|
|
246
243
|
modifiedFiles,
|
|
247
244
|
};
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Pure functions for compaction logic. The session manager handles I/O,
|
|
5
5
|
* and after compaction the session is reloaded.
|
|
6
6
|
*/
|
|
7
|
+
import { contentText, retryAssistantCall } from "@earendil-works/pi-ai";
|
|
7
8
|
import { completeSimple } from "@earendil-works/pi-ai/compat";
|
|
8
9
|
import { convertToLlm } from "../messages.js";
|
|
9
10
|
import { buildSessionContext, sessionEntryToContextMessages, } from "../session-manager.js";
|
|
@@ -48,6 +49,28 @@ function getMessageFromEntryForCompaction(entry) {
|
|
|
48
49
|
}
|
|
49
50
|
return sessionEntryToContextMessages(entry)[0];
|
|
50
51
|
}
|
|
52
|
+
function combineUsage(first, second) {
|
|
53
|
+
return {
|
|
54
|
+
input: first.input + second.input,
|
|
55
|
+
output: first.output + second.output,
|
|
56
|
+
cacheRead: first.cacheRead + second.cacheRead,
|
|
57
|
+
cacheWrite: first.cacheWrite + second.cacheWrite,
|
|
58
|
+
...(first.cacheWrite1h !== undefined || second.cacheWrite1h !== undefined
|
|
59
|
+
? { cacheWrite1h: (first.cacheWrite1h ?? 0) + (second.cacheWrite1h ?? 0) }
|
|
60
|
+
: {}),
|
|
61
|
+
...(first.reasoning !== undefined || second.reasoning !== undefined
|
|
62
|
+
? { reasoning: (first.reasoning ?? 0) + (second.reasoning ?? 0) }
|
|
63
|
+
: {}),
|
|
64
|
+
totalTokens: first.totalTokens + second.totalTokens,
|
|
65
|
+
cost: {
|
|
66
|
+
input: first.cost.input + second.cost.input,
|
|
67
|
+
output: first.cost.output + second.cost.output,
|
|
68
|
+
cacheRead: first.cost.cacheRead + second.cost.cacheRead,
|
|
69
|
+
cacheWrite: first.cost.cacheWrite + second.cost.cacheWrite,
|
|
70
|
+
total: first.cost.total + second.cost.total,
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
51
74
|
export const DEFAULT_COMPACTION_SETTINGS = {
|
|
52
75
|
enabled: true,
|
|
53
76
|
reserveTokens: 16384,
|
|
@@ -407,18 +430,26 @@ function createSummarizationOptions(model, maxTokens, apiKey, headers, env, sign
|
|
|
407
430
|
}
|
|
408
431
|
return options;
|
|
409
432
|
}
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
433
|
+
/**
|
|
434
|
+
* Shared choke point for every compaction/branch-summary summarization call. Wraps the
|
|
435
|
+
* single LLM call in {@link retryAssistantCall} so transient stream drops (e.g.
|
|
436
|
+
* `terminated`, socket close) honor the configured retry policy instead of failing
|
|
437
|
+
* the whole compaction on the first attempt. Deterministic errors and aborts return
|
|
438
|
+
* immediately (see {@link retryAssistantCall}).
|
|
439
|
+
*/
|
|
440
|
+
export async function completeSummarization(model, context, options, streamFn, retry, callbacks) {
|
|
441
|
+
const produce = async () => streamFn ? (await streamFn(model, context, options)).result() : completeSimple(model, context, options);
|
|
442
|
+
return retryAssistantCall(produce, retry, options.signal, callbacks);
|
|
416
443
|
}
|
|
417
444
|
/**
|
|
418
445
|
* Generate a summary of the conversation using the LLM.
|
|
419
446
|
* If previousSummary is provided, uses the update prompt to merge.
|
|
420
447
|
*/
|
|
421
|
-
export async function generateSummary(currentMessages, model, reserveTokens, apiKey, headers, signal, customInstructions, previousSummary, thinkingLevel, streamFn, env) {
|
|
448
|
+
export async function generateSummary(currentMessages, model, reserveTokens, apiKey, headers, signal, customInstructions, previousSummary, thinkingLevel, streamFn, env, retry, callbacks) {
|
|
449
|
+
return (await generateSummaryWithUsage(currentMessages, model, reserveTokens, apiKey, headers, signal, customInstructions, previousSummary, thinkingLevel, streamFn, env, retry, callbacks)).text;
|
|
450
|
+
}
|
|
451
|
+
/** Generate or update a conversation summary and return its provider usage. */
|
|
452
|
+
export async function generateSummaryWithUsage(currentMessages, model, reserveTokens, apiKey, headers, signal, customInstructions, previousSummary, thinkingLevel, streamFn, env, retry, callbacks) {
|
|
422
453
|
const maxTokens = Math.min(Math.floor(0.8 * reserveTokens), model.maxTokens > 0 ? model.maxTokens : Number.POSITIVE_INFINITY);
|
|
423
454
|
// Use update prompt if we have a previous summary, otherwise initial prompt
|
|
424
455
|
let basePrompt = previousSummary ? UPDATE_SUMMARIZATION_PROMPT : SUMMARIZATION_PROMPT;
|
|
@@ -443,15 +474,12 @@ export async function generateSummary(currentMessages, model, reserveTokens, api
|
|
|
443
474
|
},
|
|
444
475
|
];
|
|
445
476
|
const completionOptions = createSummarizationOptions(model, maxTokens, apiKey, headers, env, signal, thinkingLevel);
|
|
446
|
-
const response = await completeSummarization(model, { systemPrompt: SUMMARIZATION_SYSTEM_PROMPT, messages: summarizationMessages }, completionOptions, streamFn);
|
|
477
|
+
const response = await completeSummarization(model, { systemPrompt: SUMMARIZATION_SYSTEM_PROMPT, messages: summarizationMessages }, completionOptions, streamFn, retry, callbacks);
|
|
447
478
|
if (response.stopReason === "error") {
|
|
448
479
|
throw new Error(`Summarization failed: ${response.errorMessage || "Unknown error"}`);
|
|
449
480
|
}
|
|
450
|
-
const textContent = response.content
|
|
451
|
-
|
|
452
|
-
.map((c) => c.text)
|
|
453
|
-
.join("\n");
|
|
454
|
-
return textContent;
|
|
481
|
+
const textContent = contentText(response.content);
|
|
482
|
+
return { text: textContent, usage: response.usage };
|
|
455
483
|
}
|
|
456
484
|
export function prepareCompaction(pathEntries, settings) {
|
|
457
485
|
if (pathEntries.length > 0 && pathEntries[pathEntries.length - 1].type === "compaction") {
|
|
@@ -544,21 +572,29 @@ Be concise. Focus on what's needed to understand the kept suffix.`;
|
|
|
544
572
|
* @param preparation - Pre-calculated preparation from prepareCompaction()
|
|
545
573
|
* @param customInstructions - Optional custom focus for the summary
|
|
546
574
|
*/
|
|
547
|
-
export async function compact(preparation, model, apiKey, headers, customInstructions, signal, thinkingLevel, streamFn, env) {
|
|
575
|
+
export async function compact(preparation, model, apiKey, headers, customInstructions, signal, thinkingLevel, streamFn, env, retry, callbacks) {
|
|
548
576
|
const { firstKeptEntryId, messagesToSummarize, turnPrefixMessages, isSplitTurn, tokensBefore, previousSummary, fileOps, settings, } = preparation;
|
|
549
577
|
// Generate summaries and merge into one
|
|
550
578
|
let summary;
|
|
579
|
+
let summaryUsage;
|
|
551
580
|
if (isSplitTurn && turnPrefixMessages.length > 0) {
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
581
|
+
let historyText = "No prior history.";
|
|
582
|
+
let historyUsage;
|
|
583
|
+
if (messagesToSummarize.length > 0) {
|
|
584
|
+
const historyResult = await generateSummaryWithUsage(messagesToSummarize, model, settings.reserveTokens, apiKey, headers, signal, customInstructions, previousSummary, thinkingLevel, streamFn, env, retry, callbacks);
|
|
585
|
+
historyText = historyResult.text;
|
|
586
|
+
historyUsage = historyResult.usage;
|
|
587
|
+
}
|
|
588
|
+
const turnPrefixResult = await generateTurnPrefixSummary(turnPrefixMessages, model, settings.reserveTokens, apiKey, headers, env, signal, thinkingLevel, streamFn, retry, callbacks);
|
|
556
589
|
// Merge into single summary
|
|
557
|
-
summary = `${
|
|
590
|
+
summary = `${historyText}\n\n---\n\n**Turn Context (split turn):**\n\n${turnPrefixResult.text}`;
|
|
591
|
+
summaryUsage = historyUsage ? combineUsage(historyUsage, turnPrefixResult.usage) : turnPrefixResult.usage;
|
|
558
592
|
}
|
|
559
593
|
else {
|
|
560
594
|
// Just generate history summary
|
|
561
|
-
|
|
595
|
+
const result = await generateSummaryWithUsage(messagesToSummarize, model, settings.reserveTokens, apiKey, headers, signal, customInstructions, previousSummary, thinkingLevel, streamFn, env, retry, callbacks);
|
|
596
|
+
summary = result.text;
|
|
597
|
+
summaryUsage = result.usage;
|
|
562
598
|
}
|
|
563
599
|
// Compute file lists and append to summary
|
|
564
600
|
const { readFiles, modifiedFiles } = computeFileLists(fileOps);
|
|
@@ -570,13 +606,14 @@ export async function compact(preparation, model, apiKey, headers, customInstruc
|
|
|
570
606
|
summary,
|
|
571
607
|
firstKeptEntryId,
|
|
572
608
|
tokensBefore,
|
|
609
|
+
usage: summaryUsage,
|
|
573
610
|
details: { readFiles, modifiedFiles },
|
|
574
611
|
};
|
|
575
612
|
}
|
|
576
613
|
/**
|
|
577
614
|
* Generate a summary for a turn prefix (when splitting a turn).
|
|
578
615
|
*/
|
|
579
|
-
async function generateTurnPrefixSummary(messages, model, reserveTokens, apiKey, headers, env, signal, thinkingLevel, streamFn) {
|
|
616
|
+
async function generateTurnPrefixSummary(messages, model, reserveTokens, apiKey, headers, env, signal, thinkingLevel, streamFn, retry, callbacks) {
|
|
580
617
|
const maxTokens = Math.min(Math.floor(0.5 * reserveTokens), model.maxTokens > 0 ? model.maxTokens : Number.POSITIVE_INFINITY); // Smaller budget for turn prefix
|
|
581
618
|
const llmMessages = convertToLlm(messages);
|
|
582
619
|
const conversationText = serializeConversation(llmMessages);
|
|
@@ -588,13 +625,13 @@ async function generateTurnPrefixSummary(messages, model, reserveTokens, apiKey,
|
|
|
588
625
|
timestamp: Date.now(),
|
|
589
626
|
},
|
|
590
627
|
];
|
|
591
|
-
const response = await completeSummarization(model, { systemPrompt: SUMMARIZATION_SYSTEM_PROMPT, messages: summarizationMessages }, createSummarizationOptions(model, maxTokens, apiKey, headers, env, signal, thinkingLevel), streamFn);
|
|
628
|
+
const response = await completeSummarization(model, { systemPrompt: SUMMARIZATION_SYSTEM_PROMPT, messages: summarizationMessages }, createSummarizationOptions(model, maxTokens, apiKey, headers, env, signal, thinkingLevel), streamFn, retry, callbacks);
|
|
592
629
|
if (response.stopReason === "error") {
|
|
593
630
|
throw new Error(`Turn prefix summarization failed: ${response.errorMessage || "Unknown error"}`);
|
|
594
631
|
}
|
|
595
|
-
return
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
632
|
+
return {
|
|
633
|
+
text: contentText(response.content),
|
|
634
|
+
usage: response.usage,
|
|
635
|
+
};
|
|
599
636
|
}
|
|
600
637
|
//# sourceMappingURL=compaction.js.map
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared utilities for compaction and branch summarization.
|
|
3
3
|
*/
|
|
4
|
+
import { contentText } from "@earendil-works/pi-ai";
|
|
4
5
|
export function createFileOps() {
|
|
5
6
|
return {
|
|
6
7
|
read: new Set(),
|
|
@@ -94,24 +95,15 @@ export function serializeConversation(messages) {
|
|
|
94
95
|
const parts = [];
|
|
95
96
|
for (const msg of messages) {
|
|
96
97
|
if (msg.role === "user") {
|
|
97
|
-
const content =
|
|
98
|
-
? msg.content
|
|
99
|
-
: msg.content
|
|
100
|
-
.filter((c) => c.type === "text")
|
|
101
|
-
.map((c) => c.text)
|
|
102
|
-
.join("");
|
|
98
|
+
const content = contentText(msg.content, "");
|
|
103
99
|
if (content)
|
|
104
100
|
parts.push(`[User]: ${content}`);
|
|
105
101
|
}
|
|
106
102
|
else if (msg.role === "assistant") {
|
|
107
|
-
const textParts = [];
|
|
108
103
|
const thinkingParts = [];
|
|
109
104
|
const toolCalls = [];
|
|
110
105
|
for (const block of msg.content) {
|
|
111
|
-
if (block.type === "
|
|
112
|
-
textParts.push(block.text);
|
|
113
|
-
}
|
|
114
|
-
else if (block.type === "thinking") {
|
|
106
|
+
if (block.type === "thinking") {
|
|
115
107
|
thinkingParts.push(block.thinking);
|
|
116
108
|
}
|
|
117
109
|
else if (block.type === "toolCall") {
|
|
@@ -125,18 +117,15 @@ export function serializeConversation(messages) {
|
|
|
125
117
|
if (thinkingParts.length > 0) {
|
|
126
118
|
parts.push(`[Assistant thinking]: ${thinkingParts.join("\n")}`);
|
|
127
119
|
}
|
|
128
|
-
if (
|
|
129
|
-
parts.push(`[Assistant]: ${
|
|
120
|
+
if (msg.content.some((block) => block.type === "text")) {
|
|
121
|
+
parts.push(`[Assistant]: ${contentText(msg.content)}`);
|
|
130
122
|
}
|
|
131
123
|
if (toolCalls.length > 0) {
|
|
132
124
|
parts.push(`[Assistant tool calls]: ${toolCalls.join("; ")}`);
|
|
133
125
|
}
|
|
134
126
|
}
|
|
135
127
|
else if (msg.role === "toolResult") {
|
|
136
|
-
const content = msg.content
|
|
137
|
-
.filter((c) => c.type === "text")
|
|
138
|
-
.map((c) => c.text)
|
|
139
|
-
.join("");
|
|
128
|
+
const content = contentText(msg.content, "");
|
|
140
129
|
if (content) {
|
|
141
130
|
parts.push(`[Tool result]: ${truncateForSummary(content, TOOL_RESULT_MAX_CHARS)}`);
|
|
142
131
|
}
|
|
@@ -657,6 +657,10 @@ export class ExtensionRunner {
|
|
|
657
657
|
currentEvent.isError = handlerResult.isError;
|
|
658
658
|
modified = true;
|
|
659
659
|
}
|
|
660
|
+
if (handlerResult.usage !== undefined) {
|
|
661
|
+
currentEvent.usage = handlerResult.usage;
|
|
662
|
+
modified = true;
|
|
663
|
+
}
|
|
660
664
|
}
|
|
661
665
|
catch (err) {
|
|
662
666
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -677,6 +681,7 @@ export class ExtensionRunner {
|
|
|
677
681
|
content: currentEvent.content,
|
|
678
682
|
details: currentEvent.details,
|
|
679
683
|
isError: currentEvent.isError,
|
|
684
|
+
usage: currentEvent.usage,
|
|
680
685
|
};
|
|
681
686
|
}
|
|
682
687
|
async emitToolCall(event) {
|
|
@@ -40,6 +40,8 @@ export const defaultModelPerProvider = {
|
|
|
40
40
|
"kimi-coding": "kimi-for-coding",
|
|
41
41
|
"cloudflare-workers-ai": "@cf/moonshotai/kimi-k2.6",
|
|
42
42
|
"cloudflare-ai-gateway": "workers-ai/@cf/moonshotai/kimi-k2.6",
|
|
43
|
+
"qwen-token-plan": "qwen3.7-max",
|
|
44
|
+
"qwen-token-plan-cn": "qwen3.7-max",
|
|
43
45
|
xiaomi: "mimo-v2.5-pro",
|
|
44
46
|
"xiaomi-token-plan-cn": "mimo-v2.5-pro",
|
|
45
47
|
"xiaomi-token-plan-ams": "mimo-v2.5-pro",
|