# Vercel AI SDK

URL: https://cogitator.app/docs/integrations/ai-sdk

> Bidirectional bridge between Cogitator and the Vercel AI SDK 4, 5, 6 and 7 -- use Cogitator agents as AI SDK models or use AI SDK models inside Cogitator.

## Overview

The `@cogitator-ai/ai-sdk` package connects the two ecosystems in both directions:

* **Cogitator as model** -- expose Cogitator agents as AI SDK language models, so you can use them with `generateText`, `streamText` and structured output.
* **AI SDK as backend** -- wrap any AI SDK model as a Cogitator `LLMBackend`.
* **Tools** -- convert tools in both directions.

```bash
pnpm add @cogitator-ai/ai-sdk @cogitator-ai/core ai
```

## Supported AI SDK Versions

`ai` is a peer dependency: `^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0`.

| AI SDK | Accepted model specs | Default `cogitatorModel()` spec | `fromAISDK()` input |
| ------ | -------------------- | ------------------------------- | ------------------- |
| `ai@4` | `v1`                 | `v1`                            | `LanguageModelV1`   |
| `ai@5` | `v2`                 | `v2`                            | `LanguageModelV2`   |
| `ai@6` | `v2`, `v3`           | `v3`                            | `LanguageModelV3`   |
| `ai@7` | `v2`, `v3`, `v4`     | `v4`                            | `LanguageModelV4`   |

### Specification Auto-Detection

Without a `specificationVersion` option, the model spec follows the installed `ai` package:

* **At runtime** `ai/package.json` is resolved from `process.cwd()`, then from `@cogitator-ai/ai-sdk` itself (ESM-only installs that hide `package.json` behind `exports` are handled). The result is cached. If `ai` cannot be resolved, `'v2'` is used -- ai\@5, ai\@6 and ai\@7 accept it.
* **In TypeScript** the default return type is derived from the installed `ai` package's `LanguageModel` type with the same mapping, so code written for any major type-checks unchanged.

Pass `specificationVersion: 'v1' | 'v2' | 'v3' | 'v4'` to override it, e.g. when several `ai` majors are installed. AI SDK 6 and 7 accept `v2` models only in a compatibility mode that logs a warning on every call, which the detected `v3` / `v4` defaults avoid.

## Cogitator as AI SDK Model

### cogitatorModel

Pass a `Cogitator` instance and an `Agent` to get an AI SDK language model:

```typescript
import { generateText, streamText } from 'ai';
import { Cogitator, Agent } from '@cogitator-ai/core';
import { cogitatorModel } from '@cogitator-ai/ai-sdk';

const cog = new Cogitator({
  llm: {
    defaultProvider: 'openai',
    providers: { openai: { apiKey: process.env.OPENAI_API_KEY! } },
  },
});

const agent = new Agent({
  name: 'researcher',
  model: 'openai/gpt-6.1-sol',
  instructions: 'You are an expert researcher. Always cite sources.',
  tools: [webSearch, readUrl],
});

const { text } = await generateText({
  model: cogitatorModel(cog, agent, { temperature: 0.7 }),
  prompt: 'What are the latest advances in quantum computing?',
});

const result = streamText({
  model: cogitatorModel(cog, agent, { specificationVersion: 'v2' }),
  prompt: 'Summarize them in three bullet points',
});

for await (const chunk of result.textStream) {
  process.stdout.write(chunk);
}
```

The first model uses the auto-detected spec; the second overrides it. On **ai\@4** the default is a `LanguageModelV1`.

### How an Agent Behaves as a Model

* One model call runs the whole agent loop, including the agent's own tool calls, and finishes with `finishReason: 'stop'`.
* The agent's tool calls are reported as **provider-executed** tool calls with results, so the AI SDK never runs them a second time:
  * `v3` / `v4` models always report them in `toolCalls` / `toolResults` and as `tool-call` / `tool-result` stream parts.
  * `v2` models report them only for tools that you also pass in `tools`, because ai\@5 cannot handle provider-executed calls of undeclared tools.
  * All models list them in `providerMetadata.cogitator.toolCalls` next to `runId`, `threadId`, `agentId`, `model`, `cost` and `duration`.
