agentblit 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,760 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ Agent: () => Agent,
34
+ ChatMemory: () => ChatMemory,
35
+ ToolRegistry: () => ToolRegistry,
36
+ tool: () => tool
37
+ });
38
+ module.exports = __toCommonJS(index_exports);
39
+
40
+ // src/agent.ts
41
+ var import_openai = __toESM(require("openai"), 1);
42
+ var import_node_crypto2 = require("crypto");
43
+
44
+ // src/memory.ts
45
+ var ChatMemory = class {
46
+ messagesStore = [];
47
+ maxHistory;
48
+ summarizeFn;
49
+ constructor(options) {
50
+ const maxHistory = options?.maxHistory ?? 5;
51
+ if (maxHistory < 1) {
52
+ throw new Error("maxHistory must be at least 1");
53
+ }
54
+ this.maxHistory = maxHistory;
55
+ this.summarizeFn = options?.summarizeFn;
56
+ }
57
+ get messages() {
58
+ return [...this.messagesStore];
59
+ }
60
+ append(message) {
61
+ this.messagesStore.push(message);
62
+ }
63
+ extend(messages) {
64
+ this.messagesStore.push(...messages);
65
+ }
66
+ clear() {
67
+ this.messagesStore.length = 0;
68
+ }
69
+ async buildMessagesForLLM(systemPrompt) {
70
+ const out = [{ role: "system", content: systemPrompt }];
71
+ if (this.messagesStore.length <= this.maxHistory) {
72
+ out.push(...this.messagesStore);
73
+ return out;
74
+ }
75
+ const older = this.messagesStore.slice(0, -this.maxHistory);
76
+ const recent = this.messagesStore.slice(-this.maxHistory);
77
+ let summaryText;
78
+ if (!this.summarizeFn) {
79
+ summaryText = `[Earlier conversation truncated: ${older.length} message(s) not shown. Provide summarizeFn on Agent to compress older context with the LLM.]`;
80
+ } else {
81
+ summaryText = await this.summarizeFn(older);
82
+ }
83
+ out.push({
84
+ role: "system",
85
+ content: `Summary of earlier conversation:
86
+ ${summaryText}`
87
+ });
88
+ out.push(...recent);
89
+ return out;
90
+ }
91
+ };
92
+
93
+ // src/utils.ts
94
+ var import_node_process = __toESM(require("process"), 1);
95
+ var import_node_crypto = require("crypto");
96
+ var LOGGER_PREFIX = "[agentblit]";
97
+ var DebugLogger = class {
98
+ constructor(enabled) {
99
+ this.enabled = enabled;
100
+ }
101
+ enabled;
102
+ log(message, ...args) {
103
+ if (!this.enabled) {
104
+ return;
105
+ }
106
+ console.debug(`${LOGGER_PREFIX} ${message}`, ...args);
107
+ }
108
+ logLLMRequest(model, messageCount, toolCount) {
109
+ this.log("LLM request model=%s messages=%s tools=%s", model, messageCount, toolCount);
110
+ }
111
+ logToolCalls(calls) {
112
+ this.log("Tool calls: %s", jsonDumpsSafe(calls));
113
+ }
114
+ logToolResult(toolCallId, ok, preview) {
115
+ this.log("Tool result id=%s ok=%s preview=%s", toolCallId, ok, preview.slice(0, 500));
116
+ }
117
+ };
118
+ function jsonDumpsSafe(obj) {
119
+ return JSON.stringify(obj, (_key, value) => {
120
+ if (value instanceof Error) {
121
+ return {
122
+ name: value.name,
123
+ message: value.message,
124
+ stack: value.stack
125
+ };
126
+ }
127
+ return value;
128
+ });
129
+ }
130
+ function extractParamNames(fn) {
131
+ const source = fn.toString();
132
+ const match = source.match(/\(([^)]*)\)/);
133
+ if (!match) {
134
+ return [];
135
+ }
136
+ const rawParams = match[1] ?? "";
137
+ return rawParams.split(",").map((segment) => segment.trim()).filter(Boolean).map((segment) => segment.replace(/=[\s\S]*$/, "").trim()).map((segment) => segment.replace(/^\.{3}/, "")).filter((segment) => segment !== "this");
138
+ }
139
+ function functionToToolSchema(fn, explicitSchema) {
140
+ if (explicitSchema) {
141
+ return explicitSchema;
142
+ }
143
+ const properties = Object.fromEntries(
144
+ extractParamNames(fn).map((name) => [name, { type: "string" }])
145
+ );
146
+ const required = Object.keys(properties);
147
+ return {
148
+ type: "object",
149
+ properties,
150
+ ...required.length > 0 ? { required } : {}
151
+ };
152
+ }
153
+ var TOOL_META = /* @__PURE__ */ Symbol.for("agentblit.tool.meta");
154
+ function setToolMetadata(fn, options) {
155
+ const metadata = {
156
+ name: options.name ?? fn.name,
157
+ description: options.description ?? "",
158
+ permissionMode: options.permissionMode ?? "always_allow",
159
+ inputSchema: options.inputSchema
160
+ };
161
+ fn[TOOL_META] = metadata;
162
+ return fn;
163
+ }
164
+ function getToolMetadata(fn) {
165
+ return fn[TOOL_META];
166
+ }
167
+ function nowIsoTimestamp() {
168
+ return (/* @__PURE__ */ new Date()).toISOString();
169
+ }
170
+ function randomId(prefix) {
171
+ return `${prefix}_${(0, import_node_crypto.randomUUID)().replace(/-/g, "")}`;
172
+ }
173
+ function readStdinLine(promptText) {
174
+ return new Promise((resolve) => {
175
+ import_node_process.default.stdout.write(promptText);
176
+ import_node_process.default.stdin.resume();
177
+ import_node_process.default.stdin.once("data", (chunk) => {
178
+ resolve(String(chunk).trim());
179
+ });
180
+ });
181
+ }
182
+
183
+ // src/tools.ts
184
+ function tool(arg1, arg2) {
185
+ if (typeof arg1 === "function") {
186
+ return setToolMetadata(arg1, arg2 ?? {});
187
+ }
188
+ return (fn) => setToolMetadata(fn, arg1);
189
+ }
190
+ var ToolRegistry = class {
191
+ baseUrl;
192
+ apiKey;
193
+ timeout;
194
+ remote = /* @__PURE__ */ new Map();
195
+ custom = /* @__PURE__ */ new Map();
196
+ constructor(options) {
197
+ this.baseUrl = options.baseUrl.replace(/\/$/, "");
198
+ this.apiKey = options.apiKey;
199
+ this.timeout = options.timeout ?? 3e4;
200
+ }
201
+ register(fn) {
202
+ const metadata = getToolMetadata(fn);
203
+ const name = metadata?.name ?? fn.name;
204
+ const description = metadata?.description ?? "";
205
+ const permissionMode = metadata?.permissionMode ?? "always_allow";
206
+ const inputSchema = functionToToolSchema(fn, metadata?.inputSchema);
207
+ this.custom.set(name, {
208
+ name,
209
+ description,
210
+ inputSchema,
211
+ permissionMode,
212
+ handler: fn
213
+ });
214
+ }
215
+ async refreshRemote() {
216
+ const url = `${this.baseUrl}/api/tools/list`;
217
+ const response = await fetch(url, {
218
+ method: "GET",
219
+ headers: { "X-API-Key": this.apiKey },
220
+ signal: AbortSignal.timeout(this.timeout)
221
+ });
222
+ if (!response.ok) {
223
+ throw new Error(
224
+ `AgentBlit request failed for GET '${url}': ${response.status} ${response.statusText}`
225
+ );
226
+ }
227
+ const data = await response.json();
228
+ if (!data.ok) {
229
+ throw new Error("tools/list returned ok=false");
230
+ }
231
+ this.remote.clear();
232
+ for (const toolItem of data.tools ?? []) {
233
+ const name = String(toolItem.name ?? "");
234
+ if (!name) {
235
+ continue;
236
+ }
237
+ this.remote.set(name, {
238
+ name,
239
+ description: String(toolItem.description ?? ""),
240
+ inputSchema: toolItem.inputSchema ?? {
241
+ type: "object",
242
+ properties: {}
243
+ },
244
+ permissionMode: String(toolItem.permissionMode ?? "always_allow") === "needs_approval" ? "needs_approval" : "always_allow",
245
+ outputSchema: toolItem.outputSchema
246
+ });
247
+ }
248
+ }
249
+ merged() {
250
+ const merged = new Map(this.remote);
251
+ for (const [name, def] of this.custom.entries()) {
252
+ merged.set(name, def);
253
+ }
254
+ return merged;
255
+ }
256
+ getDefinition(name) {
257
+ return this.merged().get(name);
258
+ }
259
+ toOpenAITools() {
260
+ return [...this.merged().values()].map((definition) => ({
261
+ type: "function",
262
+ function: {
263
+ name: definition.name,
264
+ description: definition.description,
265
+ parameters: definition.inputSchema
266
+ }
267
+ }));
268
+ }
269
+ async ensureApproval(definition, toolName, args, approvalCallback) {
270
+ if (definition.permissionMode !== "needs_approval") {
271
+ return true;
272
+ }
273
+ if (approvalCallback) {
274
+ return approvalCallback(toolName, args);
275
+ }
276
+ const answer = await readStdinLine(
277
+ `Approve tool "${toolName}" with args ${jsonDumpsSafe(args)}? [y/N]: `
278
+ );
279
+ return ["y", "yes"].includes(answer.toLowerCase());
280
+ }
281
+ async execute(toolCallId, toolName, argumentsJson, approvalCallback) {
282
+ const definition = this.getDefinition(toolName);
283
+ if (!definition) {
284
+ return jsonDumpsSafe({ error: `Unknown tool: ${toolName}` });
285
+ }
286
+ let args;
287
+ try {
288
+ args = argumentsJson ? JSON.parse(argumentsJson) : {};
289
+ } catch (error) {
290
+ return jsonDumpsSafe({ error: `Invalid JSON arguments: ${String(error)}` });
291
+ }
292
+ if (!await this.ensureApproval(definition, toolName, args, approvalCallback)) {
293
+ return jsonDumpsSafe({ error: "User denied approval for this tool call." });
294
+ }
295
+ if (definition.handler) {
296
+ try {
297
+ let result2;
298
+ try {
299
+ result2 = await definition.handler(...Object.values(args));
300
+ } catch {
301
+ result2 = await definition.handler(args);
302
+ }
303
+ return jsonDumpsSafe(result2);
304
+ } catch (error) {
305
+ return jsonDumpsSafe({ error: error instanceof Error ? error.message : String(error) });
306
+ }
307
+ }
308
+ const payload = {
309
+ tool_calls: [
310
+ {
311
+ id: toolCallId,
312
+ type: "function",
313
+ function: {
314
+ name: toolName,
315
+ arguments: jsonDumpsSafe(args)
316
+ }
317
+ }
318
+ ]
319
+ };
320
+ const url = `${this.baseUrl}/api/tools/call`;
321
+ const response = await fetch(url, {
322
+ method: "POST",
323
+ headers: {
324
+ "X-API-Key": this.apiKey,
325
+ "Content-Type": "application/json"
326
+ },
327
+ body: jsonDumpsSafe(payload),
328
+ signal: AbortSignal.timeout(this.timeout)
329
+ });
330
+ if (!response.ok) {
331
+ throw new Error(
332
+ `AgentBlit request failed for POST '${url}': ${response.status} ${response.statusText}`
333
+ );
334
+ }
335
+ const data = await response.json();
336
+ if (!data.ok) {
337
+ return jsonDumpsSafe({ error: data });
338
+ }
339
+ const result = (data.results ?? []).find((item) => item.tool_call_id === toolCallId);
340
+ if (!result) {
341
+ return jsonDumpsSafe({ error: "No result for tool_call_id" });
342
+ }
343
+ const toolResult = result.result;
344
+ if (!toolResult) {
345
+ return jsonDumpsSafe({ error: "Missing tool result payload" });
346
+ }
347
+ if (toolResult.isError) {
348
+ const text2 = (toolResult.content ?? []).map((part) => part.text ?? "").join("");
349
+ return jsonDumpsSafe({ error: text2 || "Tool error" });
350
+ }
351
+ if (typeof toolResult.structuredContent !== "undefined") {
352
+ return jsonDumpsSafe(toolResult.structuredContent);
353
+ }
354
+ const text = (toolResult.content ?? []).map((part) => part.text ?? "").join("");
355
+ return jsonDumpsSafe({ result: text });
356
+ }
357
+ };
358
+
359
+ // src/agent.ts
360
+ var VENDOR_BASE_URLS = {
361
+ openai: "https://api.openai.com/v1",
362
+ anthropic: "https://api.anthropic.com/v1",
363
+ gemini: "https://generativelanguage.googleapis.com/v1beta/openai",
364
+ openrouter: "https://openrouter.ai/api/v1"
365
+ };
366
+ var DEFAULT_TOOL_USAGE_INSTRUCTION = "Use tools when they help answer accurately.";
367
+ function resolveVendorAndModel(modelInput) {
368
+ const model = modelInput.trim();
369
+ if (!model || !model.includes("/")) {
370
+ throw new Error(
371
+ "model must be in the format 'vendor/model', for example 'openai/gpt-4o-mini'."
372
+ );
373
+ }
374
+ const [vendorRaw, ...rest] = model.split("/");
375
+ const vendor = (vendorRaw ?? "").trim().toLowerCase();
376
+ const providerModel = rest.join("/").trim();
377
+ if (!vendor || !providerModel) {
378
+ throw new Error(
379
+ "model must be in the format 'vendor/model', for example 'openai/gpt-4o-mini'."
380
+ );
381
+ }
382
+ return { vendor, model: providerModel };
383
+ }
384
+ function resolveLlmUrl(vendor) {
385
+ const url = VENDOR_BASE_URLS[vendor];
386
+ if (!url) {
387
+ const supported = Object.keys(VENDOR_BASE_URLS).sort().join(", ");
388
+ throw new Error(`Unsupported model vendor '${vendor}'. Supported vendors: ${supported}.`);
389
+ }
390
+ return url;
391
+ }
392
+ function composeSystemPrompt(systemPrompt) {
393
+ const userPrompt = systemPrompt.trim();
394
+ if (userPrompt.toLowerCase().includes(DEFAULT_TOOL_USAGE_INSTRUCTION.toLowerCase())) {
395
+ return userPrompt;
396
+ }
397
+ if (!userPrompt) {
398
+ return DEFAULT_TOOL_USAGE_INSTRUCTION;
399
+ }
400
+ return `${userPrompt}
401
+
402
+ ${DEFAULT_TOOL_USAGE_INSTRUCTION}`;
403
+ }
404
+ function formatMessagesForSummary(messages) {
405
+ return messages.map((message, index) => {
406
+ if (message.role === "assistant" && message.tool_calls) {
407
+ return `[${index}] assistant (tool_calls): ${jsonDumpsSafe(message.tool_calls)}`;
408
+ }
409
+ return `[${index}] ${message.role}: ${message.content ?? ""}`;
410
+ }).join("\n");
411
+ }
412
+ var Agent = class {
413
+ vendor;
414
+ model;
415
+ llmUrl;
416
+ agentId;
417
+ sessionId;
418
+ config;
419
+ memory;
420
+ client;
421
+ tools;
422
+ systemPrompt;
423
+ timeout;
424
+ debug;
425
+ approvalCallback;
426
+ maxToolRounds;
427
+ eventBaseUrl;
428
+ eventApiKey;
429
+ pendingCustomEvents = [];
430
+ agentInitSent = false;
431
+ toolsSignature;
432
+ constructor(options) {
433
+ const agentblitApiKey = options.agentblitApiKey.trim();
434
+ if (!agentblitApiKey) {
435
+ throw new Error("agentblitApiKey is required");
436
+ }
437
+ const maxHistory = options.maxHistory ?? 5;
438
+ if (maxHistory < 1) {
439
+ throw new Error("maxHistory must be at least 1");
440
+ }
441
+ const maxToolRounds = options.maxToolRounds ?? 25;
442
+ if (maxToolRounds < 1) {
443
+ throw new Error("maxToolRounds must be at least 1");
444
+ }
445
+ const { vendor, model } = resolveVendorAndModel(options.model);
446
+ const llmUrl = resolveLlmUrl(vendor);
447
+ const timeoutSeconds = options.timeout ?? 30;
448
+ const timeoutMs = timeoutSeconds * 1e3;
449
+ const agentblitUrl = (options.agentblitUrl ?? "https://console.agentblit.com").replace(/\/$/, "");
450
+ const systemPrompt = composeSystemPrompt(options.systemPrompt ?? "");
451
+ this.vendor = vendor;
452
+ this.model = model;
453
+ this.llmUrl = llmUrl;
454
+ this.systemPrompt = systemPrompt;
455
+ this.timeout = timeoutMs;
456
+ this.approvalCallback = options.approvalCallback;
457
+ this.maxToolRounds = maxToolRounds;
458
+ this.agentId = (0, import_node_crypto2.randomUUID)();
459
+ this.sessionId = (0, import_node_crypto2.randomUUID)();
460
+ this.eventBaseUrl = agentblitUrl;
461
+ this.eventApiKey = agentblitApiKey;
462
+ this.debug = new DebugLogger(Boolean(options.debug));
463
+ this.client = new import_openai.default({
464
+ apiKey: options.apiKey,
465
+ baseURL: llmUrl,
466
+ timeout: timeoutMs
467
+ });
468
+ this.tools = new ToolRegistry({
469
+ baseUrl: agentblitUrl,
470
+ apiKey: agentblitApiKey,
471
+ timeout: timeoutMs
472
+ });
473
+ for (const customTool of options.customTools ?? []) {
474
+ this.registerTool(customTool);
475
+ }
476
+ this.memory = new ChatMemory({
477
+ maxHistory,
478
+ summarizeFn: (older) => this.summarizeOlderMessages(older)
479
+ });
480
+ this.config = Object.freeze({
481
+ model: this.model,
482
+ vendor: this.vendor,
483
+ llmUrl: this.llmUrl,
484
+ agentblitUrl: this.eventBaseUrl,
485
+ systemPrompt: this.systemPrompt,
486
+ maxHistory,
487
+ debug: Boolean(options.debug),
488
+ timeout: timeoutSeconds,
489
+ agentId: this.agentId,
490
+ sessionId: this.sessionId
491
+ });
492
+ }
493
+ registerTool(fn) {
494
+ this.tools.register(fn);
495
+ }
496
+ track(eventType, properties) {
497
+ this.pendingCustomEvents.push(this.makeEvent({ eventType, data: properties }));
498
+ }
499
+ makeEvent(input) {
500
+ return {
501
+ id: randomId("evt"),
502
+ session_id: this.sessionId,
503
+ agent_id: this.agentId,
504
+ timestamp: nowIsoTimestamp(),
505
+ type: input.eventType,
506
+ data: input.data,
507
+ tokens: Math.max(0, Math.trunc(input.tokens ?? 0)),
508
+ latency_ms: Math.max(0, Math.trunc(input.latencyMs ?? 0))
509
+ };
510
+ }
511
+ async flushEvents(events) {
512
+ if (events.length === 0) {
513
+ return;
514
+ }
515
+ const url = `${this.eventBaseUrl}/api/events/batch`;
516
+ this.debug.log("Event batch send start url=%s count=%s", url, events.length);
517
+ try {
518
+ const response = await fetch(url, {
519
+ method: "POST",
520
+ headers: {
521
+ "X-API-Key": this.eventApiKey,
522
+ "Content-Type": "application/json"
523
+ },
524
+ body: jsonDumpsSafe({ events }),
525
+ signal: AbortSignal.timeout(this.timeout)
526
+ });
527
+ if (!response.ok) {
528
+ const body = (await response.text()).slice(0, 1e3);
529
+ this.debug.log(
530
+ "Failed to send events batch status=%s body=%s",
531
+ response.status,
532
+ body
533
+ );
534
+ return;
535
+ }
536
+ this.debug.log("Event batch send success status=%s count=%s", response.status, events.length);
537
+ } catch (error) {
538
+ this.debug.log("Failed to send events batch: %s", String(error));
539
+ }
540
+ }
541
+ extractLlmEventMessages(messages) {
542
+ if (messages.length === 0) {
543
+ return [];
544
+ }
545
+ const summaryMessage = messages.find(
546
+ (message) => message.role === "system" && typeof message.content === "string" && message.content.startsWith("Summary of earlier conversation:")
547
+ );
548
+ const lastMessage = messages[messages.length - 1];
549
+ if (!lastMessage) {
550
+ return [];
551
+ }
552
+ if (!summaryMessage) {
553
+ return [lastMessage];
554
+ }
555
+ if (summaryMessage === lastMessage) {
556
+ return [summaryMessage];
557
+ }
558
+ return [summaryMessage, lastMessage];
559
+ }
560
+ async summarizeOlderMessages(older) {
561
+ const text = formatMessagesForSummary(older);
562
+ this.debug.log("Summarizing %s older messages", older.length);
563
+ const response = await this.client.chat.completions.create({
564
+ model: this.model,
565
+ messages: [
566
+ {
567
+ role: "system",
568
+ content: "Summarize the conversation excerpt below for use as memory. Preserve key facts, decisions, and tool outcomes. Be concise."
569
+ },
570
+ { role: "user", content: text }
571
+ ],
572
+ temperature: 0.2
573
+ });
574
+ return response.choices[0]?.message?.content?.trim() ?? "";
575
+ }
576
+ async *run(userMessage) {
577
+ const events = [...this.pendingCustomEvents];
578
+ this.pendingCustomEvents.length = 0;
579
+ try {
580
+ this.memory.append({ role: "user", content: userMessage });
581
+ await this.tools.refreshRemote();
582
+ const openaiTools = this.tools.toOpenAITools();
583
+ const toolsSignature = jsonDumpsSafe(openaiTools);
584
+ if (!this.agentInitSent) {
585
+ events.push(
586
+ this.makeEvent({
587
+ eventType: "agent_init",
588
+ data: { system_prompt: this.systemPrompt, tools: openaiTools }
589
+ })
590
+ );
591
+ this.agentInitSent = true;
592
+ this.toolsSignature = toolsSignature;
593
+ } else if (toolsSignature !== this.toolsSignature) {
594
+ events.push(this.makeEvent({ eventType: "tools_updated", data: { tools: openaiTools } }));
595
+ this.toolsSignature = toolsSignature;
596
+ }
597
+ events.push(
598
+ this.makeEvent({
599
+ eventType: "user_prompt",
600
+ data: { request: { message: userMessage }, response: null }
601
+ })
602
+ );
603
+ let finishedNormally = false;
604
+ for (let round = 0; round < this.maxToolRounds; round += 1) {
605
+ const messages = await this.memory.buildMessagesForLLM(this.systemPrompt);
606
+ this.debug.logLLMRequest(this.model, messages.length, openaiTools.length);
607
+ const llmRequest = {
608
+ model: this.model,
609
+ messages,
610
+ stream: true,
611
+ ...openaiTools.length > 0 ? { tools: openaiTools } : {},
612
+ ...this.vendor === "openai" || this.vendor === "openrouter" ? { stream_options: { include_usage: true } } : {}
613
+ };
614
+ const llmEventRequestData = {
615
+ model: this.model,
616
+ messages: this.extractLlmEventMessages(messages),
617
+ stream: true
618
+ };
619
+ const startedAt = Date.now();
620
+ const stream = await this.client.chat.completions.create(
621
+ llmRequest
622
+ );
623
+ const contentParts = [];
624
+ const toolCallsMap = /* @__PURE__ */ new Map();
625
+ let finishReason = null;
626
+ let llmTotalTokens = 0;
627
+ for await (const chunk of stream) {
628
+ if (chunk.usage?.total_tokens) {
629
+ llmTotalTokens = chunk.usage.total_tokens;
630
+ }
631
+ const choice = chunk.choices?.[0];
632
+ if (!choice) {
633
+ continue;
634
+ }
635
+ if (choice.finish_reason) {
636
+ finishReason = choice.finish_reason;
637
+ }
638
+ const delta = choice.delta;
639
+ if (!delta) {
640
+ continue;
641
+ }
642
+ if (delta.content) {
643
+ contentParts.push(delta.content);
644
+ yield delta.content;
645
+ }
646
+ for (const toolCall of delta.tool_calls ?? []) {
647
+ const index = toolCall.index ?? 0;
648
+ const existing = toolCallsMap.get(index) ?? { id: "", name: "", arguments: "" };
649
+ if (toolCall.id) {
650
+ existing.id = toolCall.id;
651
+ }
652
+ if (toolCall.function?.name) {
653
+ existing.name = toolCall.function.name;
654
+ }
655
+ if (toolCall.function?.arguments) {
656
+ existing.arguments += toolCall.function.arguments;
657
+ }
658
+ toolCallsMap.set(index, existing);
659
+ }
660
+ }
661
+ const assistantContent = contentParts.join("") || null;
662
+ const llmLatencyMs = Date.now() - startedAt;
663
+ const toolCalls = [...toolCallsMap.entries()].sort(([a], [b]) => a - b).map(([, call]) => ({
664
+ id: call.id,
665
+ type: "function",
666
+ function: {
667
+ name: call.name,
668
+ arguments: call.arguments || "{}"
669
+ }
670
+ }));
671
+ events.push(
672
+ this.makeEvent({
673
+ eventType: "llm_call",
674
+ data: {
675
+ request: llmEventRequestData,
676
+ response: {
677
+ finish_reason: finishReason,
678
+ content: assistantContent,
679
+ tool_calls: toolCalls
680
+ }
681
+ },
682
+ tokens: llmTotalTokens,
683
+ latencyMs: llmLatencyMs
684
+ })
685
+ );
686
+ if (finishReason === "tool_calls" || toolCalls.length > 0) {
687
+ this.debug.logToolCalls(toolCalls);
688
+ this.memory.append({
689
+ role: "assistant",
690
+ content: assistantContent,
691
+ tool_calls: toolCalls
692
+ });
693
+ for (const toolCall of toolCalls) {
694
+ const toolStartedAt = Date.now();
695
+ const result = await this.tools.execute(
696
+ toolCall.id,
697
+ toolCall.function.name,
698
+ toolCall.function.arguments,
699
+ this.approvalCallback
700
+ );
701
+ const toolLatencyMs = Date.now() - toolStartedAt;
702
+ this.debug.logToolResult(toolCall.id, true, result);
703
+ let parsedResponse;
704
+ try {
705
+ parsedResponse = JSON.parse(result);
706
+ } catch {
707
+ parsedResponse = { raw: result };
708
+ }
709
+ events.push(
710
+ this.makeEvent({
711
+ eventType: "tool_call",
712
+ data: {
713
+ request: toolCall,
714
+ response: parsedResponse
715
+ },
716
+ latencyMs: toolLatencyMs
717
+ })
718
+ );
719
+ this.memory.append({
720
+ role: "tool",
721
+ tool_call_id: toolCall.id,
722
+ content: result
723
+ });
724
+ }
725
+ continue;
726
+ }
727
+ this.memory.append({
728
+ role: "assistant",
729
+ content: assistantContent ?? ""
730
+ });
731
+ finishedNormally = true;
732
+ break;
733
+ }
734
+ if (!finishedNormally) {
735
+ throw new Error(
736
+ `Exceeded maxToolRounds (${this.maxToolRounds}); increase maxToolRounds or simplify the task.`
737
+ );
738
+ }
739
+ } catch (error) {
740
+ events.push(
741
+ this.makeEvent({
742
+ eventType: "agent_loop_error",
743
+ data: { error: error instanceof Error ? error.message : String(error) }
744
+ })
745
+ );
746
+ throw error;
747
+ } finally {
748
+ this.debug.log("Queueing event flush count=%s", events.length);
749
+ await this.flushEvents(events);
750
+ }
751
+ }
752
+ };
753
+ // Annotate the CommonJS export names for ESM import in node:
754
+ 0 && (module.exports = {
755
+ Agent,
756
+ ChatMemory,
757
+ ToolRegistry,
758
+ tool
759
+ });
760
+ //# sourceMappingURL=index.cjs.map