@bitkyc08/opencodex 2.42.0 → 2.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (246) hide show
  1. package/AGENTS_INSTALL.md +2 -2
  2. package/README.md +31 -0
  3. package/bin/ocx.mjs +10 -1
  4. package/gui/dist/assets/index-DS1NE4Jn.css +1 -0
  5. package/gui/dist/assets/index-Djowl68T.js +112 -0
  6. package/gui/dist/index.html +2 -2
  7. package/package.json +1 -1
  8. package/src/adapters/anthropic-image-codec.ts +304 -0
  9. package/src/adapters/anthropic-image-normalize.ts +8 -298
  10. package/src/adapters/anthropic.ts +30 -7
  11. package/src/adapters/command-code.ts +7 -5
  12. package/src/adapters/cursor/desktop-executor-contract.ts +15 -0
  13. package/src/adapters/cursor/images.ts +36 -6
  14. package/src/adapters/cursor/live-transport.ts +7 -2
  15. package/src/adapters/cursor/native-exec-desktop.ts +2 -15
  16. package/src/adapters/cursor/protobuf-request.ts +13 -3
  17. package/src/adapters/cursor/tool-definitions.ts +5 -670
  18. package/src/adapters/cursor/tool-guidance.ts +236 -0
  19. package/src/adapters/cursor/tool-naming.ts +252 -0
  20. package/src/adapters/cursor/tool-schemas.ts +195 -0
  21. package/src/adapters/cursor/types.ts +6 -3
  22. package/src/adapters/exec-tool-result-normalize.ts +1 -1
  23. package/src/adapters/google-errors.ts +9 -1
  24. package/src/adapters/google.ts +1 -0
  25. package/src/adapters/kiro-calibration.ts +181 -0
  26. package/src/adapters/kiro.ts +135 -3
  27. package/src/adapters/openai-responses.ts +216 -26
  28. package/src/adapters/responses-code-mode.ts +59 -0
  29. package/src/adapters/tool-catalog-nudge.ts +1 -1
  30. package/src/adapters/xai-schema-analysis.ts +86 -0
  31. package/src/adapters/xai-tool-schema.ts +2 -87
  32. package/src/adapters/xai-web-search.ts +1 -1
  33. package/src/bridge.ts +22 -10
  34. package/src/chat/inbound.ts +11 -3
  35. package/src/claude/inbound-content-options.ts +60 -0
  36. package/src/claude/inbound-model-options.ts +142 -0
  37. package/src/claude/inbound-records.ts +7 -0
  38. package/src/claude/inbound.ts +10 -202
  39. package/src/claude/model-info.ts +45 -0
  40. package/src/cli/account-auth.ts +21 -6
  41. package/src/cli/capabilities.ts +13 -2
  42. package/src/cli/claude.ts +232 -39
  43. package/src/cli/config-command.ts +9 -1
  44. package/src/cli/dispatch.ts +5 -1
  45. package/src/cli/doctor.ts +10 -0
  46. package/src/cli/effort.ts +372 -0
  47. package/src/cli/export-command.ts +3 -9
  48. package/src/cli/help.ts +1 -0
  49. package/src/cli/index.ts +13 -0
  50. package/src/cli/init.ts +4 -0
  51. package/src/cli/model-selection-guidance.ts +30 -0
  52. package/src/cli/models-runtime.ts +3 -2
  53. package/src/cli/models.ts +8 -3
  54. package/src/cli/opencode.ts +4 -1
  55. package/src/cli/provider-runtime.ts +65 -0
  56. package/src/cli/provider.ts +8 -0
  57. package/src/cli/registry.ts +16 -2
  58. package/src/cli/runtime-api.ts +3 -1
  59. package/src/cli/star-prompt.ts +22 -6
  60. package/src/cli/status-probes.ts +168 -0
  61. package/src/cli/status.ts +5 -168
  62. package/src/clients/config-export/constants.ts +69 -0
  63. package/src/clients/config-export/contracts.ts +154 -0
  64. package/src/clients/config-export/dsh.ts +132 -0
  65. package/src/clients/config-export/fast-models.ts +29 -0
  66. package/src/clients/config-export/mcode.ts +83 -0
  67. package/src/clients/config-export/model-metadata.ts +108 -0
  68. package/src/clients/config-export/omp.ts +104 -0
  69. package/src/clients/config-export/zcode.ts +92 -0
  70. package/src/clients/config-export.ts +18 -710
  71. package/src/codex/account-lifecycle.ts +20 -3
  72. package/src/codex/account-usability.ts +2 -0
  73. package/src/codex/auth-api.ts +170 -23
  74. package/src/codex/auth-context.ts +328 -24
  75. package/src/codex/catalog/effort.ts +30 -4
  76. package/src/codex/catalog/metadata.ts +39 -9
  77. package/src/codex/catalog/native-models.ts +53 -12
  78. package/src/codex/catalog/parsing.ts +119 -5
  79. package/src/codex/catalog/provider-fetch.ts +150 -20
  80. package/src/codex/catalog/reserve.ts +52 -0
  81. package/src/codex/catalog/sync.ts +89 -16
  82. package/src/codex/convergence-types.ts +1 -0
  83. package/src/codex/convergence.ts +2 -0
  84. package/src/codex/data/upstream-models.json +1 -1
  85. package/src/codex/forward-transport-headers.ts +25 -0
  86. package/src/codex/inject.ts +3 -28
  87. package/src/codex/legacy-config-keys.ts +68 -0
  88. package/src/codex/log-guard/inspect-schema.ts +137 -0
  89. package/src/codex/log-guard/inspect.ts +2 -134
  90. package/src/codex/loopback-target.ts +54 -0
  91. package/src/codex/main-account-cache.ts +63 -1
  92. package/src/codex/main-account-hard-lock.ts +52 -0
  93. package/src/codex/main-account.ts +3 -1
  94. package/src/codex/management-convergence.ts +3 -0
  95. package/src/codex/model-entitlements.ts +54 -4
  96. package/src/codex/project-config-warnings.ts +92 -2
  97. package/src/codex/prompt-layers/encoding.ts +80 -0
  98. package/src/codex/prompt-layers/paths.ts +54 -0
  99. package/src/codex/prompt-layers/revision.ts +55 -0
  100. package/src/codex/prompt-layers/toml-edit.ts +163 -0
  101. package/src/codex/prompt-layers/toml-read.ts +181 -0
  102. package/src/codex/prompt-layers.ts +14 -520
  103. package/src/codex/quota-auto-refresh-state.ts +16 -0
  104. package/src/codex/quota-auto-refresh.ts +219 -0
  105. package/src/codex/quota-types.ts +51 -0
  106. package/src/codex/quota.ts +252 -93
  107. package/src/codex/reserve-availability.ts +177 -0
  108. package/src/codex/routing.ts +28 -9
  109. package/src/codex/shim.ts +53 -11
  110. package/src/codex/subagent-model-fallback.ts +23 -3
  111. package/src/combos/failover.ts +125 -7
  112. package/src/combos/identifiers.ts +89 -0
  113. package/src/combos/index.ts +4 -0
  114. package/src/combos/resolve.ts +80 -9
  115. package/src/combos/types.ts +20 -93
  116. package/src/config/subagent-models.ts +24 -0
  117. package/src/config.ts +156 -13
  118. package/src/generated/compatibility-version.json +454 -166
  119. package/src/generated/model-metadata.ts +1 -1
  120. package/src/integrations/journal.ts +65 -4
  121. package/src/integrations/store.ts +5 -0
  122. package/src/lab/events/limits.ts +4 -0
  123. package/src/lib/destination-policy.ts +31 -2
  124. package/src/lib/errors.ts +39 -0
  125. package/src/lib/provider-outbound.ts +69 -3
  126. package/src/lib/proxy-env.ts +22 -0
  127. package/src/lib/redact-folding.ts +176 -0
  128. package/src/lib/redact.ts +2 -175
  129. package/src/lib/state-store-sweeper.ts +20 -6
  130. package/src/lib/token-estimate.ts +94 -27
  131. package/src/lib/windows-user-principal.ts +53 -5
  132. package/src/oauth/anthropic-routing.ts +99 -3
  133. package/src/oauth/generic-account-failover.ts +36 -13
  134. package/src/oauth/index.ts +127 -26
  135. package/src/oauth/login-cli.ts +5 -0
  136. package/src/oauth/meta-muse.ts +117 -15
  137. package/src/oauth/pool-settings-capability.ts +15 -4
  138. package/src/providers/api-keys.ts +8 -10
  139. package/src/providers/default-aliases.ts +39 -0
  140. package/src/providers/derive.ts +10 -2
  141. package/src/providers/fastwire.ts +36 -7
  142. package/src/providers/initial-model-selection-runtime.ts +90 -0
  143. package/src/providers/initial-model-selection.ts +120 -0
  144. package/src/providers/key-failover.ts +134 -54
  145. package/src/providers/key-store.ts +11 -1
  146. package/src/providers/label.ts +1 -1
  147. package/src/providers/model-discovery.ts +76 -0
  148. package/src/providers/model-rename-startup.ts +72 -8
  149. package/src/providers/openai-sidecar.ts +17 -5
  150. package/src/providers/openai-tiers-destination.ts +102 -0
  151. package/src/providers/openai-tiers.ts +2 -99
  152. package/src/providers/opencode-go-transport.ts +41 -0
  153. package/src/providers/quota-key-accounts.ts +141 -0
  154. package/src/providers/quota-types.ts +9 -0
  155. package/src/providers/quota.ts +535 -104
  156. package/src/providers/registry.ts +60 -17
  157. package/src/providers/xai-responses-opt-in.ts +31 -5
  158. package/src/quota/reset-activation.ts +81 -0
  159. package/src/quota/reset-detector.ts +305 -0
  160. package/src/quota/reset-notify-config.ts +162 -0
  161. package/src/quota/reset-observer.ts +125 -0
  162. package/src/quota/reset-poller.ts +160 -0
  163. package/src/quota/reset-seen-store.ts +385 -0
  164. package/src/quota/reset-sinks.ts +199 -0
  165. package/src/quota/window-mapping.ts +106 -0
  166. package/src/responses/apply-patch-envelope.ts +46 -0
  167. package/src/responses/code-mode-helper-compat.ts +39 -1
  168. package/src/responses/custom-tool-compat.ts +10 -4
  169. package/src/responses/hosted-tool-policy.ts +12 -4
  170. package/src/responses/parser-content.ts +133 -0
  171. package/src/responses/parser-text-format.ts +24 -0
  172. package/src/responses/parser-tools.ts +188 -0
  173. package/src/responses/parser.ts +3 -326
  174. package/src/responses/state.ts +124 -28
  175. package/src/router.ts +48 -13
  176. package/src/routing/analytics.ts +1 -0
  177. package/src/routing/capability.ts +17 -4
  178. package/src/server/auth-cors.ts +7 -1
  179. package/src/server/background-lifecycle.ts +23 -1
  180. package/src/server/chat-completions.ts +25 -3
  181. package/src/server/claude-messages.ts +62 -5
  182. package/src/server/effort-row.ts +1 -1
  183. package/src/server/fast-row.ts +295 -0
  184. package/src/server/gui-static.ts +30 -4
  185. package/src/server/index.ts +107 -21
  186. package/src/server/management/agent-settings-routes.ts +2 -2
  187. package/src/server/management/combo-routes.ts +37 -9
  188. package/src/server/management/config-routes.ts +93 -2
  189. package/src/server/management/integration-routes.ts +108 -0
  190. package/src/server/management/model-routes.ts +13 -3
  191. package/src/server/management/model-rows.ts +20 -1
  192. package/src/server/management/native-integration-routes.ts +4 -1
  193. package/src/server/management/oauth-account-routes.ts +38 -10
  194. package/src/server/management/provider-routes.ts +34 -3
  195. package/src/server/management/quota-reset-routes.ts +57 -0
  196. package/src/server/management/route-registry.ts +7 -4
  197. package/src/server/management/shared.ts +19 -5
  198. package/src/server/management/system-routes.ts +3 -2
  199. package/src/server/management-api.ts +14 -2
  200. package/src/server/ports.ts +12 -2
  201. package/src/server/relay-eager.ts +38 -23
  202. package/src/server/relay.ts +4 -0
  203. package/src/server/request-log.ts +6 -0
  204. package/src/server/responses/codex-ws-correlation.ts +65 -0
  205. package/src/server/responses/codex-ws-exchange.ts +261 -0
  206. package/src/server/responses/codex-ws-metadata.ts +134 -0
  207. package/src/server/responses/codex-ws-pool.ts +162 -0
  208. package/src/server/responses/codex-ws-request.ts +87 -0
  209. package/src/server/responses/codex-ws-session.ts +93 -0
  210. package/src/server/responses/codex-ws-wire.ts +144 -0
  211. package/src/server/responses/collaboration.ts +41 -1
  212. package/src/server/responses/compact.ts +105 -12
  213. package/src/server/responses/core.ts +476 -57
  214. package/src/server/responses/fetch-helpers.ts +10 -3
  215. package/src/server/responses/input-admission.ts +16 -9
  216. package/src/server/responses/responses-field-backfill.ts +1 -1
  217. package/src/server/responses/ws-upstream.ts +34 -318
  218. package/src/server/responses-custom-tool-repair.ts +20 -4
  219. package/src/server/responses-undeclared-tool-guard.ts +100 -8
  220. package/src/server/safe-response-headers.ts +23 -0
  221. package/src/server/search.ts +9 -0
  222. package/src/server/subagent-models-startup.ts +27 -0
  223. package/src/server/system-env-shell.ts +238 -0
  224. package/src/server/system-env.ts +7 -234
  225. package/src/server/ws-bridge.ts +3 -25
  226. package/src/server/xai-responses-startup.ts +21 -0
  227. package/src/service-manager-probe.ts +1 -1
  228. package/src/service.ts +55 -16
  229. package/src/types/config.ts +108 -12
  230. package/src/types/provider.ts +36 -7
  231. package/src/types/tools.ts +26 -1
  232. package/src/types.ts +2 -0
  233. package/src/update/notify.ts +8 -2
  234. package/src/usage/cost.ts +38 -28
  235. package/src/usage/expected-prices.ts +34 -15
  236. package/src/usage/log.ts +2 -0
  237. package/src/usage/model-identity.ts +26 -0
  238. package/src/usage/summary.ts +15 -1
  239. package/src/vision/describe.ts +6 -0
  240. package/src/vision/image-rewrite.ts +108 -0
  241. package/src/vision/index.ts +19 -306
  242. package/src/vision/plan.ts +205 -0
  243. package/src/web-search/executor.ts +6 -0
  244. package/src/web-search/index.ts +8 -1
  245. package/gui/dist/assets/index-BU1tE0sr.js +0 -112
  246. package/gui/dist/assets/index-DL9-iS6J.css +0 -1
