feat(kap-server): add provider write endpoints and models.dev/registry import#2110
feat(kap-server): add provider write endpoints and models.dev/registry import#2110liruifengv wants to merge 7 commits into
Conversation
🦋 Changeset detectedLatest commit: f292a5b The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
…oToml
The models TOML transform merged each entry as { ...oldRaw, ...converted },
so a field the new record carried with an explicit undefined — deleted from
'converted' by setDefined — was put right back from the old on-disk raw.
Field-level clears issued through config.replace (e.g. the provider write
routes dropping base_url, display_name, capabilities) only took effect in
memory and resurrected on the next boot. Merge via setDefined onto the old
raw directly, matching providerEntryToToml.
a16bcce to
fe7a4ef
Compare
commit: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a16bccea96
ℹ️ 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".
…y import
Add the provider management write surface plus server-proxied catalogs so
API clients (the desktop app first) can manage providers without editing
config.toml by hand:
- POST/PUT/DELETE /providers: manual create, replace-style edit (new_id
rename with pointer migration, tri-state api_key, form-unknown fields
merged through), and delete with real alias removal.
- GET /providers/{id} additionally reveals the stored api_key for edit
prefill on the loopback+bearer transport; the list stays redacted.
- GET /catalog/providers[{id}]: pruned models.dev directory with import
eligibility resolved server-side (10-min cache, stale fallback, built-in
snapshot, shared in-flight fetch).
- POST /providers:import_catalog and :import_registry (collection actions
next to :refresh): catalog/registry imports with TUI-aligned
remove-then-apply refresh semantics, OAuth-managed guards, and the
registry source blob that scheduled refreshes rediscover.
Write-path consistency: field-level clears assign explicit undefined (the
TOML transforms only drop keys that way), import refreshes swap aliases in
two passes so stale on-disk fields cannot ride along, re-imports keep the
stored credential when api_key is omitted, and multi-step writes serialize
through a process-local chain. Global default_provider/default_model are
never modified by provider writes.
fe7a4ef to
2f4c9c9
Compare
…key collisions on replace - Cache the built-in snapshot on upstream failure, so an offline install no longer pays the full upstream timeout on every catalog call. - Reject a PUT whose rebuilt alias keys collide with an alias owned by another provider (foreign-prefix keys are global), checked before any write so a collision never lands half the edit.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b3147a79b8
ℹ️ 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".
…er TS control-flow
…one is configured A fresh setup has no global default model, so the first provider added through the write endpoints left GET /auth permanently unready and new sessions without a selected model. Seed default_model from the created provider's default (or first) model on POST /providers, :import_catalog and :import_registry when — and only when — no default is configured; an existing pointer is never moved, not even a dangling one.
…gine kap-server's provider write endpoints imported @moonshot-ai/kosong and @moonshot-ai/kimi-code-oauth directly for the models.dev browse/import and custom-registry import. Move that capability behind a new App-scope IModelsDevImportService so the server only talks to agent-core-v2: - app/kosongConfig/modelsDev.ts mirrors the third-party api.json schema and hosts the pure translation (wire inference, endpoint resolution, model pruning, thinking/gateway overrides) ported from kosong's catalog.ts; kosong's own type surface stays limited to built-in vocabulary - modelsDevUpstream.ts owns the directory fetch, 10-minute cache, and built-in snapshot fallback; modelsDevImportService.ts owns the import orchestration (tri-state api_key, two-pass alias swap, OAuth-managed rejection, serialized writes) with coded modelsDev.* errors - kap-server routes shrink to wire mapping (zod schemas + numeric error code mapping) and drop the kosong/oauth dependencies The /api/v1 surface is byte-identical: the apiSurface snapshot and all existing modelCatalog tests pass unchanged.
Related Issue
No linked issue — this is the server half of the desktop app's custom-provider feature (client PR follows in the app repo).
Problem
The kap-server API surface could read providers and models but had no way to write them: no create/edit/delete endpoints, no access to the models.dev directory, and no custom-registry import — so GUI clients (the desktop app) could not offer provider management without hand-editing
config.toml.What changed
Write endpoints (
packages/kap-server/src/routes/modelCatalog.ts):POST /providers— manual create (wire type + credentials + model list), 40921 on id conflict.PUT /providers/{id}— replace-style edit:new_idrename (providers key, aliases,default_providerand a pointingdefault_modelall migrate), tri-stateapi_key(absent = keep,""= clear, otherwise replace), model aliases rebuilt with form-unknown fields (betaApi,reasoningKey,overrides, …) merged through.DELETE /providers/{id}— removes the provider and its aliases (bare 204); OAuth-managed providers are rejected with 40003 everywhere.GET /providers/{id}— additionally reveals the storedapi_keyfor edit prefill (loopback + bearer only; the list route stays redacted).GET /catalog/providers[{id}]— pruned models.dev directory (catalogUpstream.ts): 10-minute in-memory cache with stale fallback, built-in snapshot via tsdown define (same mechanism as the CLI), shared in-flight fetch, and server-resolved import eligibility (wire_type/needs_base_url/rejected).POST /providers:import_catalog/:import_registry— collection actions next to:refresh(find-my-way cannot register a static sibling of the in-segment:actionparameter). Imports reuse the core's own normalization (resolveCatalogImport,catalogProviderModels,fetchCustomRegistry, remove-then-apply), write the registrysourceblob so scheduled refreshes rediscover it, and never move the globaldefault_provider/default_modelpointers.Write-path disk consistency (the subtle part — see also the companion fix):
undefined; import refreshes swap aliases in two passes (drop, then re-add) so stale on-disk fields cannot survive.api_keykeeps the stored credential (registry imports inherit the previoussourcekey for the same URL).Companion fix (first commit):
modelsToTomlmerged{ ...oldRaw, ...converted }, which resurrected any key the new record carried with an explicitundefined— field deletes throughconfig.replacenever reached the disk and came back on restart.Tests: 45 new kap-server endpoint tests (incl. on-disk persistence assertions), one direct
modelsToTomlunit test, apiSurface snapshot updated.pnpm vitest rungreen for kap-server (766) and agent-core-v2 (3972).Checklist