@effect-uai/responses 0.5.1 → 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/dist/OpenAIEmbedding.d.mts +2 -0
- package/dist/OpenAIEmbedding.d.mts.map +1 -1
- package/dist/OpenAIEmbedding.mjs +2 -2
- package/dist/OpenAIEmbedding.mjs.map +1 -1
- package/dist/Responses.d.mts +9 -1
- package/dist/Responses.d.mts.map +1 -1
- package/dist/Responses.mjs +10 -4
- package/dist/Responses.mjs.map +1 -1
- package/dist/codec.d.mts +4 -4
- package/dist/codec.d.mts.map +1 -1
- package/dist/codec.mjs +1 -1
- package/dist/codec.mjs.map +1 -1
- package/dist/index.d.mts +2 -1
- package/dist/index.mjs +2 -1
- package/dist/magic-string.es-PAZgLX4h.mjs +1013 -0
- package/dist/magic-string.es-PAZgLX4h.mjs.map +1 -0
- package/dist/region.d.mts +24 -0
- package/dist/region.d.mts.map +1 -0
- package/dist/region.mjs +16 -0
- package/dist/region.mjs.map +1 -0
- package/dist/region.test.d.mts +1 -0
- package/dist/region.test.mjs +13656 -0
- package/dist/region.test.mjs.map +1 -0
- package/package.json +2 -2
- package/src/OpenAIEmbedding.ts +3 -3
- package/src/Responses.ts +16 -3
- package/src/codec.ts +8 -7
- package/src/index.ts +1 -0
- package/src/region.test.ts +26 -0
- package/src/region.ts +29 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effect-uai/responses",
|
|
3
|
-
"version": "0.
|
|
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.
|
|
52
|
+
"@effect-uai/core": "0.6.0"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
55
|
"@effect-uai/core": ">=0.2.0 <1",
|
package/src/OpenAIEmbedding.ts
CHANGED
|
@@ -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(`${
|
|
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
|
@@ -6,11 +6,13 @@ import {
|
|
|
6
6
|
type CommonRequest,
|
|
7
7
|
LanguageModel,
|
|
8
8
|
type LanguageModelService,
|
|
9
|
+
turnFromStream,
|
|
9
10
|
} from "@effect-uai/core/LanguageModel"
|
|
10
11
|
import * as SSE from "@effect-uai/core/SSE"
|
|
11
|
-
import type { TurnEvent } from "@effect-uai/core/Turn"
|
|
12
|
+
import type { Turn, TurnEvent } from "@effect-uai/core/Turn"
|
|
12
13
|
import { itemsToInput } from "./codec.js"
|
|
13
14
|
import type { OpenAIModel } from "./models.js"
|
|
15
|
+
import { type OpenAiRegion, resolveHost } from "./region.js"
|
|
14
16
|
import {
|
|
15
17
|
KnownProviderEvent,
|
|
16
18
|
ProviderEvent,
|
|
@@ -65,6 +67,12 @@ export type ResponsesService = {
|
|
|
65
67
|
* `streamNative |> toCanonical`.
|
|
66
68
|
*/
|
|
67
69
|
readonly streamTurn: (request: ResponsesRequest) => Stream.Stream<TurnEvent, AiError.AiError>
|
|
70
|
+
/**
|
|
71
|
+
* Drain a single turn and return the assembled `Turn`. Derived from
|
|
72
|
+
* `streamTurn` — Responses doesn't expose a native non-streaming
|
|
73
|
+
* complete endpoint we'd want to take advantage of.
|
|
74
|
+
*/
|
|
75
|
+
readonly turn: (request: ResponsesRequest) => Effect.Effect<Turn, AiError.AiError>
|
|
68
76
|
/**
|
|
69
77
|
* Project a stream of native `ProviderEvent`s into canonical `TurnEvent`s.
|
|
70
78
|
* Exposed for cases where consumers want to compose with `streamNative`
|
|
@@ -88,6 +96,7 @@ export class Responses extends Context.Service<Responses, ResponsesService>()(
|
|
|
88
96
|
export type Config = {
|
|
89
97
|
readonly apiKey: Redacted.Redacted
|
|
90
98
|
readonly baseUrl?: string
|
|
99
|
+
readonly region?: OpenAiRegion
|
|
91
100
|
}
|
|
92
101
|
|
|
93
102
|
// ---------------------------------------------------------------------------
|
|
@@ -215,7 +224,7 @@ const httpStatusError = (status: number, body: string): AiError.AiError => {
|
|
|
215
224
|
}
|
|
216
225
|
|
|
217
226
|
const buildNativeStream = (cfg: Config) => {
|
|
218
|
-
const url = `${cfg
|
|
227
|
+
const url = `${resolveHost(cfg)}/responses`
|
|
219
228
|
return (
|
|
220
229
|
request: ResponsesRequest,
|
|
221
230
|
): Stream.Stream<ProviderEvent, AiError.AiError, HttpClient.HttpClient> =>
|
|
@@ -282,9 +291,12 @@ export const make = (cfg: Config): Effect.Effect<ResponsesService, never, HttpCl
|
|
|
282
291
|
Effect.map(HttpClient.HttpClient.asEffect(), (client) => {
|
|
283
292
|
const streamNative: ResponsesService["streamNative"] = (request) =>
|
|
284
293
|
buildNativeStream(cfg)(request).pipe(Stream.provideService(HttpClient.HttpClient, client))
|
|
294
|
+
const streamTurn: ResponsesService["streamTurn"] = (request) =>
|
|
295
|
+
toCanonical(streamNative(request))
|
|
285
296
|
return {
|
|
286
297
|
streamNative,
|
|
287
|
-
streamTurn
|
|
298
|
+
streamTurn,
|
|
299
|
+
turn: turnFromStream(streamTurn),
|
|
288
300
|
toCanonical,
|
|
289
301
|
}
|
|
290
302
|
})
|
|
@@ -306,6 +318,7 @@ export const layer = (
|
|
|
306
318
|
make(cfg),
|
|
307
319
|
(s): LanguageModelService => ({
|
|
308
320
|
streamTurn: (request) => s.streamTurn(request as ResponsesRequest),
|
|
321
|
+
turn: (request) => s.turn(request as ResponsesRequest),
|
|
309
322
|
}),
|
|
310
323
|
),
|
|
311
324
|
)
|
package/src/codec.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Encoding, Match, Option, Schema } from "effect"
|
|
2
|
-
import type { ContentBlock, InputImage,
|
|
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:
|
|
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:
|
|
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 `
|
|
203
|
-
export const itemsToInput = (
|
|
204
|
-
items
|
|
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):
|
|
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
|
@@ -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
|
+
}
|