@ottocode/sdk 0.1.265 → 0.1.267

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 (31) hide show
  1. package/package.json +2 -2
  2. package/src/config/src/index.ts +4 -0
  3. package/src/config/src/manager.ts +8 -14
  4. package/src/config/src/paths.ts +4 -0
  5. package/src/core/src/providers/resolver.ts +29 -70
  6. package/src/core/src/tools/bin-manager/cache.ts +13 -0
  7. package/src/core/src/tools/bin-manager/filesystem.ts +32 -0
  8. package/src/core/src/tools/bin-manager/paths.ts +36 -0
  9. package/src/core/src/tools/bin-manager/vendor.ts +80 -0
  10. package/src/core/src/tools/bin-manager.ts +14 -140
  11. package/src/core/src/tools/builtin/patch/apply-hunk.ts +308 -0
  12. package/src/core/src/tools/builtin/patch/apply-report.ts +99 -0
  13. package/src/core/src/tools/builtin/patch/apply.ts +6 -663
  14. package/src/core/src/tools/builtin/patch/hunk-header.ts +17 -0
  15. package/src/core/src/tools/builtin/patch/indentation.ts +160 -0
  16. package/src/core/src/tools/builtin/patch/matching.ts +58 -0
  17. package/src/core/src/tools/builtin/patch/parse-enveloped.ts +10 -72
  18. package/src/core/src/tools/builtin/patch/parse-unified.ts +15 -105
  19. package/src/core/src/tools/builtin/patch/replace-builder.ts +64 -0
  20. package/src/core/src/tools/builtin/patch/unified-state.ts +86 -0
  21. package/src/core/src/tools/builtin/websearch-strategies.ts +197 -0
  22. package/src/core/src/tools/builtin/websearch.ts +9 -187
  23. package/src/core/src/tools/loader.ts +6 -49
  24. package/src/core/src/tools/plugin-discovery.ts +86 -0
  25. package/src/core/src/utils/logger/format.ts +50 -0
  26. package/src/core/src/utils/logger/sinks.ts +61 -0
  27. package/src/core/src/utils/logger.ts +2 -119
  28. package/src/index.ts +3 -0
  29. package/src/providers/src/index.ts +4 -0
  30. package/src/providers/src/model-resolution.ts +21 -0
  31. package/src/providers/src/zai-client.ts +5 -2
@@ -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
+ }