Skip to content

feat(codex): support local CLI config, reasoning, and speed options - #1252

Merged
qdaxb merged 2 commits into
wecode-ai:mainfrom
icycrystal4:codex/local-cli-config-runtimes
Jun 4, 2026
Merged

qdaxb merged 2 commits into
wecode-ai:mainfrom
icycrystal4:codex/local-cli-config-runtimes

Conversation

@icycrystal4

@icycrystal4 icycrystal4 commented Jun 3, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • Add device-local CLI runtime selection for Codex/Claude via WEGENT_LOCAL_CLI_CONFIG_RUNTIMES.
  • Propagate WeWork model options for Codex reasoning and speed through backend and executor.
  • Map speed=fast/快速 to Codex service_tier=priority and standard/标准 to default.
  • Enable model-scoped WeWork speed controls through ui.controls.speed and refine model selector hover states.
  • Improve local skill mention deletion behavior in the WeWork composer.

Associated Task

  • J0gV2IL2vk / 支持Codex Agent

Tests

  • cd backend && uv run pytest tests/services/chat/test_reasoning_config.py
  • cd executor && uv run pytest tests/agents/test_codex_config_builder.py
  • cd wework && npm test -- src/components/chat/ChatInput.test.tsx src/components/layout/MobileWorkbenchLayout.test.tsx src/lib/model-ui.test.ts
  • git diff --check

Integration

  • Deployed to the integration test environment and restarted services.
  • Verified WeWork sends model_options={reasoning: high, speed: fast}.
  • Verified backend logs apply service_tier=priority.
  • Rebuilt/restarted local executor and verified the new task was handled by executor_version=1.8.4.

Summary by CodeRabbit

  • New Features

    • Device-local CLI config can be enabled for the Codex runtime via WEGENT_LOCAL_CLI_CONFIG_RUNTIMES.
    • New "speed" model option (standard / fast — 1.5x) with UI labels and translations.
  • UX Improvements

    • More reliable deletion behavior for local-skill mentions in the chat composer.
  • Documentation

    • English and Chinese docs added describing device-local CLI behavior and scope.
  • Tests

    • Tests added/updated for local-CLI parsing, reasoning/service-tier normalization, and UI controls.

@coderabbitai

coderabbitai Bot commented Jun 3, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds device-local CLI configuration support for Codex agents. A new environment variable WEGENT_LOCAL_CLI_CONFIG_RUNTIMES allows specifying runtimes (currently codex) to use device-local CLI/provider config instead of Wegent-provided config. When enabled, the Codex config builder returns a minimal config and the Codex agent centralizes thread kwargs. Documentation and tests were added; backend and frontend model_options typings were broadened and UI controls (speed) + composer mention handling were updated.

Changes

Device-Local CLI Configuration Feature

Layer / File(s) Summary
Local CLI Config Utility and Tests
executor/config/local_cli_config.py, executor/tests/config/test_local_cli_config.py
New use_local_cli_config(runtime: str) reads WEGENT_LOCAL_CLI_CONFIG_RUNTIMES and returns whether a runtime is enabled for local CLI mode. SUPPORTED_LOCAL_CLI_CONFIG_RUNTIMES contains {"codex"}. Tests cover default unset behavior and comma-separated parsing with whitespace normalization.
Codex Configuration Integration and Thread Refactoring
executor/agents/codex/config_builder.py, executor/tests/agents/test_codex_config_builder.py
Imports use_local_cli_config; changes CodeXConfig.model_provider to Optional[str]; normalizes reasoning/service_tier earlier; adds an early-return branch when local CLI is enabled returning a minimal config (model_provider=None, config_overrides only containing model=...); refactors thread_config assembly into _build_thread_config. Tests include autouse fixture clearing the env var and coverage for reasoning/service-tier normalization and local-CLI behavior.
Codex Agent Thread kwargs refactor
executor/agents/codex/codex_agent.py
Centralizes shared thread parameters into _build_thread_kwargs(developer_instructions) and reuses **thread_kwargs for thread_resume and thread_start, preserving existing exception handling and session persistence.
Documentation
docs/en/user-guide/ai-devices/local-device-support.md, docs/zh/user-guide/ai-devices/local-device-support.md
Adds "Use Device-Local CLI Configuration" section describing WEGENT_LOCAL_CLI_CONFIG_RUNTIMES, supported value codex, device-local scope, and default behavior when unset/empty; notes Claude remains using Wegent-issued configuration.

Backend: model_options widening & service-tier aliasing

Layer / File(s) Summary
Schema and storage typing updates
backend/app/api/ws/events.py, backend/app/schemas/task.py, backend/app/services/chat/storage/task_manager.py
Widened model_options types from Dict[str, str]/dict[str, str] to Dict[str, Any]/dict[str, Any] (optional) across WS payloads, task schemas, and task creation params.
Service-tier aliasing & request wiring
backend/app/services/chat/trigger/unified.py, backend/tests/services/chat/test_reasoning_config.py
Adds SERVICE_TIER_ALIASES, _service_tier_from_model_options(payload) to map UI speed/service_tier values (including nested object forms) to canonical tiers, and writes resolved service_tier into execution request model_config; tests validate mappings and object-shaped speed handling.

Frontend: model controls, composer, and tests

