@effect/ai-openrouter 0.8.2 → 4.0.0-beta.0

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 (80) hide show
  1. package/dist/Generated.d.ts +19505 -0
  2. package/dist/Generated.d.ts.map +1 -0
  3. package/dist/Generated.js +5115 -0
  4. package/dist/Generated.js.map +1 -0
  5. package/dist/OpenRouterClient.d.ts +116 -0
  6. package/dist/OpenRouterClient.d.ts.map +1 -0
  7. package/dist/OpenRouterClient.js +120 -0
  8. package/dist/OpenRouterClient.js.map +1 -0
  9. package/dist/{dts/OpenRouterConfig.d.ts → OpenRouterConfig.d.ts} +9 -9
  10. package/dist/OpenRouterConfig.d.ts.map +1 -0
  11. package/dist/{esm/OpenRouterConfig.js → OpenRouterConfig.js} +8 -5
  12. package/dist/OpenRouterConfig.js.map +1 -0
  13. package/dist/OpenRouterError.d.ts +83 -0
  14. package/dist/OpenRouterError.d.ts.map +1 -0
  15. package/dist/OpenRouterError.js +10 -0
  16. package/dist/OpenRouterError.js.map +1 -0
  17. package/dist/OpenRouterLanguageModel.d.ts +285 -0
  18. package/dist/OpenRouterLanguageModel.d.ts.map +1 -0
  19. package/dist/OpenRouterLanguageModel.js +1210 -0
  20. package/dist/OpenRouterLanguageModel.js.map +1 -0
  21. package/dist/index.d.ts +29 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +30 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/internal/errors.d.ts +2 -0
  26. package/dist/internal/errors.d.ts.map +1 -0
  27. package/dist/internal/errors.js +347 -0
  28. package/dist/internal/errors.js.map +1 -0
  29. package/dist/{dts/internal → internal}/utilities.d.ts.map +1 -1
  30. package/dist/internal/utilities.js +77 -0
  31. package/dist/internal/utilities.js.map +1 -0
  32. package/package.json +45 -62
  33. package/src/Generated.ts +9312 -5435
  34. package/src/OpenRouterClient.ts +223 -304
  35. package/src/OpenRouterConfig.ts +14 -14
  36. package/src/OpenRouterError.ts +92 -0
  37. package/src/OpenRouterLanguageModel.ts +941 -572
  38. package/src/index.ts +20 -4
  39. package/src/internal/errors.ts +373 -0
  40. package/src/internal/utilities.ts +78 -11
  41. package/Generated/package.json +0 -6
  42. package/OpenRouterClient/package.json +0 -6
  43. package/OpenRouterConfig/package.json +0 -6
  44. package/OpenRouterLanguageModel/package.json +0 -6
  45. package/README.md +0 -5
  46. package/dist/cjs/Generated.js +0 -5813
  47. package/dist/cjs/Generated.js.map +0 -1
  48. package/dist/cjs/OpenRouterClient.js +0 -229
  49. package/dist/cjs/OpenRouterClient.js.map +0 -1
  50. package/dist/cjs/OpenRouterConfig.js +0 -30
  51. package/dist/cjs/OpenRouterConfig.js.map +0 -1
  52. package/dist/cjs/OpenRouterLanguageModel.js +0 -826
  53. package/dist/cjs/OpenRouterLanguageModel.js.map +0 -1
  54. package/dist/cjs/index.js +0 -16
  55. package/dist/cjs/index.js.map +0 -1
  56. package/dist/cjs/internal/utilities.js +0 -29
  57. package/dist/cjs/internal/utilities.js.map +0 -1
  58. package/dist/dts/Generated.d.ts +0 -11026
  59. package/dist/dts/Generated.d.ts.map +0 -1
  60. package/dist/dts/OpenRouterClient.d.ts +0 -407
  61. package/dist/dts/OpenRouterClient.d.ts.map +0 -1
  62. package/dist/dts/OpenRouterConfig.d.ts.map +0 -1
  63. package/dist/dts/OpenRouterLanguageModel.d.ts +0 -215
  64. package/dist/dts/OpenRouterLanguageModel.d.ts.map +0 -1
  65. package/dist/dts/index.d.ts +0 -17
  66. package/dist/dts/index.d.ts.map +0 -1
  67. package/dist/esm/Generated.js +0 -5457
  68. package/dist/esm/Generated.js.map +0 -1
  69. package/dist/esm/OpenRouterClient.js +0 -214
  70. package/dist/esm/OpenRouterClient.js.map +0 -1
  71. package/dist/esm/OpenRouterConfig.js.map +0 -1
  72. package/dist/esm/OpenRouterLanguageModel.js +0 -815
  73. package/dist/esm/OpenRouterLanguageModel.js.map +0 -1
  74. package/dist/esm/index.js +0 -17
  75. package/dist/esm/index.js.map +0 -1
  76. package/dist/esm/internal/utilities.js +0 -21
  77. package/dist/esm/internal/utilities.js.map +0 -1
  78. package/dist/esm/package.json +0 -4
  79. package/index/package.json +0 -6
  80. /package/dist/{dts/internal → internal}/utilities.d.ts +0 -0
