@effect/ai 0.26.0 → 0.27.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 (188) hide show
  1. package/Chat/package.json +6 -0
  2. package/EmbeddingModel/package.json +6 -0
  3. package/IdGenerator/package.json +6 -0
  4. package/LanguageModel/package.json +6 -0
  5. package/Model/package.json +6 -0
  6. package/Prompt/package.json +6 -0
  7. package/Response/package.json +6 -0
  8. package/Telemetry/package.json +6 -0
  9. package/Tool/package.json +6 -0
  10. package/Toolkit/package.json +6 -0
  11. package/dist/cjs/AiError.js +575 -11
  12. package/dist/cjs/AiError.js.map +1 -1
  13. package/dist/cjs/Chat.js +302 -0
  14. package/dist/cjs/Chat.js.map +1 -0
  15. package/dist/cjs/EmbeddingModel.js +184 -0
  16. package/dist/cjs/EmbeddingModel.js.map +1 -0
  17. package/dist/cjs/IdGenerator.js +255 -0
  18. package/dist/cjs/IdGenerator.js.map +1 -0
  19. package/dist/cjs/LanguageModel.js +584 -0
  20. package/dist/cjs/LanguageModel.js.map +1 -0
  21. package/dist/cjs/McpServer.js +12 -4
  22. package/dist/cjs/McpServer.js.map +1 -1
  23. package/dist/cjs/Model.js +118 -0
  24. package/dist/cjs/Model.js.map +1 -0
  25. package/dist/cjs/Prompt.js +649 -0
  26. package/dist/cjs/Prompt.js.map +1 -0
  27. package/dist/cjs/Response.js +635 -0
  28. package/dist/cjs/Response.js.map +1 -0
  29. package/dist/cjs/Telemetry.js +176 -0
  30. package/dist/cjs/Telemetry.js.map +1 -0
  31. package/dist/cjs/Tokenizer.js +87 -8
  32. package/dist/cjs/Tokenizer.js.map +1 -1
  33. package/dist/cjs/Tool.js +556 -0
  34. package/dist/cjs/Tool.js.map +1 -0
  35. package/dist/cjs/Toolkit.js +279 -0
  36. package/dist/cjs/Toolkit.js.map +1 -0
  37. package/dist/cjs/index.js +21 -19
  38. package/dist/dts/AiError.d.ts +577 -9
  39. package/dist/dts/AiError.d.ts.map +1 -1
  40. package/dist/dts/Chat.d.ts +356 -0
  41. package/dist/dts/Chat.d.ts.map +1 -0
  42. package/dist/dts/EmbeddingModel.d.ts +153 -0
  43. package/dist/dts/EmbeddingModel.d.ts.map +1 -0
  44. package/dist/dts/IdGenerator.d.ts +272 -0
  45. package/dist/dts/IdGenerator.d.ts.map +1 -0
  46. package/dist/dts/LanguageModel.d.ts +458 -0
  47. package/dist/dts/LanguageModel.d.ts.map +1 -0
  48. package/dist/dts/McpSchema.d.ts +25 -25
  49. package/dist/dts/McpServer.d.ts +6 -4
  50. package/dist/dts/McpServer.d.ts.map +1 -1
  51. package/dist/dts/Model.d.ts +124 -0
  52. package/dist/dts/Model.d.ts.map +1 -0
  53. package/dist/dts/Prompt.d.ts +1119 -0
  54. package/dist/dts/Prompt.d.ts.map +1 -0
  55. package/dist/dts/Response.d.ts +1519 -0
  56. package/dist/dts/Response.d.ts.map +1 -0
  57. package/dist/dts/Telemetry.d.ts +520 -0
  58. package/dist/dts/Telemetry.d.ts.map +1 -0
  59. package/dist/dts/Tokenizer.d.ts +131 -13
  60. package/dist/dts/Tokenizer.d.ts.map +1 -1
  61. package/dist/dts/Tool.d.ts +876 -0
  62. package/dist/dts/Tool.d.ts.map +1 -0
  63. package/dist/dts/Toolkit.d.ts +310 -0
  64. package/dist/dts/Toolkit.d.ts.map +1 -0
  65. package/dist/dts/index.d.ts +498 -13
  66. package/dist/dts/index.d.ts.map +1 -1
  67. package/dist/esm/AiError.js +570 -10
  68. package/dist/esm/AiError.js.map +1 -1
  69. package/dist/esm/Chat.js +291 -0
  70. package/dist/esm/Chat.js.map +1 -0
  71. package/dist/esm/EmbeddingModel.js +173 -0
  72. package/dist/esm/EmbeddingModel.js.map +1 -0
  73. package/dist/esm/IdGenerator.js +245 -0
  74. package/dist/esm/IdGenerator.js.map +1 -0
  75. package/dist/esm/LanguageModel.js +572 -0
  76. package/dist/esm/LanguageModel.js.map +1 -0
  77. package/dist/esm/McpServer.js +12 -4
  78. package/dist/esm/McpServer.js.map +1 -1
  79. package/dist/esm/Model.js +108 -0
  80. package/dist/esm/Model.js.map +1 -0
  81. package/dist/esm/Prompt.js +633 -0
  82. package/dist/esm/Prompt.js.map +1 -0
  83. package/dist/esm/Response.js +619 -0
  84. package/dist/esm/Response.js.map +1 -0
  85. package/dist/esm/Telemetry.js +166 -0
  86. package/dist/esm/Telemetry.js.map +1 -0
  87. package/dist/esm/Tokenizer.js +87 -8
  88. package/dist/esm/Tokenizer.js.map +1 -1
  89. package/dist/esm/Tool.js +534 -0
  90. package/dist/esm/Tool.js.map +1 -0
  91. package/dist/esm/Toolkit.js +269 -0
  92. package/dist/esm/Toolkit.js.map +1 -0
  93. package/dist/esm/index.js +498 -13
  94. package/dist/esm/index.js.map +1 -1
  95. package/package.json +76 -68
  96. package/src/AiError.ts +739 -9
  97. package/src/Chat.ts +546 -0
  98. package/src/EmbeddingModel.ts +311 -0
  99. package/src/IdGenerator.ts +320 -0
  100. package/src/LanguageModel.ts +1074 -0
  101. package/src/McpServer.ts +337 -194
  102. package/src/Model.ts +155 -0
  103. package/src/Prompt.ts +1616 -0
  104. package/src/Response.ts +2131 -0
  105. package/src/Telemetry.ts +655 -0
  106. package/src/Tokenizer.ts +145 -24
  107. package/src/Tool.ts +1267 -0
  108. package/src/Toolkit.ts +516 -0
  109. package/src/index.ts +499 -13
  110. package/AiChat/package.json +0 -6
  111. package/AiEmbeddingModel/package.json +0 -6
  112. package/AiInput/package.json +0 -6
  113. package/AiLanguageModel/package.json +0 -6
  114. package/AiModel/package.json +0 -6
  115. package/AiResponse/package.json +0 -6
  116. package/AiTelemetry/package.json +0 -6
  117. package/AiTool/package.json +0 -6
  118. package/AiToolkit/package.json +0 -6
  119. package/dist/cjs/AiChat.js +0 -122
  120. package/dist/cjs/AiChat.js.map +0 -1
  121. package/dist/cjs/AiEmbeddingModel.js +0 -109
  122. package/dist/cjs/AiEmbeddingModel.js.map +0 -1
  123. package/dist/cjs/AiInput.js +0 -458
  124. package/dist/cjs/AiInput.js.map +0 -1
  125. package/dist/cjs/AiLanguageModel.js +0 -351
  126. package/dist/cjs/AiLanguageModel.js.map +0 -1
  127. package/dist/cjs/AiModel.js +0 -37
  128. package/dist/cjs/AiModel.js.map +0 -1
  129. package/dist/cjs/AiResponse.js +0 -681
  130. package/dist/cjs/AiResponse.js.map +0 -1
  131. package/dist/cjs/AiTelemetry.js +0 -58
  132. package/dist/cjs/AiTelemetry.js.map +0 -1
  133. package/dist/cjs/AiTool.js +0 -150
  134. package/dist/cjs/AiTool.js.map +0 -1
  135. package/dist/cjs/AiToolkit.js +0 -157
  136. package/dist/cjs/AiToolkit.js.map +0 -1
  137. package/dist/cjs/internal/common.js +0 -21
  138. package/dist/cjs/internal/common.js.map +0 -1
  139. package/dist/dts/AiChat.d.ts +0 -101
  140. package/dist/dts/AiChat.d.ts.map +0 -1
  141. package/dist/dts/AiEmbeddingModel.d.ts +0 -65
  142. package/dist/dts/AiEmbeddingModel.d.ts.map +0 -1
  143. package/dist/dts/AiInput.d.ts +0 -590
  144. package/dist/dts/AiInput.d.ts.map +0 -1
  145. package/dist/dts/AiLanguageModel.d.ts +0 -302
  146. package/dist/dts/AiLanguageModel.d.ts.map +0 -1
  147. package/dist/dts/AiModel.d.ts +0 -25
  148. package/dist/dts/AiModel.d.ts.map +0 -1
  149. package/dist/dts/AiResponse.d.ts +0 -863
  150. package/dist/dts/AiResponse.d.ts.map +0 -1
  151. package/dist/dts/AiTelemetry.d.ts +0 -242
  152. package/dist/dts/AiTelemetry.d.ts.map +0 -1
  153. package/dist/dts/AiTool.d.ts +0 -334
  154. package/dist/dts/AiTool.d.ts.map +0 -1
  155. package/dist/dts/AiToolkit.d.ts +0 -96
  156. package/dist/dts/AiToolkit.d.ts.map +0 -1
  157. package/dist/dts/internal/common.d.ts +0 -2
  158. package/dist/dts/internal/common.d.ts.map +0 -1
  159. package/dist/esm/AiChat.js +0 -111
  160. package/dist/esm/AiChat.js.map +0 -1
  161. package/dist/esm/AiEmbeddingModel.js +0 -98
  162. package/dist/esm/AiEmbeddingModel.js.map +0 -1
  163. package/dist/esm/AiInput.js +0 -433
  164. package/dist/esm/AiInput.js.map +0 -1
  165. package/dist/esm/AiLanguageModel.js +0 -340
  166. package/dist/esm/AiLanguageModel.js.map +0 -1
  167. package/dist/esm/AiModel.js +0 -29
  168. package/dist/esm/AiModel.js.map +0 -1
  169. package/dist/esm/AiResponse.js +0 -657
  170. package/dist/esm/AiResponse.js.map +0 -1
  171. package/dist/esm/AiTelemetry.js +0 -48
  172. package/dist/esm/AiTelemetry.js.map +0 -1
  173. package/dist/esm/AiTool.js +0 -134
  174. package/dist/esm/AiTool.js.map +0 -1
  175. package/dist/esm/AiToolkit.js +0 -147
  176. package/dist/esm/AiToolkit.js.map +0 -1
  177. package/dist/esm/internal/common.js +0 -14
  178. package/dist/esm/internal/common.js.map +0 -1
  179. package/src/AiChat.ts +0 -251
  180. package/src/AiEmbeddingModel.ts +0 -169
  181. package/src/AiInput.ts +0 -602
  182. package/src/AiLanguageModel.ts +0 -685
  183. package/src/AiModel.ts +0 -53
  184. package/src/AiResponse.ts +0 -986
  185. package/src/AiTelemetry.ts +0 -333
  186. package/src/AiTool.ts +0 -579
  187. package/src/AiToolkit.ts +0 -265
  188. package/src/internal/common.ts +0 -12
