Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/adapters/openai-responses/passthrough.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import {
import {
createAdapterTierMetadata,
} from "../../providers/fastwire";
import { dropResponsesReasoningInputItems, mapRoutedResponsesReasoningEffort, normalizeConfiguredReasoningSummaryDelivery, sanitizeReasoningInputContent, stripDisabledReasoningSummaries, stripDisabledVerbosity, stripUnsupportedReasoningSummaryDelivery } from "./reasoning";
import { dropResponsesReasoningInputItems, mapRoutedResponsesReasoningEffort, normalizeConfiguredReasoningSummaryDelivery, sanitizeReasoningInputContent, stripDisabledReasoningSummaries, stripDisabledVerbosity, stripNoneReasoningSummary, stripUnsupportedReasoningSummaryDelivery } from "./reasoning";
import { scrubOcxCompactionItems, stripCanonicalOnlyToolFields, stripCanonicalOnlyTopLevelFields, stripInternalChatMessageMetadataPassthrough, stripInvalidItemIds, stripItemIdsWhenUnstored, stripRejectedSamplingParams } from "./request-strips";
import { stripCanonicalForwardPromptCacheOptions, stripDeprecatedPromptCacheRetention } from "./prompt-cache";
import { isPlainObject } from "./internal";
Expand Down Expand Up @@ -352,6 +352,7 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
outBody = repairOversizedReplayCallIds(outBody);
}
outBody = stripUnsupportedReasoningSummaryDelivery(outBody, parsed.modelId);
outBody = stripNoneReasoningSummary(outBody);
// #4587: on a bridged provider, hand the destination back the search call and result the
// proxy executed on its behalf, in place of the hosted cell the caller replays. Scoped to
// its exact conversation and serving identity and recorded by the bridge itself, so a
Expand Down
15 changes: 15 additions & 0 deletions src/adapters/openai-responses/reasoning.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,21 @@ export function sanitizeReasoningInputContent(
return changed ? { ...raw, input } : body;
}

/**
* `reasoning.summary: "none"` is an internal "hide the thinking summary" marker: Claude's
* `thinking.display: "omitted"` maps to it, and the parser reads it into
* `options.hideThinkingSummary`. It is not a Responses API value (valid: auto, concise,
* detailed), so drop it from the body that is serialized to the upstream.
*/
export function stripNoneReasoningSummary(body: unknown): unknown {
if (!isPlainObject(body) || !isPlainObject(body.reasoning) || body.reasoning.summary !== "none") return body;
const { summary: _summary, ...rest } = body.reasoning;
const next = { ...body };
if (Object.keys(rest).length > 0) next.reasoning = rest;
else delete next.reasoning;
return next;
}

export function stripUnsupportedReasoningSummaryDelivery(body: unknown, modelId: string): unknown {
if (catalogModelSupportsReasoningSummaries(modelId) !== false) return body;
if (!isPlainObject(body) || !isPlainObject(body.stream_options)) return body;
Expand Down
28 changes: 28 additions & 0 deletions tests/responses/openai-responses-passthrough.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1825,6 +1825,34 @@ describe("OpenAI Responses passthrough sanitization", () => {
expect(body.reasoning).toEqual({ effort: "high" });
});

test("Claude omitted thinking display never puts summary:none on the Responses wire", () => {
const claudeBody = anthropicToResponsesBody({
model: "gpt-6-astra",
max_tokens: 10,
messages: [{ role: "user", content: "hi" }],
thinking: { type: "adaptive", display: "omitted" },
output_config: { effort: "high" },
});
// The translator still emits the marker, so the parser can hide the thinking summary.
expect((claudeBody as { reasoning?: unknown }).reasoning).toEqual({ summary: "none", effort: "high" });

for (const target of [
{ ...provider, baseUrl: "https://chatgpt.com/backend-api/codex", authMode: "forward" as const },
{ adapter: "openai-responses", baseUrl: "https://api.openai.com/v1", authMode: "key" as const, apiKey: "sk-test" },
]) {
const parsed = parseRequest(claudeBody);
expect(parsed.options.hideThinkingSummary).toBe(true);
const request = createResponsesPassthroughAdapter(target).buildRequest(parsed, { headers: new Headers() });
const wire = JSON.parse(request.body) as { reasoning?: Record<string, unknown> };
expect(wire.reasoning).toEqual({ effort: "high" });
}

// A summary-only marker leaves no empty reasoning object behind.
const summaryOnly = parseRequest({ model: "gpt-6-astra", input: "hi", reasoning: { summary: "none" } });
const wire = JSON.parse(createResponsesPassthroughAdapter(provider).buildRequest(summaryOnly, { headers: new Headers() }).body);
expect(wire.reasoning).toBeUndefined();
});

function routedXaiResponsesProvider() {
const entry = getProviderRegistryEntry("xai")!;
const route = routeModel({
Expand Down
Loading