risicare 0.2.1 → 0.3.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 (74) hide show
  1. package/README.md +131 -45
  2. package/dist/frameworks/instructor.cjs +45 -17
  3. package/dist/frameworks/instructor.cjs.map +1 -1
  4. package/dist/frameworks/instructor.js +47 -17
  5. package/dist/frameworks/instructor.js.map +1 -1
  6. package/dist/frameworks/langchain.cjs +73 -6
  7. package/dist/frameworks/langchain.cjs.map +1 -1
  8. package/dist/frameworks/langchain.d.cts +20 -4
  9. package/dist/frameworks/langchain.d.ts +20 -4
  10. package/dist/frameworks/langchain.js +75 -6
  11. package/dist/frameworks/langchain.js.map +1 -1
  12. package/dist/frameworks/langgraph.cjs +73 -6
  13. package/dist/frameworks/langgraph.cjs.map +1 -1
  14. package/dist/frameworks/langgraph.js +75 -6
  15. package/dist/frameworks/langgraph.js.map +1 -1
  16. package/dist/frameworks/llamaindex.cjs +41 -14
  17. package/dist/frameworks/llamaindex.cjs.map +1 -1
  18. package/dist/frameworks/llamaindex.js +43 -14
  19. package/dist/frameworks/llamaindex.js.map +1 -1
  20. package/dist/index.cjs +1494 -67
  21. package/dist/index.cjs.map +1 -1
  22. package/dist/index.d.cts +436 -1
  23. package/dist/index.d.ts +436 -1
  24. package/dist/index.js +1515 -67
  25. package/dist/index.js.map +1 -1
  26. package/dist/providers/anthropic/index.cjs +74 -24
  27. package/dist/providers/anthropic/index.cjs.map +1 -1
  28. package/dist/providers/anthropic/index.js +76 -24
  29. package/dist/providers/anthropic/index.js.map +1 -1
  30. package/dist/providers/bedrock/index.cjs +81 -24
  31. package/dist/providers/bedrock/index.cjs.map +1 -1
  32. package/dist/providers/bedrock/index.js +83 -24
  33. package/dist/providers/bedrock/index.js.map +1 -1
  34. package/dist/providers/cerebras/index.cjs +78 -25
  35. package/dist/providers/cerebras/index.cjs.map +1 -1
  36. package/dist/providers/cerebras/index.js +80 -25
  37. package/dist/providers/cerebras/index.js.map +1 -1
  38. package/dist/providers/cohere/index.cjs +95 -25
  39. package/dist/providers/cohere/index.cjs.map +1 -1
  40. package/dist/providers/cohere/index.js +97 -25
  41. package/dist/providers/cohere/index.js.map +1 -1
  42. package/dist/providers/google/index.cjs +77 -25
  43. package/dist/providers/google/index.cjs.map +1 -1
  44. package/dist/providers/google/index.js +79 -25
  45. package/dist/providers/google/index.js.map +1 -1
  46. package/dist/providers/groq/index.cjs +80 -25
  47. package/dist/providers/groq/index.cjs.map +1 -1
  48. package/dist/providers/groq/index.js +82 -25
  49. package/dist/providers/groq/index.js.map +1 -1
  50. package/dist/providers/huggingface/index.cjs +80 -25
  51. package/dist/providers/huggingface/index.cjs.map +1 -1
  52. package/dist/providers/huggingface/index.js +82 -25
  53. package/dist/providers/huggingface/index.js.map +1 -1
  54. package/dist/providers/mistral/index.cjs +72 -24
  55. package/dist/providers/mistral/index.cjs.map +1 -1
  56. package/dist/providers/mistral/index.js +74 -24
  57. package/dist/providers/mistral/index.js.map +1 -1
  58. package/dist/providers/ollama/index.cjs +83 -25
  59. package/dist/providers/ollama/index.cjs.map +1 -1
  60. package/dist/providers/ollama/index.js +85 -25
  61. package/dist/providers/ollama/index.js.map +1 -1
  62. package/dist/providers/openai/index.cjs +1429 -28
  63. package/dist/providers/openai/index.cjs.map +1 -1
  64. package/dist/providers/openai/index.js +1447 -28
  65. package/dist/providers/openai/index.js.map +1 -1
  66. package/dist/providers/together/index.cjs +80 -25
  67. package/dist/providers/together/index.cjs.map +1 -1
  68. package/dist/providers/together/index.js +82 -25
  69. package/dist/providers/together/index.js.map +1 -1
  70. package/dist/providers/vercel-ai/index.cjs +45 -17
  71. package/dist/providers/vercel-ai/index.cjs.map +1 -1
  72. package/dist/providers/vercel-ai/index.js +47 -17
  73. package/dist/providers/vercel-ai/index.js.map +1 -1
  74. package/package.json +17 -7
