feat(coding-agent): credential broker with pooled selection and background refresh#194
feat(coding-agent): credential broker with pooled selection and background refresh#194islee23520 wants to merge 36 commits into
Conversation
Add 18 API-key providers (alibaba-coding-plan, cursor, deepinfra, firepass, fugu, gitlab-duo, glm-zcode, kagi, kilo, kimi-code, litellm, lm-studio, minimax-code(-cn), moonshot, nanogpt, ollama(-cloud), openai-codex-device, parallel, perplexity, qianfan, qwen-portal, synthetic, tavily, venice, vllm, zenmux) plus OAuth flows for cursor, gitlab-duo, glm-zcode, google-antigravity, google-gemini-cli, kilo, kimi-code, openai-codex-device, perplexity, xai, and the remaining Gajae provider-ID gaps for full /login parity. The coding-agent provider wiring (model-resolver defaults, provider-display-names, auth-providers oauth-only set) travels here because model-resolver is exhaustive over the ai KnownProvider union. Source of truth for keys remains env-api-keys.ts; parity is enforced by test/api-key-provider-parity.test.ts.
…ackground refresh Add a loopback credential broker that stores OAuth/API-key credentials in a SQLite vault and leases them to authorized gateway clients: - auth-multi-account / credential-selection / in-memory-credential-vault: multi-account credential contracts, pooled selection, and an in-memory vault. - auth-broker + wire-contract: broker service with CAS selection leases, redacting wire contract, and a remote store for clients. - auth-broker-cli + auth-broker-server: `senpi auth-broker` CLI (serve, token, login, logout, import, backup, restore, migrate) over a loopback HTTP server. - auth-broker-refresher: background OAuth refresh loop (5 min skew / 60 s cadence) that renews expiring tokens and disables definitive failures. - model-registry / sdk: wire pooled credential selection and outcome reporting into the agent loop; index exports the credential surface; main registers the `auth-broker` command. Stacked on the provider-parity branch (depends on its KnownProvider union).
Declare the API-key providers added for /login parity (alibaba-coding-plan, deepinfra, firepass, fugu, kagi, litellm, lm-studio, nanogpt, ollama, ollama-cloud, parallel, qianfan, qwen-portal, synthetic, tavily, venice, vllm, zenmux) across the three synchronized environment surfaces: .devcontainer/devcontainer.json secrets, scripts/devenv-setup.mjs PROVIDER_KEYS, and .agents/skills/senpi-qa/references/env-vars.md. Source of truth remains packages/ai/src/env-api-keys.ts.
…ause, migration writes
…ause, migration writes - Harden auth-broker-refresher with improved credential refresh logic - Add restore lease support and remote-store session handling - Add refresher-start and remote-store-session test coverage - Update auth-broker-wire-contract with additional contract checks
Resolve changes.md conflict by keeping both the credential broker section and the upstream compaction/extension sections.
- Remove kagi, parallel, and tavily providers (search-only, no chat) - Fix GLM ZCode OAuth to pin endpoints to z.ai - Add CRLF-delimited SSE parsing for provider streams - Add GitLab Duo and Perplexity OAuth test coverage - Update model-registry and model-resolver for removed providers
Resolve changes.md conflicts in packages/ai and packages/coding-agent by keeping both the provider parity sections and the upstream sections.
Resolve provider-union conflicts with main Radius support while keeping parity providers. Drop residual kagi/tavily/parallel env-surface wiring.
Cascade provider-parity + Radius onto the broker branch. Keep getRequestAuth and pooled credential selection. Drop residual kagi/tavily/parallel providers.
|
@code-yeongyu This PR's CI is blocked before any jobs start: the fork workflow run concluded Could you open the run below and choose Approve and run workflows? https://github.com/code-yeongyu/senpi/actions/runs/29494565503 No branch-protection or workflow change is requested; this only approves the existing run for this PR head SHA. |
code-yeongyu
left a comment
There was a problem hiding this comment.
Review verdict: NEEDS-CHANGES
The broker core (SQLite vault schema, parameter-bound SQL, loopback allowlist, 0600/0700 perms, redacting wire contract) is a good base, but the increment has correctness gaps in exactly the paths the broker exists for: pooled OAuth, refresh-vs-relogin races, and provider-specific credentials. The auth-storage/model-registry/sdk wiring also predates main's model-runtime refactor (9993c9690) and must be redesigned against CredentialStore/ModelRuntime.
Findings
| Pri | Location | Finding |
|---|---|---|
| P1 | packages/coding-agent/src/core/auth-broker.ts:433 |
CAS-guard refresher disables against the loaded snapshot — The sweep loads a credential, awaits its refresh, and then disables by credential_id alone on a definitive failure. Because the identity-conflict upsert preserves the existing credential_id, a user re-login that rotates material while the old refresh is in flight is still matched and the fresh credential is disabled; the catch only helps if the row disappeared. |
| P1 | packages/coding-agent/src/core/auth-storage.ts:627 |
Resolve pooled OAuth through the provider auth adapter — selectionFromLease treats every OAuth access token as the provider API key. For google-gemini-cli, the provider adapter serializes { token, projectId } as the API key, and providers may also require request-specific headers, so a pooled OAuth selection bypasses required auth transformation and produces failing requests. |
| P1 | packages/coding-agent/src/cli/auth-broker-cli.ts:335 |
Preserve provider-specific OAuth fields in the vault — The broker login narrows OAuthCredentials to access, expiry, and refresh token, discarding provider-specific fields. Google login returns projectId and its refresh implementation requires that field, while the CLIProxy import also accepts project_id but does not retain it; login/import therefore succeeds but the next refresh cannot be performed and request auth cannot be reconstructed. |
| P1 | packages/coding-agent/src/core/auth-storage.ts:392 |
Count pooled credentials as configured auth — setCredentialVault can make a pool the only available credential source, but hasAuth still checks only runtime overrides, auth.json, and environment variables. ModelRegistry.getAvailable() and initial-model selection route through this method, so a pool-only setup is reported as having no usable models unless the user forces an explicit model, even though getApiKeyAndHeaders can select the credential. |
| P2 | packages/coding-agent/src/cli/auth-broker-cli.ts:249 |
Normalize login-only provider aliases before storing pools — Broker login stores the selected provider ID directly in the credential pool. Logging in through the supported openai-codex-device alias therefore creates an openai-codex-device pool, while request-side selection normalizes the model provider to openai-codex and never finds it. |
| P1 | packages/coding-agent/src/core/auth-broker.ts:84 |
Redact imported disable causes before metadata exposure — upsertCredential writes record.disabled.cause verbatim, whereas redaction is applied only by the explicit disable method. Restore/import accepts caller-controlled disabled-state causes and metadataSnapshot returns them, so a backup or CLIProxy record containing a token in its cause bypasses redactDisableCause and exposes it to metadata-read clients. |
| P2 | packages/coding-agent/src/core/auth-broker.ts:132 |
Expire and prune persisted selection leases — Every selection inserts a lease row, but consume and outcome reporting only update it; there is no issued time, expiry, or deletion. Normal broker traffic therefore grows the SQLite database by one permanent row per model request, and an arbitrarily old consumed lease can submit its first unauthorized or rate_limited outcome later, at which point cooldown is calculated from the current time. |
| P1 | packages/coding-agent/src/core/model-registry.ts:680 |
Retain custom Radius registration from the parent PR — This increment removes the parent PR's oauth: "radius" schema and registration path, then treats every no-model provider as an ordinary override. Existing custom Radius gateway configurations consequently stop registering their OAuth provider and dynamic model catalog, regressing users who do not enable the broker. |
Merge path
- Re-port the wire contract, vault, refresher, and server core onto current main; redesign the agent-loop integration against
ModelRuntimeinstead of the replaced registry surfaces. - Fix the four P1 credential-correctness bugs first (CAS disable race, pooled OAuth via provider
getRequestAuth/getApiKey, provider-specific fields likeprojectIdsurviving the vault round-trip,hasAuthcounting pools). - Note the vault DB/WAL files rely on directory perms only — consider explicit 0600 on the SQLite files too.
(Reviewed with the PR refs fetched locally against origin/main; line numbers refer to the PR head.)
… P1s, drop GLM ZCode Re-port OAuth flows from utils/oauth to auth/oauth with bundledLoaders, route Cursor through Connect/protobuf ChatService, fix GitLab PAT exchange and gpt-5.1 chat-completions routing, delist Perplexity session OAuth from /login, exclude Google CCA providers from API-key login, remove unofficial GLM ZCode login, keep browser smoke Node-free, and update stale kimi-coding live-test model ids.
…ndings CAS-guard OAuth disable against updatedAt snapshots, preserve OAuth extras (projectId) through vault round-trips, resolve pooled OAuth via provider getRequestAuth/getApiKey, count pools in hasAuth, redact disabled.cause on upsert/import, prune expired leases, restore models.json oauth:"radius" registration, and normalize broker login provider aliases.
Review follow-up (P1/P2)Addressed the CHANGES_REQUESTED findings on Fixes
EvidenceResidual / out of scope this commit
|
Bring PR code-yeongyu#193 onto current main auth/ModelRuntime layout while preserving provider OAuth parity review fixes (Cursor Connect, GitLab, Perplexity delist, Google OAuth-only, GLM ZCode removed) and regenerating model catalogs.
… main rebase Rebase the broker branch onto the conflict-resolved code-yeongyu#193/main auth stack, re-port multi-account vault hooks onto CredentialStore AuthStorage, and keep broker review P1/P2 fixes (CAS disable, extras, lease prune, getRequestAuth).
|
| GitGuardian id | GitGuardian status | Secret | Commit | Filename | |
|---|---|---|---|---|---|
| 34886147 | Triggered | Generic High Entropy Secret | a7648c9 | packages/ai/scripts/generate-models.ts | View secret |
🛠 Guidelines to remediate hardcoded secrets
- Understand the implications of revoking this secret by investigating where it is used in your code.
- Replace and store your secret safely. Learn here the best practices.
- Revoke and rotate this secret.
- If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.
To avoid such incidents in the future consider
- following these best practices for managing and storing secrets including API keys and other credentials
- install secret detection on pre-commit to catch secret before it leaves your machine and ease remediation.
🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.
…in rebase Bring the gateway branch onto the conflict-resolved code-yeongyu#193/code-yeongyu#194 stack and keep auth-gateway review P1/P2 fixes (broker URL validation, CORS preflight order, verifier evidence, 502 mapping, qualified models).
Conflict resolution (stack)Merged conflict-resolved
|
…er-parity PR base is upstream main (not the fork). Merge upstream main and keep provider/OAuth parity review fixes while accepting upstream model catalogs.
Drop the PR's xAI browser OAuth in favor of main's device-code flow, restore OAuth-only API-key login exclusions for Google CCA providers, and keep the provider/OAuth parity work on current main.
Keep main's device-code xAI flow (drop PR browser discovery), add a thin legacy OAuthProviderInterface adapter for the compat registry, and exclude google-gemini-cli / google-antigravity from API-key /login.
Integrate main's ModelRuntime/video/bash abort changes with the credential broker branch. Keep both change logs (main video/truncation + broker/OAuth).
CAS-disable now matches credential_id + updatedAt + material so a concurrent re-login cannot be disabled by a stale refresh. Preserve CLIProxy/Gajae projectId extras through import, count pools in hasAuth/getAuthStatus, and use optional chaining for credentialVault (biome useOptionalChain).
Babysit update (post-review)Integrated current Integration
Review P1 fixes (this push + prior
|
builtinProviders listed googleProvider() twice, so Models.setProvider collapsed to 63 unique providers while the array length was 64 and providers.test failed. Matches main's single registration.
- Remove double googleProvider() so builtinModels length matches unique IDs - Replace browser/discovery xAI OAuth with main's device-code flow plus compat xaiOAuthProvider adapter for the legacy registry
Evidence-based follow-up (CI green path)Review P1 verification
CI / integration this push
Tests run locally
Full monorepo CLI integration tests that need workspace package dist links can fail in bare worktrees; CI is the authority for those. Not merging until green CI + re-review. |
Expose minimax-code, minimax-code-cn, moonshot, and opencode-zen as API-key login targets via display-name eligibility.
- Add display names so minimax-code/moonshot/opencode-zen appear in /login - Exclude oauth-only providers from API-key login (codex-device, google CCA) - Store openai-codex-device credentials under canonical openai-codex key - Align gajae-login-provider-parity tests with async list() API
Evidence cycle 2Same Gajae login + codex-device storage alias fixes as #195 (shared stack surface), so CI does not hit the same login-parity failures when Check and test reaches those tests. Local: Not merging until green CI + re-review. |
…render Long absolute worktree paths wrap at render width 120, so compact collapsed text is no longer a single line. Flatten whitespace before asserting the compact resource label.
…render Long absolute worktree paths wrap at render width 120, so compact collapsed text is no longer a single line. Flatten whitespace before asserting the compact resource label. Also normalize multi-account suite formatting for biome.
Build harness under real timers and enable fake timers only for the idle-unload countdown so Vitest no longer flakes with 30s hangs on advanceTimersByTimeAsync/runOnlyPendingTimersAsync in CI.
Build harness under real timers and enable fake timers only for the idle-unload countdown so Vitest no longer flakes with 30s hangs on advanceTimersByTimeAsync/runOnlyPendingTimersAsync in CI.
CI fix: app-server idle-unload timer flakeRoot cause
Those tests called Fix (head
|
kimi-code and kimi-coding are the same product at api.kimi.com/coding with the same KIMI_API_KEY. Upstream kimi-coding is the maintained entry (k3 video input, adaptive thinking), so drop the PR-only kimi-code OAuth provider and its catalog so /login shows exactly one Kimi entry. Adds a regression asserting a single kimi-* provider and KIMI_API_KEY resolution.
…parity # Conflicts: # packages/ai/src/providers/data/openrouter.json
…feat/auth-credential-broker # Conflicts: # package-lock.json # packages/ai/src/changes.md # packages/coding-agent/src/core/auth-providers.ts # packages/coding-agent/src/core/changes.md
…parity # Conflicts: # packages/ai/test/abort.test.ts # packages/ai/test/context-overflow.test.ts # packages/ai/test/cross-provider-handoff.test.ts # packages/ai/test/empty.test.ts # packages/ai/test/image-tool-result.test.ts # packages/ai/test/stream.test.ts # packages/ai/test/tokens.test.ts # packages/ai/test/tool-call-without-result.test.ts # packages/ai/test/total-tokens.test.ts # packages/ai/test/unicode-surrogate.test.ts
…feat/auth-credential-broker
Summary
Loopback credential broker that stores OAuth/API-key credentials in a SQLite vault and leases them to authorized gateway clients:
auth-multi-account/credential-selection/in-memory-credential-vault: multi-account credential contracts, pooled selection, in-memory vault.auth-broker+auth-broker-wire-contract: broker service with CAS selection leases, redacting wire contract, remote store.auth-broker-cli+auth-broker-server:senpi auth-brokerCLI (serve, token, login, logout, import, backup, restore, migrate) over loopback HTTP.auth-broker-refresher: background OAuth refresh loop (5 min skew / 60 s cadence) — renews expiring tokens, disables definitive failures.model-registry/sdk: pooled credential selection + outcome reporting wired into the agent loop;indexexports the credential surface;mainregisters the command.Stack
Stacked on the provider-parity PR (depends on its
KnownProviderunion). This branch includes that PR's commit; merge after it, then rebase to drop it.Evidence
npm run checkgreen.Summary by cubic
Adds a local credential broker that stores OAuth/API keys, leases them with pooled selection, and auto-refreshes for the agent and gateway. Finalizes login parity with Cursor Connect, Google CCA, and clearer provider login rules; updates dev env secrets for new API‑key providers.
New Features
senpi auth-broker(serve, token, login, logout, import, backup, restore, migrate).model-registry/sdk; command registered inmain.google-gemini-cli/Antigravity) with SSE and project discovery.ollama/ollama-cloud,lm-studio,litellm,deepinfra,moonshot,qwen-portal,perplexity,venice,vllm, and others; removes search‑only providers..devcontainersecrets and docs updated for new provider keys.Bug Fixes
credential_id+updatedAt+ material; preserve OAuth extras (e.g.,projectId); prune expired leases; count pools inhasAuth/getAuthStatus; optional‑chaincredentialVault.getRequestAuth/getApiKey; CRLF‑delimited SSE parsing; GitLab PAT exchange + direct‑access headers; route GitLab GPT‑5.1 via chat‑completions; delist Perplexity session OAuth from/login; restoreoauth: "radius"inmodels.json.xaiOAuthProvider); drop duplicate Google provider registration; keep Google CCA providers OAuth‑only for/login.minimax-code/moonshot/opencode-zen; exclude OAuth‑only providers from API‑key login (openai-codex-device, Google CCA); storeopenai-codex-devicecreds under theopenai-codexkey; unify Kimi login tokimi-coding.Written for commit ed7407a. Summary will update on new commits.