@node-llm/core 1.8.0 → 1.10.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.
Files changed (101) hide show
  1. package/README.md +35 -0
  2. package/dist/aliases.d.ts +105 -9
  3. package/dist/aliases.d.ts.map +1 -1
  4. package/dist/aliases.js +105 -9
  5. package/dist/chat/Chat.d.ts +8 -3
  6. package/dist/chat/Chat.d.ts.map +1 -1
  7. package/dist/chat/Chat.js +181 -131
  8. package/dist/chat/ChatOptions.d.ts +2 -0
  9. package/dist/chat/ChatOptions.d.ts.map +1 -1
  10. package/dist/chat/ChatResponse.d.ts +24 -3
  11. package/dist/chat/ChatResponse.d.ts.map +1 -1
  12. package/dist/chat/ChatResponse.js +72 -5
  13. package/dist/chat/ChatStream.d.ts.map +1 -1
  14. package/dist/chat/ChatStream.js +111 -56
  15. package/dist/config.d.ts.map +1 -1
  16. package/dist/config.js +9 -7
  17. package/dist/constants.d.ts +6 -0
  18. package/dist/constants.d.ts.map +1 -1
  19. package/dist/constants.js +6 -0
  20. package/dist/errors/index.d.ts +20 -2
  21. package/dist/errors/index.d.ts.map +1 -1
  22. package/dist/errors/index.js +31 -3
  23. package/dist/index.d.ts +5 -2
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +2 -0
  26. package/dist/llm.d.ts +8 -1
  27. package/dist/llm.d.ts.map +1 -1
  28. package/dist/llm.js +156 -59
  29. package/dist/middlewares/CostGuardMiddleware.d.ts +24 -0
  30. package/dist/middlewares/CostGuardMiddleware.d.ts.map +1 -0
  31. package/dist/middlewares/CostGuardMiddleware.js +23 -0
  32. package/dist/middlewares/PIIMaskMiddleware.d.ts +23 -0
  33. package/dist/middlewares/PIIMaskMiddleware.d.ts.map +1 -0
  34. package/dist/middlewares/PIIMaskMiddleware.js +41 -0
  35. package/dist/middlewares/UsageLoggerMiddleware.d.ts +22 -0
  36. package/dist/middlewares/UsageLoggerMiddleware.d.ts.map +1 -0
  37. package/dist/middlewares/UsageLoggerMiddleware.js +30 -0
  38. package/dist/middlewares/index.d.ts +4 -0
  39. package/dist/middlewares/index.d.ts.map +1 -0
  40. package/dist/middlewares/index.js +3 -0
  41. package/dist/models/ModelRegistry.d.ts.map +1 -1
  42. package/dist/models/ModelRegistry.js +4 -2
  43. package/dist/models/PricingRegistry.js +3 -3
  44. package/dist/models/{models.js → models.json} +9934 -8196
  45. package/dist/providers/BaseProvider.d.ts +6 -1
  46. package/dist/providers/BaseProvider.d.ts.map +1 -1
  47. package/dist/providers/BaseProvider.js +19 -0
  48. package/dist/providers/anthropic/AnthropicProvider.d.ts.map +1 -1
  49. package/dist/providers/anthropic/AnthropicProvider.js +2 -1
  50. package/dist/providers/anthropic/Chat.d.ts.map +1 -1
  51. package/dist/providers/anthropic/Chat.js +2 -1
  52. package/dist/providers/anthropic/Errors.d.ts.map +1 -1
  53. package/dist/providers/anthropic/Errors.js +15 -1
  54. package/dist/providers/anthropic/Streaming.d.ts.map +1 -1
  55. package/dist/providers/anthropic/Streaming.js +19 -3
  56. package/dist/providers/bedrock/Chat.d.ts.map +1 -1
  57. package/dist/providers/bedrock/Chat.js +2 -20
  58. package/dist/providers/bedrock/Errors.d.ts +2 -0
  59. package/dist/providers/bedrock/Errors.d.ts.map +1 -0
  60. package/dist/providers/bedrock/Errors.js +51 -0
  61. package/dist/providers/bedrock/Streaming.d.ts.map +1 -1
  62. package/dist/providers/bedrock/Streaming.js +2 -3
  63. package/dist/providers/deepseek/Chat.d.ts.map +1 -1
  64. package/dist/providers/deepseek/Chat.js +2 -2
  65. package/dist/providers/deepseek/DeepSeekProvider.d.ts.map +1 -1
  66. package/dist/providers/deepseek/DeepSeekProvider.js +2 -1
  67. package/dist/providers/deepseek/Errors.d.ts +2 -0
  68. package/dist/providers/deepseek/Errors.d.ts.map +1 -0
  69. package/dist/providers/deepseek/Errors.js +45 -0
  70. package/dist/providers/deepseek/Streaming.d.ts.map +1 -1
  71. package/dist/providers/deepseek/Streaming.js +13 -2
  72. package/dist/providers/gemini/Errors.d.ts.map +1 -1
  73. package/dist/providers/gemini/Errors.js +13 -1
  74. package/dist/providers/gemini/GeminiProvider.d.ts.map +1 -1
  75. package/dist/providers/gemini/GeminiProvider.js +2 -1
  76. package/dist/providers/ollama/OllamaProvider.d.ts.map +1 -1
  77. package/dist/providers/ollama/OllamaProvider.js +2 -2
  78. package/dist/providers/openai/Errors.d.ts.map +1 -1
  79. package/dist/providers/openai/Errors.js +31 -5
  80. package/dist/providers/openai/OpenAIProvider.d.ts +1 -1
  81. package/dist/providers/openai/OpenAIProvider.d.ts.map +1 -1
  82. package/dist/providers/openai/OpenAIProvider.js +15 -3
  83. package/dist/providers/openai/Streaming.d.ts.map +1 -1
  84. package/dist/providers/openai/Streaming.js +10 -0
  85. package/dist/providers/openrouter/OpenRouterProvider.d.ts.map +1 -1
  86. package/dist/providers/openrouter/OpenRouterProvider.js +2 -1
  87. package/dist/providers/registry.d.ts +3 -0
  88. package/dist/providers/registry.d.ts.map +1 -1
  89. package/dist/providers/registry.js +10 -2
  90. package/dist/types/Middleware.d.ts +98 -0
  91. package/dist/types/Middleware.d.ts.map +1 -0
  92. package/dist/types/Middleware.js +1 -0
  93. package/dist/utils/json.d.ts +6 -0
  94. package/dist/utils/json.d.ts.map +1 -0
  95. package/dist/utils/json.js +43 -0
  96. package/dist/utils/middleware-runner.d.ts +7 -0
  97. package/dist/utils/middleware-runner.d.ts.map +1 -0
  98. package/dist/utils/middleware-runner.js +20 -0
  99. package/package.json +1 -1
  100. package/dist/models/models.d.ts +0 -572
  101. package/dist/models/models.d.ts.map +0 -1
