@tanstack/ai-anthropic 0.0.3 → 0.2.0
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.
- package/dist/esm/adapters/summarize.d.ts +54 -0
- package/dist/esm/adapters/summarize.js +117 -0
- package/dist/esm/adapters/summarize.js.map +1 -0
- package/dist/esm/adapters/text.d.ts +60 -0
- package/dist/esm/{anthropic-adapter.js → adapters/text.js} +109 -146
- package/dist/esm/adapters/text.js.map +1 -0
- package/dist/esm/index.d.ts +2 -1
- package/dist/esm/index.js +8 -4
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/text/text-provider-options.js.map +1 -1
- package/dist/esm/tools/custom-tool.d.ts +2 -3
- package/dist/esm/tools/custom-tool.js +6 -4
- package/dist/esm/tools/custom-tool.js.map +1 -1
- package/dist/esm/utils/client.d.ts +17 -0
- package/dist/esm/utils/client.js +25 -0
- package/dist/esm/utils/client.js.map +1 -0
- package/dist/esm/utils/index.d.ts +1 -0
- package/package.json +5 -6
- package/src/adapters/summarize.ts +200 -0
- package/src/{anthropic-adapter.ts → adapters/text.ts} +223 -262
- package/src/index.ts +25 -5
- package/src/text/text-provider-options.ts +1 -2
- package/src/tools/custom-tool.ts +10 -8
- package/src/utils/client.ts +45 -0
- package/src/utils/index.ts +6 -0
- package/dist/esm/anthropic-adapter.d.ts +0 -69
- package/dist/esm/anthropic-adapter.js.map +0 -1
- package/dist/esm/model-meta.js +0 -42
- package/dist/esm/model-meta.js.map +0 -1
|
@@ -1,32 +1,20 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
AnthropicMessageMetadataByModality,
|
|
10
|
-
AnthropicTextMetadata,
|
|
11
|
-
} from './message-types'
|
|
12
|
-
import type {
|
|
13
|
-
ChatOptions,
|
|
14
|
-
ContentPart,
|
|
15
|
-
EmbeddingOptions,
|
|
16
|
-
EmbeddingResult,
|
|
17
|
-
ModelMessage,
|
|
18
|
-
StreamChunk,
|
|
19
|
-
SummarizationOptions,
|
|
20
|
-
SummarizationResult,
|
|
21
|
-
} from '@tanstack/ai'
|
|
1
|
+
import { BaseTextAdapter } from '@tanstack/ai/adapters'
|
|
2
|
+
import { convertToolsToProviderFormat } from '../tools/tool-converter'
|
|
3
|
+
import { validateTextProviderOptions } from '../text/text-provider-options'
|
|
4
|
+
import {
|
|
5
|
+
createAnthropicClient,
|
|
6
|
+
generateId,
|
|
7
|
+
getAnthropicApiKeyFromEnv,
|
|
8
|
+
} from '../utils'
|
|
22
9
|
import type {
|
|
10
|
+
ANTHROPIC_MODELS,
|
|
23
11
|
AnthropicChatModelProviderOptionsByName,
|
|
24
12
|
AnthropicModelInputModalitiesByName,
|
|
25
|
-
} from '
|
|
13
|
+
} from '../model-meta'
|
|
26
14
|
import type {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
} from '
|
|
15
|
+
StructuredOutputOptions,
|
|
16
|
+
StructuredOutputResult,
|
|
17
|
+
} from '@tanstack/ai/adapters'
|
|
30
18
|
import type {
|
|
31
19
|
Base64ImageSource,
|
|
32
20
|
Base64PDFSource,
|
|
@@ -37,16 +25,35 @@ import type {
|
|
|
37
25
|
URLImageSource,
|
|
38
26
|
URLPDFSource,
|
|
39
27
|
} from '@anthropic-ai/sdk/resources/messages'
|
|
28
|
+
import type Anthropic_SDK from '@anthropic-ai/sdk'
|
|
29
|
+
import type {
|
|
30
|
+
ContentPart,
|
|
31
|
+
Modality,
|
|
32
|
+
ModelMessage,
|
|
33
|
+
StreamChunk,
|
|
34
|
+
TextOptions,
|
|
35
|
+
} from '@tanstack/ai'
|
|
36
|
+
import type {
|
|
37
|
+
ExternalTextProviderOptions,
|
|
38
|
+
InternalTextProviderOptions,
|
|
39
|
+
} from '../text/text-provider-options'
|
|
40
|
+
import type {
|
|
41
|
+
AnthropicDocumentMetadata,
|
|
42
|
+
AnthropicImageMetadata,
|
|
43
|
+
AnthropicMessageMetadataByModality,
|
|
44
|
+
AnthropicTextMetadata,
|
|
45
|
+
} from '../message-types'
|
|
46
|
+
import type { AnthropicClientConfig } from '../utils'
|
|
40
47
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Configuration for Anthropic text adapter
|
|
50
|
+
*/
|
|
51
|
+
export interface AnthropicTextConfig extends AnthropicClientConfig {}
|
|
44
52
|
|
|
45
53
|
/**
|
|
46
|
-
* Anthropic-specific provider options
|
|
47
|
-
* @see https://ai-sdk.dev/providers/ai-sdk-providers/anthropic
|
|
54
|
+
* Anthropic-specific provider options for text/chat
|
|
48
55
|
*/
|
|
49
|
-
export type
|
|
56
|
+
export type AnthropicTextProviderOptions = ExternalTextProviderOptions
|
|
50
57
|
|
|
51
58
|
type AnthropicContentBlocks =
|
|
52
59
|
Extract<MessageParam['content'], Array<unknown>> extends Array<infer Block>
|
|
@@ -55,36 +62,63 @@ type AnthropicContentBlocks =
|
|
|
55
62
|
type AnthropicContentBlock =
|
|
56
63
|
AnthropicContentBlocks extends Array<infer Block> ? Block : never
|
|
57
64
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
+
// ===========================
|
|
66
|
+
// Type Resolution Helpers
|
|
67
|
+
// ===========================
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Resolve provider options for a specific model.
|
|
71
|
+
* If the model has explicit options in the map, use those; otherwise use base options.
|
|
72
|
+
*/
|
|
73
|
+
type ResolveProviderOptions<TModel extends string> =
|
|
74
|
+
TModel extends keyof AnthropicChatModelProviderOptionsByName
|
|
75
|
+
? AnthropicChatModelProviderOptionsByName[TModel]
|
|
76
|
+
: AnthropicTextProviderOptions
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Resolve input modalities for a specific model.
|
|
80
|
+
* If the model has explicit modalities in the map, use those; otherwise use default.
|
|
81
|
+
*/
|
|
82
|
+
type ResolveInputModalities<TModel extends string> =
|
|
83
|
+
TModel extends keyof AnthropicModelInputModalitiesByName
|
|
84
|
+
? AnthropicModelInputModalitiesByName[TModel]
|
|
85
|
+
: readonly ['text', 'image', 'document']
|
|
86
|
+
|
|
87
|
+
// ===========================
|
|
88
|
+
// Adapter Implementation
|
|
89
|
+
// ===========================
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Anthropic Text (Chat) Adapter
|
|
93
|
+
*
|
|
94
|
+
* Tree-shakeable adapter for Anthropic chat/text completion functionality.
|
|
95
|
+
* Import only what you need for smaller bundle sizes.
|
|
96
|
+
*/
|
|
97
|
+
export class AnthropicTextAdapter<
|
|
98
|
+
TModel extends (typeof ANTHROPIC_MODELS)[number],
|
|
99
|
+
TProviderOptions extends object = ResolveProviderOptions<TModel>,
|
|
100
|
+
TInputModalities extends ReadonlyArray<Modality> =
|
|
101
|
+
ResolveInputModalities<TModel>,
|
|
102
|
+
> extends BaseTextAdapter<
|
|
103
|
+
TModel,
|
|
104
|
+
TProviderOptions,
|
|
105
|
+
TInputModalities,
|
|
65
106
|
AnthropicMessageMetadataByModality
|
|
66
107
|
> {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
declare _modelProviderOptionsByName: AnthropicChatModelProviderOptionsByName
|
|
71
|
-
declare _modelInputModalitiesByName: AnthropicModelInputModalitiesByName
|
|
72
|
-
declare _messageMetadataByModality: AnthropicMessageMetadataByModality
|
|
108
|
+
readonly kind = 'text' as const
|
|
109
|
+
readonly name = 'anthropic' as const
|
|
73
110
|
|
|
74
111
|
private client: Anthropic_SDK
|
|
75
112
|
|
|
76
|
-
constructor(config:
|
|
77
|
-
super({})
|
|
78
|
-
this.client =
|
|
79
|
-
apiKey: config.apiKey,
|
|
80
|
-
})
|
|
113
|
+
constructor(config: AnthropicTextConfig, model: TModel) {
|
|
114
|
+
super({}, model)
|
|
115
|
+
this.client = createAnthropicClient(config)
|
|
81
116
|
}
|
|
82
117
|
|
|
83
118
|
async *chatStream(
|
|
84
|
-
options:
|
|
119
|
+
options: TextOptions<AnthropicTextProviderOptions>,
|
|
85
120
|
): AsyncIterable<StreamChunk> {
|
|
86
121
|
try {
|
|
87
|
-
// Map common options to Anthropic format using the centralized mapping function
|
|
88
122
|
const requestParams = this.mapCommonOptionsToAnthropic(options)
|
|
89
123
|
|
|
90
124
|
const stream = await this.client.beta.messages.create(
|
|
@@ -96,105 +130,111 @@ export class Anthropic extends BaseAdapter<
|
|
|
96
130
|
)
|
|
97
131
|
|
|
98
132
|
yield* this.processAnthropicStream(stream, options.model, () =>
|
|
99
|
-
this.
|
|
133
|
+
generateId(this.name),
|
|
100
134
|
)
|
|
101
|
-
} catch (error:
|
|
102
|
-
|
|
103
|
-
message: error?.message,
|
|
104
|
-
status: error?.status,
|
|
105
|
-
statusText: error?.statusText,
|
|
106
|
-
code: error?.code,
|
|
107
|
-
type: error?.type,
|
|
108
|
-
error: error,
|
|
109
|
-
stack: error?.stack,
|
|
110
|
-
})
|
|
111
|
-
|
|
112
|
-
// Emit an error chunk
|
|
135
|
+
} catch (error: unknown) {
|
|
136
|
+
const err = error as Error & { status?: number; code?: string }
|
|
113
137
|
yield {
|
|
114
138
|
type: 'error',
|
|
115
|
-
id: this.
|
|
139
|
+
id: generateId(this.name),
|
|
116
140
|
model: options.model,
|
|
117
141
|
timestamp: Date.now(),
|
|
118
142
|
error: {
|
|
119
|
-
message:
|
|
120
|
-
code:
|
|
143
|
+
message: err.message || 'Unknown error occurred',
|
|
144
|
+
code: err.code || String(err.status),
|
|
121
145
|
},
|
|
122
146
|
}
|
|
123
147
|
}
|
|
124
148
|
}
|
|
125
149
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
totalTokens: response.usage.input_tokens + response.usage.output_tokens,
|
|
150
|
+
/**
|
|
151
|
+
* Generate structured output using Anthropic's tool-based approach.
|
|
152
|
+
* Anthropic doesn't have native structured output, so we use a tool with the schema
|
|
153
|
+
* and force the model to call it.
|
|
154
|
+
* The outputSchema is already JSON Schema (converted in the ai layer).
|
|
155
|
+
*/
|
|
156
|
+
async structuredOutput(
|
|
157
|
+
options: StructuredOutputOptions<AnthropicTextProviderOptions>,
|
|
158
|
+
): Promise<StructuredOutputResult<unknown>> {
|
|
159
|
+
const { chatOptions, outputSchema } = options
|
|
160
|
+
|
|
161
|
+
const requestParams = this.mapCommonOptionsToAnthropic(chatOptions)
|
|
162
|
+
|
|
163
|
+
// Create a tool that will capture the structured output
|
|
164
|
+
// Anthropic's SDK requires input_schema with type: 'object' literal
|
|
165
|
+
const structuredOutputTool = {
|
|
166
|
+
name: 'structured_output',
|
|
167
|
+
description:
|
|
168
|
+
'Use this tool to provide your response in the required structured format.',
|
|
169
|
+
input_schema: {
|
|
170
|
+
type: 'object' as const,
|
|
171
|
+
properties: outputSchema.properties ?? {},
|
|
172
|
+
required: outputSchema.required ?? [],
|
|
150
173
|
},
|
|
151
174
|
}
|
|
152
|
-
}
|
|
153
175
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
176
|
+
try {
|
|
177
|
+
// Make non-streaming request with tool_choice forced to our structured output tool
|
|
178
|
+
const response = await this.client.messages.create(
|
|
179
|
+
{
|
|
180
|
+
...requestParams,
|
|
181
|
+
stream: false,
|
|
182
|
+
tools: [structuredOutputTool],
|
|
183
|
+
tool_choice: { type: 'tool', name: 'structured_output' },
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
signal: chatOptions.request?.signal,
|
|
187
|
+
headers: chatOptions.request?.headers,
|
|
188
|
+
},
|
|
189
|
+
)
|
|
161
190
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
switch (options.style) {
|
|
166
|
-
case 'bullet-points':
|
|
167
|
-
prompt += 'Provide a summary in bullet point format. '
|
|
168
|
-
break
|
|
169
|
-
case 'paragraph':
|
|
170
|
-
prompt += 'Provide a summary in paragraph format. '
|
|
171
|
-
break
|
|
172
|
-
case 'concise':
|
|
173
|
-
prompt += 'Provide a very concise summary in 1-2 sentences. '
|
|
174
|
-
break
|
|
175
|
-
default:
|
|
176
|
-
prompt += 'Provide a clear and concise summary. '
|
|
177
|
-
}
|
|
191
|
+
// Extract the tool use content from the response
|
|
192
|
+
let parsed: unknown = null
|
|
193
|
+
let rawText = ''
|
|
178
194
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
195
|
+
for (const block of response.content) {
|
|
196
|
+
if (block.type === 'tool_use' && block.name === 'structured_output') {
|
|
197
|
+
parsed = block.input
|
|
198
|
+
rawText = JSON.stringify(block.input)
|
|
199
|
+
break
|
|
200
|
+
}
|
|
201
|
+
}
|
|
182
202
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
203
|
+
if (parsed === null) {
|
|
204
|
+
// Fallback: try to extract text content and parse as JSON
|
|
205
|
+
rawText = response.content
|
|
206
|
+
.map((b) => {
|
|
207
|
+
if (b.type === 'text') {
|
|
208
|
+
return b.text
|
|
209
|
+
}
|
|
210
|
+
return ''
|
|
211
|
+
})
|
|
212
|
+
.join('')
|
|
213
|
+
try {
|
|
214
|
+
parsed = JSON.parse(rawText)
|
|
215
|
+
} catch {
|
|
216
|
+
throw new Error(
|
|
217
|
+
`Failed to extract structured output from response. Content: ${rawText.slice(0, 200)}${rawText.length > 200 ? '...' : ''}`,
|
|
218
|
+
)
|
|
219
|
+
}
|
|
220
|
+
}
|
|
186
221
|
|
|
187
|
-
|
|
222
|
+
return {
|
|
223
|
+
data: parsed,
|
|
224
|
+
rawText,
|
|
225
|
+
}
|
|
226
|
+
} catch (error: unknown) {
|
|
227
|
+
const err = error as Error
|
|
228
|
+
throw new Error(
|
|
229
|
+
`Structured output generation failed: ${err.message || 'Unknown error occurred'}`,
|
|
230
|
+
)
|
|
231
|
+
}
|
|
188
232
|
}
|
|
189
233
|
|
|
190
|
-
/**
|
|
191
|
-
* Maps common options to Anthropic-specific format
|
|
192
|
-
* Handles translation of normalized options to Anthropic's API format
|
|
193
|
-
*/
|
|
194
234
|
private mapCommonOptionsToAnthropic(
|
|
195
|
-
options:
|
|
235
|
+
options: TextOptions<AnthropicTextProviderOptions>,
|
|
196
236
|
) {
|
|
197
|
-
const
|
|
237
|
+
const modelOptions = options.modelOptions as
|
|
198
238
|
| InternalTextProviderOptions
|
|
199
239
|
| undefined
|
|
200
240
|
|
|
@@ -203,9 +243,8 @@ export class Anthropic extends BaseAdapter<
|
|
|
203
243
|
? convertToolsToProviderFormat(options.tools)
|
|
204
244
|
: undefined
|
|
205
245
|
|
|
206
|
-
// Filter out invalid fields from providerOptions (like 'store' which is OpenAI-specific)
|
|
207
246
|
const validProviderOptions: Partial<InternalTextProviderOptions> = {}
|
|
208
|
-
if (
|
|
247
|
+
if (modelOptions) {
|
|
209
248
|
const validKeys: Array<keyof InternalTextProviderOptions> = [
|
|
210
249
|
'container',
|
|
211
250
|
'context_management',
|
|
@@ -218,34 +257,34 @@ export class Anthropic extends BaseAdapter<
|
|
|
218
257
|
'top_k',
|
|
219
258
|
]
|
|
220
259
|
for (const key of validKeys) {
|
|
221
|
-
if (key in
|
|
222
|
-
const value =
|
|
223
|
-
// Anthropic expects tool_choice to be an object, not a string
|
|
260
|
+
if (key in modelOptions) {
|
|
261
|
+
const value = modelOptions[key]
|
|
224
262
|
if (key === 'tool_choice' && typeof value === 'string') {
|
|
225
|
-
;(validProviderOptions as
|
|
263
|
+
;(validProviderOptions as Record<string, unknown>)[key] = {
|
|
264
|
+
type: value,
|
|
265
|
+
}
|
|
226
266
|
} else {
|
|
227
|
-
;(validProviderOptions as
|
|
267
|
+
;(validProviderOptions as Record<string, unknown>)[key] = value
|
|
228
268
|
}
|
|
229
269
|
}
|
|
230
270
|
}
|
|
231
271
|
}
|
|
232
272
|
|
|
233
|
-
// Ensure max_tokens is greater than thinking.budget_tokens if thinking is enabled
|
|
234
273
|
const thinkingBudget =
|
|
235
274
|
validProviderOptions.thinking?.type === 'enabled'
|
|
236
275
|
? validProviderOptions.thinking.budget_tokens
|
|
237
276
|
: undefined
|
|
238
|
-
const defaultMaxTokens = options.
|
|
277
|
+
const defaultMaxTokens = options.maxTokens || 1024
|
|
239
278
|
const maxTokens =
|
|
240
279
|
thinkingBudget && thinkingBudget >= defaultMaxTokens
|
|
241
|
-
? thinkingBudget + 1
|
|
280
|
+
? thinkingBudget + 1
|
|
242
281
|
: defaultMaxTokens
|
|
243
282
|
|
|
244
283
|
const requestParams: InternalTextProviderOptions = {
|
|
245
284
|
model: options.model,
|
|
246
285
|
max_tokens: maxTokens,
|
|
247
|
-
temperature: options.
|
|
248
|
-
top_p: options.
|
|
286
|
+
temperature: options.temperature,
|
|
287
|
+
top_p: options.topP,
|
|
249
288
|
messages: formattedMessages,
|
|
250
289
|
system: options.systemPrompts?.join('\n'),
|
|
251
290
|
tools: tools,
|
|
@@ -260,9 +299,7 @@ export class Anthropic extends BaseAdapter<
|
|
|
260
299
|
): TextBlockParam | ImageBlockParam | DocumentBlockParam {
|
|
261
300
|
switch (part.type) {
|
|
262
301
|
case 'text': {
|
|
263
|
-
const metadata = part.metadata as
|
|
264
|
-
| AnthropicTextMetadata
|
|
265
|
-
| undefined
|
|
302
|
+
const metadata = part.metadata as AnthropicTextMetadata | undefined
|
|
266
303
|
return {
|
|
267
304
|
type: 'text',
|
|
268
305
|
text: part.content,
|
|
@@ -271,9 +308,7 @@ export class Anthropic extends BaseAdapter<
|
|
|
271
308
|
}
|
|
272
309
|
|
|
273
310
|
case 'image': {
|
|
274
|
-
const metadata = part.metadata as
|
|
275
|
-
| AnthropicImageMetadata
|
|
276
|
-
| undefined
|
|
311
|
+
const metadata = part.metadata as AnthropicImageMetadata | undefined
|
|
277
312
|
const imageSource: Base64ImageSource | URLImageSource =
|
|
278
313
|
part.source.type === 'data'
|
|
279
314
|
? {
|
|
@@ -285,8 +320,7 @@ export class Anthropic extends BaseAdapter<
|
|
|
285
320
|
type: 'url',
|
|
286
321
|
url: part.source.value,
|
|
287
322
|
}
|
|
288
|
-
|
|
289
|
-
const { mediaType, ...meta } = metadata || {}
|
|
323
|
+
const { mediaType: _mediaType, ...meta } = metadata || {}
|
|
290
324
|
return {
|
|
291
325
|
type: 'image',
|
|
292
326
|
source: imageSource,
|
|
@@ -294,9 +328,7 @@ export class Anthropic extends BaseAdapter<
|
|
|
294
328
|
}
|
|
295
329
|
}
|
|
296
330
|
case 'document': {
|
|
297
|
-
const metadata = part.metadata as
|
|
298
|
-
| AnthropicDocumentMetadata
|
|
299
|
-
| undefined
|
|
331
|
+
const metadata = part.metadata as AnthropicDocumentMetadata | undefined
|
|
300
332
|
const docSource: Base64PDFSource | URLPDFSource =
|
|
301
333
|
part.source.type === 'data'
|
|
302
334
|
? {
|
|
@@ -316,12 +348,10 @@ export class Anthropic extends BaseAdapter<
|
|
|
316
348
|
}
|
|
317
349
|
case 'audio':
|
|
318
350
|
case 'video':
|
|
319
|
-
// Anthropic doesn't support audio/video directly, treat as text with a note
|
|
320
351
|
throw new Error(
|
|
321
352
|
`Anthropic does not support ${part.type} content directly`,
|
|
322
353
|
)
|
|
323
354
|
default: {
|
|
324
|
-
// Exhaustive check - this should never happen with known types
|
|
325
355
|
const _exhaustiveCheck: never = part
|
|
326
356
|
throw new Error(
|
|
327
357
|
`Unsupported content part type: ${(_exhaustiveCheck as ContentPart).type}`,
|
|
@@ -393,7 +423,6 @@ export class Anthropic extends BaseAdapter<
|
|
|
393
423
|
continue
|
|
394
424
|
}
|
|
395
425
|
|
|
396
|
-
// Handle user messages with multimodal content
|
|
397
426
|
if (role === 'user' && Array.isArray(message.content)) {
|
|
398
427
|
const contentBlocks = message.content.map((part) =>
|
|
399
428
|
this.convertContentPartToAnthropic(part),
|
|
@@ -424,7 +453,7 @@ export class Anthropic extends BaseAdapter<
|
|
|
424
453
|
private async *processAnthropicStream(
|
|
425
454
|
stream: AsyncIterable<Anthropic_SDK.Beta.BetaRawMessageStreamEvent>,
|
|
426
455
|
model: string,
|
|
427
|
-
|
|
456
|
+
genId: () => string,
|
|
428
457
|
): AsyncIterable<StreamChunk> {
|
|
429
458
|
let accumulatedContent = ''
|
|
430
459
|
let accumulatedThinking = ''
|
|
@@ -446,7 +475,6 @@ export class Anthropic extends BaseAdapter<
|
|
|
446
475
|
input: '',
|
|
447
476
|
})
|
|
448
477
|
} else if (event.content_block.type === 'thinking') {
|
|
449
|
-
// Reset thinking content when a new thinking block starts
|
|
450
478
|
accumulatedThinking = ''
|
|
451
479
|
}
|
|
452
480
|
} else if (event.type === 'content_block_delta') {
|
|
@@ -455,7 +483,7 @@ export class Anthropic extends BaseAdapter<
|
|
|
455
483
|
accumulatedContent += delta
|
|
456
484
|
yield {
|
|
457
485
|
type: 'content',
|
|
458
|
-
id:
|
|
486
|
+
id: genId(),
|
|
459
487
|
model: model,
|
|
460
488
|
timestamp,
|
|
461
489
|
delta,
|
|
@@ -463,29 +491,24 @@ export class Anthropic extends BaseAdapter<
|
|
|
463
491
|
role: 'assistant',
|
|
464
492
|
}
|
|
465
493
|
} else if (event.delta.type === 'thinking_delta') {
|
|
466
|
-
// Handle thinking content
|
|
467
494
|
const delta = event.delta.thinking
|
|
468
495
|
accumulatedThinking += delta
|
|
469
496
|
yield {
|
|
470
497
|
type: 'thinking',
|
|
471
|
-
id:
|
|
498
|
+
id: genId(),
|
|
472
499
|
model: model,
|
|
473
500
|
timestamp,
|
|
474
501
|
delta,
|
|
475
502
|
content: accumulatedThinking,
|
|
476
503
|
}
|
|
477
504
|
} else if (event.delta.type === 'input_json_delta') {
|
|
478
|
-
// Tool input is being streamed
|
|
479
505
|
const existing = toolCallsMap.get(currentToolIndex)
|
|
480
506
|
if (existing) {
|
|
481
|
-
// Accumulate the input for final processing
|
|
482
507
|
existing.input += event.delta.partial_json
|
|
483
508
|
|
|
484
|
-
// Yield the DELTA (partial_json), not the full accumulated input
|
|
485
|
-
// The stream processor will concatenate these deltas
|
|
486
509
|
yield {
|
|
487
510
|
type: 'tool_call',
|
|
488
|
-
id:
|
|
511
|
+
id: genId(),
|
|
489
512
|
model: model,
|
|
490
513
|
timestamp,
|
|
491
514
|
toolCall: {
|
|
@@ -501,14 +524,11 @@ export class Anthropic extends BaseAdapter<
|
|
|
501
524
|
}
|
|
502
525
|
}
|
|
503
526
|
} else if (event.type === 'content_block_stop') {
|
|
504
|
-
// If this is a tool call and we haven't received any input deltas,
|
|
505
|
-
// emit a tool_call chunk with empty arguments
|
|
506
527
|
const existing = toolCallsMap.get(currentToolIndex)
|
|
507
528
|
if (existing && existing.input === '') {
|
|
508
|
-
// No input_json_delta events received, emit empty arguments
|
|
509
529
|
yield {
|
|
510
530
|
type: 'tool_call',
|
|
511
|
-
id:
|
|
531
|
+
id: genId(),
|
|
512
532
|
model: model,
|
|
513
533
|
timestamp,
|
|
514
534
|
toolCall: {
|
|
@@ -525,7 +545,7 @@ export class Anthropic extends BaseAdapter<
|
|
|
525
545
|
} else if (event.type === 'message_stop') {
|
|
526
546
|
yield {
|
|
527
547
|
type: 'done',
|
|
528
|
-
id:
|
|
548
|
+
id: genId(),
|
|
529
549
|
model: model,
|
|
530
550
|
timestamp,
|
|
531
551
|
finishReason: 'stop',
|
|
@@ -536,11 +556,10 @@ export class Anthropic extends BaseAdapter<
|
|
|
536
556
|
case 'tool_use': {
|
|
537
557
|
yield {
|
|
538
558
|
type: 'done',
|
|
539
|
-
id:
|
|
559
|
+
id: genId(),
|
|
540
560
|
model: model,
|
|
541
561
|
timestamp,
|
|
542
562
|
finishReason: 'tool_calls',
|
|
543
|
-
|
|
544
563
|
usage: {
|
|
545
564
|
promptTokens: event.usage.input_tokens || 0,
|
|
546
565
|
completionTokens: event.usage.output_tokens || 0,
|
|
@@ -554,7 +573,7 @@ export class Anthropic extends BaseAdapter<
|
|
|
554
573
|
case 'max_tokens': {
|
|
555
574
|
yield {
|
|
556
575
|
type: 'error',
|
|
557
|
-
id:
|
|
576
|
+
id: genId(),
|
|
558
577
|
model: model,
|
|
559
578
|
timestamp,
|
|
560
579
|
error: {
|
|
@@ -565,37 +584,10 @@ export class Anthropic extends BaseAdapter<
|
|
|
565
584
|
}
|
|
566
585
|
break
|
|
567
586
|
}
|
|
568
|
-
case 'model_context_window_exceeded': {
|
|
569
|
-
yield {
|
|
570
|
-
type: 'error',
|
|
571
|
-
id: generateId(),
|
|
572
|
-
model: model,
|
|
573
|
-
timestamp,
|
|
574
|
-
error: {
|
|
575
|
-
message:
|
|
576
|
-
"The response was cut off because the model's context window was exceeded.",
|
|
577
|
-
code: 'context_window_exceeded',
|
|
578
|
-
},
|
|
579
|
-
}
|
|
580
|
-
break
|
|
581
|
-
}
|
|
582
|
-
case 'refusal': {
|
|
583
|
-
yield {
|
|
584
|
-
type: 'error',
|
|
585
|
-
id: generateId(),
|
|
586
|
-
model: model,
|
|
587
|
-
timestamp,
|
|
588
|
-
error: {
|
|
589
|
-
message: 'The model refused to complete the request.',
|
|
590
|
-
code: 'refusal',
|
|
591
|
-
},
|
|
592
|
-
}
|
|
593
|
-
break
|
|
594
|
-
}
|
|
595
587
|
default: {
|
|
596
588
|
yield {
|
|
597
589
|
type: 'done',
|
|
598
|
-
id:
|
|
590
|
+
id: genId(),
|
|
599
591
|
model: model,
|
|
600
592
|
timestamp,
|
|
601
593
|
finishReason: 'stop',
|
|
@@ -612,84 +604,53 @@ export class Anthropic extends BaseAdapter<
|
|
|
612
604
|
}
|
|
613
605
|
}
|
|
614
606
|
}
|
|
615
|
-
} catch (error:
|
|
616
|
-
|
|
617
|
-
message: error?.message,
|
|
618
|
-
status: error?.status,
|
|
619
|
-
statusText: error?.statusText,
|
|
620
|
-
code: error?.code,
|
|
621
|
-
type: error?.type,
|
|
622
|
-
error: error,
|
|
623
|
-
stack: error?.stack,
|
|
624
|
-
})
|
|
607
|
+
} catch (error: unknown) {
|
|
608
|
+
const err = error as Error & { status?: number; code?: string }
|
|
625
609
|
|
|
626
610
|
yield {
|
|
627
611
|
type: 'error',
|
|
628
|
-
id:
|
|
612
|
+
id: genId(),
|
|
629
613
|
model: model,
|
|
630
614
|
timestamp,
|
|
631
615
|
error: {
|
|
632
|
-
message:
|
|
633
|
-
code:
|
|
616
|
+
message: err.message || 'Unknown error occurred',
|
|
617
|
+
code: err.code || String(err.status),
|
|
634
618
|
},
|
|
635
619
|
}
|
|
636
620
|
}
|
|
637
621
|
}
|
|
638
622
|
}
|
|
623
|
+
|
|
639
624
|
/**
|
|
640
|
-
* Creates an Anthropic adapter with
|
|
641
|
-
*
|
|
642
|
-
* @returns A fully configured Anthropic adapter instance
|
|
643
|
-
*
|
|
644
|
-
* @example
|
|
645
|
-
* ```typescript
|
|
646
|
-
* const anthropic = createAnthropic("sk-ant-...");
|
|
647
|
-
*
|
|
648
|
-
* const ai = new AI({
|
|
649
|
-
* adapters: {
|
|
650
|
-
* anthropic,
|
|
651
|
-
* }
|
|
652
|
-
* });
|
|
653
|
-
* ```
|
|
625
|
+
* Creates an Anthropic chat adapter with explicit API key.
|
|
626
|
+
* Type resolution happens here at the call site.
|
|
654
627
|
*/
|
|
655
|
-
export function
|
|
628
|
+
export function createAnthropicChat<
|
|
629
|
+
TModel extends (typeof ANTHROPIC_MODELS)[number],
|
|
630
|
+
>(
|
|
631
|
+
model: TModel,
|
|
656
632
|
apiKey: string,
|
|
657
|
-
config?: Omit<
|
|
658
|
-
):
|
|
659
|
-
|
|
633
|
+
config?: Omit<AnthropicTextConfig, 'apiKey'>,
|
|
634
|
+
): AnthropicTextAdapter<
|
|
635
|
+
TModel,
|
|
636
|
+
ResolveProviderOptions<TModel>,
|
|
637
|
+
ResolveInputModalities<TModel>
|
|
638
|
+
> {
|
|
639
|
+
return new AnthropicTextAdapter({ apiKey, ...config }, model)
|
|
660
640
|
}
|
|
661
641
|
|
|
662
642
|
/**
|
|
663
|
-
*
|
|
664
|
-
*
|
|
665
|
-
* Looks for `ANTHROPIC_API_KEY` in:
|
|
666
|
-
* - `process.env` (Node.js)
|
|
667
|
-
* - `window.env` (Browser with injected env)
|
|
668
|
-
*
|
|
669
|
-
* @param config - Optional configuration (excluding apiKey which is auto-detected)
|
|
670
|
-
* @returns Configured Anthropic adapter instance
|
|
671
|
-
* @throws Error if ANTHROPIC_API_KEY is not found in environment
|
|
672
|
-
*
|
|
673
|
-
* @example
|
|
674
|
-
* ```typescript
|
|
675
|
-
* // Automatically uses ANTHROPIC_API_KEY from environment
|
|
676
|
-
* const aiInstance = ai(anthropic());
|
|
677
|
-
* ```
|
|
643
|
+
* Creates an Anthropic text adapter with automatic API key detection.
|
|
644
|
+
* Type resolution happens here at the call site.
|
|
678
645
|
*/
|
|
679
|
-
export function
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
throw new Error(
|
|
690
|
-
'ANTHROPIC_API_KEY is required. Please set it in your environment variables or use createAnthropic(apiKey, config) instead.',
|
|
691
|
-
)
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
return createAnthropic(key, config)
|
|
646
|
+
export function anthropicText<TModel extends (typeof ANTHROPIC_MODELS)[number]>(
|
|
647
|
+
model: TModel,
|
|
648
|
+
config?: Omit<AnthropicTextConfig, 'apiKey'>,
|
|
649
|
+
): AnthropicTextAdapter<
|
|
650
|
+
TModel,
|
|
651
|
+
ResolveProviderOptions<TModel>,
|
|
652
|
+
ResolveInputModalities<TModel>
|
|
653
|
+
> {
|
|
654
|
+
const apiKey = getAnthropicApiKeyFromEnv()
|
|
655
|
+
return createAnthropicChat(model, apiKey, config)
|
|
695
656
|
}
|