Skip to content

Commit 4d7e5b1

Browse files
committed
fix: respect Codex websocket config in status
1 parent 74491fd commit 4d7e5b1

5 files changed

Lines changed: 104 additions & 11 deletions

File tree

docs-linhay/dev/20260426-relay-service-config-boundary.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ sidecar 顶层 `api-keys` 原生支持列表,因此状态页不能再只建模
258258
因此:
259259

260260
1. “一键应用”是 relay 相关配置的 merge,不是整份本地 Codex 配置的 replace
261+
2. 状态页的草稿显示也必须尊重当前 `config.toml`:当当前 `model_provider` 指向的 provider 显式写了 `supports_websockets = true/false` 时,`supports_websockets` 开关初始显示跟随该显式值;切到非当前 provider 时回到 GetTokens relay 的默认关闭策略。检测到当前 GetTokens relay provider 仍是 `true` 时,应先展示真实开启状态与风险提示,再允许用户关闭并应用为 `false`,不能为了默认规避 WebSocket 风险而把已有配置静默显示成关闭。
261262

262263
### 8. 状态页的 provider / model 目录要尽量贴近用户本地 Codex 现实
263264

docs-linhay/memory/2026-06-05.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,3 +379,10 @@
379379
- 决策:Balance 内容由左 rail checkbox 决定,支持 `quota-billing / quota-only / billing-only` 三态;取消额度模块后 Quota section 必须完全不渲染,只剩余额时 Billing 使用单模块形态。
380380
- UI 收口:账号详情嵌套模块 header 使用紧凑 title/action row,meta 不再单独成松散一行。
381381
- 验证:账号详情与 design-system focused tests、typecheck、build、docs check 通过。
382+
383+
## Status 页 supports_websockets 配置显示修正
384+
385+
- 用户指出 Status 页“应用到本地 Codex”里的 `supports_websockets` 没有尊重 `config.toml`;本机复现为 `model_provider = "gettokens"``[model_providers.gettokens] supports_websockets = true`,但前端开关默认仍显示关闭。
386+
- 修正:新增 `resolveInitialSupportsWebsocketsSelection`,Status 页加载/刷新本地 provider 状态时,若当前选中 provider 就是 `config.toml` 的当前 provider 且显式配置了 `supports_websockets`,开关初始显示跟随该值;手动切到非当前 provider 时回到 GetTokens relay 默认关闭策略。
387+
- 文档同步:`docs-linhay/dev/20260426-relay-service-config-boundary.md` 补充显示层也必须尊重当前 `config.toml`,不能为了默认规避 WebSocket 风险把已有 `true` 静默显示成关闭。
388+
- 验证:`node --test frontend/src/features/status/tests/relayLocalState.test.mjs``npm --prefix frontend run typecheck``go test ./internal/wailsapp -run 'TestParseLocalCodexModelProviderStateReadsCurrentProvider|TestGetLocalCodexModelProviderStateReadsConfigTomlFromCodexHome|TestApplyRelayServiceConfigToLocalV2WritesExplicitSupportsWebsocketsFalse|TestApplyRelayServiceConfigToLocalV2PreservesExistingSupportsWebsocketsWhenUnset'` 通过。

frontend/src/features/status/StatusFeature.tsx

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import {
4141
loadSelectedRelayReasoningEffort,
4242
resolveInitialRelayModelSelection,
4343
resolveInitialRelayProviderSelection,
44+
resolveInitialSupportsWebsocketsSelection,
4445
resolveRelayEndpointSelection,
4546
saveCodexLocalAuthStrategy,
4647
saveLANAccessEnabled,
@@ -227,6 +228,16 @@ export default function StatusFeature({
227228
}
228229
}, [codexLocalPreflight.reason, t]);
229230

