@nuvin/nuvin-core 1.1.2 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -36,6 +36,345 @@ var AgentEventTypes = {
36
36
  SubAgentCompleted: "sub_agent_completed"
37
37
  };
38
38
 
39
+ // metrics.ts
40
+ var createEmptySnapshot = () => ({
41
+ totalTokens: 0,
42
+ totalPromptTokens: 0,
43
+ totalCompletionTokens: 0,
44
+ totalCachedTokens: 0,
45
+ totalReasoningTokens: 0,
46
+ requestCount: 0,
47
+ llmCallCount: 0,
48
+ toolCallCount: 0,
49
+ totalTimeMs: 0,
50
+ totalCost: 0,
51
+ currentTokens: 0,
52
+ currentPromptTokens: 0,
53
+ currentCompletionTokens: 0,
54
+ currentCachedTokens: 0,
55
+ currentCost: 0
56
+ });
57
+ var NoopMetricsPort = class {
58
+ recordLLMCall(_usage, _cost) {
59
+ }
60
+ recordToolCall() {
61
+ }
62
+ recordRequestComplete(_responseTimeMs) {
63
+ }
64
+ setContextWindow(_limit, _usage) {
65
+ }
66
+ reset() {
67
+ }
68
+ getSnapshot() {
69
+ return createEmptySnapshot();
70
+ }
71
+ };
72
+ var InMemoryMetricsPort = class {
73
+ snapshot = createEmptySnapshot();
74
+ onChange;
75
+ constructor(onChange) {
76
+ this.onChange = onChange;
77
+ }
78
+ emit() {
79
+ this.onChange?.({ ...this.snapshot });
80
+ }
81
+ recordLLMCall(usage, cost) {
82
+ const prompt = usage.prompt_tokens ?? 0;
83
+ const completion = usage.completion_tokens ?? 0;
84
+ const total = usage.total_tokens ?? prompt + completion;
85
+ const cached = usage.prompt_tokens_details?.cached_tokens ?? 0;
86
+ const reasoning = usage.reasoning_tokens ?? usage.completion_tokens_details?.reasoning_tokens ?? 0;
87
+ const actualCost = cost ?? usage.cost ?? 0;
88
+ this.snapshot.totalTokens += total;
89
+ this.snapshot.totalPromptTokens += prompt;
90
+ this.snapshot.totalCompletionTokens += completion;
91
+ this.snapshot.totalCachedTokens += cached;
92
+ this.snapshot.totalReasoningTokens += reasoning;
93
+ this.snapshot.totalCost += actualCost;
94
+ this.snapshot.llmCallCount += 1;
95
+ this.snapshot.currentTokens = total;
96
+ this.snapshot.currentPromptTokens = prompt;
97
+ this.snapshot.currentCompletionTokens = completion;
98
+ this.snapshot.currentCachedTokens = cached;
99
+ this.snapshot.currentCost = actualCost;
100
+ this.emit();
101
+ }
102
+ recordToolCall() {
103
+ this.snapshot.toolCallCount += 1;
104
+ this.emit();
105
+ }
106
+ recordRequestComplete(responseTimeMs) {
107
+ this.snapshot.requestCount += 1;
108
+ this.snapshot.totalTimeMs += responseTimeMs;
109
+ this.emit();
110
+ }
111
+ setContextWindow(limit, usage) {
112
+ this.snapshot.contextWindowLimit = limit;
113
+ this.snapshot.contextWindowUsage = usage;
114
+ this.emit();
115
+ }
116
+ reset() {
117
+ this.snapshot = createEmptySnapshot();
118
+ this.emit();
119
+ }
120
+ getSnapshot() {
121
+ return { ...this.snapshot };
122
+ }
123
+ setOnChange(fn) {
124
+ this.onChange = fn;
125
+ }
126
+ };
127
+
128
+ // clock.ts
129
+ var SystemClock = class {
130
+ now() {
131
+ return Date.now();
132
+ }
133
+ iso(dateMs) {
134
+ return new Date(dateMs ?? Date.now()).toISOString();
135
+ }
136
+ };
137
+
138
+ // id.ts
139
+ var SimpleId = class {
140
+ uuid() {
141
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
142
+ const r = Math.random() * 16 | 0;
143
+ const v = c === "x" ? r : r & 3 | 8;
144
+ return v.toString(16);
145
+ });
146
+ }
147
+ };
148
+
149
+ // cost.ts
150
+ var SimpleCost = class {
151
+ estimate(_model, usage) {
152
+ return usage?.cost;
153
+ }
154
+ };
155
+
156
+ // reminders.ts
157
+ var NoopReminders = class {
158
+ enhance(content, _opts) {
159
+ return [content];
160
+ }
161
+ };
162
+
163
+ // context.ts
164
+ var toProviderContent = (content) => {
165
+ if (content === null || content === void 0) {
166
+ return "";
167
+ }
168
+ if (typeof content === "string") {
169
+ return content;
170
+ }
171
+ if (content.type === "parts") {
172
+ const providerParts = [];
173
+ for (const part of content.parts) {
174
+ if (part.type === "text") {
175
+ if (part.text.length > 0) {
176
+ providerParts.push({ type: "text", text: part.text });
177
+ }
178
+ continue;
179
+ }
180
+ const label = part.altText ?? (part.name ? `Image attachment: ${part.name}` : void 0);
181
+ if (label) {
182
+ providerParts.push({ type: "text", text: label });
183
+ }
184
+ const url = `data:${part.mimeType};base64,${part.data}`;
185
+ providerParts.push({ type: "image_url", image_url: { url } });
186
+ }
187
+ return providerParts.length > 0 ? providerParts : [];
188
+ }
189
+ return [];
190
+ };
191
+ var SimpleContextBuilder = class {
192
+ toProviderMessages(history, systemPrompt, newUserContent) {
193
+ const transformed = [];
194
+ for (const m of history) {
195
+ const providerContent = toProviderContent(m.content);
196
+ if (m.role === "user") {
197
+ transformed.push({ role: "user", content: providerContent ?? "" });
198
+ } else if (m.role === "assistant") {
199
+ if (m.tool_calls && m.tool_calls.length > 0) {
200
+ transformed.push({
201
+ ...m,
202
+ role: "assistant",
203
+ content: providerContent ?? null,
204
+ tool_calls: m.tool_calls
205
+ });
206
+ } else {
207
+ transformed.push({
208
+ ...m,
209
+ role: "assistant",
210
+ content: providerContent ?? ""
211
+ });
212
+ }
213
+ } else if (m.role === "tool") {
214
+ if (m.tool_call_id) {
215
+ transformed.push({
216
+ role: "tool",
217
+ content: typeof providerContent === "string" ? providerContent : providerContent ?? "",
218
+ tool_call_id: m.tool_call_id,
219
+ name: m.name
220
+ });
221
+ }
222
+ }
223
+ }
224
+ const userMsgs = newUserContent.map((c) => ({
225
+ role: "user",
226
+ content: toProviderContent(c) ?? ""
227
+ }));
228
+ return [{ role: "system", content: systemPrompt }, ...transformed, ...userMsgs];
229
+ }
230
+ };
231
+
232
+ // persistent/memory.ts
233
+ var InMemoryMemory = class {
234
+ store = /* @__PURE__ */ new Map();
235
+ async get(key) {
236
+ return this.store.get(key) ?? [];
237
+ }
238
+ async set(key, items) {
239
+ this.store.set(key, [...items]);
240
+ }
241
+ async append(key, items) {
242
+ const existing = this.store.get(key) ?? [];
243
+ this.store.set(key, [...existing, ...items]);
244
+ }
245
+ async delete(key) {
246
+ this.store.delete(key);
247
+ }
248
+ async keys() {
249
+ return Array.from(this.store.keys());
250
+ }
251
+ async clear() {
252
+ this.store.clear();
253
+ }
254
+ async exportSnapshot() {
255
+ const snap = {};
256
+ for (const [k, v] of this.store.entries()) snap[k] = [...v];
257
+ return snap;
258
+ }
259
+ async importSnapshot(snapshot) {
260
+ this.store.clear();
261
+ for (const [k, v] of Object.entries(snapshot)) this.store.set(k, [...v]);
262
+ }
263
+ };
264
+ var JsonFileMemoryPersistence = class {
265
+ constructor(filename = "history.json") {
266
+ this.filename = filename;
267
+ }
268
+ async load() {
269
+ try {
270
+ const fs10 = await import("fs");
271
+ if (!fs10.existsSync(this.filename)) return {};
272
+ const text = fs10.readFileSync(this.filename, "utf-8");
273
+ const data = JSON.parse(text);
274
+ return typeof data === "object" && data ? data : {};
275
+ } catch {
276
+ console.warn(`Failed to load memory from ${this.filename}`);
277
+ return {};
278
+ }
279
+ }
280
+ async save(snapshot) {
281
+ try {
282
+ const fs10 = await import("fs");
283
+ const path9 = await import("path");
284
+ const dir = path9.dirname(this.filename);
285
+ if (dir && dir !== "." && !fs10.existsSync(dir)) {
286
+ fs10.mkdirSync(dir, { recursive: true });
287
+ }
288
+ fs10.writeFileSync(this.filename, JSON.stringify(snapshot, null, 2), "utf-8");
289
+ } catch (err2) {
290
+ console.warn(`Failed to save memory to ${this.filename}`, err2);
291
+ }
292
+ }
293
+ };
294
+ var PersistedMemory = class {
295
+ constructor(persistence) {
296
+ this.persistence = persistence;
297
+ }
298
+ inner = new InMemoryMemory();
299
+ initialized = false;
300
+ async ensureInitialized() {
301
+ if (this.initialized) return;
302
+ const snap = await this.persistence.load();
303
+ if (snap && typeof snap === "object") await this.inner.importSnapshot(snap);
304
+ this.initialized = true;
305
+ }
306
+ async save() {
307
+ const snap = await this.inner.exportSnapshot();
308
+ await this.persistence.save(snap);
309
+ }
310
+ async get(key) {
311
+ await this.ensureInitialized();
312
+ return this.inner.get(key);
313
+ }
314
+ async set(key, items) {
315
+ await this.ensureInitialized();
316
+ await this.inner.set(key, items);
317
+ await this.save();
318
+ }
319
+ async append(key, items) {
320
+ await this.ensureInitialized();
321
+ await this.inner.append(key, items);
322
+ await this.save();
323
+ }
324
+ async delete(key) {
325
+ await this.ensureInitialized();
326
+ await this.inner.delete(key);
327
+ await this.save();
328
+ }
329
+ async keys() {
330
+ await this.ensureInitialized();
331
+ return this.inner.keys();
332
+ }
333
+ async clear() {
334
+ await this.ensureInitialized();
335
+ await this.inner.clear();
336
+ await this.save();
337
+ }
338
+ async exportSnapshot() {
339
+ await this.ensureInitialized();
340
+ return this.inner.exportSnapshot();
341
+ }
342
+ async importSnapshot(snapshot) {
343
+ await this.ensureInitialized();
344
+ await this.inner.importSnapshot(snapshot);
345
+ await this.save();
346
+ }
347
+ };
348
+
349
+ // events.ts
350
+ var NoopEventPort = class {
351
+ emit(_event) {
352
+ }
353
+ };
354
+ var PersistingConsoleEventPort = class {
355
+ memory;
356
+ maxPerConversation;
357
+ writeQueue = Promise.resolve();
358
+ constructor(opts) {
359
+ this.memory = opts?.memory ?? new PersistedMemory(new JsonFileMemoryPersistence(opts?.filename || "events.json"));
360
+ this.maxPerConversation = opts?.maxPerConversation ?? 500;
361
+ }
362
+ async emit(event) {
363
+ this.writeQueue = this.writeQueue.then(async () => {
364
+ try {
365
+ const key = event?.conversationId ?? "default";
366
+ const existing = await this.memory.get(key);
367
+ const next = [...existing, { ...event }];
368
+ const max = this.maxPerConversation;
369
+ const trimmed = max > 0 && next.length > max ? next.slice(next.length - max) : next;
370
+ await this.memory.set(key, trimmed);
371
+ } catch {
372
+ }
373
+ });
374
+ return this.writeQueue;
375
+ }
376
+ };
377
+
39
378
  // orchestrator.ts
