# Openrouter Agent Migration

> Migration guide from @openrouter/sdk to @openrouter/agent for callModel, tool(), stop conditions, and agent features. This skill should be used when code imports callModel, tool(), or stop conditions from @openrouter/sdk and needs to migrate to @openrouter/agent.

- Skill: `solizardking/openrouter-agent-migration` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds add solizardking/openrouter-agent-migration`
- Raw SKILL.md: https://api.skillmd.com/api/skills/solizardking/openrouter-agent-migration/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: Solizardking (https://skillmd.com/u/solizardking)
- Updated: 2026-09-09
- Page: https://skillmd.com/skills/solizardking/openrouter-agent-migration

---


# Migrating from @openrouter/sdk to @openrouter/agent

Agent functionality (`callModel`, `tool()`, stop conditions, format converters, streaming helpers) has moved from `@openrouter/sdk` to the standalone `@openrouter/agent` package. The `@openrouter/agent` package includes its own `OpenRouter` client class, so you do not need `@openrouter/sdk` for agent use cases.

---

## When This Applies

Migrate if your code imports any of these from `@openrouter/sdk`:

- `callModel` or uses `client.callModel()`
- `tool()` factory function
- Stop conditions: `stepCountIs`, `hasToolCall`, `maxCost`, `maxTokensUsed`, `finishReasonIs`
- Format converters: `fromClawdMessages`, `toClawdMessage`, `fromChatMessages`, `toChatMessage`
- Types: `Tool`, `ToolWithExecute`, `ToolWithGenerator`, `ManualTool`, `CallModelInput`, `ModelResult`

---

## Quick Migration

### Step 1: Install

```bash
npm install @openrouter/agent
```

If you only use agent features, you can remove `@openrouter/sdk`:

```bash
npm uninstall @openrouter/sdk
npm install @openrouter/agent
```

If you also use non-agent SDK features (models list, chat completions, credits, OAuth, API keys), keep both packages installed.

### Step 2: Update Imports

The `OpenRouter` client class and `client.callModel()` pattern work identically. Only the import source changes:

```diff
- import OpenRouter from '@openrouter/sdk';
+ import { OpenRouter } from '@openrouter/agent';
```

The rest of your code stays the same:

```typescript
const client = new OpenRouter({ apiKey: process.env.OPENROUTER_API_KEY });

const result = client.callModel({
  model: 'openai/gpt-5-nano',
  input: 'Hello!',
});

const text = await result.getText();
```

---

## Complete Import Mapping

### Client & callModel

| Old | New |
|-----|-----|
| `import OpenRouter from '@openrouter/sdk'` | `import { OpenRouter } from '@openrouter/agent'` |
| `import OpenRouter, { tool, stepCountIs } from '@openrouter/sdk'` | `import { OpenRouter } from '@openrouter/agent'`<br>`import { tool } from '@openrouter/agent/tool'`<br>`import { stepCountIs } from '@openrouter/agent/stop-conditions'` |

A standalone `callModel` function is also available for advanced use cases where a pre-existing `OpenRouterCore` instance is available:

```typescript
import { callModel } from '@openrouter/agent/call-model';

