@effect-uai/responses 0.1.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/LICENSE +21 -0
- package/README.md +45 -0
- package/dist/chunk-CfYAbeIz.mjs +13 -0
- package/dist/index.d.mts +828 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +525 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +54 -0
- package/src/Responses.ts +316 -0
- package/src/codec.ts +298 -0
- package/src/index.ts +4 -0
- package/src/models.ts +24 -0
- package/src/streamEvents.ts +199 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { Match, Schema } from "effect"
|
|
2
|
+
import { matchType } from "@effect-uai/core/Match"
|
|
3
|
+
import type { TurnEvent } from "@effect-uai/core/Turn"
|
|
4
|
+
import { WireOutputItem, WireResponseCompleted, turnFromCompleted } from "./codec.js"
|
|
5
|
+
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// Schemas for the SSE event payloads we care about. The Responses API ships
|
|
8
|
+
// many event types; we model only the ones that map to a `TurnEvent`. All
|
|
9
|
+
// others are silently ignored at the dispatch site.
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
const OutputItemAdded = Schema.Struct({
|
|
13
|
+
type: Schema.Literal("response.output_item.added"),
|
|
14
|
+
item: WireOutputItem,
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const OutputTextDelta = Schema.Struct({
|
|
18
|
+
type: Schema.Literal("response.output_text.delta"),
|
|
19
|
+
delta: Schema.String,
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
const FunctionArgsDelta = Schema.Struct({
|
|
23
|
+
type: Schema.Literal("response.function_call_arguments.delta"),
|
|
24
|
+
item_id: Schema.String,
|
|
25
|
+
delta: Schema.String,
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
const ReasoningTextDelta = Schema.Struct({
|
|
29
|
+
type: Schema.Literal("response.reasoning_text.delta"),
|
|
30
|
+
delta: Schema.String,
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
const ReasoningSummaryDelta = Schema.Struct({
|
|
34
|
+
type: Schema.Literal("response.reasoning_summary_text.delta"),
|
|
35
|
+
delta: Schema.String,
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
const RefusalDelta = Schema.Struct({
|
|
39
|
+
type: Schema.Literal("response.refusal.delta"),
|
|
40
|
+
delta: Schema.String,
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
const RefusalDone = Schema.Struct({
|
|
44
|
+
type: Schema.Literal("response.refusal.done"),
|
|
45
|
+
refusal: Schema.optional(Schema.String),
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
const Completed = Schema.Struct({
|
|
49
|
+
type: Schema.Literal("response.completed"),
|
|
50
|
+
response: WireResponseCompleted,
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
const Incomplete = Schema.Struct({
|
|
54
|
+
type: Schema.Literal("response.incomplete"),
|
|
55
|
+
response: WireResponseCompleted,
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
const Failed = Schema.Struct({
|
|
59
|
+
type: Schema.Literal("response.failed"),
|
|
60
|
+
response: WireResponseCompleted,
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
const ErrorEvent = Schema.Struct({
|
|
64
|
+
type: Schema.Literal("error"),
|
|
65
|
+
message: Schema.optional(Schema.String),
|
|
66
|
+
code: Schema.optional(Schema.String),
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Catch-all variant for wire events that fail to decode against any known
|
|
71
|
+
* schema, plus events that fail to JSON-parse. The decoder never produces
|
|
72
|
+
* this directly - it's synthesized by `sseEventToProviderEvent` when
|
|
73
|
+
* `decodeKnown` fails.
|
|
74
|
+
*/
|
|
75
|
+
const Unknown = Schema.Struct({
|
|
76
|
+
type: Schema.Literal("_unknown"),
|
|
77
|
+
raw: Schema.Unknown,
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Internal: union of variants we actually know how to decode from the wire.
|
|
82
|
+
* Used as the decode target; failures are caught and re-emitted as `Unknown`.
|
|
83
|
+
*/
|
|
84
|
+
export const KnownProviderEvent = Schema.Union([
|
|
85
|
+
OutputItemAdded,
|
|
86
|
+
OutputTextDelta,
|
|
87
|
+
FunctionArgsDelta,
|
|
88
|
+
ReasoningTextDelta,
|
|
89
|
+
ReasoningSummaryDelta,
|
|
90
|
+
RefusalDelta,
|
|
91
|
+
RefusalDone,
|
|
92
|
+
Completed,
|
|
93
|
+
Incomplete,
|
|
94
|
+
Failed,
|
|
95
|
+
ErrorEvent,
|
|
96
|
+
])
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Public: every event the native stream can emit. Discriminated on `type`.
|
|
100
|
+
* The `_unknown` branch closes the cardinality so downstream `Match.exhaustive`
|
|
101
|
+
* cannot silently miss a wire event we didn't model.
|
|
102
|
+
*/
|
|
103
|
+
export const ProviderEvent = Schema.Union([
|
|
104
|
+
OutputItemAdded,
|
|
105
|
+
OutputTextDelta,
|
|
106
|
+
FunctionArgsDelta,
|
|
107
|
+
ReasoningTextDelta,
|
|
108
|
+
ReasoningSummaryDelta,
|
|
109
|
+
RefusalDelta,
|
|
110
|
+
RefusalDone,
|
|
111
|
+
Completed,
|
|
112
|
+
Incomplete,
|
|
113
|
+
Failed,
|
|
114
|
+
ErrorEvent,
|
|
115
|
+
Unknown,
|
|
116
|
+
])
|
|
117
|
+
export type ProviderEvent = typeof ProviderEvent.Type
|
|
118
|
+
|
|
119
|
+
// `call_id` lookup for `function_call_arguments.delta`. The SSE event
|
|
120
|
+
// references the *item id*, not the call id; we keep a small map populated
|
|
121
|
+
// from the `output_item.added` events.
|
|
122
|
+
export interface CallIdLookup {
|
|
123
|
+
readonly resolve: (itemId: string) => string | undefined
|
|
124
|
+
readonly remember: (itemId: string, callId: string) => void
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export const makeCallIdLookup = (): CallIdLookup => {
|
|
128
|
+
const map = new Map<string, string>()
|
|
129
|
+
return {
|
|
130
|
+
resolve: (id) => map.get(id),
|
|
131
|
+
remember: (id, callId) => {
|
|
132
|
+
map.set(id, callId)
|
|
133
|
+
},
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Translate a decoded provider event into zero-or-more `TurnEvent`s.
|
|
139
|
+
* Mutates `lookup` to record `(item_id → call_id)` from `output_item.added`
|
|
140
|
+
* so subsequent `function_call_arguments.delta` events can be tagged with
|
|
141
|
+
* the right call id.
|
|
142
|
+
*/
|
|
143
|
+
export const eventToDeltas = (
|
|
144
|
+
event: ProviderEvent,
|
|
145
|
+
lookup: CallIdLookup,
|
|
146
|
+
): ReadonlyArray<TurnEvent> =>
|
|
147
|
+
Match.value(event).pipe(
|
|
148
|
+
matchType("response.output_item.added", ({ item }) => {
|
|
149
|
+
if (item.type !== "function_call") return []
|
|
150
|
+
if (item.id !== undefined) lookup.remember(item.id, item.call_id)
|
|
151
|
+
return [
|
|
152
|
+
{
|
|
153
|
+
type: "tool_call_start" as const,
|
|
154
|
+
call_id: item.call_id,
|
|
155
|
+
name: item.name,
|
|
156
|
+
},
|
|
157
|
+
]
|
|
158
|
+
}),
|
|
159
|
+
matchType("response.output_text.delta", ({ delta }) => [
|
|
160
|
+
{ type: "text_delta" as const, text: delta },
|
|
161
|
+
]),
|
|
162
|
+
matchType("response.function_call_arguments.delta", ({ item_id, delta }) => {
|
|
163
|
+
const call_id = lookup.resolve(item_id)
|
|
164
|
+
return call_id === undefined
|
|
165
|
+
? []
|
|
166
|
+
: [{ type: "tool_call_args_delta" as const, call_id, delta }]
|
|
167
|
+
}),
|
|
168
|
+
matchType("response.reasoning_text.delta", ({ delta }) => [
|
|
169
|
+
{ type: "reasoning_delta" as const, text: delta, kind: "trace" as const },
|
|
170
|
+
]),
|
|
171
|
+
matchType("response.reasoning_summary_text.delta", ({ delta }) => [
|
|
172
|
+
{ type: "reasoning_delta" as const, text: delta, kind: "summary" as const },
|
|
173
|
+
]),
|
|
174
|
+
matchType("response.refusal.delta", ({ delta }) => [
|
|
175
|
+
{ type: "refusal_delta" as const, text: delta },
|
|
176
|
+
]),
|
|
177
|
+
// Terminal marker for the refusal stream; the deltas already covered
|
|
178
|
+
// the text, so no canonical event is emitted here.
|
|
179
|
+
matchType("response.refusal.done", () => []),
|
|
180
|
+
matchType("response.completed", ({ response }) => [
|
|
181
|
+
{ type: "turn_complete" as const, turn: turnFromCompleted(response) },
|
|
182
|
+
]),
|
|
183
|
+
// Incomplete = the model stopped early but produced a usable partial
|
|
184
|
+
// turn (max_tokens, content_filter, max_tool_calls, refusal). Same
|
|
185
|
+
// payload shape as `response.completed`; the stop_reason carries the
|
|
186
|
+
// reason.
|
|
187
|
+
matchType("response.incomplete", ({ response }) => [
|
|
188
|
+
{ type: "turn_complete" as const, turn: turnFromCompleted(response) },
|
|
189
|
+
]),
|
|
190
|
+
// Failed and error are surfaced separately as `AiError` by the stream
|
|
191
|
+
// pipeline, not as canonical events.
|
|
192
|
+
matchType("response.failed", () => []),
|
|
193
|
+
matchType("error", () => []),
|
|
194
|
+
// No silent drops: unknown wire events flow through `streamNative` but
|
|
195
|
+
// produce no canonical delta. Step 3 (canonical `other` event) replaces
|
|
196
|
+
// this with a forwarded `other` delta on `TurnEvent`.
|
|
197
|
+
matchType("_unknown", () => []),
|
|
198
|
+
Match.exhaustive,
|
|
199
|
+
)
|