@nuvin/nuvin-core 1.2.0 → 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,307 +904,89 @@ 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
- const { usage: _usage, ...extraField } = result;
454
- accumulatedMessages.push({
455
- ...extraField,
456
- role: "assistant",
457
- content: result.content ?? null,
458
- tool_calls: approvedCalls
459
- });
460
- for (const tr of toolResults) {
461
- const contentStr = tr.status === "error" ? String(tr.result) : typeof tr.result === "string" ? tr.result : JSON.stringify(tr.result);
462
- accumulatedMessages.push({ role: "tool", content: contentStr, tool_call_id: tr.id, name: tr.name });
463
- }
464
- if (opts.signal?.aborted) throw new Error("Aborted");
465
- streamedAssistantContent = "";
466
- if (opts.stream && typeof this.deps.llm.streamCompletion === "function") {
467
- let isFirstChunk = true;
468
- result = await this.deps.llm.streamCompletion(
469
- { ...params, messages: accumulatedMessages },
470
- {
471
- onChunk: async (delta, usage) => {
472
- try {
473
- streamedAssistantContent += delta;
474
- } catch {
475
- }
476
- const cleanDelta = isFirstChunk ? delta.replace(/^\n+/, "") : delta;
477
- isFirstChunk = false;
478
- const chunkEvent = {
479
- type: AgentEventTypes.AssistantChunk,
480
- conversationId: convo,
481
- messageId: msgId,
482
- delta: cleanDelta,
483
- ...usage && { usage }
484
- };
485
- await this.deps.events?.emit(chunkEvent);
486
- },
487
- onStreamFinish: async (finishReason, usage) => {
488
- const finishEvent = {
489
- type: AgentEventTypes.StreamFinish,
490
- conversationId: convo,
491
- messageId: msgId,
492
- ...finishReason && { finishReason },
493
- ...usage && { usage }
494
- };
495
- await this.deps.events?.emit(finishEvent);
496
- }
497
- },
498
- opts.signal
499
- );
500
- } else {
501
- result = await this.deps.llm.generateCompletion({ ...params, messages: accumulatedMessages }, opts.signal);
502
- }
503
- if (!result.tool_calls?.length && result.content && !finalResponseSaved) {
504
- const content2 = opts.stream ? streamedAssistantContent : result.content;
505
- const assistantMsg2 = {
506
- id: msgId,
507
- role: "assistant",
508
- content: content2,
509
- timestamp: this.deps.clock.iso(),
510
- usage: result.usage
511
- };
512
- await this.deps.memory.append(convo, [assistantMsg2]);
513
- finalResponseSaved = true;
514
- if (content2.trim()) {
515
- const messageEvent = {
516
- type: AgentEventTypes.AssistantMessage,
517
- conversationId: convo,
518
- messageId: msgId,
519
- content: content2,
520
- ...result.usage && { usage: result.usage }
521
- };
522
- await this.deps.events?.emit(messageEvent);
523
- }
907
+ await this.events?.emit(messageEvent);
524
908
  }
525
909
  }
