@victor-software-house/pi-openai-proxy 4.7.0 → 4.8.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 +188 -8
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1247,18 +1247,28 @@ const REASONING_EFFORT_MAP = {
|
|
|
1247
1247
|
xhigh: "xhigh"
|
|
1248
1248
|
};
|
|
1249
1249
|
/**
|
|
1250
|
-
* APIs
|
|
1251
|
-
*
|
|
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.
|
|
1252
1252
|
*/
|
|
1253
|
-
const
|
|
1253
|
+
const OPENAI_COMPATIBLE_APIS = new Set([
|
|
1254
|
+
"openai-completions",
|
|
1255
|
+
"openai-responses",
|
|
1256
|
+
"azure-openai-responses",
|
|
1257
|
+
"mistral-conversations"
|
|
1258
|
+
]);
|
|
1259
|
+
const ANTHROPIC_APIS = new Set(["anthropic-messages"]);
|
|
1260
|
+
const GOOGLE_APIS = new Set([
|
|
1261
|
+
"google-generative-ai",
|
|
1262
|
+
"google-gemini-cli",
|
|
1263
|
+
"google-vertex"
|
|
1264
|
+
]);
|
|
1254
1265
|
/**
|
|
1255
|
-
* Collect fields
|
|
1256
|
-
*
|
|
1266
|
+
* Collect OpenAI-format fields for OpenAI-compatible APIs.
|
|
1267
|
+
* Fields are injected as flat top-level properties on the payload.
|
|
1257
1268
|
*
|
|
1258
1269
|
* @internal Exported for unit testing only.
|
|
1259
1270
|
*/
|
|
1260
|
-
function
|
|
1261
|
-
if (SKIP_PAYLOAD_PASSTHROUGH_APIS.has(api)) return;
|
|
1271
|
+
function collectOpenAIPayloadFields(request) {
|
|
1262
1272
|
const fields = {};
|
|
1263
1273
|
let hasFields = false;
|
|
1264
1274
|
if (request.stop !== void 0) {
|
|
@@ -1308,6 +1318,174 @@ function collectPayloadFields(request, api) {
|
|
|
1308
1318
|
return hasFields ? fields : void 0;
|
|
1309
1319
|
}
|
|
1310
1320
|
/**
|
|
1321
|
+
* Translate OpenAI tool_choice to Anthropic tool_choice format.
|
|
1322
|
+
*
|
|
1323
|
+
* OpenAI "auto" -> { type: "auto" }
|
|
1324
|
+
* OpenAI "none" -> { type: "none" } (Anthropic skips tool calling)
|
|
1325
|
+
* OpenAI "required" -> { type: "any" } (force tool use)
|
|
1326
|
+
* OpenAI { type: "function", function: { name } } -> { type: "tool", name }
|
|
1327
|
+
*
|
|
1328
|
+
* @internal Exported for unit testing only.
|
|
1329
|
+
*/
|
|
1330
|
+
function translateToolChoiceForAnthropic(toolChoice) {
|
|
1331
|
+
if (toolChoice === void 0) return;
|
|
1332
|
+
if (toolChoice === "auto") return { type: "auto" };
|
|
1333
|
+
if (toolChoice === "none") return { type: "none" };
|
|
1334
|
+
if (toolChoice === "required") return { type: "any" };
|
|
1335
|
+
return {
|
|
1336
|
+
type: "tool",
|
|
1337
|
+
name: toolChoice.function.name
|
|
1338
|
+
};
|
|
1339
|
+
}
|
|
1340
|
+
/**
|
|
1341
|
+
* Collect Anthropic-format fields translated from the OpenAI request.
|
|
1342
|
+
*
|
|
1343
|
+
* Supported translations:
|
|
1344
|
+
* - top_p -> top_p (same name, natively supported)
|
|
1345
|
+
* - stop -> stop_sequences (different field name)
|
|
1346
|
+
* - tool_choice -> Anthropic tool_choice format (object with type)
|
|
1347
|
+
* - parallel_tool_calls: false -> disable_parallel_tool_use on tool_choice
|
|
1348
|
+
* - user -> metadata.user_id
|
|
1349
|
+
*
|
|
1350
|
+
* Not supported (silently skipped — these concepts don't exist in Anthropic):
|
|
1351
|
+
* - seed, frequency_penalty, presence_penalty, response_format, prediction, metadata (arbitrary keys)
|
|
1352
|
+
*
|
|
1353
|
+
* @internal Exported for unit testing only.
|
|
1354
|
+
*/
|
|
1355
|
+
function collectAnthropicPayloadFields(request) {
|
|
1356
|
+
const fields = {};
|
|
1357
|
+
let hasFields = false;
|
|
1358
|
+
if (request.top_p !== void 0) {
|
|
1359
|
+
fields["top_p"] = request.top_p;
|
|
1360
|
+
hasFields = true;
|
|
1361
|
+
}
|
|
1362
|
+
if (request.stop !== void 0) {
|
|
1363
|
+
fields["stop_sequences"] = Array.isArray(request.stop) ? request.stop : [request.stop];
|
|
1364
|
+
hasFields = true;
|
|
1365
|
+
}
|
|
1366
|
+
const toolChoice = translateToolChoiceForAnthropic(request.tool_choice);
|
|
1367
|
+
const disableParallel = request.parallel_tool_calls === false;
|
|
1368
|
+
if (toolChoice !== void 0) {
|
|
1369
|
+
if (disableParallel) toolChoice["disable_parallel_tool_use"] = true;
|
|
1370
|
+
fields["tool_choice"] = toolChoice;
|
|
1371
|
+
hasFields = true;
|
|
1372
|
+
} else if (disableParallel) {
|
|
1373
|
+
fields["tool_choice"] = {
|
|
1374
|
+
type: "auto",
|
|
1375
|
+
disable_parallel_tool_use: true
|
|
1376
|
+
};
|
|
1377
|
+
hasFields = true;
|
|
1378
|
+
}
|
|
1379
|
+
if (request.user !== void 0) {
|
|
1380
|
+
fields["metadata"] = { user_id: request.user };
|
|
1381
|
+
hasFields = true;
|
|
1382
|
+
}
|
|
1383
|
+
return hasFields ? fields : void 0;
|
|
1384
|
+
}
|
|
1385
|
+
/**
|
|
1386
|
+
* Translate OpenAI tool_choice to Google FunctionCallingConfigMode string.
|
|
1387
|
+
*
|
|
1388
|
+
* OpenAI "auto" -> "AUTO"
|
|
1389
|
+
* OpenAI "none" -> "NONE"
|
|
1390
|
+
* OpenAI "required" -> "ANY"
|
|
1391
|
+
* Named function choice -> "ANY" (Google doesn't support per-function forcing
|
|
1392
|
+
* in the same way, but ANY forces tool use)
|
|
1393
|
+
*/
|
|
1394
|
+
function translateToolChoiceForGoogle(toolChoice) {
|
|
1395
|
+
if (toolChoice === void 0) return;
|
|
1396
|
+
if (toolChoice === "auto") return "AUTO";
|
|
1397
|
+
if (toolChoice === "none") return "NONE";
|
|
1398
|
+
return "ANY";
|
|
1399
|
+
}
|
|
1400
|
+
/**
|
|
1401
|
+
* Patch Google's nested payload structure with translated fields.
|
|
1402
|
+
*
|
|
1403
|
+
* Google's payload shape: { model, contents, config: { generationConfig, toolConfig, ... } }
|
|
1404
|
+
* Fields go into config.generationConfig (camelCase) or config.toolConfig.
|
|
1405
|
+
*
|
|
1406
|
+
* Supported translations:
|
|
1407
|
+
* - top_p -> config.generationConfig.topP (camelCase, nested)
|
|
1408
|
+
* - stop -> config.generationConfig.stopSequences (array, nested)
|
|
1409
|
+
* - seed -> config.generationConfig.seed (nested)
|
|
1410
|
+
* - frequency_penalty -> config.generationConfig.frequencyPenalty (nested)
|
|
1411
|
+
* - presence_penalty -> config.generationConfig.presencePenalty (nested)
|
|
1412
|
+
* - tool_choice -> config.toolConfig.functionCallingConfig.mode (nested)
|
|
1413
|
+
*
|
|
1414
|
+
* Not supported (silently skipped):
|
|
1415
|
+
* - response_format, metadata, prediction, parallel_tool_calls, user
|
|
1416
|
+
*
|
|
1417
|
+
* @internal Exported for unit testing only.
|
|
1418
|
+
*/
|
|
1419
|
+
function patchGooglePayload(payload, request) {
|
|
1420
|
+
let patched = false;
|
|
1421
|
+
const config = isRecord(payload["config"]) ? payload["config"] : void 0;
|
|
1422
|
+
if (config === void 0) return false;
|
|
1423
|
+
let genConfig = isRecord(config["generationConfig"]) ? config["generationConfig"] : void 0;
|
|
1424
|
+
if (request.top_p !== void 0) {
|
|
1425
|
+
genConfig ??= {};
|
|
1426
|
+
genConfig["topP"] = request.top_p;
|
|
1427
|
+
patched = true;
|
|
1428
|
+
}
|
|
1429
|
+
if (request.stop !== void 0) {
|
|
1430
|
+
genConfig ??= {};
|
|
1431
|
+
const sequences = Array.isArray(request.stop) ? request.stop : [request.stop];
|
|
1432
|
+
genConfig["stopSequences"] = sequences;
|
|
1433
|
+
patched = true;
|
|
1434
|
+
}
|
|
1435
|
+
if (request.seed !== void 0) {
|
|
1436
|
+
genConfig ??= {};
|
|
1437
|
+
genConfig["seed"] = request.seed;
|
|
1438
|
+
patched = true;
|
|
1439
|
+
}
|
|
1440
|
+
if (request.frequency_penalty !== void 0) {
|
|
1441
|
+
genConfig ??= {};
|
|
1442
|
+
genConfig["frequencyPenalty"] = request.frequency_penalty;
|
|
1443
|
+
patched = true;
|
|
1444
|
+
}
|
|
1445
|
+
if (request.presence_penalty !== void 0) {
|
|
1446
|
+
genConfig ??= {};
|
|
1447
|
+
genConfig["presencePenalty"] = request.presence_penalty;
|
|
1448
|
+
patched = true;
|
|
1449
|
+
}
|
|
1450
|
+
if (genConfig !== void 0 && patched) config["generationConfig"] = genConfig;
|
|
1451
|
+
const mode = translateToolChoiceForGoogle(request.tool_choice);
|
|
1452
|
+
if (mode !== void 0) {
|
|
1453
|
+
let toolConfig = isRecord(config["toolConfig"]) ? config["toolConfig"] : void 0;
|
|
1454
|
+
toolConfig ??= {};
|
|
1455
|
+
let funcConfig = isRecord(toolConfig["functionCallingConfig"]) ? toolConfig["functionCallingConfig"] : void 0;
|
|
1456
|
+
funcConfig ??= {};
|
|
1457
|
+
funcConfig["mode"] = mode;
|
|
1458
|
+
toolConfig["functionCallingConfig"] = funcConfig;
|
|
1459
|
+
config["toolConfig"] = toolConfig;
|
|
1460
|
+
patched = true;
|
|
1461
|
+
}
|
|
1462
|
+
return patched;
|
|
1463
|
+
}
|
|
1464
|
+
/**
|
|
1465
|
+
* Collect API-specific payload fields from an OpenAI request.
|
|
1466
|
+
*
|
|
1467
|
+
* Dispatches to the appropriate translator based on the target API:
|
|
1468
|
+
* - OpenAI-compatible: flat field injection (same names)
|
|
1469
|
+
* - Anthropic: translated field names and formats
|
|
1470
|
+
* - Google: nested generationConfig patching (handled separately in onPayload)
|
|
1471
|
+
* - Others (Bedrock, Codex): no passthrough
|
|
1472
|
+
*
|
|
1473
|
+
* For Google APIs, returns undefined (patching is done directly in onPayload
|
|
1474
|
+
* via patchGooglePayload because the payload structure is nested).
|
|
1475
|
+
*
|
|
1476
|
+
* @internal Exported for unit testing only.
|
|
1477
|
+
*/
|
|
1478
|
+
function collectPayloadFields(request, api) {
|
|
1479
|
+
if (OPENAI_COMPATIBLE_APIS.has(api)) return collectOpenAIPayloadFields(request);
|
|
1480
|
+
if (ANTHROPIC_APIS.has(api)) return collectAnthropicPayloadFields(request);
|
|
1481
|
+
}
|
|
1482
|
+
/**
|
|
1483
|
+
* Whether the given API requires Google-style nested payload patching.
|
|
1484
|
+
*/
|
|
1485
|
+
function isGoogleApi(api) {
|
|
1486
|
+
return GOOGLE_APIS.has(api);
|
|
1487
|
+
}
|
|
1488
|
+
/**
|
|
1311
1489
|
* Collect tool strict flags from the original OpenAI request.
|
|
1312
1490
|
*
|
|
1313
1491
|
* The pi SDK's `Tool` interface has no `strict` field, so the SDK always sets
|
|
@@ -1381,9 +1559,11 @@ async function buildStreamOptions(model, request, options) {
|
|
|
1381
1559
|
}
|
|
1382
1560
|
const payloadFields = collectPayloadFields(request, model.api);
|
|
1383
1561
|
const strictFlags = collectToolStrictFlags(request.tools);
|
|
1384
|
-
|
|
1562
|
+
const needsGooglePatch = isGoogleApi(model.api);
|
|
1563
|
+
if (payloadFields !== void 0 || strictFlags !== void 0 || needsGooglePatch) opts.onPayload = (payload) => {
|
|
1385
1564
|
if (isRecord(payload)) {
|
|
1386
1565
|
if (payloadFields !== void 0) for (const [key, value] of Object.entries(payloadFields)) payload[key] = value;
|
|
1566
|
+
if (needsGooglePatch) patchGooglePayload(payload, request);
|
|
1387
1567
|
if (strictFlags !== void 0) applyToolStrictFlags(payload, strictFlags);
|
|
1388
1568
|
}
|
|
1389
1569
|
return payload;
|
package/package.json
CHANGED