### Quick Start: Basic Agent Call Source: https://github.com/openrouterteam/typescript-agent/blob/main/packages/agent/README.md Initialize the OpenRouter client and use `callModel` with a tool to get a text response. Ensure you replace 'YOUR_API_KEY' with your actual API key. ```typescript import OpenRouter from '@openrouter/sdk'; import { callModel, tool } from '@openrouter/agent'; import { z } from 'zod'; const client = new OpenRouter({ apiKey: 'YOUR_API_KEY' }); const weatherTool = tool({ name: 'get_weather', description: 'Get the current weather for a location', inputSchema: z.object({ location: z.string() }), execute: async ({ location }) => ({ temperature: 72, condition: 'sunny', location, }), }); const result = callModel(client, { model: 'openai/gpt-4o', input: 'What is the weather in San Francisco?', tools: [weatherTool] as const, }); // Get the final text response (tools are auto-executed) const text = await result.getText(); console.log(text); ``` -------------------------------- ### Development: Install Dependencies Source: https://github.com/openrouterteam/typescript-agent/blob/main/packages/agent/README.md Install project dependencies using pnpm. ```bash # Install dependencies pnpm install ``` -------------------------------- ### Running Tests: Environment Setup Source: https://github.com/openrouterteam/typescript-agent/blob/main/packages/agent/README.md Create a .env file to store your OpenRouter API key for running tests. ```env OPENROUTER_API_KEY=sk-or-... ``` -------------------------------- ### Install Workspace Dependencies with pnpm Source: https://github.com/openrouterteam/typescript-agent/blob/main/README.md Installs all dependencies for the monorepo's workspaces. ```bash pnpm install ``` -------------------------------- ### Install OpenRouter Agent SDK Source: https://github.com/openrouterteam/typescript-agent/blob/main/packages/agent/README.md Install the OpenRouter Agent SDK using npm, pnpm, bun, or yarn. This package is ESM-only. ```bash # npm npm install @openrouter/agent # pnpm pnpm add @openrouter/agent # bun bun add @openrouter/agent # yarn yarn add @openrouter/agent ``` -------------------------------- ### Tool Approval Workflow with Conditional Logic Source: https://context7.com/openrouterteam/typescript-agent/llms.txt Implements a tool execution approval workflow where certain tools require explicit user confirmation. This example shows conditional approval based on tool input and how to resume the conversation after approval. ```typescript import { callModel, tool } from '@openrouter/agent'; import { z } from 'zod'; const deleteTool = tool({ name: 'delete_record', description: 'Delete a database record', inputSchema: z.object({ id: z.string(), table: z.string() }), requireApproval: ({ table }) => table === 'users', // conditional approval execute: async ({ id, table }) => ({ deleted: true, id, table }), }); const stateAccessor = createMemoryStateAccessor(); // Initial call — execution pauses when approval is needed const result1 = callModel(client, { model: 'openai/gpt-4o', input: 'Delete user record with id "abc-123"', tools: [deleteTool] as const, state: stateAccessor, }); await result1.getText().catch(() => {}); // may not have text yet if (await result1.requiresApproval()) { const pending = await result1.getPendingToolCalls(); console.log('Pending approvals:', pending.map(tc => `${tc.name}(${JSON.stringify(tc.arguments)})`)); const approvedIds = pending.map(tc => tc.id); // approve all // Resume with approval decisions const result2 = callModel(client, { model: 'openai/gpt-4o', input: 'Delete user record with id "abc-123"', tools: [deleteTool] as const, state: stateAccessor, approveToolCalls: approvedIds, // rejectToolCalls: [pendingIds[0]] // or reject }); const finalText = await result2.getText(); console.log(finalText); } ``` -------------------------------- ### Conversation State Management Source: https://github.com/openrouterteam/typescript-agent/blob/main/packages/agent/README.md Persist multi-turn conversations with full state tracking. Use `createInitialState` to start and update the state after each `callModel` response. ```typescript import { createInitialState, callModel } from '@openrouter/agent'; // Start a conversation let state = createInitialState(); // First turn const result1 = callModel(client, { model: 'openai/gpt-4o', input: 'Search for TypeScript best practices', tools: [searchTool] as const, state, }); // State is updated with messages, tool calls, and metadata state = (await result1.getResponse()).state; // Continue the conversation const result2 = callModel(client, { model: 'openai/gpt-4o', input: 'Now summarize what you found', tools: [searchTool] as const, state, }); ``` -------------------------------- ### Discriminate Event Types in Stream Source: https://context7.com/openrouterteam/typescript-agent/llms.txt Iterate through a stream of events and use imported type guard functions to identify and handle different event types, such as turn start, tool results, or progress updates. ```typescript // Discriminate events in getFullResponsesStream for await (const event of result.getFullResponsesStream()) { if (isTurnStartEvent(event)) { console.log(`Turn ${event.turnNumber} started at ${event.timestamp}`); } else if (isToolResultEvent(event)) { console.log('Tool completed:', event.toolCallId, event.result); } else if (isToolPreliminaryResultEvent(event)) { console.log('Progress event:', event.result); } else if (isTurnEndEvent(event)) { console.log(`Turn ${event.turnNumber} ended`); } } ``` -------------------------------- ### Development: Build Project Source: https://github.com/openrouterteam/typescript-agent/blob/main/packages/agent/README.md Build the project using pnpm. ```bash # Build pnpm build ``` -------------------------------- ### Development: Run Unit Tests Source: https://github.com/openrouterteam/typescript-agent/blob/main/packages/agent/README.md Run unit tests using pnpm. ```bash # Run unit tests pnpm test ``` -------------------------------- ### Development: Lint Source: https://github.com/openrouterteam/typescript-agent/blob/main/packages/agent/README.md Run linting on the project using pnpm. ```bash # Lint pnpm lint ``` -------------------------------- ### Define Regular Tool with `tool()` Source: https://context7.com/openrouterteam/typescript-agent/llms.txt Use `tool()` to create a regular tool. Define `name`, `description`, `inputSchema`, `outputSchema`, and an `execute` function. The `requireApproval` function can conditionally enforce approval based on input. ```typescript import { tool } from '@openrouter/agent'; import { z } from 'zod'; // Regular tool const fetchTool = tool({ name: 'fetch_url', description: 'Fetch content from a URL', inputSchema: z.object({ url: z.string().url() }), outputSchema: z.object({ content: z.string(), statusCode: z.number() }), requireApproval: ({ url }) => url.includes('sensitive.com'), execute: async ({ url }) => { const res = await fetch(url); return { content: await res.text(), statusCode: res.status }; }, }); ``` -------------------------------- ### Run Unit Tests with Vitest Source: https://github.com/openrouterteam/typescript-agent/blob/main/README.md Executes unit tests for all packages using Vitest. ```bash pnpm run test ``` -------------------------------- ### Running Tests: Execution Source: https://github.com/openrouterteam/typescript-agent/blob/main/packages/agent/README.md Execute unit or integration tests using pnpm commands. ```bash pnpm test # Unit tests pnpm test:e2e # Integration tests (requires API key) ``` -------------------------------- ### Development: Run End-to-End Tests Source: https://github.com/openrouterteam/typescript-agent/blob/main/packages/agent/README.md Run end-to-end tests, which require OPENROUTER_API_KEY in .env. ```bash # Run end-to-end tests (requires OPENROUTER_API_KEY in .env) pnpm test:e2e ``` -------------------------------- ### Run Biome Linting Across Packages Source: https://github.com/openrouterteam/typescript-agent/blob/main/README.md Executes Biome lint checks across all packages in the monorepo. ```bash pnpm run lint ``` -------------------------------- ### Run End-to-End Tests with Vitest Source: https://github.com/openrouterteam/typescript-agent/blob/main/README.md Executes end-to-end tests using Vitest. Requires the OPENROUTER_API_KEY environment variable to be set. ```bash pnpm run test:e2e ``` -------------------------------- ### serverTool() — OpenRouter Server-Executed Tools Source: https://context7.com/openrouterteam/typescript-agent/llms.txt Creates tools executed server-side by OpenRouter (web search, image generation, datetime, etc.). No `execute` function is required; results appear in the response output. ```APIDOC ## `serverTool()` — OpenRouter Server-Executed Tools Creates tools executed server-side by OpenRouter (web search, image generation, datetime, etc.). No `execute` function is required; results appear in the response output. ```typescript import { callModel, serverTool } from '@openrouter/agent'; import OpenRouter from '@openrouter/sdk'; const client = new OpenRouter({ apiKey: process.env.OPENROUTER_API_KEY }); const result = callModel(client, { model: 'openai/gpt-4o', input: 'Search for the current date and recent TypeScript news, then generate an image of code.', tools: [ serverTool({ type: 'web_search_2025_08_26', engine: 'exa', maxResults: 5 }), serverTool({ type: 'openrouter:datetime', parameters: { timezone: 'UTC' } }), serverTool({ type: 'image_generation', size: '1024x1024', quality: 'standard' }), ] as const, }); // Stream all output items including server-tool results for await (const item of result.getItemsStream()) { if (item.type === 'message') { console.log('Text:', item.content); } else if (item.type === 'web_search_call') { console.log('Web search executed'); } else if (item.type === 'image_generation_call') { console.log('Image URL:', item.result); } } ``` ``` -------------------------------- ### Tool Approval with `requireApproval` Source: https://github.com/openrouterteam/typescript-agent/blob/main/packages/agent/README.md Gate tool execution with approval checks. Use `true` for always requiring approval, or a function for conditional approval based on input. ```typescript const deleteTool = tool({ name: 'delete_record', inputSchema: z.object({ id: z.string() }), requireApproval: true, // Always require approval execute: async ({ id }) => { /* ... */ }, }); // Or use a function for conditional approval const writeTool = tool({ name: 'write_file', inputSchema: z.object({ path: z.string(), content: z.string() }), requireApproval: ({ path }) => path.startsWith('/etc'), execute: async ({ path, content }) => { /* ... */ }, }); ``` ```typescript // Handle approvals at the callModel level const result = callModel(client, { model: 'openai/gpt-4o', input: 'Delete record abc-123', tools: [deleteTool] as const, approveToolCalls: async (toolCalls) => { // Return IDs of approved tool calls return toolCalls.map(tc => tc.id); }, }); ``` -------------------------------- ### Create a New Changeset for Releases Source: https://github.com/openrouterteam/typescript-agent/blob/main/README.md Initiates the Changesets process to create a new versioned change for packages. This command prompts for package selection and bump type. ```bash pnpm changeset ``` -------------------------------- ### Manage Tool Context with Schemas Source: https://context7.com/openrouterteam/typescript-agent/llms.txt Shows how to define and inject typed context data into tools using `contextSchema` and `sharedContextSchema`. The `context` parameter can be a static value, sync function, or async function, allowing for dynamic context based on turns. It also demonstrates observing context changes in real-time. ```typescript import { callModel, tool } from '@openrouter/agent'; import { z } from 'zod'; const SharedSchema = z.object({ requestId: z.string(), callCount: z.number() }); const dbTool = tool>({ name: 'query_db', inputSchema: z.object({ sql: z.string() }), contextSchema: z.object({ connectionString: z.string() }), execute: async ({ sql }, ctx) => { // ctx.local has this tool's injected context const conn = ctx?.local.connectionString; // ctx.shared has shared context across all tools const reqId = ctx?.shared.requestId; // Mutate context for subsequent turns ctx?.setContext({ connectionString: conn }); ctx?.setSharedContext({ callCount: (ctx.shared.callCount ?? 0) + 1 }); return { rows: [], requestId: reqId }; }, }); const result = callModel(client, { model: 'openai/gpt-4o', input: 'List all users ordered by name', tools: [dbTool] as const, sharedContextSchema: SharedSchema, context: (turn) => ({ query_db: { connectionString: process.env.DB_URL! }, shared: { requestId: `req-${turn.numberOfTurns}`, callCount: 0 }, }), }); // Observe context changes in real-time for await (const snapshot of result.getContextUpdates()) { console.log('Context snapshot:', snapshot); } ``` -------------------------------- ### Define a Regular Tool Source: https://github.com/openrouterteam/typescript-agent/blob/main/packages/agent/README.md Create a type-safe tool using the `tool()` factory with input and output schemas defined by Zod. Regular tools are automatically executed by the agent loop. ```typescript const searchTool = tool({ name: 'search', description: 'Search the web', inputSchema: z.object({ query: z.string() }), outputSchema: z.object({ results: z.array(z.string()) }), execute: async ({ query }) => { const results = await performSearch(query); return { results }; }, }); ``` -------------------------------- ### Define a Manual Tool Source: https://github.com/openrouterteam/typescript-agent/blob/main/packages/agent/README.md Create a manual tool by setting `execute: false`. These tools are reported to the model but not automatically executed, useful for human-in-the-loop workflows. ```typescript const confirmTool = tool({ name: 'confirm_action', inputSchema: z.object({ action: z.string() }), execute: false, }); ``` -------------------------------- ### Consume Model Responses with ModelResult Source: https://context7.com/openrouterteam/typescript-agent/llms.txt Demonstrates various methods to consume and process the results from `callModel`, including awaiting final text, retrieving full responses with usage metadata, and streaming different types of output like text deltas, reasoning tokens, items, tool streams, and full SDK events. Also shows how to retrieve completed tool calls and cancel the stream. ```typescript import { callModel, tool } from '@openrouter/agent'; import { z } from 'zod'; const result = callModel(client, { model: 'openai/gpt-4o', input: 'Analyze these numbers: [1,2,3,4,5]', tools: [analysisTool] as const, }); // Await the final text (tools auto-executed) const text = await result.getText(); // Full response with usage metadata const response = await result.getResponse(); console.log(response.usage); // { inputTokens, outputTokens, totalTokens, cost } // Stream text deltas in real-time across all turns for await (const delta of result.getTextStream()) { process.stdout.write(delta); } // Stream reasoning tokens (for models that support it) for await (const delta of result.getReasoningStream()) { process.stdout.write(delta); } // Stream all output items (messages, tool calls, tool results, server-tool outputs) for await (const item of result.getItemsStream()) { console.log(item.type, item); } // Stream tool argument deltas + generator preliminary results for await (const event of result.getToolStream()) { if (event.type === 'delta') { process.stdout.write(event.content); // streaming argument JSON } else if (event.type === 'preliminary_result') { console.log('Progress:', event.result); // generator yields } } // Get all completed tool calls (structured + typed) const toolCalls = await result.getToolCalls(); for (const tc of toolCalls) { console.log(tc.name, tc.arguments); } // Stream structured tool calls as they complete for await (const toolCall of result.getToolCallsStream()) { console.log(toolCall.name, toolCall.arguments); } // Stream all raw SDK events + tool events + turn delimiters for await (const event of result.getFullResponsesStream()) { if (event.type === 'turn.start') console.log('Turn', event.turnNumber, 'started'); if (event.type === 'turn.end') console.log('Turn', event.turnNumber, 'ended'); if (event.type === 'tool.result') console.log('Tool result:', event.result); } // Cancel the underlying stream await result.cancel(); ``` -------------------------------- ### Development: Type Check Source: https://github.com/openrouterteam/typescript-agent/blob/main/packages/agent/README.md Perform type checking on the project using pnpm. ```bash # Type check pnpm typecheck ``` -------------------------------- ### Dynamic Model Parameters with `nextTurnParams` Source: https://context7.com/openrouterteam/typescript-agent/llms.txt Enables tools to dynamically modify model parameters for subsequent turns based on tool input and context. This allows for adaptive behavior, such as adjusting temperature or changing models. ```typescript import { tool, callModel } from '@openrouter/agent'; import { z } from 'zod'; const searchTool = tool({ name: 'search', inputSchema: z.object({ query: z.string(), creative: z.boolean().optional() }), outputSchema: z.object({ results: z.array(z.string()) }), nextTurnParams: { // Raise temperature when creative mode is requested temperature: (input) => input.creative ? 0.9 : 0.1, // Always increase token budget after searching maxOutputTokens: (input, context) => Math.min((context.maxOutputTokens ?? 1000) * 2, 8000), // Switch model after the first search model: (input, context) => context.model === 'openai/gpt-4o-mini' ? 'openai/gpt-4o' : context.model, }, execute: async ({ query }) => ({ results: [`Result: ${query}`] }), }); const result = callModel(client, { model: 'openai/gpt-4o-mini', input: 'Do a creative search about AI art', tools: [searchTool] as const, }); const text = await result.getText(); ``` -------------------------------- ### Define HITL Tool with `tool()` Source: https://context7.com/openrouterteam/typescript-agent/llms.txt Human-in-the-loop (HITL) tools use `onToolCalled` to trigger hooks, optionally pausing execution for human input. Returning `null` from `onToolCalled` pauses the tool, requiring external resumption. `onResponseReceived` can process the raw result. ```typescript // HITL tool — fires a hook, optionally pauses for human input const reviewTool = tool({ name: 'request_review', inputSchema: z.object({ content: z.string(), reason: z.string() }), outputSchema: z.object({ approved: z.boolean(), feedback: z.string() }), onToolCalled: async ({ content, reason }) => { // Return null to pause and await external input, or a value to continue console.log(`Review requested for: ${reason}`); return null; // pause — caller must resume via FunctionCallOutputItem }, onResponseReceived: async (rawResult) => { const r = rawResult as { approved: boolean; feedback: string }; return { approved: r.approved, feedback: r.feedback }; }, }); ``` -------------------------------- ### ToolEventBroadcaster for Multicasting Events Source: https://context7.com/openrouterteam/typescript-agent/llms.txt A push-based broadcaster for multicasting events to multiple consumers independently. Useful for custom streaming pipelines. Consumers track their own positions. ```typescript import { ToolEventBroadcaster } from '@openrouter/agent'; type ProgressEvent = { type: 'progress'; percent: number } | { type: 'done'; result: string }; const broadcaster = new ToolEventBroadcaster(); // Create two independent consumers before events arrive const consumer1 = broadcaster.createConsumer(); const consumer2 = broadcaster.createConsumer(); // Push events asynchronously setTimeout(() => broadcaster.push({ type: 'progress', percent: 50 }), 10); setTimeout(() => broadcaster.push({ type: 'progress', percent: 100 }), 20); setTimeout(() => broadcaster.push({ type: 'done', result: 'finished' }), 30); setTimeout(() => broadcaster.complete(), 40); // Both consumers receive all events independently for await (const event of consumer1) { console.log('Consumer 1:', event); } for await (const event of consumer2) { console.log('Consumer 2:', event); } ``` -------------------------------- ### Define Generator Tool with `tool()` Source: https://context7.com/openrouterteam/typescript-agent/llms.txt Create a generator tool by providing an `eventSchema` alongside `inputSchema`, `outputSchema`, and an asynchronous generator `execute` function. This allows streaming intermediate events during execution. ```typescript // Generator tool — streams progress events const analysisTool = tool({ name: 'analyze_data', inputSchema: z.object({ data: z.array(z.number()) }), eventSchema: z.object({ progress: z.number(), stage: z.string() }), outputSchema: z.object({ mean: z.number(), stddev: z.number() }), execute: async function* ({ data }) { yield { progress: 0.3, stage: 'computing mean' }; const mean = data.reduce((a, b) => a + b, 0) / data.length; yield { progress: 0.7, stage: 'computing stddev' }; const stddev = Math.sqrt(data.reduce((s, x) => s + (x - mean) ** 2, 0) / data.length); yield { progress: 1.0, stage: 'done' }; return { mean, stddev }; }, }); ``` -------------------------------- ### ToolContextStore for Cross-Turn Context Management Source: https://context7.com/openrouterteam/typescript-agent/llms.txt A mutable key-value store keyed by tool name that persists context across conversation turns. Supports reactive updates via subscriptions. Used internally by `callModel`. ```typescript import { ToolContextStore } from '@openrouter/agent'; const store = new ToolContextStore({ my_tool: { apiKey: 'secret', callCount: 0 }, shared: { sessionId: 'sess-123' }, }); // Subscribe to changes const unsubscribe = store.subscribe((snapshot) => { console.log('Store updated:', snapshot); }); // Read context const toolCtx = store.getToolContext('my_tool'); console.log(toolCtx.callCount); // 0 // Merge partial updates store.mergeToolContext('my_tool', { callCount: 1 }); store.setToolContext('shared', { sessionId: 'sess-456' }); // Get full snapshot const snap = store.getSnapshot(); console.log(snap); // { my_tool: { apiKey: 'secret', callCount: 1 }, shared: { sessionId: 'sess-456' } } unsubscribe(); ``` -------------------------------- ### Use `serverTool()` for Server-Executed Tools Source: https://context7.com/openrouterteam/typescript-agent/llms.txt Employ `serverTool()` to define tools executed server-side by OpenRouter, such as web search or image generation. No `execute` function is needed as results are directly included in the model's response. Specify the tool `type` and relevant parameters. ```typescript import { callModel, serverTool } from '@openrouter/agent'; import OpenRouter from '@openrouter/sdk'; const client = new OpenRouter({ apiKey: process.env.OPENROUTER_API_KEY }); const result = callModel(client, { model: 'openai/gpt-4o', input: 'Search for the current date and recent TypeScript news, then generate an image of code.', tools: [ serverTool({ type: 'web_search_2025_08_26', engine: 'exa', maxResults: 5 }), serverTool({ type: 'openrouter:datetime', parameters: { timezone: 'UTC' } }), serverTool({ type: 'image_generation', size: '1024x1024', quality: 'standard' }), ] as const, }); // Stream all output items including server-tool results for await (const item of result.getItemsStream()) { if (item.type === 'message') { console.log('Text:', item.content); } else if (item.type === 'web_search_call') { console.log('Web search executed'); } else if (item.type === 'image_generation_call') { console.log('Image URL:', item.result); } } ``` -------------------------------- ### tool() — Tool Factory Source: https://context7.com/openrouterteam/typescript-agent/llms.txt Creates type-safe tools with full Zod schema inference. Automatically detects the tool type from configuration: Regular, Generator, Manual, or HITL. ```APIDOC ## `tool()` — Tool Factory Creates type-safe tools with full Zod schema inference. Automatically detects the tool type from configuration: - **Regular tool**: `execute` is a function - **Generator tool**: `eventSchema` is provided (streams intermediate events) - **Manual tool**: `execute: false` (model can call it but it is not auto-executed) - **HITL tool**: `onToolCalled` is provided (human-in-the-loop) ```typescript import { tool } from '@openrouter/agent'; import { z } from 'zod'; // Regular tool const fetchTool = tool({ name: 'fetch_url', description: 'Fetch content from a URL', inputSchema: z.object({ url: z.string().url() }), outputSchema: z.object({ content: z.string(), statusCode: z.number() }), requireApproval: ({ url }) => url.includes('sensitive.com'), execute: async ({ url }) => { const res = await fetch(url); return { content: await res.text(), statusCode: res.status }; }, }); // Generator tool — streams progress events const analysisTool = tool({ name: 'analyze_data', inputSchema: z.object({ data: z.array(z.number()) }), eventSchema: z.object({ progress: z.number(), stage: z.string() }), outputSchema: z.object({ mean: z.number(), stddev: z.number() }), execute: async function* ({ data }) { yield { progress: 0.3, stage: 'computing mean' }; const mean = data.reduce((a, b) => a + b, 0) / data.length; yield { progress: 0.7, stage: 'computing stddev' }; const stddev = Math.sqrt(data.reduce((s, x) => s + (x - mean) ** 2, 0) / data.length); yield { progress: 1.0, stage: 'done' }; return { mean, stddev }; }, }); // Manual tool — model calls it but developer handles execution externally const paymentTool = tool({ name: 'process_payment', description: 'Process a payment (requires manual confirmation)', inputSchema: z.object({ amount: z.number(), currency: z.string() }), execute: false, }); // HITL tool — fires a hook, optionally pauses for human input const reviewTool = tool({ name: 'request_review', inputSchema: z.object({ content: z.string(), reason: z.string() }), outputSchema: z.object({ approved: z.boolean(), feedback: z.string() }), onToolCalled: async ({ content, reason }) => { // Return null to pause and await external input, or a value to continue console.log(`Review requested for: ${reason}`); return null; // pause — caller must resume via FunctionCallOutputItem }, onResponseReceived: async (rawResult) => { const r = rawResult as { approved: boolean; feedback: string }; return { approved: r.approved, feedback: r.feedback }; }, }); ``` ``` -------------------------------- ### Define Agent Stop Conditions Source: https://context7.com/openrouterteam/typescript-agent/llms.txt Illustrates how to configure stop conditions for the agent loop using a combination of functions like `stepCountIs`, `maxCost`, `hasToolCall`, `maxTokensUsed`, and `finishReasonIs`. Execution halts when any of these conditions evaluate to true. ```typescript import { callModel, stepCountIs, hasToolCall, maxTokensUsed, maxCost, finishReasonIs, } from '@openrouter/agent'; const result = callModel(client, { model: 'openai/gpt-4o', input: 'Research and summarize TypeScript best practices', tools: [searchTool, summarizeTool] as const, // Stop after 10 tool steps OR when cost exceeds $0.50 // OR when the summarize tool is called OR when 50k tokens consumed stopWhen: [ stepCountIs(10), maxCost(0.50), hasToolCall('summarize'), maxTokensUsed(50_000), finishReasonIs('length'), ], }); const text = await result.getText(); ``` -------------------------------- ### `OpenRouter` class — Client with Built-in `callModel` Source: https://context7.com/openrouterteam/typescript-agent/llms.txt An extended SDK client that adds a `callModel` instance method and supports hooks for request/response interception. Accepts `SDKHooks`, a single hook, or an array of hooks. ```APIDOC ## `OpenRouter` class — Client with Built-in `callModel` An extended SDK client that adds a `callModel` instance method and supports hooks for request/response interception. Accepts `SDKHooks`, a single hook, or an array of hooks. ### Constructor ```typescript new OpenRouter(options?: OpenRouterOptions) ``` ### Parameters - **options** (`OpenRouterOptions`, optional): Configuration for the OpenRouter client. - **apiKey** (`string`): Your OpenRouter API key. - **hooks** (`SDKHooks | SDKHook | SDKHook[]`, optional): Hooks for intercepting requests and responses. - **beforeRequest** (`(ctx: RequestContext, next: NextFunction) => Promise`): Hook executed before a request is sent. - **afterSuccess** (`(ctx: ResponseContext, next: NextFunction) => Promise`): Hook executed after a successful response is received. ### Methods - **`callModel(options: CallModelOptions): ModelResult`**: Initiates a model call with the agent loop, similar to the standalone `callModel` function. ### Example ```typescript import { OpenRouter } from '@openrouter/agent'; import { tool } from '@openrouter/agent'; import { z } from 'zod'; const client = new OpenRouter({ apiKey: process.env.OPENROUTER_API_KEY, hooks: { beforeRequest: async (ctx, next) => { console.log('Request URL:', ctx.request.url); return next(ctx); }, afterSuccess: async (ctx, next) => { console.log('Request succeeded'); return next(ctx); }, }, }); const echoTool = tool({ name: 'echo', inputSchema: z.object({ message: z.string() }), execute: async ({ message }) => ({ echoed: message }), }); const result = client.callModel({ model: 'anthropic/claude-3-5-sonnet', input: 'Echo back "hello world"', tools: [echoTool] as const, }); const text = await result.getText(); console.log(text); ``` ``` -------------------------------- ### `callModel` — Core Agent Loop Source: https://context7.com/openrouterteam/typescript-agent/llms.txt Sends a request to an OpenRouter model and returns a `ModelResult` that auto-executes tools, respects stop conditions, and supports multiple concurrent response consumers. The default stop condition is `stepCountIs(5)`. ```APIDOC ## `callModel` — Core Agent Loop Sends a request to an OpenRouter model and returns a `ModelResult` that auto-executes tools, respects stop conditions, and supports multiple concurrent response consumers. The default stop condition is `stepCountIs(5)`. ### Method Signature ```typescript callModel(client: OpenRouter, options: CallModelOptions) ``` ### Parameters - **client** (`OpenRouter`): An instance of the OpenRouter client. - **options** (`CallModelOptions`): Configuration for the model call. - **model** (`string`): The identifier of the OpenRouter model to use. - **input** (`string | object`): The input prompt or data for the model. - **tools** (`readonly Tool[]`): An array of available tools for the agent to use. - **stopWhen** (`readonly StopCondition[]`, optional): Conditions to stop the agent loop. ### Returns - **`ModelResult`**: An object that provides methods to consume the model's response, such as `getText()` and `getResponse()`. ### Example ```typescript import OpenRouter from '@openrouter/sdk'; import { callModel, tool, stepCountIs, maxCost } from '@openrouter/agent'; import { z } from 'zod'; const client = new OpenRouter({ apiKey: process.env.OPENROUTER_API_KEY }); const searchTool = tool({ name: 'search', description: 'Search the web for information', inputSchema: z.object({ query: z.string() }), outputSchema: z.object({ results: z.array(z.string()) }), execute: async ({ query }) => ({ results: [`Result for: ${query}`] }), }); const result = callModel(client, { model: 'openai/gpt-4o', input: 'What is the latest news about TypeScript?', tools: [searchTool] as const, stopWhen: [stepCountIs(3), maxCost(0.10)], }); // All consumers can run concurrently on the same result const [text, response] = await Promise.all([ result.getText(), result.getResponse(), ]); console.log(text); console.log(`Tokens used: ${response.usage?.totalTokens}`); ``` ``` -------------------------------- ### Dynamic Parameters with `nextTurnParams` Source: https://github.com/openrouterteam/typescript-agent/blob/main/packages/agent/README.md Adjust model parameters dynamically based on tool execution results. The `nextTurnParams` function receives tool inputs and returns parameter overrides for the next turn. ```typescript const searchTool = tool({ name: 'search', inputSchema: z.object({ query: z.string() }), nextTurnParams: { temperature: (input) => input.query.includes('creative') ? 0.9 : 0.1, maxOutputTokens: () => 2000, }, execute: async ({ query }) => { /* ... */ }, }); ``` -------------------------------- ### Perform TypeScript Type Checking Source: https://github.com/openrouterteam/typescript-agent/blob/main/README.md Runs TypeScript's noEmit check across all packages to ensure type safety. ```bash pnpm run typecheck ``` -------------------------------- ### Convert Chat Messages to OpenRouter Input and Back Source: https://context7.com/openrouterteam/typescript-agent/llms.txt Use `fromChatMessages` to convert OpenAI Chat message history to OpenRouter's input format, and `toChatMessage` to convert the response back to OpenAI Chat compatible format. ```typescript import { fromChatMessages, toChatMessage, callModel } from '@openrouter/agent'; // Convert Chat messages to OpenRouter input const chatHistory = [ { role: 'system' as const, content: 'You are a helpful assistant.' }, { role: 'user' as const, content: 'What is 2+2?' }, { role: 'assistant' as const, content: '4' }, { role: 'user' as const, content: 'And 3+3?' }, ]; const result = callModel(client, { model: 'openai/gpt-4o', input: fromChatMessages(chatHistory), }); const response = await result.getResponse(); // Convert response to Chat format const chatMessage = toChatMessage(response); // chatMessage: { role: 'assistant', content: '6' } console.log(chatMessage.content); ``` -------------------------------- ### Format Compatibility: Chat Source: https://github.com/openrouterteam/typescript-agent/blob/main/packages/agent/README.md Convert between OpenRouter message format and standard Chat message format using `toChatMessage` and `fromChatMessages`. ```typescript import { toChatMessage, fromChatMessages } from '@openrouter/agent'; // Standard Chat format const chatMsg = toChatMessage(openRouterMessage); const orMessages2 = fromChatMessages(chatMessages); ``` -------------------------------- ### Client with `callModel` Instance Method and Hooks Source: https://context7.com/openrouterteam/typescript-agent/llms.txt Extend the OpenRouter SDK client with a `callModel` instance method and hooks for request/response interception. This allows for pre-request logging or post-success actions. ```typescript import { OpenRouter } from '@openrouter/agent'; import { tool } from '@openrouter/agent'; import { z } from 'zod'; const client = new OpenRouter({ apiKey: process.env.OPENROUTER_API_KEY, hooks: { beforeRequest: async (ctx, next) => { console.log('Request URL:', ctx.request.url); return next(ctx); }, afterSuccess: async (ctx, next) => { console.log('Request succeeded'); return next(ctx); }, }, }); const echoTool = tool({ name: 'echo', inputSchema: z.object({ message: z.string() }), execute: async ({ message }) => ({ echoed: message }), }); const result = client.callModel({ model: 'anthropic/claude-3-5-sonnet', input: 'Echo back "hello world"', tools: [echoTool] as const, }); const text = await result.getText(); console.log(text); ``` -------------------------------- ### In-Memory Conversation State Management Source: https://context7.com/openrouterteam/typescript-agent/llms.txt Demonstrates persisting conversation state using a simple in-memory StateAccessor. This is useful for maintaining message history and conversation status across multiple turns. ```typescript import { callModel, createInitialState, tool } from '@openrouter/agent'; import type { ConversationState, StateAccessor } from '@openrouter/agent'; import { z } from 'zod'; // Simple in-memory state accessor function createMemoryStateAccessor(): StateAccessor { let state: ConversationState | null = null; return { load: async () => state, save: async (s) => { state = s; }, }; } const stateAccessor = createMemoryStateAccessor(); const searchTool = tool({ name: 'search', inputSchema: z.object({ query: z.string() }), execute: async ({ query }) => ({ result: `Found: ${query}` }), }); // First turn const result1 = callModel(client, { model: 'openai/gpt-4o', input: 'Search for TypeScript generics', tools: [searchTool] as const, state: stateAccessor, }); const response1 = await result1.getResponse(); console.log('Status:', (await result1.getState()).status); // 'complete' // Second turn — state is automatically loaded and message history continued const result2 = callModel(client, { model: 'openai/gpt-4o', input: 'Now summarize what you found', tools: [searchTool] as const, state: stateAccessor, }); const text2 = await result2.getText(); console.log(text2); ``` -------------------------------- ### Core Agent Loop with `callModel` Source: https://context7.com/openrouterteam/typescript-agent/llms.txt Use `callModel` to send a request to an OpenRouter model, automatically executing tools, respecting stop conditions, and supporting concurrent response consumers. Define typed tools and pass them to `callModel`. The default stop condition is `stepCountIs(5)`. ```typescript import OpenRouter from '@openrouter/sdk'; import { callModel, tool, stepCountIs, maxCost } from '@openrouter/agent'; import { z } from 'zod'; const client = new OpenRouter({ apiKey: process.env.OPENROUTER_API_KEY }); const searchTool = tool({ name: 'search', description: 'Search the web for information', inputSchema: z.object({ query: z.string() }), outputSchema: z.object({ results: z.array(z.string()) }), execute: async ({ query }) => ({ results: [`Result for: ${query}`] }), }); const result = callModel(client, { model: 'openai/gpt-4o', input: 'What is the latest news about TypeScript?', tools: [searchTool] as const, stopWhen: [stepCountIs(3), maxCost(0.10)], }); // All consumers can run concurrently on the same result const [text, response] = await Promise.all([ result.getText(), result.getResponse(), ]); console.log(text); console.log(`Tokens used: ${response.usage?.totalTokens}`); ``` -------------------------------- ### Define a Generator Tool Source: https://github.com/openrouterteam/typescript-agent/blob/main/packages/agent/README.md Define a generator tool that streams intermediate events during execution using `yield`. It requires an `eventSchema` in addition to input and output schemas. ```typescript const analysisTool = tool({ name: 'analyze', inputSchema: z.object({ data: z.string() }), eventSchema: z.object({ progress: z.number() }), outputSchema: z.object({ summary: z.string() }), execute: async function* ({ data }) { yield { progress: 0.5 }; // ... processing ... return { summary: 'Analysis complete' }; }, }); ``` -------------------------------- ### Tool Context with `contextSchema` Source: https://github.com/openrouterteam/typescript-agent/blob/main/packages/agent/README.md Provide typed context data to tools without passing it through the model. The context is available in the `ctx` argument of the `execute` function. ```typescript const dbTool = tool({ name: 'query_db', inputSchema: z.object({ sql: z.string() }), contextSchema: z.object({ connectionString: z.string() }), execute: async ({ sql }, ctx) => { const db = connect(ctx?.context.connectionString); return db.query(sql); }, }); const result = callModel(client, { model: 'openai/gpt-4o', input: 'List all users', tools: [dbTool] as const, context: { query_db: { connectionString: 'postgres://localhost/mydb' }, }, }); ``` -------------------------------- ### Build All Packages with tsc Source: https://github.com/openrouterteam/typescript-agent/blob/main/README.md Compiles all TypeScript packages within the monorepo using the TypeScript compiler (tsc). ```bash pnpm run build ``` -------------------------------- ### Define Manual Tool with `tool()` Source: https://context7.com/openrouterteam/typescript-agent/llms.txt A manual tool is defined with `execute: false`. The model can call it, but the developer must handle the execution logic externally. This is useful for operations requiring explicit confirmation or custom handling. ```typescript // Manual tool — model calls it but developer handles execution externally const paymentTool = tool({ name: 'process_payment', description: 'Process a payment (requires manual confirmation)', inputSchema: z.object({ amount: z.number(), currency: z.string() }), execute: false, }); ``` -------------------------------- ### Convert Claude Messages to OpenRouter Input and Back Source: https://context7.com/openrouterteam/typescript-agent/llms.txt Use `fromClaudeMessages` to convert Anthropic Claude message history to OpenRouter's input format, and `toClaudeMessage` to convert the response back to Anthropic SDK compatible format. Handles text, images, tool use, and tool results. ```typescript import { fromClaudeMessages, toClaudeMessage, callModel } from '@openrouter/agent'; import type { ClaudeMessageParam } from '@openrouter/agent'; // Convert Claude messages to OpenRouter input const claudeHistory: ClaudeMessageParam[] = [ { role: 'user', content: 'Hello!' }, { role: 'assistant', content: 'Hi! How can I help?' }, { role: 'user', content: [ { type: 'text', text: 'Describe this image:' }, { type: 'image', source: { type: 'url', url: 'https://example.com/img.png' } }, ], }, ]; const result = callModel(client, { model: 'anthropic/claude-3-5-sonnet', input: fromClaudeMessages(claudeHistory), }); const response = await result.getResponse(); // Convert back to Claude format const claudeMessage = toClaudeMessage(response); // claudeMessage is now Anthropic SDK compatible: { role: 'assistant', content: [...] } console.log(claudeMessage.content); ``` -------------------------------- ### Format Compatibility: Claude Source: https://github.com/openrouterteam/typescript-agent/blob/main/packages/agent/README.md Convert between OpenRouter message format and Anthropic Claude message format using `toClaudeMessage` and `fromClaudeMessages`. ```typescript import { toClaudeMessage, fromClaudeMessages } from '@openrouter/agent'; // Anthropic Claude format const claudeMsg = toClaudeMessage(openRouterMessage); const orMessages = fromClaudeMessages(claudeMessages); ``` -------------------------------- ### Infer Tool Input and Output Types Source: https://context7.com/openrouterteam/typescript-agent/llms.txt Leverage TypeScript utility types to automatically infer the input and output shapes of your tools. This enhances type safety and reduces boilerplate code. ```typescript // TypeScript type inference from tool definitions type SearchInput = InferToolInput; // { query: string } type SearchOutput = InferToolOutput; // { results: string[] } ``` -------------------------------- ### Conversation State Management Helpers Source: https://context7.com/openrouterteam/typescript-agent/llms.txt Low-level utilities for managing `ConversationState` objects, including creating initial states, updating them immutably, appending messages, and generating unique conversation IDs. Used internally by `ModelResult`. ```typescript import { createInitialState, updateState, appendToMessages, generateConversationId, } from '@openrouter/agent'; // Create a fresh conversation state const state = createInitialState('my-conversation-id'); // { id: 'my-conversation-id', messages: [], status: 'in_progress', createdAt: ..., updatedAt: ... } // Update state immutably (updatedAt is auto-set) const updated = updateState(state, { status: 'complete' }); // Append messages const withMessages = appendToMessages(state.messages, [ { role: 'user', content: 'Hello!' }, ]); // Generate a unique conversation ID const id = generateConversationId(); // 'conv_a1b2c3d4-e5f6-...' or 'conv_1702123456789_abc123' ``` -------------------------------- ### Subpath Exports for Targeted Imports Source: https://github.com/openrouterteam/typescript-agent/blob/main/packages/agent/README.md Import specific functions and types directly from subpaths for better tree-shaking and modularity. ```typescript import { callModel } from '@openrouter/agent/call-model'; import { tool } from '@openrouter/agent/tool'; import { ModelResult } from '@openrouter/agent/model-result'; import { stepCountIs, maxCost } from '@openrouter/agent/stop-conditions'; import { toClaudeMessage } from '@openrouter/agent/anthropic-compat'; import { toChatMessage } from '@openrouter/agent/chat-compat'; import { ToolContextStore } from '@openrouter/agent/tool-context'; import { ToolEventBroadcaster } from '@openrouter/agent/tool-event-broadcaster'; import { createInitialState } from '@openrouter/agent/conversation-state'; ``` -------------------------------- ### Discriminate Tool Types at Runtime Source: https://context7.com/openrouterteam/typescript-agent/llms.txt Use imported type guard functions to check the specific type of a tool during runtime. This is useful when iterating over a collection of tools with varying capabilities. ```typescript import { isServerTool, isClientTool, isGeneratorTool, isHITLTool, isManualTool, hasExecuteFunction, isToolResultEvent, isTurnStartEvent, isTurnEndEvent, isToolPreliminaryResultEvent, } from '@openrouter/agent'; import type { InferToolInput, InferToolOutput, InferToolEvent } from '@openrouter/agent'; // Discriminate tool types at runtime const tools = [searchTool, serverTool({ type: 'web_search_2025_08_26' })]; for (const t of tools) { if (isServerTool(t)) { console.log('Server tool:', t.config.type); } else if (isGeneratorTool(t)) { console.log('Generator tool with events'); } else if (isHITLTool(t)) { console.log('HITL tool'); } else if (hasExecuteFunction(t)) { console.log('Regular tool:', t.function.name); } } ```