Skip to content

Conversation

@subtleGradient
Copy link
Contributor

No description provided.

…nfig

F0.1: Switch to Bun
- Run bun install to create bun.lock
- Keep pnpm-lock.yaml for backward compatibility
- Update scripts to use bun instead of pnpm

F0.2: Update package.json for v6
- Update version to 6.0.0-alpha.0
- Move @ai-sdk/provider (^3.0.0) and @ai-sdk/provider-utils (^4.0.0) to dependencies
- Update ai to ^6.0.0 in devDependencies
- Update peerDependencies: zod ^3.25 || ^4.1
- Remove ai from peerDependencies (devDep only)

F0.3: Update tsconfig.json
- Change target from es2017 to ES2022
- Clean up include patterns for src/ and e2e/
- Remove Next.js specific configuration
- S1.11: Add OpenRouterEmbeddingModel stub (EmbeddingModelV3)
- S1.12: Add OpenRouterImageModel stub (ImageModelV3, Tier 3)
- S1.13: Add OpenRouterProvider interface and createOpenRouter factory
- S1.14: Add internal/index.ts re-exports for advanced users
- S1.15: Add src/index.ts with public exports and default instance

Config updates:
- Add baseUrl alias (deprecated) for compatibility
- Add extraBody to provider settings
- Make plugin and model options allow arbitrary properties
- Add @effect-native/fetch-hooks dev dependency
- Add missing vi import to reasoning-multiturn e2e test
- Add non-null assertion to baseURL to fix type error
Implement streaming support for the V3 language model:
- Create OpenRouter client with settings (apiKey, baseURL)
- Call client.chat.send({ stream: true })
- Transform SDK's EventStream to AI SDK V3 stream parts
- Emit proper lifecycle: stream-start, text-start/delta/end, reasoning-start/delta/end, response-metadata, finish
- Wire up existing utils: convertToOpenRouterMessages, mapOpenRouterFinishReason, buildUsage, buildProviderMetadata
- Handle headers with combineHeaders + normalizeHeaders
- Support call options: maxOutputTokens, temperature, topP, frequencyPenalty, presencePenalty, seed, stopSequences
- Add e2e/setup.ts that configures @effect-native/fetch-hooks
- E2E_RECORD=1 enables recording mode (real API calls saved to fixtures)
- Default replay mode uses cached fixtures (no network calls)
- Sensitive headers (Authorization, x-api-key) are redacted
- Create e2e/__fixtures__/ directory for cached responses
- Update vitest.e2e.config.ts to use setup file
Implements LanguageModelV3 doGenerate method in OpenRouterChatLanguageModel:
- Calls OpenRouter SDK with stream: false for non-streaming requests
- Parses response to build content array (reasoning, text, tool-calls)
- Uses existing utils: buildUsage, buildProviderMetadata, mapOpenRouterFinishReason
- Returns full LanguageModelV3GenerateResult with content, usage, response metadata

Adds e2e fixtures for reasoning-effort tests.
Change message content types to match OpenRouter SDK expectations:
- text content: 'text' instead of 'input_text'/'output_text'
- image content: 'image_url' with nested imageUrl object
- file content: 'file' with fileUrl property

This fixes SDKValidationError when sending messages to OpenRouter.
The OpenRouter SDK's Chat API only accepts specific content types:
- text: { type: 'text', text: string }
- image_url: { type: 'image_url', imageUrl: { url, detail? } }
- audio/video types

The previous code used { type: 'file', fileUrl } for non-image files,
which caused SDKValidationError during request serialization.

Changed convertFilePart() to always use image_url format since
OpenRouter's Chat API uses this type for all URL-based content.
The file-parser plugin handles non-image files (PDFs, etc.) when enabled.
- Fix streaming logic to emit finish only after receiving usage chunk
  (OpenRouter sends finish_reason and usage in separate chunks)
- Update buildProviderMetadata to use camelCase fields from SDK
- Add state tracking for finish reason, provider, text/reasoning end
- Update tests to use camelCase (matching SDK transformed output)

Note: The @openrouter/sdk strips 'provider' and 'cost' fields from
API responses due to strict Zod parsing. Token usage data is working.
The @openrouter/sdk strips the cost field due to Zod schema limitations.
…rted

OpenRouter's Chat Completions API only supports image URLs via image_url type.
PDF URLs fail with 'unsupported format' errors from upstream providers.

Changes:
- Remove application/pdf from supportedUrls in language model
- Update pdf-url E2E test to test image URLs instead (renamed to image urls)
- Update pdf-blob tests to use Gemini (supports PDF data URIs)
- Adjust FileParserPlugin token threshold to be realistic (5000 vs 150)

