neoctl 0.1.17 → 0.1.19
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/agents/local-agent-task.js +1 -2
- package/dist/agents/local-agent-task.js.map +1 -1
- package/dist/agents/smoke-agents.js +4 -21
- package/dist/agents/smoke-agents.js.map +1 -1
- package/dist/context/prompts.js +0 -4
- package/dist/context/prompts.js.map +1 -1
- package/dist/core/query-engine.d.ts +1 -22
- package/dist/core/query-engine.js +12 -106
- package/dist/core/query-engine.js.map +1 -1
- package/dist/core/query.d.ts +1 -2
- package/dist/core/query.js +5 -59
- package/dist/core/query.js.map +1 -1
- package/dist/core/smoke-core-loop.js +6 -95
- package/dist/core/smoke-core-loop.js.map +1 -1
- package/dist/index.d.ts +1 -25
- package/dist/index.js +1 -25
- package/dist/index.js.map +1 -1
- package/dist/model/communication-logger.d.ts +1 -2
- package/dist/model/communication-logger.js +0 -3
- package/dist/model/communication-logger.js.map +1 -1
- package/dist/model/config.d.ts +4 -10
- package/dist/model/config.js +12 -61
- package/dist/model/config.js.map +1 -1
- package/dist/model/context-window.js +0 -1
- package/dist/model/context-window.js.map +1 -1
- package/dist/model/env.js +19 -35
- package/dist/model/env.js.map +1 -1
- package/dist/model/model-metadata.json +677 -726
- package/dist/model/openai-adapter.d.ts +1 -1
- package/dist/model/openai-chat-mapper.d.ts +1 -4
- package/dist/model/openai-chat-mapper.js +8 -30
- package/dist/model/openai-chat-mapper.js.map +1 -1
- package/dist/model/openai-mappers.d.ts +2 -5
- package/dist/model/openai-mappers.js +4 -17
- package/dist/model/openai-mappers.js.map +1 -1
- package/dist/model/openai-responses-mapper.d.ts +1 -1
- package/dist/model/openai-responses-mapper.js +1 -2
- package/dist/model/openai-responses-mapper.js.map +1 -1
- package/dist/model/provider-factory.js +0 -32
- package/dist/model/provider-factory.js.map +1 -1
- package/dist/model/smoke-openai.js +1 -1
- package/dist/model/smoke-openai.js.map +1 -1
- package/dist/model/smoke-responses-mapper.js +6 -6
- package/dist/model/smoke-responses-mapper.js.map +1 -1
- package/dist/repl/commands.d.ts +0 -15
- package/dist/repl/commands.js +0 -58
- package/dist/repl/commands.js.map +1 -1
- package/dist/repl/index.js +144 -970
- package/dist/repl/index.js.map +1 -1
- package/dist/repl/render.js +2 -0
- package/dist/repl/render.js.map +1 -1
- package/dist/repl/status-line.d.ts +1 -0
- package/dist/repl/status-line.js +34 -27
- package/dist/repl/status-line.js.map +1 -1
- package/dist/session/session-store.js +2 -2
- package/dist/session/session-store.js.map +1 -1
- package/dist/session/smoke-session.js +1 -22
- package/dist/session/smoke-session.js.map +1 -1
- package/dist/skills/skill-tool.d.ts +5 -85
- package/dist/skills/skill-tool.js +14 -173
- package/dist/skills/skill-tool.js.map +1 -1
- package/dist/skills/smoke-skills.js +5 -54
- package/dist/skills/smoke-skills.js.map +1 -1
- package/dist/tools/builtins/search-providers.d.ts +1 -15
- package/dist/tools/builtins/search-providers.js +1 -195
- package/dist/tools/builtins/search-providers.js.map +1 -1
- package/dist/tools/builtins/search-tool.js +2 -2
- package/dist/tools/builtins/search-tool.js.map +1 -1
- package/dist/tools/registry.d.ts +0 -1
- package/dist/tools/registry.js +0 -11
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/run-tool-use.js +1 -1
- package/dist/tools/run-tool-use.js.map +1 -1
- package/dist/tools/smoke-tool-system.js +9 -43
- package/dist/tools/smoke-tool-system.js.map +1 -1
- package/dist/tools/tool.d.ts +1 -9
- package/dist/tools/tool.js.map +1 -1
- package/package.json +1 -1
- package/scripts/build-standalone.mjs +139 -139
- package/dist/model/deepseek-adapter.d.ts +0 -29
- package/dist/model/deepseek-adapter.js +0 -108
- package/dist/model/deepseek-adapter.js.map +0 -1
- package/dist/model/kimi-adapter.d.ts +0 -29
- package/dist/model/kimi-adapter.js +0 -108
- package/dist/model/kimi-adapter.js.map +0 -1
- package/dist/model/smoke-deepseek-mapper.d.ts +0 -1
- package/dist/model/smoke-deepseek-mapper.js +0 -65
- package/dist/model/smoke-deepseek-mapper.js.map +0 -1
- package/dist/open-directory.d.ts +0 -1
- package/dist/open-directory.js +0 -26
- package/dist/open-directory.js.map +0 -1
- package/dist/paths.d.ts +0 -7
- package/dist/paths.js +0 -12
- package/dist/paths.js.map +0 -1
- package/dist/session/session-export.d.ts +0 -33
- package/dist/session/session-export.js +0 -351
- package/dist/session/session-export.js.map +0 -1
- package/dist/session/simple-session-runtime.d.ts +0 -74
- package/dist/session/simple-session-runtime.js +0 -171
- package/dist/session/simple-session-runtime.js.map +0 -1
- package/dist/skills/skill-filesystem.d.ts +0 -32
- package/dist/skills/skill-filesystem.js +0 -371
- package/dist/skills/skill-filesystem.js.map +0 -1
- package/dist/skills/skill-management-tools.d.ts +0 -36
- package/dist/skills/skill-management-tools.js +0 -188
- package/dist/skills/skill-management-tools.js.map +0 -1
- package/dist/tips.d.ts +0 -10
- package/dist/tips.js +0 -168
- package/dist/tips.js.map +0 -1
- package/dist/tools/builtins/image-generation-tool.d.ts +0 -68
- package/dist/tools/builtins/image-generation-tool.js +0 -315
- package/dist/tools/builtins/image-generation-tool.js.map +0 -1
- package/dist/ui/display-message.d.ts +0 -101
- package/dist/ui/display-message.js +0 -113
- package/dist/ui/display-message.js.map +0 -1
- package/dist/web/html.d.ts +0 -1
- package/dist/web/html.js +0 -858
- package/dist/web/html.js.map +0 -1
- package/dist/web/index.d.ts +0 -2
- package/dist/web/index.js +0 -1810
- package/dist/web/index.js.map +0 -1
package/dist/repl/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
4
3
|
import path from "node:path";
|
|
5
4
|
import { stdin, stdout } from "node:process";
|
|
6
5
|
import React, { useCallback, useEffect, useRef, useState } from "react";
|
|
@@ -8,29 +7,25 @@ import { Box, Static, Text, render, useApp, useInput } from "ink";
|
|
|
8
7
|
import stripAnsi from "strip-ansi";
|
|
9
8
|
import wrapAnsi from "wrap-ansi";
|
|
10
9
|
import { QueryEngine } from "../core/query-engine.js";
|
|
11
|
-
import {
|
|
10
|
+
import { loadDefaultDotEnvFiles } from "../model/env.js";
|
|
12
11
|
import { readModelProviderConfig } from "../model/config.js";
|
|
13
|
-
import {
|
|
12
|
+
import { loadModelCatalog, reasoningEffortsForModel, resolveContextWindowTokens } from "../model/context-window.js";
|
|
14
13
|
import { CommunicationLogger, LoggingModelGateway } from "../model/communication-logger.js";
|
|
15
|
-
import {
|
|
14
|
+
import { createModelGatewayFromProcessEnv } from "../model/provider-factory.js";
|
|
16
15
|
import { ToolRegistry } from "../tools/registry.js";
|
|
16
|
+
import { echoTool } from "../tools/builtins/echo-tool.js";
|
|
17
17
|
import { editTool, writeTool } from "../tools/builtins/edit-tool.js";
|
|
18
18
|
import { createExecTool } from "../tools/builtins/exec-tool.js";
|
|
19
19
|
import { listDirectoryTool, readFileTool } from "../tools/builtins/filesystem-tools.js";
|
|
20
20
|
import { grepTool } from "../tools/builtins/grep-tool.js";
|
|
21
21
|
import { searchTool } from "../tools/builtins/search-tool.js";
|
|
22
22
|
import { planTool } from "../tools/builtins/plan-tool.js";
|
|
23
|
-
import { createOpenAIImageGenerationTool } from "../tools/builtins/image-generation-tool.js";
|
|
24
23
|
import { createAgentTool, resumeAgentTask } from "../agents/agent-tool.js";
|
|
25
24
|
import { createTaskTools } from "../tasks/task-tools.js";
|
|
26
25
|
import { TaskStore } from "../tasks/task-store.js";
|
|
27
|
-
import {
|
|
26
|
+
import { isModelReasoningArgument, isValidReplCommandLine, parseReplCommand, helpText, replCommandDefinitions } from "./commands.js";
|
|
28
27
|
import { estimateMarkdownLineCount, markdownRenderKey, MarkdownText } from "./markdown-renderer.js";
|
|
29
|
-
import { writeSessionMarkdownExport } from "../session/session-export.js";
|
|
30
28
|
import { readClipboard } from "./clipboard.js";
|
|
31
|
-
import { formatTipLine, initialTipIndex, tipAt } from "../tips.js";
|
|
32
|
-
import { openDirectory } from "../open-directory.js";
|
|
33
|
-
import { runWebServer } from "../web/index.js";
|
|
34
29
|
const e = React.createElement;
|
|
35
30
|
class SessionUsageTracker {
|
|
36
31
|
totals = emptyUsageTotals();
|
|
@@ -88,45 +83,14 @@ function sumUsageTokens(left, right) {
|
|
|
88
83
|
return undefined;
|
|
89
84
|
return (left ?? 0) + (right ?? 0);
|
|
90
85
|
}
|
|
91
|
-
async function main(
|
|
92
|
-
const webArgs = parseWebCliArgs(argv);
|
|
93
|
-
if (webArgs) {
|
|
94
|
-
await runWebServer(webArgs);
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
const initialCommand = parseCliReplCommandArgs(argv);
|
|
98
|
-
if (argv.length > 0 && !initialCommand) {
|
|
99
|
-
console.error(`Unknown or incomplete command: ${argv.join(" ")}\n\n${cliHelpText(binaryName())}`);
|
|
100
|
-
process.exitCode = 1;
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
if (initialCommand?.definition.name === "/help") {
|
|
104
|
-
console.log(cliHelpText(binaryName()));
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
86
|
+
async function main() {
|
|
107
87
|
const runtime = await createRuntime();
|
|
108
|
-
const instance = render(e(InkRepl, { runtime
|
|
88
|
+
const instance = render(e(InkRepl, { runtime }), {
|
|
109
89
|
exitOnCtrlC: false,
|
|
110
90
|
});
|
|
111
91
|
await instance.waitUntilExit();
|
|
112
92
|
console.log("bye.");
|
|
113
93
|
}
|
|
114
|
-
function parseWebCliArgs(argv) {
|
|
115
|
-
if (argv.length === 0)
|
|
116
|
-
return undefined;
|
|
117
|
-
const first = argv[0];
|
|
118
|
-
if (first !== "-web" && first !== "--web")
|
|
119
|
-
return undefined;
|
|
120
|
-
return argv.slice(1);
|
|
121
|
-
}
|
|
122
|
-
function binaryName() {
|
|
123
|
-
const arg = process.argv[1];
|
|
124
|
-
if (!arg)
|
|
125
|
-
return "neo";
|
|
126
|
-
const parsed = path.parse(arg);
|
|
127
|
-
const name = parsed.name || "neo";
|
|
128
|
-
return name === "index" ? "neo" : name;
|
|
129
|
-
}
|
|
130
94
|
function createTaskNotificationSource(taskStore) {
|
|
131
95
|
return {
|
|
132
96
|
collectUnnotifiedCompletions() {
|
|
@@ -150,6 +114,7 @@ async function createRuntime() {
|
|
|
150
114
|
const modelGateway = new LoggingModelGateway(createModelGatewayFromProcessEnv(process.env), communicationLogger);
|
|
151
115
|
const taskStore = new TaskStore();
|
|
152
116
|
const tools = new ToolRegistry();
|
|
117
|
+
tools.register(echoTool);
|
|
153
118
|
tools.register(editTool);
|
|
154
119
|
tools.register(writeTool);
|
|
155
120
|
tools.register(createExecTool({ taskStore }));
|
|
@@ -157,8 +122,6 @@ async function createRuntime() {
|
|
|
157
122
|
tools.register(readFileTool);
|
|
158
123
|
tools.register(grepTool);
|
|
159
124
|
tools.register(searchTool);
|
|
160
|
-
if (modelConfig?.provider === "openai")
|
|
161
|
-
tools.register(createOpenAIImageGenerationTool());
|
|
162
125
|
tools.register(planTool);
|
|
163
126
|
const agentRuntime = { modelGateway, tools, taskStore };
|
|
164
127
|
tools.register(createAgentTool(agentRuntime));
|
|
@@ -182,7 +145,6 @@ async function createRuntime() {
|
|
|
182
145
|
modelGateway,
|
|
183
146
|
tools,
|
|
184
147
|
taskNotificationSource,
|
|
185
|
-
commands: replCommandDefinitions.map((command) => command.usage),
|
|
186
148
|
session: {
|
|
187
149
|
enabled: process.env.AGENT_SESSION_TRANSCRIPT !== "0",
|
|
188
150
|
sessionId: process.env.AGENT_SESSION_ID,
|
|
@@ -194,59 +156,65 @@ async function createRuntime() {
|
|
|
194
156
|
},
|
|
195
157
|
});
|
|
196
158
|
await engine.initialize();
|
|
197
|
-
const initialMetrics = await engine.contextMetrics();
|
|
198
159
|
return {
|
|
199
160
|
engine,
|
|
200
161
|
communicationLogger,
|
|
201
|
-
modelGateway,
|
|
202
|
-
agentRuntime,
|
|
203
162
|
usage: new SessionUsageTracker(),
|
|
204
163
|
taskStore,
|
|
205
|
-
tools,
|
|
206
|
-
initialMetrics,
|
|
164
|
+
initialMetrics: initialContextMetrics(modelConfig?.model, engine.snapshot().messages, tools.names().length),
|
|
207
165
|
defaultReasoning: modelConfig?.defaultReasoning,
|
|
208
|
-
envPath: process.env.NEO_ENV_FILE?.trim() ? path.resolve(process.env.NEO_ENV_FILE.trim()) : envLoad.userDotEnvPath,
|
|
209
166
|
envNotice: envLoad.createdUserDotEnv ? formatCreatedEnvNotice(envLoad.userDotEnvPath) : undefined,
|
|
210
167
|
};
|
|
211
168
|
}
|
|
212
|
-
function syncImageGenerationTool(runtime, provider) {
|
|
213
|
-
runtime.tools.unregister("image2");
|
|
214
|
-
if (provider === "openai")
|
|
215
|
-
runtime.tools.register(createOpenAIImageGenerationTool());
|
|
216
|
-
}
|
|
217
169
|
function formatCreatedEnvNotice(path) {
|
|
218
|
-
return `Created default config file: ${path}\
|
|
170
|
+
return `Created default config file: ${path}\nFill MODEL_API_KEY in that file, then restart neo.`;
|
|
219
171
|
}
|
|
220
172
|
function parseResumeFlag(value) {
|
|
221
173
|
if (!value)
|
|
222
174
|
return false;
|
|
223
175
|
return ["1", "true", "yes", "latest"].includes(value.toLowerCase());
|
|
224
176
|
}
|
|
225
|
-
function
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
177
|
+
function initialContextMetrics(model, messageCount, toolCount) {
|
|
178
|
+
const window = resolveContextWindowTokens(model);
|
|
179
|
+
return {
|
|
180
|
+
model,
|
|
181
|
+
estimatedInputTokens: 0,
|
|
182
|
+
estimatedChars: 0,
|
|
183
|
+
messageCount,
|
|
184
|
+
toolCount,
|
|
185
|
+
contextWindowTokens: window.tokens,
|
|
186
|
+
contextWindowSource: window.source,
|
|
187
|
+
contextUsageRatio: window.tokens ? 0 : undefined,
|
|
188
|
+
modelMetadata: window.model
|
|
189
|
+
? {
|
|
190
|
+
id: window.model.id,
|
|
191
|
+
provider: window.model.provider,
|
|
192
|
+
maxOutputTokens: window.model.maxOutputTokens,
|
|
193
|
+
knowledgeCutoff: window.model.knowledgeCutoff,
|
|
194
|
+
reasoning: window.model.reasoning,
|
|
195
|
+
imageInput: window.model.imageInput,
|
|
196
|
+
source: window.model.source,
|
|
197
|
+
}
|
|
198
|
+
: undefined,
|
|
199
|
+
};
|
|
230
200
|
}
|
|
231
|
-
function initialStatus(runtime
|
|
201
|
+
function initialStatus(runtime) {
|
|
232
202
|
return {
|
|
233
203
|
phase: "ready",
|
|
234
204
|
metrics: {
|
|
235
|
-
...
|
|
205
|
+
...runtime.initialMetrics,
|
|
236
206
|
messageCount: runtime.engine.snapshot().messages,
|
|
237
207
|
},
|
|
238
208
|
streamedOutputTokens: 0,
|
|
239
209
|
activityTick: 0,
|
|
240
210
|
};
|
|
241
211
|
}
|
|
242
|
-
|
|
243
|
-
return initialStatus(runtime, await runtime.engine.contextMetrics());
|
|
244
|
-
}
|
|
245
|
-
function setTerminalTitle(title, prefix = TERMINAL_TITLE_WORKING_PREFIX) {
|
|
212
|
+
function setTerminalTitle(title, dotFilled = true) {
|
|
246
213
|
if (!stdout.isTTY)
|
|
247
214
|
return;
|
|
248
215
|
const safeTitle = title.replace(/[\u0000-\u001f\u007f]+/g, " ").replace(/\s+/g, " ").trim();
|
|
249
|
-
const
|
|
216
|
+
const dotPrefix = dotFilled ? TERMINAL_TITLE_DOT_FILLED_PREFIX : TERMINAL_TITLE_DOT_BLANK_PREFIX;
|
|
217
|
+
const decoratedTitle = `${dotPrefix}${safeTitle || "neo"}`.slice(0, 120);
|
|
250
218
|
stdout.write(`\u001b]0;${decoratedTitle}\u0007`);
|
|
251
219
|
}
|
|
252
220
|
function playReadySound() {
|
|
@@ -369,7 +337,7 @@ function pushTextBlock(blocks, text) {
|
|
|
369
337
|
function escapeRegExp(value) {
|
|
370
338
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
371
339
|
}
|
|
372
|
-
function InkRepl({ runtime
|
|
340
|
+
function InkRepl({ runtime }) {
|
|
373
341
|
const app = useApp();
|
|
374
342
|
const lineId = useRef(0);
|
|
375
343
|
const assistantLineId = useRef(undefined);
|
|
@@ -386,19 +354,13 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
386
354
|
const queuedAttachmentsRef = useRef(undefined);
|
|
387
355
|
const [cursor, setCursor] = useState(0);
|
|
388
356
|
const [promptPlaceholder, setPromptPlaceholder] = useState(undefined);
|
|
389
|
-
const [tipIndex, setTipIndex] = useState(() => initialTipIndex(runtime.engine.snapshot().session?.sessionId ?? process.cwd()));
|
|
390
357
|
const [busy, setBusy] = useState(false);
|
|
391
358
|
const [status, setStatus] = useState(() => initialStatus(runtime));
|
|
392
359
|
const sessionTitleRef = useRef(sessionTerminalTitle(runtime.engine.snapshot().session));
|
|
393
|
-
const [
|
|
394
|
-
const [backgroundSessionRuns, setBackgroundSessionRuns] = useState([]);
|
|
395
|
-
const backgroundSessionRunsRef = useRef(new Map());
|
|
396
|
-
const suppressReattachedStreamingRef = useRef(new Set());
|
|
397
|
-
const activePromptRunRef = useRef(undefined);
|
|
360
|
+
const [backgroundTaskCount, setBackgroundTaskCount] = useState(() => runtime.taskStore.activeCount());
|
|
398
361
|
const [animationTick, setAnimationTick] = useState(0);
|
|
399
|
-
const [
|
|
400
|
-
const
|
|
401
|
-
const terminalTitleWorking = isActivePhase(status.phase) || backgroundTaskCount > 0 || backgroundSessionRuns.length > 0;
|
|
362
|
+
const [terminalTitleDotVisible, setTerminalTitleDotVisible] = useState(true);
|
|
363
|
+
const terminalTitleWorking = isActivePhase(status.phase) || backgroundTaskCount > 0;
|
|
402
364
|
const [sessionsBrowser, setSessionsBrowser] = useState(undefined);
|
|
403
365
|
const inputRef = useRef(input);
|
|
404
366
|
const queuedInputRef = useRef(undefined);
|
|
@@ -415,8 +377,6 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
415
377
|
const [pasteStatus, setPasteStatus] = useState(undefined);
|
|
416
378
|
const pasteStatusTimerRef = useRef(undefined);
|
|
417
379
|
const [slashCompletionIndex, setSlashCompletionIndex] = useState(0);
|
|
418
|
-
const [loginForm, setLoginForm] = useState(undefined);
|
|
419
|
-
const loginFormRef = useRef(undefined);
|
|
420
380
|
useEffect(() => {
|
|
421
381
|
enableTerminalFocusReporting();
|
|
422
382
|
enableTerminalMouseReporting();
|
|
@@ -426,35 +386,36 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
426
386
|
};
|
|
427
387
|
}, []);
|
|
428
388
|
useEffect(() => {
|
|
429
|
-
if (!busy && backgroundTaskCount === 0
|
|
389
|
+
if (!busy && backgroundTaskCount === 0)
|
|
430
390
|
return undefined;
|
|
431
391
|
const interval = setInterval(() => setAnimationTick((current) => current + 1), REPL_ANIMATION_INTERVAL_MS);
|
|
432
392
|
return () => clearInterval(interval);
|
|
433
|
-
}, [busy, backgroundTaskCount
|
|
393
|
+
}, [busy, backgroundTaskCount]);
|
|
434
394
|
useEffect(() => {
|
|
435
|
-
const
|
|
436
|
-
|
|
437
|
-
return runtime.taskStore.subscribe(
|
|
395
|
+
const updateBackgroundTaskCount = () => setBackgroundTaskCount(runtime.taskStore.activeCount());
|
|
396
|
+
updateBackgroundTaskCount();
|
|
397
|
+
return runtime.taskStore.subscribe(updateBackgroundTaskCount);
|
|
438
398
|
}, [runtime]);
|
|
439
399
|
useEffect(() => {
|
|
440
400
|
if (!terminalTitleWorking) {
|
|
441
|
-
|
|
401
|
+
setTerminalTitleDotVisible(true);
|
|
442
402
|
return undefined;
|
|
443
403
|
}
|
|
444
|
-
|
|
445
|
-
|
|
404
|
+
setTerminalTitleDotVisible(true);
|
|
405
|
+
const interval = setInterval(() => setTerminalTitleDotVisible((visible) => !visible), TERMINAL_TITLE_BLINK_INTERVAL_MS);
|
|
406
|
+
return () => clearInterval(interval);
|
|
446
407
|
}, [terminalTitleWorking]);
|
|
447
408
|
useEffect(() => {
|
|
448
409
|
const updateTitle = (snapshot) => {
|
|
449
410
|
sessionTitleRef.current = sessionTerminalTitle(snapshot);
|
|
450
|
-
setTerminalTitle(sessionTitleRef.current,
|
|
411
|
+
setTerminalTitle(sessionTitleRef.current, terminalTitleDotVisible);
|
|
451
412
|
};
|
|
452
413
|
updateTitle(runtime.engine.snapshot().session);
|
|
453
414
|
return runtime.engine.onSessionTitleChange(updateTitle);
|
|
454
|
-
}, [runtime,
|
|
415
|
+
}, [runtime, terminalTitleDotVisible]);
|
|
455
416
|
useEffect(() => {
|
|
456
|
-
setTerminalTitle(sessionTitleRef.current,
|
|
457
|
-
}, [
|
|
417
|
+
setTerminalTitle(sessionTitleRef.current, terminalTitleDotVisible);
|
|
418
|
+
}, [terminalTitleDotVisible]);
|
|
458
419
|
const setPromptState = (text, nextCursor, options) => {
|
|
459
420
|
const safeCursor = Math.max(0, Math.min(nextCursor, text.length));
|
|
460
421
|
inputRef.current = text;
|
|
@@ -481,10 +442,6 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
481
442
|
setSlashCompletionIndex(safeIndex);
|
|
482
443
|
};
|
|
483
444
|
const resetSlashCompletionSelection = () => setSlashCompletionSelection(0);
|
|
484
|
-
const setLoginFormState = (next) => {
|
|
485
|
-
loginFormRef.current = next;
|
|
486
|
-
setLoginForm(next);
|
|
487
|
-
};
|
|
488
445
|
const syncAttachmentsForText = (text) => {
|
|
489
446
|
const next = attachmentsRef.current.filter((attachment) => text.includes(attachment.label));
|
|
490
447
|
if (next.length === attachmentsRef.current.length)
|
|
@@ -511,11 +468,9 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
511
468
|
}, PASTE_STATUS_DISPLAY_MS);
|
|
512
469
|
pasteStatusTimerRef.current = timer;
|
|
513
470
|
};
|
|
514
|
-
const advanceTip = () => setTipIndex((current) => current + 1);
|
|
515
471
|
const insertAtCursor = (value) => {
|
|
516
472
|
const currentText = inputRef.current;
|
|
517
473
|
const currentCursor = cursorRef.current;
|
|
518
|
-
advanceTip();
|
|
519
474
|
setPromptState(`${currentText.slice(0, currentCursor)}${value}${currentText.slice(currentCursor)}`, currentCursor + value.length);
|
|
520
475
|
};
|
|
521
476
|
const insertAttachmentLabel = (attachment) => {
|
|
@@ -531,10 +486,6 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
531
486
|
return;
|
|
532
487
|
}
|
|
533
488
|
if (payload.type === "image") {
|
|
534
|
-
if (!runtime.engine.canAcceptImageInput()) {
|
|
535
|
-
setPasteStatusMessage("current model does not support image input; image was not added");
|
|
536
|
-
return;
|
|
537
|
-
}
|
|
538
489
|
const id = ++imageAttachmentCounterRef.current;
|
|
539
490
|
insertAttachmentLabel({ id, kind: "image", label: `[img#${id}]`, image: payload.image });
|
|
540
491
|
setPasteStatusMessage(undefined);
|
|
@@ -587,73 +538,17 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
587
538
|
const replaceLine = (id, patch) => {
|
|
588
539
|
setLines((current) => current.map((line) => line.id === id ? { ...line, ...patch, renderedKey: undefined } : line));
|
|
589
540
|
};
|
|
590
|
-
const
|
|
591
|
-
setBackgroundSessionRuns([...backgroundSessionRunsRef.current.values()]);
|
|
592
|
-
};
|
|
593
|
-
const detachRunningForeground = (reason) => {
|
|
594
|
-
if (!busyRef.current)
|
|
595
|
-
return false;
|
|
596
|
-
const snapshot = runtime.engine.snapshot().session;
|
|
597
|
-
const sessionId = snapshot?.sessionId ?? `session-${Date.now().toString(36)}`;
|
|
598
|
-
const run = activePromptRunRef.current;
|
|
599
|
-
if (run && !backgroundSessionRunsRef.current.has(sessionId)) {
|
|
600
|
-
const backgroundRun = {
|
|
601
|
-
sessionId,
|
|
602
|
-
title: snapshot?.title,
|
|
603
|
-
reason,
|
|
604
|
-
startedAt: Date.now(),
|
|
605
|
-
engine: runtime.engine,
|
|
606
|
-
abortController: activeAbortController.current ?? new AbortController(),
|
|
607
|
-
promise: run,
|
|
608
|
-
};
|
|
609
|
-
backgroundSessionRunsRef.current.set(sessionId, backgroundRun);
|
|
610
|
-
syncBackgroundSessionRuns();
|
|
611
|
-
setSessionsBrowser((current) => current ? { ...current, runningSessionIds: runningSessionIds(backgroundSessionRunsRef.current) } : current);
|
|
612
|
-
run.finally(() => {
|
|
613
|
-
backgroundSessionRunsRef.current.delete(sessionId);
|
|
614
|
-
suppressReattachedStreamingRef.current.delete(backgroundRun.engine);
|
|
615
|
-
syncBackgroundSessionRuns();
|
|
616
|
-
setSessionsBrowser((current) => current ? { ...current, runningSessionIds: runningSessionIds(backgroundSessionRunsRef.current) } : current);
|
|
617
|
-
}).catch(() => undefined);
|
|
618
|
-
}
|
|
619
|
-
activeAbortController.current = undefined;
|
|
620
|
-
interruptArmed.current = false;
|
|
621
|
-
setQueuedPromptState(undefined);
|
|
622
|
-
setBusyState(false);
|
|
623
|
-
setStatus((current) => ({ ...current, phase: "ready", detail: undefined }));
|
|
624
|
-
append(systemLine(`Detached running ${sessionId} to background for ${reason}.`));
|
|
625
|
-
return true;
|
|
626
|
-
};
|
|
627
|
-
const resetForegroundView = (metrics) => {
|
|
541
|
+
const resumeSnapshot = (snapshot) => {
|
|
628
542
|
runtime.usage.reset();
|
|
629
|
-
setStatus(initialStatus(runtime
|
|
543
|
+
setStatus(initialStatus(runtime));
|
|
630
544
|
resetLinesToHistory(runtime, setLines, lineId);
|
|
631
545
|
assistantLineId.current = undefined;
|
|
632
546
|
thinkingLineId.current = undefined;
|
|
633
547
|
finalizedThinkingLineId.current = undefined;
|
|
634
548
|
toolLineIds.current.clear();
|
|
635
549
|
clearPendingToolResultTimers();
|
|
636
|
-
};
|
|
637
|
-
const resumeSnapshot = (snapshot, metrics) => {
|
|
638
|
-
resetForegroundView(metrics);
|
|
639
550
|
append(systemLine(formatResume(snapshot)));
|
|
640
551
|
};
|
|
641
|
-
const reattachRunningSession = async (run) => {
|
|
642
|
-
detachRunningForeground("session switch");
|
|
643
|
-
backgroundSessionRunsRef.current.delete(run.sessionId);
|
|
644
|
-
syncBackgroundSessionRuns();
|
|
645
|
-
setSessionsBrowser((current) => current ? { ...current, runningSessionIds: runningSessionIds(backgroundSessionRunsRef.current) } : current);
|
|
646
|
-
runtime.engine = run.engine;
|
|
647
|
-
activeAbortController.current = run.abortController;
|
|
648
|
-
interruptArmed.current = false;
|
|
649
|
-
activePromptRunRef.current = run.promise;
|
|
650
|
-
suppressReattachedStreamingRef.current.add(run.engine);
|
|
651
|
-
const metrics = await runtime.engine.contextMetrics();
|
|
652
|
-
resetForegroundView(metrics);
|
|
653
|
-
setBusyState(true);
|
|
654
|
-
setStatus((current) => ({ ...current, phase: "running", detail: "working" }));
|
|
655
|
-
append(systemLine(`reattached running session ${run.sessionId}`));
|
|
656
|
-
};
|
|
657
552
|
const finalizeLiveLine = (id) => {
|
|
658
553
|
if (id === undefined)
|
|
659
554
|
return;
|
|
@@ -798,12 +693,14 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
798
693
|
const trimmed = text.trim();
|
|
799
694
|
if (!trimmed)
|
|
800
695
|
return;
|
|
801
|
-
if (submitAttachments.some((attachment) => attachment.kind === "image") && !runtime.engine.canAcceptImageInput()) {
|
|
802
|
-
append({ kind: "error", text: "Current model does not support image input; image attachments were not added to the conversation." });
|
|
803
|
-
return;
|
|
804
|
-
}
|
|
805
696
|
if (busyRef.current) {
|
|
806
|
-
|
|
697
|
+
if (queuedInputRef.current !== undefined)
|
|
698
|
+
return;
|
|
699
|
+
setQueuedPromptState(text, submitAttachments);
|
|
700
|
+
setHistorySelection(undefined);
|
|
701
|
+
setPromptState("", 0);
|
|
702
|
+
clearAttachments();
|
|
703
|
+
return;
|
|
807
704
|
}
|
|
808
705
|
history.current = [text, ...history.current.filter((entry) => entry !== text)].slice(0, 100);
|
|
809
706
|
setHistorySelection(undefined);
|
|
@@ -901,68 +798,16 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
901
798
|
if (command.type === "reset") {
|
|
902
799
|
runtime.engine.reset();
|
|
903
800
|
runtime.usage.reset();
|
|
904
|
-
setStatus(
|
|
801
|
+
setStatus(initialStatus(runtime));
|
|
905
802
|
append(systemLine("transcript reset"));
|
|
906
803
|
return;
|
|
907
804
|
}
|
|
908
805
|
if (command.type === "state") {
|
|
909
|
-
|
|
910
|
-
append(systemLine(formatReplData({ ...runtime.engine.snapshot(), contextMetrics, communicationLog: runtime.communicationLogger.snapshot() }, 12000), EXPANDED_SUMMARY_MAX_LINES));
|
|
911
|
-
return;
|
|
912
|
-
}
|
|
913
|
-
if (command.type === "export") {
|
|
914
|
-
setBusyState(true);
|
|
915
|
-
setStatus((current) => ({ ...current, phase: "running", detail: "exporting session", activityTick: current.activityTick + 1 }));
|
|
916
|
-
try {
|
|
917
|
-
const line = await handleExportCommand(command, runtime);
|
|
918
|
-
append(line);
|
|
919
|
-
}
|
|
920
|
-
catch (error) {
|
|
921
|
-
append({ kind: "error", text: error instanceof Error ? error.message : String(error) });
|
|
922
|
-
}
|
|
923
|
-
finally {
|
|
924
|
-
setBusyState(false);
|
|
925
|
-
setStatus((current) => ({ ...current, phase: "ready", detail: undefined, activityTick: current.activityTick + 1 }));
|
|
926
|
-
}
|
|
927
|
-
return;
|
|
928
|
-
}
|
|
929
|
-
if (command.type === "env") {
|
|
930
|
-
const envDirectory = path.dirname(runtime.envPath);
|
|
931
|
-
try {
|
|
932
|
-
await fs.mkdir(envDirectory, { recursive: true });
|
|
933
|
-
await openDirectory(envDirectory);
|
|
934
|
-
append({ kind: "system", title: "System", text: `Opened env directory: ${envDirectory}`, format: "plain", previewStyle: "summary" });
|
|
935
|
-
}
|
|
936
|
-
catch (error) {
|
|
937
|
-
append({ kind: "error", text: `Failed to open env directory ${envDirectory}: ${error instanceof Error ? error.message : String(error)}`, format: "plain" });
|
|
938
|
-
}
|
|
939
|
-
return;
|
|
940
|
-
}
|
|
941
|
-
if (command.type === "new") {
|
|
942
|
-
detachRunningForeground("new session");
|
|
943
|
-
runtime.engine = runtime.engine.forkForSession(undefined, false);
|
|
944
|
-
await runtime.engine.initialize();
|
|
945
|
-
const snapshot = runtime.engine.snapshot().session;
|
|
946
|
-
const metrics = await runtime.engine.contextMetrics();
|
|
947
|
-
runtime.usage.reset();
|
|
948
|
-
setStatus(initialStatus(runtime, metrics));
|
|
949
|
-
resetLinesToHistory(runtime, setLines, lineId);
|
|
950
|
-
assistantLineId.current = undefined;
|
|
951
|
-
thinkingLineId.current = undefined;
|
|
952
|
-
finalizedThinkingLineId.current = undefined;
|
|
953
|
-
toolLineIds.current.clear();
|
|
954
|
-
clearPendingToolResultTimers();
|
|
955
|
-
append(systemLine(snapshot ? `new session ${snapshot.sessionId}` : "new session"));
|
|
806
|
+
append(systemLine(formatReplData({ ...runtime.engine.snapshot(), communicationLog: runtime.communicationLogger.snapshot() }, 12000), EXPANDED_SUMMARY_MAX_LINES));
|
|
956
807
|
return;
|
|
957
808
|
}
|
|
958
809
|
if (command.type === "sessions") {
|
|
959
|
-
await handleSessionsCommand(runtime,
|
|
960
|
-
return;
|
|
961
|
-
}
|
|
962
|
-
if (command.type === "login") {
|
|
963
|
-
setSessionsBrowser(undefined);
|
|
964
|
-
setLoginFormState(createLoginFormState(runtime.envPath));
|
|
965
|
-
append(systemLine("Opening provider login. Use ↑/↓ to choose, Enter to continue/save, Esc to cancel."));
|
|
810
|
+
await handleSessionsCommand(runtime, setSessionsBrowser, (line) => append(line));
|
|
966
811
|
return;
|
|
967
812
|
}
|
|
968
813
|
if (command.type === "log") {
|
|
@@ -970,23 +815,9 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
970
815
|
return;
|
|
971
816
|
}
|
|
972
817
|
if (command.type === "model") {
|
|
973
|
-
|
|
974
|
-
setStatus((current) => ({ ...current,
|
|
975
|
-
|
|
976
|
-
const line = await handleModelCommand(command, runtime);
|
|
977
|
-
const metrics = await runtime.engine.contextMetrics();
|
|
978
|
-
setStatus((current) => ({
|
|
979
|
-
...current,
|
|
980
|
-
phase: "ready",
|
|
981
|
-
detail: undefined,
|
|
982
|
-
metrics,
|
|
983
|
-
activityTick: current.activityTick + 1,
|
|
984
|
-
}));
|
|
985
|
-
append(line);
|
|
986
|
-
}
|
|
987
|
-
finally {
|
|
988
|
-
setBusyState(false);
|
|
989
|
-
}
|
|
818
|
+
const line = handleModelCommand(command, runtime);
|
|
819
|
+
setStatus((current) => ({ ...current, metrics: { ...initialContextMetrics(runtime.engine.getModelSettings().model, runtime.engine.snapshot().messages, runtime.initialMetrics.toolCount), messageCount: runtime.engine.snapshot().messages } }));
|
|
820
|
+
append(line);
|
|
990
821
|
return;
|
|
991
822
|
}
|
|
992
823
|
if (text.trimStart().startsWith("/")) {
|
|
@@ -994,10 +825,6 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
994
825
|
return;
|
|
995
826
|
}
|
|
996
827
|
const promptPayload = buildPromptPayload(command.text, submitAttachments);
|
|
997
|
-
if (promptPayload.blocks?.some((block) => block.type === "image") && !runtime.engine.canAcceptImageInput()) {
|
|
998
|
-
append({ kind: "error", text: "Current model does not support image input; image attachments were not added to the conversation." });
|
|
999
|
-
return;
|
|
1000
|
-
}
|
|
1001
828
|
append({ kind: "user", text });
|
|
1002
829
|
const abortController = new AbortController();
|
|
1003
830
|
activeAbortController.current = abortController;
|
|
@@ -1013,41 +840,20 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
1013
840
|
outputTokenUpdatedAt: undefined,
|
|
1014
841
|
retryCooldownUntil: undefined,
|
|
1015
842
|
}));
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
for await (const event of engine.sendUserText(promptPayload.text, { abortSignal: abortController.signal, blocks: promptPayload.blocks, displayText: text })) {
|
|
1019
|
-
if (runtime.engine !== engine)
|
|
1020
|
-
continue;
|
|
1021
|
-
if (suppressReattachedStreamingRef.current.has(engine)) {
|
|
1022
|
-
if (event.type === "message" || event.type === "terminal" || event.type === "error" || event.type === "context.metrics" || event.type === "usage") {
|
|
1023
|
-
if (event.type === "message" || event.type === "terminal" || event.type === "error")
|
|
1024
|
-
suppressReattachedStreamingRef.current.delete(engine);
|
|
1025
|
-
handleEvent(event);
|
|
1026
|
-
}
|
|
1027
|
-
continue;
|
|
1028
|
-
}
|
|
843
|
+
try {
|
|
844
|
+
for await (const event of runtime.engine.sendUserText(promptPayload.text, { abortSignal: abortController.signal, blocks: promptPayload.blocks, displayText: text })) {
|
|
1029
845
|
handleEvent(event);
|
|
1030
846
|
}
|
|
1031
|
-
})();
|
|
1032
|
-
activePromptRunRef.current = run;
|
|
1033
|
-
try {
|
|
1034
|
-
await run;
|
|
1035
847
|
}
|
|
1036
848
|
catch (error) {
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
append({ kind: "error", text: error instanceof Error ? error.message : String(error) });
|
|
1044
|
-
}
|
|
849
|
+
finalizeLiveLine(assistantLineId.current);
|
|
850
|
+
finalizeThinkingLine();
|
|
851
|
+
finalizeActiveToolLines();
|
|
852
|
+
assistantLineId.current = undefined;
|
|
853
|
+
finalizedThinkingLineId.current = undefined;
|
|
854
|
+
append({ kind: "error", text: error instanceof Error ? error.message : String(error) });
|
|
1045
855
|
}
|
|
1046
856
|
finally {
|
|
1047
|
-
if (activePromptRunRef.current === run)
|
|
1048
|
-
activePromptRunRef.current = undefined;
|
|
1049
|
-
if (runtime.engine !== engine)
|
|
1050
|
-
return;
|
|
1051
857
|
if (activeAbortController.current === abortController)
|
|
1052
858
|
activeAbortController.current = undefined;
|
|
1053
859
|
interruptArmed.current = false;
|
|
@@ -1074,7 +880,6 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
1074
880
|
}
|
|
1075
881
|
};
|
|
1076
882
|
useEffect(() => {
|
|
1077
|
-
setTipIndex(initialTipIndex(runtime.engine.snapshot().session?.sessionId ?? process.cwd()));
|
|
1078
883
|
setLines(initialLines(runtime, lineId));
|
|
1079
884
|
assistantLineId.current = undefined;
|
|
1080
885
|
thinkingLineId.current = undefined;
|
|
@@ -1083,26 +888,16 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
1083
888
|
clearPendingToolResultTimers();
|
|
1084
889
|
setStatus(initialStatus(runtime));
|
|
1085
890
|
setSessionsBrowser(undefined);
|
|
1086
|
-
setLoginFormState(undefined);
|
|
1087
891
|
setQueuedPromptState(undefined);
|
|
1088
892
|
setPromptState("", 0);
|
|
1089
893
|
}, [runtime]);
|
|
1090
|
-
useEffect(() => {
|
|
1091
|
-
if (initialCommandLine === undefined)
|
|
1092
|
-
return;
|
|
1093
|
-
void submitLine(initialCommandLine);
|
|
1094
|
-
}, []);
|
|
1095
894
|
const terminalSize = useTerminalSize();
|
|
1096
895
|
const width = terminalSize.columns;
|
|
1097
896
|
const inputLockedByQueue = busy && queuedInput !== undefined;
|
|
1098
897
|
const prompt = promptPrefix(busy);
|
|
1099
|
-
const
|
|
1100
|
-
const
|
|
1101
|
-
const
|
|
1102
|
-
const promptDisplayCursor = cursor;
|
|
1103
|
-
const promptLayoutText = activePlaceholder ? ` ${activePlaceholder}` : promptDisplayText;
|
|
1104
|
-
const promptLayoutCursor = activePlaceholder ? 0 : promptDisplayCursor;
|
|
1105
|
-
const slashCompletions = inputLockedByQueue || (input.length === 0 && promptPlaceholder !== undefined) || loginForm ? [] : slashCommandCompletions(input, cursor);
|
|
898
|
+
const promptDisplayText = input.length === 0 && promptPlaceholder ? promptPlaceholder : input;
|
|
899
|
+
const promptDisplayCursor = input.length === 0 && promptPlaceholder ? promptPlaceholder.length : cursor;
|
|
900
|
+
const slashCompletions = inputLockedByQueue || promptPlaceholder ? [] : slashCommandCompletions(input, cursor);
|
|
1106
901
|
const visibleSlashCompletionCount = slashCompletions.length;
|
|
1107
902
|
const selectedSlashCompletionIndex = visibleSlashCompletionCount === 0
|
|
1108
903
|
? 0
|
|
@@ -1110,7 +905,7 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
1110
905
|
if (selectedSlashCompletionIndex !== slashCompletionIndexRef.current) {
|
|
1111
906
|
slashCompletionIndexRef.current = selectedSlashCompletionIndex;
|
|
1112
907
|
}
|
|
1113
|
-
const promptHeight = promptTextView(
|
|
908
|
+
const promptHeight = promptTextView(promptDisplayText, promptDisplayCursor, width, prompt).length + slashCompletionViewHeight(slashCompletions) + (queuedInput !== undefined ? QUEUED_INPUT_RENDER_ROWS : 0) + (pasteStatus ? 1 : 0);
|
|
1114
909
|
const firstDynamicLineIndex = lines.findIndex((line) => lineNeedsDynamicRender(line, messageContentWidth(width)));
|
|
1115
910
|
const staticLines = firstDynamicLineIndex === -1 ? lines : lines.slice(0, firstDynamicLineIndex);
|
|
1116
911
|
const dynamicLines = firstDynamicLineIndex === -1 ? [] : lines.slice(firstDynamicLineIndex);
|
|
@@ -1118,10 +913,9 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
1118
913
|
const blockIndex = staticLines.length + i;
|
|
1119
914
|
return sum + (blockIndex > 0 ? MESSAGE_BLOCK_SPACING_LINES : 0);
|
|
1120
915
|
}, 0);
|
|
1121
|
-
const statusRenderRows = STATUS_BAR_RENDER_ROWS +
|
|
916
|
+
const statusRenderRows = STATUS_BAR_RENDER_ROWS + (backgroundTaskCount > 0 ? BACKGROUND_TASK_STATUS_RENDER_ROWS : 0);
|
|
1122
917
|
const sessionsBrowserHeight = sessionsBrowser ? sessionsBrowserViewHeight(sessionsBrowser) : 0;
|
|
1123
|
-
const
|
|
1124
|
-
const liveViewportLines = Math.max(MIN_LIVE_VIEWPORT_LINES, terminalSize.rows - promptHeight - statusRenderRows - sessionsBrowserHeight - loginFormHeight - dynamicMarginOverhead - 1);
|
|
918
|
+
const liveViewportLines = Math.max(MIN_LIVE_VIEWPORT_LINES, terminalSize.rows - promptHeight - statusRenderRows - sessionsBrowserHeight - dynamicMarginOverhead - 1);
|
|
1125
919
|
useInput((value, key) => {
|
|
1126
920
|
if (isTerminalFocusInSequence(value)) {
|
|
1127
921
|
terminalFocusedRef.current = true;
|
|
@@ -1169,10 +963,6 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
1169
963
|
restoreQueuedPromptToEditor();
|
|
1170
964
|
return;
|
|
1171
965
|
}
|
|
1172
|
-
if (loginFormRef.current) {
|
|
1173
|
-
handleLoginFormInput(value, key, loginFormRef.current, setLoginFormState, runtime, append, setStatus);
|
|
1174
|
-
return;
|
|
1175
|
-
}
|
|
1176
966
|
if (sessionsBrowser) {
|
|
1177
967
|
if (key.escape) {
|
|
1178
968
|
setSessionsBrowser(undefined);
|
|
@@ -1198,17 +988,10 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
1198
988
|
const selected = sessionsBrowser.sessions[sessionAbsoluteIndex(sessionsBrowser)];
|
|
1199
989
|
if (selected) {
|
|
1200
990
|
setSessionsBrowser(undefined);
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
}
|
|
1205
|
-
else {
|
|
1206
|
-
detachRunningForeground("session switch");
|
|
1207
|
-
void handleResumeCommand(selected.sessionId, runtime, (line) => append(line)).then((result) => {
|
|
1208
|
-
if (result)
|
|
1209
|
-
resumeSnapshot(result.snapshot, result.metrics);
|
|
1210
|
-
});
|
|
1211
|
-
}
|
|
991
|
+
void handleResumeCommand(selected.sessionId, runtime, (line) => append(line)).then((resumed) => {
|
|
992
|
+
if (resumed)
|
|
993
|
+
resumeSnapshot(resumed);
|
|
994
|
+
});
|
|
1212
995
|
}
|
|
1213
996
|
return;
|
|
1214
997
|
}
|
|
@@ -1240,10 +1023,6 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
1240
1023
|
if (key.backspace || key.delete) {
|
|
1241
1024
|
const currentText = inputRef.current;
|
|
1242
1025
|
const currentCursor = cursorRef.current;
|
|
1243
|
-
if (currentText.length === 0) {
|
|
1244
|
-
setTipIndex((current) => current + 1);
|
|
1245
|
-
return;
|
|
1246
|
-
}
|
|
1247
1026
|
if (currentCursor > 0) {
|
|
1248
1027
|
setPromptState(`${currentText.slice(0, currentCursor - 1)}${currentText.slice(currentCursor)}`, currentCursor - 1);
|
|
1249
1028
|
}
|
|
@@ -1255,10 +1034,6 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
1255
1034
|
setSlashCompletionSelection((slashCompletionIndexRef.current + completionCount - SLASH_COMPLETION_PAGE_SIZE) % completionCount);
|
|
1256
1035
|
return;
|
|
1257
1036
|
}
|
|
1258
|
-
if (inputRef.current.length === 0) {
|
|
1259
|
-
setTipIndex((current) => current - 1);
|
|
1260
|
-
return;
|
|
1261
|
-
}
|
|
1262
1037
|
setPromptState(inputRef.current, cursorRef.current - 1);
|
|
1263
1038
|
return;
|
|
1264
1039
|
}
|
|
@@ -1268,32 +1043,18 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
1268
1043
|
setSlashCompletionSelection((slashCompletionIndexRef.current + SLASH_COMPLETION_PAGE_SIZE) % completionCount);
|
|
1269
1044
|
return;
|
|
1270
1045
|
}
|
|
1271
|
-
if (inputRef.current.length === 0) {
|
|
1272
|
-
setTipIndex((current) => current + 1);
|
|
1273
|
-
return;
|
|
1274
|
-
}
|
|
1275
1046
|
setPromptState(inputRef.current, cursorRef.current + 1);
|
|
1276
1047
|
return;
|
|
1277
1048
|
}
|
|
1278
1049
|
if (key.home) {
|
|
1279
|
-
|
|
1280
|
-
setTipIndex(0);
|
|
1281
|
-
else
|
|
1282
|
-
setPromptState(inputRef.current, 0);
|
|
1050
|
+
setPromptState(inputRef.current, 0);
|
|
1283
1051
|
return;
|
|
1284
1052
|
}
|
|
1285
1053
|
if (key.end) {
|
|
1286
|
-
|
|
1287
|
-
setTipIndex((current) => current + 1);
|
|
1288
|
-
else
|
|
1289
|
-
setPromptState(inputRef.current, inputRef.current.length);
|
|
1054
|
+
setPromptState(inputRef.current, inputRef.current.length);
|
|
1290
1055
|
return;
|
|
1291
1056
|
}
|
|
1292
1057
|
if (key.upArrow) {
|
|
1293
|
-
if (inputRef.current.length === 0 && history.current.length === 0) {
|
|
1294
|
-
setTipIndex((current) => current - 1);
|
|
1295
|
-
return;
|
|
1296
|
-
}
|
|
1297
1058
|
const completionCount = slashCompletionSelectableCount(inputRef.current, cursorRef.current);
|
|
1298
1059
|
if (completionCount > 0) {
|
|
1299
1060
|
setSlashCompletionSelection((slashCompletionIndexRef.current + completionCount - 1) % completionCount);
|
|
@@ -1307,10 +1068,6 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
1307
1068
|
return;
|
|
1308
1069
|
}
|
|
1309
1070
|
if (key.downArrow) {
|
|
1310
|
-
if (inputRef.current.length === 0 && historyIndexRef.current === undefined) {
|
|
1311
|
-
setTipIndex((current) => current + 1);
|
|
1312
|
-
return;
|
|
1313
|
-
}
|
|
1314
1071
|
const completionCount = slashCompletionSelectableCount(inputRef.current, cursorRef.current);
|
|
1315
1072
|
if (completionCount > 0) {
|
|
1316
1073
|
setSlashCompletionSelection((slashCompletionIndexRef.current + 1) % completionCount);
|
|
@@ -1332,10 +1089,6 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
1332
1089
|
}
|
|
1333
1090
|
if (key.tab) {
|
|
1334
1091
|
const currentText = inputRef.current;
|
|
1335
|
-
if (currentText.length === 0) {
|
|
1336
|
-
setTipIndex((current) => current + 1);
|
|
1337
|
-
return;
|
|
1338
|
-
}
|
|
1339
1092
|
const currentCursor = cursorRef.current;
|
|
1340
1093
|
const completions = slashCommandCompletions(currentText, currentCursor);
|
|
1341
1094
|
const completion = completions[Math.min(slashCompletionIndexRef.current, completions.length - 1)];
|
|
@@ -1347,10 +1100,9 @@ function InkRepl({ runtime, initialCommandLine }) {
|
|
|
1347
1100
|
}
|
|
1348
1101
|
if (value && !key.ctrl && !key.meta) {
|
|
1349
1102
|
insertAtCursor(value);
|
|
1350
|
-
return;
|
|
1351
1103
|
}
|
|
1352
1104
|
});
|
|
1353
|
-
return e(Box, { flexDirection: "column" }, e((Static), { items: staticLines, children: (line, index) => e(MessageBlock, { key: line.id, line, width, blockIndex: index }) }), e(MessageList, { lines: dynamicLines, width, liveMaxLines: liveViewportLines, lineIndexOffset: staticLines.length, onMarkdownRenderComplete: markLineRendered }), sessionsBrowser ? e(SessionsBrowser, { state: sessionsBrowser, width }) : null,
|
|
1105
|
+
return e(Box, { flexDirection: "column" }, e((Static), { items: staticLines, children: (line, index) => e(MessageBlock, { key: line.id, line, width, blockIndex: index }) }), e(MessageList, { lines: dynamicLines, width, liveMaxLines: liveViewportLines, lineIndexOffset: staticLines.length, onMarkdownRenderComplete: markLineRendered }), sessionsBrowser ? e(SessionsBrowser, { state: sessionsBrowser, width }) : null, e(StatusBar, { status, animationTick, width }), backgroundTaskCount > 0 ? e(BackgroundTaskStatusLine, { count: backgroundTaskCount, width }) : null, pasteStatus ? e(PasteStatusLine, { text: pasteStatus, width }) : null, queuedInput !== undefined ? e(QueuedInputLine, { text: queuedInput, width }) : null, e(PromptLine, { text: promptDisplayText, cursor: promptDisplayCursor, busy, locked: inputLockedByQueue, placeholder: input.length === 0 && promptPlaceholder !== undefined, width, prompt, slashCompletions, selectedSlashCompletionIndex, attachments }));
|
|
1354
1106
|
}
|
|
1355
1107
|
const MessageList = React.memo(function MessageList({ lines, width, liveMaxLines, lineIndexOffset = 0, onMarkdownRenderComplete }) {
|
|
1356
1108
|
const contentWidth = messageContentWidth(width);
|
|
@@ -1376,17 +1128,9 @@ function MessageLine({ line, width, contentWidth = messageContentWidth(width), t
|
|
|
1376
1128
|
const display = displayWindowForLine(line, summaryWidth, line.live ? liveMaxLines : undefined);
|
|
1377
1129
|
return e(Box, { flexDirection: "row" }, useRoleMarker ? e(Text, { color: markerColorForKind(line.kind) }, messageRoleMarker(line.kind)) : null, e(Box, { flexDirection: "column", width: summaryWidth }, ...renderDisplayText(line, summaryWidth, display.maxLines, display.skipTop)));
|
|
1378
1130
|
}
|
|
1379
|
-
const
|
|
1380
|
-
const
|
|
1381
|
-
|
|
1382
|
-
const display = displayWindowForLine(line, lineWidth, line.live || clipPendingMarkdown ? liveMaxLines : undefined);
|
|
1383
|
-
const contentNodes = [];
|
|
1384
|
-
if (line.title)
|
|
1385
|
-
contentNodes.push(renderBlockTitle(line));
|
|
1386
|
-
if (line.bodyTitle)
|
|
1387
|
-
contentNodes.push(e(Text, { key: `body-title-${line.id}`, bold: true }, line.bodyTitle));
|
|
1388
|
-
contentNodes.push(...renderDisplayText(line, lineWidth, display.maxLines, display.skipTop, onMarkdownRenderComplete));
|
|
1389
|
-
return e(Box, { flexDirection: "row" }, useRoleMarker ? e(Text, { color: markerColorForKind(line.kind) }, messageRoleMarker(line.kind)) : null, e(Box, { flexDirection: "column", width: lineWidth }, ...contentNodes));
|
|
1131
|
+
const clipPendingMarkdown = !line.live && onMarkdownRenderComplete !== undefined && lineNeedsDynamicRender(line, contentWidth);
|
|
1132
|
+
const display = displayWindowForLine(line, contentWidth, line.live || clipPendingMarkdown ? liveMaxLines : undefined);
|
|
1133
|
+
return e(Box, { flexDirection: "row" }, e(Text, { color: markerColorForKind(line.kind) }, messageRoleMarker(line.kind)), e(Box, { flexDirection: "column", width: contentWidth }, ...renderDisplayText(line, contentWidth, display.maxLines, display.skipTop, onMarkdownRenderComplete)));
|
|
1390
1134
|
}
|
|
1391
1135
|
function displayWindowForLine(line, width, maxLines) {
|
|
1392
1136
|
if (maxLines === undefined)
|
|
@@ -1456,21 +1200,12 @@ function summaryTitle(line) {
|
|
|
1456
1200
|
function summaryUsesRoleMarker(line) {
|
|
1457
1201
|
return line.previewStyle === "summary" && (line.kind === "system" || line.kind === "meta");
|
|
1458
1202
|
}
|
|
1459
|
-
function titleProvidesToolMarker(line) {
|
|
1460
|
-
return line.kind === "tool" && !!line.title && (line.title.startsWith("◇ ") || line.title.startsWith("◆ "));
|
|
1461
|
-
}
|
|
1462
1203
|
function titleStatusMarker(status) {
|
|
1463
1204
|
return status === "success" ? "✓" : "✗";
|
|
1464
1205
|
}
|
|
1465
1206
|
function titleStatusColor(status) {
|
|
1466
1207
|
return status === "success" ? "green" : "red";
|
|
1467
1208
|
}
|
|
1468
|
-
function renderBlockTitle(line) {
|
|
1469
|
-
const title = line.title ?? titleForKind(line.kind);
|
|
1470
|
-
if (!line.titleStatus)
|
|
1471
|
-
return e(Text, { key: `title-${line.id}`, color: colorForKind(line.kind), bold: true }, title);
|
|
1472
|
-
return e(Text, { key: `title-${line.id}`, color: colorForKind(line.kind), bold: true }, `${title} `, e(Text, { color: titleStatusColor(line.titleStatus), bold: true }, titleStatusMarker(line.titleStatus)));
|
|
1473
|
-
}
|
|
1474
1209
|
function renderSummaryBlock(line, width, maxLines, skipTop = 0) {
|
|
1475
1210
|
const allPreviewLines = renderSummaryLines(line, width);
|
|
1476
1211
|
const preview = clipStrings(allPreviewLines, maxLines, skipTop);
|
|
@@ -1702,27 +1437,10 @@ function StatusBar({ status, animationTick, width: terminalWidth }) {
|
|
|
1702
1437
|
const segments = fitStatusSegments(renderCompactStatusSegments(status, animationTick, width, inputTokens, outputTokens, displayPhase), width);
|
|
1703
1438
|
return e(Box, { marginTop: 1, width, height: 1, overflow: "hidden" }, ...segments.map((segment, index) => e(Text, { key: index, color: segment.color ?? "gray", bold: segment.bold ?? false }, segment.text)));
|
|
1704
1439
|
}
|
|
1705
|
-
function
|
|
1706
|
-
if (taskCount <= 0)
|
|
1707
|
-
return 0;
|
|
1708
|
-
return 1 + Math.min(taskCount, 2);
|
|
1709
|
-
}
|
|
1710
|
-
function BackgroundTaskStatusLine({ tasks, width: terminalWidth }) {
|
|
1440
|
+
function BackgroundTaskStatusLine({ count, width: terminalWidth }) {
|
|
1711
1441
|
const width = statusBarWidth(terminalWidth);
|
|
1712
|
-
const
|
|
1713
|
-
|
|
1714
|
-
return e(Box, { flexDirection: "column", width, overflow: "hidden" }, e(Text, { color: "yellow" }, fitToWidth(summary, width)), ...detailTasks.map((task) => e(Text, { key: task.taskId, color: "yellow" }, fitToWidth(` ${task.type}:${truncateMiddle(task.description || task.agentId || task.taskId, Math.max(12, width - 30))} · ${task.status} · ${formatElapsed(Date.now() - new Date(task.createdAt).getTime())}`, width))));
|
|
1715
|
-
}
|
|
1716
|
-
function formatElapsed(ms) {
|
|
1717
|
-
const seconds = Math.max(0, Math.floor(ms / 1000));
|
|
1718
|
-
if (seconds < 60)
|
|
1719
|
-
return `${seconds}s`;
|
|
1720
|
-
const minutes = Math.floor(seconds / 60);
|
|
1721
|
-
const remainder = seconds % 60;
|
|
1722
|
-
if (minutes < 60)
|
|
1723
|
-
return `${minutes}m${remainder.toString().padStart(2, "0")}s`;
|
|
1724
|
-
const hours = Math.floor(minutes / 60);
|
|
1725
|
-
return `${hours}h${(minutes % 60).toString().padStart(2, "0")}m`;
|
|
1442
|
+
const text = count <= 3 ? "◇".repeat(Math.max(0, count)) : `◇×${count}`;
|
|
1443
|
+
return e(Box, { width, height: 1, overflow: "hidden" }, e(Text, { color: "yellow" }, fitToWidth(text, width)));
|
|
1726
1444
|
}
|
|
1727
1445
|
function renderCompactStatusSegments(status, animationTick, width, inputTokens, outputTokens, displayPhase = status.phase) {
|
|
1728
1446
|
const phase = displayPhase;
|
|
@@ -1733,7 +1451,7 @@ function renderCompactStatusSegments(status, animationTick, width, inputTokens,
|
|
|
1733
1451
|
const context = renderContextParts(status.metrics);
|
|
1734
1452
|
const fixedText = [
|
|
1735
1453
|
phaseText,
|
|
1736
|
-
context.percent
|
|
1454
|
+
`ctx ${context.used} / ${context.limit} (${context.percent})`,
|
|
1737
1455
|
`↑ ${inputValue}`,
|
|
1738
1456
|
`↓ ${outputValue}`,
|
|
1739
1457
|
].join(STATUS_SEPARATOR);
|
|
@@ -1750,7 +1468,9 @@ function renderCompactStatusSegments(status, animationTick, width, inputTokens,
|
|
|
1750
1468
|
statusDividerSegment(),
|
|
1751
1469
|
{ text: model },
|
|
1752
1470
|
statusDividerSegment(),
|
|
1753
|
-
|
|
1471
|
+
statusLabelSegment("ctx"),
|
|
1472
|
+
{ text: ` ${context.used} / ${context.limit}` },
|
|
1473
|
+
{ text: ` (${context.percent})`, color: contextColor(status.metrics) },
|
|
1754
1474
|
statusDividerSegment(),
|
|
1755
1475
|
statusLabelSegment("↑", tokenInputColor),
|
|
1756
1476
|
{ text: ` ${inputValue}` },
|
|
@@ -1896,16 +1616,10 @@ function selectedSlashCommandCompletion(text, cursor, selectedIndex) {
|
|
|
1896
1616
|
return undefined;
|
|
1897
1617
|
return completions[Math.max(0, Math.min(selectedIndex, completions.length - 1))];
|
|
1898
1618
|
}
|
|
1899
|
-
function PromptLine({ text, cursor, busy, locked, placeholder = false,
|
|
1900
|
-
const
|
|
1901
|
-
const displayCursor = text.length === 0 && ghostText ? 0 : cursor;
|
|
1902
|
-
const visualLines = promptTextView(displayText, displayCursor, width, prompt);
|
|
1619
|
+
function PromptLine({ text, cursor, busy, locked, placeholder = false, width, prompt, slashCompletions, selectedSlashCompletionIndex, attachments }) {
|
|
1620
|
+
const visualLines = promptTextView(text, cursor, width, prompt);
|
|
1903
1621
|
const inputColor = placeholder ? "gray" : (!locked && isValidReplCommandLine(text) ? "cyan" : undefined);
|
|
1904
|
-
return e(Box, { flexDirection: "column" }, ...visualLines.map((line, index) => {
|
|
1905
|
-
const isGhostLine = text.length === 0 && ghostText !== undefined;
|
|
1906
|
-
const afterColor = isGhostLine ? "gray" : inputColor;
|
|
1907
|
-
return e(Box, { key: `prompt-${index}`, height: 1, overflow: "hidden" }, e(Text, { color: locked ? "gray" : "cyan" }, index === 0 ? prompt : " ".repeat(prompt.length)), ...renderPromptPart(line.before, inputColor, attachments, `prompt-${index}-before`), e(Text, { key: `prompt-${index}-cursor`, inverse: true, color: inputColor }, line.selected), ...renderPromptPart(line.after, afterColor, attachments, `prompt-${index}-after`));
|
|
1908
|
-
}), ...SlashCompletionLines({ completions: slashCompletions, width, prompt, selectedIndex: selectedSlashCompletionIndex }));
|
|
1622
|
+
return e(Box, { flexDirection: "column" }, ...visualLines.map((line, index) => e(Box, { key: `prompt-${index}`, height: 1, overflow: "hidden" }, e(Text, { color: locked ? "gray" : "cyan" }, index === 0 ? prompt : " ".repeat(prompt.length)), ...renderPromptPart(line.before, inputColor, attachments, `prompt-${index}-before`), e(Text, { key: `prompt-${index}-cursor`, inverse: true, color: inputColor }, line.selected), ...renderPromptPart(line.after, inputColor, attachments, `prompt-${index}-after`))), ...SlashCompletionLines({ completions: slashCompletions, width, prompt, selectedIndex: selectedSlashCompletionIndex }));
|
|
1909
1623
|
}
|
|
1910
1624
|
function PasteStatusLine({ text, width: terminalWidth }) {
|
|
1911
1625
|
const width = statusBarWidth(terminalWidth);
|
|
@@ -1964,41 +1678,17 @@ function SlashCompletionLines({ completions, width, prompt, selectedIndex }) {
|
|
|
1964
1678
|
e(Text, { key: "slash-completion-footer", color: "gray" }, fitToWidth(footer, contentWidth)),
|
|
1965
1679
|
].map((line, index) => e(Box, { key: `slash-completion-line-${index}`, height: 1, overflow: "hidden" }, e(Text, { color: "gray" }, " ".repeat(prompt.length)), line));
|
|
1966
1680
|
}
|
|
1967
|
-
|
|
1681
|
+
function handleModelCommand(command, runtime) {
|
|
1968
1682
|
const current = runtime.engine.getModelSettings();
|
|
1969
1683
|
const nextModel = command.model ?? current.model;
|
|
1970
1684
|
const validationError = validateModelReasoningArgument(nextModel, command.reasoning);
|
|
1971
1685
|
if (validationError)
|
|
1972
1686
|
return { kind: "error", text: validationError };
|
|
1973
1687
|
const reasoningUpdate = resolveModelReasoningUpdate(command.reasoning, current.reasoning, nextModel, command.model !== undefined);
|
|
1974
|
-
|
|
1975
|
-
if (changed) {
|
|
1688
|
+
if (command.model !== undefined || command.reasoning !== undefined) {
|
|
1976
1689
|
runtime.engine.setModel(nextModel, reasoningUpdate.reasoning, reasoningUpdate.update);
|
|
1977
|
-
try {
|
|
1978
|
-
const { providerChanged } = await persistModelCommandSettings(runtime, command, reasoningUpdate);
|
|
1979
|
-
if (providerChanged) {
|
|
1980
|
-
const config = readModelProviderConfig(process.env);
|
|
1981
|
-
if (config) {
|
|
1982
|
-
const innerGateway = createModelGatewayFromConfig(config);
|
|
1983
|
-
runtime.modelGateway.setInner(innerGateway);
|
|
1984
|
-
runtime.agentRuntime.modelGateway = runtime.modelGateway;
|
|
1985
|
-
runtime.engine.setModelProvider({
|
|
1986
|
-
modelGateway: runtime.modelGateway,
|
|
1987
|
-
model: config.model,
|
|
1988
|
-
fallbackModel: config.fallbackModel,
|
|
1989
|
-
reasoning: config.defaultReasoning,
|
|
1990
|
-
});
|
|
1991
|
-
syncImageGenerationTool(runtime, config.provider);
|
|
1992
|
-
runtime.defaultReasoning = config.defaultReasoning;
|
|
1993
|
-
}
|
|
1994
|
-
}
|
|
1995
|
-
}
|
|
1996
|
-
catch (error) {
|
|
1997
|
-
return { kind: "error", text: `Model settings changed for this session, but saving to ${runtime.envPath} failed: ${error instanceof Error ? error.message : String(error)}` };
|
|
1998
|
-
}
|
|
1999
1690
|
}
|
|
2000
|
-
|
|
2001
|
-
return systemLine(changed ? `${settings}\nSaved to ${runtime.envPath}` : settings);
|
|
1691
|
+
return systemLine(formatModelSettings(runtime.engine.getModelSettings(), runtime.defaultReasoning));
|
|
2002
1692
|
}
|
|
2003
1693
|
function resolveModelReasoningUpdate(value, current, modelId, modelChanged) {
|
|
2004
1694
|
if (value === "off")
|
|
@@ -2012,62 +1702,6 @@ function resolveModelReasoningUpdate(value, current, modelId, modelChanged) {
|
|
|
2012
1702
|
}
|
|
2013
1703
|
return { reasoning: current, update: false };
|
|
2014
1704
|
}
|
|
2015
|
-
async function persistModelCommandSettings(runtime, command, reasoningUpdate) {
|
|
2016
|
-
const currentProvider = currentModelProvider();
|
|
2017
|
-
let targetProvider = currentProvider;
|
|
2018
|
-
const updates = {};
|
|
2019
|
-
if (command.model !== undefined) {
|
|
2020
|
-
const metadata = findModelMetadata(command.model);
|
|
2021
|
-
if (metadata) {
|
|
2022
|
-
const modelProvider = parseLoginProvider(metadata.provider);
|
|
2023
|
-
if (modelProvider) {
|
|
2024
|
-
targetProvider = modelProvider;
|
|
2025
|
-
if (targetProvider !== currentProvider)
|
|
2026
|
-
updates.MODEL_PROVIDER = targetProvider;
|
|
2027
|
-
}
|
|
2028
|
-
}
|
|
2029
|
-
updates[modelEnvKeyForProvider(targetProvider)] = command.model.trim() || undefined;
|
|
2030
|
-
}
|
|
2031
|
-
if (command.reasoning !== undefined || reasoningUpdate.update) {
|
|
2032
|
-
updates.MODEL_REASONING_EFFORT = envValueForReasoning(reasoningUpdate.reasoning);
|
|
2033
|
-
updates.MODEL_REASONING_SUMMARY = undefined;
|
|
2034
|
-
}
|
|
2035
|
-
if (Object.keys(updates).length === 0)
|
|
2036
|
-
return { providerChanged: false };
|
|
2037
|
-
await writeEnvUpdates(runtime.envPath, updates);
|
|
2038
|
-
applyEnvUpdatesToProcess(updates);
|
|
2039
|
-
runtime.defaultReasoning = reasoningUpdate.update ? reasoningUpdate.reasoning : runtime.defaultReasoning;
|
|
2040
|
-
return { providerChanged: targetProvider !== currentProvider };
|
|
2041
|
-
}
|
|
2042
|
-
function currentModelProvider() {
|
|
2043
|
-
return parseLoginProvider(process.env.MODEL_PROVIDER) ?? "openai";
|
|
2044
|
-
}
|
|
2045
|
-
function modelEnvKeyForProvider(provider) {
|
|
2046
|
-
if (provider === "deepseek")
|
|
2047
|
-
return "DEEPSEEK_MODEL";
|
|
2048
|
-
if (provider === "kimi")
|
|
2049
|
-
return "KIMI_MODEL";
|
|
2050
|
-
return "OPENAI_MODEL";
|
|
2051
|
-
}
|
|
2052
|
-
function envValueForReasoning(reasoning) {
|
|
2053
|
-
if (reasoning === null)
|
|
2054
|
-
return "off";
|
|
2055
|
-
return reasoning?.effort;
|
|
2056
|
-
}
|
|
2057
|
-
async function writeEnvUpdates(envPath, updates, removeKeys = []) {
|
|
2058
|
-
await fs.mkdir(path.dirname(envPath), { recursive: true });
|
|
2059
|
-
const existing = existsSync(envPath) ? readFileSync(envPath, "utf8") : "";
|
|
2060
|
-
const next = updateEnvContent(existing, updates, removeKeys);
|
|
2061
|
-
await fs.writeFile(envPath, next, "utf8");
|
|
2062
|
-
}
|
|
2063
|
-
function applyEnvUpdatesToProcess(updates) {
|
|
2064
|
-
for (const [key, value] of Object.entries(updates)) {
|
|
2065
|
-
if (value === undefined)
|
|
2066
|
-
delete process.env[key];
|
|
2067
|
-
else
|
|
2068
|
-
process.env[key] = value;
|
|
2069
|
-
}
|
|
2070
|
-
}
|
|
2071
1705
|
function validateModelReasoningArgument(modelId, reasoning) {
|
|
2072
1706
|
if (!reasoning || reasoning === "default" || reasoning === "off")
|
|
2073
1707
|
return undefined;
|
|
@@ -2290,38 +1924,18 @@ function reduceStatus(status, event) {
|
|
|
2290
1924
|
}
|
|
2291
1925
|
return status;
|
|
2292
1926
|
}
|
|
2293
|
-
async function handleSessionsCommand(runtime,
|
|
1927
|
+
async function handleSessionsCommand(runtime, setBrowser, append) {
|
|
2294
1928
|
const sessions = await runtime.engine.listSessions(Number.POSITIVE_INFINITY);
|
|
2295
1929
|
if (sessions.length === 0) {
|
|
2296
1930
|
setBrowser(undefined);
|
|
2297
1931
|
append(systemLine("No saved sessions found."));
|
|
2298
1932
|
return;
|
|
2299
1933
|
}
|
|
2300
|
-
setBrowser({ sessions,
|
|
2301
|
-
}
|
|
2302
|
-
async function handleExportCommand(command, runtime) {
|
|
2303
|
-
const snapshot = runtime.engine.snapshot();
|
|
2304
|
-
if (!snapshot.session)
|
|
2305
|
-
throw new Error("session transcripts are disabled; cannot export current session");
|
|
2306
|
-
const promptSnapshot = await runtime.engine.promptExportSnapshot();
|
|
2307
|
-
const result = await writeSessionMarkdownExport({
|
|
2308
|
-
outputPath: command.path,
|
|
2309
|
-
session: snapshot.session,
|
|
2310
|
-
agentId: snapshot.agentId,
|
|
2311
|
-
promptSnapshot,
|
|
2312
|
-
engineSnapshot: { ...snapshot, communicationLog: runtime.communicationLogger.snapshot(), usage: runtime.usage.snapshot() },
|
|
2313
|
-
});
|
|
2314
|
-
return systemLine(`Exported current session to ${result.outputPath}\nEntries: ${result.entries}\nMessages: ${result.messages}\nBytes: ${result.bytes}`);
|
|
1934
|
+
setBrowser({ sessions, pageSize: SESSIONS_DEFAULT_PAGE_SIZE, pageIndex: 0, selectedIndex: 0 });
|
|
2315
1935
|
}
|
|
2316
1936
|
async function handleResumeCommand(sessionId, runtime, append) {
|
|
2317
1937
|
try {
|
|
2318
|
-
|
|
2319
|
-
await runtime.engine.initialize();
|
|
2320
|
-
const snapshot = runtime.engine.snapshot().session;
|
|
2321
|
-
if (!snapshot)
|
|
2322
|
-
throw new Error("session transcripts are disabled");
|
|
2323
|
-
const metrics = await runtime.engine.contextMetrics();
|
|
2324
|
-
return { snapshot, metrics };
|
|
1938
|
+
return await runtime.engine.resumeSession(sessionId);
|
|
2325
1939
|
}
|
|
2326
1940
|
catch (error) {
|
|
2327
1941
|
append({ kind: "error", text: error instanceof Error ? error.message : String(error) });
|
|
@@ -2346,7 +1960,6 @@ async function handleDeleteSessionCommand(sessionId, current, runtime, setBrowse
|
|
|
2346
1960
|
setBrowser({
|
|
2347
1961
|
...current,
|
|
2348
1962
|
sessions: nextSessions,
|
|
2349
|
-
runningSessionIds: current.runningSessionIds.filter((id) => id !== sessionId),
|
|
2350
1963
|
pageIndex,
|
|
2351
1964
|
selectedIndex: Math.min(current.selectedIndex, Math.max(0, pageLength - 1)),
|
|
2352
1965
|
});
|
|
@@ -2363,11 +1976,11 @@ function initialLines(runtime, lineId) {
|
|
|
2363
1976
|
? ` Session: ${session.sessionId}${session.resumedMessages > 0 ? ` (${session.resumedMessages} resumed messages)` : ""}.`
|
|
2364
1977
|
: "";
|
|
2365
1978
|
const lines = [
|
|
2366
|
-
{ id: 0, kind: "system", title: "System", text: `Interactive UI enabled. Type /help for commands.${suffix}
|
|
1979
|
+
{ id: 0, kind: "system", title: "System", text: `Interactive UI enabled. Type /help for commands.${suffix}`, previewStyle: "summary" },
|
|
2367
1980
|
];
|
|
2368
1981
|
lineId.current = 0;
|
|
2369
1982
|
if (runtime.envNotice)
|
|
2370
|
-
lines.push({ id: ++lineId.current, kind: "system", title: "Config", text: runtime.envNotice,
|
|
1983
|
+
lines.push({ id: ++lineId.current, kind: "system", title: "Config", text: runtime.envNotice, previewStyle: "summary" });
|
|
2371
1984
|
for (const line of restoredHistoryLines(runtime))
|
|
2372
1985
|
lines.push({ id: ++lineId.current, ...line });
|
|
2373
1986
|
return lines;
|
|
@@ -2386,71 +1999,6 @@ function restoredHistoryLines(runtime) {
|
|
|
2386
1999
|
}
|
|
2387
2000
|
return lines;
|
|
2388
2001
|
}
|
|
2389
|
-
const LOGIN_PROVIDERS = ["openai", "deepseek", "kimi"];
|
|
2390
|
-
const SHARED_LOGIN_FIELDS = [
|
|
2391
|
-
{ key: "reasoningEffort", label: "Reasoning effort", envKey: "MODEL_REASONING_EFFORT", scope: "shared", options: ["", "off", "none", "minimal", "low", "medium", "high", "xhigh", "max"] },
|
|
2392
|
-
{ key: "reasoningSummary", label: "Reasoning summary", envKey: "MODEL_REASONING_SUMMARY", scope: "shared", options: ["", "auto", "concise", "detailed"] },
|
|
2393
|
-
{ key: "maxOutputTokens", label: "Max output tokens", envKey: "MODEL_MAX_OUTPUT_TOKENS", scope: "shared", placeholder: "800" },
|
|
2394
|
-
{ key: "timeoutMs", label: "Timeout ms", envKey: "MODEL_TIMEOUT_MS", scope: "shared", placeholder: "120000" },
|
|
2395
|
-
{ key: "streamIdleTimeoutMs", label: "Stream idle timeout ms", envKey: "MODEL_STREAM_IDLE_TIMEOUT_MS", scope: "shared", placeholder: "120000" },
|
|
2396
|
-
{ key: "maxRetries", label: "Max retries", envKey: "MODEL_MAX_RETRIES", scope: "shared", placeholder: "2" },
|
|
2397
|
-
];
|
|
2398
|
-
const LOGIN_FIELD_DEFINITIONS = {
|
|
2399
|
-
openai: [
|
|
2400
|
-
{ key: "apiKey", label: "API key", envKey: "OPENAI_API_KEY", scope: "provider", required: true, secret: true, placeholder: "sk-..." },
|
|
2401
|
-
{ key: "baseUrl", label: "Base URL", envKey: "OPENAI_BASE_URL", scope: "provider", placeholder: "https://api.openai.com" },
|
|
2402
|
-
{ key: "model", label: "Model", envKey: "OPENAI_MODEL", scope: "provider", required: true, placeholder: "gpt-5.5" },
|
|
2403
|
-
{ key: "fallbackModel", label: "Fallback model", envKey: "OPENAI_FALLBACK_MODEL", scope: "provider" },
|
|
2404
|
-
{ key: "endpoint", label: "Endpoint", envKey: "OPENAI_ENDPOINT", scope: "provider", placeholder: "auto", options: ["auto", "responses", "chat"] },
|
|
2405
|
-
...SHARED_LOGIN_FIELDS,
|
|
2406
|
-
],
|
|
2407
|
-
deepseek: [
|
|
2408
|
-
{ key: "apiKey", label: "API key", envKey: "DEEPSEEK_API_KEY", scope: "provider", required: true, secret: true, placeholder: "sk-..." },
|
|
2409
|
-
{ key: "baseUrl", label: "Base URL", envKey: "DEEPSEEK_BASE_URL", scope: "provider", placeholder: "https://api.deepseek.com" },
|
|
2410
|
-
{ key: "model", label: "Model", envKey: "DEEPSEEK_MODEL", scope: "provider", required: true, placeholder: "deepseek-chat" },
|
|
2411
|
-
{ key: "fallbackModel", label: "Fallback model", envKey: "DEEPSEEK_FALLBACK_MODEL", scope: "provider" },
|
|
2412
|
-
...SHARED_LOGIN_FIELDS,
|
|
2413
|
-
],
|
|
2414
|
-
kimi: [
|
|
2415
|
-
{ key: "apiKey", label: "API key", envKey: "KIMI_API_KEY", scope: "provider", required: true, secret: true, placeholder: "sk-..." },
|
|
2416
|
-
{ key: "baseUrl", label: "Base URL", envKey: "KIMI_BASE_URL", scope: "provider", placeholder: "https://api.moonshot.cn/v1" },
|
|
2417
|
-
{ key: "model", label: "Model", envKey: "KIMI_MODEL", scope: "provider", required: true, placeholder: "kimi-k2.6" },
|
|
2418
|
-
{ key: "fallbackModel", label: "Fallback model", envKey: "KIMI_FALLBACK_MODEL", scope: "provider" },
|
|
2419
|
-
...SHARED_LOGIN_FIELDS,
|
|
2420
|
-
],
|
|
2421
|
-
};
|
|
2422
|
-
const DEPRECATED_MODEL_ENV_KEYS = [
|
|
2423
|
-
"MODEL_API_KEY",
|
|
2424
|
-
"MODEL_BASE_URL",
|
|
2425
|
-
"MODEL_ID",
|
|
2426
|
-
"MODEL_FALLBACK_ID",
|
|
2427
|
-
"MODEL_ENDPOINT",
|
|
2428
|
-
"OPENAI_PROVIDER",
|
|
2429
|
-
"OPENAI_REASONING_EFFORT",
|
|
2430
|
-
"OPENAI_REASONING_SUMMARY",
|
|
2431
|
-
"OPENAI_MAX_OUTPUT_TOKENS",
|
|
2432
|
-
"OPENAI_TIMEOUT_MS",
|
|
2433
|
-
"OPENAI_STREAM_IDLE_TIMEOUT_MS",
|
|
2434
|
-
"OPENAI_MAX_RETRIES",
|
|
2435
|
-
"DEEPSEEK_REASONING_EFFORT",
|
|
2436
|
-
"DEEPSEEK_REASONING_SUMMARY",
|
|
2437
|
-
"DEEPSEEK_MAX_OUTPUT_TOKENS",
|
|
2438
|
-
"DEEPSEEK_TIMEOUT_MS",
|
|
2439
|
-
"DEEPSEEK_STREAM_IDLE_TIMEOUT_MS",
|
|
2440
|
-
"DEEPSEEK_MAX_RETRIES",
|
|
2441
|
-
"KIMI_REASONING_EFFORT",
|
|
2442
|
-
"KIMI_REASONING_SUMMARY",
|
|
2443
|
-
"KIMI_MAX_OUTPUT_TOKENS",
|
|
2444
|
-
"KIMI_TIMEOUT_MS",
|
|
2445
|
-
"KIMI_STREAM_IDLE_TIMEOUT_MS",
|
|
2446
|
-
"KIMI_MAX_RETRIES",
|
|
2447
|
-
"MOONSHOT_REASONING_EFFORT",
|
|
2448
|
-
"MOONSHOT_REASONING_SUMMARY",
|
|
2449
|
-
"MOONSHOT_MAX_OUTPUT_TOKENS",
|
|
2450
|
-
"MOONSHOT_TIMEOUT_MS",
|
|
2451
|
-
"MOONSHOT_STREAM_IDLE_TIMEOUT_MS",
|
|
2452
|
-
"MOONSHOT_MAX_RETRIES",
|
|
2453
|
-
];
|
|
2454
2002
|
function sessionsPageCount(state) {
|
|
2455
2003
|
return Math.max(1, Math.ceil(state.sessions.length / state.pageSize));
|
|
2456
2004
|
}
|
|
@@ -2493,363 +2041,23 @@ function SessionsBrowser({ state, width }) {
|
|
|
2493
2041
|
return e(Box, { flexDirection: "column", marginTop: 1 }, e(Text, { color: "cyan", bold: true }, fitToWidth(header, contentWidth)), ...pageItems.map((session, index) => {
|
|
2494
2042
|
const selected = index === state.selectedIndex;
|
|
2495
2043
|
const absoluteIndex = state.pageIndex * state.pageSize + index;
|
|
2496
|
-
const row = formatSessionBrowserRow(session, absoluteIndex, contentWidth
|
|
2044
|
+
const row = formatSessionBrowserRow(session, absoluteIndex, contentWidth);
|
|
2497
2045
|
return e(Text, { key: session.sessionId, color: "white" }, e(Text, {
|
|
2498
2046
|
color: selected ? "black" : "white",
|
|
2499
2047
|
backgroundColor: selected ? "cyan" : undefined,
|
|
2500
2048
|
}, row.numberPrefix), row.rest);
|
|
2501
2049
|
}), e(Text, { color: "gray" }, fitToWidth(footer, contentWidth)));
|
|
2502
2050
|
}
|
|
2503
|
-
function
|
|
2504
|
-
if (key.escape) {
|
|
2505
|
-
if (state.step === "fields")
|
|
2506
|
-
setLoginFormState({ ...state, step: "provider" });
|
|
2507
|
-
else {
|
|
2508
|
-
setLoginFormState(undefined);
|
|
2509
|
-
append(systemLine("Login cancelled."));
|
|
2510
|
-
}
|
|
2511
|
-
return;
|
|
2512
|
-
}
|
|
2513
|
-
if (state.step === "provider") {
|
|
2514
|
-
if (key.upArrow) {
|
|
2515
|
-
setLoginFormState(moveLoginProviderSelection(state, -1));
|
|
2516
|
-
return;
|
|
2517
|
-
}
|
|
2518
|
-
if (key.downArrow) {
|
|
2519
|
-
setLoginFormState(moveLoginProviderSelection(state, 1));
|
|
2520
|
-
return;
|
|
2521
|
-
}
|
|
2522
|
-
if (key.return) {
|
|
2523
|
-
const provider = state.providers[state.selectedProviderIndex] ?? state.provider;
|
|
2524
|
-
setLoginFormState({ ...loginFormForProvider(provider, state.envPath), step: "fields" });
|
|
2525
|
-
return;
|
|
2526
|
-
}
|
|
2527
|
-
return;
|
|
2528
|
-
}
|
|
2529
|
-
const fields = LOGIN_FIELD_DEFINITIONS[state.provider];
|
|
2530
|
-
const field = fields[state.selectedFieldIndex];
|
|
2531
|
-
if (!field)
|
|
2532
|
-
return;
|
|
2533
|
-
if (key.upArrow) {
|
|
2534
|
-
setLoginFormState(moveLoginFieldSelection(state, -1));
|
|
2535
|
-
return;
|
|
2536
|
-
}
|
|
2537
|
-
if (key.downArrow) {
|
|
2538
|
-
setLoginFormState(moveLoginFieldSelection(state, 1));
|
|
2539
|
-
return;
|
|
2540
|
-
}
|
|
2541
|
-
if (key.leftArrow) {
|
|
2542
|
-
setLoginFormState({ ...state, cursor: Math.max(0, state.cursor - 1) });
|
|
2543
|
-
return;
|
|
2544
|
-
}
|
|
2545
|
-
if (key.rightArrow) {
|
|
2546
|
-
const current = state.values[field.key] ?? "";
|
|
2547
|
-
setLoginFormState({ ...state, cursor: Math.min(current.length, state.cursor + 1) });
|
|
2548
|
-
return;
|
|
2549
|
-
}
|
|
2550
|
-
if (key.tab && field.options?.length) {
|
|
2551
|
-
setLoginFormState(cycleLoginFieldOption(state, field));
|
|
2552
|
-
return;
|
|
2553
|
-
}
|
|
2554
|
-
if (key.backspace || key.delete) {
|
|
2555
|
-
setLoginFormState(deleteLoginFieldCharacter(state, field));
|
|
2556
|
-
return;
|
|
2557
|
-
}
|
|
2558
|
-
if (key.return) {
|
|
2559
|
-
void submitLoginForm(state, runtime, append, setLoginFormState, setStatus);
|
|
2560
|
-
return;
|
|
2561
|
-
}
|
|
2562
|
-
if (value && !key.ctrl && !key.meta) {
|
|
2563
|
-
setLoginFormState(insertLoginFieldText(state, field, value));
|
|
2564
|
-
}
|
|
2565
|
-
}
|
|
2566
|
-
function moveLoginProviderSelection(state, delta) {
|
|
2567
|
-
const selectedProviderIndex = (state.selectedProviderIndex + delta + state.providers.length) % state.providers.length;
|
|
2568
|
-
return { ...state, selectedProviderIndex, provider: state.providers[selectedProviderIndex] ?? state.provider };
|
|
2569
|
-
}
|
|
2570
|
-
function moveLoginFieldSelection(state, delta) {
|
|
2571
|
-
const fields = LOGIN_FIELD_DEFINITIONS[state.provider];
|
|
2572
|
-
const selectedFieldIndex = (state.selectedFieldIndex + delta + fields.length) % fields.length;
|
|
2573
|
-
const field = fields[selectedFieldIndex];
|
|
2574
|
-
return { ...state, selectedFieldIndex, cursor: field ? (state.values[field.key] ?? "").length : 0 };
|
|
2575
|
-
}
|
|
2576
|
-
function cycleLoginFieldOption(state, field) {
|
|
2577
|
-
const options = field.options ?? [];
|
|
2578
|
-
const current = state.values[field.key] ?? "";
|
|
2579
|
-
const index = options.indexOf(current);
|
|
2580
|
-
const next = options[(index + 1 + options.length) % options.length] ?? "";
|
|
2581
|
-
return { ...state, values: { ...state.values, [field.key]: next }, cursor: next.length };
|
|
2582
|
-
}
|
|
2583
|
-
function insertLoginFieldText(state, field, value) {
|
|
2584
|
-
const current = state.values[field.key] ?? "";
|
|
2585
|
-
const cursor = Math.max(0, Math.min(state.cursor, current.length));
|
|
2586
|
-
const next = `${current.slice(0, cursor)}${value}${current.slice(cursor)}`;
|
|
2587
|
-
return { ...state, values: { ...state.values, [field.key]: next }, cursor: cursor + value.length };
|
|
2588
|
-
}
|
|
2589
|
-
function deleteLoginFieldCharacter(state, field) {
|
|
2590
|
-
const current = state.values[field.key] ?? "";
|
|
2591
|
-
const cursor = Math.max(0, Math.min(state.cursor, current.length));
|
|
2592
|
-
if (cursor <= 0)
|
|
2593
|
-
return state;
|
|
2594
|
-
const next = `${current.slice(0, cursor - 1)}${current.slice(cursor)}`;
|
|
2595
|
-
return { ...state, values: { ...state.values, [field.key]: next }, cursor: cursor - 1 };
|
|
2596
|
-
}
|
|
2597
|
-
async function submitLoginForm(state, runtime, append, setLoginFormState, setStatus) {
|
|
2598
|
-
const validationError = validateLoginForm(state);
|
|
2599
|
-
if (validationError) {
|
|
2600
|
-
append({ kind: "error", text: validationError });
|
|
2601
|
-
return;
|
|
2602
|
-
}
|
|
2603
|
-
try {
|
|
2604
|
-
await saveLoginFormToEnv(state);
|
|
2605
|
-
applyLoginFormToProcessEnv(state);
|
|
2606
|
-
const config = readModelProviderConfig(process.env);
|
|
2607
|
-
if (!config)
|
|
2608
|
-
throw new Error("Saved provider config could not be loaded from environment.");
|
|
2609
|
-
const innerGateway = createModelGatewayFromConfig(config);
|
|
2610
|
-
runtime.modelGateway.setInner(innerGateway);
|
|
2611
|
-
runtime.agentRuntime.modelGateway = runtime.modelGateway;
|
|
2612
|
-
runtime.engine.setModelProvider({
|
|
2613
|
-
modelGateway: runtime.modelGateway,
|
|
2614
|
-
model: config.model,
|
|
2615
|
-
fallbackModel: config.fallbackModel,
|
|
2616
|
-
reasoning: config.defaultReasoning,
|
|
2617
|
-
});
|
|
2618
|
-
syncImageGenerationTool(runtime, config.provider);
|
|
2619
|
-
runtime.defaultReasoning = config.defaultReasoning;
|
|
2620
|
-
const metrics = await runtime.engine.contextMetrics();
|
|
2621
|
-
setStatus((current) => ({
|
|
2622
|
-
...current,
|
|
2623
|
-
metrics,
|
|
2624
|
-
activityTick: current.activityTick + 1,
|
|
2625
|
-
}));
|
|
2626
|
-
setLoginFormState(undefined);
|
|
2627
|
-
append(systemLine(`Saved ${state.provider} login to ${state.envPath}\n${formatModelSettings(runtime.engine.getModelSettings(), runtime.defaultReasoning)}`, EXPANDED_SUMMARY_MAX_LINES));
|
|
2628
|
-
}
|
|
2629
|
-
catch (error) {
|
|
2630
|
-
append({ kind: "error", text: `Login save failed: ${error instanceof Error ? error.message : String(error)}` });
|
|
2631
|
-
}
|
|
2632
|
-
}
|
|
2633
|
-
function validateLoginForm(state) {
|
|
2634
|
-
for (const field of LOGIN_FIELD_DEFINITIONS[state.provider]) {
|
|
2635
|
-
const value = (state.values[field.key] ?? "").trim();
|
|
2636
|
-
if (field.required && !value)
|
|
2637
|
-
return `${field.label} is required.`;
|
|
2638
|
-
if (field.options?.length && value && !field.options.includes(value))
|
|
2639
|
-
return `${field.label} must be one of: ${field.options.filter(Boolean).join(", ")}`;
|
|
2640
|
-
}
|
|
2641
|
-
for (const fieldKey of ["maxOutputTokens", "timeoutMs", "streamIdleTimeoutMs", "maxRetries"]) {
|
|
2642
|
-
const value = state.values[fieldKey]?.trim();
|
|
2643
|
-
if (value && !Number.isFinite(Number(value)))
|
|
2644
|
-
return `${fieldKey} must be a number.`;
|
|
2645
|
-
}
|
|
2646
|
-
return undefined;
|
|
2647
|
-
}
|
|
2648
|
-
function createLoginFormState(envPath = getUserDotEnvPath()) {
|
|
2649
|
-
const env = parseEnvFileSafe(envPath);
|
|
2650
|
-
const currentProvider = parseLoginProvider(env.MODEL_PROVIDER ?? process.env.MODEL_PROVIDER) ?? guessLoginProvider(env);
|
|
2651
|
-
return loginFormForProvider(currentProvider, envPath, env);
|
|
2652
|
-
}
|
|
2653
|
-
function loginFormForProvider(provider, envPath, env = parseEnvFileSafe(envPath)) {
|
|
2654
|
-
const selectedProviderIndex = Math.max(0, LOGIN_PROVIDERS.indexOf(provider));
|
|
2655
|
-
return {
|
|
2656
|
-
step: "provider",
|
|
2657
|
-
providers: LOGIN_PROVIDERS,
|
|
2658
|
-
selectedProviderIndex,
|
|
2659
|
-
provider,
|
|
2660
|
-
selectedFieldIndex: 0,
|
|
2661
|
-
cursor: 0,
|
|
2662
|
-
values: loginValuesForProvider(provider, env),
|
|
2663
|
-
envPath,
|
|
2664
|
-
};
|
|
2665
|
-
}
|
|
2666
|
-
function loginValuesForProvider(provider, env) {
|
|
2667
|
-
const values = {};
|
|
2668
|
-
for (const field of LOGIN_FIELD_DEFINITIONS[provider]) {
|
|
2669
|
-
values[field.key] = env[field.envKey] ?? "";
|
|
2670
|
-
}
|
|
2671
|
-
if (provider === "kimi") {
|
|
2672
|
-
values.apiKey ||= env.MOONSHOT_API_KEY ?? process.env.MOONSHOT_API_KEY ?? "";
|
|
2673
|
-
values.baseUrl ||= env.MOONSHOT_BASE_URL ?? process.env.MOONSHOT_BASE_URL ?? "";
|
|
2674
|
-
values.model ||= env.MOONSHOT_MODEL ?? process.env.MOONSHOT_MODEL ?? "";
|
|
2675
|
-
values.fallbackModel ||= env.MOONSHOT_FALLBACK_MODEL ?? process.env.MOONSHOT_FALLBACK_MODEL ?? "";
|
|
2676
|
-
}
|
|
2677
|
-
if (!values.baseUrl)
|
|
2678
|
-
values.baseUrl = defaultBaseUrlForLoginProvider(provider);
|
|
2679
|
-
if (!values.model)
|
|
2680
|
-
values.model = defaultModelForLoginProvider(provider);
|
|
2681
|
-
if (provider === "openai" && !values.endpoint)
|
|
2682
|
-
values.endpoint = "auto";
|
|
2683
|
-
return values;
|
|
2684
|
-
}
|
|
2685
|
-
function parseLoginProvider(value) {
|
|
2686
|
-
if (value === "openai" || value === "deepseek" || value === "kimi")
|
|
2687
|
-
return value;
|
|
2688
|
-
return undefined;
|
|
2689
|
-
}
|
|
2690
|
-
function guessLoginProvider(env) {
|
|
2691
|
-
if (env.KIMI_API_KEY ?? env.MOONSHOT_API_KEY ?? process.env.KIMI_API_KEY ?? process.env.MOONSHOT_API_KEY)
|
|
2692
|
-
return "kimi";
|
|
2693
|
-
if (env.DEEPSEEK_API_KEY ?? process.env.DEEPSEEK_API_KEY)
|
|
2694
|
-
return "deepseek";
|
|
2695
|
-
return "openai";
|
|
2696
|
-
}
|
|
2697
|
-
function defaultBaseUrlForLoginProvider(provider) {
|
|
2698
|
-
if (provider === "deepseek")
|
|
2699
|
-
return "https://api.deepseek.com";
|
|
2700
|
-
if (provider === "kimi")
|
|
2701
|
-
return "https://api.moonshot.cn/v1";
|
|
2702
|
-
return "https://api.openai.com";
|
|
2703
|
-
}
|
|
2704
|
-
function defaultModelForLoginProvider(provider) {
|
|
2705
|
-
if (provider === "deepseek")
|
|
2706
|
-
return "deepseek-chat";
|
|
2707
|
-
if (provider === "kimi")
|
|
2708
|
-
return "kimi-k2.6";
|
|
2709
|
-
return "gpt-5.5";
|
|
2710
|
-
}
|
|
2711
|
-
function loginFormViewHeight(state) {
|
|
2712
|
-
return state.step === "provider" ? state.providers.length + 3 : LOGIN_FIELD_DEFINITIONS[state.provider].length + 4;
|
|
2713
|
-
}
|
|
2714
|
-
function LoginFormView({ state, width }) {
|
|
2715
|
-
const contentWidth = Math.max(30, width);
|
|
2716
|
-
if (state.step === "provider") {
|
|
2717
|
-
return e(Box, { flexDirection: "column", marginTop: 1 }, e(Text, { color: "cyan", bold: true }, fitToWidth(`Login: choose provider · saving to ${state.envPath}`, contentWidth)), ...state.providers.map((provider, index) => e(Text, { key: provider, color: "white" }, e(Text, { color: index === state.selectedProviderIndex ? "black" : "white", backgroundColor: index === state.selectedProviderIndex ? "cyan" : undefined }, `${index + 1}.`.padStart(3)), e(Text, { color: "gray" }, " "), e(Text, { color: "cyan" }, provider))), e(Text, { color: "gray" }, fitToWidth("↑/↓ select · Enter edit config · Esc close", contentWidth)));
|
|
2718
|
-
}
|
|
2719
|
-
const fields = LOGIN_FIELD_DEFINITIONS[state.provider];
|
|
2720
|
-
const maxLabel = Math.max(...fields.map((field) => field.label.length));
|
|
2721
|
-
return e(Box, { flexDirection: "column", marginTop: 1 }, e(Text, { color: "cyan", bold: true }, fitToWidth(`Login: ${state.provider} · ${state.envPath}`, contentWidth)), ...fields.map((field, index) => {
|
|
2722
|
-
const selected = index === state.selectedFieldIndex;
|
|
2723
|
-
const rawValue = state.values[field.key] ?? "";
|
|
2724
|
-
const visibleValue = formatLoginFieldValue(field, rawValue, selected ? state.cursor : undefined);
|
|
2725
|
-
const placeholder = rawValue ? "" : (field.placeholder ? ` (${field.placeholder})` : "");
|
|
2726
|
-
return e(Text, { key: field.key, color: "white" }, e(Text, { color: selected ? "black" : "white", backgroundColor: selected ? "cyan" : undefined }, `${index + 1}.`.padStart(3)), e(Text, { color: field.required ? "yellow" : "gray" }, ` ${field.label.padEnd(maxLabel)} `), e(Text, { color: field.scope === "shared" ? "blue" : "gray" }, field.scope === "shared" ? "shared " : "provider "), e(Text, { color: rawValue ? "white" : "gray" }, fitToWidth(`${visibleValue}${placeholder}`, Math.max(8, contentWidth - maxLabel - 14))));
|
|
2727
|
-
}), e(Text, { color: "gray" }, fitToWidth("↑/↓ field · ←/→ cursor · type edit · Tab cycle choices · Enter save · Esc back/cancel", contentWidth)), e(Text, { color: "gray" }, fitToWidth("Provider fields save as OPENAI_* / DEEPSEEK_* / KIMI_*; shared runtime fields save as MODEL_*.", contentWidth)));
|
|
2728
|
-
}
|
|
2729
|
-
function formatLoginFieldValue(field, value, cursor) {
|
|
2730
|
-
const display = field.secret && value ? "•".repeat(Math.min(value.length, 24)) : value;
|
|
2731
|
-
if (cursor === undefined)
|
|
2732
|
-
return display;
|
|
2733
|
-
const safeCursor = Math.max(0, Math.min(cursor, display.length));
|
|
2734
|
-
const selected = display[safeCursor] ?? " ";
|
|
2735
|
-
return `${display.slice(0, safeCursor)}█${selected === " " ? "" : display.slice(safeCursor + 1)}`;
|
|
2736
|
-
}
|
|
2737
|
-
function applyLoginFormToProcessEnv(state) {
|
|
2738
|
-
applyEnvUpdatesToProcess(envEntriesForLoginForm(state));
|
|
2739
|
-
for (const key of DEPRECATED_MODEL_ENV_KEYS)
|
|
2740
|
-
delete process.env[key];
|
|
2741
|
-
}
|
|
2742
|
-
async function saveLoginFormToEnv(state) {
|
|
2743
|
-
await writeEnvUpdates(state.envPath, envEntriesForLoginForm(state), DEPRECATED_MODEL_ENV_KEYS);
|
|
2744
|
-
}
|
|
2745
|
-
function envEntriesForLoginForm(state) {
|
|
2746
|
-
const entries = {
|
|
2747
|
-
MODEL_PROVIDER: state.provider,
|
|
2748
|
-
};
|
|
2749
|
-
for (const field of LOGIN_FIELD_DEFINITIONS[state.provider]) {
|
|
2750
|
-
const value = (state.values[field.key] ?? "").trim();
|
|
2751
|
-
entries[field.envKey] = value || undefined;
|
|
2752
|
-
}
|
|
2753
|
-
if (state.provider === "kimi") {
|
|
2754
|
-
entries.MOONSHOT_API_KEY = undefined;
|
|
2755
|
-
entries.MOONSHOT_BASE_URL = undefined;
|
|
2756
|
-
entries.MOONSHOT_MODEL = undefined;
|
|
2757
|
-
entries.MOONSHOT_FALLBACK_MODEL = undefined;
|
|
2758
|
-
}
|
|
2759
|
-
return entries;
|
|
2760
|
-
}
|
|
2761
|
-
function updateEnvContent(content, updates, removeKeys = []) {
|
|
2762
|
-
const keys = new Set(Object.keys(updates));
|
|
2763
|
-
const removals = new Set(removeKeys);
|
|
2764
|
-
const seen = new Set();
|
|
2765
|
-
const lines = content ? content.split(/\r?\n/) : [];
|
|
2766
|
-
const updatedLines = lines.map((line) => {
|
|
2767
|
-
const parsed = parseEnvLine(line);
|
|
2768
|
-
if (!parsed)
|
|
2769
|
-
return line;
|
|
2770
|
-
if (removals.has(parsed.key) && !keys.has(parsed.key))
|
|
2771
|
-
return undefined;
|
|
2772
|
-
if (!keys.has(parsed.key))
|
|
2773
|
-
return line;
|
|
2774
|
-
seen.add(parsed.key);
|
|
2775
|
-
const value = updates[parsed.key];
|
|
2776
|
-
if (value === undefined)
|
|
2777
|
-
return undefined;
|
|
2778
|
-
return `${parsed.key}=${quoteEnvValue(value)}`;
|
|
2779
|
-
}).filter((line) => line !== undefined);
|
|
2780
|
-
const missing = Object.entries(updates).filter((entry) => !seen.has(entry[0]) && entry[1] !== undefined);
|
|
2781
|
-
if (missing.length > 0) {
|
|
2782
|
-
const grouped = groupLoginEnvEntries(missing);
|
|
2783
|
-
appendEnvGroup(updatedLines, "# Neo active provider", grouped.active);
|
|
2784
|
-
appendEnvGroup(updatedLines, "# OpenAI provider settings", grouped.openai);
|
|
2785
|
-
appendEnvGroup(updatedLines, "# DeepSeek provider settings", grouped.deepseek);
|
|
2786
|
-
appendEnvGroup(updatedLines, "# Kimi provider settings", grouped.kimi);
|
|
2787
|
-
appendEnvGroup(updatedLines, "# Shared model runtime settings", grouped.shared);
|
|
2788
|
-
}
|
|
2789
|
-
return `${updatedLines.join("\n").replace(/\n*$/u, "")}\n`;
|
|
2790
|
-
}
|
|
2791
|
-
function groupLoginEnvEntries(entries) {
|
|
2792
|
-
return {
|
|
2793
|
-
active: entries.filter(([key]) => key === "MODEL_PROVIDER"),
|
|
2794
|
-
openai: entries.filter(([key]) => key.startsWith("OPENAI_")),
|
|
2795
|
-
deepseek: entries.filter(([key]) => key.startsWith("DEEPSEEK_")),
|
|
2796
|
-
kimi: entries.filter(([key]) => key.startsWith("KIMI_") || key.startsWith("MOONSHOT_")),
|
|
2797
|
-
shared: entries.filter(([key]) => key.startsWith("MODEL_") && key !== "MODEL_PROVIDER"),
|
|
2798
|
-
};
|
|
2799
|
-
}
|
|
2800
|
-
function appendEnvGroup(lines, header, entries) {
|
|
2801
|
-
if (entries.length === 0)
|
|
2802
|
-
return;
|
|
2803
|
-
if (lines.length > 0 && lines[lines.length - 1]?.trim())
|
|
2804
|
-
lines.push("");
|
|
2805
|
-
lines.push(header);
|
|
2806
|
-
for (const [key, value] of entries)
|
|
2807
|
-
lines.push(`${key}=${quoteEnvValue(value)}`);
|
|
2808
|
-
}
|
|
2809
|
-
function parseEnvFileSafe(envPath) {
|
|
2810
|
-
if (!existsSync(envPath))
|
|
2811
|
-
return {};
|
|
2812
|
-
const env = {};
|
|
2813
|
-
for (const line of readFileSync(envPath, "utf8").split(/\r?\n/)) {
|
|
2814
|
-
const parsed = parseEnvLine(line);
|
|
2815
|
-
if (parsed)
|
|
2816
|
-
env[parsed.key] = stripEnvQuotes(parsed.value.trim());
|
|
2817
|
-
}
|
|
2818
|
-
return env;
|
|
2819
|
-
}
|
|
2820
|
-
function parseEnvLine(line) {
|
|
2821
|
-
const trimmed = line.trim();
|
|
2822
|
-
if (!trimmed || trimmed.startsWith("#"))
|
|
2823
|
-
return undefined;
|
|
2824
|
-
const separator = trimmed.indexOf("=");
|
|
2825
|
-
if (separator <= 0)
|
|
2826
|
-
return undefined;
|
|
2827
|
-
const key = trimmed.slice(0, separator).trim();
|
|
2828
|
-
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key))
|
|
2829
|
-
return undefined;
|
|
2830
|
-
return { key, value: trimmed.slice(separator + 1) };
|
|
2831
|
-
}
|
|
2832
|
-
function quoteEnvValue(value) {
|
|
2833
|
-
if (/^[A-Za-z0-9_./:@+-]*$/.test(value))
|
|
2834
|
-
return value;
|
|
2835
|
-
return JSON.stringify(value);
|
|
2836
|
-
}
|
|
2837
|
-
function stripEnvQuotes(value) {
|
|
2838
|
-
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'")))
|
|
2839
|
-
return value.slice(1, -1);
|
|
2840
|
-
return value;
|
|
2841
|
-
}
|
|
2842
|
-
function formatSessionBrowserRow(session, absoluteIndex, width, running = false) {
|
|
2051
|
+
function formatSessionBrowserRow(session, absoluteIndex, width) {
|
|
2843
2052
|
const numberPrefix = `${absoluteIndex + 1}.`.padStart(4);
|
|
2844
2053
|
const title = session.title?.trim() || "(untitled)";
|
|
2845
|
-
const runningTag = running ? " · running" : "";
|
|
2846
2054
|
const updated = session.updatedAt ? ` · ${formatSessionTimestamp(session.updatedAt)}` : "";
|
|
2847
2055
|
const messages = ` · ${session.messages} messages`;
|
|
2848
|
-
const fixedParts = `${numberPrefix} ${
|
|
2056
|
+
const fixedParts = `${numberPrefix} ${updated}${messages}`;
|
|
2849
2057
|
const idBudget = Math.max(12, Math.min(32, Math.floor(width * 0.28)));
|
|
2850
2058
|
const id = truncateMiddle(session.sessionId, idBudget);
|
|
2851
2059
|
const titleBudget = Math.max(8, width - fixedParts.length - id.length - 5);
|
|
2852
|
-
const row = fitToWidth(`${numberPrefix} ${truncateMiddle(title, titleBudget)} · ${id}${
|
|
2060
|
+
const row = fitToWidth(`${numberPrefix} ${truncateMiddle(title, titleBudget)} · ${id}${updated}${messages}`, width);
|
|
2853
2061
|
return { numberPrefix, rest: row.slice(numberPrefix.length) };
|
|
2854
2062
|
}
|
|
2855
2063
|
function formatSessionTimestamp(value) {
|
|
@@ -2928,7 +2136,7 @@ function kindForRole(role) {
|
|
|
2928
2136
|
}
|
|
2929
2137
|
function titleForKind(kind) {
|
|
2930
2138
|
if (kind === "thinking")
|
|
2931
|
-
return `${THINKING_MARKER}
|
|
2139
|
+
return `${THINKING_MARKER} Think`;
|
|
2932
2140
|
if (kind === "tool")
|
|
2933
2141
|
return "Tool";
|
|
2934
2142
|
if (kind === "error")
|
|
@@ -2982,7 +2190,6 @@ function formatToolUse(toolUse) {
|
|
|
2982
2190
|
return {
|
|
2983
2191
|
kind: "tool",
|
|
2984
2192
|
title: toolTitle(toolUse.name, "running"),
|
|
2985
|
-
bodyTitle: planToolBodyTitle(toolUse.input),
|
|
2986
2193
|
text: formatPlanToolPayload(toolUse.input),
|
|
2987
2194
|
};
|
|
2988
2195
|
}
|
|
@@ -2998,7 +2205,6 @@ function formatToolResultLine(toolName, output, ok) {
|
|
|
2998
2205
|
const line = {
|
|
2999
2206
|
kind: ok ? "tool" : "error",
|
|
3000
2207
|
title: toolTitle(toolName, "finished"),
|
|
3001
|
-
bodyTitle: formatted.bodyTitle,
|
|
3002
2208
|
titleStatus: ok ? "success" : "failure",
|
|
3003
2209
|
text: formatted.text,
|
|
3004
2210
|
format: formatted.format,
|
|
@@ -3040,12 +2246,10 @@ function isPlanToolPayload(value) {
|
|
|
3040
2246
|
(item.status === "pending" || item.status === "in_progress" || item.status === "completed"));
|
|
3041
2247
|
});
|
|
3042
2248
|
}
|
|
3043
|
-
function planToolBodyTitle(payload) {
|
|
3044
|
-
const title = payload.title?.trim();
|
|
3045
|
-
return title ? title : undefined;
|
|
3046
|
-
}
|
|
3047
2249
|
function formatPlanToolPayload(payload) {
|
|
3048
2250
|
const sections = [];
|
|
2251
|
+
if (payload.title?.trim())
|
|
2252
|
+
sections.push(`**${payload.title.trim()}**`);
|
|
3049
2253
|
if (payload.summary?.trim())
|
|
3050
2254
|
sections.push(payload.summary.trim());
|
|
3051
2255
|
if (payload.note?.trim())
|
|
@@ -3133,11 +2337,26 @@ function isReplScalar(value) {
|
|
|
3133
2337
|
return value === null || value === undefined || typeof value !== "object" || value instanceof Date;
|
|
3134
2338
|
}
|
|
3135
2339
|
function formatToolResult(toolName, output, ok) {
|
|
3136
|
-
if (
|
|
2340
|
+
if (toolName === "edit" && isRecord(output) && isEditToolOutput(output)) {
|
|
3137
2341
|
return { text: formatEditToolDiff(output, ok), format: "ansi", summaryMaxLines: EDIT_TOOL_SUMMARY_MAX_LINES };
|
|
3138
2342
|
}
|
|
3139
2343
|
if (isExecOutput(output)) {
|
|
3140
|
-
|
|
2344
|
+
const status = output.timedOut
|
|
2345
|
+
? "timed out"
|
|
2346
|
+
: output.exitCode === 0
|
|
2347
|
+
? "exit 0"
|
|
2348
|
+
: `exit ${output.exitCode ?? output.signal ?? "unknown"}`;
|
|
2349
|
+
const sections = [
|
|
2350
|
+
`${status} · ${output.durationMs}ms`,
|
|
2351
|
+
`$ ${output.command}`,
|
|
2352
|
+
];
|
|
2353
|
+
if (output.stdout)
|
|
2354
|
+
sections.push("stdout:", output.stdout.replace(/\s+$/u, ""));
|
|
2355
|
+
if (output.stderr)
|
|
2356
|
+
sections.push("stderr:", output.stderr.replace(/\s+$/u, ""));
|
|
2357
|
+
if (!output.stdout && !output.stderr)
|
|
2358
|
+
sections.push(ok ? "no output" : "no captured output");
|
|
2359
|
+
return { text: sections.join("\n"), format: "ansi" };
|
|
3141
2360
|
}
|
|
3142
2361
|
if (typeof output === "string" && hasAnsi(output)) {
|
|
3143
2362
|
return { text: output, format: "ansi" };
|
|
@@ -3154,11 +2373,8 @@ function formatToolResult(toolName, output, ok) {
|
|
|
3154
2373
|
if (toolName === "search" && isRecord(output)) {
|
|
3155
2374
|
return { text: formatWebSearchToolResult(output, ok), summaryMaxLines: EXPANDED_SUMMARY_MAX_LINES };
|
|
3156
2375
|
}
|
|
3157
|
-
if (toolName === "image2" && isRecord(output)) {
|
|
3158
|
-
return { text: formatImageGenerationToolResult(output, ok), summaryMaxLines: 4 };
|
|
3159
|
-
}
|
|
3160
2376
|
if (toolName === "plan" && isPlanToolPayload(output)) {
|
|
3161
|
-
return { text: formatPlanToolPayload(output),
|
|
2377
|
+
return { text: formatPlanToolPayload(output), full: true };
|
|
3162
2378
|
}
|
|
3163
2379
|
return { text: `${ok ? "ok" : "failed"}\n${formatJson(output, 6000)}`, summaryMaxLines: EXPANDED_SUMMARY_MAX_LINES };
|
|
3164
2380
|
}
|
|
@@ -3247,54 +2463,9 @@ function isExecOutput(value) {
|
|
|
3247
2463
|
typeof record.stdout === "string" &&
|
|
3248
2464
|
typeof record.stderr === "string");
|
|
3249
2465
|
}
|
|
3250
|
-
function formatExecToolResult(output, ok) {
|
|
3251
|
-
const status = output.timedOut
|
|
3252
|
-
? "timed out"
|
|
3253
|
-
: output.exitCode === 0
|
|
3254
|
-
? "exit 0"
|
|
3255
|
-
: `exit ${output.exitCode ?? output.signal ?? "unknown"}`;
|
|
3256
|
-
const lines = [
|
|
3257
|
-
"exec result",
|
|
3258
|
-
`status: ${status}`,
|
|
3259
|
-
`duration: ${output.durationMs}ms`,
|
|
3260
|
-
`command: ${output.command}`,
|
|
3261
|
-
];
|
|
3262
|
-
const stdout = output.stdout.replace(/\s+$/u, "");
|
|
3263
|
-
const stderr = output.stderr.replace(/\s+$/u, "");
|
|
3264
|
-
if (stdout)
|
|
3265
|
-
lines.push("stdout:", stdout);
|
|
3266
|
-
if (stderr)
|
|
3267
|
-
lines.push("stderr:", stderr);
|
|
3268
|
-
if (!stdout && !stderr)
|
|
3269
|
-
lines.push(ok ? "output: (none)" : "output: (not captured)");
|
|
3270
|
-
return lines.join("\n");
|
|
3271
|
-
}
|
|
3272
2466
|
function isRecord(value) {
|
|
3273
2467
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
3274
2468
|
}
|
|
3275
|
-
function formatImageGenerationToolResult(output, ok) {
|
|
3276
|
-
const error = typeof output.error === "string" ? output.error : undefined;
|
|
3277
|
-
if (!ok || error)
|
|
3278
|
-
return ["image generation failed", error ?? formatReplData(output, 1200)].join("\n");
|
|
3279
|
-
const provider = typeof output.provider === "string" ? output.provider : "openai";
|
|
3280
|
-
const model = typeof output.model === "string" ? output.model : undefined;
|
|
3281
|
-
const returnedImages = typeof output.returnedImages === "number" ? output.returnedImages : Array.isArray(output.images) ? output.images.length : undefined;
|
|
3282
|
-
const size = typeof output.size === "string" ? output.size : undefined;
|
|
3283
|
-
const quality = typeof output.quality === "string" ? output.quality : undefined;
|
|
3284
|
-
const format = typeof output.outputFormat === "string" ? output.outputFormat : undefined;
|
|
3285
|
-
const lines = [`generated ${returnedImages ?? 0} image${returnedImages === 1 ? "" : "s"}`];
|
|
3286
|
-
const details = [provider, model, size, quality && quality !== "auto" ? quality : undefined, format].filter((value) => Boolean(value));
|
|
3287
|
-
if (details.length > 0)
|
|
3288
|
-
lines.push(details.join(" · "));
|
|
3289
|
-
const duration = imageGenerationDuration(output);
|
|
3290
|
-
if (duration !== undefined)
|
|
3291
|
-
lines.push(`duration: ${duration}ms`);
|
|
3292
|
-
return lines.join("\n");
|
|
3293
|
-
}
|
|
3294
|
-
function imageGenerationDuration(output) {
|
|
3295
|
-
const value = output.duration ?? output.elapsed ?? output.durationMs ?? output.elapsedMs;
|
|
3296
|
-
return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.round(value)) : undefined;
|
|
3297
|
-
}
|
|
3298
2469
|
function formatListToolResult(output, ok) {
|
|
3299
2470
|
const pathValue = typeof output.path === "string" ? output.path : "";
|
|
3300
2471
|
const typeValue = typeof output.type === "string" ? output.type : "result";
|
|
@@ -3442,9 +2613,11 @@ function formatGrepContextLine(line, marker) {
|
|
|
3442
2613
|
}
|
|
3443
2614
|
function renderContextParts(metrics) {
|
|
3444
2615
|
if (!metrics)
|
|
3445
|
-
return { percent: "?" };
|
|
2616
|
+
return { used: "?", limit: "?", percent: "?" };
|
|
2617
|
+
const used = compactNumber(metrics.estimatedInputTokens);
|
|
2618
|
+
const limit = metrics.contextWindowTokens ? compactNumber(metrics.contextWindowTokens) : "?";
|
|
3446
2619
|
const percent = metrics.contextUsageRatio === undefined ? "?" : `${(metrics.contextUsageRatio * 100).toFixed(1)}%`;
|
|
3447
|
-
return { percent };
|
|
2620
|
+
return { used, limit, percent };
|
|
3448
2621
|
}
|
|
3449
2622
|
function contextColor(metrics) {
|
|
3450
2623
|
const ratio = metrics?.contextUsageRatio;
|
|
@@ -3727,8 +2900,9 @@ function isFullWidthCodePoint(codePoint) {
|
|
|
3727
2900
|
(codePoint >= 0x20000 && codePoint <= 0x3fffd)));
|
|
3728
2901
|
}
|
|
3729
2902
|
const SESSIONS_DEFAULT_PAGE_SIZE = 10;
|
|
3730
|
-
const
|
|
3731
|
-
const
|
|
2903
|
+
const TERMINAL_TITLE_DOT_FILLED_PREFIX = "● ";
|
|
2904
|
+
const TERMINAL_TITLE_DOT_BLANK_PREFIX = " ";
|
|
2905
|
+
const TERMINAL_TITLE_BLINK_INTERVAL_MS = 1000;
|
|
3732
2906
|
const REPL_ANIMATION_INTERVAL_MS = 420;
|
|
3733
2907
|
const TOOL_RESULT_REPLACEMENT_DELAY_MS = 2000;
|
|
3734
2908
|
const TOKEN_PULSE_MS = 900;
|