|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## Overview |
| 6 | + |
| 7 | +This is the OpenRouter TypeScript SDK - a type-safe toolkit for building AI applications with access to 300+ language models. The SDK is **generated using Speakeasy** from an OpenAPI specification, with custom hand-written features for tool orchestration, async parameter resolution, and streaming. |
| 8 | + |
| 9 | +**IMPORTANT**: Most code in this repository is auto-generated by Speakeasy. Do not manually edit generated files - changes will be overwritten. See the "Code Generation" section below for how to make changes. |
| 10 | + |
| 11 | +## Common Commands |
| 12 | + |
| 13 | +### Building |
| 14 | +```bash |
| 15 | +pnpm run build |
| 16 | +``` |
| 17 | +Compiles TypeScript to `esm/` directory using `tsc`. |
| 18 | + |
| 19 | +### Linting |
| 20 | +```bash |
| 21 | +pnpm run lint |
| 22 | +``` |
| 23 | +**Note**: This project uses **ESLint** (not Biome). Configuration is in `eslint.config.mjs`. |
| 24 | + |
| 25 | +### Testing |
| 26 | +```bash |
| 27 | +# Run all tests |
| 28 | +npx vitest |
| 29 | + |
| 30 | +# Run specific test file |
| 31 | +npx vitest tests/e2e/call-model.test.ts |
| 32 | + |
| 33 | +# Run tests in watch mode |
| 34 | +npx vitest --watch |
| 35 | +``` |
| 36 | + |
| 37 | +Tests require an OpenRouter API key: |
| 38 | +1. Copy `.env.example` to `.env` |
| 39 | +2. Add your API key: `OPENROUTER_API_KEY=your_key_here` |
| 40 | + |
| 41 | +Test organization: |
| 42 | +- `tests/e2e/` - End-to-end integration tests |
| 43 | +- `tests/unit/` - Unit tests |
| 44 | +- `tests/funcs/` - Function-specific tests |
| 45 | + |
| 46 | +### Publishing |
| 47 | +```bash |
| 48 | +pnpm run prepublishOnly |
| 49 | +``` |
| 50 | +This runs the build automatically before publishing. |
| 51 | + |
| 52 | +## Code Generation with Speakeasy |
| 53 | + |
| 54 | +The SDK is generated from `.speakeasy/in.openapi.yaml` using [Speakeasy](https://www.speakeasy.com/docs). |
| 55 | + |
| 56 | +### Generated vs Hand-Written Code |
| 57 | + |
| 58 | +**Generated Files** (DO NOT EDIT - will be overwritten): |
| 59 | +- `src/models/` - Type definitions from OpenAPI schemas |
| 60 | +- `src/funcs/*Send.ts`, `src/funcs/*Get.ts`, etc. - Most API operation functions |
| 61 | +- `src/sdk/` - SDK service classes |
| 62 | +- `src/hooks/registration.ts` - Hook registration |
| 63 | + |
| 64 | +**Hand-Written Files** (safe to edit): |
| 65 | +- `src/lib/` - All library utilities and helpers |
| 66 | +- `src/funcs/call-model.ts` - High-level model calling abstraction |
| 67 | +- `src/index.ts` - Main exports |
| 68 | +- `src/hooks/hooks.ts` and `src/hooks/types.ts` - Custom hooks |
| 69 | + |
| 70 | +### Regenerating the SDK |
| 71 | + |
| 72 | +To regenerate after updating the OpenAPI spec: |
| 73 | +```bash |
| 74 | +speakeasy run |
| 75 | +``` |
| 76 | + |
| 77 | +This reads configuration from `.speakeasy/gen.yaml` and workflow from `.speakeasy/workflow.yaml`. |
| 78 | + |
| 79 | +### Making Changes to Generated Code |
| 80 | + |
| 81 | +1. **For type/schema changes**: Update `.speakeasy/in.openapi.yaml` and regenerate |
| 82 | +2. **For overlays**: Edit files in `.speakeasy/overlays/` to apply transformations |
| 83 | +3. **For generation config**: Edit `.speakeasy/gen.yaml` |
| 84 | +4. **Always commit both** the OpenAPI spec changes AND the regenerated code |
| 85 | + |
| 86 | +## Architecture |
| 87 | + |
| 88 | +### Core Abstractions |
| 89 | + |
| 90 | +**callModel** (`src/funcs/call-model.ts`) |
| 91 | +- High-level function for making model requests with tools |
| 92 | +- Returns a `ModelResult` wrapper with multiple consumption patterns |
| 93 | +- Supports async parameter resolution and automatic tool execution |
| 94 | +- Example consumption: `.getText()`, `.getTextStream()`, `.getToolStream()`, etc. |
| 95 | + |
| 96 | +**ModelResult** (`src/lib/model-result.ts`) |
| 97 | +- Wraps streaming responses with multiple consumption patterns |
| 98 | +- Handles automatic tool execution and turn orchestration |
| 99 | +- Uses `ReusableReadableStream` to enable multiple parallel consumers |
| 100 | + |
| 101 | +**Tool System** (`src/lib/tool.ts`, `src/lib/tool-types.ts`, `src/lib/tool-executor.ts`) |
| 102 | +- `tool()` helper creates type-safe tools with Zod schemas |
| 103 | +- Three tool types: |
| 104 | + - **Regular tools** (`execute: function`) - auto-executed, return final result |
| 105 | + - **Generator tools** (`execute: async generator`) - stream preliminary results |
| 106 | + - **Manual tools** (`execute: false`) - return tool calls without execution |
| 107 | +- Tool orchestrator (`src/lib/tool-orchestrator.ts`) manages multi-turn conversations |
| 108 | + |
| 109 | +**Async Parameter Resolution** (`src/lib/async-params.ts`) |
| 110 | +- Any parameter in `CallModelInput` can be a function: `(ctx: TurnContext) => value` |
| 111 | +- Functions resolved before each turn, allowing dynamic parameter adjustment |
| 112 | +- Supports both sync and async functions |
| 113 | +- Example: `model: (ctx) => ctx.numberOfTurns > 3 ? 'gpt-4' : 'gpt-3.5-turbo'` |
| 114 | + |
| 115 | +**Next Turn Params** (`src/lib/next-turn-params.ts`) |
| 116 | +- Tools can define `nextTurnParams` to modify request parameters after execution |
| 117 | +- Functions receive tool input and can return parameter updates |
| 118 | +- Applied after tool execution, before next API request |
| 119 | +- Example: Increase temperature after seeing tool results |
| 120 | + |
| 121 | +**Stop Conditions** (`src/lib/stop-conditions.ts`) |
| 122 | +- Control when tool execution loops terminate |
| 123 | +- Built-in helpers: `stepCountIs()`, `hasToolCall()`, `maxTokensUsed()`, `maxCost()`, `finishReasonIs()` |
| 124 | +- Custom conditions receive full step history |
| 125 | +- Default: `stepCountIs(5)` if not specified |
| 126 | + |
| 127 | +## Message Format Compatibility |
| 128 | + |
| 129 | +The SDK supports multiple message formats: |
| 130 | + |
| 131 | +- **OpenRouter format** (native) |
| 132 | +- **Claude format** via `fromClaudeMessages()` / `toClaudeMessage()` (`src/lib/anthropic-compat.ts`) |
| 133 | +- **OpenAI Chat format** via `fromChatMessages()` / `toChatMessage()` (`src/lib/chat-compat.ts`) |
| 134 | + |
| 135 | +These converters handle content types, tool calls, and format-specific features. |
| 136 | + |
| 137 | +## Streaming Architecture |
| 138 | + |
| 139 | +**ReusableReadableStream** (`src/lib/reusable-stream.ts`) |
| 140 | + |
| 141 | +- Caches stream events to enable multiple independent consumers |
| 142 | +- Critical for allowing parallel consumption patterns (text + tools + reasoning) |
| 143 | +- Handles both SSE and standard ReadableStream |
| 144 | + |
| 145 | +**Stream Transformers** (`src/lib/stream-transformers.ts`) |
| 146 | + |
| 147 | +- Extract specific data from response streams |
| 148 | +- `extractTextDeltas()`, `extractReasoningDeltas()`, `extractToolDeltas()` |
| 149 | +- Build higher-level streams for different consumption patterns |
| 150 | +- Handle both streaming and non-streaming responses uniformly |
| 151 | + |
| 152 | +## Development Workflow |
| 153 | + |
| 154 | +### When Adding New Features |
| 155 | + |
| 156 | +1. **If it's an API change**: Update `.speakeasy/in.openapi.yaml` in the monorepo (see `/Users/mattapperson/Development/CLAUDE.md` for monorepo workflow) |
| 157 | +2. **If it's SDK functionality**: Add to `src/lib/` or extend existing hand-written files |
| 158 | +3. **Add tests** to appropriate directory (`tests/e2e/`, `tests/unit/`) |
| 159 | +4. **Update examples** if user-facing (in `examples/`) |
| 160 | + |
| 161 | +### When Fixing Bugs |
| 162 | + |
| 163 | +1. **In generated code**: Fix the OpenAPI spec or Speakeasy generation config, then regenerate |
| 164 | +2. **In hand-written code**: Fix directly in `src/lib/` or other hand-written files |
| 165 | +3. **Add regression test** to prevent reoccurrence |
| 166 | + |
| 167 | +### Running Examples |
| 168 | + |
| 169 | +```bash |
| 170 | +cd examples |
| 171 | +# Set your API key in .env first |
| 172 | +node --loader ts-node/esm call-model.example.ts |
| 173 | +``` |
| 174 | + |
| 175 | +Examples demonstrate: |
| 176 | +- `call-model.example.ts` - Basic usage |
| 177 | +- `call-model-typed-tool-calling.example.ts` - Type-safe tools |
| 178 | +- `anthropic-multimodal-tools.example.ts` - Multimodal inputs with tools |
| 179 | +- `anthropic-reasoning.example.ts` - Extended thinking/reasoning |
| 180 | +- `chat-reasoning.example.ts` - Reasoning with chat format |
| 181 | +- `tools-example.ts` - Comprehensive tool usage |
| 182 | + |
| 183 | +## TypeScript Configuration |
| 184 | + |
| 185 | +- **Target**: ES2020, module: Node16 |
| 186 | +- **Strict mode**: Enabled with strictest settings from tsconfig/bases |
| 187 | +- **Output**: `esm/` directory with declaration files |
| 188 | +- **Module format**: ESM only (no CommonJS) |
| 189 | + |
| 190 | +Key compiler options: |
| 191 | +- `exactOptionalPropertyTypes: true` - Strict optional handling |
| 192 | +- `noUncheckedIndexedAccess: true` - Array access safety |
| 193 | +- `isolatedModules: true` - Required for module transforms |
| 194 | + |
| 195 | +## Testing Strategy |
| 196 | + |
| 197 | +Tests use Vitest with: |
| 198 | +- 30s timeout for API calls |
| 199 | +- Environment variables from `.env` |
| 200 | +- Type checking enabled for test files |
| 201 | + |
| 202 | +E2E tests (`tests/e2e/`) make real API calls and test: |
| 203 | +- Basic chat completions |
| 204 | +- Tool execution flows |
| 205 | +- Streaming responses |
| 206 | +- Multi-turn conversations |
| 207 | +- Different message formats |
| 208 | + |
| 209 | +## Package Structure |
| 210 | + |
| 211 | +This is an ES Module (ESM) package with multiple exports: |
| 212 | +- `@openrouter/sdk` - Main SDK |
| 213 | +- `@openrouter/sdk/types` - Type definitions |
| 214 | +- `@openrouter/sdk/models` - Model types |
| 215 | +- `@openrouter/sdk/models/operations` - Operation types |
| 216 | +- `@openrouter/sdk/models/errors` - Error types |
| 217 | + |
| 218 | +The package uses conditional exports in `package.json` to map source files to build outputs. |
0 commit comments