@effect-uai/ui 0.0.0-stage → 0.19.0-dev-20261109183959

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 +113 -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,225 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
+ import { InputId, StreamId } from "./RunEvent.mjs";
3
+ import { InputJson, UserMessage } from "./Input.mjs";
4
+ import { RunView, applyToolUpdate, initial as initial$1, isTerminal, isToolUpdate, step as step$1 } from "./RunView.mjs";
5
+ import { Array, Option, Schema } from "effect";
6
+ //#region src/Thread.ts
7
+ /**
8
+ * Optional chat model on top of `RunView`: a message list, the run that is
9
+ * still streaming, and inputs waiting for their run. `make` builds the fold
10
+ * for any entry types; the module's own exports are the default preset
11
+ * (user entries hold the input's messages, assistant entries the `RunView`).
12
+ *
13
+ * A fold's fields (`initial`, `step`, `submitted`, `ended`) are what
14
+ * `Atoms.session` and `Atoms.make` take, so `{ ...Thread.make(config) }`
15
+ * plugs in directly.
16
+ *
17
+ * A settled assistant entry with a call awaiting approval still changes when
18
+ * that call is answered (`updateTool`): save entries when they change, not
19
+ * only when they settle.
20
+ */
21
+ var Thread_exports = /* @__PURE__ */ __exportAll({
22
+ State: () => State,
23
+ Thread: () => Thread,
24
+ UserContent: () => UserContent,
25
+ ended: () => ended,
26
+ hasInput: () => hasInput,
27
+ initial: () => initial,
28
+ make: () => make,
29
+ preset: () => preset,
30
+ presetFor: () => presetFor,
31
+ step: () => step,
32
+ submitted: () => submitted,
33
+ truncateAfter: () => truncateAfter
34
+ });
35
+ /**
36
+ * The state as a Schema, for persisting or sending it. `view` is the active
37
+ * run's schema: `RunView.RunView` (default) or a contract's `RunView`.
38
+ */
39
+ const State = (user, assistant, view) => {
40
+ const UserEntry = Schema.Struct({
41
+ role: Schema.Literal("user"),
42
+ id: InputId,
43
+ value: user
44
+ });
45
+ const AssistantEntry = Schema.Struct({
46
+ role: Schema.Literal("assistant"),
47
+ id: Schema.String,
48
+ value: assistant
49
+ });
50
+ return Schema.Struct({
51
+ messages: Schema.Array(Schema.Union([UserEntry, AssistantEntry])),
52
+ active: Schema.NullOr(view),
53
+ pending: Schema.Array(UserEntry),
54
+ streamId: Schema.NullOr(StreamId),
55
+ lastSeq: Schema.Number
56
+ });
57
+ };
58
+ /** Whether the state already shows the input `id` (settled or pending). */
59
+ const hasInput = (state, id) => state.messages.some((m) => m.role === "user" && m.id === id) || state.pending.some((m) => m.id === id);
60
+ /** Drop everything after the entry `id` (regenerate, edit). */
61
+ const truncateAfter = (state, id) => {
62
+ const index = state.messages.findIndex((m) => m.id === id);
63
+ return index === -1 ? state : {
64
+ ...state,
65
+ messages: state.messages.slice(0, index + 1),
66
+ active: null
67
+ };
68
+ };
69
+ function make(config) {
70
+ const initial = {
71
+ messages: [],
72
+ active: null,
73
+ pending: [],
74
+ streamId: null,
75
+ lastSeq: 0
76
+ };
77
+ const settled = (run, fallbackSeq) => ({
78
+ role: "assistant",
79
+ id: run.runId ?? `seq-${fallbackSeq}`,
80
+ value: config.settle(run)
81
+ });
82
+ const onStream = (state, envelope) => envelope.streamId === state.streamId ? state : {
83
+ ...state,
84
+ streamId: envelope.streamId,
85
+ lastSeq: 0,
86
+ active: state.active === null ? null : {
87
+ ...state.active,
88
+ lastSeq: 0
89
+ }
90
+ };
91
+ const consume = (state, envelope) => {
92
+ const event = envelope.event;
93
+ if (event.type !== "run-started" || state.pending.length === 0) return state;
94
+ const taken = (m) => event.inputs.includes(m.id);
95
+ return {
96
+ ...state,
97
+ messages: [...state.messages, ...state.pending.filter(taken)],
98
+ pending: state.pending.filter((m) => !taken(m))
99
+ };
100
+ };
101
+ const accepted = (state, value) => Option.fromNullishOr(config.decodeInput).pipe(Option.flatMap((decode) => decode(value)), Option.filter((input) => !hasInput(state, input.id)), Option.flatMap((input) => config.toUser(input).pipe(Option.map((u) => ({
102
+ role: "user",
103
+ id: input.id,
104
+ value: u
105
+ })))), Option.match({
106
+ onNone: () => state,
107
+ onSome: (entry) => ({
108
+ ...state,
109
+ pending: [...state.pending, entry]
110
+ })
111
+ }));
112
+ const submitted = (state, input) => {
113
+ const idle = state.active === null;
114
+ const run = {
115
+ ...initial$1,
116
+ status: "submitted",
117
+ lastSeq: state.lastSeq
118
+ };
119
+ return Option.match(config.toUser(input), {
120
+ onNone: () => idle ? {
121
+ ...state,
122
+ active: run
123
+ } : state,
124
+ onSome: (u) => {
125
+ const entry = {
126
+ role: "user",
127
+ id: input.id,
128
+ value: u
129
+ };
130
+ return idle ? {
131
+ ...state,
132
+ messages: [...state.messages, entry],
133
+ active: run
134
+ } : {
135
+ ...state,
136
+ pending: [...state.pending, entry]
137
+ };
138
+ }
139
+ });
140
+ };
141
+ const updateSettled = (messages, event) => Option.fromNullishOr(config.updateTool).pipe(Option.flatMap((update) => Array.findLast(messages, (m, index) => m.role === "assistant" ? update(m.value, event).pipe(Option.map((value) => ({
142
+ index,
143
+ entry: {
144
+ ...m,
145
+ value
146
+ }
147
+ }))) : Option.none())), Option.map(({ index, entry }) => messages.map((m, i) => i === index ? entry : m)));
148
+ const step = (current, envelope) => {
149
+ const fresh = onStream(current, envelope);
150
+ if (envelope.seq <= fresh.lastSeq) return current;
151
+ const state = consume(fresh, envelope);
152
+ const event = envelope.event;
153
+ if (event.type === "input-accepted") return {
154
+ ...accepted(state, event.input),
155
+ lastSeq: envelope.seq
156
+ };
157
+ if (envelope.runId === void 0 || state.active === null && event.type !== "run-started") return {
158
+ ...state,
159
+ lastSeq: envelope.seq
160
+ };
161
+ if (isToolUpdate(event)) {
162
+ const updated = updateSettled(state.messages, event);
163
+ if (Option.isSome(updated)) return {
164
+ ...state,
165
+ messages: updated.value,
166
+ lastSeq: envelope.seq
167
+ };
168
+ }
169
+ const active = state.active ?? {
170
+ ...initial$1,
171
+ lastSeq: state.lastSeq
172
+ };
173
+ const run = step$1(active, envelope);
174
+ if (!isTerminal(run)) return {
175
+ ...state,
176
+ active: run,
177
+ lastSeq: envelope.seq
178
+ };
179
+ return {
180
+ ...state,
181
+ messages: [...state.messages, settled(run, envelope.seq)],
182
+ active: null,
183
+ lastSeq: envelope.seq
184
+ };
185
+ };
186
+ const ended = (state, reason) => {
187
+ const run = state.active;
188
+ if (run === null) return state;
189
+ const final = reason === "stopped" ? {
190
+ ...run,
191
+ status: "stopped"
192
+ } : {
193
+ ...run,
194
+ status: "failed",
195
+ error: { reason: "disconnected" }
196
+ };
197
+ return {
198
+ ...state,
199
+ messages: [...state.messages, settled(final, state.lastSeq)],
200
+ active: null
201
+ };
202
+ };
203
+ return {
204
+ initial,
205
+ step,
206
+ submitted,
207
+ ended
208
+ };
209
+ }
210
+ /** A user entry's value in the preset: the messages the input carried. */
211
+ const UserContent = Schema.Struct({ messages: Schema.Array(UserMessage) });
212
+ const Thread = State(UserContent, RunView, RunView);
213
+ /** The preset's fold for a contract's `P` (`UiContract` and `Atoms` use it). */
214
+ const presetFor = () => make({
215
+ toUser: (input) => Option.fromNullishOr(input.messages).pipe(Option.map((messages) => ({ messages }))),
216
+ settle: (run) => run,
217
+ decodeInput: Schema.decodeUnknownOption(InputJson),
218
+ updateTool: applyToolUpdate
219
+ });
220
+ const preset = presetFor();
221
+ const { initial, step, submitted, ended } = preset;
222
+ //#endregion
223
+ export { State, Thread, UserContent, ended, hasInput, initial, make, preset, presetFor, step, submitted, Thread_exports as t, truncateAfter };
224
+
225
+ //# sourceMappingURL=Thread.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Thread.mjs","names":["RunView.initial","Arr","RunView.isToolUpdate","RunView.step","RunView.isTerminal","Input.UserMessage","RunView.RunView","Input.InputJson","RunView.applyToolUpdate"],"sources":["../src/Thread.ts"],"sourcesContent":["/**\n * Optional chat model on top of `RunView`: a message list, the run that is\n * still streaming, and inputs waiting for their run. `make` builds the fold\n * for any entry types; the module's own exports are the default preset\n * (user entries hold the input's messages, assistant entries the `RunView`).\n *\n * A fold's fields (`initial`, `step`, `submitted`, `ended`) are what\n * `Atoms.session` and `Atoms.make` take, so `{ ...Thread.make(config) }`\n * plugs in directly.\n *\n * A settled assistant entry with a call awaiting approval still changes when\n * that call is answered (`updateTool`): save entries when they change, not\n * only when they settle.\n */\nimport { Array as Arr, Option, Schema } from \"effect\"\nimport * as Input from \"./Input.js\"\nimport { type Envelope, InputId, type Payloads, StreamId, type Untyped } from \"./RunEvent.js\"\nimport * as RunView from \"./RunView.js\"\n\n// ---------------------------------------------------------------------------\n// Generic fold\n// ---------------------------------------------------------------------------\n\nexport type UserEntry<U> = { readonly role: \"user\"; readonly id: InputId; readonly value: U }\nexport type AssistantEntry<A> = {\n readonly role: \"assistant\"\n readonly id: string\n readonly value: A\n}\nexport type Entry<U, A> = UserEntry<U> | AssistantEntry<A>\n\n/** `P` types the active run's payloads (see `UiContract`). */\nexport type State<U, A, P extends Payloads = Untyped> = {\n readonly messages: ReadonlyArray<Entry<U, A>>\n /** The run still streaming, if any. */\n readonly active: RunView.RunView<P> | null\n /**\n * Inputs waiting for their own run (sent during a run, or echoed by a\n * server that queues). They join `messages` when a `run-started` lists them.\n */\n readonly pending: ReadonlyArray<UserEntry<U>>\n /** The stream `lastSeq` counts in; a frame from another stream starts a new count. */\n readonly streamId: StreamId | null\n /** Highest applied `seq` across runs, so a settled run's frames stay deduplicated. */\n readonly lastSeq: number\n}\n\n/**\n * The state as a Schema, for persisting or sending it. `view` is the active\n * run's schema: `RunView.RunView` (default) or a contract's `RunView`.\n */\nexport const State = <U, A, V>(\n user: Schema.Codec<U, unknown, never, never>,\n assistant: Schema.Codec<A, unknown, never, never>,\n view: Schema.Codec<V, unknown, never, never>,\n) => {\n const UserEntry = Schema.Struct({ role: Schema.Literal(\"user\"), id: InputId, value: user })\n const AssistantEntry = Schema.Struct({\n role: Schema.Literal(\"assistant\"),\n id: Schema.String,\n value: assistant,\n })\n return Schema.Struct({\n messages: Schema.Array(Schema.Union([UserEntry, AssistantEntry])),\n active: Schema.NullOr(view),\n pending: Schema.Array(UserEntry),\n streamId: Schema.NullOr(StreamId),\n lastSeq: Schema.Number,\n })\n}\n\nexport type Config<I, U, A, P extends Payloads = Untyped> = {\n /** The user entry for an input; `None` for inputs without one (approvals). */\n readonly toUser: (input: I) => Option.Option<U>\n /** The assistant entry a finished run settles into. */\n readonly settle: (run: RunView.RunView<P>) => A\n /** Decodes an `input-accepted` echo into an input; without it, echoes are ignored. */\n readonly decodeInput?: (value: unknown) => Option.Option<I>\n /**\n * Applies a tool event to a settled entry that holds the call (`None` if it\n * doesn't). An approved call's result arrives in the next run; with this,\n * it updates the card it was asked from. Without it, it lands in the new run.\n */\n readonly updateTool?: (entry: A, event: RunView.ToolUpdate<P>) => Option.Option<A>\n}\n\nexport type Fold<I, U, A, P extends Payloads = Untyped> = {\n readonly initial: State<U, A, P>\n readonly step: (state: State<U, A, P>, envelope: Envelope<P>) => State<U, A, P>\n /** Optimistic: show the input before the server answers. */\n readonly submitted: (state: State<U, A, P>, input: I) => State<U, A, P>\n /** Settle the active run when its stream ends without a terminal. */\n readonly ended: (state: State<U, A, P>, reason: \"stopped\" | \"disconnected\") => State<U, A, P>\n}\n\n/** Whether the state already shows the input `id` (settled or pending). */\nexport const hasInput = <U, A, P extends Payloads>(state: State<U, A, P>, id: InputId): boolean =>\n state.messages.some((m) => m.role === \"user\" && m.id === id) ||\n state.pending.some((m) => m.id === id)\n\n/** Drop everything after the entry `id` (regenerate, edit). */\nexport const truncateAfter = <U, A, P extends Payloads>(\n state: State<U, A, P>,\n id: string,\n): State<U, A, P> => {\n const index = state.messages.findIndex((m) => m.id === id)\n return index === -1\n ? state\n : { ...state, messages: state.messages.slice(0, index + 1), active: null }\n}\n\n// Written once over `Payloads`; the exported signature carries the caller's `P`.\nexport function make<I extends { readonly id: InputId }, U, A, P extends Payloads = Untyped>(\n config: Config<I, U, A, P>,\n): Fold<I, U, A, P>\nexport function make<I extends { readonly id: InputId }, U, A>(\n config: Config<I, U, A, Payloads>,\n): Fold<I, U, A, Payloads> {\n type S = State<U, A, Payloads>\n\n const initial: S = { messages: [], active: null, pending: [], streamId: null, lastSeq: 0 }\n\n const settled = (run: RunView.RunView<Payloads>, fallbackSeq: number): AssistantEntry<A> => ({\n role: \"assistant\",\n id: run.runId ?? `seq-${fallbackSeq}`,\n value: config.settle(run),\n })\n\n // `seq` is per stream: a run on a new stream (one stream per run) counts from 1 again.\n const onStream = (state: S, envelope: Envelope<Payloads>): S =>\n envelope.streamId === state.streamId\n ? state\n : {\n ...state,\n streamId: envelope.streamId,\n lastSeq: 0,\n active: state.active === null ? null : { ...state.active, lastSeq: 0 },\n }\n\n // A starting run takes its inputs out of `pending`, so they sit above its answer.\n const consume = (state: S, envelope: Envelope<Payloads>): S => {\n const event = envelope.event\n if (event.type !== \"run-started\" || state.pending.length === 0) return state\n const taken = (m: UserEntry<U>) => event.inputs.includes(m.id)\n return {\n ...state,\n messages: [...state.messages, ...state.pending.filter(taken)],\n pending: state.pending.filter((m) => !taken(m)),\n }\n }\n\n // The server's echo of an input it accepted: shown as pending unless already shown.\n const accepted = (state: S, value: unknown): S =>\n Option.fromNullishOr(config.decodeInput).pipe(\n Option.flatMap((decode) => decode(value)),\n Option.filter((input) => !hasInput(state, input.id)),\n Option.flatMap((input) =>\n config\n .toUser(input)\n .pipe(Option.map((u): UserEntry<U> => ({ role: \"user\", id: input.id, value: u }))),\n ),\n Option.match({\n onNone: () => state,\n onSome: (entry) => ({ ...state, pending: [...state.pending, entry] }),\n }),\n )\n\n const submitted = (state: S, input: I): S => {\n const idle = state.active === null\n const run: RunView.RunView<Payloads> = {\n ...RunView.initial,\n status: \"submitted\",\n lastSeq: state.lastSeq,\n }\n return Option.match(config.toUser(input), {\n onNone: () => (idle ? { ...state, active: run } : state),\n onSome: (u): S => {\n const entry: UserEntry<U> = { role: \"user\", id: input.id, value: u }\n return idle\n ? { ...state, messages: [...state.messages, entry], active: run }\n : { ...state, pending: [...state.pending, entry] }\n },\n })\n }\n\n // The latest settled entry that holds the call, updated by `config.updateTool`.\n const updateSettled = (\n messages: ReadonlyArray<Entry<U, A>>,\n event: RunView.ToolUpdate<Payloads>,\n ): Option.Option<ReadonlyArray<Entry<U, A>>> =>\n Option.fromNullishOr(config.updateTool).pipe(\n Option.flatMap((update) =>\n Arr.findLast(messages, (m, index) =>\n m.role === \"assistant\"\n ? update(m.value, event).pipe(\n Option.map((value) => ({ index, entry: { ...m, value } })),\n )\n : Option.none(),\n ),\n ),\n Option.map(({ index, entry }) =>\n messages.map((m, i): Entry<U, A> => (i === index ? entry : m)),\n ),\n )\n\n const step = (current: S, envelope: Envelope<Payloads>): S => {\n const fresh = onStream(current, envelope)\n if (envelope.seq <= fresh.lastSeq) return current\n const state = consume(fresh, envelope)\n const event = envelope.event\n if (event.type === \"input-accepted\")\n return { ...accepted(state, event.input), lastSeq: envelope.seq }\n // Stream-scoped frames (no `runId`) and frames outside a run are not a run's content.\n if (envelope.runId === undefined || (state.active === null && event.type !== \"run-started\")) {\n return { ...state, lastSeq: envelope.seq }\n }\n // A call made in an earlier run (approved after it was interrupted) updates that run.\n if (RunView.isToolUpdate(event)) {\n const updated = updateSettled(state.messages, event)\n if (Option.isSome(updated))\n return { ...state, messages: updated.value, lastSeq: envelope.seq }\n }\n const active: RunView.RunView<Payloads> = state.active ?? {\n ...RunView.initial,\n lastSeq: state.lastSeq,\n }\n const run = RunView.step(active, envelope)\n if (!RunView.isTerminal(run)) return { ...state, active: run, lastSeq: envelope.seq }\n return {\n ...state,\n messages: [...state.messages, settled(run, envelope.seq)],\n active: null,\n lastSeq: envelope.seq,\n }\n }\n\n const ended = (state: S, reason: \"stopped\" | \"disconnected\"): S => {\n const run = state.active\n if (run === null) return state\n const final: RunView.RunView<Payloads> =\n reason === \"stopped\"\n ? { ...run, status: \"stopped\" }\n : { ...run, status: \"failed\", error: { reason: \"disconnected\" } }\n return { ...state, messages: [...state.messages, settled(final, state.lastSeq)], active: null }\n }\n\n return { initial, step, submitted, ended }\n}\n\n// ---------------------------------------------------------------------------\n// Default preset: user entries hold the input's messages, assistant entries the run\n// ---------------------------------------------------------------------------\n\n/** A user entry's value in the preset: the messages the input carried. */\nexport const UserContent = Schema.Struct({ messages: Schema.Array(Input.UserMessage) })\nexport type UserContent = typeof UserContent.Type\n\nexport const Thread = State(UserContent, RunView.RunView, RunView.RunView)\n/** The preset's state; with a contract's `P`, runs carry decoded payloads. */\nexport type Thread<P extends Payloads = Untyped> = State<UserContent, RunView.RunView<P>, P>\nexport type Message<P extends Payloads = Untyped> = Entry<UserContent, RunView.RunView<P>>\n\n/** Inputs the preset understands: any with an id; those with messages get a user entry. */\nexport type PresetInput = {\n readonly id: InputId\n readonly messages?: ReadonlyArray<Input.UserMessage>\n}\n\n/** The preset's fold for a contract's `P` (`UiContract` and `Atoms` use it). */\nexport const presetFor = <P extends Payloads = Untyped>(): Fold<\n PresetInput,\n UserContent,\n RunView.RunView<P>,\n P\n> =>\n make<PresetInput, UserContent, RunView.RunView<P>, P>({\n toUser: (input) =>\n Option.fromNullishOr(input.messages).pipe(Option.map((messages) => ({ messages }))),\n settle: (run) => run,\n decodeInput: Schema.decodeUnknownOption(Input.InputJson),\n updateTool: RunView.applyToolUpdate,\n })\n\nexport const preset = presetFor()\n\nexport const { initial, step, submitted, ended } = preset\n"],"mappings":"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"}
@@ -0,0 +1,96 @@
1
+ import { Envelope, RunEvent, StreamId } from "./RunEvent.mjs";
2
+ import { r as Disconnected, t as ClientError } from "./Client-C9UYN_VK.mjs";
3
+ import { Effect, Option, Schedule, Stream } from "effect";
4
+ import { HttpClient, HttpClientRequest } from "effect/http";
5
+ declare namespace Transport_d_exports {
6
+ export { Durable, HttpError, Session, Transport, direct, durable, durableHttp, local, session, sessionHttp };
7
+ }
8
+ type Transport<I, S, E, R> = {
9
+ /** Start a run for `input` and stream its envelopes. Interrupting the stream abandons it. */
10
+ readonly start: (input: I, state: S) => Stream.Stream<Envelope, E, R>;
11
+ /** Re-attach to a run after a reload, from `after`. Durable only. */
12
+ readonly resume?: (streamId: StreamId, after: number) => Stream.Stream<Envelope, E, R>;
13
+ /** Ask the server to stop a run. Durable only; direct mode stops by interrupting `start`. */
14
+ readonly stop?: (streamId: StreamId) => Effect.Effect<void, E, R>;
15
+ };
16
+ /**
17
+ * Direct mode: one HTTP request per input whose response body is the run.
18
+ * Stop interrupts the stream, which aborts the request; the server sees the
19
+ * client go away and interrupts its run.
20
+ *
21
+ * Any other source (RPC streams, server functions) is a transport too:
22
+ * write `{ start: (input, state) => stream }` directly.
23
+ */
24
+ declare const direct: <I, S, EB = never, RB = never>(options: {
25
+ readonly request: (input: I, state: S) => Effect.Effect<HttpClientRequest.HttpClientRequest, EB, RB>;
26
+ readonly format?: "sse" | "ndjson";
27
+ }) => Transport<I, S, ClientError | EB, HttpClient.HttpClient | RB>;
28
+ /**
29
+ * Run the agent in the same process (browser agent, tests, Storybook). The
30
+ * run's events are sequenced under `streamId`.
31
+ */
32
+ declare const local: <I, S, E, R>(run: (input: I, state: S) => Stream.Stream<RunEvent, E, R>, options?: {
33
+ readonly streamId?: StreamId;
34
+ }) => Transport<I, S, E, R>;
35
+ /** A per-run transport that can re-attach to a run (durable). */
36
+ type Durable<I, S, E, R> = Transport<I, S, E, R> & {
37
+ readonly resume: (streamId: StreamId, after: number) => Stream.Stream<Envelope, E, R>;
38
+ };
39
+ /**
40
+ * Durable runs: `submit` starts one on the server and returns its stream id;
41
+ * `events` reads it from `after` (wrap it in `Client.resumable` to survive
42
+ * drops). The run lives on the server, so a reload re-attaches with `resume`.
43
+ */
44
+ declare const durable: <I, S, ES, RS, EE, RE, EX = never, RX = never>(options: {
45
+ readonly submit: (input: I, state: S) => Effect.Effect<StreamId, ES, RS>;
46
+ readonly events: (streamId: StreamId, after: number) => Stream.Stream<Envelope, EE, RE>;
47
+ readonly stop?: (streamId: StreamId) => Effect.Effect<void, EX, RX>;
48
+ }) => Durable<I, S, ES | EE | EX, RS | RE | RX>;
49
+ /**
50
+ * A durable session (a chat): one stream carries every run, and the server
51
+ * owns its history. `send` without a stream id starts a new session and
52
+ * returns its id (the app's own chat id, branded); with one, it adds a run to
53
+ * that session. `events` follows the whole session from `after` (wrap it in
54
+ * `Client.resumable` with `until: "never"`). The run arrives on that stream,
55
+ * not as a response to `send`.
56
+ */
57
+ type Session<I, S, E, R> = {
58
+ readonly send: (streamId: Option.Option<StreamId>, input: I, state: S) => Effect.Effect<StreamId, E, R>;
59
+ readonly events: (streamId: StreamId, after: number) => Stream.Stream<Envelope, E, R>;
60
+ readonly stop?: (streamId: StreamId) => Effect.Effect<void, E, R>;
61
+ };
62
+ declare const session: <I, S, ES, RS, EE, RE, EX = never, RX = never>(options: {
63
+ readonly send: (streamId: Option.Option<StreamId>, input: I, state: S) => Effect.Effect<StreamId, ES, RS>;
64
+ readonly events: (streamId: StreamId, after: number) => Stream.Stream<Envelope, EE, RE>;
65
+ readonly stop?: (streamId: StreamId) => Effect.Effect<void, EX, RX>;
66
+ }) => Session<I, S, ES | EE | EX, RS | RE | RX>;
67
+ /** What the HTTP helpers can fail with. */
68
+ type HttpError = ClientError | Disconnected;
69
+ type HttpOptions = {
70
+ /** The SSE request for a stream from `after`. */
71
+ readonly events: (streamId: StreamId, after: number) => HttpClientRequest.HttpClientRequest;
72
+ /** The request that stops a stream's active run. */
73
+ readonly stop?: (streamId: StreamId) => HttpClientRequest.HttpClientRequest;
74
+ /** When to reconnect a dropped event stream (it resumes from the last `seq`). */
75
+ readonly reconnect: Schedule.Schedule<unknown, HttpError>;
76
+ };
77
+ /**
78
+ * `durable` over HTTP. `submit` starts a run and returns its stream id (the
79
+ * app decodes its own response, e.g. with `HttpClientResponse.schemaJson`);
80
+ * `events` is read as SSE and reconnects per `reconnect`.
81
+ */
82
+ declare const durableHttp: <I, S, ES = never, RS = never>(options: HttpOptions & {
83
+ readonly submit: (input: I, state: S) => Effect.Effect<StreamId, ES, RS>;
84
+ }) => Durable<I, S, HttpError | ES, HttpClient.HttpClient | RS>;
85
+ /**
86
+ * `session` over HTTP. `send` adds an input and returns the session's stream
87
+ * id (without one, the server starts a session); `events` follows the whole
88
+ * session as SSE. It carries many runs and never ends on its own, so
89
+ * `reconnect` usually retries forever (e.g. `Schedule.spaced("1 second")`).
90
+ */
91
+ declare const sessionHttp: <I, S, ES = never, RS = never>(options: HttpOptions & {
92
+ readonly send: (streamId: Option.Option<StreamId>, input: I, state: S) => Effect.Effect<StreamId, ES, RS>;
93
+ }) => Session<I, S, HttpError | ES, HttpClient.HttpClient | RS>;
94
+ //#endregion
95
+ export { Durable, HttpError, Session, Transport, direct, durable, durableHttp, local, session, sessionHttp, Transport_d_exports as t };
96
+ //# sourceMappingURL=Transport.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Transport.d.mts","names":[],"sources":["../src/Transport.ts"],"mappings":";;;;;;;KAgBY,UAAU,GAAG,GAAG,GAAG;;WAEpB,QAAQ,OAAO,GAAG,OAAO,MAAM,OAAO,OAAO,UAAU,GAAG;;WAE1D,UAAU,UAAU,UAAU,kBAAkB,OAAO,OAAO,UAAU,GAAG;;WAE3E,QAAQ,UAAU,aAAa,OAAO,aAAa,GAAG;;;;;;;;;;cAWpD,SAAU,GAAG,GAAG,YAAY,YAAU;WACxC,UACP,OAAO,GACP,OAAO,MACJ,OAAO,OAAO,kBAAkB,mBAAmB,IAAI;WACnD;MACP,UAAU,GAAG,GAAG,cAAqB,IAAI,WAAW,aAAa;;;;;cAkBxD,QAAS,GAAG,GAAG,GAAG,GAAC,MACxB,OAAO,GAAG,OAAO,MAAM,OAAO,OAAO,UAAU,GAAG,IAAE;WACrC,WAAW;MAC/B,UAAU,GAAG,GAAG,GAAG;;KAMV,QAAQ,GAAG,GAAG,GAAG,KAAK,UAAU,GAAG,GAAG,GAAG;WAC1C,SAAS,UAAU,UAAU,kBAAkB,OAAO,OAAO,UAAU,GAAG;;;;;;;cAQxE,UAAW,GAAG,GAAG,IAAI,IAAI,IAAI,IAAI,YAAY,YAAU;WACzD,SAAS,OAAO,GAAG,OAAO,MAAM,OAAO,OAAO,UAAU,IAAI;WAC5D,SAAS,UAAU,UAAU,kBAAkB,OAAO,OAAO,UAAU,IAAI;WAC3E,QAAQ,UAAU,aAAa,OAAO,aAAa,IAAI;MAC9D,QAAQ,GAAG,GAAG,KAAK,KAAK,IAAI,KAAK,KAAK;;;;;;;;;KAiB9B,QAAQ,GAAG,GAAG,GAAG;WAClB,OACP,UAAU,OAAO,OAAO,WACxB,OAAO,GACP,OAAO,MACJ,OAAO,OAAO,UAAU,GAAG;WACvB,SAAS,UAAU,UAAU,kBAAkB,OAAO,OAAO,UAAU,GAAG;WAC1E,QAAQ,UAAU,aAAa,OAAO,aAAa,GAAG;;cAGpD,UAAW,GAAG,GAAG,IAAI,IAAI,IAAI,IAAI,YAAY,YAAU;WACzD,OACP,UAAU,OAAO,OAAO,WACxB,OAAO,GACP,OAAO,MACJ,OAAO,OAAO,UAAU,IAAI;WACxB,SAAS,UAAU,UAAU,kBAAkB,OAAO,OAAO,UAAU,IAAI;WAC3E,QAAQ,UAAU,aAAa,OAAO,aAAa,IAAI;MAC9D,QAAQ,GAAG,GAAG,KAAK,KAAK,IAAI,KAAK,KAAK;;KAO9B,YAAY,cAAqB;KAExC;;WAEM,SAAS,UAAU,UAAU,kBAAkB,kBAAkB;;WAEjE,QAAQ,UAAU,aAAa,kBAAkB;;WAEjD,WAAW,SAAS,kBAAkB;;;;;;;cAapC,cAAe,GAAG,GAAG,YAAY,YAAU,SAC7C;WACE,SAAS,OAAO,GAAG,OAAO,MAAM,OAAO,OAAO,UAAU,IAAI;MAEtE,QAAQ,GAAG,GAAG,YAAY,IAAI,WAAW,aAAa;;;;;;;cAiB5C,cAAe,GAAG,GAAG,YAAY,YAAU,SAC7C;WACE,OACP,UAAU,OAAO,OAAO,WACxB,OAAO,GACP,OAAO,MACJ,OAAO,OAAO,UAAU,IAAI;MAElC,QAAQ,GAAG,GAAG,YAAY,IAAI,WAAW,aAAa"}
@@ -0,0 +1,82 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
+ import { StreamId } from "./RunEvent.mjs";
3
+ import { execute, ndjson, resumable, sse } from "./Client.mjs";
4
+ import { sequence } from "./Run.mjs";
5
+ import { Effect, Stream } from "effect";
6
+ //#region src/Transport.ts
7
+ /**
8
+ * How a client reaches the server. Every transport turns an input into the
9
+ * envelopes of the run it starts; durable transports can also re-attach to a
10
+ * run and stop it. Missing capabilities are absent from the value, so a UI
11
+ * can tell whether stop or resume exists.
12
+ *
13
+ * `I` is the app's input (`Input.Input` for the preset); `S` is the client
14
+ * state passed to `start`, so a request can carry any app data from it (a
15
+ * chat id, settings).
16
+ */
17
+ var Transport_exports = /* @__PURE__ */ __exportAll({
18
+ direct: () => direct,
19
+ durable: () => durable,
20
+ durableHttp: () => durableHttp,
21
+ local: () => local,
22
+ session: () => session,
23
+ sessionHttp: () => sessionHttp
24
+ });
25
+ /**
26
+ * Direct mode: one HTTP request per input whose response body is the run.
27
+ * Stop interrupts the stream, which aborts the request; the server sees the
28
+ * client go away and interrupts its run.
29
+ *
30
+ * Any other source (RPC streams, server functions) is a transport too:
31
+ * write `{ start: (input, state) => stream }` directly.
32
+ */
33
+ const direct = (options) => ({ start: (input, state) => Stream.unwrap(options.request(input, state).pipe(Effect.map((request) => options.format === "ndjson" ? ndjson(request) : sse(request)))) });
34
+ /**
35
+ * Run the agent in the same process (browser agent, tests, Storybook). The
36
+ * run's events are sequenced under `streamId`.
37
+ */
38
+ const local = (run, options) => ({ start: (input, state) => run(input, state).pipe(sequence(options?.streamId ?? StreamId.make("local"))) });
39
+ /**
40
+ * Durable runs: `submit` starts one on the server and returns its stream id;
41
+ * `events` reads it from `after` (wrap it in `Client.resumable` to survive
42
+ * drops). The run lives on the server, so a reload re-attaches with `resume`.
43
+ */
44
+ const durable = (options) => ({
45
+ start: (input, state) => Stream.unwrap(options.submit(input, state).pipe(Effect.map((streamId) => options.events(streamId, 0)))),
46
+ resume: options.events,
47
+ ...options.stop !== void 0 ? { stop: options.stop } : {}
48
+ });
49
+ const session = (options) => options;
50
+ const stopOver = (stop) => stop === void 0 ? {} : { stop: (streamId) => execute(stop(streamId)).pipe(Effect.asVoid) };
51
+ /**
52
+ * `durable` over HTTP. `submit` starts a run and returns its stream id (the
53
+ * app decodes its own response, e.g. with `HttpClientResponse.schemaJson`);
54
+ * `events` is read as SSE and reconnects per `reconnect`.
55
+ */
56
+ const durableHttp = (options) => durable({
57
+ submit: options.submit,
58
+ events: (streamId, after) => resumable((from) => sse(options.events(streamId, from)), {
59
+ from: after,
60
+ schedule: options.reconnect
61
+ }),
62
+ ...stopOver(options.stop)
63
+ });
64
+ /**
65
+ * `session` over HTTP. `send` adds an input and returns the session's stream
66
+ * id (without one, the server starts a session); `events` follows the whole
67
+ * session as SSE. It carries many runs and never ends on its own, so
68
+ * `reconnect` usually retries forever (e.g. `Schedule.spaced("1 second")`).
69
+ */
70
+ const sessionHttp = (options) => session({
71
+ send: options.send,
72
+ events: (streamId, after) => resumable((from) => sse(options.events(streamId, from)), {
73
+ from: after,
74
+ until: "never",
75
+ schedule: options.reconnect
76
+ }),
77
+ ...stopOver(options.stop)
78
+ });
79
+ //#endregion
80
+ export { direct, durable, durableHttp, local, session, sessionHttp, Transport_exports as t };
81
+
82
+ //# sourceMappingURL=Transport.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Transport.mjs","names":["Client.ndjson","Client.sse","Run.sequence","Client.execute","Client.resumable"],"sources":["../src/Transport.ts"],"sourcesContent":["/**\n * How a client reaches the server. Every transport turns an input into the\n * envelopes of the run it starts; durable transports can also re-attach to a\n * run and stop it. Missing capabilities are absent from the value, so a UI\n * can tell whether stop or resume exists.\n *\n * `I` is the app's input (`Input.Input` for the preset); `S` is the client\n * state passed to `start`, so a request can carry any app data from it (a\n * chat id, settings).\n */\nimport { Effect, type Option, type Schedule, Stream } from \"effect\"\nimport type { HttpClient, HttpClientRequest } from \"effect/http\"\nimport * as Client from \"./Client.js\"\nimport * as Run from \"./Run.js\"\nimport { type Envelope, type RunEvent, StreamId } from \"./RunEvent.js\"\n\nexport type Transport<I, S, E, R> = {\n /** Start a run for `input` and stream its envelopes. Interrupting the stream abandons it. */\n readonly start: (input: I, state: S) => Stream.Stream<Envelope, E, R>\n /** Re-attach to a run after a reload, from `after`. Durable only. */\n readonly resume?: (streamId: StreamId, after: number) => Stream.Stream<Envelope, E, R>\n /** Ask the server to stop a run. Durable only; direct mode stops by interrupting `start`. */\n readonly stop?: (streamId: StreamId) => Effect.Effect<void, E, R>\n}\n\n/**\n * Direct mode: one HTTP request per input whose response body is the run.\n * Stop interrupts the stream, which aborts the request; the server sees the\n * client go away and interrupts its run.\n *\n * Any other source (RPC streams, server functions) is a transport too:\n * write `{ start: (input, state) => stream }` directly.\n */\nexport const direct = <I, S, EB = never, RB = never>(options: {\n readonly request: (\n input: I,\n state: S,\n ) => Effect.Effect<HttpClientRequest.HttpClientRequest, EB, RB>\n readonly format?: \"sse\" | \"ndjson\"\n}): Transport<I, S, Client.ClientError | EB, HttpClient.HttpClient | RB> => ({\n start: (input, state) =>\n Stream.unwrap(\n options\n .request(input, state)\n .pipe(\n Effect.map(\n (request): Stream.Stream<Envelope, Client.ClientError, HttpClient.HttpClient> =>\n options.format === \"ndjson\" ? Client.ndjson(request) : Client.sse(request),\n ),\n ),\n ),\n})\n\n/**\n * Run the agent in the same process (browser agent, tests, Storybook). The\n * run's events are sequenced under `streamId`.\n */\nexport const local = <I, S, E, R>(\n run: (input: I, state: S) => Stream.Stream<RunEvent, E, R>,\n options?: { readonly streamId?: StreamId },\n): Transport<I, S, E, R> => ({\n start: (input, state) =>\n run(input, state).pipe(Run.sequence(options?.streamId ?? StreamId.make(\"local\"))),\n})\n\n/** A per-run transport that can re-attach to a run (durable). */\nexport type Durable<I, S, E, R> = Transport<I, S, E, R> & {\n readonly resume: (streamId: StreamId, after: number) => Stream.Stream<Envelope, E, R>\n}\n\n/**\n * Durable runs: `submit` starts one on the server and returns its stream id;\n * `events` reads it from `after` (wrap it in `Client.resumable` to survive\n * drops). The run lives on the server, so a reload re-attaches with `resume`.\n */\nexport const durable = <I, S, ES, RS, EE, RE, EX = never, RX = never>(options: {\n readonly submit: (input: I, state: S) => Effect.Effect<StreamId, ES, RS>\n readonly events: (streamId: StreamId, after: number) => Stream.Stream<Envelope, EE, RE>\n readonly stop?: (streamId: StreamId) => Effect.Effect<void, EX, RX>\n}): Durable<I, S, ES | EE | EX, RS | RE | RX> => ({\n start: (input, state) =>\n Stream.unwrap(\n options.submit(input, state).pipe(Effect.map((streamId) => options.events(streamId, 0))),\n ),\n resume: options.events,\n ...(options.stop !== undefined ? { stop: options.stop } : {}),\n})\n\n/**\n * A durable session (a chat): one stream carries every run, and the server\n * owns its history. `send` without a stream id starts a new session and\n * returns its id (the app's own chat id, branded); with one, it adds a run to\n * that session. `events` follows the whole session from `after` (wrap it in\n * `Client.resumable` with `until: \"never\"`). The run arrives on that stream,\n * not as a response to `send`.\n */\nexport type Session<I, S, E, R> = {\n readonly send: (\n streamId: Option.Option<StreamId>,\n input: I,\n state: S,\n ) => Effect.Effect<StreamId, E, R>\n readonly events: (streamId: StreamId, after: number) => Stream.Stream<Envelope, E, R>\n readonly stop?: (streamId: StreamId) => Effect.Effect<void, E, R>\n}\n\nexport const session = <I, S, ES, RS, EE, RE, EX = never, RX = never>(options: {\n readonly send: (\n streamId: Option.Option<StreamId>,\n input: I,\n state: S,\n ) => Effect.Effect<StreamId, ES, RS>\n readonly events: (streamId: StreamId, after: number) => Stream.Stream<Envelope, EE, RE>\n readonly stop?: (streamId: StreamId) => Effect.Effect<void, EX, RX>\n}): Session<I, S, ES | EE | EX, RS | RE | RX> => options\n\n// ---------------------------------------------------------------------------\n// Over HTTP: `durable` and `session` with SSE events that reconnect\n// ---------------------------------------------------------------------------\n\n/** What the HTTP helpers can fail with. */\nexport type HttpError = Client.ClientError | Client.Disconnected\n\ntype HttpOptions = {\n /** The SSE request for a stream from `after`. */\n readonly events: (streamId: StreamId, after: number) => HttpClientRequest.HttpClientRequest\n /** The request that stops a stream's active run. */\n readonly stop?: (streamId: StreamId) => HttpClientRequest.HttpClientRequest\n /** When to reconnect a dropped event stream (it resumes from the last `seq`). */\n readonly reconnect: Schedule.Schedule<unknown, HttpError>\n}\n\nconst stopOver = (stop: HttpOptions[\"stop\"]) =>\n stop === undefined\n ? {}\n : { stop: (streamId: StreamId) => Client.execute(stop(streamId)).pipe(Effect.asVoid) }\n\n/**\n * `durable` over HTTP. `submit` starts a run and returns its stream id (the\n * app decodes its own response, e.g. with `HttpClientResponse.schemaJson`);\n * `events` is read as SSE and reconnects per `reconnect`.\n */\nexport const durableHttp = <I, S, ES = never, RS = never>(\n options: HttpOptions & {\n readonly submit: (input: I, state: S) => Effect.Effect<StreamId, ES, RS>\n },\n): Durable<I, S, HttpError | ES, HttpClient.HttpClient | RS> =>\n durable({\n submit: options.submit,\n events: (streamId, after) =>\n Client.resumable((from) => Client.sse(options.events(streamId, from)), {\n from: after,\n schedule: options.reconnect,\n }),\n ...stopOver(options.stop),\n })\n\n/**\n * `session` over HTTP. `send` adds an input and returns the session's stream\n * id (without one, the server starts a session); `events` follows the whole\n * session as SSE. It carries many runs and never ends on its own, so\n * `reconnect` usually retries forever (e.g. `Schedule.spaced(\"1 second\")`).\n */\nexport const sessionHttp = <I, S, ES = never, RS = never>(\n options: HttpOptions & {\n readonly send: (\n streamId: Option.Option<StreamId>,\n input: I,\n state: S,\n ) => Effect.Effect<StreamId, ES, RS>\n },\n): Session<I, S, HttpError | ES, HttpClient.HttpClient | RS> =>\n session({\n send: options.send,\n events: (streamId, after) =>\n Client.resumable((from) => Client.sse(options.events(streamId, from)), {\n from: after,\n until: \"never\",\n schedule: options.reconnect,\n }),\n ...stopOver(options.stop),\n })\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCA,MAAa,UAAwC,aAMwB,EAC3E,QAAQ,OAAO,UACb,OAAO,OACL,QACG,QAAQ,OAAO,KAAK,CAAC,CACrB,KACC,OAAO,KACJ,YACC,QAAQ,WAAW,WAAWA,OAAc,OAAO,IAAIC,IAAW,OAAO,CAC7E,CACF,CACJ,EACJ;;;;;AAMA,MAAa,SACX,KACA,aAC2B,EAC3B,QAAQ,OAAO,UACb,IAAI,OAAO,KAAK,CAAC,CAAC,KAAKC,SAAa,SAAS,YAAY,SAAS,KAAK,OAAO,CAAC,CAAC,EACpF;;;;;;AAYA,MAAa,WAAyD,aAIpB;CAChD,QAAQ,OAAO,UACb,OAAO,OACL,QAAQ,OAAO,OAAO,KAAK,CAAC,CAAC,KAAK,OAAO,KAAK,aAAa,QAAQ,OAAO,UAAU,CAAC,CAAC,CAAC,CACzF;CACF,QAAQ,QAAQ;CAChB,GAAI,QAAQ,SAAS,KAAA,IAAY,EAAE,MAAM,QAAQ,KAAK,IAAI,CAAC;AAC7D;AAoBA,MAAa,WAAyD,YAQrB;AAkBjD,MAAM,YAAY,SAChB,SAAS,KAAA,IACL,CAAC,IACD,EAAE,OAAO,aAAuBC,QAAe,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,OAAO,MAAM,EAAE;;;;;;AAOzF,MAAa,eACX,YAIA,QAAQ;CACN,QAAQ,QAAQ;CAChB,SAAS,UAAU,UACjBC,WAAkB,SAASH,IAAW,QAAQ,OAAO,UAAU,IAAI,CAAC,GAAG;EACrE,MAAM;EACN,UAAU,QAAQ;CACpB,CAAC;CACH,GAAG,SAAS,QAAQ,IAAI;AAC1B,CAAC;;;;;;;AAQH,MAAa,eACX,YAQA,QAAQ;CACN,MAAM,QAAQ;CACd,SAAS,UAAU,UACjBG,WAAkB,SAASH,IAAW,QAAQ,OAAO,UAAU,IAAI,CAAC,GAAG;EACrE,MAAM;EACN,OAAO;EACP,UAAU,QAAQ;CACpB,CAAC;CACH,GAAG,SAAS,QAAQ,IAAI;AAC1B,CAAC"}
@@ -0,0 +1,113 @@
1
+ import { Envelope, Json, Payloads as Payloads$1, RunEvent, ToolPayload } from "./RunEvent.mjs";
2
+ import { l as RunView } from "./RunView-nHfJwN8t.mjs";
3
+ import { c as Thread } from "./Thread-CAXsjhpX.mjs";
4
+ import { Effect, Schema } from "effect";
5
+ import * as Items from "@effect-uai/core/Items";
6
+ import * as Content from "@effect-uai/core/Content";
7
+ import * as ToolOutput from "@effect-uai/core/ToolOutput";
8
+ import * as Tool from "@effect-uai/core/Tool";
9
+ import { ToolInputSchema } from "@effect-uai/core/Tool";
10
+ import * as ToolEvent from "@effect-uai/core/ToolEvent";
11
+ declare namespace UiContract_d_exports {
12
+ export { Call, Payloads, PayloadsOf, Spec, ToolSpec, UiContract, make };
13
+ }
14
+ type AnyCodec = Schema.Codec<any, any, never, never>;
15
+ type JsonCodec = Schema.Codec<any, Schema.Json, never, never>;
16
+ /** A tool as the contract sees it: a core `Tool` or a shared spec (`as const`). */
17
+ type ToolSpec = {
18
+ readonly name: string;
19
+ readonly inputSchema: AnyCodec | ToolInputSchema<any>;
20
+ readonly outputSchema?: JsonCodec;
21
+ readonly failureSchema?: JsonCodec;
22
+ };
23
+ type Spec = {
24
+ readonly tools?: ReadonlyArray<ToolSpec>;
25
+ readonly objects?: Readonly<Record<string, AnyCodec>>;
26
+ readonly data?: Readonly<Record<string, AnyCodec>>;
27
+ readonly live?: Readonly<Record<string, AnyCodec>>;
28
+ readonly metadata?: AnyCodec;
29
+ };
30
+ type InputOf<S> = S extends Schema.Top ? S["Type"] : S extends ToolInputSchema<infer A> ? A : Json;
31
+ type CoreTool = {
32
+ readonly _tag: "LocalTool" | "DeclaredTool";
33
+ };
34
+ type OutputOf<T> = T extends CoreTool ? Tool.ToolOutput<T> : T extends {
35
+ readonly outputSchema: infer O extends Schema.Top;
36
+ } ? O["Type"] : Content.Value;
37
+ type FailureOf<T> = T extends CoreTool ? Tool.ToolFailure<T> : T extends {
38
+ readonly failureSchema: infer F extends Schema.Top;
39
+ } ? F["Type"] : never;
40
+ type ToolOf<T> = T extends ToolSpec ? {
41
+ readonly name: T["name"];
42
+ readonly input: InputOf<T["inputSchema"]>;
43
+ readonly output: OutputOf<T>;
44
+ readonly failure: FailureOf<T> | ToolOutput.CoreError;
45
+ } : never;
46
+ type ElementOf<T> = T extends ReadonlyArray<infer E> ? E : never;
47
+ type NamedOf<M> = { readonly [K in keyof M & string]: {
48
+ readonly name: K;
49
+ readonly value: M[K] extends Schema.Top ? M[K]["Type"] : never;
50
+ }; }[keyof M & string];
51
+ /** The payload types a spec describes: its listed names only. */
52
+ type PayloadsOf<S extends Spec> = {
53
+ readonly tool: ToolOf<ElementOf<S["tools"]>>;
54
+ readonly object: NamedOf<S["objects"]>;
55
+ readonly data: NamedOf<S["data"]>;
56
+ readonly live: NamedOf<S["live"]>;
57
+ readonly metadata: S["metadata"] extends Schema.Top ? S["metadata"]["Type"] : Json;
58
+ };
59
+ type NameOf<T> = T extends {
60
+ readonly name: infer N;
61
+ } ? N : never;
62
+ type ValueOf<T, N> = Extract<T, {
63
+ readonly name: N;
64
+ }> extends {
65
+ readonly value: infer V;
66
+ } ? V : never;
67
+ type ToolField<T, N, K extends "output" | "failure"> = Extract<T, {
68
+ readonly name: N;
69
+ }> extends (infer X extends ToolPayload) ? X[K] : never;
70
+ /** The call an app closes itself: its id and the tool's name. */
71
+ type Call<N extends string = string> = {
72
+ readonly call_id: string;
73
+ readonly name: N;
74
+ };
75
+ /**
76
+ * A contract's payload types, for the generic folds:
77
+ * `Thread.presetFor<UiContract.Payloads<typeof ChatUi>>()`.
78
+ */
79
+ type Payloads<C> = C extends UiContract<infer P> ? P : never;
80
+ interface UiContract<P extends Payloads$1> {
81
+ /** Decode a wire envelope (as `Client` reads it) into a typed one. */
82
+ decode(envelope: Envelope): Effect.Effect<Envelope<P>, Schema.SchemaError>;
83
+ /** Typed envelope ⇄ wire envelope. */
84
+ readonly Envelope: Schema.Codec<Envelope<P>, Envelope>;
85
+ /** A typed run view ⇄ the untyped one, for persisting or a framework's model. */
86
+ readonly RunView: Schema.Codec<RunView<P>, RunView>;
87
+ /** A typed `Thread` ⇄ the untyped one. */
88
+ readonly Thread: Schema.Codec<Thread<P>, Thread>;
89
+ /** Server: a `data` event with its value encoded for the wire. */
90
+ data<N extends NameOf<P["data"]>>(name: N, value: ValueOf<P["data"], N>, options?: {
91
+ readonly id?: string;
92
+ }): Effect.Effect<RunEvent, Schema.SchemaError>;
93
+ /** Server: a `live` event with its value encoded for the wire. */
94
+ live<N extends NameOf<P["live"]>>(name: N, value: ValueOf<P["live"], N>): Effect.Effect<RunEvent, Schema.SchemaError>;
95
+ /** Server: run metadata encoded for `run-completed`. */
96
+ metadata(value: P["metadata"]): Effect.Effect<Json, Schema.SchemaError>;
97
+ /** Server: a toolkit event (`Toolkit.run`) encoded with the tool's schemas. */
98
+ toolEvent(event: ToolEvent.ToolEvent): Effect.Effect<RunEvent, Schema.SchemaError>;
99
+ /** Server: a history item (already encoded) as the `tool-result` of tool `name`. */
100
+ toolOutput(item: Items.ToolCallOutput, name: string): Effect.Effect<RunEvent, Schema.SchemaError>;
101
+ /** Server: close a call with a value of its tool's output (e.g. supplied by the user). */
102
+ success<N extends NameOf<P["tool"]>>(call: Call<N>, value: ToolField<P["tool"], N, "output">): Effect.Effect<RunEvent, Schema.SchemaError>;
103
+ /** Server: close a call with an error (e.g. the app's `Denied`). */
104
+ failure<N extends NameOf<P["tool"]>>(call: Call<N>, error: ToolField<P["tool"], N, "failure">): Effect.Effect<RunEvent, Schema.SchemaError>;
105
+ }
106
+ /**
107
+ * Build a contract. Tool names must be literal types: pass core `Tool`s or
108
+ * specs declared `as const`.
109
+ */
110
+ declare function make<const S extends Spec>(spec: S): UiContract<PayloadsOf<S>>;
111
+ //#endregion
112
+ export { Call, Payloads, PayloadsOf, Spec, ToolSpec, UiContract, make, UiContract_d_exports as t };
113
+ //# sourceMappingURL=UiContract.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UiContract.d.mts","names":[],"sources":["../src/UiContract.ts"],"mappings":";;;;;;;;;;;;;KAuCK,WAAW,OAAO;KAElB,YAAY,OAAO,WAAW,OAAO;;KAG9B;WACD;WACA,aAAa,WAAW;WACxB,eAAe;WACf,gBAAgB;;KAGf;WACD,QAAQ,cAAc;WACtB,UAAU,SAAS,eAAe;WAClC,OAAO,SAAS,eAAe;WAC/B,OAAO,SAAS,eAAe;WAC/B,WAAW;;KAGjB,QAAQ,KAAK,UAAU,OAAO,MAAM,YAAY,UAAU,sBAAsB,KAAK,IAAI;KAGzF;WAAsB;;KACtB,SAAS,KAAK,UAAU,WACzB,KAAK,WAAW,KAChB;WAAqB,oBAAoB,UAAU,OAAO;IACxD,YACA,QAAQ;KACT,UAAU,KAAK,UAAU,WAC1B,KAAK,YAAY,KACjB;WAAqB,qBAAqB,UAAU,OAAO;IACzD;KAED,OAAO,KAAK,UAAU;WAEZ,MAAM;WACN,OAAO,QAAQ;WACf,QAAQ,SAAS;WACjB,SAAS,UAAU,KAAK,WAAW;;KAG7C,UAAU,KAAK,UAAU,oBAAoB,KAAK;KAClD,QAAQ,iBACD,WAAW;WACV,MAAM;WACN,OAAO,EAAE,WAAW,OAAO,MAAM,EAAE;WAExC;;KAEI,WAAW,UAAU;WACtB,MAAM,OAAO,UAAU;WACvB,QAAQ,QAAQ;WAChB,MAAM,QAAQ;WACd,MAAM,QAAQ;WACd,UAAU,sBAAsB,OAAO,MAAM,wBAAwB;;KAG3E,OAAO,KAAK;WAAqB,YAAY;IAAM;KACnD,QAAQ,GAAG,KACd,QAAQ;WAAc,MAAM;;WAAwB,aAAa;IAAM;KACpE,UAAU,GAAG,GAAG,kCACnB,QAAQ;WAAc,MAAM;kBAAmB,UAAU,eAAc,EAAE;;KAG/D,KAAK;WAAwC;WAA0B,MAAM;;;;;;KAM7E,SAAS,KAAK,UAAU,iBAAiB,KAAK;UAEzC,WAAW,UAAU;;EAEpC,OAAO,UAAU,WAAW,OAAO,OAAO,SAAS,IAAI,OAAO;;WAErD,UAAU,OAAO,MAAM,SAAS,IAAI;;WAEpC,SAAS,OAAO,MAAM,QAAgB,IAAI;;WAE1C,QAAQ,OAAO,MAAM,OAAc,IAAI;;EAEhD,KAAK,UAAU,OAAO,YACpB,MAAM,GACN,OAAO,QAAQ,WAAW,IAC1B;aAAqB;MACpB,OAAO,OAAO,UAAU,OAAO;;EAElC,KAAK,UAAU,OAAO,YACpB,MAAM,GACN,OAAO,QAAQ,WAAW,KACzB,OAAO,OAAO,UAAU,OAAO;;EAElC,SAAS,OAAO,gBAAgB,OAAO,OAAO,MAAM,OAAO;;EAE3D,UAAU,OAAO,UAAU,YAAY,OAAO,OAAO,UAAU,OAAO;;EAEtE,WAAW,MAAM,MAAM,gBAAgB,eAAe,OAAO,OAAO,UAAU,OAAO;;EAErF,QAAQ,UAAU,OAAO,YACvB,MAAM,KAAK,IACX,OAAO,UAAU,WAAW,eAC3B,OAAO,OAAO,UAAU,OAAO;;EAElC,QAAQ,UAAU,OAAO,YACvB,MAAM,KAAK,IACX,OAAO,UAAU,WAAW,gBAC3B,OAAO,OAAO,UAAU,OAAO;;;;;;iBAyEpB,WAAW,UAAU,MAAM,MAAM,IAAI,WAAW,WAAW"}