@@ -1,372 +1,291 @@
1
1
  /**
2
2
  * @since 1.0.0
3
3
  */
4
- import * as AiError from "@effect/ai/AiError"
5
- import * as Sse from "@effect/experimental/Sse"
6
- import * as HttpBody from "@effect/platform/HttpBody"
7
- import * as HttpClient from "@effect/platform/HttpClient"
8
- import * as HttpClientRequest from "@effect/platform/HttpClientRequest"
9
- import * as Config from "effect/Config"
10
- import type { ConfigError } from "effect/ConfigError"
11
- import * as Context from "effect/Context"
4
+ import type * as Config from "effect/Config"
12
5
  import * as Effect from "effect/Effect"
13
6
  import { identity } from "effect/Function"
14
7
  import * as Layer from "effect/Layer"
8
+ import * as Predicate from "effect/Predicate"
15
9
  import type * as Redacted from "effect/Redacted"
16
10
  import * as Schema from "effect/Schema"
11
+ import * as ServiceMap from "effect/ServiceMap"
17
12
  import * as Stream from "effect/Stream"
18
- import * as Generated from "./Generated.js"
19
- import { OpenRouterConfig } from "./OpenRouterConfig.js"
13
+ import type * as AiError from "effect/unstable/ai/AiError"
14
+ import * as Sse from "effect/unstable/encoding/Sse"
15
+ import * as HttpBody from "effect/unstable/http/HttpBody"
16
+ import * as HttpClient from "effect/unstable/http/HttpClient"
17
+ import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest"
18
+ import type * as HttpClientResponse from "effect/unstable/http/HttpClientResponse"
19
+ import * as Generated from "./Generated.ts"
20
+ import * as Errors from "./internal/errors.ts"
21
+ import { OpenRouterConfig } from "./OpenRouterConfig.ts"
20
22
 
21
- /**
22
- * @since 1.0.0
23
- * @category Context
24
- */
25
- export class OpenRouterClient extends Context.Tag(
26
- "@effect/ai-openrouter/OpenRouterClient"
27
- )<OpenRouterClient, Service>() {}
23
+ // =============================================================================
24
+ // Service Interface
25
+ // =============================================================================
28
26
 
29
27
  /**
28
+ * The OpenRouter client service interface.
29
+ *
30
+ * Provides methods for interacting with OpenRouter's Chat Completions API,
31
+ * including both synchronous and streaming message creation.
32
+ *
30
33
  * @since 1.0.0
31
- * @category Models
34
+ * @category models
32
35
  */
33
36
  export interface Service {
34
- /**
35
- * The underlying HTTP client capable of communicating with the OpenRouter API.
36
- *
37
- * This client is pre-configured with authentication, base URL, and standard
38
- * headers required for OpenRouter API communication. It provides direct access
39
- * to the generated OpenRouter API client for operations not covered by the
40
- * higher-level methods.
41
- *
42
- * Use this when you need to:
43
- * - Access provider-specific API endpoints not available through the AI SDK
44
- * - Implement custom request/response handling
45
- * - Use OpenRouter API features not yet supported by the Effect AI abstractions
46
- * - Perform batch operations or non-streaming requests
47
- *
48
- * The client automatically handles authentication and follows OpenRouter's
49
- * API conventions for request formatting and error handling.
50
- */
51
- readonly client: Generated.Client
37
+ readonly client: Generated.OpenRouterClient
52
38
 
53
39
  readonly createChatCompletion: (
54
40
  options: typeof Generated.ChatGenerationParams.Encoded
55
- ) => Effect.Effect<Generated.ChatResponse, AiError.AiError>
41
+ ) => Effect.Effect<
42
+ [body: typeof Generated.SendChatCompletionRequest200.Type, response: HttpClientResponse.HttpClientResponse],
43
+ AiError.AiError
44
+ >
56
45
 
