@earendil-works/pi-coding-agent 0.80.3 → 0.80.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +67 -0
- package/README.md +1 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +2 -2
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/config-selector.d.ts +4 -2
- package/dist/cli/config-selector.d.ts.map +1 -1
- package/dist/cli/config-selector.js +1 -1
- package/dist/cli/config-selector.js.map +1 -1
- package/dist/core/agent-session.d.ts +20 -4
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +95 -22
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +13 -5
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/cache-stats.d.ts +49 -0
- package/dist/core/cache-stats.d.ts.map +1 -0
- package/dist/core/cache-stats.js +101 -0
- package/dist/core/cache-stats.js.map +1 -0
- package/dist/core/compaction/compaction.d.ts +1 -2
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +56 -81
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/extensions/index.d.ts +1 -1
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +6 -0
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +5 -3
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +38 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +31 -4
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +3 -6
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/model-registry.d.ts +3 -0
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +16 -2
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver.d.ts +10 -0
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +14 -17
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/package-manager.d.ts +4 -1
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +68 -26
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/provider-attribution.d.ts.map +1 -1
- package/dist/core/provider-attribution.js +0 -10
- package/dist/core/provider-attribution.js.map +1 -1
- package/dist/core/resource-loader.d.ts +2 -2
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +6 -7
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/sdk.d.ts +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +8 -1
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts +20 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +97 -68
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +5 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +8 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/slash-commands.d.ts +1 -0
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +3 -3
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +20 -5
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +2 -2
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts +2 -2
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +1 -0
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/config-selector.d.ts +34 -3
- package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/config-selector.js +280 -25
- package/dist/modes/interactive/components/config-selector.js.map +1 -1
- package/dist/modes/interactive/components/custom-entry.d.ts +19 -0
- package/dist/modes/interactive/components/custom-entry.d.ts.map +1 -0
- package/dist/modes/interactive/components/custom-entry.js +52 -0
- package/dist/modes/interactive/components/custom-entry.js.map +1 -0
- package/dist/modes/interactive/components/footer.d.ts +4 -0
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +1 -1
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.d.ts +3 -1
- package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.js +17 -6
- package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +2 -0
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +10 -0
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +15 -3
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +302 -61
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +1 -1
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +6 -6
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +3 -3
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +4 -1
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/package-manager-cli.d.ts +2 -2
- package/dist/package-manager-cli.d.ts.map +1 -1
- package/dist/package-manager-cli.js +71 -17
- package/dist/package-manager-cli.js.map +1 -1
- package/dist/utils/clipboard-image.d.ts.map +1 -1
- package/dist/utils/clipboard-image.js +1 -1
- package/dist/utils/clipboard-image.js.map +1 -1
- package/docs/extensions.md +74 -17
- package/docs/models.md +4 -4
- package/docs/packages.md +2 -2
- package/docs/rpc.md +13 -3
- package/docs/sdk.md +50 -0
- package/docs/session-format.md +18 -8
- package/docs/settings.md +1 -0
- package/docs/usage.md +1 -1
- package/examples/extensions/README.md +1 -0
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/entry-renderer.ts +41 -0
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/question.ts +1 -0
- package/examples/extensions/reload-runtime.ts +2 -2
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/examples/sdk/12-full-control.ts +1 -1
- package/examples/sdk/README.md +1 -1
- package/npm-shrinkwrap.json +12 -12
- package/package.json +4 -4
|
@@ -13,6 +13,7 @@ import { spawn, spawnSync } from "child_process";
|
|
|
13
13
|
import { APP_NAME, APP_TITLE, CONFIG_DIR_NAME, getAgentDir, getAuthPath, getDebugLogPath, getDocsPath, getShareViewerUrl, VERSION, } from "../../config.js";
|
|
14
14
|
import { parseSkillBlock } from "../../core/agent-session.js";
|
|
15
15
|
import { SessionImportFileNotFoundError } from "../../core/agent-session-runtime.js";
|
|
16
|
+
import { CACHE_TTL_MS, collectCacheMisses, computeCacheWaste, detectCacheMiss, } from "../../core/cache-stats.js";
|
|
16
17
|
import { FooterDataProvider } from "../../core/footer-data-provider.js";
|
|
17
18
|
import { configureHttpDispatcher, formatHttpIdleTimeoutMs } from "../../core/http-dispatcher.js";
|
|
18
19
|
import { KeybindingsManager } from "../../core/keybindings.js";
|
|
@@ -21,7 +22,7 @@ import { defaultModelPerProvider, findExactModelReferenceMatch, resolveModelScop
|
|
|
21
22
|
import { DefaultPackageManager } from "../../core/package-manager.js";
|
|
22
23
|
import { BUILT_IN_PROVIDER_DISPLAY_NAMES } from "../../core/provider-display-names.js";
|
|
23
24
|
import { formatMissingSessionCwdPrompt, MissingSessionCwdError } from "../../core/session-cwd.js";
|
|
24
|
-
import { SessionManager } from "../../core/session-manager.js";
|
|
25
|
+
import { SessionManager, sessionEntryToContextMessages } from "../../core/session-manager.js";
|
|
25
26
|
import { BUILTIN_SLASH_COMMANDS } from "../../core/slash-commands.js";
|
|
26
27
|
import { isInstallTelemetryEnabled } from "../../core/telemetry.js";
|
|
27
28
|
import { hasTrustRequiringProjectResources, ProjectTrustStore } from "../../core/trust-manager.js";
|
|
@@ -41,6 +42,7 @@ import { BorderedLoader } from "./components/bordered-loader.js";
|
|
|
41
42
|
import { BranchSummaryMessageComponent } from "./components/branch-summary-message.js";
|
|
42
43
|
import { CompactionSummaryMessageComponent } from "./components/compaction-summary-message.js";
|
|
43
44
|
import { CustomEditor } from "./components/custom-editor.js";
|
|
45
|
+
import { CustomEntryComponent } from "./components/custom-entry.js";
|
|
44
46
|
import { CustomMessageComponent } from "./components/custom-message.js";
|
|
45
47
|
import { DaxnutsComponent } from "./components/daxnuts.js";
|
|
46
48
|
import { DynamicBorder } from "./components/dynamic-border.js";
|
|
@@ -48,11 +50,11 @@ import { EarendilAnnouncementComponent } from "./components/earendil-announcemen
|
|
|
48
50
|
import { ExtensionEditorComponent } from "./components/extension-editor.js";
|
|
49
51
|
import { ExtensionInputComponent } from "./components/extension-input.js";
|
|
50
52
|
import { ExtensionSelectorComponent } from "./components/extension-selector.js";
|
|
51
|
-
import { FooterComponent } from "./components/footer.js";
|
|
53
|
+
import { FooterComponent, formatTokens } from "./components/footer.js";
|
|
52
54
|
import { formatKeyText, keyDisplayText, keyHint, keyText, rawKeyHint } from "./components/keybinding-hints.js";
|
|
53
55
|
import { LoginDialogComponent } from "./components/login-dialog.js";
|
|
54
56
|
import { ModelSelectorComponent } from "./components/model-selector.js";
|
|
55
|
-
import { OAuthSelectorComponent } from "./components/oauth-selector.js";
|
|
57
|
+
import { formatAuthSelectorProviderType, OAuthSelectorComponent, } from "./components/oauth-selector.js";
|
|
56
58
|
import { ScopedModelsSelectorComponent } from "./components/scoped-models-selector.js";
|
|
57
59
|
import { SessionSelectorComponent } from "./components/session-selector.js";
|
|
58
60
|
import { SettingsSelectorComponent } from "./components/settings-selector.js";
|
|
@@ -81,6 +83,9 @@ class ExpandableText extends Text {
|
|
|
81
83
|
this.setText(expanded ? this.getExpandedText() : this.getCollapsedText());
|
|
82
84
|
}
|
|
83
85
|
}
|
|
86
|
+
function isCustomSessionEntry(item) {
|
|
87
|
+
return "type" in item && item.type === "custom";
|
|
88
|
+
}
|
|
84
89
|
const DEAD_TERMINAL_ERROR_CODES = new Set(["EIO", "EPIPE", "ENOTCONN"]);
|
|
85
90
|
function isDeadTerminalError(error) {
|
|
86
91
|
if (!error || typeof error !== "object" || !("code" in error)) {
|
|
@@ -131,6 +136,42 @@ export function isApiKeyLoginProvider(providerId, oauthProviderIds, builtInProvi
|
|
|
131
136
|
}
|
|
132
137
|
return !oauthProviderIds.has(providerId);
|
|
133
138
|
}
|
|
139
|
+
const AUTH_TYPE_ORDER = { oauth: 0, api_key: 1 };
|
|
140
|
+
function createFuzzyAutocompleteItems(items, prefix, getSearchText, toAutocompleteItem) {
|
|
141
|
+
const filtered = fuzzyFilter(items, prefix, getSearchText);
|
|
142
|
+
if (filtered.length === 0)
|
|
143
|
+
return null;
|
|
144
|
+
return filtered.map(toAutocompleteItem);
|
|
145
|
+
}
|
|
146
|
+
function getLoginProviderCompletionOptions(providerOptions) {
|
|
147
|
+
const byId = new Map();
|
|
148
|
+
for (const provider of providerOptions) {
|
|
149
|
+
const existing = byId.get(provider.id);
|
|
150
|
+
if (existing) {
|
|
151
|
+
if (!existing.authTypes.includes(provider.authType)) {
|
|
152
|
+
existing.authTypes.push(provider.authType);
|
|
153
|
+
existing.authTypes.sort((a, b) => AUTH_TYPE_ORDER[a] - AUTH_TYPE_ORDER[b]);
|
|
154
|
+
}
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
byId.set(provider.id, {
|
|
158
|
+
id: provider.id,
|
|
159
|
+
name: provider.name,
|
|
160
|
+
authTypes: [provider.authType],
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
return Array.from(byId.values()).sort((a, b) => a.name.localeCompare(b.name));
|
|
164
|
+
}
|
|
165
|
+
function getLoginProviderSearchText(provider) {
|
|
166
|
+
const authTypes = provider.authTypes
|
|
167
|
+
.map((authType) => `${authType} ${formatAuthSelectorProviderType(authType)}`)
|
|
168
|
+
.join(" ");
|
|
169
|
+
return `${provider.id} ${provider.name} ${authTypes}`;
|
|
170
|
+
}
|
|
171
|
+
function formatLoginProviderCompletionDescription(provider) {
|
|
172
|
+
const authTypes = provider.authTypes.map(formatAuthSelectorProviderType).join("/");
|
|
173
|
+
return provider.name === provider.id ? authTypes : `${provider.name} · ${authTypes}`;
|
|
174
|
+
}
|
|
134
175
|
export class InteractiveMode {
|
|
135
176
|
runtimeHost;
|
|
136
177
|
ui;
|
|
@@ -317,6 +358,7 @@ export class InteractiveMode {
|
|
|
317
358
|
const slashCommands = BUILTIN_SLASH_COMMANDS.map((command) => ({
|
|
318
359
|
name: command.name,
|
|
319
360
|
description: command.description,
|
|
361
|
+
...(command.argumentHint && { argumentHint: command.argumentHint }),
|
|
320
362
|
}));
|
|
321
363
|
const modelCommand = slashCommands.find((command) => command.name === "model");
|
|
322
364
|
if (modelCommand) {
|
|
@@ -334,17 +376,24 @@ export class InteractiveMode {
|
|
|
334
376
|
name: m.name,
|
|
335
377
|
label: `${m.provider}/${m.id}`,
|
|
336
378
|
}));
|
|
337
|
-
|
|
338
|
-
const filtered = fuzzyFilter(items, prefix, getModelSearchText);
|
|
339
|
-
if (filtered.length === 0)
|
|
340
|
-
return null;
|
|
341
|
-
return filtered.map((item) => ({
|
|
379
|
+
return createFuzzyAutocompleteItems(items, prefix, getModelSearchText, (item) => ({
|
|
342
380
|
value: item.label,
|
|
343
381
|
label: item.id,
|
|
344
382
|
description: item.provider,
|
|
345
383
|
}));
|
|
346
384
|
};
|
|
347
385
|
}
|
|
386
|
+
const loginCommand = slashCommands.find((command) => command.name === "login");
|
|
387
|
+
if (loginCommand) {
|
|
388
|
+
loginCommand.getArgumentCompletions = (prefix) => {
|
|
389
|
+
const providers = getLoginProviderCompletionOptions(this.getLoginProviderOptions());
|
|
390
|
+
return createFuzzyAutocompleteItems(providers, prefix, getLoginProviderSearchText, (provider) => ({
|
|
391
|
+
value: provider.id,
|
|
392
|
+
label: provider.id,
|
|
393
|
+
description: formatLoginProviderCompletionDescription(provider),
|
|
394
|
+
}));
|
|
395
|
+
};
|
|
396
|
+
}
|
|
348
397
|
// Convert prompt templates to SlashCommand format for autocomplete
|
|
349
398
|
const templateCommands = this.session.promptTemplates.map((cmd) => ({
|
|
350
399
|
name: cmd.name,
|
|
@@ -1155,7 +1204,7 @@ export class InteractiveMode {
|
|
|
1155
1204
|
this.restoreQueuedMessagesToEditor({ abort: true });
|
|
1156
1205
|
},
|
|
1157
1206
|
commandContextActions: {
|
|
1158
|
-
waitForIdle: () => this.session.
|
|
1207
|
+
waitForIdle: () => this.session.waitForIdle(),
|
|
1159
1208
|
newSession: async (options) => {
|
|
1160
1209
|
this.clearStatusIndicator();
|
|
1161
1210
|
try {
|
|
@@ -1206,7 +1255,7 @@ export class InteractiveMode {
|
|
|
1206
1255
|
},
|
|
1207
1256
|
shutdownHandler: () => {
|
|
1208
1257
|
this.shutdownRequested = true;
|
|
1209
|
-
if (
|
|
1258
|
+
if (this.session.isIdle) {
|
|
1210
1259
|
void this.shutdown();
|
|
1211
1260
|
}
|
|
1212
1261
|
},
|
|
@@ -1299,7 +1348,7 @@ export class InteractiveMode {
|
|
|
1299
1348
|
sessionManager: this.sessionManager,
|
|
1300
1349
|
modelRegistry: this.session.modelRegistry,
|
|
1301
1350
|
model: this.session.model,
|
|
1302
|
-
isIdle: () =>
|
|
1351
|
+
isIdle: () => this.session.isIdle,
|
|
1303
1352
|
isProjectTrusted: () => this.settingsManager.isProjectTrusted(),
|
|
1304
1353
|
signal: this.session.agent.signal,
|
|
1305
1354
|
abort: () => {
|
|
@@ -2105,9 +2154,10 @@ export class InteractiveMode {
|
|
|
2105
2154
|
this.editor.setText("");
|
|
2106
2155
|
return;
|
|
2107
2156
|
}
|
|
2108
|
-
if (text === "/login") {
|
|
2109
|
-
|
|
2157
|
+
if (text === "/login" || text.startsWith("/login ")) {
|
|
2158
|
+
const providerRef = text.startsWith("/login ") ? text.slice(7).trim() : undefined;
|
|
2110
2159
|
this.editor.setText("");
|
|
2160
|
+
await this.handleLoginCommand(providerRef);
|
|
2111
2161
|
return;
|
|
2112
2162
|
}
|
|
2113
2163
|
if (text === "/logout") {
|
|
@@ -2241,6 +2291,12 @@ export class InteractiveMode {
|
|
|
2241
2291
|
this.updatePendingMessagesDisplay();
|
|
2242
2292
|
this.ui.requestRender();
|
|
2243
2293
|
break;
|
|
2294
|
+
case "entry_appended":
|
|
2295
|
+
if (event.entry.type === "custom") {
|
|
2296
|
+
this.addCustomEntryToChat(event.entry);
|
|
2297
|
+
this.ui.requestRender();
|
|
2298
|
+
}
|
|
2299
|
+
break;
|
|
2244
2300
|
case "session_info_changed":
|
|
2245
2301
|
this.updateTerminalTitle();
|
|
2246
2302
|
this.footer.invalidate();
|
|
@@ -2326,6 +2382,7 @@ export class InteractiveMode {
|
|
|
2326
2382
|
for (const [, component] of this.pendingTools.entries()) {
|
|
2327
2383
|
component.setArgsComplete();
|
|
2328
2384
|
}
|
|
2385
|
+
this.maybeShowCacheMissNotice(this.streamingMessage);
|
|
2329
2386
|
}
|
|
2330
2387
|
this.streamingComponent = undefined;
|
|
2331
2388
|
this.streamingMessage = undefined;
|
|
@@ -2376,9 +2433,11 @@ export class InteractiveMode {
|
|
|
2376
2433
|
this.streamingMessage = undefined;
|
|
2377
2434
|
}
|
|
2378
2435
|
this.pendingTools.clear();
|
|
2379
|
-
await this.checkShutdownRequested();
|
|
2380
2436
|
this.ui.requestRender();
|
|
2381
2437
|
break;
|
|
2438
|
+
case "agent_settled":
|
|
2439
|
+
await this.checkShutdownRequested();
|
|
2440
|
+
break;
|
|
2382
2441
|
case "compaction_start": {
|
|
2383
2442
|
if (this.settingsManager.getShowTerminalProgress()) {
|
|
2384
2443
|
this.ui.terminal.setProgress(true);
|
|
@@ -2486,6 +2545,25 @@ export class InteractiveMode {
|
|
|
2486
2545
|
this.lastStatusText = text;
|
|
2487
2546
|
this.ui.requestRender();
|
|
2488
2547
|
}
|
|
2548
|
+
addCustomEntryToChat(entry) {
|
|
2549
|
+
const renderer = this.session.extensionRunner.getEntryRenderer(entry.customType);
|
|
2550
|
+
if (!renderer) {
|
|
2551
|
+
return;
|
|
2552
|
+
}
|
|
2553
|
+
const component = new CustomEntryComponent(entry, renderer);
|
|
2554
|
+
component.setExpanded(this.toolOutputExpanded);
|
|
2555
|
+
if (!component.hasContent()) {
|
|
2556
|
+
return;
|
|
2557
|
+
}
|
|
2558
|
+
if (this.streamingComponent) {
|
|
2559
|
+
const streamingIndex = this.chatContainer.children.indexOf(this.streamingComponent);
|
|
2560
|
+
if (streamingIndex >= 0) {
|
|
2561
|
+
this.chatContainer.children.splice(streamingIndex, 0, component);
|
|
2562
|
+
return;
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
2565
|
+
this.chatContainer.addChild(component);
|
|
2566
|
+
}
|
|
2489
2567
|
addMessageToChat(message, options) {
|
|
2490
2568
|
switch (message.role) {
|
|
2491
2569
|
case "bashExecution": {
|
|
@@ -2563,20 +2641,24 @@ export class InteractiveMode {
|
|
|
2563
2641
|
}
|
|
2564
2642
|
}
|
|
2565
2643
|
}
|
|
2566
|
-
|
|
2567
|
-
* Render session context to chat. Used for initial load and rebuild after compaction.
|
|
2568
|
-
* @param sessionContext Session context to render
|
|
2569
|
-
* @param options.updateFooter Update footer state
|
|
2570
|
-
* @param options.populateHistory Add user messages to editor history
|
|
2571
|
-
*/
|
|
2572
|
-
renderSessionContext(sessionContext, options = {}) {
|
|
2644
|
+
renderSessionItems(items, options = {}) {
|
|
2573
2645
|
this.pendingTools.clear();
|
|
2574
2646
|
const renderedPendingTools = new Map();
|
|
2647
|
+
// Cache-miss notices are not persisted; re-derive them from the full entry
|
|
2648
|
+
// list and re-inject them after the assistant messages that paid for them.
|
|
2649
|
+
const cacheMisses = this.settingsManager.getShowCacheMissNotices()
|
|
2650
|
+
? collectCacheMisses(this.sessionManager.getEntries(), this.session.modelRegistry)
|
|
2651
|
+
: new Map();
|
|
2575
2652
|
if (options.updateFooter) {
|
|
2576
2653
|
this.footer.invalidate();
|
|
2577
2654
|
this.updateEditorBorderColor();
|
|
2578
2655
|
}
|
|
2579
|
-
for (const
|
|
2656
|
+
for (const item of items) {
|
|
2657
|
+
if (isCustomSessionEntry(item)) {
|
|
2658
|
+
this.addCustomEntryToChat(item);
|
|
2659
|
+
continue;
|
|
2660
|
+
}
|
|
2661
|
+
const message = item;
|
|
2580
2662
|
// Assistant messages need special handling for tool calls
|
|
2581
2663
|
if (message.role === "assistant") {
|
|
2582
2664
|
this.addMessageToChat(message);
|
|
@@ -2608,6 +2690,11 @@ export class InteractiveMode {
|
|
|
2608
2690
|
}
|
|
2609
2691
|
}
|
|
2610
2692
|
}
|
|
2693
|
+
if (message.stopReason !== "aborted" && message.stopReason !== "error") {
|
|
2694
|
+
const miss = cacheMisses.get(message);
|
|
2695
|
+
if (miss)
|
|
2696
|
+
this.addCacheMissNotice(miss);
|
|
2697
|
+
}
|
|
2611
2698
|
}
|
|
2612
2699
|
else if (message.role === "toolResult") {
|
|
2613
2700
|
// Match tool results to pending tool components
|
|
@@ -2627,10 +2714,53 @@ export class InteractiveMode {
|
|
|
2627
2714
|
}
|
|
2628
2715
|
this.ui.requestRender();
|
|
2629
2716
|
}
|
|
2717
|
+
/**
|
|
2718
|
+
* Render session entries to chat. Used for initial load and rebuild after compaction.
|
|
2719
|
+
* @param entries Compaction-aware session entries to render
|
|
2720
|
+
* @param options.updateFooter Update footer state
|
|
2721
|
+
* @param options.populateHistory Add user messages to editor history
|
|
2722
|
+
*/
|
|
2723
|
+
renderSessionEntries(entries, options = {}) {
|
|
2724
|
+
const items = entries.flatMap((entry) => {
|
|
2725
|
+
if (entry.type === "custom") {
|
|
2726
|
+
return [entry];
|
|
2727
|
+
}
|
|
2728
|
+
return sessionEntryToContextMessages(entry);
|
|
2729
|
+
});
|
|
2730
|
+
this.renderSessionItems(items, options);
|
|
2731
|
+
}
|
|
2732
|
+
/**
|
|
2733
|
+
* Show a transcript notice when a completed assistant message paid for a
|
|
2734
|
+
* significant cache miss. Only states observable facts: the miss itself,
|
|
2735
|
+
* a model switch, or an idle gap past the cache TTL.
|
|
2736
|
+
*/
|
|
2737
|
+
maybeShowCacheMissNotice(message) {
|
|
2738
|
+
if (!this.settingsManager.getShowCacheMissNotices())
|
|
2739
|
+
return;
|
|
2740
|
+
// Entries don't contain `message` yet: message_end fires before persistence.
|
|
2741
|
+
const miss = detectCacheMiss(this.sessionManager.getEntries(), message, this.session.modelRegistry);
|
|
2742
|
+
if (miss)
|
|
2743
|
+
this.addCacheMissNotice(miss);
|
|
2744
|
+
}
|
|
2745
|
+
addCacheMissNotice(miss) {
|
|
2746
|
+
if (miss.missedTokens < 20_000 && miss.missedCost < 0.1)
|
|
2747
|
+
return;
|
|
2748
|
+
const cost = miss.missedCost >= 0.01 ? ` (~$${miss.missedCost.toFixed(2)})` : "";
|
|
2749
|
+
const reBilled = `${formatTokens(miss.missedTokens)} tokens re-billed${cost}`;
|
|
2750
|
+
let label = "Cache miss";
|
|
2751
|
+
if (miss.modelChanged) {
|
|
2752
|
+
label = "Cache miss after model switch";
|
|
2753
|
+
}
|
|
2754
|
+
else if (miss.idleMs >= CACHE_TTL_MS) {
|
|
2755
|
+
label = `Cache miss after ${Math.round(miss.idleMs / 60_000)}m idle`;
|
|
2756
|
+
}
|
|
2757
|
+
const text = theme.fg("warning", `${label}: ${reBilled}`);
|
|
2758
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
2759
|
+
this.chatContainer.addChild(new Text(text, 1, 0));
|
|
2760
|
+
}
|
|
2630
2761
|
renderInitialMessages() {
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
this.renderSessionContext(context, {
|
|
2762
|
+
const entries = this.sessionManager.buildContextEntries();
|
|
2763
|
+
this.renderSessionEntries(entries, {
|
|
2634
2764
|
updateFooter: true,
|
|
2635
2765
|
populateHistory: true,
|
|
2636
2766
|
});
|
|
@@ -2666,8 +2796,7 @@ export class InteractiveMode {
|
|
|
2666
2796
|
}
|
|
2667
2797
|
rebuildChatFromMessages() {
|
|
2668
2798
|
this.chatContainer.clear();
|
|
2669
|
-
|
|
2670
|
-
this.renderSessionContext(context);
|
|
2799
|
+
this.renderSessionEntries(this.sessionManager.buildContextEntries());
|
|
2671
2800
|
}
|
|
2672
2801
|
// =========================================================================
|
|
2673
2802
|
// Key handlers
|
|
@@ -3266,6 +3395,7 @@ export class InteractiveMode {
|
|
|
3266
3395
|
doubleEscapeAction: this.settingsManager.getDoubleEscapeAction(),
|
|
3267
3396
|
treeFilterMode: this.settingsManager.getTreeFilterMode(),
|
|
3268
3397
|
showHardwareCursor: this.settingsManager.getShowHardwareCursor(),
|
|
3398
|
+
showCacheMissNotices: this.settingsManager.getShowCacheMissNotices(),
|
|
3269
3399
|
defaultProjectTrust: this.settingsManager.getDefaultProjectTrust(),
|
|
3270
3400
|
editorPaddingX: this.settingsManager.getEditorPaddingX(),
|
|
3271
3401
|
outputPad: this.settingsManager.getOutputPad(),
|
|
@@ -3341,6 +3471,10 @@ export class InteractiveMode {
|
|
|
3341
3471
|
this.chatContainer.clear();
|
|
3342
3472
|
this.rebuildChatFromMessages();
|
|
3343
3473
|
},
|
|
3474
|
+
onShowCacheMissNoticesChange: (shown) => {
|
|
3475
|
+
this.settingsManager.setShowCacheMissNotices(shown);
|
|
3476
|
+
this.rebuildChatFromMessages();
|
|
3477
|
+
},
|
|
3344
3478
|
onCollapseChangelogChange: (collapsed) => {
|
|
3345
3479
|
this.settingsManager.setCollapseChangelog(collapsed);
|
|
3346
3480
|
},
|
|
@@ -3630,19 +3764,17 @@ export class InteractiveMode {
|
|
|
3630
3764
|
const initialSelectedId = userMessages[userMessages.length - 1]?.entryId;
|
|
3631
3765
|
this.showSelector((done) => {
|
|
3632
3766
|
const selector = new UserMessageSelectorComponent(userMessages.map((m) => ({ id: m.entryId, text: m.text })), async (entryId) => {
|
|
3767
|
+
done();
|
|
3633
3768
|
try {
|
|
3634
3769
|
const result = await this.runtimeHost.fork(entryId);
|
|
3635
3770
|
if (result.cancelled) {
|
|
3636
|
-
done();
|
|
3637
3771
|
this.ui.requestRender();
|
|
3638
3772
|
return;
|
|
3639
3773
|
}
|
|
3640
3774
|
this.editor.setText(result.selectedText ?? "");
|
|
3641
|
-
done();
|
|
3642
3775
|
this.showStatus("Forked to new session");
|
|
3643
3776
|
}
|
|
3644
3777
|
catch (error) {
|
|
3645
|
-
done();
|
|
3646
3778
|
this.showError(error instanceof Error ? error.message : String(error));
|
|
3647
3779
|
}
|
|
3648
3780
|
}, () => {
|
|
@@ -3871,13 +4003,82 @@ export class InteractiveMode {
|
|
|
3871
4003
|
}
|
|
3872
4004
|
return options.sort((a, b) => a.name.localeCompare(b.name));
|
|
3873
4005
|
}
|
|
3874
|
-
|
|
4006
|
+
findLoginProviderOptions(providerRef) {
|
|
4007
|
+
const normalizedProviderRef = providerRef.trim().toLowerCase();
|
|
4008
|
+
if (!normalizedProviderRef) {
|
|
4009
|
+
return [];
|
|
4010
|
+
}
|
|
4011
|
+
return this.getLoginProviderOptions().filter((provider) => provider.id.toLowerCase() === normalizedProviderRef ||
|
|
4012
|
+
provider.name.toLowerCase() === normalizedProviderRef);
|
|
4013
|
+
}
|
|
4014
|
+
async handleLoginCommand(providerRef) {
|
|
4015
|
+
if (!providerRef) {
|
|
4016
|
+
this.showLoginAuthTypeSelector();
|
|
4017
|
+
return;
|
|
4018
|
+
}
|
|
4019
|
+
const providerOptions = this.findLoginProviderOptions(providerRef);
|
|
4020
|
+
if (providerOptions.length === 1) {
|
|
4021
|
+
await this.startProviderLogin(providerOptions[0]);
|
|
4022
|
+
return;
|
|
4023
|
+
}
|
|
4024
|
+
if (providerOptions.length > 1) {
|
|
4025
|
+
const providerIds = new Set(providerOptions.map((provider) => provider.id));
|
|
4026
|
+
if (providerIds.size === 1) {
|
|
4027
|
+
this.showLoginAuthTypeSelector(providerOptions);
|
|
4028
|
+
return;
|
|
4029
|
+
}
|
|
4030
|
+
}
|
|
4031
|
+
this.showLoginProviderSelector(undefined, providerRef);
|
|
4032
|
+
}
|
|
4033
|
+
async startProviderLogin(providerOption) {
|
|
4034
|
+
if (providerOption.authType === "oauth") {
|
|
4035
|
+
await this.showLoginDialog(providerOption.id, providerOption.name);
|
|
4036
|
+
}
|
|
4037
|
+
else if (providerOption.id === BEDROCK_PROVIDER_ID) {
|
|
4038
|
+
this.showBedrockSetupDialog(providerOption.id, providerOption.name);
|
|
4039
|
+
}
|
|
4040
|
+
else {
|
|
4041
|
+
await this.showApiKeyLoginDialog(providerOption.id, providerOption.name);
|
|
4042
|
+
}
|
|
4043
|
+
}
|
|
4044
|
+
showLoginAuthTypeSelector(providerOptions) {
|
|
3875
4045
|
const subscriptionLabel = "Use a subscription";
|
|
3876
4046
|
const apiKeyLabel = "Use an API key";
|
|
4047
|
+
const availableAuthTypes = providerOptions
|
|
4048
|
+
? new Set(providerOptions.map((provider) => provider.authType))
|
|
4049
|
+
: new Set(["oauth", "api_key"]);
|
|
4050
|
+
const options = [];
|
|
4051
|
+
if (availableAuthTypes.has("oauth")) {
|
|
4052
|
+
options.push(subscriptionLabel);
|
|
4053
|
+
}
|
|
4054
|
+
if (availableAuthTypes.has("api_key")) {
|
|
4055
|
+
options.push(apiKeyLabel);
|
|
4056
|
+
}
|
|
4057
|
+
if (options.length === 0) {
|
|
4058
|
+
this.showStatus("No login methods available.");
|
|
4059
|
+
return;
|
|
4060
|
+
}
|
|
4061
|
+
if (providerOptions && options.length === 1) {
|
|
4062
|
+
const providerOption = providerOptions[0];
|
|
4063
|
+
if (providerOption) {
|
|
4064
|
+
void this.startProviderLogin(providerOption);
|
|
4065
|
+
}
|
|
4066
|
+
return;
|
|
4067
|
+
}
|
|
4068
|
+
const title = providerOptions?.[0]
|
|
4069
|
+
? `Select authentication method for ${providerOptions[0].name}:`
|
|
4070
|
+
: "Select authentication method:";
|
|
3877
4071
|
this.showSelector((done) => {
|
|
3878
|
-
const selector = new ExtensionSelectorComponent(
|
|
4072
|
+
const selector = new ExtensionSelectorComponent(title, options, (option) => {
|
|
3879
4073
|
done();
|
|
3880
4074
|
const authType = option === subscriptionLabel ? "oauth" : "api_key";
|
|
4075
|
+
if (providerOptions) {
|
|
4076
|
+
const providerOption = providerOptions.find((provider) => provider.authType === authType);
|
|
4077
|
+
if (providerOption) {
|
|
4078
|
+
void this.startProviderLogin(providerOption);
|
|
4079
|
+
}
|
|
4080
|
+
return;
|
|
4081
|
+
}
|
|
3881
4082
|
this.showLoginProviderSelector(authType);
|
|
3882
4083
|
}, () => {
|
|
3883
4084
|
done();
|
|
@@ -3886,32 +4087,34 @@ export class InteractiveMode {
|
|
|
3886
4087
|
return { component: selector, focus: selector };
|
|
3887
4088
|
});
|
|
3888
4089
|
}
|
|
3889
|
-
showLoginProviderSelector(authType) {
|
|
4090
|
+
showLoginProviderSelector(authType, initialSearchInput) {
|
|
3890
4091
|
const providerOptions = this.getLoginProviderOptions(authType);
|
|
3891
4092
|
if (providerOptions.length === 0) {
|
|
3892
|
-
|
|
4093
|
+
const message = authType === "oauth"
|
|
4094
|
+
? "No subscription providers available."
|
|
4095
|
+
: authType === "api_key"
|
|
4096
|
+
? "No API key providers available."
|
|
4097
|
+
: "No login providers available.";
|
|
4098
|
+
this.showStatus(message);
|
|
3893
4099
|
return;
|
|
3894
4100
|
}
|
|
3895
4101
|
this.showSelector((done) => {
|
|
3896
|
-
const selector = new OAuthSelectorComponent("login", this.session.modelRegistry.authStorage, providerOptions, async (providerId) => {
|
|
4102
|
+
const selector = new OAuthSelectorComponent("login", this.session.modelRegistry.authStorage, providerOptions, async (providerId, selectedAuthType) => {
|
|
3897
4103
|
done();
|
|
3898
|
-
const providerOption = providerOptions.find((provider) => provider.id === providerId);
|
|
4104
|
+
const providerOption = providerOptions.find((provider) => provider.id === providerId && provider.authType === selectedAuthType);
|
|
3899
4105
|
if (!providerOption) {
|
|
3900
4106
|
return;
|
|
3901
4107
|
}
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
this.
|
|
4108
|
+
await this.startProviderLogin(providerOption);
|
|
4109
|
+
}, () => {
|
|
4110
|
+
done();
|
|
4111
|
+
if (authType) {
|
|
4112
|
+
this.showLoginAuthTypeSelector();
|
|
3907
4113
|
}
|
|
3908
4114
|
else {
|
|
3909
|
-
|
|
4115
|
+
this.ui.requestRender();
|
|
3910
4116
|
}
|
|
3911
|
-
}, () =>
|
|
3912
|
-
done();
|
|
3913
|
-
this.showLoginAuthTypeSelector();
|
|
3914
|
-
}, (providerId) => this.session.modelRegistry.getProviderAuthStatus(providerId));
|
|
4117
|
+
}, (providerId) => this.session.modelRegistry.getProviderAuthStatus(providerId), initialSearchInput);
|
|
3915
4118
|
return { component: selector, focus: selector };
|
|
3916
4119
|
});
|
|
3917
4120
|
}
|
|
@@ -4170,7 +4373,7 @@ export class InteractiveMode {
|
|
|
4170
4373
|
const borderColor = (s) => theme.fg("border", s);
|
|
4171
4374
|
reloadBox.addChild(new DynamicBorder(borderColor));
|
|
4172
4375
|
reloadBox.addChild(new Spacer(1));
|
|
4173
|
-
reloadBox.addChild(new Text(theme.fg("muted", "Reloading keybindings, extensions, skills, prompts, themes..."), 1, 0));
|
|
4376
|
+
reloadBox.addChild(new Text(theme.fg("muted", "Reloading keybindings, extensions, skills, prompts, themes, and context files..."), 1, 0));
|
|
4174
4377
|
reloadBox.addChild(new Spacer(1));
|
|
4175
4378
|
reloadBox.addChild(new DynamicBorder(borderColor));
|
|
4176
4379
|
const previousEditor = this.editor;
|
|
@@ -4234,8 +4437,8 @@ export class InteractiveMode {
|
|
|
4234
4437
|
this.showError(`models.json error: ${modelsJsonError}`);
|
|
4235
4438
|
}
|
|
4236
4439
|
this.showStatus(savedImplicitProjectTrust
|
|
4237
|
-
? "Reloaded keybindings, extensions, skills, prompts, themes; saved project trust"
|
|
4238
|
-
: "Reloaded keybindings, extensions, skills, prompts, themes");
|
|
4440
|
+
? "Reloaded keybindings, extensions, skills, prompts, themes, and context files; saved project trust"
|
|
4441
|
+
: "Reloaded keybindings, extensions, skills, prompts, themes, and context files");
|
|
4239
4442
|
dismissReloadBox(this.editor);
|
|
4240
4443
|
reloadBoxDismissed = true;
|
|
4241
4444
|
}
|
|
@@ -4456,6 +4659,26 @@ export class InteractiveMode {
|
|
|
4456
4659
|
handleSessionCommand() {
|
|
4457
4660
|
const stats = this.session.getSessionStats();
|
|
4458
4661
|
const sessionName = this.sessionManager.getSessionName();
|
|
4662
|
+
const entries = this.sessionManager.getEntries();
|
|
4663
|
+
const cacheWaste = computeCacheWaste(entries, this.session.modelRegistry);
|
|
4664
|
+
// Cost/token totals per provider/model actually used (e.g. OpenRouter `auto`
|
|
4665
|
+
// resolves to a concrete responseModel), sorted by cost descending.
|
|
4666
|
+
const perModelMap = new Map();
|
|
4667
|
+
for (const entry of entries) {
|
|
4668
|
+
if (entry.type !== "message" || entry.message.role !== "assistant")
|
|
4669
|
+
continue;
|
|
4670
|
+
const message = entry.message;
|
|
4671
|
+
const usage = message.usage;
|
|
4672
|
+
const key = `${message.provider}/${message.responseModel ?? message.model}`;
|
|
4673
|
+
let bucket = perModelMap.get(key);
|
|
4674
|
+
if (!bucket) {
|
|
4675
|
+
bucket = { key, cost: 0, tokens: 0 };
|
|
4676
|
+
perModelMap.set(key, bucket);
|
|
4677
|
+
}
|
|
4678
|
+
bucket.cost += usage.cost.total;
|
|
4679
|
+
bucket.tokens += usage.input + usage.output + usage.cacheRead + usage.cacheWrite;
|
|
4680
|
+
}
|
|
4681
|
+
const perModel = Array.from(perModelMap.values()).sort((a, b) => b.cost - a.cost);
|
|
4459
4682
|
let info = `${theme.bold("Session Info")}\n\n`;
|
|
4460
4683
|
if (sessionName) {
|
|
4461
4684
|
info += `${theme.fg("dim", "Name:")} ${sessionName}\n`;
|
|
@@ -4463,24 +4686,42 @@ export class InteractiveMode {
|
|
|
4463
4686
|
info += `${theme.fg("dim", "File:")} ${stats.sessionFile ?? "In-memory"}\n`;
|
|
4464
4687
|
info += `${theme.fg("dim", "ID:")} ${stats.sessionId}\n\n`;
|
|
4465
4688
|
info += `${theme.bold("Messages")}\n`;
|
|
4689
|
+
info += `${theme.fg("dim", "Total:")} ${stats.totalMessages}\n`;
|
|
4466
4690
|
info += `${theme.fg("dim", "User:")} ${stats.userMessages}\n`;
|
|
4467
4691
|
info += `${theme.fg("dim", "Assistant:")} ${stats.assistantMessages}\n`;
|
|
4468
|
-
info += `${theme.fg("dim", "
|
|
4469
|
-
info += `${theme.fg("dim", "Tool Results:")} ${stats.toolResults}\n`;
|
|
4470
|
-
info += `${theme.fg("dim", "Total:")} ${stats.totalMessages}\n\n`;
|
|
4692
|
+
info += `${theme.fg("dim", "Tools:")} ${stats.toolCalls} calls, ${stats.toolResults} results\n\n`;
|
|
4471
4693
|
info += `${theme.bold("Tokens")}\n`;
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
}
|
|
4477
|
-
|
|
4478
|
-
|
|
4694
|
+
// "Input" is the full prompt volume. With cache activity, split it into
|
|
4695
|
+
// cached (served from cache) vs uncached (everything else) - the only
|
|
4696
|
+
// provider-independent split. Cache writes, where reported, are a detail
|
|
4697
|
+
// of the uncached portion.
|
|
4698
|
+
const { input, cacheRead, cacheWrite } = stats.tokens;
|
|
4699
|
+
const promptTokens = input + cacheRead + cacheWrite;
|
|
4700
|
+
info += `${theme.fg("dim", "Input:")} ${promptTokens.toLocaleString()}\n`;
|
|
4701
|
+
if (promptTokens > 0 && (cacheRead > 0 || cacheWrite > 0)) {
|
|
4702
|
+
const hitRate = theme.fg("dim", `(${((cacheRead / promptTokens) * 100).toFixed(1)}%)`);
|
|
4703
|
+
info += ` ${theme.fg("dim", "Cached:")} ${cacheRead.toLocaleString()} ${hitRate}\n`;
|
|
4704
|
+
const written = cacheWrite > 0 ? ` ${theme.fg("dim", `(${cacheWrite.toLocaleString()} written to cache)`)}` : "";
|
|
4705
|
+
info += ` ${theme.fg("dim", "Uncached:")} ${(input + cacheWrite).toLocaleString()}${written}\n`;
|
|
4479
4706
|
}
|
|
4707
|
+
info += `${theme.fg("dim", "Output:")} ${stats.tokens.output.toLocaleString()}\n`;
|
|
4480
4708
|
info += `${theme.fg("dim", "Total:")} ${stats.tokens.total.toLocaleString()}\n`;
|
|
4481
|
-
if (stats.cost > 0) {
|
|
4709
|
+
if (stats.cost > 0 || cacheWaste.missedTokens > 0) {
|
|
4482
4710
|
info += `\n${theme.bold("Cost")}\n`;
|
|
4483
|
-
info += `${theme.fg("dim", "Total:")}
|
|
4711
|
+
info += `${theme.fg("dim", "Total:")} $${stats.cost.toFixed(3)}`;
|
|
4712
|
+
if (perModel.length > 1) {
|
|
4713
|
+
for (const entry of perModel) {
|
|
4714
|
+
info += `\n ${theme.fg("dim", `${entry.key}:`)} $${entry.cost.toFixed(3)} ${theme.fg("dim", `(${formatTokens(entry.tokens)} tokens)`)}`;
|
|
4715
|
+
}
|
|
4716
|
+
}
|
|
4717
|
+
if (cacheWaste.missedTokens > 0) {
|
|
4718
|
+
const missLabel = cacheWaste.missCount === 1 ? "1 miss" : `${cacheWaste.missCount} misses`;
|
|
4719
|
+
const detail = `${cacheWaste.missedTokens.toLocaleString()} tokens, ${missLabel}`;
|
|
4720
|
+
info +=
|
|
4721
|
+
cacheWaste.missedCost >= 0.0001
|
|
4722
|
+
? `\n${theme.fg("dim", "Cache Re-billed:")} $${cacheWaste.missedCost.toFixed(3)} ${theme.fg("dim", `(${detail})`)}`
|
|
4723
|
+
: `\n${theme.fg("dim", "Cache Re-billed:")} ${detail}`;
|
|
4724
|
+
}
|
|
4484
4725
|
}
|
|
4485
4726
|
this.chatContainer.addChild(new Spacer(1));
|
|
4486
4727
|
this.chatContainer.addChild(new Text(info, 1, 0));
|