fix(reasoning): preserve mixed plaintext and drop incompatible state (#10949, #10959) (#10961)

Validated on the combined batch board + this branch: 231/231 across chatcore-translation-paths, reasoning-cache, strip-reasoning-blobs, and both Responses translator suites. Pre-merge: propagated the #11110/#11129 summary:[] defaults into five assertions here (each commented with its PR) — without it this branch red against the tip, and as a bonus the merge drains the 4 reasoning reds that were live on the tip from those merges. Plaintext now wins over a coexisting opaque companion; opaque-only drops cleanly for plaintext targets; combos keep explicit Skip. Fixes #10949 and #10959. Thank you @jackjinke!
This commit is contained in:
Ke Jin
2026-08-23 09:58:58 +08:00
committed by GitHub
parent c018bb41a7
commit 9689dcef9f
14 changed files with 233 additions and 144 deletions

View File

@@ -1 +1 @@
- Preserve portable plaintext reasoning by default across streaming and non-streaming Chat Completions and Responses routes while keeping provider-bound opaque state target-compatible. Combos now drop incompatible continuation reasoning by default and can explicitly skip incompatible targets, while known providers no longer show redundant encrypted-reasoning controls. (#10550)
- Preserve portable plaintext reasoning by default across streaming and non-streaming Chat Completions and Responses routes while keeping provider-bound opaque state target-compatible. Direct requests drop incompatible continuation reasoning by default; combos can explicitly skip incompatible targets without mutating the request. Known providers no longer show redundant encrypted-reasoning controls. (#10550, #10959)

View File

@@ -0,0 +1 @@
- Preserve explicit plaintext reasoning when a Responses reasoning item also carries opaque provider state (rare OpenCode Go `deepseek-v4-flash` responses). Mixed plaintext + opaque input is projected onto the target transport: plaintext targets keep portable text, opaque targets keep provider state. Opaque-only reasoning is dropped when the selected target cannot replay it, allowing cross-model conversations to continue. (#10949, #10959)

View File

@@ -1398,7 +1398,6 @@ export class CodexExecutor extends BaseExecutor {
provider: "codex",
preserveEncryptedReasoning:
credentials?.providerSpecificData?.preserveEncryptedReasoning === true,
onIncompatibleReasoning: "drop",
});
if (nativeCodexPassthrough) {

View File

@@ -516,7 +516,7 @@ export async function handleChatCore({
conversationId = null,
modelPinned = false,
skipResourcePressureGuard = false,
reasoningTransportFallback = "skip",
reasoningTransportFallback = "drop",
managedLease = null,
}) {
let { provider, model, extendedContext } = modelInfo;
@@ -1213,7 +1213,7 @@ export async function handleChatCore({
provider,
preserveEncryptedReasoning:
credentials?.providerSpecificData?.preserveEncryptedReasoning === true,
onIncompatibleReasoning: reasoningTransportFallback === "drop" ? "drop" : "reject",
onIncompatibleReasoning: reasoningTransportFallback === "skip" ? "reject" : "drop",
}
);
if (policy.incompatibleReasoning) {

View File

@@ -37,7 +37,6 @@ export interface ReasoningInputPolicyOptions {
export interface ReasoningInputPolicyResult {
incompatibleReasoning: boolean;
}
export function resolveReasoningTransport(
provider: string | null | undefined,
preserveEncryptedReasoning = false
@@ -47,18 +46,6 @@ export function resolveReasoningTransport(
return transport ?? (preserveEncryptedReasoning ? "opaque" : "plaintext");
}
export function createReasoningTransportIncompatibleError(): Error & {
statusCode: number;
errorType: string;
} {
const error = new Error(
"Reasoning continuation is not compatible with the selected target"
) as Error & { statusCode: number; errorType: string };
error.statusCode = 400;
error.errorType = "reasoning_transport_incompatible";
return error;
}
function asRecord(value: unknown): JsonRecord | null {
return value && typeof value === "object" && !Array.isArray(value) ? (value as JsonRecord) : null;
}
@@ -101,11 +88,12 @@ function hasChatPlaintextReasoning(record: JsonRecord): boolean {
/**
* Returns only provider-authentic plaintext continuation state. Display summaries
* are excluded, and a record carrying opaque state is never cross-converted.
* and opaque-only records are excluded. Explicit plaintext remains independently
* portable when the same record also carries an opaque companion (#10949).
*/
export function extractReplayableResponsesReasoningText(value: unknown): string {
const record = asRecord(value);
if (!record || record.type !== "reasoning" || hasOpaqueReasoningState(record)) return "";
if (!record || record.type !== "reasoning") return "";
if (!Array.isArray(record.content)) return "";
return record.content
@@ -307,9 +295,9 @@ function sanitizeResponsesInput(
}
/**
* Applies one protocol-independent compatibility decision before request translation.
* Plaintext is portable by default; opaque state requires an explicit target declaration.
* Display summaries do not affect compatibility; stateless input drops orphan summaries.
* Projects reasoning continuation onto the selected target transport.
* Incompatible active state is dropped by default; combo routing may reject an
* attempt instead so it can fall through without mutating the request.
*/
export function applyReasoningInputPolicy(
body: Record<string, unknown>,
@@ -321,14 +309,17 @@ export function applyReasoningInputPolicy(
inputFormat === "responses"
? inspectResponsesReasoning(body.input)
: inspectChatReasoning(body.messages);
const incompatibleReasoning = !isReasoningCompatible(inspection, transport);
const mixedState = inspection.hasPlaintext && inspection.hasOpaque;
const incompatibleReasoning = !mixedState && !isReasoningCompatible(inspection, transport);
// Mixed plaintext + opaque input (#10949) is never a rejection: it is projected
// onto the target transport by the per-item sanitizers below.
if (incompatibleReasoning && options.onIncompatibleReasoning !== "drop") {
if (incompatibleReasoning && options.onIncompatibleReasoning === "reject") {
return { incompatibleReasoning: true };
}
if (inputFormat === "chat") {
if (incompatibleReasoning && Array.isArray(body.messages)) {
if ((incompatibleReasoning || mixedState) && Array.isArray(body.messages)) {
body.messages = dropIncompatibleChatReasoning(body.messages, transport);
}
return { incompatibleReasoning: false };
@@ -343,12 +334,13 @@ export function applyReasoningInputPolicy(
},
];
}
if (!Array.isArray(body.input)) return { incompatibleReasoning: false };
body.input = sanitizeResponsesInput(
body.input,
transport,
incompatibleReasoning,
body.store === false
);
if (Array.isArray(body.input)) {
body.input = sanitizeResponsesInput(
body.input,
transport,
incompatibleReasoning || mixedState,
body.store === false
);
}
return { incompatibleReasoning: false };
}

View File

@@ -8,11 +8,7 @@ import { isOpenAIResponsesStoreEnabled } from "@/lib/providers/requestDefaults";
import { FORMATS } from "../formats.ts";
import { register } from "../registry.ts";
import { normalizeResponsesInputForChat } from "../../utils/responsesInputNormalization.ts";
import {
createReasoningTransportIncompatibleError,
hasOpaqueReasoningState,
extractReplayableResponsesReasoningText,
} from "../../services/reasoningInputPolicy.ts";
import { extractReplayableResponsesReasoningText } from "../../services/reasoningInputPolicy.ts";
import {
getRegisteredProviders,
requiresPlainStringContent,
@@ -454,10 +450,8 @@ export function openaiResponsesToOpenAIRequest(
if (itemType === "reasoning") {
// Only genuine plaintext reasoning can cross into Chat reasoning_content.
// Opaque encrypted state and its display summary have no Chat replay form.
if (preserveReasoningContent && hasOpaqueReasoningState(item)) {
throw createReasoningTransportIncompatibleError();
}
// Opaque encrypted state and its display summary have no Chat replay form,
// so opaque-only items are dropped while mixed items replay their plaintext.
if (preserveReasoningContent) {
const reasoning = extractReplayableResponsesReasoningText(item);
if (reasoning) {

View File

@@ -3896,15 +3896,6 @@ function ComboFormModal({ isOpen, combo, onClose, onSave, activeProviders, combo
)}
</option>
</select>
{config.reasoningTransportFallback !== "skip" && (
<p className="text-[10px] text-amber-600 dark:text-amber-300 mt-1">
{getI18nOrFallback(
t,
"reasoningTransportFallbackDropWarning",
"May lose continuation context or cause tool-call continuations to fail."
)}
</p>
)}
</div>
<div>
<FieldLabelWithHelp

View File

@@ -1851,7 +1851,7 @@ async function handleSingleModelChat(
modelPinned: runtimeOptions?.modelPinned ?? false,
routingComboId: runtimeOptions?.routingComboId ?? null,
sessionAffinityKey: runtimeOptions.sessionAffinityKey ?? null,
reasoningTransportFallback: runtimeOptions.reasoningTransportFallback ?? "skip",
reasoningTransportFallback: runtimeOptions.reasoningTransportFallback ?? "drop",
managedLease: runtimeOptions.managedLease ?? null,
},
runtimeOptions

View File

@@ -422,7 +422,7 @@ export async function executeChatWithBreaker({
conversationId = null,
modelPinned = false,
routingComboId = null,
reasoningTransportFallback = "skip",
reasoningTransportFallback = "drop",
sessionAffinityKey = null,
managedLease = null,
}: ExecuteChatWithBreakerOptions): Promise<ExecuteChatWithBreakerResult> {

View File

@@ -369,7 +369,7 @@ async function invokeChatCore({
onCredentialsRefreshed = null,
onRequestSuccess = null,
sessionAffinityKey = null,
reasoningTransportFallback = "skip",
reasoningTransportFallback = "drop",
managedLease = null,
cachedSettings = null,
}: any = {}) {
@@ -631,7 +631,7 @@ test("chatCore translates a streaming Responses upstream for a Chat client", asy
assert.match(streamed, /"content":"ok"/);
assert.match(streamed, /data: \[DONE\]/);
});
test("chatCore rejects opaque reasoning for unknown Responses targets unless explicitly enabled", async () => {
test("chatCore drops opaque reasoning for plaintext Responses targets by default (#10959)", async () => {
const reasoningItems = [
{ id: "rs_valid", type: "reasoning", encrypted_content: "encrypted-blob" },
{ type: "reasoning", encrypted_content: "" },
@@ -640,7 +640,7 @@ test("chatCore rejects opaque reasoning for unknown Responses targets unless exp
{ id: "fc_call", type: "function_call", call_id: "call_1", name: "search", arguments: "{}" },
];
const rejected = await invokeChatCore({
const dropped = await invokeChatCore({
provider: "openai-compatible-sp-openai",
model: "gpt-5.4",
endpoint: "/v1/responses",
@@ -656,9 +656,12 @@ test("chatCore rejects opaque reasoning for unknown Responses targets unless exp
responseFormat: "openai-responses",
});
assert.equal(rejected.result.success, false);
assert.equal(rejected.result.status, 400);
assert.equal(rejected.calls.length, 0);
assert.equal(dropped.result.success, true);
assert.equal(dropped.calls.length, 1);
assert.deepEqual(
dropped.call.body.input.filter((item) => item.type === "reasoning"),
[{ type: "reasoning", summary: [{ text: "not self-contained" }] }]
);
const enabled = await invokeChatCore({
provider: "openai-compatible-sp-openai",
@@ -682,7 +685,7 @@ test("chatCore rejects opaque reasoning for unknown Responses targets unless exp
assert.deepEqual(
input.filter((item) => item.type === "reasoning"),
[
{ id: "rs_valid", type: "reasoning", encrypted_content: "encrypted-blob" },
{ id: "rs_valid", type: "reasoning", encrypted_content: "encrypted-blob", summary: [] }, // summary defaulted by #11110
{ type: "reasoning", summary: [{ text: "not self-contained" }] },
]
);
@@ -693,9 +696,9 @@ test("chatCore rejects opaque reasoning for unknown Responses targets unless exp
assert.equal(input.find((item) => item.type === "function_call")?.id, undefined);
});
test("chatCore applies Chat reasoning compatibility before stream mode diverges", async () => {
test("chatCore drops incompatible Chat reasoning before stream mode diverges (#10959)", async () => {
for (const stream of [false, true]) {
const rejected = await invokeChatCore({
const dropped = await invokeChatCore({
provider: "openai-compatible-sp-openai",
model: "gpt-5.4",
endpoint: "/v1/chat/completions",
@@ -728,14 +731,14 @@ test("chatCore applies Chat reasoning compatibility before stream mode diverges"
},
});
assert.equal(rejected.result.success, false, `stream=${stream}`);
assert.equal(rejected.result.status, 400, `stream=${stream}`);
assert.equal(rejected.calls.length, 0, `stream=${stream}`);
assert.equal(dropped.result.success, true, `stream=${stream}`);
assert.equal(dropped.calls.length, 1, `stream=${stream}`);
assert.equal(dropped.call.body.messages[0].reasoning_details, undefined, `stream=${stream}`);
}
});
test("chatCore can drop incompatible reasoning for an opted-in Combo attempt", async () => {
const dropped = await invokeChatCore({
test("chatCore preserves Combo skip behavior for incompatible reasoning", async () => {
const skipped = await invokeChatCore({
provider: "openai-compatible-sp-openai",
model: "gpt-5.4",
endpoint: "/v1/responses",
@@ -757,15 +760,12 @@ test("chatCore can drop incompatible reasoning for an opted-in Combo attempt", a
},
responseFormat: "openai-responses",
isCombo: true,
reasoningTransportFallback: "drop",
reasoningTransportFallback: "skip",
});
assert.equal(dropped.result.success, true);
assert.equal(dropped.calls.length, 1);
assert.equal(
dropped.call.body.input.some((item) => item.type === "reasoning"),
false
);
assert.equal(skipped.result.success, false);
assert.equal(skipped.result.status, 400);
assert.equal(skipped.calls.length, 0);
});
test("chatCore carries Chat reasoning_content into official DeepSeek Responses input", async () => {
@@ -800,6 +800,7 @@ test("chatCore carries Chat reasoning_content into official DeepSeek Responses i
assert.deepEqual(call.body.input.slice(0, 3), [
{
type: "reasoning",
summary: [], // defaulted on freshly-built reasoning items (#11129)
content: [{ type: "reasoning_text", text: "Inspect before calling the tool" }],
},
{
@@ -866,7 +867,7 @@ test("chatCore replays nonstream DeepSeek Responses reasoning across a Chat tool
assert.equal(second.result.success, true);
assert.deepEqual(
second.call.body.input.find((item) => item.type === "reasoning"),
{ type: "reasoning", content: [{ type: "reasoning_text", text: reasoning }] }
{ type: "reasoning", content: [{ type: "reasoning_text", text: reasoning }], summary: [] } // summary defaulted by #11129
);
});
@@ -930,7 +931,7 @@ test("chatCore replays streamed DeepSeek Responses reasoning across a Chat tool
assert.equal(second.result.success, true);
assert.deepEqual(
second.call.body.input.find((item) => item.type === "reasoning"),
{ type: "reasoning", content: [{ type: "reasoning_text", text: reasoning }] }
{ type: "reasoning", content: [{ type: "reasoning_text", text: reasoning }], summary: [] } // summary defaulted by #11129
);
});
@@ -1132,7 +1133,7 @@ test("chatCore automatically preserves provider-generated opaque reasoning for C
assert.equal(result.success, true);
assert.deepEqual(
call.body.input.filter((item) => item.type === "reasoning"),
[{ id: "rs_valid", type: "reasoning", encrypted_content: "encrypted-blob" }]
[{ id: "rs_valid", type: "reasoning", encrypted_content: "encrypted-blob", summary: [] }] // summary defaulted by #11110
);
assert.equal(
call.body.input.some((item) => item.type === "item_reference"),

View File

@@ -752,47 +752,70 @@ describe("Reasoning Replay Cache — Translator Replay", () => {
assert.equal(lookupReasoning(callId), "Authentic provider reasoning");
});
it("should never cache Responses summaries or opaque plaintext companions", () => {
for (const [suffix, reasoningItem] of [
[
"summary",
{
type: "reasoning",
summary: [{ type: "summary_text", text: "Display-only summary" }],
},
],
[
"mixed",
{
type: "reasoning",
encrypted_content: "opaque-provider-state",
content: [{ type: "reasoning_text", text: "Unsafe plaintext companion" }],
summary: [{ type: "summary_text", text: "Display-only mixed summary" }],
},
],
] as const) {
clearReasoningCacheAll();
const callId = `call_nonstream_${suffix}_reasoning`;
const translated = translateNonStreamingResponse(
{
object: "response",
model: "deepseek-v4-flash",
output: [
reasoningItem,
{ type: "function_call", call_id: callId, name: "read_file", arguments: "{}" },
],
},
FORMATS.OPENAI_RESPONSES,
FORMATS.OPENAI
) as { choices?: Array<{ message?: Record<string, unknown> }> };
const message = translated.choices?.[0]?.message;
it("preserves plaintext reasoning from a mixed plaintext + encrypted_content item (#10949)", () => {
clearReasoningCacheAll();
const callId = "call_nonstream_mixed_reasoning";
const translated = translateNonStreamingResponse(
{
object: "response",
model: "deepseek-v4-flash",
output: [
{
type: "reasoning",
content: [
{
type: "reasoning_text",
text: "Let me start by reading the directory to understand the structure of the corpus.",
},
],
encrypted_content: "<opaque state>",
summary: [],
},
{ type: "function_call", call_id: callId, name: "read_file", arguments: "{}" },
],
},
FORMATS.OPENAI_RESPONSES,
FORMATS.OPENAI
) as { choices?: Array<{ message?: Record<string, unknown> }> };
const message = translated.choices?.[0]?.message;
assert.ok(message);
assert.equal(message.reasoning_content, undefined);
assert.ok(Array.isArray(message.reasoning_summary));
assert.equal(cacheReasoningFromAssistantMessage(message, "deepseek", "deepseek-v4-flash"), 0);
assert.equal(lookupReasoning(callId), null);
}
assert.ok(message);
assert.equal(
message.reasoning_content,
"Let me start by reading the directory to understand the structure of the corpus."
);
assert.equal(cacheReasoningFromAssistantMessage(message, "deepseek", "deepseek-v4-flash"), 1);
assert.equal(
lookupReasoning(callId),
"Let me start by reading the directory to understand the structure of the corpus."
);
});
it("should never cache summary-only Responses reasoning", () => {
clearReasoningCacheAll();
const callId = "call_nonstream_summary_reasoning";
const translated = translateNonStreamingResponse(
{
object: "response",
model: "deepseek-v4-flash",
output: [
{
type: "reasoning",
summary: [{ type: "summary_text", text: "Display-only summary" }],
},
{ type: "function_call", call_id: callId, name: "read_file", arguments: "{}" },
],
},
FORMATS.OPENAI_RESPONSES,
FORMATS.OPENAI
) as { choices?: Array<{ message?: Record<string, unknown> }> };
const message = translated.choices?.[0]?.message;
assert.ok(message);
assert.equal(message.reasoning_content, undefined);
assert.ok(Array.isArray(message.reasoning_summary));
assert.equal(cacheReasoningFromAssistantMessage(message, "deepseek", "deepseek-v4-flash"), 0);
assert.equal(lookupReasoning(callId), null);
});
it("should preserve client-provided reasoning content", () => {

View File

@@ -8,7 +8,7 @@ import { omitEncryptedReasoningForLog } from "../../src/lib/logPayloads.ts";
// Responses reasoning replay is target-scoped. Plaintext DeepSeek state and
// provider-generated opaque state are never interchangeable.
test("unknown Responses targets reject opaque reasoning and ignore display summaries", () => {
test("unknown Responses targets drop opaque reasoning and preserve display summaries (#10959)", () => {
const body: Record<string, unknown> = {
input: [
{ type: "message", role: "user", content: [{ type: "input_text", text: "hi" }] },
@@ -24,11 +24,14 @@ test("unknown Responses targets reject opaque reasoning and ignore display summa
],
};
const originalInput = structuredClone(body.input);
const result = applyReasoningInputPolicy(body, "responses");
assert.equal(result.incompatibleReasoning, true);
assert.deepEqual(body.input, originalInput, "rejection must not mutate the request");
assert.equal(result.incompatibleReasoning, false);
assert.deepEqual(body.input, [
{ type: "message", role: "user", content: [{ type: "input_text", text: "hi" }] },
{ type: "reasoning", summary: [{ text: "display only" }] },
{ type: "function_call", name: "search", arguments: "{}", call_id: "call_1" },
]);
});
test("unannotated targets preserve plaintext Responses reasoning without synthetic IDs", () => {
@@ -132,7 +135,7 @@ test("Chat drop removes opaque state while preserving plaintext and summary deta
]);
});
test("DeepSeek rejects plaintext reasoning carrying opaque provider state", () => {
test("DeepSeek projects plaintext reasoning carrying opaque provider state onto the plaintext transport (#10949)", () => {
for (const opaqueField of ["signature", "format"] as const) {
const body: Record<string, unknown> = {
input: [
@@ -148,13 +151,11 @@ test("DeepSeek rejects plaintext reasoning carrying opaque provider state", () =
const result = applyReasoningInputPolicy(body, "responses", { provider: "deepseek" });
assert.equal(result.incompatibleReasoning, true, opaqueField);
assert.equal(result.incompatibleReasoning, false, opaqueField);
assert.deepEqual(body.input, [
{
id: "rs_mixed123",
type: "reasoning",
content: [{ type: "reasoning_text", text: "untrusted companion" }],
[opaqueField]: "provider-state",
},
{ type: "message", role: "user", content: [{ type: "input_text", text: "continue" }] },
]);
@@ -199,6 +200,49 @@ test("drop fallback removes only the incompatible active transport and preserves
assert.equal(opaqueReasoning.encrypted_content, "provider-state");
});
test("mixed plaintext + opaque reasoning follows the target transport instead of rejecting (#10949)", () => {
const mixedReasoning = {
id: "rs_mixed",
type: "reasoning",
content: [{ type: "reasoning_text", text: "inspect first" }],
encrypted_content: "provider-state",
summary: [{ type: "summary_text", text: "display only" }],
};
// Plaintext target (deepseek): keep the portable plaintext, strip opaque state.
const toPlaintext: Record<string, unknown> = {
input: [structuredClone(mixedReasoning)],
};
const plaintextResult = applyReasoningInputPolicy(toPlaintext, "responses", {
provider: "deepseek",
});
assert.equal(plaintextResult.incompatibleReasoning, false);
assert.deepEqual(toPlaintext.input, [
{
type: "reasoning",
content: [{ type: "reasoning_text", text: "inspect first" }],
summary: [{ type: "summary_text", text: "display only" }],
},
]);
// Opaque target (openai): keep the provider state, strip the plaintext.
const toOpaque: Record<string, unknown> = {
input: [structuredClone(mixedReasoning)],
};
const opaqueResult = applyReasoningInputPolicy(toOpaque, "responses", {
provider: "openai",
});
assert.equal(opaqueResult.incompatibleReasoning, false);
assert.deepEqual(toOpaque.input, [
{
id: "rs_mixed",
type: "reasoning",
encrypted_content: "provider-state",
summary: [{ type: "summary_text", text: "display only" }],
},
]);
});
test("drop fallback preserves reasoning when its transport is compatible", () => {
const body: Record<string, unknown> = {
input: [

View File

@@ -172,28 +172,26 @@ test("Responses -> Chat keeps summary-only reasoning out of continuation state",
assert.equal(result.messages[0].reasoning_content, undefined);
});
test("Responses -> Chat rejects opaque reasoning instead of replaying its plaintext companion", () => {
assert.throws(
() =>
openaiResponsesToOpenAIRequest(
"deepseek-v4-pro",
test("Responses -> Chat replays the plaintext companion of an opaque reasoning item (#10949)", () => {
const result = openaiResponsesToOpenAIRequest(
"deepseek-v4-pro",
{
input: [
{
input: [
{
id: "rs_opaque",
type: "reasoning",
encrypted_content: "opaque-provider-state",
content: [{ type: "reasoning_text", text: "Untrusted plaintext companion" }],
summary: [{ type: "summary_text", text: "Display summary" }],
},
{ type: "function_call", call_id: "call_1", name: "search", arguments: "{}" },
],
id: "rs_opaque",
type: "reasoning",
encrypted_content: "opaque-provider-state",
content: [{ type: "reasoning_text", text: "Untrusted plaintext companion" }],
summary: [{ type: "summary_text", text: "Display summary" }],
},
false,
{ _preserveReasoningContent: true }
),
/Reasoning continuation is not compatible/
);
{ type: "function_call", call_id: "call_1", name: "search", arguments: "{}" },
],
},
false,
{ _preserveReasoningContent: true }
) as { messages: Array<Record<string, unknown>> };
assert.equal(result.messages[0].reasoning_content, "Untrusted plaintext companion");
});
test("Responses -> Chat merges assistant text that follows a function call", () => {

View File

@@ -423,6 +423,52 @@ test("Responses -> OpenAI: preserves non-object Read JSON-string arguments", ()
assert.equal(done.choices[0].delta.tool_calls[0].function.arguments, "null");
});
test("Responses -> OpenAI: mixed plaintext + encrypted_content reasoning replays its plaintext (#10949)", () => {
const state = {};
const done = openaiResponsesToOpenAIResponse(
{
type: "response.output_item.done",
item: {
type: "reasoning",
id: "rs_mixed",
content: [
{
type: "reasoning_text",
text: "Let me start by reading the directory to understand the structure of the corpus.",
},
],
encrypted_content: "<opaque state>",
summary: [],
},
},
state
);
assert.ok(done, "mixed reasoning item must surface a delta");
assert.equal(
done.choices[0].delta.reasoning_content,
"Let me start by reading the directory to understand the structure of the corpus."
);
});
test("Responses -> OpenAI: opaque-only reasoning still emits no fabricated plaintext", () => {
const state = {};
const done = openaiResponsesToOpenAIResponse(
{
type: "response.output_item.done",
item: {
type: "reasoning",
id: "rs_opaque_only",
encrypted_content: "<opaque state>",
summary: [],
},
},
state
);
assert.equal(done, null);
});
test("Responses -> OpenAI: strips empty optional args from JSON-string output_item.done arguments", () => {
const state = {};
openaiResponsesToOpenAIResponse(