Commit 860736e
MLuqmanBR
feat(providers): custom providers as first-class platform objects (tashfeenahmed#230)
Redesign custom providers from a per-model {baseUrl, modelId, apiKey} blob into
first-class platform objects that work like the built-in ones. The operator adds
a provider once (slug + display name + base URL), then adds as many models as
they like under it. Multiple API keys for the same custom provider now round-
robin across a single shared base URL.
Server:
- server/src/db/index.ts — new custom_providers table (id, slug UNIQUE,
display_name, base_url, created_at) + V23 migration that re-points any
existing 'custom' rows to a synthetic 'legacy-custom' provider row, so no
data is lost on upgrade.
- shared/types.ts — Platform no longer includes 'custom'. New
CustomProvider/CustomProviderCreate/CustomProviderUpdate/CustomModel* types.
Model.platform and ApiKey.platform are relaxed to string so they can hold
any custom slug.
- server/src/providers/index.ts — add buildProviderFor(platformSlug): returns
the registered singleton for built-ins, or constructs an OpenAICompatProvider
bound to the custom_providers row's base_url for custom slugs. The old
resolveProvider(platform, baseUrl) overload is gone.
- server/src/routes/custom.ts — new, 8 endpoints:
GET /api/custom-providers
POST /api/custom-providers
PATCH /api/custom-providers/:slug
DELETE /api/custom-providers/:slug (cascades to models, api_keys, fallback_config)
GET /api/custom-providers/:slug/models
POST /api/custom-providers/:slug/models
PATCH /api/custom-models/:id
DELETE /api/custom-models/:id
Slug regex ^[a-z0-9][a-z0-9-]{1,30}[a-z0-9]$ (2-32 chars, no leading/trailing
dash). Built-in slugs (google/groq/...) are deny-listed. PATCH on
custom_providers keeps api_keys.base_url in lockstep.
- server/src/routes/keys.ts — drop POST /api/keys/custom. POST /api/keys now
validates platform via buildProviderFor and stores base_url on the key row
for custom slugs. PATCH /api/keys/platform/:platform now accepts custom
slugs (verified against custom_providers).
- server/src/services/router.ts — uses buildProviderFor(entry.platform).
Drop the 'custom && key_id != null' branch; the models.key_id column is
now a denormalized legacy column that the router doesn't consult.
- server/src/services/health.ts — uses buildProviderFor(row.platform).
- server/src/app.ts — mount customRouter behind a path-aware requireAuth
guard so /api/ping and /api/auth/* stay public. The guard regex-scopes
requireAuth to /api/custom-providers/* and /api/custom-models/* only.
Tests:
- server/src/__tests__/routes/custom-providers.test.ts — 16 tests across
provider CRUD, model CRUD, cascade deletes, slug-collision rejection,
buildProviderFor integration.
- server/src/__tests__/routes/proxy-empty-completion.test.ts,
server/src/__tests__/routes/responses-tool-args-repair.test.ts — add
buildProviderFor: () => fakeProvider to the vi.mock factory for the new
provider-lookup function.
Client:
- client/src/pages/KeysPage.tsx — full rewrite. New components:
AddPlatformModal, EditPlatformModal, PlatformTile, PlatformsSection,
CustomModelsSection. Custom provider tiles get Edit/Remove; the
'Add New Platform' tile is always last in the provider list.
Docs:
- README.md — update the custom-provider blurb and add a 'Custom platforms
and models' subsection covering slug rules, modal fields, model form
essential-vs-advanced toggle, and cascade-on-delete behavior.
Behavior change worth highlighting: the 'model belongs to one specific key'
binding (the models.key_id column) is intentionally dropped. Any enabled key
for the custom provider can now serve any model on it.1 parent d42380b commit 860736e
14 files changed
Lines changed: 1477 additions & 570 deletions
File tree
- client/src/pages
- server/src
- __tests__/routes
- db
- providers
- routes
- services
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
69 | 69 | | |
70 | 70 | | |
71 | 71 | | |
72 | | - | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
73 | 87 | | |
74 | 88 | | |
75 | 89 | | |
| |||
83 | 97 | | |
84 | 98 | | |
85 | 99 | | |
86 | | - | |
| 100 | + | |
87 | 101 | | |
88 | 102 | | |
89 | 103 | | |
| |||
0 commit comments