@victor-software-house/pi-openai-proxy 4.5.1 → 4.6.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/index.mjs CHANGED
@@ -1141,7 +1141,17 @@ const chatCompletionRequestSchema = z.object({
1141
1141
  }).strict();
1142
1142
  /**
1143
1143
  * Fields that are explicitly rejected with a helpful error.
1144
- * These are not supported and won't be promoted.
1144
+ *
1145
+ * `n`, `logprobs`, `top_logprobs`, `logit_bias`: not supported by the pi SDK's
1146
+ * simple completion interface and unlikely to be promoted.
1147
+ *
1148
+ * `functions`, `function_call`: deprecated OpenAI fields, superseded by `tools`
1149
+ * and `tool_choice`.
1150
+ *
1151
+ * `parallel_tool_calls`: the pi SDK does not expose parallel tool call control.
1152
+ * The SSE streaming code handles multiple tool calls per response, so the response
1153
+ * side is capable, but the proxy cannot guarantee the flag reaches the provider.
1154
+ * Needs deeper analysis — see Phase 3D in TODO.md.
1145
1155
  */
1146
1156
  const rejectedFields = [
1147
1157
  "n",
@@ -1227,6 +1237,8 @@ const SKIP_PAYLOAD_PASSTHROUGH_APIS = new Set(["openai-codex-responses"]);
1227
1237
  /**
1228
1238
  * Collect fields that need to be injected via onPayload.
1229
1239
  * Skips passthrough for APIs that use non-standard request formats.
1240
+ *
1241
+ * @internal Exported for unit testing only.
1230
1242
  */
1231
1243
  function collectPayloadFields(request, api) {
1232
1244
  if (SKIP_PAYLOAD_PASSTHROUGH_APIS.has(api)) return;
@@ -1260,9 +1272,55 @@ function collectPayloadFields(request, api) {
1260
1272
  fields["response_format"] = request.response_format;
1261
1273
  hasFields = true;
1262
1274
  }
1275
+ if (request.tool_choice !== void 0) {
1276
+ fields["tool_choice"] = request.tool_choice;
1277
+ hasFields = true;
1278
+ }
1263
1279
  return hasFields ? fields : void 0;
1264
1280
  }
1265
1281
  /**
1282
+ * Collect tool strict flags from the original OpenAI request.
1283
+ *
1284
+ * The pi SDK's `Tool` interface has no `strict` field, so the SDK always sets
1285
+ * `strict: false` when building the upstream payload. This function extracts
1286
+ * the per-tool strict flags from the original request so they can be restored
1287
+ * via `onPayload` after the SDK builds the payload.
1288
+ *
1289
+ * Returns a map of tool index -> true for tools that requested strict mode,
1290
+ * or undefined if no tools use strict mode.
1291
+ *
1292
+ * @internal Exported for unit testing only.
1293
+ */
1294
+ function collectToolStrictFlags(tools) {
1295
+ if (tools === void 0 || tools.length === 0) return;
1296
+ let flags;
1297
+ for (let i = 0; i < tools.length; i++) if (tools[i]?.function.strict === true) {
1298
+ flags ??= /* @__PURE__ */ new Map();
1299
+ flags.set(i, true);
1300
+ }
1301
+ return flags;
1302
+ }
1303
+ /**
1304
+ * Apply strict flags to tool definitions in the upstream payload.
1305
+ *
1306
+ * The pi SDK always sets `strict: false` on tool definitions. This function
1307
+ * patches the payload's `tools` array to restore the client's requested
1308
+ * `strict: true` flags on the matching tool definitions.
1309
+ *
1310
+ * @internal Exported for unit testing only.
1311
+ */
1312
+ function applyToolStrictFlags(payload, strictFlags) {
1313
+ const tools = payload["tools"];
1314
+ if (!Array.isArray(tools)) return;
1315
+ for (const [index, _flag] of strictFlags) {
1316
+ const tool = tools[index];
1317
+ if (isRecord(tool)) {
1318
+ const fn = tool["function"];
1319
+ if (isRecord(fn)) fn["strict"] = true;
1320
+ }
1321
+ }
1322
+ }
1323
+ /**
1266
1324
  * Combine a client disconnect signal with an upstream timeout into a single signal.
1267
1325
  * Returns the combined signal, or undefined if neither is provided.
1268
1326
  */
@@ -1293,8 +1351,12 @@ async function buildStreamOptions(model, request, options) {
1293
1351
  if (apiKey !== void 0) opts.apiKey = apiKey;
1294
1352
  }
1295
1353
  const payloadFields = collectPayloadFields(request, model.api);
1296
- if (payloadFields !== void 0) opts.onPayload = (payload) => {
1297
- if (isRecord(payload)) for (const [key, value] of Object.entries(payloadFields)) payload[key] = value;
1354
+ const strictFlags = collectToolStrictFlags(request.tools);
1355
+ if (payloadFields !== void 0 || strictFlags !== void 0) opts.onPayload = (payload) => {
1356
+ if (isRecord(payload)) {
1357
+ if (payloadFields !== void 0) for (const [key, value] of Object.entries(payloadFields)) payload[key] = value;
1358
+ if (strictFlags !== void 0) applyToolStrictFlags(payload, strictFlags);
1359
+ }
1298
1360
  return payload;
1299
1361
  };
1300
1362
  return opts;
@@ -137,6 +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
141
  await refreshStatus(ctx);
141
142
  });
142
143
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@victor-software-house/pi-openai-proxy",
3
- "version": "4.5.1",
3
+ "version": "4.6.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",