@victor-software-house/pi-openai-proxy 4.5.0 → 4.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/config.d.mts CHANGED
@@ -1,3 +1,3 @@
1
1
 
2
- import { a as ZedSyncConfig, c as isModelExposureMode, d as normalizeConfig, f as saveConfigToFile, i as PublicModelIdMode, l as isPublicModelIdMode, n as ModelExposureMode, o as configToEnv, r as ProxyConfig, s as getConfigPath, t as DEFAULT_CONFIG, u as loadConfigFromFile } from "./schema-B2x0rLJN.mjs";
2
+ import { a as ZedSyncConfig, c as isModelExposureMode, d as normalizeConfig, f as saveConfigToFile, i as PublicModelIdMode, l as isPublicModelIdMode, n as ModelExposureMode, o as configToEnv, r as ProxyConfig, s as getConfigPath, t as DEFAULT_CONFIG, u as loadConfigFromFile } from "./schema-CrOJW-mE.mjs";
3
3
  export { DEFAULT_CONFIG, ModelExposureMode, ProxyConfig, PublicModelIdMode, ZedSyncConfig, configToEnv, getConfigPath, isModelExposureMode, isPublicModelIdMode, loadConfigFromFile, normalizeConfig, saveConfigToFile };
package/dist/config.mjs CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env bun
2
- import { a as isPublicModelIdMode, c as saveConfigToFile, i as isModelExposureMode, n as configToEnv, o as loadConfigFromFile, r as getConfigPath, s as normalizeConfig, t as DEFAULT_CONFIG } from "./schema-IE4WK0Dj.mjs";
2
+ import { a as isPublicModelIdMode, c as saveConfigToFile, i as isModelExposureMode, n as configToEnv, o as loadConfigFromFile, r as getConfigPath, s as normalizeConfig, t as DEFAULT_CONFIG } from "./schema-quaXHZsz.mjs";
3
3
  export { DEFAULT_CONFIG, configToEnv, getConfigPath, isModelExposureMode, isPublicModelIdMode, loadConfigFromFile, normalizeConfig, saveConfigToFile };
@@ -1,5 +1,5 @@
1
1
 
2
- import { i as PublicModelIdMode, n as ModelExposureMode } from "./schema-B2x0rLJN.mjs";
2
+ import { i as PublicModelIdMode, n as ModelExposureMode } from "./schema-CrOJW-mE.mjs";
3
3
  import { Api, Model } from "@mariozechner/pi-ai";
4
4
 
5
5
  //#region src/openai/model-exposure.d.ts