57
46
  readonly createChatCompletionStream: (
58
- options: Omit<typeof Generated.ChatGenerationParams.Encoded, "stream">
59
- ) => Stream.Stream<ChatStreamingResponseChunk, AiError.AiError>
47
+ options: Omit<typeof Generated.ChatGenerationParams.Encoded, "stream" | "stream_options">
48
+ ) => Effect.Effect<
49
+ [
50
+ response: HttpClientResponse.HttpClientResponse,
51
+ stream: Stream.Stream<ChatStreamingResponseChunkData, AiError.AiError>
52
+ ],
53
+ AiError.AiError
54
+ >
60
55
  }
61
56
 
62
57
  /**
63
58
  * @since 1.0.0
64
- * @category Constructors
59
+ * @category Models
60
+ */
61
+ export type ChatStreamingResponseChunkData = typeof Generated.ChatStreamingResponseChunk.fields.data.Type
62
+
63
+ // =============================================================================
64
+ // Service Identifier
65
+ // =============================================================================
66
+
67
+ /**
68
+ * Service identifier for the OpenRouter client.
69
+ *
70
+ * @since 1.0.0
71
+ * @category service
72
+ */
73
+ export class OpenRouterClient extends ServiceMap.Service<
74
+ OpenRouterClient,
75
+ Service
76
+ >()("@effect/ai-openrouter/OpenRouterClient") {}
77
+
78
+ // =============================================================================
79
+ // Options
80
+ // =============================================================================
81
+
82
+ /**
83
+ * Configuration options for creating an OpenRouter client.
84
+ *
85
+ * @since 1.0.0
86
+ * @category models
65
87
  */
