diff --git a/studio/frontend/src/features/chat/chat-providers-dialog.tsx b/studio/frontend/src/features/chat/chat-providers-dialog.tsx
index 67da6cee33..bcbab008ed 100644
--- a/studio/frontend/src/features/chat/chat-providers-dialog.tsx
+++ b/studio/frontend/src/features/chat/chat-providers-dialog.tsx
@@ -53,6 +53,9 @@ import {
CUSTOM_BACKEND_PROVIDER_TYPE,
CUSTOM_PROVIDER_PRESETS,
allowsManualModelIdsWithCatalog,
+ CODEX_DEFAULT_PARALLEL_CALLS,
+ CODEX_MAX_PARALLEL_CALLS,
+ clampCodexParallelCalls,
customProviderBaseUrlPlaceholder,
customProviderDisplayName,
customProviderModelIdsPlaceholder,
@@ -249,6 +252,14 @@ export function ChatProvidersSettings({
const [modelSearchQuery, setModelSearchQuery] = useState("");
const [customProviderName, setCustomProviderName] = useState("Custom");
const [isReasoningModel, setIsReasoningModel] = useState(false);
+ // Per-Codex-connection fan-out width. Stored on the provider so
+ // restoring it after a refresh / page reload does not collapse back
+ // to single-call. Clamped to [1, MAX] at every write because the
+ // input is a plain `` and a hand-edited
+ // localStorage entry could otherwise overflow.
+ const [codexParallelCalls, setCodexParallelCalls] = useState(
+ CODEX_DEFAULT_PARALLEL_CALLS,
+ );
const reduceMotion = useReducedMotion();
const connectionsEnabled = useExternalProvidersStore(
(s) => s.connectionsEnabled,
@@ -462,6 +473,15 @@ export function ChatProvidersSettings({
isReasoningModel: supportsProviderReasoningToggle(uiProviderType)
? existing?.isReasoningModel === true
: undefined,
+ // Preserve the per-Codex fan-out width on sync. The
+ // backend provider row does not carry it (it lives in
+ // localStorage only), so we read it from `existing` and
+ // skip the field entirely for non-Codex providers.
+ codexParallelCalls: isCodexProviderType(uiProviderType)
+ ? clampCodexParallelCalls(
+ existing?.codexParallelCalls ?? CODEX_DEFAULT_PARALLEL_CALLS,
+ )
+ : undefined,
createdAt: existing?.createdAt ?? createdAt,
updatedAt,
};
@@ -519,6 +539,7 @@ export function ChatProvidersSettings({
setModelSearchQuery("");
setCustomProviderName(customProviderDisplayName(providerType));
setIsReasoningModel(false);
+ setCodexParallelCalls(CODEX_DEFAULT_PARALLEL_CALLS);
}
function openAddProvider() {
@@ -756,6 +777,11 @@ export function ChatProvidersSettings({
isReasoningModel: supportsProviderReasoningToggle(uiProviderType)
? isReasoningModel
: undefined,
+ // Persist the fan-out width on the Codex provider only; other
+ // providers must not carry the field through normalization.
+ codexParallelCalls: isCodexProviderType(uiProviderType)
+ ? clampCodexParallelCalls(codexParallelCalls)
+ : undefined,
createdAt,
updatedAt,
};
@@ -876,6 +902,12 @@ export function ChatProvidersSettings({
)
? isReasoningModel
: undefined,
+ // Carry through the fan-out width for Codex; clear it on
+ // every other provider type so a left-over value cannot
+ // hitchhike on the persisted record.
+ codexParallelCalls: isCodexProviderType(existing.providerType)
+ ? clampCodexParallelCalls(codexParallelCalls)
+ : undefined,
updatedAt,
}
: provider,
@@ -908,6 +940,13 @@ export function ChatProvidersSettings({
? provider.isReasoningModel === true
: false,
);
+ setCodexParallelCalls(
+ isCodexProviderType(provider.providerType)
+ ? clampCodexParallelCalls(
+ provider.codexParallelCalls ?? CODEX_DEFAULT_PARALLEL_CALLS,
+ )
+ : CODEX_DEFAULT_PARALLEL_CALLS,
+ );
if (
isCustomProviderType(provider.providerType) &&
!supportsRemoteModelCatalog(provider.providerType)
@@ -1175,6 +1214,46 @@ export function ChatProvidersSettings({
) : null}
+ {isCodexProvider ? (
+
+
+
+
+ Fan-out width. Each call runs the same prompt against
+ Codex and the results are unified in a final synthesis
+ tab. 1-{CODEX_MAX_PARALLEL_CALLS}.
+