@victor-software-house/pi-openai-proxy 4.4.1 → 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 +2 -2
- package/dist/config.mjs +1 -1
- package/dist/exposure.d.mts +9 -4
- package/dist/exposure.mjs +9 -8
- package/dist/index.mjs +21 -10
- package/dist/{schema-BTAG6Urs.d.mts → schema-CrOJW-mE.d.mts} +16 -14
- package/dist/{schema-x6mps-hM.mjs → schema-quaXHZsz.mjs} +17 -4
- package/extensions/proxy.ts +124 -41
- package/package.json +2 -2
package/dist/config.d.mts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
|
|
2
|
-
import { a as
|
|
3
|
-
export { DEFAULT_CONFIG, ModelExposureMode, ProxyConfig, PublicModelIdMode, configToEnv, getConfigPath, isModelExposureMode, isPublicModelIdMode, loadConfigFromFile, normalizeConfig, saveConfigToFile };
|
|
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
|
+
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-
|
|
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 };
|
package/dist/exposure.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { i as PublicModelIdMode, n as ModelExposureMode } from "./schema-
|
|
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
|
-
|
|
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>[],
|
|
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,
|
|
3
|
+
function filterExposedModels(available, config) {
|
|
4
4
|
switch (config.modelExposureMode) {
|
|
5
|
+
case "all": return [...available];
|
|
5
6
|
case "scoped": {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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,
|
|
136
|
-
const exposed = filterExposedModels(available,
|
|
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,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
import { l as isRecord, o as loadConfigFromFile, r as getConfigPath } from "./schema-
|
|
2
|
+
import { l as isRecord, o as loadConfigFromFile, r as getConfigPath } from "./schema-quaXHZsz.mjs";
|
|
3
3
|
import { computeModelExposure, resolveExposedModel } from "./exposure.mjs";
|
|
4
|
-
import { AuthStorage, ModelRegistry } from "@mariozechner/pi-coding-agent";
|
|
5
|
-
import { randomBytes } from "node:crypto";
|
|
6
4
|
import * as z from "zod";
|
|
5
|
+
import { AuthStorage, ModelRegistry, SettingsManager } from "@mariozechner/pi-coding-agent";
|
|
6
|
+
import { randomBytes } from "node:crypto";
|
|
7
7
|
import { Type } from "@sinclair/typebox";
|
|
8
8
|
import { completeSimple, streamSimple } from "@mariozechner/pi-ai";
|
|
9
9
|
import { Hono } from "hono";
|
|
@@ -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
|
|
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
|
|
73
|
-
|
|
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
|
-
|
|
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(),
|
|
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
|
}
|
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
|
|
2
4
|
//#region src/config/schema.d.ts
|
|
3
|
-
/**
|
|
4
|
-
* Proxy configuration schema -- single source of truth.
|
|
5
|
-
*
|
|
6
|
-
* Used by both the proxy server and the pi extension.
|
|
7
|
-
* The server reads the JSON config file as defaults, with env vars and CLI args as overrides.
|
|
8
|
-
* The pi extension reads and writes the JSON config file via the /proxy config panel.
|
|
9
|
-
*/
|
|
10
5
|
/**
|
|
11
6
|
* How public model IDs are generated from canonical provider/model-id pairs.
|
|
12
7
|
*
|
|
@@ -17,10 +12,12 @@
|
|
|
17
12
|
type PublicModelIdMode = "collision-prefixed" | "universal" | "always-prefixed";
|
|
18
13
|
/**
|
|
19
14
|
* Which models are exposed on the public HTTP API.
|
|
15
|
+
* All modes only expose auth-configured models (pi's getAvailable()).
|
|
20
16
|
*
|
|
21
|
-
* - "all": every available model
|
|
22
|
-
* - "scoped":
|
|
23
|
-
* - "custom":
|
|
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
|
|
24
21
|
*/
|
|
25
22
|
type ModelExposureMode = "all" | "scoped" | "custom";
|
|
26
23
|
interface ProxyConfig {
|
|
@@ -40,15 +37,20 @@ interface ProxyConfig {
|
|
|
40
37
|
readonly lifetime: "detached" | "session";
|
|
41
38
|
/** How public model IDs are generated. Default: "collision-prefixed" */
|
|
42
39
|
readonly publicModelIdMode: PublicModelIdMode;
|
|
43
|
-
/** Which models are exposed. Default: "
|
|
40
|
+
/** Which models are exposed. Default: "scoped" */
|
|
44
41
|
readonly modelExposureMode: ModelExposureMode;
|
|
45
|
-
/** Provider keys to expose when modelExposureMode is "scoped". */
|
|
46
|
-
readonly scopedProviders: readonly string[];
|
|
47
42
|
/** Canonical model IDs to expose when modelExposureMode is "custom". */
|
|
48
43
|
readonly customModels: readonly string[];
|
|
49
44
|
/** Provider key -> custom public prefix label. Default prefix = provider key. */
|
|
50
45
|
readonly providerPrefixes: Readonly<Record<string, string>>;
|
|
46
|
+
/** Zed editor sync settings. */
|
|
47
|
+
readonly zed: ZedSyncConfig;
|
|
51
48
|
}
|
|
49
|
+
declare const ZedSyncConfigSchema: z.ZodObject<{
|
|
50
|
+
providerName: z.ZodDefault<z.ZodString>;
|
|
51
|
+
autoSync: z.ZodDefault<z.ZodBoolean>;
|
|
52
|
+
}, z.core.$strip>;
|
|
53
|
+
type ZedSyncConfig = z.infer<typeof ZedSyncConfigSchema>;
|
|
52
54
|
declare const DEFAULT_CONFIG: Readonly<ProxyConfig>;
|
|
53
55
|
declare function isPublicModelIdMode(value: string): value is PublicModelIdMode;
|
|
54
56
|
declare function isModelExposureMode(value: string): value is ModelExposureMode;
|
|
@@ -58,4 +60,4 @@ declare function loadConfigFromFile(): ProxyConfig;
|
|
|
58
60
|
declare function saveConfigToFile(config: ProxyConfig): void;
|
|
59
61
|
declare function configToEnv(config: ProxyConfig): Record<string, string>;
|
|
60
62
|
//#endregion
|
|
61
|
-
export {
|
|
63
|
+
export { ZedSyncConfig as a, isModelExposureMode as c, normalizeConfig as d, saveConfigToFile as f, PublicModelIdMode as i, isPublicModelIdMode as l, ModelExposureMode as n, configToEnv as o, ProxyConfig as r, getConfigPath as s, DEFAULT_CONFIG as t, loadConfigFromFile as u };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { dirname, resolve } from "node:path";
|
|
4
|
+
import * as z from "zod";
|
|
4
5
|
//#region src/utils/guards.ts
|
|
5
6
|
/**
|
|
6
7
|
* Shared type guard utilities.
|
|
@@ -21,6 +22,10 @@ function isRecord(value) {
|
|
|
21
22
|
* The server reads the JSON config file as defaults, with env vars and CLI args as overrides.
|
|
22
23
|
* The pi extension reads and writes the JSON config file via the /proxy config panel.
|
|
23
24
|
*/
|
|
25
|
+
const ZedSyncConfigSchema = z.object({
|
|
26
|
+
providerName: z.string().trim().min(1, { error: "Provider name must not be empty" }).default("Pi Proxy"),
|
|
27
|
+
autoSync: z.boolean({ error: "autoSync must be a boolean" }).default(false)
|
|
28
|
+
});
|
|
24
29
|
const DEFAULT_CONFIG = {
|
|
25
30
|
host: "127.0.0.1",
|
|
26
31
|
port: 4141,
|
|
@@ -31,9 +36,12 @@ const DEFAULT_CONFIG = {
|
|
|
31
36
|
lifetime: "detached",
|
|
32
37
|
publicModelIdMode: "collision-prefixed",
|
|
33
38
|
modelExposureMode: "scoped",
|
|
34
|
-
scopedProviders: [],
|
|
35
39
|
customModels: [],
|
|
36
|
-
providerPrefixes: {}
|
|
40
|
+
providerPrefixes: {},
|
|
41
|
+
zed: {
|
|
42
|
+
providerName: "Pi Proxy",
|
|
43
|
+
autoSync: false
|
|
44
|
+
}
|
|
37
45
|
};
|
|
38
46
|
function clampInt(raw, min, max, fallback) {
|
|
39
47
|
if (typeof raw !== "number" || !Number.isFinite(raw)) return fallback;
|
|
@@ -84,11 +92,16 @@ function normalizeConfig(raw) {
|
|
|
84
92
|
lifetime: v["lifetime"] === "session" ? "session" : "detached",
|
|
85
93
|
publicModelIdMode: typeof rawPublicIdMode === "string" && isPublicModelIdMode(rawPublicIdMode) ? rawPublicIdMode : DEFAULT_CONFIG.publicModelIdMode,
|
|
86
94
|
modelExposureMode: typeof rawExposureMode === "string" && isModelExposureMode(rawExposureMode) ? rawExposureMode : DEFAULT_CONFIG.modelExposureMode,
|
|
87
|
-
scopedProviders: normalizeStringArray(v["scopedProviders"]),
|
|
88
95
|
customModels: normalizeStringArray(v["customModels"]),
|
|
89
|
-
providerPrefixes: normalizeStringRecord(v["providerPrefixes"])
|
|
96
|
+
providerPrefixes: normalizeStringRecord(v["providerPrefixes"]),
|
|
97
|
+
zed: parseZedSyncConfig(v["zed"])
|
|
90
98
|
};
|
|
91
99
|
}
|
|
100
|
+
function parseZedSyncConfig(raw) {
|
|
101
|
+
const result = ZedSyncConfigSchema.safeParse(isRecord(raw) ? raw : {});
|
|
102
|
+
if (result.success) return result.data;
|
|
103
|
+
return ZedSyncConfigSchema.parse({});
|
|
104
|
+
}
|
|
92
105
|
function getConfigPath() {
|
|
93
106
|
return resolve(process.env.PI_CODING_AGENT_DIR ?? resolve(process.env.HOME ?? "~", ".pi", "agent"), "proxy-config.json");
|
|
94
107
|
}
|
package/extensions/proxy.ts
CHANGED
|
@@ -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
|
|
93
|
-
|
|
94
|
-
return
|
|
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
|
-
|
|
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 =
|
|
278
|
-
|
|
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"
|
|
468
|
-
|
|
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
|
|
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
|
|
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,
|
|
588
|
+
const outcome = computeModelExposure(models, buildExposureConfig());
|
|
573
589
|
if (!outcome.ok) {
|
|
574
590
|
issues.push(outcome.message);
|
|
575
591
|
}
|
|
@@ -587,27 +603,22 @@ export default function proxyExtension(pi: ExtensionAPI): void {
|
|
|
587
603
|
|
|
588
604
|
// --- Zed sync ---
|
|
589
605
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
// Compute exposed models (same as /proxy models)
|
|
606
|
+
/**
|
|
607
|
+
* Run Zed sync and return the result. Shared by the command and auto-sync.
|
|
608
|
+
*/
|
|
609
|
+
function runZedSync(dryRun: boolean): { ok: boolean; message: string } {
|
|
596
610
|
const available = getAvailableModels();
|
|
597
|
-
const
|
|
598
|
-
const outcome = computeModelExposure(available, allModels, buildExposureConfig());
|
|
611
|
+
const outcome = computeModelExposure(available, buildExposureConfig());
|
|
599
612
|
if (!outcome.ok) {
|
|
600
|
-
|
|
601
|
-
return;
|
|
613
|
+
return { ok: false, message: `Model exposure error: ${outcome.message}` };
|
|
602
614
|
}
|
|
603
615
|
|
|
604
616
|
if (outcome.models.length === 0) {
|
|
605
|
-
|
|
606
|
-
return;
|
|
617
|
+
return { ok: false, message: "No models exposed. Nothing to sync." };
|
|
607
618
|
}
|
|
608
619
|
|
|
609
620
|
const syncOptions: ZedSyncOptions = {
|
|
610
|
-
providerName:
|
|
621
|
+
providerName: config.zed.providerName,
|
|
611
622
|
apiUrl: `http://${config.host}:${String(config.port)}/v1`,
|
|
612
623
|
dryRun,
|
|
613
624
|
};
|
|
@@ -615,14 +626,30 @@ export default function proxyExtension(pi: ExtensionAPI): void {
|
|
|
615
626
|
const result = syncToZed(outcome.models, syncOptions);
|
|
616
627
|
|
|
617
628
|
if (!result.ok) {
|
|
618
|
-
|
|
619
|
-
return;
|
|
629
|
+
return { ok: false, message: result.error ?? "Zed sync failed" };
|
|
620
630
|
}
|
|
621
631
|
|
|
622
|
-
const
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
632
|
+
const prefix = dryRun ? "[dry-run] " : "";
|
|
633
|
+
return { ok: true, message: `${prefix}${result.summary} (${result.configPath})` };
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
function handleZedSync(ctx: ExtensionContext, args: string): void {
|
|
637
|
+
config = loadConfigFromFile();
|
|
638
|
+
const dryRun = args.includes("--dry-run");
|
|
639
|
+
const result = runZedSync(dryRun);
|
|
640
|
+
ctx.ui.notify(`Zed sync: ${result.message}`, result.ok ? "info" : "error");
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* Trigger auto-sync to Zed if enabled. Called after config save.
|
|
645
|
+
*/
|
|
646
|
+
function maybeAutoSyncZed(ctx: ExtensionContext): void {
|
|
647
|
+
if (!config.zed.autoSync) return;
|
|
648
|
+
const result = runZedSync(false);
|
|
649
|
+
if (result.ok) {
|
|
650
|
+
ctx.ui.notify(`Zed auto-sync: ${result.message}`, "info");
|
|
651
|
+
}
|
|
652
|
+
// Silent on failure during auto-sync -- don't spam the user
|
|
626
653
|
}
|
|
627
654
|
|
|
628
655
|
async function waitForReady(timeoutMs: number): Promise<RuntimeStatus> {
|
|
@@ -697,12 +724,40 @@ export default function proxyExtension(pi: ExtensionAPI): void {
|
|
|
697
724
|
// Accesses private fields via bracket notation for provider jumping.
|
|
698
725
|
// Pinned to pi-tui behavior as of @mariozechner/pi-coding-agent ^0.62.0.
|
|
699
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
|
+
|
|
700
756
|
function jumpProvider(direction: "prev" | "next"): void {
|
|
701
|
-
const
|
|
702
|
-
|
|
703
|
-
const
|
|
704
|
-
|
|
705
|
-
) as SettingItem[];
|
|
757
|
+
const rawIdx = listGet("selectedIndex");
|
|
758
|
+
if (typeof rawIdx !== "number") return;
|
|
759
|
+
const idx = rawIdx;
|
|
760
|
+
const display = getSettingsListItems();
|
|
706
761
|
if (display.length === 0) return;
|
|
707
762
|
|
|
708
763
|
const current = display[idx];
|
|
@@ -738,7 +793,7 @@ export default function proxyExtension(pi: ExtensionAPI): void {
|
|
|
738
793
|
}
|
|
739
794
|
}
|
|
740
795
|
}
|
|
741
|
-
|
|
796
|
+
listSet("selectedIndex", target);
|
|
742
797
|
}
|
|
743
798
|
|
|
744
799
|
return {
|
|
@@ -866,7 +921,22 @@ export default function proxyExtension(pi: ExtensionAPI): void {
|
|
|
866
921
|
label: "Select models",
|
|
867
922
|
description: customModelsDescription(),
|
|
868
923
|
currentValue: customModelsDisplay(),
|
|
869
|
-
|
|
924
|
+
...(config.modelExposureMode === "custom" ? { submenu: buildModelSelectorSubmenu } : {}),
|
|
925
|
+
},
|
|
926
|
+
// --- Zed sync ---
|
|
927
|
+
{
|
|
928
|
+
id: "zed.autoSync",
|
|
929
|
+
label: "Zed auto-sync",
|
|
930
|
+
description: "Sync models to Zed settings.json when config changes",
|
|
931
|
+
currentValue: config.zed.autoSync ? "on" : "off",
|
|
932
|
+
values: ["off", "on"],
|
|
933
|
+
},
|
|
934
|
+
{
|
|
935
|
+
id: "zed.providerName",
|
|
936
|
+
label: "Zed provider name",
|
|
937
|
+
description: "Provider label in Zed's openai_compatible section",
|
|
938
|
+
currentValue: config.zed.providerName,
|
|
939
|
+
values: ["Pi Proxy"],
|
|
870
940
|
},
|
|
871
941
|
];
|
|
872
942
|
}
|
|
@@ -958,6 +1028,14 @@ export default function proxyExtension(pi: ExtensionAPI): void {
|
|
|
958
1028
|
case "customModels":
|
|
959
1029
|
// Handled by submenu -- no cycling
|
|
960
1030
|
break;
|
|
1031
|
+
case "zed.autoSync":
|
|
1032
|
+
config = { ...config, zed: { ...config.zed, autoSync: value === "on" } };
|
|
1033
|
+
break;
|
|
1034
|
+
case "zed.providerName":
|
|
1035
|
+
if (value.length > 0) {
|
|
1036
|
+
config = { ...config, zed: { ...config.zed, providerName: value } };
|
|
1037
|
+
}
|
|
1038
|
+
break;
|
|
961
1039
|
}
|
|
962
1040
|
saveConfigToFile(config);
|
|
963
1041
|
config = loadConfigFromFile();
|
|
@@ -988,6 +1066,10 @@ export default function proxyExtension(pi: ExtensionAPI): void {
|
|
|
988
1066
|
return config.modelExposureMode;
|
|
989
1067
|
case "customModels":
|
|
990
1068
|
return customModelsDisplay();
|
|
1069
|
+
case "zed.autoSync":
|
|
1070
|
+
return config.zed.autoSync ? "on" : "off";
|
|
1071
|
+
case "zed.providerName":
|
|
1072
|
+
return config.zed.providerName;
|
|
991
1073
|
default:
|
|
992
1074
|
return "";
|
|
993
1075
|
}
|
|
@@ -1023,6 +1105,7 @@ export default function proxyExtension(pi: ExtensionAPI): void {
|
|
|
1023
1105
|
settingsList.updateValue("customModels", customModelsDisplay());
|
|
1024
1106
|
}
|
|
1025
1107
|
|
|
1108
|
+
maybeAutoSyncZed(ctx);
|
|
1026
1109
|
tui.requestRender();
|
|
1027
1110
|
},
|
|
1028
1111
|
() => done(undefined),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@victor-software-house/pi-openai-proxy",
|
|
3
|
-
"version": "4.
|
|
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
|
|
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",
|