@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.
Files changed (46) hide show
  1. package/dist/agent/tool-intent.js +0 -1
  2. package/dist/agent.d.ts +1 -0
  3. package/dist/agent.js +54 -21
  4. package/dist/context/prune.d.ts +1 -0
  5. package/dist/context/prune.js +32 -0
  6. package/dist/feishu/agent-host/run-driver.js +2 -2
  7. package/dist/feishu/card/run-state.js +1 -0
  8. package/dist/main.js +11 -9
  9. package/dist/model-pricing.js +2 -1
  10. package/dist/model-selection.d.ts +7 -0
  11. package/dist/model-selection.js +9 -0
  12. package/dist/network/chatgpt-transport.d.ts +1 -0
  13. package/dist/network/chatgpt-transport.js +123 -16
  14. package/dist/orchestrator/default-hooks.js +1 -1
  15. package/dist/prompt/environment.js +1 -3
  16. package/dist/prompt/runtime.js +1 -1
  17. package/dist/provider-anthropic.d.ts +15 -3
  18. package/dist/provider-anthropic.js +55 -2
  19. package/dist/provider-openai-codex.js +3 -1
  20. package/dist/provider.js +1 -1
  21. package/dist/session-title.js +3 -6
  22. package/dist/slash-commands/commands.js +4 -0
  23. package/dist/stats/usage.d.ts +1 -0
  24. package/dist/stats/usage.js +28 -3
  25. package/dist/tools/edit.js +75 -1
  26. package/dist/tools/glob.js +77 -12
  27. package/dist/tools/index.d.ts +1 -1
  28. package/dist/tools/index.js +1 -3
  29. package/dist/tools/prompt-metadata.d.ts +3 -0
  30. package/dist/tools/prompt-metadata.js +17 -0
  31. package/dist/tools/write.js +14 -0
  32. package/dist/tui/paste-placeholder.d.ts +10 -0
  33. package/dist/tui/paste-placeholder.js +45 -0
  34. package/dist/tui/run.js +23 -0
  35. package/dist/tui-ink/app.js +2 -0
  36. package/dist/tui-ink/input-box.d.ts +1 -8
  37. package/dist/tui-ink/input-box.js +8 -38
  38. package/dist/tui-opentui/app.js +2 -0
  39. package/dist/tui-opentui/input-box.d.ts +1 -3
  40. package/dist/tui-opentui/input-box.js +17 -26
  41. package/dist/types.d.ts +9 -0
  42. package/package.json +7 -3
  43. package/dist/tools/apply-patch.d.ts +0 -9
  44. package/dist/tools/apply-patch.js +0 -330
  45. package/dist/tools/patch-apply.d.ts +0 -41
  46. 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",
@@ -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?.();
@@ -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(new Map());
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(new Map());
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
- let expanded = b;
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(new Map());
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.17",
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": "^7.0.0",
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;