@happyvertical/ai 0.97.0 → 0.98.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 +123 -3
- package/dist/chunks/{anthropic-CD9Ud7a5.js → anthropic-6ZDF32jo.js} +181 -27
- package/dist/chunks/anthropic-6ZDF32jo.js.map +1 -0
- package/dist/chunks/{bedrock-Bv1qCquH.js → bedrock-BQi3Z9jR.js} +71 -21
- package/dist/chunks/bedrock-BQi3Z9jR.js.map +1 -0
- package/dist/chunks/{bifrost-DzT2J0Lq.js → bifrost-DsI2I4ka.js} +9 -6
- package/dist/chunks/bifrost-DsI2I4ka.js.map +1 -0
- package/dist/chunks/{byteplus-modelark-BIEXaTWw.js → byteplus-modelark-Dq8AgCQD.js} +7 -5
- package/dist/chunks/byteplus-modelark-Dq8AgCQD.js.map +1 -0
- package/dist/chunks/{claude-cli-Chfnckyj.js → claude-cli-DIFxvG3c.js} +7 -5
- package/dist/chunks/claude-cli-DIFxvG3c.js.map +1 -0
- package/dist/chunks/continuation-D0NisZA1.js +209 -0
- package/dist/chunks/continuation-D0NisZA1.js.map +1 -0
- package/dist/chunks/{gateway-admin-04mvbDHC.js → gateway-audio-models-Dn7Wrc6P.js} +53 -3
- package/dist/chunks/gateway-audio-models-Dn7Wrc6P.js.map +1 -0
- package/dist/chunks/{gemini-BGwrHeGc.js → gemini-BLFwWN90.js} +131 -13
- package/dist/chunks/gemini-BLFwWN90.js.map +1 -0
- package/dist/chunks/{huggingface-mthYVbLR.js → huggingface-BuRwlABZ.js} +8 -6
- package/dist/chunks/huggingface-BuRwlABZ.js.map +1 -0
- package/dist/chunks/{litellm-xCO8QlGI.js → litellm-CpfCT6jN.js} +9 -6
- package/dist/chunks/litellm-CpfCT6jN.js.map +1 -0
- package/dist/chunks/{ollama-Bedt8Agy.js → ollama-CnrskTUg.js} +38 -7
- package/dist/chunks/ollama-CnrskTUg.js.map +1 -0
- package/dist/chunks/{openai-compat-video-B-xVe9bW.js → openai-compat-video-CJIDdkUO.js} +7 -5
- package/dist/chunks/openai-compat-video-CJIDdkUO.js.map +1 -0
- package/dist/chunks/{qwen-tts-D-UmiMMy.js → qwen-tts-PzEkWcEb.js} +2 -2
- package/dist/chunks/{qwen-tts-D-UmiMMy.js.map → qwen-tts-PzEkWcEb.js.map} +1 -1
- package/dist/chunks/rate-limit-BzWfHA0e.js +713 -0
- package/dist/chunks/rate-limit-BzWfHA0e.js.map +1 -0
- package/dist/chunks/{seevio-BJRiguLc.js → seevio-D2y5Io7C.js} +4 -5
- package/dist/chunks/seevio-D2y5Io7C.js.map +1 -0
- package/dist/chunks/{src-BUQUZK2M.js → src-CLT6r1W0.js} +36 -23
- package/dist/chunks/src-CLT6r1W0.js.map +1 -0
- package/dist/chunks/tool-messages-0ND35mYX.js +57 -0
- package/dist/chunks/tool-messages-0ND35mYX.js.map +1 -0
- package/dist/chunks/{types-BpnNcTU1.js → types-DB5W6_F-.js} +37 -5
- package/dist/chunks/types-DB5W6_F-.js.map +1 -0
- package/dist/chunks/{typesafe-B0gPZQJg.js → typesafe-CVUqQ8N1.js} +7 -5
- package/dist/chunks/typesafe-CVUqQ8N1.js.map +1 -0
- package/dist/index.js +4 -4
- package/dist/node.js +5 -5
- package/dist/shared/continuation.d.ts +51 -0
- package/dist/shared/continuation.d.ts.map +1 -0
- package/dist/shared/factory.d.ts.map +1 -1
- package/dist/shared/providers/anthropic.d.ts +53 -0
- package/dist/shared/providers/anthropic.d.ts.map +1 -1
- package/dist/shared/providers/bedrock.d.ts +13 -0
- package/dist/shared/providers/bedrock.d.ts.map +1 -1
- package/dist/shared/providers/bifrost.d.ts.map +1 -1
- package/dist/shared/providers/claude-cli.d.ts.map +1 -1
- package/dist/shared/providers/gateway-audio-models.d.ts +24 -0
- package/dist/shared/providers/gateway-audio-models.d.ts.map +1 -0
- package/dist/shared/providers/gemini.d.ts +28 -0
- package/dist/shared/providers/gemini.d.ts.map +1 -1
- package/dist/shared/providers/huggingface.d.ts.map +1 -1
- package/dist/shared/providers/litellm.d.ts.map +1 -1
- package/dist/shared/providers/ollama.d.ts.map +1 -1
- package/dist/shared/providers/openai.d.ts.map +1 -1
- package/dist/shared/providers/seevio.d.ts.map +1 -1
- package/dist/shared/providers/typesafe.d.ts.map +1 -1
- package/dist/shared/rate-limit.d.ts +19 -1
- package/dist/shared/rate-limit.d.ts.map +1 -1
- package/dist/shared/safety.d.ts.map +1 -1
- package/dist/shared/tool-messages.d.ts +27 -0
- package/dist/shared/tool-messages.d.ts.map +1 -0
- package/dist/shared/types.d.ts +191 -22
- package/dist/shared/types.d.ts.map +1 -1
- package/package.json +3 -3
- package/dist/chunks/anthropic-CD9Ud7a5.js.map +0 -1
- package/dist/chunks/bedrock-Bv1qCquH.js.map +0 -1
- package/dist/chunks/bifrost-DzT2J0Lq.js.map +0 -1
- package/dist/chunks/byteplus-modelark-BIEXaTWw.js.map +0 -1
- package/dist/chunks/claude-cli-Chfnckyj.js.map +0 -1
- package/dist/chunks/gateway-admin-04mvbDHC.js.map +0 -1
- package/dist/chunks/gemini-BGwrHeGc.js.map +0 -1
- package/dist/chunks/huggingface-mthYVbLR.js.map +0 -1
- package/dist/chunks/litellm-xCO8QlGI.js.map +0 -1
- package/dist/chunks/ollama-Bedt8Agy.js.map +0 -1
- package/dist/chunks/openai-compat-video-B-xVe9bW.js.map +0 -1
- package/dist/chunks/rate-limit-BVW_1Tey.js +0 -202
- package/dist/chunks/rate-limit-BVW_1Tey.js.map +0 -1
- package/dist/chunks/safety-BKswBoqy.js +0 -279
- package/dist/chunks/safety-BKswBoqy.js.map +0 -1
- package/dist/chunks/seevio-BJRiguLc.js.map +0 -1
- package/dist/chunks/src-BUQUZK2M.js.map +0 -1
- package/dist/chunks/types-BpnNcTU1.js.map +0 -1
- package/dist/chunks/typesafe-B0gPZQJg.js.map +0 -1
|
@@ -0,0 +1,713 @@
|
|
|
1
|
+
import { c as RateLimitError, o as DEFAULT_RATE_LIMIT_MAX_RETRY_DELAY_MS, t as AIError, u as isRateLimitRetryableSoon } from "./types-DB5W6_F-.js";
|
|
2
|
+
//#region src/shared/safety.ts
|
|
3
|
+
var DEFAULT_AI_TIMEOUT_MS = 12e4;
|
|
4
|
+
var DEFAULT_AI_MAX_RETRIES = 0;
|
|
5
|
+
var DEFAULT_AI_GENERATION_LIMITS = Object.freeze({
|
|
6
|
+
maxOutputTokens: 131072,
|
|
7
|
+
defaultOutputTokens: 4096,
|
|
8
|
+
maxReasoningTokens: 1024,
|
|
9
|
+
maxImagesPerRequest: 1,
|
|
10
|
+
onExceeded: "error"
|
|
11
|
+
});
|
|
12
|
+
function positiveInteger(value, fallback, field) {
|
|
13
|
+
if (value === void 0) return fallback;
|
|
14
|
+
if (!Number.isFinite(value) || value <= 0) throw new AIError(`${field} must be a positive finite number`, "AI_LIMIT_INVALID");
|
|
15
|
+
return Math.trunc(value);
|
|
16
|
+
}
|
|
17
|
+
function normalizeBaseAIOptions(options) {
|
|
18
|
+
const configuredLimits = options.generationLimits || {};
|
|
19
|
+
const generationLimits = {
|
|
20
|
+
maxOutputTokens: positiveInteger(configuredLimits.maxOutputTokens, DEFAULT_AI_GENERATION_LIMITS.maxOutputTokens, "generationLimits.maxOutputTokens"),
|
|
21
|
+
defaultOutputTokens: Math.min(positiveInteger(configuredLimits.defaultOutputTokens, DEFAULT_AI_GENERATION_LIMITS.defaultOutputTokens, "generationLimits.defaultOutputTokens"), positiveInteger(configuredLimits.maxOutputTokens, DEFAULT_AI_GENERATION_LIMITS.maxOutputTokens, "generationLimits.maxOutputTokens")),
|
|
22
|
+
maxReasoningTokens: positiveInteger(configuredLimits.maxReasoningTokens, DEFAULT_AI_GENERATION_LIMITS.maxReasoningTokens, "generationLimits.maxReasoningTokens"),
|
|
23
|
+
maxImagesPerRequest: positiveInteger(configuredLimits.maxImagesPerRequest, DEFAULT_AI_GENERATION_LIMITS.maxImagesPerRequest, "generationLimits.maxImagesPerRequest"),
|
|
24
|
+
onExceeded: configuredLimits.onExceeded || DEFAULT_AI_GENERATION_LIMITS.onExceeded
|
|
25
|
+
};
|
|
26
|
+
if (!["error", "clamp"].includes(generationLimits.onExceeded)) throw new AIError("generationLimits.onExceeded must be \"error\" or \"clamp\"", "AI_LIMIT_INVALID");
|
|
27
|
+
return {
|
|
28
|
+
...options,
|
|
29
|
+
timeout: positiveInteger(options.timeout, DEFAULT_AI_TIMEOUT_MS, "timeout"),
|
|
30
|
+
maxRetries: (() => {
|
|
31
|
+
if (options.maxRetries === void 0) return 0;
|
|
32
|
+
if (!Number.isFinite(options.maxRetries) || options.maxRetries < 0) throw new AIError("maxRetries must be a non-negative finite number", "AI_LIMIT_INVALID");
|
|
33
|
+
return Math.trunc(options.maxRetries);
|
|
34
|
+
})(),
|
|
35
|
+
generationLimits
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function reasoningTokensForEffort(effort) {
|
|
39
|
+
switch (effort) {
|
|
40
|
+
case false:
|
|
41
|
+
case "none": return 0;
|
|
42
|
+
case "minimal": return 1024;
|
|
43
|
+
case "low": return 2048;
|
|
44
|
+
case "medium": return 4096;
|
|
45
|
+
case "high": return 8192;
|
|
46
|
+
default: return;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function enforceLimit(requested, limit, behavior, label, provider, model) {
|
|
50
|
+
if (!Number.isFinite(requested) || requested < 0) throw new AIError(`${label} must be a non-negative finite number`, "AI_LIMIT_INVALID", provider, model);
|
|
51
|
+
const normalized = Math.trunc(requested);
|
|
52
|
+
if (normalized <= limit) return normalized;
|
|
53
|
+
if (behavior === "clamp") return limit;
|
|
54
|
+
throw new AIError(`${label} ${normalized} exceeds configured ceiling ${limit}`, "AI_LIMIT_EXCEEDED", provider, model);
|
|
55
|
+
}
|
|
56
|
+
function normalizeChatOptions(providerOptions, options, provider, model) {
|
|
57
|
+
const limits = normalizeBaseAIOptions(providerOptions).generationLimits;
|
|
58
|
+
const requestedOutput = options.maxTokens ?? limits.defaultOutputTokens ?? limits.maxOutputTokens;
|
|
59
|
+
const legacyEffort = options.thinkingLevel ?? providerOptions.thinkingLevel;
|
|
60
|
+
const requestedReasoning = options.reasoning !== void 0 || legacyEffort !== void 0 || options.includeThoughts === true ? options.reasoning?.maxTokens ?? reasoningTokensForEffort(options.reasoning?.effort ?? legacyEffort) ?? limits.maxReasoningTokens : 0;
|
|
61
|
+
return {
|
|
62
|
+
...options,
|
|
63
|
+
maxTokens: enforceLimit(requestedOutput, limits.maxOutputTokens, limits.onExceeded, "maxTokens", provider, model),
|
|
64
|
+
reasoning: {
|
|
65
|
+
...options.reasoning,
|
|
66
|
+
maxTokens: enforceLimit(requestedReasoning, limits.maxReasoningTokens, limits.onExceeded, "reasoning.maxTokens", provider, model),
|
|
67
|
+
includeThoughts: options.reasoning?.includeThoughts ?? options.includeThoughts
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function normalizeImageGenerationOptions(providerOptions, options, provider, model) {
|
|
72
|
+
const limits = normalizeBaseAIOptions(providerOptions).generationLimits;
|
|
73
|
+
return {
|
|
74
|
+
...options,
|
|
75
|
+
n: enforceLimit(options.n ?? 1, limits.maxImagesPerRequest, limits.onExceeded, "n", provider, model)
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function prepareRequestControls(providerOptions, requestOptions = {}) {
|
|
79
|
+
const normalized = normalizeBaseAIOptions(providerOptions);
|
|
80
|
+
const timeout = positiveInteger(requestOptions.timeout, normalized.timeout, "timeout");
|
|
81
|
+
const controller = new AbortController();
|
|
82
|
+
let timedOut = false;
|
|
83
|
+
const abortFromCaller = () => controller.abort(requestOptions.signal?.reason);
|
|
84
|
+
if (requestOptions.signal?.aborted) abortFromCaller();
|
|
85
|
+
else requestOptions.signal?.addEventListener("abort", abortFromCaller, { once: true });
|
|
86
|
+
const timer = setTimeout(() => {
|
|
87
|
+
timedOut = true;
|
|
88
|
+
controller.abort(/* @__PURE__ */ new Error(`AI request timed out after ${timeout}ms`));
|
|
89
|
+
}, timeout);
|
|
90
|
+
return {
|
|
91
|
+
signal: controller.signal,
|
|
92
|
+
timeout,
|
|
93
|
+
didTimeout: () => timedOut,
|
|
94
|
+
cleanup: () => {
|
|
95
|
+
clearTimeout(timer);
|
|
96
|
+
requestOptions.signal?.removeEventListener("abort", abortFromCaller);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function mergeTags(providerOptions, callTags) {
|
|
101
|
+
return providerOptions.usageTags || callTags ? {
|
|
102
|
+
...providerOptions.usageTags,
|
|
103
|
+
...callTags
|
|
104
|
+
} : void 0;
|
|
105
|
+
}
|
|
106
|
+
function emitRequestEvent(providerOptions, event) {
|
|
107
|
+
if (!providerOptions.onRequest) return;
|
|
108
|
+
try {
|
|
109
|
+
providerOptions.onRequest({
|
|
110
|
+
...event,
|
|
111
|
+
tags: mergeTags(providerOptions, event.tags)
|
|
112
|
+
});
|
|
113
|
+
} catch {}
|
|
114
|
+
}
|
|
115
|
+
function classifyRequestFailure(error, didTimeout = false) {
|
|
116
|
+
const code = error instanceof AIError ? error.code : void 0;
|
|
117
|
+
if (code === "AI_LIMIT_EXCEEDED" || code === "AI_LIMIT_INVALID") return {
|
|
118
|
+
status: "rejected",
|
|
119
|
+
errorCode: code
|
|
120
|
+
};
|
|
121
|
+
if (didTimeout || code === "AI_TIMEOUT" || code === "TIMEOUT" || code === "REQUEST_TIMEOUT") return {
|
|
122
|
+
status: "timed_out",
|
|
123
|
+
errorCode: code || "AI_TIMEOUT"
|
|
124
|
+
};
|
|
125
|
+
if (error instanceof Error && error.name === "AbortError") return {
|
|
126
|
+
status: "aborted",
|
|
127
|
+
errorCode: "ABORTED"
|
|
128
|
+
};
|
|
129
|
+
if (code === "AI_ABORTED" || code === "REQUEST_ABORTED") return {
|
|
130
|
+
status: "aborted",
|
|
131
|
+
errorCode: code
|
|
132
|
+
};
|
|
133
|
+
return {
|
|
134
|
+
status: "failed",
|
|
135
|
+
errorCode: code || (error instanceof Error ? error.name : void 0)
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function requestEventBase(options) {
|
|
139
|
+
const normalized = normalizeBaseAIOptions(options.providerOptions);
|
|
140
|
+
return {
|
|
141
|
+
provider: options.provider,
|
|
142
|
+
model: options.model,
|
|
143
|
+
operation: options.operation,
|
|
144
|
+
attempts: normalized.maxRetries + 1,
|
|
145
|
+
requestedMaxOutputTokens: options.operation === "decide" ? void 0 : options.callOptions?.maxTokens,
|
|
146
|
+
effectiveMaxOutputTokens: options.operation === "decide" || options.operation === "generateImage" || VIDEO_GENERATION_OPERATIONS.has(options.operation) ? void 0 : options.effectiveMaxOutputTokens ?? Math.min(options.callOptions?.maxTokens ?? normalized.generationLimits.defaultOutputTokens ?? normalized.generationLimits.maxOutputTokens, normalized.generationLimits.maxOutputTokens),
|
|
147
|
+
tags: options.callOptions?.usageTags
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
var OBSERVED_OPERATIONS = /* @__PURE__ */ new Set([
|
|
151
|
+
"decide",
|
|
152
|
+
"chat",
|
|
153
|
+
"complete",
|
|
154
|
+
"message",
|
|
155
|
+
"describeImage",
|
|
156
|
+
"generateImage",
|
|
157
|
+
"stream",
|
|
158
|
+
"submitVideoGenerationJob",
|
|
159
|
+
"getVideoGenerationJob",
|
|
160
|
+
"fetchVideoGenerationResult",
|
|
161
|
+
"cancelVideoGenerationJob"
|
|
162
|
+
]);
|
|
163
|
+
/**
|
|
164
|
+
* `getVideoGenerationJob` / `fetchVideoGenerationResult` /
|
|
165
|
+
* `cancelVideoGenerationJob` take a `VideoGenerationJob` handle as their
|
|
166
|
+
* first (and only) argument rather than a chat-shaped `options` object as
|
|
167
|
+
* the second argument. The handle still carries a `model` field, so
|
|
168
|
+
* `callOptionsFor` reads it from the same position as `submitVideoGenerationJob`'s
|
|
169
|
+
* `options` (index 0) rather than index 1.
|
|
170
|
+
*/
|
|
171
|
+
var VIDEO_HANDLE_OPERATIONS = /* @__PURE__ */ new Set([
|
|
172
|
+
"getVideoGenerationJob",
|
|
173
|
+
"fetchVideoGenerationResult",
|
|
174
|
+
"cancelVideoGenerationJob"
|
|
175
|
+
]);
|
|
176
|
+
var VIDEO_GENERATION_OPERATIONS = /* @__PURE__ */ new Set(["submitVideoGenerationJob", ...VIDEO_HANDLE_OPERATIONS]);
|
|
177
|
+
function callOptionsFor(operation, args) {
|
|
178
|
+
const value = args[operation === "decide" ? 1 : operation === "describeImage" ? 2 : operation === "submitVideoGenerationJob" || VIDEO_HANDLE_OPERATIONS.has(operation) ? 0 : 1];
|
|
179
|
+
return value && typeof value === "object" ? value : void 0;
|
|
180
|
+
}
|
|
181
|
+
function providerName(options) {
|
|
182
|
+
return options.type || "openai";
|
|
183
|
+
}
|
|
184
|
+
function effectiveOutputTokens(operation, providerOptions, callOptions) {
|
|
185
|
+
if (operation === "decide" || operation === "generateImage" || VIDEO_GENERATION_OPERATIONS.has(operation)) {
|
|
186
|
+
if (operation === "generateImage") normalizeImageGenerationOptions(providerOptions, callOptions || {}, providerName(providerOptions), callOptions?.model);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
return normalizeChatOptions(providerOptions, callOptions || {}, providerName(providerOptions), callOptions?.model || providerOptions.defaultModel).maxTokens;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Wrap a public provider instance with prompt-free lifecycle observation.
|
|
193
|
+
* Provider adapters still enforce limits internally so direct construction is
|
|
194
|
+
* protected; this wrapper adds one terminal event to the public getAI() path.
|
|
195
|
+
*/
|
|
196
|
+
function createObservedAI(client, providerOptions) {
|
|
197
|
+
if (!providerOptions.onRequest) return client;
|
|
198
|
+
return new Proxy(client, { get(target, property, receiver) {
|
|
199
|
+
const value = Reflect.get(target, property, receiver);
|
|
200
|
+
if (typeof property !== "string" || typeof value !== "function" || !OBSERVED_OPERATIONS.has(property)) return typeof value === "function" ? value.bind(target) : value;
|
|
201
|
+
const operation = property;
|
|
202
|
+
if (operation === "stream") return async function* (...args) {
|
|
203
|
+
const startedAt = Date.now();
|
|
204
|
+
const callOptions = callOptionsFor(operation, args);
|
|
205
|
+
let base;
|
|
206
|
+
try {
|
|
207
|
+
base = requestEventBase({
|
|
208
|
+
providerOptions,
|
|
209
|
+
provider: providerName(providerOptions),
|
|
210
|
+
model: callOptions?.model || providerOptions.defaultModel || "unknown",
|
|
211
|
+
operation,
|
|
212
|
+
callOptions,
|
|
213
|
+
effectiveMaxOutputTokens: effectiveOutputTokens(operation, providerOptions, callOptions)
|
|
214
|
+
});
|
|
215
|
+
const iterable = Reflect.apply(value, target, args);
|
|
216
|
+
for await (const chunk of iterable) yield chunk;
|
|
217
|
+
emitRequestEvent(providerOptions, {
|
|
218
|
+
...base,
|
|
219
|
+
status: "succeeded",
|
|
220
|
+
duration: Date.now() - startedAt
|
|
221
|
+
});
|
|
222
|
+
} catch (error) {
|
|
223
|
+
const failure = classifyRequestFailure(error);
|
|
224
|
+
base ||= requestEventBase({
|
|
225
|
+
providerOptions,
|
|
226
|
+
provider: providerName(providerOptions),
|
|
227
|
+
model: callOptions?.model || providerOptions.defaultModel || "unknown",
|
|
228
|
+
operation,
|
|
229
|
+
callOptions
|
|
230
|
+
});
|
|
231
|
+
emitRequestEvent(providerOptions, {
|
|
232
|
+
...base,
|
|
233
|
+
...failure,
|
|
234
|
+
duration: Date.now() - startedAt
|
|
235
|
+
});
|
|
236
|
+
throw error;
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
return async (...args) => {
|
|
240
|
+
const startedAt = Date.now();
|
|
241
|
+
const callOptions = callOptionsFor(operation, args);
|
|
242
|
+
let base;
|
|
243
|
+
try {
|
|
244
|
+
base = requestEventBase({
|
|
245
|
+
providerOptions,
|
|
246
|
+
provider: providerName(providerOptions),
|
|
247
|
+
model: callOptions?.model || providerOptions.defaultModel || "unknown",
|
|
248
|
+
operation,
|
|
249
|
+
callOptions,
|
|
250
|
+
effectiveMaxOutputTokens: effectiveOutputTokens(operation, providerOptions, callOptions)
|
|
251
|
+
});
|
|
252
|
+
const result = await Reflect.apply(value, target, args);
|
|
253
|
+
emitRequestEvent(providerOptions, {
|
|
254
|
+
...base,
|
|
255
|
+
status: "succeeded",
|
|
256
|
+
duration: Date.now() - startedAt
|
|
257
|
+
});
|
|
258
|
+
return result;
|
|
259
|
+
} catch (error) {
|
|
260
|
+
const failure = classifyRequestFailure(error);
|
|
261
|
+
base ||= requestEventBase({
|
|
262
|
+
providerOptions,
|
|
263
|
+
provider: providerName(providerOptions),
|
|
264
|
+
model: callOptions?.model || providerOptions.defaultModel || "unknown",
|
|
265
|
+
operation,
|
|
266
|
+
callOptions
|
|
267
|
+
});
|
|
268
|
+
emitRequestEvent(providerOptions, {
|
|
269
|
+
...base,
|
|
270
|
+
...failure,
|
|
271
|
+
duration: Date.now() - startedAt
|
|
272
|
+
});
|
|
273
|
+
throw error;
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
} });
|
|
277
|
+
}
|
|
278
|
+
//#endregion
|
|
279
|
+
//#region src/shared/rate-limit.ts
|
|
280
|
+
var RATE_LIMITED_METHODS = /* @__PURE__ */ new Set([
|
|
281
|
+
"decide",
|
|
282
|
+
"chat",
|
|
283
|
+
"complete",
|
|
284
|
+
"message",
|
|
285
|
+
"embed",
|
|
286
|
+
"embedImage",
|
|
287
|
+
"describeImage",
|
|
288
|
+
"generateImage",
|
|
289
|
+
"getModels",
|
|
290
|
+
"synthesizeSpeech",
|
|
291
|
+
"cloneVoice",
|
|
292
|
+
"designVoice",
|
|
293
|
+
"getVoices",
|
|
294
|
+
"submitVideoGenerationJob"
|
|
295
|
+
]);
|
|
296
|
+
/**
|
|
297
|
+
* Where each paced method takes its request controls (`timeout`, `signal`).
|
|
298
|
+
* For these, the timeout covers the whole call: time queued behind other calls
|
|
299
|
+
* on the same key, pacing and retry waits, and the request itself.
|
|
300
|
+
*/
|
|
301
|
+
var REQUEST_CONTROLS_INDEX = {
|
|
302
|
+
decide: 1,
|
|
303
|
+
chat: 1,
|
|
304
|
+
complete: 1,
|
|
305
|
+
message: 1,
|
|
306
|
+
describeImage: 2,
|
|
307
|
+
generateImage: 1,
|
|
308
|
+
submitVideoGenerationJob: 0
|
|
309
|
+
};
|
|
310
|
+
var MAX_BUDGET_COORDINATORS = 128;
|
|
311
|
+
var BUDGET_COORDINATOR_TTL_MS = 900 * 1e3;
|
|
312
|
+
function queueTimeoutError(budget) {
|
|
313
|
+
return new AIError(`AI request timed out after ${budget.timeoutMs}ms, including time waiting for its rate-limit turn`, "AI_TIMEOUT", budget.provider);
|
|
314
|
+
}
|
|
315
|
+
function abortedError(budget) {
|
|
316
|
+
return new AIError("AI request aborted by caller", "AI_ABORTED", budget.provider);
|
|
317
|
+
}
|
|
318
|
+
var BudgetCoordinator = class {
|
|
319
|
+
nextAvailableAt = 0;
|
|
320
|
+
pendingSchedules = 0;
|
|
321
|
+
tail = Promise.resolve();
|
|
322
|
+
lastUsedAt = Date.now();
|
|
323
|
+
/** The rate-limit error that holds the key closed until `blockedUntil`. */
|
|
324
|
+
blockedBy;
|
|
325
|
+
blockedUntil = 0;
|
|
326
|
+
touch() {
|
|
327
|
+
this.lastUsedAt = Date.now();
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Run `work` after every earlier call on this key. While the call is still
|
|
331
|
+
* queued, its deadline or abort signal releases the caller, and the work is
|
|
332
|
+
* skipped when its turn comes.
|
|
333
|
+
*/
|
|
334
|
+
schedule(work, budget) {
|
|
335
|
+
this.pendingSchedules += 1;
|
|
336
|
+
this.touch();
|
|
337
|
+
let gaveUp;
|
|
338
|
+
let started;
|
|
339
|
+
const guarded = () => {
|
|
340
|
+
if (gaveUp) return Promise.reject(gaveUp);
|
|
341
|
+
started?.();
|
|
342
|
+
return work();
|
|
343
|
+
};
|
|
344
|
+
const run = this.tail.then(guarded, guarded);
|
|
345
|
+
this.tail = run.then(() => void 0, () => void 0);
|
|
346
|
+
const result = run.finally(() => {
|
|
347
|
+
this.pendingSchedules = Math.max(0, this.pendingSchedules - 1);
|
|
348
|
+
this.touch();
|
|
349
|
+
});
|
|
350
|
+
if (budget.deadline === void 0 && !budget.signal) return result;
|
|
351
|
+
return new Promise((resolve, reject) => {
|
|
352
|
+
let timer;
|
|
353
|
+
const stopWaiting = () => {
|
|
354
|
+
if (timer) clearTimeout(timer);
|
|
355
|
+
budget.signal?.removeEventListener("abort", onAbort);
|
|
356
|
+
};
|
|
357
|
+
const giveUp = (error) => {
|
|
358
|
+
gaveUp = error;
|
|
359
|
+
stopWaiting();
|
|
360
|
+
reject(error);
|
|
361
|
+
};
|
|
362
|
+
const onAbort = () => giveUp(abortedError(budget));
|
|
363
|
+
started = stopWaiting;
|
|
364
|
+
if (budget.deadline !== void 0) timer = setTimeout(() => giveUp(queueTimeoutError(budget)), Math.max(0, budget.deadline - Date.now()));
|
|
365
|
+
if (budget.signal?.aborted) onAbort();
|
|
366
|
+
else budget.signal?.addEventListener("abort", onAbort, { once: true });
|
|
367
|
+
result.then((value) => {
|
|
368
|
+
stopWaiting();
|
|
369
|
+
resolve(value);
|
|
370
|
+
}, (error) => {
|
|
371
|
+
stopWaiting();
|
|
372
|
+
reject(error);
|
|
373
|
+
});
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Wait out pacing before a request. When the wait would run past the call's
|
|
378
|
+
* deadline, fail now: with a RateLimitError (reason and time until the key
|
|
379
|
+
* reopens) when a rate limit closed the key, else with a timeout.
|
|
380
|
+
*/
|
|
381
|
+
async waitUntilReady(budget, config) {
|
|
382
|
+
this.touch();
|
|
383
|
+
const now = Date.now();
|
|
384
|
+
const delayMs = this.nextAvailableAt - now;
|
|
385
|
+
if (delayMs > 0) {
|
|
386
|
+
if (budget.deadline !== void 0 && now + delayMs >= budget.deadline) {
|
|
387
|
+
const blockedBy = this.blockedUntil > now ? this.blockedBy : void 0;
|
|
388
|
+
if (blockedBy) throw new RateLimitError(blockedBy.provider, void 0, {
|
|
389
|
+
reason: blockedBy.reason,
|
|
390
|
+
retryAfterMs: Math.max(0, this.blockedUntil - now),
|
|
391
|
+
limitWindowMs: blockedBy.limitWindowMs,
|
|
392
|
+
model: blockedBy.model,
|
|
393
|
+
cause: blockedBy,
|
|
394
|
+
maxRetryDelayMs: config.maxRetryDelayMs
|
|
395
|
+
});
|
|
396
|
+
throw queueTimeoutError(budget);
|
|
397
|
+
}
|
|
398
|
+
await sleep(delayMs, budget.signal);
|
|
399
|
+
if (budget.signal?.aborted) throw abortedError(budget);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
delayFor(delayMs, cause) {
|
|
403
|
+
this.touch();
|
|
404
|
+
if (delayMs <= 0) return;
|
|
405
|
+
const until = Date.now() + delayMs;
|
|
406
|
+
this.nextAvailableAt = Math.max(this.nextAvailableAt, until);
|
|
407
|
+
if (cause && until >= this.blockedUntil) {
|
|
408
|
+
this.blockedBy = cause;
|
|
409
|
+
this.blockedUntil = until;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
isEvictable(now) {
|
|
413
|
+
return this.pendingSchedules === 0 && now >= this.nextAvailableAt;
|
|
414
|
+
}
|
|
415
|
+
isExpired(now) {
|
|
416
|
+
return this.isEvictable(now) && now - this.lastUsedAt >= BUDGET_COORDINATOR_TTL_MS;
|
|
417
|
+
}
|
|
418
|
+
getLastUsedAt() {
|
|
419
|
+
return this.lastUsedAt;
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
var budgetCoordinators = /* @__PURE__ */ new Map();
|
|
423
|
+
/** Sleep; an abort ends the sleep early (the caller checks the signal). */
|
|
424
|
+
function sleep(delayMs, signal) {
|
|
425
|
+
return new Promise((resolve) => {
|
|
426
|
+
const done = () => {
|
|
427
|
+
clearTimeout(timer);
|
|
428
|
+
signal?.removeEventListener("abort", done);
|
|
429
|
+
resolve();
|
|
430
|
+
};
|
|
431
|
+
const timer = setTimeout(done, delayMs);
|
|
432
|
+
signal?.addEventListener("abort", done, { once: true });
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
function pruneBudgetCoordinators() {
|
|
436
|
+
const now = Date.now();
|
|
437
|
+
for (const [key, coordinator] of budgetCoordinators.entries()) if (coordinator.isExpired(now)) budgetCoordinators.delete(key);
|
|
438
|
+
while (budgetCoordinators.size > MAX_BUDGET_COORDINATORS) {
|
|
439
|
+
const evictableEntries = [...budgetCoordinators.entries()].filter(([, coordinator]) => coordinator.isEvictable(now)).sort(([, leftCoordinator], [, rightCoordinator]) => leftCoordinator.getLastUsedAt() - rightCoordinator.getLastUsedAt());
|
|
440
|
+
if (evictableEntries.length === 0) break;
|
|
441
|
+
budgetCoordinators.delete(evictableEntries[0][0]);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
function getBudgetCoordinator(key) {
|
|
445
|
+
pruneBudgetCoordinators();
|
|
446
|
+
let coordinator = budgetCoordinators.get(key);
|
|
447
|
+
if (!coordinator) {
|
|
448
|
+
coordinator = new BudgetCoordinator();
|
|
449
|
+
budgetCoordinators.set(key, coordinator);
|
|
450
|
+
pruneBudgetCoordinators();
|
|
451
|
+
}
|
|
452
|
+
coordinator.touch();
|
|
453
|
+
return coordinator;
|
|
454
|
+
}
|
|
455
|
+
function hasPacingConfig(rateLimit) {
|
|
456
|
+
if (!rateLimit || rateLimit.enabled === false) return false;
|
|
457
|
+
return rateLimit.enabled === true || rateLimit.key !== void 0 || rateLimit.cooldownMs !== void 0 || rateLimit.initialDelayMs !== void 0 || rateLimit.maxAttempts !== void 0 || rateLimit.maxRetryDelayMs !== void 0;
|
|
458
|
+
}
|
|
459
|
+
function normalizeNonNegativeInteger(value, fallback) {
|
|
460
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
|
|
461
|
+
return Math.max(0, Math.trunc(value));
|
|
462
|
+
}
|
|
463
|
+
function hashKey(value) {
|
|
464
|
+
let hash = 2166136261;
|
|
465
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
466
|
+
hash ^= value.charCodeAt(index);
|
|
467
|
+
hash = Math.imul(hash, 16777619);
|
|
468
|
+
}
|
|
469
|
+
return (hash >>> 0).toString(36);
|
|
470
|
+
}
|
|
471
|
+
function deriveBudgetKey(options) {
|
|
472
|
+
const type = options.type;
|
|
473
|
+
const provider = typeof type === "string" && type ? type : "openai";
|
|
474
|
+
const seed = [
|
|
475
|
+
"apiKey" in options ? options.apiKey : void 0,
|
|
476
|
+
"apiToken" in options ? options.apiToken : void 0,
|
|
477
|
+
"credentials" in options ? options.credentials?.accessKeyId : void 0,
|
|
478
|
+
"endpoint" in options ? options.endpoint : void 0,
|
|
479
|
+
"baseUrl" in options ? options.baseUrl : void 0,
|
|
480
|
+
"cliPath" in options ? options.cliPath : void 0
|
|
481
|
+
].find((value) => typeof value === "string" && value.length > 0);
|
|
482
|
+
return seed ? `${provider}:${hashKey(seed)}` : `${provider}:default`;
|
|
483
|
+
}
|
|
484
|
+
function normalizeRateLimitConfig(options) {
|
|
485
|
+
const rateLimit = "rateLimit" in options ? options.rateLimit : void 0;
|
|
486
|
+
if (!hasPacingConfig(rateLimit)) return null;
|
|
487
|
+
return {
|
|
488
|
+
cooldownMs: normalizeNonNegativeInteger(rateLimit?.cooldownMs, 0),
|
|
489
|
+
initialDelayMs: normalizeNonNegativeInteger(rateLimit?.initialDelayMs, 5e3),
|
|
490
|
+
key: rateLimit?.key?.trim() || deriveBudgetKey(options),
|
|
491
|
+
maxAttempts: Math.max(1, normalizeNonNegativeInteger(rateLimit?.maxAttempts, 1)),
|
|
492
|
+
maxRetryDelayMs: normalizeNonNegativeInteger(rateLimit?.maxRetryDelayMs, DEFAULT_RATE_LIMIT_MAX_RETRY_DELAY_MS)
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
function getRetryDelayMs(error, config) {
|
|
496
|
+
const hintedDelayMs = typeof error.retryAfterMs === "number" && Number.isFinite(error.retryAfterMs) ? Math.max(0, Math.ceil(error.retryAfterMs)) : typeof error.retryAfter === "number" && Number.isFinite(error.retryAfter) ? Math.max(0, Math.ceil(error.retryAfter * 1e3)) : void 0;
|
|
497
|
+
if (hintedDelayMs !== void 0) return Math.max(config.cooldownMs, hintedDelayMs);
|
|
498
|
+
return Math.max(config.cooldownMs, config.initialDelayMs);
|
|
499
|
+
}
|
|
500
|
+
async function invokeWithPacing(execute, coordinator, config, budget, allowRetry = true) {
|
|
501
|
+
let attempt = 1;
|
|
502
|
+
while (true) {
|
|
503
|
+
await coordinator.waitUntilReady(budget, config);
|
|
504
|
+
const remainingMs = budget.deadline === void 0 ? void 0 : budget.deadline - Date.now();
|
|
505
|
+
if (remainingMs !== void 0 && remainingMs <= 0) throw queueTimeoutError(budget);
|
|
506
|
+
try {
|
|
507
|
+
const result = await execute(remainingMs);
|
|
508
|
+
coordinator.delayFor(config.cooldownMs);
|
|
509
|
+
return result;
|
|
510
|
+
} catch (error) {
|
|
511
|
+
if (error instanceof RateLimitError) {
|
|
512
|
+
const retryDelayMs = getRetryDelayMs(error, config);
|
|
513
|
+
coordinator.delayFor(retryDelayMs, error);
|
|
514
|
+
error.retryable = isRateLimitRetryableSoon(error.retryAfterMs ?? error.limitWindowMs, config.maxRetryDelayMs);
|
|
515
|
+
const fitsDeadline = budget.deadline === void 0 || Date.now() + retryDelayMs < budget.deadline;
|
|
516
|
+
if (allowRetry && error.retryable && fitsDeadline && attempt < config.maxAttempts) {
|
|
517
|
+
attempt += 1;
|
|
518
|
+
continue;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
throw error;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
function parseRetryAfterSeconds(retryAfter) {
|
|
526
|
+
if (typeof retryAfter === "number" && Number.isFinite(retryAfter)) return Math.max(0, retryAfter);
|
|
527
|
+
if (typeof retryAfter !== "string") return;
|
|
528
|
+
const trimmed = retryAfter.trim();
|
|
529
|
+
if (!trimmed) return;
|
|
530
|
+
const seconds = Number.parseFloat(trimmed);
|
|
531
|
+
if (!Number.isNaN(seconds)) return Math.max(0, seconds);
|
|
532
|
+
const retryAt = Date.parse(trimmed);
|
|
533
|
+
if (Number.isNaN(retryAt)) return;
|
|
534
|
+
return Math.max(0, Math.ceil((retryAt - Date.now()) / 1e3));
|
|
535
|
+
}
|
|
536
|
+
function getHeaderValue(headers, headerName) {
|
|
537
|
+
if (!headers || typeof headers !== "object") return;
|
|
538
|
+
if ("get" in headers && typeof headers.get === "function") return headers.get(headerName) ?? headers.get(headerName.toLowerCase());
|
|
539
|
+
const objectHeaders = headers;
|
|
540
|
+
return objectHeaders[headerName] ?? objectHeaders[headerName.toLowerCase()];
|
|
541
|
+
}
|
|
542
|
+
function extractRetryAfterSeconds(error) {
|
|
543
|
+
if (!error || typeof error !== "object") return;
|
|
544
|
+
if ("retryAfter" in error) {
|
|
545
|
+
const retryAfter = parseRetryAfterSeconds(error.retryAfter);
|
|
546
|
+
if (retryAfter !== void 0) return retryAfter;
|
|
547
|
+
}
|
|
548
|
+
if ("headers" in error) {
|
|
549
|
+
const retryAfter = parseRetryAfterSeconds(getHeaderValue(error.headers, "retry-after"));
|
|
550
|
+
if (retryAfter !== void 0) return retryAfter;
|
|
551
|
+
}
|
|
552
|
+
const message = "message" in error && typeof error.message === "string" ? error.message : "";
|
|
553
|
+
const retryAfterMatch = message.match(/retry after\s+(\d+(?:\.\d+)?)\s*s?/i) ?? message.match(/retryDelay[^\d]*(\d+(?:\.\d+)?)s/i);
|
|
554
|
+
return retryAfterMatch ? parseRetryAfterSeconds(retryAfterMatch[1]) : void 0;
|
|
555
|
+
}
|
|
556
|
+
var DURATION_UNITS_MS = {
|
|
557
|
+
ms: 1,
|
|
558
|
+
millisecond: 1,
|
|
559
|
+
milliseconds: 1,
|
|
560
|
+
s: 1e3,
|
|
561
|
+
sec: 1e3,
|
|
562
|
+
secs: 1e3,
|
|
563
|
+
second: 1e3,
|
|
564
|
+
seconds: 1e3,
|
|
565
|
+
m: 6e4,
|
|
566
|
+
min: 6e4,
|
|
567
|
+
mins: 6e4,
|
|
568
|
+
minute: 6e4,
|
|
569
|
+
minutes: 6e4,
|
|
570
|
+
h: 36e5,
|
|
571
|
+
hr: 36e5,
|
|
572
|
+
hrs: 36e5,
|
|
573
|
+
hour: 36e5,
|
|
574
|
+
hours: 36e5,
|
|
575
|
+
d: 864e5,
|
|
576
|
+
day: 864e5,
|
|
577
|
+
days: 864e5
|
|
578
|
+
};
|
|
579
|
+
var DURATION_PART = "\\d+(?:\\.\\d+)?\\s*(?:milliseconds?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d)(?![a-z])";
|
|
580
|
+
var DURATION = `${DURATION_PART}(?:\\s*${DURATION_PART})*`;
|
|
581
|
+
/**
|
|
582
|
+
* Parse a duration such as `20s`, `6m0s`, `1h`, `250ms` or `2 minutes` into
|
|
583
|
+
* milliseconds. Returns undefined for anything else.
|
|
584
|
+
*/
|
|
585
|
+
function parseDurationMs(text) {
|
|
586
|
+
const trimmed = text.trim();
|
|
587
|
+
if (!new RegExp(`^${DURATION}$`, "i").test(trimmed)) return void 0;
|
|
588
|
+
let total = 0;
|
|
589
|
+
for (const match of trimmed.matchAll(/(\d+(?:\.\d+)?)\s*([a-z]+)/gi)) total += Number.parseFloat(match[1]) * DURATION_UNITS_MS[match[2].toLowerCase()];
|
|
590
|
+
return Number.isFinite(total) ? Math.ceil(total) : void 0;
|
|
591
|
+
}
|
|
592
|
+
/** Reset headers that name when a limit lifts, as a duration or a timestamp. */
|
|
593
|
+
var RESET_HEADERS = [
|
|
594
|
+
"x-ratelimit-reset-requests",
|
|
595
|
+
"x-ratelimit-reset-tokens",
|
|
596
|
+
"anthropic-ratelimit-requests-reset",
|
|
597
|
+
"anthropic-ratelimit-tokens-reset",
|
|
598
|
+
"anthropic-ratelimit-input-tokens-reset",
|
|
599
|
+
"anthropic-ratelimit-output-tokens-reset"
|
|
600
|
+
];
|
|
601
|
+
function resetHeaderMs(headers) {
|
|
602
|
+
let latest;
|
|
603
|
+
for (const name of RESET_HEADERS) {
|
|
604
|
+
const raw = getHeaderValue(headers, name);
|
|
605
|
+
if (raw === void 0 || raw === null || raw === "") continue;
|
|
606
|
+
const text = String(raw).trim();
|
|
607
|
+
let ms = parseDurationMs(text);
|
|
608
|
+
if (ms === void 0 && /\d{4}-\d{2}-\d{2}T/.test(text)) {
|
|
609
|
+
const at = Date.parse(text);
|
|
610
|
+
if (!Number.isNaN(at)) ms = Math.max(0, at - Date.now());
|
|
611
|
+
}
|
|
612
|
+
if (ms !== void 0) latest = Math.max(latest ?? 0, ms);
|
|
613
|
+
}
|
|
614
|
+
return latest;
|
|
615
|
+
}
|
|
616
|
+
function stringAt(value, ...path) {
|
|
617
|
+
let current = value;
|
|
618
|
+
for (const key of path) {
|
|
619
|
+
if (!current || typeof current !== "object") return void 0;
|
|
620
|
+
current = current[key];
|
|
621
|
+
}
|
|
622
|
+
return typeof current === "string" && current.trim() ? current : void 0;
|
|
623
|
+
}
|
|
624
|
+
/**
|
|
625
|
+
* The provider's own reason text from an SDK error or response body, for
|
|
626
|
+
* example `token limit exceeded (277867/250000, resets every 1h)`.
|
|
627
|
+
*/
|
|
628
|
+
function extractRateLimitReason(error) {
|
|
629
|
+
const fromBody = stringAt(error, "error", "error", "message") ?? stringAt(error, "error", "message") ?? stringAt(error, "body", "error", "message") ?? stringAt(error, "body", "message");
|
|
630
|
+
if (fromBody) return fromBody;
|
|
631
|
+
const message = stringAt(error, "message");
|
|
632
|
+
if (!message) return void 0;
|
|
633
|
+
if (/^\s*\{/.test(message)) try {
|
|
634
|
+
const body = JSON.parse(message);
|
|
635
|
+
const text = stringAt(body, "error", "message") ?? stringAt(body, "message") ?? stringAt(body, "error");
|
|
636
|
+
if (text) return text;
|
|
637
|
+
} catch {}
|
|
638
|
+
const reason = message.replace(/^\d{3}\s+/, "").trim();
|
|
639
|
+
return /^(?:status code \(no body\)|too many requests|rate limit(?:ed| exceeded)?)\.?$/i.test(reason) ? void 0 : reason;
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Build a RateLimitError that keeps what the provider said: its reason text,
|
|
643
|
+
* any `Retry-After` or reset hint (headers or text), and a named limit window.
|
|
644
|
+
*/
|
|
645
|
+
function rateLimitErrorFrom(provider, error, extra = {}) {
|
|
646
|
+
const reason = extra.reason?.trim() || extractRateLimitReason(error);
|
|
647
|
+
const retryAfter = extractRetryAfterSeconds(error);
|
|
648
|
+
const headers = error && typeof error === "object" && "headers" in error ? error.headers : void 0;
|
|
649
|
+
let retryAfterMs = retryAfter !== void 0 ? retryAfter * 1e3 : resetHeaderMs(headers);
|
|
650
|
+
let limitWindowMs;
|
|
651
|
+
if (reason) {
|
|
652
|
+
if (retryAfterMs === void 0) {
|
|
653
|
+
const inMatch = reason.match(new RegExp(`(?:try again|retry|resets?|available)\\s+in\\s+(${DURATION})`, "i"));
|
|
654
|
+
if (inMatch) retryAfterMs = parseDurationMs(inMatch[1]);
|
|
655
|
+
}
|
|
656
|
+
const everyMatch = reason.match(new RegExp(`resets?\\s+(?:every|each)\\s+(${DURATION})`, "i"));
|
|
657
|
+
if (everyMatch) limitWindowMs = parseDurationMs(everyMatch[1]);
|
|
658
|
+
}
|
|
659
|
+
return new RateLimitError(provider, retryAfter, {
|
|
660
|
+
reason,
|
|
661
|
+
retryAfterMs,
|
|
662
|
+
limitWindowMs,
|
|
663
|
+
model: extra.model,
|
|
664
|
+
cause: error
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
function createRateLimitedAI(client, options) {
|
|
668
|
+
const config = normalizeRateLimitConfig(options);
|
|
669
|
+
if (!config) return client;
|
|
670
|
+
const wrappedMethods = /* @__PURE__ */ new Map();
|
|
671
|
+
const provider = typeof options.type === "string" && options.type ? options.type : "openai";
|
|
672
|
+
const clientTimeoutMs = positiveTimeout("timeout" in options ? options.timeout : void 0);
|
|
673
|
+
const allowSeevioSubmitRetry = options.type !== "seevio";
|
|
674
|
+
return new Proxy(client, { get(target, property, receiver) {
|
|
675
|
+
const value = Reflect.get(target, property, receiver);
|
|
676
|
+
if (typeof value !== "function") return value;
|
|
677
|
+
if (!RATE_LIMITED_METHODS.has(property)) {
|
|
678
|
+
if (!wrappedMethods.has(property)) wrappedMethods.set(property, value.bind(target));
|
|
679
|
+
return wrappedMethods.get(property);
|
|
680
|
+
}
|
|
681
|
+
if (!wrappedMethods.has(property)) {
|
|
682
|
+
const controlsIndex = REQUEST_CONTROLS_INDEX[property];
|
|
683
|
+
wrappedMethods.set(property, (...args) => {
|
|
684
|
+
const coordinator = getBudgetCoordinator(config.key);
|
|
685
|
+
const controls = controlsIndex === void 0 ? void 0 : args[controlsIndex];
|
|
686
|
+
const timeoutMs = controlsIndex === void 0 ? clientTimeoutMs : controls?.timeout !== void 0 ? positiveTimeout(controls.timeout) : clientTimeoutMs ?? 12e4;
|
|
687
|
+
const budget = {
|
|
688
|
+
provider,
|
|
689
|
+
timeoutMs,
|
|
690
|
+
deadline: timeoutMs === void 0 ? void 0 : Date.now() + timeoutMs,
|
|
691
|
+
signal: controls?.signal
|
|
692
|
+
};
|
|
693
|
+
return coordinator.schedule(() => invokeWithPacing((remainingMs) => {
|
|
694
|
+
const callArgs = [...args];
|
|
695
|
+
if (controlsIndex !== void 0 && remainingMs !== void 0) callArgs[controlsIndex] = {
|
|
696
|
+
...controls,
|
|
697
|
+
timeout: Math.max(1, Math.floor(remainingMs))
|
|
698
|
+
};
|
|
699
|
+
return Reflect.apply(value, target, callArgs);
|
|
700
|
+
}, coordinator, config, budget, property !== "submitVideoGenerationJob" || allowSeevioSubmitRetry), budget);
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
return wrappedMethods.get(property);
|
|
704
|
+
} });
|
|
705
|
+
}
|
|
706
|
+
/** A usable timeout, or undefined so the provider reports a bad value itself. */
|
|
707
|
+
function positiveTimeout(value) {
|
|
708
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.trunc(value) : void 0;
|
|
709
|
+
}
|
|
710
|
+
//#endregion
|
|
711
|
+
export { DEFAULT_AI_TIMEOUT_MS as a, normalizeChatOptions as c, DEFAULT_AI_MAX_RETRIES as i, normalizeImageGenerationOptions as l, rateLimitErrorFrom as n, createObservedAI as o, DEFAULT_AI_GENERATION_LIMITS as r, normalizeBaseAIOptions as s, createRateLimitedAI as t, prepareRequestControls as u };
|
|
712
|
+
|
|
713
|
+
//# sourceMappingURL=rate-limit-BzWfHA0e.js.map
|