@astralyn/pi 0.84.6 → 0.84.8
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 +27 -0
- package/dist/core/compaction/compaction.d.ts +2 -0
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +7 -7
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/extensions/background/index.d.ts.map +1 -1
- package/dist/extensions/background/index.js +48 -72
- package/dist/extensions/background/index.js.map +1 -1
- package/dist/extensions/background/manager.d.ts.map +1 -1
- package/dist/extensions/background/manager.js +5 -3
- package/dist/extensions/background/manager.js.map +1 -1
- package/dist/extensions/background/registry.d.ts +11 -13
- package/dist/extensions/background/registry.d.ts.map +1 -1
- package/dist/extensions/background/registry.js +71 -80
- package/dist/extensions/background/registry.js.map +1 -1
- package/dist/extensions/background/render.d.ts.map +1 -1
- package/dist/extensions/background/render.js +17 -4
- package/dist/extensions/background/render.js.map +1 -1
- package/dist/extensions/deepwiki/constants.d.ts +2 -2
- package/dist/extensions/deepwiki/constants.d.ts.map +1 -1
- package/dist/extensions/deepwiki/constants.js +5 -5
- package/dist/extensions/deepwiki/constants.js.map +1 -1
- package/dist/extensions/deepwiki/schema.js +1 -1
- package/dist/extensions/deepwiki/schema.js.map +1 -1
- package/dist/extensions/question/constants.d.ts +2 -2
- package/dist/extensions/question/constants.d.ts.map +1 -1
- package/dist/extensions/question/constants.js +5 -2
- package/dist/extensions/question/constants.js.map +1 -1
- package/dist/extensions/router/dialog-host.d.ts +60 -0
- package/dist/extensions/router/dialog-host.d.ts.map +1 -0
- package/dist/extensions/router/dialog-host.js +196 -0
- package/dist/extensions/router/dialog-host.js.map +1 -0
- package/dist/extensions/router/probe.d.ts.map +1 -1
- package/dist/extensions/router/probe.js +2 -0
- package/dist/extensions/router/probe.js.map +1 -1
- package/dist/extensions/router/ui.d.ts.map +1 -1
- package/dist/extensions/router/ui.js +117 -67
- package/dist/extensions/router/ui.js.map +1 -1
- package/dist/extensions/subagent/activity.d.ts +9 -0
- package/dist/extensions/subagent/activity.d.ts.map +1 -1
- package/dist/extensions/subagent/activity.js +21 -0
- package/dist/extensions/subagent/activity.js.map +1 -1
- package/dist/extensions/subagent/agents-command.d.ts.map +1 -1
- package/dist/extensions/subagent/agents-command.js +355 -71
- package/dist/extensions/subagent/agents-command.js.map +1 -1
- package/dist/extensions/subagent/agents.d.ts.map +1 -1
- package/dist/extensions/subagent/agents.js +7 -3
- package/dist/extensions/subagent/agents.js.map +1 -1
- package/dist/extensions/subagent/constants.d.ts +1 -0
- package/dist/extensions/subagent/constants.d.ts.map +1 -1
- package/dist/extensions/subagent/constants.js +4 -0
- package/dist/extensions/subagent/constants.js.map +1 -1
- package/dist/extensions/subagent/index.js +2 -2
- package/dist/extensions/subagent/index.js.map +1 -1
- package/dist/extensions/subagent/render.d.ts.map +1 -1
- package/dist/extensions/subagent/render.js +1 -7
- package/dist/extensions/subagent/render.js.map +1 -1
- package/dist/extensions/subagent/sdk-runner.d.ts.map +1 -1
- package/dist/extensions/subagent/sdk-runner.js +19 -0
- package/dist/extensions/subagent/sdk-runner.js.map +1 -1
- package/dist/extensions/subagent/settings.d.ts.map +1 -1
- package/dist/extensions/subagent/settings.js +11 -4
- package/dist/extensions/subagent/settings.js.map +1 -1
- package/dist/extensions/subagent/state.d.ts +9 -0
- package/dist/extensions/subagent/state.d.ts.map +1 -1
- package/dist/extensions/subagent/state.js +33 -2
- package/dist/extensions/subagent/state.js.map +1 -1
- package/dist/extensions/subagent/ui/model-list.d.ts +1 -0
- package/dist/extensions/subagent/ui/model-list.d.ts.map +1 -1
- package/dist/extensions/subagent/ui/model-list.js +5 -0
- package/dist/extensions/subagent/ui/model-list.js.map +1 -1
- package/dist/extensions/todo/constants.d.ts +1 -1
- package/dist/extensions/todo/constants.d.ts.map +1 -1
- package/dist/extensions/todo/constants.js +10 -27
- package/dist/extensions/todo/constants.js.map +1 -1
- package/dist/extensions/todo/state.d.ts.map +1 -1
- package/dist/extensions/todo/state.js +27 -16
- package/dist/extensions/todo/state.js.map +1 -1
- package/dist/extensions/todo/view.d.ts.map +1 -1
- package/dist/extensions/todo/view.js +16 -5
- package/dist/extensions/todo/view.js.map +1 -1
- package/docs/bundled/extensions/background.md +3 -3
- package/docs/bundled/extensions/todo.md +2 -2
- package/docs/compaction.md +2 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
|
@@ -8,6 +8,7 @@ import { BorderedLoader } from "../../modes/interactive/components/bordered-load
|
|
|
8
8
|
import { getModelSelectorSearchText } from "../../modes/interactive/model-search.js";
|
|
9
9
|
import { formatError, isValidRelayId, NO_UI_WARNING, ROUTER_THINKING_LEVELS, truncate } from "./constants.js";
|
|
10
10
|
import { createModelChecklist, createSearchableSelector, createThinkingMapEditor, } from "./dialog.js";
|
|
11
|
+
import { createNativeRouterDialogs, RouterTuiSession, RouterTuiSessionClosedError, } from "./dialog-host.js";
|
|
11
12
|
import { createDefaultModelConfig, displayModelLabel, resolveModelConfig, resolveRouterThinkingMap, summarizeThinkingMap, } from "./presets.js";
|
|
12
13
|
import { probeRelayModels } from "./probe.js";
|
|
13
14
|
import { applyRouterFile, registerOneRelay, unregisterOneRelay } from "./register.js";
|
|
@@ -52,6 +53,18 @@ class RelayAutoSaver {
|
|
|
52
53
|
}
|
|
53
54
|
}
|
|
54
55
|
}
|
|
56
|
+
/** Abort the catalog request if its internal page or the whole Router session is disposed. */
|
|
57
|
+
class RouterProbeLoader extends BorderedLoader {
|
|
58
|
+
constructor(tui, theme, message) {
|
|
59
|
+
super(tui, theme, message, { cancellable: true });
|
|
60
|
+
this.disposeController = new AbortController();
|
|
61
|
+
this.probeSignal = AbortSignal.any([this.signal, this.disposeController.signal]);
|
|
62
|
+
}
|
|
63
|
+
dispose() {
|
|
64
|
+
this.disposeController.abort();
|
|
65
|
+
super.dispose();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
55
68
|
export function isCurrentRouterModel(current, relayId, modelId) {
|
|
56
69
|
return current?.provider === relayId && current.id === modelId;
|
|
57
70
|
}
|
|
@@ -61,14 +74,6 @@ export async function runRouterCommand(args, ctx, pi) {
|
|
|
61
74
|
return;
|
|
62
75
|
}
|
|
63
76
|
const trimmed = args.trim().toLowerCase();
|
|
64
|
-
if (!trimmed || trimmed === "list") {
|
|
65
|
-
await openMainMenu(ctx, pi);
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
if (trimmed === "add") {
|
|
69
|
-
await addRelayFlow(ctx, pi);
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
77
|
if (trimmed === "reload") {
|
|
73
78
|
const file = await loadRouterFile();
|
|
74
79
|
applyRouterFile(pi, file);
|
|
@@ -76,16 +81,63 @@ export async function runRouterCommand(args, ctx, pi) {
|
|
|
76
81
|
ctx.ui.notify(`Reloaded ${file.relays.length} relay(s).`, "info");
|
|
77
82
|
return;
|
|
78
83
|
}
|
|
79
|
-
|
|
84
|
+
if (ctx.mode === "tui") {
|
|
85
|
+
await runRouterTuiCommand(args, ctx, pi);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
await runRouterDialogCommand(args, ctx, pi, createNativeRouterDialogs(ctx));
|
|
89
|
+
}
|
|
90
|
+
async function runRouterTuiCommand(args, ctx, pi) {
|
|
91
|
+
let failure;
|
|
92
|
+
let closedDuringTransition = false;
|
|
93
|
+
await ctx.ui.custom((tui, theme, keybindings, done) => {
|
|
94
|
+
const session = new RouterTuiSession(tui, theme, keybindings, () => {
|
|
95
|
+
closedDuringTransition = true;
|
|
96
|
+
done();
|
|
97
|
+
});
|
|
98
|
+
void runRouterDialogCommand(args, ctx, pi, session).then(() => {
|
|
99
|
+
if (!closedDuringTransition)
|
|
100
|
+
done();
|
|
101
|
+
}, (error) => {
|
|
102
|
+
if (closedDuringTransition) {
|
|
103
|
+
if (!(error instanceof RouterTuiSessionClosedError)) {
|
|
104
|
+
try {
|
|
105
|
+
ctx.ui.notify(formatError(error), "error");
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
// The command context may have gone stale after the modal closed.
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
failure = { error };
|
|
114
|
+
done();
|
|
115
|
+
});
|
|
116
|
+
return session;
|
|
117
|
+
});
|
|
118
|
+
if (failure)
|
|
119
|
+
throw failure.error;
|
|
120
|
+
}
|
|
121
|
+
async function runRouterDialogCommand(args, ctx, pi, dialogs) {
|
|
122
|
+
const trimmed = args.trim().toLowerCase();
|
|
123
|
+
if (!trimmed || trimmed === "list") {
|
|
124
|
+
await openMainMenu(ctx, pi, dialogs);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (trimmed === "add") {
|
|
128
|
+
await addRelayFlow(ctx, pi, dialogs);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
// Treat as relay id open.
|
|
80
132
|
const file = await loadRouterFile();
|
|
81
133
|
const exact = file.relays.find((relay) => relay.id.toLowerCase() === trimmed);
|
|
82
134
|
if (exact) {
|
|
83
|
-
await editRelayFlow(ctx, pi, exact);
|
|
135
|
+
await editRelayFlow(ctx, pi, dialogs, exact);
|
|
84
136
|
return;
|
|
85
137
|
}
|
|
86
|
-
await openMainMenu(ctx, pi, args.trim());
|
|
138
|
+
await openMainMenu(ctx, pi, dialogs, args.trim());
|
|
87
139
|
}
|
|
88
|
-
async function openMainMenu(ctx, pi, initialQuery) {
|
|
140
|
+
async function openMainMenu(ctx, pi, dialogs, initialQuery) {
|
|
89
141
|
let cursor;
|
|
90
142
|
let query = initialQuery;
|
|
91
143
|
while (true) {
|
|
@@ -111,8 +163,8 @@ async function openMainMenu(ctx, pi, initialQuery) {
|
|
|
111
163
|
searchText: "reload refresh",
|
|
112
164
|
},
|
|
113
165
|
];
|
|
114
|
-
const selected =
|
|
115
|
-
? await
|
|
166
|
+
const selected = dialogs.kind === "tui"
|
|
167
|
+
? await dialogs.show(createSearchableSelector({
|
|
116
168
|
title: "API relays",
|
|
117
169
|
subtitle: file.relays.length === 0
|
|
118
170
|
? "No relays yet — add a base URL + API key."
|
|
@@ -122,13 +174,13 @@ async function openMainMenu(ctx, pi, initialQuery) {
|
|
|
122
174
|
initialQuery: query,
|
|
123
175
|
maxVisible: 10,
|
|
124
176
|
}))
|
|
125
|
-
: await
|
|
177
|
+
: await selectRouterItem(dialogs, "API relays", items.map((item) => ({ value: item.value, label: item.label, description: item.description })));
|
|
126
178
|
query = undefined;
|
|
127
179
|
if (selected === undefined)
|
|
128
180
|
return;
|
|
129
181
|
cursor = selected;
|
|
130
182
|
if (selected === "action:add") {
|
|
131
|
-
const id = await addRelayFlow(ctx, pi);
|
|
183
|
+
const id = await addRelayFlow(ctx, pi, dialogs);
|
|
132
184
|
if (id)
|
|
133
185
|
cursor = `relay:${id}`;
|
|
134
186
|
continue;
|
|
@@ -148,14 +200,14 @@ async function openMainMenu(ctx, pi, initialQuery) {
|
|
|
148
200
|
ctx.ui.notify(`Relay "${id}" not found.`, "warning");
|
|
149
201
|
continue;
|
|
150
202
|
}
|
|
151
|
-
await editRelayFlow(ctx, pi, relay);
|
|
203
|
+
await editRelayFlow(ctx, pi, dialogs, relay);
|
|
152
204
|
}
|
|
153
205
|
}
|
|
154
206
|
}
|
|
155
|
-
async function addRelayFlow(ctx, pi) {
|
|
207
|
+
async function addRelayFlow(ctx, pi, dialogs) {
|
|
156
208
|
const file = await loadRouterFile();
|
|
157
209
|
const existing = new Set(file.relays.map((relay) => relay.id));
|
|
158
|
-
const id = await promptText(ctx, "New relay · name (provider id)", "my-relay", (value) => {
|
|
210
|
+
const id = await promptText(ctx, dialogs, "New relay · name (provider id)", "my-relay", (value) => {
|
|
159
211
|
const trimmed = value.trim();
|
|
160
212
|
if (!trimmed)
|
|
161
213
|
return "Name is required.";
|
|
@@ -167,7 +219,7 @@ async function addRelayFlow(ctx, pi) {
|
|
|
167
219
|
});
|
|
168
220
|
if (id === undefined)
|
|
169
221
|
return undefined;
|
|
170
|
-
const baseUrl = await promptText(ctx, `New relay · ${id.trim()} · base URL`, "https://relay.example/v1", (value) => {
|
|
222
|
+
const baseUrl = await promptText(ctx, dialogs, `New relay · ${id.trim()} · base URL`, "https://relay.example/v1", (value) => {
|
|
171
223
|
const trimmed = value.trim();
|
|
172
224
|
if (!trimmed)
|
|
173
225
|
return "Base URL is required.";
|
|
@@ -183,7 +235,7 @@ async function addRelayFlow(ctx, pi) {
|
|
|
183
235
|
});
|
|
184
236
|
if (baseUrl === undefined)
|
|
185
237
|
return undefined;
|
|
186
|
-
const apiKey = await promptText(ctx, `New relay · ${id.trim()} · API key`, "sk-… or $RELAY_KEY", (value) => {
|
|
238
|
+
const apiKey = await promptText(ctx, dialogs, `New relay · ${id.trim()} · API key`, "sk-… or $RELAY_KEY", (value) => {
|
|
187
239
|
if (!value.trim())
|
|
188
240
|
return "API key is required.";
|
|
189
241
|
return undefined;
|
|
@@ -199,7 +251,7 @@ async function addRelayFlow(ctx, pi) {
|
|
|
199
251
|
// Connection fields are committed before the network step so cancelling or
|
|
200
252
|
// failing catalog discovery never loses the relay the user just entered.
|
|
201
253
|
await persistRelay(ctx, pi, relay);
|
|
202
|
-
await fetchAndSelectModels(ctx, pi, relay);
|
|
254
|
+
await fetchAndSelectModels(ctx, pi, dialogs, relay);
|
|
203
255
|
if (relay.models.length === 0) {
|
|
204
256
|
ctx.ui.notify(`Relay "${relay.id}" has no models yet.`, "warning");
|
|
205
257
|
}
|
|
@@ -209,7 +261,7 @@ async function addRelayFlow(ctx, pi) {
|
|
|
209
261
|
* Relay editor — flat menu. Every mutation writes router.json immediately.
|
|
210
262
|
* Hierarchy: list → relay → (models list | connection field | fetch | remove).
|
|
211
263
|
*/
|
|
212
|
-
async function editRelayFlow(ctx, pi, initial) {
|
|
264
|
+
async function editRelayFlow(ctx, pi, dialogs, initial) {
|
|
213
265
|
const relay = structuredClone(initial);
|
|
214
266
|
while (true) {
|
|
215
267
|
const modelSummary = relay.models.length === 0
|
|
@@ -220,7 +272,7 @@ async function editRelayFlow(ctx, pi, initial) {
|
|
|
220
272
|
.slice(0, 2)
|
|
221
273
|
.map((m) => displayModelLabel(m))
|
|
222
274
|
.join(", ")}…`;
|
|
223
|
-
const choice = await
|
|
275
|
+
const choice = await selectRouterItem(dialogs, `Relay · ${relay.id}`, [
|
|
224
276
|
{
|
|
225
277
|
value: "models",
|
|
226
278
|
label: "Models",
|
|
@@ -234,7 +286,7 @@ async function editRelayFlow(ctx, pi, initial) {
|
|
|
234
286
|
if (choice === undefined || choice === "back")
|
|
235
287
|
return;
|
|
236
288
|
if (choice === "baseUrl") {
|
|
237
|
-
const next = await promptText(ctx, `Relay · ${relay.id} · base URL`, relay.baseUrl, (value) => {
|
|
289
|
+
const next = await promptText(ctx, dialogs, `Relay · ${relay.id} · base URL`, relay.baseUrl, (value) => {
|
|
238
290
|
try {
|
|
239
291
|
const url = new URL(value.trim());
|
|
240
292
|
if (url.protocol !== "http:" && url.protocol !== "https:")
|
|
@@ -255,7 +307,7 @@ async function editRelayFlow(ctx, pi, initial) {
|
|
|
255
307
|
continue;
|
|
256
308
|
}
|
|
257
309
|
if (choice === "apiKey") {
|
|
258
|
-
const next = await promptText(ctx, `Relay · ${relay.id} · API key`, relay.apiKey, (value) => value.trim() ? undefined : "API key is required.");
|
|
310
|
+
const next = await promptText(ctx, dialogs, `Relay · ${relay.id} · API key`, relay.apiKey, (value) => value.trim() ? undefined : "API key is required.");
|
|
259
311
|
if (next === undefined)
|
|
260
312
|
continue;
|
|
261
313
|
const trimmed = next.trim();
|
|
@@ -268,7 +320,7 @@ async function editRelayFlow(ctx, pi, initial) {
|
|
|
268
320
|
if (choice === "models") {
|
|
269
321
|
// Always open the models screen (fetch lives only here). Empty list still
|
|
270
322
|
// shows Fetch so the path is Relay → Models → Fetch, not a top-level shortcut.
|
|
271
|
-
await manageModelsFlow(ctx, pi, relay);
|
|
323
|
+
await manageModelsFlow(ctx, pi, dialogs, relay);
|
|
272
324
|
continue;
|
|
273
325
|
}
|
|
274
326
|
if (choice === "remove") {
|
|
@@ -276,7 +328,7 @@ async function editRelayFlow(ctx, pi, initial) {
|
|
|
276
328
|
ctx.ui.notify(`Switch away from relay "${relay.id}" before removing it.`, "warning");
|
|
277
329
|
continue;
|
|
278
330
|
}
|
|
279
|
-
const ok = await
|
|
331
|
+
const ok = await dialogs.confirm(`Remove relay "${relay.id}"?`, "Models will disappear from /model.");
|
|
280
332
|
if (!ok)
|
|
281
333
|
continue;
|
|
282
334
|
await removeRelay(relay.id);
|
|
@@ -291,7 +343,7 @@ async function editRelayFlow(ctx, pi, initial) {
|
|
|
291
343
|
* Models screen: searchable configured models plus fetch/manual-add actions.
|
|
292
344
|
* Selecting a model opens its editor (name / thinking / remove).
|
|
293
345
|
*/
|
|
294
|
-
async function manageModelsFlow(ctx, pi, relay) {
|
|
346
|
+
async function manageModelsFlow(ctx, pi, dialogs, relay) {
|
|
295
347
|
let cursor;
|
|
296
348
|
while (true) {
|
|
297
349
|
const items = [
|
|
@@ -325,16 +377,16 @@ async function manageModelsFlow(ctx, pi, relay) {
|
|
|
325
377
|
];
|
|
326
378
|
const currentValue = `model:${ctx.model?.id}`;
|
|
327
379
|
const initialValue = cursor ?? (ctx.model?.provider === relay.id ? currentValue : undefined);
|
|
328
|
-
const choice = await
|
|
380
|
+
const choice = await selectRouterItem(dialogs, `Relay · ${relay.id} · models`, items, { initialValue });
|
|
329
381
|
if (choice === undefined || choice === "action:back")
|
|
330
382
|
return;
|
|
331
383
|
cursor = choice;
|
|
332
384
|
if (choice === "action:fetch") {
|
|
333
|
-
await fetchAndSelectModels(ctx, pi, relay);
|
|
385
|
+
await fetchAndSelectModels(ctx, pi, dialogs, relay);
|
|
334
386
|
continue;
|
|
335
387
|
}
|
|
336
388
|
if (choice === "action:manual") {
|
|
337
|
-
const additions = await manualModelEntry(ctx);
|
|
389
|
+
const additions = await manualModelEntry(ctx, dialogs);
|
|
338
390
|
if (!additions)
|
|
339
391
|
continue;
|
|
340
392
|
relay.models = mergeAddedModels(relay.models, additions);
|
|
@@ -346,16 +398,16 @@ async function manageModelsFlow(ctx, pi, relay) {
|
|
|
346
398
|
const model = relay.models.find((entry) => entry.id === modelId);
|
|
347
399
|
if (!model)
|
|
348
400
|
continue;
|
|
349
|
-
await editModelFlow(ctx, pi, relay, model);
|
|
401
|
+
await editModelFlow(ctx, pi, dialogs, relay, model);
|
|
350
402
|
}
|
|
351
403
|
}
|
|
352
404
|
}
|
|
353
405
|
/** Single-model editor: display name + thinking. Every confirmed change is live. */
|
|
354
|
-
async function editModelFlow(ctx, pi, relay, model) {
|
|
406
|
+
async function editModelFlow(ctx, pi, dialogs, relay, model) {
|
|
355
407
|
while (true) {
|
|
356
408
|
const resolved = resolveModelConfig(model);
|
|
357
409
|
const nameDesc = resolved.name ? resolved.name : "(empty · /model shows id)";
|
|
358
|
-
const action = await
|
|
410
|
+
const action = await selectRouterItem(dialogs, `Relay · ${relay.id} · ${model.id}`, [
|
|
359
411
|
{ value: "name", label: "Display name", description: nameDesc },
|
|
360
412
|
{
|
|
361
413
|
value: "thinking",
|
|
@@ -368,7 +420,7 @@ async function editModelFlow(ctx, pi, relay, model) {
|
|
|
368
420
|
if (action === undefined || action === "back")
|
|
369
421
|
return;
|
|
370
422
|
if (action === "name") {
|
|
371
|
-
const next = await promptText(ctx, `Display name · ${model.id} (empty = show id)`, resolved.name ?? "");
|
|
423
|
+
const next = await promptText(ctx, dialogs, `Display name · ${model.id} (empty = show id)`, resolved.name ?? "");
|
|
372
424
|
if (next === undefined)
|
|
373
425
|
continue;
|
|
374
426
|
const trimmed = next.trim();
|
|
@@ -385,8 +437,8 @@ async function editModelFlow(ctx, pi, relay, model) {
|
|
|
385
437
|
}
|
|
386
438
|
if (action === "thinking") {
|
|
387
439
|
const saver = new RelayAutoSaver(ctx, pi, relay);
|
|
388
|
-
if (
|
|
389
|
-
await
|
|
440
|
+
if (dialogs.kind === "tui") {
|
|
441
|
+
await dialogs.show(createThinkingMapEditor({
|
|
390
442
|
title: `Thinking · ${relay.id} / ${model.id}`,
|
|
391
443
|
map: resolved.thinkingLevelMap,
|
|
392
444
|
levels: ROUTER_THINKING_LEVELS,
|
|
@@ -398,7 +450,7 @@ async function editModelFlow(ctx, pi, relay, model) {
|
|
|
398
450
|
}));
|
|
399
451
|
}
|
|
400
452
|
else {
|
|
401
|
-
await editThinkingMapNative(
|
|
453
|
+
await editThinkingMapNative(dialogs, resolved.thinkingLevelMap, (nextMap) => {
|
|
402
454
|
model.thinkingLevelMap = nextMap;
|
|
403
455
|
model.reasoning = true;
|
|
404
456
|
saver.save();
|
|
@@ -412,7 +464,7 @@ async function editModelFlow(ctx, pi, relay, model) {
|
|
|
412
464
|
ctx.ui.notify(`Switch away from model "${model.id}" before removing it.`, "warning");
|
|
413
465
|
continue;
|
|
414
466
|
}
|
|
415
|
-
const ok = await
|
|
467
|
+
const ok = await dialogs.confirm(`Remove model "${model.id}"?`, "Its display name and thinking settings will be removed.");
|
|
416
468
|
if (!ok)
|
|
417
469
|
continue;
|
|
418
470
|
relay.models = relay.models.filter((entry) => entry.id !== model.id);
|
|
@@ -422,10 +474,10 @@ async function editModelFlow(ctx, pi, relay, model) {
|
|
|
422
474
|
}
|
|
423
475
|
}
|
|
424
476
|
}
|
|
425
|
-
async function editThinkingMapNative(
|
|
477
|
+
async function editThinkingMapNative(dialogs, map, onChange) {
|
|
426
478
|
let working = resolveRouterThinkingMap(map);
|
|
427
479
|
while (true) {
|
|
428
|
-
const choice = await
|
|
480
|
+
const choice = await selectRouterItem(dialogs, "Toggle thinking level", [
|
|
429
481
|
...ROUTER_THINKING_LEVELS.map((level) => ({
|
|
430
482
|
value: level,
|
|
431
483
|
label: level,
|
|
@@ -447,21 +499,19 @@ async function editThinkingMapNative(ctx, map, onChange) {
|
|
|
447
499
|
onChange({ ...working });
|
|
448
500
|
}
|
|
449
501
|
}
|
|
450
|
-
async function fetchAndSelectModels(ctx, pi, relay) {
|
|
502
|
+
async function fetchAndSelectModels(ctx, pi, dialogs, relay) {
|
|
451
503
|
const key = resolveProbeApiKey(relay.apiKey);
|
|
452
504
|
if (key.error) {
|
|
453
505
|
ctx.ui.notify(key.error, "warning");
|
|
454
|
-
await recoverWithManualModels(ctx, pi, relay);
|
|
506
|
+
await recoverWithManualModels(ctx, pi, dialogs, relay);
|
|
455
507
|
return;
|
|
456
508
|
}
|
|
457
509
|
let result;
|
|
458
510
|
while (true) {
|
|
459
511
|
const probeOptions = { baseUrl: relay.baseUrl, apiKey: key.value };
|
|
460
|
-
if (
|
|
461
|
-
result = await
|
|
462
|
-
const loader = new
|
|
463
|
-
cancellable: true,
|
|
464
|
-
});
|
|
512
|
+
if (dialogs.kind === "tui") {
|
|
513
|
+
result = await dialogs.show((tui, theme, _kb, done) => {
|
|
514
|
+
const loader = new RouterProbeLoader(tui, theme, `Fetching models · ${relay.id}`);
|
|
465
515
|
let settled = false;
|
|
466
516
|
const finish = (value) => {
|
|
467
517
|
if (settled)
|
|
@@ -471,7 +521,7 @@ async function fetchAndSelectModels(ctx, pi, relay) {
|
|
|
471
521
|
done(value);
|
|
472
522
|
};
|
|
473
523
|
loader.onAbort = () => finish(undefined);
|
|
474
|
-
void probeRelayModels({ ...probeOptions, signal: loader.
|
|
524
|
+
void probeRelayModels({ ...probeOptions, signal: loader.probeSignal })
|
|
475
525
|
.then(finish)
|
|
476
526
|
.catch((error) => finish({ ok: false, error: formatError(error) }));
|
|
477
527
|
return loader;
|
|
@@ -485,7 +535,7 @@ async function fetchAndSelectModels(ctx, pi, relay) {
|
|
|
485
535
|
return;
|
|
486
536
|
if (result.ok && result.models.length === 0) {
|
|
487
537
|
ctx.ui.notify("Server returned an empty model list.", "warning");
|
|
488
|
-
const recovery = await
|
|
538
|
+
const recovery = await selectRouterItem(dialogs, `Catalog empty · ${relay.id}`, [
|
|
489
539
|
{ value: "retry", label: "Retry", description: "Fetch /models again" },
|
|
490
540
|
{ value: "manual", label: "Add models manually", description: "Enter model ids without a catalog" },
|
|
491
541
|
{ value: "back", label: "Back", description: "Return to relay settings" },
|
|
@@ -493,13 +543,13 @@ async function fetchAndSelectModels(ctx, pi, relay) {
|
|
|
493
543
|
if (recovery === "retry")
|
|
494
544
|
continue;
|
|
495
545
|
if (recovery === "manual")
|
|
496
|
-
await recoverWithManualModels(ctx, pi, relay);
|
|
546
|
+
await recoverWithManualModels(ctx, pi, dialogs, relay);
|
|
497
547
|
return;
|
|
498
548
|
}
|
|
499
549
|
if (result.ok)
|
|
500
550
|
break;
|
|
501
551
|
ctx.ui.notify(`Fetch failed: ${result.error}`, "error");
|
|
502
|
-
const recovery = await
|
|
552
|
+
const recovery = await selectRouterItem(dialogs, `Catalog unavailable · ${relay.id}`, [
|
|
503
553
|
{ value: "retry", label: "Retry", description: "Fetch /models again" },
|
|
504
554
|
{ value: "manual", label: "Add models manually", description: "Enter model ids without a catalog" },
|
|
505
555
|
{ value: "back", label: "Back", description: "Return to relay settings" },
|
|
@@ -507,7 +557,7 @@ async function fetchAndSelectModels(ctx, pi, relay) {
|
|
|
507
557
|
if (recovery === "retry")
|
|
508
558
|
continue;
|
|
509
559
|
if (recovery === "manual")
|
|
510
|
-
await recoverWithManualModels(ctx, pi, relay);
|
|
560
|
+
await recoverWithManualModels(ctx, pi, dialogs, relay);
|
|
511
561
|
return;
|
|
512
562
|
}
|
|
513
563
|
if (!result || !result.ok)
|
|
@@ -536,8 +586,8 @@ async function fetchAndSelectModels(ctx, pi, relay) {
|
|
|
536
586
|
});
|
|
537
587
|
saver.save();
|
|
538
588
|
};
|
|
539
|
-
if (
|
|
540
|
-
const choice = await
|
|
589
|
+
if (dialogs.kind === "tui") {
|
|
590
|
+
const choice = await dialogs.show(createModelChecklist({
|
|
541
591
|
title: `Select models · ${relay.id}`,
|
|
542
592
|
subtitle: "Space toggles immediately · Enter/Esc returns",
|
|
543
593
|
models: catalog,
|
|
@@ -550,7 +600,7 @@ async function fetchAndSelectModels(ctx, pi, relay) {
|
|
|
550
600
|
applySelectionIfChanged(relay, choice.selectedIds, applySelection);
|
|
551
601
|
}
|
|
552
602
|
else {
|
|
553
|
-
const ok = await
|
|
603
|
+
const ok = await dialogs.confirm(`Import ${result.models.length} models?`, "Non-TUI mode imports the full catalog.");
|
|
554
604
|
if (ok)
|
|
555
605
|
applySelection([...new Set([...initiallySelected, ...result.models.map((model) => model.id)])]);
|
|
556
606
|
}
|
|
@@ -558,8 +608,8 @@ async function fetchAndSelectModels(ctx, pi, relay) {
|
|
|
558
608
|
if (relay.models.length === 0)
|
|
559
609
|
ctx.ui.notify(`No models enabled for "${relay.id}".`, "warning");
|
|
560
610
|
}
|
|
561
|
-
async function manualModelEntry(ctx) {
|
|
562
|
-
const value = await
|
|
611
|
+
async function manualModelEntry(ctx, dialogs) {
|
|
612
|
+
const value = await dialogs.input("Model IDs (comma-separated)", "gpt-5.6-sol, gpt-5.6-luna");
|
|
563
613
|
if (value === undefined)
|
|
564
614
|
return undefined;
|
|
565
615
|
const ids = [
|
|
@@ -574,8 +624,8 @@ async function manualModelEntry(ctx) {
|
|
|
574
624
|
}
|
|
575
625
|
return ids.map((id) => createDefaultModelConfig(id));
|
|
576
626
|
}
|
|
577
|
-
async function recoverWithManualModels(ctx, pi, relay) {
|
|
578
|
-
const additions = await manualModelEntry(ctx);
|
|
627
|
+
async function recoverWithManualModels(ctx, pi, dialogs, relay) {
|
|
628
|
+
const additions = await manualModelEntry(ctx, dialogs);
|
|
579
629
|
if (!additions)
|
|
580
630
|
return;
|
|
581
631
|
relay.models = mergeAddedModels(relay.models, additions);
|
|
@@ -624,9 +674,9 @@ async function persistRelay(ctx, pi, relay) {
|
|
|
624
674
|
registerOneRelay(pi, relay);
|
|
625
675
|
await ctx.modelRegistry.refresh();
|
|
626
676
|
}
|
|
627
|
-
async function promptText(ctx, title, placeholder, validate) {
|
|
677
|
+
async function promptText(ctx, dialogs, title, placeholder, validate) {
|
|
628
678
|
while (true) {
|
|
629
|
-
const value = await
|
|
679
|
+
const value = await dialogs.input(title, placeholder);
|
|
630
680
|
if (value === undefined)
|
|
631
681
|
return undefined;
|
|
632
682
|
const error = validate?.(value);
|
|
@@ -637,9 +687,9 @@ async function promptText(ctx, title, placeholder, validate) {
|
|
|
637
687
|
return value;
|
|
638
688
|
}
|
|
639
689
|
}
|
|
640
|
-
async function
|
|
641
|
-
if (
|
|
642
|
-
return
|
|
690
|
+
async function selectRouterItem(dialogs, title, items, opts) {
|
|
691
|
+
if (dialogs.kind === "tui") {
|
|
692
|
+
return dialogs.show(createSearchableSelector({
|
|
643
693
|
title,
|
|
644
694
|
items,
|
|
645
695
|
initialValue: opts?.initialValue,
|
|
@@ -648,7 +698,7 @@ async function selectNative(ctx, title, items, opts) {
|
|
|
648
698
|
}));
|
|
649
699
|
}
|
|
650
700
|
const labels = items.map((item) => (item.description ? `${item.label} — ${item.description}` : item.label));
|
|
651
|
-
const selected = await
|
|
701
|
+
const selected = await dialogs.select(title, labels);
|
|
652
702
|
if (selected === undefined)
|
|
653
703
|
return undefined;
|
|
654
704
|
return items[labels.indexOf(selected)]?.value;
|