Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
8583c9a
Update openrouter package, models, and scripts to generate openrouter…
tombeckenham Feb 23, 2026
2bbda65
ci: apply automated fixes
autofix-ci[bot] Feb 23, 2026
6f8873b
Added compare script and fixed key name issue in fetch models
tombeckenham Feb 23, 2026
50ec22e
resolved coderabbit issues
tombeckenham Feb 23, 2026
cde3155
ci: apply automated fixes
autofix-ci[bot] Feb 23, 2026
70f0bcf
Added support for openrouter structured output
tombeckenham Feb 24, 2026
8acf2a4
Address PR #312 review feedback: improve error handling and cleanup
tombeckenham Feb 24, 2026
348d363
Merge branch 'main' into pr/tombeckenham/312
tombeckenham Mar 3, 2026
761380a
Update model metadata and pricing in OpenRouter, add new models, and …
tombeckenham Mar 4, 2026
823747b
ci: apply automated fixes
autofix-ci[bot] Mar 4, 2026
6467f59
Update openrouter package models and added prettier to fetch script
tombeckenham Mar 4, 2026
f60bd3b
Updated models and script
tombeckenham Mar 4, 2026
c498acc
ci: apply automated fixes
autofix-ci[bot] Mar 4, 2026
6ebe8b1
Update openrouter package to 0.9.11
tombeckenham Mar 4, 2026
340cb82
Refactor OpenRouter options passthrough and bump fal client
tombeckenham Mar 4, 2026
c7ce94b
Refactor text-provider-options to derive types from SDK's ChatGenerat…
tombeckenham Mar 4, 2026
655e5fe
ci: apply automated fixes
autofix-ci[bot] Mar 4, 2026
ed027a1
Changeset updated
tombeckenham Mar 4, 2026
42e73fb
Merge remote-tracking branch 'upstream/main' into tombeckenham/issue310
tombeckenham Mar 9, 2026
50c4617
Merge remote-tracking branch 'upstream/main' into pr/tombeckenham/312
tombeckenham Mar 12, 2026
5e27d94
Merge remote-tracking branch 'upstream/main' into tombeckenham/issue310
tombeckenham Mar 13, 2026
13ed768
Merge remote-tracking branch 'upstream/main' into tombeckenham/issue310
tombeckenham Apr 17, 2026
4ebc7d2
ci: apply automated fixes
autofix-ci[bot] Apr 17, 2026
6e16c0e
chore(ai-openrouter): upgrade @openrouter/sdk to 0.12.13
tombeckenham Apr 17, 2026
a45e2fb
ci: apply automated fixes
autofix-ci[bot] Apr 17, 2026
45d216f
chore(ai-openrouter): expand changeset, wire format into generate:models
tombeckenham Apr 17, 2026
d21f546
Corrected openrouter params in example
tombeckenham Apr 17, 2026
b094d8b
chore(ai-openrouter): bump @openrouter/sdk to 0.12.14
tombeckenham Apr 17, 2026
9593342
chore(ai-openrouter): clarify changeset, tidy image error check, docu…
tombeckenham Apr 17, 2026
d00d811
docs(ai-openrouter): explain modelOptions-first spread ordering in te…
tombeckenham Apr 17, 2026
09962f7
fix(ai-openrouter): apply OpenAI-strict transformation to structuredO…
tombeckenham Apr 17, 2026
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
15 changes: 15 additions & 0 deletions .changeset/giant-garlics-crash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
'@tanstack/ai-openrouter': patch
---

- Upgrade `@openrouter/sdk` to 0.12.14 (from 0.3.15)
- Migrate adapters and tests to the SDK's renamed chat types (`ChatGenerationParams` → `ChatRequest`, `ChatResponse` → `ChatResult`) and the renamed `chatRequest` key on `chat.send`
- Derive `text-provider-options` types from the SDK's `ChatRequest` so provider options stay in lockstep with the SDK surface
- Drop `topK` / `topA` / `minP` / `repetitionPenalty` / `includeReasoning` / `verbosity` / `webSearchOptions` from `OpenRouterBaseOptions` now that the SDK narrows `ChatRequest` to OpenAI-compatible fields — callers passing these previously saw them silently stripped at runtime, and now get a TypeScript error instead
- Add those same keys to `excludedParams` in the model-catalog generator so future syncs don't reintroduce them
- Switch `structuredOutput` to the SDK's native `responseFormat: { type: 'json_schema' }` instead of coercing the model via a forced `structured_output` tool call — the schema now flows straight to the provider, yielding cleaner responses and dropping the dummy-tool round-trip
- Apply the OpenAI-strict schema transformation (`additionalProperties: false`, all properties required, optional fields widened to include `null`) inside `structuredOutput` before forwarding — without this, upstream OpenAI rejected the request with "Provider returned error" for schemas generated by Zod / ArkType / Valibot
- Refactor options passthrough to use camelCase naming convention
- Refresh the model catalog with the latest OpenRouter models (Opus 4.6, Sonnet 4.6, Gemini 3.1 Pro, etc.) and remove the deprecated `openrouter/auto` model
- Unify the outbound request payload envelope
- Improve error handling and add tests for nested payloads, structured output parsing, and error cases
1 change: 1 addition & 0 deletions .changeset/sync-models.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
---
'@tanstack/ai-anthropic': patch
'@tanstack/ai-openrouter': patch
---

