@effect/ai-openai 4.0.0-beta.51 → 4.0.0-beta.53

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.
package/dist/index.js CHANGED
@@ -15,6 +15,10 @@ export * as Generated from "./Generated.js";
15
15
  * @since 1.0.0
16
16
  */
17
17
  export * as OpenAiClient from "./OpenAiClient.js";
18
+ /**
19
+ * @since 1.0.0
20
+ */
21
+ export * as OpenAiClientGenerated from "./OpenAiClientGenerated.js";
18
22
  /**
19
23
  * @since 1.0.0
20
24
  */
@@ -45,6 +49,12 @@ export * as OpenAiError from "./OpenAiError.js";
45
49
  * @since 1.0.0
46
50
  */
47
51
  export * as OpenAiLanguageModel from "./OpenAiLanguageModel.js";
52
+ /**
53
+ * Minimal local OpenAI schemas used by the handwritten Responses client path.
54
+ *
55
+ * @since 1.0.0
56
+ */
57
+ export * as OpenAiSchema from "./OpenAiSchema.js";
48
58
  /**
49
59
  * OpenAI telemetry attributes for OpenTelemetry integration.
50
60
  *
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["Generated","OpenAiClient","OpenAiConfig","OpenAiEmbeddingModel","OpenAiError","OpenAiLanguageModel","OpenAiTelemetry","OpenAiTool"],"sources":["../src/index.ts"],"sourcesContent":[null],"mappings":"AAAA;;;AAIA;AAEA;;;AAGA,OAAO,KAAKA,SAAS,MAAM,gBAAgB;AAE3C;;;;;;;;AAQA,OAAO,KAAKC,YAAY,MAAM,mBAAmB;AAEjD;;;AAGA,OAAO,KAAKC,YAAY,MAAM,mBAAmB;AAEjD;;;;;;;AAOA,OAAO,KAAKC,oBAAoB,MAAM,2BAA2B;AAEjE;;;;;;;;AAQA,OAAO,KAAKC,WAAW,MAAM,kBAAkB;AAE/C;;;;;;;;AAQA,OAAO,KAAKC,mBAAmB,MAAM,0BAA0B;AAE/D;;;;;;;;;AASA,OAAO,KAAKC,eAAe,MAAM,sBAAsB;AAEvD;;;;;;;;AAQA,OAAO,KAAKC,UAAU,MAAM,iBAAiB","ignoreList":[]}
1
+ {"version":3,"file":"index.js","names":["Generated","OpenAiClient","OpenAiClientGenerated","OpenAiConfig","OpenAiEmbeddingModel","OpenAiError","OpenAiLanguageModel","OpenAiSchema","OpenAiTelemetry","OpenAiTool"],"sources":["../src/index.ts"],"sourcesContent":[null],"mappings":"AAAA;;;AAIA;AAEA;;;AAGA,OAAO,KAAKA,SAAS,MAAM,gBAAgB;AAE3C;;;;;;;;AAQA,OAAO,KAAKC,YAAY,MAAM,mBAAmB;AAEjD;;;AAGA,OAAO,KAAKC,qBAAqB,MAAM,4BAA4B;AAEnE;;;AAGA,OAAO,KAAKC,YAAY,MAAM,mBAAmB;AAEjD;;;;;;;AAOA,OAAO,KAAKC,oBAAoB,MAAM,2BAA2B;AAEjE;;;;;;;;AAQA,OAAO,KAAKC,WAAW,MAAM,kBAAkB;AAE/C;;;;;;;;AAQA,OAAO,KAAKC,mBAAmB,MAAM,0BAA0B;AAE/D;;;;;AAKA,OAAO,KAAKC,YAAY,MAAM,mBAAmB;AAEjD;;;;;;;;;AASA,OAAO,KAAKC,eAAe,MAAM,sBAAsB;AAEvD;;;;;;;;AAQA,OAAO,KAAKC,UAAU,MAAM,iBAAiB","ignoreList":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effect/ai-openai",
3
- "version": "4.0.0-beta.51",
3
+ "version": "4.0.0-beta.53",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "An OpenAI provider integration for Effect AI SDK",
@@ -43,10 +43,10 @@
43
43
  "provenance": true
44
44
  },
45
45
  "devDependencies": {
46
- "effect": "^4.0.0-beta.51"
46
+ "effect": "^4.0.0-beta.53"
47
47
  },
48
48
  "peerDependencies": {
49
- "effect": "^4.0.0-beta.51"
49
+ "effect": "^4.0.0-beta.53"
50
50
  },
51
51
  "scripts": {
52
52
  "codegen": "effect-utils codegen",
@@ -30,9 +30,9 @@ import * as HttpClient from "effect/unstable/http/HttpClient"
30
30
  import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest"
31
31
  import * as HttpClientResponse from "effect/unstable/http/HttpClientResponse"
32
32
  import * as Socket from "effect/unstable/socket/Socket"
33
- import * as Generated from "./Generated.ts"
34
33
  import * as Errors from "./internal/errors.ts"
35
34
  import { OpenAiConfig } from "./OpenAiConfig.ts"
35
+ import * as OpenAiSchema from "./OpenAiSchema.ts"
36
36
 
37
37
  // =============================================================================
38
38
  // Service Interface
@@ -46,17 +46,17 @@ import { OpenAiConfig } from "./OpenAiConfig.ts"
46
46
  */
