@axlsdk/axl 0.18.1 → 0.19.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 +13 -2
- package/dist/index.cjs +1612 -420
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +801 -42
- package/dist/index.d.ts +801 -42
- package/dist/index.js +1582 -420
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -120,11 +120,218 @@ function resolveThinkingOptions(options) {
|
|
|
120
120
|
hasBudgetOverride
|
|
121
121
|
};
|
|
122
122
|
}
|
|
123
|
+
async function resolveApiKey(source) {
|
|
124
|
+
if (typeof source === "function") return await source() ?? "";
|
|
125
|
+
return source ?? "";
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// src/errors.ts
|
|
129
|
+
var AxlError = class extends Error {
|
|
130
|
+
code;
|
|
131
|
+
constructor(code, message) {
|
|
132
|
+
super(message);
|
|
133
|
+
this.name = "AxlError";
|
|
134
|
+
this.code = code;
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
var VerifyError = class extends AxlError {
|
|
138
|
+
lastOutput;
|
|
139
|
+
zodError;
|
|
140
|
+
retries;
|
|
141
|
+
constructor(lastOutput, zodError, retries) {
|
|
142
|
+
super("VERIFY_ERROR", `Schema validation failed after ${retries} retries: ${zodError.message}`);
|
|
143
|
+
this.name = "VerifyError";
|
|
144
|
+
this.lastOutput = lastOutput;
|
|
145
|
+
this.zodError = zodError;
|
|
146
|
+
this.retries = retries;
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
var QuorumNotMet = class extends AxlError {
|
|
150
|
+
results;
|
|
151
|
+
constructor(required, actual, results) {
|
|
152
|
+
super("QUORUM_NOT_MET", `Quorum not met: needed ${required} successes, got ${actual}`);
|
|
153
|
+
this.name = "QuorumNotMet";
|
|
154
|
+
this.results = results;
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
var NoConsensus = class extends AxlError {
|
|
158
|
+
constructor(reason) {
|
|
159
|
+
super("NO_CONSENSUS", `No consensus: ${reason}`);
|
|
160
|
+
this.name = "NoConsensus";
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
var TimeoutError = class extends AxlError {
|
|
164
|
+
constructor(operation, timeoutMs) {
|
|
165
|
+
super("TIMEOUT", `${operation} exceeded timeout of ${timeoutMs}ms`);
|
|
166
|
+
this.name = "TimeoutError";
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
function formatBudgetCost(cost) {
|
|
170
|
+
if (Number.isNaN(cost)) return "$NaN";
|
|
171
|
+
if (cost === Infinity) return "$Infinity";
|
|
172
|
+
if (cost === -Infinity) return "-$Infinity";
|
|
173
|
+
if (cost === 0) return "$0.00";
|
|
174
|
+
const sign = cost < 0 ? "-" : "";
|
|
175
|
+
const abs = Math.abs(cost);
|
|
176
|
+
if (abs < 1e-6) return `${sign}< $0.000001`;
|
|
177
|
+
if (abs < 1e-4) {
|
|
178
|
+
const [mantissa, exponent] = abs.toExponential(2).split("e");
|
|
179
|
+
return `${sign}$${mantissa}e${parseInt(exponent, 10)}`;
|
|
180
|
+
}
|
|
181
|
+
if (abs < 0.01) return `${sign}$${abs.toFixed(6)}`;
|
|
182
|
+
return `${sign}$${abs.toFixed(2)}`;
|
|
183
|
+
}
|
|
184
|
+
var BudgetExceededError = class extends AxlError {
|
|
185
|
+
limit;
|
|
186
|
+
spent;
|
|
187
|
+
policy;
|
|
188
|
+
constructor(limit, spent, policy) {
|
|
189
|
+
super(
|
|
190
|
+
"BUDGET_EXCEEDED",
|
|
191
|
+
`Budget exceeded: spent ${formatBudgetCost(spent)} of ${formatBudgetCost(
|
|
192
|
+
limit
|
|
193
|
+
)} limit (policy: ${policy})`
|
|
194
|
+
);
|
|
195
|
+
this.name = "BudgetExceededError";
|
|
196
|
+
this.limit = limit;
|
|
197
|
+
this.spent = spent;
|
|
198
|
+
this.policy = policy;
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
var MaxTurnsError = class extends AxlError {
|
|
202
|
+
maxTurns;
|
|
203
|
+
constructor(operation, maxTurns) {
|
|
204
|
+
super("MAX_TURNS", `${operation} exceeded maximum of ${maxTurns} turns`);
|
|
205
|
+
this.name = "MaxTurnsError";
|
|
206
|
+
this.maxTurns = maxTurns;
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
var GuardrailError = class extends AxlError {
|
|
210
|
+
guardrailType;
|
|
211
|
+
reason;
|
|
212
|
+
constructor(guardrailType, reason) {
|
|
213
|
+
super("GUARDRAIL_BLOCKED", `${guardrailType} guardrail blocked: ${reason}`);
|
|
214
|
+
this.name = "GuardrailError";
|
|
215
|
+
this.guardrailType = guardrailType;
|
|
216
|
+
this.reason = reason;
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
var ValidationError = class extends AxlError {
|
|
220
|
+
lastOutput;
|
|
221
|
+
reason;
|
|
222
|
+
retries;
|
|
223
|
+
constructor(lastOutput, reason, retries) {
|
|
224
|
+
super("VALIDATION_ERROR", `Validation failed after ${retries} retries: ${reason}`);
|
|
225
|
+
this.name = "ValidationError";
|
|
226
|
+
this.lastOutput = lastOutput;
|
|
227
|
+
this.reason = reason;
|
|
228
|
+
this.retries = retries;
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
var ToolDenied = class extends AxlError {
|
|
232
|
+
toolName;
|
|
233
|
+
agentName;
|
|
234
|
+
constructor(toolName, agentName) {
|
|
235
|
+
super(
|
|
236
|
+
"TOOL_DENIED",
|
|
237
|
+
`Agent "${agentName}" attempted to call tool "${toolName}" which is not in its ACL`
|
|
238
|
+
);
|
|
239
|
+
this.name = "ToolDenied";
|
|
240
|
+
this.toolName = toolName;
|
|
241
|
+
this.agentName = agentName;
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
// src/providers/errors.ts
|
|
246
|
+
var ProviderError = class extends AxlError {
|
|
247
|
+
/** Adapter/profile name, e.g. `'openai'`, `'anthropic'`, `'google'`. */
|
|
248
|
+
provider;
|
|
249
|
+
/** HTTP status code; `0` for non-HTTP (network) failures. */
|
|
250
|
+
status;
|
|
251
|
+
/** Semantic failover hint (see module doc) — NOT the transport-retry set. */
|
|
252
|
+
retryable;
|
|
253
|
+
/** Parsed `Retry-After` delay in ms (RAW/unclamped), when the header was present. */
|
|
254
|
+
retryAfterMs;
|
|
255
|
+
/** Provider request id from response headers, when present. */
|
|
256
|
+
requestId;
|
|
257
|
+
/**
|
|
258
|
+
* Raw provider error body. Lives ONLY on the error object — it is
|
|
259
|
+
* intentionally never placed on the event stream (it can echo prompt text
|
|
260
|
+
* and is redaction-eligible). See `docs/security.md`.
|
|
261
|
+
*/
|
|
262
|
+
body;
|
|
263
|
+
constructor(args) {
|
|
264
|
+
super("PROVIDER_ERROR", args.message);
|
|
265
|
+
this.name = "ProviderError";
|
|
266
|
+
this.provider = args.provider;
|
|
267
|
+
this.status = args.status;
|
|
268
|
+
this.retryable = args.retryable;
|
|
269
|
+
this.retryAfterMs = args.retryAfterMs;
|
|
270
|
+
this.requestId = args.requestId;
|
|
271
|
+
this.body = args.body;
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
function isRetryableStatus(status) {
|
|
275
|
+
switch (status) {
|
|
276
|
+
case 0:
|
|
277
|
+
return true;
|
|
278
|
+
case 408:
|
|
279
|
+
// request timeout
|
|
280
|
+
case 429:
|
|
281
|
+
// rate limit
|
|
282
|
+
case 500:
|
|
283
|
+
case 502:
|
|
284
|
+
case 503:
|
|
285
|
+
case 504:
|
|
286
|
+
case 529:
|
|
287
|
+
return true;
|
|
288
|
+
// Explicitly non-retryable (bad request / auth / not found / payload):
|
|
289
|
+
// 400, 401, 403, 404, 413, 422 fall through to the default.
|
|
290
|
+
default:
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
function parseRetryAfter(headers) {
|
|
295
|
+
const raw = headers.get("retry-after");
|
|
296
|
+
if (!raw) return void 0;
|
|
297
|
+
const trimmed = raw.trim();
|
|
298
|
+
if (/^\d+(\.\d+)?$/.test(trimmed)) {
|
|
299
|
+
const ms = Number(trimmed) * 1e3;
|
|
300
|
+
return ms > 0 ? ms : void 0;
|
|
301
|
+
}
|
|
302
|
+
const dateMs = Date.parse(trimmed);
|
|
303
|
+
if (Number.isNaN(dateMs)) return void 0;
|
|
304
|
+
const delta = dateMs - Date.now();
|
|
305
|
+
return delta > 0 ? delta : void 0;
|
|
306
|
+
}
|
|
307
|
+
var REQUEST_ID_HEADERS = ["x-request-id", "request-id"];
|
|
308
|
+
function extractRequestId(headers) {
|
|
309
|
+
for (const name of REQUEST_ID_HEADERS) {
|
|
310
|
+
const value = headers.get(name);
|
|
311
|
+
if (value) return value;
|
|
312
|
+
}
|
|
313
|
+
return void 0;
|
|
314
|
+
}
|
|
315
|
+
function buildProviderError(args) {
|
|
316
|
+
return new ProviderError({
|
|
317
|
+
provider: args.provider,
|
|
318
|
+
status: args.status,
|
|
319
|
+
retryable: isRetryableStatus(args.status),
|
|
320
|
+
message: args.message,
|
|
321
|
+
retryAfterMs: args.headers ? parseRetryAfter(args.headers) : void 0,
|
|
322
|
+
requestId: args.headers ? extractRequestId(args.headers) : void 0,
|
|
323
|
+
body: args.body
|
|
324
|
+
});
|
|
325
|
+
}
|
|
123
326
|
|
|
124
327
|
// src/providers/retry.ts
|
|
125
328
|
var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([429, 503, 529]);
|
|
126
329
|
var MAX_RETRIES = 2;
|
|
127
330
|
var BASE_DELAY_MS = 1e3;
|
|
331
|
+
var MAX_BACKOFF_MS = 6e4;
|
|
332
|
+
function jitter(ms) {
|
|
333
|
+
return ms * (0.75 + Math.random() * 0.5);
|
|
334
|
+
}
|
|
128
335
|
function sleep2(ms, signal) {
|
|
129
336
|
return new Promise((resolve2) => {
|
|
130
337
|
if (signal?.aborted) {
|
|
@@ -142,9 +349,14 @@ function sleep2(ms, signal) {
|
|
|
142
349
|
);
|
|
143
350
|
});
|
|
144
351
|
}
|
|
352
|
+
function isAbortError(err, signal) {
|
|
353
|
+
if (signal?.aborted) return true;
|
|
354
|
+
return typeof err === "object" && err !== null && "name" in err && err.name === "AbortError";
|
|
355
|
+
}
|
|
145
356
|
async function fetchWithRetry(input, init, opts) {
|
|
146
357
|
const maxRetries = opts?.maxRetries ?? MAX_RETRIES;
|
|
147
358
|
const governor = opts?.governor;
|
|
359
|
+
const provider = opts?.provider ?? "unknown";
|
|
148
360
|
let acquired = false;
|
|
149
361
|
if (governor) {
|
|
150
362
|
await governor.acquire(init?.signal ?? void 0);
|
|
@@ -152,7 +364,21 @@ async function fetchWithRetry(input, init, opts) {
|
|
|
152
364
|
}
|
|
153
365
|
try {
|
|
154
366
|
for (let attempt = 0; ; attempt++) {
|
|
155
|
-
|
|
367
|
+
let res;
|
|
368
|
+
try {
|
|
369
|
+
res = await fetch(input, init);
|
|
370
|
+
} catch (err) {
|
|
371
|
+
if (isAbortError(err, init?.signal ?? void 0)) throw err;
|
|
372
|
+
if (attempt >= maxRetries) {
|
|
373
|
+
throw buildProviderError({
|
|
374
|
+
provider,
|
|
375
|
+
status: 0,
|
|
376
|
+
message: err instanceof Error ? err.message : String(err)
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
await sleep2(jitter(BASE_DELAY_MS * 2 ** attempt), init?.signal ?? void 0);
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
156
382
|
governor?.observe(res);
|
|
157
383
|
if (res.ok || !RETRYABLE_STATUS_CODES.has(res.status) || attempt >= maxRetries) {
|
|
158
384
|
return res;
|
|
@@ -160,15 +386,9 @@ async function fetchWithRetry(input, init, opts) {
|
|
|
160
386
|
if (init?.signal?.aborted) {
|
|
161
387
|
return res;
|
|
162
388
|
}
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
delay = Number(retryAfter) * 1e3;
|
|
167
|
-
} else {
|
|
168
|
-
delay = BASE_DELAY_MS * 2 ** attempt;
|
|
169
|
-
}
|
|
170
|
-
delay *= 0.75 + Math.random() * 0.5;
|
|
171
|
-
await sleep2(delay, init?.signal ?? void 0);
|
|
389
|
+
const retryAfterMs = parseRetryAfter(res.headers);
|
|
390
|
+
const baseDelay = retryAfterMs !== void 0 ? Math.min(retryAfterMs, MAX_BACKOFF_MS) : BASE_DELAY_MS * 2 ** attempt;
|
|
391
|
+
await sleep2(jitter(baseDelay), init?.signal ?? void 0);
|
|
172
392
|
}
|
|
173
393
|
} finally {
|
|
174
394
|
if (acquired) governor.release();
|
|
@@ -316,254 +536,467 @@ var RateLimiter = class {
|
|
|
316
536
|
}
|
|
317
537
|
};
|
|
318
538
|
|
|
319
|
-
// src/providers/openai.ts
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
"
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
"o3-mini": [11e-7, 44e-7, 0.25],
|
|
336
|
-
"o3-pro": [2e-5, 8e-5, 0.25],
|
|
337
|
-
"o4-mini": [11e-7, 44e-7, 0.25],
|
|
338
|
-
// gpt-5 era — cache reads at 10% of input rate
|
|
339
|
-
"gpt-5": [125e-8, 1e-5, 0.1],
|
|
340
|
-
"gpt-5-mini": [25e-8, 2e-6, 0.1],
|
|
341
|
-
"gpt-5-nano": [5e-8, 4e-7, 0.1],
|
|
342
|
-
"gpt-5.1": [125e-8, 1e-5, 0.1],
|
|
343
|
-
"gpt-5.2": [175e-8, 14e-6, 0.1],
|
|
344
|
-
"gpt-5.3": [175e-8, 14e-6, 0.1],
|
|
345
|
-
"gpt-5.4": [25e-7, 15e-6, 0.1],
|
|
346
|
-
"gpt-5.4-pro": [3e-5, 18e-5, 0.1],
|
|
347
|
-
// gpt-5.5 era (flagship released 2026-04) — cache reads at 10% of input rate
|
|
348
|
-
"gpt-5.5": [5e-6, 3e-5, 0.1],
|
|
349
|
-
"gpt-5.5-pro": [3e-5, 18e-5, 0.1]
|
|
350
|
-
};
|
|
351
|
-
var PRICING_KEYS_BY_LENGTH = Object.keys(OPENAI_PRICING).sort((a, b) => b.length - a.length);
|
|
352
|
-
function estimateOpenAICost(model, promptTokens, completionTokens, cachedTokens) {
|
|
353
|
-
let pricing = OPENAI_PRICING[model];
|
|
539
|
+
// src/providers/openai-compatible.ts
|
|
540
|
+
function resolvePerModel(value, model, fallback) {
|
|
541
|
+
if (value === void 0) return fallback;
|
|
542
|
+
return typeof value === "function" ? value(model) : value;
|
|
543
|
+
}
|
|
544
|
+
var SORTED_KEYS_CACHE = /* @__PURE__ */ new WeakMap();
|
|
545
|
+
function sortedKeys(table) {
|
|
546
|
+
let keys = SORTED_KEYS_CACHE.get(table);
|
|
547
|
+
if (!keys) {
|
|
548
|
+
keys = Object.keys(table).sort((a, b) => b.length - a.length);
|
|
549
|
+
SORTED_KEYS_CACHE.set(table, keys);
|
|
550
|
+
}
|
|
551
|
+
return keys;
|
|
552
|
+
}
|
|
553
|
+
function priceFromTable(table, model, promptTokens, completionTokens, cachedTokens) {
|
|
554
|
+
let pricing = table[model];
|
|
354
555
|
if (!pricing) {
|
|
355
|
-
for (const key of
|
|
556
|
+
for (const key of sortedKeys(table)) {
|
|
356
557
|
if (model.startsWith(key)) {
|
|
357
|
-
pricing =
|
|
558
|
+
pricing = table[key];
|
|
358
559
|
break;
|
|
359
560
|
}
|
|
360
561
|
}
|
|
361
562
|
}
|
|
362
|
-
if (!pricing) return 0;
|
|
563
|
+
if (!pricing) return void 0;
|
|
363
564
|
const [inputRate, outputRate, cacheMultiplier] = pricing;
|
|
364
565
|
const cached = cachedTokens ?? 0;
|
|
365
566
|
const inputCost = (promptTokens - cached) * inputRate + cached * inputRate * cacheMultiplier;
|
|
366
567
|
return inputCost + completionTokens * outputRate;
|
|
367
568
|
}
|
|
368
|
-
|
|
369
|
-
|
|
569
|
+
var THINK_OPEN = "<think>";
|
|
570
|
+
var THINK_CLOSE = "</think>";
|
|
571
|
+
function partialTagSuffixLen(s, tag) {
|
|
572
|
+
const max = Math.min(s.length, tag.length - 1);
|
|
573
|
+
for (let n = max; n > 0; n--) {
|
|
574
|
+
if (tag.startsWith(s.slice(s.length - n))) return n;
|
|
575
|
+
}
|
|
576
|
+
return 0;
|
|
370
577
|
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
578
|
+
var ThinkTagScanner = class {
|
|
579
|
+
buffer = "";
|
|
580
|
+
inside = false;
|
|
581
|
+
push(chunk) {
|
|
582
|
+
this.buffer += chunk;
|
|
583
|
+
let text = "";
|
|
584
|
+
let thinking = "";
|
|
585
|
+
for (; ; ) {
|
|
586
|
+
const tag = this.inside ? THINK_CLOSE : THINK_OPEN;
|
|
587
|
+
const idx = this.buffer.indexOf(tag);
|
|
588
|
+
if (idx === -1) {
|
|
589
|
+
const hold = partialTagSuffixLen(this.buffer, tag);
|
|
590
|
+
const emit = this.buffer.slice(0, this.buffer.length - hold);
|
|
591
|
+
if (this.inside) thinking += emit;
|
|
592
|
+
else text += emit;
|
|
593
|
+
this.buffer = this.buffer.slice(this.buffer.length - hold);
|
|
594
|
+
break;
|
|
595
|
+
}
|
|
596
|
+
const before = this.buffer.slice(0, idx);
|
|
597
|
+
if (this.inside) thinking += before;
|
|
598
|
+
else text += before;
|
|
599
|
+
this.buffer = this.buffer.slice(idx + tag.length);
|
|
600
|
+
this.inside = !this.inside;
|
|
601
|
+
}
|
|
602
|
+
return { text, thinking };
|
|
603
|
+
}
|
|
604
|
+
/** Flush any residual buffer at stream end (treat as the current channel). */
|
|
605
|
+
flush() {
|
|
606
|
+
const rest = this.buffer;
|
|
607
|
+
this.buffer = "";
|
|
608
|
+
return this.inside ? { text: "", thinking: rest } : { text: rest, thinking: "" };
|
|
609
|
+
}
|
|
610
|
+
};
|
|
611
|
+
function extractThinkTags(content) {
|
|
612
|
+
const scanner = new ThinkTagScanner();
|
|
613
|
+
const a = scanner.push(content);
|
|
614
|
+
const b = scanner.flush();
|
|
615
|
+
return { content: a.text + b.text, thinking: a.thinking + b.thinking };
|
|
376
616
|
}
|
|
377
|
-
function
|
|
378
|
-
return
|
|
617
|
+
function reasoningEffortEmit(map) {
|
|
618
|
+
return (body, resolved, model) => {
|
|
619
|
+
const value = map(resolved, model);
|
|
620
|
+
if (value) body.reasoning_effort = value;
|
|
621
|
+
};
|
|
379
622
|
}
|
|
380
|
-
function
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
623
|
+
function reasoningObjectEmit(mapEffort) {
|
|
624
|
+
return (body, resolved, model) => {
|
|
625
|
+
if (resolved.hasBudgetOverride) {
|
|
626
|
+
body.reasoning = { max_tokens: resolved.thinkingBudget };
|
|
627
|
+
} else if (resolved.activeEffort) {
|
|
628
|
+
body.reasoning = { effort: mapEffort(resolved.activeEffort, model) };
|
|
629
|
+
} else if (resolved.thinkingDisabled) {
|
|
630
|
+
body.reasoning = { enabled: false };
|
|
631
|
+
}
|
|
632
|
+
};
|
|
385
633
|
}
|
|
386
|
-
|
|
387
|
-
|
|
634
|
+
var ROUND_TRIP_FIELDS = ["reasoning_content", "reasoning", "reasoning_details"];
|
|
635
|
+
function isRoundTripField(field) {
|
|
636
|
+
return typeof field === "string" && ROUND_TRIP_FIELDS.includes(field);
|
|
388
637
|
}
|
|
389
|
-
function
|
|
390
|
-
if (
|
|
391
|
-
if (
|
|
392
|
-
return "
|
|
638
|
+
function normalizeMessageContent(content) {
|
|
639
|
+
if (typeof content === "string") return content;
|
|
640
|
+
if (content == null) return "";
|
|
641
|
+
if (!Array.isArray(content)) return "";
|
|
642
|
+
return content.map((part) => {
|
|
643
|
+
if (typeof part === "string") return part;
|
|
644
|
+
if (!part || typeof part !== "object") return "";
|
|
645
|
+
if (typeof part.text === "string") return part.text;
|
|
646
|
+
if (typeof part.content === "string") return part.content;
|
|
647
|
+
return "";
|
|
648
|
+
}).join("");
|
|
393
649
|
}
|
|
394
|
-
var
|
|
395
|
-
name
|
|
650
|
+
var OpenAICompatibleProvider = class {
|
|
651
|
+
name;
|
|
652
|
+
/** `json_schema` is honored natively when the profile's `supportsJsonSchema`
|
|
653
|
+
* capability is true for this model (the default); otherwise the engine
|
|
654
|
+
* downgrades the request to plain `json_object` (see `buildRequestBody`),
|
|
655
|
+
* which does NOT enforce the schema shape. */
|
|
656
|
+
nativeStructuredOutputSupport(model) {
|
|
657
|
+
const supportsSchema = resolvePerModel(
|
|
658
|
+
this.profile.capabilities?.supportsJsonSchema,
|
|
659
|
+
model,
|
|
660
|
+
true
|
|
661
|
+
);
|
|
662
|
+
return supportsSchema ? "schema" : "downgraded";
|
|
663
|
+
}
|
|
664
|
+
profile;
|
|
396
665
|
baseUrl;
|
|
397
|
-
|
|
666
|
+
/** A key string, or a resolver invoked per request (expiring tokens). */
|
|
667
|
+
apiKeySource;
|
|
668
|
+
authHeader;
|
|
398
669
|
governor;
|
|
399
|
-
constructor(options
|
|
400
|
-
|
|
401
|
-
this.
|
|
670
|
+
constructor(options) {
|
|
671
|
+
const p = options.profile;
|
|
672
|
+
this.profile = p;
|
|
673
|
+
this.name = p.name;
|
|
674
|
+
this.apiKeySource = options.apiKey ?? (p.envApiKey ? process.env[p.envApiKey] : void 0) ?? "";
|
|
675
|
+
this.authHeader = options.authHeader;
|
|
676
|
+
const envBase = p.envBaseUrl ? process.env[p.envBaseUrl] : void 0;
|
|
677
|
+
const explicitBase = options.baseUrl ?? envBase;
|
|
678
|
+
this.baseUrl = (explicitBase ?? p.defaultBaseUrl).replace(/\/$/, "");
|
|
402
679
|
this.governor = options.rateLimit ? new RateLimiter(options.rateLimit) : void 0;
|
|
403
|
-
|
|
404
|
-
|
|
680
|
+
const label = p.label ?? p.name;
|
|
681
|
+
if (p.requireExplicitBaseUrl && explicitBase === void 0) {
|
|
682
|
+
const env = p.envBaseUrl ? ` or ${p.envBaseUrl}` : "";
|
|
683
|
+
throw new Error(
|
|
684
|
+
`${label} requires a base URL. Set providers.${p.name}.baseUrl${env} (it is resource-specific and has no default).`
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
if (typeof this.apiKeySource === "string" && !this.apiKeySource && !p.allowMissingApiKey) {
|
|
688
|
+
const env = p.envApiKey ?? "the API key env var";
|
|
689
|
+
throw new Error(`${label} API key is required. Set ${env} or pass apiKey in options.`);
|
|
405
690
|
}
|
|
406
691
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
692
|
+
/** Resolve the API key for one request, validating against allowMissingApiKey. */
|
|
693
|
+
async resolveKey() {
|
|
694
|
+
const key = await resolveApiKey(this.apiKeySource);
|
|
695
|
+
if (!key && !this.profile.allowMissingApiKey) {
|
|
696
|
+
const label = this.profile.label ?? this.profile.name;
|
|
697
|
+
const env = this.profile.envApiKey ?? "the API key env var";
|
|
698
|
+
throw new Error(`${label} API key is required. Set ${env} or pass apiKey in options.`);
|
|
699
|
+
}
|
|
700
|
+
return key;
|
|
701
|
+
}
|
|
410
702
|
async chat(messages, options) {
|
|
703
|
+
const headers = this.buildHeaders(await this.resolveKey());
|
|
411
704
|
const body = this.buildRequestBody(messages, options, false);
|
|
412
705
|
const res = await fetchWithRetry(
|
|
413
706
|
`${this.baseUrl}/chat/completions`,
|
|
414
707
|
{
|
|
415
708
|
method: "POST",
|
|
416
|
-
headers
|
|
417
|
-
"Content-Type": "application/json",
|
|
418
|
-
Authorization: `Bearer ${this.apiKey}`
|
|
419
|
-
},
|
|
709
|
+
headers,
|
|
420
710
|
body: JSON.stringify(body),
|
|
421
711
|
signal: options.signal
|
|
422
712
|
},
|
|
423
|
-
{ governor: this.governor }
|
|
713
|
+
{ governor: this.governor, provider: this.name }
|
|
424
714
|
);
|
|
425
715
|
if (!res.ok) {
|
|
426
716
|
const errorBody = await res.text();
|
|
427
|
-
|
|
428
|
-
|
|
717
|
+
throw buildProviderError({
|
|
718
|
+
provider: this.name,
|
|
719
|
+
status: res.status,
|
|
720
|
+
headers: res.headers,
|
|
721
|
+
message: this.extractErrorMessage(errorBody, res.status),
|
|
722
|
+
body: errorBody
|
|
723
|
+
});
|
|
429
724
|
}
|
|
430
725
|
const json = await res.json();
|
|
431
|
-
|
|
432
|
-
const usage = json.usage ? {
|
|
433
|
-
prompt_tokens: json.usage.prompt_tokens,
|
|
434
|
-
completion_tokens: json.usage.completion_tokens,
|
|
435
|
-
total_tokens: json.usage.total_tokens,
|
|
436
|
-
reasoning_tokens: json.usage.completion_tokens_details?.reasoning_tokens,
|
|
437
|
-
cached_tokens: json.usage.prompt_tokens_details?.cached_tokens
|
|
438
|
-
} : void 0;
|
|
439
|
-
const cost = usage ? estimateOpenAICost(
|
|
440
|
-
options.model,
|
|
441
|
-
usage.prompt_tokens,
|
|
442
|
-
usage.completion_tokens,
|
|
443
|
-
usage.cached_tokens
|
|
444
|
-
) : void 0;
|
|
445
|
-
return {
|
|
446
|
-
content: choice.message.content ?? "",
|
|
447
|
-
tool_calls: choice.message.tool_calls?.map((tc) => ({
|
|
448
|
-
id: tc.id,
|
|
449
|
-
type: "function",
|
|
450
|
-
function: {
|
|
451
|
-
name: tc.function.name,
|
|
452
|
-
arguments: tc.function.arguments
|
|
453
|
-
}
|
|
454
|
-
})),
|
|
455
|
-
usage,
|
|
456
|
-
cost
|
|
457
|
-
};
|
|
726
|
+
return this.parseResponse(json, options.model);
|
|
458
727
|
}
|
|
459
|
-
// ---------------------------------------------------------------------------
|
|
460
|
-
// stream - SSE streaming completion
|
|
461
|
-
// ---------------------------------------------------------------------------
|
|
462
728
|
async *stream(messages, options) {
|
|
729
|
+
const headers = this.buildHeaders(await this.resolveKey());
|
|
463
730
|
const body = this.buildRequestBody(messages, options, true);
|
|
464
731
|
const res = await fetchWithRetry(
|
|
465
732
|
`${this.baseUrl}/chat/completions`,
|
|
466
733
|
{
|
|
467
734
|
method: "POST",
|
|
468
|
-
headers
|
|
469
|
-
"Content-Type": "application/json",
|
|
470
|
-
Authorization: `Bearer ${this.apiKey}`
|
|
471
|
-
},
|
|
735
|
+
headers,
|
|
472
736
|
body: JSON.stringify(body),
|
|
473
737
|
signal: options.signal
|
|
474
738
|
},
|
|
475
|
-
{ governor: this.governor }
|
|
739
|
+
{ governor: this.governor, provider: this.name }
|
|
476
740
|
);
|
|
477
741
|
if (!res.ok) {
|
|
478
742
|
const errorBody = await res.text();
|
|
479
|
-
|
|
480
|
-
|
|
743
|
+
throw buildProviderError({
|
|
744
|
+
provider: this.name,
|
|
745
|
+
status: res.status,
|
|
746
|
+
headers: res.headers,
|
|
747
|
+
message: this.extractErrorMessage(errorBody, res.status),
|
|
748
|
+
body: errorBody
|
|
749
|
+
});
|
|
481
750
|
}
|
|
482
751
|
if (!res.body) {
|
|
483
|
-
throw new Error(
|
|
752
|
+
throw new Error(`${this.profile.label ?? this.profile.name} stream response has no body`);
|
|
484
753
|
}
|
|
485
754
|
yield* this.parseSSEStream(res.body, options.model);
|
|
486
755
|
}
|
|
487
756
|
// ---------------------------------------------------------------------------
|
|
488
|
-
//
|
|
757
|
+
// Request building
|
|
489
758
|
// ---------------------------------------------------------------------------
|
|
490
|
-
|
|
491
|
-
const
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
if (
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
} else if (thinkingDisabled) {
|
|
501
|
-
wireEffort = clampReasoningEffort(options.model, "none");
|
|
502
|
-
}
|
|
759
|
+
buildHeaders(apiKey) {
|
|
760
|
+
const headers = {
|
|
761
|
+
"Content-Type": "application/json",
|
|
762
|
+
...this.profile.headers ?? {}
|
|
763
|
+
};
|
|
764
|
+
if (apiKey) {
|
|
765
|
+
const auth = this.authHeader ?? this.profile.authHeader ?? "bearer";
|
|
766
|
+
if (auth === "bearer") headers.Authorization = `Bearer ${apiKey}`;
|
|
767
|
+
else if (auth === "api-key") headers["api-key"] = apiKey;
|
|
768
|
+
else headers[auth.header] = auth.scheme ? `${auth.scheme} ${apiKey}` : apiKey;
|
|
503
769
|
}
|
|
504
|
-
|
|
770
|
+
return headers;
|
|
771
|
+
}
|
|
772
|
+
buildRequestBody(messages, options, stream) {
|
|
773
|
+
const profile = this.profile;
|
|
774
|
+
const resolved = resolveThinkingOptions(options);
|
|
505
775
|
const body = {
|
|
506
776
|
model: options.model,
|
|
507
|
-
messages: messages.map((m) => this.formatMessage(m,
|
|
777
|
+
messages: messages.map((m) => this.formatMessage(m, options.model)),
|
|
508
778
|
stream
|
|
509
779
|
};
|
|
510
|
-
|
|
780
|
+
const emitResult = profile.reasoning.emit(body, resolved, options.model) ?? {};
|
|
781
|
+
const stripTemperature = emitResult.stripTemperature ?? false;
|
|
782
|
+
if (options.temperature !== void 0 && !stripTemperature) {
|
|
511
783
|
body.temperature = options.temperature;
|
|
512
784
|
}
|
|
513
785
|
if (options.maxTokens !== void 0) {
|
|
514
|
-
body.max_completion_tokens = options.maxTokens;
|
|
786
|
+
body[profile.maxTokensField ?? "max_completion_tokens"] = options.maxTokens;
|
|
515
787
|
}
|
|
516
788
|
if (options.stop) body.stop = options.stop;
|
|
517
789
|
if (options.tools && options.tools.length > 0) {
|
|
518
790
|
body.tools = options.tools;
|
|
519
|
-
if (
|
|
791
|
+
if (resolvePerModel(profile.parallelToolCalls, options.model, false)) {
|
|
520
792
|
body.parallel_tool_calls = true;
|
|
521
793
|
}
|
|
522
794
|
}
|
|
523
|
-
if (options.toolChoice !== void 0)
|
|
524
|
-
body.tool_choice = options.toolChoice;
|
|
525
|
-
}
|
|
795
|
+
if (options.toolChoice !== void 0) body.tool_choice = options.toolChoice;
|
|
526
796
|
if (options.responseFormat) {
|
|
527
|
-
|
|
797
|
+
const supportsSchema = resolvePerModel(
|
|
798
|
+
profile.capabilities?.supportsJsonSchema,
|
|
799
|
+
options.model,
|
|
800
|
+
true
|
|
801
|
+
);
|
|
802
|
+
body.response_format = options.responseFormat.type === "json_schema" && !supportsSchema ? { type: "json_object" } : options.responseFormat;
|
|
528
803
|
}
|
|
529
|
-
if (
|
|
530
|
-
if (stream) {
|
|
804
|
+
if (stream && (profile.capabilities?.supportsStreamUsage ?? true)) {
|
|
531
805
|
body.stream_options = { include_usage: true };
|
|
532
806
|
}
|
|
533
|
-
if (
|
|
534
|
-
|
|
807
|
+
if (profile.requestDefaults) Object.assign(body, profile.requestDefaults);
|
|
808
|
+
const userKeys = options.providerOptions ? Object.keys(options.providerOptions) : [];
|
|
809
|
+
if (options.providerOptions) Object.assign(body, options.providerOptions);
|
|
810
|
+
const forbidden = resolvePerModel(profile.capabilities?.forbiddenParams, options.model, []);
|
|
811
|
+
for (const key of forbidden) {
|
|
812
|
+
if (!userKeys.includes(key)) delete body[key];
|
|
535
813
|
}
|
|
536
814
|
return body;
|
|
537
815
|
}
|
|
538
|
-
|
|
816
|
+
formatMessage(msg, model) {
|
|
817
|
+
const out = {
|
|
818
|
+
role: this.profile.roleFor ? this.profile.roleFor(msg.role, model) : msg.role,
|
|
819
|
+
content: msg.content
|
|
820
|
+
};
|
|
821
|
+
if (msg.name && (this.profile.capabilities?.emitsMessageName ?? true)) out.name = msg.name;
|
|
822
|
+
if (msg.tool_calls) out.tool_calls = msg.tool_calls;
|
|
823
|
+
if (msg.tool_call_id) out.tool_call_id = msg.tool_call_id;
|
|
824
|
+
if (this.profile.reasoning.roundTrip === "on-tool-call-turns" && msg.role === "assistant" && msg.tool_calls && msg.tool_calls.length > 0) {
|
|
825
|
+
const rt = msg.providerMetadata?.openaiCompatReasoning;
|
|
826
|
+
if (rt && rt.provider === this.name && isRoundTripField(rt.field) && rt.value !== void 0 && rt.value !== null) {
|
|
827
|
+
out[rt.field] = rt.value;
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
return out;
|
|
831
|
+
}
|
|
832
|
+
// ---------------------------------------------------------------------------
|
|
833
|
+
// Response parsing
|
|
834
|
+
// ---------------------------------------------------------------------------
|
|
539
835
|
extractErrorMessage(body, status) {
|
|
836
|
+
const label = this.profile.label ?? this.profile.name;
|
|
540
837
|
try {
|
|
541
838
|
const json = JSON.parse(body);
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
}
|
|
839
|
+
const message = json.error?.message ?? json.message ?? json.detail;
|
|
840
|
+
if (message) return `${label} API error (${status}): ${message}`;
|
|
545
841
|
} catch {
|
|
546
842
|
}
|
|
547
|
-
return
|
|
843
|
+
return `${label} API error (${status}): ${body}`;
|
|
548
844
|
}
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
845
|
+
toUsage(raw) {
|
|
846
|
+
if (!raw) return void 0;
|
|
847
|
+
return {
|
|
848
|
+
prompt_tokens: raw.prompt_tokens,
|
|
849
|
+
completion_tokens: raw.completion_tokens,
|
|
850
|
+
total_tokens: raw.total_tokens,
|
|
851
|
+
reasoning_tokens: raw.completion_tokens_details?.reasoning_tokens,
|
|
852
|
+
cached_tokens: raw.prompt_tokens_details?.cached_tokens
|
|
553
853
|
};
|
|
554
|
-
if (msg.name) out.name = msg.name;
|
|
555
|
-
if (msg.tool_calls) out.tool_calls = msg.tool_calls;
|
|
556
|
-
if (msg.tool_call_id) out.tool_call_id = msg.tool_call_id;
|
|
557
|
-
return out;
|
|
558
854
|
}
|
|
855
|
+
computeCost(model, usage, reportedCost) {
|
|
856
|
+
const p = this.profile.pricing;
|
|
857
|
+
if (p.kind === "zero") return 0;
|
|
858
|
+
if (!usage) return void 0;
|
|
859
|
+
switch (p.kind) {
|
|
860
|
+
case "unknown":
|
|
861
|
+
return void 0;
|
|
862
|
+
case "from-response":
|
|
863
|
+
return typeof reportedCost === "number" && Number.isFinite(reportedCost) ? reportedCost : void 0;
|
|
864
|
+
case "table": {
|
|
865
|
+
const c = priceFromTable(
|
|
866
|
+
p.table,
|
|
867
|
+
model,
|
|
868
|
+
usage.prompt_tokens,
|
|
869
|
+
usage.completion_tokens,
|
|
870
|
+
usage.cached_tokens
|
|
871
|
+
);
|
|
872
|
+
return c !== void 0 && Number.isFinite(c) ? c : void 0;
|
|
873
|
+
}
|
|
874
|
+
default: {
|
|
875
|
+
const _exhaustive = p;
|
|
876
|
+
throw new Error(`Unhandled pricing source: ${JSON.stringify(_exhaustive)}`);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
parseResponse(json, model) {
|
|
881
|
+
const message = json.choices?.[0]?.message ?? {};
|
|
882
|
+
let content = normalizeMessageContent(message.content);
|
|
883
|
+
let thinking;
|
|
884
|
+
let roundTrip;
|
|
885
|
+
switch (this.profile.reasoning.capture) {
|
|
886
|
+
case "reasoning_content":
|
|
887
|
+
if (typeof message.reasoning_content === "string") {
|
|
888
|
+
thinking = message.reasoning_content;
|
|
889
|
+
roundTrip = {
|
|
890
|
+
provider: this.name,
|
|
891
|
+
field: "reasoning_content",
|
|
892
|
+
value: message.reasoning_content
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
break;
|
|
896
|
+
case "reasoning":
|
|
897
|
+
if (typeof message.reasoning === "string") {
|
|
898
|
+
thinking = message.reasoning;
|
|
899
|
+
roundTrip = { provider: this.name, field: "reasoning", value: message.reasoning };
|
|
900
|
+
}
|
|
901
|
+
break;
|
|
902
|
+
case "reasoning_details":
|
|
903
|
+
if (typeof message.reasoning === "string") thinking = message.reasoning;
|
|
904
|
+
if (message.reasoning_details !== void 0) {
|
|
905
|
+
roundTrip = {
|
|
906
|
+
provider: this.name,
|
|
907
|
+
field: "reasoning_details",
|
|
908
|
+
value: message.reasoning_details
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
break;
|
|
912
|
+
case "think_tags": {
|
|
913
|
+
const ex = extractThinkTags(content);
|
|
914
|
+
content = ex.content;
|
|
915
|
+
if (ex.thinking) thinking = ex.thinking;
|
|
916
|
+
break;
|
|
917
|
+
}
|
|
918
|
+
case "none":
|
|
919
|
+
break;
|
|
920
|
+
default: {
|
|
921
|
+
const _exhaustive = this.profile.reasoning.capture;
|
|
922
|
+
throw new Error(`Unhandled reasoning capture: ${String(_exhaustive)}`);
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
const usage = this.toUsage(json.usage);
|
|
926
|
+
const cost = this.computeCost(model, usage, json.usage?.cost);
|
|
927
|
+
return {
|
|
928
|
+
content,
|
|
929
|
+
thinking_content: thinking || void 0,
|
|
930
|
+
tool_calls: message.tool_calls?.map((tc) => ({
|
|
931
|
+
id: tc.id,
|
|
932
|
+
type: "function",
|
|
933
|
+
function: { name: tc.function.name, arguments: tc.function.arguments }
|
|
934
|
+
})),
|
|
935
|
+
usage,
|
|
936
|
+
cost,
|
|
937
|
+
providerMetadata: this.roundTripMetadata(roundTrip)
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
roundTripMetadata(rt) {
|
|
941
|
+
if (!rt || this.profile.reasoning.roundTrip === void 0 || this.profile.reasoning.roundTrip === "none") {
|
|
942
|
+
return void 0;
|
|
943
|
+
}
|
|
944
|
+
return rt.value === void 0 || rt.value === null ? void 0 : { openaiCompatReasoning: rt };
|
|
945
|
+
}
|
|
946
|
+
// ---------------------------------------------------------------------------
|
|
947
|
+
// SSE streaming
|
|
948
|
+
// ---------------------------------------------------------------------------
|
|
559
949
|
async *parseSSEStream(body, model) {
|
|
560
950
|
const reader = body.getReader();
|
|
561
951
|
const decoder = new TextDecoder();
|
|
562
952
|
let buffer = "";
|
|
563
953
|
let usageData;
|
|
954
|
+
const capture = this.profile.reasoning.capture;
|
|
955
|
+
const thinkScanner = capture === "think_tags" ? new ThinkTagScanner() : void 0;
|
|
956
|
+
let reasoningText = "";
|
|
957
|
+
const reasoningDetails = [];
|
|
564
958
|
const indexToId = /* @__PURE__ */ new Map();
|
|
959
|
+
const pendingToolCalls = /* @__PURE__ */ new Map();
|
|
960
|
+
const flushPendingToolCalls = () => {
|
|
961
|
+
const out = [];
|
|
962
|
+
for (const [index, buf] of pendingToolCalls) {
|
|
963
|
+
out.push({
|
|
964
|
+
type: "tool_call_delta",
|
|
965
|
+
id: `__toolcall_${index}`,
|
|
966
|
+
name: buf.name,
|
|
967
|
+
arguments: buf.arguments
|
|
968
|
+
});
|
|
969
|
+
}
|
|
970
|
+
pendingToolCalls.clear();
|
|
971
|
+
return out;
|
|
972
|
+
};
|
|
973
|
+
const makeDone = () => {
|
|
974
|
+
const usage = this.toUsage(usageData);
|
|
975
|
+
let providerMetadata;
|
|
976
|
+
if (this.profile.reasoning.roundTrip === "on-tool-call-turns") {
|
|
977
|
+
if (capture === "reasoning_details" && reasoningDetails.length > 0) {
|
|
978
|
+
providerMetadata = {
|
|
979
|
+
openaiCompatReasoning: {
|
|
980
|
+
provider: this.name,
|
|
981
|
+
field: "reasoning_details",
|
|
982
|
+
value: reasoningDetails
|
|
983
|
+
}
|
|
984
|
+
};
|
|
985
|
+
} else if ((capture === "reasoning_content" || capture === "reasoning") && reasoningText) {
|
|
986
|
+
providerMetadata = {
|
|
987
|
+
openaiCompatReasoning: { provider: this.name, field: capture, value: reasoningText }
|
|
988
|
+
};
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
return {
|
|
992
|
+
type: "done",
|
|
993
|
+
usage,
|
|
994
|
+
cost: this.computeCost(model, usage, usageData?.cost),
|
|
995
|
+
providerMetadata
|
|
996
|
+
};
|
|
997
|
+
};
|
|
565
998
|
try {
|
|
566
|
-
|
|
999
|
+
for (; ; ) {
|
|
567
1000
|
const { done, value } = await reader.read();
|
|
568
1001
|
if (done) break;
|
|
569
1002
|
buffer += decoder.decode(value, { stream: true });
|
|
@@ -573,47 +1006,76 @@ var OpenAIProvider = class {
|
|
|
573
1006
|
const trimmed = line.trim();
|
|
574
1007
|
if (!trimmed || trimmed.startsWith(":")) continue;
|
|
575
1008
|
if (trimmed === "data: [DONE]") {
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
usageData.cached_tokens
|
|
584
|
-
) : void 0
|
|
585
|
-
};
|
|
1009
|
+
if (thinkScanner) {
|
|
1010
|
+
const flushed = thinkScanner.flush();
|
|
1011
|
+
if (flushed.text) yield { type: "text_delta", content: flushed.text };
|
|
1012
|
+
if (flushed.thinking) yield { type: "thinking_delta", content: flushed.thinking };
|
|
1013
|
+
}
|
|
1014
|
+
yield* flushPendingToolCalls();
|
|
1015
|
+
yield makeDone();
|
|
586
1016
|
return;
|
|
587
1017
|
}
|
|
588
|
-
if (trimmed.startsWith("data: "))
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
if (!
|
|
608
|
-
|
|
1018
|
+
if (!trimmed.startsWith("data: ")) continue;
|
|
1019
|
+
let parsed;
|
|
1020
|
+
try {
|
|
1021
|
+
parsed = JSON.parse(trimmed.slice(6));
|
|
1022
|
+
} catch {
|
|
1023
|
+
continue;
|
|
1024
|
+
}
|
|
1025
|
+
if (parsed.error) {
|
|
1026
|
+
const message = typeof parsed.error.message === "string" ? parsed.error.message : `${this.profile.label ?? this.profile.name} stream error`;
|
|
1027
|
+
throw new ProviderError({
|
|
1028
|
+
provider: this.name,
|
|
1029
|
+
status: 0,
|
|
1030
|
+
retryable: false,
|
|
1031
|
+
message,
|
|
1032
|
+
body: JSON.stringify(parsed.error)
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
1035
|
+
if (parsed.usage) {
|
|
1036
|
+
usageData = parsed.usage;
|
|
1037
|
+
if (!parsed.choices || parsed.choices.length === 0) continue;
|
|
1038
|
+
}
|
|
1039
|
+
const delta = parsed.choices?.[0]?.delta;
|
|
1040
|
+
if (!delta) continue;
|
|
1041
|
+
const deltaReasoning = capture === "reasoning_content" ? delta.reasoning_content : capture === "reasoning" || capture === "reasoning_details" ? delta.reasoning : void 0;
|
|
1042
|
+
if (typeof deltaReasoning === "string" && deltaReasoning.length > 0) {
|
|
1043
|
+
reasoningText += deltaReasoning;
|
|
1044
|
+
yield { type: "thinking_delta", content: deltaReasoning };
|
|
1045
|
+
}
|
|
1046
|
+
if (capture === "reasoning_details" && Array.isArray(delta.reasoning_details)) {
|
|
1047
|
+
reasoningDetails.push(...delta.reasoning_details);
|
|
1048
|
+
}
|
|
1049
|
+
if (typeof delta.content === "string" && delta.content.length > 0) {
|
|
1050
|
+
if (thinkScanner) {
|
|
1051
|
+
const { text, thinking } = thinkScanner.push(delta.content);
|
|
1052
|
+
if (text) yield { type: "text_delta", content: text };
|
|
1053
|
+
if (thinking) yield { type: "thinking_delta", content: thinking };
|
|
1054
|
+
} else {
|
|
609
1055
|
yield { type: "text_delta", content: delta.content };
|
|
610
1056
|
}
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
1057
|
+
}
|
|
1058
|
+
if (delta.tool_calls) {
|
|
1059
|
+
for (const tc of delta.tool_calls) {
|
|
1060
|
+
if (tc.id) indexToId.set(tc.index, tc.id);
|
|
1061
|
+
const id = indexToId.get(tc.index);
|
|
1062
|
+
if (id === void 0) {
|
|
1063
|
+
const buf2 = pendingToolCalls.get(tc.index) ?? { arguments: "" };
|
|
1064
|
+
if (tc.function?.name) buf2.name = tc.function.name;
|
|
1065
|
+
if (tc.function?.arguments) buf2.arguments += tc.function.arguments;
|
|
1066
|
+
pendingToolCalls.set(tc.index, buf2);
|
|
1067
|
+
continue;
|
|
1068
|
+
}
|
|
1069
|
+
const buf = pendingToolCalls.get(tc.index);
|
|
1070
|
+
if (buf) {
|
|
1071
|
+
pendingToolCalls.delete(tc.index);
|
|
1072
|
+
yield {
|
|
1073
|
+
type: "tool_call_delta",
|
|
1074
|
+
id,
|
|
1075
|
+
name: buf.name ?? tc.function?.name,
|
|
1076
|
+
arguments: buf.arguments + (tc.function?.arguments ?? "")
|
|
1077
|
+
};
|
|
1078
|
+
} else {
|
|
617
1079
|
yield {
|
|
618
1080
|
type: "tool_call_delta",
|
|
619
1081
|
id,
|
|
@@ -625,58 +1087,176 @@ var OpenAIProvider = class {
|
|
|
625
1087
|
}
|
|
626
1088
|
}
|
|
627
1089
|
}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
1090
|
+
if (thinkScanner) {
|
|
1091
|
+
const flushed = thinkScanner.flush();
|
|
1092
|
+
if (flushed.text) yield { type: "text_delta", content: flushed.text };
|
|
1093
|
+
if (flushed.thinking) yield { type: "thinking_delta", content: flushed.thinking };
|
|
1094
|
+
}
|
|
1095
|
+
yield* flushPendingToolCalls();
|
|
1096
|
+
throw new ProviderError({
|
|
1097
|
+
provider: this.name,
|
|
1098
|
+
status: 0,
|
|
1099
|
+
retryable: true,
|
|
1100
|
+
message: `${this.profile.label ?? this.profile.name} stream ended before [DONE]`
|
|
1101
|
+
});
|
|
638
1102
|
} finally {
|
|
639
1103
|
reader.releaseLock();
|
|
640
1104
|
}
|
|
641
1105
|
}
|
|
642
1106
|
};
|
|
643
1107
|
|
|
1108
|
+
// src/providers/openai.ts
|
|
1109
|
+
var OPENAI_PRICING = {
|
|
1110
|
+
// gpt-4o era — cache reads at 50% of input rate
|
|
1111
|
+
"gpt-4o": [25e-7, 1e-5, 0.5],
|
|
1112
|
+
"gpt-4o-mini": [15e-8, 6e-7, 0.5],
|
|
1113
|
+
"gpt-4-turbo": [1e-5, 3e-5, 0.5],
|
|
1114
|
+
"gpt-4": [3e-5, 6e-5, 0.5],
|
|
1115
|
+
"gpt-3.5-turbo": [5e-7, 15e-7, 0.5],
|
|
1116
|
+
o1: [15e-6, 6e-5, 0.5],
|
|
1117
|
+
"o1-mini": [3e-6, 12e-6, 0.5],
|
|
1118
|
+
"o1-pro": [15e-5, 6e-4, 0.5],
|
|
1119
|
+
// gpt-4.1 / o3 / o4 era — cache reads at 25% of input rate
|
|
1120
|
+
"gpt-4.1": [2e-6, 8e-6, 0.25],
|
|
1121
|
+
"gpt-4.1-mini": [4e-7, 16e-7, 0.25],
|
|
1122
|
+
"gpt-4.1-nano": [1e-7, 4e-7, 0.25],
|
|
1123
|
+
o3: [1e-5, 4e-5, 0.25],
|
|
1124
|
+
"o3-mini": [11e-7, 44e-7, 0.25],
|
|
1125
|
+
"o3-pro": [2e-5, 8e-5, 0.25],
|
|
1126
|
+
"o4-mini": [11e-7, 44e-7, 0.25],
|
|
1127
|
+
// gpt-5 era — cache reads at 10% of input rate
|
|
1128
|
+
"gpt-5": [125e-8, 1e-5, 0.1],
|
|
1129
|
+
"gpt-5-mini": [25e-8, 2e-6, 0.1],
|
|
1130
|
+
"gpt-5-nano": [5e-8, 4e-7, 0.1],
|
|
1131
|
+
"gpt-5.1": [125e-8, 1e-5, 0.1],
|
|
1132
|
+
"gpt-5.2": [175e-8, 14e-6, 0.1],
|
|
1133
|
+
"gpt-5.3": [175e-8, 14e-6, 0.1],
|
|
1134
|
+
"gpt-5.4": [25e-7, 15e-6, 0.1],
|
|
1135
|
+
"gpt-5.4-pro": [3e-5, 18e-5, 0.1],
|
|
1136
|
+
// gpt-5.5 era (flagship released 2026-04) — cache reads at 10% of input rate
|
|
1137
|
+
"gpt-5.5": [5e-6, 3e-5, 0.1],
|
|
1138
|
+
"gpt-5.5-pro": [3e-5, 18e-5, 0.1]
|
|
1139
|
+
};
|
|
1140
|
+
function estimateOpenAICost(model, promptTokens, completionTokens, cachedTokens) {
|
|
1141
|
+
return priceFromTable(OPENAI_PRICING, model, promptTokens, completionTokens, cachedTokens);
|
|
1142
|
+
}
|
|
1143
|
+
function isOSeriesModel(model) {
|
|
1144
|
+
return /^(o1|o3|o4-mini)/.test(model);
|
|
1145
|
+
}
|
|
1146
|
+
function supportsReasoningEffort(model) {
|
|
1147
|
+
return isOSeriesModel(model) || /^gpt-5/.test(model);
|
|
1148
|
+
}
|
|
1149
|
+
function supportsReasoningNone(model) {
|
|
1150
|
+
return /^gpt-5\.[1-9]/.test(model);
|
|
1151
|
+
}
|
|
1152
|
+
function supportsXhigh(model) {
|
|
1153
|
+
return /^gpt-5\.([2-9]|\d{2,})/.test(model);
|
|
1154
|
+
}
|
|
1155
|
+
function clampReasoningEffort(model, effort) {
|
|
1156
|
+
if (model.startsWith("gpt-5-pro")) return "high";
|
|
1157
|
+
if (effort === "none" && !supportsReasoningNone(model)) return "minimal";
|
|
1158
|
+
if (effort === "xhigh" && !supportsXhigh(model)) return "high";
|
|
1159
|
+
return effort;
|
|
1160
|
+
}
|
|
1161
|
+
function effortToReasoningEffort(effort) {
|
|
1162
|
+
return effort === "max" ? "xhigh" : effort;
|
|
1163
|
+
}
|
|
1164
|
+
function budgetToReasoningEffort(budget) {
|
|
1165
|
+
if (budget <= 1024) return "low";
|
|
1166
|
+
if (budget <= 8192) return "medium";
|
|
1167
|
+
return "high";
|
|
1168
|
+
}
|
|
1169
|
+
var openaiReasoningEmit = (body, resolved, model) => {
|
|
1170
|
+
const oSeries = isOSeriesModel(model);
|
|
1171
|
+
const reasoningCapable = supportsReasoningEffort(model);
|
|
1172
|
+
const { thinkingBudget, thinkingDisabled, activeEffort, hasBudgetOverride } = resolved;
|
|
1173
|
+
let wireEffort;
|
|
1174
|
+
if (reasoningCapable) {
|
|
1175
|
+
if (hasBudgetOverride) {
|
|
1176
|
+
wireEffort = clampReasoningEffort(model, budgetToReasoningEffort(thinkingBudget));
|
|
1177
|
+
} else if (!thinkingDisabled && activeEffort) {
|
|
1178
|
+
wireEffort = clampReasoningEffort(model, effortToReasoningEffort(activeEffort));
|
|
1179
|
+
} else if (thinkingDisabled) {
|
|
1180
|
+
wireEffort = clampReasoningEffort(model, "none");
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
if (wireEffort) body.reasoning_effort = wireEffort;
|
|
1184
|
+
return { stripTemperature: oSeries || reasoningCapable && wireEffort !== void 0 };
|
|
1185
|
+
};
|
|
1186
|
+
var OPENAI_PROFILE = {
|
|
1187
|
+
name: "openai",
|
|
1188
|
+
label: "OpenAI",
|
|
1189
|
+
defaultBaseUrl: "https://api.openai.com/v1",
|
|
1190
|
+
envApiKey: "OPENAI_API_KEY",
|
|
1191
|
+
envBaseUrl: "OPENAI_BASE_URL",
|
|
1192
|
+
pricing: { kind: "table", table: OPENAI_PRICING },
|
|
1193
|
+
reasoning: { emit: openaiReasoningEmit, capture: "none" },
|
|
1194
|
+
roleFor: (role, model) => role === "system" && isOSeriesModel(model) ? "developer" : role,
|
|
1195
|
+
maxTokensField: "max_completion_tokens",
|
|
1196
|
+
parallelToolCalls: (model) => !isOSeriesModel(model)
|
|
1197
|
+
};
|
|
1198
|
+
var OpenAIProvider = class extends OpenAICompatibleProvider {
|
|
1199
|
+
constructor(options = {}) {
|
|
1200
|
+
super({ profile: OPENAI_PROFILE, ...options });
|
|
1201
|
+
}
|
|
1202
|
+
};
|
|
1203
|
+
|
|
644
1204
|
// src/providers/openai-responses.ts
|
|
645
1205
|
var OpenAIResponsesProvider = class {
|
|
646
1206
|
name = "openai-responses";
|
|
1207
|
+
/** The Responses API honors `json_schema` (structured outputs) natively. */
|
|
1208
|
+
nativeStructuredOutputSupport() {
|
|
1209
|
+
return "schema";
|
|
1210
|
+
}
|
|
647
1211
|
baseUrl;
|
|
648
|
-
|
|
1212
|
+
apiKeySource;
|
|
649
1213
|
governor;
|
|
650
1214
|
constructor(options = {}) {
|
|
651
|
-
this.
|
|
1215
|
+
this.apiKeySource = options.apiKey ?? process.env.OPENAI_API_KEY ?? "";
|
|
652
1216
|
this.baseUrl = (options.baseUrl ?? process.env.OPENAI_BASE_URL ?? "https://api.openai.com/v1").replace(/\/$/, "");
|
|
653
1217
|
this.governor = options.rateLimit ? new RateLimiter(options.rateLimit) : void 0;
|
|
654
|
-
if (!this.
|
|
1218
|
+
if (typeof this.apiKeySource === "string" && !this.apiKeySource) {
|
|
655
1219
|
throw new Error("OpenAI API key is required. Set OPENAI_API_KEY or pass apiKey in options.");
|
|
656
1220
|
}
|
|
657
1221
|
}
|
|
1222
|
+
/** Resolve the API key for one request (supports an expiring-token callback). */
|
|
1223
|
+
async resolveKey() {
|
|
1224
|
+
const key = await resolveApiKey(this.apiKeySource);
|
|
1225
|
+
if (!key) {
|
|
1226
|
+
throw new Error("OpenAI API key is required. Set OPENAI_API_KEY or pass apiKey in options.");
|
|
1227
|
+
}
|
|
1228
|
+
return key;
|
|
1229
|
+
}
|
|
1230
|
+
/** Build request headers from a resolved key (used by both chat and stream). */
|
|
1231
|
+
buildHeaders(apiKey) {
|
|
1232
|
+
return { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` };
|
|
1233
|
+
}
|
|
658
1234
|
// ---------------------------------------------------------------------------
|
|
659
1235
|
// chat - non-streaming
|
|
660
1236
|
// ---------------------------------------------------------------------------
|
|
661
1237
|
async chat(messages, options) {
|
|
1238
|
+
const headers = this.buildHeaders(await this.resolveKey());
|
|
662
1239
|
const body = this.buildRequestBody(messages, options, false);
|
|
663
1240
|
const res = await fetchWithRetry(
|
|
664
1241
|
`${this.baseUrl}/responses`,
|
|
665
1242
|
{
|
|
666
1243
|
method: "POST",
|
|
667
|
-
headers
|
|
668
|
-
"Content-Type": "application/json",
|
|
669
|
-
Authorization: `Bearer ${this.apiKey}`
|
|
670
|
-
},
|
|
1244
|
+
headers,
|
|
671
1245
|
body: JSON.stringify(body),
|
|
672
1246
|
signal: options.signal
|
|
673
1247
|
},
|
|
674
|
-
{ governor: this.governor }
|
|
1248
|
+
{ governor: this.governor, provider: this.name }
|
|
675
1249
|
);
|
|
676
1250
|
if (!res.ok) {
|
|
677
1251
|
const errorBody = await res.text();
|
|
678
1252
|
const message = this.extractErrorMessage(errorBody, res.status);
|
|
679
|
-
throw
|
|
1253
|
+
throw buildProviderError({
|
|
1254
|
+
provider: this.name,
|
|
1255
|
+
status: res.status,
|
|
1256
|
+
headers: res.headers,
|
|
1257
|
+
message,
|
|
1258
|
+
body: errorBody
|
|
1259
|
+
});
|
|
680
1260
|
}
|
|
681
1261
|
const json = await res.json();
|
|
682
1262
|
return this.parseResponse(json, options.model);
|
|
@@ -685,24 +1265,28 @@ var OpenAIResponsesProvider = class {
|
|
|
685
1265
|
// stream - SSE streaming
|
|
686
1266
|
// ---------------------------------------------------------------------------
|
|
687
1267
|
async *stream(messages, options) {
|
|
1268
|
+
const headers = this.buildHeaders(await this.resolveKey());
|
|
688
1269
|
const body = this.buildRequestBody(messages, options, true);
|
|
689
1270
|
const res = await fetchWithRetry(
|
|
690
1271
|
`${this.baseUrl}/responses`,
|
|
691
1272
|
{
|
|
692
1273
|
method: "POST",
|
|
693
|
-
headers
|
|
694
|
-
"Content-Type": "application/json",
|
|
695
|
-
Authorization: `Bearer ${this.apiKey}`
|
|
696
|
-
},
|
|
1274
|
+
headers,
|
|
697
1275
|
body: JSON.stringify(body),
|
|
698
1276
|
signal: options.signal
|
|
699
1277
|
},
|
|
700
|
-
{ governor: this.governor }
|
|
1278
|
+
{ governor: this.governor, provider: this.name }
|
|
701
1279
|
);
|
|
702
1280
|
if (!res.ok) {
|
|
703
1281
|
const errorBody = await res.text();
|
|
704
1282
|
const message = this.extractErrorMessage(errorBody, res.status);
|
|
705
|
-
throw
|
|
1283
|
+
throw buildProviderError({
|
|
1284
|
+
provider: this.name,
|
|
1285
|
+
status: res.status,
|
|
1286
|
+
headers: res.headers,
|
|
1287
|
+
message,
|
|
1288
|
+
body: errorBody
|
|
1289
|
+
});
|
|
706
1290
|
}
|
|
707
1291
|
if (!res.body) {
|
|
708
1292
|
throw new Error("OpenAI Responses stream has no body");
|
|
@@ -991,7 +1575,12 @@ var OpenAIResponsesProvider = class {
|
|
|
991
1575
|
}
|
|
992
1576
|
case "response.failed": {
|
|
993
1577
|
const errorMsg = data.response?.error?.message ?? data.response?.status_details?.error?.message ?? "Unknown error";
|
|
994
|
-
throw new
|
|
1578
|
+
throw new ProviderError({
|
|
1579
|
+
provider: this.name,
|
|
1580
|
+
status: 0,
|
|
1581
|
+
retryable: false,
|
|
1582
|
+
message: `OpenAI Responses API error: ${errorMsg}`
|
|
1583
|
+
});
|
|
995
1584
|
}
|
|
996
1585
|
default:
|
|
997
1586
|
return null;
|
|
@@ -1045,7 +1634,7 @@ function estimateAnthropicCost(model, inputTokens, outputTokens, cacheReadTokens
|
|
|
1045
1634
|
}
|
|
1046
1635
|
}
|
|
1047
1636
|
}
|
|
1048
|
-
if (!pricing) return 0;
|
|
1637
|
+
if (!pricing) return void 0;
|
|
1049
1638
|
const [inputRate, outputRate] = pricing;
|
|
1050
1639
|
const cacheRead = cacheReadTokens ?? 0;
|
|
1051
1640
|
const cacheWrite = cacheWriteTokens ?? 0;
|
|
@@ -1079,63 +1668,90 @@ function clampAnthropicEffort(model, effort) {
|
|
|
1079
1668
|
}
|
|
1080
1669
|
var AnthropicProvider = class {
|
|
1081
1670
|
name = "anthropic";
|
|
1671
|
+
/** Anthropic ignores native `json_schema` structurally — Axl uses a system
|
|
1672
|
+
* prompt JSON instruction + client-side Zod validation (see buildRequest). */
|
|
1673
|
+
nativeStructuredOutputSupport() {
|
|
1674
|
+
return "unsupported";
|
|
1675
|
+
}
|
|
1082
1676
|
baseUrl;
|
|
1083
|
-
|
|
1084
|
-
currentModel;
|
|
1677
|
+
apiKeySource;
|
|
1085
1678
|
governor;
|
|
1086
1679
|
constructor(options = {}) {
|
|
1087
|
-
this.
|
|
1680
|
+
this.apiKeySource = options.apiKey ?? process.env.ANTHROPIC_API_KEY ?? "";
|
|
1088
1681
|
this.baseUrl = (options.baseUrl ?? "https://api.anthropic.com/v1").replace(/\/$/, "");
|
|
1089
1682
|
this.governor = options.rateLimit ? new RateLimiter(options.rateLimit) : void 0;
|
|
1090
|
-
if (!this.
|
|
1683
|
+
if (typeof this.apiKeySource === "string" && !this.apiKeySource) {
|
|
1684
|
+
throw new Error(
|
|
1685
|
+
"Anthropic API key is required. Set ANTHROPIC_API_KEY or pass apiKey in options."
|
|
1686
|
+
);
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
/** Resolve the API key for one request (supports an expiring-token callback). */
|
|
1690
|
+
async resolveKey() {
|
|
1691
|
+
const key = await resolveApiKey(this.apiKeySource);
|
|
1692
|
+
if (!key) {
|
|
1091
1693
|
throw new Error(
|
|
1092
1694
|
"Anthropic API key is required. Set ANTHROPIC_API_KEY or pass apiKey in options."
|
|
1093
1695
|
);
|
|
1094
1696
|
}
|
|
1697
|
+
return key;
|
|
1095
1698
|
}
|
|
1096
1699
|
// ---------------------------------------------------------------------------
|
|
1097
1700
|
// chat - non-streaming completion
|
|
1098
1701
|
// ---------------------------------------------------------------------------
|
|
1099
1702
|
async chat(messages, options) {
|
|
1100
|
-
|
|
1703
|
+
const headers = this.buildHeaders(await this.resolveKey());
|
|
1101
1704
|
const body = this.buildRequestBody(messages, options, false);
|
|
1102
1705
|
const res = await fetchWithRetry(
|
|
1103
1706
|
`${this.baseUrl}/messages`,
|
|
1104
1707
|
{
|
|
1105
1708
|
method: "POST",
|
|
1106
|
-
headers
|
|
1709
|
+
headers,
|
|
1107
1710
|
body: JSON.stringify(body),
|
|
1108
1711
|
signal: options.signal
|
|
1109
1712
|
},
|
|
1110
|
-
{ governor: this.governor }
|
|
1713
|
+
{ governor: this.governor, provider: this.name }
|
|
1111
1714
|
);
|
|
1112
1715
|
if (!res.ok) {
|
|
1113
1716
|
const errorBody = await res.text();
|
|
1114
1717
|
const message = this.extractErrorMessage(errorBody, res.status);
|
|
1115
|
-
throw
|
|
1718
|
+
throw buildProviderError({
|
|
1719
|
+
provider: this.name,
|
|
1720
|
+
status: res.status,
|
|
1721
|
+
headers: res.headers,
|
|
1722
|
+
message,
|
|
1723
|
+
body: errorBody
|
|
1724
|
+
});
|
|
1116
1725
|
}
|
|
1117
1726
|
const json = await res.json();
|
|
1118
|
-
return this.parseResponse(json);
|
|
1727
|
+
return this.parseResponse(json, options.model);
|
|
1119
1728
|
}
|
|
1120
1729
|
// ---------------------------------------------------------------------------
|
|
1121
1730
|
// stream - SSE streaming completion
|
|
1122
1731
|
// ---------------------------------------------------------------------------
|
|
1123
1732
|
async *stream(messages, options) {
|
|
1733
|
+
const headers = this.buildHeaders(await this.resolveKey());
|
|
1124
1734
|
const body = this.buildRequestBody(messages, options, true);
|
|
1125
1735
|
const res = await fetchWithRetry(
|
|
1126
1736
|
`${this.baseUrl}/messages`,
|
|
1127
1737
|
{
|
|
1128
1738
|
method: "POST",
|
|
1129
|
-
headers
|
|
1739
|
+
headers,
|
|
1130
1740
|
body: JSON.stringify(body),
|
|
1131
1741
|
signal: options.signal
|
|
1132
1742
|
},
|
|
1133
|
-
{ governor: this.governor }
|
|
1743
|
+
{ governor: this.governor, provider: this.name }
|
|
1134
1744
|
);
|
|
1135
1745
|
if (!res.ok) {
|
|
1136
1746
|
const errorBody = await res.text();
|
|
1137
1747
|
const message = this.extractErrorMessage(errorBody, res.status);
|
|
1138
|
-
throw
|
|
1748
|
+
throw buildProviderError({
|
|
1749
|
+
provider: this.name,
|
|
1750
|
+
status: res.status,
|
|
1751
|
+
headers: res.headers,
|
|
1752
|
+
message,
|
|
1753
|
+
body: errorBody
|
|
1754
|
+
});
|
|
1139
1755
|
}
|
|
1140
1756
|
if (!res.body) {
|
|
1141
1757
|
throw new Error("Anthropic stream response has no body");
|
|
@@ -1145,10 +1761,10 @@ var AnthropicProvider = class {
|
|
|
1145
1761
|
// ---------------------------------------------------------------------------
|
|
1146
1762
|
// Internal: request building
|
|
1147
1763
|
// ---------------------------------------------------------------------------
|
|
1148
|
-
buildHeaders() {
|
|
1764
|
+
buildHeaders(apiKey) {
|
|
1149
1765
|
return {
|
|
1150
1766
|
"Content-Type": "application/json",
|
|
1151
|
-
"x-api-key":
|
|
1767
|
+
"x-api-key": apiKey,
|
|
1152
1768
|
"anthropic-version": ANTHROPIC_API_VERSION
|
|
1153
1769
|
};
|
|
1154
1770
|
}
|
|
@@ -1333,7 +1949,7 @@ ${jsonInstruction}` : jsonInstruction;
|
|
|
1333
1949
|
// ---------------------------------------------------------------------------
|
|
1334
1950
|
// Internal: response parsing
|
|
1335
1951
|
// ---------------------------------------------------------------------------
|
|
1336
|
-
parseResponse(json) {
|
|
1952
|
+
parseResponse(json, model) {
|
|
1337
1953
|
let content = "";
|
|
1338
1954
|
let thinkingContent = "";
|
|
1339
1955
|
const toolCalls = [];
|
|
@@ -1362,13 +1978,7 @@ ${jsonInstruction}` : jsonInstruction;
|
|
|
1362
1978
|
total_tokens: totalInput + json.usage.output_tokens,
|
|
1363
1979
|
cached_tokens: cacheRead > 0 ? cacheRead : void 0
|
|
1364
1980
|
} : void 0;
|
|
1365
|
-
const cost = json.usage ? estimateAnthropicCost(
|
|
1366
|
-
this.currentModel ?? "",
|
|
1367
|
-
totalInput,
|
|
1368
|
-
json.usage.output_tokens,
|
|
1369
|
-
cacheRead,
|
|
1370
|
-
cacheWrite
|
|
1371
|
-
) : void 0;
|
|
1981
|
+
const cost = json.usage ? estimateAnthropicCost(model, totalInput, json.usage.output_tokens, cacheRead, cacheWrite) : void 0;
|
|
1372
1982
|
return {
|
|
1373
1983
|
content,
|
|
1374
1984
|
thinking_content: thinkingContent || void 0,
|
|
@@ -1570,7 +2180,7 @@ function estimateGeminiCost(model, inputTokens, outputTokens, cachedTokens) {
|
|
|
1570
2180
|
}
|
|
1571
2181
|
}
|
|
1572
2182
|
}
|
|
1573
|
-
if (!pricing) return 0;
|
|
2183
|
+
if (!pricing) return void 0;
|
|
1574
2184
|
const [inputRate, outputRate] = pricing;
|
|
1575
2185
|
const cached = cachedTokens ?? 0;
|
|
1576
2186
|
const inputCost = (inputTokens - cached) * inputRate + cached * inputRate * 0.1;
|
|
@@ -1615,40 +2225,61 @@ function warnGemini3xEffortNone(model) {
|
|
|
1615
2225
|
}
|
|
1616
2226
|
var GeminiProvider = class {
|
|
1617
2227
|
name = "google";
|
|
2228
|
+
/** Gemini accepts a `responseSchema` but `sanitizeSchemaForGemini` strips
|
|
2229
|
+
* keywords it doesn't support (`$ref`/`$defs`/`additionalProperties`/…), so a
|
|
2230
|
+
* derived schema can lose constraints — lossy, not faithful. */
|
|
2231
|
+
nativeStructuredOutputSupport() {
|
|
2232
|
+
return "lossy";
|
|
2233
|
+
}
|
|
1618
2234
|
baseUrl;
|
|
1619
|
-
|
|
2235
|
+
apiKeySource;
|
|
1620
2236
|
callCounter = 0;
|
|
1621
2237
|
governor;
|
|
1622
2238
|
constructor(options = {}) {
|
|
1623
|
-
this.
|
|
2239
|
+
this.apiKeySource = options.apiKey ?? process.env.GOOGLE_API_KEY ?? process.env.GEMINI_API_KEY ?? "";
|
|
1624
2240
|
this.baseUrl = (options.baseUrl ?? "https://generativelanguage.googleapis.com/v1beta").replace(
|
|
1625
2241
|
/\/$/,
|
|
1626
2242
|
""
|
|
1627
2243
|
);
|
|
1628
2244
|
this.governor = options.rateLimit ? new RateLimiter(options.rateLimit) : void 0;
|
|
1629
|
-
if (!this.
|
|
2245
|
+
if (typeof this.apiKeySource === "string" && !this.apiKeySource) {
|
|
1630
2246
|
throw new Error("Google API key is required. Set GOOGLE_API_KEY or pass apiKey in options.");
|
|
1631
2247
|
}
|
|
1632
2248
|
}
|
|
2249
|
+
/** Resolve the API key for one request (supports an expiring-token callback). */
|
|
2250
|
+
async resolveKey() {
|
|
2251
|
+
const key = await resolveApiKey(this.apiKeySource);
|
|
2252
|
+
if (!key) {
|
|
2253
|
+
throw new Error("Google API key is required. Set GOOGLE_API_KEY or pass apiKey in options.");
|
|
2254
|
+
}
|
|
2255
|
+
return key;
|
|
2256
|
+
}
|
|
1633
2257
|
// ---------------------------------------------------------------------------
|
|
1634
2258
|
// chat - non-streaming completion
|
|
1635
2259
|
// ---------------------------------------------------------------------------
|
|
1636
2260
|
async chat(messages, options) {
|
|
2261
|
+
const headers = this.buildHeaders(await this.resolveKey());
|
|
1637
2262
|
const body = this.buildRequestBody(messages, options);
|
|
1638
2263
|
const res = await fetchWithRetry(
|
|
1639
2264
|
`${this.baseUrl}/models/${options.model}:generateContent`,
|
|
1640
2265
|
{
|
|
1641
2266
|
method: "POST",
|
|
1642
|
-
headers
|
|
2267
|
+
headers,
|
|
1643
2268
|
body: JSON.stringify(body),
|
|
1644
2269
|
signal: options.signal
|
|
1645
2270
|
},
|
|
1646
|
-
{ governor: this.governor }
|
|
2271
|
+
{ governor: this.governor, provider: this.name }
|
|
1647
2272
|
);
|
|
1648
2273
|
if (!res.ok) {
|
|
1649
2274
|
const errorBody = await res.text();
|
|
1650
2275
|
const message = this.extractErrorMessage(errorBody, res.status);
|
|
1651
|
-
throw
|
|
2276
|
+
throw buildProviderError({
|
|
2277
|
+
provider: this.name,
|
|
2278
|
+
status: res.status,
|
|
2279
|
+
headers: res.headers,
|
|
2280
|
+
message,
|
|
2281
|
+
body: errorBody
|
|
2282
|
+
});
|
|
1652
2283
|
}
|
|
1653
2284
|
const json = await res.json();
|
|
1654
2285
|
return this.parseResponse(json, options.model);
|
|
@@ -1657,21 +2288,28 @@ var GeminiProvider = class {
|
|
|
1657
2288
|
// stream - SSE streaming completion
|
|
1658
2289
|
// ---------------------------------------------------------------------------
|
|
1659
2290
|
async *stream(messages, options) {
|
|
2291
|
+
const headers = this.buildHeaders(await this.resolveKey());
|
|
1660
2292
|
const body = this.buildRequestBody(messages, options);
|
|
1661
2293
|
const res = await fetchWithRetry(
|
|
1662
2294
|
`${this.baseUrl}/models/${options.model}:streamGenerateContent?alt=sse`,
|
|
1663
2295
|
{
|
|
1664
2296
|
method: "POST",
|
|
1665
|
-
headers
|
|
2297
|
+
headers,
|
|
1666
2298
|
body: JSON.stringify(body),
|
|
1667
2299
|
signal: options.signal
|
|
1668
2300
|
},
|
|
1669
|
-
{ governor: this.governor }
|
|
2301
|
+
{ governor: this.governor, provider: this.name }
|
|
1670
2302
|
);
|
|
1671
2303
|
if (!res.ok) {
|
|
1672
2304
|
const errorBody = await res.text();
|
|
1673
2305
|
const message = this.extractErrorMessage(errorBody, res.status);
|
|
1674
|
-
throw
|
|
2306
|
+
throw buildProviderError({
|
|
2307
|
+
provider: this.name,
|
|
2308
|
+
status: res.status,
|
|
2309
|
+
headers: res.headers,
|
|
2310
|
+
message,
|
|
2311
|
+
body: errorBody
|
|
2312
|
+
});
|
|
1675
2313
|
}
|
|
1676
2314
|
if (!res.body) {
|
|
1677
2315
|
throw new Error("Gemini stream response has no body");
|
|
@@ -1681,10 +2319,10 @@ var GeminiProvider = class {
|
|
|
1681
2319
|
// ---------------------------------------------------------------------------
|
|
1682
2320
|
// Internal: request building
|
|
1683
2321
|
// ---------------------------------------------------------------------------
|
|
1684
|
-
buildHeaders() {
|
|
2322
|
+
buildHeaders(apiKey) {
|
|
1685
2323
|
return {
|
|
1686
2324
|
"Content-Type": "application/json",
|
|
1687
|
-
"x-goog-api-key":
|
|
2325
|
+
"x-goog-api-key": apiKey
|
|
1688
2326
|
};
|
|
1689
2327
|
}
|
|
1690
2328
|
extractErrorMessage(body, status) {
|
|
@@ -2041,6 +2679,203 @@ var GeminiProvider = class {
|
|
|
2041
2679
|
}
|
|
2042
2680
|
};
|
|
2043
2681
|
|
|
2682
|
+
// src/providers/profiles/openrouter.ts
|
|
2683
|
+
var OPENROUTER_PROFILE = {
|
|
2684
|
+
name: "openrouter",
|
|
2685
|
+
label: "OpenRouter",
|
|
2686
|
+
defaultBaseUrl: "https://openrouter.ai/api/v1",
|
|
2687
|
+
envApiKey: "OPENROUTER_API_KEY",
|
|
2688
|
+
envBaseUrl: "OPENROUTER_BASE_URL",
|
|
2689
|
+
pricing: { kind: "from-response" },
|
|
2690
|
+
// Ask OpenRouter to report per-call cost in usage.cost.
|
|
2691
|
+
requestDefaults: { usage: { include: true } },
|
|
2692
|
+
reasoning: {
|
|
2693
|
+
// OpenRouter's effort vocabulary is low|medium|high; xhigh/max clamp to high.
|
|
2694
|
+
emit: reasoningObjectEmit(
|
|
2695
|
+
(effort) => effort === "xhigh" || effort === "max" ? "high" : effort
|
|
2696
|
+
),
|
|
2697
|
+
capture: "reasoning_details",
|
|
2698
|
+
roundTrip: "on-tool-call-turns"
|
|
2699
|
+
},
|
|
2700
|
+
// OpenRouter normalizes to max_tokens across its backends.
|
|
2701
|
+
maxTokensField: "max_tokens"
|
|
2702
|
+
};
|
|
2703
|
+
|
|
2704
|
+
// src/providers/profiles/azure.ts
|
|
2705
|
+
var AZURE_PROFILE = {
|
|
2706
|
+
name: "azure",
|
|
2707
|
+
label: "Azure OpenAI",
|
|
2708
|
+
// Never actually used — requireExplicitBaseUrl forces a real value first.
|
|
2709
|
+
defaultBaseUrl: "https://YOUR-RESOURCE.openai.azure.com/openai/v1",
|
|
2710
|
+
requireExplicitBaseUrl: true,
|
|
2711
|
+
envApiKey: "AZURE_OPENAI_API_KEY",
|
|
2712
|
+
envBaseUrl: "AZURE_OPENAI_BASE_URL",
|
|
2713
|
+
authHeader: "api-key",
|
|
2714
|
+
pricing: { kind: "table", table: OPENAI_PRICING },
|
|
2715
|
+
reasoning: { emit: openaiReasoningEmit, capture: "none" },
|
|
2716
|
+
roleFor: (role, model) => role === "system" && isOSeriesModel(model) ? "developer" : role,
|
|
2717
|
+
maxTokensField: "max_completion_tokens",
|
|
2718
|
+
parallelToolCalls: (model) => !isOSeriesModel(model)
|
|
2719
|
+
};
|
|
2720
|
+
|
|
2721
|
+
// src/providers/profiles/xai.ts
|
|
2722
|
+
var acceptsReasoningEffort = (model) => model.startsWith("grok-3-mini");
|
|
2723
|
+
var isReasoningModel = (model) => /^grok-(3-mini|4)/.test(model);
|
|
2724
|
+
var XAI_PROFILE = {
|
|
2725
|
+
name: "xai",
|
|
2726
|
+
label: "xAI",
|
|
2727
|
+
defaultBaseUrl: "https://api.x.ai/v1",
|
|
2728
|
+
envApiKey: "XAI_API_KEY",
|
|
2729
|
+
envBaseUrl: "XAI_BASE_URL",
|
|
2730
|
+
pricing: { kind: "unknown" },
|
|
2731
|
+
reasoning: {
|
|
2732
|
+
emit: reasoningEffortEmit((resolved, model) => {
|
|
2733
|
+
if (!acceptsReasoningEffort(model)) return void 0;
|
|
2734
|
+
if (resolved.thinkingDisabled || !resolved.activeEffort) return void 0;
|
|
2735
|
+
return resolved.activeEffort === "low" ? "low" : "high";
|
|
2736
|
+
}),
|
|
2737
|
+
capture: "reasoning"
|
|
2738
|
+
},
|
|
2739
|
+
capabilities: {
|
|
2740
|
+
forbiddenParams: (model) => isReasoningModel(model) ? ["stop", "presence_penalty", "frequency_penalty"] : []
|
|
2741
|
+
}
|
|
2742
|
+
};
|
|
2743
|
+
|
|
2744
|
+
// src/providers/profiles/deepseek.ts
|
|
2745
|
+
var isReasoner = (model) => /reasoner|v4/i.test(model);
|
|
2746
|
+
var deepseekEmit = (_body, _resolved, model) => ({
|
|
2747
|
+
stripTemperature: isReasoner(model)
|
|
2748
|
+
});
|
|
2749
|
+
var DEEPSEEK_PROFILE = {
|
|
2750
|
+
name: "deepseek",
|
|
2751
|
+
label: "DeepSeek",
|
|
2752
|
+
defaultBaseUrl: "https://api.deepseek.com/v1",
|
|
2753
|
+
envApiKey: "DEEPSEEK_API_KEY",
|
|
2754
|
+
envBaseUrl: "DEEPSEEK_BASE_URL",
|
|
2755
|
+
pricing: { kind: "unknown" },
|
|
2756
|
+
reasoning: {
|
|
2757
|
+
emit: deepseekEmit,
|
|
2758
|
+
capture: "reasoning_content",
|
|
2759
|
+
roundTrip: "on-tool-call-turns"
|
|
2760
|
+
},
|
|
2761
|
+
capabilities: {
|
|
2762
|
+
supportsJsonSchema: false,
|
|
2763
|
+
forbiddenParams: (model) => isReasoner(model) ? ["top_p", "presence_penalty", "frequency_penalty"] : []
|
|
2764
|
+
}
|
|
2765
|
+
};
|
|
2766
|
+
|
|
2767
|
+
// src/providers/profiles/mistral.ts
|
|
2768
|
+
var acceptsReasoningEffort2 = (model) => /^mistral-(small|medium)/i.test(model);
|
|
2769
|
+
var MISTRAL_PROFILE = {
|
|
2770
|
+
name: "mistral",
|
|
2771
|
+
label: "Mistral",
|
|
2772
|
+
defaultBaseUrl: "https://api.mistral.ai/v1",
|
|
2773
|
+
envApiKey: "MISTRAL_API_KEY",
|
|
2774
|
+
envBaseUrl: "MISTRAL_BASE_URL",
|
|
2775
|
+
pricing: { kind: "unknown" },
|
|
2776
|
+
reasoning: {
|
|
2777
|
+
emit: reasoningEffortEmit((resolved, model) => {
|
|
2778
|
+
if (!acceptsReasoningEffort2(model)) return void 0;
|
|
2779
|
+
if (resolved.thinkingDisabled || !resolved.activeEffort) return void 0;
|
|
2780
|
+
return "high";
|
|
2781
|
+
}),
|
|
2782
|
+
capture: "reasoning_content"
|
|
2783
|
+
}
|
|
2784
|
+
};
|
|
2785
|
+
|
|
2786
|
+
// src/providers/profiles/groq.ts
|
|
2787
|
+
var isGptOss = (model) => /gpt-oss/i.test(model);
|
|
2788
|
+
var GROQ_PROFILE = {
|
|
2789
|
+
name: "groq",
|
|
2790
|
+
label: "Groq",
|
|
2791
|
+
defaultBaseUrl: "https://api.groq.com/openai/v1",
|
|
2792
|
+
envApiKey: "GROQ_API_KEY",
|
|
2793
|
+
envBaseUrl: "GROQ_BASE_URL",
|
|
2794
|
+
pricing: { kind: "unknown" },
|
|
2795
|
+
reasoning: {
|
|
2796
|
+
emit: reasoningEffortEmit((resolved, model) => {
|
|
2797
|
+
if (!isGptOss(model) || resolved.thinkingDisabled || !resolved.activeEffort) {
|
|
2798
|
+
return void 0;
|
|
2799
|
+
}
|
|
2800
|
+
const e = resolved.activeEffort;
|
|
2801
|
+
return e === "xhigh" || e === "max" ? "high" : e;
|
|
2802
|
+
}),
|
|
2803
|
+
capture: "reasoning"
|
|
2804
|
+
},
|
|
2805
|
+
capabilities: {
|
|
2806
|
+
emitsMessageName: false,
|
|
2807
|
+
// Groq's `response_format: json_schema` is supported only on a subset of
|
|
2808
|
+
// models — the `openai/gpt-oss-*` family accepts it; llama/gemma/etc 400
|
|
2809
|
+
// with "This model does not support response format `json_schema`".
|
|
2810
|
+
// Per-model so `nativeStructuredOutput` engages the native path only where
|
|
2811
|
+
// it works and otherwise downgrades cleanly to `json_object` (verified live).
|
|
2812
|
+
supportsJsonSchema: isGptOss
|
|
2813
|
+
}
|
|
2814
|
+
};
|
|
2815
|
+
|
|
2816
|
+
// src/providers/profiles/bedrock.ts
|
|
2817
|
+
var isGptOss2 = (model) => /gpt-oss/i.test(model);
|
|
2818
|
+
var BEDROCK_PROFILE = {
|
|
2819
|
+
name: "bedrock",
|
|
2820
|
+
label: "AWS Bedrock",
|
|
2821
|
+
// Never used — requireExplicitBaseUrl forces a real (region-specific) value first.
|
|
2822
|
+
defaultBaseUrl: "https://bedrock-mantle.us-east-1.api.aws/v1",
|
|
2823
|
+
requireExplicitBaseUrl: true,
|
|
2824
|
+
envApiKey: "AWS_BEARER_TOKEN_BEDROCK",
|
|
2825
|
+
envBaseUrl: "BEDROCK_BASE_URL",
|
|
2826
|
+
pricing: { kind: "unknown" },
|
|
2827
|
+
reasoning: {
|
|
2828
|
+
emit: reasoningEffortEmit((resolved, model) => {
|
|
2829
|
+
if (!isGptOss2(model) || resolved.thinkingDisabled || !resolved.activeEffort) {
|
|
2830
|
+
return void 0;
|
|
2831
|
+
}
|
|
2832
|
+
const e = resolved.activeEffort;
|
|
2833
|
+
return e === "xhigh" || e === "max" ? "high" : e;
|
|
2834
|
+
}),
|
|
2835
|
+
capture: "reasoning"
|
|
2836
|
+
}
|
|
2837
|
+
};
|
|
2838
|
+
|
|
2839
|
+
// src/providers/profiles/local.ts
|
|
2840
|
+
function localProfile(name, label, port, envApiKey) {
|
|
2841
|
+
return {
|
|
2842
|
+
name,
|
|
2843
|
+
label,
|
|
2844
|
+
defaultBaseUrl: `http://localhost:${port}/v1`,
|
|
2845
|
+
envApiKey,
|
|
2846
|
+
envBaseUrl: `${envApiKey.replace(/_API_KEY$/, "")}_BASE_URL`,
|
|
2847
|
+
allowMissingApiKey: true,
|
|
2848
|
+
pricing: { kind: "zero" },
|
|
2849
|
+
reasoning: { emit: () => {
|
|
2850
|
+
}, capture: "think_tags" },
|
|
2851
|
+
maxTokensField: "max_tokens"
|
|
2852
|
+
};
|
|
2853
|
+
}
|
|
2854
|
+
var OLLAMA_PROFILE = localProfile("ollama", "Ollama", 11434, "OLLAMA_API_KEY");
|
|
2855
|
+
var VLLM_PROFILE = localProfile("vllm", "vLLM", 8e3, "VLLM_API_KEY");
|
|
2856
|
+
var LMSTUDIO_PROFILE = localProfile("lmstudio", "LM Studio", 1234, "LMSTUDIO_API_KEY");
|
|
2857
|
+
var LLAMACPP_PROFILE = localProfile("llamacpp", "llama.cpp", 8080, "LLAMACPP_API_KEY");
|
|
2858
|
+
var SGLANG_PROFILE = localProfile("sglang", "SGLang", 3e4, "SGLANG_API_KEY");
|
|
2859
|
+
var LOCAL_PROFILES = [
|
|
2860
|
+
OLLAMA_PROFILE,
|
|
2861
|
+
VLLM_PROFILE,
|
|
2862
|
+
LMSTUDIO_PROFILE,
|
|
2863
|
+
LLAMACPP_PROFILE,
|
|
2864
|
+
SGLANG_PROFILE
|
|
2865
|
+
];
|
|
2866
|
+
|
|
2867
|
+
// src/providers/profiles/index.ts
|
|
2868
|
+
var BUILTIN_PROFILES = [
|
|
2869
|
+
OPENROUTER_PROFILE,
|
|
2870
|
+
AZURE_PROFILE,
|
|
2871
|
+
XAI_PROFILE,
|
|
2872
|
+
DEEPSEEK_PROFILE,
|
|
2873
|
+
MISTRAL_PROFILE,
|
|
2874
|
+
GROQ_PROFILE,
|
|
2875
|
+
BEDROCK_PROFILE,
|
|
2876
|
+
...LOCAL_PROFILES
|
|
2877
|
+
];
|
|
2878
|
+
|
|
2044
2879
|
// src/providers/registry.ts
|
|
2045
2880
|
var builtinFactories = {
|
|
2046
2881
|
openai: (config) => {
|
|
@@ -2080,6 +2915,23 @@ var builtinFactories = {
|
|
|
2080
2915
|
});
|
|
2081
2916
|
}
|
|
2082
2917
|
};
|
|
2918
|
+
function presetFactory(profile) {
|
|
2919
|
+
return (config) => {
|
|
2920
|
+
const opts = config.providers?.[profile.name] ?? {};
|
|
2921
|
+
return new OpenAICompatibleProvider({
|
|
2922
|
+
profile,
|
|
2923
|
+
apiKey: opts.apiKey,
|
|
2924
|
+
baseUrl: opts.baseUrl,
|
|
2925
|
+
authHeader: opts.authHeader,
|
|
2926
|
+
rateLimit: opts.rateLimit
|
|
2927
|
+
});
|
|
2928
|
+
};
|
|
2929
|
+
}
|
|
2930
|
+
for (const profile of BUILTIN_PROFILES) {
|
|
2931
|
+
if (!(profile.name in builtinFactories)) {
|
|
2932
|
+
builtinFactories[profile.name] = presetFactory(profile);
|
|
2933
|
+
}
|
|
2934
|
+
}
|
|
2083
2935
|
var ProviderRegistry = class {
|
|
2084
2936
|
/** Cached provider instances, keyed by provider name */
|
|
2085
2937
|
instances = /* @__PURE__ */ new Map();
|
|
@@ -2197,123 +3049,6 @@ import { AsyncLocalStorage } from "async_hooks";
|
|
|
2197
3049
|
import { randomUUID } from "crypto";
|
|
2198
3050
|
import { z, ZodError } from "zod";
|
|
2199
3051
|
|
|
2200
|
-
// src/errors.ts
|
|
2201
|
-
var AxlError = class extends Error {
|
|
2202
|
-
code;
|
|
2203
|
-
constructor(code, message) {
|
|
2204
|
-
super(message);
|
|
2205
|
-
this.name = "AxlError";
|
|
2206
|
-
this.code = code;
|
|
2207
|
-
}
|
|
2208
|
-
};
|
|
2209
|
-
var VerifyError = class extends AxlError {
|
|
2210
|
-
lastOutput;
|
|
2211
|
-
zodError;
|
|
2212
|
-
retries;
|
|
2213
|
-
constructor(lastOutput, zodError, retries) {
|
|
2214
|
-
super("VERIFY_ERROR", `Schema validation failed after ${retries} retries: ${zodError.message}`);
|
|
2215
|
-
this.name = "VerifyError";
|
|
2216
|
-
this.lastOutput = lastOutput;
|
|
2217
|
-
this.zodError = zodError;
|
|
2218
|
-
this.retries = retries;
|
|
2219
|
-
}
|
|
2220
|
-
};
|
|
2221
|
-
var QuorumNotMet = class extends AxlError {
|
|
2222
|
-
results;
|
|
2223
|
-
constructor(required, actual, results) {
|
|
2224
|
-
super("QUORUM_NOT_MET", `Quorum not met: needed ${required} successes, got ${actual}`);
|
|
2225
|
-
this.name = "QuorumNotMet";
|
|
2226
|
-
this.results = results;
|
|
2227
|
-
}
|
|
2228
|
-
};
|
|
2229
|
-
var NoConsensus = class extends AxlError {
|
|
2230
|
-
constructor(reason) {
|
|
2231
|
-
super("NO_CONSENSUS", `No consensus: ${reason}`);
|
|
2232
|
-
this.name = "NoConsensus";
|
|
2233
|
-
}
|
|
2234
|
-
};
|
|
2235
|
-
var TimeoutError = class extends AxlError {
|
|
2236
|
-
constructor(operation, timeoutMs) {
|
|
2237
|
-
super("TIMEOUT", `${operation} exceeded timeout of ${timeoutMs}ms`);
|
|
2238
|
-
this.name = "TimeoutError";
|
|
2239
|
-
}
|
|
2240
|
-
};
|
|
2241
|
-
function formatBudgetCost(cost) {
|
|
2242
|
-
if (Number.isNaN(cost)) return "$NaN";
|
|
2243
|
-
if (cost === Infinity) return "$Infinity";
|
|
2244
|
-
if (cost === -Infinity) return "-$Infinity";
|
|
2245
|
-
if (cost === 0) return "$0.00";
|
|
2246
|
-
const sign = cost < 0 ? "-" : "";
|
|
2247
|
-
const abs = Math.abs(cost);
|
|
2248
|
-
if (abs < 1e-6) return `${sign}< $0.000001`;
|
|
2249
|
-
if (abs < 1e-4) {
|
|
2250
|
-
const [mantissa, exponent] = abs.toExponential(2).split("e");
|
|
2251
|
-
return `${sign}$${mantissa}e${parseInt(exponent, 10)}`;
|
|
2252
|
-
}
|
|
2253
|
-
if (abs < 0.01) return `${sign}$${abs.toFixed(6)}`;
|
|
2254
|
-
return `${sign}$${abs.toFixed(2)}`;
|
|
2255
|
-
}
|
|
2256
|
-
var BudgetExceededError = class extends AxlError {
|
|
2257
|
-
limit;
|
|
2258
|
-
spent;
|
|
2259
|
-
policy;
|
|
2260
|
-
constructor(limit, spent, policy) {
|
|
2261
|
-
super(
|
|
2262
|
-
"BUDGET_EXCEEDED",
|
|
2263
|
-
`Budget exceeded: spent ${formatBudgetCost(spent)} of ${formatBudgetCost(
|
|
2264
|
-
limit
|
|
2265
|
-
)} limit (policy: ${policy})`
|
|
2266
|
-
);
|
|
2267
|
-
this.name = "BudgetExceededError";
|
|
2268
|
-
this.limit = limit;
|
|
2269
|
-
this.spent = spent;
|
|
2270
|
-
this.policy = policy;
|
|
2271
|
-
}
|
|
2272
|
-
};
|
|
2273
|
-
var MaxTurnsError = class extends AxlError {
|
|
2274
|
-
maxTurns;
|
|
2275
|
-
constructor(operation, maxTurns) {
|
|
2276
|
-
super("MAX_TURNS", `${operation} exceeded maximum of ${maxTurns} turns`);
|
|
2277
|
-
this.name = "MaxTurnsError";
|
|
2278
|
-
this.maxTurns = maxTurns;
|
|
2279
|
-
}
|
|
2280
|
-
};
|
|
2281
|
-
var GuardrailError = class extends AxlError {
|
|
2282
|
-
guardrailType;
|
|
2283
|
-
reason;
|
|
2284
|
-
constructor(guardrailType, reason) {
|
|
2285
|
-
super("GUARDRAIL_BLOCKED", `${guardrailType} guardrail blocked: ${reason}`);
|
|
2286
|
-
this.name = "GuardrailError";
|
|
2287
|
-
this.guardrailType = guardrailType;
|
|
2288
|
-
this.reason = reason;
|
|
2289
|
-
}
|
|
2290
|
-
};
|
|
2291
|
-
var ValidationError = class extends AxlError {
|
|
2292
|
-
lastOutput;
|
|
2293
|
-
reason;
|
|
2294
|
-
retries;
|
|
2295
|
-
constructor(lastOutput, reason, retries) {
|
|
2296
|
-
super("VALIDATION_ERROR", `Validation failed after ${retries} retries: ${reason}`);
|
|
2297
|
-
this.name = "ValidationError";
|
|
2298
|
-
this.lastOutput = lastOutput;
|
|
2299
|
-
this.reason = reason;
|
|
2300
|
-
this.retries = retries;
|
|
2301
|
-
}
|
|
2302
|
-
};
|
|
2303
|
-
var ToolDenied = class extends AxlError {
|
|
2304
|
-
toolName;
|
|
2305
|
-
agentName;
|
|
2306
|
-
constructor(toolName, agentName) {
|
|
2307
|
-
super(
|
|
2308
|
-
"TOOL_DENIED",
|
|
2309
|
-
`Agent "${agentName}" attempted to call tool "${toolName}" which is not in its ACL`
|
|
2310
|
-
);
|
|
2311
|
-
this.name = "ToolDenied";
|
|
2312
|
-
this.toolName = toolName;
|
|
2313
|
-
this.agentName = agentName;
|
|
2314
|
-
}
|
|
2315
|
-
};
|
|
2316
|
-
|
|
2317
3052
|
// src/partial-json.ts
|
|
2318
3053
|
var MAX_DEPTH = 256;
|
|
2319
3054
|
function parsePartialJson(input) {
|
|
@@ -2888,6 +3623,14 @@ var COST_BEARING_LEAF_TYPES = [
|
|
|
2888
3623
|
"memory_recall"
|
|
2889
3624
|
];
|
|
2890
3625
|
var COST_LEAF_SET = new Set(COST_BEARING_LEAF_TYPES);
|
|
3626
|
+
function hasPositiveTokens(event) {
|
|
3627
|
+
const t = event.tokens;
|
|
3628
|
+
if (!t) return false;
|
|
3629
|
+
return (t.input ?? 0) + (t.output ?? 0) + (t.reasoning ?? 0) > 0;
|
|
3630
|
+
}
|
|
3631
|
+
function isUsableCost(cost) {
|
|
3632
|
+
return typeof cost === "number" && Number.isFinite(cost) && cost >= 0;
|
|
3633
|
+
}
|
|
2891
3634
|
function eventCostContribution(event) {
|
|
2892
3635
|
if (event.type === "ask_end") return 0;
|
|
2893
3636
|
const c = event.cost;
|
|
@@ -2896,6 +3639,9 @@ function eventCostContribution(event) {
|
|
|
2896
3639
|
function isCostBearingLeaf(event) {
|
|
2897
3640
|
return COST_LEAF_SET.has(event.type);
|
|
2898
3641
|
}
|
|
3642
|
+
function isUnpricedLeaf(event) {
|
|
3643
|
+
return event.type !== void 0 && COST_LEAF_SET.has(event.type) && !isUsableCost(event.cost) && hasPositiveTokens(event);
|
|
3644
|
+
}
|
|
2899
3645
|
function isRootLevel(event) {
|
|
2900
3646
|
const d = event.depth;
|
|
2901
3647
|
return (d ?? 0) === 0;
|
|
@@ -2944,6 +3690,10 @@ var AXL_EVENT_TYPES = [
|
|
|
2944
3690
|
"string_delta",
|
|
2945
3691
|
// Verification
|
|
2946
3692
|
"verify",
|
|
3693
|
+
// Schema capability diagnostics (spec 22, Problem E) — surface the silent
|
|
3694
|
+
// structured-output cliffs (oversized appended schema, dropped refinements,
|
|
3695
|
+
// streaming disabled, zero-guidance `schemaPrompt:'none'`).
|
|
3696
|
+
"schema_diagnostic",
|
|
2947
3697
|
// Observability
|
|
2948
3698
|
"log",
|
|
2949
3699
|
"memory_remember",
|
|
@@ -2986,6 +3736,7 @@ var LIFECYCLE_TYPES = /* @__PURE__ */ new Set([
|
|
|
2986
3736
|
"delegate",
|
|
2987
3737
|
"pipeline",
|
|
2988
3738
|
"verify",
|
|
3739
|
+
"schema_diagnostic",
|
|
2989
3740
|
"workflow_start",
|
|
2990
3741
|
"workflow_end",
|
|
2991
3742
|
"checkpoint_save",
|
|
@@ -3790,6 +4541,11 @@ var REDACTION_RULES = {
|
|
|
3790
4541
|
// Scrub only the delta — preserving path keeps "which fields stream" telemetry.
|
|
3791
4542
|
string_delta: (e) => ({ ...e, data: { ...e.data, delta: REDACTED } }),
|
|
3792
4543
|
verify: (e) => e.data.lastError !== void 0 ? { ...e, data: { ...e.data, lastError: REDACTED } } : e,
|
|
4544
|
+
// `schema_diagnostic` carries only structural schema metadata — token counts,
|
|
4545
|
+
// thresholds, JSON-Schema field paths, a root type name, a tool name — none of
|
|
4546
|
+
// which is user content or PII (field paths are schema-shape, preserved like
|
|
4547
|
+
// `string_delta.path`). Passthrough, as an explicit reviewed decision.
|
|
4548
|
+
schema_diagnostic: passthrough,
|
|
3793
4549
|
log: (e) => {
|
|
3794
4550
|
if (!e.data || typeof e.data !== "object" || Array.isArray(e.data)) return e;
|
|
3795
4551
|
return { ...e, data: walkObjectOneLevel(e.data, LOG_PRESERVE_KEYS) };
|
|
@@ -3822,6 +4578,80 @@ function redactEvent(event) {
|
|
|
3822
4578
|
return rule(event);
|
|
3823
4579
|
}
|
|
3824
4580
|
|
|
4581
|
+
// src/schema-diagnostics.ts
|
|
4582
|
+
var DEFAULT_SCHEMA_OVERSIZED_TOKENS = 4e3;
|
|
4583
|
+
var refinementCache = /* @__PURE__ */ new WeakMap();
|
|
4584
|
+
function detectDroppedRefinements(schema) {
|
|
4585
|
+
const cached = refinementCache.get(schema);
|
|
4586
|
+
if (cached) return cached;
|
|
4587
|
+
const paths = /* @__PURE__ */ new Set();
|
|
4588
|
+
let count = 0;
|
|
4589
|
+
const visited = /* @__PURE__ */ new WeakSet();
|
|
4590
|
+
const walk = (node, path) => {
|
|
4591
|
+
if (node === null || typeof node !== "object") return;
|
|
4592
|
+
if (visited.has(node)) return;
|
|
4593
|
+
visited.add(node);
|
|
4594
|
+
const def = node._zod?.def;
|
|
4595
|
+
if (!def) return;
|
|
4596
|
+
const here = () => paths.add(path === "" ? "<root>" : path);
|
|
4597
|
+
const checks = def.checks;
|
|
4598
|
+
if (Array.isArray(checks)) {
|
|
4599
|
+
for (const check of checks) {
|
|
4600
|
+
const kind = check?._zod?.def?.check;
|
|
4601
|
+
if (kind === "custom") {
|
|
4602
|
+
count += 1;
|
|
4603
|
+
here();
|
|
4604
|
+
}
|
|
4605
|
+
}
|
|
4606
|
+
}
|
|
4607
|
+
if (def.check === "custom") {
|
|
4608
|
+
count += 1;
|
|
4609
|
+
here();
|
|
4610
|
+
}
|
|
4611
|
+
const join2 = (child) => path === "" ? child : `${path}.${child}`;
|
|
4612
|
+
const shape = def.shape;
|
|
4613
|
+
if (shape && typeof shape === "object") {
|
|
4614
|
+
for (const [key, child] of Object.entries(shape)) walk(child, join2(key));
|
|
4615
|
+
}
|
|
4616
|
+
if ("element" in def) walk(def.element, `${path}[]`);
|
|
4617
|
+
if (Array.isArray(def.options)) {
|
|
4618
|
+
for (const opt of def.options) walk(opt, path);
|
|
4619
|
+
}
|
|
4620
|
+
if ("innerType" in def) walk(def.innerType, path);
|
|
4621
|
+
if (Array.isArray(def.items)) {
|
|
4622
|
+
def.items.forEach((item, i) => walk(item, `${path}[${i}]`));
|
|
4623
|
+
}
|
|
4624
|
+
if ("rest" in def && def.rest) walk(def.rest, `${path}[]`);
|
|
4625
|
+
if ("catchall" in def && def.catchall) walk(def.catchall, `${path}[*]`);
|
|
4626
|
+
if ("keyType" in def) walk(def.keyType, `${path}[key]`);
|
|
4627
|
+
if ("valueType" in def) walk(def.valueType, `${path}[*]`);
|
|
4628
|
+
if ("in" in def) walk(def.in, path);
|
|
4629
|
+
if ("out" in def) walk(def.out, path);
|
|
4630
|
+
if ("left" in def) walk(def.left, path);
|
|
4631
|
+
if ("right" in def) walk(def.right, path);
|
|
4632
|
+
if (typeof def.getter === "function") {
|
|
4633
|
+
try {
|
|
4634
|
+
walk(def.getter(), path);
|
|
4635
|
+
} catch {
|
|
4636
|
+
}
|
|
4637
|
+
}
|
|
4638
|
+
};
|
|
4639
|
+
walk(schema, "");
|
|
4640
|
+
const result = { count, paths: [...paths] };
|
|
4641
|
+
refinementCache.set(schema, result);
|
|
4642
|
+
return result;
|
|
4643
|
+
}
|
|
4644
|
+
var DOCS_URL = "docs/observability.md#schema-diagnostics";
|
|
4645
|
+
var MAX_WARNED_KEYS = 2048;
|
|
4646
|
+
var warnedDiagnosticKeys = /* @__PURE__ */ new Set();
|
|
4647
|
+
function warnSchemaDiagnosticOnce(key, message, silent) {
|
|
4648
|
+
if (silent || process.env.AXL_DIAGNOSTICS_SILENT === "true") return;
|
|
4649
|
+
if (warnedDiagnosticKeys.has(key)) return;
|
|
4650
|
+
if (warnedDiagnosticKeys.size >= MAX_WARNED_KEYS) warnedDiagnosticKeys.clear();
|
|
4651
|
+
warnedDiagnosticKeys.add(key);
|
|
4652
|
+
console.warn(`[axl] ${message} See ${DOCS_URL}.`);
|
|
4653
|
+
}
|
|
4654
|
+
|
|
3825
4655
|
// src/config.ts
|
|
3826
4656
|
function defineConfig(config) {
|
|
3827
4657
|
return config;
|
|
@@ -3900,11 +4730,47 @@ function resolveConfig(config) {
|
|
|
3900
4730
|
// src/context.ts
|
|
3901
4731
|
var signalStorage = new AsyncLocalStorage();
|
|
3902
4732
|
var askStorage = new AsyncLocalStorage();
|
|
4733
|
+
var inlineSchemaCache = /* @__PURE__ */ new WeakMap();
|
|
3903
4734
|
function zodToJsonSchema(schema) {
|
|
4735
|
+
const cached = inlineSchemaCache.get(schema);
|
|
4736
|
+
if (cached !== void 0) return cached;
|
|
3904
4737
|
const result = z.toJSONSchema(schema, { unrepresentable: "any" });
|
|
3905
4738
|
delete result.$schema;
|
|
4739
|
+
Object.freeze(result);
|
|
4740
|
+
inlineSchemaCache.set(schema, result);
|
|
3906
4741
|
return result;
|
|
3907
4742
|
}
|
|
4743
|
+
var promptSchemaCache = /* @__PURE__ */ new WeakMap();
|
|
4744
|
+
var inlineSchemaTokenCache = /* @__PURE__ */ new WeakMap();
|
|
4745
|
+
function estimateInlineSchemaTokens(schema) {
|
|
4746
|
+
const cached = inlineSchemaTokenCache.get(schema);
|
|
4747
|
+
if (cached !== void 0) return cached;
|
|
4748
|
+
const tokens = estimateTokens(JSON.stringify(zodToJsonSchema(schema)));
|
|
4749
|
+
inlineSchemaTokenCache.set(schema, tokens);
|
|
4750
|
+
return tokens;
|
|
4751
|
+
}
|
|
4752
|
+
var nativeSchemaCache = /* @__PURE__ */ new WeakMap();
|
|
4753
|
+
function deriveNativeSchema(schema) {
|
|
4754
|
+
const cached = nativeSchemaCache.get(schema);
|
|
4755
|
+
if (cached !== void 0) return cached;
|
|
4756
|
+
const json = z.toJSONSchema(schema, { unrepresentable: "any", io: "input" });
|
|
4757
|
+
delete json.$schema;
|
|
4758
|
+
nativeSchemaCache.set(schema, json);
|
|
4759
|
+
return json;
|
|
4760
|
+
}
|
|
4761
|
+
function renderSchemaForPrompt(schema) {
|
|
4762
|
+
const cached = promptSchemaCache.get(schema);
|
|
4763
|
+
if (cached !== void 0) return cached;
|
|
4764
|
+
const json = z.toJSONSchema(schema, {
|
|
4765
|
+
unrepresentable: "any",
|
|
4766
|
+
io: "input",
|
|
4767
|
+
reused: "ref"
|
|
4768
|
+
});
|
|
4769
|
+
delete json.$schema;
|
|
4770
|
+
const rendered = JSON.stringify(json);
|
|
4771
|
+
promptSchemaCache.set(schema, rendered);
|
|
4772
|
+
return rendered;
|
|
4773
|
+
}
|
|
3908
4774
|
function estimateTokens(text) {
|
|
3909
4775
|
return Math.ceil(text.length / 4);
|
|
3910
4776
|
}
|
|
@@ -4285,12 +5151,14 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
4285
5151
|
depth,
|
|
4286
5152
|
agent: agent2._name,
|
|
4287
5153
|
stepRef,
|
|
4288
|
-
askCost: { value: 0 }
|
|
5154
|
+
askCost: { value: 0 },
|
|
5155
|
+
askUnpriced: false
|
|
4289
5156
|
};
|
|
4290
5157
|
return askStorage.run(frame, async () => {
|
|
4291
5158
|
const askStart = Date.now();
|
|
4292
5159
|
this.emitEvent({ type: "ask_start", prompt });
|
|
4293
5160
|
const costBefore = this.budgetContext?.totalCost ?? 0;
|
|
5161
|
+
const unpricedCountBefore = this.budgetContext?.unpricedCount ?? 0;
|
|
4294
5162
|
const resolveCtx = options?.metadata ? { metadata: { ...this.metadata, ...options.metadata } } : { metadata: this.metadata };
|
|
4295
5163
|
const usageCapture = {};
|
|
4296
5164
|
const doCall = async () => {
|
|
@@ -4336,6 +5204,7 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
4336
5204
|
) : await doCall();
|
|
4337
5205
|
outcome = { ok: true, result };
|
|
4338
5206
|
const costAfter = this.budgetContext?.totalCost ?? 0;
|
|
5207
|
+
const unpricedCountAfter = this.budgetContext?.unpricedCount ?? 0;
|
|
4339
5208
|
if (this.onAgentCallComplete) {
|
|
4340
5209
|
try {
|
|
4341
5210
|
this.onAgentCallComplete({
|
|
@@ -4344,6 +5213,7 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
4344
5213
|
response: typeof result === "string" ? result : JSON.stringify(result),
|
|
4345
5214
|
model: agent2.resolveModel(resolveCtx),
|
|
4346
5215
|
cost: costAfter - costBefore,
|
|
5216
|
+
unpriced: frame.askUnpriced || unpricedCountAfter > unpricedCountBefore,
|
|
4347
5217
|
duration: Date.now() - askStart,
|
|
4348
5218
|
promptVersion: agent2._config.version,
|
|
4349
5219
|
temperature: options?.temperature ?? agent2._config.temperature,
|
|
@@ -4378,6 +5248,7 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
4378
5248
|
error: "ask_end emitted without outcome \u2014 internal bug"
|
|
4379
5249
|
},
|
|
4380
5250
|
cost: frame.askCost.value,
|
|
5251
|
+
...frame.askUnpriced ? { unpriced: true } : {},
|
|
4381
5252
|
duration: Date.now() - askStart
|
|
4382
5253
|
});
|
|
4383
5254
|
} catch (emitErr) {
|
|
@@ -4461,15 +5332,32 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
4461
5332
|
messages.push(msg);
|
|
4462
5333
|
}
|
|
4463
5334
|
}
|
|
5335
|
+
const schemaPromptMode = options?.schemaPrompt ?? agent2._config.schemaPrompt ?? "json-schema";
|
|
4464
5336
|
let userContent = prompt;
|
|
5337
|
+
let appendedSchemaText;
|
|
4465
5338
|
if (options?.schema) {
|
|
4466
|
-
const
|
|
4467
|
-
|
|
5339
|
+
const schema = options.schema;
|
|
5340
|
+
if (schemaPromptMode === "none") {
|
|
5341
|
+
} else if (schemaPromptMode === "json-schema") {
|
|
5342
|
+
appendedSchemaText = renderSchemaForPrompt(schema);
|
|
5343
|
+
userContent += `
|
|
4468
5344
|
|
|
4469
5345
|
Respond with valid JSON matching this schema:
|
|
4470
|
-
${
|
|
5346
|
+
${appendedSchemaText}`;
|
|
5347
|
+
} else {
|
|
5348
|
+
appendedSchemaText = typeof schemaPromptMode.render === "function" ? schemaPromptMode.render(schema) : schemaPromptMode.render;
|
|
5349
|
+
userContent += `
|
|
5350
|
+
|
|
5351
|
+
${appendedSchemaText}`;
|
|
5352
|
+
}
|
|
4471
5353
|
}
|
|
4472
5354
|
messages.push({ role: "user", content: userContent });
|
|
5355
|
+
this.emitSchemaDiagnostics(agent2, options, toolDefs, {
|
|
5356
|
+
mode: schemaPromptMode,
|
|
5357
|
+
appendedSchemaText,
|
|
5358
|
+
provider,
|
|
5359
|
+
model
|
|
5360
|
+
});
|
|
4473
5361
|
if (handoffMessages && handoffMessages.length > 0) {
|
|
4474
5362
|
const handoffContext = handoffMessages.filter(
|
|
4475
5363
|
(m) => m.role === "assistant" || m.role === "tool"
|
|
@@ -4582,7 +5470,17 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
4582
5470
|
signal: this.currentSignal
|
|
4583
5471
|
};
|
|
4584
5472
|
if (options?.schema && toolDefs.length === 0) {
|
|
4585
|
-
|
|
5473
|
+
const nativeOut = options?.nativeStructuredOutput ?? agent2._config.nativeStructuredOutput ?? false;
|
|
5474
|
+
chatOptions.responseFormat = nativeOut ? {
|
|
5475
|
+
type: "json_schema",
|
|
5476
|
+
json_schema: {
|
|
5477
|
+
name: "response",
|
|
5478
|
+
// Input-side rendering — see `deriveNativeSchema`. Keeps the
|
|
5479
|
+
// native schema consistent with the prompt and non-empty for
|
|
5480
|
+
// `.transform()` schemas.
|
|
5481
|
+
schema: deriveNativeSchema(options.schema)
|
|
5482
|
+
}
|
|
5483
|
+
} : { type: "json_object" };
|
|
4586
5484
|
}
|
|
4587
5485
|
const callbackMeta = this.currentCallbackMeta(agent2._name);
|
|
4588
5486
|
const callParams = {
|
|
@@ -4700,10 +5598,7 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
4700
5598
|
}
|
|
4701
5599
|
});
|
|
4702
5600
|
}
|
|
4703
|
-
response ??= {
|
|
4704
|
-
content: content2,
|
|
4705
|
-
usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
|
|
4706
|
-
};
|
|
5601
|
+
response ??= { content: content2 };
|
|
4707
5602
|
if (toolCalls.length > 0) {
|
|
4708
5603
|
response.tool_calls = toolCalls;
|
|
4709
5604
|
}
|
|
@@ -4727,7 +5622,11 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
4727
5622
|
response: "",
|
|
4728
5623
|
turn: turns,
|
|
4729
5624
|
...retryReason ? { retryReason } : {},
|
|
4730
|
-
error: err instanceof Error ? err.message : String(err)
|
|
5625
|
+
error: err instanceof Error ? err.message : String(err),
|
|
5626
|
+
// Enrich with typed-error metadata when available. `body` is
|
|
5627
|
+
// deliberately NOT emitted — it's redaction-eligible (see
|
|
5628
|
+
// docs/security.md). `data.error` already carries the message.
|
|
5629
|
+
...err instanceof ProviderError ? { status: err.status, retryable: err.retryable } : {}
|
|
4731
5630
|
}
|
|
4732
5631
|
});
|
|
4733
5632
|
throw err;
|
|
@@ -4791,10 +5690,13 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
4791
5690
|
depth: handoffTargetDepth,
|
|
4792
5691
|
agent: descriptor.agent._name,
|
|
4793
5692
|
stepRef: sourceFrame?.stepRef ?? this.stepRefRoot,
|
|
4794
|
-
askCost: { value: 0 }
|
|
5693
|
+
askCost: { value: 0 },
|
|
5694
|
+
askUnpriced: false
|
|
4795
5695
|
};
|
|
4796
5696
|
const handoffOptions = options ? {
|
|
4797
5697
|
schema: options.schema,
|
|
5698
|
+
schemaPrompt: options.schemaPrompt,
|
|
5699
|
+
nativeStructuredOutput: options.nativeStructuredOutput,
|
|
4798
5700
|
retries: options.retries,
|
|
4799
5701
|
metadata: options.metadata,
|
|
4800
5702
|
validate: options.validate,
|
|
@@ -4815,6 +5717,7 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
4815
5717
|
type: "ask_end",
|
|
4816
5718
|
outcome: { ok: true, result },
|
|
4817
5719
|
cost: targetFrame.askCost.value,
|
|
5720
|
+
...targetFrame.askUnpriced ? { unpriced: true } : {},
|
|
4818
5721
|
duration: Date.now() - targetAskStart
|
|
4819
5722
|
});
|
|
4820
5723
|
return result;
|
|
@@ -4826,6 +5729,7 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
4826
5729
|
error: err instanceof Error ? err.message : String(err)
|
|
4827
5730
|
},
|
|
4828
5731
|
cost: targetFrame.askCost.value,
|
|
5732
|
+
...targetFrame.askUnpriced ? { unpriced: true } : {},
|
|
4829
5733
|
duration: Date.now() - targetAskStart
|
|
4830
5734
|
});
|
|
4831
5735
|
throw err;
|
|
@@ -5364,6 +6268,148 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
5364
6268
|
...providerMetadata ? { providerMetadata } : {}
|
|
5365
6269
|
});
|
|
5366
6270
|
}
|
|
6271
|
+
/**
|
|
6272
|
+
* Emit `schema_diagnostic` events for the silent structured-output cliffs
|
|
6273
|
+
* (spec 22, Problems B + E). Called once per ask. Covers:
|
|
6274
|
+
* - the appended **prompt** schema (oversized; dropped refinements; or the
|
|
6275
|
+
* zero-guidance `schemaPrompt:'none'` footgun),
|
|
6276
|
+
* - each user **tool-def** schema (oversized, dropped refinements),
|
|
6277
|
+
* - the **streaming** gate (disabled by a non-object root or by tools),
|
|
6278
|
+
* - **native structured output** the resolved provider can't honor (R10).
|
|
6279
|
+
*
|
|
6280
|
+
* The structured event always fires (persisted + streamed). For the genuinely
|
|
6281
|
+
* surprising, low-frequency cliffs it ALSO fires a one-time deduped
|
|
6282
|
+
* `console.warn` so the median consumer — who never wires up `ctx.events` and
|
|
6283
|
+
* runs with the trace console off — still sees it. `oversized` is event-only
|
|
6284
|
+
* (O3: it's tunable/noisy); the `tools` streaming cause is event-only too
|
|
6285
|
+
* (expected behavior for any tool-using agent, not a surprise).
|
|
6286
|
+
*/
|
|
6287
|
+
emitSchemaDiagnostics(agent2, options, toolDefs, ctx) {
|
|
6288
|
+
const silent = this.config.diagnostics?.silent;
|
|
6289
|
+
const threshold = this.config.diagnostics?.schemaOversizedTokens ?? DEFAULT_SCHEMA_OVERSIZED_TOKENS;
|
|
6290
|
+
const schema = options?.schema;
|
|
6291
|
+
if (schema) {
|
|
6292
|
+
if (ctx.mode === "none") {
|
|
6293
|
+
this.emitEvent({
|
|
6294
|
+
type: "schema_diagnostic",
|
|
6295
|
+
agent: agent2._name,
|
|
6296
|
+
data: { kind: "schema_prompt_none_no_guidance" }
|
|
6297
|
+
});
|
|
6298
|
+
warnSchemaDiagnosticOnce(
|
|
6299
|
+
`${agent2._name}\0schema_prompt_none`,
|
|
6300
|
+
`schemaPrompt:'none' is set on agent '${agent2._name}' with an output schema and no custom guidance \u2014 the model receives zero shape hints while the reply is still parsed against the schema, which invites a parse-failure retry loop. Provide a custom { render } or drop 'none'.`,
|
|
6301
|
+
silent
|
|
6302
|
+
);
|
|
6303
|
+
} else {
|
|
6304
|
+
const promptTokens = estimateTokens(ctx.appendedSchemaText ?? "");
|
|
6305
|
+
if (promptTokens > threshold) {
|
|
6306
|
+
this.emitEvent({
|
|
6307
|
+
type: "schema_diagnostic",
|
|
6308
|
+
agent: agent2._name,
|
|
6309
|
+
data: {
|
|
6310
|
+
kind: "prompt_schema_oversized",
|
|
6311
|
+
estimatedTokens: promptTokens,
|
|
6312
|
+
threshold,
|
|
6313
|
+
site: "prompt"
|
|
6314
|
+
}
|
|
6315
|
+
});
|
|
6316
|
+
}
|
|
6317
|
+
if (ctx.mode === "json-schema") {
|
|
6318
|
+
const dropped = detectDroppedRefinements(schema);
|
|
6319
|
+
if (dropped.count > 0) {
|
|
6320
|
+
this.emitEvent({
|
|
6321
|
+
type: "schema_diagnostic",
|
|
6322
|
+
agent: agent2._name,
|
|
6323
|
+
data: {
|
|
6324
|
+
kind: "dropped_refinements",
|
|
6325
|
+
count: dropped.count,
|
|
6326
|
+
paths: dropped.paths,
|
|
6327
|
+
site: "prompt"
|
|
6328
|
+
}
|
|
6329
|
+
});
|
|
6330
|
+
warnSchemaDiagnosticOnce(
|
|
6331
|
+
`${agent2._name}\0dropped_refinements\0prompt\0${dropped.count}\0${dropped.paths.join(",")}`,
|
|
6332
|
+
`${dropped.count} refinement(s) on the output schema for agent '${agent2._name}' (at ${dropped.paths.join(", ")}) are dropped from the model-facing JSON Schema \u2014 the model is never told the rule, then parsing rejects it. Surface it in the prompt (schemaPrompt) or repair with ctx.verify.`,
|
|
6333
|
+
silent
|
|
6334
|
+
);
|
|
6335
|
+
}
|
|
6336
|
+
}
|
|
6337
|
+
}
|
|
6338
|
+
const nativeOut = options?.nativeStructuredOutput ?? agent2._config.nativeStructuredOutput;
|
|
6339
|
+
if (nativeOut && toolDefs.length === 0) {
|
|
6340
|
+
const support = ctx.provider.nativeStructuredOutputSupport?.(ctx.model) ?? "schema";
|
|
6341
|
+
if (support !== "schema") {
|
|
6342
|
+
this.emitEvent({
|
|
6343
|
+
type: "schema_diagnostic",
|
|
6344
|
+
agent: agent2._name,
|
|
6345
|
+
data: {
|
|
6346
|
+
kind: "native_output_unsupported",
|
|
6347
|
+
...ctx.provider.name ? { provider: ctx.provider.name } : {},
|
|
6348
|
+
support
|
|
6349
|
+
}
|
|
6350
|
+
});
|
|
6351
|
+
warnSchemaDiagnosticOnce(
|
|
6352
|
+
`${agent2._name}\0native_output_unsupported\0${ctx.provider.name ?? ctx.model}`,
|
|
6353
|
+
`nativeStructuredOutput is set on agent '${agent2._name}' but provider '${ctx.provider.name ?? ctx.model}' ${support === "downgraded" ? "downgrades it to plain JSON mode" : support === "lossy" ? "sanitizes the schema lossily" : "ignores it structurally"} \u2014 the schema shape is not natively enforced. The prompt text remains the parse contract; the call proceeds.`,
|
|
6354
|
+
silent
|
|
6355
|
+
);
|
|
6356
|
+
}
|
|
6357
|
+
}
|
|
6358
|
+
const rootIsObject = schema instanceof z.ZodObject;
|
|
6359
|
+
const hasTools = toolDefs.length > 0;
|
|
6360
|
+
if (this._streamingEnabled && (!rootIsObject || hasTools)) {
|
|
6361
|
+
const cause = !rootIsObject ? "non-object" : "tools";
|
|
6362
|
+
const rootType = schema.constructor?.name ?? "unknown";
|
|
6363
|
+
this.emitEvent({
|
|
6364
|
+
type: "schema_diagnostic",
|
|
6365
|
+
agent: agent2._name,
|
|
6366
|
+
data: { kind: "streaming_disabled", rootType, cause }
|
|
6367
|
+
});
|
|
6368
|
+
if (cause === "non-object") {
|
|
6369
|
+
warnSchemaDiagnosticOnce(
|
|
6370
|
+
`${agent2._name}\0streaming_disabled\0${rootType}`,
|
|
6371
|
+
`Progressive object streaming is disabled for agent '${agent2._name}' because the output schema root is ${rootType}, not a ZodObject. Wrap it \u2014 z.object({ result: <yourSchema> }) \u2014 to re-enable partial_object streaming.`,
|
|
6372
|
+
silent
|
|
6373
|
+
);
|
|
6374
|
+
}
|
|
6375
|
+
}
|
|
6376
|
+
}
|
|
6377
|
+
for (const tool2 of agent2._config.tools ?? []) {
|
|
6378
|
+
const toolTokens = estimateInlineSchemaTokens(tool2.inputSchema);
|
|
6379
|
+
if (toolTokens > threshold) {
|
|
6380
|
+
this.emitEvent({
|
|
6381
|
+
type: "schema_diagnostic",
|
|
6382
|
+
agent: agent2._name,
|
|
6383
|
+
data: {
|
|
6384
|
+
kind: "prompt_schema_oversized",
|
|
6385
|
+
estimatedTokens: toolTokens,
|
|
6386
|
+
threshold,
|
|
6387
|
+
site: "tool",
|
|
6388
|
+
tool: tool2.name
|
|
6389
|
+
}
|
|
6390
|
+
});
|
|
6391
|
+
}
|
|
6392
|
+
const droppedTool = detectDroppedRefinements(tool2.inputSchema);
|
|
6393
|
+
if (droppedTool.count > 0) {
|
|
6394
|
+
this.emitEvent({
|
|
6395
|
+
type: "schema_diagnostic",
|
|
6396
|
+
agent: agent2._name,
|
|
6397
|
+
data: {
|
|
6398
|
+
kind: "dropped_refinements",
|
|
6399
|
+
count: droppedTool.count,
|
|
6400
|
+
paths: droppedTool.paths,
|
|
6401
|
+
site: "tool",
|
|
6402
|
+
tool: tool2.name
|
|
6403
|
+
}
|
|
6404
|
+
});
|
|
6405
|
+
warnSchemaDiagnosticOnce(
|
|
6406
|
+
`${agent2._name}\0dropped_refinements\0tool\0${tool2.name}\0${droppedTool.count}`,
|
|
6407
|
+
`${droppedTool.count} refinement(s) on tool '${tool2.name}' input schema (at ${droppedTool.paths.join(", ")}) are dropped from the tool definition \u2014 the model is never told the constraint. Validate inside the tool handler instead.`,
|
|
6408
|
+
silent
|
|
6409
|
+
);
|
|
6410
|
+
}
|
|
6411
|
+
}
|
|
6412
|
+
}
|
|
5367
6413
|
buildToolDefs(agent2, resolvedHandoffs) {
|
|
5368
6414
|
const defs = [];
|
|
5369
6415
|
if (agent2._config.tools) {
|
|
@@ -5856,24 +6902,44 @@ ${summaryResponse.content}`
|
|
|
5856
6902
|
limit,
|
|
5857
6903
|
exceeded: false,
|
|
5858
6904
|
policy,
|
|
5859
|
-
abortController: controller
|
|
6905
|
+
abortController: controller,
|
|
6906
|
+
unpriced: false,
|
|
6907
|
+
unpricedCount: 0,
|
|
6908
|
+
unpricedWarned: false
|
|
5860
6909
|
};
|
|
5861
6910
|
const executeBudget = async () => {
|
|
5862
6911
|
try {
|
|
5863
6912
|
const value = budgetSignal ? await signalStorage.run(budgetSignal, fn) : await fn();
|
|
5864
6913
|
const totalCost = this.budgetContext.totalCost;
|
|
5865
6914
|
const exceeded = this.budgetContext.exceeded;
|
|
5866
|
-
|
|
6915
|
+
const unpriced = this.budgetContext.unpriced;
|
|
6916
|
+
return { value, budgetExceeded: exceeded, totalCost, unpriced };
|
|
5867
6917
|
} catch (err) {
|
|
5868
6918
|
if (this.budgetContext.exceeded) {
|
|
5869
|
-
return {
|
|
6919
|
+
return {
|
|
6920
|
+
value: null,
|
|
6921
|
+
budgetExceeded: true,
|
|
6922
|
+
totalCost: this.budgetContext.totalCost,
|
|
6923
|
+
unpriced: this.budgetContext.unpriced
|
|
6924
|
+
};
|
|
5870
6925
|
}
|
|
5871
6926
|
if (err instanceof DOMException && err.name === "AbortError" && controller) {
|
|
5872
|
-
return {
|
|
6927
|
+
return {
|
|
6928
|
+
value: null,
|
|
6929
|
+
budgetExceeded: true,
|
|
6930
|
+
totalCost: this.budgetContext.totalCost,
|
|
6931
|
+
unpriced: this.budgetContext.unpriced
|
|
6932
|
+
};
|
|
5873
6933
|
}
|
|
5874
6934
|
throw err;
|
|
5875
6935
|
} finally {
|
|
5876
|
-
if (parentBudget)
|
|
6936
|
+
if (parentBudget) {
|
|
6937
|
+
parentBudget.totalCost += this.budgetContext.totalCost;
|
|
6938
|
+
if (this.budgetContext.unpriced) {
|
|
6939
|
+
parentBudget.unpriced = true;
|
|
6940
|
+
parentBudget.unpricedCount += this.budgetContext.unpricedCount;
|
|
6941
|
+
}
|
|
6942
|
+
}
|
|
5877
6943
|
this.budgetContext = parentBudget;
|
|
5878
6944
|
}
|
|
5879
6945
|
};
|
|
@@ -5894,13 +6960,22 @@ ${summaryResponse.content}`
|
|
|
5894
6960
|
}
|
|
5895
6961
|
return executeBudget();
|
|
5896
6962
|
}
|
|
5897
|
-
/**
|
|
6963
|
+
/**
|
|
6964
|
+
* Get the current budget status, or null if not inside a budget block. Inside
|
|
6965
|
+
* nested budgets this reflects the **innermost active** block (the nested block's
|
|
6966
|
+
* `finally` restores the parent before control returns to it).
|
|
6967
|
+
*
|
|
6968
|
+
* `unpriced` is true when this block ran a model with no usable cost (unpriced /
|
|
6969
|
+
* pricing-table miss): `spent` is then a LOWER BOUND and `remaining` an upper bound —
|
|
6970
|
+
* the limit cannot be enforced on the unmeasured spend. See {@link BudgetResult.unpriced}.
|
|
6971
|
+
*/
|
|
5898
6972
|
getBudgetStatus() {
|
|
5899
6973
|
if (!this.budgetContext) return null;
|
|
5900
6974
|
return {
|
|
5901
6975
|
spent: this.budgetContext.totalCost,
|
|
5902
6976
|
limit: this.budgetContext.limit,
|
|
5903
|
-
remaining: Math.max(0, this.budgetContext.limit - this.budgetContext.totalCost)
|
|
6977
|
+
remaining: Math.max(0, this.budgetContext.limit - this.budgetContext.totalCost),
|
|
6978
|
+
unpriced: this.budgetContext.unpriced
|
|
5904
6979
|
};
|
|
5905
6980
|
}
|
|
5906
6981
|
// ── ctx.race() ────────────────────────────────────────────────────────
|
|
@@ -6482,6 +7557,8 @@ ${summaryResponse.content}`
|
|
|
6482
7557
|
});
|
|
6483
7558
|
return this.ask(agents[0], prompt, {
|
|
6484
7559
|
schema: options?.schema,
|
|
7560
|
+
schemaPrompt: options?.schemaPrompt,
|
|
7561
|
+
nativeStructuredOutput: options?.nativeStructuredOutput,
|
|
6485
7562
|
retries: options?.retries,
|
|
6486
7563
|
metadata: options?.metadata,
|
|
6487
7564
|
validate: options?.validate,
|
|
@@ -6526,6 +7603,8 @@ ${summaryResponse.content}`
|
|
|
6526
7603
|
});
|
|
6527
7604
|
return this.ask(routerAgent, prompt, {
|
|
6528
7605
|
schema: options?.schema,
|
|
7606
|
+
schemaPrompt: options?.schemaPrompt,
|
|
7607
|
+
nativeStructuredOutput: options?.nativeStructuredOutput,
|
|
6529
7608
|
retries: options?.retries,
|
|
6530
7609
|
metadata: options?.metadata,
|
|
6531
7610
|
validate: options?.validate,
|
|
@@ -6544,8 +7623,25 @@ ${summaryResponse.content}`
|
|
|
6544
7623
|
const frame = askStorage.getStore();
|
|
6545
7624
|
const step = (frame?.stepRef ?? this.stepRefRoot).value++;
|
|
6546
7625
|
const cost = partial.cost;
|
|
6547
|
-
if (
|
|
6548
|
-
|
|
7626
|
+
if (COST_BEARING_LEAF_TYPES.includes(partial.type)) {
|
|
7627
|
+
const usable = isUsableCost(cost);
|
|
7628
|
+
const unpriced = !usable && hasPositiveTokens(
|
|
7629
|
+
partial
|
|
7630
|
+
);
|
|
7631
|
+
if (frame) {
|
|
7632
|
+
if (usable) frame.askCost.value += cost;
|
|
7633
|
+
else if (unpriced) frame.askUnpriced = true;
|
|
7634
|
+
}
|
|
7635
|
+
if (unpriced && this.budgetContext) {
|
|
7636
|
+
this.budgetContext.unpriced = true;
|
|
7637
|
+
this.budgetContext.unpricedCount += 1;
|
|
7638
|
+
if (!this.budgetContext.unpricedWarned && Number.isFinite(this.budgetContext.limit)) {
|
|
7639
|
+
this.budgetContext.unpricedWarned = true;
|
|
7640
|
+
console.warn(
|
|
7641
|
+
"Budget honesty: unpriced work detected \u2014 this budget's cost limit is a lower bound and is NOT enforced on unpriced models. See docs/observability.md#budget-honesty."
|
|
7642
|
+
);
|
|
7643
|
+
}
|
|
7644
|
+
}
|
|
6549
7645
|
}
|
|
6550
7646
|
const event = {
|
|
6551
7647
|
executionId: this.executionId,
|
|
@@ -8372,7 +9468,7 @@ var StreamingFlusher = class {
|
|
|
8372
9468
|
await Promise.allSettled(pendings);
|
|
8373
9469
|
}
|
|
8374
9470
|
};
|
|
8375
|
-
function
|
|
9471
|
+
function isAbortError2(err) {
|
|
8376
9472
|
return typeof err === "object" && err !== null && err.name === "AbortError";
|
|
8377
9473
|
}
|
|
8378
9474
|
function pushEventBounded(execInfo, event, cap) {
|
|
@@ -8635,7 +9731,7 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
8635
9731
|
this.persistExecution(execInfo);
|
|
8636
9732
|
return result;
|
|
8637
9733
|
} catch (err) {
|
|
8638
|
-
const aborted =
|
|
9734
|
+
const aborted = isAbortError2(err);
|
|
8639
9735
|
execInfo.status = "failed";
|
|
8640
9736
|
execInfo.completedAt = Date.now();
|
|
8641
9737
|
execInfo.duration = execInfo.completedAt - execInfo.startedAt;
|
|
@@ -8909,7 +10005,10 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
8909
10005
|
totalCost: 0,
|
|
8910
10006
|
limit: budgetLimit,
|
|
8911
10007
|
exceeded: false,
|
|
8912
|
-
policy: "finish_and_stop"
|
|
10008
|
+
policy: "finish_and_stop",
|
|
10009
|
+
unpriced: false,
|
|
10010
|
+
unpricedCount: 0,
|
|
10011
|
+
unpricedWarned: false
|
|
8913
10012
|
}
|
|
8914
10013
|
});
|
|
8915
10014
|
}
|
|
@@ -8944,6 +10043,7 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
8944
10043
|
status: "running",
|
|
8945
10044
|
events: [],
|
|
8946
10045
|
totalCost: 0,
|
|
10046
|
+
unpriced: false,
|
|
8947
10047
|
startedAt: Date.now(),
|
|
8948
10048
|
duration: 0,
|
|
8949
10049
|
metadata: liftPersistedMetadata(options?.metadata)
|
|
@@ -8962,6 +10062,7 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
8962
10062
|
onTrace: (event) => {
|
|
8963
10063
|
pushEventBounded(execInfo, event, this.maxEventsPerExecution);
|
|
8964
10064
|
execInfo.totalCost += eventCostContribution(event);
|
|
10065
|
+
if (isUnpricedLeaf(event)) execInfo.unpriced = true;
|
|
8965
10066
|
if (this.streamableExecutionIds.has(executionId)) {
|
|
8966
10067
|
this.streamingFlusher?.append(executionId, event);
|
|
8967
10068
|
}
|
|
@@ -9008,7 +10109,10 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
9008
10109
|
totalCost: 0,
|
|
9009
10110
|
limit: Infinity,
|
|
9010
10111
|
exceeded: false,
|
|
9011
|
-
policy: "finish_and_stop"
|
|
10112
|
+
policy: "finish_and_stop",
|
|
10113
|
+
unpriced: false,
|
|
10114
|
+
unpricedCount: 0,
|
|
10115
|
+
unpricedWarned: false
|
|
9012
10116
|
}
|
|
9013
10117
|
});
|
|
9014
10118
|
return this.spanManager.withSpanAsync(
|
|
@@ -9052,6 +10156,7 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
9052
10156
|
status: "running",
|
|
9053
10157
|
events: [],
|
|
9054
10158
|
totalCost: 0,
|
|
10159
|
+
unpriced: false,
|
|
9055
10160
|
startedAt: Date.now(),
|
|
9056
10161
|
duration: 0,
|
|
9057
10162
|
metadata: liftPersistedMetadata(options?.metadata)
|
|
@@ -9075,6 +10180,7 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
9075
10180
|
onTrace: (event) => {
|
|
9076
10181
|
pushEventBounded(execInfo, event, this.maxEventsPerExecution);
|
|
9077
10182
|
execInfo.totalCost += eventCostContribution(event);
|
|
10183
|
+
if (isUnpricedLeaf(event)) execInfo.unpriced = true;
|
|
9078
10184
|
if (this.streamableExecutionIds.has(executionId)) {
|
|
9079
10185
|
this.streamingFlusher?.append(executionId, event);
|
|
9080
10186
|
}
|
|
@@ -9122,7 +10228,10 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
9122
10228
|
totalCost: 0,
|
|
9123
10229
|
limit: Infinity,
|
|
9124
10230
|
exceeded: false,
|
|
9125
|
-
policy: "finish_and_stop"
|
|
10231
|
+
policy: "finish_and_stop",
|
|
10232
|
+
unpriced: false,
|
|
10233
|
+
unpricedCount: 0,
|
|
10234
|
+
unpricedWarned: false
|
|
9126
10235
|
}
|
|
9127
10236
|
});
|
|
9128
10237
|
return this.spanManager.withSpanAsync(
|
|
@@ -9284,12 +10393,14 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
9284
10393
|
const completedAt = lastEvent.timestamp ?? startedAt;
|
|
9285
10394
|
const workflowName = workflowStartEvent?.workflow ?? RECOVERED_UNKNOWN_WORKFLOW;
|
|
9286
10395
|
const totalCost = boundedEvents.reduce((sum, e) => sum + eventCostContribution(e), 0);
|
|
10396
|
+
const unpriced = boundedEvents.some(isUnpricedLeaf);
|
|
9287
10397
|
const synthesized = {
|
|
9288
10398
|
executionId: id,
|
|
9289
10399
|
workflow: workflowName,
|
|
9290
10400
|
status: "failed",
|
|
9291
10401
|
events: boundedEvents,
|
|
9292
10402
|
totalCost,
|
|
10403
|
+
unpriced,
|
|
9293
10404
|
startedAt,
|
|
9294
10405
|
completedAt,
|
|
9295
10406
|
duration: completedAt - startedAt,
|
|
@@ -9591,8 +10702,8 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
9591
10702
|
* Works with both `createContext()` and `execute()` calls inside `fn`.
|
|
9592
10703
|
*/
|
|
9593
10704
|
async trackCost(fn) {
|
|
9594
|
-
const { result, cost } = await this.trackExecution(fn);
|
|
9595
|
-
return { result, cost };
|
|
10705
|
+
const { result, cost, unpriced } = await this.trackExecution(fn);
|
|
10706
|
+
return { result, cost, unpriced };
|
|
9596
10707
|
}
|
|
9597
10708
|
/**
|
|
9598
10709
|
* Track cost and execution metadata across any runtime operations within the given function.
|
|
@@ -9637,10 +10748,12 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
9637
10748
|
const workflowCalls = /* @__PURE__ */ new Map();
|
|
9638
10749
|
const tokens = { input: 0, output: 0, reasoning: 0 };
|
|
9639
10750
|
let agentCalls = 0;
|
|
10751
|
+
let unpriced = false;
|
|
9640
10752
|
const capturedTraces = options?.captureTraces ? [] : void 0;
|
|
9641
10753
|
const listener = (event) => {
|
|
9642
10754
|
if (!scope.trackedIds.has(event.executionId)) return;
|
|
9643
10755
|
scope.totalCost += eventCostContribution(event);
|
|
10756
|
+
if (isUnpricedLeaf(event)) unpriced = true;
|
|
9644
10757
|
if (event.type === "agent_call_end") {
|
|
9645
10758
|
if (event.model) modelCalls.set(event.model, (modelCalls.get(event.model) ?? 0) + 1);
|
|
9646
10759
|
agentCalls++;
|
|
@@ -9678,6 +10791,7 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
9678
10791
|
return {
|
|
9679
10792
|
result,
|
|
9680
10793
|
cost: scope.totalCost,
|
|
10794
|
+
unpriced,
|
|
9681
10795
|
...capturedTraces ? { traces: capturedTraces } : {},
|
|
9682
10796
|
metadata: {
|
|
9683
10797
|
models: [...modelCalls.keys()],
|
|
@@ -10572,22 +11686,40 @@ var OpenAIEmbedder = class {
|
|
|
10572
11686
|
this.dimensions = options.dimensions ?? 1536;
|
|
10573
11687
|
}
|
|
10574
11688
|
async embed(texts, signal) {
|
|
10575
|
-
const response = await fetchWithRetry(
|
|
10576
|
-
|
|
10577
|
-
|
|
10578
|
-
|
|
10579
|
-
|
|
11689
|
+
const response = await fetchWithRetry(
|
|
11690
|
+
`${this.baseUrl}/v1/embeddings`,
|
|
11691
|
+
{
|
|
11692
|
+
method: "POST",
|
|
11693
|
+
headers: {
|
|
11694
|
+
"Content-Type": "application/json",
|
|
11695
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
11696
|
+
},
|
|
11697
|
+
body: JSON.stringify({
|
|
11698
|
+
model: this.model,
|
|
11699
|
+
input: texts,
|
|
11700
|
+
dimensions: this.dimensions
|
|
11701
|
+
}),
|
|
11702
|
+
signal
|
|
10580
11703
|
},
|
|
10581
|
-
|
|
10582
|
-
|
|
10583
|
-
|
|
10584
|
-
|
|
10585
|
-
}),
|
|
10586
|
-
signal
|
|
10587
|
-
});
|
|
11704
|
+
// Participate in the same typed-error contract as the provider adapters: a
|
|
11705
|
+
// thrown network failure normalizes to ProviderError{status:0,provider:'openai'}.
|
|
11706
|
+
{ provider: "openai" }
|
|
11707
|
+
);
|
|
10588
11708
|
if (!response.ok) {
|
|
10589
11709
|
const body = await response.text();
|
|
10590
|
-
|
|
11710
|
+
let detail = body;
|
|
11711
|
+
try {
|
|
11712
|
+
const json2 = JSON.parse(body);
|
|
11713
|
+
detail = json2.error?.message ?? json2.message ?? body;
|
|
11714
|
+
} catch {
|
|
11715
|
+
}
|
|
11716
|
+
throw buildProviderError({
|
|
11717
|
+
provider: "openai",
|
|
11718
|
+
status: response.status,
|
|
11719
|
+
headers: response.headers,
|
|
11720
|
+
message: `OpenAI embeddings API error (${response.status}): ${detail}`,
|
|
11721
|
+
body
|
|
11722
|
+
});
|
|
10591
11723
|
}
|
|
10592
11724
|
const json = await response.json();
|
|
10593
11725
|
const vectors = json.data.sort((a, b) => a.index - b.index).map((d) => d.embedding);
|
|
@@ -10728,57 +11860,87 @@ function cosineSimilarity2(a, b) {
|
|
|
10728
11860
|
}
|
|
10729
11861
|
export {
|
|
10730
11862
|
AXL_EVENT_TYPES,
|
|
11863
|
+
AZURE_PROFILE,
|
|
10731
11864
|
AnthropicProvider,
|
|
10732
11865
|
AxlError,
|
|
10733
11866
|
AxlEventBus,
|
|
10734
11867
|
AxlRuntime,
|
|
10735
11868
|
AxlStream,
|
|
11869
|
+
BEDROCK_PROFILE,
|
|
11870
|
+
BUILTIN_PROFILES,
|
|
10736
11871
|
BudgetExceededError,
|
|
10737
11872
|
CONFIG_CANDIDATES,
|
|
10738
11873
|
COST_BEARING_LEAF_TYPES,
|
|
11874
|
+
DEEPSEEK_PROFILE,
|
|
10739
11875
|
EventStreamOverflowError,
|
|
11876
|
+
GROQ_PROFILE,
|
|
10740
11877
|
GeminiProvider,
|
|
10741
11878
|
GuardrailError,
|
|
10742
11879
|
InMemoryVectorStore,
|
|
11880
|
+
LLAMACPP_PROFILE,
|
|
11881
|
+
LMSTUDIO_PROFILE,
|
|
11882
|
+
MISTRAL_PROFILE,
|
|
10743
11883
|
MaxTurnsError,
|
|
10744
11884
|
McpManager,
|
|
10745
11885
|
MemoryManager,
|
|
10746
11886
|
MemoryStore,
|
|
10747
11887
|
NoConsensus,
|
|
10748
11888
|
NoopSpanManager,
|
|
11889
|
+
OLLAMA_PROFILE,
|
|
11890
|
+
OPENAI_PRICING,
|
|
11891
|
+
OPENAI_PROFILE,
|
|
11892
|
+
OPENROUTER_PROFILE,
|
|
11893
|
+
OpenAICompatibleProvider,
|
|
10749
11894
|
OpenAIEmbedder,
|
|
10750
11895
|
OpenAIProvider,
|
|
10751
11896
|
OpenAIResponsesProvider,
|
|
11897
|
+
ProviderError,
|
|
10752
11898
|
ProviderRegistry,
|
|
10753
11899
|
QuorumNotMet,
|
|
10754
11900
|
REDACTED,
|
|
10755
11901
|
REDACTION_RULES,
|
|
10756
11902
|
RateLimiter,
|
|
10757
11903
|
RedisStore,
|
|
11904
|
+
SGLANG_PROFILE,
|
|
10758
11905
|
SQLiteStore,
|
|
10759
11906
|
Session,
|
|
10760
11907
|
SqliteVectorStore,
|
|
11908
|
+
ThinkTagScanner,
|
|
10761
11909
|
TimeoutError,
|
|
10762
11910
|
ToolDenied,
|
|
11911
|
+
VLLM_PROFILE,
|
|
10763
11912
|
ValidationError,
|
|
10764
11913
|
VerifyError,
|
|
10765
11914
|
WorkflowContext,
|
|
11915
|
+
XAI_PROFILE,
|
|
10766
11916
|
agent,
|
|
10767
11917
|
createSpanManager,
|
|
10768
11918
|
defineConfig,
|
|
11919
|
+
estimateOpenAICost,
|
|
10769
11920
|
eventCostContribution,
|
|
10770
11921
|
expandGlob,
|
|
10771
11922
|
extractJson,
|
|
11923
|
+
extractThinkTags,
|
|
10772
11924
|
findConfig,
|
|
11925
|
+
hasPositiveTokens,
|
|
10773
11926
|
importModule,
|
|
10774
11927
|
isCostBearingLeaf,
|
|
11928
|
+
isRetryableStatus,
|
|
10775
11929
|
isRootLevel,
|
|
11930
|
+
isUnpricedLeaf,
|
|
11931
|
+
isUsableCost,
|
|
10776
11932
|
needsTsxLoader,
|
|
11933
|
+
openaiReasoningEmit,
|
|
10777
11934
|
parsePartialJson,
|
|
10778
11935
|
pickDefault,
|
|
10779
11936
|
pickExport,
|
|
11937
|
+
priceFromTable,
|
|
11938
|
+
reasoningEffortEmit,
|
|
11939
|
+
reasoningObjectEmit,
|
|
10780
11940
|
redactEvent,
|
|
10781
11941
|
registerConditions,
|
|
11942
|
+
resolveApiKey,
|
|
11943
|
+
resolvePerModel,
|
|
10782
11944
|
resolveRuntime,
|
|
10783
11945
|
resolveThinkingOptions,
|
|
10784
11946
|
stringStreamFromEvents,
|