Repository navigation
feat(codex): support local CLI config, reasoning, and speed options - #1252
Conversation
📝 WalkthroughWalkthroughThis PR adds device-local CLI configuration support for Codex agents. A new environment variable ChangesDevice-Local CLI Configuration Feature
Backend: model_options widening & service-tier aliasing
Frontend: model controls, composer, and tests
Sequence Diagram(s) sequenceDiagram
participant Client
participant build_codex_config
participant use_local_cli_config
participant CodeXAgent
Client->>build_codex_config: request CodeXConfig(reasoning, model)
build_codex_config->>use_local_cli_config: use_local_cli_config("codex")?
alt local CLI enabled
build_codex_config-->>Client: return minimal CodeXConfig (model_provider=None, thread_config)
else not enabled
build_codex_config-->>Client: return full CodeXConfig (provider, thread_config)
end
Client->>CodeXAgent: start/resume thread with CodeXConfig
CodeXAgent->>CodeXAgent: _build_thread_kwargs(developer_instructions, model_provider?)
CodeXAgent->>CodeXAgent: thread_resume or thread_start(**thread_kwargs)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (3)
executor/agents/claude_code/config_manager.py (1)
160-167: ⚡ Quick winTrace the Claude local-cli bypass decision.
This branch suppresses Wegent-injected auth/model env construction; add trace attributes/events for enabled runtime and bypass path to keep audits/debugging straightforward.
As per coding guidelines, "Use OpenTelemetry tracing decorators:
@trace_asyncor@trace_syncfor entire functions, andadd_span_event()orset_span_attribute()for adding details to spans".🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@executor/agents/claude_code/config_manager.py` around lines 160 - 167, The branch that returns the device-local Claude CLI config (the use_local_cli_config("claude") path) lacks OpenTelemetry trace information; wrap the entire enclosing function (e.g., the config retrieval function in config_manager.py) with the appropriate tracing decorator (`@trace_sync` or `@trace_async` as used elsewhere) and, inside this branch, call set_span_attribute() to record that "claude_local_cli_bypass" or similar is true and add_span_event() to log details like the runtime source ("device-local") and the resolved includeCoAuthoredBy value (from the CLAUDE_CODE_INCLUDE_CO_AUTHORED_BY env var), so audits can see that Wegent-injected auth/model env construction was bypassed.executor/config/local_cli_config.py (1)
13-23: ⚡ Quick winAdd tracing to the new public config helper.
Please decorate this function with the repo-standard sync tracing decorator and add a small span attribute/event for runtime selection outcome to keep local/system config decisions observable.
As per coding guidelines, "Use OpenTelemetry tracing decorators:
@trace_asyncor@trace_syncfor entire functions, andadd_span_event()orset_span_attribute()for adding details to spans".🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@executor/config/local_cli_config.py` around lines 13 - 23, Decorate the use_local_cli_config function with the repo-standard sync tracer (use `@trace_sync`) and add a span attribute or event recording the selection outcome; specifically, import trace_sync and add_span_event (or set_span_attribute) from the tracing utilities, wrap the entire use_local_cli_config with `@trace_sync`, and inside the function after computing enabled_runtimes and the boolean result include a span event/attribute such as "local_cli_config.runtime" = runtime_name and "local_cli_config.enabled" = (runtime_name in enabled_runtimes) so the decision (selected/not selected) is observable; keep names consistent with existing tracing helpers and ensure imports are added for trace_sync and add_span_event/set_span_attribute.executor/agents/codex/config_builder.py (1)
61-74: ⚡ Quick winInstrument local-vs-system Codex config path in traces.
This branch changes auth/provider injection behavior; add trace attributes/events around the branch decision and resulting config mode to make runtime diagnostics easier.
As per coding guidelines, "Use OpenTelemetry tracing decorators:
@trace_asyncor@trace_syncfor entire functions, andadd_span_event()orset_span_attribute()for adding details to spans".Also applies to: 103-115
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@executor/agents/codex/config_builder.py` around lines 61 - 74, Wrap the enclosing function with the OpenTelemetry tracing decorator (`@trace_sync` or `@trace_async` as appropriate) and, inside the local_config branch (where use_local_cli_config("codex") is checked and the CodeXConfig is constructed), add span metadata: call set_span_attribute("codex.config.mode","local") and set_span_attribute for key details such as "codex.model" (model), "codex.overrides" (overrides tuple), and "codex.codex_bin" (value returned by _resolve_codex_binary); also add a span event via add_span_event("codex.config.created", {"provider": None, "thread_config": "<summary/ref>"}) to record the created config, and mirror the same decorator/attribute/event pattern in the other branch area mentioned (the code that constructs system/provider-backed CodeXConfig around the _build_thread_config and provider injection logic) so the span reflects whether local vs system mode was chosen and key config values (use_local_cli_config, CodeXConfig, _resolve_codex_binary, _build_thread_config, reasoning).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@executor/agents/claude_code/config_manager.py`:
- Around line 160-167: The branch that returns the device-local Claude CLI
config (the use_local_cli_config("claude") path) lacks OpenTelemetry trace
information; wrap the entire enclosing function (e.g., the config retrieval
function in config_manager.py) with the appropriate tracing decorator
(`@trace_sync` or `@trace_async` as used elsewhere) and, inside this branch, call
set_span_attribute() to record that "claude_local_cli_bypass" or similar is true
and add_span_event() to log details like the runtime source ("device-local") and
the resolved includeCoAuthoredBy value (from the
CLAUDE_CODE_INCLUDE_CO_AUTHORED_BY env var), so audits can see that
Wegent-injected auth/model env construction was bypassed.
In `@executor/agents/codex/config_builder.py`:
- Around line 61-74: Wrap the enclosing function with the OpenTelemetry tracing
decorator (`@trace_sync` or `@trace_async` as appropriate) and, inside the
local_config branch (where use_local_cli_config("codex") is checked and the
CodeXConfig is constructed), add span metadata: call
set_span_attribute("codex.config.mode","local") and set_span_attribute for key
details such as "codex.model" (model), "codex.overrides" (overrides tuple), and
"codex.codex_bin" (value returned by _resolve_codex_binary); also add a span
event via add_span_event("codex.config.created", {"provider": None,
"thread_config": "<summary/ref>"}) to record the created config, and mirror the
same decorator/attribute/event pattern in the other branch area mentioned (the
code that constructs system/provider-backed CodeXConfig around the
_build_thread_config and provider injection logic) so the span reflects whether
local vs system mode was chosen and key config values (use_local_cli_config,
CodeXConfig, _resolve_codex_binary, _build_thread_config, reasoning).
In `@executor/config/local_cli_config.py`:
- Around line 13-23: Decorate the use_local_cli_config function with the
repo-standard sync tracer (use `@trace_sync`) and add a span attribute or event
recording the selection outcome; specifically, import trace_sync and
add_span_event (or set_span_attribute) from the tracing utilities, wrap the
entire use_local_cli_config with `@trace_sync`, and inside the function after
computing enabled_runtimes and the boolean result include a span event/attribute
such as "local_cli_config.runtime" = runtime_name and "local_cli_config.enabled"
= (runtime_name in enabled_runtimes) so the decision (selected/not selected) is
observable; keep names consistent with existing tracing helpers and ensure
imports are added for trace_sync and add_span_event/set_span_attribute.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 538f1b70-d4af-4359-ad5a-e3a0907193fd
📒 Files selected for processing (9)
docs/en/user-guide/ai-devices/local-device-support.mddocs/zh/user-guide/ai-devices/local-device-support.mdexecutor/agents/claude_code/config_manager.pyexecutor/agents/codex/codex_agent.pyexecutor/agents/codex/config_builder.pyexecutor/config/local_cli_config.pyexecutor/tests/agents/test_claude_code_config_security.pyexecutor/tests/agents/test_codex_config_builder.pyexecutor/tests/config/test_local_cli_config.py
0d3f0ca to
0e98bb5
Compare
0e98bb5 to
d31effe
Compare
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (2)
backend/app/services/chat/trigger/unified.py (1)
79-94: ⚡ Quick winAdd tracing decorator to new service-tier helper.
_service_tier_from_model_optionsis newly added but not instrumented; please add@trace_sync(or@trace_asyncif converted later) to keep observability compliance.Suggested patch
from app.services.context import context_service +from shared.telemetry.decorators import trace_sync @@ -def _service_tier_from_model_options(payload: Any) -> Optional[str]: +@trace_sync +def _service_tier_from_model_options(payload: Any) -> Optional[str]: """Convert UI speed options into Codex service tier values."""As per coding guidelines: Use OpenTelemetry tracing decorators:
@trace_asyncor@trace_syncfor entire functions, andadd_span_event()orset_span_attribute()for adding details to spans.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@backend/app/services/chat/trigger/unified.py` around lines 79 - 94, The helper _service_tier_from_model_options is missing OpenTelemetry instrumentation; add the `@trace_sync` decorator above its definition and inside the function call add_span_event() or set_span_attribute() to record the resolved service_tier (use the resolved value before returning). Ensure you import the correct decorator (trace_sync) and span helpers and reference the function name _service_tier_from_model_options when making the changes.executor/agents/codex/config_builder.py (1)
84-136: ⚡ Quick winAdd tracing on the new config-builder entry path.
This function now decides between device-local CLI config and Wegent-injected provider config, and it also normalizes reasoning/service-tier inputs, but none of that is visible in tracing. Adding
@trace_syncplus a couple of span attributes here would make production debugging much easier.As per coding guidelines, "Use OpenTelemetry tracing decorators:
@trace_asyncor@trace_syncfor entire functions, andadd_span_event()orset_span_attribute()for adding details to spans".🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@executor/agents/codex/config_builder.py` around lines 84 - 136, Annotate build_codex_config with the OpenTelemetry decorator `@trace_sync` and inside the function set span attributes (via set_span_attribute) for key values: "codex.model" = model, "codex.local_config" = bool(local_config), "codex.model_provider" = model_provider or "none", and "codex.service_tier" = service_tier; also emit a brief span event (add_span_event) when returning the constructed CodeXConfig indicating which path was used ("local" vs "remote") and include normalized reasoning fields effort/summary as attributes. Ensure you import trace_sync, set_span_attribute, and add_span_event from the tracing helpers used elsewhere in the codebase and add attributes after local_config/model_provider are resolved but before returning.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@executor/agents/codex/config_builder.py`:
- Around line 25-41: REASONING_EFFORT_ALIASES currently maps
omitted/explicit-off keys ("none","off","disabled","关闭") to
DEFAULT_REASONING_EFFORT causing explicit overrides; change those entries to a
distinct sentinel (e.g., None or "unset") instead of DEFAULT_REASONING_EFFORT in
REASONING_EFFORT_ALIASES, and update the logic that computes
model_reasoning_effort to treat that sentinel as "no override" (leave unset)
while still falling back to DEFAULT_REASONING_EFFORT only when the alias is the
empty string or truly missing; reference REASONING_EFFORT_ALIASES,
DEFAULT_REASONING_EFFORT and the code paths that assign model_reasoning_effort
to implement this separation.
In `@wework/src/features/workbench/WorkbenchProvider.tsx`:
- Around line 317-333: The helper normalizeStoredModelOptions currently filters
ModelOptions to only string values, which strips numbers/booleans/objects on
rehydration; update normalizeStoredModelOptions (and its return typing) so it
preserves non-string values by returning
Object.fromEntries(Object.entries(options).filter(([, v]) => v !== undefined))
or simply Object.fromEntries(Object.entries(options)) cast to ModelOptions, and
ensure getTaskModelSelection uses that updated function so reopened tasks retain
their original non-string model_options (reference functions:
normalizeStoredModelOptions and getTaskModelSelection).
- Around line 317-326: Extract the normalizeStoredModelOptions helper (and any
related task/model-selection normalization helpers) out of WorkbenchProvider
into a new small module (e.g., workbenchModelUtils or useModelNormalization) and
export it; then replace the local function in WorkbenchProvider with an import
of that exported function. Ensure the new module exports a properly typed
function signature (normalizeStoredModelOptions(options?: Record<string,
unknown> | null): ModelOptions) and update any call sites inside
WorkbenchProvider to import the helper so the provider file remains smaller and
the normalization logic is centralized.
In `@wework/src/lib/model-ui.ts`:
- Around line 123-137: The hard-coded label/description strings inside
MODEL_FAMILY_CONFIGS are now Chinese, causing mixed-language fallbacks when
resolveLabel is omitted by getModelDisplayLabel/getSelectedModelDisplayLabel;
change those literal values (e.g., the label, summaryLabel, description fields
for the 'standard' and 'fast' entries) to locale-neutral English/default tokens
(e.g., "Standard", "Fast", "Default speed", "1.5x speed, increased cost" or
simple neutral keys) so the fallback path remains language-neutral and
translations come from labelKey/descriptionKey via resolveLabel.
---
Nitpick comments:
In `@backend/app/services/chat/trigger/unified.py`:
- Around line 79-94: The helper _service_tier_from_model_options is missing
OpenTelemetry instrumentation; add the `@trace_sync` decorator above its
definition and inside the function call add_span_event() or set_span_attribute()
to record the resolved service_tier (use the resolved value before returning).
Ensure you import the correct decorator (trace_sync) and span helpers and
reference the function name _service_tier_from_model_options when making the
changes.
In `@executor/agents/codex/config_builder.py`:
- Around line 84-136: Annotate build_codex_config with the OpenTelemetry
decorator `@trace_sync` and inside the function set span attributes (via
set_span_attribute) for key values: "codex.model" = model, "codex.local_config"
= bool(local_config), "codex.model_provider" = model_provider or "none", and
"codex.service_tier" = service_tier; also emit a brief span event
(add_span_event) when returning the constructed CodeXConfig indicating which
path was used ("local" vs "remote") and include normalized reasoning fields
effort/summary as attributes. Ensure you import trace_sync, set_span_attribute,
and add_span_event from the tracing helpers used elsewhere in the codebase and
add attributes after local_config/model_provider are resolved but before
returning.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: c6b4f69a-e40b-4561-8e93-1492f60abf54
📒 Files selected for processing (17)
backend/app/api/ws/events.pybackend/app/schemas/task.pybackend/app/services/chat/storage/task_manager.pybackend/app/services/chat/trigger/unified.pybackend/tests/services/chat/test_reasoning_config.pyexecutor/agents/codex/config_builder.pyexecutor/tests/agents/test_codex_config_builder.pywework/src/components/chat/ChatInput.test.tsxwework/src/components/chat/composer/ComposerTextarea.tsxwework/src/components/chat/composer/ModelSelector.tsxwework/src/components/layout/MobileWorkbenchLayout.test.tsxwework/src/features/workbench/WorkbenchProvider.tsxwework/src/i18n/locales/en/common.jsonwework/src/i18n/locales/zh-CN/common.jsonwework/src/lib/model-ui.test.tswework/src/lib/model-ui.tswework/src/types/api.ts
✅ Files skipped from review due to trivial changes (1)
- wework/src/i18n/locales/en/common.json
| REASONING_EFFORT_ALIASES = { | ||
| "": DEFAULT_REASONING_EFFORT, | ||
| "none": DEFAULT_REASONING_EFFORT, | ||
| "off": DEFAULT_REASONING_EFFORT, | ||
| "false": DEFAULT_REASONING_EFFORT, | ||
| "disabled": DEFAULT_REASONING_EFFORT, | ||
| "关闭": DEFAULT_REASONING_EFFORT, | ||
| "低": "low", | ||
| "中": "medium", | ||
| "中等": "medium", | ||
| "高": "high", | ||
| "超高": "xhigh", | ||
| "最高": "xhigh", | ||
| "extra_high": "xhigh", | ||
| "ultra": "xhigh", | ||
| "x-high": "xhigh", | ||
| } |
There was a problem hiding this comment.
Keep “reasoning unset/off” separate from the fallback path.
Lines 207-223 currently turn both omitted reasoning and explicit disable values (none, off, disabled, 关闭) into model_reasoning_effort=medium. That means every Codex thread now gets an explicit reasoning override, even when the caller wanted no override or explicitly turned reasoning off. It also contradicts Line 22, where "none" is still treated as a valid Codex effort.
Suggested fix
def _normalize_reasoning(value: Any) -> dict[str, str]:
+ if value is None:
+ return {}
+
if isinstance(value, str):
effort = value
summary = None
elif isinstance(value, dict):
effort = value.get("effort") or value.get("reasoning")
summary = value.get("summary")
if isinstance(effort, dict):
summary = summary or effort.get("summary")
effort = effort.get("effort") or effort.get("reasoning")
else:
- return {"effort": DEFAULT_REASONING_EFFORT}
+ return {}
effort_value = _normalize_reasoning_effort(effort)
- result: dict[str, str] = {"effort": effort_value}
+ result: dict[str, str] = {}
+ if effort_value is not None:
+ result["effort"] = effort_value
if summary:
summary_value = str(summary).lower()
if summary_value in CODEX_REASONING_SUMMARIES:
result["summary"] = summary_value
return result
-def _normalize_reasoning_effort(value: Any) -> str:
+def _normalize_reasoning_effort(value: Any) -> Optional[str]:
normalized = str(value or "").strip().lower()
+ if normalized in {"", "off", "false", "disabled", "关闭"}:
+ return None
aliased = REASONING_EFFORT_ALIASES.get(normalized, normalized)
- if aliased in CODEX_REASONING_EFFORTS and aliased != "none":
+ if aliased in CODEX_REASONING_EFFORTS:
return aliased
return DEFAULT_REASONING_EFFORTAlso applies to: 196-223
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@executor/agents/codex/config_builder.py` around lines 25 - 41,
REASONING_EFFORT_ALIASES currently maps omitted/explicit-off keys
("none","off","disabled","关闭") to DEFAULT_REASONING_EFFORT causing explicit
overrides; change those entries to a distinct sentinel (e.g., None or "unset")
instead of DEFAULT_REASONING_EFFORT in REASONING_EFFORT_ALIASES, and update the
logic that computes model_reasoning_effort to treat that sentinel as "no
override" (leave unset) while still falling back to DEFAULT_REASONING_EFFORT
only when the alias is the empty string or truly missing; reference
REASONING_EFFORT_ALIASES, DEFAULT_REASONING_EFFORT and the code paths that
assign model_reasoning_effort to implement this separation.
| function normalizeStoredModelOptions( | ||
| options?: Record<string, unknown> | null, | ||
| ): ModelOptions { | ||
| if (!options) return {} | ||
| return Object.fromEntries( | ||
| Object.entries(options).filter((entry): entry is [string, string] => { | ||
| return typeof entry[1] === 'string' | ||
| }), | ||
| ) | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift
Extract this helper into a smaller module instead of growing the provider further.
This file is already well past the repo's 1000-line limit, and adding more local helpers here makes the provider harder to maintain. Please move the task/model-selection normalization helpers into a dedicated utility or hook module as you continue this change.
As per coding guidelines, "File size limit: If a file exceeds 1000 lines, split it into multiple sub-modules".
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@wework/src/features/workbench/WorkbenchProvider.tsx` around lines 317 - 326,
Extract the normalizeStoredModelOptions helper (and any related
task/model-selection normalization helpers) out of WorkbenchProvider into a new
small module (e.g., workbenchModelUtils or useModelNormalization) and export it;
then replace the local function in WorkbenchProvider with an import of that
exported function. Ensure the new module exports a properly typed function
signature (normalizeStoredModelOptions(options?: Record<string, unknown> |
null): ModelOptions) and update any call sites inside WorkbenchProvider to
import the helper so the provider file remains smaller and the normalization
logic is centralized.
| function normalizeStoredModelOptions( | ||
| options?: Record<string, unknown> | null, | ||
| ): ModelOptions { | ||
| if (!options) return {} | ||
| return Object.fromEntries( | ||
| Object.entries(options).filter((entry): entry is [string, string] => { | ||
| return typeof entry[1] === 'string' | ||
| }), | ||
| ) | ||
| } | ||
|
|
||
| function getTaskModelSelection(task: Task | null): ModelSelectionConfig | null { | ||
| if (!task?.model_id) return null | ||
| return { | ||
| modelName: task.model_id, | ||
| modelType: task.force_override_bot_model_type ?? null, | ||
| options: task.model_options ?? {}, | ||
| options: normalizeStoredModelOptions(task.model_options), |
There was a problem hiding this comment.
Preserve persisted non-string model options for reopened tasks.
Task.model_options is now widened to arbitrary value types, but this helper drops everything except strings before the task is rehydrated. That means reopening a task strips object/boolean/number options from the active selection, so the next turn can resend a different config than the one the task was created with.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@wework/src/features/workbench/WorkbenchProvider.tsx` around lines 317 - 333,
The helper normalizeStoredModelOptions currently filters ModelOptions to only
string values, which strips numbers/booleans/objects on rehydration; update
normalizeStoredModelOptions (and its return typing) so it preserves non-string
values by returning Object.fromEntries(Object.entries(options).filter(([, v]) =>
v !== undefined)) or simply Object.fromEntries(Object.entries(options)) cast to
ModelOptions, and ensure getTaskModelSelection uses that updated function so
reopened tasks retain their original non-string model_options (reference
functions: normalizeStoredModelOptions and getTaskModelSelection).
| { | ||
| value: 'standard', | ||
| label: 'Standard', | ||
| description: 'Default speed', | ||
| label: '标准', | ||
| labelKey: 'workbench.speed_standard', | ||
| description: '默认速度', | ||
| descriptionKey: 'workbench.speed_standard_description', | ||
| order: 10, | ||
| }, | ||
| { | ||
| value: 'fast', | ||
| label: 'Fast', | ||
| description: '1.5x speed, increased usage', | ||
| label: '⚡ 快速', | ||
| labelKey: 'workbench.speed_fast', | ||
| summaryLabel: '⚡', | ||
| description: '1.5 倍速度,消耗增加', | ||
| descriptionKey: 'workbench.speed_fast_description', |
There was a problem hiding this comment.
Keep MODEL_FAMILY_CONFIGS fallbacks locale-neutral.
getModelDisplayLabel() and getSelectedModelDisplayLabel() still allow resolveLabel to be omitted. With these literals changed to Chinese, that fallback path now returns mixed-language labels for any unresolved usage. Keep the hard-coded fallback text locale-neutral and let labelKey / descriptionKey supply the translated copy.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@wework/src/lib/model-ui.ts` around lines 123 - 137, The hard-coded
label/description strings inside MODEL_FAMILY_CONFIGS are now Chinese, causing
mixed-language fallbacks when resolveLabel is omitted by
getModelDisplayLabel/getSelectedModelDisplayLabel; change those literal values
(e.g., the label, summaryLabel, description fields for the 'standard' and 'fast'
entries) to locale-neutral English/default tokens (e.g., "Standard", "Fast",
"Default speed", "1.5x speed, increased cost" or simple neutral keys) so the
fallback path remains language-neutral and translations come from
labelKey/descriptionKey via resolveLabel.
Summary
Associated Task
Tests
Integration
Summary by CodeRabbit
New Features
UX Improvements
Documentation
Tests