@node-llm/core 1.5.4 → 1.6.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.
Files changed (188) hide show
  1. package/README.md +76 -43
  2. package/dist/aliases.d.ts +4 -0
  3. package/dist/aliases.d.ts.map +1 -1
  4. package/dist/aliases.js +4 -0
  5. package/dist/chat/Chat.d.ts +28 -17
  6. package/dist/chat/Chat.d.ts.map +1 -1
  7. package/dist/chat/Chat.js +75 -42
  8. package/dist/chat/ChatOptions.d.ts +8 -9
  9. package/dist/chat/ChatOptions.d.ts.map +1 -1
  10. package/dist/chat/ChatResponse.d.ts +26 -1
  11. package/dist/chat/ChatResponse.d.ts.map +1 -1
  12. package/dist/chat/ChatResponse.js +54 -8
  13. package/dist/chat/ChatStream.d.ts.map +1 -1
  14. package/dist/chat/ChatStream.js +14 -21
  15. package/dist/chat/Content.d.ts +3 -3
  16. package/dist/chat/Content.d.ts.map +1 -1
  17. package/dist/chat/Content.js +3 -6
  18. package/dist/chat/Message.d.ts +3 -1
  19. package/dist/chat/Message.d.ts.map +1 -1
  20. package/dist/chat/Role.d.ts.map +1 -1
  21. package/dist/chat/Tool.d.ts +8 -8
  22. package/dist/chat/Tool.d.ts.map +1 -1
  23. package/dist/chat/Tool.js +9 -7
  24. package/dist/chat/ToolHandler.d.ts +4 -3
  25. package/dist/chat/ToolHandler.d.ts.map +1 -1
  26. package/dist/chat/ToolHandler.js +10 -15
  27. package/dist/chat/Validation.d.ts.map +1 -1
  28. package/dist/chat/Validation.js +9 -3
  29. package/dist/config.d.ts +4 -0
  30. package/dist/config.d.ts.map +1 -1
  31. package/dist/config.js +80 -25
  32. package/dist/constants.js +1 -1
  33. package/dist/errors/index.d.ts +21 -7
  34. package/dist/errors/index.d.ts.map +1 -1
  35. package/dist/errors/index.js +14 -0
  36. package/dist/index.d.ts +1 -1
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +1 -1
  39. package/dist/llm.d.ts +44 -46
  40. package/dist/llm.d.ts.map +1 -1
  41. package/dist/llm.js +201 -130
  42. package/dist/model_aliases.d.ts.map +1 -1
  43. package/dist/models/ModelRegistry.d.ts.map +1 -1
  44. package/dist/models/ModelRegistry.js +13 -10
  45. package/dist/models/PricingRegistry.d.ts +31 -0
  46. package/dist/models/PricingRegistry.d.ts.map +1 -0
  47. package/dist/models/PricingRegistry.js +109 -0
  48. package/dist/models/models.d.ts.map +1 -1
  49. package/dist/models/models.js +230 -138
  50. package/dist/models/types.d.ts +37 -34
  51. package/dist/models/types.d.ts.map +1 -1
  52. package/dist/moderation/Moderation.d.ts.map +1 -1
  53. package/dist/moderation/Moderation.js +15 -5
  54. package/dist/providers/BaseProvider.d.ts +12 -8
  55. package/dist/providers/BaseProvider.d.ts.map +1 -1
  56. package/dist/providers/BaseProvider.js +17 -7
  57. package/dist/providers/Provider.d.ts +20 -5
  58. package/dist/providers/Provider.d.ts.map +1 -1
  59. package/dist/providers/anthropic/AnthropicProvider.d.ts +1 -1
  60. package/dist/providers/anthropic/AnthropicProvider.d.ts.map +1 -1
  61. package/dist/providers/anthropic/AnthropicProvider.js +3 -3
  62. package/dist/providers/anthropic/Capabilities.d.ts +2 -1
  63. package/dist/providers/anthropic/Capabilities.d.ts.map +1 -1
  64. package/dist/providers/anthropic/Capabilities.js +3 -20
  65. package/dist/providers/anthropic/Chat.d.ts.map +1 -1
  66. package/dist/providers/anthropic/Chat.js +27 -17
  67. package/dist/providers/anthropic/Errors.d.ts.map +1 -1
  68. package/dist/providers/anthropic/Errors.js +5 -2
  69. package/dist/providers/anthropic/Models.d.ts.map +1 -1
  70. package/dist/providers/anthropic/Models.js +6 -6
  71. package/dist/providers/anthropic/Streaming.d.ts.map +1 -1
  72. package/dist/providers/anthropic/Streaming.js +17 -12
  73. package/dist/providers/anthropic/Utils.js +8 -5
  74. package/dist/providers/anthropic/index.d.ts.map +1 -1
  75. package/dist/providers/anthropic/index.js +4 -3
  76. package/dist/providers/anthropic/types.d.ts +11 -4
  77. package/dist/providers/anthropic/types.d.ts.map +1 -1
  78. package/dist/providers/deepseek/Capabilities.d.ts +7 -5
  79. package/dist/providers/deepseek/Capabilities.d.ts.map +1 -1
  80. package/dist/providers/deepseek/Capabilities.js +9 -5
  81. package/dist/providers/deepseek/Chat.d.ts.map +1 -1
  82. package/dist/providers/deepseek/Chat.js +10 -9
  83. package/dist/providers/deepseek/DeepSeekProvider.d.ts +1 -1
  84. package/dist/providers/deepseek/DeepSeekProvider.d.ts.map +1 -1
  85. package/dist/providers/deepseek/DeepSeekProvider.js +4 -4
  86. package/dist/providers/deepseek/Models.d.ts.map +1 -1
  87. package/dist/providers/deepseek/Models.js +7 -7
  88. package/dist/providers/deepseek/Streaming.d.ts.map +1 -1
  89. package/dist/providers/deepseek/Streaming.js +11 -8
  90. package/dist/providers/deepseek/index.d.ts.map +1 -1
  91. package/dist/providers/deepseek/index.js +5 -4
  92. package/dist/providers/gemini/Capabilities.d.ts +5 -33
  93. package/dist/providers/gemini/Capabilities.d.ts.map +1 -1
  94. package/dist/providers/gemini/Capabilities.js +7 -30
  95. package/dist/providers/gemini/Chat.d.ts.map +1 -1
  96. package/dist/providers/gemini/Chat.js +24 -19
  97. package/dist/providers/gemini/ChatUtils.d.ts.map +1 -1
  98. package/dist/providers/gemini/ChatUtils.js +10 -10
  99. package/dist/providers/gemini/Embeddings.d.ts.map +1 -1
  100. package/dist/providers/gemini/Embeddings.js +2 -2
  101. package/dist/providers/gemini/Errors.d.ts.map +1 -1
  102. package/dist/providers/gemini/Errors.js +5 -2
  103. package/dist/providers/gemini/GeminiProvider.d.ts +1 -1
  104. package/dist/providers/gemini/GeminiProvider.d.ts.map +1 -1
  105. package/dist/providers/gemini/GeminiProvider.js +3 -3
  106. package/dist/providers/gemini/Image.d.ts.map +1 -1
  107. package/dist/providers/gemini/Image.js +7 -7
  108. package/dist/providers/gemini/Models.d.ts.map +1 -1
  109. package/dist/providers/gemini/Models.js +6 -6
  110. package/dist/providers/gemini/Streaming.d.ts.map +1 -1
  111. package/dist/providers/gemini/Streaming.js +18 -14
  112. package/dist/providers/gemini/Transcription.d.ts.map +1 -1
  113. package/dist/providers/gemini/Transcription.js +11 -11
  114. package/dist/providers/gemini/index.d.ts +1 -1
  115. package/dist/providers/gemini/index.d.ts.map +1 -1
  116. package/dist/providers/gemini/index.js +5 -4
  117. package/dist/providers/gemini/types.d.ts +4 -4
  118. package/dist/providers/gemini/types.d.ts.map +1 -1
  119. package/dist/providers/ollama/Capabilities.d.ts.map +1 -1
  120. package/dist/providers/ollama/Capabilities.js +6 -2
  121. package/dist/providers/ollama/Models.d.ts.map +1 -1
  122. package/dist/providers/ollama/Models.js +1 -1
  123. package/dist/providers/ollama/OllamaProvider.d.ts +1 -1
  124. package/dist/providers/ollama/OllamaProvider.d.ts.map +1 -1
  125. package/dist/providers/ollama/OllamaProvider.js +2 -2
  126. package/dist/providers/ollama/index.d.ts +1 -1
  127. package/dist/providers/ollama/index.d.ts.map +1 -1
  128. package/dist/providers/ollama/index.js +7 -3
  129. package/dist/providers/openai/Capabilities.d.ts +2 -1
  130. package/dist/providers/openai/Capabilities.d.ts.map +1 -1
  131. package/dist/providers/openai/Capabilities.js +9 -21
  132. package/dist/providers/openai/Chat.d.ts.map +1 -1
  133. package/dist/providers/openai/Chat.js +18 -15
  134. package/dist/providers/openai/Embedding.d.ts.map +1 -1
  135. package/dist/providers/openai/Embedding.js +11 -7
  136. package/dist/providers/openai/Errors.d.ts.map +1 -1
  137. package/dist/providers/openai/Errors.js +5 -2
  138. package/dist/providers/openai/Image.d.ts.map +1 -1
  139. package/dist/providers/openai/Image.js +6 -6
  140. package/dist/providers/openai/Models.d.ts +1 -1
  141. package/dist/providers/openai/Models.d.ts.map +1 -1
  142. package/dist/providers/openai/Models.js +12 -8
  143. package/dist/providers/openai/Moderation.d.ts.map +1 -1
  144. package/dist/providers/openai/Moderation.js +6 -6
  145. package/dist/providers/openai/OpenAIProvider.d.ts +2 -3
  146. package/dist/providers/openai/OpenAIProvider.d.ts.map +1 -1
  147. package/dist/providers/openai/OpenAIProvider.js +4 -4
  148. package/dist/providers/openai/Streaming.d.ts.map +1 -1
  149. package/dist/providers/openai/Streaming.js +18 -13
  150. package/dist/providers/openai/Transcription.d.ts.map +1 -1
  151. package/dist/providers/openai/Transcription.js +15 -12
  152. package/dist/providers/openai/index.d.ts +1 -1
  153. package/dist/providers/openai/index.d.ts.map +1 -1
  154. package/dist/providers/openai/index.js +6 -5
  155. package/dist/providers/openai/types.d.ts +1 -1
  156. package/dist/providers/openai/utils.js +2 -2
  157. package/dist/providers/openrouter/Capabilities.d.ts +3 -3
  158. package/dist/providers/openrouter/Capabilities.d.ts.map +1 -1
  159. package/dist/providers/openrouter/Capabilities.js +21 -24
  160. package/dist/providers/openrouter/Models.d.ts.map +1 -1
  161. package/dist/providers/openrouter/Models.js +20 -16
  162. package/dist/providers/openrouter/OpenRouterProvider.d.ts.map +1 -1
  163. package/dist/providers/openrouter/OpenRouterProvider.js +1 -1
  164. package/dist/providers/openrouter/index.d.ts +1 -1
  165. package/dist/providers/openrouter/index.d.ts.map +1 -1
  166. package/dist/providers/openrouter/index.js +6 -5
  167. package/dist/providers/registry.d.ts +18 -2
  168. package/dist/providers/registry.d.ts.map +1 -1
  169. package/dist/providers/registry.js +17 -2
  170. package/dist/providers/utils.js +1 -1
  171. package/dist/schema/Schema.d.ts +3 -3
  172. package/dist/schema/Schema.d.ts.map +1 -1
  173. package/dist/schema/Schema.js +2 -2
  174. package/dist/schema/to-json-schema.d.ts +1 -1
  175. package/dist/schema/to-json-schema.d.ts.map +1 -1
  176. package/dist/streaming/Stream.d.ts.map +1 -1
  177. package/dist/streaming/Stream.js +3 -3
  178. package/dist/utils/Binary.d.ts.map +1 -1
  179. package/dist/utils/Binary.js +23 -13
  180. package/dist/utils/FileLoader.d.ts.map +1 -1
  181. package/dist/utils/FileLoader.js +25 -4
  182. package/dist/utils/audio.js +1 -1
  183. package/dist/utils/fetch.d.ts.map +1 -1
  184. package/dist/utils/fetch.js +3 -2
  185. package/dist/utils/logger.d.ts +3 -3
  186. package/dist/utils/logger.d.ts.map +1 -1
  187. package/dist/utils/logger.js +2 -2
  188. package/package.json +1 -1
