@averyyy/pi-coding-agent 0.80.3-piclient.9 → 0.80.6-piclient.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 +86 -0
- package/README.md +2 -2
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +4 -4
- 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 +33 -11
- 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 +4 -6
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +34 -8
- 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/pi-client-runtime-update.d.ts +14 -0
- package/dist/core/pi-client-runtime-update.d.ts.map +1 -0
- package/dist/core/pi-client-runtime-update.js +27 -0
- package/dist/core/pi-client-runtime-update.js.map +1 -0
- package/dist/core/pi-server-client.d.ts.map +1 -1
- package/dist/core/pi-server-client.js +122 -5
- package/dist/core/pi-server-client.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 +9 -3
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +10 -1
- 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/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 +12 -1
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.js +2 -1
- package/dist/modes/interactive/components/thinking-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 +328 -61
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/dark.json +1 -0
- package/dist/modes/interactive/theme/light.json +1 -0
- package/dist/modes/interactive/theme/theme-schema.json +6 -2
- package/dist/modes/interactive/theme/theme.d.ts +3 -2
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +10 -3
- package/dist/modes/interactive/theme/theme.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/custom-provider.md +4 -3
- package/docs/extensions.md +75 -18
- package/docs/models.md +49 -8
- package/docs/packages.md +2 -2
- package/docs/rpc.md +15 -5
- package/docs/sdk.md +51 -1
- package/docs/session-format.md +18 -8
- package/docs/settings.md +3 -2
- package/docs/themes.md +5 -3
- package/docs/tui.md +1 -1
- package/docs/usage.md +2 -2
- 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/preset.ts +2 -2
- 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/package.json +4 -4
|
@@ -13,15 +13,17 @@ 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";
|
|
19
20
|
import { createCompactionSummaryMessage } from "../../core/messages.js";
|
|
20
21
|
import { defaultModelPerProvider, findExactModelReferenceMatch, resolveModelScope } from "../../core/model-resolver.js";
|
|
21
22
|
import { DefaultPackageManager } from "../../core/package-manager.js";
|
|
23
|
+
import { getPiClientUpdateMarkerPath, PI_CLIENT_RUNTIME_RELOAD_EXIT_CODE, readPiClientUpdateMarker, writePiClientRuntimeReloadState, } from "../../core/pi-client-runtime-update.js";
|
|
22
24
|
import { BUILT_IN_PROVIDER_DISPLAY_NAMES } from "../../core/provider-display-names.js";
|
|
23
25
|
import { formatMissingSessionCwdPrompt, MissingSessionCwdError } from "../../core/session-cwd.js";
|
|
24
|
-
import { SessionManager } from "../../core/session-manager.js";
|
|
26
|
+
import { SessionManager, sessionEntryToContextMessages } from "../../core/session-manager.js";
|
|
25
27
|
import { BUILTIN_SLASH_COMMANDS } from "../../core/slash-commands.js";
|
|
26
28
|
import { isInstallTelemetryEnabled } from "../../core/telemetry.js";
|
|
27
29
|
import { hasTrustRequiringProjectResources, ProjectTrustStore } from "../../core/trust-manager.js";
|
|
@@ -41,6 +43,7 @@ import { BorderedLoader } from "./components/bordered-loader.js";
|
|
|
41
43
|
import { BranchSummaryMessageComponent } from "./components/branch-summary-message.js";
|
|
42
44
|
import { CompactionSummaryMessageComponent } from "./components/compaction-summary-message.js";
|
|
43
45
|
import { CustomEditor } from "./components/custom-editor.js";
|
|
46
|
+
import { CustomEntryComponent } from "./components/custom-entry.js";
|
|
44
47
|
import { CustomMessageComponent } from "./components/custom-message.js";
|
|
45
48
|
import { DaxnutsComponent } from "./components/daxnuts.js";
|
|
46
49
|
import { DynamicBorder } from "./components/dynamic-border.js";
|
|
@@ -48,11 +51,11 @@ import { EarendilAnnouncementComponent } from "./components/earendil-announcemen
|
|
|
48
51
|
import { ExtensionEditorComponent } from "./components/extension-editor.js";
|
|
49
52
|
import { ExtensionInputComponent } from "./components/extension-input.js";
|
|
50
53
|
import { ExtensionSelectorComponent } from "./components/extension-selector.js";
|
|
51
|
-
import { FooterComponent } from "./components/footer.js";
|
|
54
|
+
import { FooterComponent, formatTokens } from "./components/footer.js";
|
|
52
55
|
import { formatKeyText, keyDisplayText, keyHint, keyText, rawKeyHint } from "./components/keybinding-hints.js";
|
|
53
56
|
import { LoginDialogComponent } from "./components/login-dialog.js";
|
|
54
57
|
import { ModelSelectorComponent } from "./components/model-selector.js";
|
|
55
|
-
import { OAuthSelectorComponent } from "./components/oauth-selector.js";
|
|
58
|
+
import { formatAuthSelectorProviderType, OAuthSelectorComponent, } from "./components/oauth-selector.js";
|
|
56
59
|
import { ScopedModelsSelectorComponent } from "./components/scoped-models-selector.js";
|
|
57
60
|
import { SessionSelectorComponent } from "./components/session-selector.js";
|
|
58
61
|
import { SettingsSelectorComponent } from "./components/settings-selector.js";
|
|
@@ -81,6 +84,9 @@ class ExpandableText extends Text {
|
|
|
81
84
|
this.setText(expanded ? this.getExpandedText() : this.getCollapsedText());
|
|
82
85
|
}
|
|
83
86
|
}
|
|
87
|
+
function isCustomSessionEntry(item) {
|
|
88
|
+
return "type" in item && item.type === "custom";
|
|
89
|
+
}
|
|
84
90
|
const DEAD_TERMINAL_ERROR_CODES = new Set(["EIO", "EPIPE", "ENOTCONN"]);
|
|
85
91
|
function isDeadTerminalError(error) {
|
|
86
92
|
if (!error || typeof error !== "object" || !("code" in error)) {
|
|
@@ -131,6 +137,42 @@ export function isApiKeyLoginProvider(providerId, oauthProviderIds, builtInProvi
|
|
|
131
137
|
}
|
|
132
138
|
return !oauthProviderIds.has(providerId);
|
|
133
139
|
}
|
|
140
|
+
const AUTH_TYPE_ORDER = { oauth: 0, api_key: 1 };
|
|
141
|
+
function createFuzzyAutocompleteItems(items, prefix, getSearchText, toAutocompleteItem) {
|
|
142
|
+
const filtered = fuzzyFilter(items, prefix, getSearchText);
|
|
143
|
+
if (filtered.length === 0)
|
|
144
|
+
return null;
|
|
145
|
+
return filtered.map(toAutocompleteItem);
|
|
146
|
+
}
|
|
147
|
+
function getLoginProviderCompletionOptions(providerOptions) {
|
|
148
|
+
const byId = new Map();
|
|
149
|
+
for (const provider of providerOptions) {
|
|
150
|
+
const existing = byId.get(provider.id);
|
|
151
|
+
if (existing) {
|
|
152
|
+
if (!existing.authTypes.includes(provider.authType)) {
|
|
153
|
+
existing.authTypes.push(provider.authType);
|
|
154
|
+
existing.authTypes.sort((a, b) => AUTH_TYPE_ORDER[a] - AUTH_TYPE_ORDER[b]);
|
|
155
|
+
}
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
byId.set(provider.id, {
|
|
159
|
+
id: provider.id,
|
|
160
|
+
name: provider.name,
|
|
161
|
+
authTypes: [provider.authType],
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
return Array.from(byId.values()).sort((a, b) => a.name.localeCompare(b.name));
|
|
165
|
+
}
|
|
166
|
+
function getLoginProviderSearchText(provider) {
|
|
167
|
+
const authTypes = provider.authTypes
|
|
168
|
+
.map((authType) => `${authType} ${formatAuthSelectorProviderType(authType)}`)
|
|
169
|
+
.join(" ");
|
|
170
|
+
return `${provider.id} ${provider.name} ${authTypes}`;
|
|
171
|
+
}
|
|
172
|
+
function formatLoginProviderCompletionDescription(provider) {
|
|
173
|
+
const authTypes = provider.authTypes.map(formatAuthSelectorProviderType).join("/");
|
|
174
|
+
return provider.name === provider.id ? authTypes : `${provider.name} · ${authTypes}`;
|
|
175
|
+
}
|
|
134
176
|
export class InteractiveMode {
|
|
135
177
|
runtimeHost;
|
|
136
178
|
ui;
|
|
@@ -317,6 +359,7 @@ export class InteractiveMode {
|
|
|
317
359
|
const slashCommands = BUILTIN_SLASH_COMMANDS.map((command) => ({
|
|
318
360
|
name: command.name,
|
|
319
361
|
description: command.description,
|
|
362
|
+
...(command.argumentHint && { argumentHint: command.argumentHint }),
|
|
320
363
|
}));
|
|
321
364
|
const modelCommand = slashCommands.find((command) => command.name === "model");
|
|
322
365
|
if (modelCommand) {
|
|
@@ -334,17 +377,24 @@ export class InteractiveMode {
|
|
|
334
377
|
name: m.name,
|
|
335
378
|
label: `${m.provider}/${m.id}`,
|
|
336
379
|
}));
|
|
337
|
-
|
|
338
|
-
const filtered = fuzzyFilter(items, prefix, getModelSearchText);
|
|
339
|
-
if (filtered.length === 0)
|
|
340
|
-
return null;
|
|
341
|
-
return filtered.map((item) => ({
|
|
380
|
+
return createFuzzyAutocompleteItems(items, prefix, getModelSearchText, (item) => ({
|
|
342
381
|
value: item.label,
|
|
343
382
|
label: item.id,
|
|
344
383
|
description: item.provider,
|
|
345
384
|
}));
|
|
346
385
|
};
|
|
347
386
|
}
|
|
387
|
+
const loginCommand = slashCommands.find((command) => command.name === "login");
|
|
388
|
+
if (loginCommand) {
|
|
389
|
+
loginCommand.getArgumentCompletions = (prefix) => {
|
|
390
|
+
const providers = getLoginProviderCompletionOptions(this.getLoginProviderOptions());
|
|
391
|
+
return createFuzzyAutocompleteItems(providers, prefix, getLoginProviderSearchText, (provider) => ({
|
|
392
|
+
value: provider.id,
|
|
393
|
+
label: provider.id,
|
|
394
|
+
description: formatLoginProviderCompletionDescription(provider),
|
|
395
|
+
}));
|
|
396
|
+
};
|
|
397
|
+
}
|
|
348
398
|
// Convert prompt templates to SlashCommand format for autocomplete
|
|
349
399
|
const templateCommands = this.session.promptTemplates.map((cmd) => ({
|
|
350
400
|
name: cmd.name,
|
|
@@ -1155,7 +1205,7 @@ export class InteractiveMode {
|
|
|
1155
1205
|
this.restoreQueuedMessagesToEditor({ abort: true });
|
|
1156
1206
|
},
|
|
1157
1207
|
commandContextActions: {
|
|
1158
|
-
waitForIdle: () => this.session.
|
|
1208
|
+
waitForIdle: () => this.session.waitForIdle(),
|
|
1159
1209
|
newSession: async (options) => {
|
|
1160
1210
|
this.clearStatusIndicator();
|
|
1161
1211
|
try {
|
|
@@ -1206,7 +1256,7 @@ export class InteractiveMode {
|
|
|
1206
1256
|
},
|
|
1207
1257
|
shutdownHandler: () => {
|
|
1208
1258
|
this.shutdownRequested = true;
|
|
1209
|
-
if (
|
|
1259
|
+
if (this.session.isIdle) {
|
|
1210
1260
|
void this.shutdown();
|
|
1211
1261
|
}
|
|
1212
1262
|
},
|
|
@@ -1299,7 +1349,7 @@ export class InteractiveMode {
|
|
|
1299
1349
|
sessionManager: this.sessionManager,
|
|
1300
1350
|
modelRegistry: this.session.modelRegistry,
|
|
1301
1351
|
model: this.session.model,
|
|
1302
|
-
isIdle: () =>
|
|
1352
|
+
isIdle: () => this.session.isIdle,
|
|
1303
1353
|
isProjectTrusted: () => this.settingsManager.isProjectTrusted(),
|
|
1304
1354
|
signal: this.session.agent.signal,
|
|
1305
1355
|
abort: () => {
|
|
@@ -2105,9 +2155,10 @@ export class InteractiveMode {
|
|
|
2105
2155
|
this.editor.setText("");
|
|
2106
2156
|
return;
|
|
2107
2157
|
}
|
|
2108
|
-
if (text === "/login") {
|
|
2109
|
-
|
|
2158
|
+
if (text === "/login" || text.startsWith("/login ")) {
|
|
2159
|
+
const providerRef = text.startsWith("/login ") ? text.slice(7).trim() : undefined;
|
|
2110
2160
|
this.editor.setText("");
|
|
2161
|
+
await this.handleLoginCommand(providerRef);
|
|
2111
2162
|
return;
|
|
2112
2163
|
}
|
|
2113
2164
|
if (text === "/logout") {
|
|
@@ -2131,6 +2182,12 @@ export class InteractiveMode {
|
|
|
2131
2182
|
await this.handleReloadCommand();
|
|
2132
2183
|
return;
|
|
2133
2184
|
}
|
|
2185
|
+
const pendingPiClientUpdate = readPiClientUpdateMarker();
|
|
2186
|
+
if (pendingPiClientUpdate) {
|
|
2187
|
+
this.showWarning(`pi-client ${pendingPiClientUpdate.version} is installed. Run /reload to continue this session with the new version.`);
|
|
2188
|
+
this.editor.setText(text);
|
|
2189
|
+
return;
|
|
2190
|
+
}
|
|
2134
2191
|
if (text === "/debug") {
|
|
2135
2192
|
this.handleDebugCommand();
|
|
2136
2193
|
this.editor.setText("");
|
|
@@ -2241,6 +2298,12 @@ export class InteractiveMode {
|
|
|
2241
2298
|
this.updatePendingMessagesDisplay();
|
|
2242
2299
|
this.ui.requestRender();
|
|
2243
2300
|
break;
|
|
2301
|
+
case "entry_appended":
|
|
2302
|
+
if (event.entry.type === "custom") {
|
|
2303
|
+
this.addCustomEntryToChat(event.entry);
|
|
2304
|
+
this.ui.requestRender();
|
|
2305
|
+
}
|
|
2306
|
+
break;
|
|
2244
2307
|
case "session_info_changed":
|
|
2245
2308
|
this.updateTerminalTitle();
|
|
2246
2309
|
this.footer.invalidate();
|
|
@@ -2326,6 +2389,7 @@ export class InteractiveMode {
|
|
|
2326
2389
|
for (const [, component] of this.pendingTools.entries()) {
|
|
2327
2390
|
component.setArgsComplete();
|
|
2328
2391
|
}
|
|
2392
|
+
this.maybeShowCacheMissNotice(this.streamingMessage);
|
|
2329
2393
|
}
|
|
2330
2394
|
this.streamingComponent = undefined;
|
|
2331
2395
|
this.streamingMessage = undefined;
|
|
@@ -2376,9 +2440,11 @@ export class InteractiveMode {
|
|
|
2376
2440
|
this.streamingMessage = undefined;
|
|
2377
2441
|
}
|
|
2378
2442
|
this.pendingTools.clear();
|
|
2379
|
-
await this.checkShutdownRequested();
|
|
2380
2443
|
this.ui.requestRender();
|
|
2381
2444
|
break;
|
|
2445
|
+
case "agent_settled":
|
|
2446
|
+
await this.checkShutdownRequested();
|
|
2447
|
+
break;
|
|
2382
2448
|
case "compaction_start": {
|
|
2383
2449
|
if (this.settingsManager.getShowTerminalProgress()) {
|
|
2384
2450
|
this.ui.terminal.setProgress(true);
|
|
@@ -2486,6 +2552,25 @@ export class InteractiveMode {
|
|
|
2486
2552
|
this.lastStatusText = text;
|
|
2487
2553
|
this.ui.requestRender();
|
|
2488
2554
|
}
|
|
2555
|
+
addCustomEntryToChat(entry) {
|
|
2556
|
+
const renderer = this.session.extensionRunner.getEntryRenderer(entry.customType);
|
|
2557
|
+
if (!renderer) {
|
|
2558
|
+
return;
|
|
2559
|
+
}
|
|
2560
|
+
const component = new CustomEntryComponent(entry, renderer);
|
|
2561
|
+
component.setExpanded(this.toolOutputExpanded);
|
|
2562
|
+
if (!component.hasContent()) {
|
|
2563
|
+
return;
|
|
2564
|
+
}
|
|
2565
|
+
if (this.streamingComponent) {
|
|
2566
|
+
const streamingIndex = this.chatContainer.children.indexOf(this.streamingComponent);
|
|
2567
|
+
if (streamingIndex >= 0) {
|
|
2568
|
+
this.chatContainer.children.splice(streamingIndex, 0, component);
|
|
2569
|
+
return;
|
|
2570
|
+
}
|
|
2571
|
+
}
|
|
2572
|
+
this.chatContainer.addChild(component);
|
|
2573
|
+
}
|
|
2489
2574
|
addMessageToChat(message, options) {
|
|
2490
2575
|
switch (message.role) {
|
|
2491
2576
|
case "bashExecution": {
|
|
@@ -2563,20 +2648,24 @@ export class InteractiveMode {
|
|
|
2563
2648
|
}
|
|
2564
2649
|
}
|
|
2565
2650
|
}
|
|
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 = {}) {
|
|
2651
|
+
renderSessionItems(items, options = {}) {
|
|
2573
2652
|
this.pendingTools.clear();
|
|
2574
2653
|
const renderedPendingTools = new Map();
|
|
2654
|
+
// Cache-miss notices are not persisted; re-derive them from the full entry
|
|
2655
|
+
// list and re-inject them after the assistant messages that paid for them.
|
|
2656
|
+
const cacheMisses = this.settingsManager.getShowCacheMissNotices()
|
|
2657
|
+
? collectCacheMisses(this.sessionManager.getEntries(), this.session.modelRegistry)
|
|
2658
|
+
: new Map();
|
|
2575
2659
|
if (options.updateFooter) {
|
|
2576
2660
|
this.footer.invalidate();
|
|
2577
2661
|
this.updateEditorBorderColor();
|
|
2578
2662
|
}
|
|
2579
|
-
for (const
|
|
2663
|
+
for (const item of items) {
|
|
2664
|
+
if (isCustomSessionEntry(item)) {
|
|
2665
|
+
this.addCustomEntryToChat(item);
|
|
2666
|
+
continue;
|
|
2667
|
+
}
|
|
2668
|
+
const message = item;
|
|
2580
2669
|
// Assistant messages need special handling for tool calls
|
|
2581
2670
|
if (message.role === "assistant") {
|
|
2582
2671
|
this.addMessageToChat(message);
|
|
@@ -2608,6 +2697,11 @@ export class InteractiveMode {
|
|
|
2608
2697
|
}
|
|
2609
2698
|
}
|
|
2610
2699
|
}
|
|
2700
|
+
if (message.stopReason !== "aborted" && message.stopReason !== "error") {
|
|
2701
|
+
const miss = cacheMisses.get(message);
|
|
2702
|
+
if (miss)
|
|
2703
|
+
this.addCacheMissNotice(miss);
|
|
2704
|
+
}
|
|
2611
2705
|
}
|
|
2612
2706
|
else if (message.role === "toolResult") {
|
|
2613
2707
|
// Match tool results to pending tool components
|
|
@@ -2627,10 +2721,53 @@ export class InteractiveMode {
|
|
|
2627
2721
|
}
|
|
2628
2722
|
this.ui.requestRender();
|
|
2629
2723
|
}
|
|
2724
|
+
/**
|
|
2725
|
+
* Render session entries to chat. Used for initial load and rebuild after compaction.
|
|
2726
|
+
* @param entries Compaction-aware session entries to render
|
|
2727
|
+
* @param options.updateFooter Update footer state
|
|
2728
|
+
* @param options.populateHistory Add user messages to editor history
|
|
2729
|
+
*/
|
|
2730
|
+
renderSessionEntries(entries, options = {}) {
|
|
2731
|
+
const items = entries.flatMap((entry) => {
|
|
2732
|
+
if (entry.type === "custom") {
|
|
2733
|
+
return [entry];
|
|
2734
|
+
}
|
|
2735
|
+
return sessionEntryToContextMessages(entry);
|
|
2736
|
+
});
|
|
2737
|
+
this.renderSessionItems(items, options);
|
|
2738
|
+
}
|
|
2739
|
+
/**
|
|
2740
|
+
* Show a transcript notice when a completed assistant message paid for a
|
|
2741
|
+
* significant cache miss. Only states observable facts: the miss itself,
|
|
2742
|
+
* a model switch, or an idle gap past the cache TTL.
|
|
2743
|
+
*/
|
|
2744
|
+
maybeShowCacheMissNotice(message) {
|
|
2745
|
+
if (!this.settingsManager.getShowCacheMissNotices())
|
|
2746
|
+
return;
|
|
2747
|
+
// Entries don't contain `message` yet: message_end fires before persistence.
|
|
2748
|
+
const miss = detectCacheMiss(this.sessionManager.getEntries(), message, this.session.modelRegistry);
|
|
2749
|
+
if (miss)
|
|
2750
|
+
this.addCacheMissNotice(miss);
|
|
2751
|
+
}
|
|
2752
|
+
addCacheMissNotice(miss) {
|
|
2753
|
+
if (miss.missedTokens < 20_000 && miss.missedCost < 0.1)
|
|
2754
|
+
return;
|
|
2755
|
+
const cost = miss.missedCost >= 0.01 ? ` (~$${miss.missedCost.toFixed(2)})` : "";
|
|
2756
|
+
const reBilled = `${formatTokens(miss.missedTokens)} tokens re-billed${cost}`;
|
|
2757
|
+
let label = "Cache miss";
|
|
2758
|
+
if (miss.modelChanged) {
|
|
2759
|
+
label = "Cache miss after model switch";
|
|
2760
|
+
}
|
|
2761
|
+
else if (miss.idleMs >= CACHE_TTL_MS) {
|
|
2762
|
+
label = `Cache miss after ${Math.round(miss.idleMs / 60_000)}m idle`;
|
|
2763
|
+
}
|
|
2764
|
+
const text = theme.fg("warning", `${label}: ${reBilled}`);
|
|
2765
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
2766
|
+
this.chatContainer.addChild(new Text(text, 1, 0));
|
|
2767
|
+
}
|
|
2630
2768
|
renderInitialMessages() {
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
this.renderSessionContext(context, {
|
|
2769
|
+
const entries = this.sessionManager.buildContextEntries();
|
|
2770
|
+
this.renderSessionEntries(entries, {
|
|
2634
2771
|
updateFooter: true,
|
|
2635
2772
|
populateHistory: true,
|
|
2636
2773
|
});
|
|
@@ -2666,8 +2803,7 @@ export class InteractiveMode {
|
|
|
2666
2803
|
}
|
|
2667
2804
|
rebuildChatFromMessages() {
|
|
2668
2805
|
this.chatContainer.clear();
|
|
2669
|
-
|
|
2670
|
-
this.renderSessionContext(context);
|
|
2806
|
+
this.renderSessionEntries(this.sessionManager.buildContextEntries());
|
|
2671
2807
|
}
|
|
2672
2808
|
// =========================================================================
|
|
2673
2809
|
// Key handlers
|
|
@@ -3266,6 +3402,7 @@ export class InteractiveMode {
|
|
|
3266
3402
|
doubleEscapeAction: this.settingsManager.getDoubleEscapeAction(),
|
|
3267
3403
|
treeFilterMode: this.settingsManager.getTreeFilterMode(),
|
|
3268
3404
|
showHardwareCursor: this.settingsManager.getShowHardwareCursor(),
|
|
3405
|
+
showCacheMissNotices: this.settingsManager.getShowCacheMissNotices(),
|
|
3269
3406
|
defaultProjectTrust: this.settingsManager.getDefaultProjectTrust(),
|
|
3270
3407
|
editorPaddingX: this.settingsManager.getEditorPaddingX(),
|
|
3271
3408
|
outputPad: this.settingsManager.getOutputPad(),
|
|
@@ -3341,6 +3478,10 @@ export class InteractiveMode {
|
|
|
3341
3478
|
this.chatContainer.clear();
|
|
3342
3479
|
this.rebuildChatFromMessages();
|
|
3343
3480
|
},
|
|
3481
|
+
onShowCacheMissNoticesChange: (shown) => {
|
|
3482
|
+
this.settingsManager.setShowCacheMissNotices(shown);
|
|
3483
|
+
this.rebuildChatFromMessages();
|
|
3484
|
+
},
|
|
3344
3485
|
onCollapseChangelogChange: (collapsed) => {
|
|
3345
3486
|
this.settingsManager.setCollapseChangelog(collapsed);
|
|
3346
3487
|
},
|
|
@@ -3630,19 +3771,17 @@ export class InteractiveMode {
|
|
|
3630
3771
|
const initialSelectedId = userMessages[userMessages.length - 1]?.entryId;
|
|
3631
3772
|
this.showSelector((done) => {
|
|
3632
3773
|
const selector = new UserMessageSelectorComponent(userMessages.map((m) => ({ id: m.entryId, text: m.text })), async (entryId) => {
|
|
3774
|
+
done();
|
|
3633
3775
|
try {
|
|
3634
3776
|
const result = await this.runtimeHost.fork(entryId);
|
|
3635
3777
|
if (result.cancelled) {
|
|
3636
|
-
done();
|
|
3637
3778
|
this.ui.requestRender();
|
|
3638
3779
|
return;
|
|
3639
3780
|
}
|
|
3640
3781
|
this.editor.setText(result.selectedText ?? "");
|
|
3641
|
-
done();
|
|
3642
3782
|
this.showStatus("Forked to new session");
|
|
3643
3783
|
}
|
|
3644
3784
|
catch (error) {
|
|
3645
|
-
done();
|
|
3646
3785
|
this.showError(error instanceof Error ? error.message : String(error));
|
|
3647
3786
|
}
|
|
3648
3787
|
}, () => {
|
|
@@ -3871,13 +4010,82 @@ export class InteractiveMode {
|
|
|
3871
4010
|
}
|
|
3872
4011
|
return options.sort((a, b) => a.name.localeCompare(b.name));
|
|
3873
4012
|
}
|
|
3874
|
-
|
|
4013
|
+
findLoginProviderOptions(providerRef) {
|
|
4014
|
+
const normalizedProviderRef = providerRef.trim().toLowerCase();
|
|
4015
|
+
if (!normalizedProviderRef) {
|
|
4016
|
+
return [];
|
|
4017
|
+
}
|
|
4018
|
+
return this.getLoginProviderOptions().filter((provider) => provider.id.toLowerCase() === normalizedProviderRef ||
|
|
4019
|
+
provider.name.toLowerCase() === normalizedProviderRef);
|
|
4020
|
+
}
|
|
4021
|
+
async handleLoginCommand(providerRef) {
|
|
4022
|
+
if (!providerRef) {
|
|
4023
|
+
this.showLoginAuthTypeSelector();
|
|
4024
|
+
return;
|
|
4025
|
+
}
|
|
4026
|
+
const providerOptions = this.findLoginProviderOptions(providerRef);
|
|
4027
|
+
if (providerOptions.length === 1) {
|
|
4028
|
+
await this.startProviderLogin(providerOptions[0]);
|
|
4029
|
+
return;
|
|
4030
|
+
}
|
|
4031
|
+
if (providerOptions.length > 1) {
|
|
4032
|
+
const providerIds = new Set(providerOptions.map((provider) => provider.id));
|
|
4033
|
+
if (providerIds.size === 1) {
|
|
4034
|
+
this.showLoginAuthTypeSelector(providerOptions);
|
|
4035
|
+
return;
|
|
4036
|
+
}
|
|
4037
|
+
}
|
|
4038
|
+
this.showLoginProviderSelector(undefined, providerRef);
|
|
4039
|
+
}
|
|
4040
|
+
async startProviderLogin(providerOption) {
|
|
4041
|
+
if (providerOption.authType === "oauth") {
|
|
4042
|
+
await this.showLoginDialog(providerOption.id, providerOption.name);
|
|
4043
|
+
}
|
|
4044
|
+
else if (providerOption.id === BEDROCK_PROVIDER_ID) {
|
|
4045
|
+
this.showBedrockSetupDialog(providerOption.id, providerOption.name);
|
|
4046
|
+
}
|
|
4047
|
+
else {
|
|
4048
|
+
await this.showApiKeyLoginDialog(providerOption.id, providerOption.name);
|
|
4049
|
+
}
|
|
4050
|
+
}
|
|
4051
|
+
showLoginAuthTypeSelector(providerOptions) {
|
|
3875
4052
|
const subscriptionLabel = "Use a subscription";
|
|
3876
4053
|
const apiKeyLabel = "Use an API key";
|
|
4054
|
+
const availableAuthTypes = providerOptions
|
|
4055
|
+
? new Set(providerOptions.map((provider) => provider.authType))
|
|
4056
|
+
: new Set(["oauth", "api_key"]);
|
|
4057
|
+
const options = [];
|
|
4058
|
+
if (availableAuthTypes.has("oauth")) {
|
|
4059
|
+
options.push(subscriptionLabel);
|
|
4060
|
+
}
|
|
4061
|
+
if (availableAuthTypes.has("api_key")) {
|
|
4062
|
+
options.push(apiKeyLabel);
|
|
4063
|
+
}
|
|
4064
|
+
if (options.length === 0) {
|
|
4065
|
+
this.showStatus("No login methods available.");
|
|
4066
|
+
return;
|
|
4067
|
+
}
|
|
4068
|
+
if (providerOptions && options.length === 1) {
|
|
4069
|
+
const providerOption = providerOptions[0];
|
|
4070
|
+
if (providerOption) {
|
|
4071
|
+
void this.startProviderLogin(providerOption);
|
|
4072
|
+
}
|
|
4073
|
+
return;
|
|
4074
|
+
}
|
|
4075
|
+
const title = providerOptions?.[0]
|
|
4076
|
+
? `Select authentication method for ${providerOptions[0].name}:`
|
|
4077
|
+
: "Select authentication method:";
|
|
3877
4078
|
this.showSelector((done) => {
|
|
3878
|
-
const selector = new ExtensionSelectorComponent(
|
|
4079
|
+
const selector = new ExtensionSelectorComponent(title, options, (option) => {
|
|
3879
4080
|
done();
|
|
3880
4081
|
const authType = option === subscriptionLabel ? "oauth" : "api_key";
|
|
4082
|
+
if (providerOptions) {
|
|
4083
|
+
const providerOption = providerOptions.find((provider) => provider.authType === authType);
|
|
4084
|
+
if (providerOption) {
|
|
4085
|
+
void this.startProviderLogin(providerOption);
|
|
4086
|
+
}
|
|
4087
|
+
return;
|
|
4088
|
+
}
|
|
3881
4089
|
this.showLoginProviderSelector(authType);
|
|
3882
4090
|
}, () => {
|
|
3883
4091
|
done();
|
|
@@ -3886,32 +4094,34 @@ export class InteractiveMode {
|
|
|
3886
4094
|
return { component: selector, focus: selector };
|
|
3887
4095
|
});
|
|
3888
4096
|
}
|
|
3889
|
-
showLoginProviderSelector(authType) {
|
|
4097
|
+
showLoginProviderSelector(authType, initialSearchInput) {
|
|
3890
4098
|
const providerOptions = this.getLoginProviderOptions(authType);
|
|
3891
4099
|
if (providerOptions.length === 0) {
|
|
3892
|
-
|
|
4100
|
+
const message = authType === "oauth"
|
|
4101
|
+
? "No subscription providers available."
|
|
4102
|
+
: authType === "api_key"
|
|
4103
|
+
? "No API key providers available."
|
|
4104
|
+
: "No login providers available.";
|
|
4105
|
+
this.showStatus(message);
|
|
3893
4106
|
return;
|
|
3894
4107
|
}
|
|
3895
4108
|
this.showSelector((done) => {
|
|
3896
|
-
const selector = new OAuthSelectorComponent("login", this.session.modelRegistry.authStorage, providerOptions, async (providerId) => {
|
|
4109
|
+
const selector = new OAuthSelectorComponent("login", this.session.modelRegistry.authStorage, providerOptions, async (providerId, selectedAuthType) => {
|
|
3897
4110
|
done();
|
|
3898
|
-
const providerOption = providerOptions.find((provider) => provider.id === providerId);
|
|
4111
|
+
const providerOption = providerOptions.find((provider) => provider.id === providerId && provider.authType === selectedAuthType);
|
|
3899
4112
|
if (!providerOption) {
|
|
3900
4113
|
return;
|
|
3901
4114
|
}
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
this.
|
|
4115
|
+
await this.startProviderLogin(providerOption);
|
|
4116
|
+
}, () => {
|
|
4117
|
+
done();
|
|
4118
|
+
if (authType) {
|
|
4119
|
+
this.showLoginAuthTypeSelector();
|
|
3907
4120
|
}
|
|
3908
4121
|
else {
|
|
3909
|
-
|
|
4122
|
+
this.ui.requestRender();
|
|
3910
4123
|
}
|
|
3911
|
-
}, () =>
|
|
3912
|
-
done();
|
|
3913
|
-
this.showLoginAuthTypeSelector();
|
|
3914
|
-
}, (providerId) => this.session.modelRegistry.getProviderAuthStatus(providerId));
|
|
4124
|
+
}, (providerId) => this.session.modelRegistry.getProviderAuthStatus(providerId), initialSearchInput);
|
|
3915
4125
|
return { component: selector, focus: selector };
|
|
3916
4126
|
});
|
|
3917
4127
|
}
|
|
@@ -4165,12 +4375,31 @@ export class InteractiveMode {
|
|
|
4165
4375
|
this.showWarning("Wait for compaction to finish before reloading.");
|
|
4166
4376
|
return;
|
|
4167
4377
|
}
|
|
4378
|
+
const pendingPiClientUpdate = readPiClientUpdateMarker();
|
|
4379
|
+
if (pendingPiClientUpdate) {
|
|
4380
|
+
const reloadStatePath = process.env.PI_CLIENT_RELOAD_STATE_PATH;
|
|
4381
|
+
if (!reloadStatePath) {
|
|
4382
|
+
this.showWarning(`pi-client ${pendingPiClientUpdate.version} is installed. Restart pi-client to load the new runtime.`);
|
|
4383
|
+
return;
|
|
4384
|
+
}
|
|
4385
|
+
writePiClientRuntimeReloadState(reloadStatePath, {
|
|
4386
|
+
sessionId: this.sessionManager.getSessionId(),
|
|
4387
|
+
sessionDir: this.sessionManager.usesDefaultSessionDir() ? undefined : this.sessionManager.getSessionDir(),
|
|
4388
|
+
updateMarkerPath: getPiClientUpdateMarkerPath(),
|
|
4389
|
+
});
|
|
4390
|
+
this.isShuttingDown = true;
|
|
4391
|
+
this.themeController.disableAutoSync();
|
|
4392
|
+
await this.ui.terminal.drainInput(1000);
|
|
4393
|
+
this.stop();
|
|
4394
|
+
await this.runtimeHost.dispose();
|
|
4395
|
+
process.exit(PI_CLIENT_RUNTIME_RELOAD_EXIT_CODE);
|
|
4396
|
+
}
|
|
4168
4397
|
this.resetExtensionUI();
|
|
4169
4398
|
const reloadBox = new Container();
|
|
4170
4399
|
const borderColor = (s) => theme.fg("border", s);
|
|
4171
4400
|
reloadBox.addChild(new DynamicBorder(borderColor));
|
|
4172
4401
|
reloadBox.addChild(new Spacer(1));
|
|
4173
|
-
reloadBox.addChild(new Text(theme.fg("muted", "Reloading keybindings, extensions, skills, prompts, themes..."), 1, 0));
|
|
4402
|
+
reloadBox.addChild(new Text(theme.fg("muted", "Reloading keybindings, extensions, skills, prompts, themes, and context files..."), 1, 0));
|
|
4174
4403
|
reloadBox.addChild(new Spacer(1));
|
|
4175
4404
|
reloadBox.addChild(new DynamicBorder(borderColor));
|
|
4176
4405
|
const previousEditor = this.editor;
|
|
@@ -4234,8 +4463,8 @@ export class InteractiveMode {
|
|
|
4234
4463
|
this.showError(`models.json error: ${modelsJsonError}`);
|
|
4235
4464
|
}
|
|
4236
4465
|
this.showStatus(savedImplicitProjectTrust
|
|
4237
|
-
? "Reloaded keybindings, extensions, skills, prompts, themes; saved project trust"
|
|
4238
|
-
: "Reloaded keybindings, extensions, skills, prompts, themes");
|
|
4466
|
+
? "Reloaded keybindings, extensions, skills, prompts, themes, and context files; saved project trust"
|
|
4467
|
+
: "Reloaded keybindings, extensions, skills, prompts, themes, and context files");
|
|
4239
4468
|
dismissReloadBox(this.editor);
|
|
4240
4469
|
reloadBoxDismissed = true;
|
|
4241
4470
|
}
|
|
@@ -4456,6 +4685,26 @@ export class InteractiveMode {
|
|
|
4456
4685
|
handleSessionCommand() {
|
|
4457
4686
|
const stats = this.session.getSessionStats();
|
|
4458
4687
|
const sessionName = this.sessionManager.getSessionName();
|
|
4688
|
+
const entries = this.sessionManager.getEntries();
|
|
4689
|
+
const cacheWaste = computeCacheWaste(entries, this.session.modelRegistry);
|
|
4690
|
+
// Cost/token totals per provider/model actually used (e.g. OpenRouter `auto`
|
|
4691
|
+
// resolves to a concrete responseModel), sorted by cost descending.
|
|
4692
|
+
const perModelMap = new Map();
|
|
4693
|
+
for (const entry of entries) {
|
|
4694
|
+
if (entry.type !== "message" || entry.message.role !== "assistant")
|
|
4695
|
+
continue;
|
|
4696
|
+
const message = entry.message;
|
|
4697
|
+
const usage = message.usage;
|
|
4698
|
+
const key = `${message.provider}/${message.responseModel ?? message.model}`;
|
|
4699
|
+
let bucket = perModelMap.get(key);
|
|
4700
|
+
if (!bucket) {
|
|
4701
|
+
bucket = { key, cost: 0, tokens: 0 };
|
|
4702
|
+
perModelMap.set(key, bucket);
|
|
4703
|
+
}
|
|
4704
|
+
bucket.cost += usage.cost.total;
|
|
4705
|
+
bucket.tokens += usage.input + usage.output + usage.cacheRead + usage.cacheWrite;
|
|
4706
|
+
}
|
|
4707
|
+
const perModel = Array.from(perModelMap.values()).sort((a, b) => b.cost - a.cost);
|
|
4459
4708
|
let info = `${theme.bold("Session Info")}\n\n`;
|
|
4460
4709
|
if (sessionName) {
|
|
4461
4710
|
info += `${theme.fg("dim", "Name:")} ${sessionName}\n`;
|
|
@@ -4463,24 +4712,42 @@ export class InteractiveMode {
|
|
|
4463
4712
|
info += `${theme.fg("dim", "File:")} ${stats.sessionFile ?? "In-memory"}\n`;
|
|
4464
4713
|
info += `${theme.fg("dim", "ID:")} ${stats.sessionId}\n\n`;
|
|
4465
4714
|
info += `${theme.bold("Messages")}\n`;
|
|
4715
|
+
info += `${theme.fg("dim", "Total:")} ${stats.totalMessages}\n`;
|
|
4466
4716
|
info += `${theme.fg("dim", "User:")} ${stats.userMessages}\n`;
|
|
4467
4717
|
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`;
|
|
4718
|
+
info += `${theme.fg("dim", "Tools:")} ${stats.toolCalls} calls, ${stats.toolResults} results\n\n`;
|
|
4471
4719
|
info += `${theme.bold("Tokens")}\n`;
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
}
|
|
4477
|
-
|
|
4478
|
-
|
|
4720
|
+
// "Input" is the full prompt volume. With cache activity, split it into
|
|
4721
|
+
// cached (served from cache) vs uncached (everything else) - the only
|
|
4722
|
+
// provider-independent split. Cache writes, where reported, are a detail
|
|
4723
|
+
// of the uncached portion.
|
|
4724
|
+
const { input, cacheRead, cacheWrite } = stats.tokens;
|
|
4725
|
+
const promptTokens = input + cacheRead + cacheWrite;
|
|
4726
|
+
info += `${theme.fg("dim", "Input:")} ${promptTokens.toLocaleString()}\n`;
|
|
4727
|
+
if (promptTokens > 0 && (cacheRead > 0 || cacheWrite > 0)) {
|
|
4728
|
+
const hitRate = theme.fg("dim", `(${((cacheRead / promptTokens) * 100).toFixed(1)}%)`);
|
|
4729
|
+
info += ` ${theme.fg("dim", "Cached:")} ${cacheRead.toLocaleString()} ${hitRate}\n`;
|
|
4730
|
+
const written = cacheWrite > 0 ? ` ${theme.fg("dim", `(${cacheWrite.toLocaleString()} written to cache)`)}` : "";
|
|
4731
|
+
info += ` ${theme.fg("dim", "Uncached:")} ${(input + cacheWrite).toLocaleString()}${written}\n`;
|
|
4479
4732
|
}
|
|
4733
|
+
info += `${theme.fg("dim", "Output:")} ${stats.tokens.output.toLocaleString()}\n`;
|
|
4480
4734
|
info += `${theme.fg("dim", "Total:")} ${stats.tokens.total.toLocaleString()}\n`;
|
|
4481
|
-
if (stats.cost > 0) {
|
|
4735
|
+
if (stats.cost > 0 || cacheWaste.missedTokens > 0) {
|
|
4482
4736
|
info += `\n${theme.bold("Cost")}\n`;
|
|
4483
|
-
info += `${theme.fg("dim", "Total:")}
|
|
4737
|
+
info += `${theme.fg("dim", "Total:")} $${stats.cost.toFixed(3)}`;
|
|
4738
|
+
if (perModel.length > 1) {
|
|
4739
|
+
for (const entry of perModel) {
|
|
4740
|
+
info += `\n ${theme.fg("dim", `${entry.key}:`)} $${entry.cost.toFixed(3)} ${theme.fg("dim", `(${formatTokens(entry.tokens)} tokens)`)}`;
|
|
4741
|
+
}
|
|
4742
|
+
}
|
|
4743
|
+
if (cacheWaste.missedTokens > 0) {
|
|
4744
|
+
const missLabel = cacheWaste.missCount === 1 ? "1 miss" : `${cacheWaste.missCount} misses`;
|
|
4745
|
+
const detail = `${cacheWaste.missedTokens.toLocaleString()} tokens, ${missLabel}`;
|
|
4746
|
+
info +=
|
|
4747
|
+
cacheWaste.missedCost >= 0.0001
|
|
4748
|
+
? `\n${theme.fg("dim", "Cache Re-billed:")} $${cacheWaste.missedCost.toFixed(3)} ${theme.fg("dim", `(${detail})`)}`
|
|
4749
|
+
: `\n${theme.fg("dim", "Cache Re-billed:")} ${detail}`;
|
|
4750
|
+
}
|
|
4484
4751
|
}
|
|
4485
4752
|
this.chatContainer.addChild(new Spacer(1));
|
|
4486
4753
|
this.chatContainer.addChild(new Text(info, 1, 0));
|