@bubblebrain-ai/bubble 0.0.34 → 0.0.35

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 (52) hide show
  1. package/dist/agent/abort-errors.d.ts +2 -2
  2. package/dist/agent/budget-ledger.d.ts +4 -45
  3. package/dist/agent/budget-ledger.js +5 -75
  4. package/dist/agent/child-runner.d.ts +1 -3
  5. package/dist/agent/child-runner.js +6 -37
  6. package/dist/agent/profiles.d.ts +0 -2
  7. package/dist/agent/profiles.js +0 -3
  8. package/dist/agent/subagent-control.d.ts +5 -12
  9. package/dist/agent/subagent-control.js +0 -2
  10. package/dist/agent/subagent-store.d.ts +1 -1
  11. package/dist/agent/subagent-store.js +1 -3
  12. package/dist/agent/workflow/control.d.ts +7 -0
  13. package/dist/agent/workflow/control.js +22 -1
  14. package/dist/agent/workflow/runtime.d.ts +15 -2
  15. package/dist/agent/workflow/runtime.js +112 -30
  16. package/dist/agent.d.ts +7 -43
  17. package/dist/agent.js +47 -171
  18. package/dist/config.d.ts +1 -3
  19. package/dist/config.js +6 -5
  20. package/dist/main.js +9 -1
  21. package/dist/model-catalog.js +8 -14
  22. package/dist/orchestrator/default-hooks.js +7 -1
  23. package/dist/prompt/delegation.d.ts +1 -1
  24. package/dist/prompt/delegation.js +21 -7
  25. package/dist/prompt/task-reminders.d.ts +1 -0
  26. package/dist/prompt/task-reminders.js +24 -1
  27. package/dist/provider-transform.d.ts +1 -1
  28. package/dist/provider-transform.js +8 -13
  29. package/dist/tools/agent-lifecycle.d.ts +1 -9
  30. package/dist/tools/agent-lifecycle.js +35 -263
  31. package/dist/tui-ink/app.js +25 -12
  32. package/dist/tui-ink/approval/approval-dialog.js +1 -1
  33. package/dist/tui-ink/approval/diff-view.js +4 -4
  34. package/dist/tui-ink/approval/select.js +2 -2
  35. package/dist/tui-ink/feedback-dialog.js +2 -2
  36. package/dist/tui-ink/feishu-setup-picker.js +3 -3
  37. package/dist/tui-ink/input-box.d.ts +1 -6
  38. package/dist/tui-ink/input-box.js +14 -35
  39. package/dist/tui-ink/markdown.js +11 -9
  40. package/dist/tui-ink/message-list.d.ts +5 -1
  41. package/dist/tui-ink/message-list.js +15 -2
  42. package/dist/tui-ink/model-picker.js +19 -13
  43. package/dist/tui-ink/plan-confirm.js +1 -1
  44. package/dist/tui-ink/subagent-view.d.ts +3 -1
  45. package/dist/tui-ink/theme.d.ts +19 -2
  46. package/dist/tui-ink/theme.js +25 -8
  47. package/dist/tui-ink/welcome.d.ts +3 -2
  48. package/dist/tui-ink/welcome.js +10 -6
  49. package/dist/types.d.ts +3 -26
  50. package/dist/variant/variant-resolver.d.ts +13 -0
  51. package/dist/variant/variant-resolver.js +26 -0
  52. package/package.json +5 -2
@@ -14,7 +14,6 @@ import { submitPayloadFingerprint } from "./submit-dedupe.js";
14
14
  export { createPastedContentMarker, expandPastedContentMarkers, shouldCollapsePastedContent, } from "../tui/paste-placeholder.js";
15
15
  import { createPastedContentMarker, expandPastedContentMarkers, shouldCollapsePastedContent, } from "../tui/paste-placeholder.js";
16
16
  import { imageDisplayLabel, stripInlineImageLabels } from "../tui/image-display.js";
17
- const MIN_VISIBLE_LINES = 3;
18
17
  const MAX_VISIBLE_LINES = 6;
19
18
  const PADDING_X = 1;
20
19
  const PROMPT = " > ";