47
47
  export interface Service {
48
48
  /**
49
- * The underlying generated OpenAI client.
49
+ * The transformed HTTP client used by this service.
50
50
  */
51
- readonly client: Generated.OpenAiClient
51
+ readonly client: HttpClient.HttpClient
52
52
 
53
53
  /**
54
54
  * Create a response using the OpenAI responses endpoint.
55
55
  */
56
56
  readonly createResponse: (
57
- options: typeof Generated.CreateResponse.Encoded
57
+ options: typeof OpenAiSchema.CreateResponse.Encoded
58
58
  ) => Effect.Effect<
59
- readonly [body: typeof Generated.Response.Type, response: HttpClientResponse.HttpClientResponse],
59
+ readonly [body: typeof OpenAiSchema.Response.Type, response: HttpClientResponse.HttpClientResponse],
60
60
  AiError.AiError
61
61
  >
62
62
 
@@ -64,11 +64,11 @@ export interface Service {
64
64
  * Create a streaming response using the OpenAI responses endpoint.
65
65
  */
66
66
  readonly createResponseStream: (
67
- options: Omit<typeof Generated.CreateResponse.Encoded, "stream">
67
+ options: Omit<typeof OpenAiSchema.CreateResponse.Encoded, "stream">
68
68
  ) => Effect.Effect<
69
69
  readonly [
70
70
  response: HttpClientResponse.HttpClientResponse,
71
- stream: Stream.Stream<typeof Generated.ResponseStreamEvent.Type, AiError.AiError>
71
+ stream: Stream.Stream<typeof OpenAiSchema.ResponseStreamEvent.Type, AiError.AiError>
72
72
  ],
73
73
  AiError.AiError
74
74
  >
@@ -77,8 +77,8 @@ export interface Service {
77
77
  * Create embeddings using the OpenAI embeddings endpoint.
78
78
  */
79
79
  readonly createEmbedding: (
80
- options: typeof Generated.CreateEmbeddingRequest.Encoded
81
- ) => Effect.Effect<typeof Generated.CreateEmbeddingResponse.Type, AiError.AiError>
80
+ options: typeof OpenAiSchema.CreateEmbeddingRequest.Encoded
81
+ ) => Effect.Effect<typeof OpenAiSchema.CreateEmbeddingResponse.Type, AiError.AiError>
82
82
  }
83
83
 
84
84
  // =============================================================================
@@ -176,45 +176,57 @@ export const make = Effect.fnUntraced(
176
176
  : identity,
177
177
  HttpClientRequest.acceptJson
178
178
  )),
179
+ HttpClient.filterStatusOk,
179
180
  options.transformClient
180
181
  ? options.transformClient
181
182
  : identity
182
183
  )
183
184
 
184
- 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
+ )
185
192
 
186
- const client = Generated.make(httpClient, {
187
- transformClient: Effect.fnUntraced(function*(client) {
188
- const config = yield* OpenAiConfig.getOrUndefined
189
- if (Predicate.isNotUndefined(config?.transformClient)) {
190
- return config.transformClient(client)
191
- }
192
- return client
193
- })
194
- })
193
+ const decodeResponse = HttpClientResponse.schemaBodyJson(OpenAiSchema.Response)
195
194
 
