Skip to content

copilot: discover and switch between all authorized models - #1203

Closed
xi-db wants to merge 10 commits into
databricks:mainfrom
xi-db:stack/copilot-model-discovery
Closed

xi-db wants to merge 10 commits into
databricks:mainfrom
xi-db:stack/copilot-model-discovery

Conversation

@xi-db

@xi-db xi-db commented Oct 20, 2026 •

Copy link
Copy Markdown
Collaborator

🥞 Stacked PR

Use this link to review incremental changes.


What did you change, and why?

Change: ug copilot now registers every Claude and GPT model the user can use on the workspace and lets them switch between them inside one Copilot session.

  • Per-session registry. Each launch writes a Copilot providers.json (COPILOT_PROVIDERS_CONFIG) to ~/.copilot/ucode-providers-<id>.json (0600), deleted when the session ends. Each session holds a lock on a sibling .lock file, and registries whose lock nobody holds (a killed session) are swept at the next launch and by ug revert. The user's own ~/.copilot/providers.json is never touched.
    • Providers: dbx-anthropic (Anthropic Messages), dbx-responses (Responses, GPT-5+) and dbx-completions (Chat Completions, older GPT). All of them point at the existing loopback auth proxy with the session secret; only providers that have models are written.
    • Claude entries carry Copilot's catalog modelId, so Copilot sends Claude request settings, plus prompt/output limits. Without explicit limits Copilot assumes a 128K prompt for registry models.
    • The launch env sets COPILOT_PROVIDERS_CONFIG, COPILOT_MODEL=<provider>/<id> and COPILOT_OFFLINE=true, and drops inherited COPILOT_PROVIDER* settings. The single-provider env (COPILOT_PROVIDER_TYPE/BASE_URL/WIRE_API/MODEL_ID) and the COPILOT_PROVIDER_WIRE_MODEL override are gone.
  • Discovery.
    • The catalog is classified from the UC model-services listing, which now keeps each model's supported_api_types. When that listing is empty, it falls back to the gateway listings. No extra requests.
    • Names decide the wire: claude-* (including bare databricks-claude-*, GitHub Copilot does not work with Anthropic models #108) -> Anthropic; gpt-N -> Responses for N >= 5, else Completions. Advertised API types only exclude.
    • Excluded, with a reason: Gemini (the gateway rejects Copilot's stream_options), open-source models (not validated with Copilot yet), non-chat models, claude-opus-4-1 (the gateway 400s Copilot's thinking settings).
    • The catalog is saved per workspace. A failed listing keeps the last good one and warns; a listing with nothing usable is an error.
  • Model selection.
    • --model/-m accept a short name, system.ai.* or Copilot's provider/id. They're rewritten to provider/id, because Copilot only matches that form; Copilot has no -m, so it becomes --model.
    • An unknown or unsupported model fails before Copilot starts and lists the available models.
    • The start model is: --model, then the budget recommendation or managed default (now passed through LaunchOptions.launch_model; it was ignored for Copilot before), then the managed list, then the newest Sonnet/Opus/Haiku, then GPT.
  • Managed config. The admin's model_services list for Copilot now reaches state through the existing static-models seam and replaces discovery. Entries Copilot can't serve are skipped with a warning, and the wizard's Copilot families drop Gemini/OSS.
  • Visibility. ug status lists Copilot's catalog and default, and ug doctor reports the model counts.
  • Other changes.
    • Copilot CLI 1.0.91 or newer is required: older versions ignore the registry.
    • ug copilot -p keeps stdout for Copilot by moving ug's banners to stderr.
    • The shared launch_token helper is reused.

Why: Copilot's env-var BYOK supports one provider per session, so /model couldn't reach Databricks models across families, and switching between Claude and GPT meant relaunching. Copilot also only saw the newest model per Claude family plus the Codex catalog, which leaked gpt-oss. Copilot doesn't discover gateway models itself the way Claude Code and Codex do, so ug builds the catalog.

BEFORE:

  • ug copilot pins one model and wire per launch, and /model can't switch to another Databricks model family.
  • Older Claude versions and databricks-claude-* ids don't reach Copilot or take the OpenAI path.
  • A managed Copilot model list and the budget launch model are ignored.

AFTER:

  • /model lists all authorized Claude and GPT models (on dogfood: 14 Claude + 18 GPT) and switches across providers mid-session, carrying tool history.
  • --model accepts friendly names and fails fast with the list.
  • Managed lists and the budget model apply.

How do you know it works?

Testing:

  • Unit:
    • classifier, catalog order and precedence, keep-last-good vs definitive empty, resolver and argv rewrite, launch-model order, Claude limits table and fallback;
    • registry content (pinned provider type/wireApi/baseUrl), file mode and cleanup, sweep of dead sessions, env, version gate;
    • managed static list and unservable warnings, status/doctor rows, -p stdout, the listing's API types;
    • tests/test_agent_copilot.py runs the real proxy with a stubbed Copilot process.
    • The full unit suite passes except 8 tests in TestInstallToolBinary / test_e2e_user_agent, which also fail on main in this environment. just lint is clean.
  • Integration (new tests/integration/test_ug_copilot_models.py, non-blocking Copilot lane):
    • one setup runs a Claude task, then a GPT task selected as dbx-responses/<model>;
    • an older Claude from the full catalog;
    • an unknown model exits 1 before Copilot starts and lists the models;
    • a TUI journey answers folder trust, completes a Claude task, runs /model dbx-responses/<model>, completes a GPT task and /exits.
    • Model evidence comes from Copilot's own session-state/*/events.jsonl (assistant.message.model, session.start.selectedModel, session.model_change.newModel). The existing headless journeys now also assert the answering model and that no registry is left.
  • Live (Copilot 1.0.98, ug from this branch, dogfood):
    • claude-haiku-4-5, dbx-responses/system.ai.gpt-5-4-nano and claude-sonnet-4-5 headless tasks completed with clean stdout;
    • system.ai.gemini-3-flash failed before launch with the list;
    • in the TUI, Haiku answered, /model dbx-responses/system.ai.gpt-5-4-nano printed "Model changed ... for this session", and GPT answered;
    • no registry was left.
    • The new integration journeys haven't run in CI yet: this repo's fork /integration-test uses main's workflow.

Not in this PR: open-source models (next on this stack), Model Provider Service models in the registry, GPT token limits, mixing GitHub-seat models into /model (depends on the seat-based-auth decision).

Stacked on #1191.

This pull request and its description were written by Isaac.

xi-db and others added 7 commits October 20, 2026 09:47
Copilot CLI holds its provider credential for the whole session, so the
Databricks token ug passed at launch expired mid-session (the 30-minute
refresher only rewrote ~/.copilot/ucode.env, which Copilot never reads).
The token was also persisted in that file and its backup, and `ug revert`
restored it.

`ug copilot` now starts a per-session proxy on 127.0.0.1. Copilot gets the
proxy URL and a random session secret; the proxy accepts the secret only on
gateway model routes (/ai-gateway/{anthropic,mlflow/v1,openai/v1}/), swaps
it for a fresh Databricks token (CLI profile or custom OAuth), and adds the
ucode/copilot User-Agent, request tags and managed http_headers. A rejected
token is refreshed and the request retried; when refresh itself fails the
proxy answers with a "Databricks sign-in expired" error in the request's
dialect and logs to ~/.ucode/logs/copilot-proxy.log instead of the TUI.

- Configure and launch write no credential; earlier tokens are scrubbed
  from ucode.env, a token-bearing backup is dropped (also before revert),
  and ug no longer backs up its own file on reconfigure.
- The refresher thread is gone, so a running session no longer resets
  current_workspace or rewrites state.
- Ctrl-C no longer re-signals Copilot (send_signal(SIGINT) raised on
  Windows); ug waits for Copilot to exit and then stops the proxy.
- Inherited COPILOT_PROVIDER_API_KEY(_COMMAND) / _TRANSPORT are dropped so
  they can't bypass the proxy.

Tests: hermetic socket tests for the proxy (secret, path allowlist incl.
encoded traversal, header swap, refresh-and-retry, actionable 401), Copilot
unit tests, the Copilot e2e now runs through the proxy, and a new
non-blocking Copilot integration lane (Linux + Windows) with Claude and GPT
headless journeys that also assert no credential on disk.

Co-authored-by: Isaac <no-reply@databricks.com>
…fused connect

Co-authored-by: Isaac <no-reply@databricks.com>
Co-authored-by: Isaac <no-reply@databricks.com>
… revert

Co-authored-by: Isaac <no-reply@databricks.com>
…oken

Co-authored-by: Isaac <no-reply@databricks.com>
Co-authored-by: Isaac <no-reply@databricks.com>
Co-authored-by: Isaac <no-reply@databricks.com>
@xi-db

xi-db commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator Author

/integration-test d333475

@xi-db

xi-db commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator Author

/user-journey-check

@github-actions

Copy link
Copy Markdown

@github-actions

Copy link
Copy Markdown

….json

Co-authored-by: Isaac <no-reply@databricks.com>
@xi-db

xi-db commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator Author

/integration-test 46fd13d

@xi-db

xi-db commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator Author

/user-journey-check

@github-actions

Copy link
Copy Markdown

@github-actions

Copy link
Copy Markdown

Co-authored-by: Isaac <no-reply@databricks.com>
@xi-db

xi-db commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator Author

/integration-test 5d4bc4a

@xi-db

xi-db commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator Author

/user-journey-check

@github-actions

Copy link
Copy Markdown

@github-actions

Copy link
Copy Markdown

@xi-db
xi-db marked this pull request as ready for review October 21, 2026 11:33
@xi-db
xi-db requested a review from masonc08 October 21, 2026 11:33
Co-authored-by: Isaac <no-reply@databricks.com>
@xi-db

xi-db commented Oct 21, 2026

Copy link
Copy Markdown
Collaborator Author

Moved to #1218 (in-repo branch); readable model names split out to #1219. Closing in favor of them.

@xi-db xi-db closed this Oct 21, 2026
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