@code-yeongyu/senpi 2026.7.9 → 2026.7.10
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 +37 -1
- package/README.md +1 -1
- package/dist/cli/args.js +3 -3
- 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 +18 -3
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +153 -36
- package/dist/core/agent-session.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 +54 -79
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/extensions/builtin/goal/continuation.js +1 -1
- package/dist/core/extensions/builtin/goal/continuation.js.map +1 -1
- package/dist/core/extensions/builtin/goal/index.d.ts.map +1 -1
- package/dist/core/extensions/builtin/goal/index.js +1 -0
- package/dist/core/extensions/builtin/goal/index.js.map +1 -1
- package/dist/core/extensions/builtin/import-repro.d.ts.map +1 -1
- package/dist/core/extensions/builtin/import-repro.js +100 -6
- package/dist/core/extensions/builtin/import-repro.js.map +1 -1
- package/dist/core/extensions/builtin/mcp/connection.d.ts +1 -0
- package/dist/core/extensions/builtin/mcp/connection.d.ts.map +1 -1
- package/dist/core/extensions/builtin/mcp/connection.js +15 -0
- package/dist/core/extensions/builtin/mcp/connection.js.map +1 -1
- package/dist/core/extensions/builtin/mcp/service.d.ts.map +1 -1
- package/dist/core/extensions/builtin/mcp/service.js +3 -1
- package/dist/core/extensions/builtin/mcp/service.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/runner.d.ts +4 -3
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +30 -24
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +23 -3
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.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 +20 -3
- package/dist/core/model-registry.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/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 +9 -2
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +11 -2
- 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/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -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 +272 -24
- package/dist/modes/interactive/components/config-selector.js.map +1 -1
- 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 +5 -2
- 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 +16 -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 +10 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +261 -37
- package/dist/modes/interactive/interactive-mode.js.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/package-manager-cli.d.ts +2 -2
- package/dist/package-manager-cli.d.ts.map +1 -1
- package/dist/package-manager-cli.js +64 -17
- package/dist/package-manager-cli.js.map +1 -1
- package/dist/utils/clipboard-image.js +1 -1
- package/dist/utils/clipboard-image.js.map +1 -1
- package/docs/extensions.md +33 -9
- package/docs/models.md +4 -4
- package/docs/packages.md +7 -10
- package/docs/rpc.md +13 -3
- package/docs/sdk.md +22 -0
- package/docs/settings.md +1 -0
- package/docs/usage.md +1 -1
- 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/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- 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/README.md +1 -1
- package/node_modules/@code-yeongyu/senpi-codemode/package.json +3 -3
- package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js +37 -2
- package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/jsonl-repo.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/jsonl-repo.js +2 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/jsonl-repo.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/jsonl-storage.d.ts +1 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/jsonl-storage.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/jsonl-storage.js +7 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/jsonl-storage.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/session.d.ts +18 -4
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/session.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/session.js +72 -37
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/session.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.d.ts +2 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/index.d.ts +2 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/index.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/index.js +2 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/index.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/package.json +2 -2
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js +2 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses-shared.js +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses-shared.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/transform-messages.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/transform-messages.js +4 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/transform-messages.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts +446 -195
- package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.models.d.ts +84 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.models.js +72 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/github-copilot.models.js +4 -4
- package/node_modules/@earendil-works/pi-ai/dist/providers/github-copilot.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex.models.d.ts +63 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex.models.js +54 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai.models.d.ts +84 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai.models.js +72 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.d.ts +138 -63
- package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.js +136 -76
- package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.d.ts +77 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.js +72 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-ams.models.d.ts +0 -44
- package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-ams.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-ams.models.js +9 -45
- package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-ams.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-cn.models.d.ts +0 -44
- package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-cn.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-cn.models.js +9 -45
- package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-cn.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-sgp.models.d.ts +0 -44
- package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-sgp.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-sgp.models.js +9 -45
- package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-sgp.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/retry.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js +3 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/package.json +1 -1
- package/node_modules/@earendil-works/pi-pty/package.json +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/editor.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/editor.js +22 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/editor.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/package.json +1 -1
- package/package.json +7 -8
- package/npm-shrinkwrap.json +0 -3479
|
@@ -13,6 +13,7 @@ import { APP_NAME, APP_TITLE, CONFIG_DIR_NAME, expandTildePath, getAuthPath, get
|
|
|
13
13
|
import { parseSkillBlock } from "../../core/agent-session.js";
|
|
14
14
|
import { SessionImportFileNotFoundError } from "../../core/agent-session-runtime.js";
|
|
15
15
|
import { isApiKeyLoginProvider } from "../../core/auth-providers.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 { appendHiddenTuiStdout } from "../../core/hidden-stdout-log.js";
|
|
18
19
|
import { configureHttpDispatcher, formatHttpIdleTimeoutMs } from "../../core/http-dispatcher.js";
|
|
@@ -49,12 +50,12 @@ import { ExtensionEditorComponent } from "./components/extension-editor.js";
|
|
|
49
50
|
import { ExtensionInputComponent } from "./components/extension-input.js";
|
|
50
51
|
import { ExtensionSelectorComponent } from "./components/extension-selector.js";
|
|
51
52
|
import { FavoriteModelsSelectorComponent } from "./components/favorite-models-selector.js";
|
|
52
|
-
import { FooterComponent } from "./components/footer.js";
|
|
53
|
+
import { FooterComponent, formatTokens } from "./components/footer.js";
|
|
53
54
|
import { formatKeyText, keyDisplayText, keyHint, keyText, rawKeyHint } from "./components/keybinding-hints.js";
|
|
54
55
|
import { LoginDialogComponent } from "./components/login-dialog.js";
|
|
55
56
|
import { getModelFullId } from "./components/model-favorites.js";
|
|
56
57
|
import { ModelSelectorComponent } from "./components/model-selector.js";
|
|
57
|
-
import { OAuthSelectorComponent } from "./components/oauth-selector.js";
|
|
58
|
+
import { formatAuthSelectorProviderType, OAuthSelectorComponent, } from "./components/oauth-selector.js";
|
|
58
59
|
import { SessionSelectorComponent } from "./components/session-selector.js";
|
|
59
60
|
import { SettingsSelectorComponent } from "./components/settings-selector.js";
|
|
60
61
|
import { SkillInvocationMessageComponent } from "./components/skill-invocation-message.js";
|
|
@@ -66,7 +67,6 @@ import { UserMessageComponent } from "./components/user-message.js";
|
|
|
66
67
|
import { UserMessageSelectorComponent } from "./components/user-message-selector.js";
|
|
67
68
|
import { restoreInteractiveStderr, takeOverInteractiveStderr } from "./interactive-stderr-guard.js";
|
|
68
69
|
import { getModelSearchText } from "./model-search.js";
|
|
69
|
-
import { formatSessionInfo } from "./session-info-format.js";
|
|
70
70
|
import { resolveStartupToolPaths } from "./startup-tools.js";
|
|
71
71
|
import { getAvailableThemes, getAvailableThemesWithPaths, getEditorTheme, getMarkdownTheme, getThemeByName, onThemeChange, setRegisteredThemes, stopThemeWatcher, Theme, theme, } from "./theme/theme.js";
|
|
72
72
|
import { InteractiveThemeController } from "./theme/theme-controller.js";
|
|
@@ -179,6 +179,42 @@ const BEDROCK_PROVIDER_ID = "amazon-bedrock";
|
|
|
179
179
|
// keep the existing public import (test/oauth-selector.test.ts) working, and used
|
|
180
180
|
// locally by getLoginProviderOptions below.
|
|
181
181
|
export { isApiKeyLoginProvider };
|
|
182
|
+
const AUTH_TYPE_ORDER = { oauth: 0, api_key: 1 };
|
|
183
|
+
function createFuzzyAutocompleteItems(items, prefix, getSearchText, toAutocompleteItem) {
|
|
184
|
+
const filtered = fuzzyFilter(items, prefix, getSearchText);
|
|
185
|
+
if (filtered.length === 0)
|
|
186
|
+
return null;
|
|
187
|
+
return filtered.map(toAutocompleteItem);
|
|
188
|
+
}
|
|
189
|
+
function getLoginProviderCompletionOptions(providerOptions) {
|
|
190
|
+
const byId = new Map();
|
|
191
|
+
for (const provider of providerOptions) {
|
|
192
|
+
const existing = byId.get(provider.id);
|
|
193
|
+
if (existing) {
|
|
194
|
+
if (!existing.authTypes.includes(provider.authType)) {
|
|
195
|
+
existing.authTypes.push(provider.authType);
|
|
196
|
+
existing.authTypes.sort((a, b) => AUTH_TYPE_ORDER[a] - AUTH_TYPE_ORDER[b]);
|
|
197
|
+
}
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
byId.set(provider.id, {
|
|
201
|
+
id: provider.id,
|
|
202
|
+
name: provider.name,
|
|
203
|
+
authTypes: [provider.authType],
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
return Array.from(byId.values()).sort((a, b) => a.name.localeCompare(b.name));
|
|
207
|
+
}
|
|
208
|
+
function getLoginProviderSearchText(provider) {
|
|
209
|
+
const authTypes = provider.authTypes
|
|
210
|
+
.map((authType) => `${authType} ${formatAuthSelectorProviderType(authType)}`)
|
|
211
|
+
.join(" ");
|
|
212
|
+
return `${provider.id} ${provider.name} ${authTypes}`;
|
|
213
|
+
}
|
|
214
|
+
function formatLoginProviderCompletionDescription(provider) {
|
|
215
|
+
const authTypes = provider.authTypes.map(formatAuthSelectorProviderType).join("/");
|
|
216
|
+
return provider.name === provider.id ? authTypes : `${provider.name} · ${authTypes}`;
|
|
217
|
+
}
|
|
182
218
|
export class InteractiveMode {
|
|
183
219
|
requestStreamingRender() {
|
|
184
220
|
this.ui.requestRender();
|
|
@@ -350,6 +386,7 @@ export class InteractiveMode {
|
|
|
350
386
|
const slashCommands = BUILTIN_SLASH_COMMANDS.map((command) => ({
|
|
351
387
|
name: command.name,
|
|
352
388
|
description: command.description,
|
|
389
|
+
...(command.argumentHint && { argumentHint: command.argumentHint }),
|
|
353
390
|
}));
|
|
354
391
|
const modelCommand = slashCommands.find((command) => command.name === "model");
|
|
355
392
|
if (modelCommand) {
|
|
@@ -367,17 +404,24 @@ export class InteractiveMode {
|
|
|
367
404
|
name: m.name,
|
|
368
405
|
label: `${m.provider}/${m.id}`,
|
|
369
406
|
}));
|
|
370
|
-
|
|
371
|
-
const filtered = fuzzyFilter(items, prefix, getModelSearchText);
|
|
372
|
-
if (filtered.length === 0)
|
|
373
|
-
return null;
|
|
374
|
-
return filtered.map((item) => ({
|
|
407
|
+
return createFuzzyAutocompleteItems(items, prefix, getModelSearchText, (item) => ({
|
|
375
408
|
value: item.label,
|
|
376
409
|
label: item.id,
|
|
377
410
|
description: item.provider,
|
|
378
411
|
}));
|
|
379
412
|
};
|
|
380
413
|
}
|
|
414
|
+
const loginCommand = slashCommands.find((command) => command.name === "login");
|
|
415
|
+
if (loginCommand) {
|
|
416
|
+
loginCommand.getArgumentCompletions = (prefix) => {
|
|
417
|
+
const providers = getLoginProviderCompletionOptions(this.getLoginProviderOptions());
|
|
418
|
+
return createFuzzyAutocompleteItems(providers, prefix, getLoginProviderSearchText, (provider) => ({
|
|
419
|
+
value: provider.id,
|
|
420
|
+
label: provider.id,
|
|
421
|
+
description: formatLoginProviderCompletionDescription(provider),
|
|
422
|
+
}));
|
|
423
|
+
};
|
|
424
|
+
}
|
|
381
425
|
// Convert prompt templates to SlashCommand format for autocomplete
|
|
382
426
|
const templateCommands = this.session.promptTemplates.map((cmd) => ({
|
|
383
427
|
name: cmd.name,
|
|
@@ -1205,7 +1249,7 @@ export class InteractiveMode {
|
|
|
1205
1249
|
this.restoreQueuedMessagesToEditor({ abort: true });
|
|
1206
1250
|
},
|
|
1207
1251
|
commandContextActions: {
|
|
1208
|
-
waitForIdle: () => this.session.
|
|
1252
|
+
waitForIdle: () => this.session.waitForIdle(),
|
|
1209
1253
|
newSession: async (options) => {
|
|
1210
1254
|
this.clearStatusIndicator();
|
|
1211
1255
|
try {
|
|
@@ -1256,7 +1300,7 @@ export class InteractiveMode {
|
|
|
1256
1300
|
},
|
|
1257
1301
|
shutdownHandler: () => {
|
|
1258
1302
|
this.shutdownRequested = true;
|
|
1259
|
-
if (
|
|
1303
|
+
if (this.session.isIdle) {
|
|
1260
1304
|
void this.shutdown();
|
|
1261
1305
|
}
|
|
1262
1306
|
},
|
|
@@ -1351,7 +1395,7 @@ export class InteractiveMode {
|
|
|
1351
1395
|
modelRegistry: this.session.modelRegistry,
|
|
1352
1396
|
model: this.session.model,
|
|
1353
1397
|
serviceTier: this.session.serviceTier,
|
|
1354
|
-
isIdle: () =>
|
|
1398
|
+
isIdle: () => this.session.isIdle,
|
|
1355
1399
|
isProjectTrusted: () => this.settingsManager.isProjectTrusted(),
|
|
1356
1400
|
signal: this.session.agent.signal,
|
|
1357
1401
|
abort: () => this.session.abort(),
|
|
@@ -2347,9 +2391,10 @@ export class InteractiveMode {
|
|
|
2347
2391
|
this.editor.setText("");
|
|
2348
2392
|
return;
|
|
2349
2393
|
}
|
|
2350
|
-
if (text === "/login") {
|
|
2351
|
-
|
|
2394
|
+
if (text === "/login" || text.startsWith("/login ")) {
|
|
2395
|
+
const providerRef = text.startsWith("/login ") ? text.slice(7).trim() : undefined;
|
|
2352
2396
|
this.editor.setText("");
|
|
2397
|
+
await this.handleLoginCommand(providerRef);
|
|
2353
2398
|
return;
|
|
2354
2399
|
}
|
|
2355
2400
|
if (text === "/logout") {
|
|
@@ -2582,6 +2627,7 @@ export class InteractiveMode {
|
|
|
2582
2627
|
for (const [, component] of this.pendingTools.entries()) {
|
|
2583
2628
|
component.setArgsComplete();
|
|
2584
2629
|
}
|
|
2630
|
+
this.maybeShowCacheMissNotice(this.streamingMessage);
|
|
2585
2631
|
}
|
|
2586
2632
|
this.streamingComponent = undefined;
|
|
2587
2633
|
this.streamingMessage = undefined;
|
|
@@ -2639,9 +2685,11 @@ export class InteractiveMode {
|
|
|
2639
2685
|
this.streamingMessage = undefined;
|
|
2640
2686
|
}
|
|
2641
2687
|
this.clearPendingTools();
|
|
2642
|
-
await this.checkShutdownRequested();
|
|
2643
2688
|
this.ui.requestRender();
|
|
2644
2689
|
break;
|
|
2690
|
+
case "agent_settled":
|
|
2691
|
+
await this.checkShutdownRequested();
|
|
2692
|
+
break;
|
|
2645
2693
|
case "compaction_start": {
|
|
2646
2694
|
if (this.settingsManager.getShowTerminalProgress()) {
|
|
2647
2695
|
this.ui.terminal.setProgress(true);
|
|
@@ -2871,6 +2919,11 @@ export class InteractiveMode {
|
|
|
2871
2919
|
renderSessionItems(items, options = {}) {
|
|
2872
2920
|
this.clearPendingTools();
|
|
2873
2921
|
const renderedPendingTools = new Map();
|
|
2922
|
+
// Cache-miss notices are not persisted; re-derive them from the full entry
|
|
2923
|
+
// list and re-inject them after the assistant messages that paid for them.
|
|
2924
|
+
const cacheMisses = this.settingsManager.getShowCacheMissNotices()
|
|
2925
|
+
? collectCacheMisses(this.sessionManager.getEntries(), this.session.modelRegistry)
|
|
2926
|
+
: new Map();
|
|
2874
2927
|
if (options.updateFooter) {
|
|
2875
2928
|
this.footer.invalidate();
|
|
2876
2929
|
this.updateEditorBorderColor();
|
|
@@ -2908,6 +2961,11 @@ export class InteractiveMode {
|
|
|
2908
2961
|
}
|
|
2909
2962
|
}
|
|
2910
2963
|
}
|
|
2964
|
+
if (message.stopReason !== "aborted" && message.stopReason !== "error") {
|
|
2965
|
+
const miss = cacheMisses.get(message);
|
|
2966
|
+
if (miss)
|
|
2967
|
+
this.addCacheMissNotice(miss);
|
|
2968
|
+
}
|
|
2911
2969
|
}
|
|
2912
2970
|
else if (message.role === "toolResult") {
|
|
2913
2971
|
// Match tool results to pending tool components
|
|
@@ -2942,6 +3000,35 @@ export class InteractiveMode {
|
|
|
2942
3000
|
});
|
|
2943
3001
|
this.renderSessionItems(items, options);
|
|
2944
3002
|
}
|
|
3003
|
+
/**
|
|
3004
|
+
* Show a transcript notice when a completed assistant message paid for a
|
|
3005
|
+
* significant cache miss. Only states observable facts: the miss itself,
|
|
3006
|
+
* a model switch, or an idle gap past the cache TTL.
|
|
3007
|
+
*/
|
|
3008
|
+
maybeShowCacheMissNotice(message) {
|
|
3009
|
+
if (!this.settingsManager.getShowCacheMissNotices())
|
|
3010
|
+
return;
|
|
3011
|
+
// Entries don't contain `message` yet: message_end fires before persistence.
|
|
3012
|
+
const miss = detectCacheMiss(this.sessionManager.getEntries(), message, this.session.modelRegistry);
|
|
3013
|
+
if (miss)
|
|
3014
|
+
this.addCacheMissNotice(miss);
|
|
3015
|
+
}
|
|
3016
|
+
addCacheMissNotice(miss) {
|
|
3017
|
+
if (miss.missedTokens < 20_000 && miss.missedCost < 0.1)
|
|
3018
|
+
return;
|
|
3019
|
+
const cost = miss.missedCost >= 0.01 ? ` (~$${miss.missedCost.toFixed(2)})` : "";
|
|
3020
|
+
const reBilled = `${formatTokens(miss.missedTokens)} tokens re-billed${cost}`;
|
|
3021
|
+
let label = "Cache miss";
|
|
3022
|
+
if (miss.modelChanged) {
|
|
3023
|
+
label = "Cache miss after model switch";
|
|
3024
|
+
}
|
|
3025
|
+
else if (miss.idleMs >= CACHE_TTL_MS) {
|
|
3026
|
+
label = `Cache miss after ${Math.round(miss.idleMs / 60_000)}m idle`;
|
|
3027
|
+
}
|
|
3028
|
+
const text = theme.fg("warning", `${label}: ${reBilled}`);
|
|
3029
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
3030
|
+
this.chatContainer.addChild(new Text(text, 1, 0));
|
|
3031
|
+
}
|
|
2945
3032
|
renderInitialMessages() {
|
|
2946
3033
|
const entries = this.sessionManager.buildContextEntries();
|
|
2947
3034
|
this.renderSessionEntries(entries, {
|
|
@@ -3596,6 +3683,7 @@ export class InteractiveMode {
|
|
|
3596
3683
|
doubleEscapeAction: this.settingsManager.getDoubleEscapeAction(),
|
|
3597
3684
|
treeFilterMode: this.settingsManager.getTreeFilterMode(),
|
|
3598
3685
|
showHardwareCursor: this.settingsManager.getShowHardwareCursor(),
|
|
3686
|
+
showCacheMissNotices: this.settingsManager.getShowCacheMissNotices(),
|
|
3599
3687
|
defaultProjectTrust: this.settingsManager.getDefaultProjectTrust(),
|
|
3600
3688
|
editorPaddingX: this.settingsManager.getEditorPaddingX(),
|
|
3601
3689
|
outputPad: this.settingsManager.getOutputPad(),
|
|
@@ -3672,6 +3760,10 @@ export class InteractiveMode {
|
|
|
3672
3760
|
this.chatContainer.clear();
|
|
3673
3761
|
this.rebuildChatFromMessages();
|
|
3674
3762
|
},
|
|
3763
|
+
onShowCacheMissNoticesChange: (shown) => {
|
|
3764
|
+
this.settingsManager.setShowCacheMissNotices(shown);
|
|
3765
|
+
this.rebuildChatFromMessages();
|
|
3766
|
+
},
|
|
3675
3767
|
onCollapseChangelogChange: (collapsed) => {
|
|
3676
3768
|
this.settingsManager.setCollapseChangelog(collapsed);
|
|
3677
3769
|
},
|
|
@@ -3975,19 +4067,17 @@ export class InteractiveMode {
|
|
|
3975
4067
|
const initialSelectedId = userMessages[userMessages.length - 1]?.entryId;
|
|
3976
4068
|
this.showSelector((done) => {
|
|
3977
4069
|
const selector = new UserMessageSelectorComponent(userMessages.map((m) => ({ id: m.entryId, text: m.text })), async (entryId) => {
|
|
4070
|
+
done();
|
|
3978
4071
|
try {
|
|
3979
4072
|
const result = await this.runtimeHost.fork(entryId);
|
|
3980
4073
|
if (result.cancelled) {
|
|
3981
|
-
done();
|
|
3982
4074
|
this.ui.requestRender();
|
|
3983
4075
|
return;
|
|
3984
4076
|
}
|
|
3985
4077
|
this.editor.setText(result.selectedText ?? "");
|
|
3986
|
-
done();
|
|
3987
4078
|
this.showStatus("Forked to new session");
|
|
3988
4079
|
}
|
|
3989
4080
|
catch (error) {
|
|
3990
|
-
done();
|
|
3991
4081
|
this.showError(error instanceof Error ? error.message : String(error));
|
|
3992
4082
|
}
|
|
3993
4083
|
}, () => {
|
|
@@ -4216,13 +4306,82 @@ export class InteractiveMode {
|
|
|
4216
4306
|
}
|
|
4217
4307
|
return options.sort((a, b) => a.name.localeCompare(b.name));
|
|
4218
4308
|
}
|
|
4219
|
-
|
|
4309
|
+
findLoginProviderOptions(providerRef) {
|
|
4310
|
+
const normalizedProviderRef = providerRef.trim().toLowerCase();
|
|
4311
|
+
if (!normalizedProviderRef) {
|
|
4312
|
+
return [];
|
|
4313
|
+
}
|
|
4314
|
+
return this.getLoginProviderOptions().filter((provider) => provider.id.toLowerCase() === normalizedProviderRef ||
|
|
4315
|
+
provider.name.toLowerCase() === normalizedProviderRef);
|
|
4316
|
+
}
|
|
4317
|
+
async handleLoginCommand(providerRef) {
|
|
4318
|
+
if (!providerRef) {
|
|
4319
|
+
this.showLoginAuthTypeSelector();
|
|
4320
|
+
return;
|
|
4321
|
+
}
|
|
4322
|
+
const providerOptions = this.findLoginProviderOptions(providerRef);
|
|
4323
|
+
if (providerOptions.length === 1) {
|
|
4324
|
+
await this.startProviderLogin(providerOptions[0]);
|
|
4325
|
+
return;
|
|
4326
|
+
}
|
|
4327
|
+
if (providerOptions.length > 1) {
|
|
4328
|
+
const providerIds = new Set(providerOptions.map((provider) => provider.id));
|
|
4329
|
+
if (providerIds.size === 1) {
|
|
4330
|
+
this.showLoginAuthTypeSelector(providerOptions);
|
|
4331
|
+
return;
|
|
4332
|
+
}
|
|
4333
|
+
}
|
|
4334
|
+
this.showLoginProviderSelector(undefined, providerRef);
|
|
4335
|
+
}
|
|
4336
|
+
async startProviderLogin(providerOption) {
|
|
4337
|
+
if (providerOption.authType === "oauth") {
|
|
4338
|
+
await this.showLoginDialog(providerOption.id, providerOption.name);
|
|
4339
|
+
}
|
|
4340
|
+
else if (providerOption.id === BEDROCK_PROVIDER_ID) {
|
|
4341
|
+
this.showBedrockSetupDialog(providerOption.id, providerOption.name);
|
|
4342
|
+
}
|
|
4343
|
+
else {
|
|
4344
|
+
await this.showApiKeyLoginDialog(providerOption.id, providerOption.name);
|
|
4345
|
+
}
|
|
4346
|
+
}
|
|
4347
|
+
showLoginAuthTypeSelector(providerOptions) {
|
|
4220
4348
|
const subscriptionLabel = "Use a subscription";
|
|
4221
4349
|
const apiKeyLabel = "Use an API key";
|
|
4350
|
+
const availableAuthTypes = providerOptions
|
|
4351
|
+
? new Set(providerOptions.map((provider) => provider.authType))
|
|
4352
|
+
: new Set(["oauth", "api_key"]);
|
|
4353
|
+
const options = [];
|
|
4354
|
+
if (availableAuthTypes.has("oauth")) {
|
|
4355
|
+
options.push(subscriptionLabel);
|
|
4356
|
+
}
|
|
4357
|
+
if (availableAuthTypes.has("api_key")) {
|
|
4358
|
+
options.push(apiKeyLabel);
|
|
4359
|
+
}
|
|
4360
|
+
if (options.length === 0) {
|
|
4361
|
+
this.showStatus("No login methods available.");
|
|
4362
|
+
return;
|
|
4363
|
+
}
|
|
4364
|
+
if (providerOptions && options.length === 1) {
|
|
4365
|
+
const providerOption = providerOptions[0];
|
|
4366
|
+
if (providerOption) {
|
|
4367
|
+
void this.startProviderLogin(providerOption);
|
|
4368
|
+
}
|
|
4369
|
+
return;
|
|
4370
|
+
}
|
|
4371
|
+
const title = providerOptions?.[0]
|
|
4372
|
+
? `Select authentication method for ${providerOptions[0].name}:`
|
|
4373
|
+
: "Select authentication method:";
|
|
4222
4374
|
this.showSelector((done) => {
|
|
4223
|
-
const selector = new ExtensionSelectorComponent(
|
|
4375
|
+
const selector = new ExtensionSelectorComponent(title, options, (option) => {
|
|
4224
4376
|
done();
|
|
4225
4377
|
const authType = option === subscriptionLabel ? "oauth" : "api_key";
|
|
4378
|
+
if (providerOptions) {
|
|
4379
|
+
const providerOption = providerOptions.find((provider) => provider.authType === authType);
|
|
4380
|
+
if (providerOption) {
|
|
4381
|
+
void this.startProviderLogin(providerOption);
|
|
4382
|
+
}
|
|
4383
|
+
return;
|
|
4384
|
+
}
|
|
4226
4385
|
this.showLoginProviderSelector(authType);
|
|
4227
4386
|
}, () => {
|
|
4228
4387
|
done();
|
|
@@ -4231,32 +4390,34 @@ export class InteractiveMode {
|
|
|
4231
4390
|
return { component: selector, focus: selector };
|
|
4232
4391
|
});
|
|
4233
4392
|
}
|
|
4234
|
-
showLoginProviderSelector(authType) {
|
|
4393
|
+
showLoginProviderSelector(authType, initialSearchInput) {
|
|
4235
4394
|
const providerOptions = this.getLoginProviderOptions(authType);
|
|
4236
4395
|
if (providerOptions.length === 0) {
|
|
4237
|
-
|
|
4396
|
+
const message = authType === "oauth"
|
|
4397
|
+
? "No subscription providers available."
|
|
4398
|
+
: authType === "api_key"
|
|
4399
|
+
? "No API key providers available."
|
|
4400
|
+
: "No login providers available.";
|
|
4401
|
+
this.showStatus(message);
|
|
4238
4402
|
return;
|
|
4239
4403
|
}
|
|
4240
4404
|
this.showSelector((done) => {
|
|
4241
|
-
const selector = new OAuthSelectorComponent("login", this.session.modelRegistry.authStorage, providerOptions, async (providerId) => {
|
|
4405
|
+
const selector = new OAuthSelectorComponent("login", this.session.modelRegistry.authStorage, providerOptions, async (providerId, selectedAuthType) => {
|
|
4242
4406
|
done();
|
|
4243
|
-
const providerOption = providerOptions.find((provider) => provider.id === providerId);
|
|
4407
|
+
const providerOption = providerOptions.find((provider) => provider.id === providerId && provider.authType === selectedAuthType);
|
|
4244
4408
|
if (!providerOption) {
|
|
4245
4409
|
return;
|
|
4246
4410
|
}
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
this.
|
|
4411
|
+
await this.startProviderLogin(providerOption);
|
|
4412
|
+
}, () => {
|
|
4413
|
+
done();
|
|
4414
|
+
if (authType) {
|
|
4415
|
+
this.showLoginAuthTypeSelector();
|
|
4252
4416
|
}
|
|
4253
4417
|
else {
|
|
4254
|
-
|
|
4418
|
+
this.ui.requestRender();
|
|
4255
4419
|
}
|
|
4256
|
-
}, () =>
|
|
4257
|
-
done();
|
|
4258
|
-
this.showLoginAuthTypeSelector();
|
|
4259
|
-
}, (providerId) => this.session.modelRegistry.getProviderAuthStatus(providerId));
|
|
4420
|
+
}, (providerId) => this.session.modelRegistry.getProviderAuthStatus(providerId), initialSearchInput);
|
|
4260
4421
|
return { component: selector, focus: selector };
|
|
4261
4422
|
});
|
|
4262
4423
|
}
|
|
@@ -4517,7 +4678,7 @@ export class InteractiveMode {
|
|
|
4517
4678
|
const borderColor = (s) => theme.fg("border", s);
|
|
4518
4679
|
reloadBox.addChild(new DynamicBorder(borderColor));
|
|
4519
4680
|
reloadBox.addChild(new Spacer(1));
|
|
4520
|
-
reloadBox.addChild(new Text(theme.fg("muted", "Reloading keybindings, extensions, skills, prompts, themes..."), 1, 0));
|
|
4681
|
+
reloadBox.addChild(new Text(theme.fg("muted", "Reloading keybindings, extensions, skills, prompts, themes, and context files..."), 1, 0));
|
|
4521
4682
|
reloadBox.addChild(new Spacer(1));
|
|
4522
4683
|
reloadBox.addChild(new DynamicBorder(borderColor));
|
|
4523
4684
|
const previousEditor = this.editor;
|
|
@@ -4581,8 +4742,8 @@ export class InteractiveMode {
|
|
|
4581
4742
|
this.showError(`models.json error: ${modelsJsonError}`);
|
|
4582
4743
|
}
|
|
4583
4744
|
this.showStatus(savedImplicitProjectTrust
|
|
4584
|
-
? "Reloaded keybindings, extensions, skills, prompts, themes; saved project trust"
|
|
4585
|
-
: "Reloaded keybindings, extensions, skills, prompts, themes");
|
|
4745
|
+
? "Reloaded keybindings, extensions, skills, prompts, themes, and context files; saved project trust"
|
|
4746
|
+
: "Reloaded keybindings, extensions, skills, prompts, themes, and context files");
|
|
4586
4747
|
dismissReloadBox(this.editor);
|
|
4587
4748
|
reloadBoxDismissed = true;
|
|
4588
4749
|
}
|
|
@@ -4803,7 +4964,70 @@ export class InteractiveMode {
|
|
|
4803
4964
|
handleSessionCommand() {
|
|
4804
4965
|
const stats = this.session.getSessionStats();
|
|
4805
4966
|
const sessionName = this.sessionManager.getSessionName();
|
|
4806
|
-
const
|
|
4967
|
+
const entries = this.sessionManager.getEntries();
|
|
4968
|
+
const cacheWaste = computeCacheWaste(entries, this.session.modelRegistry);
|
|
4969
|
+
// Cost/token totals per provider/model actually used (e.g. OpenRouter `auto`
|
|
4970
|
+
// resolves to a concrete responseModel), sorted by cost descending.
|
|
4971
|
+
const perModelMap = new Map();
|
|
4972
|
+
for (const entry of entries) {
|
|
4973
|
+
if (entry.type !== "message" || entry.message.role !== "assistant")
|
|
4974
|
+
continue;
|
|
4975
|
+
const message = entry.message;
|
|
4976
|
+
const usage = message.usage;
|
|
4977
|
+
const key = `${message.provider}/${message.responseModel ?? message.model}`;
|
|
4978
|
+
let bucket = perModelMap.get(key);
|
|
4979
|
+
if (!bucket) {
|
|
4980
|
+
bucket = { key, cost: 0, tokens: 0 };
|
|
4981
|
+
perModelMap.set(key, bucket);
|
|
4982
|
+
}
|
|
4983
|
+
bucket.cost += usage.cost.total;
|
|
4984
|
+
bucket.tokens += usage.input + usage.output + usage.cacheRead + usage.cacheWrite;
|
|
4985
|
+
}
|
|
4986
|
+
const perModel = Array.from(perModelMap.values()).sort((a, b) => b.cost - a.cost);
|
|
4987
|
+
let info = `${theme.bold("Session Info")}\n\n`;
|
|
4988
|
+
if (sessionName) {
|
|
4989
|
+
info += `${theme.fg("dim", "Name:")} ${sessionName}\n`;
|
|
4990
|
+
}
|
|
4991
|
+
info += `${theme.fg("dim", "File:")} ${stats.sessionFile ?? "In-memory"}\n`;
|
|
4992
|
+
info += `${theme.fg("dim", "ID:")} ${stats.sessionId}\n\n`;
|
|
4993
|
+
info += `${theme.bold("Messages")}\n`;
|
|
4994
|
+
info += `${theme.fg("dim", "Total:")} ${stats.totalMessages}\n`;
|
|
4995
|
+
info += `${theme.fg("dim", "User:")} ${stats.userMessages}\n`;
|
|
4996
|
+
info += `${theme.fg("dim", "Assistant:")} ${stats.assistantMessages}\n`;
|
|
4997
|
+
info += `${theme.fg("dim", "Tools:")} ${stats.toolCalls} calls, ${stats.toolResults} results\n\n`;
|
|
4998
|
+
info += `${theme.bold("Tokens")}\n`;
|
|
4999
|
+
// "Input" is the full prompt volume. With cache activity, split it into
|
|
5000
|
+
// cached (served from cache) vs uncached (everything else) - the only
|
|
5001
|
+
// provider-independent split. Cache writes, where reported, are a detail
|
|
5002
|
+
// of the uncached portion.
|
|
5003
|
+
const { input, cacheRead, cacheWrite } = stats.tokens;
|
|
5004
|
+
const promptTokens = input + cacheRead + cacheWrite;
|
|
5005
|
+
info += `${theme.fg("dim", "Input:")} ${promptTokens.toLocaleString()}\n`;
|
|
5006
|
+
if (promptTokens > 0 && (cacheRead > 0 || cacheWrite > 0)) {
|
|
5007
|
+
const hitRate = theme.fg("dim", `(${((cacheRead / promptTokens) * 100).toFixed(1)}%)`);
|
|
5008
|
+
info += ` ${theme.fg("dim", "Cached:")} ${cacheRead.toLocaleString()} ${hitRate}\n`;
|
|
5009
|
+
const written = cacheWrite > 0 ? ` ${theme.fg("dim", `(${cacheWrite.toLocaleString()} written to cache)`)}` : "";
|
|
5010
|
+
info += ` ${theme.fg("dim", "Uncached:")} ${(input + cacheWrite).toLocaleString()}${written}\n`;
|
|
5011
|
+
}
|
|
5012
|
+
info += `${theme.fg("dim", "Output:")} ${stats.tokens.output.toLocaleString()}\n`;
|
|
5013
|
+
info += `${theme.fg("dim", "Total:")} ${stats.tokens.total.toLocaleString()}\n`;
|
|
5014
|
+
if (stats.cost > 0 || cacheWaste.missedTokens > 0) {
|
|
5015
|
+
info += `\n${theme.bold("Cost")}\n`;
|
|
5016
|
+
info += `${theme.fg("dim", "Total:")} $${stats.cost.toFixed(3)}`;
|
|
5017
|
+
if (perModel.length > 1) {
|
|
5018
|
+
for (const entry of perModel) {
|
|
5019
|
+
info += `\n ${theme.fg("dim", `${entry.key}:`)} $${entry.cost.toFixed(3)} ${theme.fg("dim", `(${formatTokens(entry.tokens)} tokens)`)}`;
|
|
5020
|
+
}
|
|
5021
|
+
}
|
|
5022
|
+
if (cacheWaste.missedTokens > 0) {
|
|
5023
|
+
const missLabel = cacheWaste.missCount === 1 ? "1 miss" : `${cacheWaste.missCount} misses`;
|
|
5024
|
+
const detail = `${cacheWaste.missedTokens.toLocaleString()} tokens, ${missLabel}`;
|
|
5025
|
+
info +=
|
|
5026
|
+
cacheWaste.missedCost >= 0.0001
|
|
5027
|
+
? `\n${theme.fg("dim", "Cache Re-billed:")} $${cacheWaste.missedCost.toFixed(3)} ${theme.fg("dim", `(${detail})`)}`
|
|
5028
|
+
: `\n${theme.fg("dim", "Cache Re-billed:")} ${detail}`;
|
|
5029
|
+
}
|
|
5030
|
+
}
|
|
4807
5031
|
this.chatContainer.addChild(new Spacer(1));
|
|
4808
5032
|
this.chatContainer.addChild(new Text(info, 1, 0));
|
|
4809
5033
|
this.ui.requestRender();
|