@@ -1,3 +1,1386 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __esm = (fn, res) => function __init() {
6
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
7
+ };
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
21
+
22
+ // src/globals.ts
23
+ import { AsyncLocalStorage } from "async_hooks";
24
+ function getTracer() {
25
+ return G[PREFIX + "tracer"];
26
+ }
27
+ function getContextStorage() {
28
+ if (!G[PREFIX + "ctx"]) {
29
+ G[PREFIX + "ctx"] = new AsyncLocalStorage();
30
+ }
31
+ return G[PREFIX + "ctx"];
32
+ }
33
+ function getDebug() {
34
+ return G[PREFIX + "debug"] ?? false;
35
+ }
36
+ var G, PREFIX;
37
+ var init_globals = __esm({
38
+ "src/globals.ts"() {
39
+ "use strict";
40
+ G = globalThis;
41
+ PREFIX = "__risicare_";
42
+ }
43
+ });
44
+
45
+ // src/utils/log.ts
46
+ function debug(msg) {
47
+ if (getDebug()) {
48
+ process.stderr.write(`[risicare] ${msg}
49
+ `);
50
+ }
51
+ }
52
+ function warn(msg) {
53
+ process.stderr.write(`[risicare] WARNING: ${msg}
54
+ `);
55
+ }
56
+ var init_log = __esm({
57
+ "src/utils/log.ts"() {
58
+ "use strict";
59
+ init_globals();
60
+ }
61
+ });
62
+
63
+ // src/runtime/config.ts
64
+ function resolveFixRuntimeConfig(config) {
65
+ return {
66
+ apiEndpoint: config.apiEndpoint,
67
+ apiKey: config.apiKey,
68
+ enabled: config.enabled ?? true,
69
+ cacheEnabled: config.cacheEnabled ?? true,
70
+ cacheTtlMs: config.cacheTtlMs ?? 3e5,
71
+ cacheMaxEntries: config.cacheMaxEntries ?? 1e3,
72
+ autoRefresh: config.autoRefresh ?? true,
73
+ refreshIntervalMs: config.refreshIntervalMs ?? 6e4,
74
+ dryRun: config.dryRun ?? false,
75
+ abTestingEnabled: config.abTestingEnabled ?? true,
76
+ timeoutMs: config.timeoutMs ?? 1e3,
77
+ debug: config.debug ?? false
78
+ };
79
+ }
80
+ function matchesError(fix, errorCode) {
81
+ if (fix.errorCode === errorCode) {
82
+ return true;
83
+ }
84
+ if (fix.errorCode.endsWith("*")) {
85
+ const prefix = fix.errorCode.slice(0, -1);
86
+ return errorCode.startsWith(prefix);
87
+ }
88
+ return false;
89
+ }
90
+ function shouldApply(fix, sessionHash) {
91
+ if (fix.trafficPercentage >= 100) return true;
92
+ if (fix.trafficPercentage <= 0) return false;
93
+ const bucket = sessionHash % 100;
94
+ return bucket < fix.trafficPercentage;
95
+ }
96
+ function activeFixFromApiResponse(item) {
97
+ return {
98
+ fixId: item.id ?? item.fix_id ?? item.fixId ?? "",
99
+ deploymentId: item.deployment_id ?? item.deploymentId ?? "",
100
+ errorCode: item.error_code ?? item.errorCode ?? "",
101
+ fixType: item.fix_type ?? item.fixType ?? "prompt",
102
+ config: item.config ?? {},
103
+ trafficPercentage: item.traffic_percentage ?? item.trafficPercentage ?? 100,
104
+ version: item.version ?? 1
105
+ };
106
+ }
107
+ var init_config = __esm({
108
+ "src/runtime/config.ts"() {
109
+ "use strict";
110
+ }
111
+ });
112
+
113
+ // src/runtime/cache.ts
114
+ var FixCache;
115
+ var init_cache = __esm({
116
+ "src/runtime/cache.ts"() {
117
+ "use strict";
118
+ init_config();
119
+ init_log();
120
+ FixCache = class {
121
+ _ttlMs;
122
+ _maxEntries;
123
+ _enabled;
124
+ _cache;
125
+ _stats;
126
+ constructor(config) {
127
+ this._enabled = config?.cacheEnabled ?? true;
128
+ this._ttlMs = config?.cacheTtlMs ?? 3e5;
129
+ this._maxEntries = config?.cacheMaxEntries ?? 1e3;
130
+ this._cache = /* @__PURE__ */ new Map();
131
+ this._stats = {
132
+ hits: 0,
133
+ misses: 0,
134
+ evictions: 0,
135
+ size: 0,
136
+ lastRefresh: null
137
+ };
138
+ }
139
+ /** Whether caching is enabled. */
140
+ get enabled() {
141
+ return this._enabled;
142
+ }
143
+ /**
144
+ * Get a fix by error code.
145
+ *
146
+ * Checks exact match first, then scans for wildcard matches.
147
+ * Expired entries are evicted on access.
148
+ */
149
+ get(errorCode) {
150
+ try {
151
+ if (!this._enabled) return null;
152
+ const now = Date.now();
153
+ const exactEntry = this._cache.get(errorCode);
154
+ if (exactEntry) {
155
+ if (exactEntry.expiresAt > now) {
156
+ this._stats.hits++;
157
+ return exactEntry.fix;
158
+ }
159
+ this._cache.delete(errorCode);
160
+ this._stats.evictions++;
161
+ }
162
+ for (const [key, entry] of this._cache) {
163
+ if (entry.expiresAt <= now) {
164
+ this._cache.delete(key);
165
+ this._stats.evictions++;
166
+ continue;
167
+ }
168
+ if (matchesError(entry.fix, errorCode)) {
169
+ this._stats.hits++;
170
+ return entry.fix;
171
+ }
172
+ }
173
+ this._stats.misses++;
174
+ return null;
175
+ } catch (e) {
176
+ debug(`FixCache.get error: ${e}`);
177
+ return null;
178
+ }
179
+ }
180
+ /**
181
+ * Add a single fix to the cache.
182
+ *
183
+ * Evicts the oldest entry if at capacity.
184
+ */
185
+ set(fix) {
186
+ try {
187
+ if (!this._enabled) return;
188
+ if (this._cache.size >= this._maxEntries) {
189
+ this._evictOldest();
190
+ }
191
+ const now = Date.now();
192
+ this._cache.set(fix.errorCode, {
193
+ fix,
194
+ createdAt: now,
195
+ expiresAt: now + this._ttlMs
196
+ });
197
+ this._stats.size = this._cache.size;
198
+ } catch (e) {
199
+ debug(`FixCache.set error: ${e}`);
200
+ }
201
+ }
202
+ /**
203
+ * Replace all cached fixes (bulk refresh).
204
+ *
205
+ * Clears the cache and populates with the given fixes.
206
+ */
207
+ setAll(fixes) {
208
+ try {
209
+ if (!this._enabled) return;
210
+ this._cache.clear();
211
+ const now = Date.now();
212
+ for (const fix of fixes) {
213
+ if (this._cache.size >= this._maxEntries) break;
214
+ this._cache.set(fix.errorCode, {
215
+ fix,
216
+ createdAt: now,
217
+ expiresAt: now + this._ttlMs
218
+ });
219
+ }
220
+ this._stats.size = this._cache.size;
221
+ this._stats.lastRefresh = now;
222
+ } catch (e) {
223
+ debug(`FixCache.setAll error: ${e}`);
224
+ }
225
+ }
226
+ /**
227
+ * Get all non-expired fixes.
228
+ */
229
+ getAll() {
230
+ try {
231
+ const now = Date.now();
232
+ const valid = [];
233
+ const expired = [];
234
+ for (const [key, entry] of this._cache) {
235
+ if (entry.expiresAt > now) {
236
+ valid.push(entry.fix);
237
+ } else {
238
+ expired.push(key);
239
+ }
240
+ }
241
+ for (const key of expired) {
242
+ this._cache.delete(key);
243
+ this._stats.evictions++;
244
+ }
245
+ this._stats.size = this._cache.size;
246
+ return valid;
247
+ } catch (e) {
248
+ debug(`FixCache.getAll error: ${e}`);
249
+ return [];
250
+ }
251
+ }
252
+ /** Clear all cache entries. Returns the number of entries cleared. */
253
+ clear() {
254
+ const count = this._cache.size;
255
+ this._cache.clear();
256
+ this._stats.size = 0;
257
+ return count;
258
+ }
259
+ /** Get cache statistics. */
260
+ get stats() {
261
+ this._stats.size = this._cache.size;
262
+ return { ...this._stats };
263
+ }
264
+ /** Evict the oldest cache entry. */
265
+ _evictOldest() {
266
+ if (this._cache.size === 0) return;
267
+ let oldestKey = null;
268
+ let oldestTime = Infinity;
269
+ for (const [key, entry] of this._cache) {
270
+ if (entry.createdAt < oldestTime) {
271
+ oldestTime = entry.createdAt;
272
+ oldestKey = key;
273
+ }
274
+ }
275
+ if (oldestKey !== null) {
276
+ this._cache.delete(oldestKey);
277
+ this._stats.evictions++;
278
+ }
279
+ }
280
+ };
281
+ }
282
+ });
283
+
284
+ // src/runtime/applier.ts
285
+ import { createHash } from "crypto";
286
+ function emptyResult(fix, fixType) {
287
+ return {
288
+ applied: false,
289
+ fixId: fix.fixId,
290
+ deploymentId: fix.deploymentId,
291
+ fixType,
292
+ modifications: {}
293
+ };
294
+ }
295
+ var MAX_FIX_CONTENT_LENGTH, FixApplier;
296
+ var init_applier = __esm({
297
+ "src/runtime/applier.ts"() {
298
+ "use strict";
299
+ init_config();
300
+ init_log();
301
+ MAX_FIX_CONTENT_LENGTH = 1e4;
302
+ FixApplier = class {
303
+ _config;
304
+ _cache;
305
+ _applicationLog = [];
306
+ constructor(config, cache) {
307
+ this._config = resolveFixRuntimeConfig(config);
308
+ this._cache = cache;
309
+ }
310
+ // ═══════════════════════════════════════════════════════════════════════════
311
+ // Fix Lookup
312
+ // ═══════════════════════════════════════════════════════════════════════════
313
+ /**
314
+ * Get applicable fix for an error code.
315
+ *
316
+ * Considers A/B testing bucket if sessionId is provided. Uses
317
+ * crypto.createHash('md5') for deterministic bucketing across restarts.
318
+ */
319
+ getFixForError(errorCode, sessionId) {
320
+ try {
321
+ const fix = this._cache.get(errorCode);
322
+ if (!fix) return null;
323
+ if (sessionId && this._config.abTestingEnabled) {
324
+ const hashHex = createHash("md5").update(sessionId).digest("hex").substring(0, 8);
325
+ const sessionHash = parseInt(hashHex, 16);
326
+ if (!shouldApply(fix, sessionHash)) {
327
+ debug(`Fix ${fix.fixId} not applied (A/B control group)`);
328
+ return null;
329
+ }
330
+ }
331
+ return fix;
332
+ } catch (e) {
333
+ debug(`getFixForError error: ${e}`);
334
+ return null;
335
+ }
336
+ }
337
+ // ═══════════════════════════════════════════════════════════════════════════
338
+ // 1. Prompt Fix
339
+ // ═══════════════════════════════════════════════════════════════════════════
340
+ /**
341
+ * Apply a prompt fix to messages.
342
+ *
343
+ * Supports 4 modification types:
344
+ * - prepend: Add content before first system message
345
+ * - append: Add content after first system message
346
+ * - replace: Regex replace across all messages
347
+ * - few_shot: Insert user/assistant example pairs after system message
348
+ */
349
+ applyPromptFix(fix, messages) {
350
+ try {
351
+ const cfg = fix.config;
352
+ const modificationType = cfg.modification_type ?? cfg.modificationType ?? "append";
353
+ const target = cfg.target ?? "system";
354
+ let content = cfg.content ?? "";
355
+ if (content.length > MAX_FIX_CONTENT_LENGTH) {
356
+ warn(
357
+ `Fix ${fix.fixId} content truncated from ${content.length} to ${MAX_FIX_CONTENT_LENGTH} chars`
358
+ );
359
+ content = content.slice(0, MAX_FIX_CONTENT_LENGTH);
360
+ }
361
+ const result = emptyResult(fix, "prompt");
362
+ if (this._config.dryRun) {
363
+ result.modifications = {
364
+ type: modificationType,
365
+ target,
366
+ contentPreview: content.slice(0, 100)
367
+ };
368
+ return { messages, result };
369
+ }
370
+ let modified = messages.map((m) => ({ ...m }));
371
+ if (modificationType === "prepend") {
372
+ modified = this._prependToTarget(modified, target, content);
373
+ result.applied = true;
374
+ result.modifications = { type: "prepend", target };
375
+ } else if (modificationType === "append") {
376
+ modified = this._appendToTarget(modified, target, content);
377
+ result.applied = true;
378
+ result.modifications = { type: "append", target };
379
+ } else if (modificationType === "replace") {
380
+ const pattern = cfg.pattern ?? "";
381
+ if (pattern) {
382
+ modified = this._replaceInMessages(modified, pattern, content);
383
+ result.applied = true;
384
+ result.modifications = { type: "replace", pattern };
385
+ }
386
+ } else if (modificationType === "few_shot") {
387
+ const examples = cfg.examples ?? [];
388
+ if (examples.length > 0) {
389
+ modified = this._addFewShotExamples(modified, examples);
390
+ result.applied = true;
391
+ result.modifications = { type: "few_shot", count: examples.length };
392
+ }
393
+ }
394
+ return { messages: modified, result };
395
+ } catch (e) {
396
+ debug(`applyPromptFix error: ${e}`);
397
+ return {
398
+ messages,
399
+ result: {
400
+ applied: false,
401
+ fixId: fix.fixId,
402
+ fixType: "prompt",
403
+ modifications: {},
404
+ error: String(e)
405
+ }
406
+ };
407
+ }
408
+ }
409
+ // ═══════════════════════════════════════════════════════════════════════════
410
+ // 2. Parameter Fix
411
+ // ═══════════════════════════════════════════════════════════════════════════
412
+ /**
413
+ * Apply a parameter fix — merge config.parameters into params.
414
+ */
415
+ applyParameterFix(fix, params) {
416
+ try {
417
+ const newParams = fix.config.parameters ?? {};
418
+ const result = emptyResult(fix, "parameter");
419
+ if (this._config.dryRun) {
420
+ result.modifications = { parameters: newParams };
421
+ return { params, result };
422
+ }
423
+ const modified = { ...params, ...newParams };
424
+ result.applied = true;
425
+ result.modifications = { parameters: newParams };
426
+ return { params: modified, result };
427
+ } catch (e) {
428
+ debug(`applyParameterFix error: ${e}`);
429
+ return {
430
+ params,
431
+ result: {
432
+ applied: false,
433
+ fixId: fix.fixId,
434
+ fixType: "parameter",
435
+ modifications: {},
436
+ error: String(e)
437
+ }
438
+ };
439
+ }
440
+ }
441
+ // ═══════════════════════════════════════════════════════════════════════════
442
+ // 3. Retry Fix (async — uses setTimeout for sleeping)
443
+ // ═══════════════════════════════════════════════════════════════════════════
444
+ /**
445
+ * Apply a retry fix to an async operation.
446
+ *
447
+ * Retries with exponential backoff + jitter using
448
+ * `await new Promise(r => setTimeout(r, delay))`.
449
+ */
450
+ async applyRetryFix(fix, operation) {
451
+ const cfg = fix.config;
452
+ const maxRetries = cfg.max_retries ?? cfg.maxRetries ?? 3;
453
+ const initialDelayMs = cfg.initial_delay_ms ?? cfg.initialDelayMs ?? 1e3;
454
+ const maxDelayMs = cfg.max_delay_ms ?? cfg.maxDelayMs ?? 3e4;
455
+ const exponentialBase = cfg.exponential_base ?? cfg.exponentialBase ?? 2;
456
+ const jitter = cfg.jitter ?? true;
457
+ const retryOn = cfg.retry_on ?? cfg.retryOn ?? [];
458
+ const result = emptyResult(fix, "retry");
459
+ if (this._config.dryRun) {
460
+ result.modifications = { maxRetries, initialDelayMs };
461
+ const value = await operation();
462
+ return { value, result };
463
+ }
464
+ let lastError = null;
465
+ let attempts = 0;
466
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
467
+ attempts++;
468
+ try {
469
+ const value = await operation();
470
+ result.applied = true;
471
+ result.modifications = { attempts };
472
+ return { value, result };
473
+ } catch (e) {
474
+ lastError = e instanceof Error ? e : new Error(String(e));
475
+ const errorType = lastError.constructor.name;
476
+ if (retryOn.length > 0 && !retryOn.includes(errorType)) {
477
+ throw lastError;
478
+ }
479
+ if (attempt < maxRetries) {
480
+ let delayMs = Math.min(
481
+ initialDelayMs * Math.pow(exponentialBase, attempt),
482
+ maxDelayMs
483
+ );
484
+ if (jitter) {
485
+ delayMs *= 0.5 + Math.random();
486
+ }
487
+ debug(
488
+ `Retry ${attempt + 1}/${maxRetries} after ${Math.round(delayMs)}ms`
489
+ );
490
+ await new Promise((r) => setTimeout(r, delayMs));
491
+ }
492
+ }
493
+ }
494
+ result.applied = true;
495
+ result.error = lastError?.message;
496
+ result.modifications = { attempts, exhausted: true };
497
+ throw lastError;
498
+ }
499
+ // ═══════════════════════════════════════════════════════════════════════════
500
+ // 4. Fallback Fix
501
+ // ═══════════════════════════════════════════════════════════════════════════
502
+ /**
503
+ * Apply a fallback fix.
504
+ *
505
+ * - 'model': Replace params.model with the fallback model
506
+ * - 'default': Set params._fallbackResponse for the caller to use
507
+ */
508
+ applyFallbackFix(fix, params) {
509
+ try {
510
+ const cfg = fix.config;
511
+ const fallbackType = cfg.fallback_type ?? cfg.fallbackType ?? "model";
512
+ const fallbackConfig = cfg.fallback_config ?? cfg.fallbackConfig ?? {};
513
+ const result = emptyResult(fix, "fallback");
514
+ if (this._config.dryRun) {
515
+ result.modifications = { fallbackType, fallbackConfig };
516
+ return { params, result };
517
+ }
518
+ const modified = { ...params };
519
+ if (fallbackType === "model") {
520
+ const fallbackModel = fallbackConfig.model;
521
+ if (fallbackModel) {
522
+ modified.model = fallbackModel;
523
+ result.applied = true;
524
+ result.modifications = { model: fallbackModel };
525
+ }
526
+ } else if (fallbackType === "default") {
527
+ const defaultResponse = fallbackConfig.response;
528
+ if (defaultResponse !== void 0) {
529
+ modified._fallbackResponse = defaultResponse;
530
+ result.applied = true;
531
+ result.modifications = { defaultResponse: true };
532
+ }
533
+ }
534
+ return { params: modified, result };
535
+ } catch (e) {
536
+ debug(`applyFallbackFix error: ${e}`);
537
+ return {
538
+ params,
539
+ result: {
540
+ applied: false,
541
+ fixId: fix.fixId,
542
+ fixType: "fallback",
543
+ modifications: {},
544
+ error: String(e)
545
+ }
546
+ };
547
+ }
548
+ }
549
+ // ═══════════════════════════════════════════════════════════════════════════
550
+ // 5. Guard Fix
551
+ // ═══════════════════════════════════════════════════════════════════════════
552
+ /**
553
+ * Apply a guard fix (validation).
554
+ *
555
+ * Guard types:
556
+ * - content_filter: Regex-based blocked patterns
557
+ * - format_check: JSON.parse() validity
558
+ * - input_validation / output_validation: min/max length
559
+ */
560
+ applyGuardFix(fix, content, isInput = true) {
561
+ try {
562
+ const cfg = fix.config;
563
+ const guardType = cfg.guard_type ?? cfg.guardType ?? "output_validation";
564
+ const guardConfig = cfg.guard_config ?? cfg.guardConfig ?? {};
565
+ const result = emptyResult(fix, "guard");
566
+ if (isInput && guardType !== "input_validation" && guardType !== "content_filter") {
567
+ return { content, passed: true, result };
568
+ }
569
+ if (!isInput && guardType !== "output_validation" && guardType !== "format_check") {
570
+ return { content, passed: true, result };
571
+ }
572
+ if (this._config.dryRun) {
573
+ result.modifications = { guardType };
574
+ return { content, passed: true, result };
575
+ }
576
+ let passed = true;
577
+ if (guardType === "content_filter") {
578
+ const blockedPatterns = guardConfig.blocked_patterns ?? guardConfig.blockedPatterns ?? [];
579
+ for (const pattern of blockedPatterns) {
580
+ const regex = this._safeCompileRegex(pattern);
581
+ if (regex && regex.test(content)) {
582
+ passed = false;
583
+ break;
584
+ }
585
+ }
586
+ } else if (guardType === "format_check") {
587
+ const requiredFormat = guardConfig.format;
588
+ if (requiredFormat === "json") {
589
+ try {
590
+ JSON.parse(content);
591
+ } catch {
592
+ passed = false;
593
+ }
594
+ }
595
+ } else if (guardType === "input_validation" || guardType === "output_validation") {
596
+ const minLength = guardConfig.min_length ?? guardConfig.minLength ?? 0;
597
+ const maxLength = guardConfig.max_length ?? guardConfig.maxLength ?? Infinity;
598
+ if (content.length < minLength || content.length > maxLength) {
599
+ passed = false;
600
+ }
601
+ }
602
+ result.applied = true;
603
+ result.modifications = { passed, guardType };
604
+ return { content, passed, result };
605
+ } catch (e) {
606
+ debug(`applyGuardFix error: ${e}`);
607
+ return {
608
+ content,
609
+ passed: true,
610
+ result: {
611
+ applied: false,
612
+ fixId: fix.fixId,
613
+ fixType: "guard",
614
+ modifications: {},
615
+ error: String(e)
616
+ }
617
+ };
618
+ }
619
+ }
620
+ // ═══════════════════════════════════════════════════════════════════════════
621
+ // Application Log
622
+ // ═══════════════════════════════════════════════════════════════════════════
623
+ /** Log a fix application result. */
624
+ logApplication(result) {
625
+ this._applicationLog.push(result);
626
+ if (this._applicationLog.length > 1e3) {
627
+ this._applicationLog = this._applicationLog.slice(-500);
628
+ }
629
+ }
630
+ /** Get the application log. */
631
+ getApplicationLog() {
632
+ return [...this._applicationLog];
633
+ }
634
+ // ═══════════════════════════════════════════════════════════════════════════
635
+ // Private Helpers
636
+ // ═══════════════════════════════════════════════════════════════════════════
637
+ /**
638
+ * Safely compile a regex pattern from untrusted input.
639
+ * Returns null if invalid or too long.
640
+ */
641
+ _safeCompileRegex(pattern, maxLength = 500) {
642
+ if (!pattern || pattern.length > maxLength) return null;
643
+ try {
644
+ return new RegExp(pattern, "i");
645
+ } catch {
646
+ debug(`Invalid regex pattern (length=${pattern.length})`);
647
+ return null;
648
+ }
649
+ }
650
+ /** Prepend content to the first message matching the target role. */
651
+ _prependToTarget(messages, target, content) {
652
+ return messages.map((msg) => {
653
+ if (msg.role === target) {
654
+ return {
655
+ ...msg,
656
+ content: content + "\n\n" + (msg.content ?? "")
657
+ };
658
+ }
659
+ return msg;
660
+ });
661
+ }
662
+ /** Append content to the first message matching the target role. */
663
+ _appendToTarget(messages, target, content) {
664
+ return messages.map((msg) => {
665
+ if (msg.role === target) {
666
+ return {
667
+ ...msg,
668
+ content: (msg.content ?? "") + "\n\n" + content
669
+ };
670
+ }
671
+ return msg;
672
+ });
673
+ }
674
+ /** Regex replace across all messages. */
675
+ _replaceInMessages(messages, pattern, replacement) {
676
+ const regex = this._safeCompileRegex(pattern);
677
+ if (!regex) {
678
+ debug("Skipping invalid regex pattern in prompt fix");
679
+ return messages;
680
+ }
681
+ return messages.map((msg) => {
682
+ const msgContent = msg.content;
683
+ if (typeof msgContent === "string") {
684
+ return { ...msg, content: msgContent.replace(regex, replacement) };
685
+ }
686
+ return msg;
687
+ });
688
+ }
689
+ /** Add few-shot examples after the first system message. */
690
+ _addFewShotExamples(messages, examples) {
691
+ const result = [];
692
+ let systemFound = false;
693
+ for (const msg of messages) {
694
+ result.push(msg);
695
+ if (msg.role === "system" && !systemFound) {
696
+ systemFound = true;
697
+ for (const example of examples) {
698
+ if (example.user) {
699
+ result.push({ role: "user", content: example.user });
700
+ }
701
+ if (example.assistant) {
702
+ result.push({ role: "assistant", content: example.assistant });
703
+ }
704
+ }
705
+ }
706
+ }
707
+ return result;
708
+ }
709
+ };
710
+ }
711
+ });
712
+
713
+ // src/runtime/loader.ts
714
+ var SDK_VERSION, FixLoader;
715
+ var init_loader = __esm({
716
+ "src/runtime/loader.ts"() {
717
+ "use strict";
718
+ init_config();
719
+ init_config();
720
+ init_log();
721
+ SDK_VERSION = "0.1.3";
722
+ FixLoader = class _FixLoader {
723
+ _config;
724
+ _cache;
725
+ _refreshTimer = null;
726
+ _lastLoadTime = null;
727
+ _consecutiveFailures = 0;
728
+ _circuitOpenUntil = 0;
729
+ _onLoadCallbacks = [];
730
+ // Circuit breaker settings
731
+ static CIRCUIT_BREAKER_THRESHOLD = 3;
732
+ static CIRCUIT_BREAKER_COOLDOWN_MS = 3e4;
733
+ constructor(config, cache) {
734
+ this._config = resolveFixRuntimeConfig(config);
735
+ this._cache = cache;
736
+ }
737
+ /** Timestamp of last successful load. */
738
+ get lastLoadTime() {
739
+ return this._lastLoadTime;
740
+ }
741
+ /**
742
+ * Start the loader: perform initial load and start background refresh.
743
+ *
744
+ * Never throws — failures are logged and the runtime degrades gracefully.
745
+ */
746
+ start() {
747
+ try {
748
+ if (!this._config.enabled) {
749
+ debug("Fix runtime disabled, skipping loader start");
750
+ return;
751
+ }
752
+ if (!this._config.apiKey) {
753
+ warn("No API key configured, fixes will not be loaded");
754
+ return;
755
+ }
756
+ this.loadSync().catch((e) => {
757
+ debug(`Initial fix load failed: ${e}`);
758
+ });
759
+ if (this._config.autoRefresh) {
760
+ this._startRefreshInterval();
761
+ }
762
+ } catch (e) {
763
+ debug(`FixLoader.start error: ${e}`);
764
+ }
765
+ }
766
+ /** Stop the loader: clear the refresh interval. */
767
+ stop() {
768
+ try {
769
+ if (this._refreshTimer !== null) {
770
+ clearInterval(this._refreshTimer);
771
+ this._refreshTimer = null;
772
+ }
773
+ } catch (e) {
774
+ debug(`FixLoader.stop error: ${e}`);
775
+ }
776
+ }
777
+ /**
778
+ * Register a callback invoked after each successful load.
779
+ */
780
+ onLoad(callback) {
781
+ this._onLoadCallbacks.push(callback);
782
+ }
783
+ /**
784
+ * Load fixes from the API.
785
+ *
786
+ * Name kept as "loadSync" for parity with the Python SDK, but this
787
+ * returns a Promise in JS (there's no synchronous fetch in Node.js).
788
+ */
789
+ async loadSync() {
790
+ if (!this._config.apiKey) {
791
+ return [];
792
+ }
793
+ const now = Date.now();
794
+ if (this._consecutiveFailures >= _FixLoader.CIRCUIT_BREAKER_THRESHOLD && now < this._circuitOpenUntil) {
795
+ debug("Fix loader circuit breaker open \u2014 skipping load");
796
+ return this._cache.getAll();
797
+ }
798
+ try {
799
+ const endpoint = this._config.apiEndpoint.replace(/\/+$/, "");
800
+ const url = `${endpoint}/api/v1/fixes/active`;
801
+ const controller = new AbortController();
802
+ const timeoutId = setTimeout(
803
+ () => controller.abort(),
804
+ this._config.timeoutMs
805
+ );
806
+ const response = await fetch(url, {
807
+ method: "GET",
808
+ headers: {
809
+ "Authorization": `Bearer ${this._config.apiKey}`,
810
+ "Content-Type": "application/json",
811
+ "X-Risicare-SDK-Version": SDK_VERSION
812
+ },
813
+ signal: controller.signal
814
+ });
815
+ clearTimeout(timeoutId);
816
+ if (!response.ok) {
817
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
818
+ }
819
+ const data = await response.json();
820
+ const fixes = this._parseResponse(data);
821
+ this._onLoadSuccess(fixes);
822
+ return fixes;
823
+ } catch (e) {
824
+ this._onLoadFailure(e);
825
+ throw e;
826
+ }
827
+ }
828
+ /** Get currently cached fixes without hitting the API. */
829
+ getCached() {
830
+ return this._cache.getAll();
831
+ }
832
+ // ─── Private ────────────────────────────────────────────────────────────
833
+ _parseResponse(data) {
834
+ const fixes = [];
835
+ const items = data.fixes ?? data.data ?? [];
836
+ if (!Array.isArray(items)) return fixes;
837
+ for (const item of items) {
838
+ try {
839
+ if (item && typeof item === "object") {
840
+ fixes.push(activeFixFromApiResponse(item));
841
+ }
842
+ } catch (e) {
843
+ debug(`Failed to parse fix item: ${e}`);
844
+ }
845
+ }
846
+ return fixes;
847
+ }
848
+ _onLoadSuccess(fixes) {
849
+ this._lastLoadTime = Date.now();
850
+ this._consecutiveFailures = 0;
851
+ this._cache.setAll(fixes);
852
+ for (const callback of this._onLoadCallbacks) {
853
+ try {
854
+ callback(fixes);
855
+ } catch (e) {
856
+ debug(`Load callback error: ${e}`);
857
+ }
858
+ }
859
+ debug(`Loaded ${fixes.length} active fixes`);
860
+ }
861
+ _onLoadFailure(error) {
862
+ this._consecutiveFailures++;
863
+ if (this._consecutiveFailures >= _FixLoader.CIRCUIT_BREAKER_THRESHOLD) {
864
+ this._circuitOpenUntil = Date.now() + _FixLoader.CIRCUIT_BREAKER_COOLDOWN_MS;
865
+ warn(
866
+ `Fix loader circuit breaker opened after ${this._consecutiveFailures} failures. Cooldown: ${_FixLoader.CIRCUIT_BREAKER_COOLDOWN_MS / 1e3}s`
867
+ );
868
+ } else {
869
+ debug(
870
+ `Fix load failed (attempt ${this._consecutiveFailures}): ${error.message}`
871
+ );
872
+ }
873
+ }
874
+ _startRefreshInterval() {
875
+ const tick = () => {
876
+ this.loadSync().catch((e) => {
877
+ debug(`Background fix refresh failed: ${e}`);
878
+ });
879
+ };
880
+ this._refreshTimer = setInterval(
881
+ () => {
882
+ if (this._consecutiveFailures >= _FixLoader.CIRCUIT_BREAKER_THRESHOLD && Date.now() < this._circuitOpenUntil) {
883
+ return;
884
+ }
885
+ tick();
886
+ },
887
+ this._config.refreshIntervalMs
888
+ );
889
+ this._refreshTimer.unref();
890
+ debug("Started fix refresh interval");
891
+ }
892
+ };
893
+ }
894
+ });
895
+
896
+ // src/runtime/interceptors.ts
897
+ function createInterceptContext(operationType, operationName, options) {
898
+ return {
899
+ operationType,
900
+ operationName,
901
+ sessionId: options?.sessionId,
902
+ traceId: options?.traceId,
903
+ errorCode: options?.errorCode,
904
+ attempt: 1,
905
+ appliedFixes: []
906
+ };
907
+ }
908
+ function classifyError(error) {
909
+ const errorType = error.constructor.name.toLowerCase();
910
+ const errorMsg = error.message.toLowerCase();
911
+ if (errorType.includes("timeout") || errorMsg.includes("timeout") || errorMsg.includes("aborted")) {
912
+ return "TOOL.EXECUTION.TIMEOUT";
913
+ }
914
+ if (errorMsg.includes("rate") && errorMsg.includes("limit")) {
915
+ return "TOOL.EXECUTION.RATE_LIMIT";
916
+ }
917
+ if (errorMsg.includes("429") || errorMsg.includes("too many requests")) {
918
+ return "TOOL.EXECUTION.RATE_LIMIT";
919
+ }
920
+ if (errorType.includes("connection") || errorMsg.includes("connection") || errorMsg.includes("econnrefused") || errorMsg.includes("econnreset") || errorMsg.includes("fetch failed")) {
921
+ return "TOOL.EXECUTION.CONNECTION_ERROR";
922
+ }
923
+ if (errorMsg.includes("auth") || errorMsg.includes("401") || errorMsg.includes("403") || errorMsg.includes("unauthorized") || errorMsg.includes("forbidden")) {
924
+ return "TOOL.EXECUTION.AUTH_ERROR";
925
+ }
926
+ if (errorType.includes("syntaxerror") || errorType.includes("json") || errorMsg.includes("json") || errorMsg.includes("unexpected token")) {
927
+ return "OUTPUT.FORMAT.JSON_INVALID";
928
+ }
929
+ return "TOOL.EXECUTION.FAILURE";
930
+ }
931
+ var DefaultFixInterceptor;
932
+ var init_interceptors = __esm({
933
+ "src/runtime/interceptors.ts"() {
934
+ "use strict";
935
+ init_config();
936
+ init_log();
937
+ DefaultFixInterceptor = class {
938
+ _config;
939
+ _cache;
940
+ _applier;
941
+ constructor(config, cache, applier) {
942
+ this._config = resolveFixRuntimeConfig(config);
943
+ this._cache = cache;
944
+ this._applier = applier;
945
+ }
946
+ /**
947
+ * Apply pre-call fixes.
948
+ *
949
+ * - If retrying after error: look up fix by error_code, apply
950
+ * prompt/parameter/fallback fixes
951
+ * - Runs input guard fixes on all message content
952
+ */
953
+ preCall(ctx, messages, params) {
954
+ try {
955
+ if (!this._config.enabled) {
956
+ return { messages, params };
957
+ }
958
+ let modifiedMessages = messages;
959
+ let modifiedParams = { ...params };
960
+ if (ctx.errorCode) {
961
+ const fix = this._applier.getFixForError(ctx.errorCode, ctx.sessionId);
962
+ if (fix) {
963
+ debug(`Applying fix ${fix.fixId} for ${ctx.errorCode}`);
964
+ if (fix.fixType === "prompt" && messages) {
965
+ const msgRecords = messages;
966
+ const { messages: newMessages, result } = this._applier.applyPromptFix(fix, msgRecords);
967
+ modifiedMessages = newMessages;
968
+ ctx.appliedFixes.push(result);
969
+ this._applier.logApplication(result);
970
+ } else if (fix.fixType === "parameter") {
971
+ const { params: newParams, result } = this._applier.applyParameterFix(fix, modifiedParams);
972
+ modifiedParams = newParams;
973
+ ctx.appliedFixes.push(result);
974
+ this._applier.logApplication(result);
975
+ } else if (fix.fixType === "fallback") {
976
+ const { params: newParams, result } = this._applier.applyFallbackFix(fix, modifiedParams);
977
+ modifiedParams = newParams;
978
+ ctx.appliedFixes.push(result);
979
+ this._applier.logApplication(result);
980
+ }
981
+ }
982
+ }
983
+ if (modifiedMessages && this._config.enabled) {
984
+ for (const msg of modifiedMessages) {
985
+ const content = msg.content;
986
+ if (typeof content === "string") {
987
+ for (const fix of this._cache.getAll()) {
988
+ if (fix.fixType === "guard") {
989
+ const { result } = this._applier.applyGuardFix(
990
+ fix,
991
+ content,
992
+ true
993
+ );
994
+ if (result.applied) {
995
+ ctx.appliedFixes.push(result);
996
+ this._applier.logApplication(result);
997
+ }
998
+ }
999
+ }
1000
+ }
1001
+ }
1002
+ }
1003
+ return { messages: modifiedMessages, params: modifiedParams };
1004
+ } catch (e) {
1005
+ debug(`DefaultFixInterceptor.preCall error: ${e}`);
1006
+ return { messages, params };
1007
+ }
1008
+ }
1009
+ /**
1010
+ * Apply post-call fixes (output validation).
1011
+ *
1012
+ * Runs output guard fixes on response content.
1013
+ */
1014
+ postCall(ctx, response) {
1015
+ try {
1016
+ if (!this._config.enabled) {
1017
+ return { response, shouldContinue: true };
1018
+ }
1019
+ for (const fix of this._cache.getAll()) {
1020
+ if (fix.fixType !== "guard") continue;
1021
+ const content = this._extractResponseContent(response);
1022
+ if (!content) continue;
1023
+ const { passed, result } = this._applier.applyGuardFix(
1024
+ fix,
1025
+ content,
1026
+ false
1027
+ );
1028
+ if (result.applied) {
1029
+ ctx.appliedFixes.push(result);
1030
+ this._applier.logApplication(result);
1031
+ }
1032
+ if (!passed) {
1033
+ warn(`Output guard failed for fix ${fix.fixId}`);
1034
+ }
1035
+ }
1036
+ return { response, shouldContinue: true };
1037
+ } catch (e) {
1038
+ debug(`DefaultFixInterceptor.postCall error: ${e}`);
1039
+ return { response, shouldContinue: true };
1040
+ }
1041
+ }
1042
+ /**
1043
+ * Handle errors and decide whether to retry.
1044
+ *
1045
+ * Classifies the error, looks up retry/fallback fixes, and returns
1046
+ * whether the caller should retry.
1047
+ */
1048
+ onError(ctx, error) {
1049
+ try {
1050
+ if (!this._config.enabled) {
1051
+ return { shouldRetry: false, modifiedParams: null };
1052
+ }
1053
+ const errorCode = classifyError(error);
1054
+ ctx.errorCode = errorCode;
1055
+ ctx.errorMessage = error.message;
1056
+ const fix = this._applier.getFixForError(errorCode, ctx.sessionId);
1057
+ if (!fix) {
1058
+ return { shouldRetry: false, modifiedParams: null };
1059
+ }
1060
+ if (fix.fixType === "retry") {
1061
+ const maxRetries = fix.config.max_retries ?? fix.config.maxRetries ?? 3;
1062
+ if (ctx.attempt <= maxRetries) {
1063
+ debug(
1064
+ `Retry fix ${fix.fixId}: attempt ${ctx.attempt}/${maxRetries}`
1065
+ );
1066
+ const result = {
1067
+ applied: true,
1068
+ fixId: fix.fixId,
1069
+ deploymentId: fix.deploymentId,
1070
+ fixType: "retry",
1071
+ modifications: { attempt: ctx.attempt }
1072
+ };
1073
+ ctx.appliedFixes.push(result);
1074
+ this._applier.logApplication(result);
1075
+ return { shouldRetry: true, modifiedParams: null };
1076
+ }
1077
+ }
1078
+ if (fix.fixType === "fallback") {
1079
+ const { params: modifiedParams, result } = this._applier.applyFallbackFix(fix, {});
1080
+ if (result.applied) {
1081
+ ctx.appliedFixes.push(result);
1082
+ this._applier.logApplication(result);
1083
+ return { shouldRetry: true, modifiedParams };
1084
+ }
1085
+ }
1086
+ return { shouldRetry: false, modifiedParams: null };
1087
+ } catch (e) {
1088
+ debug(`DefaultFixInterceptor.onError error: ${e}`);
1089
+ return { shouldRetry: false, modifiedParams: null };
1090
+ }
1091
+ }
1092
+ // ─── Private ────────────────────────────────────────────────────────────
1093
+ /** Extract text content from a response object. */
1094
+ _extractResponseContent(response) {
1095
+ if (typeof response === "string") return response;
1096
+ if (response && typeof response === "object") {
1097
+ const resp = response;
1098
+ const choices = resp.choices;
1099
+ if (Array.isArray(choices) && choices.length > 0) {
1100
+ const first = choices[0];
1101
+ const message = first?.message;
1102
+ if (typeof message?.content === "string") {
1103
+ return message.content;
1104
+ }
1105
+ }
1106
+ const content = resp.content;
1107
+ if (typeof content === "string") return content;
1108
+ if (Array.isArray(content) && content.length > 0) {
1109
+ const first = content[0];
1110
+ if (typeof first?.text === "string") return first.text;
1111
+ }
1112
+ }
1113
+ return null;
1114
+ }
1115
+ };
1116
+ }
1117
+ });
1118
+
1119
+ // src/runtime/runtime.ts
1120
+ var runtime_exports = {};
1121
+ __export(runtime_exports, {
1122
+ FixRuntime: () => FixRuntime,
1123
+ getFixRuntime: () => getFixRuntime,
1124
+ initFixRuntime: () => initFixRuntime,
1125
+ setFixRuntime: () => setFixRuntime,
1126
+ shutdownFixRuntime: () => shutdownFixRuntime
1127
+ });
1128
+ function getFixRuntime() {
1129
+ return G2[RUNTIME_KEY];
1130
+ }
1131
+ function setFixRuntime(runtime) {
1132
+ G2[RUNTIME_KEY] = runtime;
1133
+ }
1134
+ function initFixRuntime(config) {
1135
+ try {
1136
+ const existing = G2[RUNTIME_KEY];
1137
+ if (existing) {
1138
+ debug("Fix runtime already initialized");
1139
+ return existing;
1140
+ }
1141
+ const runtime = new FixRuntime(config);
1142
+ runtime.start();
1143
+ G2[RUNTIME_KEY] = runtime;
1144
+ return runtime;
1145
+ } catch (e) {
1146
+ warn(`initFixRuntime failed: ${e}`);
1147
+ const fallback = new FixRuntime({ ...config, enabled: false });
1148
+ G2[RUNTIME_KEY] = fallback;
1149
+ return fallback;
1150
+ }
1151
+ }
1152
+ function shutdownFixRuntime() {
1153
+ try {
1154
+ const runtime = G2[RUNTIME_KEY];
1155
+ if (runtime) {
1156
+ runtime.stop();
1157
+ }
1158
+ G2[RUNTIME_KEY] = void 0;
1159
+ } catch (e) {
1160
+ debug(`shutdownFixRuntime error: ${e}`);
1161
+ G2[RUNTIME_KEY] = void 0;
1162
+ }
1163
+ }
1164
+ var G2, RUNTIME_KEY, FixRuntime;
1165
+ var init_runtime = __esm({
1166
+ "src/runtime/runtime.ts"() {
1167
+ "use strict";
1168
+ init_cache();
1169
+ init_config();
1170
+ init_applier();
1171
+ init_loader();
1172
+ init_interceptors();
1173
+ init_log();
1174
+ G2 = globalThis;
1175
+ RUNTIME_KEY = "__risicare_fix_runtime";
1176
+ FixRuntime = class {
1177
+ _config;
1178
+ _cache;
1179
+ _loader;
1180
+ _applier;
1181
+ _interceptor;
1182
+ _started = false;
1183
+ // Effectiveness tracking
1184
+ _fixApplications = /* @__PURE__ */ new Map();
1185
+ _fixSuccesses = /* @__PURE__ */ new Map();
1186
+ constructor(config) {
1187
+ this._config = resolveFixRuntimeConfig(config);
1188
+ this._cache = new FixCache(this._config);
1189
+ this._loader = new FixLoader(config, this._cache);
1190
+ this._applier = new FixApplier(config, this._cache);
1191
+ this._interceptor = new DefaultFixInterceptor(
1192
+ config,
1193
+ this._cache,
1194
+ this._applier
1195
+ );
1196
+ }
1197
+ // ─── Accessors ──────────────────────────────────────────────────────────
1198
+ /** Runtime configuration (with defaults resolved). */
1199
+ get config() {
1200
+ return this._config;
1201
+ }
1202
+ /** Whether the runtime is enabled and started. */
1203
+ get isEnabled() {
1204
+ return this._config.enabled && this._started;
1205
+ }
1206
+ /** The fix cache. */
1207
+ get cache() {
1208
+ return this._cache;
1209
+ }
1210
+ /** The fix loader. */
1211
+ get loader() {
1212
+ return this._loader;
1213
+ }
1214
+ /** The fix applier. */
1215
+ get applier() {
1216
+ return this._applier;
1217
+ }
1218
+ /** The interceptor. */
1219
+ get interceptor() {
1220
+ return this._interceptor;
1221
+ }
1222
+ // ─── Lifecycle ──────────────────────────────────────────────────────────
1223
+ /**
1224
+ * Start the runtime.
1225
+ *
1226
+ * Triggers initial fix load and starts background refresh.
1227
+ * Never throws — failures degrade gracefully.
1228
+ */
1229
+ start() {
1230
+ try {
1231
+ if (this._started) return;
1232
+ if (!this._config.enabled) {
1233
+ debug("Fix runtime disabled");
1234
+ return;
1235
+ }
1236
+ debug("Starting fix runtime...");
1237
+ this._loader.start();
1238
+ this._started = true;
1239
+ debug("Fix runtime started");
1240
+ } catch (e) {
1241
+ warn(`Fix runtime start failed: ${e}`);
1242
+ }
1243
+ }
1244
+ /**
1245
+ * Stop the runtime.
1246
+ *
1247
+ * Stops background refresh and clears the cache.
1248
+ * Never throws.
1249
+ */
1250
+ stop() {
1251
+ try {
1252
+ if (!this._started) return;
1253
+ debug("Stopping fix runtime...");
1254
+ this._loader.stop();
1255
+ this._cache.clear();
1256
+ this._started = false;
1257
+ debug("Fix runtime stopped");
1258
+ } catch (e) {
1259
+ debug(`Fix runtime stop error: ${e}`);
1260
+ }
1261
+ }
1262
+ // ─── Intercept API ──────────────────────────────────────────────────────
1263
+ /**
1264
+ * Intercept a call: create context and run preCall fixes.
1265
+ *
1266
+ * Returns modified messages/params and the InterceptContext for
1267
+ * use in subsequent interceptResponse / interceptError calls.
1268
+ */
1269
+ interceptCall(operationType, operationName, messages, params, options) {
1270
+ const ctx = createInterceptContext(operationType, operationName, options);
1271
+ try {
1272
+ if (!this.isEnabled) {
1273
+ return { messages, params, ctx };
1274
+ }
1275
+ const result = this._interceptor.preCall(ctx, messages, params);
1276
+ return { messages: result.messages, params: result.params, ctx };
1277
+ } catch (e) {
1278
+ debug(`interceptCall error: ${e}`);
1279
+ return { messages, params, ctx };
1280
+ }
1281
+ }
1282
+ /**
1283
+ * Intercept a response: run postCall fixes (output validation).
1284
+ */
1285
+ interceptResponse(ctx, response) {
1286
+ try {
1287
+ if (!this.isEnabled) {
1288
+ return { response, shouldContinue: true };
1289
+ }
1290
+ const result = this._interceptor.postCall(ctx, response);
1291
+ this._trackSuccess(ctx);
1292
+ return result;
1293
+ } catch (e) {
1294
+ debug(`interceptResponse error: ${e}`);
1295
+ return { response, shouldContinue: true };
1296
+ }
1297
+ }
1298
+ /**
1299
+ * Intercept an error: decide on retry/fallback.
1300
+ */
1301
+ interceptError(ctx, error) {
1302
+ try {
1303
+ if (!this.isEnabled) {
1304
+ return { shouldRetry: false, modifiedParams: null };
1305
+ }
1306
+ ctx.attempt++;
1307
+ const result = this._interceptor.onError(ctx, error);
1308
+ if (!result.shouldRetry) {
1309
+ this._trackFailure(ctx);
1310
+ }
1311
+ return result;
1312
+ } catch (e) {
1313
+ debug(`interceptError error: ${e}`);
1314
+ return { shouldRetry: false, modifiedParams: null };
1315
+ }
1316
+ }
1317
+ // ─── Direct Fix Access ──────────────────────────────────────────────────
1318
+ /**
1319
+ * Get applicable fix for an error code.
1320
+ */
1321
+ getFix(errorCode, sessionId) {
1322
+ try {
1323
+ if (!this.isEnabled) return null;
1324
+ return this._applier.getFixForError(errorCode, sessionId);
1325
+ } catch (e) {
1326
+ debug(`getFix error: ${e}`);
1327
+ return null;
1328
+ }
1329
+ }
1330
+ /**
1331
+ * Manually refresh fixes from the API.
1332
+ */
1333
+ async refreshFixes() {
1334
+ try {
1335
+ return await this._loader.loadSync();
1336
+ } catch (e) {
1337
+ debug(`refreshFixes error: ${e}`);
1338
+ return [];
1339
+ }
1340
+ }
1341
+ // ─── Effectiveness ──────────────────────────────────────────────────────
1342
+ /** Get effectiveness statistics for all fixes. */
1343
+ getEffectivenessStats() {
1344
+ const stats = {};
1345
+ for (const [fixId, applications] of this._fixApplications) {
1346
+ const successes = this._fixSuccesses.get(fixId) ?? 0;
1347
+ stats[fixId] = {
1348
+ applications,
1349
+ successes,
1350
+ successRate: applications > 0 ? successes / applications : 0
1351
+ };
1352
+ }
1353
+ return stats;
1354
+ }
1355
+ // ─── Private ────────────────────────────────────────────────────────────
1356
+ _trackSuccess(ctx) {
1357
+ for (const result of ctx.appliedFixes) {
1358
+ if (result.applied && result.fixId) {
1359
+ this._fixApplications.set(
1360
+ result.fixId,
1361
+ (this._fixApplications.get(result.fixId) ?? 0) + 1
1362
+ );
1363
+ this._fixSuccesses.set(
1364
+ result.fixId,
1365
+ (this._fixSuccesses.get(result.fixId) ?? 0) + 1
1366
+ );
1367
+ }
1368
+ }
1369
+ }
1370
+ _trackFailure(ctx) {
1371
+ for (const result of ctx.appliedFixes) {
1372
+ if (result.applied && result.fixId) {
1373
+ this._fixApplications.set(
1374
+ result.fixId,
1375
+ (this._fixApplications.get(result.fixId) ?? 0) + 1
1376
+ );
1377
+ }
1378
+ }
1379
+ }
1380
+ };
1381
+ }
1382
+ });
1383
+
1
1384
  // src/ids.ts
