@bubblebrain-ai/bubble 0.0.17 → 0.0.19
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/tool-intent.js +0 -1
- package/dist/agent.d.ts +1 -0
- package/dist/agent.js +54 -21
- package/dist/context/prune.d.ts +1 -0
- package/dist/context/prune.js +32 -0
- package/dist/feishu/agent-host/run-driver.js +2 -2
- package/dist/feishu/card/run-state.js +1 -0
- package/dist/main.js +11 -9
- 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.d.ts +1 -0
- package/dist/network/chatgpt-transport.js +123 -16
- package/dist/orchestrator/default-hooks.js +1 -1
- package/dist/prompt/environment.js +1 -3
- package/dist/prompt/runtime.js +1 -1
- package/dist/provider-anthropic.d.ts +15 -3
- package/dist/provider-anthropic.js +55 -2
- package/dist/provider-openai-codex.js +3 -1
- package/dist/provider.js +1 -1
- package/dist/session-title.js +3 -6
- package/dist/slash-commands/commands.js +4 -0
- 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 +9 -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
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const LONG_PASTE_CHAR_THRESHOLD = 1000;
|
|
2
|
+
export declare const LONG_PASTE_LINE_THRESHOLD = 20;
|
|
3
|
+
export interface PastedContentReference {
|
|
4
|
+
marker: string;
|
|
5
|
+
content: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function countTextLines(text: string): number;
|
|
8
|
+
export declare function shouldCollapsePastedContent(text: string): boolean;
|
|
9
|
+
export declare function createPastedContentMarker(content: string, index?: number): string;
|
|
10
|
+
export declare function expandPastedContentMarkers(displayText: string, references: PastedContentReference[]): string;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export const LONG_PASTE_CHAR_THRESHOLD = 1000;
|
|
2
|
+
export const LONG_PASTE_LINE_THRESHOLD = 20;
|
|
3
|
+
export function countTextLines(text) {
|
|
4
|
+
return text.length === 0 ? 0 : text.split(/\r?\n/).length;
|
|
5
|
+
}
|
|
6
|
+
export function shouldCollapsePastedContent(text) {
|
|
7
|
+
if (text.length >= LONG_PASTE_CHAR_THRESHOLD)
|
|
8
|
+
return true;
|
|
9
|
+
return countTextLines(text) >= LONG_PASTE_LINE_THRESHOLD;
|
|
10
|
+
}
|
|
11
|
+
export function createPastedContentMarker(content, index = 1) {
|
|
12
|
+
const safeIndex = Math.max(1, Math.floor(index));
|
|
13
|
+
const lineCount = countTextLines(content);
|
|
14
|
+
const size = lineCount > 1
|
|
15
|
+
? `${lineCount} ${lineCount === 1 ? "line" : "lines"}`
|
|
16
|
+
: `${content.length} ${content.length === 1 ? "char" : "chars"}`;
|
|
17
|
+
return `[Pasted text #${safeIndex} +${size}]`;
|
|
18
|
+
}
|
|
19
|
+
export function expandPastedContentMarkers(displayText, references) {
|
|
20
|
+
if (references.length === 0 || displayText.length === 0)
|
|
21
|
+
return displayText;
|
|
22
|
+
let expanded = "";
|
|
23
|
+
let index = 0;
|
|
24
|
+
const used = new Set();
|
|
25
|
+
while (index < displayText.length) {
|
|
26
|
+
let matched = -1;
|
|
27
|
+
for (let i = 0; i < references.length; i++) {
|
|
28
|
+
const ref = references[i];
|
|
29
|
+
if (!used.has(i) && displayText.startsWith(ref.marker, index)) {
|
|
30
|
+
matched = i;
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (matched >= 0) {
|
|
35
|
+
const ref = references[matched];
|
|
36
|
+
expanded += ref.content;
|
|
37
|
+
index += ref.marker.length;
|
|
38
|
+
used.add(matched);
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
expanded += displayText[index];
|
|
42
|
+
index += 1;
|
|
43
|
+
}
|
|
44
|
+
return expanded;
|
|
45
|
+
}
|
package/dist/tui/run.js
CHANGED
|
@@ -480,6 +480,7 @@ function OpenTuiApp(props) {
|
|
|
480
480
|
completionTokens: 0,
|
|
481
481
|
promptCacheHitTokens: 0,
|
|
482
482
|
promptCacheMissTokens: 0,
|
|
483
|
+
cacheCreationTokens: 0,
|
|
483
484
|
reasoningTokens: 0,
|
|
484
485
|
turns: 0,
|
|
485
486
|
});
|
|
@@ -602,6 +603,7 @@ function OpenTuiApp(props) {
|
|
|
602
603
|
let sidebarGaugeText;
|
|
603
604
|
let sidebarGaugeLabelText;
|
|
604
605
|
let sidebarUsageText;
|
|
606
|
+
let sidebarCacheText;
|
|
605
607
|
let sidebarReasoningText;
|
|
606
608
|
let sidebarCostText;
|
|
607
609
|
let sidebarLspSummaryText;
|
|
@@ -956,6 +958,7 @@ function OpenTuiApp(props) {
|
|
|
956
958
|
setSidebarText(sidebarUsageText, context.turns > 0
|
|
957
959
|
? `${formatCompactNumber(context.promptTokens)} in · ${formatCompactNumber(context.completionTokens)} out`
|
|
958
960
|
: "usage pending");
|
|
961
|
+
setSidebarText(sidebarCacheText, context.cacheText);
|
|
959
962
|
setSidebarText(sidebarReasoningText, context.reasoningTokens > 0
|
|
960
963
|
? `${formatCompactNumber(context.reasoningTokens)} reasoning`
|
|
961
964
|
: "");
|
|
@@ -5341,6 +5344,7 @@ function OpenTuiApp(props) {
|
|
|
5341
5344
|
promptCacheHitTokens: current.promptCacheHitTokens + (event.usage.promptCacheHitTokens ?? 0),
|
|
5342
5345
|
promptCacheMissTokens: current.promptCacheMissTokens + (event.usage.promptCacheMissTokens
|
|
5343
5346
|
?? (event.usage.promptCacheHitTokens === undefined ? event.usage.promptTokens : 0)),
|
|
5347
|
+
cacheCreationTokens: current.cacheCreationTokens + (event.usage.cacheCreationTokens ?? 0),
|
|
5344
5348
|
reasoningTokens: current.reasoningTokens + (event.usage.reasoningTokens ?? 0),
|
|
5345
5349
|
turns: current.turns + 1,
|
|
5346
5350
|
}));
|
|
@@ -6368,6 +6372,13 @@ function OpenTuiApp(props) {
|
|
|
6368
6372
|
: "usage pending";
|
|
6369
6373
|
},
|
|
6370
6374
|
}),
|
|
6375
|
+
h("text", {
|
|
6376
|
+
fg: theme.textMuted,
|
|
6377
|
+
ref: (ref) => {
|
|
6378
|
+
sidebarCacheText = ref;
|
|
6379
|
+
ref.content = context.cacheText;
|
|
6380
|
+
},
|
|
6381
|
+
}),
|
|
6371
6382
|
h("text", {
|
|
6372
6383
|
fg: theme.textMuted,
|
|
6373
6384
|
ref: (ref) => {
|
|
@@ -6573,16 +6584,28 @@ function OpenTuiApp(props) {
|
|
|
6573
6584
|
completionTokens: usage.completionTokens,
|
|
6574
6585
|
promptCacheHitTokens: usage.promptCacheHitTokens,
|
|
6575
6586
|
promptCacheMissTokens: usage.promptCacheMissTokens,
|
|
6587
|
+
cacheCreationTokens: usage.cacheCreationTokens,
|
|
6576
6588
|
reasoningTokens: usage.reasoningTokens,
|
|
6577
6589
|
totalTokens: usage.promptTokens + usage.completionTokens,
|
|
6578
6590
|
};
|
|
6579
6591
|
const cost = providerId && modelId ? calculateUsageCost(providerId, modelId, tokenUsage) : undefined;
|
|
6592
|
+
const cacheReadTokens = usage.promptCacheHitTokens;
|
|
6593
|
+
const cacheCreateTokens = usage.cacheCreationTokens;
|
|
6594
|
+
const cacheMissTokens = Math.max(0, usage.promptCacheMissTokens - cacheCreateTokens);
|
|
6595
|
+
const cacheObservedTokens = cacheReadTokens + cacheCreateTokens + cacheMissTokens;
|
|
6596
|
+
const cacheHitRate = cacheObservedTokens > 0
|
|
6597
|
+
? Math.round((cacheReadTokens / cacheObservedTokens) * 100)
|
|
6598
|
+
: 0;
|
|
6599
|
+
const cacheText = cacheObservedTokens > 0
|
|
6600
|
+
? `cache ${formatCompactNumber(cacheReadTokens)} read · ${formatCompactNumber(cacheCreateTokens)} create · ${formatCompactNumber(cacheMissTokens)} miss · ${cacheHitRate}% hit`
|
|
6601
|
+
: "";
|
|
6580
6602
|
return {
|
|
6581
6603
|
tokens: contextTokens,
|
|
6582
6604
|
percent: contextPercent,
|
|
6583
6605
|
remainingTokens,
|
|
6584
6606
|
promptTokens: usage.promptTokens,
|
|
6585
6607
|
completionTokens: usage.completionTokens,
|
|
6608
|
+
cacheText,
|
|
6586
6609
|
reasoningTokens: usage.reasoningTokens,
|
|
6587
6610
|
turns: usage.turns,
|
|
6588
6611
|
costText: cost ? `${formatCurrency(cost.cost, cost.currency)} spent${cost.estimated ? " est." : ""}` : "cost unavailable",
|
package/dist/tui-ink/app.js
CHANGED
|
@@ -501,6 +501,7 @@ export function App({ agent, args, sessionManager, createProvider, registry, ski
|
|
|
501
501
|
thinkingLevel: overrides?.thinkingLevel ?? agent.thinking,
|
|
502
502
|
mode: overrides?.mode ?? agent.mode,
|
|
503
503
|
workingDir: args.cwd,
|
|
504
|
+
...agent.getSystemPromptToolOptions(),
|
|
504
505
|
}));
|
|
505
506
|
}, [agent, args.cwd, safeRegistry, safeSkillRegistry]);
|
|
506
507
|
useInput((input, key) => {
|
|
@@ -614,6 +615,7 @@ export function App({ agent, args, sessionManager, createProvider, registry, ski
|
|
|
614
615
|
configuredModelId: model,
|
|
615
616
|
thinkingLevel: agent.thinking,
|
|
616
617
|
workingDir: args.cwd,
|
|
618
|
+
...agent.getSystemPromptToolOptions(),
|
|
617
619
|
}));
|
|
618
620
|
userConfig.pushRecentModel(model);
|
|
619
621
|
setThinkingLevel(agent.thinking);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { SkillRegistry } from "../skills/registry.js";
|
|
2
2
|
import { type ImageAttachment } from "./image-paste.js";
|
|
3
|
+
export { createPastedContentMarker, expandPastedContentMarkers, shouldCollapsePastedContent, type PastedContentReference, } from "../tui/paste-placeholder.js";
|
|
3
4
|
export interface SubmitPayload {
|
|
4
5
|
/** Fully-expanded text sent to the agent. */
|
|
5
6
|
text: string;
|
|
@@ -19,10 +20,6 @@ interface InputBoxProps {
|
|
|
19
20
|
terminalColumns: number;
|
|
20
21
|
cwd: string;
|
|
21
22
|
}
|
|
22
|
-
export interface PastedContentReference {
|
|
23
|
-
marker: string;
|
|
24
|
-
content: string;
|
|
25
|
-
}
|
|
26
23
|
export declare function needsCursorRowCompensation(nextOutputHeight: number, viewportRows: number, previousOutputHeight: number | null): boolean;
|
|
27
24
|
export declare function resolveCursorRowCompensation(input: {
|
|
28
25
|
sameRenderedFrame: boolean;
|
|
@@ -58,8 +55,4 @@ export declare function insertNewlineAtCursor(text: string, cursor: number): {
|
|
|
58
55
|
text: string;
|
|
59
56
|
cursor: number;
|
|
60
57
|
};
|
|
61
|
-
export declare function shouldCollapsePastedContent(text: string): boolean;
|
|
62
|
-
export declare function createPastedContentMarker(content: string): string;
|
|
63
|
-
export declare function expandPastedContentMarkers(displayText: string, references: PastedContentReference[]): string;
|
|
64
58
|
export declare function InputBox({ onSubmit, onPasteNotice, disabled, cursorResetEpoch, draftText, draftEpoch, onDraftApplied, skillRegistry, terminalColumns, cwd, }: InputBoxProps): import("react/jsx-runtime").JSX.Element;
|
|
65
|
-
export {};
|
|
@@ -9,13 +9,13 @@ import { filterFileSuggestions, findAtContext, listProjectFiles } from "./file-m
|
|
|
9
9
|
import { ingestClipboardImage, ingestImagePath, isImageFilePath, isScreenshotTempPath, splitPastedPaths, } from "./image-paste.js";
|
|
10
10
|
import { appendHistoryEntry, loadHistorySync, pushHistoryEntry, stepHistory, } from "./input-history.js";
|
|
11
11
|
import { stripTerminalMouseSequences } from "./terminal-mouse.js";
|
|
12
|
+
export { createPastedContentMarker, expandPastedContentMarkers, shouldCollapsePastedContent, } from "../tui/paste-placeholder.js";
|
|
13
|
+
import { createPastedContentMarker, expandPastedContentMarkers, shouldCollapsePastedContent, } from "../tui/paste-placeholder.js";
|
|
12
14
|
const MIN_VISIBLE_LINES = 3;
|
|
13
15
|
const MAX_VISIBLE_LINES = 6;
|
|
14
16
|
const PADDING_X = 1;
|
|
15
17
|
const PROMPT = " > ";
|
|
16
18
|
const MAX_VISIBLE_SUGGESTIONS = 8;
|
|
17
|
-
const LONG_PASTE_CHAR_THRESHOLD = 1000;
|
|
18
|
-
const LONG_PASTE_LINE_THRESHOLD = 20;
|
|
19
19
|
export function needsCursorRowCompensation(nextOutputHeight, viewportRows, previousOutputHeight) {
|
|
20
20
|
const hadPreviousFrame = previousOutputHeight !== null && previousOutputHeight > 0;
|
|
21
21
|
const isFullscreen = nextOutputHeight >= viewportRows;
|
|
@@ -148,41 +148,6 @@ export function insertNewlineAtCursor(text, cursor) {
|
|
|
148
148
|
cursor: clampedCursor + 1,
|
|
149
149
|
};
|
|
150
150
|
}
|
|
151
|
-
export function shouldCollapsePastedContent(text) {
|
|
152
|
-
if (text.length >= LONG_PASTE_CHAR_THRESHOLD)
|
|
153
|
-
return true;
|
|
154
|
-
return text.split("\n").length >= LONG_PASTE_LINE_THRESHOLD;
|
|
155
|
-
}
|
|
156
|
-
export function createPastedContentMarker(content) {
|
|
157
|
-
return `[Pasted Content ${content.length} chars]`;
|
|
158
|
-
}
|
|
159
|
-
export function expandPastedContentMarkers(displayText, references) {
|
|
160
|
-
if (references.length === 0 || displayText.length === 0)
|
|
161
|
-
return displayText;
|
|
162
|
-
let expanded = "";
|
|
163
|
-
let index = 0;
|
|
164
|
-
const used = new Set();
|
|
165
|
-
while (index < displayText.length) {
|
|
166
|
-
let matched = -1;
|
|
167
|
-
for (let i = 0; i < references.length; i++) {
|
|
168
|
-
const ref = references[i];
|
|
169
|
-
if (!used.has(i) && displayText.startsWith(ref.marker, index)) {
|
|
170
|
-
matched = i;
|
|
171
|
-
break;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
if (matched >= 0) {
|
|
175
|
-
const ref = references[matched];
|
|
176
|
-
expanded += ref.content;
|
|
177
|
-
index += ref.marker.length;
|
|
178
|
-
used.add(matched);
|
|
179
|
-
continue;
|
|
180
|
-
}
|
|
181
|
-
expanded += displayText[index];
|
|
182
|
-
index += 1;
|
|
183
|
-
}
|
|
184
|
-
return expanded;
|
|
185
|
-
}
|
|
186
151
|
export function InputBox({ onSubmit, onPasteNotice, disabled, cursorResetEpoch = 0, draftText, draftEpoch = 0, onDraftApplied, skillRegistry, terminalColumns, cwd, }) {
|
|
187
152
|
const theme = useTheme();
|
|
188
153
|
const width = terminalColumns;
|
|
@@ -196,6 +161,7 @@ export function InputBox({ onSubmit, onPasteNotice, disabled, cursorResetEpoch =
|
|
|
196
161
|
const [historyIndex, setHistoryIndex] = useState(null);
|
|
197
162
|
const historyDraftRef = useRef("");
|
|
198
163
|
const loadingFilesRef = useRef(false);
|
|
164
|
+
const nextPastedContentIndexRef = useRef(1);
|
|
199
165
|
// Paste and the keystrokes that follow can arrive inside the same stdin chunk
|
|
200
166
|
// and dispatch within one discreteUpdates batch. If the Enter that a user
|
|
201
167
|
// typed after a paste fires before React commits the paste-driven setState,
|
|
@@ -327,7 +293,7 @@ export function InputBox({ onSubmit, onPasteNotice, disabled, cursorResetEpoch =
|
|
|
327
293
|
if (imageTokens.length === 0) {
|
|
328
294
|
// Plain text paste — insert into the input at the cursor.
|
|
329
295
|
if (shouldCollapsePastedContent(clean)) {
|
|
330
|
-
const marker = createPastedContentMarker(clean);
|
|
296
|
+
const marker = createPastedContentMarker(clean, nextPastedContentIndexRef.current++);
|
|
331
297
|
setPastedContentRefs((prev) => [...prev, { marker, content: clean }]);
|
|
332
298
|
insertTextAtCursor(marker);
|
|
333
299
|
}
|
|
@@ -409,6 +375,7 @@ export function InputBox({ onSubmit, onPasteNotice, disabled, cursorResetEpoch =
|
|
|
409
375
|
setSelectedIndex(0);
|
|
410
376
|
setAttachments([]);
|
|
411
377
|
setPastedContentRefs([]);
|
|
378
|
+
nextPastedContentIndexRef.current = 1;
|
|
412
379
|
setHistoryIndex(null);
|
|
413
380
|
historyDraftRef.current = "";
|
|
414
381
|
};
|
|
@@ -560,6 +527,7 @@ export function InputBox({ onSubmit, onPasteNotice, disabled, cursorResetEpoch =
|
|
|
560
527
|
historyDraftRef.current = result.draft;
|
|
561
528
|
setSelectedIndex(0);
|
|
562
529
|
setPastedContentRefs([]);
|
|
530
|
+
nextPastedContentIndexRef.current = 1;
|
|
563
531
|
}
|
|
564
532
|
return;
|
|
565
533
|
}
|
|
@@ -576,6 +544,7 @@ export function InputBox({ onSubmit, onPasteNotice, disabled, cursorResetEpoch =
|
|
|
576
544
|
historyDraftRef.current = result.draft;
|
|
577
545
|
setSelectedIndex(0);
|
|
578
546
|
setPastedContentRefs([]);
|
|
547
|
+
nextPastedContentIndexRef.current = 1;
|
|
579
548
|
}
|
|
580
549
|
return;
|
|
581
550
|
}
|
|
@@ -627,6 +596,7 @@ export function InputBox({ onSubmit, onPasteNotice, disabled, cursorResetEpoch =
|
|
|
627
596
|
setCursor(draftText.length);
|
|
628
597
|
setSelectedIndex(0);
|
|
629
598
|
setPastedContentRefs([]);
|
|
599
|
+
nextPastedContentIndexRef.current = 1;
|
|
630
600
|
setHistoryIndex(null);
|
|
631
601
|
historyDraftRef.current = "";
|
|
632
602
|
onDraftApplied?.();
|
package/dist/tui-opentui/app.js
CHANGED
|
@@ -523,6 +523,7 @@ export function App({ agent, args, sessionManager, createProvider, registry, ski
|
|
|
523
523
|
thinkingLevel: overrides?.thinkingLevel ?? agent.thinking,
|
|
524
524
|
mode: overrides?.mode ?? agent.mode,
|
|
525
525
|
workingDir: args.cwd,
|
|
526
|
+
...agent.getSystemPromptToolOptions(),
|
|
526
527
|
}));
|
|
527
528
|
}, [agent, args.cwd, safeRegistry, safeSkillRegistry]);
|
|
528
529
|
useKeyboard((key) => {
|
|
@@ -636,6 +637,7 @@ export function App({ agent, args, sessionManager, createProvider, registry, ski
|
|
|
636
637
|
configuredModelId: model,
|
|
637
638
|
thinkingLevel: agent.thinking,
|
|
638
639
|
workingDir: args.cwd,
|
|
640
|
+
...agent.getSystemPromptToolOptions(),
|
|
639
641
|
}));
|
|
640
642
|
userConfig.pushRecentModel(model);
|
|
641
643
|
setThinkingLevel(agent.thinking);
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import React from "react";
|
|
9
9
|
import type { SkillRegistry } from "../skills/registry.js";
|
|
10
10
|
import { type ImageAttachment } from "./image-paste.js";
|
|
11
|
+
export { createPastedContentMarker, shouldCollapsePastedContent, } from "../tui/paste-placeholder.js";
|
|
11
12
|
export interface SubmitPayload {
|
|
12
13
|
text: string;
|
|
13
14
|
displayText?: string;
|
|
@@ -25,10 +26,7 @@ interface InputBoxProps {
|
|
|
25
26
|
terminalColumns: number;
|
|
26
27
|
cwd: string;
|
|
27
28
|
}
|
|
28
|
-
export declare function shouldCollapsePastedContent(text: string): boolean;
|
|
29
|
-
export declare function createPastedContentMarker(content: string): string;
|
|
30
29
|
export declare function isCtrlCInput(input: string, key: {
|
|
31
30
|
ctrl?: boolean;
|
|
32
31
|
}): boolean;
|
|
33
32
|
export declare function InputBox({ onSubmit, onPasteNotice, disabled, cursorResetEpoch, draftText, draftEpoch, onDraftApplied, skillRegistry, terminalColumns, cwd, }: InputBoxProps): React.ReactNode;
|
|
34
|
-
export {};
|
|
@@ -14,21 +14,10 @@ import { useTheme } from "./theme.js";
|
|
|
14
14
|
import { filterFileSuggestions, findAtContext, listProjectFiles } from "./file-mentions.js";
|
|
15
15
|
import { ingestImagePath, isImageFilePath, isScreenshotTempPath, splitPastedPaths, } from "./image-paste.js";
|
|
16
16
|
import { appendHistoryEntry, loadHistorySync, stepHistory, } from "./input-history.js";
|
|
17
|
+
export { createPastedContentMarker, shouldCollapsePastedContent, } from "../tui/paste-placeholder.js";
|
|
18
|
+
import { createPastedContentMarker, expandPastedContentMarkers, shouldCollapsePastedContent, } from "../tui/paste-placeholder.js";
|
|
17
19
|
const PROMPT = " > ";
|
|
18
|
-
const LONG_PASTE_CHAR_THRESHOLD = 1000;
|
|
19
|
-
const LONG_PASTE_LINE_THRESHOLD = 20;
|
|
20
20
|
const MAX_VISIBLE_SUGGESTIONS = 8;
|
|
21
|
-
export function shouldCollapsePastedContent(text) {
|
|
22
|
-
if (text.length >= LONG_PASTE_CHAR_THRESHOLD)
|
|
23
|
-
return true;
|
|
24
|
-
const lines = text.split("\n").length;
|
|
25
|
-
return lines >= LONG_PASTE_LINE_THRESHOLD;
|
|
26
|
-
}
|
|
27
|
-
export function createPastedContentMarker(content) {
|
|
28
|
-
const lineCount = content.split("\n").length;
|
|
29
|
-
const wordCount = content.trim().split(/\s+/).length;
|
|
30
|
-
return `[Pasted ${lineCount} lines · ${wordCount} words]`;
|
|
31
|
-
}
|
|
32
21
|
export function isCtrlCInput(input, key) {
|
|
33
22
|
return input === "\x03" || (key.ctrl === true && input.toLowerCase() === "c");
|
|
34
23
|
}
|
|
@@ -37,7 +26,7 @@ export function InputBox({ onSubmit, onPasteNotice, disabled = false, cursorRese
|
|
|
37
26
|
const [buffer, setBuffer] = useState("");
|
|
38
27
|
const [cursor, setCursor] = useState(0);
|
|
39
28
|
const [images, setImages] = useState([]);
|
|
40
|
-
const [pastedRefs, setPastedRefs] = useState(
|
|
29
|
+
const [pastedRefs, setPastedRefs] = useState([]);
|
|
41
30
|
const [history] = useState(() => loadHistorySync());
|
|
42
31
|
const [historyIndex, setHistoryIndex] = useState(null);
|
|
43
32
|
const [suggestions, setSuggestions] = useState([]);
|
|
@@ -55,6 +44,7 @@ export function InputBox({ onSubmit, onPasteNotice, disabled = false, cursorRese
|
|
|
55
44
|
const suggestionIndexRef = useRef(suggestionIndex);
|
|
56
45
|
const suggestionKindRef = useRef(suggestionKind);
|
|
57
46
|
const historyIndexRef = useRef(historyIndex);
|
|
47
|
+
const nextPastedContentIndexRef = useRef(1);
|
|
58
48
|
bufferRef.current = buffer;
|
|
59
49
|
cursorRef.current = cursor;
|
|
60
50
|
imagesRef.current = images;
|
|
@@ -70,7 +60,8 @@ export function InputBox({ onSubmit, onPasteNotice, disabled = false, cursorRese
|
|
|
70
60
|
setBuffer("");
|
|
71
61
|
setCursor(0);
|
|
72
62
|
setImages([]);
|
|
73
|
-
setPastedRefs(
|
|
63
|
+
setPastedRefs([]);
|
|
64
|
+
nextPastedContentIndexRef.current = 1;
|
|
74
65
|
setSuggestions([]);
|
|
75
66
|
setSuggestionKind(null);
|
|
76
67
|
setHistoryIndex(null);
|
|
@@ -81,6 +72,8 @@ export function InputBox({ onSubmit, onPasteNotice, disabled = false, cursorRese
|
|
|
81
72
|
return;
|
|
82
73
|
setBuffer(draftText);
|
|
83
74
|
setCursor(draftText.length);
|
|
75
|
+
setPastedRefs([]);
|
|
76
|
+
nextPastedContentIndexRef.current = 1;
|
|
84
77
|
onDraftApplied?.();
|
|
85
78
|
}, [draftText, draftEpoch, onDraftApplied]);
|
|
86
79
|
const updateSuggestions = useCallback(async (text, cursorPos) => {
|
|
@@ -168,10 +161,7 @@ export function InputBox({ onSubmit, onPasteNotice, disabled = false, cursorRese
|
|
|
168
161
|
const refs = pastedRefsRef.current;
|
|
169
162
|
if (!b.trim() && imgs.length === 0)
|
|
170
163
|
return;
|
|
171
|
-
|
|
172
|
-
for (const [marker, content] of refs) {
|
|
173
|
-
expanded = expanded.split(marker).join(content);
|
|
174
|
-
}
|
|
164
|
+
const expanded = expandPastedContentMarkers(b, refs);
|
|
175
165
|
const payload = {
|
|
176
166
|
text: expanded,
|
|
177
167
|
displayText: expanded !== b ? b : undefined,
|
|
@@ -183,7 +173,8 @@ export function InputBox({ onSubmit, onPasteNotice, disabled = false, cursorRese
|
|
|
183
173
|
setBuffer("");
|
|
184
174
|
setCursor(0);
|
|
185
175
|
setImages([]);
|
|
186
|
-
setPastedRefs(
|
|
176
|
+
setPastedRefs([]);
|
|
177
|
+
nextPastedContentIndexRef.current = 1;
|
|
187
178
|
setSuggestions([]);
|
|
188
179
|
setSuggestionKind(null);
|
|
189
180
|
setHistoryIndex(null);
|
|
@@ -211,12 +202,8 @@ export function InputBox({ onSubmit, onPasteNotice, disabled = false, cursorRese
|
|
|
211
202
|
}
|
|
212
203
|
// Plain text: collapse if long, otherwise insert at cursor.
|
|
213
204
|
if (shouldCollapsePastedContent(text)) {
|
|
214
|
-
const marker = createPastedContentMarker(text);
|
|
215
|
-
setPastedRefs((prev) => {
|
|
216
|
-
const next = new Map(prev);
|
|
217
|
-
next.set(marker, text);
|
|
218
|
-
return next;
|
|
219
|
-
});
|
|
205
|
+
const marker = createPastedContentMarker(text, nextPastedContentIndexRef.current++);
|
|
206
|
+
setPastedRefs((prev) => [...prev, { marker, content: text }]);
|
|
220
207
|
insertAtCursor(marker);
|
|
221
208
|
}
|
|
222
209
|
else {
|
|
@@ -306,6 +293,8 @@ export function InputBox({ onSubmit, onPasteNotice, disabled = false, cursorRese
|
|
|
306
293
|
setBuffer(next.text);
|
|
307
294
|
setCursor(next.text.length);
|
|
308
295
|
setHistoryIndex(next.index);
|
|
296
|
+
setPastedRefs([]);
|
|
297
|
+
nextPastedContentIndexRef.current = 1;
|
|
309
298
|
}
|
|
310
299
|
return;
|
|
311
300
|
}
|
|
@@ -324,6 +313,8 @@ export function InputBox({ onSubmit, onPasteNotice, disabled = false, cursorRese
|
|
|
324
313
|
setBuffer(next.text);
|
|
325
314
|
setCursor(next.text.length);
|
|
326
315
|
setHistoryIndex(next.index);
|
|
316
|
+
setPastedRefs([]);
|
|
317
|
+
nextPastedContentIndexRef.current = 1;
|
|
327
318
|
return;
|
|
328
319
|
}
|
|
329
320
|
const lineEnd = b.indexOf("\n", c);
|
package/dist/types.d.ts
CHANGED
|
@@ -87,6 +87,7 @@ export interface ToolDefinition {
|
|
|
87
87
|
description: string;
|
|
88
88
|
parameters: ToolSchema;
|
|
89
89
|
}
|
|
90
|
+
export type ToolChoiceMode = "auto" | "none";
|
|
90
91
|
export interface ToolCall {
|
|
91
92
|
id: string;
|
|
92
93
|
name: string;
|
|
@@ -203,6 +204,12 @@ export interface ToolContext {
|
|
|
203
204
|
}
|
|
204
205
|
export interface ToolRegistryEntry extends ToolDefinition {
|
|
205
206
|
execute: ToolExecutor;
|
|
207
|
+
/** Optional one-line summary for the Available tools section. */
|
|
208
|
+
promptSnippet?: string;
|
|
209
|
+
/** Optional tool-specific rules appended to the system prompt when this tool is active. */
|
|
210
|
+
promptGuidelines?: string[];
|
|
211
|
+
/** Optional compatibility shim for provider-specific argument shapes. */
|
|
212
|
+
prepareArguments?: (args: Record<string, any>) => Record<string, any>;
|
|
206
213
|
/** Whether this tool is allowed in plan mode. Defaults to false (treated as write-capable). */
|
|
207
214
|
readOnly?: boolean;
|
|
208
215
|
/** Capability classification used by subagent profiles. Defaults to "unknown". */
|
|
@@ -272,6 +279,7 @@ export interface TokenUsage {
|
|
|
272
279
|
completionTokens: number;
|
|
273
280
|
promptCacheHitTokens?: number;
|
|
274
281
|
promptCacheMissTokens?: number;
|
|
282
|
+
cacheCreationTokens?: number;
|
|
275
283
|
reasoningTokens?: number;
|
|
276
284
|
totalTokens?: number;
|
|
277
285
|
}
|
|
@@ -279,6 +287,7 @@ export interface Provider {
|
|
|
279
287
|
streamChat(messages: ProviderMessage[], options: {
|
|
280
288
|
model: string;
|
|
281
289
|
tools?: ToolDefinition[];
|
|
290
|
+
toolChoice?: ToolChoiceMode;
|
|
282
291
|
temperature?: number;
|
|
283
292
|
thinkingLevel?: ThinkingLevel;
|
|
284
293
|
abortSignal?: AbortSignal;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bubblebrain-ai/bubble",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.19",
|
|
4
4
|
"description": "A terminal coding agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"@vue/language-server": "^3.2.7",
|
|
34
34
|
"better-sqlite3": "^12.9.0",
|
|
35
35
|
"chalk": "^5.3.0",
|
|
36
|
-
"diff": "^
|
|
36
|
+
"diff": "^9.0.0",
|
|
37
37
|
"ink": "^7.0.3",
|
|
38
38
|
"js-tiktoken": "^1.0.21",
|
|
39
39
|
"openai": "^4.77.0",
|
|
@@ -49,12 +49,16 @@
|
|
|
49
49
|
"vscode-langservers-extracted": "^4.10.0"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
|
-
"@types/diff": "^7.0.0",
|
|
53
52
|
"@types/node": "^22.0.0",
|
|
54
53
|
"@types/picomatch": "^4.0.3",
|
|
55
54
|
"@types/qrcode-terminal": "^0.12.2",
|
|
56
55
|
"@vitest/coverage-v8": "^4.1.4",
|
|
57
56
|
"typescript": "^5.7.0",
|
|
58
57
|
"vitest": "^4.1.4"
|
|
58
|
+
},
|
|
59
|
+
"overrides": {
|
|
60
|
+
"axios": "^1.17.0",
|
|
61
|
+
"postcss": "^8.5.15",
|
|
62
|
+
"ws": "^8.21.0"
|
|
59
63
|
}
|
|
60
64
|
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { ApprovalController } from "../approval/types.js";
|
|
2
|
-
import { type LspService } from "../lsp/index.js";
|
|
3
|
-
import type { ToolRegistryEntry } from "../types.js";
|
|
4
|
-
import { type FileStateTracker } from "./file-state.js";
|
|
5
|
-
export interface ApplyPatchArgs {
|
|
6
|
-
patch?: string;
|
|
7
|
-
patchText?: string;
|
|
8
|
-
}
|
|
9
|
-
export declare function createApplyPatchTool(cwd: string, approval?: ApprovalController, lsp?: LspService, fileState?: FileStateTracker): ToolRegistryEntry;
|