@bubblebrain-ai/bubble 0.0.16 → 0.0.18
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/dist/agent/internal-reminder-sanitizer.d.ts +2 -0
- package/dist/agent/internal-reminder-sanitizer.js +27 -0
- package/dist/agent/tool-intent.js +0 -1
- package/dist/agent.d.ts +1 -0
- package/dist/agent.js +148 -23
- package/dist/context/budget.js +15 -0
- package/dist/context/prune.d.ts +1 -0
- package/dist/context/prune.js +32 -0
- package/dist/debug-trace.js +14 -0
- package/dist/feishu/agent-host/run-driver.js +2 -2
- package/dist/feishu/card/run-state.js +1 -0
- package/dist/feishu/serve.js +1 -0
- package/dist/main.js +13 -9
- package/dist/model-catalog.d.ts +3 -0
- package/dist/model-catalog.js +38 -0
- package/dist/model-config.d.ts +3 -0
- package/dist/model-config.js +3 -0
- package/dist/model-pricing.js +2 -1
- package/dist/model-selection.d.ts +7 -0
- package/dist/model-selection.js +9 -0
- package/dist/network/chatgpt-transport.js +1 -0
- package/dist/orchestrator/default-hooks.js +1 -1
- package/dist/prompt/compose.js +1 -1
- package/dist/prompt/environment.js +1 -3
- package/dist/prompt/reminders.js +3 -3
- package/dist/prompt/runtime.js +2 -1
- package/dist/provider-anthropic.d.ts +89 -0
- package/dist/provider-anthropic.js +597 -0
- package/dist/provider-openai-codex.js +3 -1
- package/dist/provider-registry.d.ts +2 -0
- package/dist/provider-registry.js +29 -3
- package/dist/provider-transform.d.ts +1 -1
- package/dist/provider-transform.js +14 -0
- package/dist/provider.d.ts +4 -1
- package/dist/provider.js +120 -41
- package/dist/session-log.js +14 -2
- package/dist/session-title.js +3 -6
- package/dist/slash-commands/commands.js +8 -2
- package/dist/stats/usage.d.ts +1 -0
- package/dist/stats/usage.js +28 -3
- package/dist/tools/edit.js +75 -1
- package/dist/tools/glob.js +77 -12
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.js +1 -3
- package/dist/tools/prompt-metadata.d.ts +3 -0
- package/dist/tools/prompt-metadata.js +17 -0
- package/dist/tools/write.js +14 -0
- package/dist/tui/paste-placeholder.d.ts +10 -0
- package/dist/tui/paste-placeholder.js +45 -0
- package/dist/tui/run.js +23 -0
- package/dist/tui-ink/app.js +2 -0
- package/dist/tui-ink/input-box.d.ts +1 -8
- package/dist/tui-ink/input-box.js +8 -38
- package/dist/tui-opentui/app.js +2 -0
- package/dist/tui-opentui/input-box.d.ts +1 -3
- package/dist/tui-opentui/input-box.js +17 -26
- package/dist/types.d.ts +22 -0
- package/package.json +7 -3
- package/dist/tools/apply-patch.d.ts +0 -9
- package/dist/tools/apply-patch.js +0 -330
- package/dist/tools/patch-apply.d.ts +0 -41
- package/dist/tools/patch-apply.js +0 -312
|
@@ -1,312 +0,0 @@
|
|
|
1
|
-
export class PatchApplyError extends Error {
|
|
2
|
-
status;
|
|
3
|
-
constructor(message, status = "no_match") {
|
|
4
|
-
super(message);
|
|
5
|
-
this.status = status;
|
|
6
|
-
this.name = "PatchApplyError";
|
|
7
|
-
}
|
|
8
|
-
}
|
|
9
|
-
const CHANGE_MARKERS = [
|
|
10
|
-
"*** Add File: ",
|
|
11
|
-
"*** Delete File: ",
|
|
12
|
-
"*** Update File: ",
|
|
13
|
-
"*** End Patch",
|
|
14
|
-
];
|
|
15
|
-
export function parseApplyPatch(patchText) {
|
|
16
|
-
const lines = normalizeToLF(patchText).split("\n");
|
|
17
|
-
if (lines[lines.length - 1] === "")
|
|
18
|
-
lines.pop();
|
|
19
|
-
if (lines[0] !== "*** Begin Patch") {
|
|
20
|
-
throw new PatchApplyError("Error: apply_patch must start with *** Begin Patch", "blocked");
|
|
21
|
-
}
|
|
22
|
-
const operations = [];
|
|
23
|
-
let index = 1;
|
|
24
|
-
while (index < lines.length) {
|
|
25
|
-
const line = lines[index];
|
|
26
|
-
if (line === "*** End Patch") {
|
|
27
|
-
if (index !== lines.length - 1) {
|
|
28
|
-
throw new PatchApplyError("Error: Unexpected content after *** End Patch", "blocked");
|
|
29
|
-
}
|
|
30
|
-
if (operations.length === 0) {
|
|
31
|
-
throw new PatchApplyError("Error: apply_patch rejected an empty patch", "blocked");
|
|
32
|
-
}
|
|
33
|
-
return { operations };
|
|
34
|
-
}
|
|
35
|
-
if (line.startsWith("*** Add File: ")) {
|
|
36
|
-
const path = parseMarkerPath(line, "*** Add File: ");
|
|
37
|
-
index++;
|
|
38
|
-
const addLines = [];
|
|
39
|
-
while (index < lines.length && !isFileMarker(lines[index])) {
|
|
40
|
-
const current = lines[index];
|
|
41
|
-
if (!current.startsWith("+")) {
|
|
42
|
-
throw new PatchApplyError(`Error: Add File ${path} contains a non-added line: ${current}`, "blocked");
|
|
43
|
-
}
|
|
44
|
-
addLines.push(current.slice(1));
|
|
45
|
-
index++;
|
|
46
|
-
}
|
|
47
|
-
operations.push({ type: "add", path, lines: addLines });
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
if (line.startsWith("*** Delete File: ")) {
|
|
51
|
-
const path = parseMarkerPath(line, "*** Delete File: ");
|
|
52
|
-
operations.push({ type: "delete", path });
|
|
53
|
-
index++;
|
|
54
|
-
continue;
|
|
55
|
-
}
|
|
56
|
-
if (line.startsWith("*** Update File: ")) {
|
|
57
|
-
const path = parseMarkerPath(line, "*** Update File: ");
|
|
58
|
-
index++;
|
|
59
|
-
let movePath;
|
|
60
|
-
const chunks = [];
|
|
61
|
-
while (index < lines.length && !isFileMarker(lines[index])) {
|
|
62
|
-
const current = lines[index];
|
|
63
|
-
if (current.startsWith("*** Move to: ")) {
|
|
64
|
-
if (movePath || chunks.length > 0) {
|
|
65
|
-
throw new PatchApplyError(`Error: Move marker for ${path} must appear before update chunks`, "blocked");
|
|
66
|
-
}
|
|
67
|
-
movePath = parseMarkerPath(current, "*** Move to: ");
|
|
68
|
-
index++;
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
if (!current.startsWith("@@")) {
|
|
72
|
-
throw new PatchApplyError(`Error: Update File ${path} expected @@ hunk header, got: ${current}`, "blocked");
|
|
73
|
-
}
|
|
74
|
-
const header = current;
|
|
75
|
-
index++;
|
|
76
|
-
const chunkLines = [];
|
|
77
|
-
while (index < lines.length && !isFileMarker(lines[index]) && !lines[index].startsWith("@@")) {
|
|
78
|
-
const patchLine = lines[index];
|
|
79
|
-
if (patchLine.startsWith("\")) {
|
|
80
|
-
index++;
|
|
81
|
-
continue;
|
|
82
|
-
}
|
|
83
|
-
const prefix = patchLine[0];
|
|
84
|
-
const text = patchLine.slice(1);
|
|
85
|
-
if (prefix === " ") {
|
|
86
|
-
chunkLines.push({ kind: "context", text });
|
|
87
|
-
}
|
|
88
|
-
else if (prefix === "-") {
|
|
89
|
-
chunkLines.push({ kind: "remove", text });
|
|
90
|
-
}
|
|
91
|
-
else if (prefix === "+") {
|
|
92
|
-
chunkLines.push({ kind: "add", text });
|
|
93
|
-
}
|
|
94
|
-
else {
|
|
95
|
-
throw new PatchApplyError(`Error: Hunk for ${path} contains invalid line: ${patchLine}`, "blocked");
|
|
96
|
-
}
|
|
97
|
-
index++;
|
|
98
|
-
}
|
|
99
|
-
if (chunkLines.length === 0) {
|
|
100
|
-
throw new PatchApplyError(`Error: Empty hunk in ${path}`, "blocked");
|
|
101
|
-
}
|
|
102
|
-
chunks.push({ header, lines: chunkLines });
|
|
103
|
-
}
|
|
104
|
-
if (!movePath && chunks.length === 0) {
|
|
105
|
-
throw new PatchApplyError(`Error: Update File ${path} has no hunks`, "blocked");
|
|
106
|
-
}
|
|
107
|
-
operations.push({ type: "update", path, ...(movePath ? { movePath } : {}), chunks });
|
|
108
|
-
continue;
|
|
109
|
-
}
|
|
110
|
-
throw new PatchApplyError(`Error: Unexpected patch marker: ${line}`, "blocked");
|
|
111
|
-
}
|
|
112
|
-
throw new PatchApplyError("Error: apply_patch must end with *** End Patch", "blocked");
|
|
113
|
-
}
|
|
114
|
-
export function buildAddedFileContent(lines) {
|
|
115
|
-
if (lines.length === 0)
|
|
116
|
-
return "";
|
|
117
|
-
return `${lines.join("\n")}\n`;
|
|
118
|
-
}
|
|
119
|
-
export function applyPatchChunks(rawContent, chunks, path) {
|
|
120
|
-
const { bom, text } = stripBom(rawContent);
|
|
121
|
-
const lineEnding = detectLineEnding(text);
|
|
122
|
-
let normalized = normalizeToLF(text);
|
|
123
|
-
let usedFallback = false;
|
|
124
|
-
for (let index = 0; index < chunks.length; index++) {
|
|
125
|
-
const result = applyChunk(normalized, chunks[index], path, index);
|
|
126
|
-
normalized = result.content;
|
|
127
|
-
usedFallback ||= result.usedFallback;
|
|
128
|
-
}
|
|
129
|
-
return {
|
|
130
|
-
content: bom + restoreLineEndings(normalized, lineEnding),
|
|
131
|
-
usedFallback,
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
function applyChunk(content, chunk, path, chunkIndex) {
|
|
135
|
-
const oldLines = chunk.lines
|
|
136
|
-
.filter((line) => line.kind === "context" || line.kind === "remove")
|
|
137
|
-
.map((line) => line.text);
|
|
138
|
-
const newLines = chunk.lines
|
|
139
|
-
.filter((line) => line.kind === "context" || line.kind === "add")
|
|
140
|
-
.map((line) => line.text);
|
|
141
|
-
if (oldLines.length === 0) {
|
|
142
|
-
throw new PatchApplyError(`Error: Hunk ${chunkIndex + 1} in ${path} has no context to locate an insertion.`, "blocked");
|
|
143
|
-
}
|
|
144
|
-
const exactMatches = findExactLineBlockMatches(content, oldLines);
|
|
145
|
-
if (exactMatches.length === 1) {
|
|
146
|
-
return {
|
|
147
|
-
content: replaceSpan(content, exactMatches[0], newLines),
|
|
148
|
-
usedFallback: false,
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
if (exactMatches.length > 1) {
|
|
152
|
-
throw new PatchApplyError(`Error: Hunk ${chunkIndex + 1} in ${path} matched ${exactMatches.length} exact locations. Add more context.`, "blocked");
|
|
153
|
-
}
|
|
154
|
-
const fallbackMatches = findNormalizedLineBlockMatches(content, oldLines, path);
|
|
155
|
-
if (fallbackMatches.length === 1) {
|
|
156
|
-
return {
|
|
157
|
-
content: replaceSpan(content, fallbackMatches[0], newLines),
|
|
158
|
-
usedFallback: true,
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
if (fallbackMatches.length > 1) {
|
|
162
|
-
throw new PatchApplyError(`Error: Hunk ${chunkIndex + 1} in ${path} matched ${fallbackMatches.length} normalized locations. Add more context.`, "blocked");
|
|
163
|
-
}
|
|
164
|
-
throw new PatchApplyError(`Error: Hunk ${chunkIndex + 1} in ${path} did not match the file. Re-read the file and regenerate the patch.`);
|
|
165
|
-
}
|
|
166
|
-
function parseMarkerPath(line, marker) {
|
|
167
|
-
const path = line.slice(marker.length).trim();
|
|
168
|
-
if (!path)
|
|
169
|
-
throw new PatchApplyError(`Error: Patch marker is missing a path: ${line}`, "blocked");
|
|
170
|
-
return path;
|
|
171
|
-
}
|
|
172
|
-
function isFileMarker(line) {
|
|
173
|
-
return CHANGE_MARKERS.some((marker) => line === marker || line.startsWith(marker));
|
|
174
|
-
}
|
|
175
|
-
function detectLineEnding(content) {
|
|
176
|
-
const crlf = content.indexOf("\r\n");
|
|
177
|
-
const lf = content.indexOf("\n");
|
|
178
|
-
return crlf !== -1 && crlf === lf - 1 ? "\r\n" : "\n";
|
|
179
|
-
}
|
|
180
|
-
function stripBom(content) {
|
|
181
|
-
return content.startsWith("\uFEFF") ? { bom: "\uFEFF", text: content.slice(1) } : { bom: "", text: content };
|
|
182
|
-
}
|
|
183
|
-
function normalizeToLF(text) {
|
|
184
|
-
return text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
185
|
-
}
|
|
186
|
-
function restoreLineEndings(text, lineEnding) {
|
|
187
|
-
return lineEnding === "\r\n" ? text.replace(/\n/g, "\r\n") : text;
|
|
188
|
-
}
|
|
189
|
-
function splitLines(content) {
|
|
190
|
-
const lines = [];
|
|
191
|
-
let start = 0;
|
|
192
|
-
for (let index = 0; index < content.length; index++) {
|
|
193
|
-
if (content[index] === "\n") {
|
|
194
|
-
lines.push({ text: content.slice(start, index), start, endNoNewline: index });
|
|
195
|
-
start = index + 1;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
lines.push({ text: content.slice(start), start, endNoNewline: content.length });
|
|
199
|
-
return lines;
|
|
200
|
-
}
|
|
201
|
-
function findExactLineBlockMatches(content, oldLines) {
|
|
202
|
-
const lines = splitLines(content);
|
|
203
|
-
const matches = [];
|
|
204
|
-
for (let index = 0; index <= lines.length - oldLines.length; index++) {
|
|
205
|
-
let matched = true;
|
|
206
|
-
for (let offset = 0; offset < oldLines.length; offset++) {
|
|
207
|
-
if (lines[index + offset].text !== oldLines[offset]) {
|
|
208
|
-
matched = false;
|
|
209
|
-
break;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
if (matched) {
|
|
213
|
-
matches.push({
|
|
214
|
-
start: lines[index].start,
|
|
215
|
-
end: lines[index + oldLines.length - 1].endNoNewline,
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
return matches;
|
|
220
|
-
}
|
|
221
|
-
function findNormalizedLineBlockMatches(content, oldLines, path) {
|
|
222
|
-
const expected = oldLines
|
|
223
|
-
.map((line) => normalizeLineForMatch(line))
|
|
224
|
-
.filter((line) => line.trim().length > 0);
|
|
225
|
-
if (expected.length === 0)
|
|
226
|
-
return [];
|
|
227
|
-
const contentLines = splitLines(content)
|
|
228
|
-
.map((line) => ({ line, normalized: normalizeLineForMatch(line.text) }))
|
|
229
|
-
.filter((item) => item.normalized.trim().length > 0);
|
|
230
|
-
const matches = [];
|
|
231
|
-
for (let index = 0; index <= contentLines.length - expected.length; index++) {
|
|
232
|
-
let matched = true;
|
|
233
|
-
for (let offset = 0; offset < expected.length; offset++) {
|
|
234
|
-
if (!lineEquivalent(contentLines[index + offset].line.text, expected[offset], path, expected.length)) {
|
|
235
|
-
matched = false;
|
|
236
|
-
break;
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
if (matched) {
|
|
240
|
-
matches.push({
|
|
241
|
-
start: contentLines[index].line.start,
|
|
242
|
-
end: contentLines[index + expected.length - 1].line.endNoNewline,
|
|
243
|
-
});
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
return matches;
|
|
247
|
-
}
|
|
248
|
-
function normalizeLineForMatch(line) {
|
|
249
|
-
return line
|
|
250
|
-
.normalize("NFKC")
|
|
251
|
-
.trimEnd()
|
|
252
|
-
.replace(/[\u2018\u2019\u201A\u201B]/g, "'")
|
|
253
|
-
.replace(/[\u201C\u201D\u201E\u201F]/g, '"')
|
|
254
|
-
.replace(/[\u2010\u2011\u2012\u2013\u2014\u2015\u2212]/g, "-")
|
|
255
|
-
.replace(/[\u00A0\u2002-\u200A\u202F\u205F\u3000]/g, " ");
|
|
256
|
-
}
|
|
257
|
-
function lineEquivalent(actual, expectedNormalized, path, expectedLineCount) {
|
|
258
|
-
const actualNormalized = normalizeLineForMatch(actual);
|
|
259
|
-
if (actualNormalized === expectedNormalized)
|
|
260
|
-
return true;
|
|
261
|
-
const actualCells = splitMarkdownTableCells(actualNormalized);
|
|
262
|
-
const expectedCells = splitMarkdownTableCells(expectedNormalized);
|
|
263
|
-
if (actualCells && expectedCells && sameCells(actualCells, expectedCells))
|
|
264
|
-
return true;
|
|
265
|
-
if (expectedLineCount === 1 && isDocumentLikePath(path)) {
|
|
266
|
-
return collapseInlineWhitespace(actualNormalized) === collapseInlineWhitespace(expectedNormalized);
|
|
267
|
-
}
|
|
268
|
-
return false;
|
|
269
|
-
}
|
|
270
|
-
function splitMarkdownTableCells(line) {
|
|
271
|
-
const normalized = line.trim();
|
|
272
|
-
if (!normalized.startsWith("|") || !normalized.endsWith("|"))
|
|
273
|
-
return undefined;
|
|
274
|
-
const parts = [];
|
|
275
|
-
let current = "";
|
|
276
|
-
let escaped = false;
|
|
277
|
-
for (const char of normalized) {
|
|
278
|
-
if (escaped) {
|
|
279
|
-
current += char;
|
|
280
|
-
escaped = false;
|
|
281
|
-
continue;
|
|
282
|
-
}
|
|
283
|
-
if (char === "\\") {
|
|
284
|
-
current += char;
|
|
285
|
-
escaped = true;
|
|
286
|
-
continue;
|
|
287
|
-
}
|
|
288
|
-
if (char === "|") {
|
|
289
|
-
parts.push(current);
|
|
290
|
-
current = "";
|
|
291
|
-
continue;
|
|
292
|
-
}
|
|
293
|
-
current += char;
|
|
294
|
-
}
|
|
295
|
-
parts.push(current);
|
|
296
|
-
if (parts.length < 4 || parts[0] !== "" || parts[parts.length - 1] !== "")
|
|
297
|
-
return undefined;
|
|
298
|
-
const cells = parts.slice(1, -1).map((cell) => cell.trim());
|
|
299
|
-
return cells.length >= 2 ? cells : undefined;
|
|
300
|
-
}
|
|
301
|
-
function sameCells(a, b) {
|
|
302
|
-
return a.length === b.length && a.every((cell, index) => cell === b[index]);
|
|
303
|
-
}
|
|
304
|
-
function collapseInlineWhitespace(text) {
|
|
305
|
-
return text.trim().replace(/[ \t]+/g, " ");
|
|
306
|
-
}
|
|
307
|
-
function isDocumentLikePath(path) {
|
|
308
|
-
return /\.(?:md|mdx|markdown|txt|rst|adoc)$/i.test(path);
|
|
309
|
-
}
|
|
310
|
-
function replaceSpan(content, span, newLines) {
|
|
311
|
-
return content.slice(0, span.start) + newLines.join("\n") + content.slice(span.end);
|
|
312
|
-
}
|