@effect/ai-openai 4.0.0-beta.6 → 4.0.0-beta.60

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 (52) hide show
  1. package/dist/Generated.d.ts +66011 -38686
  2. package/dist/Generated.d.ts.map +1 -1
  3. package/dist/Generated.js +1 -1
  4. package/dist/Generated.js.map +1 -1
  5. package/dist/OpenAiClient.d.ts +63 -17
  6. package/dist/OpenAiClient.d.ts.map +1 -1
  7. package/dist/OpenAiClient.js +210 -33
  8. package/dist/OpenAiClient.js.map +1 -1
  9. package/dist/OpenAiClientGenerated.d.ts +91 -0
  10. package/dist/OpenAiClientGenerated.d.ts.map +1 -0
  11. package/dist/OpenAiClientGenerated.js +84 -0
  12. package/dist/OpenAiClientGenerated.js.map +1 -0
  13. package/dist/OpenAiConfig.d.ts +2 -2
  14. package/dist/OpenAiConfig.d.ts.map +1 -1
  15. package/dist/OpenAiConfig.js +3 -3
  16. package/dist/OpenAiConfig.js.map +1 -1
  17. package/dist/OpenAiEmbeddingModel.d.ts +85 -0
  18. package/dist/OpenAiEmbeddingModel.d.ts.map +1 -0
  19. package/dist/OpenAiEmbeddingModel.js +119 -0
  20. package/dist/OpenAiEmbeddingModel.js.map +1 -0
  21. package/dist/OpenAiError.d.ts +22 -32
  22. package/dist/OpenAiError.d.ts.map +1 -1
  23. package/dist/OpenAiLanguageModel.d.ts +43 -49
  24. package/dist/OpenAiLanguageModel.d.ts.map +1 -1
  25. package/dist/OpenAiLanguageModel.js +296 -152
  26. package/dist/OpenAiLanguageModel.js.map +1 -1
  27. package/dist/OpenAiSchema.d.ts +1920 -0
  28. package/dist/OpenAiSchema.d.ts.map +1 -0
  29. package/dist/OpenAiSchema.js +536 -0
  30. package/dist/OpenAiSchema.js.map +1 -0
  31. package/dist/OpenAiTool.d.ts +8 -7
  32. package/dist/OpenAiTool.d.ts.map +1 -1
  33. package/dist/OpenAiTool.js +2 -1
  34. package/dist/OpenAiTool.js.map +1 -1
  35. package/dist/index.d.ts +18 -0
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +18 -0
  38. package/dist/index.js.map +1 -1
  39. package/dist/internal/errors.js +4 -4
  40. package/dist/internal/errors.js.map +1 -1
  41. package/package.json +3 -3
  42. package/src/Generated.ts +7416 -4257
  43. package/src/OpenAiClient.ts +377 -81
  44. package/src/OpenAiClientGenerated.ts +202 -0
  45. package/src/OpenAiConfig.ts +3 -3
  46. package/src/OpenAiEmbeddingModel.ts +203 -0
  47. package/src/OpenAiError.ts +24 -32
  48. package/src/OpenAiLanguageModel.ts +420 -144
  49. package/src/OpenAiSchema.ts +875 -0
  50. package/src/OpenAiTool.ts +2 -1
  51. package/src/index.ts +21 -0
  52. package/src/internal/errors.ts +6 -4
@@ -8,23 +8,31 @@
8
8
  */
9
9
  import * as Array from "effect/Array"
10
10
  import type * as Config from "effect/Config"
11
+ import * as Context from "effect/Context"
11
12
  import * as Effect from "effect/Effect"
12
13
  import { identity } from "effect/Function"
14
+ import * as Function from "effect/Function"
13
15
  import * as Layer from "effect/Layer"
14
16
  import * as Predicate from "effect/Predicate"
17
+ import * as Queue from "effect/Queue"
18
+ import * as RcRef from "effect/RcRef"
15
19
  import * as Redacted from "effect/Redacted"
16
- import * as ServiceMap from "effect/ServiceMap"
20
+ import * as Schema from "effect/Schema"
21
+ import * as Scope from "effect/Scope"
22
+ import * as Semaphore from "effect/Semaphore"
17
23
  import * as Stream from "effect/Stream"