66
- export const make: (options: {
67
- readonly apiKey?: Redacted.Redacted | undefined
88
+ export type Options = {
89
+ readonly apiKey?: Redacted.Redacted<string> | undefined
90
+
68
91
  readonly apiUrl?: string | undefined
92
+
69
93
  /**
70
94
  * Optional URL of your site for rankings on `openrouter.ai`.
71
95
  */
72
- readonly referrer?: string | undefined
96
+ readonly siteReferrer?: string | undefined
97
+
73
98
  /**
74
99
  * Optional title of your site for rankings on `openrouter.ai`.
75
100
  */
76
- readonly title?: string | undefined
101
+ readonly siteTitle?: string | undefined
102
+
77
103
  /**
78
- * A function to transform the underlying HTTP client before it's used to send
79
- * API requests.
104
+ * Optional transformer for the underlying HTTP client.
80
105
  *
81
- * This transformation function receives the configured HTTP client and returns
82
- * a modified version. It's applied after all standard client configuration
83
- * (authentication, base URL, headers) but before any requests are made.
84
- *
85
- * Use this for:
86
- * - Adding custom middleware (logging, metrics, caching)
87
- * - Modifying request/response processing behavior
88
- * - Adding custom retry logic or error handling
89
- * - Integrating with monitoring or debugging tools
90
- * - Applying organization-specific HTTP client policies
91
- *
92
- * The transformation is applied once during client initialization and affects
93
- * all subsequent API requests made through this client instance.
94
- *
95
- * Leave absent or set to `undefined` if no custom HTTP client behavior is
96
- * needed.
106
+ * Use this to add middleware, logging, or custom request/response handling.
97
107
  */
98
108
  readonly transformClient?: ((client: HttpClient.HttpClient) => HttpClient.HttpClient) | undefined
99
- }) => Effect.Effect<Service, never, HttpClient.HttpClient> = Effect.fnUntraced(function*(options) {
100
- const httpClient = (yield* HttpClient.HttpClient).pipe(
101
- HttpClient.mapRequest((request) =>
102
- request.pipe(
103
- HttpClientRequest.prependUrl(options.apiUrl ?? "https://openrouter.ai/api/v1"),
104
- options.apiKey ? HttpClientRequest.bearerToken(options.apiKey) : identity,
105
- options.referrer ? HttpClientRequest.setHeader("HTTP-Referrer", options.referrer) : identity,
106
- options.title ? HttpClientRequest.setHeader("X-Title", options.title) : identity,
107
- HttpClientRequest.acceptJson
108
- )
109
- ),
110
- options.transformClient ?? identity
111
- )
109
+ }
112
110
 
113
- const httpClientOk = HttpClient.filterStatusOk(httpClient)
111
+ // =============================================================================
112
+ // Constructor
113
+ // =============================================================================
114
114
 
115
- const client = Generated.make(httpClient, {
116
- transformClient: (client) =>
117
- OpenRouterConfig.getOrUndefined.pipe(
118
- Effect.map((config) => config?.transformClient ? config.transformClient(client) : client)
119
- )
120
- })
121
-
122
- const streamRequest = <A, I, R>(
123
- request: HttpClientRequest.HttpClientRequest,
124
- schema: Schema.Schema<A, I, R>
125
- ): Stream.Stream<A, AiError.AiError, R> => {
126
- const decodeEvent = Schema.decode(Schema.parseJson(schema))
127
- return httpClientOk.execute(request).pipe(
128
- Effect.map((r) => r.stream),
129
- Stream.unwrapScoped,
130
- Stream.decodeText(),
131
- Stream.pipeThroughChannel(Sse.makeChannel()),
132
- Stream.takeWhile((event) => event.data !== "[DONE]"),
133
- Stream.mapEffect((event) => decodeEvent(event.data)),
134
- Stream.catchTags({
135
- RequestError: (error) =>
136
- AiError.HttpRequestError.fromRequestError({
137
- module: "OpenRouterClient",
138
- method: "streamRequest",
139
- error
140
- }),
141
- ResponseError: (error) =>
142
- AiError.HttpResponseError.fromResponseError({
143
- module: "OpenRouterClient",
144
- method: "streamRequest",
145
- error
146
- }),
147
- ParseError: (error) =>
148
- AiError.MalformedOutput.fromParseError({
149
- module: "OpenRouterClient",
150
- method: "streamRequest",
151
- error
152
- })
153
- })
115
+ /**
116
+ * Creates an OpenRouter client service with the given options.
117
+ *
118
+ * @since 1.0.0
119
+ * @category constructors
120
+ */
121
+ export const make = Effect.fnUntraced(
122
+ function*(options: Options): Effect.fn.Return<Service, never, HttpClient.HttpClient> {
123
+ const baseClient = yield* HttpClient.HttpClient
124
+
125
+ const httpClient = baseClient.pipe(
126
+ HttpClient.mapRequest((request) =>
127
+ request.pipe(
128
+ HttpClientRequest.prependUrl(options.apiUrl ?? "https://openrouter.ai/api/v1"),
129
+ options.apiKey ? HttpClientRequest.bearerToken(options.apiKey) : identity,
130
+ options.siteReferrer ? HttpClientRequest.setHeader("HTTP-Referrer", options.siteReferrer) : identity,
131
+ options.siteTitle ? HttpClientRequest.setHeader("X-Title", options.siteTitle) : identity,
132
+ HttpClientRequest.acceptJson
133
+ )
134
+ ),
135
+ options.transformClient ?? identity
154
136
  )
155
- }
156
137
 
157
- const createChatCompletion: (
158
- options: typeof Generated.ChatGenerationParams.Encoded
159
- ) => Effect.Effect<Generated.ChatResponse, AiError.AiError> = Effect.fnUntraced(
160
- function*(options) {
161
- return yield* client.sendChatCompletionRequest(options).pipe(
162
- Effect.catchTag("ChatError", (error) =>
163
- new AiError.HttpResponseError({
164
- module: "OpenRouterClient",
165
- method: "createChatCompletion",
166
- reason: "StatusCode",
167
- request: {
168
- hash: error.request.hash,
169
- headers: error.request.headers,
170
- method: error.request.method,
171
- url: error.request.url,
172
- urlParams: error.request.urlParams
173
- },
174
- response: {
175
- headers: error.response.headers,
176
- status: error.response.status
177
- }
178
- })),
138
+ const httpClientOk = HttpClient.filterStatusOk(httpClient)
139
+
140
+ const client = Generated.make(httpClient, {
141
+ transformClient: Effect.fnUntraced(function*(client) {
142
+ const config = yield* OpenRouterConfig.getOrUndefined
143
+ if (Predicate.isNotUndefined(config?.transformClient)) {
144
+ return config.transformClient(client)
145
+ }
146
+ return client
147
+ })
148
+ })
149
+
150
+ const createChatCompletion: Service["createChatCompletion"] = (payload) =>
151
+ client.sendChatCompletionRequest({ payload, config: { includeResponse: true } }).pipe(
179
152
  Effect.catchTags({
180
- RequestError: (error) =>
181
- AiError.HttpRequestError.fromRequestError({
182
- module: "OpenRouterClient",
183
- method: "createChatCompletion",
184
- error
185
- }),
186
- ResponseError: (error) =>
187
- AiError.HttpResponseError.fromResponseError({
188
- module: "OpenRouterClient",
189
- method: "createChatCompletion",
190
- error
191
- }),
192
- ParseError: (error) =>
193
- AiError.MalformedOutput.fromParseError({
194
- module: "OpenRouterClient",
195
- method: "createChatCompletion",
196
- error
197
- })
153
+ SendChatCompletionRequest400: (error) => Effect.fail(Errors.mapClientError(error, "createChatCompletion")),
154
+ SendChatCompletionRequest401: (error) => Effect.fail(Errors.mapClientError(error, "createChatCompletion")),
155
+ SendChatCompletionRequest429: (error) => Effect.fail(Errors.mapClientError(error, "createChatCompletion")),
156
+ SendChatCompletionRequest500: (error) => Effect.fail(Errors.mapClientError(error, "createChatCompletion")),
157
+ HttpClientError: (error) => Errors.mapHttpClientError(error, "createChatCompletion"),
158
+ SchemaError: (error) => Effect.fail(Errors.mapSchemaError(error, "createChatCompletion"))
198
159
  })
199
160
  )
161
+
162
+ const buildChatCompletionStream = (
163
+ response: HttpClientResponse.HttpClientResponse
164
+ ): [
165
+ HttpClientResponse.HttpClientResponse,
166
+ Stream.Stream<ChatStreamingResponseChunkData, AiError.AiError>
167
+ ] => {
168
+ const stream = response.stream.pipe(
169
+ Stream.decodeText(),
170
+ Stream.pipeThroughChannel(Sse.decode()),
171
+ Stream.mapEffect((event) => decodeChatCompletionSseData(event.data)),
172
+ Stream.takeWhile((data) => data !== "[DONE]"),
173
+ Stream.catchTags({
174
+ // TODO: handle SSE retries
175
+ Retry: (error) => Stream.die(error),
176
+ HttpClientError: (error) => Stream.fromEffect(Errors.mapHttpClientError(error, "createChatCompletionStream")),
177
+ SchemaError: (error) => Stream.fail(Errors.mapSchemaError(error, "createChatCompletionStream"))
178
+ })
179
+ ) as any
180
+ return [response, stream]
200
181
  }
201
- )
202
182
 
203
- const createChatCompletionStream = (
204
- options: Omit<typeof Generated.ChatGenerationParams.Encoded, "stream">
205
- ): Stream.Stream<ChatStreamingResponseChunk, AiError.AiError> => {
206
- const request = HttpClientRequest.post("/chat/completions", {
207
- body: HttpBody.unsafeJson({
208
- ...options,
209
- stream: true,
210
- stream_options: { include_usage: true }
211
- })
183
+ const createChatCompletionStream: Service["createChatCompletionStream"] = (payload) =>
184
+ httpClientOk.execute(
185
+ HttpClientRequest.post("/chat/completions", {
186
+ body: HttpBody.jsonUnsafe({
187
+ ...payload,
188
+ stream: true,
189
+ stream_options: { include_usage: true }
190
+ })
191
+ })
192
+ ).pipe(
193
+ Effect.map(buildChatCompletionStream),
194
+ Effect.catchTag(
195
+ "HttpClientError",
196
+ (error) => Errors.mapHttpClientError(error, "createChatCompletionStream")
197
+ )
198
+ )
199
+
200
+ return OpenRouterClient.of({
201
+ client,
202
+ createChatCompletion,
203
+ createChatCompletionStream
212
204
  })
213
- return streamRequest(request, ChatStreamingResponseChunk)
214
205
  }
206
+ )
215
207
 
216
- return OpenRouterClient.of({
217
- client,
218
- createChatCompletion,
219
- createChatCompletionStream
220
- })
221
- })
208
+ // =============================================================================
209
+ // Layers
210
+ // =============================================================================
222
211
 
223
212
  /**
213
+ * Creates a layer for the OpenRouter client with the given options.
214
+ *
224
215
  * @since 1.0.0
225
- * @category Layers
216
+ * @category layers
226
217
  */
227
- export const layer = (options: {
228
- readonly apiKey?: Redacted.Redacted | undefined
229
- readonly apiUrl?: string | undefined
230
- /**
231
- * Optional URL of your site for rankings on `openrouter.ai`.
232
- */
233
- readonly referrer?: string | undefined
234
- /**
235
- * Optional title of your site for rankings on `openrouter.ai`.
236
- */
237
- readonly title?: string | undefined
238
- /**
239
- * A function to transform the underlying HTTP client before it's used to send
240
- * API requests.
241
- *
242
- * This transformation function receives the configured HTTP client and returns
243
- * a modified version. It's applied after all standard client configuration
244
- * (authentication, base URL, headers) but before any requests are made.
245
- *
246
- * Use this for:
247
- * - Adding custom middleware (logging, metrics, caching)
248
- * - Modifying request/response processing behavior
249
- * - Adding custom retry logic or error handling
250
- * - Integrating with monitoring or debugging tools
251
- * - Applying organization-specific HTTP client policies
252
- *
253
- * The transformation is applied once during client initialization and affects
254
- * all subsequent API requests made through this client instance.
255
- *
256
- * Leave absent or set to `undefined` if no custom HTTP client behavior is
257
- * needed.
258
- */
259
- readonly transformClient?: ((client: HttpClient.HttpClient) => HttpClient.HttpClient) | undefined
260
- }): Layer.Layer<OpenRouterClient, never, HttpClient.HttpClient> => Layer.effect(OpenRouterClient, make(options))
218
+ export const layer = (options: Options): Layer.Layer<OpenRouterClient, never, HttpClient.HttpClient> =>
219
+ Layer.effect(OpenRouterClient, make(options))
261
220
 
262
221
  /**
222
+ * Creates a layer for the OpenRouter client, loading the requisite
223
+ * configuration via Effect's `Config` module.
224
+ *
263
225
  * @since 1.0.0
264
- * @category Layers
226
+ * @category layers
265
227
  */
266
- export const layerConfig = (options: {
267
- readonly apiKey?: Config.Config<Redacted.Redacted> | undefined
228
+ export const layerConfig = (options?: {
229
+ /**
230
+ * The config value to load for the API key.
231
+ */
232
+ readonly apiKey?: Config.Config<Redacted.Redacted<string>> | undefined
233
+
234
+ /**
235
+ * The config value to load for the API URL.
236
+ */
268
237
  readonly apiUrl?: Config.Config<string> | undefined
238
+
269
239
  /**
270
- * Optional URL of your site for rankings on `openrouter.ai`.
240
+ * The config value to load for the site referrer URL.
271
241
  */
272
- readonly referrer?: Config.Config<string> | undefined
242
+ readonly siteReferrer?: Config.Config<string> | undefined
243
+
273
244
  /**
274
- * Optional title of your site for rankings on `openrouter.ai`.
245
+ * The config value to load for the site title.
275
246
  */
276
- readonly title?: Config.Config<string> | undefined
247
+ readonly siteTitle?: Config.Config<string> | undefined
248
+
277
249
  /**
278
- * A function to transform the underlying HTTP client before it's used to send
279
- * API requests.
280
- *
281
- * This transformation function receives the configured HTTP client and returns
282
- * a modified version. It's applied after all standard client configuration
283
- * (authentication, base URL, headers) but before any requests are made.
284
- *
285
- * Use this for:
286
- * - Adding custom middleware (logging, metrics, caching)
287
- * - Modifying request/response processing behavior
288
- * - Adding custom retry logic or error handling
289
- * - Integrating with monitoring or debugging tools
290
- * - Applying organization-specific HTTP client policies
291
- *
292
- * The transformation is applied once during client initialization and affects
293
- * all subsequent API requests made through this client instance.
294
- *
295
- * Leave absent or set to `undefined` if no custom HTTP client behavior is
296
- * needed.
250
+ * Optional transformer for the HTTP client.
297
251
  */
298
252
  readonly transformClient?: ((client: HttpClient.HttpClient) => HttpClient.HttpClient) | undefined
299
- }): Layer.Layer<OpenRouterClient, ConfigError, HttpClient.HttpClient> => {
300
- const { transformClient, ...configs } = options
301
- return Config.all(configs).pipe(
302
- Effect.flatMap((configs) => make({ ...configs, transformClient })),
303
- Layer.effect(OpenRouterClient)
253
+ }): Layer.Layer<OpenRouterClient, Config.ConfigError, HttpClient.HttpClient> =>
254
+ Layer.effect(
255
+ OpenRouterClient,
256
+ Effect.gen(function*() {
257
+ const apiKey = Predicate.isNotUndefined(options?.apiKey)
258
+ ? yield* options.apiKey
259
+ : undefined
260
+ const apiUrl = Predicate.isNotUndefined(options?.apiUrl)
261
+ ? yield* options.apiUrl
262
+ : undefined
263
+ const siteReferrer = Predicate.isNotUndefined(options?.siteReferrer)
264
+ ? yield* options.siteReferrer
265
+ : undefined
266
+ const siteTitle = Predicate.isNotUndefined(options?.siteTitle)
267
+ ? yield* options.siteTitle
268
+ : undefined
269
+ return yield* make({
270
+ apiKey,
271
+ apiUrl,
272
+ siteReferrer,
273
+ siteTitle,
274
+ transformClient: options?.transformClient
275
+ })
276
+ })
304
277
  )
305
- }
306
278
 
307
- /**
308
- * @since 1.0.0
309
- * @category Schemas
310
- */
311
- export class ChatStreamingMessageToolCall extends Schema.Class<ChatStreamingMessageToolCall>(
312
- "@effect/ai-openrouter/ChatStreamingMessageToolCall"
313
- )({
314
- index: Schema.Number,
315
- id: Schema.optionalWith(Schema.String, { nullable: true }),
316
- type: Schema.Literal("function"),
317
- function: Schema.Struct({
318
- name: Schema.String,
319
- arguments: Schema.String
320
- })
321
- }) {}
279
+ // =============================================================================
280
+ // Internal Utilities
281
+ // =============================================================================
322
282
 
323
- /**
324
- * @since 1.0.0
325
- * @category Schemas
326
- */
327
- export class ChatStreamingMessageChunk extends Schema.Class<ChatStreamingMessageChunk>(
328
- "@effect/ai-openrouter/ChatStreamingMessageChunk"
329
- )({
330
- role: Schema.optionalWith(Schema.Literal("assistant"), { nullable: true }),
331
- content: Schema.optionalWith(Schema.String, { nullable: true }),
332
- reasoning: Schema.optionalWith(Schema.String, { nullable: true }),
333
- reasoning_details: Schema.optionalWith(Schema.Array(Generated.ReasoningDetail), { nullable: true }),
334
- images: Schema.optionalWith(Schema.Array(Generated.ChatMessageContentItemImage), { nullable: true }),
335
- refusal: Schema.optionalWith(Schema.String, { nullable: true }),
336
- tool_calls: Schema.optionalWith(Schema.Array(ChatStreamingMessageToolCall), { nullable: true }),
337
- annotations: Schema.optionalWith(Schema.Array(Generated.AnnotationDetail), { nullable: true })
338
- }) {}
283
+ const ChatStreamingResponseChunkDataFromString = Schema.fromJsonString(Generated.ChatStreamingResponseChunk.fields.data)
284
+ const decodeChatStreamingResponseChunkData = Schema.decodeUnknownEffect(ChatStreamingResponseChunkDataFromString)
339
285
 
340
- /**
341
- * @since 1.0.0
342
- * @category Schemas
343
- */
344
- export class ChatStreamingChoice extends Schema.Class<ChatStreamingChoice>(
345
- "@effect/ai-openrouter/ChatStreamingChoice"
346
- )({
347
- index: Schema.Number,
348
- delta: Schema.optionalWith(ChatStreamingMessageChunk, { nullable: true }),
349
- finish_reason: Schema.optionalWith(Generated.ChatCompletionFinishReason, { nullable: true }),
350
- native_finish_reason: Schema.optionalWith(Schema.String, { nullable: true }),
351
- logprobs: Schema.optionalWith(Generated.ChatMessageTokenLogprobs, { nullable: true })
352
- }) {}
353
-
354
- /**
355
- * @since 1.0.0
356
- * @category Schemas
357
- */
358
- export class ChatStreamingResponseChunk extends Schema.Class<ChatStreamingResponseChunk>(
359
- "@effect/ai-openrouter/ChatStreamingResponseChunk"
360
- )({
361
- id: Schema.optionalWith(Schema.String, { nullable: true }),
362
- model: Schema.optionalWith(
363
- Schema.TemplateLiteral(Schema.String, Schema.Literal("/"), Schema.String),
364
- { nullable: true }
365
- ),
366
- provider: Schema.optionalWith(Schema.String, { nullable: true }),
367
- created: Schema.DateTimeUtcFromNumber,
368
- choices: Schema.Array(ChatStreamingChoice),
369
- error: Schema.optionalWith(Generated.ChatError.fields.error, { nullable: true }),
370
- system_fingerprint: Schema.optionalWith(Schema.String, { nullable: true }),
371
- usage: Schema.optionalWith(Generated.ChatGenerationTokenUsage, { nullable: true })
372
- }) {}
286
+ const decodeChatCompletionSseData = (
287
+ data: string
288
+ ): Effect.Effect<ChatStreamingResponseChunkData | "[DONE]", Schema.SchemaError> =>
289
+ data === "[DONE]"
290
+ ? Effect.succeed(data)
291
+ : decodeChatStreamingResponseChunkData(data)
@@ -1,25 +1,25 @@
1
1
  /**
2
2
  * @since 1.0.0
3
3
  */
4
- import type { HttpClient } from "@effect/platform/HttpClient"
5
- import * as Context from "effect/Context"
6
4
  import * as Effect from "effect/Effect"
7
5
  import { dual } from "effect/Function"
6
+ import * as ServiceMap from "effect/ServiceMap"
7
+ import type { HttpClient } from "effect/unstable/http/HttpClient"
8
8
 
9
9
  /**
10
10
  * @since 1.0.0
11
- * @category Context
11
+ * @category services
12
12
  */
13
- export class OpenRouterConfig extends Context.Tag("@effect/ai-openrouter/OpenRouterConfig")<
13
+ export class OpenRouterConfig extends ServiceMap.Service<
14
14
  OpenRouterConfig,
15
15
  OpenRouterConfig.Service
16
- >() {
16
+ >()("@effect/ai-openrouter/OpenRouterConfig") {
17
17
  /**
18
18
  * @since 1.0.0
19
19
  */
20
20
  static readonly getOrUndefined: Effect.Effect<typeof OpenRouterConfig.Service | undefined> = Effect.map(
21
- Effect.context<never>(),
22
- (context) => context.unsafeMap.get(OpenRouterConfig.key)
21
+ Effect.services<never>(),
22
+ (services) => services.mapUnsafe.get(OpenRouterConfig.key)
23
23
  )
24
24
  }
25
25
 
@@ -29,26 +29,26 @@ export class OpenRouterConfig extends Context.Tag("@effect/ai-openrouter/OpenRou
29
29
  export declare namespace OpenRouterConfig {
30
30
  /**
31
31
  * @since 1.0.0
32
- * @category Models
32
+ * @category models
33
33
  */
34
34
  export interface Service {
35
- readonly transformClient?: (client: HttpClient) => HttpClient
35
+ readonly transformClient?: ((client: HttpClient) => HttpClient) | undefined
36
36
  }
37
37
  }
38
38
 
39
39
  /**
40
40
  * @since 1.0.0
41
- * @category Configuration
41
+ * @category configuration
42
42
  */
43
43
  export const withClientTransform: {
44
44
  /**
45
45
  * @since 1.0.0
46
- * @category Configuration
46
+ * @category configuration
47
47
  */
48
48
  (transform: (client: HttpClient) => HttpClient): <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>
49
49
  /**
50
50
  * @since 1.0.0
51
- * @category Configuration
51
+ * @category configuration
52
52
  */
53
53
  <A, E, R>(
54
54
  self: Effect.Effect<A, E, R>,
@@ -57,12 +57,12 @@ export const withClientTransform: {
57
57
  } = dual<
58
58
  /**
59
59
  * @since 1.0.0
60
- * @category Configuration
60
+ * @category configuration
61
61
  */
62
62
  (transform: (client: HttpClient) => HttpClient) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>,
63
63
  /**
64
64
  * @since 1.0.0
65
- * @category Configuration
65
+ * @category configuration
66
66
  */
67
67
  <A, E, R>(
68
68
  self: Effect.Effect<A, E, R>,