@jeffusion/bungee-llms 5.22.0 → 5.23.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.js +169 -17
- package/dist/plugin-api.d.ts +1 -0
- package/dist/plugin-api.js +376 -206
- package/dist/protocol-session/endpoint.d.ts +3 -0
- package/dist/protocol-session/types.d.ts +2 -0
- package/dist/responses-codec/agent-messages.d.ts +33 -0
- package/dist/responses-codec/common.d.ts +6 -1
- package/dist/responses-codec/events.d.ts +2 -0
- package/dist/responses-codec/index.d.ts +1 -0
- package/dist/responses-codec/result.d.ts +4 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -3465,7 +3465,13 @@ function normalizeUsage(raw, protocol, prior = {}) {
|
|
|
3465
3465
|
output_tokens_details: { reasoning_tokens: tokens(reasoning ?? prior.output_tokens_details?.reasoning_tokens, "reasoning_tokens") }
|
|
3466
3466
|
};
|
|
3467
3467
|
}
|
|
3468
|
-
function
|
|
3468
|
+
function isCollaborationFunction(name, namespace, custom = false) {
|
|
3469
|
+
return !custom && namespace === "collaboration" && typeof name === "string" && ["spawn_agent", "send_message", "followup_task"].includes(name);
|
|
3470
|
+
}
|
|
3471
|
+
function plaintextCollaborationCall(item, delivery) {
|
|
3472
|
+
return delivery === "plaintext" && item.type === "function_call" && isCollaborationFunction(item.name, item.namespace) ? { ...item, encrypted_function_args: [] } : item;
|
|
3473
|
+
}
|
|
3474
|
+
function toolItem(id, name, args, names, status = "completed", agentMessageDelivery) {
|
|
3469
3475
|
const original = restoreTool(name, names);
|
|
3470
3476
|
const item = { id: `fc_${id}`, type: original.custom ? "custom_tool_call" : "function_call", call_id: id, name: original.name, status };
|
|
3471
3477
|
if (original.namespace)
|
|
@@ -3476,7 +3482,7 @@ function toolItem(id, name, args, names, status = "completed") {
|
|
|
3476
3482
|
argumentObject(args);
|
|
3477
3483
|
item.arguments = args;
|
|
3478
3484
|
}
|
|
3479
|
-
return item;
|
|
3485
|
+
return plaintextCollaborationCall(item, agentMessageDelivery);
|
|
3480
3486
|
}
|
|
3481
3487
|
function responsePayload(id, model, output, end, usage, error, createdAt = Math.floor(Date.now() / 1000)) {
|
|
3482
3488
|
const result = { id, object: "response", created_at: createdAt, model, status: end.status, output, usage: Object.keys(usage).length ? usage : null };
|
|
@@ -3487,6 +3493,114 @@ function responsePayload(id, model, output, end, usage, error, createdAt = Math.
|
|
|
3487
3493
|
return result;
|
|
3488
3494
|
}
|
|
3489
3495
|
|
|
3496
|
+
// src/responses-codec/agent-messages.ts
|
|
3497
|
+
function exact(value, keys, path) {
|
|
3498
|
+
for (const key of Object.keys(value))
|
|
3499
|
+
if (!keys.includes(key))
|
|
3500
|
+
fail("unsupported_content", "Unknown agent message field", `${path}.${key}`);
|
|
3501
|
+
}
|
|
3502
|
+
function validateAgentMessage(raw, path, configuredLimits, allowEncrypted = false) {
|
|
3503
|
+
const budget = limits(configuredLimits);
|
|
3504
|
+
const item = atParam(path, () => record(raw, "agent message"));
|
|
3505
|
+
exact(item, ["type", "id", "author", "recipient", "content", "internal_chat_message_metadata_passthrough"], path);
|
|
3506
|
+
if (item.type !== "agent_message")
|
|
3507
|
+
fail("unsupported_content", "Expected agent message", `${path}.type`);
|
|
3508
|
+
if (item.id !== undefined)
|
|
3509
|
+
atParam(`${path}.id`, () => string(item.id, "agent message id", true));
|
|
3510
|
+
if (item.internal_chat_message_metadata_passthrough !== undefined)
|
|
3511
|
+
atParam(`${path}.internal_chat_message_metadata_passthrough`, () => record(item.internal_chat_message_metadata_passthrough, "message metadata"));
|
|
3512
|
+
for (const key of ["author", "recipient"])
|
|
3513
|
+
atParam(`${path}.${key}`, () => string(item[key], "agent identity", true));
|
|
3514
|
+
const content = atParam(`${path}.content`, () => list(item.content, "agent content"));
|
|
3515
|
+
if (content.length > budget.maxItems)
|
|
3516
|
+
fail("resource_limit", "Too many agent content parts", `${path}.content`);
|
|
3517
|
+
for (const [index, rawPart] of content.entries()) {
|
|
3518
|
+
const partPath = `${path}.content[${index}]`;
|
|
3519
|
+
const part = atParam(partPath, () => record(rawPart, "agent content part"));
|
|
3520
|
+
if (part.type === "encrypted_content") {
|
|
3521
|
+
exact(part, ["type", "encrypted_content"], partPath);
|
|
3522
|
+
atParam(`${partPath}.encrypted_content`, () => string(part.encrypted_content, "encrypted content", true));
|
|
3523
|
+
if (!allowEncrypted)
|
|
3524
|
+
fail("unsupported_content", "Encrypted agent content requires explicit plaintext replacement", `${partPath}.encrypted_content`);
|
|
3525
|
+
} else {
|
|
3526
|
+
exact(part, ["type", "text"], partPath);
|
|
3527
|
+
if (part.type !== "input_text")
|
|
3528
|
+
fail("unsupported_content", "Agent messages require input text", `${partPath}.type`);
|
|
3529
|
+
atParam(`${partPath}.text`, () => string(part.text, "agent input text"));
|
|
3530
|
+
}
|
|
3531
|
+
}
|
|
3532
|
+
atParam(path, () => serialized(item, budget.maxRequestBytes, "agent message"));
|
|
3533
|
+
return item;
|
|
3534
|
+
}
|
|
3535
|
+
function inspectAgentMessages(raw, options = {}) {
|
|
3536
|
+
const budget = limits(options.limits);
|
|
3537
|
+
const body = atParam("request", () => record(raw, "request"));
|
|
3538
|
+
atParam("request", () => serialized(body, budget.maxRequestBytes, "request"));
|
|
3539
|
+
const input = typeof body.input === "string" ? [] : atParam("input", () => list(body.input, "input"));
|
|
3540
|
+
if (input.length > budget.maxItems)
|
|
3541
|
+
fail("resource_limit", "Too many input items", "input");
|
|
3542
|
+
const messages = [], diagnostics = [];
|
|
3543
|
+
let parts = 0;
|
|
3544
|
+
for (const [inputIndex, rawItem] of input.entries()) {
|
|
3545
|
+
const item = atParam(`input[${inputIndex}]`, () => record(rawItem, "input item"));
|
|
3546
|
+
if (item.type !== "agent_message")
|
|
3547
|
+
continue;
|
|
3548
|
+
validateAgentMessage(item, `input[${inputIndex}]`, options.limits, true);
|
|
3549
|
+
const encryptedContents = [];
|
|
3550
|
+
for (const [contentIndex, part] of item.content.entries()) {
|
|
3551
|
+
if (++parts > budget.maxItems)
|
|
3552
|
+
fail("resource_limit", "Too many agent content parts", "input");
|
|
3553
|
+
if (part.type === "encrypted_content") {
|
|
3554
|
+
const param = `input[${inputIndex}].content[${contentIndex}].encrypted_content`;
|
|
3555
|
+
encryptedContents.push({ contentIndex, param });
|
|
3556
|
+
}
|
|
3557
|
+
}
|
|
3558
|
+
messages.push({ inputIndex, author: item.author, recipient: item.recipient, encryptedContents });
|
|
3559
|
+
}
|
|
3560
|
+
return { messages, diagnostics };
|
|
3561
|
+
}
|
|
3562
|
+
function replaceAgentMessageEncryptedContent(raw, replacements, options = {}) {
|
|
3563
|
+
const inspection = inspectAgentMessages(raw, options);
|
|
3564
|
+
const budget = limits(options.limits);
|
|
3565
|
+
if (!Array.isArray(replacements))
|
|
3566
|
+
fail("invalid_payload", "Replacements must be an array", "replacements");
|
|
3567
|
+
if (replacements.length > budget.maxItems)
|
|
3568
|
+
fail("resource_limit", "Too many replacements", "replacements");
|
|
3569
|
+
const locations = new Map(inspection.messages.flatMap((m) => m.encryptedContents.map((c) => [c.param, { inputIndex: m.inputIndex, contentIndex: c.contentIndex }])));
|
|
3570
|
+
const selected = new Set;
|
|
3571
|
+
let body;
|
|
3572
|
+
try {
|
|
3573
|
+
body = structuredClone(record(raw, "request"));
|
|
3574
|
+
} catch {
|
|
3575
|
+
fail("invalid_payload", "Request must be cloneable JSON", "request");
|
|
3576
|
+
}
|
|
3577
|
+
const diagnostics = [];
|
|
3578
|
+
for (const [i, rawReplacement] of replacements.entries()) {
|
|
3579
|
+
const replacement = atParam(`replacements[${i}]`, () => record(rawReplacement, "replacement"));
|
|
3580
|
+
exact(replacement, ["param", "text"], `replacements[${i}]`);
|
|
3581
|
+
const param = atParam(`replacements[${i}].param`, () => string(replacement.param, "replacement param", true));
|
|
3582
|
+
const text = atParam(`replacements[${i}].text`, () => string(replacement.text, "replacement text"));
|
|
3583
|
+
const location = locations.get(param);
|
|
3584
|
+
if (!location || selected.has(param))
|
|
3585
|
+
fail("invalid_payload", "Replacement must identify one unique encrypted agent content block", `replacements[${i}].param`);
|
|
3586
|
+
selected.add(param);
|
|
3587
|
+
record(body.input[location.inputIndex], "agent message").content[location.contentIndex] = { type: "input_text", text };
|
|
3588
|
+
diagnostics.push({ param, action: "mapped", reason: "agent_message_plaintext_replacement" });
|
|
3589
|
+
}
|
|
3590
|
+
atParam("request", () => serialized(body, budget.maxRequestBytes, "request"));
|
|
3591
|
+
return { body, diagnostics };
|
|
3592
|
+
}
|
|
3593
|
+
function validateEncryptedFunctionArgs(item, delivery, path) {
|
|
3594
|
+
if (item.encrypted_function_args === undefined)
|
|
3595
|
+
return;
|
|
3596
|
+
const param = `${path}.encrypted_function_args`;
|
|
3597
|
+
if (delivery !== "plaintext" || !isCollaborationFunction(item.name, item.namespace, item.type !== "function_call"))
|
|
3598
|
+
fail("unsupported_content", "Encrypted function argument marker is unavailable for this tool", param);
|
|
3599
|
+
const values = atParam(param, () => list(item.encrypted_function_args, "encrypted function arguments"));
|
|
3600
|
+
if (values.length)
|
|
3601
|
+
fail("unsupported_content", "Encrypted function arguments cannot cross protocols", param);
|
|
3602
|
+
}
|
|
3603
|
+
|
|
3490
3604
|
// src/responses-codec/request-preferences.ts
|
|
3491
3605
|
function fields(value, allowed, path) {
|
|
3492
3606
|
for (const key of Object.keys(value))
|
|
@@ -3620,6 +3734,8 @@ function definitionKey(value) {
|
|
|
3620
3734
|
}
|
|
3621
3735
|
function decodeResponsesRequest(raw, protocol, capabilities = {}) {
|
|
3622
3736
|
const body = record(raw, "Responses request");
|
|
3737
|
+
if (capabilities.agentMessageDelivery !== undefined && capabilities.agentMessageDelivery !== "plaintext")
|
|
3738
|
+
fail("invalid_payload", "Unknown agent message delivery policy", "agentMessageDelivery");
|
|
3623
3739
|
const budget = limits(capabilities.limits);
|
|
3624
3740
|
serialized(body, budget.maxRequestBytes, "request");
|
|
3625
3741
|
const supported = new Set([
|
|
@@ -3744,7 +3860,18 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}) {
|
|
|
3744
3860
|
}
|
|
3745
3861
|
const wire = register(name, namespace, custom);
|
|
3746
3862
|
definitions.set(key, signature);
|
|
3747
|
-
|
|
3863
|
+
let parameters = custom ? { type: "object", properties: { input: { type: "string" } }, required: ["input"], additionalProperties: false } : record(tool.parameters ?? { type: "object", properties: {} }, "tool parameters");
|
|
3864
|
+
if (capabilities.agentMessageDelivery === "plaintext" && isCollaborationFunction(name, namespace, custom)) {
|
|
3865
|
+
const properties = parameters.properties;
|
|
3866
|
+
if (properties && typeof properties === "object" && !Array.isArray(properties)) {
|
|
3867
|
+
const message = properties.message;
|
|
3868
|
+
if (message && typeof message === "object" && !Array.isArray(message) && message.encrypted === true) {
|
|
3869
|
+
const { encrypted: _encrypted, ...plaintext } = message;
|
|
3870
|
+
parameters = { ...parameters, properties: { ...properties, message: plaintext } };
|
|
3871
|
+
diagnostics.push({ param: `${path}.parameters.properties.message.encrypted`, action: "omitted", reason: "collaboration_message_plaintext_delivery" });
|
|
3872
|
+
}
|
|
3873
|
+
}
|
|
3874
|
+
}
|
|
3748
3875
|
const declaredDescription = tool.description === undefined ? undefined : string(tool.description, "tool description");
|
|
3749
3876
|
const identityHint = capabilities.preserveToolIdentityDescription ? `Original tool identity: ${JSON.stringify({ name, ...namespace ? { namespace } : {} })}` : undefined;
|
|
3750
3877
|
const description = [identityHint, namespaceDescription, declaredDescription].filter((value) => value !== undefined).join(`
|
|
@@ -3849,7 +3976,20 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}) {
|
|
|
3849
3976
|
const item = record(rawItem, "input item");
|
|
3850
3977
|
if (item.type === "additional_tools")
|
|
3851
3978
|
return;
|
|
3852
|
-
if (item.
|
|
3979
|
+
if (item.type === "agent_message") {
|
|
3980
|
+
validateAgentMessage(item, messageParam, capabilities.limits);
|
|
3981
|
+
if (capabilities.agentMessageDelivery !== "plaintext")
|
|
3982
|
+
fail("unsupported_content", "Agent messages require explicit plaintext delivery", messageParam);
|
|
3983
|
+
if (pendingReasoning)
|
|
3984
|
+
fail("unsupported_reasoning", "Reasoning must precede an assistant item", messageParam);
|
|
3985
|
+
addMessage({ role: "user", content: [{ type: "text", text: `[Agent message metadata: ${JSON.stringify({ author: item.author, recipient: item.recipient })}]
|
|
3986
|
+
` }, ...item.content.map((part) => ({ type: "text", text: part.text }))] });
|
|
3987
|
+
diagnostics.push({ param: messageParam, action: "mapped", reason: "agent_message_plaintext_delivery" });
|
|
3988
|
+
return;
|
|
3989
|
+
}
|
|
3990
|
+
if (item.encrypted_content != null)
|
|
3991
|
+
fail("unsupported_content", "Encrypted content cannot cross protocols", `${messageParam}.encrypted_content`);
|
|
3992
|
+
if (item.type === "compaction")
|
|
3853
3993
|
fail("unsupported_content", "Encrypted reasoning and compaction cannot be restored");
|
|
3854
3994
|
if (item.type === "item_reference")
|
|
3855
3995
|
fail("unresolved_reference", "Input item references must be resolved before conversion");
|
|
@@ -3869,6 +4009,7 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}) {
|
|
|
3869
4009
|
}
|
|
3870
4010
|
if (item.type === "function_call" || item.type === "custom_tool_call") {
|
|
3871
4011
|
const custom = item.type === "custom_tool_call";
|
|
4012
|
+
validateEncryptedFunctionArgs(item, capabilities.agentMessageDelivery, messageParam);
|
|
3872
4013
|
const id = string(item.call_id, "call_id", true);
|
|
3873
4014
|
if (callIds.has(id))
|
|
3874
4015
|
fail("invalid_tool_history", "Duplicate tool call identifier");
|
|
@@ -4087,9 +4228,9 @@ class ResponsesEventEncoder {
|
|
|
4087
4228
|
item(state, status, strict = false) {
|
|
4088
4229
|
if (state.kind === "tool") {
|
|
4089
4230
|
if (strict)
|
|
4090
|
-
return { ...toolItem(state.callId, state.name, state.args || "{}", this.toolNames, status), id: state.id };
|
|
4231
|
+
return { ...toolItem(state.callId, state.name, state.args || "{}", this.toolNames, status, this.options.agentMessageDelivery), id: state.id };
|
|
4091
4232
|
const original = state.original ?? restoreTool(state.name, this.toolNames);
|
|
4092
|
-
return {
|
|
4233
|
+
return plaintextCollaborationCall({
|
|
4093
4234
|
id: state.id,
|
|
4094
4235
|
type: original.custom ? "custom_tool_call" : "function_call",
|
|
4095
4236
|
call_id: state.callId,
|
|
@@ -4097,7 +4238,7 @@ class ResponsesEventEncoder {
|
|
|
4097
4238
|
...original.namespace ? { namespace: original.namespace } : {},
|
|
4098
4239
|
status,
|
|
4099
4240
|
...original.custom ? { input: state.emittedInput } : { arguments: state.args }
|
|
4100
|
-
};
|
|
4241
|
+
}, this.options.agentMessageDelivery);
|
|
4101
4242
|
}
|
|
4102
4243
|
if (state.kind === "reasoning")
|
|
4103
4244
|
return { id: state.id, type: "reasoning", summary: status === "in_progress" ? [] : [{ type: "summary_text", text: state.text }] };
|
|
@@ -4410,13 +4551,13 @@ class ResponsesEventEncoder {
|
|
|
4410
4551
|
}
|
|
4411
4552
|
|
|
4412
4553
|
// src/responses-codec/result.ts
|
|
4413
|
-
function encodeResponsesResult(raw, protocol, model, toolNames = new Map, configuredLimits) {
|
|
4554
|
+
function encodeResponsesResult(raw, protocol, model, toolNames = new Map, configuredLimits, options = {}) {
|
|
4414
4555
|
const body = record(raw, "upstream response");
|
|
4415
4556
|
const budget = limits(configuredLimits);
|
|
4416
4557
|
serialized(body, budget.maxOutputBytes, "upstream response");
|
|
4417
4558
|
if (body.error !== undefined || body.type === "error")
|
|
4418
4559
|
return responsePayload(`resp_${crypto.randomUUID().replaceAll("-", "")}`, model, [], { status: "failed", reason: "upstream_error" }, {}, record(body.error, "upstream error"));
|
|
4419
|
-
const encoder = new ResponsesEventEncoder(protocol, model, toolNames, configuredLimits);
|
|
4560
|
+
const encoder = new ResponsesEventEncoder(protocol, model, toolNames, configuredLimits, options);
|
|
4420
4561
|
if (protocol === "chat_completions") {
|
|
4421
4562
|
const choices = list(body.choices, "choices");
|
|
4422
4563
|
if (choices.length !== 1)
|
|
@@ -4834,7 +4975,7 @@ function canonicalRequest(raw, protocol, model) {
|
|
|
4834
4975
|
}
|
|
4835
4976
|
return out;
|
|
4836
4977
|
}
|
|
4837
|
-
function validateCanonical(b) {
|
|
4978
|
+
function validateCanonical(b, context) {
|
|
4838
4979
|
for (const key of ["stream", "parallel_tool_calls", "store", "background"])
|
|
4839
4980
|
if (b[key] !== undefined)
|
|
4840
4981
|
bool(b[key], key);
|
|
@@ -4864,7 +5005,11 @@ function validateCanonical(b) {
|
|
|
4864
5005
|
const input = typeof b.input === "string" ? [] : list(b.input, "input");
|
|
4865
5006
|
for (const [i, raw] of input.entries()) {
|
|
4866
5007
|
const t = record(raw, "input item"), p = `input[${i}]`;
|
|
4867
|
-
|
|
5008
|
+
if (t.type === "agent_message") {
|
|
5009
|
+
validateAgentMessage(t, p, context.capabilities?.limits);
|
|
5010
|
+
continue;
|
|
5011
|
+
}
|
|
5012
|
+
const allowed = t.type === "function_call" ? ["type", "id", "status", "call_id", "name", "namespace", "arguments", "encrypted_function_args"] : t.type === "custom_tool_call" ? ["type", "id", "status", "call_id", "name", "namespace", "input"] : t.type === "function_call_output" || t.type === "custom_tool_call_output" ? ["type", "id", "call_id", "output", "is_error"] : t.type === "reasoning" ? ["type", "id", "status", "summary", "content", "encrypted_content", "internal_chat_message_metadata_passthrough"] : t.type === "additional_tools" ? ["type", "id", "role", "tools"] : ["type", "id", "status", "role", "content", "internal_chat_message_metadata_passthrough"];
|
|
4868
5013
|
fields2(t, [...allowed, "internal_chat_message_metadata_passthrough"], p, "unsupported_content");
|
|
4869
5014
|
if (t.is_error !== undefined)
|
|
4870
5015
|
bool(t.is_error, `${p}.is_error`);
|
|
@@ -4914,8 +5059,8 @@ function convertProtocolRequest(raw, context) {
|
|
|
4914
5059
|
fail("invalid_payload", "Request stream mode disagrees with transport context", "stream");
|
|
4915
5060
|
b.stream = context.streaming;
|
|
4916
5061
|
}
|
|
4917
|
-
validateCanonical(b);
|
|
4918
|
-
const cap = { ...context.capabilities, omitOptionalWebSearch: true, preserveToolIdentityDescription: true, preserveRefusalContent: true }, policy = context.reasoningPolicy;
|
|
5062
|
+
validateCanonical(b, context);
|
|
5063
|
+
const cap = { ...context.capabilities, agentMessageDelivery: context.agentMessageDelivery ?? context.capabilities?.agentMessageDelivery, omitOptionalWebSearch: true, preserveToolIdentityDescription: true, preserveRefusalContent: true }, policy = context.reasoningPolicy;
|
|
4919
5064
|
const reasoning = b.reasoning === undefined ? undefined : record(b.reasoning, "reasoning");
|
|
4920
5065
|
const sourceThinking = b.sourceThinking;
|
|
4921
5066
|
delete b.sourceThinking;
|
|
@@ -5202,7 +5347,7 @@ function validateCanonicalResponse(raw, context, itemOnly = false) {
|
|
|
5202
5347
|
fail("unsupported_response", "Unknown item status", `${path}.status`);
|
|
5203
5348
|
if (b.status === "completed" && t.status !== undefined && t.status !== "completed")
|
|
5204
5349
|
fail("invalid_stream", "Completed response contains an unfinished item", `${path}.status`);
|
|
5205
|
-
fields2(t, t.type === "message" ? ["type", "id", "role", "status", "content"] : t.type === "reasoning" ? ["type", "id", "summary", "status", "encrypted_content", "content"] : t.type === "function_call" ? ["type", "id", "status", "name", "namespace", "call_id", "arguments"] : ["type", "id", "status", "name", "namespace", "call_id", "input"], path, "unsupported_content");
|
|
5350
|
+
fields2(t, t.type === "message" ? ["type", "id", "role", "status", "content"] : t.type === "reasoning" ? ["type", "id", "summary", "status", "encrypted_content", "content"] : t.type === "function_call" ? ["type", "id", "status", "name", "namespace", "call_id", "arguments", "encrypted_function_args"] : ["type", "id", "status", "name", "namespace", "call_id", "input"], path, "unsupported_content");
|
|
5206
5351
|
if (t.type === "message") {
|
|
5207
5352
|
if (t.role !== "assistant")
|
|
5208
5353
|
fail("unsupported_content", "Output message must be assistant", `${path}.role`);
|
|
@@ -5229,6 +5374,7 @@ function validateCanonicalResponse(raw, context, itemOnly = false) {
|
|
|
5229
5374
|
string(p.text, "summary");
|
|
5230
5375
|
}
|
|
5231
5376
|
} else if (t.type === "function_call" || t.type === "custom_tool_call") {
|
|
5377
|
+
validateEncryptedFunctionArgs(t, context.capabilities?.agentMessageDelivery, path);
|
|
5232
5378
|
const id = string(t.call_id, "call_id", true);
|
|
5233
5379
|
if (ids.has(id))
|
|
5234
5380
|
fail("invalid_tool_arguments", "Duplicate output tool call id", path);
|
|
@@ -5439,7 +5585,7 @@ function canonicalResponse(raw, context, names) {
|
|
|
5439
5585
|
return record(events.at(-1)?.response, "response");
|
|
5440
5586
|
}
|
|
5441
5587
|
validateProviderResponse(raw, context.targetProtocol);
|
|
5442
|
-
const result = encodeResponsesResult(raw, context.targetProtocol, context.responseModel ?? context.model, names, context.capabilities?.limits);
|
|
5588
|
+
const result = encodeResponsesResult(raw, context.targetProtocol, context.responseModel ?? context.model, names, context.capabilities?.limits, { agentMessageDelivery: context.capabilities?.agentMessageDelivery });
|
|
5443
5589
|
if (result.status === "failed")
|
|
5444
5590
|
result.error = safeProtocolError();
|
|
5445
5591
|
const usage = record(raw, "response").usage;
|
|
@@ -5834,6 +5980,10 @@ function createProtocolSession(context) {
|
|
|
5834
5980
|
capabilities: context.capabilities ? { ...context.capabilities, limits: context.capabilities.limits ? { ...context.capabilities.limits } : undefined } : undefined,
|
|
5835
5981
|
reasoningPolicy: context.reasoningPolicy ? { ...context.reasoningPolicy, effortMap: context.reasoningPolicy.effortMap ? { ...context.reasoningPolicy.effortMap } : undefined } : undefined
|
|
5836
5982
|
};
|
|
5983
|
+
const delivery = context.agentMessageDelivery ?? context.capabilities?.agentMessageDelivery;
|
|
5984
|
+
if (delivery !== undefined && delivery !== "plaintext")
|
|
5985
|
+
fail("invalid_payload", "Unknown agent message delivery policy", "agentMessageDelivery");
|
|
5986
|
+
context.capabilities = { ...context.capabilities, agentMessageDelivery: delivery };
|
|
5837
5987
|
if (!LLM_PROTOCOLS.includes(context.sourceProtocol) || !LLM_PROTOCOLS.includes(context.targetProtocol))
|
|
5838
5988
|
fail("unsupported_protocol", "Unknown protocol");
|
|
5839
5989
|
if (typeof context.model !== "string" || !context.model)
|
|
@@ -5872,7 +6022,7 @@ function createProtocolSession(context) {
|
|
|
5872
6022
|
else if (context.targetProtocol === "gemini_generate_content")
|
|
5873
6023
|
encoder = new GeminiCanonicalDecoder(context, names);
|
|
5874
6024
|
else
|
|
5875
|
-
encoder = new ResponsesEventEncoder(context.targetProtocol, context.responseModel ?? context.model, names, context.capabilities?.limits, { preserveContentOrder: true });
|
|
6025
|
+
encoder = new ResponsesEventEncoder(context.targetProtocol, context.responseModel ?? context.model, names, context.capabilities?.limits, { preserveContentOrder: true, agentMessageDelivery: context.capabilities?.agentMessageDelivery });
|
|
5876
6026
|
validator = context.targetProtocol === "responses" ? undefined : new CanonicalStreamValidator(context);
|
|
5877
6027
|
renderer = new CanonicalStreamRenderer(context.sourceProtocol, context);
|
|
5878
6028
|
};
|
|
@@ -6086,7 +6236,9 @@ export {
|
|
|
6086
6236
|
encodeResponsesResult,
|
|
6087
6237
|
finalizeTokenAccountingEvent,
|
|
6088
6238
|
getProviderTokenAccountingCapabilities,
|
|
6239
|
+
inspectAgentMessages,
|
|
6089
6240
|
isRecord5 as isRecord,
|
|
6090
6241
|
readNumber,
|
|
6091
|
-
readRecord
|
|
6242
|
+
readRecord,
|
|
6243
|
+
replaceAgentMessageEncryptedContent
|
|
6092
6244
|
};
|
package/dist/plugin-api.d.ts
CHANGED
|
@@ -2,3 +2,4 @@ export { TOKEN_ACCOUNTING_AUTHORITIES, assertCanonicalTokenAccountingEventV2, cr
|
|
|
2
2
|
export type { CanonicalTokenAccountingEventV2 } from './token-accounting/types';
|
|
3
3
|
export * from './responses-codec';
|
|
4
4
|
export * from './protocol-session';
|
|
5
|
+
export { protocolRequestPath } from './protocol-session/endpoint';
|
package/dist/plugin-api.js
CHANGED
|
@@ -1757,6 +1757,311 @@ function createTokenAccountingSession(input, options = {}) {
|
|
|
1757
1757
|
}
|
|
1758
1758
|
};
|
|
1759
1759
|
}
|
|
1760
|
+
// src/responses-codec/common.ts
|
|
1761
|
+
var DEFAULT_RESPONSES_CODEC_LIMITS = Object.freeze({
|
|
1762
|
+
maxRequestBytes: 8 * 1024 * 1024,
|
|
1763
|
+
maxOutputBytes: 8 * 1024 * 1024,
|
|
1764
|
+
maxArgumentBytes: 1024 * 1024,
|
|
1765
|
+
maxItems: 1024
|
|
1766
|
+
});
|
|
1767
|
+
|
|
1768
|
+
class ResponsesCodecError extends Error {
|
|
1769
|
+
code;
|
|
1770
|
+
param;
|
|
1771
|
+
constructor(code, message, param) {
|
|
1772
|
+
super(message);
|
|
1773
|
+
this.code = code;
|
|
1774
|
+
this.param = param;
|
|
1775
|
+
this.name = "ResponsesCodecError";
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
function fail(code, message, param) {
|
|
1779
|
+
throw new ResponsesCodecError(code, message, param);
|
|
1780
|
+
}
|
|
1781
|
+
function atParam(param, run) {
|
|
1782
|
+
try {
|
|
1783
|
+
return run();
|
|
1784
|
+
} catch (error) {
|
|
1785
|
+
if (error instanceof ResponsesCodecError && error.param === undefined)
|
|
1786
|
+
throw new ResponsesCodecError(error.code, error.message, param);
|
|
1787
|
+
throw error;
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
function record(value, where) {
|
|
1791
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
1792
|
+
fail("invalid_payload", `${where} must be an object`);
|
|
1793
|
+
return value;
|
|
1794
|
+
}
|
|
1795
|
+
function string(value, where, nonempty = false) {
|
|
1796
|
+
if (typeof value !== "string" || nonempty && !value)
|
|
1797
|
+
fail("invalid_payload", `${where} must be a${nonempty ? " non-empty" : ""} string`);
|
|
1798
|
+
return value;
|
|
1799
|
+
}
|
|
1800
|
+
function list(value, where) {
|
|
1801
|
+
if (!Array.isArray(value))
|
|
1802
|
+
fail("invalid_payload", `${where} must be an array`);
|
|
1803
|
+
return value;
|
|
1804
|
+
}
|
|
1805
|
+
function limits(overrides) {
|
|
1806
|
+
const result = { ...DEFAULT_RESPONSES_CODEC_LIMITS, ...overrides };
|
|
1807
|
+
for (const [key, value] of Object.entries(result)) {
|
|
1808
|
+
if (!Number.isSafeInteger(value) || value <= 0)
|
|
1809
|
+
fail("invalid_limit", `${key} must be a positive integer`);
|
|
1810
|
+
}
|
|
1811
|
+
return result;
|
|
1812
|
+
}
|
|
1813
|
+
function bytes(value) {
|
|
1814
|
+
return new TextEncoder().encode(value).byteLength;
|
|
1815
|
+
}
|
|
1816
|
+
function bounded(value, max, where) {
|
|
1817
|
+
if (bytes(value) > max)
|
|
1818
|
+
fail("resource_limit", `${where} exceeds ${max} bytes`);
|
|
1819
|
+
return value;
|
|
1820
|
+
}
|
|
1821
|
+
function serialized(value, max, where) {
|
|
1822
|
+
let result;
|
|
1823
|
+
try {
|
|
1824
|
+
result = JSON.stringify(value);
|
|
1825
|
+
} catch {
|
|
1826
|
+
fail("invalid_payload", `${where} must be serializable JSON`);
|
|
1827
|
+
}
|
|
1828
|
+
if (result === undefined)
|
|
1829
|
+
fail("invalid_payload", `${where} must be serializable JSON`);
|
|
1830
|
+
return bounded(result, max, where);
|
|
1831
|
+
}
|
|
1832
|
+
function argumentObject(text) {
|
|
1833
|
+
let value;
|
|
1834
|
+
try {
|
|
1835
|
+
value = JSON.parse(text);
|
|
1836
|
+
} catch {
|
|
1837
|
+
fail("invalid_tool_arguments", "Tool arguments must be complete JSON");
|
|
1838
|
+
}
|
|
1839
|
+
return record(value, "tool arguments");
|
|
1840
|
+
}
|
|
1841
|
+
function restoreTool(name, names) {
|
|
1842
|
+
const result = names.get(name);
|
|
1843
|
+
if (result)
|
|
1844
|
+
return result;
|
|
1845
|
+
if (names.size)
|
|
1846
|
+
fail("unknown_tool", `Provider returned undeclared tool ${name}`);
|
|
1847
|
+
return { name, custom: false };
|
|
1848
|
+
}
|
|
1849
|
+
function customInput(text) {
|
|
1850
|
+
const value = argumentObject(text);
|
|
1851
|
+
if (Object.keys(value).length !== 1 || typeof value.input !== "string") {
|
|
1852
|
+
fail("invalid_tool_arguments", "Custom tools require exactly one string input property");
|
|
1853
|
+
}
|
|
1854
|
+
return value.input;
|
|
1855
|
+
}
|
|
1856
|
+
function customInputPrefix(text) {
|
|
1857
|
+
const prefix = /^\s*\{\s*"input"\s*:\s*"/.exec(text);
|
|
1858
|
+
if (!prefix)
|
|
1859
|
+
return "";
|
|
1860
|
+
let result = "";
|
|
1861
|
+
for (let i = prefix[0].length;i < text.length; ) {
|
|
1862
|
+
const char = text[i];
|
|
1863
|
+
if (char === '"')
|
|
1864
|
+
break;
|
|
1865
|
+
if (char !== "\\") {
|
|
1866
|
+
if (char.charCodeAt(0) < 32)
|
|
1867
|
+
fail("invalid_tool_arguments", "Invalid JSON string character");
|
|
1868
|
+
result += char;
|
|
1869
|
+
i++;
|
|
1870
|
+
continue;
|
|
1871
|
+
}
|
|
1872
|
+
const escape = text[i + 1];
|
|
1873
|
+
if (escape === undefined)
|
|
1874
|
+
break;
|
|
1875
|
+
const size = escape === "u" ? 6 : 2;
|
|
1876
|
+
if (i + size > text.length)
|
|
1877
|
+
break;
|
|
1878
|
+
try {
|
|
1879
|
+
result += JSON.parse(`"${text.slice(i, i + size)}"`);
|
|
1880
|
+
} catch {
|
|
1881
|
+
fail("invalid_tool_arguments", "Invalid JSON string escape");
|
|
1882
|
+
}
|
|
1883
|
+
i += size;
|
|
1884
|
+
}
|
|
1885
|
+
return result;
|
|
1886
|
+
}
|
|
1887
|
+
function terminal(reason, protocol) {
|
|
1888
|
+
if (typeof reason !== "string" || !reason)
|
|
1889
|
+
fail("missing_terminal", "Upstream has no explicit completion reason");
|
|
1890
|
+
const completed = protocol === "chat_completions" ? ["stop", "tool_calls", "function_call"] : ["end_turn", "tool_use", "stop_sequence"];
|
|
1891
|
+
if (completed.includes(reason))
|
|
1892
|
+
return { status: "completed", reason };
|
|
1893
|
+
if (reason === "length" || reason === "max_tokens" || reason === "model_context_window_exceeded")
|
|
1894
|
+
return { status: "incomplete", reason: "max_output_tokens" };
|
|
1895
|
+
if (reason === "content_filter" || reason === "refusal")
|
|
1896
|
+
return { status: "incomplete", reason: "content_filter" };
|
|
1897
|
+
if (reason === "error")
|
|
1898
|
+
return { status: "failed", reason: "upstream_error" };
|
|
1899
|
+
fail("unknown_terminal", `Unsupported upstream completion reason: ${reason}`);
|
|
1900
|
+
}
|
|
1901
|
+
function tokens(value, where) {
|
|
1902
|
+
if (value === undefined)
|
|
1903
|
+
return 0;
|
|
1904
|
+
if (!Number.isSafeInteger(value) || value < 0)
|
|
1905
|
+
fail("invalid_usage", `${where} must be a non-negative integer`);
|
|
1906
|
+
return value;
|
|
1907
|
+
}
|
|
1908
|
+
function normalizeUsage(raw, protocol, prior = {}) {
|
|
1909
|
+
if (raw === undefined || raw === null)
|
|
1910
|
+
return prior;
|
|
1911
|
+
const usage = record(raw, "usage");
|
|
1912
|
+
const chat = protocol === "chat_completions";
|
|
1913
|
+
const input = tokens(usage[chat ? "prompt_tokens" : "input_tokens"] ?? prior.input_tokens, "input_tokens");
|
|
1914
|
+
const output = tokens(usage[chat ? "completion_tokens" : "output_tokens"] ?? prior.output_tokens, "output_tokens");
|
|
1915
|
+
const details = chat ? usage.prompt_tokens_details : undefined;
|
|
1916
|
+
const cached = chat ? details ? record(details, "prompt_tokens_details").cached_tokens : undefined : usage.cache_read_input_tokens;
|
|
1917
|
+
const cacheRead = tokens(cached ?? prior.input_tokens_details?.cached_tokens, "cached_tokens");
|
|
1918
|
+
const cacheWrite = chat ? 0 : tokens(usage.cache_creation_input_tokens ?? prior.input_tokens_details?.cache_creation_tokens, "cache_creation_input_tokens");
|
|
1919
|
+
const totalInput = chat ? input : usage.input_tokens === undefined ? input : input + cacheRead + cacheWrite;
|
|
1920
|
+
const reasoning = chat && usage.completion_tokens_details ? record(usage.completion_tokens_details, "completion_tokens_details").reasoning_tokens : undefined;
|
|
1921
|
+
return {
|
|
1922
|
+
input_tokens: totalInput,
|
|
1923
|
+
output_tokens: output,
|
|
1924
|
+
total_tokens: totalInput + output,
|
|
1925
|
+
input_tokens_details: { cached_tokens: cacheRead, ...cacheWrite ? { cache_creation_tokens: cacheWrite } : {} },
|
|
1926
|
+
output_tokens_details: { reasoning_tokens: tokens(reasoning ?? prior.output_tokens_details?.reasoning_tokens, "reasoning_tokens") }
|
|
1927
|
+
};
|
|
1928
|
+
}
|
|
1929
|
+
function isCollaborationFunction(name, namespace, custom = false) {
|
|
1930
|
+
return !custom && namespace === "collaboration" && typeof name === "string" && ["spawn_agent", "send_message", "followup_task"].includes(name);
|
|
1931
|
+
}
|
|
1932
|
+
function plaintextCollaborationCall(item, delivery) {
|
|
1933
|
+
return delivery === "plaintext" && item.type === "function_call" && isCollaborationFunction(item.name, item.namespace) ? { ...item, encrypted_function_args: [] } : item;
|
|
1934
|
+
}
|
|
1935
|
+
function toolItem(id, name, args, names, status = "completed", agentMessageDelivery) {
|
|
1936
|
+
const original = restoreTool(name, names);
|
|
1937
|
+
const item = { id: `fc_${id}`, type: original.custom ? "custom_tool_call" : "function_call", call_id: id, name: original.name, status };
|
|
1938
|
+
if (original.namespace)
|
|
1939
|
+
item.namespace = original.namespace;
|
|
1940
|
+
if (original.custom)
|
|
1941
|
+
item.input = customInput(args);
|
|
1942
|
+
else {
|
|
1943
|
+
argumentObject(args);
|
|
1944
|
+
item.arguments = args;
|
|
1945
|
+
}
|
|
1946
|
+
return plaintextCollaborationCall(item, agentMessageDelivery);
|
|
1947
|
+
}
|
|
1948
|
+
function responsePayload(id, model, output, end, usage, error, createdAt = Math.floor(Date.now() / 1000)) {
|
|
1949
|
+
const result = { id, object: "response", created_at: createdAt, model, status: end.status, output, usage: Object.keys(usage).length ? usage : null };
|
|
1950
|
+
if (end.status === "incomplete")
|
|
1951
|
+
result.incomplete_details = { reason: end.reason };
|
|
1952
|
+
if (end.status === "failed")
|
|
1953
|
+
result.error = error ?? { code: end.reason, message: end.reason };
|
|
1954
|
+
return result;
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1957
|
+
// src/responses-codec/agent-messages.ts
|
|
1958
|
+
function exact(value, keys, path) {
|
|
1959
|
+
for (const key of Object.keys(value))
|
|
1960
|
+
if (!keys.includes(key))
|
|
1961
|
+
fail("unsupported_content", "Unknown agent message field", `${path}.${key}`);
|
|
1962
|
+
}
|
|
1963
|
+
function validateAgentMessage(raw, path, configuredLimits, allowEncrypted = false) {
|
|
1964
|
+
const budget = limits(configuredLimits);
|
|
1965
|
+
const item = atParam(path, () => record(raw, "agent message"));
|
|
1966
|
+
exact(item, ["type", "id", "author", "recipient", "content", "internal_chat_message_metadata_passthrough"], path);
|
|
1967
|
+
if (item.type !== "agent_message")
|
|
1968
|
+
fail("unsupported_content", "Expected agent message", `${path}.type`);
|
|
1969
|
+
if (item.id !== undefined)
|
|
1970
|
+
atParam(`${path}.id`, () => string(item.id, "agent message id", true));
|
|
1971
|
+
if (item.internal_chat_message_metadata_passthrough !== undefined)
|
|
1972
|
+
atParam(`${path}.internal_chat_message_metadata_passthrough`, () => record(item.internal_chat_message_metadata_passthrough, "message metadata"));
|
|
1973
|
+
for (const key of ["author", "recipient"])
|
|
1974
|
+
atParam(`${path}.${key}`, () => string(item[key], "agent identity", true));
|
|
1975
|
+
const content = atParam(`${path}.content`, () => list(item.content, "agent content"));
|
|
1976
|
+
if (content.length > budget.maxItems)
|
|
1977
|
+
fail("resource_limit", "Too many agent content parts", `${path}.content`);
|
|
1978
|
+
for (const [index, rawPart] of content.entries()) {
|
|
1979
|
+
const partPath = `${path}.content[${index}]`;
|
|
1980
|
+
const part = atParam(partPath, () => record(rawPart, "agent content part"));
|
|
1981
|
+
if (part.type === "encrypted_content") {
|
|
1982
|
+
exact(part, ["type", "encrypted_content"], partPath);
|
|
1983
|
+
atParam(`${partPath}.encrypted_content`, () => string(part.encrypted_content, "encrypted content", true));
|
|
1984
|
+
if (!allowEncrypted)
|
|
1985
|
+
fail("unsupported_content", "Encrypted agent content requires explicit plaintext replacement", `${partPath}.encrypted_content`);
|
|
1986
|
+
} else {
|
|
1987
|
+
exact(part, ["type", "text"], partPath);
|
|
1988
|
+
if (part.type !== "input_text")
|
|
1989
|
+
fail("unsupported_content", "Agent messages require input text", `${partPath}.type`);
|
|
1990
|
+
atParam(`${partPath}.text`, () => string(part.text, "agent input text"));
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
atParam(path, () => serialized(item, budget.maxRequestBytes, "agent message"));
|
|
1994
|
+
return item;
|
|
1995
|
+
}
|
|
1996
|
+
function inspectAgentMessages(raw, options = {}) {
|
|
1997
|
+
const budget = limits(options.limits);
|
|
1998
|
+
const body = atParam("request", () => record(raw, "request"));
|
|
1999
|
+
atParam("request", () => serialized(body, budget.maxRequestBytes, "request"));
|
|
2000
|
+
const input = typeof body.input === "string" ? [] : atParam("input", () => list(body.input, "input"));
|
|
2001
|
+
if (input.length > budget.maxItems)
|
|
2002
|
+
fail("resource_limit", "Too many input items", "input");
|
|
2003
|
+
const messages = [], diagnostics = [];
|
|
2004
|
+
let parts = 0;
|
|
2005
|
+
for (const [inputIndex, rawItem] of input.entries()) {
|
|
2006
|
+
const item = atParam(`input[${inputIndex}]`, () => record(rawItem, "input item"));
|
|
2007
|
+
if (item.type !== "agent_message")
|
|
2008
|
+
continue;
|
|
2009
|
+
validateAgentMessage(item, `input[${inputIndex}]`, options.limits, true);
|
|
2010
|
+
const encryptedContents = [];
|
|
2011
|
+
for (const [contentIndex, part] of item.content.entries()) {
|
|
2012
|
+
if (++parts > budget.maxItems)
|
|
2013
|
+
fail("resource_limit", "Too many agent content parts", "input");
|
|
2014
|
+
if (part.type === "encrypted_content") {
|
|
2015
|
+
const param = `input[${inputIndex}].content[${contentIndex}].encrypted_content`;
|
|
2016
|
+
encryptedContents.push({ contentIndex, param });
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
messages.push({ inputIndex, author: item.author, recipient: item.recipient, encryptedContents });
|
|
2020
|
+
}
|
|
2021
|
+
return { messages, diagnostics };
|
|
2022
|
+
}
|
|
2023
|
+
function replaceAgentMessageEncryptedContent(raw, replacements, options = {}) {
|
|
2024
|
+
const inspection = inspectAgentMessages(raw, options);
|
|
2025
|
+
const budget = limits(options.limits);
|
|
2026
|
+
if (!Array.isArray(replacements))
|
|
2027
|
+
fail("invalid_payload", "Replacements must be an array", "replacements");
|
|
2028
|
+
if (replacements.length > budget.maxItems)
|
|
2029
|
+
fail("resource_limit", "Too many replacements", "replacements");
|
|
2030
|
+
const locations = new Map(inspection.messages.flatMap((m) => m.encryptedContents.map((c) => [c.param, { inputIndex: m.inputIndex, contentIndex: c.contentIndex }])));
|
|
2031
|
+
const selected = new Set;
|
|
2032
|
+
let body;
|
|
2033
|
+
try {
|
|
2034
|
+
body = structuredClone(record(raw, "request"));
|
|
2035
|
+
} catch {
|
|
2036
|
+
fail("invalid_payload", "Request must be cloneable JSON", "request");
|
|
2037
|
+
}
|
|
2038
|
+
const diagnostics = [];
|
|
2039
|
+
for (const [i, rawReplacement] of replacements.entries()) {
|
|
2040
|
+
const replacement = atParam(`replacements[${i}]`, () => record(rawReplacement, "replacement"));
|
|
2041
|
+
exact(replacement, ["param", "text"], `replacements[${i}]`);
|
|
2042
|
+
const param = atParam(`replacements[${i}].param`, () => string(replacement.param, "replacement param", true));
|
|
2043
|
+
const text = atParam(`replacements[${i}].text`, () => string(replacement.text, "replacement text"));
|
|
2044
|
+
const location = locations.get(param);
|
|
2045
|
+
if (!location || selected.has(param))
|
|
2046
|
+
fail("invalid_payload", "Replacement must identify one unique encrypted agent content block", `replacements[${i}].param`);
|
|
2047
|
+
selected.add(param);
|
|
2048
|
+
record(body.input[location.inputIndex], "agent message").content[location.contentIndex] = { type: "input_text", text };
|
|
2049
|
+
diagnostics.push({ param, action: "mapped", reason: "agent_message_plaintext_replacement" });
|
|
2050
|
+
}
|
|
2051
|
+
atParam("request", () => serialized(body, budget.maxRequestBytes, "request"));
|
|
2052
|
+
return { body, diagnostics };
|
|
2053
|
+
}
|
|
2054
|
+
function validateEncryptedFunctionArgs(item, delivery, path) {
|
|
2055
|
+
if (item.encrypted_function_args === undefined)
|
|
2056
|
+
return;
|
|
2057
|
+
const param = `${path}.encrypted_function_args`;
|
|
2058
|
+
if (delivery !== "plaintext" || !isCollaborationFunction(item.name, item.namespace, item.type !== "function_call"))
|
|
2059
|
+
fail("unsupported_content", "Encrypted function argument marker is unavailable for this tool", param);
|
|
2060
|
+
const values = atParam(param, () => list(item.encrypted_function_args, "encrypted function arguments"));
|
|
2061
|
+
if (values.length)
|
|
2062
|
+
fail("unsupported_content", "Encrypted function arguments cannot cross protocols", param);
|
|
2063
|
+
}
|
|
2064
|
+
|
|
1760
2065
|
// src/providers/openai/protocol-conversion.ts
|
|
1761
2066
|
var THINKING_TAG_PATTERN = /<thinking>([\s\S]*?)<\/thinking>/gi;
|
|
1762
2067
|
var TOOL_CALL_CONTENT_TYPES = new Set([
|
|
@@ -2336,197 +2641,6 @@ class OpenAIProtocolConversion {
|
|
|
2336
2641
|
}
|
|
2337
2642
|
}
|
|
2338
2643
|
|
|
2339
|
-
// src/responses-codec/common.ts
|
|
2340
|
-
var DEFAULT_RESPONSES_CODEC_LIMITS = Object.freeze({
|
|
2341
|
-
maxRequestBytes: 8 * 1024 * 1024,
|
|
2342
|
-
maxOutputBytes: 8 * 1024 * 1024,
|
|
2343
|
-
maxArgumentBytes: 1024 * 1024,
|
|
2344
|
-
maxItems: 1024
|
|
2345
|
-
});
|
|
2346
|
-
|
|
2347
|
-
class ResponsesCodecError extends Error {
|
|
2348
|
-
code;
|
|
2349
|
-
param;
|
|
2350
|
-
constructor(code, message, param) {
|
|
2351
|
-
super(message);
|
|
2352
|
-
this.code = code;
|
|
2353
|
-
this.param = param;
|
|
2354
|
-
this.name = "ResponsesCodecError";
|
|
2355
|
-
}
|
|
2356
|
-
}
|
|
2357
|
-
function fail(code, message, param) {
|
|
2358
|
-
throw new ResponsesCodecError(code, message, param);
|
|
2359
|
-
}
|
|
2360
|
-
function atParam(param, run) {
|
|
2361
|
-
try {
|
|
2362
|
-
return run();
|
|
2363
|
-
} catch (error) {
|
|
2364
|
-
if (error instanceof ResponsesCodecError && error.param === undefined)
|
|
2365
|
-
throw new ResponsesCodecError(error.code, error.message, param);
|
|
2366
|
-
throw error;
|
|
2367
|
-
}
|
|
2368
|
-
}
|
|
2369
|
-
function record(value, where) {
|
|
2370
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
2371
|
-
fail("invalid_payload", `${where} must be an object`);
|
|
2372
|
-
return value;
|
|
2373
|
-
}
|
|
2374
|
-
function string(value, where, nonempty = false) {
|
|
2375
|
-
if (typeof value !== "string" || nonempty && !value)
|
|
2376
|
-
fail("invalid_payload", `${where} must be a${nonempty ? " non-empty" : ""} string`);
|
|
2377
|
-
return value;
|
|
2378
|
-
}
|
|
2379
|
-
function list(value, where) {
|
|
2380
|
-
if (!Array.isArray(value))
|
|
2381
|
-
fail("invalid_payload", `${where} must be an array`);
|
|
2382
|
-
return value;
|
|
2383
|
-
}
|
|
2384
|
-
function limits(overrides) {
|
|
2385
|
-
const result = { ...DEFAULT_RESPONSES_CODEC_LIMITS, ...overrides };
|
|
2386
|
-
for (const [key, value] of Object.entries(result)) {
|
|
2387
|
-
if (!Number.isSafeInteger(value) || value <= 0)
|
|
2388
|
-
fail("invalid_limit", `${key} must be a positive integer`);
|
|
2389
|
-
}
|
|
2390
|
-
return result;
|
|
2391
|
-
}
|
|
2392
|
-
function bytes(value) {
|
|
2393
|
-
return new TextEncoder().encode(value).byteLength;
|
|
2394
|
-
}
|
|
2395
|
-
function bounded(value, max, where) {
|
|
2396
|
-
if (bytes(value) > max)
|
|
2397
|
-
fail("resource_limit", `${where} exceeds ${max} bytes`);
|
|
2398
|
-
return value;
|
|
2399
|
-
}
|
|
2400
|
-
function serialized(value, max, where) {
|
|
2401
|
-
let result;
|
|
2402
|
-
try {
|
|
2403
|
-
result = JSON.stringify(value);
|
|
2404
|
-
} catch {
|
|
2405
|
-
fail("invalid_payload", `${where} must be serializable JSON`);
|
|
2406
|
-
}
|
|
2407
|
-
if (result === undefined)
|
|
2408
|
-
fail("invalid_payload", `${where} must be serializable JSON`);
|
|
2409
|
-
return bounded(result, max, where);
|
|
2410
|
-
}
|
|
2411
|
-
function argumentObject(text) {
|
|
2412
|
-
let value;
|
|
2413
|
-
try {
|
|
2414
|
-
value = JSON.parse(text);
|
|
2415
|
-
} catch {
|
|
2416
|
-
fail("invalid_tool_arguments", "Tool arguments must be complete JSON");
|
|
2417
|
-
}
|
|
2418
|
-
return record(value, "tool arguments");
|
|
2419
|
-
}
|
|
2420
|
-
function restoreTool(name, names) {
|
|
2421
|
-
const result = names.get(name);
|
|
2422
|
-
if (result)
|
|
2423
|
-
return result;
|
|
2424
|
-
if (names.size)
|
|
2425
|
-
fail("unknown_tool", `Provider returned undeclared tool ${name}`);
|
|
2426
|
-
return { name, custom: false };
|
|
2427
|
-
}
|
|
2428
|
-
function customInput(text) {
|
|
2429
|
-
const value = argumentObject(text);
|
|
2430
|
-
if (Object.keys(value).length !== 1 || typeof value.input !== "string") {
|
|
2431
|
-
fail("invalid_tool_arguments", "Custom tools require exactly one string input property");
|
|
2432
|
-
}
|
|
2433
|
-
return value.input;
|
|
2434
|
-
}
|
|
2435
|
-
function customInputPrefix(text) {
|
|
2436
|
-
const prefix = /^\s*\{\s*"input"\s*:\s*"/.exec(text);
|
|
2437
|
-
if (!prefix)
|
|
2438
|
-
return "";
|
|
2439
|
-
let result = "";
|
|
2440
|
-
for (let i = prefix[0].length;i < text.length; ) {
|
|
2441
|
-
const char = text[i];
|
|
2442
|
-
if (char === '"')
|
|
2443
|
-
break;
|
|
2444
|
-
if (char !== "\\") {
|
|
2445
|
-
if (char.charCodeAt(0) < 32)
|
|
2446
|
-
fail("invalid_tool_arguments", "Invalid JSON string character");
|
|
2447
|
-
result += char;
|
|
2448
|
-
i++;
|
|
2449
|
-
continue;
|
|
2450
|
-
}
|
|
2451
|
-
const escape = text[i + 1];
|
|
2452
|
-
if (escape === undefined)
|
|
2453
|
-
break;
|
|
2454
|
-
const size = escape === "u" ? 6 : 2;
|
|
2455
|
-
if (i + size > text.length)
|
|
2456
|
-
break;
|
|
2457
|
-
try {
|
|
2458
|
-
result += JSON.parse(`"${text.slice(i, i + size)}"`);
|
|
2459
|
-
} catch {
|
|
2460
|
-
fail("invalid_tool_arguments", "Invalid JSON string escape");
|
|
2461
|
-
}
|
|
2462
|
-
i += size;
|
|
2463
|
-
}
|
|
2464
|
-
return result;
|
|
2465
|
-
}
|
|
2466
|
-
function terminal(reason, protocol) {
|
|
2467
|
-
if (typeof reason !== "string" || !reason)
|
|
2468
|
-
fail("missing_terminal", "Upstream has no explicit completion reason");
|
|
2469
|
-
const completed = protocol === "chat_completions" ? ["stop", "tool_calls", "function_call"] : ["end_turn", "tool_use", "stop_sequence"];
|
|
2470
|
-
if (completed.includes(reason))
|
|
2471
|
-
return { status: "completed", reason };
|
|
2472
|
-
if (reason === "length" || reason === "max_tokens" || reason === "model_context_window_exceeded")
|
|
2473
|
-
return { status: "incomplete", reason: "max_output_tokens" };
|
|
2474
|
-
if (reason === "content_filter" || reason === "refusal")
|
|
2475
|
-
return { status: "incomplete", reason: "content_filter" };
|
|
2476
|
-
if (reason === "error")
|
|
2477
|
-
return { status: "failed", reason: "upstream_error" };
|
|
2478
|
-
fail("unknown_terminal", `Unsupported upstream completion reason: ${reason}`);
|
|
2479
|
-
}
|
|
2480
|
-
function tokens(value, where) {
|
|
2481
|
-
if (value === undefined)
|
|
2482
|
-
return 0;
|
|
2483
|
-
if (!Number.isSafeInteger(value) || value < 0)
|
|
2484
|
-
fail("invalid_usage", `${where} must be a non-negative integer`);
|
|
2485
|
-
return value;
|
|
2486
|
-
}
|
|
2487
|
-
function normalizeUsage(raw, protocol, prior = {}) {
|
|
2488
|
-
if (raw === undefined || raw === null)
|
|
2489
|
-
return prior;
|
|
2490
|
-
const usage = record(raw, "usage");
|
|
2491
|
-
const chat = protocol === "chat_completions";
|
|
2492
|
-
const input = tokens(usage[chat ? "prompt_tokens" : "input_tokens"] ?? prior.input_tokens, "input_tokens");
|
|
2493
|
-
const output = tokens(usage[chat ? "completion_tokens" : "output_tokens"] ?? prior.output_tokens, "output_tokens");
|
|
2494
|
-
const details = chat ? usage.prompt_tokens_details : undefined;
|
|
2495
|
-
const cached = chat ? details ? record(details, "prompt_tokens_details").cached_tokens : undefined : usage.cache_read_input_tokens;
|
|
2496
|
-
const cacheRead = tokens(cached ?? prior.input_tokens_details?.cached_tokens, "cached_tokens");
|
|
2497
|
-
const cacheWrite = chat ? 0 : tokens(usage.cache_creation_input_tokens ?? prior.input_tokens_details?.cache_creation_tokens, "cache_creation_input_tokens");
|
|
2498
|
-
const totalInput = chat ? input : usage.input_tokens === undefined ? input : input + cacheRead + cacheWrite;
|
|
2499
|
-
const reasoning = chat && usage.completion_tokens_details ? record(usage.completion_tokens_details, "completion_tokens_details").reasoning_tokens : undefined;
|
|
2500
|
-
return {
|
|
2501
|
-
input_tokens: totalInput,
|
|
2502
|
-
output_tokens: output,
|
|
2503
|
-
total_tokens: totalInput + output,
|
|
2504
|
-
input_tokens_details: { cached_tokens: cacheRead, ...cacheWrite ? { cache_creation_tokens: cacheWrite } : {} },
|
|
2505
|
-
output_tokens_details: { reasoning_tokens: tokens(reasoning ?? prior.output_tokens_details?.reasoning_tokens, "reasoning_tokens") }
|
|
2506
|
-
};
|
|
2507
|
-
}
|
|
2508
|
-
function toolItem(id, name, args, names, status = "completed") {
|
|
2509
|
-
const original = restoreTool(name, names);
|
|
2510
|
-
const item = { id: `fc_${id}`, type: original.custom ? "custom_tool_call" : "function_call", call_id: id, name: original.name, status };
|
|
2511
|
-
if (original.namespace)
|
|
2512
|
-
item.namespace = original.namespace;
|
|
2513
|
-
if (original.custom)
|
|
2514
|
-
item.input = customInput(args);
|
|
2515
|
-
else {
|
|
2516
|
-
argumentObject(args);
|
|
2517
|
-
item.arguments = args;
|
|
2518
|
-
}
|
|
2519
|
-
return item;
|
|
2520
|
-
}
|
|
2521
|
-
function responsePayload(id, model, output, end, usage, error, createdAt = Math.floor(Date.now() / 1000)) {
|
|
2522
|
-
const result = { id, object: "response", created_at: createdAt, model, status: end.status, output, usage: Object.keys(usage).length ? usage : null };
|
|
2523
|
-
if (end.status === "incomplete")
|
|
2524
|
-
result.incomplete_details = { reason: end.reason };
|
|
2525
|
-
if (end.status === "failed")
|
|
2526
|
-
result.error = error ?? { code: end.reason, message: end.reason };
|
|
2527
|
-
return result;
|
|
2528
|
-
}
|
|
2529
|
-
|
|
2530
2644
|
// src/responses-codec/request-preferences.ts
|
|
2531
2645
|
function fields(value, allowed, path) {
|
|
2532
2646
|
for (const key of Object.keys(value))
|
|
@@ -2660,6 +2774,8 @@ function definitionKey(value) {
|
|
|
2660
2774
|
}
|
|
2661
2775
|
function decodeResponsesRequest(raw, protocol, capabilities = {}) {
|
|
2662
2776
|
const body = record(raw, "Responses request");
|
|
2777
|
+
if (capabilities.agentMessageDelivery !== undefined && capabilities.agentMessageDelivery !== "plaintext")
|
|
2778
|
+
fail("invalid_payload", "Unknown agent message delivery policy", "agentMessageDelivery");
|
|
2663
2779
|
const budget = limits(capabilities.limits);
|
|
2664
2780
|
serialized(body, budget.maxRequestBytes, "request");
|
|
2665
2781
|
const supported = new Set([
|
|
@@ -2784,7 +2900,18 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}) {
|
|
|
2784
2900
|
}
|
|
2785
2901
|
const wire = register(name, namespace, custom);
|
|
2786
2902
|
definitions.set(key, signature);
|
|
2787
|
-
|
|
2903
|
+
let parameters = custom ? { type: "object", properties: { input: { type: "string" } }, required: ["input"], additionalProperties: false } : record(tool.parameters ?? { type: "object", properties: {} }, "tool parameters");
|
|
2904
|
+
if (capabilities.agentMessageDelivery === "plaintext" && isCollaborationFunction(name, namespace, custom)) {
|
|
2905
|
+
const properties = parameters.properties;
|
|
2906
|
+
if (properties && typeof properties === "object" && !Array.isArray(properties)) {
|
|
2907
|
+
const message = properties.message;
|
|
2908
|
+
if (message && typeof message === "object" && !Array.isArray(message) && message.encrypted === true) {
|
|
2909
|
+
const { encrypted: _encrypted, ...plaintext } = message;
|
|
2910
|
+
parameters = { ...parameters, properties: { ...properties, message: plaintext } };
|
|
2911
|
+
diagnostics.push({ param: `${path}.parameters.properties.message.encrypted`, action: "omitted", reason: "collaboration_message_plaintext_delivery" });
|
|
2912
|
+
}
|
|
2913
|
+
}
|
|
2914
|
+
}
|
|
2788
2915
|
const declaredDescription = tool.description === undefined ? undefined : string(tool.description, "tool description");
|
|
2789
2916
|
const identityHint = capabilities.preserveToolIdentityDescription ? `Original tool identity: ${JSON.stringify({ name, ...namespace ? { namespace } : {} })}` : undefined;
|
|
2790
2917
|
const description = [identityHint, namespaceDescription, declaredDescription].filter((value) => value !== undefined).join(`
|
|
@@ -2889,7 +3016,20 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}) {
|
|
|
2889
3016
|
const item = record(rawItem, "input item");
|
|
2890
3017
|
if (item.type === "additional_tools")
|
|
2891
3018
|
return;
|
|
2892
|
-
if (item.
|
|
3019
|
+
if (item.type === "agent_message") {
|
|
3020
|
+
validateAgentMessage(item, messageParam, capabilities.limits);
|
|
3021
|
+
if (capabilities.agentMessageDelivery !== "plaintext")
|
|
3022
|
+
fail("unsupported_content", "Agent messages require explicit plaintext delivery", messageParam);
|
|
3023
|
+
if (pendingReasoning)
|
|
3024
|
+
fail("unsupported_reasoning", "Reasoning must precede an assistant item", messageParam);
|
|
3025
|
+
addMessage({ role: "user", content: [{ type: "text", text: `[Agent message metadata: ${JSON.stringify({ author: item.author, recipient: item.recipient })}]
|
|
3026
|
+
` }, ...item.content.map((part) => ({ type: "text", text: part.text }))] });
|
|
3027
|
+
diagnostics.push({ param: messageParam, action: "mapped", reason: "agent_message_plaintext_delivery" });
|
|
3028
|
+
return;
|
|
3029
|
+
}
|
|
3030
|
+
if (item.encrypted_content != null)
|
|
3031
|
+
fail("unsupported_content", "Encrypted content cannot cross protocols", `${messageParam}.encrypted_content`);
|
|
3032
|
+
if (item.type === "compaction")
|
|
2893
3033
|
fail("unsupported_content", "Encrypted reasoning and compaction cannot be restored");
|
|
2894
3034
|
if (item.type === "item_reference")
|
|
2895
3035
|
fail("unresolved_reference", "Input item references must be resolved before conversion");
|
|
@@ -2909,6 +3049,7 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}) {
|
|
|
2909
3049
|
}
|
|
2910
3050
|
if (item.type === "function_call" || item.type === "custom_tool_call") {
|
|
2911
3051
|
const custom = item.type === "custom_tool_call";
|
|
3052
|
+
validateEncryptedFunctionArgs(item, capabilities.agentMessageDelivery, messageParam);
|
|
2912
3053
|
const id = string(item.call_id, "call_id", true);
|
|
2913
3054
|
if (callIds.has(id))
|
|
2914
3055
|
fail("invalid_tool_history", "Duplicate tool call identifier");
|
|
@@ -3127,9 +3268,9 @@ class ResponsesEventEncoder {
|
|
|
3127
3268
|
item(state, status, strict = false) {
|
|
3128
3269
|
if (state.kind === "tool") {
|
|
3129
3270
|
if (strict)
|
|
3130
|
-
return { ...toolItem(state.callId, state.name, state.args || "{}", this.toolNames, status), id: state.id };
|
|
3271
|
+
return { ...toolItem(state.callId, state.name, state.args || "{}", this.toolNames, status, this.options.agentMessageDelivery), id: state.id };
|
|
3131
3272
|
const original = state.original ?? restoreTool(state.name, this.toolNames);
|
|
3132
|
-
return {
|
|
3273
|
+
return plaintextCollaborationCall({
|
|
3133
3274
|
id: state.id,
|
|
3134
3275
|
type: original.custom ? "custom_tool_call" : "function_call",
|
|
3135
3276
|
call_id: state.callId,
|
|
@@ -3137,7 +3278,7 @@ class ResponsesEventEncoder {
|
|
|
3137
3278
|
...original.namespace ? { namespace: original.namespace } : {},
|
|
3138
3279
|
status,
|
|
3139
3280
|
...original.custom ? { input: state.emittedInput } : { arguments: state.args }
|
|
3140
|
-
};
|
|
3281
|
+
}, this.options.agentMessageDelivery);
|
|
3141
3282
|
}
|
|
3142
3283
|
if (state.kind === "reasoning")
|
|
3143
3284
|
return { id: state.id, type: "reasoning", summary: status === "in_progress" ? [] : [{ type: "summary_text", text: state.text }] };
|
|
@@ -3450,13 +3591,13 @@ class ResponsesEventEncoder {
|
|
|
3450
3591
|
}
|
|
3451
3592
|
|
|
3452
3593
|
// src/responses-codec/result.ts
|
|
3453
|
-
function encodeResponsesResult(raw, protocol, model, toolNames = new Map, configuredLimits) {
|
|
3594
|
+
function encodeResponsesResult(raw, protocol, model, toolNames = new Map, configuredLimits, options = {}) {
|
|
3454
3595
|
const body = record(raw, "upstream response");
|
|
3455
3596
|
const budget = limits(configuredLimits);
|
|
3456
3597
|
serialized(body, budget.maxOutputBytes, "upstream response");
|
|
3457
3598
|
if (body.error !== undefined || body.type === "error")
|
|
3458
3599
|
return responsePayload(`resp_${crypto.randomUUID().replaceAll("-", "")}`, model, [], { status: "failed", reason: "upstream_error" }, {}, record(body.error, "upstream error"));
|
|
3459
|
-
const encoder = new ResponsesEventEncoder(protocol, model, toolNames, configuredLimits);
|
|
3600
|
+
const encoder = new ResponsesEventEncoder(protocol, model, toolNames, configuredLimits, options);
|
|
3460
3601
|
if (protocol === "chat_completions") {
|
|
3461
3602
|
const choices = list(body.choices, "choices");
|
|
3462
3603
|
if (choices.length !== 1)
|
|
@@ -3874,7 +4015,7 @@ function canonicalRequest(raw, protocol, model) {
|
|
|
3874
4015
|
}
|
|
3875
4016
|
return out;
|
|
3876
4017
|
}
|
|
3877
|
-
function validateCanonical(b) {
|
|
4018
|
+
function validateCanonical(b, context) {
|
|
3878
4019
|
for (const key of ["stream", "parallel_tool_calls", "store", "background"])
|
|
3879
4020
|
if (b[key] !== undefined)
|
|
3880
4021
|
bool(b[key], key);
|
|
@@ -3904,7 +4045,11 @@ function validateCanonical(b) {
|
|
|
3904
4045
|
const input = typeof b.input === "string" ? [] : list(b.input, "input");
|
|
3905
4046
|
for (const [i, raw] of input.entries()) {
|
|
3906
4047
|
const t = record(raw, "input item"), p = `input[${i}]`;
|
|
3907
|
-
|
|
4048
|
+
if (t.type === "agent_message") {
|
|
4049
|
+
validateAgentMessage(t, p, context.capabilities?.limits);
|
|
4050
|
+
continue;
|
|
4051
|
+
}
|
|
4052
|
+
const allowed = t.type === "function_call" ? ["type", "id", "status", "call_id", "name", "namespace", "arguments", "encrypted_function_args"] : t.type === "custom_tool_call" ? ["type", "id", "status", "call_id", "name", "namespace", "input"] : t.type === "function_call_output" || t.type === "custom_tool_call_output" ? ["type", "id", "call_id", "output", "is_error"] : t.type === "reasoning" ? ["type", "id", "status", "summary", "content", "encrypted_content", "internal_chat_message_metadata_passthrough"] : t.type === "additional_tools" ? ["type", "id", "role", "tools"] : ["type", "id", "status", "role", "content", "internal_chat_message_metadata_passthrough"];
|
|
3908
4053
|
fields2(t, [...allowed, "internal_chat_message_metadata_passthrough"], p, "unsupported_content");
|
|
3909
4054
|
if (t.is_error !== undefined)
|
|
3910
4055
|
bool(t.is_error, `${p}.is_error`);
|
|
@@ -3954,8 +4099,8 @@ function convertProtocolRequest(raw, context) {
|
|
|
3954
4099
|
fail("invalid_payload", "Request stream mode disagrees with transport context", "stream");
|
|
3955
4100
|
b.stream = context.streaming;
|
|
3956
4101
|
}
|
|
3957
|
-
validateCanonical(b);
|
|
3958
|
-
const cap = { ...context.capabilities, omitOptionalWebSearch: true, preserveToolIdentityDescription: true, preserveRefusalContent: true }, policy = context.reasoningPolicy;
|
|
4102
|
+
validateCanonical(b, context);
|
|
4103
|
+
const cap = { ...context.capabilities, agentMessageDelivery: context.agentMessageDelivery ?? context.capabilities?.agentMessageDelivery, omitOptionalWebSearch: true, preserveToolIdentityDescription: true, preserveRefusalContent: true }, policy = context.reasoningPolicy;
|
|
3959
4104
|
const reasoning = b.reasoning === undefined ? undefined : record(b.reasoning, "reasoning");
|
|
3960
4105
|
const sourceThinking = b.sourceThinking;
|
|
3961
4106
|
delete b.sourceThinking;
|
|
@@ -4242,7 +4387,7 @@ function validateCanonicalResponse(raw, context, itemOnly = false) {
|
|
|
4242
4387
|
fail("unsupported_response", "Unknown item status", `${path}.status`);
|
|
4243
4388
|
if (b.status === "completed" && t.status !== undefined && t.status !== "completed")
|
|
4244
4389
|
fail("invalid_stream", "Completed response contains an unfinished item", `${path}.status`);
|
|
4245
|
-
fields2(t, t.type === "message" ? ["type", "id", "role", "status", "content"] : t.type === "reasoning" ? ["type", "id", "summary", "status", "encrypted_content", "content"] : t.type === "function_call" ? ["type", "id", "status", "name", "namespace", "call_id", "arguments"] : ["type", "id", "status", "name", "namespace", "call_id", "input"], path, "unsupported_content");
|
|
4390
|
+
fields2(t, t.type === "message" ? ["type", "id", "role", "status", "content"] : t.type === "reasoning" ? ["type", "id", "summary", "status", "encrypted_content", "content"] : t.type === "function_call" ? ["type", "id", "status", "name", "namespace", "call_id", "arguments", "encrypted_function_args"] : ["type", "id", "status", "name", "namespace", "call_id", "input"], path, "unsupported_content");
|
|
4246
4391
|
if (t.type === "message") {
|
|
4247
4392
|
if (t.role !== "assistant")
|
|
4248
4393
|
fail("unsupported_content", "Output message must be assistant", `${path}.role`);
|
|
@@ -4269,6 +4414,7 @@ function validateCanonicalResponse(raw, context, itemOnly = false) {
|
|
|
4269
4414
|
string(p.text, "summary");
|
|
4270
4415
|
}
|
|
4271
4416
|
} else if (t.type === "function_call" || t.type === "custom_tool_call") {
|
|
4417
|
+
validateEncryptedFunctionArgs(t, context.capabilities?.agentMessageDelivery, path);
|
|
4272
4418
|
const id = string(t.call_id, "call_id", true);
|
|
4273
4419
|
if (ids.has(id))
|
|
4274
4420
|
fail("invalid_tool_arguments", "Duplicate output tool call id", path);
|
|
@@ -4479,7 +4625,7 @@ function canonicalResponse(raw, context, names) {
|
|
|
4479
4625
|
return record(events.at(-1)?.response, "response");
|
|
4480
4626
|
}
|
|
4481
4627
|
validateProviderResponse(raw, context.targetProtocol);
|
|
4482
|
-
const result = encodeResponsesResult(raw, context.targetProtocol, context.responseModel ?? context.model, names, context.capabilities?.limits);
|
|
4628
|
+
const result = encodeResponsesResult(raw, context.targetProtocol, context.responseModel ?? context.model, names, context.capabilities?.limits, { agentMessageDelivery: context.capabilities?.agentMessageDelivery });
|
|
4483
4629
|
if (result.status === "failed")
|
|
4484
4630
|
result.error = safeProtocolError();
|
|
4485
4631
|
const usage = record(raw, "response").usage;
|
|
@@ -4874,6 +5020,10 @@ function createProtocolSession(context) {
|
|
|
4874
5020
|
capabilities: context.capabilities ? { ...context.capabilities, limits: context.capabilities.limits ? { ...context.capabilities.limits } : undefined } : undefined,
|
|
4875
5021
|
reasoningPolicy: context.reasoningPolicy ? { ...context.reasoningPolicy, effortMap: context.reasoningPolicy.effortMap ? { ...context.reasoningPolicy.effortMap } : undefined } : undefined
|
|
4876
5022
|
};
|
|
5023
|
+
const delivery = context.agentMessageDelivery ?? context.capabilities?.agentMessageDelivery;
|
|
5024
|
+
if (delivery !== undefined && delivery !== "plaintext")
|
|
5025
|
+
fail("invalid_payload", "Unknown agent message delivery policy", "agentMessageDelivery");
|
|
5026
|
+
context.capabilities = { ...context.capabilities, agentMessageDelivery: delivery };
|
|
4877
5027
|
if (!LLM_PROTOCOLS.includes(context.sourceProtocol) || !LLM_PROTOCOLS.includes(context.targetProtocol))
|
|
4878
5028
|
fail("unsupported_protocol", "Unknown protocol");
|
|
4879
5029
|
if (typeof context.model !== "string" || !context.model)
|
|
@@ -4912,7 +5062,7 @@ function createProtocolSession(context) {
|
|
|
4912
5062
|
else if (context.targetProtocol === "gemini_generate_content")
|
|
4913
5063
|
encoder = new GeminiCanonicalDecoder(context, names);
|
|
4914
5064
|
else
|
|
4915
|
-
encoder = new ResponsesEventEncoder(context.targetProtocol, context.responseModel ?? context.model, names, context.capabilities?.limits, { preserveContentOrder: true });
|
|
5065
|
+
encoder = new ResponsesEventEncoder(context.targetProtocol, context.responseModel ?? context.model, names, context.capabilities?.limits, { preserveContentOrder: true, agentMessageDelivery: context.capabilities?.agentMessageDelivery });
|
|
4916
5066
|
validator = context.targetProtocol === "responses" ? undefined : new CanonicalStreamValidator(context);
|
|
4917
5067
|
renderer = new CanonicalStreamRenderer(context.sourceProtocol, context);
|
|
4918
5068
|
};
|
|
@@ -5092,6 +5242,23 @@ function validateStreamEnvelope(raw, protocol) {
|
|
|
5092
5242
|
validateUsage(e.usage, protocol);
|
|
5093
5243
|
}
|
|
5094
5244
|
}
|
|
5245
|
+
// src/protocol-session/endpoint.ts
|
|
5246
|
+
function protocolRequestPath(protocol, model, streaming) {
|
|
5247
|
+
switch (protocol) {
|
|
5248
|
+
case "responses":
|
|
5249
|
+
return "/responses";
|
|
5250
|
+
case "chat_completions":
|
|
5251
|
+
return "/v1/chat/completions";
|
|
5252
|
+
case "anthropic_messages":
|
|
5253
|
+
return "/v1/messages";
|
|
5254
|
+
case "gemini_generate_content":
|
|
5255
|
+
if (typeof model !== "string" || !model)
|
|
5256
|
+
fail("invalid_payload", "Target model is required", "model");
|
|
5257
|
+
return `/v1beta/models/${encodeURIComponent(model)}:${streaming ? "streamGenerateContent" : "generateContent"}`;
|
|
5258
|
+
default:
|
|
5259
|
+
return fail("unsupported_protocol", "Unknown protocol");
|
|
5260
|
+
}
|
|
5261
|
+
}
|
|
5095
5262
|
export {
|
|
5096
5263
|
DEFAULT_RESPONSES_CODEC_LIMITS,
|
|
5097
5264
|
LLM_PROTOCOLS,
|
|
@@ -5105,5 +5272,8 @@ export {
|
|
|
5105
5272
|
decodeResponsesRequest,
|
|
5106
5273
|
describeProtocolConversion,
|
|
5107
5274
|
encodeResponsesResult,
|
|
5108
|
-
getProviderTokenAccountingCapabilities
|
|
5275
|
+
getProviderTokenAccountingCapabilities,
|
|
5276
|
+
inspectAgentMessages,
|
|
5277
|
+
protocolRequestPath,
|
|
5278
|
+
replaceAgentMessageEncryptedContent
|
|
5109
5279
|
};
|
|
@@ -19,6 +19,8 @@ export interface ProtocolSessionCapabilities extends ResponsesCodecCapabilities
|
|
|
19
19
|
geminiJsonSchema?: boolean;
|
|
20
20
|
}
|
|
21
21
|
export interface ProtocolSessionContext {
|
|
22
|
+
/** Request-scoped opt-in; absence preserves strict collaboration handling. */
|
|
23
|
+
agentMessageDelivery?: ResponsesCodecCapabilities['agentMessageDelivery'];
|
|
22
24
|
sourceProtocol: LLMProtocol;
|
|
23
25
|
targetProtocol: LLMProtocol;
|
|
24
26
|
/** Target model; caller owns routing and source URL validation. */
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type JsonRecord, type ResponsesCodecLimits, type ResponsesConversionDiagnostic } from './common';
|
|
2
|
+
export type AgentMessageDelivery = 'plaintext';
|
|
3
|
+
export interface AgentMessageInspection {
|
|
4
|
+
inputIndex: number;
|
|
5
|
+
author: string;
|
|
6
|
+
recipient: string;
|
|
7
|
+
encryptedContents: Array<{
|
|
8
|
+
contentIndex: number;
|
|
9
|
+
param: string;
|
|
10
|
+
}>;
|
|
11
|
+
}
|
|
12
|
+
export interface AgentMessagePlaintextReplacement {
|
|
13
|
+
param: string;
|
|
14
|
+
text: string;
|
|
15
|
+
}
|
|
16
|
+
export interface AgentMessageInspectionResult {
|
|
17
|
+
messages: AgentMessageInspection[];
|
|
18
|
+
diagnostics: ResponsesConversionDiagnostic[];
|
|
19
|
+
}
|
|
20
|
+
/** Validate identities as metadata, never as instruction roles. Inspection permits opaque blocks only to locate them. */
|
|
21
|
+
export declare function validateAgentMessage(raw: unknown, path: string, configuredLimits?: Partial<ResponsesCodecLimits>, allowEncrypted?: boolean): JsonRecord;
|
|
22
|
+
/** Pure bounded inspection. Neither plaintext nor opaque content is returned in diagnostics. */
|
|
23
|
+
export declare function inspectAgentMessages(raw: unknown, options?: {
|
|
24
|
+
limits?: Partial<ResponsesCodecLimits>;
|
|
25
|
+
}): AgentMessageInspectionResult;
|
|
26
|
+
/** Replace only explicitly selected agent opaque blocks, at their original positions. Other history is untouched. */
|
|
27
|
+
export declare function replaceAgentMessageEncryptedContent(raw: unknown, replacements: readonly AgentMessagePlaintextReplacement[], options?: {
|
|
28
|
+
limits?: Partial<ResponsesCodecLimits>;
|
|
29
|
+
}): {
|
|
30
|
+
body: JsonRecord;
|
|
31
|
+
diagnostics: ResponsesConversionDiagnostic[];
|
|
32
|
+
};
|
|
33
|
+
export declare function validateEncryptedFunctionArgs(item: JsonRecord, delivery: AgentMessageDelivery | undefined, path: string): void;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AgentMessageDelivery } from './agent-messages';
|
|
1
2
|
export type ResponsesProtocol = 'chat_completions' | 'anthropic_messages';
|
|
2
3
|
export type JsonRecord = Record<string, unknown>;
|
|
3
4
|
export interface ResponsesToolName {
|
|
@@ -15,6 +16,8 @@ export interface ResponsesCodecLimits {
|
|
|
15
16
|
}
|
|
16
17
|
export declare const DEFAULT_RESPONSES_CODEC_LIMITS: Readonly<ResponsesCodecLimits>;
|
|
17
18
|
export interface ResponsesCodecCapabilities {
|
|
19
|
+
/** Explicit caller policy for plaintext collaboration across protocols. */
|
|
20
|
+
agentMessageDelivery?: AgentMessageDelivery;
|
|
18
21
|
reasoningEffort?: boolean;
|
|
19
22
|
/** Plain reasoning history support is independent of effort parameter mapping. */
|
|
20
23
|
reasoningHistory?: boolean;
|
|
@@ -59,5 +62,7 @@ export interface Terminal {
|
|
|
59
62
|
}
|
|
60
63
|
export declare function terminal(reason: unknown, protocol: ResponsesProtocol): Terminal;
|
|
61
64
|
export declare function normalizeUsage(raw: unknown, protocol: ResponsesProtocol, prior?: JsonRecord): JsonRecord;
|
|
62
|
-
export declare function
|
|
65
|
+
export declare function isCollaborationFunction(name: unknown, namespace: unknown, custom?: boolean): boolean;
|
|
66
|
+
export declare function plaintextCollaborationCall(item: JsonRecord, delivery?: AgentMessageDelivery): JsonRecord;
|
|
67
|
+
export declare function toolItem(id: string, name: string, args: string, names: ResponsesToolNames, status?: string, agentMessageDelivery?: AgentMessageDelivery): JsonRecord;
|
|
63
68
|
export declare function responsePayload(id: string, model: string, output: JsonRecord[], end: Terminal, usage: JsonRecord, error?: unknown, createdAt?: number): JsonRecord;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AgentMessageDelivery } from './agent-messages';
|
|
1
2
|
import { type JsonRecord, type ResponsesCodecLimits, type ResponsesProtocol, type ResponsesToolNames } from './common';
|
|
2
3
|
/** Incremental payload codec. SSE framing and transport cancellation remain caller-owned.
|
|
3
4
|
* Chat terminal is deferred until finish() so trailing usage-only chunks are retained.
|
|
@@ -28,6 +29,7 @@ export declare class ResponsesEventEncoder {
|
|
|
28
29
|
private contentSerial;
|
|
29
30
|
constructor(protocol: ResponsesProtocol, model: string, toolNames?: ResponsesToolNames, configuredLimits?: Partial<ResponsesCodecLimits>, options?: {
|
|
30
31
|
preserveContentOrder?: boolean;
|
|
32
|
+
agentMessageDelivery?: AgentMessageDelivery;
|
|
31
33
|
});
|
|
32
34
|
private contentState;
|
|
33
35
|
private event;
|
|
@@ -2,3 +2,4 @@ export { decodeResponsesRequest, type DecodedResponsesRequest } from './request'
|
|
|
2
2
|
export { encodeResponsesResult } from './result';
|
|
3
3
|
export { ResponsesEventEncoder } from './events';
|
|
4
4
|
export { ResponsesCodecError, DEFAULT_RESPONSES_CODEC_LIMITS, type ResponsesProtocol, type ResponsesToolName, type ResponsesToolNames, type ResponsesCodecCapabilities, type ResponsesCodecLimits, type ResponsesConversionDiagnostic } from './common';
|
|
5
|
+
export { inspectAgentMessages, replaceAgentMessageEncryptedContent, type AgentMessageDelivery, type AgentMessageInspection, type AgentMessageInspectionResult, type AgentMessagePlaintextReplacement } from './agent-messages';
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import type { AgentMessageDelivery } from './agent-messages';
|
|
1
2
|
import { type JsonRecord, type ResponsesCodecLimits, type ResponsesProtocol, type ResponsesToolNames } from './common';
|
|
2
3
|
/** JSON and SSE share the same validation and item/terminal semantics. */
|
|
3
|
-
export declare function encodeResponsesResult(raw: unknown, protocol: ResponsesProtocol, model: string, toolNames?: ResponsesToolNames, configuredLimits?: Partial<ResponsesCodecLimits
|
|
4
|
+
export declare function encodeResponsesResult(raw: unknown, protocol: ResponsesProtocol, model: string, toolNames?: ResponsesToolNames, configuredLimits?: Partial<ResponsesCodecLimits>, options?: {
|
|
5
|
+
agentMessageDelivery?: AgentMessageDelivery;
|
|
6
|
+
}): JsonRecord;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jeffusion/bungee-llms",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.23.1",
|
|
4
4
|
"description": "Extensible LLM adapters and protocol conversion package for Bungee",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"prepublishOnly": "bun run build"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@jeffusion/bungee-types": "^5.
|
|
14
|
+
"@jeffusion/bungee-types": "^5.23.1"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
17
|
"bun-types": "^1.3.2",
|