@bubblebrain-ai/bubble 0.0.24 → 0.0.26

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 (171) hide show
  1. package/README.md +5 -3
  2. package/dist/agent.js +1 -1
  3. package/dist/clipboard.d.ts +14 -0
  4. package/dist/clipboard.js +132 -0
  5. package/dist/config.d.ts +3 -0
  6. package/dist/config.js +22 -6
  7. package/dist/goal/format.js +34 -4
  8. package/dist/goal/store.d.ts +3 -0
  9. package/dist/goal/store.js +14 -1
  10. package/dist/goal/usage.d.ts +2 -0
  11. package/dist/goal/usage.js +3 -0
  12. package/dist/main.js +23 -42
  13. package/dist/model-catalog.d.ts +3 -1
  14. package/dist/model-catalog.js +17 -28
  15. package/dist/prompt/compose.js +1 -1
  16. package/dist/provider-anthropic.d.ts +4 -0
  17. package/dist/provider-anthropic.js +31 -0
  18. package/dist/provider-ark-responses.d.ts +17 -0
  19. package/dist/provider-ark-responses.js +462 -0
  20. package/dist/provider-transform.js +7 -0
  21. package/dist/provider.d.ts +7 -0
  22. package/dist/provider.js +170 -27
  23. package/dist/slash-commands/commands.js +22 -0
  24. package/dist/tools/todo.js +22 -38
  25. package/dist/tui/detect-theme.d.ts +1 -0
  26. package/dist/tui/detect-theme.js +23 -0
  27. package/dist/tui/image-display.d.ts +13 -0
  28. package/dist/tui/image-display.js +49 -0
  29. package/dist/tui/input-history.d.ts +37 -6
  30. package/dist/tui/input-history.js +194 -23
  31. package/dist/tui/model-switch.d.ts +42 -0
  32. package/dist/tui/model-switch.js +55 -0
  33. package/dist/tui-ink/app.d.ts +32 -2
  34. package/dist/tui-ink/app.js +1409 -549
  35. package/dist/tui-ink/approval/select.js +10 -0
  36. package/dist/tui-ink/detect-theme.d.ts +1 -2
  37. package/dist/tui-ink/detect-theme.js +1 -87
  38. package/dist/tui-ink/display-history.d.ts +1 -0
  39. package/dist/tui-ink/display-history.js +11 -0
  40. package/dist/tui-ink/feedback-dialog.js +10 -0
  41. package/dist/tui-ink/feishu-setup-picker.js +10 -0
  42. package/dist/tui-ink/footer.d.ts +1 -0
  43. package/dist/tui-ink/footer.js +8 -2
  44. package/dist/tui-ink/input-box.d.ts +71 -9
  45. package/dist/tui-ink/input-box.js +359 -121
  46. package/dist/tui-ink/input-history.d.ts +1 -16
  47. package/dist/tui-ink/input-history.js +1 -79
  48. package/dist/tui-ink/input-queue.d.ts +12 -0
  49. package/dist/tui-ink/input-queue.js +17 -0
  50. package/dist/tui-ink/key-events.d.ts +9 -0
  51. package/dist/tui-ink/key-events.js +8 -0
  52. package/dist/tui-ink/markdown.js +1 -1
  53. package/dist/tui-ink/message-list.d.ts +19 -1
  54. package/dist/tui-ink/message-list.js +111 -32
  55. package/dist/tui-ink/model-picker.d.ts +25 -2
  56. package/dist/tui-ink/model-picker.js +237 -20
  57. package/dist/tui-ink/plan-confirm.js +10 -0
  58. package/dist/tui-ink/question-dialog.js +46 -10
  59. package/dist/tui-ink/run.d.ts +10 -1
  60. package/dist/tui-ink/run.js +27 -42
  61. package/dist/tui-ink/session-picker.js +3 -0
  62. package/dist/tui-ink/submit-dedupe.d.ts +5 -0
  63. package/dist/tui-ink/submit-dedupe.js +25 -0
  64. package/dist/tui-ink/terminal-mouse.d.ts +24 -1
  65. package/dist/tui-ink/terminal-mouse.js +76 -21
  66. package/dist/tui-ink/theme.d.ts +6 -3
  67. package/dist/tui-ink/theme.js +10 -4
  68. package/dist/tui-ink/welcome.d.ts +1 -0
  69. package/dist/tui-ink/welcome.js +34 -27
  70. package/dist/variant/variant-resolver.js +4 -1
  71. package/package.json +1 -5
  72. package/dist/tui/clipboard.d.ts +0 -1
  73. package/dist/tui/clipboard.js +0 -53
  74. package/dist/tui/escape-confirmation.d.ts +0 -15
  75. package/dist/tui/escape-confirmation.js +0 -30
  76. package/dist/tui/global-key-router.d.ts +0 -3
  77. package/dist/tui/global-key-router.js +0 -87
  78. package/dist/tui/markdown-inline.d.ts +0 -22
  79. package/dist/tui/markdown-inline.js +0 -68
  80. package/dist/tui/markdown-theme-rules.d.ts +0 -23
  81. package/dist/tui/markdown-theme-rules.js +0 -164
  82. package/dist/tui/markdown-theme.d.ts +0 -5
  83. package/dist/tui/markdown-theme.js +0 -27
  84. package/dist/tui/opencode-spinner.d.ts +0 -22
  85. package/dist/tui/opencode-spinner.js +0 -216
  86. package/dist/tui/prompt-keybindings.d.ts +0 -42
  87. package/dist/tui/prompt-keybindings.js +0 -35
  88. package/dist/tui/render-signature.d.ts +0 -1
  89. package/dist/tui/render-signature.js +0 -7
  90. package/dist/tui/run.d.ts +0 -67
  91. package/dist/tui/run.js +0 -10166
  92. package/dist/tui/sidebar-mcp.d.ts +0 -31
  93. package/dist/tui/sidebar-mcp.js +0 -62
  94. package/dist/tui/sidebar-state.d.ts +0 -12
  95. package/dist/tui/sidebar-state.js +0 -69
  96. package/dist/tui/streaming-tool-args.d.ts +0 -15
  97. package/dist/tui/streaming-tool-args.js +0 -30
  98. package/dist/tui/tool-renderers/fallback.d.ts +0 -2
  99. package/dist/tui/tool-renderers/fallback.js +0 -75
  100. package/dist/tui/tool-renderers/registry.d.ts +0 -3
  101. package/dist/tui/tool-renderers/registry.js +0 -11
  102. package/dist/tui/tool-renderers/subagent.d.ts +0 -2
  103. package/dist/tui/tool-renderers/subagent.js +0 -135
  104. package/dist/tui/tool-renderers/types.d.ts +0 -36
  105. package/dist/tui/tool-renderers/types.js +0 -1
  106. package/dist/tui/tool-renderers/write-preview.d.ts +0 -12
  107. package/dist/tui/tool-renderers/write-preview.js +0 -32
  108. package/dist/tui/tool-renderers/write.d.ts +0 -6
  109. package/dist/tui/tool-renderers/write.js +0 -88
  110. package/dist/tui/transcript-scroll.d.ts +0 -25
  111. package/dist/tui/transcript-scroll.js +0 -20
  112. package/dist/tui-ink/transcript-viewport-math.d.ts +0 -11
  113. package/dist/tui-ink/transcript-viewport-math.js +0 -17
  114. package/dist/tui-ink/transcript-viewport.d.ts +0 -24
  115. package/dist/tui-ink/transcript-viewport.js +0 -83
  116. package/dist/tui-opentui/app.d.ts +0 -54
  117. package/dist/tui-opentui/app.js +0 -1371
  118. package/dist/tui-opentui/approval/approval-dialog.d.ts +0 -15
  119. package/dist/tui-opentui/approval/approval-dialog.js +0 -155
  120. package/dist/tui-opentui/approval/diff-view.d.ts +0 -9
  121. package/dist/tui-opentui/approval/diff-view.js +0 -43
  122. package/dist/tui-opentui/approval/select.d.ts +0 -37
  123. package/dist/tui-opentui/approval/select.js +0 -91
  124. package/dist/tui-opentui/detect-theme.d.ts +0 -2
  125. package/dist/tui-opentui/detect-theme.js +0 -87
  126. package/dist/tui-opentui/display-history.d.ts +0 -56
  127. package/dist/tui-opentui/display-history.js +0 -130
  128. package/dist/tui-opentui/edit-diff.d.ts +0 -11
  129. package/dist/tui-opentui/edit-diff.js +0 -57
  130. package/dist/tui-opentui/feedback-dialog.d.ts +0 -21
  131. package/dist/tui-opentui/feedback-dialog.js +0 -164
  132. package/dist/tui-opentui/feishu-setup-picker.d.ts +0 -7
  133. package/dist/tui-opentui/feishu-setup-picker.js +0 -272
  134. package/dist/tui-opentui/file-mentions.d.ts +0 -29
  135. package/dist/tui-opentui/file-mentions.js +0 -174
  136. package/dist/tui-opentui/footer.d.ts +0 -26
  137. package/dist/tui-opentui/footer.js +0 -40
  138. package/dist/tui-opentui/image-paste.d.ts +0 -54
  139. package/dist/tui-opentui/image-paste.js +0 -288
  140. package/dist/tui-opentui/input-box.d.ts +0 -32
  141. package/dist/tui-opentui/input-box.js +0 -462
  142. package/dist/tui-opentui/input-history.d.ts +0 -16
  143. package/dist/tui-opentui/input-history.js +0 -79
  144. package/dist/tui-opentui/markdown.d.ts +0 -66
  145. package/dist/tui-opentui/markdown.js +0 -127
  146. package/dist/tui-opentui/message-list.d.ts +0 -31
  147. package/dist/tui-opentui/message-list.js +0 -131
  148. package/dist/tui-opentui/model-picker.d.ts +0 -63
  149. package/dist/tui-opentui/model-picker.js +0 -450
  150. package/dist/tui-opentui/plan-confirm.d.ts +0 -9
  151. package/dist/tui-opentui/plan-confirm.js +0 -124
  152. package/dist/tui-opentui/question-dialog.d.ts +0 -10
  153. package/dist/tui-opentui/question-dialog.js +0 -110
  154. package/dist/tui-opentui/recent-activity.d.ts +0 -8
  155. package/dist/tui-opentui/recent-activity.js +0 -71
  156. package/dist/tui-opentui/run-session-picker.d.ts +0 -10
  157. package/dist/tui-opentui/run-session-picker.js +0 -28
  158. package/dist/tui-opentui/run.d.ts +0 -38
  159. package/dist/tui-opentui/run.js +0 -48
  160. package/dist/tui-opentui/session-picker.d.ts +0 -12
  161. package/dist/tui-opentui/session-picker.js +0 -120
  162. package/dist/tui-opentui/theme.d.ts +0 -89
  163. package/dist/tui-opentui/theme.js +0 -157
  164. package/dist/tui-opentui/todos.d.ts +0 -9
  165. package/dist/tui-opentui/todos.js +0 -45
  166. package/dist/tui-opentui/trace-groups.d.ts +0 -27
  167. package/dist/tui-opentui/trace-groups.js +0 -455
  168. package/dist/tui-opentui/use-terminal-size.d.ts +0 -4
  169. package/dist/tui-opentui/use-terminal-size.js +0 -5
  170. package/dist/tui-opentui/welcome.d.ts +0 -25
  171. package/dist/tui-opentui/welcome.js +0 -77