@@ -47,12 +46,6 @@ export function isCtrlLetterInput(input, key, letter) {
47
46
  export function isCtrlCInput(input, key) {
48
47
  return isCtrlLetterInput(input, key, "c");
49
48
  }
50
- export function shouldUseLineComposerFrame(_background) {
51
- return true;
52
- }
53
- export function composerSurfaceBackground(lineFrame, background, inputBg) {
54
- return lineFrame ? background : inputBg;
55
- }
56
49
  export function shouldUseHardwareComposerCursor(env = process.env) {
57
50
  return env.BUBBLE_HARDWARE_CURSOR === "1";
58
51
  }
@@ -64,16 +57,10 @@ export function composerVerticalArrowDirection(key) {
64
57
  return undefined;
65
58
  }
66
59
  export function resolveSoftwareCursorCellStyle(input) {
67
- if (input.visible) {
68
- return {
69
- backgroundColor: input.cursorBackground,
70
- color: input.cursorForeground,
71
- };
72
- }
73
- return {
74
- backgroundColor: input.rowBackground,
75
- color: input.textColor,
76
- };
60
+ // Inverse video swaps the cell's real foreground/background at the terminal
61
+ // level, so the cursor block stays visible on any terminal palette without
62
+ // guessing a contrasting pair.
63
+ return { inverse: input.visible, color: input.textColor };
77
64
  }
78
65
  /**
79
66
  * Split a composer line around the cursor so the cell under it can render as
@@ -995,8 +982,7 @@ export function InputBox({ onSubmit, onQueue, onPasteNotice, disabled, onArrowDo
995
982
  const classifyVerticalArrow = (direction) => {
996
983
  performVerticalArrowRef.current(direction);
997
984
  };
998
- const lineFrame = shouldUseLineComposerFrame(theme.background);
999
- const minVisibleLines = lineFrame ? 1 : MIN_VISIBLE_LINES;
985
+ const minVisibleLines = 1;
1000
986
  const totalLines = Math.max(visualLines.length, 1);
1001
987
  const visibleLines = Math.min(Math.max(totalLines, minVisibleLines), MAX_VISIBLE_LINES);
1002
988
  let scrollOffset = 0;
@@ -1130,25 +1116,18 @@ export function InputBox({ onSubmit, onQueue, onPasteNotice, disabled, onArrowDo
1130
1116
  });
1131
1117
  // Reference cursorTick so the effect re-runs on the forced render pass.
1132
1118
  void cursorTick;
1133
- const inputBg = disabled ? theme.inputBgDisabled : theme.inputBg;
1134
- const composerBg = composerSurfaceBackground(lineFrame, theme.background, inputBg);
1135
- const rowBg = lineFrame ? undefined : inputBg;
1136
- const cursorFg = lineFrame ? theme.background : inputBg;
1137
1119
  const cursorCellStyle = resolveSoftwareCursorCellStyle({
1138
1120
  visible: softwareCursorVisible,
1139
- cursorBackground: theme.inputText,
1140
- cursorForeground: cursorFg,
1141
1121
  textColor: theme.inputText,
1142
- rowBackground: rowBg,
1143
1122
  });
1144
1123
  const moreBelow = totalLines - scrollOffset - visibleLines;
1145
1124
  const filledLine = (value) => {
1146
1125
  const visibleWidth = visualWidth(value);
1147
1126
  return value + " ".repeat(Math.max(0, contentWidth - visibleWidth));
1148
1127
  };
1149
- return (_jsxs(Box, { flexDirection: "column", width: width, backgroundColor: theme.background, children: [lineFrame && (_jsx(Box, { paddingX: PADDING_X, children: _jsx(Text, { color: theme.border, children: "─".repeat(contentWidth) }) })), _jsxs(Box, { flexDirection: "column", paddingX: PADDING_X, width: width, backgroundColor: composerBg, children: [hasMoreAbove && (_jsx(Text, { backgroundColor: rowBg, color: theme.muted, dimColor: true, children: filledLine(` ↑ ${scrollOffset} more`) })), displayedLines.map((row) => {
1128
+ return (_jsxs(Box, { flexDirection: "column", width: width, backgroundColor: theme.background, children: [_jsx(Box, { paddingX: PADDING_X, children: _jsx(Text, { color: theme.border, children: "─".repeat(contentWidth) }) }), _jsxs(Box, { flexDirection: "column", paddingX: PADDING_X, width: width, backgroundColor: theme.background, children: [hasMoreAbove && (_jsx(Text, { color: theme.muted, dimColor: true, children: filledLine(` ↑ ${scrollOffset} more`) })), displayedLines.map((row) => {
1150
1129
  if (row.kind === "pad") {
1151
- return (_jsx(Text, { backgroundColor: rowBg, children: " ".repeat(contentWidth) }, row.key));
1130
+ return (_jsx(Text, { children: " ".repeat(contentWidth) }, row.key));
1152
1131
  }
1153
1132
  const { text: line, visualIdx } = row;
1154
1133
  const visualLine = visualLines[visualIdx];
@@ -1167,17 +1146,17 @@ export function InputBox({ onSubmit, onQueue, onPasteNotice, disabled, onArrowDo
1167
1146
  });
1168
1147
  const renderedLine = renderedSegments.map((segment) => segment.text).join("");
1169
1148
  const fill = " ".repeat(Math.max(0, lineWidth - visualWidth(renderedLine)));
1170
- return (_jsxs(Box, { height: 1, overflow: "hidden", backgroundColor: composerBg, ref: isCursorLine
1149
+ return (_jsxs(Box, { height: 1, overflow: "hidden", backgroundColor: theme.background, ref: isCursorLine
1171
1150
  ? (el) => {
1172
1151
  cursorLineRef.current = el;
1173
1152
  }
1174
- : undefined, children: [_jsx(Text, { backgroundColor: rowBg, color: isFirst ? theme.accent : theme.inputText, children: prompt }), renderedSegments.map((segment, index) => {
1153
+ : undefined, children: [_jsx(Text, { color: isFirst ? theme.accent : theme.inputText, children: prompt }), renderedSegments.map((segment, index) => {
1175
1154
  if (segment.kind === "cursor") {
1176
- return (_jsx(Text, { backgroundColor: cursorCellStyle.backgroundColor, color: cursorCellStyle.color, children: segment.text }, index));
1155
+ return (_jsx(Text, { inverse: cursorCellStyle.inverse, color: cursorCellStyle.color, children: segment.text }, index));
1177
1156
  }
1178
- return (_jsx(Text, { backgroundColor: rowBg, color: segment.kind === "command" ? theme.accent : theme.inputText, bold: segment.kind === "command", children: segment.text }, index));
1179
- }), _jsx(Text, { backgroundColor: rowBg, children: fill })] }, visualIdx));
1180
- }), hasMoreBelow && (_jsx(Text, { backgroundColor: rowBg, color: theme.muted, dimColor: true, children: filledLine(` ↓ ${moreBelow} more`) }))] }), lineFrame && (_jsx(Box, { paddingX: PADDING_X, children: _jsx(Text, { color: theme.border, children: "─".repeat(contentWidth) }) })), showSuggestions && mode === "slash" && (_jsxs(Box, { flexDirection: "column", marginTop: 1, paddingLeft: 4, children: [slashSuggestions
1157
+ return (_jsx(Text, { color: segment.kind === "command" ? theme.accent : theme.inputText, bold: segment.kind === "command", children: segment.text }, index));
1158
+ }), _jsx(Text, { children: fill })] }, visualIdx));
1159
+ }), hasMoreBelow && (_jsx(Text, { color: theme.muted, dimColor: true, children: filledLine(` ↓ ${moreBelow} more`) }))] }), _jsx(Box, { paddingX: PADDING_X, children: _jsx(Text, { color: theme.border, children: "─".repeat(contentWidth) }) }), showSuggestions && mode === "slash" && (_jsxs(Box, { flexDirection: "column", marginTop: 1, paddingLeft: 4, children: [slashSuggestions
1181
1160
  .slice(suggestionOffset, suggestionOffset + MAX_VISIBLE_SUGGESTIONS)
1182
1161
  .map((cmd, visibleIndex) => {
1183
1162
  const i = suggestionOffset + visibleIndex;
@@ -1190,6 +1169,6 @@ export function InputBox({ onSubmit, onQueue, onPasteNotice, disabled, onArrowDo
1190
1169
  const i = suggestionOffset + visibleIndex;
1191
1170
  const maxWidth = Math.max(10, Math.min(80, contentWidth - 2));
1192
1171
  const label = s.path.length > maxWidth ? "…" + s.path.slice(-(maxWidth - 1)) : s.path;
1193
- return (_jsx(Box, { height: 1, children: i === selectedIndex ? (_jsx(Text, { backgroundColor: "white", color: "black", children: ` ${label} ` })) : (_jsx(Text, { children: ` ${label}` })) }, s.path));
1172
+ return (_jsx(Box, { height: 1, children: i === selectedIndex ? (_jsx(Text, { color: theme.accent, bold: true, children: `› ${label}` })) : (_jsx(Text, { children: ` ${label}` })) }, s.path));
1194
1173
  }), fileSuggestions.length > MAX_VISIBLE_SUGGESTIONS && (_jsx(Text, { color: theme.muted, children: `Showing ${suggestionOffset + 1}-${Math.min(suggestionOffset + MAX_VISIBLE_SUGGESTIONS, fileSuggestions.length)} of ${fileSuggestions.length}` }))] }))] }));
1195
1174
  }
@@ -320,16 +320,17 @@ function appendInlineSegment(segments, text, style) {
320
320
  segments.push(next);
321
321
  }
322
322
  }
323
- function renderSegmentNodes(segments, keyPrefix) {
324
- return segments.map((segment, index) => (_jsx(Text, { bold: segment.bold, italic: segment.italic, color: segment.code ? "#a78bfa" : undefined, children: segment.text }, `${keyPrefix}-${index}`)));
323
+ function renderSegmentNodes(segments, keyPrefix, codeColor) {
324
+ return segments.map((segment, index) => (_jsx(Text, { bold: segment.bold, italic: segment.italic, color: segment.code ? codeColor : undefined, children: segment.text }, `${keyPrefix}-${index}`)));
325
325
  }
326
- function renderInlineSegments(text, keyPrefix, style = {}) {
327
- return renderSegmentNodes(parseMarkdownInlineSegments(text, style), keyPrefix);
326
+ function renderInlineSegments(text, keyPrefix, codeColor, style = {}) {
327
+ return renderSegmentNodes(parseMarkdownInlineSegments(text, style), keyPrefix, codeColor);
328
328
  }
329
329
  function inlinePlainText(text) {
330
330
  return parseMarkdownInlineSegments(text).map((segment) => segment.text).join("");
331
331
  }
332
332
  function InlineText({ text, maxWidth }) {
333
+ const theme = useTheme();
333
334
  const { columns } = useTerminalSize();
334
335
  // Pre-wrap CJK-aware so Ink's space-only wrap-ansi can't chop "、"-joined runs
335
336
  // mid-token. A caller-supplied maxWidth is exact (it already nets out the
@@ -338,10 +339,10 @@ function InlineText({ text, maxWidth }) {
338
339
  // wrapping a few cells early is invisible, a mid-token chop is not.
339
340
  const effectiveWidth = maxWidth ?? (columns ? Math.max(20, columns - 10) : undefined);
340
341
  if (effectiveWidth === undefined) {
341
- return _jsx(Text, { children: renderInlineSegments(text, "inline") });
342
+ return _jsx(Text, { children: renderInlineSegments(text, "inline", theme.inlineCode) });
342
343
  }
343
344
  const wrapped = wrapInlineSegments(parseMarkdownInlineSegments(text), effectiveWidth);
344
- return (_jsx(_Fragment, { children: wrapped.map((segments, li) => (_jsx(Text, { children: segments.length ? renderSegmentNodes(segments, `inline-${li}`) : " " }, li))) }));
345
+ return (_jsx(_Fragment, { children: wrapped.map((segments, li) => (_jsx(Text, { children: segments.length ? renderSegmentNodes(segments, `inline-${li}`, theme.inlineCode) : " " }, li))) }));
345
346
  }
346
347
  function CodeBlock({ lang, lines }) {
347
348
  const theme = useTheme();
@@ -378,6 +379,7 @@ function CodeBlock({ lang, lines }) {
378
379
  return (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [lang && _jsx(Text, { color: theme.muted, children: lang }), _jsx(Box, { flexDirection: "column", children: highlighted.map((line, i) => (_jsx(Text, { children: line || " " }, i))) })] }));
379
380
  }
380
381
  function TableBlock({ headers, rows, maxWidth, }) {
382
+ const theme = useTheme();
381
383
  const { columns: termWidth } = useTerminalSize();
382
384
  const colCount = headers.length;
383
385
  // Reserve a buffer so the table fits even when wrapped inside an indented
@@ -425,7 +427,7 @@ function TableBlock({ headers, rows, maxWidth, }) {
425
427
  return lines;
426
428
  });
427
429
  const height = Math.max(1, ...wrapped.map((lines) => lines.length));
428
- return (_jsx(Box, { flexDirection: "column", children: Array.from({ length: height }, (_, line) => (_jsxs(Text, { children: [`${g.v} `, wrapped.map((cellLines, i) => (_jsxs(React.Fragment, { children: [renderCellLine(cellLines[line] ?? [], widths[i] ?? 4, `${keyPrefix}-cell-${i}-${line}`), i < colCount - 1 ? ` ${g.v} ` : ` ${g.v}`] }, i)))] }, `${keyPrefix}-line-${line}`))) }, keyPrefix));
430
+ return (_jsx(Box, { flexDirection: "column", children: Array.from({ length: height }, (_, line) => (_jsxs(Text, { children: [`${g.v} `, wrapped.map((cellLines, i) => (_jsxs(React.Fragment, { children: [renderCellLine(cellLines[line] ?? [], widths[i] ?? 4, `${keyPrefix}-cell-${i}-${line}`, theme.inlineCode), i < colCount - 1 ? ` ${g.v} ` : ` ${g.v}`] }, i)))] }, `${keyPrefix}-line-${line}`))) }, keyPrefix));
429
431
  };
430
432
  return (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { children: top }), renderRow(headers, "header", true), _jsx(Text, { children: mid }), rows.map((row, ri) => renderRow(row, `row-${ri}`)), _jsx(Text, { children: bot })] }));
431
433
  }
@@ -491,10 +493,10 @@ export function allocateColumnWidths(natural, available) {
491
493
  }
492
494
  return widths;
493
495
  }
494
- function renderCellLine(segments, width, keyPrefix) {
496
+ function renderCellLine(segments, width, keyPrefix, codeColor) {
495
497
  const padding = " ".repeat(Math.max(0, width - inlineSegmentsWidth(segments)));
496
498
  return [
497
- ...segments.map((segment, index) => (_jsx(Text, { bold: segment.bold, italic: segment.italic, color: segment.code ? "#a78bfa" : undefined, children: segment.text }, `${keyPrefix}-${index}`))),
499
+ ...renderSegmentNodes(segments, keyPrefix, codeColor),
498
500
  _jsx(Text, { children: padding }, `${keyPrefix}-pad`),
499
501
  ];
500
502
  }
@@ -31,7 +31,11 @@ interface MessageListProps {
31
31
  * a freshly-cleared screen instead of appending duplicates.
32
32
  */
33
33
  staticGeneration?: number;
34
- /** Horizontal padding applied inside each committed/streaming row. */
34
+ /**
35
+ * Horizontal padding applied inside each committed/streaming row. Wrap
36
+ * budgets net this out via TRANSCRIPT_ROW_PADDING (= both sides of the
37
+ * default of 1) — keep the two in sync if this ever changes.
38
+ */
35
39
  paddingX?: number;
36
40
  /**
37
41
  * Maximum height (rows) for the live/dynamic region. The in-progress turn is
@@ -12,6 +12,16 @@ import { latestSubagentNote, sortSubagents, subagentDescriptor, subagentLabel, s
12
12
  import { sanitizeInternalReminderBlocks } from "../agent/internal-reminder-sanitizer.js";
13
13
  import { splitImageDisplayContent } from "../tui/image-display.js";
14
14
  const EXECUTE_COMMAND_BLOCK_MAX_LINES = 4;
15
+ /**
16
+ * Every transcript row — each committed <Static> item and the DynamicClamp
17
+ * live region — wraps its content in paddingX={1}: one column of inset on
18
+ * EACH side. Any wrap budget derived from terminalColumns inside a row must
19
+ * net out these two columns. Forgetting them hands the pre-wrapper a budget
20
+ * two columns wider than the real room, and a dense CJK line that packs flush
21
+ * against that budget renders past the terminal edge — the terminal then
22
+ * hard-wraps the last glyph onto a stray row at column 0.
23
+ */
24
+ const TRANSCRIPT_ROW_PADDING = 2;
15
25
  export function MessageList({ messages, streamingContent, streamingReasoning, streamingTools, streamingParts, terminalColumns, showThinking = false, verboseTrace, pendingApproval, nowTick, welcomeBanner, staticGeneration = 0, paddingX = 1, maxStreamRows, }) {
16
26
  const theme = useTheme();
17
27
  const hasStreaming = !!(streamingContent ||
@@ -195,7 +205,9 @@ function TimelineText({ content, compactTop, terminalColumns, streaming = false,
195
205
  // cell right and would overflow. Reserve that extra cell up front so the
196
206
  // pre-wrap never packs a line the terminal then hard-wraps.
197
207
  const gutter = ambiguousIsWide() ? 6 : 5;
198
- const available = terminalColumns ? Math.max(20, terminalColumns - gutter) : undefined;
208
+ const available = terminalColumns
209
+ ? Math.max(20, terminalColumns - gutter - TRANSCRIPT_ROW_PADDING)
210
+ : undefined;
199
211
  const trimmed = visible.trim();
200
212
  return (_jsxs(Box, { marginLeft: 2, marginTop: compactTop ? 0 : 1, children: [_jsx(Text, { color: theme.agent, children: "\u25CF " }), _jsx(Box, { flexDirection: "column", flexGrow: 1, children: streaming ? (_jsx(StreamingMarkdown, { content: trimmed, maxWidth: available })) : (_jsx(MarkdownContent, { content: trimmed, maxWidth: available })) })] }));
201
213
  }
@@ -347,7 +359,8 @@ function UserMessageBlock({ content, terminalColumns, inputStatus, separateFromP
347
359
  }
348
360
  function PendingInputMessagesBlock({ messages, terminalColumns, title, hint, bulletColor, }) {
349
361
  const theme = useTheme();
350
- const contentWidth = Math.max(20, terminalColumns - 5);
362
+ // Inset per row: marginLeft (2) + "↳ " (2) + the transcript row padding.
363
+ const contentWidth = Math.max(20, terminalColumns - 4 - TRANSCRIPT_ROW_PADDING);
351
364
  return (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: bulletColor, children: "\u2022 " }), _jsxs(Text, { bold: true, color: theme.inputText, children: [title, " "] }), _jsxs(Text, { color: theme.dim, children: ["(", hint, ")"] })] }), messages.flatMap((message, messageIndex) => {
352
365
  const { bodyLines, referenceLines } = splitImageDisplayContent(message.content || " ");
353
366
  const wrappedBody = bodyLines.flatMap((line) => wrapByVisualWidth(line || " ", contentWidth));
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useState, useEffect, useMemo } from "react";
3
3
  import { Box, Text, useInput, usePaste, useStdout } from "ink";
4
4
  import { isKeyReleaseEvent } from "./key-events.js";
@@ -7,7 +7,7 @@ import { encodeModel, decodeModel, displayModel, isUserVisibleProvider } from ".
7
7
  import { listBuiltinModels } from "../model-catalog.js";
8
8
  import { padVisual, truncateVisual } from "../text-display.js";
9
9
  import { hasTerminalMouseSequence } from "./terminal-mouse.js";
10
- import { getAvailableThinkingLevels, normalizeThinkingLevel } from "../provider-transform.js";
10
+ import { getAvailableThinkingLevels, isThinkingOnlyLevels, isThinkingToggleModel, normalizeThinkingLevel } from "../provider-transform.js";
11
11
  export { padVisual, truncateVisual } from "../text-display.js";
12
12
  export function resolvePickerKeyAction(input, key) {
13
13
  if (key.escape)
@@ -78,25 +78,24 @@ export function padPickerRows(rows, bodyRows, width) {
78
78
  }
79
79
  return padded;
80
80
  }
81
- // MiniMax models expose thinking as a binary on/off switch (the API's `thinking`
82
- // param is disabled|adaptive — there's no graded effort), so render the "on"
83
- // level as on/off instead of our internal "medium". Scoped to MiniMax only —
84
- // other 2-level models (e.g. GLM toggles) keep their effort labels.
85
- function isMiniMaxToggleModel(modelId) {
86
- return modelId.toLowerCase().includes("minimax");
81
+ function isThinkingToggleOption(option) {
82
+ const decoded = decodeModel(option.id);
83
+ return isThinkingToggleModel(decoded.providerId || option.providerBadge, decoded.modelId);
87
84
  }
88
85
  export function formatReasoningLevelsLabel(levels, asToggle = false) {
89
- const normalized = levels.length > 0 ? levels : ["off"];
86
+ if (isThinkingOnlyLevels(levels))
87
+ return "on";
90
88
  if (asToggle)
91
89
  return "thinking on/off";
92
- return `effort ${normalized.join("/")}`;
90
+ const normalized = levels.length > 0 ? levels : ["off"];
91
+ return normalized.join("/");
93
92
  }
94
93
  export function formatModelPickerRow(option, options) {
95
94
  const width = Math.max(24, options.width);
96
95
  const marker = options.selected ? "> " : " ";
97
96
  const label = option.label.replace(/\s+/g, " ").trim();
98
97
  const provider = option.providerBadge.replace(/\s+/g, " ").trim();
99
- const effort = formatReasoningLevelsLabel(option.reasoningLevels, isMiniMaxToggleModel(option.id));
98
+ const effort = formatReasoningLevelsLabel(option.reasoningLevels, isThinkingToggleOption(option));
100
99
  const current = options.current ? " ●" : "";
101
100
  const providerWidth = Math.max(6, Math.min(16, Math.floor(width * 0.18)));
102
101
  const effortWidth = Math.max(12, Math.min(30, Math.floor(width * 0.32)));
@@ -345,7 +344,14 @@ export function ModelPicker({ registry, current, currentThinkingLevel, recent, o
345
344
  row,
346
345
  selected: rawModelRows[index]?.selected ?? false,
347
346
  }));
348
- return (_jsxs(Box, { flexDirection: "column", marginY: 1, paddingX: 1, borderStyle: "round", borderColor: theme.borderActive, children: [_jsx(Text, { bold: true, color: theme.accent, children: phase.kind === "effort" ? "Select Reasoning Effort" : "Select Model" }), phase.kind === "effort" ? (_jsx(EffortPickerView, { model: phase.model, selectedIndex: phase.selectedIndex, bodyRows: bodyRows, rowWidth: rowWidth })) : (_jsxs(_Fragment, { children: [_jsx(SearchField, { query: query, placeholder: "Type to search models...", width: rowWidth }), _jsx(Text, { color: theme.muted, children: "\u2191/\u2193 navigate \u00B7 Enter choose effort \u00B7 Esc cancel \u00B7 Backspace clear" })] })), phase.kind === "model" && _jsx(Box, { flexDirection: "column", height: bodyRows, overflow: "hidden", marginTop: 1, children: modelRows.map(({ key, row, selected }) => (_jsx(Box, { height: 1, overflow: "hidden", children: _jsx(Text, { color: selected ? theme.accent : (key === "no-results" ? theme.muted : undefined), bold: selected, children: row }) }, key))) })] }));
347
+ const highlightedOption = options[clampPickerIndex(selectedIndex, options.length)];
348
+ const enterAction = highlightedOption && shouldOpenEffortPicker(highlightedOption)
349
+ ? (isThinkingToggleOption(highlightedOption) ? "choose mode" : "choose effort")
350
+ : "select";
351
+ const effortTitle = phase.kind === "effort" && isThinkingToggleOption(phase.model)
352
+ ? "Select Thinking Mode"
353
+ : "Select Reasoning Effort";
354
+ return (_jsxs(Box, { flexDirection: "column", marginY: 1, paddingX: 1, borderStyle: "round", borderColor: theme.borderActive, children: [_jsx(Text, { bold: true, color: theme.accent, children: phase.kind === "effort" ? effortTitle : "Select Model" }), phase.kind === "effort" ? (_jsx(EffortPickerView, { model: phase.model, selectedIndex: phase.selectedIndex, bodyRows: bodyRows, rowWidth: rowWidth })) : (_jsxs(_Fragment, { children: [_jsx(SearchField, { query: query, placeholder: "Type to search models...", width: rowWidth }), _jsxs(Text, { color: theme.muted, children: ["\u2191/\u2193 navigate \u00B7 Enter ", enterAction, " \u00B7 Esc cancel \u00B7 Backspace clear"] })] })), phase.kind === "model" && _jsx(Box, { flexDirection: "column", height: bodyRows, overflow: "hidden", marginTop: 1, children: modelRows.map(({ key, row, selected }) => (_jsx(Box, { height: 1, overflow: "hidden", children: _jsx(Text, { color: selected ? theme.accent : (key === "no-results" ? theme.muted : undefined), bold: selected, children: row }) }, key))) })] }));
349
355
  }
350
356
  function EffortPickerView({ model, selectedIndex, bodyRows, rowWidth, }) {
351
357
  const theme = useTheme();
@@ -355,7 +361,7 @@ function EffortPickerView({ model, selectedIndex, bodyRows, rowWidth, }) {
355
361
  row: formatEffortPickerRow(level, {
356
362
  selected: index === safeSelectedIndex,
357
363
  width: rowWidth,
358
- asToggle: isMiniMaxToggleModel(model.id),
364
+ asToggle: isThinkingToggleOption(model),
359
365
  }),
360
366
  selected: index === safeSelectedIndex,
361
367
  }));
@@ -110,6 +110,6 @@ export function PlanConfirm({ initialPlan, onApprove, onReject }) {
110
110
  return (_jsx(Text, { children: line || " " }, index));
111
111
  }
112
112
  const safe = line || " ";
113
- return (_jsxs(Box, { children: [_jsx(Text, { children: safe.slice(0, cursorCol) }), _jsx(Text, { backgroundColor: "white", color: "black", children: safe[cursorCol] || " " }), _jsx(Text, { children: safe.slice(cursorCol + 1) })] }, index));
113
+ return (_jsxs(Box, { children: [_jsx(Text, { children: safe.slice(0, cursorCol) }), _jsx(Text, { inverse: true, children: safe[cursorCol] || " " }), _jsx(Text, { children: safe.slice(cursorCol + 1) })] }, index));
114
114
  }) }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: theme.muted, children: [_jsx(Text, { color: theme.accent, bold: true, children: "\u2303S" }), " save & approve \u00A0\u00A0", _jsx(Text, { color: theme.accent, bold: true, children: "esc" }), " cancel edit"] }) })] }));
