@ottocode/sdk 0.1.264 → 0.1.266
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -2
- package/src/config/src/index.ts +1 -0
- package/src/core/src/providers/resolver.ts +33 -64
- package/src/core/src/tools/bin-manager/cache.ts +13 -0
- package/src/core/src/tools/bin-manager/filesystem.ts +32 -0
- package/src/core/src/tools/bin-manager/paths.ts +36 -0
- package/src/core/src/tools/bin-manager/vendor.ts +80 -0
- package/src/core/src/tools/bin-manager.ts +14 -140
- package/src/core/src/tools/builtin/patch/apply-hunk.ts +308 -0
- package/src/core/src/tools/builtin/patch/apply-report.ts +99 -0
- package/src/core/src/tools/builtin/patch/apply.ts +6 -663
- package/src/core/src/tools/builtin/patch/hunk-header.ts +17 -0
- package/src/core/src/tools/builtin/patch/indentation.ts +160 -0
- package/src/core/src/tools/builtin/patch/matching.ts +58 -0
- package/src/core/src/tools/builtin/patch/parse-enveloped.ts +10 -72
- package/src/core/src/tools/builtin/patch/parse-unified.ts +15 -105
- package/src/core/src/tools/builtin/patch/replace-builder.ts +64 -0
- package/src/core/src/tools/builtin/patch/unified-state.ts +86 -0
- package/src/core/src/tools/builtin/websearch-strategies.ts +197 -0
- package/src/core/src/tools/builtin/websearch.ts +9 -187
- package/src/core/src/tools/loader.ts +6 -49
- package/src/core/src/tools/plugin-discovery.ts +86 -0
- package/src/core/src/utils/logger/format.ts +50 -0
- package/src/core/src/utils/logger/sinks.ts +61 -0
- package/src/core/src/utils/logger.ts +2 -119
- package/src/index.ts +4 -0
- package/src/providers/src/catalog.ts +1126 -220
- package/src/providers/src/env.ts +1 -0
- package/src/providers/src/index.ts +6 -0
- package/src/providers/src/model-resolution.ts +21 -0
- package/src/providers/src/pricing.ts +3 -0
- package/src/providers/src/registry.ts +2 -0
- package/src/providers/src/utils.ts +3 -0
- package/src/providers/src/xai-client.ts +15 -0
- package/src/providers/src/zai-client.ts +5 -2
- package/src/types/src/provider.ts +1 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import { adjustReplacementIndentation } from './indentation.ts';
|
|
2
|
+
import {
|
|
3
|
+
findLineIndex,
|
|
4
|
+
findSubsequence,
|
|
5
|
+
lineExists,
|
|
6
|
+
linesMatch,
|
|
7
|
+
} from './matching.ts';
|
|
8
|
+
import type { AppliedPatchHunk, PatchHunk } from './types.ts';
|
|
9
|
+
|
|
10
|
+
function computeInsertionIndex(
|
|
11
|
+
lines: string[],
|
|
12
|
+
header: PatchHunk['header'],
|
|
13
|
+
hint: number,
|
|
14
|
+
): number {
|
|
15
|
+
if (header.context) {
|
|
16
|
+
const contextIndex = findLineIndex(lines, header.context, 0, true);
|
|
17
|
+
if (contextIndex !== -1) return contextIndex + 1;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (typeof header.oldStart === 'number') {
|
|
21
|
+
const zeroBased = Math.max(0, header.oldStart - 1);
|
|
22
|
+
return Math.min(lines.length, zeroBased);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (typeof header.newStart === 'number') {
|
|
26
|
+
const zeroBased = Math.max(0, header.newStart - 1);
|
|
27
|
+
return Math.min(lines.length, zeroBased);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return Math.min(lines.length, Math.max(0, hint));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function isHunkAlreadyApplied(
|
|
34
|
+
lines: string[],
|
|
35
|
+
hunk: PatchHunk,
|
|
36
|
+
useFuzzy: boolean,
|
|
37
|
+
): boolean {
|
|
38
|
+
const replacement = hunk.lines
|
|
39
|
+
.filter((line) => line.kind !== 'remove')
|
|
40
|
+
.map((line) => line.content);
|
|
41
|
+
|
|
42
|
+
if (replacement.length > 0) {
|
|
43
|
+
return findSubsequence(lines, replacement, 0, useFuzzy) !== -1;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const removals = hunk.lines.filter((line) => line.kind === 'remove');
|
|
47
|
+
const _additions = hunk.lines
|
|
48
|
+
.filter((line) => line.kind === 'add')
|
|
49
|
+
.map((line) => line.content);
|
|
50
|
+
const _contextLines = hunk.lines
|
|
51
|
+
.filter((line) => line.kind === 'context')
|
|
52
|
+
.map((line) => line.content);
|
|
53
|
+
if (removals.length === 0) return false;
|
|
54
|
+
return removals.every((line) => !lineExists(lines, line.content, useFuzzy));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function applyHunkToLines(
|
|
58
|
+
lines: string[],
|
|
59
|
+
originalLines: string[],
|
|
60
|
+
hunk: PatchHunk,
|
|
61
|
+
hint: number,
|
|
62
|
+
useFuzzy: boolean,
|
|
63
|
+
): AppliedPatchHunk | null {
|
|
64
|
+
const expected = hunk.lines
|
|
65
|
+
.filter((line) => line.kind !== 'add')
|
|
66
|
+
.map((line) => line.content);
|
|
67
|
+
const replacement = hunk.lines
|
|
68
|
+
.filter((line) => line.kind !== 'remove')
|
|
69
|
+
.map((line) => line.content);
|
|
70
|
+
|
|
71
|
+
const removals = hunk.lines.filter((line) => line.kind === 'remove');
|
|
72
|
+
const additions = hunk.lines
|
|
73
|
+
.filter((line) => line.kind === 'add')
|
|
74
|
+
.map((line) => line.content);
|
|
75
|
+
const contextLines = hunk.lines
|
|
76
|
+
.filter((line) => line.kind === 'context')
|
|
77
|
+
.map((line) => line.content);
|
|
78
|
+
|
|
79
|
+
const hasExpected = expected.length > 0;
|
|
80
|
+
const initialHint =
|
|
81
|
+
typeof hunk.header.oldStart === 'number'
|
|
82
|
+
? Math.max(0, hunk.header.oldStart - 1)
|
|
83
|
+
: hint;
|
|
84
|
+
|
|
85
|
+
let matchIndex = hasExpected
|
|
86
|
+
? findSubsequence(lines, expected, Math.max(0, initialHint - 3), useFuzzy)
|
|
87
|
+
: -1;
|
|
88
|
+
let matchedExpected = expected;
|
|
89
|
+
|
|
90
|
+
if (hasExpected && matchIndex === -1) {
|
|
91
|
+
matchIndex = findSubsequence(lines, expected, 0, useFuzzy);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (matchIndex === -1 && removals.length > 0) {
|
|
95
|
+
const allContextPresent = contextLines.every((line) =>
|
|
96
|
+
lineExists(lines, line, useFuzzy),
|
|
97
|
+
);
|
|
98
|
+
if (!allContextPresent) {
|
|
99
|
+
matchIndex = -1;
|
|
100
|
+
} else {
|
|
101
|
+
const expectedWithoutMissingRemovals = hunk.lines
|
|
102
|
+
.filter((line) => {
|
|
103
|
+
if (line.kind === 'add') return false;
|
|
104
|
+
if (line.kind === 'remove') {
|
|
105
|
+
return lineExists(lines, line.content, useFuzzy);
|
|
106
|
+
}
|
|
107
|
+
return true;
|
|
108
|
+
})
|
|
109
|
+
.map((line) => line.content);
|
|
110
|
+
const includedRemovalCount = hunk.lines.filter(
|
|
111
|
+
(line) =>
|
|
112
|
+
line.kind === 'remove' && lineExists(lines, line.content, useFuzzy),
|
|
113
|
+
).length;
|
|
114
|
+
const minRequired = Math.max(contextLines.length, 2);
|
|
115
|
+
if (
|
|
116
|
+
includedRemovalCount > 0 &&
|
|
117
|
+
expectedWithoutMissingRemovals.length >= minRequired
|
|
118
|
+
) {
|
|
119
|
+
matchIndex = findSubsequence(
|
|
120
|
+
lines,
|
|
121
|
+
expectedWithoutMissingRemovals,
|
|
122
|
+
Math.max(0, initialHint - 3),
|
|
123
|
+
useFuzzy,
|
|
124
|
+
);
|
|
125
|
+
if (matchIndex === -1) {
|
|
126
|
+
matchIndex = findSubsequence(
|
|
127
|
+
lines,
|
|
128
|
+
expectedWithoutMissingRemovals,
|
|
129
|
+
0,
|
|
130
|
+
useFuzzy,
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
if (matchIndex !== -1) {
|
|
134
|
+
matchedExpected = expectedWithoutMissingRemovals;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (
|
|
141
|
+
matchIndex === -1 &&
|
|
142
|
+
useFuzzy &&
|
|
143
|
+
removals.length >= 2 &&
|
|
144
|
+
contextLines.length === 0
|
|
145
|
+
) {
|
|
146
|
+
const firstRemoval = removals[0].content;
|
|
147
|
+
const lastRemoval = removals[removals.length - 1].content;
|
|
148
|
+
const firstIdx = findLineIndex(lines, firstRemoval, 0, true);
|
|
149
|
+
if (firstIdx !== -1) {
|
|
150
|
+
const rangeEnd = firstIdx + expected.length - 1;
|
|
151
|
+
if (rangeEnd < lines.length) {
|
|
152
|
+
const lastInRange = lines[rangeEnd];
|
|
153
|
+
const lastMatches = linesMatch(lastInRange, lastRemoval, true);
|
|
154
|
+
if (lastMatches) {
|
|
155
|
+
let matchCount = 0;
|
|
156
|
+
for (let k = 0; k < expected.length; k++) {
|
|
157
|
+
const fileLine = lines[firstIdx + k];
|
|
158
|
+
const expLine = expected[k];
|
|
159
|
+
if (linesMatch(fileLine, expLine, true)) {
|
|
160
|
+
matchCount++;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const matchRatio = matchCount / expected.length;
|
|
164
|
+
if (matchRatio >= 0.5) {
|
|
165
|
+
matchIndex = firstIdx;
|
|
166
|
+
matchedExpected = lines.slice(firstIdx, firstIdx + expected.length);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (matchIndex === -1 && isHunkAlreadyApplied(lines, hunk, useFuzzy)) {
|
|
174
|
+
const skipStart =
|
|
175
|
+
initialHint >= 0 && initialHint < lines.length ? initialHint + 1 : 1;
|
|
176
|
+
return {
|
|
177
|
+
header: { ...hunk.header },
|
|
178
|
+
lines: hunk.lines.map((line) => ({ ...line })),
|
|
179
|
+
oldStart: skipStart,
|
|
180
|
+
oldLines: 0,
|
|
181
|
+
newStart: skipStart,
|
|
182
|
+
newLines: replacement.length,
|
|
183
|
+
additions: hunk.lines.filter((l) => l.kind === 'add').length,
|
|
184
|
+
deletions: hunk.lines.filter((l) => l.kind === 'remove').length,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (matchIndex === -1 && !hasExpected) {
|
|
189
|
+
matchIndex = computeInsertionIndex(lines, hunk.header, initialHint);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (matchIndex === -1) {
|
|
193
|
+
const contextInfo = hunk.header.context
|
|
194
|
+
? ` near context '${hunk.header.context}'`
|
|
195
|
+
: '';
|
|
196
|
+
|
|
197
|
+
if (additions.length > 0) {
|
|
198
|
+
const hasRemovals = removals.length > 0;
|
|
199
|
+
let anchorIndex = -1;
|
|
200
|
+
if (!hasRemovals && contextLines.length > 0) {
|
|
201
|
+
const anchorContext = contextLines[contextLines.length - 1];
|
|
202
|
+
anchorIndex = findLineIndex(lines, anchorContext, 0, useFuzzy);
|
|
203
|
+
} else if (!hasRemovals) {
|
|
204
|
+
anchorIndex = -1;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const insertionIndex =
|
|
208
|
+
anchorIndex !== -1
|
|
209
|
+
? anchorIndex + 1
|
|
210
|
+
: computeInsertionIndex(lines, hunk.header, initialHint);
|
|
211
|
+
|
|
212
|
+
if (
|
|
213
|
+
findSubsequence(
|
|
214
|
+
lines,
|
|
215
|
+
additions,
|
|
216
|
+
Math.max(0, insertionIndex - additions.length),
|
|
217
|
+
useFuzzy,
|
|
218
|
+
) !== -1
|
|
219
|
+
) {
|
|
220
|
+
const skipStart =
|
|
221
|
+
insertionIndex >= 0 && insertionIndex < lines.length
|
|
222
|
+
? insertionIndex + 1
|
|
223
|
+
: lines.length + 1;
|
|
224
|
+
return {
|
|
225
|
+
header: { ...hunk.header },
|
|
226
|
+
lines: hunk.lines.map((line) => ({ ...line })),
|
|
227
|
+
oldStart: skipStart,
|
|
228
|
+
oldLines: 0,
|
|
229
|
+
newStart: skipStart,
|
|
230
|
+
newLines: additions.length,
|
|
231
|
+
additions: additions.length,
|
|
232
|
+
deletions: 0,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
let errorMsg = `Failed to apply patch hunk${contextInfo}.`;
|
|
238
|
+
if (expected.length > 0) {
|
|
239
|
+
errorMsg += `\nExpected to find:\n${expected
|
|
240
|
+
.map((l) => ` ${l}`)
|
|
241
|
+
.join('\n')}`;
|
|
242
|
+
}
|
|
243
|
+
if (removals.length > 0) {
|
|
244
|
+
const missing = removals
|
|
245
|
+
.filter((line) => !lineExists(lines, line.content, useFuzzy))
|
|
246
|
+
.map((line) => line.content);
|
|
247
|
+
if (missing.length === removals.length) {
|
|
248
|
+
errorMsg +=
|
|
249
|
+
'\nAll removal lines already absent; consider reading the file again to capture current state.';
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
throw new Error(errorMsg);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const deleteCount = hasExpected ? matchedExpected.length : 0;
|
|
256
|
+
const originalIndex = matchIndex;
|
|
257
|
+
const oldStart = Math.min(
|
|
258
|
+
originalLines.length,
|
|
259
|
+
Math.max(0, originalIndex) + 1,
|
|
260
|
+
);
|
|
261
|
+
const newStart = matchIndex + 1;
|
|
262
|
+
|
|
263
|
+
const adjustedReplacement =
|
|
264
|
+
useFuzzy && hasExpected && matchedExpected.length === expected.length
|
|
265
|
+
? adjustReplacementIndentation(
|
|
266
|
+
hunk,
|
|
267
|
+
lines.slice(matchIndex, matchIndex + matchedExpected.length),
|
|
268
|
+
originalLines,
|
|
269
|
+
)
|
|
270
|
+
: replacement;
|
|
271
|
+
|
|
272
|
+
const targetSlice = lines.slice(
|
|
273
|
+
matchIndex,
|
|
274
|
+
matchIndex + adjustedReplacement.length,
|
|
275
|
+
);
|
|
276
|
+
if (
|
|
277
|
+
adjustedReplacement.length > 0 &&
|
|
278
|
+
adjustedReplacement.length === targetSlice.length &&
|
|
279
|
+
adjustedReplacement.every((line, i) => {
|
|
280
|
+
return linesMatch(line, targetSlice[i], useFuzzy);
|
|
281
|
+
})
|
|
282
|
+
) {
|
|
283
|
+
const skipStart = matchIndex + 1;
|
|
284
|
+
return {
|
|
285
|
+
header: { ...hunk.header },
|
|
286
|
+
lines: hunk.lines.map((line) => ({ ...line })),
|
|
287
|
+
oldStart: skipStart,
|
|
288
|
+
oldLines: 0,
|
|
289
|
+
newStart: skipStart,
|
|
290
|
+
newLines: adjustedReplacement.length,
|
|
291
|
+
additions: 0,
|
|
292
|
+
deletions: 0,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
lines.splice(matchIndex, deleteCount, ...adjustedReplacement);
|
|
297
|
+
|
|
298
|
+
return {
|
|
299
|
+
header: { ...hunk.header },
|
|
300
|
+
lines: hunk.lines.map((line) => ({ ...line })),
|
|
301
|
+
oldStart,
|
|
302
|
+
oldLines: deleteCount,
|
|
303
|
+
newStart,
|
|
304
|
+
newLines: adjustedReplacement.length,
|
|
305
|
+
additions: hunk.lines.filter((l) => l.kind === 'add').length,
|
|
306
|
+
deletions: hunk.lines.filter((l) => l.kind === 'remove').length,
|
|
307
|
+
};
|
|
308
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PATCH_ADD_PREFIX,
|
|
3
|
+
PATCH_BEGIN_MARKER,
|
|
4
|
+
PATCH_DELETE_PREFIX,
|
|
5
|
+
PATCH_END_MARKER,
|
|
6
|
+
PATCH_UPDATE_PREFIX,
|
|
7
|
+
} from './constants.ts';
|
|
8
|
+
import type {
|
|
9
|
+
AppliedPatchHunk,
|
|
10
|
+
AppliedPatchOperation,
|
|
11
|
+
PatchHunkLine,
|
|
12
|
+
PatchSummary,
|
|
13
|
+
} from './types.ts';
|
|
14
|
+
|
|
15
|
+
export function makeAppliedRecord(
|
|
16
|
+
kind: AppliedPatchOperation['kind'],
|
|
17
|
+
filePath: string,
|
|
18
|
+
hunks: AppliedPatchHunk[],
|
|
19
|
+
): AppliedPatchOperation {
|
|
20
|
+
const stats = hunks.reduce(
|
|
21
|
+
(acc, hunk) => ({
|
|
22
|
+
additions: acc.additions + hunk.additions,
|
|
23
|
+
deletions: acc.deletions + hunk.deletions,
|
|
24
|
+
}),
|
|
25
|
+
{ additions: 0, deletions: 0 },
|
|
26
|
+
);
|
|
27
|
+
return {
|
|
28
|
+
kind,
|
|
29
|
+
filePath,
|
|
30
|
+
stats,
|
|
31
|
+
hunks,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function makeSummary(operations: AppliedPatchOperation[]): PatchSummary {
|
|
36
|
+
return operations.reduce<PatchSummary>(
|
|
37
|
+
(acc, op) => ({
|
|
38
|
+
files: acc.files + 1,
|
|
39
|
+
additions: acc.additions + op.stats.additions,
|
|
40
|
+
deletions: acc.deletions + op.stats.deletions,
|
|
41
|
+
}),
|
|
42
|
+
{ files: 0, additions: 0, deletions: 0 },
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function formatNormalizedPatch(
|
|
47
|
+
operations: AppliedPatchOperation[],
|
|
48
|
+
): string {
|
|
49
|
+
const lines: string[] = [PATCH_BEGIN_MARKER];
|
|
50
|
+
for (const op of operations) {
|
|
51
|
+
switch (op.kind) {
|
|
52
|
+
case 'add':
|
|
53
|
+
lines.push(`${PATCH_ADD_PREFIX} ${op.filePath}`);
|
|
54
|
+
break;
|
|
55
|
+
case 'delete':
|
|
56
|
+
lines.push(`${PATCH_DELETE_PREFIX} ${op.filePath}`);
|
|
57
|
+
break;
|
|
58
|
+
case 'update':
|
|
59
|
+
lines.push(`${PATCH_UPDATE_PREFIX} ${op.filePath}`);
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
for (const hunk of op.hunks) {
|
|
64
|
+
lines.push(formatHunkHeader(hunk));
|
|
65
|
+
for (const line of hunk.lines) {
|
|
66
|
+
lines.push(serializePatchLine(line));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
lines.push(PATCH_END_MARKER);
|
|
71
|
+
return lines.join('\n');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function serializePatchLine(line: PatchHunkLine): string {
|
|
75
|
+
switch (line.kind) {
|
|
76
|
+
case 'add':
|
|
77
|
+
return `+${line.content}`;
|
|
78
|
+
case 'remove':
|
|
79
|
+
return `-${line.content}`;
|
|
80
|
+
default:
|
|
81
|
+
return ` ${line.content}`;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function formatRange(start: number, count: number) {
|
|
86
|
+
const normalizedStart = Math.max(0, start);
|
|
87
|
+
if (count === 0) return `${normalizedStart},0`;
|
|
88
|
+
if (count === 1) return `${normalizedStart}`;
|
|
89
|
+
return `${normalizedStart},${count}`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function formatHunkHeader(hunk: AppliedPatchHunk) {
|
|
93
|
+
const oldRange = formatRange(hunk.oldStart, hunk.oldLines);
|
|
94
|
+
const newRange = formatRange(hunk.newStart, hunk.newLines);
|
|
95
|
+
const context = hunk.header.context?.trim();
|
|
96
|
+
return context
|
|
97
|
+
? `@@ -${oldRange} +${newRange} @@ ${context}`
|
|
98
|
+
: `@@ -${oldRange} +${newRange} @@`;
|
|
99
|
+
}
|