@effect/ai 0.14.1 → 0.16.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 (134) hide show
  1. package/AiEmbeddingModel/package.json +6 -0
  2. package/AiLanguageModel/package.json +6 -0
  3. package/AiTool/package.json +6 -0
  4. package/dist/cjs/AiChat.js +65 -86
  5. package/dist/cjs/AiChat.js.map +1 -1
  6. package/dist/cjs/{Embeddings.js → AiEmbeddingModel.js} +12 -12
  7. package/dist/cjs/AiEmbeddingModel.js.map +1 -0
  8. package/dist/cjs/AiError.js +8 -1
  9. package/dist/cjs/AiError.js.map +1 -1
  10. package/dist/cjs/AiInput.js +335 -248
  11. package/dist/cjs/AiInput.js.map +1 -1
  12. package/dist/cjs/AiLanguageModel.js +311 -0
  13. package/dist/cjs/AiLanguageModel.js.map +1 -0
  14. package/dist/cjs/AiModel.js +11 -5
  15. package/dist/cjs/AiModel.js.map +1 -1
  16. package/dist/cjs/AiPlan.js +10 -3
  17. package/dist/cjs/AiPlan.js.map +1 -1
  18. package/dist/cjs/AiResponse.js +481 -165
  19. package/dist/cjs/AiResponse.js.map +1 -1
  20. package/dist/cjs/AiTelemetry.js +10 -3
  21. package/dist/cjs/AiTelemetry.js.map +1 -1
  22. package/dist/cjs/AiTool.js +93 -0
  23. package/dist/cjs/AiTool.js.map +1 -0
  24. package/dist/cjs/AiToolkit.js +121 -98
  25. package/dist/cjs/AiToolkit.js.map +1 -1
  26. package/dist/cjs/Tokenizer.js +14 -16
  27. package/dist/cjs/Tokenizer.js.map +1 -1
  28. package/dist/cjs/index.js +7 -9
  29. package/dist/cjs/internal/aiPlan.js +6 -9
  30. package/dist/cjs/internal/aiPlan.js.map +1 -1
  31. package/dist/cjs/internal/common.js +22 -0
  32. package/dist/cjs/internal/common.js.map +1 -0
  33. package/dist/dts/AiChat.d.ts +58 -44
  34. package/dist/dts/AiChat.d.ts.map +1 -1
  35. package/dist/dts/{Embeddings.d.ts → AiEmbeddingModel.d.ts} +13 -14
  36. package/dist/dts/AiEmbeddingModel.d.ts.map +1 -0
  37. package/dist/dts/AiError.d.ts +4 -3
  38. package/dist/dts/AiError.d.ts.map +1 -1
  39. package/dist/dts/AiInput.d.ts +441 -146
  40. package/dist/dts/AiInput.d.ts.map +1 -1
  41. package/dist/dts/AiLanguageModel.d.ts +263 -0
  42. package/dist/dts/AiLanguageModel.d.ts.map +1 -0
  43. package/dist/dts/AiModel.d.ts +21 -20
  44. package/dist/dts/AiModel.d.ts.map +1 -1
  45. package/dist/dts/AiPlan.d.ts +90 -26
  46. package/dist/dts/AiPlan.d.ts.map +1 -1
  47. package/dist/dts/AiResponse.d.ts +711 -100
  48. package/dist/dts/AiResponse.d.ts.map +1 -1
  49. package/dist/dts/AiTelemetry.d.ts +175 -157
  50. package/dist/dts/AiTelemetry.d.ts.map +1 -1
  51. package/dist/dts/AiTool.d.ts +288 -0
  52. package/dist/dts/AiTool.d.ts.map +1 -0
  53. package/dist/dts/AiToolkit.d.ts +50 -111
  54. package/dist/dts/AiToolkit.d.ts.map +1 -1
  55. package/dist/dts/Tokenizer.d.ts +8 -6
  56. package/dist/dts/Tokenizer.d.ts.map +1 -1
  57. package/dist/dts/index.d.ts +8 -12
  58. package/dist/dts/index.d.ts.map +1 -1
  59. package/dist/dts/internal/common.d.ts +2 -0
  60. package/dist/dts/internal/common.d.ts.map +1 -0
  61. package/dist/esm/AiChat.js +62 -83
  62. package/dist/esm/AiChat.js.map +1 -1
  63. package/dist/esm/{Embeddings.js → AiEmbeddingModel.js} +10 -10
  64. package/dist/esm/AiEmbeddingModel.js.map +1 -0
  65. package/dist/esm/AiError.js +8 -1
  66. package/dist/esm/AiError.js.map +1 -1
  67. package/dist/esm/AiInput.js +316 -238
  68. package/dist/esm/AiInput.js.map +1 -1
  69. package/dist/esm/AiLanguageModel.js +300 -0
  70. package/dist/esm/AiLanguageModel.js.map +1 -0
  71. package/dist/esm/AiModel.js +11 -5
  72. package/dist/esm/AiModel.js.map +1 -1
  73. package/dist/esm/AiPlan.js +8 -2
  74. package/dist/esm/AiPlan.js.map +1 -1
  75. package/dist/esm/AiResponse.js +467 -162
  76. package/dist/esm/AiResponse.js.map +1 -1
  77. package/dist/esm/AiTelemetry.js +8 -2
  78. package/dist/esm/AiTelemetry.js.map +1 -1
  79. package/dist/esm/AiTool.js +82 -0
  80. package/dist/esm/AiTool.js.map +1 -0
  81. package/dist/esm/AiToolkit.js +118 -96
  82. package/dist/esm/AiToolkit.js.map +1 -1
  83. package/dist/esm/Tokenizer.js +14 -16
  84. package/dist/esm/Tokenizer.js.map +1 -1
  85. package/dist/esm/index.js +8 -12
  86. package/dist/esm/index.js.map +1 -1
  87. package/dist/esm/internal/aiPlan.js +4 -7
  88. package/dist/esm/internal/aiPlan.js.map +1 -1
  89. package/dist/esm/internal/common.js +14 -0
  90. package/dist/esm/internal/common.js.map +1 -0
  91. package/package.json +28 -36
  92. package/src/AiChat.ts +182 -207
  93. package/src/{Embeddings.ts → AiEmbeddingModel.ts} +19 -18
  94. package/src/AiError.ts +8 -1
  95. package/src/AiInput.ts +434 -313
  96. package/src/AiLanguageModel.ts +569 -0
  97. package/src/AiModel.ts +47 -29
  98. package/src/AiPlan.ts +102 -30
  99. package/src/AiResponse.ts +743 -187
  100. package/src/AiTelemetry.ts +214 -197
  101. package/src/AiTool.ts +496 -0
  102. package/src/AiToolkit.ts +200 -240
  103. package/src/Tokenizer.ts +18 -22
  104. package/src/index.ts +9 -14
  105. package/src/internal/aiPlan.ts +12 -14
  106. package/src/internal/common.ts +12 -0
  107. package/AiModels/package.json +0 -6
  108. package/AiRole/package.json +0 -6
  109. package/Completions/package.json +0 -6
  110. package/Embeddings/package.json +0 -6
  111. package/dist/cjs/AiModels.js +0 -54
  112. package/dist/cjs/AiModels.js.map +0 -1
  113. package/dist/cjs/AiRole.js +0 -106
  114. package/dist/cjs/AiRole.js.map +0 -1
  115. package/dist/cjs/Completions.js +0 -256
  116. package/dist/cjs/Completions.js.map +0 -1
  117. package/dist/cjs/Embeddings.js.map +0 -1
  118. package/dist/dts/AiModels.d.ts +0 -34
  119. package/dist/dts/AiModels.d.ts.map +0 -1
  120. package/dist/dts/AiRole.d.ts +0 -111
  121. package/dist/dts/AiRole.d.ts.map +0 -1
  122. package/dist/dts/Completions.d.ts +0 -128
  123. package/dist/dts/Completions.d.ts.map +0 -1
  124. package/dist/dts/Embeddings.d.ts.map +0 -1
  125. package/dist/esm/AiModels.js +0 -44
  126. package/dist/esm/AiModels.js.map +0 -1
  127. package/dist/esm/AiRole.js +0 -93
  128. package/dist/esm/AiRole.js.map +0 -1
  129. package/dist/esm/Completions.js +0 -245
  130. package/dist/esm/Completions.js.map +0 -1
  131. package/dist/esm/Embeddings.js.map +0 -1
  132. package/src/AiModels.ts +0 -77
  133. package/src/AiRole.ts +0 -122
  134. package/src/Completions.ts +0 -434