526
- const t1 = this.deps.clock.now();
527
- const timestamp = this.deps.clock.iso();
528
- const shouldEmitFinalMessage = result.content?.trim() && !toolApprovalDenied && !finalResponseSaved;
529
- if (shouldEmitFinalMessage) {
530
- const messageEvent = {
531
- type: AgentEventTypes.AssistantMessage,
532
- conversationId: convo,
533
- messageId: msgId,
534
- content: result.content,
535
- ...result.usage && { usage: result.usage }
536
- };
537
- await this.deps.events?.emit(messageEvent);
538
- }
539
- const responseContent = toolApprovalDenied ? denialMessage : result.content;
540
- const resp = {
541
- id: msgId,
542
- content: responseContent,
543
- role: MessageRoles.Assistant,
544
- timestamp,
545
- metadata: {
546
- model: this.cfg.model,
547
- provider: "echo",
548
- agentId: this.cfg.id,
549
- responseTime: t1 - t0,
550
- promptTokens: result.usage?.prompt_tokens,
551
- completionTokens: result.usage?.completion_tokens,
552
- totalTokens: result.usage?.total_tokens,
553
- estimatedCost: this.deps.cost.estimate(this.cfg.model, result.usage),
554
- toolCalls: allToolResults.length
555
- }
556
- };
557
- await this.deps.events?.emit({
558
- 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,
559
918
  conversationId: convo,
560
919
  messageId: msgId,
561
- responseTimeMs: t1 - t0,
562
- usage: result.usage
563
- });
564
- return resp;
565
- } catch (err2) {
566
- throw err2;
567
- }
568
- }
569
- async waitForToolApproval(toolCalls, conversationId, messageId) {
570
- const approvalId = this.deps.ids.uuid();
571
- return new Promise((resolve6, reject) => {
572
- this.pendingApprovals.set(approvalId, { resolve: resolve6, reject });
573
- this.deps.events?.emit({
574
- type: AgentEventTypes.ToolApprovalRequired,
575
- conversationId,
576
- messageId,
577
- toolCalls,
578
- approvalId
579
- });
580
- });
581
- }
582
- handleToolApproval(approvalId, decision, approvedCalls) {
583
- const approval = this.pendingApprovals.get(approvalId);
584
- if (!approval) {
585
- console.warn(`[Orchestrator] Received approval for unknown or already processed ID: ${approvalId}`);
586
- return;
587
- }
588
- this.pendingApprovals.delete(approvalId);
589
- if (decision === "deny") {
590
- approval.reject(new Error("Tool execution denied by user"));
591
- } else if (decision === "approve_all" || decision === "approve") {
592
- approval.resolve(approvedCalls || []);
593
- } else {
594
- approval.reject(new Error(`Invalid approval decision: ${decision}`));
595
- }
596
- }
597
- toInvocations(toolCalls) {
598
- return toolCalls.map((tc) => {
599
- let parameters = {};
600
- try {
601
- parameters = JSON.parse(tc.function.arguments || "{}");
602
- } catch {
603
- parameters = {};
604
- }
605
- return { id: tc.id, name: tc.function.name, parameters };
606
- });
607
- }
608
- };
609
-
610
- // context.ts
611
- var toProviderContent = (content) => {
612
- if (content === null || content === void 0) {
613
- return "";
614
- }
615
- if (typeof content === "string") {
616
- return content;
617
- }
618
- if (content.type === "parts") {
619
- const providerParts = [];
620
- for (const part of content.parts) {
621
- if (part.type === "text") {
622
- if (part.text.length > 0) {
623
- providerParts.push({ type: "text", text: part.text });
624
- }
625
- continue;
626
- }
627
- const label = part.altText ?? (part.name ? `Image attachment: ${part.name}` : void 0);
628
- if (label) {
629
- providerParts.push({ type: "text", text: label });
630
- }
631
- const url = `data:${part.mimeType};base64,${part.data}`;
632
- providerParts.push({ type: "image_url", image_url: { url } });
633
- }
634
- return providerParts.length > 0 ? providerParts : [];
635
- }
636
- return [];
637
- };
638
- var SimpleContextBuilder = class {
639
- toProviderMessages(history, systemPrompt, newUserContent) {
640
- const transformed = [];
641
- for (const m of history) {
642
- const providerContent = toProviderContent(m.content);
643
- if (m.role === "user") {
644
- transformed.push({ role: "user", content: providerContent ?? "" });
645
- } else if (m.role === "assistant") {
646
- if (m.tool_calls && m.tool_calls.length > 0) {
647
- transformed.push({
648
- ...m,
649
- role: "assistant",
650
- content: providerContent ?? null,
651
- tool_calls: m.tool_calls
652
- });
653
- } else {
654
- transformed.push({
655
- ...m,
656
- role: "assistant",
657
- content: providerContent ?? ""
658
- });
659
- }
660
- } else if (m.role === "tool") {
661
- if (m.tool_call_id) {
662
- transformed.push({
663
- role: "tool",
664
- content: typeof providerContent === "string" ? providerContent : providerContent ?? "",
665
- tool_call_id: m.tool_call_id,
666
- name: m.name
667
- });
668
- }
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
669
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;
951
+ }
952
+ async waitForToolApproval(toolCalls, conversationId, messageId) {
953
+ const approvalId = this.ids.uuid();
954
+ return new Promise((resolve6, reject) => {
955
+ this.pendingApprovals.set(approvalId, { resolve: resolve6, reject });
956
+ this.events?.emit({
957
+ type: AgentEventTypes.ToolApprovalRequired,
958
+ conversationId,
959
+ messageId,
960
+ toolCalls,
961
+ approvalId
962
+ });
963
+ });
964
+ }
965
+ handleToolApproval(approvalId, decision, approvedCalls) {
966
+ const approval = this.pendingApprovals.get(approvalId);
967
+ if (!approval) {
968
+ console.warn(`[Orchestrator] Received approval for unknown or already processed ID: ${approvalId}`);
969
+ return;
670
970
  }
671
- const userMsgs = newUserContent.map((c) => ({
672
- role: "user",
673
- content: toProviderContent(c) ?? ""
674
- }));
675
- return [{ role: "system", content: systemPrompt }, ...transformed, ...userMsgs];
971
+ this.pendingApprovals.delete(approvalId);
972
+ if (decision === "deny") {
973
+ approval.reject(new Error("Tool execution denied by user"));
974
+ } else if (decision === "approve_all" || decision === "approve") {
975
+ approval.resolve(approvedCalls || []);
976
+ } else {
977
+ approval.reject(new Error(`Invalid approval decision: ${decision}`));
978
+ }
979
+ }
980
+ toInvocations(toolCalls) {
981
+ return toolCalls.map((tc) => {
982
+ let parameters = {};
983
+ try {
984
+ parameters = JSON.parse(tc.function.arguments || "{}");
985
+ } catch {
986
+ parameters = {};
987
+ }
988
+ return { id: tc.id, name: tc.function.name, parameters };
989
+ });
676
990
  }
677
991
  };
678
992
 
@@ -937,7 +1251,12 @@ async function buildTree(dir, rootDir, gitignore, options, depth, fileCount) {
937
1251
  if (depth >= options.maxDepth || fileCount.count >= options.maxFiles) {
938
1252
  return [];
939
1253
  }
940
- 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
+ }
941
1260
  const lines = [];
942
1261
  const sortedEntries = entries.sort((a, b) => {
943
1262
  if (a.isDirectory() && !b.isDirectory()) return -1;
@@ -981,123 +1300,6 @@ async function generateFolderTree(rootDir, options = {}) {
981
1300
  return [header, ...lines].join("\n");
982
1301
  }
983
1302
 
984
- // persistent/memory.ts
985
- var InMemoryMemory = class {
986
- store = /* @__PURE__ */ new Map();
987
- async get(key) {
988
- return this.store.get(key) ?? [];
989
- }
990
- async set(key, items) {
991
- this.store.set(key, [...items]);
992
- }
993
- async append(key, items) {
994
- const existing = this.store.get(key) ?? [];
995
- this.store.set(key, [...existing, ...items]);
996
- }
997
- async delete(key) {
998
- this.store.delete(key);
999
- }
1000
- async keys() {
1001
- return Array.from(this.store.keys());
1002
- }
1003
- async clear() {
1004
- this.store.clear();
1005
- }
1006
- async exportSnapshot() {
1007
- const snap = {};
1008
- for (const [k, v] of this.store.entries()) snap[k] = [...v];
1009
- return snap;
1010
- }
1011
- async importSnapshot(snapshot) {
1012
- this.store.clear();
1013
- for (const [k, v] of Object.entries(snapshot)) this.store.set(k, [...v]);
1014
- }
1015
- };
1016
- var JsonFileMemoryPersistence = class {
1017
- constructor(filename = "history.json") {
1018
- this.filename = filename;
1019
- }
1020
- async load() {
1021
- try {
1022
- const fs10 = await import("fs");
1023
- if (!fs10.existsSync(this.filename)) return {};
1024
- const text = fs10.readFileSync(this.filename, "utf-8");
1025
- const data = JSON.parse(text);
1026
- return typeof data === "object" && data ? data : {};
1027
- } catch {
1028
- console.warn(`Failed to load memory from ${this.filename}`);
1029
- return {};
1030
- }
1031
- }
1032
- async save(snapshot) {
1033
- try {
1034
- const fs10 = await import("fs");
1035
- const path9 = await import("path");
1036
- const dir = path9.dirname(this.filename);
1037
- if (dir && dir !== "." && !fs10.existsSync(dir)) {
1038
- fs10.mkdirSync(dir, { recursive: true });
1039
- }
1040
- fs10.writeFileSync(this.filename, JSON.stringify(snapshot, null, 2), "utf-8");
1041
- } catch (err2) {
1042
- console.warn(`Failed to save memory to ${this.filename}`, err2);
1043
- }
1044
- }
1045
- };
1046
- var PersistedMemory = class {
1047
- constructor(persistence) {
1048
- this.persistence = persistence;
1049
- }
1050
- inner = new InMemoryMemory();
1051
- initialized = false;
1052
- async ensureInitialized() {
1053
- if (this.initialized) return;
1054
- const snap = await this.persistence.load();
1055
- if (snap && typeof snap === "object") await this.inner.importSnapshot(snap);
1056
- this.initialized = true;
1057
- }
1058
- async save() {
1059
- const snap = await this.inner.exportSnapshot();
1060
- await this.persistence.save(snap);
1061
- }
1062
- async get(key) {
1063
- await this.ensureInitialized();
1064
- return this.inner.get(key);
1065
- }
1066
- async set(key, items) {
1067
- await this.ensureInitialized();
1068
- await this.inner.set(key, items);
1069
- await this.save();
1070
- }
1071
- async append(key, items) {
1072
- await this.ensureInitialized();
1073
- await this.inner.append(key, items);
1074
- await this.save();
1075
- }
1076
- async delete(key) {
1077
- await this.ensureInitialized();
1078
- await this.inner.delete(key);
1079
- await this.save();
1080
- }
1081
- async keys() {
1082
- await this.ensureInitialized();
1083
- return this.inner.keys();
1084
- }
1085
- async clear() {
1086
- await this.ensureInitialized();
1087
- await this.inner.clear();
1088
- await this.save();
1089
- }
1090
- async exportSnapshot() {
1091
- await this.ensureInitialized();
1092
- return this.inner.exportSnapshot();
1093
- }
1094
- async importSnapshot(snapshot) {
1095
- await this.ensureInitialized();
1096
- await this.inner.importSnapshot(snapshot);
1097
- await this.save();
1098
- }
1099
- };
1100
-
1101
1303
  // persistent/metadata-memory.ts
1102
1304
  var MemoryPortMetadataAdapter = class {
1103
1305
  constructor(memory, prefix = "__metadata__") {
@@ -1232,6 +1434,27 @@ var ConversationStore = class {
1232
1434
  };
1233
1435
  await this.metadataMemory.set(conversationId, updatedMetadata);
1234
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
+ }
1235
1458
  async updateTopic(conversationId, topic) {
1236
1459
  const metadata = await this.metadataMemory.get(conversationId);
1237
1460
  const updatedMetadata = {
@@ -1294,41 +1517,6 @@ var ConversationContext = class {
1294
1517
  }
1295
1518
  };
1296
1519
 
1297
- // id.ts
1298
- var SimpleId = class {
1299
- uuid() {
1300
- return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
1301
- const r = Math.random() * 16 | 0;
1302
- const v = c === "x" ? r : r & 3 | 8;
1303
- return v.toString(16);
1304
- });
1305
- }
1306
- };
1307
-
1308
- // clock.ts
1309
- var SystemClock = class {
1310
- now() {
1311
- return Date.now();
1312
- }
1313
- iso(dateMs) {
1314
- return new Date(dateMs ?? Date.now()).toISOString();
1315
- }
1316
- };
1317
-
1318
- // cost.ts
1319
- var SimpleCost = class {
1320
- estimate(_model, usage) {
1321
- return usage?.cost;
1322
- }
1323
- };
1324
-
1325
- // reminders.ts
1326
- var NoopReminders = class {
1327
- enhance(content, _opts) {
1328
- return [content];
1329
- }
1330
- };
1331
-
1332
1520
  // todo-store.ts
1333
1521
  var TodoStore = class {
1334
1522
  constructor(memory) {
@@ -3627,9 +3815,12 @@ var AgentFilePersistence = class {
3627
3815
  * Load all agents from the agents directory
3628
3816
  */
3629
3817
  async loadAll() {
3630
- this.ensureAgentsDir();
3631
3818
  const agents = [];
3632
3819
  try {
3820
+ this.ensureAgentsDir();
3821
+ if (!fs8.existsSync(this.agentsDir)) {
3822
+ return agents;
3823
+ }
3633
3824
  const files = fs8.readdirSync(this.agentsDir);
3634
3825
  const yamlFiles = files.filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"));
3635
3826
  for (const file of yamlFiles) {
@@ -3642,8 +3833,7 @@ var AgentFilePersistence = class {
3642
3833
  console.warn(`Failed to load agent from ${file}:`, error);
3643
3834
  }
3644
3835
  }
3645
- } catch (error) {
3646
- console.warn("Failed to read agents directory:", error);
3836
+ } catch (_error) {
3647
3837
  }
3648
3838
  return agents;
3649
3839
  }
@@ -3774,6 +3964,7 @@ function normalizeUsage(usage) {
3774
3964
  const prompt_tokens = usage.prompt_tokens ?? (typeof usageObj.input_tokens === "number" ? usageObj.input_tokens : void 0);
3775
3965
  const completion_tokens = usage.completion_tokens ?? (typeof usageObj.output_tokens === "number" ? usageObj.output_tokens : void 0);
3776
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);
3777
3968
  return {
3778
3969
  prompt_tokens,
3779
3970
  completion_tokens,
@@ -3781,7 +3972,7 @@ function normalizeUsage(usage) {
3781
3972
  ...usage.reasoning_tokens !== void 0 && { reasoning_tokens: usage.reasoning_tokens },
3782
3973
  ...usage.prompt_tokens_details && { prompt_tokens_details: usage.prompt_tokens_details },
3783
3974
  ...usage.completion_tokens_details && { completion_tokens_details: usage.completion_tokens_details },
3784
- ...usage.cost !== void 0 && { cost: usage.cost },
3975
+ ...cost !== void 0 && { cost },
3785
3976
  ...usage.cost_details && { cost_details: usage.cost_details }
3786
3977
  };
3787
3978
  }
@@ -4574,6 +4765,105 @@ function createTransport(inner, defaultBaseUrl, apiKey, baseUrl, version) {
4574
4765
  return new SimpleBearerAuthTransport(inner, defaultBaseUrl, apiKey, baseUrl, version);
4575
4766
  }
4576
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
+
4577
4867
  // llm-providers/llm-github.ts
4578
4868
  var GithubLLM = class extends BaseLLM {
4579
4869
  opts;
@@ -4604,7 +4894,7 @@ var GithubLLM = class extends BaseLLM {
4604
4894
  throw new LLMError(text || `Failed to fetch models: ${res.status}`, res.status);
4605
4895
  }
4606
4896
  const body = await res.json();
4607
- return body.data;
4897
+ return body.data.map((m) => normalizeModelInfo("github", m));
4608
4898
  }
4609
4899
  handleError(error, model) {
4610
4900
  if (error instanceof LLMError) {
@@ -5137,11 +5427,13 @@ var GenericLLM = class extends BaseLLM {
5137
5427
  opts;
5138
5428
  includeUsage;
5139
5429
  modelConfig;
5430
+ providerName;
5140
5431
  constructor(baseUrl, modelConfig, opts = {}) {
5141
- const { enablePromptCaching = false, includeUsage = false, ...restOpts } = opts;
5432
+ const { enablePromptCaching = false, includeUsage = false, providerName = "unknown", ...restOpts } = opts;
5142
5433
  super(opts.apiUrl || baseUrl, { enablePromptCaching });
5143
5434
  this.includeUsage = includeUsage;
5144
5435
  this.modelConfig = modelConfig;
5436
+ this.providerName = providerName;
5145
5437
  this.opts = restOpts;
5146
5438
  }
5147
5439
  createTransport() {
@@ -5159,7 +5451,10 @@ var GenericLLM = class extends BaseLLM {
5159
5451
  throw new Error("Provider does not support getModels");
5160
5452
  }
5161
5453
  if (Array.isArray(this.modelConfig)) {
5162
- 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
+ });
5163
5458
  }
5164
5459
  if (typeof this.modelConfig === "string") {
5165
5460
  const transport2 = this.createTransport();
@@ -5169,7 +5464,7 @@ var GenericLLM = class extends BaseLLM {
5169
5464
  throw new Error(`Failed to fetch models: ${res2.status} ${text}`);
5170
5465
  }
5171
5466
  const data2 = await res2.json();
5172
- return data2.data;
5467
+ return data2.data.map((m) => normalizeModelInfo(this.providerName, m));
5173
5468
  }
5174
5469
  const transport = this.createTransport();
5175
5470
  const res = await transport.get("/models", void 0, signal);
@@ -5178,7 +5473,7 @@ var GenericLLM = class extends BaseLLM {
5178
5473
  throw new Error(`Failed to fetch models: ${res.status} ${text}`);
5179
5474
  }
5180
5475
  const data = await res.json();
5181
- return data.data;
5476
+ return data.data.map((m) => normalizeModelInfo(this.providerName, m));
5182
5477
  }
5183
5478
  async generateCompletion(params, signal) {
5184
5479
  let enhancedParams = params;
@@ -5237,6 +5532,7 @@ function createLLM(providerName, options = {}, customProviders) {
5237
5532
  const modelConfig = normalizeModelConfig(config);
5238
5533
  return new GenericLLM(config.baseUrl, modelConfig, {
5239
5534
  ...options,
5535
+ providerName: config.name,
5240
5536
  enablePromptCaching: options.enablePromptCaching ?? config.features.promptCaching,
5241
5537
  includeUsage: options.includeUsage ?? config.features.includeUsage
5242
5538
  });
@@ -5522,31 +5818,6 @@ function isValidConfig(value) {
5522
5818
  }
5523
5819
  return true;
5524
5820
  }
5525
-
5526
- // events.ts
5527
- var PersistingConsoleEventPort = class {
5528
- memory;
5529
- maxPerConversation;
5530
- writeQueue = Promise.resolve();
5531
- constructor(opts) {
5532
- this.memory = opts?.memory ?? new PersistedMemory(new JsonFileMemoryPersistence(opts?.filename || "events.json"));
5533
- this.maxPerConversation = opts?.maxPerConversation ?? 500;
5534
- }
5535
- async emit(event) {
5536
- this.writeQueue = this.writeQueue.then(async () => {
5537
- try {
5538
- const key = event?.conversationId ?? "default";
5539
- const existing = await this.memory.get(key);
5540
- const next = [...existing, { ...event }];
5541
- const max = this.maxPerConversation;
5542
- const trimmed = max > 0 && next.length > max ? next.slice(next.length - max) : next;
5543
- await this.memory.set(key, trimmed);
5544
- } catch {
5545
- }
5546
- });
5547
- return this.writeQueue;
5548
- }
5549
- };
5550
5821
  export {
5551
5822
  AGENT_CREATOR_SYSTEM_PROMPT,
5552
5823
  AgentEventTypes,
@@ -5570,11 +5841,13 @@ export {
5570
5841
  GithubLLM,
5571
5842
  InMemoryMemory,
5572
5843
  InMemoryMetadata,
5844
+ InMemoryMetricsPort,
5573
5845
  JsonFileMemoryPersistence,
5574
5846
  LLMError,
5575
5847
  LLMResolver,
5576
5848
  MCPToolPort,
5577
5849
  MemoryPortMetadataAdapter,
5850
+ NoopMetricsPort,
5578
5851
  NoopReminders,
5579
5852
  PersistedMemory,
5580
5853
  PersistingConsoleEventPort,
@@ -5587,10 +5860,14 @@ export {
5587
5860
  buildAgentCreationPrompt,
5588
5861
  buildInjectedSystem,
5589
5862
  canonicalizeTerminalPaste,
5863
+ createEmptySnapshot,
5590
5864
  createLLM,
5591
5865
  generateFolderTree,
5592
5866
  getAvailableProviders,
5867
+ getFallbackLimits,
5593
5868
  loadMCPConfig,
5869
+ normalizeModelInfo,
5870
+ normalizeModelLimits,
5594
5871
  normalizeNewlines,
5595
5872
  renderTemplate,
5596
5873
  resolveBackspaces,