231+
function selectRelayProviderID(providerID: string) {
232+
setSelectedRelayProviderID(providerID);
233+
setSupportsWebsockets(
234+
resolveInitialSupportsWebsocketsSelection({
235+
selectedProviderID: providerID,
236+
providerState: localCodexProviderState,
237+
})
238+
);
239+
}
240+
230241
useEffect(() => {
231242
let cancelled = false;
232243

@@ -356,11 +367,16 @@ export default function StatusFeature({
356367
providerState?.providers || [],
357368
activeProvider
358369
);
359-
setSelectedRelayProviderID(
360-
resolveInitialRelayProviderSelection({
361-
providerOptions: next,
362-
activeProviderID: providerState?.currentProviderID,
363-
hasExplicitActiveProvider: Boolean(providerState?.hasExplicitCurrentProvider),
370+
const nextSelectedProviderID = resolveInitialRelayProviderSelection({
371+
providerOptions: next,
372+
activeProviderID: providerState?.currentProviderID,
373+
hasExplicitActiveProvider: Boolean(providerState?.hasExplicitCurrentProvider),
374+
});
375+
setSelectedRelayProviderID(nextSelectedProviderID);
376+
setSupportsWebsockets(
377+
resolveInitialSupportsWebsocketsSelection({
378+
selectedProviderID: nextSelectedProviderID,
379+
providerState,
364380
})
365381
);
366382
return next;
@@ -839,11 +855,16 @@ export default function StatusFeature({
839855
providerState?.providers || [],
840856
activeProvider
841857
);
842-
setSelectedRelayProviderID(
843-
resolveInitialRelayProviderSelection({
844-
providerOptions: next,
845-
activeProviderID: providerState?.currentProviderID,
846-
hasExplicitActiveProvider: Boolean(providerState?.hasExplicitCurrentProvider),
858+
const nextSelectedProviderID = resolveInitialRelayProviderSelection({
859+
providerOptions: next,
860+
activeProviderID: providerState?.currentProviderID,
861+
hasExplicitActiveProvider: Boolean(providerState?.hasExplicitCurrentProvider),
862+
});
863+
setSelectedRelayProviderID(nextSelectedProviderID);
864+
setSupportsWebsockets(
865+
resolveInitialSupportsWebsocketsSelection({
866+
selectedProviderID: nextSelectedProviderID,
867+
providerState,
847868
})
848869
);
849870
return next;
@@ -1089,7 +1110,7 @@ export default function StatusFeature({
10891110
onSelectEndpointID={setSelectedEndpointID}
10901111
onCopyEndpointBaseUrl={() => void copyText(selectedEndpoint.baseUrl, t('status.endpoint_copied'))}
10911112
onOpenCreateRelayProviderEditor={openCreateRelayProviderEditor}
1092-
onSelectRelayProviderID={setSelectedRelayProviderID}
1113+
onSelectRelayProviderID={selectRelayProviderID}
10931114
onSelectCodexLocalAuthStrategy={setCodexLocalAuthStrategy}
10941115
onDeleteRelayProviderOption={deleteRelayProviderOption}
10951116
onSelectRelayReasoningEffort={setSelectedRelayReasoningEffort}

frontend/src/features/status/model/relayLocalState.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -763,6 +763,34 @@ export function saveSelectedRelayProvider(value: string) {
763763
}
764764
}
765765

766+
export interface LocalCodexModelProviderStateLike {
767+
currentProviderID?: string;
768+
currentProviderSupportsWebsockets?: boolean;
769+
currentProviderSupportsWebsocketsSet?: boolean;
770+
}
771+
772+
export interface ResolveInitialSupportsWebsocketsSelectionInput {
773+
selectedProviderID: string;
774+
providerState?: LocalCodexModelProviderStateLike | null;
775+
fallbackValue?: boolean;
776+
}
777+
778+
export function resolveInitialSupportsWebsocketsSelection(
779+
input: ResolveInitialSupportsWebsocketsSelectionInput
780+
) {
781+
const selectedProviderID = String(input.selectedProviderID || '').trim();
782+
const currentProviderID = String(input.providerState?.currentProviderID || '').trim();
783+
if (
784+
selectedProviderID &&
785+
selectedProviderID === currentProviderID &&
786+
input.providerState?.currentProviderSupportsWebsocketsSet
787+
) {
788+
return Boolean(input.providerState.currentProviderSupportsWebsockets);
789+
}
790+
791+
return Boolean(input.fallbackValue);
792+
}
793+
766794
export function loadSelectedRelayReasoningEffort() {
767795
if (typeof window === 'undefined') {
768796
return RELAY_CODEX_DEFAULT_REASONING_EFFORT;

frontend/src/features/status/tests/relayLocalState.test.mjs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
loadRelayModelOptions,
1010
resolveInitialRelayModelSelection,
1111
resolveInitialRelayProviderSelection,
12+
resolveInitialSupportsWebsocketsSelection,
1213
resolveRelayEndpointSelection,
1314
resolveCodexLocalApplyState,
1415
resolveUnifiedDiffLineTone,
@@ -73,6 +74,41 @@ test('resolveInitialRelayProviderSelection follows explicit Codex config provide
7374
);
7475
});
7576

77+
test('resolveInitialSupportsWebsocketsSelection follows explicit current Codex provider config', () => {
78+
const providerState = {
79+
currentProviderID: 'gettokens',
80+
currentProviderSupportsWebsockets: true,
81+
currentProviderSupportsWebsocketsSet: true,
82+
};
83+
84+
assert.equal(
85+
resolveInitialSupportsWebsocketsSelection({
86+
selectedProviderID: 'gettokens',
87+
providerState,
88+
}),
89+
true
90+
);
91+
assert.equal(
92+
resolveInitialSupportsWebsocketsSelection({
93+
selectedProviderID: 'other',
94+
providerState,
95+
}),
96+
false
97+
);
98+
assert.equal(
99+
resolveInitialSupportsWebsocketsSelection({
100+
selectedProviderID: 'gettokens',
101+
providerState: {
102+
currentProviderID: 'gettokens',
103+
currentProviderSupportsWebsockets: false,
104+
currentProviderSupportsWebsocketsSet: true,
105+
},
106+
fallbackValue: true,
107+
}),
108+
false
109+
);
110+
});
111+
76112
test('resolveInitialRelayProviderSelection defaults to GetTokens when Codex config has no explicit provider', () => {
77113
assert.equal(
78114
resolveInitialRelayProviderSelection({

0 commit comments

Comments
 (0)