@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/AiChat.ts ADDED
@@ -0,0 +1,274 @@
1
+ /**
2
+ * @since 1.0.0
3
+ */
4
+ import type { ParseError } from "@effect/schema/ParseResult"
5
+ import * as Schema from "@effect/schema/Schema"
6
+ import * as Channel from "effect/Channel"
7
+ import * as Chunk from "effect/Chunk"
8
+ import * as Effect from "effect/Effect"
9
+ import * as Ref from "effect/Ref"
10
+ import * as Stream from "effect/Stream"
11
+ import type { Concurrency } from "effect/Types"
12
+ import type { AiError } from "./AiError.js"
13
+ import * as AiInput from "./AiInput.js"
14
+ import { AiResponse, WithResolved } from "./AiResponse.js"
15
+ import type * as AiToolkit from "./AiToolkit.js"
16
+ import { Completions } from "./Completions.js"
17
+
18
+ /**
19
+ * @since 1.0.0
20
+ * @category tags
21
+ */
22
+ export class AiChat extends Effect.Tag("@effect/ai/AiChat")<
23
+ AiChat,
24
+ AiChat.Service
25
+ >() {}
26
+
27
+ /**
28
+ * @since 1.0.0
29
+ * @category models
30
+ */
31
+ export declare namespace AiChat {
32
+ /**
33
+ * @since 1.0.0
34
+ * @category models
35
+ */
36
+ export interface Service {
37
+ readonly history: Effect.Effect<AiInput.AiInput>
38
+ readonly export: Effect.Effect<unknown>
39
+ readonly exportJson: Effect.Effect<string>
40
+ readonly send: (input: AiInput.Input) => Effect.Effect<AiResponse, AiError>
41
+ readonly stream: (input: AiInput.Input) => Stream.Stream<AiResponse, AiError>
42
+ readonly structured: <A, I, R>(
43
+ tool: Completions.StructuredSchema<A, I, R>,
44
+ input: AiInput.Input
45
+ ) => Effect.Effect<A, AiError, R>
46
+ readonly toolkit: <Tools extends AiToolkit.Tool.AnySchema>(
47
+ options: {
48
+ readonly input: AiInput.Input
49
+ readonly tools: AiToolkit.Handlers<Tools>
50
+ readonly required?: Tools["_tag"] | boolean | undefined
51
+ readonly concurrency?: Concurrency | undefined
52
+ }
53
+ ) => Effect.Effect<
54
+ WithResolved<AiToolkit.Tool.Success<Tools>>,
55
+ AiError | AiToolkit.Tool.Failure<Tools>,
56
+ AiToolkit.Tool.Context<Tools>
57
+ >
58
+ readonly toolkitStream: <Tools extends AiToolkit.Tool.AnySchema>(
59
+ options: {
60
+ readonly input: AiInput.Input
61
+ readonly tools: AiToolkit.Handlers<Tools>
62
+ readonly required?: Tools["_tag"] | boolean | undefined
63
+ readonly concurrency?: Concurrency | undefined
64
+ }
65
+ ) => Stream.Stream<
66
+ WithResolved<AiToolkit.Tool.Success<Tools>>,
67
+ AiError | AiToolkit.Tool.Failure<Tools>,
68
+ AiToolkit.Tool.Context<Tools>
69
+ >
70
+ }
71
+ }
72
+
73
+ /**
74
+ * @since 1.0.0
75
+ * @category constructors
76
+ */
77
+ export const fromInput = (input: AiInput.Input): Effect.Effect<AiChat.Service, never, Completions> =>
78
+ Ref.make(AiInput.make(input)).pipe(
79
+ Effect.bindTo("historyRef"),
80
+ Effect.bind("completions", () => Completions),
81
+ Effect.map(({ completions, historyRef }) => new AiChatImpl(historyRef, completions))
82
+ )
83
+
84
+ class AiChatImpl implements AiChat.Service {
85
+ readonly semaphore = Effect.unsafeMakeSemaphore(1)
86
+
87
+ constructor(
88
+ readonly historyRef: Ref.Ref<AiInput.AiInput>,
89
+ readonly completions: Completions.Service
90
+ ) {}
91
+
92
+ get history() {
93
+ return Ref.get(this.historyRef)
94
+ }
95
+
96
+ get export() {
97
+ return Ref.get(this.historyRef).pipe(
98
+ Effect.flatMap(Schema.encode(AiInput.Schema)),
99
+ Effect.orDie
100
+ )
101
+ }
102
+
103
+ get exportJson() {
104
+ return Ref.get(this.historyRef).pipe(
105
+ Effect.flatMap(Schema.encode(AiInput.SchemaJson)),
106
+ Effect.orDie
107
+ )
108
+ }
109
+
110
+ send(input: AiInput.Input) {
111
+ const newParts = AiInput.make(input)
112
+ return Ref.get(this.historyRef).pipe(
113
+ Effect.flatMap((parts) => {
114
+ const allParts = Chunk.appendAll(parts, newParts)
115
+ return this.completions.create(allParts).pipe(
116
+ Effect.tap((response) => {
117
+ const responseParts = AiInput.make(response)
118
+ return Ref.set(this.historyRef, Chunk.appendAll(allParts, responseParts))
119
+ })
120
+ )
121
+ }),
122
+ this.semaphore.withPermits(1),
123
+ Effect.withSpan("AiChat.send", { attributes: { input }, captureStackTrace: false })
124
+ )
125
+ }
126
+
127
+ stream(input: AiInput.Input) {
128
+ return Stream.suspend(() => {
129
+ let combined = AiResponse.empty
130
+ return Stream.fromChannel(Channel.acquireUseRelease(
131
+ this.semaphore.take(1).pipe(
132
+ Effect.zipRight(Ref.get(this.historyRef)),
133
+ Effect.map(Chunk.appendAll(AiInput.make(input)))
134
+ ),
135
+ (parts) =>
136
+ this.completions.stream(parts).pipe(
137
+ Stream.map((chunk) => {
138
+ combined = combined.concat(chunk)
139
+ return chunk
140
+ }),
141
+ Stream.toChannel
142
+ ),
143
+ (parts) =>
144
+ Effect.zipRight(
145
+ Ref.set(this.historyRef, Chunk.appendAll(parts, AiInput.make(combined))),
146
+ this.semaphore.release(1)
147
+ )
148
+ ))
149
+ }).pipe(Stream.withSpan("AiChat.stream", { attributes: { input }, captureStackTrace: false }))
150
+ }
151
+
152
+ structured<A, I, R>(
153
+ schema: Completions.StructuredSchema<A, I, R>,
154
+ input: AiInput.Input
155
+ ): Effect.Effect<A, AiError, R> {
156
+ const newParts = AiInput.make(input)
157
+ return Ref.get(this.historyRef).pipe(
158
+ Effect.flatMap((parts) => {
159
+ const allParts = Chunk.appendAll(parts, newParts)
160
+ return this.completions.structured({
161
+ input: allParts,
162
+ schema
163
+ }).pipe(
164
+ Effect.flatMap((response) => {
165
+ const responseParts = AiInput.make(response)
166
+ return Effect.as(
167
+ Ref.set(this.historyRef, Chunk.appendAll(allParts, responseParts)),
168
+ response.unsafeValue
169
+ )
170
+ })
171
+ )
172
+ }),
173
+ this.semaphore.withPermits(1),
174
+ Effect.withSpan("AiChat.structured", {
175
+ attributes: { input, schema: schema._tag ?? schema.identifier },
176
+ captureStackTrace: false
177
+ })
178
+ )
179
+ }
180
+
181
+ toolkit<Tools extends AiToolkit.Tool.AnySchema>(
182
+ options: {
183
+ readonly input: AiInput.Input
184
+ readonly tools: AiToolkit.Handlers<Tools>
185
+ readonly required?: Tools["_tag"] | boolean | undefined
186
+ readonly concurrency?: Concurrency | undefined
187
+ }
188
+ ): Effect.Effect<
189
+ WithResolved<AiToolkit.Tool.Success<Tools>>,
190
+ AiError | AiToolkit.Tool.Failure<Tools>,
191
+ AiToolkit.Tool.Context<Tools>
192
+ > {
193
+ const newParts = AiInput.make(options.input)
194
+ return Ref.get(this.historyRef).pipe(
195
+ Effect.flatMap((parts) => {
196
+ const allParts = Chunk.appendAll(parts, newParts)
197
+ return this.completions.toolkit({
198
+ ...options,
199
+ input: allParts
200
+ }).pipe(
201
+ Effect.tap((response) => {
202
+ const responseParts = AiInput.make(response)
203
+ return Ref.set(this.historyRef, Chunk.appendAll(allParts, responseParts))
204
+ })
205
+ )
206
+ }),
207
+ this.semaphore.withPermits(1),
208
+ Effect.withSpan("AiChat.toolkit", { attributes: { input: options.input }, captureStackTrace: false })
209
+ )
210
+ }
211
+
212
+ toolkitStream<Tools extends AiToolkit.Tool.AnySchema>(
213
+ options: {
214
+ readonly input: AiInput.Input
215
+ readonly tools: AiToolkit.Handlers<Tools>
216
+ readonly required?: Tools["_tag"] | boolean | undefined
217
+ readonly concurrency?: Concurrency | undefined
218
+ }
219
+ ): Stream.Stream<
220
+ WithResolved<AiToolkit.Tool.Success<Tools>>,
221
+ AiError | AiToolkit.Tool.Failure<Tools>,
222
+ AiToolkit.Tool.Context<Tools>
223
+ > {
224
+ return Stream.suspend(() => {
225
+ let combined = WithResolved.empty as WithResolved<AiToolkit.Tool.Success<Tools>>
226
+ return Stream.fromChannel(Channel.acquireUseRelease(
227
+ this.semaphore.take(1).pipe(
228
+ Effect.zipRight(Ref.get(this.historyRef)),
229
+ Effect.map(Chunk.appendAll(AiInput.make(options.input)))
230
+ ),
231
+ (parts) =>
232
+ this.completions.toolkitStream({
233
+ ...options,
234
+ input: parts
235
+ }).pipe(
236
+ Stream.map((chunk) => {
237
+ combined = combined.concat(chunk)
238
+ return chunk
239
+ }),
240
+ Stream.toChannel
241
+ ),
242
+ (parts) =>
243
+ Effect.zipRight(
244
+ Ref.set(this.historyRef, Chunk.appendAll(parts, AiInput.make(combined))),
245
+ this.semaphore.release(1)
246
+ )
247
+ ))
248
+ }).pipe(Stream.withSpan("AiChat.toolkitStream", { attributes: { input: options.input }, captureStackTrace: false }))
249
+ }
250
+ }
251
+
252
+ /**
253
+ * @since 1.0.0
254
+ * @category constructors
255
+ */
256
+ export const empty: Effect.Effect<AiChat.Service, never, Completions> = fromInput(AiInput.empty)
257
+
258
+ /**
259
+ * @since 1.0.0
260
+ * @category constructors
261
+ */
262
+ export const fromExport = (data: unknown): Effect.Effect<AiChat.Service, ParseError, Completions> =>
263
+ Schema.decodeUnknown(AiInput.Schema)(data).pipe(
264
+ Effect.flatMap(fromInput)
265
+ )
266
+
267
+ /**
268
+ * @since 1.0.0
269
+ * @category constructors
270
+ */
271
+ export const fromJson = (data: string): Effect.Effect<AiChat.Service, ParseError, Completions> =>
272
+ Schema.decode(AiInput.SchemaJson)(data).pipe(
273
+ Effect.flatMap(fromInput)
274
+ )
package/src/AiError.ts ADDED
@@ -0,0 +1,38 @@
1
+ /**
2
+ * @since 1.0.0
3
+ */
4
+ import * as Schema from "@effect/schema/Schema"
5
+
6
+ /**
7
+ * @since 1.0.0
8
+ * @category type ids
9
+ */
10
+ export const TypeId: unique symbol = Symbol("@effect/ai/AiError")
11
+
12
+ /**
13
+ * @since 1.0.0
14
+ * @category type ids
15
+ */
16
+ export type TypeId = typeof TypeId
17
+
18
+ /**
19
+ * @since 1.0.0
20
+ * @category errors
21
+ */
22
+ export class AiError extends Schema.TaggedError<AiError>("@effect/ai/AiError")("AiError", {
23
+ module: Schema.String,
24
+ method: Schema.String,
25
+ description: Schema.String,
26
+ cause: Schema.optional(Schema.Defect)
27
+ }) {
28
+ /**
29
+ * @since 1.0.0
30
+ */
31
+ readonly [TypeId]: TypeId = TypeId
32
+ /**
33
+ * @since 1.0.0
34
+ */
35
+ get message(): string {
36
+ return `${this.module}.${this.method}: ${this.description}`
37
+ }
38
+ }