@jsonstudio/llms 0.6.1643 → 0.6.1739

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 (96) hide show
  1. package/dist/conversion/compat/actions/harvest-tool-calls-from-text.d.ts +10 -0
  2. package/dist/conversion/compat/actions/harvest-tool-calls-from-text.js +121 -0
  3. package/dist/conversion/compat/actions/iflow-kimi-cli-defaults.d.ts +10 -0
  4. package/dist/conversion/compat/actions/iflow-kimi-cli-defaults.js +80 -0
  5. package/dist/conversion/compat/actions/iflow-kimi-history-media-placeholder.d.ts +7 -0
  6. package/dist/conversion/compat/actions/iflow-kimi-history-media-placeholder.js +161 -0
  7. package/dist/conversion/compat/actions/iflow-kimi-thinking-reasoning-fill.d.ts +12 -0
  8. package/dist/conversion/compat/actions/iflow-kimi-thinking-reasoning-fill.js +67 -0
  9. package/dist/conversion/compat/actions/iflow-response-body-unwrap.d.ts +9 -0
  10. package/dist/conversion/compat/actions/iflow-response-body-unwrap.js +140 -0
  11. package/dist/conversion/compat/actions/lmstudio-responses-fc-ids.d.ts +10 -0
  12. package/dist/conversion/compat/actions/lmstudio-responses-fc-ids.js +59 -0
  13. package/dist/conversion/compat/actions/lmstudio-responses-input-stringify.d.ts +14 -0
  14. package/dist/conversion/compat/actions/lmstudio-responses-input-stringify.js +125 -0
  15. package/dist/conversion/compat/actions/normalize-tool-call-ids.d.ts +11 -0
  16. package/dist/conversion/compat/actions/normalize-tool-call-ids.js +140 -0
  17. package/dist/conversion/compat/actions/strip-orphan-function-calls-tag.d.ts +2 -0
  18. package/dist/conversion/compat/actions/strip-orphan-function-calls-tag.js +152 -0
  19. package/dist/conversion/compat/antigravity-session-signature.d.ts +1 -1
  20. package/dist/conversion/compat/antigravity-session-signature.js +5 -4
  21. package/dist/conversion/compat/profiles/chat-iflow.json +6 -0
  22. package/dist/conversion/compat/profiles/chat-lmstudio.json +7 -1
  23. package/dist/conversion/hub/operation-table/operation-table-runner.js +1 -1
  24. package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.js +19 -2
  25. package/dist/conversion/hub/pipeline/compat/compat-pipeline-executor.js +101 -5
  26. package/dist/conversion/hub/pipeline/compat/compat-profile-resolver.d.ts +2 -0
  27. package/dist/conversion/hub/pipeline/compat/compat-profile-resolver.js +63 -0
  28. package/dist/conversion/hub/pipeline/compat/compat-types.d.ts +18 -0
  29. package/dist/conversion/hub/pipeline/hub-pipeline.js +1 -1
  30. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.js +8 -5
  31. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage3_compat/index.js +5 -1
  32. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage1_sse_decode/index.js +113 -0
  33. package/dist/conversion/hub/pipeline/target-utils.js +3 -0
  34. package/dist/conversion/hub/response/provider-response.js +27 -1
  35. package/dist/conversion/responses/responses-openai-bridge.js +32 -6
  36. package/dist/conversion/shared/anthropic-message-utils.js +20 -5
  37. package/dist/conversion/shared/bridge-id-utils.d.ts +2 -0
  38. package/dist/conversion/shared/bridge-id-utils.js +52 -15
  39. package/dist/conversion/shared/responses-conversation-store.js +40 -5
  40. package/dist/conversion/shared/responses-output-builder.js +23 -7
  41. package/dist/conversion/shared/responses-tool-utils.d.ts +1 -0
  42. package/dist/conversion/shared/responses-tool-utils.js +30 -13
  43. package/dist/conversion/shared/text-markup-normalizer.d.ts +1 -0
  44. package/dist/conversion/shared/text-markup-normalizer.js +269 -1
  45. package/dist/router/virtual-router/bootstrap.js +31 -7
  46. package/dist/router/virtual-router/classifier.js +1 -1
  47. package/dist/router/virtual-router/engine/antigravity/alias-lease.d.ts +33 -0
  48. package/dist/router/virtual-router/engine/antigravity/alias-lease.js +247 -0
  49. package/dist/router/virtual-router/engine/health/index.d.ts +23 -0
  50. package/dist/router/virtual-router/engine/health/index.js +720 -0
  51. package/dist/router/virtual-router/engine/provider-key/parse.d.ts +6 -0
  52. package/dist/router/virtual-router/engine/provider-key/parse.js +43 -0
  53. package/dist/router/virtual-router/engine/routing-pools/index.d.ts +13 -0
  54. package/dist/router/virtual-router/engine/routing-pools/index.js +225 -0
  55. package/dist/router/virtual-router/engine/routing-state/keys.d.ts +3 -0
  56. package/dist/router/virtual-router/engine/routing-state/keys.js +30 -0
  57. package/dist/router/virtual-router/engine/routing-state/metadata.d.ts +6 -0
  58. package/dist/router/virtual-router/engine/routing-state/metadata.js +132 -0
  59. package/dist/router/virtual-router/engine/routing-state/store.d.ts +11 -0
  60. package/dist/router/virtual-router/engine/routing-state/store.js +107 -0
  61. package/dist/router/virtual-router/engine-health.d.ts +1 -23
  62. package/dist/router/virtual-router/engine-health.js +1 -720
  63. package/dist/router/virtual-router/engine-selection/route-utils.js +57 -0
  64. package/dist/router/virtual-router/engine-selection/tier-selection-select.js +8 -48
  65. package/dist/router/virtual-router/engine-selection/tier-selection.js +34 -17
  66. package/dist/router/virtual-router/engine-selection.d.ts +1 -13
  67. package/dist/router/virtual-router/engine-selection.js +1 -225
  68. package/dist/router/virtual-router/engine.d.ts +2 -23
  69. package/dist/router/virtual-router/engine.js +130 -603
  70. package/dist/router/virtual-router/message-utils.js +15 -5
  71. package/dist/servertool/engine.js +4 -4
  72. package/dist/servertool/handlers/followup-request-builder.js +46 -0
  73. package/dist/servertool/handlers/gemini-empty-reply-continue.js +48 -47
  74. package/dist/servertool/handlers/stop-message-auto.js +64 -7
  75. package/dist/servertool/handlers/vision.js +10 -0
  76. package/dist/servertool/types.d.ts +3 -0
  77. package/dist/sse/sse-to-json/builders/response-builder.js +6 -0
  78. package/dist/sse/sse-to-json/chat-sse-to-json-converter.js +32 -2
  79. package/dist/sse/sse-to-json/parsers/sse-parser.js +34 -0
  80. package/dist/sse/sse-to-json/responses-sse-to-json-converter.d.ts +1 -0
  81. package/dist/sse/sse-to-json/responses-sse-to-json-converter.js +33 -1
  82. package/dist/tools/apply-patch/args-normalizer/default-actions.d.ts +2 -0
  83. package/dist/tools/apply-patch/args-normalizer/default-actions.js +12 -0
  84. package/dist/tools/apply-patch/args-normalizer/extract-patch.d.ts +2 -0
  85. package/dist/tools/apply-patch/args-normalizer/extract-patch.js +15 -0
  86. package/dist/tools/apply-patch/args-normalizer/index.d.ts +2 -0
  87. package/dist/tools/apply-patch/args-normalizer/index.js +164 -0
  88. package/dist/tools/apply-patch/args-normalizer/structured-builders.d.ts +7 -0
  89. package/dist/tools/apply-patch/args-normalizer/structured-builders.js +85 -0
  90. package/dist/tools/apply-patch/args-normalizer/types.d.ts +54 -0
  91. package/dist/tools/apply-patch/args-normalizer/types.js +1 -0
  92. package/dist/tools/apply-patch/patch-text/looks-like-patch.js +1 -0
  93. package/dist/tools/apply-patch/patch-text/normalize.js +104 -5
  94. package/dist/tools/apply-patch/structured/coercion.js +28 -4
  95. package/dist/tools/apply-patch/validator.js +7 -146
  96. package/package.json +1 -1
