feat(auth): split model readiness from sign-in state in /api/v1/auth - #3293
Conversation
GET /api/v1/auth now reports models_ready (the default model resolves
against the configured catalog, providerless and env-injected models
included) instead of the compound ready flag, and no longer carries
default_model — config values are served by /config alone. The v1
summary schema follows.
OAuth managed-model refreshes now heal a lost default model: the
refresh snapshot includes defaultModel, so an unchanged catalog with
a missing default still lands the write-back branch and re-selects
one. The refresh also rebases onto a fresh config read after the
remote fetch, so a model or thinking change made during the fetch is
no longer overwritten. The shared discovery refresh path (scheduler,
POST /providers/{id}:refresh) heals the default the same way.
Config changes are now published to WS clients on every write path:
a debounced+trailing publisher bridges IConfigService section changes
to ConfigChanged with camelCase changedFields and a full config
projection, and the broadcaster forwards event.config.changed and
event.model_catalog.changed (both previously published but never
delivered). All three event types are registered in the event unions,
so session_event parsing and AsyncAPI describe them.
BREAKING CHANGE: GET /api/v1/auth drops the ready and default_model
fields in favor of models_ready; event.config.changed's changedFields
is now camelCase domain names instead of the raw snake_case request
keys (v1 summary schema follows).
|
commit: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c620856e0e
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
GET /api/v1/sessions hardcoded agent_config.model to '' and the v2 projection had no model field at all, so clients could only learn a session's model via the post-select /status read — which races the WS replay and often never lands. SessionFacts now carries the live session's model (same source as the snapshot route), toWireSession emits it, and the v2 activity domain gains a nullable model field.
The submit gate called ensureReady() with no override, so it only ever validated config.default_model: a session with a bound model (or a prompt carrying one) was rejected with 40113 whenever default_model was missing or dangling. Pass the effective model (request model, then the agent profile's bound model, falling back to default_model inside ensureReady) on both the prompt submit and btw routes.
resolveModelForReady stopped at the flat baseUrl fallback, so a model that omits provider/providerId and relies on the configured defaultProvider resolved at runtime (ModelCatalog.resolveProviderContext falls back to it) while /api/v1/auth reported models_ready:false and the send gate rejected the prompt. Mirror the runtime order (providerId -> provider -> defaultProvider -> flat baseUrl) and pass the configured default provider from both readiness callers.
toConfigResponse only redacted the providers section, so a model's inline apiKey/oauth rode GET /config verbatim and, via the new event.config.changed publisher, every WS connection plus the persistent event journal. Project the models section the same way: strip credential fields and report has_api_key.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: dab3ac2c23
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
…hecks The readiness phase learned the defaultProvider fallback, but the credential phase right after still derived the provider only from the model's explicit fields: a model omitting provider/providerId passed readiness yet missed the default provider's apiKey/OAuth material and prompts failed with auth.token_missing. Mirror the same provider chain (providerId -> provider -> defaultProvider) when resolving credentials.
… prompt gate The gate validated the session's current model even for a prompt that switches profile without a model — but bind falls back to defaultModel in that case, so a stale session model drew a misleading 40113 before bind could run. Gate on bind's selection order instead: the request's explicit model, then the default on a profile switch, then the session's bound model.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1185efbd57
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
The earlier redaction covered providers and models, but toConfigResponse still passed the services section through verbatim: inline or env-injected apiKey, oauth references, and credential-bearing customHeaders rode GET /config and, via the event.config.changed publisher, every WS connection plus the persistent event journal. Project services the same way: strip apiKey/oauth into has_api_key and report only the header names as custom_header_keys (the MCP envKeys/headerKeys convention).
The config.changed broadcaster returned the zod-parsed config, which strips domains absent from configResponseSchema (mcp, identity, model_catalog, image, tools, token_counting): changedFields named them while the advertised full snapshot no longer matched GET /api/v1/config. Make the response projection passthrough (defineRoute validates only requests, so REST responses are unaffected).
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0abb0de6d1
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
…lookups resolveModelForReady trimmed the model id before the models-table lookup while ModelCatalog and ensureReady use the configured string as an exact record key: a whitespace-padded default_model was reported ready and then crashed the submit gate with an internal error instead of 40113, and a legitimate key containing spaces was reported dangling. Trim only rejects blank values now; the lookup always uses the raw key.
…ction The shared configResponseSchema stripped domains it does not enumerate (mcp, identity, model_catalog, image, tools, token_counting, subagent, secondary_model), so event.config.changed parsed through agentEventSchema named them in changedFields while omitting their values. Make the shared projection passthrough like the kap-server-local one.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5eeb378dc1
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
…hecks The defaultProvider fallback trimmed the configured value before the providers-table lookup while ProviderService and ModelCatalog use the configured string verbatim: a whitespace-padded default_provider could build successfully yet report not-ready (40113), or report ready for a provider runtime resolution cannot find. Trim only rejects blank values; the lookup uses the raw key.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d5dc506e8b
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Rebuild the bundled web UI against this branch's /auth contract (models_ready, no ready/default_model): the previous bundle still read the old fields and stayed in the not-ready flow against this server. code-app: 000d2594ff3e95b553be326126bab3f939b62944
This reverts commit 9400a24.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8af9566425
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8af9566425
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
A provider refresh snapshots the config before the remote catalog fetch; when the user selects a default model while the fetch is in flight, the stale snapshot's empty default made an otherwise unchanged catalog enter the write path and the self-heal persisted the generated default over the user's newer selection. Each branch now re-reads and rebases the default/thinking selection after its fetch, before cloning, comparing, or writing.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4a5c2e9506
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Related Issue
N/A — internal change (server half of the sign-in/model-readiness rework; client half ships in MoonshotAI/kimi-code-app).
Problem
The
/api/v1/authreadyflag conflated "providers configured + default model set + managed account not revoked" into one boolean that clients used as a sign-in gate, so a signed-in user without a default model was told to sign in. Related defects: OAuth refreshes never healed a lost default model (the catalog-unchanged branch compared only model aliases); a refresh could overwrite a concurrent user model/thinking choice made during its own remote fetch; andevent.config.changed/event.model_catalog.changedwere published but never reached WS clients (no broadcaster branch — dead letters).What changed
/authcontract: reportsmodels_ready(sharedresolveModelForReadyaligned with the real model-construction requirements — dangling alias, missing provider, providerless flat models, env-injected models,maxContextSize/protocol resolvability), computed from oneconfig.getAll()snapshot;readyanddefault_modelare gone. v1 summary schema follows; v1/v2ensureReady()internals untouched.providerModelSnapshotincludesdefaultModelon both the OAuthService path and the shared discovery/scheduler/:refreshpath, so a lost default is rewritten on the next refresh or login. The refresh also rebases onto a fresh config read after the remote fetch — user-last-write-wins for default model and thinking.IConfigService.onDidSectionChange→ oneConfigChangedper flush carrying camelCasechangedFieldsand a full projection; route-level manual publish removed; publisher closes beforeapp.close()(timer/pending/subscription disposed).event.config.changedandevent.model_catalog.changedglobally; all three (plusevent.config.warning) registered in the kap-server and canonical protocol event unions — verified in generated AsyncAPI.Breaking changes
GET /api/v1/auth:readyanddefault_modelremoved,models_readyadded. The only production consumer is the code-app client, which ships the matching change (link below); CLI/TUI use the in-process SDK facade and are unaffected.event.config.changedchangedFields: camelCase domain names instead of raw snake_case request keys (scanned: no in-repo consumers read the field).Test plan
/authreflects externalconfig.tomledits in real time (incl. dangling-default detection); WS clients receive config events for POST /config, external edits, and OAuth refresh writes, with camelCase changedFields.Checklist
gen-changesetsskill — deferred intentionally; changeset to be added before release.gen-docsskill, or this PR needs no doc update (server-api reference updated en/zh).