@ai-matrx/content-ir 0.22.6 → 0.23.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.
@@ -0,0 +1,942 @@
1
+ 'use strict';
2
+
3
+ var JSON5 = require('json5');
4
+ var jsonrepair = require('jsonrepair');
5
+
6
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
7
+
8
+ var JSON5__default = /*#__PURE__*/_interopDefault(JSON5);
9
+
10
+ // source/fence-nesting.ts
11
+ var NESTING_FENCE_LANGUAGES = /* @__PURE__ */ new Set([
12
+ "markdown",
13
+ "md",
14
+ "mdx"
15
+ ]);
16
+ function fenceNestsInnerFences(language) {
17
+ return !!language && NESTING_FENCE_LANGUAGES.has(language.toLowerCase());
18
+ }
19
+ var FENCE_WHITESPACE = " \n\r\f\v";
20
+ function isFenceWhitespace(ch) {
21
+ return ch !== void 0 && ch !== "" && FENCE_WHITESPACE.includes(ch);
22
+ }
23
+ function trimFenceLine(line) {
24
+ let start = 0;
25
+ let end = line.length;
26
+ while (start < end && isFenceWhitespace(line[start])) start++;
27
+ while (end > start && isFenceWhitespace(line[end - 1])) end--;
28
+ return start === 0 && end === line.length ? line : line.slice(start, end);
29
+ }
30
+ var FENCE_OPENER_WHITESPACE = " \n\v\f\r \xA0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF";
31
+ function isOpenerWhitespace(ch) {
32
+ return ch !== void 0 && ch !== "" && FENCE_OPENER_WHITESPACE.includes(ch);
33
+ }
34
+ function parseFenceOpener(line) {
35
+ let start = 0;
36
+ let end = line.length;
37
+ while (start < end && isOpenerWhitespace(line[start])) start++;
38
+ while (end > start && isOpenerWhitespace(line[end - 1])) end--;
39
+ let ticks = 0;
40
+ while (start + ticks < end && line[start + ticks] === "`") ticks++;
41
+ if (ticks < 3) return null;
42
+ if (line.slice(start + ticks, end).includes("`")) return null;
43
+ let infoStart = start + ticks;
44
+ while (infoStart < end && isOpenerWhitespace(line[infoStart])) infoStart++;
45
+ let langEnd = infoStart;
46
+ while (langEnd < end && !isOpenerWhitespace(line[langEnd])) langEnd++;
47
+ return { ticks, lang: line.slice(infoStart, langEnd) };
48
+ }
49
+ function classifyInnerFenceLine(trimmed, openTicks, nests, nestedDepth) {
50
+ let ticks = 0;
51
+ while (ticks < trimmed.length && trimmed[ticks] === "`") ticks++;
52
+ if (ticks < 3) return "content";
53
+ const info = trimFenceLine(trimmed.slice(ticks));
54
+ if (info === "") {
55
+ if (ticks < openTicks) return "content";
56
+ if (nests && nestedDepth > 0) return "close-nested";
57
+ return "close-outer";
58
+ }
59
+ if (nests && ticks >= openTicks && !info.includes("`")) return "open-nested";
60
+ return "content";
61
+ }
62
+
63
+ // source/code-ranges.ts
64
+ function fenceOpenerOf(content) {
65
+ const t = content.trimStart();
66
+ if (t.startsWith("```")) {
67
+ const parsed = parseFenceOpener(t);
68
+ if (!parsed) return null;
69
+ return { char: "`", ticks: parsed.ticks, lang: parsed.lang };
70
+ }
71
+ if (content.length - t.length <= 3 && t.startsWith("~~~")) {
72
+ const open = /^(~{3,})(.*)$/.exec(t);
73
+ if (!open) return null;
74
+ return { char: "~", ticks: open[1].length, lang: open[2].trim().split(/\s/)[0] ?? "" };
75
+ }
76
+ return null;
77
+ }
78
+ var LINE_START = { inString: false, escaped: false };
79
+ var FenceReader = class {
80
+ opener;
81
+ depth = 0;
82
+ /** A nested fence was opened at some point. */
83
+ sawNested = false;
84
+ isJson;
85
+ nesting;
86
+ tildeCloser;
87
+ constructor(opener, options = {}) {
88
+ this.opener = opener;
89
+ this.isJson = opener.char === "`" && opener.lang === "json";
90
+ this.nesting = opener.char === "`" && (options.nesting ?? fenceNestsInnerFences(opener.lang));
91
+ this.tildeCloser = opener.char === "~" ? new RegExp(`^ {0,3}~{${opener.ticks},}[ \\t]*$`) : null;
92
+ }
93
+ /** How many nested fences are open right now. */
94
+ get nestedDepth() {
95
+ return this.depth;
96
+ }
97
+ feed(line) {
98
+ if (this.tildeCloser) return this.tildeCloser.test(line);
99
+ const trimmedLine = trimFenceLine(line);
100
+ const at = line.indexOf("```");
101
+ if (at === -1) return false;
102
+ if (this.isJson && jsonStringState(line, 0, at, LINE_START).inString) return false;
103
+ if (trimmedLine.startsWith("```")) {
104
+ const kind = classifyInnerFenceLine(trimmedLine, this.opener.ticks, this.nesting, this.depth);
105
+ if (kind === "close-outer") return true;
106
+ if (kind === "open-nested") {
107
+ this.depth++;
108
+ this.sawNested = true;
109
+ }
110
+ if (kind === "close-nested") this.depth--;
111
+ } else {
112
+ const closeTicks = backtickRunLength(line, at);
113
+ const after = trimFenceLine(line.slice(at + closeTicks));
114
+ if (closeTicks >= this.opener.ticks && after === "" && this.depth === 0) return true;
115
+ }
116
+ return false;
117
+ }
118
+ };
119
+ function closeFence(src, open, opener) {
120
+ const first = readFence(src, open, new FenceReader(opener));
121
+ if (first.closed || !first.reader.sawNested || first.reader.nestedDepth <= 0) return { line: first.line, closed: first.closed };
122
+ if (src.streaming) return { line: first.line, closed: false };
123
+ const strict = readFence(src, open, new FenceReader(opener, { nesting: false }));
124
+ return { line: strict.line, closed: strict.closed };
125
+ }
126
+ function readFence(src, open, reader) {
127
+ for (let k = open + 1; k < src.count; k++) {
128
+ if (src.openTail?.(k)) continue;
129
+ const line = src.line(k);
130
+ if (line === null) return { line: k - 1, closed: false, reader };
131
+ if (reader.feed(line)) return { line: k, closed: true, reader };
132
+ }
133
+ return { line: src.count - 1, closed: false, reader };
134
+ }
135
+ function jsonStringState(line, from, upTo, state) {
136
+ let { inString, escaped } = state;
137
+ for (let idx = from; idx < upTo; idx++) {
138
+ const ch = line[idx];
139
+ if (escaped) {
140
+ escaped = false;
141
+ continue;
142
+ }
143
+ if (ch === "\\" && inString) {
144
+ escaped = true;
145
+ continue;
146
+ }
147
+ if (ch === '"') inString = !inString;
148
+ }
149
+ return { inString, escaped };
150
+ }
151
+ function backtickRunLength(str, pos) {
152
+ let n = 0;
153
+ while (pos + n < str.length && str[pos + n] === "`") n++;
154
+ return n;
155
+ }
156
+ function quotePrefix(line) {
157
+ let depth = 0;
158
+ let offset = 0;
159
+ for (; ; ) {
160
+ const m = /^ {0,3}> ?/.exec(line.slice(offset));
161
+ if (!m) return { depth, offset };
162
+ depth++;
163
+ offset += m[0].length;
164
+ }
165
+ }
166
+ function stripQuotes(line, depth) {
167
+ let offset = 0;
168
+ for (let d = 0; d < depth; d++) {
169
+ const m = /^ {0,3}> ?/.exec(line.slice(offset));
170
+ if (!m) return null;
171
+ offset += m[0].length;
172
+ }
173
+ return line.slice(offset);
174
+ }
175
+ var LIST_ITEM = /^( *)([-*+]|\d{1,9}[.)])( +|\t|$)/;
176
+ function leadingSpaces(s) {
177
+ let i = 0;
178
+ while (i < s.length && s[i] === " ") i++;
179
+ return i;
180
+ }
181
+ function scan(text) {
182
+ const starts = [];
183
+ const ends = [];
184
+ for (let pos = 0; ; ) {
185
+ const nl = text.indexOf("\n", pos);
186
+ const lineEnd = nl === -1 ? text.length : nl;
187
+ starts.push(pos);
188
+ ends.push(lineEnd > pos && text.charCodeAt(lineEnd - 1) === 13 ? lineEnd - 1 : lineEnd);
189
+ if (nl === -1) break;
190
+ pos = nl + 1;
191
+ }
192
+ const count = starts.length;
193
+ const raw = (k) => text.slice(starts[k], ends[k]);
194
+ const out = { ranges: [], fences: [], paragraphs: [], endsInFence: false, pendingPara: -1 };
195
+ const rawLines = { count, line: raw };
196
+ let segment = 0;
197
+ for (let k = 0; k < count; k++) {
198
+ const opener = fenceOpenerOf(raw(k));
199
+ if (!opener || opener.char !== "`") continue;
200
+ prose(text, starts, ends, raw, segment, k, out);
201
+ const end = closeFence(rawLines, k, opener);
202
+ out.ranges.push({ start: starts[k], end: ends[end.line], kind: "fence" });
203
+ out.fences.push([k, end.line, end.closed]);
204
+ out.pendingPara = -1;
205
+ if (!end.closed && end.line === count - 1) out.endsInFence = true;
206
+ k = end.line;
207
+ segment = k + 1;
208
+ }
209
+ prose(text, starts, ends, raw, segment, count, out);
210
+ return out;
211
+ }
212
+ function prose(text, starts, ends, raw, from, to, out) {
213
+ let para = -1;
214
+ let quoteDepth = 0;
215
+ let lists = [];
216
+ const closePara = (at) => {
217
+ if (para !== -1) out.paragraphs.push([para, at]);
218
+ para = -1;
219
+ };
220
+ for (let k = from; k < to; k++) {
221
+ const line = raw(k);
222
+ const { depth, offset } = quotePrefix(line);
223
+ if (depth !== quoteDepth) {
224
+ quoteDepth = depth;
225
+ lists = [];
226
+ }
227
+ let content = line.slice(offset);
228
+ if (content.trim() === "") {
229
+ closePara(starts[k]);
230
+ if (!(k === starts.length - 1 && line === "")) out.pendingPara = -1;
231
+ continue;
232
+ }
233
+ const indent = leadingSpaces(content);
234
+ while (lists.length && indent < lists[lists.length - 1]) lists.pop();
235
+ const item = LIST_ITEM.exec(content);
236
+ let column = lists.length ? lists[lists.length - 1] : 0;
237
+ if (item) {
238
+ const gap = item[3].length;
239
+ column = item[1].length + item[2].length + (gap >= 1 && gap <= 4 ? gap : 1);
240
+ lists.push(column);
241
+ content = content.slice(Math.min(column, content.length));
242
+ } else {
243
+ content = content.slice(Math.min(indent, column));
244
+ }
245
+ const opener = fenceOpenerOf(content);
246
+ if (!opener) {
247
+ if (para === -1) para = starts[k];
248
+ if (out.pendingPara === -1) out.pendingPara = starts[k];
249
+ continue;
250
+ }
251
+ closePara(starts[k]);
252
+ out.pendingPara = -1;
253
+ const col = column;
254
+ const src = {
255
+ count: to,
256
+ line: (j) => {
257
+ const inner = stripQuotes(raw(j), depth);
258
+ if (inner === null) return null;
259
+ if (opener.char === "~" && col > 0) {
260
+ const ind = leadingSpaces(inner);
261
+ if (inner.trim() !== "" && ind < col) return null;
262
+ return inner.slice(Math.min(ind, col));
263
+ }
264
+ return inner;
265
+ }
266
+ };
267
+ const end = closeFence(src, k, opener);
268
+ out.ranges.push({ start: starts[k], end: ends[end.line], kind: "fence" });
269
+ out.fences.push([k, end.line, end.closed]);
270
+ if (!end.closed && end.line === starts.length - 1) out.endsInFence = true;
271
+ k = end.line;
272
+ }
273
+ closePara(to < starts.length ? starts[to] : text.length);
274
+ }
275
+ var BacktickRuns = class {
276
+ /** Every run, in order: [start, length]. */
277
+ all = [];
278
+ byLength = /* @__PURE__ */ new Map();
279
+ indexOf = /* @__PURE__ */ new Map();
280
+ constructor(text, from, to) {
281
+ for (let k = from; k < to; ) {
282
+ if (text.charCodeAt(k) !== 96) {
283
+ k++;
284
+ continue;
285
+ }
286
+ let len = 1;
287
+ while (k + len < to && text.charCodeAt(k + len) === 96) len++;
288
+ this.indexOf.set(k, this.all.length);
289
+ this.all.push([k, len]);
290
+ const list = this.byLength.get(len);
291
+ if (list) list.push(k);
292
+ else this.byLength.set(len, [k]);
293
+ k += len;
294
+ }
295
+ }
296
+ /** Start of the first run of exactly `n` backticks at or after `after`, or -1. */
297
+ next(n, after) {
298
+ const list = this.byLength.get(n);
299
+ if (!list) return -1;
300
+ let lo = 0;
301
+ let hi = list.length;
302
+ while (lo < hi) {
303
+ const mid = lo + hi >>> 1;
304
+ if (list[mid] < after) lo = mid + 1;
305
+ else hi = mid;
306
+ }
307
+ return list[lo] ?? -1;
308
+ }
309
+ /** Index of the run starting at `start` in `all`. */
310
+ index(start) {
311
+ return this.indexOf.get(start) ?? -1;
312
+ }
313
+ };
314
+ function codeSpans(text, from, to, out) {
315
+ let runs = null;
316
+ for (let i = from; i < to; ) {
317
+ const ch = text.charCodeAt(i);
318
+ if (ch === 92) {
319
+ i += 2;
320
+ continue;
321
+ }
322
+ if (ch !== 96) {
323
+ i++;
324
+ continue;
325
+ }
326
+ const n = backtickRunLength(text, i);
327
+ const len = Math.min(n, to - i);
328
+ runs ??= new BacktickRuns(text, from, to);
329
+ const close = runs.next(len, i + len);
330
+ if (close === -1) {
331
+ i += len;
332
+ continue;
333
+ }
334
+ out.push({ start: i, end: close + len, kind: "span" });
335
+ i = close + len;
336
+ }
337
+ }
338
+ function findCodeRanges(text) {
339
+ const s = scan(text);
340
+ const ranges = [...s.ranges];
341
+ for (const [from, to] of s.paragraphs) codeSpans(text, from, to, ranges);
342
+ return ranges.sort((a, b) => a.start - b.start);
343
+ }
344
+ function fenceParts(raw) {
345
+ const lines = raw.split("\n").map((l) => l.endsWith("\r") ? l.slice(0, -1) : l);
346
+ const { offset } = quotePrefix(lines[0] ?? "");
347
+ const opener = fenceOpenerOf((lines[0] ?? "").slice(offset).replace(/^ *(?:[-*+]|\d{1,9}[.)])(?= |\t|$) ?/, ""));
348
+ if (!opener) return null;
349
+ const rest = lines.slice(1);
350
+ const closed = rest.length > 0 && new FenceReader(opener, { nesting: false }).feed(rest[rest.length - 1]);
351
+ if (!closed) return { opener, body: rest.join("\n"), closed };
352
+ const last = rest[rest.length - 1];
353
+ const before = trimFenceLine(last).startsWith("```") ? "" : last.slice(0, last.indexOf("```"));
354
+ return { opener, body: [...rest.slice(0, -1), ...before.trim() ? [before] : []].join("\n"), closed };
355
+ }
356
+
357
+ // json-extract/json-structural.ts
358
+ function findBalancedEnd(text, startIndex, openChar, closeChar) {
359
+ let depth = 1;
360
+ let inString = false;
361
+ let escapeNext = false;
362
+ for (let i = startIndex + 1; i < text.length; i++) {
363
+ const ch = text[i];
364
+ if (escapeNext) {
365
+ escapeNext = false;
366
+ continue;
367
+ }
368
+ if (ch === "\\") {
369
+ if (inString) escapeNext = true;
370
+ continue;
371
+ }
372
+ if (ch === '"') {
373
+ inString = !inString;
374
+ continue;
375
+ }
376
+ if (inString) continue;
377
+ if (ch === openChar) {
378
+ depth++;
379
+ } else if (ch === closeChar) {
380
+ depth--;
381
+ if (depth === 0) {
382
+ return { endIndex: i, isComplete: true };
383
+ }
384
+ }
385
+ }
386
+ return { endIndex: text.length - 1, isComplete: false };
387
+ }
388
+ function computeClosingSequence(text) {
389
+ const stack = [];
390
+ let inString = false;
391
+ let escapeNext = false;
392
+ for (let i = 0; i < text.length; i++) {
393
+ const ch = text[i];
394
+ if (escapeNext) {
395
+ escapeNext = false;
396
+ continue;
397
+ }
398
+ if (ch === "\\") {
399
+ if (inString) escapeNext = true;
400
+ continue;
401
+ }
402
+ if (ch === '"') {
403
+ inString = !inString;
404
+ continue;
405
+ }
406
+ if (inString) continue;
407
+ if (ch === "{") stack.push("}");
408
+ else if (ch === "[") stack.push("]");
409
+ else if (ch === "}" || ch === "]") stack.pop();
410
+ }
411
+ return stack.reverse().join("");
412
+ }
413
+ function findAllFencedBlocks(text) {
414
+ const blocks = [];
415
+ for (const range of findCodeRanges(text)) {
416
+ if (range.kind !== "fence") continue;
417
+ const parts = fenceParts(text.slice(range.start, range.end));
418
+ const openerEnd = text.indexOf("\n", range.start);
419
+ const contentStart = openerEnd === -1 || openerEnd >= range.end ? range.end : openerEnd + 1;
420
+ blocks.push({
421
+ content: parts?.body ?? text.slice(contentStart, range.end),
422
+ language: (parts?.opener.lang ?? "").toLowerCase(),
423
+ fenceStart: range.start,
424
+ contentStart,
425
+ fenceEnd: range.end,
426
+ isComplete: parts?.closed ?? false
427
+ });
428
+ }
429
+ return blocks;
430
+ }
431
+ function findBareJsonCandidates(text, excludeRanges = []) {
432
+ const candidates = [];
433
+ let inString = false;
434
+ let escapeNext = false;
435
+ const isExcluded = (idx) => excludeRanges.some(([s, e]) => idx >= s && idx < e);
436
+ for (let i = 0; i < text.length; i++) {
437
+ if (isExcluded(i)) continue;
438
+ const ch = text[i];
439
+ if (escapeNext) {
440
+ escapeNext = false;
441
+ continue;
442
+ }
443
+ if (ch === "\\") {
444
+ if (inString) escapeNext = true;
445
+ continue;
446
+ }
447
+ if (ch === '"') {
448
+ inString = !inString;
449
+ continue;
450
+ }
451
+ if (inString) continue;
452
+ if (ch === "{" || ch === "[") {
453
+ const closeChar = ch === "{" ? "}" : "]";
454
+ const result = findBalancedEnd(text, i, ch, closeChar);
455
+ candidates.push({
456
+ startIndex: i,
457
+ endIndex: result.endIndex,
458
+ openChar: ch,
459
+ isComplete: result.isComplete
460
+ });
461
+ if (result.isComplete) {
462
+ i = result.endIndex;
463
+ } else {
464
+ break;
465
+ }
466
+ }
467
+ }
468
+ return candidates;
469
+ }
470
+ function tryParse(text) {
471
+ try {
472
+ return JSON.parse(text);
473
+ } catch {
474
+ return void 0;
475
+ }
476
+ }
477
+ function tryRepairAndParse(text) {
478
+ const direct = tryParse(text);
479
+ if (direct !== void 0) return { value: direct, repaired: false };
480
+ let repaired = text;
481
+ repaired = repaired.replace(/,(\s*[}\]])/g, "$1");
482
+ const result = tryParse(repaired);
483
+ if (result !== void 0) return { value: result, repaired: true };
484
+ repaired = text;
485
+ repaired = repaired.replace(/\bTrue\b/g, "true");
486
+ repaired = repaired.replace(/\bFalse\b/g, "false");
487
+ repaired = repaired.replace(/\bNone\b/g, "null");
488
+ repaired = repaired.replace(/,(\s*[}\]])/g, "$1");
489
+ const result2 = tryParse(repaired);
490
+ if (result2 !== void 0) return { value: result2, repaired: true };
491
+ try {
492
+ return { value: JSON5__default.default.parse(text), repaired: true };
493
+ } catch {
494
+ return void 0;
495
+ }
496
+ }
497
+ function classifyValue(value) {
498
+ if (Array.isArray(value)) return "array";
499
+ if (value !== null && typeof value === "object") return "object";
500
+ return "primitive";
501
+ }
502
+ function extractFromFencedBlocks(_text, blocks, opts) {
503
+ const results = [];
504
+ const jsonBlocks = blocks.filter(
505
+ (b) => b.language === "json" || b.language === ""
506
+ );
507
+ for (const block of jsonBlocks) {
508
+ if (results.length >= opts.maxResults) break;
509
+ const content = block.content.trim();
510
+ if (!content) continue;
511
+ if (block.isComplete) {
512
+ const parsed = opts.repairEnabled ? tryRepairAndParse(content) : (() => {
513
+ const v = tryParse(content);
514
+ return v !== void 0 ? { value: v, repaired: false } : void 0;
515
+ })();
516
+ if (parsed !== void 0) {
517
+ results.push({
518
+ value: parsed.value,
519
+ type: classifyValue(parsed.value),
520
+ source: "fenced",
521
+ startIndex: block.fenceStart,
522
+ endIndex: block.fenceEnd,
523
+ isComplete: true,
524
+ repairApplied: parsed.repaired,
525
+ warnings: parsed.repaired ? ["Repair applied (trailing comma or Python syntax)"] : []
526
+ });
527
+ }
528
+ } else if (opts.isStreaming) {
529
+ const attempted = attemptStreamingClose(content, opts.repairEnabled);
530
+ if (attempted) {
531
+ results.push({
532
+ value: attempted.value,
533
+ type: classifyValue(attempted.value),
534
+ source: "fenced",
535
+ startIndex: block.fenceStart,
536
+ endIndex: block.fenceEnd,
537
+ isComplete: false,
538
+ repairApplied: attempted.repaired,
539
+ warnings: [
540
+ "Incomplete fenced block \u2014 auto-closed for streaming preview"
541
+ ]
542
+ });
543
+ }
544
+ }
545
+ }
546
+ return results;
547
+ }
548
+ function attemptStreamingClose(content, repairEnabled) {
549
+ const candidates = buildStreamingCloseCandidates(content.trim());
550
+ for (const candidate of candidates) {
551
+ if (repairEnabled) {
552
+ const result = tryRepairAndParse(candidate);
553
+ if (result) return { value: result.value, repaired: true };
554
+ }
555
+ const direct = tryParse(candidate);
556
+ if (direct !== void 0) return { value: direct, repaired: true };
557
+ }
558
+ return void 0;
559
+ }
560
+ function buildStreamingCloseCandidates(text) {
561
+ const candidates = [];
562
+ const inString = isInsideString(text);
563
+ if (inString) {
564
+ const closedString = text + '"';
565
+ const closingForString = computeClosingSequence(closedString);
566
+ candidates.push(closedString + closingForString);
567
+ candidates.push(closedString + ": null" + closingForString);
568
+ candidates.push(closedString + ': ""' + closingForString);
569
+ }
570
+ if (!inString && /:\s*$/.test(text)) {
571
+ const closingD = computeClosingSequence(text);
572
+ candidates.push(text + ' ""' + closingD);
573
+ candidates.push(text + " null" + closingD);
574
+ }
575
+ if (!inString && /,\s*$/.test(text)) {
576
+ const truncated = text.replace(/,\s*$/, "");
577
+ const closingE = computeClosingSequence(truncated);
578
+ candidates.push(truncated + closingE);
579
+ }
580
+ const lastClean = findLastCleanBreak(text);
581
+ if (lastClean > 0 && lastClean < text.length) {
582
+ const truncated = text.slice(0, lastClean);
583
+ const closingB = computeClosingSequence(truncated);
584
+ candidates.push(truncated + closingB);
585
+ }
586
+ const closingC = computeClosingSequence(text);
587
+ if (closingC) {
588
+ candidates.push(text + closingC);
589
+ }
590
+ if (!inString && !computeClosingSequence(text)) {
591
+ candidates.push(text);
592
+ }
593
+ return candidates;
594
+ }
595
+ function findLastCleanBreak(text) {
596
+ let inStr = false;
597
+ let escapeNext = false;
598
+ let lastBreak = 0;
599
+ for (let i = 0; i < text.length; i++) {
600
+ const ch = text[i];
601
+ if (escapeNext) {
602
+ escapeNext = false;
603
+ continue;
604
+ }
605
+ if (ch === "\\") {
606
+ if (inStr) escapeNext = true;
607
+ continue;
608
+ }
609
+ if (ch === '"') {
610
+ inStr = !inStr;
611
+ continue;
612
+ }
613
+ if (inStr) continue;
614
+ if (ch === "," || ch === "}" || ch === "]") {
615
+ lastBreak = i + 1;
616
+ }
617
+ }
618
+ return lastBreak;
619
+ }
620
+ function isInsideString(text) {
621
+ let inString = false;
622
+ let escapeNext = false;
623
+ for (let i = 0; i < text.length; i++) {
624
+ const ch = text[i];
625
+ if (escapeNext) {
626
+ escapeNext = false;
627
+ continue;
628
+ }
629
+ if (ch === "\\") {
630
+ if (inString) escapeNext = true;
631
+ continue;
632
+ }
633
+ if (ch === '"') {
634
+ inString = !inString;
635
+ }
636
+ }
637
+ return inString;
638
+ }
639
+ function extractFromBareBlocks(text, fencedBlocks, opts) {
640
+ const results = [];
641
+ const excludeRanges = fencedBlocks.map((b) => [
642
+ b.fenceStart,
643
+ b.fenceEnd
644
+ ]);
645
+ const candidates = findBareJsonCandidates(text, excludeRanges);
646
+ for (const candidate of candidates) {
647
+ if (results.length >= opts.maxResults) break;
648
+ if (candidate.isComplete) {
649
+ const raw2 = text.slice(candidate.startIndex, candidate.endIndex + 1);
650
+ const parsed = opts.repairEnabled ? tryRepairAndParse(raw2) : (() => {
651
+ const v = tryParse(raw2);
652
+ return v !== void 0 ? { value: v, repaired: false } : void 0;
653
+ })();
654
+ if (parsed !== void 0) {
655
+ results.push({
656
+ value: parsed.value,
657
+ type: classifyValue(parsed.value),
658
+ source: "bare-block",
659
+ startIndex: candidate.startIndex,
660
+ endIndex: candidate.endIndex + 1,
661
+ isComplete: true,
662
+ repairApplied: parsed.repaired,
663
+ warnings: parsed.repaired ? ["Repair applied to bare JSON block"] : ["JSON found without code fence markers"]
664
+ });
665
+ }
666
+ continue;
667
+ }
668
+ if (!opts.isStreaming) continue;
669
+ const raw = text.slice(candidate.startIndex, candidate.endIndex + 1);
670
+ const attempted = attemptStreamingClose(raw, opts.repairEnabled);
671
+ if (attempted) {
672
+ results.push({
673
+ value: attempted.value,
674
+ type: classifyValue(attempted.value),
675
+ source: "bare-block",
676
+ startIndex: candidate.startIndex,
677
+ endIndex: candidate.endIndex + 1,
678
+ isComplete: false,
679
+ repairApplied: attempted.repaired,
680
+ warnings: ["Incomplete bare JSON \u2014 auto-closed for streaming preview"]
681
+ });
682
+ }
683
+ }
684
+ return results;
685
+ }
686
+ function extractFromWholeString(text, opts) {
687
+ const trimmed = text.trim();
688
+ if (!trimmed) return [];
689
+ const parsed = opts.repairEnabled ? tryRepairAndParse(trimmed) : (() => {
690
+ const v = tryParse(trimmed);
691
+ return v !== void 0 ? { value: v, repaired: false } : void 0;
692
+ })();
693
+ if (parsed !== void 0) {
694
+ return [
695
+ {
696
+ value: parsed.value,
697
+ type: classifyValue(parsed.value),
698
+ source: "whole-string",
699
+ startIndex: 0,
700
+ endIndex: text.length,
701
+ isComplete: true,
702
+ repairApplied: parsed.repaired,
703
+ warnings: [
704
+ "Entire input parsed as JSON (no fences or structure detected)"
705
+ ]
706
+ }
707
+ ];
708
+ }
709
+ return [];
710
+ }
711
+ function repairIsFaithful(text, value) {
712
+ const first = text[0];
713
+ if (first !== "{" && first !== "[") return false;
714
+ if (first === "{" && !(value !== null && typeof value === "object" && !Array.isArray(value))) return false;
715
+ if (first === "[" && !Array.isArray(value)) return false;
716
+ return text.includes(":") || text.includes('"');
717
+ }
718
+ function dropStrayClosers(text) {
719
+ const stack = [];
720
+ let out = "";
721
+ let inString = false;
722
+ let escapeNext = false;
723
+ for (const ch of text) {
724
+ if (escapeNext) {
725
+ escapeNext = false;
726
+ out += ch;
727
+ continue;
728
+ }
729
+ if (inString) {
730
+ if (ch === "\\") escapeNext = true;
731
+ else if (ch === '"') inString = false;
732
+ out += ch;
733
+ continue;
734
+ }
735
+ if (ch === '"') inString = true;
736
+ else if (ch === "{" || ch === "[") stack.push(ch);
737
+ else if (ch === "}" || ch === "]") {
738
+ const top = stack[stack.length - 1];
739
+ if (top === void 0 || top === "{" !== (ch === "}")) continue;
740
+ stack.pop();
741
+ }
742
+ out += ch;
743
+ }
744
+ return out;
745
+ }
746
+ function extractWholeDocumentRepair(text) {
747
+ const trimmed = text.trim();
748
+ if (trimmed[0] !== "{" && trimmed[0] !== "[") return void 0;
749
+ if (tryRepairAndParse(trimmed) !== void 0) return void 0;
750
+ let value;
751
+ for (const attempt of [trimmed, dropStrayClosers(trimmed)]) {
752
+ try {
753
+ value = JSON.parse(jsonrepair.jsonrepair(attempt));
754
+ break;
755
+ } catch {
756
+ value = void 0;
757
+ }
758
+ }
759
+ if (value === void 0) return void 0;
760
+ if (!repairIsFaithful(trimmed, value)) return void 0;
761
+ return {
762
+ value,
763
+ type: classifyValue(value),
764
+ source: "whole-string",
765
+ startIndex: text.indexOf(trimmed),
766
+ endIndex: text.indexOf(trimmed) + trimmed.length,
767
+ isComplete: true,
768
+ repairApplied: true,
769
+ warnings: [
770
+ "Whole document had malformed syntax; recovered via jsonrepair instead of a fragment"
771
+ ]
772
+ };
773
+ }
774
+ var DEFAULTS = {
775
+ isStreaming: false,
776
+ allowFuzzy: false,
777
+ maxResults: Infinity,
778
+ repairEnabled: true
779
+ };
780
+ function extractAllJson(text, options) {
781
+ if (!text || typeof text !== "string") return [];
782
+ const opts = { ...DEFAULTS, ...options };
783
+ if (!opts.isStreaming && opts.repairEnabled) {
784
+ const whole = extractWholeDocumentRepair(text);
785
+ if (whole) return [whole];
786
+ }
787
+ const allFenced = findAllFencedBlocks(text);
788
+ const fencedResults = extractFromFencedBlocks(text, allFenced, opts);
789
+ if (fencedResults.length >= opts.maxResults) {
790
+ return fencedResults.slice(0, opts.maxResults);
791
+ }
792
+ const remaining = opts.maxResults - fencedResults.length;
793
+ let bareResults = [];
794
+ if (opts.allowFuzzy || fencedResults.length === 0) {
795
+ bareResults = extractFromBareBlocks(text, allFenced, {
796
+ ...opts,
797
+ maxResults: remaining
798
+ });
799
+ }
800
+ const combined = [...fencedResults, ...bareResults];
801
+ if (combined.length >= opts.maxResults) {
802
+ return combined.slice(0, opts.maxResults);
803
+ }
804
+ if (opts.isStreaming) {
805
+ return combined;
806
+ }
807
+ if (opts.allowFuzzy && combined.length === 0) {
808
+ const wholeResults = extractFromWholeString(text, opts);
809
+ return wholeResults.slice(0, opts.maxResults);
810
+ }
811
+ return combined;
812
+ }
813
+ function extractFirstJson(text, options) {
814
+ const results = extractAllJson(text, { ...options, maxResults: 1 });
815
+ return results[0] ?? null;
816
+ }
817
+ function extractFirstObject(text, options) {
818
+ const results = extractAllJson(text, options);
819
+ return results.find((r) => r.type === "object") ?? null;
820
+ }
821
+ function containsJson(text, options) {
822
+ return extractAllJson(text, { ...options, maxResults: 1 }).length > 0;
823
+ }
824
+
825
+ // json-extract/streaming-json-tracker.ts
826
+ var INITIAL_STATE = {
827
+ results: [],
828
+ isAllComplete: true,
829
+ hasOpenFence: false,
830
+ revision: 0,
831
+ textLength: 0
832
+ };
833
+ var StreamingJsonTracker = class {
834
+ text = "";
835
+ state = { ...INITIAL_STATE };
836
+ opts;
837
+ lastResultsHash = "";
838
+ constructor(options) {
839
+ this.opts = {
840
+ maxResults: options?.maxResults ?? Infinity,
841
+ repairEnabled: options?.repairEnabled ?? true,
842
+ fuzzyOnFinalize: options?.fuzzyOnFinalize ?? true
843
+ };
844
+ }
845
+ /**
846
+ * Append a new chunk of text and re-extract. Returns the new state.
847
+ * Only bumps `revision` if the extraction results actually changed.
848
+ */
849
+ append(chunk) {
850
+ this.text += chunk;
851
+ return this.extract(true);
852
+ }
853
+ /**
854
+ * Run a final extraction pass with full strategies (including fuzzy).
855
+ * Call once when the stream is complete.
856
+ */
857
+ finalize() {
858
+ return this.extract(false);
859
+ }
860
+ /** Replace the entire accumulated text (useful if you manage accumulation externally). */
861
+ setFullText(text) {
862
+ this.text = text;
863
+ return this.extract(true);
864
+ }
865
+ /** Get the current state without re-extracting. */
866
+ getState() {
867
+ return this.state;
868
+ }
869
+ /** Get the accumulated text. */
870
+ getText() {
871
+ return this.text;
872
+ }
873
+ /** Reset to initial state. */
874
+ reset() {
875
+ this.text = "";
876
+ this.state = { ...INITIAL_STATE };
877
+ this.lastResultsHash = "";
878
+ }
879
+ // ---------------------------------------------------------------------------
880
+ // Internal
881
+ // ---------------------------------------------------------------------------
882
+ extract(isStreaming) {
883
+ const options = {
884
+ isStreaming,
885
+ allowFuzzy: isStreaming ? false : this.opts.fuzzyOnFinalize,
886
+ maxResults: this.opts.maxResults,
887
+ repairEnabled: this.opts.repairEnabled
888
+ };
889
+ const results = extractAllJson(this.text, options);
890
+ const hash = this.computeHash(results);
891
+ if (hash !== this.lastResultsHash) {
892
+ this.lastResultsHash = hash;
893
+ this.state = {
894
+ results,
895
+ isAllComplete: results.every((r) => r.isComplete),
896
+ hasOpenFence: results.some(
897
+ (r) => r.source === "fenced" && !r.isComplete
898
+ ),
899
+ revision: this.state.revision + 1,
900
+ textLength: this.text.length
901
+ };
902
+ } else {
903
+ this.state = {
904
+ ...this.state,
905
+ textLength: this.text.length
906
+ };
907
+ }
908
+ return this.state;
909
+ }
910
+ /**
911
+ * Cheap hash of extraction results for change detection.
912
+ * We hash on: count, types, completeness, and the stringified values.
913
+ * This avoids deep-equality checks on every chunk.
914
+ */
915
+ computeHash(results) {
916
+ if (results.length === 0) return "empty";
917
+ const parts = [];
918
+ for (const r of results) {
919
+ parts.push(
920
+ `${r.type}:${r.source}:${r.isComplete}:${r.startIndex}-${r.endIndex}`
921
+ );
922
+ try {
923
+ parts.push(JSON.stringify(r.value));
924
+ } catch {
925
+ parts.push("unstringifiable");
926
+ }
927
+ }
928
+ return parts.join("|");
929
+ }
930
+ };
931
+
932
+ exports.StreamingJsonTracker = StreamingJsonTracker;
933
+ exports.computeClosingSequence = computeClosingSequence;
934
+ exports.containsJson = containsJson;
935
+ exports.extractAllJson = extractAllJson;
936
+ exports.extractFirstJson = extractFirstJson;
937
+ exports.extractFirstObject = extractFirstObject;
938
+ exports.findAllFencedBlocks = findAllFencedBlocks;
939
+ exports.findBalancedEnd = findBalancedEnd;
940
+ exports.findBareJsonCandidates = findBareJsonCandidates;
941
+ //# sourceMappingURL=json-extract.cjs.map
942
+ //# sourceMappingURL=json-extract.cjs.map