package/src/AiInput.ts CHANGED
@@ -1,227 +1,197 @@
1
1
  /**
2
2
  * @since 1.0.0
3
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 Chunk from "effect/Chunk"
8
- import * as Context from "effect/Context"
9
- import * as Effect from "effect/Effect"
10
- import * as Encoding from "effect/Encoding"
11
4
  import { dual } from "effect/Function"
12
- import * as Option from "effect/Option"
13
- import * as ParseResult from "effect/ParseResult"
14
5
  import * as Predicate from "effect/Predicate"
15
- import * as Schema_ from "effect/Schema"
16
- import { AiResponse, ToolCallId, WithResolved } from "./AiResponse.js"
17
- import * as AiRole from "./AiRole.js"
18
-
19
- const constDisableValidation = { disableValidation: true } as const
6
+ import * as Schema from "effect/Schema"
7
+ import * as AiResponse from "./AiResponse.js"
8
+ import * as InternalCommon from "./internal/common.js"
20
9
 
21
10
  /**
22
11
  * @since 1.0.0
23
- * @category parts
12
+ * @category Type Ids
24
13
  */
25
- export const PartTypeId: unique symbol = Symbol("@effect/ai/AiInput/Part")
14
+ export const TypeId = Symbol.for("@effect/ai/AiInput")
26
15
 
