A translation layer that lets you use the OpenAI API interface to interact with Anthropic's Claude models. Maps the OpenAI public API surface to Anthropic's API — models, endpoints, parameters, and data classes. Uses anthropic_sdk_dart under the hood.
dependencies:
open_ai_anthropic: ^0.1.0Then run dart pub get.
Use AnthropicOpenAIClient as a drop-in replacement for OpenAIClient. The rest of the OpenAI API remains the same — refer to openai_dart for full documentation.
import 'package:open_ai_anthropic/open_ai_anthropic.dart';
import 'package:openai_dart/openai_dart.dart';
final client = AnthropicOpenAIClient(apiKey: 'your-anthropic-api-key');
final response = await client.chat.completions.create(
ChatCompletionCreateRequest(
model: 'claude-sonnet-4-20250514',
messages: [
ChatMessage.system('You are a helpful assistant.'),
ChatMessage.user('Hello!'),
],
),
);
print(response.choices.first.message.content);final stream = client.chat.completions.createStream(
ChatCompletionCreateRequest(
model: 'claude-sonnet-4-20250514',
messages: [ChatMessage.user('Write a haiku.')],
),
);
await for (final chunk in stream) {
stdout.write(chunk.textDelta ?? '');
}For OAuth-based authentication (e.g. Claude Code tokens):
// From credentials JSON (auto-detects short-lived vs long-lived)
final credentialsJson = Platform.environment['CLAUDE_CODE_CREDENTIALS'];
final credentials = ClaudeCodeCredentials.fromJsonString(credentialsJson!);
final client = ClaudeCodeOpenAIClient(credentials: credentials);
// Short-lived OAuth credentials (with refresh token, ~8 hour expiry)
final credentials = ShortLivedClaudeCodeCredentials(
accessToken: 'oauth-access-token',
refreshToken: 'oauth-refresh-token',
expiresAt: DateTime.now().add(Duration(hours: 8)),
);
// Long-lived API key credentials (via `claude setup-token`, ~360 day expiry)
final credentials = LongLivedClaudeCodeCredentials(
accessToken: 'sk-ant-...',
expiresAt: DateTime.now().add(Duration(days: 360)),
);ClaudeCodeCredentials is a sealed class with two subtypes:
ShortLivedClaudeCodeCredentials— OAuth tokens with a refresh token. Expire in ~8 hours and can be auto-refreshed by the token store.isExpiredincludes a 5-minute proactive buffer.LongLivedClaudeCodeCredentials— API keys generated viaclaude setup-token. Valid for ~360 days, no refresh token, no expiration buffer.
The fromJson and fromJsonString factories on the sealed class auto-detect the subtype based on the presence of a non-empty refresh_token field.
You can also pass a ClaudeCodeTokenStore instead of credentials for custom token storage and refresh logic. The token store auto-refreshes short-lived credentials and throws StateError for expired long-lived credentials.
dart run open_ai_anthropic:generateFollow the terminal instructions to complete the OAuth flow. This generates a claude_code_credentials.json file and prints the credentials JSON for use as an environment variable.
Both AnthropicOpenAIClient and ClaudeCodeOpenAIClient accept an optional bodyTransformer callback. This receives the Anthropic request body as a mutable JSON map before it is sent to the API, allowing you to inject cache_control breakpoints or other provider-specific mutations.
final client = AnthropicOpenAIClient(
apiKey: 'your-key',
bodyTransformer: (body) {
// Cache the system message
final system = body['system'];
if (system is String) {
body['system'] = [
{
'type': 'text',
'text': system,
'cache_control': {'type': 'ephemeral'},
},
];
}
// Cache the last two user messages
final messages = body['messages'];
if (messages is! List) return;
final userIndices = <int>[];
for (int i = 0; i < messages.length; i++) {
if (messages[i] is Map && messages[i]['role'] == 'user') {
userIndices.add(i);
}
}
final lastTwo = userIndices.length <= 2
? userIndices
: userIndices.sublist(userIndices.length - 2);
for (final idx in lastTwo) {
final msg = messages[idx];
if (msg is! Map) continue;
final content = msg['content'];
if (content is String) {
msg['content'] = [
{
'type': 'text',
'text': content,
'cache_control': {'type': 'ephemeral'},
},
];
}
}
},
);The bodyTransformer works on the Anthropic-format JSON body (with system, messages, tools keys). This is the same shape used by existing cache breakpoint utilities like addCacheBreakpointsAnthropic().
Cache token usage is reported in both streaming and non-streaming responses:
final response = await client.chat.completions.create(request);
final usage = response.usage;
print('Total prompt tokens: ${usage?.promptTokens}'); // includes cached
print('Cached tokens: ${usage?.promptTokensDetails?.cachedTokens}'); // cache hits
print('Completion tokens: ${usage?.completionTokens}');promptTokens includes all input token categories (uncached + cache read + cache creation). promptTokensDetails.cachedTokens reports the cache-read subset, matching OpenAI's convention.
The OpenAI-format conversation history (List<ChatMessage>) can be shared seamlessly across providers. Tool calls, tool results, system messages, and assistant responses all translate 1:1 — no data is lost between OpenAI and Claude.