Skip to content
Open
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
7 changes: 7 additions & 0 deletions docs-site/src/content/docs/fr/reference/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,13 @@ Par défaut, `server/index.ts` sert HTTP/SSE sur `/v1/responses`. Si Codex tente

Indépendamment de ce réglage côté client, les requêtes canoniques transmises à ChatGPT avec `stream: true` à la racine peuvent utiliser le transport WebSocket en amont de Codex avec une version stable de Bun 1.4.0 ou ultérieure. La version intégrée Bun 1.3.14, les préversions et les identités de runtime impossibles à vérifier utilisent HTTP/SSE. Les réponses WS en amont qui réussissent conservent le contrat SSE en aval et contournent `tee()` au moyen d’un relais borné à lecteur unique et avide (4 MiB par trame brute/enveloppée et une file de production de 8 MiB). Le dépassement de la file ferme la connexion en amont et émet en aval un événement terminal `response.failed`, suivi de `[DONE]`.

Pour le modèle sortant final `gpt-5.3-codex-spark`, la transmission canonique à ChatGPT
désactive explicitement Responses Lite dans l’en-tête HTTP et les métadonnées natives des
trames WS, même lorsqu’un alias sélectionne Spark. Un changement d’identité Lite retire
Comment thread
coderabbitai[bot] marked this conversation as resolved.
l’ancien socket ; les requêtes admissibles suivantes ayant la même identité peuvent réutiliser
le nouveau socket. Les autres modèles et passerelles conservent leur politique Lite.
Des métadonnées natives mal formées entraînent toujours un repli HTTP, sans modifier le corps.

Le compactage du contexte Codex fonctionne avec les modèles routés. `server/responses/compact.ts` traite `POST /v1/responses/compact` en exécutant un tour interne de synthèse routé et en renvoyant un historique compacté, tandis que `responses/parser.ts` et `bridge.ts` traitent les tours de compactage distant v2 `compaction_trigger` en émettant exactement un élément de sortie synthétique `compaction`.

## Mise en cache et catalogue
Expand Down
7 changes: 7 additions & 0 deletions docs-site/src/content/docs/ja/reference/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,13 @@ HTTP の境界は `server/index.ts` が担い、Responses データプレーン

`server/index.ts` はデフォルトで `/v1/responses` を HTTP/SSE で提供します。`websockets` が `false` の状態で Codex が Responses WebSocket アップグレードを試みると、opencodex は `426 upgrade_required` を返し、Codex はそのセッションで HTTP にフォールバックします。`"websockets": true` を設定すると同じエンドポイントがアップグレードを受け入れ WebSocket ブリッジを使います。

最終送信モデルが `gpt-5.3-codex-spark` の場合、canonical ChatGPT 転送は HTTP ヘッダーと
ネイティブ WS フレームのメタデータの両方で Responses Lite を明示的に無効にします。
エイリアスで Spark を選択した場合も同様です。Lite の識別値が変わると古いソケットは退役し、
以後の条件を満たす同じ識別値のリクエストは新しいソケットを再利用できます。他のモデルと
ゲートウェイの Lite ポリシーは維持されます。不正なネイティブメタデータは引き続き、
本文を変更せずに HTTP にフォールバックします。

Codex コンテキスト compaction はルーティングされたモデルでも動作します。`server/responses/compact.ts` は
`POST /v1/responses/compact` を内部ルーティング要約ターンとして扱い、圧縮されたヒストリーを返します。
`responses/parser.ts` と `bridge.ts` は remote compaction v2 の `compaction_trigger` ターンを扱い、合成 `compaction` 出力項目を正確に 1 つ送ります。
Expand Down
7 changes: 7 additions & 0 deletions docs-site/src/content/docs/ko/reference/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,13 @@ envelope를 각각 4 MiB로 제한하고 8 MiB producer queue 상한이 있는 b
relay를 거칩니다. queue overflow 시 업스트림을 닫고 downstream에는
terminal `response.failed` 이벤트와 `[DONE]`을 내보냅니다.

최종 전송 모델이 `gpt-5.3-codex-spark`이면 canonical ChatGPT forward 경로는 HTTP 헤더와
네이티브 WS 프레임 메타데이터 모두에서 Responses Lite를 명시적으로 끕니다. 별칭으로 Spark를
선택해도 동일합니다. Lite 식별값이 바뀌면 기존 소켓은 사용을 종료하며, 이후 같은 식별값으로
재사용 조건을 충족하는 요청은 새 소켓을 재사용할 수 있습니다. 다른 모델과 게이트웨이는 기존
Lite 정책을 유지합니다. 네이티브 메타데이터 형식이 잘못된 경우에는 본문을 바꾸지 않고
기존처럼 HTTP로 폴백합니다.

