feat(settings): add Skills toggle for Codex CLI and improve config editor UX

Add "Enable Skills" toggle below "Enable WebSocket" in Codex CLI settings,
writing `skills = true` under [features] in config.toml. Also add max-height
to auth.json and config.toml textareas, and fix TOML section key insertion
to avoid stray blank lines between entries.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
xintaofei
2026-04-11 01:14:17 +08:00
parent 19979d50d0
commit 1eb9b4872e
11 changed files with 105 additions and 3 deletions

View File

@@ -118,6 +118,7 @@ interface AgentDraft {
codexProviderOptions: string[]
codexReasoningEffort: CodexReasoningEffort
codexSupportsWebsockets: boolean
codexSkills: boolean
claudeMainModel: string
claudeReasoningModel: string
claudeDefaultHaikuModel: string
@@ -1142,6 +1143,7 @@ interface CodexTomlImportantValues {
providerBaseUrls: Record<string, string>
providerSupportsWebsockets: Record<string, boolean>
featureResponsesWebsocketsV2: boolean
featureSkills: boolean
}
interface CodexImportantValues {
@@ -1152,6 +1154,7 @@ interface CodexImportantValues {
reasoningEffort: CodexReasoningEffort
providerOptions: string[]
supportsWebsockets: boolean
skills: boolean
}
const CODEX_DEFAULT_MODEL_PROVIDER = "codeg"
@@ -1312,6 +1315,7 @@ function extractCodexTomlImportantValues(
let modelReasoningEffort: CodexReasoningEffort =
CODEX_DEFAULT_REASONING_EFFORT
let featureResponsesWebsocketsV2 = false
let featureSkills = false
let currentProviderSection: string | null = null
let inFeaturesSection = false
@@ -1377,6 +1381,10 @@ function extractCodexTomlImportantValues(
featureResponsesWebsocketsV2 = boolAssignment.value
continue
}
if (inFeaturesSection && boolAssignment.key === "skills") {
featureSkills = boolAssignment.value
continue
}
const dottedProviderWebsocketMatch = boolAssignment.key.match(
/^model_providers\.([A-Za-z0-9_-]+)\.supports_websockets$/
)
@@ -1390,6 +1398,10 @@ function extractCodexTomlImportantValues(
featureResponsesWebsocketsV2 = boolAssignment.value
continue
}
if (boolAssignment.key === "features.skills") {
featureSkills = boolAssignment.value
continue
}
}
if (!assignment) continue
@@ -1436,6 +1448,7 @@ function extractCodexTomlImportantValues(
providerBaseUrls,
providerSupportsWebsockets,
featureResponsesWebsocketsV2,
featureSkills,
}
}
@@ -1530,6 +1543,7 @@ function extractCodexImportantValues(
toml.providerNames
),
supportsWebsockets: providerSupportsWebsockets,
skills: toml.featureSkills,
}
}
@@ -1701,7 +1715,14 @@ function upsertTomlSectionBooleanKey(
if (assignmentIndex >= 0) {
lines[assignmentIndex] = lineText
} else {
lines.splice(section.end, 0, lineText)
let insertAt = section.end
for (let i = section.end - 1; i > section.start; i -= 1) {
if (lines[i].trim() !== "") {
insertAt = i + 1
break
}
}
lines.splice(insertAt, 0, lineText)
}
return lines.join("\n").trim()
}
@@ -1927,6 +1948,7 @@ function patchCodexConfigTomlText(
modelProvider?: string
modelReasoningEffort?: string
supportsWebsockets?: boolean
skills?: boolean
}
): string {
let nextTomlText = configTomlText
@@ -2019,6 +2041,14 @@ function patchCodexConfigTomlText(
"responses_websockets_v2",
shouldEnableFeature ? true : null
)
if (typeof patch.skills === "boolean") {
nextTomlText = upsertTomlSectionBooleanKey(
nextTomlText,
"features",
"skills",
patch.skills ? true : null
)
}
nextTomlText = updateTomlRootBooleanKey(
nextTomlText,
"disable_response_storage",
@@ -2252,6 +2282,7 @@ function buildAgentDraft(agent: AcpAgentInfo): AgentDraft {
codexProviderOptions: codexImportant.providerOptions,
codexReasoningEffort: codexImportant.reasoningEffort,
codexSupportsWebsockets: codexImportant.supportsWebsockets,
codexSkills: codexImportant.skills,
claudeMainModel: important.claudeMainModel,
claudeReasoningModel: important.claudeReasoningModel,
claudeDefaultHaikuModel: important.claudeDefaultHaikuModel,
@@ -4432,6 +4463,7 @@ export function AcpAgentSettings() {
codexProviderOptions: important.providerOptions,
codexReasoningEffort: important.reasoningEffort,
codexSupportsWebsockets: important.supportsWebsockets,
codexSkills: important.skills,
}))
},
[selectedAgent, selectedDraft, updateSelectedDraft]
@@ -4454,6 +4486,7 @@ export function AcpAgentSettings() {
codexProviderOptions: important.providerOptions,
codexReasoningEffort: important.reasoningEffort,
codexSupportsWebsockets: important.supportsWebsockets,
codexSkills: important.skills,
}))
},
[selectedAgent, selectedDraft, updateSelectedDraft]
@@ -4505,6 +4538,7 @@ export function AcpAgentSettings() {
codexProviderOptions: synced.providerOptions,
codexReasoningEffort: synced.reasoningEffort,
codexSupportsWebsockets: synced.supportsWebsockets,
codexSkills: synced.skills,
}))
return
}
@@ -4537,6 +4571,7 @@ export function AcpAgentSettings() {
codexProviderOptions: synced.providerOptions,
codexReasoningEffort: synced.reasoningEffort,
codexSupportsWebsockets: synced.supportsWebsockets,
codexSkills: synced.skills,
}))
},
[selectedAgent, selectedDraft, updateSelectedDraft]
@@ -4602,6 +4637,7 @@ export function AcpAgentSettings() {
codexProviderOptions: synced.providerOptions,
codexReasoningEffort: synced.reasoningEffort,
codexSupportsWebsockets: synced.supportsWebsockets,
codexSkills: synced.skills,
codexAuthJsonText: nextAuth.authJsonText,
codexConfigTomlText: nextToml,
}))
@@ -4637,6 +4673,39 @@ export function AcpAgentSettings() {
codexProviderOptions: synced.providerOptions,
codexReasoningEffort: synced.reasoningEffort,
codexSupportsWebsockets: synced.supportsWebsockets,
codexSkills: synced.skills,
codexConfigTomlText: nextToml,
}))
},
[selectedAgent, selectedDraft, updateSelectedDraft]
)
const handleCodexSkillsChange = useCallback(
(enabled: boolean) => {
if (
!selectedAgent ||
!selectedDraft ||
selectedAgent.agent_type !== "codex"
)
return
const nextToml = patchCodexConfigTomlText(
selectedDraft.codexConfigTomlText,
{ skills: enabled }
)
const synced = extractCodexImportantValues(
selectedDraft.codexAuthJsonText,
nextToml
)
updateSelectedDraft((current) => ({
...current,
apiBaseUrl: synced.apiBaseUrl,
apiKey: synced.apiKey ?? current.apiKey,
model: synced.model,
codexModelProvider: synced.modelProvider,
codexProviderOptions: synced.providerOptions,
codexReasoningEffort: synced.reasoningEffort,
codexSupportsWebsockets: synced.supportsWebsockets,
codexSkills: synced.skills,
codexConfigTomlText: nextToml,
}))
},
@@ -5196,6 +5265,19 @@ export function AcpAgentSettings() {
</div>
</div>
<div className="space-y-1.5">
<div className="flex items-center justify-between rounded-md border px-3 py-2">
<label className="text-[11px] text-muted-foreground">
{t("codex.enableSkills")}
</label>
<Switch
checked={selectedDraft.codexSkills}
onCheckedChange={handleCodexSkillsChange}
aria-label={t("codex.enableSkillsAria")}
/>
</div>
</div>
<div className="space-y-1.5">
<label className="text-[11px] text-muted-foreground">
{t("codex.authJsonNative")}
@@ -5208,7 +5290,7 @@ export function AcpAgentSettings() {
placeholder={`{
"OPENAI_API_KEY": "sk-..."
}`}
className="min-h-28 font-mono text-xs"
className="min-h-28 max-h-60 font-mono text-xs"
/>
{selectedCodexAuthError && (
<div className="rounded-md border border-red-500/30 bg-red-500/5 px-2.5 py-1.5 text-[11px] text-red-400">
@@ -5237,7 +5319,7 @@ responses_websockets_v2 = true
[model_providers.codeg]
base_url = "https://api.openai.com/v1"
supports_websockets = true`}
className="min-h-40 font-mono text-xs"
className="min-h-40 max-h-80 font-mono text-xs"
/>
</div>

View File

@@ -515,6 +515,8 @@
"selectReasoningEffort": "اختر Reasoning Effort",
"enableWebsocket": "تفعيل WebSocket",
"enableWebsocketAria": "تفعيل WebSocket لـ Codex Provider",
"enableSkills": "تفعيل Skills",
"enableSkillsAria": "تفعيل Skills لـ Codex",
"authJsonNative": "auth.json (أصلي)",
"configTomlNative": "config.toml (أصلي)"
},

View File

@@ -515,6 +515,8 @@
"selectReasoningEffort": "Reasoning Effort auswählen",
"enableWebsocket": "WebSocket aktivieren",
"enableWebsocketAria": "WebSocket für Codex Provider aktivieren",
"enableSkills": "Skills aktivieren",
"enableSkillsAria": "Skills für Codex aktivieren",
"authJsonNative": "auth.json (nativ)",
"configTomlNative": "config.toml (nativ)"
},

View File

@@ -515,6 +515,8 @@
"selectReasoningEffort": "Select Reasoning Effort",
"enableWebsocket": "Enable WebSocket",
"enableWebsocketAria": "Enable WebSocket for Codex Provider",
"enableSkills": "Enable Skills",
"enableSkillsAria": "Enable Skills for Codex",
"authJsonNative": "auth.json (native)",
"configTomlNative": "config.toml (native)"
},

View File

@@ -515,6 +515,8 @@
"selectReasoningEffort": "Seleccionar Reasoning Effort",
"enableWebsocket": "Habilitar WebSocket",
"enableWebsocketAria": "Habilitar WebSocket para Codex Provider",
"enableSkills": "Habilitar Skills",
"enableSkillsAria": "Habilitar Skills para Codex",
"authJsonNative": "auth.json (nativo)",
"configTomlNative": "config.toml (nativo)"
},

View File

@@ -515,6 +515,8 @@
"selectReasoningEffort": "Sélectionner Reasoning Effort",
"enableWebsocket": "Activer WebSocket",
"enableWebsocketAria": "Activer WebSocket pour Codex Provider",
"enableSkills": "Activer Skills",
"enableSkillsAria": "Activer Skills pour Codex",
"authJsonNative": "auth.json (natif)",
"configTomlNative": "config.toml (natif)"
},

View File

@@ -515,6 +515,8 @@
"selectReasoningEffort": "Reasoning Effort を選択",
"enableWebsocket": "WebSocket を有効化",
"enableWebsocketAria": "Codex Provider の WebSocket を有効化",
"enableSkills": "Skills を有効化",
"enableSkillsAria": "Codex の Skills を有効化",
"authJsonNative": "auth.jsonネイティブ",
"configTomlNative": "config.tomlネイティブ"
},

View File

@@ -515,6 +515,8 @@
"selectReasoningEffort": "Reasoning Effort 선택",
"enableWebsocket": "WebSocket 활성화",
"enableWebsocketAria": "Codex Provider용 WebSocket 활성화",
"enableSkills": "Skills 활성화",
"enableSkillsAria": "Codex용 Skills 활성화",
"authJsonNative": "auth.json (네이티브)",
"configTomlNative": "config.toml (네이티브)"
},

View File

@@ -515,6 +515,8 @@
"selectReasoningEffort": "Selecionar Reasoning Effort",
"enableWebsocket": "Habilitar WebSocket",
"enableWebsocketAria": "Habilitar WebSocket para Codex Provider",
"enableSkills": "Habilitar Skills",
"enableSkillsAria": "Habilitar Skills para Codex",
"authJsonNative": "auth.json (nativo)",
"configTomlNative": "config.toml (nativo)"
},

View File

@@ -515,6 +515,8 @@
"selectReasoningEffort": "选择 Reasoning Effort",
"enableWebsocket": "启用 WebSocket",
"enableWebsocketAria": "Codex Provider 启用 WebSocket",
"enableSkills": "启用 Skills",
"enableSkillsAria": "Codex 启用 Skills",
"authJsonNative": "auth.json原生",
"configTomlNative": "config.toml原生"
},

View File

@@ -515,6 +515,8 @@
"selectReasoningEffort": "選擇 Reasoning Effort",
"enableWebsocket": "啟用 WebSocket",
"enableWebsocketAria": "Codex Provider 啟用 WebSocket",
"enableSkills": "啟用 Skills",
"enableSkillsAria": "Codex 啟用 Skills",
"authJsonNative": "auth.json原生",
"configTomlNative": "config.toml原生"
},