@earendil-works/pi-coding-agent 0.80.6 → 0.80.8
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 +77 -0
- package/README.md +9 -7
- 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 +1 -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/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 +10 -9
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session.d.ts +6 -7
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +61 -34
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage.d.ts +15 -99
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +46 -259
- package/dist/core/auth-storage.js.map +1 -1
- 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 +1 -1
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/core/compaction/branch-summarization.js.map +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +8 -2
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +2 -0
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +8 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +9 -2
- 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/model-config.d.ts +507 -0
- package/dist/core/model-config.d.ts.map +1 -0
- package/dist/core/model-config.js +242 -0
- package/dist/core/model-config.js.map +1 -0
- package/dist/core/model-registry.d.ts +13 -123
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +44 -731
- 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 +18 -17
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/model-runtime.d.ts +77 -0
- package/dist/core/model-runtime.d.ts.map +1 -0
- package/dist/core/model-runtime.js +418 -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/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 +83 -0
- package/dist/core/remote-catalog-provider.js.map +1 -0
- package/dist/core/runtime-credentials.d.ts +15 -0
- package/dist/core/runtime-credentials.d.ts.map +1 -0
- package/dist/core/runtime-credentials.js +36 -0
- package/dist/core/runtime-credentials.js.map +1 -0
- package/dist/core/sdk.d.ts +3 -6
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +14 -25
- package/dist/core/sdk.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/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +11 -13
- 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 +1 -1
- 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 +7 -5
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +187 -171
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +2 -2
- package/dist/modes/rpc/rpc-mode.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/docs/custom-provider.md +8 -11
- package/docs/extensions.md +160 -1
- package/docs/keybindings.md +1 -0
- package/docs/models.md +3 -0
- package/docs/packages.md +1 -0
- package/docs/providers.md +16 -1
- package/docs/quickstart.md +1 -1
- package/docs/sdk.md +40 -50
- package/docs/usage.md +2 -0
- package/examples/extensions/custom-provider-anthropic/index.ts +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/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/02-custom-model.ts +5 -8
- package/examples/sdk/09-api-keys-and-oauth.ts +13 -31
- package/examples/sdk/12-full-control.ts +7 -12
- package/examples/sdk/README.md +14 -18
- package/npm-shrinkwrap.json +12 -12
- package/package.json +4 -4
- package/dist/core/provider-display-names.d.ts +0 -2
- package/dist/core/provider-display-names.d.ts.map +0 -1
- package/dist/core/provider-display-names.js +0 -36
- package/dist/core/provider-display-names.js.map +0 -1
|
@@ -6,7 +6,6 @@ import * as crypto from "node:crypto";
|
|
|
6
6
|
import * as fs from "node:fs";
|
|
7
7
|
import * as os from "node:os";
|
|
8
8
|
import * as path from "node:path";
|
|
9
|
-
import { getProviders, } from "@earendil-works/pi-ai/compat";
|
|
10
9
|
import { CombinedAutocompleteProvider, Container, fuzzyFilter, getCapabilities, hyperlink, Markdown, matchesKey, ProcessTerminal, Spacer, setKeybindings, Text, TruncatedText, TUI, visibleWidth, } from "@earendil-works/pi-tui";
|
|
11
10
|
import chalk from "chalk";
|
|
12
11
|
import { spawn, spawnSync } from "child_process";
|
|
@@ -20,14 +19,13 @@ import { KeybindingsManager } from "../../core/keybindings.js";
|
|
|
20
19
|
import { createCompactionSummaryMessage } from "../../core/messages.js";
|
|
21
20
|
import { defaultModelPerProvider, findExactModelReferenceMatch, resolveModelScope } from "../../core/model-resolver.js";
|
|
22
21
|
import { DefaultPackageManager } from "../../core/package-manager.js";
|
|
23
|
-
import { BUILT_IN_PROVIDER_DISPLAY_NAMES } from "../../core/provider-display-names.js";
|
|
24
22
|
import { formatMissingSessionCwdPrompt, MissingSessionCwdError } from "../../core/session-cwd.js";
|
|
25
23
|
import { SessionManager, sessionEntryToContextMessages } from "../../core/session-manager.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";
|
|
29
27
|
import { getChangelogPath, getNewEntries, normalizeChangelogLinks, parseChangelog } from "../../utils/changelog.js";
|
|
30
|
-
import { copyToClipboard } from "../../utils/clipboard.js";
|
|
28
|
+
import { copyToClipboard, readClipboardText } from "../../utils/clipboard.js";
|
|
31
29
|
import { extensionForImageMimeType, readClipboardImage } from "../../utils/clipboard-image.js";
|
|
32
30
|
import { parseGitUrl } from "../../utils/git.js";
|
|
33
31
|
import { getCwdRelativePath } from "../../utils/paths.js";
|
|
@@ -94,7 +92,7 @@ function isDeadTerminalError(error) {
|
|
|
94
92
|
const code = error.code;
|
|
95
93
|
return code !== undefined && DEAD_TERMINAL_ERROR_CODES.has(code);
|
|
96
94
|
}
|
|
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.";
|
|
95
|
+
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.";
|
|
98
96
|
function isAnthropicSubscriptionAuthKey(apiKey) {
|
|
99
97
|
return typeof apiKey === "string" && apiKey.startsWith("sk-ant-oat");
|
|
100
98
|
}
|
|
@@ -125,17 +123,6 @@ export function formatResumeCommand(sessionManager) {
|
|
|
125
123
|
function hasDefaultModelProvider(providerId) {
|
|
126
124
|
return providerId in defaultModelPerProvider;
|
|
127
125
|
}
|
|
128
|
-
const BEDROCK_PROVIDER_ID = "amazon-bedrock";
|
|
129
|
-
const BUILT_IN_MODEL_PROVIDERS = new Set(getProviders());
|
|
130
|
-
export function isApiKeyLoginProvider(providerId, oauthProviderIds, builtInProviderIds = BUILT_IN_MODEL_PROVIDERS) {
|
|
131
|
-
if (BUILT_IN_PROVIDER_DISPLAY_NAMES[providerId]) {
|
|
132
|
-
return true;
|
|
133
|
-
}
|
|
134
|
-
if (builtInProviderIds.has(providerId)) {
|
|
135
|
-
return false;
|
|
136
|
-
}
|
|
137
|
-
return !oauthProviderIds.has(providerId);
|
|
138
|
-
}
|
|
139
126
|
const AUTH_TYPE_ORDER = { oauth: 0, api_key: 1 };
|
|
140
127
|
function createFuzzyAutocompleteItems(items, prefix, getSearchText, toAutocompleteItem) {
|
|
141
128
|
const filtered = fuzzyFilter(items, prefix, getSearchText);
|
|
@@ -362,11 +349,11 @@ export class InteractiveMode {
|
|
|
362
349
|
}));
|
|
363
350
|
const modelCommand = slashCommands.find((command) => command.name === "model");
|
|
364
351
|
if (modelCommand) {
|
|
365
|
-
modelCommand.getArgumentCompletions = (prefix) => {
|
|
352
|
+
modelCommand.getArgumentCompletions = async (prefix) => {
|
|
366
353
|
// Get available models (scoped or from registry)
|
|
367
354
|
const models = this.session.scopedModels.length > 0
|
|
368
355
|
? this.session.scopedModels.map((s) => s.model)
|
|
369
|
-
: this.session.
|
|
356
|
+
: await this.session.modelRuntime.getAvailable();
|
|
370
357
|
if (models.length === 0)
|
|
371
358
|
return null;
|
|
372
359
|
// Create items with provider/id format
|
|
@@ -532,7 +519,7 @@ export class InteractiveMode {
|
|
|
532
519
|
rawKeyHint("!!", "to run bash (no context)"),
|
|
533
520
|
hint("app.message.followUp", "to queue follow-up"),
|
|
534
521
|
hint("app.message.dequeue", "to edit all queued messages"),
|
|
535
|
-
hint("app.clipboard.pasteImage", "to paste image"),
|
|
522
|
+
hint("app.clipboard.pasteImage", "to paste image (with text fallback)"),
|
|
536
523
|
rawKeyHint("drop files", "to attach"),
|
|
537
524
|
].join("\n");
|
|
538
525
|
const compactInstructions = [
|
|
@@ -599,10 +586,18 @@ export class InteractiveMode {
|
|
|
599
586
|
}
|
|
600
587
|
});
|
|
601
588
|
// Start package update check asynchronously
|
|
602
|
-
this.checkForPackageUpdates()
|
|
589
|
+
this.checkForPackageUpdates()
|
|
590
|
+
.then((updates) => {
|
|
603
591
|
if (updates.length > 0) {
|
|
604
592
|
this.showPackageUpdateNotification(updates);
|
|
605
593
|
}
|
|
594
|
+
})
|
|
595
|
+
.finally(() => {
|
|
596
|
+
// On Windows, npm can overwrite the shared console title while checking
|
|
597
|
+
// extension package versions. Restore Pi's title after the startup check.
|
|
598
|
+
if (process.platform === "win32" && this.isInitialized) {
|
|
599
|
+
this.updateTerminalTitle();
|
|
600
|
+
}
|
|
606
601
|
});
|
|
607
602
|
// Check tmux keyboard setup asynchronously
|
|
608
603
|
this.checkTmuxKeyboardSetup().then((warning) => {
|
|
@@ -615,7 +610,7 @@ export class InteractiveMode {
|
|
|
615
610
|
if (migratedProviders && migratedProviders.length > 0) {
|
|
616
611
|
this.showWarning(`Migrated credentials to auth.json: ${migratedProviders.join(", ")}`);
|
|
617
612
|
}
|
|
618
|
-
const modelsJsonError = this.session.
|
|
613
|
+
const modelsJsonError = this.session.modelRuntime.getError();
|
|
619
614
|
if (modelsJsonError) {
|
|
620
615
|
this.showError(`models.json error: ${modelsJsonError}`);
|
|
621
616
|
}
|
|
@@ -1346,7 +1341,7 @@ export class InteractiveMode {
|
|
|
1346
1341
|
hasUI: true,
|
|
1347
1342
|
cwd: this.sessionManager.getCwd(),
|
|
1348
1343
|
sessionManager: this.sessionManager,
|
|
1349
|
-
modelRegistry:
|
|
1344
|
+
modelRegistry: extensionRunner.getModelRegistry(),
|
|
1350
1345
|
model: this.session.model,
|
|
1351
1346
|
isIdle: () => this.session.isIdle,
|
|
1352
1347
|
isProjectTrusted: () => this.settingsManager.isProjectTrusted(),
|
|
@@ -2034,6 +2029,7 @@ export class InteractiveMode {
|
|
|
2034
2029
|
this.defaultEditor.onAction("app.tools.expand", () => this.toggleToolOutputExpansion());
|
|
2035
2030
|
this.defaultEditor.onAction("app.thinking.toggle", () => this.toggleThinkingBlockVisibility());
|
|
2036
2031
|
this.defaultEditor.onAction("app.editor.external", () => this.openExternalEditor());
|
|
2032
|
+
this.defaultEditor.onAction("app.message.copy", () => void this.handleCopyCommand());
|
|
2037
2033
|
this.defaultEditor.onAction("app.message.followUp", () => this.handleFollowUp());
|
|
2038
2034
|
this.defaultEditor.onAction("app.message.dequeue", () => this.handleDequeue());
|
|
2039
2035
|
this.defaultEditor.onAction("app.session.new", () => this.handleClearCommand());
|
|
@@ -2047,26 +2043,30 @@ export class InteractiveMode {
|
|
|
2047
2043
|
this.updateEditorBorderColor();
|
|
2048
2044
|
}
|
|
2049
2045
|
};
|
|
2050
|
-
// Handle clipboard
|
|
2046
|
+
// Handle clipboard paste (triggered on Ctrl+V). Images are attached by path;
|
|
2047
|
+
// otherwise, paste plain text from the system clipboard.
|
|
2051
2048
|
this.defaultEditor.onPasteImage = () => {
|
|
2052
|
-
this.
|
|
2049
|
+
void this.handleClipboardPaste();
|
|
2053
2050
|
};
|
|
2054
2051
|
}
|
|
2055
|
-
async
|
|
2052
|
+
async handleClipboardPaste() {
|
|
2056
2053
|
try {
|
|
2057
2054
|
const image = await readClipboardImage();
|
|
2058
|
-
if (
|
|
2055
|
+
if (image) {
|
|
2056
|
+
const tmpDir = os.tmpdir();
|
|
2057
|
+
const ext = extensionForImageMimeType(image.mimeType) ?? "png";
|
|
2058
|
+
const fileName = `pi-clipboard-${crypto.randomUUID()}.${ext}`;
|
|
2059
|
+
const filePath = path.join(tmpDir, fileName);
|
|
2060
|
+
fs.writeFileSync(filePath, Buffer.from(image.bytes));
|
|
2061
|
+
this.editor.insertTextAtCursor?.(filePath);
|
|
2062
|
+
this.ui.requestRender();
|
|
2059
2063
|
return;
|
|
2060
2064
|
}
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
fs.writeFileSync(filePath, Buffer.from(image.bytes));
|
|
2067
|
-
// Insert file path directly
|
|
2068
|
-
this.editor.insertTextAtCursor?.(filePath);
|
|
2069
|
-
this.ui.requestRender();
|
|
2065
|
+
const text = await readClipboardText();
|
|
2066
|
+
if (text) {
|
|
2067
|
+
this.editor.insertTextAtCursor?.(text);
|
|
2068
|
+
this.ui.requestRender();
|
|
2069
|
+
}
|
|
2070
2070
|
}
|
|
2071
2071
|
catch {
|
|
2072
2072
|
// Silently ignore clipboard errors (may not have permission, etc.)
|
|
@@ -2647,7 +2647,7 @@ export class InteractiveMode {
|
|
|
2647
2647
|
// Cache-miss notices are not persisted; re-derive them from the full entry
|
|
2648
2648
|
// list and re-inject them after the assistant messages that paid for them.
|
|
2649
2649
|
const cacheMisses = this.settingsManager.getShowCacheMissNotices()
|
|
2650
|
-
? collectCacheMisses(this.sessionManager.getEntries(), this.session.
|
|
2650
|
+
? collectCacheMisses(this.sessionManager.getEntries(), this.session.modelRuntime)
|
|
2651
2651
|
: new Map();
|
|
2652
2652
|
if (options.updateFooter) {
|
|
2653
2653
|
this.footer.invalidate();
|
|
@@ -2738,7 +2738,7 @@ export class InteractiveMode {
|
|
|
2738
2738
|
if (!this.settingsManager.getShowCacheMissNotices())
|
|
2739
2739
|
return;
|
|
2740
2740
|
// Entries don't contain `message` yet: message_end fires before persistence.
|
|
2741
|
-
const miss = detectCacheMiss(this.sessionManager.getEntries(), message, this.session.
|
|
2741
|
+
const miss = detectCacheMiss(this.sessionManager.getEntries(), message, this.session.modelRuntime);
|
|
2742
2742
|
if (miss)
|
|
2743
2743
|
this.addCacheMissNotice(miss);
|
|
2744
2744
|
}
|
|
@@ -3579,9 +3579,9 @@ export class InteractiveMode {
|
|
|
3579
3579
|
if (this.session.scopedModels.length > 0) {
|
|
3580
3580
|
return this.session.scopedModels.map((scoped) => scoped.model);
|
|
3581
3581
|
}
|
|
3582
|
-
this.session.modelRegistry.refresh();
|
|
3583
3582
|
try {
|
|
3584
|
-
|
|
3583
|
+
await this.session.modelRuntime.refresh();
|
|
3584
|
+
return [...(await this.session.modelRuntime.getAvailable())];
|
|
3585
3585
|
}
|
|
3586
3586
|
catch {
|
|
3587
3587
|
return [];
|
|
@@ -3603,14 +3603,13 @@ export class InteractiveMode {
|
|
|
3603
3603
|
if (!model || model.provider !== "anthropic") {
|
|
3604
3604
|
return;
|
|
3605
3605
|
}
|
|
3606
|
-
const storedCredential = this.session.modelRegistry.authStorage.get("anthropic");
|
|
3607
|
-
if (storedCredential?.type === "oauth") {
|
|
3608
|
-
this.anthropicSubscriptionWarningShown = true;
|
|
3609
|
-
this.showWarning(ANTHROPIC_SUBSCRIPTION_AUTH_WARNING);
|
|
3610
|
-
return;
|
|
3611
|
-
}
|
|
3612
3606
|
try {
|
|
3613
|
-
|
|
3607
|
+
if ((await this.session.modelRuntime.checkAuth("anthropic"))?.type === "oauth") {
|
|
3608
|
+
this.anthropicSubscriptionWarningShown = true;
|
|
3609
|
+
this.showWarning(ANTHROPIC_SUBSCRIPTION_AUTH_WARNING);
|
|
3610
|
+
return;
|
|
3611
|
+
}
|
|
3612
|
+
const apiKey = (await this.session.modelRuntime.getAuth(model.provider))?.auth.apiKey;
|
|
3614
3613
|
if (!isAnthropicSubscriptionAuthKey(apiKey)) {
|
|
3615
3614
|
return;
|
|
3616
3615
|
}
|
|
@@ -3668,7 +3667,7 @@ export class InteractiveMode {
|
|
|
3668
3667
|
}
|
|
3669
3668
|
showModelSelector(initialSearchInput) {
|
|
3670
3669
|
this.showSelector((done) => {
|
|
3671
|
-
const selector = new ModelSelectorComponent(this.ui, this.session.model, this.settingsManager, this.session.
|
|
3670
|
+
const selector = new ModelSelectorComponent(this.ui, this.session.model, this.settingsManager, this.session.modelRuntime, this.session.scopedModels, async (model) => {
|
|
3672
3671
|
try {
|
|
3673
3672
|
await this.session.setModel(model);
|
|
3674
3673
|
this.footer.invalidate();
|
|
@@ -3691,8 +3690,8 @@ export class InteractiveMode {
|
|
|
3691
3690
|
}
|
|
3692
3691
|
async showModelsSelector() {
|
|
3693
3692
|
// Get all available models
|
|
3694
|
-
this.session.
|
|
3695
|
-
const allModels = this.session.
|
|
3693
|
+
await this.session.modelRuntime.refresh();
|
|
3694
|
+
const allModels = [...(await this.session.modelRuntime.getAvailable())];
|
|
3696
3695
|
if (allModels.length === 0) {
|
|
3697
3696
|
this.showStatus("No models available");
|
|
3698
3697
|
return;
|
|
@@ -3710,7 +3709,7 @@ export class InteractiveMode {
|
|
|
3710
3709
|
// Fall back to settings
|
|
3711
3710
|
const patterns = this.settingsManager.getEnabledModels();
|
|
3712
3711
|
if (patterns !== undefined && patterns.length > 0) {
|
|
3713
|
-
const scopedModels = await resolveModelScope(patterns, this.session.
|
|
3712
|
+
const scopedModels = await resolveModelScope(patterns, this.session.modelRuntime);
|
|
3714
3713
|
currentEnabledIds = scopedModels.map((scoped) => `${scoped.model.provider}/${scoped.model.id}`);
|
|
3715
3714
|
}
|
|
3716
3715
|
}
|
|
@@ -3718,7 +3717,7 @@ export class InteractiveMode {
|
|
|
3718
3717
|
const updateSessionModels = async (enabledIds) => {
|
|
3719
3718
|
currentEnabledIds = enabledIds === null ? null : [...enabledIds];
|
|
3720
3719
|
if (enabledIds && enabledIds.length > 0 && enabledIds.length < allModels.length) {
|
|
3721
|
-
const newScopedModels = await resolveModelScope(enabledIds, this.session.
|
|
3720
|
+
const newScopedModels = await resolveModelScope(enabledIds, this.session.modelRuntime);
|
|
3722
3721
|
this.session.setScopedModels(newScopedModels.map((sm) => ({
|
|
3723
3722
|
model: sm.model,
|
|
3724
3723
|
thinkingLevel: sm.thinkingLevel,
|
|
@@ -3901,6 +3900,19 @@ export class InteractiveMode {
|
|
|
3901
3900
|
this.sessionManager.appendLabelChange(entryId, label);
|
|
3902
3901
|
this.ui.requestRender();
|
|
3903
3902
|
}, initialSelectedId, initialFilterMode);
|
|
3903
|
+
selector.onCopy = async (text) => {
|
|
3904
|
+
if (!text) {
|
|
3905
|
+
this.showError("Selected entry has no text to copy");
|
|
3906
|
+
return;
|
|
3907
|
+
}
|
|
3908
|
+
try {
|
|
3909
|
+
await copyToClipboard(text);
|
|
3910
|
+
this.showStatus("Copied selected message to clipboard");
|
|
3911
|
+
}
|
|
3912
|
+
catch (error) {
|
|
3913
|
+
this.showError(error instanceof Error ? error.message : String(error));
|
|
3914
|
+
}
|
|
3915
|
+
};
|
|
3904
3916
|
return { component: selector, focus: selector };
|
|
3905
3917
|
});
|
|
3906
3918
|
}
|
|
@@ -3965,44 +3977,46 @@ export class InteractiveMode {
|
|
|
3965
3977
|
}
|
|
3966
3978
|
}
|
|
3967
3979
|
getLoginProviderOptions(authType) {
|
|
3968
|
-
const authStorage = this.session.modelRegistry.authStorage;
|
|
3969
|
-
const oauthProviders = authStorage.getOAuthProviders();
|
|
3970
|
-
const oauthProviderIds = new Set(oauthProviders.map((provider) => provider.id));
|
|
3971
|
-
const options = oauthProviders.map((provider) => ({
|
|
3972
|
-
id: provider.id,
|
|
3973
|
-
name: provider.name,
|
|
3974
|
-
authType: "oauth",
|
|
3975
|
-
}));
|
|
3976
|
-
const modelProviders = new Set(this.session.modelRegistry.getAll().map((model) => model.provider));
|
|
3977
|
-
for (const providerId of modelProviders) {
|
|
3978
|
-
if (!isApiKeyLoginProvider(providerId, oauthProviderIds)) {
|
|
3979
|
-
continue;
|
|
3980
|
-
}
|
|
3981
|
-
options.push({
|
|
3982
|
-
id: providerId,
|
|
3983
|
-
name: this.session.modelRegistry.getProviderDisplayName(providerId),
|
|
3984
|
-
authType: "api_key",
|
|
3985
|
-
});
|
|
3986
|
-
}
|
|
3987
|
-
const filteredOptions = authType ? options.filter((option) => option.authType === authType) : options;
|
|
3988
|
-
return filteredOptions.sort((a, b) => a.name.localeCompare(b.name));
|
|
3989
|
-
}
|
|
3990
|
-
getLogoutProviderOptions() {
|
|
3991
|
-
const authStorage = this.session.modelRegistry.authStorage;
|
|
3992
3980
|
const options = [];
|
|
3993
|
-
for (const
|
|
3994
|
-
const
|
|
3995
|
-
|
|
3996
|
-
|
|
3981
|
+
for (const provider of this.session.modelRuntime.getProviders()) {
|
|
3982
|
+
const authStatus = this.session.modelRuntime.getProviderAuthStatus(provider.id);
|
|
3983
|
+
const status = authStatus.configured
|
|
3984
|
+
? {
|
|
3985
|
+
type: this.session.modelRuntime.isUsingOAuth(provider.id) ? "oauth" : "api_key",
|
|
3986
|
+
source: authStatus.label ?? authStatus.source,
|
|
3987
|
+
}
|
|
3988
|
+
: undefined;
|
|
3989
|
+
if ((!authType || authType === "oauth") && provider.auth.oauth) {
|
|
3990
|
+
options.push({
|
|
3991
|
+
id: provider.id,
|
|
3992
|
+
name: provider.name,
|
|
3993
|
+
authType: "oauth",
|
|
3994
|
+
method: provider.auth.oauth,
|
|
3995
|
+
status,
|
|
3996
|
+
});
|
|
3997
|
+
}
|
|
3998
|
+
if ((!authType || authType === "api_key") && provider.auth.apiKey) {
|
|
3999
|
+
options.push({
|
|
4000
|
+
id: provider.id,
|
|
4001
|
+
name: provider.name,
|
|
4002
|
+
authType: "api_key",
|
|
4003
|
+
method: provider.auth.apiKey,
|
|
4004
|
+
status,
|
|
4005
|
+
});
|
|
3997
4006
|
}
|
|
3998
|
-
options.push({
|
|
3999
|
-
id: providerId,
|
|
4000
|
-
name: this.session.modelRegistry.getProviderDisplayName(providerId),
|
|
4001
|
-
authType: credential.type,
|
|
4002
|
-
});
|
|
4003
4007
|
}
|
|
4004
4008
|
return options.sort((a, b) => a.name.localeCompare(b.name));
|
|
4005
4009
|
}
|
|
4010
|
+
async getLogoutProviderOptions() {
|
|
4011
|
+
return (await this.session.modelRuntime.listCredentials())
|
|
4012
|
+
.map(({ providerId, type }) => ({
|
|
4013
|
+
id: providerId,
|
|
4014
|
+
name: this.session.modelRuntime.getProvider(providerId)?.name ?? providerId,
|
|
4015
|
+
authType: type,
|
|
4016
|
+
status: { type, source: "stored credential" },
|
|
4017
|
+
}))
|
|
4018
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
4019
|
+
}
|
|
4006
4020
|
findLoginProviderOptions(providerRef) {
|
|
4007
4021
|
const normalizedProviderRef = providerRef.trim().toLowerCase();
|
|
4008
4022
|
if (!normalizedProviderRef) {
|
|
@@ -4012,6 +4026,7 @@ export class InteractiveMode {
|
|
|
4012
4026
|
provider.name.toLowerCase() === normalizedProviderRef);
|
|
4013
4027
|
}
|
|
4014
4028
|
async handleLoginCommand(providerRef) {
|
|
4029
|
+
await this.session.modelRuntime.getAvailable();
|
|
4015
4030
|
if (!providerRef) {
|
|
4016
4031
|
this.showLoginAuthTypeSelector();
|
|
4017
4032
|
return;
|
|
@@ -4034,16 +4049,16 @@ export class InteractiveMode {
|
|
|
4034
4049
|
if (providerOption.authType === "oauth") {
|
|
4035
4050
|
await this.showLoginDialog(providerOption.id, providerOption.name);
|
|
4036
4051
|
}
|
|
4037
|
-
else if (providerOption.
|
|
4038
|
-
this.
|
|
4052
|
+
else if (providerOption.method?.login) {
|
|
4053
|
+
await this.showApiKeyLoginDialog(providerOption.id, providerOption.name);
|
|
4039
4054
|
}
|
|
4040
4055
|
else {
|
|
4041
|
-
|
|
4056
|
+
this.showAmbientAuthDialog(providerOption);
|
|
4042
4057
|
}
|
|
4043
4058
|
}
|
|
4044
4059
|
showLoginAuthTypeSelector(providerOptions) {
|
|
4045
|
-
const subscriptionLabel = "
|
|
4046
|
-
const apiKeyLabel = "
|
|
4060
|
+
const subscriptionLabel = "Sign in with an account";
|
|
4061
|
+
const apiKeyLabel = "Sign in with an API key";
|
|
4047
4062
|
const availableAuthTypes = providerOptions
|
|
4048
4063
|
? new Set(providerOptions.map((provider) => provider.authType))
|
|
4049
4064
|
: new Set(["oauth", "api_key"]);
|
|
@@ -4099,7 +4114,7 @@ export class InteractiveMode {
|
|
|
4099
4114
|
return;
|
|
4100
4115
|
}
|
|
4101
4116
|
this.showSelector((done) => {
|
|
4102
|
-
const selector = new OAuthSelectorComponent("login",
|
|
4117
|
+
const selector = new OAuthSelectorComponent("login", providerOptions, async (providerId, selectedAuthType) => {
|
|
4103
4118
|
done();
|
|
4104
4119
|
const providerOption = providerOptions.find((provider) => provider.id === providerId && provider.authType === selectedAuthType);
|
|
4105
4120
|
if (!providerOption) {
|
|
@@ -4114,7 +4129,7 @@ export class InteractiveMode {
|
|
|
4114
4129
|
else {
|
|
4115
4130
|
this.ui.requestRender();
|
|
4116
4131
|
}
|
|
4117
|
-
},
|
|
4132
|
+
}, initialSearchInput);
|
|
4118
4133
|
return { component: selector, focus: selector };
|
|
4119
4134
|
});
|
|
4120
4135
|
}
|
|
@@ -4123,21 +4138,20 @@ export class InteractiveMode {
|
|
|
4123
4138
|
this.showLoginAuthTypeSelector();
|
|
4124
4139
|
return;
|
|
4125
4140
|
}
|
|
4126
|
-
const providerOptions = this.getLogoutProviderOptions();
|
|
4141
|
+
const providerOptions = await this.getLogoutProviderOptions();
|
|
4127
4142
|
if (providerOptions.length === 0) {
|
|
4128
4143
|
this.showStatus("No stored credentials to remove. /logout only removes credentials saved by /login; environment variables and models.json config are unchanged.");
|
|
4129
4144
|
return;
|
|
4130
4145
|
}
|
|
4131
4146
|
this.showSelector((done) => {
|
|
4132
|
-
const selector = new OAuthSelectorComponent(mode,
|
|
4147
|
+
const selector = new OAuthSelectorComponent(mode, providerOptions, async (providerId) => {
|
|
4133
4148
|
done();
|
|
4134
4149
|
const providerOption = providerOptions.find((provider) => provider.id === providerId);
|
|
4135
4150
|
if (!providerOption) {
|
|
4136
4151
|
return;
|
|
4137
4152
|
}
|
|
4138
4153
|
try {
|
|
4139
|
-
this.session.
|
|
4140
|
-
this.session.modelRegistry.refresh();
|
|
4154
|
+
await this.session.modelRuntime.logout(providerOption.id);
|
|
4141
4155
|
await this.updateAvailableProviderCount();
|
|
4142
4156
|
const message = providerOption.authType === "oauth"
|
|
4143
4157
|
? `Logged out of ${providerOption.name}`
|
|
@@ -4155,12 +4169,12 @@ export class InteractiveMode {
|
|
|
4155
4169
|
});
|
|
4156
4170
|
}
|
|
4157
4171
|
async completeProviderAuthentication(providerId, providerName, authType, previousModel) {
|
|
4158
|
-
this.session.
|
|
4172
|
+
await this.session.modelRuntime.getAvailable();
|
|
4159
4173
|
const actionLabel = authType === "oauth" ? `Logged in to ${providerName}` : `Saved API key for ${providerName}`;
|
|
4160
4174
|
let selectedModel;
|
|
4161
4175
|
let selectionError;
|
|
4162
4176
|
if (isUnknownModel(previousModel)) {
|
|
4163
|
-
const availableModels = this.session.
|
|
4177
|
+
const availableModels = await this.session.modelRuntime.getAvailable();
|
|
4164
4178
|
const providerModels = availableModels.filter((model) => model.provider === providerId);
|
|
4165
4179
|
if (!hasDefaultModelProvider(providerId)) {
|
|
4166
4180
|
selectionError = `${actionLabel}, but no default model is configured for provider "${providerId}". Use /model to select a model.`;
|
|
@@ -4204,20 +4218,15 @@ export class InteractiveMode {
|
|
|
4204
4218
|
}
|
|
4205
4219
|
}
|
|
4206
4220
|
}
|
|
4207
|
-
|
|
4221
|
+
showAmbientAuthDialog(providerOption) {
|
|
4208
4222
|
const restoreEditor = () => {
|
|
4209
4223
|
this.editorContainer.clear();
|
|
4210
4224
|
this.editorContainer.addChild(this.editor);
|
|
4211
4225
|
this.ui.setFocus(this.editor);
|
|
4212
4226
|
this.ui.requestRender();
|
|
4213
4227
|
};
|
|
4214
|
-
const dialog = new LoginDialogComponent(this.ui,
|
|
4215
|
-
dialog.showInfo([
|
|
4216
|
-
theme.fg("text", "Amazon Bedrock uses AWS credentials instead of a single API key."),
|
|
4217
|
-
theme.fg("text", "Configure an AWS profile, IAM keys, bearer token, or role-based credentials."),
|
|
4218
|
-
theme.fg("muted", "See:"),
|
|
4219
|
-
theme.fg("accent", ` ${path.join(getDocsPath(), "providers.md")}`),
|
|
4220
|
-
]);
|
|
4228
|
+
const dialog = new LoginDialogComponent(this.ui, providerOption.id, () => restoreEditor(), providerOption.name, `${providerOption.name} setup`);
|
|
4229
|
+
dialog.showInfo(`${providerOption.method?.name ?? "Authentication"} is configured outside pi.`, [], true);
|
|
4221
4230
|
this.editorContainer.clear();
|
|
4222
4231
|
this.editorContainer.addChild(dialog);
|
|
4223
4232
|
this.ui.setFocus(dialog);
|
|
@@ -4228,6 +4237,13 @@ export class InteractiveMode {
|
|
|
4228
4237
|
const dialog = new LoginDialogComponent(this.ui, providerId, (_success, _message) => {
|
|
4229
4238
|
// Completion handled below
|
|
4230
4239
|
}, providerName);
|
|
4240
|
+
if (providerId === "amazon-bedrock") {
|
|
4241
|
+
dialog.showDetails([
|
|
4242
|
+
theme.fg("text", "You can also use an AWS profile, IAM keys, or role-based credentials."),
|
|
4243
|
+
theme.fg("muted", "See:"),
|
|
4244
|
+
theme.fg("accent", ` ${path.join(getDocsPath(), "providers.md")}`),
|
|
4245
|
+
]);
|
|
4246
|
+
}
|
|
4231
4247
|
this.editorContainer.clear();
|
|
4232
4248
|
this.editorContainer.addChild(dialog);
|
|
4233
4249
|
this.ui.setFocus(dialog);
|
|
@@ -4239,11 +4255,7 @@ export class InteractiveMode {
|
|
|
4239
4255
|
this.ui.requestRender();
|
|
4240
4256
|
};
|
|
4241
4257
|
try {
|
|
4242
|
-
|
|
4243
|
-
if (!apiKey) {
|
|
4244
|
-
throw new Error("API key cannot be empty.");
|
|
4245
|
-
}
|
|
4246
|
-
this.session.modelRegistry.authStorage.set(providerId, { type: "api_key", key: apiKey });
|
|
4258
|
+
await this.loginProvider(dialog, providerId, "api_key");
|
|
4247
4259
|
restoreEditor();
|
|
4248
4260
|
await this.completeProviderAuthentication(providerId, providerName, "api_key", previousModel);
|
|
4249
4261
|
}
|
|
@@ -4255,8 +4267,8 @@ export class InteractiveMode {
|
|
|
4255
4267
|
}
|
|
4256
4268
|
}
|
|
4257
4269
|
}
|
|
4258
|
-
|
|
4259
|
-
return new Promise((resolve) => {
|
|
4270
|
+
showAuthSelect(dialog, prompt) {
|
|
4271
|
+
return new Promise((resolve, reject) => {
|
|
4260
4272
|
const restoreDialog = () => {
|
|
4261
4273
|
this.editorContainer.clear();
|
|
4262
4274
|
this.editorContainer.addChild(dialog);
|
|
@@ -4266,10 +4278,14 @@ export class InteractiveMode {
|
|
|
4266
4278
|
const labels = prompt.options.map((option) => option.label);
|
|
4267
4279
|
const selector = new ExtensionSelectorComponent(prompt.message, labels, (optionLabel) => {
|
|
4268
4280
|
restoreDialog();
|
|
4269
|
-
|
|
4281
|
+
const id = prompt.options.find((option) => option.label === optionLabel)?.id;
|
|
4282
|
+
if (id)
|
|
4283
|
+
resolve(id);
|
|
4284
|
+
else
|
|
4285
|
+
reject(new Error("Login cancelled"));
|
|
4270
4286
|
}, () => {
|
|
4271
4287
|
restoreDialog();
|
|
4272
|
-
|
|
4288
|
+
reject(new Error("Login cancelled"));
|
|
4273
4289
|
});
|
|
4274
4290
|
this.editorContainer.clear();
|
|
4275
4291
|
this.editorContainer.addChild(selector);
|
|
@@ -4277,30 +4293,64 @@ export class InteractiveMode {
|
|
|
4277
4293
|
this.ui.requestRender();
|
|
4278
4294
|
});
|
|
4279
4295
|
}
|
|
4296
|
+
async showAuthPrompt(dialog, prompt) {
|
|
4297
|
+
let response;
|
|
4298
|
+
if (prompt.type === "select") {
|
|
4299
|
+
response = this.showAuthSelect(dialog, prompt);
|
|
4300
|
+
}
|
|
4301
|
+
else if (prompt.type === "manual_code") {
|
|
4302
|
+
response = dialog.showManualInput(prompt.message);
|
|
4303
|
+
}
|
|
4304
|
+
else {
|
|
4305
|
+
response = dialog.showPrompt(prompt.message, prompt.placeholder);
|
|
4306
|
+
}
|
|
4307
|
+
if (!prompt.signal)
|
|
4308
|
+
return response;
|
|
4309
|
+
if (prompt.signal.aborted)
|
|
4310
|
+
throw new Error("Login cancelled");
|
|
4311
|
+
const signal = prompt.signal;
|
|
4312
|
+
let onAbort;
|
|
4313
|
+
const aborted = new Promise((_resolve, reject) => {
|
|
4314
|
+
onAbort = () => reject(new Error("Login cancelled"));
|
|
4315
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
4316
|
+
});
|
|
4317
|
+
try {
|
|
4318
|
+
return await Promise.race([response, aborted]);
|
|
4319
|
+
}
|
|
4320
|
+
finally {
|
|
4321
|
+
if (onAbort)
|
|
4322
|
+
signal.removeEventListener("abort", onAbort);
|
|
4323
|
+
}
|
|
4324
|
+
}
|
|
4325
|
+
notifyAuthDialog(dialog, event) {
|
|
4326
|
+
if (event.type === "auth_url") {
|
|
4327
|
+
dialog.showAuth(event.url, event.instructions);
|
|
4328
|
+
}
|
|
4329
|
+
else if (event.type === "device_code") {
|
|
4330
|
+
dialog.showDeviceCode(event);
|
|
4331
|
+
dialog.showWaiting("Waiting for authentication...");
|
|
4332
|
+
}
|
|
4333
|
+
else if (event.type === "info") {
|
|
4334
|
+
dialog.showInfo(event.message, event.links);
|
|
4335
|
+
}
|
|
4336
|
+
else {
|
|
4337
|
+
dialog.showProgress(event.message);
|
|
4338
|
+
}
|
|
4339
|
+
}
|
|
4340
|
+
async loginProvider(dialog, providerId, method) {
|
|
4341
|
+
await this.session.modelRuntime.login(providerId, method, {
|
|
4342
|
+
signal: dialog.signal,
|
|
4343
|
+
prompt: (prompt) => this.showAuthPrompt(dialog, prompt),
|
|
4344
|
+
notify: (event) => this.notifyAuthDialog(dialog, event),
|
|
4345
|
+
});
|
|
4346
|
+
}
|
|
4280
4347
|
async showLoginDialog(providerId, providerName) {
|
|
4281
|
-
const providerInfo = this.session.modelRegistry.authStorage
|
|
4282
|
-
.getOAuthProviders()
|
|
4283
|
-
.find((provider) => provider.id === providerId);
|
|
4284
4348
|
const previousModel = this.session.model;
|
|
4285
|
-
|
|
4286
|
-
const usesCallbackServer = providerInfo?.usesCallbackServer ?? false;
|
|
4287
|
-
// Create login dialog component
|
|
4288
|
-
const dialog = new LoginDialogComponent(this.ui, providerId, (_success, _message) => {
|
|
4289
|
-
// Completion handled below
|
|
4290
|
-
}, providerName);
|
|
4291
|
-
// Show dialog in editor container
|
|
4349
|
+
const dialog = new LoginDialogComponent(this.ui, providerId, (_success, _message) => { }, providerName);
|
|
4292
4350
|
this.editorContainer.clear();
|
|
4293
4351
|
this.editorContainer.addChild(dialog);
|
|
4294
4352
|
this.ui.setFocus(dialog);
|
|
4295
4353
|
this.ui.requestRender();
|
|
4296
|
-
// Promise for manual code input (racing with callback server)
|
|
4297
|
-
let manualCodeResolve;
|
|
4298
|
-
let manualCodeReject;
|
|
4299
|
-
const manualCodePromise = new Promise((resolve, reject) => {
|
|
4300
|
-
manualCodeResolve = resolve;
|
|
4301
|
-
manualCodeReject = reject;
|
|
4302
|
-
});
|
|
4303
|
-
// Restore editor helper
|
|
4304
4354
|
const restoreEditor = () => {
|
|
4305
4355
|
this.editorContainer.clear();
|
|
4306
4356
|
this.editorContainer.addChild(this.editor);
|
|
@@ -4308,43 +4358,7 @@ export class InteractiveMode {
|
|
|
4308
4358
|
this.ui.requestRender();
|
|
4309
4359
|
};
|
|
4310
4360
|
try {
|
|
4311
|
-
await this.
|
|
4312
|
-
onAuth: (info) => {
|
|
4313
|
-
dialog.showAuth(info.url, info.instructions);
|
|
4314
|
-
if (usesCallbackServer) {
|
|
4315
|
-
// Show input for manual paste, racing with callback
|
|
4316
|
-
dialog
|
|
4317
|
-
.showManualInput("Paste redirect URL below, or complete login in browser:")
|
|
4318
|
-
.then((value) => {
|
|
4319
|
-
if (value && manualCodeResolve) {
|
|
4320
|
-
manualCodeResolve(value);
|
|
4321
|
-
manualCodeResolve = undefined;
|
|
4322
|
-
}
|
|
4323
|
-
})
|
|
4324
|
-
.catch(() => {
|
|
4325
|
-
if (manualCodeReject) {
|
|
4326
|
-
manualCodeReject(new Error("Login cancelled"));
|
|
4327
|
-
manualCodeReject = undefined;
|
|
4328
|
-
}
|
|
4329
|
-
});
|
|
4330
|
-
}
|
|
4331
|
-
// For Anthropic: onPrompt is called immediately after
|
|
4332
|
-
},
|
|
4333
|
-
onDeviceCode: (info) => {
|
|
4334
|
-
dialog.showDeviceCode(info);
|
|
4335
|
-
dialog.showWaiting("Waiting for authentication...");
|
|
4336
|
-
},
|
|
4337
|
-
onPrompt: async (prompt) => {
|
|
4338
|
-
return dialog.showPrompt(prompt.message, prompt.placeholder);
|
|
4339
|
-
},
|
|
4340
|
-
onProgress: (message) => {
|
|
4341
|
-
dialog.showProgress(message);
|
|
4342
|
-
},
|
|
4343
|
-
onSelect: (prompt) => this.showOAuthLoginSelect(dialog, prompt),
|
|
4344
|
-
onManualCodeInput: () => manualCodePromise,
|
|
4345
|
-
signal: dialog.signal,
|
|
4346
|
-
});
|
|
4347
|
-
// Success
|
|
4361
|
+
await this.loginProvider(dialog, providerId, "oauth");
|
|
4348
4362
|
restoreEditor();
|
|
4349
4363
|
await this.completeProviderAuthentication(providerId, providerName, "oauth", previousModel);
|
|
4350
4364
|
}
|
|
@@ -4432,7 +4446,7 @@ export class InteractiveMode {
|
|
|
4432
4446
|
showDiagnosticsWhenQuiet: true,
|
|
4433
4447
|
});
|
|
4434
4448
|
const savedImplicitProjectTrust = this.maybeSaveImplicitProjectTrustAfterReload();
|
|
4435
|
-
const modelsJsonError = this.session.
|
|
4449
|
+
const modelsJsonError = this.session.modelRuntime.getError();
|
|
4436
4450
|
if (modelsJsonError) {
|
|
4437
4451
|
this.showError(`models.json error: ${modelsJsonError}`);
|
|
4438
4452
|
}
|
|
@@ -4660,7 +4674,7 @@ export class InteractiveMode {
|
|
|
4660
4674
|
const stats = this.session.getSessionStats();
|
|
4661
4675
|
const sessionName = this.sessionManager.getSessionName();
|
|
4662
4676
|
const entries = this.sessionManager.getEntries();
|
|
4663
|
-
const cacheWaste = computeCacheWaste(entries, this.session.
|
|
4677
|
+
const cacheWaste = computeCacheWaste(entries, this.session.modelRuntime);
|
|
4664
4678
|
// Cost/token totals per provider/model actually used (e.g. OpenRouter `auto`
|
|
4665
4679
|
// resolves to a concrete responseModel), sorted by cost descending.
|
|
4666
4680
|
const perModelMap = new Map();
|
|
@@ -4793,6 +4807,7 @@ export class InteractiveMode {
|
|
|
4793
4807
|
const toggleThinking = this.getAppKeyDisplay("app.thinking.toggle");
|
|
4794
4808
|
const externalEditor = this.getAppKeyDisplay("app.editor.external");
|
|
4795
4809
|
const cycleModelBackward = this.getAppKeyDisplay("app.model.cycleBackward");
|
|
4810
|
+
const copyMessage = this.getAppKeyDisplay("app.message.copy");
|
|
4796
4811
|
const followUp = this.getAppKeyDisplay("app.message.followUp");
|
|
4797
4812
|
const dequeue = this.getAppKeyDisplay("app.message.dequeue");
|
|
4798
4813
|
const pasteImage = this.getAppKeyDisplay("app.clipboard.pasteImage");
|
|
@@ -4835,9 +4850,10 @@ export class InteractiveMode {
|
|
|
4835
4850
|
| \`${expandTools}\` | Toggle tool output expansion |
|
|
4836
4851
|
| \`${toggleThinking}\` | Toggle thinking block visibility |
|
|
4837
4852
|
| \`${externalEditor}\` | Edit message in external editor |
|
|
4853
|
+
| \`${copyMessage}\` | Copy last assistant message |
|
|
4838
4854
|
| \`${followUp}\` | Queue follow-up message |
|
|
4839
4855
|
| \`${dequeue}\` | Restore queued messages |
|
|
4840
|
-
| \`${pasteImage}\` | Paste image from clipboard |
|
|
4856
|
+
| \`${pasteImage}\` | Paste image or text from clipboard |
|
|
4841
4857
|
| \`/\` | Slash commands |
|
|
4842
4858
|
| \`!\` | Run bash command |
|
|
4843
4859
|
| \`!!\` | Run bash command (excluded from context) |
|