27
16
  /**
28
17
  * @since 1.0.0
29
- * @category parts
18
+ * @category Type Ids
30
19
  */
31
- export type PartTypeId = typeof PartTypeId
20
+ export type TypeId = typeof TypeId
32
21
 
33
22
  /**
23
+ * Represents input to a large language model.
24
+ *
34
25
  * @since 1.0.0
35
- * @category parts
26
+ * @category Models
36
27
  */
37
- export class TextPart extends Schema_.TaggedClass<TextPart>("@effect/ai/AiInput/TextPart")("Text", {
38
- content: Schema_.String
28
+ export class AiInput extends Schema.Class<AiInput>(
29
+ "@effect/ai/AiInput"
30
+ )({
31
+ messages: Schema.Array(Schema.suspend(() => Message))
39
32
  }) {
40
33
  /**
41
34
  * @since 1.0.0
42
35
  */
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
- }
36
+ readonly [TypeId]: TypeId = TypeId
50
37
  }
51
38
 
52
39
  /**
53
40
  * @since 1.0.0
54
- * @category parts
41
+ * @category Models
42
+ */
43
+ export const FromJson = Schema.parseJson(AiInput)
44
+
45
+ /**
46
+ * Represents raw input types that can be converted into an `AiInput`.
47
+ *
48
+ * @since 1.0.0
49
+ * @category Models
50
+ */
51
+ export type Raw =
52
+ | string
53
+ | Message
54
+ | Iterable<Message>
55
+ | AiInput
56
+ | AiResponse.AiResponse
57
+ | AiResponse.WithStructuredOutput<any>
58
+ | AiResponse.WithToolCallResults<any>
59
+
60
+ // =============================================================================
61
+ // Message
62
+ // =============================================================================
63
+
64
+ /**
65
+ * @since 1.0.0
66
+ * @category Type Ids
55
67
  */
56
- export const ImageQuality = Schema_.Literal("low", "high", "auto")
68
+ export const MessageTypeId = Symbol.for("@effect/ai/AiInput/Message")
57
69
 
58
70
  /**
59
71
  * @since 1.0.0
60
- * @category parts
72
+ * @category Type Ids
61
73
  */
62
- export type ImageQuality = typeof ImageQuality.Type
74
+ export type MessageTypeId = typeof MessageTypeId
63
75
 
64
76
  /**
65
77
  * @since 1.0.0
66
- * @category parts
78
+ * @category Models
67
79
  */
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
- )
80
+ export class UserMessage extends Schema.TaggedClass<UserMessage>(
81
+ "@effect/ai/AiInput/Message/UserMessage"
82
+ )("UserMessage", {
83
+ parts: Schema.Array(Schema.suspend(() => UserMessagePart)),
84
+ userName: Schema.optional(Schema.String)
74
85
  }) {
75
86
  /**
76
87
  * @since 1.0.0
77
88
  */
78
- readonly [PartTypeId]: PartTypeId = PartTypeId
89
+ readonly [MessageTypeId]: MessageTypeId = MessageTypeId
79
90
  }
80
91
 
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
92
  /**
135
93
  * @since 1.0.0
136
- * @category parts
94
+ * @category Models
137
95
  */
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
- )
96
+ export class AssistantMessage extends Schema.TaggedClass<AssistantMessage>(
97
+ "@effect/ai/AiInput/Message/AssistantMessage"
98
+ )("AssistantMessage", {
99
+ parts: Schema.Array(Schema.suspend(() => AssistantMessagePart))
144
100
  }) {
145
101
  /**
146
102
  * @since 1.0.0
147
103
  */
148
- readonly [PartTypeId]: PartTypeId = PartTypeId
104
+ readonly [MessageTypeId]: MessageTypeId = MessageTypeId
105
+ }
149
106
 
107
+ /**
108
+ * @since 1.0.0
109
+ * @category Models
110
+ */
111
+ export class ToolMessage extends Schema.TaggedClass<ToolMessage>(
112
+ "@effect/ai/AiInput/Message/ToolMessage"
113
+ )("ToolMessage", {
114
+ parts: Schema.Array(Schema.suspend(() => ToolMessagePart))
115
+ }) {
150
116
  /**
151
117
  * @since 1.0.0
152
118
  */
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
- }
119
+ readonly [MessageTypeId]: MessageTypeId = MessageTypeId
120
+ }
121
+
122
+ /**
123
+ * @since 1.0.0
124
+ * @category Models
125
+ */
126
+ export const Message: Schema.Union<[
127
+ typeof UserMessage,
128
+ typeof AssistantMessage,
129
+ typeof ToolMessage
130
+ ]> = Schema.Union(
131
+ UserMessage,
132
+ AssistantMessage,
133
+ ToolMessage
134
+ )
135
+
136
+ /**
137
+ * @since 1.0.0
138
+ * @category Models
139
+ */
140
+ export type Message = typeof Message.Type
141
+
142
+ // =============================================================================
143
+ // Part
144
+ // =============================================================================
190
145
 
