Skip to content

Commit bd3a917

Browse files
tombeckenhamclaude
andcommitted
feat(schemas): add OpenRouter provider
Pulls the public OpenAPI 3.1 spec from openrouter.ai/openapi.json and classifies into chat (chat/completions, messages, responses), audio (speech, transcriptions), and video (/videos — including the frame_images / input_references image-conditioning shape). Account management, preset-scoped variants, and rerank drop out of generation. /embeddings is intentionally unclassified: OpenRouter declares its schemas inline rather than via $ref, so it can't be mapped to named exports. Per-model video constraints (supported_durations, resolutions, aspect ratios from GET /api/v1/videos/models) are a candidate follow-up — noted in the provider module. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 4ca7256 commit bd3a917

24 files changed

Lines changed: 133099 additions & 2 deletions

.changeset/initial-ai-schemas.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
Initial release of `@tanstack/ai-schemas` — JSON Schema and Zod schemas for AI provider generation endpoints, generated nightly from upstream OpenAPI specs.
66

7-
Covers OpenAI, Anthropic, Gemini, ElevenLabs, xAI Grok, and FAL. Endpoints are grouped per provider by activity — `chat`, `image`, `video`, `audio` (TTS, transcription, music, sound effects in one group), `embeddings`, `moderation` — behind `@tanstack/ai-schemas/{provider}/{activity}/{json-schema,zod}` subpaths. Platform/admin endpoints are excluded from generation. Architecture ported from fal-ai/fal-js PR #212; extended to every provider that publishes an OpenAPI spec.
7+
Covers OpenAI, Anthropic, Gemini, ElevenLabs, xAI Grok, OpenRouter, and FAL. Endpoints are grouped per provider by activity — `chat`, `image`, `video`, `audio` (TTS, transcription, music, sound effects in one group), `embeddings`, `moderation` — behind `@tanstack/ai-schemas/{provider}/{activity}/{json-schema,zod}` subpaths. Platform/admin endpoints are excluded from generation. Architecture ported from fal-ai/fal-js PR #212; extended to every provider that publishes an OpenAPI spec.
88

99
Sources:
1010

@@ -13,6 +13,7 @@ Sources:
1313
- Gemini: Google Generative Language Discovery doc, converted to OpenAPI in-pipeline
1414
- ElevenLabs: `api.elevenlabs.io/openapi.json`
1515
- xAI Grok: `docs.x.ai/openapi.json`
16+
- OpenRouter: `openrouter.ai/openapi.json`
1617
- FAL: per-model OpenAPI from the FAL models API (requires `FAL_KEY`)
1718

1819
The `.github/workflows/sync-schemas.yml` workflow runs daily and opens a PR when any provider's spec changes. Resolves #619.

docs/advanced/ai-schemas.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ title: '@tanstack/ai-schemas: provider endpoint schemas'
33
id: ai-schemas
44
---
55

6-
`@tanstack/ai-schemas` is a separate package that ships **JSON Schema** and **Zod** schemas for the generation endpoints of every supported provider (OpenAI, Anthropic, Gemini, ElevenLabs, xAI Grok, FAL). The schemas are generated nightly from each provider's official OpenAPI spec, so they track upstream changes automatically.
6+
`@tanstack/ai-schemas` is a separate package that ships **JSON Schema** and **Zod** schemas for the generation endpoints of every supported provider (OpenAI, Anthropic, Gemini, ElevenLabs, xAI Grok, OpenRouter, FAL). The schemas are generated nightly from each provider's official OpenAPI spec, so they track upstream changes automatically.
77

88
It complements the `model-meta.ts` shipped with each provider adapter: where `model-meta` describes coarse facts (context window, modalities, pricing), `ai-schemas` describes the rich per-endpoint constraint surface — allowed video durations, image sizes, voice IDs, prompt-length caps, etc.
99

@@ -121,4 +121,5 @@ Provider sources:
121121
| Gemini | `generativelanguage.googleapis.com/$discovery/rest?version=v1beta` | Google Discovery doc converted to OpenAPI in pipeline. |
122122
| ElevenLabs | `api.elevenlabs.io/openapi.json` | Public. |
123123
| xAI Grok | `docs.x.ai/openapi.json` | Public. |
124+
| OpenRouter | `openrouter.ai/openapi.json` | Public. |
124125
| FAL | `api.fal.ai/v1/models?status=active&expand=openapi-3.0` (per-model) | Needs `FAL_KEY`. Model categories regroup into the shared activities. |

packages/ai-schemas/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ pnpm add zod
2121
| Gemini | `generativelanguage.googleapis.com/$discovery/rest?version=v1beta` | Google Discovery doc converted to OpenAPI in-pipeline. |
2222
| ElevenLabs | `api.elevenlabs.io/openapi.json` | Public. |
2323
| xAI Grok | `docs.x.ai/openapi.json` | Public. |
24+
| OpenRouter | `openrouter.ai/openapi.json` | Public. |
2425
| FAL | `api.fal.ai/v1/models?status=active&expand=openapi-3.0` (per-model OpenAPI) | Needs `FAL_KEY` to fetch. |
2526

