@effect/ai-openai 0.37.2 → 4.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/dist/Generated.d.ts +70887 -0
  2. package/dist/Generated.d.ts.map +1 -0
  3. package/dist/Generated.js +4 -0
  4. package/dist/Generated.js.map +1 -0
  5. package/dist/OpenAiClient.d.ts +124 -0
  6. package/dist/OpenAiClient.d.ts.map +1 -0
  7. package/dist/OpenAiClient.js +128 -0
  8. package/dist/OpenAiClient.js.map +1 -0
  9. package/dist/{dts/OpenAiConfig.d.ts → OpenAiConfig.d.ts} +9 -9
  10. package/dist/OpenAiConfig.d.ts.map +1 -0
  11. package/dist/{esm/OpenAiConfig.js → OpenAiConfig.js} +8 -5
  12. package/dist/OpenAiConfig.js.map +1 -0
  13. package/dist/OpenAiError.d.ts +98 -0
  14. package/dist/OpenAiError.d.ts.map +1 -0
  15. package/dist/OpenAiError.js +10 -0
  16. package/dist/OpenAiError.js.map +1 -0
  17. package/dist/OpenAiLanguageModel.d.ts +318 -0
  18. package/dist/OpenAiLanguageModel.d.ts.map +1 -0
  19. package/dist/OpenAiLanguageModel.js +2207 -0
  20. package/dist/OpenAiLanguageModel.js.map +1 -0
  21. package/dist/{dts/OpenAiTelemetry.d.ts → OpenAiTelemetry.d.ts} +31 -13
  22. package/dist/OpenAiTelemetry.d.ts.map +1 -0
  23. package/dist/{esm/OpenAiTelemetry.js → OpenAiTelemetry.js} +11 -6
  24. package/dist/OpenAiTelemetry.js.map +1 -0
  25. package/dist/OpenAiTool.d.ts +479 -0
  26. package/dist/OpenAiTool.d.ts.map +1 -0
  27. package/dist/OpenAiTool.js +231 -0
  28. package/dist/OpenAiTool.js.map +1 -0
  29. package/dist/index.d.ts +58 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +59 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/internal/errors.d.ts +2 -0
  34. package/dist/internal/errors.d.ts.map +1 -0
  35. package/dist/internal/errors.js +316 -0
  36. package/dist/internal/errors.js.map +1 -0
  37. package/dist/{dts/internal → internal}/utilities.d.ts.map +1 -1
  38. package/dist/{esm/internal → internal}/utilities.js +4 -3
  39. package/dist/internal/utilities.js.map +1 -0
  40. package/package.json +45 -97
  41. package/src/Generated.ts +28521 -20036
  42. package/src/OpenAiClient.ts +220 -1816
  43. package/src/OpenAiConfig.ts +20 -34
  44. package/src/OpenAiError.ts +107 -0
  45. package/src/OpenAiLanguageModel.ts +1807 -638
  46. package/src/OpenAiTelemetry.ts +24 -19
  47. package/src/OpenAiTool.ts +216 -70
  48. package/src/index.ts +35 -8
  49. package/src/internal/errors.ts +347 -0
  50. package/src/internal/utilities.ts +7 -5
  51. package/Generated/package.json +0 -6
  52. package/OpenAiClient/package.json +0 -6
  53. package/OpenAiConfig/package.json +0 -6
  54. package/OpenAiEmbeddingModel/package.json +0 -6
  55. package/OpenAiLanguageModel/package.json +0 -6
  56. package/OpenAiTelemetry/package.json +0 -6
  57. package/OpenAiTokenizer/package.json +0 -6
  58. package/OpenAiTool/package.json +0 -6
  59. package/README.md +0 -5
  60. package/dist/cjs/Generated.js +0 -7150
  61. package/dist/cjs/Generated.js.map +0 -1
  62. package/dist/cjs/OpenAiClient.js +0 -1567
  63. package/dist/cjs/OpenAiClient.js.map +0 -1
  64. package/dist/cjs/OpenAiConfig.js +0 -30
  65. package/dist/cjs/OpenAiConfig.js.map +0 -1
  66. package/dist/cjs/OpenAiEmbeddingModel.js +0 -155
  67. package/dist/cjs/OpenAiEmbeddingModel.js.map +0 -1
  68. package/dist/cjs/OpenAiLanguageModel.js +0 -1147
  69. package/dist/cjs/OpenAiLanguageModel.js.map +0 -1
  70. package/dist/cjs/OpenAiTelemetry.js +0 -38
  71. package/dist/cjs/OpenAiTelemetry.js.map +0 -1
  72. package/dist/cjs/OpenAiTokenizer.js +0 -83
  73. package/dist/cjs/OpenAiTokenizer.js.map +0 -1
  74. package/dist/cjs/OpenAiTool.js +0 -93
  75. package/dist/cjs/OpenAiTool.js.map +0 -1
  76. package/dist/cjs/index.js +0 -24
  77. package/dist/cjs/index.js.map +0 -1
  78. package/dist/cjs/internal/utilities.js +0 -32
  79. package/dist/cjs/internal/utilities.js.map +0 -1
  80. package/dist/dts/Generated.d.ts +0 -40661
  81. package/dist/dts/Generated.d.ts.map +0 -1
  82. package/dist/dts/OpenAiClient.d.ts +0 -3120
  83. package/dist/dts/OpenAiClient.d.ts.map +0 -1
  84. package/dist/dts/OpenAiConfig.d.ts.map +0 -1
  85. package/dist/dts/OpenAiEmbeddingModel.d.ts +0 -109
  86. package/dist/dts/OpenAiEmbeddingModel.d.ts.map +0 -1
  87. package/dist/dts/OpenAiLanguageModel.d.ts +0 -235
  88. package/dist/dts/OpenAiLanguageModel.d.ts.map +0 -1
  89. package/dist/dts/OpenAiTelemetry.d.ts.map +0 -1
  90. package/dist/dts/OpenAiTokenizer.d.ts +0 -17
  91. package/dist/dts/OpenAiTokenizer.d.ts.map +0 -1
  92. package/dist/dts/OpenAiTool.d.ts +0 -200
  93. package/dist/dts/OpenAiTool.d.ts.map +0 -1
  94. package/dist/dts/index.d.ts +0 -33
  95. package/dist/dts/index.d.ts.map +0 -1
  96. package/dist/esm/Generated.js +0 -7150
  97. package/dist/esm/Generated.js.map +0 -1
  98. package/dist/esm/OpenAiClient.js +0 -1504
  99. package/dist/esm/OpenAiClient.js.map +0 -1
  100. package/dist/esm/OpenAiConfig.js.map +0 -1
  101. package/dist/esm/OpenAiEmbeddingModel.js +0 -143
  102. package/dist/esm/OpenAiEmbeddingModel.js.map +0 -1
  103. package/dist/esm/OpenAiLanguageModel.js +0 -1134
  104. package/dist/esm/OpenAiLanguageModel.js.map +0 -1
  105. package/dist/esm/OpenAiTelemetry.js.map +0 -1
  106. package/dist/esm/OpenAiTokenizer.js +0 -73
  107. package/dist/esm/OpenAiTokenizer.js.map +0 -1
  108. package/dist/esm/OpenAiTool.js +0 -84
  109. package/dist/esm/OpenAiTool.js.map +0 -1
  110. package/dist/esm/index.js +0 -33
  111. package/dist/esm/index.js.map +0 -1
  112. package/dist/esm/internal/utilities.js.map +0 -1
  113. package/dist/esm/package.json +0 -4
  114. package/index/package.json +0 -6
  115. package/src/OpenAiEmbeddingModel.ts +0 -243
  116. package/src/OpenAiTokenizer.ts +0 -70
  117. /package/dist/{dts/internal → internal}/utilities.d.ts +0 -0
