@victor-software-house/pi-openai-proxy 4.7.1 → 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 +181 -11
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1249,26 +1249,26 @@ const REASONING_EFFORT_MAP = {
|
|
|
1249
1249
|
/**
|
|
1250
1250
|
* APIs that use the OpenAI chat completions wire format and accept standard
|
|
1251
1251
|
* passthrough fields (stop, seed, top_p, tool_choice, etc.) in the payload.
|
|
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.
|
|
1256
1252
|
*/
|
|
1257
|
-
const
|
|
1253
|
+
const OPENAI_COMPATIBLE_APIS = new Set([
|
|
1258
1254
|
"openai-completions",
|
|
1259
1255
|
"openai-responses",
|
|
1260
1256
|
"azure-openai-responses",
|
|
1261
1257
|
"mistral-conversations"
|
|
1262
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
|
+
]);
|
|
1263
1265
|
/**
|
|
1264
|
-
* Collect fields
|
|
1265
|
-
*
|
|
1266
|
-
* Non-compatible APIs (Anthropic, Google, Bedrock, Codex) reject unknown fields.
|
|
1266
|
+
* Collect OpenAI-format fields for OpenAI-compatible APIs.
|
|
1267
|
+
* Fields are injected as flat top-level properties on the payload.
|
|
1267
1268
|
*
|
|
1268
1269
|
* @internal Exported for unit testing only.
|
|
1269
1270
|
*/
|
|
1270
|
-
function
|
|
1271
|
-
if (!OPENAI_COMPLETIONS_COMPATIBLE_APIS.has(api)) return;
|
|
1271
|
+
function collectOpenAIPayloadFields(request) {
|
|
1272
1272
|
const fields = {};
|
|
1273
1273
|
let hasFields = false;
|
|
1274
1274
|
if (request.stop !== void 0) {
|
|
@@ -1318,6 +1318,174 @@ function collectPayloadFields(request, api) {
|
|
|
1318
1318
|
return hasFields ? fields : void 0;
|
|
1319
1319
|
}
|
|
1320
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
|
+
/**
|
|
1321
1489
|
* Collect tool strict flags from the original OpenAI request.
|
|
1322
1490
|
*
|
|
1323
1491
|
* The pi SDK's `Tool` interface has no `strict` field, so the SDK always sets
|
|
@@ -1391,9 +1559,11 @@ async function buildStreamOptions(model, request, options) {
|
|
|
1391
1559
|
}
|
|
1392
1560
|
const payloadFields = collectPayloadFields(request, model.api);
|
|
1393
1561
|
const strictFlags = collectToolStrictFlags(request.tools);
|
|
1394
|
-
|
|
1562
|
+
const needsGooglePatch = isGoogleApi(model.api);
|
|
1563
|
+
if (payloadFields !== void 0 || strictFlags !== void 0 || needsGooglePatch) opts.onPayload = (payload) => {
|
|
1395
1564
|
if (isRecord(payload)) {
|
|
1396
1565
|
if (payloadFields !== void 0) for (const [key, value] of Object.entries(payloadFields)) payload[key] = value;
|
|
1566
|
+
if (needsGooglePatch) patchGooglePayload(payload, request);
|
|
1397
1567
|
if (strictFlags !== void 0) applyToolStrictFlags(payload, strictFlags);
|
|
1398
1568
|
}
|
|
1399
1569
|
return payload;
|
package/package.json
CHANGED