@victor-software-house/pi-openai-proxy 2.1.0 → 3.0.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/dist/config.mjs +1 -1
- package/dist/exposure.d.mts +5 -1
- package/dist/exposure.mjs +9 -8
- package/dist/index.mjs +7 -1
- package/extensions/proxy.ts +166 -179
- package/package.json +1 -1
package/dist/config.mjs
CHANGED
package/dist/exposure.d.mts
CHANGED
|
@@ -37,9 +37,13 @@ type ModelExposureOutcome = ModelExposureResult | ModelExposureError;
|
|
|
37
37
|
/**
|
|
38
38
|
* Compute the full model-exposure result from config and available models.
|
|
39
39
|
*
|
|
40
|
+
* @param available - Models with auth configured (pi's getAvailable())
|
|
41
|
+
* @param allRegistered - All registered models regardless of auth (pi's getAll())
|
|
42
|
+
* @param config - Model exposure configuration
|
|
43
|
+
*
|
|
40
44
|
* Call this at startup and whenever config or the model registry changes.
|
|
41
45
|
*/
|
|
42
|
-
declare function computeModelExposure(available: readonly Model<Api>[], config: ModelExposureConfig): ModelExposureOutcome;
|
|
46
|
+
declare function computeModelExposure(available: readonly Model<Api>[], allRegistered: readonly Model<Api>[], config: ModelExposureConfig): ModelExposureOutcome;
|
|
43
47
|
/**
|
|
44
48
|
* Resolve a model ID from an incoming request against the exposure result.
|
|
45
49
|
*
|
package/dist/exposure.mjs
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
//#region src/openai/model-exposure.ts
|
|
3
|
-
function filterExposedModels(available, config) {
|
|
3
|
+
function filterExposedModels(available, allRegistered, config) {
|
|
4
4
|
switch (config.modelExposureMode) {
|
|
5
|
-
case "
|
|
6
|
-
case "
|
|
7
|
-
const providers = new Set(config.scopedProviders);
|
|
8
|
-
return available.filter((m) => providers.has(m.provider));
|
|
9
|
-
}
|
|
5
|
+
case "scoped": return [...available];
|
|
6
|
+
case "all": return [...allRegistered];
|
|
10
7
|
case "custom": {
|
|
11
8
|
const allowed = new Set(config.customModels);
|
|
12
9
|
return available.filter((m) => allowed.has(`${m.provider}/${m.id}`));
|
|
@@ -125,10 +122,14 @@ function validatePrefixUniqueness(models, prefixes, mode) {
|
|
|
125
122
|
/**
|
|
126
123
|
* Compute the full model-exposure result from config and available models.
|
|
127
124
|
*
|
|
125
|
+
* @param available - Models with auth configured (pi's getAvailable())
|
|
126
|
+
* @param allRegistered - All registered models regardless of auth (pi's getAll())
|
|
127
|
+
* @param config - Model exposure configuration
|
|
128
|
+
*
|
|
128
129
|
* Call this at startup and whenever config or the model registry changes.
|
|
129
130
|
*/
|
|
130
|
-
function computeModelExposure(available, config) {
|
|
131
|
-
const exposed = filterExposedModels(available, config);
|
|
131
|
+
function computeModelExposure(available, allRegistered, config) {
|
|
132
|
+
const exposed = filterExposedModels(available, allRegistered, config);
|
|
132
133
|
const prefixError = validatePrefixUniqueness(exposed, config.providerPrefixes, config.publicModelIdMode);
|
|
133
134
|
if (prefixError !== void 0) return {
|
|
134
135
|
ok: false,
|
package/dist/index.mjs
CHANGED
|
@@ -66,6 +66,12 @@ function getRegistry() {
|
|
|
66
66
|
function getAvailableModels() {
|
|
67
67
|
return getRegistry().getAvailable();
|
|
68
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Get all registered models (regardless of auth state).
|
|
71
|
+
*/
|
|
72
|
+
function getAllModels() {
|
|
73
|
+
return getRegistry().getAll();
|
|
74
|
+
}
|
|
69
75
|
//#endregion
|
|
70
76
|
//#region src/server/errors.ts
|
|
71
77
|
function makeError(message, type, param, code) {
|
|
@@ -1294,7 +1300,7 @@ function buildExposureConfig(config) {
|
|
|
1294
1300
|
* Returns the exposure result or throws on config errors.
|
|
1295
1301
|
*/
|
|
1296
1302
|
function getExposure(config) {
|
|
1297
|
-
const outcome = computeModelExposure(getAvailableModels(), buildExposureConfig(config));
|
|
1303
|
+
const outcome = computeModelExposure(getAvailableModels(), getAllModels(), buildExposureConfig(config));
|
|
1298
1304
|
if (!outcome.ok) throw new Error(`Model exposure configuration error: ${outcome.message}`);
|
|
1299
1305
|
return outcome;
|
|
1300
1306
|
}
|
package/extensions/proxy.ts
CHANGED
|
@@ -30,7 +30,13 @@ import {
|
|
|
30
30
|
getSettingsListTheme,
|
|
31
31
|
ModelRegistry,
|
|
32
32
|
} from "@mariozechner/pi-coding-agent";
|
|
33
|
-
import {
|
|
33
|
+
import {
|
|
34
|
+
type Component,
|
|
35
|
+
Container,
|
|
36
|
+
type SettingItem,
|
|
37
|
+
SettingsList,
|
|
38
|
+
Text,
|
|
39
|
+
} from "@mariozechner/pi-tui";
|
|
34
40
|
|
|
35
41
|
// Config schema -- single source of truth
|
|
36
42
|
import {
|
|
@@ -532,27 +538,92 @@ export default function proxyExtension(pi: ExtensionAPI): void {
|
|
|
532
538
|
|
|
533
539
|
let lastGeneratedToken = "";
|
|
534
540
|
|
|
535
|
-
function
|
|
536
|
-
|
|
541
|
+
function customModelsDisplay(): string {
|
|
542
|
+
if (config.modelExposureMode !== "custom") return "n/a";
|
|
543
|
+
return config.customModels.length > 0
|
|
544
|
+
? `${String(config.customModels.length)} selected`
|
|
545
|
+
: "(none)";
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Build a submenu Component for selecting custom models.
|
|
550
|
+
* Shows all available models as a toggleable checklist.
|
|
551
|
+
*/
|
|
552
|
+
function buildModelSelectorSubmenu(
|
|
553
|
+
_currentValue: string,
|
|
554
|
+
done: (selectedValue?: string) => void,
|
|
555
|
+
): Component {
|
|
537
556
|
const models = getAvailableModels();
|
|
538
|
-
const
|
|
557
|
+
const selected = new Set(config.customModels);
|
|
558
|
+
let selectedIndex = 0;
|
|
539
559
|
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
560
|
+
function toggle(canonicalId: string): void {
|
|
561
|
+
if (selected.has(canonicalId)) {
|
|
562
|
+
selected.delete(canonicalId);
|
|
563
|
+
} else {
|
|
564
|
+
selected.add(canonicalId);
|
|
565
|
+
}
|
|
566
|
+
config = { ...config, customModels: [...selected] };
|
|
567
|
+
saveConfigToFile(config);
|
|
568
|
+
config = loadConfigFromFile();
|
|
569
|
+
}
|
|
544
570
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
571
|
+
return {
|
|
572
|
+
render(width: number): string[] {
|
|
573
|
+
const lines: string[] = [];
|
|
574
|
+
lines.push(" Select models (Space: toggle, Esc: done)");
|
|
575
|
+
lines.push("");
|
|
576
|
+
|
|
577
|
+
if (models.length === 0) {
|
|
578
|
+
lines.push(" No models available (no auth configured)");
|
|
579
|
+
return lines;
|
|
580
|
+
}
|
|
548
581
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
582
|
+
const maxVisible = 15;
|
|
583
|
+
const start = Math.max(
|
|
584
|
+
0,
|
|
585
|
+
Math.min(selectedIndex - Math.floor(maxVisible / 2), models.length - maxVisible),
|
|
586
|
+
);
|
|
587
|
+
const end = Math.min(start + maxVisible, models.length);
|
|
588
|
+
|
|
589
|
+
for (let i = start; i < end; i++) {
|
|
590
|
+
const m = models[i];
|
|
591
|
+
if (m === undefined) continue;
|
|
592
|
+
const canonical = `${m.provider}/${m.id}`;
|
|
593
|
+
const check = selected.has(canonical) ? "[x]" : "[ ]";
|
|
594
|
+
const cursor = i === selectedIndex ? "> " : " ";
|
|
595
|
+
const line = `${cursor}${check} ${canonical}`;
|
|
596
|
+
const truncated = line.length > width ? line.slice(0, width - 1) : line;
|
|
597
|
+
lines.push(truncated);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
lines.push("");
|
|
601
|
+
lines.push(` ${String(selected.size)} of ${String(models.length)} selected`);
|
|
602
|
+
return lines;
|
|
603
|
+
},
|
|
604
|
+
invalidate(): void {
|
|
605
|
+
// no-op
|
|
606
|
+
},
|
|
607
|
+
handleInput(data: string): void {
|
|
608
|
+
if (data === "\x1B" || data === "q") {
|
|
609
|
+
done(`${String(selected.size)} selected`);
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
if (data === "\x1B[A" && selectedIndex > 0) {
|
|
613
|
+
selectedIndex--;
|
|
614
|
+
} else if (data === "\x1B[B" && selectedIndex < models.length - 1) {
|
|
615
|
+
selectedIndex++;
|
|
616
|
+
} else if (data === " " || data === "\r") {
|
|
617
|
+
const m = models[selectedIndex];
|
|
618
|
+
if (m !== undefined) {
|
|
619
|
+
toggle(`${m.provider}/${m.id}`);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
},
|
|
623
|
+
};
|
|
624
|
+
}
|
|
555
625
|
|
|
626
|
+
function buildSettingItems(): SettingItem[] {
|
|
556
627
|
return [
|
|
557
628
|
// --- Server ---
|
|
558
629
|
{
|
|
@@ -618,67 +689,24 @@ export default function proxyExtension(pi: ExtensionAPI): void {
|
|
|
618
689
|
{
|
|
619
690
|
id: "modelExposureMode",
|
|
620
691
|
label: "Exposure mode",
|
|
621
|
-
description:
|
|
692
|
+
description:
|
|
693
|
+
"scoped = pi's available models, all = all registered, custom = manual selection",
|
|
622
694
|
currentValue: config.modelExposureMode,
|
|
623
|
-
values: ["
|
|
624
|
-
},
|
|
625
|
-
{
|
|
626
|
-
id: "scopedProviders",
|
|
627
|
-
label: "Scoped providers",
|
|
628
|
-
description: `Toggle providers for scoped mode. Available: ${availableProviders.join(", ") || "(none)"}`,
|
|
629
|
-
currentValue: scopedDisplay,
|
|
630
|
-
values: availableProviders.map((p) => {
|
|
631
|
-
const selected = scopedSet.has(p);
|
|
632
|
-
return selected ? `[-] ${p}` : `[+] ${p}`;
|
|
633
|
-
}),
|
|
695
|
+
values: ["scoped", "all", "custom"],
|
|
634
696
|
},
|
|
635
697
|
{
|
|
636
698
|
id: "customModels",
|
|
637
|
-
label: "
|
|
638
|
-
description:
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
label: "Prefix overrides",
|
|
645
|
-
description: "Custom prefix labels for providers (provider=label)",
|
|
646
|
-
currentValue: prefixDisplay,
|
|
647
|
-
values: buildPrefixValues(availableProviders),
|
|
699
|
+
label: "Select models",
|
|
700
|
+
description:
|
|
701
|
+
config.modelExposureMode === "custom"
|
|
702
|
+
? "Press Enter to open model selector"
|
|
703
|
+
: "Switch exposure mode to 'custom' to select models",
|
|
704
|
+
currentValue: customModelsDisplay(),
|
|
705
|
+
submenu: config.modelExposureMode === "custom" ? buildModelSelectorSubmenu : undefined,
|
|
648
706
|
},
|
|
649
707
|
];
|
|
650
708
|
}
|
|
651
709
|
|
|
652
|
-
/**
|
|
653
|
-
* Build toggle values for custom model selection.
|
|
654
|
-
* Each model is shown as "[+] provider/id" or "[-] provider/id".
|
|
655
|
-
*/
|
|
656
|
-
function buildCustomModelValues(models: readonly Model<Api>[]): string[] {
|
|
657
|
-
const customSet = new Set(config.customModels);
|
|
658
|
-
return models.map((m) => {
|
|
659
|
-
const canonical = `${m.provider}/${m.id}`;
|
|
660
|
-
const selected = customSet.has(canonical);
|
|
661
|
-
return selected ? `[-] ${canonical}` : `[+] ${canonical}`;
|
|
662
|
-
});
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
/**
|
|
666
|
-
* Build toggle values for prefix override editing.
|
|
667
|
-
* Each provider is shown as "provider=label" or "provider (default)".
|
|
668
|
-
*/
|
|
669
|
-
function buildPrefixValues(providers: readonly string[]): string[] {
|
|
670
|
-
const values: string[] = [];
|
|
671
|
-
for (const p of providers) {
|
|
672
|
-
const override = config.providerPrefixes[p];
|
|
673
|
-
if (override !== undefined && override.length > 0) {
|
|
674
|
-
values.push(`clear ${p}`);
|
|
675
|
-
} else {
|
|
676
|
-
values.push(`set ${p}`);
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
return values;
|
|
680
|
-
}
|
|
681
|
-
|
|
682
710
|
const VALID_ID_MODES = new Set<string>(["collision-prefixed", "universal", "always-prefixed"]);
|
|
683
711
|
|
|
684
712
|
function isPublicModelIdMode(v: string): v is PublicModelIdMode {
|
|
@@ -743,14 +771,8 @@ export default function proxyExtension(pi: ExtensionAPI): void {
|
|
|
743
771
|
config = { ...config, modelExposureMode: value };
|
|
744
772
|
}
|
|
745
773
|
break;
|
|
746
|
-
case "scopedProviders":
|
|
747
|
-
applyScopedProviderToggle(value);
|
|
748
|
-
break;
|
|
749
774
|
case "customModels":
|
|
750
|
-
|
|
751
|
-
break;
|
|
752
|
-
case "providerPrefixes":
|
|
753
|
-
applyPrefixAction(value);
|
|
775
|
+
// Handled by submenu -- no cycling
|
|
754
776
|
break;
|
|
755
777
|
}
|
|
756
778
|
saveConfigToFile(config);
|
|
@@ -758,70 +780,32 @@ export default function proxyExtension(pi: ExtensionAPI): void {
|
|
|
758
780
|
}
|
|
759
781
|
|
|
760
782
|
/**
|
|
761
|
-
*
|
|
762
|
-
* Value format: "[+] provider" to add, "[-] provider" to remove.
|
|
783
|
+
* Get the display value for a setting after it has been applied.
|
|
763
784
|
*/
|
|
764
|
-
function
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
const canonicalId = match[2];
|
|
789
|
-
if (canonicalId === undefined) return;
|
|
790
|
-
|
|
791
|
-
const current = new Set(config.customModels);
|
|
792
|
-
if (action === "+") {
|
|
793
|
-
current.add(canonicalId);
|
|
794
|
-
} else {
|
|
795
|
-
current.delete(canonicalId);
|
|
796
|
-
}
|
|
797
|
-
config = { ...config, customModels: [...current] };
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
/**
|
|
801
|
-
* Apply a prefix override action.
|
|
802
|
-
* Value format: "set provider" to prompt for a label, "clear provider" to remove override.
|
|
803
|
-
*/
|
|
804
|
-
function applyPrefixAction(value: string): void {
|
|
805
|
-
const clearMatch = /^clear\s+(.+)$/.exec(value);
|
|
806
|
-
if (clearMatch !== null) {
|
|
807
|
-
const provider = clearMatch[1];
|
|
808
|
-
if (provider === undefined) return;
|
|
809
|
-
const next = { ...config.providerPrefixes };
|
|
810
|
-
delete next[provider];
|
|
811
|
-
config = { ...config, providerPrefixes: next };
|
|
812
|
-
return;
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
// "set provider" -- set a simple abbreviated prefix
|
|
816
|
-
const setMatch = /^set\s+(.+)$/.exec(value);
|
|
817
|
-
if (setMatch !== null) {
|
|
818
|
-
const provider = setMatch[1];
|
|
819
|
-
if (provider === undefined) return;
|
|
820
|
-
// Use first 3 characters as a short prefix (user can edit JSON for custom values)
|
|
821
|
-
const shortPrefix = provider.slice(0, 3);
|
|
822
|
-
const next = { ...config.providerPrefixes };
|
|
823
|
-
next[provider] = shortPrefix;
|
|
824
|
-
config = { ...config, providerPrefixes: next };
|
|
785
|
+
function getDisplayValue(id: string): string {
|
|
786
|
+
switch (id) {
|
|
787
|
+
case "lifetime":
|
|
788
|
+
return config.lifetime;
|
|
789
|
+
case "host":
|
|
790
|
+
return config.host;
|
|
791
|
+
case "port":
|
|
792
|
+
return String(config.port);
|
|
793
|
+
case "authToken":
|
|
794
|
+
return config.authToken.length > 0 ? "enabled" : "disabled";
|
|
795
|
+
case "remoteImages":
|
|
796
|
+
return config.remoteImages ? "on" : "off";
|
|
797
|
+
case "maxBodySizeMb":
|
|
798
|
+
return `${String(config.maxBodySizeMb)} MB`;
|
|
799
|
+
case "upstreamTimeoutSec":
|
|
800
|
+
return `${String(config.upstreamTimeoutSec)}s`;
|
|
801
|
+
case "publicModelIdMode":
|
|
802
|
+
return config.publicModelIdMode;
|
|
803
|
+
case "modelExposureMode":
|
|
804
|
+
return config.modelExposureMode;
|
|
805
|
+
case "customModels":
|
|
806
|
+
return customModelsDisplay();
|
|
807
|
+
default:
|
|
808
|
+
return "";
|
|
825
809
|
}
|
|
826
810
|
}
|
|
827
811
|
|
|
@@ -830,54 +814,57 @@ export default function proxyExtension(pi: ExtensionAPI): void {
|
|
|
830
814
|
|
|
831
815
|
await ctx.ui.custom<void>(
|
|
832
816
|
(tui, theme, _kb, done) => {
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
container.addChild(
|
|
857
|
-
new Text(
|
|
858
|
-
theme.fg(
|
|
859
|
-
"dim",
|
|
860
|
-
"Esc: close | Arrow keys: navigate | Space: toggle | Restart proxy to apply",
|
|
861
|
-
),
|
|
862
|
-
1,
|
|
863
|
-
0,
|
|
864
|
-
),
|
|
865
|
-
);
|
|
817
|
+
const container = new Container();
|
|
818
|
+
container.addChild(new Text(theme.fg("accent", theme.bold("Proxy Settings")), 1, 0));
|
|
819
|
+
container.addChild(new Text(theme.fg("dim", getConfigPath()), 1, 0));
|
|
820
|
+
|
|
821
|
+
const items = buildSettingItems();
|
|
822
|
+
const settingsList = new SettingsList(
|
|
823
|
+
items,
|
|
824
|
+
12,
|
|
825
|
+
getSettingsListTheme(),
|
|
826
|
+
(id, newValue) => {
|
|
827
|
+
lastGeneratedToken = "";
|
|
828
|
+
applySetting(id, newValue);
|
|
829
|
+
if (lastGeneratedToken.length > 0) {
|
|
830
|
+
ctx.ui.notify(`Auth token: ${lastGeneratedToken}`, "info");
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
// Update display value in-place (no rebuild, preserves selection)
|
|
834
|
+
settingsList.updateValue(id, getDisplayValue(id));
|
|
835
|
+
|
|
836
|
+
// When exposure mode changes, update the "Select models" item
|
|
837
|
+
if (id === "modelExposureMode") {
|
|
838
|
+
settingsList.updateValue("customModels", customModelsDisplay());
|
|
839
|
+
}
|
|
866
840
|
|
|
867
|
-
|
|
868
|
-
|
|
841
|
+
tui.requestRender();
|
|
842
|
+
},
|
|
843
|
+
() => done(undefined),
|
|
844
|
+
{ enableSearch: true },
|
|
845
|
+
);
|
|
869
846
|
|
|
870
|
-
|
|
847
|
+
container.addChild(settingsList);
|
|
848
|
+
container.addChild(
|
|
849
|
+
new Text(
|
|
850
|
+
theme.fg(
|
|
851
|
+
"dim",
|
|
852
|
+
"Esc: close | Arrow keys: navigate | Space: toggle | Restart proxy to apply",
|
|
853
|
+
),
|
|
854
|
+
1,
|
|
855
|
+
0,
|
|
856
|
+
),
|
|
857
|
+
);
|
|
871
858
|
|
|
872
859
|
return {
|
|
873
860
|
render(width: number): string[] {
|
|
874
|
-
return
|
|
861
|
+
return container.render(width);
|
|
875
862
|
},
|
|
876
863
|
invalidate(): void {
|
|
877
|
-
|
|
864
|
+
container.invalidate();
|
|
878
865
|
},
|
|
879
866
|
handleInput(data: string): void {
|
|
880
|
-
|
|
867
|
+
settingsList.handleInput?.(data);
|
|
881
868
|
tui.requestRender();
|
|
882
869
|
},
|
|
883
870
|
};
|
package/package.json
CHANGED