Smithery Logo
MCPsSkillsDocsPricing
Login
Smithery Logo

Accelerating the Agent Economy

Resources

DocumentationPrivacy PolicySystem Status

Company

PricingAboutBlog

Connect

© 2026 Smithery. All rights reserved.

    openrouterteam

    create-agent

    openrouterteam/create-agent
    Productivity
    7
    1 installs

    About

    SKILL.md

    Install

    Install via Skills CLI

    or add to your agent
    • Claude Code
      Claude Code
    • Codex
      Codex
    • OpenClaw
      OpenClaw
    • Cursor
      Cursor
    • Amp
      Amp
    • GitHub Copilot
      GitHub Copilot
    • Gemini CLI
      Gemini CLI
    • Kilo Code
      Kilo Code
    • Junie
      Junie
    • Replit
      Replit
    • Windsurf
      Windsurf
    • Cline
      Cline
    • Continue
      Continue
    • OpenCode
      OpenCode
    • OpenHands
      OpenHands
    • Roo Code
      Roo Code
    • Augment
      Augment
    • Goose
      Goose
    • Trae
      Trae
    • Zencoder
      Zencoder
    • Antigravity
      Antigravity
    ├─
    ├─
    └─

    About

    Bootstrap a modular AI agent with OpenRouter SDK, extensible hooks, and optional Ink TUI

    SKILL.md

    Build a Modular AI Agent with OpenRouter

    This skill helps you create a modular AI agent with:

    • Standalone Agent Core - Runs independently, extensible via hooks
    • OpenRouter SDK - Unified access to 300+ language models
    • Optional Ink TUI - Beautiful terminal UI (separate from agent logic)

    Architecture

    ┌─────────────────────────────────────────────────────┐
    │                    Your Application                 │
    ├─────────────────────────────────────────────────────┤
    │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐  │
    │  │   Ink TUI   │  │  HTTP API   │  │   Discord   │  │
    │  └──────┬──────┘  └──────┬──────┘  └──────┬──────┘  │
    │         │                │                │         │
    │         └────────────────┼────────────────┘         │
    │                          ▼                          │
    │              ┌───────────────────────┐              │
    │              │      Agent Core       │              │
    │              │  (hooks & lifecycle)  │              │
    │              └───────────┬───────────┘              │
    │                          ▼                          │
    │              ┌───────────────────────┐              │
    │              │    OpenRouter SDK     │              │
    │              └───────────────────────┘              │
    └─────────────────────────────────────────────────────┘
    

    Prerequisites

    Get an OpenRouter API key at: https://openrouter.ai/settings/keys

    ⚠️ Security: Never commit API keys. Use environment variables.

    Project Setup

    Step 1: Initialize Project

    mkdir my-agent && cd my-agent
    npm init -y
    npm pkg set type="module"
    

    Step 2: Install Dependencies

    npm install @openrouter/sdk zod eventemitter3
    npm install ink react  # Optional: only for TUI
    npm install -D typescript @types/react tsx
    

    Step 3: Create tsconfig.json

    {
      "compilerOptions": {
        "target": "ES2022",
        "module": "NodeNext",
        "moduleResolution": "NodeNext",
        "jsx": "react-jsx",
        "strict": true,
        "esModuleInterop": true,
        "skipLibCheck": true,
        "outDir": "dist"
      },
      "include": ["src"]
    }
    

    Step 4: Add Scripts to package.json

    {
      "scripts": {
        "start": "tsx src/cli.tsx",
        "start:headless": "tsx src/headless.ts",
        "dev": "tsx watch src/cli.tsx"
      }
    }
    

    File Structure

    src/
    ├── agent.ts        # Standalone agent core with hooks
    ├── tools.ts        # Tool definitions
    ├── cli.tsx         # Ink TUI (optional interface)
    └── headless.ts     # Headless usage example
    

    Step 1: Agent Core with Hooks

    Create src/agent.ts - the standalone agent that can run anywhere:

    import { OpenRouter, tool, stepCountIs } from '@openrouter/sdk';
    import type { Tool, StopCondition, StreamableOutputItem } from '@openrouter/sdk';
    import { EventEmitter } from 'eventemitter3';
    import { z } from 'zod';
    
    // Message types
    export interface Message {
      role: 'user' | 'assistant' | 'system';
      content: string;
    }
    
    // Agent events for hooks (items-based streaming model)
    export interface AgentEvents {
      'message:user': (message: Message) => void;
      'message:assistant': (message: Message) => void;
      'item:update': (item: StreamableOutputItem) => void;  // Items emitted with same ID, replace by ID
      'stream:start': () => void;
      'stream:delta': (delta: string, accumulated: string) => void;
      'stream:end': (fullText: string) => void;
      'tool:call': (name: string, args: unknown) => void;
      'tool:result': (name: string, result: unknown) => void;
      'reasoning:update': (text: string) => void;  // Extended thinking content
      'error': (error: Error) => void;
      'thinking:start': () => void;
      'thinking:end': () => void;
    }
    
    
    // Agent configuration
    export interface AgentConfig {
      apiKey: string;
      model?: string;
      instructions?: string;
      tools?: Tool<z.ZodTypeAny, z.ZodTypeAny>[];
      maxSteps?: number;
    }
    
    // The Agent class - runs independently of any UI
    export class Agent extends EventEmitter<AgentEvents> {
      private client: OpenRouter;
      private messages: Message[] = [];
      private config: Required<Omit<AgentConfig, 'apiKey'>> & { apiKey: string };
    
      constructor(config: AgentConfig) {
        super();
        this.client = new OpenRouter({ apiKey: config.apiKey });
        this.config = {
          apiKey: config.apiKey,
          model: config.model ?? 'openrouter/auto',
          instructions: config.instructions ?? 'You are a helpful assistant.',
          tools: config.tools ?? [],
          maxSteps: config.maxSteps ?? 5,
        };
      }
    
      // Get conversation history
      getMessages(): Message[] {
        return [...this.messages];
      }
    
      // Clear conversation
      clearHistory(): void {
        this.messages = [];
      }
    
      // Add a system message
      setInstructions(instructions: string): void {
        this.config.instructions = instructions;
      }
    
      // Register additional tools at runtime
      addTool(newTool: Tool<z.ZodTypeAny, z.ZodTypeAny>): void {
        this.config.tools.push(newTool);
      }
    
      // Send a message and get streaming response using items-based model
      // Items are emitted multiple times with the same ID but progressively updated content
      // Replace items by their ID rather than accumulating chunks
      async send(content: string): Promise<string> {
        const userMessage: Message = { role: 'user', content };
        this.messages.push(userMessage);
        this.emit('message:user', userMessage);
        this.emit('thinking:start');
    
        try {
          const result = this.client.callModel({
            model: this.config.model,
            instructions: this.config.instructions,
            input: this.messages.map((m) => ({ role: m.role, content: m.content })),
            tools: this.config.tools.length > 0 ? this.config.tools : undefined,
            stopWhen: [stepCountIs(this.config.maxSteps)],
          });
    
          this.emit('stream:start');
          let fullText = '';
    
          // Use getItemsStream() for items-based streaming (recommended)
          // Each item emission is complete - replace by ID, don't accumulate
          for await (const item of result.getItemsStream()) {
            // Emit the item for UI state management (use Map keyed by item.id)
            this.emit('item:update', item);
    
            switch (item.type) {
              case 'message':
                // Message items contain progressively updated content
                const textContent = item.content?.find((c: { type: string }) => c.type === 'output_text');
                if (textContent && 'text' in textContent) {
                  const newText = textContent.text;
                  if (newText !== fullText) {
                    const delta = newText.slice(fullText.length);
                    fullText = newText;
                    this.emit('stream:delta', delta, fullText);
                  }
                }
                break;
              case 'function_call':
                // Function call arguments stream progressively
                if (item.status === 'completed') {
                  this.emit('tool:call', item.name, JSON.parse(item.arguments || '{}'));
                }
                break;
              case 'function_call_output':
                this.emit('tool:result', item.callId, item.output);
                break;
              case 'reasoning':
                // Extended thinking/reasoning content
                const reasoningText = item.content?.find((c: { type: string }) => c.type === 'reasoning_text');
                if (reasoningText && 'text' in reasoningText) {
                  this.emit('reasoning:update', reasoningText.text);
                }
                break;
              // Additional item types: web_search_call, file_search_call, image_generation_call
            }
          }
    
          // Get final text if streaming didn't capture it
          if (!fullText) {
            fullText = await result.getText();
          }
    
          this.emit('stream:end', fullText);
    
          const assistantMessage: Message = { role: 'assistant', content: fullText };
          this.messages.push(assistantMessage);
          this.emit('message:assistant', assistantMessage);
    
          return fullText;
        } catch (err) {
          const error = err instanceof Error ? err : new Error(String(err));
          this.emit('error', error);
          throw error;
        } finally {
          this.emit('thinking:end');
        }
      }
    
      // Send without streaming (simpler for programmatic use)
      async sendSync(content: string): Promise<string> {
        const userMessage: Message = { role: 'user', content };
        this.messages.push(userMessage);
        this.emit('message:user', userMessage);
    
        try {
          const result = this.client.callModel({
            model: this.config.model,
            instructions: this.config.instructions,
            input: this.messages.map((m) => ({ role: m.role, content: m.content })),
            tools: this.config.tools.length > 0 ? this.config.tools : undefined,
            stopWhen: [stepCountIs(this.config.maxSteps)],
          });
    
          const fullText = await result.getText();
          const assistantMessage: Message = { role: 'assistant', content: fullText };
          this.messages.push(assistantMessage);
          this.emit('message:assistant', assistantMessage);
    
          return fullText;
        } catch (err) {
          const error = err instanceof Error ? err : new Error(String(err));
          this.emit('error', error);
          throw error;
        }
      }
    }
    
    // Factory function for easy creation
    export function createAgent(config: AgentConfig): Agent {
      return new Agent(config);
    }
    

    Step 2: Define Tools

    Create src/tools.ts:

    import { tool } from '@openrouter/sdk';
    import { z } from 'zod';
    
    export const timeTool = tool({
      name: 'get_current_time',
      description: 'Get the current date and time',
      inputSchema: z.object({
        timezone: z.string().optional().describe('Timezone (e.g., "UTC", "America/New_York")'),
      }),
      execute: async ({ timezone }) => {
        return {
          time: new Date().toLocaleString('en-US', { timeZone: timezone || 'UTC' }),
          timezone: timezone || 'UTC',
        };
      },
    });
    
    export const calculatorTool = tool({
      name: 'calculate',
      description: 'Perform mathematical calculations',
      inputSchema: z.object({
        expression: z.string().describe('Math expression (e.g., "2 + 2", "sqrt(16)")'),
      }),
      execute: async ({ expression }) => {
        // Simple safe eval for basic math
        const sanitized = expression.replace(/[^0-9+\-*/().\s]/g, '');
        const result = Function(`"use strict"; return (${sanitized})`)();
        return { expression, result };
      },
    });
    
    export const defaultTools = [timeTool, calculatorTool];
    

    Step 3: Headless Usage (No UI)

    Create src/headless.ts - use the agent programmatically:

    import { createAgent } from './agent.js';
    import { defaultTools } from './tools.js';
    
    async function main() {
      const agent = createAgent({
        apiKey: process.env.OPENROUTER_API_KEY!,
        model: 'openrouter/auto',
        instructions: 'You are a helpful assistant with access to tools.',
        tools: defaultTools,
      });
    
      // Hook into events
      agent.on('thinking:start', () => console.log('\n🤔 Thinking...'));
      agent.on('tool:call', (name, args) => console.log(`🔧 Using ${name}:`, args));
      agent.on('stream:delta', (delta) => process.stdout.write(delta));
      agent.on('stream:end', () => console.log('\n'));
      agent.on('error', (err) => console.error('❌ Error:', err.message));
    
      // Interactive loop
      const readline = await import('readline');
      const rl = readline.createInterface({
        input: process.stdin,
        output: process.stdout,
      });
    
      console.log('Agent ready. Type your message (Ctrl+C to exit):\n');
    
      const prompt = () => {
        rl.question('You: ', async (input) => {
          if (!input.trim()) {
            prompt();
            return;
          }
          await agent.send(input);
          prompt();
        });
      };
    
      prompt();
    }
    
    main().catch(console.error);
    

    Run headless: OPENROUTER_API_KEY=sk-or-... npm run start:headless

    Step 4: Ink TUI (Optional Interface)

    Create src/cli.tsx - a beautiful terminal UI that uses the agent with items-based streaming:

    import React, { useState, useEffect, useCallback } from 'react';
    import { render, Box, Text, useInput, useApp } from 'ink';
    import type { StreamableOutputItem } from '@openrouter/sdk';
    import { createAgent, type Agent, type Message } from './agent.js';
    import { defaultTools } from './tools.js';
    
    // Initialize agent (runs independently of UI)
    const agent = createAgent({
      apiKey: process.env.OPENROUTER_API_KEY!,
      model: 'openrouter/auto',
      instructions: 'You are a helpful assistant. Be concise.',
      tools: defaultTools,
    });
    
    function ChatMessage({ message }: { message: Message }) {
      const isUser = message.role === 'user';
      return (
        <Box flexDirection="column" marginBottom={1}>
          <Text bold color={isUser ? 'cyan' : 'green'}>
            {isUser ? '▶ You' : '◀ Assistant'}
          </Text>
          <Text wrap="wrap">{message.content}</Text>
        </Box>
      );
    }
    
    // Render streaming items by type using the items-based pattern
    function ItemRenderer({ item }: { item: StreamableOutputItem }) {
      switch (item.type) {
        case 'message': {
          const textContent = item.content?.find((c: { type: string }) => c.type === 'output_text');
          const text = textContent && 'text' in textContent ? textContent.text : '';
          return (
            <Box flexDirection="column" marginBottom={1}>
              <Text bold color="green">◀ Assistant</Text>
              <Text wrap="wrap">{text}</Text>
              {item.status !== 'completed' && <Text color="gray">▌</Text>}
            </Box>
          );
        }
        case 'function_call':
          return (
            <Text color="yellow">
              {item.status === 'completed' ? '  ✓' : '  🔧'} {item.name}
              {item.status === 'in_progress' && '...'}
            </Text>
          );
        case 'reasoning': {
          const reasoningText = item.content?.find((c: { type: string }) => c.type === 'reasoning_text');
          const text = reasoningText && 'text' in reasoningText ? reasoningText.text : '';
          return (
            <Box flexDirection="column" marginBottom={1}>
              <Text bold color="magenta">💭 Thinking</Text>
              <Text wrap="wrap" color="gray">{text}</Text>
            </Box>
          );
        }
        default:
          return null;
      }
    }
    
    function InputField({
      value,
      onChange,
      onSubmit,
      disabled,
    }: {
      value: string;
      onChange: (v: string) => void;
      onSubmit: () => void;
      disabled: boolean;
    }) {
      useInput((input, key) => {
        if (disabled) return;
        if (key.return) onSubmit();
        else if (key.backspace || key.delete) onChange(value.slice(0, -1));
        else if (input && !key.ctrl && !key.meta) onChange(value + input);
      });
    
      return (
        <Box>
          <Text color="yellow">{'> '}</Text>
          <Text>{value}</Text>
          <Text color="gray">{disabled ? ' ···' : '█'}</Text>
        </Box>
      );
    }
    
    function App() {
      const { exit } = useApp();
      const [messages, setMessages] = useState<Message[]>([]);
      const [input, setInput] = useState('');
      const [isLoading, setIsLoading] = useState(false);
      // Use Map keyed by item ID for efficient React state updates (items-based pattern)
      const [items, setItems] = useState<Map<string, StreamableOutputItem>>(new Map());
    
      useInput((_, key) => {
        if (key.escape) exit();
      });
    
      // Subscribe to agent events using items-based streaming
      useEffect(() => {
        const onThinkingStart = () => {
          setIsLoading(true);
          setItems(new Map()); // Clear items for new response
        };
    
        // Items-based streaming: replace items by ID, don't accumulate
        const onItemUpdate = (item: StreamableOutputItem) => {
          setItems((prev) => new Map(prev).set(item.id, item));
        };
    
        const onMessageAssistant = () => {
          setMessages(agent.getMessages());
          setItems(new Map()); // Clear streaming items
          setIsLoading(false);
        };
    
        const onError = (err: Error) => {
          setIsLoading(false);
        };
    
        agent.on('thinking:start', onThinkingStart);
        agent.on('item:update', onItemUpdate);
        agent.on('message:assistant', onMessageAssistant);
        agent.on('error', onError);
    
        return () => {
          agent.off('thinking:start', onThinkingStart);
          agent.off('item:update', onItemUpdate);
          agent.off('message:assistant', onMessageAssistant);
          agent.off('error', onError);
        };
      }, []);
    
      const sendMessage = useCallback(async () => {
        if (!input.trim() || isLoading) return;
        const text = input.trim();
        setInput('');
        setMessages((prev) => [...prev, { role: 'user', content: text }]);
        await agent.send(text);
      }, [input, isLoading]);
    
      return (
        <Box flexDirection="column" padding={1}>
          <Box marginBottom={1}>
            <Text bold color="magenta">🤖 OpenRouter Agent</Text>
            <Text color="gray"> (Esc to exit)</Text>
          </Box>
    
          <Box flexDirection="column" marginBottom={1}>
            {/* Render completed messages */}
            {messages.map((msg, i) => (
              <ChatMessage key={i} message={msg} />
            ))}
    
            {/* Render streaming items by type (items-based pattern) */}
            {Array.from(items.values()).map((item) => (
              <ItemRenderer key={item.id} item={item} />
            ))}
          </Box>
    
          <Box borderStyle="single" borderColor="gray" paddingX={1}>
            <InputField
              value={input}
              onChange={setInput}
              onSubmit={sendMessage}
              disabled={isLoading}
            />
          </Box>
        </Box>
      );
    }
    
    render(<App />);
    

    Run TUI: OPENROUTER_API_KEY=sk-or-... npm start

    Understanding Items-Based Streaming

    The OpenRouter SDK uses an items-based streaming model - a key paradigm where items are emitted multiple times with the same ID but progressively updated content. Instead of accumulating chunks, you replace items by their ID.

    How It Works

    Each iteration of getItemsStream() yields a complete item with updated content:

    // Iteration 1: Partial message
    { id: "msg_123", type: "message", content: [{ type: "output_text", text: "Hello" }] }
    
    // Iteration 2: Updated message (replace, don't append)
    { id: "msg_123", type: "message", content: [{ type: "output_text", text: "Hello world" }] }
    

    For function calls, arguments stream progressively:

    // Iteration 1: Partial arguments
    { id: "call_456", type: "function_call", name: "get_weather", arguments: "{\"q" }
    
    // Iteration 2: Complete arguments
    { id: "call_456", type: "function_call", name: "get_weather", arguments: "{\"query\": \"Paris\"}", status: "completed" }
    

    Why Items Are Better

    Traditional (accumulation required):

    let text = '';
    for await (const chunk of result.getTextStream()) {
      text += chunk;  // Manual accumulation
      updateUI(text);
    }
    

    Items (complete replacement):

    const items = new Map<string, StreamableOutputItem>();
    for await (const item of result.getItemsStream()) {
      items.set(item.id, item);  // Replace by ID
      updateUI(items);
    }
    

    Benefits:

    • No manual chunk management - each item is complete
    • Handles concurrent outputs - function calls and messages can stream in parallel
    • Full TypeScript inference for all item types
    • Natural Map-based state works perfectly with React/UI frameworks

    Extending the Agent

    Add Custom Hooks

    const agent = createAgent({ apiKey: '...' });
    
    // Log all events
    agent.on('message:user', (msg) => {
      saveToDatabase('user', msg.content);
    });
    
    agent.on('message:assistant', (msg) => {
      saveToDatabase('assistant', msg.content);
      sendWebhook('new_message', msg);
    });
    
    agent.on('tool:call', (name, args) => {
      analytics.track('tool_used', { name, args });
    });
    
    agent.on('error', (err) => {
      errorReporting.capture(err);
    });
    

    Use with HTTP Server

    import express from 'express';
    import { createAgent } from './agent.js';
    
    const app = express();
    app.use(express.json());
    
    // One agent per session (store in memory or Redis)
    const sessions = new Map<string, Agent>();
    
    app.post('/chat', async (req, res) => {
      const { sessionId, message } = req.body;
    
      let agent = sessions.get(sessionId);
      if (!agent) {
        agent = createAgent({ apiKey: process.env.OPENROUTER_API_KEY! });
        sessions.set(sessionId, agent);
      }
    
      const response = await agent.sendSync(message);
      res.json({ response, history: agent.getMessages() });
    });
    
    app.listen(3000);
    

    Use with Discord

    import { Client, GatewayIntentBits } from 'discord.js';
    import { createAgent } from './agent.js';
    
    const discord = new Client({
      intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages],
    });
    
    const agents = new Map<string, Agent>();
    
    discord.on('messageCreate', async (msg) => {
      if (msg.author.bot) return;
    
      let agent = agents.get(msg.channelId);
      if (!agent) {
        agent = createAgent({ apiKey: process.env.OPENROUTER_API_KEY! });
        agents.set(msg.channelId, agent);
      }
    
      const response = await agent.sendSync(msg.content);
      await msg.reply(response);
    });
    
    discord.login(process.env.DISCORD_TOKEN);
    

    Agent API Reference

    Constructor Options

    Option Type Default Description
    apiKey string required OpenRouter API key
    model string 'openrouter/auto' Model to use
    instructions string 'You are a helpful assistant.' System prompt
    tools Tool[] [] Available tools
    maxSteps number 5 Max agentic loop iterations

    Methods

    Method Returns Description
    send(content) Promise Send message with streaming
    sendSync(content) Promise Send message without streaming
    getMessages() Message[] Get conversation history
    clearHistory() void Clear conversation
    setInstructions(text) void Update system prompt
    addTool(tool) void Add tool at runtime

    Events

    Event Payload Description
    message:user Message User message added
    message:assistant Message Assistant response complete
    item:update StreamableOutputItem Item emitted (replace by ID, don't accumulate)
    stream:start - Streaming started
    stream:delta (delta, accumulated) New text chunk
    stream:end fullText Streaming complete
    tool:call (name, args) Tool being called
    tool:result (name, result) Tool returned result
    reasoning:update text Extended thinking content
    thinking:start - Agent processing
    thinking:end - Agent done processing
    error Error Error occurred

    Item Types (from getItemsStream)

    The SDK uses an items-based streaming model where items are emitted multiple times with the same ID but progressively updated content. Replace items by their ID rather than accumulating chunks.

    Type Purpose
    message Assistant text responses
    function_call Tool invocations with streaming arguments
    function_call_output Results from executed tools
    reasoning Extended thinking content
    web_search_call Web search operations
    file_search_call File search operations
    image_generation_call Image generation operations

    Discovering Models

    Do not hardcode model IDs - they change frequently. Use the models API:

    Fetch Available Models

    interface OpenRouterModel {
      id: string;
      name: string;
      description?: string;
      context_length: number;
      pricing: { prompt: string; completion: string };
      top_provider?: { is_moderated: boolean };
    }
    
    async function fetchModels(): Promise<OpenRouterModel[]> {
      const res = await fetch('https://openrouter.ai/api/v1/models');
      const data = await res.json();
      return data.data;
    }
    
    // Find models by criteria
    async function findModels(filter: {
      author?: string;      // e.g., 'anthropic', 'openai', 'google'
      minContext?: number;  // e.g., 100000 for 100k context
      maxPromptPrice?: number; // e.g., 0.001 for cheap models
    }): Promise<OpenRouterModel[]> {
      const models = await fetchModels();
    
      return models.filter((m) => {
        if (filter.author && !m.id.startsWith(filter.author + '/')) return false;
        if (filter.minContext && m.context_length < filter.minContext) return false;
        if (filter.maxPromptPrice) {
          const price = parseFloat(m.pricing.prompt);
          if (price > filter.maxPromptPrice) return false;
        }
        return true;
      });
    }
    
    // Example: Get latest Claude models
    const claudeModels = await findModels({ author: 'anthropic' });
    console.log(claudeModels.map((m) => m.id));
    
    // Example: Get models with 100k+ context
    const longContextModels = await findModels({ minContext: 100000 });
    
    // Example: Get cheap models
    const cheapModels = await findModels({ maxPromptPrice: 0.0005 });
    

    Dynamic Model Selection in Agent

    // Create agent with dynamic model selection
    const models = await fetchModels();
    const bestModel = models.find((m) => m.id.includes('claude')) || models[0];
    
    const agent = createAgent({
      apiKey: process.env.OPENROUTER_API_KEY!,
      model: bestModel.id,  // Use discovered model
      instructions: 'You are a helpful assistant.',
    });
    

    Using openrouter/auto

    For simplicity, use openrouter/auto which automatically selects the best available model for your request:

    const agent = createAgent({
      apiKey: process.env.OPENROUTER_API_KEY!,
      model: 'openrouter/auto',  // Auto-selects best model
    });
    

    Models API Reference

    • Endpoint: GET https://openrouter.ai/api/v1/models
    • Response: { data: OpenRouterModel[] }
    • Browse models: https://openrouter.ai/models

    Resources

    • OpenRouter Docs: https://openrouter.ai/docs
    • Models API: https://openrouter.ai/api/v1/models
    • Ink Docs: https://github.com/vadimdemedes/ink
    • Get API Key: https://openrouter.ai/settings/keys
    Recommended Servers
    Browser tool
    Browser tool
    Hostsmith
    Hostsmith
    Nimble MCP Server
    Nimble MCP Server
    Repository
    openrouterteam/agent-skills
    Files