@effect/ai-anthropic 0.16.1 → 0.17.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/AnthropicTool/package.json +6 -0
- package/dist/cjs/AnthropicClient.js +286 -190
- package/dist/cjs/AnthropicClient.js.map +1 -1
- package/dist/cjs/AnthropicLanguageModel.js +1026 -311
- package/dist/cjs/AnthropicLanguageModel.js.map +1 -1
- package/dist/cjs/AnthropicTokenizer.js +8 -6
- package/dist/cjs/AnthropicTokenizer.js.map +1 -1
- package/dist/cjs/AnthropicTool.js +461 -0
- package/dist/cjs/AnthropicTool.js.map +1 -0
- package/dist/cjs/Generated.js +3507 -1230
- package/dist/cjs/Generated.js.map +1 -1
- package/dist/cjs/index.js +3 -1
- package/dist/cjs/internal/utilities.js +13 -3
- package/dist/cjs/internal/utilities.js.map +1 -1
- package/dist/dts/AnthropicClient.d.ts +673 -17
- package/dist/dts/AnthropicClient.d.ts.map +1 -1
- package/dist/dts/AnthropicLanguageModel.d.ts +217 -26
- package/dist/dts/AnthropicLanguageModel.d.ts.map +1 -1
- package/dist/dts/AnthropicTokenizer.d.ts +1 -1
- package/dist/dts/AnthropicTokenizer.d.ts.map +1 -1
- package/dist/dts/AnthropicTool.d.ts +523 -0
- package/dist/dts/AnthropicTool.d.ts.map +1 -0
- package/dist/dts/Generated.d.ts +7863 -3496
- package/dist/dts/Generated.d.ts.map +1 -1
- package/dist/dts/index.d.ts +4 -0
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/esm/AnthropicClient.js +269 -188
- package/dist/esm/AnthropicClient.js.map +1 -1
- package/dist/esm/AnthropicLanguageModel.js +1022 -306
- package/dist/esm/AnthropicLanguageModel.js.map +1 -1
- package/dist/esm/AnthropicTokenizer.js +8 -6
- package/dist/esm/AnthropicTokenizer.js.map +1 -1
- package/dist/esm/AnthropicTool.js +452 -0
- package/dist/esm/AnthropicTool.js.map +1 -0
- package/dist/esm/Generated.js +3492 -1063
- package/dist/esm/Generated.js.map +1 -1
- package/dist/esm/index.js +4 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/internal/utilities.js +12 -2
- package/dist/esm/internal/utilities.js.map +1 -1
- package/package.json +11 -3
- package/src/AnthropicClient.ts +713 -369
- package/src/AnthropicLanguageModel.ts +1404 -345
- package/src/AnthropicTokenizer.ts +14 -23
- package/src/AnthropicTool.ts +553 -0
- package/src/Generated.ts +4165 -1681
- package/src/index.ts +5 -0
- package/src/internal/utilities.ts +18 -4
|
@@ -1,31 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @since 1.0.0
|
|
3
3
|
*/
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import * as
|
|
7
|
-
import * as AiModel from "@effect/ai/
|
|
8
|
-
import * as
|
|
9
|
-
import
|
|
4
|
+
import * as AiError from "@effect/ai/AiError"
|
|
5
|
+
import * as IdGenerator from "@effect/ai/IdGenerator"
|
|
6
|
+
import * as LanguageModel from "@effect/ai/LanguageModel"
|
|
7
|
+
import * as AiModel from "@effect/ai/Model"
|
|
8
|
+
import type * as Prompt from "@effect/ai/Prompt"
|
|
9
|
+
import type * as Response from "@effect/ai/Response"
|
|
10
|
+
import { addGenAIAnnotations } from "@effect/ai/Telemetry"
|
|
10
11
|
import type * as Tokenizer from "@effect/ai/Tokenizer"
|
|
12
|
+
import * as Tool from "@effect/ai/Tool"
|
|
11
13
|
import * as Arr from "effect/Array"
|
|
12
14
|
import * as Context from "effect/Context"
|
|
15
|
+
import * as DateTime from "effect/DateTime"
|
|
13
16
|
import * as Effect from "effect/Effect"
|
|
14
17
|
import * as Encoding from "effect/Encoding"
|
|
15
18
|
import { dual } from "effect/Function"
|
|
16
19
|
import * as Layer from "effect/Layer"
|
|
17
|
-
import * as Option from "effect/Option"
|
|
18
20
|
import * as Predicate from "effect/Predicate"
|
|
19
21
|
import * as Stream from "effect/Stream"
|
|
20
22
|
import type { Span } from "effect/Tracer"
|
|
21
23
|
import type { Mutable, Simplify } from "effect/Types"
|
|
22
|
-
import { AnthropicClient } from "./AnthropicClient.js"
|
|
24
|
+
import { AnthropicClient, type MessageStreamEvent } from "./AnthropicClient.js"
|
|
23
25
|
import * as AnthropicTokenizer from "./AnthropicTokenizer.js"
|
|
26
|
+
import * as AnthropicTool from "./AnthropicTool.js"
|
|
24
27
|
import type * as Generated from "./Generated.js"
|
|
25
28
|
import * as InternalUtilities from "./internal/utilities.js"
|
|
26
29
|
|
|
27
|
-
const constDisableValidation = { disableValidation: true } as const
|
|
28
|
-
|
|
29
30
|
/**
|
|
30
31
|
* @since 1.0.0
|
|
31
32
|
* @category Models
|
|
@@ -70,38 +71,219 @@ export declare namespace Config {
|
|
|
70
71
|
>
|
|
71
72
|
>
|
|
72
73
|
>
|
|
73
|
-
{
|
|
74
|
+
{
|
|
75
|
+
readonly disableParallelToolCalls?: boolean
|
|
76
|
+
}
|
|
74
77
|
}
|
|
75
78
|
|
|
76
79
|
// =============================================================================
|
|
77
|
-
// Anthropic Provider Metadata
|
|
80
|
+
// Anthropic Provider Options / Metadata
|
|
78
81
|
// =============================================================================
|
|
79
82
|
|
|
80
83
|
/**
|
|
81
84
|
* @since 1.0.0
|
|
82
|
-
* @category
|
|
85
|
+
* @category Provider Metadata
|
|
83
86
|
*/
|
|
84
|
-
export
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
87
|
+
export type AnthropicReasoningInfo = {
|
|
88
|
+
readonly type: "thinking"
|
|
89
|
+
/**
|
|
90
|
+
* Thinking content as an encrypted string, which is used to verify
|
|
91
|
+
* that thinking content was indeed generated by Anthropic's API.
|
|
92
|
+
*/
|
|
93
|
+
readonly signature: typeof Generated.ResponseThinkingBlock.fields.thinking.Encoded
|
|
94
|
+
} | {
|
|
95
|
+
readonly type: "redacted_thinking"
|
|
96
|
+
/**
|
|
97
|
+
* Thinking content which was flagged by Anthropic's safety systems, and
|
|
98
|
+
* was therefore encrypted.
|
|
99
|
+
*/
|
|
100
|
+
readonly redactedData: typeof Generated.RequestRedactedThinkingBlock.fields.data.Encoded
|
|
101
|
+
}
|
|
88
102
|
|
|
89
103
|
/**
|
|
90
104
|
* @since 1.0.0
|
|
105
|
+
* @category Provider Options
|
|
91
106
|
*/
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
107
|
+
declare module "@effect/ai/Prompt" {
|
|
108
|
+
export interface SystemMessageOptions extends ProviderOptions {
|
|
109
|
+
readonly anthropic?: {
|
|
110
|
+
/**
|
|
111
|
+
* A breakpoint which marks the end of reusable content eligible for caching.
|
|
112
|
+
*/
|
|
113
|
+
readonly cacheControl?: typeof Generated.CacheControlEphemeral.Encoded | undefined
|
|
114
|
+
} | undefined
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface UserMessageOptions extends ProviderOptions {
|
|
118
|
+
readonly anthropic?: {
|
|
119
|
+
/**
|
|
120
|
+
* A breakpoint which marks the end of reusable content eligible for caching.
|
|
121
|
+
*/
|
|
122
|
+
readonly cacheControl?: typeof Generated.CacheControlEphemeral.Encoded | undefined
|
|
123
|
+
} | undefined
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface AssistantMessageOptions extends ProviderOptions {
|
|
127
|
+
readonly anthropic?: {
|
|
128
|
+
/**
|
|
129
|
+
* A breakpoint which marks the end of reusable content eligible for caching.
|
|
130
|
+
*/
|
|
131
|
+
readonly cacheControl?: typeof Generated.CacheControlEphemeral.Encoded | undefined
|
|
132
|
+
} | undefined
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface ToolMessageOptions extends ProviderOptions {
|
|
136
|
+
readonly anthropic?: {
|
|
137
|
+
/**
|
|
138
|
+
* A breakpoint which marks the end of reusable content eligible for caching.
|
|
139
|
+
*/
|
|
140
|
+
readonly cacheControl?: typeof Generated.CacheControlEphemeral.Encoded | undefined
|
|
141
|
+
} | undefined
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export interface TextPartOptions extends ProviderOptions {
|
|
145
|
+
readonly anthropic?: {
|
|
146
|
+
/**
|
|
147
|
+
* A breakpoint which marks the end of reusable content eligible for caching.
|
|
148
|
+
*/
|
|
149
|
+
readonly cacheControl?: typeof Generated.CacheControlEphemeral.Encoded | undefined
|
|
150
|
+
} | undefined
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export interface ReasoningPartOptions extends ProviderOptions {
|
|
154
|
+
readonly anthropic?:
|
|
155
|
+
| Simplify<
|
|
156
|
+
AnthropicReasoningInfo & {
|
|
157
|
+
/**
|
|
158
|
+
* A breakpoint which marks the end of reusable content eligible for caching.
|
|
159
|
+
*/
|
|
160
|
+
readonly cacheControl?: typeof Generated.CacheControlEphemeral.Encoded | undefined
|
|
161
|
+
}
|
|
162
|
+
>
|
|
163
|
+
| undefined
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export interface FilePartOptions extends ProviderOptions {
|
|
167
|
+
readonly anthropic?: {
|
|
168
|
+
/**
|
|
169
|
+
* A breakpoint which marks the end of reusable content eligible for caching.
|
|
170
|
+
*/
|
|
171
|
+
readonly cacheControl?: typeof Generated.CacheControlEphemeral.Encoded | undefined
|
|
172
|
+
/**
|
|
173
|
+
* Whether or not citations should be enabled for the file part.
|
|
174
|
+
*/
|
|
175
|
+
readonly citations?: typeof Generated.RequestCitationsConfig.Encoded | undefined
|
|
176
|
+
/**
|
|
177
|
+
* A custom title to provide to the document. If omitted, the file part's
|
|
178
|
+
* `fileName` property will be used.
|
|
179
|
+
*/
|
|
180
|
+
readonly documentTitle?: string | undefined
|
|
181
|
+
/**
|
|
182
|
+
* Additional context about the document that will be forwarded to the
|
|
183
|
+
* large language model, but will not be used towards cited content.
|
|
184
|
+
*
|
|
185
|
+
* Useful for storing additional document metadata as text or stringified JSON.
|
|
186
|
+
*/
|
|
187
|
+
readonly documentContext?: string | undefined
|
|
188
|
+
} | undefined
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export interface ToolCallPartOptions extends ProviderOptions {
|
|
192
|
+
readonly anthropic?: {
|
|
193
|
+
/**
|
|
194
|
+
* A breakpoint which marks the end of reusable content eligible for caching.
|
|
195
|
+
*/
|
|
196
|
+
readonly cacheControl?: typeof Generated.CacheControlEphemeral.Encoded | undefined
|
|
197
|
+
} | undefined
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export interface ToolResultPartOptions extends ProviderOptions {
|
|
201
|
+
readonly anthropic?: {
|
|
202
|
+
/**
|
|
203
|
+
* A breakpoint which marks the end of reusable content eligible for caching.
|
|
204
|
+
*/
|
|
205
|
+
readonly cacheControl?: typeof Generated.CacheControlEphemeral.Encoded | undefined
|
|
206
|
+
} | undefined
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
declare module "@effect/ai/Response" {
|
|
211
|
+
export interface ReasoningPartMetadata extends ProviderMetadata {
|
|
212
|
+
readonly anthropic?: AnthropicReasoningInfo | undefined
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export interface ReasoningStartPartMetadata extends ProviderMetadata {
|
|
216
|
+
readonly anthropic?: AnthropicReasoningInfo | undefined
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export interface ReasoningDeltaPartMetadata extends ProviderMetadata {
|
|
220
|
+
readonly anthropic?: AnthropicReasoningInfo | undefined
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export interface FinishPartMetadata extends ProviderMetadata {
|
|
224
|
+
readonly anthropic?: {
|
|
225
|
+
/**
|
|
226
|
+
* Additional usage information provided by the Anthropic API.
|
|
227
|
+
*/
|
|
228
|
+
readonly usage?: Generated.BetaUsage | undefined
|
|
229
|
+
/**
|
|
230
|
+
* Which custom stop sequence was generated, if any.
|
|
231
|
+
*
|
|
232
|
+
* If one of the custom user-defined stop sequences was generated, the
|
|
233
|
+
* value will be a `string` with that stop sequence.
|
|
234
|
+
*/
|
|
235
|
+
readonly stopSequence?: string | undefined
|
|
236
|
+
} | undefined
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export interface DocumentSourcePartMetadata extends ProviderMetadata {
|
|
240
|
+
readonly anthropic?: {
|
|
241
|
+
readonly source: "document"
|
|
242
|
+
readonly type: "char_location"
|
|
243
|
+
/**
|
|
244
|
+
* The text that was cited in the response.
|
|
245
|
+
*/
|
|
246
|
+
readonly citedText: string
|
|
247
|
+
/**
|
|
248
|
+
* The 0-indexed starting position of the characters that were cited.
|
|
249
|
+
*/
|
|
250
|
+
readonly startCharIndex: number
|
|
251
|
+
/**
|
|
252
|
+
* The exclusive ending position of the characters that were cited.
|
|
253
|
+
*/
|
|
254
|
+
readonly endCharIndex: number
|
|
255
|
+
} | {
|
|
256
|
+
readonly source: "document"
|
|
257
|
+
readonly type: "page_location"
|
|
258
|
+
/**
|
|
259
|
+
* The text that was cited in the response.
|
|
260
|
+
*/
|
|
261
|
+
readonly citedText: string
|
|
262
|
+
/**
|
|
263
|
+
* The 1-indexed starting page of pages that were cited.
|
|
264
|
+
*/
|
|
265
|
+
readonly startPageNumber: number
|
|
266
|
+
/**
|
|
267
|
+
* The exclusive ending position of the pages that were cited.
|
|
268
|
+
*/
|
|
269
|
+
readonly endPageNumber: number
|
|
270
|
+
} | undefined
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export interface UrlSourcePartMetadata extends ProviderMetadata {
|
|
274
|
+
readonly anthropic?: {
|
|
275
|
+
readonly source: "url"
|
|
276
|
+
/**
|
|
277
|
+
* Up to 150 characters of the text content that was referenced from the
|
|
278
|
+
* URL source material.
|
|
279
|
+
*/
|
|
280
|
+
readonly citedText: string
|
|
281
|
+
/**
|
|
282
|
+
* An internal reference that must be passed back to the Anthropic API
|
|
283
|
+
* during multi-turn conversations.
|
|
284
|
+
*/
|
|
285
|
+
readonly encryptedIndex: string
|
|
286
|
+
} | undefined
|
|
105
287
|
}
|
|
106
288
|
}
|
|
107
289
|
|
|
@@ -111,22 +293,23 @@ export declare namespace ProviderMetadata {
|
|
|
111
293
|
|
|
112
294
|
/**
|
|
113
295
|
* @since 1.0.0
|
|
114
|
-
* @category
|
|
296
|
+
* @category Ai Models
|
|
115
297
|
*/
|
|
116
298
|
export const model = (
|
|
117
299
|
model: (string & {}) | Model,
|
|
118
300
|
config?: Omit<Config.Service, "model">
|
|
119
|
-
): AiModel.
|
|
301
|
+
): AiModel.Model<"anthropic", LanguageModel.LanguageModel, AnthropicClient> =>
|
|
302
|
+
AiModel.make("anthropic", layer({ model, config }))
|
|
120
303
|
|
|
121
304
|
/**
|
|
122
305
|
* @since 1.0.0
|
|
123
|
-
* @category
|
|
306
|
+
* @category Ai Models
|
|
124
307
|
*/
|
|
125
308
|
export const modelWithTokenizer = (
|
|
126
309
|
model: (string & {}) | Model,
|
|
127
310
|
config?: Omit<Config.Service, "model">
|
|
128
|
-
): AiModel.
|
|
129
|
-
AiModel.make(layerWithTokenizer({ model, config }))
|
|
311
|
+
): AiModel.Model<"anthropic", LanguageModel.LanguageModel | Tokenizer.Tokenizer, AnthropicClient> =>
|
|
312
|
+
AiModel.make("anthropic", layerWithTokenizer({ model, config }))
|
|
130
313
|
|
|
131
314
|
/**
|
|
132
315
|
* @since 1.0.0
|
|
@@ -139,80 +322,71 @@ export const make = Effect.fnUntraced(function*(options: {
|
|
|
139
322
|
const client = yield* AnthropicClient
|
|
140
323
|
|
|
141
324
|
const makeRequest = Effect.fnUntraced(
|
|
142
|
-
function*(
|
|
325
|
+
function*(providerOptions: LanguageModel.ProviderOptions) {
|
|
143
326
|
const context = yield* Effect.context<never>()
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
if (toolChoice === "required") {
|
|
150
|
-
tool_choice = { type: "any" }
|
|
151
|
-
} else if (typeof toolChoice === "object") {
|
|
152
|
-
tool_choice = { type: "tool", name: toolChoice.tool }
|
|
153
|
-
} else {
|
|
154
|
-
tool_choice = { type: toolChoice }
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
const messages = yield* makeMessages(method, prompt)
|
|
158
|
-
return {
|
|
159
|
-
model: options.model,
|
|
327
|
+
const config = { model: options.model, ...options.config, ...context.unsafeMap.get(Config.key) }
|
|
328
|
+
const { betas: messageBetas, messages, system } = yield* prepareMessages(providerOptions)
|
|
329
|
+
const { betas: toolBetas, toolChoice, tools } = yield* prepareTools(providerOptions, config)
|
|
330
|
+
const responseFormat = providerOptions.responseFormat
|
|
331
|
+
const request: typeof Generated.BetaCreateMessageParams.Encoded = {
|
|
160
332
|
max_tokens: 4096,
|
|
161
|
-
...
|
|
162
|
-
|
|
163
|
-
// TODO: re-evaluate a better way to do this
|
|
164
|
-
system: Option.getOrUndefined(system),
|
|
333
|
+
...config,
|
|
334
|
+
system,
|
|
165
335
|
messages,
|
|
166
|
-
tools:
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
336
|
+
tools: responseFormat.type === "text"
|
|
337
|
+
? tools
|
|
338
|
+
: [{
|
|
339
|
+
name: responseFormat.objectName,
|
|
340
|
+
description: Tool.getDescriptionFromSchemaAst(responseFormat.schema.ast) ?? "Respond with a JSON object",
|
|
341
|
+
input_schema: Tool.getJsonSchemaFromSchemaAst(responseFormat.schema.ast) as any
|
|
342
|
+
}],
|
|
343
|
+
tool_choice: responseFormat.type === "text"
|
|
344
|
+
? toolChoice
|
|
345
|
+
: {
|
|
346
|
+
type: "tool",
|
|
347
|
+
name: responseFormat.objectName,
|
|
348
|
+
disable_parallel_tool_use: true
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return { betas: new Set([...messageBetas, ...toolBetas]), request }
|
|
173
352
|
}
|
|
174
353
|
)
|
|
175
354
|
|
|
176
|
-
return yield*
|
|
355
|
+
return yield* LanguageModel.make({
|
|
177
356
|
generateText: Effect.fnUntraced(
|
|
178
357
|
function*(options) {
|
|
179
|
-
const request = yield* makeRequest(
|
|
358
|
+
const { betas, request } = yield* makeRequest(options)
|
|
180
359
|
annotateRequest(options.span, request)
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
},
|
|
186
|
-
Effect.catchAll((cause) =>
|
|
187
|
-
AiError.is(cause) ? cause : new AiError({
|
|
188
|
-
module: "AnthropicLanguageModel",
|
|
189
|
-
method: "generateText",
|
|
190
|
-
description: "An error occurred",
|
|
191
|
-
cause
|
|
360
|
+
const anthropicBeta = betas.size > 0 ? Array.from(betas).join(",") : undefined
|
|
361
|
+
const rawResponse = yield* client.createMessage({
|
|
362
|
+
params: { "anthropic-beta": anthropicBeta },
|
|
363
|
+
payload: request
|
|
192
364
|
})
|
|
193
|
-
|
|
365
|
+
annotateResponse(options.span, rawResponse)
|
|
366
|
+
return yield* makeResponse(rawResponse, options)
|
|
367
|
+
}
|
|
194
368
|
),
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
369
|
+
|
|
370
|
+
streamText: Effect.fnUntraced(
|
|
371
|
+
function*(options) {
|
|
372
|
+
const { betas, request } = yield* makeRequest(options)
|
|
373
|
+
annotateRequest(options.span, request)
|
|
374
|
+
const anthropicBeta = betas.size > 0 ? Array.from(betas).join(",") : undefined
|
|
375
|
+
return client.createMessageStream({
|
|
376
|
+
params: { "anthropic-beta": anthropicBeta },
|
|
377
|
+
payload: request
|
|
378
|
+
})
|
|
379
|
+
},
|
|
380
|
+
(effect, options) =>
|
|
381
|
+
effect.pipe(
|
|
382
|
+
Effect.flatMap((stream) => makeStreamResponse(stream, options)),
|
|
383
|
+
Stream.unwrap,
|
|
384
|
+
Stream.map((response) => {
|
|
385
|
+
annotateStreamResponse(options.span, response)
|
|
386
|
+
return response
|
|
387
|
+
})
|
|
213
388
|
)
|
|
214
|
-
|
|
215
|
-
}
|
|
389
|
+
)
|
|
216
390
|
})
|
|
217
391
|
})
|
|
218
392
|
|
|
@@ -223,8 +397,8 @@ export const make = Effect.fnUntraced(function*(options: {
|
|
|
223
397
|
export const layer = (options: {
|
|
224
398
|
readonly model: (string & {}) | Model
|
|
225
399
|
readonly config?: Omit<Config.Service, "model">
|
|
226
|
-
}): Layer.Layer<
|
|
227
|
-
Layer.effect(
|
|
400
|
+
}): Layer.Layer<LanguageModel.LanguageModel, never, AnthropicClient> =>
|
|
401
|
+
Layer.effect(LanguageModel.LanguageModel, make({ model: options.model, config: options.config }))
|
|
228
402
|
|
|
229
403
|
/**
|
|
230
404
|
* @since 1.0.0
|
|
@@ -233,7 +407,7 @@ export const layer = (options: {
|
|
|
233
407
|
export const layerWithTokenizer = (options: {
|
|
234
408
|
readonly model: (string & {}) | Model
|
|
235
409
|
readonly config?: Omit<Config.Service, "model">
|
|
236
|
-
}): Layer.Layer<
|
|
410
|
+
}): Layer.Layer<LanguageModel.LanguageModel | Tokenizer.Tokenizer, never, AnthropicClient> =>
|
|
237
411
|
Layer.merge(layer(options), AnthropicTokenizer.layer)
|
|
238
412
|
|
|
239
413
|
/**
|
|
@@ -269,282 +443,797 @@ export const withConfigOverride: {
|
|
|
269
443
|
))
|
|
270
444
|
|
|
271
445
|
// =============================================================================
|
|
272
|
-
//
|
|
446
|
+
// Prompt Conversion
|
|
273
447
|
// =============================================================================
|
|
274
448
|
|
|
275
|
-
|
|
449
|
+
const prepareMessages: (options: LanguageModel.ProviderOptions) => Effect.Effect<{
|
|
450
|
+
readonly betas: ReadonlySet<string>
|
|
451
|
+
readonly system: ReadonlyArray<typeof Generated.BetaRequestTextBlock.Encoded> | undefined
|
|
452
|
+
readonly messages: ReadonlyArray<typeof Generated.BetaInputMessage.Encoded>
|
|
453
|
+
}, AiError.AiError> = Effect.fnUntraced(function*(options) {
|
|
454
|
+
const betas = new Set<string>()
|
|
455
|
+
const groups = groupMessages(options.prompt)
|
|
276
456
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
readonly messages: Array<AiInput.AssistantMessage>
|
|
280
|
-
}
|
|
457
|
+
let system: Array<typeof Generated.BetaRequestTextBlock.Encoded> | undefined = undefined
|
|
458
|
+
const messages: Array<typeof Generated.BetaInputMessage.Encoded> = []
|
|
281
459
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
}
|
|
460
|
+
for (let i = 0; i < groups.length; i++) {
|
|
461
|
+
const group = groups[i]
|
|
462
|
+
const isLastGroup = i === groups.length - 1
|
|
286
463
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
current = { type: "assistant", messages: [] }
|
|
295
|
-
messages.push(current)
|
|
296
|
-
}
|
|
297
|
-
current.messages.push(message)
|
|
464
|
+
switch (group.type) {
|
|
465
|
+
case "system": {
|
|
466
|
+
system = group.messages.map((message) => ({
|
|
467
|
+
type: "text",
|
|
468
|
+
text: message.content,
|
|
469
|
+
cache_control: getCacheControl(message)
|
|
470
|
+
}))
|
|
298
471
|
break
|
|
299
472
|
}
|
|
300
|
-
case "ToolMessage":
|
|
301
|
-
case "UserMessage": {
|
|
302
|
-
if (current?.type !== "user") {
|
|
303
|
-
current = { type: "user", messages: [] }
|
|
304
|
-
messages.push(current)
|
|
305
|
-
}
|
|
306
|
-
current.messages.push(message)
|
|
307
|
-
break
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
return messages
|
|
312
|
-
}
|
|
313
473
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
}
|
|
358
|
-
case "ToolCallPart": {
|
|
359
|
-
content.push({
|
|
360
|
-
type: "tool_use",
|
|
361
|
-
id: part.id,
|
|
362
|
-
name: part.name,
|
|
363
|
-
input: part.params as any
|
|
364
|
-
})
|
|
365
|
-
break
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
messages.push({ role: "assistant", content })
|
|
371
|
-
break
|
|
372
|
-
}
|
|
373
|
-
case "user": {
|
|
374
|
-
const content: Array<typeof Generated.InputContentBlock.Encoded> = []
|
|
375
|
-
for (let j = 0; j < group.messages.length; j++) {
|
|
376
|
-
const message = group.messages[j]
|
|
377
|
-
switch (message._tag) {
|
|
378
|
-
case "ToolMessage": {
|
|
379
|
-
for (let k = 0; k < message.parts.length; k++) {
|
|
380
|
-
const part = message.parts[k]
|
|
381
|
-
// TODO: support advanced tool result content parts
|
|
382
|
-
content.push({
|
|
383
|
-
type: "tool_result",
|
|
384
|
-
tool_use_id: part.id,
|
|
385
|
-
content: JSON.stringify(part.result)
|
|
386
|
-
})
|
|
387
|
-
}
|
|
388
|
-
break
|
|
389
|
-
}
|
|
390
|
-
case "UserMessage": {
|
|
391
|
-
for (let k = 0; k < message.parts.length; k++) {
|
|
392
|
-
const part = message.parts[k]
|
|
393
|
-
switch (part._tag) {
|
|
394
|
-
case "FilePart": {
|
|
395
|
-
if (Predicate.isUndefined(part.mediaType) || part.mediaType !== "application/pdf") {
|
|
396
|
-
return yield* new AiError({
|
|
397
|
-
module: "AnthropicLanguageModel",
|
|
398
|
-
method,
|
|
399
|
-
description: "AnthropicLanguageModel only supports PDF file inputs"
|
|
400
|
-
})
|
|
401
|
-
}
|
|
474
|
+
case "user": {
|
|
475
|
+
const content: Array<typeof Generated.BetaInputContentBlock.Encoded> = []
|
|
476
|
+
|
|
477
|
+
for (const message of group.messages) {
|
|
478
|
+
switch (message.role) {
|
|
479
|
+
case "user": {
|
|
480
|
+
for (let j = 0; j < message.content.length; j++) {
|
|
481
|
+
const part = message.content[j]
|
|
482
|
+
const isLastPart = j === message.content.length - 1
|
|
483
|
+
|
|
484
|
+
// Attempt to get the cache control from the part first. If
|
|
485
|
+
// the part does not have cache control defined and we are
|
|
486
|
+
// evaluating the last part for this message, also check the
|
|
487
|
+
// message for cache control.
|
|
488
|
+
const cacheControl = getCacheControl(part) ?? (
|
|
489
|
+
isLastPart ? getCacheControl(message) : undefined
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
switch (part.type) {
|
|
493
|
+
case "text": {
|
|
494
|
+
content.push({
|
|
495
|
+
type: "text",
|
|
496
|
+
text: part.text,
|
|
497
|
+
cache_control: cacheControl
|
|
498
|
+
})
|
|
499
|
+
break
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
case "file": {
|
|
503
|
+
if (part.mediaType.startsWith("image/")) {
|
|
504
|
+
const source = part.data instanceof URL ?
|
|
505
|
+
{
|
|
506
|
+
type: "url",
|
|
507
|
+
url: part.data.toString()
|
|
508
|
+
} as const :
|
|
509
|
+
{
|
|
510
|
+
type: "base64",
|
|
511
|
+
media_type: part.mediaType === "image/*"
|
|
512
|
+
? "image/jpeg"
|
|
513
|
+
: (part.mediaType as typeof Generated.Base64ImageSourceMediaType.Encoded),
|
|
514
|
+
data: typeof part.data === "string" ? part.data : Encoding.encodeBase64(part.data)
|
|
515
|
+
} as const
|
|
516
|
+
|
|
402
517
|
content.push({
|
|
403
|
-
type: "
|
|
404
|
-
source
|
|
518
|
+
type: "image",
|
|
519
|
+
source,
|
|
520
|
+
cache_control: cacheControl
|
|
521
|
+
})
|
|
522
|
+
} else if (part.mediaType === "application/pdf" || part.mediaType === "text/plain") {
|
|
523
|
+
if (part.mediaType === "application/pdf") {
|
|
524
|
+
betas.add("pdfs-2024-09-25")
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
const enableCitations = shouldEnableCitations(part)
|
|
528
|
+
const documentOptions = getDocumentMetadata(part)
|
|
529
|
+
|
|
530
|
+
const source = part.data instanceof URL
|
|
531
|
+
? {
|
|
532
|
+
type: "url",
|
|
533
|
+
url: part.data.toString()
|
|
534
|
+
} as const
|
|
535
|
+
: part.mediaType === "application/pdf"
|
|
536
|
+
? {
|
|
405
537
|
type: "base64",
|
|
406
538
|
media_type: "application/pdf",
|
|
407
|
-
data:
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
539
|
+
data: typeof part.data === "string"
|
|
540
|
+
? part.data
|
|
541
|
+
: Encoding.encodeBase64(part.data)
|
|
542
|
+
} as const
|
|
543
|
+
: {
|
|
544
|
+
type: "text",
|
|
545
|
+
media_type: "text/plain",
|
|
546
|
+
data: typeof part.data === "string"
|
|
547
|
+
? part.data
|
|
548
|
+
: Encoding.encodeBase64(part.data)
|
|
549
|
+
} as const
|
|
550
|
+
|
|
413
551
|
content.push({
|
|
414
552
|
type: "document",
|
|
415
|
-
source
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
break
|
|
421
|
-
}
|
|
422
|
-
case "TextPart": {
|
|
423
|
-
content.push({
|
|
424
|
-
type: "text",
|
|
425
|
-
text: part.text
|
|
553
|
+
source,
|
|
554
|
+
title: documentOptions?.title ?? part.fileName,
|
|
555
|
+
...(documentOptions?.context ? { context: documentOptions.context } : undefined),
|
|
556
|
+
...(enableCitations ? { citations: { enabled: true } } : undefined),
|
|
557
|
+
cache_control: cacheControl
|
|
426
558
|
})
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
type:
|
|
432
|
-
source: {
|
|
433
|
-
type: "base64",
|
|
434
|
-
media_type: part.mediaType ?? "image/jpeg" as any,
|
|
435
|
-
data: Encoding.encodeBase64(part.data)
|
|
436
|
-
}
|
|
437
|
-
})
|
|
438
|
-
break
|
|
439
|
-
}
|
|
440
|
-
case "ImageUrlPart": {
|
|
441
|
-
content.push({
|
|
442
|
-
type: "image",
|
|
443
|
-
source: {
|
|
444
|
-
type: "url",
|
|
445
|
-
url: part.url.toString()
|
|
446
|
-
}
|
|
559
|
+
} else {
|
|
560
|
+
return yield* new AiError.MalformedInput({
|
|
561
|
+
module: "AnthropicLanguageModel",
|
|
562
|
+
method: "prepareMessages",
|
|
563
|
+
description: `Detected unsupported media type for file: '${part.mediaType}'`
|
|
447
564
|
})
|
|
448
|
-
break
|
|
449
565
|
}
|
|
566
|
+
break
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
break
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// TODO: advanced tool result content parts
|
|
575
|
+
case "tool": {
|
|
576
|
+
for (const part of message.content) {
|
|
577
|
+
content.push({
|
|
578
|
+
type: "tool_result",
|
|
579
|
+
tool_use_id: part.id,
|
|
580
|
+
content: JSON.stringify(part.result)
|
|
581
|
+
})
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
break
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
messages.push({ role: "user", content })
|
|
590
|
+
|
|
591
|
+
break
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
case "assistant": {
|
|
595
|
+
const content: Array<typeof Generated.BetaInputContentBlock.Encoded> = []
|
|
596
|
+
|
|
597
|
+
for (let j = 0; j < group.messages.length; j++) {
|
|
598
|
+
const message = group.messages[j]
|
|
599
|
+
const isLastMessage = j === group.messages.length - 1
|
|
600
|
+
|
|
601
|
+
for (let k = 0; k < message.content.length; k++) {
|
|
602
|
+
const part = message.content[k]
|
|
603
|
+
const isLastPart = k === message.content.length - 1
|
|
604
|
+
|
|
605
|
+
// Attempt to get the cache control from the part first. If
|
|
606
|
+
// the part does not have cache control defined and we are
|
|
607
|
+
// evaluating the last part for this message, also check the
|
|
608
|
+
// message for cache control.
|
|
609
|
+
const cacheControl = getCacheControl(part) ?? (
|
|
610
|
+
isLastPart ? getCacheControl(message) : undefined
|
|
611
|
+
)
|
|
612
|
+
|
|
613
|
+
switch (part.type) {
|
|
614
|
+
case "text": {
|
|
615
|
+
content.push({
|
|
616
|
+
type: "text",
|
|
617
|
+
// Anthropic does not allow trailing whitespace in assistant
|
|
618
|
+
// content blocks
|
|
619
|
+
text: isLastGroup && isLastMessage && isLastPart
|
|
620
|
+
? part.text.trim()
|
|
621
|
+
: part.text
|
|
622
|
+
})
|
|
623
|
+
break
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
case "reasoning": {
|
|
627
|
+
const options = part.options.anthropic
|
|
628
|
+
if (Predicate.isNotUndefined(options)) {
|
|
629
|
+
if (options.type === "thinking") {
|
|
630
|
+
content.push({
|
|
631
|
+
type: "thinking",
|
|
632
|
+
thinking: part.text,
|
|
633
|
+
signature: options.signature
|
|
634
|
+
})
|
|
635
|
+
} else {
|
|
636
|
+
content.push({
|
|
637
|
+
type: "redacted_thinking",
|
|
638
|
+
data: options.redactedData
|
|
639
|
+
})
|
|
450
640
|
}
|
|
451
641
|
}
|
|
452
642
|
break
|
|
453
643
|
}
|
|
644
|
+
|
|
645
|
+
case "tool-call": {
|
|
646
|
+
if (part.providerExecuted) {
|
|
647
|
+
if (part.name === "AnthropicWebSearch") {
|
|
648
|
+
content.push({
|
|
649
|
+
type: "server_tool_use",
|
|
650
|
+
id: part.id,
|
|
651
|
+
name: "web_search",
|
|
652
|
+
input: part.params as any,
|
|
653
|
+
cache_control: cacheControl
|
|
654
|
+
})
|
|
655
|
+
}
|
|
656
|
+
if (part.name === "AnthropicCodeExecution") {
|
|
657
|
+
content.push({
|
|
658
|
+
type: "server_tool_use",
|
|
659
|
+
id: part.id,
|
|
660
|
+
name: "code_execution",
|
|
661
|
+
input: part.params as any,
|
|
662
|
+
cache_control: cacheControl
|
|
663
|
+
})
|
|
664
|
+
}
|
|
665
|
+
} else {
|
|
666
|
+
content.push({
|
|
667
|
+
type: "tool_use",
|
|
668
|
+
id: part.id,
|
|
669
|
+
name: part.name,
|
|
670
|
+
input: part.params as any,
|
|
671
|
+
cache_control: cacheControl
|
|
672
|
+
})
|
|
673
|
+
}
|
|
674
|
+
break
|
|
675
|
+
}
|
|
454
676
|
}
|
|
455
677
|
}
|
|
456
|
-
messages.push({ role: "user", content })
|
|
457
|
-
break
|
|
458
678
|
}
|
|
679
|
+
|
|
680
|
+
messages.push({ role: "assistant", content })
|
|
681
|
+
|
|
682
|
+
break
|
|
459
683
|
}
|
|
460
684
|
}
|
|
461
|
-
if (Arr.isNonEmptyReadonlyArray(messages)) {
|
|
462
|
-
return messages
|
|
463
|
-
}
|
|
464
|
-
return yield* new AiError({
|
|
465
|
-
module: "AnthropicLanguageModel",
|
|
466
|
-
method,
|
|
467
|
-
description: "Prompt contained no messages"
|
|
468
|
-
})
|
|
469
685
|
}
|
|
470
|
-
)
|
|
471
686
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
687
|
+
return {
|
|
688
|
+
system,
|
|
689
|
+
messages,
|
|
690
|
+
betas
|
|
691
|
+
}
|
|
692
|
+
})
|
|
693
|
+
|
|
694
|
+
// =============================================================================
|
|
695
|
+
// Response Conversion
|
|
696
|
+
// =============================================================================
|
|
697
|
+
|
|
698
|
+
const makeResponse: (
|
|
699
|
+
response: Generated.BetaMessage,
|
|
700
|
+
options: LanguageModel.ProviderOptions
|
|
701
|
+
) => Effect.Effect<
|
|
702
|
+
Array<Response.PartEncoded>,
|
|
703
|
+
never,
|
|
704
|
+
IdGenerator.IdGenerator
|
|
705
|
+
> = Effect.fnUntraced(
|
|
706
|
+
function*(response, options) {
|
|
707
|
+
const idGenerator = yield* IdGenerator.IdGenerator
|
|
708
|
+
const parts: Array<Response.PartEncoded> = []
|
|
709
|
+
const citableDocuments = extractCitableDocuments(options.prompt)
|
|
710
|
+
|
|
711
|
+
parts.push({
|
|
712
|
+
type: "response-metadata",
|
|
713
|
+
id: response.id,
|
|
714
|
+
modelId: response.model,
|
|
715
|
+
timestamp: DateTime.formatIso(yield* DateTime.now)
|
|
716
|
+
})
|
|
717
|
+
|
|
481
718
|
for (const part of response.content) {
|
|
482
719
|
switch (part.type) {
|
|
483
720
|
case "text": {
|
|
484
|
-
parts
|
|
485
|
-
|
|
721
|
+
// The text parts should only be added to the response here if the
|
|
722
|
+
// response format is `"text"`. If the response format is `"json"`,
|
|
723
|
+
// then the text parts must instead be added to the response when a
|
|
724
|
+
// tool call is received.
|
|
725
|
+
if (options.responseFormat.type === "text") {
|
|
726
|
+
parts.push({
|
|
727
|
+
type: "text",
|
|
486
728
|
text: part.text
|
|
487
|
-
}
|
|
488
|
-
|
|
729
|
+
})
|
|
730
|
+
|
|
731
|
+
if (Predicate.isNotNullable(part.citations)) {
|
|
732
|
+
for (const citation of part.citations) {
|
|
733
|
+
const source = yield* processCitation(citation, citableDocuments, idGenerator)
|
|
734
|
+
if (Predicate.isNotUndefined(source)) {
|
|
735
|
+
parts.push(source)
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
break
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
case "thinking": {
|
|
745
|
+
parts.push({
|
|
746
|
+
type: "reasoning",
|
|
747
|
+
text: part.thinking,
|
|
748
|
+
metadata: { anthropic: { type: "thinking", signature: part.signature } }
|
|
749
|
+
})
|
|
750
|
+
break
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
case "redacted_thinking": {
|
|
754
|
+
parts.push({
|
|
755
|
+
type: "reasoning",
|
|
756
|
+
text: "",
|
|
757
|
+
metadata: { anthropic: { type: "redacted_thinking", redactedData: part.data } }
|
|
758
|
+
})
|
|
489
759
|
break
|
|
490
760
|
}
|
|
761
|
+
|
|
491
762
|
case "tool_use": {
|
|
492
|
-
|
|
493
|
-
|
|
763
|
+
// When a `"json"` response format is requested, the JSON that we need
|
|
764
|
+
// will be returned by the tool call injected into the request
|
|
765
|
+
if (options.responseFormat.type === "json") {
|
|
766
|
+
parts.push({
|
|
767
|
+
type: "text",
|
|
768
|
+
text: JSON.stringify(part.input)
|
|
769
|
+
})
|
|
770
|
+
} else {
|
|
771
|
+
const providerTool = AnthropicTool.getProviderDefinedToolName(part.name)
|
|
772
|
+
const name = Predicate.isNotUndefined(providerTool) ? providerTool : part.name
|
|
773
|
+
const providerName = Predicate.isNotUndefined(providerTool) ? part.name : undefined
|
|
774
|
+
parts.push({
|
|
775
|
+
type: "tool-call",
|
|
494
776
|
id: part.id,
|
|
495
|
-
name
|
|
496
|
-
params: part.input
|
|
777
|
+
name,
|
|
778
|
+
params: part.input,
|
|
779
|
+
providerName,
|
|
780
|
+
providerExecuted: false
|
|
497
781
|
})
|
|
498
|
-
|
|
782
|
+
}
|
|
783
|
+
|
|
499
784
|
break
|
|
500
785
|
}
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
786
|
+
|
|
787
|
+
case "server_tool_use": {
|
|
788
|
+
const providerTool = AnthropicTool.getProviderDefinedToolName(part.name)
|
|
789
|
+
if (Predicate.isNotUndefined(providerTool)) {
|
|
790
|
+
parts.push({
|
|
791
|
+
type: "tool-call",
|
|
792
|
+
id: part.id,
|
|
793
|
+
name: providerTool,
|
|
794
|
+
params: part.input,
|
|
795
|
+
providerName: part.name,
|
|
796
|
+
providerExecuted: true
|
|
797
|
+
})
|
|
798
|
+
}
|
|
799
|
+
|
|
508
800
|
break
|
|
509
801
|
}
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
802
|
+
|
|
803
|
+
case "code_execution_tool_result":
|
|
804
|
+
case "bash_code_execution_tool_result":
|
|
805
|
+
case "text_editor_code_execution_tool_result": {
|
|
806
|
+
parts.push({
|
|
807
|
+
type: "tool-result",
|
|
808
|
+
id: part.tool_use_id,
|
|
809
|
+
name: "AnthropicCodeExecution",
|
|
810
|
+
result: part.content,
|
|
811
|
+
providerName: "code_execution",
|
|
812
|
+
providerExecuted: true
|
|
813
|
+
})
|
|
814
|
+
|
|
815
|
+
break
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
case "web_search_tool_result": {
|
|
819
|
+
parts.push({
|
|
820
|
+
type: "tool-result",
|
|
821
|
+
id: part.tool_use_id,
|
|
822
|
+
name: "AnthropicWebSearch",
|
|
823
|
+
result: part.content,
|
|
824
|
+
providerName: "web_search",
|
|
825
|
+
providerExecuted: true
|
|
826
|
+
})
|
|
827
|
+
|
|
516
828
|
break
|
|
517
829
|
}
|
|
518
830
|
}
|
|
519
831
|
}
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
832
|
+
|
|
833
|
+
// Anthropic always returns a non-null `stop_reason` for non-streaming responses
|
|
834
|
+
const finishReason = InternalUtilities.resolveFinishReason(
|
|
835
|
+
response.stop_reason!,
|
|
836
|
+
options.responseFormat.type === "json"
|
|
837
|
+
)
|
|
838
|
+
|
|
839
|
+
parts.push({
|
|
840
|
+
type: "finish",
|
|
841
|
+
reason: finishReason,
|
|
842
|
+
usage: {
|
|
843
|
+
inputTokens: response.usage.input_tokens,
|
|
844
|
+
outputTokens: response.usage.output_tokens,
|
|
845
|
+
totalTokens: response.usage.input_tokens + response.usage.output_tokens,
|
|
846
|
+
cachedInputTokens: response.usage.cache_read_input_tokens ?? undefined
|
|
847
|
+
},
|
|
848
|
+
metadata: {
|
|
849
|
+
anthropic: {
|
|
850
|
+
usage: response.usage,
|
|
851
|
+
stopSequence: response.stop_sequence ?? undefined
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
})
|
|
855
|
+
|
|
856
|
+
return parts
|
|
857
|
+
}
|
|
858
|
+
)
|
|
859
|
+
|
|
860
|
+
const makeStreamResponse: (
|
|
861
|
+
stream: Stream.Stream<MessageStreamEvent, AiError.AiError>,
|
|
862
|
+
options: LanguageModel.ProviderOptions
|
|
863
|
+
) => Effect.Effect<
|
|
864
|
+
Stream.Stream<Response.StreamPartEncoded, AiError.AiError>,
|
|
865
|
+
never,
|
|
866
|
+
IdGenerator.IdGenerator
|
|
867
|
+
> = Effect.fnUntraced(
|
|
868
|
+
function*(stream, options) {
|
|
869
|
+
const idGenerator = yield* IdGenerator.IdGenerator
|
|
870
|
+
const citableDocuments = extractCitableDocuments(options.prompt)
|
|
871
|
+
|
|
872
|
+
// Setup all requisite state for the streaming response
|
|
873
|
+
let finishReason: Response.FinishReason = "unknown"
|
|
874
|
+
const contentBlocks: Record<
|
|
875
|
+
number,
|
|
876
|
+
| {
|
|
877
|
+
readonly type: "text"
|
|
878
|
+
}
|
|
879
|
+
| {
|
|
880
|
+
readonly type: "reasoning"
|
|
881
|
+
}
|
|
882
|
+
| {
|
|
883
|
+
readonly type: "tool-call"
|
|
884
|
+
readonly id: string
|
|
885
|
+
readonly name: string
|
|
886
|
+
params: string
|
|
887
|
+
readonly providerName: string | undefined
|
|
888
|
+
readonly providerExecuted: boolean
|
|
889
|
+
}
|
|
890
|
+
> = {}
|
|
891
|
+
let blockType:
|
|
892
|
+
| "text"
|
|
893
|
+
| "thinking"
|
|
894
|
+
| "redacted_thinking"
|
|
895
|
+
| "tool_use"
|
|
896
|
+
| "server_tool_use"
|
|
897
|
+
| "web_search_tool_result"
|
|
898
|
+
| "code_execution_tool_result"
|
|
899
|
+
| "bash_code_execution_tool_result"
|
|
900
|
+
| "text_editor_code_execution_tool_result"
|
|
901
|
+
| "mcp_tool_use"
|
|
902
|
+
| "mcp_tool_result"
|
|
903
|
+
| "container_upload"
|
|
904
|
+
| undefined = undefined
|
|
905
|
+
const usage: Mutable<typeof Response.Usage.Encoded> = {
|
|
906
|
+
inputTokens: undefined,
|
|
907
|
+
outputTokens: undefined,
|
|
908
|
+
totalTokens: undefined
|
|
523
909
|
}
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
910
|
+
let metaUsage: Generated.BetaUsage | undefined = undefined
|
|
911
|
+
let stopSequence: string | undefined = undefined
|
|
912
|
+
|
|
913
|
+
return stream.pipe(
|
|
914
|
+
Stream.mapEffect(Effect.fnUntraced(function*(event) {
|
|
915
|
+
const parts: Array<Response.StreamPartEncoded> = []
|
|
916
|
+
|
|
917
|
+
switch (event.type) {
|
|
918
|
+
case "ping": {
|
|
919
|
+
break
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
case "message_start": {
|
|
923
|
+
// Track usage metadata
|
|
924
|
+
usage.inputTokens = event.message.usage.input_tokens
|
|
925
|
+
metaUsage = event.message.usage
|
|
926
|
+
|
|
927
|
+
// Track response metadata
|
|
928
|
+
parts.push({
|
|
929
|
+
type: "response-metadata",
|
|
930
|
+
id: event.message.id,
|
|
931
|
+
modelId: event.message.model,
|
|
932
|
+
timestamp: DateTime.formatIso(yield* DateTime.now)
|
|
933
|
+
})
|
|
934
|
+
|
|
935
|
+
break
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
case "message_delta": {
|
|
939
|
+
// Track usage metadata
|
|
940
|
+
if (Predicate.isNotNullable(event.usage.output_tokens)) {
|
|
941
|
+
usage.outputTokens = event.usage.output_tokens
|
|
942
|
+
}
|
|
943
|
+
usage.totalTokens = (usage.inputTokens ?? 0) + (event.usage.output_tokens ?? 0)
|
|
944
|
+
|
|
945
|
+
// Track stop sequence metadata
|
|
946
|
+
if (Predicate.isNotNullable(event.delta.stop_sequence)) {
|
|
947
|
+
stopSequence = event.delta.stop_sequence
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
// Track the response finish reason
|
|
951
|
+
if (Predicate.isNotNullable(event.delta.stop_reason)) {
|
|
952
|
+
finishReason = InternalUtilities.resolveFinishReason(event.delta.stop_reason)
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
break
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
case "message_stop": {
|
|
959
|
+
parts.push({
|
|
960
|
+
type: "finish",
|
|
961
|
+
reason: finishReason,
|
|
962
|
+
usage,
|
|
963
|
+
metadata: { anthropic: { usage: metaUsage, stopSequence } }
|
|
964
|
+
})
|
|
965
|
+
|
|
966
|
+
break
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
case "content_block_start": {
|
|
970
|
+
blockType = event.content_block.type
|
|
971
|
+
|
|
972
|
+
switch (event.content_block.type) {
|
|
973
|
+
case "text": {
|
|
974
|
+
contentBlocks[event.index] = { type: "text" }
|
|
975
|
+
|
|
976
|
+
parts.push({
|
|
977
|
+
type: "text-start",
|
|
978
|
+
id: event.index.toString()
|
|
979
|
+
})
|
|
980
|
+
|
|
981
|
+
break
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
case "thinking": {
|
|
985
|
+
contentBlocks[event.index] = { type: "reasoning" }
|
|
986
|
+
|
|
987
|
+
parts.push({
|
|
988
|
+
type: "reasoning-start",
|
|
989
|
+
id: event.index.toString()
|
|
990
|
+
})
|
|
991
|
+
|
|
992
|
+
break
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
case "redacted_thinking": {
|
|
996
|
+
contentBlocks[event.index] = { type: "reasoning" }
|
|
997
|
+
|
|
998
|
+
parts.push({
|
|
999
|
+
type: "reasoning-start",
|
|
1000
|
+
id: event.index.toString(),
|
|
1001
|
+
metadata: {
|
|
1002
|
+
anthropic: {
|
|
1003
|
+
type: "redacted_thinking",
|
|
1004
|
+
redactedData: event.content_block.data
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
})
|
|
1008
|
+
|
|
1009
|
+
break
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
case "tool_use": {
|
|
1013
|
+
const toolName = event.content_block.name
|
|
1014
|
+
const providerTool = AnthropicTool.getProviderDefinedToolName(toolName)
|
|
1015
|
+
const name = Predicate.isNotUndefined(providerTool) ? providerTool : toolName
|
|
1016
|
+
const providerName = Predicate.isNotUndefined(providerTool) ? toolName : undefined
|
|
1017
|
+
|
|
1018
|
+
contentBlocks[event.index] = {
|
|
1019
|
+
type: "tool-call",
|
|
1020
|
+
id: event.content_block.id,
|
|
1021
|
+
name,
|
|
1022
|
+
params: "",
|
|
1023
|
+
providerName,
|
|
1024
|
+
providerExecuted: false
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
parts.push({
|
|
1028
|
+
type: "tool-params-start",
|
|
1029
|
+
id: event.content_block.id,
|
|
1030
|
+
name: toolName,
|
|
1031
|
+
providerName,
|
|
1032
|
+
providerExecuted: false
|
|
1033
|
+
})
|
|
1034
|
+
|
|
1035
|
+
break
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
case "server_tool_use": {
|
|
1039
|
+
const toolName = event.content_block.name
|
|
1040
|
+
const providerTool = AnthropicTool.getProviderDefinedToolName(toolName)
|
|
1041
|
+
if (Predicate.isNotUndefined(providerTool)) {
|
|
1042
|
+
contentBlocks[event.index] = {
|
|
1043
|
+
type: "tool-call",
|
|
1044
|
+
id: event.content_block.id,
|
|
1045
|
+
name: providerTool,
|
|
1046
|
+
params: "",
|
|
1047
|
+
providerName: toolName,
|
|
1048
|
+
providerExecuted: true
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
parts.push({
|
|
1052
|
+
type: "tool-params-start",
|
|
1053
|
+
id: event.content_block.id,
|
|
1054
|
+
name: providerTool,
|
|
1055
|
+
providerName: toolName,
|
|
1056
|
+
providerExecuted: true
|
|
1057
|
+
})
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
break
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
case "code_execution_tool_result":
|
|
1064
|
+
case "bash_code_execution_tool_result":
|
|
1065
|
+
case "text_editor_code_execution_tool_result": {
|
|
1066
|
+
parts.push({
|
|
1067
|
+
type: "tool-result",
|
|
1068
|
+
id: event.content_block.tool_use_id,
|
|
1069
|
+
name: "AnthropicCodeExecution",
|
|
1070
|
+
result: event.content_block.content,
|
|
1071
|
+
providerName: "code_execution",
|
|
1072
|
+
providerExecuted: true
|
|
1073
|
+
})
|
|
1074
|
+
|
|
1075
|
+
break
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
case "web_search_tool_result": {
|
|
1079
|
+
parts.push({
|
|
1080
|
+
type: "tool-result",
|
|
1081
|
+
id: event.content_block.tool_use_id,
|
|
1082
|
+
name: "AnthropicWebSearch",
|
|
1083
|
+
result: event.content_block.content,
|
|
1084
|
+
providerName: "web_search",
|
|
1085
|
+
providerExecuted: true
|
|
1086
|
+
})
|
|
1087
|
+
|
|
1088
|
+
break
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
break
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
case "content_block_delta": {
|
|
1096
|
+
switch (event.delta.type) {
|
|
1097
|
+
case "text_delta": {
|
|
1098
|
+
parts.push({
|
|
1099
|
+
type: "text-delta",
|
|
1100
|
+
id: event.index.toString(),
|
|
1101
|
+
delta: event.delta.text
|
|
1102
|
+
})
|
|
1103
|
+
|
|
1104
|
+
break
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
case "thinking_delta": {
|
|
1108
|
+
parts.push({
|
|
1109
|
+
type: "reasoning-delta",
|
|
1110
|
+
id: event.index.toString(),
|
|
1111
|
+
delta: event.delta.thinking
|
|
1112
|
+
})
|
|
1113
|
+
|
|
1114
|
+
break
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
case "signature_delta": {
|
|
1118
|
+
if (blockType === "thinking") {
|
|
1119
|
+
parts.push({
|
|
1120
|
+
type: "reasoning-delta",
|
|
1121
|
+
id: event.index.toString(),
|
|
1122
|
+
delta: "",
|
|
1123
|
+
metadata: {
|
|
1124
|
+
anthropic: {
|
|
1125
|
+
type: "thinking",
|
|
1126
|
+
signature: event.delta.signature
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
})
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
break
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
case "input_json_delta": {
|
|
1136
|
+
const contentBlock = contentBlocks[event.index]
|
|
1137
|
+
const delta = event.delta.partial_json
|
|
1138
|
+
|
|
1139
|
+
if (contentBlock.type === "tool-call") {
|
|
1140
|
+
parts.push({
|
|
1141
|
+
type: "tool-params-delta",
|
|
1142
|
+
id: contentBlock.id,
|
|
1143
|
+
delta
|
|
1144
|
+
})
|
|
1145
|
+
|
|
1146
|
+
contentBlock.params += delta
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
break
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
case "citations_delta": {
|
|
1153
|
+
const citation = event.delta.citation
|
|
1154
|
+
|
|
1155
|
+
const source = yield* processCitation(citation, citableDocuments, idGenerator)
|
|
1156
|
+
if (Predicate.isNotUndefined(source)) {
|
|
1157
|
+
parts.push(source)
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
break
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
case "content_block_stop": {
|
|
1166
|
+
if (Predicate.isNotNullable(contentBlocks[event.index])) {
|
|
1167
|
+
const contentBlock = contentBlocks[event.index]
|
|
1168
|
+
|
|
1169
|
+
switch (contentBlock.type) {
|
|
1170
|
+
case "text": {
|
|
1171
|
+
parts.push({
|
|
1172
|
+
type: "text-end",
|
|
1173
|
+
id: event.index.toString()
|
|
1174
|
+
})
|
|
1175
|
+
break
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
case "reasoning": {
|
|
1179
|
+
parts.push({
|
|
1180
|
+
type: "reasoning-end",
|
|
1181
|
+
id: event.index.toString()
|
|
1182
|
+
})
|
|
1183
|
+
break
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
case "tool-call": {
|
|
1187
|
+
parts.push({
|
|
1188
|
+
type: "tool-params-end",
|
|
1189
|
+
id: contentBlock.id
|
|
1190
|
+
})
|
|
1191
|
+
|
|
1192
|
+
// If the tool call has no parameters, an empty string is returned
|
|
1193
|
+
const params = contentBlock.params.length === 0 ? "{}" : contentBlock.params
|
|
1194
|
+
|
|
1195
|
+
parts.push({
|
|
1196
|
+
type: "tool-call",
|
|
1197
|
+
id: contentBlock.id,
|
|
1198
|
+
name: contentBlock.name,
|
|
1199
|
+
params: JSON.parse(params),
|
|
1200
|
+
providerName: contentBlock.providerName,
|
|
1201
|
+
providerExecuted: contentBlock.providerExecuted
|
|
1202
|
+
})
|
|
1203
|
+
|
|
1204
|
+
break
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
delete contentBlocks[event.index]
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
blockType = undefined
|
|
1212
|
+
|
|
1213
|
+
break
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
case "error": {
|
|
1217
|
+
parts.push({ type: "error", error: event.error })
|
|
1218
|
+
|
|
1219
|
+
break
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
return parts
|
|
1224
|
+
})),
|
|
1225
|
+
Stream.flattenIterables
|
|
538
1226
|
)
|
|
539
|
-
return new AiResponse.AiResponse({
|
|
540
|
-
parts
|
|
541
|
-
}, constDisableValidation)
|
|
542
1227
|
}
|
|
543
1228
|
)
|
|
544
1229
|
|
|
1230
|
+
// =============================================================================
|
|
1231
|
+
// Telemetry
|
|
1232
|
+
// =============================================================================
|
|
1233
|
+
|
|
545
1234
|
const annotateRequest = (
|
|
546
1235
|
span: Span,
|
|
547
|
-
request: typeof Generated.
|
|
1236
|
+
request: typeof Generated.BetaCreateMessageParams.Encoded
|
|
548
1237
|
): void => {
|
|
549
1238
|
addGenAIAnnotations(span, {
|
|
550
1239
|
system: "anthropic",
|
|
@@ -562,10 +1251,7 @@ const annotateRequest = (
|
|
|
562
1251
|
})
|
|
563
1252
|
}
|
|
564
1253
|
|
|
565
|
-
const
|
|
566
|
-
span: Span,
|
|
567
|
-
response: typeof Generated.Message.Encoded
|
|
568
|
-
): void => {
|
|
1254
|
+
const annotateResponse = (span: Span, response: Generated.BetaMessage): void => {
|
|
569
1255
|
addGenAIAnnotations(span, {
|
|
570
1256
|
response: {
|
|
571
1257
|
id: response.id,
|
|
@@ -579,21 +1265,394 @@ const annotateChatResponse = (
|
|
|
579
1265
|
})
|
|
580
1266
|
}
|
|
581
1267
|
|
|
582
|
-
const annotateStreamResponse = (
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
1268
|
+
const annotateStreamResponse = (span: Span, part: Response.StreamPartEncoded) => {
|
|
1269
|
+
if (part.type === "response-metadata") {
|
|
1270
|
+
addGenAIAnnotations(span, {
|
|
1271
|
+
response: {
|
|
1272
|
+
id: part.id,
|
|
1273
|
+
model: part.modelId
|
|
1274
|
+
}
|
|
1275
|
+
})
|
|
1276
|
+
}
|
|
1277
|
+
if (part.type === "finish") {
|
|
1278
|
+
addGenAIAnnotations(span, {
|
|
1279
|
+
response: {
|
|
1280
|
+
finishReasons: [part.reason]
|
|
1281
|
+
},
|
|
1282
|
+
usage: {
|
|
1283
|
+
inputTokens: part.usage.inputTokens,
|
|
1284
|
+
outputTokens: part.usage.outputTokens
|
|
1285
|
+
}
|
|
1286
|
+
})
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
// =============================================================================
|
|
1291
|
+
// Tool Calling
|
|
1292
|
+
// =============================================================================
|
|
1293
|
+
|
|
1294
|
+
/**
|
|
1295
|
+
* Represents all possible Anthropic provider-defined tools.
|
|
1296
|
+
*
|
|
1297
|
+
* @since 1.0.0
|
|
1298
|
+
* @category Models
|
|
1299
|
+
*/
|
|
1300
|
+
export type AnthropicTools =
|
|
1301
|
+
| typeof Generated.BetaTool.Encoded
|
|
1302
|
+
| typeof Generated.BetaBashTool20241022.Encoded
|
|
1303
|
+
| typeof Generated.BetaBashTool20250124.Encoded
|
|
1304
|
+
| typeof Generated.BetaComputerUseTool20241022.Encoded
|
|
1305
|
+
| typeof Generated.BetaComputerUseTool20250124.Encoded
|
|
1306
|
+
| typeof Generated.BetaTextEditor20241022.Encoded
|
|
1307
|
+
| typeof Generated.BetaTextEditor20250124.Encoded
|
|
1308
|
+
| typeof Generated.BetaTextEditor20250429.Encoded
|
|
1309
|
+
| typeof Generated.BetaTextEditor20250728.Encoded
|
|
1310
|
+
|
|
1311
|
+
/**
|
|
1312
|
+
* A helper method which takes in large language model provider options from
|
|
1313
|
+
* the base Effect AI SDK as well as Anthropic request configuration options
|
|
1314
|
+
* and returns the prepared tools, tool choice, and Anthropic betas to include
|
|
1315
|
+
* in a request.
|
|
1316
|
+
*
|
|
1317
|
+
* This method is primarily exposed for use by other Effect provider
|
|
1318
|
+
* integrations which can utilize Anthropic models (i.e. Amazon Bedrock).
|
|
1319
|
+
*
|
|
1320
|
+
* @since 1.0.0
|
|
1321
|
+
* @category Tool Calling
|
|
1322
|
+
*/
|
|
1323
|
+
export const prepareTools: (options: LanguageModel.ProviderOptions, config: Config.Service) => Effect.Effect<{
|
|
1324
|
+
readonly betas: ReadonlySet<string>
|
|
1325
|
+
readonly tools: ReadonlyArray<AnthropicTools> | undefined
|
|
1326
|
+
readonly toolChoice: typeof Generated.BetaToolChoice.Encoded | undefined
|
|
1327
|
+
}, AiError.AiError> = Effect.fnUntraced(function*(options, config) {
|
|
1328
|
+
// Return immediately if no tools are in the toolkit or a tool choice of
|
|
1329
|
+
// "none" was specified
|
|
1330
|
+
if (options.tools.length === 0 || options.toolChoice === "none") {
|
|
1331
|
+
return { betas: new Set(), tools: undefined, toolChoice: undefined }
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
const betas = new Set<string>()
|
|
1335
|
+
let tools: Array<AnthropicTools> = []
|
|
1336
|
+
let toolChoice: typeof Generated.BetaToolChoice.Encoded | undefined = undefined
|
|
1337
|
+
|
|
1338
|
+
// Convert the tools in the toolkit to the provider-defined format
|
|
1339
|
+
for (const tool of options.tools) {
|
|
1340
|
+
if (Tool.isUserDefined(tool)) {
|
|
1341
|
+
tools.push({
|
|
1342
|
+
name: tool.name,
|
|
1343
|
+
description: Tool.getDescription(tool as any),
|
|
1344
|
+
input_schema: Tool.getJsonSchema(tool as any) as any
|
|
1345
|
+
})
|
|
597
1346
|
}
|
|
598
|
-
|
|
1347
|
+
|
|
1348
|
+
if (Tool.isProviderDefined(tool)) {
|
|
1349
|
+
switch (tool.id) {
|
|
1350
|
+
case "anthropic.bash_20241022": {
|
|
1351
|
+
betas.add("computer-use-2024-10-22")
|
|
1352
|
+
tools.push({
|
|
1353
|
+
name: "bash",
|
|
1354
|
+
type: "bash_20241022"
|
|
1355
|
+
})
|
|
1356
|
+
break
|
|
1357
|
+
}
|
|
1358
|
+
case "anthropic.bash_20250124": {
|
|
1359
|
+
betas.add("computer-use-2025-01-24")
|
|
1360
|
+
tools.push({
|
|
1361
|
+
name: "bash",
|
|
1362
|
+
type: "bash_20250124"
|
|
1363
|
+
})
|
|
1364
|
+
break
|
|
1365
|
+
}
|
|
1366
|
+
case "anthropic.code_execution_20250522": {
|
|
1367
|
+
betas.add("code-execution-2025-05-22")
|
|
1368
|
+
tools.push({
|
|
1369
|
+
...tool.args,
|
|
1370
|
+
name: "code_execution",
|
|
1371
|
+
type: "code_execution_2025522"
|
|
1372
|
+
})
|
|
1373
|
+
break
|
|
1374
|
+
}
|
|
1375
|
+
case "anthropic.code_execution_20250825": {
|
|
1376
|
+
betas.add("code-execution-2025-08-25")
|
|
1377
|
+
tools.push({
|
|
1378
|
+
...tool.args,
|
|
1379
|
+
name: "code_execution",
|
|
1380
|
+
type: "code_execution_20250825"
|
|
1381
|
+
})
|
|
1382
|
+
break
|
|
1383
|
+
}
|
|
1384
|
+
case "anthropic.computer_use_20241022": {
|
|
1385
|
+
betas.add("computer-use-2025-10-22")
|
|
1386
|
+
tools.push({
|
|
1387
|
+
...tool.args,
|
|
1388
|
+
name: "computer",
|
|
1389
|
+
type: "computer_20241022"
|
|
1390
|
+
})
|
|
1391
|
+
break
|
|
1392
|
+
}
|
|
1393
|
+
case "anthropic.computer_use_20250124": {
|
|
1394
|
+
betas.add("computer-use-2025-01-24")
|
|
1395
|
+
tools.push({
|
|
1396
|
+
...tool.args,
|
|
1397
|
+
name: "computer",
|
|
1398
|
+
type: "computer_20250124"
|
|
1399
|
+
})
|
|
1400
|
+
break
|
|
1401
|
+
}
|
|
1402
|
+
case "anthropic.text_editor_20241022": {
|
|
1403
|
+
betas.add("computer-use-2024-10-22")
|
|
1404
|
+
tools.push({
|
|
1405
|
+
name: "str_replace_editor",
|
|
1406
|
+
type: "text_editor_20241022"
|
|
1407
|
+
})
|
|
1408
|
+
break
|
|
1409
|
+
}
|
|
1410
|
+
case "anthropic.text_editor_20250124": {
|
|
1411
|
+
betas.add("computer-use-2025-01-24")
|
|
1412
|
+
tools.push({
|
|
1413
|
+
name: "str_replace_editor",
|
|
1414
|
+
type: "text_editor_20250124"
|
|
1415
|
+
})
|
|
1416
|
+
break
|
|
1417
|
+
}
|
|
1418
|
+
case "anthropic.text_editor_20250429": {
|
|
1419
|
+
betas.add("computer-use-2025-01-24")
|
|
1420
|
+
tools.push({
|
|
1421
|
+
name: "str_replace_based_edit_tool",
|
|
1422
|
+
type: "text_editor_20250429"
|
|
1423
|
+
})
|
|
1424
|
+
break
|
|
1425
|
+
}
|
|
1426
|
+
case "anthropic.text_editor_20250728": {
|
|
1427
|
+
tools.push({
|
|
1428
|
+
name: "str_replace_based_edit_tool",
|
|
1429
|
+
type: "text_editor_20250728"
|
|
1430
|
+
})
|
|
1431
|
+
break
|
|
1432
|
+
}
|
|
1433
|
+
case "anthropic.web_search_20250305": {
|
|
1434
|
+
tools.push({
|
|
1435
|
+
...tool.args,
|
|
1436
|
+
name: "web_search",
|
|
1437
|
+
type: "web_search_20250305"
|
|
1438
|
+
})
|
|
1439
|
+
break
|
|
1440
|
+
}
|
|
1441
|
+
default: {
|
|
1442
|
+
return yield* new AiError.MalformedInput({
|
|
1443
|
+
module: "AnthropicLanguageModel",
|
|
1444
|
+
method: "prepareTools",
|
|
1445
|
+
description: `Received request to call unknown provider-defined tool '${tool.name}'`
|
|
1446
|
+
})
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
// Convert the tool choice to the provider-defined format
|
|
1453
|
+
if (options.toolChoice === "auto") {
|
|
1454
|
+
toolChoice = {
|
|
1455
|
+
type: "auto",
|
|
1456
|
+
disable_parallel_tool_use: config.disableParallelToolCalls
|
|
1457
|
+
}
|
|
1458
|
+
} else if (options.toolChoice === "required") {
|
|
1459
|
+
toolChoice = {
|
|
1460
|
+
type: "any",
|
|
1461
|
+
disable_parallel_tool_use: config.disableParallelToolCalls
|
|
1462
|
+
}
|
|
1463
|
+
} else if ("tool" in options.toolChoice) {
|
|
1464
|
+
toolChoice = {
|
|
1465
|
+
type: "tool",
|
|
1466
|
+
name: options.toolChoice.tool,
|
|
1467
|
+
disable_parallel_tool_use: config.disableParallelToolCalls
|
|
1468
|
+
}
|
|
1469
|
+
} else {
|
|
1470
|
+
const allowedTools = new Set(options.toolChoice.oneOf)
|
|
1471
|
+
tools = tools.filter((tool) => allowedTools.has(tool.name))
|
|
1472
|
+
toolChoice = {
|
|
1473
|
+
type: options.toolChoice.mode === "required" ? "any" : "auto",
|
|
1474
|
+
disable_parallel_tool_use: config.disableParallelToolCalls
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
return { betas, tools, toolChoice }
|
|
1479
|
+
})
|
|
1480
|
+
|
|
1481
|
+
// =============================================================================
|
|
1482
|
+
// Utilities
|
|
1483
|
+
// =============================================================================
|
|
1484
|
+
|
|
1485
|
+
type ContentGroup = SystemMessageGroup | AssistantMessageGroup | UserMessageGroup
|
|
1486
|
+
|
|
1487
|
+
interface SystemMessageGroup {
|
|
1488
|
+
readonly type: "system"
|
|
1489
|
+
readonly messages: Array<Prompt.SystemMessage>
|
|
599
1490
|
}
|
|
1491
|
+
|
|
1492
|
+
interface AssistantMessageGroup {
|
|
1493
|
+
readonly type: "assistant"
|
|
1494
|
+
readonly messages: Array<Prompt.AssistantMessage>
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
interface UserMessageGroup {
|
|
1498
|
+
readonly type: "user"
|
|
1499
|
+
readonly messages: Array<Prompt.ToolMessage | Prompt.UserMessage>
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
const groupMessages = (prompt: Prompt.Prompt): Array<ContentGroup> => {
|
|
1503
|
+
const messages: Array<ContentGroup> = []
|
|
1504
|
+
let current: ContentGroup | undefined = undefined
|
|
1505
|
+
for (const message of prompt.content) {
|
|
1506
|
+
switch (message.role) {
|
|
1507
|
+
case "system": {
|
|
1508
|
+
if (current?.type !== "system") {
|
|
1509
|
+
current = { type: "system", messages: [] }
|
|
1510
|
+
messages.push(current)
|
|
1511
|
+
}
|
|
1512
|
+
current.messages.push(message)
|
|
1513
|
+
break
|
|
1514
|
+
}
|
|
1515
|
+
case "assistant": {
|
|
1516
|
+
if (current?.type !== "assistant") {
|
|
1517
|
+
current = { type: "assistant", messages: [] }
|
|
1518
|
+
messages.push(current)
|
|
1519
|
+
}
|
|
1520
|
+
current.messages.push(message)
|
|
1521
|
+
break
|
|
1522
|
+
}
|
|
1523
|
+
case "tool":
|
|
1524
|
+
case "user": {
|
|
1525
|
+
if (current?.type !== "user") {
|
|
1526
|
+
current = { type: "user", messages: [] }
|
|
1527
|
+
messages.push(current)
|
|
1528
|
+
}
|
|
1529
|
+
current.messages.push(message)
|
|
1530
|
+
break
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
return messages
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
const isCitationPart = (part: Prompt.UserMessage["content"][number]): part is Prompt.FilePart => {
|
|
1538
|
+
if (part.type === "file" && (part.mediaType === "application/pdf" || part.mediaType === "text/plain")) {
|
|
1539
|
+
return part.options.anthropic?.citations?.enabled ?? false
|
|
1540
|
+
}
|
|
1541
|
+
return false
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
interface CitableDocument {
|
|
1545
|
+
readonly title: string
|
|
1546
|
+
readonly fileName: string | undefined
|
|
1547
|
+
readonly mediaType: string
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
const extractCitableDocuments = (prompt: Prompt.Prompt): ReadonlyArray<CitableDocument> => {
|
|
1551
|
+
const citableDocuments: Array<CitableDocument> = []
|
|
1552
|
+
for (const message of prompt.content) {
|
|
1553
|
+
if (message.role === "user") {
|
|
1554
|
+
for (const part of message.content) {
|
|
1555
|
+
if (isCitationPart(part)) {
|
|
1556
|
+
citableDocuments.push({
|
|
1557
|
+
title: part.fileName ?? "Untitled Document",
|
|
1558
|
+
fileName: part.fileName,
|
|
1559
|
+
mediaType: part.mediaType
|
|
1560
|
+
})
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
return citableDocuments
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
const getCacheControl = (
|
|
1569
|
+
part:
|
|
1570
|
+
| Prompt.SystemMessage
|
|
1571
|
+
| Prompt.UserMessage
|
|
1572
|
+
| Prompt.AssistantMessage
|
|
1573
|
+
| Prompt.ToolMessage
|
|
1574
|
+
| Prompt.UserMessagePart
|
|
1575
|
+
| Prompt.AssistantMessagePart
|
|
1576
|
+
| Prompt.ToolMessagePart
|
|
1577
|
+
): typeof Generated.CacheControlEphemeral.Encoded | undefined => part.options.anthropic?.cacheControl
|
|
1578
|
+
|
|
1579
|
+
const getDocumentMetadata = (part: Prompt.FilePart): {
|
|
1580
|
+
readonly title: string | undefined
|
|
1581
|
+
readonly context: string | undefined
|
|
1582
|
+
} | undefined => {
|
|
1583
|
+
const options = part.options.anthropic
|
|
1584
|
+
if (Predicate.isNotUndefined(options)) {
|
|
1585
|
+
return {
|
|
1586
|
+
title: options.documentTitle,
|
|
1587
|
+
context: options.documentContext
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
return undefined
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
const shouldEnableCitations = (part: Prompt.FilePart): boolean => part.options.anthropic?.citations?.enabled ?? false
|
|
1594
|
+
|
|
1595
|
+
const processCitation: (
|
|
1596
|
+
citation:
|
|
1597
|
+
| Generated.ResponseCharLocationCitation
|
|
1598
|
+
| Generated.ResponsePageLocationCitation
|
|
1599
|
+
| Generated.ResponseContentBlockLocationCitation
|
|
1600
|
+
| Generated.ResponseWebSearchResultLocationCitation
|
|
1601
|
+
| Generated.ResponseSearchResultLocationCitation,
|
|
1602
|
+
citableDocuments: ReadonlyArray<CitableDocument>,
|
|
1603
|
+
idGenerator: IdGenerator.Service
|
|
1604
|
+
) => Effect.Effect<Response.DocumentSourcePartEncoded | Response.UrlSourcePartEncoded | undefined> = Effect.fnUntraced(
|
|
1605
|
+
function*(citation, citableDocuments, idGenerator) {
|
|
1606
|
+
if (citation.type === "page_location" || citation.type === "char_location") {
|
|
1607
|
+
const citedDocument = citableDocuments[citation.document_index]
|
|
1608
|
+
if (Predicate.isNotUndefined(citedDocument)) {
|
|
1609
|
+
const id = yield* idGenerator.generateId()
|
|
1610
|
+
|
|
1611
|
+
const metadata = citation.type === "char_location"
|
|
1612
|
+
? {
|
|
1613
|
+
source: "document",
|
|
1614
|
+
type: citation.type,
|
|
1615
|
+
citedText: citation.cited_text,
|
|
1616
|
+
startCharIndex: citation.start_char_index,
|
|
1617
|
+
endCharIndex: citation.end_char_index
|
|
1618
|
+
} as const
|
|
1619
|
+
: {
|
|
1620
|
+
source: "document",
|
|
1621
|
+
type: citation.type,
|
|
1622
|
+
citedText: citation.cited_text,
|
|
1623
|
+
startPageNumber: citation.start_page_number,
|
|
1624
|
+
endPageNumber: citation.end_page_number
|
|
1625
|
+
} as const
|
|
1626
|
+
|
|
1627
|
+
return {
|
|
1628
|
+
type: "source",
|
|
1629
|
+
sourceType: "document",
|
|
1630
|
+
id,
|
|
1631
|
+
mediaType: citedDocument.mediaType,
|
|
1632
|
+
title: citation.document_title ?? citedDocument.title,
|
|
1633
|
+
fileName: citedDocument.fileName,
|
|
1634
|
+
metadata: { anthropic: metadata }
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
if (citation.type === "web_search_result_location") {
|
|
1640
|
+
const id = yield* idGenerator.generateId()
|
|
1641
|
+
|
|
1642
|
+
const metadata = {
|
|
1643
|
+
source: "url",
|
|
1644
|
+
citedText: citation.cited_text,
|
|
1645
|
+
encryptedIndex: citation.encrypted_index
|
|
1646
|
+
} as const
|
|
1647
|
+
|
|
1648
|
+
return {
|
|
1649
|
+
type: "source",
|
|
1650
|
+
sourceType: "url",
|
|
1651
|
+
id,
|
|
1652
|
+
url: citation.url,
|
|
1653
|
+
title: citation.title ?? "Untitled",
|
|
1654
|
+
metadata: { anthropic: metadata }
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
)
|