moderado 0.3.4 → 0.3.7
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/dist/budget.d.ts +6 -0
- package/dist/budget.d.ts.map +1 -0
- package/dist/budget.js +25 -0
- package/dist/budget.js.map +1 -0
- package/dist/commands/chat.d.ts +5 -1
- package/dist/commands/chat.d.ts.map +1 -1
- package/dist/commands/chat.js +170 -41
- package/dist/commands/chat.js.map +1 -1
- package/dist/commands/models.d.ts.map +1 -1
- package/dist/commands/models.js +61 -14
- package/dist/commands/models.js.map +1 -1
- package/dist/config.d.ts +73 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +116 -0
- package/dist/config.js.map +1 -1
- package/dist/model_pricing.d.ts +14 -1
- package/dist/model_pricing.d.ts.map +1 -1
- package/dist/model_pricing.js +52 -0
- package/dist/model_pricing.js.map +1 -1
- package/dist/repo_map.d.ts +3 -0
- package/dist/repo_map.d.ts.map +1 -0
- package/dist/repo_map.js +29 -0
- package/dist/repo_map.js.map +1 -0
- package/dist/sessions.d.ts +22 -15
- package/dist/sessions.d.ts.map +1 -1
- package/dist/sessions.js +16 -1
- package/dist/sessions.js.map +1 -1
- package/dist/skills.d.ts +2 -2
- package/dist/ui/model_selector.d.ts +2 -2
- package/dist/ui/model_selector.d.ts.map +1 -1
- package/dist/ui/model_selector.js +25 -16
- package/dist/ui/model_selector.js.map +1 -1
- package/dist/ui/provider_connect.d.ts +6 -3
- package/dist/ui/provider_connect.d.ts.map +1 -1
- package/dist/ui/provider_connect.js +41 -14
- package/dist/ui/provider_connect.js.map +1 -1
- package/dist/ui/renderer.d.ts +4 -0
- package/dist/ui/renderer.d.ts.map +1 -1
- package/dist/ui/renderer.js +36 -0
- package/dist/ui/renderer.js.map +1 -1
- package/dist/ui/welcome.d.ts +11 -0
- package/dist/ui/welcome.d.ts.map +1 -1
- package/dist/ui/welcome.js +38 -20
- package/dist/ui/welcome.js.map +1 -1
- package/dist/vendor/contracts/events.d.ts +159 -0
- package/dist/vendor/contracts/events.d.ts.map +1 -1
- package/dist/vendor/contracts/events.js +12 -0
- package/dist/vendor/contracts/events.js.map +1 -1
- package/dist/vendor/contracts/provider.d.ts +1 -0
- package/dist/vendor/contracts/provider.d.ts.map +1 -1
- package/dist/vendor/contracts/provider.js +6 -0
- package/dist/vendor/contracts/provider.js.map +1 -1
- package/dist/vendor/core/agent.d.ts +2 -1
- package/dist/vendor/core/agent.d.ts.map +1 -1
- package/dist/vendor/core/agent.js +123 -9
- package/dist/vendor/core/agent.js.map +1 -1
- package/dist/vendor/providers/model_discovery.d.ts +2 -2
- package/dist/vendor/providers/model_discovery.d.ts.map +1 -1
- package/dist/vendor/providers/model_discovery.js +4 -2
- package/dist/vendor/providers/model_discovery.js.map +1 -1
- package/dist/vendor/providers/nvidia/nvidia_adapter.d.ts.map +1 -1
- package/dist/vendor/providers/nvidia/nvidia_adapter.js +1 -0
- package/dist/vendor/providers/nvidia/nvidia_adapter.js.map +1 -1
- package/dist/vendor/providers/nvidia/sse_parser.d.ts +2 -1
- package/dist/vendor/providers/nvidia/sse_parser.d.ts.map +1 -1
- package/dist/vendor/providers/nvidia/sse_parser.js +30 -16
- package/dist/vendor/providers/nvidia/sse_parser.js.map +1 -1
- package/package.json +1 -1
package/dist/budget.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function parseBudgetCommand(input: string, currentLimit?: number): {
|
|
2
|
+
limit?: number;
|
|
3
|
+
message: string;
|
|
4
|
+
};
|
|
5
|
+
export declare function evaluateTokenBudget(totalTokens: number, limit?: number): 'ok' | 'warning' | 'stop';
|
|
6
|
+
//# sourceMappingURL=budget.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"budget.d.ts","sourceRoot":"","sources":["../src/budget.ts"],"names":[],"mappings":"AAAA,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAY5G;AAED,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAKlG"}
|
package/dist/budget.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export function parseBudgetCommand(input, currentLimit) {
|
|
2
|
+
const value = input.slice('/budget'.length).trim();
|
|
3
|
+
if (!value)
|
|
4
|
+
return { limit: currentLimit, message: currentLimit
|
|
5
|
+
? `Token budget: ${currentLimit.toLocaleString()} per task (best-effort stop).`
|
|
6
|
+
: 'Token budget is off. Use /budget <tokens> to set a per-task limit.' };
|
|
7
|
+
if (value === 'off')
|
|
8
|
+
return { limit: undefined, message: 'Token budget disabled.' };
|
|
9
|
+
const amount = value.replace(/^tokens\s+/, '');
|
|
10
|
+
const limit = /^\d+$/.test(amount) ? Number(amount) : NaN;
|
|
11
|
+
if (!Number.isSafeInteger(limit) || limit < 1) {
|
|
12
|
+
return { limit: currentLimit, message: 'Usage: /budget <tokens> | /budget off. Cost limits require reliable provider pricing and are not available.' };
|
|
13
|
+
}
|
|
14
|
+
return { limit, message: `Token budget: ${limit.toLocaleString()} per task (best-effort stop).` };
|
|
15
|
+
}
|
|
16
|
+
export function evaluateTokenBudget(totalTokens, limit) {
|
|
17
|
+
if (!limit)
|
|
18
|
+
return 'ok';
|
|
19
|
+
if (totalTokens >= limit)
|
|
20
|
+
return 'stop';
|
|
21
|
+
if (totalTokens >= limit * 0.8)
|
|
22
|
+
return 'warning';
|
|
23
|
+
return 'ok';
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=budget.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"budget.js","sourceRoot":"","sources":["../src/budget.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,kBAAkB,CAAC,KAAa,EAAE,YAAqB;IACrE,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;IACnD,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,YAAY;gBAC7D,CAAC,CAAC,iBAAiB,YAAY,CAAC,cAAc,EAAE,+BAA+B;gBAC/E,CAAC,CAAC,oEAAoE,EAAE,CAAC;IAC3E,IAAI,KAAK,KAAK,KAAK;QAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,wBAAwB,EAAE,CAAC;IACpF,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;IAC/C,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;IAC1D,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QAC9C,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,6GAA6G,EAAE,CAAC;IACzJ,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,iBAAiB,KAAK,CAAC,cAAc,EAAE,+BAA+B,EAAE,CAAC;AACpG,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,WAAmB,EAAE,KAAc;IACrE,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,IAAI,WAAW,IAAI,KAAK;QAAE,OAAO,MAAM,CAAC;IACxC,IAAI,WAAW,IAAI,KAAK,GAAG,GAAG;QAAE,OAAO,SAAS,CAAC;IACjD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
package/dist/commands/chat.d.ts
CHANGED
|
@@ -4,9 +4,13 @@ import type { WebSearchProviderName, WebSearchToolOptions } from '@moderado/tool
|
|
|
4
4
|
import { ApprovalDecision, ApprovalRequest, ChatMessage, IToolRegistry, McpServerConfig, ToolResult } from '@moderado/contracts';
|
|
5
5
|
import { CliParsedArgs } from '../args.js';
|
|
6
6
|
import { ModeradoConfig } from '../config.js';
|
|
7
|
+
import { StoredSession } from '../sessions.js';
|
|
7
8
|
import { selectConfirmPopup, selectListPopup } from '../ui/popup.js';
|
|
9
|
+
export declare function formatSessionExitSummary(session: StoredSession, modelId?: string): string;
|
|
8
10
|
export declare function createAgentTask(request: string, mode: 'Plan' | 'Execute'): string;
|
|
9
|
-
export declare
|
|
11
|
+
export declare function createReviewTask(changedFiles: string[]): string;
|
|
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"];
|
|
10
14
|
export declare function levenshteinDistance(a: string, b: string): number;
|
|
11
15
|
export interface SlashCommandAdvice {
|
|
12
16
|
isSlashCommand: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../src/commands/chat.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,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
|
@@ -2,7 +2,7 @@ import path from 'node:path';
|
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import { AgentLoop, PolicyManager, Router, cleanConversationalFiller, DEFAULT_MAX_OUTPUT_TOKENS } from '../vendor/core/index.js';
|
|
4
4
|
import { NvidiaAdapter } from '../vendor/providers/index.js';
|
|
5
|
-
import { createDefaultToolRegistry, canonicalizeRoot, createMcpTools, createWebSearchTool, discoverMcpServers, resolveInJail, WorkspaceCheckpointStore, WriteFileTool } from '../vendor/tools/index.js';
|
|
5
|
+
import { createDefaultToolRegistry, ToolRegistry, canonicalizeRoot, createMcpTools, createWebSearchTool, discoverMcpServers, resolveInJail, WorkspaceCheckpointStore, WriteFileTool } from '../vendor/tools/index.js';
|
|
6
6
|
import { McpServerConfigSchema } from '../vendor/contracts/index.js';
|
|
7
7
|
import { getActiveConnection, loadConfig, removeMcpServer, resolveApiKey, resolveConnectionCredential, saveConnection, saveMcpServer, setMcpServerEnabled, storeConnectionCredential } from '../config.js';
|
|
8
8
|
import { MemoryCredentialStore } from '../credentials.js';
|
|
@@ -13,13 +13,29 @@ import { connectProviderInteractive, isAuthenticationFailure, replaceProviderKey
|
|
|
13
13
|
import { initWorkspace } from './init.js';
|
|
14
14
|
import { expandMentions, createWorkspaceFileSource } from '../ui/file_mentions.js';
|
|
15
15
|
import { listFiles } from '../vendor/tools/index.js';
|
|
16
|
-
import { exitCleanly, promptInteractiveTurn, renderChatAnswerDelta, renderChatComposerCursor, renderChatThoughtTimeUpdate, renderFullWelcomeScreen, renderWelcomePopupLayer, terminalCleanExitDone, wrapText } from '../ui/welcome.js';
|
|
17
|
-
import {
|
|
16
|
+
import { exitCleanly, promptInteractiveTurn, renderChatAnswerDelta, renderChatComposerCursor, renderChatThoughtTimeUpdate, renderChatUsageUpdate, renderFullWelcomeScreen, renderWelcomePopupLayer, terminalCleanExitDone, wrapText } from '../ui/welcome.js';
|
|
17
|
+
import { accumulateSessionUsage, compactSessionMessages, createSession, exportSessionMarkdown, formatSessionCost, SessionStore } from '../sessions.js';
|
|
18
18
|
import { layerPromptBox, renderBoxLines, selectConfirmPopup, selectListPopup } from '../ui/popup.js';
|
|
19
19
|
import { askModalChoice } from '../ui/prompt.js';
|
|
20
20
|
import { findModelPricing } from '../model_pricing.js';
|
|
21
21
|
import { inspectGitWorkspace, readGitDiff } from '../vendor/tools/index.js';
|
|
22
|
+
import { buildWorkspaceMap } from '../repo_map.js';
|
|
23
|
+
import { evaluateTokenBudget, parseBudgetCommand } from '../budget.js';
|
|
22
24
|
import { discoverSkills, formatSkillContext } from '../skills.js';
|
|
25
|
+
export function formatSessionExitSummary(session, modelId) {
|
|
26
|
+
const usage = session.usage;
|
|
27
|
+
const tokens = usage.available
|
|
28
|
+
? `Input ${usage.promptTokens.toLocaleString('en-US')} | Output ${usage.completionTokens.toLocaleString('en-US')} | Total ${usage.totalTokens.toLocaleString('en-US')}${usage.estimated ? ' (estimated)' : ''}`
|
|
29
|
+
: 'Usage unavailable';
|
|
30
|
+
return [
|
|
31
|
+
'\x1b[1;38;5;25mSession Summary\x1b[0m',
|
|
32
|
+
`ID: ${session.id}`,
|
|
33
|
+
`Model: ${modelId ?? session.modelId ?? 'No model selected'}`,
|
|
34
|
+
`Tokens: ${tokens}`,
|
|
35
|
+
`Cost: ${formatSessionCost(usage)}`,
|
|
36
|
+
'Resume: /session resume',
|
|
37
|
+
].join('\n');
|
|
38
|
+
}
|
|
23
39
|
function mutationPaths(toolName, parameters) {
|
|
24
40
|
const value = parameters;
|
|
25
41
|
if (toolName === 'apply_patch' && Array.isArray(value.edits)) {
|
|
@@ -32,6 +48,19 @@ export function createAgentTask(request, mode) {
|
|
|
32
48
|
return request;
|
|
33
49
|
return `Create a concise implementation checklist for this request. Inspect files as needed, but do not modify files or run commands.\n\nUser request:\n${request}`;
|
|
34
50
|
}
|
|
51
|
+
export function createReviewTask(changedFiles) {
|
|
52
|
+
return `Review the current workspace changes. Inspect staged and unstaged Git diffs with git_diff, then inspect relevant surrounding code. Also review untracked files listed below with read_file. Do not modify files or run commands. Report only actionable bugs or security issues, ordered by severity, with file paths and line numbers. If none are found, say "No findings". Keep the report concise.\n\nChanged files:\n${changedFiles.join('\n') || '(none)'}`;
|
|
53
|
+
}
|
|
54
|
+
export function createReviewToolRegistry() {
|
|
55
|
+
const source = createDefaultToolRegistry();
|
|
56
|
+
const registry = new ToolRegistry();
|
|
57
|
+
for (const name of ['git_diff', 'read_file', 'list_files', 'search_files', 'get_definition', 'find_references']) {
|
|
58
|
+
const tool = source.get(name);
|
|
59
|
+
if (tool)
|
|
60
|
+
registry.register(tool);
|
|
61
|
+
}
|
|
62
|
+
return registry;
|
|
63
|
+
}
|
|
35
64
|
export const STANDARD_SLASH_COMMANDS = [
|
|
36
65
|
'/connect',
|
|
37
66
|
'/model',
|
|
@@ -41,6 +70,9 @@ export const STANDARD_SLASH_COMMANDS = [
|
|
|
41
70
|
'/session',
|
|
42
71
|
'/queue',
|
|
43
72
|
'/workflow',
|
|
73
|
+
'/review',
|
|
74
|
+
'/map',
|
|
75
|
+
'/budget',
|
|
44
76
|
'/skills',
|
|
45
77
|
'/clear',
|
|
46
78
|
'/help',
|
|
@@ -139,6 +171,10 @@ export function formatTurnFailureAnswer(status, errorEvent, modelId, otherConnec
|
|
|
139
171
|
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 switch providers with \`/connect\`${altSuggestion}.`;
|
|
140
172
|
}
|
|
141
173
|
export function resolveTurnAssistantAnswer(result, previousHistoryLength, lastErrorEvent, streamedAnswer = '', currentModel = 'unknown', otherConnections = []) {
|
|
174
|
+
if (result.status === 'cancelled') {
|
|
175
|
+
const partial = cleanConversationalFiller(streamedAnswer);
|
|
176
|
+
return { answer: [partial, 'Task stopped.'].filter(Boolean).join('\n\n'), isError: true };
|
|
177
|
+
}
|
|
142
178
|
if (result.status === 'failed' || lastErrorEvent) {
|
|
143
179
|
return {
|
|
144
180
|
answer: formatTurnFailureAnswer(result.status, lastErrorEvent, result.selectedModel?.id || currentModel, otherConnections),
|
|
@@ -216,15 +252,9 @@ export function handleGenerationKeypress(str, key, queue, actions) {
|
|
|
216
252
|
actions.abort();
|
|
217
253
|
return;
|
|
218
254
|
}
|
|
219
|
-
// 2. Escape
|
|
255
|
+
// 2. Escape always stops the active task; the caller clears queued input.
|
|
220
256
|
if (isGenerationCancelKey(key, str)) {
|
|
221
|
-
|
|
222
|
-
queue.setDraft('');
|
|
223
|
-
actions.onDraftChange();
|
|
224
|
-
}
|
|
225
|
-
else {
|
|
226
|
-
actions.abort();
|
|
227
|
-
}
|
|
257
|
+
actions.abort();
|
|
228
258
|
return;
|
|
229
259
|
}
|
|
230
260
|
// 3. Return / Enter: commit draft or execute /btw immediately
|
|
@@ -422,9 +452,6 @@ export async function decideApproval(request, options, signal) {
|
|
|
422
452
|
if (options.autoApprove && !request.toolName.startsWith('mcp.')) {
|
|
423
453
|
return { requestId: request.requestId, status: 'approved' };
|
|
424
454
|
}
|
|
425
|
-
if (['write_file', 'edit_file', 'apply_patch', 'run_command', 'run_diagnostics'].includes(request.toolName)) {
|
|
426
|
-
return { requestId: request.requestId, status: 'approved' };
|
|
427
|
-
}
|
|
428
455
|
return options.requestInteractiveApproval(request, signal);
|
|
429
456
|
}
|
|
430
457
|
export async function createMcpToolRegistry(servers, webSearch = {}) {
|
|
@@ -697,7 +724,8 @@ export async function handleChatSession(args, version, signal) {
|
|
|
697
724
|
if (activeConnection)
|
|
698
725
|
activateConnection(activeConnection, args.model);
|
|
699
726
|
let activeMode = args.readOnly ? 'Plan' : 'Execute';
|
|
700
|
-
let activeAutoApprove =
|
|
727
|
+
let activeAutoApprove = !args.nonInteractive;
|
|
728
|
+
let tokenBudget;
|
|
701
729
|
const sessionStore = new SessionStore();
|
|
702
730
|
let activeSession = sessionStore.loadLatestSession(canonicalWorkspace) ??
|
|
703
731
|
createSession(canonicalWorkspace, {
|
|
@@ -742,8 +770,10 @@ export async function handleChatSession(args, version, signal) {
|
|
|
742
770
|
let lastAnswer = '';
|
|
743
771
|
let lastThoughtTime = 0;
|
|
744
772
|
let lastOutputTokenRate;
|
|
773
|
+
let lastTokenUsage;
|
|
745
774
|
let activePlan;
|
|
746
775
|
const costLabel = () => formatSessionCost(activeSession.usage);
|
|
776
|
+
const exitMessage = () => `\x1b[32mGoodbye! Stay Tuned with Moderado!\x1b[0m\n\n${formatSessionExitSummary(activeSession, currentModel)}`;
|
|
747
777
|
const commandQueue = new TurnCommandQueue();
|
|
748
778
|
const btwHistory = [];
|
|
749
779
|
while (!signal?.aborted) {
|
|
@@ -755,8 +785,9 @@ export async function handleChatSession(args, version, signal) {
|
|
|
755
785
|
else {
|
|
756
786
|
const turn = await promptInteractiveTurn({
|
|
757
787
|
model: currentModel ?? 'No model connected — use /connect', tokens: Math.round(sessionTokens), cost: costLabel(), workspace: canonicalWorkspace, version,
|
|
758
|
-
usageAvailable: activeSession.usage.available, initialMode: activeMode, initialAutoApprove: activeAutoApprove, isFirstTurn: isFirst, signal, chatQuestion: lastQuestion || undefined, chatAnswer: lastAnswer || undefined, chatThoughtTime: lastThoughtTime, outputTokenRate: lastOutputTokenRate,
|
|
788
|
+
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,
|
|
759
789
|
queuedCommands: commandQueue.items,
|
|
790
|
+
onExit: exitMessage,
|
|
760
791
|
questionHistory: conversationHistory.flatMap((message) => message.role === 'user' && message.content?.trim() ? [message.content] : []),
|
|
761
792
|
onModelSelect: async (drawFrame) => {
|
|
762
793
|
if (!activeConnection) {
|
|
@@ -770,7 +801,6 @@ export async function handleChatSession(args, version, signal) {
|
|
|
770
801
|
providerId: activeConnection.id,
|
|
771
802
|
providerName: activeConnection.displayName,
|
|
772
803
|
currentModel,
|
|
773
|
-
allModelsFree: activeConnection.id === 'agnes-ai',
|
|
774
804
|
signal,
|
|
775
805
|
drawFrame,
|
|
776
806
|
});
|
|
@@ -792,7 +822,7 @@ export async function handleChatSession(args, version, signal) {
|
|
|
792
822
|
return selection.modelId;
|
|
793
823
|
},
|
|
794
824
|
onConnect: async (drawFrame) => {
|
|
795
|
-
const connection = await connectProviderInteractive({ signal, drawFrame, savedConnections: config.connections, resolveSavedConnection: (saved) => resolveConnectionCredential(saved, credentialStore) });
|
|
825
|
+
const connection = await connectProviderInteractive({ signal, drawFrame, savedConnections: config.connections, connectProviders: config.connectProviders, resolveSavedConnection: (saved) => resolveConnectionCredential(saved, credentialStore) });
|
|
796
826
|
if (!connection)
|
|
797
827
|
return currentModel;
|
|
798
828
|
const runtimeConnection = process.platform === 'win32' ? await storeConnectionCredential(connection, credentialStore) : connection;
|
|
@@ -808,6 +838,7 @@ export async function handleChatSession(args, version, signal) {
|
|
|
808
838
|
lastAnswer = '';
|
|
809
839
|
lastThoughtTime = 0;
|
|
810
840
|
lastOutputTokenRate = undefined;
|
|
841
|
+
lastTokenUsage = undefined;
|
|
811
842
|
activeSession.messages = [];
|
|
812
843
|
sessionStore.save(activeSession);
|
|
813
844
|
},
|
|
@@ -926,6 +957,7 @@ export async function handleChatSession(args, version, signal) {
|
|
|
926
957
|
lastAnswer = '';
|
|
927
958
|
lastThoughtTime = 0;
|
|
928
959
|
lastOutputTokenRate = undefined;
|
|
960
|
+
lastTokenUsage = undefined;
|
|
929
961
|
sessionStore.save(activeSession);
|
|
930
962
|
return;
|
|
931
963
|
}
|
|
@@ -1026,11 +1058,46 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1026
1058
|
continue;
|
|
1027
1059
|
}
|
|
1028
1060
|
}
|
|
1061
|
+
if (trimmed === '/budget' || trimmed.startsWith('/budget ')) {
|
|
1062
|
+
const budget = parseBudgetCommand(trimmed, tokenBudget);
|
|
1063
|
+
tokenBudget = budget.limit;
|
|
1064
|
+
lastQuestion = trimmed;
|
|
1065
|
+
lastAnswer = budget.message;
|
|
1066
|
+
lastThoughtTime = 0.001;
|
|
1067
|
+
lastOutputTokenRate = undefined;
|
|
1068
|
+
lastTokenUsage = undefined;
|
|
1069
|
+
process.stdout.write('\x1b[H\x1b[J');
|
|
1070
|
+
process.stdout.write(renderFullWelcomeScreen({
|
|
1071
|
+
model: currentModel ?? 'No model connected — use /connect', tokens: Math.round(sessionTokens), cost: costLabel(),
|
|
1072
|
+
usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated, workspace: canonicalWorkspace, mode: activeMode,
|
|
1073
|
+
autoApprove: activeAutoApprove, chatQuestion: lastQuestion, chatAnswer: lastAnswer,
|
|
1074
|
+
chatThoughtTime: lastThoughtTime, queuedCommands: commandQueue.items,
|
|
1075
|
+
}, process.stdout.rows));
|
|
1076
|
+
process.stdout.write(renderChatComposerCursor({ width: process.stdout.columns }, 0));
|
|
1077
|
+
continue;
|
|
1078
|
+
}
|
|
1079
|
+
if (trimmed === '/map') {
|
|
1080
|
+
lastQuestion = trimmed;
|
|
1081
|
+
lastAnswer = await buildWorkspaceMap(canonicalWorkspace);
|
|
1082
|
+
lastThoughtTime = 0.001;
|
|
1083
|
+
lastOutputTokenRate = undefined;
|
|
1084
|
+
lastTokenUsage = undefined;
|
|
1085
|
+
process.stdout.write('\x1b[H\x1b[J');
|
|
1086
|
+
process.stdout.write(renderFullWelcomeScreen({
|
|
1087
|
+
model: currentModel ?? 'No model connected — use /connect', tokens: Math.round(sessionTokens), cost: costLabel(),
|
|
1088
|
+
usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated, workspace: canonicalWorkspace, mode: activeMode,
|
|
1089
|
+
autoApprove: activeAutoApprove, chatQuestion: lastQuestion, chatAnswer: lastAnswer,
|
|
1090
|
+
chatThoughtTime: lastThoughtTime, queuedCommands: commandQueue.items,
|
|
1091
|
+
}, process.stdout.rows));
|
|
1092
|
+
process.stdout.write(renderChatComposerCursor({ width: process.stdout.columns }, 0));
|
|
1093
|
+
continue;
|
|
1094
|
+
}
|
|
1029
1095
|
if (trimmed.startsWith('/queue')) {
|
|
1030
1096
|
const args = trimmed.slice('/queue'.length).trim();
|
|
1031
1097
|
lastQuestion = trimmed;
|
|
1032
1098
|
lastThoughtTime = 0.001;
|
|
1033
1099
|
lastOutputTokenRate = undefined;
|
|
1100
|
+
lastTokenUsage = undefined;
|
|
1034
1101
|
if (!args || args === 'list') {
|
|
1035
1102
|
lastAnswer = commandQueue.length === 0
|
|
1036
1103
|
? 'Command queue is empty. Use `/queue <command>` to add commands.'
|
|
@@ -1053,7 +1120,7 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1053
1120
|
process.stdout.write('\x1b[H\x1b[J');
|
|
1054
1121
|
process.stdout.write(renderFullWelcomeScreen({
|
|
1055
1122
|
model: currentModel ?? 'No model connected — use /connect', tokens: Math.round(sessionTokens), cost: costLabel(),
|
|
1056
|
-
usageAvailable: activeSession.usage.available, workspace: canonicalWorkspace, mode: activeMode,
|
|
1123
|
+
usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated, workspace: canonicalWorkspace, mode: activeMode,
|
|
1057
1124
|
autoApprove: activeAutoApprove, chatQuestion: lastQuestion, chatAnswer: lastAnswer,
|
|
1058
1125
|
chatThoughtTime: lastThoughtTime,
|
|
1059
1126
|
queuedCommands: commandQueue.items,
|
|
@@ -1068,6 +1135,7 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1068
1135
|
lastAnswer = '';
|
|
1069
1136
|
lastThoughtTime = 0;
|
|
1070
1137
|
lastOutputTokenRate = undefined;
|
|
1138
|
+
lastTokenUsage = undefined;
|
|
1071
1139
|
activeSession.messages = [];
|
|
1072
1140
|
sessionStore.save(activeSession);
|
|
1073
1141
|
continue;
|
|
@@ -1077,6 +1145,7 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1077
1145
|
lastAnswer = 'To leave Moderado, type /exit.';
|
|
1078
1146
|
lastThoughtTime = 0;
|
|
1079
1147
|
lastOutputTokenRate = undefined;
|
|
1148
|
+
lastTokenUsage = undefined;
|
|
1080
1149
|
continue;
|
|
1081
1150
|
}
|
|
1082
1151
|
// Non-standard or misspelled slash command rejection with advice
|
|
@@ -1086,10 +1155,11 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1086
1155
|
lastAnswer = slashAdvice.advice;
|
|
1087
1156
|
lastThoughtTime = 0.001;
|
|
1088
1157
|
lastOutputTokenRate = undefined;
|
|
1158
|
+
lastTokenUsage = undefined;
|
|
1089
1159
|
process.stdout.write('\x1b[H\x1b[J');
|
|
1090
1160
|
process.stdout.write(renderFullWelcomeScreen({
|
|
1091
1161
|
model: currentModel ?? 'No model connected — use /connect', tokens: Math.round(sessionTokens), cost: costLabel(),
|
|
1092
|
-
usageAvailable: activeSession.usage.available, workspace: canonicalWorkspace, mode: activeMode,
|
|
1162
|
+
usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated, workspace: canonicalWorkspace, mode: activeMode,
|
|
1093
1163
|
autoApprove: activeAutoApprove, chatQuestion: lastQuestion, chatAnswer: lastAnswer,
|
|
1094
1164
|
chatThoughtTime: lastThoughtTime,
|
|
1095
1165
|
queuedCommands: commandQueue.items,
|
|
@@ -1105,13 +1175,14 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1105
1175
|
: 'No valid user skills found in ~/.moderado/skills.';
|
|
1106
1176
|
lastThoughtTime = 0.001;
|
|
1107
1177
|
lastOutputTokenRate = undefined;
|
|
1178
|
+
lastTokenUsage = undefined;
|
|
1108
1179
|
process.stdout.write('\x1b[H\x1b[J');
|
|
1109
|
-
process.stdout.write(renderFullWelcomeScreen({ model: currentModel ?? 'No model connected — use /connect', tokens: Math.round(sessionTokens), cost: costLabel(), usageAvailable: activeSession.usage.available, workspace: canonicalWorkspace, mode: activeMode, autoApprove: activeAutoApprove, chatQuestion: lastQuestion, chatAnswer: lastAnswer, chatThoughtTime: lastThoughtTime, queuedCommands: commandQueue.items }, process.stdout.rows));
|
|
1180
|
+
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));
|
|
1110
1181
|
process.stdout.write(renderChatComposerCursor({ width: process.stdout.columns }, 0));
|
|
1111
1182
|
continue;
|
|
1112
1183
|
}
|
|
1113
1184
|
if (trimmed === '/exit' || trimmed === '/quit') {
|
|
1114
|
-
exitCleanly(
|
|
1185
|
+
exitCleanly(exitMessage());
|
|
1115
1186
|
}
|
|
1116
1187
|
if (trimmed === '/help') {
|
|
1117
1188
|
lastQuestion = trimmed;
|
|
@@ -1123,16 +1194,20 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1123
1194
|
' /session - Create, resume, undo, redo, share, export, or compact sessions\n' +
|
|
1124
1195
|
' /queue - Add, inspect, or clear queued follow-up commands\n' +
|
|
1125
1196
|
' /workflow - Inspect Git, build plans, or undo agent changes\n' +
|
|
1197
|
+
' /review - Review current workspace changes for bugs\n' +
|
|
1198
|
+
' /map - Show a compact workspace file map\n' +
|
|
1199
|
+
' /budget - Set a per-task token limit (/budget 5000, /budget off)\n' +
|
|
1126
1200
|
' /skills - List installed user skills and reload them\n' +
|
|
1127
1201
|
' /clear - Reset conversation memory\n' +
|
|
1128
1202
|
' /help - Display commands, shortcuts & version\n' +
|
|
1129
1203
|
' /exit - Exit Moderado';
|
|
1130
1204
|
lastThoughtTime = 0.001;
|
|
1131
1205
|
lastOutputTokenRate = undefined;
|
|
1206
|
+
lastTokenUsage = undefined;
|
|
1132
1207
|
process.stdout.write('\x1b[H\x1b[J');
|
|
1133
1208
|
process.stdout.write(renderFullWelcomeScreen({
|
|
1134
1209
|
model: currentModel ?? 'No model connected — use /connect', tokens: Math.round(sessionTokens), cost: costLabel(),
|
|
1135
|
-
usageAvailable: activeSession.usage.available, workspace: canonicalWorkspace, mode: activeMode,
|
|
1210
|
+
usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated, workspace: canonicalWorkspace, mode: activeMode,
|
|
1136
1211
|
autoApprove: activeAutoApprove, chatQuestion: lastQuestion, chatAnswer: lastAnswer,
|
|
1137
1212
|
chatThoughtTime: lastThoughtTime,
|
|
1138
1213
|
queuedCommands: commandQueue.items,
|
|
@@ -1158,13 +1233,14 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1158
1233
|
lastAnswer = localMetaAnswer;
|
|
1159
1234
|
lastThoughtTime = 0.001;
|
|
1160
1235
|
lastOutputTokenRate = undefined;
|
|
1236
|
+
lastTokenUsage = undefined;
|
|
1161
1237
|
conversationHistory = [...conversationHistory, { role: 'user', content: trimmed }, { role: 'assistant', content: localMetaAnswer }];
|
|
1162
1238
|
activeSession.messages = conversationHistory;
|
|
1163
1239
|
sessionStore.save(activeSession);
|
|
1164
1240
|
process.stdout.write('\x1b[H\x1b[J');
|
|
1165
1241
|
process.stdout.write(renderFullWelcomeScreen({
|
|
1166
1242
|
model: currentModel ?? 'No model connected — use /connect', tokens: Math.round(sessionTokens), cost: costLabel(),
|
|
1167
|
-
usageAvailable: activeSession.usage.available, workspace: canonicalWorkspace, mode: activeMode,
|
|
1243
|
+
usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated, workspace: canonicalWorkspace, mode: activeMode,
|
|
1168
1244
|
autoApprove: activeAutoApprove, chatQuestion: lastQuestion, chatAnswer: lastAnswer,
|
|
1169
1245
|
chatThoughtTime: lastThoughtTime,
|
|
1170
1246
|
queuedCommands: commandQueue.items,
|
|
@@ -1177,13 +1253,14 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1177
1253
|
lastQuestion = trimmed;
|
|
1178
1254
|
lastThoughtTime = 0.001;
|
|
1179
1255
|
lastOutputTokenRate = undefined;
|
|
1256
|
+
lastTokenUsage = undefined;
|
|
1180
1257
|
conversationHistory = [...conversationHistory, { role: 'user', content: trimmed }, { role: 'assistant', content: lastAnswer }];
|
|
1181
1258
|
activeSession.messages = conversationHistory;
|
|
1182
1259
|
sessionStore.save(activeSession);
|
|
1183
1260
|
process.stdout.write('\x1b[H\x1b[J');
|
|
1184
1261
|
process.stdout.write(renderFullWelcomeScreen({
|
|
1185
1262
|
model: currentModel ?? 'No model connected — use /connect', tokens: Math.round(sessionTokens), cost: costLabel(),
|
|
1186
|
-
usageAvailable: activeSession.usage.available, workspace: canonicalWorkspace, mode: activeMode,
|
|
1263
|
+
usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated, workspace: canonicalWorkspace, mode: activeMode,
|
|
1187
1264
|
autoApprove: activeAutoApprove, chatQuestion: lastQuestion, chatAnswer: lastAnswer,
|
|
1188
1265
|
chatThoughtTime: lastThoughtTime,
|
|
1189
1266
|
queuedCommands: commandQueue.items,
|
|
@@ -1200,10 +1277,11 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1200
1277
|
lastAnswer = 'Searching the web...';
|
|
1201
1278
|
lastThoughtTime = 0;
|
|
1202
1279
|
lastOutputTokenRate = undefined;
|
|
1280
|
+
lastTokenUsage = undefined;
|
|
1203
1281
|
process.stdout.write('\x1b[H\x1b[J');
|
|
1204
1282
|
process.stdout.write(renderFullWelcomeScreen({
|
|
1205
1283
|
model: currentModel ?? 'Web search', tokens: Math.round(sessionTokens), cost: costLabel(),
|
|
1206
|
-
usageAvailable: activeSession.usage.available, workspace: canonicalWorkspace, mode: activeMode,
|
|
1284
|
+
usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated, workspace: canonicalWorkspace, mode: activeMode,
|
|
1207
1285
|
autoApprove: activeAutoApprove, chatQuestion: lastQuestion, chatAnswer: lastAnswer,
|
|
1208
1286
|
}, process.stdout.rows));
|
|
1209
1287
|
process.stdout.write(renderChatComposerCursor({ width: process.stdout.columns }, 0));
|
|
@@ -1224,7 +1302,7 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1224
1302
|
process.stdout.write('\x1b[H\x1b[J');
|
|
1225
1303
|
process.stdout.write(renderFullWelcomeScreen({
|
|
1226
1304
|
model: currentModel ?? 'Web search', tokens: Math.round(sessionTokens), cost: costLabel(),
|
|
1227
|
-
usageAvailable: activeSession.usage.available, workspace: canonicalWorkspace, mode: activeMode,
|
|
1305
|
+
usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated, workspace: canonicalWorkspace, mode: activeMode,
|
|
1228
1306
|
autoApprove: activeAutoApprove, chatQuestion: lastQuestion, chatAnswer: lastAnswer,
|
|
1229
1307
|
chatThoughtTime: lastThoughtTime,
|
|
1230
1308
|
}, process.stdout.rows));
|
|
@@ -1234,7 +1312,7 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1234
1312
|
}
|
|
1235
1313
|
if (!provider) {
|
|
1236
1314
|
process.stdout.write('\nNo provider is connected. Let\'s connect one before sending this task.\n');
|
|
1237
|
-
const connection = await connectProviderInteractive({ signal, savedConnections: config.connections, resolveSavedConnection: (saved) => resolveConnectionCredential(saved, credentialStore) });
|
|
1315
|
+
const connection = await connectProviderInteractive({ signal, savedConnections: config.connections, connectProviders: config.connectProviders, resolveSavedConnection: (saved) => resolveConnectionCredential(saved, credentialStore) });
|
|
1238
1316
|
if (!connection) {
|
|
1239
1317
|
process.stdout.write('No provider connected. Use /connect whenever you are ready.\n\n');
|
|
1240
1318
|
continue;
|
|
@@ -1244,10 +1322,22 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1244
1322
|
config = loadConfig();
|
|
1245
1323
|
activateConnection(runtimeConnection);
|
|
1246
1324
|
}
|
|
1247
|
-
const
|
|
1325
|
+
const isReview = trimmed === '/review';
|
|
1326
|
+
const reviewFiles = isReview ? (await inspectGitWorkspace(canonicalWorkspace)).files.map(file => `${file.status} ${file.path}`) : [];
|
|
1327
|
+
const policy = new PolicyManager({ maxSteps: args.maxSteps, readOnly: isReview || activeMode === 'Plan' || args.readOnly, nonInteractive: args.nonInteractive, timeoutSeconds: args.timeout });
|
|
1248
1328
|
const startedAt = Date.now();
|
|
1249
1329
|
let thinkingTimer;
|
|
1250
1330
|
let removeGenerationListener;
|
|
1331
|
+
let turnUsage;
|
|
1332
|
+
let usageRecorded = false;
|
|
1333
|
+
lastTokenUsage = undefined;
|
|
1334
|
+
const recordTurnUsage = (pricing) => {
|
|
1335
|
+
if (!turnUsage || usageRecorded)
|
|
1336
|
+
return;
|
|
1337
|
+
activeSession.usage = accumulateSessionUsage(activeSession.usage, turnUsage.usage, turnUsage.estimated, pricing);
|
|
1338
|
+
sessionTokens = activeSession.usage.totalTokens;
|
|
1339
|
+
usageRecorded = true;
|
|
1340
|
+
};
|
|
1251
1341
|
try {
|
|
1252
1342
|
const requestAbort = new AbortController();
|
|
1253
1343
|
const requestSignal = signal ? AbortSignal.any([signal, requestAbort.signal]) : requestAbort.signal;
|
|
@@ -1255,6 +1345,10 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1255
1345
|
lastAnswer = '';
|
|
1256
1346
|
let firstAssistantDeltaAt;
|
|
1257
1347
|
let outputTokenRate;
|
|
1348
|
+
let budgetWarningIssued = false;
|
|
1349
|
+
let budgetStopped = false;
|
|
1350
|
+
let lastUsagePaint = 0;
|
|
1351
|
+
const usedModels = new Set();
|
|
1258
1352
|
let answerPosition = { row: 15, column: 1 };
|
|
1259
1353
|
let lastErrorEvent;
|
|
1260
1354
|
const redrawChatFrame = () => {
|
|
@@ -1265,7 +1359,7 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1265
1359
|
model: currentModel ?? 'No model connected — use /connect',
|
|
1266
1360
|
tokens: Math.round(sessionTokens),
|
|
1267
1361
|
cost: costLabel(),
|
|
1268
|
-
usageAvailable: activeSession.usage.available,
|
|
1362
|
+
usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated,
|
|
1269
1363
|
workspace: canonicalWorkspace,
|
|
1270
1364
|
mode: activeMode,
|
|
1271
1365
|
autoApprove: activeAutoApprove,
|
|
@@ -1273,6 +1367,8 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1273
1367
|
chatAnswer: displayAnswer,
|
|
1274
1368
|
chatThoughtTime: thoughtTime,
|
|
1275
1369
|
outputTokenRate,
|
|
1370
|
+
tokenUsage: turnUsage,
|
|
1371
|
+
budgetStatus: budgetStopped ? 'Budget reached' : budgetWarningIssued ? 'Budget ≥80%' : undefined,
|
|
1276
1372
|
input: commandQueue.currentDraft,
|
|
1277
1373
|
queuedCommands: commandQueue.items,
|
|
1278
1374
|
}, process.stdout.rows));
|
|
@@ -1311,7 +1407,7 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1311
1407
|
model: currentModel ?? 'No model connected',
|
|
1312
1408
|
tokens: Math.round(sessionTokens),
|
|
1313
1409
|
cost: costLabel(),
|
|
1314
|
-
usageAvailable: activeSession.usage.available,
|
|
1410
|
+
usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated,
|
|
1315
1411
|
workspace: canonicalWorkspace,
|
|
1316
1412
|
mode: activeMode,
|
|
1317
1413
|
autoApprove: activeAutoApprove,
|
|
@@ -1370,17 +1466,36 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1370
1466
|
// Move to the chat layout before the provider can emit its first event.
|
|
1371
1467
|
redrawChatFrame();
|
|
1372
1468
|
thinkingTimer = setInterval(() => {
|
|
1373
|
-
if (!firstAssistantDeltaAt) {
|
|
1469
|
+
if (!firstAssistantDeltaAt && !turnUsage) {
|
|
1374
1470
|
process.stdout.write(renderChatThoughtTimeUpdate((Date.now() - startedAt) / 1000));
|
|
1375
1471
|
}
|
|
1376
1472
|
}, 400);
|
|
1377
1473
|
const evidenceTask = liveSearch ? buildSearchAnswerTask(effectivePrompt, liveSearch) : undefined;
|
|
1378
|
-
const runAgent = () => loop.run(evidenceTask ?? createAgentTask(effectivePrompt, activeMode), {
|
|
1379
|
-
workspaceRoot: canonicalWorkspace, provider: provider, tools, approvalHandler, router, policy,
|
|
1474
|
+
const runAgent = () => loop.run(isReview ? createReviewTask(reviewFiles) : evidenceTask ?? createAgentTask(effectivePrompt, activeMode), {
|
|
1475
|
+
workspaceRoot: canonicalWorkspace, provider: provider, tools: isReview ? createReviewToolRegistry() : tools, approvalHandler, router, policy,
|
|
1380
1476
|
maxOutputTokens: args.maxTokens ?? config.maxOutputTokens ?? DEFAULT_MAX_OUTPUT_TOKENS,
|
|
1381
1477
|
skillContext: skillContext(),
|
|
1382
1478
|
routeOptions: { pinnedModelId: currentModel === 'auto' ? undefined : currentModel, allowPaid: config.allowPaid ?? args.allowPaid, allowUnknown: config.allowUnknown ?? args.allowUnknown, isLocalProfile: args.profile.includes('local') },
|
|
1383
1479
|
eventListener: (event) => {
|
|
1480
|
+
if (event.type === 'model_change')
|
|
1481
|
+
usedModels.add(event.newModelId);
|
|
1482
|
+
if (event.type === 'usage') {
|
|
1483
|
+
turnUsage = event;
|
|
1484
|
+
lastTokenUsage = event;
|
|
1485
|
+
outputTokenRate = event.generationMs > 0 ? event.outputTokensPerSecond : undefined;
|
|
1486
|
+
const budgetState = evaluateTokenBudget(event.usage.totalTokens, tokenBudget);
|
|
1487
|
+
const firstBudgetWarning = budgetState === 'warning' && !budgetWarningIssued;
|
|
1488
|
+
if (budgetState === 'warning')
|
|
1489
|
+
budgetWarningIssued = true;
|
|
1490
|
+
if (budgetState === 'stop' && !budgetStopped) {
|
|
1491
|
+
budgetStopped = true;
|
|
1492
|
+
requestAbort.abort();
|
|
1493
|
+
}
|
|
1494
|
+
if (budgetStopped || firstBudgetWarning || event.final || event.timestamp - lastUsagePaint >= 400) {
|
|
1495
|
+
process.stdout.write(renderChatUsageUpdate(event, process.stdout.columns || 80, budgetStopped ? 'Budget reached' : budgetWarningIssued ? 'Budget ≥80%' : undefined));
|
|
1496
|
+
lastUsagePaint = event.timestamp;
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1384
1499
|
if (event.type === 'assistant_delta') {
|
|
1385
1500
|
if (!firstAssistantDeltaAt && event.delta.trim().length > 0) {
|
|
1386
1501
|
firstAssistantDeltaAt = Date.now();
|
|
@@ -1421,28 +1536,38 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1421
1536
|
const replacement = await replaceProviderKeyInteractive(activeConnection, { signal });
|
|
1422
1537
|
if (!replacement)
|
|
1423
1538
|
throw error;
|
|
1539
|
+
recordTurnUsage();
|
|
1540
|
+
turnUsage = undefined;
|
|
1541
|
+
usageRecorded = false;
|
|
1424
1542
|
const secured = process.platform === 'win32' ? await storeConnectionCredential(replacement, credentialStore) : replacement;
|
|
1425
1543
|
saveConnection(secured);
|
|
1426
1544
|
config = loadConfig();
|
|
1427
1545
|
activateConnection(secured);
|
|
1428
1546
|
result = await runAgent();
|
|
1429
1547
|
}
|
|
1430
|
-
if (
|
|
1548
|
+
if (turnUsage && !turnUsage.estimated) {
|
|
1431
1549
|
let pricing;
|
|
1432
1550
|
try {
|
|
1433
|
-
|
|
1551
|
+
if (usedModels.size <= 1)
|
|
1552
|
+
pricing = findModelPricing(await provider.discoverModels(signal), result.selectedModel.id);
|
|
1434
1553
|
}
|
|
1435
1554
|
catch {
|
|
1436
1555
|
// A valid answer remains usable when the provider catalog is unavailable.
|
|
1437
1556
|
}
|
|
1438
|
-
|
|
1439
|
-
sessionTokens = result.usage.totalTokens;
|
|
1440
|
-
if (firstAssistantDeltaAt)
|
|
1441
|
-
outputTokenRate = calculateOutputTokenRate(result.usage.completionTokens, Date.now() - firstAssistantDeltaAt);
|
|
1557
|
+
recordTurnUsage(pricing);
|
|
1442
1558
|
}
|
|
1559
|
+
recordTurnUsage();
|
|
1560
|
+
// Failed/cancelled tasks can still consume tokens, even without a saved answer.
|
|
1561
|
+
if (turnUsage)
|
|
1562
|
+
sessionStore.save(activeSession);
|
|
1443
1563
|
const otherConnections = Object.keys(config.connections ?? {}).filter((id) => id !== activeConnection?.id);
|
|
1444
1564
|
const resolution = resolveTurnAssistantAnswer(result, conversationHistory.length, lastErrorEvent, streamedAnswer, currentModel, otherConnections);
|
|
1445
1565
|
lastAnswer = resolution.answer;
|
|
1566
|
+
if (budgetStopped) {
|
|
1567
|
+
const note = `Token budget reached (${tokenBudget?.toLocaleString()} per task). Task stopped; usage may exceed the limit because providers report in chunks.`;
|
|
1568
|
+
lastAnswer = [lastAnswer, note].filter(Boolean).join('\n\n');
|
|
1569
|
+
streamedAnswer = lastAnswer;
|
|
1570
|
+
}
|
|
1446
1571
|
if (resolution.isError) {
|
|
1447
1572
|
streamedAnswer = lastAnswer;
|
|
1448
1573
|
}
|
|
@@ -1469,6 +1594,9 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1469
1594
|
if (thinkingTimer)
|
|
1470
1595
|
clearInterval(thinkingTimer);
|
|
1471
1596
|
removeGenerationListener?.();
|
|
1597
|
+
recordTurnUsage();
|
|
1598
|
+
if (turnUsage)
|
|
1599
|
+
sessionStore.save(activeSession);
|
|
1472
1600
|
const otherConnections = Object.keys(config.connections ?? {}).filter((id) => id !== activeConnection?.id);
|
|
1473
1601
|
const altSuggestion = otherConnections.length > 0
|
|
1474
1602
|
? ` (other configured providers: ${otherConnections.join(', ')})`
|
|
@@ -1486,13 +1614,14 @@ export async function handleChatSession(args, version, signal) {
|
|
|
1486
1614
|
model: currentModel ?? 'No model connected — use /connect',
|
|
1487
1615
|
tokens: Math.round(sessionTokens),
|
|
1488
1616
|
cost: costLabel(),
|
|
1489
|
-
usageAvailable: activeSession.usage.available,
|
|
1617
|
+
usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated,
|
|
1490
1618
|
workspace: canonicalWorkspace,
|
|
1491
1619
|
mode: activeMode,
|
|
1492
1620
|
autoApprove: activeAutoApprove,
|
|
1493
1621
|
chatQuestion: lastQuestion,
|
|
1494
1622
|
chatAnswer: lastAnswer,
|
|
1495
1623
|
chatThoughtTime: lastThoughtTime,
|
|
1624
|
+
tokenUsage: turnUsage,
|
|
1496
1625
|
queuedCommands: commandQueue.items,
|
|
1497
1626
|
}, process.stdout.rows));
|
|
1498
1627
|
process.stdout.write(renderChatComposerCursor({ width: process.stdout.columns }, 0));
|