2
1385
  import { randomBytes } from "crypto";
3
1386
 
@@ -71,24 +1454,8 @@ var NOOP_SPAN = Object.freeze({
71
1454
  }
72
1455
  });
73
1456
 
74
- // src/globals.ts
75
- import { AsyncLocalStorage } from "async_hooks";
76
- var G = globalThis;
77
- var PREFIX = "__risicare_";
78
- function getTracer() {
79
- return G[PREFIX + "tracer"];
80
- }
81
- function getContextStorage() {
82
- if (!G[PREFIX + "ctx"]) {
83
- G[PREFIX + "ctx"] = new AsyncLocalStorage();
84
- }
85
- return G[PREFIX + "ctx"];
86
- }
87
- function getDebug() {
88
- return G[PREFIX + "debug"] ?? false;
89
- }
90
-
91
1457
  // src/context/storage.ts
1458
+ init_globals();
92
1459
  function storage() {
93
1460
  return getContextStorage();
94
1461
  }
@@ -96,15 +1463,15 @@ function getContext() {
96
1463
  return storage().getStore() ?? {};
97
1464
  }
98
1465
 
99
- // src/utils/log.ts
100
- function debug(msg) {
101
- if (getDebug()) {
102
- process.stderr.write(`[risicare] ${msg}
103
- `);
104
- }
105
- }
1466
+ // src/exporters/batch.ts
1467
+ init_log();
1468
+
1469
+ // src/exporters/http.ts
1470
+ init_log();
106
1471
 
