From f671c8f402b077786d483fbe2a4789b03922e975 Mon Sep 17 00:00:00 2001 From: chenxue Date: Sun, 20 Sep 2026 17:20:49 +0800 Subject: [PATCH 1/2] feat(aihubmix): add provider description, website, and API endpoint The AIHubMix provider entry carried only name/npm/env/doc, so its page showed an empty API column and no link back to the service itself. - Provider schema gains optional `description` and `website` fields, and `@aihubmix/ai-sdk-provider` joins the packages allowed to declare `api` (its native SDK takes a baseURL and the gateway is OpenAI-compatible). - Provider pages render the description in the header plus a Website fact; both fields feed search tokens and the page meta description. - providers/aihubmix/provider.toml: api = https://aihubmix.com/v1 (verified live against GET /v1/models, HTTP 200), website = https://aihubmix.com, and a description taken from the service's own wording on aihubmix.com ("The Unified Gateway for AI Models" / "Access leading AI models through one unified, OpenAI-compatible API."). Co-Authored-By: Claude Opus 5 --- AGENTS.md | 6 ++++++ README.md | 2 ++ packages/core/src/schema.ts | 14 +++++++++++++- packages/sdk/src/types.ts | 4 ++++ packages/web/src/render.tsx | 25 ++++++++++++++++++++++++- providers/aihubmix/provider.toml | 6 +++++- 6 files changed, 54 insertions(+), 3 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 3aaf0ed9edf..819b4ff4521 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -88,8 +88,14 @@ npm = "@ai-sdk/openai-compatible" # or the native AI SDK package env = ["EXAMPLE_API_KEY"] api = "https://api.example.com/v1" # required for openai-compatible doc = "https://example.com/docs" +description = "One-line summary of what this host serves" # optional +website = "https://example.com" # optional ``` +`description` and `website` are optional; both surface on the provider page and in +`api.json`. Keep `doc` pointed at the documentation/model list, and use `website` for +the provider's main site. + ### Logo (blocker for new providers) - Path: `providers//logo.svg` diff --git a/README.md b/README.md index f26ba19ab83..f9bef4e3a36 100644 --- a/README.md +++ b/README.md @@ -132,6 +132,8 @@ If the provider isn't already in `providers/`: npm = "@ai-sdk/provider" # AI SDK Package name env = ["PROVIDER_API_KEY"] # Environment Variable keys used for auth doc = "https://example.com/docs/models" # Link to provider's documentation + description = "One line on what this provider serves" # Optional + website = "https://example.com" # Optional, link to the provider's main site ``` If the provider doesn’t publish an npm package but exposes an OpenAI-compatible endpoint, set the npm field accordingly and include the base URL: diff --git a/packages/core/src/schema.ts b/packages/core/src/schema.ts index c6c8b8f38b2..bda898e894e 100644 --- a/packages/core/src/schema.ts +++ b/packages/core/src/schema.ts @@ -383,12 +383,20 @@ export const Provider = z npm: z.string().min(1, "Provider npm module cannot be empty"), api: z.string().optional(), name: z.string().min(1, "Provider name cannot be empty"), + description: z + .string() + .min(1, "Provider description cannot be empty") + .optional(), doc: z .string() .min( 1, "Please provide a link to the provider documentation where models are listed", ), + website: z + .string() + .min(1, "Provider website cannot be empty") + .optional(), models: z.record(Model), }) .strict() @@ -400,6 +408,7 @@ export const Provider = z const isMergeGateway = data.npm === "merge-gateway-ai-sdk-provider"; const isAnthropic = data.npm === "@ai-sdk/anthropic"; const isKiro = data.npm === "kiro-acp-ai-provider"; + const isAIHubMix = data.npm === "@aihubmix/ai-sdk-provider"; const hasApi = data.api !== undefined; return ( @@ -415,6 +424,8 @@ export const Provider = z isOpenAI || // kiro: api optional (always allowed) isKiro || + // AIHubMix: native provider fronting an OpenAI-compatible gateway; api optional + isAIHubMix || // all others: must NOT have api (!isOpenAI && !isOpenAIcompatible && @@ -422,12 +433,13 @@ export const Provider = z !isMergeGateway && !isAnthropic && !isKiro && + !isAIHubMix && !hasApi) ); }, { message: - "'api' is required for openai-compatible, openrouter, and Merge Gateway; optional for anthropic, openai, and kiro; forbidden otherwise", + "'api' is required for openai-compatible, openrouter, and Merge Gateway; optional for anthropic, openai, kiro, and aihubmix; forbidden otherwise", path: ["api"], }, ); diff --git a/packages/sdk/src/types.ts b/packages/sdk/src/types.ts index 92999e2dea7..545033c3688 100644 --- a/packages/sdk/src/types.ts +++ b/packages/sdk/src/types.ts @@ -257,8 +257,12 @@ export interface Provider { api?: string /** Human-readable provider name. */ name: string + /** Short summary of what this provider serves. */ + description?: string /** URL of the provider's model documentation. */ doc: string + /** URL of the provider's main website. */ + website?: string /** Models offered by this provider, keyed by provider-scoped model ID. */ models: Record } diff --git a/packages/web/src/render.tsx b/packages/web/src/render.tsx index 93c73c6e66e..3dfcd64acd7 100644 --- a/packages/web/src/render.tsx +++ b/packages/web/src/render.tsx @@ -357,12 +357,15 @@ function buildSearchItems( api: provider.api, releaseDate: providerLastReleased, updated: providerLastUpdated, + description: provider.description, tokens: [ provider.name, providerId, provider.npm, provider.api, provider.doc, + provider.website, + provider.description, ].filter((token): token is string => Boolean(token)), }); } @@ -532,6 +535,7 @@ function providerPageMetadata( : undefined; const description = compactMetadataDescription( [ + provider.description, `Browse ${plural(models.length, `${provider.name} model`)} on Models.dev.`, factSentence([ labSummary, @@ -613,6 +617,14 @@ function plural(count: number, singular: string, pluralForm = `${singular}s`) { return `${count} ${count === 1 ? singular : pluralForm}`; } +function hostname(url: string) { + try { + return new URL(url).hostname.replace(/^www\./, ""); + } catch { + return url; + } +} + function Header(props: { active: ActiveSection }) { return (
@@ -750,7 +762,7 @@ function ProvidersPage(props: { providers: Array<[string, CatalogProvider]> }) { ); return ( - + @@ -876,6 +888,7 @@ function ProviderPage(props: { Providers} title={props.provider.name} + description={props.provider.description} code={props.providerId} copyValue={props.providerId} /> @@ -890,6 +903,16 @@ function ProviderPage(props: { Provider docs , ], + [ + "Website", + props.provider.website ? ( + + {hostname(props.provider.website)} + + ) : ( + "-" + ), + ], ]} /> diff --git a/providers/aihubmix/provider.toml b/providers/aihubmix/provider.toml index 171a1996ec5..5d18ead20c8 100644 --- a/providers/aihubmix/provider.toml +++ b/providers/aihubmix/provider.toml @@ -1,9 +1,13 @@ name = "AIHubMix" +description = "The unified gateway for AI models: access leading AI models through one unified, OpenAI-compatible API." npm = "@aihubmix/ai-sdk-provider" +# OpenAI-compatible base; the same host also serves Anthropic Messages at /v1/messages +# and Gemini native at /gemini/v1beta. https://docs.aihubmix.com/cn/api/unified-inference (accessed 2026-09-20) +api = "https://aihubmix.com/v1" # Raw Chat: $.reasoning_effort = "none"|"minimal"|"low"|"medium"|"high"|"xhigh"; aliases are $.reasoning.effort and integer $.reasoning.max_tokens. "none" disables models that permit it. https://docs.aihubmix.com/cn/api/unified-inference (accessed 2026-06-25) # Raw Responses: $.reasoning.effort carries effort; this endpoint has no reasoning-token budget field. https://docs.aihubmix.com/cn/api-reference/openai-compatible/create-a-model-response (accessed 2026-06-25) # Raw Messages: $.thinking.type = "enabled"|"disabled"|"adaptive"; enabled uses $.thinking.budget_tokens >= 1024, and $.output_config.effort = "low"|"medium"|"high"|"xhigh"|"max" subject to model support. https://docs.aihubmix.com/cn/api-reference/anthropic-compatible/create-a-message (accessed 2026-06-25) # Raw Gemini native: $.generationConfig.thinkingConfig uses integer thinkingBudget (-1 dynamic; 0 off where supported) or string thinkingLevel; model bounds differ below. https://docs.aihubmix.com/cn/api-reference/google-vertex-ai-compatible/generate-content (accessed 2026-06-25) env = ["AIHUBMIX_API_KEY"] doc = "https://docs.aihubmix.com" - +website = "https://aihubmix.com" From 96ae0998dcc8a09cac35b768932569bd6c45fd9e Mon Sep 17 00:00:00 2001 From: chenxue Date: Sun, 20 Sep 2026 18:13:11 +0800 Subject: [PATCH 2/2] docs(aihubmix): distinguish the OpenAI base from the catalog API Review flagged that /v1 (this api field) and /api/v1 (cited by qwen3.8-max.toml and claude-fable-5-1.toml for pricing) look like disagreeing spellings of the same base. They are two different APIs: - GET /v1/models returns OpenAI shape ({id, object: "model", owned_by}) and POST /v1/chat/completions returns 200, so /v1 is the base clients use. - GET /api/v1/models returns AIHubMix's own catalog ({model_id, model_name, developer_id, success}), and POST /api/v1/chat/completions is 404 -- no inference routes hang off it. Record that distinction next to the field so the citations stop reading as a contradiction. No data change. Co-Authored-By: Claude Opus 5 --- providers/aihubmix/provider.toml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/providers/aihubmix/provider.toml b/providers/aihubmix/provider.toml index 5d18ead20c8..13ea3e9fdf8 100644 --- a/providers/aihubmix/provider.toml +++ b/providers/aihubmix/provider.toml @@ -1,8 +1,12 @@ name = "AIHubMix" description = "The unified gateway for AI models: access leading AI models through one unified, OpenAI-compatible API." npm = "@aihubmix/ai-sdk-provider" -# OpenAI-compatible base; the same host also serves Anthropic Messages at /v1/messages -# and Gemini native at /gemini/v1beta. https://docs.aihubmix.com/cn/api/unified-inference (accessed 2026-09-20) +# OpenAI-compatible base: /v1/chat/completions and /v1/models both answer here (verified +# 2026-09-20). The same host also serves Anthropic Messages at /v1/messages and Gemini +# native at /gemini/v1beta. https://docs.aihubmix.com/cn/api/unified-inference (accessed 2026-09-20) +# Not the same as https://aihubmix.com/api/v1/models, cited by some model TOMLs for pricing: +# that is AIHubMix's own catalog API (model_id/model_name/success fields, not OpenAI shape) +# and serves no inference routes -- /api/v1/chat/completions is a 404. api = "https://aihubmix.com/v1" # Raw Chat: $.reasoning_effort = "none"|"minimal"|"low"|"medium"|"high"|"xhigh"; aliases are $.reasoning.effort and integer $.reasoning.max_tokens. "none" disables models that permit it. https://docs.aihubmix.com/cn/api/unified-inference (accessed 2026-06-25) # Raw Responses: $.reasoning.effort carries effort; this endpoint has no reasoning-token budget field. https://docs.aihubmix.com/cn/api-reference/openai-compatible/create-a-model-response (accessed 2026-06-25)