* The text of user and assistant messages becomes the agent input; multi-turn prompts are sent as a `User:` / `Assistant:` transcript. System messages, images and files are not forwarded (the agent `instructions` are the system prompt) and produce a warning.
* `temperature`, `topP`, `maxOutputTokens` (`maxTokens` on ai\@4) and `stopSequences` override the agent configuration for that call. `abortSignal` and cancelling a stream abort the run.
* The `reasoning` option of `v4` models overrides the effort of the agent's `reasoning`. With `reasoning: { summary: true }` the agent's summary comes back as `reasoning` content and `reasoning-*` stream parts.
* Structured output (`Output.object`, `generateObject`, ai\@4 object modes) switches the agent to JSON mode and adds the JSON schema to the input.
* AI SDK `tools` that the agent does not own cannot be called by the agent and produce a warning.

```typescript
import { Output, generateText } from 'ai';
import { z } from 'zod';

const { output } = await generateText({
  model: cogitatorModel(cog, agent),
  output: Output.object({ schema: z.object({ title: z.string(), year: z.number() }) }),
  prompt: 'Name a landmark quantum computing paper',
});
```

### createCogitatorProvider

When you have multiple agents, `createCogitatorProvider` resolves them by name:

```typescript
import { generateText } from 'ai';
import { createCogitatorProvider } from '@cogitator-ai/ai-sdk';

const provider = createCogitatorProvider(cog, {
  agents: [researcher, summarizer],
  specificationVersion: 'v2',
});

const { text } = await generateText({
  model: provider('researcher', { temperature: 0.5 }),
  prompt: 'Explain CRISPR gene editing',
});

const { text: summary } = await generateText({
  model: provider.languageModel('summarizer', { maxTokens: 2048 }),
  prompt: text,
});
```

`agents` may be an array (keyed by agent name), a `Map` or a record. An unknown name throws with the list of available agents.

### Options

| Option                 | Type                           | Description                                                                                                                            |
| ---------------------- | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
| `temperature`          | `number`                       | Default temperature for the agent                                                                                                      |
| `maxTokens`            | `number`                       | Default max output tokens                                                                                                              |
| `topP`                 | `number`                       | Default top-p sampling                                                                                                                 |
| `specificationVersion` | `'v1' \| 'v2' \| 'v3' \| 'v4'` | Model spec to implement (`cogitatorModel` and `createCogitatorProvider` config), detected from the installed `ai` package when omitted |

## AI SDK Models in Cogitator

### fromAISDK

Wrap any AI SDK language model -- `LanguageModelV1` (ai\@4 providers) through `LanguageModelV4` (ai\@7 providers) -- as a Cogitator `LLMBackend`:

```typescript
import { google } from '@ai-sdk/google';
import { fromAISDK } from '@cogitator-ai/ai-sdk';

const backend = fromAISDK(google('gemini-3.5-flash-lite'));

const response = await backend.chat({
  model: 'gemini-3.5-flash-lite',
  messages: [
    { role: 'system', content: 'You are a creative writing assistant.' },
    { role: 'user', content: 'Write a haiku about TypeScript' },
  ],
});

for await (const chunk of backend.chatStream({
  model: 'gemini-3.5-flash-lite',
  messages: [{ role: 'user', content: 'Tell me a story' }],
})) {
  process.stdout.write(chunk.delta.content ?? '');
}
```

The backend branches on the model's `specificationVersion` and converts:

* messages -- text, images (URL or base64), assistant tool calls and tool results as structured prompt parts
* `tools`, `toolChoice`, `responseFormat`, `temperature`, `topP`, `maxTokens`, `stop` and the abort signal
* `reasoning.effort` -- the `reasoning` call option of `v4` models (`max` becomes `xhigh`); for older specifications, set reasoning on the AI SDK provider model
* responses -- text, reasoning (`ChatResponse.reasoning`, `delta.reasoning`), client tool calls (provider-executed calls are skipped), finish reasons and usage including cached, cache-write and reasoning tokens
* Gemini thought signatures on tool calls, which are replayed on the next turn

### Running Agents on an AI SDK Model

To run Cogitator agents on an AI SDK model, register the backend under a name in `llm.backends` and point agents at it with `name/model`:

```typescript
import { Agent, Cogitator } from '@cogitator-ai/core';

const cog = new Cogitator({
  llm: { backends: { gemini: fromAISDK(google('gemini-3.5-flash-lite')) } },
});

const agent = new Agent({
  name: 'writer',
  model: 'gemini/gemini-3.5-flash-lite',
  instructions: 'You write short poems.',
});

const result = await cog.run(agent, { input: 'A haiku about types' });
```

