@effect-uai/responses 0.1.0 → 0.3.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 +53 -0
- package/dist/OpenAIEmbedding.d.mts.map +1 -0
- package/dist/OpenAIEmbedding.mjs +157 -0
- package/dist/OpenAIEmbedding.mjs.map +1 -0
- package/dist/Responses.d.mts +96 -0
- package/dist/Responses.d.mts.map +1 -0
- package/dist/Responses.mjs +194 -0
- package/dist/Responses.mjs.map +1 -0
- package/dist/codec.d.mts +133 -0
- package/dist/codec.d.mts.map +1 -0
- package/dist/codec.mjs +218 -0
- package/dist/codec.mjs.map +1 -0
- package/dist/index.d.mts +6 -828
- package/dist/index.mjs +6 -525
- package/dist/models.d.mts +25 -0
- package/dist/models.d.mts.map +1 -0
- package/dist/models.mjs +1 -0
- package/dist/streamEvents.d.mts +607 -0
- package/dist/streamEvents.d.mts.map +1 -0
- package/dist/streamEvents.mjs +170 -0
- package/dist/streamEvents.mjs.map +1 -0
- package/package.json +20 -12
- package/src/OpenAIEmbedding.ts +275 -0
- package/src/Responses.ts +15 -17
- package/src/codec.ts +75 -66
- package/src/index.ts +3 -2
- package/src/models.ts +18 -0
- package/src/streamEvents.ts +48 -52
- package/dist/index.d.mts.map +0 -1
- package/dist/index.mjs.map +0 -1
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { t as __exportAll } from "./chunk-CfYAbeIz.mjs";
|
|
2
|
+
import { WireOutputItem, WireResponseCompleted, turnFromCompleted } from "./codec.mjs";
|
|
3
|
+
import { Match, Schema } from "effect";
|
|
4
|
+
//#region src/streamEvents.ts
|
|
5
|
+
var streamEvents_exports = /* @__PURE__ */ __exportAll({
|
|
6
|
+
KnownProviderEvent: () => KnownProviderEvent,
|
|
7
|
+
ProviderEvent: () => ProviderEvent,
|
|
8
|
+
eventToDeltas: () => eventToDeltas,
|
|
9
|
+
makeCallIdLookup: () => makeCallIdLookup
|
|
10
|
+
});
|
|
11
|
+
const OutputItemAdded = Schema.Struct({
|
|
12
|
+
type: Schema.Literal("response.output_item.added"),
|
|
13
|
+
item: WireOutputItem
|
|
14
|
+
});
|
|
15
|
+
const OutputTextDelta = Schema.Struct({
|
|
16
|
+
type: Schema.Literal("response.output_text.delta"),
|
|
17
|
+
delta: Schema.String
|
|
18
|
+
});
|
|
19
|
+
const FunctionArgsDelta = Schema.Struct({
|
|
20
|
+
type: Schema.Literal("response.function_call_arguments.delta"),
|
|
21
|
+
item_id: Schema.String,
|
|
22
|
+
delta: Schema.String
|
|
23
|
+
});
|
|
24
|
+
const ReasoningTextDelta = Schema.Struct({
|
|
25
|
+
type: Schema.Literal("response.reasoning_text.delta"),
|
|
26
|
+
delta: Schema.String
|
|
27
|
+
});
|
|
28
|
+
const ReasoningSummaryDelta = Schema.Struct({
|
|
29
|
+
type: Schema.Literal("response.reasoning_summary_text.delta"),
|
|
30
|
+
delta: Schema.String
|
|
31
|
+
});
|
|
32
|
+
const RefusalDelta = Schema.Struct({
|
|
33
|
+
type: Schema.Literal("response.refusal.delta"),
|
|
34
|
+
delta: Schema.String
|
|
35
|
+
});
|
|
36
|
+
const RefusalDone = Schema.Struct({
|
|
37
|
+
type: Schema.Literal("response.refusal.done"),
|
|
38
|
+
refusal: Schema.optional(Schema.String)
|
|
39
|
+
});
|
|
40
|
+
const Completed = Schema.Struct({
|
|
41
|
+
type: Schema.Literal("response.completed"),
|
|
42
|
+
response: WireResponseCompleted
|
|
43
|
+
});
|
|
44
|
+
const Incomplete = Schema.Struct({
|
|
45
|
+
type: Schema.Literal("response.incomplete"),
|
|
46
|
+
response: WireResponseCompleted
|
|
47
|
+
});
|
|
48
|
+
const Failed = Schema.Struct({
|
|
49
|
+
type: Schema.Literal("response.failed"),
|
|
50
|
+
response: WireResponseCompleted
|
|
51
|
+
});
|
|
52
|
+
const ErrorEvent = Schema.Struct({
|
|
53
|
+
type: Schema.Literal("error"),
|
|
54
|
+
message: Schema.optional(Schema.String),
|
|
55
|
+
code: Schema.optional(Schema.String)
|
|
56
|
+
});
|
|
57
|
+
/**
|
|
58
|
+
* Catch-all variant for wire events that fail to decode against any known
|
|
59
|
+
* schema, plus events that fail to JSON-parse. The decoder never produces
|
|
60
|
+
* this directly - it's synthesized by `sseEventToProviderEvent` when
|
|
61
|
+
* `decodeKnown` fails.
|
|
62
|
+
*/
|
|
63
|
+
const Unknown = Schema.Struct({
|
|
64
|
+
type: Schema.Literal("_unknown"),
|
|
65
|
+
raw: Schema.Unknown
|
|
66
|
+
});
|
|
67
|
+
/**
|
|
68
|
+
* Internal: union of variants we actually know how to decode from the wire.
|
|
69
|
+
* Used as the decode target; failures are caught and re-emitted as `Unknown`.
|
|
70
|
+
*/
|
|
71
|
+
const KnownProviderEvent = Schema.Union([
|
|
72
|
+
OutputItemAdded,
|
|
73
|
+
OutputTextDelta,
|
|
74
|
+
FunctionArgsDelta,
|
|
75
|
+
ReasoningTextDelta,
|
|
76
|
+
ReasoningSummaryDelta,
|
|
77
|
+
RefusalDelta,
|
|
78
|
+
RefusalDone,
|
|
79
|
+
Completed,
|
|
80
|
+
Incomplete,
|
|
81
|
+
Failed,
|
|
82
|
+
ErrorEvent
|
|
83
|
+
]);
|
|
84
|
+
/**
|
|
85
|
+
* Public: every event the native stream can emit. Discriminated on `type`.
|
|
86
|
+
* The `_unknown` branch closes the cardinality so downstream `Match.exhaustive`
|
|
87
|
+
* cannot silently miss a wire event we didn't model.
|
|
88
|
+
*/
|
|
89
|
+
const ProviderEvent = Schema.Union([
|
|
90
|
+
OutputItemAdded,
|
|
91
|
+
OutputTextDelta,
|
|
92
|
+
FunctionArgsDelta,
|
|
93
|
+
ReasoningTextDelta,
|
|
94
|
+
ReasoningSummaryDelta,
|
|
95
|
+
RefusalDelta,
|
|
96
|
+
RefusalDone,
|
|
97
|
+
Completed,
|
|
98
|
+
Incomplete,
|
|
99
|
+
Failed,
|
|
100
|
+
ErrorEvent,
|
|
101
|
+
Unknown
|
|
102
|
+
]);
|
|
103
|
+
const makeCallIdLookup = () => {
|
|
104
|
+
const map = /* @__PURE__ */ new Map();
|
|
105
|
+
return {
|
|
106
|
+
resolve: (id) => map.get(id),
|
|
107
|
+
remember: (id, callId) => {
|
|
108
|
+
map.set(id, callId);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* Translate a decoded provider event into zero-or-more `TurnEvent`s.
|
|
114
|
+
* Mutates `lookup` to record `(item_id → call_id)` from `output_item.added`
|
|
115
|
+
* so subsequent `function_call_arguments.delta` events can be tagged with
|
|
116
|
+
* the right call id.
|
|
117
|
+
*/
|
|
118
|
+
const eventToDeltas = (event, lookup) => Match.value(event).pipe(Match.discriminatorsExhaustive("type")({
|
|
119
|
+
"response.output_item.added": ({ item }) => {
|
|
120
|
+
if (item.type !== "function_call") return [];
|
|
121
|
+
if (item.id !== void 0) lookup.remember(item.id, item.call_id);
|
|
122
|
+
return [{
|
|
123
|
+
type: "tool_call_start",
|
|
124
|
+
call_id: item.call_id,
|
|
125
|
+
name: item.name
|
|
126
|
+
}];
|
|
127
|
+
},
|
|
128
|
+
"response.output_text.delta": ({ delta }) => [{
|
|
129
|
+
type: "text_delta",
|
|
130
|
+
text: delta
|
|
131
|
+
}],
|
|
132
|
+
"response.function_call_arguments.delta": ({ item_id, delta }) => {
|
|
133
|
+
const call_id = lookup.resolve(item_id);
|
|
134
|
+
return call_id === void 0 ? [] : [{
|
|
135
|
+
type: "tool_call_args_delta",
|
|
136
|
+
call_id,
|
|
137
|
+
delta
|
|
138
|
+
}];
|
|
139
|
+
},
|
|
140
|
+
"response.reasoning_text.delta": ({ delta }) => [{
|
|
141
|
+
type: "reasoning_delta",
|
|
142
|
+
text: delta,
|
|
143
|
+
kind: "trace"
|
|
144
|
+
}],
|
|
145
|
+
"response.reasoning_summary_text.delta": ({ delta }) => [{
|
|
146
|
+
type: "reasoning_delta",
|
|
147
|
+
text: delta,
|
|
148
|
+
kind: "summary"
|
|
149
|
+
}],
|
|
150
|
+
"response.refusal.delta": ({ delta }) => [{
|
|
151
|
+
type: "refusal_delta",
|
|
152
|
+
text: delta
|
|
153
|
+
}],
|
|
154
|
+
"response.refusal.done": () => [],
|
|
155
|
+
"response.completed": ({ response }) => [{
|
|
156
|
+
type: "turn_complete",
|
|
157
|
+
turn: turnFromCompleted(response)
|
|
158
|
+
}],
|
|
159
|
+
"response.incomplete": ({ response }) => [{
|
|
160
|
+
type: "turn_complete",
|
|
161
|
+
turn: turnFromCompleted(response)
|
|
162
|
+
}],
|
|
163
|
+
"response.failed": () => [],
|
|
164
|
+
error: () => [],
|
|
165
|
+
_unknown: () => []
|
|
166
|
+
}));
|
|
167
|
+
//#endregion
|
|
168
|
+
export { KnownProviderEvent, ProviderEvent, eventToDeltas, makeCallIdLookup, streamEvents_exports as t };
|
|
169
|
+
|
|
170
|
+
//# sourceMappingURL=streamEvents.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"streamEvents.mjs","names":[],"sources":["../src/streamEvents.ts"],"sourcesContent":["import { Match, Schema } from \"effect\"\nimport type { TurnEvent } from \"@effect-uai/core/Turn\"\nimport { WireOutputItem, WireResponseCompleted, turnFromCompleted } from \"./codec.js\"\n\n// ---------------------------------------------------------------------------\n// Schemas for the SSE event payloads we care about. The Responses API ships\n// many event types; we model only the ones that map to a `TurnEvent`. All\n// others are silently ignored at the dispatch site.\n// ---------------------------------------------------------------------------\n\nconst OutputItemAdded = Schema.Struct({\n type: Schema.Literal(\"response.output_item.added\"),\n item: WireOutputItem,\n})\n\nconst OutputTextDelta = Schema.Struct({\n type: Schema.Literal(\"response.output_text.delta\"),\n delta: Schema.String,\n})\n\nconst FunctionArgsDelta = Schema.Struct({\n type: Schema.Literal(\"response.function_call_arguments.delta\"),\n item_id: Schema.String,\n delta: Schema.String,\n})\n\nconst ReasoningTextDelta = Schema.Struct({\n type: Schema.Literal(\"response.reasoning_text.delta\"),\n delta: Schema.String,\n})\n\nconst ReasoningSummaryDelta = Schema.Struct({\n type: Schema.Literal(\"response.reasoning_summary_text.delta\"),\n delta: Schema.String,\n})\n\nconst RefusalDelta = Schema.Struct({\n type: Schema.Literal(\"response.refusal.delta\"),\n delta: Schema.String,\n})\n\nconst RefusalDone = Schema.Struct({\n type: Schema.Literal(\"response.refusal.done\"),\n refusal: Schema.optional(Schema.String),\n})\n\nconst Completed = Schema.Struct({\n type: Schema.Literal(\"response.completed\"),\n response: WireResponseCompleted,\n})\n\nconst Incomplete = Schema.Struct({\n type: Schema.Literal(\"response.incomplete\"),\n response: WireResponseCompleted,\n})\n\nconst Failed = Schema.Struct({\n type: Schema.Literal(\"response.failed\"),\n response: WireResponseCompleted,\n})\n\nconst ErrorEvent = Schema.Struct({\n type: Schema.Literal(\"error\"),\n message: Schema.optional(Schema.String),\n code: Schema.optional(Schema.String),\n})\n\n/**\n * Catch-all variant for wire events that fail to decode against any known\n * schema, plus events that fail to JSON-parse. The decoder never produces\n * this directly - it's synthesized by `sseEventToProviderEvent` when\n * `decodeKnown` fails.\n */\nconst Unknown = Schema.Struct({\n type: Schema.Literal(\"_unknown\"),\n raw: Schema.Unknown,\n})\n\n/**\n * Internal: union of variants we actually know how to decode from the wire.\n * Used as the decode target; failures are caught and re-emitted as `Unknown`.\n */\nexport const KnownProviderEvent = Schema.Union([\n OutputItemAdded,\n OutputTextDelta,\n FunctionArgsDelta,\n ReasoningTextDelta,\n ReasoningSummaryDelta,\n RefusalDelta,\n RefusalDone,\n Completed,\n Incomplete,\n Failed,\n ErrorEvent,\n])\n\n/**\n * Public: every event the native stream can emit. Discriminated on `type`.\n * The `_unknown` branch closes the cardinality so downstream `Match.exhaustive`\n * cannot silently miss a wire event we didn't model.\n */\nexport const ProviderEvent = Schema.Union([\n OutputItemAdded,\n OutputTextDelta,\n FunctionArgsDelta,\n ReasoningTextDelta,\n ReasoningSummaryDelta,\n RefusalDelta,\n RefusalDone,\n Completed,\n Incomplete,\n Failed,\n ErrorEvent,\n Unknown,\n])\nexport type ProviderEvent = typeof ProviderEvent.Type\n\n// `call_id` lookup for `function_call_arguments.delta`. The SSE event\n// references the *item id*, not the call id; we keep a small map populated\n// from the `output_item.added` events.\nexport type CallIdLookup = {\n readonly resolve: (itemId: string) => string | undefined\n readonly remember: (itemId: string, callId: string) => void\n}\n\nexport const makeCallIdLookup = (): CallIdLookup => {\n const map = new Map<string, string>()\n return {\n resolve: (id) => map.get(id),\n remember: (id, callId) => {\n map.set(id, callId)\n },\n }\n}\n\n/**\n * Translate a decoded provider event into zero-or-more `TurnEvent`s.\n * Mutates `lookup` to record `(item_id → call_id)` from `output_item.added`\n * so subsequent `function_call_arguments.delta` events can be tagged with\n * the right call id.\n */\nexport const eventToDeltas = (\n event: ProviderEvent,\n lookup: CallIdLookup,\n): ReadonlyArray<TurnEvent> =>\n Match.value(event).pipe(\n Match.discriminatorsExhaustive(\"type\")({\n \"response.output_item.added\": ({ item }) => {\n if (item.type !== \"function_call\") return []\n if (item.id !== undefined) lookup.remember(item.id, item.call_id)\n return [\n {\n type: \"tool_call_start\" as const,\n call_id: item.call_id,\n name: item.name,\n },\n ]\n },\n \"response.output_text.delta\": ({ delta }) => [{ type: \"text_delta\" as const, text: delta }],\n \"response.function_call_arguments.delta\": ({ item_id, delta }) => {\n const call_id = lookup.resolve(item_id)\n return call_id === undefined\n ? []\n : [{ type: \"tool_call_args_delta\" as const, call_id, delta }]\n },\n \"response.reasoning_text.delta\": ({ delta }) => [\n { type: \"reasoning_delta\" as const, text: delta, kind: \"trace\" as const },\n ],\n \"response.reasoning_summary_text.delta\": ({ delta }) => [\n { type: \"reasoning_delta\" as const, text: delta, kind: \"summary\" as const },\n ],\n \"response.refusal.delta\": ({ delta }) => [{ type: \"refusal_delta\" as const, text: delta }],\n // Terminal marker for the refusal stream; the deltas already covered\n // the text, so no canonical event is emitted here.\n \"response.refusal.done\": () => [],\n \"response.completed\": ({ response }) => [\n { type: \"turn_complete\" as const, turn: turnFromCompleted(response) },\n ],\n // Incomplete = the model stopped early but produced a usable partial\n // turn (max_tokens, content_filter, max_tool_calls, refusal). Same\n // payload shape as `response.completed`; the stop_reason carries the\n // reason.\n \"response.incomplete\": ({ response }) => [\n { type: \"turn_complete\" as const, turn: turnFromCompleted(response) },\n ],\n // Failed and error are surfaced separately as `AiError` by the stream\n // pipeline, not as canonical events.\n \"response.failed\": () => [],\n error: () => [],\n // No silent drops: unknown wire events flow through `streamNative` but\n // produce no canonical delta. Step 3 (canonical `other` event) replaces\n // this with a forwarded `other` delta on `TurnEvent`.\n _unknown: () => [],\n }),\n )\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effect-uai/responses",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "OpenAI Responses API provider for @effect-uai/core.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -20,6 +20,12 @@
|
|
|
20
20
|
"url": "https://github.com/betalyra/effect-uai",
|
|
21
21
|
"directory": "packages/providers/responses"
|
|
22
22
|
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"src",
|
|
26
|
+
"README.md",
|
|
27
|
+
"LICENSE"
|
|
28
|
+
],
|
|
23
29
|
"type": "module",
|
|
24
30
|
"main": "./dist/index.mjs",
|
|
25
31
|
"types": "./dist/index.d.mts",
|
|
@@ -27,25 +33,27 @@
|
|
|
27
33
|
".": {
|
|
28
34
|
"types": "./dist/index.d.mts",
|
|
29
35
|
"import": "./dist/index.mjs"
|
|
36
|
+
},
|
|
37
|
+
"./Responses": {
|
|
38
|
+
"types": "./dist/Responses.d.mts",
|
|
39
|
+
"import": "./dist/Responses.mjs"
|
|
40
|
+
},
|
|
41
|
+
"./OpenAIEmbedding": {
|
|
42
|
+
"types": "./dist/OpenAIEmbedding.d.mts",
|
|
43
|
+
"import": "./dist/OpenAIEmbedding.mjs"
|
|
30
44
|
}
|
|
31
45
|
},
|
|
32
|
-
"files": [
|
|
33
|
-
"dist",
|
|
34
|
-
"src",
|
|
35
|
-
"README.md",
|
|
36
|
-
"LICENSE"
|
|
37
|
-
],
|
|
38
46
|
"publishConfig": {
|
|
39
47
|
"access": "public"
|
|
40
48
|
},
|
|
41
|
-
"peerDependencies": {
|
|
42
|
-
"effect": "4.0.0-beta.57",
|
|
43
|
-
"@effect-uai/core": "^0.1.0"
|
|
44
|
-
},
|
|
45
49
|
"devDependencies": {
|
|
46
50
|
"effect": "4.0.0-beta.57",
|
|
47
51
|
"typescript": "^6.0.3",
|
|
48
|
-
"@effect-uai/core": "0.
|
|
52
|
+
"@effect-uai/core": "0.3.0"
|
|
53
|
+
},
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"@effect-uai/core": ">=0.2.0 <1",
|
|
56
|
+
"effect": "4.0.0-beta.57"
|
|
49
57
|
},
|
|
50
58
|
"scripts": {
|
|
51
59
|
"build": "tsdown",
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { Context, Effect, Layer, Redacted, Schema } from "effect"
|
|
2
|
+
import { HttpClient, HttpClientRequest } from "effect/unstable/http"
|
|
3
|
+
import * as AiError from "@effect-uai/core/AiError"
|
|
4
|
+
import type { Embedding, EmbedContentPart, EmbedInput, Usage } from "@effect-uai/core/Embedding"
|
|
5
|
+
import {
|
|
6
|
+
type CommonEmbedManyRequest,
|
|
7
|
+
type CommonEmbedRequest,
|
|
8
|
+
EmbeddingModel,
|
|
9
|
+
type EmbeddingModelService,
|
|
10
|
+
type EmbedManyResponse,
|
|
11
|
+
type EmbedResponse,
|
|
12
|
+
} from "@effect-uai/core/EmbeddingModel"
|
|
13
|
+
import type { OpenAIEmbeddingModel } from "./models.js"
|
|
14
|
+
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Public types
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* OpenAI's embedding API has no task-type semantics, so `task` is omitted
|
|
21
|
+
* from the request type intentionally - passing it would be a compile
|
|
22
|
+
* error. The generic `EmbeddingModel` registration accepts and silently
|
|
23
|
+
* ignores `task` from `CommonEmbedRequest`.
|
|
24
|
+
*/
|
|
25
|
+
export type OpenAIEmbedRequest = Omit<CommonEmbedRequest, "model" | "task"> & {
|
|
26
|
+
/** Narrows `CommonEmbedRequest.model` to the typed OpenAI union. */
|
|
27
|
+
readonly model: OpenAIEmbeddingModel
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type OpenAIEmbedManyRequest = Omit<OpenAIEmbedRequest, "input"> & {
|
|
31
|
+
readonly inputs: ReadonlyArray<EmbedInput>
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type OpenAIEmbeddingService = {
|
|
35
|
+
readonly embed: (request: OpenAIEmbedRequest) => Effect.Effect<EmbedResponse, AiError.AiError>
|
|
36
|
+
readonly embedMany: (
|
|
37
|
+
request: OpenAIEmbedManyRequest,
|
|
38
|
+
) => Effect.Effect<EmbedManyResponse, AiError.AiError>
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Provider-typed service tag. Yield this for OpenAI-typed embedding
|
|
43
|
+
* requests; yield the generic `EmbeddingModel` tag for provider-portable
|
|
44
|
+
* code. Both are registered by `layer`.
|
|
45
|
+
*/
|
|
46
|
+
export class OpenAIEmbedding extends Context.Service<OpenAIEmbedding, OpenAIEmbeddingService>()(
|
|
47
|
+
"@betalyra/effect-uai/providers/responses/OpenAIEmbedding",
|
|
48
|
+
) {}
|
|
49
|
+
|
|
50
|
+
export type Config = {
|
|
51
|
+
readonly apiKey: Redacted.Redacted
|
|
52
|
+
readonly baseUrl?: string
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// Codec - input flattening to the single string OpenAI expects
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
const imageRejected = (param: string): AiError.AiError =>
|
|
60
|
+
new AiError.InvalidRequest({
|
|
61
|
+
provider: "openai",
|
|
62
|
+
param,
|
|
63
|
+
raw: "OpenAI embeddings are text-only; pass a string or { text }",
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
const partToText = (
|
|
67
|
+
param: string,
|
|
68
|
+
part: EmbedContentPart,
|
|
69
|
+
): Effect.Effect<string, AiError.AiError> =>
|
|
70
|
+
"text" in part ? Effect.succeed(part.text) : Effect.fail(imageRejected(param))
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* OpenAI accepts a single string per input. Pure-text `content[]` is
|
|
74
|
+
* concatenated with newlines (treats them as paragraphs of one document);
|
|
75
|
+
* any image part fails with `InvalidRequest`.
|
|
76
|
+
*/
|
|
77
|
+
const inputToString = (input: EmbedInput): Effect.Effect<string, AiError.AiError> => {
|
|
78
|
+
if (typeof input === "string") return Effect.succeed(input)
|
|
79
|
+
if ("text" in input) return Effect.succeed(input.text)
|
|
80
|
+
if ("image" in input) return Effect.fail(imageRejected("input.image"))
|
|
81
|
+
return Effect.forEach(input.content, (p, i) => partToText(`input.content[${i}].image`, p)).pipe(
|
|
82
|
+
Effect.map((texts) => texts.join("\n")),
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
// Codec - request body
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
|
|
90
|
+
type WireBody = {
|
|
91
|
+
readonly model: string
|
|
92
|
+
readonly input: string | ReadonlyArray<string>
|
|
93
|
+
readonly dimensions?: number
|
|
94
|
+
readonly encoding_format?: "float" | "base64"
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const buildSingleBody = (request: OpenAIEmbedRequest): Effect.Effect<WireBody, AiError.AiError> =>
|
|
98
|
+
inputToString(request.input).pipe(
|
|
99
|
+
Effect.map((input) => ({
|
|
100
|
+
model: request.model,
|
|
101
|
+
input,
|
|
102
|
+
...(request.dimensions !== undefined && { dimensions: request.dimensions }),
|
|
103
|
+
})),
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
const buildBatchBody = (
|
|
107
|
+
request: OpenAIEmbedManyRequest,
|
|
108
|
+
): Effect.Effect<WireBody, AiError.AiError> =>
|
|
109
|
+
Effect.forEach(request.inputs, inputToString).pipe(
|
|
110
|
+
Effect.map((inputs) => ({
|
|
111
|
+
model: request.model,
|
|
112
|
+
input: inputs,
|
|
113
|
+
...(request.dimensions !== undefined && { dimensions: request.dimensions }),
|
|
114
|
+
})),
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
// Codec - response
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
|
|
121
|
+
const WireEmbeddingItem = Schema.Struct({
|
|
122
|
+
embedding: Schema.Array(Schema.Number),
|
|
123
|
+
index: Schema.Number,
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
const WireUsage = Schema.Struct({
|
|
127
|
+
prompt_tokens: Schema.Number,
|
|
128
|
+
total_tokens: Schema.Number,
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
const WireResponse = Schema.Struct({
|
|
132
|
+
data: Schema.Array(WireEmbeddingItem),
|
|
133
|
+
model: Schema.String,
|
|
134
|
+
usage: WireUsage,
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
const valuesToEmbedding = (values: ReadonlyArray<number>): Embedding => ({
|
|
138
|
+
_tag: "float32",
|
|
139
|
+
vector: Float32Array.from(values),
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
// OpenAI returns `data[]` unsorted in theory; sort by `index` so caller
|
|
143
|
+
// gets results in the order they passed inputs.
|
|
144
|
+
const orderedEmbeddings = (
|
|
145
|
+
data: ReadonlyArray<{ readonly embedding: ReadonlyArray<number>; readonly index: number }>,
|
|
146
|
+
): ReadonlyArray<Embedding> =>
|
|
147
|
+
[...data].sort((a, b) => a.index - b.index).map((item) => valuesToEmbedding(item.embedding))
|
|
148
|
+
|
|
149
|
+
// ---------------------------------------------------------------------------
|
|
150
|
+
// HTTP errors
|
|
151
|
+
// ---------------------------------------------------------------------------
|
|
152
|
+
|
|
153
|
+
const httpStatusError = (status: number, body: string): AiError.AiError => {
|
|
154
|
+
const provider = "openai"
|
|
155
|
+
const raw = body
|
|
156
|
+
if (status === 429) return new AiError.RateLimited({ provider, raw })
|
|
157
|
+
if (status === 408 || status === 504) return new AiError.Timeout({ provider, raw })
|
|
158
|
+
if (status === 401) return new AiError.AuthFailed({ provider, subtype: "auth", raw })
|
|
159
|
+
if (status === 403) return new AiError.AuthFailed({ provider, subtype: "permission", raw })
|
|
160
|
+
if (status === 402) return new AiError.AuthFailed({ provider, subtype: "billing", raw })
|
|
161
|
+
if (status === 413) return new AiError.ContextLengthExceeded({ provider, raw })
|
|
162
|
+
if (status >= 500) return new AiError.Unavailable({ provider, status, raw })
|
|
163
|
+
return new AiError.InvalidRequest({ provider, raw })
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const transportFailure = (cause: unknown): AiError.AiError =>
|
|
167
|
+
new AiError.Unavailable({ provider: "openai", raw: cause })
|
|
168
|
+
|
|
169
|
+
// ---------------------------------------------------------------------------
|
|
170
|
+
// HTTP plumbing
|
|
171
|
+
// ---------------------------------------------------------------------------
|
|
172
|
+
|
|
173
|
+
const baseUrl = (cfg: Config): string => cfg.baseUrl ?? "https://api.openai.com/v1"
|
|
174
|
+
|
|
175
|
+
const postEmbed = (
|
|
176
|
+
cfg: Config,
|
|
177
|
+
body: WireBody,
|
|
178
|
+
): Effect.Effect<typeof WireResponse.Type, AiError.AiError, HttpClient.HttpClient> =>
|
|
179
|
+
Effect.gen(function* () {
|
|
180
|
+
const client = yield* HttpClient.HttpClient
|
|
181
|
+
const httpRequest = HttpClientRequest.post(`${baseUrl(cfg)}/embeddings`).pipe(
|
|
182
|
+
HttpClientRequest.bearerToken(cfg.apiKey),
|
|
183
|
+
HttpClientRequest.bodyJsonUnsafe(body),
|
|
184
|
+
)
|
|
185
|
+
const response = yield* client.execute(httpRequest).pipe(Effect.mapError(transportFailure))
|
|
186
|
+
if (response.status >= 400) {
|
|
187
|
+
const text = yield* response.text.pipe(Effect.orElseSucceed(() => ""))
|
|
188
|
+
return yield* Effect.fail(httpStatusError(response.status, text))
|
|
189
|
+
}
|
|
190
|
+
const json = yield* response.json.pipe(Effect.mapError(transportFailure))
|
|
191
|
+
return yield* Schema.decodeUnknownEffect(WireResponse)(json).pipe(
|
|
192
|
+
Effect.mapError(transportFailure),
|
|
193
|
+
)
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
// ---------------------------------------------------------------------------
|
|
197
|
+
// Service implementation
|
|
198
|
+
// ---------------------------------------------------------------------------
|
|
199
|
+
|
|
200
|
+
const usageOf = (u: typeof WireUsage.Type): Usage => ({ inputTokens: u.prompt_tokens })
|
|
201
|
+
|
|
202
|
+
const embedImpl =
|
|
203
|
+
(cfg: Config) =>
|
|
204
|
+
(
|
|
205
|
+
request: OpenAIEmbedRequest,
|
|
206
|
+
): Effect.Effect<EmbedResponse, AiError.AiError, HttpClient.HttpClient> =>
|
|
207
|
+
buildSingleBody(request).pipe(
|
|
208
|
+
Effect.flatMap((body) => postEmbed(cfg, body)),
|
|
209
|
+
Effect.flatMap((decoded) => {
|
|
210
|
+
const first = decoded.data[0]
|
|
211
|
+
if (first === undefined) {
|
|
212
|
+
return Effect.fail(transportFailure("OpenAI returned empty `data` array"))
|
|
213
|
+
}
|
|
214
|
+
return Effect.succeed<EmbedResponse>({
|
|
215
|
+
embedding: valuesToEmbedding(first.embedding),
|
|
216
|
+
usage: usageOf(decoded.usage),
|
|
217
|
+
})
|
|
218
|
+
}),
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
const embedManyImpl =
|
|
222
|
+
(cfg: Config) =>
|
|
223
|
+
(
|
|
224
|
+
request: OpenAIEmbedManyRequest,
|
|
225
|
+
): Effect.Effect<EmbedManyResponse, AiError.AiError, HttpClient.HttpClient> =>
|
|
226
|
+
buildBatchBody(request).pipe(
|
|
227
|
+
Effect.flatMap((body) => postEmbed(cfg, body)),
|
|
228
|
+
Effect.map(
|
|
229
|
+
(decoded): EmbedManyResponse => ({
|
|
230
|
+
embeddings: orderedEmbeddings(decoded.data),
|
|
231
|
+
usage: usageOf(decoded.usage),
|
|
232
|
+
}),
|
|
233
|
+
),
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
// ---------------------------------------------------------------------------
|
|
237
|
+
// Constructors
|
|
238
|
+
// ---------------------------------------------------------------------------
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Build an `OpenAIEmbeddingService` value. For Layer-based setup, prefer
|
|
242
|
+
* `layer`.
|
|
243
|
+
*/
|
|
244
|
+
export const make = (
|
|
245
|
+
cfg: Config,
|
|
246
|
+
): Effect.Effect<OpenAIEmbeddingService, never, HttpClient.HttpClient> =>
|
|
247
|
+
Effect.map(HttpClient.HttpClient.asEffect(), (client) => ({
|
|
248
|
+
embed: (request) =>
|
|
249
|
+
embedImpl(cfg)(request).pipe(Effect.provideService(HttpClient.HttpClient, client)),
|
|
250
|
+
embedMany: (request) =>
|
|
251
|
+
embedManyImpl(cfg)(request).pipe(Effect.provideService(HttpClient.HttpClient, client)),
|
|
252
|
+
}))
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Layer that registers both the provider-specific `OpenAIEmbedding` tag
|
|
256
|
+
* and the generic `EmbeddingModel` tag, sharing one underlying
|
|
257
|
+
* implementation. The generic registration trusts the caller; invalid
|
|
258
|
+
* encodings produce an `InvalidRequest` from OpenAI's API.
|
|
259
|
+
*/
|
|
260
|
+
export const layer = (
|
|
261
|
+
cfg: Config,
|
|
262
|
+
): Layer.Layer<OpenAIEmbedding | EmbeddingModel, never, HttpClient.HttpClient> => {
|
|
263
|
+
const typed = Layer.effect(OpenAIEmbedding, make(cfg))
|
|
264
|
+
const generic = Layer.effect(
|
|
265
|
+
EmbeddingModel,
|
|
266
|
+
Effect.map(
|
|
267
|
+
make(cfg),
|
|
268
|
+
(s): EmbeddingModelService => ({
|
|
269
|
+
embed: (req: CommonEmbedRequest) => s.embed(req as OpenAIEmbedRequest),
|
|
270
|
+
embedMany: (req: CommonEmbedManyRequest) => s.embedMany(req as OpenAIEmbedManyRequest),
|
|
271
|
+
}),
|
|
272
|
+
),
|
|
273
|
+
)
|
|
274
|
+
return Layer.merge(typed, generic)
|
|
275
|
+
}
|
package/src/Responses.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { Context, Effect, Layer, Match, Option, Redacted, Schema, Stream } from "effect"
|
|
2
2
|
import { HttpClient, HttpClientRequest } from "effect/unstable/http"
|
|
3
3
|
import * as AiError from "@effect-uai/core/AiError"
|
|
4
|
-
import { matchType } from "@effect-uai/core/Match"
|
|
5
4
|
import * as StructuredFormat from "@effect-uai/core/StructuredFormat"
|
|
6
5
|
import {
|
|
7
6
|
type CommonRequest,
|
|
@@ -23,7 +22,7 @@ import {
|
|
|
23
22
|
// Public types
|
|
24
23
|
// ---------------------------------------------------------------------------
|
|
25
24
|
|
|
26
|
-
export
|
|
25
|
+
export type ResponsesRequest = Omit<CommonRequest, "model"> & {
|
|
27
26
|
/**
|
|
28
27
|
* Narrows `CommonRequest.model` (`string`) to the typed `OpenAIModel`
|
|
29
28
|
* literal union for autocomplete.
|
|
@@ -52,7 +51,7 @@ export interface ResponsesRequest extends Omit<CommonRequest, "model"> {
|
|
|
52
51
|
readonly verbosity?: "low" | "medium" | "high"
|
|
53
52
|
}
|
|
54
53
|
|
|
55
|
-
export
|
|
54
|
+
export type ResponsesService = {
|
|
56
55
|
/**
|
|
57
56
|
* Stream the provider's native event vocabulary (post-SSE-decode).
|
|
58
57
|
* Use this when you need full vendor fidelity. For provider-portable
|
|
@@ -86,7 +85,7 @@ export class Responses extends Context.Service<Responses, ResponsesService>()(
|
|
|
86
85
|
"@betalyra/effect-uai/providers/responses/Responses",
|
|
87
86
|
) {}
|
|
88
87
|
|
|
89
|
-
export
|
|
88
|
+
export type Config = {
|
|
90
89
|
readonly apiKey: Redacted.Redacted
|
|
91
90
|
readonly baseUrl?: string
|
|
92
91
|
}
|
|
@@ -172,19 +171,18 @@ const makeUnknown = (raw: unknown): ProviderEvent => ({ type: "_unknown", raw })
|
|
|
172
171
|
* and produce `Option.none`.
|
|
173
172
|
*/
|
|
174
173
|
const eventToError = Match.type<ProviderEvent>().pipe(
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
(e): AiError.AiError =>
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
})
|
|
174
|
+
Match.discriminators("type")({
|
|
175
|
+
error: (e): AiError.AiError => new AiError.Unavailable({ provider: "responses", raw: e }),
|
|
176
|
+
"response.failed": (e): AiError.AiError => {
|
|
177
|
+
const code = e.response.error?.code
|
|
178
|
+
const message = e.response.error?.message
|
|
179
|
+
return new AiError.GenerationFailed({
|
|
180
|
+
provider: "responses",
|
|
181
|
+
...(code !== undefined && code !== null && { code }),
|
|
182
|
+
...(message !== undefined && message !== null && { message }),
|
|
183
|
+
raw: e,
|
|
184
|
+
})
|
|
185
|
+
},
|
|
188
186
|
}),
|
|
189
187
|
Match.option,
|
|
190
188
|
)
|