Skip to main content
The Vercel AI SDK ships a first-class OpenAI adapter. Override the base URL and key to route through RunInfra.

Install

npm i ai @ai-sdk/openai-compatible @ai-sdk/react zod

Configure the provider

// lib/runinfra.ts
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";

const apiKey = process.env.RUNINFRA_GATEWAY_KEY;
if (!apiKey) {
  throw new Error("Set RUNINFRA_GATEWAY_KEY.");
}

export const runinfra = createOpenAICompatible({
  name: "runinfra",
  baseURL: "https://api.runinfra.ai/v1",
  apiKey,
});

export function getRunInfraModel(): string {
  const model = process.env.RUNINFRA_MODEL;
  if (!model) {
    throw new Error("Set RUNINFRA_MODEL from GET /v1/models.");
  }
  return model;
}

Stream text in a Next.js route

// app/api/chat/route.ts
import { convertToModelMessages, streamText } from "ai";
import { getRunInfraModel, runinfra } from "@/lib/runinfra";

export async function POST(req: Request) {
  const { messages } = await req.json();
  const result = streamText({
    model: runinfra(getRunInfraModel()),
    messages: convertToModelMessages(messages),
  });
  return result.toUIMessageStreamResponse();
}

Generate structured data

import { generateObject } from "ai";
import { getRunInfraModel, runinfra } from "@/lib/runinfra";
import { z } from "zod";

const { object } = await generateObject({
  model: runinfra(getRunInfraModel()),
  schema: z.object({
    merchant: z.string(),
    total_usd: z.number(),
    items: z.array(z.string()),
  }),
  prompt: "Extract receipt fields: $12.50 at Blue Bottle for coffee and a muffin",
});

Tool calling

import { streamText, tool } from "ai";
import { getRunInfraModel, runinfra } from "@/lib/runinfra";
import { z } from "zod";

const result = streamText({
  model: runinfra(getRunInfraModel()),
  prompt: "What is the weather in Austin?",
  tools: {
    getWeather: tool({
      description: "Get weather for a city",
      inputSchema: z.object({ city: z.string() }),
      execute: async ({ city }) => ({ city, temp: 21 }),
    }),
  },
});

Client hook

The SDK’s useChat hook needs no change. It hits your /api/chat route; your route calls RunInfra:
"use client";
import { useChat } from "@ai-sdk/react";
import { useState } from "react";

export function Chat() {
  const [input, setInput] = useState("");
  const { messages, sendMessage, status } = useChat();

  return (
    <form
      onSubmit={(event) => {
        event.preventDefault();
        const text = input.trim();
        if (!text) return;
        void sendMessage({ text });
        setInput("");
      }}
    >
      {messages.map((message) => (
        <div key={message.id}>
          {message.role}:{" "}
          {message.parts.map((part, index) =>
            part.type === "text" ? <span key={index}>{part.text}</span> : null,
          )}
        </div>
      ))}
      <input
        value={input}
        disabled={status !== "ready"}
        onChange={(event) => setInput(event.currentTarget.value)}
      />
    </form>
  );
}

Next steps

Streaming cookbook

Streaming patterns from scratch.

Tool calling cookbook

Tool-loop patterns at the API level.

Structured output cookbook

JSON Schema responses.

OpenAI compatibility

The contract this integration rides on.