@tanstack/ai-anthropic 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/LICENSE +21 -0
  2. package/dist/esm/anthropic-adapter.d.ts +69 -0
  3. package/dist/esm/anthropic-adapter.js +473 -0
  4. package/dist/esm/anthropic-adapter.js.map +1 -0
  5. package/dist/esm/index.d.ts +5 -0
  6. package/dist/esm/index.js +9 -0
  7. package/dist/esm/index.js.map +1 -0
  8. package/dist/esm/message-types.d.ts +69 -0
  9. package/dist/esm/model-meta.d.ts +215 -0
  10. package/dist/esm/model-meta.js +42 -0
  11. package/dist/esm/model-meta.js.map +1 -0
  12. package/dist/esm/text/text-provider-options.d.ts +139 -0
  13. package/dist/esm/text/text-provider-options.js +30 -0
  14. package/dist/esm/text/text-provider-options.js.map +1 -0
  15. package/dist/esm/tools/bash-tool.d.ts +5 -0
  16. package/dist/esm/tools/bash-tool.js +8 -0
  17. package/dist/esm/tools/bash-tool.js.map +1 -0
  18. package/dist/esm/tools/code-execution-tool.d.ts +5 -0
  19. package/dist/esm/tools/code-execution-tool.js +8 -0
  20. package/dist/esm/tools/code-execution-tool.js.map +1 -0
  21. package/dist/esm/tools/computer-use-tool.d.ts +5 -0
  22. package/dist/esm/tools/computer-use-tool.js +8 -0
  23. package/dist/esm/tools/computer-use-tool.js.map +1 -0
  24. package/dist/esm/tools/custom-tool.d.ts +25 -0
  25. package/dist/esm/tools/custom-tool.js +21 -0
  26. package/dist/esm/tools/custom-tool.js.map +1 -0
  27. package/dist/esm/tools/index.d.ts +10 -0
  28. package/dist/esm/tools/memory-tool.d.ts +5 -0
  29. package/dist/esm/tools/memory-tool.js +11 -0
  30. package/dist/esm/tools/memory-tool.js.map +1 -0
  31. package/dist/esm/tools/text-editor-tool.d.ts +5 -0
  32. package/dist/esm/tools/text-editor-tool.js +10 -0
  33. package/dist/esm/tools/text-editor-tool.js.map +1 -0
  34. package/dist/esm/tools/tool-converter.d.ts +27 -0
  35. package/dist/esm/tools/tool-converter.js +35 -0
  36. package/dist/esm/tools/tool-converter.js.map +1 -0
  37. package/dist/esm/tools/web-fetch-tool.d.ts +5 -0
  38. package/dist/esm/tools/web-fetch-tool.js +12 -0
  39. package/dist/esm/tools/web-fetch-tool.js.map +1 -0
  40. package/dist/esm/tools/web-search-tool.d.ts +5 -0
  41. package/dist/esm/tools/web-search-tool.js +16 -0
  42. package/dist/esm/tools/web-search-tool.js.map +1 -0
  43. package/package.json +53 -0
  44. package/src/anthropic-adapter.ts +680 -0
  45. package/src/index.ts +25 -0
  46. package/src/message-types.ts +85 -0
  47. package/src/model-meta.ts +482 -0
  48. package/src/text/text-provider-options.ts +205 -0
  49. package/src/tools/bash-tool.ts +19 -0
  50. package/src/tools/code-execution-tool.ts +24 -0
  51. package/src/tools/computer-use-tool.ts +24 -0
  52. package/src/tools/custom-tool.ts +64 -0
  53. package/src/tools/index.ts +30 -0
  54. package/src/tools/memory-tool.ts +20 -0
  55. package/src/tools/text-editor-tool.ts +28 -0
  56. package/src/tools/tool-converter.ts +61 -0
  57. package/src/tools/web-fetch-tool.ts +23 -0
  58. package/src/tools/web-search-tool.ts +81 -0
