@effect/ai-anthropic 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/AnthropicClient/package.json +6 -0
  2. package/AnthropicCompletions/package.json +6 -0
  3. package/AnthropicConfig/package.json +6 -0
  4. package/AnthropicTokenizer/package.json +6 -0
  5. package/Generated/package.json +6 -0
  6. package/LICENSE +21 -0
  7. package/README.md +5 -0
  8. package/dist/cjs/AnthropicClient.js +213 -0
  9. package/dist/cjs/AnthropicClient.js.map +1 -0
  10. package/dist/cjs/AnthropicCompletions.js +290 -0
  11. package/dist/cjs/AnthropicCompletions.js.map +1 -0
  12. package/dist/cjs/AnthropicConfig.js +31 -0
  13. package/dist/cjs/AnthropicConfig.js.map +1 -0
  14. package/dist/cjs/AnthropicTokenizer.js +50 -0
  15. package/dist/cjs/AnthropicTokenizer.js.map +1 -0
  16. package/dist/cjs/Generated.js +1510 -0
  17. package/dist/cjs/Generated.js.map +1 -0
  18. package/dist/cjs/index.js +19 -0
  19. package/dist/cjs/index.js.map +1 -0
  20. package/dist/dts/AnthropicClient.d.ts +126 -0
  21. package/dist/dts/AnthropicClient.d.ts.map +1 -0
  22. package/dist/dts/AnthropicCompletions.d.ts +25 -0
  23. package/dist/dts/AnthropicCompletions.d.ts.map +1 -0
  24. package/dist/dts/AnthropicConfig.d.ts +39 -0
  25. package/dist/dts/AnthropicConfig.d.ts.map +1 -0
  26. package/dist/dts/AnthropicTokenizer.d.ts +8 -0
  27. package/dist/dts/AnthropicTokenizer.d.ts.map +1 -0
  28. package/dist/dts/Generated.d.ts +3937 -0
  29. package/dist/dts/Generated.d.ts.map +1 -0
  30. package/dist/dts/index.d.ts +21 -0
  31. package/dist/dts/index.d.ts.map +1 -0
  32. package/dist/esm/AnthropicClient.js +199 -0
  33. package/dist/esm/AnthropicClient.js.map +1 -0
  34. package/dist/esm/AnthropicCompletions.js +279 -0
  35. package/dist/esm/AnthropicCompletions.js.map +1 -0
  36. package/dist/esm/AnthropicConfig.js +22 -0
  37. package/dist/esm/AnthropicConfig.js.map +1 -0
  38. package/dist/esm/AnthropicTokenizer.js +41 -0
  39. package/dist/esm/AnthropicTokenizer.js.map +1 -0
  40. package/dist/esm/Generated.js +1273 -0
  41. package/dist/esm/Generated.js.map +1 -0
  42. package/dist/esm/index.js +21 -0
  43. package/dist/esm/index.js.map +1 -0
  44. package/dist/esm/package.json +4 -0
  45. package/package.json +79 -0
  46. package/src/AnthropicClient.ts +415 -0
  47. package/src/AnthropicCompletions.ts +352 -0
  48. package/src/AnthropicConfig.ts +76 -0
  49. package/src/AnthropicTokenizer.ts +52 -0
  50. package/src/Generated.ts +1811 -0
  51. package/src/index.ts +24 -0
