Skip to content

feat: Add multi-profile support per provider#21353

Open
Pablo-GNU wants to merge 29 commits intoanomalyco:devfrom
Pablo-GNU:feature/auth-multi-profile
Open

feat: Add multi-profile support per provider#21353
Pablo-GNU wants to merge 29 commits intoanomalyco:devfrom
Pablo-GNU:feature/auth-multi-profile

Conversation

@Pablo-GNU
Copy link
Copy Markdown

@Pablo-GNU Pablo-GNU commented Apr 7, 2026

Issue for this PR

Closes #13038

Type of change

  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Adds multi-profile auth support per provider and fixes several related auth/TUI issues.
Main changes:

  • Support credentials per provider profile (provider:profile) while keeping backward compatibility for legacy bare keys (provider).
  • Allow selecting profile in TUI flow (Ctrl+P -> Switch Model -> Connect provider) before entering API key.
  • Show profile variants in Switch Model and disambiguate Recent/Favorites entries with profile suffixes.
  • Fix logout behavior for default profile so it correctly removes bare keys and legacy :default keys.
  • Ensure runtime model auth uses the resolved profile key and avoid cross-profile leakage through model/runtime caching.
  • Make fallback deterministic for models without explicit profile:
    • use bare provider key if present
    • else use :default if present
    • else use first available profile (sorted)
      Why this works:
  • Auth key resolution is now explicit and consistent across CLI, TUI, session runtime, and provider model resolution.
  • Profile-aware state is propagated end-to-end (model -> prompt/session -> llm runtime -> provider sdk).
  • Cache keys include profile/auth context so one profile cannot accidentally reuse another profile’s auth configuration.