@@ -0,0 +1,15 @@
1
+ import { normalizeApplyPatchText, looksLikePatch } from '../patch-text/normalize.js';
2
+ export function extractNormalizedPatch(value) {
3
+ if (!value) {
4
+ return {};
5
+ }
6
+ const normalized = normalizeApplyPatchText(value);
7
+ const patchLike = looksLikePatch(value) || looksLikePatch(normalized) || normalized.includes('*** Begin Patch');
8
+ if (!patchLike) {
9
+ return {};
10
+ }
11
+ if (!normalized.includes('*** Begin Patch')) {
12
+ return { failureReason: 'unsupported_patch_format' };
13
+ }
14
+ return { patchText: normalized };
15
+ }
@@ -0,0 +1,2 @@
1
+ import type { ApplyPatchNormalizeOptions, ApplyPatchNormalizeResult } from './types.js';
2
+ export declare function normalizeApplyPatchArgs(argsString: string, rawArgs: unknown, options?: ApplyPatchNormalizeOptions): ApplyPatchNormalizeResult;
@@ -0,0 +1,164 @@
1
+ import { coerceStructuredPayload } from '../structured/coercion.js';
2
+ import { tryParseJsonLoose } from '../json/parse-loose.js';
3
+ import { looksLikePatch } from '../patch-text/normalize.js';
4
+ import { asString, isRecord } from '../validation/shared.js';
5
+ import { DEFAULT_APPLY_PATCH_NORMALIZE_ACTIONS } from './default-actions.js';
6
+ import { extractNormalizedPatch } from './extract-patch.js';
7
+ import { buildPatchFromChangesArray, buildPatchFromStructuredPayload, extractConflictPatchAsStructuredReplace, resolvePathAlias } from './structured-builders.js';
8
+ function getActionList(options) {
9
+ if (Array.isArray(options?.actions) && options?.actions.length > 0) {
10
+ return options.actions;
11
+ }
12
+ return DEFAULT_APPLY_PATCH_NORMALIZE_ACTIONS;
13
+ }
14
+ function runRecordActions(record, state, actions, runNested, isFinalPass) {
15
+ for (const action of actions) {
16
+ if (action.action === 'record_text_fields') {
17
+ for (const field of action.fields) {
18
+ const value = asString(record[field]);
19
+ const extracted = extractNormalizedPatch(value);
20
+ if (extracted.patchText || extracted.failureReason) {
21
+ return extracted;
22
+ }
23
+ }
24
+ continue;
25
+ }
26
+ if (action.action === 'record_conflict_patch') {
27
+ const patchFieldValue = asString(record[action.patchField]);
28
+ const pathAlias = resolvePathAlias(record, action.fileFields);
29
+ const extracted = extractConflictPatchAsStructuredReplace(pathAlias, patchFieldValue ?? undefined);
30
+ if (extracted.patchText || extracted.failureReason) {
31
+ return extracted;
32
+ }
33
+ continue;
34
+ }
35
+ if (action.action === 'record_raw_envelope') {
36
+ const rawEnvelope = asString(record[action.field]);
37
+ if (!rawEnvelope) {
38
+ continue;
39
+ }
40
+ const fromEnvelope = extractNormalizedPatch(rawEnvelope.trim());
41
+ if (fromEnvelope.patchText || fromEnvelope.failureReason) {
42
+ return fromEnvelope;
43
+ }
44
+ if (action.parseJson === false) {
45
+ continue;
46
+ }
47
+ const maxDepth = typeof action.maxDepth === 'number' ? action.maxDepth : 2;
48
+ if (state.depth >= maxDepth) {
49
+ continue;
50
+ }
51
+ const parsed = tryParseJsonLoose(rawEnvelope.trim());
52
+ if (isRecord(parsed) || Array.isArray(parsed)) {
53
+ const nested = runNested(rawEnvelope.trim(), parsed, state.depth + 1);
54
+ if (nested.patchText || nested.failureReason) {
55
+ return nested;
56
+ }
57
+ }
58
+ continue;
59
+ }
60
+ if (action.action === 'record_structured_payload') {
61
+ const payload = coerceStructuredPayload(record);
62
+ const extracted = buildPatchFromStructuredPayload(payload);
63
+ if (extracted.patchText || extracted.failureReason) {
64
+ return extracted;
65
+ }
66
+ continue;
67
+ }
68
+ if (action.action === 'invalid_json_guard' && isFinalPass) {
69
+ if (state.looksJsonContainer &&
70
+ isRecord(state.rawArgs) &&
71
+ Object.keys(state.rawArgs).length === 0 &&
72
+ state.rawTrimmed.length > 0 &&
73
+ !looksLikePatch(state.rawTrimmed)) {
74
+ return { failureReason: 'invalid_json' };
75
+ }
76
+ }
77
+ }
78
+ return {};
79
+ }
80
+ function runActions(state, actions, runNested, isFinalPass) {
81
+ for (const action of actions) {
82
+ if (action.action === 'raw_non_json_patch') {
83
+ if (!state.looksJsonContainer) {
84
+ const extracted = extractNormalizedPatch(state.rawTrimmed);
85
+ if (extracted.patchText || extracted.failureReason) {
86
+ return extracted;
87
+ }
88
+ }
89
+ continue;
90
+ }
91
+ if (action.action === 'json_container_patch_fallback') {
92
+ if (state.looksJsonContainer && isRecord(state.rawArgs) && Object.keys(state.rawArgs).length === 0) {
93
+ const extracted = extractNormalizedPatch(state.rawTrimmed);
94
+ if (extracted.patchText || extracted.failureReason) {
95
+ return extracted;
96
+ }
97
+ }
98
+ continue;
99
+ }
100
+ if (action.action === 'array_structured_payload') {
101
+ if (!Array.isArray(state.rawArgs) || state.rawArgs.length === 0) {
102
+ continue;
103
+ }
104
+ const firstRecord = state.rawArgs.find((entry) => isRecord(entry));
105
+ if (firstRecord && Array.isArray(firstRecord.changes)) {
106
+ const extracted = runRecordActions(firstRecord, state, actions, runNested, isFinalPass);
107
+ if (extracted.patchText || extracted.failureReason) {
108
+ return extracted;
109
+ }
110
+ continue;
111
+ }
112
+ const changesArray = state.rawArgs.filter((entry) => isRecord(entry));
113
+ const extracted = buildPatchFromChangesArray(changesArray);
114
+ if (extracted.patchText || extracted.failureReason) {
115
+ return extracted;
116
+ }
117
+ continue;
118
+ }
119
+ if (action.action === 'raw_string_patch') {
120
+ if (typeof state.rawArgs === 'string') {
121
+ const extracted = extractNormalizedPatch(state.rawArgs);
122
+ if (extracted.patchText || extracted.failureReason) {
123
+ return extracted;
124
+ }
125
+ }
126
+ continue;
127
+ }
128
+ if (isRecord(state.rawArgs)) {
129
+ const extracted = runRecordActions(state.rawArgs, state, [action], runNested, isFinalPass);
130
+ if (extracted.patchText || extracted.failureReason) {
131
+ return extracted;
132
+ }
133
+ }
134
+ }
135
+ return {};
136
+ }
137
+ function normalizeState(argsString, rawArgs, depth) {
138
+ const rawTrimmed = typeof argsString === 'string' ? argsString.trim() : '';
139
+ return {
140
+ argsString,
141
+ rawArgs,
142
+ rawTrimmed,
143
+ looksJsonContainer: rawTrimmed.startsWith('{') || rawTrimmed.startsWith('['),
144
+ depth
145
+ };
146
+ }
147
+ function resolveExtraction(argsString, rawArgs, actions, depth, isFinalPass) {
148
+ const state = normalizeState(argsString, rawArgs, depth);
149
+ const runNested = (nextArgsString, nextRawArgs, nextDepth) => {
150
+ return resolveExtraction(nextArgsString, nextRawArgs, actions, nextDepth, false);
151
+ };
152
+ return runActions(state, actions, runNested, isFinalPass);
153
+ }
154
+ export function normalizeApplyPatchArgs(argsString, rawArgs, options) {
155
+ const actions = getActionList(options);
156
+ const extracted = resolveExtraction(argsString, rawArgs, actions, 0, true);
157
+ if (extracted.patchText) {
158
+ return { ok: true, patchText: extracted.patchText };
159
+ }
160
+ if (extracted.failureReason) {
161
+ return { ok: false, reason: extracted.failureReason };
162
+ }
163
+ return { ok: false, reason: 'missing_changes' };
164
+ }
@@ -0,0 +1,7 @@
1
+ import { type StructuredApplyPatchPayload } from '../structured.js';
2
+ import { type UnknownRecord } from '../validation/shared.js';
3
+ import type { ApplyPatchExtraction } from './types.js';
4
+ export declare function resolvePathAlias(record: UnknownRecord, fileFields: string[]): string | undefined;
5
+ export declare function buildPatchFromChangesArray(changesArray: UnknownRecord[]): ApplyPatchExtraction;
6
+ export declare function buildPatchFromStructuredPayload(payload: StructuredApplyPatchPayload | undefined): ApplyPatchExtraction;
7
+ export declare function extractConflictPatchAsStructuredReplace(filePath: string | undefined, conflictText: string | undefined): ApplyPatchExtraction;
@@ -0,0 +1,85 @@
1
+ import { buildStructuredPatch, StructuredApplyPatchError } from '../structured.js';
2
+ import { asString } from '../validation/shared.js';
3
+ export function resolvePathAlias(record, fileFields) {
4
+ for (const field of fileFields) {
5
+ const value = asString(record[field]);
6
+ if (value) {
7
+ return value;
8
+ }
9
+ }
10
+ return undefined;
11
+ }
12
+ function safeBuildStructuredPatch(payload) {
13
+ try {
14
+ return { patchText: buildStructuredPatch(payload) };
15
+ }
16
+ catch (error) {
17
+ if (!(error instanceof StructuredApplyPatchError)) {
18
+ throw error;
19
+ }
20
+ return { failureReason: error.reason || 'structured_apply_patch_error' };
21
+ }
22
+ }
23
+ export function buildPatchFromChangesArray(changesArray) {
24
+ if (!changesArray.length || !changesArray.some((change) => typeof change.kind === 'string')) {
25
+ return {};
26
+ }
27
+ const payload = {
28
+ changes: changesArray
29
+ };
30
+ return safeBuildStructuredPatch(payload);
31
+ }
32
+ export function buildPatchFromStructuredPayload(payload) {
33
+ if (!payload) {
34
+ return {};
35
+ }
36
+ return safeBuildStructuredPatch(payload);
37
+ }
38
+ export function extractConflictPatchAsStructuredReplace(filePath, conflictText) {
39
+ const safeFilePath = typeof filePath === 'string' ? filePath.trim() : '';
40
+ const safeText = typeof conflictText === 'string' ? conflictText : '';
41
+ if (!safeFilePath || !safeText) {
42
+ return {};
43
+ }
44
+ const lines = safeText.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n');
45
+ let start = -1;
46
+ let middle = -1;
47
+ let end = -1;
48
+ for (let index = 0; index < lines.length; index += 1) {
49
+ const line = lines[index] ?? '';
50
+ if (start < 0) {
51
+ if (line.startsWith('<<<<<<<')) {
52
+ start = index;
53
+ }
54
+ continue;
55
+ }
56
+ if (middle < 0) {
57
+ if (line.startsWith('=======')) {
58
+ middle = index;
59
+ }
60
+ continue;
61
+ }
62
+ if (line.startsWith('>>>>>>>')) {
63
+ end = index;
64
+ break;
65
+ }
66
+ }
67
+ if (start < 0 || middle <= start || end <= middle) {
68
+ return {};
69
+ }
70
+ const original = lines.slice(start + 1, middle).join('\n');
71
+ const updated = lines.slice(middle + 1, end).join('\n');
72
+ if (!original.trim() || !updated.trim()) {
73
+ return {};
74
+ }
75
+ return safeBuildStructuredPatch({
76
+ file: safeFilePath,
77
+ changes: [
78
+ {
79
+ kind: 'replace',
80
+ target: original,
81
+ lines: updated
82
+ }
83
+ ]
84
+ });
85
+ }
@@ -0,0 +1,54 @@
1
+ import type { UnknownRecord } from '../validation/shared.js';
2
+ export type ApplyPatchExtraction = {
3
+ patchText?: string;
4
+ failureReason?: string;
5
+ };
6
+ export type ApplyPatchNormalizeResult = {
7
+ ok: true;
8
+ patchText: string;
9
+ } | {
10
+ ok: false;
11
+ reason: string;
12
+ };
13
+ export type ApplyPatchNormalizeAction = {
14
+ action: 'raw_non_json_patch';
15
+ } | {
16
+ action: 'json_container_patch_fallback';
17
+ } | {
18
+ action: 'record_text_fields';
19
+ fields: string[];
20
+ } | {
21
+ action: 'record_conflict_patch';
22
+ patchField: string;
23
+ fileFields: string[];
24
+ } | {
25
+ action: 'record_raw_envelope';
26
+ field: string;
27
+ parseJson?: boolean;
28
+ maxDepth?: number;
29
+ } | {
30
+ action: 'record_structured_payload';
31
+ } | {
32
+ action: 'array_structured_payload';
33
+ } | {
34
+ action: 'raw_string_patch';
35
+ } | {
36
+ action: 'invalid_json_guard';
37
+ };
38
+ export type ApplyPatchNormalizeOptions = {
39
+ actions?: ApplyPatchNormalizeAction[];
40
+ };
41
+ export type ApplyPatchNormalizeState = {
42
+ argsString: string;
43
+ rawArgs: unknown;
44
+ rawTrimmed: string;
45
+ looksJsonContainer: boolean;
46
+ depth: number;
47
+ };
48
+ export type ApplyPatchActionContext = {
49
+ state: ApplyPatchNormalizeState;
50
+ runNested: (argsString: string, rawArgs: unknown, depth: number) => ApplyPatchExtraction;
51
+ };
52
+ export type ApplyPatchRecordActionContext = ApplyPatchActionContext & {
53
+ record: UnknownRecord;
54
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -7,6 +7,7 @@ export const looksLikePatch = (text) => {
7
7
  return (t.includes('*** Begin Patch') ||
8
8
  t.includes('*** Update File:') ||
9
9
  t.includes('*** Add File:') ||
10
+ t.includes('*** Create File:') ||
10
11
  t.includes('*** Delete File:') ||
11
12
  t.includes('diff --git') ||
12
13
  /^(?:@@|\+\+\+\s|---\s)/m.test(t));
@@ -53,6 +53,77 @@ const convertUnifiedDiffToApplyPatchIfPossible = (text) => {
53
53
  return null;
54
54
  }
55
55
  };
56
+ const convertStarHeaderDiffToApplyPatchIfPossible = (text) => {
57
+ try {
58
+ if (!text || text.includes('*** Begin Patch') || text.includes('diff --git'))
59
+ return null;
60
+ const lines = text.replace(/\r\n/g, '\n').split('\n');
61
+ if (lines.length < 3)
62
+ return null;
63
+ const first = String(lines[0] ?? '').trim();
64
+ const second = String(lines[1] ?? '').trim();
65
+ if (!first.startsWith('*** ') || !second.startsWith('--- '))
66
+ return null;
67
+ if (first.startsWith('*** Add File:') || first.startsWith('*** Update File:') || first.startsWith('*** Delete File:')) {
68
+ return null;
69
+ }
70
+ if (!lines.some((line) => line.startsWith('@@')))
71
+ return null;
72
+ const normalizeHeaderPath = (value) => {
73
+ const v = String(value || '').trim().replace(/\s+\*\*\*$/g, '').trim();
74
+ if (!v)
75
+ return '';
76
+ const m = v.match(/^(?:a\/|b\/)?(.+)$/);
77
+ return (m && m[1] ? m[1] : v).trim();
78
+ };
79
+ const oldPath = normalizeHeaderPath(first.slice(4));
80
+ const newPath = normalizeHeaderPath(second.slice(4));
81
+ const body = lines.slice(2);
82
+ const out = ['*** Begin Patch'];
83
+ if (oldPath === '/dev/null' && newPath && newPath !== '/dev/null') {
84
+ out.push(`*** Add File: ${newPath}`);
85
+ for (const line of body) {
86
+ if (line.startsWith('+')) {
87
+ out.push(line);
88
+ }
89
+ }
90
+ out.push('*** End Patch');
91
+ return out.join('\n');
92
+ }
93
+ if (newPath === '/dev/null' && oldPath && oldPath !== '/dev/null') {
94
+ out.push(`*** Delete File: ${oldPath}`);
95
+ out.push('*** End Patch');
96
+ return out.join('\n');
97
+ }
98
+ const filePath = newPath || oldPath;
99
+ if (!filePath)
100
+ return null;
101
+ out.push(`*** Update File: ${filePath}`);
102
+ for (const line of body) {
103
+ out.push(line);
104
+ }
105
+ out.push('*** End Patch');
106
+ return out.join('\n');
107
+ }
108
+ catch {
109
+ return null;
110
+ }
111
+ };
112
+ const normalizeApplyPatchFileHeader = (line) => {
113
+ const addMatch = line.match(/^\*\*\* Add File:\s*(.+?)(?:\s+\*\*\*)?\s*$/);
114
+ if (addMatch && addMatch[1]) {
115
+ return `*** Add File: ${addMatch[1].trim()}`;
116
+ }
117
+ const updateMatch = line.match(/^\*\*\* Update File:\s*(.+?)(?:\s+\*\*\*)?\s*$/);
118
+ if (updateMatch && updateMatch[1]) {
119
+ return `*** Update File: ${updateMatch[1].trim()}`;
120
+ }
121
+ const deleteMatch = line.match(/^\*\*\* Delete File:\s*(.+?)(?:\s+\*\*\*)?\s*$/);
122
+ if (deleteMatch && deleteMatch[1]) {
123
+ return `*** Delete File: ${deleteMatch[1].trim()}`;
124
+ }
125
+ return line;
126
+ };
56
127
  const stripCodeFences = (text) => {
57
128
  const trimmed = text.trim();
58
129
  // Only treat the entire payload as fenced when it *starts* with a code fence.
@@ -152,7 +223,12 @@ export const normalizeApplyPatchText = (raw) => {
152
223
  if (converted)
153
224
  text = converted;
154
225
  }
155
- else if (!text.includes('*** Begin Patch') &&
226
+ else if (!text.includes('*** Begin Patch') && !text.includes('diff --git')) {
227
+ const converted = convertStarHeaderDiffToApplyPatchIfPossible(text);
228
+ if (converted)
229
+ text = converted;
230
+ }
231
+ if (!text.includes('*** Begin Patch') &&
156
232
  !text.includes('diff --git') &&
157
233
  text.includes('***************') &&
158
234
  /^\*\*\*\s+\S+/m.test(text) &&
@@ -172,9 +248,11 @@ export const normalizeApplyPatchText = (raw) => {
172
248
  if (converted)
173
249
  text = converted;
174
250
  }
175
- if (text.includes('*** Add File:')) {
176
- text = text.replace(/\*\*\* Create File:/g, '*** Add File:');
177
- }
251
+ text = text.replace(/\*\*\* Create File:/g, '*** Add File:');
252
+ text = text
253
+ .split('\n')
254
+ .map((line) => normalizeApplyPatchFileHeader(line))
255
+ .join('\n');
178
256
  let hasBegin = text.includes('*** Begin Patch');
179
257
  const hasEnd = text.includes('*** End Patch');
180
258
  if (hasBegin && !hasEnd) {
@@ -216,29 +294,41 @@ export const normalizeApplyPatchText = (raw) => {
216
294
  const lines = text.split('\n');
217
295
  const output = [];
218
296
  let inUpdateSection = false;
297
+ let inAddSection = false;
219
298
  let afterUpdateHeader = false;
220
299
  for (const line of lines) {
221
300
  if (line.startsWith('*** Begin Patch')) {
222
301
  output.push(line);
223
302
  inUpdateSection = false;
303
+ inAddSection = false;
224
304
  afterUpdateHeader = false;
225
305
  continue;
226
306
  }
227
307
  if (line.startsWith('*** End Patch')) {
228
308
  output.push(line);
229
309
  inUpdateSection = false;
310
+ inAddSection = false;
230
311
  afterUpdateHeader = false;
231
312
  continue;
232
313
  }
233
314
  if (line.startsWith('*** Update File:')) {
234
315
  output.push(line);
235
316
  inUpdateSection = true;
317
+ inAddSection = false;
236
318
  afterUpdateHeader = true;
237
319
  continue;
238
320
  }
239
- if (line.startsWith('*** Add File:') || line.startsWith('*** Delete File:')) {
321
+ if (line.startsWith('*** Add File:')) {
240
322
  output.push(line);
241
323
  inUpdateSection = false;
324
+ inAddSection = true;
325
+ afterUpdateHeader = false;
326
+ continue;
327
+ }
328
+ if (line.startsWith('*** Delete File:')) {
329
+ output.push(line);
330
+ inUpdateSection = false;
331
+ inAddSection = false;
242
332
  afterUpdateHeader = false;
243
333
  continue;
244
334
  }
@@ -255,6 +345,15 @@ export const normalizeApplyPatchText = (raw) => {
255
345
  }
256
346
  continue;
257
347
  }
348
+ if (inAddSection) {
349
+ if (line.startsWith('+')) {
350
+ output.push(line);
351
+ }
352
+ else {
353
+ output.push(`+${line}`);
354
+ }
355
+ continue;
356
+ }
258
357
  output.push(line);
259
358
  }
260
359
  return output.join('\n');
@@ -1,6 +1,22 @@
1
1
  import { isStructuredApplyPatchPayload } from '../structured.js';
2
2
  import { tryParseJson } from '../json/parse-loose.js';
3
3
  import { asString } from '../validation/shared.js';
4
+ const resolveTopLevelFile = (record) => {
5
+ const direct = asString(record.file) ??
6
+ asString(record.path) ??
7
+ asString(record.filepath) ??
8
+ asString(record.filename);
9
+ if (direct)
10
+ return direct;
11
+ const targetAlias = asString(record.target);
12
+ if (!targetAlias)
13
+ return undefined;
14
+ if (targetAlias.includes('\n') || targetAlias.includes('\r'))
15
+ return undefined;
16
+ if (!/[./\\]/.test(targetAlias))
17
+ return undefined;
18
+ return targetAlias;
19
+ };
4
20
  const buildSingleChangePayload = (record) => {
5
21
  const kindRaw = asString(record.kind);
6
22
  if (!kindRaw)
@@ -14,7 +30,11 @@ const buildSingleChangePayload = (record) => {
14
30
  if (typeof record.use_anchor_indent === 'boolean') {
15
31
  change.use_anchor_indent = record.use_anchor_indent;
16
32
  }
17
- const changeFile = asString(record.file);
33
+ const changeFile = asString(record.file) ??
34
+ asString(record.path) ??
35
+ asString(record.filepath) ??
36
+ asString(record.filename) ??
37
+ resolveTopLevelFile(record);
18
38
  if (changeFile) {
19
39
  change.file = changeFile;
20
40
  }
@@ -44,7 +64,11 @@ const coerceChangesArray = (value) => {
44
64
  return undefined;
45
65
  };
46
66
  export const coerceStructuredPayload = (record) => {
67
+ const topLevelFile = resolveTopLevelFile(record);
47
68
  if (isStructuredApplyPatchPayload(record)) {
69
+ if (topLevelFile && !asString(record.file)) {
70
+ return { ...record, file: topLevelFile };
71
+ }
48
72
  return record;
49
73
  }
50
74
  if (Array.isArray(record.changes) && record.changes.length === 0) {
@@ -55,7 +79,7 @@ export const coerceStructuredPayload = (record) => {
55
79
  const changesFromInstructions = coerceChangesArray(record.instructions);
56
80
  if (changesFromInstructions) {
57
81
  return {
58
- ...(typeof record.file === 'string' ? { file: record.file } : {}),
82
+ ...(topLevelFile ? { file: topLevelFile } : {}),
59
83
  changes: changesFromInstructions
60
84
  };
61
85
  }
@@ -63,14 +87,14 @@ export const coerceStructuredPayload = (record) => {
63
87
  const changesFromString = coerceChangesArray(record.changes);
64
88
  if (changesFromString) {
65
89
  return {
66
- ...(typeof record.file === 'string' ? { file: record.file } : {}),
90
+ ...(topLevelFile ? { file: topLevelFile } : {}),
67
91
  changes: changesFromString
68
92
  };
69
93
  }
70
94
  const editsFromString = coerceChangesArray(record.edits ?? record.operations ?? record.ops);
71
95
  if (editsFromString) {
72
96
  return {
73
- ...(typeof record.file === 'string' ? { file: record.file } : {}),
97
+ ...(topLevelFile ? { file: topLevelFile } : {}),
74
98
  changes: editsFromString
75
99
  };
76
100
  }