@openeryc/pi-coding-agent 0.75.54 → 0.75.56
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 +30 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +4 -0
- package/dist/cli.js.map +1 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +11 -26
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/fetch-openai-models.d.ts +25 -0
- package/dist/core/fetch-openai-models.d.ts.map +1 -0
- package/dist/core/fetch-openai-models.js +107 -0
- package/dist/core/fetch-openai-models.js.map +1 -0
- package/dist/core/mcp/client.d.ts +16 -7
- package/dist/core/mcp/client.d.ts.map +1 -1
- package/dist/core/mcp/client.js +135 -107
- package/dist/core/mcp/client.js.map +1 -1
- package/dist/core/mcp/manager.d.ts +8 -1
- package/dist/core/mcp/manager.d.ts.map +1 -1
- package/dist/core/mcp/manager.js +56 -33
- package/dist/core/mcp/manager.js.map +1 -1
- package/dist/core/mcp/types.d.ts +0 -13
- package/dist/core/mcp/types.d.ts.map +1 -1
- package/dist/core/mcp/types.js.map +1 -1
- package/dist/core/model-registry.d.ts +112 -0
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +99 -2
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +3 -1
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +1 -1
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +4 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +12 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +2 -2
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/modes/interactive/components/index.d.ts +1 -1
- package/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/dist/modes/interactive/components/index.js +1 -1
- package/dist/modes/interactive/components/index.js.map +1 -1
- package/dist/modes/interactive/components/model-hub.d.ts +115 -0
- package/dist/modes/interactive/components/model-hub.d.ts.map +1 -0
- package/dist/modes/interactive/components/model-hub.js +753 -0
- package/dist/modes/interactive/components/model-hub.js.map +1 -0
- package/dist/modes/interactive/components/model-selector.d.ts +2 -44
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +2 -275
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +22 -2
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +666 -557
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +5 -1
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/utils/sleep.d.ts.map +1 -1
- package/dist/utils/sleep.js +7 -3
- package/dist/utils/sleep.js.map +1 -1
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js.map +1 -1
- package/docs/models.md +20 -0
- package/docs/providers.md +13 -0
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/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/npm-shrinkwrap.json +12 -12
- package/package.json +4 -4
|
@@ -12,6 +12,7 @@ import { spawn, spawnSync } from "child_process";
|
|
|
12
12
|
import { APP_NAME, APP_TITLE, getAgentDir, getAuthPath, getDebugLogPath, getDocsPath, getSelfUpdateCommand, getShareViewerUrl, PACKAGE_NAME, VERSION, } from "../../config.js";
|
|
13
13
|
import { parseSkillBlock } from "../../core/agent-session.js";
|
|
14
14
|
import { SessionImportFileNotFoundError } from "../../core/agent-session-runtime.js";
|
|
15
|
+
import { fetchOpenAICompatibleModels, slugifyProviderId } from "../../core/fetch-openai-models.js";
|
|
15
16
|
import { FooterDataProvider } from "../../core/footer-data-provider.js";
|
|
16
17
|
import { configureHttpDispatcher, formatHttpIdleTimeoutMs } from "../../core/http-dispatcher.js";
|
|
17
18
|
import { KeybindingsManager } from "../../core/keybindings.js";
|
|
@@ -52,9 +53,8 @@ import { FooterComponent } from "./components/footer.js";
|
|
|
52
53
|
import { formatKeyText, keyDisplayText, keyHint, keyText, rawKeyHint } from "./components/keybinding-hints.js";
|
|
53
54
|
import { LoginDialogComponent } from "./components/login-dialog.js";
|
|
54
55
|
import { McpSelectorComponent } from "./components/mcp-selector.js";
|
|
55
|
-
import {
|
|
56
|
+
import { ModelHubComponent } from "./components/model-hub.js";
|
|
56
57
|
import { OAuthSelectorComponent } from "./components/oauth-selector.js";
|
|
57
|
-
import { ScopedModelsSelectorComponent } from "./components/scoped-models-selector.js";
|
|
58
58
|
import { SessionSelectorComponent } from "./components/session-selector.js";
|
|
59
59
|
import { SettingsSelectorComponent } from "./components/settings-selector.js";
|
|
60
60
|
import { SkillInvocationMessageComponent } from "./components/skill-invocation-message.js";
|
|
@@ -153,6 +153,8 @@ export class InteractiveMode {
|
|
|
153
153
|
hideThinkingBlock = false;
|
|
154
154
|
// Skill commands: command name -> skill file path
|
|
155
155
|
skillCommands = new Map();
|
|
156
|
+
/** Slash command dispatch map */
|
|
157
|
+
commands = new Map();
|
|
156
158
|
// Agent subscription unsubscribe function
|
|
157
159
|
unsubscribe;
|
|
158
160
|
signalCleanupHandlers = [];
|
|
@@ -478,6 +480,7 @@ export class InteractiveMode {
|
|
|
478
480
|
this.ui.setFocus(this.editor);
|
|
479
481
|
this.setupKeyHandlers();
|
|
480
482
|
this.setupEditorSubmitHandler();
|
|
483
|
+
this.initCommands();
|
|
481
484
|
// Start the UI before initializing extensions so session_start handlers can use interactive dialogs
|
|
482
485
|
this.ui.start();
|
|
483
486
|
this.isInitialized = true;
|
|
@@ -528,23 +531,29 @@ export class InteractiveMode {
|
|
|
528
531
|
await this.goalRunnerContinuationLoop(resumedGoal);
|
|
529
532
|
}
|
|
530
533
|
// Start version check asynchronously
|
|
531
|
-
checkForNewPiVersion(this.version)
|
|
534
|
+
checkForNewPiVersion(this.version)
|
|
535
|
+
.then((newRelease) => {
|
|
532
536
|
if (newRelease) {
|
|
533
537
|
this.showNewVersionNotification(newRelease);
|
|
534
538
|
}
|
|
535
|
-
})
|
|
539
|
+
})
|
|
540
|
+
.catch(() => { });
|
|
536
541
|
// Start package update check asynchronously
|
|
537
|
-
this.checkForPackageUpdates()
|
|
542
|
+
this.checkForPackageUpdates()
|
|
543
|
+
.then((updates) => {
|
|
538
544
|
if (updates.length > 0) {
|
|
539
545
|
this.showPackageUpdateNotification(updates);
|
|
540
546
|
}
|
|
541
|
-
})
|
|
547
|
+
})
|
|
548
|
+
.catch(() => { });
|
|
542
549
|
// Check tmux keyboard setup asynchronously
|
|
543
|
-
this.checkTmuxKeyboardSetup()
|
|
550
|
+
this.checkTmuxKeyboardSetup()
|
|
551
|
+
.then((warning) => {
|
|
544
552
|
if (warning) {
|
|
545
553
|
this.showWarning(warning);
|
|
546
554
|
}
|
|
547
|
-
})
|
|
555
|
+
})
|
|
556
|
+
.catch(() => { });
|
|
548
557
|
// Show startup warnings
|
|
549
558
|
const { migratedProviders, modelFallbackMessage, initialMessage, initialImages, initialMessages } = this.options;
|
|
550
559
|
if (migratedProviders && migratedProviders.length > 0) {
|
|
@@ -1957,7 +1966,7 @@ export class InteractiveMode {
|
|
|
1957
1966
|
this.defaultEditor.onAction("app.model.cycleBackward", () => this.cycleModel("backward"));
|
|
1958
1967
|
// Global debug handler on TUI (works regardless of focus)
|
|
1959
1968
|
this.ui.onDebug = () => this.handleDebugCommand();
|
|
1960
|
-
this.defaultEditor.onAction("app.model.select", () => this.
|
|
1969
|
+
this.defaultEditor.onAction("app.model.select", () => this.showModelHub("select"));
|
|
1961
1970
|
this.defaultEditor.onAction("app.tools.expand", () => this.toggleToolOutputExpansion());
|
|
1962
1971
|
this.defaultEditor.onAction("app.thinking.toggle", () => this.toggleThinkingBlockVisibility());
|
|
1963
1972
|
this.defaultEditor.onAction("app.editor.external", () => this.openExternalEditor());
|
|
@@ -1999,6 +2008,60 @@ export class InteractiveMode {
|
|
|
1999
2008
|
// Silently ignore clipboard errors (may not have permission, etc.)
|
|
2000
2009
|
}
|
|
2001
2010
|
}
|
|
2011
|
+
initCommands() {
|
|
2012
|
+
const noArg = (fn) => async (_text) => {
|
|
2013
|
+
this.editor.setText("");
|
|
2014
|
+
await fn();
|
|
2015
|
+
};
|
|
2016
|
+
this.commands.set("/settings", noArg(() => this.showSettingsSelector()));
|
|
2017
|
+
this.commands.set("/scoped-models", noArg(() => this.showModelHub("cycle")));
|
|
2018
|
+
this.commands.set("/model", async (text) => {
|
|
2019
|
+
const s = text.startsWith("/model ") ? text.slice(7).trim() : undefined;
|
|
2020
|
+
this.editor.setText("");
|
|
2021
|
+
await this.handleModelCommand(s);
|
|
2022
|
+
});
|
|
2023
|
+
this.commands.set("/export", async (text) => {
|
|
2024
|
+
await this.handleExportCommand(text);
|
|
2025
|
+
this.editor.setText("");
|
|
2026
|
+
});
|
|
2027
|
+
this.commands.set("/import", async (text) => {
|
|
2028
|
+
await this.handleImportCommand(text);
|
|
2029
|
+
this.editor.setText("");
|
|
2030
|
+
});
|
|
2031
|
+
this.commands.set("/share", noArg(() => this.handleShareCommand()));
|
|
2032
|
+
this.commands.set("/copy", noArg(() => this.handleCopyCommand()));
|
|
2033
|
+
this.commands.set("/name", async (text) => {
|
|
2034
|
+
this.handleNameCommand(text);
|
|
2035
|
+
this.editor.setText("");
|
|
2036
|
+
});
|
|
2037
|
+
this.commands.set("/session", noArg(() => this.handleSessionCommand()));
|
|
2038
|
+
this.commands.set("/mcp", noArg(() => this.showMcpSelector()));
|
|
2039
|
+
this.commands.set("/skills", noArg(() => this.showSkillsSelector()));
|
|
2040
|
+
this.commands.set("/usage", noArg(() => this.handleUsageCommand()));
|
|
2041
|
+
this.commands.set("/memory", noArg(() => this.handleMemoryCommand()));
|
|
2042
|
+
this.commands.set("/changelog", noArg(() => this.handleChangelogCommand()));
|
|
2043
|
+
this.commands.set("/hotkeys", noArg(() => this.handleHotkeysCommand()));
|
|
2044
|
+
this.commands.set("/fork", noArg(() => this.showUserMessageSelector()));
|
|
2045
|
+
this.commands.set("/clone", noArg(() => this.handleCloneCommand()));
|
|
2046
|
+
this.commands.set("/tree", noArg(() => this.showTreeSelector()));
|
|
2047
|
+
this.commands.set("/login", noArg(() => this.showOAuthSelector("login")));
|
|
2048
|
+
this.commands.set("/logout", noArg(() => this.showOAuthSelector("logout")));
|
|
2049
|
+
this.commands.set("/new", noArg(() => this.handleClearCommand()));
|
|
2050
|
+
this.commands.set("/compact", async (text) => {
|
|
2051
|
+
const ci = text.startsWith("/compact ") ? text.slice(9).trim() : undefined;
|
|
2052
|
+
this.editor.setText("");
|
|
2053
|
+
await this.handleCompactCommand(ci);
|
|
2054
|
+
});
|
|
2055
|
+
this.commands.set("/cleanup", noArg(() => this.handleCleanupCommand()));
|
|
2056
|
+
this.commands.set("/reload", noArg(() => this.handleReloadCommand()));
|
|
2057
|
+
this.commands.set("/debug", noArg(() => this.handleDebugCommand()));
|
|
2058
|
+
this.commands.set("/arminsayshi", noArg(() => this.handleArminSaysHi()));
|
|
2059
|
+
this.commands.set("/dementedelves", noArg(() => this.handleDementedDelves()));
|
|
2060
|
+
this.commands.set("/daxnuts", noArg(() => this.handleDaxnuts()));
|
|
2061
|
+
this.commands.set("/resume", noArg(() => this.showSessionSelector()));
|
|
2062
|
+
this.commands.set("/update", noArg(() => this.handleUpdateCommand()));
|
|
2063
|
+
this.commands.set("/quit", noArg(() => this.shutdown()));
|
|
2064
|
+
}
|
|
2002
2065
|
setupEditorSubmitHandler() {
|
|
2003
2066
|
this.defaultEditor.onSubmit = async (text) => {
|
|
2004
2067
|
text = text.trim();
|
|
@@ -2006,171 +2069,22 @@ export class InteractiveMode {
|
|
|
2006
2069
|
return;
|
|
2007
2070
|
// Add to history before handling (so up/down can recall commands too)
|
|
2008
2071
|
this.editor.addToHistory?.(text);
|
|
2009
|
-
// Handle commands
|
|
2010
|
-
if (text
|
|
2011
|
-
this.showSettingsSelector();
|
|
2012
|
-
this.editor.setText("");
|
|
2013
|
-
return;
|
|
2014
|
-
}
|
|
2015
|
-
if (text === "/scoped-models") {
|
|
2016
|
-
this.editor.setText("");
|
|
2017
|
-
await this.showModelsSelector();
|
|
2018
|
-
return;
|
|
2019
|
-
}
|
|
2020
|
-
if (text === "/model" || text.startsWith("/model ")) {
|
|
2021
|
-
const searchTerm = text.startsWith("/model ") ? text.slice(7).trim() : undefined;
|
|
2022
|
-
this.editor.setText("");
|
|
2023
|
-
await this.handleModelCommand(searchTerm);
|
|
2024
|
-
return;
|
|
2025
|
-
}
|
|
2026
|
-
if (text === "/export" || text.startsWith("/export ")) {
|
|
2027
|
-
await this.handleExportCommand(text);
|
|
2028
|
-
this.editor.setText("");
|
|
2029
|
-
return;
|
|
2030
|
-
}
|
|
2031
|
-
if (text === "/import" || text.startsWith("/import ")) {
|
|
2032
|
-
await this.handleImportCommand(text);
|
|
2033
|
-
this.editor.setText("");
|
|
2034
|
-
return;
|
|
2035
|
-
}
|
|
2036
|
-
if (text === "/share") {
|
|
2037
|
-
await this.handleShareCommand();
|
|
2038
|
-
this.editor.setText("");
|
|
2039
|
-
return;
|
|
2040
|
-
}
|
|
2041
|
-
if (text === "/copy") {
|
|
2042
|
-
await this.handleCopyCommand();
|
|
2043
|
-
this.editor.setText("");
|
|
2044
|
-
return;
|
|
2045
|
-
}
|
|
2046
|
-
if (text === "/name" || text.startsWith("/name ")) {
|
|
2047
|
-
this.handleNameCommand(text);
|
|
2048
|
-
this.editor.setText("");
|
|
2049
|
-
return;
|
|
2050
|
-
}
|
|
2051
|
-
if (text === "/goal" || text.startsWith("/goal ")) {
|
|
2072
|
+
// Handle commands (with special case for /goal which continues to submit)
|
|
2073
|
+
if (text.startsWith("/goal")) {
|
|
2052
2074
|
const goal = text.replace(/^\/goal\s*/, "").trim();
|
|
2053
2075
|
this.handleGoalCommand(text);
|
|
2054
|
-
if (goal)
|
|
2055
|
-
text = goal; // Submit goal text as the user message, not /goal prefix
|
|
2056
|
-
// Don't return — let the normal submit flow run so the agent starts working
|
|
2057
|
-
}
|
|
2058
|
-
if (text === "/session") {
|
|
2059
|
-
this.handleSessionCommand();
|
|
2060
|
-
this.editor.setText("");
|
|
2061
|
-
return;
|
|
2062
|
-
}
|
|
2063
|
-
if (text === "/mcp") {
|
|
2064
|
-
this.editor.setText("");
|
|
2065
|
-
this.showMcpSelector();
|
|
2066
|
-
return;
|
|
2067
|
-
}
|
|
2068
|
-
if (text.startsWith("/mcp ")) {
|
|
2069
|
-
const name = text.slice(5).trim();
|
|
2070
|
-
this.editor.setText("");
|
|
2071
|
-
this.showMcpSelector(name);
|
|
2072
|
-
return;
|
|
2073
|
-
}
|
|
2074
|
-
if (text === "/skills") {
|
|
2075
|
-
this.editor.setText("");
|
|
2076
|
-
this.showSkillsSelector();
|
|
2077
|
-
return;
|
|
2078
|
-
}
|
|
2079
|
-
if (text === "/usage") {
|
|
2080
|
-
this.editor.setText("");
|
|
2081
|
-
await this.handleUsageCommand();
|
|
2082
|
-
return;
|
|
2083
|
-
}
|
|
2084
|
-
if (text === "/memory") {
|
|
2085
|
-
this.handleMemoryCommand();
|
|
2086
|
-
this.editor.setText("");
|
|
2087
|
-
return;
|
|
2088
|
-
}
|
|
2089
|
-
if (text === "/changelog") {
|
|
2090
|
-
this.handleChangelogCommand();
|
|
2091
|
-
this.editor.setText("");
|
|
2092
|
-
return;
|
|
2093
|
-
}
|
|
2094
|
-
if (text === "/hotkeys") {
|
|
2095
|
-
this.handleHotkeysCommand();
|
|
2096
|
-
this.editor.setText("");
|
|
2097
|
-
return;
|
|
2098
|
-
}
|
|
2099
|
-
if (text === "/fork") {
|
|
2100
|
-
this.showUserMessageSelector();
|
|
2101
|
-
this.editor.setText("");
|
|
2102
|
-
return;
|
|
2103
|
-
}
|
|
2104
|
-
if (text === "/clone") {
|
|
2105
|
-
this.editor.setText("");
|
|
2106
|
-
await this.handleCloneCommand();
|
|
2107
|
-
return;
|
|
2108
|
-
}
|
|
2109
|
-
if (text === "/tree") {
|
|
2110
|
-
this.showTreeSelector();
|
|
2111
|
-
this.editor.setText("");
|
|
2112
|
-
return;
|
|
2113
|
-
}
|
|
2114
|
-
if (text === "/login") {
|
|
2115
|
-
this.showOAuthSelector("login");
|
|
2116
|
-
this.editor.setText("");
|
|
2117
|
-
return;
|
|
2118
|
-
}
|
|
2119
|
-
if (text === "/logout") {
|
|
2120
|
-
this.showOAuthSelector("logout");
|
|
2121
|
-
this.editor.setText("");
|
|
2122
|
-
return;
|
|
2123
|
-
}
|
|
2124
|
-
if (text === "/new") {
|
|
2125
2076
|
this.editor.setText("");
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
}
|
|
2129
|
-
if (text === "/compact" || text.startsWith("/compact ")) {
|
|
2130
|
-
const customInstructions = text.startsWith("/compact ") ? text.slice(9).trim() : undefined;
|
|
2131
|
-
this.editor.setText("");
|
|
2132
|
-
await this.handleCompactCommand(customInstructions);
|
|
2133
|
-
return;
|
|
2134
|
-
}
|
|
2135
|
-
if (text === "/cleanup") {
|
|
2136
|
-
this.editor.setText("");
|
|
2137
|
-
await this.handleCleanupCommand();
|
|
2138
|
-
return;
|
|
2139
|
-
}
|
|
2140
|
-
if (text === "/reload") {
|
|
2141
|
-
this.editor.setText("");
|
|
2142
|
-
await this.handleReloadCommand();
|
|
2143
|
-
return;
|
|
2144
|
-
}
|
|
2145
|
-
if (text === "/debug") {
|
|
2146
|
-
this.handleDebugCommand();
|
|
2147
|
-
this.editor.setText("");
|
|
2148
|
-
return;
|
|
2149
|
-
}
|
|
2150
|
-
if (text === "/arminsayshi") {
|
|
2151
|
-
this.handleArminSaysHi();
|
|
2152
|
-
this.editor.setText("");
|
|
2153
|
-
return;
|
|
2154
|
-
}
|
|
2155
|
-
if (text === "/dementedelves") {
|
|
2156
|
-
this.handleDementedDelves();
|
|
2157
|
-
this.editor.setText("");
|
|
2158
|
-
return;
|
|
2159
|
-
}
|
|
2160
|
-
if (text === "/resume") {
|
|
2161
|
-
this.showSessionSelector();
|
|
2162
|
-
this.editor.setText("");
|
|
2163
|
-
return;
|
|
2164
|
-
}
|
|
2165
|
-
if (text === "/update") {
|
|
2166
|
-
this.editor.setText("");
|
|
2167
|
-
await this.handleUpdateCommand();
|
|
2168
|
-
return;
|
|
2077
|
+
if (goal)
|
|
2078
|
+
text = goal;
|
|
2169
2079
|
}
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2080
|
+
else {
|
|
2081
|
+
const spaceIdx = text.indexOf(" ");
|
|
2082
|
+
const cmd = spaceIdx === -1 ? text : text.slice(0, spaceIdx);
|
|
2083
|
+
const handler = this.commands.get(cmd);
|
|
2084
|
+
if (handler) {
|
|
2085
|
+
await handler(text);
|
|
2086
|
+
return;
|
|
2087
|
+
}
|
|
2174
2088
|
}
|
|
2175
2089
|
// Handle bash command (! for normal, !! for excluded from context)
|
|
2176
2090
|
if (text.startsWith("!")) {
|
|
@@ -2232,280 +2146,299 @@ export class InteractiveMode {
|
|
|
2232
2146
|
this.footer.invalidate();
|
|
2233
2147
|
switch (event.type) {
|
|
2234
2148
|
case "agent_start":
|
|
2235
|
-
this.
|
|
2236
|
-
if (this.settingsManager.getShowTerminalProgress()) {
|
|
2237
|
-
this.ui.terminal.setProgress(true);
|
|
2238
|
-
}
|
|
2239
|
-
// Restore main escape handler if retry handler is still active
|
|
2240
|
-
// (retry success event fires later, but we need main handler now)
|
|
2241
|
-
if (this.retryEscapeHandler) {
|
|
2242
|
-
this.defaultEditor.onEscape = this.retryEscapeHandler;
|
|
2243
|
-
this.retryEscapeHandler = undefined;
|
|
2244
|
-
}
|
|
2245
|
-
if (this.retryCountdown) {
|
|
2246
|
-
this.retryCountdown.dispose();
|
|
2247
|
-
this.retryCountdown = undefined;
|
|
2248
|
-
}
|
|
2249
|
-
if (this.retryLoader) {
|
|
2250
|
-
this.retryLoader.stop();
|
|
2251
|
-
this.retryLoader = undefined;
|
|
2252
|
-
}
|
|
2253
|
-
this.stopWorkingLoader();
|
|
2254
|
-
if (this.workingVisible) {
|
|
2255
|
-
this.loadingAnimation = this.createWorkingLoader();
|
|
2256
|
-
this.statusContainer.addChild(this.loadingAnimation);
|
|
2257
|
-
}
|
|
2258
|
-
this.ui.requestRender();
|
|
2149
|
+
this._handleAgentStart();
|
|
2259
2150
|
break;
|
|
2260
2151
|
case "queue_update":
|
|
2261
|
-
this.
|
|
2262
|
-
this.ui.requestRender();
|
|
2152
|
+
this._handleQueueUpdate();
|
|
2263
2153
|
break;
|
|
2264
2154
|
case "session_info_changed":
|
|
2265
|
-
this.
|
|
2266
|
-
this.footer.invalidate();
|
|
2267
|
-
this.ui.requestRender();
|
|
2155
|
+
this._handleSessionInfoChanged();
|
|
2268
2156
|
break;
|
|
2269
2157
|
case "thinking_level_changed":
|
|
2270
|
-
this.
|
|
2271
|
-
this.updateEditorBorderColor();
|
|
2158
|
+
this._handleThinkingLevelChanged();
|
|
2272
2159
|
break;
|
|
2273
2160
|
case "message_start":
|
|
2274
|
-
|
|
2275
|
-
this.addMessageToChat(event.message);
|
|
2276
|
-
this.ui.requestRender();
|
|
2277
|
-
}
|
|
2278
|
-
else if (event.message.role === "user") {
|
|
2279
|
-
this.addMessageToChat(event.message);
|
|
2280
|
-
this.updatePendingMessagesDisplay();
|
|
2281
|
-
this.ui.requestRender();
|
|
2282
|
-
}
|
|
2283
|
-
else if (event.message.role === "assistant") {
|
|
2284
|
-
this.streamingComponent = new AssistantMessageComponent(undefined, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel);
|
|
2285
|
-
this.streamingMessage = event.message;
|
|
2286
|
-
this.chatContainer.addChild(this.streamingComponent);
|
|
2287
|
-
this.streamingComponent.updateContent(this.streamingMessage);
|
|
2288
|
-
this.ui.requestRender();
|
|
2289
|
-
}
|
|
2161
|
+
this._handleMessageStart(event);
|
|
2290
2162
|
break;
|
|
2291
2163
|
case "message_update":
|
|
2292
|
-
|
|
2293
|
-
this.streamingMessage = event.message;
|
|
2294
|
-
this.streamingComponent.updateContent(this.streamingMessage);
|
|
2295
|
-
for (const content of this.streamingMessage.content) {
|
|
2296
|
-
if (content.type === "toolCall") {
|
|
2297
|
-
if (!this.pendingTools.has(content.id)) {
|
|
2298
|
-
const component = new ToolExecutionComponent(content.name, content.id, content.arguments, {
|
|
2299
|
-
showImages: this.settingsManager.getShowImages(),
|
|
2300
|
-
imageWidthCells: this.settingsManager.getImageWidthCells(),
|
|
2301
|
-
}, this.getRegisteredToolDefinition(content.name), this.ui, this.sessionManager.getCwd());
|
|
2302
|
-
component.setExpanded(this.toolOutputExpanded);
|
|
2303
|
-
this.chatContainer.addChild(component);
|
|
2304
|
-
this.pendingTools.set(content.id, component);
|
|
2305
|
-
}
|
|
2306
|
-
else {
|
|
2307
|
-
const component = this.pendingTools.get(content.id);
|
|
2308
|
-
if (component) {
|
|
2309
|
-
component.updateArgs(content.arguments);
|
|
2310
|
-
}
|
|
2311
|
-
}
|
|
2312
|
-
}
|
|
2313
|
-
}
|
|
2314
|
-
this.ui.requestRender();
|
|
2315
|
-
}
|
|
2164
|
+
this._handleMessageUpdate(event);
|
|
2316
2165
|
break;
|
|
2317
2166
|
case "message_end":
|
|
2318
|
-
|
|
2319
|
-
break;
|
|
2320
|
-
if (this.streamingComponent && event.message.role === "assistant") {
|
|
2321
|
-
this.streamingMessage = event.message;
|
|
2322
|
-
let errorMessage;
|
|
2323
|
-
if (this.streamingMessage.stopReason === "aborted") {
|
|
2324
|
-
const retryAttempt = this.session.retryAttempt;
|
|
2325
|
-
errorMessage =
|
|
2326
|
-
retryAttempt > 0
|
|
2327
|
-
? `Aborted after ${retryAttempt} retry attempt${retryAttempt > 1 ? "s" : ""}`
|
|
2328
|
-
: "Operation aborted";
|
|
2329
|
-
this.streamingMessage.errorMessage = errorMessage;
|
|
2330
|
-
}
|
|
2331
|
-
this.streamingComponent.updateContent(this.streamingMessage);
|
|
2332
|
-
if (this.streamingMessage.stopReason === "aborted" || this.streamingMessage.stopReason === "error") {
|
|
2333
|
-
if (!errorMessage) {
|
|
2334
|
-
errorMessage = this.streamingMessage.errorMessage || "Error";
|
|
2335
|
-
}
|
|
2336
|
-
for (const [, component] of this.pendingTools.entries()) {
|
|
2337
|
-
component.updateResult({
|
|
2338
|
-
content: [{ type: "text", text: errorMessage }],
|
|
2339
|
-
isError: true,
|
|
2340
|
-
});
|
|
2341
|
-
}
|
|
2342
|
-
this.pendingTools.clear();
|
|
2343
|
-
}
|
|
2344
|
-
else {
|
|
2345
|
-
// Args are now complete - trigger diff computation for edit tools
|
|
2346
|
-
for (const [, component] of this.pendingTools.entries()) {
|
|
2347
|
-
component.setArgsComplete();
|
|
2348
|
-
}
|
|
2349
|
-
}
|
|
2350
|
-
this.streamingComponent = undefined;
|
|
2351
|
-
this.streamingMessage = undefined;
|
|
2352
|
-
this.footer.invalidate();
|
|
2353
|
-
}
|
|
2354
|
-
this.ui.requestRender();
|
|
2167
|
+
this._handleMessageEnd(event);
|
|
2355
2168
|
break;
|
|
2356
|
-
case "tool_execution_start":
|
|
2357
|
-
|
|
2358
|
-
if (!component) {
|
|
2359
|
-
component = new ToolExecutionComponent(event.toolName, event.toolCallId, event.args, {
|
|
2360
|
-
showImages: this.settingsManager.getShowImages(),
|
|
2361
|
-
imageWidthCells: this.settingsManager.getImageWidthCells(),
|
|
2362
|
-
}, this.getRegisteredToolDefinition(event.toolName), this.ui, this.sessionManager.getCwd());
|
|
2363
|
-
component.setExpanded(this.toolOutputExpanded);
|
|
2364
|
-
this.chatContainer.addChild(component);
|
|
2365
|
-
this.pendingTools.set(event.toolCallId, component);
|
|
2366
|
-
}
|
|
2367
|
-
component.markExecutionStarted();
|
|
2368
|
-
this.ui.requestRender();
|
|
2169
|
+
case "tool_execution_start":
|
|
2170
|
+
this._handleToolExecutionStart(event);
|
|
2369
2171
|
break;
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
const component = this.pendingTools.get(event.toolCallId);
|
|
2373
|
-
if (component) {
|
|
2374
|
-
component.updateResult({ ...event.partialResult, isError: false }, true);
|
|
2375
|
-
this.ui.requestRender();
|
|
2376
|
-
}
|
|
2172
|
+
case "tool_execution_update":
|
|
2173
|
+
this._handleToolExecutionUpdate(event);
|
|
2377
2174
|
break;
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
const component = this.pendingTools.get(event.toolCallId);
|
|
2381
|
-
if (component) {
|
|
2382
|
-
component.updateResult({ ...event.result, isError: event.isError });
|
|
2383
|
-
this.pendingTools.delete(event.toolCallId);
|
|
2384
|
-
this.ui.requestRender();
|
|
2385
|
-
}
|
|
2175
|
+
case "tool_execution_end":
|
|
2176
|
+
this._handleToolExecutionEnd(event);
|
|
2386
2177
|
break;
|
|
2387
|
-
}
|
|
2388
2178
|
case "agent_end":
|
|
2389
|
-
|
|
2390
|
-
this.ui.terminal.setProgress(false);
|
|
2391
|
-
}
|
|
2392
|
-
if (this.loadingAnimation) {
|
|
2393
|
-
this.loadingAnimation.stop();
|
|
2394
|
-
this.loadingAnimation = undefined;
|
|
2395
|
-
this.statusContainer.clear();
|
|
2396
|
-
}
|
|
2397
|
-
if (this.streamingComponent) {
|
|
2398
|
-
this.chatContainer.removeChild(this.streamingComponent);
|
|
2399
|
-
this.streamingComponent = undefined;
|
|
2400
|
-
this.streamingMessage = undefined;
|
|
2401
|
-
}
|
|
2402
|
-
this.pendingTools.clear();
|
|
2403
|
-
await this.checkShutdownRequested();
|
|
2404
|
-
this.ui.requestRender();
|
|
2179
|
+
await this._handleAgentEnd();
|
|
2405
2180
|
break;
|
|
2406
|
-
case "compaction_start":
|
|
2407
|
-
|
|
2408
|
-
this.ui.terminal.setProgress(true);
|
|
2409
|
-
}
|
|
2410
|
-
// Keep editor active; submissions are queued during compaction.
|
|
2411
|
-
this.autoCompactionEscapeHandler = this.defaultEditor.onEscape;
|
|
2412
|
-
this.defaultEditor.onEscape = () => {
|
|
2413
|
-
this.session.abortCompaction();
|
|
2414
|
-
};
|
|
2415
|
-
this.statusContainer.clear();
|
|
2416
|
-
const cancelHint = `(${keyText("app.interrupt")} to cancel)`;
|
|
2417
|
-
const label = event.reason === "manual"
|
|
2418
|
-
? `Compacting context... ${cancelHint}`
|
|
2419
|
-
: `${event.reason === "overflow" ? "Context overflow detected, " : ""}Auto-compacting... ${cancelHint}`;
|
|
2420
|
-
this.autoCompactionLoader = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), label);
|
|
2421
|
-
this.statusContainer.addChild(this.autoCompactionLoader);
|
|
2422
|
-
this.ui.requestRender();
|
|
2181
|
+
case "compaction_start":
|
|
2182
|
+
this._handleCompactionStart(event);
|
|
2423
2183
|
break;
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
if (this.settingsManager.getShowTerminalProgress()) {
|
|
2427
|
-
this.ui.terminal.setProgress(false);
|
|
2428
|
-
}
|
|
2429
|
-
if (this.autoCompactionEscapeHandler) {
|
|
2430
|
-
this.defaultEditor.onEscape = this.autoCompactionEscapeHandler;
|
|
2431
|
-
this.autoCompactionEscapeHandler = undefined;
|
|
2432
|
-
}
|
|
2433
|
-
if (this.autoCompactionLoader) {
|
|
2434
|
-
this.autoCompactionLoader.stop();
|
|
2435
|
-
this.autoCompactionLoader = undefined;
|
|
2436
|
-
this.statusContainer.clear();
|
|
2437
|
-
}
|
|
2438
|
-
if (event.aborted) {
|
|
2439
|
-
if (event.reason === "manual") {
|
|
2440
|
-
this.showError("Compaction cancelled");
|
|
2441
|
-
}
|
|
2442
|
-
else {
|
|
2443
|
-
this.showStatus("Auto-compaction cancelled");
|
|
2444
|
-
}
|
|
2445
|
-
}
|
|
2446
|
-
else if (event.result) {
|
|
2447
|
-
this.chatContainer.clear();
|
|
2448
|
-
this.rebuildChatFromMessages();
|
|
2449
|
-
this.addMessageToChat(createCompactionSummaryMessage(event.result.summary, event.result.tokensBefore, new Date().toISOString()));
|
|
2450
|
-
this.footer.invalidate();
|
|
2451
|
-
}
|
|
2452
|
-
else if (event.errorMessage) {
|
|
2453
|
-
if (event.reason === "manual") {
|
|
2454
|
-
this.showError(event.errorMessage);
|
|
2455
|
-
}
|
|
2456
|
-
else {
|
|
2457
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
2458
|
-
this.chatContainer.addChild(new Text(theme.fg("error", event.errorMessage), 1, 0));
|
|
2459
|
-
}
|
|
2460
|
-
}
|
|
2461
|
-
void this.flushCompactionQueue({ willRetry: event.willRetry });
|
|
2462
|
-
this.ui.requestRender();
|
|
2184
|
+
case "compaction_end":
|
|
2185
|
+
this._handleCompactionEnd(event);
|
|
2463
2186
|
break;
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
// Set up escape to abort retry
|
|
2467
|
-
this.retryEscapeHandler = this.defaultEditor.onEscape;
|
|
2468
|
-
this.defaultEditor.onEscape = () => {
|
|
2469
|
-
this.session.abortRetry();
|
|
2470
|
-
};
|
|
2471
|
-
// Show retry indicator
|
|
2472
|
-
this.statusContainer.clear();
|
|
2473
|
-
this.retryCountdown?.dispose();
|
|
2474
|
-
const retryMessage = (seconds) => `Retrying (${event.attempt}/${event.maxAttempts}) in ${seconds}s... (${keyText("app.interrupt")} to cancel)`;
|
|
2475
|
-
this.retryLoader = new Loader(this.ui, (spinner) => theme.fg("warning", spinner), (text) => theme.fg("muted", text), retryMessage(Math.ceil(event.delayMs / 1000)));
|
|
2476
|
-
this.retryCountdown = new CountdownTimer(event.delayMs, this.ui, (seconds) => {
|
|
2477
|
-
this.retryLoader?.setMessage(retryMessage(seconds));
|
|
2478
|
-
}, () => {
|
|
2479
|
-
this.retryCountdown = undefined;
|
|
2480
|
-
});
|
|
2481
|
-
this.statusContainer.addChild(this.retryLoader);
|
|
2482
|
-
this.ui.requestRender();
|
|
2187
|
+
case "auto_retry_start":
|
|
2188
|
+
this._handleAutoRetryStart(event);
|
|
2483
2189
|
break;
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
// Restore escape handler
|
|
2487
|
-
if (this.retryEscapeHandler) {
|
|
2488
|
-
this.defaultEditor.onEscape = this.retryEscapeHandler;
|
|
2489
|
-
this.retryEscapeHandler = undefined;
|
|
2490
|
-
}
|
|
2491
|
-
if (this.retryCountdown) {
|
|
2492
|
-
this.retryCountdown.dispose();
|
|
2493
|
-
this.retryCountdown = undefined;
|
|
2494
|
-
}
|
|
2495
|
-
// Stop loader
|
|
2496
|
-
if (this.retryLoader) {
|
|
2497
|
-
this.retryLoader.stop();
|
|
2498
|
-
this.retryLoader = undefined;
|
|
2499
|
-
this.statusContainer.clear();
|
|
2500
|
-
}
|
|
2501
|
-
// Show error only on final failure (success shows normal response)
|
|
2502
|
-
if (!event.success) {
|
|
2503
|
-
this.showError(`Retry failed after ${event.attempt} attempts: ${event.finalError || "Unknown error"}`);
|
|
2504
|
-
}
|
|
2505
|
-
this.ui.requestRender();
|
|
2190
|
+
case "auto_retry_end":
|
|
2191
|
+
this._handleAutoRetryEnd(event);
|
|
2506
2192
|
break;
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
_handleAgentStart() {
|
|
2196
|
+
this.pendingTools.clear();
|
|
2197
|
+
if (this.settingsManager.getShowTerminalProgress()) {
|
|
2198
|
+
this.ui.terminal.setProgress(true);
|
|
2199
|
+
}
|
|
2200
|
+
if (this.retryEscapeHandler) {
|
|
2201
|
+
this.defaultEditor.onEscape = this.retryEscapeHandler;
|
|
2202
|
+
this.retryEscapeHandler = undefined;
|
|
2203
|
+
}
|
|
2204
|
+
if (this.retryCountdown) {
|
|
2205
|
+
this.retryCountdown.dispose();
|
|
2206
|
+
this.retryCountdown = undefined;
|
|
2207
|
+
}
|
|
2208
|
+
if (this.retryLoader) {
|
|
2209
|
+
this.retryLoader.stop();
|
|
2210
|
+
this.retryLoader = undefined;
|
|
2211
|
+
}
|
|
2212
|
+
this.stopWorkingLoader();
|
|
2213
|
+
if (this.workingVisible) {
|
|
2214
|
+
this.loadingAnimation = this.createWorkingLoader();
|
|
2215
|
+
this.statusContainer.addChild(this.loadingAnimation);
|
|
2216
|
+
}
|
|
2217
|
+
this.ui.requestRender();
|
|
2218
|
+
}
|
|
2219
|
+
_handleQueueUpdate() {
|
|
2220
|
+
this.updatePendingMessagesDisplay();
|
|
2221
|
+
this.ui.requestRender();
|
|
2222
|
+
}
|
|
2223
|
+
_handleSessionInfoChanged() {
|
|
2224
|
+
this.updateTerminalTitle();
|
|
2225
|
+
this.footer.invalidate();
|
|
2226
|
+
this.ui.requestRender();
|
|
2227
|
+
}
|
|
2228
|
+
_handleThinkingLevelChanged() {
|
|
2229
|
+
this.footer.invalidate();
|
|
2230
|
+
this.updateEditorBorderColor();
|
|
2231
|
+
}
|
|
2232
|
+
_handleMessageStart(event) {
|
|
2233
|
+
if (event.message.role === "custom") {
|
|
2234
|
+
this.addMessageToChat(event.message);
|
|
2235
|
+
this.ui.requestRender();
|
|
2236
|
+
}
|
|
2237
|
+
else if (event.message.role === "user") {
|
|
2238
|
+
this.addMessageToChat(event.message);
|
|
2239
|
+
this.updatePendingMessagesDisplay();
|
|
2240
|
+
this.ui.requestRender();
|
|
2241
|
+
}
|
|
2242
|
+
else if (event.message.role === "assistant") {
|
|
2243
|
+
this.streamingComponent = new AssistantMessageComponent(undefined, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel);
|
|
2244
|
+
this.streamingMessage = event.message;
|
|
2245
|
+
this.chatContainer.addChild(this.streamingComponent);
|
|
2246
|
+
this.streamingComponent.updateContent(this.streamingMessage);
|
|
2247
|
+
this.ui.requestRender();
|
|
2248
|
+
}
|
|
2249
|
+
}
|
|
2250
|
+
_handleMessageUpdate(event) {
|
|
2251
|
+
if (!this.streamingComponent || event.message.role !== "assistant")
|
|
2252
|
+
return;
|
|
2253
|
+
this.streamingMessage = event.message;
|
|
2254
|
+
this.streamingComponent.updateContent(this.streamingMessage);
|
|
2255
|
+
for (const content of this.streamingMessage.content) {
|
|
2256
|
+
if (content.type !== "toolCall")
|
|
2257
|
+
continue;
|
|
2258
|
+
if (!this.pendingTools.has(content.id)) {
|
|
2259
|
+
const component = new ToolExecutionComponent(content.name, content.id, content.arguments, {
|
|
2260
|
+
showImages: this.settingsManager.getShowImages(),
|
|
2261
|
+
imageWidthCells: this.settingsManager.getImageWidthCells(),
|
|
2262
|
+
}, this.getRegisteredToolDefinition(content.name), this.ui, this.sessionManager.getCwd());
|
|
2263
|
+
component.setExpanded(this.toolOutputExpanded);
|
|
2264
|
+
this.chatContainer.addChild(component);
|
|
2265
|
+
this.pendingTools.set(content.id, component);
|
|
2266
|
+
}
|
|
2267
|
+
else {
|
|
2268
|
+
this.pendingTools.get(content.id)?.updateArgs(content.arguments);
|
|
2269
|
+
}
|
|
2270
|
+
}
|
|
2271
|
+
this.ui.requestRender();
|
|
2272
|
+
}
|
|
2273
|
+
_handleMessageEnd(event) {
|
|
2274
|
+
if (event.message.role === "user")
|
|
2275
|
+
return;
|
|
2276
|
+
if (!this.streamingComponent || event.message.role !== "assistant")
|
|
2277
|
+
return;
|
|
2278
|
+
this.streamingMessage = event.message;
|
|
2279
|
+
let errorMessage;
|
|
2280
|
+
if (this.streamingMessage.stopReason === "aborted") {
|
|
2281
|
+
const retryAttempt = this.session.retryAttempt;
|
|
2282
|
+
errorMessage =
|
|
2283
|
+
retryAttempt > 0
|
|
2284
|
+
? `Aborted after ${retryAttempt} retry attempt${retryAttempt > 1 ? "s" : ""}`
|
|
2285
|
+
: "Operation aborted";
|
|
2286
|
+
this.streamingMessage.errorMessage = errorMessage;
|
|
2287
|
+
}
|
|
2288
|
+
this.streamingComponent.updateContent(this.streamingMessage);
|
|
2289
|
+
if (this.streamingMessage.stopReason === "aborted" || this.streamingMessage.stopReason === "error") {
|
|
2290
|
+
const msg = errorMessage || this.streamingMessage.errorMessage || "Error";
|
|
2291
|
+
for (const [, component] of this.pendingTools.entries()) {
|
|
2292
|
+
component.updateResult({ content: [{ type: "text", text: msg }], isError: true });
|
|
2293
|
+
}
|
|
2294
|
+
this.pendingTools.clear();
|
|
2295
|
+
}
|
|
2296
|
+
else {
|
|
2297
|
+
for (const [, component] of this.pendingTools.entries()) {
|
|
2298
|
+
component.setArgsComplete();
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
this.streamingComponent = undefined;
|
|
2302
|
+
this.streamingMessage = undefined;
|
|
2303
|
+
this.footer.invalidate();
|
|
2304
|
+
this.ui.requestRender();
|
|
2305
|
+
}
|
|
2306
|
+
_handleToolExecutionStart(event) {
|
|
2307
|
+
let component = this.pendingTools.get(event.toolCallId);
|
|
2308
|
+
if (!component) {
|
|
2309
|
+
component = new ToolExecutionComponent(event.toolName, event.toolCallId, event.args, {
|
|
2310
|
+
showImages: this.settingsManager.getShowImages(),
|
|
2311
|
+
imageWidthCells: this.settingsManager.getImageWidthCells(),
|
|
2312
|
+
}, this.getRegisteredToolDefinition(event.toolName), this.ui, this.sessionManager.getCwd());
|
|
2313
|
+
component.setExpanded(this.toolOutputExpanded);
|
|
2314
|
+
this.chatContainer.addChild(component);
|
|
2315
|
+
this.pendingTools.set(event.toolCallId, component);
|
|
2316
|
+
}
|
|
2317
|
+
component.markExecutionStarted();
|
|
2318
|
+
this.ui.requestRender();
|
|
2319
|
+
}
|
|
2320
|
+
_handleToolExecutionUpdate(event) {
|
|
2321
|
+
const component = this.pendingTools.get(event.toolCallId);
|
|
2322
|
+
if (component) {
|
|
2323
|
+
component.updateResult({ ...event.partialResult, isError: false }, true);
|
|
2324
|
+
this.ui.requestRender();
|
|
2325
|
+
}
|
|
2326
|
+
}
|
|
2327
|
+
_handleToolExecutionEnd(event) {
|
|
2328
|
+
const component = this.pendingTools.get(event.toolCallId);
|
|
2329
|
+
if (component) {
|
|
2330
|
+
component.updateResult({ ...event.result, isError: event.isError });
|
|
2331
|
+
this.pendingTools.delete(event.toolCallId);
|
|
2332
|
+
this.ui.requestRender();
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
async _handleAgentEnd() {
|
|
2336
|
+
if (this.settingsManager.getShowTerminalProgress()) {
|
|
2337
|
+
this.ui.terminal.setProgress(false);
|
|
2338
|
+
}
|
|
2339
|
+
if (this.loadingAnimation) {
|
|
2340
|
+
this.loadingAnimation.stop();
|
|
2341
|
+
this.loadingAnimation = undefined;
|
|
2342
|
+
this.statusContainer.clear();
|
|
2343
|
+
}
|
|
2344
|
+
if (this.streamingComponent) {
|
|
2345
|
+
this.chatContainer.removeChild(this.streamingComponent);
|
|
2346
|
+
this.streamingComponent = undefined;
|
|
2347
|
+
this.streamingMessage = undefined;
|
|
2348
|
+
}
|
|
2349
|
+
this.pendingTools.clear();
|
|
2350
|
+
await this.checkShutdownRequested();
|
|
2351
|
+
this.ui.requestRender();
|
|
2352
|
+
}
|
|
2353
|
+
_handleCompactionStart(event) {
|
|
2354
|
+
if (this.settingsManager.getShowTerminalProgress()) {
|
|
2355
|
+
this.ui.terminal.setProgress(true);
|
|
2356
|
+
}
|
|
2357
|
+
this.autoCompactionEscapeHandler = this.defaultEditor.onEscape;
|
|
2358
|
+
this.defaultEditor.onEscape = () => {
|
|
2359
|
+
this.session.abortCompaction();
|
|
2360
|
+
};
|
|
2361
|
+
this.statusContainer.clear();
|
|
2362
|
+
const cancelHint = `(${keyText("app.interrupt")} to cancel)`;
|
|
2363
|
+
const label = event.reason === "manual"
|
|
2364
|
+
? `Compacting context... ${cancelHint}`
|
|
2365
|
+
: `${event.reason === "overflow" ? "Context overflow detected, " : ""}Auto-compacting... ${cancelHint}`;
|
|
2366
|
+
this.autoCompactionLoader = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), label);
|
|
2367
|
+
this.statusContainer.addChild(this.autoCompactionLoader);
|
|
2368
|
+
this.ui.requestRender();
|
|
2369
|
+
}
|
|
2370
|
+
_handleCompactionEnd(event) {
|
|
2371
|
+
if (this.settingsManager.getShowTerminalProgress()) {
|
|
2372
|
+
this.ui.terminal.setProgress(false);
|
|
2373
|
+
}
|
|
2374
|
+
if (this.autoCompactionEscapeHandler) {
|
|
2375
|
+
this.defaultEditor.onEscape = this.autoCompactionEscapeHandler;
|
|
2376
|
+
this.autoCompactionEscapeHandler = undefined;
|
|
2377
|
+
}
|
|
2378
|
+
if (this.autoCompactionLoader) {
|
|
2379
|
+
this.autoCompactionLoader.stop();
|
|
2380
|
+
this.autoCompactionLoader = undefined;
|
|
2381
|
+
this.statusContainer.clear();
|
|
2382
|
+
}
|
|
2383
|
+
if (event.aborted) {
|
|
2384
|
+
if (event.reason === "manual") {
|
|
2385
|
+
this.showError("Compaction cancelled");
|
|
2386
|
+
}
|
|
2387
|
+
else {
|
|
2388
|
+
this.showStatus("Auto-compaction cancelled");
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
else if (event.result) {
|
|
2392
|
+
this.chatContainer.clear();
|
|
2393
|
+
this.rebuildChatFromMessages();
|
|
2394
|
+
this.addMessageToChat(createCompactionSummaryMessage(event.result.summary, event.result.tokensBefore, new Date().toISOString()));
|
|
2395
|
+
this.footer.invalidate();
|
|
2396
|
+
}
|
|
2397
|
+
else if (event.errorMessage) {
|
|
2398
|
+
if (event.reason === "manual") {
|
|
2399
|
+
this.showError(event.errorMessage);
|
|
2400
|
+
}
|
|
2401
|
+
else {
|
|
2402
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
2403
|
+
this.chatContainer.addChild(new Text(theme.fg("error", event.errorMessage), 1, 0));
|
|
2507
2404
|
}
|
|
2508
2405
|
}
|
|
2406
|
+
void this.flushCompactionQueue({ willRetry: event.willRetry });
|
|
2407
|
+
this.ui.requestRender();
|
|
2408
|
+
}
|
|
2409
|
+
_handleAutoRetryStart(event) {
|
|
2410
|
+
this.retryEscapeHandler = this.defaultEditor.onEscape;
|
|
2411
|
+
this.defaultEditor.onEscape = () => {
|
|
2412
|
+
this.session.abortRetry();
|
|
2413
|
+
};
|
|
2414
|
+
this.statusContainer.clear();
|
|
2415
|
+
this.retryCountdown?.dispose();
|
|
2416
|
+
const retryMessage = (seconds) => `Retrying (${event.attempt}/${event.maxAttempts}) in ${seconds}s... (${keyText("app.interrupt")} to cancel)`;
|
|
2417
|
+
this.retryLoader = new Loader(this.ui, (spinner) => theme.fg("warning", spinner), (text) => theme.fg("muted", text), retryMessage(Math.ceil(event.delayMs / 1000)));
|
|
2418
|
+
this.retryCountdown = new CountdownTimer(event.delayMs, this.ui, (seconds) => this.retryLoader?.setMessage(retryMessage(seconds)), () => {
|
|
2419
|
+
this.retryCountdown = undefined;
|
|
2420
|
+
});
|
|
2421
|
+
this.statusContainer.addChild(this.retryLoader);
|
|
2422
|
+
this.ui.requestRender();
|
|
2423
|
+
}
|
|
2424
|
+
_handleAutoRetryEnd(event) {
|
|
2425
|
+
if (this.retryEscapeHandler) {
|
|
2426
|
+
this.defaultEditor.onEscape = this.retryEscapeHandler;
|
|
2427
|
+
this.retryEscapeHandler = undefined;
|
|
2428
|
+
}
|
|
2429
|
+
if (this.retryCountdown) {
|
|
2430
|
+
this.retryCountdown.dispose();
|
|
2431
|
+
this.retryCountdown = undefined;
|
|
2432
|
+
}
|
|
2433
|
+
if (this.retryLoader) {
|
|
2434
|
+
this.retryLoader.stop();
|
|
2435
|
+
this.retryLoader = undefined;
|
|
2436
|
+
this.statusContainer.clear();
|
|
2437
|
+
}
|
|
2438
|
+
if (!event.success) {
|
|
2439
|
+
this.showError(`Retry failed after ${event.attempt} attempts: ${event.finalError || "Unknown error"}`);
|
|
2440
|
+
}
|
|
2441
|
+
this.ui.requestRender();
|
|
2509
2442
|
}
|
|
2510
2443
|
/** Extract text content from a user message */
|
|
2511
2444
|
getUserMessageText(message) {
|
|
@@ -3415,7 +3348,7 @@ export class InteractiveMode {
|
|
|
3415
3348
|
}
|
|
3416
3349
|
async handleModelCommand(searchTerm) {
|
|
3417
3350
|
if (!searchTerm) {
|
|
3418
|
-
this.
|
|
3351
|
+
this.showModelHub("select");
|
|
3419
3352
|
return;
|
|
3420
3353
|
}
|
|
3421
3354
|
const model = await this.findExactModelMatch(searchTerm);
|
|
@@ -3433,7 +3366,7 @@ export class InteractiveMode {
|
|
|
3433
3366
|
}
|
|
3434
3367
|
return;
|
|
3435
3368
|
}
|
|
3436
|
-
this.
|
|
3369
|
+
this.showModelHub("select", searchTerm);
|
|
3437
3370
|
}
|
|
3438
3371
|
async findExactModelMatch(searchTerm) {
|
|
3439
3372
|
const models = await this.getModelCandidates();
|
|
@@ -3485,93 +3418,100 @@ export class InteractiveMode {
|
|
|
3485
3418
|
// Ignore auth lookup failures for warning-only checks.
|
|
3486
3419
|
}
|
|
3487
3420
|
}
|
|
3488
|
-
|
|
3489
|
-
this.
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
}
|
|
3500
|
-
catch (error) {
|
|
3501
|
-
done();
|
|
3502
|
-
this.showError(error instanceof Error ? error.message : String(error));
|
|
3503
|
-
}
|
|
3504
|
-
}, () => {
|
|
3505
|
-
done();
|
|
3506
|
-
this.ui.requestRender();
|
|
3507
|
-
}, initialSearchInput);
|
|
3508
|
-
return { component: selector, focus: selector };
|
|
3509
|
-
});
|
|
3421
|
+
async resolveEnabledModelIds() {
|
|
3422
|
+
const sessionScopedModels = this.session.scopedModels;
|
|
3423
|
+
if (sessionScopedModels.length > 0) {
|
|
3424
|
+
return sessionScopedModels.map((scoped) => `${scoped.model.provider}/${scoped.model.id}`);
|
|
3425
|
+
}
|
|
3426
|
+
const patterns = this.settingsManager.getEnabledModels();
|
|
3427
|
+
if (patterns !== undefined && patterns.length > 0) {
|
|
3428
|
+
const scopedModels = await resolveModelScope(patterns, this.session.modelRegistry);
|
|
3429
|
+
return scopedModels.map((scoped) => `${scoped.model.provider}/${scoped.model.id}`);
|
|
3430
|
+
}
|
|
3431
|
+
return null;
|
|
3510
3432
|
}
|
|
3511
|
-
async
|
|
3512
|
-
// Get all available models
|
|
3513
|
-
this.session.modelRegistry.refresh();
|
|
3433
|
+
async applyCycleEnabledIds(enabledIds) {
|
|
3514
3434
|
const allModels = this.session.modelRegistry.getAvailable();
|
|
3515
|
-
if (
|
|
3516
|
-
this.
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
const hasSessionScope = sessionScopedModels.length > 0;
|
|
3522
|
-
// Build enabled model IDs from session state or settings
|
|
3523
|
-
let currentEnabledIds = null;
|
|
3524
|
-
if (hasSessionScope) {
|
|
3525
|
-
// Use current session's scoped models
|
|
3526
|
-
currentEnabledIds = sessionScopedModels.map((scoped) => `${scoped.model.provider}/${scoped.model.id}`);
|
|
3435
|
+
if (enabledIds && enabledIds.length > 0 && enabledIds.length < allModels.length) {
|
|
3436
|
+
const newScopedModels = await resolveModelScope(enabledIds, this.session.modelRegistry);
|
|
3437
|
+
this.session.setScopedModels(newScopedModels.map((sm) => ({
|
|
3438
|
+
model: sm.model,
|
|
3439
|
+
thinkingLevel: sm.thinkingLevel,
|
|
3440
|
+
})));
|
|
3527
3441
|
}
|
|
3528
3442
|
else {
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3443
|
+
this.session.setScopedModels([]);
|
|
3444
|
+
}
|
|
3445
|
+
await this.updateAvailableProviderCount();
|
|
3446
|
+
this.ui.requestRender();
|
|
3447
|
+
}
|
|
3448
|
+
showModelHub(initialTab = "select", initialSearchInput) {
|
|
3449
|
+
void this.resolveEnabledModelIds().then((enabledModelIds) => {
|
|
3450
|
+
this.showSelector((done) => {
|
|
3451
|
+
const hub = new ModelHubComponent(this.ui, this.session.model, this.settingsManager, this.session.modelRegistry, this.session.scopedModels, enabledModelIds, {
|
|
3452
|
+
onSelectModel: async (model) => {
|
|
3453
|
+
try {
|
|
3454
|
+
await this.session.setModel(model);
|
|
3455
|
+
this.footer.invalidate();
|
|
3456
|
+
this.updateEditorBorderColor();
|
|
3457
|
+
done();
|
|
3458
|
+
this.showStatus(`Model: ${model.id}`);
|
|
3459
|
+
void this.maybeWarnAboutAnthropicSubscriptionAuth(model);
|
|
3460
|
+
this.checkDaxnutsEasterEgg(model);
|
|
3461
|
+
}
|
|
3462
|
+
catch (error) {
|
|
3463
|
+
done();
|
|
3464
|
+
this.showError(error instanceof Error ? error.message : String(error));
|
|
3465
|
+
}
|
|
3466
|
+
},
|
|
3467
|
+
onCancel: () => {
|
|
3468
|
+
done();
|
|
3469
|
+
this.ui.requestRender();
|
|
3470
|
+
},
|
|
3471
|
+
onCycleChange: async (ids) => {
|
|
3472
|
+
await this.applyCycleEnabledIds(ids);
|
|
3473
|
+
},
|
|
3474
|
+
onCyclePersist: async (ids) => {
|
|
3475
|
+
const allModels = this.session.modelRegistry.getAvailable();
|
|
3476
|
+
const newPatterns = ids === null || ids.length === allModels.length ? undefined : ids;
|
|
3477
|
+
this.settingsManager.setEnabledModels(newPatterns ? [...newPatterns] : undefined);
|
|
3478
|
+
this.showStatus("Model selection saved to settings");
|
|
3479
|
+
},
|
|
3480
|
+
onManageAdd: async () => {
|
|
3481
|
+
done();
|
|
3482
|
+
await this.showCustomProviderWizard({ mode: "add", target: "models_json" });
|
|
3483
|
+
},
|
|
3484
|
+
onManageEdit: async (source, providerId) => {
|
|
3485
|
+
done();
|
|
3486
|
+
await this.showCustomProviderWizard({ mode: "edit", target: source, providerId });
|
|
3487
|
+
},
|
|
3488
|
+
onManageRemove: async (source, providerId) => {
|
|
3489
|
+
done();
|
|
3490
|
+
const confirmed = await this.showExtensionConfirm("Remove provider", `Remove "${providerId}" from ${source === "models_json" ? "models.json" : "auth.json"}?`);
|
|
3491
|
+
if (!confirmed) {
|
|
3492
|
+
this.showModelHub("manage");
|
|
3493
|
+
return;
|
|
3494
|
+
}
|
|
3495
|
+
try {
|
|
3496
|
+
if (source === "models_json") {
|
|
3497
|
+
this.session.modelRegistry.removeProviderFromModelsJson(providerId);
|
|
3498
|
+
}
|
|
3499
|
+
else {
|
|
3500
|
+
this.session.modelRegistry.authStorage.remove(providerId);
|
|
3501
|
+
this.session.modelRegistry.refresh();
|
|
3502
|
+
}
|
|
3503
|
+
await this.updateAvailableProviderCount();
|
|
3504
|
+
this.showStatus(`Removed provider ${providerId}`);
|
|
3505
|
+
this.showModelHub("manage");
|
|
3506
|
+
}
|
|
3507
|
+
catch (error) {
|
|
3508
|
+
this.showError(error instanceof Error ? error.message : String(error));
|
|
3509
|
+
this.showModelHub("manage");
|
|
3510
|
+
}
|
|
3511
|
+
},
|
|
3512
|
+
}, { initialTab, initialSearchInput });
|
|
3513
|
+
return { component: hub, focus: hub };
|
|
3573
3514
|
});
|
|
3574
|
-
return { component: selector, focus: selector };
|
|
3575
3515
|
});
|
|
3576
3516
|
}
|
|
3577
3517
|
showUserMessageSelector() {
|
|
@@ -3932,17 +3872,18 @@ export class InteractiveMode {
|
|
|
3932
3872
|
if (isUnknownModel(previousModel)) {
|
|
3933
3873
|
const availableModels = this.session.modelRegistry.getAvailable();
|
|
3934
3874
|
const providerModels = availableModels.filter((model) => model.provider === providerId);
|
|
3935
|
-
if (
|
|
3936
|
-
selectionError = `${actionLabel}, but no default model is configured for provider "${providerId}". Use /model to select a model.`;
|
|
3937
|
-
}
|
|
3938
|
-
else if (providerModels.length === 0) {
|
|
3875
|
+
if (providerModels.length === 0) {
|
|
3939
3876
|
selectionError = `${actionLabel}, but no models are available for that provider. Use /model to select a model.`;
|
|
3940
3877
|
}
|
|
3941
3878
|
else {
|
|
3942
|
-
const defaultModelId =
|
|
3943
|
-
|
|
3879
|
+
const defaultModelId = hasDefaultModelProvider(providerId)
|
|
3880
|
+
? defaultModelPerProvider[providerId]
|
|
3881
|
+
: undefined;
|
|
3882
|
+
selectedModel =
|
|
3883
|
+
(defaultModelId ? providerModels.find((model) => model.id === defaultModelId) : undefined) ??
|
|
3884
|
+
providerModels[0];
|
|
3944
3885
|
if (!selectedModel) {
|
|
3945
|
-
selectionError = `${actionLabel}, but
|
|
3886
|
+
selectionError = `${actionLabel}, but no model could be selected. Use /model to select a model.`;
|
|
3946
3887
|
}
|
|
3947
3888
|
else {
|
|
3948
3889
|
try {
|
|
@@ -3951,7 +3892,7 @@ export class InteractiveMode {
|
|
|
3951
3892
|
catch (error) {
|
|
3952
3893
|
selectedModel = undefined;
|
|
3953
3894
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
3954
|
-
selectionError = `${actionLabel}, but selecting
|
|
3895
|
+
selectionError = `${actionLabel}, but selecting model failed: ${errorMessage}. Use /model to select a model.`;
|
|
3955
3896
|
}
|
|
3956
3897
|
}
|
|
3957
3898
|
}
|
|
@@ -4026,12 +3967,60 @@ export class InteractiveMode {
|
|
|
4026
3967
|
}
|
|
4027
3968
|
}
|
|
4028
3969
|
async showOpenAICompatibleLoginDialog() {
|
|
3970
|
+
await this.showCustomProviderWizard({ mode: "add", target: "openai_compatible" });
|
|
3971
|
+
}
|
|
3972
|
+
/**
|
|
3973
|
+
* Interactive wizard to add/edit a custom OpenAI-compatible provider.
|
|
3974
|
+
* - target models_json: writes ~/.pi/agent/models.json (Manage tab default)
|
|
3975
|
+
* - target openai_compatible: writes auth.json ( /login path )
|
|
3976
|
+
*/
|
|
3977
|
+
async showCustomProviderWizard(options) {
|
|
4029
3978
|
const previousModel = this.session.model;
|
|
4030
|
-
const
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
3979
|
+
const isEdit = options.mode === "edit" && !!options.providerId;
|
|
3980
|
+
let existing;
|
|
3981
|
+
if (isEdit && options.providerId) {
|
|
3982
|
+
if (options.target === "openai_compatible") {
|
|
3983
|
+
const all = this.session.modelRegistry.authStorage.getOpenAICompatibleProviders();
|
|
3984
|
+
const info = all[options.providerId];
|
|
3985
|
+
if (!info) {
|
|
3986
|
+
this.showError(`Provider "${options.providerId}" not found in auth.json`);
|
|
3987
|
+
this.showModelHub("manage");
|
|
3988
|
+
return;
|
|
3989
|
+
}
|
|
3990
|
+
existing = {
|
|
3991
|
+
providerId: options.providerId,
|
|
3992
|
+
baseUrl: info.baseUrl,
|
|
3993
|
+
apiKey: info.key,
|
|
3994
|
+
models: info.models,
|
|
3995
|
+
};
|
|
3996
|
+
}
|
|
3997
|
+
else {
|
|
3998
|
+
const listed = this.session.modelRegistry
|
|
3999
|
+
.listModelsJsonProviders()
|
|
4000
|
+
.find((p) => p.name === options.providerId);
|
|
4001
|
+
if (!listed) {
|
|
4002
|
+
this.showError(`Provider "${options.providerId}" not found in models.json`);
|
|
4003
|
+
this.showModelHub("manage");
|
|
4004
|
+
return;
|
|
4005
|
+
}
|
|
4006
|
+
// models.json apiKey is opaque (env/command/literal); do not surface the raw value
|
|
4007
|
+
existing = {
|
|
4008
|
+
providerId: options.providerId,
|
|
4009
|
+
baseUrl: listed.baseUrl ?? "",
|
|
4010
|
+
apiKey: "",
|
|
4011
|
+
models: listed.modelIds.map((id) => ({ id, name: id })),
|
|
4012
|
+
};
|
|
4013
|
+
}
|
|
4014
|
+
}
|
|
4015
|
+
const dialogProviderId = existing?.providerId ?? "custom-provider";
|
|
4016
|
+
const title = options.target === "models_json"
|
|
4017
|
+
? isEdit
|
|
4018
|
+
? `Edit models.json provider (${existing?.providerId})`
|
|
4019
|
+
: "Add custom provider (models.json)"
|
|
4020
|
+
: isEdit
|
|
4021
|
+
? `Edit OpenAI Compatible (${existing?.providerId})`
|
|
4022
|
+
: "Configure OpenAI Compatible Provider";
|
|
4023
|
+
const dialog = new LoginDialogComponent(this.ui, dialogProviderId, (_success, _message) => { }, existing?.providerId ?? "Custom Provider", title);
|
|
4035
4024
|
this.editorContainer.clear();
|
|
4036
4025
|
this.editorContainer.addChild(dialog);
|
|
4037
4026
|
this.ui.setFocus(dialog);
|
|
@@ -4043,45 +4032,165 @@ export class InteractiveMode {
|
|
|
4043
4032
|
this.ui.requestRender();
|
|
4044
4033
|
};
|
|
4045
4034
|
try {
|
|
4046
|
-
|
|
4035
|
+
let providerId = existing?.providerId ?? "";
|
|
4036
|
+
if (!isEdit) {
|
|
4037
|
+
const rawName = (await dialog.showPrompt("Provider name/id (e.g. ollama, agnes):", existing?.providerId ?? "ollama")).trim();
|
|
4038
|
+
providerId = slugifyProviderId(rawName);
|
|
4039
|
+
if (!providerId) {
|
|
4040
|
+
throw new Error("Provider name cannot be empty.");
|
|
4041
|
+
}
|
|
4042
|
+
if (BUILT_IN_MODEL_PROVIDERS.has(providerId) && options.target === "openai_compatible") {
|
|
4043
|
+
throw new Error(`"${providerId}" is a built-in provider. Use a different name, or add it via Manage → models.json override.`);
|
|
4044
|
+
}
|
|
4045
|
+
}
|
|
4046
|
+
const baseUrl = (await dialog.showPrompt("Enter base URL:", existing?.baseUrl || "http://localhost:11434/v1")).trim();
|
|
4047
4047
|
if (!baseUrl) {
|
|
4048
4048
|
throw new Error("Base URL cannot be empty.");
|
|
4049
4049
|
}
|
|
4050
|
-
const
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
const
|
|
4056
|
-
|
|
4057
|
-
.
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
models
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4050
|
+
const apiKeyPrompt = options.target === "models_json"
|
|
4051
|
+
? "API key (literal, env var name, or !command; blank = not-needed):"
|
|
4052
|
+
: "Enter API key (or leave blank for local endpoints):";
|
|
4053
|
+
const apiKey = (await dialog.showPrompt(apiKeyPrompt, existing?.apiKey === "not-needed" ? "" : (existing?.apiKey ?? ""))).trim();
|
|
4054
|
+
// On models.json edit, blank keeps the existing apiKey field (not rewritten).
|
|
4055
|
+
const effectiveApiKey = apiKey
|
|
4056
|
+
? apiKey
|
|
4057
|
+
: isEdit && options.target === "models_json"
|
|
4058
|
+
? undefined
|
|
4059
|
+
: "not-needed";
|
|
4060
|
+
dialog.showInfo([theme.fg("muted", "Discovering models from /models ...")]);
|
|
4061
|
+
this.ui.requestRender();
|
|
4062
|
+
const discovered = await fetchOpenAICompatibleModels(baseUrl, effectiveApiKey);
|
|
4063
|
+
let models;
|
|
4064
|
+
if (discovered.ok && discovered.models.length > 0) {
|
|
4065
|
+
// Offer: use all discovered, or hand-edit
|
|
4066
|
+
const useAllLabel = `Use all ${discovered.models.length} discovered models`;
|
|
4067
|
+
const manualLabel = "Enter model ids manually";
|
|
4068
|
+
const preview = discovered.models
|
|
4069
|
+
.slice(0, 8)
|
|
4070
|
+
.map((m) => m.id)
|
|
4071
|
+
.join(", ");
|
|
4072
|
+
const more = discovered.models.length > 8 ? ` (+${discovered.models.length - 8} more)` : "";
|
|
4073
|
+
dialog.showInfo([
|
|
4074
|
+
theme.fg("success", `Found ${discovered.models.length} models`),
|
|
4075
|
+
theme.fg("muted", `${preview}${more}`),
|
|
4076
|
+
]);
|
|
4077
|
+
this.ui.requestRender();
|
|
4078
|
+
const choice = await new Promise((resolve) => {
|
|
4079
|
+
const restoreDialog = () => {
|
|
4080
|
+
this.editorContainer.clear();
|
|
4081
|
+
this.editorContainer.addChild(dialog);
|
|
4082
|
+
this.ui.setFocus(dialog);
|
|
4083
|
+
this.ui.requestRender();
|
|
4084
|
+
};
|
|
4085
|
+
const selector = new ExtensionSelectorComponent("Discovered models", [useAllLabel, manualLabel], (option) => {
|
|
4086
|
+
restoreDialog();
|
|
4087
|
+
resolve(option);
|
|
4088
|
+
}, () => {
|
|
4089
|
+
restoreDialog();
|
|
4090
|
+
resolve(undefined);
|
|
4091
|
+
});
|
|
4092
|
+
this.editorContainer.clear();
|
|
4093
|
+
this.editorContainer.addChild(selector);
|
|
4094
|
+
this.ui.setFocus(selector);
|
|
4095
|
+
this.ui.requestRender();
|
|
4096
|
+
});
|
|
4097
|
+
if (!choice) {
|
|
4098
|
+
throw new Error("Login cancelled");
|
|
4099
|
+
}
|
|
4100
|
+
if (choice === useAllLabel) {
|
|
4101
|
+
models = discovered.models.map((m) => ({ id: m.id, name: m.name }));
|
|
4102
|
+
}
|
|
4103
|
+
else {
|
|
4104
|
+
const defaultIds = existing?.models.map((m) => m.id).join(", ") ||
|
|
4105
|
+
discovered.models
|
|
4106
|
+
.slice(0, 5)
|
|
4107
|
+
.map((m) => m.id)
|
|
4108
|
+
.join(", ");
|
|
4109
|
+
const modelNames = (await dialog.showPrompt("Model id(s), comma-separated:", defaultIds)).trim();
|
|
4110
|
+
if (!modelNames)
|
|
4111
|
+
throw new Error("At least one model name is required.");
|
|
4112
|
+
models = modelNames
|
|
4113
|
+
.split(",")
|
|
4114
|
+
.map((s) => s.trim())
|
|
4115
|
+
.filter((s) => s.length > 0)
|
|
4116
|
+
.map((id) => ({ id, name: id }));
|
|
4117
|
+
}
|
|
4118
|
+
}
|
|
4119
|
+
else {
|
|
4120
|
+
dialog.showInfo([
|
|
4121
|
+
theme.fg("warning", `Auto-discover failed: ${discovered.ok ? "no models" : discovered.error}`),
|
|
4122
|
+
theme.fg("muted", "Falling back to manual model entry."),
|
|
4123
|
+
]);
|
|
4124
|
+
this.ui.requestRender();
|
|
4125
|
+
const defaultIds = existing?.models.map((m) => m.id).join(", ") || "llama3.1";
|
|
4126
|
+
const modelNames = (await dialog.showPrompt("Enter model name(s) (comma-separated):", defaultIds)).trim();
|
|
4127
|
+
if (!modelNames) {
|
|
4128
|
+
throw new Error("At least one model name is required.");
|
|
4129
|
+
}
|
|
4130
|
+
models = modelNames
|
|
4131
|
+
.split(",")
|
|
4132
|
+
.map((s) => s.trim())
|
|
4133
|
+
.filter((s) => s.length > 0)
|
|
4134
|
+
.map((id) => ({ id, name: id }));
|
|
4135
|
+
}
|
|
4136
|
+
if (options.target === "openai_compatible") {
|
|
4137
|
+
const storedKey = effectiveApiKey ?? "not-needed";
|
|
4138
|
+
// Replace previous id if renaming isn't supported (edit keeps id)
|
|
4139
|
+
if (isEdit && existing && existing.providerId !== providerId) {
|
|
4140
|
+
this.session.modelRegistry.authStorage.remove(existing.providerId);
|
|
4141
|
+
}
|
|
4142
|
+
this.session.modelRegistry.configureOpenAICompatibleProvider(providerId, {
|
|
4143
|
+
baseUrl,
|
|
4144
|
+
apiKey: storedKey,
|
|
4145
|
+
models,
|
|
4146
|
+
});
|
|
4147
|
+
this.session.modelRegistry.authStorage.set(providerId, {
|
|
4148
|
+
type: "openai_compatible",
|
|
4149
|
+
key: storedKey,
|
|
4150
|
+
baseUrl,
|
|
4151
|
+
models,
|
|
4152
|
+
});
|
|
4153
|
+
// refresh so getAvailable sees it
|
|
4154
|
+
this.session.modelRegistry.refresh();
|
|
4155
|
+
restoreEditor();
|
|
4156
|
+
await this.completeProviderAuthentication(providerId, baseUrl, "api_key", previousModel);
|
|
4157
|
+
}
|
|
4158
|
+
else {
|
|
4159
|
+
this.session.modelRegistry.upsertProviderInModelsJson(providerId, {
|
|
4160
|
+
baseUrl,
|
|
4161
|
+
...(effectiveApiKey !== undefined ? { apiKey: effectiveApiKey } : {}),
|
|
4162
|
+
api: "openai-completions",
|
|
4163
|
+
models: models.map((m) => ({
|
|
4164
|
+
id: m.id,
|
|
4165
|
+
name: m.name,
|
|
4166
|
+
reasoning: false,
|
|
4167
|
+
input: ["text"],
|
|
4168
|
+
})),
|
|
4169
|
+
});
|
|
4170
|
+
restoreEditor();
|
|
4171
|
+
await this.updateAvailableProviderCount();
|
|
4172
|
+
// Prefer selecting first model if no real model yet
|
|
4173
|
+
if (isUnknownModel(previousModel)) {
|
|
4174
|
+
const available = this.session.modelRegistry.getAvailable().filter((m) => m.provider === providerId);
|
|
4175
|
+
if (available[0]) {
|
|
4176
|
+
try {
|
|
4177
|
+
await this.session.setModel(available[0]);
|
|
4178
|
+
this.footer.invalidate();
|
|
4179
|
+
this.updateEditorBorderColor();
|
|
4180
|
+
}
|
|
4181
|
+
catch {
|
|
4182
|
+
// ignore
|
|
4183
|
+
}
|
|
4184
|
+
}
|
|
4185
|
+
}
|
|
4186
|
+
this.showStatus(`Saved provider "${providerId}" to models.json (${models.length} model${models.length === 1 ? "" : "s"}). Comments/trailing commas in that file are not preserved.`);
|
|
4187
|
+
}
|
|
4079
4188
|
}
|
|
4080
4189
|
catch (error) {
|
|
4081
4190
|
restoreEditor();
|
|
4082
4191
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
4083
4192
|
if (errorMsg !== "Login cancelled") {
|
|
4084
|
-
this.showError(`Failed to configure
|
|
4193
|
+
this.showError(`Failed to configure provider: ${errorMsg}`);
|
|
4085
4194
|
}
|
|
4086
4195
|
}
|
|
4087
4196
|
}
|
|
@@ -4612,7 +4721,7 @@ export class InteractiveMode {
|
|
|
4612
4721
|
this.chatContainer.addChild(new Text(info, 1, 0));
|
|
4613
4722
|
this.ui.requestRender();
|
|
4614
4723
|
}
|
|
4615
|
-
showMcpSelector(highlightServer) {
|
|
4724
|
+
async showMcpSelector(highlightServer) {
|
|
4616
4725
|
const buildItems = () => {
|
|
4617
4726
|
// Fetch fresh config each time so toggle reflects immediately
|
|
4618
4727
|
const configured = this.settingsManager.getMcpServers();
|
|
@@ -4635,22 +4744,22 @@ export class InteractiveMode {
|
|
|
4635
4744
|
};
|
|
4636
4745
|
});
|
|
4637
4746
|
};
|
|
4638
|
-
const items = buildItems();
|
|
4639
|
-
if (items.length === 0) {
|
|
4640
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
4641
|
-
this.chatContainer.addChild(new Text(`${theme.bold("MCP Servers")}\n\n${theme.fg("dim", "No MCP servers configured. Add 'mcpServers' to settings.json.")}`, 1, 0));
|
|
4642
|
-
this.ui.requestRender();
|
|
4643
|
-
return;
|
|
4644
|
-
}
|
|
4645
4747
|
if (highlightServer) {
|
|
4646
4748
|
const configured = this.settingsManager.getMcpServers();
|
|
4647
4749
|
const server = configured?.[highlightServer];
|
|
4648
4750
|
if (server) {
|
|
4649
4751
|
const currentEnabled = server.enabled !== false;
|
|
4650
4752
|
this.settingsManager.setMcpServerEnabled(highlightServer, !currentEnabled);
|
|
4651
|
-
this.session.reloadMcp(highlightServer);
|
|
4753
|
+
await this.session.reloadMcp(highlightServer);
|
|
4652
4754
|
}
|
|
4653
4755
|
}
|
|
4756
|
+
const items = buildItems();
|
|
4757
|
+
if (items.length === 0) {
|
|
4758
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
4759
|
+
this.chatContainer.addChild(new Text(`${theme.bold("MCP Servers")}\n\n${theme.fg("dim", "No MCP servers configured. Add 'mcpServers' to settings.json.")}`, 1, 0));
|
|
4760
|
+
this.ui.requestRender();
|
|
4761
|
+
return;
|
|
4762
|
+
}
|
|
4654
4763
|
this.showSelector((done) => {
|
|
4655
4764
|
const selector = new McpSelectorComponent("MCP Servers", items, async (name) => {
|
|
4656
4765
|
const cur = this.settingsManager.getMcpServers();
|