risicare 0.2.2 → 0.4.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 +26 -10
- package/dist/frameworks/instructor.cjs +45 -17
- package/dist/frameworks/instructor.cjs.map +1 -1
- package/dist/frameworks/instructor.js +47 -17
- package/dist/frameworks/instructor.js.map +1 -1
- package/dist/frameworks/langchain.cjs +73 -6
- package/dist/frameworks/langchain.cjs.map +1 -1
- package/dist/frameworks/langchain.d.cts +20 -4
- package/dist/frameworks/langchain.d.ts +20 -4
- package/dist/frameworks/langchain.js +75 -6
- package/dist/frameworks/langchain.js.map +1 -1
- package/dist/frameworks/langgraph.cjs +73 -6
- package/dist/frameworks/langgraph.cjs.map +1 -1
- package/dist/frameworks/langgraph.js +75 -6
- package/dist/frameworks/langgraph.js.map +1 -1
- package/dist/frameworks/llamaindex.cjs +41 -14
- package/dist/frameworks/llamaindex.cjs.map +1 -1
- package/dist/frameworks/llamaindex.js +43 -14
- package/dist/frameworks/llamaindex.js.map +1 -1
- package/dist/index.cjs +1705 -74
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +597 -6
- package/dist/index.d.ts +597 -6
- package/dist/index.js +1723 -74
- package/dist/index.js.map +1 -1
- package/dist/providers/anthropic/index.cjs +74 -24
- package/dist/providers/anthropic/index.cjs.map +1 -1
- package/dist/providers/anthropic/index.js +76 -24
- package/dist/providers/anthropic/index.js.map +1 -1
- package/dist/providers/bedrock/index.cjs +81 -24
- package/dist/providers/bedrock/index.cjs.map +1 -1
- package/dist/providers/bedrock/index.js +83 -24
- package/dist/providers/bedrock/index.js.map +1 -1
- package/dist/providers/cerebras/index.cjs +78 -25
- package/dist/providers/cerebras/index.cjs.map +1 -1
- package/dist/providers/cerebras/index.js +80 -25
- package/dist/providers/cerebras/index.js.map +1 -1
- package/dist/providers/cohere/index.cjs +95 -25
- package/dist/providers/cohere/index.cjs.map +1 -1
- package/dist/providers/cohere/index.js +97 -25
- package/dist/providers/cohere/index.js.map +1 -1
- package/dist/providers/google/index.cjs +77 -25
- package/dist/providers/google/index.cjs.map +1 -1
- package/dist/providers/google/index.js +79 -25
- package/dist/providers/google/index.js.map +1 -1
- package/dist/providers/groq/index.cjs +80 -25
- package/dist/providers/groq/index.cjs.map +1 -1
- package/dist/providers/groq/index.js +82 -25
- package/dist/providers/groq/index.js.map +1 -1
- package/dist/providers/huggingface/index.cjs +80 -25
- package/dist/providers/huggingface/index.cjs.map +1 -1
- package/dist/providers/huggingface/index.js +82 -25
- package/dist/providers/huggingface/index.js.map +1 -1
- package/dist/providers/mistral/index.cjs +72 -24
- package/dist/providers/mistral/index.cjs.map +1 -1
- package/dist/providers/mistral/index.js +74 -24
- package/dist/providers/mistral/index.js.map +1 -1
- package/dist/providers/ollama/index.cjs +83 -25
- package/dist/providers/ollama/index.cjs.map +1 -1
- package/dist/providers/ollama/index.js +85 -25
- package/dist/providers/ollama/index.js.map +1 -1
- package/dist/providers/openai/index.cjs +118 -28
- package/dist/providers/openai/index.cjs.map +1 -1
- package/dist/providers/openai/index.js +120 -28
- package/dist/providers/openai/index.js.map +1 -1
- package/dist/providers/together/index.cjs +80 -25
- package/dist/providers/together/index.cjs.map +1 -1
- package/dist/providers/together/index.js +82 -25
- package/dist/providers/together/index.js.map +1 -1
- package/dist/providers/vercel-ai/index.cjs +45 -17
- package/dist/providers/vercel-ai/index.cjs.map +1 -1
- package/dist/providers/vercel-ai/index.js +47 -17
- package/dist/providers/vercel-ai/index.js.map +1 -1
- package/package.json +5 -4
package/dist/index.cjs
CHANGED
|
@@ -5,6 +5,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __esm = (fn, res) => function __init() {
|
|
9
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
10
|
+
};
|
|
8
11
|
var __export = (target, all) => {
|
|
9
12
|
for (var name in all)
|
|
10
13
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -27,16 +30,1407 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
27
30
|
));
|
|
28
31
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
32
|
|
|
33
|
+
// src/globals.ts
|
|
34
|
+
function getClient() {
|
|
35
|
+
return G[PREFIX + "client"];
|
|
36
|
+
}
|
|
37
|
+
function setClient(client) {
|
|
38
|
+
G[PREFIX + "client"] = client;
|
|
39
|
+
}
|
|
40
|
+
function getTracer() {
|
|
41
|
+
return G[PREFIX + "tracer"];
|
|
42
|
+
}
|
|
43
|
+
function setTracer(tracer) {
|
|
44
|
+
G[PREFIX + "tracer"] = tracer;
|
|
45
|
+
}
|
|
46
|
+
function getContextStorage() {
|
|
47
|
+
if (!G[PREFIX + "ctx"]) {
|
|
48
|
+
G[PREFIX + "ctx"] = new import_node_async_hooks.AsyncLocalStorage();
|
|
49
|
+
}
|
|
50
|
+
return G[PREFIX + "ctx"];
|
|
51
|
+
}
|
|
52
|
+
function getRegistry() {
|
|
53
|
+
if (!G[PREFIX + "registry"]) {
|
|
54
|
+
G[PREFIX + "registry"] = /* @__PURE__ */ new Map();
|
|
55
|
+
}
|
|
56
|
+
return G[PREFIX + "registry"];
|
|
57
|
+
}
|
|
58
|
+
function getOpCount() {
|
|
59
|
+
return G[PREFIX + "opcount"] ?? 0;
|
|
60
|
+
}
|
|
61
|
+
function setOpCount(n) {
|
|
62
|
+
G[PREFIX + "opcount"] = n;
|
|
63
|
+
}
|
|
64
|
+
function getDebug() {
|
|
65
|
+
return G[PREFIX + "debug"] ?? false;
|
|
66
|
+
}
|
|
67
|
+
function setDebugFlag(enabled) {
|
|
68
|
+
G[PREFIX + "debug"] = enabled;
|
|
69
|
+
}
|
|
70
|
+
var import_node_async_hooks, G, PREFIX;
|
|
71
|
+
var init_globals = __esm({
|
|
72
|
+
"src/globals.ts"() {
|
|
73
|
+
"use strict";
|
|
74
|
+
import_node_async_hooks = require("async_hooks");
|
|
75
|
+
G = globalThis;
|
|
76
|
+
PREFIX = "__risicare_";
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// src/utils/log.ts
|
|
81
|
+
function setDebug(enabled) {
|
|
82
|
+
setDebugFlag(enabled);
|
|
83
|
+
}
|
|
84
|
+
function debug(msg) {
|
|
85
|
+
if (getDebug()) {
|
|
86
|
+
process.stderr.write(`[risicare] ${msg}
|
|
87
|
+
`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function warn(msg) {
|
|
91
|
+
process.stderr.write(`[risicare] WARNING: ${msg}
|
|
92
|
+
`);
|
|
93
|
+
}
|
|
94
|
+
var init_log = __esm({
|
|
95
|
+
"src/utils/log.ts"() {
|
|
96
|
+
"use strict";
|
|
97
|
+
init_globals();
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// src/runtime/config.ts
|
|
102
|
+
function resolveFixRuntimeConfig(config) {
|
|
103
|
+
return {
|
|
104
|
+
apiEndpoint: config.apiEndpoint,
|
|
105
|
+
apiKey: config.apiKey,
|
|
106
|
+
enabled: config.enabled ?? true,
|
|
107
|
+
cacheEnabled: config.cacheEnabled ?? true,
|
|
108
|
+
cacheTtlMs: config.cacheTtlMs ?? 3e5,
|
|
109
|
+
cacheMaxEntries: config.cacheMaxEntries ?? 1e3,
|
|
110
|
+
autoRefresh: config.autoRefresh ?? true,
|
|
111
|
+
refreshIntervalMs: config.refreshIntervalMs ?? 6e4,
|
|
112
|
+
dryRun: config.dryRun ?? false,
|
|
113
|
+
abTestingEnabled: config.abTestingEnabled ?? true,
|
|
114
|
+
timeoutMs: config.timeoutMs ?? 1e3,
|
|
115
|
+
debug: config.debug ?? false
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function matchesError(fix, errorCode) {
|
|
119
|
+
if (fix.errorCode === errorCode) {
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
if (fix.errorCode.endsWith("*")) {
|
|
123
|
+
const prefix = fix.errorCode.slice(0, -1);
|
|
124
|
+
return errorCode.startsWith(prefix);
|
|
125
|
+
}
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
function shouldApply(fix, sessionHash) {
|
|
129
|
+
if (fix.trafficPercentage >= 100) return true;
|
|
130
|
+
if (fix.trafficPercentage <= 0) return false;
|
|
131
|
+
const bucket = sessionHash % 100;
|
|
132
|
+
return bucket < fix.trafficPercentage;
|
|
133
|
+
}
|
|
134
|
+
function activeFixFromApiResponse(item) {
|
|
135
|
+
return {
|
|
136
|
+
fixId: item.id ?? item.fix_id ?? item.fixId ?? "",
|
|
137
|
+
deploymentId: item.deployment_id ?? item.deploymentId ?? "",
|
|
138
|
+
errorCode: item.error_code ?? item.errorCode ?? "",
|
|
139
|
+
fixType: item.fix_type ?? item.fixType ?? "prompt",
|
|
140
|
+
config: item.config ?? {},
|
|
141
|
+
trafficPercentage: item.traffic_percentage ?? item.trafficPercentage ?? 100,
|
|
142
|
+
version: item.version ?? 1
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
var init_config = __esm({
|
|
146
|
+
"src/runtime/config.ts"() {
|
|
147
|
+
"use strict";
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// src/runtime/cache.ts
|
|
152
|
+
var FixCache;
|
|
153
|
+
var init_cache = __esm({
|
|
154
|
+
"src/runtime/cache.ts"() {
|
|
155
|
+
"use strict";
|
|
156
|
+
init_config();
|
|
157
|
+
init_log();
|
|
158
|
+
FixCache = class {
|
|
159
|
+
_ttlMs;
|
|
160
|
+
_maxEntries;
|
|
161
|
+
_enabled;
|
|
162
|
+
_cache;
|
|
163
|
+
_stats;
|
|
164
|
+
constructor(config) {
|
|
165
|
+
this._enabled = config?.cacheEnabled ?? true;
|
|
166
|
+
this._ttlMs = config?.cacheTtlMs ?? 3e5;
|
|
167
|
+
this._maxEntries = config?.cacheMaxEntries ?? 1e3;
|
|
168
|
+
this._cache = /* @__PURE__ */ new Map();
|
|
169
|
+
this._stats = {
|
|
170
|
+
hits: 0,
|
|
171
|
+
misses: 0,
|
|
172
|
+
evictions: 0,
|
|
173
|
+
size: 0,
|
|
174
|
+
lastRefresh: null
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
/** Whether caching is enabled. */
|
|
178
|
+
get enabled() {
|
|
179
|
+
return this._enabled;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Get a fix by error code.
|
|
183
|
+
*
|
|
184
|
+
* Checks exact match first, then scans for wildcard matches.
|
|
185
|
+
* Expired entries are evicted on access.
|
|
186
|
+
*/
|
|
187
|
+
get(errorCode) {
|
|
188
|
+
try {
|
|
189
|
+
if (!this._enabled) return null;
|
|
190
|
+
const now = Date.now();
|
|
191
|
+
const exactEntry = this._cache.get(errorCode);
|
|
192
|
+
if (exactEntry) {
|
|
193
|
+
if (exactEntry.expiresAt > now) {
|
|
194
|
+
this._stats.hits++;
|
|
195
|
+
return exactEntry.fix;
|
|
196
|
+
}
|
|
197
|
+
this._cache.delete(errorCode);
|
|
198
|
+
this._stats.evictions++;
|
|
199
|
+
}
|
|
200
|
+
for (const [key, entry] of this._cache) {
|
|
201
|
+
if (entry.expiresAt <= now) {
|
|
202
|
+
this._cache.delete(key);
|
|
203
|
+
this._stats.evictions++;
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
if (matchesError(entry.fix, errorCode)) {
|
|
207
|
+
this._stats.hits++;
|
|
208
|
+
return entry.fix;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
this._stats.misses++;
|
|
212
|
+
return null;
|
|
213
|
+
} catch (e) {
|
|
214
|
+
debug(`FixCache.get error: ${e}`);
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Add a single fix to the cache.
|
|
220
|
+
*
|
|
221
|
+
* Evicts the oldest entry if at capacity.
|
|
222
|
+
*/
|
|
223
|
+
set(fix) {
|
|
224
|
+
try {
|
|
225
|
+
if (!this._enabled) return;
|
|
226
|
+
if (this._cache.size >= this._maxEntries) {
|
|
227
|
+
this._evictOldest();
|
|
228
|
+
}
|
|
229
|
+
const now = Date.now();
|
|
230
|
+
this._cache.set(fix.errorCode, {
|
|
231
|
+
fix,
|
|
232
|
+
createdAt: now,
|
|
233
|
+
expiresAt: now + this._ttlMs
|
|
234
|
+
});
|
|
235
|
+
this._stats.size = this._cache.size;
|
|
236
|
+
} catch (e) {
|
|
237
|
+
debug(`FixCache.set error: ${e}`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Replace all cached fixes (bulk refresh).
|
|
242
|
+
*
|
|
243
|
+
* Clears the cache and populates with the given fixes.
|
|
244
|
+
*/
|
|
245
|
+
setAll(fixes) {
|
|
246
|
+
try {
|
|
247
|
+
if (!this._enabled) return;
|
|
248
|
+
this._cache.clear();
|
|
249
|
+
const now = Date.now();
|
|
250
|
+
for (const fix of fixes) {
|
|
251
|
+
if (this._cache.size >= this._maxEntries) break;
|
|
252
|
+
this._cache.set(fix.errorCode, {
|
|
253
|
+
fix,
|
|
254
|
+
createdAt: now,
|
|
255
|
+
expiresAt: now + this._ttlMs
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
this._stats.size = this._cache.size;
|
|
259
|
+
this._stats.lastRefresh = now;
|
|
260
|
+
} catch (e) {
|
|
261
|
+
debug(`FixCache.setAll error: ${e}`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Get all non-expired fixes.
|
|
266
|
+
*/
|
|
267
|
+
getAll() {
|
|
268
|
+
try {
|
|
269
|
+
const now = Date.now();
|
|
270
|
+
const valid = [];
|
|
271
|
+
const expired = [];
|
|
272
|
+
for (const [key, entry] of this._cache) {
|
|
273
|
+
if (entry.expiresAt > now) {
|
|
274
|
+
valid.push(entry.fix);
|
|
275
|
+
} else {
|
|
276
|
+
expired.push(key);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
for (const key of expired) {
|
|
280
|
+
this._cache.delete(key);
|
|
281
|
+
this._stats.evictions++;
|
|
282
|
+
}
|
|
283
|
+
this._stats.size = this._cache.size;
|
|
284
|
+
return valid;
|
|
285
|
+
} catch (e) {
|
|
286
|
+
debug(`FixCache.getAll error: ${e}`);
|
|
287
|
+
return [];
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
/** Clear all cache entries. Returns the number of entries cleared. */
|
|
291
|
+
clear() {
|
|
292
|
+
const count = this._cache.size;
|
|
293
|
+
this._cache.clear();
|
|
294
|
+
this._stats.size = 0;
|
|
295
|
+
return count;
|
|
296
|
+
}
|
|
297
|
+
/** Get cache statistics. */
|
|
298
|
+
get stats() {
|
|
299
|
+
this._stats.size = this._cache.size;
|
|
300
|
+
return { ...this._stats };
|
|
301
|
+
}
|
|
302
|
+
/** Evict the oldest cache entry. */
|
|
303
|
+
_evictOldest() {
|
|
304
|
+
if (this._cache.size === 0) return;
|
|
305
|
+
let oldestKey = null;
|
|
306
|
+
let oldestTime = Infinity;
|
|
307
|
+
for (const [key, entry] of this._cache) {
|
|
308
|
+
if (entry.createdAt < oldestTime) {
|
|
309
|
+
oldestTime = entry.createdAt;
|
|
310
|
+
oldestKey = key;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
if (oldestKey !== null) {
|
|
314
|
+
this._cache.delete(oldestKey);
|
|
315
|
+
this._stats.evictions++;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
// src/runtime/applier.ts
|
|
323
|
+
function emptyResult(fix, fixType) {
|
|
324
|
+
return {
|
|
325
|
+
applied: false,
|
|
326
|
+
fixId: fix.fixId,
|
|
327
|
+
deploymentId: fix.deploymentId,
|
|
328
|
+
fixType,
|
|
329
|
+
modifications: {}
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
var import_node_crypto2, MAX_FIX_CONTENT_LENGTH, FixApplier;
|
|
333
|
+
var init_applier = __esm({
|
|
334
|
+
"src/runtime/applier.ts"() {
|
|
335
|
+
"use strict";
|
|
336
|
+
import_node_crypto2 = require("crypto");
|
|
337
|
+
init_config();
|
|
338
|
+
init_log();
|
|
339
|
+
MAX_FIX_CONTENT_LENGTH = 1e4;
|
|
340
|
+
FixApplier = class {
|
|
341
|
+
_config;
|
|
342
|
+
_cache;
|
|
343
|
+
_applicationLog = [];
|
|
344
|
+
constructor(config, cache) {
|
|
345
|
+
this._config = resolveFixRuntimeConfig(config);
|
|
346
|
+
this._cache = cache;
|
|
347
|
+
}
|
|
348
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
349
|
+
// Fix Lookup
|
|
350
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
351
|
+
/**
|
|
352
|
+
* Get applicable fix for an error code.
|
|
353
|
+
*
|
|
354
|
+
* Considers A/B testing bucket if sessionId is provided. Uses
|
|
355
|
+
* crypto.createHash('md5') for deterministic bucketing across restarts.
|
|
356
|
+
*/
|
|
357
|
+
getFixForError(errorCode, sessionId) {
|
|
358
|
+
try {
|
|
359
|
+
const fix = this._cache.get(errorCode);
|
|
360
|
+
if (!fix) return null;
|
|
361
|
+
if (sessionId && this._config.abTestingEnabled) {
|
|
362
|
+
const hashHex = (0, import_node_crypto2.createHash)("md5").update(sessionId).digest("hex").substring(0, 8);
|
|
363
|
+
const sessionHash = parseInt(hashHex, 16);
|
|
364
|
+
if (!shouldApply(fix, sessionHash)) {
|
|
365
|
+
debug(`Fix ${fix.fixId} not applied (A/B control group)`);
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
return fix;
|
|
370
|
+
} catch (e) {
|
|
371
|
+
debug(`getFixForError error: ${e}`);
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
376
|
+
// 1. Prompt Fix
|
|
377
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
378
|
+
/**
|
|
379
|
+
* Apply a prompt fix to messages.
|
|
380
|
+
*
|
|
381
|
+
* Supports 4 modification types:
|
|
382
|
+
* - prepend: Add content before first system message
|
|
383
|
+
* - append: Add content after first system message
|
|
384
|
+
* - replace: Regex replace across all messages
|
|
385
|
+
* - few_shot: Insert user/assistant example pairs after system message
|
|
386
|
+
*/
|
|
387
|
+
applyPromptFix(fix, messages) {
|
|
388
|
+
try {
|
|
389
|
+
const cfg = fix.config;
|
|
390
|
+
const modificationType = cfg.modification_type ?? cfg.modificationType ?? "append";
|
|
391
|
+
const target = cfg.target ?? "system";
|
|
392
|
+
let content = cfg.content ?? "";
|
|
393
|
+
if (content.length > MAX_FIX_CONTENT_LENGTH) {
|
|
394
|
+
warn(
|
|
395
|
+
`Fix ${fix.fixId} content truncated from ${content.length} to ${MAX_FIX_CONTENT_LENGTH} chars`
|
|
396
|
+
);
|
|
397
|
+
content = content.slice(0, MAX_FIX_CONTENT_LENGTH);
|
|
398
|
+
}
|
|
399
|
+
const result = emptyResult(fix, "prompt");
|
|
400
|
+
if (this._config.dryRun) {
|
|
401
|
+
result.modifications = {
|
|
402
|
+
type: modificationType,
|
|
403
|
+
target,
|
|
404
|
+
contentPreview: content.slice(0, 100)
|
|
405
|
+
};
|
|
406
|
+
return { messages, result };
|
|
407
|
+
}
|
|
408
|
+
let modified = messages.map((m) => ({ ...m }));
|
|
409
|
+
if (modificationType === "prepend") {
|
|
410
|
+
modified = this._prependToTarget(modified, target, content);
|
|
411
|
+
result.applied = true;
|
|
412
|
+
result.modifications = { type: "prepend", target };
|
|
413
|
+
} else if (modificationType === "append") {
|
|
414
|
+
modified = this._appendToTarget(modified, target, content);
|
|
415
|
+
result.applied = true;
|
|
416
|
+
result.modifications = { type: "append", target };
|
|
417
|
+
} else if (modificationType === "replace") {
|
|
418
|
+
const pattern = cfg.pattern ?? "";
|
|
419
|
+
if (pattern) {
|
|
420
|
+
modified = this._replaceInMessages(modified, pattern, content);
|
|
421
|
+
result.applied = true;
|
|
422
|
+
result.modifications = { type: "replace", pattern };
|
|
423
|
+
}
|
|
424
|
+
} else if (modificationType === "few_shot") {
|
|
425
|
+
const examples = cfg.examples ?? [];
|
|
426
|
+
if (examples.length > 0) {
|
|
427
|
+
modified = this._addFewShotExamples(modified, examples);
|
|
428
|
+
result.applied = true;
|
|
429
|
+
result.modifications = { type: "few_shot", count: examples.length };
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
return { messages: modified, result };
|
|
433
|
+
} catch (e) {
|
|
434
|
+
debug(`applyPromptFix error: ${e}`);
|
|
435
|
+
return {
|
|
436
|
+
messages,
|
|
437
|
+
result: {
|
|
438
|
+
applied: false,
|
|
439
|
+
fixId: fix.fixId,
|
|
440
|
+
fixType: "prompt",
|
|
441
|
+
modifications: {},
|
|
442
|
+
error: String(e)
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
448
|
+
// 2. Parameter Fix
|
|
449
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
450
|
+
/**
|
|
451
|
+
* Apply a parameter fix — merge config.parameters into params.
|
|
452
|
+
*/
|
|
453
|
+
applyParameterFix(fix, params) {
|
|
454
|
+
try {
|
|
455
|
+
const newParams = fix.config.parameters ?? {};
|
|
456
|
+
const result = emptyResult(fix, "parameter");
|
|
457
|
+
if (this._config.dryRun) {
|
|
458
|
+
result.modifications = { parameters: newParams };
|
|
459
|
+
return { params, result };
|
|
460
|
+
}
|
|
461
|
+
const modified = { ...params, ...newParams };
|
|
462
|
+
result.applied = true;
|
|
463
|
+
result.modifications = { parameters: newParams };
|
|
464
|
+
return { params: modified, result };
|
|
465
|
+
} catch (e) {
|
|
466
|
+
debug(`applyParameterFix error: ${e}`);
|
|
467
|
+
return {
|
|
468
|
+
params,
|
|
469
|
+
result: {
|
|
470
|
+
applied: false,
|
|
471
|
+
fixId: fix.fixId,
|
|
472
|
+
fixType: "parameter",
|
|
473
|
+
modifications: {},
|
|
474
|
+
error: String(e)
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
480
|
+
// 3. Retry Fix (async — uses setTimeout for sleeping)
|
|
481
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
482
|
+
/**
|
|
483
|
+
* Apply a retry fix to an async operation.
|
|
484
|
+
*
|
|
485
|
+
* Retries with exponential backoff + jitter using
|
|
486
|
+
* `await new Promise(r => setTimeout(r, delay))`.
|
|
487
|
+
*/
|
|
488
|
+
async applyRetryFix(fix, operation) {
|
|
489
|
+
const cfg = fix.config;
|
|
490
|
+
const maxRetries = cfg.max_retries ?? cfg.maxRetries ?? 3;
|
|
491
|
+
const initialDelayMs = cfg.initial_delay_ms ?? cfg.initialDelayMs ?? 1e3;
|
|
492
|
+
const maxDelayMs = cfg.max_delay_ms ?? cfg.maxDelayMs ?? 3e4;
|
|
493
|
+
const exponentialBase = cfg.exponential_base ?? cfg.exponentialBase ?? 2;
|
|
494
|
+
const jitter = cfg.jitter ?? true;
|
|
495
|
+
const retryOn = cfg.retry_on ?? cfg.retryOn ?? [];
|
|
496
|
+
const result = emptyResult(fix, "retry");
|
|
497
|
+
if (this._config.dryRun) {
|
|
498
|
+
result.modifications = { maxRetries, initialDelayMs };
|
|
499
|
+
const value = await operation();
|
|
500
|
+
return { value, result };
|
|
501
|
+
}
|
|
502
|
+
let lastError = null;
|
|
503
|
+
let attempts = 0;
|
|
504
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
505
|
+
attempts++;
|
|
506
|
+
try {
|
|
507
|
+
const value = await operation();
|
|
508
|
+
result.applied = true;
|
|
509
|
+
result.modifications = { attempts };
|
|
510
|
+
return { value, result };
|
|
511
|
+
} catch (e) {
|
|
512
|
+
lastError = e instanceof Error ? e : new Error(String(e));
|
|
513
|
+
const errorType = lastError.constructor.name;
|
|
514
|
+
if (retryOn.length > 0 && !retryOn.includes(errorType)) {
|
|
515
|
+
throw lastError;
|
|
516
|
+
}
|
|
517
|
+
if (attempt < maxRetries) {
|
|
518
|
+
let delayMs = Math.min(
|
|
519
|
+
initialDelayMs * Math.pow(exponentialBase, attempt),
|
|
520
|
+
maxDelayMs
|
|
521
|
+
);
|
|
522
|
+
if (jitter) {
|
|
523
|
+
delayMs *= 0.5 + Math.random();
|
|
524
|
+
}
|
|
525
|
+
debug(
|
|
526
|
+
`Retry ${attempt + 1}/${maxRetries} after ${Math.round(delayMs)}ms`
|
|
527
|
+
);
|
|
528
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
result.applied = true;
|
|
533
|
+
result.error = lastError?.message;
|
|
534
|
+
result.modifications = { attempts, exhausted: true };
|
|
535
|
+
throw lastError;
|
|
536
|
+
}
|
|
537
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
538
|
+
// 4. Fallback Fix
|
|
539
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
540
|
+
/**
|
|
541
|
+
* Apply a fallback fix.
|
|
542
|
+
*
|
|
543
|
+
* - 'model': Replace params.model with the fallback model
|
|
544
|
+
* - 'default': Set params._fallbackResponse for the caller to use
|
|
545
|
+
*/
|
|
546
|
+
applyFallbackFix(fix, params) {
|
|
547
|
+
try {
|
|
548
|
+
const cfg = fix.config;
|
|
549
|
+
const fallbackType = cfg.fallback_type ?? cfg.fallbackType ?? "model";
|
|
550
|
+
const fallbackConfig = cfg.fallback_config ?? cfg.fallbackConfig ?? {};
|
|
551
|
+
const result = emptyResult(fix, "fallback");
|
|
552
|
+
if (this._config.dryRun) {
|
|
553
|
+
result.modifications = { fallbackType, fallbackConfig };
|
|
554
|
+
return { params, result };
|
|
555
|
+
}
|
|
556
|
+
const modified = { ...params };
|
|
557
|
+
if (fallbackType === "model") {
|
|
558
|
+
const fallbackModel = fallbackConfig.model;
|
|
559
|
+
if (fallbackModel) {
|
|
560
|
+
modified.model = fallbackModel;
|
|
561
|
+
result.applied = true;
|
|
562
|
+
result.modifications = { model: fallbackModel };
|
|
563
|
+
}
|
|
564
|
+
} else if (fallbackType === "default") {
|
|
565
|
+
const defaultResponse = fallbackConfig.response;
|
|
566
|
+
if (defaultResponse !== void 0) {
|
|
567
|
+
modified._fallbackResponse = defaultResponse;
|
|
568
|
+
result.applied = true;
|
|
569
|
+
result.modifications = { defaultResponse: true };
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
return { params: modified, result };
|
|
573
|
+
} catch (e) {
|
|
574
|
+
debug(`applyFallbackFix error: ${e}`);
|
|
575
|
+
return {
|
|
576
|
+
params,
|
|
577
|
+
result: {
|
|
578
|
+
applied: false,
|
|
579
|
+
fixId: fix.fixId,
|
|
580
|
+
fixType: "fallback",
|
|
581
|
+
modifications: {},
|
|
582
|
+
error: String(e)
|
|
583
|
+
}
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
588
|
+
// 5. Guard Fix
|
|
589
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
590
|
+
/**
|
|
591
|
+
* Apply a guard fix (validation).
|
|
592
|
+
*
|
|
593
|
+
* Guard types:
|
|
594
|
+
* - content_filter: Regex-based blocked patterns
|
|
595
|
+
* - format_check: JSON.parse() validity
|
|
596
|
+
* - input_validation / output_validation: min/max length
|
|
597
|
+
*/
|
|
598
|
+
applyGuardFix(fix, content, isInput = true) {
|
|
599
|
+
try {
|
|
600
|
+
const cfg = fix.config;
|
|
601
|
+
const guardType = cfg.guard_type ?? cfg.guardType ?? "output_validation";
|
|
602
|
+
const guardConfig = cfg.guard_config ?? cfg.guardConfig ?? {};
|
|
603
|
+
const result = emptyResult(fix, "guard");
|
|
604
|
+
if (isInput && guardType !== "input_validation" && guardType !== "content_filter") {
|
|
605
|
+
return { content, passed: true, result };
|
|
606
|
+
}
|
|
607
|
+
if (!isInput && guardType !== "output_validation" && guardType !== "format_check") {
|
|
608
|
+
return { content, passed: true, result };
|
|
609
|
+
}
|
|
610
|
+
if (this._config.dryRun) {
|
|
611
|
+
result.modifications = { guardType };
|
|
612
|
+
return { content, passed: true, result };
|
|
613
|
+
}
|
|
614
|
+
let passed = true;
|
|
615
|
+
if (guardType === "content_filter") {
|
|
616
|
+
const blockedPatterns = guardConfig.blocked_patterns ?? guardConfig.blockedPatterns ?? [];
|
|
617
|
+
for (const pattern of blockedPatterns) {
|
|
618
|
+
const regex = this._safeCompileRegex(pattern);
|
|
619
|
+
if (regex && regex.test(content)) {
|
|
620
|
+
passed = false;
|
|
621
|
+
break;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
} else if (guardType === "format_check") {
|
|
625
|
+
const requiredFormat = guardConfig.format;
|
|
626
|
+
if (requiredFormat === "json") {
|
|
627
|
+
try {
|
|
628
|
+
JSON.parse(content);
|
|
629
|
+
} catch {
|
|
630
|
+
passed = false;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
} else if (guardType === "input_validation" || guardType === "output_validation") {
|
|
634
|
+
const minLength = guardConfig.min_length ?? guardConfig.minLength ?? 0;
|
|
635
|
+
const maxLength = guardConfig.max_length ?? guardConfig.maxLength ?? Infinity;
|
|
636
|
+
if (content.length < minLength || content.length > maxLength) {
|
|
637
|
+
passed = false;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
result.applied = true;
|
|
641
|
+
result.modifications = { passed, guardType };
|
|
642
|
+
return { content, passed, result };
|
|
643
|
+
} catch (e) {
|
|
644
|
+
debug(`applyGuardFix error: ${e}`);
|
|
645
|
+
return {
|
|
646
|
+
content,
|
|
647
|
+
passed: true,
|
|
648
|
+
result: {
|
|
649
|
+
applied: false,
|
|
650
|
+
fixId: fix.fixId,
|
|
651
|
+
fixType: "guard",
|
|
652
|
+
modifications: {},
|
|
653
|
+
error: String(e)
|
|
654
|
+
}
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
659
|
+
// Application Log
|
|
660
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
661
|
+
/** Log a fix application result. */
|
|
662
|
+
logApplication(result) {
|
|
663
|
+
this._applicationLog.push(result);
|
|
664
|
+
if (this._applicationLog.length > 1e3) {
|
|
665
|
+
this._applicationLog = this._applicationLog.slice(-500);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
/** Get the application log. */
|
|
669
|
+
getApplicationLog() {
|
|
670
|
+
return [...this._applicationLog];
|
|
671
|
+
}
|
|
672
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
673
|
+
// Private Helpers
|
|
674
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
675
|
+
/**
|
|
676
|
+
* Safely compile a regex pattern from untrusted input.
|
|
677
|
+
* Returns null if invalid or too long.
|
|
678
|
+
*/
|
|
679
|
+
_safeCompileRegex(pattern, maxLength = 500) {
|
|
680
|
+
if (!pattern || pattern.length > maxLength) return null;
|
|
681
|
+
try {
|
|
682
|
+
return new RegExp(pattern, "i");
|
|
683
|
+
} catch {
|
|
684
|
+
debug(`Invalid regex pattern (length=${pattern.length})`);
|
|
685
|
+
return null;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
/** Prepend content to the first message matching the target role. */
|
|
689
|
+
_prependToTarget(messages, target, content) {
|
|
690
|
+
return messages.map((msg) => {
|
|
691
|
+
if (msg.role === target) {
|
|
692
|
+
return {
|
|
693
|
+
...msg,
|
|
694
|
+
content: content + "\n\n" + (msg.content ?? "")
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
return msg;
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
/** Append content to the first message matching the target role. */
|
|
701
|
+
_appendToTarget(messages, target, content) {
|
|
702
|
+
return messages.map((msg) => {
|
|
703
|
+
if (msg.role === target) {
|
|
704
|
+
return {
|
|
705
|
+
...msg,
|
|
706
|
+
content: (msg.content ?? "") + "\n\n" + content
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
return msg;
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
/** Regex replace across all messages. */
|
|
713
|
+
_replaceInMessages(messages, pattern, replacement) {
|
|
714
|
+
const regex = this._safeCompileRegex(pattern);
|
|
715
|
+
if (!regex) {
|
|
716
|
+
debug("Skipping invalid regex pattern in prompt fix");
|
|
717
|
+
return messages;
|
|
718
|
+
}
|
|
719
|
+
return messages.map((msg) => {
|
|
720
|
+
const msgContent = msg.content;
|
|
721
|
+
if (typeof msgContent === "string") {
|
|
722
|
+
return { ...msg, content: msgContent.replace(regex, replacement) };
|
|
723
|
+
}
|
|
724
|
+
return msg;
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
/** Add few-shot examples after the first system message. */
|
|
728
|
+
_addFewShotExamples(messages, examples) {
|
|
729
|
+
const result = [];
|
|
730
|
+
let systemFound = false;
|
|
731
|
+
for (const msg of messages) {
|
|
732
|
+
result.push(msg);
|
|
733
|
+
if (msg.role === "system" && !systemFound) {
|
|
734
|
+
systemFound = true;
|
|
735
|
+
for (const example of examples) {
|
|
736
|
+
if (example.user) {
|
|
737
|
+
result.push({ role: "user", content: example.user });
|
|
738
|
+
}
|
|
739
|
+
if (example.assistant) {
|
|
740
|
+
result.push({ role: "assistant", content: example.assistant });
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
return result;
|
|
746
|
+
}
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
});
|
|
750
|
+
|
|
751
|
+
// src/runtime/loader.ts
|
|
752
|
+
var SDK_VERSION2, FixLoader;
|
|
753
|
+
var init_loader = __esm({
|
|
754
|
+
"src/runtime/loader.ts"() {
|
|
755
|
+
"use strict";
|
|
756
|
+
init_config();
|
|
757
|
+
init_config();
|
|
758
|
+
init_log();
|
|
759
|
+
SDK_VERSION2 = "0.1.3";
|
|
760
|
+
FixLoader = class _FixLoader {
|
|
761
|
+
_config;
|
|
762
|
+
_cache;
|
|
763
|
+
_refreshTimer = null;
|
|
764
|
+
_lastLoadTime = null;
|
|
765
|
+
_consecutiveFailures = 0;
|
|
766
|
+
_circuitOpenUntil = 0;
|
|
767
|
+
_onLoadCallbacks = [];
|
|
768
|
+
// Circuit breaker settings
|
|
769
|
+
static CIRCUIT_BREAKER_THRESHOLD = 3;
|
|
770
|
+
static CIRCUIT_BREAKER_COOLDOWN_MS = 3e4;
|
|
771
|
+
constructor(config, cache) {
|
|
772
|
+
this._config = resolveFixRuntimeConfig(config);
|
|
773
|
+
this._cache = cache;
|
|
774
|
+
}
|
|
775
|
+
/** Timestamp of last successful load. */
|
|
776
|
+
get lastLoadTime() {
|
|
777
|
+
return this._lastLoadTime;
|
|
778
|
+
}
|
|
779
|
+
/**
|
|
780
|
+
* Start the loader: perform initial load and start background refresh.
|
|
781
|
+
*
|
|
782
|
+
* Never throws — failures are logged and the runtime degrades gracefully.
|
|
783
|
+
*/
|
|
784
|
+
start() {
|
|
785
|
+
try {
|
|
786
|
+
if (!this._config.enabled) {
|
|
787
|
+
debug("Fix runtime disabled, skipping loader start");
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
if (!this._config.apiKey) {
|
|
791
|
+
warn("No API key configured, fixes will not be loaded");
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
this.loadSync().catch((e) => {
|
|
795
|
+
debug(`Initial fix load failed: ${e}`);
|
|
796
|
+
});
|
|
797
|
+
if (this._config.autoRefresh) {
|
|
798
|
+
this._startRefreshInterval();
|
|
799
|
+
}
|
|
800
|
+
} catch (e) {
|
|
801
|
+
debug(`FixLoader.start error: ${e}`);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
/** Stop the loader: clear the refresh interval. */
|
|
805
|
+
stop() {
|
|
806
|
+
try {
|
|
807
|
+
if (this._refreshTimer !== null) {
|
|
808
|
+
clearInterval(this._refreshTimer);
|
|
809
|
+
this._refreshTimer = null;
|
|
810
|
+
}
|
|
811
|
+
} catch (e) {
|
|
812
|
+
debug(`FixLoader.stop error: ${e}`);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
/**
|
|
816
|
+
* Register a callback invoked after each successful load.
|
|
817
|
+
*/
|
|
818
|
+
onLoad(callback) {
|
|
819
|
+
this._onLoadCallbacks.push(callback);
|
|
820
|
+
}
|
|
821
|
+
/**
|
|
822
|
+
* Load fixes from the API.
|
|
823
|
+
*
|
|
824
|
+
* Name kept as "loadSync" for parity with the Python SDK, but this
|
|
825
|
+
* returns a Promise in JS (there's no synchronous fetch in Node.js).
|
|
826
|
+
*/
|
|
827
|
+
async loadSync() {
|
|
828
|
+
if (!this._config.apiKey) {
|
|
829
|
+
return [];
|
|
830
|
+
}
|
|
831
|
+
const now = Date.now();
|
|
832
|
+
if (this._consecutiveFailures >= _FixLoader.CIRCUIT_BREAKER_THRESHOLD && now < this._circuitOpenUntil) {
|
|
833
|
+
debug("Fix loader circuit breaker open \u2014 skipping load");
|
|
834
|
+
return this._cache.getAll();
|
|
835
|
+
}
|
|
836
|
+
try {
|
|
837
|
+
const endpoint = this._config.apiEndpoint.replace(/\/+$/, "");
|
|
838
|
+
const url = `${endpoint}/api/v1/fixes/active`;
|
|
839
|
+
const controller = new AbortController();
|
|
840
|
+
const timeoutId = setTimeout(
|
|
841
|
+
() => controller.abort(),
|
|
842
|
+
this._config.timeoutMs
|
|
843
|
+
);
|
|
844
|
+
const response = await fetch(url, {
|
|
845
|
+
method: "GET",
|
|
846
|
+
headers: {
|
|
847
|
+
"Authorization": `Bearer ${this._config.apiKey}`,
|
|
848
|
+
"Content-Type": "application/json",
|
|
849
|
+
"X-Risicare-SDK-Version": SDK_VERSION2
|
|
850
|
+
},
|
|
851
|
+
signal: controller.signal
|
|
852
|
+
});
|
|
853
|
+
clearTimeout(timeoutId);
|
|
854
|
+
if (!response.ok) {
|
|
855
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
856
|
+
}
|
|
857
|
+
const data = await response.json();
|
|
858
|
+
const fixes = this._parseResponse(data);
|
|
859
|
+
this._onLoadSuccess(fixes);
|
|
860
|
+
return fixes;
|
|
861
|
+
} catch (e) {
|
|
862
|
+
this._onLoadFailure(e);
|
|
863
|
+
throw e;
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
/** Get currently cached fixes without hitting the API. */
|
|
867
|
+
getCached() {
|
|
868
|
+
return this._cache.getAll();
|
|
869
|
+
}
|
|
870
|
+
// ─── Private ────────────────────────────────────────────────────────────
|
|
871
|
+
_parseResponse(data) {
|
|
872
|
+
const fixes = [];
|
|
873
|
+
const items = data.fixes ?? data.data ?? [];
|
|
874
|
+
if (!Array.isArray(items)) return fixes;
|
|
875
|
+
for (const item of items) {
|
|
876
|
+
try {
|
|
877
|
+
if (item && typeof item === "object") {
|
|
878
|
+
fixes.push(activeFixFromApiResponse(item));
|
|
879
|
+
}
|
|
880
|
+
} catch (e) {
|
|
881
|
+
debug(`Failed to parse fix item: ${e}`);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
return fixes;
|
|
885
|
+
}
|
|
886
|
+
_onLoadSuccess(fixes) {
|
|
887
|
+
this._lastLoadTime = Date.now();
|
|
888
|
+
this._consecutiveFailures = 0;
|
|
889
|
+
this._cache.setAll(fixes);
|
|
890
|
+
for (const callback of this._onLoadCallbacks) {
|
|
891
|
+
try {
|
|
892
|
+
callback(fixes);
|
|
893
|
+
} catch (e) {
|
|
894
|
+
debug(`Load callback error: ${e}`);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
debug(`Loaded ${fixes.length} active fixes`);
|
|
898
|
+
}
|
|
899
|
+
_onLoadFailure(error) {
|
|
900
|
+
this._consecutiveFailures++;
|
|
901
|
+
if (this._consecutiveFailures >= _FixLoader.CIRCUIT_BREAKER_THRESHOLD) {
|
|
902
|
+
this._circuitOpenUntil = Date.now() + _FixLoader.CIRCUIT_BREAKER_COOLDOWN_MS;
|
|
903
|
+
warn(
|
|
904
|
+
`Fix loader circuit breaker opened after ${this._consecutiveFailures} failures. Cooldown: ${_FixLoader.CIRCUIT_BREAKER_COOLDOWN_MS / 1e3}s`
|
|
905
|
+
);
|
|
906
|
+
} else {
|
|
907
|
+
debug(
|
|
908
|
+
`Fix load failed (attempt ${this._consecutiveFailures}): ${error.message}`
|
|
909
|
+
);
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
_startRefreshInterval() {
|
|
913
|
+
const tick = () => {
|
|
914
|
+
this.loadSync().catch((e) => {
|
|
915
|
+
debug(`Background fix refresh failed: ${e}`);
|
|
916
|
+
});
|
|
917
|
+
};
|
|
918
|
+
this._refreshTimer = setInterval(
|
|
919
|
+
() => {
|
|
920
|
+
if (this._consecutiveFailures >= _FixLoader.CIRCUIT_BREAKER_THRESHOLD && Date.now() < this._circuitOpenUntil) {
|
|
921
|
+
return;
|
|
922
|
+
}
|
|
923
|
+
tick();
|
|
924
|
+
},
|
|
925
|
+
this._config.refreshIntervalMs
|
|
926
|
+
);
|
|
927
|
+
this._refreshTimer.unref();
|
|
928
|
+
debug("Started fix refresh interval");
|
|
929
|
+
}
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
});
|
|
933
|
+
|
|
934
|
+
// src/runtime/interceptors.ts
|
|
935
|
+
function createInterceptContext(operationType, operationName, options) {
|
|
936
|
+
return {
|
|
937
|
+
operationType,
|
|
938
|
+
operationName,
|
|
939
|
+
sessionId: options?.sessionId,
|
|
940
|
+
traceId: options?.traceId,
|
|
941
|
+
errorCode: options?.errorCode,
|
|
942
|
+
attempt: 1,
|
|
943
|
+
appliedFixes: []
|
|
944
|
+
};
|
|
945
|
+
}
|
|
946
|
+
function classifyError(error) {
|
|
947
|
+
const errorType = error.constructor.name.toLowerCase();
|
|
948
|
+
const errorMsg = error.message.toLowerCase();
|
|
949
|
+
if (errorType.includes("timeout") || errorMsg.includes("timeout") || errorMsg.includes("aborted")) {
|
|
950
|
+
return "TOOL.EXECUTION.TIMEOUT";
|
|
951
|
+
}
|
|
952
|
+
if (errorMsg.includes("rate") && errorMsg.includes("limit")) {
|
|
953
|
+
return "TOOL.EXECUTION.RATE_LIMIT";
|
|
954
|
+
}
|
|
955
|
+
if (errorMsg.includes("429") || errorMsg.includes("too many requests")) {
|
|
956
|
+
return "TOOL.EXECUTION.RATE_LIMIT";
|
|
957
|
+
}
|
|
958
|
+
if (errorType.includes("connection") || errorMsg.includes("connection") || errorMsg.includes("econnrefused") || errorMsg.includes("econnreset") || errorMsg.includes("fetch failed")) {
|
|
959
|
+
return "TOOL.EXECUTION.CONNECTION_ERROR";
|
|
960
|
+
}
|
|
961
|
+
if (errorMsg.includes("auth") || errorMsg.includes("401") || errorMsg.includes("403") || errorMsg.includes("unauthorized") || errorMsg.includes("forbidden")) {
|
|
962
|
+
return "TOOL.EXECUTION.AUTH_ERROR";
|
|
963
|
+
}
|
|
964
|
+
if (errorType.includes("syntaxerror") || errorType.includes("json") || errorMsg.includes("json") || errorMsg.includes("unexpected token")) {
|
|
965
|
+
return "OUTPUT.FORMAT.JSON_INVALID";
|
|
966
|
+
}
|
|
967
|
+
return "TOOL.EXECUTION.FAILURE";
|
|
968
|
+
}
|
|
969
|
+
var DefaultFixInterceptor;
|
|
970
|
+
var init_interceptors = __esm({
|
|
971
|
+
"src/runtime/interceptors.ts"() {
|
|
972
|
+
"use strict";
|
|
973
|
+
init_config();
|
|
974
|
+
init_log();
|
|
975
|
+
DefaultFixInterceptor = class {
|
|
976
|
+
_config;
|
|
977
|
+
_cache;
|
|
978
|
+
_applier;
|
|
979
|
+
constructor(config, cache, applier) {
|
|
980
|
+
this._config = resolveFixRuntimeConfig(config);
|
|
981
|
+
this._cache = cache;
|
|
982
|
+
this._applier = applier;
|
|
983
|
+
}
|
|
984
|
+
/**
|
|
985
|
+
* Apply pre-call fixes.
|
|
986
|
+
*
|
|
987
|
+
* - If retrying after error: look up fix by error_code, apply
|
|
988
|
+
* prompt/parameter/fallback fixes
|
|
989
|
+
* - Runs input guard fixes on all message content
|
|
990
|
+
*/
|
|
991
|
+
preCall(ctx, messages, params) {
|
|
992
|
+
try {
|
|
993
|
+
if (!this._config.enabled) {
|
|
994
|
+
return { messages, params };
|
|
995
|
+
}
|
|
996
|
+
let modifiedMessages = messages;
|
|
997
|
+
let modifiedParams = { ...params };
|
|
998
|
+
if (ctx.errorCode) {
|
|
999
|
+
const fix = this._applier.getFixForError(ctx.errorCode, ctx.sessionId);
|
|
1000
|
+
if (fix) {
|
|
1001
|
+
debug(`Applying fix ${fix.fixId} for ${ctx.errorCode}`);
|
|
1002
|
+
if (fix.fixType === "prompt" && messages) {
|
|
1003
|
+
const msgRecords = messages;
|
|
1004
|
+
const { messages: newMessages, result } = this._applier.applyPromptFix(fix, msgRecords);
|
|
1005
|
+
modifiedMessages = newMessages;
|
|
1006
|
+
ctx.appliedFixes.push(result);
|
|
1007
|
+
this._applier.logApplication(result);
|
|
1008
|
+
} else if (fix.fixType === "parameter") {
|
|
1009
|
+
const { params: newParams, result } = this._applier.applyParameterFix(fix, modifiedParams);
|
|
1010
|
+
modifiedParams = newParams;
|
|
1011
|
+
ctx.appliedFixes.push(result);
|
|
1012
|
+
this._applier.logApplication(result);
|
|
1013
|
+
} else if (fix.fixType === "fallback") {
|
|
1014
|
+
const { params: newParams, result } = this._applier.applyFallbackFix(fix, modifiedParams);
|
|
1015
|
+
modifiedParams = newParams;
|
|
1016
|
+
ctx.appliedFixes.push(result);
|
|
1017
|
+
this._applier.logApplication(result);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
if (modifiedMessages && this._config.enabled) {
|
|
1022
|
+
for (const msg of modifiedMessages) {
|
|
1023
|
+
const content = msg.content;
|
|
1024
|
+
if (typeof content === "string") {
|
|
1025
|
+
for (const fix of this._cache.getAll()) {
|
|
1026
|
+
if (fix.fixType === "guard") {
|
|
1027
|
+
const { result } = this._applier.applyGuardFix(
|
|
1028
|
+
fix,
|
|
1029
|
+
content,
|
|
1030
|
+
true
|
|
1031
|
+
);
|
|
1032
|
+
if (result.applied) {
|
|
1033
|
+
ctx.appliedFixes.push(result);
|
|
1034
|
+
this._applier.logApplication(result);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
return { messages: modifiedMessages, params: modifiedParams };
|
|
1042
|
+
} catch (e) {
|
|
1043
|
+
debug(`DefaultFixInterceptor.preCall error: ${e}`);
|
|
1044
|
+
return { messages, params };
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
/**
|
|
1048
|
+
* Apply post-call fixes (output validation).
|
|
1049
|
+
*
|
|
1050
|
+
* Runs output guard fixes on response content.
|
|
1051
|
+
*/
|
|
1052
|
+
postCall(ctx, response) {
|
|
1053
|
+
try {
|
|
1054
|
+
if (!this._config.enabled) {
|
|
1055
|
+
return { response, shouldContinue: true };
|
|
1056
|
+
}
|
|
1057
|
+
for (const fix of this._cache.getAll()) {
|
|
1058
|
+
if (fix.fixType !== "guard") continue;
|
|
1059
|
+
const content = this._extractResponseContent(response);
|
|
1060
|
+
if (!content) continue;
|
|
1061
|
+
const { passed, result } = this._applier.applyGuardFix(
|
|
1062
|
+
fix,
|
|
1063
|
+
content,
|
|
1064
|
+
false
|
|
1065
|
+
);
|
|
1066
|
+
if (result.applied) {
|
|
1067
|
+
ctx.appliedFixes.push(result);
|
|
1068
|
+
this._applier.logApplication(result);
|
|
1069
|
+
}
|
|
1070
|
+
if (!passed) {
|
|
1071
|
+
warn(`Output guard failed for fix ${fix.fixId}`);
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
return { response, shouldContinue: true };
|
|
1075
|
+
} catch (e) {
|
|
1076
|
+
debug(`DefaultFixInterceptor.postCall error: ${e}`);
|
|
1077
|
+
return { response, shouldContinue: true };
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
/**
|
|
1081
|
+
* Handle errors and decide whether to retry.
|
|
1082
|
+
*
|
|
1083
|
+
* Classifies the error, looks up retry/fallback fixes, and returns
|
|
1084
|
+
* whether the caller should retry.
|
|
1085
|
+
*/
|
|
1086
|
+
onError(ctx, error) {
|
|
1087
|
+
try {
|
|
1088
|
+
if (!this._config.enabled) {
|
|
1089
|
+
return { shouldRetry: false, modifiedParams: null };
|
|
1090
|
+
}
|
|
1091
|
+
const errorCode = classifyError(error);
|
|
1092
|
+
ctx.errorCode = errorCode;
|
|
1093
|
+
ctx.errorMessage = error.message;
|
|
1094
|
+
const fix = this._applier.getFixForError(errorCode, ctx.sessionId);
|
|
1095
|
+
if (!fix) {
|
|
1096
|
+
return { shouldRetry: false, modifiedParams: null };
|
|
1097
|
+
}
|
|
1098
|
+
if (fix.fixType === "retry") {
|
|
1099
|
+
const maxRetries = fix.config.max_retries ?? fix.config.maxRetries ?? 3;
|
|
1100
|
+
if (ctx.attempt <= maxRetries) {
|
|
1101
|
+
debug(
|
|
1102
|
+
`Retry fix ${fix.fixId}: attempt ${ctx.attempt}/${maxRetries}`
|
|
1103
|
+
);
|
|
1104
|
+
const result = {
|
|
1105
|
+
applied: true,
|
|
1106
|
+
fixId: fix.fixId,
|
|
1107
|
+
deploymentId: fix.deploymentId,
|
|
1108
|
+
fixType: "retry",
|
|
1109
|
+
modifications: { attempt: ctx.attempt }
|
|
1110
|
+
};
|
|
1111
|
+
ctx.appliedFixes.push(result);
|
|
1112
|
+
this._applier.logApplication(result);
|
|
1113
|
+
return { shouldRetry: true, modifiedParams: null };
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
if (fix.fixType === "fallback") {
|
|
1117
|
+
const { params: modifiedParams, result } = this._applier.applyFallbackFix(fix, {});
|
|
1118
|
+
if (result.applied) {
|
|
1119
|
+
ctx.appliedFixes.push(result);
|
|
1120
|
+
this._applier.logApplication(result);
|
|
1121
|
+
return { shouldRetry: true, modifiedParams };
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
return { shouldRetry: false, modifiedParams: null };
|
|
1125
|
+
} catch (e) {
|
|
1126
|
+
debug(`DefaultFixInterceptor.onError error: ${e}`);
|
|
1127
|
+
return { shouldRetry: false, modifiedParams: null };
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
// ─── Private ────────────────────────────────────────────────────────────
|
|
1131
|
+
/** Extract text content from a response object. */
|
|
1132
|
+
_extractResponseContent(response) {
|
|
1133
|
+
if (typeof response === "string") return response;
|
|
1134
|
+
if (response && typeof response === "object") {
|
|
1135
|
+
const resp = response;
|
|
1136
|
+
const choices = resp.choices;
|
|
1137
|
+
if (Array.isArray(choices) && choices.length > 0) {
|
|
1138
|
+
const first = choices[0];
|
|
1139
|
+
const message = first?.message;
|
|
1140
|
+
if (typeof message?.content === "string") {
|
|
1141
|
+
return message.content;
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
const content = resp.content;
|
|
1145
|
+
if (typeof content === "string") return content;
|
|
1146
|
+
if (Array.isArray(content) && content.length > 0) {
|
|
1147
|
+
const first = content[0];
|
|
1148
|
+
if (typeof first?.text === "string") return first.text;
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
return null;
|
|
1152
|
+
}
|
|
1153
|
+
};
|
|
1154
|
+
}
|
|
1155
|
+
});
|
|
1156
|
+
|
|
1157
|
+
// src/runtime/runtime.ts
|
|
1158
|
+
var runtime_exports = {};
|
|
1159
|
+
__export(runtime_exports, {
|
|
1160
|
+
FixRuntime: () => FixRuntime,
|
|
1161
|
+
getFixRuntime: () => getFixRuntime,
|
|
1162
|
+
initFixRuntime: () => initFixRuntime,
|
|
1163
|
+
setFixRuntime: () => setFixRuntime,
|
|
1164
|
+
shutdownFixRuntime: () => shutdownFixRuntime
|
|
1165
|
+
});
|
|
1166
|
+
function getFixRuntime() {
|
|
1167
|
+
return G2[RUNTIME_KEY];
|
|
1168
|
+
}
|
|
1169
|
+
function setFixRuntime(runtime) {
|
|
1170
|
+
G2[RUNTIME_KEY] = runtime;
|
|
1171
|
+
}
|
|
1172
|
+
function initFixRuntime(config) {
|
|
1173
|
+
try {
|
|
1174
|
+
const existing = G2[RUNTIME_KEY];
|
|
1175
|
+
if (existing) {
|
|
1176
|
+
debug("Fix runtime already initialized");
|
|
1177
|
+
return existing;
|
|
1178
|
+
}
|
|
1179
|
+
const runtime = new FixRuntime(config);
|
|
1180
|
+
runtime.start();
|
|
1181
|
+
G2[RUNTIME_KEY] = runtime;
|
|
1182
|
+
return runtime;
|
|
1183
|
+
} catch (e) {
|
|
1184
|
+
warn(`initFixRuntime failed: ${e}`);
|
|
1185
|
+
const fallback = new FixRuntime({ ...config, enabled: false });
|
|
1186
|
+
G2[RUNTIME_KEY] = fallback;
|
|
1187
|
+
return fallback;
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
function shutdownFixRuntime() {
|
|
1191
|
+
try {
|
|
1192
|
+
const runtime = G2[RUNTIME_KEY];
|
|
1193
|
+
if (runtime) {
|
|
1194
|
+
runtime.stop();
|
|
1195
|
+
}
|
|
1196
|
+
G2[RUNTIME_KEY] = void 0;
|
|
1197
|
+
} catch (e) {
|
|
1198
|
+
debug(`shutdownFixRuntime error: ${e}`);
|
|
1199
|
+
G2[RUNTIME_KEY] = void 0;
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
var G2, RUNTIME_KEY, FixRuntime;
|
|
1203
|
+
var init_runtime = __esm({
|
|
1204
|
+
"src/runtime/runtime.ts"() {
|
|
1205
|
+
"use strict";
|
|
1206
|
+
init_cache();
|
|
1207
|
+
init_config();
|
|
1208
|
+
init_applier();
|
|
1209
|
+
init_loader();
|
|
1210
|
+
init_interceptors();
|
|
1211
|
+
init_log();
|
|
1212
|
+
G2 = globalThis;
|
|
1213
|
+
RUNTIME_KEY = "__risicare_fix_runtime";
|
|
1214
|
+
FixRuntime = class {
|
|
1215
|
+
_config;
|
|
1216
|
+
_cache;
|
|
1217
|
+
_loader;
|
|
1218
|
+
_applier;
|
|
1219
|
+
_interceptor;
|
|
1220
|
+
_started = false;
|
|
1221
|
+
// Effectiveness tracking
|
|
1222
|
+
_fixApplications = /* @__PURE__ */ new Map();
|
|
1223
|
+
_fixSuccesses = /* @__PURE__ */ new Map();
|
|
1224
|
+
constructor(config) {
|
|
1225
|
+
this._config = resolveFixRuntimeConfig(config);
|
|
1226
|
+
this._cache = new FixCache(this._config);
|
|
1227
|
+
this._loader = new FixLoader(config, this._cache);
|
|
1228
|
+
this._applier = new FixApplier(config, this._cache);
|
|
1229
|
+
this._interceptor = new DefaultFixInterceptor(
|
|
1230
|
+
config,
|
|
1231
|
+
this._cache,
|
|
1232
|
+
this._applier
|
|
1233
|
+
);
|
|
1234
|
+
}
|
|
1235
|
+
// ─── Accessors ──────────────────────────────────────────────────────────
|
|
1236
|
+
/** Runtime configuration (with defaults resolved). */
|
|
1237
|
+
get config() {
|
|
1238
|
+
return this._config;
|
|
1239
|
+
}
|
|
1240
|
+
/** Whether the runtime is enabled and started. */
|
|
1241
|
+
get isEnabled() {
|
|
1242
|
+
return this._config.enabled && this._started;
|
|
1243
|
+
}
|
|
1244
|
+
/** The fix cache. */
|
|
1245
|
+
get cache() {
|
|
1246
|
+
return this._cache;
|
|
1247
|
+
}
|
|
1248
|
+
/** The fix loader. */
|
|
1249
|
+
get loader() {
|
|
1250
|
+
return this._loader;
|
|
1251
|
+
}
|
|
1252
|
+
/** The fix applier. */
|
|
1253
|
+
get applier() {
|
|
1254
|
+
return this._applier;
|
|
1255
|
+
}
|
|
1256
|
+
/** The interceptor. */
|
|
1257
|
+
get interceptor() {
|
|
1258
|
+
return this._interceptor;
|
|
1259
|
+
}
|
|
1260
|
+
// ─── Lifecycle ──────────────────────────────────────────────────────────
|
|
1261
|
+
/**
|
|
1262
|
+
* Start the runtime.
|
|
1263
|
+
*
|
|
1264
|
+
* Triggers initial fix load and starts background refresh.
|
|
1265
|
+
* Never throws — failures degrade gracefully.
|
|
1266
|
+
*/
|
|
1267
|
+
start() {
|
|
1268
|
+
try {
|
|
1269
|
+
if (this._started) return;
|
|
1270
|
+
if (!this._config.enabled) {
|
|
1271
|
+
debug("Fix runtime disabled");
|
|
1272
|
+
return;
|
|
1273
|
+
}
|
|
1274
|
+
debug("Starting fix runtime...");
|
|
1275
|
+
this._loader.start();
|
|
1276
|
+
this._started = true;
|
|
1277
|
+
debug("Fix runtime started");
|
|
1278
|
+
} catch (e) {
|
|
1279
|
+
warn(`Fix runtime start failed: ${e}`);
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
/**
|
|
1283
|
+
* Stop the runtime.
|
|
1284
|
+
*
|
|
1285
|
+
* Stops background refresh and clears the cache.
|
|
1286
|
+
* Never throws.
|
|
1287
|
+
*/
|
|
1288
|
+
stop() {
|
|
1289
|
+
try {
|
|
1290
|
+
if (!this._started) return;
|
|
1291
|
+
debug("Stopping fix runtime...");
|
|
1292
|
+
this._loader.stop();
|
|
1293
|
+
this._cache.clear();
|
|
1294
|
+
this._started = false;
|
|
1295
|
+
debug("Fix runtime stopped");
|
|
1296
|
+
} catch (e) {
|
|
1297
|
+
debug(`Fix runtime stop error: ${e}`);
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
// ─── Intercept API ──────────────────────────────────────────────────────
|
|
1301
|
+
/**
|
|
1302
|
+
* Intercept a call: create context and run preCall fixes.
|
|
1303
|
+
*
|
|
1304
|
+
* Returns modified messages/params and the InterceptContext for
|
|
1305
|
+
* use in subsequent interceptResponse / interceptError calls.
|
|
1306
|
+
*/
|
|
1307
|
+
interceptCall(operationType, operationName, messages, params, options) {
|
|
1308
|
+
const ctx = createInterceptContext(operationType, operationName, options);
|
|
1309
|
+
try {
|
|
1310
|
+
if (!this.isEnabled) {
|
|
1311
|
+
return { messages, params, ctx };
|
|
1312
|
+
}
|
|
1313
|
+
const result = this._interceptor.preCall(ctx, messages, params);
|
|
1314
|
+
return { messages: result.messages, params: result.params, ctx };
|
|
1315
|
+
} catch (e) {
|
|
1316
|
+
debug(`interceptCall error: ${e}`);
|
|
1317
|
+
return { messages, params, ctx };
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
/**
|
|
1321
|
+
* Intercept a response: run postCall fixes (output validation).
|
|
1322
|
+
*/
|
|
1323
|
+
interceptResponse(ctx, response) {
|
|
1324
|
+
try {
|
|
1325
|
+
if (!this.isEnabled) {
|
|
1326
|
+
return { response, shouldContinue: true };
|
|
1327
|
+
}
|
|
1328
|
+
const result = this._interceptor.postCall(ctx, response);
|
|
1329
|
+
this._trackSuccess(ctx);
|
|
1330
|
+
return result;
|
|
1331
|
+
} catch (e) {
|
|
1332
|
+
debug(`interceptResponse error: ${e}`);
|
|
1333
|
+
return { response, shouldContinue: true };
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
/**
|
|
1337
|
+
* Intercept an error: decide on retry/fallback.
|
|
1338
|
+
*/
|
|
1339
|
+
interceptError(ctx, error) {
|
|
1340
|
+
try {
|
|
1341
|
+
if (!this.isEnabled) {
|
|
1342
|
+
return { shouldRetry: false, modifiedParams: null };
|
|
1343
|
+
}
|
|
1344
|
+
ctx.attempt++;
|
|
1345
|
+
const result = this._interceptor.onError(ctx, error);
|
|
1346
|
+
if (!result.shouldRetry) {
|
|
1347
|
+
this._trackFailure(ctx);
|
|
1348
|
+
}
|
|
1349
|
+
return result;
|
|
1350
|
+
} catch (e) {
|
|
1351
|
+
debug(`interceptError error: ${e}`);
|
|
1352
|
+
return { shouldRetry: false, modifiedParams: null };
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
// ─── Direct Fix Access ──────────────────────────────────────────────────
|
|
1356
|
+
/**
|
|
1357
|
+
* Get applicable fix for an error code.
|
|
1358
|
+
*/
|
|
1359
|
+
getFix(errorCode, sessionId) {
|
|
1360
|
+
try {
|
|
1361
|
+
if (!this.isEnabled) return null;
|
|
1362
|
+
return this._applier.getFixForError(errorCode, sessionId);
|
|
1363
|
+
} catch (e) {
|
|
1364
|
+
debug(`getFix error: ${e}`);
|
|
1365
|
+
return null;
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
/**
|
|
1369
|
+
* Manually refresh fixes from the API.
|
|
1370
|
+
*/
|
|
1371
|
+
async refreshFixes() {
|
|
1372
|
+
try {
|
|
1373
|
+
return await this._loader.loadSync();
|
|
1374
|
+
} catch (e) {
|
|
1375
|
+
debug(`refreshFixes error: ${e}`);
|
|
1376
|
+
return [];
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
// ─── Effectiveness ──────────────────────────────────────────────────────
|
|
1380
|
+
/** Get effectiveness statistics for all fixes. */
|
|
1381
|
+
getEffectivenessStats() {
|
|
1382
|
+
const stats = {};
|
|
1383
|
+
for (const [fixId, applications] of this._fixApplications) {
|
|
1384
|
+
const successes = this._fixSuccesses.get(fixId) ?? 0;
|
|
1385
|
+
stats[fixId] = {
|
|
1386
|
+
applications,
|
|
1387
|
+
successes,
|
|
1388
|
+
successRate: applications > 0 ? successes / applications : 0
|
|
1389
|
+
};
|
|
1390
|
+
}
|
|
1391
|
+
return stats;
|
|
1392
|
+
}
|
|
1393
|
+
// ─── Private ────────────────────────────────────────────────────────────
|
|
1394
|
+
_trackSuccess(ctx) {
|
|
1395
|
+
for (const result of ctx.appliedFixes) {
|
|
1396
|
+
if (result.applied && result.fixId) {
|
|
1397
|
+
this._fixApplications.set(
|
|
1398
|
+
result.fixId,
|
|
1399
|
+
(this._fixApplications.get(result.fixId) ?? 0) + 1
|
|
1400
|
+
);
|
|
1401
|
+
this._fixSuccesses.set(
|
|
1402
|
+
result.fixId,
|
|
1403
|
+
(this._fixSuccesses.get(result.fixId) ?? 0) + 1
|
|
1404
|
+
);
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
_trackFailure(ctx) {
|
|
1409
|
+
for (const result of ctx.appliedFixes) {
|
|
1410
|
+
if (result.applied && result.fixId) {
|
|
1411
|
+
this._fixApplications.set(
|
|
1412
|
+
result.fixId,
|
|
1413
|
+
(this._fixApplications.get(result.fixId) ?? 0) + 1
|
|
1414
|
+
);
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
};
|
|
1419
|
+
}
|
|
1420
|
+
});
|
|
1421
|
+
|
|
30
1422
|
// src/index.ts
|
|
31
1423
|
var src_exports = {};
|
|
32
1424
|
__export(src_exports, {
|
|
33
1425
|
AgentRole: () => AgentRole,
|
|
1426
|
+
DEFAULT_TIMESTAMP_TOLERANCE_S: () => DEFAULT_TIMESTAMP_TOLERANCE_S,
|
|
34
1427
|
MessageType: () => MessageType,
|
|
35
1428
|
RisicareCallbackHandler: () => RisicareCallbackHandler,
|
|
36
1429
|
RisicareLlamaIndexHandler: () => RisicareLlamaIndexHandler,
|
|
37
1430
|
SemanticPhase: () => SemanticPhase,
|
|
38
1431
|
SpanKind: () => SpanKind,
|
|
39
1432
|
SpanStatus: () => SpanStatus,
|
|
1433
|
+
WebhookVerificationError: () => WebhookVerificationError,
|
|
40
1434
|
agent: () => agent,
|
|
41
1435
|
disable: () => disable,
|
|
42
1436
|
enable: () => enable,
|
|
@@ -51,12 +1445,14 @@ __export(src_exports, {
|
|
|
51
1445
|
getCurrentSpan: () => getCurrentSpan,
|
|
52
1446
|
getCurrentSpanId: () => getCurrentSpanId,
|
|
53
1447
|
getCurrentTraceId: () => getCurrentTraceId,
|
|
1448
|
+
getFixRuntime: () => getFixRuntime,
|
|
54
1449
|
getMetrics: () => getMetrics,
|
|
55
1450
|
getSpanById: () => getSpanById,
|
|
56
1451
|
getTraceContent: () => getTraceContent,
|
|
57
1452
|
getTraceContext: () => getTraceContext,
|
|
58
1453
|
getTracer: () => getTracer2,
|
|
59
1454
|
init: () => init,
|
|
1455
|
+
initFixRuntime: () => initFixRuntime,
|
|
60
1456
|
injectTraceContext: () => injectTraceContext,
|
|
61
1457
|
instrumentLangGraph: () => instrumentLangGraph,
|
|
62
1458
|
isEnabled: () => isEnabled,
|
|
@@ -67,6 +1463,7 @@ __export(src_exports, {
|
|
|
67
1463
|
score: () => score,
|
|
68
1464
|
session: () => session,
|
|
69
1465
|
shutdown: () => shutdown,
|
|
1466
|
+
shutdownFixRuntime: () => shutdownFixRuntime,
|
|
70
1467
|
suppressProviderInstrumentation: () => suppressProviderInstrumentation,
|
|
71
1468
|
traceAct: () => traceAct,
|
|
72
1469
|
traceCoordinate: () => traceCoordinate,
|
|
@@ -77,6 +1474,7 @@ __export(src_exports, {
|
|
|
77
1474
|
traceThink: () => traceThink,
|
|
78
1475
|
tracedStream: () => tracedStream,
|
|
79
1476
|
unregisterSpan: () => unregisterSpan,
|
|
1477
|
+
verifyWebhookSignature: () => verifyWebhookSignature,
|
|
80
1478
|
withAgent: () => withAgent,
|
|
81
1479
|
withPhase: () => withPhase,
|
|
82
1480
|
withSession: () => withSession
|
|
@@ -488,48 +1886,8 @@ function shouldSample(traceId, sampleRate) {
|
|
|
488
1886
|
return hash / 4294967295 < sampleRate;
|
|
489
1887
|
}
|
|
490
1888
|
|
|
491
|
-
// src/globals.ts
|
|
492
|
-
var import_node_async_hooks = require("async_hooks");
|
|
493
|
-
var G = globalThis;
|
|
494
|
-
var PREFIX = "__risicare_";
|
|
495
|
-
function getClient() {
|
|
496
|
-
return G[PREFIX + "client"];
|
|
497
|
-
}
|
|
498
|
-
function setClient(client) {
|
|
499
|
-
G[PREFIX + "client"] = client;
|
|
500
|
-
}
|
|
501
|
-
function getTracer() {
|
|
502
|
-
return G[PREFIX + "tracer"];
|
|
503
|
-
}
|
|
504
|
-
function setTracer(tracer) {
|
|
505
|
-
G[PREFIX + "tracer"] = tracer;
|
|
506
|
-
}
|
|
507
|
-
function getContextStorage() {
|
|
508
|
-
if (!G[PREFIX + "ctx"]) {
|
|
509
|
-
G[PREFIX + "ctx"] = new import_node_async_hooks.AsyncLocalStorage();
|
|
510
|
-
}
|
|
511
|
-
return G[PREFIX + "ctx"];
|
|
512
|
-
}
|
|
513
|
-
function getRegistry() {
|
|
514
|
-
if (!G[PREFIX + "registry"]) {
|
|
515
|
-
G[PREFIX + "registry"] = /* @__PURE__ */ new Map();
|
|
516
|
-
}
|
|
517
|
-
return G[PREFIX + "registry"];
|
|
518
|
-
}
|
|
519
|
-
function getOpCount() {
|
|
520
|
-
return G[PREFIX + "opcount"] ?? 0;
|
|
521
|
-
}
|
|
522
|
-
function setOpCount(n) {
|
|
523
|
-
G[PREFIX + "opcount"] = n;
|
|
524
|
-
}
|
|
525
|
-
function getDebug() {
|
|
526
|
-
return G[PREFIX + "debug"] ?? false;
|
|
527
|
-
}
|
|
528
|
-
function setDebugFlag(enabled) {
|
|
529
|
-
G[PREFIX + "debug"] = enabled;
|
|
530
|
-
}
|
|
531
|
-
|
|
532
1889
|
// src/context/storage.ts
|
|
1890
|
+
init_globals();
|
|
533
1891
|
function storage() {
|
|
534
1892
|
return getContextStorage();
|
|
535
1893
|
}
|
|
@@ -631,7 +1989,8 @@ var Tracer = class {
|
|
|
631
1989
|
const session2 = getCurrentSession();
|
|
632
1990
|
const agent2 = getCurrentAgent();
|
|
633
1991
|
const phase = getCurrentPhase();
|
|
634
|
-
const
|
|
1992
|
+
const ctx = getContext();
|
|
1993
|
+
const traceId = opts.traceId ?? parentSpan?.traceId ?? ctx._rootTraceId ?? generateTraceId();
|
|
635
1994
|
if (!parentSpan && this._sampleRate < 1) {
|
|
636
1995
|
if (!shouldSample(traceId, this._sampleRate)) {
|
|
637
1996
|
return fn(NOOP_SPAN);
|
|
@@ -726,22 +2085,8 @@ var Tracer = class {
|
|
|
726
2085
|
}
|
|
727
2086
|
};
|
|
728
2087
|
|
|
729
|
-
// src/utils/log.ts
|
|
730
|
-
function setDebug(enabled) {
|
|
731
|
-
setDebugFlag(enabled);
|
|
732
|
-
}
|
|
733
|
-
function debug(msg) {
|
|
734
|
-
if (getDebug()) {
|
|
735
|
-
process.stderr.write(`[risicare] ${msg}
|
|
736
|
-
`);
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
function warn(msg) {
|
|
740
|
-
process.stderr.write(`[risicare] WARNING: ${msg}
|
|
741
|
-
`);
|
|
742
|
-
}
|
|
743
|
-
|
|
744
2088
|
// src/exporters/batch.ts
|
|
2089
|
+
init_log();
|
|
745
2090
|
var BatchSpanProcessor = class _BatchSpanProcessor {
|
|
746
2091
|
_exporters;
|
|
747
2092
|
_batchSize;
|
|
@@ -762,7 +2107,14 @@ var BatchSpanProcessor = class _BatchSpanProcessor {
|
|
|
762
2107
|
failedExports = 0;
|
|
763
2108
|
constructor(options) {
|
|
764
2109
|
this._exporters = [...options.exporters];
|
|
765
|
-
|
|
2110
|
+
const requestedBatchSize = options.batchSize ?? 100;
|
|
2111
|
+
const clampedBatchSize = Math.max(1, Math.min(requestedBatchSize, 1e4));
|
|
2112
|
+
if (clampedBatchSize !== requestedBatchSize) {
|
|
2113
|
+
warn(
|
|
2114
|
+
`batchSize=${requestedBatchSize} is outside the allowed range [1, 10000]; clamping to ${clampedBatchSize}`
|
|
2115
|
+
);
|
|
2116
|
+
}
|
|
2117
|
+
this._batchSize = clampedBatchSize;
|
|
766
2118
|
this._batchTimeoutMs = options.batchTimeoutMs ?? 1e3;
|
|
767
2119
|
this._maxQueueSize = options.maxQueueSize ?? 1e4;
|
|
768
2120
|
this._debug = options.debug ?? false;
|
|
@@ -892,6 +2244,7 @@ var BatchSpanProcessor = class _BatchSpanProcessor {
|
|
|
892
2244
|
};
|
|
893
2245
|
|
|
894
2246
|
// src/exporters/http.ts
|
|
2247
|
+
init_log();
|
|
895
2248
|
var SDK_VERSION = "0.1.1";
|
|
896
2249
|
var HttpExporter = class {
|
|
897
2250
|
name = "http";
|
|
@@ -902,11 +2255,15 @@ var HttpExporter = class {
|
|
|
902
2255
|
_timeoutMs;
|
|
903
2256
|
_maxRetries;
|
|
904
2257
|
_compress;
|
|
905
|
-
// Circuit breaker
|
|
2258
|
+
// Circuit breaker (CON-201 / B-16). Threshold and cooldown sized for
|
|
2259
|
+
// a "30-second prod restart" scenario: with default 1s batch flush a
|
|
2260
|
+
// 5-failure threshold opens the circuit ~5s into an outage and the 60s
|
|
2261
|
+
// cooldown gives the gateway time to come back before the half-open
|
|
2262
|
+
// probe fires. Lower numbers (was 3@30s) tripped on transient blips.
|
|
906
2263
|
_consecutiveFailures = 0;
|
|
907
2264
|
_circuitOpenUntil = 0;
|
|
908
|
-
_circuitBreakerThreshold =
|
|
909
|
-
_circuitBreakerCooldownMs =
|
|
2265
|
+
_circuitBreakerThreshold = 5;
|
|
2266
|
+
_circuitBreakerCooldownMs = 6e4;
|
|
910
2267
|
constructor(options) {
|
|
911
2268
|
this._endpoint = options.endpoint.replace(/\/+$/, "");
|
|
912
2269
|
this._apiKey = options.apiKey;
|
|
@@ -1017,6 +2374,8 @@ var ConsoleExporter = class {
|
|
|
1017
2374
|
};
|
|
1018
2375
|
|
|
1019
2376
|
// src/client.ts
|
|
2377
|
+
init_log();
|
|
2378
|
+
init_globals();
|
|
1020
2379
|
var RisicareClient = class {
|
|
1021
2380
|
config;
|
|
1022
2381
|
processor;
|
|
@@ -1056,6 +2415,29 @@ var RisicareClient = class {
|
|
|
1056
2415
|
traceContent: this.config.traceContent
|
|
1057
2416
|
});
|
|
1058
2417
|
this.processor.start();
|
|
2418
|
+
if (this.config.apiKey && this.config.enabled) {
|
|
2419
|
+
try {
|
|
2420
|
+
const { initFixRuntime: initFixRuntime2 } = (init_runtime(), __toCommonJS(runtime_exports));
|
|
2421
|
+
initFixRuntime2({
|
|
2422
|
+
apiEndpoint: this.config.endpoint,
|
|
2423
|
+
apiKey: this.config.apiKey,
|
|
2424
|
+
enabled: true,
|
|
2425
|
+
debug: this.config.debug
|
|
2426
|
+
});
|
|
2427
|
+
} catch (e) {
|
|
2428
|
+
const errName = e instanceof Error ? e.name : "Error";
|
|
2429
|
+
const errMsg = e instanceof Error ? e.message : String(e);
|
|
2430
|
+
try {
|
|
2431
|
+
console.warn(
|
|
2432
|
+
`[risicare] FixRuntime initialization failed \u2014 auto-fix is OFFLINE for this process. endpoint=${this.config.endpoint} error=${errName}: ${errMsg}. Tracing + reporting still work; only fix application is disabled. Pass debug:true in init() options for the full traceback.`
|
|
2433
|
+
);
|
|
2434
|
+
} catch {
|
|
2435
|
+
}
|
|
2436
|
+
if (this.config.debug) {
|
|
2437
|
+
debug(`FixRuntime init traceback: ${e instanceof Error && e.stack ? e.stack : String(e)}`);
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
}
|
|
1059
2441
|
this._registerShutdownHooks();
|
|
1060
2442
|
setDebug(this.config.debug);
|
|
1061
2443
|
debug(`Initialized: enabled=${this.config.enabled}, endpoint=${this.config.endpoint}`);
|
|
@@ -1107,6 +2489,11 @@ function init(config) {
|
|
|
1107
2489
|
setTracer(client.tracer);
|
|
1108
2490
|
}
|
|
1109
2491
|
async function shutdown() {
|
|
2492
|
+
try {
|
|
2493
|
+
const { shutdownFixRuntime: shutdownFixRuntime2 } = (init_runtime(), __toCommonJS(runtime_exports));
|
|
2494
|
+
shutdownFixRuntime2();
|
|
2495
|
+
} catch {
|
|
2496
|
+
}
|
|
1110
2497
|
const client = getClient();
|
|
1111
2498
|
if (!client) return;
|
|
1112
2499
|
await client.shutdown();
|
|
@@ -1148,12 +2535,39 @@ function getMetrics() {
|
|
|
1148
2535
|
queueUtilization: 0
|
|
1149
2536
|
};
|
|
1150
2537
|
}
|
|
2538
|
+
var _ERROR_DEDUP_TTL_MS = 5 * 60 * 1e3;
|
|
2539
|
+
var _ERROR_DEDUP_MAX = 1e3;
|
|
2540
|
+
var _recentErrors = /* @__PURE__ */ new Map();
|
|
2541
|
+
function _errorFingerprint(err) {
|
|
2542
|
+
const raw = `${err.constructor?.name ?? "Error"}:${String(err.message ?? "").slice(0, 200)}`;
|
|
2543
|
+
const { createHash: createHash2 } = require("crypto");
|
|
2544
|
+
return createHash2("sha256").update(raw).digest("hex").slice(0, 16);
|
|
2545
|
+
}
|
|
2546
|
+
function _isDuplicateError(fingerprint) {
|
|
2547
|
+
const now = Date.now();
|
|
2548
|
+
for (const [fp, ts] of _recentErrors) {
|
|
2549
|
+
if (now - ts > _ERROR_DEDUP_TTL_MS) _recentErrors.delete(fp);
|
|
2550
|
+
else break;
|
|
2551
|
+
}
|
|
2552
|
+
if (_recentErrors.has(fingerprint)) return true;
|
|
2553
|
+
if (_recentErrors.size >= _ERROR_DEDUP_MAX) {
|
|
2554
|
+
const oldest = _recentErrors.keys().next().value;
|
|
2555
|
+
if (oldest !== void 0) _recentErrors.delete(oldest);
|
|
2556
|
+
}
|
|
2557
|
+
_recentErrors.set(fingerprint, now);
|
|
2558
|
+
return false;
|
|
2559
|
+
}
|
|
1151
2560
|
function reportError(error, options) {
|
|
1152
2561
|
try {
|
|
1153
2562
|
const tracer = getTracer2();
|
|
1154
2563
|
if (!tracer) return;
|
|
1155
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
2564
|
+
const err = error instanceof Error ? error : new Error(String(error ?? "unknown"));
|
|
1156
2565
|
const spanName = options?.name ?? `error:${err.constructor.name}`;
|
|
2566
|
+
const fp = _errorFingerprint(err);
|
|
2567
|
+
if (_isDuplicateError(fp)) {
|
|
2568
|
+
debug(`reportError: duplicate suppressed (fp=${fp.slice(0, 8)})`);
|
|
2569
|
+
return;
|
|
2570
|
+
}
|
|
1157
2571
|
tracer.startSpan({ name: spanName, kind: "internal" /* INTERNAL */ }, (span) => {
|
|
1158
2572
|
span.setStatus("error" /* ERROR */, err.message);
|
|
1159
2573
|
span.setAttribute("error", true);
|
|
@@ -1173,7 +2587,7 @@ function reportError(error, options) {
|
|
|
1173
2587
|
}
|
|
1174
2588
|
function score(traceId, name, value, options) {
|
|
1175
2589
|
try {
|
|
1176
|
-
if (typeof value !== "number" || value < 0 || value > 1) {
|
|
2590
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > 1) {
|
|
1177
2591
|
debug(`score: value must be in [0.0, 1.0], got ${value}. Score not sent.`);
|
|
1178
2592
|
return;
|
|
1179
2593
|
}
|
|
@@ -1223,7 +2637,7 @@ function withAgent(options, fn) {
|
|
|
1223
2637
|
}
|
|
1224
2638
|
|
|
1225
2639
|
// src/decorators/agent.ts
|
|
1226
|
-
function
|
|
2640
|
+
function _wrapAgent(options, fn) {
|
|
1227
2641
|
const spanName = `agent:${options.name ?? "agent"}`;
|
|
1228
2642
|
return (...args) => {
|
|
1229
2643
|
const tracer = getTracer2();
|
|
@@ -1237,6 +2651,12 @@ function agent(options, fn) {
|
|
|
1237
2651
|
});
|
|
1238
2652
|
};
|
|
1239
2653
|
}
|
|
2654
|
+
function agent(options, fn) {
|
|
2655
|
+
if (fn === void 0) {
|
|
2656
|
+
return (realFn) => _wrapAgent(options, realFn);
|
|
2657
|
+
}
|
|
2658
|
+
return _wrapAgent(options, fn);
|
|
2659
|
+
}
|
|
1240
2660
|
|
|
1241
2661
|
// src/context/session.ts
|
|
1242
2662
|
function withSession(options, fn) {
|
|
@@ -1251,12 +2671,18 @@ function withSession(options, fn) {
|
|
|
1251
2671
|
}
|
|
1252
2672
|
|
|
1253
2673
|
// src/decorators/session.ts
|
|
1254
|
-
function
|
|
2674
|
+
function _wrapSession(optionsOrResolver, fn) {
|
|
1255
2675
|
return (...args) => {
|
|
1256
2676
|
const options = typeof optionsOrResolver === "function" ? optionsOrResolver(...args) : optionsOrResolver;
|
|
1257
2677
|
return withSession(options, () => fn(...args));
|
|
1258
2678
|
};
|
|
1259
2679
|
}
|
|
2680
|
+
function session(optionsOrResolver, fn) {
|
|
2681
|
+
if (fn === void 0) {
|
|
2682
|
+
return (realFn) => _wrapSession(optionsOrResolver, realFn);
|
|
2683
|
+
}
|
|
2684
|
+
return _wrapSession(optionsOrResolver, fn);
|
|
2685
|
+
}
|
|
1260
2686
|
|
|
1261
2687
|
// src/context/phase.ts
|
|
1262
2688
|
function withPhase(phase, fn) {
|
|
@@ -1368,8 +2794,22 @@ function getTraceContext() {
|
|
|
1368
2794
|
const span = getCurrentSpan();
|
|
1369
2795
|
const session2 = getCurrentSession();
|
|
1370
2796
|
const agent2 = getCurrentAgent();
|
|
2797
|
+
let traceId;
|
|
2798
|
+
if (span) {
|
|
2799
|
+
traceId = span.traceId;
|
|
2800
|
+
} else {
|
|
2801
|
+
const ctx = getContext();
|
|
2802
|
+
if (ctx._rootTraceId) {
|
|
2803
|
+
traceId = ctx._rootTraceId;
|
|
2804
|
+
} else {
|
|
2805
|
+
traceId = generateTraceId();
|
|
2806
|
+
if (ctx.session || ctx.agent) {
|
|
2807
|
+
ctx._rootTraceId = traceId;
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
2810
|
+
}
|
|
1371
2811
|
return {
|
|
1372
|
-
traceId
|
|
2812
|
+
traceId,
|
|
1373
2813
|
spanId: span?.spanId ?? generateSpanId(),
|
|
1374
2814
|
sessionId: session2?.sessionId,
|
|
1375
2815
|
agentId: agent2?.agentId
|
|
@@ -1437,6 +2877,7 @@ function extractTraceContext(headers) {
|
|
|
1437
2877
|
}
|
|
1438
2878
|
|
|
1439
2879
|
// src/context/registry.ts
|
|
2880
|
+
init_globals();
|
|
1440
2881
|
var DEFAULT_TTL_MS = 6e4;
|
|
1441
2882
|
var MAX_ENTRIES = 1e4;
|
|
1442
2883
|
var CLEANUP_INTERVAL = 100;
|
|
@@ -1506,10 +2947,34 @@ async function* tracedStream(source, options) {
|
|
|
1506
2947
|
}
|
|
1507
2948
|
}
|
|
1508
2949
|
|
|
2950
|
+
// src/context/dedup.ts
|
|
2951
|
+
function suppressProviderInstrumentation(fn) {
|
|
2952
|
+
return runWithContext({ _suppressProviderInstrumentation: true }, fn);
|
|
2953
|
+
}
|
|
2954
|
+
function isProviderInstrumentationSuppressed() {
|
|
2955
|
+
return getContext()._suppressProviderInstrumentation === true;
|
|
2956
|
+
}
|
|
2957
|
+
|
|
1509
2958
|
// src/frameworks/langchain.ts
|
|
1510
2959
|
var RisicareCallbackHandler = class {
|
|
1511
2960
|
name = "RisicareCallbackHandler";
|
|
1512
2961
|
_spans = /* @__PURE__ */ new Map();
|
|
2962
|
+
/**
|
|
2963
|
+
* Run a function with provider instrumentation suppressed.
|
|
2964
|
+
*
|
|
2965
|
+
* Wrap your chain.invoke() call with this to prevent duplicate spans.
|
|
2966
|
+
* The handler creates LLM/tool spans, so provider proxies (patchOpenAI, etc.)
|
|
2967
|
+
* should be suppressed during chain execution.
|
|
2968
|
+
*
|
|
2969
|
+
* @example
|
|
2970
|
+
* const handler = new RisicareCallbackHandler();
|
|
2971
|
+
* const result = await handler.withSuppression(() =>
|
|
2972
|
+
* chain.invoke(input, { callbacks: [handler] })
|
|
2973
|
+
* );
|
|
2974
|
+
*/
|
|
2975
|
+
withSuppression(fn) {
|
|
2976
|
+
return suppressProviderInstrumentation(fn);
|
|
2977
|
+
}
|
|
1513
2978
|
// ── Chain lifecycle ────────────────────────────────────────────────────────
|
|
1514
2979
|
handleChainStart(chain, _inputs, runId, parentRunId) {
|
|
1515
2980
|
const tracer = getTracer2();
|
|
@@ -1687,6 +3152,7 @@ function instrumentLangGraph(graph) {
|
|
|
1687
3152
|
}
|
|
1688
3153
|
|
|
1689
3154
|
// src/frameworks/instructor.ts
|
|
3155
|
+
init_log();
|
|
1690
3156
|
function patchInstructor(client) {
|
|
1691
3157
|
return new Proxy(client, {
|
|
1692
3158
|
get(target, prop, receiver) {
|
|
@@ -1737,14 +3203,6 @@ function patchInstructor(client) {
|
|
|
1737
3203
|
});
|
|
1738
3204
|
}
|
|
1739
3205
|
|
|
1740
|
-
// src/context/dedup.ts
|
|
1741
|
-
function suppressProviderInstrumentation(fn) {
|
|
1742
|
-
return runWithContext({ _suppressProviderInstrumentation: true }, fn);
|
|
1743
|
-
}
|
|
1744
|
-
function isProviderInstrumentationSuppressed() {
|
|
1745
|
-
return getContext()._suppressProviderInstrumentation === true;
|
|
1746
|
-
}
|
|
1747
|
-
|
|
1748
3206
|
// src/frameworks/llamaindex.ts
|
|
1749
3207
|
var RisicareLlamaIndexHandler = class {
|
|
1750
3208
|
_spans = /* @__PURE__ */ new Map();
|
|
@@ -1843,15 +3301,184 @@ var RisicareLlamaIndexHandler = class {
|
|
|
1843
3301
|
return "component";
|
|
1844
3302
|
}
|
|
1845
3303
|
};
|
|
3304
|
+
|
|
3305
|
+
// src/index.ts
|
|
3306
|
+
init_runtime();
|
|
3307
|
+
|
|
3308
|
+
// src/webhooks.ts
|
|
3309
|
+
var import_node_crypto3 = require("crypto");
|
|
3310
|
+
var import_node_buffer = require("buffer");
|
|
3311
|
+
var DEFAULT_TIMESTAMP_TOLERANCE_S = 300;
|
|
3312
|
+
var SIGNATURE_HEADER = "X-Risicare-Signature";
|
|
3313
|
+
var TIMESTAMP_HEADER = "X-Risicare-Timestamp";
|
|
3314
|
+
var SUPPORTED_SCHEMES = ["v1"];
|
|
3315
|
+
var HEX_SHA256_RE = /^[0-9a-f]{64}$/;
|
|
3316
|
+
var WebhookVerificationError = class _WebhookVerificationError extends Error {
|
|
3317
|
+
constructor(message) {
|
|
3318
|
+
super(message);
|
|
3319
|
+
this.name = "WebhookVerificationError";
|
|
3320
|
+
Object.setPrototypeOf(this, _WebhookVerificationError.prototype);
|
|
3321
|
+
}
|
|
3322
|
+
};
|
|
3323
|
+
function getHeader(headers, name) {
|
|
3324
|
+
const lower = name.toLowerCase();
|
|
3325
|
+
if (typeof headers.get === "function") {
|
|
3326
|
+
const accessor = headers;
|
|
3327
|
+
const v = accessor.get(name) ?? accessor.get(lower);
|
|
3328
|
+
return v ?? void 0;
|
|
3329
|
+
}
|
|
3330
|
+
const dict = headers;
|
|
3331
|
+
const raw = dict[name] ?? dict[lower];
|
|
3332
|
+
if (raw === void 0) {
|
|
3333
|
+
return void 0;
|
|
3334
|
+
}
|
|
3335
|
+
if (Array.isArray(raw)) {
|
|
3336
|
+
return raw[0];
|
|
3337
|
+
}
|
|
3338
|
+
return raw;
|
|
3339
|
+
}
|
|
3340
|
+
function coercePayload(payload) {
|
|
3341
|
+
if (typeof payload === "string") {
|
|
3342
|
+
return import_node_buffer.Buffer.from(payload, "utf-8");
|
|
3343
|
+
}
|
|
3344
|
+
if (payload instanceof Uint8Array) {
|
|
3345
|
+
return import_node_buffer.Buffer.from(
|
|
3346
|
+
payload.buffer,
|
|
3347
|
+
payload.byteOffset,
|
|
3348
|
+
payload.byteLength
|
|
3349
|
+
);
|
|
3350
|
+
}
|
|
3351
|
+
if (payload instanceof ArrayBuffer) {
|
|
3352
|
+
return import_node_buffer.Buffer.from(payload);
|
|
3353
|
+
}
|
|
3354
|
+
throw new WebhookVerificationError(
|
|
3355
|
+
`payload must be Uint8Array, ArrayBuffer, Buffer, or string, got ${typeof payload}`
|
|
3356
|
+
);
|
|
3357
|
+
}
|
|
3358
|
+
function parseSignatureHeader(raw) {
|
|
3359
|
+
const parts = /* @__PURE__ */ Object.create(null);
|
|
3360
|
+
for (const rawSegment of raw.split(",")) {
|
|
3361
|
+
const segment = rawSegment.trim();
|
|
3362
|
+
if (segment === "") {
|
|
3363
|
+
continue;
|
|
3364
|
+
}
|
|
3365
|
+
const eqIdx = segment.indexOf("=");
|
|
3366
|
+
if (eqIdx === -1) {
|
|
3367
|
+
throw new WebhookVerificationError(
|
|
3368
|
+
`Malformed segment in ${SIGNATURE_HEADER}: ${JSON.stringify(segment)}`
|
|
3369
|
+
);
|
|
3370
|
+
}
|
|
3371
|
+
const key = segment.slice(0, eqIdx).trim();
|
|
3372
|
+
const value = segment.slice(eqIdx + 1).trim();
|
|
3373
|
+
parts[key] = value;
|
|
3374
|
+
}
|
|
3375
|
+
if (!("t" in parts)) {
|
|
3376
|
+
throw new WebhookVerificationError(
|
|
3377
|
+
`Missing \`t=\` (timestamp) in ${SIGNATURE_HEADER}`
|
|
3378
|
+
);
|
|
3379
|
+
}
|
|
3380
|
+
let scheme;
|
|
3381
|
+
for (const candidate of SUPPORTED_SCHEMES) {
|
|
3382
|
+
if (candidate in parts) {
|
|
3383
|
+
scheme = candidate;
|
|
3384
|
+
break;
|
|
3385
|
+
}
|
|
3386
|
+
}
|
|
3387
|
+
if (scheme === void 0) {
|
|
3388
|
+
const keys = Object.keys(parts).sort();
|
|
3389
|
+
throw new WebhookVerificationError(
|
|
3390
|
+
`No supported signature scheme in ${SIGNATURE_HEADER}; expected one of (${SUPPORTED_SCHEMES.map((s) => `'${s}'`).join(", ")}), got keys [${keys.map((k) => `'${k}'`).join(", ")}]`
|
|
3391
|
+
);
|
|
3392
|
+
}
|
|
3393
|
+
const tRaw = parts["t"];
|
|
3394
|
+
if (!/^-?\d+$/.test(tRaw)) {
|
|
3395
|
+
throw new WebhookVerificationError(
|
|
3396
|
+
`Non-integer timestamp in ${SIGNATURE_HEADER}: ${JSON.stringify(tRaw)}`
|
|
3397
|
+
);
|
|
3398
|
+
}
|
|
3399
|
+
const ts = parseInt(tRaw, 10);
|
|
3400
|
+
if (!Number.isFinite(ts)) {
|
|
3401
|
+
throw new WebhookVerificationError(
|
|
3402
|
+
`Non-integer timestamp in ${SIGNATURE_HEADER}: ${JSON.stringify(tRaw)}`
|
|
3403
|
+
);
|
|
3404
|
+
}
|
|
3405
|
+
const sigHex = parts[scheme];
|
|
3406
|
+
if (sigHex === void 0 || sigHex === "") {
|
|
3407
|
+
throw new WebhookVerificationError(
|
|
3408
|
+
`Empty ${scheme}= value in ${SIGNATURE_HEADER}`
|
|
3409
|
+
);
|
|
3410
|
+
}
|
|
3411
|
+
return [ts, sigHex];
|
|
3412
|
+
}
|
|
3413
|
+
function safeHexEqual(computed, expected) {
|
|
3414
|
+
if (!HEX_SHA256_RE.test(expected)) {
|
|
3415
|
+
return false;
|
|
3416
|
+
}
|
|
3417
|
+
const a = import_node_buffer.Buffer.from(computed, "hex");
|
|
3418
|
+
const b = import_node_buffer.Buffer.from(expected, "hex");
|
|
3419
|
+
if (a.length !== b.length) {
|
|
3420
|
+
return false;
|
|
3421
|
+
}
|
|
3422
|
+
return (0, import_node_crypto3.timingSafeEqual)(a, b);
|
|
3423
|
+
}
|
|
3424
|
+
function verifyWebhookSignature(payload, headers, secret, opts = {}) {
|
|
3425
|
+
const toleranceS = opts.toleranceS ?? DEFAULT_TIMESTAMP_TOLERANCE_S;
|
|
3426
|
+
const payloadBytes = coercePayload(payload);
|
|
3427
|
+
const sigHeader = getHeader(headers, SIGNATURE_HEADER);
|
|
3428
|
+
if (sigHeader === void 0 || sigHeader === "") {
|
|
3429
|
+
throw new WebhookVerificationError(
|
|
3430
|
+
`Missing ${SIGNATURE_HEADER} header`
|
|
3431
|
+
);
|
|
3432
|
+
}
|
|
3433
|
+
const tsHeader = getHeader(headers, TIMESTAMP_HEADER);
|
|
3434
|
+
if (tsHeader === void 0 || tsHeader === "") {
|
|
3435
|
+
throw new WebhookVerificationError(
|
|
3436
|
+
`Missing ${TIMESTAMP_HEADER} header`
|
|
3437
|
+
);
|
|
3438
|
+
}
|
|
3439
|
+
const [tsInSig, expectedHex] = parseSignatureHeader(sigHeader);
|
|
3440
|
+
if (!/^-?\d+$/.test(tsHeader)) {
|
|
3441
|
+
throw new WebhookVerificationError(
|
|
3442
|
+
`Non-integer ${TIMESTAMP_HEADER}: ${JSON.stringify(tsHeader)}`
|
|
3443
|
+
);
|
|
3444
|
+
}
|
|
3445
|
+
const tsStandalone = parseInt(tsHeader, 10);
|
|
3446
|
+
if (!Number.isFinite(tsStandalone)) {
|
|
3447
|
+
throw new WebhookVerificationError(
|
|
3448
|
+
`Non-integer ${TIMESTAMP_HEADER}: ${JSON.stringify(tsHeader)}`
|
|
3449
|
+
);
|
|
3450
|
+
}
|
|
3451
|
+
if (tsStandalone !== tsInSig) {
|
|
3452
|
+
throw new WebhookVerificationError(
|
|
3453
|
+
`Timestamp mismatch: ${TIMESTAMP_HEADER}=${tsStandalone}, signature t=${tsInSig}`
|
|
3454
|
+
);
|
|
3455
|
+
}
|
|
3456
|
+
const now = opts._now !== void 0 ? Math.trunc(opts._now) : Math.floor(Date.now() / 1e3);
|
|
3457
|
+
const skew = Math.abs(now - tsInSig);
|
|
3458
|
+
if (skew > toleranceS) {
|
|
3459
|
+
throw new WebhookVerificationError(
|
|
3460
|
+
`Webhook timestamp outside tolerance: skew=${skew}s, max=${toleranceS}s`
|
|
3461
|
+
);
|
|
3462
|
+
}
|
|
3463
|
+
const hmac = (0, import_node_crypto3.createHmac)("sha256", secret);
|
|
3464
|
+
hmac.update(`${tsInSig}.`);
|
|
3465
|
+
hmac.update(payloadBytes);
|
|
3466
|
+
const computed = hmac.digest("hex");
|
|
3467
|
+
if (!safeHexEqual(computed, expectedHex)) {
|
|
3468
|
+
throw new WebhookVerificationError("Webhook signature mismatch");
|
|
3469
|
+
}
|
|
3470
|
+
}
|
|
1846
3471
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1847
3472
|
0 && (module.exports = {
|
|
1848
3473
|
AgentRole,
|
|
3474
|
+
DEFAULT_TIMESTAMP_TOLERANCE_S,
|
|
1849
3475
|
MessageType,
|
|
1850
3476
|
RisicareCallbackHandler,
|
|
1851
3477
|
RisicareLlamaIndexHandler,
|
|
1852
3478
|
SemanticPhase,
|
|
1853
3479
|
SpanKind,
|
|
1854
3480
|
SpanStatus,
|
|
3481
|
+
WebhookVerificationError,
|
|
1855
3482
|
agent,
|
|
1856
3483
|
disable,
|
|
1857
3484
|
enable,
|
|
@@ -1866,12 +3493,14 @@ var RisicareLlamaIndexHandler = class {
|
|
|
1866
3493
|
getCurrentSpan,
|
|
1867
3494
|
getCurrentSpanId,
|
|
1868
3495
|
getCurrentTraceId,
|
|
3496
|
+
getFixRuntime,
|
|
1869
3497
|
getMetrics,
|
|
1870
3498
|
getSpanById,
|
|
1871
3499
|
getTraceContent,
|
|
1872
3500
|
getTraceContext,
|
|
1873
3501
|
getTracer,
|
|
1874
3502
|
init,
|
|
3503
|
+
initFixRuntime,
|
|
1875
3504
|
injectTraceContext,
|
|
1876
3505
|
instrumentLangGraph,
|
|
1877
3506
|
isEnabled,
|
|
@@ -1882,6 +3511,7 @@ var RisicareLlamaIndexHandler = class {
|
|
|
1882
3511
|
score,
|
|
1883
3512
|
session,
|
|
1884
3513
|
shutdown,
|
|
3514
|
+
shutdownFixRuntime,
|
|
1885
3515
|
suppressProviderInstrumentation,
|
|
1886
3516
|
traceAct,
|
|
1887
3517
|
traceCoordinate,
|
|
@@ -1892,6 +3522,7 @@ var RisicareLlamaIndexHandler = class {
|
|
|
1892
3522
|
traceThink,
|
|
1893
3523
|
tracedStream,
|
|
1894
3524
|
unregisterSpan,
|
|
3525
|
+
verifyWebhookSignature,
|
|
1895
3526
|
withAgent,
|
|
1896
3527
|
withPhase,
|
|
1897
3528
|
withSession
|