1/**
2 * @stability unstable
3 * @since 4.0.0
4 */
5import * as Array from "effect/Array"
6import type * as Config from "effect/Config"
7import * as Context from "effect/Context"
8import * as Effect from "effect/Effect"
9import { identity } from "effect/Function"
10import * as Function from "effect/Function"
11import * as Headers from "effect/http/Headers"
12import * as HttpClient from "effect/http/HttpClient"
13import * as HttpClientRequest from "effect/http/HttpClientRequest"
14import * as Layer from "effect/Layer"
15import * as Predicate from "effect/Predicate"
16import * as Redacted from "effect/Redacted"
17import * as Generated from "./Generated.ts"
18import { OpenAiConfig } from "./OpenAiConfig.ts"
19
20// =============================================================================
21// Service Identifier
22// =============================================================================
23
24/**
25 * Service identifier for the generated OpenAI client.
26 *
27 * @since 4.0.0
28 * @stability unstable
29 * @category service
30 */
31export class OpenAiClientGenerated extends Context.Service<OpenAiClientGenerated, Generated.OpenAiClient>()(
32 "@effect/ai-openai/OpenAiClientGenerated"
33) {}
34
35// =============================================================================
36// Options
37// =============================================================================
38
39/**
40 * Options for configuring the generated OpenAI client.
41 *
42 * @since 4.0.0
43 * @stability unstable
44 * @category options
45 */
46export type Options = {
47 /**
48 * The OpenAI API key.
49 */
50 readonly apiKey?: Redacted.Redacted<string> | undefined
51
52 /**
53 * The base URL for the OpenAI API.
54 *
55 * @default "https://api.openai.com/v1"
56 */
57 readonly apiUrl?: string | undefined
58
59 /**
60 * Optional organization ID for multi-org accounts.
61 */
62 readonly organizationId?: Redacted.Redacted<string> | undefined
63
64 /**
65 * Optional project ID for project-scoped requests.
66 */
67 readonly projectId?: Redacted.Redacted<string> | undefined
68
69 /**
70 * Optional transformer for the HTTP client.
71 */
72 readonly transformClient?: ((client: HttpClient.HttpClient) => HttpClient.HttpClient) | undefined
73}
74
75const RedactedOpenAiHeaders = {
76 OpenAiOrganization: "OpenAI-Organization",
77 OpenAiProject: "OpenAI-Project"
78}
79
80const withRedactedHeaders = Effect.updateService(
81 Headers.CurrentRedactedNames,
82 Array.appendAll(Object.values(RedactedOpenAiHeaders))
83)
84
85// =============================================================================
86// Constructor
87// =============================================================================
88
89/**
90 * Creates a generated OpenAI client service with the given options.
91 *
92 * @since 4.0.0
93 * @stability unstable
94 * @category constructors
95 */
96export const make = Effect.fnUntraced(
97 function*(options: Options): Effect.fn.Return<Generated.OpenAiClient, never, HttpClient.HttpClient> {
98 const baseClient = yield* HttpClient.HttpClient
99 const apiUrl = options.apiUrl ?? "https://api.openai.com/v1"
100
101 const httpClient = baseClient.pipe(
102 HttpClient.mapRequest(Function.flow(
103 HttpClientRequest.prependUrl(apiUrl),
104 options.apiKey
105 ? HttpClientRequest.bearerToken(Redacted.value(options.apiKey))
106 : identity,
107 options.organizationId
108 ? HttpClientRequest.setHeader(
109 RedactedOpenAiHeaders.OpenAiOrganization,
110 Redacted.value(options.organizationId)
111 )
112 : identity,
113 options.projectId
114 ? HttpClientRequest.setHeader(
115 RedactedOpenAiHeaders.OpenAiProject,
116 Redacted.value(options.projectId)
117 )
118 : identity,
119 HttpClientRequest.acceptJson
120 )),
121 options.transformClient
122 ? options.transformClient
123 : identity
124 )
125
126 return Generated.make(httpClient, {
127 transformClient: Effect.fnUntraced(function*(client) {
128 const config = yield* OpenAiConfig.getOrUndefined
129 if (Predicate.isNotUndefined(config?.transformClient)) {
130 return config.transformClient(client)
131 }
132 return client
133 })
134 })
135 },
136 withRedactedHeaders
137)
138
139// =============================================================================
140// Layers
141// =============================================================================
142
143/**
144 * Creates a layer for the generated OpenAI client with the given options.
145 *
146 * @since 4.0.0
147 * @stability unstable
148 * @category layers
149 */
150export const layer = (options: Options): Layer.Layer<OpenAiClientGenerated, never, HttpClient.HttpClient> =>
151 Layer.effect(OpenAiClientGenerated, make(options))
152
153/**
154 * Creates a layer for the generated OpenAI client, loading the requisite
155 * configuration via Effect's `Config` module.
156 *
157 * @since 4.0.0
158 * @stability unstable
159 * @category layers
160 */
161export const layerConfig = (options?: {
162 /**
163 * The config value to load for the API key.
164 */
165 readonly apiKey?: Config.Config<Redacted.Redacted<string> | undefined> | undefined
166
167 /**
168 * The config value to load for the API URL.
169 */
170 readonly apiUrl?: Config.Config<string> | undefined
171
172 /**
173 * The config value to load for the organization ID.
174 */
175 readonly organizationId?: Config.Config<Redacted.Redacted<string> | undefined> | undefined
176
177 /**
178 * The config value to load for the project ID.
179 */
180 readonly projectId?: Config.Config<Redacted.Redacted<string> | undefined> | undefined
181
182 /**
183 * Optional transformer for the HTTP client.
184 */
185 readonly transformClient?: ((client: HttpClient.HttpClient) => HttpClient.HttpClient) | undefined
186}): Layer.Layer<OpenAiClientGenerated, Config.ConfigError, HttpClient.HttpClient> =>
187 Layer.effect(
188 OpenAiClientGenerated,
189 Effect.gen(function*() {
190 const apiKey = Predicate.isNotUndefined(options?.apiKey)
191 ? yield* options.apiKey :
192 undefined
193 const apiUrl = Predicate.isNotUndefined(options?.apiUrl)
194 ? yield* options.apiUrl :
195 undefined
196 const organizationId = Predicate.isNotUndefined(options?.organizationId)
197 ? yield* options.organizationId
198 : undefined
199 const projectId = Predicate.isNotUndefined(options?.projectId)
200 ? yield* options.projectId :
201 undefined
202 return yield* make({
203 apiKey,
204 apiUrl,
205 organizationId,
206 projectId,
207 transformClient: options?.transformClient
208 })
209 })
210 )