Tools, memory, streaming and every other run feature work as with built-in providers. The wrapped model is used whatever model name the request carries; cost is reported only for models the price registry knows.

## Tool Conversion

Tools can be converted between the two systems, so you never need to rewrite tool definitions.

### Cogitator to AI SDK

```typescript
import { generateText, stepCountIs } from 'ai';
import { openai } from '@ai-sdk/openai';
import { toAISDKTool, convertToolsToAISDK } from '@cogitator-ai/ai-sdk';

const aiTool = toAISDKTool(calculator);

const { text } = await generateText({
  model: openai('gpt-6.1-sol'),
  tools: convertToolsToAISDK([calculator, webSearch]),
  stopWhen: stepCountIs(5),
  prompt: 'What is 42 * 17?',
});
```

Converted tools work with the `tools` option of every AI SDK major:

* `inputSchema` (ai\@5 – ai\@7) is the Cogitator tool's zod schema.
* `parameters` (ai\@4) is an AI SDK `Schema` with draft-07 JSON Schema and validation.
* `execute(input, options)` builds a Cogitator `ToolContext`: `toolCallId` becomes `runId`, `abortSignal` becomes `signal`, and the string fields `agentId`, `runId`, `threadId`, `userId`, `channelType` and `channelId` of the AI SDK tool context (`context` on ai\@7, `experimental_context` on ai\@5 / ai\@6) are copied.

### AI SDK to Cogitator

```typescript
import { tool } from 'ai';
import { z } from 'zod';
import { fromAISDKTool, convertToolsFromAISDK } from '@cogitator-ai/ai-sdk';

const weatherTool = tool({
  description: 'Get current weather for a location',
  inputSchema: z.object({
    location: z.string().describe('City name'),
    unit: z.enum(['celsius', 'fahrenheit']).optional(),
  }),
  execute: async ({ location, unit }) => {
    return { temperature: 22, unit: unit ?? 'celsius', location };
  },
});

const cogWeather = fromAISDKTool(weatherTool, 'get_weather');

const agent = new Agent({
  name: 'weather-bot',
  model: 'openai/gpt-6.1-sol',
  instructions: 'Help users check the weather.',
  tools: [cogWeather],
});
```

`fromAISDKTool` reads `inputSchema` (ai\@5+) or `parameters` (ai\@4) and accepts zod 4 schemas, Standard JSON Schemas, `jsonSchema()` / `zodSchema()` from any AI SDK major and plain JSON Schema objects. Non-zod schemas are converted to JSON Schema and validated before `execute` runs. It resolves ai\@7 dynamic descriptions, returns the last value of streaming (`async function*`) tools, and passes the Cogitator context as `context` / `experimental_context`. Wrap zod 3 schemas with `zodSchema()` from `ai`.

Convert multiple tools at once with `convertToolsFromAISDK`:

```typescript
const cogTools = convertToolsFromAISDK({ weather: weatherTool, search: searchTool });
```

## Full Example: Next.js Route with AI SDK 7

```typescript title="app/api/chat/route.ts"
import { convertToModelMessages, streamText, type UIMessage } from 'ai';
import { Cogitator, Agent, tool } from '@cogitator-ai/core';
import { cogitatorModel } from '@cogitator-ai/ai-sdk';
import { z } from 'zod';

const cog = new Cogitator({
  llm: {
    defaultProvider: 'anthropic',
    providers: { anthropic: { apiKey: process.env.ANTHROPIC_API_KEY! } },
  },
});

const agent = new Agent({
  name: 'assistant',
  model: 'anthropic/claude-sonnet-5-5',
  instructions: 'You are a helpful coding assistant.',
  tools: [
    tool({
      name: 'lookup_docs',
      description: 'Look up a documentation page',
      parameters: z.object({ topic: z.string() }),
      execute: async ({ topic }) => ({ url: `https://docs.example.com/${topic}` }),
    }),
  ],
});

const model = cogitatorModel(cog, agent);

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model,
    messages: await convertToModelMessages(messages),
  });

  return result.toUIMessageStreamResponse();
}
```

The agent executes `lookup_docs` itself; the UI stream shows the call and its result as provider-executed tool parts.