115
115
  }
@@ -26,7 +26,9 @@ export declare function subagentDescriptor(subagent: SubagentDisplay, includeThi
26
26
  export declare function subagentStatusColor(status: string | undefined, theme: Theme): string;
27
27
  export declare function subagentSummary(subagents: SubagentDisplay[]): string;
28
28
  export declare function sortSubagents(subagents: SubagentDisplay[]): SubagentDisplay[];
29
- /** A spawn_agent (one member), an agent_team/agent_batch, or a run_workflow (a group of members). */
29
+ /** A spawn_agent (one member) or a run_workflow (a group of members). The
30
+ * "team"/"batch" kinds survive only to render transcripts recorded before
31
+ * those tools were removed (2026-07-06). */
30
32
  export interface SubagentGroup {
31
33
  id: string;
32
34
  kind: "single" | "team" | "batch" | "workflow";
@@ -15,7 +15,13 @@ export interface Theme {
15
15
  error: string;
16
16
  warning: string;
17
17
  success: string;
18
- background: string;
18
+ /**
19
+ * Painted canvas color. `undefined` means "do not paint" — the terminal's
20
+ * own background shows through, so the app never fights the user's terminal
21
+ * palette (e.g. forcing pure black inside a soft-dark terminal). Users can
22
+ * still force a painted canvas via `theme.overrides.background` in config.
23
+ */
24
+ background: string | undefined;
19
25
  accent: string;
20
26
  /** Welcome banner border. */
21
27
  bannerBorder: string;
@@ -40,7 +46,8 @@ export interface Theme {
40
46
  toolResult: string;
41
47
  toolError: string;
42
48
  toolPending: string;
43
- code: string;
49
+ /** Markdown inline `code` spans. */
50
+ inlineCode: string;
44
51
  traceAction: string;
45
52
  traceCount: string;
46
53
  traceDetail: string;
@@ -50,8 +57,10 @@ export interface Theme {
50
57
  userMessageBg: string;
51
58
  userMessageText: string;
52
59
  userRail: string;
60
+ /** Painted band backgrounds for diff lines inside user-message cards. */
53
61
  diffAdd: string;
54
62
  diffRemove: string;
63
+ /** Foregrounds for +/- diff lines rendered without a painted background. */
55
64
  diffAddFg: string;
56
65
  diffRemoveFg: string;
57
66
  }
@@ -62,6 +71,14 @@ export declare const darkTheme: Theme;
62
71
  * terminal background.
63
72
  */
64
73
  export declare const lightTheme: Theme;
74
+ /**
75
+ * Decide whether the canvas needs painting. Auto mode always inherits the
76
+ * terminal's own background, and so does a forced theme that matches the
77
+ * detected terminal. A forced theme that mismatches (e.g. `/theme light`
78
+ * inside a dark terminal) paints its canvas, because its foregrounds are
79
+ * tuned for the opposite background and would otherwise be unreadable.
80
+ */
81
+ export declare function canvasBackgroundFor(mode: ThemeMode, resolved: ResolvedTheme, terminalTheme: ResolvedTheme): string | undefined;
65
82
  export declare const ThemeProvider: import("react").Provider<Theme>;
66
83
  export declare function useTheme(): Theme;
67
84
  /** Build the active palette given a resolved mode and optional overrides. */
@@ -14,7 +14,7 @@ export const darkTheme = {
14
14
  error: "red",
15
15
  warning: "yellow",
16
16
  success: "green",
17
- background: "#0A0A0A",
17
+ background: undefined,
18
18
  accent: "cyan",
19
19
  bannerBorder: "#38bdf8",
20
20
  bannerGradientFrom: "#67e8f9",
@@ -37,7 +37,7 @@ export const darkTheme = {
37
37
  toolResult: "gray",
38
38
  toolError: "red",
39
39
  toolPending: "yellow",
40
- code: "yellow",
40
+ inlineCode: "#a78bfa",
41
41
  traceAction: "#E89A6B",
42
42
  traceCount: "#c9c1bd",
43
43
  traceDetail: "gray",
@@ -49,8 +49,8 @@ export const darkTheme = {
49
49
  userRail: "#8A7FC6",
50
50
  diffAdd: "#1a3d1a",
51
51
  diffRemove: "#3d1a1a",
52
- diffAddFg: "#9CDCFE",
53
- diffRemoveFg: "#F48771",
52
+ diffAddFg: "green",
53
+ diffRemoveFg: "red",
54
54
  };
55
55
  /**
56
56
  * Light palette tuned for paper-neutral surfaces, blue focus/user rails, warm
@@ -63,7 +63,7 @@ export const lightTheme = {
63
63
  error: "#B62633",
64
64
  warning: "#8B4A00",
65
65
  success: "#2F7D4A",
66
- background: "#FCFCFA",
66
+ background: undefined,
67
67
  accent: "#8B4A00",
68
68
  bannerBorder: "#356FD2",
69
69
  bannerGradientFrom: "#0E7490",
@@ -86,7 +86,7 @@ export const lightTheme = {
86
86
  toolResult: "#171717",
87
87
  toolError: "#B62633",
88
88
  toolPending: "#8B4A00",
89
- code: "#2F7D4A",
89
+ inlineCode: "#6D28D9",
90
90
  traceAction: "#8B4A00",
91
91
  traceCount: "#6F7377",
92
92
  traceDetail: "#8B9094",
@@ -98,9 +98,26 @@ export const lightTheme = {
98
98
  userRail: "#356FD2",
99
99
  diffAdd: "#D7E8D8",
100
100
  diffRemove: "#F7DADC",
101
- diffAddFg: "#173D2D",
102
- diffRemoveFg: "#5D1922",
101
+ diffAddFg: "#2F7D4A",
102
+ diffRemoveFg: "#B62633",
103
103
  };
104
+ /** Canvas colors painted only when a forced theme mismatches the terminal. */
105
+ const paintedCanvas = {
106
+ dark: "#0A0A0A",
107
+ light: "#FCFCFA",
108
+ };
109
+ /**
110
+ * Decide whether the canvas needs painting. Auto mode always inherits the
111
+ * terminal's own background, and so does a forced theme that matches the
112
+ * detected terminal. A forced theme that mismatches (e.g. `/theme light`
113
+ * inside a dark terminal) paints its canvas, because its foregrounds are
114
+ * tuned for the opposite background and would otherwise be unreadable.
115
+ */
116
+ export function canvasBackgroundFor(mode, resolved, terminalTheme) {
117
+ if (mode === "auto" || resolved === terminalTheme)
118
+ return undefined;
119
+ return paintedCanvas[resolved];
120
+ }
104
121
  const ThemeContext = createContext(darkTheme);
105
122
  export const ThemeProvider = ThemeContext.Provider;
106
123
  export function useTheme() {
@@ -9,6 +9,7 @@ interface WelcomeBannerProps {
9
9
  /** Session identifier (session file basename). */
10
10
  sessionLabel?: string;
11
11
  providerId?: string;
12
+ modelId?: string;
12
13
  modelLabel?: string;
13
14
  /** Active thinking level, rendered as part of the model unit (e.g. "xhigh"). */
14
15
  thinkingLabel?: string;
@@ -19,6 +20,6 @@ interface WelcomeVisibilityInput {
19
20
  }
20
21
  export declare function shouldShowWelcomeBanner({ startedWithVisibleHistory, }: WelcomeVisibilityInput): boolean;
21
22
  export declare function lerpColor(from: string, to: string, t: number): string;
22
- export declare function WelcomeBanner({ terminalColumns, tips, updateNotice, cwd, sessionLabel, providerId, modelLabel, thinkingLabel, }: WelcomeBannerProps): import("react/jsx-runtime").JSX.Element;
23
- export declare function formatModelLine({ providerId, modelLabel, thinkingLabel, tips, }: Pick<WelcomeBannerProps, "providerId" | "modelLabel" | "thinkingLabel" | "tips">): string;
23
+ export declare function WelcomeBanner({ terminalColumns, tips, updateNotice, cwd, sessionLabel, providerId, modelId, modelLabel, thinkingLabel, }: WelcomeBannerProps): import("react/jsx-runtime").JSX.Element;
24
+ export declare function formatModelLine({ providerId, modelId, modelLabel, thinkingLabel, tips, }: Pick<WelcomeBannerProps, "providerId" | "modelId" | "modelLabel" | "thinkingLabel" | "tips">): string;
24
25
  export {};
@@ -2,6 +2,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
2
2
  import { Box, Text } from "ink";
3
3
  import { createRequire } from "node:module";
4
4
  import { useTheme } from "./theme.js";
5
+ import { isThinkingOnlyModel, isThinkingToggleModel } from "../provider-transform.js";
5
6
  const require = createRequire(import.meta.url);
6
7
  const PACKAGE_VERSION = readPackageVersion();
7
8
  const COMPACT_LOGO = [
@@ -28,11 +29,12 @@ function GradientText({ text, from, to }) {
28
29
  const chars = [...text];
29
30
  return (_jsx(_Fragment, { children: chars.map((ch, i) => (_jsx(Text, { bold: true, color: lerpColor(from, to, chars.length <= 1 ? 0 : i / (chars.length - 1)), children: ch }, `ch-${i}`))) }));
30
31
  }
31
- export function WelcomeBanner({ terminalColumns, tips, updateNotice, cwd, sessionLabel, providerId, modelLabel, thinkingLabel, }) {
32
+ export function WelcomeBanner({ terminalColumns, tips, updateNotice, cwd, sessionLabel, providerId, modelId, modelLabel, thinkingLabel, }) {
32
33
  const theme = useTheme();
33
34
  const effectiveWidth = Math.max(24, Math.min(terminalColumns - 2, 96));
34
35
  const modelLine = formatModelLine({
35
36
  providerId,
37
+ modelId,
36
38
  modelLabel,
37
39
  thinkingLabel,
38
40
  tips,
@@ -48,14 +50,16 @@ export function WelcomeBanner({ terminalColumns, tips, updateNotice, cwd, sessio
48
50
  const labelWidth = Math.max(...infoRows.map((row) => row.label.length)) + 1;
49
51
  return (_jsxs(Box, { width: effectiveWidth, flexDirection: "column", marginBottom: 1, borderStyle: "round", borderColor: theme.bannerBorder, paddingX: 2, children: [_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { flexDirection: "column", marginRight: 2, flexShrink: 0, children: COMPACT_LOGO.map((line, rowIndex) => (_jsx(Text, { bold: true, color: lerpColor(theme.bannerGradientFrom, theme.bannerGradientTo, COMPACT_LOGO.length <= 1 ? 0 : rowIndex / (COMPACT_LOGO.length - 1)), children: line }, `logo-row-${rowIndex}`))) }), _jsxs(Box, { flexDirection: "column", marginTop: 1, flexGrow: 1, flexShrink: 1, children: [_jsx(Box, { children: _jsx(GradientText, { text: "Welcome to Bubble!", from: theme.bannerGradientFrom, to: theme.bannerGradientTo }) }), _jsx(Text, { color: theme.muted, wrap: "wrap", children: "I am a cat and you can send /help for help information." })] })] }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: infoRows.map((row) => (_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { flexShrink: 0, children: _jsx(Text, { color: theme.dim, children: row.label.padEnd(labelWidth) }) }), _jsx(Box, { flexGrow: 1, flexShrink: 1, children: _jsx(Text, { color: row.color, wrap: "wrap", children: row.value }) })] }, row.label))) }), updateNotice && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.accent, children: updateNotice }) }))] }));
50
52
  }
