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.
Files changed (141) hide show
  1. package/CHANGELOG.md +113 -0
  2. package/LICENSE +190 -0
  3. package/README.md +449 -0
  4. package/bin/grok-cli.js +26 -0
  5. package/dist/agent.d.ts +4 -0
  6. package/dist/agent.d.ts.map +1 -0
  7. package/dist/agent.js +898 -0
  8. package/dist/agent.js.map +1 -0
  9. package/dist/approvals.d.ts +4 -0
  10. package/dist/approvals.d.ts.map +1 -0
  11. package/dist/approvals.js +90 -0
  12. package/dist/approvals.js.map +1 -0
  13. package/dist/batch-api.d.ts +11 -0
  14. package/dist/batch-api.d.ts.map +1 -0
  15. package/dist/batch-api.js +101 -0
  16. package/dist/batch-api.js.map +1 -0
  17. package/dist/cli-errors.d.ts +6 -0
  18. package/dist/cli-errors.d.ts.map +1 -0
  19. package/dist/cli-errors.js +80 -0
  20. package/dist/cli-errors.js.map +1 -0
  21. package/dist/client.d.ts +6 -0
  22. package/dist/client.d.ts.map +1 -0
  23. package/dist/client.js +41 -0
  24. package/dist/client.js.map +1 -0
  25. package/dist/collections-api.d.ts +11 -0
  26. package/dist/collections-api.d.ts.map +1 -0
  27. package/dist/collections-api.js +144 -0
  28. package/dist/collections-api.js.map +1 -0
  29. package/dist/compaction.d.ts +11 -0
  30. package/dist/compaction.d.ts.map +1 -0
  31. package/dist/compaction.js +94 -0
  32. package/dist/compaction.js.map +1 -0
  33. package/dist/config.d.ts +12 -0
  34. package/dist/config.d.ts.map +1 -0
  35. package/dist/config.js +142 -0
  36. package/dist/config.js.map +1 -0
  37. package/dist/diff.d.ts +5 -0
  38. package/dist/diff.d.ts.map +1 -0
  39. package/dist/diff.js +80 -0
  40. package/dist/diff.js.map +1 -0
  41. package/dist/hooks.d.ts +11 -0
  42. package/dist/hooks.d.ts.map +1 -0
  43. package/dist/hooks.js +30 -0
  44. package/dist/hooks.js.map +1 -0
  45. package/dist/image.d.ts +8 -0
  46. package/dist/image.d.ts.map +1 -0
  47. package/dist/image.js +58 -0
  48. package/dist/image.js.map +1 -0
  49. package/dist/index.d.ts +3 -0
  50. package/dist/index.d.ts.map +1 -0
  51. package/dist/index.js +1264 -0
  52. package/dist/index.js.map +1 -0
  53. package/dist/json-output.d.ts +27 -0
  54. package/dist/json-output.d.ts.map +1 -0
  55. package/dist/json-output.js +62 -0
  56. package/dist/json-output.js.map +1 -0
  57. package/dist/notifications.d.ts +6 -0
  58. package/dist/notifications.d.ts.map +1 -0
  59. package/dist/notifications.js +25 -0
  60. package/dist/notifications.js.map +1 -0
  61. package/dist/project-context.d.ts +6 -0
  62. package/dist/project-context.d.ts.map +1 -0
  63. package/dist/project-context.js +34 -0
  64. package/dist/project-context.js.map +1 -0
  65. package/dist/response-utils.d.ts +10 -0
  66. package/dist/response-utils.d.ts.map +1 -0
  67. package/dist/response-utils.js +123 -0
  68. package/dist/response-utils.js.map +1 -0
  69. package/dist/server-tools.d.ts +7 -0
  70. package/dist/server-tools.d.ts.map +1 -0
  71. package/dist/server-tools.js +205 -0
  72. package/dist/server-tools.js.map +1 -0
  73. package/dist/session.d.ts +46 -0
  74. package/dist/session.d.ts.map +1 -0
  75. package/dist/session.js +305 -0
  76. package/dist/session.js.map +1 -0
  77. package/dist/stream.d.ts +22 -0
  78. package/dist/stream.d.ts.map +1 -0
  79. package/dist/stream.js +102 -0
  80. package/dist/stream.js.map +1 -0
  81. package/dist/system-prompt.d.ts +3 -0
  82. package/dist/system-prompt.d.ts.map +1 -0
  83. package/dist/system-prompt.js +64 -0
  84. package/dist/system-prompt.js.map +1 -0
  85. package/dist/tools/bash.d.ts +8 -0
  86. package/dist/tools/bash.d.ts.map +1 -0
  87. package/dist/tools/bash.js +49 -0
  88. package/dist/tools/bash.js.map +1 -0
  89. package/dist/tools/definitions.d.ts +3 -0
  90. package/dist/tools/definitions.d.ts.map +1 -0
  91. package/dist/tools/definitions.js +190 -0
  92. package/dist/tools/definitions.js.map +1 -0
  93. package/dist/tools/edit-file.d.ts +10 -0
  94. package/dist/tools/edit-file.d.ts.map +1 -0
  95. package/dist/tools/edit-file.js +73 -0
  96. package/dist/tools/edit-file.js.map +1 -0
  97. package/dist/tools/glob.d.ts +7 -0
  98. package/dist/tools/glob.d.ts.map +1 -0
  99. package/dist/tools/glob.js +44 -0
  100. package/dist/tools/glob.js.map +1 -0
  101. package/dist/tools/grep.d.ts +10 -0
  102. package/dist/tools/grep.d.ts.map +1 -0
  103. package/dist/tools/grep.js +102 -0
  104. package/dist/tools/grep.js.map +1 -0
  105. package/dist/tools/index.d.ts +10 -0
  106. package/dist/tools/index.d.ts.map +1 -0
  107. package/dist/tools/index.js +41 -0
  108. package/dist/tools/index.js.map +1 -0
  109. package/dist/tools/list-dir.d.ts +7 -0
  110. package/dist/tools/list-dir.d.ts.map +1 -0
  111. package/dist/tools/list-dir.js +68 -0
  112. package/dist/tools/list-dir.js.map +1 -0
  113. package/dist/tools/policy.d.ts +4 -0
  114. package/dist/tools/policy.d.ts.map +1 -0
  115. package/dist/tools/policy.js +36 -0
  116. package/dist/tools/policy.js.map +1 -0
  117. package/dist/tools/read-file.d.ts +8 -0
  118. package/dist/tools/read-file.d.ts.map +1 -0
  119. package/dist/tools/read-file.js +45 -0
  120. package/dist/tools/read-file.js.map +1 -0
  121. package/dist/tools/write-file.d.ts +7 -0
  122. package/dist/tools/write-file.d.ts.map +1 -0
  123. package/dist/tools/write-file.js +35 -0
  124. package/dist/tools/write-file.js.map +1 -0
  125. package/dist/truncation.d.ts +13 -0
  126. package/dist/truncation.d.ts.map +1 -0
  127. package/dist/truncation.js +55 -0
  128. package/dist/truncation.js.map +1 -0
  129. package/dist/types.d.ts +159 -0
  130. package/dist/types.d.ts.map +1 -0
  131. package/dist/types.js +2 -0
  132. package/dist/types.js.map +1 -0
  133. package/dist/usage.d.ts +17 -0
  134. package/dist/usage.d.ts.map +1 -0
  135. package/dist/usage.js +80 -0
  136. package/dist/usage.js.map +1 -0
  137. package/dist/voice-api.d.ts +16 -0
  138. package/dist/voice-api.d.ts.map +1 -0
  139. package/dist/voice-api.js +84 -0
  140. package/dist/voice-api.js.map +1 -0
  141. 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