@effect/ai 0.1.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.
Files changed (75) hide show
  1. package/AiChat/package.json +6 -0
  2. package/AiError/package.json +6 -0
  3. package/AiInput/package.json +6 -0
  4. package/AiResponse/package.json +6 -0
  5. package/AiRole/package.json +6 -0
  6. package/AiToolkit/package.json +6 -0
  7. package/Completions/package.json +6 -0
  8. package/LICENSE +21 -0
  9. package/README.md +1 -0
  10. package/Tokenizer/package.json +6 -0
  11. package/dist/cjs/AiChat.js +151 -0
  12. package/dist/cjs/AiChat.js.map +1 -0
  13. package/dist/cjs/AiError.js +41 -0
  14. package/dist/cjs/AiError.js.map +1 -0
  15. package/dist/cjs/AiInput.js +349 -0
  16. package/dist/cjs/AiInput.js.map +1 -0
  17. package/dist/cjs/AiResponse.js +295 -0
  18. package/dist/cjs/AiResponse.js.map +1 -0
  19. package/dist/cjs/AiRole.js +106 -0
  20. package/dist/cjs/AiRole.js.map +1 -0
  21. package/dist/cjs/AiToolkit.js +132 -0
  22. package/dist/cjs/AiToolkit.js.map +1 -0
  23. package/dist/cjs/Completions.js +217 -0
  24. package/dist/cjs/Completions.js.map +1 -0
  25. package/dist/cjs/Tokenizer.js +59 -0
  26. package/dist/cjs/Tokenizer.js.map +1 -0
  27. package/dist/cjs/index.js +25 -0
  28. package/dist/cjs/index.js.map +1 -0
  29. package/dist/dts/AiChat.d.ts +73 -0
  30. package/dist/dts/AiChat.d.ts.map +1 -0
  31. package/dist/dts/AiError.d.ts +38 -0
  32. package/dist/dts/AiError.d.ts.map +1 -0
  33. package/dist/dts/AiInput.d.ts +283 -0
  34. package/dist/dts/AiInput.d.ts.map +1 -0
  35. package/dist/dts/AiResponse.d.ts +235 -0
  36. package/dist/dts/AiResponse.d.ts.map +1 -0
  37. package/dist/dts/AiRole.d.ts +111 -0
  38. package/dist/dts/AiRole.d.ts.map +1 -0
  39. package/dist/dts/AiToolkit.d.ts +158 -0
  40. package/dist/dts/AiToolkit.d.ts.map +1 -0
  41. package/dist/dts/Completions.d.ts +104 -0
  42. package/dist/dts/Completions.d.ts.map +1 -0
  43. package/dist/dts/Tokenizer.d.ts +34 -0
  44. package/dist/dts/Tokenizer.d.ts.map +1 -0
  45. package/dist/dts/index.d.ts +33 -0
  46. package/dist/dts/index.d.ts.map +1 -0
  47. package/dist/esm/AiChat.js +139 -0
  48. package/dist/esm/AiChat.js.map +1 -0
  49. package/dist/esm/AiError.js +31 -0
  50. package/dist/esm/AiError.js.map +1 -0
  51. package/dist/esm/AiInput.js +332 -0
  52. package/dist/esm/AiInput.js.map +1 -0
  53. package/dist/esm/AiResponse.js +281 -0
  54. package/dist/esm/AiResponse.js.map +1 -0
  55. package/dist/esm/AiRole.js +93 -0
  56. package/dist/esm/AiRole.js.map +1 -0
  57. package/dist/esm/AiToolkit.js +123 -0
  58. package/dist/esm/AiToolkit.js.map +1 -0
  59. package/dist/esm/Completions.js +206 -0
  60. package/dist/esm/Completions.js.map +1 -0
  61. package/dist/esm/Tokenizer.js +48 -0
  62. package/dist/esm/Tokenizer.js.map +1 -0
  63. package/dist/esm/index.js +33 -0
  64. package/dist/esm/index.js.map +1 -0
  65. package/dist/esm/package.json +4 -0
  66. package/package.json +100 -0
  67. package/src/AiChat.ts +274 -0
  68. package/src/AiError.ts +38 -0
  69. package/src/AiInput.ts +456 -0
  70. package/src/AiResponse.ts +343 -0
  71. package/src/AiRole.ts +122 -0
  72. package/src/AiToolkit.ts +314 -0
  73. package/src/Completions.ts +354 -0
  74. package/src/Tokenizer.ts +78 -0
  75. package/src/index.ts +39 -0
