@victor-software-house/pi-openai-proxy 4.8.0 → 4.9.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/README.md CHANGED
@@ -113,17 +113,32 @@ curl http://localhost:4141/v1/chat/completions \
113
113
  | `stop` | Via passthrough |
114
114
  | `user` | Via passthrough |
115
115
  | `stream_options.include_usage` | Final usage chunk in SSE stream |
116
- | `tools` / `tool_choice` | JSON Schema -> TypeBox conversion (supported subset) |
116
+ | `tools` / `tool_choice` | JSON Schema -> TypeBox conversion (supported subset); `tool_choice` translated per-provider |
117
117
  | `tool_calls` in messages | Assistant tool call + tool result roundtrip |
118
+ | `parallel_tool_calls` | Forwarded to OpenAI/Codex; translated to `disable_parallel_tool_use` for Anthropic |
118
119
  | `reasoning_effort` | Maps to pi's `ThinkingLevel` (`none`, `minimal`, `low`, `medium`, `high`, `xhigh`) |
119
- | `response_format` | `text`, `json_object`, and `json_schema` via passthrough |
120
- | `top_p` | Via passthrough |
121
- | `frequency_penalty` | Via passthrough |
122
- | `presence_penalty` | Via passthrough |
123
- | `seed` | Via passthrough |
120
+ | `response_format` | `text`, `json_object`, and `json_schema` (OpenAI-compatible APIs only) |
121
+ | `top_p` | Supported by OpenAI, Anthropic (native), and Google (translated to `topP`) |
122
+ | `frequency_penalty` | OpenAI and Google only (translated to `frequencyPenalty`) |
123
+ | `presence_penalty` | OpenAI and Google only (translated to `presencePenalty`) |
124
+ | `seed` | OpenAI and Google only (translated to nested `generationConfig.seed`) |
125
+ | `stop` | Translated per-provider (`stop_sequences` for Anthropic, `stopSequences` for Google) |
126
+ | `metadata` | OpenAI passthrough; arbitrary keys silently skipped for other providers |
127
+ | `prediction` | OpenAI passthrough only (speculative decoding) |
124
128
 
125
129
  **Not supported:** `n > 1`, `logprobs`, `logit_bias`, remote image URLs (disabled by default).
126
130
 
131
+ ### Provider-aware field translation
132
+
133
+ Fields are translated to each provider's native format, not blindly forwarded:
134
+
135
+ - **OpenAI / Mistral**: All fields passed directly (same wire format)
136
+ - **Codex (Responses API)**: Only `tool_choice` and `parallel_tool_calls` (other fields rejected by API)
137
+ - **Anthropic**: `tool_choice` → `{ type }` format, `stop` → `stop_sequences`, `user` → `metadata.user_id`, `parallel_tool_calls: false` → `disable_parallel_tool_use`
138
+ - **Google**: Nested into `generationConfig` with camelCase names (`topP`, `stopSequences`, `seed`, etc.)
139
+
140
+ Fields that have no equivalent in a target provider are silently skipped — the request succeeds and the provider's default applies.
141
+
127
142
  ## Model Naming and Exposure
128
143
 
129
144
  ### Public model IDs
package/dist/index.mjs CHANGED
@@ -1247,15 +1247,23 @@ const REASONING_EFFORT_MAP = {
1247
1247
  xhigh: "xhigh"
1248
1248
  };
1249
1249
  /**
1250
- * APIs that use the OpenAI chat completions wire format and accept standard
1251
- * passthrough fields (stop, seed, top_p, tool_choice, etc.) in the payload.
1250
+ * APIs that accept the full set of OpenAI passthrough fields (stop, seed, top_p,
1251
+ * tool_choice, frequency_penalty, etc.) as top-level payload properties.
1252
1252
  */