@@ -0,0 +1,415 @@
1
+ /**
2
+ * @since 1.0.0
3
+ */
4
+ import * as AiError from "@effect/ai/AiError"
5
+ import * as AiResponse from "@effect/ai/AiResponse"
6
+ import * as AiRole from "@effect/ai/AiRole"
7
+ import * as Sse from "@effect/experimental/Sse"
8
+ import * as HttpBody from "@effect/platform/HttpBody"
9
+ import * as HttpClient from "@effect/platform/HttpClient"
10
+ import type * as HttpClientError from "@effect/platform/HttpClientError"
11
+ import * as HttpClientRequest from "@effect/platform/HttpClientRequest"
12
+ import * as Chunk from "effect/Chunk"
13
+ import * as Config from "effect/Config"
14
+ import type { ConfigError } from "effect/ConfigError"
15
+ import * as Context from "effect/Context"
16
+ import * as Data from "effect/Data"
17
+ import * as Effect from "effect/Effect"
18
+ import { identity } from "effect/Function"
19
+ import * as Layer from "effect/Layer"
20
+ import * as Option from "effect/Option"
21
+ import * as Redacted from "effect/Redacted"
22
+ import * as Stream from "effect/Stream"
23
+ import type { Mutable } from "effect/Types"
24
+ import { AnthropicConfig } from "./AnthropicConfig.js"
25
+ import * as Generated from "./Generated.js"
26
+
27
+ /**
28
+ * @since 1.0.0
29
+ * @category tags
30
+ */
31
+ export class AnthropicClient extends Context.Tag(
32
+ "@effect/ai-openai/AnthropicClient"
33
+ )<AnthropicClient, AnthropicClient.Service>() {}
34
+
35
+ /**
36
+ * @since 1.0.0
37
+ * @category models
38
+ */
39
+ export declare namespace AnthropicClient {
40
+ /**
41
+ * @since 1.0.0
42
+ * @category models
43
+ */
44
+ export interface Service {
45
+ readonly client: Generated.Client
46
+ readonly streamRequest: <A>(
47
+ request: HttpClientRequest.HttpClientRequest
48
+ ) => Stream.Stream<A, HttpClientError.HttpClientError>
49
+ readonly stream: (
50
+ request: StreamCompletionRequest
51
+ ) => Stream.Stream<StreamChunk, HttpClientError.HttpClientError>
52
+ }
53
+ }
54
+
55
+ /**
56
+ * @since 1.0.0
57
+ * @category constructors
58
+ */
59
+ export const make = (options: {
60
+ readonly apiKey?: Redacted.Redacted | undefined
61
+ readonly apiUrl?: string | undefined
62
+ readonly anthropicVersion?: string
63
+ readonly organizationId?: Redacted.Redacted | undefined
64
+ readonly projectId?: Redacted.Redacted | undefined
65
+ readonly transformClient?: (
66
+ client: HttpClient.HttpClient
67
+ ) => HttpClient.HttpClient
68
+ }): Effect.Effect<AnthropicClient.Service, never, HttpClient.HttpClient> =>
69
+ Effect.gen(function*() {
70
+ const httpClient = (yield* HttpClient.HttpClient).pipe(
71
+ HttpClient.mapRequest((request) =>
72
+ request.pipe(
73
+ HttpClientRequest.prependUrl(options.apiUrl ?? "https://api.anthropic.com"),
74
+ options.apiKey ? HttpClientRequest.setHeader("x-api-key", Redacted.value(options.apiKey)) : identity,
75
+ HttpClientRequest.setHeader("anthropic-version", options.anthropicVersion ?? "2023-06-01"),
76
+ HttpClientRequest.acceptJson
77
+ )
78
+ ),
79
+ options.transformClient ? options.transformClient : identity
80
+ )
81
+ const httpClientOk = HttpClient.filterStatusOk(httpClient)
82
+ const client = Generated.make(httpClient, {
83
+ transformClient: (client) =>
84
+ AnthropicConfig.getOrUndefined.pipe(
85
+ Effect.map((config) => config?.transformClient ? config.transformClient(client) : client)
86
+ )
87
+ })
88
+ const streamRequest = <A = unknown>(
89
+ request: HttpClientRequest.HttpClientRequest
90
+ ) =>
91
+ httpClientOk.execute(request).pipe(
92
+ Effect.map((r) => r.stream),
93
+ Stream.unwrapScoped,
94
+ Stream.decodeText(),
95
+ Stream.pipeThroughChannel(Sse.makeChannel()),
96
+ Stream.takeWhile((event) => event.event !== "message_stop"),
97
+ Stream.map((event) => JSON.parse(event.data) as A)
98
+ )
99
+ const stream = (request: StreamCompletionRequest) =>
100
+ Stream.suspend(() => {
101
+ const usage: Mutable<Partial<UsagePart>> = { _tag: "Usage" }
102
+ return streamRequest<MessageStreamEvent>(
103
+ HttpClientRequest.post("/v1/messages", {
104
+ body: HttpBody.unsafeJson({ ...request, stream: true })
105
+ })
106
+ ).pipe(
107
+ Stream.mapAccumEffect(new Map<number, ContentPart | ToolCallPart>(), (acc, chunk) => {
108
+ const parts: Array<StreamChunkPart> = []
109
+ switch (chunk.type) {
110
+ case "message_start": {
111
+ usage.id = chunk.message.id
112
+ usage.model = chunk.message.model
113
+ usage.inputTokens = chunk.message.usage.input_tokens
114
+ break
115
+ }
116
+ case "message_delta": {
117
+ usage.finishReasons = [chunk.delta.stop_reason]
118
+ usage.outputTokens = chunk.usage.output_tokens
119
+ parts.push(usage as UsagePart)
120
+ break
121
+ }
122
+ case "message_stop": {
123
+ break
124
+ }
125
+ case "content_block_start": {
126
+ const content = chunk.content_block
127
+ if (content.type === "tool_use") {
128
+ acc.set(chunk.index, {
129
+ _tag: "ToolCall",
130
+ id: content.id,
131
+ name: content.name,
132
+ arguments: ""
133
+ })
134
+ }
135
+ break
136
+ }
137
+ case "content_block_delta": {
138
+ switch (chunk.delta.type) {
139
+ // TODO: add support for citations (?)
140
+ case "citations_delta": {
141
+ break
142
+ }
143
+ case "input_json_delta": {
144
+ const toolCall = acc.get(chunk.index) as ToolCallPart
145
+ acc.set(chunk.index, {
146
+ ...toolCall,
147
+ arguments: toolCall.arguments + chunk.delta.partial_json
148
+ })
149
+ break
150
+ }
151
+ case "text_delta": {
152
+ parts.push({
153
+ _tag: "Content",
154
+ content: chunk.delta.text
155
+ })
156
+ break
157
+ }
158
+ }
159
+ break
160
+ }
161
+ case "content_block_stop": {
162
+ if (acc.has(chunk.index)) {
163
+ const toolCall = acc.get(chunk.index) as ToolCallPart
164
+ try {
165
+ const args = JSON.parse(toolCall.arguments as string)
166
+ parts.push({
167
+ _tag: "ToolCall",
168
+ id: toolCall.id,
169
+ name: toolCall.name,
170
+ arguments: args
171
+ })
172
+ // eslint-disable-next-line no-empty
173
+ } catch {}
174
+ }
175
+ break
176
+ }
177
+ case "error": {
178
+ return Effect.die(
179
+ new AiError.AiError({
180
+ module: "AnthropicClient",
181
+ method: "stream",
182
+ description: `${chunk.error.type}: ${chunk.error.message}`
183
+ })
184
+ )
185
+ }
186
+ }
187
+ return Effect.succeed([
188
+ acc,
189
+ parts.length === 0
190
+ ? Option.none()
191
+ : Option.some(new StreamChunk({ parts }))
192
+ ])
193
+ }),
194
+ Stream.filterMap(identity)
195
+ )
196
+ })
197
+ return AnthropicClient.of({ client, streamRequest, stream })
198
+ })
199
+
200
+ /**
201
+ * @since 1.0.0
202
+ * @category layers
203
+ */
204
+ export const layer = (options: {
205
+ readonly apiKey?: Redacted.Redacted | undefined
206
+ readonly apiUrl?: string | undefined
207
+ readonly anthropicVersion?: string
208
+ readonly transformClient?: (
209
+ client: HttpClient.HttpClient
210
+ ) => HttpClient.HttpClient
211
+ }): Layer.Layer<AnthropicClient, never, HttpClient.HttpClient> => Layer.effect(AnthropicClient, make(options))
212
+
213
+ /**
214
+ * @since 1.0.0
215
+ * @category layers
216
+ */
217
+ export const layerConfig = (
218
+ options: Config.Config.Wrap<{
219
+ readonly apiKey?: Redacted.Redacted | undefined
220
+ readonly apiUrl?: string | undefined
221
+ readonly anthropicVersion?: string
222
+ readonly transformClient?: (
223
+ client: HttpClient.HttpClient
224
+ ) => HttpClient.HttpClient
225
+ }>
226
+ ): Layer.Layer<AnthropicClient, ConfigError, HttpClient.HttpClient> =>
227
+ Config.unwrap(options).pipe(
228
+ Effect.flatMap(make),
229
+ Layer.effect(AnthropicClient)
230
+ )
231
+
232
+ /**
233
+ * @since 1.0.0
234
+ * @category models
235
+ */
236
+ export type StreamCompletionRequest = Omit<
237
+ typeof Generated.CreateMessageParams.Encoded,
238
+ "stream"
239
+ >
240
+
241
+ type MessageStreamEvent =
242
+ | ErrorEvent
243
+ | MessageStartEvent
244
+ | MessageDeltaEvent
245
+ | MessageStopEvent
246
+ | ContentBlockStartEvent
247
+ | ContentBlockDeltaEvent
248
+ | ContentBlockStopEvent
249
+
250
+ interface MessageStartEvent {
251
+ readonly type: "message_start"
252
+ readonly message: typeof Generated.Message.Encoded
253
+ }
254
+
255
+ interface MessageDeltaEvent {
256
+ readonly type: "message_delta"
257
+ readonly delta: {
258
+ readonly stop_reason:
259
+ | "end_turn"
260
+ | "max_tokens"
261
+ | "stop_sequence"
262
+ | "tool_use"
263
+ readonly stop_sequence: string | null
264
+ }
265
+ readonly usage: {
266
+ readonly output_tokens: number
267
+ }
268
+ }
269
+
270
+ interface MessageStopEvent {
271
+ readonly type: "message_stop"
272
+ }
273
+
274
+ interface ContentBlockStartEvent {
275
+ readonly type: "content_block_start"
276
+ readonly index: number
277
+ readonly content_block:
278
+ | typeof Generated.ResponseTextBlock.Encoded
279
+ | typeof Generated.ResponseToolUseBlock.Encoded
280
+ }
281
+
282
+ interface ContentBlockDeltaEvent {
283
+ readonly type: "content_block_delta"
284
+ readonly index: number
285
+ readonly delta:
286
+ | CitationsDelta
287
+ | InputJsonContentBlockDelta
288
+ | TextContentBlockDelta
289
+ }
290
+
291
+ interface CitationsDelta {
292
+ readonly type: "citations_delta"
293
+ readonly citation: NonNullable<
294
+ (typeof Generated.ResponseTextBlock.Encoded)["citations"]
295
+ >[number]
296
+ }
297
+
298
+ interface InputJsonContentBlockDelta {
299
+ readonly type: "input_json_delta"
300
+ readonly partial_json: string
301
+ }
302
+
303
+ interface TextContentBlockDelta {
304
+ readonly type: "text_delta"
305
+ readonly text: string
306
+ }
307
+
308
+ interface ContentBlockStopEvent {
309
+ readonly type: "content_block_stop"
310
+ readonly index: number
311
+ }
312
+
313
+ interface ErrorEvent {
314
+ readonly type: "error"
315
+ readonly error: {
316
+ readonly type:
317
+ | "api_error"
318
+ | "authentication_error"
319
+ | "invalid_request_error"
320
+ | "not_found_error"
321
+ | "overloaded_error"
322
+ | "permission_error"
323
+ | "rate_limit_error"
324
+ | "request_too_large"
325
+ readonly message: string
326
+ }
327
+ }
328
+
329
+ /**
330
+ * @since 1.0.0
331
+ * @category models
332
+ */
333
+ export class StreamChunk extends Data.Class<{
334
+ readonly parts: Array<StreamChunkPart>
335
+ }> {
336
+ /**
337
+ * @since 1.0.0
338
+ */
339
+ get text(): Option.Option<string> {
340
+ return this.parts[0]?._tag === "Content"
341
+ ? Option.some(this.parts[0].content)
342
+ : Option.none()
343
+ }
344
+ /**
345
+ * @since 1.0.0
346
+ */
347
+ get asAiResponse(): AiResponse.AiResponse {
348
+ if (this.parts.length === 0) {
349
+ return AiResponse.AiResponse.fromText({
350
+ role: AiRole.model,
351
+ content: ""
352
+ })
353
+ }
354
+ const part = this.parts[0]
355
+ switch (part._tag) {
356
+ case "Content":
357
+ return AiResponse.AiResponse.fromText({
358
+ role: AiRole.model,
359
+ content: part.content
360
+ })
361
+ case "ToolCall":
362
+ return new AiResponse.AiResponse({
363
+ role: AiRole.model,
364
+ parts: Chunk.of(
365
+ AiResponse.ToolCallPart.fromUnknown({
366
+ id: part.id,
367
+ name: part.name,
368
+ params: part.arguments
369
+ })
370
+ )
371
+ })
372
+ case "Usage":
373
+ return AiResponse.AiResponse.empty
374
+ }
375
+ }
376
+ }
377
+
378
+ /**
379
+ * @since 1.0.0
380
+ * @category models
381
+ */
382
+ export type StreamChunkPart = ContentPart | ToolCallPart | UsagePart
383
+
384
+ /**
385
+ * @since 1.0.0
386
+ * @category models
387
+ */
388
+ export interface ContentPart {
389
+ readonly _tag: "Content"
390
+ readonly content: string
391
+ }
392
+
393
+ /**
394
+ * @since 1.0.0
395
+ * @category models
396
+ */
397
+ export interface ToolCallPart {
398
+ readonly _tag: "ToolCall"
399
+ readonly id: string
400
+ readonly name: string
401
+ readonly arguments: unknown
402
+ }
403
+
404
+ /**
405
+ * @since 1.0.0
406
+ * @category models
407
+ */
408
+ export interface UsagePart {
409
+ readonly _tag: "Usage"
410
+ readonly id: string
411
+ readonly model: string
412
+ readonly inputTokens: number
413
+ readonly outputTokens: number
414
+ readonly finishReasons: ReadonlyArray<string>
415
+ }