@effect-uai/anthropic 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.
@@ -0,0 +1,221 @@
1
+ import { Match, Schema } from "effect"
2
+ import { matchType } from "@effect-uai/core/Match"
3
+ import {
4
+ type Accumulator,
5
+ WireContentBlock,
6
+ type WireUsage,
7
+ appendInputJsonDelta,
8
+ appendSignatureDelta,
9
+ appendTextDelta,
10
+ appendThinkingDelta,
11
+ mergeUsage,
12
+ setStopReason,
13
+ startBlock,
14
+ } from "./codec.js"
15
+
16
+ // ---------------------------------------------------------------------------
17
+ // Wire schemas for the SSE events we map onto our `Accumulator`. Anthropic's
18
+ // streaming protocol uses named SSE events; we match on the inner JSON
19
+ // `type` field which mirrors the event name.
20
+ // ---------------------------------------------------------------------------
21
+
22
+ const WireMessageStartUsage = Schema.Struct({
23
+ input_tokens: Schema.optional(Schema.Number),
24
+ output_tokens: Schema.optional(Schema.Number),
25
+ cache_creation_input_tokens: Schema.optional(Schema.NullOr(Schema.Number)),
26
+ cache_read_input_tokens: Schema.optional(Schema.NullOr(Schema.Number)),
27
+ })
28
+
29
+ const MessageStart = Schema.Struct({
30
+ type: Schema.Literal("message_start"),
31
+ message: Schema.Struct({
32
+ id: Schema.optional(Schema.String),
33
+ usage: Schema.optional(WireMessageStartUsage),
34
+ }),
35
+ })
36
+
37
+ const ContentBlockStart = Schema.Struct({
38
+ type: Schema.Literal("content_block_start"),
39
+ index: Schema.Number,
40
+ content_block: WireContentBlock,
41
+ })
42
+
43
+ const TextDelta = Schema.Struct({
44
+ type: Schema.Literal("text_delta"),
45
+ text: Schema.String,
46
+ })
47
+
48
+ const InputJsonDelta = Schema.Struct({
49
+ type: Schema.Literal("input_json_delta"),
50
+ partial_json: Schema.String,
51
+ })
52
+
53
+ const ThinkingDelta = Schema.Struct({
54
+ type: Schema.Literal("thinking_delta"),
55
+ thinking: Schema.String,
56
+ })
57
+
58
+ const SignatureDelta = Schema.Struct({
59
+ type: Schema.Literal("signature_delta"),
60
+ signature: Schema.String,
61
+ })
62
+
63
+ const Delta = Schema.Union([TextDelta, InputJsonDelta, ThinkingDelta, SignatureDelta])
64
+
65
+ const ContentBlockDelta = Schema.Struct({
66
+ type: Schema.Literal("content_block_delta"),
67
+ index: Schema.Number,
68
+ delta: Delta,
69
+ })
70
+
71
+ const ContentBlockStop = Schema.Struct({
72
+ type: Schema.Literal("content_block_stop"),
73
+ index: Schema.Number,
74
+ })
75
+
76
+ const MessageDelta = Schema.Struct({
77
+ type: Schema.Literal("message_delta"),
78
+ delta: Schema.Struct({
79
+ stop_reason: Schema.optional(Schema.NullOr(Schema.String)),
80
+ stop_sequence: Schema.optional(Schema.NullOr(Schema.String)),
81
+ }),
82
+ usage: Schema.optional(WireMessageStartUsage),
83
+ })
84
+
85
+ const MessageStop = Schema.Struct({
86
+ type: Schema.Literal("message_stop"),
87
+ })
88
+
89
+ const Ping = Schema.Struct({
90
+ type: Schema.Literal("ping"),
91
+ })
92
+
93
+ const ErrorEvent = Schema.Struct({
94
+ type: Schema.Literal("error"),
95
+ error: Schema.optional(
96
+ Schema.Struct({
97
+ type: Schema.optional(Schema.String),
98
+ message: Schema.optional(Schema.String),
99
+ }),
100
+ ),
101
+ })
102
+
103
+ /**
104
+ * Catch-all variant for wire events that fail to decode against any known
105
+ * schema, plus events that fail to JSON-parse. The decoder never produces
106
+ * this directly - it's synthesized by `sseEventToProviderEvent` when
107
+ * `decodeKnown` fails.
108
+ */
109
+ const Unknown = Schema.Struct({
110
+ type: Schema.Literal("_unknown"),
111
+ raw: Schema.Unknown,
112
+ })
113
+
114
+ /**
115
+ * Internal: union of variants we actually know how to decode from the wire.
116
+ * Used as the decode target; failures are caught and re-emitted as `Unknown`.
117
+ */
118
+ export const KnownProviderEvent = Schema.Union([
119
+ MessageStart,
120
+ ContentBlockStart,
121
+ ContentBlockDelta,
122
+ ContentBlockStop,
123
+ MessageDelta,
124
+ MessageStop,
125
+ Ping,
126
+ ErrorEvent,
127
+ ])
128
+
129
+ /**
130
+ * Public: every event the native stream can emit. Discriminated on `type`.
131
+ * The `_unknown` branch closes the cardinality so downstream `Match.exhaustive`
132
+ * cannot silently miss a wire event we didn't model.
133
+ */
134
+ export const ProviderEvent = Schema.Union([
135
+ MessageStart,
136
+ ContentBlockStart,
137
+ ContentBlockDelta,
138
+ ContentBlockStop,
139
+ MessageDelta,
140
+ MessageStop,
141
+ Ping,
142
+ ErrorEvent,
143
+ Unknown,
144
+ ])
145
+ export type ProviderEvent = typeof ProviderEvent.Type
146
+
147
+ // ---------------------------------------------------------------------------
148
+ // Apply event → Accumulator. Pure: the mapping never fails. Caller wires
149
+ // this into `Stream.mapAccum` and emits text/turn_complete deltas based on
150
+ // the diff.
151
+ // ---------------------------------------------------------------------------
152
+
153
+ const mergeOptionalUsage = (acc: Accumulator, wire: WireUsage | undefined): Accumulator =>
154
+ wire === undefined ? acc : mergeUsage(acc, wire)
155
+
156
+ export const applyEvent = (acc: Accumulator, event: ProviderEvent): Accumulator =>
157
+ Match.value(event).pipe(
158
+ matchType("message_start", (e) => mergeOptionalUsage(acc, e.message.usage)),
159
+ matchType("content_block_start", (e) => startBlock(acc, e.index, e.content_block)),
160
+ matchType("content_block_delta", (e) =>
161
+ Match.value(e.delta).pipe(
162
+ matchType("text_delta", (d) => appendTextDelta(acc, e.index, d.text)),
163
+ matchType("input_json_delta", (d) => appendInputJsonDelta(acc, e.index, d.partial_json)),
164
+ matchType("thinking_delta", (d) => appendThinkingDelta(acc, e.index, d.thinking)),
165
+ matchType("signature_delta", (d) => appendSignatureDelta(acc, e.index, d.signature)),
166
+ Match.exhaustive,
167
+ ),
168
+ ),
169
+ matchType("content_block_stop", () => acc),
170
+ matchType("message_delta", (e) => {
171
+ const withUsage = mergeOptionalUsage(acc, e.usage)
172
+ const reason = e.delta.stop_reason
173
+ return reason === undefined || reason === null ? withUsage : setStopReason(withUsage, reason)
174
+ }),
175
+ matchType("message_stop", () => acc),
176
+ matchType("ping", () => acc),
177
+ matchType("error", () => acc),
178
+ // No silent drops: unknown wire events flow through `streamNative` but
179
+ // produce no accumulator change. Step 3 (canonical `other` event) will
180
+ // also forward them to `TurnEvent`.
181
+ matchType("_unknown", () => acc),
182
+ Match.exhaustive,
183
+ )
184
+
185
+ // ---------------------------------------------------------------------------
186
+ // Helpers for producing TurnEvent from a step.
187
+ // ---------------------------------------------------------------------------
188
+
189
+ export const isTextDeltaEvent = (
190
+ event: ProviderEvent,
191
+ ): event is Extract<
192
+ ProviderEvent,
193
+ { type: "content_block_delta"; delta: { type: "text_delta" } }
194
+ > => event.type === "content_block_delta" && event.delta.type === "text_delta"
195
+
196
+ export const isThinkingDeltaEvent = (
197
+ event: ProviderEvent,
198
+ ): event is Extract<
199
+ ProviderEvent,
200
+ { type: "content_block_delta"; delta: { type: "thinking_delta" } }
201
+ > => event.type === "content_block_delta" && event.delta.type === "thinking_delta"
202
+
203
+ export const isInputJsonDeltaEvent = (
204
+ event: ProviderEvent,
205
+ ): event is Extract<
206
+ ProviderEvent,
207
+ { type: "content_block_delta"; delta: { type: "input_json_delta" } }
208
+ > => event.type === "content_block_delta" && event.delta.type === "input_json_delta"
209
+
210
+ export const isToolUseStartEvent = (
211
+ event: ProviderEvent,
212
+ ): event is Extract<
213
+ ProviderEvent,
214
+ { type: "content_block_start"; content_block: { type: "tool_use" } }
215
+ > => event.type === "content_block_start" && event.content_block.type === "tool_use"
216
+
217
+ export const isMessageStop = (event: ProviderEvent): boolean => event.type === "message_stop"
218
+
219
+ export const isErrorEvent = (
220
+ event: ProviderEvent,
221
+ ): event is Extract<ProviderEvent, { type: "error" }> => event.type === "error"