Skip to content

Refactor UX#17

Merged
mostlygeek merged 15 commits intomainfrom
mg/refactor-ux
May 1, 2026
Merged

Refactor UX#17
mostlygeek merged 15 commits intomainfrom
mg/refactor-ux

Conversation

@mostlygeek
Copy link
Copy Markdown
Collaborator

@mostlygeek mostlygeek commented Apr 29, 2026

Huge refactor to restructure the code so each harness (internal/clients) has its own implementation. This allows specific logic to easier to manage and in an intuitive location.

Replace the previous flow (profile → backend → launch) with a
profile-centric workflow that surfaces provider choice and model
selection instead of internal backend types.

- Add CompatibleProviders and BackendsForProvider to Manager for
  provider-centric filtering
- Add DedupBackends to collapse backends sharing compat key signatures
  (e.g. Anthropic and ZAI both use anthropic_messages)
- Add ModelSelector interface and ClaudeCodeProfile.ApplyModel for
  applying user-chosen default models via ANTHROPIC_MODEL
- Add ProviderInfo.DisplayName falling back to ID when Name is empty
- Add stepSelectProvider and stepSelectModel to the TUI state machine
- Show model picker (with FQN provider_id/model_id) when provider has
  multiple models and profile supports ModelSelector
- Persist LastProviderID and LastModel in launcher state
CodexProfile was missing the ModelSelector interface, so the TUI skipped
the model picker. WriteConfig also didn't set the API base URL in
config.toml, causing Codex to connect to api.openai.com instead of the
aperture proxy, and defaulted to WebSocket transport.

- Add ApplyModel to implement ModelSelector for model picker support
- Write config.toml with a custom "aperture" provider that sets
  base_url to the aperture host and forces HTTPS transport via
  supports_websockets = false
OpenCode's UX previously asked the user to pick a backend after picking
a provider, and the generated temp config was hardcoded per backend type
rather than reflecting the chosen aperture provider. Collapse OpenCode's
SupportedBackends to one abstract entry so the backend step is skipped,
and add a ProviderConfigWriter interface that lets profiles generate
config tailored to the chosen ProviderInfo.

OpenCode's WriteProviderConfig now picks the AI SDK npm package and
options from the provider's compatibility map (anthropic_messages,
bedrock_converse, google_*, openai_responses, openai_chat) and writes
the provider's id, name, and full model list into the temp config.

For Vertex providers, set apiKey to trigger the SDK's express mode so
it skips google-auth-library's ADC lookup, and embed the full
project-scoped path (with the _aperture_auto_*_ placeholders aperture
rewrites server-side) in baseURL so the request URL matches aperture's
vertex router pattern.
Reorder pickOpenCodeSDK so providers exposing multiple compat keys land
on the preferred SDK: openai_responses → anthropic_messages → openai_chat
→ google_* → bedrock_* → gemini_generate_content. Add handlers for
bedrock_model_invoke and gemini_generate_content (mapped to @ai-sdk/google
against /v1beta).

Pin the model list to what aperture reports by writing a "whitelist"
field alongside the models map. Without it, OpenCode merges its built-in
models.dev database entries on top of our config when the provider key
matches a known ID like "openai" or "anthropic". The whitelist makes
provider.ts:1374 strip the database-sourced models so only ours remain.

Use fully qualified "<provider-id>/<model-name>" identifiers as the
model map keys and display names, keeping the bare model name in the
"id" field so OpenCode's API requests still hit the upstream model.
Claude Code on Bedrock embeds ANTHROPIC_MODEL in the request URL path,
so passing the FQN "providerID/modelID" produced
/bedrock/model/bedrock/<id>/invoke-with-response-stream and Aperture
rejected the resulting action.
Claude Code on Bedrock resolves models from
ANTHROPIC_DEFAULT_{OPUS,SONNET,HAIKU}_MODEL set in ProviderEnv and the
user can switch with /model at runtime, so the launch-time picker adds
no value. Add an optional BackendModelSelector interface so a profile
can opt out of the picker per backend, and route the TUI's existing
ModelSelector checks through a single helper that consults it.
Each agent CLI (Claude Code, Codex, Gemini, OpenCode) now lives in its
own internal/clients sub-package that owns its install, launch, env, and
config-writing logic end-to-end. The TUI becomes a generic menu-stack
engine driven by internal/menu descriptors; app-level state (active
Aperture endpoint, YOLO mode, last-launch record, provider list) moves
to internal/config.

Claude Desktop remains in internal/profiles for now with a thin adapter
to the new clients.Client contract; a follow-up will port it out and
delete the package.
OpenCode exposes a native model switcher, so selecting a model in the
Aperture TUI was redundant. The flow is now Main Menu → OpenCode → Pick
Provider → launch; the user picks a model inside OpenCode itself.
Long menus (e.g. the OpenCode provider list after all models roll up)
ran past single-digit shortcuts, leaving items unreachable by key.
Auto-numbering now draws from 1-9, a-z, A-Z in order, skipping any key
already claimed by an explicit Shortcut or Digit.

On terminals wider than 80 columns, menus with 10+ rows render in two
columns when the widest row fits twice across. Left/Right (and h/l)
move between columns; Up/Down stay within the current column. The key
handler and renderer share a single menuLayout helper so navigation
can't disagree with what's drawn.
- gemini: require an https:// FQDN Aperture endpoint and abort with a
  clear message otherwise. Gemini CLI 0.40+ rejects non-https custom
  base URLs (except literal localhost / 127.0.0.1) and strips any
  trailing slash. Also set GOOGLE_GEMINI_BASE_URL alongside the legacy
  GEMINI_BASE_URL so the new CLI picks up the gateway.
- tui: allow hidden shortcut rows (Settings "s", Install "i", endpoint
  "a" add / "d" delete) to fire from shortcut keys, and don't move the
  cursor onto a hidden row when activating it — that was causing the
  endpoints "d" handler to read a nonsense cursor and hang.
- tui: drop the redundant "Add endpoint" row from the endpoints menu;
  "a" shortcut in the footer is the single entry point.
- codex, gemini: keep the pre-refactor config paths (aperture/codex-home,
  aperture/gemini-home) so existing on-disk state is still found.
@mostlygeek mostlygeek merged commit d298d26 into main May 1, 2026
1 check passed
@mostlygeek mostlygeek deleted the mg/refactor-ux branch May 1, 2026 16:34
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