@@ -16,7 +16,13 @@ interface ExposedModel {
16
16
  interface ModelExposureConfig {
17
17
  readonly publicModelIdMode: PublicModelIdMode;
18
18
  readonly modelExposureMode: ModelExposureMode;
19
- readonly scopedProviders: readonly string[];
19
+ /**
20
+ * Canonical model IDs from pi's global `enabledModels` setting.
21
+ * Read from `SettingsManager.getEnabledModels()` at request time.
22
+ * Undefined or empty means no filter (all available models exposed).
23
+ * Used only when modelExposureMode is "scoped".
24
+ */
25
+ readonly enabledModels: readonly string[] | undefined;
20
26
  readonly customModels: readonly string[];
21
27
  readonly providerPrefixes: Readonly<Record<string, string>>;
22
28
  }
@@ -38,12 +44,11 @@ type ModelExposureOutcome = ModelExposureResult | ModelExposureError;
38
44
  * Compute the full model-exposure result from config and available models.
39
45
  *
40
46
  * @param available - Models with auth configured (pi's getAvailable())
41
- * @param allRegistered - All registered models regardless of auth (pi's getAll())
42
47
  * @param config - Model exposure configuration
43
48
  *
44
49
  * Call this at startup and whenever config or the model registry changes.
45
50
  */
46
- declare function computeModelExposure(available: readonly Model<Api>[], allRegistered: readonly Model<Api>[], config: ModelExposureConfig): ModelExposureOutcome;
51
+ declare function computeModelExposure(available: readonly Model<Api>[], config: ModelExposureConfig): ModelExposureOutcome;
47
52
  /**
48
53
  * Resolve a model ID from an incoming request against the exposure result.
49
54
  *
package/dist/exposure.mjs CHANGED
@@ -1,14 +1,16 @@
1
1
  #!/usr/bin/env bun
2
2
  //#region src/openai/model-exposure.ts
3
- function filterExposedModels(available, allRegistered, config) {
3
+ function filterExposedModels(available, config) {
4
4
  switch (config.modelExposureMode) {
5
+ case "all": return [...available];
5
6
  case "scoped": {
6
- if (config.scopedProviders.length === 0) return [...available];
7
- const allowed = new Set(config.scopedProviders);
8
- return available.filter((m) => allowed.has(m.provider));
7
+ const enabled = config.enabledModels;
8
+ if (enabled === void 0 || enabled.length === 0) return [...available];
9
+ const allowed = new Set(enabled);
10
+ return available.filter((m) => allowed.has(`${m.provider}/${m.id}`));
9
11
  }
10
- case "all": return [...allRegistered];
11
12
  case "custom": {
13
+ if (config.customModels.length === 0) return [...available];
12
14
  const allowed = new Set(config.customModels);
13
15
  return available.filter((m) => allowed.has(`${m.provider}/${m.id}`));
14
16
  }
@@ -127,13 +129,12 @@ function validatePrefixUniqueness(models, prefixes, mode) {
127
129
  * Compute the full model-exposure result from config and available models.
128
130
  *
129
131
  * @param available - Models with auth configured (pi's getAvailable())
130
- * @param allRegistered - All registered models regardless of auth (pi's getAll())
131
132
  * @param config - Model exposure configuration
132
133
  *
133
134
  * Call this at startup and whenever config or the model registry changes.
134
135
  */
135
- function computeModelExposure(available, allRegistered, config) {
136
- const exposed = filterExposedModels(available, allRegistered, config);
136
+ function computeModelExposure(available, config) {
137
+ const exposed = filterExposedModels(available, config);
137
138
  const prefixError = validatePrefixUniqueness(exposed, config.providerPrefixes, config.publicModelIdMode);
138
139
  if (prefixError !== void 0) return {
139
140
  ok: false,
package/dist/index.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env bun
2
- import { l as isRecord, o as loadConfigFromFile, r as getConfigPath } from "./schema-IE4WK0Dj.mjs";
2
+ import { l as isRecord, o as loadConfigFromFile, r as getConfigPath } from "./schema-quaXHZsz.mjs";
3
3
  import { computeModelExposure, resolveExposedModel } from "./exposure.mjs";
4
4
  import * as z from "zod";
5
- import { AuthStorage, ModelRegistry } from "@mariozechner/pi-coding-agent";
5
+ import { AuthStorage, ModelRegistry, SettingsManager } from "@mariozechner/pi-coding-agent";
6
6
  import { randomBytes } from "node:crypto";
7
7
  import { Type } from "@sinclair/typebox";
8
8
  import { completeSimple, streamSimple } from "@mariozechner/pi-ai";
@@ -38,7 +38,6 @@ function loadConfig(cli = {}) {
38
38
  upstreamTimeoutMs,
39
39
  publicModelIdMode: file.publicModelIdMode,
40
40
  modelExposureMode: file.modelExposureMode,
41
- scopedProviders: file.scopedProviders,
42
41
  customModels: file.customModels,
43
42
  providerPrefixes: file.providerPrefixes
44
43
  };
@@ -47,19 +46,25 @@ function loadConfig(cli = {}) {
47
46
  //#region src/pi/registry.ts
48
47
  let registry;
49
48
  let authStorage;
49
+ let settingsManager;
50
50
  /**
51
- * Initialize the registry. Call once at startup.
51
+ * Initialize the registry and settings. Call once at startup.
52
52
  * Returns the load error if models.json failed to parse, or undefined on success.
53
53
  */
54
54
  function initRegistry() {
55
55
  authStorage = AuthStorage.create();
56
56
  registry = new ModelRegistry(authStorage);
57
+ settingsManager = SettingsManager.create();
57
58
  return registry.getError();
58
59
  }
59
60
  function getRegistry() {
60
61
  if (registry === void 0) throw new Error("ModelRegistry not initialized. Call initRegistry() first.");
61
62
  return registry;
62
63
  }
64
+ function getSettingsManager() {
65
+ if (settingsManager === void 0) throw new Error("SettingsManager not initialized. Call initRegistry() first.");
66
+ return settingsManager;
67
+ }
63
68
  /**
64
69
  * Get all models available (have auth configured).
65
70
  */
@@ -67,10 +72,16 @@ function getAvailableModels() {
67
72
  return getRegistry().getAvailable();
68
73
  }
69
74
  /**
70
- * Get all registered models (regardless of auth state).
75
+ * Get the `enabledModels` patterns from pi's global settings.
76
+ *
77
+ * These are the canonical model IDs (e.g. "anthropic/claude-sonnet-4-6")
78
+ * persisted by the `/scoped-models` TUI when the user presses Ctrl+S.
79
+ *
80
+ * Returns undefined when no filter is configured (all models enabled).
71
81
  */
72
- function getAllModels() {
73
- return getRegistry().getAll();
82
+ function getEnabledModels() {
83
+ getSettingsManager().reload();
84
+ return getSettingsManager().getEnabledModels();
74
85
  }
75
86
  //#endregion
76
87
  //#region src/server/errors.ts
@@ -1307,14 +1318,14 @@ function fileConfigReader() {
1307
1318
  return {
1308
1319
  publicModelIdMode: file.publicModelIdMode,
1309
1320
  modelExposureMode: file.modelExposureMode,
1310
- scopedProviders: file.scopedProviders,
1321
+ enabledModels: getEnabledModels(),
1311
1322
  customModels: file.customModels,
1312
1323
  providerPrefixes: file.providerPrefixes
1313
1324
  };
1314
1325
  }
1315
1326
  function createRoutes(config, configReader = fileConfigReader) {
1316
1327
  function getExposure() {
1317
- const outcome = computeModelExposure(getAvailableModels(), getAllModels(), configReader());
1328
+ const outcome = computeModelExposure(getAvailableModels(), configReader());
1318
1329
  if (!outcome.ok) throw new Error(`Model exposure configuration error: ${outcome.message}`);
1319
1330
  return outcome;
1320
1331
  }
@@ -12,10 +12,12 @@ import * as z from "zod";
12
12
  type PublicModelIdMode = "collision-prefixed" | "universal" | "always-prefixed";
13
13
  /**
14
14
  * Which models are exposed on the public HTTP API.
15
+ * All modes only expose auth-configured models (pi's getAvailable()).
15
16
  *
16
- * - "all": every available model
17
- * - "scoped": all available models from selected providers only
18
- * - "custom": explicit allowlist of canonical model IDs
17
+ * - "all": every available model, no filter
18
+ * - "scoped": delegates to pi's global `enabledModels` setting (from `/scoped-models` Ctrl+S)
19
+ * - "custom": same per-model filtering as "scoped" but independently managed
20
+ * via the proxy's own `customModels` config
19
21
  */
20
22
  type ModelExposureMode = "all" | "scoped" | "custom";
21
23
  interface ProxyConfig {
@@ -35,10 +37,8 @@ interface ProxyConfig {
35
37
  readonly lifetime: "detached" | "session";
36
38
  /** How public model IDs are generated. Default: "collision-prefixed" */
37
39
  readonly publicModelIdMode: PublicModelIdMode;
38
- /** Which models are exposed. Default: "all" */
40
+ /** Which models are exposed. Default: "scoped" */
39
41
  readonly modelExposureMode: ModelExposureMode;
40
- /** Provider keys to expose when modelExposureMode is "scoped". */
41
- readonly scopedProviders: readonly string[];
42
42
  /** Canonical model IDs to expose when modelExposureMode is "custom". */
43
43
  readonly customModels: readonly string[];
44
44
  /** Provider key -> custom public prefix label. Default prefix = provider key. */
@@ -36,7 +36,6 @@ const DEFAULT_CONFIG = {
36
36
  lifetime: "detached",
37
37
  publicModelIdMode: "collision-prefixed",
38
38
  modelExposureMode: "scoped",
39
- scopedProviders: [],
40
39
  customModels: [],
41
40
  providerPrefixes: {},
42
41
  zed: {
@@ -93,7 +92,6 @@ function normalizeConfig(raw) {
93
92
  lifetime: v["lifetime"] === "session" ? "session" : "detached",
94
93
  publicModelIdMode: typeof rawPublicIdMode === "string" && isPublicModelIdMode(rawPublicIdMode) ? rawPublicIdMode : DEFAULT_CONFIG.publicModelIdMode,
95
94
  modelExposureMode: typeof rawExposureMode === "string" && isModelExposureMode(rawExposureMode) ? rawExposureMode : DEFAULT_CONFIG.modelExposureMode,
96
- scopedProviders: normalizeStringArray(v["scopedProviders"]),
97
95
  customModels: normalizeStringArray(v["customModels"]),
98
96
  providerPrefixes: normalizeStringRecord(v["providerPrefixes"]),
99
97
  zed: parseZedSyncConfig(v["zed"])
@@ -31,6 +31,7 @@ import {
31
31
  type ExtensionContext,
32
32
  getSettingsListTheme,
33
33
  ModelRegistry,
34
+ SettingsManager,
34
35
  } from "@mariozechner/pi-coding-agent";
35
36
  import {
36
37
  type Component,
@@ -68,6 +69,17 @@ interface RuntimeStatus {
68
69
  models: number;
69
70
  }
70
71
 
72
+ interface ProbeBody {
73
+ data: unknown[];
74
+ }
75
+
76
+ function isProbeBody(value: unknown): value is ProbeBody {
77
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
78
+ if (!("data" in value)) return false;
79
+ const v: { data: unknown } = value;
80
+ return Array.isArray(v.data);
81
+ }
82
+
71
83
  // ---------------------------------------------------------------------------
72
84
  // Extension
73
85
  // ---------------------------------------------------------------------------
@@ -83,22 +95,23 @@ export default function proxyExtension(pi: ExtensionAPI): void {
83
95
 
84
96
  const cachedAuth = AuthStorage.create();
85
97
  const cachedRegistry = new ModelRegistry(cachedAuth);
98
+ const settingsManager = SettingsManager.create();
86
99
 
87
100
  function getAvailableModels(): Model<Api>[] {
88
101
  cachedRegistry.refresh();
89
102
  return cachedRegistry.getAvailable();
90
103
  }
91
104
 
92
- function getAllRegisteredModels(): Model<Api>[] {
93
- cachedRegistry.refresh();
94
- return cachedRegistry.getAll();
105
+ function getEnabledModels(): readonly string[] | undefined {
106
+ settingsManager.reload();
107
+ return settingsManager.getEnabledModels();
95
108
  }
96
109
 
97
110
  function buildExposureConfig(): ModelExposureConfig {
98
111
  return {
99
112
  publicModelIdMode: config.publicModelIdMode,
100
113
  modelExposureMode: config.modelExposureMode,
101
- scopedProviders: config.scopedProviders,
114
+ enabledModels: getEnabledModels(),
102
115
  customModels: config.customModels,
103
116
  providerPrefixes: config.providerPrefixes,
104
117
  };
@@ -274,8 +287,12 @@ export default function proxyExtension(pi: ExtensionAPI): void {
274
287
  headers,
275
288
  });
276
289
  if (res.ok) {
277
- const body = (await res.json()) as { data?: unknown[] };
278
- return { reachable: true, models: body.data?.length ?? 0 };
290
+ const body: unknown = await res.json();
291
+ let modelCount = 0;
292
+ if (isProbeBody(body)) {
293
+ modelCount = body.data.length;
294
+ }
295
+ return { reachable: true, models: modelCount };
279
296
  }
280
297
  } catch {
281
298
  // not reachable
@@ -464,8 +481,11 @@ export default function proxyExtension(pi: ExtensionAPI): void {
464
481
  `exposure: ${config.modelExposureMode}`,
465
482
  ];
466
483
 
467
- if (config.modelExposureMode === "scoped" && config.scopedProviders.length > 0) {
468
- exposureLines.push(`providers: ${config.scopedProviders.join(", ")}`);
484
+ if (config.modelExposureMode === "scoped") {
485
+ const enabledModels = getEnabledModels();
486
+ if (enabledModels !== undefined && enabledModels.length > 0) {
487
+ exposureLines.push(`enabled: ${String(enabledModels.length)} pi model(s)`);
488
+ }
469
489
  }
470
490
  if (config.modelExposureMode === "custom" && config.customModels.length > 0) {
471
491
  exposureLines.push(`models: ${String(config.customModels.length)} custom`);
@@ -479,8 +499,7 @@ export default function proxyExtension(pi: ExtensionAPI): void {
479
499
 
480
500
  // Public ID preview (first 5 exposed models)
481
501
  const models = getAvailableModels();
482
- const allModels = getAllRegisteredModels();
483
- const outcome = computeModelExposure(models, allModels, buildExposureConfig());
502
+ const outcome = computeModelExposure(models, buildExposureConfig());
484
503
  if (outcome.ok && outcome.models.length > 0) {
485
504
  const preview = outcome.models.slice(0, 5).map((m) => m.publicId);
486
505
  const suffix =
@@ -500,8 +519,7 @@ export default function proxyExtension(pi: ExtensionAPI): void {
500
519
  function showModels(ctx: ExtensionContext): void {
501
520
  config = loadConfigFromFile();
502
521
  const models = getAvailableModels();
503
- const allModels = getAllRegisteredModels();
504
- const outcome = computeModelExposure(models, allModels, buildExposureConfig());
522
+ const outcome = computeModelExposure(models, buildExposureConfig());
505
523
 
506
524
  if (!outcome.ok) {
507
525
  ctx.ui.notify(`Model exposure error: ${outcome.message}`, "warning");
@@ -548,8 +566,6 @@ export default function proxyExtension(pi: ExtensionAPI): void {
548
566
  const models = getAvailableModels();
549
567
  const issues: string[] = [];
550
568
 
551
- const allModels = getAllRegisteredModels();
552
-
553
569
  // Check available models
554
570
  if (models.length === 0) {
555
571
  issues.push("No models have auth configured. The proxy will expose 0 models.");
@@ -569,7 +585,7 @@ export default function proxyExtension(pi: ExtensionAPI): void {
569
585
  }
570
586
 
571
587
  // Run the full exposure computation to catch ID/prefix errors
572
- const outcome = computeModelExposure(models, allModels, buildExposureConfig());
588
+ const outcome = computeModelExposure(models, buildExposureConfig());
573
589
  if (!outcome.ok) {
574
590
  issues.push(outcome.message);
575
591
  }
@@ -592,8 +608,7 @@ export default function proxyExtension(pi: ExtensionAPI): void {
592
608
  */
593
609
  function runZedSync(dryRun: boolean): { ok: boolean; message: string } {
594
610
  const available = getAvailableModels();
595
- const allModels = getAllRegisteredModels();
596
- const outcome = computeModelExposure(available, allModels, buildExposureConfig());
611
+ const outcome = computeModelExposure(available, buildExposureConfig());
597
612
  if (!outcome.ok) {
598
613
  return { ok: false, message: `Model exposure error: ${outcome.message}` };
599
614
  }
@@ -709,12 +724,40 @@ export default function proxyExtension(pi: ExtensionAPI): void {
709
724
  // Accesses private fields via bracket notation for provider jumping.
710
725
  // Pinned to pi-tui behavior as of @mariozechner/pi-coding-agent ^0.62.0.
711
726
  // Remove when SettingsList exposes a jumpTo/setSelectedIndex method.
727
+
728
+ // Isolated unsafe accessor for SettingsList private fields.
729
+ // Consolidated here so jumpProvider itself is fully type-safe.
730
+ function listGet(key: string): unknown {
731
+ return Reflect.get(list, key);
732
+ }
733
+ function listSet(key: string, value: unknown): void {
734
+ Reflect.set(list, key, value);
735
+ }
736
+
737
+ function getSettingsListItems(): SettingItem[] {
738
+ const rawSearch = listGet("searchEnabled");
739
+ const raw =
740
+ typeof rawSearch === "boolean" && rawSearch ? listGet("filteredItems") : listGet("items");
741
+ if (!Array.isArray(raw)) return [];
742
+ const result: SettingItem[] = [];
743
+ for (const item of raw) {
744
+ if (isSettingItem(item)) result.push(item);
745
+ }
746
+ return result;
747
+ }
748
+
749
+ function isSettingItem(value: unknown): value is SettingItem {
750
+ if (value === null || typeof value !== "object") return false;
751
+ if (!("id" in value)) return false;
752
+ const v: { id: unknown } = value;
753
+ return typeof v.id === "string";
754
+ }
755
+
712
756
  function jumpProvider(direction: "prev" | "next"): void {
713
- const sl = list as unknown as Record<string, unknown>;
714
- const idx = sl["selectedIndex"] as number;
715
- const display = (
716
- (sl["searchEnabled"] as boolean) ? sl["filteredItems"] : sl["items"]
717
- ) as SettingItem[];
757
+ const rawIdx = listGet("selectedIndex");
758
+ if (typeof rawIdx !== "number") return;
759
+ const idx = rawIdx;
760
+ const display = getSettingsListItems();
718
761
  if (display.length === 0) return;
719
762
 
720
763
  const current = display[idx];
@@ -750,7 +793,7 @@ export default function proxyExtension(pi: ExtensionAPI): void {
750
793
  }
751
794
  }
752
795
  }
753
- sl["selectedIndex"] = target;
796
+ listSet("selectedIndex", target);
754
797
  }
755
798
 
756
799
  return {
@@ -878,7 +921,7 @@ export default function proxyExtension(pi: ExtensionAPI): void {
878
921
  label: "Select models",
879
922
  description: customModelsDescription(),
880
923
  currentValue: customModelsDisplay(),
881
- submenu: config.modelExposureMode === "custom" ? buildModelSelectorSubmenu : undefined,
924
+ ...(config.modelExposureMode === "custom" ? { submenu: buildModelSelectorSubmenu } : {}),
882
925
  },
883
926
  // --- Zed sync ---
884
927
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@victor-software-house/pi-openai-proxy",
3
- "version": "4.5.0",
3
+ "version": "4.5.1",
4
4
  "description": "OpenAI-compatible HTTP proxy for pi's multi-provider model registry",
5
5
  "license": "MIT",
6
6
  "author": "Victor Software House",
@@ -62,7 +62,7 @@
62
62
  "typecheck": "tsc --noEmit",
63
63
  "lint": "bun run lint:biome && bun run lint:oxlint",
64
64
  "lint:biome": "biome check .",
65
- "lint:oxlint": "oxlint --import-plugin --type-aware --tsconfig=./tsconfig.json src/",
65
+ "lint:oxlint": "oxlint --import-plugin --type-aware --tsconfig=./tsconfig.json .",
66
66
  "lint:fix": "biome check --write .",
67
67
  "format": "biome format --write .",
68
68
  "test": "bun test",