@jsonstudio/llms 0.6.938 → 0.6.1164

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.
Files changed (131) hide show
  1. package/dist/conversion/hub/operation-table/operation-table-runner.d.ts +18 -0
  2. package/dist/conversion/hub/operation-table/operation-table-runner.js +158 -0
  3. package/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper.d.ts +8 -0
  4. package/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper.js +303 -0
  5. package/dist/conversion/hub/operation-table/semantic-mappers/chat-mapper.d.ts +8 -0
  6. package/dist/conversion/hub/operation-table/semantic-mappers/chat-mapper.js +413 -0
  7. package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.d.ts +7 -0
  8. package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.js +841 -0
  9. package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.d.ts +21 -0
  10. package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.js +535 -0
  11. package/dist/conversion/hub/ops/operations.d.ts +19 -0
  12. package/dist/conversion/hub/ops/operations.js +126 -0
  13. package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +9 -0
  14. package/dist/conversion/hub/pipeline/hub-pipeline.js +533 -24
  15. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.js +6 -0
  16. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.js +6 -3
  17. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.js +11 -0
  18. package/dist/conversion/hub/policy/policy-engine.js +41 -9
  19. package/dist/conversion/hub/policy/protocol-spec.d.ts +25 -0
  20. package/dist/conversion/hub/policy/protocol-spec.js +73 -23
  21. package/dist/conversion/hub/process/chat-process.js +252 -41
  22. package/dist/conversion/hub/response/provider-response.js +175 -2
  23. package/dist/conversion/hub/response/response-runtime.js +1 -1
  24. package/dist/conversion/hub/semantic-mappers/anthropic-mapper.d.ts +1 -8
  25. package/dist/conversion/hub/semantic-mappers/anthropic-mapper.js +1 -365
  26. package/dist/conversion/hub/semantic-mappers/chat-mapper.d.ts +1 -8
  27. package/dist/conversion/hub/semantic-mappers/chat-mapper.js +1 -436
  28. package/dist/conversion/hub/semantic-mappers/gemini-mapper.d.ts +1 -7
  29. package/dist/conversion/hub/semantic-mappers/gemini-mapper.js +1 -894
  30. package/dist/conversion/hub/semantic-mappers/responses-mapper.d.ts +1 -21
  31. package/dist/conversion/hub/semantic-mappers/responses-mapper.js +1 -593
  32. package/dist/conversion/hub/tool-surface/tool-surface-engine.d.ts +18 -0
  33. package/dist/conversion/hub/tool-surface/tool-surface-engine.js +571 -0
  34. package/dist/conversion/responses/responses-openai-bridge.js +14 -2
  35. package/dist/conversion/shared/bridge-message-utils.js +2 -8
  36. package/dist/conversion/shared/bridge-policies.js +5 -105
  37. package/dist/conversion/shared/gemini-tool-utils.js +121 -4
  38. package/dist/conversion/shared/protocol-field-allowlists.d.ts +7 -0
  39. package/dist/conversion/shared/protocol-field-allowlists.js +145 -0
  40. package/dist/conversion/shared/reasoning-tool-normalizer.js +4 -2
  41. package/dist/conversion/shared/snapshot-hooks.js +166 -3
  42. package/dist/conversion/shared/text-markup-normalizer.d.ts +2 -0
  43. package/dist/conversion/shared/text-markup-normalizer.js +345 -9
  44. package/dist/conversion/shared/thought-signature-validator.d.ts +52 -0
  45. package/dist/conversion/shared/thought-signature-validator.js +170 -0
  46. package/dist/conversion/shared/tool-argument-repairer.d.ts +39 -0
  47. package/dist/conversion/shared/tool-argument-repairer.js +56 -0
  48. package/dist/conversion/shared/tool-call-id-manager.d.ts +113 -0
  49. package/dist/conversion/shared/tool-call-id-manager.js +231 -0
  50. package/dist/conversion/shared/tool-canonicalizer.js +2 -11
  51. package/dist/router/virtual-router/bootstrap.js +54 -5
  52. package/dist/router/virtual-router/engine-selection.js +132 -42
  53. package/dist/router/virtual-router/engine.d.ts +3 -0
  54. package/dist/router/virtual-router/engine.js +142 -33
  55. package/dist/router/virtual-router/health-weighted.d.ts +25 -0
  56. package/dist/router/virtual-router/health-weighted.js +63 -0
  57. package/dist/router/virtual-router/load-balancer.d.ts +2 -0
  58. package/dist/router/virtual-router/load-balancer.js +45 -16
  59. package/dist/router/virtual-router/routing-instructions.js +17 -1
  60. package/dist/router/virtual-router/sticky-session-store.js +136 -24
  61. package/dist/router/virtual-router/stop-message-file-resolver.d.ts +1 -0
  62. package/dist/router/virtual-router/stop-message-file-resolver.js +74 -0
  63. package/dist/router/virtual-router/stop-message-state-sync.d.ts +15 -0
  64. package/dist/router/virtual-router/stop-message-state-sync.js +57 -0
  65. package/dist/router/virtual-router/types.d.ts +70 -0
  66. package/dist/servertool/clock/config.d.ts +7 -0
  67. package/dist/servertool/clock/config.js +27 -0
  68. package/dist/servertool/clock/daemon.d.ts +3 -0
  69. package/dist/servertool/clock/daemon.js +79 -0
  70. package/dist/servertool/clock/io.d.ts +2 -0
  71. package/dist/servertool/clock/io.js +13 -0
  72. package/dist/servertool/clock/paths.d.ts +4 -0
  73. package/dist/servertool/clock/paths.js +25 -0
  74. package/dist/servertool/clock/session-store.d.ts +3 -0
  75. package/dist/servertool/clock/session-store.js +56 -0
  76. package/dist/servertool/clock/state.d.ts +5 -0
  77. package/dist/servertool/clock/state.js +62 -0
  78. package/dist/servertool/clock/task-store.d.ts +5 -0
  79. package/dist/servertool/clock/task-store.js +4 -0
  80. package/dist/servertool/clock/tasks.d.ts +17 -0
  81. package/dist/servertool/clock/tasks.js +221 -0
  82. package/dist/servertool/clock/types.d.ts +36 -0
  83. package/dist/servertool/clock/types.js +1 -0
  84. package/dist/servertool/engine.d.ts +2 -0
  85. package/dist/servertool/engine.js +164 -8
  86. package/dist/servertool/followup-shadow.d.ts +16 -0
  87. package/dist/servertool/followup-shadow.js +145 -0
  88. package/dist/servertool/handlers/apply-patch-guard.js +1 -265
  89. package/dist/servertool/handlers/clock-auto.d.ts +1 -0
  90. package/dist/servertool/handlers/clock-auto.js +160 -0
  91. package/dist/servertool/handlers/clock.d.ts +1 -0
  92. package/dist/servertool/handlers/clock.js +197 -0
  93. package/dist/servertool/handlers/exec-command-guard.js +7 -555
  94. package/dist/servertool/handlers/followup-request-builder.d.ts +15 -7
  95. package/dist/servertool/handlers/followup-request-builder.js +248 -28
  96. package/dist/servertool/handlers/gemini-empty-reply-continue.js +62 -169
  97. package/dist/servertool/handlers/iflow-model-error-retry.js +18 -28
  98. package/dist/servertool/handlers/recursive-detection-guard.d.ts +1 -0
  99. package/dist/servertool/handlers/recursive-detection-guard.js +333 -0
  100. package/dist/servertool/handlers/stop-message-auto.js +47 -175
  101. package/dist/servertool/handlers/vision.d.ts +7 -1
  102. package/dist/servertool/handlers/vision.js +61 -117
  103. package/dist/servertool/handlers/web-search.d.ts +7 -1
  104. package/dist/servertool/handlers/web-search.js +122 -105
  105. package/dist/servertool/reenter-backend.d.ts +23 -0
  106. package/dist/servertool/reenter-backend.js +18 -0
  107. package/dist/servertool/server-side-tools.d.ts +3 -2
  108. package/dist/servertool/server-side-tools.js +64 -10
  109. package/dist/servertool/types.d.ts +92 -3
  110. package/dist/sse/json-to-sse/event-generators/responses.js +3 -21
  111. package/dist/sse/shared/serializers/responses-event-serializer.d.ts +8 -0
  112. package/dist/sse/shared/serializers/responses-event-serializer.js +19 -0
  113. package/dist/sse/shared/writer.js +24 -7
  114. package/dist/tools/apply-patch/execution-capturer.js +3 -1
  115. package/dist/tools/apply-patch/json/parse-loose.d.ts +3 -0
  116. package/dist/tools/apply-patch/json/parse-loose.js +139 -0
  117. package/dist/tools/apply-patch/patch-text/context-diff.d.ts +1 -0
  118. package/dist/tools/apply-patch/patch-text/context-diff.js +173 -0
  119. package/dist/tools/apply-patch/patch-text/git-diff.d.ts +1 -0
  120. package/dist/tools/apply-patch/patch-text/git-diff.js +138 -0
  121. package/dist/tools/apply-patch/patch-text/looks-like-patch.d.ts +1 -0
  122. package/dist/tools/apply-patch/patch-text/looks-like-patch.js +13 -0
  123. package/dist/tools/apply-patch/patch-text/normalize.d.ts +3 -0
  124. package/dist/tools/apply-patch/patch-text/normalize.js +262 -0
  125. package/dist/tools/apply-patch/structured/coercion.d.ts +3 -0
  126. package/dist/tools/apply-patch/structured/coercion.js +82 -0
  127. package/dist/tools/apply-patch/validation/shared.d.ts +3 -0
  128. package/dist/tools/apply-patch/validation/shared.js +6 -0
  129. package/dist/tools/apply-patch/validator.d.ts +2 -2
  130. package/dist/tools/apply-patch/validator.js +6 -556
  131. package/package.json +1 -1
