@victor-software-house/pi-openai-proxy 4.7.1 → 4.9.0
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 +215 -13
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1247,28 +1247,36 @@ const REASONING_EFFORT_MAP = {
|
|
|
1247
1247
|
xhigh: "xhigh"
|
|
1248
1248
|
};
|
|
1249
1249
|
/**
|
|
1250
|
-
* APIs that
|
|
1251
|
-
*
|
|
1252
|
-
*
|
|
1253
|
-
* Only these APIs receive injected fields via onPayload. All other APIs
|
|
1254
|
-
* (anthropic-messages, google-*, bedrock-*, openai-codex-responses) use
|
|
1255
|
-
* different payload schemas that reject unknown fields.
|
|
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.
|
|
1256
1252
|
*/
|
|
1257
|
-
const
|
|
1253
|
+
const OPENAI_FULL_PASSTHROUGH_APIS = new Set([
|
|
1258
1254
|
"openai-completions",
|
|
1259
1255
|
"openai-responses",
|
|
1260
1256
|
"azure-openai-responses",
|
|
1261
1257
|
"mistral-conversations"
|
|
1262
1258
|
]);
|
|
1263
1259
|
/**
|
|
1264
|
-
*
|
|
1265
|
-
*
|
|
1266
|
-
*
|
|
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"]);
|
|
1267
|
+
const ANTHROPIC_APIS = new Set(["anthropic-messages"]);
|
|
1268
|
+
const GOOGLE_APIS = new Set([
|
|
1269
|
+
"google-generative-ai",
|
|
1270
|
+
"google-gemini-cli",
|
|
1271
|
+
"google-vertex"
|
|
1272
|
+
]);
|
|
1273
|
+
/**
|
|
1274
|
+
* Collect OpenAI-format fields for OpenAI-compatible APIs.
|
|
1275
|
+
* Fields are injected as flat top-level properties on the payload.
|
|
1267
1276
|
*
|
|
1268
1277
|
* @internal Exported for unit testing only.
|
|
1269
1278
|
*/
|
|
1270
|
-
function
|
|
1271
|
-
if (!OPENAI_COMPLETIONS_COMPATIBLE_APIS.has(api)) return;
|
|
1279
|
+
function collectOpenAIPayloadFields(request) {
|
|
1272
1280
|
const fields = {};
|
|
1273
1281
|
let hasFields = false;
|
|
1274
1282
|
if (request.stop !== void 0) {
|
|
@@ -1318,6 +1326,198 @@ function collectPayloadFields(request, api) {
|
|
|
1318
1326
|
return hasFields ? fields : void 0;
|
|
1319
1327
|
}
|
|
1320
1328
|
/**
|
|
1329
|
+
* Translate OpenAI tool_choice to Anthropic tool_choice format.
|
|
1330
|
+
*
|
|
1331
|
+
* OpenAI "auto" -> { type: "auto" }
|
|
1332
|
+
* OpenAI "none" -> { type: "none" } (Anthropic skips tool calling)
|
|
1333
|
+
* OpenAI "required" -> { type: "any" } (force tool use)
|
|
1334
|
+
* OpenAI { type: "function", function: { name } } -> { type: "tool", name }
|
|
1335
|
+
*
|
|
1336
|
+
* @internal Exported for unit testing only.
|
|
1337
|
+
*/
|
|
1338
|
+
function translateToolChoiceForAnthropic(toolChoice) {
|
|
1339
|
+
if (toolChoice === void 0) return;
|
|
1340
|
+
if (toolChoice === "auto") return { type: "auto" };
|
|
1341
|
+
if (toolChoice === "none") return { type: "none" };
|
|
1342
|
+
if (toolChoice === "required") return { type: "any" };
|
|
1343
|
+
return {
|
|
1344
|
+
type: "tool",
|
|
1345
|
+
name: toolChoice.function.name
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1348
|
+
/**
|
|
1349
|
+
* Collect Anthropic-format fields translated from the OpenAI request.
|
|
1350
|
+
*
|
|
1351
|
+
* Supported translations:
|
|
1352
|
+
* - top_p -> top_p (same name, natively supported)
|
|
1353
|
+
* - stop -> stop_sequences (different field name)
|
|
1354
|
+
* - tool_choice -> Anthropic tool_choice format (object with type)
|
|
1355
|
+
* - parallel_tool_calls: false -> disable_parallel_tool_use on tool_choice
|
|
1356
|
+
* - user -> metadata.user_id
|
|
1357
|
+
*
|
|
1358
|
+
* Not supported (silently skipped — these concepts don't exist in Anthropic):
|
|
1359
|
+
* - seed, frequency_penalty, presence_penalty, response_format, prediction, metadata (arbitrary keys)
|
|
1360
|
+
*
|
|
1361
|
+
* @internal Exported for unit testing only.
|
|
1362
|
+
*/
|
|
1363
|
+
function collectAnthropicPayloadFields(request) {
|
|
1364
|
+
const fields = {};
|
|
1365
|
+
let hasFields = false;
|
|
1366
|
+
if (request.top_p !== void 0) {
|
|
1367
|
+
fields["top_p"] = request.top_p;
|
|
1368
|
+
hasFields = true;
|
|
1369
|
+
}
|
|
1370
|
+
if (request.stop !== void 0) {
|
|
1371
|
+
fields["stop_sequences"] = Array.isArray(request.stop) ? request.stop : [request.stop];
|
|
1372
|
+
hasFields = true;
|
|
1373
|
+
}
|
|
1374
|
+
const toolChoice = translateToolChoiceForAnthropic(request.tool_choice);
|
|
1375
|
+
const disableParallel = request.parallel_tool_calls === false;
|
|
1376
|
+
if (toolChoice !== void 0) {
|
|
1377
|
+
if (disableParallel) toolChoice["disable_parallel_tool_use"] = true;
|
|
1378
|
+
fields["tool_choice"] = toolChoice;
|
|
1379
|
+
hasFields = true;
|
|
1380
|
+
} else if (disableParallel) {
|
|
1381
|
+
fields["tool_choice"] = {
|
|
1382
|
+
type: "auto",
|
|
1383
|
+
disable_parallel_tool_use: true
|
|
1384
|
+
};
|
|
1385
|
+
hasFields = true;
|
|
1386
|
+
}
|
|
1387
|
+
if (request.user !== void 0) {
|
|
1388
|
+
fields["metadata"] = { user_id: request.user };
|
|
1389
|
+
hasFields = true;
|
|
1390
|
+
}
|
|
1391
|
+
return hasFields ? fields : void 0;
|
|
1392
|
+
}
|
|
1393
|
+
/**
|
|
1394
|
+
* Translate OpenAI tool_choice to Google FunctionCallingConfigMode string.
|
|
1395
|
+
*
|
|
1396
|
+
* OpenAI "auto" -> "AUTO"
|
|
1397
|
+
* OpenAI "none" -> "NONE"
|
|
1398
|
+
* OpenAI "required" -> "ANY"
|
|
1399
|
+
* Named function choice -> "ANY" (Google doesn't support per-function forcing
|
|
1400
|
+
* in the same way, but ANY forces tool use)
|
|
1401
|
+
*/
|
|
1402
|
+
function translateToolChoiceForGoogle(toolChoice) {
|
|
1403
|
+
if (toolChoice === void 0) return;
|
|
1404
|
+
if (toolChoice === "auto") return "AUTO";
|
|
1405
|
+
if (toolChoice === "none") return "NONE";
|
|
1406
|
+
return "ANY";
|
|
1407
|
+
}
|
|
1408
|
+
/**
|
|
1409
|
+
* Patch Google's nested payload structure with translated fields.
|
|
1410
|
+
*
|
|
1411
|
+
* Google's payload shape: { model, contents, config: { generationConfig, toolConfig, ... } }
|
|
1412
|
+
* Fields go into config.generationConfig (camelCase) or config.toolConfig.
|
|
1413
|
+
*
|
|
1414
|
+
* Supported translations:
|
|
1415
|
+
* - top_p -> config.generationConfig.topP (camelCase, nested)
|
|
1416
|
+
* - stop -> config.generationConfig.stopSequences (array, nested)
|
|
1417
|
+
* - seed -> config.generationConfig.seed (nested)
|
|
1418
|
+
* - frequency_penalty -> config.generationConfig.frequencyPenalty (nested)
|
|
1419
|
+
* - presence_penalty -> config.generationConfig.presencePenalty (nested)
|
|
1420
|
+
* - tool_choice -> config.toolConfig.functionCallingConfig.mode (nested)
|
|
1421
|
+
*
|
|
1422
|
+
* Not supported (silently skipped):
|
|
1423
|
+
* - response_format, metadata, prediction, parallel_tool_calls, user
|
|
1424
|
+
*
|
|
1425
|
+
* @internal Exported for unit testing only.
|
|
1426
|
+
*/
|
|
1427
|
+
function patchGooglePayload(payload, request) {
|
|
1428
|
+
let patched = false;
|
|
1429
|
+
const config = isRecord(payload["config"]) ? payload["config"] : void 0;
|
|
1430
|
+
if (config === void 0) return false;
|
|
1431
|
+
let genConfig = isRecord(config["generationConfig"]) ? config["generationConfig"] : void 0;
|
|
1432
|
+
if (request.top_p !== void 0) {
|
|
1433
|
+
genConfig ??= {};
|
|
1434
|
+
genConfig["topP"] = request.top_p;
|
|
1435
|
+
patched = true;
|
|
1436
|
+
}
|
|
1437
|
+
if (request.stop !== void 0) {
|
|
1438
|
+
genConfig ??= {};
|
|
1439
|
+
const sequences = Array.isArray(request.stop) ? request.stop : [request.stop];
|
|
1440
|
+
genConfig["stopSequences"] = sequences;
|
|
1441
|
+
patched = true;
|
|
1442
|
+
}
|
|
1443
|
+
if (request.seed !== void 0) {
|
|
1444
|
+
genConfig ??= {};
|
|
1445
|
+
genConfig["seed"] = request.seed;
|
|
1446
|
+
patched = true;
|
|
1447
|
+
}
|
|
1448
|
+
if (request.frequency_penalty !== void 0) {
|
|
1449
|
+
genConfig ??= {};
|
|
1450
|
+
genConfig["frequencyPenalty"] = request.frequency_penalty;
|
|
1451
|
+
patched = true;
|
|
1452
|
+
}
|
|
1453
|
+
if (request.presence_penalty !== void 0) {
|
|
1454
|
+
genConfig ??= {};
|
|
1455
|
+
genConfig["presencePenalty"] = request.presence_penalty;
|
|
1456
|
+
patched = true;
|
|
1457
|
+
}
|
|
1458
|
+
if (genConfig !== void 0 && patched) config["generationConfig"] = genConfig;
|
|
1459
|
+
const mode = translateToolChoiceForGoogle(request.tool_choice);
|
|
1460
|
+
if (mode !== void 0) {
|
|
1461
|
+
let toolConfig = isRecord(config["toolConfig"]) ? config["toolConfig"] : void 0;
|
|
1462
|
+
toolConfig ??= {};
|
|
1463
|
+
let funcConfig = isRecord(toolConfig["functionCallingConfig"]) ? toolConfig["functionCallingConfig"] : void 0;
|
|
1464
|
+
funcConfig ??= {};
|
|
1465
|
+
funcConfig["mode"] = mode;
|
|
1466
|
+
toolConfig["functionCallingConfig"] = funcConfig;
|
|
1467
|
+
config["toolConfig"] = toolConfig;
|
|
1468
|
+
patched = true;
|
|
1469
|
+
}
|
|
1470
|
+
return patched;
|
|
1471
|
+
}
|
|
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
|
+
/**
|
|
1495
|
+
* Collect API-specific payload fields from an OpenAI request.
|
|
1496
|
+
*
|
|
1497
|
+
* Dispatches to the appropriate translator based on the target API:
|
|
1498
|
+
* - OpenAI full passthrough: all standard fields (same names)
|
|
1499
|
+
* - Codex: restricted to tool_choice + parallel_tool_calls only
|
|
1500
|
+
* - Anthropic: translated field names and formats
|
|
1501
|
+
* - Google: nested generationConfig patching (handled separately in onPayload)
|
|
1502
|
+
* - Others (Bedrock): no passthrough
|
|
1503
|
+
*
|
|
1504
|
+
* For Google APIs, returns undefined (patching is done directly in onPayload
|
|
1505
|
+
* via patchGooglePayload because the payload structure is nested).
|
|
1506
|
+
*
|
|
1507
|
+
* @internal Exported for unit testing only.
|
|
1508
|
+
*/
|
|
1509
|
+
function collectPayloadFields(request, api) {
|
|
1510
|
+
if (OPENAI_FULL_PASSTHROUGH_APIS.has(api)) return collectOpenAIPayloadFields(request);
|
|
1511
|
+
if (CODEX_APIS.has(api)) return collectCodexPayloadFields(request);
|
|
1512
|
+
if (ANTHROPIC_APIS.has(api)) return collectAnthropicPayloadFields(request);
|
|
1513
|
+
}
|
|
1514
|
+
/**
|
|
1515
|
+
* Whether the given API requires Google-style nested payload patching.
|
|
1516
|
+
*/
|
|
1517
|
+
function isGoogleApi(api) {
|
|
1518
|
+
return GOOGLE_APIS.has(api);
|
|
1519
|
+
}
|
|
1520
|
+
/**
|
|
1321
1521
|
* Collect tool strict flags from the original OpenAI request.
|
|
1322
1522
|
*
|
|
1323
1523
|
* The pi SDK's `Tool` interface has no `strict` field, so the SDK always sets
|
|
@@ -1391,9 +1591,11 @@ async function buildStreamOptions(model, request, options) {
|
|
|
1391
1591
|
}
|
|
1392
1592
|
const payloadFields = collectPayloadFields(request, model.api);
|
|
1393
1593
|
const strictFlags = collectToolStrictFlags(request.tools);
|
|
1394
|
-
|
|
1594
|
+
const needsGooglePatch = isGoogleApi(model.api);
|
|
1595
|
+
if (payloadFields !== void 0 || strictFlags !== void 0 || needsGooglePatch) opts.onPayload = (payload) => {
|
|
1395
1596
|
if (isRecord(payload)) {
|
|
1396
1597
|
if (payloadFields !== void 0) for (const [key, value] of Object.entries(payloadFields)) payload[key] = value;
|
|
1598
|
+
if (needsGooglePatch) patchGooglePayload(payload, request);
|
|
1397
1599
|
if (strictFlags !== void 0) applyToolStrictFlags(payload, strictFlags);
|
|
1398
1600
|
}
|
|
1399
1601
|
return payload;
|
package/package.json
CHANGED