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
17 changes: 17 additions & 0 deletions src/adapters/openai-responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,20 @@ function stripUnsupportedReasoningParams(body: unknown): unknown {
return { ...body, reasoning: Object.keys(rest).length > 0 ? rest : undefined };
}

/**
* GPT-5.6 replaced the legacy 24-hour retention field with `prompt_cache_options.ttl`.
* Do not translate `24h` to the new field: GPT-5.6 currently accepts a different TTL contract,
* and implicit caching remains available when the caller did not send the replacement options.
*/
function stripDeprecatedPromptCacheRetention(body: unknown, modelId: unknown): unknown {
if (!isPlainObject(body)) return body;
if (typeof modelId !== "string") return body;
if (modelId !== "gpt-5.6" && !modelId.startsWith("gpt-5.6-")) return body;
if (!Object.hasOwn(body, "prompt_cache_retention")) return body;
const { prompt_cache_retention: _retention, ...rest } = body;
return rest;
}

/**
* A false model capability prevents Codex from emitting summary fields after the catalog refresh.
* Strip them here as well so an already-running client with a stale catalog cannot keep sending an
Expand Down Expand Up @@ -1491,6 +1505,9 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
}
if (forward) {
outBody = stripUnsupportedForwardParams(outBody);
if (isCanonicalOpenAiForwardProvider(provider)) {
outBody = stripDeprecatedPromptCacheRetention(outBody, parsed.modelId);
}
} else {
outBody = preferConfiguredHostedTools(
outBody,
Expand Down
97 changes: 97 additions & 0 deletions tests/openai-responses-passthrough.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -822,6 +822,57 @@ describe("OpenAI Responses passthrough sanitization", () => {
expect(body.prompt_cache_retention).toBe("24h");
});

test.each([
"gpt-5.6",
"gpt-5.6-sol",
"gpt-5.6-terra",
"gpt-5.6-luna",
])("drops deprecated prompt_cache_retention for %s without inventing replacement options", modelId => {
const adapter = createResponsesPassthroughAdapter(provider);
const request = adapter.buildRequest({
modelId,
context: { messages: [] },
stream: true,
options: {},
_rawBody: {
model: modelId,
input: "hi",
prompt_cache_retention: "24h",
},
}, { headers: new Headers({ authorization: "Bearer token" }) });
const body = JSON.parse(request.body) as {
prompt_cache_retention?: string;
prompt_cache_options?: unknown;
};

expect(body.prompt_cache_retention).toBeUndefined();
expect(body.prompt_cache_options).toBeUndefined();
});

test("preserves caller prompt_cache_options while dropping GPT-5.6 legacy retention", () => {
const adapter = createResponsesPassthroughAdapter(provider);
const promptCacheOptions = { mode: "explicit", ttl: "30m" };
const request = adapter.buildRequest({
modelId: "gpt-5.6-sol",
context: { messages: [] },
stream: true,
options: {},
_rawBody: {
model: "gpt-5.6-sol",
input: "hi",
prompt_cache_retention: "24h",
prompt_cache_options: promptCacheOptions,
},
}, { headers: new Headers({ authorization: "Bearer token" }) });
const body = JSON.parse(request.body) as {
prompt_cache_retention?: string;
prompt_cache_options?: unknown;
};

expect(body.prompt_cache_retention).toBeUndefined();
expect(body.prompt_cache_options).toEqual(promptCacheOptions);
});

const expandedRawBody = {
model: "gpt-5.5",
previous_response_id: "resp_1",
Expand Down Expand Up @@ -2017,6 +2068,52 @@ describe("OpenAI Responses forward-mode unsupported param stripping", () => {
expect(body.max_output_tokens).toBe(32000);
expect(body.metadata).toEqual({ user_id: "u-1" });
});

test("key-auth custom endpoints preserve GPT-5.6 prompt_cache_retention", () => {
const adapter = createResponsesPassthroughAdapter({
adapter: "openai-responses",
baseUrl: "https://api.openai.example/v1",
authMode: "key",
apiKey: "sk-test",
});
const request = adapter.buildRequest({
modelId: "gpt-5.6-sol",
context: { messages: [] },
stream: true,
options: {},
_rawBody: {
model: "gpt-5.6-sol",
input: "hi",
prompt_cache_retention: "24h",
},
}, { headers: new Headers() });
const body = JSON.parse(request.body) as { prompt_cache_retention?: string };

expect(body.prompt_cache_retention).toBe("24h");
});

test("noncanonical forward endpoints preserve GPT-5.6 prompt_cache_retention", () => {
const adapter = createResponsesPassthroughAdapter({
adapter: "openai-responses",
baseUrl: "https://provider.example/v1",
authMode: "forward",
headers: { authorization: "Bearer provider-static" },
});
const request = adapter.buildRequest({
modelId: "gpt-5.6-sol",
context: { messages: [] },
stream: true,
options: {},
_rawBody: {
model: "gpt-5.6-sol",
input: "hi",
prompt_cache_retention: "24h",
},
}, { headers: new Headers() });
const body = JSON.parse(request.body) as { prompt_cache_retention?: string };

expect(body.prompt_cache_retention).toBe("24h");
});
});

describe("openaiResponsesUrl", () => {
Expand Down
Loading