From d7be0676a1eb62bb7b4d6fb6269ee112e4a21fe2 Mon Sep 17 00:00:00 2001 From: Tom Aylott Date: Thu, 13 Nov 2025 20:03:55 -0500 Subject: [PATCH 01/11] Add prompt-caching examples for AI SDK v5 - Add typescript/ai-sdk-v5/src/prompt-caching/user-message-cache.ts - Demonstrates cache_control using providerOptions.openrouter.cacheControl - Shows critical configuration: extraBody.stream_options.include_usage - Evidence-based verification via providerMetadata.openrouter.usage --- docs/prompt-caching.md | 1 + typescript/ai-sdk-v5/README.md | 40 +++++ typescript/ai-sdk-v5/package.json | 18 ++ .../ai-sdk-v5/src/prompt-caching/README.md | 100 +++++++++++ .../src/prompt-caching/multi-message-cache.ts | 137 +++++++++++++++ .../src/prompt-caching/no-cache-control.ts | 119 +++++++++++++ .../src/prompt-caching/user-message-cache.ts | 165 ++++++++++++++++++ typescript/ai-sdk-v5/tsconfig.json | 16 ++ typescript/package.json | 3 +- 9 files changed, 598 insertions(+), 1 deletion(-) create mode 100644 typescript/ai-sdk-v5/README.md create mode 100644 typescript/ai-sdk-v5/package.json create mode 100644 typescript/ai-sdk-v5/src/prompt-caching/README.md create mode 100644 typescript/ai-sdk-v5/src/prompt-caching/multi-message-cache.ts create mode 100644 typescript/ai-sdk-v5/src/prompt-caching/no-cache-control.ts create mode 100644 typescript/ai-sdk-v5/src/prompt-caching/user-message-cache.ts create mode 100644 typescript/ai-sdk-v5/tsconfig.json diff --git a/docs/prompt-caching.md b/docs/prompt-caching.md index b23e8d6..674eac9 100644 --- a/docs/prompt-caching.md +++ b/docs/prompt-caching.md @@ -11,3 +11,4 @@ That guide covers provider-specific behavior, pricing, configuration requirement See ecosystem-specific examples in this repository for runnable reference implementations: - **TypeScript + fetch**: [typescript/fetch/src/prompt-caching/](../typescript/fetch/src/prompt-caching/) +- **AI SDK v5** (Vercel): [typescript/ai-sdk-v5/src/prompt-caching/](../typescript/ai-sdk-v5/src/prompt-caching/) diff --git a/typescript/ai-sdk-v5/README.md b/typescript/ai-sdk-v5/README.md new file mode 100644 index 0000000..2a5aca7 --- /dev/null +++ b/typescript/ai-sdk-v5/README.md @@ -0,0 +1,40 @@ +# AI SDK v5 Examples + +Examples using Vercel AI SDK v5 with @openrouter/ai-sdk-provider. + +## Prerequisites + +- Bun runtime: `curl -fsSL https://bun.sh/install | bash` +- `OPENROUTER_API_KEY` environment variable + +## Running Examples + +```bash +# From monorepo root (typescript/) +bun examples + +# Or from this workspace +cd ai-sdk-v5 +bun examples +``` + +## Features + +- [prompt-caching.ts](./src/prompt-caching.ts) - Anthropic caching with AI SDK v5 + +### Key Configuration + +**CRITICAL**: The AI SDK example requires: +```typescript +extraBody: { + stream_options: { include_usage: true } +} +``` + +Without this, usage details (including cached_tokens) are not populated in the response. + +## Dependencies + +- `@openrouter-examples/shared` - Shared constants (LARGE_SYSTEM_PROMPT) and types +- `@openrouter/ai-sdk-provider` - OpenRouter provider for AI SDK +- `ai` v5.x - Vercel AI SDK diff --git a/typescript/ai-sdk-v5/package.json b/typescript/ai-sdk-v5/package.json new file mode 100644 index 0000000..cea8ebe --- /dev/null +++ b/typescript/ai-sdk-v5/package.json @@ -0,0 +1,18 @@ +{ + "name": "@openrouter-examples/ai-sdk-v5", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "examples": "bun run src/prompt-caching/user-message-cache.ts && bun run src/prompt-caching/multi-message-cache.ts && bun run src/prompt-caching/no-cache-control.ts", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@openrouter-examples/shared": "workspace:*", + "@openrouter/ai-sdk-provider": "^1.2.1", + "ai": "^5.0.92" + }, + "devDependencies": { + "@types/bun": "latest" + } +} diff --git a/typescript/ai-sdk-v5/src/prompt-caching/README.md b/typescript/ai-sdk-v5/src/prompt-caching/README.md new file mode 100644 index 0000000..5682c51 --- /dev/null +++ b/typescript/ai-sdk-v5/src/prompt-caching/README.md @@ -0,0 +1,100 @@ +# Anthropic Prompt Caching Examples (AI SDK v5) + +This directory contains examples demonstrating Anthropic's prompt caching feature via OpenRouter using Vercel AI SDK v5. + +## What is Prompt Caching? + +Anthropic's prompt caching allows you to cache large portions of your prompts to: +- **Reduce costs** - Cached tokens cost significantly less +- **Improve latency** - Cached content is processed faster +- **Enable larger contexts** - Use more context without proportional cost increases + +Cache TTL: 5 minutes for ephemeral caches + +## Examples + +### User Message Cache (`user-message-cache.ts`) +Cache large context in user messages using AI SDK: +```bash +bun run typescript/ai-sdk-v5/src/prompt-caching/user-message-cache.ts +``` + +**Pattern**: User message with `providerOptions.openrouter.cacheControl` + +## How to Use with AI SDK + +```typescript +import { createOpenRouter } from '@openrouter/ai-sdk-provider'; +import { generateText } from 'ai'; + +// CRITICAL: Must include stream_options for usage details +const openrouter = createOpenRouter({ + apiKey: process.env.OPENROUTER_API_KEY, + extraBody: { + stream_options: { include_usage: true }, // Required! + }, +}); + +const result = await generateText({ + model: openrouter('anthropic/claude-3.5-sonnet'), + messages: [ + { + role: 'user', + content: [ + { + type: 'text', + text: 'Large context here...', + providerOptions: { + openrouter: { + cacheControl: { type: 'ephemeral' }, // Cache this block + }, + }, + }, + { + type: 'text', + text: 'Your question here', + }, + ], + }, + ], +}); + +// Check cache metrics +const cachedTokens = result.providerMetadata?.openrouter?.usage?.promptTokensDetails?.cachedTokens ?? 0; +``` + +## Important Notes + +### Critical Configuration +**MUST include `extraBody: { stream_options: { include_usage: true } }`** +- Without this, usage details (including cached_tokens) are not populated +- This is a provider-level configuration, not per-request + +### Cache Metrics Location +Cache metrics are in `providerMetadata.openrouter.usage`: +```typescript +{ + promptTokens: number, + completionTokens: number, + promptTokensDetails: { + cachedTokens: number // Number of tokens read from cache + } +} +``` + +### Requirements +1. **stream_options.include_usage = true** - CRITICAL for usage details +2. **Minimum 2048+ tokens** - Smaller content may not be cached +3. **providerOptions.openrouter.cacheControl** - On content items, not messages +4. **Exact match** - Cache only hits on identical content + +### Expected Behavior +- **First call**: `cachedTokens = 0` (cache miss, creates cache) +- **Second call**: `cachedTokens > 0` (cache hit, reads from cache) + +## Scientific Method +All examples follow evidence-based verification: +- **Hypothesis**: providerOptions.openrouter.cacheControl triggers caching +- **Experiment**: Make identical calls twice +- **Evidence**: Measure via providerMetadata.openrouter.usage +- **Analysis**: Compare cache miss vs cache hit diff --git a/typescript/ai-sdk-v5/src/prompt-caching/multi-message-cache.ts b/typescript/ai-sdk-v5/src/prompt-caching/multi-message-cache.ts new file mode 100644 index 0000000..8f0ef64 --- /dev/null +++ b/typescript/ai-sdk-v5/src/prompt-caching/multi-message-cache.ts @@ -0,0 +1,137 @@ +/** + * Example: Anthropic Prompt Caching - Multi-Message Conversation (AI SDK v5) + * + * This example demonstrates Anthropic prompt caching in a multi-message conversation + * via OpenRouter using Vercel AI SDK v5. + * + * Pattern: User message cache in multi-turn conversation + * - Cache large context in first user message + * - Cache persists through conversation history + * + * To run: bun run typescript/ai-sdk-v5/src/prompt-caching/multi-message-cache.ts + */ + +import { LARGE_SYSTEM_PROMPT } from '@openrouter-examples/shared/constants'; +import { createOpenRouter } from '@openrouter/ai-sdk-provider'; +import { generateText } from 'ai'; + +const openrouter = createOpenRouter({ + apiKey: process.env.OPENROUTER_API_KEY, + extraBody: { + stream_options: { include_usage: true }, + }, +}); + +async function main() { + console.log('╔════════════════════════════════════════════════════════════════════════════╗'); + console.log('║ Anthropic Prompt Caching - Multi-Message (AI SDK v5) ║'); + console.log('╚════════════════════════════════════════════════════════════════════════════╝'); + console.log(); + console.log('Testing cache_control in multi-turn conversation'); + console.log(); + + try { + const testId = Date.now(); + const model = openrouter('anthropic/claude-3-5-sonnet'); + const largeContext = `Test ${testId}: Context:\n\n${LARGE_SYSTEM_PROMPT}`; + + // First call with conversation history + console.log('First Call (Cache Miss Expected)'); + const result1 = await generateText({ + model, + messages: [ + { + role: 'user', + content: [ + { + type: 'text', + text: largeContext, + providerOptions: { + openrouter: { + cacheControl: { type: 'ephemeral' }, + }, + }, + }, + { + type: 'text', + text: "Hello, what's your purpose?", + }, + ], + }, + { + role: 'assistant', + content: "I'm an AI assistant designed to help with various tasks.", + }, + { + role: 'user', + content: 'What programming languages do you know?', + }, + ], + }); + + const cached1 = result1.providerMetadata?.openrouter?.usage?.promptTokensDetails?.cachedTokens ?? 0; + console.log(` Response: ${result1.text.substring(0, 80)}...`); + console.log(` cached_tokens=${cached1}`); + + await new Promise((resolve) => setTimeout(resolve, 1000)); + + // Second identical call - should hit cache + console.log('\nSecond Call (Cache Hit Expected)'); + const result2 = await generateText({ + model, + messages: [ + { + role: 'user', + content: [ + { + type: 'text', + text: largeContext, + providerOptions: { + openrouter: { + cacheControl: { type: 'ephemeral' }, + }, + }, + }, + { + type: 'text', + text: "Hello, what's your purpose?", + }, + ], + }, + { + role: 'assistant', + content: "I'm an AI assistant designed to help with various tasks.", + }, + { + role: 'user', + content: 'What programming languages do you know?', + }, + ], + }); + + const cached2 = result2.providerMetadata?.openrouter?.usage?.promptTokensDetails?.cachedTokens ?? 0; + console.log(` Response: ${result2.text.substring(0, 80)}...`); + console.log(` cached_tokens=${cached2}`); + + // Analysis + console.log('\n' + '='.repeat(80)); + console.log('ANALYSIS'); + console.log('='.repeat(80)); + console.log(`First call: cached_tokens=${cached1} (expected: 0)`); + console.log(`Second call: cached_tokens=${cached2} (expected: >0)`); + + const success = cached1 === 0 && cached2 > 0; + console.log(`\nResult: ${success ? '✓ CACHE WORKING' : '✗ CACHE NOT WORKING'}`); + + if (success) { + console.log('\n✓ SUCCESS - Multi-message caching is working correctly'); + } else { + console.log('\n✗ FAILURE - Multi-message caching is not working as expected'); + } + } catch (error) { + console.error('\n❌ ERROR:', error); + process.exit(1); + } +} + +main(); diff --git a/typescript/ai-sdk-v5/src/prompt-caching/no-cache-control.ts b/typescript/ai-sdk-v5/src/prompt-caching/no-cache-control.ts new file mode 100644 index 0000000..6cc349c --- /dev/null +++ b/typescript/ai-sdk-v5/src/prompt-caching/no-cache-control.ts @@ -0,0 +1,119 @@ +/** + * Example: Anthropic Prompt Caching - Control (No cache_control) (AI SDK v5) + * + * This is a CONTROL scenario demonstrating that without cache_control, + * no caching occurs. + * + * Purpose: Validates that cache behavior is due to cache_control, not coincidence + * + * To run: bun run typescript/ai-sdk-v5/src/prompt-caching/no-cache-control.ts + */ + +import { LARGE_SYSTEM_PROMPT } from '@openrouter-examples/shared/constants'; +import { createOpenRouter } from '@openrouter/ai-sdk-provider'; +import { generateText } from 'ai'; + +const openrouter = createOpenRouter({ + apiKey: process.env.OPENROUTER_API_KEY, + extraBody: { + stream_options: { include_usage: true }, + }, +}); + +async function main() { + console.log('╔════════════════════════════════════════════════════════════════════════════╗'); + console.log('║ Anthropic Prompt Caching - Control (No cache_control) (AI SDK v5) ║'); + console.log('╚════════════════════════════════════════════════════════════════════════════╝'); + console.log(); + console.log('Testing WITHOUT cache_control (control scenario)'); + console.log(); + console.log('Expected behavior:'); + console.log(' 1st call: cached_tokens = 0 (no cache_control)'); + console.log(' 2nd call: cached_tokens = 0 (no cache_control)'); + console.log(); + + try { + const testId = Date.now(); + const model = openrouter('anthropic/claude-3-5-sonnet'); + const largeContext = `Test ${testId}: Context:\n\n${LARGE_SYSTEM_PROMPT}`; + + // First call - NO cache_control + console.log('First Call (No Cache Expected)'); + const result1 = await generateText({ + model, + messages: [ + { + role: 'user', + content: [ + { + type: 'text', + text: largeContext, + // NO cache_control - this is the control + }, + { + type: 'text', + text: 'What are the key principles?', + }, + ], + }, + ], + }); + + const cached1 = result1.providerMetadata?.openrouter?.usage?.promptTokensDetails?.cachedTokens ?? 0; + console.log(` cached_tokens=${cached1}`); + + await new Promise((resolve) => setTimeout(resolve, 1000)); + + // Second call - still NO cache_control + console.log('\nSecond Call (No Cache Expected)'); + const result2 = await generateText({ + model, + messages: [ + { + role: 'user', + content: [ + { + type: 'text', + text: largeContext, + // NO cache_control + }, + { + type: 'text', + text: 'What are the key principles?', + }, + ], + }, + ], + }); + + const cached2 = result2.providerMetadata?.openrouter?.usage?.promptTokensDetails?.cachedTokens ?? 0; + console.log(` cached_tokens=${cached2}`); + + // Analysis + console.log('\n' + '='.repeat(80)); + console.log('ANALYSIS (CONTROL)'); + console.log('='.repeat(80)); + console.log(`First call: cached_tokens=${cached1} (expected: 0)`); + console.log(`Second call: cached_tokens=${cached2} (expected: 0)`); + + if (cached1 === 0 && cached2 === 0) { + console.log('✓ No cache metrics present (expected for control - no cache_control)'); + } else { + console.log('✗ Unexpected cache metrics in control scenario'); + } + + const success = cached1 === 0 && cached2 === 0; + console.log(`\nResult: ${success ? '✓ CONTROL VALID' : '✗ CONTROL INVALID'}`); + + if (success) { + console.log('\n✓ SUCCESS - Control scenario confirms no false positives'); + } else { + console.log('\n✗ FAILURE - Control scenario shows unexpected cache behavior'); + } + } catch (error) { + console.error('\n❌ ERROR:', error); + process.exit(1); + } +} + +main(); diff --git a/typescript/ai-sdk-v5/src/prompt-caching/user-message-cache.ts b/typescript/ai-sdk-v5/src/prompt-caching/user-message-cache.ts new file mode 100644 index 0000000..61d785e --- /dev/null +++ b/typescript/ai-sdk-v5/src/prompt-caching/user-message-cache.ts @@ -0,0 +1,165 @@ +/** + * Example: Anthropic Prompt Caching - User Message (AI SDK v5) + * + * This example demonstrates Anthropic prompt caching on a user message via OpenRouter + * using Vercel AI SDK v5. + * + * Pattern: User message with providerOptions.openrouter.cacheControl + * - User message with content array + * - cache_control on text content block via providerOptions + * + * CRITICAL CONFIGURATION: + * - **MUST** include extraBody: { stream_options: { include_usage: true } } + * - Without this, usage details (including cached_tokens) are not populated + * + * To run: bun run typescript/ai-sdk-v5/src/prompt-caching/user-message-cache.ts + */ + +import { LARGE_SYSTEM_PROMPT } from '@openrouter-examples/shared/constants'; +import { createOpenRouter } from '@openrouter/ai-sdk-provider'; +import { generateText } from 'ai'; + +// Create the OpenRouter provider +// CRITICAL: extraBody with stream_options is REQUIRED for usage details +const openrouter = createOpenRouter({ + apiKey: process.env.OPENROUTER_API_KEY, + extraBody: { + stream_options: { include_usage: true }, // Required for cached_tokens field + }, +}); + +/** + * Main example + */ +async function main() { + console.log('╔════════════════════════════════════════════════════════════════════════════╗'); + console.log('║ Anthropic Prompt Caching - User Message (AI SDK v5) ║'); + console.log('╚════════════════════════════════════════════════════════════════════════════╝'); + console.log(); + console.log('Testing cache_control on user message content block'); + console.log( + `Context size: ${LARGE_SYSTEM_PROMPT.length} characters (~${Math.round(LARGE_SYSTEM_PROMPT.length / 4)} tokens)`, + ); + console.log(); + console.log('Expected behavior:'); + console.log(' 1st call: cached_tokens = 0 (cache miss, creates cache)'); + console.log(' 2nd call: cached_tokens > 0 (cache hit, reads from cache)'); + console.log(); + + try { + const testId = Date.now(); + const model = openrouter('anthropic/claude-3-5-sonnet'); + + // Use large context in user message + const largeContext = `Test ${testId}: Here is a comprehensive codebase to analyze:\n\n${LARGE_SYSTEM_PROMPT}`; + + // First call - should create cache + console.log('First Call (Cache Miss Expected)'); + const result1 = await generateText({ + model, + messages: [ + { + role: 'user', + content: [ + { + type: 'text', + text: largeContext, + providerOptions: { + openrouter: { + cacheControl: { type: 'ephemeral' }, // Cache this content block + }, + }, + }, + { + type: 'text', + text: 'Based on this codebase, what are the main patterns used?', + }, + ], + }, + ], + }); + + console.log(' Response:', result1.text.substring(0, 100) + '...'); + const usage1 = result1.providerMetadata?.openrouter?.usage; + const cached1 = usage1?.promptTokensDetails?.cachedTokens ?? 0; + console.log(` Tokens: prompt=${usage1?.promptTokens}, completion=${usage1?.completionTokens}, cached=${cached1}`); + + // Wait 1 second + await new Promise((resolve) => setTimeout(resolve, 1000)); + + // Second identical call - should hit cache + console.log('\nSecond Call (Cache Hit Expected)'); + const result2 = await generateText({ + model, + messages: [ + { + role: 'user', + content: [ + { + type: 'text', + text: largeContext, + providerOptions: { + openrouter: { + cacheControl: { type: 'ephemeral' }, + }, + }, + }, + { + type: 'text', + text: 'Based on this codebase, what are the main patterns used?', + }, + ], + }, + ], + }); + + console.log(' Response:', result2.text.substring(0, 100) + '...'); + const usage2 = result2.providerMetadata?.openrouter?.usage; + const cached2 = usage2?.promptTokensDetails?.cachedTokens ?? 0; + console.log(` Tokens: prompt=${usage2?.promptTokens}, completion=${usage2?.completionTokens}, cached=${cached2}`); + + // Analysis + console.log('\n' + '='.repeat(80)); + console.log('ANALYSIS'); + console.log('='.repeat(80)); + + console.log(`First call: cached_tokens=${cached1} (expected: 0)`); + console.log(`Second call: cached_tokens=${cached2} (expected: >0)`); + + if (cached1 === 0) { + console.log('✓ First call cache miss (created cache)'); + } else { + console.log(`⚠ First call unexpectedly had cached tokens: ${cached1}`); + } + + if (cached2 > 0) { + console.log(`✓ Second call cache hit: ${cached2} tokens read from cache`); + } else { + console.log(`✗ Second call did NOT hit cache`); + } + + const success = cached1 === 0 && cached2 > 0; + console.log(`\nResult: ${success ? '✓ CACHE WORKING' : '✗ CACHE NOT WORKING'}`); + + if (success) { + console.log('\n════════════════════════════════════════════════════════════════════════════'); + console.log('✓ SUCCESS - User message caching is working correctly'); + console.log('════════════════════════════════════════════════════════════════════════════'); + } else { + console.log('\n════════════════════════════════════════════════════════════════════════════'); + console.log('✗ FAILURE - User message caching is not working as expected'); + console.log('════════════════════════════════════════════════════════════════════════════'); + } + } catch (error) { + console.error('\n❌ ERROR during testing:'); + if (error instanceof Error) { + console.error('Error message:', error.message); + console.error('Stack trace:', error.stack); + } else { + console.error('Unknown error:', error); + } + process.exit(1); + } +} + +main(); diff --git a/typescript/ai-sdk-v5/tsconfig.json b/typescript/ai-sdk-v5/tsconfig.json new file mode 100644 index 0000000..822a727 --- /dev/null +++ b/typescript/ai-sdk-v5/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "lib": ["ES2022"], + "moduleResolution": "bundler", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "types": ["bun-types"] + }, + "include": ["src/**/*"], + "exclude": ["node_modules"] +} diff --git a/typescript/package.json b/typescript/package.json index 9624171..4b437ad 100644 --- a/typescript/package.json +++ b/typescript/package.json @@ -11,7 +11,8 @@ }, "workspaces": [ "shared", - "fetch" + "fetch", + "ai-sdk-v5" ], "devDependencies": { "@types/bun": "1.3.2", From 6edf6b1d20a75d79a6d5670ee189d1be6406a3c7 Mon Sep 17 00:00:00 2001 From: Tom Aylott Date: Tue, 11 Nov 2025 18:16:05 -0500 Subject: [PATCH 02/11] Run biome format and fix lint issues --- .../src/prompt-caching/multi-message-cache.ts | 6 ++++-- .../ai-sdk-v5/src/prompt-caching/no-cache-control.ts | 6 ++++-- .../ai-sdk-v5/src/prompt-caching/user-message-cache.ts | 10 +++++++--- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/typescript/ai-sdk-v5/src/prompt-caching/multi-message-cache.ts b/typescript/ai-sdk-v5/src/prompt-caching/multi-message-cache.ts index 8f0ef64..d700a15 100644 --- a/typescript/ai-sdk-v5/src/prompt-caching/multi-message-cache.ts +++ b/typescript/ai-sdk-v5/src/prompt-caching/multi-message-cache.ts @@ -69,7 +69,8 @@ async function main() { ], }); - const cached1 = result1.providerMetadata?.openrouter?.usage?.promptTokensDetails?.cachedTokens ?? 0; + const cached1 = + result1.providerMetadata?.openrouter?.usage?.promptTokensDetails?.cachedTokens ?? 0; console.log(` Response: ${result1.text.substring(0, 80)}...`); console.log(` cached_tokens=${cached1}`); @@ -109,7 +110,8 @@ async function main() { ], }); - const cached2 = result2.providerMetadata?.openrouter?.usage?.promptTokensDetails?.cachedTokens ?? 0; + const cached2 = + result2.providerMetadata?.openrouter?.usage?.promptTokensDetails?.cachedTokens ?? 0; console.log(` Response: ${result2.text.substring(0, 80)}...`); console.log(` cached_tokens=${cached2}`); diff --git a/typescript/ai-sdk-v5/src/prompt-caching/no-cache-control.ts b/typescript/ai-sdk-v5/src/prompt-caching/no-cache-control.ts index 6cc349c..7766b58 100644 --- a/typescript/ai-sdk-v5/src/prompt-caching/no-cache-control.ts +++ b/typescript/ai-sdk-v5/src/prompt-caching/no-cache-control.ts @@ -59,7 +59,8 @@ async function main() { ], }); - const cached1 = result1.providerMetadata?.openrouter?.usage?.promptTokensDetails?.cachedTokens ?? 0; + const cached1 = + result1.providerMetadata?.openrouter?.usage?.promptTokensDetails?.cachedTokens ?? 0; console.log(` cached_tokens=${cached1}`); await new Promise((resolve) => setTimeout(resolve, 1000)); @@ -86,7 +87,8 @@ async function main() { ], }); - const cached2 = result2.providerMetadata?.openrouter?.usage?.promptTokensDetails?.cachedTokens ?? 0; + const cached2 = + result2.providerMetadata?.openrouter?.usage?.promptTokensDetails?.cachedTokens ?? 0; console.log(` cached_tokens=${cached2}`); // Analysis diff --git a/typescript/ai-sdk-v5/src/prompt-caching/user-message-cache.ts b/typescript/ai-sdk-v5/src/prompt-caching/user-message-cache.ts index 61d785e..cf33161 100644 --- a/typescript/ai-sdk-v5/src/prompt-caching/user-message-cache.ts +++ b/typescript/ai-sdk-v5/src/prompt-caching/user-message-cache.ts @@ -82,7 +82,9 @@ async function main() { console.log(' Response:', result1.text.substring(0, 100) + '...'); const usage1 = result1.providerMetadata?.openrouter?.usage; const cached1 = usage1?.promptTokensDetails?.cachedTokens ?? 0; - console.log(` Tokens: prompt=${usage1?.promptTokens}, completion=${usage1?.completionTokens}, cached=${cached1}`); + console.log( + ` Tokens: prompt=${usage1?.promptTokens}, completion=${usage1?.completionTokens}, cached=${cached1}`, + ); // Wait 1 second await new Promise((resolve) => setTimeout(resolve, 1000)); @@ -116,7 +118,9 @@ async function main() { console.log(' Response:', result2.text.substring(0, 100) + '...'); const usage2 = result2.providerMetadata?.openrouter?.usage; const cached2 = usage2?.promptTokensDetails?.cachedTokens ?? 0; - console.log(` Tokens: prompt=${usage2?.promptTokens}, completion=${usage2?.completionTokens}, cached=${cached2}`); + console.log( + ` Tokens: prompt=${usage2?.promptTokens}, completion=${usage2?.completionTokens}, cached=${cached2}`, + ); // Analysis console.log('\n' + '='.repeat(80)); @@ -135,7 +139,7 @@ async function main() { if (cached2 > 0) { console.log(`✓ Second call cache hit: ${cached2} tokens read from cache`); } else { - console.log(`✗ Second call did NOT hit cache`); + console.log('✗ Second call did NOT hit cache'); } const success = cached1 === 0 && cached2 > 0; From 7432d35500fe2d79928f590f080c1a9230e3f2bb Mon Sep 17 00:00:00 2001 From: Tom Aylott Date: Tue, 11 Nov 2025 18:40:32 -0500 Subject: [PATCH 03/11] Simplify AI SDK v5 prompt-caching README to link to main docs --- .../ai-sdk-v5/src/prompt-caching/README.md | 101 +++++------------- 1 file changed, 26 insertions(+), 75 deletions(-) diff --git a/typescript/ai-sdk-v5/src/prompt-caching/README.md b/typescript/ai-sdk-v5/src/prompt-caching/README.md index 5682c51..a8e5197 100644 --- a/typescript/ai-sdk-v5/src/prompt-caching/README.md +++ b/typescript/ai-sdk-v5/src/prompt-caching/README.md @@ -1,100 +1,51 @@ -# Anthropic Prompt Caching Examples (AI SDK v5) +# Prompt Caching Examples (AI SDK v5) -This directory contains examples demonstrating Anthropic's prompt caching feature via OpenRouter using Vercel AI SDK v5. +Examples demonstrating prompt caching with Vercel AI SDK v5. -## What is Prompt Caching? +## Documentation -Anthropic's prompt caching allows you to cache large portions of your prompts to: -- **Reduce costs** - Cached tokens cost significantly less -- **Improve latency** - Cached content is processed faster -- **Enable larger contexts** - Use more context without proportional cost increases +For full prompt caching documentation including all providers, pricing, and configuration details, see: +- **[Prompt Caching Guide](../../../../docs/prompt-caching.md)** -Cache TTL: 5 minutes for ephemeral caches +## Examples in This Directory -## Examples +- `user-message-cache.ts` - Cache large context in user messages +- `multi-message-cache.ts` - Cache system prompt across multi-turn conversations +- `no-cache-control.ts` - Control scenario (validates methodology) + +## Quick Start -### User Message Cache (`user-message-cache.ts`) -Cache large context in user messages using AI SDK: ```bash +# Run an example bun run typescript/ai-sdk-v5/src/prompt-caching/user-message-cache.ts ``` -**Pattern**: User message with `providerOptions.openrouter.cacheControl` - -## How to Use with AI SDK +## AI SDK v5 Usage ```typescript import { createOpenRouter } from '@openrouter/ai-sdk-provider'; -import { generateText } from 'ai'; -// CRITICAL: Must include stream_options for usage details const openrouter = createOpenRouter({ - apiKey: process.env.OPENROUTER_API_KEY, extraBody: { - stream_options: { include_usage: true }, // Required! + stream_options: { include_usage: true }, // Required for cache metrics }, }); +// Use providerOptions.openrouter.cacheControl on content items const result = await generateText({ model: openrouter('anthropic/claude-3.5-sonnet'), - messages: [ - { - role: 'user', - content: [ - { - type: 'text', - text: 'Large context here...', - providerOptions: { - openrouter: { - cacheControl: { type: 'ephemeral' }, // Cache this block - }, - }, - }, - { - type: 'text', - text: 'Your question here', - }, - ], - }, - ], + messages: [{ + role: 'user', + content: [{ + type: 'text', + text: 'Large context...', + providerOptions: { + openrouter: { cacheControl: { type: 'ephemeral' } } + } + }] + }] }); // Check cache metrics -const cachedTokens = result.providerMetadata?.openrouter?.usage?.promptTokensDetails?.cachedTokens ?? 0; -``` - -## Important Notes - -### Critical Configuration -**MUST include `extraBody: { stream_options: { include_usage: true } }`** -- Without this, usage details (including cached_tokens) are not populated -- This is a provider-level configuration, not per-request - -### Cache Metrics Location -Cache metrics are in `providerMetadata.openrouter.usage`: -```typescript -{ - promptTokens: number, - completionTokens: number, - promptTokensDetails: { - cachedTokens: number // Number of tokens read from cache - } -} +const cached = result.providerMetadata?.openrouter?.usage?.promptTokensDetails?.cachedTokens ?? 0; ``` - -### Requirements -1. **stream_options.include_usage = true** - CRITICAL for usage details -2. **Minimum 2048+ tokens** - Smaller content may not be cached -3. **providerOptions.openrouter.cacheControl** - On content items, not messages -4. **Exact match** - Cache only hits on identical content - -### Expected Behavior -- **First call**: `cachedTokens = 0` (cache miss, creates cache) -- **Second call**: `cachedTokens > 0` (cache hit, reads from cache) - -## Scientific Method -All examples follow evidence-based verification: -- **Hypothesis**: providerOptions.openrouter.cacheControl triggers caching -- **Experiment**: Make identical calls twice -- **Evidence**: Measure via providerMetadata.openrouter.usage -- **Analysis**: Compare cache miss vs cache hit From e2a09b9c3163543b9da5809bb792139c937feefb Mon Sep 17 00:00:00 2001 From: Tom Aylott Date: Tue, 11 Nov 2025 18:47:59 -0500 Subject: [PATCH 04/11] Rename prompt caching examples with anthropic prefix --- .../{multi-message-cache.ts => anthropic-multi-message-cache.ts} | 0 .../{no-cache-control.ts => anthropic-no-cache-control.ts} | 0 .../{user-message-cache.ts => anthropic-user-message-cache.ts} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename typescript/ai-sdk-v5/src/prompt-caching/{multi-message-cache.ts => anthropic-multi-message-cache.ts} (100%) rename typescript/ai-sdk-v5/src/prompt-caching/{no-cache-control.ts => anthropic-no-cache-control.ts} (100%) rename typescript/ai-sdk-v5/src/prompt-caching/{user-message-cache.ts => anthropic-user-message-cache.ts} (100%) diff --git a/typescript/ai-sdk-v5/src/prompt-caching/multi-message-cache.ts b/typescript/ai-sdk-v5/src/prompt-caching/anthropic-multi-message-cache.ts similarity index 100% rename from typescript/ai-sdk-v5/src/prompt-caching/multi-message-cache.ts rename to typescript/ai-sdk-v5/src/prompt-caching/anthropic-multi-message-cache.ts diff --git a/typescript/ai-sdk-v5/src/prompt-caching/no-cache-control.ts b/typescript/ai-sdk-v5/src/prompt-caching/anthropic-no-cache-control.ts similarity index 100% rename from typescript/ai-sdk-v5/src/prompt-caching/no-cache-control.ts rename to typescript/ai-sdk-v5/src/prompt-caching/anthropic-no-cache-control.ts diff --git a/typescript/ai-sdk-v5/src/prompt-caching/user-message-cache.ts b/typescript/ai-sdk-v5/src/prompt-caching/anthropic-user-message-cache.ts similarity index 100% rename from typescript/ai-sdk-v5/src/prompt-caching/user-message-cache.ts rename to typescript/ai-sdk-v5/src/prompt-caching/anthropic-user-message-cache.ts From a8bb014fc015d51086443a1ce9312b9fc6d83ad0 Mon Sep 17 00:00:00 2001 From: Tom Aylott Date: Wed, 12 Nov 2025 17:01:26 -0500 Subject: [PATCH 05/11] Update @openrouter/ai-sdk-provider to 1.2.2 --- typescript/ai-sdk-v5/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typescript/ai-sdk-v5/package.json b/typescript/ai-sdk-v5/package.json index cea8ebe..2c4efd5 100644 --- a/typescript/ai-sdk-v5/package.json +++ b/typescript/ai-sdk-v5/package.json @@ -9,7 +9,7 @@ }, "dependencies": { "@openrouter-examples/shared": "workspace:*", - "@openrouter/ai-sdk-provider": "^1.2.1", + "@openrouter/ai-sdk-provider": "1.2.2", "ai": "^5.0.92" }, "devDependencies": { From adb56f85cab6f495163422b00e5f88a6fd52e87f Mon Sep 17 00:00:00 2001 From: Tom Aylott Date: Wed, 12 Nov 2025 17:04:35 -0500 Subject: [PATCH 06/11] Fix type errors in prompt-caching examples with @ts-expect-error Add FIXME comments noting that providerMetadata.openrouter.usage should be properly typed as OpenRouterUsage instead of JSONValue. --- .../src/prompt-caching/anthropic-multi-message-cache.ts | 4 ++++ .../src/prompt-caching/anthropic-no-cache-control.ts | 4 ++++ .../src/prompt-caching/anthropic-user-message-cache.ts | 6 ++++++ 3 files changed, 14 insertions(+) diff --git a/typescript/ai-sdk-v5/src/prompt-caching/anthropic-multi-message-cache.ts b/typescript/ai-sdk-v5/src/prompt-caching/anthropic-multi-message-cache.ts index d700a15..e2b1925 100644 --- a/typescript/ai-sdk-v5/src/prompt-caching/anthropic-multi-message-cache.ts +++ b/typescript/ai-sdk-v5/src/prompt-caching/anthropic-multi-message-cache.ts @@ -69,7 +69,9 @@ async function main() { ], }); + // FIXME: providerMetadata.openrouter.usage should have proper type with promptTokensDetails const cached1 = + // @ts-expect-error - usage is typed as JSONValue but should be OpenRouterUsage result1.providerMetadata?.openrouter?.usage?.promptTokensDetails?.cachedTokens ?? 0; console.log(` Response: ${result1.text.substring(0, 80)}...`); console.log(` cached_tokens=${cached1}`); @@ -110,7 +112,9 @@ async function main() { ], }); + // FIXME: providerMetadata.openrouter.usage should have proper type with promptTokensDetails const cached2 = + // @ts-expect-error - usage is typed as JSONValue but should be OpenRouterUsage result2.providerMetadata?.openrouter?.usage?.promptTokensDetails?.cachedTokens ?? 0; console.log(` Response: ${result2.text.substring(0, 80)}...`); console.log(` cached_tokens=${cached2}`); diff --git a/typescript/ai-sdk-v5/src/prompt-caching/anthropic-no-cache-control.ts b/typescript/ai-sdk-v5/src/prompt-caching/anthropic-no-cache-control.ts index 7766b58..41c7d78 100644 --- a/typescript/ai-sdk-v5/src/prompt-caching/anthropic-no-cache-control.ts +++ b/typescript/ai-sdk-v5/src/prompt-caching/anthropic-no-cache-control.ts @@ -59,7 +59,9 @@ async function main() { ], }); + // FIXME: providerMetadata.openrouter.usage should have proper type with promptTokensDetails const cached1 = + // @ts-expect-error - usage is typed as JSONValue but should be OpenRouterUsage result1.providerMetadata?.openrouter?.usage?.promptTokensDetails?.cachedTokens ?? 0; console.log(` cached_tokens=${cached1}`); @@ -87,7 +89,9 @@ async function main() { ], }); + // FIXME: providerMetadata.openrouter.usage should have proper type with promptTokensDetails const cached2 = + // @ts-expect-error - usage is typed as JSONValue but should be OpenRouterUsage result2.providerMetadata?.openrouter?.usage?.promptTokensDetails?.cachedTokens ?? 0; console.log(` cached_tokens=${cached2}`); diff --git a/typescript/ai-sdk-v5/src/prompt-caching/anthropic-user-message-cache.ts b/typescript/ai-sdk-v5/src/prompt-caching/anthropic-user-message-cache.ts index cf33161..93ff704 100644 --- a/typescript/ai-sdk-v5/src/prompt-caching/anthropic-user-message-cache.ts +++ b/typescript/ai-sdk-v5/src/prompt-caching/anthropic-user-message-cache.ts @@ -81,8 +81,11 @@ async function main() { console.log(' Response:', result1.text.substring(0, 100) + '...'); const usage1 = result1.providerMetadata?.openrouter?.usage; + // FIXME: providerMetadata.openrouter.usage should have proper type with promptTokensDetails, promptTokens, completionTokens + // @ts-expect-error - usage is typed as JSONValue but should be OpenRouterUsage const cached1 = usage1?.promptTokensDetails?.cachedTokens ?? 0; console.log( + // @ts-expect-error - usage is typed as JSONValue but should be OpenRouterUsage ` Tokens: prompt=${usage1?.promptTokens}, completion=${usage1?.completionTokens}, cached=${cached1}`, ); @@ -117,8 +120,11 @@ async function main() { console.log(' Response:', result2.text.substring(0, 100) + '...'); const usage2 = result2.providerMetadata?.openrouter?.usage; + // FIXME: providerMetadata.openrouter.usage should have proper type with promptTokensDetails, promptTokens, completionTokens + // @ts-expect-error - usage is typed as JSONValue but should be OpenRouterUsage const cached2 = usage2?.promptTokensDetails?.cachedTokens ?? 0; console.log( + // @ts-expect-error - usage is typed as JSONValue but should be OpenRouterUsage ` Tokens: prompt=${usage2?.promptTokens}, completion=${usage2?.completionTokens}, cached=${cached2}`, ); From e540a7cfe9d80e14412174e2a6ba196dbcce59db Mon Sep 17 00:00:00 2001 From: Tom Aylott Date: Wed, 12 Nov 2025 17:05:09 -0500 Subject: [PATCH 07/11] Fix documentation issues in prompt-caching examples MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Update package.json scripts to use correct anthropic- prefixed filenames - Fix file path references in comments and README - Fix model name consistency (claude-3.5-sonnet → claude-3-5-sonnet) - Update README to reference directory instead of single file --- typescript/ai-sdk-v5/README.md | 2 +- typescript/ai-sdk-v5/package.json | 2 +- typescript/ai-sdk-v5/src/prompt-caching/README.md | 10 +++++----- .../src/prompt-caching/anthropic-user-message-cache.ts | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/typescript/ai-sdk-v5/README.md b/typescript/ai-sdk-v5/README.md index 2a5aca7..ef5a521 100644 --- a/typescript/ai-sdk-v5/README.md +++ b/typescript/ai-sdk-v5/README.md @@ -20,7 +20,7 @@ bun examples ## Features -- [prompt-caching.ts](./src/prompt-caching.ts) - Anthropic caching with AI SDK v5 +- [prompt-caching](./src/prompt-caching/) - Anthropic caching examples with AI SDK v5 ### Key Configuration diff --git a/typescript/ai-sdk-v5/package.json b/typescript/ai-sdk-v5/package.json index 2c4efd5..d741d7e 100644 --- a/typescript/ai-sdk-v5/package.json +++ b/typescript/ai-sdk-v5/package.json @@ -4,7 +4,7 @@ "private": true, "type": "module", "scripts": { - "examples": "bun run src/prompt-caching/user-message-cache.ts && bun run src/prompt-caching/multi-message-cache.ts && bun run src/prompt-caching/no-cache-control.ts", + "examples": "bun run src/prompt-caching/anthropic-user-message-cache.ts && bun run src/prompt-caching/anthropic-multi-message-cache.ts && bun run src/prompt-caching/anthropic-no-cache-control.ts", "typecheck": "tsc --noEmit" }, "dependencies": { diff --git a/typescript/ai-sdk-v5/src/prompt-caching/README.md b/typescript/ai-sdk-v5/src/prompt-caching/README.md index a8e5197..5cd694c 100644 --- a/typescript/ai-sdk-v5/src/prompt-caching/README.md +++ b/typescript/ai-sdk-v5/src/prompt-caching/README.md @@ -9,15 +9,15 @@ For full prompt caching documentation including all providers, pricing, and conf ## Examples in This Directory -- `user-message-cache.ts` - Cache large context in user messages -- `multi-message-cache.ts` - Cache system prompt across multi-turn conversations -- `no-cache-control.ts` - Control scenario (validates methodology) +- `anthropic-user-message-cache.ts` - Cache large context in user messages +- `anthropic-multi-message-cache.ts` - Cache system prompt across multi-turn conversations +- `anthropic-no-cache-control.ts` - Control scenario (validates methodology) ## Quick Start ```bash # Run an example -bun run typescript/ai-sdk-v5/src/prompt-caching/user-message-cache.ts +bun run typescript/ai-sdk-v5/src/prompt-caching/anthropic-user-message-cache.ts ``` ## AI SDK v5 Usage @@ -33,7 +33,7 @@ const openrouter = createOpenRouter({ // Use providerOptions.openrouter.cacheControl on content items const result = await generateText({ - model: openrouter('anthropic/claude-3.5-sonnet'), + model: openrouter('anthropic/claude-3-5-sonnet'), messages: [{ role: 'user', content: [{ diff --git a/typescript/ai-sdk-v5/src/prompt-caching/anthropic-user-message-cache.ts b/typescript/ai-sdk-v5/src/prompt-caching/anthropic-user-message-cache.ts index 93ff704..8a9be74 100644 --- a/typescript/ai-sdk-v5/src/prompt-caching/anthropic-user-message-cache.ts +++ b/typescript/ai-sdk-v5/src/prompt-caching/anthropic-user-message-cache.ts @@ -12,7 +12,7 @@ * - **MUST** include extraBody: { stream_options: { include_usage: true } } * - Without this, usage details (including cached_tokens) are not populated * - * To run: bun run typescript/ai-sdk-v5/src/prompt-caching/user-message-cache.ts + * To run: bun run typescript/ai-sdk-v5/src/prompt-caching/anthropic-user-message-cache.ts */ import { LARGE_SYSTEM_PROMPT } from '@openrouter-examples/shared/constants'; From a57cfb8795026cfc1e3eb28a2921a76e90e127b2 Mon Sep 17 00:00:00 2001 From: Tom Aylott Date: Wed, 12 Nov 2025 17:19:10 -0500 Subject: [PATCH 08/11] Remove code snippet with model name from README Copilot feedback: Model name 'claude-3-5-sonnet' vs 'claude-3.5-sonnet' inconsistency. Solution: Remove 'AI SDK v5 Usage' section entirely - complete code in example files. Prevents docs from going stale when models/APIs change. --- .../ai-sdk-v5/src/prompt-caching/README.md | 30 +------------------ 1 file changed, 1 insertion(+), 29 deletions(-) diff --git a/typescript/ai-sdk-v5/src/prompt-caching/README.md b/typescript/ai-sdk-v5/src/prompt-caching/README.md index 5cd694c..44e99d7 100644 --- a/typescript/ai-sdk-v5/src/prompt-caching/README.md +++ b/typescript/ai-sdk-v5/src/prompt-caching/README.md @@ -20,32 +20,4 @@ For full prompt caching documentation including all providers, pricing, and conf bun run typescript/ai-sdk-v5/src/prompt-caching/anthropic-user-message-cache.ts ``` -## AI SDK v5 Usage - -```typescript -import { createOpenRouter } from '@openrouter/ai-sdk-provider'; - -const openrouter = createOpenRouter({ - extraBody: { - stream_options: { include_usage: true }, // Required for cache metrics - }, -}); - -// Use providerOptions.openrouter.cacheControl on content items -const result = await generateText({ - model: openrouter('anthropic/claude-3-5-sonnet'), - messages: [{ - role: 'user', - content: [{ - type: 'text', - text: 'Large context...', - providerOptions: { - openrouter: { cacheControl: { type: 'ephemeral' } } - } - }] - }] -}); - -// Check cache metrics -const cached = result.providerMetadata?.openrouter?.usage?.promptTokensDetails?.cachedTokens ?? 0; -``` +See the example files for complete working code with current models and configuration. From 7703d4fd0bdc1e8f0193a0d385295636313b3100 Mon Sep 17 00:00:00 2001 From: Tom Aylott Date: Wed, 12 Nov 2025 17:21:31 -0500 Subject: [PATCH 09/11] Use glob pattern runner instead of listing filenames MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace hardcoded filename list with run-examples.ts that auto-discovers all .ts files in src/ Benefits: - Add new example → automatically included in 'bun examples' - Rename example → no package.json update needed - Impossible for package.json to reference non-existent files --- typescript/ai-sdk-v5/package.json | 2 +- typescript/ai-sdk-v5/run-examples.ts | 57 ++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100755 typescript/ai-sdk-v5/run-examples.ts diff --git a/typescript/ai-sdk-v5/package.json b/typescript/ai-sdk-v5/package.json index d741d7e..ee55441 100644 --- a/typescript/ai-sdk-v5/package.json +++ b/typescript/ai-sdk-v5/package.json @@ -4,7 +4,7 @@ "private": true, "type": "module", "scripts": { - "examples": "bun run src/prompt-caching/anthropic-user-message-cache.ts && bun run src/prompt-caching/anthropic-multi-message-cache.ts && bun run src/prompt-caching/anthropic-no-cache-control.ts", + "examples": "bun run run-examples.ts", "typecheck": "tsc --noEmit" }, "dependencies": { diff --git a/typescript/ai-sdk-v5/run-examples.ts b/typescript/ai-sdk-v5/run-examples.ts new file mode 100755 index 0000000..67429a3 --- /dev/null +++ b/typescript/ai-sdk-v5/run-examples.ts @@ -0,0 +1,57 @@ +#!/usr/bin/env bun +/** + * Run all example files in the src/ directory + * Each example is run in a separate process to handle process.exit() calls + */ + +import { readdirSync, statSync } from 'fs'; +import { join } from 'path'; +import { $ } from 'bun'; + +const srcDir = join(import.meta.dir, 'src'); + +// Recursively find all .ts files in src/ +function findExamples(dir: string): string[] { + const entries = readdirSync(dir); + const files: string[] = []; + + for (const entry of entries) { + const fullPath = join(dir, entry); + const stat = statSync(fullPath); + + if (stat.isDirectory()) { + files.push(...findExamples(fullPath)); + } else if (entry.endsWith('.ts')) { + files.push(fullPath); + } + } + + return files.sort(); +} + +const examples = findExamples(srcDir); +console.log(`Found ${examples.length} example(s)\n`); + +let failed = 0; +for (const example of examples) { + const relativePath = example.replace(import.meta.dir + '/', ''); + console.log(`\n${'='.repeat(80)}`); + console.log(`Running: ${relativePath}`); + console.log('='.repeat(80)); + + try { + await $`bun run ${example}`.quiet(); + console.log(`✅ ${relativePath} completed successfully`); + } catch (error) { + console.error(`❌ ${relativePath} failed`); + failed++; + } +} + +console.log(`\n${'='.repeat(80)}`); +console.log(`Results: ${examples.length - failed}/${examples.length} passed`); +console.log('='.repeat(80)); + +if (failed > 0) { + process.exit(1); +} From e6ef74e9e1310757ed04ef5924d0942e7e8477dd Mon Sep 17 00:00:00 2001 From: Tom Aylott Date: Wed, 12 Nov 2025 18:01:52 -0500 Subject: [PATCH 10/11] Remove filename references from docs to prevent sync issues --- typescript/ai-sdk-v5/src/prompt-caching/README.md | 13 +------------ .../prompt-caching/anthropic-multi-message-cache.ts | 2 -- .../prompt-caching/anthropic-no-cache-control.ts | 2 -- .../prompt-caching/anthropic-user-message-cache.ts | 2 -- 4 files changed, 1 insertion(+), 18 deletions(-) diff --git a/typescript/ai-sdk-v5/src/prompt-caching/README.md b/typescript/ai-sdk-v5/src/prompt-caching/README.md index 44e99d7..edc6327 100644 --- a/typescript/ai-sdk-v5/src/prompt-caching/README.md +++ b/typescript/ai-sdk-v5/src/prompt-caching/README.md @@ -9,15 +9,4 @@ For full prompt caching documentation including all providers, pricing, and conf ## Examples in This Directory -- `anthropic-user-message-cache.ts` - Cache large context in user messages -- `anthropic-multi-message-cache.ts` - Cache system prompt across multi-turn conversations -- `anthropic-no-cache-control.ts` - Control scenario (validates methodology) - -## Quick Start - -```bash -# Run an example -bun run typescript/ai-sdk-v5/src/prompt-caching/anthropic-user-message-cache.ts -``` - -See the example files for complete working code with current models and configuration. +See the TypeScript files in this directory for specific examples with complete working code. diff --git a/typescript/ai-sdk-v5/src/prompt-caching/anthropic-multi-message-cache.ts b/typescript/ai-sdk-v5/src/prompt-caching/anthropic-multi-message-cache.ts index e2b1925..b3b4d9c 100644 --- a/typescript/ai-sdk-v5/src/prompt-caching/anthropic-multi-message-cache.ts +++ b/typescript/ai-sdk-v5/src/prompt-caching/anthropic-multi-message-cache.ts @@ -7,8 +7,6 @@ * Pattern: User message cache in multi-turn conversation * - Cache large context in first user message * - Cache persists through conversation history - * - * To run: bun run typescript/ai-sdk-v5/src/prompt-caching/multi-message-cache.ts */ import { LARGE_SYSTEM_PROMPT } from '@openrouter-examples/shared/constants'; diff --git a/typescript/ai-sdk-v5/src/prompt-caching/anthropic-no-cache-control.ts b/typescript/ai-sdk-v5/src/prompt-caching/anthropic-no-cache-control.ts index 41c7d78..9383583 100644 --- a/typescript/ai-sdk-v5/src/prompt-caching/anthropic-no-cache-control.ts +++ b/typescript/ai-sdk-v5/src/prompt-caching/anthropic-no-cache-control.ts @@ -5,8 +5,6 @@ * no caching occurs. * * Purpose: Validates that cache behavior is due to cache_control, not coincidence - * - * To run: bun run typescript/ai-sdk-v5/src/prompt-caching/no-cache-control.ts */ import { LARGE_SYSTEM_PROMPT } from '@openrouter-examples/shared/constants'; diff --git a/typescript/ai-sdk-v5/src/prompt-caching/anthropic-user-message-cache.ts b/typescript/ai-sdk-v5/src/prompt-caching/anthropic-user-message-cache.ts index 8a9be74..86e159c 100644 --- a/typescript/ai-sdk-v5/src/prompt-caching/anthropic-user-message-cache.ts +++ b/typescript/ai-sdk-v5/src/prompt-caching/anthropic-user-message-cache.ts @@ -11,8 +11,6 @@ * CRITICAL CONFIGURATION: * - **MUST** include extraBody: { stream_options: { include_usage: true } } * - Without this, usage details (including cached_tokens) are not populated - * - * To run: bun run typescript/ai-sdk-v5/src/prompt-caching/anthropic-user-message-cache.ts */ import { LARGE_SYSTEM_PROMPT } from '@openrouter-examples/shared/constants'; From bfdfeebe5cc9530b8e490e42651146a4fd2e8378 Mon Sep 17 00:00:00 2001 From: Tom Aylott Date: Wed, 12 Nov 2025 18:42:47 -0500 Subject: [PATCH 11/11] docs: link to OpenRouter docs instead of local docs - Replace local docs link with https://openrouter.ai/docs/features/prompt-caching - Ensures documentation stays current and avoids duplication --- typescript/ai-sdk-v5/src/prompt-caching/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typescript/ai-sdk-v5/src/prompt-caching/README.md b/typescript/ai-sdk-v5/src/prompt-caching/README.md index edc6327..b50cdec 100644 --- a/typescript/ai-sdk-v5/src/prompt-caching/README.md +++ b/typescript/ai-sdk-v5/src/prompt-caching/README.md @@ -5,7 +5,7 @@ Examples demonstrating prompt caching with Vercel AI SDK v5. ## Documentation For full prompt caching documentation including all providers, pricing, and configuration details, see: -- **[Prompt Caching Guide](../../../../docs/prompt-caching.md)** +- **[OpenRouter Prompt Caching Guide](https://openrouter.ai/docs/features/prompt-caching)** ## Examples in This Directory