@@ -0,0 +1,347 @@
1
+ import * as Duration from "effect/Duration"
2
+ import * as Effect from "effect/Effect"
3
+ import { dual } from "effect/Function"
4
+ import * as Number from "effect/Number"
5
+ import * as Option from "effect/Option"
6
+ import * as Predicate from "effect/Predicate"
7
+ import * as Redactable from "effect/Redactable"
8
+ import * as Schema from "effect/Schema"
9
+ import * as AiError from "effect/unstable/ai/AiError"
10
+ import type * as Response from "effect/unstable/ai/Response"
11
+ import type * as HttpClientError from "effect/unstable/http/HttpClientError"
12
+ import type * as HttpClientRequest from "effect/unstable/http/HttpClientRequest"
13
+ import type * as HttpClientResponse from "effect/unstable/http/HttpClientResponse"
14
+ import type { OpenAiErrorMetadata } from "../OpenAiError.ts"
15
+
16
+ // =============================================================================
17
+ // OpenAI Error Body Schema
18
+ // =============================================================================
19
+
20
+ /** @internal */
21
+ export const OpenAiErrorBody = Schema.Struct({
22
+ error: Schema.Struct({
23
+ message: Schema.String,
24
+ type: Schema.optional(Schema.NullOr(Schema.String)),
25
+ param: Schema.optional(Schema.NullOr(Schema.String)),
26
+ code: Schema.optional(Schema.NullOr(Schema.String))
27
+ })
28
+ })
29
+
30
+ // =============================================================================
31
+ // Error Mappers
32
+ // =============================================================================
33
+
34
+ /** @internal */
35
+ export const mapSchemaError = dual<
36
+ (method: string) => (error: Schema.SchemaError) => AiError.AiError,
37
+ (error: Schema.SchemaError, method: string) => AiError.AiError
38
+ >(2, (error, method) =>
39
+ AiError.make({
40
+ module: "OpenAiClient",
41
+ method,
42
+ reason: AiError.InvalidOutputError.fromSchemaError(error)
43
+ }))
44
+
45
+ /** @internal */
46
+ export const mapHttpClientError = dual<
47
+ (method: string) => (error: HttpClientError.HttpClientError) => Effect.Effect<never, AiError.AiError>,
48
+ (error: HttpClientError.HttpClientError, method: string) => Effect.Effect<never, AiError.AiError>
49
+ >(2, (error, method) => {
50
+ const reason = error.reason
51
+ switch (reason._tag) {
52
+ case "TransportError": {
53
+ return Effect.fail(AiError.make({
54
+ module: "OpenAiClient",
55
+ method,
56
+ reason: new AiError.NetworkError({
57
+ reason: "TransportError",
58
+ description: reason.description,
59
+ request: buildHttpRequestDetails(reason.request)
60
+ })
61
+ }))
62
+ }
63
+ case "EncodeError": {
64
+ return Effect.fail(AiError.make({
65
+ module: "OpenAiClient",
66
+ method,
67
+ reason: new AiError.NetworkError({
68
+ reason: "EncodeError",
69
+ description: reason.description,
70
+ request: buildHttpRequestDetails(reason.request)
71
+ })
72
+ }))
73
+ }
74
+ case "InvalidUrlError": {
75
+ return Effect.fail(AiError.make({
76
+ module: "OpenAiClient",
77
+ method,
78
+ reason: new AiError.NetworkError({
79
+ reason: "InvalidUrlError",
80
+ description: reason.description,
81
+ request: buildHttpRequestDetails(reason.request)
82
+ })
83
+ }))
84
+ }
85
+ case "StatusCodeError": {
86
+ return mapStatusCodeError(reason, method)
87
+ }
88
+ case "DecodeError": {
89
+ return Effect.fail(AiError.make({
90
+ module: "OpenAiClient",
91
+ method,
92
+ reason: new AiError.InvalidOutputError({
93
+ description: reason.description ?? "Failed to decode response"
94
+ })
95
+ }))
96
+ }
97
+ case "EmptyBodyError": {
98
+ return Effect.fail(AiError.make({
99
+ module: "OpenAiClient",
100
+ method,
101
+ reason: new AiError.InvalidOutputError({
102
+ description: reason.description ?? "Response body was empty"
103
+ })
104
+ }))
105
+ }
106
+ }
107
+ })
108
+
109
+ /** @internal */
110
+ const mapStatusCodeError = Effect.fnUntraced(function*(
111
+ error: HttpClientError.StatusCodeError,
112
+ method: string
113
+ ) {
114
+ const { request, response, description } = error
115
+ const status = response.status
116
+ const headers = response.headers as Record<string, string>
117
+ const requestId = headers["x-request-id"]
118
+
119
+ // Try to get the actual response body. The description from filterStatusOk
120
+ // is often just "non 2xx status code", so try reading from response.text
121
+ let body: string | undefined = description
122
+ if (!description || !description.startsWith("{")) {
123
+ const responseBody = yield* Effect.option(response.text)
124
+ if (Option.isSome(responseBody) && responseBody.value) {
125
+ body = responseBody.value
126
+ }
127
+ }
128
+
129
+ // Try to parse the body as JSON to extract error details
130
+ let json: unknown = undefined
131
+ // @effect-diagnostics effect/tryCatchInEffectGen:off
132
+ try {
133
+ json = Predicate.isNotUndefined(body) ? JSON.parse(body) : undefined
134
+ } catch {
135
+ json = undefined
136
+ }
137
+ const decoded = Schema.decodeUnknownOption(OpenAiErrorBody)(json)
138
+
139
+ const reason = mapStatusCodeToReason({
140
+ status,
141
+ headers,
142
+ message: Option.isSome(decoded) ? decoded.value.error.message : undefined,
143
+ http: buildHttpContext({ request, response, body }),
144
+ metadata: {
145
+ errorCode: Option.isSome(decoded) ? decoded.value.error.code ?? null : null,
146
+ errorType: Option.isSome(decoded) ? decoded.value.error.type ?? null : null,
147
+ requestId: requestId ?? null
148
+ }
149
+ })
150
+
151
+ return yield* AiError.make({ module: "OpenAiClient", method, reason })
152
+ })
153
+
154
+ // =============================================================================
155
+ // Rate Limits
156
+ // =============================================================================
157
+
158
+ /** @internal */
159
+ export const parseRateLimitHeaders = (headers: Record<string, string>) => {
160
+ const retryAfterRaw = headers["retry-after"]
161
+ let retryAfter: Duration.Duration | undefined
162
+ if (Predicate.isNotUndefined(retryAfterRaw)) {
163
+ const parsed = Number.parse(retryAfterRaw)
164
+ if (Predicate.isNotUndefined(parsed)) {
165
+ retryAfter = Duration.seconds(parsed)
166
+ }
167
+ }
168
+ const remainingRaw = headers["x-ratelimit-remaining-requests"]
169
+ const remaining = Predicate.isNotUndefined(remainingRaw) ? Number.parse(remainingRaw) ?? null : null
170
+ return {
171
+ retryAfter,
172
+ limit: headers["x-ratelimit-limit-requests"] ?? null,
173
+ remaining,
174
+ resetRequests: headers["x-ratelimit-reset-requests"] ?? null,
175
+ resetTokens: headers["x-ratelimit-reset-tokens"] ?? null
176
+ }
177
+ }
178
+
179
+ // =============================================================================
180
+ // HTTP Context
181
+ // =============================================================================
182
+
183
+ /** @internal */
184
+ export const buildHttpRequestDetails = (
185
+ request: HttpClientRequest.HttpClientRequest
186
+ ): typeof Response.HttpRequestDetails.Type => ({
187
+ method: request.method,
188
+ url: request.url,
189
+ urlParams: Array.from(request.urlParams),
190
+ hash: request.hash,
191
+ headers: Redactable.redact(request.headers) as Record<string, string>
192
+ })
193
+
194
+ /** @internal */
195
+ export const buildHttpContext = (params: {
196
+ readonly request: HttpClientRequest.HttpClientRequest
197
+ readonly response?: HttpClientResponse.HttpClientResponse
198
+ readonly body?: string | undefined
199
+ }): typeof AiError.HttpContext.Type => ({
200
+ request: buildHttpRequestDetails(params.request),
201
+ response: Predicate.isNotUndefined(params.response)
202
+ ? {
203
+ status: params.response.status,
204
+ headers: Redactable.redact(params.response.headers) as Record<string, string>
205
+ }
206
+ : undefined,
207
+ body: params.body
208
+ })
209
+
210
+ // =============================================================================
211
+ // HTTP Status Code
212
+ // =============================================================================
213
+
214
+ const buildInvalidRequestDescription = (params: {
215
+ readonly status: number
216
+ readonly message: string | undefined
217
+ readonly method: string
218
+ readonly url: string
219
+ readonly errorCode: string | null
220
+ readonly errorType: string | null
221
+ readonly requestId: string | null
222
+ readonly body: string | undefined
223
+ }): string => {
224
+ const parts: Array<string> = []
225
+
226
+ // Primary message or status description
227
+ if (params.message) {
228
+ parts.push(params.message)
229
+ } else {
230
+ parts.push(`HTTP ${params.status}`)
231
+ }
232
+
233
+ // Request context
234
+ parts.push(`(${params.method} ${params.url})`)
235
+
236
+ // Error code/type if available
237
+ if (params.errorCode) {
238
+ parts.push(`[code: ${params.errorCode}]`)
239
+ } else if (params.errorType) {
240
+ parts.push(`[type: ${params.errorType}]`)
241
+ }
242
+
243
+ // Request ID for debugging
244
+ if (params.requestId) {
245
+ parts.push(`[requestId: ${params.requestId}]`)
246
+ }
247
+
248
+ // If no message and we have body, show truncated body
249
+ if (!params.message && params.body) {
250
+ const truncated = params.body.length > 200
251
+ ? params.body.slice(0, 200) + "..."
252
+ : params.body
253
+ parts.push(`Response: ${truncated}`)
254
+ }
255
+
256
+ return parts.join(" ")
257
+ }
258
+
259
+ /** @internal */
260
+ export const mapStatusCodeToReason = ({ status, headers, message, metadata, http }: {
261
+ readonly status: number
262
+ readonly headers: Record<string, string>
263
+ readonly message: string | undefined
264
+ readonly metadata: OpenAiErrorMetadata
265
+ readonly http: typeof AiError.HttpContext.Type
266
+ }): AiError.AiErrorReason => {
267
+ const invalidRequestDescription = buildInvalidRequestDescription({
268
+ status,
269
+ message,
270
+ method: http.request.method,
271
+ url: http.request.url,
272
+ errorCode: metadata.errorCode,
273
+ errorType: metadata.errorType,
274
+ requestId: metadata.requestId,
275
+ body: http.body
276
+ })
277
+
278
+ switch (status) {
279
+ case 400:
280
+ return new AiError.InvalidRequestError({
281
+ description: invalidRequestDescription,
282
+ metadata: { openai: metadata },
283
+ http
284
+ })
285
+ case 401:
286
+ return new AiError.AuthenticationError({
287
+ kind: "InvalidKey",
288
+ metadata,
289
+ http
290
+ })
291
+ case 403:
292
+ return new AiError.AuthenticationError({
293
+ kind: "InsufficientPermissions",
294
+ metadata,
295
+ http
296
+ })
297
+ case 404:
298
+ return new AiError.InvalidRequestError({
299
+ description: invalidRequestDescription,
300
+ metadata: { openai: metadata },
301
+ http
302
+ })
303
+ case 409:
304
+ case 422:
305
+ return new AiError.InvalidRequestError({
306
+ description: invalidRequestDescription,
307
+ metadata: { openai: metadata },
308
+ http
309
+ })
310
+ case 429: {
311
+ // Best-effort detection: OpenAI returns insufficient_quota for billing/quota issues
312
+ if (
313
+ metadata.errorCode === "insufficient_quota" ||
314
+ metadata.errorType === "insufficient_quota"
315
+ ) {
316
+ return new AiError.QuotaExhaustedError({
317
+ metadata: { openai: metadata },
318
+ http
319
+ })
320
+ }
321
+ const { retryAfter, ...rateLimitMetadata } = parseRateLimitHeaders(headers)
322
+ return new AiError.RateLimitError({
323
+ retryAfter,
324
+ metadata: {
325
+ openai: {
326
+ ...metadata,
327
+ ...rateLimitMetadata
328
+ }
329
+ },
330
+ http
331
+ })
332
+ }
333
+ default:
334
+ if (status >= 500) {
335
+ return new AiError.InternalProviderError({
336
+ description: message ?? "Server error",
337
+ metadata,
338
+ http
339
+ })
340
+ }
341
+ return new AiError.UnknownError({
342
+ description: message,
343
+ metadata,
344
+ http
345
+ })
346
+ }
347
+ }
@@ -1,5 +1,4 @@
1
- import type * as Response from "@effect/ai/Response"
2
- import * as Predicate from "effect/Predicate"
1
+ import type * as Response from "effect/unstable/ai/Response"
3
2
 