package/src/AiInput.ts ADDED
@@ -0,0 +1,456 @@
1
+ /**
2
+ * @since 1.0.0
3
+ */
4
+ import type { PlatformError } from "@effect/platform/Error"
5
+ import * as FileSystem from "@effect/platform/FileSystem"
6
+ import * as Path from "@effect/platform/Path"
7
+ import * as ParseResult from "@effect/schema/ParseResult"
8
+ import * as Schema_ from "@effect/schema/Schema"
9
+ import * as Chunk from "effect/Chunk"
10
+ import * as Context from "effect/Context"
11
+ import * as Effect from "effect/Effect"
12
+ import * as Encoding from "effect/Encoding"
13
+ import { dual } from "effect/Function"
14
+ import * as Option from "effect/Option"
15
+ import * as Predicate from "effect/Predicate"
16
+ import { AiResponse, ToolCallId, WithResolved } from "./AiResponse.js"
17
+ import * as AiRole from "./AiRole.js"
18
+
19
+ const constDisableValidation = { disableValidation: true } as const
20
+
21
+ /**
22
+ * @since 1.0.0
23
+ * @category parts
24
+ */
25
+ export const PartTypeId: unique symbol = Symbol("@effect/ai/AiInput/Part")
26
+
27
+ /**
28
+ * @since 1.0.0
29
+ * @category parts
30
+ */
31
+ export type PartTypeId = typeof PartTypeId
32
+
33
+ /**
34
+ * @since 1.0.0
35
+ * @category parts
36
+ */
37
+ export class TextPart extends Schema_.TaggedClass<TextPart>("@effect/ai/AiInput/TextPart")("Text", {
38
+ content: Schema_.String
39
+ }) {
40
+ /**
41
+ * @since 1.0.0
42
+ */
43
+ readonly [PartTypeId]: PartTypeId = PartTypeId
44
+ /**
45
+ * @since 1.0.0
46
+ */
47
+ static fromContent(content: string): TextPart {
48
+ return new TextPart({ content }, constDisableValidation)
49
+ }
50
+ }
51
+
52
+ /**
53
+ * @since 1.0.0
54
+ * @category parts
55
+ */
56
+ export const ImageQuality = Schema_.Literal("low", "high", "auto")
57
+
58
+ /**
59
+ * @since 1.0.0
60
+ * @category parts
61
+ */
62
+ export type ImageQuality = typeof ImageQuality.Type
63
+
64
+ /**
65
+ * @since 1.0.0
66
+ * @category parts
67
+ */
68
+ export class ImageUrlPart extends Schema_.TaggedClass<ImageUrlPart>("@effect/ai/AiInput/ImageUrlPart")("ImageUrl", {
69
+ url: Schema_.String,
70
+ quality: ImageQuality.pipe(
71
+ Schema_.propertySignature,
72
+ Schema_.withConstructorDefault(() => "auto" as const)
73
+ )
74
+ }) {
75
+ /**
76
+ * @since 1.0.0
77
+ */
78
+ readonly [PartTypeId]: PartTypeId = PartTypeId
79
+ }
80
+
81
+ const base64ContentTypeRegex = /^data:(.*?);base64$/
82
+
83
+ /**
84
+ * @since 1.0.0
85
+ * @category base64
86
+ */
87
+ export interface Base64DataUrl extends
88
+ Schema_.transformOrFail<
89
+ typeof Schema_.String,
90
+ Schema_.Struct<{
91
+ data: Schema_.Schema<Uint8Array>
92
+ contentType: typeof Schema_.String
93
+ }>
94
+ >
95
+ {}
96
+
97
+ /**
98
+ * @since 1.0.0
99
+ * @category base64
100
+ */
101
+ export const Base64DataUrl: Base64DataUrl = Schema_.transformOrFail(
102
+ Schema_.String.annotations({
103
+ title: "Base64 Data URL",
104
+ description: "A base64 data URL"
105
+ }),
106
+ Schema_.Struct({
107
+ data: Schema_.Uint8ArrayFromSelf,
108
+ contentType: Schema_.String
109
+ }),
110
+ {
111
+ decode(base64Url, _, ast) {
112
+ const commaIndex = base64Url.indexOf(",")
113
+ if (commaIndex === -1) {
114
+ return ParseResult.fail(new ParseResult.Type(ast, base64Url))
115
+ }
116
+ const header = base64Url.slice(0, commaIndex)
117
+ const data = base64Url.slice(commaIndex + 1)
118
+ const contentType = base64ContentTypeRegex.exec(header)
119
+ if (contentType === null) {
120
+ return ParseResult.fail(new ParseResult.Type(ast, base64Url))
121
+ }
122
+ return Encoding.decodeBase64(data).pipe(
123
+ ParseResult.mapError((_) => new ParseResult.Type(ast, base64Url)),
124
+ ParseResult.map((data) => ({ data, contentType: contentType[1] }))
125
+ )
126
+ },
127
+ encode({ contentType, data }) {
128
+ const base64 = Encoding.encodeBase64(data)
129
+ return ParseResult.succeed(`data:${contentType};base64,${base64}`)
130
+ }
131
+ }
132
+ )
133
+
134
+ /**
135
+ * @since 1.0.0
136
+ * @category parts
137
+ */
138
+ export class ImagePart extends Schema_.TaggedClass<ImagePart>("@effect/ai/AiInput/ImagePart")("Image", {
139
+ image: Base64DataUrl,
140
+ quality: ImageQuality.pipe(
141
+ Schema_.propertySignature,
142
+ Schema_.withConstructorDefault(() => "auto" as const)
143
+ )
144
+ }) {
145
+ /**
146
+ * @since 1.0.0
147
+ */
148
+ readonly [PartTypeId]: PartTypeId = PartTypeId
149
+
150
+ /**
151
+ * @since 1.0.0
152
+ */
153
+ static fromPath(
154
+ path: string,
155
+ quality: ImageQuality = "auto"
156
+ ): Effect.Effect<
157
+ ImagePart,
158
+ PlatformError,
159
+ FileSystem.FileSystem | Path.Path
160
+ > {
161
+ return FileSystem.FileSystem.pipe(
162
+ Effect.bindTo("fs"),
163
+ Effect.bind("Path", () => Path.Path),
164
+ Effect.bind("data", ({ fs }) => fs.readFile(path)),
165
+ Effect.map(({ Path, data }) => {
166
+ const ext = Path.extname(path)
167
+ let contentType: string
168
+ switch (ext) {
169
+ case ".jpg":
170
+ case ".jpeg": {
171
+ contentType = "image/jpeg"
172
+ break
173
+ }
174
+ default: {
175
+ if (ext.startsWith(".")) {
176
+ contentType = `image/${ext.slice(1)}`
177
+ } else {
178
+ contentType = "image/png"
179
+ }
180
+ break
181
+ }
182
+ }
183
+ return new ImagePart({
184
+ image: { data, contentType },
185
+ quality
186
+ }, constDisableValidation)
187
+ })
188
+ )
189
+ }
190
+
191
+ get asDataUri(): string {
192
+ const base64 = Encoding.encodeBase64(this.image.data)
193
+ return `data:${this.image.contentType};base64,${base64}`
194
+ }
195
+ }
196
+
197
+ /**
198
+ * @since 1.0.0
199
+ * @category parts
200
+ */
201
+ export class ToolCallPart extends Schema_.TaggedClass<ToolCallPart>("@effect/ai/AiInput/ToolCallPart")("ToolCall", {
202
+ id: ToolCallId,
203
+ name: Schema_.String,
204
+ params: Schema_.Unknown
205
+ }) {
206
+ /**
207
+ * @since 1.0.0
208
+ */
209
+ readonly [PartTypeId]: PartTypeId = PartTypeId
210
+ }
211
+
212
+ /**
213
+ * @since 1.0.0
214
+ * @category parts
215
+ */
216
+ export class ToolCallResolvedPart
217
+ extends Schema_.TaggedClass<ToolCallResolvedPart>("@effect/ai/AiInput/ToolCallResolvedPart")("ToolCallResolved", {
218
+ toolCallId: ToolCallId,
219
+ value: Schema_.Unknown
220
+ })
221
+ {
222
+ /**
223
+ * @since 1.0.0
224
+ */
225
+ readonly [PartTypeId]: PartTypeId = PartTypeId
226
+ }
227
+
228
+ /**
229
+ * @since 1.0.0
230
+ * @category parts
231
+ */
232
+ export type Part = TextPart | ToolCallPart | ToolCallResolvedPart | ImagePart | ImageUrlPart
233
+
234
+ /**
235
+ * @since 1.0.0
236
+ * @category parts
237
+ */
238
+ export const isPart = (u: unknown): u is Part => Predicate.hasProperty(u, PartTypeId)
239
+
240
+ /**
241
+ * @since 1.0.0
242
+ * @category parts
243
+ */
244
+ export declare namespace Part {
245
+ /**
246
+ * @since 1.0.0
247
+ * @category parts
248
+ */
249
+ export type Schema = Schema_.Union<[
250
+ typeof TextPart,
251
+ typeof ToolCallPart,
252
+ typeof ToolCallResolvedPart,
253
+ typeof ImagePart,
254
+ typeof ImageUrlPart
255
+ ]>
256
+ }
257
+
258
+ /**
259
+ * @since 1.0.0
260
+ * @category parts
261
+ */
262
+ export const Part: Part.Schema = Schema_.Union(TextPart, ToolCallPart, ToolCallResolvedPart, ImagePart, ImageUrlPart)
263
+
264
+ /**
265
+ * @since 1.0.0
266
+ * @category message
267
+ */
268
+ export const MessageTypeId: unique symbol = Symbol("@effect/ai/AiInput/Message")
269
+
270
+ /**
271
+ * @since 1.0.0
272
+ * @category message
273
+ */
274
+ export type MessageTypeId = typeof MessageTypeId
275
+
276
+ /**
277
+ * @since 1.0.0
278
+ * @category message
279
+ */
280
+ export class Message extends Schema_.Class<Message>("@effect/ai/AiInput/Message")({
281
+ role: AiRole.AiRole,
282
+ parts: Schema_.Chunk(Part)
283
+ }) {
284
+ /**
285
+ * @since 1.0.0
286
+ */
287
+ readonly [MessageTypeId]: MessageTypeId = MessageTypeId
288
+ /**
289
+ * @since 1.0.0
290
+ */
291
+ static is(u: unknown): u is Message {
292
+ return Predicate.hasProperty(u, MessageTypeId)
293
+ }
294
+ /**
295
+ * @since 1.0.0
296
+ */
297
+ static fromInput(input: Message.Input, role: AiRole.AiRole = AiRole.user): Message {
298
+ if (typeof input === "string") {
299
+ return new Message({ role, parts: Chunk.of(TextPart.fromContent(input)) }, constDisableValidation)
300
+ } else if (isPart(input)) {
301
+ return new Message({ role, parts: Chunk.of(input) }, constDisableValidation)
302
+ }
303
+ return new Message({ role, parts: Chunk.fromIterable(input) }, constDisableValidation)
304
+ }
305
+ /**
306
+ * @since 1.0.0
307
+ */
308
+ static fromResponse(response: AiResponse): Option.Option<Message> {
309
+ if (Chunk.isEmpty(response.parts)) {
310
+ return Option.none()
311
+ }
312
+ return Option.some(
313
+ new Message({
314
+ role: response.role,
315
+ parts: Chunk.map(response.parts, (part) => {
316
+ switch (part._tag) {
317
+ case "Text": {
318
+ return TextPart.fromContent(part.content)
319
+ }
320
+ case "ToolCall": {
321
+ return new ToolCallPart(part, constDisableValidation)
322
+ }
323
+ case "ImageUrl": {
324
+ return new ImageUrlPart(part, constDisableValidation)
325
+ }
326
+ }
327
+ })
328
+ }, constDisableValidation)
329
+ )
330
+ }
331
+ /**
332
+ * @since 1.0.0
333
+ */
334
+ static fromWithResolved<A>(response: WithResolved<A>): Message {
335
+ const toolParts: Array<ToolCallResolvedPart> = []
336
+ for (const [toolCallId, value] of response.encoded) {
337
+ toolParts.push(new ToolCallResolvedPart({ toolCallId, value }, constDisableValidation))
338
+ }
339
+ const toolPartsChunk = Chunk.unsafeFromArray(toolParts)
340
+ return Option.match(Message.fromResponse(response.response), {
341
+ onNone: () => new Message({ role: AiRole.model, parts: toolPartsChunk }, constDisableValidation),
342
+ onSome: (message) =>
343
+ new Message({
344
+ role: message.role,
345
+ parts: Chunk.appendAll(message.parts, toolPartsChunk)
346
+ }, constDisableValidation)
347
+ })
348
+ }
349
+ }
350
+
351
+ /**
352
+ * @since 1.0.0
353
+ * @category message
354
+ */
355
+ export declare namespace Message {
356
+ /**
357
+ * @since 1.0.0
358
+ * @category message
359
+ */
360
+ export type Input = string | Part | Iterable<Part>
361
+ }
362
+
363
+ /**
364
+ * @since 1.0.0
365
+ * @category constructors
366
+ */
367
+ export const make = (input: Input, options?: {
368
+ readonly role?: AiRole.AiRole
369
+ }): AiInput => {
370
+ if (typeof input !== "string" && Predicate.isIterable(input)) {
371
+ const chunk = Chunk.fromIterable(input as any)
372
+ if (Chunk.isEmpty(chunk)) {
373
+ return chunk as AiInput
374
+ } else if (Message.is(Chunk.unsafeHead(chunk))) {
375
+ return chunk as AiInput
376
+ }
377
+ return Chunk.of(Message.fromInput(chunk as any, options?.role))
378
+ } else if (AiResponse.is(input)) {
379
+ return Option.match(Message.fromResponse(input), {
380
+ onNone: Chunk.empty,
381
+ onSome: Chunk.of
382
+ })
383
+ } else if (WithResolved.is(input)) {
384
+ return Chunk.of(Message.fromWithResolved(input))
385
+ } else if (Message.is(input)) {
386
+ return Chunk.of(input)
387
+ }
388
+ return Chunk.of(Message.fromInput(input, options?.role))
389
+ }
390
+
391
+ /**
392
+ * @since 1.0.0
393
+ * @category constructors
394
+ */
395
+ export const empty: AiInput = Chunk.empty()
396
+
397
+ /**
398
+ * @since 1.0.0
399
+ * @category schemas
400
+ */
401
+ export const Schema: Schema_.Chunk<typeof Message> = Schema_.Chunk(Message)
402
+
403
+ /**
404
+ * @since 1.0.0
405
+ * @category schemas
406
+ */
407
+ export const SchemaJson: Schema_.Schema<Chunk.Chunk<Message>, string> = Schema_.parseJson(Schema)
408
+
409
+ /**
410
+ * @since 1.0.0
411
+ * @category models
412
+ */
413
+ export type Input =
414
+ | string
415
+ | Part
416
+ | Iterable<Part>
417
+ | Message
418
+ | Iterable<Message>
419
+ | AiResponse
420
+ | WithResolved<unknown>
421
+
422
+ /**
423
+ * @since 1.0.0
424
+ * @category models
425
+ */
426
+ export type AiInput = Chunk.Chunk<Message>
427
+
428
+ /**
429
+ * @since 1.0.0
430
+ * @category system
431
+ */
432
+ export class SystemInstruction extends Context.Tag("@effect/ai/AiInput/SystemInstruction")<
433
+ SystemInstruction,
434
+ string
435
+ >() {}
436
+
437
+ /**
438
+ * @since 1.0.0
439
+ * @category system
440
+ */
441
+ export const provideSystem: {
442
+ /**
443
+ * @since 1.0.0
444
+ * @category system
445
+ */
446
+ (input: string): <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, SystemInstruction>>
447
+ /**
448
+ * @since 1.0.0
449
+ * @category system
450
+ */
451
+ <A, E, R>(effect: Effect.Effect<A, E, R>, input: string): Effect.Effect<A, E, Exclude<R, SystemInstruction>>
452
+ } = dual(
453
+ 2,
454
+ <A, E, R>(effect: Effect.Effect<A, E, R>, input: string): Effect.Effect<A, E, Exclude<R, SystemInstruction>> =>
455
+ Effect.provideService(effect, SystemInstruction, input)
456
+ )