PDF base64 data URIs work with Gemini through the Chat API.
For other models, use the OpenRouter Responses API for PDF support.
- Replace SDK streaming with native fetch to handle large annotation chunks
- The OpenRouter SDK has parsing issues with large JSON chunks containing web search annotations
- Native fetch properly parses the raw SSE stream and extracts url_citation annotations
- Convert raw snake_case API fields to camelCase for provider metadata
- Also pass through plugins, transforms, and other model options to streaming requests
- Update test to handle variable API behavior (sources may not always be returned)
- Pass cache_control from providerOptions.openrouter to message content
- Add cacheWriteTokens to promptTokensDetails in provider metadata
- Add imageTokens to completionTokensDetails in provider metadata
- Fix cache-control E2E test to use correct model and provider routing
- Add unit test for cache_control message conversion

The test was failing because:
1. OpenRouter was routing to Google instead of Anthropic
2. cache_control wasn't being passed through from providerOptions
3. cacheWriteTokens wasn't being extracted from API response
- Updated from ^0.0.2 to ^0.1.0 (released npm version)
- Fixed typecheck error in web-search test (narrow source type)
- Change from client.chat.send() to client.beta.responses.send()
- Update request format to OpenResponsesRequest (input instead of messages)
- Update response parsing for OpenResponsesNonStreamingResponse format
- Update convertToOpenRouterMessages to produce Responses API format:
  - type: 'input_text' instead of 'text' for user messages
  - type: 'input_image' instead of 'image_url' for images
  - type: 'input_file' for non-image files
  - callId (camelCase) instead of call_id for function calls
  - Simple string content for assistant messages
- Create separate convert-to-chat-completions-messages.ts for streaming
- doStream continues to use Chat Completions API (/chat/completions)
- doGenerate uses Responses API (via previous commit)
- Update E2E fixtures for new request formats
The Responses API requires a status field ('completed' | 'incomplete') for
function_call_output items. Successful tool results get 'completed', while
error outputs (error-text, error-json, execution-denied) get 'incomplete'.
- Add convertToolsToResponsesFormat to convert AI SDK function tools
  to OpenRouter Responses API format
- Add convertToolChoiceToResponsesFormat to map tool choice options:
  - 'auto', 'none', 'required' map directly
  - { type: 'tool', toolName } maps to { type: 'function', name }
- Include tools and toolChoice in request params when provided
- Emit 'unsupported' warnings for non-function tool types
- Response parsing for function_call output items was already implemented
The streaming e2e tests were failing due to:
1. Stale/corrupted fixture files that had malformed JSON chunks
2. @openrouter/sdk@0.3.11 now requires zod v4 for its 'zod/v4' exports

Changes:
- Upgraded zod from ^3.25 to 4.3.5 for SDK compatibility
- Regenerated all e2e fixtures with fresh API responses
- Streaming provider metadata now works correctly

Note: cache-control test still fails due to model not triggering
cache tokens (expected behavior, not a code issue).
Test constructor properties:
- specificationVersion is 'v3'
- provider is 'openrouter'
- modelId is set correctly
- maxEmbeddingsPerCall is 2048
- supportsParallelCalls is true
- implements EmbeddingModelV3 interface
Tests provider creation, callable interface, and all model factory methods:
- specificationVersion 'v3' verification
- provider(modelId) returns LanguageModelV3
- languageModel(), chat(), embeddingModel(), imageModel() methods
- Deprecated aliases (textEmbeddingModel, embedding)
- Provider configuration options (baseURL, headers, fetch, extraBody)
The OpenRouter API doesn't always return cache token data (depends on
provider/model support and cache availability). Updated test to:
- Verify promptTokensDetails structure exists without requiring values
- Add separate live-only test for verifying actual cache hits
- Log cache details for debugging visibility
Map AI SDK responseFormat option to OpenRouter Responses API text.format:
- { type: 'text' } -> { type: 'text' }
- { type: 'json' } -> { type: 'json_object' }
- { type: 'json', schema, name } -> { type: 'json_schema', name, schema }

This enables structured output / JSON schema constraints for models that
support it via the AI SDK generateObject() and streamObject() APIs.
Document that OpenRouter model variants (:online, :nitro, :floor, :free)
work automatically since the model ID is passed through unchanged.
docs: add npm alpha channel badge

docs: add alpha install instructions

docs: add Coming Soon section with planned features
@subtleGradient subtleGradient self-assigned this Jan 7, 2026
* ci: add PR snapshot workflow using pkg-pr-new

* ci: add contents:read permission and concurrency group
readonly provider = 'openrouter';
readonly modelId: string;

private readonly settings: OpenRouterModelSettings;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get Property 'settings' of exported anonymous class type may not be private or protected. ts(4094) from this btw

@brrock
Copy link

brrock commented Jan 16, 2026

ETA? I need this quick!

@wlib
Copy link

wlib commented Jan 18, 2026

@brrock npm i @openrouter/ai-sdk-provider@alpha for now

@davbai davbai closed this Jan 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants