@nuvin/nuvin-core 1.2.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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,205 +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);
907
+ await this.events?.emit(messageEvent);
376
908
  }
377
909
  }
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
- }
524
- }
525
- }
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
- }
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;
568
951
  }
569
952
  async waitForToolApproval(toolCalls, conversationId, messageId) {
570
- const approvalId = this.deps.ids.uuid();
953
+ const approvalId = this.ids.uuid();
571
954
  return new Promise((resolve6, reject) => {
572
955
  this.pendingApprovals.set(approvalId, { resolve: resolve6, reject });
573
- this.deps.events?.emit({
956
+ this.events?.emit({
574
957
  type: AgentEventTypes.ToolApprovalRequired,
575
958
  conversationId,
576
959
  messageId,
@@ -579,100 +962,31 @@ var AgentOrchestrator = class {
579
962
  });
580
963
  });
581
964
  }
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
- }
669
- }
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
  }
@@ -4520,11 +4711,13 @@ var BaseBearerAuthTransport = class {
4520
4711
  apiKey;
4521
4712
  baseUrl;
4522
4713
  version;
4523
- constructor(inner, apiKey, baseUrl, version) {
4714
+ customHeaders;
4715
+ constructor(inner, apiKey, baseUrl, version, customHeaders) {
4524
4716
  this.inner = inner;
4525
4717
  this.apiKey = apiKey;
4526
4718
  this.baseUrl = baseUrl ?? this.getDefaultBaseUrl();
4527
4719
  this.version = version;
4720
+ this.customHeaders = customHeaders;
4528
4721
  }
4529
4722
  buildFullUrl(path9) {
4530
4723
  if (path9.startsWith("/")) {
@@ -4533,14 +4726,16 @@ var BaseBearerAuthTransport = class {
4533
4726
  return path9;
4534
4727
  }
4535
4728
  makeAuthHeaders(headers) {
4536
- if (!this.apiKey || this.apiKey.trim() === "") {
4537
- throw new Error("API key missing");
4538
- }
4539
4729
  const base = headers ? { ...headers } : {};
4540
- base.Authorization = `Bearer ${this.apiKey}`;
4541
4730
  if (!base["User-Agent"] && this.version) {
4542
4731
  base["User-Agent"] = `nuvin-cli/${this.version}`;
4543
4732
  }
4733
+ if (this.apiKey && this.apiKey.trim() !== "" && !this.customHeaders?.Authorization) {
4734
+ base.Authorization = `Bearer ${this.apiKey}`;
4735
+ }
4736
+ if (this.customHeaders) {
4737
+ Object.assign(base, this.customHeaders);
4738
+ }
4544
4739
  return base;
4545
4740
  }
4546
4741
  async get(url, headers, signal) {
@@ -4560,8 +4755,8 @@ var BaseBearerAuthTransport = class {
4560
4755
  // transports/simple-bearer-transport.ts
4561
4756
  var SimpleBearerAuthTransport = class extends BaseBearerAuthTransport {
4562
4757
  defaultUrl;
4563
- constructor(inner, defaultBaseUrl, apiKey, baseUrl, version) {
4564
- super(inner, apiKey, baseUrl ?? defaultBaseUrl, version);
4758
+ constructor(inner, defaultBaseUrl, apiKey, baseUrl, version, customHeaders) {
4759
+ super(inner, apiKey, baseUrl ?? defaultBaseUrl, version, customHeaders);
4565
4760
  this.defaultUrl = defaultBaseUrl;
4566
4761
  }
4567
4762
  getDefaultBaseUrl() {
@@ -4570,8 +4765,107 @@ var SimpleBearerAuthTransport = class extends BaseBearerAuthTransport {
4570
4765
  };
4571
4766
 
4572
4767
  // transports/transport-factory.ts
4573
- function createTransport(inner, defaultBaseUrl, apiKey, baseUrl, version) {
4574
- return new SimpleBearerAuthTransport(inner, defaultBaseUrl, apiKey, baseUrl, version);
4768
+ function createTransport(inner, defaultBaseUrl, apiKey, baseUrl, version, customHeaders) {
4769
+ return new SimpleBearerAuthTransport(inner, defaultBaseUrl, apiKey, baseUrl, version, customHeaders);
4770
+ }
4771
+
4772
+ // llm-providers/model-limits.ts
4773
+ function normalizeModelLimits(provider, model) {
4774
+ switch (provider.toLowerCase()) {
4775
+ case "github": {
4776
+ const capabilities = model.capabilities;
4777
+ const limits = capabilities?.limits;
4778
+ const contextWindow = limits?.max_context_window_tokens;
4779
+ if (!contextWindow) return null;
4780
+ return {
4781
+ contextWindow,
4782
+ maxOutput: limits?.max_output_tokens
4783
+ };
4784
+ }
4785
+ case "deepinfra": {
4786
+ const metadata = model.metadata;
4787
+ const contextLength = metadata?.context_length;
4788
+ if (!contextLength) return null;
4789
+ return {
4790
+ contextWindow: contextLength,
4791
+ maxOutput: metadata?.max_tokens
4792
+ };
4793
+ }
4794
+ case "moonshot": {
4795
+ const contextLength = model.context_length;
4796
+ if (!contextLength) return null;
4797
+ return { contextWindow: contextLength };
4798
+ }
4799
+ case "openrouter": {
4800
+ const topProvider = model.top_provider;
4801
+ const contextLength = model.context_length ?? topProvider?.context_length;
4802
+ if (!contextLength) return null;
4803
+ return {
4804
+ contextWindow: contextLength,
4805
+ maxOutput: topProvider?.max_completion_tokens
4806
+ };
4807
+ }
4808
+ default: {
4809
+ const contextLength = model.context_length;
4810
+ if (!contextLength) return null;
4811
+ return { contextWindow: contextLength };
4812
+ }
4813
+ }
4814
+ }
4815
+ var FALLBACK_LIMITS = {
4816
+ zai: {
4817
+ "glm-4.6": { contextWindow: 2e5, maxOutput: 128e3 }
4818
+ },
4819
+ openrouter: {
4820
+ "anthropic/claude-sonnet-4": { contextWindow: 2e5, maxOutput: 16e3 },
4821
+ "anthropic/claude-3.5-sonnet": { contextWindow: 2e5, maxOutput: 8192 },
4822
+ "openai/gpt-4o": { contextWindow: 128e3, maxOutput: 16384 },
4823
+ "openai/gpt-4.1": { contextWindow: 128e3, maxOutput: 32768 },
4824
+ "openai/gpt-4o-mini": { contextWindow: 128e3, maxOutput: 16384 },
4825
+ "google/gemini-pro-1.5": { contextWindow: 2097152, maxOutput: 8192 },
4826
+ "meta-llama/llama-3.1-70b-instruct": { contextWindow: 131072, maxOutput: 131072 }
4827
+ },
4828
+ github: {
4829
+ "gpt-4.1": { contextWindow: 128e3, maxOutput: 16384 },
4830
+ "gpt-4o": { contextWindow: 128e3, maxOutput: 16384 },
4831
+ "gpt-4o-mini": { contextWindow: 128e3, maxOutput: 16384 },
4832
+ "claude-sonnet-4": { contextWindow: 2e5, maxOutput: 16e3 },
4833
+ "claude-3.5-sonnet": { contextWindow: 2e5, maxOutput: 8192 },
4834
+ o1: { contextWindow: 2e5, maxOutput: 1e5 },
4835
+ "o1-mini": { contextWindow: 128e3, maxOutput: 65536 }
4836
+ },
4837
+ deepinfra: {
4838
+ "meta-llama/Meta-Llama-3.1-70B-Instruct": { contextWindow: 131072, maxOutput: 131072 },
4839
+ "meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo": { contextWindow: 131072, maxOutput: 131072 },
4840
+ "Qwen/Qwen2.5-72B-Instruct": { contextWindow: 131072, maxOutput: 131072 }
4841
+ },
4842
+ moonshot: {
4843
+ "moonshot-v1-8k": { contextWindow: 8192 },
4844
+ "moonshot-v1-32k": { contextWindow: 32768 },
4845
+ "moonshot-v1-128k": { contextWindow: 131072 },
4846
+ "kimi-k2-turbo-preview": { contextWindow: 262144 }
4847
+ },
4848
+ anthropic: {
4849
+ "claude-sonnet-4-5": { contextWindow: 2e5, maxOutput: 16e3 },
4850
+ "claude-3-5-sonnet-20241022": { contextWindow: 2e5, maxOutput: 8192 },
4851
+ "claude-3-opus-20240229": { contextWindow: 2e5, maxOutput: 4096 },
4852
+ "claude-3-haiku-20240307": { contextWindow: 2e5, maxOutput: 4096 }
4853
+ }
4854
+ };
4855
+ function getFallbackLimits(provider, model) {
4856
+ const providerLimits = FALLBACK_LIMITS[provider.toLowerCase()];
4857
+ if (!providerLimits) return null;
4858
+ return providerLimits[model] ?? null;
4859
+ }
4860
+ function normalizeModelInfo(provider, model) {
4861
+ const id = model.id;
4862
+ const name = model.name ?? id;
4863
+ const limits = normalizeModelLimits(provider, model);
4864
+ return {
4865
+ id,
4866
+ name,
4867
+ ...limits ? { limits } : {}
4868
+ };
4575
4869
  }
4576
4870
 
4577
4871
  // llm-providers/llm-github.ts
@@ -4604,7 +4898,7 @@ var GithubLLM = class extends BaseLLM {
4604
4898
  throw new LLMError(text || `Failed to fetch models: ${res.status}`, res.status);
4605
4899
  }
4606
4900
  const body = await res.json();
4607
- return body.data;
4901
+ return body.data.map((m) => normalizeModelInfo("github", m));
4608
4902
  }
4609
4903
  handleError(error, model) {
4610
4904
  if (error instanceof LLMError) {
@@ -5127,6 +5421,19 @@ var llm_provider_config_default = {
5127
5421
  promptCaching: false,
5128
5422
  getModels: true
5129
5423
  }
5424
+ },
5425
+ {
5426
+ name: "kimi-for-coding",
5427
+ type: "openai-compat",
5428
+ baseUrl: "https://api.kimi.com/coding/v1",
5429
+ features: {
5430
+ promptCaching: true,
5431
+ getModels: true,
5432
+ includeUsage: true
5433
+ },
5434
+ customHeaders: {
5435
+ "User-Agent": "KimiCLI/0.59"
5436
+ }
5130
5437
  }
5131
5438
  ]
5132
5439
  };
@@ -5137,12 +5444,16 @@ var GenericLLM = class extends BaseLLM {
5137
5444
  opts;
5138
5445
  includeUsage;
5139
5446
  modelConfig;
5140
- constructor(baseUrl, modelConfig, opts = {}) {
5141
- const { enablePromptCaching = false, includeUsage = false, ...restOpts } = opts;
5447
+ providerName;
5448
+ customHeaders;
5449
+ constructor(baseUrl, modelConfig, opts = {}, customHeaders) {
5450
+ const { enablePromptCaching = false, includeUsage = false, providerName = "unknown", ...restOpts } = opts;
5142
5451
  super(opts.apiUrl || baseUrl, { enablePromptCaching });
5143
5452
  this.includeUsage = includeUsage;
5144
5453
  this.modelConfig = modelConfig;
5454
+ this.providerName = providerName;
5145
5455
  this.opts = restOpts;
5456
+ this.customHeaders = customHeaders;
5146
5457
  }
5147
5458
  createTransport() {
5148
5459
  const base = new FetchTransport({
@@ -5152,14 +5463,17 @@ var GenericLLM = class extends BaseLLM {
5152
5463
  maxFileSize: 5 * 1024 * 1024,
5153
5464
  captureResponseBody: true
5154
5465
  });
5155
- return createTransport(base, this.apiUrl, this.opts.apiKey, this.opts.apiUrl, this.opts.version);
5466
+ return createTransport(base, this.apiUrl, this.opts.apiKey, this.opts.apiUrl, this.opts.version, this.customHeaders);
5156
5467
  }
5157
5468
  async getModels(signal) {
5158
5469
  if (this.modelConfig === false) {
5159
5470
  throw new Error("Provider does not support getModels");
5160
5471
  }
5161
5472
  if (Array.isArray(this.modelConfig)) {
5162
- return this.modelConfig.map((m) => typeof m === "string" ? { id: m } : m);
5473
+ return this.modelConfig.map((m) => {
5474
+ const raw = typeof m === "string" ? { id: m } : m;
5475
+ return normalizeModelInfo(this.providerName, raw);
5476
+ });
5163
5477
  }
5164
5478
  if (typeof this.modelConfig === "string") {
5165
5479
  const transport2 = this.createTransport();
@@ -5169,7 +5483,7 @@ var GenericLLM = class extends BaseLLM {
5169
5483
  throw new Error(`Failed to fetch models: ${res2.status} ${text}`);
5170
5484
  }
5171
5485
  const data2 = await res2.json();
5172
- return data2.data;
5486
+ return data2.data.map((m) => normalizeModelInfo(this.providerName, m));
5173
5487
  }
5174
5488
  const transport = this.createTransport();
5175
5489
  const res = await transport.get("/models", void 0, signal);
@@ -5178,7 +5492,7 @@ var GenericLLM = class extends BaseLLM {
5178
5492
  throw new Error(`Failed to fetch models: ${res.status} ${text}`);
5179
5493
  }
5180
5494
  const data = await res.json();
5181
- return data.data;
5495
+ return data.data.map((m) => normalizeModelInfo(this.providerName, m));
5182
5496
  }
5183
5497
  async generateCompletion(params, signal) {
5184
5498
  let enhancedParams = params;
@@ -5217,6 +5531,7 @@ function mergeProviders(customProviders) {
5217
5531
  type: custom.type ?? "openai-compat",
5218
5532
  baseUrl: custom.baseUrl,
5219
5533
  models: custom.models ?? false,
5534
+ customHeaders: custom.customHeaders,
5220
5535
  features: existing?.features ?? {
5221
5536
  promptCaching: false,
5222
5537
  getModels: custom.models !== false,
@@ -5237,9 +5552,10 @@ function createLLM(providerName, options = {}, customProviders) {
5237
5552
  const modelConfig = normalizeModelConfig(config);
5238
5553
  return new GenericLLM(config.baseUrl, modelConfig, {
5239
5554
  ...options,
5555
+ providerName: config.name,
5240
5556
  enablePromptCaching: options.enablePromptCaching ?? config.features.promptCaching,
5241
5557
  includeUsage: options.includeUsage ?? config.features.includeUsage
5242
- });
5558
+ }, config.customHeaders);
5243
5559
  }
5244
5560
  function getAvailableProviders(customProviders) {
5245
5561
  const allProviders = mergeProviders(customProviders);
@@ -5522,31 +5838,6 @@ function isValidConfig(value) {
5522
5838
  }
5523
5839
  return true;
5524
5840
  }
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
5841
  export {
5551
5842
  AGENT_CREATOR_SYSTEM_PROMPT,
5552
5843
  AgentEventTypes,
@@ -5570,11 +5861,13 @@ export {
5570
5861
  GithubLLM,
5571
5862
  InMemoryMemory,
5572
5863
  InMemoryMetadata,
5864
+ InMemoryMetricsPort,
5573
5865
  JsonFileMemoryPersistence,
5574
5866
  LLMError,
5575
5867
  LLMResolver,
5576
5868
  MCPToolPort,
5577
5869
  MemoryPortMetadataAdapter,
5870
+ NoopMetricsPort,
5578
5871
  NoopReminders,
5579
5872
  PersistedMemory,
5580
5873
  PersistingConsoleEventPort,
@@ -5587,10 +5880,14 @@ export {
5587
5880
  buildAgentCreationPrompt,
5588
5881
  buildInjectedSystem,
5589
5882
  canonicalizeTerminalPaste,
5883
+ createEmptySnapshot,
5590
5884
  createLLM,
5591
5885
  generateFolderTree,
5592
5886
  getAvailableProviders,
5887
+ getFallbackLimits,
5593
5888
  loadMCPConfig,
5889
+ normalizeModelInfo,
5890
+ normalizeModelLimits,
5594
5891
  normalizeNewlines,
5595
5892
  renderTemplate,
5596
5893
  resolveBackspaces,