@victor-software-house/pi-openai-proxy 4.9.2 → 4.9.4

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/index.mjs CHANGED
@@ -4,7 +4,6 @@ import { computeModelExposure, resolveExposedModel } from "./exposure.mjs";
4
4
  import * as z from "zod";
5
5
  import { AuthStorage, ModelRegistry, SettingsManager } from "@mariozechner/pi-coding-agent";
6
6
  import { randomBytes } from "node:crypto";
7
- import { Type } from "@sinclair/typebox";
8
7
  import { completeSimple, streamSimple } from "@mariozechner/pi-ai";
9
8
  import { Hono } from "hono";
10
9
  import { stream } from "hono/streaming";
@@ -53,8 +52,8 @@ let settingsManager;
53
52
  */
54
53
  function initRegistry() {
55
54
  authStorage = AuthStorage.create();
56
- registry = new ModelRegistry(authStorage);
57
- settingsManager = SettingsManager.create();
55
+ registry = ModelRegistry.create(authStorage);
56
+ settingsManager = SettingsManager.create(process.cwd());
58
57
  return registry.getError();
59
58
  }
60
59
  function getRegistry() {
@@ -72,6 +71,16 @@ function getAvailableModels() {
72
71
  return getRegistry().getAvailable();
73
72
  }
74
73
  /**
74
+ * Resolve request auth for a specific model at request time.
75
+ *
76
+ * This uses Pi's current request-auth contract, which may return both an API
77
+ * key and model-specific headers. That covers API-key providers, OAuth-backed
78
+ * providers, authHeader handling, and dynamic models.json header resolution.
79
+ */
80
+ async function getRequestAuth(model) {
81
+ return getRegistry().getApiKeyAndHeaders(model);
82
+ }
83
+ /**
75
84
  * Get the `enabledModels` patterns from pi's global settings.
76
85
  *
77
86
  * These are the canonical model IDs (e.g. "anthropic/claude-sonnet-4-6")
@@ -79,9 +88,10 @@ function getAvailableModels() {
79
88
  *
80
89
  * Returns undefined when no filter is configured (all models enabled).
81
90
  */
82
- function getEnabledModels() {
83
- getSettingsManager().reload();
84
- return getSettingsManager().getEnabledModels();
91
+ async function getEnabledModels() {
92
+ const manager = getSettingsManager();
93
+ await manager.reload();
94
+ return manager.getEnabledModels();
85
95
  }
86
96
  //#endregion
87
97
  //#region src/server/errors.ts
@@ -751,6 +761,328 @@ async function* streamToSSE(events, requestId, model, includeUsage) {
751
761
  yield encodeDone();
752
762
  }
753
763
  //#endregion
764
+ //#region node_modules/typebox/build/system/memory/metrics.mjs
765
+ /** TypeBox instantiation metrics */
766
+ const Metrics = {
767
+ assign: 0,
768
+ create: 0,
769
+ clone: 0,
770
+ discard: 0,
771
+ update: 0
772
+ };
773
+ //#endregion
774
+ //#region node_modules/typebox/build/guard/guard.mjs
775
+ /** Returns true if this value is an array */
776
+ function IsArray(value) {
777
+ return Array.isArray(value);
778
+ }
779
+ /** Returns true if this value is bigint */
780
+ function IsBigInt(value) {
781
+ return IsEqual(typeof value, "bigint");
782
+ }
783
+ /** Returns true if this value is a boolean */
784
+ function IsBoolean(value) {
785
+ return IsEqual(typeof value, "boolean");
786
+ }
787
+ /** Returns true if this value is null */
788
+ function IsNull(value) {
789
+ return IsEqual(value, null);
790
+ }
791
+ /** Returns true if this value is number */
792
+ function IsNumber(value) {
793
+ return Number.isFinite(value);
794
+ }
795
+ /** Returns true if this value is an object */
796
+ function IsObject(value) {
797
+ return IsEqual(typeof value, "object") && !IsNull(value);
798
+ }
799
+ /** Returns true if this value is string */
800
+ function IsString(value) {
801
+ return IsEqual(typeof value, "string");
802
+ }
803
+ function IsEqual(left, right) {
804
+ return left === right;
805
+ }
806
+ /** Returns true if the PropertyKey is Unsafe (ref: prototype-pollution). */
807
+ function IsUnsafePropertyKey(key) {
808
+ return IsEqual(key, "__proto__") || IsEqual(key, "constructor") || IsEqual(key, "prototype");
809
+ }
810
+ /** Returns true if this value has this property key */
811
+ function HasPropertyKey(value, key) {
812
+ return IsUnsafePropertyKey(key) ? Object.prototype.hasOwnProperty.call(value, key) : key in value;
813
+ }
814
+ /** Returns property keys for this object via `Object.getOwnPropertyKeys({ ... })` */
815
+ function Keys(value) {
816
+ return Object.getOwnPropertyNames(value);
817
+ }
818
+ //#endregion
819
+ //#region node_modules/typebox/build/system/memory/clone.mjs
820
+ function IsGuard(value) {
821
+ return IsObject(value) && HasPropertyKey(value, "~guard");
822
+ }
823
+ function FromGuard(value) {
824
+ return value;
825
+ }
826
+ function FromArray(value) {
827
+ return value.map((value) => FromValue(value));
828
+ }
829
+ function FromObject(value) {
830
+ const result = {};
831
+ const descriptors = Object.getOwnPropertyDescriptors(value);
832
+ for (const key of Object.keys(descriptors)) {
833
+ const descriptor = descriptors[key];
834
+ if (HasPropertyKey(descriptor, "value")) Object.defineProperty(result, key, {
835
+ ...descriptor,
836
+ value: FromValue(descriptor.value)
837
+ });
838
+ }
839
+ return result;
840
+ }
841
+ function FromRegExp(value) {
842
+ return new RegExp(value.source, value.flags);
843
+ }
844
+ function FromUnknown(value) {
845
+ return value;
846
+ }
847
+ function FromValue(value) {
848
+ return value instanceof RegExp ? FromRegExp(value) : IsGuard(value) ? FromGuard(value) : IsArray(value) ? FromArray(value) : IsObject(value) ? FromObject(value) : FromUnknown(value);
849
+ }
850
+ /**
851
+ * Clones a value using the TypeBox type cloning strategy. This function preserves non-enumerable
852
+ * properties from the source value. This is to ensure cloned types retain discriminable
853
+ * hidden properties.
854
+ */
855
+ function Clone(value) {
856
+ Metrics.clone += 1;
857
+ return FromValue(value);
858
+ }
859
+ //#endregion
860
+ //#region node_modules/typebox/build/system/settings/settings.mjs
861
+ const settings = {
862
+ immutableTypes: false,
863
+ maxErrors: 8,
864
+ useAcceleration: true,
865
+ exactOptionalPropertyTypes: false,
866
+ enumerableKind: false,
867
+ correctiveParse: false
868
+ };
869
+ /** Gets current system settings */
870
+ function Get() {
871
+ return settings;
872
+ }
873
+ //#endregion
874
+ //#region node_modules/typebox/build/system/memory/create.mjs
875
+ function MergeHidden(left, right) {
876
+ for (const key of Object.keys(right)) Object.defineProperty(left, key, {
877
+ configurable: true,
878
+ writable: true,
879
+ enumerable: false,
880
+ value: right[key]
881
+ });
882
+ return left;
883
+ }
884
+ function Merge(left, right) {
885
+ return {
886
+ ...left,
887
+ ...right
888
+ };
889
+ }
890
+ /**
891
+ * Creates an object with hidden, enumerable, and optional property sets. This function
892
+ * ensures types are instantiated according to configuration rules for enumerable and
893
+ * non-enumerable properties.
894
+ */
895
+ function Create(hidden, enumerable, options = {}) {
896
+ Metrics.create += 1;
897
+ const settings = Get();
898
+ const withOptions = Merge(enumerable, options);
899
+ const withHidden = settings.enumerableKind ? Merge(withOptions, hidden) : MergeHidden(withOptions, hidden);
900
+ return settings.immutableTypes ? Object.freeze(withHidden) : withHidden;
901
+ }
902
+ //#endregion
903
+ //#region node_modules/typebox/build/system/memory/update.mjs
904
+ /**
905
+ * Updates a value with new properties while preserving property enumerability. Use this function to modify
906
+ * existing types without altering their configuration.
907
+ */
908
+ function Update(current, hidden, enumerable) {
909
+ Metrics.update += 1;
910
+ const settings = Get();
911
+ const result = Clone(current);
912
+ for (const key of Object.keys(hidden)) Object.defineProperty(result, key, {
913
+ configurable: true,
914
+ writable: true,
915
+ enumerable: settings.enumerableKind,
916
+ value: hidden[key]
917
+ });
918
+ for (const key of Object.keys(enumerable)) Object.defineProperty(result, key, {
919
+ configurable: true,
920
+ enumerable: true,
921
+ writable: true,
922
+ value: enumerable[key]
923
+ });
924
+ return result;
925
+ }
926
+ //#endregion
927
+ //#region node_modules/typebox/build/type/types/schema.mjs
928
+ function IsSchema(value) {
929
+ return IsObject(value);
930
+ }
931
+ //#endregion
932
+ //#region node_modules/typebox/build/type/types/_optional.mjs
933
+ /** Adds Optional to the given type. */
934
+ function OptionalAdd(type) {
935
+ return Update(type, { "~optional": true }, {});
936
+ }
937
+ /** Applies an Optional modifier to the given type. */
938
+ function Optional(type) {
939
+ return OptionalAdd(type);
940
+ }
941
+ /** Returns true if the given value is TOptional */
942
+ function IsOptional(value) {
943
+ return IsSchema(value) && HasPropertyKey(value, "~optional");
944
+ }
945
+ //#endregion
946
+ //#region node_modules/typebox/build/type/types/array.mjs
947
+ /** Creates an Array type. */
948
+ function _Array_(items, options) {
949
+ return Create({ "~kind": "Array" }, {
950
+ type: "array",
951
+ items
952
+ }, options);
953
+ }
954
+ //#endregion
955
+ //#region node_modules/typebox/build/type/types/properties.mjs
956
+ /** Creates a RequiredArray derived from the given TProperties value. */
957
+ function RequiredArray(properties) {
958
+ return Keys(properties).filter((key) => !IsOptional(properties[key]));
959
+ }
960
+ //#endregion
961
+ //#region node_modules/typebox/build/type/types/object.mjs
962
+ /** Creates an Object type. */
963
+ function _Object_(properties, options = {}) {
964
+ const requiredKeys = RequiredArray(properties);
965
+ const required = requiredKeys.length > 0 ? { required: requiredKeys } : {};
966
+ return Create({ "~kind": "Object" }, {
967
+ type: "object",
968
+ ...required,
969
+ properties
970
+ }, options);
971
+ }
972
+ //#endregion
973
+ //#region node_modules/typebox/build/type/types/union.mjs
974
+ /** Creates a Union type. */
975
+ function Union(anyOf, options = {}) {
976
+ return Create({ "~kind": "Union" }, { anyOf }, options);
977
+ }
978
+ //#endregion
979
+ //#region node_modules/typebox/build/type/types/unknown.mjs
980
+ /** Creates an Unknown type. */
981
+ function Unknown(options) {
982
+ return Create({ ["~kind"]: "Unknown" }, {}, options);
983
+ }
984
+ //#endregion
985
+ //#region node_modules/typebox/build/system/hashing/hash.mjs
986
+ var ByteMarker;
987
+ (function(ByteMarker) {
988
+ ByteMarker[ByteMarker["Array"] = 0] = "Array";
989
+ ByteMarker[ByteMarker["BigInt"] = 1] = "BigInt";
990
+ ByteMarker[ByteMarker["Boolean"] = 2] = "Boolean";
991
+ ByteMarker[ByteMarker["Date"] = 3] = "Date";
992
+ ByteMarker[ByteMarker["Constructor"] = 4] = "Constructor";
993
+ ByteMarker[ByteMarker["Function"] = 5] = "Function";
994
+ ByteMarker[ByteMarker["Null"] = 6] = "Null";
995
+ ByteMarker[ByteMarker["Number"] = 7] = "Number";
996
+ ByteMarker[ByteMarker["Object"] = 8] = "Object";
997
+ ByteMarker[ByteMarker["RegExp"] = 9] = "RegExp";
998
+ ByteMarker[ByteMarker["String"] = 10] = "String";
999
+ ByteMarker[ByteMarker["Symbol"] = 11] = "Symbol";
1000
+ ByteMarker[ByteMarker["TypeArray"] = 12] = "TypeArray";
1001
+ ByteMarker[ByteMarker["Undefined"] = 13] = "Undefined";
1002
+ })(ByteMarker || (ByteMarker = {}));
1003
+ const [Prime, Size] = [BigInt("1099511628211"), BigInt("18446744073709551616")];
1004
+ Array.from({ length: 256 }).map((_, i) => BigInt(i));
1005
+ const F64 = new Float64Array(1);
1006
+ new DataView(F64.buffer);
1007
+ new Uint8Array(F64.buffer);
1008
+ new TextEncoder();
1009
+ //#endregion
1010
+ //#region node_modules/typebox/build/type/types/boolean.mjs
1011
+ /** Creates a Boolean type. */
1012
+ function Boolean(options) {
1013
+ return Create({ "~kind": "Boolean" }, { type: "boolean" }, options);
1014
+ }
1015
+ //#endregion
1016
+ //#region node_modules/typebox/build/type/types/integer.mjs
1017
+ const IntegerPattern = "-?(?:0|[1-9][0-9]*)";
1018
+ /** Creates a Integer type. */
1019
+ function Integer(options) {
1020
+ return Create({ "~kind": "Integer" }, { type: "integer" }, options);
1021
+ }
1022
+ //#endregion
1023
+ //#region node_modules/typebox/build/type/types/literal.mjs
1024
+ var InvalidLiteralValue = class extends Error {
1025
+ constructor(value) {
1026
+ super(`Invalid Literal value`);
1027
+ Object.defineProperty(this, "cause", {
1028
+ value: { value },
1029
+ writable: false,
1030
+ configurable: false,
1031
+ enumerable: false
1032
+ });
1033
+ }
1034
+ };
1035
+ function LiteralTypeName(value) {
1036
+ return IsBigInt(value) ? "bigint" : IsBoolean(value) ? "boolean" : IsNumber(value) ? "number" : IsString(value) ? "string" : (() => {
1037
+ throw new InvalidLiteralValue(value);
1038
+ })();
1039
+ }
1040
+ /** Creates a Literal type. */
1041
+ function Literal(value, options) {
1042
+ return Create({ "~kind": "Literal" }, {
1043
+ type: LiteralTypeName(value),
1044
+ const: value
1045
+ }, options);
1046
+ }
1047
+ //#endregion
1048
+ //#region node_modules/typebox/build/type/types/null.mjs
1049
+ /** Creates a Null type. */
1050
+ function Null(options) {
1051
+ return Create({ "~kind": "Null" }, { type: "null" }, options);
1052
+ }
1053
+ //#endregion
1054
+ //#region node_modules/typebox/build/type/types/number.mjs
1055
+ const NumberPattern = "-?(?:0|[1-9][0-9]*)(?:.[0-9]+)?";
1056
+ /** Creates a Number type. */
1057
+ function Number$1(options) {
1058
+ return Create({ "~kind": "Number" }, { type: "number" }, options);
1059
+ }
1060
+ //#endregion
1061
+ //#region node_modules/typebox/build/type/types/string.mjs
1062
+ /** Creates a String type. */
1063
+ function String$1(options) {
1064
+ return Create({ "~kind": "String" }, { type: "string" }, options);
1065
+ }
1066
+ //#endregion
1067
+ //#region node_modules/typebox/build/type/types/record.mjs
1068
+ const IntegerKey = `^${IntegerPattern}$`;
1069
+ `${NumberPattern}`;
1070
+ //#endregion
1071
+ //#region node_modules/typebox/build/type/script/token/internal/char.mjs
1072
+ function Range(start, end) {
1073
+ return Array.from({ length: end - start + 1 }, (_, i) => String.fromCharCode(start + i));
1074
+ }
1075
+ const Alpha = [...Range(97, 122), ...Range(65, 90)];
1076
+ const Digit = ["0", ...Range(49, 57)];
1077
+ [...Digit];
1078
+ [...[
1079
+ ...Alpha,
1080
+ "_",
1081
+ "$"
1082
+ ], ...Digit];
1083
+ [...Digit];
1084
+ new RegExp(IntegerKey);
1085
+ //#endregion
754
1086
  //#region src/openai/json-schema-to-typebox.ts
755
1087
  /**
756
1088
  * JSON Schema -> TypeBox conversion for OpenAI function tool parameters.
@@ -819,7 +1151,7 @@ function jsonSchemaToTypebox(schema, path = "") {
819
1151
  if (typeof rawType !== "string") {
820
1152
  if (Object.keys(schema).length === 0 || Object.keys(schema).length === 1 && description !== void 0) return {
821
1153
  ok: true,
822
- schema: Type.Unknown(opts)
1154
+ schema: Unknown(opts)
823
1155
  };
824
1156
  return {
825
1157
  ok: false,
@@ -831,23 +1163,23 @@ function jsonSchemaToTypebox(schema, path = "") {
831
1163
  case "object": return convertObject(schema, path, opts);
832
1164
  case "string": return {
833
1165
  ok: true,
834
- schema: Type.String(opts)
1166
+ schema: String$1(opts)
835
1167
  };
836
1168
  case "number": return {
837
1169
  ok: true,
838
- schema: Type.Number(opts)
1170
+ schema: Number$1(opts)
839
1171
  };
840
1172
  case "integer": return {
841
1173
  ok: true,
842
- schema: Type.Integer(opts)
1174
+ schema: Integer(opts)
843
1175
  };
844
1176
  case "boolean": return {
845
1177
  ok: true,
846
- schema: Type.Boolean(opts)
1178
+ schema: Boolean(opts)
847
1179
  };
848
1180
  case "null": return {
849
1181
  ok: true,
850
- schema: Type.Null(opts)
1182
+ schema: Null(opts)
851
1183
  };
852
1184
  case "array": return convertArray(schema, path, opts);
853
1185
  default: return {
@@ -884,7 +1216,7 @@ function convertAnyOf(branches, path, opts) {
884
1216
  }
885
1217
  return {
886
1218
  ok: true,
887
- schema: Type.Union(converted, opts)
1219
+ schema: Union(converted, opts)
888
1220
  };
889
1221
  }
890
1222
  function convertEnum(values, path, opts) {
@@ -902,10 +1234,9 @@ function convertEnum(values, path, opts) {
902
1234
  message: `Empty enum at ${path || "root"}`,
903
1235
  path
904
1236
  };
905
- const literals = stringValues.map((v) => Type.Literal(v));
906
1237
  return {
907
1238
  ok: true,
908
- schema: Type.Union(literals, opts)
1239
+ schema: Union(stringValues.map((v) => Literal(v)), opts)
909
1240
  };
910
1241
  }
911
1242
  function convertNullableType(typeArray, schema, path, opts) {
@@ -928,7 +1259,7 @@ function convertNullableType(typeArray, schema, path, opts) {
928
1259
  if (!innerResult.ok) return innerResult;
929
1260
  return {
930
1261
  ok: true,
931
- schema: Type.Union([innerResult.schema, Type.Null()], opts)
1262
+ schema: Union([innerResult.schema, Null()], opts)
932
1263
  };
933
1264
  }
934
1265
  function convertObject(schema, path, opts) {
@@ -942,7 +1273,7 @@ function convertObject(schema, path, opts) {
942
1273
  };
943
1274
  if (rawProperties === void 0) return {
944
1275
  ok: true,
945
- schema: Type.Object({}, opts)
1276
+ schema: _Object_({}, opts)
946
1277
  };
947
1278
  if (!isRecord(rawProperties)) return {
948
1279
  ok: false,
@@ -958,24 +1289,24 @@ function convertObject(schema, path, opts) {
958
1289
  const result = jsonSchemaToTypebox(propSchema, path.length > 0 ? `${path}.${key}` : key);
959
1290
  if (!result.ok) return result;
960
1291
  if (requiredSet.has(key)) typeboxProperties[key] = result.schema;
961
- else typeboxProperties[key] = Type.Optional(result.schema);
1292
+ else typeboxProperties[key] = Optional(result.schema);
962
1293
  }
963
1294
  return {
964
1295
  ok: true,
965
- schema: Type.Object(typeboxProperties, opts)
1296
+ schema: _Object_(typeboxProperties, opts)
966
1297
  };
967
1298
  }
968
1299
  function convertArray(schema, path, opts) {
969
1300
  const items = schema["items"];
970
1301
  if (items === void 0) return {
971
1302
  ok: true,
972
- schema: Type.Array(Type.Unknown(), opts)
1303
+ schema: _Array_(Unknown(), opts)
973
1304
  };
974
1305
  const itemResult = jsonSchemaToTypebox(items, path.length > 0 ? `${path}.items` : "items");
975
1306
  if (!itemResult.ok) return itemResult;
976
1307
  return {
977
1308
  ok: true,
978
- schema: Type.Array(itemResult.schema, opts)
1309
+ schema: _Array_(itemResult.schema, opts)
979
1310
  };
980
1311
  }
981
1312
  //#endregion
@@ -998,7 +1329,7 @@ function convertTools(openaiTools) {
998
1329
  param: result.path
999
1330
  };
1000
1331
  parameters = result.schema;
1001
- } else parameters = Type.Object({});
1332
+ } else parameters = _Object_({});
1002
1333
  piTools.push({
1003
1334
  name: fn.name,
1004
1335
  description: fn.description ?? "",
@@ -1589,8 +1920,10 @@ async function buildStreamOptions(model, request, options) {
1589
1920
  if (combinedSignal !== void 0) opts.signal = combinedSignal;
1590
1921
  if (options.upstreamApiKey !== void 0) opts.apiKey = options.upstreamApiKey;
1591
1922
  else {
1592
- const apiKey = await getRegistry().getApiKey(model);
1593
- if (apiKey !== void 0) opts.apiKey = apiKey;
1923
+ const auth = await getRequestAuth(model);
1924
+ if (!auth.ok) throw new Error(auth.error);
1925
+ if (auth.apiKey !== void 0) opts.apiKey = auth.apiKey;
1926
+ if (auth.headers !== void 0) opts.headers = auth.headers;
1594
1927
  }
1595
1928
  const payloadFields = collectPayloadFields(request, model.api);
1596
1929
  const strictFlags = collectToolStrictFlags(request.tools);
@@ -1619,19 +1952,19 @@ async function piStream(model, context, request, options) {
1619
1952
  }
1620
1953
  //#endregion
1621
1954
  //#region src/server/routes.ts
1622
- function fileConfigReader() {
1955
+ async function fileConfigReader() {
1623
1956
  const file = loadConfigFromFile();
1624
1957
  return {
1625
1958
  publicModelIdMode: file.publicModelIdMode,
1626
1959
  modelExposureMode: file.modelExposureMode,
1627
- enabledModels: getEnabledModels(),
1960
+ enabledModels: await getEnabledModels(),
1628
1961
  customModels: file.customModels,
1629
1962
  providerPrefixes: file.providerPrefixes
1630
1963
  };
1631
1964
  }
1632
1965
  function createRoutes(config, configReader = fileConfigReader) {
1633
- function getExposure() {
1634
- const outcome = computeModelExposure(getAvailableModels(), configReader());
1966
+ async function getExposure() {
1967
+ const outcome = computeModelExposure(getAvailableModels(), await configReader());
1635
1968
  if (!outcome.ok) throw new Error(`Model exposure configuration error: ${outcome.message}`);
1636
1969
  return outcome;
1637
1970
  }
@@ -1645,17 +1978,17 @@ function createRoutes(config, configReader = fileConfigReader) {
1645
1978
  return mapped;
1646
1979
  }
1647
1980
  const routes = new Hono();
1648
- routes.get("/v1/models", (c) => {
1649
- const exposure = getExposure();
1981
+ routes.get("/v1/models", async (c) => {
1982
+ const exposure = await getExposure();
1650
1983
  return c.json(buildModelList(exposure.models));
1651
1984
  });
1652
- routes.get("/v1/models/*", (c) => {
1985
+ routes.get("/v1/models/*", async (c) => {
1653
1986
  const rawPath = c.req.path;
1654
1987
  if (!rawPath.startsWith("/v1/models/")) return c.json(modelNotFound(""), 404);
1655
1988
  const modelIdEncoded = rawPath.slice(11);
1656
1989
  if (modelIdEncoded.length === 0) return c.json(modelNotFound(""), 404);
1657
1990
  const modelId = decodeURIComponent(modelIdEncoded);
1658
- const resolved = resolveExposedModel(getExposure(), modelId);
1991
+ const resolved = resolveExposedModel(await getExposure(), modelId);
1659
1992
  if (resolved === void 0) return c.json(modelNotFound(modelId), 404);
1660
1993
  return c.json(toOpenAIModel(resolved));
1661
1994
  });
@@ -1675,7 +2008,7 @@ function createRoutes(config, configReader = fileConfigReader) {
1675
2008
  return c.json(invalidRequest(validation.message, validation.param ?? void 0), 400);
1676
2009
  }
1677
2010
  const request = validation.data;
1678
- const resolved = resolveExposedModel(getExposure(), request.model);
2011
+ const resolved = resolveExposedModel(await getExposure(), request.model);
1679
2012
  if (resolved === void 0) return c.json(modelNotFound(request.model), 404);
1680
2013
  const model = resolved.model;
1681
2014
  const canonicalModelId = resolved.canonicalId;
@@ -1689,7 +2022,8 @@ function createRoutes(config, configReader = fileConfigReader) {
1689
2022
  context.tools = toolConversion.tools;
1690
2023
  }
1691
2024
  if (upstreamApiKey === void 0) {
1692
- if (await getRegistry().getApiKey(model) === void 0) return c.json(authenticationError(`No API key configured for provider '${model.provider}'. Configure credentials via 'pi /login' or pass X-Pi-Upstream-Api-Key header.`), 401);
2025
+ const requestAuth = await getRequestAuth(model);
2026
+ if (!requestAuth.ok) return c.json(authenticationError(`Could not resolve request auth for provider '${model.provider}'. ${requestAuth.error}. Configure credentials via 'pi /login' or pass X-Pi-Upstream-Api-Key header.`), 401);
1693
2027
  }
1694
2028
  const completionOptions = {
1695
2029
  upstreamApiKey,
@@ -94,24 +94,24 @@ export default function proxyExtension(pi: ExtensionAPI): void {
94
94
  // --- Model registry access (cached, refreshed per call) ---
95
95
 
96
96
  const cachedAuth = AuthStorage.create();
97
- const cachedRegistry = new ModelRegistry(cachedAuth);
98
- const settingsManager = SettingsManager.create();
97
+ const cachedRegistry = ModelRegistry.create(cachedAuth);
98
+ const settingsManager = SettingsManager.create(process.cwd());
99
99
 
100
100
  function getAvailableModels(): Model<Api>[] {
101
101
  cachedRegistry.refresh();
102
102
  return cachedRegistry.getAvailable();
103
103
  }
104
104
 
105
- function getEnabledModels(): readonly string[] | undefined {
106
- settingsManager.reload();
105
+ async function getEnabledModels(): Promise<readonly string[] | undefined> {
106
+ await settingsManager.reload();
107
107
  return settingsManager.getEnabledModels();
108
108
  }
109
109
 
110
- function buildExposureConfig(): ModelExposureConfig {
110
+ async function buildExposureConfig(): Promise<ModelExposureConfig> {
111
111
  return {
112
112
  publicModelIdMode: config.publicModelIdMode,
113
113
  modelExposureMode: config.modelExposureMode,
114
- enabledModels: getEnabledModels(),
114
+ enabledModels: await getEnabledModels(),
115
115
  customModels: config.customModels,
116
116
  providerPrefixes: config.providerPrefixes,
117
117
  };
@@ -137,7 +137,7 @@ export default function proxyExtension(pi: ExtensionAPI): void {
137
137
 
138
138
  pi.on("session_start", async (_event, ctx) => {
139
139
  config = loadConfigFromFile();
140
- maybeAutoSyncZed(ctx);
140
+ await maybeAutoSyncZed(ctx);
141
141
  await refreshStatus(ctx);
142
142
  });
143
143
 
@@ -193,16 +193,16 @@ export default function proxyExtension(pi: ExtensionAPI): void {
193
193
  await showStatus(ctx);
194
194
  return;
195
195
  case "verify":
196
- verifyExposure(ctx);
196
+ await verifyExposure(ctx);
197
197
  return;
198
198
  case "models":
199
- showModels(ctx);
199
+ await showModels(ctx);
200
200
  return;
201
201
  case "zed-sync":
202
- handleZedSync(ctx, args);
202
+ await handleZedSync(ctx, args);
203
203
  return;
204
204
  case "show":
205
- showConfig(ctx);
205
+ await showConfig(ctx);
206
206
  return;
207
207
  case "path":
208
208
  ctx.ui.notify(getConfigPath(), "info");
@@ -460,7 +460,7 @@ export default function proxyExtension(pi: ExtensionAPI): void {
460
460
  await refreshStatus(ctx);
461
461
  }
462
462
 
463
- function showConfig(ctx: ExtensionContext): void {
463
+ async function showConfig(ctx: ExtensionContext): Promise<void> {
464
464
  config = loadConfigFromFile();
465
465
  const authDisplay =
466
466
  config.authToken.length > 0 ? `enabled (token: ${config.authToken})` : "disabled";
@@ -483,7 +483,7 @@ export default function proxyExtension(pi: ExtensionAPI): void {
483
483
  ];
484
484
 
485
485
  if (config.modelExposureMode === "scoped") {
486
- const enabledModels = getEnabledModels();
486
+ const enabledModels = await getEnabledModels();
487
487
  if (enabledModels !== undefined && enabledModels.length > 0) {
488
488
  exposureLines.push(`enabled: ${String(enabledModels.length)} pi model(s)`);
489
489
  }
@@ -500,7 +500,7 @@ export default function proxyExtension(pi: ExtensionAPI): void {
500
500
 
501
501
  // Public ID preview (first 5 exposed models)
502
502
  const models = getAvailableModels();
503
- const outcome = computeModelExposure(models, buildExposureConfig());
503
+ const outcome = computeModelExposure(models, await buildExposureConfig());
504
504
  if (outcome.ok && outcome.models.length > 0) {
505
505
  const preview = outcome.models.slice(0, 5).map((m) => m.publicId);
506
506
  const suffix =
@@ -517,10 +517,10 @@ export default function proxyExtension(pi: ExtensionAPI): void {
517
517
 
518
518
  // --- /proxy models ---
519
519
 
520
- function showModels(ctx: ExtensionContext): void {
520
+ async function showModels(ctx: ExtensionContext): Promise<void> {
521
521
  config = loadConfigFromFile();
522
522
  const models = getAvailableModels();
523
- const outcome = computeModelExposure(models, buildExposureConfig());
523
+ const outcome = computeModelExposure(models, await buildExposureConfig());
524
524
 
525
525
  if (!outcome.ok) {
526
526
  ctx.ui.notify(`Model exposure error: ${outcome.message}`, "warning");
@@ -562,7 +562,7 @@ export default function proxyExtension(pi: ExtensionAPI): void {
562
562
 
563
563
  // --- /proxy verify ---
564
564
 
565
- function verifyExposure(ctx: ExtensionContext): void {
565
+ async function verifyExposure(ctx: ExtensionContext): Promise<void> {
566
566
  config = loadConfigFromFile();
567
567
  const models = getAvailableModels();
568
568
  const issues: string[] = [];
@@ -586,7 +586,7 @@ export default function proxyExtension(pi: ExtensionAPI): void {
586
586
  }
587
587
 
588
588
  // Run the full exposure computation to catch ID/prefix errors
589
- const outcome = computeModelExposure(models, buildExposureConfig());
589
+ const outcome = computeModelExposure(models, await buildExposureConfig());
590
590
  if (!outcome.ok) {
591
591
  issues.push(outcome.message);
592
592
  }
@@ -607,9 +607,9 @@ export default function proxyExtension(pi: ExtensionAPI): void {
607
607
  /**
608
608
  * Run Zed sync and return the result. Shared by the command and auto-sync.
609
609
  */
610
- function runZedSync(dryRun: boolean): { ok: boolean; message: string } {
610
+ async function runZedSync(dryRun: boolean): Promise<{ ok: boolean; message: string }> {
611
611
  const available = getAvailableModels();
612
- const outcome = computeModelExposure(available, buildExposureConfig());
612
+ const outcome = computeModelExposure(available, await buildExposureConfig());
613
613
  if (!outcome.ok) {
614
614
  return { ok: false, message: `Model exposure error: ${outcome.message}` };
615
615
  }
@@ -634,19 +634,19 @@ export default function proxyExtension(pi: ExtensionAPI): void {
634
634
  return { ok: true, message: `${prefix}${result.summary} (${result.configPath})` };
635
635
  }
636
636
 
637
- function handleZedSync(ctx: ExtensionContext, args: string): void {
637
+ async function handleZedSync(ctx: ExtensionContext, args: string): Promise<void> {
638
638
  config = loadConfigFromFile();
639
639
  const dryRun = args.includes("--dry-run");
640
- const result = runZedSync(dryRun);
640
+ const result = await runZedSync(dryRun);
641
641
  ctx.ui.notify(`Zed sync: ${result.message}`, result.ok ? "info" : "error");
642
642
  }
643
643
 
644
644
  /**
645
645
  * Trigger auto-sync to Zed if enabled. Called after config save.
646
646
  */
647
- function maybeAutoSyncZed(ctx: ExtensionContext): void {
647
+ async function maybeAutoSyncZed(ctx: ExtensionContext): Promise<void> {
648
648
  if (!config.zed.autoSync) return;
649
- const result = runZedSync(false);
649
+ const result = await runZedSync(false);
650
650
  if (result.ok) {
651
651
  ctx.ui.notify(`Zed auto-sync: ${result.message}`, "info");
652
652
  }
@@ -723,7 +723,7 @@ export default function proxyExtension(pi: ExtensionAPI): void {
723
723
 
724
724
  // HACK: SettingsList has no public API for jumping to an index.
725
725
  // Accesses private fields via bracket notation for provider jumping.
726
- // Pinned to pi-tui behavior as of @mariozechner/pi-coding-agent ^0.62.0.
726
+ // Pinned to pi-tui behavior as of @mariozechner/pi-coding-agent ^0.63.1.
727
727
  // Remove when SettingsList exposes a jumpTo/setSelectedIndex method.
728
728
 
729
729
  // Isolated unsafe accessor for SettingsList private fields.
@@ -1106,7 +1106,7 @@ export default function proxyExtension(pi: ExtensionAPI): void {
1106
1106
  settingsList.updateValue("customModels", customModelsDisplay());
1107
1107
  }
1108
1108
 
1109
- maybeAutoSyncZed(ctx);
1109
+ void maybeAutoSyncZed(ctx);
1110
1110
  tui.requestRender();
1111
1111
  },
1112
1112
  () => done(undefined),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@victor-software-house/pi-openai-proxy",
3
- "version": "4.9.2",
3
+ "version": "4.9.4",
4
4
  "description": "OpenAI-compatible HTTP proxy for pi's multi-provider model registry",
5
5
  "license": "MIT",
6
6
  "author": "Victor Software House",
@@ -72,9 +72,8 @@
72
72
  "prepublishOnly": "bun test && bun run build"
73
73
  },
74
74
  "dependencies": {
75
- "@mariozechner/pi-ai": "^0.62.0",
76
- "@mariozechner/pi-coding-agent": "^0.62.0",
77
- "@sinclair/typebox": "^0.34.0",
75
+ "@mariozechner/pi-ai": "^0.73.0",
76
+ "@mariozechner/pi-coding-agent": "^0.73.0",
78
77
  "citty": "^0.1.6",
79
78
  "hono": "^4.12.8",
80
79
  "jsonc-parser": "^3.3.1",
@@ -98,6 +97,7 @@
98
97
  "oxlint-tsgolint": "^0.17.1",
99
98
  "semantic-release": "^25.0.3",
100
99
  "tsdown": "^0.21.4",
100
+ "typebox": "^1.1.38",
101
101
  "typescript": "^5.9.3"
102
102
  }
103
103
  }