@@ -0,0 +1,6 @@
1
+ export const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
2
+ export const asString = (value) => {
3
+ if (typeof value === 'string' && value.trim().length > 0)
4
+ return value;
5
+ return null;
6
+ };
@@ -1,5 +1,5 @@
1
- export declare const looksLikePatch: (text?: string) => boolean;
2
- export declare const normalizeApplyPatchText: (raw: string) => string;
1
+ import { normalizeApplyPatchText, looksLikePatch } from './patch-text/normalize.js';
2
+ export { looksLikePatch, normalizeApplyPatchText };
3
3
  export type ApplyPatchValidationResult = {
4
4
  ok: boolean;
5
5
  reason?: string;
@@ -1,559 +1,9 @@
1
- import { buildStructuredPatch, isStructuredApplyPatchPayload, StructuredApplyPatchError } from './structured.js';
2
- const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
3
- const asString = (value) => {
4
- if (typeof value === 'string' && value.trim().length > 0)
5
- return value;
6
- return null;
7
- };
8
- const stripCodeFences = (text) => {
9
- const trimmed = text.trim();
10
- // Only treat the entire payload as fenced when it *starts* with a code fence.
11
- // Patch bodies (especially added Markdown files) may legitimately contain ``` blocks;
12
- // we must not strip those.
13
- if (!trimmed.startsWith('```'))
14
- return text;
15
- const fenceRe = /^```(?:diff|patch|apply_patch|text|json)?[ \t]*\n([\s\S]*?)\n```/gmi;
16
- const candidates = [];
17
- let match = null;
18
- while ((match = fenceRe.exec(trimmed))) {
19
- if (match[1])
20
- candidates.push(match[1].trim());
21
- }
22
- if (!candidates.length)
23
- return text;
24
- for (const candidate of candidates) {
25
- if (candidate.includes('*** Begin Patch') ||
26
- candidate.includes('*** Update File:') ||
27
- candidate.includes('diff --git')) {
28
- return candidate;
29
- }
30
- }
31
- return candidates[0] ?? text;
32
- };
33
- export const looksLikePatch = (text) => {
34
- if (!text)
35
- return false;
36
- const t = text.trim();
37
- if (!t)
38
- return false;
39
- return (t.includes('*** Begin Patch') ||
40
- t.includes('*** Update File:') ||
41
- t.includes('*** Add File:') ||
42
- t.includes('*** Delete File:') ||
43
- t.includes('diff --git') ||
44
- /^(?:@@|\+\+\+\s|---\s)/m.test(t));
45
- };
46
- const decodeEscapedNewlinesIfNeeded = (value) => {
47
- try {
48
- if (!value)
49
- return value;
50
- if (value.includes('\n'))
51
- return value;
52
- if (!value.includes('\\n'))
53
- return value;
54
- return value.replace(/\\n/g, '\n');
55
- }
56
- catch {
57
- return value;
58
- }
59
- };
60
- const stripConflictMarkers = (text) => {
61
- try {
62
- const lines = text.replace(/\r\n/g, '\n').split('\n');
63
- const out = [];
64
- for (const line of lines) {
65
- if (line.startsWith('<<<<<<<') || line.startsWith('=======') || line.startsWith('>>>>>>>')) {
66
- continue;
67
- }
68
- out.push(line);
69
- }
70
- return out.join('\n');
71
- }
72
- catch {
73
- return text;
74
- }
75
- };
76
- const convertGitDiffToApplyPatch = (text) => {
77
- const lines = text.replace(/\r\n/g, '\n').split('\n');
78
- const files = [];
79
- let current = null;
80
- let sawDiff = false;
81
- const flush = () => {
82
- if (!current)
83
- return;
84
- if (current.path && current.kind === 'delete') {
85
- files.push(current);
86
- current = null;
87
- return;
88
- }
89
- // Include rename-only diffs (no hunks) when we have a move target.
90
- if (current.path && (current.lines.length || current.moveTo)) {
91
- files.push(current);
92
- }
93
- current = null;
94
- };
95
- const extractPath = (value) => {
96
- const v = String(value || '').trim();
97
- if (!v)
98
- return '';
99
- const head = v.split('\t')[0] ?? v;
100
- const m = String(head).trim().match(/^(?:a\/|b\/)?(.+)$/);
101
- return (m && m[1] ? m[1] : String(head)).trim();
102
- };
103
- for (const line of lines) {
104
- const diffMatch = line.match(/^diff --git\s+a\/(.+?)\s+b\/(.+)$/);
105
- if (diffMatch) {
106
- sawDiff = true;
107
- flush();
108
- current = {
109
- path: extractPath(diffMatch[2]),
110
- kind: 'update',
111
- lines: [],
112
- oldPath: extractPath(diffMatch[1]),
113
- newPath: extractPath(diffMatch[2]),
114
- };
115
- continue;
116
- }
117
- if (!current)
118
- continue;
119
- if (line.startsWith('GIT binary patch') || line.startsWith('Binary files ')) {
120
- current.binary = true;
121
- continue;
122
- }
123
- const delMatch = line.match(/^deleted file mode\s+/);
124
- const newMatch = line.match(/^new file mode\s+/);
125
- if (delMatch) {
126
- current.kind = 'delete';
127
- continue;
128
- }
129
- if (newMatch) {
130
- current.kind = 'add';
131
- continue;
132
- }
133
- if (line.startsWith('rename from ')) {
134
- const p = extractPath(line.slice('rename from '.length));
135
- if (p)
136
- current.oldPath = p;
137
- continue;
138
- }
139
- if (line.startsWith('rename to ')) {
140
- const p = extractPath(line.slice('rename to '.length));
141
- if (p) {
142
- current.newPath = p;
143
- current.moveTo = p;
144
- }
145
- continue;
146
- }
147
- if (line.startsWith('--- ')) {
148
- const p = extractPath(line.slice(4));
149
- if (p) {
150
- current.oldPath = p;
151
- if (p === '/dev/null')
152
- current.kind = 'add';
153
- }
154
- continue;
155
- }
156
- if (line.startsWith('+++ ')) {
157
- const p = extractPath(line.slice(4));
158
- if (p) {
159
- current.newPath = p;
160
- if (p === '/dev/null')
161
- current.kind = 'delete';
162
- }
163
- continue;
164
- }
165
- if (line.startsWith('index ') || line.startsWith('similarity index ') || line.startsWith('dissimilarity index ')) {
166
- continue;
167
- }
168
- if (line.startsWith('@@')) {
169
- current.lines.push(line);
170
- continue;
171
- }
172
- if (line.startsWith('+') || line.startsWith('-') || line.startsWith(' ')) {
173
- current.lines.push(line);
174
- continue;
175
- }
176
- }
177
- flush();
178
- if (!sawDiff || files.length === 0)
179
- return null;
180
- if (files.some((f) => f.binary))
181
- return null;
182
- const out = ['*** Begin Patch'];
183
- for (const file of files) {
184
- const oldPath = typeof file.oldPath === 'string' ? file.oldPath : '';
185
- const newPath = typeof file.newPath === 'string' ? file.newPath : '';
186
- const resolvedPath = file.kind === 'add'
187
- ? (newPath && newPath !== '/dev/null' ? newPath : file.path)
188
- : file.kind === 'delete'
189
- ? (oldPath && oldPath !== '/dev/null' ? oldPath : file.path)
190
- : (file.moveTo && oldPath && oldPath !== '/dev/null'
191
- ? oldPath
192
- : (newPath && newPath !== '/dev/null' ? newPath : file.path));
193
- if (file.kind === 'delete') {
194
- out.push(`*** Delete File: ${resolvedPath}`);
195
- continue;
196
- }
197
- if (file.kind === 'add') {
198
- out.push(`*** Add File: ${resolvedPath}`);
199
- for (const l of file.lines) {
200
- if (l.startsWith('+'))
201
- out.push(l);
202
- }
203
- continue;
204
- }
205
- out.push(`*** Update File: ${resolvedPath}`);
206
- if (file.moveTo && file.moveTo !== resolvedPath) {
207
- out.push(`*** Move to: ${file.moveTo}`);
208
- }
209
- out.push(...file.lines);
210
- }
211
- out.push('*** End Patch');
212
- return out.join('\n');
213
- };
214
- export const normalizeApplyPatchText = (raw) => {
215
- if (!raw)
216
- return raw;
217
- let text = raw.replace(/\r\n/g, '\n');
218
- text = decodeEscapedNewlinesIfNeeded(text);
219
- text = stripCodeFences(text);
220
- text = text.trim();
221
- if (!text)
222
- return raw;
223
- text = stripConflictMarkers(text);
224
- if (!text.includes('*** Begin Patch') && text.includes('diff --git')) {
225
- const converted = convertGitDiffToApplyPatch(text);
226
- if (converted)
227
- text = converted;
228
- }
229
- else if (!text.includes('*** Begin Patch') && !text.includes('diff --git')) {
230
- const minusMatch = text.match(/^---\s+(.*)$/m);
231
- const plusMatch = text.match(/^\+\+\+\s+(.*)$/m);
232
- if (minusMatch && plusMatch) {
233
- const rawMinus = (minusMatch[1] || '').split('\t')[0] || '';
234
- const rawPlus = (plusMatch[1] || '').split('\t')[0] || '';
235
- const normalizeHeaderPath = (value) => {
236
- const v = String(value || '').trim();
237
- if (!v)
238
- return '';
239
- const m = v.match(/^(?:a\/|b\/)?(.+)$/);
240
- return (m && m[1] ? m[1] : v).trim();
241
- };
242
- const minusPath = normalizeHeaderPath(rawMinus);
243
- const plusPath = normalizeHeaderPath(rawPlus);
244
- const filePath = plusPath === '/dev/null' ? minusPath : plusPath;
245
- if (filePath) {
246
- const synthetic = `diff --git a/${filePath} b/${filePath}\n${text}`;
247
- const converted = convertGitDiffToApplyPatch(synthetic);
248
- if (converted)
249
- text = converted;
250
- }
251
- }
252
- }
253
- if (text.includes('*** Add File:')) {
254
- text = text.replace(/\*\*\* Create File:/g, '*** Add File:');
255
- }
256
- let hasBegin = text.includes('*** Begin Patch');
257
- const hasEnd = text.includes('*** End Patch');
258
- if (hasBegin && !hasEnd) {
259
- text = `${text}\n*** End Patch`;
260
- }
261
- if (!hasBegin && /^\*\*\* (Add|Update|Delete) File:/m.test(text)) {
262
- text = `*** Begin Patch\n${text}\n*** End Patch`;
263
- hasBegin = true;
264
- }
265
- if (!text.includes('*** Begin Patch')) {
266
- return text;
267
- }
268
- const beginIndex = text.indexOf('*** Begin Patch');
269
- if (beginIndex > 0) {
270
- text = text.slice(beginIndex);
271
- }
272
- const endMarker = '*** End Patch';
273
- const firstEndIndex = text.indexOf(endMarker);
274
- const concatSignatures = [
275
- `${endMarker}","input":"*** Begin Patch`,
276
- `${endMarker}","patch":"*** Begin Patch`,
277
- `${endMarker}\\",\\"input\\":\\"*** Begin Patch`,
278
- `${endMarker}\\",\\"patch\\":\\"*** Begin Patch`
279
- ];
280
- const hasConcatenationSignal = concatSignatures.some((sig) => text.includes(sig));
281
- if (hasConcatenationSignal && firstEndIndex >= 0) {
282
- text = text.slice(0, firstEndIndex + endMarker.length);
283
- }
284
- else {
285
- const lastEndIndex = text.lastIndexOf(endMarker);
286
- if (lastEndIndex >= 0) {
287
- const afterEnd = text.slice(lastEndIndex + endMarker.length);
288
- if (afterEnd.trim().length > 0) {
289
- text = text.slice(0, lastEndIndex + endMarker.length);
290
- }
291
- }
292
- }
293
- // Fix missing prefix lines in Update File sections: treat as context (" ").
294
- const lines = text.split('\n');
295
- const output = [];
296
- let inUpdateSection = false;
297
- let afterUpdateHeader = false;
298
- for (const line of lines) {
299
- if (line.startsWith('*** Begin Patch')) {
300
- output.push(line);
301
- inUpdateSection = false;
302
- afterUpdateHeader = false;
303
- continue;
304
- }
305
- if (line.startsWith('*** End Patch')) {
306
- output.push(line);
307
- inUpdateSection = false;
308
- afterUpdateHeader = false;
309
- continue;
310
- }
311
- if (line.startsWith('*** Update File:')) {
312
- output.push(line);
313
- inUpdateSection = true;
314
- afterUpdateHeader = true;
315
- continue;
316
- }
317
- if (line.startsWith('*** Add File:') || line.startsWith('*** Delete File:')) {
318
- output.push(line);
319
- inUpdateSection = false;
320
- afterUpdateHeader = false;
321
- continue;
322
- }
323
- if (inUpdateSection) {
324
- if (afterUpdateHeader && line.trim() === '') {
325
- continue;
326
- }
327
- afterUpdateHeader = false;
328
- if (line.startsWith('@@') || line.startsWith('+') || line.startsWith('-') || line.startsWith(' ')) {
329
- output.push(line);
330
- }
331
- else {
332
- output.push(` ${line}`);
333
- }
334
- continue;
335
- }
336
- output.push(line);
337
- }
338
- return output.join('\n');
339
- };
340
- const buildSingleChangePayload = (record) => {
341
- const kindRaw = asString(record.kind);
342
- if (!kindRaw)
343
- return undefined;
344
- const change = {
345
- kind: kindRaw.toLowerCase(),
346
- lines: record.lines ?? record.text ?? record.body,
347
- target: asString(record.target) ?? undefined,
348
- anchor: asString(record.anchor) ?? undefined
349
- };
350
- if (typeof record.use_anchor_indent === 'boolean') {
351
- change.use_anchor_indent = record.use_anchor_indent;
352
- }
353
- const changeFile = asString(record.file);
354
- if (changeFile) {
355
- change.file = changeFile;
356
- }
357
- return { file: changeFile, changes: [change] };
358
- };
359
- const tryParseJson = (value) => {
360
- if (typeof value !== 'string')
361
- return undefined;
362
- const trimmed = value.trim();
363
- if (!trimmed)
364
- return undefined;
365
- if (!(trimmed.startsWith('{') || trimmed.startsWith('[')))
366
- return undefined;
367
- try {
368
- return JSON.parse(trimmed);
369
- }
370
- catch {
371
- return undefined;
372
- }
373
- };
374
- const escapeUnescapedQuotesInJsonStrings = (input) => {
375
- // Best-effort: when JSON is almost valid but contains unescaped `"` inside string values
376
- // (e.g. JSX snippets like className="..."), escape quotes that are not followed by a
377
- // valid JSON token delimiter. Deterministic; does not attempt to fix structural issues.
378
- let out = '';
379
- let inString = false;
380
- let escaped = false;
381
- for (let i = 0; i < input.length; i += 1) {
382
- const ch = input[i] ?? '';
383
- if (!inString) {
384
- if (ch === '"') {
385
- inString = true;
386
- escaped = false;
387
- }
388
- out += ch;
389
- continue;
390
- }
391
- if (escaped) {
392
- out += ch;
393
- escaped = false;
394
- continue;
395
- }
396
- if (ch === '\\') {
397
- out += ch;
398
- escaped = true;
399
- continue;
400
- }
401
- if (ch === '"') {
402
- let j = i + 1;
403
- while (j < input.length && /\s/.test(input[j] ?? ''))
404
- j += 1;
405
- const next = j < input.length ? input[j] : '';
406
- if (next === '' || next === ':' || next === ',' || next === '}' || next === ']') {
407
- inString = false;
408
- out += ch;
409
- }
410
- else {
411
- out += '\\"';
412
- }
413
- continue;
414
- }
415
- out += ch;
416
- }
417
- return out;
418
- };
419
- const balanceJsonContainers = (input) => {
420
- // Best-effort bracket/brace balancing for JSON-like strings.
421
- // Only operates outside string literals. When encountering a closing token that doesn't
422
- // match the current stack top, inserts the missing closer(s) to recover.
423
- let out = '';
424
- let inString = false;
425
- let escaped = false;
426
- const stack = [];
427
- const closeFor = (open) => (open === '{' ? '}' : ']');
428
- for (let i = 0; i < input.length; i += 1) {
429
- const ch = input[i] ?? '';
430
- if (inString) {
431
- out += ch;
432
- if (escaped) {
433
- escaped = false;
434
- continue;
435
- }
436
- if (ch === '\\') {
437
- escaped = true;
438
- continue;
439
- }
440
- if (ch === '"') {
441
- inString = false;
442
- }
443
- continue;
444
- }
445
- if (ch === '"') {
446
- inString = true;
447
- out += ch;
448
- continue;
449
- }
450
- if (ch === '{' || ch === '[') {
451
- stack.push(ch);
452
- out += ch;
453
- continue;
454
- }
455
- if (ch === '}' || ch === ']') {
456
- const expectedOpen = ch === '}' ? '{' : '[';
457
- while (stack.length && stack[stack.length - 1] !== expectedOpen) {
458
- const open = stack.pop();
459
- out += closeFor(open);
460
- }
461
- if (stack.length && stack[stack.length - 1] === expectedOpen) {
462
- stack.pop();
463
- }
464
- out += ch;
465
- continue;
466
- }
467
- out += ch;
468
- }
469
- while (stack.length) {
470
- const open = stack.pop();
471
- out += closeFor(open);
472
- }
473
- return out;
474
- };
475
- const tryParseJsonLoose = (value) => {
476
- const parsed = tryParseJson(value);
477
- if (parsed !== undefined)
478
- return parsed;
479
- if (typeof value !== 'string')
480
- return undefined;
481
- const trimmed = value.trim();
482
- if (!trimmed)
483
- return undefined;
484
- if (!(trimmed.startsWith('{') || trimmed.startsWith('[')))
485
- return undefined;
486
- let repaired = escapeUnescapedQuotesInJsonStrings(trimmed);
487
- repaired = balanceJsonContainers(repaired);
488
- if (!repaired || repaired === trimmed)
489
- return undefined;
490
- try {
491
- return JSON.parse(repaired);
492
- }
493
- catch {
494
- return undefined;
495
- }
496
- };
497
- const coerceChangesArray = (value) => {
498
- const parsed = tryParseJson(value);
499
- if (!parsed)
500
- return undefined;
501
- if (Array.isArray(parsed)) {
502
- const items = parsed.filter((entry) => entry && typeof entry === 'object');
503
- if (!items.length)
504
- return undefined;
505
- // Ensure at least one entry looks like a structured change.
506
- if (!items.some((c) => typeof c.kind === 'string' && String(c.kind).trim()))
507
- return undefined;
508
- return items;
509
- }
510
- if (parsed && typeof parsed === 'object' && Array.isArray(parsed.changes)) {
511
- const items = parsed.changes.filter((entry) => entry && typeof entry === 'object');
512
- if (!items.length)
513
- return undefined;
514
- if (!items.some((c) => typeof c.kind === 'string' && String(c.kind).trim()))
515
- return undefined;
516
- return items;
517
- }
518
- return undefined;
519
- };
520
- const coerceStructuredPayload = (record) => {
521
- if (isStructuredApplyPatchPayload(record)) {
522
- return record;
523
- }
524
- if (Array.isArray(record.changes) && record.changes.length === 0) {
525
- return undefined;
526
- }
527
- // Common shape error: { file, instructions: "[{...},{...}]" } where instructions contains JSON changes.
528
- if (!Array.isArray(record.changes)) {
529
- const changesFromInstructions = coerceChangesArray(record.instructions);
530
- if (changesFromInstructions) {
531
- return {
532
- ...(typeof record.file === 'string' ? { file: record.file } : {}),
533
- changes: changesFromInstructions
534
- };
535
- }
536
- // Another common shape: changes is a JSON string.
537
- const changesFromString = coerceChangesArray(record.changes);
538
- if (changesFromString) {
539
- return {
540
- ...(typeof record.file === 'string' ? { file: record.file } : {}),
541
- changes: changesFromString
542
- };
543
- }
544
- const editsFromString = coerceChangesArray(record.edits ?? record.operations ?? record.ops);
545
- if (editsFromString) {
546
- return {
547
- ...(typeof record.file === 'string' ? { file: record.file } : {}),
548
- changes: editsFromString
549
- };
550
- }
551
- }
552
- const single = buildSingleChangePayload(record);
553
- if (single)
554
- return single;
555
- return undefined;
556
- };
1
+ import { buildStructuredPatch, StructuredApplyPatchError } from './structured.js';
2
+ import { coerceStructuredPayload } from './structured/coercion.js';
3
+ import { tryParseJsonLoose } from './json/parse-loose.js';
4
+ import { normalizeApplyPatchText, looksLikePatch } from './patch-text/normalize.js';
5
+ import { asString, isRecord } from './validation/shared.js';
6
+ export { looksLikePatch, normalizeApplyPatchText };
557
7
  const toJson = (value) => {
558
8
  try {
559
9
  return JSON.stringify(value ?? {});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsonstudio/llms",
3
- "version": "0.6.938",
3
+ "version": "0.6.1164",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",