package/dist/llm.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import { Chat } from "./chat/Chat.js";
2
- import { providerRegistry, ensureOpenAIRegistered, registerAnthropicProvider, registerGeminiProvider, registerDeepSeekProvider, registerOllamaProvider, registerOpenRouterProvider, } from "./providers/registry.js";
2
+ import { providerRegistry, ensureOpenAIRegistered, registerAnthropicProvider, registerGeminiProvider, registerDeepSeekProvider, registerOllamaProvider, registerOpenRouterProvider } from "./providers/registry.js";
3
3
  import { GeneratedImage } from "./image/GeneratedImage.js";
4
4
  import { ModelRegistry } from "./models/ModelRegistry.js";
5
+ import { PricingRegistry } from "./models/PricingRegistry.js";
5
6
  import { Transcription } from "./transcription/Transcription.js";
6
7
  import { Moderation } from "./moderation/Moderation.js";
7
8
  import { Embedding } from "./embedding/Embedding.js";
@@ -16,125 +17,63 @@ const PROVIDER_REGISTRARS = {
16
17
  anthropic: registerAnthropicProvider,
17
18
  deepseek: registerDeepSeekProvider,
18
19
  ollama: registerOllamaProvider,
19
- openrouter: registerOpenRouterProvider,
20
+ openrouter: registerOpenRouterProvider
20
21
  };
