@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.
- package/README.md +35 -0
- package/dist/aliases.d.ts +105 -9
- package/dist/aliases.d.ts.map +1 -1
- package/dist/aliases.js +105 -9
- package/dist/chat/Chat.d.ts +8 -3
- package/dist/chat/Chat.d.ts.map +1 -1
- package/dist/chat/Chat.js +181 -131
- package/dist/chat/ChatOptions.d.ts +2 -0
- package/dist/chat/ChatOptions.d.ts.map +1 -1
- package/dist/chat/ChatResponse.d.ts +24 -3
- package/dist/chat/ChatResponse.d.ts.map +1 -1
- package/dist/chat/ChatResponse.js +72 -5
- package/dist/chat/ChatStream.d.ts.map +1 -1
- package/dist/chat/ChatStream.js +111 -56
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +9 -7
- package/dist/constants.d.ts +6 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +6 -0
- package/dist/errors/index.d.ts +20 -2
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/index.js +31 -3
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/llm.d.ts +8 -1
- package/dist/llm.d.ts.map +1 -1
- package/dist/llm.js +156 -59
- package/dist/middlewares/CostGuardMiddleware.d.ts +24 -0
- package/dist/middlewares/CostGuardMiddleware.d.ts.map +1 -0
- package/dist/middlewares/CostGuardMiddleware.js +23 -0
- package/dist/middlewares/PIIMaskMiddleware.d.ts +23 -0
- package/dist/middlewares/PIIMaskMiddleware.d.ts.map +1 -0
- package/dist/middlewares/PIIMaskMiddleware.js +41 -0
- package/dist/middlewares/UsageLoggerMiddleware.d.ts +22 -0
- package/dist/middlewares/UsageLoggerMiddleware.d.ts.map +1 -0
- package/dist/middlewares/UsageLoggerMiddleware.js +30 -0
- package/dist/middlewares/index.d.ts +4 -0
- package/dist/middlewares/index.d.ts.map +1 -0
- package/dist/middlewares/index.js +3 -0
- package/dist/models/ModelRegistry.d.ts.map +1 -1
- package/dist/models/ModelRegistry.js +4 -2
- package/dist/models/PricingRegistry.js +3 -3
- package/dist/models/{models.js → models.json} +9934 -8196
- package/dist/providers/BaseProvider.d.ts +6 -1
- package/dist/providers/BaseProvider.d.ts.map +1 -1
- package/dist/providers/BaseProvider.js +19 -0
- package/dist/providers/anthropic/AnthropicProvider.d.ts.map +1 -1
- package/dist/providers/anthropic/AnthropicProvider.js +2 -1
- package/dist/providers/anthropic/Chat.d.ts.map +1 -1
- package/dist/providers/anthropic/Chat.js +2 -1
- package/dist/providers/anthropic/Errors.d.ts.map +1 -1
- package/dist/providers/anthropic/Errors.js +15 -1
- package/dist/providers/anthropic/Streaming.d.ts.map +1 -1
- package/dist/providers/anthropic/Streaming.js +19 -3
- package/dist/providers/bedrock/Chat.d.ts.map +1 -1
- package/dist/providers/bedrock/Chat.js +2 -20
- package/dist/providers/bedrock/Errors.d.ts +2 -0
- package/dist/providers/bedrock/Errors.d.ts.map +1 -0
- package/dist/providers/bedrock/Errors.js +51 -0
- package/dist/providers/bedrock/Streaming.d.ts.map +1 -1
- package/dist/providers/bedrock/Streaming.js +2 -3
- package/dist/providers/deepseek/Chat.d.ts.map +1 -1
- package/dist/providers/deepseek/Chat.js +2 -2
- package/dist/providers/deepseek/DeepSeekProvider.d.ts.map +1 -1
- package/dist/providers/deepseek/DeepSeekProvider.js +2 -1
- package/dist/providers/deepseek/Errors.d.ts +2 -0
- package/dist/providers/deepseek/Errors.d.ts.map +1 -0
- package/dist/providers/deepseek/Errors.js +45 -0
- package/dist/providers/deepseek/Streaming.d.ts.map +1 -1
- package/dist/providers/deepseek/Streaming.js +13 -2
- package/dist/providers/gemini/Errors.d.ts.map +1 -1
- package/dist/providers/gemini/Errors.js +13 -1
- package/dist/providers/gemini/GeminiProvider.d.ts.map +1 -1
- package/dist/providers/gemini/GeminiProvider.js +2 -1
- package/dist/providers/ollama/OllamaProvider.d.ts.map +1 -1
- package/dist/providers/ollama/OllamaProvider.js +2 -2
- package/dist/providers/openai/Errors.d.ts.map +1 -1
- package/dist/providers/openai/Errors.js +31 -5
- package/dist/providers/openai/OpenAIProvider.d.ts +1 -1
- package/dist/providers/openai/OpenAIProvider.d.ts.map +1 -1
- package/dist/providers/openai/OpenAIProvider.js +15 -3
- package/dist/providers/openai/Streaming.d.ts.map +1 -1
- package/dist/providers/openai/Streaming.js +10 -0
- package/dist/providers/openrouter/OpenRouterProvider.d.ts.map +1 -1
- package/dist/providers/openrouter/OpenRouterProvider.js +2 -1
- package/dist/providers/registry.d.ts +3 -0
- package/dist/providers/registry.d.ts.map +1 -1
- package/dist/providers/registry.js +10 -2
- package/dist/types/Middleware.d.ts +98 -0
- package/dist/types/Middleware.d.ts.map +1 -0
- package/dist/types/Middleware.js +1 -0
- package/dist/utils/json.d.ts +6 -0
- package/dist/utils/json.d.ts.map +1 -0
- package/dist/utils/json.js +43 -0
- package/dist/utils/middleware-runner.d.ts +7 -0
- package/dist/utils/middleware-runner.d.ts.map +1 -0
- package/dist/utils/middleware-runner.js +20 -0
- package/package.json +1 -1
- package/dist/models/models.d.ts +0 -572
- 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
|
|
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
|
-
|
|
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
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
128
|
-
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
148
|
-
|
|
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
|
-
|
|
151
|
-
|
|
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
|
|
166
|
-
|
|
240
|
+
const requestId = randomUUID();
|
|
241
|
+
const state = {};
|
|
242
|
+
const context = {
|
|
243
|
+
requestId,
|
|
244
|
+
provider: provider.id,
|
|
167
245
|
model,
|
|
168
|
-
|
|
169
|
-
|
|
246
|
+
input,
|
|
247
|
+
embeddingOptions: options,
|
|
248
|
+
state
|
|
170
249
|
};
|
|
171
|
-
|
|
172
|
-
|
|
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
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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 @@
|
|
|
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"}
|
|
@@ -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;
|
|
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
|
-
|
|
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() &&
|
|
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.
|