@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.
- package/LICENSE +21 -0
- package/README.md +64 -2
- package/dist/Atoms.d.mts +141 -0
- package/dist/Atoms.d.mts.map +1 -0
- package/dist/Atoms.mjs +132 -0
- package/dist/Atoms.mjs.map +1 -0
- package/dist/Client-C9UYN_VK.d.mts +219 -0
- package/dist/Client-C9UYN_VK.d.mts.map +1 -0
- package/dist/Client.d.mts +2 -0
- package/dist/Client.mjs +55 -0
- package/dist/Client.mjs.map +1 -0
- package/dist/Input-Cz-Kxusm.d.mts +277 -0
- package/dist/Input-Cz-Kxusm.d.mts.map +1 -0
- package/dist/Input.d.mts +2 -0
- package/dist/Input.mjs +127 -0
- package/dist/Input.mjs.map +1 -0
- package/dist/Projection.d.mts +44 -0
- package/dist/Projection.d.mts.map +1 -0
- package/dist/Projection.mjs +133 -0
- package/dist/Projection.mjs.map +1 -0
- package/dist/Run.d.mts +44 -0
- package/dist/Run.d.mts.map +1 -0
- package/dist/Run.mjs +117 -0
- package/dist/Run.mjs.map +1 -0
- package/dist/RunEvent.d.mts +1225 -0
- package/dist/RunEvent.d.mts.map +1 -0
- package/dist/RunEvent.mjs +264 -0
- package/dist/RunEvent.mjs.map +1 -0
- package/dist/RunView-nHfJwN8t.d.mts +380 -0
- package/dist/RunView-nHfJwN8t.d.mts.map +1 -0
- package/dist/RunView.d.mts +2 -0
- package/dist/RunView.mjs +452 -0
- package/dist/RunView.mjs.map +1 -0
- package/dist/SseEncoding.d.mts +437 -0
- package/dist/SseEncoding.d.mts.map +1 -0
- package/dist/SseEncoding.mjs +64 -0
- package/dist/SseEncoding.mjs.map +1 -0
- package/dist/Thread-CAXsjhpX.d.mts +474 -0
- package/dist/Thread-CAXsjhpX.d.mts.map +1 -0
- package/dist/Thread.d.mts +2 -0
- package/dist/Thread.mjs +225 -0
- package/dist/Thread.mjs.map +1 -0
- package/dist/Transport.d.mts +96 -0
- package/dist/Transport.d.mts.map +1 -0
- package/dist/Transport.mjs +82 -0
- package/dist/Transport.mjs.map +1 -0
- package/dist/UiContract.d.mts +113 -0
- package/dist/UiContract.d.mts.map +1 -0
- package/dist/UiContract.mjs +229 -0
- package/dist/UiContract.mjs.map +1 -0
- package/dist/index.d.mts +12 -0
- package/dist/index.mjs +12 -0
- package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
- package/package.json +99 -3
- package/src/Atoms.test.ts +163 -0
- package/src/Atoms.ts +355 -0
- package/src/Client.test.ts +53 -0
- package/src/Client.ts +92 -0
- package/src/Input.test.ts +68 -0
- package/src/Input.ts +165 -0
- package/src/Projection.test.ts +198 -0
- package/src/Projection.ts +142 -0
- package/src/Run.test.ts +119 -0
- package/src/Run.ts +166 -0
- package/src/RunEvent.test.ts +84 -0
- package/src/RunEvent.ts +374 -0
- package/src/RunView.test.ts +98 -0
- package/src/RunView.ts +583 -0
- package/src/SseEncoding.test.ts +46 -0
- package/src/SseEncoding.ts +77 -0
- package/src/Thread.test.ts +304 -0
- package/src/Thread.ts +286 -0
- package/src/Transport.ts +182 -0
- package/src/UiContract.test.ts +342 -0
- package/src/UiContract.ts +402 -0
- package/src/index.ts +11 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
|
+
import { Data as Data$1, Interrupt, Live, ObjectDone, Outcome, RunCompleted, RunInterrupted, ToolApprovalRequested, ToolCallReady, ToolPartial, ToolResult, envelopeFields, outcome, plainEvents } from "./RunEvent.mjs";
|
|
3
|
+
import { contentBlocks, dataBlock, objectBlock, schema, toolBlock, unnamedObject } from "./RunView.mjs";
|
|
4
|
+
import { State, UserContent } from "./Thread.mjs";
|
|
5
|
+
import { Effect, HashMap, Match, Option, Result, Schema } from "effect";
|
|
6
|
+
import * as Content from "@effect-uai/core/Content";
|
|
7
|
+
import * as ToolOutput from "@effect-uai/core/ToolOutput";
|
|
8
|
+
//#region src/UiContract.ts
|
|
9
|
+
/**
|
|
10
|
+
* A typed contract between server and client, built from Schemas only, so
|
|
11
|
+
* both sides import the same value (the pattern of Effect's `HttpApi`). The
|
|
12
|
+
* wire stays JSON; the client decodes each envelope once on arrival, and from
|
|
13
|
+
* then on the state holds decoded values: a tool block narrowed by `name`
|
|
14
|
+
* has its tool's input and output types, a `Date` is a `Date`.
|
|
15
|
+
*
|
|
16
|
+
* const ChatUi = UiContract.make({ tools: [GetWeather], live: { metrics: Metrics } })
|
|
17
|
+
*
|
|
18
|
+
* // server: typed payload events (tool outputs are encoded by the Toolkit)
|
|
19
|
+
* yield* ChatUi.live("metrics", { tokps: 42 })
|
|
20
|
+
*
|
|
21
|
+
* // client: the state is typed by the contract
|
|
22
|
+
* const chat = Atoms.thread({ contract: ChatUi, transport, busy: "block" })
|
|
23
|
+
*
|
|
24
|
+
* The types list the contract's names only, so narrowing by name is exact. A
|
|
25
|
+
* name the contract doesn't list (a newer server rolling out a tool) still
|
|
26
|
+
* arrives, as JSON, but the types don't mention it: a `switch (b.name)` skips
|
|
27
|
+
* it. A listed name whose payload doesn't decode fails the envelope: that is
|
|
28
|
+
* a contract violation, not data to show.
|
|
29
|
+
*
|
|
30
|
+
* A tool is a core `Tool` or a shared spec (`as const`, schemas only). Its
|
|
31
|
+
* result is `Success { value } | Failure { error }`: the error is the tool's
|
|
32
|
+
* `failureSchema` (app closings like `ToolDecision.Denied` included) or core's
|
|
33
|
+
* `ToolOutput.CoreError`.
|
|
34
|
+
* Without `outputSchema`, values are a `Content.Value` (media as classes).
|
|
35
|
+
*/
|
|
36
|
+
var UiContract_exports = /* @__PURE__ */ __exportAll({ make: () => make });
|
|
37
|
+
const json = (schema) => Schema.toCodecJson(schema);
|
|
38
|
+
const listed = (name) => Schema.Literal(name);
|
|
39
|
+
const unlisted = (names) => Schema.String.check(Schema.makeFilter((name) => !names.includes(name)));
|
|
40
|
+
const isCodec = (schema) => Schema.isSchema(schema);
|
|
41
|
+
const inputCodec = (tool) => {
|
|
42
|
+
const schema = tool.inputSchema;
|
|
43
|
+
if (isCodec(schema)) return json(schema);
|
|
44
|
+
return Schema.declare((u) => {
|
|
45
|
+
const result = schema["~standard"].validate(u);
|
|
46
|
+
return !(result instanceof Promise) && result.issues === void 0;
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
const isAnything = (tool) => tool?.outputSchema === void 0 || tool.outputSchema === Content.Value;
|
|
50
|
+
const outputCodec = (tool) => isAnything(tool) ? ToolOutput.ValueParts : tool?.outputSchema ?? ToolOutput.ValueParts;
|
|
51
|
+
const encodeParts = Schema.encodeEffect(Schema.Array(ToolOutput.PartJson));
|
|
52
|
+
const failureCodec = (tool) => tool?.failureSchema === void 0 ? ToolOutput.CoreError : Schema.Union([tool.failureSchema, ToolOutput.CoreError]);
|
|
53
|
+
const entries = (record) => Object.entries(record ?? {});
|
|
54
|
+
const toJson = Schema.decodeUnknownEffect(Schema.Json);
|
|
55
|
+
const toOutcome = Schema.decodeUnknownEffect(Outcome);
|
|
56
|
+
function make(spec) {
|
|
57
|
+
const tools = spec.tools ?? [];
|
|
58
|
+
const objects = entries(spec.objects);
|
|
59
|
+
const data = entries(spec.data);
|
|
60
|
+
const live = entries(spec.live);
|
|
61
|
+
const toolNames = tools.map((t) => t.name);
|
|
62
|
+
const metadata = spec.metadata === void 0 ? Schema.Json : json(spec.metadata);
|
|
63
|
+
const resultCodec = (tool) => outcome(outputCodec(tool), failureCodec(tool));
|
|
64
|
+
const codecsOf = (tool) => ({
|
|
65
|
+
output: outputCodec(tool),
|
|
66
|
+
result: resultCodec(tool),
|
|
67
|
+
anything: isAnything(tool)
|
|
68
|
+
});
|
|
69
|
+
const unlistedCodecs = codecsOf(void 0);
|
|
70
|
+
const codecsByName = HashMap.fromIterable(tools.map((t) => [t.name, codecsOf(t)]));
|
|
71
|
+
const codecs = (name) => Option.getOrElse(HashMap.get(codecsByName, name), () => unlistedCodecs);
|
|
72
|
+
const interrupt = Schema.Union([...tools.map((t) => Schema.Struct({
|
|
73
|
+
...Interrupt.fields,
|
|
74
|
+
name: listed(t.name),
|
|
75
|
+
input: inputCodec(t)
|
|
76
|
+
})), Schema.Struct({
|
|
77
|
+
...Interrupt.fields,
|
|
78
|
+
name: unlisted(toolNames)
|
|
79
|
+
})]);
|
|
80
|
+
const toolInput = (event) => Schema.Union([...tools.map((t) => Schema.Struct({
|
|
81
|
+
...event.fields,
|
|
82
|
+
name: listed(t.name),
|
|
83
|
+
input: inputCodec(t)
|
|
84
|
+
})), Schema.Struct({
|
|
85
|
+
...event.fields,
|
|
86
|
+
name: unlisted(toolNames)
|
|
87
|
+
})]);
|
|
88
|
+
const named = (event, members) => Schema.Union([...members.map(([name, schema]) => Schema.Struct({
|
|
89
|
+
...event.fields,
|
|
90
|
+
name: listed(name),
|
|
91
|
+
value: json(schema)
|
|
92
|
+
})), Schema.Struct({
|
|
93
|
+
...event.fields,
|
|
94
|
+
name: unlisted(members.map(([name]) => name))
|
|
95
|
+
})]);
|
|
96
|
+
const event = Schema.Union([
|
|
97
|
+
...plainEvents.map((e) => Schema.toType(e)),
|
|
98
|
+
Schema.Struct({
|
|
99
|
+
...RunCompleted.fields,
|
|
100
|
+
metadata: Schema.optional(metadata)
|
|
101
|
+
}),
|
|
102
|
+
Schema.Struct({
|
|
103
|
+
...RunInterrupted.fields,
|
|
104
|
+
interrupts: Schema.Array(interrupt)
|
|
105
|
+
}),
|
|
106
|
+
Schema.Union([...objects.map(([name, schema]) => Schema.Struct({
|
|
107
|
+
...ObjectDone.fields,
|
|
108
|
+
name: listed(name),
|
|
109
|
+
value: json(schema)
|
|
110
|
+
})), Schema.Struct({
|
|
111
|
+
...ObjectDone.fields,
|
|
112
|
+
name: Schema.optional(unlisted(objects.map(([name]) => name)))
|
|
113
|
+
})]),
|
|
114
|
+
toolInput(ToolCallReady),
|
|
115
|
+
toolInput(ToolApprovalRequested),
|
|
116
|
+
Schema.Union([...tools.map((t) => Schema.Struct({
|
|
117
|
+
...ToolPartial.fields,
|
|
118
|
+
name: listed(t.name),
|
|
119
|
+
value: outputCodec(t)
|
|
120
|
+
})), Schema.Struct({
|
|
121
|
+
...ToolPartial.fields,
|
|
122
|
+
name: unlisted(toolNames)
|
|
123
|
+
})]),
|
|
124
|
+
Schema.Union([...tools.map((t) => Schema.Struct({
|
|
125
|
+
...ToolResult.fields,
|
|
126
|
+
name: listed(t.name),
|
|
127
|
+
result: resultCodec(t)
|
|
128
|
+
})), Schema.Struct({
|
|
129
|
+
...ToolResult.fields,
|
|
130
|
+
name: unlisted(toolNames)
|
|
131
|
+
})]),
|
|
132
|
+
named(Data$1, data),
|
|
133
|
+
named(Live, live)
|
|
134
|
+
]);
|
|
135
|
+
const envelope = Schema.Struct({
|
|
136
|
+
...envelopeFields,
|
|
137
|
+
event
|
|
138
|
+
});
|
|
139
|
+
const view = schema({
|
|
140
|
+
block: Schema.Union([
|
|
141
|
+
...contentBlocks,
|
|
142
|
+
...tools.map((t) => toolBlock(listed(t.name), inputCodec(t), outputCodec(t), failureCodec(t))),
|
|
143
|
+
toolBlock(unlisted(toolNames), Schema.Json, Schema.Json, Schema.Json),
|
|
144
|
+
...objects.map(([name, schema]) => objectBlock(listed(name), json(schema))),
|
|
145
|
+
objectBlock(unlisted(objects.map(([name]) => name)), Schema.Json),
|
|
146
|
+
unnamedObject,
|
|
147
|
+
...data.map(([name, schema]) => dataBlock(listed(name), json(schema))),
|
|
148
|
+
dataBlock(unlisted(data.map(([name]) => name)), Schema.Json)
|
|
149
|
+
]),
|
|
150
|
+
interrupt,
|
|
151
|
+
live: Schema.Struct(Object.fromEntries(live.map(([name, schema]) => [name, Schema.optionalKey(json(schema))]))),
|
|
152
|
+
metadata
|
|
153
|
+
});
|
|
154
|
+
const codecOf = (members, name) => members.find(([n]) => n === name)?.[1];
|
|
155
|
+
const encode = (schema, value) => Schema.encodeUnknownEffect(schema === void 0 ? Schema.Json : json(schema))(value).pipe(Effect.flatMap(toJson));
|
|
156
|
+
const toolResult = (call, outcome) => Schema.encodeUnknownEffect(codecs(call.name).result)(outcome).pipe(Effect.flatMap(toOutcome), Effect.map((result) => ({
|
|
157
|
+
type: "tool-result",
|
|
158
|
+
callId: call.call_id,
|
|
159
|
+
name: call.name,
|
|
160
|
+
result
|
|
161
|
+
})));
|
|
162
|
+
return {
|
|
163
|
+
decode: Schema.decodeUnknownEffect(envelope),
|
|
164
|
+
Envelope: envelope,
|
|
165
|
+
RunView: view,
|
|
166
|
+
Thread: State(UserContent, view, view),
|
|
167
|
+
data: (name, value, options) => encode(codecOf(data, name), value).pipe(Effect.map((encoded) => options?.id === void 0 ? {
|
|
168
|
+
type: "data",
|
|
169
|
+
name,
|
|
170
|
+
value: encoded
|
|
171
|
+
} : {
|
|
172
|
+
type: "data",
|
|
173
|
+
name,
|
|
174
|
+
value: encoded,
|
|
175
|
+
id: options.id
|
|
176
|
+
})),
|
|
177
|
+
live: (name, value) => encode(codecOf(live, name), value).pipe(Effect.map((encoded) => ({
|
|
178
|
+
type: "live",
|
|
179
|
+
name,
|
|
180
|
+
value: encoded
|
|
181
|
+
}))),
|
|
182
|
+
metadata: (value) => encode(spec.metadata, value),
|
|
183
|
+
toolEvent: (event) => Match.value(event).pipe(Match.tagsExhaustive({
|
|
184
|
+
Partial: (e) => Schema.encodeUnknownEffect(codecs(e.tool).output)(e.value).pipe(Effect.flatMap(toJson), Effect.map((value) => ({
|
|
185
|
+
type: "tool-partial",
|
|
186
|
+
callId: e.call_id,
|
|
187
|
+
name: e.tool,
|
|
188
|
+
value
|
|
189
|
+
}))),
|
|
190
|
+
Output: (e) => toolResult({
|
|
191
|
+
call_id: e.call_id,
|
|
192
|
+
name: e.tool
|
|
193
|
+
}, Result.match(e.result, {
|
|
194
|
+
onSuccess: (value) => ({
|
|
195
|
+
_tag: "Success",
|
|
196
|
+
value
|
|
197
|
+
}),
|
|
198
|
+
onFailure: (error) => ({
|
|
199
|
+
_tag: "Failure",
|
|
200
|
+
error
|
|
201
|
+
})
|
|
202
|
+
}))
|
|
203
|
+
})),
|
|
204
|
+
toolOutput: (item, name) => Match.value(item).pipe(Match.when({ isError: true }, (o) => Effect.map(ToolOutput.encoded(o.output), (error) => ({
|
|
205
|
+
_tag: "Failure",
|
|
206
|
+
error
|
|
207
|
+
}))), Match.orElse((o) => Effect.map(codecs(name).anything ? encodeParts(o.output) : ToolOutput.encoded(o.output), (value) => ({
|
|
208
|
+
_tag: "Success",
|
|
209
|
+
value
|
|
210
|
+
}))), Effect.flatMap(toOutcome), Effect.map((result) => ({
|
|
211
|
+
type: "tool-result",
|
|
212
|
+
callId: item.call_id,
|
|
213
|
+
name,
|
|
214
|
+
result
|
|
215
|
+
}))),
|
|
216
|
+
success: (call, value) => toolResult(call, {
|
|
217
|
+
_tag: "Success",
|
|
218
|
+
value
|
|
219
|
+
}),
|
|
220
|
+
failure: (call, error) => toolResult(call, {
|
|
221
|
+
_tag: "Failure",
|
|
222
|
+
error
|
|
223
|
+
})
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
//#endregion
|
|
227
|
+
export { make, UiContract_exports as t };
|
|
228
|
+
|
|
229
|
+
//# sourceMappingURL=UiContract.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"UiContract.mjs","names":["Event.Outcome","Event.outcome","Event.ToolCallReady","Event.ToolApprovalRequested","Event.Data","Event.Live","Event.envelopeFields","RunView.schema","RunView.contentBlocks","RunView.toolBlock","RunView.objectBlock","RunView.unnamedObject","RunView.dataBlock","Thread.State","Thread.UserContent"],"sources":["../src/UiContract.ts"],"sourcesContent":["/**\n * A typed contract between server and client, built from Schemas only, so\n * both sides import the same value (the pattern of Effect's `HttpApi`). The\n * wire stays JSON; the client decodes each envelope once on arrival, and from\n * then on the state holds decoded values: a tool block narrowed by `name`\n * has its tool's input and output types, a `Date` is a `Date`.\n *\n * const ChatUi = UiContract.make({ tools: [GetWeather], live: { metrics: Metrics } })\n *\n * // server: typed payload events (tool outputs are encoded by the Toolkit)\n * yield* ChatUi.live(\"metrics\", { tokps: 42 })\n *\n * // client: the state is typed by the contract\n * const chat = Atoms.thread({ contract: ChatUi, transport, busy: \"block\" })\n *\n * The types list the contract's names only, so narrowing by name is exact. A\n * name the contract doesn't list (a newer server rolling out a tool) still\n * arrives, as JSON, but the types don't mention it: a `switch (b.name)` skips\n * it. A listed name whose payload doesn't decode fails the envelope: that is\n * a contract violation, not data to show.\n *\n * A tool is a core `Tool` or a shared spec (`as const`, schemas only). Its\n * result is `Success { value } | Failure { error }`: the error is the tool's\n * `failureSchema` (app closings like `ToolDecision.Denied` included) or core's\n * `ToolOutput.CoreError`.\n * Without `outputSchema`, values are a `Content.Value` (media as classes).\n */\nimport * as Content from \"@effect-uai/core/Content\"\nimport type * as Items from \"@effect-uai/core/Items\"\nimport type * as Tool from \"@effect-uai/core/Tool\"\nimport type { ToolInputSchema } from \"@effect-uai/core/Tool\"\nimport type * as ToolEvent from \"@effect-uai/core/ToolEvent\"\nimport * as ToolOutput from \"@effect-uai/core/ToolOutput\"\nimport { Effect, HashMap, Match, Option, Result, Schema } from \"effect\"\nimport * as Event from \"./RunEvent.js\"\nimport type { Envelope, Json, Payloads as AnyPayloads, RunEvent, ToolPayload } from \"./RunEvent.js\"\nimport * as RunView from \"./RunView.js\"\nimport * as Thread from \"./Thread.js\"\n\ntype AnyCodec = Schema.Codec<any, any, never, never>\n// Tool values reach history and the UI through the same schema, so it must encode to JSON.\ntype JsonCodec = Schema.Codec<any, Schema.Json, never, never>\n\n/** A tool as the contract sees it: a core `Tool` or a shared spec (`as const`). */\nexport type ToolSpec = {\n readonly name: string\n readonly inputSchema: AnyCodec | ToolInputSchema<any>\n readonly outputSchema?: JsonCodec\n readonly failureSchema?: JsonCodec\n}\n\nexport type Spec = {\n readonly tools?: ReadonlyArray<ToolSpec>\n readonly objects?: Readonly<Record<string, AnyCodec>>\n readonly data?: Readonly<Record<string, AnyCodec>>\n readonly live?: Readonly<Record<string, AnyCodec>>\n readonly metadata?: AnyCodec\n}\n\ntype InputOf<S> = S extends Schema.Top ? S[\"Type\"] : S extends ToolInputSchema<infer A> ? A : Json\n// A core `Tool` carries its types; a spec has them in its schemas. Without\n// `outputSchema` a tool returns a `Content.Value`, and that is what arrives.\ntype CoreTool = { readonly _tag: \"LocalTool\" | \"DeclaredTool\" }\ntype OutputOf<T> = T extends CoreTool\n ? Tool.ToolOutput<T>\n : T extends { readonly outputSchema: infer O extends Schema.Top }\n ? O[\"Type\"]\n : Content.Value\ntype FailureOf<T> = T extends CoreTool\n ? Tool.ToolFailure<T>\n : T extends { readonly failureSchema: infer F extends Schema.Top }\n ? F[\"Type\"]\n : never\ntype ToolOf<T> = T extends ToolSpec\n ? {\n readonly name: T[\"name\"]\n readonly input: InputOf<T[\"inputSchema\"]>\n readonly output: OutputOf<T>\n readonly failure: FailureOf<T> | ToolOutput.CoreError\n }\n : never\ntype ElementOf<T> = T extends ReadonlyArray<infer E> ? E : never\ntype NamedOf<M> = {\n readonly [K in keyof M & string]: {\n readonly name: K\n readonly value: M[K] extends Schema.Top ? M[K][\"Type\"] : never\n }\n}[keyof M & string]\n/** The payload types a spec describes: its listed names only. */\nexport type PayloadsOf<S extends Spec> = {\n readonly tool: ToolOf<ElementOf<S[\"tools\"]>>\n readonly object: NamedOf<S[\"objects\"]>\n readonly data: NamedOf<S[\"data\"]>\n readonly live: NamedOf<S[\"live\"]>\n readonly metadata: S[\"metadata\"] extends Schema.Top ? S[\"metadata\"][\"Type\"] : Json\n}\n\ntype NameOf<T> = T extends { readonly name: infer N } ? N : never\ntype ValueOf<T, N> =\n Extract<T, { readonly name: N }> extends { readonly value: infer V } ? V : never\ntype ToolField<T, N, K extends \"output\" | \"failure\"> =\n Extract<T, { readonly name: N }> extends infer X extends ToolPayload ? X[K] : never\n\n/** The call an app closes itself: its id and the tool's name. */\nexport type Call<N extends string = string> = { readonly call_id: string; readonly name: N }\n\n/**\n * A contract's payload types, for the generic folds:\n * `Thread.presetFor<UiContract.Payloads<typeof ChatUi>>()`.\n */\nexport type Payloads<C> = C extends UiContract<infer P> ? P : never\n\nexport interface UiContract<P extends AnyPayloads> {\n /** Decode a wire envelope (as `Client` reads it) into a typed one. */\n decode(envelope: Envelope): Effect.Effect<Envelope<P>, Schema.SchemaError>\n /** Typed envelope ⇄ wire envelope. */\n readonly Envelope: Schema.Codec<Envelope<P>, Envelope>\n /** A typed run view ⇄ the untyped one, for persisting or a framework's model. */\n readonly RunView: Schema.Codec<RunView.RunView<P>, RunView.RunView>\n /** A typed `Thread` ⇄ the untyped one. */\n readonly Thread: Schema.Codec<Thread.Thread<P>, Thread.Thread>\n /** Server: a `data` event with its value encoded for the wire. */\n data<N extends NameOf<P[\"data\"]>>(\n name: N,\n value: ValueOf<P[\"data\"], N>,\n options?: { readonly id?: string },\n ): Effect.Effect<RunEvent, Schema.SchemaError>\n /** Server: a `live` event with its value encoded for the wire. */\n live<N extends NameOf<P[\"live\"]>>(\n name: N,\n value: ValueOf<P[\"live\"], N>,\n ): Effect.Effect<RunEvent, Schema.SchemaError>\n /** Server: run metadata encoded for `run-completed`. */\n metadata(value: P[\"metadata\"]): Effect.Effect<Json, Schema.SchemaError>\n /** Server: a toolkit event (`Toolkit.run`) encoded with the tool's schemas. */\n toolEvent(event: ToolEvent.ToolEvent): Effect.Effect<RunEvent, Schema.SchemaError>\n /** Server: a history item (already encoded) as the `tool-result` of tool `name`. */\n toolOutput(item: Items.ToolCallOutput, name: string): Effect.Effect<RunEvent, Schema.SchemaError>\n /** Server: close a call with a value of its tool's output (e.g. supplied by the user). */\n success<N extends NameOf<P[\"tool\"]>>(\n call: Call<N>,\n value: ToolField<P[\"tool\"], N, \"output\">,\n ): Effect.Effect<RunEvent, Schema.SchemaError>\n /** Server: close a call with an error (e.g. the app's `Denied`). */\n failure<N extends NameOf<P[\"tool\"]>>(\n call: Call<N>,\n error: ToolField<P[\"tool\"], N, \"failure\">,\n ): Effect.Effect<RunEvent, Schema.SchemaError>\n}\n\n// ---------------------------------------------------------------------------\n// Runtime: one schema member per listed name, plus a fallback for the rest\n// ---------------------------------------------------------------------------\n\nconst json = <S extends Schema.Top>(schema: S) => Schema.toCodecJson(schema)\nconst listed = (name: string) => Schema.Literal(name)\nconst unlisted = (names: ReadonlyArray<string>) =>\n Schema.String.check(Schema.makeFilter((name: string) => !names.includes(name)))\n\n/** A payload codec: decodes from JSON without services. */\ntype PayloadCodec = Schema.Codec<unknown, unknown, never, never>\n\n// A core `Tool` built from an Effect Schema keeps it (`fromEffectSchema`), so it decodes too.\nconst isCodec = (schema: ToolSpec[\"inputSchema\"]): schema is AnyCodec => Schema.isSchema(schema)\n\n// Effect Schemas decode the model's JSON; other Standard Schemas validate it as is.\nconst inputCodec = (tool: ToolSpec): PayloadCodec => {\n const schema = tool.inputSchema\n if (isCodec(schema)) return json(schema)\n return Schema.declare((u: unknown): u is Json => {\n const result = schema[\"~standard\"].validate(u)\n return !(result instanceof Promise) && result.issues === undefined\n })\n}\n// \"Anything\": a tool typed `Content.Value`, or a name the contract doesn't list.\nconst isAnything = (tool: ToolSpec | undefined): boolean =>\n tool?.outputSchema === undefined || tool.outputSchema === Content.Value\n\n// The tool's own schemas, as `Toolkit.output` uses them: they encode to JSON, so\n// the UI and history agree by construction (no `toCodecJson` on top). \"Anything\"\n// travels as typed parts, so JSON can't be mistaken for media.\nconst outputCodec = (tool: ToolSpec | undefined): PayloadCodec =>\n isAnything(tool) ? ToolOutput.ValueParts : (tool?.outputSchema ?? ToolOutput.ValueParts)\n\nconst encodeParts = Schema.encodeEffect(Schema.Array(ToolOutput.PartJson))\n\nconst failureCodec = (tool: ToolSpec | undefined): PayloadCodec =>\n tool?.failureSchema === undefined\n ? ToolOutput.CoreError\n : Schema.Union([tool.failureSchema, ToolOutput.CoreError])\n\nconst entries = (record: Readonly<Record<string, AnyCodec>> | undefined) =>\n Object.entries(record ?? {})\n\n/** Encoded loosely; `make`'s signature carries the types. */\ntype Loose = {\n decode(envelope: Envelope): Effect.Effect<unknown, Schema.SchemaError>\n readonly Envelope: Schema.Top\n readonly RunView: Schema.Top\n readonly Thread: Schema.Top\n data(\n name: string,\n value: unknown,\n options?: { readonly id?: string },\n ): Effect.Effect<RunEvent, Schema.SchemaError>\n live(name: string, value: unknown): Effect.Effect<RunEvent, Schema.SchemaError>\n metadata(value: unknown): Effect.Effect<Json, Schema.SchemaError>\n toolEvent(event: ToolEvent.ToolEvent): Effect.Effect<RunEvent, Schema.SchemaError>\n toolOutput(item: Items.ToolCallOutput, name: string): Effect.Effect<RunEvent, Schema.SchemaError>\n success(call: Call, value: unknown): Effect.Effect<RunEvent, Schema.SchemaError>\n failure(call: Call, error: unknown): Effect.Effect<RunEvent, Schema.SchemaError>\n}\n\nconst toJson = Schema.decodeUnknownEffect(Schema.Json)\nconst toOutcome = Schema.decodeUnknownEffect(Event.Outcome)\n\n/**\n * Build a contract. Tool names must be literal types: pass core `Tool`s or\n * specs declared `as const`.\n */\nexport function make<const S extends Spec>(spec: S): UiContract<PayloadsOf<S>>\nexport function make(spec: Spec): Loose {\n const tools = spec.tools ?? []\n const objects = entries(spec.objects)\n const data = entries(spec.data)\n const live = entries(spec.live)\n const toolNames = tools.map((t) => t.name)\n const metadata = spec.metadata === undefined ? Schema.Json : json(spec.metadata)\n const resultCodec = (tool: ToolSpec | undefined) =>\n Event.outcome(outputCodec(tool), failureCodec(tool))\n // Built once per tool; an unlisted name falls back to the tool-less codecs.\n const codecsOf = (tool: ToolSpec | undefined) => ({\n output: outputCodec(tool),\n result: resultCodec(tool),\n anything: isAnything(tool),\n })\n const unlistedCodecs = codecsOf(undefined)\n const codecsByName = HashMap.fromIterable(tools.map((t) => [t.name, codecsOf(t)] as const))\n const codecs = (name: string) =>\n Option.getOrElse(HashMap.get(codecsByName, name), () => unlistedCodecs)\n\n const interrupt = Schema.Union([\n ...tools.map((t) =>\n Schema.Struct({ ...Event.Interrupt.fields, name: listed(t.name), input: inputCodec(t) }),\n ),\n Schema.Struct({ ...Event.Interrupt.fields, name: unlisted(toolNames) }),\n ])\n\n const toolInput = (event: typeof Event.ToolCallReady | typeof Event.ToolApprovalRequested) =>\n Schema.Union([\n ...tools.map((t) =>\n Schema.Struct({ ...event.fields, name: listed(t.name), input: inputCodec(t) }),\n ),\n Schema.Struct({ ...event.fields, name: unlisted(toolNames) }),\n ])\n\n const named = (\n event: typeof Event.Data | typeof Event.Live,\n members: ReadonlyArray<readonly [string, AnyCodec]>,\n ) =>\n Schema.Union([\n ...members.map(([name, schema]) =>\n Schema.Struct({ ...event.fields, name: listed(name), value: json(schema) }),\n ),\n Schema.Struct({ ...event.fields, name: unlisted(members.map(([name]) => name)) }),\n ])\n\n const event = Schema.Union([\n ...Event.plainEvents.map((e) => Schema.toType(e)),\n Schema.Struct({ ...Event.RunCompleted.fields, metadata: Schema.optional(metadata) }),\n Schema.Struct({ ...Event.RunInterrupted.fields, interrupts: Schema.Array(interrupt) }),\n Schema.Union([\n ...objects.map(([name, schema]) =>\n Schema.Struct({ ...Event.ObjectDone.fields, name: listed(name), value: json(schema) }),\n ),\n Schema.Struct({\n ...Event.ObjectDone.fields,\n name: Schema.optional(unlisted(objects.map(([name]) => name))),\n }),\n ]),\n toolInput(Event.ToolCallReady),\n toolInput(Event.ToolApprovalRequested),\n Schema.Union([\n ...tools.map((t) =>\n Schema.Struct({ ...Event.ToolPartial.fields, name: listed(t.name), value: outputCodec(t) }),\n ),\n Schema.Struct({ ...Event.ToolPartial.fields, name: unlisted(toolNames) }),\n ]),\n Schema.Union([\n ...tools.map((t) =>\n Schema.Struct({ ...Event.ToolResult.fields, name: listed(t.name), result: resultCodec(t) }),\n ),\n Schema.Struct({ ...Event.ToolResult.fields, name: unlisted(toolNames) }),\n ]),\n named(Event.Data, data),\n named(Event.Live, live),\n ])\n const envelope = Schema.Struct({ ...Event.envelopeFields, event })\n\n const view = RunView.schema({\n block: Schema.Union([\n ...RunView.contentBlocks,\n ...tools.map((t) =>\n RunView.toolBlock(listed(t.name), inputCodec(t), outputCodec(t), failureCodec(t)),\n ),\n RunView.toolBlock(unlisted(toolNames), Schema.Json, Schema.Json, Schema.Json),\n ...objects.map(([name, schema]) => RunView.objectBlock(listed(name), json(schema))),\n RunView.objectBlock(unlisted(objects.map(([name]) => name)), Schema.Json),\n RunView.unnamedObject,\n ...data.map(([name, schema]) => RunView.dataBlock(listed(name), json(schema))),\n RunView.dataBlock(unlisted(data.map(([name]) => name)), Schema.Json),\n ]),\n interrupt,\n // Unlisted live values are transient and untyped: the schema keeps listed ones only.\n live: Schema.Struct(\n Object.fromEntries(live.map(([name, schema]) => [name, Schema.optionalKey(json(schema))])),\n ),\n metadata,\n })\n\n const codecOf = (members: ReadonlyArray<readonly [string, AnyCodec]>, name: string) =>\n members.find(([n]) => n === name)?.[1]\n const encode = (schema: AnyCodec | undefined, value: unknown) =>\n Schema.encodeUnknownEffect(schema === undefined ? Schema.Json : json(schema))(value).pipe(\n Effect.flatMap(toJson),\n )\n const toolResult = (call: Call, outcome: Event.Outcome<unknown, unknown>) =>\n Schema.encodeUnknownEffect(codecs(call.name).result)(outcome).pipe(\n Effect.flatMap(toOutcome),\n Effect.map((result): RunEvent => ({\n type: \"tool-result\",\n callId: call.call_id,\n name: call.name,\n result,\n })),\n )\n\n return {\n decode: Schema.decodeUnknownEffect(envelope),\n Envelope: envelope,\n RunView: view,\n Thread: Thread.State(Thread.UserContent, view, view),\n data: (name, value, options) =>\n encode(codecOf(data, name), value).pipe(\n Effect.map((encoded): RunEvent =>\n options?.id === undefined\n ? { type: \"data\", name, value: encoded }\n : { type: \"data\", name, value: encoded, id: options.id },\n ),\n ),\n live: (name, value) =>\n encode(codecOf(live, name), value).pipe(\n Effect.map((encoded): RunEvent => ({ type: \"live\", name, value: encoded })),\n ),\n metadata: (value) => encode(spec.metadata, value),\n toolEvent: (event) =>\n Match.value(event).pipe(\n Match.tagsExhaustive({\n Partial: (e) =>\n Schema.encodeUnknownEffect(codecs(e.tool).output)(e.value).pipe(\n Effect.flatMap(toJson),\n Effect.map((value): RunEvent => ({\n type: \"tool-partial\",\n callId: e.call_id,\n name: e.tool,\n value,\n })),\n ),\n Output: (e) =>\n toolResult(\n { call_id: e.call_id, name: e.tool },\n Result.match(e.result, {\n onSuccess: (value): Event.Outcome<unknown, unknown> => ({ _tag: \"Success\", value }),\n onFailure: (error): Event.Outcome<unknown, unknown> => ({ _tag: \"Failure\", error }),\n }),\n ),\n }),\n ),\n // The item holds the encoded value: \"anything\" goes as its parts, the rest as the JSON they encode.\n toolOutput: (item, name) =>\n Match.value(item).pipe(\n Match.when({ isError: true }, (o) =>\n Effect.map(ToolOutput.encoded(o.output), (error) => ({ _tag: \"Failure\", error })),\n ),\n Match.orElse((o) =>\n Effect.map(\n codecs(name).anything ? encodeParts(o.output) : ToolOutput.encoded(o.output),\n (value) => ({ _tag: \"Success\", value }),\n ),\n ),\n Effect.flatMap(toOutcome),\n Effect.map((result): RunEvent => ({\n type: \"tool-result\",\n callId: item.call_id,\n name,\n result,\n })),\n ),\n success: (call, value) => toolResult(call, { _tag: \"Success\", value }),\n failure: (call, error) => toolResult(call, { _tag: \"Failure\", error }),\n }\n}\n"],"mappings":"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"}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { t as RunEvent_d_exports } from "./RunEvent.mjs";
|
|
2
|
+
import { s as Input_d_exports } from "./Input-Cz-Kxusm.mjs";
|
|
3
|
+
import { u as RunView_d_exports } from "./RunView-nHfJwN8t.mjs";
|
|
4
|
+
import { l as Thread_d_exports } from "./Thread-CAXsjhpX.mjs";
|
|
5
|
+
import { n as Client_d_exports } from "./Client-C9UYN_VK.mjs";
|
|
6
|
+
import { t as Transport_d_exports } from "./Transport.mjs";
|
|
7
|
+
import { t as UiContract_d_exports } from "./UiContract.mjs";
|
|
8
|
+
import { t as Atoms_d_exports } from "./Atoms.mjs";
|
|
9
|
+
import { t as Projection_d_exports } from "./Projection.mjs";
|
|
10
|
+
import { t as Run_d_exports } from "./Run.mjs";
|
|
11
|
+
import { t as SseEncoding_d_exports } from "./SseEncoding.mjs";
|
|
12
|
+
export { Atoms_d_exports as Atoms, Client_d_exports as Client, Input_d_exports as Input, Projection_d_exports as Projection, Run_d_exports as Run, RunEvent_d_exports as RunEvent, RunView_d_exports as RunView, SseEncoding_d_exports as SseEncoding, Thread_d_exports as Thread, Transport_d_exports as Transport, UiContract_d_exports as UiContract };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { t as RunEvent_exports } from "./RunEvent.mjs";
|
|
2
|
+
import { t as Input_exports } from "./Input.mjs";
|
|
3
|
+
import { t as RunView_exports } from "./RunView.mjs";
|
|
4
|
+
import { t as Thread_exports } from "./Thread.mjs";
|
|
5
|
+
import { t as Atoms_exports } from "./Atoms.mjs";
|
|
6
|
+
import { t as SseEncoding_exports } from "./SseEncoding.mjs";
|
|
7
|
+
import { t as Client_exports } from "./Client.mjs";
|
|
8
|
+
import { t as Projection_exports } from "./Projection.mjs";
|
|
9
|
+
import { t as Run_exports } from "./Run.mjs";
|
|
10
|
+
import { t as Transport_exports } from "./Transport.mjs";
|
|
11
|
+
import { t as UiContract_exports } from "./UiContract.mjs";
|
|
12
|
+
export { Atoms_exports as Atoms, Client_exports as Client, Input_exports as Input, Projection_exports as Projection, Run_exports as Run, RunEvent_exports as RunEvent, RunView_exports as RunView, SseEncoding_exports as SseEncoding, Thread_exports as Thread, Transport_exports as Transport, UiContract_exports as UiContract };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region \0rolldown/runtime.js
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __exportAll = (all, no_symbols) => {
|
|
4
|
+
let target = {};
|
|
5
|
+
for (var name in all) __defProp(target, name, {
|
|
6
|
+
get: all[name],
|
|
7
|
+
enumerable: true
|
|
8
|
+
});
|
|
9
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
10
|
+
return target;
|
|
11
|
+
};
|
|
12
|
+
//#endregion
|
|
13
|
+
export { __exportAll as t };
|
package/package.json
CHANGED
|
@@ -1,6 +1,102 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effect-uai/ui",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.19.0-dev-20261109183959",
|
|
4
|
+
"description": "Agentic UI primitives for @effect-uai/core: a resumable run-event protocol, projection, client folds and Effect atoms.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"agents",
|
|
7
|
+
"ai",
|
|
8
|
+
"chat",
|
|
9
|
+
"effect",
|
|
10
|
+
"sse",
|
|
11
|
+
"streaming",
|
|
12
|
+
"ui"
|
|
13
|
+
],
|
|
14
|
+
"homepage": "https://github.com/betalyra/effect-uai/tree/main/packages/ui",
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/betalyra/effect-uai/issues"
|
|
17
|
+
},
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"author": "Betalyra",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "https://github.com/betalyra/effect-uai",
|
|
23
|
+
"directory": "packages/ui"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"src",
|
|
28
|
+
"README.md",
|
|
29
|
+
"LICENSE"
|
|
30
|
+
],
|
|
31
|
+
"type": "module",
|
|
32
|
+
"main": "./dist/index.mjs",
|
|
33
|
+
"types": "./dist/index.d.mts",
|
|
34
|
+
"exports": {
|
|
35
|
+
".": {
|
|
36
|
+
"types": "./dist/index.d.mts",
|
|
37
|
+
"import": "./dist/index.mjs"
|
|
38
|
+
},
|
|
39
|
+
"./RunEvent": {
|
|
40
|
+
"types": "./dist/RunEvent.d.mts",
|
|
41
|
+
"import": "./dist/RunEvent.mjs"
|
|
42
|
+
},
|
|
43
|
+
"./Input": {
|
|
44
|
+
"types": "./dist/Input.d.mts",
|
|
45
|
+
"import": "./dist/Input.mjs"
|
|
46
|
+
},
|
|
47
|
+
"./Projection": {
|
|
48
|
+
"types": "./dist/Projection.d.mts",
|
|
49
|
+
"import": "./dist/Projection.mjs"
|
|
50
|
+
},
|
|
51
|
+
"./Run": {
|
|
52
|
+
"types": "./dist/Run.d.mts",
|
|
53
|
+
"import": "./dist/Run.mjs"
|
|
54
|
+
},
|
|
55
|
+
"./RunView": {
|
|
56
|
+
"types": "./dist/RunView.d.mts",
|
|
57
|
+
"import": "./dist/RunView.mjs"
|
|
58
|
+
},
|
|
59
|
+
"./Thread": {
|
|
60
|
+
"types": "./dist/Thread.d.mts",
|
|
61
|
+
"import": "./dist/Thread.mjs"
|
|
62
|
+
},
|
|
63
|
+
"./UiContract": {
|
|
64
|
+
"types": "./dist/UiContract.d.mts",
|
|
65
|
+
"import": "./dist/UiContract.mjs"
|
|
66
|
+
},
|
|
67
|
+
"./SseEncoding": {
|
|
68
|
+
"types": "./dist/SseEncoding.d.mts",
|
|
69
|
+
"import": "./dist/SseEncoding.mjs"
|
|
70
|
+
},
|
|
71
|
+
"./Client": {
|
|
72
|
+
"types": "./dist/Client.d.mts",
|
|
73
|
+
"import": "./dist/Client.mjs"
|
|
74
|
+
},
|
|
75
|
+
"./Transport": {
|
|
76
|
+
"types": "./dist/Transport.d.mts",
|
|
77
|
+
"import": "./dist/Transport.mjs"
|
|
78
|
+
},
|
|
79
|
+
"./Atoms": {
|
|
80
|
+
"types": "./dist/Atoms.d.mts",
|
|
81
|
+
"import": "./dist/Atoms.mjs"
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
"publishConfig": {
|
|
85
|
+
"access": "public"
|
|
86
|
+
},
|
|
87
|
+
"dependencies": {},
|
|
88
|
+
"devDependencies": {
|
|
89
|
+
"@effect/vitest": "4.1.2",
|
|
90
|
+
"effect": "4.1.2",
|
|
91
|
+
"typescript": "^7.0.2",
|
|
92
|
+
"@effect-uai/core": "0.19.0-dev-20261109183959"
|
|
93
|
+
},
|
|
94
|
+
"peerDependencies": {
|
|
95
|
+
"@effect-uai/core": "0.19.0-dev-20261109183959",
|
|
96
|
+
"effect": ">=4.0.1 <5.0.0"
|
|
97
|
+
},
|
|
98
|
+
"scripts": {
|
|
99
|
+
"build": "tsdown",
|
|
100
|
+
"typecheck": "tsc --noEmit"
|
|
101
|
+
}
|
|
6
102
|
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { describe, it } from "@effect/vitest"
|
|
2
|
+
import { Effect, Fiber, Option, PubSub, Ref, Stream } from "effect"
|
|
3
|
+
import { SecureRandom } from "effect/crypto"
|
|
4
|
+
import { Atom, AtomRegistry } from "effect/reactivity"
|
|
5
|
+
import { expect } from "vitest"
|
|
6
|
+
import * as Atoms from "./Atoms.js"
|
|
7
|
+
import * as Input from "./Input.js"
|
|
8
|
+
import * as Run from "./Run.js"
|
|
9
|
+
import { type Envelope, type InputId, RunId, type RunEvent, StreamId } from "./RunEvent.js"
|
|
10
|
+
import type * as Thread from "./Thread.js"
|
|
11
|
+
import * as Transport from "./Transport.js"
|
|
12
|
+
|
|
13
|
+
const runtime = Atom.runtime(SecureRandom.layerWebCrypto)
|
|
14
|
+
|
|
15
|
+
const reply = (inputId: InputId): ReadonlyArray<RunEvent> => [
|
|
16
|
+
{ type: "run-started", runId: RunId.make("r1"), inputs: [inputId], protocol: 2 },
|
|
17
|
+
{ type: "text-delta", text: "Hello" },
|
|
18
|
+
{ type: "run-completed" },
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
const finished = (registry: AtomRegistry.AtomRegistry, atom: Atom.Atom<any>) =>
|
|
22
|
+
AtomRegistry.getResult(registry, atom, { suspendOnWaiting: true })
|
|
23
|
+
|
|
24
|
+
const streaming = (
|
|
25
|
+
registry: AtomRegistry.AtomRegistry,
|
|
26
|
+
chat: { readonly state: Atom.Atom<Thread.Thread> },
|
|
27
|
+
) =>
|
|
28
|
+
AtomRegistry.toStream(registry, chat.state).pipe(
|
|
29
|
+
Stream.filter((t) => t.active?.status === "streaming"),
|
|
30
|
+
Stream.runHead,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
describe("Atoms.thread", () => {
|
|
34
|
+
it.effect(
|
|
35
|
+
"submit folds the run into the thread and settles it, observed only through state",
|
|
36
|
+
() =>
|
|
37
|
+
Effect.gen(function* () {
|
|
38
|
+
const chat = Atoms.thread({
|
|
39
|
+
runtime,
|
|
40
|
+
busy: "block",
|
|
41
|
+
// Async frames: the run outlives the tick `submit` was called in.
|
|
42
|
+
transport: Transport.local((input: Input.Sent) =>
|
|
43
|
+
Stream.fromIterable(reply(input.id)).pipe(Stream.tap(() => Effect.yieldNow)),
|
|
44
|
+
),
|
|
45
|
+
})
|
|
46
|
+
const registry = AtomRegistry.make()
|
|
47
|
+
const settled = yield* AtomRegistry.toStream(registry, chat.state).pipe(
|
|
48
|
+
Stream.filter((t) => t.messages.length === 2),
|
|
49
|
+
Stream.runHead,
|
|
50
|
+
Effect.forkChild,
|
|
51
|
+
)
|
|
52
|
+
registry.set(chat.submit, Input.submit("Hi"))
|
|
53
|
+
const thread = yield* Fiber.join(settled).pipe(Effect.map(Option.getOrThrow))
|
|
54
|
+
|
|
55
|
+
expect(thread.active).toBeNull()
|
|
56
|
+
const assistant = thread.messages[1]
|
|
57
|
+
expect(assistant?.role === "assistant" && assistant.value.status).toBe("completed")
|
|
58
|
+
}),
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
it.effect("stop interrupts a direct run and settles it locally as stopped", () =>
|
|
62
|
+
Effect.gen(function* () {
|
|
63
|
+
const chat = Atoms.thread({
|
|
64
|
+
runtime,
|
|
65
|
+
busy: "block",
|
|
66
|
+
transport: Transport.local((input: Input.Sent) =>
|
|
67
|
+
Stream.fromIterable(reply(input.id).slice(0, 2)).pipe(Stream.concat(Stream.never)),
|
|
68
|
+
),
|
|
69
|
+
})
|
|
70
|
+
const registry = AtomRegistry.make()
|
|
71
|
+
registry.set(chat.submit, Input.submit("Hi"))
|
|
72
|
+
yield* streaming(registry, chat)
|
|
73
|
+
registry.set(chat.stop, undefined)
|
|
74
|
+
yield* finished(registry, chat.stop)
|
|
75
|
+
|
|
76
|
+
const thread = registry.get(chat.state)
|
|
77
|
+
const assistant = thread.messages[1]
|
|
78
|
+
expect(thread.active).toBeNull()
|
|
79
|
+
expect(assistant?.role === "assistant" && assistant.value.status).toBe("stopped")
|
|
80
|
+
}),
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
it.effect('busy "block" ignores input while a run is active', () =>
|
|
84
|
+
Effect.gen(function* () {
|
|
85
|
+
const chat = Atoms.thread({
|
|
86
|
+
runtime,
|
|
87
|
+
busy: "block",
|
|
88
|
+
transport: Transport.local((input: Input.Sent) =>
|
|
89
|
+
Stream.fromIterable(reply(input.id).slice(0, 2)).pipe(Stream.concat(Stream.never)),
|
|
90
|
+
),
|
|
91
|
+
})
|
|
92
|
+
const registry = AtomRegistry.make()
|
|
93
|
+
registry.set(chat.submit, Input.submit("first"))
|
|
94
|
+
yield* streaming(registry, chat)
|
|
95
|
+
registry.set(chat.submit, Input.submit("second"))
|
|
96
|
+
yield* finished(registry, chat.submit)
|
|
97
|
+
|
|
98
|
+
const thread = registry.get(chat.state)
|
|
99
|
+
expect(
|
|
100
|
+
thread.messages.flatMap((m) => (m.role === "user" ? [Input.text(m.value)] : [])),
|
|
101
|
+
).toEqual(["first"])
|
|
102
|
+
expect(thread.active?.status).toBe("streaming")
|
|
103
|
+
}),
|
|
104
|
+
)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
// An in-memory session server: every run is appended to one replayed stream.
|
|
108
|
+
const fakeSession = Effect.gen(function* () {
|
|
109
|
+
const chatId = StreamId.make("chat_1")
|
|
110
|
+
const events = yield* PubSub.unbounded<Envelope>({ replay: 1_000 })
|
|
111
|
+
const last = yield* Ref.make(0)
|
|
112
|
+
const transport = Transport.session({
|
|
113
|
+
send: (_: Option.Option<StreamId>, input: Input.Sent) =>
|
|
114
|
+
Ref.get(last).pipe(
|
|
115
|
+
Effect.flatMap((from) =>
|
|
116
|
+
Stream.fromIterable(reply(input.id)).pipe(
|
|
117
|
+
Run.sequence(chatId, { from }),
|
|
118
|
+
Stream.runForEach((envelope) =>
|
|
119
|
+
PubSub.publish(events, envelope).pipe(Effect.andThen(Ref.set(last, envelope.seq))),
|
|
120
|
+
),
|
|
121
|
+
),
|
|
122
|
+
),
|
|
123
|
+
Effect.as(chatId),
|
|
124
|
+
),
|
|
125
|
+
events: (_, after) => Stream.fromPubSub(events).pipe(Stream.filter((e) => e.seq > after)),
|
|
126
|
+
})
|
|
127
|
+
return { chatId, transport }
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
const settledCount = (
|
|
131
|
+
registry: AtomRegistry.AtomRegistry,
|
|
132
|
+
state: Atom.Atom<Thread.Thread>,
|
|
133
|
+
count: number,
|
|
134
|
+
) =>
|
|
135
|
+
AtomRegistry.toStream(registry, state).pipe(
|
|
136
|
+
Stream.filter((t) => t.active === null && t.messages.length === count),
|
|
137
|
+
Stream.runHead,
|
|
138
|
+
Effect.map(Option.getOrThrow),
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
describe("Atoms.sessionThread", () => {
|
|
142
|
+
it.effect("runs of one session land in one thread, and a new client re-opens all of them", () =>
|
|
143
|
+
Effect.gen(function* () {
|
|
144
|
+
const { chatId, transport } = yield* fakeSession
|
|
145
|
+
const chat = Atoms.sessionThread({ runtime, transport })
|
|
146
|
+
const registry = AtomRegistry.make()
|
|
147
|
+
|
|
148
|
+
registry.set(chat.submit, Input.submit("first"))
|
|
149
|
+
yield* settledCount(registry, chat.state, 2)
|
|
150
|
+
registry.set(chat.submit, Input.submit("second"))
|
|
151
|
+
const thread = yield* settledCount(registry, chat.state, 4)
|
|
152
|
+
expect(thread.messages.map((m) => m.role)).toEqual(["user", "assistant", "user", "assistant"])
|
|
153
|
+
expect(registry.get(chat.streamId)).toBe(chatId)
|
|
154
|
+
|
|
155
|
+
// After a reload: the same session, replayed from the start.
|
|
156
|
+
const reloaded = Atoms.sessionThread({ runtime, transport })
|
|
157
|
+
const fresh = AtomRegistry.make()
|
|
158
|
+
fresh.set(reloaded.open, chatId)
|
|
159
|
+
const replayed = yield* settledCount(fresh, reloaded.state, 2)
|
|
160
|
+
expect(replayed.messages.map((m) => m.role)).toEqual(["assistant", "assistant"])
|
|
161
|
+
}),
|
|
162
|
+
)
|
|
163
|
+
})
|