pi-freeflow 1.3.3 → 1.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +110 -93
- package/package.json +2 -2
- package/src/catalog.ts +8 -5
- package/src/commands.ts +242 -92
- package/src/index.ts +47 -14
- package/src/models.ts +72 -8
- package/src/proxy.ts +31 -10
- package/src/relay-state.ts +125 -21
- package/src/types.ts +2 -1
package/src/commands.ts
CHANGED
|
@@ -17,10 +17,12 @@ import {
|
|
|
17
17
|
} from "./logger.ts";
|
|
18
18
|
import {
|
|
19
19
|
ensureRelay,
|
|
20
|
+
findRelay,
|
|
20
21
|
getActiveRelayState,
|
|
21
22
|
removeRelay,
|
|
22
23
|
saveRelayState,
|
|
23
24
|
setActiveRelayState,
|
|
25
|
+
setRelayLabel,
|
|
24
26
|
setStatusUi,
|
|
25
27
|
shortRelayLabel,
|
|
26
28
|
} from "./relay-state.ts";
|
|
@@ -48,7 +50,10 @@ export function updateStatusBar(ui?: ExtensionUIContext): void {
|
|
|
48
50
|
relayState.relays.findIndex((r) => r.url === relayState.url) + 1,
|
|
49
51
|
);
|
|
50
52
|
const total = relayState.relays.length;
|
|
51
|
-
|
|
53
|
+
const modeLabel = relayState.mode === "on" ? "ON" : "AUTO (ON)";
|
|
54
|
+
ui.setStatus("freeflow", `relay: ${modeLabel} | ${label} ${idx}/${total}`);
|
|
55
|
+
} else if (relayState.mode === "off" || !relayState.enabled) {
|
|
56
|
+
ui.setStatus("freeflow", "relay: OFF (direct)");
|
|
52
57
|
} else {
|
|
53
58
|
ui.setStatus("freeflow", undefined);
|
|
54
59
|
}
|
|
@@ -60,17 +65,21 @@ export function createCommandSpec(
|
|
|
60
65
|
): Omit<RegisteredCommand, "name"> {
|
|
61
66
|
return {
|
|
62
67
|
description:
|
|
63
|
-
"Relay egress: on | off | status |
|
|
68
|
+
"Relay egress: auto | on | off | status | add <URL> [name] | list | use <URL|name|index> [name] | label <target> <name> | remove <target> | logs [level] [n] | debug on|off | refresh | deploy",
|
|
64
69
|
getArgumentCompletions: (prefix: string) =>
|
|
65
70
|
[
|
|
71
|
+
"auto",
|
|
66
72
|
"on",
|
|
67
73
|
"off",
|
|
68
74
|
"status",
|
|
69
|
-
"
|
|
70
|
-
"deploy",
|
|
75
|
+
"add",
|
|
71
76
|
"list",
|
|
72
77
|
"use",
|
|
78
|
+
"label",
|
|
79
|
+
"rename",
|
|
73
80
|
"remove",
|
|
81
|
+
"url",
|
|
82
|
+
"deploy",
|
|
74
83
|
"refresh",
|
|
75
84
|
"models",
|
|
76
85
|
"logs",
|
|
@@ -88,37 +97,100 @@ export function createCommandSpec(
|
|
|
88
97
|
const relayState = getActiveRelayState();
|
|
89
98
|
|
|
90
99
|
const flash = () => {
|
|
91
|
-
const activeLabel = shortRelayLabel(relayState.url);
|
|
100
|
+
const activeLabel = shortRelayLabel(relayState.url, relayState.relays);
|
|
92
101
|
const activeIdx = Math.max(
|
|
93
102
|
1,
|
|
94
103
|
relayState.relays.findIndex((r) => r.url === relayState.url) + 1,
|
|
95
104
|
);
|
|
96
105
|
const total = relayState.relays.length || 1;
|
|
106
|
+
const modeStr = (relayState.mode || "auto").toUpperCase();
|
|
97
107
|
ctx.ui.notify(
|
|
98
|
-
`Relay ${relayState.enabled ? "ON" : "OFF"}${relayState.enabled ? ` → ${activeLabel} (${activeIdx}/${total})` : " (direct)"} | saved=${relayState.relays.length} (auto-fallback rolling)`,
|
|
108
|
+
`Relay mode: ${modeStr} (${relayState.enabled ? "ON" : "OFF"})${relayState.enabled ? ` → ${activeLabel} (${activeIdx}/${total})` : " (direct)"} | saved=${relayState.relays.length} (auto-fallback rolling)`,
|
|
99
109
|
"info",
|
|
100
110
|
);
|
|
101
111
|
};
|
|
102
112
|
|
|
103
113
|
const persist = () => {
|
|
104
|
-
|
|
114
|
+
setActiveRelayState(relayState, true);
|
|
105
115
|
setStatusUi(ctx.ui);
|
|
106
116
|
updateStatusBar(ctx.ui);
|
|
107
117
|
};
|
|
108
|
-
|
|
109
118
|
const setRelay = (
|
|
110
119
|
enabled: boolean,
|
|
111
120
|
url: string,
|
|
112
121
|
addLabel?: string,
|
|
113
122
|
) => {
|
|
114
123
|
relayState.enabled = enabled;
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
124
|
+
const cleanUrl = (url || "").trim() || DEFAULT_RELAY_URL;
|
|
125
|
+
relayState.url = cleanUrl;
|
|
126
|
+
if (cleanUrl) {
|
|
127
|
+
ensureRelay(relayState, cleanUrl, addLabel);
|
|
118
128
|
}
|
|
119
129
|
setActiveRelayState(relayState);
|
|
120
130
|
};
|
|
121
131
|
|
|
132
|
+
const addRelayInteractive = async () => {
|
|
133
|
+
const inputUrl = (
|
|
134
|
+
await ctx.ui.input(
|
|
135
|
+
"Custom relay URL (e.g. https://my-relay.workers.dev):",
|
|
136
|
+
"",
|
|
137
|
+
)
|
|
138
|
+
)?.trim();
|
|
139
|
+
if (!inputUrl) {
|
|
140
|
+
ctx.ui.notify("Cancelled — no URL provided", "warning");
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const defaultLabel = shortRelayLabel(inputUrl, relayState.relays);
|
|
144
|
+
const inputLabel = (
|
|
145
|
+
await ctx.ui.input(
|
|
146
|
+
"Short name / alias for TUI (e.g. cf-sg, vercel-1):",
|
|
147
|
+
defaultLabel,
|
|
148
|
+
)
|
|
149
|
+
)?.trim() || defaultLabel;
|
|
150
|
+
|
|
151
|
+
const added = ensureRelay(relayState, inputUrl, inputLabel);
|
|
152
|
+
setRelay(true, added.url, added.label);
|
|
153
|
+
persist();
|
|
154
|
+
flash();
|
|
155
|
+
ctx.ui.notify(
|
|
156
|
+
`✓ Added & activated relay [${added.label || shortRelayLabel(added.url)}]: ${added.url}`,
|
|
157
|
+
"info",
|
|
158
|
+
);
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const editRelayLabelMenu = async () => {
|
|
162
|
+
if (!relayState.relays.length) {
|
|
163
|
+
ctx.ui.notify("No saved relays to rename", "warning");
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const fmt = (r: KnownRelay, idx: number) =>
|
|
167
|
+
`[${idx + 1}] ${r.label ? `[${r.label}] ` : ""}${r.url}`;
|
|
168
|
+
const opts = relayState.relays.map(fmt);
|
|
169
|
+
const choice = await ctx.ui.select("Select relay to rename / set short name", opts);
|
|
170
|
+
if (!choice) return;
|
|
171
|
+
const match = relayState.relays.find((r, idx) => fmt(r, idx) === choice);
|
|
172
|
+
if (!match) return;
|
|
173
|
+
|
|
174
|
+
const curLabel = match.label || shortRelayLabel(match.url, relayState.relays);
|
|
175
|
+
const newLabel = (
|
|
176
|
+
await ctx.ui.input(
|
|
177
|
+
`New short name for ${match.url}:`,
|
|
178
|
+
curLabel,
|
|
179
|
+
)
|
|
180
|
+
)?.trim();
|
|
181
|
+
if (!newLabel) {
|
|
182
|
+
ctx.ui.notify("Cancelled — short name not changed", "warning");
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
setRelayLabel(relayState, match.url, newLabel);
|
|
186
|
+
persist();
|
|
187
|
+
flash();
|
|
188
|
+
ctx.ui.notify(
|
|
189
|
+
`✓ Relay short name updated to [${newLabel}] for ${match.url}`,
|
|
190
|
+
"info",
|
|
191
|
+
);
|
|
192
|
+
};
|
|
193
|
+
|
|
122
194
|
const doDeploy = async () => {
|
|
123
195
|
const defaultName = `relay-${Date.now().toString(36)}`;
|
|
124
196
|
const token = (
|
|
@@ -158,31 +230,40 @@ export function createCommandSpec(
|
|
|
158
230
|
ctx.ui.notify("No saved relays yet", "warning");
|
|
159
231
|
return;
|
|
160
232
|
}
|
|
161
|
-
const fmt = (r: KnownRelay) =>
|
|
162
|
-
|
|
233
|
+
const fmt = (r: KnownRelay, idx: number) => {
|
|
234
|
+
const isAct = r.url === relayState.url ? "★ " : " ";
|
|
235
|
+
const lbl = r.label ? `[${r.label}] ` : `[${shortRelayLabel(r.url, relayState.relays)}] `;
|
|
236
|
+
return `${isAct}[${idx + 1}] ${lbl}→ ${r.url}`;
|
|
237
|
+
};
|
|
163
238
|
const opts = relayState.relays.map(fmt);
|
|
164
|
-
const choice = await ctx.ui.select("Switch relay", opts);
|
|
239
|
+
const choice = await ctx.ui.select("Switch active relay", opts);
|
|
165
240
|
if (!choice) return;
|
|
166
|
-
const match = relayState.relays.find((r) => fmt(r) === choice);
|
|
241
|
+
const match = relayState.relays.find((r, idx) => fmt(r, idx) === choice);
|
|
167
242
|
if (!match) return;
|
|
168
|
-
setRelay(true, match.url);
|
|
243
|
+
setRelay(true, match.url, match.label);
|
|
169
244
|
persist();
|
|
170
245
|
flash();
|
|
171
246
|
};
|
|
172
247
|
|
|
173
248
|
const showList = () => {
|
|
174
249
|
if (!relayState.relays.length) {
|
|
175
|
-
ctx.ui.notify("No saved relays", "info");
|
|
250
|
+
ctx.ui.notify("No saved relays (direct upstream mode)", "info");
|
|
176
251
|
return;
|
|
177
252
|
}
|
|
178
|
-
const lines = relayState.relays.map(
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
253
|
+
const lines = relayState.relays.map((r, idx) => {
|
|
254
|
+
const star = r.url === relayState.url ? "★" : " ";
|
|
255
|
+
const shortName = r.label ? `[${r.label}]` : `[${shortRelayLabel(r.url, relayState.relays)}]`;
|
|
256
|
+
const paddedName = shortName.padEnd(16, " ");
|
|
257
|
+
return `${star} [${idx + 1}] ${paddedName} → ${r.url}`;
|
|
258
|
+
});
|
|
259
|
+
const activeLabel = shortRelayLabel(relayState.url, relayState.relays);
|
|
260
|
+
const activeIdx = Math.max(
|
|
261
|
+
1,
|
|
262
|
+
relayState.relays.findIndex((r) => r.url === relayState.url) + 1,
|
|
185
263
|
);
|
|
264
|
+
const modeStr = (relayState.mode || "auto").toUpperCase();
|
|
265
|
+
const header = `Saved Relays (${relayState.relays.length}):\n${lines.join("\n")}\n\nActive: [${activeIdx}] ${activeLabel} | Mode: ${modeStr} (${relayState.enabled ? "ON" : "OFF"})`;
|
|
266
|
+
ctx.ui.notify(header, "info");
|
|
186
267
|
};
|
|
187
268
|
|
|
188
269
|
const removeRelayMenu = async () => {
|
|
@@ -196,25 +277,35 @@ export function createCommandSpec(
|
|
|
196
277
|
);
|
|
197
278
|
return;
|
|
198
279
|
}
|
|
199
|
-
const fmt = (r: KnownRelay) =>
|
|
200
|
-
|
|
280
|
+
const fmt = (r: KnownRelay, idx: number) => {
|
|
281
|
+
const lbl = r.label ? `[${r.label}] ` : `[${shortRelayLabel(r.url, relayState.relays)}] `;
|
|
282
|
+
return `[${idx + 1}] ${lbl}→ ${r.url}`;
|
|
283
|
+
};
|
|
201
284
|
const choice = await ctx.ui.select(
|
|
202
285
|
"Remove relay",
|
|
203
286
|
removable.map(fmt),
|
|
204
287
|
);
|
|
205
288
|
if (!choice) return;
|
|
206
|
-
const match = removable.find((r) => fmt(r) === choice);
|
|
289
|
+
const match = removable.find((r, idx) => fmt(r, idx) === choice);
|
|
207
290
|
if (!match) return;
|
|
208
291
|
removeRelay(relayState, match.url);
|
|
209
292
|
persist();
|
|
210
|
-
ctx.ui.notify(`Removed: ${match.url}`, "info");
|
|
293
|
+
ctx.ui.notify(`Removed: [${shortRelayLabel(match.url, relayState.relays)}] ${match.url}`, "info");
|
|
211
294
|
};
|
|
212
295
|
|
|
213
|
-
if (sub === "
|
|
296
|
+
if (sub === "auto") {
|
|
297
|
+
relayState.mode = "auto";
|
|
298
|
+
relayState.enabled = true;
|
|
299
|
+
setRelay(true, relayState.url || DEFAULT_RELAY_URL);
|
|
300
|
+
persist();
|
|
301
|
+
flash();
|
|
302
|
+
} else if (sub === "on") {
|
|
303
|
+
relayState.mode = "on";
|
|
214
304
|
setRelay(true, relayState.url || DEFAULT_RELAY_URL);
|
|
215
305
|
persist();
|
|
216
306
|
flash();
|
|
217
307
|
} else if (sub === "off") {
|
|
308
|
+
relayState.mode = "off";
|
|
218
309
|
relayState.enabled = false;
|
|
219
310
|
setActiveRelayState(relayState);
|
|
220
311
|
persist();
|
|
@@ -223,19 +314,74 @@ export function createCommandSpec(
|
|
|
223
314
|
flash();
|
|
224
315
|
} else if (sub === "list") {
|
|
225
316
|
showList();
|
|
317
|
+
} else if (sub === "add") {
|
|
318
|
+
if (!rest.trim()) {
|
|
319
|
+
await addRelayInteractive();
|
|
320
|
+
} else {
|
|
321
|
+
const tokens = rest.trim().split(/\s+/);
|
|
322
|
+
const targetUrl = tokens[0];
|
|
323
|
+
const customLabel = tokens.slice(1).join(" ").trim() || undefined;
|
|
324
|
+
const added = ensureRelay(relayState, targetUrl, customLabel);
|
|
325
|
+
setRelay(true, added.url, added.label);
|
|
326
|
+
persist();
|
|
327
|
+
flash();
|
|
328
|
+
ctx.ui.notify(
|
|
329
|
+
`✓ Added & activated relay [${added.label || shortRelayLabel(added.url)}]: ${added.url}`,
|
|
330
|
+
"info",
|
|
331
|
+
);
|
|
332
|
+
}
|
|
226
333
|
} else if (sub === "use") {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
334
|
+
if (!rest.trim()) {
|
|
335
|
+
await switchRelay();
|
|
336
|
+
} else {
|
|
337
|
+
const tokens = rest.trim().split(/\s+/);
|
|
338
|
+
const targetToken = tokens[0];
|
|
339
|
+
const customLabel = tokens.slice(1).join(" ").trim() || undefined;
|
|
340
|
+
const matched = findRelay(relayState, targetToken);
|
|
341
|
+
if (matched) {
|
|
342
|
+
if (customLabel) {
|
|
343
|
+
matched.label = customLabel;
|
|
344
|
+
}
|
|
345
|
+
setRelay(true, matched.url, matched.label);
|
|
346
|
+
persist();
|
|
347
|
+
flash();
|
|
348
|
+
} else {
|
|
349
|
+
// New URL
|
|
350
|
+
const added = ensureRelay(relayState, targetToken, customLabel);
|
|
351
|
+
setRelay(true, added.url, added.label);
|
|
352
|
+
persist();
|
|
353
|
+
flash();
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
} else if (sub === "label" || sub === "rename") {
|
|
357
|
+
if (!rest.trim()) {
|
|
358
|
+
await editRelayLabelMenu();
|
|
359
|
+
} else {
|
|
360
|
+
const tokens = rest.trim().split(/\s+/);
|
|
361
|
+
const targetToken = tokens[0];
|
|
362
|
+
const newLabel = tokens.slice(1).join(" ").trim();
|
|
363
|
+
const matched = findRelay(relayState, targetToken);
|
|
364
|
+
if (!matched) {
|
|
365
|
+
ctx.ui.notify(`Relay '${targetToken}' not found in saved list`, "warning");
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
let finalLabel = newLabel;
|
|
369
|
+
if (!finalLabel) {
|
|
370
|
+
finalLabel = (
|
|
371
|
+
await ctx.ui.input(
|
|
372
|
+
`New short name / alias for ${matched.url}:`,
|
|
373
|
+
matched.label || shortRelayLabel(matched.url, relayState.relays),
|
|
374
|
+
)
|
|
375
|
+
)?.trim() || "";
|
|
376
|
+
}
|
|
377
|
+
setRelayLabel(relayState, matched.url, finalLabel);
|
|
378
|
+
persist();
|
|
379
|
+
flash();
|
|
380
|
+
ctx.ui.notify(
|
|
381
|
+
`✓ Relay short name updated to [${shortRelayLabel(matched.url, relayState.relays)}] for ${matched.url}`,
|
|
382
|
+
"info",
|
|
383
|
+
);
|
|
235
384
|
}
|
|
236
|
-
setRelay(true, url, "manual");
|
|
237
|
-
persist();
|
|
238
|
-
flash();
|
|
239
385
|
} else if (sub === "debug") {
|
|
240
386
|
const arg = rest.trim().toLowerCase();
|
|
241
387
|
if (arg === "on" || arg === "enable" || arg === "true") {
|
|
@@ -362,29 +508,26 @@ export function createCommandSpec(
|
|
|
362
508
|
"info",
|
|
363
509
|
);
|
|
364
510
|
} else if (sub === "remove") {
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
511
|
+
if (!rest.trim()) {
|
|
512
|
+
await removeRelayMenu();
|
|
513
|
+
} else {
|
|
514
|
+
const target = rest.trim();
|
|
515
|
+
const matched = findRelay(relayState, target);
|
|
516
|
+
if (!matched) {
|
|
517
|
+
ctx.ui.notify(`Relay '${target}' not in saved list`, "warning");
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
if (matched.url === relayState.url) {
|
|
521
|
+
ctx.ui.notify(
|
|
522
|
+
"Cannot remove the active relay — switch to another relay first",
|
|
523
|
+
"warning",
|
|
524
|
+
);
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
removeRelay(relayState, matched.url);
|
|
528
|
+
persist();
|
|
529
|
+
ctx.ui.notify(`Removed: [${shortRelayLabel(matched.url, relayState.relays)}] ${matched.url}`, "info");
|
|
384
530
|
}
|
|
385
|
-
removeRelay(relayState, url);
|
|
386
|
-
persist();
|
|
387
|
-
ctx.ui.notify(`Removed: ${url}`, "info");
|
|
388
531
|
} else if (sub === "url") {
|
|
389
532
|
const input =
|
|
390
533
|
rest ||
|
|
@@ -392,55 +535,62 @@ export function createCommandSpec(
|
|
|
392
535
|
"Relay URL (empty = default):",
|
|
393
536
|
relayState.url || DEFAULT_RELAY_URL,
|
|
394
537
|
));
|
|
538
|
+
const cleanInput = (input || "").trim() || DEFAULT_RELAY_URL;
|
|
395
539
|
setRelay(
|
|
396
540
|
relayState.enabled,
|
|
397
|
-
|
|
398
|
-
|
|
541
|
+
cleanInput,
|
|
542
|
+
shortRelayLabel(cleanInput, relayState.relays),
|
|
399
543
|
);
|
|
400
544
|
persist();
|
|
401
545
|
flash();
|
|
402
546
|
} else if (sub === "deploy") {
|
|
403
547
|
await doDeploy();
|
|
404
548
|
} else {
|
|
549
|
+
const currentMode = (relayState.mode || "auto").toUpperCase();
|
|
550
|
+
const activeLabel = shortRelayLabel(relayState.url, relayState.relays);
|
|
405
551
|
const choice = await ctx.ui.select("freeflow relay", [
|
|
406
|
-
`
|
|
407
|
-
"
|
|
408
|
-
"
|
|
409
|
-
"
|
|
552
|
+
`Mode: ${currentMode} (${relayState.enabled ? "ON" : "OFF"}) → ${activeLabel}`,
|
|
553
|
+
"Add custom relay (URL + Short name)…",
|
|
554
|
+
"Switch active relay…",
|
|
555
|
+
"Rename / Set relay short name…",
|
|
410
556
|
"Remove relay…",
|
|
411
|
-
"Set URL",
|
|
412
|
-
"Deploy Vercel relay…",
|
|
413
557
|
"List saved relays",
|
|
558
|
+
"Deploy Vercel relay…",
|
|
559
|
+
"Mode: AUTO (auto-detect on model select)",
|
|
560
|
+
"Mode: ON (always relay)",
|
|
561
|
+
"Mode: OFF (always direct)",
|
|
414
562
|
]);
|
|
415
|
-
if (choice === "
|
|
563
|
+
if (choice === `Mode: ${currentMode} (${relayState.enabled ? "ON" : "OFF"}) → ${activeLabel}`) {
|
|
564
|
+
flash();
|
|
565
|
+
} else if (choice === "Add custom relay (URL + Short name)…") {
|
|
566
|
+
await addRelayInteractive();
|
|
567
|
+
} else if (choice === "Switch active relay…") {
|
|
568
|
+
await switchRelay();
|
|
569
|
+
} else if (choice === "Rename / Set relay short name…") {
|
|
570
|
+
await editRelayLabelMenu();
|
|
571
|
+
} else if (choice === "Remove relay…") {
|
|
572
|
+
await removeRelayMenu();
|
|
573
|
+
} else if (choice === "List saved relays") {
|
|
574
|
+
showList();
|
|
575
|
+
} else if (choice === "Deploy Vercel relay…") {
|
|
576
|
+
await doDeploy();
|
|
577
|
+
} else if (choice === "Mode: AUTO (auto-detect on model select)") {
|
|
578
|
+
relayState.mode = "auto";
|
|
579
|
+
relayState.enabled = true;
|
|
416
580
|
setRelay(true, relayState.url || DEFAULT_RELAY_URL);
|
|
417
581
|
persist();
|
|
418
582
|
flash();
|
|
419
|
-
} else if (choice === "
|
|
420
|
-
relayState.
|
|
421
|
-
|
|
583
|
+
} else if (choice === "Mode: ON (always relay)") {
|
|
584
|
+
relayState.mode = "on";
|
|
585
|
+
setRelay(true, relayState.url || DEFAULT_RELAY_URL);
|
|
422
586
|
persist();
|
|
423
587
|
flash();
|
|
424
|
-
} else if (choice === "
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
} else if (choice === "Set URL") {
|
|
429
|
-
const input = await ctx.ui.input(
|
|
430
|
-
"Relay URL (empty = default):",
|
|
431
|
-
relayState.url || DEFAULT_RELAY_URL,
|
|
432
|
-
);
|
|
433
|
-
setRelay(
|
|
434
|
-
relayState.enabled,
|
|
435
|
-
(input || "").trim() || DEFAULT_RELAY_URL,
|
|
436
|
-
"manual",
|
|
437
|
-
);
|
|
588
|
+
} else if (choice === "Mode: OFF (always direct)") {
|
|
589
|
+
relayState.mode = "off";
|
|
590
|
+
relayState.enabled = false;
|
|
591
|
+
setActiveRelayState(relayState);
|
|
438
592
|
persist();
|
|
439
593
|
flash();
|
|
440
|
-
} else if (choice === "Deploy Vercel relay…") {
|
|
441
|
-
await doDeploy();
|
|
442
|
-
} else if (choice === "List saved relays") {
|
|
443
|
-
showList();
|
|
444
594
|
}
|
|
445
595
|
}
|
|
446
596
|
},
|
package/src/index.ts
CHANGED
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
import { createCommandSpec, updateStatusBar } from "./commands.ts";
|
|
19
19
|
import { DEFAULT_HOST, HOST, PORT } from "./config.ts";
|
|
20
20
|
import { log, logInfo, logWarn } from "./logger.ts";
|
|
21
|
-
import { ALL_MODELS, KILO_MODEL_IDS } from "./models.ts";
|
|
21
|
+
import { ALL_MODELS, KILO_MODEL_IDS, MODEL_MAP, getAllRegisteredModels, resolveCanonicalModelId } from "./models.ts";
|
|
22
22
|
import { isProxyAlive, startProxy } from "./proxy.ts";
|
|
23
23
|
import { resetRateLimits } from "./rate-limiter.ts";
|
|
24
24
|
import {
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
resolveRelayState,
|
|
27
27
|
setActiveRelayState,
|
|
28
28
|
setStatusUi,
|
|
29
|
+
setFreeFlowModelActive,
|
|
29
30
|
} from "./relay-state.ts";
|
|
30
31
|
import type {
|
|
31
32
|
ExtensionAPI,
|
|
@@ -138,14 +139,14 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
138
139
|
|
|
139
140
|
// 3. Instant 0ms Static Catalog Registration
|
|
140
141
|
// Register static models immediately on boot so Pi/OMP picker is populated with zero latency!
|
|
141
|
-
const
|
|
142
|
+
const registeredCatalog: RegisteredModel[] = getAllRegisteredModels().map((m) => ({
|
|
142
143
|
...m,
|
|
143
144
|
source: KILO_MODEL_IDS.has(m.id) ? "kilo" : "opencode",
|
|
144
145
|
}));
|
|
145
|
-
setAliveCatalog(
|
|
146
|
+
setAliveCatalog(ALL_MODELS.map((m) => ({ ...m, source: KILO_MODEL_IDS.has(m.id) ? "kilo" : "opencode" })));
|
|
146
147
|
pi.registerProvider(
|
|
147
148
|
"freeflow",
|
|
148
|
-
buildProviderConfig(
|
|
149
|
+
buildProviderConfig(registeredCatalog, actualPort),
|
|
149
150
|
);
|
|
150
151
|
|
|
151
152
|
// 4. Background Catalog Refresh
|
|
@@ -156,7 +157,7 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
156
157
|
setAliveCatalog(aliveModels);
|
|
157
158
|
pi.registerProvider(
|
|
158
159
|
"freeflow",
|
|
159
|
-
buildProviderConfig(
|
|
160
|
+
buildProviderConfig(registeredCatalog, actualPort),
|
|
160
161
|
);
|
|
161
162
|
logInfo(
|
|
162
163
|
`Catalog refreshed: ${aliveModels.length} models verified active`,
|
|
@@ -179,9 +180,27 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
179
180
|
pi.registerCommand("freeflow", commandSpec);
|
|
180
181
|
|
|
181
182
|
// 6. Lifecycle Listeners
|
|
183
|
+
function isFreeFlowModelMatch(provider?: string, modelId?: string): boolean {
|
|
184
|
+
if (provider === "freeflow") return true;
|
|
185
|
+
if (typeof modelId === "string" && modelId.trim()) {
|
|
186
|
+
const clean = modelId.trim();
|
|
187
|
+
const canonical = resolveCanonicalModelId(clean);
|
|
188
|
+
return (
|
|
189
|
+
MODEL_MAP.has(clean) ||
|
|
190
|
+
MODEL_MAP.has(canonical) ||
|
|
191
|
+
getAliveCatalog().some((m) => m.id === clean || m.id === canonical)
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
// When provider and modelId are not yet populated on session_start,
|
|
195
|
+
// default to true so the widget is present when starting with freeflow
|
|
196
|
+
if (!provider && !modelId) {
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
|
|
182
202
|
pi.on?.("session_start", async (_event, ctx: ExtensionContext) => {
|
|
183
203
|
const freshRelayState = resolveRelayState();
|
|
184
|
-
setActiveRelayState(freshRelayState, false);
|
|
185
204
|
setStatusUi(ctx.ui);
|
|
186
205
|
|
|
187
206
|
let provider: string | undefined;
|
|
@@ -195,9 +214,13 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
195
214
|
modelId = m.id;
|
|
196
215
|
}
|
|
197
216
|
}
|
|
198
|
-
const isFreeFlow =
|
|
199
|
-
|
|
200
|
-
|
|
217
|
+
const isFreeFlow = isFreeFlowModelMatch(provider, modelId);
|
|
218
|
+
setFreeFlowModelActive(isFreeFlow);
|
|
219
|
+
|
|
220
|
+
if (freshRelayState.mode !== "off") {
|
|
221
|
+
freshRelayState.enabled = freshRelayState.relays.length > 0;
|
|
222
|
+
setActiveRelayState(freshRelayState, false);
|
|
223
|
+
}
|
|
201
224
|
|
|
202
225
|
if (isFreeFlow) {
|
|
203
226
|
updateStatusBar(ctx.ui);
|
|
@@ -208,7 +231,6 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
208
231
|
|
|
209
232
|
pi.on?.("model_select", async (event, ctx: ExtensionContext) => {
|
|
210
233
|
const freshRelayState = resolveRelayState();
|
|
211
|
-
setActiveRelayState(freshRelayState, false);
|
|
212
234
|
setStatusUi(ctx.ui);
|
|
213
235
|
let provider: string | undefined;
|
|
214
236
|
let modelId: string | undefined;
|
|
@@ -220,10 +242,22 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
220
242
|
if ("id" in m && typeof m.id === "string") {
|
|
221
243
|
modelId = m.id;
|
|
222
244
|
}
|
|
245
|
+
} else if (ctx && typeof ctx === "object" && "model" in ctx && ctx.model && typeof ctx.model === "object") {
|
|
246
|
+
const m = ctx.model;
|
|
247
|
+
if ("provider" in m && typeof m.provider === "string") {
|
|
248
|
+
provider = m.provider;
|
|
249
|
+
}
|
|
250
|
+
if ("id" in m && typeof m.id === "string") {
|
|
251
|
+
modelId = m.id;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
const isFreeFlow = isFreeFlowModelMatch(provider, modelId);
|
|
255
|
+
setFreeFlowModelActive(isFreeFlow);
|
|
256
|
+
|
|
257
|
+
if (freshRelayState.mode !== "off") {
|
|
258
|
+
freshRelayState.enabled = freshRelayState.relays.length > 0;
|
|
259
|
+
setActiveRelayState(freshRelayState, false);
|
|
223
260
|
}
|
|
224
|
-
const isFreeFlow =
|
|
225
|
-
provider === "freeflow" ||
|
|
226
|
-
Boolean(modelId && getAliveCatalog().some((m) => m.id === modelId));
|
|
227
261
|
|
|
228
262
|
if (isFreeFlow) {
|
|
229
263
|
updateStatusBar(ctx.ui);
|
|
@@ -231,7 +265,6 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
231
265
|
ctx.ui?.setStatus?.("freeflow", undefined);
|
|
232
266
|
}
|
|
233
267
|
});
|
|
234
|
-
|
|
235
268
|
pi.on?.("session_shutdown", () => {
|
|
236
269
|
if (server) {
|
|
237
270
|
logInfo("shutting down proxy daemon...");
|