// Requires an OpenRouterCore instance (from @openrouter/sdk/core)
const result = callModel(coreInstance, { model: 'openai/gpt-5-nano', input: 'Hello' });
```

For most use cases, prefer the `client.callModel()` method shown above.

### Tool Creation

| Old | New |
|-----|-----|
| `import { tool } from '@openrouter/sdk'` | `import { tool } from '@openrouter/agent/tool'` |

### Stop Conditions

| Old | New |
|-----|-----|
| `import { stepCountIs, hasToolCall, maxCost } from '@openrouter/sdk'` | `import { stepCountIs, hasToolCall, maxCost } from '@openrouter/agent/stop-conditions'` |
| `import { maxTokensUsed, finishReasonIs } from '@openrouter/sdk'` | `import { maxTokensUsed, finishReasonIs } from '@openrouter/agent/stop-conditions'` |

### Types

| Old | New |
|-----|-----|
| `import type { Tool, ToolWithExecute, ToolWithGenerator, ManualTool } from '@openrouter/sdk/lib/tool-types'` | `import type { Tool, ToolWithExecute, ToolWithGenerator, ManualTool } from '@openrouter/agent/tool-types'` |
| `import type { CallModelInput } from '@openrouter/sdk/lib/async-params'` | `import type { CallModelInput } from '@openrouter/agent/async-params'` |
| `import { ModelResult } from '@openrouter/sdk/lib/model-result'` | `import { ModelResult } from '@openrouter/agent/model-result'` |

### Format Converters

| Old | New |
|-----|-----|
| `import { fromClawdMessages, toClawdMessage } from '@openrouter/sdk'` | `import { fromClawdMessages, toClawdMessage } from '@openrouter/agent'` |
| `import { fromChatMessages, toChatMessage } from '@openrouter/sdk'` | `import { fromChatMessages, toChatMessage } from '@openrouter/agent'` |

### Type Guards

| Old | New |
|-----|-----|
| `import { hasExecuteFunction, isGeneratorTool, isRegularExecuteTool } from '@openrouter/sdk'` | `import { hasExecuteFunction, isGeneratorTool, isRegularExecuteTool } from '@openrouter/agent/tool-types'` |

---

## Before & After Example

### Before (using @openrouter/sdk)

```typescript
import OpenRouter, { tool, stepCountIs, hasToolCall } from '@openrouter/sdk';
import { z } from 'zod';

const client = new OpenRouter({
  apiKey: process.env.OPENROUTER_API_KEY,
});

const searchTool = tool({
  name: 'web_search',
  description: 'Search the web',
  inputSchema: z.object({ query: z.string() }),
  outputSchema: z.object({ results: z.array(z.string()) }),
  execute: async ({ query }) => {
    return { results: ['Result 1', 'Result 2'] };
  },
});

const finishTool = tool({
  name: 'finish',
  description: 'Complete the task',
  inputSchema: z.object({ answer: z.string() }),
  execute: async ({ answer }) => ({ answer }),
});

const result = client.callModel({
  model: 'openai/gpt-5-nano',
  instructions: 'You are a research assistant.',
  input: 'What are the latest AI developments?',
  tools: [searchTool, finishTool],
  stopWhen: [stepCountIs(10), hasToolCall('finish')],
});

const text = await result.getText();
```

### After (using @openrouter/agent)

```typescript
import { OpenRouter } from '@openrouter/agent';
import { tool } from '@openrouter/agent/tool';
import { stepCountIs, hasToolCall } from '@openrouter/agent/stop-conditions';
import { z } from 'zod';

const client = new OpenRouter({
  apiKey: process.env.OPENROUTER_API_KEY,
});

const searchTool = tool({
  name: 'web_search',
  description: 'Search the web',
  inputSchema: z.object({ query: z.string() }),
  outputSchema: z.object({ results: z.array(z.string()) }),
  execute: async ({ query }) => {
    return { results: ['Result 1', 'Result 2'] };
  },
});

const finishTool = tool({
  name: 'finish',
  description: 'Complete the task',
  inputSchema: z.object({ answer: z.string() }),
  execute: async ({ answer }) => ({ answer }),
});

const result = client.callModel({
  model: 'openai/gpt-5-nano',
  instructions: 'You are a research assistant.',
  input: 'What are the latest AI developments?',
  tools: [searchTool, finishTool],
  stopWhen: [stepCountIs(10), hasToolCall('finish')],
});

const text = await result.getText();
```

The only changes are the three import lines at the top.

---

## When to Keep @openrouter/sdk

Keep `@openrouter/sdk` installed if you use any of these non-agent features:

| Feature | Access |
|---------|--------|
| Model listing | `client.models.list()` |
| Chat completions | `client.chat.send()` |
| Legacy completions | `client.completions.generate()` |
| Usage analytics | `client.analytics.getUserActivity()` |
| Credit balance | `client.credits.getCredits()` |
| API key management | `client.apiKeys.list()`, `.create()`, etc. |
| OAuth PKCE flow | `client.oAuth.createAuthCode()`, `.exchangeAuthCodeForAPIKey()` |

For mixed projects, use `@openrouter/sdk` for these features and `@openrouter/agent` for agent features:

```typescript
import OpenRouter from '@openrouter/sdk';               // SDK client for models, credits, etc.
import { OpenRouter as Agent } from '@openrouter/agent'; // Agent client for callModel
import { tool } from '@openrouter/agent/tool';
import { stepCountIs } from '@openrouter/agent/stop-conditions';