4
3
  /** @internal */
5
4
  export const ProviderOptionsKey = "@effect/ai-openai/OpenAiLanguageModel/ProviderOptions"
@@ -15,16 +14,19 @@ const finishReasonMap: Record<string, Response.FinishReason> = {
15
14
  tool_calls: "tool-calls"
16
15
  }
17
16
 
17
+ /** @internal */
18
+ export const escapeJSONDelta = (delta: string): string => JSON.stringify(delta).slice(1, -1)
19
+
18
20
  /** @internal */
19
21
  export const resolveFinishReason = (
20
- finishReason: string | undefined,
22
+ finishReason: string | null | undefined,
21
23
  hasToolCalls: boolean
22
24
  ): Response.FinishReason => {
23
- if (Predicate.isNullable(finishReason)) {
25
+ if (finishReason == null) {
24
26
  return hasToolCalls ? "tool-calls" : "stop"
25
27
  }
26
28
  const reason = finishReasonMap[finishReason]
27
- if (Predicate.isNullable(reason)) {
29
+ if (reason == null) {
28
30
  return hasToolCalls ? "tool-calls" : "unknown"
29
31
  }
30
32
  return reason
@@ -1,6 +0,0 @@
1
- {
2
- "sideEffects": [],
3
- "main": "../dist/cjs/Generated.js",
4
- "module": "../dist/esm/Generated.js",
5
- "types": "../dist/dts/Generated.d.ts"
6
- }
@@ -1,6 +0,0 @@
1
- {
2
- "sideEffects": [],
3
- "main": "../dist/cjs/OpenAiClient.js",
4
- "module": "../dist/esm/OpenAiClient.js",
5
- "types": "../dist/dts/OpenAiClient.d.ts"
6
- }
@@ -1,6 +0,0 @@
1
- {
2
- "sideEffects": [],
3
- "main": "../dist/cjs/OpenAiConfig.js",
4
- "module": "../dist/esm/OpenAiConfig.js",
5
- "types": "../dist/dts/OpenAiConfig.d.ts"
6
- }
@@ -1,6 +0,0 @@
1
- {
2
- "sideEffects": [],
3
- "main": "../dist/cjs/OpenAiEmbeddingModel.js",
4
- "module": "../dist/esm/OpenAiEmbeddingModel.js",
5
- "types": "../dist/dts/OpenAiEmbeddingModel.d.ts"
6
- }
@@ -1,6 +0,0 @@
1
- {
2
- "sideEffects": [],
3
- "main": "../dist/cjs/OpenAiLanguageModel.js",
4
- "module": "../dist/esm/OpenAiLanguageModel.js",
5
- "types": "../dist/dts/OpenAiLanguageModel.d.ts"
6
- }
@@ -1,6 +0,0 @@
1
- {
2
- "sideEffects": [],
3
- "main": "../dist/cjs/OpenAiTelemetry.js",
4
- "module": "../dist/esm/OpenAiTelemetry.js",
5
- "types": "../dist/dts/OpenAiTelemetry.d.ts"
6
- }
@@ -1,6 +0,0 @@
1
- {
2
- "sideEffects": [],
3
- "main": "../dist/cjs/OpenAiTokenizer.js",
4
- "module": "../dist/esm/OpenAiTokenizer.js",
5
- "types": "../dist/dts/OpenAiTokenizer.d.ts"
6
- }
@@ -1,6 +0,0 @@
1
- {
2
- "sideEffects": [],
3
- "main": "../dist/cjs/OpenAiTool.js",
4
- "module": "../dist/esm/OpenAiTool.js",
5
- "types": "../dist/dts/OpenAiTool.d.ts"
6
- }
package/README.md DELETED
@@ -1,5 +0,0 @@
1
- # `@effect/ai-openai`
2
-
3
- ## Documentation
4
-
5
- - **API Reference**: [View the full documentation](https://effect-ts.github.io/effect/docs/ai/openai).