package/README.md CHANGED
@@ -69,7 +69,9 @@ Bubble ships with a catalog of built-in providers. Configure them inside the app
69
69
  | `/key <provider> <key>` | Set the API key for a provider. |
70
70
  | `/model` | Pick the active model and reasoning effort. |
71
71
 
72
- Built-in providers include OpenAI, Anthropic, Google, DeepSeek, Moonshot (CN and international), Kimi for Coding, Zhipu AI, Z.AI, Alibaba DashScope, MiniMax, StepFun, Groq, Together AI, Fireworks, and a `local` profile for any OpenAI-compatible endpoint (Ollama, vLLM, LM Studio, etc.).
72
+ Built-in providers include OpenAI, Anthropic, Google, DeepSeek, Moonshot (CN and international), Kimi for Coding, Zhipu AI, Z.AI, Alibaba DashScope, Doubao (Volcengine Ark), MiniMax, StepFun, Groq, Together AI, Fireworks, and a `local` profile for any OpenAI-compatible endpoint (Ollama, vLLM, LM Studio, etc.).
73
+
74
+ For Doubao Seed models on Volcengine Ark, run `/provider --add doubao` and paste your Ark API key. The built-in endpoint is `https://ark.cn-beijing.volces.com/api/v3` and uses Ark's Responses API. The model picker exposes `minimal`, `low`, `medium`, and `high`, defaulting to `high`; `minimal` disables Ark thinking, while the other levels enable it.
73
75
 
74
76
  ### Custom providers and models
75
77
 
@@ -90,7 +92,7 @@ For full control — custom base URLs, self-hosted gateways, extra models, or pi
90
92
  }
91
93
  ```
92
94
 
93
- `protocol` accepts `openai-chat` (default) or `anthropic-messages`. Entries in `models.json` take precedence over the built-in catalog.
95
+ `protocol` accepts `openai-chat` (default), `anthropic-messages`, or `ark-responses`. Entries in `models.json` take precedence over the built-in catalog.
94
96
 
95
97
  ### Reasoning effort
96
98
 
@@ -249,7 +251,7 @@ npm test # run the test suite (vitest)
249
251
  npm start # run the built agent
250
252
  ```