196
195
  const createResponse = (
197
- payload: typeof Generated.CreateResponse.Encoded
196
+ payload: typeof OpenAiSchema.CreateResponse.Encoded
198
197
  ): Effect.Effect<
199
- [body: typeof Generated.Response.Type, response: HttpClientResponse.HttpClientResponse],
198
+ [body: typeof OpenAiSchema.Response.Type, response: HttpClientResponse.HttpClientResponse],
200
199
  AiError.AiError
201
200
  > =>
202
- client.createResponse({ payload, config: { includeResponse: true } }).pipe(
203
- Effect.catchTags({
204
- HttpClientError: (error) => Errors.mapHttpClientError(error, "createResponse"),
205
- SchemaError: (error) => Effect.fail(Errors.mapSchemaError(error, "createResponse"))
206
- })
207
- )
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
+ ))
208
220
 
209
221
  const buildResponseStream = (
210
222
  response: HttpClientResponse.HttpClientResponse
211
223
  ): [
212
224
  HttpClientResponse.HttpClientResponse,
213
- Stream.Stream<typeof Generated.ResponseStreamEvent.Type, AiError.AiError>
225
+ Stream.Stream<typeof OpenAiSchema.ResponseStreamEvent.Type, AiError.AiError>
214
226
  ] => {
215
227
  const stream = response.stream.pipe(
216
228
  Stream.decodeText(),
217
- Stream.pipeThroughChannel(Sse.decodeDataSchema(Generated.ResponseStreamEvent)),
229
+ Stream.pipeThroughChannel(Sse.decodeDataSchema(OpenAiSchema.ResponseStreamEvent)),
218
230
  Stream.takeUntil((event) =>
219
231
  event.data.type === "response.completed" ||
220
232
  event.data.type === "response.incomplete"
@@ -226,7 +238,7 @@ export const make = Effect.fnUntraced(
226
238
  HttpClientError: (error) => Stream.fromEffect(Errors.mapHttpClientError(error, "createResponseStream")),
227
239
  SchemaError: (error) => Stream.fail(Errors.mapSchemaError(error, "createResponseStream"))
228
240
  })
229
- ) as any
241
+ )
230
242
  return [response, stream]
231
243
  }
232
244
 
@@ -234,31 +246,40 @@ export const make = Effect.fnUntraced(
234
246
  Effect.contextWith((services) => {
235
247
  const socket = Context.getOrUndefined(services, OpenAiSocket)
236
248
  if (socket) return socket.createResponseStream(payload)
237
- return httpClientOk.execute(
238
- HttpClientRequest.post("/responses", {
239
- body: HttpBody.jsonUnsafe({ ...payload, stream: true })
240
- })
241
- ).pipe(
242
- Effect.map(buildResponseStream),
243
- Effect.catchTag(
244
- "HttpClientError",
245
- (error) => Errors.mapHttpClientError(error, "createResponseStream")
246
- )
247
- )
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
+ ))
248
261
  })
249
262
 
263
+ const decodeEmbedding = HttpClientResponse.schemaBodyJson(OpenAiSchema.CreateEmbeddingResponse)
264
+
250
265
  const createEmbedding = (
251
- payload: typeof Generated.CreateEmbeddingRequest.Encoded
252
- ): Effect.Effect<typeof Generated.CreateEmbeddingResponse.Type, AiError.AiError> =>
253
- client.createEmbedding({ payload }).pipe(
254
- Effect.catchTags({
255
- HttpClientError: (error) => Errors.mapHttpClientError(error, "createEmbedding"),
256
- SchemaError: (error) => Effect.fail(Errors.mapSchemaError(error, "createEmbedding"))
257
- })
258
- )
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
+ ))
259
280
 