Codex 컨텍스트 compaction은 라우팅된 모델에서도 동작합니다. `server/responses/compact.ts`는
`POST /v1/responses/compact`를 내부 라우팅 요약 턴으로 처리해 압축된 히스토리를 반환합니다.
`responses/parser.ts`와 `bridge.ts`는 remote compaction v2의 `compaction_trigger` 턴을 처리해
Expand Down
6 changes: 6 additions & 0 deletions docs-site/src/content/docs/reference/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,12 @@ upstream WS responses keep the downstream SSE contract and bypass `tee()` throug
single-reader relay (4 MiB per raw/enveloped frame and an 8 MiB producer queue). Queue overflow
closes the upstream and emits a terminal downstream `response.failed` event followed by `[DONE]`.

For the final outgoing model `gpt-5.3-codex-spark`, canonical ChatGPT forwarding explicitly
disables Responses Lite in both the HTTP header and native WS frame metadata, including when
an alias selects Spark. A changed Lite identity retires the old socket; subsequent eligible
requests with the same identity can reuse the new socket. Other models and gateways keep
their existing Lite policy. Malformed native metadata still falls back to HTTP with its body unchanged.

When a provider rejects a streaming request with HTTP 413 before SSE begins, OpenCodex emits one
terminal `response.failed` event with `context_length_exceeded` instead of relaying the retryable
unknown status. This lets Codex stop its reconnect loop and apply its own context-compaction policy
Expand Down
7 changes: 7 additions & 0 deletions docs-site/src/content/docs/ru/reference/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,13 @@ loopback; настроенные записи `corsAllowOrigins` расширя
`426 upgrade_required`; Codex тогда откатывается на HTTP для этой сессии. Когда установлено
`"websockets": true`, та же конечная точка принимает апгрейд и использует WebSocket-мост.

Для итоговой исходящей модели `gpt-5.3-codex-spark` каноническая пересылка в ChatGPT явно
отключает Responses Lite в HTTP-заголовке и нативных метаданных WS-кадра, в том числе при
выборе Spark через псевдоним. Изменение идентичности Lite выводит старый сокет из использования;
последующие подходящие запросы с той же идентичностью могут повторно использовать новый сокет.
Другие модели и шлюзы сохраняют прежнюю политику Lite. Некорректные нативные метаданные
по-прежнему приводят к откату на HTTP без изменения тела запроса.

Compaction контекста Codex работает для маршрутизируемых моделей. `server/responses/compact.ts`
обрабатывает `POST /v1/responses/compact`, выполняя внутренний маршрутизируемый ход суммаризации
и возвращая сжатую историю, а `responses/parser.ts` и `bridge.ts` обрабатывают ходы
Expand Down
8 changes: 7 additions & 1 deletion docs-site/src/content/docs/tr/reference/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,13 @@ opencodex `426 upgrade_required` döndürür; Codex daha sonra bu oturum için
HTTP'ye geri döner. `"websockets": true` ayarlandığında aynı uç nokta
yükseltmeyi kabul eder ve WebSocket köprüsünü kullanır.

Son gönderilen model `gpt-5.3-codex-spark` olduğunda, kanonik ChatGPT iletimi HTTP başlığında
ve yerel WS çerçevesi meta verilerinde Responses Lite'ı açıkça kapatır; Spark bir takma adla
seçildiğinde de bu geçerlidir. Lite kimliği değişince eski soket kullanım dışı bırakılır;
aynı kimliğe sahip sonraki uygun istekler yeni soketi yeniden kullanabilir. Diğer modeller ve
ağ geçitleri mevcut Lite politikalarını korur. Bozuk yerel meta verilerde, istek gövdesi
değiştirilmeden HTTP'ye geri dönülmeye devam edilir.

Codex bağlam sıkıştırması yönlendirilen modeller için çalışır.
`server/responses/compact.ts`, dahili bir yönlendirilen özetleme turu
çalıştırarak ve sıkıştırılmış geçmişi döndürerek `POST /v1/responses/compact`'ı
Expand Down Expand Up @@ -220,4 +227,3 @@ Dahili model `types.ts` içinde yer alır: `OcxParsedRequest`, `OcxContext`,
`OcxProviderConfig`). İki yardımcı yaygın olarak kullanılır:
`namespacedToolName()` ve `modelInList()` (`noVisionModels` /
`noReasoningModels` için toleranslı `:size` etiketi eşleştirmesi).

5 changes: 5 additions & 0 deletions docs-site/src/content/docs/zh-cn/reference/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,11 @@ thread affinity 位于 `codex/` 下,不会出现在管理 API 响应中。请
session 中回退到 HTTP。设置 `"websockets": true` 后,同一 endpoint 会接受 upgrade 并使用
WebSocket bridge。