146
+ /**
147
+ * @since 1.0.0
148
+ * @category Type Ids
149
+ */
150
+ export const PartTypeId = Symbol.for("@effect/ai/AiInput/Message/Part")
151
+
152
+ /**
153
+ * @since 1.0.0
154
+ * @category Type Ids
155
+ */
156
+ export type PartTypeId = typeof PartTypeId
157
+
158
+ /**
159
+ * Represents a text part of a message.
160
+ *
161
+ * @since 1.0.0
162
+ * @category Models
163
+ */
164
+ export class TextPart extends Schema.TaggedClass<TextPart>(
165
+ "@effect/ai/AiInput/TextPart"
166
+ )("TextPart", {
167
+ /**
168
+ * The text content.
169
+ */
170
+ text: Schema.String
171
+ }) {
191
172
  /**
192
173
  * @since 1.0.0
193
174
  */
194
- static fromBlob(blob: Blob, quality: ImageQuality = "auto"): Effect.Effect<ImagePart> {
195
- return Effect.promise(() => blob.arrayBuffer()).pipe(
196
- Effect.map((buffer) =>
197
- new ImagePart({
198
- image: {
199
- contentType: blob.type,
200
- data: new Uint8Array(buffer)
201
- },
202
- quality
203
- })
204
- )
205
- )
206
- }
207
-
208
- get asBase64(): string {
209
- return Encoding.encodeBase64(this.image.data)
210
- }
211
-
212
- get asDataUri(): string {
213
- return `data:${this.image.contentType};base64,${this.asBase64}`
214
- }
175
+ readonly [PartTypeId]: PartTypeId = PartTypeId
215
176
  }
216
177
 
217
178
  /**
179
+ * Represents an image part of a message with binary image data.
180
+ *
218
181
  * @since 1.0.0
219
- * @category parts
182
+ * @category Models
220
183
  */
