@effect-uai/responses 0.5.2 → 0.6.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effect-uai/responses",
3
- "version": "0.5.2",
3
+ "version": "0.6.0",
4
4
  "description": "OpenAI Responses API provider for @effect-uai/core.",
5
5
  "keywords": [
6
6
  "ai",
@@ -49,7 +49,7 @@
49
49
  "devDependencies": {
50
50
  "effect": "4.0.0-beta.57",
51
51
  "typescript": "^6.0.3",
52
- "@effect-uai/core": "0.5.2"
52
+ "@effect-uai/core": "0.6.0"
53
53
  },
54
54
  "peerDependencies": {
55
55
  "@effect-uai/core": ">=0.2.0 <1",
@@ -17,6 +17,7 @@ import {
17
17
  type EmbedResponse,
18
18
  } from "@effect-uai/core/EmbeddingModel"
19
19
  import type { OpenAIEmbeddingModel } from "./models.js"
20
+ import { type OpenAiRegion, resolveHost } from "./region.js"
20
21
 
21
22
  // ---------------------------------------------------------------------------
22
23
  // Public types
@@ -56,6 +57,7 @@ export class OpenAIEmbedding extends Context.Service<OpenAIEmbedding, OpenAIEmbe
56
57
  export type Config = {
57
58
  readonly apiKey: Redacted.Redacted
58
59
  readonly baseUrl?: string
60
+ readonly region?: OpenAiRegion
59
61
  }
60
62
 
61
63
  // ---------------------------------------------------------------------------
@@ -176,15 +178,13 @@ const transportFailure = (cause: unknown): AiError.AiError =>
176
178
  // HTTP plumbing
177
179
  // ---------------------------------------------------------------------------
178
180
 
179
- const baseUrl = (cfg: Config): string => cfg.baseUrl ?? "https://api.openai.com/v1"
180
-
181
181
  const postEmbed = (
182
182
  cfg: Config,
183
183
  body: WireBody,
184
184
  ): Effect.Effect<typeof WireResponse.Type, AiError.AiError, HttpClient.HttpClient> =>
185
185
  Effect.gen(function* () {
186
186
  const client = yield* HttpClient.HttpClient
187
- const httpRequest = HttpClientRequest.post(`${baseUrl(cfg)}/embeddings`).pipe(
187
+ const httpRequest = HttpClientRequest.post(`${resolveHost(cfg)}/embeddings`).pipe(
188
188
  HttpClientRequest.bearerToken(cfg.apiKey),
189
189
  HttpClientRequest.bodyJsonUnsafe(body),
190
190
  )
package/src/Responses.ts CHANGED
@@ -12,6 +12,7 @@ import * as SSE from "@effect-uai/core/SSE"
12
12
  import type { Turn, TurnEvent } from "@effect-uai/core/Turn"
13
13
  import { itemsToInput } from "./codec.js"
14
14
  import type { OpenAIModel } from "./models.js"
15
+ import { type OpenAiRegion, resolveHost } from "./region.js"
15
16
  import {
16
17
  KnownProviderEvent,
17
18
  ProviderEvent,
@@ -95,6 +96,7 @@ export class Responses extends Context.Service<Responses, ResponsesService>()(
95
96
  export type Config = {
96
97
  readonly apiKey: Redacted.Redacted
97
98
  readonly baseUrl?: string
99
+ readonly region?: OpenAiRegion
98
100
  }
99
101
 
100
102
  // ---------------------------------------------------------------------------
@@ -222,7 +224,7 @@ const httpStatusError = (status: number, body: string): AiError.AiError => {
222
224
  }
223
225
 
224
226
  const buildNativeStream = (cfg: Config) => {
225
- const url = `${cfg.baseUrl ?? "https://api.openai.com/v1"}/responses`
227
+ const url = `${resolveHost(cfg)}/responses`
226
228
  return (
227
229
  request: ResponsesRequest,
228
230
  ): Stream.Stream<ProviderEvent, AiError.AiError, HttpClient.HttpClient> =>
package/src/codec.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Encoding, Match, Option, Schema } from "effect"
2
- import type { ContentBlock, InputImage, Item } from "@effect-uai/core/Items"
2
+ import type { ContentBlock, InputImage, HistoryItem } from "@effect-uai/core/Items"
3
3
  import type { Turn } from "@effect-uai/core/Turn"
4
4
 
5
5
  // ---------------------------------------------------------------------------
@@ -137,7 +137,7 @@ export type WireResponseCompleted = typeof WireResponseCompleted.Type
137
137
  * verbatim - preserves `encrypted_content`, item ids, and any field this
138
138
  * codec doesn't model.
139
139
  */
140
- const passthrough = (item: Item): Record<string, unknown> | undefined =>
140
+ const passthrough = (item: HistoryItem): Record<string, unknown> | undefined =>
141
141
  item.providerData !== undefined &&
142
142
  typeof item.providerData === "object" &&
143
143
  item.providerData !== null
@@ -168,7 +168,7 @@ const contentBlockToInput = Match.type<ContentBlock>().pipe(
168
168
  }),
169
169
  )
170
170
 
171
- const itemToInput = (item: Item): Record<string, unknown> =>
171
+ const itemToInput = (item: HistoryItem): Record<string, unknown> =>
172
172
  passthrough(item) ??
173
173
  Match.value(item).pipe(
174
174
  Match.discriminatorsExhaustive("type")({
@@ -199,9 +199,10 @@ const itemToInput = (item: Item): Record<string, unknown> =>
199
199
  }),
200
200
  )
201
201
 
202
- /** Convert our `Item[]` history into the Responses API `input` array. */
203
- export const itemsToInput = (items: ReadonlyArray<Item>): ReadonlyArray<Record<string, unknown>> =>
204
- items.map(itemToInput)
202
+ /** Convert our `HistoryItem[]` history into the Responses API `input` array. */
203
+ export const itemsToInput = (
204
+ items: ReadonlyArray<HistoryItem>,
205
+ ): ReadonlyArray<Record<string, unknown>> => items.map(itemToInput)
205
206
 
206
207
  // ---------------------------------------------------------------------------
207
208
  // Wire output items → our Items
@@ -218,7 +219,7 @@ const wireMessageContentToBlock = Match.type<typeof WireMessageContent.Type>().p
218
219
  }),
219
220
  )
220
221
 
221
- export const wireItemToItem = (wire: WireOutputItem): Item =>
222
+ export const wireItemToItem = (wire: WireOutputItem): HistoryItem =>
222
223
  Match.value(wire).pipe(
223
224
  Match.discriminatorsExhaustive("type")({
224
225
  message: (m) => ({
package/src/index.ts CHANGED
@@ -3,3 +3,4 @@ export * as OpenAIEmbedding from "./OpenAIEmbedding.js"
3
3
  export * as codec from "./codec.js"
4
4
  export * as streamEvents from "./streamEvents.js"
5
5
  export * from "./models.js"
6
+ export * from "./region.js"
@@ -0,0 +1,26 @@
1
+ import { describe, expect, it } from "vitest"
2
+ import { resolveHost } from "./region.js"
3
+
4
+ describe("responses/region.resolveHost", () => {
5
+ it("returns default host when no region or baseUrl", () => {
6
+ expect(resolveHost({})).toBe("https://api.openai.com/v1")
7
+ })
8
+
9
+ it("returns default host for region 'default'", () => {
10
+ expect(resolveHost({ region: "default" })).toBe("https://api.openai.com/v1")
11
+ })
12
+
13
+ it("computes EU host for region 'eu'", () => {
14
+ expect(resolveHost({ region: "eu" })).toBe("https://eu.api.openai.com/v1")
15
+ })
16
+
17
+ it("passes unknown region through as host prefix", () => {
18
+ expect(resolveHost({ region: "apac" })).toBe("https://apac.api.openai.com/v1")
19
+ })
20
+
21
+ it("baseUrl wins over region", () => {
22
+ expect(resolveHost({ baseUrl: "http://localhost:8080/v1", region: "eu" })).toBe(
23
+ "http://localhost:8080/v1",
24
+ )
25
+ })
26
+ })
package/src/region.ts ADDED
@@ -0,0 +1,29 @@
1
+ /**
2
+ * OpenAI regional endpoints. `eu.api.openai.com` covers `/v1/responses`
3
+ * and `/v1/embeddings` (along with the rest of the REST surface) for
4
+ * projects with EU data residency enabled. Keys are project-scoped.
5
+ *
6
+ * Reference: https://developers.openai.com/api/docs/guides/your-data
7
+ *
8
+ * Defined locally in this package rather than imported from
9
+ * `@effect-uai/openai` to keep the packages decoupled.
10
+ */
11
+
12
+ // eslint-disable-next-line @typescript-eslint/ban-types
13
+ export type OpenAiRegion = "default" | "eu" | (string & {})
14
+
15
+ const DEFAULT_HOST = "https://api.openai.com/v1"
16
+
17
+ /**
18
+ * Resolve the HTTP base URL from a Config. `baseUrl` wins when set;
19
+ * otherwise the host is computed from `region`. Unknown region strings
20
+ * pass through as `{region}.api.openai.com/v1` for forward compat.
21
+ */
22
+ export const resolveHost = (cfg: {
23
+ readonly baseUrl?: string
24
+ readonly region?: OpenAiRegion
25
+ }): string => {
26
+ if (cfg.baseUrl !== undefined) return cfg.baseUrl
27
+ if (cfg.region === undefined || cfg.region === "default") return DEFAULT_HOST
28
+ return `https://${cfg.region}.api.openai.com/v1`
29
+ }