Skip to content
Draft
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
17 changes: 17 additions & 0 deletions .changeset/initial-ai-schemas.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
'@tanstack/ai-schemas': minor
---

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

Covers OpenAI, Anthropic, Gemini, ElevenLabs, and FAL. Architecture ported from fal-ai/fal-js PR #212; extended to every provider that publishes an OpenAPI spec.

Sources:

- OpenAI: `github.com/openai/openai-openapi`
- Anthropic: `docs.anthropic.com/openapi.json`
- Gemini: Google Generative Language Discovery doc, converted to OpenAPI in-pipeline
- ElevenLabs: `api.elevenlabs.io/openapi.json`
- FAL: per-model OpenAPI from the FAL models API (requires `FAL_KEY`)

The `.github/workflows/sync-schemas.yml` workflow runs daily and opens a PR when any provider's spec changes. Resolves #619.
95 changes: 95 additions & 0 deletions .github/workflows/sync-schemas.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
name: Sync Provider Schemas

on:
schedule:
# 05:00 UTC daily — one hour before sync-models.yml so the two automated
# PRs don't contend on the changeset / branch push at the same minute.
- cron: '0 5 * * *'
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: write
pull-requests: write

jobs:
sync:
name: Sync Schemas
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
persist-credentials: true

- name: Setup Tools
uses: TanStack/config/.github/setup@e4b48f16568324f76f467aa4c2aac2f05db632c3 # main

- name: Fetch provider OpenAPI specs
run: pnpm --filter @tanstack/ai-schemas fetch-schemas
env:
FAL_KEY: ${{ secrets.FAL_KEY }}

- name: Generate JSON Schemas + Zod
run: pnpm --filter @tanstack/ai-schemas generate-schemas

- name: Generate endpoint maps and barrels
run: pnpm --filter @tanstack/ai-schemas generate-endpoint-maps

- name: Check for package changes
id: changes
run: |
if git diff --quiet -- packages/typescript/ai-schemas/; then
echo "changed=false" >> $GITHUB_OUTPUT
else
echo "changed=true" >> $GITHUB_OUTPUT
fi

- name: Write changeset and commit
if: steps.changes.outputs.changed == 'true'
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
mkdir -p .changeset
cat > .changeset/automated-sync-schemas.md <<'EOF'
---
"@tanstack/ai-schemas": patch
---

Nightly sync of provider OpenAPI schemas.
EOF
git add packages/typescript/ai-schemas/ .changeset/
git commit -m "chore(schemas): sync provider OpenAPI schemas"
git push --force origin HEAD:automated/sync-schemas

- name: Create or update PR
if: steps.changes.outputs.changed == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
shell: bash
run: |
BRANCH="automated/sync-schemas"
EXISTING_PR=$(gh pr list --head "$BRANCH" --base main --json number --jq '.[0].number' 2>/dev/null || true)
if [ -z "$EXISTING_PR" ] || [ "$EXISTING_PR" = "null" ]; then
BODY=$(cat <<'PRBODY'
Automated nightly sync of provider OpenAPI schemas.

- Fetches upstream specs (OpenAI, Anthropic, Gemini, ElevenLabs, FAL).
- Runs `@hey-api/openapi-ts` to regenerate JSON Schemas + Zod.
- Bundles each schema's `$defs` closure and regenerates endpoint maps.
- Patch changeset for `@tanstack/ai-schemas`.

Providers with missing secrets are skipped; the diff reflects only the
providers whose specs the workflow could fetch.
PRBODY
)
gh pr create \
--title "chore(schemas): sync provider OpenAPI schemas" \
--body "$BODY" \
--base main \
--head "$BRANCH"
fi
108 changes: 108 additions & 0 deletions docs/advanced/ai-schemas.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
---
title: '@tanstack/ai-schemas: provider endpoint schemas'
id: ai-schemas
---

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

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.

## When to reach for it

- **Runtime validation before submitting a request** — surface bad inputs client-side instead of paying a round-trip and waiting on a vague upstream error.
- **Discovering allowed values** — populate dropdowns or pick lists with the exact enum a model accepts.
- **Feeding an LLM tool API** — each JSON Schema is self-contained (its `$ref` closure is bundled under `$defs`), so it can be passed straight to OpenAI / Anthropic / Gemini tool-use APIs without extra resolution.
- **Optimising prompts across models** — the schemas expose every model's constraints in a comparable shape, which is the foundation for prompt-portability helpers.