@@ -0,0 +1,680 @@
1
+ import Anthropic_SDK from '@anthropic-ai/sdk'
2
+ import { BaseAdapter } from '@tanstack/ai'
3
+ import { ANTHROPIC_MODELS } from './model-meta'
4
+ import { convertToolsToProviderFormat } from './tools/tool-converter'
5
+ import { validateTextProviderOptions } from './text/text-provider-options'
6
+ import type {
7
+ AnthropicImageMetadata,
8
+ AnthropicMessageMetadataByModality,
9
+ } from './message-types'
10
+ import type {
11
+ ChatOptions,
12
+ ContentPart,
13
+ EmbeddingOptions,
14
+ EmbeddingResult,
15
+ ModelMessage,
16
+ StreamChunk,
17
+ SummarizationOptions,
18
+ SummarizationResult,
19
+ } from '@tanstack/ai'
20
+ import type {
21
+ AnthropicChatModelProviderOptionsByName,
22
+ AnthropicModelInputModalitiesByName,
23
+ } from './model-meta'
24
+ import type {
25
+ ExternalTextProviderOptions,
26
+ InternalTextProviderOptions,
27
+ } from './text/text-provider-options'
28
+ import type {
29
+ Base64ImageSource,
30
+ Base64PDFSource,
31
+ DocumentBlockParam,
32
+ ImageBlockParam,
33
+ MessageParam,
34
+ TextBlockParam,
35
+ URLImageSource,
36
+ URLPDFSource,
37
+ } from '@anthropic-ai/sdk/resources/messages'
38
+
39
+ export interface AnthropicConfig {
40
+ apiKey: string
41
+ }
42
+
43
+ /**
44
+ * Anthropic-specific provider options
45
+ * @see https://ai-sdk.dev/providers/ai-sdk-providers/anthropic
46
+ */
47
+ export type AnthropicProviderOptions = ExternalTextProviderOptions
48
+
49
+ type AnthropicContentBlocks =
50
+ Extract<MessageParam['content'], Array<unknown>> extends Array<infer Block>
51
+ ? Array<Block>
52
+ : never
53
+ type AnthropicContentBlock =
54
+ AnthropicContentBlocks extends Array<infer Block> ? Block : never
55
+
56
+ export class Anthropic extends BaseAdapter<
57
+ typeof ANTHROPIC_MODELS,
58
+ [],
59
+ AnthropicProviderOptions,
60
+ Record<string, any>,
61
+ AnthropicChatModelProviderOptionsByName,
62
+ AnthropicModelInputModalitiesByName,
63
+ AnthropicMessageMetadataByModality
64
+ > {
65
+ name = 'anthropic' as const
66
+ models = ANTHROPIC_MODELS
67
+
68
+ declare _modelProviderOptionsByName: AnthropicChatModelProviderOptionsByName
69
+ declare _modelInputModalitiesByName: AnthropicModelInputModalitiesByName
70
+ declare _messageMetadataByModality: AnthropicMessageMetadataByModality
71
+
72
+ private client: Anthropic_SDK
73
+
74
+ constructor(config: AnthropicConfig) {
75
+ super({})
76
+ this.client = new Anthropic_SDK({
77
+ apiKey: config.apiKey,
78
+ })
79
+ }
80
+
81
+ async *chatStream(
82
+ options: ChatOptions<string, AnthropicProviderOptions>,
83
+ ): AsyncIterable<StreamChunk> {
84
+ try {
85
+ // Map common options to Anthropic format using the centralized mapping function
86
+ const requestParams = this.mapCommonOptionsToAnthropic(options)
87
+
88
+ const stream = await this.client.beta.messages.create(
89
+ { ...requestParams, stream: true },
90
+ {
91
+ signal: options.request?.signal,
92
+ headers: options.request?.headers,
93
+ },
94
+ )
95
+
96
+ yield* this.processAnthropicStream(stream, options.model, () =>
97
+ this.generateId(),
98
+ )
99
+ } catch (error: any) {
100
+ console.error('[Anthropic Adapter] Error in chatStream:', {
101
+ message: error?.message,
102
+ status: error?.status,
103
+ statusText: error?.statusText,
104
+ code: error?.code,
105
+ type: error?.type,
106
+ error: error,
107
+ stack: error?.stack,
108
+ })
109
+
110
+ // Emit an error chunk
111
+ yield {
112
+ type: 'error',
113
+ id: this.generateId(),
114
+ model: options.model,
115
+ timestamp: Date.now(),
116
+ error: {
117
+ message: error?.message || 'Unknown error occurred',
118
+ code: error?.code || error?.status,
119
+ },
120
+ }
121
+ }
122
+ }
123
+
124
+ async summarize(options: SummarizationOptions): Promise<SummarizationResult> {
125
+ const systemPrompt = this.buildSummarizationPrompt(options)
126
+
127
+ const response = await this.client.messages.create({
128
+ model: options.model,
129
+ messages: [{ role: 'user', content: options.text }],
130
+ system: systemPrompt,
131
+ max_tokens: options.maxLength || 500,
132
+ temperature: 0.3,
133
+ stream: false,
134
+ })
135
+
136
+ const content = response.content
137
+ .map((c) => (c.type === 'text' ? c.text : ''))
138
+ .join('')
139
+
140
+ return {
141
+ id: response.id,
142
+ model: response.model,
143
+ summary: content,
144
+ usage: {
145
+ promptTokens: response.usage.input_tokens,
146
+ completionTokens: response.usage.output_tokens,
147
+ totalTokens: response.usage.input_tokens + response.usage.output_tokens,
148
+ },
149
+ }
150
+ }
151
+
152
+ createEmbeddings(_options: EmbeddingOptions): Promise<EmbeddingResult> {
153
+ // Note: Anthropic doesn't have a native embeddings API
154
+ // You would need to use a different service or implement a workaround
155
+ throw new Error(
156
+ 'Embeddings are not natively supported by Anthropic. Consider using OpenAI or another provider for embeddings.',
157
+ )
158
+ }
159
+
160
+ private buildSummarizationPrompt(options: SummarizationOptions): string {
161
+ let prompt = 'You are a professional summarizer. '
162
+
163
+ switch (options.style) {
164
+ case 'bullet-points':
165
+ prompt += 'Provide a summary in bullet point format. '
166
+ break
167
+ case 'paragraph':
168
+ prompt += 'Provide a summary in paragraph format. '
169
+ break
170
+ case 'concise':
171
+ prompt += 'Provide a very concise summary in 1-2 sentences. '
172
+ break
173
+ default:
174
+ prompt += 'Provide a clear and concise summary. '
175
+ }
176
+
177
+ if (options.focus && options.focus.length > 0) {
178
+ prompt += `Focus on the following aspects: ${options.focus.join(', ')}. `
179
+ }
180
+
181
+ if (options.maxLength) {
182
+ prompt += `Keep the summary under ${options.maxLength} tokens. `
183
+ }
184
+
185
+ return prompt
186
+ }
187
+
188
+ /**
189
+ * Maps common options to Anthropic-specific format
190
+ * Handles translation of normalized options to Anthropic's API format
191
+ */
192
+ private mapCommonOptionsToAnthropic(
193
+ options: ChatOptions<string, AnthropicProviderOptions>,
194
+ ) {
195
+ const providerOptions = options.providerOptions as
196
+ | InternalTextProviderOptions
197
+ | undefined
198
+
199
+ const formattedMessages = this.formatMessages(options.messages)
200
+ const tools = options.tools
201
+ ? convertToolsToProviderFormat(options.tools)
202
+ : undefined
203
+
204
+ // Filter out invalid fields from providerOptions (like 'store' which is OpenAI-specific)
205
+ const validProviderOptions: Partial<InternalTextProviderOptions> = {}
206
+ if (providerOptions) {
207
+ const validKeys: Array<keyof InternalTextProviderOptions> = [
208
+ 'container',
209
+ 'context_management',
210
+ 'mcp_servers',
211
+ 'service_tier',
212
+ 'stop_sequences',
213
+ 'system',
214
+ 'thinking',
215
+ 'tool_choice',
216
+ 'top_k',
217
+ ]
218
+ for (const key of validKeys) {
219
+ if (key in providerOptions) {
220
+ const value = providerOptions[key]
221
+ // Anthropic expects tool_choice to be an object, not a string
222
+ if (key === 'tool_choice' && typeof value === 'string') {
223
+ ;(validProviderOptions as any)[key] = { type: value }
224
+ } else {
225
+ ;(validProviderOptions as any)[key] = value
226
+ }
227
+ }
228
+ }
229
+ }
230
+
231
+ // Ensure max_tokens is greater than thinking.budget_tokens if thinking is enabled
232
+ const thinkingBudget =
233
+ validProviderOptions.thinking?.type === 'enabled'
234
+ ? validProviderOptions.thinking.budget_tokens
235
+ : undefined
236
+ const defaultMaxTokens = options.options?.maxTokens || 1024
237
+ const maxTokens =
238
+ thinkingBudget && thinkingBudget >= defaultMaxTokens
239
+ ? thinkingBudget + 1 // Ensure max_tokens is greater than budget_tokens
240
+ : defaultMaxTokens
241
+
242
+ const requestParams: InternalTextProviderOptions = {
243
+ model: options.model,
244
+ max_tokens: maxTokens,
245
+ temperature: options.options?.temperature,
246
+ top_p: options.options?.topP,
247
+ messages: formattedMessages,
248
+ system: options.systemPrompts?.join('\n'),
249
+ tools: tools,
250
+ ...validProviderOptions,
251
+ }
252
+ validateTextProviderOptions(requestParams)
253
+ return requestParams
254
+ }
255
+
256
+ private convertContentPartToAnthropic(
257
+ part: ContentPart,
258
+ ): TextBlockParam | ImageBlockParam | DocumentBlockParam {
259
+ switch (part.type) {
260
+ case 'text':
261
+ return {
262
+ type: 'text',
263
+ text: part.content,
264
+ }
265
+ case 'image': {
266
+ const metadata = part.metadata as any as
267
+ | AnthropicImageMetadata
268
+ | undefined
269
+ const imageSource: Base64ImageSource | URLImageSource =
270
+ part.source.type === 'data'
271
+ ? {
272
+ type: 'base64',
273
+ data: part.source.value,
274
+ media_type: metadata?.mediaType ?? 'image/jpeg',
275
+ }
276
+ : {
277
+ type: 'url',
278
+ url: part.source.value,
279
+ }
280
+ return {
281
+ type: 'image',
282
+ source: imageSource,
283
+ }
284
+ }
285
+ case 'document': {
286
+ const docSource: Base64PDFSource | URLPDFSource =
287
+ part.source.type === 'data'
288
+ ? {
289
+ type: 'base64',
290
+ data: part.source.value,
291
+ media_type: 'application/pdf',
292
+ }
293
+ : {
294
+ type: 'url',
295
+ url: part.source.value,
296
+ }
297
+ return {
298
+ type: 'document',
299
+ source: docSource,
300
+ }
301
+ }
302
+ case 'audio':
303
+ case 'video':
304
+ // Anthropic doesn't support audio/video directly, treat as text with a note
305
+ throw new Error(
306
+ `Anthropic does not support ${part.type} content directly`,
307
+ )
308
+ default: {
309
+ // Exhaustive check - this should never happen with known types
310
+ const _exhaustiveCheck: never = part
311
+ throw new Error(
312
+ `Unsupported content part type: ${(_exhaustiveCheck as ContentPart).type}`,
313
+ )
314
+ }
315
+ }
316
+ }
317
+
318
+ private formatMessages(
319
+ messages: Array<ModelMessage>,
320
+ ): InternalTextProviderOptions['messages'] {
321
+ const formattedMessages: InternalTextProviderOptions['messages'] = []
322
+
323
+ for (const message of messages) {
324
+ const role = message.role
325
+
326
+ if (role === 'tool' && message.toolCallId) {
327
+ formattedMessages.push({
328
+ role: 'user',
329
+ content: [
330
+ {
331
+ type: 'tool_result',
332
+ tool_use_id: message.toolCallId,
333
+ content:
334
+ typeof message.content === 'string' ? message.content : '',
335
+ },
336
+ ],
337
+ })
338
+ continue
339
+ }
340
+
341
+ if (role === 'assistant' && message.toolCalls?.length) {
342
+ const contentBlocks: AnthropicContentBlocks = []
343
+
344
+ if (message.content) {
345
+ const content =
346
+ typeof message.content === 'string' ? message.content : ''
347
+ const textBlock: AnthropicContentBlock = {
348
+ type: 'text',
349
+ text: content,
350
+ }
351
+ contentBlocks.push(textBlock)
352
+ }
353
+
354
+ for (const toolCall of message.toolCalls) {
355
+ let parsedInput: unknown = {}
356
+ try {
357
+ parsedInput = toolCall.function.arguments
358
+ ? JSON.parse(toolCall.function.arguments)
359
+ : {}
360
+ } catch {
361
+ parsedInput = toolCall.function.arguments
362
+ }
363
+
364
+ const toolUseBlock: AnthropicContentBlock = {
365
+ type: 'tool_use',
366
+ id: toolCall.id,
367
+ name: toolCall.function.name,
368
+ input: parsedInput,
369
+ }
370
+ contentBlocks.push(toolUseBlock)
371
+ }
372
+
373
+ formattedMessages.push({
374
+ role: 'assistant',
375
+ content: contentBlocks,
376
+ })
377
+
378
+ continue
379
+ }
380
+
381
+ // Handle user messages with multimodal content
382
+ if (role === 'user' && Array.isArray(message.content)) {
383
+ const contentBlocks = message.content.map((part) =>
384
+ this.convertContentPartToAnthropic(part),
385
+ )
386
+ formattedMessages.push({
387
+ role: 'user',
388
+ content: contentBlocks,
389
+ })
390
+ continue
391
+ }
392
+
393
+ formattedMessages.push({
394
+ role: role === 'assistant' ? 'assistant' : 'user',
395
+ content:
396
+ typeof message.content === 'string'
397
+ ? message.content
398
+ : message.content
399
+ ? message.content.map((c) =>
400
+ this.convertContentPartToAnthropic(c),
401
+ )
402
+ : '',
403
+ })
404
+ }
405
+
406
+ return formattedMessages
407
+ }
408
+
409
+ private async *processAnthropicStream(
410
+ stream: AsyncIterable<Anthropic_SDK.Beta.BetaRawMessageStreamEvent>,
411
+ model: string,
412
+ generateId: () => string,
413
+ ): AsyncIterable<StreamChunk> {
414
+ let accumulatedContent = ''
415
+ let accumulatedThinking = ''
416
+ const timestamp = Date.now()
417
+ const toolCallsMap = new Map<
418
+ number,
419
+ { id: string; name: string; input: string }
420
+ >()
421
+ let currentToolIndex = -1
422
+
423
+ try {
424
+ for await (const event of stream) {
425
+ if (event.type === 'content_block_start') {
426
+ if (event.content_block.type === 'tool_use') {
427
+ currentToolIndex++
428
+ toolCallsMap.set(currentToolIndex, {
429
+ id: event.content_block.id,
430
+ name: event.content_block.name,
431
+ input: '',
432
+ })
433
+ } else if (event.content_block.type === 'thinking') {
434
+ // Reset thinking content when a new thinking block starts
435
+ accumulatedThinking = ''
436
+ }
437
+ } else if (event.type === 'content_block_delta') {
438
+ if (event.delta.type === 'text_delta') {
439
+ const delta = event.delta.text
440
+ accumulatedContent += delta
441
+ yield {
442
+ type: 'content',
443
+ id: generateId(),
444
+ model: model,
445
+ timestamp,
446
+ delta,
447
+ content: accumulatedContent,
448
+ role: 'assistant',
449
+ }
450
+ } else if (event.delta.type === 'thinking_delta') {
451
+ // Handle thinking content
452
+ const delta = event.delta.thinking
453
+ accumulatedThinking += delta
454
+ yield {
455
+ type: 'thinking',
456
+ id: generateId(),
457
+ model: model,
458
+ timestamp,
459
+ delta,
460
+ content: accumulatedThinking,
461
+ }
462
+ } else if (event.delta.type === 'input_json_delta') {
463
+ // Tool input is being streamed
464
+ const existing = toolCallsMap.get(currentToolIndex)
465
+ if (existing) {
466
+ // Accumulate the input for final processing
467
+ existing.input += event.delta.partial_json
468
+
469
+ // Yield the DELTA (partial_json), not the full accumulated input
470
+ // The stream processor will concatenate these deltas
471
+ yield {
472
+ type: 'tool_call',
473
+ id: generateId(),
474
+ model: model,
475
+ timestamp,
476
+ toolCall: {
477
+ id: existing.id,
478
+ type: 'function',
479
+ function: {
480
+ name: existing.name,
481
+ arguments: event.delta.partial_json,
482
+ },
483
+ },
484
+ index: currentToolIndex,
485
+ }
486
+ }
487
+ }
488
+ } else if (event.type === 'content_block_stop') {
489
+ // If this is a tool call and we haven't received any input deltas,
490
+ // emit a tool_call chunk with empty arguments
491
+ const existing = toolCallsMap.get(currentToolIndex)
492
+ if (existing && existing.input === '') {
493
+ // No input_json_delta events received, emit empty arguments
494
+ yield {
495
+ type: 'tool_call',
496
+ id: generateId(),
497
+ model: model,
498
+ timestamp,
499
+ toolCall: {
500
+ id: existing.id,
501
+ type: 'function',
502
+ function: {
503
+ name: existing.name,
504
+ arguments: '{}',
505
+ },
506
+ },
507
+ index: currentToolIndex,
508
+ }
509
+ }
510
+ } else if (event.type === 'message_stop') {
511
+ yield {
512
+ type: 'done',
513
+ id: generateId(),
514
+ model: model,
515
+ timestamp,
516
+ finishReason: 'stop',
517
+ }
518
+ } else if (event.type === 'message_delta') {
519
+ if (event.delta.stop_reason) {
520
+ switch (event.delta.stop_reason) {
521
+ case 'tool_use': {
522
+ yield {
523
+ type: 'done',
524
+ id: generateId(),
525
+ model: model,
526
+ timestamp,
527
+ finishReason: 'tool_calls',
528
+
529
+ usage: {
530
+ promptTokens: event.usage.input_tokens || 0,
531
+ completionTokens: event.usage.output_tokens || 0,
532
+ totalTokens:
533
+ (event.usage.input_tokens || 0) +
534
+ (event.usage.output_tokens || 0),
535
+ },
536
+ }
537
+ break
538
+ }
539
+ case 'max_tokens': {
540
+ yield {
541
+ type: 'error',
542
+ id: generateId(),
543
+ model: model,
544
+ timestamp,
545
+ error: {
546
+ message:
547
+ 'The response was cut off because the maximum token limit was reached.',
548
+ code: 'max_tokens',
549
+ },
550
+ }
551
+ break
552
+ }
553
+ case 'model_context_window_exceeded': {
554
+ yield {
555
+ type: 'error',
556
+ id: generateId(),
557
+ model: model,
558
+ timestamp,
559
+ error: {
560
+ message:
561
+ "The response was cut off because the model's context window was exceeded.",
562
+ code: 'context_window_exceeded',
563
+ },
564
+ }
565
+ break
566
+ }
567
+ case 'refusal': {
568
+ yield {
569
+ type: 'error',
570
+ id: generateId(),
571
+ model: model,
572
+ timestamp,
573
+ error: {
574
+ message: 'The model refused to complete the request.',
575
+ code: 'refusal',
576
+ },
577
+ }
578
+ break
579
+ }
580
+ default: {
581
+ yield {
582
+ type: 'done',
583
+ id: generateId(),
584
+ model: model,
585
+ timestamp,
586
+ finishReason: 'stop',
587
+ usage: {
588
+ promptTokens: event.usage.input_tokens || 0,
589
+ completionTokens: event.usage.output_tokens || 0,
590
+ totalTokens:
591
+ (event.usage.input_tokens || 0) +
592
+ (event.usage.output_tokens || 0),
593
+ },
594
+ }
595
+ }
596
+ }
597
+ }
598
+ }
599
+ }
600
+ } catch (error: any) {
601
+ console.error('[Anthropic Adapter] Error in processAnthropicStream:', {
602
+ message: error?.message,
603
+ status: error?.status,
604
+ statusText: error?.statusText,
605
+ code: error?.code,
606
+ type: error?.type,
607
+ error: error,
608
+ stack: error?.stack,
609
+ })
610
+
611
+ yield {
612
+ type: 'error',
613
+ id: generateId(),
614
+ model: model,
615
+ timestamp,
616
+ error: {
617
+ message: error?.message || 'Unknown error occurred',
618
+ code: error?.code || error?.status,
619
+ },
620
+ }
621
+ }
622
+ }
623
+ }
624
+ /**
625
+ * Creates an Anthropic adapter with simplified configuration
626
+ * @param apiKey - Your Anthropic API key
627
+ * @returns A fully configured Anthropic adapter instance
628
+ *
629
+ * @example
630
+ * ```typescript
631
+ * const anthropic = createAnthropic("sk-ant-...");
632
+ *
633
+ * const ai = new AI({
634
+ * adapters: {
635
+ * anthropic,
636
+ * }
637
+ * });
638
+ * ```
639
+ */
640
+ export function createAnthropic(
641
+ apiKey: string,
642
+ config?: Omit<AnthropicConfig, 'apiKey'>,
643
+ ): Anthropic {
644
+ return new Anthropic({ apiKey, ...config })
645
+ }
646
+
647
+ /**
648
+ * Create an Anthropic adapter with automatic API key detection from environment variables.
649
+ *
650
+ * Looks for `ANTHROPIC_API_KEY` in:
651
+ * - `process.env` (Node.js)
652
+ * - `window.env` (Browser with injected env)
653
+ *
654
+ * @param config - Optional configuration (excluding apiKey which is auto-detected)
655
+ * @returns Configured Anthropic adapter instance
656
+ * @throws Error if ANTHROPIC_API_KEY is not found in environment
657
+ *
658
+ * @example
659
+ * ```typescript
660
+ * // Automatically uses ANTHROPIC_API_KEY from environment
661
+ * const aiInstance = ai(anthropic());
662
+ * ```
663
+ */
664
+ export function anthropic(config?: Omit<AnthropicConfig, 'apiKey'>): Anthropic {
665
+ const env =
666
+ typeof globalThis !== 'undefined' && (globalThis as any).window?.env
667
+ ? (globalThis as any).window.env
668
+ : typeof process !== 'undefined'
669
+ ? process.env
670
+ : undefined
671
+ const key = env?.ANTHROPIC_API_KEY
672
+
673
+ if (!key) {
674
+ throw new Error(
675
+ 'ANTHROPIC_API_KEY is required. Please set it in your environment variables or use createAnthropic(apiKey, config) instead.',
676
+ )
677
+ }
678
+
679
+ return createAnthropic(key, config)
680
+ }
package/src/index.ts ADDED
@@ -0,0 +1,25 @@
1
+ export {
2
+ Anthropic,
3
+ createAnthropic,
4
+ anthropic,
5
+ type AnthropicConfig,
6
+ } from './anthropic-adapter'
7
+ export type {
8
+ AnthropicChatModelProviderOptionsByName,
9
+ AnthropicModelInputModalitiesByName,
10
+ } from './model-meta'
11
+ export type {
12
+ AnthropicImageMetadata,
13
+ AnthropicDocumentMetadata,
14
+ AnthropicAudioMetadata,
15
+ AnthropicVideoMetadata,
16
+ AnthropicImageMediaType,
17
+ AnthropicDocumentMediaType,
18
+ AnthropicMessageMetadataByModality,
19
+ } from './message-types'
20
+
21
+ // Export tool conversion utilities
22
+ export { convertToolsToProviderFormat } from './tools/tool-converter'
23
+
24
+ // Export tool types
25
+ export type { AnthropicTool, CustomTool } from './tools'