@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.
- package/README.md +5 -3
- package/dist/agent.js +1 -1
- package/dist/clipboard.d.ts +14 -0
- package/dist/clipboard.js +132 -0
- package/dist/config.d.ts +3 -0
- package/dist/config.js +22 -6
- package/dist/goal/format.js +34 -4
- package/dist/goal/store.d.ts +3 -0
- package/dist/goal/store.js +14 -1
- package/dist/goal/usage.d.ts +2 -0
- package/dist/goal/usage.js +3 -0
- package/dist/main.js +23 -42
- package/dist/model-catalog.d.ts +3 -1
- package/dist/model-catalog.js +17 -28
- package/dist/prompt/compose.js +1 -1
- package/dist/provider-anthropic.d.ts +4 -0
- package/dist/provider-anthropic.js +31 -0
- package/dist/provider-ark-responses.d.ts +17 -0
- package/dist/provider-ark-responses.js +462 -0
- package/dist/provider-transform.js +7 -0
- package/dist/provider.d.ts +7 -0
- package/dist/provider.js +170 -27
- package/dist/slash-commands/commands.js +22 -0
- package/dist/tools/todo.js +22 -38
- package/dist/tui/detect-theme.d.ts +1 -0
- package/dist/tui/detect-theme.js +23 -0
- package/dist/tui/image-display.d.ts +13 -0
- package/dist/tui/image-display.js +49 -0
- package/dist/tui/input-history.d.ts +37 -6
- package/dist/tui/input-history.js +194 -23
- package/dist/tui/model-switch.d.ts +42 -0
- package/dist/tui/model-switch.js +55 -0
- package/dist/tui-ink/app.d.ts +32 -2
- package/dist/tui-ink/app.js +1409 -549
- package/dist/tui-ink/approval/select.js +10 -0
- package/dist/tui-ink/detect-theme.d.ts +1 -2
- package/dist/tui-ink/detect-theme.js +1 -87
- package/dist/tui-ink/display-history.d.ts +1 -0
- package/dist/tui-ink/display-history.js +11 -0
- package/dist/tui-ink/feedback-dialog.js +10 -0
- package/dist/tui-ink/feishu-setup-picker.js +10 -0
- package/dist/tui-ink/footer.d.ts +1 -0
- package/dist/tui-ink/footer.js +8 -2
- package/dist/tui-ink/input-box.d.ts +71 -9
- package/dist/tui-ink/input-box.js +359 -121
- package/dist/tui-ink/input-history.d.ts +1 -16
- package/dist/tui-ink/input-history.js +1 -79
- package/dist/tui-ink/input-queue.d.ts +12 -0
- package/dist/tui-ink/input-queue.js +17 -0
- package/dist/tui-ink/key-events.d.ts +9 -0
- package/dist/tui-ink/key-events.js +8 -0
- package/dist/tui-ink/markdown.js +1 -1
- package/dist/tui-ink/message-list.d.ts +19 -1
- package/dist/tui-ink/message-list.js +111 -32
- package/dist/tui-ink/model-picker.d.ts +25 -2
- package/dist/tui-ink/model-picker.js +237 -20
- package/dist/tui-ink/plan-confirm.js +10 -0
- package/dist/tui-ink/question-dialog.js +46 -10
- package/dist/tui-ink/run.d.ts +10 -1
- package/dist/tui-ink/run.js +27 -42
- package/dist/tui-ink/session-picker.js +3 -0
- package/dist/tui-ink/submit-dedupe.d.ts +5 -0
- package/dist/tui-ink/submit-dedupe.js +25 -0
- package/dist/tui-ink/terminal-mouse.d.ts +24 -1
- package/dist/tui-ink/terminal-mouse.js +76 -21
- package/dist/tui-ink/theme.d.ts +6 -3
- package/dist/tui-ink/theme.js +10 -4
- package/dist/tui-ink/welcome.d.ts +1 -0
- package/dist/tui-ink/welcome.js +34 -27
- package/dist/variant/variant-resolver.js +4 -1
- package/package.json +1 -5
- package/dist/tui/clipboard.d.ts +0 -1
- package/dist/tui/clipboard.js +0 -53
- package/dist/tui/escape-confirmation.d.ts +0 -15
- package/dist/tui/escape-confirmation.js +0 -30
- package/dist/tui/global-key-router.d.ts +0 -3
- package/dist/tui/global-key-router.js +0 -87
- package/dist/tui/markdown-inline.d.ts +0 -22
- package/dist/tui/markdown-inline.js +0 -68
- package/dist/tui/markdown-theme-rules.d.ts +0 -23
- package/dist/tui/markdown-theme-rules.js +0 -164
- package/dist/tui/markdown-theme.d.ts +0 -5
- package/dist/tui/markdown-theme.js +0 -27
- package/dist/tui/opencode-spinner.d.ts +0 -22
- package/dist/tui/opencode-spinner.js +0 -216
- package/dist/tui/prompt-keybindings.d.ts +0 -42
- package/dist/tui/prompt-keybindings.js +0 -35
- package/dist/tui/render-signature.d.ts +0 -1
- package/dist/tui/render-signature.js +0 -7
- package/dist/tui/run.d.ts +0 -67
- package/dist/tui/run.js +0 -10166
- package/dist/tui/sidebar-mcp.d.ts +0 -31
- package/dist/tui/sidebar-mcp.js +0 -62
- package/dist/tui/sidebar-state.d.ts +0 -12
- package/dist/tui/sidebar-state.js +0 -69
- package/dist/tui/streaming-tool-args.d.ts +0 -15
- package/dist/tui/streaming-tool-args.js +0 -30
- package/dist/tui/tool-renderers/fallback.d.ts +0 -2
- package/dist/tui/tool-renderers/fallback.js +0 -75
- package/dist/tui/tool-renderers/registry.d.ts +0 -3
- package/dist/tui/tool-renderers/registry.js +0 -11
- package/dist/tui/tool-renderers/subagent.d.ts +0 -2
- package/dist/tui/tool-renderers/subagent.js +0 -135
- package/dist/tui/tool-renderers/types.d.ts +0 -36
- package/dist/tui/tool-renderers/types.js +0 -1
- package/dist/tui/tool-renderers/write-preview.d.ts +0 -12
- package/dist/tui/tool-renderers/write-preview.js +0 -32
- package/dist/tui/tool-renderers/write.d.ts +0 -6
- package/dist/tui/tool-renderers/write.js +0 -88
- package/dist/tui/transcript-scroll.d.ts +0 -25
- package/dist/tui/transcript-scroll.js +0 -20
- package/dist/tui-ink/transcript-viewport-math.d.ts +0 -11
- package/dist/tui-ink/transcript-viewport-math.js +0 -17
- package/dist/tui-ink/transcript-viewport.d.ts +0 -24
- package/dist/tui-ink/transcript-viewport.js +0 -83
- package/dist/tui-opentui/app.d.ts +0 -54
- package/dist/tui-opentui/app.js +0 -1371
- package/dist/tui-opentui/approval/approval-dialog.d.ts +0 -15
- package/dist/tui-opentui/approval/approval-dialog.js +0 -155
- package/dist/tui-opentui/approval/diff-view.d.ts +0 -9
- package/dist/tui-opentui/approval/diff-view.js +0 -43
- package/dist/tui-opentui/approval/select.d.ts +0 -37
- package/dist/tui-opentui/approval/select.js +0 -91
- package/dist/tui-opentui/detect-theme.d.ts +0 -2
- package/dist/tui-opentui/detect-theme.js +0 -87
- package/dist/tui-opentui/display-history.d.ts +0 -56
- package/dist/tui-opentui/display-history.js +0 -130
- package/dist/tui-opentui/edit-diff.d.ts +0 -11
- package/dist/tui-opentui/edit-diff.js +0 -57
- package/dist/tui-opentui/feedback-dialog.d.ts +0 -21
- package/dist/tui-opentui/feedback-dialog.js +0 -164
- package/dist/tui-opentui/feishu-setup-picker.d.ts +0 -7
- package/dist/tui-opentui/feishu-setup-picker.js +0 -272
- package/dist/tui-opentui/file-mentions.d.ts +0 -29
- package/dist/tui-opentui/file-mentions.js +0 -174
- package/dist/tui-opentui/footer.d.ts +0 -26
- package/dist/tui-opentui/footer.js +0 -40
- package/dist/tui-opentui/image-paste.d.ts +0 -54
- package/dist/tui-opentui/image-paste.js +0 -288
- package/dist/tui-opentui/input-box.d.ts +0 -32
- package/dist/tui-opentui/input-box.js +0 -462
- package/dist/tui-opentui/input-history.d.ts +0 -16
- package/dist/tui-opentui/input-history.js +0 -79
- package/dist/tui-opentui/markdown.d.ts +0 -66
- package/dist/tui-opentui/markdown.js +0 -127
- package/dist/tui-opentui/message-list.d.ts +0 -31
- package/dist/tui-opentui/message-list.js +0 -131
- package/dist/tui-opentui/model-picker.d.ts +0 -63
- package/dist/tui-opentui/model-picker.js +0 -450
- package/dist/tui-opentui/plan-confirm.d.ts +0 -9
- package/dist/tui-opentui/plan-confirm.js +0 -124
- package/dist/tui-opentui/question-dialog.d.ts +0 -10
- package/dist/tui-opentui/question-dialog.js +0 -110
- package/dist/tui-opentui/recent-activity.d.ts +0 -8
- package/dist/tui-opentui/recent-activity.js +0 -71
- package/dist/tui-opentui/run-session-picker.d.ts +0 -10
- package/dist/tui-opentui/run-session-picker.js +0 -28
- package/dist/tui-opentui/run.d.ts +0 -38
- package/dist/tui-opentui/run.js +0 -48
- package/dist/tui-opentui/session-picker.d.ts +0 -12
- package/dist/tui-opentui/session-picker.js +0 -120
- package/dist/tui-opentui/theme.d.ts +0 -89
- package/dist/tui-opentui/theme.js +0 -157
- package/dist/tui-opentui/todos.d.ts +0 -9
- package/dist/tui-opentui/todos.js +0 -45
- package/dist/tui-opentui/trace-groups.d.ts +0 -27
- package/dist/tui-opentui/trace-groups.js +0 -455
- package/dist/tui-opentui/use-terminal-size.d.ts +0 -4
- package/dist/tui-opentui/use-terminal-size.js +0 -5
- package/dist/tui-opentui/welcome.d.ts +0 -25
- package/dist/tui-opentui/welcome.js +0 -77
|
@@ -1,16 +1 @@
|
|
|
1
|
-
export
|
|
2
|
-
export declare function loadHistorySync(filePath?: string): string[];
|
|
3
|
-
export declare function appendHistoryEntry(entry: string, filePath?: string): void;
|
|
4
|
-
export interface HistoryNavState {
|
|
5
|
-
history: string[];
|
|
6
|
-
index: number | null;
|
|
7
|
-
draft: string;
|
|
8
|
-
}
|
|
9
|
-
export interface HistoryNavResult {
|
|
10
|
-
text: string;
|
|
11
|
-
index: number | null;
|
|
12
|
-
draft: string;
|
|
13
|
-
changed: boolean;
|
|
14
|
-
}
|
|
15
|
-
export declare function stepHistory(state: HistoryNavState, direction: "up" | "down", currentText: string): HistoryNavResult;
|
|
16
|
-
export declare function pushHistoryEntry(history: string[], entry: string): string[];
|
|
1
|
+
export * from "../tui/input-history.js";
|
|
@@ -1,79 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { dirname, join } from "node:path";
|
|
3
|
-
import { getBubbleHome } from "../bubble-home.js";
|
|
4
|
-
const MAX_HISTORY_ENTRIES = 1000;
|
|
5
|
-
export function defaultHistoryFilePath() {
|
|
6
|
-
return join(getBubbleHome(), "input-history.jsonl");
|
|
7
|
-
}
|
|
8
|
-
// JSONL on disk: each line is a JSON-encoded string. JSON encoding handles
|
|
9
|
-
// embedded newlines and quotes so multi-line composer entries round-trip safely.
|
|
10
|
-
export function loadHistorySync(filePath = defaultHistoryFilePath()) {
|
|
11
|
-
try {
|
|
12
|
-
if (!existsSync(filePath))
|
|
13
|
-
return [];
|
|
14
|
-
const raw = readFileSync(filePath, "utf8");
|
|
15
|
-
const out = [];
|
|
16
|
-
for (const line of raw.split("\n")) {
|
|
17
|
-
if (!line)
|
|
18
|
-
continue;
|
|
19
|
-
try {
|
|
20
|
-
const parsed = JSON.parse(line);
|
|
21
|
-
if (typeof parsed === "string" && parsed.length > 0)
|
|
22
|
-
out.push(parsed);
|
|
23
|
-
}
|
|
24
|
-
catch {
|
|
25
|
-
// Malformed line - skip rather than fail the whole load.
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
return out.length > MAX_HISTORY_ENTRIES ? out.slice(-MAX_HISTORY_ENTRIES) : out;
|
|
29
|
-
}
|
|
30
|
-
catch {
|
|
31
|
-
return [];
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
export function appendHistoryEntry(entry, filePath = defaultHistoryFilePath()) {
|
|
35
|
-
if (!entry || entry.trim().length === 0)
|
|
36
|
-
return;
|
|
37
|
-
try {
|
|
38
|
-
mkdirSync(dirname(filePath), { recursive: true });
|
|
39
|
-
appendFileSync(filePath, JSON.stringify(entry) + "\n", "utf8");
|
|
40
|
-
}
|
|
41
|
-
catch {
|
|
42
|
-
// Persistence is best-effort; never crash the composer over disk IO.
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
// Pure transition for up/down navigation. `index === null` means the user is
|
|
46
|
-
// editing a fresh draft; otherwise it points at history[index]. When stepping
|
|
47
|
-
// from the draft into history we snapshot the current text so down past the
|
|
48
|
-
// newest entry can restore it.
|
|
49
|
-
export function stepHistory(state, direction, currentText) {
|
|
50
|
-
const { history, index, draft } = state;
|
|
51
|
-
const noChange = { text: currentText, index, draft, changed: false };
|
|
52
|
-
if (direction === "up") {
|
|
53
|
-
if (history.length === 0)
|
|
54
|
-
return noChange;
|
|
55
|
-
if (index === null) {
|
|
56
|
-
const newIdx = history.length - 1;
|
|
57
|
-
return { text: history[newIdx], index: newIdx, draft: currentText, changed: true };
|
|
58
|
-
}
|
|
59
|
-
if (index > 0) {
|
|
60
|
-
return { text: history[index - 1], index: index - 1, draft, changed: true };
|
|
61
|
-
}
|
|
62
|
-
return noChange;
|
|
63
|
-
}
|
|
64
|
-
if (index === null)
|
|
65
|
-
return noChange;
|
|
66
|
-
if (index < history.length - 1) {
|
|
67
|
-
return { text: history[index + 1], index: index + 1, draft, changed: true };
|
|
68
|
-
}
|
|
69
|
-
return { text: draft, index: null, draft: "", changed: true };
|
|
70
|
-
}
|
|
71
|
-
// Push to in-memory history with last-entry dedupe so repeated identical
|
|
72
|
-
// submissions don't spam the stack.
|
|
73
|
-
export function pushHistoryEntry(history, entry) {
|
|
74
|
-
if (!entry || entry.trim().length === 0)
|
|
75
|
-
return history;
|
|
76
|
-
if (history.length > 0 && history[history.length - 1] === entry)
|
|
77
|
-
return history;
|
|
78
|
-
return [...history, entry];
|
|
79
|
-
}
|
|
1
|
+
export * from "../tui/input-history.js";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { SubmitPayload } from "./input-box.js";
|
|
2
|
+
export interface QueuedInput {
|
|
3
|
+
payload: SubmitPayload;
|
|
4
|
+
displayKey?: string;
|
|
5
|
+
sessionFile?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface PendingSteerMeta {
|
|
8
|
+
displayKey: string;
|
|
9
|
+
sessionFile?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function isQueuedInputForCurrentSession(input: QueuedInput, currentSessionFile?: string): boolean;
|
|
12
|
+
export declare function queuedAndPendingDisplayKeys(queuedInputs: QueuedInput[], pendingSteers: Iterable<PendingSteerMeta>): Set<string>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function isQueuedInputForCurrentSession(input, currentSessionFile) {
|
|
2
|
+
if (!input.sessionFile || !currentSessionFile)
|
|
3
|
+
return true;
|
|
4
|
+
return input.sessionFile === currentSessionFile;
|
|
5
|
+
}
|
|
6
|
+
export function queuedAndPendingDisplayKeys(queuedInputs, pendingSteers) {
|
|
7
|
+
const keys = new Set();
|
|
8
|
+
for (const input of queuedInputs) {
|
|
9
|
+
if (input.displayKey)
|
|
10
|
+
keys.add(input.displayKey);
|
|
11
|
+
}
|
|
12
|
+
for (const steer of pendingSteers) {
|
|
13
|
+
if (steer.displayKey)
|
|
14
|
+
keys.add(steer.displayKey);
|
|
15
|
+
}
|
|
16
|
+
return keys;
|
|
17
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface InkKeyEvent {
|
|
2
|
+
eventType?: string;
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Kitty keyboard protocol can report key press/repeat/release separately.
|
|
6
|
+
* Release events still carry the printable text, so handling them like normal
|
|
7
|
+
* input inserts every typed character twice.
|
|
8
|
+
*/
|
|
9
|
+
export declare function isKeyReleaseEvent(key: InkKeyEvent): boolean;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kitty keyboard protocol can report key press/repeat/release separately.
|
|
3
|
+
* Release events still carry the printable text, so handling them like normal
|
|
4
|
+
* input inserts every typed character twice.
|
|
5
|
+
*/
|
|
6
|
+
export function isKeyReleaseEvent(key) {
|
|
7
|
+
return key.eventType === "release";
|
|
8
|
+
}
|
package/dist/tui-ink/markdown.js
CHANGED
|
@@ -377,7 +377,7 @@ function TableBlock({ headers, rows, maxWidth, }) {
|
|
|
377
377
|
const { columns: termWidth } = useTerminalSize();
|
|
378
378
|
const colCount = headers.length;
|
|
379
379
|
// Reserve a buffer so the table fits even when wrapped inside an indented
|
|
380
|
-
// box (e.g. the timeline gutter contributes marginLeft + "
|
|
380
|
+
// box (e.g. the timeline gutter contributes marginLeft + "● " = 5 cells).
|
|
381
381
|
const budget = Math.max(20, (maxWidth ?? termWidth) - 8);
|
|
382
382
|
const maxWidths = headers.map((h, i) => {
|
|
383
383
|
let max = visualWidth(inlinePlainText(h));
|
|
@@ -17,12 +17,30 @@ interface MessageListProps {
|
|
|
17
17
|
streamingTools: DisplayToolCall[];
|
|
18
18
|
streamingParts: DisplayMessagePart[];
|
|
19
19
|
terminalColumns: number;
|
|
20
|
+
showThinking?: boolean;
|
|
21
|
+
expandedToolOutput?: boolean;
|
|
20
22
|
verboseTrace: boolean;
|
|
21
23
|
pendingApproval?: PendingApprovalHint | null;
|
|
22
24
|
/** Animation tick used to refresh in-progress elapsed counters. */
|
|
23
25
|
nowTick?: number;
|
|
24
26
|
/** Optional banner rendered as the first item in the app-controlled transcript. */
|
|
25
27
|
welcomeBanner?: React.ReactNode;
|
|
28
|
+
/**
|
|
29
|
+
* Bumped whenever the settled transcript is rebuilt non-monotonically
|
|
30
|
+
* (/clear, /compact, /rewind, session switch). Used as the <Static> key so
|
|
31
|
+
* Ink discards its already-printed rows and re-prints the rebuilt list onto
|
|
32
|
+
* a freshly-cleared screen instead of appending duplicates.
|
|
33
|
+
*/
|
|
34
|
+
staticGeneration?: number;
|
|
35
|
+
/** Horizontal padding applied inside each committed/streaming row. */
|
|
36
|
+
paddingX?: number;
|
|
37
|
+
/**
|
|
38
|
+
* Maximum height (rows) for the live/dynamic region. The in-progress turn is
|
|
39
|
+
* clipped to this and pinned to the bottom (tail view) so the live frame
|
|
40
|
+
* never exceeds the viewport — a taller-than-pane frame breaks Ink's redraw
|
|
41
|
+
* under tmux. The full turn still lands in <Static> scrollback on commit.
|
|
42
|
+
*/
|
|
43
|
+
maxStreamRows?: number;
|
|
26
44
|
}
|
|
27
|
-
export declare function MessageList({ messages, streamingContent, streamingReasoning, streamingTools, streamingParts, terminalColumns, verboseTrace, pendingApproval, nowTick, welcomeBanner, }: MessageListProps): import("react/jsx-runtime").JSX.Element;
|
|
45
|
+
export declare function MessageList({ messages, streamingContent, streamingReasoning, streamingTools, streamingParts, terminalColumns, showThinking, expandedToolOutput, verboseTrace, pendingApproval, nowTick, welcomeBanner, staticGeneration, paddingX, maxStreamRows, }: MessageListProps): import("react/jsx-runtime").JSX.Element;
|
|
28
46
|
export {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import React from "react";
|
|
3
|
-
import { Box, Text } from "ink";
|
|
3
|
+
import { Box, Static, Text, measureElement } from "ink";
|
|
4
4
|
import { useTheme } from "./theme.js";
|
|
5
5
|
import { highlightCode, inferLang } from "./code-highlight.js";
|
|
6
6
|
import { MarkdownContent, StreamingMarkdown } from "./markdown.js";
|
|
@@ -9,42 +9,99 @@ import { buildTraceGroups, executeCommandBlock, formatTracePath, shouldInlineExe
|
|
|
9
9
|
import { EDIT_COLLAPSED_DIFF_LINES, formatEditSuccessSummary, getEditDiffDetails } from "./edit-diff.js";
|
|
10
10
|
import { formatSubagentRoute } from "../agent/subagent-route-format.js";
|
|
11
11
|
import { sanitizeInternalReminderBlocks } from "../agent/internal-reminder-sanitizer.js";
|
|
12
|
+
import { splitImageDisplayContent } from "../tui/image-display.js";
|
|
12
13
|
const EXECUTE_COMMAND_BLOCK_MAX_LINES = 4;
|
|
13
|
-
export function MessageList({ messages, streamingContent, streamingReasoning, streamingTools, streamingParts, terminalColumns, verboseTrace, pendingApproval, nowTick, welcomeBanner, }) {
|
|
14
|
+
export function MessageList({ messages, streamingContent, streamingReasoning, streamingTools, streamingParts, terminalColumns, showThinking = false, expandedToolOutput = false, verboseTrace, pendingApproval, nowTick, welcomeBanner, staticGeneration = 0, paddingX = 1, maxStreamRows, }) {
|
|
15
|
+
const theme = useTheme();
|
|
14
16
|
const hasStreaming = !!(streamingContent ||
|
|
15
17
|
streamingReasoning ||
|
|
16
18
|
streamingTools.length > 0 ||
|
|
17
19
|
streamingParts.length > 0);
|
|
20
|
+
const regularMessages = messages.filter((message) => !message.inputStatus);
|
|
21
|
+
const pendingSteerMessages = messages.filter((message) => message.inputStatus === "pending_steer");
|
|
22
|
+
const queuedInputMessages = messages.filter((message) => message.inputStatus === "queued");
|
|
18
23
|
const staticItems = [];
|
|
19
24
|
if (welcomeBanner) {
|
|
20
25
|
staticItems.push({ kind: "welcome", key: "welcome" });
|
|
21
26
|
}
|
|
22
|
-
const lastMessageIndex =
|
|
23
|
-
for (let i = 0; i <
|
|
24
|
-
const msg =
|
|
27
|
+
const lastMessageIndex = regularMessages.length - 1;
|
|
28
|
+
for (let i = 0; i < regularMessages.length; i++) {
|
|
29
|
+
const msg = regularMessages[i];
|
|
25
30
|
staticItems.push({
|
|
26
31
|
kind: "message",
|
|
27
32
|
key: msg.key ?? `message-${i}`,
|
|
28
33
|
message: msg,
|
|
29
34
|
showExpandHint: !hasStreaming && i === lastMessageIndex,
|
|
35
|
+
separateFromPrevious: msg.role === "user" && regularMessages[i - 1]?.role === "user",
|
|
30
36
|
});
|
|
31
37
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
const hasDynamic = hasStreaming || pendingSteerMessages.length > 0 || queuedInputMessages.length > 0;
|
|
39
|
+
// The live region must never grow taller than the viewport: a frame taller
|
|
40
|
+
// than the pane breaks Ink's in-place redraw under tmux (the cursor-up clear
|
|
41
|
+
// can't reach scrolled-off rows), leaving large blank gaps + stray glyphs.
|
|
42
|
+
// Clip it to maxStreamRows and pin to the bottom so the user sees the latest
|
|
43
|
+
// output (tail view); the full turn lands in <Static> scrollback on commit.
|
|
44
|
+
const clampDynamic = typeof maxStreamRows === "number" && maxStreamRows > 0;
|
|
45
|
+
return (_jsxs(Box, { flexDirection: "column", flexShrink: 0, children: [_jsx(Static, { items: staticItems, children: (item) => {
|
|
46
|
+
if (item.kind === "welcome") {
|
|
47
|
+
return (_jsx(Box, { flexDirection: "column", paddingX: paddingX, children: welcomeBanner }, item.key));
|
|
48
|
+
}
|
|
49
|
+
return (_jsx(Box, { flexDirection: "column", paddingX: paddingX, children: _jsx(MessageItem, { message: item.message, terminalColumns: terminalColumns, showThinking: showThinking, expandedToolOutput: expandedToolOutput, verboseTrace: verboseTrace, showExpandHint: item.showExpandHint, separateFromPrevious: item.separateFromPrevious }) }, item.key));
|
|
50
|
+
} }, `transcript-${staticGeneration}`), hasDynamic && (_jsxs(DynamicClamp, { maxRows: clampDynamic ? maxStreamRows : undefined, paddingX: paddingX, children: [hasStreaming && (_jsx(StreamingMessage, { content: streamingContent, reasoning: streamingReasoning, tools: streamingTools, parts: streamingParts, terminalColumns: terminalColumns, showThinking: showThinking, expandedToolOutput: expandedToolOutput, verboseTrace: verboseTrace, pendingApproval: pendingApproval, nowTick: nowTick })), pendingSteerMessages.length > 0 && (_jsx(PendingInputMessagesBlock, { messages: pendingSteerMessages, terminalColumns: terminalColumns, title: "Messages to steer at next model call", hint: "applies before the next provider request", bulletColor: theme.warning })), queuedInputMessages.length > 0 && (_jsx(PendingInputMessagesBlock, { messages: queuedInputMessages, terminalColumns: terminalColumns, title: "Messages queued for next turn", hint: "runs after the current answer", bulletColor: theme.muted }))] }))] }));
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Bounds the live (in-progress turn) region to at most `maxRows` rows, pinned
|
|
54
|
+
* to the bottom so the user always sees the latest output (tail view). A live
|
|
55
|
+
* frame taller than the terminal pane breaks Ink's in-place redraw under tmux
|
|
56
|
+
* (the cursor-up clear can't reach rows that scrolled off), leaving large blank
|
|
57
|
+
* gaps and stray glyphs. We measure the natural content height and only clip
|
|
58
|
+
* when it actually exceeds `maxRows`, so short turns keep their natural height
|
|
59
|
+
* (no reserved-space gap). The full turn still lands in <Static> scrollback the
|
|
60
|
+
* moment it commits, so nothing is lost — only the live preview is windowed.
|
|
61
|
+
*/
|
|
62
|
+
function DynamicClamp({ maxRows, paddingX, children, }) {
|
|
63
|
+
const innerRef = React.useRef(null);
|
|
64
|
+
const [offset, setOffset] = React.useState(0);
|
|
65
|
+
const [clipHeight, setClipHeight] = React.useState(undefined);
|
|
66
|
+
// Re-measure after every commit (streaming changes height with each token).
|
|
67
|
+
// useLayoutEffect runs before Ink flushes the frame to the terminal, so the
|
|
68
|
+
// clamp is applied in the same paint — avoiding a one-frame overflow flash
|
|
69
|
+
// under tmux when a turn first grows past the pane. setState bails out via
|
|
70
|
+
// Object.is when nothing moved, so the steady state does not loop.
|
|
71
|
+
React.useLayoutEffect(() => {
|
|
72
|
+
if (!maxRows || !innerRef.current) {
|
|
73
|
+
if (offset !== 0)
|
|
74
|
+
setOffset(0);
|
|
75
|
+
if (clipHeight !== undefined)
|
|
76
|
+
setClipHeight(undefined);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const height = measureElement(innerRef.current).height;
|
|
80
|
+
if (height > maxRows) {
|
|
81
|
+
const nextOffset = -(height - maxRows);
|
|
82
|
+
if (nextOffset !== offset)
|
|
83
|
+
setOffset(nextOffset);
|
|
84
|
+
if (clipHeight !== maxRows)
|
|
85
|
+
setClipHeight(maxRows);
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
if (offset !== 0)
|
|
89
|
+
setOffset(0);
|
|
90
|
+
if (clipHeight !== undefined)
|
|
91
|
+
setClipHeight(undefined);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
return (_jsx(Box, { flexDirection: "column", flexShrink: 0, paddingX: paddingX, ...(clipHeight !== undefined ? { height: clipHeight, overflowY: "hidden" } : {}), children: _jsx(Box, { ref: innerRef, flexDirection: "column", flexShrink: 0, marginTop: offset, children: children }) }));
|
|
38
95
|
}
|
|
39
96
|
// Memoized: with no <Static> region, every transcript row re-renders on each
|
|
40
97
|
// state change unless its props are referentially stable. Message objects are
|
|
41
98
|
// append-only (compaction reuses already-compacted instances), keys are
|
|
42
99
|
// stable, and nowTick is only threaded to the last row, so memo hits for all
|
|
43
100
|
// settled history rows.
|
|
44
|
-
const MessageItem = React.memo(function MessageItem({ message, terminalColumns, verboseTrace, showExpandHint, nowTick, }) {
|
|
101
|
+
const MessageItem = React.memo(function MessageItem({ message, terminalColumns, showThinking, expandedToolOutput, verboseTrace, showExpandHint, separateFromPrevious, nowTick, }) {
|
|
45
102
|
const theme = useTheme();
|
|
46
103
|
if (message.role === "user") {
|
|
47
|
-
return (_jsx(UserMessageBlock, { content: message.content, terminalColumns: terminalColumns, inputStatus: message.inputStatus }));
|
|
104
|
+
return (_jsx(UserMessageBlock, { content: message.content, terminalColumns: terminalColumns, inputStatus: message.inputStatus, separateFromPrevious: separateFromPrevious }));
|
|
48
105
|
}
|
|
49
106
|
if (message.role === "error") {
|
|
50
107
|
return (_jsx(Box, { marginBottom: 1, flexDirection: "column", children: _jsxs(Text, { color: theme.error, children: ["Error: ", message.content] }) }));
|
|
@@ -56,15 +113,18 @@ const MessageItem = React.memo(function MessageItem({ message, terminalColumns,
|
|
|
56
113
|
return (_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { color: theme.error, children: "\u23F9 " }), _jsx(Text, { color: theme.muted, dimColor: true, children: message.content || "Interrupted by user" })] }));
|
|
57
114
|
}
|
|
58
115
|
const visibleReasoning = sanitizeInternalReminderBlocks(message.reasoning ?? "").trim();
|
|
59
|
-
|
|
116
|
+
// Same defense as reasoning: strip any internal reminder markup the model
|
|
117
|
+
// echoed back into its visible answer so it never reaches the transcript.
|
|
118
|
+
const visibleContent = sanitizeInternalReminderBlocks(message.content ?? "");
|
|
119
|
+
const hasVisibleAssistantContent = !!visibleContent.trim() ||
|
|
60
120
|
(message.toolCalls?.length ?? 0) > 0 ||
|
|
61
121
|
(message.parts?.length ?? 0) > 0 ||
|
|
62
|
-
(!!visibleReasoning && verboseTrace);
|
|
122
|
+
(!!visibleReasoning && (showThinking || verboseTrace));
|
|
63
123
|
if (!hasVisibleAssistantContent)
|
|
64
124
|
return null;
|
|
65
|
-
return (_jsxs(Box, { marginTop: 1, marginBottom: 1, flexDirection: "column", children: [visibleReasoning && verboseTrace && _jsx(ReasoningTraceBlock, { reasoning: visibleReasoning }), message.parts && message.parts.length > 0 ? (_jsx(MessageParts, { parts: message.parts, terminalColumns: terminalColumns, verboseTrace: verboseTrace, pendingApproval: undefined, showExpandHint: showExpandHint, nowTick: nowTick })) : (_jsxs(_Fragment, { children: [message.toolCalls && (_jsx(ToolsPart, { toolCalls: message.toolCalls, terminalColumns: terminalColumns, verboseTrace: verboseTrace, pendingApproval: undefined, showExpandHint: showExpandHint, nowTick: nowTick })),
|
|
125
|
+
return (_jsxs(Box, { marginTop: 1, marginBottom: 1, flexDirection: "column", children: [visibleReasoning && (showThinking || verboseTrace) && _jsx(ReasoningTraceBlock, { reasoning: visibleReasoning }), message.parts && message.parts.length > 0 ? (_jsx(MessageParts, { parts: message.parts, terminalColumns: terminalColumns, expandedToolOutput: expandedToolOutput, verboseTrace: verboseTrace, pendingApproval: undefined, showExpandHint: showExpandHint, nowTick: nowTick })) : (_jsxs(_Fragment, { children: [message.toolCalls && (_jsx(ToolsPart, { toolCalls: message.toolCalls, terminalColumns: terminalColumns, expandedToolOutput: expandedToolOutput, verboseTrace: verboseTrace, pendingApproval: undefined, showExpandHint: showExpandHint, nowTick: nowTick })), visibleContent.trim() && _jsx(MarkdownContent, { content: visibleContent })] })), verboseTrace && message.toolCalls && message.toolCalls.length > 0 && (_jsx(TurnDigest, { toolCalls: message.toolCalls })), message.taskElapsedMs !== undefined && (_jsx(TaskDurationLine, { elapsedMs: message.taskElapsedMs }))] }));
|
|
66
126
|
});
|
|
67
|
-
function StreamingMessage({ content, reasoning, tools, parts, terminalColumns, verboseTrace, pendingApproval, nowTick, }) {
|
|
127
|
+
function StreamingMessage({ content, reasoning, tools, parts, terminalColumns, showThinking, expandedToolOutput, verboseTrace, pendingApproval, nowTick, }) {
|
|
68
128
|
const deferredContent = React.useDeferredValue(content);
|
|
69
129
|
const deferredReasoning = React.useDeferredValue(reasoning);
|
|
70
130
|
const deferredParts = React.useDeferredValue(parts);
|
|
@@ -72,22 +132,22 @@ function StreamingMessage({ content, reasoning, tools, parts, terminalColumns, v
|
|
|
72
132
|
const visibleParts = deferredParts.length > 0
|
|
73
133
|
? deferredParts
|
|
74
134
|
: fallbackStreamingParts(deferredContent, tools);
|
|
75
|
-
return (_jsxs(Box, { flexDirection: "column", children: [visibleReasoning && verboseTrace && (_jsx(Box, { marginTop: 1, flexDirection: "column", children: _jsx(ReasoningTraceBlock, { reasoning: visibleReasoning }) })), visibleParts.length > 0 && (
|
|
135
|
+
return (_jsxs(Box, { flexDirection: "column", children: [visibleReasoning && (showThinking || verboseTrace) && (_jsx(Box, { marginTop: 1, flexDirection: "column", children: _jsx(ReasoningTraceBlock, { reasoning: visibleReasoning }) })), visibleParts.length > 0 && (
|
|
76
136
|
// marginTop=1 matches the committed MessageItem layout exactly, so the
|
|
77
137
|
// gap under the user message is identical while streaming and after the
|
|
78
138
|
// turn commits — no spacing jump at finalize time. (The old marginTop=0
|
|
79
139
|
// was a flicker mitigation for the main-screen <Static> renderer; the
|
|
80
140
|
// alt-screen viewport repaints frames atomically, so it's obsolete.)
|
|
81
|
-
_jsx(Box, { marginTop: 1, marginBottom: 1, flexDirection: "column", children: _jsx(MessageParts, { parts: visibleParts, terminalColumns: terminalColumns, verboseTrace: verboseTrace, pendingApproval: pendingApproval, showExpandHint: true, nowTick: nowTick, showActivity: true, streaming: true }) }))] }));
|
|
141
|
+
_jsx(Box, { marginTop: 1, marginBottom: 1, flexDirection: "column", children: _jsx(MessageParts, { parts: visibleParts, terminalColumns: terminalColumns, expandedToolOutput: expandedToolOutput, verboseTrace: verboseTrace, pendingApproval: pendingApproval, showExpandHint: true, nowTick: nowTick, showActivity: true, streaming: true }) }))] }));
|
|
82
142
|
}
|
|
83
|
-
function MessageParts({ parts, terminalColumns, verboseTrace, pendingApproval, showExpandHint, nowTick, showActivity = false, streaming = false, }) {
|
|
143
|
+
function MessageParts({ parts, terminalColumns, expandedToolOutput, verboseTrace, pendingApproval, showExpandHint, nowTick, showActivity = false, streaming = false, }) {
|
|
84
144
|
const lastToolsPartIndex = findLastToolsPartIndex(parts);
|
|
85
145
|
const lastTextPartIndex = findLastTextPartIndex(parts);
|
|
86
146
|
return (_jsx(Box, { flexDirection: "column", children: parts.map((part, idx) => {
|
|
87
147
|
if (part.type === "text") {
|
|
88
148
|
return (_jsx(TimelineText, { content: part.content, compactTop: idx === 0, terminalColumns: terminalColumns, streaming: streaming && idx === lastTextPartIndex }, `text-${idx}`));
|
|
89
149
|
}
|
|
90
|
-
return (_jsx(ToolsPart, { toolCalls: part.toolCalls, terminalColumns: terminalColumns, verboseTrace: verboseTrace, pendingApproval: pendingApproval, showExpandHint: showExpandHint && idx === lastToolsPartIndex, compactTop: idx === 0, nowTick: nowTick, showActivity: showActivity && idx === lastToolsPartIndex }, `tools-${idx}`));
|
|
150
|
+
return (_jsx(ToolsPart, { toolCalls: part.toolCalls, terminalColumns: terminalColumns, expandedToolOutput: expandedToolOutput, verboseTrace: verboseTrace, pendingApproval: pendingApproval, showExpandHint: showExpandHint && idx === lastToolsPartIndex, compactTop: idx === 0, nowTick: nowTick, showActivity: showActivity && idx === lastToolsPartIndex }, `tools-${idx}`));
|
|
91
151
|
}) }));
|
|
92
152
|
}
|
|
93
153
|
function findLastTextPartIndex(parts) {
|
|
@@ -99,25 +159,32 @@ function findLastTextPartIndex(parts) {
|
|
|
99
159
|
}
|
|
100
160
|
function TimelineText({ content, compactTop, terminalColumns, streaming = false, }) {
|
|
101
161
|
const theme = useTheme();
|
|
102
|
-
|
|
162
|
+
// Strip any internal reminder/context markup the model echoed back into its
|
|
163
|
+
// visible text — the reasoning path already does this. Without it, a model
|
|
164
|
+
// that parrots a <bubble_internal_*> block (e.g. an injected system reminder)
|
|
165
|
+
// leaks it straight into the transcript. The streaming sanitizer also holds a
|
|
166
|
+
// half-typed block until it closes, so partial markup never flashes.
|
|
167
|
+
const visible = sanitizeInternalReminderBlocks(content);
|
|
168
|
+
if (!visible.trim())
|
|
103
169
|
return null;
|
|
104
|
-
// marginLeft (2) + "
|
|
170
|
+
// marginLeft (2) + "● " marker (3 visual cells) = 5 cells consumed by the
|
|
105
171
|
// timeline gutter; pass the remaining width so wide blocks like tables size
|
|
106
172
|
// themselves against the actual content area instead of the raw terminal.
|
|
107
173
|
const available = terminalColumns ? Math.max(20, terminalColumns - 5) : undefined;
|
|
108
|
-
const trimmed =
|
|
109
|
-
return (_jsxs(Box, { marginLeft: 2, marginTop: compactTop ? 0 : 1, children: [_jsx(Text, { color: theme.agent, children: "\
|
|
174
|
+
const trimmed = visible.trim();
|
|
175
|
+
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 })) })] }));
|
|
110
176
|
}
|
|
111
|
-
function ToolsPart({ toolCalls, terminalColumns, verboseTrace, pendingApproval, showExpandHint, compactTop = false, nowTick, showActivity = false, }) {
|
|
177
|
+
function ToolsPart({ toolCalls, terminalColumns, expandedToolOutput, verboseTrace, pendingApproval, showExpandHint, compactTop = false, nowTick, showActivity = false, }) {
|
|
112
178
|
if (toolCalls.length === 0)
|
|
113
179
|
return null;
|
|
114
|
-
|
|
180
|
+
const expandTools = verboseTrace || expandedToolOutput;
|
|
181
|
+
if (!expandTools) {
|
|
115
182
|
return (_jsx(TraceGroupList, { toolCalls: toolCalls, terminalColumns: terminalColumns, pendingApproval: pendingApproval, nowTick: nowTick, compactTop: compactTop, showActivity: showActivity }));
|
|
116
183
|
}
|
|
117
184
|
const lastIdx = toolCalls.length - 1;
|
|
118
185
|
return (_jsx(Box, { flexDirection: "column", children: toolCalls.map((tc, idx) => {
|
|
119
186
|
const isWaitingApproval = isToolPending(tc) && !!pendingApproval && approvalMatchesTool(pendingApproval, tc);
|
|
120
|
-
return (_jsx(ToolCallDisplay, { toolCall: tc, isStreaming: isToolPending(tc), verbose:
|
|
187
|
+
return (_jsx(ToolCallDisplay, { toolCall: tc, isStreaming: isToolPending(tc), verbose: expandTools, terminalColumns: terminalColumns, showExpandHint: showExpandHint && idx === lastIdx, waitingApproval: isWaitingApproval, compactTop: idx === 0 && compactTop, nowTick: nowTick }, tc.id));
|
|
121
188
|
}) }));
|
|
122
189
|
}
|
|
123
190
|
function fallbackStreamingParts(content, tools) {
|
|
@@ -236,7 +303,7 @@ function CompactionSummaryBlock({ message }) {
|
|
|
236
303
|
const summary = message.compactionSummary?.trim();
|
|
237
304
|
return (_jsxs(Box, { marginTop: 1, marginBottom: 1, paddingX: 1, flexDirection: "column", borderStyle: "round", borderColor: theme.borderActive, children: [_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: theme.success, bold: true, children: "\u2713 " }), _jsx(Text, { color: theme.accent, bold: true, children: "Compaction checkpoint" }), _jsxs(Text, { color: theme.muted, children: [" \u00B7 ", status] })] }), summary && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: theme.muted, dimColor: true, children: "Preserved context summary" }), _jsx(Box, { paddingLeft: 2, flexDirection: "column", children: _jsx(MarkdownContent, { content: summary }) })] }))] }));
|
|
238
305
|
}
|
|
239
|
-
function UserMessageBlock({ content, terminalColumns, inputStatus, }) {
|
|
306
|
+
function UserMessageBlock({ content, terminalColumns, inputStatus, separateFromPrevious = false, }) {
|
|
240
307
|
const theme = useTheme();
|
|
241
308
|
const badge = userInputStatusBadgeLabel(inputStatus);
|
|
242
309
|
// Rail and its right gutter must share the bubble background; otherwise the
|
|
@@ -244,10 +311,22 @@ function UserMessageBlock({ content, terminalColumns, inputStatus, }) {
|
|
|
244
311
|
const railWidth = 2;
|
|
245
312
|
const horizontalRoom = Math.max(20, terminalColumns - 2);
|
|
246
313
|
const bubbleTextWidth = Math.max(1, horizontalRoom - railWidth - 2);
|
|
247
|
-
const
|
|
248
|
-
|
|
314
|
+
const { bodyLines, referenceLines } = splitImageDisplayContent(content);
|
|
315
|
+
const wrappedLines = bodyLines
|
|
249
316
|
.flatMap((line) => wrapByVisualWidth(line, bubbleTextWidth));
|
|
250
|
-
|
|
317
|
+
const attachmentReferenceIndent = " ".repeat(railWidth + 1);
|
|
318
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: separateFromPrevious ? 1 : 0, children: [badge && (_jsxs(Box, { children: [_jsx(Text, { bold: true, color: inputStatus === "pending_steer" ? theme.warning : theme.muted, children: ` ${badge} ` }), _jsx(Text, { color: theme.dim, children: inputStatus === "pending_steer" ? "applies at the next model call" : "runs after this turn" })] })), wrappedLines.map((line, index) => (_jsxs(Box, { children: [_jsx(Text, { backgroundColor: theme.userMessageBg, color: theme.userRail, children: "▌ " }), _jsx(Text, { backgroundColor: theme.userMessageBg, color: theme.userMessageText, children: ` ${padVisual(line || " ", bubbleTextWidth)} ` })] }, index))), referenceLines.map((line, index) => (_jsx(Box, { children: _jsx(Text, { color: theme.muted, children: `${attachmentReferenceIndent}${line}` }) }, `attachment-${index}`)))] }));
|
|
319
|
+
}
|
|
320
|
+
function PendingInputMessagesBlock({ messages, terminalColumns, title, hint, bulletColor, }) {
|
|
321
|
+
const theme = useTheme();
|
|
322
|
+
const contentWidth = Math.max(20, terminalColumns - 5);
|
|
323
|
+
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) => {
|
|
324
|
+
const { bodyLines, referenceLines } = splitImageDisplayContent(message.content || " ");
|
|
325
|
+
const wrappedBody = bodyLines.flatMap((line) => wrapByVisualWidth(line || " ", contentWidth));
|
|
326
|
+
const bodyRows = wrappedBody.map((line, lineIndex) => (_jsxs(Box, { marginLeft: 2, children: [_jsx(Text, { color: theme.dim, children: lineIndex === 0 ? "↳ " : " " }), _jsx(Text, { color: theme.inputText, children: line })] }, `body-${message.key ?? messageIndex}-${lineIndex}`)));
|
|
327
|
+
const attachmentRows = referenceLines.map((line, lineIndex) => (_jsx(Box, { marginLeft: 2, children: _jsxs(Text, { color: theme.dim, children: [" ", line] }) }, `attachment-${message.key ?? messageIndex}-${lineIndex}`)));
|
|
328
|
+
return [...bodyRows, ...attachmentRows];
|
|
329
|
+
})] }));
|
|
251
330
|
}
|
|
252
331
|
const TOOL_DISPLAY_NAMES = {
|
|
253
332
|
read: "Read",
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { ProviderRegistry } from "../provider-registry.js";
|
|
2
|
+
import type { ThinkingLevel } from "../types.js";
|
|
2
3
|
export { padVisual, truncateVisual } from "../text-display.js";
|
|
3
4
|
export interface ModelPickerOption {
|
|
4
5
|
id: string;
|
|
5
6
|
label: string;
|
|
6
7
|
group: string;
|
|
7
8
|
providerBadge: string;
|
|
9
|
+
reasoningLevels: ThinkingLevel[];
|
|
8
10
|
}
|
|
9
11
|
export type PickerKeyAction = "up" | "down" | "enter" | "escape" | "backspace" | "delete";
|
|
10
12
|
export declare function resolvePickerKeyAction(input: string, key: {
|
|
@@ -23,14 +25,35 @@ export declare function formatSkillPickerRow(skill: {
|
|
|
23
25
|
selected: boolean;
|
|
24
26
|
width: number;
|
|
25
27
|
}): string;
|
|
28
|
+
export declare const MODEL_PICKER_MAX_BODY_ROWS = 10;
|
|
29
|
+
export declare const MODEL_PICKER_CHROME_ROWS = 13;
|
|
30
|
+
export declare function modelPickerBodyRows(termHeight: number): number;
|
|
31
|
+
export declare function clampPickerIndex(index: number, length: number): number;
|
|
32
|
+
export declare function pickerWindowStart(selectedIndex: number, length: number, visibleRows: number): number;
|
|
33
|
+
export declare function padPickerRows(rows: string[], bodyRows: number, width: number): string[];
|
|
34
|
+
export declare function formatReasoningLevelsLabel(levels: readonly ThinkingLevel[], asToggle?: boolean): string;
|
|
35
|
+
export declare function formatModelPickerRow(option: Pick<ModelPickerOption, "id" | "label" | "providerBadge" | "reasoningLevels">, options: {
|
|
36
|
+
selected: boolean;
|
|
37
|
+
current: boolean;
|
|
38
|
+
width: number;
|
|
39
|
+
}): string;
|
|
40
|
+
export declare function formatEffortPickerRow(level: ThinkingLevel, options: {
|
|
41
|
+
selected: boolean;
|
|
42
|
+
width: number;
|
|
43
|
+
asToggle?: boolean;
|
|
44
|
+
}): string;
|
|
45
|
+
export declare function formatNoModelResultsRow(query: string, width: number): string;
|
|
46
|
+
export declare function preferredEffortIndex(option: Pick<ModelPickerOption, "reasoningLevels">, currentThinkingLevel: ThinkingLevel): number;
|
|
47
|
+
export declare function shouldOpenEffortPicker(option: Pick<ModelPickerOption, "reasoningLevels">): boolean;
|
|
26
48
|
export interface ModelPickerProps {
|
|
27
49
|
registry: ProviderRegistry;
|
|
28
50
|
current: string;
|
|
51
|
+
currentThinkingLevel: ThinkingLevel;
|
|
29
52
|
recent: string[];
|
|
30
|
-
onSelect: (model: string) => void;
|
|
53
|
+
onSelect: (model: string, thinkingLevel: ThinkingLevel) => void;
|
|
31
54
|
onCancel: () => void;
|
|
32
55
|
}
|
|
33
|
-
export declare function ModelPicker({ registry, current, recent, onSelect, onCancel }: ModelPickerProps): import("react/jsx-runtime").JSX.Element;
|
|
56
|
+
export declare function ModelPicker({ registry, current, currentThinkingLevel, recent, onSelect, onCancel }: ModelPickerProps): import("react/jsx-runtime").JSX.Element;
|
|
34
57
|
export declare function buildLocalModelOptions(registry: ProviderRegistry, current: string, recent: string[]): ModelPickerOption[];
|
|
35
58
|
export interface ProviderPickerProps {
|
|
36
59
|
providers: Array<{
|