@@ -0,0 +1,205 @@
1
+ import type { OcxConfig, OcxContentPart, OcxParsedRequest, OcxProviderConfig } from "../types";
2
+ import type { VisionReasoningEffort } from "../reasoning-effort";
3
+ import type { VisionSettings } from "./describe";
4
+ import type { ResolvedOpenAiForwardSidecar } from "../providers/openai-sidecar";
5
+ import type { CodexAuthPolicyConfig } from "../codex/auth-context";
6
+ import { isCodexReserveRequestEligible } from "../codex/loopback-target";
7
+ import type { DataPlaneAdmission } from "../server/auth-cors";
8
+ import { isModelVisionSidecarConsumer as isModelTextOnly, modelAcceptsImageInput } from "./eligibility";
9
+ import { normalizeVisionReasoningForModel } from "./reasoning";
10
+ import { resolveSidecarAuth } from "../sidecar/auth";
11
+ import { DEFAULT_VISION_TIMEOUT_MS, MAX_VISION_TIMEOUT_MS, MIN_VISION_TIMEOUT_MS } from "./timeout-bounds";
12
+ import { carriesImages } from "./image-rewrite";
13
+
14
+ const DEFAULT_VISION_MODEL = "gpt-5.4-mini";
15
+ const DEFAULT_ANTHROPIC_VISION_MODEL = "claude-sonnet-5";
16
+ const DEFAULT_REASONING: VisionReasoningEffort = "low";
17
+ export const DEFAULT_MAX_DESCRIPTIONS_PER_TURN = 8;
18
+
19
+ /** Runtime config is permissive: zero is intentional; malformed values fall back to the bounded default. */
20
+ export function resolveMaxDescriptionsPerTurn(value: unknown): number {
21
+ if (value === 0) return 0;
22
+ return typeof value === "number" && Number.isInteger(value) && value > 0
23
+ ? value
24
+ : DEFAULT_MAX_DESCRIPTIONS_PER_TURN;
25
+ }
26
+
27
+ export function isValidVisionTimeoutMs(value: unknown): value is number {
28
+ return typeof value === "number"
29
+ && Number.isInteger(value)
30
+ && value >= MIN_VISION_TIMEOUT_MS
31
+ && value <= MAX_VISION_TIMEOUT_MS;
32
+ }
33
+
34
+ /** Runtime config is permissive: malformed or out-of-range values fall back to the default. */
35
+ export function resolveVisionTimeoutMs(value: unknown): number {
36
+ return isValidVisionTimeoutMs(value) ? value : DEFAULT_VISION_TIMEOUT_MS;
37
+ }
38
+
39
+ export interface AnthropicVisionProvider {
40
+ providerName: string;
41
+ provider: OcxProviderConfig;
42
+ }
43
+
44
+ /**
45
+ * First enabled Anthropic OAuth provider whose active stored account is not marked for reauth.
46
+ * Delegates to the shared sidecar auth module (#2188) — same predicate as web-search.
47
+ */
48
+ export function findAnthropicVisionProvider(config: OcxConfig): AnthropicVisionProvider | undefined {
49
+ const auth = resolveSidecarAuth(config);
50
+ if (!auth.isAnthropicAuth || !auth.anthropicProviderName || !auth.anthropicProvider) return undefined;
51
+ return { providerName: auth.anthropicProviderName, provider: auth.anthropicProvider };
52
+ }
53
+
54
+ export function resolveVisionBackend(
55
+ explicit: "openai" | "anthropic" | "routed" | undefined,
56
+ anthropicSidecar: AnthropicVisionProvider | undefined,
57
+ ): "openai" | "anthropic" {
58
+ if (explicit === "openai" || explicit === "anthropic") return explicit;
59
+ // "routed" collapses to the legacy default order until its describe executor
60
+ // lands (roadmap 170 → 180 revised): a persisted routed backend without a
61
+ // dispatchable arm degrades exactly like unset rather than crashing. wp3
62
+ // replaces this collapse with the real routed arm in planVisionSidecar.
63
+ return anthropicSidecar ? "anthropic" : "openai";
64
+ }
65
+
66
+ /** Native model used by the OpenAI vision helper, including its bounded default. */
67
+ export function resolveOpenAiVisionModel(config: Pick<OcxConfig, "visionSidecar">): string {
68
+ const configured = config.visionSidecar?.model;
69
+ // Namespaced routed ids never reach the forward executor (see
70
+ // resolveEffectiveVisionModel).
71
+ return configured && !configured.includes("/") ? configured : DEFAULT_VISION_MODEL;
72
+ }
73
+
74
+ /** Effective describer model for the backend `planVisionSidecar` selected. */
75
+ export function resolveEffectiveVisionModel(
76
+ config: Pick<OcxConfig, "visionSidecar">,
77
+ backend: "openai" | "anthropic",
78
+ ): string {
79
+ const configured = config.visionSidecar?.model;
80
+ // A namespaced "provider/model" id belongs to the routed backend only; the
81
+ // forward/OAuth executors POST the model string verbatim, so it falls back
82
+ // to the side's default here (PUT coherence rejects new writes of this
83
+ // shape, but a legacy or hand-edited config must not break the executor).
84
+ const usable = configured && !configured.includes("/") ? configured : undefined;
85
+ return backend === "anthropic"
86
+ ? usable || DEFAULT_ANTHROPIC_VISION_MODEL
87
+ : usable || DEFAULT_VISION_MODEL;
88
+ }
89
+
90
+ function messagesHaveImage(parsed: OcxParsedRequest): boolean {
91
+ return parsed.context.messages.some(m =>
92
+ carriesImages(m.role) && Array.isArray(m.content) && (m.content as OcxContentPart[]).some(p => p.type === "image"));
93
+ }
94
+
95
+ export function shouldResolveOpenAiVisionSidecar(
96
+ config: OcxConfig,
97
+ provider: OcxProviderConfig,
98
+ modelId: string,
99
+ parsed: OcxParsedRequest,
100
+ ): boolean {
101
+ if (!isModelTextOnly(provider, modelId) || !messagesHaveImage(parsed)) return false;
102
+ const cfg = config.visionSidecar ?? {};
103
+ if (cfg.enabled === false) return false;
104
+ return resolveVisionBackend(cfg.backend, findAnthropicVisionProvider(config)) === "openai";
105
+ }
106
+
107
+ export interface VisionPlan {
108
+ backend: "openai" | "anthropic" | "routed";
109
+ forwardSidecar?: ResolvedOpenAiForwardSidecar;
110
+ anthropicSidecar?: AnthropicVisionProvider;
111
+ /** Namespaced "provider/model" describer for the routed backend (roadmap 180). */
112
+ routedModel?: string;
113
+ /** Loopback dispatch inputs for the routed backend. */
114
+ routedConfig?: Pick<OcxConfig, "port" | "apiKeys">;
115
+ settings: VisionSettings;
116
+ maxDescriptionsPerTurn: number;
117
+ }
118
+
119
+ /**
120
+ * Decide whether the vision sidecar should pre-describe images for this request, returning the plan
121
+ * if so. Active when: the routed model is in `provider.noVisionModels`, the request actually carries
122
+ * an image, the sidecar isn't disabled, and the selected backend has usable auth. Returns undefined
123
+ * otherwise (the caller strips images before sending to a text-only model).
124
+ */
125
+ export function planVisionSidecar(
126
+ config: OcxConfig,
127
+ provider: OcxProviderConfig,
128
+ modelId: string,
129
+ parsed: OcxParsedRequest,
130
+ openAiSidecar?: ResolvedOpenAiForwardSidecar,
131
+ options: { admission?: Pick<DataPlaneAdmission, "source">; codexAuthPolicy?: CodexAuthPolicyConfig } = {},
132
+ ): VisionPlan | undefined {
133
+ if (!isModelTextOnly(provider, modelId)) return undefined;
134
+ if (!messagesHaveImage(parsed)) return undefined;
135
+ const cfg = config.visionSidecar ?? {};
136
+ if (cfg.enabled === false) return undefined;
137
+
138
+ // Routed arm (roadmap 180 revised): explicit backend + NAMESPACED explicit
139
+ // model only — never inferred from credential availability. Plan-time
140
+ // fence: the target must not be provably blind, and must not itself be a
141
+ // model this planner would re-enter for (belt; the terminal marker on the
142
+ // loopback request is the braces).
143
+ if (cfg.backend === "routed") {
144
+ const routedModel = cfg.model;
145
+ const sep = routedModel ? routedModel.indexOf("/") : -1;
146
+ if (routedModel && sep > 0) {
147
+ const targetProvider = routedModel.slice(0, sep);
148
+ const targetId = routedModel.slice(sep + 1);
149
+ const targetProviderConfig = config.providers?.[targetProvider];
150
+ const targetVisible = modelAcceptsImageInput(config, { provider: targetProvider, id: targetId }) !== false
151
+ && !(targetProviderConfig && isModelTextOnly(targetProviderConfig, targetId));
152
+ if (targetVisible) {
153
+ return {
154
+ backend: "routed",
155
+ routedModel,
156
+ routedConfig: { port: config.port, ...(config.apiKeys ? { apiKeys: config.apiKeys } : {}) },
157
+ settings: {
158
+ model: routedModel,
159
+ reasoning: DEFAULT_REASONING,
160
+ timeoutMs: resolveVisionTimeoutMs(cfg.timeoutMs),
161
+ },
162
+ maxDescriptionsPerTurn: resolveMaxDescriptionsPerTurn(cfg.maxDescriptionsPerTurn),
163
+ };
164
+ }
165
+ }
166
+ // Misconfigured routed backend (bare id, unknown provider, or provably
167
+ // blind target): fall through to the legacy default order below rather
168
+ // than dispatching a describe that cannot work.
169
+ }
170
+
171
+ const anthropicSidecar = findAnthropicVisionProvider(config);
172
+ const backend = resolveVisionBackend(cfg.backend, anthropicSidecar);
173
+ // A namespaced routed model must never reach the forward/OAuth executors
174
+ // (they POST the string verbatim); the effective-model resolver falls back
175
+ // to each side's default in that case.
176
+ const model = resolveEffectiveVisionModel(config, backend);
177
+ const maxDescriptionsPerTurn = resolveMaxDescriptionsPerTurn(cfg.maxDescriptionsPerTurn);
178
+
179
+ if (backend === "anthropic") {
180
+ if (!anthropicSidecar) return undefined;
181
+ return {
182
+ backend,
183
+ anthropicSidecar,
184
+ settings: {
185
+ model,
186
+ reasoning: normalizeVisionReasoningForModel(model, cfg.reasoning) ?? DEFAULT_REASONING,
187
+ timeoutMs: resolveVisionTimeoutMs(cfg.timeoutMs),
188
+ },
189
+ maxDescriptionsPerTurn,
190
+ };
191
+ }
192
+
193
+ if (!openAiSidecar) return undefined;
194
+ return {
195
+ backend,
196
+ forwardSidecar: openAiSidecar,
197
+ settings: {
198
+ ...(isCodexReserveRequestEligible(options.codexAuthPolicy ?? config, options.admission) ? { reserveCompatibility: true } : {}),
199
+ model,
200
+ reasoning: normalizeVisionReasoningForModel(model, cfg.reasoning) ?? DEFAULT_REASONING,
201
+ timeoutMs: resolveVisionTimeoutMs(cfg.timeoutMs),
202
+ },
203
+ maxDescriptionsPerTurn,
204
+ };
205
+ }
@@ -7,11 +7,14 @@ import { applyUpstreamRecoveryInit, fetchWithResetRetry } from "../lib/upstream-
7
7
  import { withUpstreamHttpVersion } from "../lib/upstream-http-version";
