diff --git a/docs-site/src/content/docs/fr/reference/configuration/agents.md b/docs-site/src/content/docs/fr/reference/configuration/agents.md index 03e289e3e9..8e6d4522a2 100644 --- a/docs-site/src/content/docs/fr/reference/configuration/agents.md +++ b/docs-site/src/content/docs/fr/reference/configuration/agents.md @@ -119,4 +119,6 @@ Les plafonds s’appliquent uniquement à la fonctionnalité de collaboration v2 Un plafond ne peut que réduire l’effort. Le niveau retenu est le niveau annoncé le plus élevé qui ne dépasse pas le plafond. Si le modèle ne propose aucun contrôle d’effort ou si aucun niveau pris en charge ne convient, opencodex supprime le paramètre d’effort et laisse le fournisseur appliquer sa valeur par défaut. `max` et `ultra` sont acceptés, tandis que le tableau de bord propose les niveaux de `low` à `xhigh`. +Les plafonds configurés s’appliquent aussi aux tours Chat Completions natifs admissibles sans effort épinglé pour le modèle. La conversion vers la valeur du fournisseur intervient lorsqu’un épinglage est appliqué ou qu’un plafond modifie la valeur ; sinon, la valeur de l’appelant natif conserve sa forme d’origine. + Pour une présentation destinée aux débutants des comportements v1, default et v2, consultez [Surfaces de sous-agents](/fr/guides/sub-agent-surface/). diff --git a/docs-site/src/content/docs/ja/reference/configuration/agents.md b/docs-site/src/content/docs/ja/reference/configuration/agents.md index def092132c..3ab50e22fc 100644 --- a/docs-site/src/content/docs/ja/reference/configuration/agents.md +++ b/docs-site/src/content/docs/ja/reference/configuration/agents.md @@ -87,4 +87,6 @@ opencodex は、無効、ルーティング不能、異常、冷却期間、ま キャップは労力を軽減するだけです。これらは、キャップまたはキャップの下で宣伝されている最も高い段にスナップします。モデルにエフォート制御がない場合、またはサポートされているラングフィットがない場合、opencodex はエフォートを削除し、プロバイダーのデフォルトを適用します。 `max` および `ultra` が受け入れられますが、ダッシュボードでは `low` から `xhigh` が提供されます。 +モデルの effort pin がなくても、対象のネイティブ Chat Completions ターンには設定された上限が適用されます。pin を適用した場合、または上限で値を変更した場合にプロバイダーの送信値へ変換し、どちらも起きない呼び出し元の値は元の表記を維持します。 + v1、デフォルト、および v2 の動作に関する初心者向けの説明については、「[サブエージェントサーフェス](/guides/sub-agent-surface/)」を参照してください。 diff --git a/docs-site/src/content/docs/ko/reference/configuration/agents.md b/docs-site/src/content/docs/ko/reference/configuration/agents.md index 5393052849..58383e63a4 100644 --- a/docs-site/src/content/docs/ko/reference/configuration/agents.md +++ b/docs-site/src/content/docs/ko/reference/configuration/agents.md @@ -87,4 +87,6 @@ opencodex는 비활성, 라우팅 불가, 비정상, 쿨다운 중, 또는 할 상한은 노력만 낮춥니다. 모델이 광고한 단계 중 상한 이하에서 가장 높은 단계로 맞춥니다. 모델에 노력 제어가 없거나 맞는 지원 단계가 없으면, opencodex는 노력을 제거하고 제공자 기본값을 적용합니다. `max`와 `ultra`는 허용되며, 대시보드는 `low`부터 `xhigh`까지 제공합니다. +모델 effort pin이 없어도 적용 대상 native Chat Completions 요청에는 설정된 상한이 적용됩니다. pin을 적용하거나 상한이 값을 바꾼 경우에 제공자 전송 값으로 매핑하며, 둘 다 없으면 호출자 값은 원래 표기를 유지합니다. + v1, default, v2 동작에 대한 초보자용 설명은 [Sub-agent surfaces](/guides/sub-agent-surface/)를 참고하세요. diff --git a/docs-site/src/content/docs/reference/configuration/agents.md b/docs-site/src/content/docs/reference/configuration/agents.md index 24dd9dec16..a113813b40 100644 --- a/docs-site/src/content/docs/reference/configuration/agents.md +++ b/docs-site/src/content/docs/reference/configuration/agents.md @@ -253,6 +253,8 @@ Caps only lower effort. They snap to the highest advertised rung at or below the no effort control or no supported rung fits, opencodex removes the effort and lets the provider default apply. `max` and `ultra` are accepted, while the dashboard offers `low` through `xhigh`. +Configured caps also apply to eligible native Chat Completions turns that carry no model effort pin. Provider wire mapping runs when a pin is applied or when a cap changes the value; a native caller value keeps its original wire spelling when neither happens. + For a beginner-oriented explanation of v1, default, and v2 behavior, see [Sub-agent surfaces](/guides/sub-agent-surface/). diff --git a/docs-site/src/content/docs/ru/reference/configuration/agents.md b/docs-site/src/content/docs/ru/reference/configuration/agents.md index 831f49bcf9..62a5723508 100644 --- a/docs-site/src/content/docs/ru/reference/configuration/agents.md +++ b/docs-site/src/content/docs/ru/reference/configuration/agents.md @@ -136,5 +136,7 @@ Cap'ы умеют только понижать effort. Они опускают помещается под cap, opencodex убирает поле effort и позволяет провайдеру применить собственный дефолт. `max` и `ultra` принимаются, хотя дашборд предлагает только `low`–`xhigh`. +Настроенные потолки применяются и к подходящим нативным запросам Chat Completions без закреплённого effort модели. Преобразование в значение провайдера выполняется, когда применяется закрепление или потолок меняет значение; иначе значение нативного вызывающего клиента сохраняет исходное написание. + Если нужен объясняющий вариант для начинающих о поведении v1, default и v2, см. [Поверхность подагентов](/guides/sub-agent-surface/). diff --git a/docs-site/src/content/docs/tr/reference/configuration/agents.md b/docs-site/src/content/docs/tr/reference/configuration/agents.md index 128ac3920a..6320e18f08 100644 --- a/docs-site/src/content/docs/tr/reference/configuration/agents.md +++ b/docs-site/src/content/docs/tr/reference/configuration/agents.md @@ -259,5 +259,7 @@ hiçbir basamak uymuyorsa opencodex çabayı kaldırır ve sağlayıcı varsayı uygulanmasına izin verir. `max` ve `ultra` kabul edilirken kontrol paneli `low` ile `xhigh` arasını sunar. +Yapılandırılmış sınırlar, model effort sabitlemesi olmayan uygun yerel Chat Completions turlarına da uygulanır. Sağlayıcının iletim değerine eşleme yalnızca sabitleme uygulandığında veya sınır değeri değiştirdiğinde yapılır; aksi hâlde yerel çağıran değeri özgün yazımını korur. + v1, varsayılan ve v2 davranışının yeni başlayanlara yönelik açıklaması için [Alt ajan yüzeyleri](/tr/guides/sub-agent-surface/) sayfasına bakın. diff --git a/docs-site/src/content/docs/zh-cn/reference/configuration/agents.md b/docs-site/src/content/docs/zh-cn/reference/configuration/agents.md index 094e6df720..fcc84ab87f 100644 --- a/docs-site/src/content/docs/zh-cn/reference/configuration/agents.md +++ b/docs-site/src/content/docs/zh-cn/reference/configuration/agents.md @@ -86,4 +86,6 @@ opencodex 会跳过已禁用、不可路由、不健康、处于冷却中,或 上限只会降低 effort。它们会向下贴合到不高于上限、且模型公开的最高档位。如果模型没有 effort 控制,或者没有任何受支持的档位可用,opencodex 会移除 effort,让提供方默认值生效。`max` 和 `ultra` 都可接受,而仪表板提供 `low` 到 `xhigh`。 +即使没有设置模型 effort pin,符合条件的原生 Chat Completions 轮次也会应用配置的上限。仅在应用 pin 或上限改变值时才映射为提供方的传输值;两者都未发生时,原生调用方值保留原始写法。 + 关于 v1、default 和 v2 行为的面向初学者说明,请参阅 [Sub-agent surfaces](/guides/sub-agent-surface/)。 diff --git a/docs-site/src/content/docs/zh-tw/reference/configuration/agents.md b/docs-site/src/content/docs/zh-tw/reference/configuration/agents.md index d3fdf7059b..6f3ad3c84e 100644 --- a/docs-site/src/content/docs/zh-tw/reference/configuration/agents.md +++ b/docs-site/src/content/docs/zh-tw/reference/configuration/agents.md @@ -71,4 +71,6 @@ opencodex 會跳過已停用、不可路由、不健康、冷卻中或達到配 上限僅會降低 effort。它們吸附到上限或以下的最高宣告級別。若模型沒有 effort 控制或沒有支援的級別符合,opencodex 會移除 effort 並讓供應商預設值套用。`max` 與 `ultra` 被接受,而儀表板提供 `low` 到 `xhigh`。 +即使沒有設定模型 effort pin,符合條件的原生 Chat Completions 回合也會套用設定的上限。套用 pin 或上限改變值時才會對應為供應商的傳輸值;兩者皆未發生時,原生呼叫端值保留原始寫法。 + 關於 v1、default 與 v2 行為的入門導向說明,請見[子代理介面](/zh-tw/guides/sub-agent-surface/)。 diff --git a/src/server/chat-native.ts b/src/server/chat-native.ts index 7f3c97c10e..9fb24e143a 100644 --- a/src/server/chat-native.ts +++ b/src/server/chat-native.ts @@ -105,19 +105,27 @@ function normalizePinnedChatEffort(options: HandleNativeChatOptions): void { const from = typeof chatBody.reasoning_effort === "string" ? chatBody.reasoning_effort : undefined; logCtx.requestedEffort = from; // Compaction is normally excluded by native-route eligibility; preserve that boundary here too. - const pinned = chatBody.compaction_trigger === undefined + const compaction = chatBody.compaction_trigger !== undefined; + const pinned = !compaction ? resolvePinnedEffort(route, selector, config) : undefined; + let normalizeForWire = false; if (pinned !== undefined) { logCtx.requestedEffort = from ? `${from}->${pinned}` : pinned; if (pinned === "none") delete chatBody.reasoning_effort; else chatBody.reasoning_effort = pinned; - // The native lane historically passes caller effort through, including with caps set. - // Only a newly operator-pinned value enters the cap and provider-mapping pipeline. - if (effortCapAppliesTo(chatCollabSurface(chatBody), req.headers, config)) { - const capped = applyChatEffortCap(chatBody, req.headers, config, supportedLadderFor(route)); - if (capped) logCtx.requestedEffort = `${logCtx.requestedEffort}->${capped.to}`; + normalizeForWire = true; + } + // A qualifying turn's ceiling is independent of whether an operator pin resolved. + if (effortCapAppliesTo(chatCollabSurface(chatBody), req.headers, config, compaction)) { + const capped = applyChatEffortCap(chatBody, req.headers, config, supportedLadderFor(route)); + if (capped) { + logCtx.requestedEffort = `${logCtx.requestedEffort ?? capped.from}->${capped.to}`; + normalizeForWire = true; } + } + // Normalize operator-pinned values and cap rewrites; otherwise preserve caller spelling. + if (normalizeForWire) { const effort = typeof chatBody.reasoning_effort === "string" ? chatBody.reasoning_effort : undefined; const wireEffort = mapReasoningEffort(route.provider, route.modelId, effort); if (wireEffort === undefined) delete chatBody.reasoning_effort; diff --git a/structure/adapters/registry.md b/structure/adapters/registry.md index 9e869c4f7a..db26a7cb4d 100644 --- a/structure/adapters/registry.md +++ b/structure/adapters/registry.md @@ -70,3 +70,4 @@ see [Combo editor routing quota](../gui-and-management-api.md#combo-editor-routi Claude replay carries [Go conversation affinity](../data-planes/inbound-compat.md#claude-affinity-at-final-go-dispatch) privately to final dispatch; preliminary route selection does not inject Go-only headers. +Native Chat applies qualifying effort ceilings independently of model pins; pin selection precedes the cap and only pins or cap rewrites enter wire mapping. The [catalog effort contract](../catalog.md#ultra-reasoning-level) records the V1/compaction exemptions and caller-preservation boundary. diff --git a/structure/catalog.md b/structure/catalog.md index 0ba4acca3e..eda6f26e80 100644 --- a/structure/catalog.md +++ b/structure/catalog.md @@ -251,8 +251,10 @@ Provider model pins precede provider-wide pins, then global selector/destination A pin can raise the effective caller effort; the later cap can still lower or omit it. `none` means explicit-effort omission (provider default), not guaranteed reasoning disablement. Compaction maintenance is exempt. Pins are user overlays and do not alter registry seeds, -model discovery or advertised ladders. Native Chat normalizes newly pinned values through -provider wire mapping; unpinned native requests retain their existing pass-through contract. +model discovery or advertised ladders. Native Chat applies qualifying caps even without a +pin, and normalizes pinned values and values rewritten by a cap through provider wire +mapping. Without a pin or a cap rewrite, native caller values retain their original wire +spelling; the V1 and compaction cap exemptions are preserved. > Decision record: [ADR-0023](decisions/ADR-0023-ultra-reasoning-level.md) diff --git a/structure/clients/claude-desktop.md b/structure/clients/claude-desktop.md index 37b7eeef4c..54689b36a1 100644 --- a/structure/clients/claude-desktop.md +++ b/structure/clients/claude-desktop.md @@ -93,3 +93,5 @@ The explicit sync coordinator also accepts Cline CLI as a separate file integrat Config JSON preserves the boolean; only literal true activates the role-changing transform. The lightweight top-level CLI help counts Cline CLI among the fifteen registered export clients; registry parity remains covered by the client help and integration tests. + +Native Chat applies qualifying effort ceilings independently of model pins; pin selection precedes the cap and only pins or cap rewrites enter wire mapping. The [catalog effort contract](../catalog.md#ultra-reasoning-level) records the V1/compaction exemptions and caller-preservation boundary. diff --git a/structure/data-planes/images.md b/structure/data-planes/images.md index 25646c7de4..d1d0193048 100644 --- a/structure/data-planes/images.md +++ b/structure/data-planes/images.md @@ -79,3 +79,4 @@ see [Combo editor routing quota](../gui-and-management-api.md#combo-editor-routi Claude replay carries [Go conversation affinity](inbound-compat.md#claude-affinity-at-final-go-dispatch) privately to final dispatch; preliminary route selection does not inject Go-only headers. +Native Chat applies qualifying effort ceilings independently of model pins; pin selection precedes the cap and only pins or cap rewrites enter wire mapping. The [catalog effort contract](../catalog.md#ultra-reasoning-level) records the V1/compaction exemptions and caller-preservation boundary. diff --git a/structure/data-planes/inbound-compat.md b/structure/data-planes/inbound-compat.md index ee1e7b69c0..ee84baebee 100644 --- a/structure/data-planes/inbound-compat.md +++ b/structure/data-planes/inbound-compat.md @@ -137,3 +137,4 @@ changes prompt roles, not conversation identity, and cannot guarantee upstream c Instruction notice extraction scans fence ranges once and walks original lines backwards with a decreasing cursor. It accepts exactly one ASCII space inside the token notice, preserves unmatched prefix bytes, and does not repeatedly scan or copy shrinking prompt prefixes. +Native Chat applies qualifying effort ceilings independently of model pins; pin selection precedes the cap and only pins or cap rewrites enter wire mapping. The [catalog effort contract](../catalog.md#ultra-reasoning-level) records the V1/compaction exemptions and caller-preservation boundary. diff --git a/structure/gui-and-management-api.md b/structure/gui-and-management-api.md index 73090d646e..82d0ef5d20 100644 --- a/structure/gui-and-management-api.md +++ b/structure/gui-and-management-api.md @@ -543,3 +543,4 @@ integration IO adapter. Its snapshot fingerprint cannot be checked against provi [the integration contract](clients/integrations.md#cline-paired-files) defines recovery. The existing dashboard file-client maps include Cline CLI and reuse its committed color mark. The export panel labels its download as a settings/catalog bundle; all locales explain that Undo restores both original files. +Native Chat applies qualifying effort ceilings independently of model pins; pin selection precedes the cap and only pins or cap rewrites enter wire mapping. The [catalog effort contract](catalog.md#ultra-reasoning-level) records the V1/compaction exemptions and caller-preservation boundary. diff --git a/structure/ops/docs-and-release.md b/structure/ops/docs-and-release.md index 6655aad7db..8e31434039 100644 --- a/structure/ops/docs-and-release.md +++ b/structure/ops/docs-and-release.md @@ -316,3 +316,5 @@ see [Combo editor routing quota](../gui-and-management-api.md#combo-editor-routi The integrations guide documents Cline CLI as a two-file, loopback-only integration. Hosted CI validates its source-backed fixtures; the packaged dashboard exposes it through the existing client list. The lightweight top-level CLI help counts Cline CLI among the fifteen registered export clients; registry parity remains covered by the client help and integration tests. + +Native Chat applies qualifying effort ceilings independently of model pins; pin selection precedes the cap and only pins or cap rewrites enter wire mapping. The [catalog effort contract](../catalog.md#ultra-reasoning-level) records the V1/compaction exemptions and caller-preservation boundary. diff --git a/structure/ops/service-and-sidecars.md b/structure/ops/service-and-sidecars.md index 39dc9a82da..c8ef069110 100644 --- a/structure/ops/service-and-sidecars.md +++ b/structure/ops/service-and-sidecars.md @@ -142,3 +142,4 @@ see [Combo editor routing quota](../gui-and-management-api.md#combo-editor-routi Claude replay carries [Go conversation affinity](../data-planes/inbound-compat.md#claude-affinity-at-final-go-dispatch) privately to final dispatch; preliminary route selection does not inject Go-only headers. +Native Chat applies qualifying effort ceilings independently of model pins; pin selection precedes the cap and only pins or cap rewrites enter wire mapping. The [catalog effort contract](../catalog.md#ultra-reasoning-level) records the V1/compaction exemptions and caller-preservation boundary. diff --git a/structure/providers/xai-grok.md b/structure/providers/xai-grok.md index c6e7cd48f3..5f4217b093 100644 --- a/structure/providers/xai-grok.md +++ b/structure/providers/xai-grok.md @@ -67,3 +67,5 @@ Claude replay carries [Go conversation affinity](../data-planes/inbound-compat.m privately to final dispatch; preliminary route selection does not inject Go-only headers. Devin CLI credential path composition in `src/oauth/devin-cli.ts` follows the selected platform: Windows uses Win32 APPDATA paths, other platforms use POSIX XDG-data paths. The explicit absolute override remains verbatim; credential parsing and login behavior are unchanged. + +Native Chat applies qualifying effort ceilings independently of model pins; pin selection precedes the cap and only pins or cap rewrites enter wire mapping. The [catalog effort contract](../catalog.md#ultra-reasoning-level) records the V1/compaction exemptions and caller-preservation boundary. diff --git a/structure/runtime.md b/structure/runtime.md index 365fb04e07..89276a1839 100644 --- a/structure/runtime.md +++ b/structure/runtime.md @@ -235,3 +235,5 @@ Config JSON preserves the boolean; only literal true activates the role-changing The lightweight top-level CLI help counts Cline CLI among the fifteen registered export clients; registry parity remains covered by the client help and integration tests. Devin CLI credential path composition in `src/oauth/devin-cli.ts` follows the selected platform: Windows uses Win32 APPDATA paths, other platforms use POSIX XDG-data paths. The explicit absolute override remains verbatim; credential parsing and login behavior are unchanged. + +Native Chat applies qualifying effort ceilings independently of model pins; pin selection precedes the cap and only pins or cap rewrites enter wire mapping. The [catalog effort contract](catalog.md#ultra-reasoning-level) records the V1/compaction exemptions and caller-preservation boundary. diff --git a/structure/subagents.md b/structure/subagents.md index f190aab084..a80af4d990 100644 --- a/structure/subagents.md +++ b/structure/subagents.md @@ -214,3 +214,4 @@ see [Combo editor routing quota](gui-and-management-api.md#combo-editor-routing- Claude replay carries [Go conversation affinity](data-planes/inbound-compat.md#claude-affinity-at-final-go-dispatch) privately to final dispatch; preliminary route selection does not inject Go-only headers. +Native Chat applies qualifying effort ceilings independently of model pins; pin selection precedes the cap and only pins or cap rewrites enter wire mapping. The [catalog effort contract](catalog.md#ultra-reasoning-level) records the V1/compaction exemptions and caller-preservation boundary. diff --git a/structure/transports/inventory.md b/structure/transports/inventory.md index 88a26d3500..ec75ff03d4 100644 --- a/structure/transports/inventory.md +++ b/structure/transports/inventory.md @@ -72,3 +72,5 @@ Claude replay carries [Go conversation affinity](../data-planes/inbound-compat.m privately to final dispatch; preliminary route selection does not inject Go-only headers. Devin CLI credential path composition in `src/oauth/devin-cli.ts` follows the selected platform: Windows uses Win32 APPDATA paths, other platforms use POSIX XDG-data paths. The explicit absolute override remains verbatim; credential parsing and login behavior are unchanged. + +Native Chat applies qualifying effort ceilings independently of model pins; pin selection precedes the cap and only pins or cap rewrites enter wire mapping. The [catalog effort contract](../catalog.md#ultra-reasoning-level) records the V1/compaction exemptions and caller-preservation boundary. diff --git a/structure/transports/responses.md b/structure/transports/responses.md index 2321d78dd6..6e975af2ac 100644 --- a/structure/transports/responses.md +++ b/structure/transports/responses.md @@ -523,3 +523,4 @@ see [Combo editor routing quota](../gui-and-management-api.md#combo-editor-routi Claude replay carries [Go conversation affinity](../data-planes/inbound-compat.md#claude-affinity-at-final-go-dispatch) privately to final dispatch; preliminary route selection does not inject Go-only headers. +Native Chat applies qualifying effort ceilings independently of model pins; pin selection precedes the cap and only pins or cap rewrites enter wire mapping. The [catalog effort contract](../catalog.md#ultra-reasoning-level) records the V1/compaction exemptions and caller-preservation boundary. diff --git a/structure/transports/streaming-health.md b/structure/transports/streaming-health.md index 68093843ea..6a5574c6d2 100644 --- a/structure/transports/streaming-health.md +++ b/structure/transports/streaming-health.md @@ -199,3 +199,4 @@ see [Combo editor routing quota](../gui-and-management-api.md#combo-editor-routi Claude replay carries [Go conversation affinity](../data-planes/inbound-compat.md#claude-affinity-at-final-go-dispatch) privately to final dispatch; preliminary route selection does not inject Go-only headers. +Native Chat applies qualifying effort ceilings independently of model pins; pin selection precedes the cap and only pins or cap rewrites enter wire mapping. The [catalog effort contract](../catalog.md#ultra-reasoning-level) records the V1/compaction exemptions and caller-preservation boundary. diff --git a/tests/codex-integration/model-pinned-effort.test.ts b/tests/codex-integration/model-pinned-effort.test.ts index 7c7b15401c..a808b86767 100644 --- a/tests/codex-integration/model-pinned-effort.test.ts +++ b/tests/codex-integration/model-pinned-effort.test.ts @@ -428,12 +428,74 @@ describe("operator pins on the actual request wire", () => { expect(wire.reasoning).toEqual({ effort: "max", summary: "auto" }); }); - test("native Chat without pins preserves caller wire spelling and existing cap behavior", async () => { + test("native Chat without pins caps spawned children and preserves unqualified caller spelling", async () => { const c = config({ reasoningEfforts: ["low"], reasoningEffortMap: { max: "enabled" } }, { effortCap: "low", subagentEffortCap: "low" }); - expect((await request(c, "chat", { reasoning_effort: "ultra" }, { "x-openai-subagent": "collab_spawn" })).reasoning_effort).toBe("ultra"); + expect((await request(c, "chat", { reasoning_effort: "ultra" }, { "x-openai-subagent": "collab_spawn" })).reasoning_effort).toBe("low"); + expect((await request(c, "chat", { reasoning_effort: "ultra" })).reasoning_effort).toBe("ultra"); expect(Object.hasOwn(await request(c, "chat", { reasoning_effort: undefined }), "reasoning_effort")).toBe(false); }); + test("native Chat without pins caps v2 main turns and preserves forced v1", async () => { + const c = config({}, { effortCap: "medium", subagentEffortCap: "low" }); + const tools = [{ type: "function", function: { name: "spawn_agent", parameters: { type: "object", properties: {} } } }]; + expect((await request(c, "chat", { tools, reasoning_effort: "ultra" })).reasoning_effort).toBe("medium"); + expect((await request(c, "chat", { tools, reasoning_effort: "ultra" }, { "x-openai-subagent": "collab_spawn" })).reasoning_effort).toBe("low"); + c.multiAgentMode = "v1"; + expect((await request(c, "chat", { tools, reasoning_effort: "ultra" }, { "x-openai-subagent": "collab_spawn" })).reasoning_effort).toBe("ultra"); + }); + + test("native Chat maps newly capped values and preserves lower, non-ladder and absent efforts", async () => { + const c = config({ reasoningEffortMap: { medium: "enabled", low: "disabled" } }, { subagentEffortCap: "medium" }); + const headers = { "x-codex-turn-metadata": JSON.stringify({ subagent_kind: "thread_spawn" }) }; + expect((await request(c, "chat", { reasoning_effort: "ultra" }, headers)).reasoning_effort).toBe("enabled"); + expect((await request(c, "chat", { reasoning_effort: "low" }, headers)).reasoning_effort).toBe("low"); + expect((await request(c, "chat", { reasoning_effort: "enabled" }, headers)).reasoning_effort).toBe("enabled"); + expect(Object.hasOwn(await request(c, "chat", { reasoning_effort: undefined }, headers), "reasoning_effort")).toBe(false); + c.providers.fixture!.pinnedReasoningEffort = "medium"; + expect((await request(c, "chat", { reasoning_effort: "medium" })).reasoning_effort).toBe("enabled"); + }); + + test("native Chat without pins omits effort when no supported rung fits the cap", async () => { + for (const reasoningEfforts of [[], ["high", "max"]]) { + const c = config({ reasoningEfforts }, { subagentEffortCap: "low" }); + const wire = await request(c, "chat", { reasoning_effort: "max", temperature: 0.4 }, { "x-openai-subagent": "collab_spawn" }); + expect(Object.hasOwn(wire, "reasoning_effort")).toBe(false); + expect(wire.temperature).toBe(0.4); + } + }); + + test("direct native Chat compaction keeps its pin and cap exemption", async () => { + const c = config({ pinnedReasoningEffort: "high" }, { subagentEffortCap: "low" }); + const chatBody = { model: "fixture/pin-model", messages: [{ role: "user", content: "summarize" }], + reasoning_effort: "ultra", compaction_trigger: {} }; + const req = new Request("http://localhost/v1/chat/completions", { method: "POST", headers: { "x-openai-subagent": "collab_spawn" } }); + const response = await handleNativeChatCompletions({ req, config: c, logCtx: { model: "", provider: "" }, + route: routeModel(c, chatBody.model), chatBody, requestedModel: chatBody.model, + requestedStream: false, translatorBudget: createTestTranslatorBudget() }); + expect(response.status, await response.text()).toBe(200); + expect(captured.at(-1)!.body.reasoning_effort).toBe("ultra"); + }); + + test("native cap retries retain their annotation and restore original effort for a new destination", async () => { + const c = config({}, { defaultProvider: "first", subagentEffortCap: "high", providers: { + first: provider({ reasoningEfforts: ["low", "max"] }), + second: provider({ reasoningEfforts: ["medium", "high"] }), + } }); + const chatBody = { model: "first/pin-model", messages: [{ role: "user", content: "hello" }], reasoning_effort: "ultra" }; + const req = new Request("http://localhost/v1/chat/completions", { method: "POST", headers: { "x-openai-subagent": "collab_spawn" } }); + const annotations: Array = []; + for (const name of ["first", "first", "second"]) { + const logCtx = { model: "", provider: "", requestedEffort: undefined as string | undefined }; + const response = await handleNativeChatCompletions({ req, config: c, logCtx, + route: routeModel(c, `${name}/pin-model`), chatBody, requestedModel: `${name}/pin-model`, + requestedStream: false, translatorBudget: createTestTranslatorBudget() }); + expect(response.status, await response.text()).toBe(200); + annotations.push(logCtx.requestedEffort); + } + expect(captured.map(({ body }) => body.reasoning_effort)).toEqual(["low", "low", "high"]); + expect(annotations).toEqual(["ultra->low", "ultra->low", "ultra->high"]); + }); + test("unpinned Responses keeps its existing applicable cap", async () => { expect((await request(config({}, { subagentEffortCap: "medium" }), "responses", { reasoning: { effort: "max", summary: "auto" } }, { "x-openai-subagent": "collab_spawn" })).reasoning_effort).toBe("medium");