min-agent 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +146 -18
- package/dist/agent.js +293 -408
- package/dist/assistant-stream.js +11 -7
- package/dist/cli.js +403 -140
- package/dist/clipboard.js +59 -23
- package/dist/code-mode.js +3 -3
- package/dist/compaction.js +182 -81
- package/dist/config.js +186 -35
- package/dist/confirm.js +55 -6
- package/dist/context-window.js +67 -54
- package/dist/doom-loop.js +19 -12
- package/dist/http.js +119 -0
- package/dist/instructions.js +51 -33
- package/dist/logger.js +66 -0
- package/dist/markdown.js +3 -44
- package/dist/mcp.js +547 -100
- package/dist/memory.js +48 -6
- package/dist/output.js +36 -27
- package/dist/paste-handler.js +3 -3
- package/dist/plugins.js +33 -6
- package/dist/pricing.js +119 -0
- package/dist/provider.js +17 -15
- package/dist/serve.js +658 -369
- package/dist/sessions.js +151 -13
- package/dist/skills.js +466 -76
- package/dist/synthetic.js +7 -0
- package/dist/title-gen.js +2 -1
- package/dist/tool-display.js +173 -0
- package/dist/tool-output.js +54 -45
- package/dist/tools/apply_patch.js +191 -0
- package/dist/tools/backend.js +61 -0
- package/dist/tools/bash.js +147 -70
- package/dist/tools/code_search.js +6 -5
- package/dist/tools/edit.js +23 -7
- package/dist/tools/explore.js +80 -12
- package/dist/tools/glob.js +3 -3
- package/dist/tools/grep.js +146 -14
- package/dist/tools/index.js +7 -7
- package/dist/tools/question.js +4 -22
- package/dist/tools/read.js +71 -11
- package/dist/tools/task.js +33 -20
- package/dist/tools/todo.js +83 -73
- package/dist/tools/web_fetch.js +150 -46
- package/dist/tools/web_search.js +706 -28
- package/dist/tools/write.js +13 -7
- package/dist/tui/App.js +40 -6
- package/dist/tui/ConfirmBar.js +24 -3
- package/dist/tui/InputBar.js +390 -45
- package/dist/tui/MessageList.js +533 -20
- package/dist/tui/ModelPicker.js +108 -0
- package/dist/tui/QuestionBar.js +104 -0
- package/dist/tui/StatusBar.js +19 -11
- package/dist/tui/agent-runner.js +103 -0
- package/dist/tui/caret-pos.js +134 -0
- package/dist/tui/caret.js +69 -0
- package/dist/tui/diff-view.js +61 -0
- package/dist/tui/drag-state.js +44 -0
- package/dist/tui/index.js +153 -24
- package/dist/tui/input-history.js +44 -0
- package/dist/tui/layout.js +17 -0
- package/dist/tui/mouse.js +46 -0
- package/dist/tui/selection.js +134 -0
- package/dist/tui/slash-commands.js +90 -0
- package/dist/tui/slash-handler.js +370 -0
- package/dist/tui/text-width.js +91 -0
- package/dist/tui/theme.js +12 -0
- package/dist/tui/undo-stack.js +14 -0
- package/dist/tui/use-sgr-mouse.js +27 -0
- package/dist/tui-chat.js +111 -331
- package/dist/updater.js +57 -0
- package/docs/API.md +160 -14
- package/docs/superpowers/plans/2026-08-16-batch1-tui-improvements.md +1510 -0
- package/docs/superpowers/plans/2026-08-16-batch2-cli-tools-api.md +2105 -0
- package/docs/superpowers/plans/2026-08-16-batch3-config-engineering.md +1595 -0
- package/docs/superpowers/plans/2026-08-16-input-caret.md +782 -0
- package/docs/superpowers/specs/2026-08-16-batch1-tui-improvements-design.md +183 -0
- package/docs/superpowers/specs/2026-08-16-batch2-cli-tools-api-design.md +220 -0
- package/docs/superpowers/specs/2026-08-16-batch3-config-engineering-design.md +196 -0
- package/docs/superpowers/specs/2026-08-16-input-caret-design.md +63 -0
- package/docs/superpowers/specs/2026-08-17-mouse-selection-design.md +116 -0
- package/package.json +7 -8
package/dist/agent.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { streamText, stepCountIs } from "ai";
|
|
2
|
-
import { readFileSync, existsSync } from "fs";
|
|
2
|
+
import { readFileSync, existsSync, statSync } from "fs";
|
|
3
3
|
import path from "path";
|
|
4
4
|
import { resolveModel } from "./provider.js";
|
|
5
|
+
import { loadConfig, getActiveProvider } from "./config.js";
|
|
6
|
+
import { getModelPrice, estimateCost } from "./pricing.js";
|
|
5
7
|
import { createChatTools, createCodeTools } from "./tools/index.js";
|
|
6
8
|
import { initMcp, shutdownMcp, getMcpTools } from "./mcp.js";
|
|
7
|
-
import { discoverSkills,
|
|
9
|
+
import { discoverSkills, attachSkills, collectLoadedSkillNames } from "./skills.js";
|
|
8
10
|
import { loadInstructions } from "./instructions.js";
|
|
9
11
|
import { getMemorySystemPrompt, getMemoryTools } from "./memory.js";
|
|
10
12
|
import { needsCompaction, compactMessages, estimateTokens, TokenTracker } from "./compaction.js";
|
|
@@ -13,19 +15,27 @@ import { MarkdownRenderer } from "./markdown.js";
|
|
|
13
15
|
import { DoomLoopDetector } from "./doom-loop.js";
|
|
14
16
|
import { ThinkingBodySplitter, stripThinkingFromAssistantText } from "./assistant-stream.js";
|
|
15
17
|
import { printHeader, printDivider, printToolCall, printToolResult, printDone } from "./output.js";
|
|
18
|
+
import { createTodoTool } from "./tools/todo.js";
|
|
19
|
+
import { getContextWindow } from "./context-window.js";
|
|
20
|
+
import { log, logToolCall, logToolResult } from "./logger.js";
|
|
21
|
+
import { markSyntheticMessage } from "./synthetic.js";
|
|
16
22
|
const MAX_STEPS = 30;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
const MAX_IMAGE_BYTES = 5 * 1024 * 1024;
|
|
24
|
+
const IMAGE_MIME_TYPES = {
|
|
25
|
+
".png": "image/png",
|
|
26
|
+
".jpg": "image/jpeg",
|
|
27
|
+
".jpeg": "image/jpeg",
|
|
28
|
+
".gif": "image/gif",
|
|
29
|
+
".webp": "image/webp",
|
|
30
|
+
};
|
|
31
|
+
const DIM_STYLE = "NO_COLOR" in process.env ? { dim: "", reset: "" } : { dim: "\x1b[90m", reset: "\x1b[0m" };
|
|
22
32
|
/** Shown immediately so the terminal does not look frozen while MCP / rules load. */
|
|
23
33
|
function printInitLoading() {
|
|
24
|
-
const { dim, reset } =
|
|
34
|
+
const { dim, reset } = DIM_STYLE;
|
|
25
35
|
console.log(`${dim}⟳ 正在初始化(MCP、技能、规则)…${reset}`);
|
|
26
36
|
}
|
|
27
37
|
function printInitReady() {
|
|
28
|
-
const { dim, reset } =
|
|
38
|
+
const { dim, reset } = DIM_STYLE;
|
|
29
39
|
console.log(`${dim}✓ 就绪${reset}`);
|
|
30
40
|
}
|
|
31
41
|
/** Stream thinking to stderr (dim). Set MIN_AGENT_SHOW_THINKING=0 to hide. */
|
|
@@ -34,10 +44,7 @@ function writeThinkingDelta(text) {
|
|
|
34
44
|
return;
|
|
35
45
|
if (process.env.MIN_AGENT_SHOW_THINKING === "0" || process.env.MIN_AGENT_SHOW_THINKING === "false")
|
|
36
46
|
return;
|
|
37
|
-
|
|
38
|
-
const dim = noColor ? "" : "\x1b[2m";
|
|
39
|
-
const reset = noColor ? "" : "\x1b[0m";
|
|
40
|
-
process.stderr.write(`${dim}${text}${reset}`);
|
|
47
|
+
process.stderr.write(`${DIM_STYLE.dim}${text}${DIM_STYLE.reset}`);
|
|
41
48
|
}
|
|
42
49
|
function buildSystemPrompt(instructions) {
|
|
43
50
|
const parts = [
|
|
@@ -45,7 +52,7 @@ function buildSystemPrompt(instructions) {
|
|
|
45
52
|
"",
|
|
46
53
|
"Be concise and direct. When you run a command, briefly explain why.",
|
|
47
54
|
"Use the available tools to complete tasks. When multiple independent operations are needed, call tools in parallel.",
|
|
48
|
-
"When the user asks about current events, news, or anything requiring up-to-date information, use the
|
|
55
|
+
"When the user asks about current events, news, or anything requiring up-to-date information, use the search_web tool.",
|
|
49
56
|
"",
|
|
50
57
|
`Working directory: ${process.cwd()}`,
|
|
51
58
|
`Platform: ${process.platform}`,
|
|
@@ -53,330 +60,245 @@ function buildSystemPrompt(instructions) {
|
|
|
53
60
|
"",
|
|
54
61
|
`When the user asks to configure, install, or manage MCP servers, skills, rules, memory, or other min-agent features, use the read tool on the file at ${path.resolve(path.dirname(new URL(import.meta.url).pathname), "../README.md")} first, then follow what it says.`,
|
|
55
62
|
];
|
|
56
|
-
const skillsPrompt = getSkillsSystemPrompt();
|
|
57
|
-
if (skillsPrompt) {
|
|
58
|
-
parts.push("", skillsPrompt);
|
|
59
|
-
}
|
|
60
|
-
const memoryPrompt = getMemorySystemPrompt();
|
|
61
|
-
if (memoryPrompt) {
|
|
62
|
-
parts.push("", memoryPrompt);
|
|
63
|
-
}
|
|
64
63
|
if (instructions.length > 0) {
|
|
65
64
|
parts.push("", "# User Instructions", "");
|
|
66
65
|
parts.push(...instructions);
|
|
67
66
|
}
|
|
68
67
|
return parts.join("\n");
|
|
69
68
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (!imagePaths || imagePaths.length === 0)
|
|
73
|
-
return message;
|
|
74
|
-
const parts = [{ type: "text", text: message }];
|
|
69
|
+
export function loadImageParts(imagePaths, notify) {
|
|
70
|
+
const parts = [];
|
|
75
71
|
for (const imgPath of imagePaths) {
|
|
76
72
|
const resolved = path.resolve(process.cwd(), imgPath);
|
|
77
73
|
if (!existsSync(resolved)) {
|
|
78
|
-
|
|
74
|
+
notify?.("not_found", imgPath);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const size = statSync(resolved).size;
|
|
78
|
+
if (size > MAX_IMAGE_BYTES) {
|
|
79
|
+
notify?.("too_large", imgPath, size);
|
|
79
80
|
continue;
|
|
80
81
|
}
|
|
81
82
|
const data = readFileSync(resolved);
|
|
82
83
|
const ext = path.extname(resolved).toLowerCase();
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
".jpg": "image/jpeg",
|
|
86
|
-
".jpeg": "image/jpeg",
|
|
87
|
-
".gif": "image/gif",
|
|
88
|
-
".webp": "image/webp",
|
|
89
|
-
};
|
|
90
|
-
const mimeType = mimeMap[ext] ?? "image/png";
|
|
91
|
-
parts.push({
|
|
92
|
-
type: "image",
|
|
93
|
-
image: data,
|
|
94
|
-
mimeType,
|
|
95
|
-
});
|
|
96
|
-
console.log(`\x1b[90m 📎 ${imgPath}\x1b[0m`);
|
|
84
|
+
parts.push({ type: "image", image: data, mimeType: IMAGE_MIME_TYPES[ext] ?? "image/png" });
|
|
85
|
+
notify?.("attached", imgPath);
|
|
97
86
|
}
|
|
98
87
|
return parts;
|
|
99
88
|
}
|
|
89
|
+
/** Build user message content, optionally with images */
|
|
90
|
+
export async function buildUserContent(message, imagePaths) {
|
|
91
|
+
if (!imagePaths || imagePaths.length === 0)
|
|
92
|
+
return message;
|
|
93
|
+
const images = loadImageParts(imagePaths, (kind, imgPath, sizeBytes) => {
|
|
94
|
+
if (kind === "not_found") {
|
|
95
|
+
console.error(`\x1b[33m Warning: Image not found: ${imgPath}\x1b[0m`);
|
|
96
|
+
}
|
|
97
|
+
else if (kind === "too_large") {
|
|
98
|
+
console.error(`\x1b[33m Warning: Image skipped (${((sizeBytes ?? 0) / 1024 / 1024).toFixed(1)} MB exceeds ${MAX_IMAGE_BYTES / 1024 / 1024} MB limit): ${imgPath}\x1b[0m`);
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
console.log(`\x1b[90m 📎 ${imgPath}\x1b[0m`);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
return [{ type: "text", text: message }, ...images];
|
|
105
|
+
}
|
|
100
106
|
/** Single-shot: send one message, get response, exit */
|
|
101
|
-
export async function runAgent(message, modelId, imagePaths) {
|
|
107
|
+
export async function runAgent(message, modelId, imagePaths, providerName) {
|
|
102
108
|
printHeader(modelId);
|
|
103
109
|
printDivider();
|
|
104
110
|
printInitLoading();
|
|
105
111
|
await initMcp();
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
112
|
+
try {
|
|
113
|
+
discoverSkills();
|
|
114
|
+
const instructions = await loadInstructions();
|
|
115
|
+
printInitReady();
|
|
116
|
+
console.log(`\x1b[36m> ${message}\x1b[0m\n`);
|
|
117
|
+
const content = await buildUserContent(message, imagePaths);
|
|
118
|
+
const messages = [{ role: "user", content }];
|
|
119
|
+
const tracker = new TokenTracker();
|
|
120
|
+
await runOnce(messages, instructions, modelId, undefined, undefined, tracker, { providerName });
|
|
121
|
+
}
|
|
122
|
+
finally {
|
|
123
|
+
await shutdownMcp();
|
|
124
|
+
}
|
|
115
125
|
}
|
|
116
|
-
/**
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
126
|
+
/**
|
|
127
|
+
* Push assistant text + tool call/result history into `messages` so the model
|
|
128
|
+
* remembers its tool activity across turns and resumed sessions.
|
|
129
|
+
*/
|
|
130
|
+
export function pushTurn(messages, assistantText, toolCalls, toolResults) {
|
|
131
|
+
const cleaned = stripThinkingFromAssistantText(assistantText);
|
|
132
|
+
const resultIds = new Set(toolResults.map((r) => r.toolCallId));
|
|
133
|
+
const parts = [];
|
|
134
|
+
if (cleaned.trim())
|
|
135
|
+
parts.push({ type: "text", text: cleaned });
|
|
136
|
+
for (const c of toolCalls) {
|
|
137
|
+
if (!resultIds.has(c.toolCallId))
|
|
138
|
+
continue;
|
|
139
|
+
parts.push({ type: "tool-call", toolCallId: c.toolCallId, toolName: c.toolName, input: c.input });
|
|
140
|
+
}
|
|
141
|
+
if (parts.length > 0) {
|
|
142
|
+
messages.push({ role: "assistant", content: parts });
|
|
143
|
+
}
|
|
144
|
+
for (const r of toolResults) {
|
|
145
|
+
const text = typeof r.output === "string" ? r.output : JSON.stringify(r.output);
|
|
146
|
+
messages.push({
|
|
147
|
+
role: "tool",
|
|
148
|
+
content: [
|
|
149
|
+
{
|
|
150
|
+
type: "tool-result",
|
|
151
|
+
toolCallId: r.toolCallId,
|
|
152
|
+
toolName: r.toolName,
|
|
153
|
+
output: { type: "text", value: text },
|
|
154
|
+
},
|
|
155
|
+
],
|
|
156
|
+
});
|
|
134
157
|
}
|
|
135
|
-
|
|
158
|
+
}
|
|
159
|
+
async function buildTools(mode, modelId, abortSignal, planMode = false, messages = [], tracker) {
|
|
160
|
+
const builtinTools = mode === "code" ? createCodeTools() : createChatTools();
|
|
161
|
+
builtinTools["todo"] = createTodoTool();
|
|
136
162
|
const mcpTools = getMcpTools();
|
|
137
163
|
const memoryTools = getMemoryTools();
|
|
138
164
|
const pluginTools = await loadPluginTools();
|
|
139
|
-
const { createTaskTool } = await import("./tools/task.js");
|
|
140
|
-
const { createExploreTool } = await import("./tools/explore.js");
|
|
141
|
-
const skills = getSkills();
|
|
142
165
|
const allTools = { ...builtinTools, ...memoryTools, ...pluginTools };
|
|
143
166
|
for (const [id, t] of Object.entries(mcpTools))
|
|
144
167
|
allTools[id] = t;
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
allTools["explore"] = createExploreTool(modelId);
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
168
|
+
const skillsPrompt = attachSkills(allTools, collectLoadedSkillNames(messages));
|
|
169
|
+
const onUsage = tracker ? (usage) => tracker.add(usage) : undefined;
|
|
170
|
+
const { createExploreTool } = await import("./tools/explore.js");
|
|
171
|
+
allTools["explore"] = createExploreTool(modelId, abortSignal, onUsage);
|
|
172
|
+
if (mode === "code") {
|
|
173
|
+
const { createTaskTool } = await import("./tools/task.js");
|
|
174
|
+
allTools["task"] = createTaskTool(modelId, abortSignal, onUsage);
|
|
175
|
+
}
|
|
176
|
+
if (planMode) {
|
|
177
|
+
for (const name of ["bash", "write", "edit", "apply_patch"])
|
|
178
|
+
delete allTools[name];
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
156
181
|
tools: allTools,
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
182
|
+
promptSections: [skillsPrompt, getMemorySystemPrompt()].filter((s) => s.length > 0),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
function formatErrorMessage(msg) {
|
|
186
|
+
if (msg.includes("API key") || msg.includes("Unauthorized") || msg.includes("Forbidden")) {
|
|
187
|
+
return "Authentication error: Check your API key.";
|
|
188
|
+
}
|
|
189
|
+
if (msg.includes("429") || msg.includes("rate limit") || msg.includes("Rate limit")) {
|
|
190
|
+
return "Rate limited after retries. Please wait and try again.";
|
|
191
|
+
}
|
|
192
|
+
if (msg.includes("timeout") || msg.includes("ETIMEDOUT") || msg.includes("ECONNRESET")) {
|
|
193
|
+
return `Network error (retries exhausted): ${msg}`;
|
|
194
|
+
}
|
|
195
|
+
return msg;
|
|
196
|
+
}
|
|
197
|
+
async function safeUsage(result) {
|
|
168
198
|
try {
|
|
169
|
-
|
|
170
|
-
switch (event.type) {
|
|
171
|
-
case "text-delta": {
|
|
172
|
-
const { display, thinking } = thinkingSplit.feed(event.text);
|
|
173
|
-
if (thinking) {
|
|
174
|
-
if (callbacks?.onThinkingDelta)
|
|
175
|
-
callbacks.onThinkingDelta(thinking);
|
|
176
|
-
else
|
|
177
|
-
writeThinkingDelta(thinking);
|
|
178
|
-
}
|
|
179
|
-
if (display) {
|
|
180
|
-
rawText += display;
|
|
181
|
-
assistantText += display;
|
|
182
|
-
if (callbacks?.onAssistantDisplayDelta)
|
|
183
|
-
callbacks.onAssistantDisplayDelta(display);
|
|
184
|
-
else {
|
|
185
|
-
const formatted = md.write(display);
|
|
186
|
-
if (formatted)
|
|
187
|
-
process.stdout.write(formatted);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
break;
|
|
191
|
-
}
|
|
192
|
-
case "tool-call": {
|
|
193
|
-
if (doomLoop.record(event.toolName, event.input)) {
|
|
194
|
-
const warn = `Doom loop detected: "${event.toolName}" — breaking.`;
|
|
195
|
-
if (callbacks?.onStreamError)
|
|
196
|
-
callbacks.onStreamError(warn);
|
|
197
|
-
else
|
|
198
|
-
console.log(`\n\x1b[33m⚠ ${warn}\x1b[0m`);
|
|
199
|
-
hasError = true;
|
|
200
|
-
break;
|
|
201
|
-
}
|
|
202
|
-
const flush = thinkingSplit.flush();
|
|
203
|
-
if (flush.thinking) {
|
|
204
|
-
if (callbacks?.onThinkingDelta)
|
|
205
|
-
callbacks.onThinkingDelta(flush.thinking);
|
|
206
|
-
else
|
|
207
|
-
writeThinkingDelta(flush.thinking);
|
|
208
|
-
}
|
|
209
|
-
if (flush.display) {
|
|
210
|
-
rawText += flush.display;
|
|
211
|
-
assistantText += flush.display;
|
|
212
|
-
if (callbacks?.onAssistantDisplayDelta)
|
|
213
|
-
callbacks.onAssistantDisplayDelta(flush.display);
|
|
214
|
-
else {
|
|
215
|
-
const extra = md.write(flush.display);
|
|
216
|
-
if (extra)
|
|
217
|
-
process.stdout.write(extra);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
if (!api) {
|
|
221
|
-
const flushed = md.flush();
|
|
222
|
-
if (flushed)
|
|
223
|
-
process.stdout.write(flushed);
|
|
224
|
-
if (rawText.trim())
|
|
225
|
-
console.log();
|
|
226
|
-
}
|
|
227
|
-
rawText = "";
|
|
228
|
-
if (callbacks?.onToolCall)
|
|
229
|
-
callbacks.onToolCall(event.toolName, event.input);
|
|
230
|
-
else
|
|
231
|
-
printToolCall(event.toolName, event.input);
|
|
232
|
-
break;
|
|
233
|
-
}
|
|
234
|
-
case "tool-result":
|
|
235
|
-
if (callbacks?.onToolResult)
|
|
236
|
-
callbacks.onToolResult(event.toolName, event.output);
|
|
237
|
-
else
|
|
238
|
-
printToolResult(event.toolName, event.output);
|
|
239
|
-
break;
|
|
240
|
-
case "error":
|
|
241
|
-
hasError = true;
|
|
242
|
-
if (callbacks?.onStreamError)
|
|
243
|
-
callbacks.onStreamError(String(event.error));
|
|
244
|
-
else
|
|
245
|
-
console.error(`\x1b[31mError: ${event.error}\x1b[0m`);
|
|
246
|
-
break;
|
|
247
|
-
case "finish":
|
|
248
|
-
break;
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
const end = thinkingSplit.flush();
|
|
252
|
-
if (end.thinking) {
|
|
253
|
-
if (callbacks?.onThinkingDelta)
|
|
254
|
-
callbacks.onThinkingDelta(end.thinking);
|
|
255
|
-
else
|
|
256
|
-
writeThinkingDelta(end.thinking);
|
|
257
|
-
}
|
|
258
|
-
if (end.display) {
|
|
259
|
-
assistantText += end.display;
|
|
260
|
-
if (callbacks?.onAssistantDisplayDelta)
|
|
261
|
-
callbacks.onAssistantDisplayDelta(end.display);
|
|
262
|
-
else {
|
|
263
|
-
const tail = md.write(end.display);
|
|
264
|
-
if (tail)
|
|
265
|
-
process.stdout.write(tail);
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
if (!api) {
|
|
269
|
-
const remaining = md.flush();
|
|
270
|
-
if (remaining)
|
|
271
|
-
process.stdout.write(remaining);
|
|
272
|
-
if (rawText.trim())
|
|
273
|
-
console.log();
|
|
274
|
-
}
|
|
275
|
-
const cleaned = stripThinkingFromAssistantText(assistantText);
|
|
276
|
-
if (cleaned.trim())
|
|
277
|
-
messages.push({ role: "assistant", content: cleaned });
|
|
278
|
-
let usage;
|
|
279
|
-
try {
|
|
280
|
-
usage = await result.usage;
|
|
281
|
-
}
|
|
282
|
-
catch {
|
|
283
|
-
usage = undefined;
|
|
284
|
-
}
|
|
285
|
-
if (usage && tracker)
|
|
286
|
-
tracker.update(usage);
|
|
287
|
-
if (callbacks?.onRunFinish) {
|
|
288
|
-
callbacks.onRunFinish({ stepCount, usage, hasError, aborted: false });
|
|
289
|
-
}
|
|
290
|
-
else {
|
|
291
|
-
if (!hasError && usage) {
|
|
292
|
-
printDivider();
|
|
293
|
-
const { getContextWindow } = await import("./context-window.js");
|
|
294
|
-
const ctxWindow = await getContextWindow(modelId);
|
|
295
|
-
printDone(stepCount, usage, ctxWindow);
|
|
296
|
-
}
|
|
297
|
-
else {
|
|
298
|
-
printDivider();
|
|
299
|
-
}
|
|
300
|
-
}
|
|
199
|
+
return await result.usage;
|
|
301
200
|
}
|
|
302
|
-
catch
|
|
303
|
-
|
|
304
|
-
const cleaned = stripThinkingFromAssistantText(assistantText);
|
|
305
|
-
if (cleaned.trim())
|
|
306
|
-
messages.push({ role: "assistant", content: cleaned });
|
|
307
|
-
if (callbacks?.onRunFinish)
|
|
308
|
-
callbacks.onRunFinish({ stepCount, usage: undefined, hasError: false, aborted: true });
|
|
309
|
-
return;
|
|
310
|
-
}
|
|
311
|
-
if (callbacks?.onStreamError)
|
|
312
|
-
callbacks.onStreamError(err.message);
|
|
313
|
-
else {
|
|
314
|
-
printDivider();
|
|
315
|
-
console.error(`\x1b[31mError: ${err.message}\x1b[0m`);
|
|
316
|
-
}
|
|
201
|
+
catch {
|
|
202
|
+
return undefined;
|
|
317
203
|
}
|
|
318
204
|
}
|
|
319
|
-
|
|
320
|
-
const model = resolveModel(modelId);
|
|
321
|
-
|
|
205
|
+
async function runOnceCore(messages, systemPrompt, modelId, abortSignal, callbacks, tracker, mode, options) {
|
|
206
|
+
const model = resolveModel(modelId, options?.providerName);
|
|
207
|
+
log("info", `run start mode=${mode} messages=${messages.length} model=${typeof model === "string" ? model : model.modelId}`);
|
|
208
|
+
const cbs = callbacks ?? {};
|
|
209
|
+
const sampling = loadConfig().sampling ?? {};
|
|
210
|
+
const temperature = options?.temperature ?? sampling.temperature;
|
|
211
|
+
const maxTokens = options?.maxTokens ?? sampling.maxTokens;
|
|
212
|
+
const topP = options?.topP ?? sampling.topP;
|
|
322
213
|
// Auto-compact if context is getting too large
|
|
323
|
-
if (needsCompaction(messages, tracker)) {
|
|
324
|
-
if (
|
|
325
|
-
|
|
214
|
+
if (await needsCompaction(messages, tracker)) {
|
|
215
|
+
if (cbs.onCompaction) {
|
|
216
|
+
cbs.onCompaction("compacting_start");
|
|
326
217
|
}
|
|
327
|
-
else {
|
|
218
|
+
else if (!callbacks) {
|
|
328
219
|
console.log("\x1b[90m⟳ Compacting context...\x1b[0m");
|
|
329
220
|
}
|
|
330
|
-
const result = await compactMessages(messages, model);
|
|
221
|
+
const result = await compactMessages(messages, model, { abortSignal });
|
|
331
222
|
if (result.compacted) {
|
|
223
|
+
if (result.usage && tracker)
|
|
224
|
+
tracker.add(result.usage);
|
|
332
225
|
messages.length = 0;
|
|
333
226
|
messages.push(...result.messages);
|
|
334
227
|
// Auto-continue: inject a message so the agent keeps working
|
|
335
228
|
if (result.shouldContinue) {
|
|
336
229
|
const continueText = result.replayText ||
|
|
337
230
|
"Continue if you have next steps, or stop and ask for clarification if you are unsure how to proceed.";
|
|
338
|
-
|
|
231
|
+
const continueMsg = { role: "user", content: continueText };
|
|
232
|
+
messages.push(continueMsg);
|
|
233
|
+
markSyntheticMessage(continueMsg);
|
|
339
234
|
}
|
|
340
235
|
if (tracker)
|
|
341
236
|
tracker.resetContext();
|
|
342
|
-
if (
|
|
343
|
-
|
|
237
|
+
if (cbs.onCompaction) {
|
|
238
|
+
cbs.onCompaction(`compacted_ok estimated_tokens=${estimateTokens(messages)}`);
|
|
344
239
|
}
|
|
345
|
-
else {
|
|
240
|
+
else if (!callbacks) {
|
|
346
241
|
console.log(`\x1b[90m ✓ Compacted (${estimateTokens(messages)} tokens estimated)\x1b[0m`);
|
|
347
242
|
}
|
|
348
243
|
}
|
|
349
244
|
}
|
|
350
|
-
|
|
351
|
-
const
|
|
352
|
-
const mcpTools = getMcpTools();
|
|
353
|
-
const memoryTools = getMemoryTools();
|
|
354
|
-
const pluginTools = await loadPluginTools();
|
|
355
|
-
const skills = getSkills();
|
|
356
|
-
const allTools = { ...builtinTools, ...memoryTools, ...pluginTools };
|
|
357
|
-
for (const [id, t] of Object.entries(mcpTools)) {
|
|
358
|
-
allTools[id] = t;
|
|
359
|
-
}
|
|
360
|
-
if (skills.length > 0) {
|
|
361
|
-
allTools["skill"] = getSkillsTool();
|
|
362
|
-
}
|
|
245
|
+
const { tools: allTools, promptSections } = await buildTools(mode, modelId, abortSignal, options?.planMode, messages, tracker);
|
|
246
|
+
const system = [systemPrompt, ...promptSections].join("\n\n");
|
|
363
247
|
let stepCount = 0;
|
|
248
|
+
let maxStepsReached = false;
|
|
364
249
|
let hasError = false;
|
|
250
|
+
let budgetExceeded = false;
|
|
251
|
+
const cfg = loadConfig();
|
|
252
|
+
const budgetLimit = cfg.budget?.maxCostUSD;
|
|
253
|
+
let budgetPrice = null;
|
|
254
|
+
if (budgetLimit != null && budgetLimit > 0) {
|
|
255
|
+
const provider = options?.providerName
|
|
256
|
+
? cfg.providers?.find((p) => p.name === options.providerName)
|
|
257
|
+
: getActiveProvider(cfg);
|
|
258
|
+
budgetPrice = await getModelPrice(modelId ?? provider?.defaultModel ?? "");
|
|
259
|
+
}
|
|
260
|
+
const checkBudget = () => {
|
|
261
|
+
if (!tracker || budgetLimit == null || budgetLimit <= 0)
|
|
262
|
+
return false;
|
|
263
|
+
const cost = estimateCost({ inputTokens: tracker.totalInputTokens, outputTokens: tracker.totalOutputTokens }, budgetPrice);
|
|
264
|
+
if (cost != null && cost > budgetLimit) {
|
|
265
|
+
budgetExceeded = true;
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
268
|
+
return false;
|
|
269
|
+
};
|
|
270
|
+
// Internal controller so the budget limit can abort mid-run; external
|
|
271
|
+
// abort requests are forwarded to it.
|
|
272
|
+
const budgetController = new AbortController();
|
|
273
|
+
const forwardAbort = () => budgetController.abort();
|
|
274
|
+
abortSignal?.addEventListener("abort", forwardAbort, { once: true });
|
|
365
275
|
const doomLoop = new DoomLoopDetector();
|
|
276
|
+
const toolCalls = [];
|
|
277
|
+
const toolResults = [];
|
|
366
278
|
const result = streamText({
|
|
367
279
|
model,
|
|
368
|
-
system
|
|
280
|
+
system,
|
|
369
281
|
messages,
|
|
370
282
|
tools: allTools,
|
|
371
283
|
stopWhen: stepCountIs(MAX_STEPS),
|
|
372
284
|
maxRetries: 3,
|
|
373
|
-
abortSignal,
|
|
374
|
-
|
|
375
|
-
|
|
285
|
+
abortSignal: budgetController.signal,
|
|
286
|
+
...(temperature != null ? { temperature } : {}),
|
|
287
|
+
...(maxTokens != null ? { maxTokens } : {}),
|
|
288
|
+
...(topP != null ? { topP } : {}),
|
|
289
|
+
onStepFinish({ stepNumber, usage }) {
|
|
290
|
+
stepCount = stepNumber;
|
|
291
|
+
if (usage && tracker)
|
|
292
|
+
tracker.update(usage);
|
|
293
|
+
if (stepNumber >= MAX_STEPS)
|
|
294
|
+
maxStepsReached = true;
|
|
295
|
+
if (checkBudget())
|
|
296
|
+
budgetController.abort();
|
|
297
|
+
},
|
|
298
|
+
onError({ error }) {
|
|
299
|
+
log("warn", `step failed (will retry): ${String(error)}`);
|
|
376
300
|
},
|
|
377
|
-
onError() { },
|
|
378
301
|
});
|
|
379
|
-
Promise.resolve(result.usage).catch(() => { });
|
|
380
302
|
let rawText = "";
|
|
381
303
|
let assistantText = "";
|
|
382
304
|
const md = new MarkdownRenderer();
|
|
@@ -384,13 +306,42 @@ export async function runOnce(messages, instructions, modelId, abortSignal, call
|
|
|
384
306
|
const emitThinking = (t) => {
|
|
385
307
|
if (!t)
|
|
386
308
|
return;
|
|
387
|
-
if (
|
|
388
|
-
|
|
309
|
+
if (cbs.onThinkingDelta)
|
|
310
|
+
cbs.onThinkingDelta(t);
|
|
389
311
|
else
|
|
390
312
|
writeThinkingDelta(t);
|
|
391
313
|
};
|
|
314
|
+
const emitDisplay = (delta) => {
|
|
315
|
+
if (cbs.onAssistantDisplayDelta) {
|
|
316
|
+
cbs.onAssistantDisplayDelta(delta);
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
const formatted = md.write(delta);
|
|
320
|
+
if (formatted)
|
|
321
|
+
process.stdout.write(formatted);
|
|
322
|
+
};
|
|
323
|
+
const flushSplit = () => {
|
|
324
|
+
const out = thinkingSplit.flush();
|
|
325
|
+
emitThinking(out.thinking);
|
|
326
|
+
if (out.display) {
|
|
327
|
+
rawText += out.display;
|
|
328
|
+
assistantText += out.display;
|
|
329
|
+
emitDisplay(out.display);
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
const flushOutput = () => {
|
|
333
|
+
flushSplit();
|
|
334
|
+
if (!callbacks) {
|
|
335
|
+
const flushed = md.flush();
|
|
336
|
+
if (flushed)
|
|
337
|
+
process.stdout.write(flushed);
|
|
338
|
+
if (rawText.trim())
|
|
339
|
+
console.log();
|
|
340
|
+
}
|
|
341
|
+
rawText = "";
|
|
342
|
+
};
|
|
392
343
|
try {
|
|
393
|
-
for await (const event of result.fullStream) {
|
|
344
|
+
streamLoop: for await (const event of result.fullStream) {
|
|
394
345
|
switch (event.type) {
|
|
395
346
|
case "text-delta": {
|
|
396
347
|
const { display, thinking } = thinkingSplit.feed(event.text);
|
|
@@ -398,114 +349,65 @@ export async function runOnce(messages, instructions, modelId, abortSignal, call
|
|
|
398
349
|
if (display) {
|
|
399
350
|
rawText += display;
|
|
400
351
|
assistantText += display;
|
|
401
|
-
|
|
402
|
-
callbacks.onAssistantDisplayDelta(display);
|
|
403
|
-
else {
|
|
404
|
-
const formatted = md.write(display);
|
|
405
|
-
if (formatted)
|
|
406
|
-
process.stdout.write(formatted);
|
|
407
|
-
}
|
|
352
|
+
emitDisplay(display);
|
|
408
353
|
}
|
|
409
354
|
break;
|
|
410
355
|
}
|
|
411
356
|
case "tool-call": {
|
|
412
|
-
|
|
357
|
+
toolCalls.push({ toolCallId: event.toolCallId, toolName: event.toolName, input: event.input });
|
|
413
358
|
if (doomLoop.record(event.toolName, event.input)) {
|
|
414
|
-
const warning = `\x1b[33m⚠ Doom loop detected: "${event.toolName}" called ${3} times with same args. Breaking loop.\x1b[0m`;
|
|
415
|
-
if (callbacks?.onStreamError)
|
|
416
|
-
callbacks.onStreamError(warning);
|
|
417
|
-
else
|
|
418
|
-
console.log(`\n${warning}`);
|
|
419
359
|
hasError = true;
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
if (callbacks?.onAssistantDisplayDelta)
|
|
428
|
-
callbacks.onAssistantDisplayDelta(splitFlush.display);
|
|
429
|
-
else {
|
|
430
|
-
const extra = md.write(splitFlush.display);
|
|
431
|
-
if (extra)
|
|
432
|
-
process.stdout.write(extra);
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
if (!callbacks) {
|
|
436
|
-
const flushed = md.flush();
|
|
437
|
-
if (flushed)
|
|
438
|
-
process.stdout.write(flushed);
|
|
439
|
-
if (rawText.trim())
|
|
440
|
-
console.log();
|
|
360
|
+
const warning = `Doom loop detected: "${event.toolName}" called 3 times with same args. Breaking loop.`;
|
|
361
|
+
if (cbs.onStreamError)
|
|
362
|
+
cbs.onStreamError(warning);
|
|
363
|
+
else
|
|
364
|
+
console.log(`\n\x1b[33m⚠ ${warning}\x1b[0m`);
|
|
365
|
+
flushOutput();
|
|
366
|
+
break streamLoop;
|
|
441
367
|
}
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
368
|
+
logToolCall(event.toolName, event.input);
|
|
369
|
+
flushOutput();
|
|
370
|
+
if (cbs.onToolCall)
|
|
371
|
+
cbs.onToolCall(event.toolName, event.input, event.toolCallId);
|
|
445
372
|
else
|
|
446
373
|
printToolCall(event.toolName, event.input);
|
|
447
374
|
break;
|
|
448
375
|
}
|
|
449
376
|
case "tool-result":
|
|
450
|
-
|
|
451
|
-
|
|
377
|
+
case "tool-error": {
|
|
378
|
+
const isError = event.type === "tool-error";
|
|
379
|
+
const output = isError ? event.error : event.output;
|
|
380
|
+
const displayed = isError ? `Error: ${event.error}` : event.output;
|
|
381
|
+
toolResults.push({ toolCallId: event.toolCallId, toolName: event.toolName, output });
|
|
382
|
+
logToolResult(event.toolName, displayed);
|
|
383
|
+
if (cbs.onToolResult)
|
|
384
|
+
cbs.onToolResult(event.toolName, displayed, { toolCallId: event.toolCallId, isError });
|
|
452
385
|
else
|
|
453
|
-
printToolResult(event.toolName,
|
|
386
|
+
printToolResult(event.toolName, displayed, isError);
|
|
454
387
|
break;
|
|
388
|
+
}
|
|
455
389
|
case "error":
|
|
456
390
|
hasError = true;
|
|
457
391
|
const errorMsg = String(event.error);
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
else if (errorMsg.includes("Forbidden") || errorMsg.includes("Unauthorized") || errorMsg.includes("API key")) {
|
|
462
|
-
console.error(`\x1b[31mAuthentication error: Check your API key.\x1b[0m`);
|
|
392
|
+
log("error", `stream error: ${errorMsg}`);
|
|
393
|
+
if (cbs.onStreamError) {
|
|
394
|
+
cbs.onStreamError(errorMsg);
|
|
463
395
|
}
|
|
464
396
|
else {
|
|
465
|
-
console.error(`\x1b[
|
|
397
|
+
console.error(`\x1b[31m${formatErrorMessage(errorMsg)}\x1b[0m`);
|
|
466
398
|
}
|
|
467
399
|
break;
|
|
468
|
-
case "finish":
|
|
469
|
-
break;
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
const splitEnd = thinkingSplit.flush();
|
|
473
|
-
emitThinking(splitEnd.thinking);
|
|
474
|
-
if (splitEnd.display) {
|
|
475
|
-
rawText += splitEnd.display;
|
|
476
|
-
assistantText += splitEnd.display;
|
|
477
|
-
if (callbacks?.onAssistantDisplayDelta)
|
|
478
|
-
callbacks.onAssistantDisplayDelta(splitEnd.display);
|
|
479
|
-
else {
|
|
480
|
-
const tail = md.write(splitEnd.display);
|
|
481
|
-
if (tail)
|
|
482
|
-
process.stdout.write(tail);
|
|
483
400
|
}
|
|
484
401
|
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
console.log();
|
|
491
|
-
}
|
|
492
|
-
const cleanedAssistant = stripThinkingFromAssistantText(assistantText);
|
|
493
|
-
if (cleanedAssistant.trim()) {
|
|
494
|
-
messages.push({ role: "assistant", content: cleanedAssistant });
|
|
495
|
-
}
|
|
496
|
-
let usage;
|
|
497
|
-
try {
|
|
498
|
-
usage = await result.usage;
|
|
499
|
-
}
|
|
500
|
-
catch {
|
|
501
|
-
usage = undefined;
|
|
402
|
+
flushOutput();
|
|
403
|
+
pushTurn(messages, assistantText, toolCalls, toolResults);
|
|
404
|
+
const usage = await safeUsage(result);
|
|
405
|
+
if (maxStepsReached && !callbacks) {
|
|
406
|
+
console.log(`\x1b[33m⚠ Reached the ${MAX_STEPS}-step limit for this turn. Send a follow-up message to continue.\x1b[0m`);
|
|
502
407
|
}
|
|
503
|
-
|
|
504
|
-
if (
|
|
505
|
-
|
|
506
|
-
}
|
|
507
|
-
if (callbacks?.onRunFinish) {
|
|
508
|
-
callbacks.onRunFinish({ stepCount, usage, hasError, aborted: false });
|
|
408
|
+
log("info", `run end steps=${stepCount} tokens_in=${usage?.inputTokens ?? 0} tokens_out=${usage?.outputTokens ?? 0}`);
|
|
409
|
+
if (cbs.onRunFinish) {
|
|
410
|
+
cbs.onRunFinish({ stepCount, usage, hasError, aborted: false, maxStepsReached, budgetExceeded });
|
|
509
411
|
}
|
|
510
412
|
else {
|
|
511
413
|
if (hasError) {
|
|
@@ -513,68 +415,51 @@ export async function runOnce(messages, instructions, modelId, abortSignal, call
|
|
|
513
415
|
return;
|
|
514
416
|
}
|
|
515
417
|
printDivider();
|
|
516
|
-
const { getContextWindow } = await import("./context-window.js");
|
|
517
418
|
const ctxWindow = await getContextWindow(modelId);
|
|
518
419
|
printDone(stepCount, usage, ctxWindow);
|
|
420
|
+
if (budgetExceeded)
|
|
421
|
+
console.log(`\x1b[33m⚠ 已达到预算上限 ($${budgetLimit}),运行已中断\x1b[0m`);
|
|
519
422
|
}
|
|
520
423
|
}
|
|
521
424
|
catch (err) {
|
|
522
425
|
if (!callbacks && rawText.trim())
|
|
523
426
|
console.log();
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
process.stdout.write(w);
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
if (!callbacks)
|
|
538
|
-
process.stdout.write(md.flush());
|
|
539
|
-
const cleaned = stripThinkingFromAssistantText(assistantText);
|
|
540
|
-
if (cleaned.trim()) {
|
|
541
|
-
messages.push({ role: "assistant", content: cleaned });
|
|
542
|
-
}
|
|
543
|
-
let usage;
|
|
544
|
-
try {
|
|
545
|
-
usage = await result.usage;
|
|
546
|
-
}
|
|
547
|
-
catch {
|
|
548
|
-
usage = undefined;
|
|
549
|
-
}
|
|
550
|
-
callbacks?.onRunFinish?.({ stepCount, usage, hasError: false, aborted: true });
|
|
427
|
+
const aborted = abortSignal?.aborted || (err instanceof Error && err.name === "AbortError");
|
|
428
|
+
flushSplit();
|
|
429
|
+
if (!callbacks)
|
|
430
|
+
process.stdout.write(md.flush());
|
|
431
|
+
pushTurn(messages, assistantText, toolCalls, toolResults);
|
|
432
|
+
const usage = await safeUsage(result);
|
|
433
|
+
if (aborted) {
|
|
434
|
+
if (budgetExceeded && !callbacks)
|
|
435
|
+
console.log(`\x1b[33m⚠ 已达到预算上限 ($${budgetLimit}),运行已中断\x1b[0m`);
|
|
436
|
+
cbs.onRunFinish?.({ stepCount, usage, hasError, aborted: true, maxStepsReached, budgetExceeded });
|
|
551
437
|
return;
|
|
552
438
|
}
|
|
553
439
|
if (!callbacks)
|
|
554
440
|
printDivider();
|
|
555
|
-
const msg = err.message
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
console.error(`\x1b[31mAuthentication error: Check your API key.\x1b[0m`);
|
|
441
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
442
|
+
log("error", msg);
|
|
443
|
+
const display = formatErrorMessage(msg);
|
|
444
|
+
if (cbs.onStreamError) {
|
|
445
|
+
cbs.onStreamError(msg);
|
|
561
446
|
}
|
|
562
|
-
else if (
|
|
563
|
-
console.error(`\x1b[
|
|
564
|
-
}
|
|
565
|
-
else if (msg.includes("timeout") || msg.includes("ETIMEDOUT") || msg.includes("ECONNRESET")) {
|
|
566
|
-
console.error(`\x1b[31mNetwork error (retries exhausted): ${msg}\x1b[0m`);
|
|
447
|
+
else if (display !== msg) {
|
|
448
|
+
console.error(`\x1b[31m${display}\x1b[0m`);
|
|
567
449
|
}
|
|
568
450
|
else {
|
|
569
451
|
console.error(`\x1b[31mError: ${msg}\x1b[0m`);
|
|
570
452
|
}
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
catch {
|
|
576
|
-
usage = undefined;
|
|
577
|
-
}
|
|
578
|
-
callbacks?.onRunFinish?.({ stepCount, usage, hasError: true, aborted: false });
|
|
453
|
+
cbs.onRunFinish?.({ stepCount, usage, hasError: true, aborted: false, maxStepsReached, budgetExceeded });
|
|
454
|
+
}
|
|
455
|
+
finally {
|
|
456
|
+
abortSignal?.removeEventListener("abort", forwardAbort);
|
|
579
457
|
}
|
|
580
458
|
}
|
|
459
|
+
export async function runOnce(messages, instructions, modelId, abortSignal, callbacks, tracker, options) {
|
|
460
|
+
await runOnceCore(messages, buildSystemPrompt(instructions), modelId, abortSignal, callbacks, tracker, "chat", options);
|
|
461
|
+
}
|
|
462
|
+
/** runOnce variant that accepts a pre-built system prompt (for code mode) */
|
|
463
|
+
export async function runOnceWithSystem(messages, systemPrompt, modelId, abortSignal, callbacks, tracker, options) {
|
|
464
|
+
await runOnceCore(messages, systemPrompt, modelId, abortSignal, callbacks, tracker, "code", options);
|
|
465
|
+
}
|