## Install

```bash
pnpm add @tanstack/ai-schemas
# Optional: only required if you import from `@tanstack/ai-schemas/{provider}/zod`
pnpm add zod
```

## Subpath imports

Provider-first — pick the provider, then `json-schema` or `zod`:

```ts
// JSON Schemas (no `zod` peer required).
import { geminiEndpointSchemaMap } from '@tanstack/ai-schemas/gemini/json-schema'

// Zod (requires `zod ^4`).
import { openaiEndpointZodMap } from '@tanstack/ai-schemas/openai/zod'

// FAL splits by category — the category is part of the subpath.
import { videoEndpointZodMap } from '@tanstack/ai-schemas/fal-video/zod'
import { imageEndpointSchemaMap } from '@tanstack/ai-schemas/fal-image/json-schema'
```

There is **no aggregator barrel**. `import … from '@tanstack/ai-schemas/gemini/json-schema'` only ships Gemini's JSON Schemas — no other provider's bytes leak into the consumer's bundle.

## Validate a video-generation request

```ts
import { videoEndpointZodMap } from '@tanstack/ai-schemas/fal-video/zod'

const result = videoEndpointZodMap[
'fal-ai/kling-video/o3/pro/text-to-video'
].input.safeParse({
prompt: 'A mecha lands on the ground to save the city, in anime style',
duration: '8',
aspect_ratio: '9:16',
})

if (!result.success) console.error(result.error.issues)
```

## Discover what a model supports

```ts
import { KlingVideoO3ProTextToVideoInputSchema } from '@tanstack/ai-schemas/fal-video/json-schema'

KlingVideoO3ProTextToVideoInputSchema.properties.duration.enum
// ['3', '4', …, '15']

KlingVideoO3ProTextToVideoInputSchema.properties.aspect_ratio.enum
// ['16:9', '9:16', '1:1']
```

## OpenAI structured-outputs strict mode

```ts
import { toOpenAIStrict } from '@tanstack/ai-schemas/openai-strict'
import { Veo3InputSchema } from '@tanstack/ai-schemas/fal-video/json-schema'

await openai.chat.completions.create({
model: 'gpt-5',
messages: [...],
response_format: {
type: 'json_schema',
json_schema: {
name: 'veo3_input',
schema: toOpenAIStrict(Veo3InputSchema),
strict: true,
},
},
})
```

## How it stays current

The `.github/workflows/sync-schemas.yml` workflow runs daily and:

1. Fetches upstream OpenAPI specs for every provider.
2. Re-runs `@hey-api/openapi-ts` to regenerate JSON Schemas + Zod.
3. Bundles each schema's `$ref` closure, regenerates endpoint maps.
4. If anything changed: bumps the package patch version, writes a changeset, opens an automated PR.

Provider sources:

| Provider | Source | Notes |
| ---------- | ------------------------------------------------------------------------------- | ---------------------------------------------------- |
| OpenAI | `github.com/openai/openai-openapi` | Public, no API key required. |
| Anthropic | Stainless-generated OpenAPI (resolved via `anthropic-sdk-typescript/.stats.yml`) | Public. |
| Gemini | `generativelanguage.googleapis.com/$discovery/rest?version=v1beta` | Google Discovery doc converted to OpenAPI in pipeline. |
| ElevenLabs | `api.elevenlabs.io/openapi.json` | Public. |
| FAL | `api.fal.ai/v1/models?status=active&expand=openapi-3.0` (per-model) | Needs `FAL_KEY`. Per-category split (`fal-image`, `fal-video`, etc.). |
4 changes: 4 additions & 0 deletions docs/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,10 @@
{
"label": "Extend Adapter",
"to": "advanced/extend-adapter"
},
{
"label": "Provider Endpoint Schemas",
"to": "advanced/ai-schemas"
}
]
},
Expand Down
8 changes: 8 additions & 0 deletions knip.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@
},
"packages/typescript/ai-vue-ui": {
"ignore": ["src/use-chat-context.ts"]
},
"packages/typescript/ai-schemas": {
"ignore": [
"src/providers/**/*.gen.ts",
"scripts/**",
"openapi-ts.config.ts"
],
"ignoreDependencies": ["zod", "vite"]
}
}
}
105 changes: 105 additions & 0 deletions packages/typescript/ai-schemas/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# @tanstack/ai-schemas

