@effect/ai-openai 4.0.0-beta.7 → 4.0.0-beta.70

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 (58) hide show
  1. package/dist/Generated.d.ts +66734 -37723
  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 +81 -25
  6. package/dist/OpenAiClient.d.ts.map +1 -1
  7. package/dist/OpenAiClient.js +220 -39
  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 +45 -10
  14. package/dist/OpenAiConfig.d.ts.map +1 -1
  15. package/dist/OpenAiConfig.js +31 -7
  16. package/dist/OpenAiConfig.js.map +1 -1
  17. package/dist/OpenAiEmbeddingModel.d.ts +89 -0
  18. package/dist/OpenAiEmbeddingModel.d.ts.map +1 -0
  19. package/dist/OpenAiEmbeddingModel.js +121 -0
  20. package/dist/OpenAiEmbeddingModel.js.map +1 -0
  21. package/dist/OpenAiError.d.ts +168 -35
  22. package/dist/OpenAiError.d.ts.map +1 -1
  23. package/dist/OpenAiError.js +1 -1
  24. package/dist/OpenAiLanguageModel.d.ts +250 -57
  25. package/dist/OpenAiLanguageModel.d.ts.map +1 -1
  26. package/dist/OpenAiLanguageModel.js +311 -160
  27. package/dist/OpenAiLanguageModel.js.map +1 -1
  28. package/dist/OpenAiSchema.d.ts +2029 -0
  29. package/dist/OpenAiSchema.d.ts.map +1 -0
  30. package/dist/OpenAiSchema.js +591 -0
  31. package/dist/OpenAiSchema.js.map +1 -0
  32. package/dist/OpenAiTelemetry.d.ts +31 -18
  33. package/dist/OpenAiTelemetry.d.ts.map +1 -1
  34. package/dist/OpenAiTelemetry.js +6 -4
  35. package/dist/OpenAiTelemetry.js.map +1 -1
  36. package/dist/OpenAiTool.d.ts +56 -67
  37. package/dist/OpenAiTool.d.ts.map +1 -1
  38. package/dist/OpenAiTool.js +33 -44
  39. package/dist/OpenAiTool.js.map +1 -1
  40. package/dist/index.d.ts +42 -8
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +42 -8
  43. package/dist/index.js.map +1 -1
  44. package/dist/internal/errors.js +4 -4
  45. package/dist/internal/errors.js.map +1 -1
  46. package/package.json +3 -3
  47. package/src/Generated.ts +9858 -5044
  48. package/src/OpenAiClient.ts +396 -90
  49. package/src/OpenAiClientGenerated.ts +202 -0
  50. package/src/OpenAiConfig.ts +46 -11
  51. package/src/OpenAiEmbeddingModel.ts +207 -0
  52. package/src/OpenAiError.ts +170 -35
  53. package/src/OpenAiLanguageModel.ts +633 -157
  54. package/src/OpenAiSchema.ts +984 -0
  55. package/src/OpenAiTelemetry.ts +32 -19
  56. package/src/OpenAiTool.ts +34 -45
  57. package/src/index.ts +45 -8
  58. package/src/internal/errors.ts +6 -4
@@ -0,0 +1,202 @@
1
+ /**
2
+ * @since 4.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 4.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 4.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 4.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 4.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 4.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
+ )
@@ -1,35 +1,61 @@
1
1
  /**
2
- * @since 1.0.0
2
+ * The `OpenAiConfig` module provides contextual configuration for the
3
+ * `@effect/ai-openai` integration. It is used to customize how OpenAI clients
4
+ * are built and interpreted without threading configuration through every API
5
+ * call manually.
6
+ *
7
+ * The primary use case is installing an HTTP client transform with
8
+ * {@link withClientTransform}. This lets applications adapt the underlying
9
+ * OpenAI HTTP client for cross-cutting concerns such as custom middleware,
10
+ * instrumentation, proxying, or request policy changes while keeping the
11
+ * OpenAI service APIs unchanged.
12
+ *
13
+ * Configuration is scoped through Effect's context, so transforms only apply to
14
+ * the effect they are provided to and anything evaluated inside that scope.
15
+ * When multiple transforms are needed, compose them into a single
16
+ * `HttpClient => HttpClient` function before providing the configuration.
17
+ *
18
+ * @since 4.0.0
3
19
  */
