@eminent337/aery 0.1.117 → 0.1.119
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 +9 -9
- package/README.md +1 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +20 -13
- package/dist/cli/args.js.map +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/auth-storage.d.ts +1 -2
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +2 -2
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/extensions/loader.d.ts +0 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +32 -17
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/types.d.ts +14 -8
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/model-registry.d.ts +4 -6
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +17 -78
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +0 -2
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/package-manager.d.ts +0 -1
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +26 -15
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/provider-display-names.d.ts.map +1 -1
- package/dist/core/provider-display-names.js +0 -2
- package/dist/core/provider-display-names.js.map +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +0 -2
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +8 -31
- package/dist/main.js.map +1 -1
- package/dist/migrations.d.ts +4 -4
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +25 -29
- package/dist/migrations.js.map +1 -1
- package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
- package/dist/modes/interactive/components/login-dialog.js +0 -2
- package/dist/modes/interactive/components/login-dialog.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +16 -12
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +143 -304
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/package-manager-cli.d.ts +0 -1
- package/dist/package-manager-cli.d.ts.map +1 -1
- package/dist/package-manager-cli.js +57 -111
- package/dist/package-manager-cli.js.map +1 -1
- package/docs/development.md +1 -1
- package/docs/packages.md +1 -1
- package/docs/rpc.md +4 -4
- package/docs/usage.md +1 -1
- package/examples/extensions/preset.ts +1 -1
- package/examples/extensions/provider-payload.ts +1 -1
- package/examples/extensions/sandbox/index.ts +1 -1
- package/examples/extensions/subagent/agents.ts +1 -1
- package/package.json +94 -96
|
@@ -6,30 +6,28 @@ 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 "@eminent337/aery-ai";
|
|
10
|
-
import { CombinedAutocompleteProvider, Container, fuzzyFilter, Loader, Markdown, matchesKey, ProcessTerminal, Spacer, setKeybindings, Text, TruncatedText, TUI, visibleWidth, } from "@eminent337/aery-tui";
|
|
9
|
+
import { getProviders, } from "@eminent337/aery-ai";
|
|
10
|
+
import { CombinedAutocompleteProvider, Container, fuzzyFilter, getCapabilities, hyperlink, Loader, Markdown, matchesKey, ProcessTerminal, Spacer, setKeybindings, Text, TruncatedText, TUI, visibleWidth, } from "@eminent337/aery-tui";
|
|
11
11
|
import { spawn, spawnSync } from "child_process";
|
|
12
|
-
import {
|
|
13
|
-
import { formatCurrentCoreExtensionsReport } from "../../cli/doctor.js";
|
|
14
|
-
import { APP_NAME, getAgentDir, getAuthPath, getDebugLogPath, getModelsPath, getShareViewerUrl, getUpdateInstruction, VERSION, } from "../../config.js";
|
|
12
|
+
import { APP_NAME, APP_TITLE, getAgentDir, getAuthPath, getDebugLogPath, getDocsPath, getShareViewerUrl, VERSION, } from "../../config.js";
|
|
15
13
|
import { parseSkillBlock } from "../../core/agent-session.js";
|
|
16
14
|
import { SessionImportFileNotFoundError } from "../../core/agent-session-runtime.js";
|
|
17
|
-
import { CUSTOM_OPENAI_COMPATIBLE_PROVIDER_ID, saveCustomOpenAICompatibleProvider, } from "../../core/custom-openai-compatible.js";
|
|
18
15
|
import { FooterDataProvider } from "../../core/footer-data-provider.js";
|
|
19
16
|
import { KeybindingsManager } from "../../core/keybindings.js";
|
|
20
17
|
import { createCompactionSummaryMessage } from "../../core/messages.js";
|
|
21
18
|
import { defaultModelPerProvider, findExactModelReferenceMatch, resolveModelScope } from "../../core/model-resolver.js";
|
|
22
19
|
import { DefaultPackageManager } from "../../core/package-manager.js";
|
|
23
|
-
import {
|
|
20
|
+
import { BUILT_IN_PROVIDER_DISPLAY_NAMES } from "../../core/provider-display-names.js";
|
|
24
21
|
import { formatMissingSessionCwdPrompt, MissingSessionCwdError } from "../../core/session-cwd.js";
|
|
25
22
|
import { SessionManager } from "../../core/session-manager.js";
|
|
26
23
|
import { BUILTIN_SLASH_COMMANDS } from "../../core/slash-commands.js";
|
|
27
24
|
import { isInstallTelemetryEnabled } from "../../core/telemetry.js";
|
|
28
|
-
import { wireCoreExtensions } from "../../migrations.js";
|
|
29
25
|
import { getChangelogPath, getNewEntries, parseChangelog } from "../../utils/changelog.js";
|
|
30
26
|
import { copyToClipboard } from "../../utils/clipboard.js";
|
|
31
27
|
import { extensionForImageMimeType, readClipboardImage } from "../../utils/clipboard-image.js";
|
|
32
28
|
import { parseGitUrl } from "../../utils/git.js";
|
|
29
|
+
import { getCwdRelativePath } from "../../utils/paths.js";
|
|
30
|
+
import { getPiUserAgent } from "../../utils/pi-user-agent.js";
|
|
33
31
|
import { killTrackedDetachedChildren } from "../../utils/shell.js";
|
|
34
32
|
import { ensureTool } from "../../utils/tools-manager.js";
|
|
35
33
|
import { checkForNewPiVersion } from "../../utils/version-check.js";
|
|
@@ -49,7 +47,7 @@ import { ExtensionEditorComponent } from "./components/extension-editor.js";
|
|
|
49
47
|
import { ExtensionInputComponent } from "./components/extension-input.js";
|
|
50
48
|
import { ExtensionSelectorComponent } from "./components/extension-selector.js";
|
|
51
49
|
import { FooterComponent } from "./components/footer.js";
|
|
52
|
-
import { keyHint, keyText, rawKeyHint } from "./components/keybinding-hints.js";
|
|
50
|
+
import { formatKeyText, keyDisplayText, keyHint, keyText, rawKeyHint } from "./components/keybinding-hints.js";
|
|
53
51
|
import { LoginDialogComponent } from "./components/login-dialog.js";
|
|
54
52
|
import { ModelSelectorComponent } from "./components/model-selector.js";
|
|
55
53
|
import { OAuthSelectorComponent } from "./components/oauth-selector.js";
|
|
@@ -95,47 +93,17 @@ function isUnknownModel(model) {
|
|
|
95
93
|
function hasDefaultModelProvider(providerId) {
|
|
96
94
|
return providerId in defaultModelPerProvider;
|
|
97
95
|
}
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
"cloudflare-workers-ai": "Cloudflare Workers AI",
|
|
103
|
-
fireworks: "Fireworks",
|
|
104
|
-
google: "Google Gemini",
|
|
105
|
-
"google-vertex": "Google Vertex AI",
|
|
106
|
-
groq: "Groq",
|
|
107
|
-
huggingface: "Hugging Face",
|
|
108
|
-
"kimi-coding": "Kimi For Coding",
|
|
109
|
-
mistral: "Mistral",
|
|
110
|
-
minimax: "MiniMax",
|
|
111
|
-
"minimax-cn": "MiniMax (China)",
|
|
112
|
-
opencode: "OpenCode Zen",
|
|
113
|
-
"opencode-go": "OpenCode Go",
|
|
114
|
-
openai: "OpenAI",
|
|
115
|
-
openrouter: "OpenRouter",
|
|
116
|
-
"vercel-ai-gateway": "Vercel AI Gateway",
|
|
117
|
-
xai: "xAI",
|
|
118
|
-
zai: "ZAI",
|
|
119
|
-
};
|
|
120
|
-
const API_KEY_LOGIN_PROVIDER_BLOCKLIST = new Set(["amazon-bedrock", "llama.cpp", "lmstudio", "ollama"]);
|
|
121
|
-
const CUSTOM_OPENAI_COMPATIBLE_PROVIDER_LABEL = "Custom OpenAI-compatible";
|
|
122
|
-
const AERY_GATEWAY_PROVIDER_ID = "__aery-gateway__";
|
|
123
|
-
const AERY_GATEWAY_BASE_URL = "https://aery-gateway.eminent337.workers.dev/v1";
|
|
124
|
-
const AERY_GATEWAY_PROVIDER_APIS = {
|
|
125
|
-
anthropic: "anthropic-messages",
|
|
126
|
-
"google-generative-ai": "google-generative-ai",
|
|
127
|
-
"openai-responses": "openai-responses",
|
|
128
|
-
};
|
|
129
|
-
export function isApiKeyLoginProvider(providerId, oauthProviderIds, builtInProviderIds = new Set(getProviders())) {
|
|
130
|
-
if (API_KEY_PROVIDER_NAMES[providerId])
|
|
96
|
+
const BEDROCK_PROVIDER_ID = "amazon-bedrock";
|
|
97
|
+
const BUILT_IN_MODEL_PROVIDERS = new Set(getProviders());
|
|
98
|
+
export function isApiKeyLoginProvider(providerId, oauthProviderIds, builtInProviderIds = BUILT_IN_MODEL_PROVIDERS) {
|
|
99
|
+
if (BUILT_IN_PROVIDER_DISPLAY_NAMES[providerId]) {
|
|
131
100
|
return true;
|
|
132
|
-
|
|
101
|
+
}
|
|
102
|
+
if (builtInProviderIds.has(providerId)) {
|
|
133
103
|
return false;
|
|
104
|
+
}
|
|
134
105
|
return !oauthProviderIds.has(providerId);
|
|
135
106
|
}
|
|
136
|
-
export function getApiKeyProviderDisplayName(providerId) {
|
|
137
|
-
return API_KEY_PROVIDER_NAMES[providerId] ?? providerId;
|
|
138
|
-
}
|
|
139
107
|
export class InteractiveMode {
|
|
140
108
|
options;
|
|
141
109
|
runtimeHost;
|
|
@@ -145,7 +113,7 @@ export class InteractiveMode {
|
|
|
145
113
|
statusContainer;
|
|
146
114
|
defaultEditor;
|
|
147
115
|
editor;
|
|
148
|
-
|
|
116
|
+
editorComponentFactory;
|
|
149
117
|
autocompleteProvider;
|
|
150
118
|
autocompleteProviderWrappers = [];
|
|
151
119
|
fdPath;
|
|
@@ -457,7 +425,7 @@ export class InteractiveMode {
|
|
|
457
425
|
hint("app.tools.expand", "more"),
|
|
458
426
|
].join(theme.fg("muted", " · "));
|
|
459
427
|
const compactOnboarding = theme.fg("dim", `Press ${keyText("app.tools.expand")} to show full startup help and loaded resources.`);
|
|
460
|
-
const onboarding = theme.fg("dim", `
|
|
428
|
+
const onboarding = theme.fg("dim", `Pi can explain its own features and look up its docs. Ask it how to use or extend Pi.`);
|
|
461
429
|
this.builtInHeader = new ExpandableText(() => `${logo}\n${compactInstructions}\n${compactOnboarding}\n\n${onboarding}`, () => `${logo}\n${expandedInstructions}\n\n${onboarding}`, this.getStartupExpansionState(), 1, 0);
|
|
462
430
|
// Setup UI layout
|
|
463
431
|
this.headerContainer.addChild(new Spacer(1));
|
|
@@ -507,10 +475,10 @@ export class InteractiveMode {
|
|
|
507
475
|
const cwdBasename = path.basename(this.sessionManager.getCwd());
|
|
508
476
|
const sessionName = this.sessionManager.getSessionName();
|
|
509
477
|
if (sessionName) {
|
|
510
|
-
this.ui.terminal.setTitle(
|
|
478
|
+
this.ui.terminal.setTitle(`${APP_TITLE} - ${sessionName} - ${cwdBasename}`);
|
|
511
479
|
}
|
|
512
480
|
else {
|
|
513
|
-
this.ui.terminal.setTitle(
|
|
481
|
+
this.ui.terminal.setTitle(`${APP_TITLE} - ${cwdBasename}`);
|
|
514
482
|
}
|
|
515
483
|
}
|
|
516
484
|
/**
|
|
@@ -637,7 +605,7 @@ export class InteractiveMode {
|
|
|
637
605
|
return "tmux extended-keys is off. Modified Enter keys may not work. Add `set -g extended-keys on` to ~/.tmux.conf and restart tmux.";
|
|
638
606
|
}
|
|
639
607
|
if (extendedKeysFormat === "xterm") {
|
|
640
|
-
return "tmux extended-keys-format is xterm.
|
|
608
|
+
return "tmux extended-keys-format is xterm. Pi works best with csi-u. Add `set -g extended-keys-format csi-u` to ~/.tmux.conf and restart tmux.";
|
|
641
609
|
}
|
|
642
610
|
return undefined;
|
|
643
611
|
}
|
|
@@ -657,8 +625,6 @@ export class InteractiveMode {
|
|
|
657
625
|
// Fresh install - record the version, send telemetry, don't show changelog
|
|
658
626
|
this.settingsManager.setLastChangelogVersion(VERSION);
|
|
659
627
|
this.reportInstallTelemetry(VERSION);
|
|
660
|
-
// Auto-install core extension pack on fresh install
|
|
661
|
-
this.installCorePackIfNeeded();
|
|
662
628
|
return undefined;
|
|
663
629
|
}
|
|
664
630
|
const newEntries = getNewEntries(entries, lastVersion);
|
|
@@ -676,37 +642,15 @@ export class InteractiveMode {
|
|
|
676
642
|
if (!isInstallTelemetryEnabled(this.settingsManager)) {
|
|
677
643
|
return;
|
|
678
644
|
}
|
|
679
|
-
void fetch(`https://
|
|
645
|
+
void fetch(`https://eminent337.github.io/api/report-install?version=${encodeURIComponent(version)}`, {
|
|
646
|
+
headers: {
|
|
647
|
+
"User-Agent": getPiUserAgent(version),
|
|
648
|
+
},
|
|
680
649
|
signal: AbortSignal.timeout(5000),
|
|
681
650
|
})
|
|
682
651
|
.then(() => undefined)
|
|
683
652
|
.catch(() => undefined);
|
|
684
653
|
}
|
|
685
|
-
installCorePackIfNeeded() {
|
|
686
|
-
// Fire-and-forget: install core extension pack on fresh install
|
|
687
|
-
void (async () => {
|
|
688
|
-
try {
|
|
689
|
-
const { execFile } = await import("node:child_process");
|
|
690
|
-
const { promisify } = await import("node:util");
|
|
691
|
-
const { existsSync, readFileSync, writeFileSync } = await import("node:fs");
|
|
692
|
-
const { join } = await import("node:path");
|
|
693
|
-
const { homedir } = await import("node:os");
|
|
694
|
-
const exec = promisify(execFile);
|
|
695
|
-
await exec("aery", ["install", "https://github.com/eminent337/aery-extensions"], {
|
|
696
|
-
timeout: 30000,
|
|
697
|
-
});
|
|
698
|
-
// Wire core extensions into settings.json
|
|
699
|
-
const repoPath = join(homedir(), ".aery", "agent", "git", "github.com", "eminent337", "aery-extensions");
|
|
700
|
-
const settingsPath = join(homedir(), ".aery", "agent", "settings.json");
|
|
701
|
-
wireCoreExtensions(repoPath, settingsPath);
|
|
702
|
-
const settings = existsSync(settingsPath) ? JSON.parse(readFileSync(settingsPath, "utf-8")) : {};
|
|
703
|
-
writeFileSync(settingsPath, `${JSON.stringify({ ...settings, quietStartup: true }, null, 2)}\n`);
|
|
704
|
-
}
|
|
705
|
-
catch {
|
|
706
|
-
// Silent fail — user can install manually
|
|
707
|
-
}
|
|
708
|
-
})();
|
|
709
|
-
}
|
|
710
654
|
getMarkdownThemeWithSettings() {
|
|
711
655
|
return {
|
|
712
656
|
...getMarkdownTheme(),
|
|
@@ -733,13 +677,9 @@ export class InteractiveMode {
|
|
|
733
677
|
formatContextPath(p) {
|
|
734
678
|
const cwd = path.resolve(this.sessionManager.getCwd());
|
|
735
679
|
const absolutePath = path.isAbsolute(p) ? path.resolve(p) : path.resolve(cwd, p);
|
|
736
|
-
const relativePath =
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
!relativePath.startsWith(`..${path.sep}`) &&
|
|
740
|
-
!path.isAbsolute(relativePath));
|
|
741
|
-
if (isInsideCwd) {
|
|
742
|
-
return relativePath || ".";
|
|
680
|
+
const relativePath = getCwdRelativePath(absolutePath, cwd);
|
|
681
|
+
if (relativePath !== undefined) {
|
|
682
|
+
return relativePath;
|
|
743
683
|
}
|
|
744
684
|
return this.formatDisplayPath(absolutePath);
|
|
745
685
|
}
|
|
@@ -1601,7 +1541,7 @@ export class InteractiveMode {
|
|
|
1601
1541
|
this.setupAutocompleteProvider();
|
|
1602
1542
|
},
|
|
1603
1543
|
setEditorComponent: (factory) => this.setCustomEditorComponent(factory),
|
|
1604
|
-
getEditorComponent: () => this.
|
|
1544
|
+
getEditorComponent: () => this.editorComponentFactory,
|
|
1605
1545
|
get theme() {
|
|
1606
1546
|
return theme;
|
|
1607
1547
|
},
|
|
@@ -1648,7 +1588,7 @@ export class InteractiveMode {
|
|
|
1648
1588
|
opts?.signal?.removeEventListener("abort", onAbort);
|
|
1649
1589
|
this.hideExtensionSelector();
|
|
1650
1590
|
resolve(undefined);
|
|
1651
|
-
}, { tui: this.ui, timeout: opts?.timeout });
|
|
1591
|
+
}, { tui: this.ui, timeout: opts?.timeout, onToggleToolsExpanded: () => this.toggleToolOutputExpansion() });
|
|
1652
1592
|
this.editorContainer.clear();
|
|
1653
1593
|
this.editorContainer.addChild(this.extensionSelector);
|
|
1654
1594
|
this.ui.setFocus(this.extensionSelector);
|
|
@@ -1750,7 +1690,7 @@ export class InteractiveMode {
|
|
|
1750
1690
|
* Pass undefined to restore the default editor.
|
|
1751
1691
|
*/
|
|
1752
1692
|
setCustomEditorComponent(factory) {
|
|
1753
|
-
this.
|
|
1693
|
+
this.editorComponentFactory = factory;
|
|
1754
1694
|
// Save text from current editor before switching
|
|
1755
1695
|
const currentText = this.editor.getText();
|
|
1756
1696
|
this.editorContainer.clear();
|
|
@@ -1986,7 +1926,7 @@ export class InteractiveMode {
|
|
|
1986
1926
|
// Write to temp file
|
|
1987
1927
|
const tmpDir = os.tmpdir();
|
|
1988
1928
|
const ext = extensionForImageMimeType(image.mimeType) ?? "png";
|
|
1989
|
-
const fileName = `
|
|
1929
|
+
const fileName = `pi-clipboard-${crypto.randomUUID()}.${ext}`;
|
|
1990
1930
|
const filePath = path.join(tmpDir, fileName);
|
|
1991
1931
|
fs.writeFileSync(filePath, Buffer.from(image.bytes));
|
|
1992
1932
|
// Insert file path directly
|
|
@@ -2049,11 +1989,6 @@ export class InteractiveMode {
|
|
|
2049
1989
|
this.editor.setText("");
|
|
2050
1990
|
return;
|
|
2051
1991
|
}
|
|
2052
|
-
if (text === "/capabilities") {
|
|
2053
|
-
this.handleCapabilitiesCommand();
|
|
2054
|
-
this.editor.setText("");
|
|
2055
|
-
return;
|
|
2056
|
-
}
|
|
2057
1992
|
if (text === "/changelog") {
|
|
2058
1993
|
this.handleChangelogCommand();
|
|
2059
1994
|
this.editor.setText("");
|
|
@@ -2079,11 +2014,6 @@ export class InteractiveMode {
|
|
|
2079
2014
|
this.editor.setText("");
|
|
2080
2015
|
return;
|
|
2081
2016
|
}
|
|
2082
|
-
if (text === "/extensions doctor" || text === "/extensions") {
|
|
2083
|
-
this.handleExtensionsDoctorCommand();
|
|
2084
|
-
this.editor.setText("");
|
|
2085
|
-
return;
|
|
2086
|
-
}
|
|
2087
2017
|
if (text === "/login") {
|
|
2088
2018
|
this.showOAuthSelector("login");
|
|
2089
2019
|
this.editor.setText("");
|
|
@@ -2195,6 +2125,7 @@ export class InteractiveMode {
|
|
|
2195
2125
|
this.footer.invalidate();
|
|
2196
2126
|
switch (event.type) {
|
|
2197
2127
|
case "agent_start":
|
|
2128
|
+
this.pendingTools.clear();
|
|
2198
2129
|
if (this.settingsManager.getShowTerminalProgress()) {
|
|
2199
2130
|
this.ui.terminal.setProgress(true);
|
|
2200
2131
|
}
|
|
@@ -2228,6 +2159,10 @@ export class InteractiveMode {
|
|
|
2228
2159
|
this.footer.invalidate();
|
|
2229
2160
|
this.ui.requestRender();
|
|
2230
2161
|
break;
|
|
2162
|
+
case "thinking_level_changed":
|
|
2163
|
+
this.footer.invalidate();
|
|
2164
|
+
this.updateEditorBorderColor();
|
|
2165
|
+
break;
|
|
2231
2166
|
case "message_start":
|
|
2232
2167
|
if (event.message.role === "custom") {
|
|
2233
2168
|
this.addMessageToChat(event.message);
|
|
@@ -2581,6 +2516,7 @@ export class InteractiveMode {
|
|
|
2581
2516
|
*/
|
|
2582
2517
|
renderSessionContext(sessionContext, options = {}) {
|
|
2583
2518
|
this.pendingTools.clear();
|
|
2519
|
+
const renderedPendingTools = new Map();
|
|
2584
2520
|
if (options.updateFooter) {
|
|
2585
2521
|
this.footer.invalidate();
|
|
2586
2522
|
this.updateEditorBorderColor();
|
|
@@ -2613,17 +2549,17 @@ export class InteractiveMode {
|
|
|
2613
2549
|
component.updateResult({ content: [{ type: "text", text: errorMessage }], isError: true });
|
|
2614
2550
|
}
|
|
2615
2551
|
else {
|
|
2616
|
-
|
|
2552
|
+
renderedPendingTools.set(content.id, component);
|
|
2617
2553
|
}
|
|
2618
2554
|
}
|
|
2619
2555
|
}
|
|
2620
2556
|
}
|
|
2621
2557
|
else if (message.role === "toolResult") {
|
|
2622
2558
|
// Match tool results to pending tool components
|
|
2623
|
-
const component =
|
|
2559
|
+
const component = renderedPendingTools.get(message.toolCallId);
|
|
2624
2560
|
if (component) {
|
|
2625
2561
|
component.updateResult(message);
|
|
2626
|
-
|
|
2562
|
+
renderedPendingTools.delete(message.toolCallId);
|
|
2627
2563
|
}
|
|
2628
2564
|
}
|
|
2629
2565
|
else {
|
|
@@ -2631,6 +2567,9 @@ export class InteractiveMode {
|
|
|
2631
2567
|
this.addMessageToChat(message, options);
|
|
2632
2568
|
}
|
|
2633
2569
|
}
|
|
2570
|
+
for (const [toolCallId, component] of renderedPendingTools) {
|
|
2571
|
+
this.pendingTools.set(toolCallId, component);
|
|
2572
|
+
}
|
|
2634
2573
|
this.ui.requestRender();
|
|
2635
2574
|
}
|
|
2636
2575
|
renderInitialMessages() {
|
|
@@ -2704,6 +2643,38 @@ export class InteractiveMode {
|
|
|
2704
2643
|
// extension cleanup can write restore sequences and re-trigger EIO.
|
|
2705
2644
|
process.exit(129);
|
|
2706
2645
|
}
|
|
2646
|
+
/**
|
|
2647
|
+
* Last-resort handler for uncaught exceptions. The TUI puts stdin into raw
|
|
2648
|
+
* mode and hides the cursor; without this handler, an uncaught throw from
|
|
2649
|
+
* anywhere (e.g. an extension's async `ChildProcess.on("exit")` callback)
|
|
2650
|
+
* tears down the process while leaving the terminal in raw mode with no
|
|
2651
|
+
* cursor, requiring `stty sane && reset` to recover.
|
|
2652
|
+
*
|
|
2653
|
+
* Unlike emergencyTerminalExit, the terminal is still alive here, so we
|
|
2654
|
+
* call ui.stop() to restore cooked mode, the cursor, and disable bracketed
|
|
2655
|
+
* paste / Kitty / modifyOtherKeys sequences.
|
|
2656
|
+
*/
|
|
2657
|
+
uncaughtCrash(error) {
|
|
2658
|
+
if (this.isShuttingDown) {
|
|
2659
|
+
process.exit(1);
|
|
2660
|
+
}
|
|
2661
|
+
this.isShuttingDown = true;
|
|
2662
|
+
try {
|
|
2663
|
+
this.unregisterSignalHandlers();
|
|
2664
|
+
}
|
|
2665
|
+
catch { }
|
|
2666
|
+
try {
|
|
2667
|
+
killTrackedDetachedChildren();
|
|
2668
|
+
}
|
|
2669
|
+
catch { }
|
|
2670
|
+
try {
|
|
2671
|
+
this.ui.stop();
|
|
2672
|
+
}
|
|
2673
|
+
catch { }
|
|
2674
|
+
console.error("pi exiting due to uncaughtException:");
|
|
2675
|
+
console.error(error);
|
|
2676
|
+
process.exit(1);
|
|
2677
|
+
}
|
|
2707
2678
|
/**
|
|
2708
2679
|
* Check if shutdown was requested and perform shutdown if so.
|
|
2709
2680
|
*/
|
|
@@ -2739,6 +2710,12 @@ export class InteractiveMode {
|
|
|
2739
2710
|
process.stderr.on("error", terminalErrorHandler);
|
|
2740
2711
|
this.signalCleanupHandlers.push(() => process.stdout.off("error", terminalErrorHandler));
|
|
2741
2712
|
this.signalCleanupHandlers.push(() => process.stderr.off("error", terminalErrorHandler));
|
|
2713
|
+
// Restore the terminal before the process dies on any uncaught throw.
|
|
2714
|
+
// Without this, an unhandled exception from extension code (or anywhere
|
|
2715
|
+
// in pi) leaves the terminal in raw mode with no cursor.
|
|
2716
|
+
const uncaughtExceptionHandler = (error) => this.uncaughtCrash(error);
|
|
2717
|
+
process.prependListener("uncaughtException", uncaughtExceptionHandler);
|
|
2718
|
+
this.signalCleanupHandlers.push(() => process.off("uncaughtException", uncaughtExceptionHandler));
|
|
2742
2719
|
}
|
|
2743
2720
|
unregisterSignalHandlers() {
|
|
2744
2721
|
for (const cleanup of this.signalCleanupHandlers) {
|
|
@@ -2805,6 +2782,7 @@ export class InteractiveMode {
|
|
|
2805
2782
|
}
|
|
2806
2783
|
// If not streaming, Alt+Enter acts like regular Enter (trigger onSubmit)
|
|
2807
2784
|
else if (this.editor.onSubmit) {
|
|
2785
|
+
this.editor.setText("");
|
|
2808
2786
|
this.editor.onSubmit(text);
|
|
2809
2787
|
}
|
|
2810
2788
|
}
|
|
@@ -2895,7 +2873,7 @@ export class InteractiveMode {
|
|
|
2895
2873
|
return;
|
|
2896
2874
|
}
|
|
2897
2875
|
const currentText = this.editor.getExpandedText?.() ?? this.editor.getText();
|
|
2898
|
-
const tmpFile = path.join(os.tmpdir(), `
|
|
2876
|
+
const tmpFile = path.join(os.tmpdir(), `pi-editor-${Date.now()}.pi.md`);
|
|
2899
2877
|
try {
|
|
2900
2878
|
// Write current content to temp file
|
|
2901
2879
|
fs.writeFileSync(tmpFile, currentText, "utf-8");
|
|
@@ -2939,6 +2917,7 @@ export class InteractiveMode {
|
|
|
2939
2917
|
showError(errorMessage) {
|
|
2940
2918
|
this.chatContainer.addChild(new Spacer(1));
|
|
2941
2919
|
this.chatContainer.addChild(new Text(theme.fg("error", `Error: ${errorMessage}`), 1, 0));
|
|
2920
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
2942
2921
|
this.ui.requestRender();
|
|
2943
2922
|
}
|
|
2944
2923
|
showWarning(warningMessage) {
|
|
@@ -2947,10 +2926,13 @@ export class InteractiveMode {
|
|
|
2947
2926
|
this.ui.requestRender();
|
|
2948
2927
|
}
|
|
2949
2928
|
showNewVersionNotification(newVersion) {
|
|
2950
|
-
const action = theme.fg("accent",
|
|
2951
|
-
const updateInstruction = theme.fg("muted", `New version ${newVersion} is available. `) + action;
|
|
2952
|
-
const changelogUrl =
|
|
2953
|
-
const
|
|
2929
|
+
const action = theme.fg("accent", `${APP_NAME} update`);
|
|
2930
|
+
const updateInstruction = theme.fg("muted", `New version ${newVersion} is available. Run `) + action;
|
|
2931
|
+
const changelogUrl = "https://github.com/eminent337/aery/blob/main/packages/coding-agent/CHANGELOG.md";
|
|
2932
|
+
const changelogLink = getCapabilities().hyperlinks
|
|
2933
|
+
? hyperlink(theme.fg("accent", "open changelog"), changelogUrl)
|
|
2934
|
+
: theme.fg("accent", changelogUrl);
|
|
2935
|
+
const changelogLine = theme.fg("muted", "Changelog: ") + changelogLink;
|
|
2954
2936
|
this.chatContainer.addChild(new Spacer(1));
|
|
2955
2937
|
this.chatContainer.addChild(new DynamicBorder((text) => theme.fg("warning", text)));
|
|
2956
2938
|
this.chatContainer.addChild(new Text(`${theme.bold(theme.fg("warning", "Update Available"))}\n${updateInstruction}\n${changelogLine}`, 1, 0));
|
|
@@ -3689,24 +3671,12 @@ export class InteractiveMode {
|
|
|
3689
3671
|
}));
|
|
3690
3672
|
const modelProviders = new Set(this.session.modelRegistry.getAll().map((model) => model.provider));
|
|
3691
3673
|
for (const providerId of modelProviders) {
|
|
3692
|
-
if (
|
|
3674
|
+
if (!isApiKeyLoginProvider(providerId, oauthProviderIds)) {
|
|
3693
3675
|
continue;
|
|
3694
3676
|
}
|
|
3695
3677
|
options.push({
|
|
3696
3678
|
id: providerId,
|
|
3697
|
-
name:
|
|
3698
|
-
authType: "api_key",
|
|
3699
|
-
});
|
|
3700
|
-
}
|
|
3701
|
-
if (!authType || authType === "api_key") {
|
|
3702
|
-
options.push({
|
|
3703
|
-
id: AERY_GATEWAY_PROVIDER_ID,
|
|
3704
|
-
name: "Aery Gateway",
|
|
3705
|
-
authType: "api_key",
|
|
3706
|
-
});
|
|
3707
|
-
options.push({
|
|
3708
|
-
id: CUSTOM_OPENAI_COMPATIBLE_PROVIDER_ID,
|
|
3709
|
-
name: CUSTOM_OPENAI_COMPATIBLE_PROVIDER_LABEL,
|
|
3679
|
+
name: this.session.modelRegistry.getProviderDisplayName(providerId),
|
|
3710
3680
|
authType: "api_key",
|
|
3711
3681
|
});
|
|
3712
3682
|
}
|
|
@@ -3715,7 +3685,6 @@ export class InteractiveMode {
|
|
|
3715
3685
|
}
|
|
3716
3686
|
getLogoutProviderOptions() {
|
|
3717
3687
|
const authStorage = this.session.modelRegistry.authStorage;
|
|
3718
|
-
const oauthNameById = new Map(authStorage.getOAuthProviders().map((provider) => [provider.id, provider.name]));
|
|
3719
3688
|
const options = [];
|
|
3720
3689
|
for (const providerId of authStorage.list()) {
|
|
3721
3690
|
const credential = authStorage.get(providerId);
|
|
@@ -3724,9 +3693,7 @@ export class InteractiveMode {
|
|
|
3724
3693
|
}
|
|
3725
3694
|
options.push({
|
|
3726
3695
|
id: providerId,
|
|
3727
|
-
name:
|
|
3728
|
-
? (oauthNameById.get(providerId) ?? providerId)
|
|
3729
|
-
: getApiKeyProviderDisplayName(providerId),
|
|
3696
|
+
name: this.session.modelRegistry.getProviderDisplayName(providerId),
|
|
3730
3697
|
authType: credential.type,
|
|
3731
3698
|
});
|
|
3732
3699
|
}
|
|
@@ -3760,22 +3727,19 @@ export class InteractiveMode {
|
|
|
3760
3727
|
if (!providerOption) {
|
|
3761
3728
|
return;
|
|
3762
3729
|
}
|
|
3763
|
-
if (providerOption.
|
|
3764
|
-
await this.showAeryGatewayLoginDialog();
|
|
3765
|
-
}
|
|
3766
|
-
else if (providerOption.id === CUSTOM_OPENAI_COMPATIBLE_PROVIDER_ID) {
|
|
3767
|
-
await this.showCustomOpenAICompatibleLoginDialog();
|
|
3768
|
-
}
|
|
3769
|
-
else if (providerOption.authType === "oauth") {
|
|
3730
|
+
if (providerOption.authType === "oauth") {
|
|
3770
3731
|
await this.showLoginDialog(providerOption.id, providerOption.name);
|
|
3771
3732
|
}
|
|
3733
|
+
else if (providerOption.id === BEDROCK_PROVIDER_ID) {
|
|
3734
|
+
this.showBedrockSetupDialog(providerOption.id, providerOption.name);
|
|
3735
|
+
}
|
|
3772
3736
|
else {
|
|
3773
3737
|
await this.showApiKeyLoginDialog(providerOption.id, providerOption.name);
|
|
3774
3738
|
}
|
|
3775
3739
|
}, () => {
|
|
3776
3740
|
done();
|
|
3777
3741
|
this.showLoginAuthTypeSelector();
|
|
3778
|
-
});
|
|
3742
|
+
}, (providerId) => this.session.modelRegistry.getProviderAuthStatus(providerId));
|
|
3779
3743
|
return { component: selector, focus: selector };
|
|
3780
3744
|
});
|
|
3781
3745
|
}
|
|
@@ -3786,7 +3750,7 @@ export class InteractiveMode {
|
|
|
3786
3750
|
}
|
|
3787
3751
|
const providerOptions = this.getLogoutProviderOptions();
|
|
3788
3752
|
if (providerOptions.length === 0) {
|
|
3789
|
-
this.showStatus("No
|
|
3753
|
+
this.showStatus("No stored credentials to remove. /logout only removes credentials saved by /login; environment variables and models.json config are unchanged.");
|
|
3790
3754
|
return;
|
|
3791
3755
|
}
|
|
3792
3756
|
this.showSelector((done) => {
|
|
@@ -3802,7 +3766,7 @@ export class InteractiveMode {
|
|
|
3802
3766
|
await this.updateAvailableProviderCount();
|
|
3803
3767
|
const message = providerOption.authType === "oauth"
|
|
3804
3768
|
? `Logged out of ${providerOption.name}`
|
|
3805
|
-
: `Removed API key for ${providerOption.name}
|
|
3769
|
+
: `Removed stored API key for ${providerOption.name}. Environment variables and models.json config are unchanged.`;
|
|
3806
3770
|
this.showStatus(message);
|
|
3807
3771
|
}
|
|
3808
3772
|
catch (error) {
|
|
@@ -3864,117 +3828,31 @@ export class InteractiveMode {
|
|
|
3864
3828
|
void this.maybeWarnAboutAnthropicSubscriptionAuth();
|
|
3865
3829
|
}
|
|
3866
3830
|
}
|
|
3867
|
-
const providerCheck = checkProviderSetup(providerId, providerName, this.session.modelRegistry);
|
|
3868
|
-
if (providerCheck.level === "ok") {
|
|
3869
|
-
this.showStatus(providerCheck.message);
|
|
3870
|
-
}
|
|
3871
|
-
else if (providerCheck.level === "warning") {
|
|
3872
|
-
this.showWarning(providerCheck.message);
|
|
3873
|
-
}
|
|
3874
|
-
else {
|
|
3875
|
-
this.showError(providerCheck.message);
|
|
3876
|
-
}
|
|
3877
3831
|
}
|
|
3878
|
-
|
|
3879
|
-
const previousModel = this.session.model;
|
|
3880
|
-
const dialog = new LoginDialogComponent(this.ui, providerId, (_success, _message) => {
|
|
3881
|
-
// Completion handled below
|
|
3882
|
-
}, providerName);
|
|
3883
|
-
this.editorContainer.clear();
|
|
3884
|
-
this.editorContainer.addChild(dialog);
|
|
3885
|
-
this.ui.setFocus(dialog);
|
|
3886
|
-
this.ui.requestRender();
|
|
3832
|
+
showBedrockSetupDialog(providerId, providerName) {
|
|
3887
3833
|
const restoreEditor = () => {
|
|
3888
3834
|
this.editorContainer.clear();
|
|
3889
3835
|
this.editorContainer.addChild(this.editor);
|
|
3890
3836
|
this.ui.setFocus(this.editor);
|
|
3891
3837
|
this.ui.requestRender();
|
|
3892
3838
|
};
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
}
|
|
3899
|
-
|
|
3900
|
-
const accountId = (await dialog.showPrompt("Enter Cloudflare account ID:")).trim();
|
|
3901
|
-
if (!accountId) {
|
|
3902
|
-
throw new Error("Cloudflare account ID cannot be empty.");
|
|
3903
|
-
}
|
|
3904
|
-
this.session.modelRegistry.authStorage.set(providerId, { type: "api_key", key: apiKey, accountId });
|
|
3905
|
-
this.session.modelRegistry.refresh();
|
|
3906
|
-
}
|
|
3907
|
-
else {
|
|
3908
|
-
this.session.modelRegistry.authStorage.set(providerId, { type: "api_key", key: apiKey });
|
|
3909
|
-
}
|
|
3910
|
-
restoreEditor();
|
|
3911
|
-
await this.completeProviderAuthentication(providerId, providerName, "api_key", previousModel);
|
|
3912
|
-
}
|
|
3913
|
-
catch (error) {
|
|
3914
|
-
restoreEditor();
|
|
3915
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
3916
|
-
if (errorMsg !== "Login cancelled") {
|
|
3917
|
-
this.showError(`Failed to save API key for ${providerName}: ${errorMsg}`);
|
|
3918
|
-
}
|
|
3919
|
-
}
|
|
3920
|
-
}
|
|
3921
|
-
async showAeryGatewayLoginDialog() {
|
|
3922
|
-
const dialog = new LoginDialogComponent(this.ui, AERY_GATEWAY_PROVIDER_ID, (_success, _message) => { }, "Connect to Aery Gateway");
|
|
3839
|
+
const dialog = new LoginDialogComponent(this.ui, providerId, () => restoreEditor(), providerName, "Amazon Bedrock setup");
|
|
3840
|
+
dialog.showInfo([
|
|
3841
|
+
theme.fg("text", "Amazon Bedrock uses AWS credentials instead of a single API key."),
|
|
3842
|
+
theme.fg("text", "Configure an AWS profile, IAM keys, bearer token, or role-based credentials."),
|
|
3843
|
+
theme.fg("muted", "See:"),
|
|
3844
|
+
theme.fg("accent", ` ${path.join(getDocsPath(), "providers.md")}`),
|
|
3845
|
+
]);
|
|
3923
3846
|
this.editorContainer.clear();
|
|
3924
3847
|
this.editorContainer.addChild(dialog);
|
|
3925
3848
|
this.ui.setFocus(dialog);
|
|
3926
3849
|
this.ui.requestRender();
|
|
3927
|
-
const restoreEditor = () => {
|
|
3928
|
-
this.editorContainer.clear();
|
|
3929
|
-
this.editorContainer.addChild(this.editor);
|
|
3930
|
-
this.ui.setFocus(this.editor);
|
|
3931
|
-
this.ui.requestRender();
|
|
3932
|
-
};
|
|
3933
|
-
try {
|
|
3934
|
-
const aeryKey = (await dialog.showPrompt("Enter your Aery key:", "aery_...")).trim();
|
|
3935
|
-
if (!aeryKey)
|
|
3936
|
-
throw new Error("Aery key cannot be empty.");
|
|
3937
|
-
const provider = (await dialog.showPrompt("Provider to route through (e.g. anthropic, openai, openrouter):", "anthropic")).trim();
|
|
3938
|
-
if (!provider)
|
|
3939
|
-
throw new Error("Provider cannot be empty.");
|
|
3940
|
-
const modelId = (await dialog.showPrompt("Model ID:", "claude-sonnet-4-5")).trim();
|
|
3941
|
-
if (!modelId)
|
|
3942
|
-
throw new Error("Model ID cannot be empty.");
|
|
3943
|
-
const baseUrl = `${AERY_GATEWAY_BASE_URL}/${provider}`;
|
|
3944
|
-
const saved = saveCustomOpenAICompatibleProvider({
|
|
3945
|
-
modelsPath: getModelsPath(),
|
|
3946
|
-
baseUrl,
|
|
3947
|
-
modelId,
|
|
3948
|
-
api: AERY_GATEWAY_PROVIDER_APIS[provider] ?? "openai-completions",
|
|
3949
|
-
});
|
|
3950
|
-
this.session.modelRegistry.authStorage.set(saved.providerId, { type: "api_key", key: aeryKey });
|
|
3951
|
-
this.session.modelRegistry.refresh();
|
|
3952
|
-
restoreEditor();
|
|
3953
|
-
await this.updateAvailableProviderCount();
|
|
3954
|
-
this.footer.invalidate();
|
|
3955
|
-
this.updateEditorBorderColor();
|
|
3956
|
-
const model = this.session.modelRegistry.find(saved.providerId, saved.modelId);
|
|
3957
|
-
if (model) {
|
|
3958
|
-
try {
|
|
3959
|
-
await this.session.setModel(model);
|
|
3960
|
-
this.showStatus(`Connected to Aery Gateway → ${provider}/${modelId}.`);
|
|
3961
|
-
}
|
|
3962
|
-
catch {
|
|
3963
|
-
this.showStatus(`Aery Gateway configured. Use /model to select ${saved.providerId}/${modelId}.`);
|
|
3964
|
-
}
|
|
3965
|
-
}
|
|
3966
|
-
}
|
|
3967
|
-
catch (error) {
|
|
3968
|
-
restoreEditor();
|
|
3969
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
3970
|
-
if (errorMsg !== "Login cancelled") {
|
|
3971
|
-
this.showError(`Failed to connect to Aery Gateway: ${errorMsg}`);
|
|
3972
|
-
}
|
|
3973
|
-
}
|
|
3974
3850
|
}
|
|
3975
|
-
async
|
|
3851
|
+
async showApiKeyLoginDialog(providerId, providerName) {
|
|
3976
3852
|
const previousModel = this.session.model;
|
|
3977
|
-
const dialog = new LoginDialogComponent(this.ui,
|
|
3853
|
+
const dialog = new LoginDialogComponent(this.ui, providerId, (_success, _message) => {
|
|
3854
|
+
// Completion handled below
|
|
3855
|
+
}, providerName);
|
|
3978
3856
|
this.editorContainer.clear();
|
|
3979
3857
|
this.editorContainer.addChild(dialog);
|
|
3980
3858
|
this.ui.setFocus(dialog);
|
|
@@ -3986,58 +3864,44 @@ export class InteractiveMode {
|
|
|
3986
3864
|
this.ui.requestRender();
|
|
3987
3865
|
};
|
|
3988
3866
|
try {
|
|
3989
|
-
const
|
|
3990
|
-
if (!baseUrl) {
|
|
3991
|
-
throw new Error("Base URL cannot be empty.");
|
|
3992
|
-
}
|
|
3993
|
-
const modelId = (await dialog.showPrompt("Enter model ID:", "gpt-4o-mini")).trim();
|
|
3994
|
-
if (!modelId) {
|
|
3995
|
-
throw new Error("Model ID cannot be empty.");
|
|
3996
|
-
}
|
|
3997
|
-
const apiKey = (await dialog.showPrompt("Enter API key:", "sk-...")).trim();
|
|
3867
|
+
const apiKey = (await dialog.showPrompt("Enter API key:")).trim();
|
|
3998
3868
|
if (!apiKey) {
|
|
3999
3869
|
throw new Error("API key cannot be empty.");
|
|
4000
3870
|
}
|
|
4001
|
-
|
|
4002
|
-
modelsPath: getModelsPath(),
|
|
4003
|
-
baseUrl,
|
|
4004
|
-
modelId,
|
|
4005
|
-
});
|
|
4006
|
-
this.session.modelRegistry.authStorage.set(saved.providerId, { type: "api_key", key: apiKey });
|
|
4007
|
-
this.session.modelRegistry.refresh();
|
|
3871
|
+
this.session.modelRegistry.authStorage.set(providerId, { type: "api_key", key: apiKey });
|
|
4008
3872
|
restoreEditor();
|
|
4009
|
-
await this.
|
|
4010
|
-
this.footer.invalidate();
|
|
4011
|
-
this.updateEditorBorderColor();
|
|
4012
|
-
const model = this.session.modelRegistry.find(saved.providerId, saved.modelId);
|
|
4013
|
-
let selectedModel = false;
|
|
4014
|
-
if (model) {
|
|
4015
|
-
try {
|
|
4016
|
-
await this.session.setModel(model);
|
|
4017
|
-
selectedModel = true;
|
|
4018
|
-
}
|
|
4019
|
-
catch (error) {
|
|
4020
|
-
this.showError(`Saved ${saved.providerId}/${saved.modelId}, but selecting it failed: ${error instanceof Error ? error.message : String(error)}. Use /model to select it manually.`);
|
|
4021
|
-
}
|
|
4022
|
-
}
|
|
4023
|
-
if (selectedModel) {
|
|
4024
|
-
this.showStatus(`Configured ${saved.providerId}/${saved.modelId}. Provider saved to ${saved.modelsPath}; API key saved to ${getAuthPath()}.`);
|
|
4025
|
-
}
|
|
4026
|
-
else {
|
|
4027
|
-
this.showStatus(`Configured ${saved.providerId}/${saved.modelId}. Provider saved to ${saved.modelsPath}; API key saved to ${getAuthPath()}. Use /model to select it.`);
|
|
4028
|
-
}
|
|
4029
|
-
if (isUnknownModel(previousModel) && !selectedModel) {
|
|
4030
|
-
this.showWarning(`Custom provider ${saved.providerId} is available, but no model was selected automatically.`);
|
|
4031
|
-
}
|
|
3873
|
+
await this.completeProviderAuthentication(providerId, providerName, "api_key", previousModel);
|
|
4032
3874
|
}
|
|
4033
3875
|
catch (error) {
|
|
4034
3876
|
restoreEditor();
|
|
4035
3877
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
4036
3878
|
if (errorMsg !== "Login cancelled") {
|
|
4037
|
-
this.showError(`Failed to
|
|
3879
|
+
this.showError(`Failed to save API key for ${providerName}: ${errorMsg}`);
|
|
4038
3880
|
}
|
|
4039
3881
|
}
|
|
4040
3882
|
}
|
|
3883
|
+
showOAuthLoginSelect(dialog, prompt) {
|
|
3884
|
+
return new Promise((resolve) => {
|
|
3885
|
+
const restoreDialog = () => {
|
|
3886
|
+
this.editorContainer.clear();
|
|
3887
|
+
this.editorContainer.addChild(dialog);
|
|
3888
|
+
this.ui.setFocus(dialog);
|
|
3889
|
+
this.ui.requestRender();
|
|
3890
|
+
};
|
|
3891
|
+
const labels = prompt.options.map((option) => option.label);
|
|
3892
|
+
const selector = new ExtensionSelectorComponent(prompt.message, labels, (optionLabel) => {
|
|
3893
|
+
restoreDialog();
|
|
3894
|
+
resolve(prompt.options.find((option) => option.label === optionLabel)?.id);
|
|
3895
|
+
}, () => {
|
|
3896
|
+
restoreDialog();
|
|
3897
|
+
resolve(undefined);
|
|
3898
|
+
});
|
|
3899
|
+
this.editorContainer.clear();
|
|
3900
|
+
this.editorContainer.addChild(selector);
|
|
3901
|
+
this.ui.setFocus(selector);
|
|
3902
|
+
this.ui.requestRender();
|
|
3903
|
+
});
|
|
3904
|
+
}
|
|
4041
3905
|
async showLoginDialog(providerId, providerName) {
|
|
4042
3906
|
const providerInfo = this.session.modelRegistry.authStorage
|
|
4043
3907
|
.getOAuthProviders()
|
|
@@ -4075,7 +3939,7 @@ export class InteractiveMode {
|
|
|
4075
3939
|
if (usesCallbackServer) {
|
|
4076
3940
|
// Show input for manual paste, racing with callback
|
|
4077
3941
|
dialog
|
|
4078
|
-
.showManualInput("
|
|
3942
|
+
.showManualInput("Paste redirect URL below, or complete login in browser:")
|
|
4079
3943
|
.then((value) => {
|
|
4080
3944
|
if (value && manualCodeResolve) {
|
|
4081
3945
|
manualCodeResolve(value);
|
|
@@ -4101,6 +3965,7 @@ export class InteractiveMode {
|
|
|
4101
3965
|
onProgress: (message) => {
|
|
4102
3966
|
dialog.showProgress(message);
|
|
4103
3967
|
},
|
|
3968
|
+
onSelect: (prompt) => this.showOAuthLoginSelect(dialog, prompt),
|
|
4104
3969
|
onManualCodeInput: () => manualCodePromise,
|
|
4105
3970
|
signal: dialog.signal,
|
|
4106
3971
|
});
|
|
@@ -4451,43 +4316,17 @@ export class InteractiveMode {
|
|
|
4451
4316
|
this.chatContainer.addChild(new DynamicBorder());
|
|
4452
4317
|
this.ui.requestRender();
|
|
4453
4318
|
}
|
|
4454
|
-
handleExtensionsDoctorCommand() {
|
|
4455
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
4456
|
-
this.chatContainer.addChild(new Text(formatCurrentCoreExtensionsReport(), 1, 0));
|
|
4457
|
-
this.ui.requestRender();
|
|
4458
|
-
}
|
|
4459
|
-
handleCapabilitiesCommand() {
|
|
4460
|
-
const report = collectCapabilitiesReport({
|
|
4461
|
-
session: this.session,
|
|
4462
|
-
services: this.runtimeHost.services,
|
|
4463
|
-
});
|
|
4464
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
4465
|
-
this.chatContainer.addChild(new Text(formatCapabilitiesReport(report), 1, 0));
|
|
4466
|
-
this.ui.requestRender();
|
|
4467
|
-
}
|
|
4468
|
-
/**
|
|
4469
|
-
* Capitalize keybinding for display (e.g., "ctrl+c" -> "Ctrl+C").
|
|
4470
|
-
*/
|
|
4471
|
-
capitalizeKey(key) {
|
|
4472
|
-
return key
|
|
4473
|
-
.split("/")
|
|
4474
|
-
.map((k) => k
|
|
4475
|
-
.split("+")
|
|
4476
|
-
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
4477
|
-
.join("+"))
|
|
4478
|
-
.join("/");
|
|
4479
|
-
}
|
|
4480
4319
|
/**
|
|
4481
4320
|
* Get capitalized display string for an app keybinding action.
|
|
4482
4321
|
*/
|
|
4483
4322
|
getAppKeyDisplay(action) {
|
|
4484
|
-
return
|
|
4323
|
+
return keyDisplayText(action);
|
|
4485
4324
|
}
|
|
4486
4325
|
/**
|
|
4487
4326
|
* Get capitalized display string for an editor keybinding action.
|
|
4488
4327
|
*/
|
|
4489
4328
|
getEditorKeyDisplay(action) {
|
|
4490
|
-
return
|
|
4329
|
+
return keyDisplayText(action);
|
|
4491
4330
|
}
|
|
4492
4331
|
handleHotkeysCommand() {
|
|
4493
4332
|
// Navigation keybindings
|
|
@@ -4586,7 +4425,7 @@ export class InteractiveMode {
|
|
|
4586
4425
|
`;
|
|
4587
4426
|
for (const [key, shortcut] of shortcuts) {
|
|
4588
4427
|
const description = shortcut.description ?? shortcut.extensionPath;
|
|
4589
|
-
const keyDisplay = key
|
|
4428
|
+
const keyDisplay = formatKeyText(key, { capitalize: true });
|
|
4590
4429
|
hotkeys += `| \`${keyDisplay}\` | ${description} |\n`;
|
|
4591
4430
|
}
|
|
4592
4431
|
}
|