package/src/AiError.ts CHANGED
@@ -1,45 +1,775 @@
1
1
  /**
2
+ * The `AiError` module provides comprehensive error handling for AI operations.
3
+ *
4
+ * This module defines a hierarchy of error types that can occur when working
5
+ * with AI services, including HTTP request/response errors, input/output
6
+ * validation errors, and general runtime errors. All errors follow Effect's
7
+ * structured error patterns and provide detailed context for debugging.
8
+ *
9
+ * ## Error Types
10
+ *
11
+ * - **HttpRequestError**: Errors occurring during HTTP request processing
12
+ * - **HttpResponseError**: Errors occurring during HTTP response processing
13
+ * - **MalformedInput**: Errors when input data doesn't match expected format
14
+ * - **MalformedOutput**: Errors when output data can't be parsed or validated
15
+ * - **UnknownError**: Catch-all for unexpected runtime errors
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * import { AiError } from "@effect/ai"
20
+ * import { Effect, Match } from "effect"
21
+ *
22
+ * const handleAiError = Match.type<AiError.AiError>().pipe(
23
+ * Match.tag("HttpRequestError", (err) =>
24
+ * Effect.logError(`Request failed: ${err.message}`)
25
+ * ),
26
+ * Match.tag("HttpResponseError", (err) =>
27
+ * Effect.logError(`Response error (${err.response.status}): ${err.message}`)
28
+ * ),
29
+ * Match.tag("MalformedInput", (err) =>
30
+ * Effect.logError(`Invalid input: ${err.message}`)
31
+ * ),
32
+ * Match.tag("MalformedOutput", (err) =>
33
+ * Effect.logError(`Invalid output: ${err.message}`)
34
+ * ),
35
+ * Match.orElse((err) =>
36
+ * Effect.logError(`Unknown error: ${err.message}`)
37
+ * )
38
+ * )
39
+ * ```
40
+ *
41
+ * @example
42
+ * ```ts
43
+ * import { AiError } from "@effect/ai"
44
+ * import { Effect, Option } from "effect"
45
+ *
46
+ * const aiOperation = Effect.gen(function* () {
47
+ * // Some AI operation that might fail
48
+ * return yield* new AiError.HttpRequestError({
49
+ * module: "OpenAI",
50
+ * method: "completion",
51
+ * reason: "Transport",
52
+ * request: {
53
+ * method: "POST",
54
+ * url: "https://api.openai.com/v1/completions",
55
+ * urlParams: [],
56
+ * hash: Option.none(),
57
+ * headers: { "Authorization": "Bearer ***" }
58
+ * }
59
+ * })
60
+ * })
61
+ *
62
+ * const program = aiOperation.pipe(
63
+ * Effect.catchTag("HttpRequestError", (error) => {
64
+ * console.log("Request failed:", error.message)
65
+ * return Effect.succeed("fallback response")
66
+ * })
67
+ * )
68
+ * ```
69
+ *
2
70
  * @since 1.0.0
3
71
  */