251
253
 
252
- `npm run dev` compiles and launches in one step. The TUI is built on [OpenTUI](https://github.com/anomalyco/opentui) and Solid.
254
+ `npm run dev` compiles and launches in one step. The interactive TUI is built on React Ink.
253
255
 
254
256
  ## Feishu host (optional)
255
257
 
package/dist/agent.js CHANGED
@@ -2047,7 +2047,7 @@ export class Agent {
2047
2047
  }
2048
2048
  if (toolCall.argsCorrupt) {
2049
2049
  return {
2050
- content: `Error: The arguments for "${toolCall.name}" failed to parse as JSON, indicating the tool call was truncated or malformed mid-stream. ` +
2050
+ content: `Error: The arguments for "${toolCall.name}" failed to parse as JSON, indicating the provider returned truncated or malformed tool arguments. ` +
2051
2051
  `Re-issue the call with valid JSON arguments; do not assume the previous attempt ran.`,
2052
2052
  isError: true,
2053
2053
  status: "blocked",
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Build the OSC 52 escape sequence for the given text. Returns `null` when the
3
+ * base64 payload exceeds {@link MAX_OSC52_ENCODED_LENGTH}.
4
+ *
5
+ * Format: ESC ] 52 ; c ; <base64> BEL => `\x1b]52;c;<base64>\x07`
6
+ *
7
+ * Exported for unit testing the encoding/cap logic in isolation.
8
+ */
9
+ export declare function encodeOsc52(text: string): string | null;
10
+ /**
11
+ * Copy `text` to the system clipboard. Resolves once a copy path succeeds and
12
+ * throws only when no path (native tools nor OSC 52) could place the text.
13
+ */
14
+ export declare function copyToClipboard(text: string): Promise<void>;
@@ -0,0 +1,132 @@
1
+ import { execSync, spawn } from "node:child_process";
2
+ import { platform } from "node:os";
3
+ /**
4
+ * Maximum length of the base64-encoded payload we are willing to emit via
5
+ * OSC 52. Very large payloads can desynchronize terminal rendering and many
6
+ * terminals silently drop sequences past ~100k, so we cap and skip instead.
7
+ */
8
+ const MAX_OSC52_ENCODED_LENGTH = 100_000;
9
+ function copyToX11Clipboard(options) {
10
+ try {
11
+ execSync("xclip -selection clipboard", options);
12
+ }
13
+ catch {
14
+ execSync("xsel --clipboard --input", options);
15
+ }
16
+ }
17
+ /**
18
+ * True when we appear to be running over SSH/Mosh, i.e. the host running this
19
+ * process is not the machine whose clipboard the user is looking at.
20
+ */
21
+ function isRemoteSession(env = process.env) {
22
+ return Boolean(env.SSH_CONNECTION || env.SSH_CLIENT || env.MOSH_CONNECTION);
23
+ }
24
+ /**
25
+ * True when running inside a tmux session. tmux intercepts/relays terminal
26
+ * clipboard escapes, so OSC 52 is the reliable path to reach the outer
27
+ * terminal's clipboard (especially tmux-over-ssh). Native pbcopy still works
28
+ * locally, so we treat this as "also emit OSC 52", never as a replacement.
29
+ */
30
+ function isTmuxSession(env = process.env) {
31
+ return Boolean(env.TMUX);
32
+ }
33
+ /**
34
+ * Build the OSC 52 escape sequence for the given text. Returns `null` when the
35
+ * base64 payload exceeds {@link MAX_OSC52_ENCODED_LENGTH}.
36
+ *
37
+ * Format: ESC ] 52 ; c ; <base64> BEL => `\x1b]52;c;<base64>\x07`
38
+ *
39
+ * Exported for unit testing the encoding/cap logic in isolation.
40
+ */
41
+ export function encodeOsc52(text) {
42
+ const encoded = Buffer.from(text).toString("base64");
43
+ if (encoded.length > MAX_OSC52_ENCODED_LENGTH) {
44
+ return null;
45
+ }
46
+ return `\x1b]52;c;${encoded}\x07`;
47
+ }
48
+ /**
49
+ * Write the OSC 52 sequence to stdout. Returns false (without writing) when the
50
+ * payload is too large to emit safely.
51
+ */
52
+ function emitOsc52(text) {
53
+ const sequence = encodeOsc52(text);
54
+ if (sequence === null) {
55
+ return false;
56
+ }
57
+ process.stdout.write(sequence);
58
+ return true;
59
+ }
60
+ /**
61
+ * Copy `text` to the system clipboard. Resolves once a copy path succeeds and
62
+ * throws only when no path (native tools nor OSC 52) could place the text.
63
+ */
64
+ export async function copyToClipboard(text) {
65
+ let copied = false;
66
+ const p = platform();
67
+ const options = { input: text, timeout: 5000, stdio: ["pipe", "ignore", "ignore"] };
68
+ try {
69
+ if (p === "darwin") {
70
+ execSync("pbcopy", options);
71
+ copied = true;
72
+ }
73
+ else if (p === "win32") {
74
+ execSync("clip", options);
75
+ copied = true;
76
+ }
77
+ else {
78
+ // Linux/other. Try Termux, Wayland, then X11 clipboard tools.
79
+ if (process.env.TERMUX_VERSION) {
80
+ try {
81
+ execSync("termux-clipboard-set", options);
82
+ copied = true;
83
+ }
84
+ catch {
85
+ // Fall back to Wayland or X11 tools.
86
+ }
87
+ }
88
+ if (!copied) {
89
+ const hasWaylandDisplay = Boolean(process.env.WAYLAND_DISPLAY);
90
+ const hasX11Display = Boolean(process.env.DISPLAY);
91
+ if (hasWaylandDisplay) {
92
+ try {
93
+ // Verify wl-copy exists (spawn errors are async and won't be caught).
94
+ execSync("which wl-copy", { stdio: "ignore" });
95
+ // wl-copy with execSync hangs due to fork behavior; use spawn instead.
96
+ const proc = spawn("wl-copy", [], { stdio: ["pipe", "ignore", "ignore"] });
97
+ proc.stdin.on("error", () => {
98
+ // Ignore EPIPE errors if wl-copy exits early.
99
+ });
100
+ proc.stdin.write(text);
101
+ proc.stdin.end();
102
+ proc.unref();
103
+ copied = true;
104
+ }
105
+ catch {
106
+ if (hasX11Display) {
107
+ copyToX11Clipboard(options);
108
+ copied = true;
109
+ }
110
+ }
111
+ }
112
+ else if (hasX11Display) {
113
+ copyToX11Clipboard(options);
114
+ copied = true;
115
+ }
116
+ }
117
+ }
118
+ }
119
+ catch {
120
+ // Fall through to the OSC 52 fallback.
121
+ }
122
+ // Emit OSC 52 when the native clipboard may be unreachable from this host
123
+ // (remote sessions, tmux relaying to an outer terminal) or when no native
124
+ // tool succeeded. This is additive — never throw if a native copy worked.
125
+ if (isRemoteSession() || isTmuxSession() || !copied) {
126
+ const osc52Copied = emitOsc52(text);
127
+ copied = copied || osc52Copied;
128
+ }
129
+ if (!copied) {
130
+ throw new Error("Failed to copy to clipboard");
131
+ }
132
+ }
package/dist/config.d.ts CHANGED
@@ -10,6 +10,7 @@ export type ThemeMode = "auto" | "light" | "dark";
10
10
  export interface ThemeConfig {
11
11
  mode: ThemeMode;
12
12
  overrides?: Record<string, string>;
13
+ explicit?: boolean;
13
14
  }
14
15
  export interface UserConfigData {
15
16
  defaultModel?: string;
@@ -65,5 +66,7 @@ export declare class UserConfig {
65
66
  getAgentCategories(): AgentCategoriesConfig;
66
67
  getSubagents(): SubagentsUserConfig;
67
68
  }
69
+ export declare function shouldProbeTerminalTheme(config: ThemeConfig): boolean;
70
+ export declare function effectiveThemeModeForTerminal(config: ThemeConfig, detectedTheme: Exclude<ThemeMode, "auto">): ThemeMode;
68
71
  /** Mask an API key for safe display. */
69
72
  export declare function maskKey(key: string): string;
package/dist/config.js CHANGED
@@ -55,7 +55,7 @@ function sanitizeTheme(value) {
55
55
  return undefined;
56
56
  if (typeof value === "string") {
57
57
  return value === "auto" || value === "light" || value === "dark"
58
- ? { mode: value }
58
+ ? { mode: value, explicit: true }
59
59
  : undefined;
60
60
  }
61
61
  if (typeof value !== "object" || Array.isArray(value))
@@ -68,7 +68,12 @@ function sanitizeTheme(value) {
68
68
  if (mode !== "auto" && mode !== "light" && mode !== "dark")
69
69
  return undefined;
70
70
  const overrides = isStringMap(maybeNew.overrides) ? maybeNew.overrides : undefined;
71
- return overrides ? { mode, overrides } : { mode };
71
+ const explicit = maybeNew.explicit === true ? true : undefined;
72
+ return {
73
+ mode,
74
+ ...(overrides ? { overrides } : {}),
75
+ ...(explicit ? { explicit } : {}),
76
+ };
72
77
  }
73
78
  const overrides = pickStringEntries(value);
74
79
  if (Object.keys(overrides).length === 0)
@@ -188,15 +193,15 @@ export class UserConfig {
188
193
  setThemeMode(mode) {
189
194
  const current = this.getTheme();
190
195
  this.data.theme = current.overrides
191
- ? { mode, overrides: current.overrides }
192
- : { mode };
196
+ ? { mode, overrides: current.overrides, explicit: true }
197
+ : { mode, explicit: true };
193
198
  this.save();
194
199
  }
195
200
  setThemeOverrides(overrides) {
196
201
  const current = this.getTheme();
197
202
  this.data.theme = Object.keys(overrides).length === 0
198
- ? { mode: current.mode }
199
- : { mode: current.mode, overrides: { ...overrides } };
203
+ ? { mode: current.mode, ...(current.explicit ? { explicit: true } : {}) }
204
+ : { mode: current.mode, overrides: { ...overrides }, ...(current.explicit ? { explicit: true } : {}) };
200
205
  this.save();
201
206
  }
202
207
  getAgentCategories() {
@@ -206,6 +211,17 @@ export class UserConfig {
206
211
  return sanitizeSubagentsConfig(this.data.subagents) ?? {};
207
212
  }
208
213
  }
214
+ export function shouldProbeTerminalTheme(config) {
215
+ return config.mode === "auto" || isLegacyBareDarkTheme(config);
216
+ }
217
+ export function effectiveThemeModeForTerminal(config, detectedTheme) {
218
+ if (isLegacyBareDarkTheme(config) && detectedTheme === "light")
219
+ return "auto";
220
+ return config.mode;
221
+ }
222
+ function isLegacyBareDarkTheme(config) {
223
+ return config.mode === "dark" && config.explicit !== true && !config.overrides;
224
+ }
209
225
  /** Mask an API key for safe display. */
210
226
  export function maskKey(key) {
211
227
  if (key.length <= 12)
@@ -30,6 +30,20 @@ function trimZero(value) {
30
30
  return Number.isInteger(rounded) ? String(rounded) : rounded.toFixed(1);
31
31
  }
32
32
  function tokensPart(goal) {
33
+ const untracked = goal.untrackedTokenTurns ?? 0;
34
+ if (untracked > 0) {
35
+ const turns = `${untracked} ${untracked === 1 ? "turn" : "turns"}`;
36
+ if (goal.tokensUsed > 0) {
37
+ const budget = goal.tokenBudget !== undefined
38
+ ? `/${formatTokensCompact(goal.tokenBudget)}`
39
+ : "";
40
+ return `${formatTokensCompact(goal.tokensUsed)}${budget} tok tracked; usage unavailable for ${turns}`;
41
+ }
42
+ if (goal.tokenBudget !== undefined) {
43
+ return `usage unavailable for ${turns}; budget ${formatTokensCompact(goal.tokenBudget)} tok`;
44
+ }
45
+ return `usage unavailable for ${turns}`;
46
+ }
33
47
  if (goal.tokenBudget !== undefined) {
34
48
  return `${formatTokensCompact(goal.tokensUsed)}/${formatTokensCompact(goal.tokenBudget)} tok`;
35
49
  }
@@ -59,11 +73,27 @@ export function goalSummaryText(goal) {
59
73
  * update_goal tool can't report this — see goal/tools.ts).
60
74
  */
61
75
  export function goalCompleteNotice(goal) {
62
- const tokens = goal.tokenBudget !== undefined
63
- ? `${formatTokensCompact(goal.tokensUsed)}/${formatTokensCompact(goal.tokenBudget)} tok`
64
- : `${formatTokensCompact(goal.tokensUsed)} tok`;
76
+ const tokens = completionTokenUsagePhrase(goal);
65
77
  const turns = `${goal.turnsSpent} ${goal.turnsSpent === 1 ? "turn" : "turns"}`;
66
- return `Goal complete — ${tokens} used over ${turns}.`;
78
+ return `Goal complete — ${tokens} over ${turns}.`;
79
+ }
80
+ function completionTokenUsagePhrase(goal) {
81
+ const untracked = goal.untrackedTokenTurns ?? 0;
82
+ if (untracked > 0) {
83
+ if (goal.tokensUsed > 0) {
84
+ const budget = goal.tokenBudget !== undefined
85
+ ? `/${formatTokensCompact(goal.tokenBudget)}`
86
+ : "";
87
+ return `${formatTokensCompact(goal.tokensUsed)}${budget} tok used, plus unavailable usage`;
88
+ }
89
+ if (goal.tokenBudget !== undefined) {
90
+ return `token usage unavailable (budget ${formatTokensCompact(goal.tokenBudget)} tok)`;
91
+ }
92
+ return "token usage unavailable";
93
+ }
94
+ return goal.tokenBudget !== undefined
95
+ ? `${formatTokensCompact(goal.tokensUsed)}/${formatTokensCompact(goal.tokenBudget)} tok used`
96
+ : `${formatTokensCompact(goal.tokensUsed)} tok used`;
67
97
  }
68
98
  /** Compact single-line indicator for the status line / sidebar. */
69
99
  export function goalIndicatorLine(goal, maxObjective = 48) {
@@ -15,6 +15,8 @@ export interface GoalState {
15
15
  /** Optional positive token budget; auto-continuation stops once reached. */
16
16
  tokenBudget?: number;
17
17
  tokensUsed: number;
18
+ /** Goal turns where the provider did not report token usage. */
19
+ untrackedTokenTurns?: number;
18
20
  /** Number of completed goal turns (including the initial turn). */
19
21
  turnsSpent: number;
20
22
  createdAt: number;
@@ -52,6 +54,7 @@ export declare class GoalStore {
52
54
  markBudgetLimited(): GoalState | null;
53
55
  private setStatus;
54
56
  addTokens(n: number): void;
57
+ markTokenUsageUnavailable(): void;
55
58
  incrementTurn(): void;
56
59
  /** True when a token budget is set and usage has reached or exceeded it. */
57
60
  isBudgetExceeded(): boolean;
@@ -54,6 +54,7 @@ export class GoalStore {
54
54
  status: "active",
55
55
  tokenBudget,
56
56
  tokensUsed: 0,
57
+ untrackedTokenTurns: 0,
57
58
  turnsSpent: 0,
58
59
  createdAt: ts,
59
60
  updatedAt: ts,
@@ -131,6 +132,13 @@ export class GoalStore {
131
132
  this.touch();
132
133
  this.emit();
133
134
  }
135
+ markTokenUsageUnavailable() {
136
+ if (!this.goal)
137
+ return;
138
+ this.goal.untrackedTokenTurns = (this.goal.untrackedTokenTurns ?? 0) + 1;
139
+ this.touch();
140
+ this.emit();
141
+ }
134
142
  incrementTurn() {
135
143
  if (!this.goal)
136
144
  return;
@@ -154,7 +162,12 @@ export class GoalStore {
154
162
  this.goal = null;
155
163
  }
156
164
  else {
157
- this.goal = { ...state };
165
+ this.goal = {
166
+ ...state,
167
+ untrackedTokenTurns: state.untrackedTokenTurns !== undefined && state.untrackedTokenTurns > 0
168
+ ? Math.round(state.untrackedTokenTurns)
169
+ : 0,
170
+ };
158
171
  }
159
172
  this.emit();
160
173
  }
@@ -0,0 +1,2 @@
1
+ import type { TokenUsage } from "../types.js";
2
+ export declare function tokenUsageTotal(usage: TokenUsage): number;
@@ -0,0 +1,3 @@
1
+ export function tokenUsageTotal(usage) {
2
+ return usage.totalTokens ?? ((usage.promptTokens || 0) + (usage.completionTokens || 0));
3
+ }
package/dist/main.js CHANGED
@@ -6,7 +6,7 @@ import chalk from "chalk";
6
6
  import { Agent } from "./agent.js";
7
7
  import { BudgetLedger } from "./agent/budget-ledger.js";
8
8
  import { parseArgs, printHelp } from "./cli.js";
9
- import { UserConfig } from "./config.js";
9
+ import { effectiveThemeModeForTerminal, shouldProbeTerminalTheme, UserConfig } from "./config.js";
10
10
  import { createProviderInstance, createUnavailableProvider } from "./provider.js";
11
11
  import { resolveConfiguredModel } from "./model-selection.js";
12
12
  import { getDefaultThinkingLevel } from "./provider-transform.js";
@@ -31,10 +31,6 @@ import { basename } from "node:path";
31
31
  import { normalizeSingleLine, truncateVisual } from "./text-display.js";
32
32
  import { BUBBLE_WORDMARK } from "./tui/wordmark.js";
33
33
  import { configureDebugTrace, summarizeAgentEventForTrace, summarizeTraceMessage, traceEvent, } from "./debug-trace.js";
34
- // OpenTUI is the default renderer. The React Ink implementation (alt-screen
35
- // viewport, src/tui-ink) is feature-complete but still maturing — opt in with
36
- // BUBBLE_TUI=ink.
37
- const USE_OPENTUI = process.env.BUBBLE_TUI !== "ink";
38
34
  async function main() {
39
35
  const args = parseArgs(process.argv.slice(2));
40
36
  if (process.argv.includes("-h") || process.argv.includes("--help")) {
@@ -229,21 +225,21 @@ async function main() {
229
225
  }
230
226
  else {
231
227
  const themeConfig = userConfig.getTheme();
232
- if (themeConfig.mode === "auto") {
228
+ if (shouldProbeTerminalTheme(themeConfig)) {
233
229
  const { detectTerminalTheme } = await import("./tui/detect-theme.js");
234
230
  preResolvedTheme = await detectTerminalTheme();
235
231
  }
236
232
  else {
237
- preResolvedTheme = themeConfig.mode;
233
+ preResolvedTheme = themeConfig.mode === "light" ? "light" : "dark";
238
234
  }
239
- const { runSessionPicker } = USE_OPENTUI
240
- ? await import("./tui-opentui/run-session-picker.js")
241
- : await import("./tui-ink/run-session-picker.js");
235
+ const pickerThemeMode = effectiveThemeModeForTerminal(themeConfig, preResolvedTheme);
236
+ const pickerResolvedTheme = pickerThemeMode === "auto" ? preResolvedTheme : pickerThemeMode;
237
+ const { runSessionPicker } = await import("./tui-ink/run-session-picker.js");
242
238
  const picked = await runSessionPicker({
243
239
  currentCwd: args.cwd,
244
240
  currentSessions,
245
241
  allSessions,
246
- resolvedTheme: preResolvedTheme,
242
+ resolvedTheme: pickerResolvedTheme,
247
243
  themeOverrides: themeConfig.overrides,
248
244
  });
249
245
  if (picked) {
@@ -317,7 +313,7 @@ async function main() {
317
313
  sessionFile: sessionManager?.getSessionFile(),
318
314
  provider: activeProviderId || "none",
319
315
  model: activeModel || "none",
320
- renderer: printMode ? "print" : USE_OPENTUI ? "opentui-core" : "ink",
316
+ renderer: printMode ? "print" : "ink",
321
317
  });
322
318
  if (traceInfo.enabled) {
323
319
  traceEvent("run_start", {
@@ -508,15 +504,16 @@ async function main() {
508
504
  if (preResolvedTheme) {
509
505
  detectedTheme = preResolvedTheme;
510
506
  }
511
- else if (themeConfig.mode === "auto") {
512
- // Probe before OpenTUI owns stdin. OSC 11 needs raw mode, and the
507
+ else if (shouldProbeTerminalTheme(themeConfig)) {
508
+ // Probe before the renderer owns stdin. OSC 11 needs raw mode, and the
513
509
  // runtime renderer can consume the reply before startup code sees it.
514
510
  const { detectTerminalTheme } = await import("./tui/detect-theme.js");
515
511
  detectedTheme = await detectTerminalTheme();
516
512
  }
517
513
  else {
518
- detectedTheme = themeConfig.mode;
514
+ detectedTheme = themeConfig.mode === "light" ? "light" : "dark";
519
515
  }
516
+ const effectiveThemeMode = effectiveThemeModeForTerminal(themeConfig, detectedTheme);
520
517
  // In-place session switch for the /session picker: rebind every closure
521
518
  // that persists to the session (onMessageAppend, markers, title updater)
522
519
  // by reassigning the outer `sessionManager`, then replace the agent's
@@ -572,33 +569,17 @@ async function main() {
572
569
  const { startStartupUpdateCheck } = await import("./update/index.js");
573
570
  const updateCheck = await startStartupUpdateCheck();
574
571
  const updateNotice = updateCheck.notice;
575
- // Two explicit branches (not a dynamic ternary import) so TypeScript
576
- // checks each renderer's RunTuiOptions shape independently.
577
- let exitWallMs;
578
- if (USE_OPENTUI) {
579
- const { runTui } = await import("./tui/run.js");
580
- await runTui(agent, args, {
581
- ...commonOptions,
582
- themeMode: themeConfig.mode,
583
- themeOverrides: themeConfig.overrides,
584
- detectedTheme,
585
- onThemeModeChange: (mode) => userConfig.setThemeMode(mode),
586
- updateNotice: updateNotice ?? undefined,
587
- updateNoticeRefresh: updateCheck.refreshed,
588
- });
589
- }
590
- else {
591
- const { runTui } = await import("./tui-ink/run.js");
592
- const summary = await runTui(agent, args, {
593
- ...commonOptions,
594
- themeMode: themeConfig.mode,
595
- themeOverrides: themeConfig.overrides,
596
- detectedTheme,
597
- onThemeModeChange: (mode) => userConfig.setThemeMode(mode),
598
- updateNotice: updateNotice ?? undefined,
599
- });
600
- exitWallMs = summary?.wallMs;
601
- }
572
+ const { runTui } = await import("./tui-ink/run.js");
573
+ const summary = await runTui(agent, args, {
574
+ ...commonOptions,
575
+ themeMode: effectiveThemeMode,
576
+ themeOverrides: themeConfig.overrides,
577
+ detectedTheme,
578
+ onThemeModeChange: (mode) => userConfig.setThemeMode(mode),
579
+ updateNotice: updateNotice ?? undefined,
580
+ updateNoticeRefresh: updateCheck.refreshed,
581
+ });
582
+ const exitWallMs = summary?.wallMs;
602
583
  if (sessionManager) {
603
584
  printExitSummary(sessionManager, {
604
585
  resumed: resumedExistingSession,
@@ -1,5 +1,5 @@
1
1
  import type { ReasoningEffort } from "./types.js";
2
- export type ProviderProtocol = "openai-chat" | "anthropic-messages";
2
+ export type ProviderProtocol = "openai-chat" | "anthropic-messages" | "ark-responses";
3
3
  export interface BuiltinProviderDefinition {
4
4
  id: string;
5
5
  name: string;
@@ -13,6 +13,7 @@ export interface BuiltinModelDefinition {
13
13
  name: string;
14
14
  providerId: string;
15
15
  reasoningLevels: ReasoningEffort[];
16
+ defaultReasoningLevel?: ReasoningEffort;
16
17
  contextWindow?: number;
17
18
  /**
18
19
  * Server-declared cap on per-tool-output tokens. When set, the agent must
@@ -27,6 +28,7 @@ export declare const BUILTIN_MODELS: BuiltinModelDefinition[];
27
28
  export declare function listBuiltinModels(providerId: string): BuiltinModelDefinition[];
28
29
  export declare function registerDynamicModelMetadata(model: BuiltinModelDefinition): void;
29
30
  export declare function getBuiltinModel(providerId: string, modelId: string): BuiltinModelDefinition | undefined;
31
+ export declare function getModelDefaultReasoningLevel(providerId: string, modelId: string): ReasoningEffort | undefined;
30
32
  export declare function getBuiltinProvider(providerId: string): BuiltinProviderDefinition | undefined;
31
33
  export declare function getModelContextWindow(providerId: string, modelId: string): number | undefined;
32
34
  export declare function getToolOutputTokenLimit(providerId: string, modelId: string): number | undefined;
@@ -10,9 +10,9 @@ export const BUILTIN_PROVIDERS = [
10
10
  { id: "zai", name: "Z.AI", baseURL: "https://api.z.ai/api/paas/v4" },
11
11
  { id: "zai-coding-plan", name: "Z.AI Coding Plan", baseURL: "https://api.z.ai/api/coding/paas/v4" },
12
12
  { id: "alibaba", name: "Alibaba DashScope", baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1" },
13
+ { id: "doubao", name: "Doubao (Volcengine Ark)", baseURL: "https://ark.cn-beijing.volces.com/api/v3", protocol: "ark-responses" },
13
14
  { id: "minimax", name: "MiniMax Token Plan", baseURL: "https://api.minimaxi.com/anthropic", protocol: "anthropic-messages" },
14
- { id: "minimax-openai", name: "MiniMax (OpenAI Compatible)", baseURL: "https://api.minimaxi.com/v1" },
15
- { id: "minimax-anthropic", name: "MiniMax (Anthropic Compatible)", baseURL: "https://api.minimaxi.com/anthropic", protocol: "anthropic-messages", hidden: true },
15
+ { id: "minimax-anthropic", name: "MiniMax API", baseURL: "https://api.minimaxi.com/anthropic", protocol: "anthropic-messages" },
16
16
  { id: "stepfun", name: "StepFun Step Plan", baseURL: "https://api.stepfun.com/step_plan/v1" },
17
17
  { id: "moonshot-cn", name: "Moonshot (国内 platform.moonshot.cn)", baseURL: "https://api.moonshot.cn/v1" },
18
18
  { id: "moonshot-intl", name: "Moonshot (海外 platform.moonshot.ai)", baseURL: "https://api.moonshot.ai/v1" },
@@ -40,10 +40,16 @@ const GLM_5_2_LEVELS = ["high", "max", "off"];
40
40
  const KIMI_THINKING_ONLY_LEVELS = ["medium"];
41
41
  const DEEPSEEK_V4_LEVELS = ["high", "max"];
42
42
  const STEPFUN_REASONING_LEVELS = ["off", "low", "medium", "high"];
43
+ const DOUBAO_SEED_REASONING_LEVELS = ["minimal", "low", "medium", "high"];
43
44
  const MINIMAX_M3_REASONING_LEVELS = ["off", "medium"];
44
45
  const MINIMAX_REASONING_LEVELS = ["medium"];
45
- const ANTHROPIC_ALWAYS_ADAPTIVE_LEVELS = ["medium"];
46
- const ANTHROPIC_ADAPTIVE_LEVELS = ["off", "medium"];
46
+ // Anthropic exposes reasoning depth through output_config.effort (low | medium
47
+ // | high | xhigh | max), not a token budget. xhigh is Opus 4.7+ only; max is
48
+ // Opus 4.6+/Sonnet 4.6/Fable 5; Haiku 4.5 does not accept the effort param.
49
+ // Fable 5 has thinking always on, so it has no "off". "off" disables thinking.
50
+ const ANTHROPIC_OPUS_EFFORT_LEVELS = ["off", "low", "medium", "high", "xhigh", "max"];
51
+ const ANTHROPIC_SONNET_EFFORT_LEVELS = ["off", "low", "medium", "high", "max"];
52
+ const ANTHROPIC_FABLE_EFFORT_LEVELS = ["low", "medium", "high", "xhigh", "max"];
47
53
  const ANTHROPIC_CHAT_LEVELS = ["off"];
48
54
  export const BUILTIN_MODELS = [
49
55
  { id: "gpt-5.5", name: "gpt-5.5", providerId: "openai-codex", reasoningLevels: ALL_OPENAI_LEVELS, contextWindow: 272000, toolOutputTokenLimit: 10000 },
@@ -61,9 +67,9 @@ export const BUILTIN_MODELS = [
61
67
  { id: "o1-preview", name: "o1-preview", providerId: "openai", reasoningLevels: ["off", "low", "medium", "high"], contextWindow: 128000 },
62
68
  { id: "o1-mini", name: "o1-mini", providerId: "openai", reasoningLevels: ["off", "low", "medium", "high"], contextWindow: 128000 },
63
69
  { id: "gpt-4-turbo", name: "gpt-4-turbo", providerId: "openai", reasoningLevels: OPENAI_CHAT_LEVELS, contextWindow: 128000 },
64
- { id: "claude-fable-5", name: "Claude Fable 5", providerId: "anthropic", reasoningLevels: ANTHROPIC_ALWAYS_ADAPTIVE_LEVELS, contextWindow: 1000000 },
65
- { id: "claude-opus-4-8", name: "Claude Opus 4.8", providerId: "anthropic", reasoningLevels: ANTHROPIC_ADAPTIVE_LEVELS, contextWindow: 1000000 },
66
- { id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6", providerId: "anthropic", reasoningLevels: ANTHROPIC_ADAPTIVE_LEVELS, contextWindow: 1000000 },
70
+ { id: "claude-fable-5", name: "Claude Fable 5", providerId: "anthropic", reasoningLevels: ANTHROPIC_FABLE_EFFORT_LEVELS, defaultReasoningLevel: "high", contextWindow: 1000000 },
71
+ { id: "claude-opus-4-8", name: "Claude Opus 4.8", providerId: "anthropic", reasoningLevels: ANTHROPIC_OPUS_EFFORT_LEVELS, defaultReasoningLevel: "high", contextWindow: 1000000 },
72
+ { id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6", providerId: "anthropic", reasoningLevels: ANTHROPIC_SONNET_EFFORT_LEVELS, defaultReasoningLevel: "high", contextWindow: 1000000 },
67
73
  { id: "claude-haiku-4-5-20251001", name: "Claude Haiku 4.5", providerId: "anthropic", reasoningLevels: ANTHROPIC_CHAT_LEVELS, contextWindow: 200000 },
68
74
  { id: "deepseek-v4-flash", name: "deepseek-v4-flash", providerId: "deepseek", reasoningLevels: DEEPSEEK_V4_LEVELS, contextWindow: 1048576 },
69
75
  { id: "deepseek-v4-pro", name: "deepseek-v4-pro", providerId: "deepseek", reasoningLevels: DEEPSEEK_V4_LEVELS, contextWindow: 1048576 },
@@ -88,33 +94,13 @@ export const BUILTIN_MODELS = [
88
94
  { id: "glm-4.6", name: "GLM-4.6", providerId: "zai-coding-plan", reasoningLevels: TOGGLE_THINKING_LEVELS, contextWindow: 200000 },
89
95
  { id: "qwen3.6-plus", name: "Qwen3.6 Plus", providerId: "alibaba", reasoningLevels: ["off"], contextWindow: 1048576 },
90
96
  { id: "qwen3.7-max", name: "Qwen3.7 Max", providerId: "alibaba", reasoningLevels: ["off"], contextWindow: 1048576 },
97
+ { id: "doubao-seed-2-1-pro-260628", name: "Doubao Seed 2.1 Pro", providerId: "doubao", reasoningLevels: DOUBAO_SEED_REASONING_LEVELS, defaultReasoningLevel: "high" },
91
98
  { id: "MiniMax-M3", name: "MiniMax M3", providerId: "minimax", reasoningLevels: MINIMAX_M3_REASONING_LEVELS, contextWindow: 1000000 },
92
99
  { id: "MiniMax-M2.7", name: "MiniMax M2.7", providerId: "minimax", reasoningLevels: MINIMAX_REASONING_LEVELS, contextWindow: 204800 },
93
100
  { id: "MiniMax-M2.7-highspeed", name: "MiniMax M2.7 Highspeed", providerId: "minimax", reasoningLevels: MINIMAX_REASONING_LEVELS, contextWindow: 204800 },
94
- { id: "MiniMax-M2.5", name: "MiniMax M2.5", providerId: "minimax", reasoningLevels: MINIMAX_REASONING_LEVELS, contextWindow: 204800 },
95
- { id: "MiniMax-M2.5-highspeed", name: "MiniMax M2.5 Highspeed", providerId: "minimax", reasoningLevels: MINIMAX_REASONING_LEVELS, contextWindow: 204800 },
96
- { id: "MiniMax-M2.1", name: "MiniMax M2.1", providerId: "minimax", reasoningLevels: MINIMAX_REASONING_LEVELS, contextWindow: 204800 },
97
- { id: "MiniMax-M2.1-highspeed", name: "MiniMax M2.1 Highspeed", providerId: "minimax", reasoningLevels: MINIMAX_REASONING_LEVELS, contextWindow: 204800 },
98
- { id: "MiniMax-M2", name: "MiniMax M2", providerId: "minimax", reasoningLevels: MINIMAX_REASONING_LEVELS, contextWindow: 204800 },
99
- { id: "M2-her", name: "M2-her", providerId: "minimax", reasoningLevels: ["off"], contextWindow: 64000 },
100
- { id: "MiniMax-M3", name: "MiniMax M3", providerId: "minimax-openai", reasoningLevels: MINIMAX_M3_REASONING_LEVELS, contextWindow: 1000000 },
101
- { id: "MiniMax-M2.7", name: "MiniMax M2.7", providerId: "minimax-openai", reasoningLevels: MINIMAX_REASONING_LEVELS, contextWindow: 204800 },
102
- { id: "MiniMax-M2.7-highspeed", name: "MiniMax M2.7 Highspeed", providerId: "minimax-openai", reasoningLevels: MINIMAX_REASONING_LEVELS, contextWindow: 204800 },
103
- { id: "MiniMax-M2.5", name: "MiniMax M2.5", providerId: "minimax-openai", reasoningLevels: MINIMAX_REASONING_LEVELS, contextWindow: 204800 },
104
- { id: "MiniMax-M2.5-highspeed", name: "MiniMax M2.5 Highspeed", providerId: "minimax-openai", reasoningLevels: MINIMAX_REASONING_LEVELS, contextWindow: 204800 },
105
- { id: "MiniMax-M2.1", name: "MiniMax M2.1", providerId: "minimax-openai", reasoningLevels: MINIMAX_REASONING_LEVELS, contextWindow: 204800 },
106
- { id: "MiniMax-M2.1-highspeed", name: "MiniMax M2.1 Highspeed", providerId: "minimax-openai", reasoningLevels: MINIMAX_REASONING_LEVELS, contextWindow: 204800 },
107
- { id: "MiniMax-M2", name: "MiniMax M2", providerId: "minimax-openai", reasoningLevels: MINIMAX_REASONING_LEVELS, contextWindow: 204800 },
108
- { id: "M2-her", name: "M2-her", providerId: "minimax-openai", reasoningLevels: ["off"], contextWindow: 64000 },
109
101
  { id: "MiniMax-M3", name: "MiniMax M3", providerId: "minimax-anthropic", reasoningLevels: MINIMAX_M3_REASONING_LEVELS, contextWindow: 1000000 },
110
102
  { id: "MiniMax-M2.7", name: "MiniMax M2.7", providerId: "minimax-anthropic", reasoningLevels: MINIMAX_REASONING_LEVELS, contextWindow: 204800 },
111
103
  { id: "MiniMax-M2.7-highspeed", name: "MiniMax M2.7 Highspeed", providerId: "minimax-anthropic", reasoningLevels: MINIMAX_REASONING_LEVELS, contextWindow: 204800 },
112
- { id: "MiniMax-M2.5", name: "MiniMax M2.5", providerId: "minimax-anthropic", reasoningLevels: MINIMAX_REASONING_LEVELS, contextWindow: 204800 },
113
- { id: "MiniMax-M2.5-highspeed", name: "MiniMax M2.5 Highspeed", providerId: "minimax-anthropic", reasoningLevels: MINIMAX_REASONING_LEVELS, contextWindow: 204800 },
114
- { id: "MiniMax-M2.1", name: "MiniMax M2.1", providerId: "minimax-anthropic", reasoningLevels: MINIMAX_REASONING_LEVELS, contextWindow: 204800 },
115
- { id: "MiniMax-M2.1-highspeed", name: "MiniMax M2.1 Highspeed", providerId: "minimax-anthropic", reasoningLevels: MINIMAX_REASONING_LEVELS, contextWindow: 204800 },
116
- { id: "MiniMax-M2", name: "MiniMax M2", providerId: "minimax-anthropic", reasoningLevels: MINIMAX_REASONING_LEVELS, contextWindow: 204800 },
117
- { id: "M2-her", name: "M2-her", providerId: "minimax-anthropic", reasoningLevels: ["off"], contextWindow: 64000 },
118
104
  { id: "step-3.7-flash", name: "Step 3.7 Flash", providerId: "stepfun", reasoningLevels: STEPFUN_REASONING_LEVELS, contextWindow: 256000 },
119
105
  { id: "step-3.5-flash-2603", name: "Step 3.5 Flash 2603", providerId: "stepfun", reasoningLevels: STEPFUN_REASONING_LEVELS },
120
106
  { id: "step-3.5-flash", name: "Step 3.5 Flash", providerId: "stepfun", reasoningLevels: STEPFUN_REASONING_LEVELS },
@@ -171,6 +157,9 @@ export function getBuiltinModel(providerId, modelId) {
171
157
  ? BUILTIN_MODELS.find((model) => model.providerId === "openai-codex" && model.id === modelId)
172
158
  : undefined);
173
159
  }
160
+ export function getModelDefaultReasoningLevel(providerId, modelId) {
161
+ return getBuiltinModel(providerId, modelId)?.defaultReasoningLevel;
162
+ }
174
163
  export function getBuiltinProvider(providerId) {
175
164
  return BUILTIN_PROVIDERS.find((provider) => provider.id === providerId);
176
165
  }
@@ -84,7 +84,7 @@ function buildGuidelines(tools, extraGuidelines) {
84
84
  add("Skills may provide specialized workflows. When a task appears to match a specialized workflow, call skill_search to find relevant skills, then call skill with the exact name to load the selected skill before applying it");
85
85
  }
86
86
  if (tools.includes("todo_write")) {
87
- add("Use todo_write to plan any task that needs three or more concrete steps before you start. Mark each item completed as soon as it is done; do not batch updates");
87
+ add("Default to just doing the work. Reach for todo_write only when tracking progress would genuinely help — multi-phase work with real dependencies, a long task spanning several areas, an explicit user request, or steps you discover mid-task. Don't make a list to pad a task you could just do; when in doubt, skip it. Mark each item completed as soon as it is done; do not batch updates");
88
88
  }
89
89
  for (const item of extraGuidelines) {
90
90
  add(item);
@@ -22,7 +22,11 @@ interface AnthropicRequest {
22
22
  thinking?: {
23
23
  type: "adaptive";
24
24
  };
25
+ output_config?: {
26
+ effort: AnthropicEffort;
27
+ };
25
28
  }
29
+ type AnthropicEffort = "low" | "medium" | "high" | "xhigh" | "max";
26
30
  type AnthropicCacheControl = typeof ANTHROPIC_PROMPT_CACHE_CONTROL;
27
31
  interface AnthropicSystemBlock {
28
32
  type: "text";