Layer / File(s) Summary
Model UI and types
wework/src/lib/model-ui.ts, wework/src/types/api.ts, wework/src/i18n/locales/*
Adds summaryLabel and descriptionKey option metadata, updates getModelDisplayLabel to prefer summaryLabel via an optional resolver, updates MODEL_FAMILY_CONFIGS speed options and i18n keys, and widens frontend Task.model_options typing to `Record<string, unknown>
ModelSelector desktop submenu & control wiring
wework/src/components/chat/composer/ModelSelector.tsx
Adds desktop submenu target state for families/controls, per-control trigger refs, dedicated desktop control submenu rendering, pointer/focus handling, label resolver wiring, and container sizing adjustments.
Composer mention deletion rewrite
wework/src/components/chat/composer/ComposerTextarea.tsx
Rewrites mention-deletion logic using a deletion-range abstraction to handle Backspace/Delete across selections and multiple mentions; updates chip icon/layout styling.
ChatInput & mobile layout tests
wework/src/components/chat/ChatInput.test.tsx, wework/src/components/layout/MobileWorkbenchLayout.test.tsx
Updates tests to reflect mention-deletion behavior changes and object-shaped config.ui.controls with speed: true plus adjusted expectations for speed-related UI.
WorkbenchProvider stored options normalization
wework/src/features/workbench/WorkbenchProvider.tsx
Adds normalizeStoredModelOptions to filter stored task.model_options to string-valued entries used for model selection.
Model UI tests
wework/src/lib/model-ui.test.ts
Adds tests verifying detection of model-scoped speed control when config.ui.controls.speed is true.

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)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • qdaxb

Poem

🐰 A tiny env flag, soft and bright,
Codex listens to the device tonight,
Wegent steps back, the thread args align,
UI chips shift, and labels shine,
A rabbit cheers: local config, that's right!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.15% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main change: adding support for local CLI config, reasoning, and speed options for Codex. It is concise, specific, and directly reflects the primary objectives documented in the PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
executor/agents/claude_code/config_manager.py (1)

160-167: ⚡ Quick win

Trace 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_async or @trace_sync for entire functions, and add_span_event() or set_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 win

Add 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_async or @trace_sync for entire functions, and add_span_event() or set_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 win

Instrument 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_async or @trace_sync for entire functions, and add_span_event() or set_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

📥 Commits

Reviewing files that changed from the base of the PR and between 718f9f8 and 0d3f0ca.

📒 Files selected for processing (9)
  • docs/en/user-guide/ai-devices/local-device-support.md
  • docs/zh/user-guide/ai-devices/local-device-support.md
  • executor/agents/claude_code/config_manager.py
  • executor/agents/codex/codex_agent.py
  • executor/agents/codex/config_builder.py
  • executor/config/local_cli_config.py
  • executor/tests/agents/test_claude_code_config_security.py
  • executor/tests/agents/test_codex_config_builder.py
  • executor/tests/config/test_local_cli_config.py

@icycrystal4
icycrystal4 force-pushed the codex/local-cli-config-runtimes branch from 0d3f0ca to 0e98bb5 Compare June 3, 2026 11:30
@icycrystal4
icycrystal4 force-pushed the codex/local-cli-config-runtimes branch from 0e98bb5 to d31effe Compare June 4, 2026 03:22
@icycrystal4 icycrystal4 changed the title feat(executor): support device-local CLI config runtimes feat(codex): support local CLI config, reasoning, and speed options Jun 4, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (2)
backend/app/services/chat/trigger/unified.py (1)

79-94: ⚡ Quick win

Add tracing decorator to new service-tier helper.

_service_tier_from_model_options is newly added but not instrumented; please add @trace_sync (or @trace_async if 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_async or @trace_sync for entire functions, and add_span_event() or set_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 win

Add 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_sync plus a couple of span attributes here would make production debugging much easier.

As per coding guidelines, "Use OpenTelemetry tracing decorators: @trace_async or @trace_sync for entire functions, and add_span_event() or set_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

📥 Commits

Reviewing files that changed from the base of the PR and between d31effe and e553cb4.

📒 Files selected for processing (17)
  • backend/app/api/ws/events.py
  • backend/app/schemas/task.py
  • backend/app/services/chat/storage/task_manager.py
  • backend/app/services/chat/trigger/unified.py
  • backend/tests/services/chat/test_reasoning_config.py
  • executor/agents/codex/config_builder.py
  • executor/tests/agents/test_codex_config_builder.py
  • wework/src/components/chat/ChatInput.test.tsx
  • wework/src/components/chat/composer/ComposerTextarea.tsx
  • wework/src/components/chat/composer/ModelSelector.tsx
  • wework/src/components/layout/MobileWorkbenchLayout.test.tsx
  • wework/src/features/workbench/WorkbenchProvider.tsx
  • wework/src/i18n/locales/en/common.json
  • wework/src/i18n/locales/zh-CN/common.json
  • wework/src/lib/model-ui.test.ts
  • wework/src/lib/model-ui.ts
  • wework/src/types/api.ts
✅ Files skipped from review due to trivial changes (1)
  • wework/src/i18n/locales/en/common.json

Comment on lines +25 to +41
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",
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

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_EFFORT

Also 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.

Comment on lines +317 to +326
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'
}),
)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ 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.

Comment on lines +317 to +333
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),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

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).

Comment on lines 123 to +137
{
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',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

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.

@qdaxb
qdaxb merged commit d202e4d into wecode-ai:main Jun 4, 2026
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants