@effect-uai/ui 0.0.0-stage → 0.17.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.
Files changed (76) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +64 -2
  3. package/dist/Atoms.d.mts +141 -0
  4. package/dist/Atoms.d.mts.map +1 -0
  5. package/dist/Atoms.mjs +132 -0
  6. package/dist/Atoms.mjs.map +1 -0
  7. package/dist/Client-C9UYN_VK.d.mts +219 -0
  8. package/dist/Client-C9UYN_VK.d.mts.map +1 -0
  9. package/dist/Client.d.mts +2 -0
  10. package/dist/Client.mjs +55 -0
  11. package/dist/Client.mjs.map +1 -0
  12. package/dist/Input-Cz-Kxusm.d.mts +277 -0
  13. package/dist/Input-Cz-Kxusm.d.mts.map +1 -0
  14. package/dist/Input.d.mts +2 -0
  15. package/dist/Input.mjs +127 -0
  16. package/dist/Input.mjs.map +1 -0
  17. package/dist/Projection.d.mts +44 -0
  18. package/dist/Projection.d.mts.map +1 -0
  19. package/dist/Projection.mjs +133 -0
  20. package/dist/Projection.mjs.map +1 -0
  21. package/dist/Run.d.mts +44 -0
  22. package/dist/Run.d.mts.map +1 -0
  23. package/dist/Run.mjs +117 -0
  24. package/dist/Run.mjs.map +1 -0
  25. package/dist/RunEvent.d.mts +1225 -0
  26. package/dist/RunEvent.d.mts.map +1 -0
  27. package/dist/RunEvent.mjs +264 -0
  28. package/dist/RunEvent.mjs.map +1 -0
  29. package/dist/RunView-nHfJwN8t.d.mts +380 -0
  30. package/dist/RunView-nHfJwN8t.d.mts.map +1 -0
  31. package/dist/RunView.d.mts +2 -0
  32. package/dist/RunView.mjs +452 -0
  33. package/dist/RunView.mjs.map +1 -0
  34. package/dist/SseEncoding.d.mts +437 -0
  35. package/dist/SseEncoding.d.mts.map +1 -0
  36. package/dist/SseEncoding.mjs +64 -0
  37. package/dist/SseEncoding.mjs.map +1 -0
  38. package/dist/Thread-CAXsjhpX.d.mts +474 -0
  39. package/dist/Thread-CAXsjhpX.d.mts.map +1 -0
  40. package/dist/Thread.d.mts +2 -0
  41. package/dist/Thread.mjs +225 -0
  42. package/dist/Thread.mjs.map +1 -0
  43. package/dist/Transport.d.mts +96 -0
  44. package/dist/Transport.d.mts.map +1 -0
  45. package/dist/Transport.mjs +82 -0
  46. package/dist/Transport.mjs.map +1 -0
  47. package/dist/UiContract.d.mts +114 -0
  48. package/dist/UiContract.d.mts.map +1 -0
  49. package/dist/UiContract.mjs +229 -0
  50. package/dist/UiContract.mjs.map +1 -0
  51. package/dist/index.d.mts +12 -0
  52. package/dist/index.mjs +12 -0
  53. package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
  54. package/package.json +99 -3
  55. package/src/Atoms.test.ts +163 -0
  56. package/src/Atoms.ts +355 -0
  57. package/src/Client.test.ts +53 -0
  58. package/src/Client.ts +92 -0
  59. package/src/Input.test.ts +68 -0
  60. package/src/Input.ts +165 -0
  61. package/src/Projection.test.ts +198 -0
  62. package/src/Projection.ts +142 -0
  63. package/src/Run.test.ts +119 -0
  64. package/src/Run.ts +166 -0
  65. package/src/RunEvent.test.ts +84 -0
  66. package/src/RunEvent.ts +374 -0
  67. package/src/RunView.test.ts +98 -0
  68. package/src/RunView.ts +583 -0
  69. package/src/SseEncoding.test.ts +46 -0
  70. package/src/SseEncoding.ts +77 -0
  71. package/src/Thread.test.ts +304 -0
  72. package/src/Thread.ts +286 -0
  73. package/src/Transport.ts +182 -0
  74. package/src/UiContract.test.ts +342 -0
  75. package/src/UiContract.ts +402 -0
  76. package/src/index.ts +11 -0
@@ -0,0 +1,402 @@
1
+ /**
2
+ * A typed contract between server and client, built from Schemas only, so
3
+ * both sides import the same value (the pattern of Effect's `HttpApi`). The
4
+ * wire stays JSON; the client decodes each envelope once on arrival, and from
5
+ * then on the state holds decoded values: a tool block narrowed by `name`
6
+ * has its tool's input and output types, a `Date` is a `Date`.
7
+ *
8
+ * const ChatUi = UiContract.make({ tools: [GetWeather], live: { metrics: Metrics } })
9
+ *
10
+ * // server: typed payload events (tool outputs are encoded by the Toolkit)
11
+ * yield* ChatUi.live("metrics", { tokps: 42 })
12
+ *
13
+ * // client: the state is typed by the contract
14
+ * const chat = Atoms.thread({ contract: ChatUi, transport, busy: "block" })
15
+ *
16
+ * The types list the contract's names only, so narrowing by name is exact. A
17
+ * name the contract doesn't list (a newer server rolling out a tool) still
18
+ * arrives, as JSON, but the types don't mention it: a `switch (b.name)` skips
19
+ * it. A listed name whose payload doesn't decode fails the envelope: that is
20
+ * a contract violation, not data to show.
21
+ *
22
+ * A tool is a core `Tool` or a shared spec (`as const`, schemas only). Its
23
+ * result is `Success { value } | Failure { error }`: the error is the tool's
24
+ * `failureSchema` (app closings like `ToolDecision.Denied` included) or core's
25
+ * `ToolOutput.CoreError`.
26
+ * Without `outputSchema`, values are a `Content.Value` (media as classes).
27
+ */
28
+ import * as Content from "@effect-uai/core/Content"
29
+ import type * as Items from "@effect-uai/core/Items"
30
+ import type * as Tool from "@effect-uai/core/Tool"
31
+ import type { ToolInputSchema } from "@effect-uai/core/Tool"
32
+ import type * as ToolEvent from "@effect-uai/core/ToolEvent"
33
+ import * as ToolOutput from "@effect-uai/core/ToolOutput"
34
+ import { Effect, HashMap, Match, Option, Result, Schema } from "effect"
35
+ import * as Event from "./RunEvent.js"
36
+ import type { Envelope, Json, Payloads as AnyPayloads, RunEvent, ToolPayload } from "./RunEvent.js"
37
+ import * as RunView from "./RunView.js"
38
+ import * as Thread from "./Thread.js"
39
+
40
+ type AnyCodec = Schema.Codec<any, any, never, never>
41
+ // Tool values reach history and the UI through the same schema, so it must encode to JSON.
42
+ type JsonCodec = Schema.Codec<any, Schema.Json, never, never>
43
+
44
+ /** A tool as the contract sees it: a core `Tool` or a shared spec (`as const`). */
45
+ export type ToolSpec = {
46
+ readonly name: string
47
+ readonly inputSchema: AnyCodec | ToolInputSchema<any>
48
+ readonly outputSchema?: JsonCodec
49
+ readonly failureSchema?: JsonCodec
50
+ }
51
+
52
+ export type Spec = {
53
+ readonly tools?: ReadonlyArray<ToolSpec>
54
+ readonly objects?: Readonly<Record<string, AnyCodec>>
55
+ readonly data?: Readonly<Record<string, AnyCodec>>
56
+ readonly live?: Readonly<Record<string, AnyCodec>>
57
+ readonly metadata?: AnyCodec
58
+ }
59
+
60
+ type InputOf<S> = S extends Schema.Top ? S["Type"] : S extends ToolInputSchema<infer A> ? A : Json
61
+ // A core `Tool` carries its types; a spec has them in its schemas. Without
62
+ // `outputSchema` a tool returns a `Content.Value`, and that is what arrives.
63
+ type CoreTool = { readonly _tag: "LocalTool" | "DeclaredTool" }
64
+ type OutputOf<T> = T extends CoreTool
65
+ ? Tool.ToolOutput<T>
66
+ : T extends { readonly outputSchema: infer O extends Schema.Top }
67
+ ? O["Type"]
68
+ : Content.Value
69
+ type FailureOf<T> = T extends CoreTool
70
+ ? Tool.ToolFailure<T>
71
+ : T extends { readonly failureSchema: infer F extends Schema.Top }
72
+ ? F["Type"]
73
+ : never
74
+ type ToolOf<T> = T extends ToolSpec
75
+ ? {
76
+ readonly name: T["name"]
77
+ readonly input: InputOf<T["inputSchema"]>
78
+ readonly output: OutputOf<T>
79
+ readonly failure: FailureOf<T> | ToolOutput.CoreError
80
+ }
81
+ : never
82
+ type ElementOf<T> = T extends ReadonlyArray<infer E> ? E : never
83
+ type NamedOf<M> = {
84
+ readonly [K in keyof M & string]: {
85
+ readonly name: K
86
+ readonly value: M[K] extends Schema.Top ? M[K]["Type"] : never
87
+ }
88
+ }[keyof M & string]
89
+ /** The payload types a spec describes: its listed names only. */
90
+ export type PayloadsOf<S extends Spec> = {
91
+ readonly tool: ToolOf<ElementOf<S["tools"]>>
92
+ readonly object: NamedOf<S["objects"]>
93
+ readonly data: NamedOf<S["data"]>
94
+ readonly live: NamedOf<S["live"]>
95
+ readonly metadata: S["metadata"] extends Schema.Top ? S["metadata"]["Type"] : Json
96
+ }
97
+
98
+ type NameOf<T> = T extends { readonly name: infer N } ? N : never
99
+ type ValueOf<T, N> =
100
+ Extract<T, { readonly name: N }> extends { readonly value: infer V } ? V : never
101
+ type ToolField<T, N, K extends "output" | "failure"> =
102
+ Extract<T, { readonly name: N }> extends infer X extends ToolPayload ? X[K] : never
103
+
104
+ /** The call an app closes itself: its id and the tool's name. */
105
+ export type Call<N extends string = string> = { readonly call_id: string; readonly name: N }
106
+
107
+ /**
108
+ * A contract's payload types, for the generic folds:
109
+ * `Thread.presetFor<UiContract.Payloads<typeof ChatUi>>()`.
110
+ */
111
+ export type Payloads<C> = C extends UiContract<infer P> ? P : never
112
+
113
+ export interface UiContract<P extends AnyPayloads> {
114
+ /** Decode a wire envelope (as `Client` reads it) into a typed one. */
115
+ decode(envelope: Envelope): Effect.Effect<Envelope<P>, Schema.SchemaError>
116
+ /** Typed envelope ⇄ wire envelope. */
117
+ readonly Envelope: Schema.Codec<Envelope<P>, Envelope>
118
+ /** A typed run view ⇄ the untyped one, for persisting or a framework's model. */
119
+ readonly RunView: Schema.Codec<RunView.RunView<P>, RunView.RunView>
120
+ /** A typed `Thread` ⇄ the untyped one. */
121
+ readonly Thread: Schema.Codec<Thread.Thread<P>, Thread.Thread>
122
+ /** Server: a `data` event with its value encoded for the wire. */
123
+ data<N extends NameOf<P["data"]>>(
124
+ name: N,
125
+ value: ValueOf<P["data"], N>,
126
+ options?: { readonly id?: string },
127
+ ): Effect.Effect<RunEvent, Schema.SchemaError>
128
+ /** Server: a `live` event with its value encoded for the wire. */
129
+ live<N extends NameOf<P["live"]>>(
130
+ name: N,
131
+ value: ValueOf<P["live"], N>,
132
+ ): Effect.Effect<RunEvent, Schema.SchemaError>
133
+ /** Server: run metadata encoded for `run-completed`. */
134
+ metadata(value: P["metadata"]): Effect.Effect<Json, Schema.SchemaError>
135
+ /** Server: a toolkit event (`Toolkit.run`) encoded with the tool's schemas. */
136
+ toolEvent(event: ToolEvent.ToolEvent): Effect.Effect<RunEvent, Schema.SchemaError>
137
+ /** Server: a history item (already encoded) as the `tool-result` of tool `name`. */
138
+ toolOutput(item: Items.ToolCallOutput, name: string): Effect.Effect<RunEvent, Schema.SchemaError>
139
+ /** Server: close a call with a value of its tool's output (e.g. supplied by the user). */
140
+ success<N extends NameOf<P["tool"]>>(
141
+ call: Call<N>,
142
+ value: ToolField<P["tool"], N, "output">,
143
+ ): Effect.Effect<RunEvent, Schema.SchemaError>
144
+ /** Server: close a call with an error (e.g. the app's `Denied`). */
145
+ failure<N extends NameOf<P["tool"]>>(
146
+ call: Call<N>,
147
+ error: ToolField<P["tool"], N, "failure">,
148
+ ): Effect.Effect<RunEvent, Schema.SchemaError>
149
+ }
150
+
151
+ // ---------------------------------------------------------------------------
152
+ // Runtime: one schema member per listed name, plus a fallback for the rest
153
+ // ---------------------------------------------------------------------------
154
+
155
+ const json = <S extends Schema.Top>(schema: S) => Schema.toCodecJson(schema)
156
+ const listed = (name: string) => Schema.Literal(name)
157
+ const unlisted = (names: ReadonlyArray<string>) =>
158
+ Schema.String.check(Schema.makeFilter((name: string) => !names.includes(name)))
159
+
160
+ /** A payload codec: decodes from JSON without services. */
161
+ type PayloadCodec = Schema.Codec<unknown, unknown, never, never>
162
+
163
+ // A core `Tool` built from an Effect Schema keeps it (`fromEffectSchema`), so it decodes too.
164
+ const isCodec = (schema: ToolSpec["inputSchema"]): schema is AnyCodec => Schema.isSchema(schema)
165
+
166
+ // Effect Schemas decode the model's JSON; other Standard Schemas validate it as is.
167
+ const inputCodec = (tool: ToolSpec): PayloadCodec => {
168
+ const schema = tool.inputSchema
169
+ if (isCodec(schema)) return json(schema)
170
+ return Schema.declare((u: unknown): u is Json => {
171
+ const result = schema["~standard"].validate(u)
172
+ return !(result instanceof Promise) && result.issues === undefined
173
+ })
174
+ }
175
+ // "Anything": a tool typed `Content.Value`, or a name the contract doesn't list.
176
+ const isAnything = (tool: ToolSpec | undefined): boolean =>
177
+ tool?.outputSchema === undefined || tool.outputSchema === Content.Value
178
+
179
+ // The tool's own schemas, as `Toolkit.output` uses them: they encode to JSON, so
180
+ // the UI and history agree by construction (no `toCodecJson` on top). "Anything"
181
+ // travels as typed parts, so JSON can't be mistaken for media.
182
+ const outputCodec = (tool: ToolSpec | undefined): PayloadCodec =>
183
+ isAnything(tool) ? ToolOutput.ValueParts : (tool?.outputSchema ?? ToolOutput.ValueParts)
184
+
185
+ const encodeParts = Schema.encodeEffect(Schema.Array(ToolOutput.PartJson))
186
+
187
+ const failureCodec = (tool: ToolSpec | undefined): PayloadCodec =>
188
+ tool?.failureSchema === undefined
189
+ ? ToolOutput.CoreError
190
+ : Schema.Union([tool.failureSchema, ToolOutput.CoreError])
191
+
192
+ const entries = (record: Readonly<Record<string, AnyCodec>> | undefined) =>
193
+ Object.entries(record ?? {})
194
+
195
+ /** Encoded loosely; `make`'s signature carries the types. */
196
+ type Loose = {
197
+ decode(envelope: Envelope): Effect.Effect<unknown, Schema.SchemaError>
198
+ readonly Envelope: Schema.Top
199
+ readonly RunView: Schema.Top
200
+ readonly Thread: Schema.Top
201
+ data(
202
+ name: string,
203
+ value: unknown,
204
+ options?: { readonly id?: string },
205
+ ): Effect.Effect<RunEvent, Schema.SchemaError>
206
+ live(name: string, value: unknown): Effect.Effect<RunEvent, Schema.SchemaError>
207
+ metadata(value: unknown): Effect.Effect<Json, Schema.SchemaError>
208
+ toolEvent(event: ToolEvent.ToolEvent): Effect.Effect<RunEvent, Schema.SchemaError>
209
+ toolOutput(item: Items.ToolCallOutput, name: string): Effect.Effect<RunEvent, Schema.SchemaError>
210
+ success(call: Call, value: unknown): Effect.Effect<RunEvent, Schema.SchemaError>
211
+ failure(call: Call, error: unknown): Effect.Effect<RunEvent, Schema.SchemaError>
212
+ }
213
+
214
+ const toJson = Schema.decodeUnknownEffect(Schema.Json)
215
+ const toOutcome = Schema.decodeUnknownEffect(Event.Outcome)
216
+
217
+ /**
218
+ * Build a contract. Tool names must be literal types: pass core `Tool`s or
219
+ * specs declared `as const`.
220
+ */
221
+ export function make<const S extends Spec>(spec: S): UiContract<PayloadsOf<S>>
222
+ export function make(spec: Spec): Loose {
223
+ const tools = spec.tools ?? []
224
+ const objects = entries(spec.objects)
225
+ const data = entries(spec.data)
226
+ const live = entries(spec.live)
227
+ const toolNames = tools.map((t) => t.name)
228
+ const metadata = spec.metadata === undefined ? Schema.Json : json(spec.metadata)
229
+ const resultCodec = (tool: ToolSpec | undefined) =>
230
+ Event.outcome(outputCodec(tool), failureCodec(tool))
231
+ // Built once per tool; an unlisted name falls back to the tool-less codecs.
232
+ const codecsOf = (tool: ToolSpec | undefined) => ({
233
+ output: outputCodec(tool),
234
+ result: resultCodec(tool),
235
+ anything: isAnything(tool),
236
+ })
237
+ const unlistedCodecs = codecsOf(undefined)
238
+ const codecsByName = HashMap.fromIterable(tools.map((t) => [t.name, codecsOf(t)] as const))
239
+ const codecs = (name: string) =>
240
+ Option.getOrElse(HashMap.get(codecsByName, name), () => unlistedCodecs)
241
+
242
+ const interrupt = Schema.Union([
243
+ ...tools.map((t) =>
244
+ Schema.Struct({ ...Event.Interrupt.fields, name: listed(t.name), input: inputCodec(t) }),
245
+ ),
246
+ Schema.Struct({ ...Event.Interrupt.fields, name: unlisted(toolNames) }),
247
+ ])
248
+
249
+ const toolInput = (event: typeof Event.ToolCallReady | typeof Event.ToolApprovalRequested) =>
250
+ Schema.Union([
251
+ ...tools.map((t) =>
252
+ Schema.Struct({ ...event.fields, name: listed(t.name), input: inputCodec(t) }),
253
+ ),
254
+ Schema.Struct({ ...event.fields, name: unlisted(toolNames) }),
255
+ ])
256
+
257
+ const named = (
258
+ event: typeof Event.Data | typeof Event.Live,
259
+ members: ReadonlyArray<readonly [string, AnyCodec]>,
260
+ ) =>
261
+ Schema.Union([
262
+ ...members.map(([name, schema]) =>
263
+ Schema.Struct({ ...event.fields, name: listed(name), value: json(schema) }),
264
+ ),
265
+ Schema.Struct({ ...event.fields, name: unlisted(members.map(([name]) => name)) }),
266
+ ])
267
+
268
+ const event = Schema.Union([
269
+ ...Event.plainEvents.map((e) => Schema.toType(e)),
270
+ Schema.Struct({ ...Event.RunCompleted.fields, metadata: Schema.optional(metadata) }),
271
+ Schema.Struct({ ...Event.RunInterrupted.fields, interrupts: Schema.Array(interrupt) }),
272
+ Schema.Union([
273
+ ...objects.map(([name, schema]) =>
274
+ Schema.Struct({ ...Event.ObjectDone.fields, name: listed(name), value: json(schema) }),
275
+ ),
276
+ Schema.Struct({
277
+ ...Event.ObjectDone.fields,
278
+ name: Schema.optional(unlisted(objects.map(([name]) => name))),
279
+ }),
280
+ ]),
281
+ toolInput(Event.ToolCallReady),
282
+ toolInput(Event.ToolApprovalRequested),
283
+ Schema.Union([
284
+ ...tools.map((t) =>
285
+ Schema.Struct({ ...Event.ToolPartial.fields, name: listed(t.name), value: outputCodec(t) }),
286
+ ),
287
+ Schema.Struct({ ...Event.ToolPartial.fields, name: unlisted(toolNames) }),
288
+ ]),
289
+ Schema.Union([
290
+ ...tools.map((t) =>
291
+ Schema.Struct({ ...Event.ToolResult.fields, name: listed(t.name), result: resultCodec(t) }),
292
+ ),
293
+ Schema.Struct({ ...Event.ToolResult.fields, name: unlisted(toolNames) }),
294
+ ]),
295
+ named(Event.Data, data),
296
+ named(Event.Live, live),
297
+ ])
298
+ const envelope = Schema.Struct({ ...Event.envelopeFields, event })
299
+
300
+ const view = RunView.schema({
301
+ block: Schema.Union([
302
+ ...RunView.contentBlocks,
303
+ ...tools.map((t) =>
304
+ RunView.toolBlock(listed(t.name), inputCodec(t), outputCodec(t), failureCodec(t)),
305
+ ),
306
+ RunView.toolBlock(unlisted(toolNames), Schema.Json, Schema.Json, Schema.Json),
307
+ ...objects.map(([name, schema]) => RunView.objectBlock(listed(name), json(schema))),
308
+ RunView.objectBlock(unlisted(objects.map(([name]) => name)), Schema.Json),
309
+ RunView.unnamedObject,
310
+ ...data.map(([name, schema]) => RunView.dataBlock(listed(name), json(schema))),
311
+ RunView.dataBlock(unlisted(data.map(([name]) => name)), Schema.Json),
312
+ ]),
313
+ interrupt,
314
+ // Unlisted live values are transient and untyped: the schema keeps listed ones only.
315
+ live: Schema.Struct(
316
+ Object.fromEntries(live.map(([name, schema]) => [name, Schema.optionalKey(json(schema))])),
317
+ ),
318
+ metadata,
319
+ })
320
+
321
+ const codecOf = (members: ReadonlyArray<readonly [string, AnyCodec]>, name: string) =>
322
+ members.find(([n]) => n === name)?.[1]
323
+ const encode = (schema: AnyCodec | undefined, value: unknown) =>
324
+ Schema.encodeUnknownEffect(schema === undefined ? Schema.Json : json(schema))(value).pipe(
325
+ Effect.flatMap(toJson),
326
+ )
327
+ const toolResult = (call: Call, outcome: Event.Outcome<unknown, unknown>) =>
328
+ Schema.encodeUnknownEffect(codecs(call.name).result)(outcome).pipe(
329
+ Effect.flatMap(toOutcome),
330
+ Effect.map((result): RunEvent => ({
331
+ type: "tool-result",
332
+ callId: call.call_id,
333
+ name: call.name,
334
+ result,
335
+ })),
336
+ )
337
+
338
+ return {
339
+ decode: Schema.decodeUnknownEffect(envelope),
340
+ Envelope: envelope,
341
+ RunView: view,
342
+ Thread: Thread.State(Thread.UserContent, view, view),
343
+ data: (name, value, options) =>
344
+ encode(codecOf(data, name), value).pipe(
345
+ Effect.map((encoded): RunEvent =>
346
+ options?.id === undefined
347
+ ? { type: "data", name, value: encoded }
348
+ : { type: "data", name, value: encoded, id: options.id },
349
+ ),
350
+ ),
351
+ live: (name, value) =>
352
+ encode(codecOf(live, name), value).pipe(
353
+ Effect.map((encoded): RunEvent => ({ type: "live", name, value: encoded })),
354
+ ),
355
+ metadata: (value) => encode(spec.metadata, value),
356
+ toolEvent: (event) =>
357
+ Match.value(event).pipe(
358
+ Match.tagsExhaustive({
359
+ Partial: (e) =>
360
+ Schema.encodeUnknownEffect(codecs(e.tool).output)(e.value).pipe(
361
+ Effect.flatMap(toJson),
362
+ Effect.map((value): RunEvent => ({
363
+ type: "tool-partial",
364
+ callId: e.call_id,
365
+ name: e.tool,
366
+ value,
367
+ })),
368
+ ),
369
+ Output: (e) =>
370
+ toolResult(
371
+ { call_id: e.call_id, name: e.tool },
372
+ Result.match(e.result, {
373
+ onSuccess: (value): Event.Outcome<unknown, unknown> => ({ _tag: "Success", value }),
374
+ onFailure: (error): Event.Outcome<unknown, unknown> => ({ _tag: "Failure", error }),
375
+ }),
376
+ ),
377
+ }),
378
+ ),
379
+ // The item holds the encoded value: "anything" goes as its parts, the rest as the JSON they encode.
380
+ toolOutput: (item, name) =>
381
+ Match.value(item).pipe(
382
+ Match.when({ isError: true }, (o) =>
383
+ Effect.map(ToolOutput.encoded(o.output), (error) => ({ _tag: "Failure", error })),
384
+ ),
385
+ Match.orElse((o) =>
386
+ Effect.map(
387
+ codecs(name).anything ? encodeParts(o.output) : ToolOutput.encoded(o.output),
388
+ (value) => ({ _tag: "Success", value }),
389
+ ),
390
+ ),
391
+ Effect.flatMap(toOutcome),
392
+ Effect.map((result): RunEvent => ({
393
+ type: "tool-result",
394
+ callId: item.call_id,
395
+ name,
396
+ result,
397
+ })),
398
+ ),
399
+ success: (call, value) => toolResult(call, { _tag: "Success", value }),
400
+ failure: (call, error) => toolResult(call, { _tag: "Failure", error }),
401
+ }
402
+ }
package/src/index.ts ADDED
@@ -0,0 +1,11 @@
1
+ export * as Atoms from "./Atoms.js"
2
+ export * as Client from "./Client.js"
3
+ export * as Input from "./Input.js"
4
+ export * as Projection from "./Projection.js"
5
+ export * as Run from "./Run.js"
6
+ export * as RunEvent from "./RunEvent.js"
7
+ export * as RunView from "./RunView.js"
8
+ export * as SseEncoding from "./SseEncoding.js"
9
+ export * as Thread from "./Thread.js"
10
+ export * as Transport from "./Transport.js"
11
+ export * as UiContract from "./UiContract.js"