@axlsdk/axl 0.18.2 → 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.cjs
CHANGED
|
@@ -111,57 +111,87 @@ var init_span_manager = __esm({
|
|
|
111
111
|
var index_exports = {};
|
|
112
112
|
__export(index_exports, {
|
|
113
113
|
AXL_EVENT_TYPES: () => AXL_EVENT_TYPES,
|
|
114
|
+
AZURE_PROFILE: () => AZURE_PROFILE,
|
|
114
115
|
AnthropicProvider: () => AnthropicProvider,
|
|
115
116
|
AxlError: () => AxlError,
|
|
116
117
|
AxlEventBus: () => AxlEventBus,
|
|
117
118
|
AxlRuntime: () => AxlRuntime,
|
|
118
119
|
AxlStream: () => AxlStream,
|
|
120
|
+
BEDROCK_PROFILE: () => BEDROCK_PROFILE,
|
|
121
|
+
BUILTIN_PROFILES: () => BUILTIN_PROFILES,
|
|
119
122
|
BudgetExceededError: () => BudgetExceededError,
|
|
120
123
|
CONFIG_CANDIDATES: () => CONFIG_CANDIDATES,
|
|
121
124
|
COST_BEARING_LEAF_TYPES: () => COST_BEARING_LEAF_TYPES,
|
|
125
|
+
DEEPSEEK_PROFILE: () => DEEPSEEK_PROFILE,
|
|
122
126
|
EventStreamOverflowError: () => EventStreamOverflowError,
|
|
127
|
+
GROQ_PROFILE: () => GROQ_PROFILE,
|
|
123
128
|
GeminiProvider: () => GeminiProvider,
|
|
124
129
|
GuardrailError: () => GuardrailError,
|
|
125
130
|
InMemoryVectorStore: () => InMemoryVectorStore,
|
|
131
|
+
LLAMACPP_PROFILE: () => LLAMACPP_PROFILE,
|
|
132
|
+
LMSTUDIO_PROFILE: () => LMSTUDIO_PROFILE,
|
|
133
|
+
MISTRAL_PROFILE: () => MISTRAL_PROFILE,
|
|
126
134
|
MaxTurnsError: () => MaxTurnsError,
|
|
127
135
|
McpManager: () => McpManager,
|
|
128
136
|
MemoryManager: () => MemoryManager,
|
|
129
137
|
MemoryStore: () => MemoryStore,
|
|
130
138
|
NoConsensus: () => NoConsensus,
|
|
131
139
|
NoopSpanManager: () => NoopSpanManager,
|
|
140
|
+
OLLAMA_PROFILE: () => OLLAMA_PROFILE,
|
|
141
|
+
OPENAI_PRICING: () => OPENAI_PRICING,
|
|
142
|
+
OPENAI_PROFILE: () => OPENAI_PROFILE,
|
|
143
|
+
OPENROUTER_PROFILE: () => OPENROUTER_PROFILE,
|
|
144
|
+
OpenAICompatibleProvider: () => OpenAICompatibleProvider,
|
|
132
145
|
OpenAIEmbedder: () => OpenAIEmbedder,
|
|
133
146
|
OpenAIProvider: () => OpenAIProvider,
|
|
134
147
|
OpenAIResponsesProvider: () => OpenAIResponsesProvider,
|
|
148
|
+
ProviderError: () => ProviderError,
|
|
135
149
|
ProviderRegistry: () => ProviderRegistry,
|
|
136
150
|
QuorumNotMet: () => QuorumNotMet,
|
|
137
151
|
REDACTED: () => REDACTED,
|
|
138
152
|
REDACTION_RULES: () => REDACTION_RULES,
|
|
139
153
|
RateLimiter: () => RateLimiter,
|
|
140
154
|
RedisStore: () => RedisStore,
|
|
155
|
+
SGLANG_PROFILE: () => SGLANG_PROFILE,
|
|
141
156
|
SQLiteStore: () => SQLiteStore,
|
|
142
157
|
Session: () => Session,
|
|
143
158
|
SqliteVectorStore: () => SqliteVectorStore,
|
|
159
|
+
ThinkTagScanner: () => ThinkTagScanner,
|
|
144
160
|
TimeoutError: () => TimeoutError,
|
|
145
161
|
ToolDenied: () => ToolDenied,
|
|
162
|
+
VLLM_PROFILE: () => VLLM_PROFILE,
|
|
146
163
|
ValidationError: () => ValidationError,
|
|
147
164
|
VerifyError: () => VerifyError,
|
|
148
165
|
WorkflowContext: () => WorkflowContext,
|
|
166
|
+
XAI_PROFILE: () => XAI_PROFILE,
|
|
149
167
|
agent: () => agent,
|
|
150
168
|
createSpanManager: () => createSpanManager,
|
|
151
169
|
defineConfig: () => defineConfig,
|
|
170
|
+
estimateOpenAICost: () => estimateOpenAICost,
|
|
152
171
|
eventCostContribution: () => eventCostContribution,
|
|
153
172
|
expandGlob: () => expandGlob,
|
|
154
173
|
extractJson: () => extractJson,
|
|
174
|
+
extractThinkTags: () => extractThinkTags,
|
|
155
175
|
findConfig: () => findConfig,
|
|
176
|
+
hasPositiveTokens: () => hasPositiveTokens,
|
|
156
177
|
importModule: () => importModule,
|
|
157
178
|
isCostBearingLeaf: () => isCostBearingLeaf,
|
|
179
|
+
isRetryableStatus: () => isRetryableStatus,
|
|
158
180
|
isRootLevel: () => isRootLevel,
|
|
181
|
+
isUnpricedLeaf: () => isUnpricedLeaf,
|
|
182
|
+
isUsableCost: () => isUsableCost,
|
|
159
183
|
needsTsxLoader: () => needsTsxLoader,
|
|
184
|
+
openaiReasoningEmit: () => openaiReasoningEmit,
|
|
160
185
|
parsePartialJson: () => parsePartialJson,
|
|
161
186
|
pickDefault: () => pickDefault,
|
|
162
187
|
pickExport: () => pickExport,
|
|
188
|
+
priceFromTable: () => priceFromTable,
|
|
189
|
+
reasoningEffortEmit: () => reasoningEffortEmit,
|
|
190
|
+
reasoningObjectEmit: () => reasoningObjectEmit,
|
|
163
191
|
redactEvent: () => redactEvent,
|
|
164
192
|
registerConditions: () => registerConditions,
|
|
193
|
+
resolveApiKey: () => resolveApiKey,
|
|
194
|
+
resolvePerModel: () => resolvePerModel,
|
|
165
195
|
resolveRuntime: () => resolveRuntime,
|
|
166
196
|
resolveThinkingOptions: () => resolveThinkingOptions,
|
|
167
197
|
stringStreamFromEvents: () => stringStreamFromEvents,
|
|
@@ -289,11 +319,218 @@ function resolveThinkingOptions(options) {
|
|
|
289
319
|
hasBudgetOverride
|
|
290
320
|
};
|
|
291
321
|
}
|
|
322
|
+
async function resolveApiKey(source) {
|
|
323
|
+
if (typeof source === "function") return await source() ?? "";
|
|
324
|
+
return source ?? "";
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// src/errors.ts
|
|
328
|
+
var AxlError = class extends Error {
|
|
329
|
+
code;
|
|
330
|
+
constructor(code, message) {
|
|
331
|
+
super(message);
|
|
332
|
+
this.name = "AxlError";
|
|
333
|
+
this.code = code;
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
var VerifyError = class extends AxlError {
|
|
337
|
+
lastOutput;
|
|
338
|
+
zodError;
|
|
339
|
+
retries;
|
|
340
|
+
constructor(lastOutput, zodError, retries) {
|
|
341
|
+
super("VERIFY_ERROR", `Schema validation failed after ${retries} retries: ${zodError.message}`);
|
|
342
|
+
this.name = "VerifyError";
|
|
343
|
+
this.lastOutput = lastOutput;
|
|
344
|
+
this.zodError = zodError;
|
|
345
|
+
this.retries = retries;
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
var QuorumNotMet = class extends AxlError {
|
|
349
|
+
results;
|
|
350
|
+
constructor(required, actual, results) {
|
|
351
|
+
super("QUORUM_NOT_MET", `Quorum not met: needed ${required} successes, got ${actual}`);
|
|
352
|
+
this.name = "QuorumNotMet";
|
|
353
|
+
this.results = results;
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
var NoConsensus = class extends AxlError {
|
|
357
|
+
constructor(reason) {
|
|
358
|
+
super("NO_CONSENSUS", `No consensus: ${reason}`);
|
|
359
|
+
this.name = "NoConsensus";
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
var TimeoutError = class extends AxlError {
|
|
363
|
+
constructor(operation, timeoutMs) {
|
|
364
|
+
super("TIMEOUT", `${operation} exceeded timeout of ${timeoutMs}ms`);
|
|
365
|
+
this.name = "TimeoutError";
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
function formatBudgetCost(cost) {
|
|
369
|
+
if (Number.isNaN(cost)) return "$NaN";
|
|
370
|
+
if (cost === Infinity) return "$Infinity";
|
|
371
|
+
if (cost === -Infinity) return "-$Infinity";
|
|
372
|
+
if (cost === 0) return "$0.00";
|
|
373
|
+
const sign = cost < 0 ? "-" : "";
|
|
374
|
+
const abs = Math.abs(cost);
|
|
375
|
+
if (abs < 1e-6) return `${sign}< $0.000001`;
|
|
376
|
+
if (abs < 1e-4) {
|
|
377
|
+
const [mantissa, exponent] = abs.toExponential(2).split("e");
|
|
378
|
+
return `${sign}$${mantissa}e${parseInt(exponent, 10)}`;
|
|
379
|
+
}
|
|
380
|
+
if (abs < 0.01) return `${sign}$${abs.toFixed(6)}`;
|
|
381
|
+
return `${sign}$${abs.toFixed(2)}`;
|
|
382
|
+
}
|
|
383
|
+
var BudgetExceededError = class extends AxlError {
|
|
384
|
+
limit;
|
|
385
|
+
spent;
|
|
386
|
+
policy;
|
|
387
|
+
constructor(limit, spent, policy) {
|
|
388
|
+
super(
|
|
389
|
+
"BUDGET_EXCEEDED",
|
|
390
|
+
`Budget exceeded: spent ${formatBudgetCost(spent)} of ${formatBudgetCost(
|
|
391
|
+
limit
|
|
392
|
+
)} limit (policy: ${policy})`
|
|
393
|
+
);
|
|
394
|
+
this.name = "BudgetExceededError";
|
|
395
|
+
this.limit = limit;
|
|
396
|
+
this.spent = spent;
|
|
397
|
+
this.policy = policy;
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
var MaxTurnsError = class extends AxlError {
|
|
401
|
+
maxTurns;
|
|
402
|
+
constructor(operation, maxTurns) {
|
|
403
|
+
super("MAX_TURNS", `${operation} exceeded maximum of ${maxTurns} turns`);
|
|
404
|
+
this.name = "MaxTurnsError";
|
|
405
|
+
this.maxTurns = maxTurns;
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
var GuardrailError = class extends AxlError {
|
|
409
|
+
guardrailType;
|
|
410
|
+
reason;
|
|
411
|
+
constructor(guardrailType, reason) {
|
|
412
|
+
super("GUARDRAIL_BLOCKED", `${guardrailType} guardrail blocked: ${reason}`);
|
|
413
|
+
this.name = "GuardrailError";
|
|
414
|
+
this.guardrailType = guardrailType;
|
|
415
|
+
this.reason = reason;
|
|
416
|
+
}
|
|
417
|
+
};
|
|
418
|
+
var ValidationError = class extends AxlError {
|
|
419
|
+
lastOutput;
|
|
420
|
+
reason;
|
|
421
|
+
retries;
|
|
422
|
+
constructor(lastOutput, reason, retries) {
|
|
423
|
+
super("VALIDATION_ERROR", `Validation failed after ${retries} retries: ${reason}`);
|
|
424
|
+
this.name = "ValidationError";
|
|
425
|
+
this.lastOutput = lastOutput;
|
|
426
|
+
this.reason = reason;
|
|
427
|
+
this.retries = retries;
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
var ToolDenied = class extends AxlError {
|
|
431
|
+
toolName;
|
|
432
|
+
agentName;
|
|
433
|
+
constructor(toolName, agentName) {
|
|
434
|
+
super(
|
|
435
|
+
"TOOL_DENIED",
|
|
436
|
+
`Agent "${agentName}" attempted to call tool "${toolName}" which is not in its ACL`
|
|
437
|
+
);
|
|
438
|
+
this.name = "ToolDenied";
|
|
439
|
+
this.toolName = toolName;
|
|
440
|
+
this.agentName = agentName;
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
// src/providers/errors.ts
|
|
445
|
+
var ProviderError = class extends AxlError {
|
|
446
|
+
/** Adapter/profile name, e.g. `'openai'`, `'anthropic'`, `'google'`. */
|
|
447
|
+
provider;
|
|
448
|
+
/** HTTP status code; `0` for non-HTTP (network) failures. */
|
|
449
|
+
status;
|
|
450
|
+
/** Semantic failover hint (see module doc) — NOT the transport-retry set. */
|
|
451
|
+
retryable;
|
|
452
|
+
/** Parsed `Retry-After` delay in ms (RAW/unclamped), when the header was present. */
|
|
453
|
+
retryAfterMs;
|
|
454
|
+
/** Provider request id from response headers, when present. */
|
|
455
|
+
requestId;
|
|
456
|
+
/**
|
|
457
|
+
* Raw provider error body. Lives ONLY on the error object — it is
|
|
458
|
+
* intentionally never placed on the event stream (it can echo prompt text
|
|
459
|
+
* and is redaction-eligible). See `docs/security.md`.
|
|
460
|
+
*/
|
|
461
|
+
body;
|
|
462
|
+
constructor(args) {
|
|
463
|
+
super("PROVIDER_ERROR", args.message);
|
|
464
|
+
this.name = "ProviderError";
|
|
465
|
+
this.provider = args.provider;
|
|
466
|
+
this.status = args.status;
|
|
467
|
+
this.retryable = args.retryable;
|
|
468
|
+
this.retryAfterMs = args.retryAfterMs;
|
|
469
|
+
this.requestId = args.requestId;
|
|
470
|
+
this.body = args.body;
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
function isRetryableStatus(status) {
|
|
474
|
+
switch (status) {
|
|
475
|
+
case 0:
|
|
476
|
+
return true;
|
|
477
|
+
case 408:
|
|
478
|
+
// request timeout
|
|
479
|
+
case 429:
|
|
480
|
+
// rate limit
|
|
481
|
+
case 500:
|
|
482
|
+
case 502:
|
|
483
|
+
case 503:
|
|
484
|
+
case 504:
|
|
485
|
+
case 529:
|
|
486
|
+
return true;
|
|
487
|
+
// Explicitly non-retryable (bad request / auth / not found / payload):
|
|
488
|
+
// 400, 401, 403, 404, 413, 422 fall through to the default.
|
|
489
|
+
default:
|
|
490
|
+
return false;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
function parseRetryAfter(headers) {
|
|
494
|
+
const raw = headers.get("retry-after");
|
|
495
|
+
if (!raw) return void 0;
|
|
496
|
+
const trimmed = raw.trim();
|
|
497
|
+
if (/^\d+(\.\d+)?$/.test(trimmed)) {
|
|
498
|
+
const ms = Number(trimmed) * 1e3;
|
|
499
|
+
return ms > 0 ? ms : void 0;
|
|
500
|
+
}
|
|
501
|
+
const dateMs = Date.parse(trimmed);
|
|
502
|
+
if (Number.isNaN(dateMs)) return void 0;
|
|
503
|
+
const delta = dateMs - Date.now();
|
|
504
|
+
return delta > 0 ? delta : void 0;
|
|
505
|
+
}
|
|
506
|
+
var REQUEST_ID_HEADERS = ["x-request-id", "request-id"];
|
|
507
|
+
function extractRequestId(headers) {
|
|
508
|
+
for (const name of REQUEST_ID_HEADERS) {
|
|
509
|
+
const value = headers.get(name);
|
|
510
|
+
if (value) return value;
|
|
511
|
+
}
|
|
512
|
+
return void 0;
|
|
513
|
+
}
|
|
514
|
+
function buildProviderError(args) {
|
|
515
|
+
return new ProviderError({
|
|
516
|
+
provider: args.provider,
|
|
517
|
+
status: args.status,
|
|
518
|
+
retryable: isRetryableStatus(args.status),
|
|
519
|
+
message: args.message,
|
|
520
|
+
retryAfterMs: args.headers ? parseRetryAfter(args.headers) : void 0,
|
|
521
|
+
requestId: args.headers ? extractRequestId(args.headers) : void 0,
|
|
522
|
+
body: args.body
|
|
523
|
+
});
|
|
524
|
+
}
|
|
292
525
|
|
|
293
526
|
// src/providers/retry.ts
|
|
294
527
|
var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([429, 503, 529]);
|
|
295
528
|
var MAX_RETRIES = 2;
|
|
296
529
|
var BASE_DELAY_MS = 1e3;
|
|
530
|
+
var MAX_BACKOFF_MS = 6e4;
|
|
531
|
+
function jitter(ms) {
|
|
532
|
+
return ms * (0.75 + Math.random() * 0.5);
|
|
533
|
+
}
|
|
297
534
|
function sleep2(ms, signal) {
|
|
298
535
|
return new Promise((resolve2) => {
|
|
299
536
|
if (signal?.aborted) {
|
|
@@ -311,9 +548,14 @@ function sleep2(ms, signal) {
|
|
|
311
548
|
);
|
|
312
549
|
});
|
|
313
550
|
}
|
|
551
|
+
function isAbortError(err, signal) {
|
|
552
|
+
if (signal?.aborted) return true;
|
|
553
|
+
return typeof err === "object" && err !== null && "name" in err && err.name === "AbortError";
|
|
554
|
+
}
|
|
314
555
|
async function fetchWithRetry(input, init, opts) {
|
|
315
556
|
const maxRetries = opts?.maxRetries ?? MAX_RETRIES;
|
|
316
557
|
const governor = opts?.governor;
|
|
558
|
+
const provider = opts?.provider ?? "unknown";
|
|
317
559
|
let acquired = false;
|
|
318
560
|
if (governor) {
|
|
319
561
|
await governor.acquire(init?.signal ?? void 0);
|
|
@@ -321,7 +563,21 @@ async function fetchWithRetry(input, init, opts) {
|
|
|
321
563
|
}
|
|
322
564
|
try {
|
|
323
565
|
for (let attempt = 0; ; attempt++) {
|
|
324
|
-
|
|
566
|
+
let res;
|
|
567
|
+
try {
|
|
568
|
+
res = await fetch(input, init);
|
|
569
|
+
} catch (err) {
|
|
570
|
+
if (isAbortError(err, init?.signal ?? void 0)) throw err;
|
|
571
|
+
if (attempt >= maxRetries) {
|
|
572
|
+
throw buildProviderError({
|
|
573
|
+
provider,
|
|
574
|
+
status: 0,
|
|
575
|
+
message: err instanceof Error ? err.message : String(err)
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
await sleep2(jitter(BASE_DELAY_MS * 2 ** attempt), init?.signal ?? void 0);
|
|
579
|
+
continue;
|
|
580
|
+
}
|
|
325
581
|
governor?.observe(res);
|
|
326
582
|
if (res.ok || !RETRYABLE_STATUS_CODES.has(res.status) || attempt >= maxRetries) {
|
|
327
583
|
return res;
|
|
@@ -329,15 +585,9 @@ async function fetchWithRetry(input, init, opts) {
|
|
|
329
585
|
if (init?.signal?.aborted) {
|
|
330
586
|
return res;
|
|
331
587
|
}
|
|
332
|
-
const
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
delay = Number(retryAfter) * 1e3;
|
|
336
|
-
} else {
|
|
337
|
-
delay = BASE_DELAY_MS * 2 ** attempt;
|
|
338
|
-
}
|
|
339
|
-
delay *= 0.75 + Math.random() * 0.5;
|
|
340
|
-
await sleep2(delay, init?.signal ?? void 0);
|
|
588
|
+
const retryAfterMs = parseRetryAfter(res.headers);
|
|
589
|
+
const baseDelay = retryAfterMs !== void 0 ? Math.min(retryAfterMs, MAX_BACKOFF_MS) : BASE_DELAY_MS * 2 ** attempt;
|
|
590
|
+
await sleep2(jitter(baseDelay), init?.signal ?? void 0);
|
|
341
591
|
}
|
|
342
592
|
} finally {
|
|
343
593
|
if (acquired) governor.release();
|
|
@@ -485,254 +735,467 @@ var RateLimiter = class {
|
|
|
485
735
|
}
|
|
486
736
|
};
|
|
487
737
|
|
|
488
|
-
// src/providers/openai.ts
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
"
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
"o3-mini": [11e-7, 44e-7, 0.25],
|
|
505
|
-
"o3-pro": [2e-5, 8e-5, 0.25],
|
|
506
|
-
"o4-mini": [11e-7, 44e-7, 0.25],
|
|
507
|
-
// gpt-5 era — cache reads at 10% of input rate
|
|
508
|
-
"gpt-5": [125e-8, 1e-5, 0.1],
|
|
509
|
-
"gpt-5-mini": [25e-8, 2e-6, 0.1],
|
|
510
|
-
"gpt-5-nano": [5e-8, 4e-7, 0.1],
|
|
511
|
-
"gpt-5.1": [125e-8, 1e-5, 0.1],
|
|
512
|
-
"gpt-5.2": [175e-8, 14e-6, 0.1],
|
|
513
|
-
"gpt-5.3": [175e-8, 14e-6, 0.1],
|
|
514
|
-
"gpt-5.4": [25e-7, 15e-6, 0.1],
|
|
515
|
-
"gpt-5.4-pro": [3e-5, 18e-5, 0.1],
|
|
516
|
-
// gpt-5.5 era (flagship released 2026-04) — cache reads at 10% of input rate
|
|
517
|
-
"gpt-5.5": [5e-6, 3e-5, 0.1],
|
|
518
|
-
"gpt-5.5-pro": [3e-5, 18e-5, 0.1]
|
|
519
|
-
};
|
|
520
|
-
var PRICING_KEYS_BY_LENGTH = Object.keys(OPENAI_PRICING).sort((a, b) => b.length - a.length);
|
|
521
|
-
function estimateOpenAICost(model, promptTokens, completionTokens, cachedTokens) {
|
|
522
|
-
let pricing = OPENAI_PRICING[model];
|
|
738
|
+
// src/providers/openai-compatible.ts
|
|
739
|
+
function resolvePerModel(value, model, fallback) {
|
|
740
|
+
if (value === void 0) return fallback;
|
|
741
|
+
return typeof value === "function" ? value(model) : value;
|
|
742
|
+
}
|
|
743
|
+
var SORTED_KEYS_CACHE = /* @__PURE__ */ new WeakMap();
|
|
744
|
+
function sortedKeys(table) {
|
|
745
|
+
let keys = SORTED_KEYS_CACHE.get(table);
|
|
746
|
+
if (!keys) {
|
|
747
|
+
keys = Object.keys(table).sort((a, b) => b.length - a.length);
|
|
748
|
+
SORTED_KEYS_CACHE.set(table, keys);
|
|
749
|
+
}
|
|
750
|
+
return keys;
|
|
751
|
+
}
|
|
752
|
+
function priceFromTable(table, model, promptTokens, completionTokens, cachedTokens) {
|
|
753
|
+
let pricing = table[model];
|
|
523
754
|
if (!pricing) {
|
|
524
|
-
for (const key of
|
|
755
|
+
for (const key of sortedKeys(table)) {
|
|
525
756
|
if (model.startsWith(key)) {
|
|
526
|
-
pricing =
|
|
757
|
+
pricing = table[key];
|
|
527
758
|
break;
|
|
528
759
|
}
|
|
529
760
|
}
|
|
530
761
|
}
|
|
531
|
-
if (!pricing) return 0;
|
|
762
|
+
if (!pricing) return void 0;
|
|
532
763
|
const [inputRate, outputRate, cacheMultiplier] = pricing;
|
|
533
764
|
const cached = cachedTokens ?? 0;
|
|
534
765
|
const inputCost = (promptTokens - cached) * inputRate + cached * inputRate * cacheMultiplier;
|
|
535
766
|
return inputCost + completionTokens * outputRate;
|
|
536
767
|
}
|
|
537
|
-
|
|
538
|
-
|
|
768
|
+
var THINK_OPEN = "<think>";
|
|
769
|
+
var THINK_CLOSE = "</think>";
|
|
770
|
+
function partialTagSuffixLen(s, tag) {
|
|
771
|
+
const max = Math.min(s.length, tag.length - 1);
|
|
772
|
+
for (let n = max; n > 0; n--) {
|
|
773
|
+
if (tag.startsWith(s.slice(s.length - n))) return n;
|
|
774
|
+
}
|
|
775
|
+
return 0;
|
|
539
776
|
}
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
777
|
+
var ThinkTagScanner = class {
|
|
778
|
+
buffer = "";
|
|
779
|
+
inside = false;
|
|
780
|
+
push(chunk) {
|
|
781
|
+
this.buffer += chunk;
|
|
782
|
+
let text = "";
|
|
783
|
+
let thinking = "";
|
|
784
|
+
for (; ; ) {
|
|
785
|
+
const tag = this.inside ? THINK_CLOSE : THINK_OPEN;
|
|
786
|
+
const idx = this.buffer.indexOf(tag);
|
|
787
|
+
if (idx === -1) {
|
|
788
|
+
const hold = partialTagSuffixLen(this.buffer, tag);
|
|
789
|
+
const emit = this.buffer.slice(0, this.buffer.length - hold);
|
|
790
|
+
if (this.inside) thinking += emit;
|
|
791
|
+
else text += emit;
|
|
792
|
+
this.buffer = this.buffer.slice(this.buffer.length - hold);
|
|
793
|
+
break;
|
|
794
|
+
}
|
|
795
|
+
const before = this.buffer.slice(0, idx);
|
|
796
|
+
if (this.inside) thinking += before;
|
|
797
|
+
else text += before;
|
|
798
|
+
this.buffer = this.buffer.slice(idx + tag.length);
|
|
799
|
+
this.inside = !this.inside;
|
|
800
|
+
}
|
|
801
|
+
return { text, thinking };
|
|
802
|
+
}
|
|
803
|
+
/** Flush any residual buffer at stream end (treat as the current channel). */
|
|
804
|
+
flush() {
|
|
805
|
+
const rest = this.buffer;
|
|
806
|
+
this.buffer = "";
|
|
807
|
+
return this.inside ? { text: "", thinking: rest } : { text: rest, thinking: "" };
|
|
808
|
+
}
|
|
809
|
+
};
|
|
810
|
+
function extractThinkTags(content) {
|
|
811
|
+
const scanner = new ThinkTagScanner();
|
|
812
|
+
const a = scanner.push(content);
|
|
813
|
+
const b = scanner.flush();
|
|
814
|
+
return { content: a.text + b.text, thinking: a.thinking + b.thinking };
|
|
545
815
|
}
|
|
546
|
-
function
|
|
547
|
-
return
|
|
816
|
+
function reasoningEffortEmit(map) {
|
|
817
|
+
return (body, resolved, model) => {
|
|
818
|
+
const value = map(resolved, model);
|
|
819
|
+
if (value) body.reasoning_effort = value;
|
|
820
|
+
};
|
|
548
821
|
}
|
|
549
|
-
function
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
822
|
+
function reasoningObjectEmit(mapEffort) {
|
|
823
|
+
return (body, resolved, model) => {
|
|
824
|
+
if (resolved.hasBudgetOverride) {
|
|
825
|
+
body.reasoning = { max_tokens: resolved.thinkingBudget };
|
|
826
|
+
} else if (resolved.activeEffort) {
|
|
827
|
+
body.reasoning = { effort: mapEffort(resolved.activeEffort, model) };
|
|
828
|
+
} else if (resolved.thinkingDisabled) {
|
|
829
|
+
body.reasoning = { enabled: false };
|
|
830
|
+
}
|
|
831
|
+
};
|
|
554
832
|
}
|
|
555
|
-
|
|
556
|
-
|
|
833
|
+
var ROUND_TRIP_FIELDS = ["reasoning_content", "reasoning", "reasoning_details"];
|
|
834
|
+
function isRoundTripField(field) {
|
|
835
|
+
return typeof field === "string" && ROUND_TRIP_FIELDS.includes(field);
|
|
557
836
|
}
|
|
558
|
-
function
|
|
559
|
-
if (
|
|
560
|
-
if (
|
|
561
|
-
return "
|
|
837
|
+
function normalizeMessageContent(content) {
|
|
838
|
+
if (typeof content === "string") return content;
|
|
839
|
+
if (content == null) return "";
|
|
840
|
+
if (!Array.isArray(content)) return "";
|
|
841
|
+
return content.map((part) => {
|
|
842
|
+
if (typeof part === "string") return part;
|
|
843
|
+
if (!part || typeof part !== "object") return "";
|
|
844
|
+
if (typeof part.text === "string") return part.text;
|
|
845
|
+
if (typeof part.content === "string") return part.content;
|
|
846
|
+
return "";
|
|
847
|
+
}).join("");
|
|
562
848
|
}
|
|
563
|
-
var
|
|
564
|
-
name
|
|
849
|
+
var OpenAICompatibleProvider = class {
|
|
850
|
+
name;
|
|
851
|
+
/** `json_schema` is honored natively when the profile's `supportsJsonSchema`
|
|
852
|
+
* capability is true for this model (the default); otherwise the engine
|
|
853
|
+
* downgrades the request to plain `json_object` (see `buildRequestBody`),
|
|
854
|
+
* which does NOT enforce the schema shape. */
|
|
855
|
+
nativeStructuredOutputSupport(model) {
|
|
856
|
+
const supportsSchema = resolvePerModel(
|
|
857
|
+
this.profile.capabilities?.supportsJsonSchema,
|
|
858
|
+
model,
|
|
859
|
+
true
|
|
860
|
+
);
|
|
861
|
+
return supportsSchema ? "schema" : "downgraded";
|
|
862
|
+
}
|
|
863
|
+
profile;
|
|
565
864
|
baseUrl;
|
|
566
|
-
|
|
865
|
+
/** A key string, or a resolver invoked per request (expiring tokens). */
|
|
866
|
+
apiKeySource;
|
|
867
|
+
authHeader;
|
|
567
868
|
governor;
|
|
568
|
-
constructor(options
|
|
569
|
-
|
|
570
|
-
this.
|
|
869
|
+
constructor(options) {
|
|
870
|
+
const p = options.profile;
|
|
871
|
+
this.profile = p;
|
|
872
|
+
this.name = p.name;
|
|
873
|
+
this.apiKeySource = options.apiKey ?? (p.envApiKey ? process.env[p.envApiKey] : void 0) ?? "";
|
|
874
|
+
this.authHeader = options.authHeader;
|
|
875
|
+
const envBase = p.envBaseUrl ? process.env[p.envBaseUrl] : void 0;
|
|
876
|
+
const explicitBase = options.baseUrl ?? envBase;
|
|
877
|
+
this.baseUrl = (explicitBase ?? p.defaultBaseUrl).replace(/\/$/, "");
|
|
571
878
|
this.governor = options.rateLimit ? new RateLimiter(options.rateLimit) : void 0;
|
|
572
|
-
|
|
573
|
-
|
|
879
|
+
const label = p.label ?? p.name;
|
|
880
|
+
if (p.requireExplicitBaseUrl && explicitBase === void 0) {
|
|
881
|
+
const env = p.envBaseUrl ? ` or ${p.envBaseUrl}` : "";
|
|
882
|
+
throw new Error(
|
|
883
|
+
`${label} requires a base URL. Set providers.${p.name}.baseUrl${env} (it is resource-specific and has no default).`
|
|
884
|
+
);
|
|
885
|
+
}
|
|
886
|
+
if (typeof this.apiKeySource === "string" && !this.apiKeySource && !p.allowMissingApiKey) {
|
|
887
|
+
const env = p.envApiKey ?? "the API key env var";
|
|
888
|
+
throw new Error(`${label} API key is required. Set ${env} or pass apiKey in options.`);
|
|
574
889
|
}
|
|
575
890
|
}
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
891
|
+
/** Resolve the API key for one request, validating against allowMissingApiKey. */
|
|
892
|
+
async resolveKey() {
|
|
893
|
+
const key = await resolveApiKey(this.apiKeySource);
|
|
894
|
+
if (!key && !this.profile.allowMissingApiKey) {
|
|
895
|
+
const label = this.profile.label ?? this.profile.name;
|
|
896
|
+
const env = this.profile.envApiKey ?? "the API key env var";
|
|
897
|
+
throw new Error(`${label} API key is required. Set ${env} or pass apiKey in options.`);
|
|
898
|
+
}
|
|
899
|
+
return key;
|
|
900
|
+
}
|
|
579
901
|
async chat(messages, options) {
|
|
902
|
+
const headers = this.buildHeaders(await this.resolveKey());
|
|
580
903
|
const body = this.buildRequestBody(messages, options, false);
|
|
581
904
|
const res = await fetchWithRetry(
|
|
582
905
|
`${this.baseUrl}/chat/completions`,
|
|
583
906
|
{
|
|
584
907
|
method: "POST",
|
|
585
|
-
headers
|
|
586
|
-
"Content-Type": "application/json",
|
|
587
|
-
Authorization: `Bearer ${this.apiKey}`
|
|
588
|
-
},
|
|
908
|
+
headers,
|
|
589
909
|
body: JSON.stringify(body),
|
|
590
910
|
signal: options.signal
|
|
591
911
|
},
|
|
592
|
-
{ governor: this.governor }
|
|
912
|
+
{ governor: this.governor, provider: this.name }
|
|
593
913
|
);
|
|
594
914
|
if (!res.ok) {
|
|
595
915
|
const errorBody = await res.text();
|
|
596
|
-
|
|
597
|
-
|
|
916
|
+
throw buildProviderError({
|
|
917
|
+
provider: this.name,
|
|
918
|
+
status: res.status,
|
|
919
|
+
headers: res.headers,
|
|
920
|
+
message: this.extractErrorMessage(errorBody, res.status),
|
|
921
|
+
body: errorBody
|
|
922
|
+
});
|
|
598
923
|
}
|
|
599
924
|
const json = await res.json();
|
|
600
|
-
|
|
601
|
-
const usage = json.usage ? {
|
|
602
|
-
prompt_tokens: json.usage.prompt_tokens,
|
|
603
|
-
completion_tokens: json.usage.completion_tokens,
|
|
604
|
-
total_tokens: json.usage.total_tokens,
|
|
605
|
-
reasoning_tokens: json.usage.completion_tokens_details?.reasoning_tokens,
|
|
606
|
-
cached_tokens: json.usage.prompt_tokens_details?.cached_tokens
|
|
607
|
-
} : void 0;
|
|
608
|
-
const cost = usage ? estimateOpenAICost(
|
|
609
|
-
options.model,
|
|
610
|
-
usage.prompt_tokens,
|
|
611
|
-
usage.completion_tokens,
|
|
612
|
-
usage.cached_tokens
|
|
613
|
-
) : void 0;
|
|
614
|
-
return {
|
|
615
|
-
content: choice.message.content ?? "",
|
|
616
|
-
tool_calls: choice.message.tool_calls?.map((tc) => ({
|
|
617
|
-
id: tc.id,
|
|
618
|
-
type: "function",
|
|
619
|
-
function: {
|
|
620
|
-
name: tc.function.name,
|
|
621
|
-
arguments: tc.function.arguments
|
|
622
|
-
}
|
|
623
|
-
})),
|
|
624
|
-
usage,
|
|
625
|
-
cost
|
|
626
|
-
};
|
|
925
|
+
return this.parseResponse(json, options.model);
|
|
627
926
|
}
|
|
628
|
-
// ---------------------------------------------------------------------------
|
|
629
|
-
// stream - SSE streaming completion
|
|
630
|
-
// ---------------------------------------------------------------------------
|
|
631
927
|
async *stream(messages, options) {
|
|
928
|
+
const headers = this.buildHeaders(await this.resolveKey());
|
|
632
929
|
const body = this.buildRequestBody(messages, options, true);
|
|
633
930
|
const res = await fetchWithRetry(
|
|
634
931
|
`${this.baseUrl}/chat/completions`,
|
|
635
932
|
{
|
|
636
933
|
method: "POST",
|
|
637
|
-
headers
|
|
638
|
-
"Content-Type": "application/json",
|
|
639
|
-
Authorization: `Bearer ${this.apiKey}`
|
|
640
|
-
},
|
|
934
|
+
headers,
|
|
641
935
|
body: JSON.stringify(body),
|
|
642
936
|
signal: options.signal
|
|
643
937
|
},
|
|
644
|
-
{ governor: this.governor }
|
|
938
|
+
{ governor: this.governor, provider: this.name }
|
|
645
939
|
);
|
|
646
940
|
if (!res.ok) {
|
|
647
941
|
const errorBody = await res.text();
|
|
648
|
-
|
|
649
|
-
|
|
942
|
+
throw buildProviderError({
|
|
943
|
+
provider: this.name,
|
|
944
|
+
status: res.status,
|
|
945
|
+
headers: res.headers,
|
|
946
|
+
message: this.extractErrorMessage(errorBody, res.status),
|
|
947
|
+
body: errorBody
|
|
948
|
+
});
|
|
650
949
|
}
|
|
651
950
|
if (!res.body) {
|
|
652
|
-
throw new Error(
|
|
951
|
+
throw new Error(`${this.profile.label ?? this.profile.name} stream response has no body`);
|
|
653
952
|
}
|
|
654
953
|
yield* this.parseSSEStream(res.body, options.model);
|
|
655
954
|
}
|
|
656
955
|
// ---------------------------------------------------------------------------
|
|
657
|
-
//
|
|
956
|
+
// Request building
|
|
658
957
|
// ---------------------------------------------------------------------------
|
|
659
|
-
|
|
660
|
-
const
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
if (
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
} else if (thinkingDisabled) {
|
|
670
|
-
wireEffort = clampReasoningEffort(options.model, "none");
|
|
671
|
-
}
|
|
958
|
+
buildHeaders(apiKey) {
|
|
959
|
+
const headers = {
|
|
960
|
+
"Content-Type": "application/json",
|
|
961
|
+
...this.profile.headers ?? {}
|
|
962
|
+
};
|
|
963
|
+
if (apiKey) {
|
|
964
|
+
const auth = this.authHeader ?? this.profile.authHeader ?? "bearer";
|
|
965
|
+
if (auth === "bearer") headers.Authorization = `Bearer ${apiKey}`;
|
|
966
|
+
else if (auth === "api-key") headers["api-key"] = apiKey;
|
|
967
|
+
else headers[auth.header] = auth.scheme ? `${auth.scheme} ${apiKey}` : apiKey;
|
|
672
968
|
}
|
|
673
|
-
|
|
969
|
+
return headers;
|
|
970
|
+
}
|
|
971
|
+
buildRequestBody(messages, options, stream) {
|
|
972
|
+
const profile = this.profile;
|
|
973
|
+
const resolved = resolveThinkingOptions(options);
|
|
674
974
|
const body = {
|
|
675
975
|
model: options.model,
|
|
676
|
-
messages: messages.map((m) => this.formatMessage(m,
|
|
976
|
+
messages: messages.map((m) => this.formatMessage(m, options.model)),
|
|
677
977
|
stream
|
|
678
978
|
};
|
|
679
|
-
|
|
979
|
+
const emitResult = profile.reasoning.emit(body, resolved, options.model) ?? {};
|
|
980
|
+
const stripTemperature = emitResult.stripTemperature ?? false;
|
|
981
|
+
if (options.temperature !== void 0 && !stripTemperature) {
|
|
680
982
|
body.temperature = options.temperature;
|
|
681
983
|
}
|
|
682
984
|
if (options.maxTokens !== void 0) {
|
|
683
|
-
body.max_completion_tokens = options.maxTokens;
|
|
985
|
+
body[profile.maxTokensField ?? "max_completion_tokens"] = options.maxTokens;
|
|
684
986
|
}
|
|
685
987
|
if (options.stop) body.stop = options.stop;
|
|
686
988
|
if (options.tools && options.tools.length > 0) {
|
|
687
989
|
body.tools = options.tools;
|
|
688
|
-
if (
|
|
990
|
+
if (resolvePerModel(profile.parallelToolCalls, options.model, false)) {
|
|
689
991
|
body.parallel_tool_calls = true;
|
|
690
992
|
}
|
|
691
993
|
}
|
|
692
|
-
if (options.toolChoice !== void 0)
|
|
693
|
-
body.tool_choice = options.toolChoice;
|
|
694
|
-
}
|
|
994
|
+
if (options.toolChoice !== void 0) body.tool_choice = options.toolChoice;
|
|
695
995
|
if (options.responseFormat) {
|
|
696
|
-
|
|
996
|
+
const supportsSchema = resolvePerModel(
|
|
997
|
+
profile.capabilities?.supportsJsonSchema,
|
|
998
|
+
options.model,
|
|
999
|
+
true
|
|
1000
|
+
);
|
|
1001
|
+
body.response_format = options.responseFormat.type === "json_schema" && !supportsSchema ? { type: "json_object" } : options.responseFormat;
|
|
697
1002
|
}
|
|
698
|
-
if (
|
|
699
|
-
if (stream) {
|
|
1003
|
+
if (stream && (profile.capabilities?.supportsStreamUsage ?? true)) {
|
|
700
1004
|
body.stream_options = { include_usage: true };
|
|
701
1005
|
}
|
|
702
|
-
if (
|
|
703
|
-
|
|
1006
|
+
if (profile.requestDefaults) Object.assign(body, profile.requestDefaults);
|
|
1007
|
+
const userKeys = options.providerOptions ? Object.keys(options.providerOptions) : [];
|
|
1008
|
+
if (options.providerOptions) Object.assign(body, options.providerOptions);
|
|
1009
|
+
const forbidden = resolvePerModel(profile.capabilities?.forbiddenParams, options.model, []);
|
|
1010
|
+
for (const key of forbidden) {
|
|
1011
|
+
if (!userKeys.includes(key)) delete body[key];
|
|
704
1012
|
}
|
|
705
1013
|
return body;
|
|
706
1014
|
}
|
|
707
|
-
|
|
1015
|
+
formatMessage(msg, model) {
|
|
1016
|
+
const out = {
|
|
1017
|
+
role: this.profile.roleFor ? this.profile.roleFor(msg.role, model) : msg.role,
|
|
1018
|
+
content: msg.content
|
|
1019
|
+
};
|
|
1020
|
+
if (msg.name && (this.profile.capabilities?.emitsMessageName ?? true)) out.name = msg.name;
|
|
1021
|
+
if (msg.tool_calls) out.tool_calls = msg.tool_calls;
|
|
1022
|
+
if (msg.tool_call_id) out.tool_call_id = msg.tool_call_id;
|
|
1023
|
+
if (this.profile.reasoning.roundTrip === "on-tool-call-turns" && msg.role === "assistant" && msg.tool_calls && msg.tool_calls.length > 0) {
|
|
1024
|
+
const rt = msg.providerMetadata?.openaiCompatReasoning;
|
|
1025
|
+
if (rt && rt.provider === this.name && isRoundTripField(rt.field) && rt.value !== void 0 && rt.value !== null) {
|
|
1026
|
+
out[rt.field] = rt.value;
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
return out;
|
|
1030
|
+
}
|
|
1031
|
+
// ---------------------------------------------------------------------------
|
|
1032
|
+
// Response parsing
|
|
1033
|
+
// ---------------------------------------------------------------------------
|
|
708
1034
|
extractErrorMessage(body, status) {
|
|
1035
|
+
const label = this.profile.label ?? this.profile.name;
|
|
709
1036
|
try {
|
|
710
1037
|
const json = JSON.parse(body);
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
}
|
|
1038
|
+
const message = json.error?.message ?? json.message ?? json.detail;
|
|
1039
|
+
if (message) return `${label} API error (${status}): ${message}`;
|
|
714
1040
|
} catch {
|
|
715
1041
|
}
|
|
716
|
-
return
|
|
1042
|
+
return `${label} API error (${status}): ${body}`;
|
|
717
1043
|
}
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
1044
|
+
toUsage(raw) {
|
|
1045
|
+
if (!raw) return void 0;
|
|
1046
|
+
return {
|
|
1047
|
+
prompt_tokens: raw.prompt_tokens,
|
|
1048
|
+
completion_tokens: raw.completion_tokens,
|
|
1049
|
+
total_tokens: raw.total_tokens,
|
|
1050
|
+
reasoning_tokens: raw.completion_tokens_details?.reasoning_tokens,
|
|
1051
|
+
cached_tokens: raw.prompt_tokens_details?.cached_tokens
|
|
722
1052
|
};
|
|
723
|
-
if (msg.name) out.name = msg.name;
|
|
724
|
-
if (msg.tool_calls) out.tool_calls = msg.tool_calls;
|
|
725
|
-
if (msg.tool_call_id) out.tool_call_id = msg.tool_call_id;
|
|
726
|
-
return out;
|
|
727
1053
|
}
|
|
1054
|
+
computeCost(model, usage, reportedCost) {
|
|
1055
|
+
const p = this.profile.pricing;
|
|
1056
|
+
if (p.kind === "zero") return 0;
|
|
1057
|
+
if (!usage) return void 0;
|
|
1058
|
+
switch (p.kind) {
|
|
1059
|
+
case "unknown":
|
|
1060
|
+
return void 0;
|
|
1061
|
+
case "from-response":
|
|
1062
|
+
return typeof reportedCost === "number" && Number.isFinite(reportedCost) ? reportedCost : void 0;
|
|
1063
|
+
case "table": {
|
|
1064
|
+
const c = priceFromTable(
|
|
1065
|
+
p.table,
|
|
1066
|
+
model,
|
|
1067
|
+
usage.prompt_tokens,
|
|
1068
|
+
usage.completion_tokens,
|
|
1069
|
+
usage.cached_tokens
|
|
1070
|
+
);
|
|
1071
|
+
return c !== void 0 && Number.isFinite(c) ? c : void 0;
|
|
1072
|
+
}
|
|
1073
|
+
default: {
|
|
1074
|
+
const _exhaustive = p;
|
|
1075
|
+
throw new Error(`Unhandled pricing source: ${JSON.stringify(_exhaustive)}`);
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
parseResponse(json, model) {
|
|
1080
|
+
const message = json.choices?.[0]?.message ?? {};
|
|
1081
|
+
let content = normalizeMessageContent(message.content);
|
|
1082
|
+
let thinking;
|
|
1083
|
+
let roundTrip;
|
|
1084
|
+
switch (this.profile.reasoning.capture) {
|
|
1085
|
+
case "reasoning_content":
|
|
1086
|
+
if (typeof message.reasoning_content === "string") {
|
|
1087
|
+
thinking = message.reasoning_content;
|
|
1088
|
+
roundTrip = {
|
|
1089
|
+
provider: this.name,
|
|
1090
|
+
field: "reasoning_content",
|
|
1091
|
+
value: message.reasoning_content
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
break;
|
|
1095
|
+
case "reasoning":
|
|
1096
|
+
if (typeof message.reasoning === "string") {
|
|
1097
|
+
thinking = message.reasoning;
|
|
1098
|
+
roundTrip = { provider: this.name, field: "reasoning", value: message.reasoning };
|
|
1099
|
+
}
|
|
1100
|
+
break;
|
|
1101
|
+
case "reasoning_details":
|
|
1102
|
+
if (typeof message.reasoning === "string") thinking = message.reasoning;
|
|
1103
|
+
if (message.reasoning_details !== void 0) {
|
|
1104
|
+
roundTrip = {
|
|
1105
|
+
provider: this.name,
|
|
1106
|
+
field: "reasoning_details",
|
|
1107
|
+
value: message.reasoning_details
|
|
1108
|
+
};
|
|
1109
|
+
}
|
|
1110
|
+
break;
|
|
1111
|
+
case "think_tags": {
|
|
1112
|
+
const ex = extractThinkTags(content);
|
|
1113
|
+
content = ex.content;
|
|
1114
|
+
if (ex.thinking) thinking = ex.thinking;
|
|
1115
|
+
break;
|
|
1116
|
+
}
|
|
1117
|
+
case "none":
|
|
1118
|
+
break;
|
|
1119
|
+
default: {
|
|
1120
|
+
const _exhaustive = this.profile.reasoning.capture;
|
|
1121
|
+
throw new Error(`Unhandled reasoning capture: ${String(_exhaustive)}`);
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
const usage = this.toUsage(json.usage);
|
|
1125
|
+
const cost = this.computeCost(model, usage, json.usage?.cost);
|
|
1126
|
+
return {
|
|
1127
|
+
content,
|
|
1128
|
+
thinking_content: thinking || void 0,
|
|
1129
|
+
tool_calls: message.tool_calls?.map((tc) => ({
|
|
1130
|
+
id: tc.id,
|
|
1131
|
+
type: "function",
|
|
1132
|
+
function: { name: tc.function.name, arguments: tc.function.arguments }
|
|
1133
|
+
})),
|
|
1134
|
+
usage,
|
|
1135
|
+
cost,
|
|
1136
|
+
providerMetadata: this.roundTripMetadata(roundTrip)
|
|
1137
|
+
};
|
|
1138
|
+
}
|
|
1139
|
+
roundTripMetadata(rt) {
|
|
1140
|
+
if (!rt || this.profile.reasoning.roundTrip === void 0 || this.profile.reasoning.roundTrip === "none") {
|
|
1141
|
+
return void 0;
|
|
1142
|
+
}
|
|
1143
|
+
return rt.value === void 0 || rt.value === null ? void 0 : { openaiCompatReasoning: rt };
|
|
1144
|
+
}
|
|
1145
|
+
// ---------------------------------------------------------------------------
|
|
1146
|
+
// SSE streaming
|
|
1147
|
+
// ---------------------------------------------------------------------------
|
|
728
1148
|
async *parseSSEStream(body, model) {
|
|
729
1149
|
const reader = body.getReader();
|
|
730
1150
|
const decoder = new TextDecoder();
|
|
731
1151
|
let buffer = "";
|
|
732
1152
|
let usageData;
|
|
1153
|
+
const capture = this.profile.reasoning.capture;
|
|
1154
|
+
const thinkScanner = capture === "think_tags" ? new ThinkTagScanner() : void 0;
|
|
1155
|
+
let reasoningText = "";
|
|
1156
|
+
const reasoningDetails = [];
|
|
733
1157
|
const indexToId = /* @__PURE__ */ new Map();
|
|
1158
|
+
const pendingToolCalls = /* @__PURE__ */ new Map();
|
|
1159
|
+
const flushPendingToolCalls = () => {
|
|
1160
|
+
const out = [];
|
|
1161
|
+
for (const [index, buf] of pendingToolCalls) {
|
|
1162
|
+
out.push({
|
|
1163
|
+
type: "tool_call_delta",
|
|
1164
|
+
id: `__toolcall_${index}`,
|
|
1165
|
+
name: buf.name,
|
|
1166
|
+
arguments: buf.arguments
|
|
1167
|
+
});
|
|
1168
|
+
}
|
|
1169
|
+
pendingToolCalls.clear();
|
|
1170
|
+
return out;
|
|
1171
|
+
};
|
|
1172
|
+
const makeDone = () => {
|
|
1173
|
+
const usage = this.toUsage(usageData);
|
|
1174
|
+
let providerMetadata;
|
|
1175
|
+
if (this.profile.reasoning.roundTrip === "on-tool-call-turns") {
|
|
1176
|
+
if (capture === "reasoning_details" && reasoningDetails.length > 0) {
|
|
1177
|
+
providerMetadata = {
|
|
1178
|
+
openaiCompatReasoning: {
|
|
1179
|
+
provider: this.name,
|
|
1180
|
+
field: "reasoning_details",
|
|
1181
|
+
value: reasoningDetails
|
|
1182
|
+
}
|
|
1183
|
+
};
|
|
1184
|
+
} else if ((capture === "reasoning_content" || capture === "reasoning") && reasoningText) {
|
|
1185
|
+
providerMetadata = {
|
|
1186
|
+
openaiCompatReasoning: { provider: this.name, field: capture, value: reasoningText }
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
return {
|
|
1191
|
+
type: "done",
|
|
1192
|
+
usage,
|
|
1193
|
+
cost: this.computeCost(model, usage, usageData?.cost),
|
|
1194
|
+
providerMetadata
|
|
1195
|
+
};
|
|
1196
|
+
};
|
|
734
1197
|
try {
|
|
735
|
-
|
|
1198
|
+
for (; ; ) {
|
|
736
1199
|
const { done, value } = await reader.read();
|
|
737
1200
|
if (done) break;
|
|
738
1201
|
buffer += decoder.decode(value, { stream: true });
|
|
@@ -742,47 +1205,76 @@ var OpenAIProvider = class {
|
|
|
742
1205
|
const trimmed = line.trim();
|
|
743
1206
|
if (!trimmed || trimmed.startsWith(":")) continue;
|
|
744
1207
|
if (trimmed === "data: [DONE]") {
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
usageData.cached_tokens
|
|
753
|
-
) : void 0
|
|
754
|
-
};
|
|
1208
|
+
if (thinkScanner) {
|
|
1209
|
+
const flushed = thinkScanner.flush();
|
|
1210
|
+
if (flushed.text) yield { type: "text_delta", content: flushed.text };
|
|
1211
|
+
if (flushed.thinking) yield { type: "thinking_delta", content: flushed.thinking };
|
|
1212
|
+
}
|
|
1213
|
+
yield* flushPendingToolCalls();
|
|
1214
|
+
yield makeDone();
|
|
755
1215
|
return;
|
|
756
1216
|
}
|
|
757
|
-
if (trimmed.startsWith("data: "))
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
if (!
|
|
777
|
-
|
|
1217
|
+
if (!trimmed.startsWith("data: ")) continue;
|
|
1218
|
+
let parsed;
|
|
1219
|
+
try {
|
|
1220
|
+
parsed = JSON.parse(trimmed.slice(6));
|
|
1221
|
+
} catch {
|
|
1222
|
+
continue;
|
|
1223
|
+
}
|
|
1224
|
+
if (parsed.error) {
|
|
1225
|
+
const message = typeof parsed.error.message === "string" ? parsed.error.message : `${this.profile.label ?? this.profile.name} stream error`;
|
|
1226
|
+
throw new ProviderError({
|
|
1227
|
+
provider: this.name,
|
|
1228
|
+
status: 0,
|
|
1229
|
+
retryable: false,
|
|
1230
|
+
message,
|
|
1231
|
+
body: JSON.stringify(parsed.error)
|
|
1232
|
+
});
|
|
1233
|
+
}
|
|
1234
|
+
if (parsed.usage) {
|
|
1235
|
+
usageData = parsed.usage;
|
|
1236
|
+
if (!parsed.choices || parsed.choices.length === 0) continue;
|
|
1237
|
+
}
|
|
1238
|
+
const delta = parsed.choices?.[0]?.delta;
|
|
1239
|
+
if (!delta) continue;
|
|
1240
|
+
const deltaReasoning = capture === "reasoning_content" ? delta.reasoning_content : capture === "reasoning" || capture === "reasoning_details" ? delta.reasoning : void 0;
|
|
1241
|
+
if (typeof deltaReasoning === "string" && deltaReasoning.length > 0) {
|
|
1242
|
+
reasoningText += deltaReasoning;
|
|
1243
|
+
yield { type: "thinking_delta", content: deltaReasoning };
|
|
1244
|
+
}
|
|
1245
|
+
if (capture === "reasoning_details" && Array.isArray(delta.reasoning_details)) {
|
|
1246
|
+
reasoningDetails.push(...delta.reasoning_details);
|
|
1247
|
+
}
|
|
1248
|
+
if (typeof delta.content === "string" && delta.content.length > 0) {
|
|
1249
|
+
if (thinkScanner) {
|
|
1250
|
+
const { text, thinking } = thinkScanner.push(delta.content);
|
|
1251
|
+
if (text) yield { type: "text_delta", content: text };
|
|
1252
|
+
if (thinking) yield { type: "thinking_delta", content: thinking };
|
|
1253
|
+
} else {
|
|
778
1254
|
yield { type: "text_delta", content: delta.content };
|
|
779
1255
|
}
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
1256
|
+
}
|
|
1257
|
+
if (delta.tool_calls) {
|
|
1258
|
+
for (const tc of delta.tool_calls) {
|
|
1259
|
+
if (tc.id) indexToId.set(tc.index, tc.id);
|
|
1260
|
+
const id = indexToId.get(tc.index);
|
|
1261
|
+
if (id === void 0) {
|
|
1262
|
+
const buf2 = pendingToolCalls.get(tc.index) ?? { arguments: "" };
|
|
1263
|
+
if (tc.function?.name) buf2.name = tc.function.name;
|
|
1264
|
+
if (tc.function?.arguments) buf2.arguments += tc.function.arguments;
|
|
1265
|
+
pendingToolCalls.set(tc.index, buf2);
|
|
1266
|
+
continue;
|
|
1267
|
+
}
|
|
1268
|
+
const buf = pendingToolCalls.get(tc.index);
|
|
1269
|
+
if (buf) {
|
|
1270
|
+
pendingToolCalls.delete(tc.index);
|
|
1271
|
+
yield {
|
|
1272
|
+
type: "tool_call_delta",
|
|
1273
|
+
id,
|
|
1274
|
+
name: buf.name ?? tc.function?.name,
|
|
1275
|
+
arguments: buf.arguments + (tc.function?.arguments ?? "")
|
|
1276
|
+
};
|
|
1277
|
+
} else {
|
|
786
1278
|
yield {
|
|
787
1279
|
type: "tool_call_delta",
|
|
788
1280
|
id,
|
|
@@ -794,58 +1286,176 @@ var OpenAIProvider = class {
|
|
|
794
1286
|
}
|
|
795
1287
|
}
|
|
796
1288
|
}
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
1289
|
+
if (thinkScanner) {
|
|
1290
|
+
const flushed = thinkScanner.flush();
|
|
1291
|
+
if (flushed.text) yield { type: "text_delta", content: flushed.text };
|
|
1292
|
+
if (flushed.thinking) yield { type: "thinking_delta", content: flushed.thinking };
|
|
1293
|
+
}
|
|
1294
|
+
yield* flushPendingToolCalls();
|
|
1295
|
+
throw new ProviderError({
|
|
1296
|
+
provider: this.name,
|
|
1297
|
+
status: 0,
|
|
1298
|
+
retryable: true,
|
|
1299
|
+
message: `${this.profile.label ?? this.profile.name} stream ended before [DONE]`
|
|
1300
|
+
});
|
|
807
1301
|
} finally {
|
|
808
1302
|
reader.releaseLock();
|
|
809
1303
|
}
|
|
810
1304
|
}
|
|
811
1305
|
};
|
|
812
1306
|
|
|
1307
|
+
// src/providers/openai.ts
|
|
1308
|
+
var OPENAI_PRICING = {
|
|
1309
|
+
// gpt-4o era — cache reads at 50% of input rate
|
|
1310
|
+
"gpt-4o": [25e-7, 1e-5, 0.5],
|
|
1311
|
+
"gpt-4o-mini": [15e-8, 6e-7, 0.5],
|
|
1312
|
+
"gpt-4-turbo": [1e-5, 3e-5, 0.5],
|
|
1313
|
+
"gpt-4": [3e-5, 6e-5, 0.5],
|
|
1314
|
+
"gpt-3.5-turbo": [5e-7, 15e-7, 0.5],
|
|
1315
|
+
o1: [15e-6, 6e-5, 0.5],
|
|
1316
|
+
"o1-mini": [3e-6, 12e-6, 0.5],
|
|
1317
|
+
"o1-pro": [15e-5, 6e-4, 0.5],
|
|
1318
|
+
// gpt-4.1 / o3 / o4 era — cache reads at 25% of input rate
|
|
1319
|
+
"gpt-4.1": [2e-6, 8e-6, 0.25],
|
|
1320
|
+
"gpt-4.1-mini": [4e-7, 16e-7, 0.25],
|
|
1321
|
+
"gpt-4.1-nano": [1e-7, 4e-7, 0.25],
|
|
1322
|
+
o3: [1e-5, 4e-5, 0.25],
|
|
1323
|
+
"o3-mini": [11e-7, 44e-7, 0.25],
|
|
1324
|
+
"o3-pro": [2e-5, 8e-5, 0.25],
|
|
1325
|
+
"o4-mini": [11e-7, 44e-7, 0.25],
|
|
1326
|
+
// gpt-5 era — cache reads at 10% of input rate
|
|
1327
|
+
"gpt-5": [125e-8, 1e-5, 0.1],
|
|
1328
|
+
"gpt-5-mini": [25e-8, 2e-6, 0.1],
|
|
1329
|
+
"gpt-5-nano": [5e-8, 4e-7, 0.1],
|
|
1330
|
+
"gpt-5.1": [125e-8, 1e-5, 0.1],
|
|
1331
|
+
"gpt-5.2": [175e-8, 14e-6, 0.1],
|
|
1332
|
+
"gpt-5.3": [175e-8, 14e-6, 0.1],
|
|
1333
|
+
"gpt-5.4": [25e-7, 15e-6, 0.1],
|
|
1334
|
+
"gpt-5.4-pro": [3e-5, 18e-5, 0.1],
|
|
1335
|
+
// gpt-5.5 era (flagship released 2026-04) — cache reads at 10% of input rate
|
|
1336
|
+
"gpt-5.5": [5e-6, 3e-5, 0.1],
|
|
1337
|
+
"gpt-5.5-pro": [3e-5, 18e-5, 0.1]
|
|
1338
|
+
};
|
|
1339
|
+
function estimateOpenAICost(model, promptTokens, completionTokens, cachedTokens) {
|
|
1340
|
+
return priceFromTable(OPENAI_PRICING, model, promptTokens, completionTokens, cachedTokens);
|
|
1341
|
+
}
|
|
1342
|
+
function isOSeriesModel(model) {
|
|
1343
|
+
return /^(o1|o3|o4-mini)/.test(model);
|
|
1344
|
+
}
|
|
1345
|
+
function supportsReasoningEffort(model) {
|
|
1346
|
+
return isOSeriesModel(model) || /^gpt-5/.test(model);
|
|
1347
|
+
}
|
|
1348
|
+
function supportsReasoningNone(model) {
|
|
1349
|
+
return /^gpt-5\.[1-9]/.test(model);
|
|
1350
|
+
}
|
|
1351
|
+
function supportsXhigh(model) {
|
|
1352
|
+
return /^gpt-5\.([2-9]|\d{2,})/.test(model);
|
|
1353
|
+
}
|
|
1354
|
+
function clampReasoningEffort(model, effort) {
|
|
1355
|
+
if (model.startsWith("gpt-5-pro")) return "high";
|
|
1356
|
+
if (effort === "none" && !supportsReasoningNone(model)) return "minimal";
|
|
1357
|
+
if (effort === "xhigh" && !supportsXhigh(model)) return "high";
|
|
1358
|
+
return effort;
|
|
1359
|
+
}
|
|
1360
|
+
function effortToReasoningEffort(effort) {
|
|
1361
|
+
return effort === "max" ? "xhigh" : effort;
|
|
1362
|
+
}
|
|
1363
|
+
function budgetToReasoningEffort(budget) {
|
|
1364
|
+
if (budget <= 1024) return "low";
|
|
1365
|
+
if (budget <= 8192) return "medium";
|
|
1366
|
+
return "high";
|
|
1367
|
+
}
|
|
1368
|
+
var openaiReasoningEmit = (body, resolved, model) => {
|
|
1369
|
+
const oSeries = isOSeriesModel(model);
|
|
1370
|
+
const reasoningCapable = supportsReasoningEffort(model);
|
|
1371
|
+
const { thinkingBudget, thinkingDisabled, activeEffort, hasBudgetOverride } = resolved;
|
|
1372
|
+
let wireEffort;
|
|
1373
|
+
if (reasoningCapable) {
|
|
1374
|
+
if (hasBudgetOverride) {
|
|
1375
|
+
wireEffort = clampReasoningEffort(model, budgetToReasoningEffort(thinkingBudget));
|
|
1376
|
+
} else if (!thinkingDisabled && activeEffort) {
|
|
1377
|
+
wireEffort = clampReasoningEffort(model, effortToReasoningEffort(activeEffort));
|
|
1378
|
+
} else if (thinkingDisabled) {
|
|
1379
|
+
wireEffort = clampReasoningEffort(model, "none");
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
if (wireEffort) body.reasoning_effort = wireEffort;
|
|
1383
|
+
return { stripTemperature: oSeries || reasoningCapable && wireEffort !== void 0 };
|
|
1384
|
+
};
|
|
1385
|
+
var OPENAI_PROFILE = {
|
|
1386
|
+
name: "openai",
|
|
1387
|
+
label: "OpenAI",
|
|
1388
|
+
defaultBaseUrl: "https://api.openai.com/v1",
|
|
1389
|
+
envApiKey: "OPENAI_API_KEY",
|
|
1390
|
+
envBaseUrl: "OPENAI_BASE_URL",
|
|
1391
|
+
pricing: { kind: "table", table: OPENAI_PRICING },
|
|
1392
|
+
reasoning: { emit: openaiReasoningEmit, capture: "none" },
|
|
1393
|
+
roleFor: (role, model) => role === "system" && isOSeriesModel(model) ? "developer" : role,
|
|
1394
|
+
maxTokensField: "max_completion_tokens",
|
|
1395
|
+
parallelToolCalls: (model) => !isOSeriesModel(model)
|
|
1396
|
+
};
|
|
1397
|
+
var OpenAIProvider = class extends OpenAICompatibleProvider {
|
|
1398
|
+
constructor(options = {}) {
|
|
1399
|
+
super({ profile: OPENAI_PROFILE, ...options });
|
|
1400
|
+
}
|
|
1401
|
+
};
|
|
1402
|
+
|
|
813
1403
|
// src/providers/openai-responses.ts
|
|
814
1404
|
var OpenAIResponsesProvider = class {
|
|
815
1405
|
name = "openai-responses";
|
|
1406
|
+
/** The Responses API honors `json_schema` (structured outputs) natively. */
|
|
1407
|
+
nativeStructuredOutputSupport() {
|
|
1408
|
+
return "schema";
|
|
1409
|
+
}
|
|
816
1410
|
baseUrl;
|
|
817
|
-
|
|
1411
|
+
apiKeySource;
|
|
818
1412
|
governor;
|
|
819
1413
|
constructor(options = {}) {
|
|
820
|
-
this.
|
|
1414
|
+
this.apiKeySource = options.apiKey ?? process.env.OPENAI_API_KEY ?? "";
|
|
821
1415
|
this.baseUrl = (options.baseUrl ?? process.env.OPENAI_BASE_URL ?? "https://api.openai.com/v1").replace(/\/$/, "");
|
|
822
1416
|
this.governor = options.rateLimit ? new RateLimiter(options.rateLimit) : void 0;
|
|
823
|
-
if (!this.
|
|
1417
|
+
if (typeof this.apiKeySource === "string" && !this.apiKeySource) {
|
|
824
1418
|
throw new Error("OpenAI API key is required. Set OPENAI_API_KEY or pass apiKey in options.");
|
|
825
1419
|
}
|
|
826
1420
|
}
|
|
1421
|
+
/** Resolve the API key for one request (supports an expiring-token callback). */
|
|
1422
|
+
async resolveKey() {
|
|
1423
|
+
const key = await resolveApiKey(this.apiKeySource);
|
|
1424
|
+
if (!key) {
|
|
1425
|
+
throw new Error("OpenAI API key is required. Set OPENAI_API_KEY or pass apiKey in options.");
|
|
1426
|
+
}
|
|
1427
|
+
return key;
|
|
1428
|
+
}
|
|
1429
|
+
/** Build request headers from a resolved key (used by both chat and stream). */
|
|
1430
|
+
buildHeaders(apiKey) {
|
|
1431
|
+
return { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` };
|
|
1432
|
+
}
|
|
827
1433
|
// ---------------------------------------------------------------------------
|
|
828
1434
|
// chat - non-streaming
|
|
829
1435
|
// ---------------------------------------------------------------------------
|
|
830
1436
|
async chat(messages, options) {
|
|
1437
|
+
const headers = this.buildHeaders(await this.resolveKey());
|
|
831
1438
|
const body = this.buildRequestBody(messages, options, false);
|
|
832
1439
|
const res = await fetchWithRetry(
|
|
833
1440
|
`${this.baseUrl}/responses`,
|
|
834
1441
|
{
|
|
835
1442
|
method: "POST",
|
|
836
|
-
headers
|
|
837
|
-
"Content-Type": "application/json",
|
|
838
|
-
Authorization: `Bearer ${this.apiKey}`
|
|
839
|
-
},
|
|
1443
|
+
headers,
|
|
840
1444
|
body: JSON.stringify(body),
|
|
841
1445
|
signal: options.signal
|
|
842
1446
|
},
|
|
843
|
-
{ governor: this.governor }
|
|
1447
|
+
{ governor: this.governor, provider: this.name }
|
|
844
1448
|
);
|
|
845
1449
|
if (!res.ok) {
|
|
846
1450
|
const errorBody = await res.text();
|
|
847
1451
|
const message = this.extractErrorMessage(errorBody, res.status);
|
|
848
|
-
throw
|
|
1452
|
+
throw buildProviderError({
|
|
1453
|
+
provider: this.name,
|
|
1454
|
+
status: res.status,
|
|
1455
|
+
headers: res.headers,
|
|
1456
|
+
message,
|
|
1457
|
+
body: errorBody
|
|
1458
|
+
});
|
|
849
1459
|
}
|
|
850
1460
|
const json = await res.json();
|
|
851
1461
|
return this.parseResponse(json, options.model);
|
|
@@ -854,24 +1464,28 @@ var OpenAIResponsesProvider = class {
|
|
|
854
1464
|
// stream - SSE streaming
|
|
855
1465
|
// ---------------------------------------------------------------------------
|
|
856
1466
|
async *stream(messages, options) {
|
|
1467
|
+
const headers = this.buildHeaders(await this.resolveKey());
|
|
857
1468
|
const body = this.buildRequestBody(messages, options, true);
|
|
858
1469
|
const res = await fetchWithRetry(
|
|
859
1470
|
`${this.baseUrl}/responses`,
|
|
860
1471
|
{
|
|
861
1472
|
method: "POST",
|
|
862
|
-
headers
|
|
863
|
-
"Content-Type": "application/json",
|
|
864
|
-
Authorization: `Bearer ${this.apiKey}`
|
|
865
|
-
},
|
|
1473
|
+
headers,
|
|
866
1474
|
body: JSON.stringify(body),
|
|
867
1475
|
signal: options.signal
|
|
868
1476
|
},
|
|
869
|
-
{ governor: this.governor }
|
|
1477
|
+
{ governor: this.governor, provider: this.name }
|
|
870
1478
|
);
|
|
871
1479
|
if (!res.ok) {
|
|
872
1480
|
const errorBody = await res.text();
|
|
873
1481
|
const message = this.extractErrorMessage(errorBody, res.status);
|
|
874
|
-
throw
|
|
1482
|
+
throw buildProviderError({
|
|
1483
|
+
provider: this.name,
|
|
1484
|
+
status: res.status,
|
|
1485
|
+
headers: res.headers,
|
|
1486
|
+
message,
|
|
1487
|
+
body: errorBody
|
|
1488
|
+
});
|
|
875
1489
|
}
|
|
876
1490
|
if (!res.body) {
|
|
877
1491
|
throw new Error("OpenAI Responses stream has no body");
|
|
@@ -1160,7 +1774,12 @@ var OpenAIResponsesProvider = class {
|
|
|
1160
1774
|
}
|
|
1161
1775
|
case "response.failed": {
|
|
1162
1776
|
const errorMsg = data.response?.error?.message ?? data.response?.status_details?.error?.message ?? "Unknown error";
|
|
1163
|
-
throw new
|
|
1777
|
+
throw new ProviderError({
|
|
1778
|
+
provider: this.name,
|
|
1779
|
+
status: 0,
|
|
1780
|
+
retryable: false,
|
|
1781
|
+
message: `OpenAI Responses API error: ${errorMsg}`
|
|
1782
|
+
});
|
|
1164
1783
|
}
|
|
1165
1784
|
default:
|
|
1166
1785
|
return null;
|
|
@@ -1214,7 +1833,7 @@ function estimateAnthropicCost(model, inputTokens, outputTokens, cacheReadTokens
|
|
|
1214
1833
|
}
|
|
1215
1834
|
}
|
|
1216
1835
|
}
|
|
1217
|
-
if (!pricing) return 0;
|
|
1836
|
+
if (!pricing) return void 0;
|
|
1218
1837
|
const [inputRate, outputRate] = pricing;
|
|
1219
1838
|
const cacheRead = cacheReadTokens ?? 0;
|
|
1220
1839
|
const cacheWrite = cacheWriteTokens ?? 0;
|
|
@@ -1248,63 +1867,90 @@ function clampAnthropicEffort(model, effort) {
|
|
|
1248
1867
|
}
|
|
1249
1868
|
var AnthropicProvider = class {
|
|
1250
1869
|
name = "anthropic";
|
|
1870
|
+
/** Anthropic ignores native `json_schema` structurally — Axl uses a system
|
|
1871
|
+
* prompt JSON instruction + client-side Zod validation (see buildRequest). */
|
|
1872
|
+
nativeStructuredOutputSupport() {
|
|
1873
|
+
return "unsupported";
|
|
1874
|
+
}
|
|
1251
1875
|
baseUrl;
|
|
1252
|
-
|
|
1253
|
-
currentModel;
|
|
1876
|
+
apiKeySource;
|
|
1254
1877
|
governor;
|
|
1255
1878
|
constructor(options = {}) {
|
|
1256
|
-
this.
|
|
1879
|
+
this.apiKeySource = options.apiKey ?? process.env.ANTHROPIC_API_KEY ?? "";
|
|
1257
1880
|
this.baseUrl = (options.baseUrl ?? "https://api.anthropic.com/v1").replace(/\/$/, "");
|
|
1258
1881
|
this.governor = options.rateLimit ? new RateLimiter(options.rateLimit) : void 0;
|
|
1259
|
-
if (!this.
|
|
1882
|
+
if (typeof this.apiKeySource === "string" && !this.apiKeySource) {
|
|
1883
|
+
throw new Error(
|
|
1884
|
+
"Anthropic API key is required. Set ANTHROPIC_API_KEY or pass apiKey in options."
|
|
1885
|
+
);
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
/** Resolve the API key for one request (supports an expiring-token callback). */
|
|
1889
|
+
async resolveKey() {
|
|
1890
|
+
const key = await resolveApiKey(this.apiKeySource);
|
|
1891
|
+
if (!key) {
|
|
1260
1892
|
throw new Error(
|
|
1261
1893
|
"Anthropic API key is required. Set ANTHROPIC_API_KEY or pass apiKey in options."
|
|
1262
1894
|
);
|
|
1263
1895
|
}
|
|
1896
|
+
return key;
|
|
1264
1897
|
}
|
|
1265
1898
|
// ---------------------------------------------------------------------------
|
|
1266
1899
|
// chat - non-streaming completion
|
|
1267
1900
|
// ---------------------------------------------------------------------------
|
|
1268
1901
|
async chat(messages, options) {
|
|
1269
|
-
|
|
1902
|
+
const headers = this.buildHeaders(await this.resolveKey());
|
|
1270
1903
|
const body = this.buildRequestBody(messages, options, false);
|
|
1271
1904
|
const res = await fetchWithRetry(
|
|
1272
1905
|
`${this.baseUrl}/messages`,
|
|
1273
1906
|
{
|
|
1274
1907
|
method: "POST",
|
|
1275
|
-
headers
|
|
1908
|
+
headers,
|
|
1276
1909
|
body: JSON.stringify(body),
|
|
1277
1910
|
signal: options.signal
|
|
1278
1911
|
},
|
|
1279
|
-
{ governor: this.governor }
|
|
1912
|
+
{ governor: this.governor, provider: this.name }
|
|
1280
1913
|
);
|
|
1281
1914
|
if (!res.ok) {
|
|
1282
1915
|
const errorBody = await res.text();
|
|
1283
1916
|
const message = this.extractErrorMessage(errorBody, res.status);
|
|
1284
|
-
throw
|
|
1917
|
+
throw buildProviderError({
|
|
1918
|
+
provider: this.name,
|
|
1919
|
+
status: res.status,
|
|
1920
|
+
headers: res.headers,
|
|
1921
|
+
message,
|
|
1922
|
+
body: errorBody
|
|
1923
|
+
});
|
|
1285
1924
|
}
|
|
1286
1925
|
const json = await res.json();
|
|
1287
|
-
return this.parseResponse(json);
|
|
1926
|
+
return this.parseResponse(json, options.model);
|
|
1288
1927
|
}
|
|
1289
1928
|
// ---------------------------------------------------------------------------
|
|
1290
1929
|
// stream - SSE streaming completion
|
|
1291
1930
|
// ---------------------------------------------------------------------------
|
|
1292
1931
|
async *stream(messages, options) {
|
|
1932
|
+
const headers = this.buildHeaders(await this.resolveKey());
|
|
1293
1933
|
const body = this.buildRequestBody(messages, options, true);
|
|
1294
1934
|
const res = await fetchWithRetry(
|
|
1295
1935
|
`${this.baseUrl}/messages`,
|
|
1296
1936
|
{
|
|
1297
1937
|
method: "POST",
|
|
1298
|
-
headers
|
|
1938
|
+
headers,
|
|
1299
1939
|
body: JSON.stringify(body),
|
|
1300
1940
|
signal: options.signal
|
|
1301
1941
|
},
|
|
1302
|
-
{ governor: this.governor }
|
|
1942
|
+
{ governor: this.governor, provider: this.name }
|
|
1303
1943
|
);
|
|
1304
1944
|
if (!res.ok) {
|
|
1305
1945
|
const errorBody = await res.text();
|
|
1306
1946
|
const message = this.extractErrorMessage(errorBody, res.status);
|
|
1307
|
-
throw
|
|
1947
|
+
throw buildProviderError({
|
|
1948
|
+
provider: this.name,
|
|
1949
|
+
status: res.status,
|
|
1950
|
+
headers: res.headers,
|
|
1951
|
+
message,
|
|
1952
|
+
body: errorBody
|
|
1953
|
+
});
|
|
1308
1954
|
}
|
|
1309
1955
|
if (!res.body) {
|
|
1310
1956
|
throw new Error("Anthropic stream response has no body");
|
|
@@ -1314,10 +1960,10 @@ var AnthropicProvider = class {
|
|
|
1314
1960
|
// ---------------------------------------------------------------------------
|
|
1315
1961
|
// Internal: request building
|
|
1316
1962
|
// ---------------------------------------------------------------------------
|
|
1317
|
-
buildHeaders() {
|
|
1963
|
+
buildHeaders(apiKey) {
|
|
1318
1964
|
return {
|
|
1319
1965
|
"Content-Type": "application/json",
|
|
1320
|
-
"x-api-key":
|
|
1966
|
+
"x-api-key": apiKey,
|
|
1321
1967
|
"anthropic-version": ANTHROPIC_API_VERSION
|
|
1322
1968
|
};
|
|
1323
1969
|
}
|
|
@@ -1502,7 +2148,7 @@ ${jsonInstruction}` : jsonInstruction;
|
|
|
1502
2148
|
// ---------------------------------------------------------------------------
|
|
1503
2149
|
// Internal: response parsing
|
|
1504
2150
|
// ---------------------------------------------------------------------------
|
|
1505
|
-
parseResponse(json) {
|
|
2151
|
+
parseResponse(json, model) {
|
|
1506
2152
|
let content = "";
|
|
1507
2153
|
let thinkingContent = "";
|
|
1508
2154
|
const toolCalls = [];
|
|
@@ -1531,13 +2177,7 @@ ${jsonInstruction}` : jsonInstruction;
|
|
|
1531
2177
|
total_tokens: totalInput + json.usage.output_tokens,
|
|
1532
2178
|
cached_tokens: cacheRead > 0 ? cacheRead : void 0
|
|
1533
2179
|
} : void 0;
|
|
1534
|
-
const cost = json.usage ? estimateAnthropicCost(
|
|
1535
|
-
this.currentModel ?? "",
|
|
1536
|
-
totalInput,
|
|
1537
|
-
json.usage.output_tokens,
|
|
1538
|
-
cacheRead,
|
|
1539
|
-
cacheWrite
|
|
1540
|
-
) : void 0;
|
|
2180
|
+
const cost = json.usage ? estimateAnthropicCost(model, totalInput, json.usage.output_tokens, cacheRead, cacheWrite) : void 0;
|
|
1541
2181
|
return {
|
|
1542
2182
|
content,
|
|
1543
2183
|
thinking_content: thinkingContent || void 0,
|
|
@@ -1739,7 +2379,7 @@ function estimateGeminiCost(model, inputTokens, outputTokens, cachedTokens) {
|
|
|
1739
2379
|
}
|
|
1740
2380
|
}
|
|
1741
2381
|
}
|
|
1742
|
-
if (!pricing) return 0;
|
|
2382
|
+
if (!pricing) return void 0;
|
|
1743
2383
|
const [inputRate, outputRate] = pricing;
|
|
1744
2384
|
const cached = cachedTokens ?? 0;
|
|
1745
2385
|
const inputCost = (inputTokens - cached) * inputRate + cached * inputRate * 0.1;
|
|
@@ -1784,40 +2424,61 @@ function warnGemini3xEffortNone(model) {
|
|
|
1784
2424
|
}
|
|
1785
2425
|
var GeminiProvider = class {
|
|
1786
2426
|
name = "google";
|
|
2427
|
+
/** Gemini accepts a `responseSchema` but `sanitizeSchemaForGemini` strips
|
|
2428
|
+
* keywords it doesn't support (`$ref`/`$defs`/`additionalProperties`/…), so a
|
|
2429
|
+
* derived schema can lose constraints — lossy, not faithful. */
|
|
2430
|
+
nativeStructuredOutputSupport() {
|
|
2431
|
+
return "lossy";
|
|
2432
|
+
}
|
|
1787
2433
|
baseUrl;
|
|
1788
|
-
|
|
2434
|
+
apiKeySource;
|
|
1789
2435
|
callCounter = 0;
|
|
1790
2436
|
governor;
|
|
1791
2437
|
constructor(options = {}) {
|
|
1792
|
-
this.
|
|
2438
|
+
this.apiKeySource = options.apiKey ?? process.env.GOOGLE_API_KEY ?? process.env.GEMINI_API_KEY ?? "";
|
|
1793
2439
|
this.baseUrl = (options.baseUrl ?? "https://generativelanguage.googleapis.com/v1beta").replace(
|
|
1794
2440
|
/\/$/,
|
|
1795
2441
|
""
|
|
1796
2442
|
);
|
|
1797
2443
|
this.governor = options.rateLimit ? new RateLimiter(options.rateLimit) : void 0;
|
|
1798
|
-
if (!this.
|
|
2444
|
+
if (typeof this.apiKeySource === "string" && !this.apiKeySource) {
|
|
2445
|
+
throw new Error("Google API key is required. Set GOOGLE_API_KEY or pass apiKey in options.");
|
|
2446
|
+
}
|
|
2447
|
+
}
|
|
2448
|
+
/** Resolve the API key for one request (supports an expiring-token callback). */
|
|
2449
|
+
async resolveKey() {
|
|
2450
|
+
const key = await resolveApiKey(this.apiKeySource);
|
|
2451
|
+
if (!key) {
|
|
1799
2452
|
throw new Error("Google API key is required. Set GOOGLE_API_KEY or pass apiKey in options.");
|
|
1800
2453
|
}
|
|
2454
|
+
return key;
|
|
1801
2455
|
}
|
|
1802
2456
|
// ---------------------------------------------------------------------------
|
|
1803
2457
|
// chat - non-streaming completion
|
|
1804
2458
|
// ---------------------------------------------------------------------------
|
|
1805
2459
|
async chat(messages, options) {
|
|
2460
|
+
const headers = this.buildHeaders(await this.resolveKey());
|
|
1806
2461
|
const body = this.buildRequestBody(messages, options);
|
|
1807
2462
|
const res = await fetchWithRetry(
|
|
1808
2463
|
`${this.baseUrl}/models/${options.model}:generateContent`,
|
|
1809
2464
|
{
|
|
1810
2465
|
method: "POST",
|
|
1811
|
-
headers
|
|
2466
|
+
headers,
|
|
1812
2467
|
body: JSON.stringify(body),
|
|
1813
2468
|
signal: options.signal
|
|
1814
2469
|
},
|
|
1815
|
-
{ governor: this.governor }
|
|
2470
|
+
{ governor: this.governor, provider: this.name }
|
|
1816
2471
|
);
|
|
1817
2472
|
if (!res.ok) {
|
|
1818
2473
|
const errorBody = await res.text();
|
|
1819
2474
|
const message = this.extractErrorMessage(errorBody, res.status);
|
|
1820
|
-
throw
|
|
2475
|
+
throw buildProviderError({
|
|
2476
|
+
provider: this.name,
|
|
2477
|
+
status: res.status,
|
|
2478
|
+
headers: res.headers,
|
|
2479
|
+
message,
|
|
2480
|
+
body: errorBody
|
|
2481
|
+
});
|
|
1821
2482
|
}
|
|
1822
2483
|
const json = await res.json();
|
|
1823
2484
|
return this.parseResponse(json, options.model);
|
|
@@ -1826,21 +2487,28 @@ var GeminiProvider = class {
|
|
|
1826
2487
|
// stream - SSE streaming completion
|
|
1827
2488
|
// ---------------------------------------------------------------------------
|
|
1828
2489
|
async *stream(messages, options) {
|
|
2490
|
+
const headers = this.buildHeaders(await this.resolveKey());
|
|
1829
2491
|
const body = this.buildRequestBody(messages, options);
|
|
1830
2492
|
const res = await fetchWithRetry(
|
|
1831
2493
|
`${this.baseUrl}/models/${options.model}:streamGenerateContent?alt=sse`,
|
|
1832
2494
|
{
|
|
1833
2495
|
method: "POST",
|
|
1834
|
-
headers
|
|
2496
|
+
headers,
|
|
1835
2497
|
body: JSON.stringify(body),
|
|
1836
2498
|
signal: options.signal
|
|
1837
2499
|
},
|
|
1838
|
-
{ governor: this.governor }
|
|
2500
|
+
{ governor: this.governor, provider: this.name }
|
|
1839
2501
|
);
|
|
1840
2502
|
if (!res.ok) {
|
|
1841
2503
|
const errorBody = await res.text();
|
|
1842
2504
|
const message = this.extractErrorMessage(errorBody, res.status);
|
|
1843
|
-
throw
|
|
2505
|
+
throw buildProviderError({
|
|
2506
|
+
provider: this.name,
|
|
2507
|
+
status: res.status,
|
|
2508
|
+
headers: res.headers,
|
|
2509
|
+
message,
|
|
2510
|
+
body: errorBody
|
|
2511
|
+
});
|
|
1844
2512
|
}
|
|
1845
2513
|
if (!res.body) {
|
|
1846
2514
|
throw new Error("Gemini stream response has no body");
|
|
@@ -1850,10 +2518,10 @@ var GeminiProvider = class {
|
|
|
1850
2518
|
// ---------------------------------------------------------------------------
|
|
1851
2519
|
// Internal: request building
|
|
1852
2520
|
// ---------------------------------------------------------------------------
|
|
1853
|
-
buildHeaders() {
|
|
2521
|
+
buildHeaders(apiKey) {
|
|
1854
2522
|
return {
|
|
1855
2523
|
"Content-Type": "application/json",
|
|
1856
|
-
"x-goog-api-key":
|
|
2524
|
+
"x-goog-api-key": apiKey
|
|
1857
2525
|
};
|
|
1858
2526
|
}
|
|
1859
2527
|
extractErrorMessage(body, status) {
|
|
@@ -2210,6 +2878,203 @@ var GeminiProvider = class {
|
|
|
2210
2878
|
}
|
|
2211
2879
|
};
|
|
2212
2880
|
|
|
2881
|
+
// src/providers/profiles/openrouter.ts
|
|
2882
|
+
var OPENROUTER_PROFILE = {
|
|
2883
|
+
name: "openrouter",
|
|
2884
|
+
label: "OpenRouter",
|
|
2885
|
+
defaultBaseUrl: "https://openrouter.ai/api/v1",
|
|
2886
|
+
envApiKey: "OPENROUTER_API_KEY",
|
|
2887
|
+
envBaseUrl: "OPENROUTER_BASE_URL",
|
|
2888
|
+
pricing: { kind: "from-response" },
|
|
2889
|
+
// Ask OpenRouter to report per-call cost in usage.cost.
|
|
2890
|
+
requestDefaults: { usage: { include: true } },
|
|
2891
|
+
reasoning: {
|
|
2892
|
+
// OpenRouter's effort vocabulary is low|medium|high; xhigh/max clamp to high.
|
|
2893
|
+
emit: reasoningObjectEmit(
|
|
2894
|
+
(effort) => effort === "xhigh" || effort === "max" ? "high" : effort
|
|
2895
|
+
),
|
|
2896
|
+
capture: "reasoning_details",
|
|
2897
|
+
roundTrip: "on-tool-call-turns"
|
|
2898
|
+
},
|
|
2899
|
+
// OpenRouter normalizes to max_tokens across its backends.
|
|
2900
|
+
maxTokensField: "max_tokens"
|
|
2901
|
+
};
|
|
2902
|
+
|
|
2903
|
+
// src/providers/profiles/azure.ts
|
|
2904
|
+
var AZURE_PROFILE = {
|
|
2905
|
+
name: "azure",
|
|
2906
|
+
label: "Azure OpenAI",
|
|
2907
|
+
// Never actually used — requireExplicitBaseUrl forces a real value first.
|
|
2908
|
+
defaultBaseUrl: "https://YOUR-RESOURCE.openai.azure.com/openai/v1",
|
|
2909
|
+
requireExplicitBaseUrl: true,
|
|
2910
|
+
envApiKey: "AZURE_OPENAI_API_KEY",
|
|
2911
|
+
envBaseUrl: "AZURE_OPENAI_BASE_URL",
|
|
2912
|
+
authHeader: "api-key",
|
|
2913
|
+
pricing: { kind: "table", table: OPENAI_PRICING },
|
|
2914
|
+
reasoning: { emit: openaiReasoningEmit, capture: "none" },
|
|
2915
|
+
roleFor: (role, model) => role === "system" && isOSeriesModel(model) ? "developer" : role,
|
|
2916
|
+
maxTokensField: "max_completion_tokens",
|
|
2917
|
+
parallelToolCalls: (model) => !isOSeriesModel(model)
|
|
2918
|
+
};
|
|
2919
|
+
|
|
2920
|
+
// src/providers/profiles/xai.ts
|
|
2921
|
+
var acceptsReasoningEffort = (model) => model.startsWith("grok-3-mini");
|
|
2922
|
+
var isReasoningModel = (model) => /^grok-(3-mini|4)/.test(model);
|
|
2923
|
+
var XAI_PROFILE = {
|
|
2924
|
+
name: "xai",
|
|
2925
|
+
label: "xAI",
|
|
2926
|
+
defaultBaseUrl: "https://api.x.ai/v1",
|
|
2927
|
+
envApiKey: "XAI_API_KEY",
|
|
2928
|
+
envBaseUrl: "XAI_BASE_URL",
|
|
2929
|
+
pricing: { kind: "unknown" },
|
|
2930
|
+
reasoning: {
|
|
2931
|
+
emit: reasoningEffortEmit((resolved, model) => {
|
|
2932
|
+
if (!acceptsReasoningEffort(model)) return void 0;
|
|
2933
|
+
if (resolved.thinkingDisabled || !resolved.activeEffort) return void 0;
|
|
2934
|
+
return resolved.activeEffort === "low" ? "low" : "high";
|
|
2935
|
+
}),
|
|
2936
|
+
capture: "reasoning"
|
|
2937
|
+
},
|
|
2938
|
+
capabilities: {
|
|
2939
|
+
forbiddenParams: (model) => isReasoningModel(model) ? ["stop", "presence_penalty", "frequency_penalty"] : []
|
|
2940
|
+
}
|
|
2941
|
+
};
|
|
2942
|
+
|
|
2943
|
+
// src/providers/profiles/deepseek.ts
|
|
2944
|
+
var isReasoner = (model) => /reasoner|v4/i.test(model);
|
|
2945
|
+
var deepseekEmit = (_body, _resolved, model) => ({
|
|
2946
|
+
stripTemperature: isReasoner(model)
|
|
2947
|
+
});
|
|
2948
|
+
var DEEPSEEK_PROFILE = {
|
|
2949
|
+
name: "deepseek",
|
|
2950
|
+
label: "DeepSeek",
|
|
2951
|
+
defaultBaseUrl: "https://api.deepseek.com/v1",
|
|
2952
|
+
envApiKey: "DEEPSEEK_API_KEY",
|
|
2953
|
+
envBaseUrl: "DEEPSEEK_BASE_URL",
|
|
2954
|
+
pricing: { kind: "unknown" },
|
|
2955
|
+
reasoning: {
|
|
2956
|
+
emit: deepseekEmit,
|
|
2957
|
+
capture: "reasoning_content",
|
|
2958
|
+
roundTrip: "on-tool-call-turns"
|
|
2959
|
+
},
|
|
2960
|
+
capabilities: {
|
|
2961
|
+
supportsJsonSchema: false,
|
|
2962
|
+
forbiddenParams: (model) => isReasoner(model) ? ["top_p", "presence_penalty", "frequency_penalty"] : []
|
|
2963
|
+
}
|
|
2964
|
+
};
|
|
2965
|
+
|
|
2966
|
+
// src/providers/profiles/mistral.ts
|
|
2967
|
+
var acceptsReasoningEffort2 = (model) => /^mistral-(small|medium)/i.test(model);
|
|
2968
|
+
var MISTRAL_PROFILE = {
|
|
2969
|
+
name: "mistral",
|
|
2970
|
+
label: "Mistral",
|
|
2971
|
+
defaultBaseUrl: "https://api.mistral.ai/v1",
|
|
2972
|
+
envApiKey: "MISTRAL_API_KEY",
|
|
2973
|
+
envBaseUrl: "MISTRAL_BASE_URL",
|
|
2974
|
+
pricing: { kind: "unknown" },
|
|
2975
|
+
reasoning: {
|
|
2976
|
+
emit: reasoningEffortEmit((resolved, model) => {
|
|
2977
|
+
if (!acceptsReasoningEffort2(model)) return void 0;
|
|
2978
|
+
if (resolved.thinkingDisabled || !resolved.activeEffort) return void 0;
|
|
2979
|
+
return "high";
|
|
2980
|
+
}),
|
|
2981
|
+
capture: "reasoning_content"
|
|
2982
|
+
}
|
|
2983
|
+
};
|
|
2984
|
+
|
|
2985
|
+
// src/providers/profiles/groq.ts
|
|
2986
|
+
var isGptOss = (model) => /gpt-oss/i.test(model);
|
|
2987
|
+
var GROQ_PROFILE = {
|
|
2988
|
+
name: "groq",
|
|
2989
|
+
label: "Groq",
|
|
2990
|
+
defaultBaseUrl: "https://api.groq.com/openai/v1",
|
|
2991
|
+
envApiKey: "GROQ_API_KEY",
|
|
2992
|
+
envBaseUrl: "GROQ_BASE_URL",
|
|
2993
|
+
pricing: { kind: "unknown" },
|
|
2994
|
+
reasoning: {
|
|
2995
|
+
emit: reasoningEffortEmit((resolved, model) => {
|
|
2996
|
+
if (!isGptOss(model) || resolved.thinkingDisabled || !resolved.activeEffort) {
|
|
2997
|
+
return void 0;
|
|
2998
|
+
}
|
|
2999
|
+
const e = resolved.activeEffort;
|
|
3000
|
+
return e === "xhigh" || e === "max" ? "high" : e;
|
|
3001
|
+
}),
|
|
3002
|
+
capture: "reasoning"
|
|
3003
|
+
},
|
|
3004
|
+
capabilities: {
|
|
3005
|
+
emitsMessageName: false,
|
|
3006
|
+
// Groq's `response_format: json_schema` is supported only on a subset of
|
|
3007
|
+
// models — the `openai/gpt-oss-*` family accepts it; llama/gemma/etc 400
|
|
3008
|
+
// with "This model does not support response format `json_schema`".
|
|
3009
|
+
// Per-model so `nativeStructuredOutput` engages the native path only where
|
|
3010
|
+
// it works and otherwise downgrades cleanly to `json_object` (verified live).
|
|
3011
|
+
supportsJsonSchema: isGptOss
|
|
3012
|
+
}
|
|
3013
|
+
};
|
|
3014
|
+
|
|
3015
|
+
// src/providers/profiles/bedrock.ts
|
|
3016
|
+
var isGptOss2 = (model) => /gpt-oss/i.test(model);
|
|
3017
|
+
var BEDROCK_PROFILE = {
|
|
3018
|
+
name: "bedrock",
|
|
3019
|
+
label: "AWS Bedrock",
|
|
3020
|
+
// Never used — requireExplicitBaseUrl forces a real (region-specific) value first.
|
|
3021
|
+
defaultBaseUrl: "https://bedrock-mantle.us-east-1.api.aws/v1",
|
|
3022
|
+
requireExplicitBaseUrl: true,
|
|
3023
|
+
envApiKey: "AWS_BEARER_TOKEN_BEDROCK",
|
|
3024
|
+
envBaseUrl: "BEDROCK_BASE_URL",
|
|
3025
|
+
pricing: { kind: "unknown" },
|
|
3026
|
+
reasoning: {
|
|
3027
|
+
emit: reasoningEffortEmit((resolved, model) => {
|
|
3028
|
+
if (!isGptOss2(model) || resolved.thinkingDisabled || !resolved.activeEffort) {
|
|
3029
|
+
return void 0;
|
|
3030
|
+
}
|
|
3031
|
+
const e = resolved.activeEffort;
|
|
3032
|
+
return e === "xhigh" || e === "max" ? "high" : e;
|
|
3033
|
+
}),
|
|
3034
|
+
capture: "reasoning"
|
|
3035
|
+
}
|
|
3036
|
+
};
|
|
3037
|
+
|
|
3038
|
+
// src/providers/profiles/local.ts
|
|
3039
|
+
function localProfile(name, label, port, envApiKey) {
|
|
3040
|
+
return {
|
|
3041
|
+
name,
|
|
3042
|
+
label,
|
|
3043
|
+
defaultBaseUrl: `http://localhost:${port}/v1`,
|
|
3044
|
+
envApiKey,
|
|
3045
|
+
envBaseUrl: `${envApiKey.replace(/_API_KEY$/, "")}_BASE_URL`,
|
|
3046
|
+
allowMissingApiKey: true,
|
|
3047
|
+
pricing: { kind: "zero" },
|
|
3048
|
+
reasoning: { emit: () => {
|
|
3049
|
+
}, capture: "think_tags" },
|
|
3050
|
+
maxTokensField: "max_tokens"
|
|
3051
|
+
};
|
|
3052
|
+
}
|
|
3053
|
+
var OLLAMA_PROFILE = localProfile("ollama", "Ollama", 11434, "OLLAMA_API_KEY");
|
|
3054
|
+
var VLLM_PROFILE = localProfile("vllm", "vLLM", 8e3, "VLLM_API_KEY");
|
|
3055
|
+
var LMSTUDIO_PROFILE = localProfile("lmstudio", "LM Studio", 1234, "LMSTUDIO_API_KEY");
|
|
3056
|
+
var LLAMACPP_PROFILE = localProfile("llamacpp", "llama.cpp", 8080, "LLAMACPP_API_KEY");
|
|
3057
|
+
var SGLANG_PROFILE = localProfile("sglang", "SGLang", 3e4, "SGLANG_API_KEY");
|
|
3058
|
+
var LOCAL_PROFILES = [
|
|
3059
|
+
OLLAMA_PROFILE,
|
|
3060
|
+
VLLM_PROFILE,
|
|
3061
|
+
LMSTUDIO_PROFILE,
|
|
3062
|
+
LLAMACPP_PROFILE,
|
|
3063
|
+
SGLANG_PROFILE
|
|
3064
|
+
];
|
|
3065
|
+
|
|
3066
|
+
// src/providers/profiles/index.ts
|
|
3067
|
+
var BUILTIN_PROFILES = [
|
|
3068
|
+
OPENROUTER_PROFILE,
|
|
3069
|
+
AZURE_PROFILE,
|
|
3070
|
+
XAI_PROFILE,
|
|
3071
|
+
DEEPSEEK_PROFILE,
|
|
3072
|
+
MISTRAL_PROFILE,
|
|
3073
|
+
GROQ_PROFILE,
|
|
3074
|
+
BEDROCK_PROFILE,
|
|
3075
|
+
...LOCAL_PROFILES
|
|
3076
|
+
];
|
|
3077
|
+
|
|
2213
3078
|
// src/providers/registry.ts
|
|
2214
3079
|
var builtinFactories = {
|
|
2215
3080
|
openai: (config) => {
|
|
@@ -2249,6 +3114,23 @@ var builtinFactories = {
|
|
|
2249
3114
|
});
|
|
2250
3115
|
}
|
|
2251
3116
|
};
|
|
3117
|
+
function presetFactory(profile) {
|
|
3118
|
+
return (config) => {
|
|
3119
|
+
const opts = config.providers?.[profile.name] ?? {};
|
|
3120
|
+
return new OpenAICompatibleProvider({
|
|
3121
|
+
profile,
|
|
3122
|
+
apiKey: opts.apiKey,
|
|
3123
|
+
baseUrl: opts.baseUrl,
|
|
3124
|
+
authHeader: opts.authHeader,
|
|
3125
|
+
rateLimit: opts.rateLimit
|
|
3126
|
+
});
|
|
3127
|
+
};
|
|
3128
|
+
}
|
|
3129
|
+
for (const profile of BUILTIN_PROFILES) {
|
|
3130
|
+
if (!(profile.name in builtinFactories)) {
|
|
3131
|
+
builtinFactories[profile.name] = presetFactory(profile);
|
|
3132
|
+
}
|
|
3133
|
+
}
|
|
2252
3134
|
var ProviderRegistry = class {
|
|
2253
3135
|
/** Cached provider instances, keyed by provider name */
|
|
2254
3136
|
instances = /* @__PURE__ */ new Map();
|
|
@@ -2366,123 +3248,6 @@ var import_node_async_hooks = require("async_hooks");
|
|
|
2366
3248
|
var import_node_crypto = require("crypto");
|
|
2367
3249
|
var import_zod = require("zod");
|
|
2368
3250
|
|
|
2369
|
-
// src/errors.ts
|
|
2370
|
-
var AxlError = class extends Error {
|
|
2371
|
-
code;
|
|
2372
|
-
constructor(code, message) {
|
|
2373
|
-
super(message);
|
|
2374
|
-
this.name = "AxlError";
|
|
2375
|
-
this.code = code;
|
|
2376
|
-
}
|
|
2377
|
-
};
|
|
2378
|
-
var VerifyError = class extends AxlError {
|
|
2379
|
-
lastOutput;
|
|
2380
|
-
zodError;
|
|
2381
|
-
retries;
|
|
2382
|
-
constructor(lastOutput, zodError, retries) {
|
|
2383
|
-
super("VERIFY_ERROR", `Schema validation failed after ${retries} retries: ${zodError.message}`);
|
|
2384
|
-
this.name = "VerifyError";
|
|
2385
|
-
this.lastOutput = lastOutput;
|
|
2386
|
-
this.zodError = zodError;
|
|
2387
|
-
this.retries = retries;
|
|
2388
|
-
}
|
|
2389
|
-
};
|
|
2390
|
-
var QuorumNotMet = class extends AxlError {
|
|
2391
|
-
results;
|
|
2392
|
-
constructor(required, actual, results) {
|
|
2393
|
-
super("QUORUM_NOT_MET", `Quorum not met: needed ${required} successes, got ${actual}`);
|
|
2394
|
-
this.name = "QuorumNotMet";
|
|
2395
|
-
this.results = results;
|
|
2396
|
-
}
|
|
2397
|
-
};
|
|
2398
|
-
var NoConsensus = class extends AxlError {
|
|
2399
|
-
constructor(reason) {
|
|
2400
|
-
super("NO_CONSENSUS", `No consensus: ${reason}`);
|
|
2401
|
-
this.name = "NoConsensus";
|
|
2402
|
-
}
|
|
2403
|
-
};
|
|
2404
|
-
var TimeoutError = class extends AxlError {
|
|
2405
|
-
constructor(operation, timeoutMs) {
|
|
2406
|
-
super("TIMEOUT", `${operation} exceeded timeout of ${timeoutMs}ms`);
|
|
2407
|
-
this.name = "TimeoutError";
|
|
2408
|
-
}
|
|
2409
|
-
};
|
|
2410
|
-
function formatBudgetCost(cost) {
|
|
2411
|
-
if (Number.isNaN(cost)) return "$NaN";
|
|
2412
|
-
if (cost === Infinity) return "$Infinity";
|
|
2413
|
-
if (cost === -Infinity) return "-$Infinity";
|
|
2414
|
-
if (cost === 0) return "$0.00";
|
|
2415
|
-
const sign = cost < 0 ? "-" : "";
|
|
2416
|
-
const abs = Math.abs(cost);
|
|
2417
|
-
if (abs < 1e-6) return `${sign}< $0.000001`;
|
|
2418
|
-
if (abs < 1e-4) {
|
|
2419
|
-
const [mantissa, exponent] = abs.toExponential(2).split("e");
|
|
2420
|
-
return `${sign}$${mantissa}e${parseInt(exponent, 10)}`;
|
|
2421
|
-
}
|
|
2422
|
-
if (abs < 0.01) return `${sign}$${abs.toFixed(6)}`;
|
|
2423
|
-
return `${sign}$${abs.toFixed(2)}`;
|
|
2424
|
-
}
|
|
2425
|
-
var BudgetExceededError = class extends AxlError {
|
|
2426
|
-
limit;
|
|
2427
|
-
spent;
|
|
2428
|
-
policy;
|
|
2429
|
-
constructor(limit, spent, policy) {
|
|
2430
|
-
super(
|
|
2431
|
-
"BUDGET_EXCEEDED",
|
|
2432
|
-
`Budget exceeded: spent ${formatBudgetCost(spent)} of ${formatBudgetCost(
|
|
2433
|
-
limit
|
|
2434
|
-
)} limit (policy: ${policy})`
|
|
2435
|
-
);
|
|
2436
|
-
this.name = "BudgetExceededError";
|
|
2437
|
-
this.limit = limit;
|
|
2438
|
-
this.spent = spent;
|
|
2439
|
-
this.policy = policy;
|
|
2440
|
-
}
|
|
2441
|
-
};
|
|
2442
|
-
var MaxTurnsError = class extends AxlError {
|
|
2443
|
-
maxTurns;
|
|
2444
|
-
constructor(operation, maxTurns) {
|
|
2445
|
-
super("MAX_TURNS", `${operation} exceeded maximum of ${maxTurns} turns`);
|
|
2446
|
-
this.name = "MaxTurnsError";
|
|
2447
|
-
this.maxTurns = maxTurns;
|
|
2448
|
-
}
|
|
2449
|
-
};
|
|
2450
|
-
var GuardrailError = class extends AxlError {
|
|
2451
|
-
guardrailType;
|
|
2452
|
-
reason;
|
|
2453
|
-
constructor(guardrailType, reason) {
|
|
2454
|
-
super("GUARDRAIL_BLOCKED", `${guardrailType} guardrail blocked: ${reason}`);
|
|
2455
|
-
this.name = "GuardrailError";
|
|
2456
|
-
this.guardrailType = guardrailType;
|
|
2457
|
-
this.reason = reason;
|
|
2458
|
-
}
|
|
2459
|
-
};
|
|
2460
|
-
var ValidationError = class extends AxlError {
|
|
2461
|
-
lastOutput;
|
|
2462
|
-
reason;
|
|
2463
|
-
retries;
|
|
2464
|
-
constructor(lastOutput, reason, retries) {
|
|
2465
|
-
super("VALIDATION_ERROR", `Validation failed after ${retries} retries: ${reason}`);
|
|
2466
|
-
this.name = "ValidationError";
|
|
2467
|
-
this.lastOutput = lastOutput;
|
|
2468
|
-
this.reason = reason;
|
|
2469
|
-
this.retries = retries;
|
|
2470
|
-
}
|
|
2471
|
-
};
|
|
2472
|
-
var ToolDenied = class extends AxlError {
|
|
2473
|
-
toolName;
|
|
2474
|
-
agentName;
|
|
2475
|
-
constructor(toolName, agentName) {
|
|
2476
|
-
super(
|
|
2477
|
-
"TOOL_DENIED",
|
|
2478
|
-
`Agent "${agentName}" attempted to call tool "${toolName}" which is not in its ACL`
|
|
2479
|
-
);
|
|
2480
|
-
this.name = "ToolDenied";
|
|
2481
|
-
this.toolName = toolName;
|
|
2482
|
-
this.agentName = agentName;
|
|
2483
|
-
}
|
|
2484
|
-
};
|
|
2485
|
-
|
|
2486
3251
|
// src/partial-json.ts
|
|
2487
3252
|
var MAX_DEPTH = 256;
|
|
2488
3253
|
function parsePartialJson(input) {
|
|
@@ -3057,6 +3822,14 @@ var COST_BEARING_LEAF_TYPES = [
|
|
|
3057
3822
|
"memory_recall"
|
|
3058
3823
|
];
|
|
3059
3824
|
var COST_LEAF_SET = new Set(COST_BEARING_LEAF_TYPES);
|
|
3825
|
+
function hasPositiveTokens(event) {
|
|
3826
|
+
const t = event.tokens;
|
|
3827
|
+
if (!t) return false;
|
|
3828
|
+
return (t.input ?? 0) + (t.output ?? 0) + (t.reasoning ?? 0) > 0;
|
|
3829
|
+
}
|
|
3830
|
+
function isUsableCost(cost) {
|
|
3831
|
+
return typeof cost === "number" && Number.isFinite(cost) && cost >= 0;
|
|
3832
|
+
}
|
|
3060
3833
|
function eventCostContribution(event) {
|
|
3061
3834
|
if (event.type === "ask_end") return 0;
|
|
3062
3835
|
const c = event.cost;
|
|
@@ -3065,6 +3838,9 @@ function eventCostContribution(event) {
|
|
|
3065
3838
|
function isCostBearingLeaf(event) {
|
|
3066
3839
|
return COST_LEAF_SET.has(event.type);
|
|
3067
3840
|
}
|
|
3841
|
+
function isUnpricedLeaf(event) {
|
|
3842
|
+
return event.type !== void 0 && COST_LEAF_SET.has(event.type) && !isUsableCost(event.cost) && hasPositiveTokens(event);
|
|
3843
|
+
}
|
|
3068
3844
|
function isRootLevel(event) {
|
|
3069
3845
|
const d = event.depth;
|
|
3070
3846
|
return (d ?? 0) === 0;
|
|
@@ -3113,6 +3889,10 @@ var AXL_EVENT_TYPES = [
|
|
|
3113
3889
|
"string_delta",
|
|
3114
3890
|
// Verification
|
|
3115
3891
|
"verify",
|
|
3892
|
+
// Schema capability diagnostics (spec 22, Problem E) — surface the silent
|
|
3893
|
+
// structured-output cliffs (oversized appended schema, dropped refinements,
|
|
3894
|
+
// streaming disabled, zero-guidance `schemaPrompt:'none'`).
|
|
3895
|
+
"schema_diagnostic",
|
|
3116
3896
|
// Observability
|
|
3117
3897
|
"log",
|
|
3118
3898
|
"memory_remember",
|
|
@@ -3155,6 +3935,7 @@ var LIFECYCLE_TYPES = /* @__PURE__ */ new Set([
|
|
|
3155
3935
|
"delegate",
|
|
3156
3936
|
"pipeline",
|
|
3157
3937
|
"verify",
|
|
3938
|
+
"schema_diagnostic",
|
|
3158
3939
|
"workflow_start",
|
|
3159
3940
|
"workflow_end",
|
|
3160
3941
|
"checkpoint_save",
|
|
@@ -3959,6 +4740,11 @@ var REDACTION_RULES = {
|
|
|
3959
4740
|
// Scrub only the delta — preserving path keeps "which fields stream" telemetry.
|
|
3960
4741
|
string_delta: (e) => ({ ...e, data: { ...e.data, delta: REDACTED } }),
|
|
3961
4742
|
verify: (e) => e.data.lastError !== void 0 ? { ...e, data: { ...e.data, lastError: REDACTED } } : e,
|
|
4743
|
+
// `schema_diagnostic` carries only structural schema metadata — token counts,
|
|
4744
|
+
// thresholds, JSON-Schema field paths, a root type name, a tool name — none of
|
|
4745
|
+
// which is user content or PII (field paths are schema-shape, preserved like
|
|
4746
|
+
// `string_delta.path`). Passthrough, as an explicit reviewed decision.
|
|
4747
|
+
schema_diagnostic: passthrough,
|
|
3962
4748
|
log: (e) => {
|
|
3963
4749
|
if (!e.data || typeof e.data !== "object" || Array.isArray(e.data)) return e;
|
|
3964
4750
|
return { ...e, data: walkObjectOneLevel(e.data, LOG_PRESERVE_KEYS) };
|
|
@@ -3991,6 +4777,80 @@ function redactEvent(event) {
|
|
|
3991
4777
|
return rule(event);
|
|
3992
4778
|
}
|
|
3993
4779
|
|
|
4780
|
+
// src/schema-diagnostics.ts
|
|
4781
|
+
var DEFAULT_SCHEMA_OVERSIZED_TOKENS = 4e3;
|
|
4782
|
+
var refinementCache = /* @__PURE__ */ new WeakMap();
|
|
4783
|
+
function detectDroppedRefinements(schema) {
|
|
4784
|
+
const cached = refinementCache.get(schema);
|
|
4785
|
+
if (cached) return cached;
|
|
4786
|
+
const paths = /* @__PURE__ */ new Set();
|
|
4787
|
+
let count = 0;
|
|
4788
|
+
const visited = /* @__PURE__ */ new WeakSet();
|
|
4789
|
+
const walk = (node, path) => {
|
|
4790
|
+
if (node === null || typeof node !== "object") return;
|
|
4791
|
+
if (visited.has(node)) return;
|
|
4792
|
+
visited.add(node);
|
|
4793
|
+
const def = node._zod?.def;
|
|
4794
|
+
if (!def) return;
|
|
4795
|
+
const here = () => paths.add(path === "" ? "<root>" : path);
|
|
4796
|
+
const checks = def.checks;
|
|
4797
|
+
if (Array.isArray(checks)) {
|
|
4798
|
+
for (const check of checks) {
|
|
4799
|
+
const kind = check?._zod?.def?.check;
|
|
4800
|
+
if (kind === "custom") {
|
|
4801
|
+
count += 1;
|
|
4802
|
+
here();
|
|
4803
|
+
}
|
|
4804
|
+
}
|
|
4805
|
+
}
|
|
4806
|
+
if (def.check === "custom") {
|
|
4807
|
+
count += 1;
|
|
4808
|
+
here();
|
|
4809
|
+
}
|
|
4810
|
+
const join2 = (child) => path === "" ? child : `${path}.${child}`;
|
|
4811
|
+
const shape = def.shape;
|
|
4812
|
+
if (shape && typeof shape === "object") {
|
|
4813
|
+
for (const [key, child] of Object.entries(shape)) walk(child, join2(key));
|
|
4814
|
+
}
|
|
4815
|
+
if ("element" in def) walk(def.element, `${path}[]`);
|
|
4816
|
+
if (Array.isArray(def.options)) {
|
|
4817
|
+
for (const opt of def.options) walk(opt, path);
|
|
4818
|
+
}
|
|
4819
|
+
if ("innerType" in def) walk(def.innerType, path);
|
|
4820
|
+
if (Array.isArray(def.items)) {
|
|
4821
|
+
def.items.forEach((item, i) => walk(item, `${path}[${i}]`));
|
|
4822
|
+
}
|
|
4823
|
+
if ("rest" in def && def.rest) walk(def.rest, `${path}[]`);
|
|
4824
|
+
if ("catchall" in def && def.catchall) walk(def.catchall, `${path}[*]`);
|
|
4825
|
+
if ("keyType" in def) walk(def.keyType, `${path}[key]`);
|
|
4826
|
+
if ("valueType" in def) walk(def.valueType, `${path}[*]`);
|
|
4827
|
+
if ("in" in def) walk(def.in, path);
|
|
4828
|
+
if ("out" in def) walk(def.out, path);
|
|
4829
|
+
if ("left" in def) walk(def.left, path);
|
|
4830
|
+
if ("right" in def) walk(def.right, path);
|
|
4831
|
+
if (typeof def.getter === "function") {
|
|
4832
|
+
try {
|
|
4833
|
+
walk(def.getter(), path);
|
|
4834
|
+
} catch {
|
|
4835
|
+
}
|
|
4836
|
+
}
|
|
4837
|
+
};
|
|
4838
|
+
walk(schema, "");
|
|
4839
|
+
const result = { count, paths: [...paths] };
|
|
4840
|
+
refinementCache.set(schema, result);
|
|
4841
|
+
return result;
|
|
4842
|
+
}
|
|
4843
|
+
var DOCS_URL = "docs/observability.md#schema-diagnostics";
|
|
4844
|
+
var MAX_WARNED_KEYS = 2048;
|
|
4845
|
+
var warnedDiagnosticKeys = /* @__PURE__ */ new Set();
|
|
4846
|
+
function warnSchemaDiagnosticOnce(key, message, silent) {
|
|
4847
|
+
if (silent || process.env.AXL_DIAGNOSTICS_SILENT === "true") return;
|
|
4848
|
+
if (warnedDiagnosticKeys.has(key)) return;
|
|
4849
|
+
if (warnedDiagnosticKeys.size >= MAX_WARNED_KEYS) warnedDiagnosticKeys.clear();
|
|
4850
|
+
warnedDiagnosticKeys.add(key);
|
|
4851
|
+
console.warn(`[axl] ${message} See ${DOCS_URL}.`);
|
|
4852
|
+
}
|
|
4853
|
+
|
|
3994
4854
|
// src/config.ts
|
|
3995
4855
|
function defineConfig(config) {
|
|
3996
4856
|
return config;
|
|
@@ -4069,11 +4929,47 @@ function resolveConfig(config) {
|
|
|
4069
4929
|
// src/context.ts
|
|
4070
4930
|
var signalStorage = new import_node_async_hooks.AsyncLocalStorage();
|
|
4071
4931
|
var askStorage = new import_node_async_hooks.AsyncLocalStorage();
|
|
4932
|
+
var inlineSchemaCache = /* @__PURE__ */ new WeakMap();
|
|
4072
4933
|
function zodToJsonSchema(schema) {
|
|
4934
|
+
const cached = inlineSchemaCache.get(schema);
|
|
4935
|
+
if (cached !== void 0) return cached;
|
|
4073
4936
|
const result = import_zod.z.toJSONSchema(schema, { unrepresentable: "any" });
|
|
4074
4937
|
delete result.$schema;
|
|
4938
|
+
Object.freeze(result);
|
|
4939
|
+
inlineSchemaCache.set(schema, result);
|
|
4075
4940
|
return result;
|
|
4076
4941
|
}
|
|
4942
|
+
var promptSchemaCache = /* @__PURE__ */ new WeakMap();
|
|
4943
|
+
var inlineSchemaTokenCache = /* @__PURE__ */ new WeakMap();
|
|
4944
|
+
function estimateInlineSchemaTokens(schema) {
|
|
4945
|
+
const cached = inlineSchemaTokenCache.get(schema);
|
|
4946
|
+
if (cached !== void 0) return cached;
|
|
4947
|
+
const tokens = estimateTokens(JSON.stringify(zodToJsonSchema(schema)));
|
|
4948
|
+
inlineSchemaTokenCache.set(schema, tokens);
|
|
4949
|
+
return tokens;
|
|
4950
|
+
}
|
|
4951
|
+
var nativeSchemaCache = /* @__PURE__ */ new WeakMap();
|
|
4952
|
+
function deriveNativeSchema(schema) {
|
|
4953
|
+
const cached = nativeSchemaCache.get(schema);
|
|
4954
|
+
if (cached !== void 0) return cached;
|
|
4955
|
+
const json = import_zod.z.toJSONSchema(schema, { unrepresentable: "any", io: "input" });
|
|
4956
|
+
delete json.$schema;
|
|
4957
|
+
nativeSchemaCache.set(schema, json);
|
|
4958
|
+
return json;
|
|
4959
|
+
}
|
|
4960
|
+
function renderSchemaForPrompt(schema) {
|
|
4961
|
+
const cached = promptSchemaCache.get(schema);
|
|
4962
|
+
if (cached !== void 0) return cached;
|
|
4963
|
+
const json = import_zod.z.toJSONSchema(schema, {
|
|
4964
|
+
unrepresentable: "any",
|
|
4965
|
+
io: "input",
|
|
4966
|
+
reused: "ref"
|
|
4967
|
+
});
|
|
4968
|
+
delete json.$schema;
|
|
4969
|
+
const rendered = JSON.stringify(json);
|
|
4970
|
+
promptSchemaCache.set(schema, rendered);
|
|
4971
|
+
return rendered;
|
|
4972
|
+
}
|
|
4077
4973
|
function estimateTokens(text) {
|
|
4078
4974
|
return Math.ceil(text.length / 4);
|
|
4079
4975
|
}
|
|
@@ -4454,12 +5350,14 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
4454
5350
|
depth,
|
|
4455
5351
|
agent: agent2._name,
|
|
4456
5352
|
stepRef,
|
|
4457
|
-
askCost: { value: 0 }
|
|
5353
|
+
askCost: { value: 0 },
|
|
5354
|
+
askUnpriced: false
|
|
4458
5355
|
};
|
|
4459
5356
|
return askStorage.run(frame, async () => {
|
|
4460
5357
|
const askStart = Date.now();
|
|
4461
5358
|
this.emitEvent({ type: "ask_start", prompt });
|
|
4462
5359
|
const costBefore = this.budgetContext?.totalCost ?? 0;
|
|
5360
|
+
const unpricedCountBefore = this.budgetContext?.unpricedCount ?? 0;
|
|
4463
5361
|
const resolveCtx = options?.metadata ? { metadata: { ...this.metadata, ...options.metadata } } : { metadata: this.metadata };
|
|
4464
5362
|
const usageCapture = {};
|
|
4465
5363
|
const doCall = async () => {
|
|
@@ -4505,6 +5403,7 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
4505
5403
|
) : await doCall();
|
|
4506
5404
|
outcome = { ok: true, result };
|
|
4507
5405
|
const costAfter = this.budgetContext?.totalCost ?? 0;
|
|
5406
|
+
const unpricedCountAfter = this.budgetContext?.unpricedCount ?? 0;
|
|
4508
5407
|
if (this.onAgentCallComplete) {
|
|
4509
5408
|
try {
|
|
4510
5409
|
this.onAgentCallComplete({
|
|
@@ -4513,6 +5412,7 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
4513
5412
|
response: typeof result === "string" ? result : JSON.stringify(result),
|
|
4514
5413
|
model: agent2.resolveModel(resolveCtx),
|
|
4515
5414
|
cost: costAfter - costBefore,
|
|
5415
|
+
unpriced: frame.askUnpriced || unpricedCountAfter > unpricedCountBefore,
|
|
4516
5416
|
duration: Date.now() - askStart,
|
|
4517
5417
|
promptVersion: agent2._config.version,
|
|
4518
5418
|
temperature: options?.temperature ?? agent2._config.temperature,
|
|
@@ -4547,6 +5447,7 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
4547
5447
|
error: "ask_end emitted without outcome \u2014 internal bug"
|
|
4548
5448
|
},
|
|
4549
5449
|
cost: frame.askCost.value,
|
|
5450
|
+
...frame.askUnpriced ? { unpriced: true } : {},
|
|
4550
5451
|
duration: Date.now() - askStart
|
|
4551
5452
|
});
|
|
4552
5453
|
} catch (emitErr) {
|
|
@@ -4630,15 +5531,32 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
4630
5531
|
messages.push(msg);
|
|
4631
5532
|
}
|
|
4632
5533
|
}
|
|
5534
|
+
const schemaPromptMode = options?.schemaPrompt ?? agent2._config.schemaPrompt ?? "json-schema";
|
|
4633
5535
|
let userContent = prompt;
|
|
5536
|
+
let appendedSchemaText;
|
|
4634
5537
|
if (options?.schema) {
|
|
4635
|
-
const
|
|
4636
|
-
|
|
5538
|
+
const schema = options.schema;
|
|
5539
|
+
if (schemaPromptMode === "none") {
|
|
5540
|
+
} else if (schemaPromptMode === "json-schema") {
|
|
5541
|
+
appendedSchemaText = renderSchemaForPrompt(schema);
|
|
5542
|
+
userContent += `
|
|
4637
5543
|
|
|
4638
5544
|
Respond with valid JSON matching this schema:
|
|
4639
|
-
${
|
|
5545
|
+
${appendedSchemaText}`;
|
|
5546
|
+
} else {
|
|
5547
|
+
appendedSchemaText = typeof schemaPromptMode.render === "function" ? schemaPromptMode.render(schema) : schemaPromptMode.render;
|
|
5548
|
+
userContent += `
|
|
5549
|
+
|
|
5550
|
+
${appendedSchemaText}`;
|
|
5551
|
+
}
|
|
4640
5552
|
}
|
|
4641
5553
|
messages.push({ role: "user", content: userContent });
|
|
5554
|
+
this.emitSchemaDiagnostics(agent2, options, toolDefs, {
|
|
5555
|
+
mode: schemaPromptMode,
|
|
5556
|
+
appendedSchemaText,
|
|
5557
|
+
provider,
|
|
5558
|
+
model
|
|
5559
|
+
});
|
|
4642
5560
|
if (handoffMessages && handoffMessages.length > 0) {
|
|
4643
5561
|
const handoffContext = handoffMessages.filter(
|
|
4644
5562
|
(m) => m.role === "assistant" || m.role === "tool"
|
|
@@ -4751,7 +5669,17 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
4751
5669
|
signal: this.currentSignal
|
|
4752
5670
|
};
|
|
4753
5671
|
if (options?.schema && toolDefs.length === 0) {
|
|
4754
|
-
|
|
5672
|
+
const nativeOut = options?.nativeStructuredOutput ?? agent2._config.nativeStructuredOutput ?? false;
|
|
5673
|
+
chatOptions.responseFormat = nativeOut ? {
|
|
5674
|
+
type: "json_schema",
|
|
5675
|
+
json_schema: {
|
|
5676
|
+
name: "response",
|
|
5677
|
+
// Input-side rendering — see `deriveNativeSchema`. Keeps the
|
|
5678
|
+
// native schema consistent with the prompt and non-empty for
|
|
5679
|
+
// `.transform()` schemas.
|
|
5680
|
+
schema: deriveNativeSchema(options.schema)
|
|
5681
|
+
}
|
|
5682
|
+
} : { type: "json_object" };
|
|
4755
5683
|
}
|
|
4756
5684
|
const callbackMeta = this.currentCallbackMeta(agent2._name);
|
|
4757
5685
|
const callParams = {
|
|
@@ -4869,10 +5797,7 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
4869
5797
|
}
|
|
4870
5798
|
});
|
|
4871
5799
|
}
|
|
4872
|
-
response ??= {
|
|
4873
|
-
content: content2,
|
|
4874
|
-
usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
|
|
4875
|
-
};
|
|
5800
|
+
response ??= { content: content2 };
|
|
4876
5801
|
if (toolCalls.length > 0) {
|
|
4877
5802
|
response.tool_calls = toolCalls;
|
|
4878
5803
|
}
|
|
@@ -4896,7 +5821,11 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
4896
5821
|
response: "",
|
|
4897
5822
|
turn: turns,
|
|
4898
5823
|
...retryReason ? { retryReason } : {},
|
|
4899
|
-
error: err instanceof Error ? err.message : String(err)
|
|
5824
|
+
error: err instanceof Error ? err.message : String(err),
|
|
5825
|
+
// Enrich with typed-error metadata when available. `body` is
|
|
5826
|
+
// deliberately NOT emitted — it's redaction-eligible (see
|
|
5827
|
+
// docs/security.md). `data.error` already carries the message.
|
|
5828
|
+
...err instanceof ProviderError ? { status: err.status, retryable: err.retryable } : {}
|
|
4900
5829
|
}
|
|
4901
5830
|
});
|
|
4902
5831
|
throw err;
|
|
@@ -4960,10 +5889,13 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
4960
5889
|
depth: handoffTargetDepth,
|
|
4961
5890
|
agent: descriptor.agent._name,
|
|
4962
5891
|
stepRef: sourceFrame?.stepRef ?? this.stepRefRoot,
|
|
4963
|
-
askCost: { value: 0 }
|
|
5892
|
+
askCost: { value: 0 },
|
|
5893
|
+
askUnpriced: false
|
|
4964
5894
|
};
|
|
4965
5895
|
const handoffOptions = options ? {
|
|
4966
5896
|
schema: options.schema,
|
|
5897
|
+
schemaPrompt: options.schemaPrompt,
|
|
5898
|
+
nativeStructuredOutput: options.nativeStructuredOutput,
|
|
4967
5899
|
retries: options.retries,
|
|
4968
5900
|
metadata: options.metadata,
|
|
4969
5901
|
validate: options.validate,
|
|
@@ -4984,6 +5916,7 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
4984
5916
|
type: "ask_end",
|
|
4985
5917
|
outcome: { ok: true, result },
|
|
4986
5918
|
cost: targetFrame.askCost.value,
|
|
5919
|
+
...targetFrame.askUnpriced ? { unpriced: true } : {},
|
|
4987
5920
|
duration: Date.now() - targetAskStart
|
|
4988
5921
|
});
|
|
4989
5922
|
return result;
|
|
@@ -4995,6 +5928,7 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
4995
5928
|
error: err instanceof Error ? err.message : String(err)
|
|
4996
5929
|
},
|
|
4997
5930
|
cost: targetFrame.askCost.value,
|
|
5931
|
+
...targetFrame.askUnpriced ? { unpriced: true } : {},
|
|
4998
5932
|
duration: Date.now() - targetAskStart
|
|
4999
5933
|
});
|
|
5000
5934
|
throw err;
|
|
@@ -5533,6 +6467,148 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
5533
6467
|
...providerMetadata ? { providerMetadata } : {}
|
|
5534
6468
|
});
|
|
5535
6469
|
}
|
|
6470
|
+
/**
|
|
6471
|
+
* Emit `schema_diagnostic` events for the silent structured-output cliffs
|
|
6472
|
+
* (spec 22, Problems B + E). Called once per ask. Covers:
|
|
6473
|
+
* - the appended **prompt** schema (oversized; dropped refinements; or the
|
|
6474
|
+
* zero-guidance `schemaPrompt:'none'` footgun),
|
|
6475
|
+
* - each user **tool-def** schema (oversized, dropped refinements),
|
|
6476
|
+
* - the **streaming** gate (disabled by a non-object root or by tools),
|
|
6477
|
+
* - **native structured output** the resolved provider can't honor (R10).
|
|
6478
|
+
*
|
|
6479
|
+
* The structured event always fires (persisted + streamed). For the genuinely
|
|
6480
|
+
* surprising, low-frequency cliffs it ALSO fires a one-time deduped
|
|
6481
|
+
* `console.warn` so the median consumer — who never wires up `ctx.events` and
|
|
6482
|
+
* runs with the trace console off — still sees it. `oversized` is event-only
|
|
6483
|
+
* (O3: it's tunable/noisy); the `tools` streaming cause is event-only too
|
|
6484
|
+
* (expected behavior for any tool-using agent, not a surprise).
|
|
6485
|
+
*/
|
|
6486
|
+
emitSchemaDiagnostics(agent2, options, toolDefs, ctx) {
|
|
6487
|
+
const silent = this.config.diagnostics?.silent;
|
|
6488
|
+
const threshold = this.config.diagnostics?.schemaOversizedTokens ?? DEFAULT_SCHEMA_OVERSIZED_TOKENS;
|
|
6489
|
+
const schema = options?.schema;
|
|
6490
|
+
if (schema) {
|
|
6491
|
+
if (ctx.mode === "none") {
|
|
6492
|
+
this.emitEvent({
|
|
6493
|
+
type: "schema_diagnostic",
|
|
6494
|
+
agent: agent2._name,
|
|
6495
|
+
data: { kind: "schema_prompt_none_no_guidance" }
|
|
6496
|
+
});
|
|
6497
|
+
warnSchemaDiagnosticOnce(
|
|
6498
|
+
`${agent2._name}\0schema_prompt_none`,
|
|
6499
|
+
`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'.`,
|
|
6500
|
+
silent
|
|
6501
|
+
);
|
|
6502
|
+
} else {
|
|
6503
|
+
const promptTokens = estimateTokens(ctx.appendedSchemaText ?? "");
|
|
6504
|
+
if (promptTokens > threshold) {
|
|
6505
|
+
this.emitEvent({
|
|
6506
|
+
type: "schema_diagnostic",
|
|
6507
|
+
agent: agent2._name,
|
|
6508
|
+
data: {
|
|
6509
|
+
kind: "prompt_schema_oversized",
|
|
6510
|
+
estimatedTokens: promptTokens,
|
|
6511
|
+
threshold,
|
|
6512
|
+
site: "prompt"
|
|
6513
|
+
}
|
|
6514
|
+
});
|
|
6515
|
+
}
|
|
6516
|
+
if (ctx.mode === "json-schema") {
|
|
6517
|
+
const dropped = detectDroppedRefinements(schema);
|
|
6518
|
+
if (dropped.count > 0) {
|
|
6519
|
+
this.emitEvent({
|
|
6520
|
+
type: "schema_diagnostic",
|
|
6521
|
+
agent: agent2._name,
|
|
6522
|
+
data: {
|
|
6523
|
+
kind: "dropped_refinements",
|
|
6524
|
+
count: dropped.count,
|
|
6525
|
+
paths: dropped.paths,
|
|
6526
|
+
site: "prompt"
|
|
6527
|
+
}
|
|
6528
|
+
});
|
|
6529
|
+
warnSchemaDiagnosticOnce(
|
|
6530
|
+
`${agent2._name}\0dropped_refinements\0prompt\0${dropped.count}\0${dropped.paths.join(",")}`,
|
|
6531
|
+
`${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.`,
|
|
6532
|
+
silent
|
|
6533
|
+
);
|
|
6534
|
+
}
|
|
6535
|
+
}
|
|
6536
|
+
}
|
|
6537
|
+
const nativeOut = options?.nativeStructuredOutput ?? agent2._config.nativeStructuredOutput;
|
|
6538
|
+
if (nativeOut && toolDefs.length === 0) {
|
|
6539
|
+
const support = ctx.provider.nativeStructuredOutputSupport?.(ctx.model) ?? "schema";
|
|
6540
|
+
if (support !== "schema") {
|
|
6541
|
+
this.emitEvent({
|
|
6542
|
+
type: "schema_diagnostic",
|
|
6543
|
+
agent: agent2._name,
|
|
6544
|
+
data: {
|
|
6545
|
+
kind: "native_output_unsupported",
|
|
6546
|
+
...ctx.provider.name ? { provider: ctx.provider.name } : {},
|
|
6547
|
+
support
|
|
6548
|
+
}
|
|
6549
|
+
});
|
|
6550
|
+
warnSchemaDiagnosticOnce(
|
|
6551
|
+
`${agent2._name}\0native_output_unsupported\0${ctx.provider.name ?? ctx.model}`,
|
|
6552
|
+
`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.`,
|
|
6553
|
+
silent
|
|
6554
|
+
);
|
|
6555
|
+
}
|
|
6556
|
+
}
|
|
6557
|
+
const rootIsObject = schema instanceof import_zod.z.ZodObject;
|
|
6558
|
+
const hasTools = toolDefs.length > 0;
|
|
6559
|
+
if (this._streamingEnabled && (!rootIsObject || hasTools)) {
|
|
6560
|
+
const cause = !rootIsObject ? "non-object" : "tools";
|
|
6561
|
+
const rootType = schema.constructor?.name ?? "unknown";
|
|
6562
|
+
this.emitEvent({
|
|
6563
|
+
type: "schema_diagnostic",
|
|
6564
|
+
agent: agent2._name,
|
|
6565
|
+
data: { kind: "streaming_disabled", rootType, cause }
|
|
6566
|
+
});
|
|
6567
|
+
if (cause === "non-object") {
|
|
6568
|
+
warnSchemaDiagnosticOnce(
|
|
6569
|
+
`${agent2._name}\0streaming_disabled\0${rootType}`,
|
|
6570
|
+
`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.`,
|
|
6571
|
+
silent
|
|
6572
|
+
);
|
|
6573
|
+
}
|
|
6574
|
+
}
|
|
6575
|
+
}
|
|
6576
|
+
for (const tool2 of agent2._config.tools ?? []) {
|
|
6577
|
+
const toolTokens = estimateInlineSchemaTokens(tool2.inputSchema);
|
|
6578
|
+
if (toolTokens > threshold) {
|
|
6579
|
+
this.emitEvent({
|
|
6580
|
+
type: "schema_diagnostic",
|
|
6581
|
+
agent: agent2._name,
|
|
6582
|
+
data: {
|
|
6583
|
+
kind: "prompt_schema_oversized",
|
|
6584
|
+
estimatedTokens: toolTokens,
|
|
6585
|
+
threshold,
|
|
6586
|
+
site: "tool",
|
|
6587
|
+
tool: tool2.name
|
|
6588
|
+
}
|
|
6589
|
+
});
|
|
6590
|
+
}
|
|
6591
|
+
const droppedTool = detectDroppedRefinements(tool2.inputSchema);
|
|
6592
|
+
if (droppedTool.count > 0) {
|
|
6593
|
+
this.emitEvent({
|
|
6594
|
+
type: "schema_diagnostic",
|
|
6595
|
+
agent: agent2._name,
|
|
6596
|
+
data: {
|
|
6597
|
+
kind: "dropped_refinements",
|
|
6598
|
+
count: droppedTool.count,
|
|
6599
|
+
paths: droppedTool.paths,
|
|
6600
|
+
site: "tool",
|
|
6601
|
+
tool: tool2.name
|
|
6602
|
+
}
|
|
6603
|
+
});
|
|
6604
|
+
warnSchemaDiagnosticOnce(
|
|
6605
|
+
`${agent2._name}\0dropped_refinements\0tool\0${tool2.name}\0${droppedTool.count}`,
|
|
6606
|
+
`${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.`,
|
|
6607
|
+
silent
|
|
6608
|
+
);
|
|
6609
|
+
}
|
|
6610
|
+
}
|
|
6611
|
+
}
|
|
5536
6612
|
buildToolDefs(agent2, resolvedHandoffs) {
|
|
5537
6613
|
const defs = [];
|
|
5538
6614
|
if (agent2._config.tools) {
|
|
@@ -6025,24 +7101,44 @@ ${summaryResponse.content}`
|
|
|
6025
7101
|
limit,
|
|
6026
7102
|
exceeded: false,
|
|
6027
7103
|
policy,
|
|
6028
|
-
abortController: controller
|
|
7104
|
+
abortController: controller,
|
|
7105
|
+
unpriced: false,
|
|
7106
|
+
unpricedCount: 0,
|
|
7107
|
+
unpricedWarned: false
|
|
6029
7108
|
};
|
|
6030
7109
|
const executeBudget = async () => {
|
|
6031
7110
|
try {
|
|
6032
7111
|
const value = budgetSignal ? await signalStorage.run(budgetSignal, fn) : await fn();
|
|
6033
7112
|
const totalCost = this.budgetContext.totalCost;
|
|
6034
7113
|
const exceeded = this.budgetContext.exceeded;
|
|
6035
|
-
|
|
7114
|
+
const unpriced = this.budgetContext.unpriced;
|
|
7115
|
+
return { value, budgetExceeded: exceeded, totalCost, unpriced };
|
|
6036
7116
|
} catch (err) {
|
|
6037
7117
|
if (this.budgetContext.exceeded) {
|
|
6038
|
-
return {
|
|
7118
|
+
return {
|
|
7119
|
+
value: null,
|
|
7120
|
+
budgetExceeded: true,
|
|
7121
|
+
totalCost: this.budgetContext.totalCost,
|
|
7122
|
+
unpriced: this.budgetContext.unpriced
|
|
7123
|
+
};
|
|
6039
7124
|
}
|
|
6040
7125
|
if (err instanceof DOMException && err.name === "AbortError" && controller) {
|
|
6041
|
-
return {
|
|
7126
|
+
return {
|
|
7127
|
+
value: null,
|
|
7128
|
+
budgetExceeded: true,
|
|
7129
|
+
totalCost: this.budgetContext.totalCost,
|
|
7130
|
+
unpriced: this.budgetContext.unpriced
|
|
7131
|
+
};
|
|
6042
7132
|
}
|
|
6043
7133
|
throw err;
|
|
6044
7134
|
} finally {
|
|
6045
|
-
if (parentBudget)
|
|
7135
|
+
if (parentBudget) {
|
|
7136
|
+
parentBudget.totalCost += this.budgetContext.totalCost;
|
|
7137
|
+
if (this.budgetContext.unpriced) {
|
|
7138
|
+
parentBudget.unpriced = true;
|
|
7139
|
+
parentBudget.unpricedCount += this.budgetContext.unpricedCount;
|
|
7140
|
+
}
|
|
7141
|
+
}
|
|
6046
7142
|
this.budgetContext = parentBudget;
|
|
6047
7143
|
}
|
|
6048
7144
|
};
|
|
@@ -6063,13 +7159,22 @@ ${summaryResponse.content}`
|
|
|
6063
7159
|
}
|
|
6064
7160
|
return executeBudget();
|
|
6065
7161
|
}
|
|
6066
|
-
/**
|
|
7162
|
+
/**
|
|
7163
|
+
* Get the current budget status, or null if not inside a budget block. Inside
|
|
7164
|
+
* nested budgets this reflects the **innermost active** block (the nested block's
|
|
7165
|
+
* `finally` restores the parent before control returns to it).
|
|
7166
|
+
*
|
|
7167
|
+
* `unpriced` is true when this block ran a model with no usable cost (unpriced /
|
|
7168
|
+
* pricing-table miss): `spent` is then a LOWER BOUND and `remaining` an upper bound —
|
|
7169
|
+
* the limit cannot be enforced on the unmeasured spend. See {@link BudgetResult.unpriced}.
|
|
7170
|
+
*/
|
|
6067
7171
|
getBudgetStatus() {
|
|
6068
7172
|
if (!this.budgetContext) return null;
|
|
6069
7173
|
return {
|
|
6070
7174
|
spent: this.budgetContext.totalCost,
|
|
6071
7175
|
limit: this.budgetContext.limit,
|
|
6072
|
-
remaining: Math.max(0, this.budgetContext.limit - this.budgetContext.totalCost)
|
|
7176
|
+
remaining: Math.max(0, this.budgetContext.limit - this.budgetContext.totalCost),
|
|
7177
|
+
unpriced: this.budgetContext.unpriced
|
|
6073
7178
|
};
|
|
6074
7179
|
}
|
|
6075
7180
|
// ── ctx.race() ────────────────────────────────────────────────────────
|
|
@@ -6651,6 +7756,8 @@ ${summaryResponse.content}`
|
|
|
6651
7756
|
});
|
|
6652
7757
|
return this.ask(agents[0], prompt, {
|
|
6653
7758
|
schema: options?.schema,
|
|
7759
|
+
schemaPrompt: options?.schemaPrompt,
|
|
7760
|
+
nativeStructuredOutput: options?.nativeStructuredOutput,
|
|
6654
7761
|
retries: options?.retries,
|
|
6655
7762
|
metadata: options?.metadata,
|
|
6656
7763
|
validate: options?.validate,
|
|
@@ -6695,6 +7802,8 @@ ${summaryResponse.content}`
|
|
|
6695
7802
|
});
|
|
6696
7803
|
return this.ask(routerAgent, prompt, {
|
|
6697
7804
|
schema: options?.schema,
|
|
7805
|
+
schemaPrompt: options?.schemaPrompt,
|
|
7806
|
+
nativeStructuredOutput: options?.nativeStructuredOutput,
|
|
6698
7807
|
retries: options?.retries,
|
|
6699
7808
|
metadata: options?.metadata,
|
|
6700
7809
|
validate: options?.validate,
|
|
@@ -6713,8 +7822,25 @@ ${summaryResponse.content}`
|
|
|
6713
7822
|
const frame = askStorage.getStore();
|
|
6714
7823
|
const step = (frame?.stepRef ?? this.stepRefRoot).value++;
|
|
6715
7824
|
const cost = partial.cost;
|
|
6716
|
-
if (
|
|
6717
|
-
|
|
7825
|
+
if (COST_BEARING_LEAF_TYPES.includes(partial.type)) {
|
|
7826
|
+
const usable = isUsableCost(cost);
|
|
7827
|
+
const unpriced = !usable && hasPositiveTokens(
|
|
7828
|
+
partial
|
|
7829
|
+
);
|
|
7830
|
+
if (frame) {
|
|
7831
|
+
if (usable) frame.askCost.value += cost;
|
|
7832
|
+
else if (unpriced) frame.askUnpriced = true;
|
|
7833
|
+
}
|
|
7834
|
+
if (unpriced && this.budgetContext) {
|
|
7835
|
+
this.budgetContext.unpriced = true;
|
|
7836
|
+
this.budgetContext.unpricedCount += 1;
|
|
7837
|
+
if (!this.budgetContext.unpricedWarned && Number.isFinite(this.budgetContext.limit)) {
|
|
7838
|
+
this.budgetContext.unpricedWarned = true;
|
|
7839
|
+
console.warn(
|
|
7840
|
+
"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."
|
|
7841
|
+
);
|
|
7842
|
+
}
|
|
7843
|
+
}
|
|
6718
7844
|
}
|
|
6719
7845
|
const event = {
|
|
6720
7846
|
executionId: this.executionId,
|
|
@@ -8541,7 +9667,7 @@ var StreamingFlusher = class {
|
|
|
8541
9667
|
await Promise.allSettled(pendings);
|
|
8542
9668
|
}
|
|
8543
9669
|
};
|
|
8544
|
-
function
|
|
9670
|
+
function isAbortError2(err) {
|
|
8545
9671
|
return typeof err === "object" && err !== null && err.name === "AbortError";
|
|
8546
9672
|
}
|
|
8547
9673
|
function pushEventBounded(execInfo, event, cap) {
|
|
@@ -8804,7 +9930,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
8804
9930
|
this.persistExecution(execInfo);
|
|
8805
9931
|
return result;
|
|
8806
9932
|
} catch (err) {
|
|
8807
|
-
const aborted =
|
|
9933
|
+
const aborted = isAbortError2(err);
|
|
8808
9934
|
execInfo.status = "failed";
|
|
8809
9935
|
execInfo.completedAt = Date.now();
|
|
8810
9936
|
execInfo.duration = execInfo.completedAt - execInfo.startedAt;
|
|
@@ -9078,7 +10204,10 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
9078
10204
|
totalCost: 0,
|
|
9079
10205
|
limit: budgetLimit,
|
|
9080
10206
|
exceeded: false,
|
|
9081
|
-
policy: "finish_and_stop"
|
|
10207
|
+
policy: "finish_and_stop",
|
|
10208
|
+
unpriced: false,
|
|
10209
|
+
unpricedCount: 0,
|
|
10210
|
+
unpricedWarned: false
|
|
9082
10211
|
}
|
|
9083
10212
|
});
|
|
9084
10213
|
}
|
|
@@ -9113,6 +10242,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
9113
10242
|
status: "running",
|
|
9114
10243
|
events: [],
|
|
9115
10244
|
totalCost: 0,
|
|
10245
|
+
unpriced: false,
|
|
9116
10246
|
startedAt: Date.now(),
|
|
9117
10247
|
duration: 0,
|
|
9118
10248
|
metadata: liftPersistedMetadata(options?.metadata)
|
|
@@ -9131,6 +10261,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
9131
10261
|
onTrace: (event) => {
|
|
9132
10262
|
pushEventBounded(execInfo, event, this.maxEventsPerExecution);
|
|
9133
10263
|
execInfo.totalCost += eventCostContribution(event);
|
|
10264
|
+
if (isUnpricedLeaf(event)) execInfo.unpriced = true;
|
|
9134
10265
|
if (this.streamableExecutionIds.has(executionId)) {
|
|
9135
10266
|
this.streamingFlusher?.append(executionId, event);
|
|
9136
10267
|
}
|
|
@@ -9177,7 +10308,10 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
9177
10308
|
totalCost: 0,
|
|
9178
10309
|
limit: Infinity,
|
|
9179
10310
|
exceeded: false,
|
|
9180
|
-
policy: "finish_and_stop"
|
|
10311
|
+
policy: "finish_and_stop",
|
|
10312
|
+
unpriced: false,
|
|
10313
|
+
unpricedCount: 0,
|
|
10314
|
+
unpricedWarned: false
|
|
9181
10315
|
}
|
|
9182
10316
|
});
|
|
9183
10317
|
return this.spanManager.withSpanAsync(
|
|
@@ -9221,6 +10355,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
9221
10355
|
status: "running",
|
|
9222
10356
|
events: [],
|
|
9223
10357
|
totalCost: 0,
|
|
10358
|
+
unpriced: false,
|
|
9224
10359
|
startedAt: Date.now(),
|
|
9225
10360
|
duration: 0,
|
|
9226
10361
|
metadata: liftPersistedMetadata(options?.metadata)
|
|
@@ -9244,6 +10379,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
9244
10379
|
onTrace: (event) => {
|
|
9245
10380
|
pushEventBounded(execInfo, event, this.maxEventsPerExecution);
|
|
9246
10381
|
execInfo.totalCost += eventCostContribution(event);
|
|
10382
|
+
if (isUnpricedLeaf(event)) execInfo.unpriced = true;
|
|
9247
10383
|
if (this.streamableExecutionIds.has(executionId)) {
|
|
9248
10384
|
this.streamingFlusher?.append(executionId, event);
|
|
9249
10385
|
}
|
|
@@ -9291,7 +10427,10 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
9291
10427
|
totalCost: 0,
|
|
9292
10428
|
limit: Infinity,
|
|
9293
10429
|
exceeded: false,
|
|
9294
|
-
policy: "finish_and_stop"
|
|
10430
|
+
policy: "finish_and_stop",
|
|
10431
|
+
unpriced: false,
|
|
10432
|
+
unpricedCount: 0,
|
|
10433
|
+
unpricedWarned: false
|
|
9295
10434
|
}
|
|
9296
10435
|
});
|
|
9297
10436
|
return this.spanManager.withSpanAsync(
|
|
@@ -9453,12 +10592,14 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
9453
10592
|
const completedAt = lastEvent.timestamp ?? startedAt;
|
|
9454
10593
|
const workflowName = workflowStartEvent?.workflow ?? RECOVERED_UNKNOWN_WORKFLOW;
|
|
9455
10594
|
const totalCost = boundedEvents.reduce((sum, e) => sum + eventCostContribution(e), 0);
|
|
10595
|
+
const unpriced = boundedEvents.some(isUnpricedLeaf);
|
|
9456
10596
|
const synthesized = {
|
|
9457
10597
|
executionId: id,
|
|
9458
10598
|
workflow: workflowName,
|
|
9459
10599
|
status: "failed",
|
|
9460
10600
|
events: boundedEvents,
|
|
9461
10601
|
totalCost,
|
|
10602
|
+
unpriced,
|
|
9462
10603
|
startedAt,
|
|
9463
10604
|
completedAt,
|
|
9464
10605
|
duration: completedAt - startedAt,
|
|
@@ -9760,8 +10901,8 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
9760
10901
|
* Works with both `createContext()` and `execute()` calls inside `fn`.
|
|
9761
10902
|
*/
|
|
9762
10903
|
async trackCost(fn) {
|
|
9763
|
-
const { result, cost } = await this.trackExecution(fn);
|
|
9764
|
-
return { result, cost };
|
|
10904
|
+
const { result, cost, unpriced } = await this.trackExecution(fn);
|
|
10905
|
+
return { result, cost, unpriced };
|
|
9765
10906
|
}
|
|
9766
10907
|
/**
|
|
9767
10908
|
* Track cost and execution metadata across any runtime operations within the given function.
|
|
@@ -9806,10 +10947,12 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
9806
10947
|
const workflowCalls = /* @__PURE__ */ new Map();
|
|
9807
10948
|
const tokens = { input: 0, output: 0, reasoning: 0 };
|
|
9808
10949
|
let agentCalls = 0;
|
|
10950
|
+
let unpriced = false;
|
|
9809
10951
|
const capturedTraces = options?.captureTraces ? [] : void 0;
|
|
9810
10952
|
const listener = (event) => {
|
|
9811
10953
|
if (!scope.trackedIds.has(event.executionId)) return;
|
|
9812
10954
|
scope.totalCost += eventCostContribution(event);
|
|
10955
|
+
if (isUnpricedLeaf(event)) unpriced = true;
|
|
9813
10956
|
if (event.type === "agent_call_end") {
|
|
9814
10957
|
if (event.model) modelCalls.set(event.model, (modelCalls.get(event.model) ?? 0) + 1);
|
|
9815
10958
|
agentCalls++;
|
|
@@ -9847,6 +10990,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
9847
10990
|
return {
|
|
9848
10991
|
result,
|
|
9849
10992
|
cost: scope.totalCost,
|
|
10993
|
+
unpriced,
|
|
9850
10994
|
...capturedTraces ? { traces: capturedTraces } : {},
|
|
9851
10995
|
metadata: {
|
|
9852
10996
|
models: [...modelCalls.keys()],
|
|
@@ -10741,22 +11885,40 @@ var OpenAIEmbedder = class {
|
|
|
10741
11885
|
this.dimensions = options.dimensions ?? 1536;
|
|
10742
11886
|
}
|
|
10743
11887
|
async embed(texts, signal) {
|
|
10744
|
-
const response = await fetchWithRetry(
|
|
10745
|
-
|
|
10746
|
-
|
|
10747
|
-
|
|
10748
|
-
|
|
11888
|
+
const response = await fetchWithRetry(
|
|
11889
|
+
`${this.baseUrl}/v1/embeddings`,
|
|
11890
|
+
{
|
|
11891
|
+
method: "POST",
|
|
11892
|
+
headers: {
|
|
11893
|
+
"Content-Type": "application/json",
|
|
11894
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
11895
|
+
},
|
|
11896
|
+
body: JSON.stringify({
|
|
11897
|
+
model: this.model,
|
|
11898
|
+
input: texts,
|
|
11899
|
+
dimensions: this.dimensions
|
|
11900
|
+
}),
|
|
11901
|
+
signal
|
|
10749
11902
|
},
|
|
10750
|
-
|
|
10751
|
-
|
|
10752
|
-
|
|
10753
|
-
|
|
10754
|
-
}),
|
|
10755
|
-
signal
|
|
10756
|
-
});
|
|
11903
|
+
// Participate in the same typed-error contract as the provider adapters: a
|
|
11904
|
+
// thrown network failure normalizes to ProviderError{status:0,provider:'openai'}.
|
|
11905
|
+
{ provider: "openai" }
|
|
11906
|
+
);
|
|
10757
11907
|
if (!response.ok) {
|
|
10758
11908
|
const body = await response.text();
|
|
10759
|
-
|
|
11909
|
+
let detail = body;
|
|
11910
|
+
try {
|
|
11911
|
+
const json2 = JSON.parse(body);
|
|
11912
|
+
detail = json2.error?.message ?? json2.message ?? body;
|
|
11913
|
+
} catch {
|
|
11914
|
+
}
|
|
11915
|
+
throw buildProviderError({
|
|
11916
|
+
provider: "openai",
|
|
11917
|
+
status: response.status,
|
|
11918
|
+
headers: response.headers,
|
|
11919
|
+
message: `OpenAI embeddings API error (${response.status}): ${detail}`,
|
|
11920
|
+
body
|
|
11921
|
+
});
|
|
10760
11922
|
}
|
|
10761
11923
|
const json = await response.json();
|
|
10762
11924
|
const vectors = json.data.sort((a, b) => a.index - b.index).map((d) => d.embedding);
|
|
@@ -10898,57 +12060,87 @@ function cosineSimilarity2(a, b) {
|
|
|
10898
12060
|
// Annotate the CommonJS export names for ESM import in node:
|
|
10899
12061
|
0 && (module.exports = {
|
|
10900
12062
|
AXL_EVENT_TYPES,
|
|
12063
|
+
AZURE_PROFILE,
|
|
10901
12064
|
AnthropicProvider,
|
|
10902
12065
|
AxlError,
|
|
10903
12066
|
AxlEventBus,
|
|
10904
12067
|
AxlRuntime,
|
|
10905
12068
|
AxlStream,
|
|
12069
|
+
BEDROCK_PROFILE,
|
|
12070
|
+
BUILTIN_PROFILES,
|
|
10906
12071
|
BudgetExceededError,
|
|
10907
12072
|
CONFIG_CANDIDATES,
|
|
10908
12073
|
COST_BEARING_LEAF_TYPES,
|
|
12074
|
+
DEEPSEEK_PROFILE,
|
|
10909
12075
|
EventStreamOverflowError,
|
|
12076
|
+
GROQ_PROFILE,
|
|
10910
12077
|
GeminiProvider,
|
|
10911
12078
|
GuardrailError,
|
|
10912
12079
|
InMemoryVectorStore,
|
|
12080
|
+
LLAMACPP_PROFILE,
|
|
12081
|
+
LMSTUDIO_PROFILE,
|
|
12082
|
+
MISTRAL_PROFILE,
|
|
10913
12083
|
MaxTurnsError,
|
|
10914
12084
|
McpManager,
|
|
10915
12085
|
MemoryManager,
|
|
10916
12086
|
MemoryStore,
|
|
10917
12087
|
NoConsensus,
|
|
10918
12088
|
NoopSpanManager,
|
|
12089
|
+
OLLAMA_PROFILE,
|
|
12090
|
+
OPENAI_PRICING,
|
|
12091
|
+
OPENAI_PROFILE,
|
|
12092
|
+
OPENROUTER_PROFILE,
|
|
12093
|
+
OpenAICompatibleProvider,
|
|
10919
12094
|
OpenAIEmbedder,
|
|
10920
12095
|
OpenAIProvider,
|
|
10921
12096
|
OpenAIResponsesProvider,
|
|
12097
|
+
ProviderError,
|
|
10922
12098
|
ProviderRegistry,
|
|
10923
12099
|
QuorumNotMet,
|
|
10924
12100
|
REDACTED,
|
|
10925
12101
|
REDACTION_RULES,
|
|
10926
12102
|
RateLimiter,
|
|
10927
12103
|
RedisStore,
|
|
12104
|
+
SGLANG_PROFILE,
|
|
10928
12105
|
SQLiteStore,
|
|
10929
12106
|
Session,
|
|
10930
12107
|
SqliteVectorStore,
|
|
12108
|
+
ThinkTagScanner,
|
|
10931
12109
|
TimeoutError,
|
|
10932
12110
|
ToolDenied,
|
|
12111
|
+
VLLM_PROFILE,
|
|
10933
12112
|
ValidationError,
|
|
10934
12113
|
VerifyError,
|
|
10935
12114
|
WorkflowContext,
|
|
12115
|
+
XAI_PROFILE,
|
|
10936
12116
|
agent,
|
|
10937
12117
|
createSpanManager,
|
|
10938
12118
|
defineConfig,
|
|
12119
|
+
estimateOpenAICost,
|
|
10939
12120
|
eventCostContribution,
|
|
10940
12121
|
expandGlob,
|
|
10941
12122
|
extractJson,
|
|
12123
|
+
extractThinkTags,
|
|
10942
12124
|
findConfig,
|
|
12125
|
+
hasPositiveTokens,
|
|
10943
12126
|
importModule,
|
|
10944
12127
|
isCostBearingLeaf,
|
|
12128
|
+
isRetryableStatus,
|
|
10945
12129
|
isRootLevel,
|
|
12130
|
+
isUnpricedLeaf,
|
|
12131
|
+
isUsableCost,
|
|
10946
12132
|
needsTsxLoader,
|
|
12133
|
+
openaiReasoningEmit,
|
|
10947
12134
|
parsePartialJson,
|
|
10948
12135
|
pickDefault,
|
|
10949
12136
|
pickExport,
|
|
12137
|
+
priceFromTable,
|
|
12138
|
+
reasoningEffortEmit,
|
|
12139
|
+
reasoningObjectEmit,
|
|
10950
12140
|
redactEvent,
|
|
10951
12141
|
registerConditions,
|
|
12142
|
+
resolveApiKey,
|
|
12143
|
+
resolvePerModel,
|
|
10952
12144
|
resolveRuntime,
|
|
10953
12145
|
resolveThinkingOptions,
|
|
10954
12146
|
stringStreamFromEvents,
|