221
- export class ToolCallPart extends Schema_.TaggedClass<ToolCallPart>("@effect/ai/AiInput/ToolCallPart")("ToolCall", {
222
- id: ToolCallId,
223
- name: Schema_.String,
224
- params: Schema_.Unknown
184
+ export class ImagePart extends Schema.TaggedClass<ImagePart>(
185
+ "@effect/ai/AiInput/ImagePart"
186
+ )("ImagePart", {
187
+ /**
188
+ * The binary image data.
189
+ */
190
+ data: Schema.Uint8ArrayFromBase64,
191
+ /**
192
+ * The optional MIME type for the image.
193
+ */
194
+ mediaType: Schema.optional(Schema.String)
225
195
  }) {
226
196
  /**
227
197
  * @since 1.0.0
@@ -230,15 +200,19 @@ export class ToolCallPart extends Schema_.TaggedClass<ToolCallPart>("@effect/ai/
230
200
  }
231
201
 
232
202
  /**
203
+ * Represents an image part of a message with a URL pointing to the image.
204
+ *
233
205
  * @since 1.0.0
234
- * @category parts
206
+ * @category Models
235
207
  */
236
- export class ToolCallResolvedPart
237
- extends Schema_.TaggedClass<ToolCallResolvedPart>("@effect/ai/AiInput/ToolCallResolvedPart")("ToolCallResolved", {
238
- toolCallId: ToolCallId,
239
- value: Schema_.Unknown
240
- })
241
- {
208
+ export class ImageUrlPart extends Schema.TaggedClass<ImageUrlPart>(
209
+ "@effect/ai/AiInput/ImageUrlPart"
210
+ )("ImageUrlPart", {
211
+ /**
212
+ * The URL that points to the image.
213
+ */
214
+ url: Schema.URL
215
+ }) {
242
216
  /**
243
217
  * @since 1.0.0
244
218
  */
@@ -246,231 +220,378 @@ export class ToolCallResolvedPart
246
220
  }
247
221
 
248
222
  /**
223
+ * Represents a file part of a message with binary file data.
224
+ *
249
225
  * @since 1.0.0
250
- * @category parts
226
+ * @category Models
251
227
  */
252
- export type Part = TextPart | ToolCallPart | ToolCallResolvedPart | ImagePart | ImageUrlPart
228
+ export class FilePart extends Schema.TaggedClass<FilePart>(
229
+ "@effect/ai/AiInput/FilePart"
230
+ )("FilePart", {
231
+ /**
232
+ * The binary file data.
233
+ */
234
+ data: Schema.Uint8ArrayFromBase64,
235
+ /**
236
+ * The optional name of the file.
237
+ */
238
+ name: Schema.optional(Schema.String),
239
+ /**
240
+ * The optional MIME type for the image.
241
+ */
242
+ mediaType: Schema.optional(Schema.String)
243
+ }) {
244
+ /**
245
+ * @since 1.0.0
246
+ */
247
+ readonly [PartTypeId]: PartTypeId = PartTypeId
248
+ }
253
249
 
254
250
  /**
251
+ * Represents a file part of a message with a URL pointing to the file.
252
+ *
255
253
  * @since 1.0.0
256
- * @category parts
254
+ * @category Models
257
255
  */
258
- export const isPart = (u: unknown): u is Part => Predicate.hasProperty(u, PartTypeId)
256
+ export class FileUrlPart extends Schema.TaggedClass<FileUrlPart>(
257
+ "@effect/ai/AiInput/FileUrlPart"
258
+ )("FileUrlPart", {
259
+ /**
260
+ * The URL that points to the file.
261
+ */
262
+ url: Schema.URL
263
+ }) {
264
+ /**
265
+ * @since 1.0.0
266
+ */
267
+ readonly [PartTypeId]: PartTypeId = PartTypeId
268
+ }
259
269
 
260
270
  /**
271
+ * Represents a part of a message containing reasoning that the model used to
272
+ * generate its output.
273
+ *
261
274
  * @since 1.0.0
262
- * @category parts
275
+ * @category Models
263
276
  */
264
- export declare namespace Part {
277
+ export class ReasoningPart extends Schema.TaggedClass<ReasoningPart>(
278
+ "@effect/ai/AiInput/ReasoningPart"
279
+ )("ReasoningPart", {
280
+ /**
281
+ * The reasoning text that the model used to return the output.
282
+ */
283
+ reasoningText: Schema.String,
284
+ /**
285
+ * An optional signature which verifies that the reasoning text was generated
286
+ * by the model.
287
+ */
288
+ signature: Schema.optional(Schema.String)
289
+ }) {
265
290
  /**
266
291
  * @since 1.0.0
267
- * @category parts
268
- */
269
- export type Schema = Schema_.Union<[
270
- typeof TextPart,
271
- typeof ToolCallPart,
272
- typeof ToolCallResolvedPart,
273
- typeof ImagePart,
274
- typeof ImageUrlPart
275
- ]>
292
+ */
293
+ readonly [PartTypeId]: PartTypeId = PartTypeId
276
294
  }
277
295
 
278
296
  /**
297
+ * Represents a part of a message containing content in the model's reasoning
298
+ * that was encrypted by the model provider for safety reasons.
299
+ *
279
300
  * @since 1.0.0
280
- * @category parts
301
+ * @category Models
281
302
  */
282
- export const Part: Part.Schema = Schema_.Union(TextPart, ToolCallPart, ToolCallResolvedPart, ImagePart, ImageUrlPart)
303
+ export class RedactedReasoningPart extends Schema.TaggedClass<RedactedReasoningPart>(
304
+ "@effect/ai/AiInput/RedactedReasoningPart"
305
+ )("RedactedReasoningPart", {
306
+ /**
307
+ * The content in the reasoning that was encrypted by the model provider for
308
+ * safety reasons.
309
+ */
310
+ redactedText: Schema.String
311
+ }) {
312
+ /**
313
+ * @since 1.0.0
314
+ */
315
+ readonly [PartTypeId]: PartTypeId = PartTypeId
316
+ }
283
317
 
284
318
  /**
319
+ * Represents the identifier generated by a model when a tool call is requested.
320
+ *
285
321
  * @since 1.0.0
286
- * @category message
322
+ * @category Models
287
323
  */
288
- export const MessageTypeId: unique symbol = Symbol("@effect/ai/AiInput/Message")
324
+ export const ToolCallId: Schema.brand<typeof Schema.String, "@effect/ai/ToolCallId"> = InternalCommon.ToolCallId
289
325
 
290
326
  /**
291
327
  * @since 1.0.0
292
- * @category message
328
+ * @category Models
293
329
  */
294
- export type MessageTypeId = typeof MessageTypeId
330
+ export type ToolCallId = typeof ToolCallId.Type
295
331
 
296
332
  /**
333
+ * Represents a part of a message containing a tool call that the model has
334
+ * requested invocation of.
335
+ *
297
336
  * @since 1.0.0
298
- * @category message
337
+ * @category Models
299
338
  */
300
- export class Message extends Schema_.Class<Message>("@effect/ai/AiInput/Message")({
301
- role: AiRole.AiRole,
302
- parts: Schema_.Chunk(Part)
303
- }) {
339
+ export class ToolCallPart extends Schema.TaggedClass<ToolCallPart>(
340
+ "@effect/ai/AiInput/ToolCallPart"
341
+ )("ToolCallPart", {
304
342
  /**
305
- * @since 1.0.0
343
+ * The identifier generated by a model when requesting a tool call.
306
344
  */
307
- readonly [MessageTypeId]: MessageTypeId = MessageTypeId
308
- /**
309
- * @since 1.0.0
310
- */
311
- static is(u: unknown): u is Message {
312
- return Predicate.hasProperty(u, MessageTypeId)
313
- }
345
+ id: ToolCallId,
314
346
  /**
315
- * @since 1.0.0
347
+ * The name of the tool to call.
316
348
  */
317
- static fromInput(input: Message.Input, role: AiRole.AiRole = AiRole.user): Message {
318
- if (typeof input === "string") {
319
- return new Message({ role, parts: Chunk.of(TextPart.fromContent(input)) }, constDisableValidation)
320
- } else if (isPart(input)) {
321
- return new Message({ role, parts: Chunk.of(input) }, constDisableValidation)
322
- }
323
- return new Message({ role, parts: Chunk.fromIterable(input) }, constDisableValidation)
324
- }
349
+ name: Schema.String,
325
350
  /**
326
- * @since 1.0.0
351
+ * The arguments to call the tool with as a JSON-serializable object that
352
+ * matches the tool call input schema.
327
353
  */
328
- static fromResponse(response: AiResponse): Option.Option<Message> {
329
- if (Chunk.isEmpty(response.parts)) {
330
- return Option.none()
331
- }
332
- return Option.some(
333
- new Message({
334
- role: response.role,
335
- parts: Chunk.map(response.parts, (part) => {
336
- switch (part._tag) {
337
- case "Text": {
338
- return TextPart.fromContent(part.content)
339
- }
340
- case "ToolCall": {
341
- return new ToolCallPart(part, constDisableValidation)
342
- }
343
- case "ImageUrl": {
344
- return new ImageUrlPart(part, constDisableValidation)
345
- }
346
- }
347
- })
348
- }, constDisableValidation)
349
- )
350
- }
354
+ params: Schema.Unknown
355
+ }) {
351
356
  /**
352
357
  * @since 1.0.0
353
358
  */
354
- static fromWithResolved<A>(response: WithResolved<A>): Message {
355
- const toolParts: Array<ToolCallResolvedPart> = []
356
- for (const [toolCallId, value] of response.encoded) {
357
- toolParts.push(new ToolCallResolvedPart({ toolCallId, value }, constDisableValidation))
358
- }
359
- const toolPartsChunk = Chunk.unsafeFromArray(toolParts)
360
- return Option.match(Message.fromResponse(response.response), {
361
- onNone: () => new Message({ role: AiRole.model, parts: toolPartsChunk }, constDisableValidation),
362
- onSome: (message) =>
363
- new Message({
364
- role: message.role,
365
- parts: Chunk.appendAll(message.parts, toolPartsChunk)
366
- }, constDisableValidation)
367
- })
359
+ readonly [PartTypeId]: PartTypeId = PartTypeId
360
+
361
+ constructor(props: any, options?: Schema.MakeOptions) {
362
+ super(props, options)
368
363
  }
369
364
  }
370
365
 
371
366
  /**
367
+ * Represents a part of a message containing the results of tool calls that the
368
+ * model requested invocation of.
369
+ *
372
370
  * @since 1.0.0
373
- * @category message
371
+ * @category Models
374
372
  */
375
- export declare namespace Message {
373
+ export class ToolCallResultPart extends Schema.TaggedClass<ToolCallResultPart>(
374
+ "@effect/ai/AiInput/ToolCallResultPart"
375
+ )("ToolCallResultPart", {
376
+ /**
377
+ * The identifier generated by a model when requesting a tool call.
378
+ */
379
+ id: ToolCallId,
380
+ /**
381
+ * The result of the tool call as a JSON-serializable object.
382
+ */
383
+ result: Schema.Unknown
384
+ }) {
376
385
  /**
377
386
  * @since 1.0.0
378
- * @category message
379
387
  */
380
- export type Input = string | Part | Iterable<Part>
388
+ readonly [PartTypeId]: PartTypeId = PartTypeId
381
389
  }
382
390
 
383
391
  /**
392
+ * The valid parts of a user message.
393
+ *
384
394
  * @since 1.0.0
385
- * @category constructors
395
+ * @category Models
386
396
  */
387
- export const make = (input: Input, options?: {
388
- readonly role?: AiRole.AiRole
389
- }): AiInput => {
390
- if (typeof input !== "string" && Predicate.isIterable(input)) {
391
- const chunk = Chunk.fromIterable(input as any)
392
- if (Chunk.isEmpty(chunk)) {
393
- return chunk as AiInput
394
- } else if (Message.is(Chunk.unsafeHead(chunk))) {
395
- return chunk as AiInput
396
- }
397
- return Chunk.of(Message.fromInput(chunk as any, options?.role))
398
- } else if (AiResponse.is(input)) {
399
- return Option.match(Message.fromResponse(input), {
400
- onNone: Chunk.empty,
401
- onSome: Chunk.of
402
- })
403
- } else if (WithResolved.is(input)) {
404
- return Chunk.of(Message.fromWithResolved(input))
405
- } else if (Message.is(input)) {
406
- return Chunk.of(input)
407
- }
408
- return Chunk.of(Message.fromInput(input, options?.role))
409
- }
397
+ export const UserMessagePart: Schema.Union<[
398
+ typeof TextPart,
399
+ typeof ImagePart,
400
+ typeof ImageUrlPart,
401
+ typeof FilePart,
402
+ typeof FileUrlPart
403
+ ]> = Schema.Union(
404
+ TextPart,
405
+ ImagePart,
406
+ ImageUrlPart,
407
+ FilePart,
408
+ FileUrlPart
409
+ )
410
410
 
411
411
  /**
412
412
  * @since 1.0.0
413
- * @category constructors
413
+ * @category Models
414
414
  */
415
- export const empty: AiInput = Chunk.empty()
415
+ export type UserMessagePart = typeof UserMessagePart.Type
416
416
 
417
417
  /**
418
+ * The valid parts of an assistant message.
419
+ *
418
420
  * @since 1.0.0
419
- * @category schemas
421
+ * @category Models
420
422
  */
421
- export const Schema: Schema_.Chunk<typeof Message> = Schema_.Chunk(Message)
423
+ export const AssistantMessagePart: Schema.Union<[
424
+ typeof TextPart,
425
+ typeof ReasoningPart,
426
+ typeof RedactedReasoningPart,
427
+ typeof ToolCallPart
428
+ ]> = Schema.Union(
429
+ TextPart,
430
+ ReasoningPart,
431
+ RedactedReasoningPart,
432
+ ToolCallPart
433
+ )
422
434
 
423
435
  /**
424
436
  * @since 1.0.0
425
- * @category schemas
437
+ * @category Models
426
438
  */
427
- export const SchemaJson: Schema_.Schema<Chunk.Chunk<Message>, string> = Schema_.parseJson(Schema)
439
+ export type AssistantMessagePart = typeof AssistantMessagePart.Type
428
440
 
429
441
  /**
442
+ * The valid parts of a tool message.
443
+ *
430
444
  * @since 1.0.0
431
- * @category models
445
+ * @category Models
432
446
  */
433
- export type Input =
434
- | string
435
- | Part
436
- | Iterable<Part>
437
- | Message
438
- | Iterable<Message>
439
- | AiResponse
440
- | WithResolved<unknown>
447
+ export const ToolMessagePart: typeof ToolCallResultPart = ToolCallResultPart
448
+
449
+ /**
450
+ * @since 1.0.0
451
+ * @category Models
452
+ */
453
+ export type ToolMessagePart = typeof ToolMessagePart.Type
454
+
455
+ /**
456
+ * @since 1.0.0
457
+ * @category Guards
458
+ */
459
+ export const is = (u: unknown): u is AiInput => Predicate.hasProperty(u, TypeId)
441
460
 
442
461
  /**
443
462
  * @since 1.0.0
444
- * @category models
463
+ * @category Guards
445
464
  */
446
- export type AiInput = Chunk.Chunk<Message>
465
+ export const isMessage = (u: unknown): u is Message => Predicate.hasProperty(u, MessageTypeId)
447
466
 
448
467
  /**
449
468
  * @since 1.0.0
450
- * @category system
469
+ * @category Guards
451
470
  */
452
- export class SystemInstruction extends Context.Tag("@effect/ai/AiInput/SystemInstruction")<
453
- SystemInstruction,
454
- string
455
- >() {}
471
+ export const isPart = (u: unknown): u is UserMessagePart | AssistantMessagePart | ToolMessagePart =>
472
+ Predicate.hasProperty(u, PartTypeId)
456
473
 
457
474
  /**
458
475
  * @since 1.0.0
459
- * @category system
476
+ * @category Constructors
460
477
  */
461
- export const provideSystem: {
478
+ export const empty: AiInput = new AiInput({ messages: [] })
479
+
480
+ /**
481
+ * Constructs a new `AiInput` from raw user input.
482
+ *
483
+ * @since 1.0.0
484
+ * @category Constructors
485
+ */
486
+ export const make = (input: Raw): AiInput => {
487
+ if (Predicate.isString(input)) {
488
+ const textPart = new TextPart({ text: input })
489
+ const message = new UserMessage({ parts: [textPart] })
490
+ return new AiInput({ messages: [message] })
491
+ }
492
+ if (isMessage(input)) {
493
+ return new AiInput({ messages: [input] })
494
+ }
495
+ if (Predicate.isIterable(input)) {
496
+ return new AiInput({ messages: Array.from(input) })
497
+ }
498
+ if (is(input)) {
499
+ return input
500
+ }
501
+ if (AiResponse.isStructured(input)) {
502
+ const assistantMessages = fromResponse(input).messages
503
+ const toolPart = new ToolCallResultPart({
504
+ id: input.id,
505
+ result: input.value
506
+ })
507
+ const toolMessage = new ToolMessage({ parts: [toolPart] })
508
+ return new AiInput({ messages: [...assistantMessages, toolMessage] })
509
+ }
510
+ if (AiResponse.hasToolCallResults(input)) {
511
+ const assistantMessages = fromResponse(input).messages
512
+ const toolParts: Array<ToolCallResultPart> = []
513
+ for (const [id, result] of input.encodedResults) {
514
+ toolParts.push(new ToolCallResultPart({ id, result }))
515
+ }
516
+ const toolMessage = new ToolMessage({ parts: toolParts })
517
+ return new AiInput({ messages: [...assistantMessages, toolMessage] })
518
+ }
519
+ return fromResponse(input)
520
+ }
521
+
522
+ const EXCLUDED_RESPONSE_PARTS: Array<AiResponse.Part["_tag"]> = [
523
+ "MetadataPart",
524
+ "ReasoningPart",
525
+ "RedactedReasoningPart",
526
+ "FinishPart"
527
+ ]
528
+
529
+ const validResponseParts = (part: AiResponse.Part): part is AiResponse.TextPart | AiResponse.ToolCallPart =>
530
+ !EXCLUDED_RESPONSE_PARTS.includes(part._tag)
531
+
532
+ const fromResponse = (
533
+ response: AiResponse.AiResponse
534
+ ): AiInput => {
535
+ if (response.parts.length === 0) {
536
+ return empty
537
+ }
538
+ const parts = response.parts
539
+ .filter(validResponseParts)
540
+ .map((part) =>
541
+ part._tag === "TextPart"
542
+ ? new TextPart({ text: part.text })
543
+ : new ToolCallPart({
544
+ id: part.id,
545
+ name: part.name,
546
+ params: part.params
547
+ })
548
+ )
549
+ const message = new AssistantMessage({ parts })
550
+ return new AiInput({ messages: [message] })
551
+ }
552
+
553
+ /**
554
+ * Concatenates the messages of one `AiInput` onto the messages of another,
555
+ * creating a new `AiInput` with the messages from both.
556
+ *
557
+ * @since 1.0.0
558
+ * @category Combination
559
+ */
560
+ export const concat: {
462
561
  /**
562
+ * Concatenates the messages of one `AiInput` onto the messages of another,
563
+ * creating a new `AiInput` with the messages from both.
564
+ *
463
565
  * @since 1.0.0
464
- * @category system
566
+ * @category Combination
465
567
  */
466
- (input: string): <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, SystemInstruction>>
568
+ (other: AiInput): (self: AiInput) => AiInput
467
569
  /**
570
+ * Concatenates the messages of one `AiInput` onto the messages of another,
571
+ * creating a new `AiInput` with the messages from both.
572
+ *
468
573
  * @since 1.0.0
469
- * @category system
574
+ * @category Combination
470
575
  */
471
- <A, E, R>(effect: Effect.Effect<A, E, R>, input: string): Effect.Effect<A, E, Exclude<R, SystemInstruction>>
472
- } = dual(
473
- 2,
474
- <A, E, R>(effect: Effect.Effect<A, E, R>, input: string): Effect.Effect<A, E, Exclude<R, SystemInstruction>> =>
475
- Effect.provideService(effect, SystemInstruction, input)
476
- )
576
+ (self: AiInput, other: AiInput): AiInput
577
+ } = dual<
578
+ /**
579
+ * Concatenates the messages of one `AiInput` onto the messages of another,
580
+ * creating a new `AiInput` with the messages from both.
581
+ *
582
+ * @since 1.0.0
583
+ * @category Combination
584
+ */
585
+ (other: AiInput) => (self: AiInput) => AiInput,
586
+ /**
587
+ * Concatenates the messages of one `AiInput` onto the messages of another,
588
+ * creating a new `AiInput` with the messages from both.
589
+ *
590
+ * @since 1.0.0
591
+ * @category Combination
592
+ */
593
+ (self: AiInput, other: AiInput) => AiInput
594
+ >(2, (self, other) =>
595
+ AiInput.make({
596
+ messages: [...self.messages, ...other.messages]
597
+ }))