package/dist/llm.js CHANGED
@@ -10,6 +10,8 @@ import { ProviderNotConfiguredError, UnsupportedFeatureError, ModelCapabilityErr
10
10
  import { resolveModelAlias } from "./model_aliases.js";
11
11
  import { logger } from "./utils/logger.js";
12
12
  import { config, Configuration } from "./config.js";
13
+ import { runMiddleware } from "./utils/middleware-runner.js";
14
+ import { randomUUID } from "node:crypto";
13
15
  // Provider registration map
14
16
  const PROVIDER_REGISTRARS = {
15
17
  openai: ensureOpenAIRegistered,
@@ -24,13 +26,15 @@ export class NodeLLMCore {
24
26
  config;
25
27
  provider;
26
28
  retry;
29
+ middlewares;
27
30
  defaults;
28
31
  models = ModelRegistry;
29
32
  pricing = PricingRegistry;
30
- constructor(config, provider, retry = { attempts: 1, delayMs: 0 }, defaults = {}) {
33
+ constructor(config, provider, retry = { attempts: 1, delayMs: 0 }, middlewares = [], defaults = {}) {
31
34
  this.config = config;
32
35
  this.provider = provider;
33
36
  this.retry = retry;
37
+ this.middlewares = middlewares;
34
38
  this.defaults = defaults;
35
39
  Object.freeze(this.config);
36
40
  Object.freeze(this.retry);
@@ -59,7 +63,8 @@ export class NodeLLMCore {
59
63
  ...baseConfig,
60
64
  ...scopedConfig,
61
65
  provider: providerName,
62
- // Preserve defaults unless overridden (conceptually, though createLLM takes specific keys)
66
+ // Preserve middlewares and defaults unless overridden
67
+ middlewares: this.middlewares,
63
68
  defaultChatModel: this.defaults.chat,
64
69
  defaultTranscriptionModel: this.defaults.transcription,
65
70
  defaultModerationModel: this.defaults.moderation,
@@ -85,13 +90,18 @@ export class NodeLLMCore {
85
90
  }
86
91
  return this.provider;
87
92
  }
88
- chat(model, options) {
93
+ chat(model, options = {}) {
89
94
  if (!this.provider) {
90
95
  throw new ProviderNotConfiguredError();
91
96
  }
92
97
  const rawModel = model || this.defaults.chat || this.provider.defaultModel("chat");
93
98
  const resolvedModel = resolveModelAlias(rawModel, this.provider.id);
94
- return new Chat(this.provider, resolvedModel, options, this.retry);
99
+ // Merge global middlewares with local ones
100
+ const combinedOptions = {
101
+ ...options,
102
+ middlewares: [...this.middlewares, ...(options.middlewares || [])]
103
+ };
104
+ return new Chat(this.provider, resolvedModel, combinedOptions, this.retry);
95
105
  }
96
106
  async listModels() {
97
107
  const provider = this.ensureProviderSupport("listModels");
@@ -102,82 +112,169 @@ export class NodeLLMCore {
102
112
  }
103
113
  async paint(prompt, options) {
104
114
  const provider = this.ensureProviderSupport("paint");
105
- const rawModel = options?.model;
106
- const model = resolveModelAlias(rawModel || "", provider.id);
107
- if (options?.assumeModelExists) {
108
- logger.warn(`Skipping validation for model ${model}`);
115
+ const rawModel = options?.model || provider.defaultModel("image");
116
+ const model = resolveModelAlias(rawModel, provider.id);
117
+ const requestId = randomUUID();
118
+ const state = {};
119
+ const context = {
120
+ requestId,
121
+ provider: provider.id,
122
+ model,
123
+ input: prompt,
124
+ imageOptions: options,
125
+ state
126
+ };
127
+ const middlewares = [...this.middlewares, ...(options?.middlewares || [])];
128
+ try {
129
+ await runMiddleware(middlewares, "onRequest", context);
130
+ const currentPrompt = context.input || prompt;
131
+ if (options?.assumeModelExists) {
132
+ logger.warn(`Skipping validation for model ${model}`);
133
+ }
134
+ else if (model &&
135
+ provider.capabilities &&
136
+ !provider.capabilities.supportsImageGeneration(model)) {
137
+ throw new ModelCapabilityError(model, "image generation");
138
+ }
139
+ const response = await provider.paint({
140
+ prompt: currentPrompt,
141
+ ...options,
142
+ model,
143
+ requestTimeout: options?.requestTimeout ?? this.config.requestTimeout
144
+ });
145
+ const imageResult = new GeneratedImage(response);
146
+ await runMiddleware(middlewares, "onResponse", context, imageResult);
147
+ return imageResult;
109
148
  }
110
- else if (model &&
111
- provider.capabilities &&
112
- !provider.capabilities.supportsImageGeneration(model)) {
113
- throw new ModelCapabilityError(model, "image generation");
149
+ catch (error) {
150
+ await runMiddleware(middlewares, "onError", context, error);
151
+ throw error;
114
152
  }
115
- const response = await provider.paint({
116
- prompt,
117
- ...options,
118
- model,
119
- requestTimeout: options?.requestTimeout ?? this.config.requestTimeout
120
- });
121
- return new GeneratedImage(response);
122
153
  }
123
154
  async transcribe(file, options) {
124
155
  const provider = this.ensureProviderSupport("transcribe");
125
- const rawModel = options?.model || this.defaults.transcription || "";
156
+ const rawModel = options?.model || this.defaults.transcription || provider.defaultModel("transcription");
126
157
  const model = resolveModelAlias(rawModel, provider.id);
127
- if (options?.assumeModelExists) {
128
- logger.warn(`Skipping validation for model ${model}`);
158
+ const requestId = randomUUID();
159
+ const state = {};
160
+ const context = {
161
+ requestId,
162
+ provider: provider.id,
163
+ model,
164
+ input: file,
165
+ transcriptionOptions: options,
166
+ state
167
+ };
168
+ const middlewares = [...this.middlewares, ...(options?.middlewares || [])];
169
+ try {
170
+ await runMiddleware(middlewares, "onRequest", context);
171
+ const currentFile = context.input || file;
172
+ if (options?.assumeModelExists) {
173
+ logger.warn(`Skipping validation for model ${model}`);
174
+ }
175
+ else if (model &&
176
+ provider.capabilities &&
177
+ !provider.capabilities.supportsTranscription(model)) {
178
+ throw new ModelCapabilityError(model, "transcription");
179
+ }
180
+ const response = await provider.transcribe({
181
+ file: currentFile,
182
+ ...options,
183
+ model,
184
+ requestTimeout: options?.requestTimeout ?? this.config.requestTimeout
185
+ });
186
+ const transcriptionResult = new Transcription(response);
187
+ await runMiddleware(middlewares, "onResponse", context, transcriptionResult);
188
+ return transcriptionResult;
129
189
  }
130
- else if (model &&
131
- provider.capabilities &&
132
- !provider.capabilities.supportsTranscription(model)) {
133
- throw new ModelCapabilityError(model, "transcription");
190
+ catch (error) {
191
+ await runMiddleware(middlewares, "onError", context, error);
192
+ throw error;
134
193
  }
135
- const response = await provider.transcribe({
136
- file,
137
- ...options,
138
- model,
139
- requestTimeout: options?.requestTimeout ?? this.config.requestTimeout
140
- });
141
- return new Transcription(response);
142
194
  }
143
195
  async moderate(input, options) {
144
196
  const provider = this.ensureProviderSupport("moderate");
145
- const rawModel = options?.model || this.defaults.moderation || "";
197
+ const rawModel = options?.model || this.defaults.moderation || provider.defaultModel("moderation");
146
198
  const model = resolveModelAlias(rawModel, provider.id);
147
- if (options?.assumeModelExists) {
148
- logger.warn(`Skipping validation for model ${model}`);
199
+ const requestId = randomUUID();
200
+ const state = {};
201
+ const context = {
202
+ requestId,
203
+ provider: provider.id,
204
+ model,
205
+ input,
206
+ moderationOptions: options,
207
+ state
208
+ };
209
+ const middlewares = [...this.middlewares, ...(options?.middlewares || [])];
210
+ try {
211
+ await runMiddleware(middlewares, "onRequest", context);
212
+ const currentInput = context.input || input;
213
+ if (options?.assumeModelExists) {
214
+ logger.warn(`Skipping validation for model ${model}`);
215
+ }
216
+ else if (model &&
217
+ provider.capabilities &&
218
+ !provider.capabilities.supportsModeration(model)) {
219
+ throw new ModelCapabilityError(model, "moderation");
220
+ }
221
+ const response = await provider.moderate({
222
+ input: currentInput,
223
+ ...options,
224
+ model,
225
+ requestTimeout: options?.requestTimeout ?? this.config.requestTimeout
226
+ });
227
+ const moderationResult = new Moderation(response);
228
+ await runMiddleware(middlewares, "onResponse", context, moderationResult);
229
+ return moderationResult;
149
230
  }
150
- else if (model && provider.capabilities && !provider.capabilities.supportsModeration(model)) {
151
- throw new ModelCapabilityError(model, "moderation");
231
+ catch (error) {
232
+ await runMiddleware(middlewares, "onError", context, error);
233
+ throw error;
152
234
  }
153
- const response = await provider.moderate({
154
- input,
155
- ...options,
156
- model,
157
- requestTimeout: options?.requestTimeout ?? this.config.requestTimeout
158
- });
159
- return new Moderation(response);
160
235
  }
161
236
  async embed(input, options) {
162
237
  const provider = this.ensureProviderSupport("embed");
163
- const rawModel = options?.model || this.defaults.embedding || "";
238
+ const rawModel = options?.model || this.defaults.embedding || provider.defaultModel("embedding");
164
239
  const model = resolveModelAlias(rawModel, provider.id);
165
- const request = {
166
- input,
240
+ const requestId = randomUUID();
241
+ const state = {};
242
+ const context = {
243
+ requestId,
244
+ provider: provider.id,
167
245
  model,
168
- dimensions: options?.dimensions,
169
- requestTimeout: options?.requestTimeout ?? this.config.requestTimeout
246
+ input,
247
+ embeddingOptions: options,
248
+ state
170
249
  };
171
- if (options?.assumeModelExists) {
172
- logger.warn(`Skipping validation for model ${request.model}`);
250
+ const middlewares = [...this.middlewares, ...(options?.middlewares || [])];
251
+ try {
252
+ await runMiddleware(middlewares, "onRequest", context);
253
+ // Re-read input from context as it might have been modified
254
+ const currentInput = context.input || input;
255
+ const request = {
256
+ input: currentInput,
257
+ model,
258
+ dimensions: options?.dimensions,
259
+ requestTimeout: options?.requestTimeout ?? this.config.requestTimeout
260
+ };
261
+ if (options?.assumeModelExists) {
262
+ logger.warn(`Skipping validation for model ${request.model}`);
263
+ }
264
+ else if (request.model &&
265
+ provider.capabilities &&
266
+ !provider.capabilities.supportsEmbeddings(request.model)) {
267
+ throw new ModelCapabilityError(request.model, "embeddings");
268
+ }
269
+ const response = await provider.embed(request);
270
+ const embeddingResult = new Embedding(response);
271
+ await runMiddleware(middlewares, "onResponse", context, embeddingResult);
272
+ return embeddingResult;
173
273
  }
174
- else if (request.model &&
175
- provider.capabilities &&
176
- !provider.capabilities.supportsEmbeddings(request.model)) {
177
- throw new ModelCapabilityError(request.model, "embeddings");
274
+ catch (error) {
275
+ await runMiddleware(middlewares, "onError", context, error);
276
+ throw error;
178
277
  }
179
- const response = await provider.embed(request);
180
- return new Embedding(response);
181
278
  }
182
279
  }
183
280
  export { Transcription, Moderation, Embedding, ModelRegistry, PricingRegistry };
@@ -230,7 +327,7 @@ export function createLLM(options = {}) {
230
327
  moderation: options.defaultModerationModel,
231
328
  embedding: options.defaultEmbeddingModel
232
329
  };
233
- return new NodeLLMCore(baseConfig, providerInstance, retry, defaults);
330
+ return new NodeLLMCore(baseConfig, providerInstance, retry, options.middlewares || [], defaults);
234
331
  }
235
332
  /**
236
333
  * DEFAULT IMMUTABLE INSTANCE
@@ -0,0 +1,24 @@
1
+ import { Middleware, MiddlewareContext } from "../types/Middleware.js";
2
+ import { ChatResponseString } from "../chat/ChatResponse.js";
3
+ export interface CostGuardOptions {
4
+ /**
5
+ * Maximum allowed cost (in USD) for a single request sequence.
6
+ */
7
+ maxCost: number;
8
+ /**
9
+ * Callback when the limit is exceeded.
10
+ */
11
+ onLimitExceeded?: (ctx: MiddlewareContext, currentCost: number) => void;
12
+ }
13
+ /**
14
+ * Middleware that monitors accumulated cost during a session (especially multi-turn tool calls)
15
+ * and throws an error if a defined limit is exceeded.
16
+ */
17
+ export declare class CostGuardMiddleware implements Middleware {
18
+ private options;
19
+ readonly name = "CostGuard";
20
+ private accumulatedCost;
21
+ constructor(options: CostGuardOptions);
22
+ onResponse(ctx: MiddlewareContext, result: ChatResponseString): Promise<void>;
23
+ }
24
+ //# sourceMappingURL=CostGuardMiddleware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CostGuardMiddleware.d.ts","sourceRoot":"","sources":["../../src/middlewares/CostGuardMiddleware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AACvE,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAE7D,MAAM,WAAW,gBAAgB;IAC/B;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;OAEG;IACH,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;CACzE;AAED;;;GAGG;AACH,qBAAa,mBAAoB,YAAW,UAAU;IAIxC,OAAO,CAAC,OAAO;IAH3B,SAAgB,IAAI,eAAe;IACnC,OAAO,CAAC,eAAe,CAAa;gBAEhB,OAAO,EAAE,gBAAgB;IAMvC,UAAU,CAAC,GAAG,EAAE,iBAAiB,EAAE,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;CAWpF"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Middleware that monitors accumulated cost during a session (especially multi-turn tool calls)
3
+ * and throws an error if a defined limit is exceeded.
4
+ */
5
+ export class CostGuardMiddleware {
6
+ options;
7
+ name = "CostGuard";
8
+ accumulatedCost = 0;
9
+ constructor(options) {
10
+ this.options = options;
11
+ if (options.maxCost <= 0) {
12
+ throw new Error("CostGuard maxCost must be greater than 0");
13
+ }
14
+ }
15
+ async onResponse(ctx, result) {
16
+ const cost = result.usage?.cost || 0;
17
+ this.accumulatedCost += cost;
18
+ if (this.accumulatedCost > this.options.maxCost) {
19
+ this.options.onLimitExceeded?.(ctx, this.accumulatedCost);
20
+ throw new Error(`[CostGuard] Budget exceeded. Accumulated cost $${this.accumulatedCost.toFixed(6)} exceeds limit $${this.options.maxCost.toFixed(6)}`);
21
+ }
22
+ }
23
+ }
@@ -0,0 +1,23 @@
1
+ import { Middleware, MiddlewareContext } from "../types/Middleware.js";
2
+ export interface PIIMaskOptions {
3
+ /**
4
+ * Custom masking string. Defaults to "[REDACTED]".
5
+ */
6
+ mask?: string;
7
+ /**
8
+ * Whether to redact the assistant's output as well. Defaults to false.
9
+ */
10
+ redactOutput?: boolean;
11
+ }
12
+ /**
13
+ * Middleware that automatically redacts Personal Identifiable Information (PII)
14
+ * from user messages before they are sent to the LLM.
15
+ */
16
+ export declare class PIIMaskMiddleware implements Middleware {
17
+ private options;
18
+ readonly name = "PIIMask";
19
+ constructor(options?: PIIMaskOptions);
20
+ onRequest(ctx: MiddlewareContext): Promise<void>;
21
+ private maskText;
22
+ }
23
+ //# sourceMappingURL=PIIMaskMiddleware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PIIMaskMiddleware.d.ts","sourceRoot":"","sources":["../../src/middlewares/PIIMaskMiddleware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAEvE,MAAM,WAAW,cAAc;IAC7B;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AASD;;;GAGG;AACH,qBAAa,iBAAkB,YAAW,UAAU;IAGtC,OAAO,CAAC,OAAO;IAF3B,SAAgB,IAAI,aAAa;gBAEb,OAAO,GAAE,cAAmB;IAE1C,SAAS,CAAC,GAAG,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBtD,OAAO,CAAC,QAAQ;CAOjB"}
@@ -0,0 +1,41 @@
1
+ const PII_PATTERNS = {
2
+ email: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g,
3
+ phone: /(\+?\d{1,3}[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}/g,
4
+ creditCard: /\b(?:\d[ -]?){13,16}\b/g,
5
+ ssn: /\b\d{3}-\d{2}-\d{4}\b/g
6
+ };
7
+ /**
8
+ * Middleware that automatically redacts Personal Identifiable Information (PII)
9
+ * from user messages before they are sent to the LLM.
10
+ */
11
+ export class PIIMaskMiddleware {
12
+ options;
13
+ name = "PIIMask";
14
+ constructor(options = {}) {
15
+ this.options = options;
16
+ }
17
+ async onRequest(ctx) {
18
+ if (!ctx.messages)
19
+ return;
20
+ const mask = this.options.mask || "[REDACTED]";
21
+ for (const message of ctx.messages) {
22
+ if (typeof message.content === "string") {
23
+ message.content = this.maskText(message.content, mask);
24
+ }
25
+ else if (Array.isArray(message.content)) {
26
+ for (const part of message.content) {
27
+ if (part.type === "text") {
28
+ part.text = this.maskText(part.text, mask);
29
+ }
30
+ }
31
+ }
32
+ }
33
+ }
34
+ maskText(text, mask) {
35
+ let masked = text;
36
+ for (const pattern of Object.values(PII_PATTERNS)) {
37
+ masked = masked.replace(pattern, mask);
38
+ }
39
+ return masked;
40
+ }
41
+ }
@@ -0,0 +1,22 @@
1
+ import { Middleware, MiddlewareContext } from "../types/Middleware.js";
2
+ import { ChatResponseString } from "../chat/ChatResponse.js";
3
+ export interface UsageLoggerOptions {
4
+ /**
5
+ * Optional custom logger function. Defaults to internal NodeLLM logger.
6
+ */
7
+ logger?: (message: string, data: any) => void;
8
+ /**
9
+ * Prefix for the log message.
10
+ */
11
+ prefix?: string;
12
+ }
13
+ /**
14
+ * Middleware that logs token usage and costs for every successful request.
15
+ */
16
+ export declare class UsageLoggerMiddleware implements Middleware {
17
+ private options;
18
+ readonly name = "UsageLogger";
19
+ constructor(options?: UsageLoggerOptions);
20
+ onResponse(ctx: MiddlewareContext, result: ChatResponseString): Promise<void>;
21
+ }
22
+ //# sourceMappingURL=UsageLoggerMiddleware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UsageLoggerMiddleware.d.ts","sourceRoot":"","sources":["../../src/middlewares/UsageLoggerMiddleware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AACvE,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAG7D,MAAM,WAAW,kBAAkB;IACjC;;OAEG;IACH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC;IAC9C;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,qBAAa,qBAAsB,YAAW,UAAU;IAG1C,OAAO,CAAC,OAAO;IAF3B,SAAgB,IAAI,iBAAiB;gBAEjB,OAAO,GAAE,kBAAuB;IAE9C,UAAU,CAAC,GAAG,EAAE,iBAAiB,EAAE,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;CAsBpF"}
@@ -0,0 +1,30 @@
1
+ import { logger } from "../utils/logger.js";
2
+ /**
3
+ * Middleware that logs token usage and costs for every successful request.
4
+ */
5
+ export class UsageLoggerMiddleware {
6
+ options;
7
+ name = "UsageLogger";
8
+ constructor(options = {}) {
9
+ this.options = options;
10
+ }
11
+ async onResponse(ctx, result) {
12
+ const usage = result.usage;
13
+ if (!usage)
14
+ return;
15
+ const logFn = this.options.logger || ((msg, data) => logger.info(`${msg} ${JSON.stringify(data)}`));
16
+ const prefix = this.options.prefix ? `[${this.options.prefix}] ` : "";
17
+ const message = `${prefix}LLM Usage: ${ctx.provider}/${ctx.model}`;
18
+ const data = {
19
+ requestId: ctx.requestId,
20
+ tokens: {
21
+ input: usage.input_tokens,
22
+ output: usage.output_tokens,
23
+ total: usage.total_tokens,
24
+ cached: usage.cached_tokens
25
+ },
26
+ cost: usage.cost ? usage.cost.toFixed(6) : undefined
27
+ };
28
+ logFn(message, data);
29
+ }
30
+ }
@@ -0,0 +1,4 @@
1
+ export * from "./UsageLoggerMiddleware.js";
2
+ export * from "./CostGuardMiddleware.js";
3
+ export * from "./PIIMaskMiddleware.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/middlewares/index.ts"],"names":[],"mappings":"AAAA,cAAc,4BAA4B,CAAC;AAC3C,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC"}
@@ -0,0 +1,3 @@
1
+ export * from "./UsageLoggerMiddleware.js";
2
+ export * from "./CostGuardMiddleware.js";
3
+ export * from "./PIIMaskMiddleware.js";
@@ -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;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
+ {"version":3,"file":"ModelRegistry.d.ts","sourceRoot":"","sources":["../../src/models/ModelRegistry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAKnC,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;IAQlE;;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,9 +1,11 @@
1
- import { modelsData } from "./models.js";
1
+ // @ts-ignore - Node 20.9 requires 'assert', but TS 5.3+ enforces 'with'
2
+ import modelsData from "./models.json" assert { type: "json" };
2
3
  import { PricingRegistry } from "./PricingRegistry.js";
3
4
  export class ModelRegistry {
4
5
  static models = modelsData;
5
6
  static find(modelId, provider) {
6
- return this.models.find((m) => m.id.toLowerCase() === modelId.toLowerCase() && (!provider || m.provider === provider));
7
+ return this.models.find((m) => m.id.toLowerCase() === modelId.toLowerCase() &&
8
+ (!provider || m.provider.toLowerCase() === provider.toLowerCase()));
7
9
  }
8
10
  /**
9
11
  * Add or update models in the registry.
@@ -49,13 +49,13 @@ export class PricingRegistry {
49
49
  */
50
50
  static getPricing(modelId, provider) {
51
51
  // 1. Check custom overrides (Runtime/Remote)
52
- const key = `${provider}/${modelId}`;
52
+ const key = `${provider.toLowerCase()}/${modelId.toLowerCase()}`;
53
53
  if (this.pricingOverrides.has(key)) {
54
54
  return this.pricingOverrides.get(key);
55
55
  }
56
56
  // 2. Check library default patterns (Core Overrides)
57
57
  for (const entry of this.DEFAULT_PATTERNS) {
58
- if (entry.provider === provider && entry.pattern.test(modelId)) {
58
+ if (entry.provider.toLowerCase() === provider.toLowerCase() && entry.pattern.test(modelId)) {
59
59
  return entry.pricing;
60
60
  }
61
61
  }
@@ -67,7 +67,7 @@ export class PricingRegistry {
67
67
  * Register or override pricing at runtime.
68
68
  */
69
69
  static register(provider, modelId, pricing) {
70
- this.pricingOverrides.set(`${provider}/${modelId}`, pricing);
70
+ this.pricingOverrides.set(`${provider.toLowerCase()}/${modelId.toLowerCase()}`, pricing);
71
71
  }
72
72
  /**
73
73
  * Fetch updates from a remote URL.