51
- export function formatModelLine({ providerId, modelLabel, thinkingLabel, tips, }) {
53
+ export function formatModelLine({ providerId, modelId, modelLabel, thinkingLabel, tips, }) {
52
54
  const parts = [];
53
- // MiniMax thinking is a binary toggle (adaptive thinking), so label it
54
- // "thinking mode" rather than "<level> effort"; and its provider id
55
- // ("minimax-anthropic") is redundant with the model name, so omit it.
55
+ // Binary thinking toggles use an internal placeholder level, so label them
56
+ // "thinking mode" rather than "<level> effort". MiniMax provider ids are
57
+ // redundant with the model name, so omit them.
56
58
  const isMiniMax = (providerId || "").toLowerCase().includes("minimax");
59
+ const isThinkingToggle = isThinkingToggleModel(providerId, modelId);
60
+ const isThinkingOnly = isThinkingOnlyModel(providerId, modelId);
57
61
  if (modelLabel) {
58
- if (thinkingLabel && isMiniMax)
62
+ if (thinkingLabel && (isThinkingToggle || isThinkingOnly))
59
63
  parts.push(modelLabel, "thinking mode");
60
64
  else if (thinkingLabel)
61
65
  parts.push(`${modelLabel} with ${thinkingLabel} effort`);
package/dist/types.d.ts CHANGED
@@ -210,38 +210,15 @@ export interface ToolContext {
210
210
  }) => Promise<import("./agent/subagent-control.js").SubagentThreadSnapshot>;
211
211
  closeSubAgent?: (agentId: string) => Promise<import("./agent/subagent-control.js").SubagentThreadSnapshot>;
212
212
  listSubAgents?: () => import("./agent/subagent-control.js").SubagentThreadSnapshot[];
213
- runAgentTeam?: (cwd: string, options: {
214
- profile: import("./agent/profiles.js").AgentProfile;
215
- category?: string;
216
- model?: string;
217
- effort?: ThinkingLevel;
218
- promptTemplate: string;
219
- items: string[];
220
- parentToolCallId: string;
221
- emitUpdate?: (update: ToolUpdate) => void;
222
- abortSignal?: AbortSignal;
223
- approval?: "fail" | "disabled";
224
- }) => Promise<import("./agent/subagent-control.js").SubagentThreadSnapshot[]>;
225
- runAgentBatch?: (cwd: string, options: {
226
- specs: Array<{
227
- task: string;
228
- profile: import("./agent/profiles.js").AgentProfile;
229
- category?: string;
230
- model?: string;
231
- effort?: ThinkingLevel;
232
- outputSchema?: unknown;
233
- }>;
234
- parentToolCallId: string;
235
- emitUpdate?: (update: ToolUpdate) => void;
236
- abortSignal?: AbortSignal;
237
- approval?: "fail" | "disabled";
238
- }) => Promise<import("./agent/subagent-control.js").SubagentThreadSnapshot[]>;
239
213
  startWorkflow?: (cwd: string, options: {
240
214
  script: string;
241
215
  args?: unknown;
242
216
  title?: string;
243
217
  parentToolCallId: string;
244
218
  abortSignal?: AbortSignal;
219
+ ensureProfileTrusted?: (profile: import("./agent/profiles.js").AgentProfile) => Promise<{
220
+ content: string | unknown;
221
+ } | undefined>;
245
222
  }) => {
246
223
  runId: string;
247
224
  title: string;
@@ -1,4 +1,17 @@
1
1
  import type { ThinkingLevel } from "../types.js";
2
2
  export declare function getAvailableThinkingLevels(providerId: string, modelId: string): ThinkingLevel[];
3
3
  export declare function getDefaultThinkingLevel(providerId: string, modelId: string): ThinkingLevel;
4
+ /**
5
+ * A single non-"off" level means the model is always in thinking mode with no
6
+ * user-facing grades (e.g. kimi-k2.7-code) — the level value is an internal
7
+ * placeholder and must not be shown to the user as an effort grade.
8
+ */
9
+ export declare function isThinkingOnlyLevels(levels: readonly ThinkingLevel[]): boolean;
10
+ export declare function isThinkingOnlyModel(providerId?: string, modelId?: string): boolean;
11
+ /**
12
+ * Some providers expose thinking as a true enabled/disabled switch. We may store
13
+ * the enabled state as "medium" internally for compatibility with ThinkingLevel,
14
+ * but it is not a real user-facing effort grade.
15
+ */
16
+ export declare function isThinkingToggleModel(providerId?: string, modelId?: string): boolean;
4
17
  export declare function normalizeThinkingLevel(level: ThinkingLevel, supportedLevels: readonly ThinkingLevel[]): ThinkingLevel;
@@ -1,5 +1,7 @@
1
1
  import { getBuiltinModel, getModelDefaultReasoningLevel } from "../model-catalog.js";
2
2
  import { clampThinkingLevel } from "./thinking-level.js";
3
+ const KIMI_TOGGLE_PROVIDER_IDS = new Set(["moonshot-cn", "moonshot-intl", "kimi-for-coding"]);
4
+ const KIMI_THINKING_TOGGLE_MODELS = new Set(["kimi-k2.5", "kimi-k2.6"]);
3
5
  export function getAvailableThinkingLevels(providerId, modelId) {
4
6
  return getBuiltinModel(providerId, modelId)?.reasoningLevels ?? ["off"];
5
7
  }
@@ -10,6 +12,30 @@ export function getDefaultThinkingLevel(providerId, modelId) {
10
12
  return explicitDefault;
11
13
  return levels.includes("medium") ? "medium" : levels[0] || "off";
12
14
  }
15
+ /**
16
+ * A single non-"off" level means the model is always in thinking mode with no
17
+ * user-facing grades (e.g. kimi-k2.7-code) — the level value is an internal
18
+ * placeholder and must not be shown to the user as an effort grade.
19
+ */
20
+ export function isThinkingOnlyLevels(levels) {
21
+ return levels.length === 1 && levels[0] !== "off";
22
+ }
23
+ export function isThinkingOnlyModel(providerId = "", modelId = "") {
24
+ return isThinkingOnlyLevels(getAvailableThinkingLevels(providerId, modelId));
25
+ }
26
+ /**
27
+ * Some providers expose thinking as a true enabled/disabled switch. We may store
28
+ * the enabled state as "medium" internally for compatibility with ThinkingLevel,
29
+ * but it is not a real user-facing effort grade.
30
+ */
31
+ export function isThinkingToggleModel(providerId = "", modelId = "") {
32
+ const normalizedProvider = providerId.toLowerCase();
33
+ const normalizedModel = modelId.toLowerCase();
34
+ if (normalizedProvider.includes("minimax") || normalizedModel.includes("minimax"))
35
+ return true;
36
+ return KIMI_TOGGLE_PROVIDER_IDS.has(normalizedProvider)
37
+ && KIMI_THINKING_TOGGLE_MODELS.has(normalizedModel);
38
+ }
13
39
  export function normalizeThinkingLevel(level, supportedLevels) {
14
40
  return clampThinkingLevel(level, supportedLevels);
15
41
  }