@nuvin/nuvin-core 1.0.1 → 1.1.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/VERSION CHANGED
@@ -1,4 +1,4 @@
1
1
  {
2
- "version": "1.0.1",
3
- "commit": "d5c55a1"
2
+ "version": "1.1.0",
3
+ "commit": "6618d91"
4
4
  }
package/dist/index.d.ts CHANGED
@@ -290,6 +290,7 @@ type Message = {
290
290
  tool_calls?: ToolCall[];
291
291
  tool_call_id?: string;
292
292
  name?: string;
293
+ usage?: UsageData;
293
294
  };
294
295
  type MessageResponse = {
295
296
  id: string;
@@ -439,7 +440,6 @@ declare const AgentEventTypes: {
439
440
  readonly AssistantChunk: "assistant_chunk";
440
441
  readonly AssistantMessage: "assistant_message";
441
442
  readonly StreamFinish: "stream_finish";
442
- readonly MemoryAppended: "memory_appended";
443
443
  readonly Done: "done";
444
444
  readonly Error: "error";
445
445
  readonly MCPStderr: "mcp_stderr";
@@ -461,6 +461,7 @@ type AgentEvent = {
461
461
  conversationId: string;
462
462
  messageId: string;
463
463
  toolCalls: ToolCall[];
464
+ usage?: UsageData;
464
465
  } | {
465
466
  type: typeof AgentEventTypes.ToolApprovalRequired;
466
467
  conversationId: string;
@@ -497,10 +498,6 @@ type AgentEvent = {
497
498
  messageId: string;
498
499
  finishReason?: string;
499
500
  usage?: UsageData;
500
- } | {
501
- type: typeof AgentEventTypes.MemoryAppended;
502
- conversationId: string;
503
- delta: Message[];
504
501
  } | {
505
502
  type: typeof AgentEventTypes.Done;
506
503
  conversationId: string;
@@ -1088,12 +1085,6 @@ declare class BashTool implements FunctionTool<BashParams, ToolExecutionContext>
1088
1085
  private shellArgs;
1089
1086
  }
1090
1087
 
1091
- declare class EchoLLM implements LLMPort {
1092
- generateCompletion(params: CompletionParams): Promise<CompletionResult>;
1093
- private hasTool;
1094
- private id;
1095
- }
1096
-
1097
1088
  type LogLevel = 'DEBUG' | 'INFO' | 'WARN' | 'ERROR';
1098
1089
  type LogFormat = 'json' | 'structured';
1099
1090
  type PersistOptions = {
@@ -1226,16 +1217,27 @@ declare class AnthropicAISDKLLM {
1226
1217
  }, signal?: AbortSignal): Promise<CompletionResult>;
1227
1218
  }
1228
1219
 
1220
+ type ModelConfig = false | true | string | string[] | Array<{
1221
+ id: string;
1222
+ name?: string;
1223
+ [key: string]: unknown;
1224
+ }>;
1229
1225
  interface LLMOptions {
1230
1226
  apiKey?: string;
1231
1227
  apiUrl?: string;
1232
1228
  httpLogFile?: string;
1233
1229
  enablePromptCaching?: boolean;
1234
1230
  includeUsage?: boolean;
1231
+ version?: string;
1232
+ }
1233
+ interface CustomProviderDefinition {
1234
+ type?: 'openai-compat' | 'anthropic';
1235
+ baseUrl?: string;
1236
+ models?: ModelConfig;
1235
1237
  }
1236
- declare function createLLM(providerName: string, options?: LLMOptions): LLMPort;
1237
- declare function getAvailableProviders(): string[];
1238
- declare function supportsGetModels(providerName: string): boolean;
1238
+ declare function createLLM(providerName: string, options?: LLMOptions, customProviders?: Record<string, CustomProviderDefinition>): LLMPort;
1239
+ declare function getAvailableProviders(customProviders?: Record<string, CustomProviderDefinition>): string[];
1240
+ declare function supportsGetModels(providerName: string, customProviders?: Record<string, CustomProviderDefinition>): boolean;
1239
1241
 
1240
1242
  type MCPHttpOptions = {
1241
1243
  type: 'http';
@@ -1326,4 +1328,4 @@ declare function resolveBackspaces(s: string): string;
1326
1328
  declare function stripAnsiAndControls(s: string): string;
1327
1329
  declare function canonicalizeTerminalPaste(raw: string): string;
1328
1330
 
1329
- export { AGENT_CREATOR_SYSTEM_PROMPT, type AgentAwareToolPort, type AgentCatalog, type AgentConfig, type AgentEvent, AgentEventTypes, AgentFilePersistence, AgentManager, AgentManagerCommandRunner, AgentOrchestrator, AgentRegistry, type AgentTemplate, AnthropicAISDKLLM, type AssignParams, BashTool, CompositeToolPort, type Conversation, ConversationContext, type ConversationMetadata, type ConversationSnapshot, ConversationStore, CoreMCPClient, DefaultDelegationPolicy, DefaultDelegationResultFormatter, DefaultDelegationService, DefaultSpecialistAgentFactory, type DelegationService, type DelegationServiceConfig, DelegationServiceFactory, EchoLLM, type FolderTreeOptions, GithubLLM, InMemoryMemory, InMemoryMetadata, JsonFileMemoryPersistence, type LLMConfig, LLMError, type LLMFactory, type LLMOptions, type LLMPort, LLMResolver, type MCPConfig, type MCPServerConfig, MCPToolPort, type MemoryPort, MemoryPortMetadataAdapter, type Message, type MessageContent, type MessageContentPart, type MetadataPort, NoopReminders, type OrchestratorAwareToolPort, PersistedMemory, PersistingConsoleEventPort, RuntimeEnv, type SendMessageOptions, SimpleContextBuilder, SimpleCost, SimpleId, type SpecialistAgentConfig, type SpecialistAgentResult, SystemClock, type ToolApprovalDecision, type ToolCall, type ToolExecutionResult, type ToolPort, ToolRegistry, type UserAttachment, type UserMessagePayload, buildAgentCreationPrompt, buildInjectedSystem, canonicalizeTerminalPaste, createLLM, generateFolderTree, getAvailableProviders, loadMCPConfig, normalizeNewlines, renderTemplate, resolveBackspaces, resolveCarriageReturns, stripAnsiAndControls, supportsGetModels };
1331
+ export { AGENT_CREATOR_SYSTEM_PROMPT, type AgentAwareToolPort, type AgentCatalog, type AgentConfig, type AgentEvent, AgentEventTypes, AgentFilePersistence, AgentManager, AgentManagerCommandRunner, AgentOrchestrator, AgentRegistry, type AgentTemplate, AnthropicAISDKLLM, type AssignParams, BashTool, CompositeToolPort, type Conversation, ConversationContext, type ConversationMetadata, type ConversationSnapshot, ConversationStore, CoreMCPClient, DefaultDelegationPolicy, DefaultDelegationResultFormatter, DefaultDelegationService, DefaultSpecialistAgentFactory, type DelegationService, type DelegationServiceConfig, DelegationServiceFactory, type FolderTreeOptions, GithubLLM, InMemoryMemory, InMemoryMetadata, JsonFileMemoryPersistence, type LLMConfig, LLMError, type LLMFactory, type LLMOptions, type LLMPort, LLMResolver, type MCPConfig, type MCPServerConfig, MCPToolPort, type MemoryPort, MemoryPortMetadataAdapter, type Message, type MessageContent, type MessageContentPart, type MetadataPort, NoopReminders, type OrchestratorAwareToolPort, PersistedMemory, PersistingConsoleEventPort, RuntimeEnv, type SendMessageOptions, SimpleContextBuilder, SimpleCost, SimpleId, type SpecialistAgentConfig, type SpecialistAgentResult, SystemClock, type ToolApprovalDecision, type ToolCall, type ToolExecutionResult, type ToolPort, ToolRegistry, type UserAttachment, type UserMessagePayload, buildAgentCreationPrompt, buildInjectedSystem, canonicalizeTerminalPaste, createLLM, generateFolderTree, getAvailableProviders, loadMCPConfig, normalizeNewlines, renderTemplate, resolveBackspaces, resolveCarriageReturns, stripAnsiAndControls, supportsGetModels };
package/dist/index.js CHANGED
@@ -14,7 +14,6 @@ var AgentEventTypes = {
14
14
  AssistantChunk: "assistant_chunk",
15
15
  AssistantMessage: "assistant_message",
16
16
  StreamFinish: "stream_finish",
17
- MemoryAppended: "memory_appended",
18
17
  Done: "done",
19
18
  Error: "error",
20
19
  MCPStderr: "mcp_stderr",
@@ -161,19 +160,22 @@ var AgentOrchestrator = class {
161
160
  const todoTools = ["todo_write", "todo_read"];
162
161
  return readOnlyTools.includes(toolName) || todoTools.includes(toolName);
163
162
  }
164
- async handleToolDenial(denialMessage, conversationId, messageId, accumulatedMessages, turnHistory, originalToolCalls, assistantContent) {
165
- accumulatedMessages.push({
166
- role: "assistant",
167
- content: assistantContent ?? null,
168
- tool_calls: originalToolCalls
169
- });
170
- turnHistory.push({
163
+ async handleToolDenial(denialMessage, conversationId, messageId, accumulatedMessages, turnHistory, originalToolCalls, assistantContent, usage) {
164
+ const assistantMsg = {
171
165
  id: this.deps.ids.uuid(),
172
166
  role: "assistant",
173
167
  content: assistantContent ?? null,
174
168
  timestamp: this.deps.clock.iso(),
169
+ tool_calls: originalToolCalls,
170
+ usage
171
+ };
172
+ accumulatedMessages.push({
173
+ role: "assistant",
174
+ content: assistantContent ?? null,
175
175
  tool_calls: originalToolCalls
176
176
  });
177
+ turnHistory.push(assistantMsg);
178
+ const toolResultMsgs = [];
177
179
  for (const toolCall of originalToolCalls) {
178
180
  const toolDenialResult = "Tool execution denied by user";
179
181
  accumulatedMessages.push({
@@ -182,23 +184,27 @@ var AgentOrchestrator = class {
182
184
  tool_call_id: toolCall.id,
183
185
  name: toolCall.function.name
184
186
  });
185
- turnHistory.push({
187
+ const toolMsg = {
186
188
  id: toolCall.id,
187
189
  role: "tool",
188
190
  content: toolDenialResult,
189
191
  timestamp: this.deps.clock.iso(),
190
192
  tool_call_id: toolCall.id,
191
193
  name: toolCall.function.name
192
- });
194
+ };
195
+ turnHistory.push(toolMsg);
196
+ toolResultMsgs.push(toolMsg);
193
197
  }
198
+ await this.deps.memory.append(conversationId, [assistantMsg, ...toolResultMsgs]);
194
199
  await this.deps.events?.emit({
195
200
  type: AgentEventTypes.AssistantMessage,
196
201
  conversationId,
197
202
  messageId,
198
- content: denialMessage
203
+ content: denialMessage,
204
+ usage: void 0
199
205
  });
200
206
  }
201
- async processToolApproval(toolCalls, conversationId, messageId, accumulatedMessages, turnHistory, assistantContent) {
207
+ async processToolApproval(toolCalls, conversationId, messageId, accumulatedMessages, turnHistory, assistantContent, usage) {
202
208
  if (this.cfg.requireToolApproval === false) {
203
209
  return { approvedCalls: toolCalls, wasDenied: false };
204
210
  }
@@ -220,7 +226,8 @@ var AgentOrchestrator = class {
220
226
  accumulatedMessages,
221
227
  turnHistory,
222
228
  toolCalls,
223
- assistantContent
229
+ assistantContent,
230
+ usage
224
231
  );
225
232
  return { approvedCalls: [], wasDenied: true, denialMessage };
226
233
  }
@@ -263,6 +270,7 @@ var AgentOrchestrator = class {
263
270
  userDisplay = resolveDisplayText(normalized.text, attachments, normalized.displayText);
264
271
  const userTimestamp = this.deps.clock.iso();
265
272
  userMessages = [{ id: this.deps.ids.uuid(), role: "user", content: userContent, timestamp: userTimestamp }];
273
+ await this.deps.memory.append(convo, userMessages);
266
274
  }
267
275
  if (opts.signal?.aborted) throw new Error("Aborted");
268
276
  const toolDefs = this.deps.tools.getToolDefinitions(this.cfg.enabledTools ?? []);
@@ -293,8 +301,10 @@ var AgentOrchestrator = class {
293
301
  let result;
294
302
  let toolApprovalDenied = false;
295
303
  let denialMessage = "";
304
+ let finalResponseSaved = false;
296
305
  try {
297
306
  if (opts.stream && typeof this.deps.llm.streamCompletion === "function") {
307
+ let isFirstChunk = true;
298
308
  result = await this.deps.llm.streamCompletion(
299
309
  params,
300
310
  {
@@ -303,7 +313,8 @@ var AgentOrchestrator = class {
303
313
  streamedAssistantContent += delta;
304
314
  } catch {
305
315
  }
306
- const cleanDelta = delta.replace(/^\n+/, "");
316
+ const cleanDelta = isFirstChunk ? delta.replace(/^\n+/, "") : delta;
317
+ isFirstChunk = false;
307
318
  const chunkEvent = {
308
319
  type: AgentEventTypes.AssistantChunk,
309
320
  conversationId: convo,
@@ -329,6 +340,28 @@ var AgentOrchestrator = class {
329
340
  } else {
330
341
  result = await this.deps.llm.generateCompletion(params, opts.signal);
331
342
  }
343
+ if (!result.tool_calls?.length && result.content && !finalResponseSaved) {
344
+ const content2 = opts.stream ? streamedAssistantContent : result.content;
345
+ const assistantMsg = {
346
+ id: msgId,
347
+ role: "assistant",
348
+ content: content2,
349
+ timestamp: this.deps.clock.iso(),
350
+ usage: result.usage
351
+ };
352
+ await this.deps.memory.append(convo, [assistantMsg]);
353
+ finalResponseSaved = true;
354
+ if (content2.trim()) {
355
+ const messageEvent = {
356
+ type: AgentEventTypes.AssistantMessage,
357
+ conversationId: convo,
358
+ messageId: msgId,
359
+ content: content2,
360
+ ...result.usage && { usage: result.usage }
361
+ };
362
+ await this.deps.events?.emit(messageEvent);
363
+ }
364
+ }
332
365
  while (result.tool_calls?.length) {
333
366
  if (result.content?.trim()) {
334
367
  const messageEvent = {
@@ -345,7 +378,8 @@ var AgentOrchestrator = class {
345
378
  type: AgentEventTypes.ToolCalls,
346
379
  conversationId: convo,
347
380
  messageId: msgId,
348
- toolCalls: result.tool_calls
381
+ toolCalls: result.tool_calls,
382
+ usage: result.usage
349
383
  });
350
384
  const approvalResult = await this.processToolApproval(
351
385
  result.tool_calls,
@@ -353,7 +387,8 @@ var AgentOrchestrator = class {
353
387
  msgId,
354
388
  accumulatedMessages,
355
389
  turnHistory,
356
- result.content
390
+ result.content,
391
+ result.usage
357
392
  );
358
393
  if (approvalResult.wasDenied) {
359
394
  denialMessage = approvalResult.denialMessage || "";
@@ -375,18 +410,18 @@ var AgentOrchestrator = class {
375
410
  opts.signal
376
411
  );
377
412
  allToolResults.push(...toolResults);
378
- accumulatedMessages.push({ role: "assistant", content: result.content ?? null, tool_calls: approvedCalls });
379
- turnHistory.push({
413
+ const assistantMsg = {
380
414
  id: this.deps.ids.uuid(),
381
415
  role: "assistant",
382
416
  content: result.content ?? null,
383
417
  timestamp: this.deps.clock.iso(),
384
- tool_calls: approvedCalls
385
- });
418
+ tool_calls: approvedCalls,
419
+ usage: result.usage
420
+ };
421
+ const toolResultMsgs = [];
386
422
  for (const tr of toolResults) {
387
423
  const contentStr = tr.status === "error" ? String(tr.result) : typeof tr.result === "string" ? tr.result : JSON.stringify(tr.result);
388
- accumulatedMessages.push({ role: "tool", content: contentStr, tool_call_id: tr.id, name: tr.name });
389
- turnHistory.push({
424
+ toolResultMsgs.push({
390
425
  id: tr.id,
391
426
  role: "tool",
392
427
  content: contentStr,
@@ -401,8 +436,16 @@ var AgentOrchestrator = class {
401
436
  result: tr
402
437
  });
403
438
  }
439
+ await this.deps.memory.append(convo, [assistantMsg, ...toolResultMsgs]);
440
+ accumulatedMessages.push({ role: "assistant", content: result.content ?? null, tool_calls: approvedCalls });
441
+ for (const tr of toolResults) {
442
+ const contentStr = tr.status === "error" ? String(tr.result) : typeof tr.result === "string" ? tr.result : JSON.stringify(tr.result);
443
+ accumulatedMessages.push({ role: "tool", content: contentStr, tool_call_id: tr.id, name: tr.name });
444
+ }
404
445
  if (opts.signal?.aborted) throw new Error("Aborted");
446
+ streamedAssistantContent = "";
405
447
  if (opts.stream && typeof this.deps.llm.streamCompletion === "function") {
448
+ let isFirstChunk = true;
406
449
  result = await this.deps.llm.streamCompletion(
407
450
  { ...params, messages: accumulatedMessages },
408
451
  {
@@ -411,7 +454,8 @@ var AgentOrchestrator = class {
411
454
  streamedAssistantContent += delta;
412
455
  } catch {
413
456
  }
414
- const cleanDelta = delta.replace(/^\n+/, "");
457
+ const cleanDelta = isFirstChunk ? delta.replace(/^\n+/, "") : delta;
458
+ isFirstChunk = false;
415
459
  const chunkEvent = {
416
460
  type: AgentEventTypes.AssistantChunk,
417
461
  conversationId: convo,
@@ -437,16 +481,32 @@ var AgentOrchestrator = class {
437
481
  } else {
438
482
  result = await this.deps.llm.generateCompletion({ ...params, messages: accumulatedMessages }, opts.signal);
439
483
  }
484
+ if (!result.tool_calls?.length && result.content && !finalResponseSaved) {
485
+ const content2 = opts.stream ? streamedAssistantContent : result.content;
486
+ const assistantMsg2 = {
487
+ id: msgId,
488
+ role: "assistant",
489
+ content: content2,
490
+ timestamp: this.deps.clock.iso(),
491
+ usage: result.usage
492
+ };
493
+ await this.deps.memory.append(convo, [assistantMsg2]);
494
+ finalResponseSaved = true;
495
+ if (content2.trim()) {
496
+ const messageEvent = {
497
+ type: AgentEventTypes.AssistantMessage,
498
+ conversationId: convo,
499
+ messageId: msgId,
500
+ content: content2,
501
+ ...result.usage && { usage: result.usage }
502
+ };
503
+ await this.deps.events?.emit(messageEvent);
504
+ }
505
+ }
440
506
  }
441
507
  const t1 = this.deps.clock.now();
442
508
  const timestamp = this.deps.clock.iso();
443
- const newHistory = [];
444
- for (const m of userMessages) newHistory.push(m);
445
- for (const m of turnHistory) newHistory.push(m);
446
- if (!toolApprovalDenied) {
447
- newHistory.push({ id: msgId, role: "assistant", content: result.content, timestamp });
448
- }
449
- const shouldEmitFinalMessage = result.content?.trim() && !toolApprovalDenied;
509
+ const shouldEmitFinalMessage = result.content?.trim() && !toolApprovalDenied && !finalResponseSaved;
450
510
  if (shouldEmitFinalMessage) {
451
511
  const messageEvent = {
452
512
  type: AgentEventTypes.AssistantMessage,
@@ -475,8 +535,6 @@ var AgentOrchestrator = class {
475
535
  toolCalls: allToolResults.length
476
536
  }
477
537
  };
478
- await this.deps.memory.append(convo, newHistory);
479
- await this.deps.events?.emit({ type: AgentEventTypes.MemoryAppended, conversationId: convo, delta: newHistory });
480
538
  await this.deps.events?.emit({
481
539
  type: AgentEventTypes.Done,
482
540
  conversationId: convo,
@@ -486,26 +544,6 @@ var AgentOrchestrator = class {
486
544
  });
487
545
  return resp;
488
546
  } catch (err2) {
489
- try {
490
- const partial = [];
491
- for (const m of userMessages) partial.push(m);
492
- for (const m of turnHistory) partial.push(m);
493
- const partialAssistant = (streamedAssistantContent || "").trim();
494
- const assistantAlreadyRecorded = turnHistory.some((m) => m.role === "assistant");
495
- if (partialAssistant && !assistantAlreadyRecorded) {
496
- partial.push({
497
- id: this.deps.ids.uuid(),
498
- role: "assistant",
499
- content: partialAssistant,
500
- timestamp: this.deps.clock.iso()
501
- });
502
- }
503
- if (partial.length) {
504
- await this.deps.memory.append(convo, partial);
505
- await this.deps.events?.emit({ type: AgentEventTypes.MemoryAppended, conversationId: convo, delta: partial });
506
- }
507
- } catch {
508
- }
509
547
  throw err2;
510
548
  }
511
549
  }
@@ -1842,7 +1880,10 @@ var FileReadTool = class {
1842
1880
  parameters = {
1843
1881
  type: "object",
1844
1882
  properties: {
1845
- description: { type: "string", description: 'Explanation of what file is being read and why (e.g., "Read package.json to check dependencies")' },
1883
+ description: {
1884
+ type: "string",
1885
+ description: 'Explanation of what file is being read and why (e.g., "Read package.json to check dependencies")'
1886
+ },
1846
1887
  path: { type: "string", description: "Read contents of this file" },
1847
1888
  lineStart: { type: "integer", minimum: 1, description: "Start reading from this line number (1-based)" },
1848
1889
  lineEnd: { type: "integer", minimum: 1, description: "Stop reading at this line number (inclusive)" }
@@ -1883,7 +1924,7 @@ var FileReadTool = class {
1883
1924
  const [lo, hi] = a <= b ? [a, b] : [b, a];
1884
1925
  const numberedLines = lines.slice(lo - 1, hi).map((line, index) => {
1885
1926
  const lineNum = lo + index;
1886
- return `${lineNum}:${line}`;
1927
+ return `${lineNum}\u2502${line}`;
1887
1928
  });
1888
1929
  const slice = numberedLines.join("\n");
1889
1930
  return ok(slice, {
@@ -3642,59 +3683,6 @@ var AgentFilePersistence = class {
3642
3683
  }
3643
3684
  };
3644
3685
 
3645
- // llm-providers/llm-echo.ts
3646
- var EchoLLM = class {
3647
- async generateCompletion(params) {
3648
- const last = [...params.messages].reverse().find((m) => m.role === "user");
3649
- const content = String(last?.content ?? "");
3650
- const toolCalls = [];
3651
- if (content.startsWith("!reverse ") && this.hasTool(params, "reverse_text")) {
3652
- const text = content.slice("!reverse ".length);
3653
- toolCalls.push({
3654
- id: this.id("rev"),
3655
- type: "function",
3656
- function: { name: "reverse_text", arguments: JSON.stringify({ text }) }
3657
- });
3658
- } else if (content.startsWith("!wc ") && this.hasTool(params, "word_count")) {
3659
- const text = content.slice("!wc ".length);
3660
- toolCalls.push({
3661
- id: this.id("wc"),
3662
- type: "function",
3663
- function: { name: "word_count", arguments: JSON.stringify({ text }) }
3664
- });
3665
- } else if (content.startsWith("!todo ") && this.hasTool(params, "todo_write")) {
3666
- const payload = content.slice("!todo ".length);
3667
- let todos;
3668
- try {
3669
- todos = JSON.parse(payload);
3670
- } catch {
3671
- todos = [];
3672
- }
3673
- toolCalls.push({
3674
- id: this.id("todo"),
3675
- type: "function",
3676
- function: { name: "todo_write", arguments: JSON.stringify({ todos }) }
3677
- });
3678
- }
3679
- const sawTool = params.messages.some((m) => m.role === "tool");
3680
- if (sawTool) {
3681
- const lastTool = [...params.messages].reverse().find((m) => m.role === "tool");
3682
- const toolText = typeof lastTool?.content === "string" ? lastTool.content : "";
3683
- return { content: `Tool result: ${toolText}` };
3684
- }
3685
- if (toolCalls.length > 0) {
3686
- return { content: "", tool_calls: toolCalls };
3687
- }
3688
- return { content: `Echo: ${content}` };
3689
- }
3690
- hasTool(params, name) {
3691
- return !!params.tools?.some((t) => t.function.name === name);
3692
- }
3693
- id(prefix) {
3694
- return `${prefix}_${Math.random().toString(36).slice(2, 10)}`;
3695
- }
3696
- };
3697
-
3698
3686
  // llm-providers/llm-utils.ts
3699
3687
  function mergeChoices(choices) {
3700
3688
  const contentParts = [];
@@ -4456,10 +4444,12 @@ var BaseBearerAuthTransport = class {
4456
4444
  inner;
4457
4445
  apiKey;
4458
4446
  baseUrl;
4459
- constructor(inner, apiKey, baseUrl) {
4447
+ version;
4448
+ constructor(inner, apiKey, baseUrl, version) {
4460
4449
  this.inner = inner;
4461
4450
  this.apiKey = apiKey;
4462
4451
  this.baseUrl = baseUrl ?? this.getDefaultBaseUrl();
4452
+ this.version = version;
4463
4453
  }
4464
4454
  buildFullUrl(path9) {
4465
4455
  if (path9.startsWith("/")) {
@@ -4473,6 +4463,9 @@ var BaseBearerAuthTransport = class {
4473
4463
  }
4474
4464
  const base = headers ? { ...headers } : {};
4475
4465
  base.Authorization = `Bearer ${this.apiKey}`;
4466
+ if (!base["User-Agent"] && this.version) {
4467
+ base["User-Agent"] = `nuvin-cli/${this.version}`;
4468
+ }
4476
4469
  return base;
4477
4470
  }
4478
4471
  async get(url, headers, signal) {
@@ -4492,8 +4485,8 @@ var BaseBearerAuthTransport = class {
4492
4485
  // transports/simple-bearer-transport.ts
4493
4486
  var SimpleBearerAuthTransport = class extends BaseBearerAuthTransport {
4494
4487
  defaultUrl;
4495
- constructor(inner, defaultBaseUrl, apiKey, baseUrl) {
4496
- super(inner, apiKey, baseUrl ?? defaultBaseUrl);
4488
+ constructor(inner, defaultBaseUrl, apiKey, baseUrl, version) {
4489
+ super(inner, apiKey, baseUrl ?? defaultBaseUrl, version);
4497
4490
  this.defaultUrl = defaultBaseUrl;
4498
4491
  }
4499
4492
  getDefaultBaseUrl() {
@@ -4502,8 +4495,8 @@ var SimpleBearerAuthTransport = class extends BaseBearerAuthTransport {
4502
4495
  };
4503
4496
 
4504
4497
  // transports/transport-factory.ts
4505
- function createTransport(_name, inner, defaultBaseUrl, apiKey, baseUrl) {
4506
- return new SimpleBearerAuthTransport(inner, defaultBaseUrl, apiKey, baseUrl);
4498
+ function createTransport(inner, defaultBaseUrl, apiKey, baseUrl, version) {
4499
+ return new SimpleBearerAuthTransport(inner, defaultBaseUrl, apiKey, baseUrl, version);
4507
4500
  }
4508
4501
 
4509
4502
  // llm-providers/llm-github.ts
@@ -4980,9 +4973,8 @@ var llm_provider_config_default = {
4980
4973
  providers: [
4981
4974
  {
4982
4975
  name: "deepinfra",
4983
- className: "DeepInfraLLM",
4976
+ type: "openai-compat",
4984
4977
  baseUrl: "https://api.deepinfra.com/v1/openai",
4985
- transportName: "deepinfra",
4986
4978
  features: {
4987
4979
  promptCaching: false,
4988
4980
  getModels: true
@@ -4990,9 +4982,8 @@ var llm_provider_config_default = {
4990
4982
  },
4991
4983
  {
4992
4984
  name: "openrouter",
4993
- className: "OpenRouterLLM",
4985
+ type: "openai-compat",
4994
4986
  baseUrl: "https://openrouter.ai/api/v1",
4995
- transportName: "openrouter",
4996
4987
  features: {
4997
4988
  promptCaching: true,
4998
4989
  getModels: true,
@@ -5001,9 +4992,8 @@ var llm_provider_config_default = {
5001
4992
  },
5002
4993
  {
5003
4994
  name: "zai",
5004
- className: "ZaiLLM",
4995
+ type: "openai-compat",
5005
4996
  baseUrl: "https://api.z.ai/api/coding/paas/v4",
5006
- transportName: "zai",
5007
4997
  features: {
5008
4998
  promptCaching: false,
5009
4999
  getModels: false
@@ -5011,9 +5001,8 @@ var llm_provider_config_default = {
5011
5001
  },
5012
5002
  {
5013
5003
  name: "moonshot",
5014
- className: "MoonshotLLM",
5004
+ type: "openai-compat",
5015
5005
  baseUrl: "https://api.moonshot.ai/v1",
5016
- transportName: "moonshot",
5017
5006
  features: {
5018
5007
  promptCaching: false,
5019
5008
  getModels: true
@@ -5026,15 +5015,13 @@ var llm_provider_config_default = {
5026
5015
  var providers = llm_provider_config_default.providers;
5027
5016
  var GenericLLM = class extends BaseLLM {
5028
5017
  opts;
5029
- transportName;
5030
5018
  includeUsage;
5031
- supportsModels;
5032
- constructor(transportName, baseUrl, supportsModels, opts = {}) {
5019
+ modelConfig;
5020
+ constructor(baseUrl, modelConfig, opts = {}) {
5033
5021
  const { enablePromptCaching = false, includeUsage = false, ...restOpts } = opts;
5034
5022
  super(opts.apiUrl || baseUrl, { enablePromptCaching });
5035
- this.transportName = transportName;
5036
5023
  this.includeUsage = includeUsage;
5037
- this.supportsModels = supportsModels;
5024
+ this.modelConfig = modelConfig;
5038
5025
  this.opts = restOpts;
5039
5026
  }
5040
5027
  createTransport() {
@@ -5045,11 +5032,24 @@ var GenericLLM = class extends BaseLLM {
5045
5032
  maxFileSize: 5 * 1024 * 1024,
5046
5033
  captureResponseBody: true
5047
5034
  });
5048
- return createTransport(this.transportName, base, this.apiUrl, this.opts.apiKey, this.opts.apiUrl);
5035
+ return createTransport(base, this.apiUrl, this.opts.apiKey, this.opts.apiUrl, this.opts.version);
5049
5036
  }
5050
5037
  async getModels(signal) {
5051
- if (!this.supportsModels) {
5052
- throw new Error(`Provider ${this.transportName} does not support getModels`);
5038
+ if (this.modelConfig === false) {
5039
+ throw new Error("Provider does not support getModels");
5040
+ }
5041
+ if (Array.isArray(this.modelConfig)) {
5042
+ return this.modelConfig.map((m) => typeof m === "string" ? { id: m } : m);
5043
+ }
5044
+ if (typeof this.modelConfig === "string") {
5045
+ const transport2 = this.createTransport();
5046
+ const res2 = await transport2.get(this.modelConfig, void 0, signal);
5047
+ if (!res2.ok) {
5048
+ const text = await res2.text();
5049
+ throw new Error(`Failed to fetch models: ${res2.status} ${text}`);
5050
+ }
5051
+ const data2 = await res2.json();
5052
+ return data2.data;
5053
5053
  }
5054
5054
  const transport = this.createTransport();
5055
5055
  const res = await transport.get("/models", void 0, signal);
@@ -5075,23 +5075,62 @@ var GenericLLM = class extends BaseLLM {
5075
5075
  return super.streamCompletion(enhancedParams, handlers, signal);
5076
5076
  }
5077
5077
  };
5078
- function createLLM(providerName, options = {}) {
5079
- const config = providers.find((p) => p.name.toLowerCase() === providerName.toLowerCase());
5078
+ function normalizeModelConfig(config) {
5079
+ if (config.models !== void 0) {
5080
+ return config.models;
5081
+ }
5082
+ return config.features.getModels ?? false;
5083
+ }
5084
+ function mergeProviders(customProviders) {
5085
+ const merged = /* @__PURE__ */ new Map();
5086
+ for (const provider of providers) {
5087
+ merged.set(provider.name.toLowerCase(), provider);
5088
+ }
5089
+ if (customProviders) {
5090
+ for (const [name, custom] of Object.entries(customProviders)) {
5091
+ if (!custom.baseUrl) {
5092
+ continue;
5093
+ }
5094
+ const existing = merged.get(name.toLowerCase());
5095
+ const providerConfig = {
5096
+ name,
5097
+ type: custom.type ?? "openai-compat",
5098
+ baseUrl: custom.baseUrl,
5099
+ models: custom.models ?? false,
5100
+ features: existing?.features ?? {
5101
+ promptCaching: false,
5102
+ getModels: custom.models !== false,
5103
+ includeUsage: false
5104
+ }
5105
+ };
5106
+ merged.set(name.toLowerCase(), providerConfig);
5107
+ }
5108
+ }
5109
+ return Array.from(merged.values());
5110
+ }
5111
+ function createLLM(providerName, options = {}, customProviders) {
5112
+ const allProviders = mergeProviders(customProviders);
5113
+ const config = allProviders.find((p) => p.name.toLowerCase() === providerName.toLowerCase());
5080
5114
  if (!config) {
5081
- throw new Error(`Unknown LLM provider: ${providerName}. Available: ${providers.map((p) => p.name).join(", ")}`);
5115
+ throw new Error(`Unknown LLM provider: ${providerName}. Available: ${allProviders.map((p) => p.name).join(", ")}`);
5082
5116
  }
5083
- return new GenericLLM(config.transportName, config.baseUrl, config.features.getModels ?? false, {
5117
+ const modelConfig = normalizeModelConfig(config);
5118
+ return new GenericLLM(config.baseUrl, modelConfig, {
5084
5119
  ...options,
5085
5120
  enablePromptCaching: options.enablePromptCaching ?? config.features.promptCaching,
5086
5121
  includeUsage: options.includeUsage ?? config.features.includeUsage
5087
5122
  });
5088
5123
  }
5089
- function getAvailableProviders() {
5090
- return providers.map((p) => p.name);
5124
+ function getAvailableProviders(customProviders) {
5125
+ const allProviders = mergeProviders(customProviders);
5126
+ return allProviders.map((p) => p.name);
5091
5127
  }
5092
- function supportsGetModels(providerName) {
5093
- const config = providers.find((p) => p.name.toLowerCase() === providerName.toLowerCase());
5094
- return config?.features.getModels ?? false;
5128
+ function supportsGetModels(providerName, customProviders) {
5129
+ const allProviders = mergeProviders(customProviders);
5130
+ const config = allProviders.find((p) => p.name.toLowerCase() === providerName.toLowerCase());
5131
+ if (!config) return false;
5132
+ const modelConfig = normalizeModelConfig(config);
5133
+ return modelConfig !== false;
5095
5134
  }
5096
5135
 
5097
5136
  // mcp/mcp-client.ts
@@ -5404,7 +5443,6 @@ export {
5404
5443
  DefaultDelegationService,
5405
5444
  DefaultSpecialistAgentFactory,
5406
5445
  DelegationServiceFactory,
5407
- EchoLLM,
5408
5446
  GithubLLM,
5409
5447
  InMemoryMemory,
5410
5448
  InMemoryMetadata,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nuvin/nuvin-core",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "",
5
5
  "private": false,
6
6
  "main": "dist/index.js",