@linxiraos/pi-tui 1.2.1 → 1.3.1
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/CHANGELOG.md +245 -224
- package/README.md +36 -0
- package/THIRD-PARTY-NOTICES.txt +55 -5
- package/dist/types/app-keybindings.d.ts +18 -8
- package/dist/types/apps/git/diff-pane.d.ts +5 -0
- package/dist/types/apps/git/git-tui.d.ts +16 -2
- package/dist/types/apps/git/state.d.ts +11 -0
- package/dist/types/autocomplete.d.ts +2 -0
- package/dist/types/charts/chart-svg.d.ts +7 -13
- package/dist/types/chat/advisor-message.d.ts +4 -1
- package/dist/types/chat/assistant-message.d.ts +9 -4
- package/dist/types/chat/figure-markdown.d.ts +7 -2
- package/dist/types/chat/messages.d.ts +3 -1
- package/dist/types/chat/read-tool-group.d.ts +2 -2
- package/dist/types/chat/table-chart.d.ts +11 -15
- package/dist/types/chat/tool-execution.d.ts +32 -20
- package/dist/types/chat/user-message.d.ts +3 -2
- package/dist/types/chrome/countdown-timer.d.ts +3 -2
- package/dist/types/chrome/diff.d.ts +2 -3
- package/dist/types/chrome/status-notice.d.ts +2 -0
- package/dist/types/chrome/transcript-container.d.ts +2 -0
- package/dist/types/components/image.d.ts +2 -0
- package/dist/types/components/markdown.d.ts +49 -3
- package/dist/types/components/text.d.ts +2 -0
- package/dist/types/debug-server.d.ts +2 -0
- package/dist/types/keybinding-matchers.d.ts +6 -0
- package/dist/types/multiplexers/tmux.d.ts +1 -0
- package/dist/types/multiplexers/types.d.ts +6 -0
- package/dist/types/native/backend.d.ts +5 -4
- package/dist/types/native/blobs.d.ts +24 -12
- package/dist/types/overlays/agent-hub-projection.d.ts +10 -5
- package/dist/types/overlays/annotation-overlay.d.ts +19 -0
- package/dist/types/overlays/approval-dialog.d.ts +79 -0
- package/dist/types/overlays/ask-dialog.d.ts +11 -6
- package/dist/types/overlays/btw-history-panel.d.ts +2 -6
- package/dist/types/overlays/hook-editor.d.ts +7 -2
- package/dist/types/overlays/hook-input.d.ts +12 -4
- package/dist/types/overlays/job-tree.d.ts +50 -0
- package/dist/types/overlays/jobs-panel.d.ts +25 -11
- package/dist/types/overlays/login-dialog.d.ts +8 -3
- package/dist/types/overlays/mcp-add-wizard.d.ts +9 -1
- package/dist/types/overlays/model-browser.d.ts +6 -1
- package/dist/types/overlays/model-picker.d.ts +2 -0
- package/dist/types/overlays/pause-screen.d.ts +5 -0
- package/dist/types/overlays/plan-review-overlay.d.ts +4 -0
- package/dist/types/overlays/report-panel.d.ts +5 -1
- package/dist/types/overlays/reset-usage-selector.d.ts +7 -5
- package/dist/types/overlays/session-account-selector.d.ts +4 -0
- package/dist/types/overlays/session-info-overlay.d.ts +7 -3
- package/dist/types/overlays/session-selector.d.ts +3 -1
- package/dist/types/overlays/usage-dashboard.d.ts +6 -6
- package/dist/types/prompt/composer.d.ts +23 -20
- package/dist/types/prompt/custom-editor.d.ts +20 -10
- package/dist/types/prompt/image-references.d.ts +22 -0
- package/dist/types/prompt/internal-url-autocomplete.d.ts +1 -0
- package/dist/types/prompt/macos-spelling.d.ts +2 -1
- package/dist/types/prompt/prose-gate.d.ts +3 -0
- package/dist/types/prompt/queued-messages.d.ts +1 -1
- package/dist/types/prompt/typing-word.d.ts +25 -0
- package/dist/types/prompt/welcome.d.ts +26 -6
- package/dist/types/render/hanging-wrap.d.ts +7 -0
- package/dist/types/render/hyperlink.d.ts +2 -0
- package/dist/types/render/render-utils.d.ts +9 -1
- package/dist/types/render/utils.d.ts +1 -1
- package/dist/types/setup/scenes/splash.d.ts +6 -5
- package/dist/types/status-line/context-usage.d.ts +4 -1
- package/dist/types/status-line/host.d.ts +6 -4
- package/dist/types/status-line/startup.d.ts +6 -6
- package/dist/types/status-line/types.d.ts +3 -0
- package/dist/types/stdin-buffer.d.ts +7 -0
- package/dist/types/terminal-capabilities.d.ts +16 -1
- package/dist/types/terminal-multiplexer.d.ts +7 -0
- package/dist/types/terminal.d.ts +8 -4
- package/dist/types/theme/symbols.d.ts +1 -1
- package/dist/types/theme/theme.d.ts +11 -0
- package/dist/types/thinking.d.ts +5 -0
- package/dist/types/tmux.d.ts +39 -4
- package/dist/types/tools/ast-edit.d.ts +2 -2
- package/dist/types/tools/ast-grep.d.ts +2 -2
- package/dist/types/tools/bash.d.ts +6 -1
- package/dist/types/tools/edit.d.ts +2 -0
- package/dist/types/tools/eval.d.ts +53 -4
- package/dist/types/tools/find.d.ts +14 -4
- package/dist/types/tools/glob.d.ts +3 -3
- package/dist/types/tools/grep.d.ts +2 -2
- package/dist/types/tools/grouped-file-output.d.ts +9 -4
- package/dist/types/tools/native-view.d.ts +1 -1
- package/dist/types/tools/read.d.ts +32 -10
- package/dist/types/tools/renderer.d.ts +2 -0
- package/dist/types/tools/task.d.ts +31 -0
- package/dist/types/tools/web-search-types.d.ts +1 -0
- package/dist/types/tools/write.d.ts +2 -0
- package/dist/types/tui.d.ts +20 -3
- package/package.json +10 -10
- package/src/app-keybindings.d.ts +18 -8
- package/src/app-keybindings.ts +20 -8
- package/src/apps/autoresearch-dashboard.ts +444 -27
- package/src/apps/debug/raw-sse.ts +2 -2
- package/src/apps/git/diff-pane.ts +39 -41
- package/src/apps/git/git-tui.ts +80 -14
- package/src/apps/git/sidebar.ts +10 -1
- package/src/apps/git/state.ts +12 -0
- package/src/autocomplete.d.ts +2 -0
- package/src/autocomplete.ts +21 -19
- package/src/bracketed-paste.ts +9 -2
- package/src/charts/chart-svg.d.ts +7 -13
- package/src/charts/chart-svg.ts +19 -22
- package/src/charts/table-data.ts +17 -6
- package/src/chat/advisor-message.d.ts +4 -1
- package/src/chat/advisor-message.ts +77 -24
- package/src/chat/assistant-message.d.ts +9 -4
- package/src/chat/assistant-message.ts +127 -31
- package/src/chat/bash-execution.ts +7 -2
- package/src/chat/figure-markdown.d.ts +7 -2
- package/src/chat/figure-markdown.ts +10 -3
- package/src/chat/image-loading.ts +9 -6
- package/src/chat/messages.d.ts +3 -1
- package/src/chat/messages.ts +4 -1
- package/src/chat/read-tool-group.d.ts +2 -2
- package/src/chat/read-tool-group.ts +97 -42
- package/src/chat/svg-figure.ts +3 -1
- package/src/chat/svg-source.ts +6 -3
- package/src/chat/table-chart.d.ts +11 -15
- package/src/chat/table-chart.ts +24 -57
- package/src/chat/tool-execution.d.ts +32 -20
- package/src/chat/tool-execution.ts +103 -36
- package/src/chat/user-message.d.ts +3 -2
- package/src/chat/user-message.ts +54 -34
- package/src/chrome/countdown-timer.ts +3 -3
- package/src/chrome/diff.d.ts +2 -3
- package/src/chrome/diff.ts +4 -9
- package/src/chrome/status-notice.ts +5 -0
- package/src/chrome/transcript-container.d.ts +2 -0
- package/src/chrome/transcript-container.ts +212 -28
- package/src/components/editor.ts +12 -12
- package/src/components/image.d.ts +2 -0
- package/src/components/image.ts +54 -20
- package/src/components/markdown.d.ts +49 -3
- package/src/components/markdown.ts +482 -12
- package/src/components/settings-list.ts +2 -4
- package/src/components/text.d.ts +2 -0
- package/src/components/text.ts +11 -1
- package/src/debug-server.d.ts +2 -0
- package/src/debug-server.ts +51 -69
- package/src/hotkeys-markdown.ts +2 -0
- package/src/keybinding-matchers.d.ts +6 -0
- package/src/keybinding-matchers.ts +19 -0
- package/src/multiplexers/tmux.d.ts +1 -0
- package/src/multiplexers/tmux.ts +3 -0
- package/src/multiplexers/types.d.ts +6 -0
- package/src/multiplexers/types.ts +6 -0
- package/src/native/backend.d.ts +5 -4
- package/src/native/backend.ts +13 -5
- package/src/native/blobs.d.ts +24 -12
- package/src/native/blobs.ts +56 -7
- package/src/native/reconcile.ts +43 -21
- package/src/overlays/advisor-config.ts +259 -8
- package/src/overlays/agent-hub-projection.d.ts +10 -5
- package/src/overlays/agent-hub-projection.ts +63 -12
- package/src/overlays/annotation-overlay.ts +422 -168
- package/src/overlays/approval-dialog.ts +389 -0
- package/src/overlays/ask-dialog.ts +244 -105
- package/src/overlays/btw-history-panel.ts +208 -147
- package/src/overlays/extensions/extension-list.ts +4 -6
- package/src/overlays/hook-editor.ts +90 -32
- package/src/overlays/hook-input.ts +105 -19
- package/src/overlays/job-tree.ts +177 -0
- package/src/overlays/jobs-panel.ts +222 -142
- package/src/overlays/login-dialog.ts +201 -91
- package/src/overlays/mcp-add-wizard.ts +562 -279
- package/src/overlays/model-browser.ts +32 -7
- package/src/overlays/model-hub.ts +4 -10
- package/src/overlays/model-picker.ts +58 -21
- package/src/overlays/pause-screen.ts +81 -23
- package/src/overlays/plan-review-overlay.ts +16 -0
- package/src/overlays/plugin-selector.ts +17 -1
- package/src/overlays/queue-mode-selector.ts +10 -0
- package/src/overlays/report-panel.ts +6 -2
- package/src/overlays/reset-usage-selector.ts +130 -21
- package/src/overlays/session-account-selector.ts +36 -0
- package/src/overlays/session-info-overlay.ts +199 -76
- package/src/overlays/session-selector.ts +15 -10
- package/src/overlays/settings-selector.ts +3 -0
- package/src/overlays/show-images-selector.ts +1 -0
- package/src/overlays/snapcompact-shape-preview.ts +13 -6
- package/src/overlays/theme-selector.ts +6 -1
- package/src/overlays/thinking-selector.ts +8 -4
- package/src/overlays/tree-selector.ts +117 -2
- package/src/overlays/usage-dashboard.ts +498 -144
- package/src/prompt/composer-cache.ts +4 -1
- package/src/prompt/composer.ts +200 -74
- package/src/prompt/custom-editor.ts +168 -65
- package/src/prompt/image-references.d.ts +22 -0
- package/src/prompt/image-references.ts +79 -2
- package/src/prompt/internal-url-autocomplete.ts +1 -0
- package/src/prompt/macos-spelling.ts +28 -5
- package/src/prompt/prose-gate.ts +3 -0
- package/src/prompt/queued-messages.ts +9 -2
- package/src/prompt/tips.txt +1 -2
- package/src/prompt/typing-word.ts +59 -0
- package/src/prompt/welcome.ts +97 -45
- package/src/render/code-cell.ts +18 -5
- package/src/render/hanging-wrap.d.ts +7 -0
- package/src/render/hanging-wrap.ts +58 -0
- package/src/render/hyperlink.d.ts +2 -0
- package/src/render/hyperlink.ts +5 -0
- package/src/render/render-utils.d.ts +9 -1
- package/src/render/render-utils.ts +16 -2
- package/src/render/utils.d.ts +1 -1
- package/src/render/utils.ts +1 -1
- package/src/render/width-aware-text.ts +6 -5
- package/src/setup/scenes/splash.ts +22 -8
- package/src/status-line/component.ts +79 -11
- package/src/status-line/context-usage.ts +176 -73
- package/src/status-line/footer.ts +18 -35
- package/src/status-line/host.ts +6 -1
- package/src/status-line/segments.ts +16 -5
- package/src/status-line/startup.ts +6 -6
- package/src/status-line/types.ts +1 -0
- package/src/stdin-buffer.d.ts +7 -0
- package/src/stdin-buffer.ts +300 -62
- package/src/terminal-capabilities.d.ts +16 -1
- package/src/terminal-capabilities.ts +66 -28
- package/src/terminal-multiplexer.d.ts +7 -0
- package/src/terminal-multiplexer.ts +14 -0
- package/src/terminal.d.ts +8 -4
- package/src/terminal.ts +190 -63
- package/src/theme/symbols.d.ts +1 -1
- package/src/theme/symbols.ts +1 -0
- package/src/theme/theme-class.ts +1 -0
- package/src/theme/theme.d.ts +11 -0
- package/src/theme/theme.ts +46 -14
- package/src/thinking.d.ts +5 -0
- package/src/thinking.ts +9 -1
- package/src/tmux.d.ts +39 -4
- package/src/tmux.ts +110 -16
- package/src/tools/ask.ts +20 -9
- package/src/tools/ast-edit.d.ts +2 -2
- package/src/tools/ast-edit.ts +30 -7
- package/src/tools/ast-grep.d.ts +2 -2
- package/src/tools/ast-grep.ts +39 -35
- package/src/tools/bash.d.ts +6 -1
- package/src/tools/bash.ts +15 -9
- package/src/tools/debug.ts +2 -1
- package/src/tools/default-renderer.ts +3 -2
- package/src/tools/edit.d.ts +2 -0
- package/src/tools/edit.ts +100 -21
- package/src/tools/eval.d.ts +53 -4
- package/src/tools/eval.ts +173 -137
- package/src/tools/find.d.ts +14 -4
- package/src/tools/find.ts +71 -18
- package/src/tools/glob.d.ts +3 -3
- package/src/tools/glob.ts +51 -17
- package/src/tools/grep.d.ts +2 -2
- package/src/tools/grep.ts +99 -22
- package/src/tools/grouped-file-output.d.ts +9 -4
- package/src/tools/grouped-file-output.ts +21 -6
- package/src/tools/lsp.ts +30 -18
- package/src/tools/mcp.ts +2 -2
- package/src/tools/native-view.d.ts +1 -1
- package/src/tools/native-view.ts +2 -2
- package/src/tools/read.d.ts +32 -10
- package/src/tools/read.ts +89 -43
- package/src/tools/renderer.d.ts +2 -0
- package/src/tools/renderer.ts +2 -0
- package/src/tools/task.d.ts +31 -0
- package/src/tools/task.ts +40 -7
- package/src/tools/todo.ts +3 -2
- package/src/tools/wait.ts +16 -30
- package/src/tools/web-search-types.d.ts +1 -0
- package/src/tools/web-search-types.ts +1 -0
- package/src/tools/web-search.ts +2 -1
- package/src/tools/write.d.ts +2 -0
- package/src/tools/write.ts +36 -12
- package/src/tools/yield.ts +2 -2
- package/src/tui.d.ts +20 -3
- package/src/tui.ts +160 -43
- package/src/utils.ts +5 -3
- package/dist/types/tools/eval-format/code-mode-declarations.d.ts +0 -5
- package/src/tools/eval-format/code-mode-declarations.ts +0 -61
package/src/tui.ts
CHANGED
|
@@ -31,10 +31,12 @@ import type { DescribeContext, NativeNode, NativeScreen, NativeSurfaceProvider,
|
|
|
31
31
|
import { STDOUT_BACKLOG_CLEAR_BYTES, setAltScreenActive, type Terminal } from "./terminal";
|
|
32
32
|
import { classifyTerminalMultiplexerModule, terminalMultiplexerSessions } from "./terminal-multiplexer";
|
|
33
33
|
import {
|
|
34
|
+
directPlacementLift,
|
|
34
35
|
encodeKittyDeleteAllImages,
|
|
35
36
|
encodeKittyDeleteImage,
|
|
36
37
|
encodeKittyDeletePlacement,
|
|
37
38
|
encodeKittyPlacementLine,
|
|
39
|
+
getTerminalBackgroundColor,
|
|
38
40
|
ImageProtocol,
|
|
39
41
|
isImageProtocolForced,
|
|
40
42
|
isInsideTerminalMultiplexer,
|
|
@@ -45,7 +47,9 @@ import {
|
|
|
45
47
|
synchronizedOutputUserOverride,
|
|
46
48
|
TERMINAL,
|
|
47
49
|
} from "./terminal-capabilities";
|
|
50
|
+
import { onTerminalBackgroundFallbackChange } from "./theme/theme";
|
|
48
51
|
import { compositeLineAt } from "./render/composite";
|
|
52
|
+
import { tmuxMultiplexer } from "./multiplexers/tmux";
|
|
49
53
|
import {
|
|
50
54
|
Ellipsis,
|
|
51
55
|
getWidthConfigEpoch,
|
|
@@ -56,6 +60,7 @@ import {
|
|
|
56
60
|
truncateToWidth,
|
|
57
61
|
visibleWidth,
|
|
58
62
|
} from "./utils";
|
|
63
|
+
import { tmuxClientRendersSixel } from "./tmux";
|
|
59
64
|
|
|
60
65
|
/** Full-attribute reset terminating each rendered content row. */
|
|
61
66
|
export const SEGMENT_RESET = "\x1b[0m";
|
|
@@ -105,6 +110,11 @@ const PAINT_END_NO_SYNC = ENABLE_AUTOWRAP;
|
|
|
105
110
|
// native text selection.
|
|
106
111
|
const MOUSE_TRACKING_ON = "\x1b[?1000h\x1b[?1003h\x1b[?1006h";
|
|
107
112
|
const MOUSE_TRACKING_OFF = "\x1b[?1006l\x1b[?1003l\x1b[?1000l";
|
|
113
|
+
// Cell-size replies: CSI 6 ; height ; width t in pixels, and iTerm2's
|
|
114
|
+
// OSC 1337 ; ReportCellSize = height ; width [; scale] ST in points.
|
|
115
|
+
const CELL_SIZE_REPLY = /^\x1b\[6;(\d+);(\d+)t$/;
|
|
116
|
+
const ITERM2_CELL_SIZE_REPLY =
|
|
117
|
+
/^\x1b\]1337;ReportCellSize=(\d+(?:\.\d+)?);(\d+(?:\.\d+)?)(?:;(\d+(?:\.\d+)?))?(?:\x07|\x1b\\)$/;
|
|
108
118
|
|
|
109
119
|
type MouseTrackingState = "off" | "inline" | "full";
|
|
110
120
|
|
|
@@ -254,10 +264,6 @@ export interface Component {
|
|
|
254
264
|
*/
|
|
255
265
|
render(width: number): readonly string[];
|
|
256
266
|
|
|
257
|
-
/** Inline decision panels must keep displaced settled transcript rows in
|
|
258
|
-
* native scrollback even when mounted inside a transient editor container. */
|
|
259
|
-
readonly retireDisplacedTranscript?: boolean;
|
|
260
|
-
|
|
261
267
|
/**
|
|
262
268
|
* Describe the component semantically for a Tern Surface Protocol
|
|
263
269
|
* terminal (see `native/node.ts`). Called instead of `render()` when the
|
|
@@ -823,6 +829,10 @@ export class TUI extends Container {
|
|
|
823
829
|
// rows the paint prepended for a short viewport. Negative while prepended
|
|
824
830
|
// blanks outweigh replaced rows; zero on ordinary frames.
|
|
825
831
|
#providerViewportPadTop = 0;
|
|
832
|
+
// Whether native history holds rows the provider committed since the last
|
|
833
|
+
// destructive reset. Only paints write history and only a destructive reset
|
|
834
|
+
// erases it; an `append` resize replay keeps the rows it replays beneath.
|
|
835
|
+
#providerHistoryWritten = false;
|
|
826
836
|
// Viewport-relative row of the hardware cursor after the last normal paint
|
|
827
837
|
// (0 = parked at the viewport top). A resize reflows the normal buffer
|
|
828
838
|
// before the app hears about it; terminals keep the cursor attached to its
|
|
@@ -928,6 +938,8 @@ export class TUI extends Container {
|
|
|
928
938
|
#debugNextWindowTop = 0;
|
|
929
939
|
#inputListeners = new Set<InputListener>();
|
|
930
940
|
#startListeners = new Set<StartListener>();
|
|
941
|
+
/** True from the end of start() until stop(): the terminal modes start listeners enable are live. */
|
|
942
|
+
#started = false;
|
|
931
943
|
#paintListeners = new Set<PaintListener>();
|
|
932
944
|
|
|
933
945
|
/** Global callback for debug key (Shift+Ctrl+D). Called before input is forwarded to focused component. */
|
|
@@ -1020,6 +1032,7 @@ export class TUI extends Container {
|
|
|
1020
1032
|
#forceViewportRepaintOnNextRender = false;
|
|
1021
1033
|
#hasEverRendered = false;
|
|
1022
1034
|
#stopped = false;
|
|
1035
|
+
#appearanceReportUnsubscribe: (() => void) | undefined;
|
|
1023
1036
|
#cancelPostmortemRestore?: () => void;
|
|
1024
1037
|
/** True between a `deferInput` start() and enableInput(). */
|
|
1025
1038
|
#inputDeferred = false;
|
|
@@ -1108,6 +1121,7 @@ export class TUI extends Container {
|
|
|
1108
1121
|
overlayStack: {
|
|
1109
1122
|
component: Component;
|
|
1110
1123
|
options?: OverlayOptions;
|
|
1124
|
+
/** Focus restored after closing; tracks live focus changes beneath an input-less key holder. */
|
|
1111
1125
|
preFocus: Component | null;
|
|
1112
1126
|
hidden: boolean;
|
|
1113
1127
|
/**
|
|
@@ -1315,6 +1329,9 @@ export class TUI extends Container {
|
|
|
1315
1329
|
setFocus(component: Component | null): void {
|
|
1316
1330
|
const holder = this.#getKeyHolderOverlay();
|
|
1317
1331
|
if (holder && !isOverlayFocusTarget(holder.component, component)) {
|
|
1332
|
+
if (!holder.component.handleInput && this.#focusedComponent === holder.component) {
|
|
1333
|
+
holder.preFocus = component;
|
|
1334
|
+
}
|
|
1318
1335
|
const currentFocus = this.#focusedComponent;
|
|
1319
1336
|
component = isOverlayFocusTarget(holder.component, currentFocus) ? currentFocus : holder.component;
|
|
1320
1337
|
}
|
|
@@ -1461,6 +1478,27 @@ export class TUI extends Container {
|
|
|
1461
1478
|
return { top: this.#providerViewportTop - this.#providerViewportPadTop, length: this.#providerWindow.length };
|
|
1462
1479
|
}
|
|
1463
1480
|
|
|
1481
|
+
/**
|
|
1482
|
+
* Rows of native history the last paint left on screen above a mutable
|
|
1483
|
+
* viewport that reached the bottom row, or `undefined` when that paint left
|
|
1484
|
+
* rows free below it (or none happened, or a destructive reset will repaint
|
|
1485
|
+
* from row zero). Rows above a bottom-reaching viewport may already sit in
|
|
1486
|
+
* native scrollback, which a later shrink cannot pull back down.
|
|
1487
|
+
*/
|
|
1488
|
+
getFilledHistoryRows(): number | undefined {
|
|
1489
|
+
if (!this.#hasEverRendered || this.#clearScrollbackOnNextRender) return undefined;
|
|
1490
|
+
const height = this.terminal.rows;
|
|
1491
|
+
if (this.#providerWindow.length === 0 || this.#providerViewportTop + this.#providerWindow.length < height) {
|
|
1492
|
+
return undefined;
|
|
1493
|
+
}
|
|
1494
|
+
return Math.min(this.#providerViewportTop, height);
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
/** Whether native history holds rows the frame provider committed since the last destructive reset. */
|
|
1498
|
+
hasProviderHistory(): boolean {
|
|
1499
|
+
return this.#providerHistoryWritten && !this.#clearScrollbackOnNextRender;
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1464
1502
|
/**
|
|
1465
1503
|
* Probe for opt-in normal-buffer click capture. The provider is read every
|
|
1466
1504
|
* frame; while it returns true (and no fullscreen overlay owns the
|
|
@@ -1544,6 +1582,34 @@ export class TUI extends Container {
|
|
|
1544
1582
|
|
|
1545
1583
|
start(options?: TUIStartOptions): void {
|
|
1546
1584
|
this.#stopped = false;
|
|
1585
|
+
this.#appearanceReportUnsubscribe?.();
|
|
1586
|
+
let backgroundRgb: number | undefined;
|
|
1587
|
+
let subscribed = true;
|
|
1588
|
+
const repaintForBackgroundChange = () => {
|
|
1589
|
+
if (!subscribed || this.#stopped) return;
|
|
1590
|
+
const nextBackgroundRgb = getTerminalBackgroundColor();
|
|
1591
|
+
// Track RGB before the SIXEL guard: the startup OSC 11 reply usually
|
|
1592
|
+
// arrives before DA1 enables SIXEL, so revisiting the same color once
|
|
1593
|
+
// SIXEL is live must not replay identical rows.
|
|
1594
|
+
if (nextBackgroundRgb === backgroundRgb) return;
|
|
1595
|
+
backgroundRgb = nextBackgroundRgb;
|
|
1596
|
+
if (TERMINAL.imageProtocol !== ImageProtocol.Sixel) return;
|
|
1597
|
+
// Track RGB even during optimistic native ownership: fallback paints
|
|
1598
|
+
// with the latest color, so a later return to the startup RGB must repaint.
|
|
1599
|
+
// Never write rows while the native surface still owns rendering.
|
|
1600
|
+
if (this.#nativeLive) return;
|
|
1601
|
+
this.invalidate();
|
|
1602
|
+
// Match theme changes: replay owned history on direct terminals, but
|
|
1603
|
+
// leave multiplexer scrollback intact and repaint only the viewport.
|
|
1604
|
+
this.requestRender(true, { clearScrollback: !isInsideTerminalMultiplexer() });
|
|
1605
|
+
};
|
|
1606
|
+
const unsubscribe = this.terminal.onAppearanceReport?.(repaintForBackgroundChange);
|
|
1607
|
+
const unsubscribeFallback = onTerminalBackgroundFallbackChange(repaintForBackgroundChange);
|
|
1608
|
+
this.#appearanceReportUnsubscribe = () => {
|
|
1609
|
+
subscribed = false;
|
|
1610
|
+
unsubscribe?.();
|
|
1611
|
+
unsubscribeFallback();
|
|
1612
|
+
};
|
|
1547
1613
|
this.#debugPaint = undefined;
|
|
1548
1614
|
this.#debugServer?.stop();
|
|
1549
1615
|
this.#debugServer = undefined;
|
|
@@ -1602,6 +1668,9 @@ export class TUI extends Container {
|
|
|
1602
1668
|
this.terminal.onSixelSupport?.(supported => {
|
|
1603
1669
|
if (!supported || this.#stopped || this.#nativeLive || this.terminal.tspExpected) return;
|
|
1604
1670
|
if (isImageProtocolForced()) return;
|
|
1671
|
+
// tmux answers DA1 for its own emulator; only its record of the
|
|
1672
|
+
// attached client says whether SIXEL reaches the outer terminal.
|
|
1673
|
+
if (tmuxMultiplexer.isInside() && !tmuxClientRendersSixel()) return;
|
|
1605
1674
|
// Keep consuming an already-requested XTSMGRAPHICS reply until it
|
|
1606
1675
|
// arrives or times out, even though DA1 has established support.
|
|
1607
1676
|
this.#enableSixelProtocol();
|
|
@@ -1668,16 +1737,14 @@ export class TUI extends Container {
|
|
|
1668
1737
|
() => this.stop(),
|
|
1669
1738
|
{ deferInput: this.#inputDeferred, isLoopStalled: () => this.#watchdog.isStalled() },
|
|
1670
1739
|
);
|
|
1740
|
+
// ProcessTerminal.start() resets the previous owner's RGB. Seed only
|
|
1741
|
+
// after that reset so an identical OSC 11 reply still repaints fallback rows.
|
|
1742
|
+
backgroundRgb = TERMINAL.imageProtocol === ImageProtocol.Sixel ? getTerminalBackgroundColor() : undefined;
|
|
1671
1743
|
if (this.#stopped) return;
|
|
1672
1744
|
this.#cancelPostmortemRestore?.();
|
|
1673
1745
|
this.#cancelPostmortemRestore = postmortem.register("tui-restore", () => this.stop());
|
|
1674
|
-
for (const listener of this.#startListeners)
|
|
1675
|
-
|
|
1676
|
-
listener();
|
|
1677
|
-
} catch {
|
|
1678
|
-
// Startup listeners are feature hooks; one broken hook must not prevent rendering.
|
|
1679
|
-
}
|
|
1680
|
-
}
|
|
1746
|
+
for (const listener of this.#startListeners) this.#runStartListener(listener);
|
|
1747
|
+
this.#started = !this.#stopped;
|
|
1681
1748
|
this.terminal.hideCursor();
|
|
1682
1749
|
this.#recordHardwareCursorHidden();
|
|
1683
1750
|
if (!this.#inputDeferred) {
|
|
@@ -1819,7 +1886,7 @@ export class TUI extends Container {
|
|
|
1819
1886
|
this.#resizeSettleTimer?.cancel();
|
|
1820
1887
|
this.#resizeSettleTimer = undefined;
|
|
1821
1888
|
if (this.#altActive || this.#resizeAltActive) {
|
|
1822
|
-
this.terminal.write(`${this.#takePendingAltEnter()}${this.#
|
|
1889
|
+
this.terminal.write(`${this.#takePendingAltEnter()}${this.#altScreenExit()}`);
|
|
1823
1890
|
setAltScreenActive(false);
|
|
1824
1891
|
this.#altActive = false;
|
|
1825
1892
|
this.#resizeAltActive = false;
|
|
@@ -2133,7 +2200,7 @@ export class TUI extends Container {
|
|
|
2133
2200
|
this.#suppressResizeUntil = this.#renderScheduler.now() + 100;
|
|
2134
2201
|
this.#altPreviousLines = [];
|
|
2135
2202
|
this.#altPreparedRows = [];
|
|
2136
|
-
const exitSequence =
|
|
2203
|
+
const exitSequence = this.#altScreenExit();
|
|
2137
2204
|
// Only the provider plan frame emits a pending exit, and an exit already
|
|
2138
2205
|
// pending (a fused overlay close) owns that slot.
|
|
2139
2206
|
if (this.#frameProvider !== undefined && this.#pendingAltExit === "") {
|
|
@@ -2445,13 +2512,29 @@ export class TUI extends Container {
|
|
|
2445
2512
|
if (this.#heldInput !== undefined && this.#heldFocus === previous) this.#heldFocus = next;
|
|
2446
2513
|
}
|
|
2447
2514
|
|
|
2515
|
+
/**
|
|
2516
|
+
* Register a hook that re-enables terminal modes on every start(). A hook
|
|
2517
|
+
* added while the TUI is already running (a prepaint composer starts it
|
|
2518
|
+
* before the interactive mode wires its controllers) missed that start, so
|
|
2519
|
+
* it runs immediately instead of waiting for a stop/start cycle that may
|
|
2520
|
+
* never come.
|
|
2521
|
+
*/
|
|
2448
2522
|
addStartListener(listener: StartListener): () => void {
|
|
2449
2523
|
this.#startListeners.add(listener);
|
|
2524
|
+
if (this.#started) this.#runStartListener(listener);
|
|
2450
2525
|
return () => {
|
|
2451
2526
|
this.#startListeners.delete(listener);
|
|
2452
2527
|
};
|
|
2453
2528
|
}
|
|
2454
2529
|
|
|
2530
|
+
#runStartListener(listener: StartListener): void {
|
|
2531
|
+
try {
|
|
2532
|
+
listener();
|
|
2533
|
+
} catch {
|
|
2534
|
+
// Startup listeners are feature hooks; one broken hook must not prevent rendering.
|
|
2535
|
+
}
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2455
2538
|
addInputListener(listener: InputListener): () => void {
|
|
2456
2539
|
this.#inputListeners.add(listener);
|
|
2457
2540
|
return () => {
|
|
@@ -2583,6 +2666,9 @@ export class TUI extends Container {
|
|
|
2583
2666
|
// Query terminal for cell size in pixels: CSI 16 t
|
|
2584
2667
|
// Response format: CSI 6 ; height ; width t
|
|
2585
2668
|
this.terminal.write("\x1b[16t");
|
|
2669
|
+
// iTerm2 never answers CSI 16 t. Without a size, rows reserved for an image
|
|
2670
|
+
// and crops of a partly scrolled one assume 9x18 cells and land offset.
|
|
2671
|
+
if (TERMINAL.imageProtocol === ImageProtocol.Iterm2) this.terminal.write("\x1b]1337;ReportCellSize\x1b\\");
|
|
2586
2672
|
}
|
|
2587
2673
|
|
|
2588
2674
|
/**
|
|
@@ -2635,6 +2721,8 @@ export class TUI extends Container {
|
|
|
2635
2721
|
}
|
|
2636
2722
|
|
|
2637
2723
|
stop(): void {
|
|
2724
|
+
this.#appearanceReportUnsubscribe?.();
|
|
2725
|
+
this.#appearanceReportUnsubscribe = undefined;
|
|
2638
2726
|
this.#cancelPostmortemRestore?.();
|
|
2639
2727
|
this.#cancelPostmortemRestore = undefined;
|
|
2640
2728
|
this.#debugServer?.stop();
|
|
@@ -2662,7 +2750,7 @@ export class TUI extends Container {
|
|
|
2662
2750
|
this.#cancelResizeProbe();
|
|
2663
2751
|
if (this.#resizeAltActive) {
|
|
2664
2752
|
this.#resizeAltActive = false;
|
|
2665
|
-
this.terminal.write(`${this.#takePendingAltEnter()}${this.#
|
|
2753
|
+
this.terminal.write(`${this.#takePendingAltEnter()}${this.#altScreenExit()}`);
|
|
2666
2754
|
setAltScreenActive(false);
|
|
2667
2755
|
}
|
|
2668
2756
|
if (this.#altActive || this.#pendingAltExit) {
|
|
@@ -2674,7 +2762,7 @@ export class TUI extends Container {
|
|
|
2674
2762
|
const mouseExit = this.#mouseTracking !== "off" ? MOUSE_TRACKING_OFF : "";
|
|
2675
2763
|
const exitSequence = this.#pendingAltExit
|
|
2676
2764
|
? `${this.#takePendingAltEnter()}${this.#pendingAltExit}${mouseExit}`
|
|
2677
|
-
: `${mouseExit}${this.#
|
|
2765
|
+
: `${mouseExit}${this.#altScreenExit()}`;
|
|
2678
2766
|
this.terminal.write(exitSequence);
|
|
2679
2767
|
setAltScreenActive(false);
|
|
2680
2768
|
this.#altActive = false;
|
|
@@ -2704,6 +2792,7 @@ export class TUI extends Container {
|
|
|
2704
2792
|
// (and live-session ghosts are already bounded by the inline-image budget).
|
|
2705
2793
|
this.#clearSixelProbeState();
|
|
2706
2794
|
this.#stopped = true;
|
|
2795
|
+
this.#started = false;
|
|
2707
2796
|
this.#watchdog.stop();
|
|
2708
2797
|
if (this.#renderTimer) {
|
|
2709
2798
|
this.#renderTimer.cancel();
|
|
@@ -2771,6 +2860,13 @@ export class TUI extends Container {
|
|
|
2771
2860
|
this.#requestOrdinaryRender();
|
|
2772
2861
|
}
|
|
2773
2862
|
|
|
2863
|
+
/** Run `callback` after `delayMs` on the render scheduler's clock, unless the TUI has stopped. */
|
|
2864
|
+
scheduleAfter(delayMs: number, callback: () => void): RenderTimer {
|
|
2865
|
+
return this.#renderScheduler.scheduleRender(() => {
|
|
2866
|
+
if (!this.#stopped) callback();
|
|
2867
|
+
}, delayMs);
|
|
2868
|
+
}
|
|
2869
|
+
|
|
2774
2870
|
/**
|
|
2775
2871
|
* Paint a forced frame synchronously when startup must hand off an already
|
|
2776
2872
|
* visible component tree before further async initialization. Same as
|
|
@@ -2986,9 +3082,9 @@ export class TUI extends Container {
|
|
|
2986
3082
|
this.#inputRenderGraceUntilMs = this.#renderScheduler.now() + TUI.#INPUT_RENDER_GRACE_MS;
|
|
2987
3083
|
}
|
|
2988
3084
|
|
|
2989
|
-
//
|
|
3085
|
+
// The effective input owner can reserve its gesture before TUI-wide listeners
|
|
2990
3086
|
// and debug shortcuts see the key, then handle the raw event exactly once.
|
|
2991
|
-
const focusedForCapture = this.#
|
|
3087
|
+
const focusedForCapture = this.#inputTarget();
|
|
2992
3088
|
if (
|
|
2993
3089
|
focusedForCapture?.handleInput &&
|
|
2994
3090
|
(!isKeyRelease(data) || focusedForCapture.wantsKeyRelease) &&
|
|
@@ -3027,22 +3123,28 @@ export class TUI extends Container {
|
|
|
3027
3123
|
return;
|
|
3028
3124
|
}
|
|
3029
3125
|
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
// Opted-in components only dirty their focused subtree. Unregistered
|
|
3033
|
-
// components retain the legacy full compose because their callbacks may
|
|
3034
|
-
// mutate siblings; focus changes also require the new surface to paint.
|
|
3035
|
-
const focused = this.#focusedComponent;
|
|
3036
|
-
if (focused?.handleInput) {
|
|
3126
|
+
const target = this.#inputTarget();
|
|
3127
|
+
if (target?.handleInput) {
|
|
3037
3128
|
// Filter out key release events unless component opts in
|
|
3038
|
-
if (isKeyRelease(data) && !
|
|
3129
|
+
if (isKeyRelease(data) && !target.wantsKeyRelease) {
|
|
3039
3130
|
return;
|
|
3040
3131
|
}
|
|
3041
|
-
|
|
3132
|
+
target.handleInput(data);
|
|
3042
3133
|
this.requestRender();
|
|
3043
3134
|
}
|
|
3044
3135
|
}
|
|
3045
3136
|
|
|
3137
|
+
/**
|
|
3138
|
+
* Component that receives keyboard input. An input-less key-holding overlay
|
|
3139
|
+
* leaves input with the focus it displaced; other focus owners keep their input.
|
|
3140
|
+
*/
|
|
3141
|
+
#inputTarget(): Component | null {
|
|
3142
|
+
const focused = this.#focusedComponent;
|
|
3143
|
+
if (focused?.handleInput) return focused;
|
|
3144
|
+
const holder = this.#getKeyHolderOverlay();
|
|
3145
|
+
return holder?.component === focused ? holder.preFocus : null;
|
|
3146
|
+
}
|
|
3147
|
+
|
|
3046
3148
|
/**
|
|
3047
3149
|
* Queue a keystroke typed before the app installed its key handlers; input
|
|
3048
3150
|
* listeners see it once, on replay. The cell-size reply is a terminal report,
|
|
@@ -3056,14 +3158,19 @@ export class TUI extends Container {
|
|
|
3056
3158
|
}
|
|
3057
3159
|
|
|
3058
3160
|
#consumeCellSizeResponse(data: string): boolean {
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3161
|
+
let heightPx: number;
|
|
3162
|
+
let widthPx: number;
|
|
3163
|
+
const pixels = CELL_SIZE_REPLY.exec(data);
|
|
3164
|
+
if (pixels) {
|
|
3165
|
+
heightPx = parseInt(pixels[1], 10);
|
|
3166
|
+
widthPx = parseInt(pixels[2], 10);
|
|
3167
|
+
} else {
|
|
3168
|
+
const points = ITERM2_CELL_SIZE_REPLY.exec(data);
|
|
3169
|
+
if (!points) return false;
|
|
3170
|
+
const scale = points[3] ? Number(points[3]) : 1;
|
|
3171
|
+
heightPx = Math.round(Number(points[1]) * scale);
|
|
3172
|
+
widthPx = Math.round(Number(points[2]) * scale);
|
|
3063
3173
|
}
|
|
3064
|
-
|
|
3065
|
-
const heightPx = parseInt(match[1], 10);
|
|
3066
|
-
const widthPx = parseInt(match[2], 10);
|
|
3067
3174
|
if (heightPx <= 0 || widthPx <= 0) {
|
|
3068
3175
|
return true;
|
|
3069
3176
|
}
|
|
@@ -3278,7 +3385,12 @@ export class TUI extends Container {
|
|
|
3278
3385
|
return output;
|
|
3279
3386
|
}
|
|
3280
3387
|
|
|
3281
|
-
/**
|
|
3388
|
+
/**
|
|
3389
|
+
* Clip a Kitty direct placement and record its live owner; history
|
|
3390
|
+
* placements have no mutable owner. Other direct placements (SIXEL,
|
|
3391
|
+
* iTerm2) have no delete command and composite over whatever pixels they
|
|
3392
|
+
* land on, so the rows they paint above `screenRow` are erased first.
|
|
3393
|
+
*/
|
|
3282
3394
|
#imageLineSequence(
|
|
3283
3395
|
line: string,
|
|
3284
3396
|
screenRow: number,
|
|
@@ -3287,7 +3399,10 @@ export class TUI extends Container {
|
|
|
3287
3399
|
): string {
|
|
3288
3400
|
if (screenRow < 0) return line;
|
|
3289
3401
|
const parsed = parseKittyDirectPlacementLine(line);
|
|
3290
|
-
if (!parsed)
|
|
3402
|
+
if (!parsed) {
|
|
3403
|
+
const lift = Math.min(directPlacementLift(line), screenRow);
|
|
3404
|
+
return lift > 0 ? `\x1b[${lift}A${`${ERASE_LINE}\x1b[B`.repeat(lift)}${line}` : line;
|
|
3405
|
+
}
|
|
3291
3406
|
const previous = placements?.get(placementRow);
|
|
3292
3407
|
const placementId = previous?.placementId ?? this.#nextImagePlacementId++;
|
|
3293
3408
|
const erase = previous ? encodeKittyDeletePlacement(previous.imageId, previous.placementId) : "";
|
|
@@ -3569,6 +3684,7 @@ export class TUI extends Container {
|
|
|
3569
3684
|
this.#providerViewportTop = 0;
|
|
3570
3685
|
this.#providerWindow = [];
|
|
3571
3686
|
this.#providerPreparedRows = [];
|
|
3687
|
+
this.#providerHistoryWritten = false;
|
|
3572
3688
|
}
|
|
3573
3689
|
// The viewport stays anchored directly below whatever history remains on
|
|
3574
3690
|
// screen. Appending K history rows moves the anchor down by K; the write
|
|
@@ -3799,6 +3915,7 @@ export class TUI extends Container {
|
|
|
3799
3915
|
this.#resizeBurstWidthChanged = false;
|
|
3800
3916
|
this.#resizeBurstResized = false;
|
|
3801
3917
|
this.#previousFrameLength = mutablePreparedLines.length;
|
|
3918
|
+
if (preparedHistory.lines.length > 0 || replayViewportRows > 0) this.#providerHistoryWritten = true;
|
|
3802
3919
|
this.#clearScrollbackOnNextRender = false;
|
|
3803
3920
|
this.#forceViewportRepaintOnNextRender = false;
|
|
3804
3921
|
this.#hasEverRendered = true;
|
|
@@ -3899,8 +4016,7 @@ export class TUI extends Container {
|
|
|
3899
4016
|
// restore it in the fused exit or later frames see matching states
|
|
3900
4017
|
// and inline click/hover stays dead until the setting toggles.
|
|
3901
4018
|
const mouseEnter = wantMouse !== "off" && this.#mouseTracking === "off" ? MOUSE_TRACKING_ON : "";
|
|
3902
|
-
const
|
|
3903
|
-
const exitSequence = `${mouseExit}${mouseEnter}${enhancementExit}\x1b[?1049l`;
|
|
4019
|
+
const exitSequence = `${mouseExit}${mouseEnter}${this.#altScreenExit()}`;
|
|
3904
4020
|
// Session replacement finishes while its fullscreen selector still
|
|
3905
4021
|
// covers the old normal buffer. Fuse the restore into the destructive
|
|
3906
4022
|
// repaint so no stale frame can become visible between writes.
|
|
@@ -4443,14 +4559,15 @@ export class TUI extends Container {
|
|
|
4443
4559
|
}
|
|
4444
4560
|
|
|
4445
4561
|
/**
|
|
4446
|
-
*
|
|
4447
|
-
*
|
|
4448
|
-
*
|
|
4562
|
+
* Leave the alternate screen: pop its keyboard-enhancement frame first, then
|
|
4563
|
+
* re-assert the main-screen mode for terminals that lose it on `?1049l`.
|
|
4564
|
+
* Falls back to popping kitty whenever a custom Terminal exposes its push
|
|
4565
|
+
* sequence but predates the `keyboardEnhancementExitSequence` property.
|
|
4449
4566
|
*/
|
|
4450
|
-
#
|
|
4567
|
+
#altScreenExit(): string {
|
|
4451
4568
|
const exit = this.terminal.keyboardEnhancementExitSequence;
|
|
4452
|
-
|
|
4453
|
-
return this.terminal.
|
|
4569
|
+
const keyboardExit = exit !== undefined ? (exit ?? "") : this.terminal.kittyEnableSequence ? "\x1b[<u" : "";
|
|
4570
|
+
return `${keyboardExit}\x1b[?1049l${this.terminal.keyboardEnhancementRestoreSequence ?? ""}`;
|
|
4454
4571
|
}
|
|
4455
4572
|
|
|
4456
4573
|
/**
|
package/src/utils.ts
CHANGED
|
@@ -180,9 +180,11 @@ export function truncateToWidth(
|
|
|
180
180
|
pad?: boolean | null,
|
|
181
181
|
): string {
|
|
182
182
|
maxWidth = Math.max(0, maxWidth | 0);
|
|
183
|
-
// Fast path: every UTF-16 unit is at most 3 cells wide, so a string
|
|
184
|
-
// `length * 3` already fits within `
|
|
185
|
-
|
|
183
|
+
// Fast path: every UTF-16 unit is at most 3 cells wide, so a plain string
|
|
184
|
+
// whose `length * 3` already fits within `maxWidth` cannot need truncation.
|
|
185
|
+
// OSC 66 spans are excluded because their display width is metadata-driven,
|
|
186
|
+
// not proportional to their UTF-16 length.
|
|
187
|
+
if (!pad && !text.includes("\x1b]66") && text.length * 3 <= maxWidth) {
|
|
186
188
|
return text;
|
|
187
189
|
}
|
|
188
190
|
return nativeTruncateToWidth(
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Generate the TypeScript method signatures advertising eval-bridged tools
|
|
3
|
-
* under Codex Code Mode (spliced into the `eval-code-mode.md` prompt),
|
|
4
|
-
* mirroring codex-rs augment_tool_spec_for_code_mode.
|
|
5
|
-
*/
|
|
6
|
-
import { arkToWireSchema, isArkSchema } from "@linxiraos/pi-ai/utils/schema/wire";
|
|
7
|
-
|
|
8
|
-
interface JsonSchema {
|
|
9
|
-
type?: string;
|
|
10
|
-
properties?: Record<string, JsonSchema>;
|
|
11
|
-
required?: string[];
|
|
12
|
-
items?: JsonSchema;
|
|
13
|
-
enum?: unknown[];
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const TS_IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
17
|
-
|
|
18
|
-
function tsType(schema: JsonSchema | undefined, depth: number): string {
|
|
19
|
-
if (!schema || depth > 2) return "unknown";
|
|
20
|
-
if (Array.isArray(schema.enum) && schema.enum.every(value => typeof value === "string")) {
|
|
21
|
-
return schema.enum.map(value => JSON.stringify(value)).join(" | ");
|
|
22
|
-
}
|
|
23
|
-
switch (schema.type) {
|
|
24
|
-
case "string":
|
|
25
|
-
return "string";
|
|
26
|
-
case "number":
|
|
27
|
-
case "integer":
|
|
28
|
-
return "number";
|
|
29
|
-
case "boolean":
|
|
30
|
-
return "boolean";
|
|
31
|
-
case "array": {
|
|
32
|
-
const item = tsType(schema.items, depth + 1);
|
|
33
|
-
// `"a" | "b"[]` parses as `"a" | ("b"[])`, so a union item needs parens.
|
|
34
|
-
return /[|&]/.test(item) ? `(${item})[]` : `${item}[]`;
|
|
35
|
-
}
|
|
36
|
-
case "object": {
|
|
37
|
-
if (!schema.properties) return "Record<string, unknown>";
|
|
38
|
-
const required = new Set(schema.required ?? []);
|
|
39
|
-
const entries = Object.entries(schema.properties).map(([key, value]) => {
|
|
40
|
-
const printedKey = TS_IDENTIFIER.test(key) ? key : JSON.stringify(key);
|
|
41
|
-
return `${printedKey}${required.has(key) ? "" : "?"}: ${tsType(value, depth + 1)}`;
|
|
42
|
-
});
|
|
43
|
-
return `{ ${entries.join("; ")} }`;
|
|
44
|
-
}
|
|
45
|
-
default:
|
|
46
|
-
return "unknown";
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/** Format tool schemas as TypeScript declarations for code-mode discovery. */
|
|
51
|
-
export function generateCodeModeDeclarations(tools: ReadonlyArray<{ name: string; parameters: unknown }>): string {
|
|
52
|
-
const lines = tools.map(tool => {
|
|
53
|
-
const printedName = TS_IDENTIFIER.test(tool.name) ? tool.name : JSON.stringify(tool.name);
|
|
54
|
-
const wire = isArkSchema(tool.parameters)
|
|
55
|
-
? (arkToWireSchema(tool.parameters) as JsonSchema)
|
|
56
|
-
: (tool.parameters as JsonSchema | undefined);
|
|
57
|
-
const args = wire?.type === "object" && wire.properties ? tsType(wire, 0) : "unknown";
|
|
58
|
-
return ` ${printedName}(args: ${args}): Promise<unknown>;`;
|
|
59
|
-
});
|
|
60
|
-
return lines.join("\n");
|
|
61
|
-
}
|