Skip to main content

Provider Descriptor Migration Ledger

Inventory taken at origin/release @ 2cefa3ae4115f817f75a415b6bc70fc3ecaed2d3. TypeSafe was added afterwards (#1761) and is included below; the 25-entry count matched origin/release @ f536fd091.

mistral, huggingface, deepseek, laya and xor have since migrated to the JSON catalog and been removed from HAND_DESCRIPTORS (#1781; laya in the PR that added src/lib/providers/catalog/laya.json; xor in the PR that added src/lib/providers/catalog/xor.json) — see Migrated below — and perplexity-decider and cloudflare-clef (decision-only, like TypeSafe) were added as hand descriptors afterwards. Net effect: the 25-entry baseline minus the 3 baseline entries that migrated (mistral, huggingface, deepseek), plus the 2 added afterwards that stay hand-written (perplexity-decider, cloudflare-clef), leaves 24 entries currently in HAND_DESCRIPTORS (src/lib/factories/providerDescriptors.ts). laya and xor are not part of the 25: they were added to HAND_DESCRIPTORS after the baseline and then migrated, so they cancel out and neither is subtracted from it. The category counts below (Shared adapter needed / Must remain class / Must remain core class) cover exactly those 24; the 5 migrated providers are recorded separately as done and no longer count toward "what remains."

This ledger records, per provider, why it is (or isn't) a JSON-catalog migration candidate, so "add a provider" work doesn't re-litigate the same analysis per PR.

Every verdict allows one thing regardless of class: the static descriptor metadata (name, aliases, default model, credential env var names, setup URL) can always move into a class-backed JSON record. "Must remain class" means the execution — the inference loop (generate/stream/decide, per the provider's inferenceKinds), auth, media pipelines — cannot be reduced to declarative catalog data; it does not mean the provider is exempt from descriptor consolidation.

Migrated (5)​

The first three ran the same class-removal path this ledger recommended below: resolve the JSON/descriptor data conflicts, then delete the hand descriptor and hand-written subclass so the provider is fully catalog-derived.

ProviderNote
mistralMoved onto catalog/mistral.json. defaultModel now derives from the new optional models.registryDefaultModel field when present (only Mistral sets it), and the setupUrl/key-format/timeout/priority conflicts this ledger flagged were resolved in the JSON rather than carried forward.
huggingfaceMoved onto catalog/huggingface.json, which gained wire.apiKeyFallbackEnvVars (HF_TOKEN alongside HUGGINGFACE_API_KEY) and capabilities.tools: "model-dependent" — the two schema gaps this ledger flagged below before the extension landed.
deepseekMoved onto catalog/deepseek.json, using the new quirks.responseFormatDowngrade: "json-schema-to-json-object" field (DeepSeek 400s on json_schema) instead of an executable hook — the named-quirk approach this ledger recommended. It also sets quirks.replayReasoningContent: true.
layaMoved onto catalog/laya.json, the first decide entry. Its wire (endpoint, the auto model rule, routing.model, the request-id header, error rules and body shapes) is declarative data that ConfiguredDecisionProvider reads; the LayaProvider class was deleted. It keeps a model manifest, built from the entry, so neurolink models still lists it.
xorMoved onto catalog/xor.json, the second decide entry. Its wire (the {baseURL}/v1/systemone endpoint with no built-in base URL, the /v1 and trailing-slash normalization, the model in the body, the request-id headers, error rules and body shapes) is declarative data that ConfiguredDecisionProvider reads, and its image and video input is a decide.media block (the images and video body keys) beside decisionLimits.media; the XorProvider class was deleted. It keeps a model manifest, built from the entry, so neurolink models still lists it.

Shared adapter needed (9)​

Three adapter families, not nine one-off migrations:

Local-runtime adapter (ollama, lm-studio, llamacpp) — one OpenAI-compatible local-runtime adapter with optional auth, model discovery/probe, actionable transport errors, timeout policy, optional embeddings. Vendor-specific pull/diagnostic guidance becomes adapter data.

Embedding-only adapter (voyage, jina, cohere) — one embedding-only provider adapter parameterized by path, request shape, batch size, response-extraction/index policy, unsupported-surface messages. jina extends it with an optional rerank operation profile (don't force reranking into the boolean capability shape). cohere composes this with a generic OpenAI chat adapter (it has both /compatibility/v1 chat and native /v2/embed); preserve its batch limit and body-field quirks in the embedding profile.

Image-generation adapter (stability, ideogram, recraft) — one image-generation adapter family: explicit multipart vs JSON request profile, base64-vs-URL response profile, custom auth-header support, model-path mapping as data. SSRF and bounded-read policy remain shared mandatory behavior, not per-vendor opt-outs.

Must remain class (14)​

ProviderWhy
bedrockAWS SigV4/Converse protocol, custom loop, not OpenAI-compatible. Tier 4.
openaiChat portion is catalog-shaped, but embeddings, image generation, and OpenAI-specific telemetry/errors are executable behavior.
vertexTwo native protocols (Google GenAI + Anthropic-on-Vertex) and multiple modality pipelines behind one provider identity.
anthropicNative Messages API auth/message/tool semantics are executable protocol behavior.
azureCustom routing, auth, and per-deployment capability can't be represented without turning JSON into executable hook data.
sagemakerTier 4 — signed SDK invocation and arbitrary endpoint response parsing are explicitly outside the catalog/adapter boundary.
nvidia-nimConditional body builder and response-aware retry are real hooks; a one-off JSON mini-language would violate the catalog boundary.
litellmRuntime discovery, telemetry, and SSE/model-access parsing are substantial; also an aggregator — schedule after direct-vendor work regardless.
openrouterDynamic per-routed-model capabilities and router headers are runtime behavior; aggregator, so it doesn't count toward the first-class target anyway.
replicateAsync prediction lifecycle (not chat completions) plus cross-media handler integration is custom; descriptor JSON must not imply media-handler registrations belong to the LLM descriptor.
google-aiNative Gemini + media + embedding pipelines aren't declaratively OpenAI-compatible — but flagged as the best early proof-of-concept for class-backed JSON metadata, since its auth is simpler than the cloud-IAM providers.
typesafeDecision-only (inferenceKinds: ["decide"], no generate/stream): a dual-transport (direct + Vercel AI Gateway) wire protocol with its own question vocabulary (noul for boolean) and token-budget quirks — nothing here is OpenAI-compatible chat shape, so it isn't catalog-JSON candidate material by a different route than the generate/stream providers above.
perplexity-deciderDecision-only (inferenceKinds: ["decide"]), like TypeSafe, Laya and XOR. Wire-compatible in its noul / choice / score vocabulary, but with its own error envelope, limits and image input (as image_url parts inside state), and distinct from the catalog's perplexity Sonar provider although it shares that provider's PERPLEXITY_API_KEY — so it has no OpenAI-compatible chat shape to hang a catalog entry on.
cloudflare-clefDecision-only (inferenceKinds: ["decide"]), like TypeSafe, Laya, XOR and Perplexity. Same noul / choice / score vocabulary, but with Cloudflare's own response envelope, limits and a separate images array; distinct from the catalog's cloudflare text provider though it shares its CLOUDFLARE_API_KEY and CLOUDFLARE_ACCOUNT_ID — so no OpenAI-compatible chat shape to hang a catalog entry on.

Must remain core class (1)​

ProviderWhy
openai-compatibleThis is the generic protocol surface itself, not a direct vendor. It should consume shared descriptor/config primitives but must never become a fixed-vendor JSON row — collapsing it into the catalog would conflate "the adapter" with "an entry in the adapter's catalog."

Suggested execution order​

The former steps 1–2 below (mistral; huggingface, deepseek) are complete, and laya and xor have moved too — see Migrated above. What remains:

  1. Three shared adapters (local-runtime, embedding-only, image-generation) — each unlocks 3 providers at once; build once, migrate three.
  2. google-ai descriptor-only JSON metadata — proof of concept for moving static descriptor data out of a "must remain class" provider without touching its execution.
  3. Direct-vendor "must remain class" providers (bedrock, openai, vertex, anthropic, azure, sagemaker, nvidia-nim) get descriptor-only JSON metadata migrations, execution untouched — lower priority, cosmetic consolidation only.
  4. Aggregators (litellm, openrouter), the core adapter (openai-compatible), and the decision-only providers (typesafe, perplexity-decider, cloudflare-clef) are excluded from the first-class-count migration priority entirely — the first three add no direct-vendor coverage however they're implemented, and typesafe/perplexity-decider/cloudflare-clef are decide-only providers with no generate/stream surface to migrate at all.