1253
- const OPENAI_COMPATIBLE_APIS = new Set([
1253
+ const OPENAI_FULL_PASSTHROUGH_APIS = new Set([
1254
1254
  "openai-completions",
1255
1255
  "openai-responses",
1256
1256
  "azure-openai-responses",
1257
1257
  "mistral-conversations"
1258
1258
  ]);
1259
+ /**
1260
+ * The Codex Responses API accepts only tool_choice and parallel_tool_calls.
1261
+ * Other standard fields (top_p, seed, stop, user, frequency_penalty, etc.)
1262
+ * are rejected with "Unsupported parameter". The SDK hardcodes
1263
+ * tool_choice: "auto" and parallel_tool_calls: true; onPayload overrides
1264
+ * those when the client sends explicit values.
1265
+ */
1266
+ const CODEX_APIS = new Set(["openai-codex-responses"]);
1259
1267
  const ANTHROPIC_APIS = new Set(["anthropic-messages"]);
1260
1268
  const GOOGLE_APIS = new Set([
1261
1269
  "google-generative-ai",
@@ -1462,13 +1470,36 @@ function patchGooglePayload(payload, request) {
1462
1470
  return patched;
1463
1471
  }
1464
1472
  /**
1473
+ * Collect the restricted set of fields the Codex Responses API accepts.
1474
+ *
1475
+ * Only tool_choice and parallel_tool_calls are supported. The SDK hardcodes
1476
+ * tool_choice: "auto" and parallel_tool_calls: true; these overrides let clients
1477
+ * control tool behavior explicitly.
1478
+ *
1479
+ * @internal Exported for unit testing only.
1480
+ */
1481
+ function collectCodexPayloadFields(request) {
1482
+ const fields = {};
1483
+ let hasFields = false;
1484
+ if (request.tool_choice !== void 0) {
1485
+ fields["tool_choice"] = request.tool_choice;
1486
+ hasFields = true;
1487
+ }
1488
+ if (request.parallel_tool_calls !== void 0) {
1489
+ fields["parallel_tool_calls"] = request.parallel_tool_calls;
1490
+ hasFields = true;
1491
+ }
1492
+ return hasFields ? fields : void 0;
1493
+ }
1494
+ /**
1465
1495
  * Collect API-specific payload fields from an OpenAI request.
1466
1496
  *
1467
1497
  * Dispatches to the appropriate translator based on the target API:
1468
- * - OpenAI-compatible: flat field injection (same names)
1498
+ * - OpenAI full passthrough: all standard fields (same names)
1499
+ * - Codex: restricted to tool_choice + parallel_tool_calls only
1469
1500
  * - Anthropic: translated field names and formats
1470
1501
  * - Google: nested generationConfig patching (handled separately in onPayload)
1471
- * - Others (Bedrock, Codex): no passthrough
1502
+ * - Others (Bedrock): no passthrough
1472
1503
  *
1473
1504
  * For Google APIs, returns undefined (patching is done directly in onPayload
1474
1505
  * via patchGooglePayload because the payload structure is nested).
@@ -1476,7 +1507,8 @@ function patchGooglePayload(payload, request) {
1476
1507
  * @internal Exported for unit testing only.
1477
1508
  */
1478
1509
  function collectPayloadFields(request, api) {
1479
- if (OPENAI_COMPATIBLE_APIS.has(api)) return collectOpenAIPayloadFields(request);
1510
+ if (OPENAI_FULL_PASSTHROUGH_APIS.has(api)) return collectOpenAIPayloadFields(request);
1511
+ if (CODEX_APIS.has(api)) return collectCodexPayloadFields(request);
1480
1512
  if (ANTHROPIC_APIS.has(api)) return collectAnthropicPayloadFields(request);
1481
1513
  }
1482
1514
  /**
@@ -1647,6 +1679,7 @@ function createRoutes(config, configReader = fileConfigReader) {
1647
1679
  const conversion = convertMessages(request.messages);
1648
1680
  if (!conversion.ok) return c.json(invalidRequest(conversion.message, conversion.param), 400);
1649
1681
  const context = conversion.context;
1682
+ if (model.api === "openai-codex-responses" && context.systemPrompt === void 0) context.systemPrompt = "";
1650
1683
  if (request.tools !== void 0 && request.tools.length > 0) {
1651
1684
  const toolConversion = convertTools(request.tools);
1652
1685
  if (!toolConversion.ok) return c.json(unsupportedParameter(toolConversion.param, toolConversion.message), 422);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@victor-software-house/pi-openai-proxy",
3
- "version": "4.8.0",
3
+ "version": "4.9.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",