Skip to content

refactor(tui): unify API format representation across all apps#236

Open
heliar-k wants to merge 6 commits into
SaladDay:mainfrom
heliar-k:feat/api_format_sellection_for_codex
Open

refactor(tui): unify API format representation across all apps#236
heliar-k wants to merge 6 commits into
SaladDay:mainfrom
heliar-k:feat/api_format_sellection_for_codex

Conversation

@heliar-k
Copy link
Copy Markdown

@heliar-k heliar-k commented Jun 5, 2026

Summary

Add a Codex API format picker to the TUI provider form, unify API format representation across all apps under a single ApiFormat enum, and fix a bug where provider meta would revert to old values after editing.

Key Changes

1. Codex API Format Picker

  • Add API format picker to Codex provider form (non-official providers)
  • Picker shows 2 options: OpenAI Responses (default) and OpenAI Chat (local routing)
  • Reuses the existing picker overlay infrastructure from Claude/Gemini

2. Enum Unification

  • Rename ClaudeApiFormatApiFormat (describes generic wire protocols, not Claude-specific)
  • Replace separate claude_api_format + codex_api_format fields with a single api_format field
  • Unify field identifier: ProviderAddField::ClaudeApiFormat + ::CodexApiFormat::ApiFormat

3. Naming Consistency

  • Proxy: get_claude_api_formatget_provider_api_format
  • CLI constants: CLAUDE_API_FORMAT_*API_FORMAT_* (shared by both paths)
  • i18n: tui_label_claude_api_formattui_label_api_format
  • CLI function pairs retain symmetric naming: claude_*codex_*

4. Codex Sponsor Templates

  • Codex sponsor templates now emit apiFormat meta, aligning CLI output with TUI serializer

5. Code Cleanup

  • Remove dead ApiFormat enum from proxy/types.rs
  • Remove unreachable Codex branch from render arm

6. Bug Fix: Provider Meta Revert After Edit

  • Fix bug where changing a provider's apiFormat (e.g., from openai_chat to Anthropic) in the TUI would appear to save, but revert to the old value after restart
  • Root cause: update_provider_meta() removed the entire meta key when the meta object became empty, producing JSON without meta. On deserialization this yielded meta: None, which triggered the merge logic in ProviderService::update() to preserve the old meta (including the old apiFormat)
  • Fix: keep "meta": {} in the JSON output even when empty, so deserialization produces Some(ProviderMeta::default()) and the merge logic correctly treats the form output as authoritative
  • Added end-to-end regression test covering the full TUI form → ProviderService::update → AppState reload cycle

Impact

  • 28 files changed, 462 insertions, 282 deletions
  • Eliminates dual-field synchronization complexity
  • Makes API format semantics consistent across Claude/Codex/Gemini/OpenCode/Hermes/OpenClaw
  • Fixes provider meta revert bug that affected any edit that emptied the meta object

Tests

  • All 29 api_format tests pass (including new test_exact_tui_form_api_format_roundtrip regression test)
  • All 8 codex_local_routing tests pass
  • All 24 provider_add_form_codex tests pass
  • cli_provider_templates_match_tui_serializer_output (previously failing, now fixed)

Verification

cd src-tauri
cargo build
cargo fmt --check
cargo clippy
cargo test --lib -- api_format
cargo test --lib -- codex_local_routing
cargo test --lib -- provider_add_form_codex
cargo test --test provider_commands

@heliar-k heliar-k marked this pull request as draft June 5, 2026 05:14
heliar-k added 4 commits June 5, 2026 08:35
Why this change was needed:
The proxy layer already supported converting between Responses API and
Chat Completions API for Codex providers, but the TUI had no direct way
to configure which upstream API format to use. Users could only
indirectly control this through the Local Routing sub-page.

What changed:
- Added CodexApiFormat enum (Chat/Responses) and codex_api_format state
  field as the authoritative source of truth for Codex routing
- Added CodexApiFormat field to the main Codex provider form, positioned
  between Model and Local Routing
- Renamed ClaudeApiFormatPicker to ApiFormatPicker with handler
  dispatch by app_type (Claude writes claude_api_format, Codex writes
  codex_api_format)
- Migrated all Codex-specific claude_api_format references to
  codex_api_format (toggle, loading, serialization, templates)
- Renamed i18n functions for consistency
  (tui_claude_api_format_requires_proxy_title ->
   tui_api_format_requires_proxy_title)

Problem solved:
Users can now directly select the upstream API format for Codex
providers (OpenAI Chat Completions vs OpenAI Responses API) from the
main provider form, enabling use of Chat Completions-only providers
with the latest Codex CLI through the local proxy.
Align CLI template seed with TUI serializer by passing app_type to
template_default_meta so Codex sponsor templates emit apiFormat meta.
Add Default derive to CodexApiFormat, rename popup title function
for naming consistency, and remove unreachable Codex branch from
ClaudeApiFormat render arm.
…Format

Why this change was needed:
`CodexApiFormat::Chat` and `CodexApiFormat::Responses` were ambiguous
next to `ClaudeApiFormat::OpenAiChat` and `ClaudeApiFormat::OpenAiResponses`.
The two enums represent the same wire protocols and should share naming.

What changed:
- Renamed `CodexApiFormat::Chat` → `OpenAiChat`
- Renamed `CodexApiFormat::Responses` → `OpenAiResponses`
- Updated all 10 usage sites across 5 files

Problem solved:
Readers no longer need to mentally map `Chat` → `OpenAiChat` when
comparing the two enums; the naming is now self-documenting.
@heliar-k heliar-k force-pushed the feat/api_format_sellection_for_codex branch from 221cd7c to 0f612c6 Compare June 5, 2026 08:38
@heliar-k heliar-k changed the title feat(tui): add Codex API format picker in provider form refactor(tui): unify API format representation across all apps Jun 5, 2026
heliar-k added 2 commits June 5, 2026 08:57
Why this change was needed:
The previous refactor over-renamed Claude-specific API format functions
to generic names, losing the symmetry with their codex_ counterparts.

What changed:
- Restored 7 CLI function names: normalize_claude_api_format,
  effective_claude_api_format, strip_claude_api_format_legacy_settings,
  apply_claude_api_format, apply_fixed_claude_api_format_if_needed,
  prompt_claude_api_format, prompt_and_apply_claude_api_format
- Restored 9 test function names to claude_api_format_* prefix
- Kept prompt_api_format_select generic (shared helper)
- Kept API_FORMAT_* constants generic (used by both paths)

Problem solved:
Claude and Codex API format functions now have symmetric naming
(claude_* vs codex_*), making it clear which path each function serves.
Why this change was needed:
Users reported that changing a Claude provider's api_format from "openai_chat"
to Anthropic native in the TUI would appear to save successfully, but the value
would revert to "openai_chat" after restarting the application.

What changed:
- Modified update_provider_meta() to keep the "meta" key in JSON output even
  when the meta object is empty (produces "meta": {} instead of omitting it)
- Added comprehensive regression test exercising the full TUI form edit flow
  from ProviderAddFormState through ProviderService::update() and AppState reload

Problem solved:
The root cause was a two-part bug chain:
1. update_provider_meta() removed the entire "meta" key when meta became empty
2. ProviderService::update() merge logic preserved old meta when incoming
   provider had meta: None (JSON deserialization of missing key = None)

By keeping "meta": {} in the JSON, deserialization now produces
Some(ProviderMeta::default()) instead of None, ensuring the merge logic treats
the form output as authoritative rather than falling back to old meta values.
@heliar-k heliar-k marked this pull request as ready for review June 5, 2026 12:02
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.

1 participant