mini-coder 0.7.3 → 0.8.0
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/AGENTS.md +114 -0
- package/README.md +53 -66
- package/bin/mini-coder.ts +2 -0
- package/demo.gif +0 -0
- package/package.json +17 -20
- package/src/agent.ts +177 -255
- package/src/cli.ts +101 -0
- package/src/config.ts +150 -0
- package/src/prompt.ts +54 -207
- package/src/session.ts +124 -69
- package/src/tools/bash.ts +89 -0
- package/src/tools/common.ts +32 -0
- package/src/tools/edit.ts +41 -0
- package/src/tools/index.ts +47 -0
- package/src/tools/read.ts +64 -0
- package/src/tui/commands.ts +63 -0
- package/src/tui/editor.ts +291 -0
- package/src/tui/highlight.ts +189 -0
- package/src/tui/stream.ts +142 -0
- package/src/tui/styles.ts +42 -0
- package/src/tui/term.ts +436 -0
- package/src/tui/theme.ts +121 -0
- package/src/tui/tui.ts +595 -0
- package/src/tui/usage.ts +67 -0
- package/tsconfig.json +8 -8
- package/bin/mc.ts +0 -11
- package/bun.lock +0 -346
- package/nono-mini-coder.json +0 -42
- package/src/args.ts +0 -300
- package/src/error-handling.test.ts +0 -163
- package/src/git.ts +0 -23
- package/src/headless.ts +0 -66
- package/src/index.ts +0 -43
- package/src/oauth.ts +0 -157
- package/src/shared.ts +0 -119
- package/src/themes.ts +0 -234
- package/src/tool-bash.ts +0 -77
- package/src/tool-edit.ts +0 -121
- package/src/tool-read.ts +0 -100
- package/src/tui-components.ts +0 -127
- package/src/tui-conversation.ts +0 -218
- package/src/tui-editor.ts +0 -29
- package/src/tui-overlay.ts +0 -618
- package/src/tui.ts +0 -314
- package/src/types.ts +0 -194
- package/src/update.ts +0 -171
package/src/tui-overlay.ts
DELETED
|
@@ -1,618 +0,0 @@
|
|
|
1
|
-
import { cel, HStack, Text, TextInput, VStack } from "@cel-tui/core";
|
|
2
|
-
import {
|
|
3
|
-
getModels,
|
|
4
|
-
type Message,
|
|
5
|
-
type ThinkingLevel,
|
|
6
|
-
} from "@earendil-works/pi-ai";
|
|
7
|
-
import { getOAuthProviders } from "@earendil-works/pi-ai/oauth";
|
|
8
|
-
import { saveSettings } from "./args";
|
|
9
|
-
import { getAvailableProviders } from "./oauth";
|
|
10
|
-
import { listSessionsForCwd } from "./session";
|
|
11
|
-
import { estimateTokens, formatTimestamp } from "./shared";
|
|
12
|
-
import { applyTUITheme, getTUITheme, TUI_THEME_IDS } from "./themes";
|
|
13
|
-
import { TextPill, theme } from "./tui-components";
|
|
14
|
-
import type { SelectOptions, SelectState, Session, TUIState } from "./types";
|
|
15
|
-
|
|
16
|
-
export function SelectOverlay(
|
|
17
|
-
value: string,
|
|
18
|
-
selected: string,
|
|
19
|
-
list: { label: string; value: string }[],
|
|
20
|
-
label: string,
|
|
21
|
-
onOverlayKeyPress: (key: string) => boolean | undefined,
|
|
22
|
-
onChange: (newValue: string) => void,
|
|
23
|
-
onKeyPress: (key: string) => boolean | undefined,
|
|
24
|
-
) {
|
|
25
|
-
let isEditorFocused = true;
|
|
26
|
-
return VStack(
|
|
27
|
-
{
|
|
28
|
-
height: "100%",
|
|
29
|
-
justifyContent: "end",
|
|
30
|
-
onKeyPress: onOverlayKeyPress,
|
|
31
|
-
},
|
|
32
|
-
[
|
|
33
|
-
VStack(
|
|
34
|
-
{
|
|
35
|
-
bgColor: theme.white,
|
|
36
|
-
fgColor: theme.bblack,
|
|
37
|
-
gap: 1,
|
|
38
|
-
padding: { x: 1, y: 1 },
|
|
39
|
-
},
|
|
40
|
-
[
|
|
41
|
-
VStack(
|
|
42
|
-
{ flex: 1, minHeight: 5, maxHeight: 20, padding: { x: 1 } },
|
|
43
|
-
list.map((i) =>
|
|
44
|
-
i.value === selected
|
|
45
|
-
? Text(i.label, { fgColor: theme.black })
|
|
46
|
-
: Text(i.label, { fgColor: theme.bblack }),
|
|
47
|
-
),
|
|
48
|
-
),
|
|
49
|
-
|
|
50
|
-
HStack({ width: "100%" }, [
|
|
51
|
-
TextPill(label, theme.bwhite, theme.bblack),
|
|
52
|
-
]),
|
|
53
|
-
|
|
54
|
-
TextInput({
|
|
55
|
-
value,
|
|
56
|
-
minHeight: 3,
|
|
57
|
-
maxHeight: 10,
|
|
58
|
-
padding: { x: 1 },
|
|
59
|
-
placeholder: Text("Search...", {
|
|
60
|
-
fgColor: theme.bblack,
|
|
61
|
-
italic: true,
|
|
62
|
-
}),
|
|
63
|
-
fgColor: theme.bblack,
|
|
64
|
-
bgColor: theme.white,
|
|
65
|
-
onChange,
|
|
66
|
-
onKeyPress,
|
|
67
|
-
focused: isEditorFocused,
|
|
68
|
-
onFocus: () => {
|
|
69
|
-
isEditorFocused = true;
|
|
70
|
-
},
|
|
71
|
-
onBlur: () => {
|
|
72
|
-
isEditorFocused = false;
|
|
73
|
-
},
|
|
74
|
-
}),
|
|
75
|
-
],
|
|
76
|
-
),
|
|
77
|
-
],
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export function useSelectOverlay(initialOptions: SelectOptions) {
|
|
82
|
-
// Inner state
|
|
83
|
-
const s: SelectState = {
|
|
84
|
-
value: "",
|
|
85
|
-
selected: "",
|
|
86
|
-
label: "SELECT",
|
|
87
|
-
list: [],
|
|
88
|
-
};
|
|
89
|
-
let baseList: SelectOptions["list"] = [];
|
|
90
|
-
|
|
91
|
-
const applyOptions = (
|
|
92
|
-
options: Pick<SelectOptions, "filter" | "label" | "list">,
|
|
93
|
-
) => {
|
|
94
|
-
baseList = options.list;
|
|
95
|
-
s.value = options.filter ?? "";
|
|
96
|
-
s.label = options.label ? options.label.toUpperCase() : "SELECT";
|
|
97
|
-
s.list = s.value
|
|
98
|
-
? baseList.filter((i) => i.label.includes(s.value))
|
|
99
|
-
: baseList;
|
|
100
|
-
s.selected = s.list.length ? s.list[0].value : "";
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
applyOptions(initialOptions);
|
|
104
|
-
|
|
105
|
-
const onChange = (newValue: string) => {
|
|
106
|
-
s.value = newValue;
|
|
107
|
-
s.list = s.value
|
|
108
|
-
? baseList.filter((i) => i.label.includes(s.value))
|
|
109
|
-
: baseList;
|
|
110
|
-
s.selected = s.list.length ? s.list[0].value : "";
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
const moveSelected = (direction: -1 | 1) => {
|
|
114
|
-
if (!s.list.length) return;
|
|
115
|
-
|
|
116
|
-
const currentIndex = s.list.findIndex((i) => i.value === s.selected);
|
|
117
|
-
const nextIndex =
|
|
118
|
-
currentIndex === -1
|
|
119
|
-
? direction === 1
|
|
120
|
-
? 0
|
|
121
|
-
: s.list.length - 1
|
|
122
|
-
: (currentIndex + direction + s.list.length) % s.list.length;
|
|
123
|
-
|
|
124
|
-
s.selected = s.list[nextIndex].value;
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
const onMoveKeyPress = (key: string) => {
|
|
128
|
-
if (key !== "up" && key !== "down") return;
|
|
129
|
-
|
|
130
|
-
moveSelected(key === "up" ? -1 : 1);
|
|
131
|
-
return false;
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
const onEditorKeyPress = (key: string) => {
|
|
135
|
-
const didMove = onMoveKeyPress(key);
|
|
136
|
-
if (didMove === false) return false;
|
|
137
|
-
|
|
138
|
-
if (key === "enter") {
|
|
139
|
-
const previousList = s.list;
|
|
140
|
-
const previousBaseList = baseList;
|
|
141
|
-
const applySelectionResult = () => {
|
|
142
|
-
if (s.list !== previousList) {
|
|
143
|
-
baseList = s.list;
|
|
144
|
-
} else {
|
|
145
|
-
s.list = previousBaseList;
|
|
146
|
-
s.selected = s.list.length ? s.list[0].value : "";
|
|
147
|
-
}
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
s.value = "";
|
|
151
|
-
const result = initialOptions.onSelect(s);
|
|
152
|
-
if (result instanceof Promise) {
|
|
153
|
-
result.then(applySelectionResult).catch(() => {
|
|
154
|
-
s.list = previousBaseList;
|
|
155
|
-
s.selected = s.list.length ? s.list[0].value : "";
|
|
156
|
-
});
|
|
157
|
-
} else {
|
|
158
|
-
applySelectionResult();
|
|
159
|
-
}
|
|
160
|
-
return false;
|
|
161
|
-
}
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
const onOverlayKeyPress = (key: string) => {
|
|
165
|
-
const didMove = onMoveKeyPress(key);
|
|
166
|
-
if (didMove === false) return false;
|
|
167
|
-
|
|
168
|
-
if (key === "escape" || key === "ctrl+p") {
|
|
169
|
-
applyOptions(initialOptions);
|
|
170
|
-
initialOptions.onCancel();
|
|
171
|
-
}
|
|
172
|
-
return false;
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
return () =>
|
|
176
|
-
SelectOverlay(
|
|
177
|
-
s.value,
|
|
178
|
-
s.selected,
|
|
179
|
-
s.list,
|
|
180
|
-
s.label,
|
|
181
|
-
onOverlayKeyPress,
|
|
182
|
-
onChange,
|
|
183
|
-
onEditorKeyPress,
|
|
184
|
-
);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function cleanSnippet(text: string): string {
|
|
188
|
-
return text
|
|
189
|
-
.replaceAll(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "")
|
|
190
|
-
.trim()
|
|
191
|
-
.replace(/\s+/g, " ")
|
|
192
|
-
.slice(0, 80);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
function sessionLabel(session: Session): string {
|
|
196
|
-
const firstUserMessage = session.messages.find(
|
|
197
|
-
(message) => message.role === "user",
|
|
198
|
-
);
|
|
199
|
-
if (!firstUserMessage) return session.id;
|
|
200
|
-
|
|
201
|
-
const text =
|
|
202
|
-
typeof firstUserMessage.content === "string"
|
|
203
|
-
? firstUserMessage.content
|
|
204
|
-
: firstUserMessage.content
|
|
205
|
-
.map((block) => (block.type === "text" ? block.text : ""))
|
|
206
|
-
.join(" ");
|
|
207
|
-
const snippet = cleanSnippet(text);
|
|
208
|
-
|
|
209
|
-
return snippet || session.id;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
function messageText(message: Message): string {
|
|
213
|
-
if (message.role === "user") {
|
|
214
|
-
return typeof message.content === "string"
|
|
215
|
-
? message.content
|
|
216
|
-
: message.content
|
|
217
|
-
.map((content) =>
|
|
218
|
-
content.type === "text" ? content.text : "[image]",
|
|
219
|
-
)
|
|
220
|
-
.join(" ");
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
if (message.role === "toolResult") {
|
|
224
|
-
return message.content
|
|
225
|
-
.map((content) => (content.type === "text" ? content.text : "[image]"))
|
|
226
|
-
.join(" ");
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
return message.content
|
|
230
|
-
.map((content) => {
|
|
231
|
-
if (content.type === "text") return content.text;
|
|
232
|
-
if (content.type === "thinking") return content.thinking;
|
|
233
|
-
if (content.type === "toolCall") return `tool call: ${content.name}`;
|
|
234
|
-
return "";
|
|
235
|
-
})
|
|
236
|
-
.join(" ");
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
function messageLabel(message: Message, index: number): string {
|
|
240
|
-
const role =
|
|
241
|
-
message.role === "toolResult"
|
|
242
|
-
? `toolResult:${message.toolName}`
|
|
243
|
-
: message.role;
|
|
244
|
-
const snippet = cleanSnippet(messageText(message));
|
|
245
|
-
const prefix = `${index + 1}. ${role} ${formatTimestamp(message.timestamp)}`;
|
|
246
|
-
|
|
247
|
-
return snippet ? `${prefix} — ${snippet}` : prefix;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
function forkMessages(messages: Message[], selectedIndex: number): Message[] {
|
|
251
|
-
const forkedMessages = messages.slice(0, selectedIndex + 1);
|
|
252
|
-
const toolCallIds = new Set<string>();
|
|
253
|
-
const includedToolResultIds = new Set<string>();
|
|
254
|
-
|
|
255
|
-
for (const message of forkedMessages) {
|
|
256
|
-
if (message.role === "assistant") {
|
|
257
|
-
for (const content of message.content) {
|
|
258
|
-
if (content.type === "toolCall") toolCallIds.add(content.id);
|
|
259
|
-
}
|
|
260
|
-
} else if (message.role === "toolResult") {
|
|
261
|
-
includedToolResultIds.add(message.toolCallId);
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
for (const message of messages) {
|
|
266
|
-
if (
|
|
267
|
-
message.role === "toolResult" &&
|
|
268
|
-
toolCallIds.has(message.toolCallId) &&
|
|
269
|
-
!includedToolResultIds.has(message.toolCallId)
|
|
270
|
-
) {
|
|
271
|
-
forkedMessages.push(message);
|
|
272
|
-
includedToolResultIds.add(message.toolCallId);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
return forkedMessages;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
export function mainMenu(state: TUIState, initialPane = "main") {
|
|
280
|
-
type MenuPane = Omit<SelectOptions, "onCancel" | "onSelect">;
|
|
281
|
-
|
|
282
|
-
const oauthProviders = getOAuthProviders();
|
|
283
|
-
const getProviderLabel = (provider: string) =>
|
|
284
|
-
oauthProviders.find((oauthProvider) => oauthProvider.id === provider)
|
|
285
|
-
?.name ?? provider;
|
|
286
|
-
const reasoningEfforts: ThinkingLevel[] = [
|
|
287
|
-
"minimal",
|
|
288
|
-
"low",
|
|
289
|
-
"medium",
|
|
290
|
-
"high",
|
|
291
|
-
"xhigh",
|
|
292
|
-
];
|
|
293
|
-
const efforts = reasoningEfforts.map((v) => ({ label: v, value: v }));
|
|
294
|
-
const getProviderModels = (provider: string) => {
|
|
295
|
-
const builtIn = getModels(provider as any).map((v) => ({
|
|
296
|
-
label: v.name,
|
|
297
|
-
value: v.id,
|
|
298
|
-
}));
|
|
299
|
-
const custom =
|
|
300
|
-
state.options.customProviders
|
|
301
|
-
?.filter((m) => m.provider === provider)
|
|
302
|
-
.map((v) => ({
|
|
303
|
-
label: v.name,
|
|
304
|
-
value: v.id,
|
|
305
|
-
})) ?? [];
|
|
306
|
-
return [...builtIn, ...custom];
|
|
307
|
-
};
|
|
308
|
-
|
|
309
|
-
let currentProviders: string[] = [];
|
|
310
|
-
let currentSessions: Session[] = [];
|
|
311
|
-
|
|
312
|
-
const providersPane = async (): Promise<MenuPane> => {
|
|
313
|
-
const builtIn = await getAvailableProviders();
|
|
314
|
-
const custom =
|
|
315
|
-
state.options.customProviders?.map((cp) => cp.provider) ?? [];
|
|
316
|
-
currentProviders = [...new Set([...builtIn, ...custom])];
|
|
317
|
-
return {
|
|
318
|
-
label: "providers",
|
|
319
|
-
filter: "",
|
|
320
|
-
list: currentProviders.map((provider) => ({
|
|
321
|
-
label: getProviderLabel(provider),
|
|
322
|
-
value: provider,
|
|
323
|
-
})),
|
|
324
|
-
};
|
|
325
|
-
};
|
|
326
|
-
|
|
327
|
-
const sessionsPane = async (): Promise<MenuPane> => {
|
|
328
|
-
currentSessions = await listSessionsForCwd();
|
|
329
|
-
return {
|
|
330
|
-
label: "sessions",
|
|
331
|
-
filter: "",
|
|
332
|
-
list: currentSessions.map((session) => ({
|
|
333
|
-
label: sessionLabel(session),
|
|
334
|
-
value: session.id,
|
|
335
|
-
})),
|
|
336
|
-
};
|
|
337
|
-
};
|
|
338
|
-
|
|
339
|
-
const forkPane = (): MenuPane => ({
|
|
340
|
-
label: "fork",
|
|
341
|
-
filter: "",
|
|
342
|
-
list: state.messages.map((message, index) => ({
|
|
343
|
-
label: messageLabel(message, index),
|
|
344
|
-
value: String(index),
|
|
345
|
-
})),
|
|
346
|
-
});
|
|
347
|
-
|
|
348
|
-
const themesPane = (): MenuPane => ({
|
|
349
|
-
label: "themes",
|
|
350
|
-
filter: "",
|
|
351
|
-
list: TUI_THEME_IDS.map((id) => {
|
|
352
|
-
const tuiTheme = getTUITheme(id);
|
|
353
|
-
return {
|
|
354
|
-
label:
|
|
355
|
-
id === state.options.theme
|
|
356
|
-
? `${tuiTheme.label} (current)`
|
|
357
|
-
: tuiTheme.label,
|
|
358
|
-
value: id,
|
|
359
|
-
};
|
|
360
|
-
}),
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
const mainPane: MenuPane = {
|
|
364
|
-
label: "main",
|
|
365
|
-
filter: state.prompt.length ? state.prompt : "",
|
|
366
|
-
list: [
|
|
367
|
-
{ label: "models and providers", value: "providers" },
|
|
368
|
-
{ label: "reasoning effort", value: "effort" },
|
|
369
|
-
{ label: "themes", value: "themes" },
|
|
370
|
-
{ label: "sessions", value: "sessions" },
|
|
371
|
-
{ label: "fork", value: "fork" },
|
|
372
|
-
],
|
|
373
|
-
};
|
|
374
|
-
const effortPane: MenuPane = {
|
|
375
|
-
label: "effort",
|
|
376
|
-
filter: "",
|
|
377
|
-
list: efforts,
|
|
378
|
-
};
|
|
379
|
-
const panes: MenuPane[] = [effortPane, themesPane()];
|
|
380
|
-
let currentPane = initialPane === "fork" ? forkPane() : mainPane;
|
|
381
|
-
let selectedProvider: string | undefined;
|
|
382
|
-
|
|
383
|
-
const openPane = (s: SelectState, pane: MenuPane) => {
|
|
384
|
-
currentPane = pane;
|
|
385
|
-
s.value = pane.filter;
|
|
386
|
-
s.label = pane.label ? pane.label.toUpperCase() : "SELECT";
|
|
387
|
-
s.list = pane.list;
|
|
388
|
-
s.selected = s.list.length ? s.list[0].value : "";
|
|
389
|
-
};
|
|
390
|
-
|
|
391
|
-
const resetMenu = (s: SelectState) => {
|
|
392
|
-
currentPane = mainPane;
|
|
393
|
-
selectedProvider = undefined;
|
|
394
|
-
currentProviders = [];
|
|
395
|
-
currentSessions = [];
|
|
396
|
-
s.value = "";
|
|
397
|
-
s.label = mainPane.label ? mainPane.label.toUpperCase() : "SELECT";
|
|
398
|
-
s.list = mainPane.list;
|
|
399
|
-
s.selected = s.list.length ? s.list[0].value : "";
|
|
400
|
-
};
|
|
401
|
-
|
|
402
|
-
const closeMenu = (s: SelectState) => {
|
|
403
|
-
resetMenu(s);
|
|
404
|
-
state.overlay = false;
|
|
405
|
-
};
|
|
406
|
-
|
|
407
|
-
const requestThemeRefresh = () => {
|
|
408
|
-
state.forceThemeRefresh = true;
|
|
409
|
-
setTimeout(() => {
|
|
410
|
-
state.forceThemeRefresh = false;
|
|
411
|
-
cel.render();
|
|
412
|
-
}, 0);
|
|
413
|
-
};
|
|
414
|
-
|
|
415
|
-
const toTUIMessage = (msg: Message) => {
|
|
416
|
-
const textFromContent = (
|
|
417
|
-
content: string | { type: string; text?: string }[],
|
|
418
|
-
) =>
|
|
419
|
-
typeof content === "string"
|
|
420
|
-
? content
|
|
421
|
-
: content
|
|
422
|
-
.filter((c) => c.type === "text")
|
|
423
|
-
.map((c) => c.text ?? "")
|
|
424
|
-
.join("")
|
|
425
|
-
.trim();
|
|
426
|
-
|
|
427
|
-
if (msg.role === "user") {
|
|
428
|
-
return {
|
|
429
|
-
timestamp: formatTimestamp(msg.timestamp),
|
|
430
|
-
role: "user" as const,
|
|
431
|
-
text: textFromContent(msg.content)
|
|
432
|
-
.replaceAll(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "")
|
|
433
|
-
.trim(),
|
|
434
|
-
};
|
|
435
|
-
} else {
|
|
436
|
-
const text = msg.content
|
|
437
|
-
.filter((c) => c.type === "text")
|
|
438
|
-
.map((c) => c.text)
|
|
439
|
-
.join("")
|
|
440
|
-
.trim();
|
|
441
|
-
const thinking = msg.content
|
|
442
|
-
.filter((c) => c.type === "thinking")
|
|
443
|
-
.map((c) => c.thinking)
|
|
444
|
-
.join("")
|
|
445
|
-
.trim();
|
|
446
|
-
const toolCalls = msg.content
|
|
447
|
-
.filter((c) => c.type === "toolCall")
|
|
448
|
-
.map((c) => {
|
|
449
|
-
const toolResult = state.messages.find(
|
|
450
|
-
(m) => m.role === "toolResult" && m.toolCallId === c.id,
|
|
451
|
-
);
|
|
452
|
-
return {
|
|
453
|
-
id: c.id,
|
|
454
|
-
tool: c.name,
|
|
455
|
-
args: c.arguments,
|
|
456
|
-
output: toolResult ? textFromContent(toolResult.content) : "",
|
|
457
|
-
};
|
|
458
|
-
});
|
|
459
|
-
|
|
460
|
-
return {
|
|
461
|
-
timestamp: formatTimestamp(msg.timestamp),
|
|
462
|
-
role: "assistant" as const,
|
|
463
|
-
text,
|
|
464
|
-
thinking,
|
|
465
|
-
toolCalls,
|
|
466
|
-
};
|
|
467
|
-
}
|
|
468
|
-
};
|
|
469
|
-
|
|
470
|
-
const select = useSelectOverlay({
|
|
471
|
-
...currentPane,
|
|
472
|
-
onSelect: (s) => {
|
|
473
|
-
if (!s.selected) return;
|
|
474
|
-
|
|
475
|
-
if (currentPane.label === "main") {
|
|
476
|
-
if (s.selected === "providers") {
|
|
477
|
-
return providersPane().then((pane) => {
|
|
478
|
-
openPane(s, pane);
|
|
479
|
-
});
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
if (s.selected === "sessions") {
|
|
483
|
-
return sessionsPane().then((pane) => {
|
|
484
|
-
openPane(s, pane);
|
|
485
|
-
});
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
if (s.selected === "fork") {
|
|
489
|
-
openPane(s, forkPane());
|
|
490
|
-
return;
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
const nextPane = panes.find((pane) => pane.label === s.selected);
|
|
494
|
-
if (nextPane) openPane(s, nextPane);
|
|
495
|
-
return;
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
if (currentPane.label === "sessions") {
|
|
499
|
-
const session = currentSessions.find((v) => v.id === s.selected);
|
|
500
|
-
if (!session) return;
|
|
501
|
-
|
|
502
|
-
state.sessionId = session.id;
|
|
503
|
-
state.messages = session.messages;
|
|
504
|
-
state.tuiMessages = session.messages
|
|
505
|
-
.filter((message) => message.role !== "toolResult")
|
|
506
|
-
.map(toTUIMessage);
|
|
507
|
-
state.prompt = "";
|
|
508
|
-
state.contextSize = estimateTokens(JSON.stringify(state.messages));
|
|
509
|
-
state.scrollOffset = 0;
|
|
510
|
-
state.stickToBottom = true;
|
|
511
|
-
closeMenu(s);
|
|
512
|
-
return;
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
if (currentPane.label === "fork") {
|
|
516
|
-
const selectedIndex = Number(s.selected);
|
|
517
|
-
if (
|
|
518
|
-
!Number.isInteger(selectedIndex) ||
|
|
519
|
-
!state.messages[selectedIndex]
|
|
520
|
-
) {
|
|
521
|
-
return;
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
state.sessionId = undefined;
|
|
525
|
-
state.messages = forkMessages(state.messages, selectedIndex);
|
|
526
|
-
state.tuiMessages = state.messages
|
|
527
|
-
.filter((message) => message.role !== "toolResult")
|
|
528
|
-
.map(toTUIMessage);
|
|
529
|
-
state.prompt = "";
|
|
530
|
-
state.contextSize = estimateTokens(JSON.stringify(state.messages));
|
|
531
|
-
state.scrollOffset = 0;
|
|
532
|
-
state.stickToBottom = true;
|
|
533
|
-
closeMenu(s);
|
|
534
|
-
return;
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
if (currentPane.label === "themes") {
|
|
538
|
-
const nextTheme = TUI_THEME_IDS.find((id) => id === s.selected);
|
|
539
|
-
if (!nextTheme) return;
|
|
540
|
-
|
|
541
|
-
state.options.theme = nextTheme;
|
|
542
|
-
applyTUITheme(nextTheme);
|
|
543
|
-
saveSettings({
|
|
544
|
-
provider: state.options.provider,
|
|
545
|
-
model: state.options.model.id,
|
|
546
|
-
effort: state.options.effort,
|
|
547
|
-
customProviders: state.options.customProviders,
|
|
548
|
-
theme: nextTheme,
|
|
549
|
-
});
|
|
550
|
-
requestThemeRefresh();
|
|
551
|
-
closeMenu(s);
|
|
552
|
-
return;
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
if (currentPane.label === "providers") {
|
|
556
|
-
const provider = currentProviders.find((v) => v === s.selected);
|
|
557
|
-
if (!provider) return;
|
|
558
|
-
|
|
559
|
-
selectedProvider = provider;
|
|
560
|
-
openPane(s, {
|
|
561
|
-
label: "models",
|
|
562
|
-
filter: "",
|
|
563
|
-
list: getProviderModels(selectedProvider),
|
|
564
|
-
});
|
|
565
|
-
return;
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
if (currentPane.label === "models") {
|
|
569
|
-
if (!selectedProvider) return;
|
|
570
|
-
|
|
571
|
-
const builtIn = getModels(selectedProvider as any).find(
|
|
572
|
-
(v) => v.id === s.selected,
|
|
573
|
-
);
|
|
574
|
-
const custom = state.options.customProviders?.find(
|
|
575
|
-
(v) => v.provider === selectedProvider && v.id === s.selected,
|
|
576
|
-
);
|
|
577
|
-
const model = builtIn ?? custom;
|
|
578
|
-
if (!model) return;
|
|
579
|
-
|
|
580
|
-
state.options.provider = selectedProvider;
|
|
581
|
-
state.options.model = model;
|
|
582
|
-
saveSettings({
|
|
583
|
-
provider: selectedProvider,
|
|
584
|
-
model: model.id,
|
|
585
|
-
effort: state.options.effort,
|
|
586
|
-
customProviders: state.options.customProviders,
|
|
587
|
-
theme: state.options.theme,
|
|
588
|
-
});
|
|
589
|
-
closeMenu(s);
|
|
590
|
-
return;
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
if (currentPane.label === "effort") {
|
|
594
|
-
const effort = reasoningEfforts.find((v) => v === s.selected);
|
|
595
|
-
if (!effort) return;
|
|
596
|
-
|
|
597
|
-
state.options.effort = effort;
|
|
598
|
-
saveSettings({
|
|
599
|
-
provider: state.options.provider,
|
|
600
|
-
model: state.options.model.id,
|
|
601
|
-
effort: effort,
|
|
602
|
-
customProviders: state.options.customProviders,
|
|
603
|
-
theme: state.options.theme,
|
|
604
|
-
});
|
|
605
|
-
closeMenu(s);
|
|
606
|
-
}
|
|
607
|
-
},
|
|
608
|
-
onCancel: () => {
|
|
609
|
-
currentPane = mainPane;
|
|
610
|
-
selectedProvider = undefined;
|
|
611
|
-
currentProviders = [];
|
|
612
|
-
currentSessions = [];
|
|
613
|
-
state.overlay = false;
|
|
614
|
-
},
|
|
615
|
-
});
|
|
616
|
-
|
|
617
|
-
return select;
|
|
618
|
-
}
|