@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,173 @@
1
+ export const convertContextDiffToApplyPatch = (text) => {
2
+ const lines = text.replace(/\r\n/g, '\n').split('\n');
3
+ const patchLines = ['*** Begin Patch'];
4
+ let i = 0;
5
+ while (i < lines.length) {
6
+ const line = lines[i] ?? '';
7
+ if (!line.startsWith('*** ')) {
8
+ i += 1;
9
+ continue;
10
+ }
11
+ const oldHeader = line.slice(4).trim();
12
+ const newHeaderLine = lines[i + 1] ?? '';
13
+ if (!newHeaderLine.startsWith('--- ')) {
14
+ i += 1;
15
+ continue;
16
+ }
17
+ const newHeader = newHeaderLine.slice(4).trim();
18
+ const filePathRaw = (newHeader && newHeader !== '/dev/null' ? newHeader : oldHeader) || '';
19
+ const filePath = filePathRaw.split('\t')[0]?.trim() || '';
20
+ if (!filePath) {
21
+ i += 2;
22
+ continue;
23
+ }
24
+ i += 2;
25
+ const hunks = [];
26
+ while (i < lines.length) {
27
+ const marker = lines[i] ?? '';
28
+ if (marker.startsWith('*** ') && (lines[i + 1] ?? '').startsWith('--- ')) {
29
+ break; // next file header
30
+ }
31
+ if (marker.startsWith('***************')) {
32
+ i += 1;
33
+ const oldRangeHeader = lines[i] ?? '';
34
+ if (!oldRangeHeader.startsWith('*** ')) {
35
+ continue;
36
+ }
37
+ const oldRangeMatch = oldRangeHeader.match(/^\*\*\*\s+(\d+)(?:,(\d+))?\s+\*{4}\s*$/);
38
+ i += 1;
39
+ const oldLines = [];
40
+ while (i < lines.length && !(lines[i] ?? '').startsWith('--- ')) {
41
+ const l = lines[i] ?? '';
42
+ if (l.startsWith('***************'))
43
+ break;
44
+ if (l.startsWith('*** ') && (lines[i + 1] ?? '').startsWith('--- '))
45
+ break;
46
+ oldLines.push(l);
47
+ i += 1;
48
+ }
49
+ const newRangeHeader = lines[i] ?? '';
50
+ if (!newRangeHeader.startsWith('--- ')) {
51
+ continue;
52
+ }
53
+ const newRangeMatch = newRangeHeader.match(/^---\s+(\d+)(?:,(\d+))?\s+-{4}\s*$/);
54
+ i += 1;
55
+ const newLines = [];
56
+ while (i < lines.length) {
57
+ const l = lines[i] ?? '';
58
+ if (l.startsWith('***************'))
59
+ break;
60
+ if (l.startsWith('*** ') && (lines[i + 1] ?? '').startsWith('--- '))
61
+ break;
62
+ newLines.push(l);
63
+ i += 1;
64
+ }
65
+ const oldStart = oldRangeMatch ? Number.parseInt(oldRangeMatch[1], 10) : undefined;
66
+ const oldEnd = oldRangeMatch && oldRangeMatch[2] ? Number.parseInt(oldRangeMatch[2], 10) : oldStart;
67
+ const newStart = newRangeMatch ? Number.parseInt(newRangeMatch[1], 10) : undefined;
68
+ const newEnd = newRangeMatch && newRangeMatch[2] ? Number.parseInt(newRangeMatch[2], 10) : newStart;
69
+ const oldCount = oldStart && oldEnd && Number.isFinite(oldStart) && Number.isFinite(oldEnd)
70
+ ? Math.max(0, oldEnd - oldStart + 1)
71
+ : undefined;
72
+ const newCount = newStart && newEnd && Number.isFinite(newStart) && Number.isFinite(newEnd)
73
+ ? Math.max(0, newEnd - newStart + 1)
74
+ : undefined;
75
+ hunks.push({
76
+ oldStart,
77
+ oldCount,
78
+ newStart,
79
+ newCount,
80
+ oldLines,
81
+ newLines
82
+ });
83
+ continue;
84
+ }
85
+ i += 1;
86
+ }
87
+ if (!hunks.length) {
88
+ continue;
89
+ }
90
+ patchLines.push(`*** Update File: ${filePath}`);
91
+ const decodeContextLine = (raw) => {
92
+ if (raw === undefined || raw === null)
93
+ return null;
94
+ const lineText = String(raw);
95
+ const lead = lineText[0] ?? '';
96
+ const candidateKinds = {
97
+ ' ': 'context',
98
+ '!': 'delete',
99
+ '-': 'delete',
100
+ '+': 'add'
101
+ };
102
+ const kind = candidateKinds[lead];
103
+ const content = kind
104
+ ? (() => {
105
+ let rest = lineText.slice(1);
106
+ if (rest.startsWith(' '))
107
+ rest = rest.slice(1);
108
+ return rest;
109
+ })()
110
+ : lineText;
111
+ return { kind: kind ?? 'context', text: content };
112
+ };
113
+ for (const hunk of hunks) {
114
+ patchLines.push('@@');
115
+ const oldOps = hunk.oldLines.map(decodeContextLine).filter(Boolean);
116
+ const newOpsRaw = hunk.newLines.map(decodeContextLine).filter(Boolean);
117
+ // In new-half, "!" denotes replacement (add side) rather than delete.
118
+ const newOps = newOpsRaw.map((op) => {
119
+ if (!op)
120
+ return op;
121
+ if (op.kind === 'delete') {
122
+ // Interpret '!' (mapped to delete) as add for new-half.
123
+ return { kind: 'add', text: op.text };
124
+ }
125
+ return op;
126
+ });
127
+ let oi = 0;
128
+ let ni = 0;
129
+ while (oi < oldOps.length || ni < newOps.length) {
130
+ const o = oi < oldOps.length ? oldOps[oi] : null;
131
+ const n = ni < newOps.length ? newOps[ni] : null;
132
+ if (o && n && o.kind === 'context' && n.kind === 'context' && o.text === n.text) {
133
+ patchLines.push(` ${o.text}`);
134
+ oi += 1;
135
+ ni += 1;
136
+ continue;
137
+ }
138
+ if (o && o.kind === 'delete') {
139
+ patchLines.push(`-${o.text}`);
140
+ oi += 1;
141
+ if (n && n.kind === 'add') {
142
+ patchLines.push(`+${n.text}`);
143
+ ni += 1;
144
+ }
145
+ continue;
146
+ }
147
+ if (n && n.kind === 'add') {
148
+ patchLines.push(`+${n.text}`);
149
+ ni += 1;
150
+ continue;
151
+ }
152
+ if (o && o.kind === 'context') {
153
+ patchLines.push(` ${o.text}`);
154
+ oi += 1;
155
+ continue;
156
+ }
157
+ if (n && n.kind === 'context') {
158
+ patchLines.push(` ${n.text}`);
159
+ ni += 1;
160
+ continue;
161
+ }
162
+ // Fallback: advance to avoid infinite loops.
163
+ if (o)
164
+ oi += 1;
165
+ if (n)
166
+ ni += 1;
167
+ }
168
+ }
169
+ }
170
+ patchLines.push('*** End Patch');
171
+ const out = patchLines.join('\n').trim();
172
+ return out.includes('*** Update File:') ? out : null;
173
+ };
@@ -0,0 +1 @@
1
+ export declare const convertGitDiffToApplyPatch: (text: string) => string | null;
@@ -0,0 +1,138 @@
1
+ export const convertGitDiffToApplyPatch = (text) => {
2
+ const lines = text.replace(/\r\n/g, '\n').split('\n');
3
+ const files = [];
4
+ let current = null;
5
+ let sawDiff = false;
6
+ const flush = () => {
7
+ if (!current)
8
+ return;
9
+ if (current.path && current.kind === 'delete') {
10
+ files.push(current);
11
+ current = null;
12
+ return;
13
+ }
14
+ // Include rename-only diffs (no hunks) when we have a move target.
15
+ if (current.path && (current.lines.length || current.moveTo)) {
16
+ files.push(current);
17
+ }
18
+ current = null;
19
+ };
20
+ const extractPath = (value) => {
21
+ const v = String(value || '').trim();
22
+ if (!v)
23
+ return '';
24
+ const head = v.split('\t')[0] ?? v;
25
+ const m = String(head).trim().match(/^(?:a\/|b\/)?(.+)$/);
26
+ return (m && m[1] ? m[1] : String(head)).trim();
27
+ };
28
+ for (const line of lines) {
29
+ const diffMatch = line.match(/^diff --git\s+a\/(.+?)\s+b\/(.+)$/);
30
+ if (diffMatch) {
31
+ sawDiff = true;
32
+ flush();
33
+ current = {
34
+ path: extractPath(diffMatch[2]),
35
+ kind: 'update',
36
+ lines: [],
37
+ oldPath: extractPath(diffMatch[1]),
38
+ newPath: extractPath(diffMatch[2])
39
+ };
40
+ continue;
41
+ }
42
+ if (!current)
43
+ continue;
44
+ if (line.startsWith('GIT binary patch') || line.startsWith('Binary files ')) {
45
+ current.binary = true;
46
+ continue;
47
+ }
48
+ const delMatch = line.match(/^deleted file mode\s+/);
49
+ const newMatch = line.match(/^new file mode\s+/);
50
+ if (delMatch) {
51
+ current.kind = 'delete';
52
+ continue;
53
+ }
54
+ if (newMatch) {
55
+ current.kind = 'add';
56
+ continue;
57
+ }
58
+ if (line.startsWith('rename from ')) {
59
+ const p = extractPath(line.slice('rename from '.length));
60
+ if (p)
61
+ current.oldPath = p;
62
+ continue;
63
+ }
64
+ if (line.startsWith('rename to ')) {
65
+ const p = extractPath(line.slice('rename to '.length));
66
+ if (p) {
67
+ current.newPath = p;
68
+ current.moveTo = p;
69
+ }
70
+ continue;
71
+ }
72
+ if (line.startsWith('--- ')) {
73
+ const p = extractPath(line.slice(4));
74
+ if (p) {
75
+ current.oldPath = p;
76
+ if (p === '/dev/null')
77
+ current.kind = 'add';
78
+ }
79
+ continue;
80
+ }
81
+ if (line.startsWith('+++ ')) {
82
+ const p = extractPath(line.slice(4));
83
+ if (p) {
84
+ current.newPath = p;
85
+ if (p === '/dev/null')
86
+ current.kind = 'delete';
87
+ }
88
+ continue;
89
+ }
90
+ if (line.startsWith('index ') || line.startsWith('similarity index ') || line.startsWith('dissimilarity index ')) {
91
+ continue;
92
+ }
93
+ if (line.startsWith('@@')) {
94
+ current.lines.push(line);
95
+ continue;
96
+ }
97
+ if (line.startsWith('+') || line.startsWith('-') || line.startsWith(' ')) {
98
+ current.lines.push(line);
99
+ continue;
100
+ }
101
+ }
102
+ flush();
103
+ if (!sawDiff || files.length === 0)
104
+ return null;
105
+ if (files.some((f) => f.binary))
106
+ return null;
107
+ const out = ['*** Begin Patch'];
108
+ for (const file of files) {
109
+ const oldPath = typeof file.oldPath === 'string' ? file.oldPath : '';
110
+ const newPath = typeof file.newPath === 'string' ? file.newPath : '';
111
+ const resolvedPath = file.kind === 'add'
112
+ ? (newPath && newPath !== '/dev/null' ? newPath : file.path)
113
+ : file.kind === 'delete'
114
+ ? (oldPath && oldPath !== '/dev/null' ? oldPath : file.path)
115
+ : (file.moveTo && oldPath && oldPath !== '/dev/null'
116
+ ? oldPath
117
+ : (newPath && newPath !== '/dev/null' ? newPath : file.path));
118
+ if (file.kind === 'delete') {
119
+ out.push(`*** Delete File: ${resolvedPath}`);
120
+ continue;
121
+ }
122
+ if (file.kind === 'add') {
123
+ out.push(`*** Add File: ${resolvedPath}`);
124
+ for (const l of file.lines) {
125
+ if (l.startsWith('+'))
126
+ out.push(l);
127
+ }
128
+ continue;
129
+ }
130
+ out.push(`*** Update File: ${resolvedPath}`);
131
+ if (file.moveTo && file.moveTo !== resolvedPath) {
132
+ out.push(`*** Move to: ${file.moveTo}`);
133
+ }
134
+ out.push(...file.lines);
135
+ }
136
+ out.push('*** End Patch');
137
+ return out.join('\n');
138
+ };
@@ -0,0 +1 @@
1
+ export declare const looksLikePatch: (text?: string) => boolean;
@@ -0,0 +1,13 @@
1
+ export const looksLikePatch = (text) => {
2
+ if (!text)
3
+ return false;
4
+ const t = text.trim();
5
+ if (!t)
6
+ return false;
7
+ return (t.includes('*** Begin Patch') ||
8
+ t.includes('*** Update File:') ||
9
+ t.includes('*** Add File:') ||
10
+ t.includes('*** Delete File:') ||
11
+ t.includes('diff --git') ||
12
+ /^(?:@@|\+\+\+\s|---\s)/m.test(t));
13
+ };
@@ -0,0 +1,3 @@
1
+ import { looksLikePatch } from './looks-like-patch.js';
2
+ export declare const normalizeApplyPatchText: (raw: string) => string;
3
+ export { looksLikePatch };
@@ -0,0 +1,262 @@
1
+ import { looksLikePatch } from './looks-like-patch.js';
2
+ import { convertGitDiffToApplyPatch } from './git-diff.js';
3
+ import { convertContextDiffToApplyPatch } from './context-diff.js';
4
+ const normalizeBeginEndMarkers = (input) => {
5
+ try {
6
+ const lines = input.replace(/\r\n/g, '\n').split('\n');
7
+ if (!lines.length)
8
+ return input;
9
+ const first = String(lines[0] ?? '').trim().toLowerCase();
10
+ if (first.startsWith('*** begin patch')) {
11
+ lines[0] = '*** Begin Patch';
12
+ }
13
+ const lastIndex = lines.length - 1;
14
+ const last = String(lines[lastIndex] ?? '').trim().toLowerCase();
15
+ if (last.startsWith('*** end patch')) {
16
+ lines[lastIndex] = '*** End Patch';
17
+ }
18
+ return lines.join('\n');
19
+ }
20
+ catch {
21
+ return input;
22
+ }
23
+ };
24
+ const convertUnifiedDiffToApplyPatchIfPossible = (text) => {
25
+ try {
26
+ if (!text)
27
+ return null;
28
+ if (text.includes('diff --git')) {
29
+ return convertGitDiffToApplyPatch(text);
30
+ }
31
+ const minusMatch = text.match(/^---\s+(.*)$/m);
32
+ const plusMatch = text.match(/^\+\+\+\s+(.*)$/m);
33
+ if (!minusMatch || !plusMatch)
34
+ return null;
35
+ const rawMinus = (minusMatch[1] || '').split('\t')[0] || '';
36
+ const rawPlus = (plusMatch[1] || '').split('\t')[0] || '';
37
+ const normalizeHeaderPath = (value) => {
38
+ const v = String(value || '').trim();
39
+ if (!v)
40
+ return '';
41
+ const m = v.match(/^(?:a\/|b\/)?(.+)$/);
42
+ return (m && m[1] ? m[1] : v).trim();
43
+ };
44
+ const minusPath = normalizeHeaderPath(rawMinus);
45
+ const plusPath = normalizeHeaderPath(rawPlus);
46
+ const filePath = plusPath === '/dev/null' ? minusPath : plusPath;
47
+ if (!filePath)
48
+ return null;
49
+ const synthetic = `diff --git a/${filePath} b/${filePath}\n${text}`;
50
+ return convertGitDiffToApplyPatch(synthetic);
51
+ }
52
+ catch {
53
+ return null;
54
+ }
55
+ };
56
+ const stripCodeFences = (text) => {
57
+ const trimmed = text.trim();
58
+ // Only treat the entire payload as fenced when it *starts* with a code fence.
59
+ // Patch bodies (especially added Markdown files) may legitimately contain ``` blocks;
60
+ // we must not strip those.
61
+ if (!trimmed.startsWith('```'))
62
+ return text;
63
+ const fenceRe = /^```(?:diff|patch|apply_patch|text|json)?[ \t]*\n([\s\S]*?)\n```/gmi;
64
+ const candidates = [];
65
+ let match = null;
66
+ while ((match = fenceRe.exec(trimmed))) {
67
+ if (match[1])
68
+ candidates.push(match[1].trim());
69
+ }
70
+ if (!candidates.length)
71
+ return text;
72
+ for (const candidate of candidates) {
73
+ if (candidate.includes('*** Begin Patch') ||
74
+ candidate.includes('*** Update File:') ||
75
+ candidate.includes('diff --git')) {
76
+ return candidate;
77
+ }
78
+ }
79
+ return candidates[0] ?? text;
80
+ };
81
+ const decodeEscapedNewlinesIfNeeded = (value) => {
82
+ try {
83
+ if (!value)
84
+ return value;
85
+ if (value.includes('\n'))
86
+ return value;
87
+ if (!value.includes('\\n'))
88
+ return value;
89
+ return value.replace(/\\n/g, '\n');
90
+ }
91
+ catch {
92
+ return value;
93
+ }
94
+ };
95
+ const stripConflictMarkers = (text) => {
96
+ try {
97
+ const lines = text.replace(/\r\n/g, '\n').split('\n');
98
+ const out = [];
99
+ for (const line of lines) {
100
+ if (line.startsWith('<<<<<<<') || line.startsWith('=======') || line.startsWith('>>>>>>>')) {
101
+ continue;
102
+ }
103
+ out.push(line);
104
+ }
105
+ return out.join('\n');
106
+ }
107
+ catch {
108
+ return text;
109
+ }
110
+ };
111
+ export const normalizeApplyPatchText = (raw) => {
112
+ if (!raw)
113
+ return raw;
114
+ let text = raw.replace(/\r\n/g, '\n');
115
+ text = decodeEscapedNewlinesIfNeeded(text);
116
+ text = stripCodeFences(text);
117
+ text = text.trim();
118
+ if (!text)
119
+ return raw;
120
+ text = stripConflictMarkers(text);
121
+ text = normalizeBeginEndMarkers(text);
122
+ // Some models emit a GNU unified diff wrapped in apply_patch markers, e.g.:
123
+ // *** Begin Patch ***
124
+ // --- a/file
125
+ // +++ b/file
126
+ // @@ ...
127
+ // *** End Patch ***
128
+ // Convert it to apply_patch format so the client tool can execute it.
129
+ if (text.startsWith('*** Begin Patch') &&
130
+ !text.includes('*** Update File:') &&
131
+ !text.includes('*** Add File:') &&
132
+ !text.includes('*** Delete File:') &&
133
+ (/^---\s+/m.test(text) || /^\+\+\+\s+/m.test(text))) {
134
+ const lines = text.split('\n');
135
+ const inner = lines
136
+ .filter((l, idx) => {
137
+ if (idx === 0 && l.startsWith('*** Begin Patch'))
138
+ return false;
139
+ if (idx === lines.length - 1 && l.startsWith('*** End Patch'))
140
+ return false;
141
+ return true;
142
+ })
143
+ .join('\n')
144
+ .trim();
145
+ const converted = convertUnifiedDiffToApplyPatchIfPossible(inner);
146
+ if (converted) {
147
+ text = converted;
148
+ }
149
+ }
150
+ if (!text.includes('*** Begin Patch') && text.includes('diff --git')) {
151
+ const converted = convertGitDiffToApplyPatch(text);
152
+ if (converted)
153
+ text = converted;
154
+ }
155
+ else if (!text.includes('*** Begin Patch') &&
156
+ !text.includes('diff --git') &&
157
+ text.includes('***************') &&
158
+ /^\*\*\*\s+\S+/m.test(text) &&
159
+ /^---\s+\S+/m.test(text)) {
160
+ // Support classic "context diff" format:
161
+ // *** path
162
+ // --- path
163
+ // ***************
164
+ // *** 1,2 ****
165
+ // --- 1,3 ----
166
+ const converted = convertContextDiffToApplyPatch(text);
167
+ if (converted)
168
+ text = converted;
169
+ }
170
+ else if (!text.includes('*** Begin Patch') && !text.includes('diff --git')) {
171
+ const converted = convertUnifiedDiffToApplyPatchIfPossible(text);
172
+ if (converted)
173
+ text = converted;
174
+ }
175
+ if (text.includes('*** Add File:')) {
176
+ text = text.replace(/\*\*\* Create File:/g, '*** Add File:');
177
+ }
178
+ let hasBegin = text.includes('*** Begin Patch');
179
+ const hasEnd = text.includes('*** End Patch');
180
+ if (hasBegin && !hasEnd) {
181
+ text = `${text}\n*** End Patch`;
182
+ }
183
+ if (!hasBegin && /^\*\*\* (Add|Update|Delete) File:/m.test(text)) {
184
+ text = `*** Begin Patch\n${text}\n*** End Patch`;
185
+ hasBegin = true;
186
+ }
187
+ if (!text.includes('*** Begin Patch')) {
188
+ return text;
189
+ }
190
+ const beginIndex = text.indexOf('*** Begin Patch');
191
+ if (beginIndex > 0) {
192
+ text = text.slice(beginIndex);
193
+ }
194
+ const endMarker = '*** End Patch';
195
+ const firstEndIndex = text.indexOf(endMarker);
196
+ const concatSignatures = [
197
+ `${endMarker}","input":"*** Begin Patch`,
198
+ `${endMarker}","patch":"*** Begin Patch`,
199
+ `${endMarker}\\",\\"input\\":\\"*** Begin Patch`,
200
+ `${endMarker}\\",\\"patch\\":\\"*** Begin Patch`
201
+ ];
202
+ const hasConcatenationSignal = concatSignatures.some((sig) => text.includes(sig));
203
+ if (hasConcatenationSignal && firstEndIndex >= 0) {
204
+ text = text.slice(0, firstEndIndex + endMarker.length);
205
+ }
206
+ else {
207
+ const lastEndIndex = text.lastIndexOf(endMarker);
208
+ if (lastEndIndex >= 0) {
209
+ const afterEnd = text.slice(lastEndIndex + endMarker.length);
210
+ if (afterEnd.trim().length > 0) {
211
+ text = text.slice(0, lastEndIndex + endMarker.length);
212
+ }
213
+ }
214
+ }
215
+ // Fix missing prefix lines in Update File sections: treat as context (" ").
216
+ const lines = text.split('\n');
217
+ const output = [];
218
+ let inUpdateSection = false;
219
+ let afterUpdateHeader = false;
220
+ for (const line of lines) {
221
+ if (line.startsWith('*** Begin Patch')) {
222
+ output.push(line);
223
+ inUpdateSection = false;
224
+ afterUpdateHeader = false;
225
+ continue;
226
+ }
227
+ if (line.startsWith('*** End Patch')) {
228
+ output.push(line);
229
+ inUpdateSection = false;
230
+ afterUpdateHeader = false;
231
+ continue;
232
+ }
233
+ if (line.startsWith('*** Update File:')) {
234
+ output.push(line);
235
+ inUpdateSection = true;
236
+ afterUpdateHeader = true;
237
+ continue;
238
+ }
239
+ if (line.startsWith('*** Add File:') || line.startsWith('*** Delete File:')) {
240
+ output.push(line);
241
+ inUpdateSection = false;
242
+ afterUpdateHeader = false;
243
+ continue;
244
+ }
245
+ if (inUpdateSection) {
246
+ if (afterUpdateHeader && line.trim() === '') {
247
+ continue;
248
+ }
249
+ afterUpdateHeader = false;
250
+ if (line.startsWith('@@') || line.startsWith('+') || line.startsWith('-') || line.startsWith(' ')) {
251
+ output.push(line);
252
+ }
253
+ else {
254
+ output.push(` ${line}`);
255
+ }
256
+ continue;
257
+ }
258
+ output.push(line);
259
+ }
260
+ return output.join('\n');
261
+ };
262
+ export { looksLikePatch };
@@ -0,0 +1,3 @@
1
+ import type { StructuredApplyPatchPayload } from '../structured.js';
2
+ import { type UnknownRecord } from '../validation/shared.js';
3
+ export declare const coerceStructuredPayload: (record: UnknownRecord) => StructuredApplyPatchPayload | undefined;
@@ -0,0 +1,82 @@
1
+ import { isStructuredApplyPatchPayload } from '../structured.js';
2
+ import { tryParseJson } from '../json/parse-loose.js';
3
+ import { asString } from '../validation/shared.js';
4
+ const buildSingleChangePayload = (record) => {
5
+ const kindRaw = asString(record.kind);
6
+ if (!kindRaw)
7
+ return undefined;
8
+ const change = {
9
+ kind: kindRaw.toLowerCase(),
10
+ lines: record.lines ?? record.text ?? record.body,
11
+ target: asString(record.target) ?? undefined,
12
+ anchor: asString(record.anchor) ?? undefined
13
+ };
14
+ if (typeof record.use_anchor_indent === 'boolean') {
15
+ change.use_anchor_indent = record.use_anchor_indent;
16
+ }
17
+ const changeFile = asString(record.file);
18
+ if (changeFile) {
19
+ change.file = changeFile;
20
+ }
21
+ return { file: changeFile, changes: [change] };
22
+ };
23
+ const coerceChangesArray = (value) => {
24
+ const parsed = tryParseJson(value);
25
+ if (!parsed)
26
+ return undefined;
27
+ if (Array.isArray(parsed)) {
28
+ const items = parsed.filter((entry) => entry && typeof entry === 'object');
29
+ if (!items.length)
30
+ return undefined;
31
+ // Ensure at least one entry looks like a structured change.
32
+ if (!items.some((c) => typeof c.kind === 'string' && String(c.kind).trim()))
33
+ return undefined;
34
+ return items;
35
+ }
36
+ if (parsed && typeof parsed === 'object' && Array.isArray(parsed.changes)) {
37
+ const items = parsed.changes.filter((entry) => entry && typeof entry === 'object');
38
+ if (!items.length)
39
+ return undefined;
40
+ if (!items.some((c) => typeof c.kind === 'string' && String(c.kind).trim()))
41
+ return undefined;
42
+ return items;
43
+ }
44
+ return undefined;
45
+ };
46
+ export const coerceStructuredPayload = (record) => {
47
+ if (isStructuredApplyPatchPayload(record)) {
48
+ return record;
49
+ }
50
+ if (Array.isArray(record.changes) && record.changes.length === 0) {
51
+ return undefined;
52
+ }
53
+ // Common shape error: { file, instructions: "[{...},{...}]" } where instructions contains JSON changes.
54
+ if (!Array.isArray(record.changes)) {
55
+ const changesFromInstructions = coerceChangesArray(record.instructions);
56
+ if (changesFromInstructions) {
57
+ return {
58
+ ...(typeof record.file === 'string' ? { file: record.file } : {}),
59
+ changes: changesFromInstructions
60
+ };
61
+ }
62
+ // Another common shape: changes is a JSON string.
63
+ const changesFromString = coerceChangesArray(record.changes);
64
+ if (changesFromString) {
65
+ return {
66
+ ...(typeof record.file === 'string' ? { file: record.file } : {}),
67
+ changes: changesFromString
68
+ };
69
+ }
70
+ const editsFromString = coerceChangesArray(record.edits ?? record.operations ?? record.ops);
71
+ if (editsFromString) {
72
+ return {
73
+ ...(typeof record.file === 'string' ? { file: record.file } : {}),
74
+ changes: editsFromString
75
+ };
76
+ }
77
+ }
78
+ const single = buildSingleChangePayload(record);
79
+ if (single)
80
+ return single;
81
+ return undefined;
82
+ };
@@ -0,0 +1,3 @@
1
+ export type UnknownRecord = Record<string, unknown>;
2
+ export declare const isRecord: (value: unknown) => value is UnknownRecord;
3
+ export declare const asString: (value: unknown) => string | null;