@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/dist/index.js
CHANGED
|
@@ -142,27 +142,180 @@ function sleep2(ms, signal) {
|
|
|
142
142
|
);
|
|
143
143
|
});
|
|
144
144
|
}
|
|
145
|
-
async function fetchWithRetry(input, init,
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
145
|
+
async function fetchWithRetry(input, init, opts) {
|
|
146
|
+
const maxRetries = opts?.maxRetries ?? MAX_RETRIES;
|
|
147
|
+
const governor = opts?.governor;
|
|
148
|
+
let acquired = false;
|
|
149
|
+
if (governor) {
|
|
150
|
+
await governor.acquire(init?.signal ?? void 0);
|
|
151
|
+
acquired = true;
|
|
152
|
+
}
|
|
153
|
+
try {
|
|
154
|
+
for (let attempt = 0; ; attempt++) {
|
|
155
|
+
const res = await fetch(input, init);
|
|
156
|
+
governor?.observe(res);
|
|
157
|
+
if (res.ok || !RETRYABLE_STATUS_CODES.has(res.status) || attempt >= maxRetries) {
|
|
158
|
+
return res;
|
|
159
|
+
}
|
|
160
|
+
if (init?.signal?.aborted) {
|
|
161
|
+
return res;
|
|
162
|
+
}
|
|
163
|
+
const retryAfter = res.headers.get("retry-after");
|
|
164
|
+
let delay;
|
|
165
|
+
if (retryAfter && !isNaN(Number(retryAfter))) {
|
|
166
|
+
delay = Number(retryAfter) * 1e3;
|
|
167
|
+
} else {
|
|
168
|
+
delay = BASE_DELAY_MS * 2 ** attempt;
|
|
169
|
+
}
|
|
170
|
+
delay *= 0.75 + Math.random() * 0.5;
|
|
171
|
+
await sleep2(delay, init?.signal ?? void 0);
|
|
160
172
|
}
|
|
161
|
-
|
|
162
|
-
|
|
173
|
+
} finally {
|
|
174
|
+
if (acquired) governor.release();
|
|
163
175
|
}
|
|
164
176
|
}
|
|
165
177
|
|
|
178
|
+
// src/providers/rate-limiter.ts
|
|
179
|
+
function abortError() {
|
|
180
|
+
return Object.assign(new Error("The operation was aborted"), { name: "AbortError" });
|
|
181
|
+
}
|
|
182
|
+
var RateLimiter = class {
|
|
183
|
+
maxConcurrent;
|
|
184
|
+
minIntervalMs;
|
|
185
|
+
acquireTimeoutMs;
|
|
186
|
+
active = 0;
|
|
187
|
+
// `-Infinity` (not 0) so the FIRST grant is always immediate regardless of the
|
|
188
|
+
// wall clock: `Date.now() - (-Infinity) === Infinity`, which always satisfies
|
|
189
|
+
// `minIntervalMs`. Initializing to 0 would (only under a mocked/near-zero clock)
|
|
190
|
+
// wrongly delay the first grant by a full interval, since the limiter has never
|
|
191
|
+
// actually granted anything yet.
|
|
192
|
+
lastGrantAt = Number.NEGATIVE_INFINITY;
|
|
193
|
+
queue = [];
|
|
194
|
+
spacingTimer;
|
|
195
|
+
warnedQueued = false;
|
|
196
|
+
constructor(config = {}) {
|
|
197
|
+
const mc = config.maxConcurrent;
|
|
198
|
+
if (mc == null) {
|
|
199
|
+
this.maxConcurrent = Infinity;
|
|
200
|
+
} else if (!Number.isFinite(mc) || mc < 1) {
|
|
201
|
+
console.warn(
|
|
202
|
+
`[axl] RateLimiter: ignoring invalid maxConcurrent (${mc}); expected an integer >= 1. No concurrency cap applied.`
|
|
203
|
+
);
|
|
204
|
+
this.maxConcurrent = Infinity;
|
|
205
|
+
} else {
|
|
206
|
+
this.maxConcurrent = Math.floor(mc);
|
|
207
|
+
if (this.maxConcurrent < 2) {
|
|
208
|
+
console.warn(
|
|
209
|
+
`[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.`
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
this.minIntervalMs = config.minIntervalMs != null && Number.isFinite(config.minIntervalMs) && config.minIntervalMs > 0 ? config.minIntervalMs : 0;
|
|
214
|
+
this.acquireTimeoutMs = config.acquireTimeoutMs != null && Number.isFinite(config.acquireTimeoutMs) && config.acquireTimeoutMs > 0 ? config.acquireTimeoutMs : void 0;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Acquire a permit. Resolves when one is granted (respecting `maxConcurrent`
|
|
218
|
+
* and `minIntervalMs`). The caller MUST pair every resolved `acquire()` with
|
|
219
|
+
* exactly one {@link release}.
|
|
220
|
+
*
|
|
221
|
+
* Abort handling:
|
|
222
|
+
* - Pre-aborted signal ⇒ rejects immediately, no permit taken.
|
|
223
|
+
* - Aborted while queued ⇒ spliced out of the queue, no counter change, rejects.
|
|
224
|
+
* - (Once granted, the abort listener is removed — the holder owns the permit
|
|
225
|
+
* and releases it via `fetchWithRetry`'s `finally`.)
|
|
226
|
+
*
|
|
227
|
+
* With `acquireTimeoutMs`, a waiter that sits in the queue too long rejects.
|
|
228
|
+
*/
|
|
229
|
+
acquire(signal) {
|
|
230
|
+
if (signal?.aborted) return Promise.reject(abortError());
|
|
231
|
+
return new Promise((resolve2, reject) => {
|
|
232
|
+
const waiter = { resolve: resolve2, reject, signal };
|
|
233
|
+
if (signal) {
|
|
234
|
+
waiter.onAbort = () => this.settleWaiter(waiter, abortError());
|
|
235
|
+
signal.addEventListener("abort", waiter.onAbort, { once: true });
|
|
236
|
+
}
|
|
237
|
+
if (this.acquireTimeoutMs != null) {
|
|
238
|
+
waiter.timer = setTimeout(
|
|
239
|
+
() => this.settleWaiter(
|
|
240
|
+
waiter,
|
|
241
|
+
new Error(`RateLimiter.acquire timed out after ${this.acquireTimeoutMs}ms`)
|
|
242
|
+
),
|
|
243
|
+
this.acquireTimeoutMs
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
this.queue.push(waiter);
|
|
247
|
+
this.pump();
|
|
248
|
+
if (!waiter.granted && !this.warnedQueued) {
|
|
249
|
+
this.warnedQueued = true;
|
|
250
|
+
console.warn(
|
|
251
|
+
`[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.`
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
/** Release a previously acquired permit and wake the next waiter. */
|
|
257
|
+
release() {
|
|
258
|
+
if (this.active === 0) {
|
|
259
|
+
console.warn(
|
|
260
|
+
"[axl] RateLimiter.release() called with no active permits \u2014 likely a double-release bug."
|
|
261
|
+
);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
this.active--;
|
|
265
|
+
this.pump();
|
|
266
|
+
}
|
|
267
|
+
/** v1 no-op seam for a future adaptive (rate-limit-header-driven) pacing follow-up. */
|
|
268
|
+
observe(_res) {
|
|
269
|
+
}
|
|
270
|
+
/** Drain the queue: grant head waiters while a permit is free and spacing allows. */
|
|
271
|
+
pump() {
|
|
272
|
+
while (this.queue.length > 0 && this.active < this.maxConcurrent) {
|
|
273
|
+
if (this.minIntervalMs > 0) {
|
|
274
|
+
const waitMs = this.minIntervalMs - (Date.now() - this.lastGrantAt);
|
|
275
|
+
if (waitMs > 0) {
|
|
276
|
+
if (!this.spacingTimer) {
|
|
277
|
+
this.spacingTimer = setTimeout(() => {
|
|
278
|
+
this.spacingTimer = void 0;
|
|
279
|
+
this.pump();
|
|
280
|
+
}, waitMs);
|
|
281
|
+
}
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
const waiter = this.queue.shift();
|
|
286
|
+
this.grant(waiter);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
grant(waiter) {
|
|
290
|
+
waiter.settled = true;
|
|
291
|
+
waiter.granted = true;
|
|
292
|
+
this.active++;
|
|
293
|
+
this.lastGrantAt = Date.now();
|
|
294
|
+
this.clearWaiterTimers(waiter);
|
|
295
|
+
waiter.resolve();
|
|
296
|
+
}
|
|
297
|
+
/** Reject a still-queued waiter (abort or timeout) and remove it cleanly. */
|
|
298
|
+
settleWaiter(waiter, err) {
|
|
299
|
+
if (waiter.settled) return;
|
|
300
|
+
waiter.settled = true;
|
|
301
|
+
const idx = this.queue.indexOf(waiter);
|
|
302
|
+
if (idx !== -1) this.queue.splice(idx, 1);
|
|
303
|
+
this.clearWaiterTimers(waiter);
|
|
304
|
+
waiter.reject(err);
|
|
305
|
+
this.pump();
|
|
306
|
+
}
|
|
307
|
+
clearWaiterTimers(waiter) {
|
|
308
|
+
if (waiter.timer) {
|
|
309
|
+
clearTimeout(waiter.timer);
|
|
310
|
+
waiter.timer = void 0;
|
|
311
|
+
}
|
|
312
|
+
if (waiter.signal && waiter.onAbort) {
|
|
313
|
+
waiter.signal.removeEventListener("abort", waiter.onAbort);
|
|
314
|
+
waiter.onAbort = void 0;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
|
|
166
319
|
// src/providers/openai.ts
|
|
167
320
|
var OPENAI_PRICING = {
|
|
168
321
|
// gpt-4o era — cache reads at 50% of input rate
|
|
@@ -190,7 +343,10 @@ var OPENAI_PRICING = {
|
|
|
190
343
|
"gpt-5.2": [175e-8, 14e-6, 0.1],
|
|
191
344
|
"gpt-5.3": [175e-8, 14e-6, 0.1],
|
|
192
345
|
"gpt-5.4": [25e-7, 15e-6, 0.1],
|
|
193
|
-
"gpt-5.4-pro": [3e-5, 18e-5, 0.1]
|
|
346
|
+
"gpt-5.4-pro": [3e-5, 18e-5, 0.1],
|
|
347
|
+
// gpt-5.5 era (flagship released 2026-04) — cache reads at 10% of input rate
|
|
348
|
+
"gpt-5.5": [5e-6, 3e-5, 0.1],
|
|
349
|
+
"gpt-5.5-pro": [3e-5, 18e-5, 0.1]
|
|
194
350
|
};
|
|
195
351
|
var PRICING_KEYS_BY_LENGTH = Object.keys(OPENAI_PRICING).sort((a, b) => b.length - a.length);
|
|
196
352
|
function estimateOpenAICost(model, promptTokens, completionTokens, cachedTokens) {
|
|
@@ -239,9 +395,11 @@ var OpenAIProvider = class {
|
|
|
239
395
|
name = "openai";
|
|
240
396
|
baseUrl;
|
|
241
397
|
apiKey;
|
|
398
|
+
governor;
|
|
242
399
|
constructor(options = {}) {
|
|
243
400
|
this.apiKey = options.apiKey ?? process.env.OPENAI_API_KEY ?? "";
|
|
244
401
|
this.baseUrl = (options.baseUrl ?? process.env.OPENAI_BASE_URL ?? "https://api.openai.com/v1").replace(/\/$/, "");
|
|
402
|
+
this.governor = options.rateLimit ? new RateLimiter(options.rateLimit) : void 0;
|
|
245
403
|
if (!this.apiKey) {
|
|
246
404
|
throw new Error("OpenAI API key is required. Set OPENAI_API_KEY or pass apiKey in options.");
|
|
247
405
|
}
|
|
@@ -251,15 +409,19 @@ var OpenAIProvider = class {
|
|
|
251
409
|
// ---------------------------------------------------------------------------
|
|
252
410
|
async chat(messages, options) {
|
|
253
411
|
const body = this.buildRequestBody(messages, options, false);
|
|
254
|
-
const res = await fetchWithRetry(
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
412
|
+
const res = await fetchWithRetry(
|
|
413
|
+
`${this.baseUrl}/chat/completions`,
|
|
414
|
+
{
|
|
415
|
+
method: "POST",
|
|
416
|
+
headers: {
|
|
417
|
+
"Content-Type": "application/json",
|
|
418
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
419
|
+
},
|
|
420
|
+
body: JSON.stringify(body),
|
|
421
|
+
signal: options.signal
|
|
259
422
|
},
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
});
|
|
423
|
+
{ governor: this.governor }
|
|
424
|
+
);
|
|
263
425
|
if (!res.ok) {
|
|
264
426
|
const errorBody = await res.text();
|
|
265
427
|
const message = this.extractErrorMessage(errorBody, res.status);
|
|
@@ -299,15 +461,19 @@ var OpenAIProvider = class {
|
|
|
299
461
|
// ---------------------------------------------------------------------------
|
|
300
462
|
async *stream(messages, options) {
|
|
301
463
|
const body = this.buildRequestBody(messages, options, true);
|
|
302
|
-
const res = await fetchWithRetry(
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
464
|
+
const res = await fetchWithRetry(
|
|
465
|
+
`${this.baseUrl}/chat/completions`,
|
|
466
|
+
{
|
|
467
|
+
method: "POST",
|
|
468
|
+
headers: {
|
|
469
|
+
"Content-Type": "application/json",
|
|
470
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
471
|
+
},
|
|
472
|
+
body: JSON.stringify(body),
|
|
473
|
+
signal: options.signal
|
|
307
474
|
},
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
});
|
|
475
|
+
{ governor: this.governor }
|
|
476
|
+
);
|
|
311
477
|
if (!res.ok) {
|
|
312
478
|
const errorBody = await res.text();
|
|
313
479
|
const message = this.extractErrorMessage(errorBody, res.status);
|
|
@@ -480,9 +646,11 @@ var OpenAIResponsesProvider = class {
|
|
|
480
646
|
name = "openai-responses";
|
|
481
647
|
baseUrl;
|
|
482
648
|
apiKey;
|
|
649
|
+
governor;
|
|
483
650
|
constructor(options = {}) {
|
|
484
651
|
this.apiKey = options.apiKey ?? process.env.OPENAI_API_KEY ?? "";
|
|
485
652
|
this.baseUrl = (options.baseUrl ?? process.env.OPENAI_BASE_URL ?? "https://api.openai.com/v1").replace(/\/$/, "");
|
|
653
|
+
this.governor = options.rateLimit ? new RateLimiter(options.rateLimit) : void 0;
|
|
486
654
|
if (!this.apiKey) {
|
|
487
655
|
throw new Error("OpenAI API key is required. Set OPENAI_API_KEY or pass apiKey in options.");
|
|
488
656
|
}
|
|
@@ -492,15 +660,19 @@ var OpenAIResponsesProvider = class {
|
|
|
492
660
|
// ---------------------------------------------------------------------------
|
|
493
661
|
async chat(messages, options) {
|
|
494
662
|
const body = this.buildRequestBody(messages, options, false);
|
|
495
|
-
const res = await fetchWithRetry(
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
663
|
+
const res = await fetchWithRetry(
|
|
664
|
+
`${this.baseUrl}/responses`,
|
|
665
|
+
{
|
|
666
|
+
method: "POST",
|
|
667
|
+
headers: {
|
|
668
|
+
"Content-Type": "application/json",
|
|
669
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
670
|
+
},
|
|
671
|
+
body: JSON.stringify(body),
|
|
672
|
+
signal: options.signal
|
|
500
673
|
},
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
});
|
|
674
|
+
{ governor: this.governor }
|
|
675
|
+
);
|
|
504
676
|
if (!res.ok) {
|
|
505
677
|
const errorBody = await res.text();
|
|
506
678
|
const message = this.extractErrorMessage(errorBody, res.status);
|
|
@@ -514,15 +686,19 @@ var OpenAIResponsesProvider = class {
|
|
|
514
686
|
// ---------------------------------------------------------------------------
|
|
515
687
|
async *stream(messages, options) {
|
|
516
688
|
const body = this.buildRequestBody(messages, options, true);
|
|
517
|
-
const res = await fetchWithRetry(
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
689
|
+
const res = await fetchWithRetry(
|
|
690
|
+
`${this.baseUrl}/responses`,
|
|
691
|
+
{
|
|
692
|
+
method: "POST",
|
|
693
|
+
headers: {
|
|
694
|
+
"Content-Type": "application/json",
|
|
695
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
696
|
+
},
|
|
697
|
+
body: JSON.stringify(body),
|
|
698
|
+
signal: options.signal
|
|
522
699
|
},
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
});
|
|
700
|
+
{ governor: this.governor }
|
|
701
|
+
);
|
|
526
702
|
if (!res.ok) {
|
|
527
703
|
const errorBody = await res.text();
|
|
528
704
|
const message = this.extractErrorMessage(errorBody, res.status);
|
|
@@ -839,6 +1015,7 @@ var OpenAIResponsesProvider = class {
|
|
|
839
1015
|
// src/providers/anthropic.ts
|
|
840
1016
|
var ANTHROPIC_API_VERSION = "2023-06-01";
|
|
841
1017
|
var ANTHROPIC_PRICING = {
|
|
1018
|
+
"claude-opus-4-8": [5e-6, 25e-6],
|
|
842
1019
|
"claude-opus-4-7": [5e-6, 25e-6],
|
|
843
1020
|
"claude-opus-4-6": [5e-6, 25e-6],
|
|
844
1021
|
"claude-sonnet-4-6": [3e-6, 15e-6],
|
|
@@ -884,16 +1061,16 @@ var THINKING_BUDGETS = {
|
|
|
884
1061
|
max: 3e4
|
|
885
1062
|
};
|
|
886
1063
|
function supportsAdaptiveThinking(model) {
|
|
887
|
-
return model.startsWith("claude-opus-4-7") || model.startsWith("claude-opus-4-6") || model.startsWith("claude-sonnet-4-6");
|
|
1064
|
+
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");
|
|
888
1065
|
}
|
|
889
1066
|
function supportsMaxEffort(model) {
|
|
890
|
-
return model.startsWith("claude-opus-4-7") || model.startsWith("claude-opus-4-6");
|
|
1067
|
+
return model.startsWith("claude-opus-4-8") || model.startsWith("claude-opus-4-7") || model.startsWith("claude-opus-4-6");
|
|
891
1068
|
}
|
|
892
1069
|
function supportsXhigh2(model) {
|
|
893
|
-
return model.startsWith("claude-opus-4-7");
|
|
1070
|
+
return model.startsWith("claude-opus-4-8") || model.startsWith("claude-opus-4-7");
|
|
894
1071
|
}
|
|
895
1072
|
function supportsEffort(model) {
|
|
896
|
-
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");
|
|
1073
|
+
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");
|
|
897
1074
|
}
|
|
898
1075
|
function clampAnthropicEffort(model, effort) {
|
|
899
1076
|
if (effort === "max" && !supportsMaxEffort(model)) return "high";
|
|
@@ -905,9 +1082,11 @@ var AnthropicProvider = class {
|
|
|
905
1082
|
baseUrl;
|
|
906
1083
|
apiKey;
|
|
907
1084
|
currentModel;
|
|
1085
|
+
governor;
|
|
908
1086
|
constructor(options = {}) {
|
|
909
1087
|
this.apiKey = options.apiKey ?? process.env.ANTHROPIC_API_KEY ?? "";
|
|
910
1088
|
this.baseUrl = (options.baseUrl ?? "https://api.anthropic.com/v1").replace(/\/$/, "");
|
|
1089
|
+
this.governor = options.rateLimit ? new RateLimiter(options.rateLimit) : void 0;
|
|
911
1090
|
if (!this.apiKey) {
|
|
912
1091
|
throw new Error(
|
|
913
1092
|
"Anthropic API key is required. Set ANTHROPIC_API_KEY or pass apiKey in options."
|
|
@@ -920,12 +1099,16 @@ var AnthropicProvider = class {
|
|
|
920
1099
|
async chat(messages, options) {
|
|
921
1100
|
this.currentModel = options.model;
|
|
922
1101
|
const body = this.buildRequestBody(messages, options, false);
|
|
923
|
-
const res = await fetchWithRetry(
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
1102
|
+
const res = await fetchWithRetry(
|
|
1103
|
+
`${this.baseUrl}/messages`,
|
|
1104
|
+
{
|
|
1105
|
+
method: "POST",
|
|
1106
|
+
headers: this.buildHeaders(),
|
|
1107
|
+
body: JSON.stringify(body),
|
|
1108
|
+
signal: options.signal
|
|
1109
|
+
},
|
|
1110
|
+
{ governor: this.governor }
|
|
1111
|
+
);
|
|
929
1112
|
if (!res.ok) {
|
|
930
1113
|
const errorBody = await res.text();
|
|
931
1114
|
const message = this.extractErrorMessage(errorBody, res.status);
|
|
@@ -939,12 +1122,16 @@ var AnthropicProvider = class {
|
|
|
939
1122
|
// ---------------------------------------------------------------------------
|
|
940
1123
|
async *stream(messages, options) {
|
|
941
1124
|
const body = this.buildRequestBody(messages, options, true);
|
|
942
|
-
const res = await fetchWithRetry(
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
1125
|
+
const res = await fetchWithRetry(
|
|
1126
|
+
`${this.baseUrl}/messages`,
|
|
1127
|
+
{
|
|
1128
|
+
method: "POST",
|
|
1129
|
+
headers: this.buildHeaders(),
|
|
1130
|
+
body: JSON.stringify(body),
|
|
1131
|
+
signal: options.signal
|
|
1132
|
+
},
|
|
1133
|
+
{ governor: this.governor }
|
|
1134
|
+
);
|
|
948
1135
|
if (!res.ok) {
|
|
949
1136
|
const errorBody = await res.text();
|
|
950
1137
|
const message = this.extractErrorMessage(errorBody, res.status);
|
|
@@ -1431,12 +1618,14 @@ var GeminiProvider = class {
|
|
|
1431
1618
|
baseUrl;
|
|
1432
1619
|
apiKey;
|
|
1433
1620
|
callCounter = 0;
|
|
1621
|
+
governor;
|
|
1434
1622
|
constructor(options = {}) {
|
|
1435
1623
|
this.apiKey = options.apiKey ?? process.env.GOOGLE_API_KEY ?? process.env.GEMINI_API_KEY ?? "";
|
|
1436
1624
|
this.baseUrl = (options.baseUrl ?? "https://generativelanguage.googleapis.com/v1beta").replace(
|
|
1437
1625
|
/\/$/,
|
|
1438
1626
|
""
|
|
1439
1627
|
);
|
|
1628
|
+
this.governor = options.rateLimit ? new RateLimiter(options.rateLimit) : void 0;
|
|
1440
1629
|
if (!this.apiKey) {
|
|
1441
1630
|
throw new Error("Google API key is required. Set GOOGLE_API_KEY or pass apiKey in options.");
|
|
1442
1631
|
}
|
|
@@ -1446,12 +1635,16 @@ var GeminiProvider = class {
|
|
|
1446
1635
|
// ---------------------------------------------------------------------------
|
|
1447
1636
|
async chat(messages, options) {
|
|
1448
1637
|
const body = this.buildRequestBody(messages, options);
|
|
1449
|
-
const res = await fetchWithRetry(
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1638
|
+
const res = await fetchWithRetry(
|
|
1639
|
+
`${this.baseUrl}/models/${options.model}:generateContent`,
|
|
1640
|
+
{
|
|
1641
|
+
method: "POST",
|
|
1642
|
+
headers: this.buildHeaders(),
|
|
1643
|
+
body: JSON.stringify(body),
|
|
1644
|
+
signal: options.signal
|
|
1645
|
+
},
|
|
1646
|
+
{ governor: this.governor }
|
|
1647
|
+
);
|
|
1455
1648
|
if (!res.ok) {
|
|
1456
1649
|
const errorBody = await res.text();
|
|
1457
1650
|
const message = this.extractErrorMessage(errorBody, res.status);
|
|
@@ -1472,7 +1665,8 @@ var GeminiProvider = class {
|
|
|
1472
1665
|
headers: this.buildHeaders(),
|
|
1473
1666
|
body: JSON.stringify(body),
|
|
1474
1667
|
signal: options.signal
|
|
1475
|
-
}
|
|
1668
|
+
},
|
|
1669
|
+
{ governor: this.governor }
|
|
1476
1670
|
);
|
|
1477
1671
|
if (!res.ok) {
|
|
1478
1672
|
const errorBody = await res.text();
|
|
@@ -1851,19 +2045,39 @@ var GeminiProvider = class {
|
|
|
1851
2045
|
var builtinFactories = {
|
|
1852
2046
|
openai: (config) => {
|
|
1853
2047
|
const opts = config.providers?.openai ?? {};
|
|
1854
|
-
return new OpenAIProvider({
|
|
2048
|
+
return new OpenAIProvider({
|
|
2049
|
+
apiKey: opts.apiKey,
|
|
2050
|
+
baseUrl: opts.baseUrl,
|
|
2051
|
+
rateLimit: opts.rateLimit
|
|
2052
|
+
});
|
|
1855
2053
|
},
|
|
2054
|
+
// NB: each adapter builds its OWN RateLimiter from its config — caps are
|
|
2055
|
+
// per-adapter-instance, not pooled. Configuring `openai` AND using both
|
|
2056
|
+
// `openai:` and `openai-responses:` yields two independent governors against
|
|
2057
|
+
// the same key (effective concurrency = the sum). Documented in providers.md.
|
|
1856
2058
|
"openai-responses": (config) => {
|
|
1857
2059
|
const opts = config.providers?.["openai-responses"] ?? config.providers?.openai ?? {};
|
|
1858
|
-
return new OpenAIResponsesProvider({
|
|
2060
|
+
return new OpenAIResponsesProvider({
|
|
2061
|
+
apiKey: opts.apiKey,
|
|
2062
|
+
baseUrl: opts.baseUrl,
|
|
2063
|
+
rateLimit: opts.rateLimit
|
|
2064
|
+
});
|
|
1859
2065
|
},
|
|
1860
2066
|
anthropic: (config) => {
|
|
1861
2067
|
const opts = config.providers?.anthropic ?? {};
|
|
1862
|
-
return new AnthropicProvider({
|
|
2068
|
+
return new AnthropicProvider({
|
|
2069
|
+
apiKey: opts.apiKey,
|
|
2070
|
+
baseUrl: opts.baseUrl,
|
|
2071
|
+
rateLimit: opts.rateLimit
|
|
2072
|
+
});
|
|
1863
2073
|
},
|
|
1864
2074
|
google: (config) => {
|
|
1865
2075
|
const opts = config.providers?.google ?? {};
|
|
1866
|
-
return new GeminiProvider({
|
|
2076
|
+
return new GeminiProvider({
|
|
2077
|
+
apiKey: opts.apiKey,
|
|
2078
|
+
baseUrl: opts.baseUrl,
|
|
2079
|
+
rateLimit: opts.rateLimit
|
|
2080
|
+
});
|
|
1867
2081
|
}
|
|
1868
2082
|
};
|
|
1869
2083
|
var ProviderRegistry = class {
|
|
@@ -8596,14 +8810,11 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
8596
8810
|
captureTraces: options?.captureTraces
|
|
8597
8811
|
});
|
|
8598
8812
|
} else {
|
|
8599
|
-
result = await this.eval(
|
|
8600
|
-
|
|
8601
|
-
|
|
8602
|
-
|
|
8603
|
-
|
|
8604
|
-
captureTraces: options?.captureTraces
|
|
8605
|
-
}
|
|
8606
|
-
);
|
|
8813
|
+
result = await this.eval(entry.config, {
|
|
8814
|
+
onProgress: options?.onProgress,
|
|
8815
|
+
signal: options?.signal,
|
|
8816
|
+
captureTraces: options?.captureTraces
|
|
8817
|
+
});
|
|
8607
8818
|
}
|
|
8608
8819
|
const resultObj = result;
|
|
8609
8820
|
if (options?.metadata) {
|
|
@@ -10542,6 +10753,7 @@ export {
|
|
|
10542
10753
|
QuorumNotMet,
|
|
10543
10754
|
REDACTED,
|
|
10544
10755
|
REDACTION_RULES,
|
|
10756
|
+
RateLimiter,
|
|
10545
10757
|
RedisStore,
|
|
10546
10758
|
SQLiteStore,
|
|
10547
10759
|
Session,
|