@selesai/code 0.5.14 → 0.5.15
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/dist/bun/cli.d.ts.map +1 -1
- package/dist/bun/cli.js +2 -0
- package/dist/bun/cli.js.map +1 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +3 -1
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/list-models.d.ts +2 -2
- package/dist/cli/list-models.d.ts.map +1 -1
- package/dist/cli/list-models.js +3 -3
- package/dist/cli/list-models.js.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -2
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session-auto-handoff.test.d.ts.map +1 -1
- package/dist/core/agent-session-auto-handoff.test.js +2 -2
- package/dist/core/agent-session-auto-handoff.test.js.map +1 -1
- package/dist/core/agent-session-runtime.d.ts.map +1 -1
- package/dist/core/agent-session-runtime.js +3 -0
- package/dist/core/agent-session-runtime.js.map +1 -1
- package/dist/core/agent-session-services.d.ts +3 -6
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +23 -9
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session.d.ts +28 -10
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +136 -90
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage.d.ts +20 -98
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +59 -251
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/built-in-extensions.d.ts +3 -0
- package/dist/core/built-in-extensions.d.ts.map +1 -0
- package/dist/core/built-in-extensions.js +3 -0
- package/dist/core/built-in-extensions.js.map +1 -0
- package/dist/core/cache-stats.d.ts +2 -2
- package/dist/core/cache-stats.d.ts.map +1 -1
- package/dist/core/cache-stats.js +1 -1
- package/dist/core/cache-stats.js.map +1 -1
- package/dist/core/compaction/branch-summarization.d.ts +8 -2
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/core/compaction/branch-summarization.js +8 -11
- package/dist/core/compaction/branch-summarization.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts +19 -3
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +63 -26
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/compaction/utils.d.ts +1 -1
- package/dist/core/compaction/utils.d.ts.map +1 -1
- package/dist/core/compaction/utils.js +6 -17
- package/dist/core/compaction/utils.js.map +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +21 -4
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +4 -1
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +39 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +24 -3
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/extensions/wrapper.d.ts.map +1 -1
- package/dist/core/extensions/wrapper.js +22 -3
- package/dist/core/extensions/wrapper.js.map +1 -1
- package/dist/core/keybindings.d.ts +8 -3
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +7 -3
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/llama/client.d.ts +61 -0
- package/dist/core/llama/client.d.ts.map +1 -0
- package/dist/core/llama/client.js +302 -0
- package/dist/core/llama/client.js.map +1 -0
- package/dist/core/llama/huggingface.d.ts +23 -0
- package/dist/core/llama/huggingface.d.ts.map +1 -0
- package/dist/core/llama/huggingface.js +141 -0
- package/dist/core/llama/huggingface.js.map +1 -0
- package/dist/core/llama/index.d.ts +3 -0
- package/dist/core/llama/index.d.ts.map +1 -0
- package/dist/core/llama/index.js +208 -0
- package/dist/core/llama/index.js.map +1 -0
- package/dist/core/llama/provider.d.ts +10 -0
- package/dist/core/llama/provider.d.ts.map +1 -0
- package/dist/core/llama/provider.js +102 -0
- package/dist/core/llama/provider.js.map +1 -0
- package/dist/core/llama/ui.d.ts +42 -0
- package/dist/core/llama/ui.d.ts.map +1 -0
- package/dist/core/llama/ui.js +416 -0
- package/dist/core/llama/ui.js.map +1 -0
- package/dist/core/model-config.d.ts +513 -0
- package/dist/core/model-config.d.ts.map +1 -0
- package/dist/core/model-config.js +272 -0
- package/dist/core/model-config.js.map +1 -0
- package/dist/core/model-registry.d.ts +20 -126
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +62 -780
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver.d.ts +6 -6
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +21 -18
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/model-runtime.d.ts +83 -0
- package/dist/core/model-runtime.d.ts.map +1 -0
- package/dist/core/model-runtime.js +451 -0
- package/dist/core/model-runtime.js.map +1 -0
- package/dist/core/models-store.d.ts +17 -0
- package/dist/core/models-store.d.ts.map +1 -0
- package/dist/core/models-store.js +45 -0
- package/dist/core/models-store.js.map +1 -0
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +7 -2
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/prompt-templates.d.ts +1 -0
- package/dist/core/prompt-templates.d.ts.map +1 -1
- package/dist/core/prompt-templates.js +4 -4
- package/dist/core/prompt-templates.js.map +1 -1
- package/dist/core/provider-composer.d.ts +55 -0
- package/dist/core/provider-composer.d.ts.map +1 -0
- package/dist/core/provider-composer.js +375 -0
- package/dist/core/provider-composer.js.map +1 -0
- package/dist/core/radius.d.ts +2 -0
- package/dist/core/radius.d.ts.map +1 -0
- package/dist/core/radius.js +2 -0
- package/dist/core/radius.js.map +1 -0
- package/dist/core/remote-catalog-provider.d.ts +5 -0
- package/dist/core/remote-catalog-provider.d.ts.map +1 -0
- package/dist/core/remote-catalog-provider.js +102 -0
- package/dist/core/remote-catalog-provider.js.map +1 -0
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +1 -0
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/runtime-credentials.d.ts +17 -0
- package/dist/core/runtime-credentials.d.ts.map +1 -0
- package/dist/core/runtime-credentials.js +42 -0
- package/dist/core/runtime-credentials.js.map +1 -0
- package/dist/core/sdk.d.ts +4 -7
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +18 -25
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts +12 -7
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +103 -27
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +1 -11
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +1 -1
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/usage-totals.d.ts +19 -0
- package/dist/core/usage-totals.d.ts.map +1 -0
- package/dist/core/usage-totals.js +52 -0
- package/dist/core/usage-totals.js.map +1 -0
- package/dist/extensions/index.d.ts +3 -0
- package/dist/extensions/index.d.ts.map +1 -0
- package/dist/extensions/index.js +9 -0
- package/dist/extensions/index.js.map +1 -0
- package/dist/extensions/llama/client.d.ts +61 -0
- package/dist/extensions/llama/client.d.ts.map +1 -0
- package/dist/extensions/llama/client.js +309 -0
- package/dist/extensions/llama/client.js.map +1 -0
- package/dist/extensions/llama/huggingface.d.ts +23 -0
- package/dist/extensions/llama/huggingface.d.ts.map +1 -0
- package/dist/extensions/llama/huggingface.js +146 -0
- package/dist/extensions/llama/huggingface.js.map +1 -0
- package/dist/extensions/llama/index.d.ts +3 -0
- package/dist/extensions/llama/index.d.ts.map +1 -0
- package/dist/extensions/llama/index.js +211 -0
- package/dist/extensions/llama/index.js.map +1 -0
- package/dist/extensions/llama/provider.d.ts +10 -0
- package/dist/extensions/llama/provider.d.ts.map +1 -0
- package/dist/extensions/llama/provider.js +106 -0
- package/dist/extensions/llama/provider.js.map +1 -0
- package/dist/extensions/llama/ui.d.ts +42 -0
- package/dist/extensions/llama/ui.d.ts.map +1 -0
- package/dist/extensions/llama/ui.js +420 -0
- package/dist/extensions/llama/ui.js.map +1 -0
- package/dist/extensions/tokenin-onboarding.ts +3 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +21 -19
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +22 -10
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-editor.js +1 -1
- package/dist/modes/interactive/components/custom-editor.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +24 -23
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/login-dialog.d.ts +5 -5
- package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
- package/dist/modes/interactive/components/login-dialog.js +17 -8
- package/dist/modes/interactive/components/login-dialog.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts +11 -4
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +72 -41
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.d.ts +4 -4
- package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.js +14 -27
- package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
- package/dist/modes/interactive/components/tree-selector.d.ts +5 -0
- package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/tree-selector.js +49 -12
- package/dist/modes/interactive/components/tree-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +8 -5
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +253 -225
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +4 -0
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +7 -0
- 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 +6 -2
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +11 -0
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/package-manager-cli.d.ts.map +1 -1
- package/dist/package-manager-cli.js +67 -5
- package/dist/package-manager-cli.js.map +1 -1
- package/dist/utils/clipboard-native.d.ts +1 -0
- package/dist/utils/clipboard-native.d.ts.map +1 -1
- package/dist/utils/clipboard-native.js.map +1 -1
- package/dist/utils/clipboard.d.ts +2 -0
- package/dist/utils/clipboard.d.ts.map +1 -1
- package/dist/utils/clipboard.js +13 -0
- package/dist/utils/clipboard.js.map +1 -1
- package/package.json +4 -4
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
import * as fs from "node:fs";
|
|
6
6
|
import * as os from "node:os";
|
|
7
7
|
import * as path from "node:path";
|
|
8
|
-
import { getProviders, } from "@earendil-works/pi-ai/compat";
|
|
9
8
|
import { CombinedAutocompleteProvider, Container, fuzzyFilter, Markdown, matchesKey, ProcessTerminal, Spacer, setKeybindings, Text, TruncatedText, TUI, visibleWidth, } from "@earendil-works/pi-tui";
|
|
10
9
|
import chalk from "chalk";
|
|
11
10
|
import { spawn, spawnSync } from "child_process";
|
|
@@ -19,15 +18,15 @@ import { KeybindingsManager } from "../../core/keybindings.js";
|
|
|
19
18
|
import { createCompactionSummaryMessage } from "../../core/messages.js";
|
|
20
19
|
import { defaultModelPerProvider, findExactModelReferenceMatch, resolveModelScope } from "../../core/model-resolver.js";
|
|
21
20
|
import { DefaultPackageManager } from "../../core/package-manager.js";
|
|
22
|
-
import { BUILT_IN_PROVIDER_DISPLAY_NAMES } from "../../core/provider-display-names.js";
|
|
23
21
|
import { formatMissingSessionCwdPrompt, MissingSessionCwdError } from "../../core/session-cwd.js";
|
|
24
22
|
import { SessionManager, sessionEntryToContextMessages } from "../../core/session-manager.js";
|
|
25
23
|
import { buildGitCommandPrompt, GIT_COMMAND_MODES } from "../../core/git-command.js";
|
|
26
24
|
import { BUILTIN_SLASH_COMMANDS } from "../../core/slash-commands.js";
|
|
27
25
|
import { isInstallTelemetryEnabled } from "../../core/telemetry.js";
|
|
28
26
|
import { hasTrustRequiringProjectResources, ProjectTrustStore } from "../../core/trust-manager.js";
|
|
27
|
+
import { getUsageCostBreakdown } from "../../core/usage-totals.js";
|
|
29
28
|
import { getChangelogPath, getNewEntries, normalizeChangelogLinks, parseChangelog } from "../../utils/changelog.js";
|
|
30
|
-
import { copyToClipboard } from "../../utils/clipboard.js";
|
|
29
|
+
import { copyToClipboard, readClipboardText } from "../../utils/clipboard.js";
|
|
31
30
|
import { extensionForImageMimeType, readClipboardImage } from "../../utils/clipboard-image.js";
|
|
32
31
|
import { parseGitUrl } from "../../utils/git.js";
|
|
33
32
|
import { formatDimensionNote, resizeImage } from "../../utils/image-resize.js";
|
|
@@ -95,7 +94,7 @@ function isDeadTerminalError(error) {
|
|
|
95
94
|
const code = error.code;
|
|
96
95
|
return code !== undefined && DEAD_TERMINAL_ERROR_CODES.has(code);
|
|
97
96
|
}
|
|
98
|
-
const ANTHROPIC_SUBSCRIPTION_AUTH_WARNING = "Anthropic subscription auth is active. Third-party harness usage draws from extra usage and is billed per token, not your Claude plan limits. Manage extra usage at https://claude.ai/settings/usage.";
|
|
97
|
+
const ANTHROPIC_SUBSCRIPTION_AUTH_WARNING = "Anthropic subscription auth is active. Third-party harness usage draws from extra usage and is billed per token, not your Claude plan limits. Manage extra usage at https://claude.ai/settings/usage. Disable this warning in /settings.";
|
|
99
98
|
function isAnthropicSubscriptionAuthKey(apiKey) {
|
|
100
99
|
return typeof apiKey === "string" && apiKey.startsWith("sk-ant-oat");
|
|
101
100
|
}
|
|
@@ -126,17 +125,6 @@ export function formatResumeCommand(sessionManager) {
|
|
|
126
125
|
function hasDefaultModelProvider(providerId) {
|
|
127
126
|
return providerId in defaultModelPerProvider;
|
|
128
127
|
}
|
|
129
|
-
const BEDROCK_PROVIDER_ID = "amazon-bedrock";
|
|
130
|
-
const BUILT_IN_MODEL_PROVIDERS = new Set(getProviders());
|
|
131
|
-
export function isApiKeyLoginProvider(providerId, oauthProviderIds, builtInProviderIds = BUILT_IN_MODEL_PROVIDERS) {
|
|
132
|
-
if (BUILT_IN_PROVIDER_DISPLAY_NAMES[providerId]) {
|
|
133
|
-
return true;
|
|
134
|
-
}
|
|
135
|
-
if (builtInProviderIds.has(providerId)) {
|
|
136
|
-
return false;
|
|
137
|
-
}
|
|
138
|
-
return !oauthProviderIds.has(providerId);
|
|
139
|
-
}
|
|
140
128
|
const AUTH_TYPE_ORDER = { oauth: 0, api_key: 1 };
|
|
141
129
|
function createFuzzyAutocompleteItems(items, prefix, getSearchText, toAutocompleteItem) {
|
|
142
130
|
const filtered = fuzzyFilter(items, prefix, getSearchText);
|
|
@@ -364,11 +352,11 @@ export class InteractiveMode {
|
|
|
364
352
|
}));
|
|
365
353
|
const modelCommand = slashCommands.find((command) => command.name === "model");
|
|
366
354
|
if (modelCommand) {
|
|
367
|
-
modelCommand.getArgumentCompletions = (prefix) => {
|
|
355
|
+
modelCommand.getArgumentCompletions = async (prefix) => {
|
|
368
356
|
// Get available models (scoped or from registry)
|
|
369
357
|
const models = this.session.scopedModels.length > 0
|
|
370
358
|
? this.session.scopedModels.map((s) => s.model)
|
|
371
|
-
: this.session.
|
|
359
|
+
: await this.session.modelRuntime.getAvailable();
|
|
372
360
|
if (models.length === 0)
|
|
373
361
|
return null;
|
|
374
362
|
// Create items with provider/id format
|
|
@@ -544,7 +532,7 @@ export class InteractiveMode {
|
|
|
544
532
|
rawKeyHint("!!", "to run bash (no context)"),
|
|
545
533
|
hint("app.message.followUp", "to queue follow-up"),
|
|
546
534
|
hint("app.message.dequeue", "to edit all queued messages"),
|
|
547
|
-
hint("app.clipboard.pasteImage", "to paste image"),
|
|
535
|
+
hint("app.clipboard.pasteImage", "to paste image (with text fallback)"),
|
|
548
536
|
rawKeyHint("drop files", "to attach"),
|
|
549
537
|
].join("\n");
|
|
550
538
|
const compactInstructions = [
|
|
@@ -604,6 +592,12 @@ export class InteractiveMode {
|
|
|
604
592
|
*/
|
|
605
593
|
async run() {
|
|
606
594
|
await this.init();
|
|
595
|
+
if (!process.env.PI_OFFLINE) {
|
|
596
|
+
void this.session.modelRuntime
|
|
597
|
+
.refresh()
|
|
598
|
+
.then(() => this.updateAvailableProviderCount())
|
|
599
|
+
.catch(() => { });
|
|
600
|
+
}
|
|
607
601
|
// Start version check asynchronously
|
|
608
602
|
checkForNewPiVersion(this.version, PACKAGE_NAME).then((newRelease) => {
|
|
609
603
|
if (newRelease) {
|
|
@@ -611,10 +605,18 @@ export class InteractiveMode {
|
|
|
611
605
|
}
|
|
612
606
|
});
|
|
613
607
|
// Start package update check asynchronously
|
|
614
|
-
this.checkForPackageUpdates()
|
|
608
|
+
this.checkForPackageUpdates()
|
|
609
|
+
.then((updates) => {
|
|
615
610
|
if (updates.length > 0) {
|
|
616
611
|
this.showPackageUpdateNotification(updates);
|
|
617
612
|
}
|
|
613
|
+
})
|
|
614
|
+
.finally(() => {
|
|
615
|
+
// On Windows, npm can overwrite the shared console title while checking
|
|
616
|
+
// extension package versions. Restore Pi's title after the startup check.
|
|
617
|
+
if (process.platform === "win32" && this.isInitialized) {
|
|
618
|
+
this.updateTerminalTitle();
|
|
619
|
+
}
|
|
618
620
|
});
|
|
619
621
|
// Check tmux keyboard setup asynchronously
|
|
620
622
|
this.checkTmuxKeyboardSetup().then((warning) => {
|
|
@@ -627,7 +629,7 @@ export class InteractiveMode {
|
|
|
627
629
|
if (migratedProviders && migratedProviders.length > 0) {
|
|
628
630
|
this.showWarning(`Migrated credentials to auth.json: ${migratedProviders.join(", ")}`);
|
|
629
631
|
}
|
|
630
|
-
const modelsJsonError = this.session.
|
|
632
|
+
const modelsJsonError = this.session.modelRuntime.getError();
|
|
631
633
|
if (modelsJsonError) {
|
|
632
634
|
this.showError(`models.json error: ${modelsJsonError}`);
|
|
633
635
|
}
|
|
@@ -1079,7 +1081,10 @@ export class InteractiveMode {
|
|
|
1079
1081
|
const promptsResult = this.session.resourceLoader.getPrompts();
|
|
1080
1082
|
const themesResult = this.session.resourceLoader.getThemes();
|
|
1081
1083
|
const extensions = options?.extensions ??
|
|
1082
|
-
this.session.resourceLoader
|
|
1084
|
+
this.session.resourceLoader
|
|
1085
|
+
.getExtensions()
|
|
1086
|
+
.extensions.filter((extension) => !extension.hidden)
|
|
1087
|
+
.map((extension) => ({
|
|
1083
1088
|
path: extension.path,
|
|
1084
1089
|
sourceInfo: extension.sourceInfo,
|
|
1085
1090
|
}));
|
|
@@ -1358,7 +1363,7 @@ export class InteractiveMode {
|
|
|
1358
1363
|
hasUI: true,
|
|
1359
1364
|
cwd: this.sessionManager.getCwd(),
|
|
1360
1365
|
sessionManager: this.sessionManager,
|
|
1361
|
-
modelRegistry:
|
|
1366
|
+
modelRegistry: extensionRunner.getModelRegistry(),
|
|
1362
1367
|
model: this.session.model,
|
|
1363
1368
|
isIdle: () => this.session.isIdle,
|
|
1364
1369
|
isProjectTrusted: () => this.settingsManager.isProjectTrusted(),
|
|
@@ -2046,6 +2051,7 @@ export class InteractiveMode {
|
|
|
2046
2051
|
this.defaultEditor.onAction("app.tools.expand", () => this.toggleToolOutputExpansion());
|
|
2047
2052
|
this.defaultEditor.onAction("app.thinking.toggle", () => this.toggleThinkingBlockVisibility());
|
|
2048
2053
|
this.defaultEditor.onAction("app.editor.external", () => this.openExternalEditor());
|
|
2054
|
+
this.defaultEditor.onAction("app.message.copy", () => void this.handleCopyCommand());
|
|
2049
2055
|
this.defaultEditor.onAction("app.message.followUp", () => this.handleFollowUp());
|
|
2050
2056
|
this.defaultEditor.onAction("app.message.dequeue", () => this.handleDequeue());
|
|
2051
2057
|
this.defaultEditor.onAction("app.session.new", () => this.handleClearCommand());
|
|
@@ -2059,59 +2065,51 @@ export class InteractiveMode {
|
|
|
2059
2065
|
this.updateEditorBorderColor();
|
|
2060
2066
|
}
|
|
2061
2067
|
};
|
|
2062
|
-
// Handle clipboard
|
|
2068
|
+
// Handle clipboard paste (triggered on Ctrl+V). Images are attached by path;
|
|
2069
|
+
// otherwise, paste plain text from the system clipboard.
|
|
2063
2070
|
this.defaultEditor.onPasteImage = () => {
|
|
2064
|
-
this.
|
|
2071
|
+
void this.handleClipboardPaste();
|
|
2065
2072
|
};
|
|
2066
2073
|
}
|
|
2067
|
-
async
|
|
2074
|
+
async handleClipboardPaste() {
|
|
2068
2075
|
try {
|
|
2069
2076
|
const image = await readClipboardImage();
|
|
2070
|
-
if (
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
type: "image",
|
|
2088
|
-
|
|
2089
|
-
data: Buffer.from(image.bytes).toString("base64"),
|
|
2090
|
-
};
|
|
2077
|
+
if (image) {
|
|
2078
|
+
const tmpDir = os.tmpdir();
|
|
2079
|
+
const ext = extensionForImageMimeType(image.mimeType) ?? "png";
|
|
2080
|
+
const fileName = `selesai-clipboard-${Date.now()}-${this.pendingInputImages.length + 1}.${ext}`;
|
|
2081
|
+
const filePath = path.join(tmpDir, fileName);
|
|
2082
|
+
fs.writeFileSync(filePath, Buffer.from(image.bytes));
|
|
2083
|
+
let attachment;
|
|
2084
|
+
let dimensionNote;
|
|
2085
|
+
const autoResizeImages = this.settingsManager.getImageAutoResize();
|
|
2086
|
+
if (autoResizeImages) {
|
|
2087
|
+
const resized = await resizeImage(image.bytes, image.mimeType).catch(() => null);
|
|
2088
|
+
if (!resized) {
|
|
2089
|
+
this.showWarning("Clipboard image attached without resize");
|
|
2090
|
+
attachment = { type: "image", mimeType: image.mimeType, data: Buffer.from(image.bytes).toString("base64") };
|
|
2091
|
+
}
|
|
2092
|
+
else {
|
|
2093
|
+
dimensionNote = formatDimensionNote(resized);
|
|
2094
|
+
attachment = { type: "image", mimeType: resized.mimeType, data: resized.data };
|
|
2095
|
+
}
|
|
2091
2096
|
}
|
|
2092
2097
|
else {
|
|
2093
|
-
|
|
2094
|
-
attachment = {
|
|
2095
|
-
type: "image",
|
|
2096
|
-
mimeType: resized.mimeType,
|
|
2097
|
-
data: resized.data,
|
|
2098
|
-
};
|
|
2098
|
+
attachment = { type: "image", mimeType: image.mimeType, data: Buffer.from(image.bytes).toString("base64") };
|
|
2099
2099
|
}
|
|
2100
|
+
this.pendingInputImages.push(attachment);
|
|
2101
|
+
this.editor.insertTextAtCursor?.(dimensionNote ? `${filePath} ${dimensionNote}` : filePath);
|
|
2102
|
+
this.ui.requestRender();
|
|
2103
|
+
return;
|
|
2100
2104
|
}
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
data: Buffer.from(image.bytes).toString("base64"),
|
|
2106
|
-
};
|
|
2105
|
+
const text = await readClipboardText();
|
|
2106
|
+
if (text) {
|
|
2107
|
+
this.editor.insertTextAtCursor?.(text);
|
|
2108
|
+
this.ui.requestRender();
|
|
2107
2109
|
}
|
|
2108
|
-
this.pendingInputImages.push(attachment);
|
|
2109
|
-
const label = dimensionNote ? `${filePath} ${dimensionNote}` : filePath;
|
|
2110
|
-
this.editor.insertTextAtCursor?.(label);
|
|
2111
|
-
this.ui.requestRender();
|
|
2112
2110
|
}
|
|
2113
2111
|
catch (error) {
|
|
2114
|
-
this.showWarning(`Failed to paste
|
|
2112
|
+
this.showWarning(`Failed to paste clipboard: ${error instanceof Error ? error.message : String(error)}`);
|
|
2115
2113
|
}
|
|
2116
2114
|
}
|
|
2117
2115
|
consumePendingInputImages() {
|
|
@@ -2583,6 +2581,28 @@ export class InteractiveMode {
|
|
|
2583
2581
|
this.ui.requestRender();
|
|
2584
2582
|
break;
|
|
2585
2583
|
}
|
|
2584
|
+
case "summarization_retry_scheduled": {
|
|
2585
|
+
this.showError(event.errorMessage);
|
|
2586
|
+
this.showStatusIndicator(new RetryStatusIndicator(this.ui, event.attempt, event.maxAttempts, event.delayMs));
|
|
2587
|
+
this.ui.requestRender();
|
|
2588
|
+
break;
|
|
2589
|
+
}
|
|
2590
|
+
case "summarization_retry_attempt_start": {
|
|
2591
|
+
this.clearStatusIndicator("retry");
|
|
2592
|
+
if (event.source === "branchSummary") {
|
|
2593
|
+
this.showStatusIndicator(new BranchSummaryStatusIndicator(this.ui));
|
|
2594
|
+
}
|
|
2595
|
+
else {
|
|
2596
|
+
this.showStatusIndicator(new CompactionStatusIndicator(this.ui, event.reason));
|
|
2597
|
+
}
|
|
2598
|
+
this.ui.requestRender();
|
|
2599
|
+
break;
|
|
2600
|
+
}
|
|
2601
|
+
case "summarization_retry_finished": {
|
|
2602
|
+
this.clearStatusIndicator("retry");
|
|
2603
|
+
this.ui.requestRender();
|
|
2604
|
+
break;
|
|
2605
|
+
}
|
|
2586
2606
|
}
|
|
2587
2607
|
}
|
|
2588
2608
|
/** Extract text content from a user message */
|
|
@@ -2719,7 +2739,7 @@ export class InteractiveMode {
|
|
|
2719
2739
|
// Cache-miss notices are not persisted; re-derive them from the full entry
|
|
2720
2740
|
// list and re-inject them after the assistant messages that paid for them.
|
|
2721
2741
|
const cacheMisses = this.settingsManager.getShowCacheMissNotices()
|
|
2722
|
-
? collectCacheMisses(this.sessionManager.getEntries(), this.session.
|
|
2742
|
+
? collectCacheMisses(this.sessionManager.getEntries(), this.session.modelRuntime)
|
|
2723
2743
|
: new Map();
|
|
2724
2744
|
if (options.updateFooter) {
|
|
2725
2745
|
this.footer.invalidate();
|
|
@@ -2810,7 +2830,7 @@ export class InteractiveMode {
|
|
|
2810
2830
|
if (!this.settingsManager.getShowCacheMissNotices())
|
|
2811
2831
|
return;
|
|
2812
2832
|
// Entries don't contain `message` yet: message_end fires before persistence.
|
|
2813
|
-
const miss = detectCacheMiss(this.sessionManager.getEntries(), message, this.session.
|
|
2833
|
+
const miss = detectCacheMiss(this.sessionManager.getEntries(), message, this.session.modelRuntime);
|
|
2814
2834
|
if (miss)
|
|
2815
2835
|
this.addCacheMissNotice(miss);
|
|
2816
2836
|
}
|
|
@@ -3388,8 +3408,10 @@ export class InteractiveMode {
|
|
|
3388
3408
|
for (const message of preCommands) {
|
|
3389
3409
|
await this.session.prompt(message.text, { images: message.images });
|
|
3390
3410
|
}
|
|
3391
|
-
//
|
|
3392
|
-
const promptPromise = this.session
|
|
3411
|
+
// Start a prompt when idle, or queue it into a run still finishing compaction.
|
|
3412
|
+
const promptPromise = this.session
|
|
3413
|
+
.prompt(firstPrompt.text, { streamingBehavior: firstPrompt.mode, images: firstPrompt.images })
|
|
3414
|
+
.catch((error) => {
|
|
3393
3415
|
restoreQueue(error);
|
|
3394
3416
|
});
|
|
3395
3417
|
// Queue remaining messages
|
|
@@ -3654,18 +3676,20 @@ export class InteractiveMode {
|
|
|
3654
3676
|
if (this.session.scopedModels.length > 0) {
|
|
3655
3677
|
return this.session.scopedModels.map((scoped) => scoped.model);
|
|
3656
3678
|
}
|
|
3657
|
-
this.session.modelRegistry.refresh();
|
|
3658
3679
|
try {
|
|
3659
|
-
|
|
3680
|
+
await this.session.modelRuntime.refresh();
|
|
3681
|
+
return [...(await this.session.modelRuntime.getAvailable())];
|
|
3660
3682
|
}
|
|
3661
3683
|
catch {
|
|
3662
3684
|
return [];
|
|
3663
3685
|
}
|
|
3664
3686
|
}
|
|
3665
|
-
/** Update the footer's available provider count from current
|
|
3666
|
-
|
|
3667
|
-
const models =
|
|
3668
|
-
|
|
3687
|
+
/** Update the footer's available provider count from the current snapshot without refreshing catalogs. */
|
|
3688
|
+
updateAvailableProviderCount() {
|
|
3689
|
+
const models = this.session.scopedModels.length > 0
|
|
3690
|
+
? this.session.scopedModels.map((scoped) => scoped.model)
|
|
3691
|
+
: this.session.modelRuntime.getAvailableSnapshot();
|
|
3692
|
+
const uniqueProviders = new Set(models.map((model) => model.provider));
|
|
3669
3693
|
this.footerDataProvider.setAvailableProviderCount(uniqueProviders.size);
|
|
3670
3694
|
}
|
|
3671
3695
|
async maybeWarnAboutAnthropicSubscriptionAuth(model = this.session.model) {
|
|
@@ -3678,14 +3702,13 @@ export class InteractiveMode {
|
|
|
3678
3702
|
if (!model || model.provider !== "anthropic") {
|
|
3679
3703
|
return;
|
|
3680
3704
|
}
|
|
3681
|
-
const storedCredential = this.session.modelRegistry.authStorage.get("anthropic");
|
|
3682
|
-
if (storedCredential?.type === "oauth") {
|
|
3683
|
-
this.anthropicSubscriptionWarningShown = true;
|
|
3684
|
-
this.showWarning(ANTHROPIC_SUBSCRIPTION_AUTH_WARNING);
|
|
3685
|
-
return;
|
|
3686
|
-
}
|
|
3687
3705
|
try {
|
|
3688
|
-
|
|
3706
|
+
if ((await this.session.modelRuntime.checkAuth("anthropic"))?.type === "oauth") {
|
|
3707
|
+
this.anthropicSubscriptionWarningShown = true;
|
|
3708
|
+
this.showWarning(ANTHROPIC_SUBSCRIPTION_AUTH_WARNING);
|
|
3709
|
+
return;
|
|
3710
|
+
}
|
|
3711
|
+
const apiKey = (await this.session.modelRuntime.getAuth(model.provider))?.auth.apiKey;
|
|
3689
3712
|
if (!isAnthropicSubscriptionAuthKey(apiKey)) {
|
|
3690
3713
|
return;
|
|
3691
3714
|
}
|
|
@@ -3743,7 +3766,7 @@ export class InteractiveMode {
|
|
|
3743
3766
|
}
|
|
3744
3767
|
showModelSelector(initialSearchInput) {
|
|
3745
3768
|
this.showSelector((done) => {
|
|
3746
|
-
const selector = new ModelSelectorComponent(this.ui, this.session.model, this.settingsManager, this.session.
|
|
3769
|
+
const selector = new ModelSelectorComponent(this.ui, this.session.model, this.settingsManager, this.session.modelRuntime, this.session.scopedModels, async (model) => {
|
|
3747
3770
|
try {
|
|
3748
3771
|
await this.session.setModel(model);
|
|
3749
3772
|
this.footer.invalidate();
|
|
@@ -3766,8 +3789,8 @@ export class InteractiveMode {
|
|
|
3766
3789
|
}
|
|
3767
3790
|
async showModelsSelector() {
|
|
3768
3791
|
// Get all available models
|
|
3769
|
-
this.session.
|
|
3770
|
-
const allModels = this.session.
|
|
3792
|
+
await this.session.modelRuntime.refresh();
|
|
3793
|
+
const allModels = [...(await this.session.modelRuntime.getAvailable())];
|
|
3771
3794
|
if (allModels.length === 0) {
|
|
3772
3795
|
this.showStatus("No models available");
|
|
3773
3796
|
return;
|
|
@@ -3785,7 +3808,7 @@ export class InteractiveMode {
|
|
|
3785
3808
|
// Fall back to settings
|
|
3786
3809
|
const patterns = this.settingsManager.getEnabledModels();
|
|
3787
3810
|
if (patterns !== undefined && patterns.length > 0) {
|
|
3788
|
-
const scopedModels = await resolveModelScope(patterns, this.session.
|
|
3811
|
+
const scopedModels = await resolveModelScope(patterns, this.session.modelRuntime);
|
|
3789
3812
|
currentEnabledIds = scopedModels.map((scoped) => `${scoped.model.provider}/${scoped.model.id}`);
|
|
3790
3813
|
}
|
|
3791
3814
|
}
|
|
@@ -3793,7 +3816,7 @@ export class InteractiveMode {
|
|
|
3793
3816
|
const updateSessionModels = async (enabledIds) => {
|
|
3794
3817
|
currentEnabledIds = enabledIds === null ? null : [...enabledIds];
|
|
3795
3818
|
if (enabledIds && enabledIds.length > 0 && enabledIds.length < allModels.length) {
|
|
3796
|
-
const newScopedModels = await resolveModelScope(enabledIds, this.session.
|
|
3819
|
+
const newScopedModels = await resolveModelScope(enabledIds, this.session.modelRuntime);
|
|
3797
3820
|
this.session.setScopedModels(newScopedModels.map((sm) => ({
|
|
3798
3821
|
model: sm.model,
|
|
3799
3822
|
thinkingLevel: sm.thinkingLevel,
|
|
@@ -3976,6 +3999,19 @@ export class InteractiveMode {
|
|
|
3976
3999
|
this.sessionManager.appendLabelChange(entryId, label);
|
|
3977
4000
|
this.ui.requestRender();
|
|
3978
4001
|
}, initialSelectedId, initialFilterMode);
|
|
4002
|
+
selector.onCopy = async (text) => {
|
|
4003
|
+
if (!text) {
|
|
4004
|
+
this.showError("Selected entry has no text to copy");
|
|
4005
|
+
return;
|
|
4006
|
+
}
|
|
4007
|
+
try {
|
|
4008
|
+
await copyToClipboard(text);
|
|
4009
|
+
this.showStatus("Copied selected message to clipboard");
|
|
4010
|
+
}
|
|
4011
|
+
catch (error) {
|
|
4012
|
+
this.showError(error instanceof Error ? error.message : String(error));
|
|
4013
|
+
}
|
|
4014
|
+
};
|
|
3979
4015
|
return { component: selector, focus: selector };
|
|
3980
4016
|
});
|
|
3981
4017
|
}
|
|
@@ -4040,44 +4076,46 @@ export class InteractiveMode {
|
|
|
4040
4076
|
}
|
|
4041
4077
|
}
|
|
4042
4078
|
getLoginProviderOptions(authType) {
|
|
4043
|
-
const authStorage = this.session.modelRegistry.authStorage;
|
|
4044
|
-
const oauthProviders = authStorage.getOAuthProviders();
|
|
4045
|
-
const oauthProviderIds = new Set(oauthProviders.map((provider) => provider.id));
|
|
4046
|
-
const options = oauthProviders.map((provider) => ({
|
|
4047
|
-
id: provider.id,
|
|
4048
|
-
name: provider.name,
|
|
4049
|
-
authType: "oauth",
|
|
4050
|
-
}));
|
|
4051
|
-
const modelProviders = new Set(this.session.modelRegistry.getAll().map((model) => model.provider));
|
|
4052
|
-
for (const providerId of modelProviders) {
|
|
4053
|
-
if (!isApiKeyLoginProvider(providerId, oauthProviderIds)) {
|
|
4054
|
-
continue;
|
|
4055
|
-
}
|
|
4056
|
-
options.push({
|
|
4057
|
-
id: providerId,
|
|
4058
|
-
name: this.session.modelRegistry.getProviderDisplayName(providerId),
|
|
4059
|
-
authType: "api_key",
|
|
4060
|
-
});
|
|
4061
|
-
}
|
|
4062
|
-
const filteredOptions = authType ? options.filter((option) => option.authType === authType) : options;
|
|
4063
|
-
return filteredOptions.sort((a, b) => a.name.localeCompare(b.name));
|
|
4064
|
-
}
|
|
4065
|
-
getLogoutProviderOptions() {
|
|
4066
|
-
const authStorage = this.session.modelRegistry.authStorage;
|
|
4067
4079
|
const options = [];
|
|
4068
|
-
for (const
|
|
4069
|
-
const
|
|
4070
|
-
|
|
4071
|
-
|
|
4080
|
+
for (const provider of this.session.modelRuntime.getProviders()) {
|
|
4081
|
+
const authStatus = this.session.modelRuntime.getProviderAuthStatus(provider.id);
|
|
4082
|
+
const status = authStatus.configured
|
|
4083
|
+
? {
|
|
4084
|
+
type: this.session.modelRuntime.isUsingOAuth(provider.id) ? "oauth" : "api_key",
|
|
4085
|
+
source: authStatus.label ?? authStatus.source,
|
|
4086
|
+
}
|
|
4087
|
+
: undefined;
|
|
4088
|
+
if ((!authType || authType === "oauth") && provider.auth.oauth) {
|
|
4089
|
+
options.push({
|
|
4090
|
+
id: provider.id,
|
|
4091
|
+
name: provider.name,
|
|
4092
|
+
authType: "oauth",
|
|
4093
|
+
method: provider.auth.oauth,
|
|
4094
|
+
status,
|
|
4095
|
+
});
|
|
4096
|
+
}
|
|
4097
|
+
if ((!authType || authType === "api_key") && provider.auth.apiKey) {
|
|
4098
|
+
options.push({
|
|
4099
|
+
id: provider.id,
|
|
4100
|
+
name: provider.name,
|
|
4101
|
+
authType: "api_key",
|
|
4102
|
+
method: provider.auth.apiKey,
|
|
4103
|
+
status,
|
|
4104
|
+
});
|
|
4072
4105
|
}
|
|
4073
|
-
options.push({
|
|
4074
|
-
id: providerId,
|
|
4075
|
-
name: this.session.modelRegistry.getProviderDisplayName(providerId),
|
|
4076
|
-
authType: credential.type,
|
|
4077
|
-
});
|
|
4078
4106
|
}
|
|
4079
4107
|
return options.sort((a, b) => a.name.localeCompare(b.name));
|
|
4080
4108
|
}
|
|
4109
|
+
async getLogoutProviderOptions() {
|
|
4110
|
+
return (await this.session.modelRuntime.listCredentials())
|
|
4111
|
+
.map(({ providerId, type }) => ({
|
|
4112
|
+
id: providerId,
|
|
4113
|
+
name: this.session.modelRuntime.getProvider(providerId)?.name ?? providerId,
|
|
4114
|
+
authType: type,
|
|
4115
|
+
status: { type, source: "stored credential" },
|
|
4116
|
+
}))
|
|
4117
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
4118
|
+
}
|
|
4081
4119
|
findLoginProviderOptions(providerRef) {
|
|
4082
4120
|
const normalizedProviderRef = providerRef.trim().toLowerCase();
|
|
4083
4121
|
if (!normalizedProviderRef) {
|
|
@@ -4087,6 +4125,7 @@ export class InteractiveMode {
|
|
|
4087
4125
|
provider.name.toLowerCase() === normalizedProviderRef);
|
|
4088
4126
|
}
|
|
4089
4127
|
async handleLoginCommand(providerRef) {
|
|
4128
|
+
await this.session.modelRuntime.getAvailable();
|
|
4090
4129
|
if (!providerRef) {
|
|
4091
4130
|
this.showLoginAuthTypeSelector();
|
|
4092
4131
|
return;
|
|
@@ -4109,16 +4148,18 @@ export class InteractiveMode {
|
|
|
4109
4148
|
if (providerOption.authType === "oauth") {
|
|
4110
4149
|
await this.showLoginDialog(providerOption.id, providerOption.name);
|
|
4111
4150
|
}
|
|
4112
|
-
else if (providerOption.
|
|
4113
|
-
this.
|
|
4151
|
+
else if (providerOption.method?.login) {
|
|
4152
|
+
await this.showApiKeyLoginDialog(providerOption.id, providerOption.name);
|
|
4114
4153
|
}
|
|
4115
4154
|
else {
|
|
4116
|
-
|
|
4155
|
+
this.showAmbientAuthDialog(providerOption);
|
|
4117
4156
|
}
|
|
4118
4157
|
}
|
|
4119
4158
|
showLoginAuthTypeSelector(providerOptions) {
|
|
4120
|
-
const
|
|
4121
|
-
const
|
|
4159
|
+
const oauthProvider = providerOptions?.find((provider) => provider.authType === "oauth");
|
|
4160
|
+
const oauthLoginLabel = oauthProvider?.method && "loginLabel" in oauthProvider.method ? oauthProvider.method.loginLabel : undefined;
|
|
4161
|
+
const subscriptionLabel = oauthLoginLabel ?? "Sign in with an account";
|
|
4162
|
+
const apiKeyLabel = "Sign in with an API key";
|
|
4122
4163
|
const availableAuthTypes = providerOptions
|
|
4123
4164
|
? new Set(providerOptions.map((provider) => provider.authType))
|
|
4124
4165
|
: new Set(["oauth", "api_key"]);
|
|
@@ -4174,7 +4215,7 @@ export class InteractiveMode {
|
|
|
4174
4215
|
return;
|
|
4175
4216
|
}
|
|
4176
4217
|
this.showSelector((done) => {
|
|
4177
|
-
const selector = new OAuthSelectorComponent("login",
|
|
4218
|
+
const selector = new OAuthSelectorComponent("login", providerOptions, async (providerId, selectedAuthType) => {
|
|
4178
4219
|
done();
|
|
4179
4220
|
const providerOption = providerOptions.find((provider) => provider.id === providerId && provider.authType === selectedAuthType);
|
|
4180
4221
|
if (!providerOption) {
|
|
@@ -4189,7 +4230,7 @@ export class InteractiveMode {
|
|
|
4189
4230
|
else {
|
|
4190
4231
|
this.ui.requestRender();
|
|
4191
4232
|
}
|
|
4192
|
-
},
|
|
4233
|
+
}, initialSearchInput);
|
|
4193
4234
|
return { component: selector, focus: selector };
|
|
4194
4235
|
});
|
|
4195
4236
|
}
|
|
@@ -4198,21 +4239,20 @@ export class InteractiveMode {
|
|
|
4198
4239
|
this.showLoginAuthTypeSelector();
|
|
4199
4240
|
return;
|
|
4200
4241
|
}
|
|
4201
|
-
const providerOptions = this.getLogoutProviderOptions();
|
|
4242
|
+
const providerOptions = await this.getLogoutProviderOptions();
|
|
4202
4243
|
if (providerOptions.length === 0) {
|
|
4203
4244
|
this.showStatus("No stored credentials to remove. /logout only removes credentials saved by /login; environment variables and models.json config are unchanged.");
|
|
4204
4245
|
return;
|
|
4205
4246
|
}
|
|
4206
4247
|
this.showSelector((done) => {
|
|
4207
|
-
const selector = new OAuthSelectorComponent(mode,
|
|
4248
|
+
const selector = new OAuthSelectorComponent(mode, providerOptions, async (providerId) => {
|
|
4208
4249
|
done();
|
|
4209
4250
|
const providerOption = providerOptions.find((provider) => provider.id === providerId);
|
|
4210
4251
|
if (!providerOption) {
|
|
4211
4252
|
return;
|
|
4212
4253
|
}
|
|
4213
4254
|
try {
|
|
4214
|
-
this.session.
|
|
4215
|
-
this.session.modelRegistry.refresh();
|
|
4255
|
+
await this.session.modelRuntime.logout(providerOption.id);
|
|
4216
4256
|
await this.updateAvailableProviderCount();
|
|
4217
4257
|
const message = providerOption.authType === "oauth"
|
|
4218
4258
|
? `Logged out of ${providerOption.name}`
|
|
@@ -4230,12 +4270,12 @@ export class InteractiveMode {
|
|
|
4230
4270
|
});
|
|
4231
4271
|
}
|
|
4232
4272
|
async completeProviderAuthentication(providerId, providerName, authType, previousModel) {
|
|
4233
|
-
this.session.
|
|
4273
|
+
await this.session.modelRuntime.getAvailable();
|
|
4234
4274
|
const actionLabel = authType === "oauth" ? `Logged in to ${providerName}` : `Saved API key for ${providerName}`;
|
|
4235
4275
|
let selectedModel;
|
|
4236
4276
|
let selectionError;
|
|
4237
4277
|
if (isUnknownModel(previousModel)) {
|
|
4238
|
-
const availableModels = this.session.
|
|
4278
|
+
const availableModels = await this.session.modelRuntime.getAvailable();
|
|
4239
4279
|
const providerModels = availableModels.filter((model) => model.provider === providerId);
|
|
4240
4280
|
if (!hasDefaultModelProvider(providerId)) {
|
|
4241
4281
|
selectionError = `${actionLabel}, but no default model is configured for provider "${providerId}". Use /model to select a model.`;
|
|
@@ -4279,20 +4319,15 @@ export class InteractiveMode {
|
|
|
4279
4319
|
}
|
|
4280
4320
|
}
|
|
4281
4321
|
}
|
|
4282
|
-
|
|
4322
|
+
showAmbientAuthDialog(providerOption) {
|
|
4283
4323
|
const restoreEditor = () => {
|
|
4284
4324
|
this.editorContainer.clear();
|
|
4285
4325
|
this.editorContainer.addChild(this.editor);
|
|
4286
4326
|
this.ui.setFocus(this.editor);
|
|
4287
4327
|
this.ui.requestRender();
|
|
4288
4328
|
};
|
|
4289
|
-
const dialog = new LoginDialogComponent(this.ui,
|
|
4290
|
-
dialog.showInfo([
|
|
4291
|
-
theme.fg("text", "Amazon Bedrock uses AWS credentials instead of a single API key."),
|
|
4292
|
-
theme.fg("text", "Configure an AWS profile, IAM keys, bearer token, or role-based credentials."),
|
|
4293
|
-
theme.fg("muted", "See:"),
|
|
4294
|
-
theme.fg("accent", ` ${path.join(getDocsPath(), "providers.md")}`),
|
|
4295
|
-
]);
|
|
4329
|
+
const dialog = new LoginDialogComponent(this.ui, providerOption.id, () => restoreEditor(), providerOption.name, `${providerOption.name} setup`);
|
|
4330
|
+
dialog.showInfo(`${providerOption.method?.name ?? "Authentication"} is configured outside pi.`, [], true);
|
|
4296
4331
|
this.editorContainer.clear();
|
|
4297
4332
|
this.editorContainer.addChild(dialog);
|
|
4298
4333
|
this.ui.setFocus(dialog);
|
|
@@ -4303,6 +4338,13 @@ export class InteractiveMode {
|
|
|
4303
4338
|
const dialog = new LoginDialogComponent(this.ui, providerId, (_success, _message) => {
|
|
4304
4339
|
// Completion handled below
|
|
4305
4340
|
}, providerName);
|
|
4341
|
+
if (providerId === "amazon-bedrock") {
|
|
4342
|
+
dialog.showDetails([
|
|
4343
|
+
theme.fg("text", "You can also use an AWS profile, IAM keys, or role-based credentials."),
|
|
4344
|
+
theme.fg("muted", "See:"),
|
|
4345
|
+
theme.fg("accent", ` ${path.join(getDocsPath(), "providers.md")}`),
|
|
4346
|
+
]);
|
|
4347
|
+
}
|
|
4306
4348
|
this.editorContainer.clear();
|
|
4307
4349
|
this.editorContainer.addChild(dialog);
|
|
4308
4350
|
this.ui.setFocus(dialog);
|
|
@@ -4314,11 +4356,7 @@ export class InteractiveMode {
|
|
|
4314
4356
|
this.ui.requestRender();
|
|
4315
4357
|
};
|
|
4316
4358
|
try {
|
|
4317
|
-
|
|
4318
|
-
if (!apiKey) {
|
|
4319
|
-
throw new Error("API key cannot be empty.");
|
|
4320
|
-
}
|
|
4321
|
-
this.session.modelRegistry.authStorage.set(providerId, { type: "api_key", key: apiKey });
|
|
4359
|
+
await this.loginProvider(dialog, providerId, "api_key");
|
|
4322
4360
|
restoreEditor();
|
|
4323
4361
|
await this.completeProviderAuthentication(providerId, providerName, "api_key", previousModel);
|
|
4324
4362
|
}
|
|
@@ -4330,8 +4368,8 @@ export class InteractiveMode {
|
|
|
4330
4368
|
}
|
|
4331
4369
|
}
|
|
4332
4370
|
}
|
|
4333
|
-
|
|
4334
|
-
return new Promise((resolve) => {
|
|
4371
|
+
showAuthSelect(dialog, prompt) {
|
|
4372
|
+
return new Promise((resolve, reject) => {
|
|
4335
4373
|
const restoreDialog = () => {
|
|
4336
4374
|
this.editorContainer.clear();
|
|
4337
4375
|
this.editorContainer.addChild(dialog);
|
|
@@ -4341,10 +4379,14 @@ export class InteractiveMode {
|
|
|
4341
4379
|
const labels = prompt.options.map((option) => option.label);
|
|
4342
4380
|
const selector = new ExtensionSelectorComponent(prompt.message, labels, (optionLabel) => {
|
|
4343
4381
|
restoreDialog();
|
|
4344
|
-
|
|
4382
|
+
const id = prompt.options.find((option) => option.label === optionLabel)?.id;
|
|
4383
|
+
if (id)
|
|
4384
|
+
resolve(id);
|
|
4385
|
+
else
|
|
4386
|
+
reject(new Error("Login cancelled"));
|
|
4345
4387
|
}, () => {
|
|
4346
4388
|
restoreDialog();
|
|
4347
|
-
|
|
4389
|
+
reject(new Error("Login cancelled"));
|
|
4348
4390
|
});
|
|
4349
4391
|
this.editorContainer.clear();
|
|
4350
4392
|
this.editorContainer.addChild(selector);
|
|
@@ -4352,30 +4394,64 @@ export class InteractiveMode {
|
|
|
4352
4394
|
this.ui.requestRender();
|
|
4353
4395
|
});
|
|
4354
4396
|
}
|
|
4397
|
+
async showAuthPrompt(dialog, prompt) {
|
|
4398
|
+
let response;
|
|
4399
|
+
if (prompt.type === "select") {
|
|
4400
|
+
response = this.showAuthSelect(dialog, prompt);
|
|
4401
|
+
}
|
|
4402
|
+
else if (prompt.type === "manual_code") {
|
|
4403
|
+
response = dialog.showManualInput(prompt.message);
|
|
4404
|
+
}
|
|
4405
|
+
else {
|
|
4406
|
+
response = dialog.showPrompt(prompt.message, prompt.placeholder);
|
|
4407
|
+
}
|
|
4408
|
+
if (!prompt.signal)
|
|
4409
|
+
return response;
|
|
4410
|
+
if (prompt.signal.aborted)
|
|
4411
|
+
throw new Error("Login cancelled");
|
|
4412
|
+
const signal = prompt.signal;
|
|
4413
|
+
let onAbort;
|
|
4414
|
+
const aborted = new Promise((_resolve, reject) => {
|
|
4415
|
+
onAbort = () => reject(new Error("Login cancelled"));
|
|
4416
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
4417
|
+
});
|
|
4418
|
+
try {
|
|
4419
|
+
return await Promise.race([response, aborted]);
|
|
4420
|
+
}
|
|
4421
|
+
finally {
|
|
4422
|
+
if (onAbort)
|
|
4423
|
+
signal.removeEventListener("abort", onAbort);
|
|
4424
|
+
}
|
|
4425
|
+
}
|
|
4426
|
+
notifyAuthDialog(dialog, event) {
|
|
4427
|
+
if (event.type === "auth_url") {
|
|
4428
|
+
dialog.showAuth(event.url, event.instructions);
|
|
4429
|
+
}
|
|
4430
|
+
else if (event.type === "device_code") {
|
|
4431
|
+
dialog.showDeviceCode(event);
|
|
4432
|
+
dialog.showWaiting("Waiting for authentication...");
|
|
4433
|
+
}
|
|
4434
|
+
else if (event.type === "info") {
|
|
4435
|
+
dialog.showInfo(event.message, event.links);
|
|
4436
|
+
}
|
|
4437
|
+
else {
|
|
4438
|
+
dialog.showProgress(event.message);
|
|
4439
|
+
}
|
|
4440
|
+
}
|
|
4441
|
+
async loginProvider(dialog, providerId, method) {
|
|
4442
|
+
await this.session.modelRuntime.login(providerId, method, {
|
|
4443
|
+
signal: dialog.signal,
|
|
4444
|
+
prompt: (prompt) => this.showAuthPrompt(dialog, prompt),
|
|
4445
|
+
notify: (event) => this.notifyAuthDialog(dialog, event),
|
|
4446
|
+
});
|
|
4447
|
+
}
|
|
4355
4448
|
async showLoginDialog(providerId, providerName) {
|
|
4356
|
-
const providerInfo = this.session.modelRegistry.authStorage
|
|
4357
|
-
.getOAuthProviders()
|
|
4358
|
-
.find((provider) => provider.id === providerId);
|
|
4359
4449
|
const previousModel = this.session.model;
|
|
4360
|
-
|
|
4361
|
-
const usesCallbackServer = providerInfo?.usesCallbackServer ?? false;
|
|
4362
|
-
// Create login dialog component
|
|
4363
|
-
const dialog = new LoginDialogComponent(this.ui, providerId, (_success, _message) => {
|
|
4364
|
-
// Completion handled below
|
|
4365
|
-
}, providerName);
|
|
4366
|
-
// Show dialog in editor container
|
|
4450
|
+
const dialog = new LoginDialogComponent(this.ui, providerId, (_success, _message) => { }, providerName);
|
|
4367
4451
|
this.editorContainer.clear();
|
|
4368
4452
|
this.editorContainer.addChild(dialog);
|
|
4369
4453
|
this.ui.setFocus(dialog);
|
|
4370
4454
|
this.ui.requestRender();
|
|
4371
|
-
// Promise for manual code input (racing with callback server)
|
|
4372
|
-
let manualCodeResolve;
|
|
4373
|
-
let manualCodeReject;
|
|
4374
|
-
const manualCodePromise = new Promise((resolve, reject) => {
|
|
4375
|
-
manualCodeResolve = resolve;
|
|
4376
|
-
manualCodeReject = reject;
|
|
4377
|
-
});
|
|
4378
|
-
// Restore editor helper
|
|
4379
4455
|
const restoreEditor = () => {
|
|
4380
4456
|
this.editorContainer.clear();
|
|
4381
4457
|
this.editorContainer.addChild(this.editor);
|
|
@@ -4383,43 +4459,7 @@ export class InteractiveMode {
|
|
|
4383
4459
|
this.ui.requestRender();
|
|
4384
4460
|
};
|
|
4385
4461
|
try {
|
|
4386
|
-
await this.
|
|
4387
|
-
onAuth: (info) => {
|
|
4388
|
-
dialog.showAuth(info.url, info.instructions);
|
|
4389
|
-
if (usesCallbackServer) {
|
|
4390
|
-
// Show input for manual paste, racing with callback
|
|
4391
|
-
dialog
|
|
4392
|
-
.showManualInput("Paste redirect URL below, or complete login in browser:")
|
|
4393
|
-
.then((value) => {
|
|
4394
|
-
if (value && manualCodeResolve) {
|
|
4395
|
-
manualCodeResolve(value);
|
|
4396
|
-
manualCodeResolve = undefined;
|
|
4397
|
-
}
|
|
4398
|
-
})
|
|
4399
|
-
.catch(() => {
|
|
4400
|
-
if (manualCodeReject) {
|
|
4401
|
-
manualCodeReject(new Error("Login cancelled"));
|
|
4402
|
-
manualCodeReject = undefined;
|
|
4403
|
-
}
|
|
4404
|
-
});
|
|
4405
|
-
}
|
|
4406
|
-
// For Anthropic: onPrompt is called immediately after
|
|
4407
|
-
},
|
|
4408
|
-
onDeviceCode: (info) => {
|
|
4409
|
-
dialog.showDeviceCode(info);
|
|
4410
|
-
dialog.showWaiting("Waiting for authentication...");
|
|
4411
|
-
},
|
|
4412
|
-
onPrompt: async (prompt) => {
|
|
4413
|
-
return dialog.showPrompt(prompt.message, prompt.placeholder);
|
|
4414
|
-
},
|
|
4415
|
-
onProgress: (message) => {
|
|
4416
|
-
dialog.showProgress(message);
|
|
4417
|
-
},
|
|
4418
|
-
onSelect: (prompt) => this.showOAuthLoginSelect(dialog, prompt),
|
|
4419
|
-
onManualCodeInput: () => manualCodePromise,
|
|
4420
|
-
signal: dialog.signal,
|
|
4421
|
-
});
|
|
4422
|
-
// Success
|
|
4462
|
+
await this.loginProvider(dialog, providerId, "oauth");
|
|
4423
4463
|
restoreEditor();
|
|
4424
4464
|
await this.completeProviderAuthentication(providerId, providerName, "oauth", previousModel);
|
|
4425
4465
|
}
|
|
@@ -4507,7 +4547,7 @@ export class InteractiveMode {
|
|
|
4507
4547
|
showDiagnosticsWhenQuiet: true,
|
|
4508
4548
|
});
|
|
4509
4549
|
const savedImplicitProjectTrust = this.maybeSaveImplicitProjectTrustAfterReload();
|
|
4510
|
-
const modelsJsonError = this.session.
|
|
4550
|
+
const modelsJsonError = this.session.modelRuntime.getError();
|
|
4511
4551
|
if (modelsJsonError) {
|
|
4512
4552
|
this.showError(`models.json error: ${modelsJsonError}`);
|
|
4513
4553
|
}
|
|
@@ -4735,25 +4775,11 @@ export class InteractiveMode {
|
|
|
4735
4775
|
const stats = this.session.getSessionStats();
|
|
4736
4776
|
const sessionName = this.sessionManager.getSessionName();
|
|
4737
4777
|
const entries = this.sessionManager.getEntries();
|
|
4738
|
-
const cacheWaste = computeCacheWaste(entries, this.session.
|
|
4778
|
+
const cacheWaste = computeCacheWaste(entries, this.session.modelRuntime);
|
|
4739
4779
|
// Cost/token totals per provider/model actually used (e.g. OpenRouter `auto`
|
|
4740
|
-
// resolves to a concrete responseModel)
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
if (entry.type !== "message" || entry.message.role !== "assistant")
|
|
4744
|
-
continue;
|
|
4745
|
-
const message = entry.message;
|
|
4746
|
-
const usage = message.usage;
|
|
4747
|
-
const key = `${message.provider}/${message.responseModel ?? message.model}`;
|
|
4748
|
-
let bucket = perModelMap.get(key);
|
|
4749
|
-
if (!bucket) {
|
|
4750
|
-
bucket = { key, cost: 0, tokens: 0 };
|
|
4751
|
-
perModelMap.set(key, bucket);
|
|
4752
|
-
}
|
|
4753
|
-
bucket.cost += usage.cost.total;
|
|
4754
|
-
bucket.tokens += usage.input + usage.output + usage.cacheRead + usage.cacheWrite;
|
|
4755
|
-
}
|
|
4756
|
-
const perModel = Array.from(perModelMap.values()).sort((a, b) => b.cost - a.cost);
|
|
4780
|
+
// resolves to a concrete responseModel). Usage without model attribution is
|
|
4781
|
+
// grouped separately so the breakdown reconciles with the session total.
|
|
4782
|
+
const usageBreakdown = getUsageCostBreakdown(entries);
|
|
4757
4783
|
let info = `${theme.bold("Session Info")}\n\n`;
|
|
4758
4784
|
if (sessionName) {
|
|
4759
4785
|
info += `${theme.fg("dim", "Name:")} ${sessionName}\n`;
|
|
@@ -4784,8 +4810,8 @@ export class InteractiveMode {
|
|
|
4784
4810
|
if (stats.cost > 0 || cacheWaste.missedTokens > 0) {
|
|
4785
4811
|
info += `\n${theme.bold("Cost")}\n`;
|
|
4786
4812
|
info += `${theme.fg("dim", "Total:")} $${stats.cost.toFixed(3)}`;
|
|
4787
|
-
if (
|
|
4788
|
-
for (const entry of
|
|
4813
|
+
if (usageBreakdown.length > 1) {
|
|
4814
|
+
for (const entry of usageBreakdown) {
|
|
4789
4815
|
info += `\n ${theme.fg("dim", `${entry.key}:`)} $${entry.cost.toFixed(3)} ${theme.fg("dim", `(${formatTokens(entry.tokens)} tokens)`)}`;
|
|
4790
4816
|
}
|
|
4791
4817
|
}
|
|
@@ -4868,6 +4894,7 @@ export class InteractiveMode {
|
|
|
4868
4894
|
const toggleThinking = this.getAppKeyDisplay("app.thinking.toggle");
|
|
4869
4895
|
const externalEditor = this.getAppKeyDisplay("app.editor.external");
|
|
4870
4896
|
const cycleModelBackward = this.getAppKeyDisplay("app.model.cycleBackward");
|
|
4897
|
+
const copyMessage = this.getAppKeyDisplay("app.message.copy");
|
|
4871
4898
|
const followUp = this.getAppKeyDisplay("app.message.followUp");
|
|
4872
4899
|
const dequeue = this.getAppKeyDisplay("app.message.dequeue");
|
|
4873
4900
|
const pasteImage = this.getAppKeyDisplay("app.clipboard.pasteImage");
|
|
@@ -4910,9 +4937,10 @@ export class InteractiveMode {
|
|
|
4910
4937
|
| \`${expandTools}\` | Toggle tool output expansion |
|
|
4911
4938
|
| \`${toggleThinking}\` | Toggle thinking block visibility |
|
|
4912
4939
|
| \`${externalEditor}\` | Edit message in external editor |
|
|
4940
|
+
| \`${copyMessage}\` | Copy last assistant message |
|
|
4913
4941
|
| \`${followUp}\` | Queue follow-up message |
|
|
4914
4942
|
| \`${dequeue}\` | Restore queued messages |
|
|
4915
|
-
| \`${pasteImage}\` | Paste image from clipboard |
|
|
4943
|
+
| \`${pasteImage}\` | Paste image or text from clipboard |
|
|
4916
4944
|
| \`/\` | Slash commands |
|
|
4917
4945
|
| \`!\` | Run bash command |
|
|
4918
4946
|
| \`!!\` | Run bash command (excluded from context) |
|