Skip to content

Support adapter-specific chat model selection#44

Merged
jmagar merged 3 commits into
mainfrom
work/chat-adapter-model-switching
May 5, 2026
Merged

Support adapter-specific chat model selection#44
jmagar merged 3 commits into
mainfrom
work/chat-adapter-model-switching

Conversation

@jmagar
Copy link
Copy Markdown
Owner

@jmagar jmagar commented May 5, 2026

Summary

  • add adapter-scoped model options and selected model state through chat UI/session data
  • send selected model with prompts and persist/normalize model metadata across ACP session surfaces
  • clear invalid model selections when switching adapters

Tests

  • pnpm exec tsx --test components/chat/chat-shell.test.tsx lib/chat/*.test.ts lib/acp/*.test.ts (38/38 passing)
  • cargo test --manifest-path crates/lab/Cargo.toml acp --lib (85/85 passing before the lab-apis rerun)
  • RUSTC_WRAPPER= cargo test --manifest-path crates/lab-apis/Cargo.toml acp --lib (0 tests after filter, compile passed)
  • pnpm run build (passing)
  • NEXT_PUBLIC_MOCK_DATA=true pnpm run build fails in unrelated /settings/services mock-data prerender path; normal build passes

Agent-browser verification

  • ran NEXT_PUBLIC_MOCK_DATA=true pnpm exec next dev -H 127.0.0.1 -p 3133
  • opened http://127.0.0.1:3133/chat/ with agent-browser 0.26.0
  • verified chat UI loads and exposes agent/model-adjacent controls in the accessibility snapshot
  • limitation: mock provider state keeps prompt controls disabled, so actual model payload behavior is covered by unit tests rather than direct browser send

Notes

  • .env, config.toml, ignored plan files, generated Next files, and node modules were not committed.

Summary by cubic

Adds adapter-scoped chat model selection and applies the chosen model to new prompts and sessions. Selection is validated per provider, persisted, and reflected in the chat header, session list, and prompt events.

  • New Features

    • Model picker in chat input; keyboard accessible and disabled when only one option exists. Current model is shown in the chat header and session sidebar.
    • Provider health/list now exposes models, defaultModelId, and currentModelId. Selection is stored per provider, cleared if invalid when switching adapters, and falls back to run model → provider current/default → first option.
    • Selected model is sent on session create and prompt; server accepts model or model_id (trims blanks), validates against provider options (or passes through when none cached), applies the model in the runtime, and returns modelId/modelName in session summaries and prompt events.
  • Migration

    • SQLite schema v2 adds model_id, model_name, and config_options_json; auto-migrates on startup. Existing sessions continue to work.

Written for commit 27e7f09. Summary will update on new commits.

Summary by CodeRabbit

Release Notes

New Features

  • Users can now select a chat model alongside agent selection in the chat interface
  • Selected model is displayed in the session header and sidebar
  • Model selection is persisted and sent with chat prompts

Copilot AI review requested due to automatic review settings May 5, 2026 11:09
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 5, 2026

📝 Walkthrough

Walkthrough

This PR adds model selection capability to the chat interface and backend. It introduces model metadata types across the stack, extends chat components and session management to track user's model choice per provider, wires model selection through the ACP session lifecycle (creation, prompting, persistence), and updates the API contracts and dispatch layer to accept model parameters during session operations.

Changes

Model Selection System

Layer / File(s) Summary
Type Definitions
apps/gateway-admin/components/chat/types.ts, apps/gateway-admin/lib/acp/types.ts, crates/lab-apis/src/acp/types.rs, crates/lab/src/acp/types.rs
New ACPModelOption type (id, name, description, fixed flag) and extensions to ACPAgent (models, defaultModelId, currentModelId), ACPRun (modelId, modelName), ProviderHealth (models, defaults), BridgeSessionSummary (modelId, modelName), and session result types to carry model metadata.
Normalizer & Adapter Layer
apps/gateway-admin/lib/chat/acp-normalizers.ts, apps/gateway-admin/lib/chat/acp-normalizers.test.ts
Extended ProviderListPayload and RawSessionSummary types; updated normalizeSessionSummary, toRun, and provider normalizers to extract/forward model fields; enhanced sameProviderList to compare model metadata.
Model Resolution Helper
apps/gateway-admin/lib/chat/use-chat-session-controller.ts
Added resolveSelectedModel export to derive the effective model ID from agent models using requested ID, current session, or defaults; extended SendPromptForSelectedProviderOptions and sendPromptForSelectedProvider to accept and send selectedModelId in the request body.
Session Provider & State
apps/gateway-admin/lib/chat/chat-session-provider.tsx
Added selectedModel to context data, selectModel action to update per-provider model choice, per-provider model tracking via selectedModelByProvider, and wired model selection into session creation (passes model ID) and prompt sending.
Chat Input Component
apps/gateway-admin/components/chat/chat-input.tsx
Extended props to accept selectedModel, modelOptions, and onSelectModel; added model picker UI (dropdown button and listbox), managed picker open/close state with useEffect, implemented keyboard navigation (arrows, Enter, Space, Escape, Tab) and option focus management for model selection.
Chat Shell & Layout
apps/gateway-admin/components/chat/chat-shell.tsx, apps/gateway-admin/components/chat/session-sidebar.tsx
Wired selected model from session context into ChatInput; added breadcrumb display of selectedRun.modelName; updated RunRow layout to show model name on a second truncated line when available.
Backend Registry
crates/lab/src/acp/registry.rs
Added provider_models cache to AcpSessionRegistry, new helper resolve_model_selection to validate requested models, updated create_session to resolve and store model metadata, extended prompt_session signature to accept optional model_id and resolve effective model before dispatching to runtime, added test helpers and fixtures for model scenarios.
Backend Runtime
crates/lab/src/acp/runtime.rs
Updated RuntimeHandle::prompt signature to accept model_id, refactored prompt queuing to use structured PromptCommand { prompt, model_id }, added session_model_options helper to extract available models from provider, updated session startup to capture model metadata in RuntimeStarted, and conditionally send SetSessionModelRequest before prompting when model is selected.
API Services & Dispatch
crates/lab/src/api/services/acp.rs, crates/lab/src/dispatch/acp/dispatch.rs
Extended CreateSessionBody and PromptBody to accept model or model_id parameters with camelCase normalization; wired these into session.start and session.prompt parameters; updated dispatch layer to include model metadata in provider.list/provider.get responses and forward model selection to registry.
Persistence Layer
crates/lab/src/dispatch/acp/persistence.rs
Added schema migration to user_version 2 adding model_id, model_name, and config_options_json columns to acp_sessions; updated db_load_sessions to SELECT and deserialize these fields into AcpSessionSummary; updated db_save_session to persist these fields on insert/update.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant ChatUI as Chat UI<br/>(ChatInput)
    participant SessionProvider as Session<br/>Provider
    participant SessionController as Controller<br/>(use-chat-session)
    participant Registry as Registry<br/>(Rust)
    participant Runtime as Runtime<br/>(Rust)
    participant Provider as ACP<br/>Provider

    User->>ChatUI: Select model from dropdown
    ChatUI->>SessionProvider: onSelectModel(modelId)
    SessionProvider->>SessionProvider: selectModel(providerId, modelId)<br/>updates selectedModelByProvider
    SessionProvider->>ChatUI: provides selectedModel via context
    ChatUI->>ChatUI: displays selected model

    User->>ChatUI: Send prompt
    ChatUI->>SessionController: sendPromptForSelectedProvider<br/>with selectedModelId
    SessionController->>Registry: POST /sessions/prompt<br/>{ prompt, model: selectedModelId }
    Registry->>Runtime: prompt(prompt, model_id)
    Runtime->>Runtime: enqueue PromptCommand<br/>{ prompt, model_id }
    Runtime->>Provider: SetSessionModelRequest(model_id)<br/>(if model_id present)
    Provider-->>Runtime: ack
    Runtime->>Provider: send_prompt(prompt)
    Provider-->>Runtime: prompt_started { model_id }
    Runtime-->>Registry: command processed
    Registry-->>SessionController: response
    SessionController-->>ChatUI: prompt sent
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • jmagar/lab#38: Extends model-selection support across chat components and backend types, directly modifying chat session/provider/normalizer/controller files.

Poem

🐰 A model's choice, now made so clear,
Hopping through dropdowns without fear,
From UI to Rust, the selections flow,
Each prompt now knows which way to go!
Hop, select, and away we go! 🎯

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 48.65% 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
Title check ✅ Passed The title 'Support adapter-specific chat model selection' clearly and concisely summarizes the main objective of the changeset: adding the ability for users to select different chat models on a per-adapter basis.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch work/chat-adapter-model-switching

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.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 855f049789

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/lab/src/acp/registry.rs Outdated
Comment on lines +378 to +380
let options = self.provider_model_options(&provider).await;
let (model_id, model_name) =
resolve_model_selection(&provider, input.model_id.as_deref(), &options, started.model_id.as_deref())?;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Accept adapter model before validating against empty cache

session.start now validates model against provider_model_options, but this map is only initialized empty in this change and is never populated on the non-test path, so options is empty here. With an explicit model parameter, resolve_model_selection returns invalid_param even for valid models, which breaks the new model-selection API path for any client that sends model.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in bce147e

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in bce147e

Comment thread crates/lab/src/acp/runtime.rs
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds adapter-scoped chat model selection across the ACP backend and the gateway-admin chat UI, aiming to let the UI choose a model per provider and persist that choice through session summaries. It touches both the Rust ACP bridge (crates/lab) and the Next.js admin chat client (apps/gateway-admin).

Changes:

  • Extend ACP provider/session payloads and persistence with model IDs, model names, and config/model option metadata.
  • Thread model selection through ACP API handlers, dispatch, registry, and runtime prompt/session flows.
  • Add gateway-admin model selection state/UI, display selected model in chat/session surfaces, and expand unit tests.

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
crates/lab/src/dispatch/acp/persistence.rs Adds SQLite schema/migration support for model metadata and config option persistence.
crates/lab/src/dispatch/acp/dispatch.rs Exposes model fields in provider responses and accepts model params for session start/prompt actions.
crates/lab/src/api/services/acp.rs Extends HTTP session create/prompt payloads to carry model selection.
crates/lab/src/acp/types.rs Adds model-related fields to ACP bridge/internal session and provider types.
crates/lab/src/acp/runtime.rs Threads selected model through runtime command handling and prompt lifecycle events.
crates/lab/src/acp/registry.rs Adds provider model tracking and model resolution during session creation/prompt dispatch.
crates/lab-apis/src/acp/types.rs Expands shared ACP API types with model/config option structures.
apps/gateway-admin/lib/chat/use-chat-session-controller.ts Adds selected-model resolution and includes model in prompt requests.
apps/gateway-admin/lib/chat/chat-session-provider.tsx Stores provider-scoped selected model state and wires it into session creation/prompt flows.
apps/gateway-admin/lib/chat/acp-normalizers.ts Normalizes provider/session model metadata from ACP payloads.
apps/gateway-admin/lib/acp/types.ts Extends frontend ACP transport types with model metadata.
apps/gateway-admin/components/chat/types.ts Extends chat-facing agent/run types with model fields.
apps/gateway-admin/components/chat/session-sidebar.tsx Shows the run’s model name in the session list.
apps/gateway-admin/components/chat/chat-shell.tsx Passes selected model data into the chat input and surfaces model name in the header.
apps/gateway-admin/components/chat/chat-shell.test.tsx Adds unit coverage for model selection normalization and prompt payloads.
apps/gateway-admin/components/chat/chat-input.tsx Adds the model picker UI alongside the existing agent picker.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread crates/lab/src/acp/registry.rs Outdated
Comment thread crates/lab/src/acp/runtime.rs
Comment thread crates/lab/src/api/services/acp.rs
Comment thread crates/lab/src/api/services/acp.rs
item.defaultModelId === other.defaultModelId &&
item.currentModelId === other.currentModelId &&
models.length === otherModels.length &&
models.every((model, index) => model.id === otherModels[index]?.id)
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in bce147e

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in bce147e

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/lab/src/acp/runtime.rs (1)

467-475: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

config_options is still dropped on startup.

This always returns Vec::new(), so the new config_options_json column will persist [] for every session. After a restart, the registry has no stored model-option metadata to validate against or to rebuild the picker for restored sessions, which undercuts the new “persist and normalize model metadata” path.

🤖 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 `@crates/lab/src/acp/runtime.rs` around lines 467 - 475, The StartSessionResult
currently sets config_options to Vec::new(), dropping persisted options; replace
this with the persisted data from the started record (use started.config_options
if that field exists, otherwise deserialize started.config_options_json via
serde_json::from_str into the Vec) and propagate that value into
StartSessionResult.config_options; ensure you handle/convert types as needed and
fall back to an empty Vec only on deserialization errors (and optionally log the
error).
🤖 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 `@apps/gateway-admin/lib/chat/use-chat-session-controller.ts`:
- Around line 130-131: The current code collapses candidate selection into one
variable then checks existence, which lets a stale requestedModelId cause
immediate fallback to models[0] and skip later valid fallbacks; change the logic
to sequentially attempt to resolve each id in order (requestedModelId, runModel,
agent?.currentModelId, agent?.defaultModelId) by using models.find(...) for each
non-null id and returning the first found model, and only if none are found
return models[0] or null; update the code paths referencing candidate,
requestedModelId, runModel, agent.currentModelId, agent.defaultModelId, and the
models.find usage accordingly to implement this stepwise lookup.

In `@crates/lab/src/acp/registry.rs`:
- Around line 214-224: Do not synthesize default_model_id or current_model_id
from the first model in provider_models; remove the lines that set
health.default_model_id = provider_models.first().map(|model| model.id.clone())
and health.current_model_id = health.default_model_id.clone(), and only update
health.models = provider_models.clone(). In other words, when iterating over
provider_healths() in the block using self.provider_models.try_read(), populate
health.models but do not overwrite health.default_model_id or
health.current_model_id—leave them None (or their existing cached values) if
they are not already set.

In `@crates/lab/src/api/services/acp.rs`:
- Around line 143-145: User-supplied model strings must be sanitized so an empty
or whitespace-only model does not override model_id; replace uses of
body.model.or(body.model_id) with logic that trims and treats "" as None (e.g.,
compute a sanitized_model from body.model by trimming and treating empty ->
None, then fallback to body.model_id), updating occurrences that reference model
and model_id (e.g., the struct fields model / model_id and call sites using
body.model.or(body.model_id)) so mixed-version clients with model: "" will
correctly fall back to model_id.

---

Outside diff comments:
In `@crates/lab/src/acp/runtime.rs`:
- Around line 467-475: The StartSessionResult currently sets config_options to
Vec::new(), dropping persisted options; replace this with the persisted data
from the started record (use started.config_options if that field exists,
otherwise deserialize started.config_options_json via serde_json::from_str into
the Vec) and propagate that value into StartSessionResult.config_options; ensure
you handle/convert types as needed and fall back to an empty Vec only on
deserialization errors (and optionally log the error).
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: e40fe973-e342-44da-9728-d1da85d786c8

📥 Commits

Reviewing files that changed from the base of the PR and between bce147e and c41c1ec.

📒 Files selected for processing (17)
  • apps/gateway-admin/components/chat/chat-input.tsx
  • apps/gateway-admin/components/chat/chat-shell.test.tsx
  • apps/gateway-admin/components/chat/chat-shell.tsx
  • apps/gateway-admin/components/chat/session-sidebar.tsx
  • apps/gateway-admin/components/chat/types.ts
  • apps/gateway-admin/lib/acp/types.ts
  • apps/gateway-admin/lib/chat/acp-normalizers.test.ts
  • apps/gateway-admin/lib/chat/acp-normalizers.ts
  • apps/gateway-admin/lib/chat/chat-session-provider.tsx
  • apps/gateway-admin/lib/chat/use-chat-session-controller.ts
  • crates/lab-apis/src/acp/types.rs
  • crates/lab/src/acp/registry.rs
  • crates/lab/src/acp/runtime.rs
  • crates/lab/src/acp/types.rs
  • crates/lab/src/api/services/acp.rs
  • crates/lab/src/dispatch/acp/dispatch.rs
  • crates/lab/src/dispatch/acp/persistence.rs

Comment thread apps/gateway-admin/lib/chat/use-chat-session-controller.ts Outdated
Comment thread crates/lab/src/acp/registry.rs
Comment thread crates/lab/src/api/services/acp.rs
@jmagar jmagar merged commit f2bf6e7 into main May 5, 2026
7 of 14 checks passed
@jmagar jmagar deleted the work/chat-adapter-model-switching branch May 7, 2026 13:28
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