当最终发送的模型为 `gpt-5.3-codex-spark` 时,canonical ChatGPT 转发会在 HTTP 请求头和
原生 WS 帧元数据中明确关闭 Responses Lite,通过别名选择 Spark 时也一样。Lite 标识变化时,
旧 socket 会退出使用;后续标识相同且满足复用条件的请求可以复用新 socket。其他模型和网关
保留原有 Lite 策略。原生元数据格式不合法时,仍会回退到 HTTP,并保持请求正文不变。

Codex context compaction 同样适用于路由模型。`server/responses/compact.ts` 处理
`POST /v1/responses/compact`,运行一次内部路由 summarization turn 并返回压缩后的历史;
`responses/parser.ts` 与 `bridge.ts` 则处理 remote compaction v2 的 `compaction_trigger` turn,
Expand Down
6 changes: 6 additions & 0 deletions docs-site/src/content/docs/zh-tw/reference/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,12 @@ thread affinity 位於 `codex/` 下,不會出現在管理 API 回應中。請
session 中回退到 HTTP。設定 `"websockets": true` 後,同一 endpoint 會接受 upgrade 並使用
WebSocket bridge。

當最終傳送的模型為 `gpt-5.3-codex-spark` 時,canonical ChatGPT 轉送會在 HTTP 請求標頭與
原生 WS 訊框中繼資料中明確關閉 Responses Lite,透過別名選擇 Spark 時也一樣。Lite 識別值
改變時,舊 socket 會停止使用;後續識別值相同且符合重用條件的請求可以重用新 socket。
其他模型與閘道保留既有 Lite 政策。原生中繼資料格式不合法時,仍會退回 HTTP,並保持
請求本文不變。