107
1472
  // src/client.ts
1473
+ init_log();
1474
+ init_globals();
108
1475
  function requireTracer() {
109
1476
  const tracer = getTracer();
110
1477
  if (!tracer) {
@@ -114,6 +1481,7 @@ function requireTracer() {
114
1481
  }
115
1482
  return tracer;
116
1483
  }
1484
+ var _ERROR_DEDUP_TTL_MS = 5 * 60 * 1e3;
117
1485
 
118
1486
  // src/utils/pricing.ts
119
1487
  var PRICING = {
@@ -161,6 +1529,9 @@ function calculateCost(model, promptTokens, completionTokens) {
161
1529
  return inputCost + outputCost;
162
1530
  }
163
1531
 
1532
+ // src/providers/openai/patch.ts
1533
+ init_log();
1534
+
164
1535
  // src/context/dedup.ts
165
1536
  function isProviderInstrumentationSuppressed() {
166
1537
  return getContext()._suppressProviderInstrumentation === true;
@@ -187,11 +1558,41 @@ function detectProvider(client) {
187
1558
  return "openai";
188
1559
  }
189
1560
  }
1561
+ function enrichSpanFromRequest(span, params, provider) {
1562
+ const model = params.model;
1563
+ if (model) {
1564
+ span.setAttribute("gen_ai.system", provider);
1565
+ span.setAttribute("gen_ai.request.model", model);
1566
+ }
1567
+ if (params.temperature !== void 0) span.setAttribute("gen_ai.request.temperature", params.temperature);
1568
+ if (params.max_tokens !== void 0) span.setAttribute("gen_ai.request.max_tokens", params.max_tokens);
1569
+ if (params.top_p !== void 0) span.setAttribute("gen_ai.request.top_p", params.top_p);
1570
+ if (params.frequency_penalty !== void 0) span.setAttribute("gen_ai.request.frequency_penalty", params.frequency_penalty);
1571
+ if (params.presence_penalty !== void 0) span.setAttribute("gen_ai.request.presence_penalty", params.presence_penalty);
1572
+ if (params.stream !== void 0) span.setAttribute("llm.stream", !!params.stream);
1573
+ const tools = params.tools;
1574
+ if (Array.isArray(tools) && tools.length > 0) {
1575
+ span.setAttribute("gen_ai.request.has_tools", true);
1576
+ span.setAttribute("gen_ai.request.tool_count", tools.length);
1577
+ }
1578
+ }
190
1579
  function enrichSpanFromResponse(span, response, provider) {
191
1580
  const model = response.model;
192
1581
  const usage = response.usage;
193
1582
  if (model) {
194
1583
  span.setLlmFields({ provider, model });
1584
+ span.setAttribute("gen_ai.response.model", model);
1585
+ }
1586
+ if (response.id) span.setAttribute("gen_ai.response.id", response.id);
1587
+ const choices = response.choices;
1588
+ if (Array.isArray(choices) && choices.length > 0) {
1589
+ const reasons = choices.map((c) => c.finish_reason).filter(Boolean);
1590
+ if (reasons.length > 0) span.setAttribute("gen_ai.response.finish_reasons", reasons);
1591
+ const firstChoice = choices[0];
1592
+ const msg = firstChoice?.message;
1593
+ if (msg?.tool_calls && Array.isArray(msg.tool_calls)) {
1594
+ span.setAttribute("gen_ai.response.tool_call_count", msg.tool_calls.length);
1595
+ }
195
1596
  }
196
1597
  if (usage) {
197
1598
  const promptTokens = usage.prompt_tokens ?? 0;
@@ -204,6 +1605,10 @@ function enrichSpanFromResponse(span, response, provider) {
204
1605
  totalTokens,
205
1606
  costUsd: cost
206
1607
  });
1608
+ span.setAttribute("gen_ai.usage.prompt_tokens", promptTokens);
1609
+ span.setAttribute("gen_ai.usage.completion_tokens", completionTokens);
1610
+ span.setAttribute("gen_ai.usage.total_tokens", totalTokens);
1611
+ if (cost !== void 0) span.setAttribute("llm.cost.total_usd", cost);
207
1612
  }
208
1613
  }
209
1614
  function createChatCompletionProxy(originalCreate, provider) {
@@ -218,13 +1623,26 @@ function createChatCompletionProxy(originalCreate, provider) {
218
1623
  debug("Tracer not initialized \u2014 call init() before using patchOpenAI()");
219
1624
  return originalCreate.apply(this, args);
220
1625
  }
221
- const params = args[0] ?? {};
222
- const model = params.model ?? "unknown";
1626
+ let params = args[0] ?? {};
1627
+ let model = params.model ?? "unknown";
223
1628
  const isStream = !!params.stream;
1629
+ try {
1630
+ const { getFixRuntime: getFixRuntime2 } = (init_runtime(), __toCommonJS(runtime_exports));
1631
+ const rt = getFixRuntime2();
1632
+ if (rt?.isEnabled) {
1633
+ const result = rt.interceptCall("llm_call", model, params.messages ?? null, params);
1634
+ if (result.messages) params = { ...params, messages: result.messages };
1635
+ if (result.params) params = { ...params, ...result.params };
1636
+ model = params.model ?? model;
1637
+ args[0] = params;
1638
+ }
1639
+ } catch {
1640
+ }
224
1641
  return tracer.startSpan(
225
- { name: `openai.chat.completions.create`, kind: "llm_call" /* LLM_CALL */, attributes: { "llm.request.model": model, "llm.stream": isStream } },
1642
+ { name: `openai.chat.completions.create`, kind: "llm_call" /* LLM_CALL */ },
226
1643
  (span) => {
227
1644
  span.setLlmFields({ provider, model });
1645
+ enrichSpanFromRequest(span, params, provider);
228
1646
  const result = originalCreate.apply(this, args);
229
1647
  if (result && typeof result === "object" && typeof result.then === "function") {
230
1648
  return result.then((response) => {
@@ -254,9 +1672,10 @@ function createEmbeddingsProxy(originalCreate, provider) {
254
1672
  const params = args[0] ?? {};
255
1673
  const model = params.model ?? "unknown";
256
1674
  return tracer.startSpan(
257
- { name: `openai.embeddings.create`, kind: "llm_call" /* LLM_CALL */, attributes: { "llm.request.model": model } },
1675
+ { name: `openai.embeddings.create`, kind: "llm_call" /* LLM_CALL */ },
258
1676
  (span) => {
259
1677
  span.setLlmFields({ provider, model });
1678
+ enrichSpanFromRequest(span, params, provider);
260
1679
  const result = originalCreate.apply(this, args);
261
1680
  if (result && typeof result === "object" && typeof result.then === "function") {
262
1681
  return result.then((response) => {