@almadar/llm 1.0.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/LICENSE +72 -0
- package/dist/chunk-KH4JNOLT.js +174 -0
- package/dist/chunk-KH4JNOLT.js.map +1 -0
- package/dist/chunk-MJS33AAS.js +234 -0
- package/dist/chunk-MJS33AAS.js.map +1 -0
- package/dist/chunk-PV3G5PJS.js +633 -0
- package/dist/chunk-PV3G5PJS.js.map +1 -0
- package/dist/chunk-WM7QVK2Z.js +192 -0
- package/dist/chunk-WM7QVK2Z.js.map +1 -0
- package/dist/client.d.ts +136 -0
- package/dist/client.js +39 -0
- package/dist/client.js.map +1 -0
- package/dist/index.d.ts +67 -0
- package/dist/index.js +477 -0
- package/dist/index.js.map +1 -0
- package/dist/json-parser.d.ts +43 -0
- package/dist/json-parser.js +15 -0
- package/dist/json-parser.js.map +1 -0
- package/dist/rate-limiter-9XAWfHwe.d.ts +98 -0
- package/dist/structured-output.d.ts +113 -0
- package/dist/structured-output.js +16 -0
- package/dist/structured-output.js.map +1 -0
- package/package.json +55 -0
- package/src/client.ts +967 -0
- package/src/continuation.ts +290 -0
- package/src/index.ts +87 -0
- package/src/json-parser.ts +273 -0
- package/src/rate-limiter.ts +237 -0
- package/src/structured-output.ts +330 -0
- package/src/token-tracker.ts +116 -0
- package/src/truncation-detector.ts +308 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ANTHROPIC_MODELS,
|
|
3
|
+
DEEPSEEK_MODELS,
|
|
4
|
+
KIMI_MODELS,
|
|
5
|
+
LLMClient,
|
|
6
|
+
OPENAI_MODELS,
|
|
7
|
+
createAnthropicClient,
|
|
8
|
+
createCreativeClient,
|
|
9
|
+
createDeepSeekClient,
|
|
10
|
+
createFixClient,
|
|
11
|
+
createKimiClient,
|
|
12
|
+
createOpenAIClient,
|
|
13
|
+
createRequirementsClient,
|
|
14
|
+
getAvailableProvider,
|
|
15
|
+
getSharedLLMClient,
|
|
16
|
+
isProviderAvailable,
|
|
17
|
+
resetSharedLLMClient
|
|
18
|
+
} from "./chunk-PV3G5PJS.js";
|
|
19
|
+
import {
|
|
20
|
+
autoCloseJson,
|
|
21
|
+
extractJsonFromText,
|
|
22
|
+
isValidJson,
|
|
23
|
+
parseJsonResponse,
|
|
24
|
+
safeParseJson
|
|
25
|
+
} from "./chunk-WM7QVK2Z.js";
|
|
26
|
+
import {
|
|
27
|
+
STRUCTURED_OUTPUT_MODELS,
|
|
28
|
+
StructuredOutputClient,
|
|
29
|
+
getStructuredOutputClient,
|
|
30
|
+
isStructuredOutputAvailable,
|
|
31
|
+
resetStructuredOutputClient
|
|
32
|
+
} from "./chunk-KH4JNOLT.js";
|
|
33
|
+
import {
|
|
34
|
+
RateLimiter,
|
|
35
|
+
TokenTracker,
|
|
36
|
+
getGlobalRateLimiter,
|
|
37
|
+
getGlobalTokenTracker,
|
|
38
|
+
resetGlobalRateLimiter,
|
|
39
|
+
resetGlobalTokenTracker
|
|
40
|
+
} from "./chunk-MJS33AAS.js";
|
|
41
|
+
|
|
42
|
+
// src/truncation-detector.ts
|
|
43
|
+
function detectTruncation(response, finishReason) {
|
|
44
|
+
if (finishReason === "length") {
|
|
45
|
+
const bracketInfo2 = countBrackets(response);
|
|
46
|
+
return {
|
|
47
|
+
isTruncated: true,
|
|
48
|
+
reason: "finish_reason",
|
|
49
|
+
partialContent: response,
|
|
50
|
+
lastCompleteElement: findLastCompleteElement(response),
|
|
51
|
+
missingCloseBrackets: bracketInfo2.missingCloseBrackets,
|
|
52
|
+
missingCloseBraces: bracketInfo2.missingCloseBraces
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
JSON.parse(response);
|
|
57
|
+
return { isTruncated: false, reason: "none" };
|
|
58
|
+
} catch {
|
|
59
|
+
}
|
|
60
|
+
if (finishReason === "stop" || finishReason === null) {
|
|
61
|
+
const trimmed = response.trim();
|
|
62
|
+
const isMidContent = trimmed.endsWith(",") || trimmed.endsWith(":") || trimmed.endsWith('": ') || /:\s*$/.test(trimmed) || /,\s*$/.test(trimmed);
|
|
63
|
+
if (isMidContent) {
|
|
64
|
+
const bracketInfo2 = countBrackets(response);
|
|
65
|
+
return {
|
|
66
|
+
isTruncated: true,
|
|
67
|
+
reason: "json_incomplete",
|
|
68
|
+
partialContent: response,
|
|
69
|
+
lastCompleteElement: findLastCompleteElement(response),
|
|
70
|
+
missingCloseBrackets: bracketInfo2.missingCloseBrackets,
|
|
71
|
+
missingCloseBraces: bracketInfo2.missingCloseBraces
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
const closed = autoCloseJson(trimmed);
|
|
76
|
+
JSON.parse(closed);
|
|
77
|
+
return { isTruncated: false, reason: "none" };
|
|
78
|
+
} catch {
|
|
79
|
+
return { isTruncated: false, reason: "none" };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const bracketInfo = countBrackets(response);
|
|
83
|
+
if (bracketInfo.missingCloseBrackets > 0 || bracketInfo.missingCloseBraces > 0) {
|
|
84
|
+
return {
|
|
85
|
+
isTruncated: true,
|
|
86
|
+
reason: "bracket_mismatch",
|
|
87
|
+
partialContent: response,
|
|
88
|
+
lastCompleteElement: findLastCompleteElement(response),
|
|
89
|
+
missingCloseBrackets: bracketInfo.missingCloseBrackets,
|
|
90
|
+
missingCloseBraces: bracketInfo.missingCloseBraces
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
return { isTruncated: false, reason: "none" };
|
|
94
|
+
}
|
|
95
|
+
function countBrackets(json) {
|
|
96
|
+
let inString = false;
|
|
97
|
+
let escaped = false;
|
|
98
|
+
let openBrackets = 0;
|
|
99
|
+
let closeBrackets = 0;
|
|
100
|
+
let openBraces = 0;
|
|
101
|
+
let closeBraces = 0;
|
|
102
|
+
for (const char of json) {
|
|
103
|
+
if (escaped) {
|
|
104
|
+
escaped = false;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (char === "\\" && inString) {
|
|
108
|
+
escaped = true;
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (char === '"') {
|
|
112
|
+
inString = !inString;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (inString) continue;
|
|
116
|
+
switch (char) {
|
|
117
|
+
case "[":
|
|
118
|
+
openBrackets++;
|
|
119
|
+
break;
|
|
120
|
+
case "]":
|
|
121
|
+
closeBrackets++;
|
|
122
|
+
break;
|
|
123
|
+
case "{":
|
|
124
|
+
openBraces++;
|
|
125
|
+
break;
|
|
126
|
+
case "}":
|
|
127
|
+
closeBraces++;
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
openBrackets,
|
|
133
|
+
closeBrackets,
|
|
134
|
+
openBraces,
|
|
135
|
+
closeBraces,
|
|
136
|
+
missingCloseBrackets: Math.max(0, openBrackets - closeBrackets),
|
|
137
|
+
missingCloseBraces: Math.max(0, openBraces - closeBraces)
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function findLastCompleteElement(json) {
|
|
141
|
+
const autoClosed = autoCloseJson(json);
|
|
142
|
+
try {
|
|
143
|
+
return JSON.parse(autoClosed);
|
|
144
|
+
} catch {
|
|
145
|
+
}
|
|
146
|
+
const trimmed = json.trim();
|
|
147
|
+
if (trimmed.startsWith("[")) {
|
|
148
|
+
const lastCompleteIndex = findLastCompleteArrayElement(trimmed);
|
|
149
|
+
if (lastCompleteIndex > 0) {
|
|
150
|
+
const subset = trimmed.substring(0, lastCompleteIndex) + "]";
|
|
151
|
+
try {
|
|
152
|
+
return JSON.parse(subset);
|
|
153
|
+
} catch {
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (trimmed.startsWith("{")) {
|
|
158
|
+
const closed = autoCloseJson(trimmed);
|
|
159
|
+
try {
|
|
160
|
+
return JSON.parse(closed);
|
|
161
|
+
} catch {
|
|
162
|
+
const lastCompleteIndex = findLastCompleteObjectProperty(trimmed);
|
|
163
|
+
if (lastCompleteIndex > 0) {
|
|
164
|
+
const subset = trimmed.substring(0, lastCompleteIndex) + "}";
|
|
165
|
+
try {
|
|
166
|
+
return JSON.parse(subset);
|
|
167
|
+
} catch {
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
function findLastCompleteArrayElement(json) {
|
|
175
|
+
let depth = 0;
|
|
176
|
+
let inString = false;
|
|
177
|
+
let escaped = false;
|
|
178
|
+
let lastCompleteElementEnd = -1;
|
|
179
|
+
for (let i = 0; i < json.length; i++) {
|
|
180
|
+
const char = json[i];
|
|
181
|
+
if (escaped) {
|
|
182
|
+
escaped = false;
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
if (char === "\\" && inString) {
|
|
186
|
+
escaped = true;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (char === '"') {
|
|
190
|
+
inString = !inString;
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
if (inString) continue;
|
|
194
|
+
if (char === "[" || char === "{") {
|
|
195
|
+
depth++;
|
|
196
|
+
} else if (char === "]" || char === "}") {
|
|
197
|
+
depth--;
|
|
198
|
+
if (depth === 1) {
|
|
199
|
+
lastCompleteElementEnd = i + 1;
|
|
200
|
+
}
|
|
201
|
+
} else if (char === "," && depth === 1) {
|
|
202
|
+
lastCompleteElementEnd = i;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return lastCompleteElementEnd > 0 ? lastCompleteElementEnd : -1;
|
|
206
|
+
}
|
|
207
|
+
function findLastCompleteObjectProperty(json) {
|
|
208
|
+
let depth = 0;
|
|
209
|
+
let inString = false;
|
|
210
|
+
let escaped = false;
|
|
211
|
+
let lastCommaIndex = -1;
|
|
212
|
+
for (let i = 0; i < json.length; i++) {
|
|
213
|
+
const char = json[i];
|
|
214
|
+
if (escaped) {
|
|
215
|
+
escaped = false;
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
if (char === "\\" && inString) {
|
|
219
|
+
escaped = true;
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
if (char === '"') {
|
|
223
|
+
inString = !inString;
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
if (inString) continue;
|
|
227
|
+
if (char === "[" || char === "{") {
|
|
228
|
+
depth++;
|
|
229
|
+
} else if (char === "]" || char === "}") {
|
|
230
|
+
depth--;
|
|
231
|
+
} else if (char === "," && depth === 1) {
|
|
232
|
+
lastCommaIndex = i;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return lastCommaIndex > 0 ? lastCommaIndex : -1;
|
|
236
|
+
}
|
|
237
|
+
function isLikelyTruncated(content) {
|
|
238
|
+
const trimmed = content.trim();
|
|
239
|
+
if (!trimmed) return false;
|
|
240
|
+
const brackets = countBrackets(trimmed);
|
|
241
|
+
if (brackets.missingCloseBrackets > 0 || brackets.missingCloseBraces > 0) {
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
244
|
+
const abruptEndings = [
|
|
245
|
+
/,\s*$/,
|
|
246
|
+
/:\s*$/,
|
|
247
|
+
/"\s*:\s*$/,
|
|
248
|
+
/\[\s*$/,
|
|
249
|
+
/{\s*$/
|
|
250
|
+
];
|
|
251
|
+
for (const pattern of abruptEndings) {
|
|
252
|
+
if (pattern.test(trimmed)) return true;
|
|
253
|
+
}
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// src/continuation.ts
|
|
258
|
+
var DEFAULT_MAX_TOKENS = 8192;
|
|
259
|
+
var DEFAULT_MAX_CONTINUATIONS = 3;
|
|
260
|
+
var DEFAULT_CONTINUATION_SYSTEM_PROMPT = `You are a JSON continuation assistant. Your ONLY job is to continue generating JSON from where the previous response was truncated.
|
|
261
|
+
|
|
262
|
+
Rules:
|
|
263
|
+
1. Continue from EXACTLY where the previous output stopped
|
|
264
|
+
2. Do NOT repeat any content already generated
|
|
265
|
+
3. Complete the JSON structure properly with all closing brackets
|
|
266
|
+
4. Do NOT wrap in markdown code blocks
|
|
267
|
+
5. Output ONLY the continuation JSON, nothing else`;
|
|
268
|
+
function mergeResponses(previous, continuation) {
|
|
269
|
+
const trimmedPrev = previous.trimEnd();
|
|
270
|
+
const trimmedCont = continuation.trimStart();
|
|
271
|
+
let cleanedCont = trimmedCont.replace(/^```json?\s*/i, "").replace(/```\s*$/i, "").trim();
|
|
272
|
+
if (cleanedCont.startsWith("{")) {
|
|
273
|
+
try {
|
|
274
|
+
const contParsed = JSON.parse(autoCloseJson(cleanedCont));
|
|
275
|
+
const keys = Object.keys(contParsed);
|
|
276
|
+
if (keys.length === 1 && Array.isArray(contParsed[keys[0]])) {
|
|
277
|
+
cleanedCont = contParsed[keys[0]].map((item) => JSON.stringify(item)).join(",\n");
|
|
278
|
+
}
|
|
279
|
+
} catch {
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
if (cleanedCont.startsWith("}") || cleanedCont.startsWith("]")) {
|
|
283
|
+
return trimmedPrev + cleanedCont;
|
|
284
|
+
}
|
|
285
|
+
const prevEndsWithValue = /[\}\]\"\d]$/.test(trimmedPrev);
|
|
286
|
+
const contStartsWithValue = /^[\{\[\"]/.test(cleanedCont);
|
|
287
|
+
if (prevEndsWithValue && contStartsWithValue) {
|
|
288
|
+
return trimmedPrev + ",\n" + cleanedCont;
|
|
289
|
+
}
|
|
290
|
+
return trimmedPrev + cleanedCont;
|
|
291
|
+
}
|
|
292
|
+
function salvagePartialResponse(rawResponse) {
|
|
293
|
+
console.warn("[Continuation] Attempting to salvage partial response");
|
|
294
|
+
try {
|
|
295
|
+
const cleanedResponse = extractJsonFromText(rawResponse) || rawResponse;
|
|
296
|
+
const closed = autoCloseJson(cleanedResponse);
|
|
297
|
+
const parsed = JSON.parse(closed);
|
|
298
|
+
console.log("[Continuation] Successfully salvaged partial response");
|
|
299
|
+
return parsed;
|
|
300
|
+
} catch (error) {
|
|
301
|
+
console.error("[Continuation] Could not salvage response:", error);
|
|
302
|
+
}
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
async function callWithContinuation(options) {
|
|
306
|
+
const {
|
|
307
|
+
client,
|
|
308
|
+
systemPrompt,
|
|
309
|
+
userPrompt,
|
|
310
|
+
schema,
|
|
311
|
+
maxTokens = DEFAULT_MAX_TOKENS,
|
|
312
|
+
maxContinuations = DEFAULT_MAX_CONTINUATIONS,
|
|
313
|
+
buildContinuationPrompt,
|
|
314
|
+
continuationSystemPrompt = DEFAULT_CONTINUATION_SYSTEM_PROMPT
|
|
315
|
+
} = options;
|
|
316
|
+
let rawResponse = "";
|
|
317
|
+
let continuationCount = 0;
|
|
318
|
+
const warnings = [];
|
|
319
|
+
let wasSalvaged = false;
|
|
320
|
+
console.log("[Continuation] Starting LLM call with continuation support");
|
|
321
|
+
console.log(
|
|
322
|
+
`[Continuation] Max tokens: ${maxTokens}, Max continuations: ${maxContinuations}`
|
|
323
|
+
);
|
|
324
|
+
try {
|
|
325
|
+
const response = await client.callRawWithMetadata({
|
|
326
|
+
systemPrompt,
|
|
327
|
+
userPrompt,
|
|
328
|
+
maxTokens
|
|
329
|
+
});
|
|
330
|
+
rawResponse = extractJsonFromText(response.raw) || response.raw;
|
|
331
|
+
console.log(
|
|
332
|
+
`[Continuation] Initial response: ${rawResponse.length} chars, finish_reason: ${response.finishReason}`
|
|
333
|
+
);
|
|
334
|
+
let truncation = detectTruncation(rawResponse, response.finishReason);
|
|
335
|
+
while (truncation.isTruncated && continuationCount < maxContinuations) {
|
|
336
|
+
continuationCount++;
|
|
337
|
+
const warningMsg = `Response truncated (${truncation.reason}), continuing (attempt ${continuationCount}/${maxContinuations})`;
|
|
338
|
+
console.log(`[Continuation] ${warningMsg}`);
|
|
339
|
+
warnings.push(warningMsg);
|
|
340
|
+
const contPrompt = buildContinuationPrompt(
|
|
341
|
+
rawResponse,
|
|
342
|
+
continuationCount
|
|
343
|
+
);
|
|
344
|
+
const contResponse = await client.callRawWithMetadata({
|
|
345
|
+
systemPrompt: continuationSystemPrompt,
|
|
346
|
+
userPrompt: contPrompt,
|
|
347
|
+
maxTokens
|
|
348
|
+
});
|
|
349
|
+
console.log(
|
|
350
|
+
`[Continuation] Continuation response: ${contResponse.raw.length} chars, finish_reason: ${contResponse.finishReason}`
|
|
351
|
+
);
|
|
352
|
+
const cleanedContResponse = extractJsonFromText(contResponse.raw) || contResponse.raw;
|
|
353
|
+
rawResponse = mergeResponses(rawResponse, cleanedContResponse);
|
|
354
|
+
truncation = detectTruncation(rawResponse, contResponse.finishReason);
|
|
355
|
+
}
|
|
356
|
+
if (continuationCount >= maxContinuations && truncation.isTruncated) {
|
|
357
|
+
console.warn(
|
|
358
|
+
`[Continuation] Reached max continuations (${maxContinuations}), attempting to salvage...`
|
|
359
|
+
);
|
|
360
|
+
warnings.push(
|
|
361
|
+
`Reached max continuations - some content may be incomplete`
|
|
362
|
+
);
|
|
363
|
+
wasSalvaged = true;
|
|
364
|
+
}
|
|
365
|
+
const cleanedResponse = extractJsonFromText(rawResponse) || rawResponse;
|
|
366
|
+
let data;
|
|
367
|
+
try {
|
|
368
|
+
if (isValidJson(cleanedResponse)) {
|
|
369
|
+
data = JSON.parse(cleanedResponse);
|
|
370
|
+
} else {
|
|
371
|
+
const closed = autoCloseJson(cleanedResponse);
|
|
372
|
+
data = JSON.parse(closed);
|
|
373
|
+
if (!wasSalvaged) {
|
|
374
|
+
warnings.push("Response required auto-closing of JSON brackets");
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
} catch (parseError) {
|
|
378
|
+
const salvaged = salvagePartialResponse(cleanedResponse);
|
|
379
|
+
if (salvaged) {
|
|
380
|
+
data = salvaged;
|
|
381
|
+
wasSalvaged = true;
|
|
382
|
+
warnings.push("Response was salvaged from partial data");
|
|
383
|
+
} else {
|
|
384
|
+
throw new Error(
|
|
385
|
+
`Failed to parse response after ${continuationCount} continuations: ${parseError}`
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
if (schema) {
|
|
390
|
+
try {
|
|
391
|
+
data = schema.parse(data);
|
|
392
|
+
} catch (validationError) {
|
|
393
|
+
console.warn(
|
|
394
|
+
"[Continuation] Schema validation failed:",
|
|
395
|
+
validationError
|
|
396
|
+
);
|
|
397
|
+
warnings.push(`Schema validation issue: ${validationError}`);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
console.log(
|
|
401
|
+
`[Continuation] Complete. Continuations: ${continuationCount}, Warnings: ${warnings.length}`
|
|
402
|
+
);
|
|
403
|
+
return {
|
|
404
|
+
data,
|
|
405
|
+
raw: rawResponse,
|
|
406
|
+
continuationCount,
|
|
407
|
+
warnings,
|
|
408
|
+
wasSalvaged
|
|
409
|
+
};
|
|
410
|
+
} catch (error) {
|
|
411
|
+
console.error("[Continuation] Error during LLM call:", error);
|
|
412
|
+
throw error;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
function buildGenericContinuationPrompt(context, partialResponse, attempt, maxAttempts = DEFAULT_MAX_CONTINUATIONS) {
|
|
416
|
+
return `## CONTINUATION REQUEST (Attempt ${attempt}/${maxAttempts})
|
|
417
|
+
|
|
418
|
+
Your previous response was truncated. Continue generating from where you left off.
|
|
419
|
+
|
|
420
|
+
### ORIGINAL CONTEXT
|
|
421
|
+
${context}
|
|
422
|
+
|
|
423
|
+
### WHAT YOU GENERATED SO FAR
|
|
424
|
+
\`\`\`json
|
|
425
|
+
${partialResponse}
|
|
426
|
+
\`\`\`
|
|
427
|
+
|
|
428
|
+
### INSTRUCTIONS
|
|
429
|
+
1. Continue from EXACTLY where the response was cut off
|
|
430
|
+
2. Do NOT repeat any content already generated
|
|
431
|
+
3. Complete the JSON structure properly
|
|
432
|
+
4. Do NOT wrap your response in markdown code blocks
|
|
433
|
+
|
|
434
|
+
Continue generating now:`;
|
|
435
|
+
}
|
|
436
|
+
export {
|
|
437
|
+
ANTHROPIC_MODELS,
|
|
438
|
+
DEEPSEEK_MODELS,
|
|
439
|
+
KIMI_MODELS,
|
|
440
|
+
LLMClient,
|
|
441
|
+
OPENAI_MODELS,
|
|
442
|
+
RateLimiter,
|
|
443
|
+
STRUCTURED_OUTPUT_MODELS,
|
|
444
|
+
StructuredOutputClient,
|
|
445
|
+
TokenTracker,
|
|
446
|
+
autoCloseJson,
|
|
447
|
+
buildGenericContinuationPrompt,
|
|
448
|
+
callWithContinuation,
|
|
449
|
+
createAnthropicClient,
|
|
450
|
+
createCreativeClient,
|
|
451
|
+
createDeepSeekClient,
|
|
452
|
+
createFixClient,
|
|
453
|
+
createKimiClient,
|
|
454
|
+
createOpenAIClient,
|
|
455
|
+
createRequirementsClient,
|
|
456
|
+
detectTruncation,
|
|
457
|
+
extractJsonFromText,
|
|
458
|
+
findLastCompleteElement,
|
|
459
|
+
getAvailableProvider,
|
|
460
|
+
getGlobalRateLimiter,
|
|
461
|
+
getGlobalTokenTracker,
|
|
462
|
+
getSharedLLMClient,
|
|
463
|
+
getStructuredOutputClient,
|
|
464
|
+
isLikelyTruncated,
|
|
465
|
+
isProviderAvailable,
|
|
466
|
+
isStructuredOutputAvailable,
|
|
467
|
+
isValidJson,
|
|
468
|
+
mergeResponses,
|
|
469
|
+
parseJsonResponse,
|
|
470
|
+
resetGlobalRateLimiter,
|
|
471
|
+
resetGlobalTokenTracker,
|
|
472
|
+
resetSharedLLMClient,
|
|
473
|
+
resetStructuredOutputClient,
|
|
474
|
+
safeParseJson,
|
|
475
|
+
salvagePartialResponse
|
|
476
|
+
};
|
|
477
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/truncation-detector.ts","../src/continuation.ts"],"sourcesContent":["/**\n * Truncation Detector\n *\n * Utilities for detecting when LLM output has been truncated and\n * extracting usable content from partial responses.\n *\n * @packageDocumentation\n */\n\nimport type { LLMFinishReason } from './client.js';\nimport { autoCloseJson } from './json-parser.js';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type TruncationReason =\n | 'finish_reason'\n | 'json_incomplete'\n | 'bracket_mismatch'\n | 'none';\n\nexport interface TruncationResult {\n isTruncated: boolean;\n reason: TruncationReason;\n partialContent?: string;\n lastCompleteElement?: unknown;\n missingCloseBrackets?: number;\n missingCloseBraces?: number;\n}\n\n// ============================================================================\n// Main Detection Function\n// ============================================================================\n\nexport function detectTruncation(\n response: string,\n finishReason: LLMFinishReason,\n): TruncationResult {\n if (finishReason === 'length') {\n const bracketInfo = countBrackets(response);\n return {\n isTruncated: true,\n reason: 'finish_reason',\n partialContent: response,\n lastCompleteElement: findLastCompleteElement(response),\n missingCloseBrackets: bracketInfo.missingCloseBrackets,\n missingCloseBraces: bracketInfo.missingCloseBraces,\n };\n }\n\n try {\n JSON.parse(response);\n return { isTruncated: false, reason: 'none' };\n } catch {\n // JSON is invalid, check if due to truncation\n }\n\n if (finishReason === 'stop' || finishReason === null) {\n const trimmed = response.trim();\n\n const isMidContent =\n trimmed.endsWith(',') ||\n trimmed.endsWith(':') ||\n trimmed.endsWith('\": ') ||\n /:\\s*$/.test(trimmed) ||\n /,\\s*$/.test(trimmed);\n\n if (isMidContent) {\n const bracketInfo = countBrackets(response);\n return {\n isTruncated: true,\n reason: 'json_incomplete',\n partialContent: response,\n lastCompleteElement: findLastCompleteElement(response),\n missingCloseBrackets: bracketInfo.missingCloseBrackets,\n missingCloseBraces: bracketInfo.missingCloseBraces,\n };\n }\n\n try {\n const closed = autoCloseJson(trimmed);\n JSON.parse(closed);\n return { isTruncated: false, reason: 'none' };\n } catch {\n return { isTruncated: false, reason: 'none' };\n }\n }\n\n const bracketInfo = countBrackets(response);\n if (\n bracketInfo.missingCloseBrackets > 0 ||\n bracketInfo.missingCloseBraces > 0\n ) {\n return {\n isTruncated: true,\n reason: 'bracket_mismatch',\n partialContent: response,\n lastCompleteElement: findLastCompleteElement(response),\n missingCloseBrackets: bracketInfo.missingCloseBrackets,\n missingCloseBraces: bracketInfo.missingCloseBraces,\n };\n }\n\n return { isTruncated: false, reason: 'none' };\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\nfunction countBrackets(json: string): {\n openBrackets: number;\n closeBrackets: number;\n openBraces: number;\n closeBraces: number;\n missingCloseBrackets: number;\n missingCloseBraces: number;\n} {\n let inString = false;\n let escaped = false;\n let openBrackets = 0;\n let closeBrackets = 0;\n let openBraces = 0;\n let closeBraces = 0;\n\n for (const char of json) {\n if (escaped) {\n escaped = false;\n continue;\n }\n if (char === '\\\\' && inString) {\n escaped = true;\n continue;\n }\n if (char === '\"') {\n inString = !inString;\n continue;\n }\n if (inString) continue;\n\n switch (char) {\n case '[':\n openBrackets++;\n break;\n case ']':\n closeBrackets++;\n break;\n case '{':\n openBraces++;\n break;\n case '}':\n closeBraces++;\n break;\n }\n }\n\n return {\n openBrackets,\n closeBrackets,\n openBraces,\n closeBraces,\n missingCloseBrackets: Math.max(0, openBrackets - closeBrackets),\n missingCloseBraces: Math.max(0, openBraces - closeBraces),\n };\n}\n\nexport function findLastCompleteElement(json: string): unknown | null {\n const autoClosed = autoCloseJson(json);\n try {\n return JSON.parse(autoClosed);\n } catch {\n // Auto-close didn't work\n }\n\n const trimmed = json.trim();\n\n if (trimmed.startsWith('[')) {\n const lastCompleteIndex = findLastCompleteArrayElement(trimmed);\n if (lastCompleteIndex > 0) {\n const subset = trimmed.substring(0, lastCompleteIndex) + ']';\n try {\n return JSON.parse(subset);\n } catch {\n // Continue\n }\n }\n }\n\n if (trimmed.startsWith('{')) {\n const closed = autoCloseJson(trimmed);\n try {\n return JSON.parse(closed);\n } catch {\n const lastCompleteIndex = findLastCompleteObjectProperty(trimmed);\n if (lastCompleteIndex > 0) {\n const subset = trimmed.substring(0, lastCompleteIndex) + '}';\n try {\n return JSON.parse(subset);\n } catch {\n // Give up\n }\n }\n }\n }\n\n return null;\n}\n\nfunction findLastCompleteArrayElement(json: string): number {\n let depth = 0;\n let inString = false;\n let escaped = false;\n let lastCompleteElementEnd = -1;\n\n for (let i = 0; i < json.length; i++) {\n const char = json[i];\n\n if (escaped) {\n escaped = false;\n continue;\n }\n if (char === '\\\\' && inString) {\n escaped = true;\n continue;\n }\n if (char === '\"') {\n inString = !inString;\n continue;\n }\n if (inString) continue;\n\n if (char === '[' || char === '{') {\n depth++;\n } else if (char === ']' || char === '}') {\n depth--;\n if (depth === 1) {\n lastCompleteElementEnd = i + 1;\n }\n } else if (char === ',' && depth === 1) {\n lastCompleteElementEnd = i;\n }\n }\n\n return lastCompleteElementEnd > 0 ? lastCompleteElementEnd : -1;\n}\n\nfunction findLastCompleteObjectProperty(json: string): number {\n let depth = 0;\n let inString = false;\n let escaped = false;\n let lastCommaIndex = -1;\n\n for (let i = 0; i < json.length; i++) {\n const char = json[i];\n\n if (escaped) {\n escaped = false;\n continue;\n }\n if (char === '\\\\' && inString) {\n escaped = true;\n continue;\n }\n if (char === '\"') {\n inString = !inString;\n continue;\n }\n if (inString) continue;\n\n if (char === '[' || char === '{') {\n depth++;\n } else if (char === ']' || char === '}') {\n depth--;\n } else if (char === ',' && depth === 1) {\n lastCommaIndex = i;\n }\n }\n\n return lastCommaIndex > 0 ? lastCommaIndex : -1;\n}\n\nexport function isLikelyTruncated(content: string): boolean {\n const trimmed = content.trim();\n if (!trimmed) return false;\n\n const brackets = countBrackets(trimmed);\n if (\n brackets.missingCloseBrackets > 0 ||\n brackets.missingCloseBraces > 0\n ) {\n return true;\n }\n\n const abruptEndings = [\n /,\\s*$/,\n /:\\s*$/,\n /\"\\s*:\\s*$/,\n /\\[\\s*$/,\n /{\\s*$/,\n ];\n\n for (const pattern of abruptEndings) {\n if (pattern.test(trimmed)) return true;\n }\n\n return false;\n}\n","/**\n * LLM Continuation Utility\n *\n * Handles truncated LLM responses with automatic continuation.\n * - Detects truncation via finish_reason and JSON structure\n * - Automatically continues with full context\n * - Merges partial and continuation responses\n * - Salvages partial data if max continuations reached\n *\n * @packageDocumentation\n */\n\nimport { z } from 'zod';\nimport { LLMClient, type LLMFinishReason } from './client.js';\nimport { detectTruncation } from './truncation-detector.js';\nimport { extractJsonFromText, autoCloseJson, isValidJson } from './json-parser.js';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface ContinuationOptions<T> {\n client: LLMClient;\n systemPrompt: string;\n userPrompt: string;\n schema?: z.ZodSchema<T>;\n maxTokens?: number;\n maxContinuations?: number;\n maxRetries?: number;\n buildContinuationPrompt: (\n partialResponse: string,\n attempt: number,\n ) => string;\n continuationSystemPrompt?: string;\n}\n\nexport interface ContinuationResult<T> {\n data: T;\n raw: string;\n continuationCount: number;\n warnings: string[];\n wasSalvaged: boolean;\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst DEFAULT_MAX_TOKENS = 8192;\nconst DEFAULT_MAX_CONTINUATIONS = 3;\n\n/**\n * Default continuation system prompt.\n * Used when no custom continuationSystemPrompt is provided.\n */\nconst DEFAULT_CONTINUATION_SYSTEM_PROMPT = `You are a JSON continuation assistant. Your ONLY job is to continue generating JSON from where the previous response was truncated.\n\nRules:\n1. Continue from EXACTLY where the previous output stopped\n2. Do NOT repeat any content already generated\n3. Complete the JSON structure properly with all closing brackets\n4. Do NOT wrap in markdown code blocks\n5. Output ONLY the continuation JSON, nothing else`;\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\nexport function mergeResponses(\n previous: string,\n continuation: string,\n): string {\n const trimmedPrev = previous.trimEnd();\n const trimmedCont = continuation.trimStart();\n\n let cleanedCont = trimmedCont\n .replace(/^```json?\\s*/i, '')\n .replace(/```\\s*$/i, '')\n .trim();\n\n if (cleanedCont.startsWith('{')) {\n try {\n const contParsed = JSON.parse(autoCloseJson(cleanedCont));\n const keys = Object.keys(contParsed);\n if (keys.length === 1 && Array.isArray(contParsed[keys[0]])) {\n cleanedCont = contParsed[keys[0]]\n .map((item: unknown) => JSON.stringify(item))\n .join(',\\n');\n }\n } catch {\n // Continue with original cleaning\n }\n }\n\n if (cleanedCont.startsWith('}') || cleanedCont.startsWith(']')) {\n return trimmedPrev + cleanedCont;\n }\n\n const prevEndsWithValue = /[\\}\\]\\\"\\d]$/.test(trimmedPrev);\n const contStartsWithValue = /^[\\{\\[\\\"]/.test(cleanedCont);\n\n if (prevEndsWithValue && contStartsWithValue) {\n return trimmedPrev + ',\\n' + cleanedCont;\n }\n\n return trimmedPrev + cleanedCont;\n}\n\nexport function salvagePartialResponse<T>(rawResponse: string): T | null {\n console.warn('[Continuation] Attempting to salvage partial response');\n\n try {\n const cleanedResponse = extractJsonFromText(rawResponse) || rawResponse;\n const closed = autoCloseJson(cleanedResponse);\n const parsed = JSON.parse(closed) as T;\n console.log('[Continuation] Successfully salvaged partial response');\n return parsed;\n } catch (error) {\n console.error('[Continuation] Could not salvage response:', error);\n }\n\n return null;\n}\n\n// ============================================================================\n// Main Function\n// ============================================================================\n\nexport async function callWithContinuation<T>(\n options: ContinuationOptions<T>,\n): Promise<ContinuationResult<T>> {\n const {\n client,\n systemPrompt,\n userPrompt,\n schema,\n maxTokens = DEFAULT_MAX_TOKENS,\n maxContinuations = DEFAULT_MAX_CONTINUATIONS,\n buildContinuationPrompt,\n continuationSystemPrompt = DEFAULT_CONTINUATION_SYSTEM_PROMPT,\n } = options;\n\n let rawResponse = '';\n let continuationCount = 0;\n const warnings: string[] = [];\n let wasSalvaged = false;\n\n console.log('[Continuation] Starting LLM call with continuation support');\n console.log(\n `[Continuation] Max tokens: ${maxTokens}, Max continuations: ${maxContinuations}`,\n );\n\n try {\n const response = await client.callRawWithMetadata({\n systemPrompt,\n userPrompt,\n maxTokens,\n });\n\n rawResponse = extractJsonFromText(response.raw) || response.raw;\n\n console.log(\n `[Continuation] Initial response: ${rawResponse.length} chars, finish_reason: ${response.finishReason}`,\n );\n\n let truncation = detectTruncation(rawResponse, response.finishReason);\n\n while (truncation.isTruncated && continuationCount < maxContinuations) {\n continuationCount++;\n const warningMsg = `Response truncated (${truncation.reason}), continuing (attempt ${continuationCount}/${maxContinuations})`;\n console.log(`[Continuation] ${warningMsg}`);\n warnings.push(warningMsg);\n\n const contPrompt = buildContinuationPrompt(\n rawResponse,\n continuationCount,\n );\n\n const contResponse = await client.callRawWithMetadata({\n systemPrompt: continuationSystemPrompt,\n userPrompt: contPrompt,\n maxTokens,\n });\n\n console.log(\n `[Continuation] Continuation response: ${contResponse.raw.length} chars, finish_reason: ${contResponse.finishReason}`,\n );\n\n const cleanedContResponse =\n extractJsonFromText(contResponse.raw) || contResponse.raw;\n rawResponse = mergeResponses(rawResponse, cleanedContResponse);\n\n truncation = detectTruncation(rawResponse, contResponse.finishReason);\n }\n\n if (\n continuationCount >= maxContinuations &&\n truncation.isTruncated\n ) {\n console.warn(\n `[Continuation] Reached max continuations (${maxContinuations}), attempting to salvage...`,\n );\n warnings.push(\n `Reached max continuations - some content may be incomplete`,\n );\n wasSalvaged = true;\n }\n\n const cleanedResponse =\n extractJsonFromText(rawResponse) || rawResponse;\n let data: T;\n\n try {\n if (isValidJson(cleanedResponse)) {\n data = JSON.parse(cleanedResponse) as T;\n } else {\n const closed = autoCloseJson(cleanedResponse);\n data = JSON.parse(closed) as T;\n if (!wasSalvaged) {\n warnings.push('Response required auto-closing of JSON brackets');\n }\n }\n } catch (parseError) {\n const salvaged = salvagePartialResponse<T>(cleanedResponse);\n if (salvaged) {\n data = salvaged;\n wasSalvaged = true;\n warnings.push('Response was salvaged from partial data');\n } else {\n throw new Error(\n `Failed to parse response after ${continuationCount} continuations: ${parseError}`,\n );\n }\n }\n\n if (schema) {\n try {\n data = schema.parse(data);\n } catch (validationError) {\n console.warn(\n '[Continuation] Schema validation failed:',\n validationError,\n );\n warnings.push(`Schema validation issue: ${validationError}`);\n }\n }\n\n console.log(\n `[Continuation] Complete. Continuations: ${continuationCount}, Warnings: ${warnings.length}`,\n );\n\n return {\n data,\n raw: rawResponse,\n continuationCount,\n warnings,\n wasSalvaged,\n };\n } catch (error) {\n console.error('[Continuation] Error during LLM call:', error);\n throw error;\n }\n}\n\nexport function buildGenericContinuationPrompt(\n context: string,\n partialResponse: string,\n attempt: number,\n maxAttempts: number = DEFAULT_MAX_CONTINUATIONS,\n): string {\n return `## CONTINUATION REQUEST (Attempt ${attempt}/${maxAttempts})\n\nYour previous response was truncated. Continue generating from where you left off.\n\n### ORIGINAL CONTEXT\n${context}\n\n### WHAT YOU GENERATED SO FAR\n\\`\\`\\`json\n${partialResponse}\n\\`\\`\\`\n\n### INSTRUCTIONS\n1. Continue from EXACTLY where the response was cut off\n2. Do NOT repeat any content already generated\n3. Complete the JSON structure properly\n4. Do NOT wrap your response in markdown code blocks\n\nContinue generating now:`;\n}\n"],"mappings":"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","names":["bracketInfo"]}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* JSON Parser Utilities
|
|
5
|
+
*
|
|
6
|
+
* Robust JSON parsing for LLM responses that may contain:
|
|
7
|
+
* - Markdown code blocks
|
|
8
|
+
* - Extra text before/after JSON
|
|
9
|
+
* - Minor formatting issues
|
|
10
|
+
*
|
|
11
|
+
* @packageDocumentation
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Extract JSON from LLM response text.
|
|
16
|
+
*
|
|
17
|
+
* Handles markdown code blocks, raw JSON objects/arrays, and primitive values.
|
|
18
|
+
*/
|
|
19
|
+
declare function extractJsonFromText(text: string): string | null;
|
|
20
|
+
/**
|
|
21
|
+
* Parse JSON from LLM response with optional Zod schema validation.
|
|
22
|
+
*/
|
|
23
|
+
declare function parseJsonResponse<T>(response: string, schema?: z.ZodSchema<T>): T;
|
|
24
|
+
/**
|
|
25
|
+
* Safely parse JSON without throwing.
|
|
26
|
+
*/
|
|
27
|
+
declare function safeParseJson<T>(response: string, schema?: z.ZodSchema<T>): {
|
|
28
|
+
success: true;
|
|
29
|
+
data: T;
|
|
30
|
+
} | {
|
|
31
|
+
success: false;
|
|
32
|
+
error: Error;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Check if a string is valid JSON.
|
|
36
|
+
*/
|
|
37
|
+
declare function isValidJson(str: string): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Attempt to auto-close unclosed JSON brackets.
|
|
40
|
+
*/
|
|
41
|
+
declare function autoCloseJson(json: string): string;
|
|
42
|
+
|
|
43
|
+
export { autoCloseJson, extractJsonFromText, isValidJson, parseJsonResponse, safeParseJson };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import {
|
|
2
|
+
autoCloseJson,
|
|
3
|
+
extractJsonFromText,
|
|
4
|
+
isValidJson,
|
|
5
|
+
parseJsonResponse,
|
|
6
|
+
safeParseJson
|
|
7
|
+
} from "./chunk-WM7QVK2Z.js";
|
|
8
|
+
export {
|
|
9
|
+
autoCloseJson,
|
|
10
|
+
extractJsonFromText,
|
|
11
|
+
isValidJson,
|
|
12
|
+
parseJsonResponse,
|
|
13
|
+
safeParseJson
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=json-parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token Tracker for LLM Usage
|
|
3
|
+
*
|
|
4
|
+
* Tracks token usage across multiple LLM calls for:
|
|
5
|
+
* - Cost estimation
|
|
6
|
+
* - Usage monitoring
|
|
7
|
+
* - Quota management
|
|
8
|
+
*
|
|
9
|
+
* @packageDocumentation
|
|
10
|
+
*/
|
|
11
|
+
interface TokenUsage {
|
|
12
|
+
promptTokens: number;
|
|
13
|
+
completionTokens: number;
|
|
14
|
+
totalTokens: number;
|
|
15
|
+
callCount: number;
|
|
16
|
+
}
|
|
17
|
+
declare class TokenTracker {
|
|
18
|
+
private model;
|
|
19
|
+
private usage;
|
|
20
|
+
constructor(model?: string);
|
|
21
|
+
addUsage(promptTokens: number, completionTokens: number): void;
|
|
22
|
+
getSummary(): TokenUsage;
|
|
23
|
+
getEstimatedCost(): number;
|
|
24
|
+
getFormattedCost(): string;
|
|
25
|
+
getReport(): string;
|
|
26
|
+
reset(): void;
|
|
27
|
+
setModel(model: string): void;
|
|
28
|
+
}
|
|
29
|
+
declare function getGlobalTokenTracker(model?: string): TokenTracker;
|
|
30
|
+
declare function resetGlobalTokenTracker(): void;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Rate Limiter for LLM API Calls
|
|
34
|
+
*
|
|
35
|
+
* Implements token bucket algorithm with:
|
|
36
|
+
* - Configurable requests per minute/second
|
|
37
|
+
* - Automatic backoff on 429 errors
|
|
38
|
+
* - Queue for pending requests
|
|
39
|
+
*
|
|
40
|
+
* @packageDocumentation
|
|
41
|
+
*/
|
|
42
|
+
interface RateLimiterOptions {
|
|
43
|
+
/** Maximum requests per minute (default: 60) */
|
|
44
|
+
requestsPerMinute?: number;
|
|
45
|
+
/** Maximum requests per second (default: 3) */
|
|
46
|
+
requestsPerSecond?: number;
|
|
47
|
+
/** Maximum concurrent requests (default: 5) */
|
|
48
|
+
maxConcurrent?: number;
|
|
49
|
+
/** Base delay for exponential backoff in ms (default: 1000) */
|
|
50
|
+
baseBackoffMs?: number;
|
|
51
|
+
/** Maximum backoff delay in ms (default: 60000) */
|
|
52
|
+
maxBackoffMs?: number;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Rate limiter for LLM API calls using token bucket algorithm.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```typescript
|
|
59
|
+
* const limiter = new RateLimiter({ requestsPerMinute: 30 });
|
|
60
|
+
* const result = await limiter.execute(() => llm.invoke(messages));
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
declare class RateLimiter {
|
|
64
|
+
private requestsPerMinute;
|
|
65
|
+
private requestsPerSecond;
|
|
66
|
+
private maxConcurrent;
|
|
67
|
+
private baseBackoffMs;
|
|
68
|
+
private maxBackoffMs;
|
|
69
|
+
private minuteTokens;
|
|
70
|
+
private secondTokens;
|
|
71
|
+
private activeRequests;
|
|
72
|
+
private queue;
|
|
73
|
+
private lastMinuteReset;
|
|
74
|
+
private lastSecondReset;
|
|
75
|
+
private processing;
|
|
76
|
+
private currentBackoffMs;
|
|
77
|
+
constructor(options?: RateLimiterOptions);
|
|
78
|
+
execute<T>(fn: () => Promise<T>, _maxRetries?: number): Promise<T>;
|
|
79
|
+
getStatus(): {
|
|
80
|
+
queueLength: number;
|
|
81
|
+
activeRequests: number;
|
|
82
|
+
minuteTokens: number;
|
|
83
|
+
secondTokens: number;
|
|
84
|
+
backoffMs: number;
|
|
85
|
+
};
|
|
86
|
+
reset(): void;
|
|
87
|
+
private processQueue;
|
|
88
|
+
private refillTokens;
|
|
89
|
+
private canMakeRequest;
|
|
90
|
+
private consumeTokens;
|
|
91
|
+
private getWaitTime;
|
|
92
|
+
private isRateLimitError;
|
|
93
|
+
private sleep;
|
|
94
|
+
}
|
|
95
|
+
declare function getGlobalRateLimiter(options?: RateLimiterOptions): RateLimiter;
|
|
96
|
+
declare function resetGlobalRateLimiter(): void;
|
|
97
|
+
|
|
98
|
+
export { RateLimiter as R, TokenTracker as T, type RateLimiterOptions as a, type TokenUsage as b, getGlobalTokenTracker as c, resetGlobalTokenTracker as d, getGlobalRateLimiter as g, resetGlobalRateLimiter as r };
|