20
+ import * as Context from "effect/Context"
4
21
  import * as Effect from "effect/Effect"
5
22
  import { dual } from "effect/Function"
6
- import * as ServiceMap from "effect/ServiceMap"
7
23
  import type { HttpClient } from "effect/unstable/http/HttpClient"
8
24
 
9
25
  /**
10
- * @since 1.0.0
26
+ * Context service carrying scoped OpenAI configuration for provider
27
+ * operations.
28
+ *
11
29
  * @category services
30
+ * @since 4.0.0
12
31
  */
13
- export class OpenAiConfig extends ServiceMap.Service<
32
+ export class OpenAiConfig extends Context.Service<
14
33
  OpenAiConfig,
15
34
  OpenAiConfig.Service
16
35
  >()("@effect/ai-openai/OpenAiConfig") {
17
36
  /**
18
- * @since 1.0.0
37
+ * Gets the configured OpenAI service from the current context when present.
38
+ *
39
+ * @since 4.0.0
19
40
  */
20
41
  static readonly getOrUndefined: Effect.Effect<typeof OpenAiConfig.Service | undefined> = Effect.map(
21
- Effect.services<never>(),
42
+ Effect.context<never>(),
22
43
  (context) => context.mapUnsafe.get(OpenAiConfig.key)
23
44
  )
24
45
  }
25
46
 
26
47
  /**
27
- * @since 1.0.0
48
+ * Types used by the `OpenAiConfig` context service.
49
+ *
50
+ * @since 4.0.0
28
51
  */
