Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/<provider-id>/logo.svg`
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
14 changes: 13 additions & 1 deletion packages/core/src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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 (
Expand All @@ -415,19 +424,22 @@ 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 &&
!isOpenrouter &&
!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"],
},
);
Expand Down
4 changes: 4 additions & 0 deletions packages/sdk/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, Model>
}
Expand Down
25 changes: 24 additions & 1 deletion packages/web/src/render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
});
}
Expand Down Expand Up @@ -532,6 +535,7 @@ function providerPageMetadata(
: undefined;
const description = compactMetadataDescription(
[
provider.description,
`Browse ${plural(models.length, `${provider.name} model`)} on Models.dev.`,
factSentence([
labSummary,
Expand Down Expand Up @@ -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 (
<header>
Expand Down Expand Up @@ -750,7 +762,7 @@ function ProvidersPage(props: { providers: Array<[string, CatalogProvider]> }) {
);

return (
<tr data-search={`${provider.name} ${providerId} ${provider.npm} ${provider.api ?? ""}`}>
<tr data-search={`${provider.name} ${providerId} ${provider.npm} ${provider.api ?? ""} ${provider.description ?? ""}`}>
<td data-sort={provider.name}>
<ProviderLink providerId={providerId} provider={provider} />
</td>
Expand Down Expand Up @@ -876,6 +888,7 @@ function ProviderPage(props: {
<DetailHeader
eyebrow={<a href="/providers">Providers</a>}
title={props.provider.name}
description={props.provider.description}
code={props.providerId}
copyValue={props.providerId}
/>
Expand All @@ -890,6 +903,16 @@ function ProviderPage(props: {
Provider docs
</a>,
],
[
"Website",
props.provider.website ? (
<a href={props.provider.website} target="_blank" rel="noopener noreferrer">
{hostname(props.provider.website)}
</a>
) : (
"-"
),
],
]}
/>
<TableSection title="Models" count={props.models.length} columns={9}>
Expand Down
10 changes: 9 additions & 1 deletion providers/aihubmix/provider.toml
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
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: /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)
# 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"
Loading