2627
Other OpenAI-compatible providers (e.g. Groq) reuse the OpenAI schemas.

packages/ai-schemas/scripts/providers/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { falProvider } from './fal.js'
44
import { geminiProvider } from './gemini.js'
55
import { grokProvider } from './grok.js'
66
import { openaiProvider } from './openai.js'
7+
import { openrouterProvider } from './openrouter.js'
78
import type { ProviderConfig } from '../providers.js'
89

910
export const ALL_PROVIDERS: ReadonlyArray<ProviderConfig> = [
@@ -13,4 +14,5 @@ export const ALL_PROVIDERS: ReadonlyArray<ProviderConfig> = [
1314
grokProvider,
1415
elevenlabsProvider,
1516
falProvider,
17+
openrouterProvider,
1618
]
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/**
2+
* OpenRouter provider — pulls the public OpenAPI 3.1 spec served at
3+
* openrouter.ai/openapi.json. No auth required for the spec itself.
4+
*
5+
* The static spec describes the request/response shape per endpoint
6+
* (including video generation's frame_images / input_references). Per-model
7+
* video constraints (supported_durations, resolutions, aspect ratios) live
8+
* in the separate GET /api/v1/videos/models metadata API — synthesising
9+
* per-model schemas from that is a candidate follow-up, not covered here.
10+
*/
11+
12+
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'
13+
import { join } from 'node:path'
14+
15+
import {
16+
applyTransforms,
17+
mergeOpenAPISpecs,
18+
toActivitySpecs,
19+
type MergedSpec,
20+
} from '../merge-openapi-specs.js'
21+
import type {
22+
Activity,
23+
FetchOptions,
24+
ProviderCategorySpec,
25+
ProviderConfig,
26+
} from '../providers.js'
27+
28+
const OPENROUTER_OPENAPI_URL = 'https://openrouter.ai/openapi.json'
29+
30+
async function fetchOpenRouter({ outDir }: FetchOptions): Promise<void> {
31+
mkdirSync(outDir, { recursive: true })
32+
33+
console.log(`Fetching OpenRouter OpenAPI from ${OPENROUTER_OPENAPI_URL}...`)
34+
const response = await fetch(OPENROUTER_OPENAPI_URL)
35+
if (!response.ok) {
36+
throw new Error(
37+
`OpenRouter fetch failed: ${response.status} ${response.statusText}`,
38+
)
39+
}
40+
// The spec's key-management examples embed realistic-looking
41+
// `sk-or-v1-…` API keys that trip GitHub push protection when the spec is
42+
// committed. Redact them — they only appear in platform endpoints that
43+
// are dropped from generation anyway.
44+
const text = (await response.text()).replace(
45+
/sk-or-v1-[0-9a-f]{16,}/g,
46+
'sk-or-v1-REDACTED',
47+
)
48+
const spec = JSON.parse(text) as object
49+
writeFileSync(
50+
join(outDir, 'openrouter.openapi.json'),
51+
JSON.stringify(spec, null, 2),
52+
)
53+
console.log(' Wrote openrouter.openapi.json')
54+
}
55+
56+
/**
57+
* Path rules — OpenRouter's generation surface spans the OpenAI-compatible
58+
* endpoints (chat/completions, completions-style responses) and the
59+
* Anthropic-compatible /messages. Account management (auth/keys, byok,
60+
* credits, guardrails, workspaces, observability), the preset-scoped
61+
* endpoint variants, and rerank (no core activity) drop out of generation.
62+
*/
63+
function classifyOpenRouter(path: string): Activity | null {
64+
if (
65+
path === '/chat/completions' ||
66+
path === '/messages' ||
67+
path === '/responses'
68+
) {
69+
return 'chat'
70+
}
71+
if (path.startsWith('/audio/')) return 'audio'
72+
if (path === '/videos') return 'video'
73+
// NB: /embeddings is intentionally unclassified — OpenRouter declares its
74+
// request/response schemas inline rather than via $ref, so the endpoint
75+
// can't be mapped to named schema exports. Revisit if upstream refactors.
76+
return null
77+
}
78+
79+
function loadOpenRouter(): Array<ProviderCategorySpec> {
80+
const specDir = new URL('../specs/openrouter/', import.meta.url).pathname
81+
let raw: string
82+
try {
83+
raw = readFileSync(join(specDir, 'openrouter.openapi.json'), 'utf8')
84+
} catch {
85+
return []
86+
}
87+
const spec = JSON.parse(raw) as object
88+
applyTransforms(spec, { providerId: 'openrouter' })
89+
const mergedSpec: MergedSpec = mergeOpenAPISpecs([spec], 'OpenRouter API')
90+
return toActivitySpecs('openrouter', mergedSpec, classifyOpenRouter)
91+
}
92+
93+
export const openrouterProvider: ProviderConfig = {
94+
id: 'openrouter',
95+
namespace: 'OpenRouter',
96+
fetch: fetchOpenRouter,
97+
load: loadOpenRouter,
98+
requiresAuth: false,
99+
}

0 commit comments

Comments
 (0)