Skip to content
Merged
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
6 changes: 6 additions & 0 deletions docs-site/src/content/docs/reference/adapters.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,12 @@ API, the adapter removes item-level `extra_content` from the outgoing HTTP reque
create. Native `/responses/compact` applies the same projection. The original history keeps the Google
thought signature for later Google turns. Other Responses gateways retain their extensions.

OpenAI-operated destinations receive replayed `url_citation` annotations with missing or invalid
titles and indices repaired. This applies to ordinary HTTP/WebSocket requests and native
`/responses/compact`. Custom gateways keep the original annotations even when
`supportsNativeCompactEndpoint: true` enables native compaction. Citation repair leaves the
client's stored history unchanged.

The adapter preserves the incoming client's `User-Agent` as a fallback in both auth modes because
some Responses-compatible providers use the Codex client fingerprint for compatibility behavior.
An explicitly configured provider `User-Agent` remains authoritative regardless of header casing;
Expand Down
4 changes: 2 additions & 2 deletions src/adapters/openai-responses/passthrough.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ import {
emittedFastWire,
} from "../../providers/fastwire";
import { dropResponsesReasoningInputItems, mapRoutedResponsesReasoningEffort, normalizeConfiguredReasoningSummaryDelivery, sanitizeReasoningInputContent, stripDisabledReasoningSummaries, stripDisabledVerbosity, stripNoneReasoningSummary, stripUnsupportedReasoningSummaryDelivery } from "./reasoning";
import { scrubOcxCompactionItems, stripCanonicalOnlyInputItems, stripCanonicalOnlyToolFields, stripCanonicalOnlyTopLevelFields, stripInternalChatMessageMetadataPassthrough, stripInvalidItemIds, stripItemIdsWhenUnstored, stripProviderInputItemMetadata, stripRejectedSamplingParams } from "./request-strips";
import { repairUrlCitationAnnotations, scrubOcxCompactionItems, stripCanonicalOnlyInputItems, stripCanonicalOnlyToolFields, stripCanonicalOnlyTopLevelFields, stripInternalChatMessageMetadataPassthrough, stripInvalidItemIds, stripItemIdsWhenUnstored, stripProviderInputItemMetadata, stripRejectedSamplingParams } from "./request-strips";
import { notePlaintextReasoningDropped } from "./plaintext-reasoning-notice";
import { stripCanonicalForwardPromptCacheOptions, stripDeprecatedPromptCacheRetention } from "./prompt-cache";
import { isPlainObject } from "./internal";
Expand Down Expand Up @@ -315,7 +315,7 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
outBody = repairLegacyDottedToolCallNames(outBody);
// Shared HTTP/WS outbound projection, including concrete combo/fallback destinations.
if (isOpenAiOperatedResponsesDestination(provider)) {
outBody = stripProviderInputItemMetadata(outBody);
outBody = repairUrlCitationAnnotations(stripProviderInputItemMetadata(outBody));
}
if (!isCanonicalOpenAiForwardProvider(provider)) {
if (provider.preserveResponsesMessageMetadata !== true) {
Expand Down
2 changes: 1 addition & 1 deletion src/adapters/openai-responses/request-strips.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export function stripProviderInputItemMetadata(body: unknown): unknown {
input ??= [...body.input];
input[index] = next;
}
return repairUrlCitationAnnotations(input ? { ...body, input } : body);
return input ? { ...body, input } : body;
}

function isUrlCitationIndex(value: unknown): value is number {
Expand Down
7 changes: 4 additions & 3 deletions src/server/responses/compact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { parseRequest } from "../../responses/parser";
import { buildCompactV1Output, COMPACT_PROMPT, decodeCompactionSummary, extractCompactUserMessages } from "../../responses/compaction";
import { applyReasoningRetention, appendRetentionNotice, type ReasoningRetentionResult } from "../../responses/reasoning-retention";
import { FORWARD_HEADERS, sanitizeReasoningInputContent } from "../../adapters/openai-responses";
import { stripProviderInputItemMetadata } from "../../adapters/openai-responses/request-strips";
import { repairUrlCitationAnnotations, stripProviderInputItemMetadata } from "../../adapters/openai-responses/request-strips";
import { expandPreviousResponseInput, previousResponseProviderState, rememberResponseState } from "../../responses/state";
import { repairLegacyDottedToolCallNames } from "../../responses/legacy-dotted-tool-name-repair";
import { NoEligiblePolicyCandidateError, routeCompactionModel } from "../../router";
Expand Down Expand Up @@ -949,7 +949,7 @@ export async function handleResponsesCompact(
// #5095: the compact endpoint forwards `raw` directly, so it needs the same legacy dotted
// call-name repair the adapter applies. A damaged item here refuses the compaction itself,
// which is the request a long task depends on to keep going.
// This branch is OpenAI-only and bypasses the adapter's Google replay projection (#7111).
// Direct compact bypasses the adapter's Google replay projection (#7111).
const compactBody = repairLegacyDottedToolCallNames(
stripProviderInputItemMetadata(sanitizeReasoningInputContent(compactBodyRaw, {
preserveRawReasoningContent: !isOpenAiOperatedResponsesDestination(compactProvider)
Expand Down Expand Up @@ -1160,8 +1160,9 @@ export async function handleResponsesCompact(
if (!isOpenAiOperatedResponsesDestination(sendProvider) && sendProvider.preserveResponsesMessageMetadata !== true) {
replayBody = stripInternalChatMessageMetadataPassthrough(replayBody) as typeof replayBody;
}
// A queued key selection can refresh the destination; project only for this send.
return isOpenAiOperatedResponsesDestination(sendProvider)
? replayBody : stripCanonicalOnlyInputItems(stripCanonicalOnlyTopLevelFields(replayBody));
? repairUrlCitationAnnotations(replayBody) : stripCanonicalOnlyInputItems(stripCanonicalOnlyTopLevelFields(replayBody));
};
const doFetch = (upstreamRecovery?: UpstreamSendRecovery) => fetchWithHeaderTimeout(
compactUrl,
Expand Down
5 changes: 4 additions & 1 deletion structure/INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ The wire surfaces a client actually talks to.
| [`transports/anthropic-thinking-recovery.md`](transports/anthropic-thinking-recovery.md) | One-shot exact signature-binding recovery before output, and successful-retry memo boundaries. |
| [`transports/responses.md`](transports/responses.md) | The Responses HTTP/SSE endpoint, dispatch, credential and upload boundaries, and core module ownership. |
| [`transports/native-reasoning-retention.md`](transports/native-reasoning-retention.md) | Optional native reasoning ciphertext forwarding on model/account switches, configuration and management controls. |
| [`transports/responses-citations.md`](transports/responses-citations.md) | Destination-gated citation repair for ordinary Responses and every native compact send. |
| [`transports/responses-wire-shapes.md`](transports/responses-wire-shapes.md) | Mixed-wire model defaults, xAI agent-message continuation, declared-tool membership, and passthrough SSE stream shapes. |
| [`transports/responses-failover.md`](transports/responses-failover.md) | Upstream reset retry, the ambiguous-resend gate, combo failover and commit boundaries, compaction routing, and output headroom. |
| [`transports/responses-combo-boundaries.md`](transports/responses-combo-boundaries.md) | Combo preflight output commitment, turn lease lifetime, and bounded hop warnings. |
Expand Down Expand Up @@ -179,7 +180,8 @@ A source area can be described by more than one doc, because these docs are orga
| `src/adapters/openai-chat.ts` | [`providers/mimo-bare-calls.md`](providers/mimo-bare-calls.md) |
| `src/adapters/openai-chat/` | [`providers/mimo-bare-calls.md`](providers/mimo-bare-calls.md) |
| `src/adapters/openai-responses/opengateway-input.ts` | [`providers/opengateway.md`](providers/opengateway.md) |
| `src/adapters/openai-responses/passthrough.ts` | [`providers/opengateway.md`](providers/opengateway.md) |
| `src/adapters/openai-responses/passthrough.ts` | [`transports/responses-citations.md`](transports/responses-citations.md)<br>[`providers/opengateway.md`](providers/opengateway.md) |
| `src/adapters/openai-responses/request-strips.ts` | [`transports/responses-citations.md`](transports/responses-citations.md) |
| `src/adapters/workbuddy-schema.ts` | [`providers/workbuddy.md`](providers/workbuddy.md) |
| `src/adapters/workbuddy.ts` | [`providers/workbuddy.md`](providers/workbuddy.md) |
| `src/advisor/` | [`advisor.md`](advisor.md) |
Expand Down Expand Up @@ -299,6 +301,7 @@ A source area can be described by more than one doc, because these docs are orga
| `src/server/responses/advisor-plan-slot.ts` | [`advisor.md`](advisor.md) |
| `src/server/responses/advisor-slot.ts` | [`advisor.md`](advisor.md) |
| `src/server/responses/anthropic-thinking-recovery.ts` | [`transports/anthropic-thinking-recovery.md`](transports/anthropic-thinking-recovery.md) |
| `src/server/responses/compact.ts` | [`transports/responses-citations.md`](transports/responses-citations.md) |
| `src/server/responses/opengateway-completion-guard.ts` | [`providers/opengateway.md`](providers/opengateway.md) |
| `src/server/responses/passthrough-completion.ts` | [`providers/opengateway.md`](providers/opengateway.md) |
| `src/server/responses/passthrough-dispatch.ts` | [`providers/opengateway.md`](providers/opengateway.md) |
Expand Down
11 changes: 11 additions & 0 deletions structure/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,17 @@
"gui/"
]
},
{
"path": "transports/responses-citations.md",
"tier": 3,
"title": "Replayed URL Citations",
"scope": "Destination-gated citation repair for ordinary Responses and every native compact send.",
"documents": [
"src/adapters/openai-responses/request-strips.ts",
"src/adapters/openai-responses/passthrough.ts",
"src/server/responses/compact.ts"
]
},
{
"path": "transports/responses-wire-shapes.md",
"tier": 3,
Expand Down
2 changes: 1 addition & 1 deletion structure/providers-and-adapters.md
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,7 @@ picker, so the catalog and the wire agree; a rung the upstream then refuses is r
error rather than replayed without the effort, because the operator asked for it. The flag is part
of the compatibility behavior record for the same reason as above.

The shared Responses path follows the [bounded multipart recovery contract](subagents.md#multipart-encrypted-task-recovery); credential admission and retry policy remain unchanged.
The shared Responses path follows the [bounded multipart recovery contract](subagents.md#multipart-encrypted-task-recovery); credential admission and retry policy remain unchanged. Ordinary requests and native compact apply the [destination-specific citation projection](transports/responses-citations.md) independently of provider metadata stripping.
## Hosted-search continuation binding
> Decision record: [ADR-6930](decisions/ADR-6930-hosted-search-continuation-binding.md)
> Decision record: [ADR-6931](decisions/ADR-6931-lite-hosted-search-opt-in.md)
Expand Down
19 changes: 19 additions & 0 deletions structure/transports/responses-citations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Replayed URL Citations

OpenAI-operated Responses destinations repair replayed `url_citation` annotations to the required
string title and non-negative integer indices through `repairUrlCitationAnnotations` in
`src/adapters/openai-responses/request-strips.ts`. Provider metadata stripping remains an
independent projection. The ordinary HTTP/WS adapter in
`src/adapters/openai-responses/passthrough.ts` applies citation repair only after destination
classification.

The direct native `/responses/compact` path in `src/server/responses/compact.ts` applies the same
gate in its final body builder for every physical send, including a provider destination refreshed
after key admission. It keeps the shared history free of citation repair so an OpenAI-to-gateway
refresh preserves the gateway's original annotations and the reverse refresh still repairs OpenAI
history. Opting a third-party gateway into native compaction declares protocol support, not
permission to rewrite its citations. `tests/responses/compact-key-selection.test.ts` covers exact
gateway preservation and both refresh directions;
`tests/responses/responses-native-item-extensions-compact.test.ts` covers official API and ChatGPT
compact repair, while `tests/responses/responses-url-citation-title-repair.test.ts` covers ordinary
HTTP/WS projection, helper independence and input immutability.
2 changes: 1 addition & 1 deletion structure/transports/responses.md
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ now: the TTL was previously evaluated only on read or on a generation change, so
again held its entry for the life of the process.

> Decision record: [ADR-0038](../decisions/ADR-0038-responses-http-sse.md)
Custom `openai-responses` gateways declare native v2 triggers with `supportsNativeCompactionTrigger` and native v1 endpoints with `supportsNativeCompactEndpoint`; neither flag implies blob replay or OpenAI identity. `src/adapters/openai-responses/transport.ts` shares normal URL/auth/header construction with custom native v1 key/local/forward routes; generic OAuth retains routed compaction so the existing credential selection, refresh and dispatch fence own authentication. Native v2 remains an independent declaration. Custom native v1 honors `preserveResponsesReasoningContent: true` in both sanitizer passes only for custom destinations; absent/false and OpenAI-operated compact destinations blank raw reasoning content. Proxy-owned `ocxr1:` envelopes are always stripped, and plaintext preservation grants neither native generation nor ciphertext replay. Both custom v1 sanitizer passes omit non-array reasoning content channels and reuse `requiresPlaintextReasoningReplay` from `src/adapters/openai-responses/passthrough.ts`: custom providers with plaintext preservation and strict tool-result adjacency strip opaque reasoning ciphertext and backfill missing `reasoning_text` from `summary_text` or the existing minimal placeholder, while preserving available plaintext. Each physical send applies `dropResponsesReasoningItems: true` and strips private input message metadata unless `preserveResponsesMessageMetadata: true`, using the ordinary adapter policy; OpenAI-operated compact metadata, reasoning items and null channels retain their established policy. Custom v1 scrubs compaction ciphertext, reasoning ciphertext and foreign reasoning ids after each serving bind, using ordinary turns' parent-thread-first replay scope; provenance commits require a successful HTTP response and a validated replacement history with replayable compact ciphertext or nonblank message text, absent/completed status and absent/null error (`nativeCompactResponseSchema` in `src/responses/schema.ts`). Invalid or empty bodies preserve the prior identity; validation never rewrites the relayed body or request-log metadata. Daybreak endpoint restrictions remain canonical-only. A native 404 handoff preserves User-Agent and allowlisted provider-opted-in metadata through `src/server/responses/core-auth.ts` without widening credential forwarding. Every custom native v1 send carries only the gateway transport's headers, and Codex pool recovery (401 refresh replay, quota and transient alternates) and pool outcome recording skip gateway sends, so recovery cannot put a stored ChatGPT credential on a gateway wire (`tests/responses/native-compaction-gateway-credentials.test.ts`). Readable reasoning retention also bypasses native v2-capable gateways; the capability predicate controls local retention as well as dispatch. V1 fallback remains portable even for v2-capable gateways; cross-provider overrides still force portable summaries. `tests/responses/native-compaction-gateway.test.ts`, `tests/responses/native-compaction-gateway-policy.test.ts` `tests/responses/native-compaction-provenance.test.ts` and `tests/responses/native-compaction-oauth.test.ts` pin these boundaries. Key-auth compact revalidates the serving key after pacing, rebuilding custom gateway paths, transport headers and replay sanitization against that physical selection; `tests/responses/compact-key-selection.test.ts` covers initial gateway dispatch and queued reselection.
Custom `openai-responses` gateways declare native v2 triggers with `supportsNativeCompactionTrigger` and native v1 endpoints with `supportsNativeCompactEndpoint`; neither flag implies blob replay or OpenAI identity. `src/adapters/openai-responses/transport.ts` shares normal URL/auth/header construction with custom native v1 key/local/forward routes; generic OAuth retains routed compaction so the existing credential selection, refresh and dispatch fence own authentication. Native v2 remains an independent declaration. Custom native v1 honors `preserveResponsesReasoningContent: true` in both sanitizer passes only for custom destinations; absent/false and OpenAI-operated compact destinations blank raw reasoning content. Proxy-owned `ocxr1:` envelopes are always stripped, and plaintext preservation grants neither native generation nor ciphertext replay. Both custom v1 sanitizer passes omit non-array reasoning content channels and reuse `requiresPlaintextReasoningReplay` from `src/adapters/openai-responses/passthrough.ts`: custom providers with plaintext preservation and strict tool-result adjacency strip opaque reasoning ciphertext and backfill missing `reasoning_text` from `summary_text` or the existing minimal placeholder, while preserving available plaintext. Each physical send applies `dropResponsesReasoningItems: true` and strips private input message metadata unless `preserveResponsesMessageMetadata: true`, using the ordinary adapter policy; OpenAI-operated compact metadata, reasoning items and null channels retain their established policy. Custom v1 scrubs compaction ciphertext, reasoning ciphertext and foreign reasoning ids after each serving bind, using ordinary turns' parent-thread-first replay scope; provenance commits require a successful HTTP response and a validated replacement history with replayable compact ciphertext or nonblank message text, absent/completed status and absent/null error (`nativeCompactResponseSchema` in `src/responses/schema.ts`). Invalid or empty bodies preserve the prior identity; validation never rewrites the relayed body or request-log metadata. Daybreak endpoint restrictions remain canonical-only. A native 404 handoff preserves User-Agent and allowlisted provider-opted-in metadata through `src/server/responses/core-auth.ts` without widening credential forwarding. Every custom native v1 send carries only the gateway transport's headers, and Codex pool recovery (401 refresh replay, quota and transient alternates) and pool outcome recording skip gateway sends, so recovery cannot put a stored ChatGPT credential on a gateway wire (`tests/responses/native-compaction-gateway-credentials.test.ts`). Readable reasoning retention also bypasses native v2-capable gateways; the capability predicate controls local retention as well as dispatch. V1 fallback remains portable even for v2-capable gateways; cross-provider overrides still force portable summaries. `tests/responses/native-compaction-gateway.test.ts`, `tests/responses/native-compaction-gateway-policy.test.ts` `tests/responses/native-compaction-provenance.test.ts` and `tests/responses/native-compaction-oauth.test.ts` pin these boundaries. Key-auth compact revalidates the serving key after pacing, rebuilding custom gateway paths, transport headers, replay sanitization and [citation projection](responses-citations.md) against that physical selection; `tests/responses/compact-key-selection.test.ts` covers initial gateway dispatch and queued reselection.
Canonical native v1's 404 fallback also bypasses local readable retention: its native v2 turn receives the original reasoning and creates no archive (`tests/responses/reasoning-retention-routing.test.ts`).
A replayed compaction item carries an `encrypted_content` blob only its minting backend can decode, and the client replays it on every later turn. The proxy's own `ocx1:`/`ocx2:` envelopes are transparent base64, so they always lower to plain user messages. A
native blob is relayed only when there is no known serving-identity mismatch and the destination is known to decode native blobs — the canonical ChatGPT forward surface, the official OpenAI API, or a provider with the explicit
Expand Down
Loading
Loading