72
+ import type * as HttpClientError from "@effect/platform/HttpClientError"
73
+ import type { ParseError } from "effect/ParseResult"
4
74
  import * as Predicate from "effect/Predicate"
5
75
  import * as Schema from "effect/Schema"
6
76
 
7
77
  /**
78
+ * Unique identifier for AI errors.
79
+ *
8
80
  * @since 1.0.0
9
- * @category type ids
81
+ * @category Type Ids
10
82
  */
11
- export const TypeId: unique symbol = Symbol.for("@effect/ai/AiError")
83
+ export const TypeId = "~@effect/ai/AiError"
12
84
 
13
85
  /**
86
+ * Type-level representation of the AI error identifier.
87
+ *
14
88
  * @since 1.0.0
15
- * @category type ids
89
+ * @category Type Ids
16
90
  */
17
91
  export type TypeId = typeof TypeId
18
92
 
19
93
  /**
94
+ * Type guard to check if a value is an AI error.
95
+ *
96
+ * @param u - The value to check
97
+ * @returns `true` if the value is an `AiError`, `false` otherwise
98
+ *
99
+ * @example
100
+ * ```ts
101
+ * import { AiError } from "@effect/ai"
102
+ *
103
+ * const someError = new Error("generic error")
104
+ * const aiError = new AiError.UnknownError({
105
+ * module: "Test",
106
+ * method: "example"
107
+ * })
108
+ *
109
+ * console.log(AiError.isAiError(someError)) // false
110
+ * console.log(AiError.isAiError(aiError)) // true
111
+ * ```
112
+ *
20
113
  * @since 1.0.0
21
- * @category errors
114
+ * @category Guards
22
115
  */
