@startsimpli/llm 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs DELETED
@@ -1,835 +0,0 @@
1
- import { ZodError } from 'zod';
2
-
3
- // src/errors.ts
4
- var LLMProviderError = class extends Error {
5
- constructor(message, code, retryable = false, statusCode, provider) {
6
- super(message);
7
- this.code = code;
8
- this.retryable = retryable;
9
- this.statusCode = statusCode;
10
- this.provider = provider;
11
- this.name = "LLMProviderError";
12
- }
13
- };
14
-
15
- // src/types.ts
16
- var LLM_DEFAULTS = {
17
- openaiModel: "gpt-4o-mini",
18
- anthropicModel: "claude-sonnet-4-20250514",
19
- /**
20
- * Default sampling temperature.
21
- * Note: base gpt-5 models (gpt-5, gpt-5.1, gpt-5.2) require temperature=1.0
22
- * and the OpenAI provider enforces this automatically.
23
- */
24
- temperature: 0.7,
25
- maxTokens: 8192,
26
- timeoutMs: 6e4
27
- };
28
-
29
- // src/providers/openai.ts
30
- var OPENAI_MODELS = [
31
- "gpt-5.2",
32
- "gpt-5.1",
33
- "gpt-5",
34
- "gpt-4o",
35
- "gpt-4o-mini"
36
- ];
37
- var MODEL_COSTS = {
38
- "gpt-5.2": { input: 5e-3, output: 0.015 },
39
- "gpt-5.1": { input: 5e-3, output: 0.015 },
40
- "gpt-5": { input: 5e-3, output: 0.015 },
41
- "gpt-4o": { input: 25e-4, output: 0.01 },
42
- "gpt-4o-mini": { input: 15e-5, output: 6e-4 }
43
- };
44
- var DEFAULTS = {
45
- baseUrl: "https://api.openai.com/v1",
46
- model: LLM_DEFAULTS.openaiModel,
47
- temperature: LLM_DEFAULTS.temperature,
48
- maxTokens: LLM_DEFAULTS.maxTokens,
49
- timeoutMs: LLM_DEFAULTS.timeoutMs
50
- };
51
- var OpenAIProvider = class {
52
- constructor(config) {
53
- this.name = "openai";
54
- this.availableModels = OPENAI_MODELS;
55
- this.config = {
56
- apiKey: config.apiKey,
57
- baseUrl: config.baseUrl ?? DEFAULTS.baseUrl,
58
- defaultModel: config.defaultModel ?? DEFAULTS.model,
59
- defaultTemperature: config.defaultTemperature ?? DEFAULTS.temperature,
60
- defaultMaxTokens: config.defaultMaxTokens ?? DEFAULTS.maxTokens,
61
- timeoutMs: config.timeoutMs ?? DEFAULTS.timeoutMs
62
- };
63
- this.defaultModel = this.config.defaultModel;
64
- }
65
- isAvailable() {
66
- return Boolean(this.config.apiKey);
67
- }
68
- async generate(userPrompt, systemPrompt, options) {
69
- if (!this.isAvailable()) {
70
- throw new LLMProviderError(
71
- "OpenAI API key not configured",
72
- "AUTHENTICATION_ERROR",
73
- false,
74
- void 0,
75
- this.name
76
- );
77
- }
78
- const model = options?.model ?? this.config.defaultModel;
79
- const isBaseGpt5 = model === "gpt-5" || model === "gpt-5.1" || model === "gpt-5.2";
80
- const isFineTuned = model.startsWith("ft:");
81
- const temperature = isBaseGpt5 && !isFineTuned ? 1 : options?.temperature ?? this.config.defaultTemperature;
82
- const maxTokens = options?.maxTokens ?? this.config.defaultMaxTokens;
83
- const timeout = options?.timeout ?? this.config.timeoutMs;
84
- const requestBody = {
85
- model,
86
- messages: [
87
- { role: "system", content: systemPrompt },
88
- { role: "user", content: userPrompt }
89
- ],
90
- temperature,
91
- max_tokens: maxTokens,
92
- response_format: { type: "json_object" }
93
- };
94
- const controller = new AbortController();
95
- const timeoutId = setTimeout(() => controller.abort(), timeout);
96
- try {
97
- const response = await fetch(`${this.config.baseUrl}/chat/completions`, {
98
- method: "POST",
99
- headers: {
100
- "Content-Type": "application/json",
101
- Authorization: `Bearer ${this.config.apiKey}`
102
- },
103
- body: JSON.stringify(requestBody),
104
- signal: controller.signal
105
- });
106
- clearTimeout(timeoutId);
107
- if (!response.ok) {
108
- const errorData = await response.json().catch(() => ({
109
- error: { message: "Unknown error" }
110
- }));
111
- throw this.handleApiError(response.status, errorData);
112
- }
113
- const data = await response.json();
114
- if (!data.choices?.[0]?.message?.content) {
115
- throw new LLMProviderError(
116
- "Empty response from OpenAI",
117
- "UNKNOWN_ERROR",
118
- true,
119
- void 0,
120
- this.name
121
- );
122
- }
123
- return {
124
- content: data.choices[0].message.content,
125
- usage: {
126
- promptTokens: data.usage.prompt_tokens,
127
- completionTokens: data.usage.completion_tokens,
128
- totalTokens: data.usage.total_tokens
129
- },
130
- model: data.model,
131
- responseId: data.id,
132
- finishReason: data.choices[0].finish_reason
133
- };
134
- } catch (error) {
135
- clearTimeout(timeoutId);
136
- if (error instanceof LLMProviderError) {
137
- throw error;
138
- }
139
- if (error instanceof Error && error.name === "AbortError") {
140
- throw new LLMProviderError(
141
- `Request timed out after ${timeout}ms`,
142
- "TIMEOUT",
143
- true,
144
- void 0,
145
- this.name
146
- );
147
- }
148
- throw new LLMProviderError(
149
- `OpenAI request failed: ${error instanceof Error ? error.message : "Unknown error"}`,
150
- "UNKNOWN_ERROR",
151
- true,
152
- void 0,
153
- this.name
154
- );
155
- }
156
- }
157
- estimateCost(promptTokens, completionTokens, model) {
158
- const costs = MODEL_COSTS[model ?? this.config.defaultModel];
159
- if (!costs) return void 0;
160
- return promptTokens / 1e3 * costs.input + completionTokens / 1e3 * costs.output;
161
- }
162
- handleApiError(statusCode, errorResponse) {
163
- const message = errorResponse.error?.message ?? "Unknown OpenAI error";
164
- const errorType = errorResponse.error?.type ?? "";
165
- const errorCode = errorResponse.error?.code ?? "";
166
- let code;
167
- let retryable = false;
168
- if (statusCode === 401) {
169
- code = "AUTHENTICATION_ERROR";
170
- } else if (statusCode === 429) {
171
- code = "RATE_LIMITED";
172
- retryable = true;
173
- } else if (statusCode === 400) {
174
- if (errorCode === "context_length_exceeded") {
175
- code = "CONTEXT_LENGTH_EXCEEDED";
176
- } else if (errorType === "invalid_request_error") {
177
- code = "INVALID_REQUEST";
178
- } else {
179
- code = "UNKNOWN_ERROR";
180
- }
181
- } else if (statusCode === 403) {
182
- code = errorCode === "content_filter" ? "CONTENT_FILTERED" : "AUTHENTICATION_ERROR";
183
- } else if (statusCode >= 500) {
184
- code = "SERVICE_UNAVAILABLE";
185
- retryable = true;
186
- } else {
187
- code = "UNKNOWN_ERROR";
188
- }
189
- return new LLMProviderError(message, code, retryable, statusCode, this.name);
190
- }
191
- };
192
- function createOpenAIProvider() {
193
- const apiKey = process.env.OPENAI_API_KEY;
194
- if (!apiKey) return null;
195
- return new OpenAIProvider({
196
- apiKey,
197
- baseUrl: process.env.OPENAI_BASE_URL,
198
- defaultModel: process.env.OPENAI_MODEL
199
- });
200
- }
201
-
202
- // src/providers/anthropic.ts
203
- var ANTHROPIC_MODELS = [
204
- "claude-opus-4-6",
205
- "claude-opus-4-20250514",
206
- "claude-sonnet-4-6",
207
- "claude-sonnet-4-20250514",
208
- "claude-3-5-sonnet-20241022",
209
- "claude-3-5-haiku-20241022",
210
- "claude-3-opus-20240229",
211
- "claude-3-sonnet-20240229",
212
- "claude-3-haiku-20240307"
213
- ];
214
- var MODEL_COSTS2 = {
215
- "claude-opus-4-6": { input: 0.015, output: 0.075 },
216
- "claude-opus-4-20250514": { input: 0.015, output: 0.075 },
217
- "claude-sonnet-4-6": { input: 3e-3, output: 0.015 },
218
- "claude-sonnet-4-20250514": { input: 3e-3, output: 0.015 },
219
- "claude-3-5-sonnet-20241022": { input: 3e-3, output: 0.015 },
220
- "claude-3-5-haiku-20241022": { input: 8e-4, output: 4e-3 },
221
- "claude-3-opus-20240229": { input: 0.015, output: 0.075 },
222
- "claude-3-sonnet-20240229": { input: 3e-3, output: 0.015 },
223
- "claude-3-haiku-20240307": { input: 25e-5, output: 125e-5 }
224
- };
225
- var DEFAULTS2 = {
226
- baseUrl: "https://api.anthropic.com/v1",
227
- model: LLM_DEFAULTS.anthropicModel,
228
- temperature: LLM_DEFAULTS.temperature,
229
- maxTokens: LLM_DEFAULTS.maxTokens,
230
- timeoutMs: LLM_DEFAULTS.timeoutMs,
231
- apiVersion: "2023-06-01"
232
- };
233
- var AnthropicProvider = class {
234
- constructor(config, apiVersion) {
235
- this.name = "anthropic";
236
- this.availableModels = ANTHROPIC_MODELS;
237
- this.config = {
238
- apiKey: config.apiKey,
239
- baseUrl: config.baseUrl ?? DEFAULTS2.baseUrl,
240
- defaultModel: config.defaultModel ?? DEFAULTS2.model,
241
- defaultTemperature: config.defaultTemperature ?? DEFAULTS2.temperature,
242
- defaultMaxTokens: config.defaultMaxTokens ?? DEFAULTS2.maxTokens,
243
- timeoutMs: config.timeoutMs ?? DEFAULTS2.timeoutMs
244
- };
245
- this.defaultModel = this.config.defaultModel;
246
- this.apiVersion = apiVersion ?? DEFAULTS2.apiVersion;
247
- }
248
- isAvailable() {
249
- return Boolean(this.config.apiKey);
250
- }
251
- async generate(userPrompt, systemPrompt, options) {
252
- if (!this.isAvailable()) {
253
- throw new LLMProviderError(
254
- "Anthropic API key not configured",
255
- "AUTHENTICATION_ERROR",
256
- false,
257
- void 0,
258
- this.name
259
- );
260
- }
261
- const model = options?.model ?? this.config.defaultModel;
262
- const temperature = options?.temperature ?? this.config.defaultTemperature;
263
- const maxTokens = options?.maxTokens ?? this.config.defaultMaxTokens;
264
- const timeout = options?.timeout ?? this.config.timeoutMs;
265
- const jsonWrappedPrompt = `${userPrompt}
266
-
267
- Please respond with a valid JSON object only. Do not include any text before or after the JSON.`;
268
- const requestBody = {
269
- model,
270
- max_tokens: maxTokens,
271
- messages: [{ role: "user", content: jsonWrappedPrompt }],
272
- system: systemPrompt,
273
- temperature
274
- };
275
- const controller = new AbortController();
276
- const timeoutId = setTimeout(() => controller.abort(), timeout);
277
- try {
278
- const response = await fetch(`${this.config.baseUrl}/messages`, {
279
- method: "POST",
280
- headers: {
281
- "Content-Type": "application/json",
282
- "x-api-key": this.config.apiKey,
283
- "anthropic-version": this.apiVersion
284
- },
285
- body: JSON.stringify(requestBody),
286
- signal: controller.signal
287
- });
288
- clearTimeout(timeoutId);
289
- if (!response.ok) {
290
- const errorData = await response.json().catch(() => ({
291
- error: { type: "unknown", message: "Unknown error" }
292
- }));
293
- throw this.handleApiError(response.status, errorData);
294
- }
295
- const data = await response.json();
296
- const textContent = data.content.find((c) => c.type === "text");
297
- if (!textContent?.text) {
298
- throw new LLMProviderError(
299
- "Empty response from Anthropic",
300
- "UNKNOWN_ERROR",
301
- true,
302
- void 0,
303
- this.name
304
- );
305
- }
306
- return {
307
- content: textContent.text,
308
- usage: {
309
- promptTokens: data.usage.input_tokens,
310
- completionTokens: data.usage.output_tokens,
311
- totalTokens: data.usage.input_tokens + data.usage.output_tokens
312
- },
313
- model: data.model,
314
- responseId: data.id,
315
- finishReason: data.stop_reason ?? void 0
316
- };
317
- } catch (error) {
318
- clearTimeout(timeoutId);
319
- if (error instanceof LLMProviderError) {
320
- throw error;
321
- }
322
- if (error instanceof Error && error.name === "AbortError") {
323
- throw new LLMProviderError(
324
- `Request timed out after ${timeout}ms`,
325
- "TIMEOUT",
326
- true,
327
- void 0,
328
- this.name
329
- );
330
- }
331
- throw new LLMProviderError(
332
- `Anthropic request failed: ${error instanceof Error ? error.message : "Unknown error"}`,
333
- "UNKNOWN_ERROR",
334
- true,
335
- void 0,
336
- this.name
337
- );
338
- }
339
- }
340
- estimateCost(promptTokens, completionTokens, model) {
341
- const costs = MODEL_COSTS2[model ?? this.config.defaultModel];
342
- if (!costs) return void 0;
343
- return promptTokens / 1e3 * costs.input + completionTokens / 1e3 * costs.output;
344
- }
345
- handleApiError(statusCode, errorResponse) {
346
- const message = errorResponse.error?.message ?? "Unknown Anthropic error";
347
- const errorType = errorResponse.error?.type ?? "";
348
- let code;
349
- let retryable = false;
350
- if (statusCode === 401) {
351
- code = "AUTHENTICATION_ERROR";
352
- } else if (statusCode === 429) {
353
- code = "RATE_LIMITED";
354
- retryable = true;
355
- } else if (statusCode === 400) {
356
- if (errorType === "invalid_request_error") {
357
- code = message.includes("token") || message.includes("length") ? "CONTEXT_LENGTH_EXCEEDED" : "INVALID_REQUEST";
358
- } else {
359
- code = "UNKNOWN_ERROR";
360
- }
361
- } else if (statusCode === 403) {
362
- code = "AUTHENTICATION_ERROR";
363
- } else if (statusCode === 529 || statusCode >= 500) {
364
- code = "SERVICE_UNAVAILABLE";
365
- retryable = true;
366
- } else {
367
- code = "UNKNOWN_ERROR";
368
- }
369
- return new LLMProviderError(message, code, retryable, statusCode, this.name);
370
- }
371
- };
372
- function createAnthropicProvider() {
373
- const apiKey = process.env.ANTHROPIC_API_KEY;
374
- if (!apiKey) return null;
375
- return new AnthropicProvider({
376
- apiKey,
377
- baseUrl: process.env.ANTHROPIC_BASE_URL,
378
- defaultModel: process.env.ANTHROPIC_MODEL
379
- });
380
- }
381
-
382
- // src/providers/mock.ts
383
- var mockBehavior = {};
384
- function setMockBehavior(behavior) {
385
- mockBehavior = behavior;
386
- }
387
- function resetMockBehavior() {
388
- mockBehavior = {};
389
- }
390
- function getDefaultResponse() {
391
- return JSON.stringify({ mock: true, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
392
- }
393
- function getInvalidResponse() {
394
- return JSON.stringify({ incomplete: true });
395
- }
396
- var MockLLMProvider = class {
397
- constructor() {
398
- this.name = "mock";
399
- this.availableModels = ["mock-model-1", "mock-model-2"];
400
- this.defaultModel = "mock-model-1";
401
- this.callCount = 0;
402
- }
403
- async generate(_userPrompt, _systemPrompt, _options) {
404
- this.callCount++;
405
- if (mockBehavior.error) {
406
- throw new LLMProviderError(
407
- mockBehavior.error.message,
408
- mockBehavior.error.code,
409
- mockBehavior.error.code === "RATE_LIMITED",
410
- mockBehavior.error.code === "RATE_LIMITED" ? 429 : 503,
411
- "mock"
412
- );
413
- }
414
- if (mockBehavior.delay) {
415
- await new Promise((resolve) => setTimeout(resolve, mockBehavior.delay));
416
- }
417
- if (mockBehavior.customResponse) {
418
- return {
419
- content: mockBehavior.customResponse,
420
- usage: { promptTokens: 100, completionTokens: 500, totalTokens: 600 },
421
- model: this.defaultModel,
422
- responseId: `mock-${this.callCount}`,
423
- finishReason: "stop"
424
- };
425
- }
426
- if (mockBehavior.returnInvalidJson) {
427
- return {
428
- content: getInvalidResponse(),
429
- usage: { promptTokens: 100, completionTokens: 200, totalTokens: 300 },
430
- model: this.defaultModel,
431
- responseId: `mock-${this.callCount}`,
432
- finishReason: "stop"
433
- };
434
- }
435
- return {
436
- content: getDefaultResponse(),
437
- usage: { promptTokens: 150, completionTokens: 800, totalTokens: 950 },
438
- model: this.defaultModel,
439
- responseId: `mock-${this.callCount}`,
440
- finishReason: "stop"
441
- };
442
- }
443
- isAvailable() {
444
- return true;
445
- }
446
- estimateCost(promptTokens, completionTokens, _model) {
447
- return (promptTokens + completionTokens) * 1e-5;
448
- }
449
- getCallCount() {
450
- return this.callCount;
451
- }
452
- resetCallCount() {
453
- this.callCount = 0;
454
- }
455
- };
456
-
457
- // src/utils/json-extraction.ts
458
- function extractJson(content) {
459
- const trimmed = content.trim();
460
- try {
461
- const json = JSON.parse(trimmed);
462
- return { success: true, json, raw: trimmed };
463
- } catch {
464
- }
465
- const codeBlockMatch = trimmed.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
466
- if (codeBlockMatch) {
467
- try {
468
- const json = JSON.parse(codeBlockMatch[1].trim());
469
- return { success: true, json, raw: codeBlockMatch[1].trim() };
470
- } catch (e) {
471
- return {
472
- success: false,
473
- error: `JSON in code block is invalid: ${e instanceof Error ? e.message : "Parse error"}`,
474
- raw: trimmed
475
- };
476
- }
477
- }
478
- const firstBrace = trimmed.indexOf("{");
479
- const lastBrace = trimmed.lastIndexOf("}");
480
- if (firstBrace !== -1 && lastBrace > firstBrace) {
481
- const jsonCandidate = trimmed.slice(firstBrace, lastBrace + 1);
482
- try {
483
- const json = JSON.parse(jsonCandidate);
484
- return { success: true, json, raw: jsonCandidate };
485
- } catch (e) {
486
- return {
487
- success: false,
488
- error: `Found JSON-like content but it's invalid: ${e instanceof Error ? e.message : "Parse error"}`,
489
- raw: trimmed
490
- };
491
- }
492
- }
493
- return {
494
- success: false,
495
- error: "No valid JSON object found in response",
496
- raw: trimmed
497
- };
498
- }
499
-
500
- // src/utils/http-status.ts
501
- function llmErrorToHttpStatus(code) {
502
- switch (code) {
503
- case "RATE_LIMITED":
504
- return 429;
505
- case "CONTENT_FILTERED":
506
- case "INVALID_REQUEST":
507
- return 400;
508
- case "AUTHENTICATION_ERROR":
509
- case "SERVICE_UNAVAILABLE":
510
- return 503;
511
- case "TIMEOUT":
512
- return 504;
513
- case "CONTEXT_LENGTH_EXCEEDED":
514
- return 422;
515
- default:
516
- return 500;
517
- }
518
- }
519
-
520
- // src/utils/retry.ts
521
- function buildSchemaErrorCorrectionPrompt(previousResponse, zodError) {
522
- const errorDetails = zodError.errors.map((e) => {
523
- const path = e.path.length > 0 ? e.path.join(".") : "root";
524
- return `- Path "${path}": ${e.message}`;
525
- }).join("\n");
526
- return `Your previous response contained JSON that did not conform to the required schema.
527
-
528
- ## Previous Response
529
-
530
- \`\`\`json
531
- ${previousResponse}
532
- \`\`\`
533
-
534
- ## Validation Errors
535
-
536
- The following schema validation errors were found:
537
-
538
- ${errorDetails}
539
-
540
- ## Instructions
541
-
542
- Please generate a corrected JSON response that:
543
- 1. Fixes ALL the validation errors listed above
544
- 2. Maintains the same content and intent
545
- 3. Follows the exact schema structure required
546
- 4. Contains ONLY valid JSON with no text before or after
547
-
548
- Common fixes needed:
549
- - Ensure all required fields are present
550
- - Check that enum values match the schema exactly
551
- - Verify number fields contain numbers, not strings
552
- - Ensure arrays are not empty where minimum length is 1
553
- - Check that nested objects have all required properties
554
-
555
- Respond with ONLY the corrected JSON object.`;
556
- }
557
- function buildParseErrorCorrectionPrompt(previousResponse, parseError) {
558
- const truncatedResponse = previousResponse.length > 2e3 ? previousResponse.slice(0, 2e3) + "\n... [truncated]" : previousResponse;
559
- return `Your previous response could not be parsed as valid JSON.
560
-
561
- ## Previous Response (excerpt)
562
-
563
- \`\`\`
564
- ${truncatedResponse}
565
- \`\`\`
566
-
567
- ## Parse Error
568
-
569
- ${parseError}
570
-
571
- ## Instructions
572
-
573
- Please generate a valid JSON response that:
574
- 1. Contains ONLY the JSON object - no markdown, no explanations
575
- 2. Does not wrap the JSON in code blocks
576
- 3. Uses proper JSON syntax (double quotes for strings, no trailing commas)
577
- 4. Properly escapes any special characters in strings
578
-
579
- Common JSON issues:
580
- - Using single quotes instead of double quotes
581
- - Trailing commas after the last item in arrays/objects
582
- - Unescaped special characters in strings (\\n, \\t, \\", etc.)
583
- - Missing commas between object properties
584
- - Extra text before or after the JSON
585
-
586
- Respond with ONLY the corrected JSON object.`;
587
- }
588
- function buildGenericRetryPrompt(originalPrompt, attemptNumber) {
589
- return `The previous attempt to generate a response encountered issues. This is attempt ${attemptNumber}.
590
-
591
- ## Original Request
592
-
593
- ${originalPrompt}
594
-
595
- ## Instructions
596
-
597
- Please generate a fresh response that:
598
- 1. Follows the exact JSON schema structure
599
- 2. Contains only valid, parseable JSON
600
- 3. Includes all required fields
601
- 4. Has accurate data throughout
602
-
603
- Focus on generating a complete, valid response rather than a complex one. A simpler response that is fully valid is better than a complex one with errors.
604
-
605
- Respond with ONLY the JSON object.`;
606
- }
607
- var DEFAULT_CONFIG = {
608
- maxRetries: 2,
609
- preferredProvider: "openai",
610
- defaultModel: "",
611
- defaultTemperature: LLM_DEFAULTS.temperature,
612
- defaultMaxTokens: LLM_DEFAULTS.maxTokens
613
- };
614
- var LLMService = class {
615
- constructor(config) {
616
- this.providers = /* @__PURE__ */ new Map();
617
- this.config = { ...DEFAULT_CONFIG, ...config };
618
- this.initializeProviders();
619
- }
620
- initializeProviders() {
621
- if (process.env.LLM_PROVIDER === "mock") {
622
- this.providers.set("mock", new MockLLMProvider());
623
- return;
624
- }
625
- const openai = createOpenAIProvider();
626
- if (openai) this.providers.set("openai", openai);
627
- const anthropic = createAnthropicProvider();
628
- if (anthropic) this.providers.set("anthropic", anthropic);
629
- }
630
- getProvider() {
631
- const preferred = this.providers.get(this.config.preferredProvider);
632
- if (preferred?.isAvailable()) return preferred;
633
- for (const provider of this.providers.values()) {
634
- if (provider.isAvailable()) return provider;
635
- }
636
- throw new LLMProviderError(
637
- "No LLM providers available. Please configure OPENAI_API_KEY or ANTHROPIC_API_KEY.",
638
- "AUTHENTICATION_ERROR",
639
- false
640
- );
641
- }
642
- /** Check if any LLM provider is available. */
643
- isAvailable() {
644
- for (const provider of this.providers.values()) {
645
- if (provider.isAvailable()) return true;
646
- }
647
- return false;
648
- }
649
- /** Get list of available provider names. */
650
- getAvailableProviders() {
651
- const available = [];
652
- for (const [name, provider] of this.providers.entries()) {
653
- if (provider.isAvailable()) available.push(name);
654
- }
655
- return available;
656
- }
657
- /**
658
- * Simple chat-style generation with no schema validation.
659
- * Returns the raw LLM response.
660
- */
661
- async chat(userPrompt, systemPrompt, options) {
662
- const provider = this.getProvider();
663
- const generateOptions = {
664
- temperature: options?.temperature ?? this.config.defaultTemperature,
665
- maxTokens: options?.maxTokens ?? this.config.defaultMaxTokens,
666
- ...options
667
- };
668
- return provider.generate(userPrompt, systemPrompt, generateOptions);
669
- }
670
- /**
671
- * Generate structured output validated against a Zod schema.
672
- *
673
- * @param userPrompt - User's request
674
- * @param systemPrompt - System instructions
675
- * @param schema - Zod schema to validate the parsed JSON against
676
- * @param options - Generation options
677
- */
678
- async generate(userPrompt, systemPrompt, schema, options) {
679
- const provider = this.getProvider();
680
- const genOptions = {
681
- model: this.config.defaultModel || void 0,
682
- temperature: this.config.defaultTemperature,
683
- maxTokens: this.config.defaultMaxTokens,
684
- ...options
685
- };
686
- let lastError;
687
- let lastResponse;
688
- let validationAttempts = 0;
689
- try {
690
- lastResponse = await provider.generate(userPrompt, systemPrompt, genOptions);
691
- validationAttempts++;
692
- const extraction = extractJson(lastResponse.content);
693
- if (!extraction.success) {
694
- const result = await this.retryWithCorrection(
695
- provider,
696
- userPrompt,
697
- systemPrompt,
698
- lastResponse,
699
- "parse",
700
- extraction.error,
701
- schema,
702
- genOptions,
703
- validationAttempts
704
- );
705
- if (result.data) {
706
- return this.buildSuccess(result.data, result.response ?? lastResponse, result.attempts);
707
- }
708
- lastError = new Error(extraction.error);
709
- validationAttempts = result.attempts;
710
- } else {
711
- const validation = this.validate(extraction.json, schema);
712
- if (validation.valid && validation.data) {
713
- return this.buildSuccess(validation.data, lastResponse, validationAttempts);
714
- }
715
- const result = await this.retryWithCorrection(
716
- provider,
717
- userPrompt,
718
- systemPrompt,
719
- lastResponse,
720
- "schema",
721
- validation.zodError,
722
- schema,
723
- genOptions,
724
- validationAttempts
725
- );
726
- if (result.data) {
727
- return this.buildSuccess(result.data, result.response ?? lastResponse, result.attempts);
728
- }
729
- lastError = validation.zodError;
730
- validationAttempts = result.attempts;
731
- }
732
- } catch (error) {
733
- lastError = error instanceof Error ? error : new Error(String(error));
734
- }
735
- return this.buildError(lastError ?? new Error("Unknown error"), lastResponse, validationAttempts);
736
- }
737
- /**
738
- * Register an additional provider instance (useful for custom/third-party providers).
739
- */
740
- registerProvider(name, provider) {
741
- this.providers.set(name, provider);
742
- }
743
- // ---------------------------------------------------------------------------
744
- // Private helpers
745
- // ---------------------------------------------------------------------------
746
- async retryWithCorrection(provider, originalPrompt, systemPrompt, previousResponse, errorType, error, schema, options, currentAttempts) {
747
- let attempts = currentAttempts;
748
- for (let retry = 0; retry < this.config.maxRetries; retry++) {
749
- attempts++;
750
- try {
751
- const correctionPrompt = errorType === "parse" ? buildParseErrorCorrectionPrompt(previousResponse.content, error) : buildSchemaErrorCorrectionPrompt(previousResponse.content, error);
752
- const response = await provider.generate(correctionPrompt, systemPrompt, options);
753
- const extraction = extractJson(response.content);
754
- if (!extraction.success) {
755
- if (retry === this.config.maxRetries - 1) {
756
- const genericPrompt = buildGenericRetryPrompt(originalPrompt, attempts);
757
- const finalResponse = await provider.generate(genericPrompt, systemPrompt, options);
758
- attempts++;
759
- const finalExtraction = extractJson(finalResponse.content);
760
- if (finalExtraction.success) {
761
- const validation2 = this.validate(finalExtraction.json, schema);
762
- if (validation2.valid && validation2.data) {
763
- return { data: validation2.data, response: finalResponse, attempts };
764
- }
765
- }
766
- }
767
- continue;
768
- }
769
- const validation = this.validate(extraction.json, schema);
770
- if (validation.valid && validation.data) {
771
- return { data: validation.data, response, attempts };
772
- }
773
- if (validation.zodError) {
774
- previousResponse = response;
775
- error = validation.zodError;
776
- errorType = "schema";
777
- }
778
- } catch {
779
- }
780
- }
781
- return { attempts };
782
- }
783
- validate(data, schema) {
784
- const result = schema.safeParse(data);
785
- if (result.success) {
786
- return { valid: true, data: result.data };
787
- }
788
- return {
789
- valid: false,
790
- zodError: result.error,
791
- errors: result.error.errors.map((e) => `${e.path.join(".")}: ${e.message}`)
792
- };
793
- }
794
- buildSuccess(data, response, validationAttempts) {
795
- return {
796
- success: true,
797
- data,
798
- metadata: {
799
- model: response.model,
800
- promptTokens: response.usage.promptTokens,
801
- completionTokens: response.usage.completionTokens,
802
- validationAttempts
803
- }
804
- };
805
- }
806
- buildError(error, response, validationAttempts) {
807
- const isProviderError = error instanceof LLMProviderError;
808
- let code = "GENERATION_FAILED";
809
- let retryable = false;
810
- if (isProviderError) {
811
- code = error.code;
812
- retryable = error.retryable;
813
- } else if (error instanceof ZodError) {
814
- code = "VALIDATION_FAILED";
815
- retryable = true;
816
- }
817
- const result = {
818
- success: false,
819
- error: { code, message: error.message, retryable }
820
- };
821
- if (response) {
822
- result.metadata = {
823
- model: response.model,
824
- promptTokens: response.usage.promptTokens,
825
- completionTokens: response.usage.completionTokens,
826
- validationAttempts: validationAttempts ?? 1
827
- };
828
- }
829
- return result;
830
- }
831
- };
832
-
833
- export { AnthropicProvider, LLMProviderError, LLMService, LLM_DEFAULTS, MockLLMProvider, OpenAIProvider, buildGenericRetryPrompt, buildParseErrorCorrectionPrompt, buildSchemaErrorCorrectionPrompt, createAnthropicProvider, createOpenAIProvider, extractJson, llmErrorToHttpStatus, resetMockBehavior, setMockBehavior };
834
- //# sourceMappingURL=index.mjs.map
835
- //# sourceMappingURL=index.mjs.map