grok-agent 0.5.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/CHANGELOG.md +113 -0
- package/LICENSE +190 -0
- package/README.md +449 -0
- package/bin/grok-cli.js +26 -0
- package/dist/agent.d.ts +4 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +898 -0
- package/dist/agent.js.map +1 -0
- package/dist/approvals.d.ts +4 -0
- package/dist/approvals.d.ts.map +1 -0
- package/dist/approvals.js +90 -0
- package/dist/approvals.js.map +1 -0
- package/dist/batch-api.d.ts +11 -0
- package/dist/batch-api.d.ts.map +1 -0
- package/dist/batch-api.js +101 -0
- package/dist/batch-api.js.map +1 -0
- package/dist/cli-errors.d.ts +6 -0
- package/dist/cli-errors.d.ts.map +1 -0
- package/dist/cli-errors.js +80 -0
- package/dist/cli-errors.js.map +1 -0
- package/dist/client.d.ts +6 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +41 -0
- package/dist/client.js.map +1 -0
- package/dist/collections-api.d.ts +11 -0
- package/dist/collections-api.d.ts.map +1 -0
- package/dist/collections-api.js +144 -0
- package/dist/collections-api.js.map +1 -0
- package/dist/compaction.d.ts +11 -0
- package/dist/compaction.d.ts.map +1 -0
- package/dist/compaction.js +94 -0
- package/dist/compaction.js.map +1 -0
- package/dist/config.d.ts +12 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +142 -0
- package/dist/config.js.map +1 -0
- package/dist/diff.d.ts +5 -0
- package/dist/diff.d.ts.map +1 -0
- package/dist/diff.js +80 -0
- package/dist/diff.js.map +1 -0
- package/dist/hooks.d.ts +11 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +30 -0
- package/dist/hooks.js.map +1 -0
- package/dist/image.d.ts +8 -0
- package/dist/image.d.ts.map +1 -0
- package/dist/image.js +58 -0
- package/dist/image.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1264 -0
- package/dist/index.js.map +1 -0
- package/dist/json-output.d.ts +27 -0
- package/dist/json-output.d.ts.map +1 -0
- package/dist/json-output.js +62 -0
- package/dist/json-output.js.map +1 -0
- package/dist/notifications.d.ts +6 -0
- package/dist/notifications.d.ts.map +1 -0
- package/dist/notifications.js +25 -0
- package/dist/notifications.js.map +1 -0
- package/dist/project-context.d.ts +6 -0
- package/dist/project-context.d.ts.map +1 -0
- package/dist/project-context.js +34 -0
- package/dist/project-context.js.map +1 -0
- package/dist/response-utils.d.ts +10 -0
- package/dist/response-utils.d.ts.map +1 -0
- package/dist/response-utils.js +123 -0
- package/dist/response-utils.js.map +1 -0
- package/dist/server-tools.d.ts +7 -0
- package/dist/server-tools.d.ts.map +1 -0
- package/dist/server-tools.js +205 -0
- package/dist/server-tools.js.map +1 -0
- package/dist/session.d.ts +46 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +305 -0
- package/dist/session.js.map +1 -0
- package/dist/stream.d.ts +22 -0
- package/dist/stream.d.ts.map +1 -0
- package/dist/stream.js +102 -0
- package/dist/stream.js.map +1 -0
- package/dist/system-prompt.d.ts +3 -0
- package/dist/system-prompt.d.ts.map +1 -0
- package/dist/system-prompt.js +64 -0
- package/dist/system-prompt.js.map +1 -0
- package/dist/tools/bash.d.ts +8 -0
- package/dist/tools/bash.d.ts.map +1 -0
- package/dist/tools/bash.js +49 -0
- package/dist/tools/bash.js.map +1 -0
- package/dist/tools/definitions.d.ts +3 -0
- package/dist/tools/definitions.d.ts.map +1 -0
- package/dist/tools/definitions.js +190 -0
- package/dist/tools/definitions.js.map +1 -0
- package/dist/tools/edit-file.d.ts +10 -0
- package/dist/tools/edit-file.d.ts.map +1 -0
- package/dist/tools/edit-file.js +73 -0
- package/dist/tools/edit-file.js.map +1 -0
- package/dist/tools/glob.d.ts +7 -0
- package/dist/tools/glob.d.ts.map +1 -0
- package/dist/tools/glob.js +44 -0
- package/dist/tools/glob.js.map +1 -0
- package/dist/tools/grep.d.ts +10 -0
- package/dist/tools/grep.d.ts.map +1 -0
- package/dist/tools/grep.js +102 -0
- package/dist/tools/grep.js.map +1 -0
- package/dist/tools/index.d.ts +10 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +41 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/list-dir.d.ts +7 -0
- package/dist/tools/list-dir.d.ts.map +1 -0
- package/dist/tools/list-dir.js +68 -0
- package/dist/tools/list-dir.js.map +1 -0
- package/dist/tools/policy.d.ts +4 -0
- package/dist/tools/policy.d.ts.map +1 -0
- package/dist/tools/policy.js +36 -0
- package/dist/tools/policy.js.map +1 -0
- package/dist/tools/read-file.d.ts +8 -0
- package/dist/tools/read-file.d.ts.map +1 -0
- package/dist/tools/read-file.js +45 -0
- package/dist/tools/read-file.js.map +1 -0
- package/dist/tools/write-file.d.ts +7 -0
- package/dist/tools/write-file.d.ts.map +1 -0
- package/dist/tools/write-file.js +35 -0
- package/dist/tools/write-file.js.map +1 -0
- package/dist/truncation.d.ts +13 -0
- package/dist/truncation.d.ts.map +1 -0
- package/dist/truncation.js +55 -0
- package/dist/truncation.js.map +1 -0
- package/dist/types.d.ts +159 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/usage.d.ts +17 -0
- package/dist/usage.d.ts.map +1 -0
- package/dist/usage.js +80 -0
- package/dist/usage.js.map +1 -0
- package/dist/voice-api.d.ts +16 -0
- package/dist/voice-api.d.ts.map +1 -0
- package/dist/voice-api.js +84 -0
- package/dist/voice-api.js.map +1 -0
- package/package.json +64 -0
package/dist/agent.js
ADDED
|
@@ -0,0 +1,898 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { createClient } from "./client.js";
|
|
5
|
+
import { buildSystemPrompt } from "./system-prompt.js";
|
|
6
|
+
import { toolDefinitions, executeTool, setMaxOutputTokens } from "./tools/index.js";
|
|
7
|
+
import { createAccumulator, processChunk, formatToolCall, formatToolResult, } from "./stream.js";
|
|
8
|
+
import { SessionManager } from "./session.js";
|
|
9
|
+
import { getImageDataUrl, buildImageMessageContent, buildImageInputContent } from "./image.js";
|
|
10
|
+
import { createUsageStats, extractUsageFromChatChunk, extractUsageFromResponse, accumulateUsage, formatUsage, } from "./usage.js";
|
|
11
|
+
import { checkApproval } from "./approvals.js";
|
|
12
|
+
import { runHooks } from "./hooks.js";
|
|
13
|
+
import { needsCompaction, compactConversation } from "./compaction.js";
|
|
14
|
+
import { formatApiError } from "./cli-errors.js";
|
|
15
|
+
import { collectResponseIncludes, serializeServerTools } from "./server-tools.js";
|
|
16
|
+
import { extractCitationsFromContent, extractServerToolUsage, getServerToolEvent, sanitizeResponseText, } from "./response-utils.js";
|
|
17
|
+
import { isJsonMode, emitSessionStarted, emitTurnStarted, emitTurnCompleted, emitToolCall, emitToolResult, emitServerToolCall, emitServerToolUsage, emitCitations, emitMessage, emitError, emitSessionCompleted, } from "./json-output.js";
|
|
18
|
+
function logServerToolCall(config, item) {
|
|
19
|
+
const event = getServerToolEvent(item);
|
|
20
|
+
if (!event)
|
|
21
|
+
return;
|
|
22
|
+
emitServerToolCall(event.name, event.payload);
|
|
23
|
+
if (config.showToolCalls && !isJsonMode()) {
|
|
24
|
+
console.error(chalk.magenta(` ◆ ${event.name}`) + chalk.dim(" (server-side)"));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function logServerToolUsage(config, response) {
|
|
28
|
+
const usage = extractServerToolUsage(response);
|
|
29
|
+
if (!usage || typeof usage !== "object")
|
|
30
|
+
return;
|
|
31
|
+
emitServerToolUsage(usage);
|
|
32
|
+
if (config.showServerToolUsage && !isJsonMode()) {
|
|
33
|
+
const summary = Object.entries(usage)
|
|
34
|
+
.map(([name, count]) => `${name}=${count}`)
|
|
35
|
+
.join(", ");
|
|
36
|
+
if (summary) {
|
|
37
|
+
console.error(chalk.dim(`Server tools: ${summary}`));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function countTurns(messages) {
|
|
42
|
+
return messages.filter((msg) => msg.role === "user").length;
|
|
43
|
+
}
|
|
44
|
+
function sessionEventsFromMessages(meta, messages) {
|
|
45
|
+
const events = [
|
|
46
|
+
{ ts: meta.updated, type: "meta", meta },
|
|
47
|
+
];
|
|
48
|
+
let turn = 0;
|
|
49
|
+
for (const msg of messages) {
|
|
50
|
+
const role = msg.role;
|
|
51
|
+
if (role === "user")
|
|
52
|
+
turn++;
|
|
53
|
+
const event = {
|
|
54
|
+
ts: new Date().toISOString(),
|
|
55
|
+
type: "msg",
|
|
56
|
+
role,
|
|
57
|
+
turn: role === "system" ? undefined : turn,
|
|
58
|
+
content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content),
|
|
59
|
+
};
|
|
60
|
+
const toolCalls = msg.tool_calls;
|
|
61
|
+
if (toolCalls) {
|
|
62
|
+
event.toolCalls = toolCalls.map((tc) => ({
|
|
63
|
+
id: tc.id,
|
|
64
|
+
name: tc.function?.name || "",
|
|
65
|
+
arguments: tc.function?.arguments || "",
|
|
66
|
+
}));
|
|
67
|
+
}
|
|
68
|
+
const toolCallId = msg.tool_call_id;
|
|
69
|
+
if (toolCallId)
|
|
70
|
+
event.toolCallId = toolCallId;
|
|
71
|
+
events.push(event);
|
|
72
|
+
}
|
|
73
|
+
return events;
|
|
74
|
+
}
|
|
75
|
+
function rollbackConversationMessages(messages, turns) {
|
|
76
|
+
if (turns <= 0)
|
|
77
|
+
return messages;
|
|
78
|
+
const system = messages[0];
|
|
79
|
+
const rest = messages.slice(1);
|
|
80
|
+
const turnGroups = [];
|
|
81
|
+
let current = [];
|
|
82
|
+
for (const msg of rest) {
|
|
83
|
+
if (msg.role === "user") {
|
|
84
|
+
if (current.length > 0)
|
|
85
|
+
turnGroups.push(current);
|
|
86
|
+
current = [msg];
|
|
87
|
+
}
|
|
88
|
+
else if (current.length > 0) {
|
|
89
|
+
current.push(msg);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (current.length > 0)
|
|
93
|
+
turnGroups.push(current);
|
|
94
|
+
const kept = turnGroups.slice(0, Math.max(0, turnGroups.length - turns)).flat();
|
|
95
|
+
return [system, ...kept];
|
|
96
|
+
}
|
|
97
|
+
function serializeClientToolDefinitions(tools) {
|
|
98
|
+
return tools.map((tool) => {
|
|
99
|
+
const fn = tool.function;
|
|
100
|
+
if (!fn)
|
|
101
|
+
return tool;
|
|
102
|
+
return {
|
|
103
|
+
type: "function",
|
|
104
|
+
name: fn.name,
|
|
105
|
+
description: fn.description,
|
|
106
|
+
parameters: fn.parameters,
|
|
107
|
+
};
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
// ─── Chat Completions Agent Loop (streaming) ─────────────────────────────────
|
|
111
|
+
async function runChatLoop(config, messages, options, session) {
|
|
112
|
+
const client = createClient(config);
|
|
113
|
+
const tools = [...toolDefinitions];
|
|
114
|
+
let turn = 0;
|
|
115
|
+
const totalUsage = createUsageStats();
|
|
116
|
+
let lastFingerprint = null;
|
|
117
|
+
const jsonMode = isJsonMode();
|
|
118
|
+
setMaxOutputTokens(config.maxOutputTokens);
|
|
119
|
+
const showOutput = !jsonMode;
|
|
120
|
+
// Add structured output if requested
|
|
121
|
+
const extraParams = {};
|
|
122
|
+
if (config.jsonSchema) {
|
|
123
|
+
try {
|
|
124
|
+
const schema = JSON.parse(config.jsonSchema);
|
|
125
|
+
extraParams.response_format = {
|
|
126
|
+
type: "json_schema",
|
|
127
|
+
json_schema: { name: "output", schema, strict: true },
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
if (!jsonMode)
|
|
132
|
+
console.error(chalk.yellow("Invalid JSON schema, ignoring --json-schema"));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
while (turn < options.maxTurns) {
|
|
136
|
+
turn++;
|
|
137
|
+
emitTurnStarted(turn);
|
|
138
|
+
if (options.verbose && turn > 1 && !jsonMode) {
|
|
139
|
+
console.error(chalk.dim(`\n--- turn ${turn} ---`));
|
|
140
|
+
}
|
|
141
|
+
// Auto-compact if conversation is getting long
|
|
142
|
+
if (turn > 1 && needsCompaction(messages)) {
|
|
143
|
+
messages = await compactConversation(config, messages);
|
|
144
|
+
}
|
|
145
|
+
const acc = createAccumulator();
|
|
146
|
+
const turnUsage = createUsageStats();
|
|
147
|
+
try {
|
|
148
|
+
const stream = await client.chat.completions.create({
|
|
149
|
+
model: config.model,
|
|
150
|
+
messages,
|
|
151
|
+
tools: tools.length > 0 ? tools : undefined,
|
|
152
|
+
stream: true,
|
|
153
|
+
max_tokens: config.maxTokens,
|
|
154
|
+
temperature: 0,
|
|
155
|
+
...extraParams,
|
|
156
|
+
});
|
|
157
|
+
for await (const chunk of stream) {
|
|
158
|
+
processChunk(acc, chunk, {
|
|
159
|
+
showReasoning: options.showReasoning,
|
|
160
|
+
showOutput,
|
|
161
|
+
});
|
|
162
|
+
extractUsageFromChatChunk(chunk, turnUsage);
|
|
163
|
+
// Track fingerprint
|
|
164
|
+
if (chunk?.system_fingerprint)
|
|
165
|
+
lastFingerprint = chunk.system_fingerprint;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
if (err?.status === 429) {
|
|
170
|
+
if (!jsonMode)
|
|
171
|
+
console.error(chalk.yellow("\nRate limited. Waiting 5s..."));
|
|
172
|
+
await sleep(5000);
|
|
173
|
+
turn--;
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (err?.status === 401) {
|
|
177
|
+
emitError("Authentication failed");
|
|
178
|
+
if (!jsonMode)
|
|
179
|
+
console.error(chalk.red("\nAuth failed. Check XAI_API_KEY."));
|
|
180
|
+
process.exit(1);
|
|
181
|
+
}
|
|
182
|
+
throw err;
|
|
183
|
+
}
|
|
184
|
+
accumulateUsage(totalUsage, turnUsage);
|
|
185
|
+
emitTurnCompleted(turn, turnUsage);
|
|
186
|
+
if (acc.toolCalls.length === 0) {
|
|
187
|
+
if (showOutput && acc.content)
|
|
188
|
+
process.stdout.write("\n");
|
|
189
|
+
emitMessage(acc.content);
|
|
190
|
+
if (session) {
|
|
191
|
+
session.manager.appendMessage(session.id, "assistant", acc.content);
|
|
192
|
+
session.manager.updateMeta(session.id, { turns: turn });
|
|
193
|
+
}
|
|
194
|
+
if (config.showUsage && !jsonMode) {
|
|
195
|
+
console.error(formatUsage(config.model, totalUsage));
|
|
196
|
+
}
|
|
197
|
+
return acc.content;
|
|
198
|
+
}
|
|
199
|
+
// Tool calls
|
|
200
|
+
const serializedCalls = acc.toolCalls
|
|
201
|
+
.filter(tc => tc.id && tc.function.name)
|
|
202
|
+
.map(tc => ({ id: tc.id, name: tc.function.name, arguments: tc.function.arguments }));
|
|
203
|
+
const assistantMsg = {
|
|
204
|
+
role: "assistant",
|
|
205
|
+
content: acc.content || null,
|
|
206
|
+
tool_calls: serializedCalls.map(tc => ({
|
|
207
|
+
id: tc.id,
|
|
208
|
+
type: "function",
|
|
209
|
+
function: { name: tc.name, arguments: tc.arguments },
|
|
210
|
+
})),
|
|
211
|
+
};
|
|
212
|
+
messages.push(assistantMsg);
|
|
213
|
+
if (session) {
|
|
214
|
+
session.manager.appendMessage(session.id, "assistant", acc.content || null, {
|
|
215
|
+
toolCalls: serializedCalls,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
if (showOutput && acc.content)
|
|
219
|
+
process.stdout.write("\n");
|
|
220
|
+
for (const tc of serializedCalls) {
|
|
221
|
+
emitToolCall(tc.name, tc.arguments, tc.id);
|
|
222
|
+
if (config.showToolCalls && !jsonMode) {
|
|
223
|
+
console.error(formatToolCall(tc.name, tc.arguments, options.verbose));
|
|
224
|
+
}
|
|
225
|
+
// Approval check
|
|
226
|
+
const approved = await checkApproval(config, tc.name, tc.arguments);
|
|
227
|
+
if (!approved) {
|
|
228
|
+
messages.push({ role: "tool", tool_call_id: tc.id, content: "Tool execution denied by user." });
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
// Pre-tool hook
|
|
232
|
+
runHooks(config.hooks, { type: "pre-tool", tool: tc.name, args: tc.arguments, sessionId: session?.id });
|
|
233
|
+
const result = await executeTool(tc.name, tc.arguments, options.cwd, {
|
|
234
|
+
sandboxMode: config.sandboxMode,
|
|
235
|
+
});
|
|
236
|
+
if (config.showToolCalls && !jsonMode) {
|
|
237
|
+
console.error(formatToolResult(result.output, result.error || false));
|
|
238
|
+
}
|
|
239
|
+
// Post-tool hook
|
|
240
|
+
runHooks(config.hooks, { type: "post-tool", tool: tc.name, args: tc.arguments, output: result.output, error: result.error, sessionId: session?.id });
|
|
241
|
+
emitToolResult(tc.id, result.output, result.error || false);
|
|
242
|
+
if (session) {
|
|
243
|
+
session.manager.appendToolExec(session.id, tc.name, tc.arguments, result.output, result.error || false);
|
|
244
|
+
session.manager.appendMessage(session.id, "tool", result.output, { toolCallId: tc.id });
|
|
245
|
+
}
|
|
246
|
+
messages.push({ role: "tool", tool_call_id: tc.id, content: result.output });
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (!jsonMode)
|
|
250
|
+
console.error(chalk.yellow(`\nMax turns (${options.maxTurns}) reached.`));
|
|
251
|
+
if (config.showUsage && !jsonMode)
|
|
252
|
+
console.error(formatUsage(config.model, totalUsage));
|
|
253
|
+
if (lastFingerprint && options.verbose && !jsonMode)
|
|
254
|
+
console.error(chalk.dim(`Fingerprint: ${lastFingerprint}`));
|
|
255
|
+
return "";
|
|
256
|
+
}
|
|
257
|
+
// ─── Responses API Agent Loop ─────────────────────────────────────────────────
|
|
258
|
+
async function runResponsesLoop(config, prompt, options, session, previousResponseId, imageUrls, fileIds) {
|
|
259
|
+
const client = createClient(config);
|
|
260
|
+
const cwd = options.cwd;
|
|
261
|
+
const totalUsage = createUsageStats();
|
|
262
|
+
let lastFingerprint = null;
|
|
263
|
+
const jsonMode = isJsonMode();
|
|
264
|
+
const showOutput = !jsonMode;
|
|
265
|
+
setMaxOutputTokens(config.maxOutputTokens);
|
|
266
|
+
// Build tools array
|
|
267
|
+
const tools = [
|
|
268
|
+
...serializeServerTools(config.serverTools, config.mcpServers),
|
|
269
|
+
...serializeClientToolDefinitions(toolDefinitions),
|
|
270
|
+
];
|
|
271
|
+
// Build input content
|
|
272
|
+
const inputContent = [];
|
|
273
|
+
if (imageUrls && imageUrls.length > 0) {
|
|
274
|
+
for (const url of imageUrls) {
|
|
275
|
+
inputContent.push(...buildImageInputContent(url, ""));
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
if (fileIds && fileIds.length > 0) {
|
|
279
|
+
for (const fid of fileIds) {
|
|
280
|
+
inputContent.push({ type: "input_file", file_id: fid });
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
inputContent.push({ type: "input_text", text: prompt });
|
|
284
|
+
// Build initial input
|
|
285
|
+
const input = [];
|
|
286
|
+
if (!previousResponseId) {
|
|
287
|
+
input.push({ role: "system", content: buildSystemPrompt(cwd, config) });
|
|
288
|
+
}
|
|
289
|
+
input.push({
|
|
290
|
+
role: "user",
|
|
291
|
+
content: inputContent.length === 1 ? prompt : inputContent,
|
|
292
|
+
});
|
|
293
|
+
let turn = 0;
|
|
294
|
+
let currentResponseId = previousResponseId || undefined;
|
|
295
|
+
while (turn < options.maxTurns) {
|
|
296
|
+
turn++;
|
|
297
|
+
emitTurnStarted(turn);
|
|
298
|
+
if (options.verbose && turn > 1 && !jsonMode) {
|
|
299
|
+
console.error(chalk.dim(`\n--- turn ${turn} ---`));
|
|
300
|
+
}
|
|
301
|
+
try {
|
|
302
|
+
const reqParams = {
|
|
303
|
+
model: config.model,
|
|
304
|
+
input: turn === 1 ? input : input,
|
|
305
|
+
tools: tools.length > 0 ? tools : undefined,
|
|
306
|
+
store: true,
|
|
307
|
+
};
|
|
308
|
+
const responseIncludes = collectResponseIncludes(config.serverTools, config.includeToolOutputs);
|
|
309
|
+
if (responseIncludes.length > 0)
|
|
310
|
+
reqParams.include = responseIncludes;
|
|
311
|
+
if (currentResponseId)
|
|
312
|
+
reqParams.previous_response_id = currentResponseId;
|
|
313
|
+
if (config.jsonSchema) {
|
|
314
|
+
try {
|
|
315
|
+
reqParams.text = {
|
|
316
|
+
format: {
|
|
317
|
+
type: "json_schema",
|
|
318
|
+
name: "output",
|
|
319
|
+
schema: JSON.parse(config.jsonSchema),
|
|
320
|
+
strict: true,
|
|
321
|
+
},
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
catch { /* ignore invalid schema */ }
|
|
325
|
+
}
|
|
326
|
+
const response = await client.responses.create(reqParams);
|
|
327
|
+
currentResponseId = response.id;
|
|
328
|
+
if (response?.system_fingerprint)
|
|
329
|
+
lastFingerprint = response.system_fingerprint;
|
|
330
|
+
logServerToolUsage(config, response);
|
|
331
|
+
// Track usage
|
|
332
|
+
const turnUsage = createUsageStats();
|
|
333
|
+
extractUsageFromResponse(response, turnUsage);
|
|
334
|
+
accumulateUsage(totalUsage, turnUsage);
|
|
335
|
+
emitTurnCompleted(turn, turnUsage);
|
|
336
|
+
if (session) {
|
|
337
|
+
session.manager.updateMeta(session.id, {
|
|
338
|
+
lastResponseId: currentResponseId || null,
|
|
339
|
+
turns: turn,
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
// Process output
|
|
343
|
+
const functionCalls = [];
|
|
344
|
+
let textContent = "";
|
|
345
|
+
const citations = [];
|
|
346
|
+
for (const item of response.output) {
|
|
347
|
+
if (item.type === "message") {
|
|
348
|
+
for (const part of item.content) {
|
|
349
|
+
if (part.type === "output_text" || part.type === "text") {
|
|
350
|
+
const cleanedText = sanitizeResponseText(part.text || "");
|
|
351
|
+
if (!cleanedText)
|
|
352
|
+
continue;
|
|
353
|
+
textContent += cleanedText;
|
|
354
|
+
if (showOutput)
|
|
355
|
+
process.stdout.write(cleanedText);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
citations.push(...extractCitationsFromContent(item.content || []));
|
|
359
|
+
}
|
|
360
|
+
else if (item.type === "function_call") {
|
|
361
|
+
functionCalls.push(item);
|
|
362
|
+
}
|
|
363
|
+
else if (getServerToolEvent(item)) {
|
|
364
|
+
logServerToolCall(config, item);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
// Display citations
|
|
368
|
+
if (config.showCitations && citations.length > 0) {
|
|
369
|
+
if (jsonMode) {
|
|
370
|
+
emitCitations(citations.slice(0, 10));
|
|
371
|
+
}
|
|
372
|
+
else {
|
|
373
|
+
console.error(chalk.dim("\n\nSources:"));
|
|
374
|
+
for (const c of citations.slice(0, 10)) {
|
|
375
|
+
console.error(chalk.dim(` ${c.title || c.url}`));
|
|
376
|
+
if (c.title)
|
|
377
|
+
console.error(chalk.dim(` ${c.url}`));
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
if (functionCalls.length === 0) {
|
|
382
|
+
if (showOutput && textContent)
|
|
383
|
+
process.stdout.write("\n");
|
|
384
|
+
emitMessage(textContent);
|
|
385
|
+
if (session)
|
|
386
|
+
session.manager.appendMessage(session.id, "assistant", textContent);
|
|
387
|
+
if (config.showUsage && !jsonMode)
|
|
388
|
+
console.error(formatUsage(config.model, totalUsage));
|
|
389
|
+
if (lastFingerprint && options.verbose && !jsonMode)
|
|
390
|
+
console.error(chalk.dim(`Fingerprint: ${lastFingerprint}`));
|
|
391
|
+
return textContent;
|
|
392
|
+
}
|
|
393
|
+
// Execute client-side tools
|
|
394
|
+
if (session) {
|
|
395
|
+
session.manager.appendMessage(session.id, "assistant", textContent || null, {
|
|
396
|
+
toolCalls: functionCalls.map((fc) => ({
|
|
397
|
+
id: fc.call_id, name: fc.name, arguments: fc.arguments,
|
|
398
|
+
})),
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
if (showOutput && textContent)
|
|
402
|
+
process.stdout.write("\n");
|
|
403
|
+
const toolOutputs = [];
|
|
404
|
+
for (const fc of functionCalls) {
|
|
405
|
+
emitToolCall(fc.name, fc.arguments, fc.call_id);
|
|
406
|
+
if (config.showToolCalls && !jsonMode) {
|
|
407
|
+
console.error(formatToolCall(fc.name, fc.arguments, options.verbose));
|
|
408
|
+
}
|
|
409
|
+
const approved = await checkApproval(config, fc.name, fc.arguments);
|
|
410
|
+
if (!approved) {
|
|
411
|
+
toolOutputs.push({ type: "function_call_output", call_id: fc.call_id, output: "Denied by user." });
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
runHooks(config.hooks, { type: "pre-tool", tool: fc.name, args: fc.arguments, sessionId: session?.id });
|
|
415
|
+
const result = await executeTool(fc.name, fc.arguments, cwd, {
|
|
416
|
+
sandboxMode: config.sandboxMode,
|
|
417
|
+
});
|
|
418
|
+
if (config.showToolCalls && !jsonMode) {
|
|
419
|
+
console.error(formatToolResult(result.output, result.error || false));
|
|
420
|
+
}
|
|
421
|
+
runHooks(config.hooks, { type: "post-tool", tool: fc.name, output: result.output, error: result.error, sessionId: session?.id });
|
|
422
|
+
emitToolResult(fc.call_id, result.output, result.error || false);
|
|
423
|
+
if (session) {
|
|
424
|
+
session.manager.appendToolExec(session.id, fc.name, fc.arguments, result.output, result.error || false);
|
|
425
|
+
session.manager.appendMessage(session.id, "tool", result.output, { toolCallId: fc.call_id });
|
|
426
|
+
}
|
|
427
|
+
toolOutputs.push({
|
|
428
|
+
type: "function_call_output",
|
|
429
|
+
call_id: fc.call_id,
|
|
430
|
+
output: result.output,
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
input.length = 0;
|
|
434
|
+
input.push(...toolOutputs);
|
|
435
|
+
}
|
|
436
|
+
catch (err) {
|
|
437
|
+
if (err?.status === 429) {
|
|
438
|
+
if (!jsonMode)
|
|
439
|
+
console.error(chalk.yellow("\nRate limited. Waiting 5s..."));
|
|
440
|
+
await sleep(5000);
|
|
441
|
+
turn--;
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
if (err?.status === 401) {
|
|
445
|
+
emitError("Authentication failed");
|
|
446
|
+
if (!jsonMode)
|
|
447
|
+
console.error(chalk.red("\nAuth failed. Check XAI_API_KEY."));
|
|
448
|
+
process.exit(1);
|
|
449
|
+
}
|
|
450
|
+
// Fallback to chat.completions if Responses API unavailable
|
|
451
|
+
if (err?.status === 404 || err?.message?.includes("responses")) {
|
|
452
|
+
if (!jsonMode) {
|
|
453
|
+
console.error(chalk.yellow("\nResponses API unavailable, falling back to chat.completions..."));
|
|
454
|
+
}
|
|
455
|
+
const msgs = [
|
|
456
|
+
{ role: "system", content: buildSystemPrompt(cwd, config) },
|
|
457
|
+
{ role: "user", content: prompt },
|
|
458
|
+
];
|
|
459
|
+
return runChatLoop(config, msgs, options, session);
|
|
460
|
+
}
|
|
461
|
+
throw err;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
if (!jsonMode)
|
|
465
|
+
console.error(chalk.yellow(`\nMax turns (${options.maxTurns}) reached.`));
|
|
466
|
+
if (config.showUsage && !jsonMode)
|
|
467
|
+
console.error(formatUsage(config.model, totalUsage));
|
|
468
|
+
if (lastFingerprint && options.verbose && !jsonMode)
|
|
469
|
+
console.error(chalk.dim(`Fingerprint: ${lastFingerprint}`));
|
|
470
|
+
return "";
|
|
471
|
+
}
|
|
472
|
+
// ─── File Upload Helper ──────────────────────────────────────────────────────
|
|
473
|
+
async function uploadFiles(config, cwd) {
|
|
474
|
+
if (config.fileAttachments.length === 0)
|
|
475
|
+
return [];
|
|
476
|
+
const client = createClient(config);
|
|
477
|
+
const fileIds = [];
|
|
478
|
+
const jsonMode = isJsonMode();
|
|
479
|
+
for (const filePath of config.fileAttachments) {
|
|
480
|
+
const resolved = path.resolve(cwd, filePath);
|
|
481
|
+
if (!fs.existsSync(resolved)) {
|
|
482
|
+
if (!jsonMode)
|
|
483
|
+
console.error(chalk.yellow(`File not found, skipping: ${filePath}`));
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
try {
|
|
487
|
+
if (!jsonMode)
|
|
488
|
+
console.error(chalk.dim(` Uploading ${filePath}...`));
|
|
489
|
+
const file = await client.files.create({
|
|
490
|
+
file: fs.createReadStream(resolved),
|
|
491
|
+
purpose: "assistants",
|
|
492
|
+
});
|
|
493
|
+
fileIds.push(file.id);
|
|
494
|
+
if (!jsonMode)
|
|
495
|
+
console.error(chalk.dim(` Uploaded: ${file.id}`));
|
|
496
|
+
}
|
|
497
|
+
catch (err) {
|
|
498
|
+
if (!jsonMode)
|
|
499
|
+
console.error(chalk.yellow(`Failed to upload ${filePath}: ${err.message}`));
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
return fileIds;
|
|
503
|
+
}
|
|
504
|
+
// ─── Public API ──────────────────────────────────────────────────────────────
|
|
505
|
+
export async function runAgent(config, prompt, options) {
|
|
506
|
+
const sessionMgr = config.ephemeral ? null : new SessionManager(config.sessionDir);
|
|
507
|
+
let sessionCtx = null;
|
|
508
|
+
let runtimeSessionId = `ephemeral-${Date.now().toString(36)}`;
|
|
509
|
+
if (sessionMgr) {
|
|
510
|
+
// Create or resume session
|
|
511
|
+
if (options.sessionId && sessionMgr.sessionExists(options.sessionId)) {
|
|
512
|
+
sessionCtx = { manager: sessionMgr, id: options.sessionId };
|
|
513
|
+
}
|
|
514
|
+
else {
|
|
515
|
+
const meta = sessionMgr.createSession({
|
|
516
|
+
model: config.model,
|
|
517
|
+
cwd: options.cwd,
|
|
518
|
+
name: options.sessionName || sessionMgr.autoName(prompt),
|
|
519
|
+
});
|
|
520
|
+
sessionCtx = { manager: sessionMgr, id: meta.id };
|
|
521
|
+
if (!isJsonMode())
|
|
522
|
+
console.error(chalk.dim(`Session: ${meta.id}`));
|
|
523
|
+
}
|
|
524
|
+
runtimeSessionId = sessionCtx.id;
|
|
525
|
+
sessionMgr.appendMessage(sessionCtx.id, "user", prompt);
|
|
526
|
+
}
|
|
527
|
+
else {
|
|
528
|
+
if (options.sessionId && !isJsonMode()) {
|
|
529
|
+
console.error(chalk.dim("(ephemeral mode ignores --resume/--fork state)"));
|
|
530
|
+
}
|
|
531
|
+
if (!isJsonMode())
|
|
532
|
+
console.error(chalk.dim("(ephemeral — no session saved)"));
|
|
533
|
+
}
|
|
534
|
+
runHooks(config.hooks, { type: "session-start", sessionId: runtimeSessionId });
|
|
535
|
+
emitSessionStarted(runtimeSessionId, config.model);
|
|
536
|
+
try {
|
|
537
|
+
// Resolve image inputs
|
|
538
|
+
const imageUrls = [];
|
|
539
|
+
for (const img of config.imageInputs) {
|
|
540
|
+
try {
|
|
541
|
+
imageUrls.push(getImageDataUrl(img, options.cwd));
|
|
542
|
+
}
|
|
543
|
+
catch (err) {
|
|
544
|
+
console.error(chalk.yellow(`Image error: ${err.message}`));
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
// Upload files if any
|
|
548
|
+
const fileIds = await uploadFiles(config, options.cwd);
|
|
549
|
+
// Determine API mode
|
|
550
|
+
const useResponses = config.useResponsesApi ||
|
|
551
|
+
config.mcpServers.length > 0 ||
|
|
552
|
+
config.serverTools.length > 0 ||
|
|
553
|
+
fileIds.length > 0;
|
|
554
|
+
if (useResponses) {
|
|
555
|
+
let prevResponseId = null;
|
|
556
|
+
if (sessionMgr && options.sessionId) {
|
|
557
|
+
const loaded = sessionMgr.loadSession(options.sessionId);
|
|
558
|
+
if (loaded?.meta.lastResponseId)
|
|
559
|
+
prevResponseId = loaded.meta.lastResponseId;
|
|
560
|
+
}
|
|
561
|
+
return await runResponsesLoop(config, prompt, options, sessionCtx, prevResponseId, imageUrls, fileIds);
|
|
562
|
+
}
|
|
563
|
+
// Default: chat.completions
|
|
564
|
+
let messages;
|
|
565
|
+
if (sessionMgr && options.sessionId) {
|
|
566
|
+
const loaded = sessionMgr.loadSession(options.sessionId);
|
|
567
|
+
if (loaded) {
|
|
568
|
+
messages = loaded.messages;
|
|
569
|
+
console.error(chalk.dim(`Resumed session with ${loaded.messages.length} messages`));
|
|
570
|
+
}
|
|
571
|
+
else {
|
|
572
|
+
messages = [{ role: "system", content: buildSystemPrompt(options.cwd, config) }];
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
else {
|
|
576
|
+
messages = [{ role: "system", content: buildSystemPrompt(options.cwd, config) }];
|
|
577
|
+
if (sessionCtx) {
|
|
578
|
+
sessionCtx.manager.appendMessage(sessionCtx.id, "system", buildSystemPrompt(options.cwd, config));
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
// Add image to user message if present
|
|
582
|
+
if (imageUrls.length > 0) {
|
|
583
|
+
const content = buildImageMessageContent(imageUrls[0], prompt);
|
|
584
|
+
messages.push({ role: "user", content });
|
|
585
|
+
}
|
|
586
|
+
else {
|
|
587
|
+
messages.push({ role: "user", content: prompt });
|
|
588
|
+
}
|
|
589
|
+
return await runChatLoop(config, messages, options, sessionCtx);
|
|
590
|
+
}
|
|
591
|
+
finally {
|
|
592
|
+
runHooks(config.hooks, { type: "session-end", sessionId: runtimeSessionId });
|
|
593
|
+
emitSessionCompleted(runtimeSessionId);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
export async function runInteractive(config, options) {
|
|
597
|
+
const readline = await import("node:readline");
|
|
598
|
+
const rl = readline.createInterface({
|
|
599
|
+
input: process.stdin,
|
|
600
|
+
output: process.stderr,
|
|
601
|
+
prompt: chalk.bold.blue("grok> "),
|
|
602
|
+
});
|
|
603
|
+
const sessionMgr = config.ephemeral ? null : new SessionManager(config.sessionDir);
|
|
604
|
+
let sessionId = null;
|
|
605
|
+
let conversationMessages;
|
|
606
|
+
const showOutput = !isJsonMode();
|
|
607
|
+
let activeModel = config.model;
|
|
608
|
+
const runtimeSessionId = config.ephemeral
|
|
609
|
+
? `ephemeral-${Date.now().toString(36)}`
|
|
610
|
+
: undefined;
|
|
611
|
+
if (config.ephemeral) {
|
|
612
|
+
if (options.sessionId && !isJsonMode()) {
|
|
613
|
+
console.error(chalk.dim("(ephemeral mode ignores --resume)"));
|
|
614
|
+
}
|
|
615
|
+
conversationMessages = [{ role: "system", content: buildSystemPrompt(options.cwd, config) }];
|
|
616
|
+
console.error(chalk.bold("Grok CLI") + chalk.dim(` (${activeModel}) — ephemeral`));
|
|
617
|
+
}
|
|
618
|
+
else if (sessionMgr && options.sessionId && sessionMgr.sessionExists(options.sessionId)) {
|
|
619
|
+
const loaded = sessionMgr.loadSession(options.sessionId);
|
|
620
|
+
if (loaded) {
|
|
621
|
+
sessionId = loaded.meta.id;
|
|
622
|
+
activeModel = loaded.meta.model || activeModel;
|
|
623
|
+
conversationMessages = loaded.messages;
|
|
624
|
+
console.error(chalk.bold("Grok CLI") + chalk.dim(` (${activeModel})`) +
|
|
625
|
+
chalk.green(` — resumed ${sessionId}`));
|
|
626
|
+
console.error(chalk.dim(`Loaded ${loaded.messages.length} messages`));
|
|
627
|
+
}
|
|
628
|
+
else {
|
|
629
|
+
const meta = sessionMgr.createSession({ model: config.model, cwd: options.cwd });
|
|
630
|
+
sessionId = meta.id;
|
|
631
|
+
conversationMessages = [{ role: "system", content: buildSystemPrompt(options.cwd, config) }];
|
|
632
|
+
sessionMgr.appendMessage(sessionId, "system", buildSystemPrompt(options.cwd, config));
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
else {
|
|
636
|
+
if (!sessionMgr) {
|
|
637
|
+
throw new Error("Interactive session storage is unavailable.");
|
|
638
|
+
}
|
|
639
|
+
const meta = sessionMgr.createSession({
|
|
640
|
+
model: activeModel, cwd: options.cwd,
|
|
641
|
+
name: options.sessionName || "Interactive session",
|
|
642
|
+
});
|
|
643
|
+
sessionId = meta.id;
|
|
644
|
+
conversationMessages = [{ role: "system", content: buildSystemPrompt(options.cwd, config) }];
|
|
645
|
+
sessionMgr.appendMessage(sessionId, "system", buildSystemPrompt(options.cwd, config));
|
|
646
|
+
console.error(chalk.bold("Grok CLI") + chalk.dim(` (${activeModel}) — ${sessionId}`));
|
|
647
|
+
}
|
|
648
|
+
console.error(chalk.dim("Commands: /session /sessions /usage /name /model /archive /compact /rollback /files exit\n"));
|
|
649
|
+
const totalUsage = createUsageStats();
|
|
650
|
+
const hookSessionId = sessionId || runtimeSessionId || `ephemeral-${Date.now().toString(36)}`;
|
|
651
|
+
runHooks(config.hooks, { type: "session-start", sessionId: hookSessionId });
|
|
652
|
+
emitSessionStarted(hookSessionId, activeModel);
|
|
653
|
+
try {
|
|
654
|
+
rl.prompt();
|
|
655
|
+
for await (const line of rl) {
|
|
656
|
+
const input = line.trim();
|
|
657
|
+
if (!input) {
|
|
658
|
+
rl.prompt();
|
|
659
|
+
continue;
|
|
660
|
+
}
|
|
661
|
+
if (input.toLowerCase() === "exit" || input.toLowerCase() === "quit") {
|
|
662
|
+
if (sessionId)
|
|
663
|
+
console.error(chalk.dim(`Session saved: ${sessionId}`));
|
|
664
|
+
else
|
|
665
|
+
console.error(chalk.dim("Session ended (ephemeral)"));
|
|
666
|
+
if (config.showUsage && totalUsage.totalTokens > 0) {
|
|
667
|
+
console.error(formatUsage(activeModel, totalUsage));
|
|
668
|
+
}
|
|
669
|
+
break;
|
|
670
|
+
}
|
|
671
|
+
if (input === "/session") {
|
|
672
|
+
if (sessionId)
|
|
673
|
+
console.error(chalk.dim(`ID: ${sessionId} | Messages: ${conversationMessages.length}`));
|
|
674
|
+
else
|
|
675
|
+
console.error(chalk.dim(`Ephemeral | Messages: ${conversationMessages.length}`));
|
|
676
|
+
rl.prompt();
|
|
677
|
+
continue;
|
|
678
|
+
}
|
|
679
|
+
if (input === "/sessions") {
|
|
680
|
+
if (!sessionId) {
|
|
681
|
+
console.error(chalk.dim("Ephemeral mode has no saved sessions."));
|
|
682
|
+
}
|
|
683
|
+
else {
|
|
684
|
+
const sessions = sessionMgr.listSessions();
|
|
685
|
+
for (const s of sessions.slice(0, 10)) {
|
|
686
|
+
const cur = s.id === sessionId ? chalk.green(" ←") : "";
|
|
687
|
+
console.error(chalk.cyan(s.id) + chalk.dim(` ${s.name}`) + cur);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
rl.prompt();
|
|
691
|
+
continue;
|
|
692
|
+
}
|
|
693
|
+
if (input === "/usage") {
|
|
694
|
+
console.error(formatUsage(activeModel, totalUsage));
|
|
695
|
+
rl.prompt();
|
|
696
|
+
continue;
|
|
697
|
+
}
|
|
698
|
+
if (input.startsWith("/name ")) {
|
|
699
|
+
const nextName = input.slice("/name ".length).trim();
|
|
700
|
+
if (!nextName) {
|
|
701
|
+
console.error(chalk.yellow("Usage: /name <new session name>"));
|
|
702
|
+
}
|
|
703
|
+
else if (!sessionId || !sessionMgr) {
|
|
704
|
+
console.error(chalk.dim("Ephemeral mode has no saved session to rename."));
|
|
705
|
+
}
|
|
706
|
+
else {
|
|
707
|
+
sessionMgr.renameSession(sessionId, nextName);
|
|
708
|
+
console.error(chalk.green(`Renamed session: ${nextName}`));
|
|
709
|
+
}
|
|
710
|
+
rl.prompt();
|
|
711
|
+
continue;
|
|
712
|
+
}
|
|
713
|
+
if (input === "/model") {
|
|
714
|
+
console.error(chalk.dim(`Current model: ${activeModel}`));
|
|
715
|
+
rl.prompt();
|
|
716
|
+
continue;
|
|
717
|
+
}
|
|
718
|
+
if (input.startsWith("/model ")) {
|
|
719
|
+
activeModel = input.slice("/model ".length).trim() || activeModel;
|
|
720
|
+
if (sessionId && sessionMgr) {
|
|
721
|
+
sessionMgr.updateMeta(sessionId, { model: activeModel });
|
|
722
|
+
}
|
|
723
|
+
console.error(chalk.green(`Switched model: ${activeModel}`));
|
|
724
|
+
rl.prompt();
|
|
725
|
+
continue;
|
|
726
|
+
}
|
|
727
|
+
if (input === "/archive") {
|
|
728
|
+
if (!sessionId || !sessionMgr) {
|
|
729
|
+
console.error(chalk.dim("Ephemeral mode has no saved session to archive."));
|
|
730
|
+
}
|
|
731
|
+
else if (sessionMgr.archiveSession(sessionId)) {
|
|
732
|
+
console.error(chalk.green(`Archived session: ${sessionId}`));
|
|
733
|
+
}
|
|
734
|
+
else {
|
|
735
|
+
console.error(chalk.red(`Unable to archive session: ${sessionId}`));
|
|
736
|
+
}
|
|
737
|
+
rl.prompt();
|
|
738
|
+
continue;
|
|
739
|
+
}
|
|
740
|
+
if (input === "/compact") {
|
|
741
|
+
conversationMessages = await compactConversation(config, conversationMessages);
|
|
742
|
+
if (sessionId && sessionMgr) {
|
|
743
|
+
const loaded = sessionMgr.loadSession(sessionId);
|
|
744
|
+
if (loaded) {
|
|
745
|
+
loaded.meta.turns = countTurns(conversationMessages);
|
|
746
|
+
loaded.meta.updated = new Date().toISOString();
|
|
747
|
+
loaded.meta.model = activeModel;
|
|
748
|
+
sessionMgr.rewriteSession(sessionId, sessionEventsFromMessages(loaded.meta, conversationMessages));
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
console.error(chalk.green("Compacted conversation history."));
|
|
752
|
+
rl.prompt();
|
|
753
|
+
continue;
|
|
754
|
+
}
|
|
755
|
+
if (input.startsWith("/rollback")) {
|
|
756
|
+
const turnsToRollback = parseInt(input.split(/\s+/)[1] || "1", 10);
|
|
757
|
+
if (Number.isNaN(turnsToRollback) || turnsToRollback < 1) {
|
|
758
|
+
console.error(chalk.yellow("Usage: /rollback <num-turns>"));
|
|
759
|
+
}
|
|
760
|
+
else if (sessionId && sessionMgr) {
|
|
761
|
+
if (sessionMgr.rollbackTurns(sessionId, turnsToRollback)) {
|
|
762
|
+
const reloaded = sessionMgr.loadSession(sessionId);
|
|
763
|
+
if (reloaded)
|
|
764
|
+
conversationMessages = reloaded.messages;
|
|
765
|
+
console.error(chalk.green(`Rolled back ${turnsToRollback} turn(s).`));
|
|
766
|
+
}
|
|
767
|
+
else {
|
|
768
|
+
console.error(chalk.red("Rollback failed."));
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
else {
|
|
772
|
+
conversationMessages = rollbackConversationMessages(conversationMessages, turnsToRollback);
|
|
773
|
+
console.error(chalk.green(`Rolled back ${turnsToRollback} turn(s) in ephemeral memory.`));
|
|
774
|
+
}
|
|
775
|
+
rl.prompt();
|
|
776
|
+
continue;
|
|
777
|
+
}
|
|
778
|
+
if (input.startsWith("/files ")) {
|
|
779
|
+
const query = input.slice("/files ".length).trim();
|
|
780
|
+
const result = await executeTool("glob", JSON.stringify({ pattern: `**/*${query}*` }), options.cwd, { sandboxMode: config.sandboxMode });
|
|
781
|
+
console.error(result.output);
|
|
782
|
+
rl.prompt();
|
|
783
|
+
continue;
|
|
784
|
+
}
|
|
785
|
+
// Auto-name
|
|
786
|
+
const meta = sessionId && sessionMgr ? sessionMgr.loadSession(sessionId) : null;
|
|
787
|
+
if (meta && sessionMgr && meta.meta.name === "Interactive session" && meta.meta.turns === 0) {
|
|
788
|
+
sessionMgr.updateMeta(meta.meta.id, { name: sessionMgr.autoName(input) });
|
|
789
|
+
}
|
|
790
|
+
conversationMessages.push({ role: "user", content: input });
|
|
791
|
+
if (sessionId && sessionMgr)
|
|
792
|
+
sessionMgr.appendMessage(sessionId, "user", input);
|
|
793
|
+
try {
|
|
794
|
+
let turn = 0;
|
|
795
|
+
while (turn < options.maxTurns) {
|
|
796
|
+
turn++;
|
|
797
|
+
emitTurnStarted(turn);
|
|
798
|
+
if (turn > 1 && needsCompaction(conversationMessages)) {
|
|
799
|
+
conversationMessages = await compactConversation(config, conversationMessages);
|
|
800
|
+
}
|
|
801
|
+
const acc = createAccumulator();
|
|
802
|
+
const turnUsage = createUsageStats();
|
|
803
|
+
const client = createClient(config);
|
|
804
|
+
const stream = await client.chat.completions.create({
|
|
805
|
+
model: activeModel,
|
|
806
|
+
messages: conversationMessages,
|
|
807
|
+
tools: toolDefinitions.length > 0 ? toolDefinitions : undefined,
|
|
808
|
+
stream: true,
|
|
809
|
+
max_tokens: config.maxTokens,
|
|
810
|
+
temperature: 0,
|
|
811
|
+
});
|
|
812
|
+
for await (const chunk of stream) {
|
|
813
|
+
processChunk(acc, chunk, {
|
|
814
|
+
showReasoning: options.showReasoning,
|
|
815
|
+
showOutput,
|
|
816
|
+
});
|
|
817
|
+
extractUsageFromChatChunk(chunk, turnUsage);
|
|
818
|
+
}
|
|
819
|
+
accumulateUsage(totalUsage, turnUsage);
|
|
820
|
+
emitTurnCompleted(turn, turnUsage);
|
|
821
|
+
if (acc.toolCalls.length === 0) {
|
|
822
|
+
if (showOutput && acc.content) {
|
|
823
|
+
process.stdout.write("\n");
|
|
824
|
+
}
|
|
825
|
+
emitMessage(acc.content);
|
|
826
|
+
if (acc.content) {
|
|
827
|
+
conversationMessages.push({ role: "assistant", content: acc.content });
|
|
828
|
+
if (sessionId && sessionMgr) {
|
|
829
|
+
sessionMgr.appendMessage(sessionId, "assistant", acc.content);
|
|
830
|
+
sessionMgr.updateMeta(sessionId, { turns: meta ? meta.meta.turns + turn : turn });
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
if (config.showUsage)
|
|
834
|
+
console.error(formatUsage(activeModel, turnUsage));
|
|
835
|
+
break;
|
|
836
|
+
}
|
|
837
|
+
const serialized = acc.toolCalls
|
|
838
|
+
.filter(tc => tc.id && tc.function.name)
|
|
839
|
+
.map(tc => ({ id: tc.id, name: tc.function.name, arguments: tc.function.arguments }));
|
|
840
|
+
const aMsg = {
|
|
841
|
+
role: "assistant", content: acc.content || null,
|
|
842
|
+
tool_calls: serialized.map(tc => ({
|
|
843
|
+
id: tc.id, type: "function",
|
|
844
|
+
function: { name: tc.name, arguments: tc.arguments },
|
|
845
|
+
})),
|
|
846
|
+
};
|
|
847
|
+
conversationMessages.push(aMsg);
|
|
848
|
+
if (sessionId && sessionMgr) {
|
|
849
|
+
sessionMgr.appendMessage(sessionId, "assistant", acc.content || null, { toolCalls: serialized });
|
|
850
|
+
}
|
|
851
|
+
if (showOutput && acc.content)
|
|
852
|
+
process.stdout.write("\n");
|
|
853
|
+
for (const tc of serialized) {
|
|
854
|
+
emitToolCall(tc.name, tc.arguments, tc.id);
|
|
855
|
+
if (config.showToolCalls)
|
|
856
|
+
console.error(formatToolCall(tc.name, tc.arguments, options.verbose));
|
|
857
|
+
const approved = await checkApproval(config, tc.name, tc.arguments);
|
|
858
|
+
if (!approved) {
|
|
859
|
+
conversationMessages.push({ role: "tool", tool_call_id: tc.id, content: "Tool execution denied by user." });
|
|
860
|
+
continue;
|
|
861
|
+
}
|
|
862
|
+
runHooks(config.hooks, { type: "pre-tool", tool: tc.name, args: tc.arguments, sessionId: sessionId || hookSessionId });
|
|
863
|
+
const result = await executeTool(tc.name, tc.arguments, options.cwd, {
|
|
864
|
+
sandboxMode: config.sandboxMode,
|
|
865
|
+
});
|
|
866
|
+
if (config.showToolCalls)
|
|
867
|
+
console.error(formatToolResult(result.output, result.error || false));
|
|
868
|
+
runHooks(config.hooks, { type: "post-tool", tool: tc.name, args: tc.arguments, output: result.output, error: result.error, sessionId: sessionId || hookSessionId });
|
|
869
|
+
emitToolResult(tc.id, result.output, result.error || false);
|
|
870
|
+
if (sessionId && sessionMgr) {
|
|
871
|
+
sessionMgr.appendToolExec(sessionId, tc.name, tc.arguments, result.output, result.error || false);
|
|
872
|
+
sessionMgr.appendMessage(sessionId, "tool", result.output, { toolCallId: tc.id });
|
|
873
|
+
}
|
|
874
|
+
conversationMessages.push({ role: "tool", tool_call_id: tc.id, content: result.output });
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
catch (err) {
|
|
879
|
+
const message = formatApiError("Request failed", err);
|
|
880
|
+
emitError(message);
|
|
881
|
+
if (!isJsonMode()) {
|
|
882
|
+
console.error(chalk.red(`Error: ${message}`));
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
console.error("");
|
|
886
|
+
rl.prompt();
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
finally {
|
|
890
|
+
runHooks(config.hooks, { type: "session-end", sessionId: hookSessionId });
|
|
891
|
+
emitSessionCompleted(hookSessionId, totalUsage.totalTokens > 0 ? totalUsage : undefined);
|
|
892
|
+
rl.close();
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
function sleep(ms) {
|
|
896
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
897
|
+
}
|
|
898
|
+
//# sourceMappingURL=agent.js.map
|