moderado 0.3.10 → 0.4.10
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 +52 -30
- package/dist/commands/chat.d.ts +3 -2
- package/dist/commands/chat.d.ts.map +1 -1
- package/dist/commands/chat.js +101 -29
- package/dist/commands/chat.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/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 +7 -1
- package/dist/ui/model_selector.d.ts.map +1 -1
- package/dist/ui/model_selector.js +52 -13
- package/dist/ui/model_selector.js.map +1 -1
- package/dist/ui/popup.d.ts.map +1 -1
- package/dist/ui/popup.js +22 -2
- package/dist/ui/popup.js.map +1 -1
- package/dist/ui/provider_connect.d.ts +30 -1
- package/dist/ui/provider_connect.d.ts.map +1 -1
- package/dist/ui/provider_connect.js +202 -4
- package/dist/ui/provider_connect.js.map +1 -1
- package/dist/ui/welcome.d.ts +4 -2
- package/dist/ui/welcome.d.ts.map +1 -1
- package/dist/ui/welcome.js +46 -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/models.d.ts +4 -0
- package/dist/vendor/contracts/models.d.ts.map +1 -1
- package/dist/vendor/contracts/models.js +2 -0
- package/dist/vendor/contracts/models.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 +9 -2
- package/dist/vendor/providers/presets.d.ts.map +1 -1
- package/dist/vendor/providers/presets.js +43 -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 +2 -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,16 +88,19 @@ 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 0.4.10 source build uses `/connect` for all providers and does
|
|
94
|
+
not offer `/login`. The previous 0.3.10 release is self-contained and works
|
|
95
|
+
without the Gateway. All three use the `moderado` command name.
|
|
96
|
+
|
|
97
|
+
Install the latest release with `npm install -g moderado`. To stay on the
|
|
98
|
+
self-contained 0.3.10 CLI, run `npm install -g moderado@0.3.10`.
|
|
99
|
+
|
|
92
100
|
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.10 at https://github.com/marcuz-apl/moderado/releases/tag/v0.3.10.
|
|
97
|
-
The curl installer (`scripts/install.sh`, Linux x64 + macOS arm64), the
|
|
98
|
-
Homebrew tap, and the Scoop bucket are available; the winget submission is
|
|
99
|
-
pending merge and AUR has not been uploaded yet. See docs/DISTRIBUTION.md in
|
|
100
|
-
the repository.
|
|
101
|
+
pnpm add -g moderado, npx -y moderado@latest --help). Prebuilt binaries and the
|
|
102
|
+
curl installer are documented in the root README. Windows installation options
|
|
103
|
+
are npm and Scoop. See `docs/DISTRIBUTION.md` in the repository.
|
|
101
104
|
To build from source instead:
|
|
102
105
|
|
|
103
106
|
```bash
|
|
@@ -112,44 +115,59 @@ Maintainers can install a verified release artifact directly with
|
|
|
112
115
|
`npm install -g ./moderado-<version>.tgz`. See
|
|
113
116
|
[docs/RELEASING.md](../../docs/RELEASING.md) for the review procedure.
|
|
114
117
|
|
|
115
|
-
### Connect
|
|
118
|
+
### Connect to the Moderado Gateway
|
|
119
|
+
|
|
120
|
+
In the current source build, run `/connect`, choose Moderado Gateway, and then
|
|
121
|
+
choose public access or sign in.
|
|
122
|
+
Browser authorization or a Moderado Cloud `mrd_` API key is optional for account
|
|
123
|
+
features. `/model` lists configured Gateway routes: `auto` follows Gateway
|
|
124
|
+
routing, while selecting a route pins requests to it. Existing BYOK and local
|
|
125
|
+
provider profiles remain independent.
|
|
126
|
+
|
|
127
|
+
The same `/connect` menu offers OpenRouter, NVIDIA NIM, Agnes AI, OrcaRouter,
|
|
128
|
+
Ollama, LM Studio, and custom OpenAI-compatible providers. The published 0.4.8
|
|
129
|
+
release still uses `/login` for the Gateway.
|
|
130
|
+
|
|
131
|
+
Cloud OAuth credentials last 30 days and have no refresh token; authorize again
|
|
132
|
+
after expiry. On Windows, credentials are stored in Windows Credential Manager.
|
|
133
|
+
On other platforms, the current credential store is memory-only, so log in again
|
|
134
|
+
after starting a new CLI process.
|
|
116
135
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
`OPENROUTER_API_KEY`.
|
|
136
|
+
Cloud connections use their configured Gateway URL. Loopback URLs such as
|
|
137
|
+
`http://127.0.0.1:4788/v1` are treated as local development; HTTPS URLs for a
|
|
138
|
+
NAS, VPS, or other remote host are used as configured. To set the URL before
|
|
139
|
+
first login, define `MODERADO_CLOUD_BASE_URL`. `MODERADO_CLOUD_ENV=development`
|
|
140
|
+
and `MODERADO_CLOUD_ENV=production` remain available as explicit overrides.
|
|
123
141
|
|
|
124
142
|
Run `moderado doctor` to inspect local setup without exposing secrets. Add
|
|
125
143
|
`--connectivity` only when you want an optional live model-catalog check.
|
|
126
144
|
`npm test` never performs live provider calls.
|
|
127
145
|
## Getting Started & Development
|
|
128
146
|
|
|
129
|
-
###
|
|
147
|
+
### Log in and select a model in the TUI
|
|
130
148
|
|
|
131
149
|
Run `moderado` to open the TUI immediately. A fresh installation does not require
|
|
132
150
|
an API key or a preselected model. The welcome card shows **No model connected —
|
|
133
151
|
use `/connect`** until you add one.
|
|
134
152
|
|
|
135
|
-
Use `/connect` to
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
153
|
+
Use `/connect` to choose the Gateway or another provider. The Gateway offers a
|
|
154
|
+
route picker after connection; `/model` can change the route later. Cloud `auto`
|
|
155
|
+
follows the Gateway's configured pool, while choosing a route pins subsequent
|
|
156
|
+
requests to it.
|
|
139
157
|
|
|
140
158
|
### Sessions and usage
|
|
141
159
|
|
|
142
160
|
Moderado stores sessions per workspace in `~/.moderado/sessions/`. Use
|
|
143
161
|
`/session` to create, resume, export, or compact a local conversation. Session
|
|
144
162
|
exports redact recognized API-key prefixes. The status line uses only
|
|
145
|
-
provider-reported token usage. It shows
|
|
146
|
-
model exposes prompt and completion prices, and
|
|
163
|
+
provider-reported token usage. It shows `$0.00` for Moderado Cloud free routes,
|
|
164
|
+
calculates cost when a model exposes prompt and completion prices, and shows
|
|
165
|
+
**Cost unknown** when a metered model's pricing is unavailable.
|
|
147
166
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
in `~/.moderado/config.json`; protect that file and never commit it.
|
|
167
|
+
Saved OpenAI-compatible profiles continue to support providers such as
|
|
168
|
+
OpenRouter, Z.AI, DeepSeek, Moonshot, and Mistral. Compatibility depends on each
|
|
169
|
+
provider supporting `/v1/models` and streaming `/v1/chat/completions` with tool
|
|
170
|
+
calls. Cloud keys are never written in plaintext to `~/.moderado/config.json`.
|
|
153
171
|
|
|
154
172
|
### 1. Build & Test
|
|
155
173
|
```bash
|
|
@@ -214,4 +232,8 @@ Run `moderado doctor` to check the local Node runtime, workspace, Moderado home
|
|
|
214
232
|
|
|
215
233
|
## Windows credential storage
|
|
216
234
|
|
|
217
|
-
On Windows,
|
|
235
|
+
On Windows, Gateway sign-in through `/connect` stores credentials in Windows Credential
|
|
236
|
+
Manager and keeps only a credential reference in `~/.moderado/config.json`.
|
|
237
|
+
Non-Windows platforms use the current memory-only credential store, so log in
|
|
238
|
+
again for each new CLI process. Existing BYOK environment variables and saved
|
|
239
|
+
profiles remain independent.
|
package/dist/commands/chat.d.ts
CHANGED
|
@@ -3,9 +3,10 @@ import { discoverMcpServers } from '@moderado/tools';
|
|
|
3
3
|
import type { WebSearchProviderName, WebSearchToolOptions } from '@moderado/tools';
|
|
4
4
|
import { ApprovalDecision, ApprovalRequest, ChatMessage, IToolRegistry, McpServerConfig, ToolResult } from '@moderado/contracts';
|
|
5
5
|
import { CliParsedArgs } from '../args.js';
|
|
6
|
-
import { ModeradoConfig } from '../config.js';
|
|
6
|
+
import { ModeradoConfig, ProviderConnection } from '../config.js';
|
|
7
7
|
import { StoredSession } from '../sessions.js';
|
|
8
8
|
import { selectConfirmPopup, selectListPopup } from '../ui/popup.js';
|
|
9
|
+
export declare function createConnectionProvider(connection: ProviderConnection): NvidiaAdapter;
|
|
9
10
|
export declare function formatSessionExitSummary(session: StoredSession, modelId?: string): string;
|
|
10
11
|
export declare function createAgentTask(request: string, mode: 'Plan' | 'Execute'): string;
|
|
11
12
|
export declare function createReviewTask(changedFiles: string[]): string;
|
|
@@ -89,7 +90,7 @@ export declare function resolveLocalMetaQuery(input: string, context: LocalMetaQ
|
|
|
89
90
|
export declare function shouldFastRouteWebSearch(input: string): boolean;
|
|
90
91
|
/** Environment configuration overrides the optional persistent search endpoint. */
|
|
91
92
|
export declare function resolveWebSearchEndpoint(config: ModeradoConfig): string | undefined;
|
|
92
|
-
export declare function formatDirectWebSearchAnswer(result: ToolResult): string;
|
|
93
|
+
export declare function formatDirectWebSearchAnswer(result: ToolResult, modelConnected?: boolean): string;
|
|
93
94
|
/** Environment configuration overrides the persistent search provider preference. */
|
|
94
95
|
export declare function resolveWebSearchProvider(config: ModeradoConfig): WebSearchProviderName | undefined;
|
|
95
96
|
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,24 +5,27 @@ 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';
|
|
25
25
|
import { discoverSkills, formatSkillContext, formatSkillsList, getEnabledSkillNames, selectEnabledSkills, setSkillEnabled } from '../skills.js';
|
|
26
|
+
export function createConnectionProvider(connection) {
|
|
27
|
+
return new NvidiaAdapter({ apiKey: connection.apiKey, baseUrl: connection.baseUrl, providerId: connection.id, providerName: connection.displayName });
|
|
28
|
+
}
|
|
26
29
|
export function formatSessionExitSummary(session, modelId) {
|
|
27
30
|
const usage = session.usage;
|
|
28
31
|
const tokens = usage.available
|
|
@@ -169,7 +172,7 @@ export function formatTurnFailureAnswer(status, errorEvent, modelId, otherConnec
|
|
|
169
172
|
const cleanDetail = rawDetail
|
|
170
173
|
.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
174
|
.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\` or
|
|
175
|
+
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\` or choose a provider with \`/connect\`${altSuggestion}.`;
|
|
173
176
|
}
|
|
174
177
|
export function resolveTurnAssistantAnswer(result, previousHistoryLength, lastErrorEvent, streamedAnswer = '', currentModel = 'unknown', otherConnections = []) {
|
|
175
178
|
if (result.status === 'cancelled') {
|
|
@@ -415,7 +418,7 @@ export function resolveWebSearchEndpoint(config) {
|
|
|
415
418
|
}
|
|
416
419
|
/** Provider results are raw page excerpts; strip block metadata so the no-provider fallback stays readable. */
|
|
417
420
|
const SEARCH_BLOCK_METADATA = /^(title|url|published|author|highlights?|source|score|favicon)\s*:/i;
|
|
418
|
-
export function formatDirectWebSearchAnswer(result) {
|
|
421
|
+
export function formatDirectWebSearchAnswer(result, modelConnected = false) {
|
|
419
422
|
if (result.status !== 'success')
|
|
420
423
|
return `Web search failed: ${result.output}`;
|
|
421
424
|
const lines = result.output
|
|
@@ -424,7 +427,7 @@ export function formatDirectWebSearchAnswer(result) {
|
|
|
424
427
|
.filter((line) => line && line !== '...' && line !== '---' && !SEARCH_BLOCK_METADATA.test(line) && !/^https?:\/\//i.test(line));
|
|
425
428
|
if (!lines.length)
|
|
426
429
|
return 'Web search returned no readable content.';
|
|
427
|
-
return `${lines.slice(0, 10).join('\n')}\n\n(no model connected — raw search excerpt)`;
|
|
430
|
+
return `${lines.slice(0, 10).join('\n')}\n\n(${modelConnected ? 'model summary unavailable — ' : 'no model connected — '}raw search excerpt)`;
|
|
428
431
|
}
|
|
429
432
|
/** Environment configuration overrides the persistent search provider preference. */
|
|
430
433
|
export function resolveWebSearchProvider(config) {
|
|
@@ -624,7 +627,7 @@ export async function executeBtwQuery(command, drawFrame, provider, currentModel
|
|
|
624
627
|
if (!provider || !currentModel) {
|
|
625
628
|
await drawFrame(renderBoxLines('By The Way (/btw)', [
|
|
626
629
|
'No model or provider connected.',
|
|
627
|
-
'
|
|
630
|
+
'Use /connect to choose a provider and model.',
|
|
628
631
|
'',
|
|
629
632
|
'Press Esc or Enter to return.',
|
|
630
633
|
], 72));
|
|
@@ -720,7 +723,7 @@ export async function handleChatSession(args, version, signal) {
|
|
|
720
723
|
const activateConnection = (connection, explicitModel) => {
|
|
721
724
|
activeConnection = connection;
|
|
722
725
|
currentModel = explicitModel ?? connection.defaultModel ?? (connection.kind === 'nvidia-nim' ? 'auto' : undefined);
|
|
723
|
-
provider =
|
|
726
|
+
provider = createConnectionProvider(connection);
|
|
724
727
|
};
|
|
725
728
|
if (activeConnection)
|
|
726
729
|
activateConnection(activeConnection, args.model);
|
|
@@ -735,6 +738,14 @@ export async function handleChatSession(args, version, signal) {
|
|
|
735
738
|
modelId: currentModel,
|
|
736
739
|
mode: activeMode,
|
|
737
740
|
});
|
|
741
|
+
if (activeSession.usage.available &&
|
|
742
|
+
!activeSession.usage.costKnown &&
|
|
743
|
+
activeSession.providerId === activeConnection?.id &&
|
|
744
|
+
freeModelPolicyFor(activeConnection?.id)?.freeCatalog) {
|
|
745
|
+
activeSession.usage.costKnown = true;
|
|
746
|
+
activeSession.usage.costUsd = 0;
|
|
747
|
+
sessionStore.save(activeSession);
|
|
748
|
+
}
|
|
738
749
|
let sessionTokens = activeSession.usage.totalTokens;
|
|
739
750
|
let isFirst = true;
|
|
740
751
|
if (config.typescriptLanguageServer)
|
|
@@ -799,7 +810,7 @@ export async function handleChatSession(args, version, signal) {
|
|
|
799
810
|
}
|
|
800
811
|
if (activeConnection.kind === 'openai-compatible') {
|
|
801
812
|
const modelId = await selectCompatibleModelOverlay({
|
|
802
|
-
apiKey: activeConnection.apiKey,
|
|
813
|
+
apiKey: activeConnection.id === 'moderado-cloud' ? undefined : activeConnection.apiKey,
|
|
803
814
|
baseUrl: activeConnection.baseUrl,
|
|
804
815
|
providerId: activeConnection.id,
|
|
805
816
|
providerName: activeConnection.displayName,
|
|
@@ -815,7 +826,7 @@ export async function handleChatSession(args, version, signal) {
|
|
|
815
826
|
}
|
|
816
827
|
return modelId;
|
|
817
828
|
}
|
|
818
|
-
const selection = await selectModelOverlay({ apiKey: activeConnection.apiKey, currentModel, signal, saveSelectionByDefault: true, drawFrame });
|
|
829
|
+
const selection = await selectModelOverlay({ apiKey: activeConnection.id === 'moderado-cloud' ? undefined : activeConnection.apiKey, currentModel, signal, saveSelectionByDefault: true, drawFrame });
|
|
819
830
|
if (selection.modelId) {
|
|
820
831
|
currentModel = selection.modelId;
|
|
821
832
|
activeConnection = { ...activeConnection, defaultModel: selection.modelId };
|
|
@@ -825,10 +836,18 @@ export async function handleChatSession(args, version, signal) {
|
|
|
825
836
|
return selection.modelId;
|
|
826
837
|
},
|
|
827
838
|
onConnect: async (drawFrame) => {
|
|
828
|
-
const connection = await connectProviderInteractive({
|
|
839
|
+
const connection = await connectProviderInteractive({
|
|
840
|
+
signal,
|
|
841
|
+
drawFrame,
|
|
842
|
+
gatewayUrl: config.connections?.['moderado-cloud']?.baseUrl,
|
|
843
|
+
activeConnection,
|
|
844
|
+
savedConnections: config.connections,
|
|
845
|
+
connectProviders: config.connectProviders,
|
|
846
|
+
resolveSavedConnection: (saved) => resolveConnectionCredential(saved, credentialStore),
|
|
847
|
+
});
|
|
829
848
|
if (!connection)
|
|
830
849
|
return currentModel;
|
|
831
|
-
const runtimeConnection =
|
|
850
|
+
const runtimeConnection = requiresCredentialReference(connection) ? await storeConnectionCredential(connection, credentialStore) : connection;
|
|
832
851
|
saveConnection(runtimeConnection);
|
|
833
852
|
config = loadConfig();
|
|
834
853
|
activateConnection(runtimeConnection);
|
|
@@ -1210,7 +1229,7 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1210
1229
|
if (trimmed === '/help') {
|
|
1211
1230
|
lastQuestion = trimmed;
|
|
1212
1231
|
lastAnswer = 'Available slash commands:\n' +
|
|
1213
|
-
' /connect
|
|
1232
|
+
' /connect - Choose a Gateway, hosted, or local provider\n' +
|
|
1214
1233
|
' /model - Switch active AI model\n' +
|
|
1215
1234
|
' /init - Scaffold AGENTS.md from workspace scan\n' +
|
|
1216
1235
|
' /mcp - Manage local MCP servers\n' +
|
|
@@ -1294,8 +1313,12 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1294
1313
|
// Current-information lane: gather live evidence before inference so the model answers in one turn.
|
|
1295
1314
|
let liveSearch;
|
|
1296
1315
|
const liveSearchTool = tools.get('web_search');
|
|
1297
|
-
if (
|
|
1316
|
+
if (shouldFastRouteWebSearch(trimmed) && liveSearchTool) {
|
|
1298
1317
|
const searchStartedAt = Date.now();
|
|
1318
|
+
if (!provider && activeSession.usage.totalTokens === 0) {
|
|
1319
|
+
activeSession.usage.costKnown = true;
|
|
1320
|
+
activeSession.usage.costUsd = 0;
|
|
1321
|
+
}
|
|
1299
1322
|
lastQuestion = trimmed;
|
|
1300
1323
|
lastAnswer = 'Searching the web...';
|
|
1301
1324
|
lastThoughtTime = 0;
|
|
@@ -1334,13 +1357,29 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1334
1357
|
}
|
|
1335
1358
|
}
|
|
1336
1359
|
if (!provider) {
|
|
1337
|
-
process.stdout.write('\nNo
|
|
1338
|
-
const connection = await connectProviderInteractive({
|
|
1360
|
+
process.stdout.write('\nNo model is connected. Use /connect to choose a provider.\n');
|
|
1361
|
+
const connection = await connectProviderInteractive({
|
|
1362
|
+
signal,
|
|
1363
|
+
drawFrame: (popupLines) => {
|
|
1364
|
+
process.stdout.write('\x1b[H\x1b[J');
|
|
1365
|
+
process.stdout.write(renderWelcomePopupLayer({
|
|
1366
|
+
model: currentModel ?? 'No model connected — use /connect',
|
|
1367
|
+
tokens: Math.round(sessionTokens), cost: costLabel(), workspace: canonicalWorkspace,
|
|
1368
|
+
mode: activeMode, autoApprove: activeAutoApprove,
|
|
1369
|
+
chatQuestion: lastQuestion, chatAnswer: lastAnswer,
|
|
1370
|
+
}, popupLines, process.stdout.columns, process.stdout.rows));
|
|
1371
|
+
},
|
|
1372
|
+
gatewayUrl: config.connections?.['moderado-cloud']?.baseUrl,
|
|
1373
|
+
activeConnection,
|
|
1374
|
+
savedConnections: config.connections,
|
|
1375
|
+
connectProviders: config.connectProviders,
|
|
1376
|
+
resolveSavedConnection: (saved) => resolveConnectionCredential(saved, credentialStore),
|
|
1377
|
+
});
|
|
1339
1378
|
if (!connection) {
|
|
1340
1379
|
process.stdout.write('No provider connected. Use /connect whenever you are ready.\n\n');
|
|
1341
1380
|
continue;
|
|
1342
1381
|
}
|
|
1343
|
-
const runtimeConnection =
|
|
1382
|
+
const runtimeConnection = requiresCredentialReference(connection) ? await storeConnectionCredential(connection, credentialStore) : connection;
|
|
1344
1383
|
saveConnection(runtimeConnection);
|
|
1345
1384
|
config = loadConfig();
|
|
1346
1385
|
activateConnection(runtimeConnection);
|
|
@@ -1380,7 +1419,7 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1380
1419
|
process.stdout.write('\x1b[H\x1b[J');
|
|
1381
1420
|
process.stdout.write(renderFullWelcomeScreen({
|
|
1382
1421
|
model: currentModel ?? 'No model connected — use /connect',
|
|
1383
|
-
tokens: Math.round(sessionTokens),
|
|
1422
|
+
tokens: Math.round(sessionTokens + (usageRecorded ? 0 : turnUsage?.usage.totalTokens ?? 0)),
|
|
1384
1423
|
cost: costLabel(),
|
|
1385
1424
|
usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated,
|
|
1386
1425
|
workspace: canonicalWorkspace,
|
|
@@ -1501,6 +1540,8 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1501
1540
|
eventListener: (event) => {
|
|
1502
1541
|
if (event.type === 'model_change')
|
|
1503
1542
|
usedModels.add(event.newModelId);
|
|
1543
|
+
if (event.type === 'gateway_status')
|
|
1544
|
+
process.stdout.write(renderGatewayFallbackUpdate(event, process.stdout.columns || 80));
|
|
1504
1545
|
if (event.type === 'usage') {
|
|
1505
1546
|
turnUsage = event;
|
|
1506
1547
|
lastTokenUsage = event;
|
|
@@ -1514,7 +1555,7 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1514
1555
|
requestAbort.abort();
|
|
1515
1556
|
}
|
|
1516
1557
|
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));
|
|
1558
|
+
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
1559
|
lastUsagePaint = event.timestamp;
|
|
1519
1560
|
}
|
|
1520
1561
|
}
|
|
@@ -1554,28 +1595,45 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1554
1595
|
throw error;
|
|
1555
1596
|
if (thinkingTimer)
|
|
1556
1597
|
clearInterval(thinkingTimer);
|
|
1557
|
-
|
|
1558
|
-
const
|
|
1598
|
+
const cloudOAuth = isModeradoCloudOAuthConnection(activeConnection);
|
|
1599
|
+
const cloudWithoutKey = activeConnection.id === 'moderado-cloud' && !activeConnection.apiKey;
|
|
1600
|
+
process.stdout.write(cloudWithoutKey
|
|
1601
|
+
? '\nThis free Gateway route requires a Moderado website API key. Sign in or enter a key to retry once.\n'
|
|
1602
|
+
: cloudOAuth
|
|
1603
|
+
? '\nModerado Cloud rejected this browser login. Sign in again to retry once.\n'
|
|
1604
|
+
: `\nThe saved ${activeConnection.displayName} API key was rejected. Enter a replacement key to retry once.\n`);
|
|
1605
|
+
const replacement = cloudOAuth || cloudWithoutKey
|
|
1606
|
+
? await loginModeradoCloudInteractive({ signal, gatewayUrl: activeConnection.baseUrl })
|
|
1607
|
+
: await replaceProviderKeyInteractive(activeConnection, { signal });
|
|
1559
1608
|
if (!replacement)
|
|
1560
1609
|
throw error;
|
|
1561
1610
|
recordTurnUsage();
|
|
1562
1611
|
turnUsage = undefined;
|
|
1563
1612
|
usageRecorded = false;
|
|
1564
|
-
const secured =
|
|
1613
|
+
const secured = requiresCredentialReference(replacement) ? await storeConnectionCredential(replacement, credentialStore) : replacement;
|
|
1565
1614
|
saveConnection(secured);
|
|
1566
1615
|
config = loadConfig();
|
|
1567
1616
|
activateConnection(secured);
|
|
1568
1617
|
result = await runAgent();
|
|
1569
1618
|
}
|
|
1570
|
-
if (turnUsage
|
|
1619
|
+
if (turnUsage) {
|
|
1571
1620
|
let pricing;
|
|
1621
|
+
const modelIds = new Set([...usedModels, result.selectedModel.id]);
|
|
1622
|
+
const freePolicy = freeModelPolicyFor(activeConnection?.id);
|
|
1623
|
+
let inventory = [];
|
|
1572
1624
|
try {
|
|
1573
|
-
|
|
1574
|
-
pricing = findModelPricing(await provider.discoverModels(signal), result.selectedModel.id);
|
|
1625
|
+
inventory = await provider.discoverModels(signal);
|
|
1575
1626
|
}
|
|
1576
1627
|
catch {
|
|
1577
1628
|
// A valid answer remains usable when the provider catalog is unavailable.
|
|
1578
1629
|
}
|
|
1630
|
+
const allModelsAreFree = [...modelIds].every((modelId) => isFreeModelOption(inventory.find((entry) => entry.id === modelId) ?? { id: modelId }, modelId === result.selectedModel.id
|
|
1631
|
+
? result.selectedModel.classification
|
|
1632
|
+
: router.classifyModel(modelId, args.profile.includes('local')), freePolicy));
|
|
1633
|
+
if (allModelsAreFree)
|
|
1634
|
+
pricing = { prompt: '0', completion: '0' };
|
|
1635
|
+
else if (!turnUsage.estimated && modelIds.size === 1)
|
|
1636
|
+
pricing = findModelPricing(inventory, result.selectedModel.id);
|
|
1579
1637
|
recordTurnUsage(pricing);
|
|
1580
1638
|
}
|
|
1581
1639
|
recordTurnUsage();
|
|
@@ -1584,13 +1642,27 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1584
1642
|
sessionStore.save(activeSession);
|
|
1585
1643
|
const otherConnections = Object.keys(config.connections ?? {}).filter((id) => id !== activeConnection?.id);
|
|
1586
1644
|
const resolution = resolveTurnAssistantAnswer(result, conversationHistory.length, lastErrorEvent, streamedAnswer, currentModel, otherConnections);
|
|
1587
|
-
|
|
1645
|
+
const searchFallback = result.status === 'failed' && liveSearch?.status === 'success'
|
|
1646
|
+
? formatDirectWebSearchAnswer(liveSearch, true)
|
|
1647
|
+
: undefined;
|
|
1648
|
+
const hasSearchFallback = searchFallback !== undefined && searchFallback !== 'Web search returned no readable content.';
|
|
1649
|
+
lastAnswer = hasSearchFallback ? searchFallback : resolution.answer;
|
|
1588
1650
|
if (budgetStopped) {
|
|
1589
1651
|
const note = `Token budget reached (${tokenBudget?.toLocaleString()} per task). Task stopped; usage may exceed the limit because providers report in chunks.`;
|
|
1590
1652
|
lastAnswer = [lastAnswer, note].filter(Boolean).join('\n\n');
|
|
1591
1653
|
streamedAnswer = lastAnswer;
|
|
1592
1654
|
}
|
|
1593
|
-
if (resolution.isError) {
|
|
1655
|
+
if (resolution.isError && hasSearchFallback) {
|
|
1656
|
+
conversationHistory = [...conversationHistory, { role: 'user', content: trimmed }, { role: 'assistant', content: lastAnswer }];
|
|
1657
|
+
activeSession.messages = conversationHistory;
|
|
1658
|
+
activeSession.providerId = activeConnection?.id;
|
|
1659
|
+
activeSession.providerName = activeConnection?.displayName;
|
|
1660
|
+
activeSession.modelId = currentModel;
|
|
1661
|
+
activeSession.mode = activeMode;
|
|
1662
|
+
sessionStore.save(activeSession);
|
|
1663
|
+
streamedAnswer = lastAnswer;
|
|
1664
|
+
}
|
|
1665
|
+
else if (resolution.isError) {
|
|
1594
1666
|
streamedAnswer = lastAnswer;
|
|
1595
1667
|
}
|
|
1596
1668
|
else {
|
|
@@ -1628,7 +1700,7 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1628
1700
|
const cleanErr = rawErr
|
|
1629
1701
|
.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
1702
|
.trim();
|
|
1631
|
-
lastAnswer = `⚠️ **Error:** ${cleanErr || rawErr}\n\n👉 **Suggestion:** Switch models with \`/model\` or
|
|
1703
|
+
lastAnswer = `⚠️ **Error:** ${cleanErr || rawErr}\n\n👉 **Suggestion:** Switch models with \`/model\` or choose a provider with \`/connect\`${altSuggestion}.`;
|
|
1632
1704
|
lastThoughtTime = Math.max(0.001, (Date.now() - startedAt) / 1000);
|
|
1633
1705
|
lastOutputTokenRate = undefined;
|
|
1634
1706
|
process.stdout.write('\x1b[H\x1b[J');
|