29
52
  export declare namespace OpenAiConfig {
30
53
  /**
31
- * @since 1.0.
54
+ * Configuration values read by OpenAI provider operations when executing
55
+ * requests.
56
+ *
32
57
  * @category models
58
+ * @since 4.0.0
33
59
  */
34
60
  export interface Service {
35
61
  readonly transformClient?: ((client: HttpClient) => HttpClient) | undefined
@@ -37,18 +63,27 @@ export declare namespace OpenAiConfig {
37
63
  }
38
64
 
39
65
  /**
40
- * @since 1.0.0
66
+ * Provides a scoped transform for the OpenAI HTTP client used by provider
67
+ * operations.
68
+ *
41
69
  * @category configuration
70
+ * @since 4.0.0
42
71
  */
43
72
  export const withClientTransform: {
44
73
  /**
45
- * @since 1.0.0
74
+ * Provides a scoped transform for the OpenAI HTTP client used by provider
75
+ * operations.
76
+ *
46
77
  * @category configuration
78
+ * @since 4.0.0
47
79
  */
48
80
  (transform: (client: HttpClient) => HttpClient): <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>
49
81
  /**
50
- * @since 1.0.0
82
+ * Provides a scoped transform for the OpenAI HTTP client used by provider
83
+ * operations.
84
+ *
51
85
  * @category configuration
86
+ * @since 4.0.0
52
87
  */
53
88
  <A, E, R>(
54
89
  self: Effect.Effect<A, E, R>,
@@ -0,0 +1,207 @@
1
+ /**
2
+ * OpenAI Embedding Model implementation.
3
+ *
4
+ * Provides an EmbeddingModel implementation for OpenAI's embeddings API.
5
+ *
6
+ * @since 4.0.0
7
+ */
8
+ import * as Context from "effect/Context"
9
+ import * as Effect from "effect/Effect"
10
+ import { dual } from "effect/Function"
11
+ import * as Layer from "effect/Layer"
12
+ import type { Simplify } from "effect/Types"
13
+ import * as AiError from "effect/unstable/ai/AiError"
14
+ import * as EmbeddingModel from "effect/unstable/ai/EmbeddingModel"
15
+ import * as AiModel from "effect/unstable/ai/Model"
16
+ import { OpenAiClient } from "./OpenAiClient.ts"
17
+ import type * as OpenAiSchema from "./OpenAiSchema.ts"
18
+
19
+ /**
20
+ * Model identifiers supported by OpenAI's embeddings API.
21
+ *
22
+ * @category models
23
+ * @since 4.0.0
24
+ */
25
+ export type Model = "text-embedding-ada-002" | "text-embedding-3-small" | "text-embedding-3-large"
26
+
27
+ /**
28
+ * Service definition for OpenAI embedding model configuration.
29
+ *
30
+ * @category services
31
+ * @since 4.0.0
32
+ */
33
+ export class Config extends Context.Service<
34
+ Config,
35
+ Simplify<
36
+ & Partial<
37
+ Omit<
38
+ typeof OpenAiSchema.CreateEmbeddingRequest.Encoded,
39
+ "input"
40
+ >
41
+ >
42
+ & {
43
+ readonly [x: string]: unknown
44
+ }
45
+ >
46
+ >()("@effect/ai-openai/OpenAiEmbeddingModel/Config") {}
47
+
48
+ /**
49
+ * Creates an `AiModel` for an OpenAI embedding model with its configured vector dimensions.
50
+ *
51
+ * @category constructors
52
+ * @since 4.0.0
53
+ */
54
+ export const model = (
55
+ model: (string & {}) | Model,
56
+ options: {
57
+ readonly dimensions: number
58
+ readonly config?: Omit<typeof Config.Service, "model" | "dimensions">
59
+ }
60
+ ): AiModel.Model<"openai", EmbeddingModel.EmbeddingModel | EmbeddingModel.Dimensions, OpenAiClient> =>
61
+ AiModel.make(
62
+ "openai",
63
+ model,
64
+ Layer.merge(
65
+ layer({
66
+ model,
67
+ config: {
68
+ ...options.config,
69
+ dimensions: options.dimensions
70
+ }
71
+ }),
72
+ Layer.succeed(EmbeddingModel.Dimensions, options.dimensions)
73
+ )
74
+ )
75
+
76
+ /**
77
+ * Creates an OpenAI embedding model service.
78
+ *
79
+ * @category constructors
80
+ * @since 4.0.0
81
+ */
82
+ export const make = Effect.fnUntraced(function*({ model, config: providerConfig }: {
83
+ readonly model: (string & {}) | Model
84
+ readonly config?: Omit<typeof Config.Service, "model"> | undefined
85
+ }): Effect.fn.Return<EmbeddingModel.Service, never, OpenAiClient> {
86
+ const client = yield* OpenAiClient
87
+
88
+ const makeConfig = Effect.gen(function*() {
89
+ const services = yield* Effect.context<never>()
90
+ return { model, ...providerConfig, ...services.mapUnsafe.get(Config.key) }
91
+ })
92
+
93
+ return yield* EmbeddingModel.make({
94
+ embedMany: Effect.fnUntraced(function*({ inputs }) {
95
+ const config = yield* makeConfig
96
+ const response = yield* client.createEmbedding({ ...config, input: inputs })
97
+ return yield* mapProviderResponse(inputs.length, response)
98
+ })
99
+ })
100
+ })
101
+
102
+ /**
103
+ * Creates a layer for the OpenAI embedding model.
104
+ *
105
+ * @category layers
106
+ * @since 4.0.0
107
+ */
108
+ export const layer = (options: {
109
+ readonly model: (string & {}) | Model
110
+ readonly config?: Omit<typeof Config.Service, "model"> | undefined
111
+ }): Layer.Layer<EmbeddingModel.EmbeddingModel, never, OpenAiClient> =>
112
+ Layer.effect(EmbeddingModel.EmbeddingModel, make(options))
113
+
114
+ /**
115
+ * Provides config overrides for OpenAI embedding model operations.
116
+ *
117
+ * @category configuration
118
+ * @since 4.0.0
119
+ */
120
+ export const withConfigOverride: {
121
+ /**
122
+ * Provides config overrides for OpenAI embedding model operations.
123
+ *
124
+ * @category configuration
125
+ * @since 4.0.0
126
+ */
127
+ (overrides: typeof Config.Service): <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, Config>>
128
+ /**
129
+ * Provides config overrides for OpenAI embedding model operations.
130
+ *
131
+ * @category configuration
132
+ * @since 4.0.0
133
+ */
134
+ <A, E, R>(self: Effect.Effect<A, E, R>, overrides: typeof Config.Service): Effect.Effect<A, E, Exclude<R, Config>>
135
+ } = dual<
136
+ /**
137
+ * Provides config overrides for OpenAI embedding model operations.
138
+ *
139
+ * @category configuration
140
+ * @since 4.0.0
141
+ */
142
+ (overrides: typeof Config.Service) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, Config>>,
143
+ /**
144
+ * Provides config overrides for OpenAI embedding model operations.
145
+ *
146
+ * @category configuration
147
+ * @since 4.0.0
148
+ */
149
+ <A, E, R>(self: Effect.Effect<A, E, R>, overrides: typeof Config.Service) => Effect.Effect<A, E, Exclude<R, Config>>
150
+ >(2, (self, overrides) =>
151
+ Effect.flatMap(
152
+ Effect.serviceOption(Config),
153
+ (config) =>
154
+ Effect.provideService(self, Config, {
155
+ ...(config._tag === "Some" ? config.value : {}),
156
+ ...overrides
157
+ })
158
+ ))
159
+
160
+ const mapProviderResponse = (
161
+ inputLength: number,
162
+ response: typeof OpenAiSchema.CreateEmbeddingResponse.Type
163
+ ): Effect.Effect<EmbeddingModel.ProviderResponse, AiError.AiError> => {
164
+ if (response.data.length !== inputLength) {
165
+ return Effect.fail(
166
+ invalidOutput("Provider returned " + response.data.length + " embeddings but expected " + inputLength)
167
+ )
168
+ }
169
+
170
+ const results = new Array<Array<number>>(inputLength)
171
+ const seen = new Set<number>()
172
+
173
+ for (const entry of response.data) {
174
+ if (!Number.isInteger(entry.index) || entry.index < 0 || entry.index >= inputLength) {
175
+ return Effect.fail(invalidOutput("Provider returned invalid embedding index: " + entry.index))
176
+ }
177
+ if (seen.has(entry.index)) {
178
+ return Effect.fail(invalidOutput("Provider returned duplicate embedding index: " + entry.index))
179
+ }
180
+ if (!Array.isArray(entry.embedding)) {
181
+ return Effect.fail(invalidOutput("Provider returned non-vector embedding at index " + entry.index))
182
+ }
183
+
184
+ seen.add(entry.index)
185
+ results[entry.index] = [...entry.embedding]
186
+ }
187
+
188
+ if (seen.size !== inputLength) {
189
+ return Effect.fail(
190
+ invalidOutput("Provider returned embeddings for " + seen.size + " inputs but expected " + inputLength)
191
+ )
192
+ }
193
+
194
+ return Effect.succeed({
195
+ results,
196
+ usage: {
197
+ inputTokens: response.usage?.prompt_tokens
198
+ }
199
+ })
200
+ }
201
+
202
+ const invalidOutput = (description: string): AiError.AiError =>
203
+ AiError.make({
204
+ module: "OpenAiEmbeddingModel",
205
+ method: "embedMany",
206
+ reason: new AiError.InvalidOutputError({ description })
207
+ })