moderado 0.3.9 → 0.4.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -30
- package/dist/commands/chat.d.ts +2 -2
- package/dist/commands/chat.d.ts.map +1 -1
- package/dist/commands/chat.js +108 -41
- package/dist/commands/chat.js.map +1 -1
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +2 -1
- package/dist/commands/run.js.map +1 -1
- package/dist/config.d.ts +4 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +11 -3
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/dist/injected_version.d.ts +2 -0
- package/dist/injected_version.d.ts.map +1 -0
- package/dist/injected_version.js +5 -0
- package/dist/injected_version.js.map +1 -0
- package/dist/moderado_oauth.d.ts +14 -0
- package/dist/moderado_oauth.d.ts.map +1 -0
- package/dist/moderado_oauth.js +88 -0
- package/dist/moderado_oauth.js.map +1 -0
- package/dist/sessions.d.ts.map +1 -1
- package/dist/sessions.js +3 -2
- package/dist/sessions.js.map +1 -1
- package/dist/ui/model_selector.d.ts +6 -1
- package/dist/ui/model_selector.d.ts.map +1 -1
- package/dist/ui/model_selector.js +48 -9
- package/dist/ui/model_selector.js.map +1 -1
- package/dist/ui/provider_connect.d.ts +6 -1
- package/dist/ui/provider_connect.d.ts.map +1 -1
- package/dist/ui/provider_connect.js +118 -4
- package/dist/ui/provider_connect.js.map +1 -1
- package/dist/ui/welcome.d.ts +6 -2
- package/dist/ui/welcome.d.ts.map +1 -1
- package/dist/ui/welcome.js +69 -16
- package/dist/ui/welcome.js.map +1 -1
- package/dist/vendor/contracts/events.d.ts +93 -0
- package/dist/vendor/contracts/events.d.ts.map +1 -1
- package/dist/vendor/contracts/events.js +6 -1
- package/dist/vendor/contracts/events.js.map +1 -1
- package/dist/vendor/contracts/provider.d.ts +60 -0
- package/dist/vendor/contracts/provider.d.ts.map +1 -1
- package/dist/vendor/contracts/provider.js +39 -0
- package/dist/vendor/contracts/provider.js.map +1 -1
- package/dist/vendor/core/agent.d.ts.map +1 -1
- package/dist/vendor/core/agent.js +21 -7
- package/dist/vendor/core/agent.js.map +1 -1
- package/dist/vendor/providers/model_discovery.d.ts +1 -1
- package/dist/vendor/providers/model_discovery.d.ts.map +1 -1
- package/dist/vendor/providers/model_discovery.js +43 -2
- package/dist/vendor/providers/model_discovery.js.map +1 -1
- package/dist/vendor/providers/nvidia/nvidia_adapter.d.ts +1 -0
- package/dist/vendor/providers/nvidia/nvidia_adapter.d.ts.map +1 -1
- package/dist/vendor/providers/nvidia/nvidia_adapter.js +42 -8
- package/dist/vendor/providers/nvidia/nvidia_adapter.js.map +1 -1
- package/dist/vendor/providers/nvidia/sse_parser.d.ts +3 -1
- package/dist/vendor/providers/nvidia/sse_parser.d.ts.map +1 -1
- package/dist/vendor/providers/nvidia/sse_parser.js +31 -2
- package/dist/vendor/providers/nvidia/sse_parser.js.map +1 -1
- package/dist/vendor/providers/presets.d.ts +8 -1
- package/dist/vendor/providers/presets.d.ts.map +1 -1
- package/dist/vendor/providers/presets.js +45 -1
- package/dist/vendor/providers/presets.js.map +1 -1
- package/dist/vendor/tools/tools/web_search.d.ts.map +1 -1
- package/dist/vendor/tools/tools/web_search.js +2 -0
- package/dist/vendor/tools/tools/web_search.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> **A lightweight, provider-independent CLI coding agent with dynamic NVIDIA NIM discovery, free-first AUTO routing, and an uncompromised human-in-the-loop approval boundary.**
|
|
4
4
|
|
|
5
|
-
[](https://github.com/marcuz-apl/moderado/releases/latest)
|
|
6
6
|
[](file:///d:/projects/moderado/LICENSE)
|
|
7
7
|
[](https://github.com/marcuz-apl/alfazen-skills)
|
|
8
8
|
|
|
@@ -88,15 +88,18 @@ npm install -g moderado
|
|
|
88
88
|
moderado --help
|
|
89
89
|
```
|
|
90
90
|
|
|
91
|
-
|
|
91
|
+
The latest published release is 0.4.8. Its `/login` command uses the Moderado
|
|
92
|
+
Gateway for model routes and inference; `/connect` remains available for direct
|
|
93
|
+
providers. The previous 0.3.10 release is self-contained and works without the
|
|
94
|
+
Gateway. Both releases use the `moderado` command name.
|
|
95
|
+
|
|
96
|
+
Install the latest release with `npm install -g moderado`. To stay on the
|
|
97
|
+
self-contained 0.3.10 CLI, run `npm install -g moderado@0.3.10`.
|
|
98
|
+
|
|
92
99
|
Any npm-compatible installer works (bun add -g moderado,
|
|
93
|
-
pnpm add -g moderado, npx -y moderado@latest --help)
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
GitHub Release v0.3.0 at https://github.com/marcuz-apl/moderado/releases/tag/v0.3.0.
|
|
97
|
-
The curl installer (`scripts/install.sh`, Linux x64 + macOS arm64), the
|
|
98
|
-
Homebrew tap, and the Scoop bucket are available; the winget and AUR
|
|
99
|
-
submissions are pending. See docs/DISTRIBUTION.md in the repository.
|
|
100
|
+
pnpm add -g moderado, npx -y moderado@latest --help). Prebuilt binaries and the
|
|
101
|
+
curl installer are documented in the root README. Windows installation options
|
|
102
|
+
are npm and Scoop. See `docs/DISTRIBUTION.md` in the repository.
|
|
100
103
|
To build from source instead:
|
|
101
104
|
|
|
102
105
|
```bash
|
|
@@ -111,44 +114,58 @@ Maintainers can install a verified release artifact directly with
|
|
|
111
114
|
`npm install -g ./moderado-<version>.tgz`. See
|
|
112
115
|
[docs/RELEASING.md](../../docs/RELEASING.md) for the review procedure.
|
|
113
116
|
|
|
114
|
-
### Connect
|
|
117
|
+
### Connect to the Moderado Gateway
|
|
118
|
+
|
|
119
|
+
Run `/login` in the TUI and choose the public Gateway for keyless inference.
|
|
120
|
+
Browser authorization or a Moderado Cloud `mrd_` API key is optional for account
|
|
121
|
+
features. `/model` lists configured Gateway routes: `auto` follows Gateway
|
|
122
|
+
routing, while selecting a route pins requests to it. Existing BYOK and local
|
|
123
|
+
provider profiles remain independent.
|
|
124
|
+
|
|
125
|
+
Use `/connect` to connect directly to OpenRouter, NVIDIA NIM, Agnes AI,
|
|
126
|
+
OrcaRouter, Ollama, LM Studio, or a custom OpenAI-compatible provider. `/login`
|
|
127
|
+
selects the Moderado Gateway; `/connect` selects a direct provider.
|
|
128
|
+
|
|
129
|
+
Cloud OAuth credentials last 30 days and have no refresh token; authorize again
|
|
130
|
+
after expiry. On Windows, credentials are stored in Windows Credential Manager.
|
|
131
|
+
On other platforms, the current credential store is memory-only, so log in again
|
|
132
|
+
after starting a new CLI process.
|
|
115
133
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
`OPENROUTER_API_KEY`.
|
|
134
|
+
Cloud connections use their configured Gateway URL. Loopback URLs such as
|
|
135
|
+
`http://127.0.0.1:4788/v1` are treated as local development; HTTPS URLs for a
|
|
136
|
+
NAS, VPS, or other remote host are used as configured. To set the URL before
|
|
137
|
+
first login, define `MODERADO_CLOUD_BASE_URL`. `MODERADO_CLOUD_ENV=development`
|
|
138
|
+
and `MODERADO_CLOUD_ENV=production` remain available as explicit overrides.
|
|
122
139
|
|
|
123
140
|
Run `moderado doctor` to inspect local setup without exposing secrets. Add
|
|
124
141
|
`--connectivity` only when you want an optional live model-catalog check.
|
|
125
142
|
`npm test` never performs live provider calls.
|
|
126
143
|
## Getting Started & Development
|
|
127
144
|
|
|
128
|
-
###
|
|
145
|
+
### Log in and select a model in the TUI
|
|
129
146
|
|
|
130
147
|
Run `moderado` to open the TUI immediately. A fresh installation does not require
|
|
131
148
|
an API key or a preselected model. The welcome card shows **No model connected —
|
|
132
|
-
use `/
|
|
149
|
+
use `/login`** until you add one.
|
|
133
150
|
|
|
134
|
-
Use `/
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
151
|
+
Use `/login` for the public Moderado Gateway and `/model` to browse configured routes.
|
|
152
|
+
Cloud `auto` follows the Gateway's configured pool; choosing a route in `/model`
|
|
153
|
+
pins it for subsequent requests. Existing direct BYOK and local profiles remain
|
|
154
|
+
available separately.
|
|
138
155
|
|
|
139
156
|
### Sessions and usage
|
|
140
157
|
|
|
141
158
|
Moderado stores sessions per workspace in `~/.moderado/sessions/`. Use
|
|
142
159
|
`/session` to create, resume, export, or compact a local conversation. Session
|
|
143
160
|
exports redact recognized API-key prefixes. The status line uses only
|
|
144
|
-
provider-reported token usage. It shows
|
|
145
|
-
model exposes prompt and completion prices, and
|
|
161
|
+
provider-reported token usage. It shows `$0.00` for Moderado Cloud free routes,
|
|
162
|
+
calculates cost when a model exposes prompt and completion prices, and shows
|
|
163
|
+
**Cost unknown** when a metered model's pricing is unavailable.
|
|
146
164
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
in `~/.moderado/config.json`; protect that file and never commit it.
|
|
165
|
+
Saved OpenAI-compatible profiles continue to support providers such as
|
|
166
|
+
OpenRouter, Z.AI, DeepSeek, Moonshot, and Mistral. Compatibility depends on each
|
|
167
|
+
provider supporting `/v1/models` and streaming `/v1/chat/completions` with tool
|
|
168
|
+
calls. Cloud keys are never written in plaintext to `~/.moderado/config.json`.
|
|
152
169
|
|
|
153
170
|
### 1. Build & Test
|
|
154
171
|
```bash
|
|
@@ -213,4 +230,8 @@ Run `moderado doctor` to check the local Node runtime, workspace, Moderado home
|
|
|
213
230
|
|
|
214
231
|
## Windows credential storage
|
|
215
232
|
|
|
216
|
-
On Windows,
|
|
233
|
+
On Windows, `/login` stores Moderado Cloud credentials in Windows Credential
|
|
234
|
+
Manager and keeps only a credential reference in `~/.moderado/config.json`.
|
|
235
|
+
Non-Windows platforms use the current memory-only credential store, so log in
|
|
236
|
+
again for each new CLI process. Existing BYOK environment variables and saved
|
|
237
|
+
profiles remain independent.
|
package/dist/commands/chat.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export declare function formatSessionExitSummary(session: StoredSession, modelId
|
|
|
10
10
|
export declare function createAgentTask(request: string, mode: 'Plan' | 'Execute'): string;
|
|
11
11
|
export declare function createReviewTask(changedFiles: string[]): string;
|
|
12
12
|
export declare function createReviewToolRegistry(): IToolRegistry;
|
|
13
|
-
export declare const STANDARD_SLASH_COMMANDS: readonly ["/connect", "/model", "/init", "/btw", "/mcp", "/session", "/queue", "/workflow", "/review", "/map", "/budget", "/skills", "/clear", "/help", "/exit", "/quit"];
|
|
13
|
+
export declare const STANDARD_SLASH_COMMANDS: readonly ["/connect", "/login", "/model", "/init", "/btw", "/mcp", "/session", "/queue", "/workflow", "/review", "/map", "/budget", "/skills", "/clear", "/help", "/exit", "/quit"];
|
|
14
14
|
export declare function levenshteinDistance(a: string, b: string): number;
|
|
15
15
|
export interface SlashCommandAdvice {
|
|
16
16
|
isSlashCommand: boolean;
|
|
@@ -89,7 +89,7 @@ export declare function resolveLocalMetaQuery(input: string, context: LocalMetaQ
|
|
|
89
89
|
export declare function shouldFastRouteWebSearch(input: string): boolean;
|
|
90
90
|
/** Environment configuration overrides the optional persistent search endpoint. */
|
|
91
91
|
export declare function resolveWebSearchEndpoint(config: ModeradoConfig): string | undefined;
|
|
92
|
-
export declare function formatDirectWebSearchAnswer(result: ToolResult): string;
|
|
92
|
+
export declare function formatDirectWebSearchAnswer(result: ToolResult, modelConnected?: boolean): string;
|
|
93
93
|
/** Environment configuration overrides the persistent search provider preference. */
|
|
94
94
|
export declare function resolveWebSearchProvider(config: ModeradoConfig): WebSearchProviderName | undefined;
|
|
95
95
|
export declare function resolveWebSearchOptions(config: ModeradoConfig): WebSearchToolOptions;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../src/commands/chat.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAkG,kBAAkB,EAA0D,MAAM,iBAAiB,CAAC;AAC7M,OAAO,KAAK,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AACnF,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,WAAW,EAAoB,aAAa,EAAE,eAAe,EAAyB,UAAU,EAAmB,MAAM,qBAAqB,CAAC;AAC3L,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,
|
|
1
|
+
{"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../src/commands/chat.ts"],"names":[],"mappings":"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"}
|
package/dist/commands/chat.js
CHANGED
|
@@ -5,20 +5,20 @@ import { AgentLoop, PolicyManager, Router, cleanConversationalFiller, DEFAULT_MA
|
|
|
5
5
|
import { NvidiaAdapter } from '../vendor/providers/index.js';
|
|
6
6
|
import { createDefaultToolRegistry, ToolRegistry, canonicalizeRoot, createMcpTools, createWebSearchTool, discoverMcpServers, resolveInJail, WorkspaceCheckpointStore, WriteFileTool } from '../vendor/tools/index.js';
|
|
7
7
|
import { McpServerConfigSchema } from '../vendor/contracts/index.js';
|
|
8
|
-
import { getActiveConnection, loadConfig, removeMcpServer, resolveApiKey, resolveConnectionCredential, saveConnection, saveMcpServer, setMcpServerEnabled, storeConnectionCredential } from '../config.js';
|
|
8
|
+
import { freeModelPolicyFor, getActiveConnection, loadConfig, removeMcpServer, requiresCredentialReference, resolveApiKey, resolveConnectionCredential, saveConnection, saveMcpServer, setMcpServerEnabled, storeConnectionCredential } from '../config.js';
|
|
9
9
|
import { MemoryCredentialStore } from '../credentials.js';
|
|
10
10
|
import { WindowsCredentialStore } from '../windows_credentials.js';
|
|
11
11
|
import { TerminalApprovalHandler } from '../ui/terminal_approval.js';
|
|
12
12
|
import { selectCompatibleModelOverlay, selectModelOverlay, showModelConnectionRequired } from '../ui/model_selector.js';
|
|
13
|
-
import { connectProviderInteractive, isAuthenticationFailure, replaceProviderKeyInteractive } from '../ui/provider_connect.js';
|
|
13
|
+
import { connectProviderInteractive, isAuthenticationFailure, isModeradoCloudOAuthConnection, loginModeradoCloudInteractive, replaceProviderKeyInteractive } from '../ui/provider_connect.js';
|
|
14
14
|
import { initWorkspace } from './init.js';
|
|
15
15
|
import { expandMentions, createWorkspaceFileSource } from '../ui/file_mentions.js';
|
|
16
16
|
import { listFiles } from '../vendor/tools/index.js';
|
|
17
|
-
import { exitCleanly, promptInteractiveTurn, renderChatAnswerDelta, renderChatComposerCursor, renderChatThoughtTimeUpdate, renderChatUsageUpdate, renderFullWelcomeScreen, renderWelcomePopupLayer, terminalCleanExitDone, wrapText } from '../ui/welcome.js';
|
|
17
|
+
import { exitCleanly, promptInteractiveTurn, renderChatAnswerDelta, renderChatComposerCursor, renderChatThoughtTimeUpdate, renderChatUsageUpdate, renderGatewayFallbackUpdate, renderFullWelcomeScreen, renderWelcomePopupLayer, terminalCleanExitDone, wrapText } from '../ui/welcome.js';
|
|
18
18
|
import { accumulateSessionUsage, compactSessionMessages, createSession, exportSessionMarkdown, formatSessionCost, SessionStore } from '../sessions.js';
|
|
19
19
|
import { layerPromptBox, renderBoxLines, selectConfirmPopup, selectListPopup } from '../ui/popup.js';
|
|
20
20
|
import { askModalChoice } from '../ui/prompt.js';
|
|
21
|
-
import { findModelPricing } from '../model_pricing.js';
|
|
21
|
+
import { findModelPricing, isFreeModelOption } from '../model_pricing.js';
|
|
22
22
|
import { inspectGitWorkspace, readGitDiff } from '../vendor/tools/index.js';
|
|
23
23
|
import { buildWorkspaceMap } from '../repo_map.js';
|
|
24
24
|
import { evaluateTokenBudget, parseBudgetCommand } from '../budget.js';
|
|
@@ -64,6 +64,7 @@ export function createReviewToolRegistry() {
|
|
|
64
64
|
}
|
|
65
65
|
export const STANDARD_SLASH_COMMANDS = [
|
|
66
66
|
'/connect',
|
|
67
|
+
'/login',
|
|
67
68
|
'/model',
|
|
68
69
|
'/init',
|
|
69
70
|
'/btw',
|
|
@@ -169,7 +170,7 @@ export function formatTurnFailureAnswer(status, errorEvent, modelId, otherConnec
|
|
|
169
170
|
const cleanDetail = rawDetail
|
|
170
171
|
.replace(/^(?:NVIDIA NIM|OpenAI|OpenRouter|[a-zA-Z0-9_-]+)\s*(?:request failed|rate limit exceeded|service or model unavailable)?\s*(?:\([^)]*\))?\s*(?:during\s*streamChat)?:\s*/i, '')
|
|
171
172
|
.trim();
|
|
172
|
-
return `⚠️ **Model Error (${errorEvent?.code || 'ERR_INFERENCE_FAILED'}):**\n${cleanDetail || 'Model returned no response.'}\n\n👉 **Suggestion:** The model \`${modelId}\` encountered an error. Switch models with \`/model
|
|
173
|
+
return `⚠️ **Model Error (${errorEvent?.code || 'ERR_INFERENCE_FAILED'}):**\n${cleanDetail || 'Model returned no response.'}\n\n👉 **Suggestion:** The model \`${modelId}\` encountered an error. Switch models with \`/model\`, connect directly with \`/connect\`, or reconnect to the Gateway with \`/login\`${altSuggestion}.`;
|
|
173
174
|
}
|
|
174
175
|
export function resolveTurnAssistantAnswer(result, previousHistoryLength, lastErrorEvent, streamedAnswer = '', currentModel = 'unknown', otherConnections = []) {
|
|
175
176
|
if (result.status === 'cancelled') {
|
|
@@ -415,7 +416,7 @@ export function resolveWebSearchEndpoint(config) {
|
|
|
415
416
|
}
|
|
416
417
|
/** Provider results are raw page excerpts; strip block metadata so the no-provider fallback stays readable. */
|
|
417
418
|
const SEARCH_BLOCK_METADATA = /^(title|url|published|author|highlights?|source|score|favicon)\s*:/i;
|
|
418
|
-
export function formatDirectWebSearchAnswer(result) {
|
|
419
|
+
export function formatDirectWebSearchAnswer(result, modelConnected = false) {
|
|
419
420
|
if (result.status !== 'success')
|
|
420
421
|
return `Web search failed: ${result.output}`;
|
|
421
422
|
const lines = result.output
|
|
@@ -424,7 +425,7 @@ export function formatDirectWebSearchAnswer(result) {
|
|
|
424
425
|
.filter((line) => line && line !== '...' && line !== '---' && !SEARCH_BLOCK_METADATA.test(line) && !/^https?:\/\//i.test(line));
|
|
425
426
|
if (!lines.length)
|
|
426
427
|
return 'Web search returned no readable content.';
|
|
427
|
-
return `${lines.slice(0, 10).join('\n')}\n\n(no model connected — raw search excerpt)`;
|
|
428
|
+
return `${lines.slice(0, 10).join('\n')}\n\n(${modelConnected ? 'model summary unavailable — ' : 'no model connected — '}raw search excerpt)`;
|
|
428
429
|
}
|
|
429
430
|
/** Environment configuration overrides the persistent search provider preference. */
|
|
430
431
|
export function resolveWebSearchProvider(config) {
|
|
@@ -624,7 +625,7 @@ export async function executeBtwQuery(command, drawFrame, provider, currentModel
|
|
|
624
625
|
if (!provider || !currentModel) {
|
|
625
626
|
await drawFrame(renderBoxLines('By The Way (/btw)', [
|
|
626
627
|
'No model or provider connected.',
|
|
627
|
-
'
|
|
628
|
+
'Use /login for the Gateway or /connect for a direct provider, then select a model with /model.',
|
|
628
629
|
'',
|
|
629
630
|
'Press Esc or Enter to return.',
|
|
630
631
|
], 72));
|
|
@@ -720,7 +721,7 @@ export async function handleChatSession(args, version, signal) {
|
|
|
720
721
|
const activateConnection = (connection, explicitModel) => {
|
|
721
722
|
activeConnection = connection;
|
|
722
723
|
currentModel = explicitModel ?? connection.defaultModel ?? (connection.kind === 'nvidia-nim' ? 'auto' : undefined);
|
|
723
|
-
provider = new NvidiaAdapter({ apiKey: connection.apiKey, baseUrl: connection.baseUrl, providerId: connection.id, providerName: connection.displayName });
|
|
724
|
+
provider = new NvidiaAdapter({ apiKey: connection.id === 'moderado-cloud' ? undefined : connection.apiKey, baseUrl: connection.baseUrl, providerId: connection.id, providerName: connection.displayName });
|
|
724
725
|
};
|
|
725
726
|
if (activeConnection)
|
|
726
727
|
activateConnection(activeConnection, args.model);
|
|
@@ -735,6 +736,14 @@ export async function handleChatSession(args, version, signal) {
|
|
|
735
736
|
modelId: currentModel,
|
|
736
737
|
mode: activeMode,
|
|
737
738
|
});
|
|
739
|
+
if (activeSession.usage.available &&
|
|
740
|
+
!activeSession.usage.costKnown &&
|
|
741
|
+
activeSession.providerId === activeConnection?.id &&
|
|
742
|
+
freeModelPolicyFor(activeConnection?.id)?.freeCatalog) {
|
|
743
|
+
activeSession.usage.costKnown = true;
|
|
744
|
+
activeSession.usage.costUsd = 0;
|
|
745
|
+
sessionStore.save(activeSession);
|
|
746
|
+
}
|
|
738
747
|
let sessionTokens = activeSession.usage.totalTokens;
|
|
739
748
|
let isFirst = true;
|
|
740
749
|
if (config.typescriptLanguageServer)
|
|
@@ -787,7 +796,7 @@ export async function handleChatSession(args, version, signal) {
|
|
|
787
796
|
}
|
|
788
797
|
else {
|
|
789
798
|
const turn = await promptInteractiveTurn({
|
|
790
|
-
model: currentModel ?? 'No model connected — use /connect', tokens: Math.round(sessionTokens), cost: costLabel(), workspace: canonicalWorkspace, version,
|
|
799
|
+
model: currentModel ?? 'No model connected — use /login or /connect', tokens: Math.round(sessionTokens), cost: costLabel(), workspace: canonicalWorkspace, version,
|
|
791
800
|
usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated, initialMode: activeMode, initialAutoApprove: activeAutoApprove, isFirstTurn: isFirst, signal, chatQuestion: lastQuestion || undefined, chatAnswer: lastAnswer || undefined, chatThoughtTime: lastThoughtTime, outputTokenRate: lastOutputTokenRate, tokenUsage: lastTokenUsage,
|
|
792
801
|
queuedCommands: commandQueue.items,
|
|
793
802
|
onExit: exitMessage,
|
|
@@ -799,7 +808,7 @@ export async function handleChatSession(args, version, signal) {
|
|
|
799
808
|
}
|
|
800
809
|
if (activeConnection.kind === 'openai-compatible') {
|
|
801
810
|
const modelId = await selectCompatibleModelOverlay({
|
|
802
|
-
apiKey: activeConnection.apiKey,
|
|
811
|
+
apiKey: activeConnection.id === 'moderado-cloud' ? undefined : activeConnection.apiKey,
|
|
803
812
|
baseUrl: activeConnection.baseUrl,
|
|
804
813
|
providerId: activeConnection.id,
|
|
805
814
|
providerName: activeConnection.displayName,
|
|
@@ -815,7 +824,7 @@ export async function handleChatSession(args, version, signal) {
|
|
|
815
824
|
}
|
|
816
825
|
return modelId;
|
|
817
826
|
}
|
|
818
|
-
const selection = await selectModelOverlay({ apiKey: activeConnection.apiKey, currentModel, signal, saveSelectionByDefault: true, drawFrame });
|
|
827
|
+
const selection = await selectModelOverlay({ apiKey: activeConnection.id === 'moderado-cloud' ? undefined : activeConnection.apiKey, currentModel, signal, saveSelectionByDefault: true, drawFrame });
|
|
819
828
|
if (selection.modelId) {
|
|
820
829
|
currentModel = selection.modelId;
|
|
821
830
|
activeConnection = { ...activeConnection, defaultModel: selection.modelId };
|
|
@@ -824,11 +833,31 @@ export async function handleChatSession(args, version, signal) {
|
|
|
824
833
|
}
|
|
825
834
|
return selection.modelId;
|
|
826
835
|
},
|
|
836
|
+
onLogin: async (drawFrame) => {
|
|
837
|
+
const connection = await loginModeradoCloudInteractive({
|
|
838
|
+
signal,
|
|
839
|
+
drawFrame,
|
|
840
|
+
gatewayUrl: config.connections?.['moderado-cloud']?.baseUrl,
|
|
841
|
+
});
|
|
842
|
+
if (!connection)
|
|
843
|
+
return currentModel;
|
|
844
|
+
const runtimeConnection = requiresCredentialReference(connection) ? await storeConnectionCredential(connection, credentialStore) : connection;
|
|
845
|
+
saveConnection(runtimeConnection);
|
|
846
|
+
config = loadConfig();
|
|
847
|
+
activateConnection(runtimeConnection);
|
|
848
|
+
return currentModel ?? 'No model connected — use /login or /connect';
|
|
849
|
+
},
|
|
827
850
|
onConnect: async (drawFrame) => {
|
|
828
|
-
const connection = await connectProviderInteractive({
|
|
851
|
+
const connection = await connectProviderInteractive({
|
|
852
|
+
signal,
|
|
853
|
+
drawFrame,
|
|
854
|
+
savedConnections: config.connections,
|
|
855
|
+
connectProviders: config.connectProviders,
|
|
856
|
+
resolveSavedConnection: (saved) => resolveConnectionCredential(saved, credentialStore),
|
|
857
|
+
});
|
|
829
858
|
if (!connection)
|
|
830
859
|
return currentModel;
|
|
831
|
-
const runtimeConnection =
|
|
860
|
+
const runtimeConnection = requiresCredentialReference(connection) ? await storeConnectionCredential(connection, credentialStore) : connection;
|
|
832
861
|
saveConnection(runtimeConnection);
|
|
833
862
|
config = loadConfig();
|
|
834
863
|
activateConnection(runtimeConnection);
|
|
@@ -1071,7 +1100,7 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1071
1100
|
lastTokenUsage = undefined;
|
|
1072
1101
|
process.stdout.write('\x1b[H\x1b[J');
|
|
1073
1102
|
process.stdout.write(renderFullWelcomeScreen({
|
|
1074
|
-
model: currentModel ?? 'No model connected — use /connect', tokens: Math.round(sessionTokens), cost: costLabel(),
|
|
1103
|
+
model: currentModel ?? 'No model connected — use /login or /connect', tokens: Math.round(sessionTokens), cost: costLabel(),
|
|
1075
1104
|
usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated, workspace: canonicalWorkspace, mode: activeMode,
|
|
1076
1105
|
autoApprove: activeAutoApprove, chatQuestion: lastQuestion, chatAnswer: lastAnswer,
|
|
1077
1106
|
chatThoughtTime: lastThoughtTime, queuedCommands: commandQueue.items,
|
|
@@ -1087,7 +1116,7 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1087
1116
|
lastTokenUsage = undefined;
|
|
1088
1117
|
process.stdout.write('\x1b[H\x1b[J');
|
|
1089
1118
|
process.stdout.write(renderFullWelcomeScreen({
|
|
1090
|
-
model: currentModel ?? 'No model connected — use /connect', tokens: Math.round(sessionTokens), cost: costLabel(),
|
|
1119
|
+
model: currentModel ?? 'No model connected — use /login or /connect', tokens: Math.round(sessionTokens), cost: costLabel(),
|
|
1091
1120
|
usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated, workspace: canonicalWorkspace, mode: activeMode,
|
|
1092
1121
|
autoApprove: activeAutoApprove, chatQuestion: lastQuestion, chatAnswer: lastAnswer,
|
|
1093
1122
|
chatThoughtTime: lastThoughtTime, queuedCommands: commandQueue.items,
|
|
@@ -1122,7 +1151,7 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1122
1151
|
}
|
|
1123
1152
|
process.stdout.write('\x1b[H\x1b[J');
|
|
1124
1153
|
process.stdout.write(renderFullWelcomeScreen({
|
|
1125
|
-
model: currentModel ?? 'No model connected — use /connect', tokens: Math.round(sessionTokens), cost: costLabel(),
|
|
1154
|
+
model: currentModel ?? 'No model connected — use /login or /connect', tokens: Math.round(sessionTokens), cost: costLabel(),
|
|
1126
1155
|
usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated, workspace: canonicalWorkspace, mode: activeMode,
|
|
1127
1156
|
autoApprove: activeAutoApprove, chatQuestion: lastQuestion, chatAnswer: lastAnswer,
|
|
1128
1157
|
chatThoughtTime: lastThoughtTime,
|
|
@@ -1161,7 +1190,7 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1161
1190
|
lastTokenUsage = undefined;
|
|
1162
1191
|
process.stdout.write('\x1b[H\x1b[J');
|
|
1163
1192
|
process.stdout.write(renderFullWelcomeScreen({
|
|
1164
|
-
model: currentModel ?? 'No model connected — use /connect', tokens: Math.round(sessionTokens), cost: costLabel(),
|
|
1193
|
+
model: currentModel ?? 'No model connected — use /login or /connect', tokens: Math.round(sessionTokens), cost: costLabel(),
|
|
1165
1194
|
usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated, workspace: canonicalWorkspace, mode: activeMode,
|
|
1166
1195
|
autoApprove: activeAutoApprove, chatQuestion: lastQuestion, chatAnswer: lastAnswer,
|
|
1167
1196
|
chatThoughtTime: lastThoughtTime,
|
|
@@ -1200,7 +1229,7 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1200
1229
|
lastOutputTokenRate = undefined;
|
|
1201
1230
|
lastTokenUsage = undefined;
|
|
1202
1231
|
process.stdout.write('\x1b[H\x1b[J');
|
|
1203
|
-
process.stdout.write(renderFullWelcomeScreen({ model: currentModel ?? 'No model connected — use /connect', tokens: Math.round(sessionTokens), cost: costLabel(), usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated, workspace: canonicalWorkspace, mode: activeMode, autoApprove: activeAutoApprove, chatQuestion: lastQuestion, chatAnswer: lastAnswer, chatThoughtTime: lastThoughtTime, queuedCommands: commandQueue.items }, process.stdout.rows));
|
|
1232
|
+
process.stdout.write(renderFullWelcomeScreen({ model: currentModel ?? 'No model connected — use /login or /connect', tokens: Math.round(sessionTokens), cost: costLabel(), usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated, workspace: canonicalWorkspace, mode: activeMode, autoApprove: activeAutoApprove, chatQuestion: lastQuestion, chatAnswer: lastAnswer, chatThoughtTime: lastThoughtTime, queuedCommands: commandQueue.items }, process.stdout.rows));
|
|
1204
1233
|
process.stdout.write(renderChatComposerCursor({ width: process.stdout.columns }, 0));
|
|
1205
1234
|
continue;
|
|
1206
1235
|
}
|
|
@@ -1210,7 +1239,8 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1210
1239
|
if (trimmed === '/help') {
|
|
1211
1240
|
lastQuestion = trimmed;
|
|
1212
1241
|
lastAnswer = 'Available slash commands:\n' +
|
|
1213
|
-
' /connect
|
|
1242
|
+
' /connect - Connect directly to an external or local provider\n' +
|
|
1243
|
+
' /login - Connect to the Moderado Gateway or log in to Moderado Cloud\n' +
|
|
1214
1244
|
' /model - Switch active AI model\n' +
|
|
1215
1245
|
' /init - Scaffold AGENTS.md from workspace scan\n' +
|
|
1216
1246
|
' /mcp - Manage local MCP servers\n' +
|
|
@@ -1229,7 +1259,7 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1229
1259
|
lastTokenUsage = undefined;
|
|
1230
1260
|
process.stdout.write('\x1b[H\x1b[J');
|
|
1231
1261
|
process.stdout.write(renderFullWelcomeScreen({
|
|
1232
|
-
model: currentModel ?? 'No model connected — use /connect', tokens: Math.round(sessionTokens), cost: costLabel(),
|
|
1262
|
+
model: currentModel ?? 'No model connected — use /login or /connect', tokens: Math.round(sessionTokens), cost: costLabel(),
|
|
1233
1263
|
usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated, workspace: canonicalWorkspace, mode: activeMode,
|
|
1234
1264
|
autoApprove: activeAutoApprove, chatQuestion: lastQuestion, chatAnswer: lastAnswer,
|
|
1235
1265
|
chatThoughtTime: lastThoughtTime,
|
|
@@ -1262,7 +1292,7 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1262
1292
|
sessionStore.save(activeSession);
|
|
1263
1293
|
process.stdout.write('\x1b[H\x1b[J');
|
|
1264
1294
|
process.stdout.write(renderFullWelcomeScreen({
|
|
1265
|
-
model: currentModel ?? 'No model connected — use /connect', tokens: Math.round(sessionTokens), cost: costLabel(),
|
|
1295
|
+
model: currentModel ?? 'No model connected — use /login or /connect', tokens: Math.round(sessionTokens), cost: costLabel(),
|
|
1266
1296
|
usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated, workspace: canonicalWorkspace, mode: activeMode,
|
|
1267
1297
|
autoApprove: activeAutoApprove, chatQuestion: lastQuestion, chatAnswer: lastAnswer,
|
|
1268
1298
|
chatThoughtTime: lastThoughtTime,
|
|
@@ -1282,7 +1312,7 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1282
1312
|
sessionStore.save(activeSession);
|
|
1283
1313
|
process.stdout.write('\x1b[H\x1b[J');
|
|
1284
1314
|
process.stdout.write(renderFullWelcomeScreen({
|
|
1285
|
-
model: currentModel ?? 'No model connected — use /connect', tokens: Math.round(sessionTokens), cost: costLabel(),
|
|
1315
|
+
model: currentModel ?? 'No model connected — use /login or /connect', tokens: Math.round(sessionTokens), cost: costLabel(),
|
|
1286
1316
|
usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated, workspace: canonicalWorkspace, mode: activeMode,
|
|
1287
1317
|
autoApprove: activeAutoApprove, chatQuestion: lastQuestion, chatAnswer: lastAnswer,
|
|
1288
1318
|
chatThoughtTime: lastThoughtTime,
|
|
@@ -1294,8 +1324,12 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1294
1324
|
// Current-information lane: gather live evidence before inference so the model answers in one turn.
|
|
1295
1325
|
let liveSearch;
|
|
1296
1326
|
const liveSearchTool = tools.get('web_search');
|
|
1297
|
-
if (
|
|
1327
|
+
if (shouldFastRouteWebSearch(trimmed) && liveSearchTool) {
|
|
1298
1328
|
const searchStartedAt = Date.now();
|
|
1329
|
+
if (!provider && activeSession.usage.totalTokens === 0) {
|
|
1330
|
+
activeSession.usage.costKnown = true;
|
|
1331
|
+
activeSession.usage.costUsd = 0;
|
|
1332
|
+
}
|
|
1299
1333
|
lastQuestion = trimmed;
|
|
1300
1334
|
lastAnswer = 'Searching the web...';
|
|
1301
1335
|
lastThoughtTime = 0;
|
|
@@ -1334,13 +1368,16 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1334
1368
|
}
|
|
1335
1369
|
}
|
|
1336
1370
|
if (!provider) {
|
|
1337
|
-
process.stdout.write('\nNo
|
|
1338
|
-
const connection = await
|
|
1371
|
+
process.stdout.write('\nNo model is connected. Use /login for the Gateway or /connect for a direct provider.\n');
|
|
1372
|
+
const connection = await loginModeradoCloudInteractive({
|
|
1373
|
+
signal,
|
|
1374
|
+
gatewayUrl: config.connections?.['moderado-cloud']?.baseUrl,
|
|
1375
|
+
});
|
|
1339
1376
|
if (!connection) {
|
|
1340
|
-
process.stdout.write('No provider connected. Use /connect whenever you are ready.\n\n');
|
|
1377
|
+
process.stdout.write('No provider connected. Use /login or /connect whenever you are ready.\n\n');
|
|
1341
1378
|
continue;
|
|
1342
1379
|
}
|
|
1343
|
-
const runtimeConnection =
|
|
1380
|
+
const runtimeConnection = requiresCredentialReference(connection) ? await storeConnectionCredential(connection, credentialStore) : connection;
|
|
1344
1381
|
saveConnection(runtimeConnection);
|
|
1345
1382
|
config = loadConfig();
|
|
1346
1383
|
activateConnection(runtimeConnection);
|
|
@@ -1379,8 +1416,8 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1379
1416
|
const displayAnswer = streamedAnswer || lastAnswer;
|
|
1380
1417
|
process.stdout.write('\x1b[H\x1b[J');
|
|
1381
1418
|
process.stdout.write(renderFullWelcomeScreen({
|
|
1382
|
-
model: currentModel ?? 'No model connected — use /connect',
|
|
1383
|
-
tokens: Math.round(sessionTokens),
|
|
1419
|
+
model: currentModel ?? 'No model connected — use /login or /connect',
|
|
1420
|
+
tokens: Math.round(sessionTokens + (usageRecorded ? 0 : turnUsage?.usage.totalTokens ?? 0)),
|
|
1384
1421
|
cost: costLabel(),
|
|
1385
1422
|
usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated,
|
|
1386
1423
|
workspace: canonicalWorkspace,
|
|
@@ -1501,6 +1538,8 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1501
1538
|
eventListener: (event) => {
|
|
1502
1539
|
if (event.type === 'model_change')
|
|
1503
1540
|
usedModels.add(event.newModelId);
|
|
1541
|
+
if (event.type === 'gateway_status')
|
|
1542
|
+
process.stdout.write(renderGatewayFallbackUpdate(event, process.stdout.columns || 80));
|
|
1504
1543
|
if (event.type === 'usage') {
|
|
1505
1544
|
turnUsage = event;
|
|
1506
1545
|
lastTokenUsage = event;
|
|
@@ -1514,7 +1553,7 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1514
1553
|
requestAbort.abort();
|
|
1515
1554
|
}
|
|
1516
1555
|
if (budgetStopped || firstBudgetWarning || event.final || event.timestamp - lastUsagePaint >= 400) {
|
|
1517
|
-
process.stdout.write(renderChatUsageUpdate(event, process.stdout.columns || 80, budgetStopped ? 'Budget reached' : budgetWarningIssued ? 'Budget ≥80%' : undefined));
|
|
1556
|
+
process.stdout.write(renderChatUsageUpdate(event, process.stdout.columns || 80, budgetStopped ? 'Budget reached' : budgetWarningIssued ? 'Budget ≥80%' : undefined, `${Math.round(sessionTokens + event.usage.totalTokens)} tokens / ${costLabel()}`, process.stdout.rows || 24, activeMode));
|
|
1518
1557
|
lastUsagePaint = event.timestamp;
|
|
1519
1558
|
}
|
|
1520
1559
|
}
|
|
@@ -1554,28 +1593,42 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1554
1593
|
throw error;
|
|
1555
1594
|
if (thinkingTimer)
|
|
1556
1595
|
clearInterval(thinkingTimer);
|
|
1557
|
-
|
|
1558
|
-
|
|
1596
|
+
const cloudOAuth = isModeradoCloudOAuthConnection(activeConnection);
|
|
1597
|
+
process.stdout.write(cloudOAuth
|
|
1598
|
+
? '\nModerado Cloud rejected this browser login. Sign in again to retry once.\n'
|
|
1599
|
+
: `\nThe saved ${activeConnection.displayName} API key was rejected. Enter a replacement key to retry once.\n`);
|
|
1600
|
+
const replacement = cloudOAuth
|
|
1601
|
+
? await loginModeradoCloudInteractive({ signal, gatewayUrl: activeConnection.baseUrl })
|
|
1602
|
+
: await replaceProviderKeyInteractive(activeConnection, { signal });
|
|
1559
1603
|
if (!replacement)
|
|
1560
1604
|
throw error;
|
|
1561
1605
|
recordTurnUsage();
|
|
1562
1606
|
turnUsage = undefined;
|
|
1563
1607
|
usageRecorded = false;
|
|
1564
|
-
const secured =
|
|
1608
|
+
const secured = requiresCredentialReference(replacement) ? await storeConnectionCredential(replacement, credentialStore) : replacement;
|
|
1565
1609
|
saveConnection(secured);
|
|
1566
1610
|
config = loadConfig();
|
|
1567
1611
|
activateConnection(secured);
|
|
1568
1612
|
result = await runAgent();
|
|
1569
1613
|
}
|
|
1570
|
-
if (turnUsage
|
|
1614
|
+
if (turnUsage) {
|
|
1571
1615
|
let pricing;
|
|
1616
|
+
const modelIds = new Set([...usedModels, result.selectedModel.id]);
|
|
1617
|
+
const freePolicy = freeModelPolicyFor(activeConnection?.id);
|
|
1618
|
+
let inventory = [];
|
|
1572
1619
|
try {
|
|
1573
|
-
|
|
1574
|
-
pricing = findModelPricing(await provider.discoverModels(signal), result.selectedModel.id);
|
|
1620
|
+
inventory = await provider.discoverModels(signal);
|
|
1575
1621
|
}
|
|
1576
1622
|
catch {
|
|
1577
1623
|
// A valid answer remains usable when the provider catalog is unavailable.
|
|
1578
1624
|
}
|
|
1625
|
+
const allModelsAreFree = [...modelIds].every((modelId) => isFreeModelOption(inventory.find((entry) => entry.id === modelId) ?? { id: modelId }, modelId === result.selectedModel.id
|
|
1626
|
+
? result.selectedModel.classification
|
|
1627
|
+
: router.classifyModel(modelId, args.profile.includes('local')), freePolicy));
|
|
1628
|
+
if (allModelsAreFree)
|
|
1629
|
+
pricing = { prompt: '0', completion: '0' };
|
|
1630
|
+
else if (!turnUsage.estimated && modelIds.size === 1)
|
|
1631
|
+
pricing = findModelPricing(inventory, result.selectedModel.id);
|
|
1579
1632
|
recordTurnUsage(pricing);
|
|
1580
1633
|
}
|
|
1581
1634
|
recordTurnUsage();
|
|
@@ -1584,13 +1637,27 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1584
1637
|
sessionStore.save(activeSession);
|
|
1585
1638
|
const otherConnections = Object.keys(config.connections ?? {}).filter((id) => id !== activeConnection?.id);
|
|
1586
1639
|
const resolution = resolveTurnAssistantAnswer(result, conversationHistory.length, lastErrorEvent, streamedAnswer, currentModel, otherConnections);
|
|
1587
|
-
|
|
1640
|
+
const searchFallback = result.status === 'failed' && liveSearch?.status === 'success'
|
|
1641
|
+
? formatDirectWebSearchAnswer(liveSearch, true)
|
|
1642
|
+
: undefined;
|
|
1643
|
+
const hasSearchFallback = searchFallback !== undefined && searchFallback !== 'Web search returned no readable content.';
|
|
1644
|
+
lastAnswer = hasSearchFallback ? searchFallback : resolution.answer;
|
|
1588
1645
|
if (budgetStopped) {
|
|
1589
1646
|
const note = `Token budget reached (${tokenBudget?.toLocaleString()} per task). Task stopped; usage may exceed the limit because providers report in chunks.`;
|
|
1590
1647
|
lastAnswer = [lastAnswer, note].filter(Boolean).join('\n\n');
|
|
1591
1648
|
streamedAnswer = lastAnswer;
|
|
1592
1649
|
}
|
|
1593
|
-
if (resolution.isError) {
|
|
1650
|
+
if (resolution.isError && hasSearchFallback) {
|
|
1651
|
+
conversationHistory = [...conversationHistory, { role: 'user', content: trimmed }, { role: 'assistant', content: lastAnswer }];
|
|
1652
|
+
activeSession.messages = conversationHistory;
|
|
1653
|
+
activeSession.providerId = activeConnection?.id;
|
|
1654
|
+
activeSession.providerName = activeConnection?.displayName;
|
|
1655
|
+
activeSession.modelId = currentModel;
|
|
1656
|
+
activeSession.mode = activeMode;
|
|
1657
|
+
sessionStore.save(activeSession);
|
|
1658
|
+
streamedAnswer = lastAnswer;
|
|
1659
|
+
}
|
|
1660
|
+
else if (resolution.isError) {
|
|
1594
1661
|
streamedAnswer = lastAnswer;
|
|
1595
1662
|
}
|
|
1596
1663
|
else {
|
|
@@ -1628,12 +1695,12 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1628
1695
|
const cleanErr = rawErr
|
|
1629
1696
|
.replace(/^(?:NVIDIA NIM|OpenAI|OpenRouter|[a-zA-Z0-9_-]+)\s*(?:request failed|rate limit exceeded|service or model unavailable)?\s*(?:\([^)]*\))?\s*(?:during\s*streamChat)?:\s*/i, '')
|
|
1630
1697
|
.trim();
|
|
1631
|
-
lastAnswer = `⚠️ **Error:** ${cleanErr || rawErr}\n\n👉 **Suggestion:** Switch models with \`/model
|
|
1698
|
+
lastAnswer = `⚠️ **Error:** ${cleanErr || rawErr}\n\n👉 **Suggestion:** Switch models with \`/model\`, connect directly with \`/connect\`, or reconnect to the Gateway with \`/login\`${altSuggestion}.`;
|
|
1632
1699
|
lastThoughtTime = Math.max(0.001, (Date.now() - startedAt) / 1000);
|
|
1633
1700
|
lastOutputTokenRate = undefined;
|
|
1634
1701
|
process.stdout.write('\x1b[H\x1b[J');
|
|
1635
1702
|
process.stdout.write(renderFullWelcomeScreen({
|
|
1636
|
-
model: currentModel ?? 'No model connected — use /connect',
|
|
1703
|
+
model: currentModel ?? 'No model connected — use /login or /connect',
|
|
1637
1704
|
tokens: Math.round(sessionTokens),
|
|
1638
1705
|
cost: costLabel(),
|
|
1639
1706
|
usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated,
|