40
379
  var removeAttachmentTokens = (value, attachments) => {
41
380
  return attachments.reduce((acc, attachment) => {
@@ -107,9 +446,29 @@ var resolveDisplayText = (text, attachments, provided) => {
107
446
  var AgentOrchestrator = class {
108
447
  constructor(cfg, deps) {
109
448
  this.cfg = cfg;
110
- this.deps = deps;
449
+ this.memory = deps.memory;
450
+ this.tools = deps.tools;
451
+ this.llm = deps.llm;
452
+ this.context = deps.context ?? this.context;
453
+ this.ids = deps.ids ?? this.ids;
454
+ this.clock = deps.clock ?? this.clock;
455
+ this.cost = deps.cost ?? this.cost;
456
+ this.reminders = deps.reminders ?? this.reminders;
457
+ this.metrics = deps.metrics ?? this.metrics;
458
+ this.events = deps.events ?? this.events;
111
459
  }
112
460
  pendingApprovals = /* @__PURE__ */ new Map();
461
+ context = new SimpleContextBuilder();
462
+ ids = new SimpleId();
463
+ clock = new SystemClock();
464
+ cost = new SimpleCost();
465
+ reminders = new NoopReminders();
466
+ // private llm: LLMPort;
467
+ metrics = new NoopMetricsPort();
468
+ events = new NoopEventPort();
469
+ llm;
470
+ tools;
471
+ memory;
113
472
  /**
114
473
  * Updates the agent configuration dynamically after initialization.
115
474
  * This allows for runtime changes to model, provider, and other settings.
@@ -122,26 +481,26 @@ var AgentOrchestrator = class {
122
481
  * This preserves conversation history, MCP connections, and other state.
123
482
  */
124
483
  setLLM(newLLM) {
125
- this.deps.llm = newLLM;
484
+ this.llm = newLLM;
126
485
  }
127
486
  /**
128
487
  * Updates the tool port without reinitializing the entire orchestrator.
129
488
  * This preserves conversation history and other state while adding/removing tools.
130
489
  */
131
490
  setTools(newTools) {
132
- this.deps.tools = newTools;
491
+ this.tools = newTools;
133
492
  }
134
493
  /**
135
494
  * Gets the current tool port.
136
495
  */
137
496
  getTools() {
138
- return this.deps.tools;
497
+ return this.tools;
139
498
  }
140
499
  /**
141
500
  * Gets the current LLM port.
142
501
  */
143
502
  getLLM() {
144
- return this.deps.llm;
503
+ return this.llm;
145
504
  }
146
505
  /**
147
506
  * Gets the current agent configuration.
@@ -155,14 +514,26 @@ var AgentOrchestrator = class {
155
514
  * MCP servers, and other state.
156
515
  */
157
516
  setMemory(newMemory) {
158
- this.deps.memory = newMemory;
517
+ this.memory = newMemory;
159
518
  }
160
519
  /**
161
520
  * Updates the event port without reinitializing the entire orchestrator.
162
521
  * This is useful when switching to a new session with a different event log file.
163
522
  */
164
523
  setEvents(newEvents) {
165
- this.deps.events = newEvents;
524
+ this.events = newEvents;
525
+ }
526
+ /**
527
+ * Updates the metrics port without reinitializing the entire orchestrator.
528
+ */
529
+ setMetrics(newMetrics) {
530
+ this.metrics = newMetrics;
531
+ }
532
+ /**
533
+ * Gets the current metrics port.
534
+ */
535
+ getMetrics() {
536
+ return this.metrics;
166
537
  }
167
538
  /**
168
539
  * Determines if a tool should bypass approval requirements.
@@ -175,10 +546,10 @@ var AgentOrchestrator = class {
175
546
  }
176
547
  async handleToolDenial(denialMessage, conversationId, messageId, accumulatedMessages, turnHistory, originalToolCalls, assistantContent, usage) {
177
548
  const assistantMsg = {
178
- id: this.deps.ids.uuid(),
549
+ id: this.ids.uuid(),
179
550
  role: "assistant",
180
551
  content: assistantContent ?? null,
181
- timestamp: this.deps.clock.iso(),
552
+ timestamp: this.clock.iso(),
182
553
  tool_calls: originalToolCalls,
183
554
  usage
184
555
  };
@@ -201,15 +572,15 @@ var AgentOrchestrator = class {
201
572
  id: toolCall.id,
202
573
  role: "tool",
203
574
  content: toolDenialResult,
204
- timestamp: this.deps.clock.iso(),
575
+ timestamp: this.clock.iso(),
205
576
  tool_call_id: toolCall.id,
206
577
  name: toolCall.function.name
207
578
  };
208
579
  turnHistory.push(toolMsg);
209
580
  toolResultMsgs.push(toolMsg);
210
581
  }
211
- await this.deps.memory.append(conversationId, [assistantMsg, ...toolResultMsgs]);
212
- await this.deps.events?.emit({
582
+ await this.memory.append(conversationId, [assistantMsg, ...toolResultMsgs]);
583
+ await this.events?.emit({
213
584
  type: AgentEventTypes.AssistantMessage,
214
585
  conversationId,
215
586
  messageId,
@@ -247,15 +618,19 @@ var AgentOrchestrator = class {
247
618
  }
248
619
  async send(content, opts = {}) {
249
620
  const convo = opts.conversationId ?? "default";
250
- const t0 = this.deps.clock.now();
251
- const msgId = this.deps.ids.uuid();
252
- const history = await this.deps.memory.get(convo);
621
+ const t0 = this.clock.now();
622
+ const msgId = this.ids.uuid();
623
+ const history = await this.memory.get(convo);
253
624
  let providerMsgs;
254
625
  let userMessages;
255
626
  let userDisplay;
256
627
  let enhanced;
628
+ const _llm = this.getLLM();
629
+ if (!_llm) {
630
+ throw new Error("LLM provider not set");
631
+ }
257
632
  if (opts.retry) {
258
- providerMsgs = this.deps.context.toProviderMessages(history, this.cfg.systemPrompt, []);
633
+ providerMsgs = this.context.toProviderMessages(history, this.cfg.systemPrompt, []);
259
634
  userMessages = [];
260
635
  userDisplay = "[Retry]";
261
636
  enhanced = [];
@@ -266,7 +641,7 @@ var AgentOrchestrator = class {
266
641
  attachments: Array.isArray(content.attachments) ? content.attachments : []
267
642
  };
268
643
  const attachments = normalized.attachments;
269
- enhanced = this.deps.reminders.enhance(normalized.text, { conversationId: convo });
644
+ enhanced = this.reminders.enhance(normalized.text, { conversationId: convo });
270
645
  const enhancedCombined = enhanced.join("\n");
271
646
  const messageParts = buildMessageParts(enhancedCombined, attachments);
272
647
  let userContent;
@@ -279,16 +654,16 @@ var AgentOrchestrator = class {
279
654
  } else {
280
655
  userContent = enhancedCombined;
281
656
  }
282
- providerMsgs = this.deps.context.toProviderMessages(history, this.cfg.systemPrompt, [userContent]);
657
+ providerMsgs = this.context.toProviderMessages(history, this.cfg.systemPrompt, [userContent]);
283
658
  userDisplay = resolveDisplayText(normalized.text, attachments, normalized.displayText);
284
- const userTimestamp = this.deps.clock.iso();
285
- userMessages = [{ id: this.deps.ids.uuid(), role: "user", content: userContent, timestamp: userTimestamp }];
286
- await this.deps.memory.append(convo, userMessages);
659
+ const userTimestamp = this.clock.iso();
660
+ userMessages = [{ id: this.ids.uuid(), role: "user", content: userContent, timestamp: userTimestamp }];
661
+ await this.memory.append(convo, userMessages);
287
662
  }
288
663
  if (opts.signal?.aborted) throw new Error("Aborted");
289
- const toolDefs = this.deps.tools.getToolDefinitions(this.cfg.enabledTools ?? []);
664
+ const toolDefs = this.tools.getToolDefinitions(this.cfg.enabledTools ?? []);
290
665
  const toolNames = toolDefs.map((t) => t.function.name);
291
- await this.deps.events?.emit({
666
+ await this.events?.emit({
292
667
  type: AgentEventTypes.MessageStarted,
293
668
  conversationId: convo,
294
669
  messageId: msgId,
@@ -315,17 +690,166 @@ var AgentOrchestrator = class {
315
690
  let toolApprovalDenied = false;
316
691
  let denialMessage = "";
317
692
  let finalResponseSaved = false;
318
- try {
319
- if (opts.stream && typeof this.deps.llm.streamCompletion === "function") {
693
+ if (opts.stream && typeof _llm.streamCompletion === "function") {
694
+ let isFirstChunk = true;
695
+ result = await _llm.streamCompletion(
696
+ params,
697
+ {
698
+ onChunk: async (delta, usage) => {
699
+ streamedAssistantContent += delta;
700
+ const cleanDelta = isFirstChunk ? delta.replace(/^\n+/, "") : delta;
701
+ isFirstChunk = false;
702
+ const chunkEvent = {
703
+ type: AgentEventTypes.AssistantChunk,
704
+ conversationId: convo,
705
+ messageId: msgId,
706
+ delta: cleanDelta,
707
+ ...usage && { usage }
708
+ };
709
+ await this.events?.emit(chunkEvent);
710
+ },
711
+ onStreamFinish: async (finishReason, usage) => {
712
+ if (usage) {
713
+ const cost = this.cost.estimate(this.cfg.model, usage);
714
+ this.metrics?.recordLLMCall?.(usage, cost);
715
+ }
716
+ const finishEvent = {
717
+ type: AgentEventTypes.StreamFinish,
718
+ conversationId: convo,
719
+ messageId: msgId,
720
+ ...finishReason && { finishReason },
721
+ ...usage && { usage }
722
+ };
723
+ await this.events?.emit(finishEvent);
724
+ }
725
+ },
726
+ opts.signal
727
+ );
728
+ } else {
729
+ result = await _llm.generateCompletion(params, opts.signal);
730
+ if (result.usage) {
731
+ const cost = this.cost.estimate(this.cfg.model, result.usage);
732
+ this.metrics?.recordLLMCall?.(result.usage, cost);
733
+ }
734
+ }
735
+ if (!result.tool_calls?.length && result.content && !finalResponseSaved) {
736
+ const content2 = opts.stream ? streamedAssistantContent : result.content;
737
+ const assistantMsg = {
738
+ id: msgId,
739
+ role: "assistant",
740
+ content: content2,
741
+ timestamp: this.clock.iso(),
742
+ usage: result.usage
743
+ };
744
+ await this.memory.append(convo, [assistantMsg]);
745
+ finalResponseSaved = true;
746
+ if (content2.trim()) {
747
+ const messageEvent = {
748
+ type: AgentEventTypes.AssistantMessage,
749
+ conversationId: convo,
750
+ messageId: msgId,
751
+ content: content2,
752
+ ...result.usage && { usage: result.usage }
753
+ };
754
+ await this.events?.emit(messageEvent);
755
+ }
756
+ }
757
+ while (result.tool_calls?.length) {
758
+ if (result.content?.trim()) {
759
+ const messageEvent = {
760
+ type: AgentEventTypes.AssistantMessage,
761
+ conversationId: convo,
762
+ messageId: msgId,
763
+ content: result.content,
764
+ ...result.usage && { usage: result.usage }
765
+ };
766
+ await this.events?.emit(messageEvent);
767
+ }
768
+ if (opts.signal?.aborted) throw new Error("Aborted");
769
+ await this.events?.emit({
770
+ type: AgentEventTypes.ToolCalls,
771
+ conversationId: convo,
772
+ messageId: msgId,
773
+ toolCalls: result.tool_calls,
774
+ usage: result.usage
775
+ });
776
+ const approvalResult = await this.processToolApproval(
777
+ result.tool_calls,
778
+ convo,
779
+ msgId,
780
+ accumulatedMessages,
781
+ turnHistory,
782
+ result.content,
783
+ result.usage
784
+ );
785
+ if (approvalResult.wasDenied) {
786
+ denialMessage = approvalResult.denialMessage || "";
787
+ toolApprovalDenied = true;
788
+ break;
789
+ }
790
+ const approvedCalls = approvalResult.approvedCalls;
791
+ const invocations = this.toInvocations(approvedCalls);
792
+ if (opts.signal?.aborted) throw new Error("Aborted");
793
+ const toolResults = await this.tools.executeToolCalls(
794
+ invocations,
795
+ {
796
+ conversationId: convo,
797
+ agentId: this.cfg.id,
798
+ messageId: msgId,
799
+ eventPort: this.events
800
+ },
801
+ this.cfg.maxToolConcurrency ?? 3,
802
+ opts.signal
803
+ );
804
+ allToolResults.push(...toolResults);
805
+ const assistantMsg = {
806
+ id: this.ids.uuid(),
807
+ role: "assistant",
808
+ content: result.content ?? null,
809
+ timestamp: this.clock.iso(),
810
+ tool_calls: approvedCalls,
811
+ usage: result.usage
812
+ };
813
+ const toolResultMsgs = [];
814
+ for (const tr of toolResults) {
815
+ const contentStr = tr.status === "error" ? String(tr.result) : typeof tr.result === "string" ? tr.result : JSON.stringify(tr.result);
816
+ toolResultMsgs.push({
817
+ id: tr.id,
818
+ role: "tool",
819
+ content: contentStr,
820
+ timestamp: this.clock.iso(),
821
+ tool_call_id: tr.id,
822
+ name: tr.name
823
+ });
824
+ this.metrics?.recordToolCall?.();
825
+ await this.events?.emit({
826
+ type: AgentEventTypes.ToolResult,
827
+ conversationId: convo,
828
+ messageId: msgId,
829
+ result: tr
830
+ });
831
+ }
832
+ await this.memory.append(convo, [assistantMsg, ...toolResultMsgs]);
833
+ const { usage: _usage, ...extraField } = result;
834
+ accumulatedMessages.push({
835
+ ...extraField,
836
+ role: "assistant",
837
+ content: result.content ?? null,
838
+ tool_calls: approvedCalls
839
+ });
840
+ for (const tr of toolResults) {
841
+ const contentStr = tr.status === "error" ? String(tr.result) : typeof tr.result === "string" ? tr.result : JSON.stringify(tr.result);
842
+ accumulatedMessages.push({ role: "tool", content: contentStr, tool_call_id: tr.id, name: tr.name });
843
+ }
844
+ if (opts.signal?.aborted) throw new Error("Aborted");
845
+ streamedAssistantContent = "";
846
+ if (opts.stream && typeof _llm.streamCompletion === "function") {
320
847
  let isFirstChunk = true;
321
- result = await this.deps.llm.streamCompletion(
322
- params,
848
+ result = await _llm.streamCompletion(
849
+ { ...params, messages: accumulatedMessages },
323
850
  {
324
851
  onChunk: async (delta, usage) => {
325
- try {
326
- streamedAssistantContent += delta;
327
- } catch {
328
- }
852
+ streamedAssistantContent += delta;
329
853
  const cleanDelta = isFirstChunk ? delta.replace(/^\n+/, "") : delta;
330
854
  isFirstChunk = false;
331
855
  const chunkEvent = {
@@ -335,9 +859,13 @@ var AgentOrchestrator = class {
335
859
  delta: cleanDelta,
336
860
  ...usage && { usage }
337
861
  };
338
- await this.deps.events?.emit(chunkEvent);
862
+ await this.events?.emit(chunkEvent);
339
863
  },
340
864
  onStreamFinish: async (finishReason, usage) => {
865
+ if (usage) {
866
+ const cost = this.cost.estimate(this.cfg.model, usage);
867
+ this.metrics?.recordLLMCall?.(usage, cost);
868
+ }
341
869
  const finishEvent = {
342
870
  type: AgentEventTypes.StreamFinish,
343
871
  conversationId: convo,
@@ -345,24 +873,28 @@ var AgentOrchestrator = class {
345
873
  ...finishReason && { finishReason },
346
874
  ...usage && { usage }
347
875
  };
348
- await this.deps.events?.emit(finishEvent);
876
+ await this.events?.emit(finishEvent);
349
877
  }
350
878
  },
351
879
  opts.signal
352
880
  );
353
881
  } else {
354
- result = await this.deps.llm.generateCompletion(params, opts.signal);
882
+ result = await _llm.generateCompletion({ ...params, messages: accumulatedMessages }, opts.signal);
883
+ if (result.usage) {
884
+ const cost = this.cost.estimate(this.cfg.model, result.usage);
885
+ this.metrics?.recordLLMCall?.(result.usage, cost);
886
+ }
355
887
  }
356
888
  if (!result.tool_calls?.length && result.content && !finalResponseSaved) {
357
889
  const content2 = opts.stream ? streamedAssistantContent : result.content;
358
- const assistantMsg = {
890
+ const assistantMsg2 = {
359
891
  id: msgId,
360
892
  role: "assistant",
361
893
  content: content2,
362
- timestamp: this.deps.clock.iso(),
894
+ timestamp: this.clock.iso(),
363
895
  usage: result.usage
364
896
  };
365
- await this.deps.memory.append(convo, [assistantMsg]);
897
+ await this.memory.append(convo, [assistantMsg2]);
366
898
  finalResponseSaved = true;
367
899
  if (content2.trim()) {
368
900
  const messageEvent = {
@@ -372,199 +904,56 @@ var AgentOrchestrator = class {
372
904
  content: content2,
373
905
  ...result.usage && { usage: result.usage }
374
906
  };
375
- await this.deps.events?.emit(messageEvent);
376
- }
377
- }
378
- while (result.tool_calls?.length) {
379
- if (result.content?.trim()) {
380
- const messageEvent = {
381
- type: AgentEventTypes.AssistantMessage,
382
- conversationId: convo,
383
- messageId: msgId,
384
- content: result.content,
385
- ...result.usage && { usage: result.usage }
386
- };
387
- await this.deps.events?.emit(messageEvent);
388
- }
389
- if (opts.signal?.aborted) throw new Error("Aborted");
390
- await this.deps.events?.emit({
391
- type: AgentEventTypes.ToolCalls,
392
- conversationId: convo,
393
- messageId: msgId,
394
- toolCalls: result.tool_calls,
395
- usage: result.usage
396
- });
397
- const approvalResult = await this.processToolApproval(
398
- result.tool_calls,
399
- convo,
400
- msgId,
401
- accumulatedMessages,
402
- turnHistory,
403
- result.content,
404
- result.usage
405
- );
406
- if (approvalResult.wasDenied) {
407
- denialMessage = approvalResult.denialMessage || "";
408
- toolApprovalDenied = true;
409
- break;
410
- }
411
- const approvedCalls = approvalResult.approvedCalls;
412
- const invocations = this.toInvocations(approvedCalls);
413
- if (opts.signal?.aborted) throw new Error("Aborted");
414
- const toolResults = await this.deps.tools.executeToolCalls(
415
- invocations,
416
- {
417
- conversationId: convo,
418
- agentId: this.cfg.id,
419
- messageId: msgId,
420
- eventPort: this.deps.events
421
- },
422
- this.cfg.maxToolConcurrency ?? 3,
423
- opts.signal
424
- );
425
- allToolResults.push(...toolResults);
426
- const assistantMsg = {
427
- id: this.deps.ids.uuid(),
428
- role: "assistant",
429
- content: result.content ?? null,
430
- timestamp: this.deps.clock.iso(),
431
- tool_calls: approvedCalls,
432
- usage: result.usage
433
- };
434
- const toolResultMsgs = [];
435
- for (const tr of toolResults) {
436
- const contentStr = tr.status === "error" ? String(tr.result) : typeof tr.result === "string" ? tr.result : JSON.stringify(tr.result);
437
- toolResultMsgs.push({
438
- id: tr.id,
439
- role: "tool",
440
- content: contentStr,
441
- timestamp: this.deps.clock.iso(),
442
- tool_call_id: tr.id,
443
- name: tr.name
444
- });
445
- await this.deps.events?.emit({
446
- type: AgentEventTypes.ToolResult,
447
- conversationId: convo,
448
- messageId: msgId,
449
- result: tr
450
- });
451
- }
452
- await this.deps.memory.append(convo, [assistantMsg, ...toolResultMsgs]);
453
- accumulatedMessages.push({ role: "assistant", content: result.content ?? null, tool_calls: approvedCalls });
454
- for (const tr of toolResults) {
455
- const contentStr = tr.status === "error" ? String(tr.result) : typeof tr.result === "string" ? tr.result : JSON.stringify(tr.result);
456
- accumulatedMessages.push({ role: "tool", content: contentStr, tool_call_id: tr.id, name: tr.name });
457
- }
458
- if (opts.signal?.aborted) throw new Error("Aborted");
459
- streamedAssistantContent = "";
460
- if (opts.stream && typeof this.deps.llm.streamCompletion === "function") {
461
- let isFirstChunk = true;
462
- result = await this.deps.llm.streamCompletion(
463
- { ...params, messages: accumulatedMessages },
464
- {
465
- onChunk: async (delta, usage) => {
466
- try {
467
- streamedAssistantContent += delta;
468
- } catch {
469
- }
470
- const cleanDelta = isFirstChunk ? delta.replace(/^\n+/, "") : delta;
471
- isFirstChunk = false;
472
- const chunkEvent = {
473
- type: AgentEventTypes.AssistantChunk,
474
- conversationId: convo,
475
- messageId: msgId,
476
- delta: cleanDelta,
477
- ...usage && { usage }
478
- };
479
- await this.deps.events?.emit(chunkEvent);
480
- },
481
- onStreamFinish: async (finishReason, usage) => {
482
- const finishEvent = {
483
- type: AgentEventTypes.StreamFinish,
484
- conversationId: convo,
485
- messageId: msgId,
486
- ...finishReason && { finishReason },
487
- ...usage && { usage }
488
- };
489
- await this.deps.events?.emit(finishEvent);
490
- }
491
- },
492
- opts.signal
493
- );
494
- } else {
495
- result = await this.deps.llm.generateCompletion({ ...params, messages: accumulatedMessages }, opts.signal);
496
- }
497
- if (!result.tool_calls?.length && result.content && !finalResponseSaved) {
498
- const content2 = opts.stream ? streamedAssistantContent : result.content;
499
- const assistantMsg2 = {
500
- id: msgId,
501
- role: "assistant",
502
- content: content2,
503
- timestamp: this.deps.clock.iso(),
504
- usage: result.usage
505
- };
506
- await this.deps.memory.append(convo, [assistantMsg2]);
507
- finalResponseSaved = true;
508
- if (content2.trim()) {
509
- const messageEvent = {
510
- type: AgentEventTypes.AssistantMessage,
511
- conversationId: convo,
512
- messageId: msgId,
513
- content: content2,
514
- ...result.usage && { usage: result.usage }
515
- };
516
- await this.deps.events?.emit(messageEvent);
517
- }
907
+ await this.events?.emit(messageEvent);
518
908
  }
519
909
  }
520
- const t1 = this.deps.clock.now();
521
- const timestamp = this.deps.clock.iso();
522
- const shouldEmitFinalMessage = result.content?.trim() && !toolApprovalDenied && !finalResponseSaved;
523
- if (shouldEmitFinalMessage) {
524
- const messageEvent = {
525
- type: AgentEventTypes.AssistantMessage,
526
- conversationId: convo,
527
- messageId: msgId,
528
- content: result.content,
529
- ...result.usage && { usage: result.usage }
530
- };
531
- await this.deps.events?.emit(messageEvent);
532
- }
533
- const responseContent = toolApprovalDenied ? denialMessage : result.content;
534
- const resp = {
535
- id: msgId,
536
- content: responseContent,
537
- role: MessageRoles.Assistant,
538
- timestamp,
539
- metadata: {
540
- model: this.cfg.model,
541
- provider: "echo",
542
- agentId: this.cfg.id,
543
- responseTime: t1 - t0,
544
- promptTokens: result.usage?.prompt_tokens,
545
- completionTokens: result.usage?.completion_tokens,
546
- totalTokens: result.usage?.total_tokens,
547
- estimatedCost: this.deps.cost.estimate(this.cfg.model, result.usage),
548
- toolCalls: allToolResults.length
549
- }
550
- };
551
- await this.deps.events?.emit({
552
- type: AgentEventTypes.Done,
910
+ }
911
+ const t1 = this.clock.now();
912
+ const timestamp = this.clock.iso();
913
+ this.metrics?.recordRequestComplete?.(t1 - t0);
914
+ const shouldEmitFinalMessage = result.content?.trim() && !toolApprovalDenied && !finalResponseSaved;
915
+ if (shouldEmitFinalMessage) {
916
+ const messageEvent = {
917
+ type: AgentEventTypes.AssistantMessage,
553
918
  conversationId: convo,
554
919
  messageId: msgId,
555
- responseTimeMs: t1 - t0,
556
- usage: result.usage
557
- });
558
- return resp;
559
- } catch (err2) {
560
- throw err2;
561
- }
920
+ content: result.content,
921
+ ...result.usage && { usage: result.usage }
922
+ };
923
+ await this.events?.emit(messageEvent);
924
+ }
925
+ const responseContent = toolApprovalDenied ? denialMessage : result.content;
926
+ const resp = {
927
+ id: msgId,
928
+ content: responseContent,
929
+ role: MessageRoles.Assistant,
930
+ timestamp,
931
+ metadata: {
932
+ model: this.cfg.model,
933
+ provider: "echo",
934
+ agentId: this.cfg.id,
935
+ responseTime: t1 - t0,
936
+ promptTokens: result.usage?.prompt_tokens,
937
+ completionTokens: result.usage?.completion_tokens,
938
+ totalTokens: result.usage?.total_tokens,
939
+ estimatedCost: this.cost.estimate(this.cfg.model, result.usage),
940
+ toolCalls: allToolResults.length
941
+ }
942
+ };
943
+ await this.events?.emit({
944
+ type: AgentEventTypes.Done,
945
+ conversationId: convo,
946
+ messageId: msgId,
947
+ responseTimeMs: t1 - t0,
948
+ usage: result.usage
949
+ });
950
+ return resp;
562
951
  }
563
952
  async waitForToolApproval(toolCalls, conversationId, messageId) {
564
- const approvalId = this.deps.ids.uuid();
953
+ const approvalId = this.ids.uuid();
565
954
  return new Promise((resolve6, reject) => {
566
955
  this.pendingApprovals.set(approvalId, { resolve: resolve6, reject });
567
- this.deps.events?.emit({
956
+ this.events?.emit({
568
957
  type: AgentEventTypes.ToolApprovalRequired,
569
958
  conversationId,
570
959
  messageId,
@@ -593,71 +982,11 @@ var AgentOrchestrator = class {
593
982
  let parameters = {};
594
983
  try {
595
984
  parameters = JSON.parse(tc.function.arguments || "{}");
596
- } catch {
597
- parameters = {};
598
- }
599
- return { id: tc.id, name: tc.function.name, parameters };
600
- });
601
- }
602
- };
603
-
604
- // context.ts
605
- var toProviderContent = (content) => {
606
- if (content === null || content === void 0) {
607
- return "";
608
- }
609
- if (typeof content === "string") {
610
- return content;
611
- }
612
- if (content.type === "parts") {
613
- const providerParts = [];
614
- for (const part of content.parts) {
615
- if (part.type === "text") {
616
- if (part.text.length > 0) {
617
- providerParts.push({ type: "text", text: part.text });
618
- }
619
- continue;
620
- }
621
- const label = part.altText ?? (part.name ? `Image attachment: ${part.name}` : void 0);
622
- if (label) {
623
- providerParts.push({ type: "text", text: label });
624
- }
625
- const url = `data:${part.mimeType};base64,${part.data}`;
626
- providerParts.push({ type: "image_url", image_url: { url } });
627
- }
628
- return providerParts.length > 0 ? providerParts : [];
629
- }
630
- return [];
631
- };
632
- var SimpleContextBuilder = class {
633
- toProviderMessages(history, systemPrompt, newUserContent) {
634
- const transformed = [];
635
- for (const m of history) {
636
- const providerContent = toProviderContent(m.content);
637
- if (m.role === "user") {
638
- transformed.push({ role: "user", content: providerContent ?? "" });
639
- } else if (m.role === "assistant") {
640
- if (m.tool_calls && m.tool_calls.length > 0) {
641
- transformed.push({ role: "assistant", content: providerContent ?? null, tool_calls: m.tool_calls });
642
- } else {
643
- transformed.push({ role: "assistant", content: providerContent ?? "" });
644
- }
645
- } else if (m.role === "tool") {
646
- if (m.tool_call_id) {
647
- transformed.push({
648
- role: "tool",
649
- content: typeof providerContent === "string" ? providerContent : providerContent ?? "",
650
- tool_call_id: m.tool_call_id,
651
- name: m.name
652
- });
653
- }
985
+ } catch {
986
+ parameters = {};
654
987
  }
655
- }
656
- const userMsgs = newUserContent.map((c) => ({
657
- role: "user",
658
- content: toProviderContent(c) ?? ""
659
- }));
660
- return [{ role: "system", content: systemPrompt }, ...transformed, ...userMsgs];
988
+ return { id: tc.id, name: tc.function.name, parameters };
989
+ });
661
990
  }
662
991
  };
663
992
 
@@ -922,7 +1251,12 @@ async function buildTree(dir, rootDir, gitignore, options, depth, fileCount) {
922
1251
  if (depth >= options.maxDepth || fileCount.count >= options.maxFiles) {
923
1252
  return [];
924
1253
  }
925
- const entries = await fs2.readdir(dir, { withFileTypes: true });
1254
+ let entries;
1255
+ try {
1256
+ entries = await fs2.readdir(dir, { withFileTypes: true });
1257
+ } catch {
1258
+ return [];
1259
+ }
926
1260
  const lines = [];
927
1261
  const sortedEntries = entries.sort((a, b) => {
928
1262
  if (a.isDirectory() && !b.isDirectory()) return -1;
@@ -966,123 +1300,6 @@ async function generateFolderTree(rootDir, options = {}) {
966
1300
  return [header, ...lines].join("\n");
967
1301
  }
968
1302
 
969
- // persistent/memory.ts
970
- var InMemoryMemory = class {
971
- store = /* @__PURE__ */ new Map();
972
- async get(key) {
973
- return this.store.get(key) ?? [];
974
- }
975
- async set(key, items) {
976
- this.store.set(key, [...items]);
977
- }
978
- async append(key, items) {
979
- const existing = this.store.get(key) ?? [];
980
- this.store.set(key, [...existing, ...items]);
981
- }
982
- async delete(key) {
983
- this.store.delete(key);
984
- }
985
- async keys() {
986
- return Array.from(this.store.keys());
987
- }
988
- async clear() {
989
- this.store.clear();
990
- }
991
- async exportSnapshot() {
992
- const snap = {};
993
- for (const [k, v] of this.store.entries()) snap[k] = [...v];
994
- return snap;
995
- }
996
- async importSnapshot(snapshot) {
997
- this.store.clear();
998
- for (const [k, v] of Object.entries(snapshot)) this.store.set(k, [...v]);
999
- }
1000
- };
1001
- var JsonFileMemoryPersistence = class {
1002
- constructor(filename = "history.json") {
1003
- this.filename = filename;
1004
- }
1005
- async load() {
1006
- try {
1007
- const fs10 = await import("fs");
1008
- if (!fs10.existsSync(this.filename)) return {};
1009
- const text = fs10.readFileSync(this.filename, "utf-8");
1010
- const data = JSON.parse(text);
1011
- return typeof data === "object" && data ? data : {};
1012
- } catch {
1013
- console.warn(`Failed to load memory from ${this.filename}`);
1014
- return {};
1015
- }
1016
- }
1017
- async save(snapshot) {
1018
- try {
1019
- const fs10 = await import("fs");
1020
- const path9 = await import("path");
1021
- const dir = path9.dirname(this.filename);
1022
- if (dir && dir !== "." && !fs10.existsSync(dir)) {
1023
- fs10.mkdirSync(dir, { recursive: true });
1024
- }
1025
- fs10.writeFileSync(this.filename, JSON.stringify(snapshot, null, 2), "utf-8");
1026
- } catch (err2) {
1027
- console.warn(`Failed to save memory to ${this.filename}`, err2);
1028
- }
1029
- }
1030
- };
1031
- var PersistedMemory = class {
1032
- constructor(persistence) {
1033
- this.persistence = persistence;
1034
- }
1035
- inner = new InMemoryMemory();
1036
- initialized = false;
1037
- async ensureInitialized() {
1038
- if (this.initialized) return;
1039
- const snap = await this.persistence.load();
1040
- if (snap && typeof snap === "object") await this.inner.importSnapshot(snap);
1041
- this.initialized = true;
1042
- }
1043
- async save() {
1044
- const snap = await this.inner.exportSnapshot();
1045
- await this.persistence.save(snap);
1046
- }
1047
- async get(key) {
1048
- await this.ensureInitialized();
1049
- return this.inner.get(key);
1050
- }
1051
- async set(key, items) {
1052
- await this.ensureInitialized();
1053
- await this.inner.set(key, items);
1054
- await this.save();
1055
- }
1056
- async append(key, items) {
1057
- await this.ensureInitialized();
1058
- await this.inner.append(key, items);
1059
- await this.save();
1060
- }
1061
- async delete(key) {
1062
- await this.ensureInitialized();
1063
- await this.inner.delete(key);
1064
- await this.save();
1065
- }
1066
- async keys() {
1067
- await this.ensureInitialized();
1068
- return this.inner.keys();
1069
- }
1070
- async clear() {
1071
- await this.ensureInitialized();
1072
- await this.inner.clear();
1073
- await this.save();
1074
- }
1075
- async exportSnapshot() {
1076
- await this.ensureInitialized();
1077
- return this.inner.exportSnapshot();
1078
- }
1079
- async importSnapshot(snapshot) {
1080
- await this.ensureInitialized();
1081
- await this.inner.importSnapshot(snapshot);
1082
- await this.save();
1083
- }
1084
- };
1085
-
1086
1303
  // persistent/metadata-memory.ts
1087
1304
  var MemoryPortMetadataAdapter = class {
1088
1305
  constructor(memory, prefix = "__metadata__") {
@@ -1217,6 +1434,27 @@ var ConversationStore = class {
1217
1434
  };
1218
1435
  await this.metadataMemory.set(conversationId, updatedMetadata);
1219
1436
  }
1437
+ async recordRequestMetrics(conversationId, metrics) {
1438
+ const metadata = await this.metadataMemory.get(conversationId);
1439
+ const updatedMetadata = {
1440
+ ...metadata,
1441
+ promptTokens: (metadata?.promptTokens ?? 0) + (metrics.promptTokens ?? 0),
1442
+ completionTokens: (metadata?.completionTokens ?? 0) + (metrics.completionTokens ?? 0),
1443
+ totalTokens: (metadata?.totalTokens ?? 0) + (metrics.totalTokens ?? 0),
1444
+ requestCount: (metadata?.requestCount ?? 0) + 1,
1445
+ toolCallCount: (metadata?.toolCallCount ?? 0) + (metrics.toolCalls ?? 0),
1446
+ totalTimeMs: (metadata?.totalTimeMs ?? 0) + (metrics.responseTimeMs ?? 0),
1447
+ totalPrice: (metadata?.totalPrice ?? 0) + (metrics.cost ?? 0),
1448
+ contextWindow: {
1449
+ promptTokens: metrics.promptTokens,
1450
+ completionTokens: metrics.completionTokens,
1451
+ totalTokens: metrics.totalTokens
1452
+ },
1453
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1454
+ };
1455
+ await this.metadataMemory.set(conversationId, updatedMetadata);
1456
+ return updatedMetadata;
1457
+ }
1220
1458
  async updateTopic(conversationId, topic) {
1221
1459
  const metadata = await this.metadataMemory.get(conversationId);
1222
1460
  const updatedMetadata = {
@@ -1279,41 +1517,6 @@ var ConversationContext = class {
1279
1517
  }
1280
1518
  };
1281
1519
 
1282
- // id.ts
1283
- var SimpleId = class {
1284
- uuid() {
1285
- return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
1286
- const r = Math.random() * 16 | 0;
1287
- const v = c === "x" ? r : r & 3 | 8;
1288
- return v.toString(16);
1289
- });
1290
- }
1291
- };
1292
-
1293
- // clock.ts
1294
- var SystemClock = class {
1295
- now() {
1296
- return Date.now();
1297
- }
1298
- iso(dateMs) {
1299
- return new Date(dateMs ?? Date.now()).toISOString();
1300
- }
1301
- };
1302
-
1303
- // cost.ts
1304
- var SimpleCost = class {
1305
- estimate(_model, usage) {
1306
- return usage?.cost;
1307
- }
1308
- };
1309
-
1310
- // reminders.ts
1311
- var NoopReminders = class {
1312
- enhance(content, _opts) {
1313
- return [content];
1314
- }
1315
- };
1316
-
1317
1520
  // todo-store.ts
1318
1521
  var TodoStore = class {
1319
1522
  constructor(memory) {
@@ -3612,9 +3815,12 @@ var AgentFilePersistence = class {
3612
3815
  * Load all agents from the agents directory
3613
3816
  */
3614
3817
  async loadAll() {
3615
- this.ensureAgentsDir();
3616
3818
  const agents = [];
3617
3819
  try {
3820
+ this.ensureAgentsDir();
3821
+ if (!fs8.existsSync(this.agentsDir)) {
3822
+ return agents;
3823
+ }
3618
3824
  const files = fs8.readdirSync(this.agentsDir);
3619
3825
  const yamlFiles = files.filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"));
3620
3826
  for (const file of yamlFiles) {
@@ -3627,8 +3833,7 @@ var AgentFilePersistence = class {
3627
3833
  console.warn(`Failed to load agent from ${file}:`, error);
3628
3834
  }
3629
3835
  }
3630
- } catch (error) {
3631
- console.warn("Failed to read agents directory:", error);
3836
+ } catch (_error) {
3632
3837
  }
3633
3838
  return agents;
3634
3839
  }
@@ -3717,6 +3922,7 @@ var AgentFilePersistence = class {
3717
3922
  function mergeChoices(choices) {
3718
3923
  const contentParts = [];
3719
3924
  const mergedToolCalls = [];
3925
+ const extraFields = {};
3720
3926
  const collectText = (value) => {
3721
3927
  if (typeof value === "string") {
3722
3928
  const trimmed = value.trim();
@@ -3737,10 +3943,20 @@ function mergeChoices(choices) {
3737
3943
  if (!msg) continue;
3738
3944
  collectText(msg.content);
3739
3945
  if (Array.isArray(msg.tool_calls)) mergedToolCalls.push(...msg.tool_calls);
3946
+ const knownKeys = ["content", "tool_calls", "role"];
3947
+ for (const key of Object.keys(msg)) {
3948
+ if (!knownKeys.includes(key)) {
3949
+ extraFields[key] = msg[key];
3950
+ }
3951
+ }
3740
3952
  }
3741
3953
  const content = contentParts.join("\n\n");
3742
3954
  const tool_calls = mergedToolCalls.length ? mergedToolCalls : void 0;
3743
- return { content, ...tool_calls ? { tool_calls } : {} };
3955
+ return {
3956
+ content,
3957
+ ...tool_calls ? { tool_calls } : {},
3958
+ ...extraFields
3959
+ };
3744
3960
  }
3745
3961
  function normalizeUsage(usage) {
3746
3962
  if (!usage) return void 0;
@@ -3748,6 +3964,7 @@ function normalizeUsage(usage) {
3748
3964
  const prompt_tokens = usage.prompt_tokens ?? (typeof usageObj.input_tokens === "number" ? usageObj.input_tokens : void 0);
3749
3965
  const completion_tokens = usage.completion_tokens ?? (typeof usageObj.output_tokens === "number" ? usageObj.output_tokens : void 0);
3750
3966
  const total_tokens = usage.total_tokens ?? (prompt_tokens != null && completion_tokens != null ? prompt_tokens + completion_tokens : void 0);
3967
+ const cost = usage.cost ?? (typeof usageObj.estimated_cost === "number" ? usageObj.estimated_cost : void 0);
3751
3968
  return {
3752
3969
  prompt_tokens,
3753
3970
  completion_tokens,
@@ -3755,7 +3972,7 @@ function normalizeUsage(usage) {
3755
3972
  ...usage.reasoning_tokens !== void 0 && { reasoning_tokens: usage.reasoning_tokens },
3756
3973
  ...usage.prompt_tokens_details && { prompt_tokens_details: usage.prompt_tokens_details },
3757
3974
  ...usage.completion_tokens_details && { completion_tokens_details: usage.completion_tokens_details },
3758
- ...usage.cost !== void 0 && { cost: usage.cost },
3975
+ ...cost !== void 0 && { cost },
3759
3976
  ...usage.cost_details && { cost_details: usage.cost_details }
3760
3977
  };
3761
3978
  }
@@ -3901,6 +4118,7 @@ var BaseLLM = class {
3901
4118
  const mergedToolCalls = [];
3902
4119
  let usage;
3903
4120
  let lastFinishReason;
4121
+ const extraFields = {};
3904
4122
  const flushEvent = (rawEvent) => {
3905
4123
  const lines = rawEvent.split("\n");
3906
4124
  const dataLines = [];
@@ -3941,6 +4159,16 @@ var BaseLLM = class {
3941
4159
  handlers.onChunk?.(textDelta);
3942
4160
  }
3943
4161
  }
4162
+ const knownKeys = ["role", "content", "tool_calls"];
4163
+ for (const key of Object.keys(delta)) {
4164
+ if (knownKeys.includes(key)) continue;
4165
+ const val = delta[key];
4166
+ if (typeof val === "string") {
4167
+ extraFields[key] = (extraFields[key] || "") + val;
4168
+ } else if (val !== void 0 && val !== null) {
4169
+ extraFields[key] = val;
4170
+ }
4171
+ }
3944
4172
  const toolDeltas = Array.isArray(delta.tool_calls) ? delta.tool_calls : [];
3945
4173
  for (const td of toolDeltas) {
3946
4174
  let toolCall;
@@ -3948,6 +4176,7 @@ var BaseLLM = class {
3948
4176
  toolCall = mergedToolCalls.find((tc) => tc.id === td.id);
3949
4177
  if (!toolCall) {
3950
4178
  toolCall = {
4179
+ ...td,
3951
4180
  id: td.id,
3952
4181
  type: "function",
3953
4182
  function: { name: td.function?.name ?? "", arguments: "" }
@@ -3994,7 +4223,12 @@ var BaseLLM = class {
3994
4223
  if (buffer.trim()) flushEvent(buffer);
3995
4224
  content = content.replace(/^\n+/, "");
3996
4225
  const tool_calls = mergedToolCalls.length ? mergedToolCalls : void 0;
3997
- return { content, ...tool_calls ? { tool_calls } : {}, ...usage ? { usage } : {} };
4226
+ return {
4227
+ content,
4228
+ ...tool_calls ? { tool_calls } : {},
4229
+ ...usage ? { usage } : {},
4230
+ ...extraFields
4231
+ };
3998
4232
  }
3999
4233
  };
4000
4234
 
@@ -4364,8 +4598,9 @@ var GithubAuthTransport = class {
4364
4598
  method: "GET",
4365
4599
  headers: {
4366
4600
  Authorization: `Bearer ${this.accessToken}`,
4367
- "user-agent": "GitHubCopilotChat/0.31.3",
4368
- "editor-version": "vscode/1.104.2",
4601
+ "user-agent": "GitHubCopilotChat/0.33.1",
4602
+ "editor-version": "vscode/1.106.1",
4603
+ "x-github-api-version": "2025-10-01",
4369
4604
  accept: "application/json"
4370
4605
  },
4371
4606
  signal
@@ -4454,11 +4689,12 @@ var GithubAuthTransport = class {
4454
4689
  return res;
4455
4690
  }
4456
4691
  async postStream(url, body, headers, signal) {
4457
- if (!this.apiKey && this.accessToken) {
4692
+ let hdrs = this.makeAuthHeaders({ Accept: "text/event-stream", ...headers || {} }, body);
4693
+ if ((!this.apiKey || !hdrs.Authorization) && this.accessToken) {
4458
4694
  await this.exchangeToken(signal);
4695
+ hdrs = this.makeAuthHeaders({ Accept: "text/event-stream", ...headers || {} }, body);
4459
4696
  }
4460
4697
  const fullUrl = this.buildFullUrl(url);
4461
- const hdrs = this.makeAuthHeaders({ Accept: "text/event-stream", ...headers || {} }, body);
4462
4698
  let res = await this.inner.postStream(fullUrl, body, hdrs, signal);
4463
4699
  if (res.status === 401 && this.accessToken) {
4464
4700
  await this.exchangeToken(signal);
@@ -4529,6 +4765,105 @@ function createTransport(inner, defaultBaseUrl, apiKey, baseUrl, version) {
4529
4765
  return new SimpleBearerAuthTransport(inner, defaultBaseUrl, apiKey, baseUrl, version);
4530
4766
  }
4531
4767
 
4768
+ // llm-providers/model-limits.ts
4769
+ function normalizeModelLimits(provider, model) {
4770
+ switch (provider.toLowerCase()) {
4771
+ case "github": {
4772
+ const capabilities = model.capabilities;
4773
+ const limits = capabilities?.limits;
4774
+ const contextWindow = limits?.max_context_window_tokens;
4775
+ if (!contextWindow) return null;
4776
+ return {
4777
+ contextWindow,
4778
+ maxOutput: limits?.max_output_tokens
4779
+ };
4780
+ }
4781
+ case "deepinfra": {
4782
+ const metadata = model.metadata;
4783
+ const contextLength = metadata?.context_length;
4784
+ if (!contextLength) return null;
4785
+ return {
4786
+ contextWindow: contextLength,
4787
+ maxOutput: metadata?.max_tokens
4788
+ };
4789
+ }
4790
+ case "moonshot": {
4791
+ const contextLength = model.context_length;
4792
+ if (!contextLength) return null;
4793
+ return { contextWindow: contextLength };
4794
+ }
4795
+ case "openrouter": {
4796
+ const topProvider = model.top_provider;
4797
+ const contextLength = model.context_length ?? topProvider?.context_length;
4798
+ if (!contextLength) return null;
4799
+ return {
4800
+ contextWindow: contextLength,
4801
+ maxOutput: topProvider?.max_completion_tokens
4802
+ };
4803
+ }
4804
+ default: {
4805
+ const contextLength = model.context_length;
4806
+ if (!contextLength) return null;
4807
+ return { contextWindow: contextLength };
4808
+ }
4809
+ }
4810
+ }
4811
+ var FALLBACK_LIMITS = {
4812
+ zai: {
4813
+ "glm-4.6": { contextWindow: 2e5, maxOutput: 128e3 }
4814
+ },
4815
+ openrouter: {
4816
+ "anthropic/claude-sonnet-4": { contextWindow: 2e5, maxOutput: 16e3 },
4817
+ "anthropic/claude-3.5-sonnet": { contextWindow: 2e5, maxOutput: 8192 },
4818
+ "openai/gpt-4o": { contextWindow: 128e3, maxOutput: 16384 },
4819
+ "openai/gpt-4.1": { contextWindow: 128e3, maxOutput: 32768 },
4820
+ "openai/gpt-4o-mini": { contextWindow: 128e3, maxOutput: 16384 },
4821
+ "google/gemini-pro-1.5": { contextWindow: 2097152, maxOutput: 8192 },
4822
+ "meta-llama/llama-3.1-70b-instruct": { contextWindow: 131072, maxOutput: 131072 }
4823
+ },
4824
+ github: {
4825
+ "gpt-4.1": { contextWindow: 128e3, maxOutput: 16384 },
4826
+ "gpt-4o": { contextWindow: 128e3, maxOutput: 16384 },
4827
+ "gpt-4o-mini": { contextWindow: 128e3, maxOutput: 16384 },
4828
+ "claude-sonnet-4": { contextWindow: 2e5, maxOutput: 16e3 },
4829
+ "claude-3.5-sonnet": { contextWindow: 2e5, maxOutput: 8192 },
4830
+ o1: { contextWindow: 2e5, maxOutput: 1e5 },
4831
+ "o1-mini": { contextWindow: 128e3, maxOutput: 65536 }
4832
+ },
4833
+ deepinfra: {
4834
+ "meta-llama/Meta-Llama-3.1-70B-Instruct": { contextWindow: 131072, maxOutput: 131072 },
4835
+ "meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo": { contextWindow: 131072, maxOutput: 131072 },
4836
+ "Qwen/Qwen2.5-72B-Instruct": { contextWindow: 131072, maxOutput: 131072 }
4837
+ },
4838
+ moonshot: {
4839
+ "moonshot-v1-8k": { contextWindow: 8192 },
4840
+ "moonshot-v1-32k": { contextWindow: 32768 },
4841
+ "moonshot-v1-128k": { contextWindow: 131072 },
4842
+ "kimi-k2-turbo-preview": { contextWindow: 262144 }
4843
+ },
4844
+ anthropic: {
4845
+ "claude-sonnet-4-5": { contextWindow: 2e5, maxOutput: 16e3 },
4846
+ "claude-3-5-sonnet-20241022": { contextWindow: 2e5, maxOutput: 8192 },
4847
+ "claude-3-opus-20240229": { contextWindow: 2e5, maxOutput: 4096 },
4848
+ "claude-3-haiku-20240307": { contextWindow: 2e5, maxOutput: 4096 }
4849
+ }
4850
+ };
4851
+ function getFallbackLimits(provider, model) {
4852
+ const providerLimits = FALLBACK_LIMITS[provider.toLowerCase()];
4853
+ if (!providerLimits) return null;
4854
+ return providerLimits[model] ?? null;
4855
+ }
4856
+ function normalizeModelInfo(provider, model) {
4857
+ const id = model.id;
4858
+ const name = model.name ?? id;
4859
+ const limits = normalizeModelLimits(provider, model);
4860
+ return {
4861
+ id,
4862
+ name,
4863
+ ...limits ? { limits } : {}
4864
+ };
4865
+ }
4866
+
4532
4867
  // llm-providers/llm-github.ts
4533
4868
  var GithubLLM = class extends BaseLLM {
4534
4869
  opts;
@@ -4552,6 +4887,49 @@ var GithubLLM = class extends BaseLLM {
4552
4887
  accessToken: this.opts.accessToken
4553
4888
  });
4554
4889
  }
4890
+ async getModels(signal) {
4891
+ const res = await this.getTransport().get("/models", void 0, signal);
4892
+ if (!res.ok) {
4893
+ const text = await res.text();
4894
+ throw new LLMError(text || `Failed to fetch models: ${res.status}`, res.status);
4895
+ }
4896
+ const body = await res.json();
4897
+ return body.data.map((m) => normalizeModelInfo("github", m));
4898
+ }
4899
+ handleError(error, model) {
4900
+ if (error instanceof LLMError) {
4901
+ try {
4902
+ const errorBody = JSON.parse(error.message);
4903
+ if (errorBody?.error?.code === "unsupported_api_for_model") {
4904
+ throw new LLMError(
4905
+ `The model '${model}' is not supported for chat completions. Please select a different model using '/model'.`,
4906
+ error.statusCode,
4907
+ false
4908
+ // Not retryable
4909
+ );
4910
+ }
4911
+ } catch (e) {
4912
+ if (e instanceof LLMError && e.message.includes("not supported")) {
4913
+ throw e;
4914
+ }
4915
+ }
4916
+ }
4917
+ throw error;
4918
+ }
4919
+ async generateCompletion(params, signal) {
4920
+ try {
4921
+ return await super.generateCompletion(params, signal);
4922
+ } catch (error) {
4923
+ this.handleError(error, params.model);
4924
+ }
4925
+ }
4926
+ async streamCompletion(params, handlers, signal) {
4927
+ try {
4928
+ return await super.streamCompletion(params, handlers, signal);
4929
+ } catch (error) {
4930
+ this.handleError(error, params.model);
4931
+ }
4932
+ }
4555
4933
  };
4556
4934
 
4557
4935
  // llm-providers/llm-anthropic-aisdk.ts
@@ -5049,11 +5427,13 @@ var GenericLLM = class extends BaseLLM {
5049
5427
  opts;
5050
5428
  includeUsage;
5051
5429
  modelConfig;
5430
+ providerName;
5052
5431
  constructor(baseUrl, modelConfig, opts = {}) {
5053
- const { enablePromptCaching = false, includeUsage = false, ...restOpts } = opts;
5432
+ const { enablePromptCaching = false, includeUsage = false, providerName = "unknown", ...restOpts } = opts;
5054
5433
  super(opts.apiUrl || baseUrl, { enablePromptCaching });
5055
5434
  this.includeUsage = includeUsage;
5056
5435
  this.modelConfig = modelConfig;
5436
+ this.providerName = providerName;
5057
5437
  this.opts = restOpts;
5058
5438
  }
5059
5439
  createTransport() {
@@ -5071,7 +5451,10 @@ var GenericLLM = class extends BaseLLM {
5071
5451
  throw new Error("Provider does not support getModels");
5072
5452
  }
5073
5453
  if (Array.isArray(this.modelConfig)) {
5074
- return this.modelConfig.map((m) => typeof m === "string" ? { id: m } : m);
5454
+ return this.modelConfig.map((m) => {
5455
+ const raw = typeof m === "string" ? { id: m } : m;
5456
+ return normalizeModelInfo(this.providerName, raw);
5457
+ });
5075
5458
  }
5076
5459
  if (typeof this.modelConfig === "string") {
5077
5460
  const transport2 = this.createTransport();
@@ -5081,7 +5464,7 @@ var GenericLLM = class extends BaseLLM {
5081
5464
  throw new Error(`Failed to fetch models: ${res2.status} ${text}`);
5082
5465
  }
5083
5466
  const data2 = await res2.json();
5084
- return data2.data;
5467
+ return data2.data.map((m) => normalizeModelInfo(this.providerName, m));
5085
5468
  }
5086
5469
  const transport = this.createTransport();
5087
5470
  const res = await transport.get("/models", void 0, signal);
@@ -5090,7 +5473,7 @@ var GenericLLM = class extends BaseLLM {
5090
5473
  throw new Error(`Failed to fetch models: ${res.status} ${text}`);
5091
5474
  }
5092
5475
  const data = await res.json();
5093
- return data.data;
5476
+ return data.data.map((m) => normalizeModelInfo(this.providerName, m));
5094
5477
  }
5095
5478
  async generateCompletion(params, signal) {
5096
5479
  let enhancedParams = params;
@@ -5149,6 +5532,7 @@ function createLLM(providerName, options = {}, customProviders) {
5149
5532
  const modelConfig = normalizeModelConfig(config);
5150
5533
  return new GenericLLM(config.baseUrl, modelConfig, {
5151
5534
  ...options,
5535
+ providerName: config.name,
5152
5536
  enablePromptCaching: options.enablePromptCaching ?? config.features.promptCaching,
5153
5537
  includeUsage: options.includeUsage ?? config.features.includeUsage
5154
5538
  });
@@ -5434,31 +5818,6 @@ function isValidConfig(value) {
5434
5818
  }
5435
5819
  return true;
5436
5820
  }
5437
-
5438
- // events.ts
5439
- var PersistingConsoleEventPort = class {
5440
- memory;
5441
- maxPerConversation;
5442
- writeQueue = Promise.resolve();
5443
- constructor(opts) {
5444
- this.memory = opts?.memory ?? new PersistedMemory(new JsonFileMemoryPersistence(opts?.filename || "events.json"));
5445
- this.maxPerConversation = opts?.maxPerConversation ?? 500;
5446
- }
5447
- async emit(event) {
5448
- this.writeQueue = this.writeQueue.then(async () => {
5449
- try {
5450
- const key = event?.conversationId ?? "default";
5451
- const existing = await this.memory.get(key);
5452
- const next = [...existing, { ...event }];
5453
- const max = this.maxPerConversation;
5454
- const trimmed = max > 0 && next.length > max ? next.slice(next.length - max) : next;
5455
- await this.memory.set(key, trimmed);
5456
- } catch {
5457
- }
5458
- });
5459
- return this.writeQueue;
5460
- }
5461
- };
5462
5821
  export {
5463
5822
  AGENT_CREATOR_SYSTEM_PROMPT,
5464
5823
  AgentEventTypes,
@@ -5482,11 +5841,13 @@ export {
5482
5841
  GithubLLM,
5483
5842
  InMemoryMemory,
5484
5843
  InMemoryMetadata,
5844
+ InMemoryMetricsPort,
5485
5845
  JsonFileMemoryPersistence,
5486
5846
  LLMError,
5487
5847
  LLMResolver,
5488
5848
  MCPToolPort,
5489
5849
  MemoryPortMetadataAdapter,
5850
+ NoopMetricsPort,
5490
5851
  NoopReminders,
5491
5852
  PersistedMemory,
5492
5853
  PersistingConsoleEventPort,
@@ -5499,10 +5860,14 @@ export {
5499
5860
  buildAgentCreationPrompt,
5500
5861
  buildInjectedSystem,
5501
5862
  canonicalizeTerminalPaste,
5863
+ createEmptySnapshot,
5502
5864
  createLLM,
5503
5865
  generateFolderTree,
5504
5866
  getAvailableProviders,
5867
+ getFallbackLimits,
5505
5868
  loadMCPConfig,
5869
+ normalizeModelInfo,
5870
+ normalizeModelLimits,
5506
5871
  normalizeNewlines,
5507
5872
  renderTemplate,
5508
5873
  resolveBackspaces,