Local Codebase Study: OpenRouter SDK
What Was Researched
Architecture and implementation of the OpenRouter TypeScript SDK (OpenRouterTeam/typescript-sdk) — the official SDK for accessing 400+ language models through OpenRouter's unified API. Auto-generated from OpenAPI specs via Speakeasy, with hand-written extensions for tool orchestration, streaming, and multi-turn agent conversations.
Which Sources Were Used
- Local clone:
c:\Users\Adam\Desktop\agent2\openrouter-sdk - Files analyzed:
OVERVIEW.md,CLAUDE.md,FUNCTIONS.md,README.md,RUNTIMES.md,package.json,tsconfig.json,src/index.ts,src/core.ts, directory structure ofsrc/,src/lib/,src/models/,src/sdk/,src/funcs/,tests/,examples/
Key Findings
Architecture: Generated + Hand-Written Hybrid
The SDK uses a unique auto-generated + custom extension architecture:
Generated by Speakeasy (DO NOT manually edit):
src/models/— Type definitions from OpenAPI schemassrc/funcs/*Send.ts,src/funcs/*Get.ts— API operation functionssrc/sdk/— SDK service classessrc/hooks/registration.ts— Hook registration
Hand-written (safe to edit):
src/lib/— All library utilities and helpers (36 files)src/funcs/call-model.ts— High-level model calling abstractionsrc/index.ts— Main exportssrc/hooks/hooks.ts,src/hooks/types.ts— Custom hooks
Core Abstractions
callModel (src/funcs/call-model.ts)
- High-level function for making model requests with tools
- Returns a
ModelResultwrapper with multiple consumption patterns - Supports async parameter resolution and automatic tool execution
- Consumption patterns:
.getText(),.getTextStream(),.getToolStream(), etc.
ModelResult (src/lib/model-result.ts — 65KB, largest lib file)
- Wraps streaming responses with multiple consumption patterns
- Handles automatic tool execution and turn orchestration
- Uses
ReusableReadableStreamfor multiple parallel consumers - Critical for enabling text + tools + reasoning consumption simultaneously
Tool System (src/lib/tool*.ts)
tool()helper creates type-safe tools with Zod schemas- Three tool types:
- Regular tools (
execute: function) — auto-executed, return final result - Generator tools (
execute: async generator) — stream preliminary results - Manual tools (
execute: false) — return tool calls without execution
- Regular tools (
tool-orchestrator.ts— manages multi-turn conversations with tool execution loopstool-executor.ts— executes tool calls and handles resultstool-context.ts— provides context to tools during executiontool-event-broadcaster.ts— broadcasts tool events to consumers
Stop Conditions (src/lib/stop-conditions.ts)
- Controls when tool execution loops terminate
- Built-in helpers:
stepCountIs(),hasToolCall(),maxTokensUsed(),maxCost(),finishReasonIs() - Custom conditions receive full step history
- Default:
stepCountIs(5)if not specified
Async Parameter Resolution (src/lib/async-params.ts)
- Any parameter in
CallModelInputcan be a function:(ctx: TurnContext) => value - Functions resolved before each turn — enables dynamic model switching, temperature adjustment, etc.
- Example:
model: (ctx) => ctx.numberOfTurns > 3 ? 'gpt-4' : 'gpt-3.5-turbo'
Next Turn Params (src/lib/next-turn-params.ts)
- Tools can modify request parameters for the next turn after execution
- Applied after tool execution, before next API request
- Enables dynamic parameter adjustment based on tool results
Streaming Architecture
ReusableReadableStream (src/lib/reusable-stream.ts):
- Caches stream events to enable multiple independent consumers
- Critical for parallel consumption (text + tools + reasoning simultaneously)
- Handles both SSE and standard ReadableStream
Stream Transformers (src/lib/stream-transformers.ts — 32KB):
extractTextDeltas(),extractReasoningDeltas(),extractToolDeltas()- Build higher-level streams for different consumption patterns
- Handle both streaming and non-streaming responses uniformly
Message Format Compatibility
- OpenRouter format (native)
- Claude format via
fromClaudeMessages()/toClaudeMessage()(anthropic-compat.ts) - OpenAI Chat format via
fromChatMessages()/toChatMessage()(chat-compat.ts)
TypeScript Configuration
- Target: ES2020, Module: Node16
- Strict mode: Maximum strictness (
exactOptionalPropertyTypes,noUncheckedIndexedAccess) - Output: ESM only (no CommonJS)
- Build:
tsc→esm/directory
SDK Generation Workflow
- Edit code in monorepo
sdks/typescript/ - PR check dry-runs against OSS repo
- Release workflow runs
speakeasy run, pushes to OSS repo - Generated
src/copied back - Speakeasy version is pinned — mismatched versions fail CI
Package Structure
Multiple exports:
@openrouter/sdk— Main SDK@openrouter/sdk/types— Type definitions@openrouter/sdk/models— Model types@openrouter/sdk/models/operations— Operation types@openrouter/sdk/models/errors— Error types
What Is Confirmed
- Repository cloned successfully
- 400+ language models accessible through unified API
- Auto-generated from OpenAPI specs via Speakeasy
- Type-safe with full TypeScript strict mode
- Tool orchestration with multi-turn conversation support
- Both streaming and non-streaming response handling
- Message format conversion (OpenAI ↔ Claude ↔ OpenRouter)
- ESM-only package (no CommonJS)
What Is Uncertain
- How Speakeasy generation handles breaking API changes
- Performance of the ReusableReadableStream for high-throughput scenarios
- How well the async parameter resolution scales with complex tool chains
- Whether the tool system supports MCP tools natively or requires adaptation
- How the cost tracking works end-to-end with OpenRouter's pricing
How This Applies to Building a Modern Model-Agnostic Agent Harness
OpenRouter SDK is relevant to the harness as both a potential dependency and an architectural reference:
- Multi-Provider Routing: OpenRouter routes to 400+ models — the harness could use OpenRouter as a provider backend or replicate its routing model
- Tool System Design: The three-tier tool system (regular/generator/manual) with Zod schema validation is the most sophisticated tool abstraction studied. The harness tool layer should learn from this
- Stop Conditions Pattern: Programmatic control over tool execution loops (
stepCountIs(),maxCost(), etc.) is essential for agent safety and directly applicable - Async Parameter Resolution: The pattern of dynamically resolving parameters per-turn based on context is powerful for adaptive agent behavior
- Streaming Architecture: The
ReusableReadableStreampattern for multiple parallel consumers is a key insight for streaming agent responses - Message Format Bridges: The
anthropic-compat.tsandchat-compat.tsconverters show how to handle format translation at the SDK level - Code Generation Strategy: The Speakeasy-based auto-generation from OpenAPI specs is a reference for keeping SDKs in sync with APIs
- Type Safety: The extreme TypeScript strictness (
exactOptionalPropertyTypes,noUncheckedIndexedAccess) shows what production-grade SDK type safety looks like