@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/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 };