18
- import type * as AiError from "effect/unstable/ai/AiError"
24
+ import * as AiError from "effect/unstable/ai/AiError"
25
+ import * as ResponseIdTracker from "effect/unstable/ai/ResponseIdTracker"
19
26
  import * as Sse from "effect/unstable/encoding/Sse"
20
27
  import * as Headers from "effect/unstable/http/Headers"
21
28
  import * as HttpBody from "effect/unstable/http/HttpBody"
22
29
  import * as HttpClient from "effect/unstable/http/HttpClient"
23
30
  import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest"
24
- import type * as HttpClientResponse from "effect/unstable/http/HttpClientResponse"
25
- import * as Generated from "./Generated.ts"
31
+ import * as HttpClientResponse from "effect/unstable/http/HttpClientResponse"
32
+ import * as Socket from "effect/unstable/socket/Socket"
26
33
  import * as Errors from "./internal/errors.ts"
27
34
  import { OpenAiConfig } from "./OpenAiConfig.ts"
35
+ import * as OpenAiSchema from "./OpenAiSchema.ts"
28
36
 
29
37
  // =============================================================================
30
38
  // Service Interface
@@ -38,17 +46,17 @@ import { OpenAiConfig } from "./OpenAiConfig.ts"
38
46
  */
39
47
  export interface Service {
40
48
  /**
41
- * The underlying generated OpenAI client.
49
+ * The transformed HTTP client used by this service.
42
50
  */
43
- readonly client: Generated.OpenAiClient
51
+ readonly client: HttpClient.HttpClient
44
52
 
45
53
  /**
46
54
  * Create a response using the OpenAI responses endpoint.
47
55
  */
48
56
  readonly createResponse: (
49
- options: typeof Generated.CreateResponse.Encoded
57
+ options: typeof OpenAiSchema.CreateResponse.Encoded
50
58
  ) => Effect.Effect<
51
- [body: typeof Generated.Response.Type, response: HttpClientResponse.HttpClientResponse],
59
+ readonly [body: typeof OpenAiSchema.Response.Type, response: HttpClientResponse.HttpClientResponse],
52
60
  AiError.AiError
53
61
  >
54
62
 
@@ -56,11 +64,11 @@ export interface Service {
56
64
  * Create a streaming response using the OpenAI responses endpoint.
57
65
  */
58
66
  readonly createResponseStream: (
59
- options: Omit<typeof Generated.CreateResponse.Encoded, "stream">
67
+ options: Omit<typeof OpenAiSchema.CreateResponse.Encoded, "stream">
60
68
  ) => Effect.Effect<
61
- [
69
+ readonly [
62
70
  response: HttpClientResponse.HttpClientResponse,
63
- stream: Stream.Stream<typeof Generated.ResponseStreamEvent.Type, AiError.AiError>
71
+ stream: Stream.Stream<typeof OpenAiSchema.ResponseStreamEvent.Type, AiError.AiError>
64
72
  ],
65
73
  AiError.AiError
66
74
  >
@@ -69,8 +77,8 @@ export interface Service {
69
77
  * Create embeddings using the OpenAI embeddings endpoint.
70
78
  */
71
79
  readonly createEmbedding: (
72
- options: typeof Generated.CreateEmbeddingRequest.Encoded
73
- ) => Effect.Effect<typeof Generated.CreateEmbeddingResponse.Type, AiError.AiError>
80
+ options: typeof OpenAiSchema.CreateEmbeddingRequest.Encoded
81
+ ) => Effect.Effect<typeof OpenAiSchema.CreateEmbeddingResponse.Type, AiError.AiError>
74
82
  }
75
83
 
76
84
  // =============================================================================
@@ -83,7 +91,7 @@ export interface Service {
83
91
  * @since 1.0.0
84
92
  * @category service
85
93
  */
86
- export class OpenAiClient extends ServiceMap.Service<OpenAiClient, Service>()(
94
+ export class OpenAiClient extends Context.Service<OpenAiClient, Service>()(
87
95
  "@effect/ai-openai/OpenAiClient"
88
96
  ) {}
89
97
 
@@ -142,70 +150,83 @@ const RedactedOpenAiHeaders = {
142
150
  * @category constructors
143
151
  */
144
152
  export const make = Effect.fnUntraced(
145
- function*(options: Options): Effect.fn.Return<Service, never, HttpClient.HttpClient> {
153
+ function*(
154
+ options: Options
155
+ ): Effect.fn.Return<Service, never, HttpClient.HttpClient> {
146
156
  const baseClient = yield* HttpClient.HttpClient
157
+ const apiUrl = options.apiUrl ?? "https://api.openai.com/v1"
147
158
 
148
159
  const httpClient = baseClient.pipe(
149
- HttpClient.mapRequest((request) =>
150
- request.pipe(
151
- HttpClientRequest.prependUrl(options.apiUrl ?? "https://api.openai.com/v1"),
152
- Predicate.isNotUndefined(options.apiKey)
153
- ? HttpClientRequest.bearerToken(Redacted.value(options.apiKey))
154
- : identity,
155
- Predicate.isNotUndefined(options.organizationId)
156
- ? HttpClientRequest.setHeader(
157
- RedactedOpenAiHeaders.OpenAiOrganization,
158
- Redacted.value(options.organizationId)
159
- )
160
- : identity,
161
- Predicate.isNotUndefined(options.projectId)
162
- ? HttpClientRequest.setHeader(
163
- RedactedOpenAiHeaders.OpenAiProject,
164
- Redacted.value(options.projectId)
165
- )
166
- : identity,
167
- HttpClientRequest.acceptJson
168
- )
169
- ),
170
- Predicate.isNotUndefined(options.transformClient)
160
+ HttpClient.mapRequest(Function.flow(
161
+ HttpClientRequest.prependUrl(apiUrl),
162
+ options.apiKey
163
+ ? HttpClientRequest.bearerToken(Redacted.value(options.apiKey))
164
+ : identity,
165
+ options.organizationId
166
+ ? HttpClientRequest.setHeader(
167
+ RedactedOpenAiHeaders.OpenAiOrganization,
168
+ Redacted.value(options.organizationId)
169
+ )
170
+ : identity,
171
+ options.projectId
172
+ ? HttpClientRequest.setHeader(
173
+ RedactedOpenAiHeaders.OpenAiProject,
174
+ Redacted.value(options.projectId)
175
+ )
176
+ : identity,
177
+ HttpClientRequest.acceptJson
178
+ )),
179
+ HttpClient.filterStatusOk,
180
+ options.transformClient
171
181
  ? options.transformClient
172
182
  : identity
173
183
  )
174
184
 
175
- const httpClientOk = HttpClient.filterStatusOk(httpClient)
185
+ const resolveHttpClient = Effect.map(
186
+ OpenAiConfig.getOrUndefined,
187
+ (config) =>
188
+ Predicate.isNotUndefined(config?.transformClient)
189
+ ? config.transformClient(httpClient)
190
+ : httpClient
191
+ )
176
192
 
177
- const client = Generated.make(httpClient, {
178
- transformClient: Effect.fnUntraced(function*(client) {
179
- const config = yield* OpenAiConfig.getOrUndefined
180
- if (Predicate.isNotUndefined(config?.transformClient)) {
181
- return config.transformClient(client)
182
- }
183
- return client
184
- })
185
- })
193
+ const decodeResponse = HttpClientResponse.schemaBodyJson(OpenAiSchema.Response)
186
194
 
187
195
  const createResponse = (
188
- payload: typeof Generated.CreateResponse.Encoded
196
+ payload: typeof OpenAiSchema.CreateResponse.Encoded
189
197
  ): Effect.Effect<
190
- [body: typeof Generated.Response.Type, response: HttpClientResponse.HttpClientResponse],
198
+ [body: typeof OpenAiSchema.Response.Type, response: HttpClientResponse.HttpClientResponse],
191
199
  AiError.AiError
192
200
  > =>
193
- client.createResponse({ payload, config: { includeResponse: true } }).pipe(
194
- Effect.catchTags({
195
- HttpClientError: (error) => Errors.mapHttpClientError(error, "createResponse"),
196
- SchemaError: (error) => Effect.fail(Errors.mapSchemaError(error, "createResponse"))
197
- })
198
- )
201
+ Effect.flatMap(resolveHttpClient, (client) =>
202
+ client.execute(
203
+ HttpClientRequest.post("/responses", {
204
+ body: HttpBody.jsonUnsafe(payload)
205
+ })
206
+ ).pipe(
207
+ Effect.flatMap((response) =>
208
+ decodeResponse(response).pipe(
209
+ Effect.map((body): [typeof OpenAiSchema.Response.Type, HttpClientResponse.HttpClientResponse] => [
210
+ body,
211
+ response
212
+ ])
213
+ )
214
+ ),
215
+ Effect.catchTags({
216
+ HttpClientError: (error) => Errors.mapHttpClientError(error, "createResponse"),
217
+ SchemaError: (error) => Effect.fail(Errors.mapSchemaError(error, "createResponse"))
218
+ })
219
+ ))
199
220
 
200
221
  const buildResponseStream = (
201
222
  response: HttpClientResponse.HttpClientResponse
202
223
  ): [
203
224
  HttpClientResponse.HttpClientResponse,
204
- Stream.Stream<typeof Generated.ResponseStreamEvent.Type, AiError.AiError>
225
+ Stream.Stream<typeof OpenAiSchema.ResponseStreamEvent.Type, AiError.AiError>
205
226
  ] => {
206
227
  const stream = response.stream.pipe(
207
228
  Stream.decodeText(),
208
- Stream.pipeThroughChannel(Sse.decodeDataSchema(Generated.ResponseStreamEvent)),
229
+ Stream.pipeThroughChannel(Sse.decodeDataSchema(OpenAiSchema.ResponseStreamEvent)),
209
230
  Stream.takeUntil((event) =>
210
231
  event.data.type === "response.completed" ||
211
232
  event.data.type === "response.incomplete"
@@ -217,35 +238,48 @@ export const make = Effect.fnUntraced(
217
238
  HttpClientError: (error) => Stream.fromEffect(Errors.mapHttpClientError(error, "createResponseStream")),
218
239
  SchemaError: (error) => Stream.fail(Errors.mapSchemaError(error, "createResponseStream"))
219
240
  })
220
- ) as any
241
+ )
221
242
  return [response, stream]
222
243
  }
223
244
 
224
245
  const createResponseStream: Service["createResponseStream"] = (payload) =>
225
- httpClientOk.execute(
226
- HttpClientRequest.post("/responses", {
227
- body: HttpBody.jsonUnsafe({ ...payload, stream: true })
228
- })
229
- ).pipe(
230
- Effect.map(buildResponseStream),
231
- Effect.catchTag(
232
- "HttpClientError",
233
- (error) => Errors.mapHttpClientError(error, "createResponseStream")
234
- )
235
- )
246
+ Effect.contextWith((services) => {
247
+ const socket = Context.getOrUndefined(services, OpenAiSocket)
248
+ if (socket) return socket.createResponseStream(payload)
249
+ return Effect.flatMap(resolveHttpClient, (client) =>
250
+ client.execute(
251
+ HttpClientRequest.post("/responses", {
252
+ body: HttpBody.jsonUnsafe({ ...payload, stream: true })
253
+ })
254
+ ).pipe(
255
+ Effect.map(buildResponseStream),
256
+ Effect.catchTag(
257
+ "HttpClientError",
258
+ (error) => Errors.mapHttpClientError(error, "createResponseStream")
259
+ )
260
+ ))
261
+ })
262
+
263
+ const decodeEmbedding = HttpClientResponse.schemaBodyJson(OpenAiSchema.CreateEmbeddingResponse)
236
264
 
237
265
  const createEmbedding = (
238
- payload: typeof Generated.CreateEmbeddingRequest.Encoded
239
- ): Effect.Effect<typeof Generated.CreateEmbeddingResponse.Type, AiError.AiError> =>
240
- client.createEmbedding({ payload }).pipe(
241
- Effect.catchTags({
242
- HttpClientError: (error) => Errors.mapHttpClientError(error, "createEmbedding"),
243
- SchemaError: (error) => Effect.fail(Errors.mapSchemaError(error, "createEmbedding"))
244
- })
245
- )
266
+ payload: typeof OpenAiSchema.CreateEmbeddingRequest.Encoded
267
+ ): Effect.Effect<typeof OpenAiSchema.CreateEmbeddingResponse.Type, AiError.AiError> =>
268
+ Effect.flatMap(resolveHttpClient, (client) =>
269
+ client.execute(
270
+ HttpClientRequest.post("/embeddings", {
271
+ body: HttpBody.jsonUnsafe(payload)
272
+ })
273
+ ).pipe(
274
+ Effect.flatMap(decodeEmbedding),
275
+ Effect.catchTags({
276
+ HttpClientError: (error) => Errors.mapHttpClientError(error, "createEmbedding"),
277
+ SchemaError: (error) => Effect.fail(Errors.mapSchemaError(error, "createEmbedding"))
278
+ })
279
+ ))
246
280
 
247
281
  return OpenAiClient.of({
248
- client,
282
+ client: httpClient,
249
283
  createResponse,
250
284
  createResponseStream,
251
285
  createEmbedding
@@ -281,7 +315,7 @@ export const layerConfig = (options?: {
281
315
  /**
282
316
  * The config value to load for the API key.
283
317
  */
284
- readonly apiKey?: Config.Config<Redacted.Redacted<string>> | undefined
318
+ readonly apiKey?: Config.Config<Redacted.Redacted<string> | undefined> | undefined
285
319
 
286
320
  /**
287
321
  * The config value to load for the API URL.
@@ -291,12 +325,12 @@ export const layerConfig = (options?: {
291
325
  /**
292
326
  * The config value to load for the organization ID.
293
327
  */
294
- readonly organizationId?: Config.Config<Redacted.Redacted<string>> | undefined
328
+ readonly organizationId?: Config.Config<Redacted.Redacted<string> | undefined> | undefined
295
329
 
296
330
  /**
297
331
  * The config value to load for the project ID.
298
332
  */
299
- readonly projectId?: Config.Config<Redacted.Redacted<string>> | undefined
333
+ readonly projectId?: Config.Config<Redacted.Redacted<string> | undefined> | undefined
300
334
 
301
335
  /**
302
336
  * Optional transformer for the HTTP client.
@@ -327,3 +361,265 @@ export const layerConfig = (options?: {
327
361
  })
328
362
  })
329
363
  )
364
+
365
+ // =============================================================================
366
+ // Websocket mode
367
+ // =============================================================================
368
+
369
+ /**
370
+ * @since 1.0.0
371
+ * @category Events
372
+ */
373
+ export type ResponseStreamEvent = typeof OpenAiSchema.ResponseStreamEvent.Type
374
+
375
+ /**
376
+ * @since 1.0.0
377
+ * @category Websocket mode
378
+ */
379
+ export class OpenAiSocket extends Context.Service<OpenAiSocket, {
380
+ /**
381
+ * Create a streaming response using the OpenAI responses endpoint.
382
+ */
383
+ readonly createResponseStream: (
384
+ options: Omit<typeof OpenAiSchema.CreateResponse.Encoded, "stream">
385
+ ) => Effect.Effect<
386
+ readonly [
387
+ response: HttpClientResponse.HttpClientResponse,
388
+ stream: Stream.Stream<ResponseStreamEvent, AiError.AiError>
389
+ ],
390
+ AiError.AiError
391
+ >
392
+ }>()("@effect/ai-openai/OpenAiClient/OpenAiSocket") {}
393
+
394
+ const makeSocket = Effect.gen(function*() {
395
+ const client = yield* OpenAiClient
396
+ const tracker = yield* ResponseIdTracker.make
397
+ const socketScope = yield* Effect.scope
398
+ const makeRequest = Effect.flatMap(
399
+ OpenAiConfig.getOrUndefined,
400
+ (config) => {
401
+ const httpClient = Predicate.isNotUndefined(config?.transformClient)
402
+ ? config.transformClient(client.client)
403
+ : client.client
404
+ return Effect.orDie(httpClient.preprocess(HttpClientRequest.post("/responses")))
405
+ }
406
+ )
407
+ const makeWebSocket = yield* Socket.WebSocketConstructor
408
+
409
+ const decoder = new TextDecoder()
410
+
411
+ const queueRef: RcRef.RcRef<
412
+ {
413
+ readonly send: (message: typeof OpenAiSchema.CreateResponse.Encoded) => Effect.Effect<void, AiError.AiError>
414
+ readonly incoming: Queue.Dequeue<ResponseStreamEvent, AiError.AiError>
415
+ }
416
+ > = yield* RcRef.make({
417
+ idleTimeToLive: 60_000,
418
+ acquire: Effect.gen(function*() {
419
+ const scope = yield* Effect.scope
420
+ const request = yield* makeRequest
421
+ const socket = yield* Socket.makeWebSocket(request.url.replace(/^http/, "ws")).pipe(
422
+ Effect.provideService(Socket.WebSocketConstructor, (url) =>
423
+ makeWebSocket(url, {
424
+ headers: request.headers
425
+ } as any))
426
+ )
427
+ const write = yield* socket.writer
428
+
429
+ yield* Scope.addFinalizerExit(scope, () => {
430
+ tracker.clearUnsafe()
431
+ return Effect.void
432
+ })
433
+
434
+ const incoming = yield* Queue.unbounded<ResponseStreamEvent, AiError.AiError>()
435
+ const send = (message: typeof OpenAiSchema.CreateResponse.Encoded) =>
436
+ write(JSON.stringify({
437
+ type: "response.create",
438
+ ...message
439
+ })).pipe(
440
+ Effect.mapError((_error) =>
441
+ AiError.make({
442
+ module: "OpenAiClient",
443
+ method: "createResponseStream",
444
+ reason: new AiError.NetworkError({
445
+ reason: "TransportError",
446
+ request: {
447
+ method: "POST",
448
+ url: request.url,
449
+ urlParams: [],
450
+ hash: undefined,
451
+ headers: request.headers
452
+ },
453
+ description: "Failed to send message over WebSocket"
454
+ })
455
+ })
456
+ )
457
+ )
458
+
459
+ yield* socket.runRaw((msg) => {
460
+ const text = typeof msg === "string" ? msg : decoder.decode(msg)
461
+ try {
462
+ const event = decodeEvent(text)
463
+ if (event.type === "error" && "status" in event) {
464
+ const status = Number(event.status)
465
+ const error = "error" in event ? event.error : event
466
+ const json = JSON.stringify(error)
467
+ return Effect.fail(
468
+ AiError.make({
469
+ module: "OpenAiClient",
470
+ method: "createResponseStream",
471
+ reason: AiError.reasonFromHttpStatus({
472
+ description: json,
473
+ status: isNaN(status) ? 500 : status,
474
+ metadata: error as any,
475
+ http: {
476
+ body: json,
477
+ request: {
478
+ method: "POST",
479
+ url: request.url,
480
+ urlParams: [],
481
+ hash: undefined,
482
+ headers: request.headers
483
+ }
484
+ }
485
+ })
486
+ })
487
+ )
488
+ }
489
+ Queue.offerUnsafe(incoming, event)
490
+ } catch {}
491
+ }).pipe(
492
+ Effect.catchTag("SocketError", (error) =>
493
+ AiError.make({
494
+ module: "OpenAiClient",
495
+ method: "createResponseStream",
496
+ reason: new AiError.NetworkError({
497
+ reason: "TransportError",
498
+ request: {
499
+ method: "POST",
500
+ url: request.url,
501
+ urlParams: [],
502
+ hash: undefined,
503
+ headers: request.headers
504
+ },
505
+ description: error.message
506
+ })
507
+ }).asEffect()),
508
+ Effect.catchCause((cause) => Queue.failCause(incoming, cause)),
509
+ Effect.ensuring(Effect.forkIn(RcRef.invalidate(queueRef), socketScope, {
510
+ startImmediately: true
511
+ })),
512
+ Effect.forkScoped({ startImmediately: true })
513
+ )
514
+
515
+ return { send, incoming } as const
516
+ })
517
+ })
518
+
519
+ // Prime the websocket
520
+ yield* Effect.scoped(RcRef.get(queueRef))
521
+
522
+ // Websocket mode only allows one request at a time
523
+ const semaphore = Semaphore.makeUnsafe(1)
524
+ const request = yield* makeRequest
525
+
526
+ return OpenAiSocket.context({
527
+ createResponseStream(options) {
528
+ const stream = Stream.unwrap(Effect.gen(function*() {
529
+ const scope = yield* Effect.scope
530
+ yield* Effect.acquireRelease(
531
+ semaphore.take(1),
532
+ () => semaphore.release(1),
533
+ { interruptible: true }
534
+ )
535
+ const { send, incoming } = yield* RcRef.get(queueRef)
536
+ let done = false
537
+
538
+ yield* Scope.addFinalizerExit(
539
+ scope,
540
+ () => done ? Effect.void : RcRef.invalidate(queueRef)
541
+ )
542
+
543
+ yield* send(options).pipe(
544
+ Effect.forkScoped({ startImmediately: true })
545
+ )
546
+
547
+ return Stream.fromQueue(incoming).pipe(
548
+ Stream.takeUntil((e) => {
549
+ done = e.type === "response.completed" || e.type === "response.incomplete"
550
+ return done
551
+ })
552
+ )
553
+ }))
554
+
555
+ return Effect.succeed([
556
+ HttpClientResponse.fromWeb(request, new Response()),
557
+ stream
558
+ ])
559
+ }
560
+ }).pipe(
561
+ Context.add(ResponseIdTracker.ResponseIdTracker, tracker)
562
+ )
563
+ })
564
+
565
+ const ErrorEvent = Schema.Struct({
566
+ type: Schema.Literal("error"),
567
+ status: Schema.Number.pipe(
568
+ Schema.withDecodingDefault(Effect.succeed(500))
569
+ ),
570
+ error: Schema.Struct({
571
+ type: Schema.String,
572
+ message: Schema.String
573
+ })
574
+ })
575
+
576
+ const AllEvents = Schema.Union([ErrorEvent, OpenAiSchema.ResponseStreamEvent])
577
+ const decodeEvent = Schema.decodeUnknownSync(Schema.fromJsonString(AllEvents))
578
+
579
+ /**
580
+ * Uses OpenAI's websocket mode for all responses within the provided effect.
581
+ *
582
+ * Note: This only works with the following WebSocket constructor layers:
583
+ *
584
+ * - `NodeSocket.layerWebSocketConstructorWS`
585
+ * - `BunSocket.layerWebSocketConstructor`
586
+ *
587
+ * This is because it needs to use non-standard options for setting the
588
+ * Authorization header.
589
+ *
590
+ * @since 1.0.0
591
+ * @category Websocket mode
592
+ */
593
+ export const withWebSocketMode = <A, E, R>(
594
+ effect: Effect.Effect<A, E, R>
595
+ ): Effect.Effect<
596
+ A,
597
+ E,
598
+ Exclude<R, OpenAiSocket | ResponseIdTracker.ResponseIdTracker> | OpenAiClient | Socket.WebSocketConstructor
599
+ > =>
600
+ Effect.scopedWith((scope) =>
601
+ Effect.flatMap(
602
+ Scope.provide(makeSocket, scope),
603
+ (services) => Effect.provideContext(effect, services)
604
+ )
605
+ )
606
+
607
+ /**
608
+ * Uses OpenAI's websocket mode for all responses that use the Layer.
609
+ *
610
+ * Note: This only works with the following WebSocket constructor layers:
611
+ *
612
+ * - `NodeSocket.layerWebSocketConstructorWS`
613
+ * - `BunSocket.layerWebSocketConstructor`
614
+ *
615
+ * This is because it needs to use non-standard options for setting the
616
+ * Authorization header.
617
+ *
618
+ * @since 1.0.0
619
+ * @category Websocket mode
620
+ */
621
+ export const layerWebSocketMode: Layer.Layer<
622
+ OpenAiSocket | ResponseIdTracker.ResponseIdTracker,
623
+ never,
624
+ OpenAiClient | Socket.WebSocketConstructor
625
+ > = Layer.effectContext(makeSocket)