21
22
  export class NodeLLMCore {
22
- models = ModelRegistry;
23
23
  config;
24
24
  provider;
25
- defaultChatModelId;
26
- defaultTranscriptionModelId;
27
- defaultModerationModelId;
28
- defaultEmbeddingModelId;
29
- retry = {
30
- attempts: 1,
31
- delayMs: 0,
32
- };
33
- /**
34
- * Create a new LLM instance. Defaults to the global config.
35
- */
36
- constructor(customConfig) {
37
- if (customConfig instanceof Configuration) {
38
- this.config = customConfig;
39
- }
40
- else if (customConfig) {
41
- this.config = new Configuration();
42
- Object.assign(this.config, customConfig);
43
- }
44
- else {
45
- this.config = config;
46
- }
47
- if (this.config.maxRetries !== undefined) {
48
- this.retry.attempts = this.config.maxRetries + 1;
49
- }
25
+ retry;
26
+ defaults;
27
+ models = ModelRegistry;
28
+ pricing = PricingRegistry;
29
+ constructor(config, provider, retry = { attempts: 1, delayMs: 0 }, defaults = {}) {
30
+ this.config = config;
31
+ this.provider = provider;
32
+ this.retry = retry;
33
+ this.defaults = defaults;
34
+ Object.freeze(this.config);
35
+ Object.freeze(this.retry);
36
+ Object.freeze(this.defaults);
37
+ }
38
+ get defaultChatModel() {
39
+ return this.defaults.chat;
40
+ }
41
+ get defaultTranscriptionModel() {
42
+ return this.defaults.transcription;
43
+ }
44
+ get defaultModerationModel() {
45
+ return this.defaults.moderation;
46
+ }
47
+ get defaultEmbeddingModel() {
48
+ return this.defaults.embedding;
50
49
  }
51
50
  /**
52
51
  * Returns a scoped LLM instance configured for a specific provider.
53
- * This respects the current global configuration but avoids side effects
54
- * on the main NodeLLM singleton.
55
- *
56
- * @param providerName - The provider to use (e.g., "openai", "anthropic")
57
- * @param scopedConfig - Optional configuration overrides for this scoped instance
58
- *
59
- * @example
60
- * ```ts
61
- * const openai = NodeLLM.withProvider("openai");
62
- * const anthropic = NodeLLM.withProvider("anthropic");
63
- *
64
- * // These can now run in parallel without race conditions
65
- * await Promise.all([
66
- * openai.chat("gpt-4o").ask(prompt),
67
- * anthropic.chat("claude-3-5-sonnet").ask(prompt),
68
- * ]);
69
- * ```
70
- *
71
- * @example With scoped credentials
72
- * ```ts
73
- * const customAnthropic = NodeLLM.withProvider("anthropic", {
74
- * anthropicApiKey: "sk-ant-custom-key"
75
- * });
76
- * ```
52
+ * This returns a NEW immutable instance.
77
53
  */
78
54
  withProvider(providerName, scopedConfig) {
79
- const baseConfig = (this.config instanceof Configuration) ? this.config.toPlainObject() : this.config;
80
- const scoped = new NodeLLMCore({ ...baseConfig, ...scopedConfig });
81
- scoped.configure({ provider: providerName });
82
- return scoped;
55
+ const baseConfig = this.config instanceof Configuration ? this.config.toPlainObject() : this.config;
56
+ // We leverage createLLM to handle the resolution of the new provider string
57
+ return createLLM({
58
+ ...baseConfig,
59
+ ...scopedConfig,
60
+ provider: providerName,
61
+ // Preserve defaults unless overridden (conceptually, though createLLM takes specific keys)
62
+ defaultChatModel: this.defaults.chat,
63
+ defaultTranscriptionModel: this.defaults.transcription,
64
+ defaultModerationModel: this.defaults.moderation,
65
+ defaultEmbeddingModel: this.defaults.embedding
66
+ });
83
67
  }
84
68
  /**
85
69
  * Register a custom LLM provider.
86
- * This allows you to extend NodeLLM with your own logic at runtime.
87
- *
88
- * @param name - Unique identifier for the provider
89
- * @param factory - A function that returns a Provider instance
70
+ * Note: This modifies the global provider registry.
90
71
  */
91
72
  registerProvider(name, factory) {
92
73
  providerRegistry.register(name, factory);
93
74
  }
94
- configure(configOrCallback) {
95
- // Callback style: for setting API keys
96
- if (typeof configOrCallback === "function") {
97
- configOrCallback(this.config);
98
- return;
99
- }
100
- // Object style: for setting provider and other options
101
- const options = configOrCallback;
102
- // Extract known control keys
103
- const { provider, retry, defaultChatModel, defaultTranscriptionModel, defaultModerationModel, defaultEmbeddingModel, ...apiConfig } = options;
104
- // Merge API keys into global config
105
- Object.assign(this.config, apiConfig);
106
- if (apiConfig.maxRetries !== undefined) {
107
- this.retry.attempts = apiConfig.maxRetries + 1;
108
- }
109
- if (defaultChatModel) {
110
- this.defaultChatModelId = defaultChatModel;
111
- }
112
- if (defaultTranscriptionModel) {
113
- this.defaultTranscriptionModelId = defaultTranscriptionModel;
114
- }
115
- if (defaultModerationModel) {
116
- this.defaultModerationModelId = defaultModerationModel;
117
- }
118
- if (defaultEmbeddingModel) {
119
- this.defaultEmbeddingModelId = defaultEmbeddingModel;
120
- }
121
- if (retry) {
122
- this.retry = {
123
- attempts: retry.attempts ?? 1,
124
- delayMs: retry.delayMs ?? 0,
125
- };
126
- }
127
- if (typeof provider === "string") {
128
- // Use the provider registrars map
129
- const registrar = PROVIDER_REGISTRARS[provider];
130
- if (registrar) {
131
- registrar();
132
- }
133
- this.provider = providerRegistry.resolve(provider);
134
- }
135
- else if (provider) {
136
- this.provider = provider;
137
- }
75
+ getRetryConfig() {
76
+ return this.retry;
138
77
  }
139
78
  ensureProviderSupport(method) {
140
79
  if (!this.provider) {
@@ -149,7 +88,7 @@ export class NodeLLMCore {
149
88
  if (!this.provider) {
150
89
  throw new ProviderNotConfiguredError();
151
90
  }
152
- const rawModel = model || this.defaultChatModelId || this.provider.defaultModel("chat");
91
+ const rawModel = model || this.defaults.chat || this.provider.defaultModel("chat");
153
92
  const resolvedModel = resolveModelAlias(rawModel, this.provider.id);
154
93
  return new Chat(this.provider, resolvedModel, options, this.retry);
155
94
  }
@@ -162,56 +101,47 @@ export class NodeLLMCore {
162
101
  }
163
102
  async paint(prompt, options) {
164
103
  const provider = this.ensureProviderSupport("paint");
165
- // Default to resolving aliases
166
104
  const rawModel = options?.model;
167
105
  const model = resolveModelAlias(rawModel || "", provider.id);
168
106
  if (options?.assumeModelExists) {
169
107
  logger.warn(`Skipping validation for model ${model}`);
170
108
  }
171
- else if (model && provider.capabilities && !provider.capabilities.supportsImageGeneration(model)) {
109
+ else if (model &&
110
+ provider.capabilities &&
111
+ !provider.capabilities.supportsImageGeneration(model)) {
172
112
  throw new ModelCapabilityError(model, "image generation");
173
113
  }
174
114
  const response = await provider.paint({
175
115
  prompt,
176
116
  ...options,
177
117
  model,
178
- requestTimeout: options?.requestTimeout ?? this.config.requestTimeout,
118
+ requestTimeout: options?.requestTimeout ?? this.config.requestTimeout
179
119
  });
180
120
  return new GeneratedImage(response);
181
121
  }
182
122
  async transcribe(file, options) {
183
123
  const provider = this.ensureProviderSupport("transcribe");
184
- const rawModel = options?.model || this.defaultTranscriptionModelId || "";
124
+ const rawModel = options?.model || this.defaults.transcription || "";
185
125
  const model = resolveModelAlias(rawModel, provider.id);
186
126
  if (options?.assumeModelExists) {
187
127
  logger.warn(`Skipping validation for model ${model}`);
188
128
  }
189
- else if (model && provider.capabilities && !provider.capabilities.supportsTranscription(model)) {
129
+ else if (model &&
130
+ provider.capabilities &&
131
+ !provider.capabilities.supportsTranscription(model)) {
190
132
  throw new ModelCapabilityError(model, "transcription");
191
133
  }
192
134
  const response = await provider.transcribe({
193
135
  file,
194
136
  ...options,
195
137
  model,
196
- requestTimeout: options?.requestTimeout ?? this.config.requestTimeout,
138
+ requestTimeout: options?.requestTimeout ?? this.config.requestTimeout
197
139
  });
198
140
  return new Transcription(response);
199
141
  }
200
- get defaultTranscriptionModel() {
201
- return this.defaultTranscriptionModelId;
202
- }
203
- get defaultModerationModel() {
204
- return this.defaultModerationModelId;
205
- }
206
- get defaultEmbeddingModel() {
207
- return this.defaultEmbeddingModelId;
208
- }
209
- getRetryConfig() {
210
- return this.retry;
211
- }
212
142
  async moderate(input, options) {
213
143
  const provider = this.ensureProviderSupport("moderate");
214
- const rawModel = options?.model || this.defaultModerationModelId || "";
144
+ const rawModel = options?.model || this.defaults.moderation || "";
215
145
  const model = resolveModelAlias(rawModel, provider.id);
216
146
  if (options?.assumeModelExists) {
217
147
  logger.warn(`Skipping validation for model ${model}`);
@@ -223,29 +153,170 @@ export class NodeLLMCore {
223
153
  input,
224
154
  ...options,
225
155
  model,
226
- requestTimeout: options?.requestTimeout ?? this.config.requestTimeout,
156
+ requestTimeout: options?.requestTimeout ?? this.config.requestTimeout
227
157
  });
228
158
  return new Moderation(response);
229
159
  }
230
160
  async embed(input, options) {
231
161
  const provider = this.ensureProviderSupport("embed");
232
- const rawModel = options?.model || this.defaultEmbeddingModelId || "";
162
+ const rawModel = options?.model || this.defaults.embedding || "";
233
163
  const model = resolveModelAlias(rawModel, provider.id);
234
164
  const request = {
235
165
  input,
236
166
  model,
237
167
  dimensions: options?.dimensions,
238
- requestTimeout: options?.requestTimeout ?? this.config.requestTimeout,
168
+ requestTimeout: options?.requestTimeout ?? this.config.requestTimeout
239
169
  };
240
170
  if (options?.assumeModelExists) {
241
171
  logger.warn(`Skipping validation for model ${request.model}`);
242
172
  }
243
- else if (request.model && provider.capabilities && !provider.capabilities.supportsEmbeddings(request.model)) {
173
+ else if (request.model &&
174
+ provider.capabilities &&
175
+ !provider.capabilities.supportsEmbeddings(request.model)) {
244
176
  throw new ModelCapabilityError(request.model, "embeddings");
245
177
  }
246
178
  const response = await provider.embed(request);
247
179
  return new Embedding(response);
248
180
  }
249
181
  }
250
- export { Transcription, Moderation, Embedding };
251
- export const NodeLLM = new NodeLLMCore();
182
+ export { Transcription, Moderation, Embedding, ModelRegistry, PricingRegistry };
183
+ /**
184
+ * Creates a new immutable LLM instance.
185
+ */
186
+ export function createLLM(options = {}) {
187
+ // 1. Resolve Configuration
188
+ // We must ensure we are working with a SNAPSHOT of the configuration,
189
+ // not a live reference to the global mutable Configuration instance.
190
+ let configSnapshot;
191
+ if (options instanceof Configuration) {
192
+ configSnapshot = options.toPlainObject();
193
+ }
194
+ else {
195
+ // If it's a plain object, we merge it into a fresh Configuration to handle
196
+ // defaults and environment variable fallbacks correctly, then snapshot it.
197
+ const tempConfig = new Configuration();
198
+ Object.assign(tempConfig, options);
199
+ configSnapshot = tempConfig.toPlainObject();
200
+ }
201
+ // Use the snapshot for the rest of the logic
202
+ const baseConfig = configSnapshot;
203
+ // 2. Resolve Retry
204
+ let retry = { attempts: 1, delayMs: 0 };
205
+ if (baseConfig.maxRetries !== undefined) {
206
+ retry.attempts = baseConfig.maxRetries + 1;
207
+ }
208
+ if (options.retry) {
209
+ retry = {
210
+ attempts: options.retry.attempts ?? retry.attempts,
211
+ delayMs: options.retry.delayMs ?? retry.delayMs
212
+ };
213
+ }
214
+ // 3. Resolve Provider
215
+ let providerInstance;
216
+ if (typeof options.provider === "string") {
217
+ const registrar = PROVIDER_REGISTRARS[options.provider];
218
+ if (registrar)
219
+ registrar();
220
+ providerInstance = providerRegistry.resolve(options.provider, baseConfig);
221
+ }
222
+ else if (options.provider) {
223
+ providerInstance = options.provider;
224
+ }
225
+ // 4. Resolve Defaults
226
+ const defaults = {
227
+ chat: options.defaultChatModel,
228
+ transcription: options.defaultTranscriptionModel,
229
+ moderation: options.defaultModerationModel,
230
+ embedding: options.defaultEmbeddingModel
231
+ };
232
+ return new NodeLLMCore(baseConfig, providerInstance, retry, defaults);
233
+ }
234
+ /**
235
+ * DEFAULT IMMUTABLE INSTANCE
236
+ *
237
+ * NodeLLM is a default immutable instance created at startup.
238
+ *
239
+ * **Architectural Contract**:
240
+ * - Configuration is captured from environment variables at module load time
241
+ * - The instance is frozen and cannot be mutated
242
+ * - Runtime provider switching is achieved via `withProvider()`, not mutation
243
+ * - Model names do NOT imply provider selection (provider must be explicit)
244
+ *
245
+ * **Usage Patterns**:
246
+ *
247
+ * 1. Using the default instance (if env vars are set):
248
+ * ```typescript
249
+ * import { NodeLLM } from '@node-llm/core';
250
+ * const chat = NodeLLM.chat("gpt-4");
251
+ * ```
252
+ *
253
+ * 2. Runtime provider switching (recommended):
254
+ * ```typescript
255
+ * const llm = NodeLLM.withProvider("openai", {
256
+ * openaiApiKey: process.env.OPENAI_API_KEY
257
+ * });
258
+ * const chat = llm.chat("gpt-4");
259
+ * ```
260
+ *
261
+ * 3. Creating custom instances:
262
+ * ```typescript
263
+ * import { createLLM } from '@node-llm/core';
264
+ * const llm = createLLM({ provider: "anthropic", anthropicApiKey: "..." });
265
+ * ```
266
+ *
267
+ * @see ARCHITECTURE.md for full contract details
268
+ */
269
+ let _defaultInstance;
270
+ /**
271
+ * The global, immutable NodeLLM instance.
272
+ *
273
+ * DESIGN: Lazy Initialization
274
+ * To support 'import "dotenv/config"' patterns in ESM, this instance
275
+ * does NOT snapshot the environment until its first property access.
276
+ * Once accessed, it is frozen and becomes a stable, immutable contract.
277
+ *
278
+ * @see ARCHITECTURE.md for full contract details
279
+ */
280
+ export const NodeLLM = new Proxy({}, {
281
+ get: (target, prop) => {
282
+ if (!_defaultInstance) {
283
+ _defaultInstance = createLLM(config);
284
+ Object.freeze(_defaultInstance);
285
+ }
286
+ const val = Reflect.get(_defaultInstance, prop);
287
+ // Only bind if it's a function AND it exists on the prototype (is a method)
288
+ // This avoids binding properties like 'models' which is a Class.
289
+ if (typeof val === "function" && prop in NodeLLMCore.prototype) {
290
+ return val.bind(_defaultInstance);
291
+ }
292
+ return val;
293
+ },
294
+ getPrototypeOf: () => NodeLLMCore.prototype,
295
+ getOwnPropertyDescriptor: (target, prop) => {
296
+ if (!_defaultInstance) {
297
+ _defaultInstance = createLLM(config);
298
+ Object.freeze(_defaultInstance);
299
+ }
300
+ return Object.getOwnPropertyDescriptor(_defaultInstance, prop);
301
+ },
302
+ ownKeys: () => {
303
+ if (!_defaultInstance) {
304
+ _defaultInstance = createLLM(config);
305
+ Object.freeze(_defaultInstance);
306
+ }
307
+ return Reflect.ownKeys(_defaultInstance);
308
+ }
309
+ });
310
+ /**
311
+ * LEGACY BOOTSTRAPPER (DEPRECATED)
312
+ *
313
+ * Provided to ease migration from the mutable singleton pattern.
314
+ * configure() will warn and no-op, as the global instance is now immutable.
315
+ */
316
+ export const LegacyNodeLLM = {
317
+ configure(_options) {
318
+ console.warn("NodeLLM.configure() is deprecated and currently a NO-OP. " +
319
+ "The global NodeLLM instance is now immutable. " +
320
+ "Use createLLM() for instance-based LLMs or NodeLLM.withProvider() for runtime switching.");
321
+ }
322
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"model_aliases.d.ts","sourceRoot":"","sources":["../src/model_aliases.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,WAAW,GAAG,QAAQ,GAAG,UAAU,GAAG,YAAY,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,GAAG,MAAM,CAAC;AAEvI,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,YAAY,GAAG,MAAM,CAiBhF"}
1
+ {"version":3,"file":"model_aliases.d.ts","sourceRoot":"","sources":["../src/model_aliases.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,YAAY,GACpB,QAAQ,GACR,WAAW,GACX,QAAQ,GACR,UAAU,GACV,YAAY,GACZ,SAAS,GACT,UAAU,GACV,SAAS,GACT,MAAM,CAAC;AAEX,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,YAAY,GAAG,MAAM,CAiBhF"}
@@ -1 +1 @@
1
- {"version":3,"file":"ModelRegistry.d.ts","sourceRoot":"","sources":["../../src/models/ModelRegistry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAGnC,qBAAa,aAAa;IACtB,OAAO,CAAC,MAAM,CAAC,MAAM,CAA6C;IAElE,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS;IAMlE;;OAEG;IACH,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,GAAG,KAAK,EAAE,GAAG,IAAI;IAa1C;;OAEG;IACH,MAAM,CAAC,GAAG,IAAI,KAAK,EAAE;IAIrB;;OAEG;IACH,MAAM,CAAC,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAKhF;;OAEG;IACH,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO;IAK/E;;OAEG;IACH,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAK9E;;OAEG;IACH,MAAM,CAAC,aAAa,CAAC,KAAK,EAAE;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAC;QAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;sBAA3I,MAAM;uBAAiB,MAAM;sBAAgB,MAAM;wBAAkB,MAAM;2BAAqB,MAAM;;;;;sBAAtG,MAAM;uBAAiB,MAAM;sBAAgB,MAAM;wBAAkB,MAAM;2BAAqB,MAAM;;CA+BrJ"}
1
+ {"version":3,"file":"ModelRegistry.d.ts","sourceRoot":"","sources":["../../src/models/ModelRegistry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAInC,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAC,MAAM,CAA6C;IAElE,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS;IAMlE;;OAEG;IACH,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,GAAG,KAAK,EAAE,GAAG,IAAI;IAe1C;;OAEG;IACH,MAAM,CAAC,GAAG,IAAI,KAAK,EAAE;IAIrB;;OAEG;IACH,MAAM,CAAC,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAKhF;;OAEG;IACH,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO;IAK/E;;OAEG;IACH,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAK9E;;OAEG;IACH,MAAM,CAAC,aAAa,CAClB,KAAK,EAAE;QACL,YAAY,EAAE,MAAM,CAAC;QACrB,aAAa,EAAE,MAAM,CAAC;QACtB,YAAY,EAAE,MAAM,CAAC;QACrB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC3B,EACD,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM;sBAPA,MAAM;uBACL,MAAM;sBACP,MAAM;wBACJ,MAAM;2BACH,MAAM;;;;;sBAJX,MAAM;uBACL,MAAM;sBACP,MAAM;wBACJ,MAAM;2BACH,MAAM;;CAyC9B"}
@@ -1,16 +1,17 @@
1
1
  import { modelsData } from "./models.js";
2
+ import { PricingRegistry } from "./PricingRegistry.js";
2
3
  export class ModelRegistry {
3
4
  static models = modelsData;
4
5
  static find(modelId, provider) {
5
- return this.models.find(m => (m.id === modelId || m.family === modelId) && (!provider || m.provider === provider));
6
+ return this.models.find((m) => m.id.toLowerCase() === modelId.toLowerCase() && (!provider || m.provider === provider));
6
7
  }
7
8
  /**
8
9
  * Add or update models in the registry.
9
10
  */
10
11
  static save(models) {
11
12
  const toAdd = Array.isArray(models) ? models : [models];
12
- toAdd.forEach(newModel => {
13
- const index = this.models.findIndex(m => m.id === newModel.id && m.provider === newModel.provider);
13
+ toAdd.forEach((newModel) => {
14
+ const index = this.models.findIndex((m) => m.id === newModel.id && m.provider === newModel.provider);
14
15
  if (index >= 0) {
15
16
  this.models[index] = newModel;
16
17
  }
@@ -50,21 +51,23 @@ export class ModelRegistry {
50
51
  * Calculate cost for usage.
51
52
  */
52
53
  static calculateCost(usage, modelId, provider) {
53
- const model = this.find(modelId, provider);
54
- if (!model || !model.pricing?.text_tokens?.standard) {
54
+ const pricing = PricingRegistry.getPricing(modelId, provider);
55
+ if (!pricing || !pricing.text_tokens?.standard) {
55
56
  return usage;
56
57
  }
57
- const prices = model.pricing.text_tokens.standard;
58
+ const prices = pricing.text_tokens.standard;
58
59
  const inputPrice = prices.input_per_million || 0;
59
60
  const outputPrice = prices.output_per_million || 0;
60
- const reasoningPrice = prices.reasoning_output_per_million || outputPrice;
61
- const cachedPrice = prices.cached_input_per_million ?? (inputPrice / 2);
61
+ // Fallback for reasoning: if not specified, default to 2.5x standard output price for specific reasoning models
62
+ // or just standard output price for others.
63
+ const reasoningPrice = prices.reasoning_output_per_million ??
64
+ (modelId.includes("reasoner") || modelId.includes("3-7") ? outputPrice * 2.5 : outputPrice);
65
+ const cachedPrice = prices.cached_input_per_million ?? inputPrice / 2;
62
66
  const inputCost = ((usage.input_tokens - (usage.cached_tokens || 0)) / 1_000_000) * inputPrice +
63
67
  ((usage.cached_tokens || 0) / 1_000_000) * cachedPrice;
64
68
  const outputTokens = usage.output_tokens - (usage.reasoning_tokens || 0);
65
69
  const reasoningTokens = usage.reasoning_tokens || 0;
66
- const outputCost = (outputTokens / 1_000_000) * outputPrice +
67
- (reasoningTokens / 1_000_000) * reasoningPrice;
70
+ const outputCost = (outputTokens / 1_000_000) * outputPrice + (reasoningTokens / 1_000_000) * reasoningPrice;
68
71
  const totalCost = inputCost + outputCost;
69
72
  return {
70
73
  ...usage,
@@ -0,0 +1,31 @@
1
+ import { ModelPricing } from "./types.js";
2
+ /**
3
+ * Registry for LLM pricing information.
4
+ * Priority: Runtime Overrides > Library Default Patterns > ModelRegistry (models.ts)
5
+ */
6
+ export declare class PricingRegistry {
7
+ private static pricingOverrides;
8
+ private static lastUpdated;
9
+ private static DEFAULT_PATTERNS;
10
+ /**
11
+ * Get pricing for a model.
12
+ */
13
+ static getPricing(modelId: string, provider: string): ModelPricing | undefined;
14
+ /**
15
+ * Register or override pricing at runtime.
16
+ */
17
+ static register(provider: string, modelId: string, pricing: ModelPricing): void;
18
+ /**
19
+ * Fetch updates from a remote URL.
20
+ */
21
+ static fetchUpdates(url: string): Promise<boolean>;
22
+ /**
23
+ * Get the timestamp of the last update.
24
+ */
25
+ static getLastUpdated(): Date | null;
26
+ /**
27
+ * Reset overrides.
28
+ */
29
+ static reset(): void;
30
+ }
31
+ //# sourceMappingURL=PricingRegistry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PricingRegistry.d.ts","sourceRoot":"","sources":["../../src/models/PricingRegistry.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C;;;GAGG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAAC,gBAAgB,CAAmC;IAClE,OAAO,CAAC,MAAM,CAAC,WAAW,CAA2B;IAGrD,OAAO,CAAC,MAAM,CAAC,gBAAgB,CAwC7B;IAEF;;OAEG;IACH,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;IAmB9E;;OAEG;IACH,MAAM,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY;IAIxE;;OAEG;WACU,YAAY,CAAC,GAAG,EAAE,MAAM;IAmBrC;;OAEG;IACH,MAAM,CAAC,cAAc,IAAI,IAAI,GAAG,IAAI;IAIpC;;OAEG;IACH,MAAM,CAAC,KAAK;CAIb"}
@@ -0,0 +1,109 @@
1
+ import { ModelRegistry } from "./ModelRegistry.js";
2
+ /**
3
+ * Registry for LLM pricing information.
4
+ * Priority: Runtime Overrides > Library Default Patterns > ModelRegistry (models.ts)
5
+ */
6
+ export class PricingRegistry {
7
+ static pricingOverrides = new Map();
8
+ static lastUpdated = new Date();
9
+ // Library-level fallbacks for common model families
10
+ static DEFAULT_PATTERNS = [
11
+ {
12
+ provider: "anthropic",
13
+ pattern: /claude-3-7/,
14
+ pricing: {
15
+ text_tokens: {
16
+ standard: {
17
+ input_per_million: 3.0,
18
+ output_per_million: 15.0,
19
+ reasoning_output_per_million: 37.5
20
+ },
21
+ batch: {
22
+ input_per_million: 1.5,
23
+ output_per_million: 7.5,
24
+ reasoning_output_per_million: 18.75
25
+ }
26
+ }
27
+ }
28
+ },
29
+ {
30
+ provider: "anthropic",
31
+ pattern: /claude-3/,
32
+ pricing: {
33
+ text_tokens: {
34
+ standard: { input_per_million: 3.0, output_per_million: 15.0 },
35
+ batch: { input_per_million: 1.5, output_per_million: 7.5 }
36
+ }
37
+ }
38
+ },
39
+ {
40
+ provider: "openai",
41
+ pattern: /gpt-3/,
42
+ pricing: {
43
+ text_tokens: { standard: { input_per_million: 0.5, output_per_million: 1.5 } }
44
+ }
45
+ }
46
+ ];
47
+ /**
48
+ * Get pricing for a model.
49
+ */
50
+ static getPricing(modelId, provider) {
51
+ // 1. Check custom overrides (Runtime/Remote)
52
+ const key = `${provider}/${modelId}`;
53
+ if (this.pricingOverrides.has(key)) {
54
+ return this.pricingOverrides.get(key);
55
+ }
56
+ // 2. Check library default patterns (Core Overrides)
57
+ for (const entry of this.DEFAULT_PATTERNS) {
58
+ if (entry.provider === provider && entry.pattern.test(modelId)) {
59
+ return entry.pricing;
60
+ }
61
+ }
62
+ // 3. Fallback to registry lookup (models.ts)
63
+ const model = ModelRegistry.find(modelId, provider);
64
+ return model?.pricing;
65
+ }
66
+ /**
67
+ * Register or override pricing at runtime.
68
+ */
69
+ static register(provider, modelId, pricing) {
70
+ this.pricingOverrides.set(`${provider}/${modelId}`, pricing);
71
+ }
72
+ /**
73
+ * Fetch updates from a remote URL.
74
+ */
75
+ static async fetchUpdates(url) {
76
+ try {
77
+ const response = await fetch(url);
78
+ if (!response.ok)
79
+ throw new Error(response.statusText);
80
+ const data = await response.json();
81
+ if (data.models) {
82
+ for (const [key, pricing] of Object.entries(data.models)) {
83
+ const [provider, modelId] = key.split("/");
84
+ if (provider && modelId)
85
+ this.register(provider, modelId, pricing);
86
+ }
87
+ }
88
+ this.lastUpdated = new Date();
89
+ return true;
90
+ }
91
+ catch (error) {
92
+ console.error("[NodeLLM] Error fetching pricing updates:", error);
93
+ return false;
94
+ }
95
+ }
96
+ /**
97
+ * Get the timestamp of the last update.
98
+ */
99
+ static getLastUpdated() {
100
+ return this.lastUpdated;
101
+ }
102
+ /**
103
+ * Reset overrides.
104
+ */
105
+ static reset() {
106
+ this.pricingOverrides.clear();
107
+ this.lastUpdated = new Date();
108
+ }
109
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../../src/models/models.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IA4sVtB,CAAC"}
1
+ {"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../../src/models/models.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAwyVtB,CAAC"}