Codex context compaction 同樣適用於路由模型。`server/responses/compact.ts` 處理
`POST /v1/responses/compact`,執行一次內部路由 summarization turn 並回傳壓縮後的歷史;
`responses/parser.ts` 與 `bridge.ts` 則處理 remote compaction v2 的 `compaction_trigger` turn,
Expand Down
5 changes: 3 additions & 2 deletions src/adapters/openai-responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2515,12 +2515,13 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
parsed.modelId,
);
if (isCanonicalOpenAiForwardProvider(provider)) {
// Spark closes Responses Lite streams before a terminal completion. Select compatibility
// from the final wire model so aliases cannot leave the caller or a static header enabled.
// Select Spark's Lite compatibility from the final wire model, including aliases.
// Explicit false also overrides native WS metadata; deleting the header leaves it enabled.
if (isPlainObject(finalBody) && finalBody.model === "gpt-5.3-codex-spark") {
for (const name of Object.keys(headers)) {
if (name.toLowerCase() === CODEX_RESPONSES_LITE_HEADER) delete headers[name];
}
headers[CODEX_RESPONSES_LITE_HEADER] = "false";
}
const routingHeaders = new Headers(headers);
applyCodexRoutingHint(routingHeaders, finalBody);
Expand Down
6 changes: 6 additions & 0 deletions structure/04_transports-and-sidecars.md
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,12 @@ final outgoing model/tier. No caller identity is synthesized. Noncanonical
opt-in gateways keep their own metadata policy. Oversized/unsupported-runtime
HTTP fallback preserves the original HTTP body and Lite header.

For the final wire model `gpt-5.3-codex-spark`, the canonical forward adapter explicitly
sets the Lite header to `false`, overriding caller/configured headers and stale native
WS Lite metadata. A changed Lite identity retires the previous socket; subsequent
eligible Spark requests with the same disabled identity can reuse the new socket.
Malformed native metadata retains HTTP fallback eligibility without rewriting its body.

Canonical WS quota and response metadata preceding the first Responses event
are projected into bounded, allowlisted HTTP headers before the response is
committed. Later quota observations update only the captured serving account;
Expand Down
58 changes: 52 additions & 6 deletions tests/codex-integration/codex-metadata-integrity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,33 +208,76 @@ describe("Codex request transport metadata", () => {
expect(new Headers(dropped.headers).get(hintHeader)).toBe("model=gpt-5.6-sol");
});

test("canonical adapter drops Lite only for the Spark wire model", async () => {
test("canonical adapter disables Spark Lite in HTTP headers and WS metadata without mutating input", async () => {
const { prepareCodexWsRequest } = await import("../../src/server/responses/codex-ws-request");
const adapter = createResponsesPassthroughAdapter({
adapter: "openai-responses", authMode: "forward", baseUrl: "https://chatgpt.com/backend-api/codex",
headers: { "X-OpenAI-Internal-Codex-Responses-Lite": "true" },
});

for (const [model, incomingLite, expectedLite] of [
["gpt-5.3-codex-spark", "true", null],
["gpt-5.3-codex-spark", undefined, null],
["gpt-5.3-codex-spark", "true", "false"],
["gpt-5.3-codex-spark", "false", "false"],
["gpt-5.3-codex-spark", undefined, "false"],
["gpt-5.6-sol", "true", "true"],
["gpt-5.6-sol", "false", "false"],
["gpt-5.6-sol", undefined, "true"],
] as const) {
const parsed = minimalParsed();
parsed.modelId = model;
parsed._rawBody = { model, input: [], stream: true };
parsed._rawBody = { model, input: [], stream: true,
client_metadata: { [liteKey]: "true", other: "preserved" } };
const before = JSON.stringify(parsed._rawBody);
const incoming = new Headers();
if (incomingLite !== undefined) incoming.set(liteHeader, incomingLite);
const request = await adapter.buildRequest(parsed, {
headers: incoming,
});
expect(new Headers(request.headers).get(liteHeader)).toBe(expectedLite);
const prepared = prepareCodexWsRequest(url, { body: request.body, headers: request.headers })!;
expect(JSON.parse(prepared.frameText).client_metadata).toEqual({
[liteKey]: expectedLite, other: "preserved",
});
expect(prepared.httpInit.body).toBe(request.body);
expect(JSON.stringify(parsed._rawBody)).toBe(before);
expect(incoming.get(liteHeader)).toBe(incomingLite ?? null);
}

const routed = minimalParsed();
routed.modelId = "spark-alias";
routed._rawBody = { model: "gpt-5.3-codex-spark", input: [], stream: true };
const request = await adapter.buildRequest(routed, { headers: new Headers({ [liteHeader]: "true" }) });
expect(new Headers(request.headers).get(liteHeader)).toBeNull();
expect(new Headers(request.headers).get(liteHeader)).toBe("false");
const prepared = prepareCodexWsRequest(url, { body: request.body, headers: request.headers })!;
expect(JSON.parse(prepared.frameText).client_metadata[liteKey]).toBe("false");

routed.modelId = "gpt-5.3-codex-spark";
routed._rawBody = { model: "gpt-5.6-sol", input: [], stream: true };
const otherWireModel = await adapter.buildRequest(routed, { headers: new Headers({ [liteHeader]: "true" }) });
expect(new Headers(otherWireModel.headers).get(liteHeader)).toBe("true");
});

test("Spark disables Lite without configured headers and retains malformed-metadata HTTP fallback", async () => {
const { prepareCodexWsRequest } = await import("../../src/server/responses/codex-ws-request");
const adapter = createResponsesPassthroughAdapter({
adapter: "openai-responses", authMode: "forward", baseUrl: "https://chatgpt.com/backend-api/codex",
});
for (const client_metadata of [undefined, {}, null, [], { [liteKey]: true }]) {
const parsed = minimalParsed();
parsed._rawBody = { model: "gpt-5.3-codex-spark", input: [], stream: true,
...(client_metadata === undefined ? {} : { client_metadata }) };
const before = JSON.stringify(parsed._rawBody);
const request = await adapter.buildRequest(parsed, { headers: new Headers() });
expect(new Headers(request.headers).get(liteHeader)).toBe("false");
const prepared = prepareCodexWsRequest(url, { body: request.body, headers: request.headers });
if (client_metadata === undefined || JSON.stringify(client_metadata) === "{}") {
expect(JSON.parse(prepared!.frameText).client_metadata).toEqual({ [liteKey]: "false" });
} else {
expect(prepared).toBeNull();
expect(JSON.parse(request.body).client_metadata).toEqual(client_metadata);
}
expect(JSON.stringify(parsed._rawBody)).toBe(before);
}
});

test("noncanonical adapters neither forward caller Lite nor synthesize a routing hint", async () => {
Expand All @@ -243,7 +286,10 @@ describe("Codex request transport metadata", () => {
adapter: "openai-responses", authMode, baseUrl: "https://gateway.example/v1",
headers: { [hintHeader]: "operator-owned" },
});
const request = await adapter.buildRequest(minimalParsed(), {
const parsed = minimalParsed();
parsed.modelId = "gpt-5.3-codex-spark";
parsed._rawBody = { model: parsed.modelId, input: [] };
const request = await adapter.buildRequest(parsed, {
headers: new Headers({ [liteHeader]: "true", [hintHeader]: "caller-owned" }),
});
expect(new Headers(request.headers).has(liteHeader)).toBe(false);
Expand Down
Loading
Loading