Expand Down
14 changes: 14 additions & 0 deletions examples/ts-react-chat/src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Link } from '@tanstack/react-router'

import { useState } from 'react'
import {
Braces,
FileAudio,
FileText,
Guitar,
Expand Down Expand Up @@ -138,6 +139,19 @@ export default function Header() {
<span className="font-medium">Video Generation</span>
</Link>

<Link
to="/generations/structured-output"
onClick={() => setIsOpen(false)}
className="flex items-center gap-3 p-3 rounded-lg hover:bg-gray-800 transition-colors mb-1"
activeProps={{
className:
'flex items-center gap-3 p-3 rounded-lg bg-cyan-600 hover:bg-cyan-700 transition-colors mb-1',
}}
>
<Braces size={20} />
<span className="font-medium">Structured Output (OpenRouter)</span>
</Link>

<hr className="border-gray-700 my-2" />

<p className="text-xs text-gray-500 uppercase tracking-wider px-3 pt-2 pb-1">
Expand Down
43 changes: 43 additions & 0 deletions examples/ts-react-chat/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ import { Route as IndexRouteImport } from './routes/index'
import { Route as GenerationsVideoRouteImport } from './routes/generations.video'
import { Route as GenerationsTranscriptionRouteImport } from './routes/generations.transcription'
import { Route as GenerationsSummarizeRouteImport } from './routes/generations.summarize'
import { Route as GenerationsStructuredOutputRouteImport } from './routes/generations.structured-output'
import { Route as GenerationsSpeechRouteImport } from './routes/generations.speech'
import { Route as GenerationsImageRouteImport } from './routes/generations.image'
import { Route as ApiTranscribeRouteImport } from './routes/api.transcribe'
import { Route as ApiTanchatRouteImport } from './routes/api.tanchat'
import { Route as ApiSummarizeRouteImport } from './routes/api.summarize'
import { Route as ApiStructuredOutputRouteImport } from './routes/api.structured-output'
import { Route as ApiImageGenRouteImport } from './routes/api.image-gen'
import { Route as ExampleGuitarsIndexRouteImport } from './routes/example.guitars/index'
import { Route as ExampleGuitarsGuitarIdRouteImport } from './routes/example.guitars/$guitarId'
Expand Down Expand Up @@ -58,6 +60,12 @@ const GenerationsSummarizeRoute = GenerationsSummarizeRouteImport.update({
path: '/generations/summarize',
getParentRoute: () => rootRouteImport,
} as any)
const GenerationsStructuredOutputRoute =
GenerationsStructuredOutputRouteImport.update({
id: '/generations/structured-output',
path: '/generations/structured-output',
getParentRoute: () => rootRouteImport,
} as any)
const GenerationsSpeechRoute = GenerationsSpeechRouteImport.update({
id: '/generations/speech',
path: '/generations/speech',
Expand All @@ -83,6 +91,11 @@ const ApiSummarizeRoute = ApiSummarizeRouteImport.update({
path: '/api/summarize',
getParentRoute: () => rootRouteImport,
} as any)
const ApiStructuredOutputRoute = ApiStructuredOutputRouteImport.update({
id: '/api/structured-output',
path: '/api/structured-output',
getParentRoute: () => rootRouteImport,
} as any)
const ApiImageGenRoute = ApiImageGenRouteImport.update({
id: '/api/image-gen',
path: '/api/image-gen',
Expand Down Expand Up @@ -119,11 +132,13 @@ export interface FileRoutesByFullPath {
'/image-gen': typeof ImageGenRoute
'/realtime': typeof RealtimeRoute
'/api/image-gen': typeof ApiImageGenRoute
'/api/structured-output': typeof ApiStructuredOutputRoute
'/api/summarize': typeof ApiSummarizeRoute
'/api/tanchat': typeof ApiTanchatRoute
'/api/transcribe': typeof ApiTranscribeRoute
'/generations/image': typeof GenerationsImageRoute
'/generations/speech': typeof GenerationsSpeechRoute
'/generations/structured-output': typeof GenerationsStructuredOutputRoute
'/generations/summarize': typeof GenerationsSummarizeRoute
'/generations/transcription': typeof GenerationsTranscriptionRoute
'/generations/video': typeof GenerationsVideoRoute
Expand All @@ -138,11 +153,13 @@ export interface FileRoutesByTo {
'/image-gen': typeof ImageGenRoute
'/realtime': typeof RealtimeRoute
'/api/image-gen': typeof ApiImageGenRoute
'/api/structured-output': typeof ApiStructuredOutputRoute
'/api/summarize': typeof ApiSummarizeRoute
'/api/tanchat': typeof ApiTanchatRoute
'/api/transcribe': typeof ApiTranscribeRoute
'/generations/image': typeof GenerationsImageRoute
'/generations/speech': typeof GenerationsSpeechRoute
'/generations/structured-output': typeof GenerationsStructuredOutputRoute
'/generations/summarize': typeof GenerationsSummarizeRoute
'/generations/transcription': typeof GenerationsTranscriptionRoute
'/generations/video': typeof GenerationsVideoRoute
Expand All @@ -158,11 +175,13 @@ export interface FileRoutesById {
'/image-gen': typeof ImageGenRoute
'/realtime': typeof RealtimeRoute
'/api/image-gen': typeof ApiImageGenRoute
'/api/structured-output': typeof ApiStructuredOutputRoute
'/api/summarize': typeof ApiSummarizeRoute
'/api/tanchat': typeof ApiTanchatRoute
'/api/transcribe': typeof ApiTranscribeRoute
'/generations/image': typeof GenerationsImageRoute
'/generations/speech': typeof GenerationsSpeechRoute
'/generations/structured-output': typeof GenerationsStructuredOutputRoute
'/generations/summarize': typeof GenerationsSummarizeRoute
'/generations/transcription': typeof GenerationsTranscriptionRoute
'/generations/video': typeof GenerationsVideoRoute
Expand All @@ -179,11 +198,13 @@ export interface FileRouteTypes {
| '/image-gen'
| '/realtime'
| '/api/image-gen'
| '/api/structured-output'
| '/api/summarize'
| '/api/tanchat'
| '/api/transcribe'
| '/generations/image'
| '/generations/speech'
| '/generations/structured-output'
| '/generations/summarize'
| '/generations/transcription'
| '/generations/video'
Expand All @@ -198,11 +219,13 @@ export interface FileRouteTypes {
| '/image-gen'
| '/realtime'
| '/api/image-gen'
| '/api/structured-output'
| '/api/summarize'
| '/api/tanchat'
| '/api/transcribe'
| '/generations/image'
| '/generations/speech'
| '/generations/structured-output'
| '/generations/summarize'
| '/generations/transcription'
| '/generations/video'
Expand All @@ -217,11 +240,13 @@ export interface FileRouteTypes {
| '/image-gen'
| '/realtime'
| '/api/image-gen'
| '/api/structured-output'
| '/api/summarize'
| '/api/tanchat'
| '/api/transcribe'
| '/generations/image'
| '/generations/speech'
| '/generations/structured-output'
| '/generations/summarize'
| '/generations/transcription'
| '/generations/video'
Expand All @@ -237,11 +262,13 @@ export interface RootRouteChildren {
ImageGenRoute: typeof ImageGenRoute
RealtimeRoute: typeof RealtimeRoute
ApiImageGenRoute: typeof ApiImageGenRoute
ApiStructuredOutputRoute: typeof ApiStructuredOutputRoute
ApiSummarizeRoute: typeof ApiSummarizeRoute
ApiTanchatRoute: typeof ApiTanchatRoute
ApiTranscribeRoute: typeof ApiTranscribeRoute
GenerationsImageRoute: typeof GenerationsImageRoute
GenerationsSpeechRoute: typeof GenerationsSpeechRoute
GenerationsStructuredOutputRoute: typeof GenerationsStructuredOutputRoute
GenerationsSummarizeRoute: typeof GenerationsSummarizeRoute
GenerationsTranscriptionRoute: typeof GenerationsTranscriptionRoute
GenerationsVideoRoute: typeof GenerationsVideoRoute
Expand Down Expand Up @@ -296,6 +323,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof GenerationsSummarizeRouteImport
parentRoute: typeof rootRouteImport
}
'/generations/structured-output': {
id: '/generations/structured-output'
path: '/generations/structured-output'
fullPath: '/generations/structured-output'
preLoaderRoute: typeof GenerationsStructuredOutputRouteImport
parentRoute: typeof rootRouteImport
}
'/generations/speech': {
id: '/generations/speech'
path: '/generations/speech'
Expand Down Expand Up @@ -331,6 +365,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof ApiSummarizeRouteImport
parentRoute: typeof rootRouteImport
}
'/api/structured-output': {
id: '/api/structured-output'
path: '/api/structured-output'
fullPath: '/api/structured-output'
preLoaderRoute: typeof ApiStructuredOutputRouteImport
parentRoute: typeof rootRouteImport
}
'/api/image-gen': {
id: '/api/image-gen'
path: '/api/image-gen'
Expand Down Expand Up @@ -381,11 +422,13 @@ const rootRouteChildren: RootRouteChildren = {
ImageGenRoute: ImageGenRoute,
RealtimeRoute: RealtimeRoute,
ApiImageGenRoute: ApiImageGenRoute,
ApiStructuredOutputRoute: ApiStructuredOutputRoute,
ApiSummarizeRoute: ApiSummarizeRoute,
ApiTanchatRoute: ApiTanchatRoute,
ApiTranscribeRoute: ApiTranscribeRoute,
GenerationsImageRoute: GenerationsImageRoute,
GenerationsSpeechRoute: GenerationsSpeechRoute,
GenerationsStructuredOutputRoute: GenerationsStructuredOutputRoute,
GenerationsSummarizeRoute: GenerationsSummarizeRoute,
GenerationsTranscriptionRoute: GenerationsTranscriptionRoute,
GenerationsVideoRoute: GenerationsVideoRoute,
Expand Down
58 changes: 58 additions & 0 deletions examples/ts-react-chat/src/routes/api.structured-output.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { createFileRoute } from '@tanstack/react-router'
import { chat } from '@tanstack/ai'
import { openRouterText } from '@tanstack/ai-openrouter'
import { z } from 'zod'

const GuitarRecommendationSchema = z.object({
title: z.string().describe('Short headline for the recommendation'),
summary: z.string().describe('One paragraph summary'),
recommendations: z
.array(
z.object({
name: z.string(),
brand: z.string(),
type: z.enum(['acoustic', 'electric', 'bass', 'classical']),
priceRangeUsd: z.object({ min: z.number(), max: z.number() }),
reason: z.string(),
}),
)
.min(1)
.describe('Guitar recommendations with reasons'),
nextSteps: z.array(z.string()).describe('Practical follow-up actions'),
})

export const Route = createFileRoute('/api/structured-output')({
server: {
handlers: {
POST: async ({ request }) => {
const body = await request.json()
const { prompt, model } = body as {
prompt: string
model?: string
}

try {
const result = await chat({
adapter: openRouterText(
(model || 'openai/gpt-5.2') as 'openai/gpt-5.2',
),
messages: [{ role: 'user', content: prompt }],
outputSchema: GuitarRecommendationSchema,
})

return new Response(JSON.stringify({ data: result }), {
headers: { 'Content-Type': 'application/json' },
})
} catch (error: unknown) {
const message =
error instanceof Error ? error.message : 'An error occurred'
console.error('[api/structured-output] Error:', error)
return new Response(JSON.stringify({ error: message }), {
status: 500,
headers: { 'Content-Type': 'application/json' },
})
}
},
},
},
})
4 changes: 1 addition & 3 deletions examples/ts-react-chat/src/routes/api.tanchat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import { anthropicText } from '@tanstack/ai-anthropic'
import { geminiText } from '@tanstack/ai-gemini'
import { openRouterText } from '@tanstack/ai-openrouter'
import { grokText } from '@tanstack/ai-grok'
import type { AnyTextAdapter, ChatMiddleware } from '@tanstack/ai'
import { groqText } from '@tanstack/ai-groq'
import type { AnyTextAdapter, ChatMiddleware } from '@tanstack/ai'
import {
addToCartToolDef,
addToWishListToolDef,
Expand Down Expand Up @@ -146,8 +146,6 @@ export const Route = createFileRoute('/api/tanchat')({
createChatOptions({
adapter: openRouterText('openai/gpt-5.1'),
modelOptions: {
models: ['openai/chatgpt-4o-latest'],
route: 'fallback',
reasoning: {
effort: 'medium',
},
Expand Down
Loading
Loading