23
- export class AiError extends Schema.TaggedError<AiError>("@effect/ai/AiError")("AiError", {
116
+ export const isAiError = (u: unknown): u is AiError => Predicate.hasProperty(u, TypeId)
117
+
118
+ // =============================================================================
119
+ // Http Request Error
120
+ // =============================================================================
121
+
122
+ /**
123
+ * Schema for HTTP request details used in error reporting.
124
+ *
125
+ * Captures comprehensive information about HTTP requests that failed,
126
+ * enabling detailed error analysis and debugging.
127
+ *
128
+ * @example
129
+ * ```ts
130
+ * import { AiError } from "@effect/ai"
131
+ * import { Option } from "effect"
132
+ *
133
+ * const requestDetails: typeof AiError.HttpRequestDetails.Type = {
134
+ * method: "POST",
135
+ * url: "https://api.openai.com/v1/completions",
136
+ * urlParams: [["model", "gpt-4"], ["stream", "false"]],
137
+ * hash: Option.some("#section1"),
138
+ * headers: {
139
+ * "Content-Type": "application/json",
140
+ * "Authorization": "Bearer sk-..."
141
+ * }
142
+ * }
143
+ * ```
144
+ *
145
+ * @since 1.0.0
146
+ * @category Schemas
147
+ */
148
+ export const HttpRequestDetails = Schema.Struct({
149
+ method: Schema.Literal("GET", "POST", "PATCH", "PUT", "DELETE", "HEAD", "OPTIONS"),
150
+ url: Schema.String,
151
+ urlParams: Schema.Array(Schema.Tuple(Schema.String, Schema.String)),
152
+ hash: Schema.Option(Schema.String),
153
+ headers: Schema.Record({ key: Schema.String, value: Schema.String })
154
+ }).annotations({ identifier: "HttpRequestDetails" })
155
+
156
+ /**
157
+ * Error that occurs during HTTP request processing.
158
+ *
159
+ * This error is raised when issues arise before receiving an HTTP response,
160
+ * such as network connectivity problems, request encoding issues, or invalid
161
+ * URLs.
162
+ *
163
+ * @example
164
+ * ```ts
165
+ * import { AiError } from "@effect/ai"
166
+ * import { Effect } from "effect"
167
+ *
168
+ * const handleNetworkError = Effect.gen(function* () {
169
+ * const error = new AiError.HttpRequestError({
170
+ * module: "OpenAI",
171
+ * method: "createCompletion",
172
+ * reason: "Transport",
173
+ * request: {
174
+ * method: "POST",
175
+ * url: "https://api.openai.com/v1/completions",
176
+ * urlParams: [],
177
+ * hash: Option.none(),
178
+ * headers: { "Content-Type": "application/json" }
179
+ * },
180
+ * description: "Connection timeout after 30 seconds"
181
+ * })
182
+ *
183
+ * console.log(error.message)
184
+ * // "Transport: Connection timeout after 30 seconds (POST https://api.openai.com/v1/completions)"
185
+ * })
186
+ * ```
187
+ *
188
+ * @since 1.0.0
189
+ * @category Errors
190
+ */
191
+ export class HttpRequestError extends Schema.TaggedError<HttpRequestError>(
192
+ "@effect/ai/AiError/HttpRequestError"
193
+ )("HttpRequestError", {
194
+ module: Schema.String,
195
+ method: Schema.String,
196
+ reason: Schema.Literal("Transport", "Encode", "InvalidUrl"),
197
+ request: HttpRequestDetails,
198
+ description: Schema.optional(Schema.String),
199
+ cause: Schema.optional(Schema.Defect)
200
+ }) {
201
+ /**
202
+ * @since 1.0.0
203
+ */
204
+ readonly [TypeId]: TypeId = TypeId
205
+
206
+ /**
207
+ * Creates an HttpRequestError from a platform HttpClientError.RequestError.
208
+ *
209
+ * @example
210
+ * ```ts
211
+ * import { AiError } from "@effect/ai"
212
+ * import { HttpClientError } from "@effect/platform"
213
+ * import { Option } from "effect"
214
+ *
215
+ * declare const platformError: HttpClientError.RequestError
216
+ *
217
+ * const aiError = AiError.HttpRequestError.fromRequestError({
218
+ * module: "ChatGPT",
219
+ * method: "sendMessage",
220
+ * error: platformError
221
+ * })
222
+ * ```
223
+ *
224
+ * @since 1.0.0
225
+ * @category Constructors
226
+ */
227
+ static fromRequestError({ error, ...params }: {
228
+ readonly module: string
229
+ readonly method: string
230
+ readonly error: HttpClientError.RequestError
231
+ }): HttpRequestError {
232
+ return new HttpRequestError({
233
+ ...params,
234
+ cause: error,
235
+ description: error.description,
236
+ reason: error.reason,
237
+ request: {
238
+ hash: error.request.hash,
239
+ headers: error.request.headers,
240
+ method: error.request.method,
241
+ url: error.request.url,
242
+ urlParams: error.request.urlParams
243
+ }
244
+ })
245
+ }
246
+
247
+ get message(): string {
248
+ const methodAndUrl = `${this.request.method} ${this.request.url}`
249
+
250
+ let baseMessage = this.description
251
+ ? `${this.reason}: ${this.description}`
252
+ : `${this.reason}: An HTTP request error occurred.`
253
+
254
+ baseMessage += ` (${methodAndUrl})`
255
+
256
+ let suggestion = ""
257
+ switch (this.reason) {
258
+ case "Encode": {
259
+ suggestion += "Check that the request body data is properly formatted and matches the expected content type."
260
+ break
261
+ }
262
+
263
+ case "InvalidUrl": {
264
+ suggestion += "Verify that the URL format is correct and that all required parameters have been provided."
265
+ suggestion += " Check for any special characters that may need encoding."
266
+ break
267
+ }
268
+
269
+ case "Transport": {
270
+ suggestion += "Check your network connection and verify that the requested URL is accessible."
271
+ break
272
+ }
273
+ }
274
+
275
+ baseMessage += `\n\nSuggestion: ${suggestion}`
276
+
277
+ return baseMessage
278
+ }
279
+ }
280
+
281
+ // =============================================================================
282
+ // Http Response Error
283
+ // =============================================================================
284
+
285
+ /**
286
+ * Schema for HTTP response details used in error reporting.
287
+ *
288
+ * Captures essential information about HTTP responses that caused errors,
289
+ * including status codes and headers for debugging purposes.
290
+ *
291
+ * @example
292
+ * ```ts
293
+ * import { AiError } from "@effect/ai"
294
+ *
295
+ * const responseDetails: typeof AiError.HttpResponseDetails.Type = {
296
+ * status: 429,
297
+ * headers: {
298
+ * "Content-Type": "application/json",
299
+ * "X-RateLimit-Remaining": "0",
300
+ * "Retry-After": "60"
301
+ * }
302
+ * }
303
+ * ```
304
+ *
305
+ * @since 1.0.0
306
+ * @category Schemas
307
+ */
308
+ export const HttpResponseDetails = Schema.Struct({
309
+ status: Schema.Number,
310
+ headers: Schema.Record({ key: Schema.String, value: Schema.String })
311
+ }).annotations({ identifier: "HttpResponseDetails" })
312
+
313
+ /**
314
+ * Error that occurs during HTTP response processing.
315
+ *
316
+ * This error is thrown when issues arise after receiving an HTTP response,
317
+ * such as unexpected status codes, response decoding failures, or empty
318
+ * response bodies.
319
+ *
320
+ * @example
321
+ * ```ts
322
+ * import { AiError } from "@effect/ai"
323
+ * import { Option } from "effect"
324
+ *
325
+ * const responseError = new AiError.HttpResponseError({
326
+ * module: "OpenAI",
327
+ * method: "createCompletion",
328
+ * reason: "StatusCode",
329
+ * request: {
330
+ * method: "POST",
331
+ * url: "https://api.openai.com/v1/completions",
332
+ * urlParams: [],
333
+ * hash: Option.none(),
334
+ * headers: { "Authorization": "Bearer sk-..." }
335
+ * },
336
+ * response: {
337
+ * status: 429,
338
+ * headers: { "X-RateLimit-Remaining": "0" }
339
+ * },
340
+ * description: "Rate limit exceeded"
341
+ * })
342
+ *
343
+ * console.log(responseError.message)
344
+ * // "StatusCode: Rate limit exceeded (429 POST https://api.openai.com/v1/completions)"
345
+ * ```
346
+ *
347
+ * @since 1.0.0
348
+ * @category Errors
349
+ */
350
+ export class HttpResponseError extends Schema.TaggedError<HttpResponseError>(
351
+ "@effect/ai/AiError/HttpResponseError"
352
+ )("HttpResponseError", {
353
+ module: Schema.String,
354
+ method: Schema.String,
355
+ request: HttpRequestDetails,
356
+ response: HttpResponseDetails,
357
+ reason: Schema.Literal("StatusCode", "Decode", "EmptyBody"),
358
+ description: Schema.optional(Schema.String),
359
+ cause: Schema.optional(Schema.Defect)
360
+ }) {
361
+ /**
362
+ * @since 1.0.0
363
+ */
364
+ readonly [TypeId]: TypeId = TypeId
365
+
366
+ /**
367
+ * Creates an HttpResponseError from a platform HttpClientError.ResponseError.
368
+ *
369
+ * @example
370
+ * ```ts
371
+ * import { AiError } from "@effect/ai"
372
+ * import { Headers, HttpClientError } from "@effect/platform"
373
+ * import { Option } from "effect"
374
+ *
375
+ * declare const platformError: HttpClientError.ResponseError
376
+ *
377
+ * const aiError = AiError.HttpResponseError.fromResponseError({
378
+ * module: "OpenAI",
379
+ * method: "completion",
380
+ * error: platformError
381
+ * })
382
+ * ```
383
+ *
384
+ * @since 1.0.0
385
+ * @category Constructors
386
+ */
387
+ static fromResponseError({ error, ...params }: {
388
+ readonly module: string
389
+ readonly method: string
390
+ readonly error: HttpClientError.ResponseError
391
+ }): HttpResponseError {
392
+ return new HttpResponseError({
393
+ ...params,
394
+ cause: error,
395
+ description: error.description,
396
+ reason: error.reason,
397
+ request: {
398
+ hash: error.request.hash,
399
+ headers: error.request.headers,
400
+ method: error.request.method,
401
+ url: error.request.url,
402
+ urlParams: error.request.urlParams
403
+ },
404
+ response: {
405
+ headers: error.response.headers,
406
+ status: error.response.status
407
+ }
408
+ })
409
+ }
410
+
411
+ get message(): string {
412
+ const methodUrlStatus = `${this.response.status} ${this.request.method} ${this.request.url}`
413
+
414
+ let baseMessage = this.description
415
+ ? `${this.reason}: ${this.description}`
416
+ : `${this.reason}: An HTTP response error occurred.`
417
+
418
+ baseMessage += ` (${methodUrlStatus})`
419
+
420
+ let suggestion = ""
421
+ switch (this.reason) {
422
+ case "Decode": {
423
+ suggestion += "The response format does not match what is expected. " +
424
+ "Verify API version compatibility, check response content-type, " +
425
+ "and/or examine if the endpoint schema has changed."
426
+ break
427
+ }
428
+ case "EmptyBody": {
429
+ suggestion += "The response body was empty. This may indicate a server " +
430
+ "issue, API version mismatch, or the endpoint may have changed its response format."
431
+ break
432
+ }
433
+ case "StatusCode": {
434
+ suggestion += getStatusCodeSuggestion(this.response.status)
435
+ break
436
+ }
437
+ }
438
+
439
+ baseMessage += `\n\nSuggestion: ${suggestion}`
440
+
441
+ return baseMessage
442
+ }
443
+ }
444
+
445
+ // =============================================================================
446
+ // Malformed Input Error
447
+ // =============================================================================
448
+
449
+ /**
450
+ * Error thrown when input data doesn't match the expected format or schema.
451
+ *
452
+ * This error occurs when the data provided to an AI operation fails validation,
453
+ * is missing required fields, or doesn't conform to the expected structure.
454
+ *
455
+ * @example
456
+ * ```ts
457
+ * import { AiError } from "@effect/ai"
458
+ * import { Effect } from "effect"
459
+ *
460
+ * const validateInput = (data: unknown) =>
461
+ * typeof data === "string" && data.length > 0
462
+ * ? Effect.succeed(data)
463
+ * : Effect.fail(new AiError.MalformedInput({
464
+ * module: "ChatBot",
465
+ * method: "processMessage",
466
+ * description: "Input must be a non-empty string"
467
+ * }))
468
+ *
469
+ * const program = validateInput("").pipe(
470
+ * Effect.catchTag("MalformedInput", (error) => {
471
+ * console.log(`Input validation failed: ${error.description}`)
472
+ * return Effect.succeed("Please provide a valid message")
473
+ * })
474
+ * )
475
+ * ```
476
+ *
477
+ * @since 1.0.0
478
+ * @category Errors
479
+ */
480
+ export class MalformedInput extends Schema.TaggedError<MalformedInput>(
481
+ "@effect/ai/AiError/MalformedInput"
482
+ )("MalformedInput", {
483
+ module: Schema.String,
484
+ method: Schema.String,
485
+ description: Schema.optional(Schema.String),
486
+ cause: Schema.optional(Schema.Defect)
487
+ }) {
488
+ /**
489
+ * @since 1.0.0
490
+ */
491
+ readonly [TypeId]: TypeId = TypeId
492
+ }
493
+
494
+ // =============================================================================
495
+ // Malformed Output Error
496
+ // =============================================================================
497
+
498
+ /**
499
+ * Error thrown when output data can't be parsed or validated.
500
+ *
501
+ * This error occurs when AI service responses don't match the expected format,
502
+ * contain invalid data structures, or fail schema validation during parsing.
503
+ *
504
+ * @example
505
+ * ```ts
506
+ * import { AiError } from "@effect/ai"
507
+ * import { Effect, Schema } from "effect"
508
+ *
509
+ * const ResponseSchema = Schema.Struct({
510
+ * message: Schema.String,
511
+ * tokens: Schema.Number
512
+ * })
513
+ *
514
+ * const parseResponse = (data: unknown) =>
515
+ * Schema.decodeUnknown(ResponseSchema)(data).pipe(
516
+ * Effect.mapError(parseError =>
517
+ * new AiError.MalformedOutput({
518
+ * module: "OpenAI",
519
+ * method: "completion",
520
+ * description: "Response doesn't match expected schema",
521
+ * cause: parseError
522
+ * })
523
+ * )
524
+ * )
525
+ *
526
+ * const program = parseResponse({ invalid: "data" }).pipe(
527
+ * Effect.catchTag("MalformedOutput", (error) => {
528
+ * console.log(`Parsing failed: ${error.description}`)
529
+ * return Effect.succeed({ message: "Error", tokens: 0 })
530
+ * })
531
+ * )
532
+ * ```
533
+ *
534
+ * @since 1.0.0
535
+ * @category Errors
536
+ */
537
+ export class MalformedOutput extends Schema.TaggedError<MalformedOutput>(
538
+ "@effect/ai/AiError/MalformedOutput"
539
+ )("MalformedOutput", {
24
540
  module: Schema.String,
25
541
  method: Schema.String,
26
- description: Schema.String,
542
+ description: Schema.optional(Schema.String),
27
543
  cause: Schema.optional(Schema.Defect)
28
544
  }) {
29
545
  /**
30
546
  * @since 1.0.0
31
547
  */
32
- static is(u: unknown): u is AiError {
33
- return Predicate.hasProperty(u, TypeId)
548
+ readonly [TypeId]: TypeId = TypeId
549
+
550
+ /**
551
+ * Creates a MalformedOutput error from a Schema ParseError.
552
+ *
553
+ * @example
554
+ * ```ts
555
+ * import { AiError } from "@effect/ai"
556
+ * import { Effect, Schema } from "effect"
557
+ *
558
+ * const UserSchema = Schema.Struct({
559
+ * name: Schema.String,
560
+ * age: Schema.Number
561
+ * })
562
+ *
563
+ * const parseUser = (data: unknown) =>
564
+ * Schema.decodeUnknown(UserSchema)(data).pipe(
565
+ * Effect.mapError((parseError) =>
566
+ * AiError.MalformedOutput.fromParseError({
567
+ * module: "UserService",
568
+ * method: "parseUserData",
569
+ * error: parseError
570
+ * })
571
+ * )
572
+ * )
573
+ * ```
574
+ *
575
+ * @since 1.0.0
576
+ * @category Constructors
577
+ */
578
+ static fromParseError({ error, ...params }: {
579
+ readonly module: string
580
+ readonly method: string
581
+ readonly error: ParseError
582
+ }): MalformedOutput {
583
+ // TODO(Max): enhance
584
+ return new MalformedOutput({
585
+ ...params,
586
+ cause: error
587
+ })
34
588
  }
589
+ }
590
+
591
+ // =============================================================================
592
+ // Unknown Error
593
+ // =============================================================================
594
+
595
+ /**
596
+ * Catch-all error for unexpected runtime errors in AI operations.
597
+ *
598
+ * This error is used when an unexpected exception occurs that doesn't fit
599
+ * into the other specific error categories. It provides context about where
600
+ * the error occurred and preserves the original cause for debugging.
601
+ *
602
+ * @example
603
+ * ```ts
604
+ * import { AiError } from "@effect/ai"
605
+ * import { Effect } from "effect"
606
+ *
607
+ * const riskyOperation = () => {
608
+ * try {
609
+ * // Some operation that might throw
610
+ * throw new Error("Unexpected network issue")
611
+ * } catch (cause) {
612
+ * return Effect.fail(new AiError.UnknownError({
613
+ * module: "ChatService",
614
+ * method: "sendMessage",
615
+ * description: "An unexpected error occurred during message processing",
616
+ * cause
617
+ * }))
618
+ * }
619
+ * }
620
+ *
621
+ * const program = riskyOperation().pipe(
622
+ * Effect.catchTag("UnknownError", (error) => {
623
+ * console.log(error.message)
624
+ * // "ChatService.sendMessage: An unexpected error occurred during message processing"
625
+ * return Effect.succeed("Service temporarily unavailable")
626
+ * })
627
+ * )
628
+ * ```
629
+ *
630
+ * @since 1.0.0
631
+ * @category Errors
632
+ */
633
+ export class UnknownError extends Schema.TaggedError<UnknownError>(
634
+ "@effect/ai/UnknownError"
635
+ )("UnknownError", {
636
+ module: Schema.String,
637
+ method: Schema.String,
638
+ description: Schema.optional(Schema.String),
639
+ cause: Schema.optional(Schema.Defect)
640
+ }) {
35
641
  /**
36
642
  * @since 1.0.0
37
643
  */
38
644
  readonly [TypeId]: TypeId = TypeId
645
+
39
646
  /**
40
647
  * @since 1.0.0
41
648
  */
42
649
  get message(): string {
43
- return `${this.module}.${this.method}: ${this.description}`
650
+ const moduleMethod = `${this.module}.${this.method}`
651
+ return Predicate.isUndefined(this.description)
652
+ ? `${moduleMethod}: An error occurred`
653
+ : `${moduleMethod}: ${this.description}`
654
+ }
655
+ }
656
+
657
+ // =============================================================================
658
+ // AiError
659
+ // =============================================================================
660
+
661
+ /**
662
+ * Union type representing all possible AI operation errors.
663
+ *
664
+ * This type encompasses all error cases that can occur during AI operations,
665
+ * providing a comprehensive error handling surface for applications.
666
+ *
667
+ * @example
668
+ * ```ts
669
+ * import { AiError } from "@effect/ai"
670
+ * import { Effect, Match } from "effect"
671
+ *
672
+ * const handleAnyAiError = Match.type<AiError.AiError>().pipe(
673
+ * Match.tag("HttpRequestError", (err) =>
674
+ * `Network error: ${err.reason}`
675
+ * ),
676
+ * Match.tag("HttpResponseError", (err) =>
677
+ * `Server error: HTTP ${err.response.status}`
678
+ * ),
679
+ * Match.tag("MalformedInput", (err) =>
680
+ * `Invalid input: ${err.description || "Data validation failed"}`
681
+ * ),
682
+ * Match.tag("MalformedOutput", (err) =>
683
+ * `Invalid response: ${err.description || "Response parsing failed"}`
684
+ * ),
685
+ * Match.orElse((err) =>
686
+ * `Unknown error: ${err.message}`
687
+ * )
688
+ * )
689
+ * ```
690
+ *
691
+ * @since 1.0.0
692
+ * @category Models
693
+ */
694
+ export type AiError =
695
+ | HttpRequestError
696
+ | HttpResponseError
697
+ | MalformedInput
698
+ | MalformedOutput
699
+ | UnknownError
700
+
701
+ /**
702
+ * Schema for validating and parsing AI errors.
703
+ *
704
+ * This schema can be used to decode unknown values into properly typed AI
705
+ * errors, ensuring type safety when handling errors from external sources or
706
+ * serialized data.
707
+ *
708
+ * @example
709
+ * ```ts
710
+ * import { AiError } from "@effect/ai"
711
+ * import { Schema, Effect } from "effect"
712
+ *
713
+ * const parseAiError = (data: unknown) =>
714
+ * Schema.decodeUnknown(AiError.AiError)(data).pipe(
715
+ * Effect.map(error => {
716
+ * console.log(`Parsed AI error: ${error._tag}`)
717
+ * return error
718
+ * }),
719
+ * Effect.catchAll(() =>
720
+ * Effect.succeed(new AiError.UnknownError({
721
+ * module: "Parser",
722
+ * method: "parseAiError",
723
+ * description: "Failed to parse error data"
724
+ * }))
725
+ * )
726
+ * )
727
+ * ```
728
+ *
729
+ * @since 1.0.0
730
+ * @category Schemas
731
+ */
732
+ export const AiError: Schema.Union<[
733
+ typeof HttpRequestError,
734
+ typeof HttpResponseError,
735
+ typeof MalformedInput,
736
+ typeof MalformedOutput,
737
+ typeof UnknownError
738
+ ]> = Schema.Union(
739
+ HttpRequestError,
740
+ HttpResponseError,
741
+ MalformedInput,
742
+ MalformedOutput,
743
+ UnknownError
744
+ )
745
+
746
+ // =============================================================================
747
+ // Utilities
748
+ // =============================================================================
749
+
750
+ const getStatusCodeSuggestion = (statusCode: number): string => {
751
+ if (statusCode >= 400 && statusCode < 500) {
752
+ switch (statusCode) {
753
+ case 400:
754
+ return "Bad Request - Check request parameters, headers, and body format against API documentation."
755
+ case 401:
756
+ return "Unauthorized - Verify API key, authentication credentials, or token expiration."
757
+ case 403:
758
+ return "Forbidden - Check API permissions, usage limits, or resource access rights."
759
+ case 404:
760
+ return "Not Found - Verify the endpoint URL, API version, and resource identifiers."
761
+ case 408:
762
+ return "Request Timeout - Consider increasing timeout duration or implementing retry logic."
763
+ case 422:
764
+ return "Unprocessable Entity - Check request data validation, required fields, and data formats."
765
+ case 429:
766
+ return "Rate Limited - Implement exponential backoff or reduce request frequency."
767
+ default:
768
+ return "Client error - Review request format, parameters, and API documentation."
769
+ }
770
+ } else if (statusCode >= 500) {
771
+ return "Server error - This is likely temporary. Implement retry logic with exponential backoff."
772
+ } else {
773
+ return "Check API documentation for this status code."
44
774
  }
45
775
  }