@axlsdk/axl 0.17.9 → 0.18.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 +7 -5
- package/dist/index.cjs +298 -85
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +133 -9
- package/dist/index.d.ts +133 -9
- package/dist/index.js +297 -85
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -58,6 +58,8 @@ export const config = defineConfig({
|
|
|
58
58
|
|
|
59
59
|
Provider API keys are also read automatically from environment variables (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GOOGLE_API_KEY`/`GEMINI_API_KEY`), so for local development you can skip the `providers` block entirely.
|
|
60
60
|
|
|
61
|
+
Each provider also accepts an opt-in `rateLimit` (`{ maxConcurrent?, minIntervalMs?, acquireTimeoutMs? }`) for proactive client-side pacing on top of the automatic 429/503/529 backoff — useful when a large fan-out (e.g. an eval) shares one API key. It caps in-flight request concurrency (not token throughput) for that provider's chat calls. See [Providers → Rate limiting](../../docs/providers.md#rate-limiting-opt-in).
|
|
62
|
+
|
|
61
63
|
State store options: `'memory'` (default), `'sqlite'` (requires `better-sqlite3`), or a `RedisStore` instance for multi-process deployments. See [State Stores](#state-stores).
|
|
62
64
|
|
|
63
65
|
### Tools, Agents, and Workflows
|
|
@@ -85,7 +87,7 @@ import { lookupOrder } from '../tools/db.js';
|
|
|
85
87
|
|
|
86
88
|
export const supportAgent = agent({
|
|
87
89
|
name: 'support',
|
|
88
|
-
model: 'openai-responses:gpt-5.
|
|
90
|
+
model: 'openai-responses:gpt-5.5',
|
|
89
91
|
system: 'You are a customer support agent. Use tools to look up order information.',
|
|
90
92
|
tools: [lookupOrder],
|
|
91
93
|
});
|
|
@@ -172,7 +174,7 @@ import { agent } from '@axlsdk/axl';
|
|
|
172
174
|
|
|
173
175
|
const researcher = agent({
|
|
174
176
|
name: 'researcher',
|
|
175
|
-
model: 'openai-responses:gpt-5.
|
|
177
|
+
model: 'openai-responses:gpt-5.5',
|
|
176
178
|
system: 'You are a research assistant.',
|
|
177
179
|
tools: [calculator],
|
|
178
180
|
effort: 'high',
|
|
@@ -189,7 +191,7 @@ Dynamic model and system prompt selection:
|
|
|
189
191
|
const dynamicAgent = agent({
|
|
190
192
|
model: (ctx) =>
|
|
191
193
|
ctx.metadata?.tier === 'premium'
|
|
192
|
-
? 'openai-responses:gpt-5.
|
|
194
|
+
? 'openai-responses:gpt-5.5'
|
|
193
195
|
: 'openai-responses:gpt-5-nano',
|
|
194
196
|
system: (ctx) => `You are a ${ctx.metadata?.role ?? 'general'} assistant.`,
|
|
195
197
|
});
|
|
@@ -548,7 +550,7 @@ const containsPII = (text: string) => /\b\d{3}-\d{2}-\d{4}\b/.test(text);
|
|
|
548
550
|
const isOffTopic = (text: string) => !text.toLowerCase().includes('support');
|
|
549
551
|
|
|
550
552
|
const safe = agent({
|
|
551
|
-
model: 'openai-responses:gpt-5.
|
|
553
|
+
model: 'openai-responses:gpt-5.5',
|
|
552
554
|
system: 'You are a helpful assistant.',
|
|
553
555
|
guardrails: {
|
|
554
556
|
input: async (prompt, ctx) => {
|
|
@@ -739,7 +741,7 @@ import {
|
|
|
739
741
|
Four built-in providers using the `provider:model` URI scheme:
|
|
740
742
|
|
|
741
743
|
```
|
|
742
|
-
openai-responses:gpt-5.
|
|
744
|
+
openai-responses:gpt-5.5 # OpenAI Responses API (preferred over Chat Completions)
|
|
743
745
|
openai:gpt-5.4 # OpenAI Chat Completions
|
|
744
746
|
anthropic:claude-opus-4-7 # Anthropic (supports effort: 'xhigh')
|
|
745
747
|
google:gemini-3.1-pro-preview # Google Gemini
|
package/dist/index.cjs
CHANGED
|
@@ -136,6 +136,7 @@ __export(index_exports, {
|
|
|
136
136
|
QuorumNotMet: () => QuorumNotMet,
|
|
137
137
|
REDACTED: () => REDACTED,
|
|
138
138
|
REDACTION_RULES: () => REDACTION_RULES,
|
|
139
|
+
RateLimiter: () => RateLimiter,
|
|
139
140
|
RedisStore: () => RedisStore,
|
|
140
141
|
SQLiteStore: () => SQLiteStore,
|
|
141
142
|
Session: () => Session,
|
|
@@ -310,27 +311,180 @@ function sleep2(ms, signal) {
|
|
|
310
311
|
);
|
|
311
312
|
});
|
|
312
313
|
}
|
|
313
|
-
async function fetchWithRetry(input, init,
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
314
|
+
async function fetchWithRetry(input, init, opts) {
|
|
315
|
+
const maxRetries = opts?.maxRetries ?? MAX_RETRIES;
|
|
316
|
+
const governor = opts?.governor;
|
|
317
|
+
let acquired = false;
|
|
318
|
+
if (governor) {
|
|
319
|
+
await governor.acquire(init?.signal ?? void 0);
|
|
320
|
+
acquired = true;
|
|
321
|
+
}
|
|
322
|
+
try {
|
|
323
|
+
for (let attempt = 0; ; attempt++) {
|
|
324
|
+
const res = await fetch(input, init);
|
|
325
|
+
governor?.observe(res);
|
|
326
|
+
if (res.ok || !RETRYABLE_STATUS_CODES.has(res.status) || attempt >= maxRetries) {
|
|
327
|
+
return res;
|
|
328
|
+
}
|
|
329
|
+
if (init?.signal?.aborted) {
|
|
330
|
+
return res;
|
|
331
|
+
}
|
|
332
|
+
const retryAfter = res.headers.get("retry-after");
|
|
333
|
+
let delay;
|
|
334
|
+
if (retryAfter && !isNaN(Number(retryAfter))) {
|
|
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);
|
|
328
341
|
}
|
|
329
|
-
|
|
330
|
-
|
|
342
|
+
} finally {
|
|
343
|
+
if (acquired) governor.release();
|
|
331
344
|
}
|
|
332
345
|
}
|
|
333
346
|
|
|
347
|
+
// src/providers/rate-limiter.ts
|
|
348
|
+
function abortError() {
|
|
349
|
+
return Object.assign(new Error("The operation was aborted"), { name: "AbortError" });
|
|
350
|
+
}
|
|
351
|
+
var RateLimiter = class {
|
|
352
|
+
maxConcurrent;
|
|
353
|
+
minIntervalMs;
|
|
354
|
+
acquireTimeoutMs;
|
|
355
|
+
active = 0;
|
|
356
|
+
// `-Infinity` (not 0) so the FIRST grant is always immediate regardless of the
|
|
357
|
+
// wall clock: `Date.now() - (-Infinity) === Infinity`, which always satisfies
|
|
358
|
+
// `minIntervalMs`. Initializing to 0 would (only under a mocked/near-zero clock)
|
|
359
|
+
// wrongly delay the first grant by a full interval, since the limiter has never
|
|
360
|
+
// actually granted anything yet.
|
|
361
|
+
lastGrantAt = Number.NEGATIVE_INFINITY;
|
|
362
|
+
queue = [];
|
|
363
|
+
spacingTimer;
|
|
364
|
+
warnedQueued = false;
|
|
365
|
+
constructor(config = {}) {
|
|
366
|
+
const mc = config.maxConcurrent;
|
|
367
|
+
if (mc == null) {
|
|
368
|
+
this.maxConcurrent = Infinity;
|
|
369
|
+
} else if (!Number.isFinite(mc) || mc < 1) {
|
|
370
|
+
console.warn(
|
|
371
|
+
`[axl] RateLimiter: ignoring invalid maxConcurrent (${mc}); expected an integer >= 1. No concurrency cap applied.`
|
|
372
|
+
);
|
|
373
|
+
this.maxConcurrent = Infinity;
|
|
374
|
+
} else {
|
|
375
|
+
this.maxConcurrent = Math.floor(mc);
|
|
376
|
+
if (this.maxConcurrent < 2) {
|
|
377
|
+
console.warn(
|
|
378
|
+
`[axl] RateLimiter: maxConcurrent=${this.maxConcurrent} serializes every request to this provider. Nested same-provider calls still complete (permits aren't held across a nested ask), but throughput is a floor, not a guarantee.`
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
this.minIntervalMs = config.minIntervalMs != null && Number.isFinite(config.minIntervalMs) && config.minIntervalMs > 0 ? config.minIntervalMs : 0;
|
|
383
|
+
this.acquireTimeoutMs = config.acquireTimeoutMs != null && Number.isFinite(config.acquireTimeoutMs) && config.acquireTimeoutMs > 0 ? config.acquireTimeoutMs : void 0;
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Acquire a permit. Resolves when one is granted (respecting `maxConcurrent`
|
|
387
|
+
* and `minIntervalMs`). The caller MUST pair every resolved `acquire()` with
|
|
388
|
+
* exactly one {@link release}.
|
|
389
|
+
*
|
|
390
|
+
* Abort handling:
|
|
391
|
+
* - Pre-aborted signal ⇒ rejects immediately, no permit taken.
|
|
392
|
+
* - Aborted while queued ⇒ spliced out of the queue, no counter change, rejects.
|
|
393
|
+
* - (Once granted, the abort listener is removed — the holder owns the permit
|
|
394
|
+
* and releases it via `fetchWithRetry`'s `finally`.)
|
|
395
|
+
*
|
|
396
|
+
* With `acquireTimeoutMs`, a waiter that sits in the queue too long rejects.
|
|
397
|
+
*/
|
|
398
|
+
acquire(signal) {
|
|
399
|
+
if (signal?.aborted) return Promise.reject(abortError());
|
|
400
|
+
return new Promise((resolve2, reject) => {
|
|
401
|
+
const waiter = { resolve: resolve2, reject, signal };
|
|
402
|
+
if (signal) {
|
|
403
|
+
waiter.onAbort = () => this.settleWaiter(waiter, abortError());
|
|
404
|
+
signal.addEventListener("abort", waiter.onAbort, { once: true });
|
|
405
|
+
}
|
|
406
|
+
if (this.acquireTimeoutMs != null) {
|
|
407
|
+
waiter.timer = setTimeout(
|
|
408
|
+
() => this.settleWaiter(
|
|
409
|
+
waiter,
|
|
410
|
+
new Error(`RateLimiter.acquire timed out after ${this.acquireTimeoutMs}ms`)
|
|
411
|
+
),
|
|
412
|
+
this.acquireTimeoutMs
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
this.queue.push(waiter);
|
|
416
|
+
this.pump();
|
|
417
|
+
if (!waiter.granted && !this.warnedQueued) {
|
|
418
|
+
this.warnedQueued = true;
|
|
419
|
+
console.warn(
|
|
420
|
+
`[axl] RateLimiter: request queued (maxConcurrent=${this.maxConcurrent}${this.minIntervalMs ? `, minIntervalMs=${this.minIntervalMs}` : ""}). This is expected backpressure; lower fan-out or raise the cap if throughput suffers.`
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
/** Release a previously acquired permit and wake the next waiter. */
|
|
426
|
+
release() {
|
|
427
|
+
if (this.active === 0) {
|
|
428
|
+
console.warn(
|
|
429
|
+
"[axl] RateLimiter.release() called with no active permits \u2014 likely a double-release bug."
|
|
430
|
+
);
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
this.active--;
|
|
434
|
+
this.pump();
|
|
435
|
+
}
|
|
436
|
+
/** v1 no-op seam for a future adaptive (rate-limit-header-driven) pacing follow-up. */
|
|
437
|
+
observe(_res) {
|
|
438
|
+
}
|
|
439
|
+
/** Drain the queue: grant head waiters while a permit is free and spacing allows. */
|
|
440
|
+
pump() {
|
|
441
|
+
while (this.queue.length > 0 && this.active < this.maxConcurrent) {
|
|
442
|
+
if (this.minIntervalMs > 0) {
|
|
443
|
+
const waitMs = this.minIntervalMs - (Date.now() - this.lastGrantAt);
|
|
444
|
+
if (waitMs > 0) {
|
|
445
|
+
if (!this.spacingTimer) {
|
|
446
|
+
this.spacingTimer = setTimeout(() => {
|
|
447
|
+
this.spacingTimer = void 0;
|
|
448
|
+
this.pump();
|
|
449
|
+
}, waitMs);
|
|
450
|
+
}
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
const waiter = this.queue.shift();
|
|
455
|
+
this.grant(waiter);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
grant(waiter) {
|
|
459
|
+
waiter.settled = true;
|
|
460
|
+
waiter.granted = true;
|
|
461
|
+
this.active++;
|
|
462
|
+
this.lastGrantAt = Date.now();
|
|
463
|
+
this.clearWaiterTimers(waiter);
|
|
464
|
+
waiter.resolve();
|
|
465
|
+
}
|
|
466
|
+
/** Reject a still-queued waiter (abort or timeout) and remove it cleanly. */
|
|
467
|
+
settleWaiter(waiter, err) {
|
|
468
|
+
if (waiter.settled) return;
|
|
469
|
+
waiter.settled = true;
|
|
470
|
+
const idx = this.queue.indexOf(waiter);
|
|
471
|
+
if (idx !== -1) this.queue.splice(idx, 1);
|
|
472
|
+
this.clearWaiterTimers(waiter);
|
|
473
|
+
waiter.reject(err);
|
|
474
|
+
this.pump();
|
|
475
|
+
}
|
|
476
|
+
clearWaiterTimers(waiter) {
|
|
477
|
+
if (waiter.timer) {
|
|
478
|
+
clearTimeout(waiter.timer);
|
|
479
|
+
waiter.timer = void 0;
|
|
480
|
+
}
|
|
481
|
+
if (waiter.signal && waiter.onAbort) {
|
|
482
|
+
waiter.signal.removeEventListener("abort", waiter.onAbort);
|
|
483
|
+
waiter.onAbort = void 0;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
|
|
334
488
|
// src/providers/openai.ts
|
|
335
489
|
var OPENAI_PRICING = {
|
|
336
490
|
// gpt-4o era — cache reads at 50% of input rate
|
|
@@ -358,7 +512,10 @@ var OPENAI_PRICING = {
|
|
|
358
512
|
"gpt-5.2": [175e-8, 14e-6, 0.1],
|
|
359
513
|
"gpt-5.3": [175e-8, 14e-6, 0.1],
|
|
360
514
|
"gpt-5.4": [25e-7, 15e-6, 0.1],
|
|
361
|
-
"gpt-5.4-pro": [3e-5, 18e-5, 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]
|
|
362
519
|
};
|
|
363
520
|
var PRICING_KEYS_BY_LENGTH = Object.keys(OPENAI_PRICING).sort((a, b) => b.length - a.length);
|
|
364
521
|
function estimateOpenAICost(model, promptTokens, completionTokens, cachedTokens) {
|
|
@@ -407,9 +564,11 @@ var OpenAIProvider = class {
|
|
|
407
564
|
name = "openai";
|
|
408
565
|
baseUrl;
|
|
409
566
|
apiKey;
|
|
567
|
+
governor;
|
|
410
568
|
constructor(options = {}) {
|
|
411
569
|
this.apiKey = options.apiKey ?? process.env.OPENAI_API_KEY ?? "";
|
|
412
570
|
this.baseUrl = (options.baseUrl ?? process.env.OPENAI_BASE_URL ?? "https://api.openai.com/v1").replace(/\/$/, "");
|
|
571
|
+
this.governor = options.rateLimit ? new RateLimiter(options.rateLimit) : void 0;
|
|
413
572
|
if (!this.apiKey) {
|
|
414
573
|
throw new Error("OpenAI API key is required. Set OPENAI_API_KEY or pass apiKey in options.");
|
|
415
574
|
}
|
|
@@ -419,15 +578,19 @@ var OpenAIProvider = class {
|
|
|
419
578
|
// ---------------------------------------------------------------------------
|
|
420
579
|
async chat(messages, options) {
|
|
421
580
|
const body = this.buildRequestBody(messages, options, false);
|
|
422
|
-
const res = await fetchWithRetry(
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
581
|
+
const res = await fetchWithRetry(
|
|
582
|
+
`${this.baseUrl}/chat/completions`,
|
|
583
|
+
{
|
|
584
|
+
method: "POST",
|
|
585
|
+
headers: {
|
|
586
|
+
"Content-Type": "application/json",
|
|
587
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
588
|
+
},
|
|
589
|
+
body: JSON.stringify(body),
|
|
590
|
+
signal: options.signal
|
|
427
591
|
},
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
});
|
|
592
|
+
{ governor: this.governor }
|
|
593
|
+
);
|
|
431
594
|
if (!res.ok) {
|
|
432
595
|
const errorBody = await res.text();
|
|
433
596
|
const message = this.extractErrorMessage(errorBody, res.status);
|
|
@@ -467,15 +630,19 @@ var OpenAIProvider = class {
|
|
|
467
630
|
// ---------------------------------------------------------------------------
|
|
468
631
|
async *stream(messages, options) {
|
|
469
632
|
const body = this.buildRequestBody(messages, options, true);
|
|
470
|
-
const res = await fetchWithRetry(
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
633
|
+
const res = await fetchWithRetry(
|
|
634
|
+
`${this.baseUrl}/chat/completions`,
|
|
635
|
+
{
|
|
636
|
+
method: "POST",
|
|
637
|
+
headers: {
|
|
638
|
+
"Content-Type": "application/json",
|
|
639
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
640
|
+
},
|
|
641
|
+
body: JSON.stringify(body),
|
|
642
|
+
signal: options.signal
|
|
475
643
|
},
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
});
|
|
644
|
+
{ governor: this.governor }
|
|
645
|
+
);
|
|
479
646
|
if (!res.ok) {
|
|
480
647
|
const errorBody = await res.text();
|
|
481
648
|
const message = this.extractErrorMessage(errorBody, res.status);
|
|
@@ -648,9 +815,11 @@ var OpenAIResponsesProvider = class {
|
|
|
648
815
|
name = "openai-responses";
|
|
649
816
|
baseUrl;
|
|
650
817
|
apiKey;
|
|
818
|
+
governor;
|
|
651
819
|
constructor(options = {}) {
|
|
652
820
|
this.apiKey = options.apiKey ?? process.env.OPENAI_API_KEY ?? "";
|
|
653
821
|
this.baseUrl = (options.baseUrl ?? process.env.OPENAI_BASE_URL ?? "https://api.openai.com/v1").replace(/\/$/, "");
|
|
822
|
+
this.governor = options.rateLimit ? new RateLimiter(options.rateLimit) : void 0;
|
|
654
823
|
if (!this.apiKey) {
|
|
655
824
|
throw new Error("OpenAI API key is required. Set OPENAI_API_KEY or pass apiKey in options.");
|
|
656
825
|
}
|
|
@@ -660,15 +829,19 @@ var OpenAIResponsesProvider = class {
|
|
|
660
829
|
// ---------------------------------------------------------------------------
|
|
661
830
|
async chat(messages, options) {
|
|
662
831
|
const body = this.buildRequestBody(messages, options, false);
|
|
663
|
-
const res = await fetchWithRetry(
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
832
|
+
const res = await fetchWithRetry(
|
|
833
|
+
`${this.baseUrl}/responses`,
|
|
834
|
+
{
|
|
835
|
+
method: "POST",
|
|
836
|
+
headers: {
|
|
837
|
+
"Content-Type": "application/json",
|
|
838
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
839
|
+
},
|
|
840
|
+
body: JSON.stringify(body),
|
|
841
|
+
signal: options.signal
|
|
668
842
|
},
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
});
|
|
843
|
+
{ governor: this.governor }
|
|
844
|
+
);
|
|
672
845
|
if (!res.ok) {
|
|
673
846
|
const errorBody = await res.text();
|
|
674
847
|
const message = this.extractErrorMessage(errorBody, res.status);
|
|
@@ -682,15 +855,19 @@ var OpenAIResponsesProvider = class {
|
|
|
682
855
|
// ---------------------------------------------------------------------------
|
|
683
856
|
async *stream(messages, options) {
|
|
684
857
|
const body = this.buildRequestBody(messages, options, true);
|
|
685
|
-
const res = await fetchWithRetry(
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
858
|
+
const res = await fetchWithRetry(
|
|
859
|
+
`${this.baseUrl}/responses`,
|
|
860
|
+
{
|
|
861
|
+
method: "POST",
|
|
862
|
+
headers: {
|
|
863
|
+
"Content-Type": "application/json",
|
|
864
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
865
|
+
},
|
|
866
|
+
body: JSON.stringify(body),
|
|
867
|
+
signal: options.signal
|
|
690
868
|
},
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
});
|
|
869
|
+
{ governor: this.governor }
|
|
870
|
+
);
|
|
694
871
|
if (!res.ok) {
|
|
695
872
|
const errorBody = await res.text();
|
|
696
873
|
const message = this.extractErrorMessage(errorBody, res.status);
|
|
@@ -1007,6 +1184,7 @@ var OpenAIResponsesProvider = class {
|
|
|
1007
1184
|
// src/providers/anthropic.ts
|
|
1008
1185
|
var ANTHROPIC_API_VERSION = "2023-06-01";
|
|
1009
1186
|
var ANTHROPIC_PRICING = {
|
|
1187
|
+
"claude-opus-4-8": [5e-6, 25e-6],
|
|
1010
1188
|
"claude-opus-4-7": [5e-6, 25e-6],
|
|
1011
1189
|
"claude-opus-4-6": [5e-6, 25e-6],
|
|
1012
1190
|
"claude-sonnet-4-6": [3e-6, 15e-6],
|
|
@@ -1052,16 +1230,16 @@ var THINKING_BUDGETS = {
|
|
|
1052
1230
|
max: 3e4
|
|
1053
1231
|
};
|
|
1054
1232
|
function supportsAdaptiveThinking(model) {
|
|
1055
|
-
return model.startsWith("claude-opus-4-7") || model.startsWith("claude-opus-4-6") || model.startsWith("claude-sonnet-4-6");
|
|
1233
|
+
return model.startsWith("claude-opus-4-8") || model.startsWith("claude-opus-4-7") || model.startsWith("claude-opus-4-6") || model.startsWith("claude-sonnet-4-6");
|
|
1056
1234
|
}
|
|
1057
1235
|
function supportsMaxEffort(model) {
|
|
1058
|
-
return model.startsWith("claude-opus-4-7") || model.startsWith("claude-opus-4-6");
|
|
1236
|
+
return model.startsWith("claude-opus-4-8") || model.startsWith("claude-opus-4-7") || model.startsWith("claude-opus-4-6");
|
|
1059
1237
|
}
|
|
1060
1238
|
function supportsXhigh2(model) {
|
|
1061
|
-
return model.startsWith("claude-opus-4-7");
|
|
1239
|
+
return model.startsWith("claude-opus-4-8") || model.startsWith("claude-opus-4-7");
|
|
1062
1240
|
}
|
|
1063
1241
|
function supportsEffort(model) {
|
|
1064
|
-
return model.startsWith("claude-opus-4-7") || model.startsWith("claude-opus-4-6") || model.startsWith("claude-sonnet-4-6") || model.startsWith("claude-opus-4-5");
|
|
1242
|
+
return model.startsWith("claude-opus-4-8") || model.startsWith("claude-opus-4-7") || model.startsWith("claude-opus-4-6") || model.startsWith("claude-sonnet-4-6") || model.startsWith("claude-opus-4-5");
|
|
1065
1243
|
}
|
|
1066
1244
|
function clampAnthropicEffort(model, effort) {
|
|
1067
1245
|
if (effort === "max" && !supportsMaxEffort(model)) return "high";
|
|
@@ -1073,9 +1251,11 @@ var AnthropicProvider = class {
|
|
|
1073
1251
|
baseUrl;
|
|
1074
1252
|
apiKey;
|
|
1075
1253
|
currentModel;
|
|
1254
|
+
governor;
|
|
1076
1255
|
constructor(options = {}) {
|
|
1077
1256
|
this.apiKey = options.apiKey ?? process.env.ANTHROPIC_API_KEY ?? "";
|
|
1078
1257
|
this.baseUrl = (options.baseUrl ?? "https://api.anthropic.com/v1").replace(/\/$/, "");
|
|
1258
|
+
this.governor = options.rateLimit ? new RateLimiter(options.rateLimit) : void 0;
|
|
1079
1259
|
if (!this.apiKey) {
|
|
1080
1260
|
throw new Error(
|
|
1081
1261
|
"Anthropic API key is required. Set ANTHROPIC_API_KEY or pass apiKey in options."
|
|
@@ -1088,12 +1268,16 @@ var AnthropicProvider = class {
|
|
|
1088
1268
|
async chat(messages, options) {
|
|
1089
1269
|
this.currentModel = options.model;
|
|
1090
1270
|
const body = this.buildRequestBody(messages, options, false);
|
|
1091
|
-
const res = await fetchWithRetry(
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1271
|
+
const res = await fetchWithRetry(
|
|
1272
|
+
`${this.baseUrl}/messages`,
|
|
1273
|
+
{
|
|
1274
|
+
method: "POST",
|
|
1275
|
+
headers: this.buildHeaders(),
|
|
1276
|
+
body: JSON.stringify(body),
|
|
1277
|
+
signal: options.signal
|
|
1278
|
+
},
|
|
1279
|
+
{ governor: this.governor }
|
|
1280
|
+
);
|
|
1097
1281
|
if (!res.ok) {
|
|
1098
1282
|
const errorBody = await res.text();
|
|
1099
1283
|
const message = this.extractErrorMessage(errorBody, res.status);
|
|
@@ -1107,12 +1291,16 @@ var AnthropicProvider = class {
|
|
|
1107
1291
|
// ---------------------------------------------------------------------------
|
|
1108
1292
|
async *stream(messages, options) {
|
|
1109
1293
|
const body = this.buildRequestBody(messages, options, true);
|
|
1110
|
-
const res = await fetchWithRetry(
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1294
|
+
const res = await fetchWithRetry(
|
|
1295
|
+
`${this.baseUrl}/messages`,
|
|
1296
|
+
{
|
|
1297
|
+
method: "POST",
|
|
1298
|
+
headers: this.buildHeaders(),
|
|
1299
|
+
body: JSON.stringify(body),
|
|
1300
|
+
signal: options.signal
|
|
1301
|
+
},
|
|
1302
|
+
{ governor: this.governor }
|
|
1303
|
+
);
|
|
1116
1304
|
if (!res.ok) {
|
|
1117
1305
|
const errorBody = await res.text();
|
|
1118
1306
|
const message = this.extractErrorMessage(errorBody, res.status);
|
|
@@ -1599,12 +1787,14 @@ var GeminiProvider = class {
|
|
|
1599
1787
|
baseUrl;
|
|
1600
1788
|
apiKey;
|
|
1601
1789
|
callCounter = 0;
|
|
1790
|
+
governor;
|
|
1602
1791
|
constructor(options = {}) {
|
|
1603
1792
|
this.apiKey = options.apiKey ?? process.env.GOOGLE_API_KEY ?? process.env.GEMINI_API_KEY ?? "";
|
|
1604
1793
|
this.baseUrl = (options.baseUrl ?? "https://generativelanguage.googleapis.com/v1beta").replace(
|
|
1605
1794
|
/\/$/,
|
|
1606
1795
|
""
|
|
1607
1796
|
);
|
|
1797
|
+
this.governor = options.rateLimit ? new RateLimiter(options.rateLimit) : void 0;
|
|
1608
1798
|
if (!this.apiKey) {
|
|
1609
1799
|
throw new Error("Google API key is required. Set GOOGLE_API_KEY or pass apiKey in options.");
|
|
1610
1800
|
}
|
|
@@ -1614,12 +1804,16 @@ var GeminiProvider = class {
|
|
|
1614
1804
|
// ---------------------------------------------------------------------------
|
|
1615
1805
|
async chat(messages, options) {
|
|
1616
1806
|
const body = this.buildRequestBody(messages, options);
|
|
1617
|
-
const res = await fetchWithRetry(
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1807
|
+
const res = await fetchWithRetry(
|
|
1808
|
+
`${this.baseUrl}/models/${options.model}:generateContent`,
|
|
1809
|
+
{
|
|
1810
|
+
method: "POST",
|
|
1811
|
+
headers: this.buildHeaders(),
|
|
1812
|
+
body: JSON.stringify(body),
|
|
1813
|
+
signal: options.signal
|
|
1814
|
+
},
|
|
1815
|
+
{ governor: this.governor }
|
|
1816
|
+
);
|
|
1623
1817
|
if (!res.ok) {
|
|
1624
1818
|
const errorBody = await res.text();
|
|
1625
1819
|
const message = this.extractErrorMessage(errorBody, res.status);
|
|
@@ -1640,7 +1834,8 @@ var GeminiProvider = class {
|
|
|
1640
1834
|
headers: this.buildHeaders(),
|
|
1641
1835
|
body: JSON.stringify(body),
|
|
1642
1836
|
signal: options.signal
|
|
1643
|
-
}
|
|
1837
|
+
},
|
|
1838
|
+
{ governor: this.governor }
|
|
1644
1839
|
);
|
|
1645
1840
|
if (!res.ok) {
|
|
1646
1841
|
const errorBody = await res.text();
|
|
@@ -2019,19 +2214,39 @@ var GeminiProvider = class {
|
|
|
2019
2214
|
var builtinFactories = {
|
|
2020
2215
|
openai: (config) => {
|
|
2021
2216
|
const opts = config.providers?.openai ?? {};
|
|
2022
|
-
return new OpenAIProvider({
|
|
2217
|
+
return new OpenAIProvider({
|
|
2218
|
+
apiKey: opts.apiKey,
|
|
2219
|
+
baseUrl: opts.baseUrl,
|
|
2220
|
+
rateLimit: opts.rateLimit
|
|
2221
|
+
});
|
|
2023
2222
|
},
|
|
2223
|
+
// NB: each adapter builds its OWN RateLimiter from its config — caps are
|
|
2224
|
+
// per-adapter-instance, not pooled. Configuring `openai` AND using both
|
|
2225
|
+
// `openai:` and `openai-responses:` yields two independent governors against
|
|
2226
|
+
// the same key (effective concurrency = the sum). Documented in providers.md.
|
|
2024
2227
|
"openai-responses": (config) => {
|
|
2025
2228
|
const opts = config.providers?.["openai-responses"] ?? config.providers?.openai ?? {};
|
|
2026
|
-
return new OpenAIResponsesProvider({
|
|
2229
|
+
return new OpenAIResponsesProvider({
|
|
2230
|
+
apiKey: opts.apiKey,
|
|
2231
|
+
baseUrl: opts.baseUrl,
|
|
2232
|
+
rateLimit: opts.rateLimit
|
|
2233
|
+
});
|
|
2027
2234
|
},
|
|
2028
2235
|
anthropic: (config) => {
|
|
2029
2236
|
const opts = config.providers?.anthropic ?? {};
|
|
2030
|
-
return new AnthropicProvider({
|
|
2237
|
+
return new AnthropicProvider({
|
|
2238
|
+
apiKey: opts.apiKey,
|
|
2239
|
+
baseUrl: opts.baseUrl,
|
|
2240
|
+
rateLimit: opts.rateLimit
|
|
2241
|
+
});
|
|
2031
2242
|
},
|
|
2032
2243
|
google: (config) => {
|
|
2033
2244
|
const opts = config.providers?.google ?? {};
|
|
2034
|
-
return new GeminiProvider({
|
|
2245
|
+
return new GeminiProvider({
|
|
2246
|
+
apiKey: opts.apiKey,
|
|
2247
|
+
baseUrl: opts.baseUrl,
|
|
2248
|
+
rateLimit: opts.rateLimit
|
|
2249
|
+
});
|
|
2035
2250
|
}
|
|
2036
2251
|
};
|
|
2037
2252
|
var ProviderRegistry = class {
|
|
@@ -8764,14 +8979,11 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
8764
8979
|
captureTraces: options?.captureTraces
|
|
8765
8980
|
});
|
|
8766
8981
|
} else {
|
|
8767
|
-
result = await this.eval(
|
|
8768
|
-
|
|
8769
|
-
|
|
8770
|
-
|
|
8771
|
-
|
|
8772
|
-
captureTraces: options?.captureTraces
|
|
8773
|
-
}
|
|
8774
|
-
);
|
|
8982
|
+
result = await this.eval(entry.config, {
|
|
8983
|
+
onProgress: options?.onProgress,
|
|
8984
|
+
signal: options?.signal,
|
|
8985
|
+
captureTraces: options?.captureTraces
|
|
8986
|
+
});
|
|
8775
8987
|
}
|
|
8776
8988
|
const resultObj = result;
|
|
8777
8989
|
if (options?.metadata) {
|
|
@@ -10711,6 +10923,7 @@ function cosineSimilarity2(a, b) {
|
|
|
10711
10923
|
QuorumNotMet,
|
|
10712
10924
|
REDACTED,
|
|
10713
10925
|
REDACTION_RULES,
|
|
10926
|
+
RateLimiter,
|
|
10714
10927
|
RedisStore,
|
|
10715
10928
|
SQLiteStore,
|
|
10716
10929
|
Session,
|