{"slug": "show-hn-tanstack-start-and-tanstack-ai-jev-integration", "title": "Show HN: TanStack Start and TanStack AI Jev Integration", "summary": "TanStack AI's `decide()` evaluation API can be wired into a TanStack Start server route using the `@tanstack/ai-vercel-gateway` adapter and the `typesafe-ai/jev` model ID to return constrained routing decisions, according to a Show HN post. The integration installs `@tanstack/ai`, `@tanstack/ai-vercel-gateway`, `@vercel/oidc` and `zod`, authenticates through Vercel AI Gateway via OIDC (local tokens expire after 12 hours) or an `AI_GATEWAY_API_KEY`, and keeps credentials server-side so the browser only submits the ticket and receives the result. The example routes an unexpected-invoice message to a billing, technical support or review queue, with the assignment only giving the team a place to begin investigating.", "body_md": "Use [Jev](https://vercel.com/i/what-is-jev) in TanStack Start by calling TanStack AI's `decide()` from a server route. The `@tanstack/ai-vercel-gateway` adapter sends the evaluation through AI Gateway. Your route supplies the evidence and questions, then returns a decision that the interface can display or the application can act on.\n\n## [Copy link to heading](#what-does-each-part-of-the-integration-do)What does each part of the integration do?\n\nThe [Vercel Gateway adapter](https://vercel.com/kb/guide/tanstack-ai-vercel-ai-gateway) exposes `createVercelGatewayDecider` for evaluation with an explicit credential. Pair it with `decide()` and the model ID `typesafe-ai/jev`. Jev returns constrained decisions that the form can display once the evaluation completes.\n\n## [Copy link to heading](#how-should-you-define-the-routing-decision)How should you define the routing decision?\n\nSuppose a customer submits a message about an unexpected invoice. The application needs to choose a support queue before anyone investigates the charge. Give Jev the subject and message as evidence, then define the destinations in terms of the work each team owns.\n\nBilling handles charges and invoice questions, while technical support investigates product failures. The review option covers requests with unclear or overlapping responsibilities, such as a message that reports a payment error and asks for help restoring product access.\n\nAsk which team should handle the request first. The assignment gives the support team a place to begin investigating; it does not establish the cause of the problem or confirm that the customer's account has been checked.\n\n## [Copy link to heading](#how-do-you-connect-jev-to-a-tanstack-start-application)How do you connect Jev to a TanStack Start application?\n\n### [Copy link to heading](#1.-install-tanstack-ai-and-the-gateway-adapter)1. Install TanStack AI and the Gateway adapter\n\nIn an existing TanStack Start React project, install the evaluation packages, the Vercel OIDC helper, and Zod for request validation:\n\n```\nnpm install @tanstack/ai @tanstack/ai-vercel-gateway @vercel/oidc zod\n```\n\nThe core package provides `decide()` and the `choice()` helper used below. The Gateway adapter handles authentication and the evaluation request, so this integration does not require a separate TypeSafe API key.\n\n### [Copy link to heading](#2.-configure-ai-gateway-authentication)2. Configure AI Gateway authentication\n\nVercel deployments can use [OIDC authentication](https://vercel.com/docs/ai-gateway/authentication-and-byok/oidc) without managing an API key.\n\nFor local development, link the project and pull an OIDC token into its environment file:\n\n```\nvercel linkvercel env pull .env.local\n```\n\nLocal tokens expire after 12 hours, so refresh the file when the token expires. Alternatively, create an AI Gateway API key and set `AI_GATEWAY_API_KEY` in your application's environment. An existing API key takes precedence over OIDC; updating the token will not fix a request still using an invalid key.\n\nThe adapter's `vercelGatewayDecider` convenience factory reads OIDC tokens only from the environment, which can retain an expired token in a long-running Node.js process. The example instead calls `getVercelOidcToken()` within each request to obtain the current token from Vercel's request context, falling back to `VERCEL_OIDC_TOKEN` for local development. It passes the resolved credential to `createVercelGatewayDecider`.\n\nKeep the evaluation in the server route. The browser submits the ticket and receives the result without receiving either credential.\n\n### [Copy link to heading](#3.-define-the-question-and-the-routing-rule)3. Define the question and the routing rule\n\nCreate `src/lib/route-ticket.server.ts` for the model call. [TanStack AI's evaluation API](https://tanstack.com/ai/latest/docs/evaluate/evaluate) takes an adapter, shared state, and named questions. Each question name becomes a property on the returned result.\n\n``` js\nimport { choice, decide } from '@tanstack/ai';import { createVercelGatewayDecider } from '@tanstack/ai-vercel-gateway';import { getVercelOidcToken } from '@vercel/oidc';\nexport type Ticket = {  subject: string;  body: string;};\nexport async function routeTicket(ticket: Ticket) {  const credential =    process.env.AI_GATEWAY_API_KEY || (await getVercelOidcToken());\n  const result = await decide({    adapter: createVercelGatewayDecider('typesafe-ai/jev', credential),    state: ticket,    questions: {      queue: choice({        instructions: 'Which team should handle this request first?',        options: {          billing: 'Invoice amounts, charges, and payment questions',          technical: 'Product errors, outages, and integration failures',          needs_review: 'Unclear requests or overlapping responsibilities',        },      }),    },    abortSignal: AbortSignal.timeout(10_000),  });\n  const { value, probability } = result.queue;  const reviewRequired = value === 'needs_review' || probability < 0.85;\n  return {    proposedQueue: value,    selectedProbability: probability,    destination: reviewRequired ? 'needs_review' : value,    reviewRequired,  };}\n```\n\nHere, `result.queue.value` is the selected option key. Its `probability` describes that selected option. The code requires review when Jev selects the review category or when the selected queue falls below the example threshold.\n\nThe `0.85` cutoff and ten-second deadline are application choices. Establish your threshold with labeled requests and choose a deadline that fits the form's expected response time. Even a high probability for `needs_review` sends the ticket to review, which is why the explicit category check remains separate from the cutoff.\n\nThe result preserves both the proposed queue and the destination chosen by application code. That distinction lets a support agent see when the model proposed billing but the application required review because the probability was below the threshold.\n\n### [Copy link to heading](#4.-add-a-validated-server-route)4. Add a validated server route\n\nCreate `src/routes/api.triage.ts`. The [TanStack Start server-route convention](https://tanstack.com/start/latest/docs/framework/react/guide/server-routes) exposes this file at `/api/triage` and lets the handler return a standard `Response`.\n\n``` js\nimport { createFileRoute } from '@tanstack/react-router';import { z } from 'zod';import { routeTicket } from '../lib/route-ticket.server';\nconst ticketSchema = z.object({  subject: z.string().trim().min(1).max(200),  body: z.string().trim().min(1).max(5_000),});\nexport const Route = createFileRoute('/api/triage')({  server: {    handlers: {      POST: async ({ request }) => {        let input: unknown;        try {          input = await request.json();        } catch {          return Response.json({ error: 'Expected a JSON request.' }, { status: 400 });        }\n        const parsed = ticketSchema.safeParse(input);        if (!parsed.success) {          return Response.json({ error: 'Enter a subject and message within the limits.' }, { status: 400 });        }\n        try {          return Response.json(await routeTicket(parsed.data));        } catch {          return Response.json({ error: 'Routing is unavailable. Try again.' }, { status: 503 });        }      },    },  },});\n```\n\nThe schema restricts the model's input to the two fields the question needs. The character limits belong to this form design and do not describe Jev's context capacity. Keep the queue definitions and threshold in application code so a submission cannot replace them.\n\nMalformed input returns `400`, and an evaluation failure returns `503`. Completed evaluations return `200`, including those with `reviewRequired: true`, so the interface can distinguish a request that needs a person's judgment from one that failed to run.\n\nThis route proposes a destination without saving the ticket. When connecting it to your support system, apply the existing access controls and persist the submission before telling the customer it has been received. If routing is unavailable, that saved ticket can wait for review without forcing the customer to re-enter their message.\n\n### [Copy link to heading](#5.-call-the-route-from-a-form)5. Call the route from a form\n\nThe interface only needs to send the input and display the returned destination. This component can sit inside an existing TanStack Start page:\n\n``` js\nimport { useState, type FormEvent } from 'react';\nexport function TicketForm() {  const [pending, setPending] = useState(false);  const [message, setMessage] = useState('');\n  async function submit(event: FormEvent<HTMLFormElement>) {    event.preventDefault();    const form = new FormData(event.currentTarget);    setPending(true);    setMessage('');\n    try {      const response = await fetch('/api/triage', {        method: 'POST',        headers: { 'Content-Type': 'application/json' },        body: JSON.stringify({          subject: form.get('subject'),          body: form.get('body'),        }),      });      const result = await response.json();      if (!response.ok) throw new Error(result.error ?? 'Routing failed.');\n      setMessage(        result.reviewRequired          ? 'This request needs a person to choose the right team.'          : `Suggested team: ${result.destination}`,      );    } catch (error) {      setMessage(error instanceof Error ? error.message : 'Routing failed.');    } finally {      setPending(false);    }  }\n  return (    <form onSubmit={submit}>      <label>Subject <input name=\"subject\" required maxLength={200} /></label>      <label>Message <textarea name=\"body\" required maxLength={5_000} /></label>      <button disabled={pending} type=\"submit\">        {pending ? 'Checking…' : 'Suggest a team'}      </button>      <p role=\"status\">{message}</p>    </form>  );}\n```\n\nThe interface says which team is suggested and keeps routing errors visible. It does not claim that the issue has been resolved or that the support system has accepted the submission. After adding persistence, update the confirmation to reflect the completed operation.\n\n## [Copy link to heading](#how-should-you-interpret-jev's-other-answer-fields)How should you interpret Jev's other answer fields?\n\nChoice answers also include the full distribution in `probabilities` and a separate `confidence` statistic. Probability refers to the selected option; confidence describes the distribution's concentration. Neither establishes that an individual assignment is correct, and the example routing rule uses only the selected-option probability.\n\nTanStack AI also provides `score()` for an ordered rubric and `boolean()` for a yes-or-no statement. Boolean `probability` always describes the probability that the statement is true, even when its `value` is false. For Score answers, the fractional `score` and its nearest level label describe a position on your rubric.\n\nThe [product-review moderation guide](https://vercel.com/kb/guide/moderate-product-reviews-jev-tanstack-ai) combines all three question types and shows how to test the application rules without calling Jev. Use that guide when your decision requires several assessments, such as a topic selection alongside content flags.\n\n## [Copy link to heading](#what-should-you-test-before-routing-real-requests)What should you test before routing real requests?\n\nTest the routing rule at the threshold and on either side of it. Include an explicit needs-review answer with a high probability to verify that it still reaches review. Invalid input and provider failures should follow their error paths without producing a queue assignment.\n\nThen assess Jev's selections against tickets your team has labeled. Include messages that mention more than one issue and tickets whose correct destination depends on your team's responsibilities. If reviewers cannot agree on the category, clarify the definitions before changing the probability threshold.\n\nKeep the original ticket and routing result available so support agents can correct assignments. Record those corrections with the question definitions used at the time to identify recurring gaps in the categories or evaluation.\n\n## [Copy link to heading](#can-i-use-ai-sdk-with-jev-instead)Can I use AI SDK with Jev instead?\n\nYes. TanStack Start can run AI SDK calls in its server routes. Install `ai` to use this alternative, then call `experimental_evaluate` with a Jev model through AI Gateway:\n\n``` js\nimport { experimental_evaluate as evaluate } from 'ai';\nconst result = await evaluate({  model: 'typesafe-ai/jev',  state: { subject: 'Unexpected charge', body: 'My invoice includes an extra seat.' },  questions: {    queue: {      type: 'choice',      instructions: 'Which team should handle this request first?',      criteria: {        billing: 'Invoice amounts, charges, and payment questions',        technical: 'Product errors, outages, and integration failures',        needs_review: 'Unclear requests or overlapping responsibilities',      },    },  },});\nconst answer = result.answers.queue;console.log(answer.choice, answer.probabilities?.[answer.choice]);\n```\n\nThis example uses AI SDK's default Gateway provider, which resolves OIDC automatically when `AI_GATEWAY_API_KEY` is unset. AI SDK places the selection in `answers.queue.choice`; TanStack AI exposes it as `queue.value`. Update the code that reads the answer to match the library you choose.\n\nOur guide to [classifying, routing, and scoring with Jev and AI SDK](https://vercel.com/kb/guide/typesafe-jev-and-ai-sdk) covers the question types and testing in detail. The [Jev form-router guide](https://vercel.com/kb/guide/jev-ai-sdk-form-router) adds a deployable example with a fallback model for uncertain or failed evaluations.\n\nFor evaluations using OpenAI models, see the companion article [Using GPT-6 Sol with AI SDK evaluation](https://vercel.com/i/using-gpt-6-sol-with-ai-sdk-evaluation), which includes Astra and Luna examples. Those models use AI SDK's OpenAI evaluation adapter and have different probability semantics from Jev, so a provider change also requires reviewing the application's acceptance rules.\n\n## [Copy link to heading](#frequently-asked-questions)Frequently asked questions\n\n### [Copy link to heading](#can-i-call-jev-directly-from-the-browser-in-this-integration)Can I call Jev directly from the browser in this integration?\n\nKeep the credential and evaluation call in a TanStack Start server route. The browser can submit the form with fetch and receive the routing result without having access to the Gateway credential.\n\n### [Copy link to heading](#do-i-need-a-typesafe-api-key-when-using-the-vercel-gateway-adapter)Do I need a TypeSafe API key when using the Vercel Gateway adapter?\n\nNo. The Gateway adapter authenticates with AI_GATEWAY_API_KEY or a Vercel OIDC token. Direct TypeSafe adapters use their own provider credentials.\n\n### [Copy link to heading](#does-tanstack-ai-stream-evaluation-answers)Does TanStack AI stream evaluation answers?\n\nNo. The decide function returns one completed result containing the named answers and metadata. The form can show a pending state while it waits for that result.\n\n### [Copy link to heading](#does-using-tanstack-start-require-choosing-tanstack-ai-over-ai-sdk)Does using TanStack Start require choosing TanStack AI over AI SDK?\n\nNo. TanStack Start can host server code that uses either library. Choose the evaluation interface that fits your application and account for their different answer shapes.", "url": "https://wpnews.pro/news/show-hn-tanstack-start-and-tanstack-ai-jev-integration", "canonical_source": "https://vercel.com/i/using-jev-in-tanstack-start-with-tanstack-ai", "published_at": "2026-09-27 10:13:46+00:00", "updated_at": "2026-09-27 10:31:05.706095+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "developer-tools", "ai-infrastructure"], "entities": ["TanStack", "TanStack AI", "TanStack Start", "Vercel", "AI Gateway", "Jev", "@tanstack/ai-vercel-gateway", "Zod"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/show-hn-tanstack-start-and-tanstack-ai-jev-integration", "markdown": "https://wpnews.pro/news/show-hn-tanstack-start-and-tanstack-ai-jev-integration.md", "text": "https://wpnews.pro/news/show-hn-tanstack-start-and-tanstack-ai-jev-integration.txt", "jsonld": "https://wpnews.pro/news/show-hn-tanstack-start-and-tanstack-ai-jev-integration.jsonld"}}