8
8
  import { parseSidecarSSE, type WebSearchResult } from "./parse";
9
9
  import type { CodexUpstreamOutcome } from "../codex/routing";
10
+ import { NATIVE_RESERVE_MODEL } from "../codex/catalog/native-models";
10
11
 
11
12
  export interface SidecarSettings {
12
13
  model: string;
13
14
  reasoning: string;
14
15
  timeoutMs: number;
16
+ /** Effective Desktop authless compatibility does not grant auxiliary model use. */
17
+ reserveCompatibility?: boolean;
15
18
  /**
16
19
  * True when the routed (downstream) model is text-only. The search model CAN see images, so it's
17
20
  * told to verbalize any relevant image results and include their URLs — otherwise a non-vision model
@@ -49,6 +52,9 @@ export async function runWebSearch(
49
52
  abortSignal?: AbortSignal,
50
53
  recordOutcome?: SidecarOutcomeRecorder,
51
54
  ): Promise<SidecarOutcome> {
55
+ if (settings.reserveCompatibility && settings.model === NATIVE_RESERVE_MODEL) {
56
+ return { text: "", sources: [], error: "Luna Reserve compatibility is only available as a conversation model, not a search helper. Choose another search helper model." };
57
+ }
52
58
  const headers: Record<string, string> = { "Content-Type": "application/json" };
53
59
  if (forwardProvider.headers) Object.assign(headers, forwardProvider.headers);
54
60
  for (const h of FORWARD_HEADERS) {
@@ -2,6 +2,9 @@ import type { OcxConfig, OcxParsedRequest, OcxProviderConfig } from "../types";
2
2
  import { modelInList, toolChoiceToolPredicate } from "../types";
3
3
  import { isModelTextOnly } from "../vision";
4
4
  import type { SidecarSettings } from "./executor";
5
+ import type { CodexAuthPolicyConfig } from "../codex/auth-context";
6
+ import { isCodexReserveRequestEligible } from "../codex/loopback-target";
7
+ import type { DataPlaneAdmission } from "../server/auth-cors";
5
8
  import type { ResolvedOpenAiForwardSidecar } from "../providers/openai-sidecar";
6
9
  import { resolveSidecarAuth } from "../sidecar/auth";
7
10
  import { getAccountSet } from "../oauth/store";
@@ -215,6 +218,7 @@ export function planWebSearch(
215
218
  provider: OcxProviderConfig,
216
219
  modelId: string,
217
220
  openAiSidecar?: ResolvedOpenAiForwardSidecar,
221
+ options: { admission?: Pick<DataPlaneAdmission, "source">; codexAuthPolicy?: CodexAuthPolicyConfig } = {},
218
222
  ): SidecarPlan | undefined {
219
223
  if (!parsed._webSearch || isPassthrough) return undefined;
220
224
  if (!toolChoiceToolPredicate(parsed.options.toolChoice)(buildWebSearchTool())) return undefined;
@@ -322,7 +326,10 @@ export function planWebSearch(
322
326
  backend: "openai",
323
327
  forwardSidecar: openAiSidecar,
324
328
  hostedTool: parsed._webSearch,
325
- settings: { model: cfg.model ?? DEFAULT_SIDECAR_MODEL, reasoning, timeoutMs, describeImages },
329
+ settings: {
330
+ model: cfg.model ?? DEFAULT_SIDECAR_MODEL, reasoning, timeoutMs, describeImages,
331
+ ...(isCodexReserveRequestEligible(options.codexAuthPolicy ?? config, options.admission) ? { reserveCompatibility: true } : {}),
332
+ },
326
333
  maxSearches,
327
334
  routedModelStallTimeoutMs,
328
335
  stallTimeoutSec,