// Use SDK client for non-agent features
const sdkClient = new OpenRouter({ apiKey: process.env.OPENROUTER_API_KEY });
const models = await sdkClient.models.list();
const credits = await sdkClient.credits.getCredits();

// Use Agent client for callModel
const agent = new Agent({ apiKey: process.env.OPENROUTER_API_KEY });
const result = agent.callModel({
  model: 'openai/gpt-5-nano',
  input: 'Hello!',
  tools: [myTool],
  stopWhen: stepCountIs(5),
});
```

---

## New Features in @openrouter/agent

These features are only available in `@openrouter/agent`, not in `@openrouter/sdk`:

### Shared Context Schema

Type-safe shared state across all tools in a conversation:

```typescript
import { OpenRouter } from '@openrouter/agent';
import { z } from 'zod';

const client = new OpenRouter({ apiKey: '...' });

const result = client.callModel({
  model: 'openai/gpt-5-nano',
  input: 'Process this data',
  sharedContextSchema: z.object({
    userId: z.string(),
    sessionData: z.record(z.unknown()),
  }),
  context: {
    shared: { userId: '123', sessionData: {} },
  },
  tools: [myTool],
});
```

### Tool Context

Tools can declare their own typed context and access shared context:

```typescript
import { tool } from '@openrouter/agent/tool';
import { z } from 'zod';

const myTool = tool({
  name: 'my_tool',
  description: 'A tool with context',
  inputSchema: z.object({ query: z.string() }),
  contextSchema: z.object({ apiKey: z.string() }),
  execute: async (params, context) => {
    // context.local — this tool's own context
    // context.shared — shared context across all tools
    // context.setContext({ ... }) — update this tool's context
    // context.setSharedContext({ ... }) — update shared context
    return { result: 'done' };
  },
});
```

### Tool Approval Flow

Require user approval before tool execution:

```typescript
const dangerousTool = tool({
  name: 'delete_file',
  description: 'Delete a file',
  inputSchema: z.object({ path: z.string() }),
  requireApproval: true, // or a function: (toolCall, context) => boolean
  execute: async ({ path }) => { /* ... */ },
});
```

### Turn Lifecycle Callbacks

```typescript
const result = client.callModel({
  model: 'openai/gpt-5-nano',
  input: 'Complex task',
  tools: [myTool],
  onTurnStart: async (context) => {
    console.log(`Starting turn ${context.numberOfTurns}`);
  },
  onTurnEnd: async (context, response) => {
    console.log(`Turn ${context.numberOfTurns} complete`);
  },
});
```

---

## All Subpath Exports

`@openrouter/agent` provides granular subpath imports:

| Subpath | Exports |
|---------|---------|
| `@openrouter/agent` | Barrel: all exports below |
| `@openrouter/agent/client` | `OpenRouter` class |
| `@openrouter/agent/call-model` | `callModel` standalone function |
| `@openrouter/agent/tool` | `tool()` factory function |
| `@openrouter/agent/tool-types` | `Tool`, `ToolWithExecute`, `ToolWithGenerator`, `ManualTool`, type guards |
| `@openrouter/agent/stop-conditions` | `stepCountIs`, `hasToolCall`, `maxCost`, `maxTokensUsed`, `finishReasonIs` |
| `@openrouter/agent/model-result` | `ModelResult` response wrapper |
| `@openrouter/agent/async-params` | `CallModelInput`, `hasAsyncFunctions`, `resolveAsyncFunctions` |
| `@openrouter/agent/anthropic-compat` | `fromClawdMessages`, `toClawdMessage` |
| `@openrouter/agent/chat-compat` | `fromChatMessages`, `toChatMessage` |
| `@openrouter/agent/conversation-state` | `createInitialState`, `updateState`, `appendToMessages` |
| `@openrouter/agent/next-turn-params` | `nextTurnParams` utilities |
| `@openrouter/agent/stream-transformers` | `extractUnsupportedContent`, `getUnsupportedContentSummary` |
| `@openrouter/agent/tool-context` | `buildToolExecuteContext`, `ToolContextStore` |
| `@openrouter/agent/tool-event-broadcaster` | `ToolEventBroadcaster` |
| `@openrouter/agent/turn-context` | `buildTurnContext` |