Runtime schemas for AI provider endpoints. Ships **JSON Schema** definitions for tooling plus **Zod** schemas for runtime validation — no hand-maintained TypeScript types. Derive types from Zod with `z.infer<typeof someSchema>`.

Schemas are generated nightly from each provider's official OpenAPI spec (or equivalent), so the package tracks upstream changes automatically.

## Install

```bash
pnpm add @tanstack/ai-schemas
# Zod is an optional peer; only required if you import from `@tanstack/ai-schemas/{provider}/zod`.
pnpm add zod
```

## Providers covered

| Provider | Source | Notes |
| ---------- | --------------------------------------------------------------------------- | ------------------------------------------------------ |
| OpenAI | `github.com/openai/openai-openapi` (raw `openapi.yaml`) | Public, no API key required. |
| Anthropic | Official OpenAPI from `anthropic-sdk-typescript` repo | Public. |
| Gemini | `generativelanguage.googleapis.com/$discovery/rest?version=v1beta` | Google Discovery doc converted to OpenAPI in-pipeline. |
| ElevenLabs | `api.elevenlabs.io/openapi.json` | Public. |
| FAL | `api.fal.ai/v1/models?status=active&expand=openapi-3.0` (per-model OpenAPI) | Needs `FAL_KEY` to fetch. Per-category split. |

OpenAI-compatible providers (Groq, xAI/Grok) reuse the OpenAI schemas.

## Entry points

ES modules only. Provider-first subpaths — pick the provider, then the format:

```ts
// Per-provider JSON Schemas (no `zod` peer required).
import { openaiEndpointSchemaMap } from '@tanstack/ai-schemas/openai/json-schema'

// Per-provider Zod (requires `zod ^4`).
import { openaiEndpointZodMap } from '@tanstack/ai-schemas/openai/zod'

// OpenAI structured-outputs strict-mode helper.
import { toOpenAIStrict } from '@tanstack/ai-schemas/openai-strict'
```

For multi-category providers (currently just FAL), the category is part of the subpath:

```ts
import { videoEndpointZodMap } from '@tanstack/ai-schemas/fal-video/zod'
import { imageEndpointSchemaMap } from '@tanstack/ai-schemas/fal-image/json-schema'
```

There is **no aggregator barrel** — provider-first imports mean bundlers tree-shake by file. Importing `@tanstack/ai-schemas/gemini/json-schema` ships only Gemini's JSON Schemas; no other provider's bytes end up in your app.

## Examples

Validate a video generation request before hitting the network:

```ts
import { videoEndpointZodMap } from '@tanstack/ai-schemas/fal-video/zod'

const result = videoEndpointZodMap[
'fal-ai/kling-video/o3/pro/text-to-video'
].input.safeParse({
prompt: 'A mecha lands on the ground to save the city, in anime style',
duration: '8',
aspect_ratio: '9:16',
})

if (!result.success) console.error(result.error.issues)
```

Discover what a model supports (build a duration picker):

```ts
import { KlingVideoO3ProTextToVideoInputSchema } from '@tanstack/ai-schemas/fal-video/json-schema'

KlingVideoO3ProTextToVideoInputSchema.properties.duration.enum
// ['3', '4', …, '15']
```

## Bundle size and tree-shaking

The package is `sideEffects: false` and JSON Schemas ship self-contained — each schema bundles its `$ref` closure under `$defs`. Importing one schema pulls only that schema's transitive closure, not the whole category. The provider-first subpaths mean a `import … from '@tanstack/ai-schemas/openai/json-schema'` carries no Anthropic, Gemini, ElevenLabs, or FAL bytes.

## How updates work

The `.github/workflows/sync-schemas.yml` workflow runs nightly:

1. `fetch-schemas` — pulls upstream OpenAPI specs (or equivalents) per provider.
2. `generate-schemas` — runs `@hey-api/openapi-ts` to emit per-provider `schemas.gen.ts` (JSON Schemas) and `zod.gen.ts` (Zod).
3. `generate-endpoint-maps` — emits endpoint-id-keyed maps and bundles `$defs` closures into each JSON Schema.

If any provider's spec changes, the workflow bumps the package version, creates a changeset, and opens an automated PR.

## Local development

```bash
# Pull every provider's spec.
pnpm fetch-schemas

# Pull a single provider.
pnpm fetch-schemas --provider=openai

# Full regeneration.
pnpm update-schemas
```

`FAL_KEY` must be set in your environment to fetch FAL specs.
Loading
Loading