260
281
  return OpenAiClient.of({
261
- client,
282
+ client: httpClient,
262
283
  createResponse,
263
284
  createResponseStream,
264
285
  createEmbedding
@@ -349,7 +370,7 @@ export const layerConfig = (options?: {
349
370
  * @since 1.0.0
350
371
  * @category Events
351
372
  */
352
- export type ResponseStreamEvent = typeof Generated.ResponseStreamEvent.Type
373
+ export type ResponseStreamEvent = typeof OpenAiSchema.ResponseStreamEvent.Type
353
374
 
354
375
  /**
355
376
  * @since 1.0.0
@@ -360,7 +381,7 @@ export class OpenAiSocket extends Context.Service<OpenAiSocket, {
360
381
  * Create a streaming response using the OpenAI responses endpoint.
361
382
  */
362
383
  readonly createResponseStream: (
363
- options: Omit<typeof Generated.CreateResponse.Encoded, "stream">
384
+ options: Omit<typeof OpenAiSchema.CreateResponse.Encoded, "stream">
364
385
  ) => Effect.Effect<
365
386
  readonly [
366
387
  response: HttpClientResponse.HttpClientResponse,
@@ -374,14 +395,22 @@ const makeSocket = Effect.gen(function*() {
374
395
  const client = yield* OpenAiClient
375
396
  const tracker = yield* ResponseIdTracker.make
376
397
  const socketScope = yield* Effect.scope
377
- const makeRequest = Effect.orDie(client.client.httpClient.preprocess(HttpClientRequest.post("/responses")))
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
+ )
378
407
  const makeWebSocket = yield* Socket.WebSocketConstructor
379
408
 
380
409
  const decoder = new TextDecoder()
381
410
 
382
411
  const queueRef: RcRef.RcRef<
383
412
  {
384
- readonly send: (message: typeof Generated.CreateResponse.Encoded) => Effect.Effect<void, AiError.AiError>
413
+ readonly send: (message: typeof OpenAiSchema.CreateResponse.Encoded) => Effect.Effect<void, AiError.AiError>
385
414
  readonly incoming: Queue.Dequeue<ResponseStreamEvent, AiError.AiError>
386
415
  }
387
416
  > = yield* RcRef.make({
@@ -403,7 +432,7 @@ const makeSocket = Effect.gen(function*() {
403
432
  })
404
433
 
405
434
  const incoming = yield* Queue.unbounded<ResponseStreamEvent, AiError.AiError>()
406
- const send = (message: typeof Generated.CreateResponse.Encoded) =>
435
+ const send = (message: typeof OpenAiSchema.CreateResponse.Encoded) =>
407
436
  write(JSON.stringify({
408
437
  type: "response.create",
409
438
  ...message
@@ -432,15 +461,17 @@ const makeSocket = Effect.gen(function*() {
432
461
  try {
433
462
  const event = decodeEvent(text)
434
463
  if (event.type === "error" && "status" in event) {
435
- const json = JSON.stringify(event.error)
464
+ const status = Number(event.status)
465
+ const error = "error" in event ? event.error : event
466
+ const json = JSON.stringify(error)
436
467
  return Effect.fail(
437
468
  AiError.make({
438
469
  module: "OpenAiClient",
439
470
  method: "createResponseStream",
440
471
  reason: AiError.reasonFromHttpStatus({
441
472
  description: json,
442
- status: event.status,
443
- metadata: event.error,
473
+ status: isNaN(status) ? 500 : status,
474
+ metadata: error as any,
444
475
  http: {
445
476
  body: json,
446
477
  request: {
@@ -542,7 +573,7 @@ const ErrorEvent = Schema.Struct({
542
573
  })
543
574
  })
544
575
 
545
- const AllEvents = Schema.Union([ErrorEvent, Generated.ResponseStreamEvent])
576
+ const AllEvents = Schema.Union([ErrorEvent, OpenAiSchema.ResponseStreamEvent])
546
577
  const decodeEvent = Schema.decodeUnknownSync(Schema.fromJsonString(AllEvents))
547
578
 
548
579
  /**
@@ -0,0 +1,202 @@
1
+ /**
2
+ * @since 1.0.0
3
+ */
4
+ import * as Array from "effect/Array"
5
+ import type * as Config from "effect/Config"
6
+ import * as Context from "effect/Context"
7
+ import * as Effect from "effect/Effect"
8
+ import { identity } from "effect/Function"
9
+ import * as Function from "effect/Function"
10
+ import * as Layer from "effect/Layer"
11
+ import * as Predicate from "effect/Predicate"
12
+ import * as Redacted from "effect/Redacted"
13
+ import * as Headers from "effect/unstable/http/Headers"
14
+ import * as HttpClient from "effect/unstable/http/HttpClient"
15
+ import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest"
16
+ import * as Generated from "./Generated.ts"
17
+ import { OpenAiConfig } from "./OpenAiConfig.ts"
18
+
19
+ // =============================================================================
20
+ // Service Identifier
21
+ // =============================================================================
22
+
23
+ /**
24
+ * Service identifier for the generated OpenAI client.
25
+ *
26
+ * @since 1.0.0
27
+ * @category service
28
+ */
29
+ export class OpenAiClientGenerated extends Context.Service<OpenAiClientGenerated, Generated.OpenAiClient>()(
30
+ "@effect/ai-openai/OpenAiClientGenerated"
31
+ ) {}
32
+
33
+ // =============================================================================
34
+ // Options
35
+ // =============================================================================
36
+
37
+ /**
38
+ * Options for configuring the generated OpenAI client.
39
+ *
40
+ * @since 1.0.0
41
+ * @category models
42
+ */
43
+ export type Options = {
44
+ /**
45
+ * The OpenAI API key.
46
+ */
47
+ readonly apiKey?: Redacted.Redacted<string> | undefined
48
+
49
+ /**
50
+ * The base URL for the OpenAI API.
51
+ *
52
+ * @default "https://api.openai.com/v1"
53
+ */
54
+ readonly apiUrl?: string | undefined
55
+
56
+ /**
57
+ * Optional organization ID for multi-org accounts.
58
+ */
59
+ readonly organizationId?: Redacted.Redacted<string> | undefined
60
+
61
+ /**
62
+ * Optional project ID for project-scoped requests.
63
+ */
64
+ readonly projectId?: Redacted.Redacted<string> | undefined
65
+
66
+ /**
67
+ * Optional transformer for the HTTP client.
68
+ */
69
+ readonly transformClient?: ((client: HttpClient.HttpClient) => HttpClient.HttpClient) | undefined
70
+ }
71
+
72
+ const RedactedOpenAiHeaders = {
73
+ OpenAiOrganization: "OpenAI-Organization",
74
+ OpenAiProject: "OpenAI-Project"
75
+ }
76
+
77
+ // =============================================================================
78
+ // Constructor
79
+ // =============================================================================
80
+
81
+ /**
82
+ * Creates a generated OpenAI client service with the given options.
83
+ *
84
+ * @since 1.0.0
85
+ * @category constructors
86
+ */
87
+ export const make = Effect.fnUntraced(
88
+ function*(options: Options): Effect.fn.Return<Generated.OpenAiClient, never, HttpClient.HttpClient> {
89
+ const baseClient = yield* HttpClient.HttpClient
90
+ const apiUrl = options.apiUrl ?? "https://api.openai.com/v1"
91
+
92
+ const httpClient = baseClient.pipe(
93
+ HttpClient.mapRequest(Function.flow(
94
+ HttpClientRequest.prependUrl(apiUrl),
95
+ options.apiKey
96
+ ? HttpClientRequest.bearerToken(Redacted.value(options.apiKey))
97
+ : identity,
98
+ options.organizationId
99
+ ? HttpClientRequest.setHeader(
100
+ RedactedOpenAiHeaders.OpenAiOrganization,
101
+ Redacted.value(options.organizationId)
102
+ )
103
+ : identity,
104
+ options.projectId
105
+ ? HttpClientRequest.setHeader(
106
+ RedactedOpenAiHeaders.OpenAiProject,
107
+ Redacted.value(options.projectId)
108
+ )
109
+ : identity,
110
+ HttpClientRequest.acceptJson
111
+ )),
112
+ options.transformClient
113
+ ? options.transformClient
114
+ : identity
115
+ )
116
+
117
+ return Generated.make(httpClient, {
118
+ transformClient: Effect.fnUntraced(function*(client) {
119
+ const config = yield* OpenAiConfig.getOrUndefined
120
+ if (Predicate.isNotUndefined(config?.transformClient)) {
121
+ return config.transformClient(client)
122
+ }
123
+ return client
124
+ })
125
+ })
126
+ },
127
+ Effect.updateService(
128
+ Headers.CurrentRedactedNames,
129
+ Array.appendAll(Object.values(RedactedOpenAiHeaders))
130
+ )
131
+ )
132
+
133
+ // =============================================================================
134
+ // Layers
135
+ // =============================================================================
136
+
137
+ /**
138
+ * Creates a layer for the generated OpenAI client with the given options.
139
+ *
140
+ * @since 1.0.0
141
+ * @category layers
142
+ */
143
+ export const layer = (options: Options): Layer.Layer<OpenAiClientGenerated, never, HttpClient.HttpClient> =>
144
+ Layer.effect(OpenAiClientGenerated, make(options))
145
+
146
+ /**
147
+ * Creates a layer for the generated OpenAI client, loading the requisite
148
+ * configuration via Effect's `Config` module.
149
+ *
150
+ * @since 1.0.0
151
+ * @category layers
152
+ */
153
+ export const layerConfig = (options?: {
154
+ /**
155
+ * The config value to load for the API key.
156
+ */
157
+ readonly apiKey?: Config.Config<Redacted.Redacted<string> | undefined> | undefined
158
+
159
+ /**
160
+ * The config value to load for the API URL.
161
+ */
162
+ readonly apiUrl?: Config.Config<string> | undefined
163
+
164
+ /**
165
+ * The config value to load for the organization ID.
166
+ */
167
+ readonly organizationId?: Config.Config<Redacted.Redacted<string> | undefined> | undefined
168
+
169
+ /**
170
+ * The config value to load for the project ID.
171
+ */
172
+ readonly projectId?: Config.Config<Redacted.Redacted<string> | undefined> | undefined
173
+
174
+ /**
175
+ * Optional transformer for the HTTP client.
176
+ */
177
+ readonly transformClient?: ((client: HttpClient.HttpClient) => HttpClient.HttpClient) | undefined
178
+ }): Layer.Layer<OpenAiClientGenerated, Config.ConfigError, HttpClient.HttpClient> =>
179
+ Layer.effect(
180
+ OpenAiClientGenerated,
181
+ Effect.gen(function*() {
182
+ const apiKey = Predicate.isNotUndefined(options?.apiKey)
183
+ ? yield* options.apiKey :
184
+ undefined
185
+ const apiUrl = Predicate.isNotUndefined(options?.apiUrl)
186
+ ? yield* options.apiUrl :
187
+ undefined
188
+ const organizationId = Predicate.isNotUndefined(options?.organizationId)
189
+ ? yield* options.organizationId
190
+ : undefined
191
+ const projectId = Predicate.isNotUndefined(options?.projectId)
192
+ ? yield* options.projectId :
193
+ undefined
194
+ return yield* make({
195
+ apiKey,
196
+ apiUrl,
197
+ organizationId,
198
+ projectId,
199
+ transformClient: options?.transformClient
200
+ })
201
+ })
202
+ )
@@ -13,8 +13,8 @@ import type { Simplify } from "effect/Types"
13
13
  import * as AiError from "effect/unstable/ai/AiError"
14
14
  import * as EmbeddingModel from "effect/unstable/ai/EmbeddingModel"
15
15
  import * as AiModel from "effect/unstable/ai/Model"
16
- import type * as Generated from "./Generated.ts"
17
16
  import { OpenAiClient } from "./OpenAiClient.ts"
17
+ import type * as OpenAiSchema from "./OpenAiSchema.ts"
18
18
 
19
19
  /**
20
20
  * @since 1.0.0
@@ -33,7 +33,7 @@ export class Config extends Context.Service<
33
33
  Simplify<
34
34
  & Partial<
35
35
  Omit<
36
- typeof Generated.CreateEmbeddingRequest.Encoded,
36
+ typeof OpenAiSchema.CreateEmbeddingRequest.Encoded,
37
37
  "input"
38
38
  >
39
39
  >
@@ -155,7 +155,7 @@ export const withConfigOverride: {
155
155
 
156
156
  const mapProviderResponse = (
157
157
  inputLength: number,
158
- response: typeof Generated.CreateEmbeddingResponse.Type
158
+ response: typeof OpenAiSchema.CreateEmbeddingResponse.Type
159
159
  ): Effect.Effect<EmbeddingModel.ProviderResponse, AiError.AiError> => {
160
160
  if (response.data.length !== inputLength) {
161
161
  return Effect.fail(
@@ -173,6 +173,9 @@ const mapProviderResponse = (
173
173
  if (seen.has(entry.index)) {
174
174
  return Effect.fail(invalidOutput("Provider returned duplicate embedding index: " + entry.index))
175
175
  }
176
+ if (!Array.isArray(entry.embedding)) {
177
+ return Effect.fail(invalidOutput("Provider returned non-vector embedding at index " + entry.index))
178
+ }
176
179
 
177
180
  seen.add(entry.index)
178
181
  results[entry.index] = [...entry.embedding]