How did you verify your code works?

  • Ran typecheck:
    • HOME=~/DEV/PERSONAL/.home bun typecheck (in packages/opencode) ✅
  • Ran full test suite:
    • HOME=~/DEV/PERSONAL/.home bun test (in packages/opencode) ✅
  • Manual verification in TUI (HOME=~/DEV/PERSONAL/.home bun dev) and CLI (HOME=~/DEV/PERSONAL/.home bun dev -- `):
    • Created and selected multiple profiles for same provider.
    • Verified explicit profile usage (provider:personal/...).
    • Verified non-profile agents (build/plan) follow deterministic fallback.
    • Verified auth logout removes correct profile/default credentials.

Screenshots / recordings

image image image

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

Pablo-GNU added 21 commits April 7, 2026 13:31
- Add composite key storage (providerID:profileName)
- Implement profile resolution: config authProfile > env var > default
- CLI login: prompt profile name before auth method
- CLI logout: show profile list after provider selection
- Add profile validation (max 20 chars, alphanum + hyphen + underscore)
- Add --profile flag to login/logout commands
- Maintain backward compat via virtual alias for bare keys
- Add authProfile field to Provider.options schema
- parseModel extracts authProfile from providerID if contains ':'
- Profile embedded in model string has highest priority
- Precedence: embedded > config authProfile > env var > default
- Auth.get supports direct lookup of provider:profile keys
- Agent.Info model supports optional authProfile field
- currentProviderLabel now shows profile suffix when active
- parsed() now returns profile from model state
- isModelValid accepts authProfile optional field
- model.set includes authProfile when updating
- agent model change effect includes authProfile
- message-v2.ts: add authProfile to User.model schema
- prompt.ts: copy authProfile from model to userMsg.model
- lllm.ts: build auth key with profile when calling Auth.get()
- acp/types.ts: add authProfile to ACPSessionState.model
- acp/agent.ts: propagate authProfile in setModel call

This fixes the bug where embedded profiles (e.g., 'minimax-coding-plan:personal')
were lost when creating user messages and never reached Auth.get().
…ikeys

providers.ts:
- Use lastIndexOf + slice instead of split(':') for profile parsing

provider.ts:
- Extract base provider ID from composite auth keys (e.g., 'minimax-coding-plan:personal' → 'minimax-coding-plan')
- This fixes the issue where providers weren't recognized when using profile-based auth keys
When looking up auth credentials:
- If profile key exists (e.g., 'minimax-coding-plan:personal'), use it
- If profile key doesn't exist but base provider does (e.g., 'minimax-coding-plan'), use that as fallback

This fixes the issue where build mode (no profile) wouldn't work when only 'minimax-coding-plan:personal' existed in auth.json.

Also removes normalizeKey usage from Auth.get - the fallback logic is now inline and clearer.
When no profile is provided, normalizeKey now returns the base providerID
without appending ':default'. This ensures backward compatibility - existing
auth.json entries with bare provider keys work correctly.
Propagate authProfile from user messages into resolved models, inject profile-specific
api keys into runtime model options in LLM streaming, and include authProfile in
Provider.getLanguage cache keys to prevent cross-agent credential leakage.
SessionPrompt.PromptInput now accepts model.authProfile so TUI/API payloads
keep profile selection instead of dropping it during validation. This allows
profile-specific auth keys to be used end-to-end.

debug(auth): increase key preview length in auth log
Prevents stale language model reuse when credentials change at runtime and
avoids accidental key leakage across profile switches in the same process.
Use mergeDeep(provider.options, model.options) in resolveSDK so per-request model options
(including profile-specific apiKey injected at runtime) override provider-level defaults.
This prevents provider.key from overriding the auth key selected for the active profile.
Expand Switch Model entries by configured auth profiles per provider and preserve authProfile in recent/favorites selection keys so profile-specific model picks remain distinct.
…tate

Ensure profile variants appear even when direct auth profile discovery is unavailable by combining profile sources from auth keys, configured agents, current model, recents, and favorites.
- Parse auth keys by last colon to avoid malformed profile extraction
- Treat bare provider keys as default profile in logout flow
- Remove both bare and :default keys when logging out default profile
- Validate --profile against discovered profiles

fix(tui): only list switch-model profiles present in auth state
In Ctrl+P -> Switch Model -> Connect a provider, prompt for profile name
before auth method handling and store API credentials under provider:profile
(default maps to bare provider key).
…missing

For models without explicit authProfile:
- use bare provider key when present
- use :default when present
- otherwise choose first available provider profile (sorted)

Also reflect inferred profile in TUI status when model has no explicit profile.
Treat bare provider keys as default during logout, remove both bare and legacy :default keys, and validate profile selection against discovered profiles. Update auth tests to match bare-key storage semantics and relax memory leak test runtime to avoid CI/local timeout flakes.
@github-actions github-actions bot added needs:compliance This means the issue will auto-close after 2 hours. needs:title labels Apr 7, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 7, 2026

Hey! Your PR title Feature/auth multi profile doesn't follow conventional commit format.

Please update it to start with one of:

  • feat: or feat(scope): new feature
  • fix: or fix(scope): bug fix
  • docs: or docs(scope): documentation changes
  • chore: or chore(scope): maintenance tasks
  • refactor: or refactor(scope): code refactoring
  • test: or test(scope): adding or updating tests

Where scope is the package name (e.g., app, desktop, opencode).

See CONTRIBUTING.md for details.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 7, 2026

The following comment was made by an LLM, it may be inaccurate:

Based on my search, I found two potentially related PRs that may overlap with the current PR #21353:

  1. PR feat: multi-account profile support per provider #13218 - feat: multi-account profile support per provider

  2. PR feat(auth): multi-account support with auto-rotation #13378 - feat(auth): multi-account support with auto-rotation

These PRs likely address similar or overlapping functionality around supporting multiple authentication profiles per provider. You may want to check their current state (open/closed/merged) and review whether PR #21353 incorporates changes from these earlier attempts or if there's duplication of effort.

@Pablo-GNU Pablo-GNU changed the title Feature/auth multi profile Add multi-profile support per provider Apr 7, 2026
@Pablo-GNU Pablo-GNU changed the title Add multi-profile support per provider feat: Add multi-profile support per provider Apr 7, 2026
@github-actions github-actions bot removed needs:title needs:compliance This means the issue will auto-close after 2 hours. labels Apr 7, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 7, 2026

Thanks for updating your PR! It now meets our contributing guidelines. 👍

@github-actions github-actions bot added the needs:compliance This means the issue will auto-close after 2 hours. label Apr 7, 2026
@github-actions github-actions bot removed the needs:compliance This means the issue will auto-close after 2 hours. label Apr 7, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 7, 2026

Thanks for updating your PR! It now meets our contributing guidelines. 👍

@Pablo-GNU Pablo-GNU requested a review from adamdotdevin as a code owner April 7, 2026 19:17
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.

[FEATURE]: Add support for provider selection

1 participant