effect-assistant 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +22 -2
- package/dist/AppEvent.d.ts +111 -0
- package/dist/AppEvent.d.ts.map +1 -0
- package/dist/AppEvent.js +71 -0
- package/dist/AppEvent.js.map +1 -0
- package/dist/Assistant.d.ts +403 -0
- package/dist/Assistant.d.ts.map +1 -0
- package/dist/Assistant.js +126 -0
- package/dist/Assistant.js.map +1 -0
- package/dist/AssistantActivity.d.ts +85 -0
- package/dist/AssistantActivity.d.ts.map +1 -0
- package/dist/AssistantActivity.js +105 -0
- package/dist/AssistantActivity.js.map +1 -0
- package/dist/AssistantEngine.d.ts +116 -0
- package/dist/AssistantEngine.d.ts.map +1 -0
- package/dist/AssistantEngine.js +255 -0
- package/dist/AssistantEngine.js.map +1 -0
- package/dist/AssistantEvent.d.ts +551 -0
- package/dist/AssistantEvent.d.ts.map +1 -0
- package/dist/AssistantEvent.js +473 -0
- package/dist/AssistantEvent.js.map +1 -0
- package/dist/AssistantLoop.d.ts +173 -0
- package/dist/AssistantLoop.d.ts.map +1 -0
- package/dist/AssistantLoop.js +111 -0
- package/dist/AssistantLoop.js.map +1 -0
- package/dist/AssistantToolCall.d.ts +32 -0
- package/dist/AssistantToolCall.d.ts.map +1 -0
- package/dist/AssistantToolCall.js +37 -0
- package/dist/AssistantToolCall.js.map +1 -0
- package/dist/Block.d.ts +69 -0
- package/dist/Block.d.ts.map +1 -0
- package/dist/Block.js +39 -0
- package/dist/Block.js.map +1 -0
- package/dist/BlockKit.d.ts +180 -0
- package/dist/BlockKit.d.ts.map +1 -0
- package/dist/BlockKit.js +148 -0
- package/dist/BlockKit.js.map +1 -0
- package/dist/ChatId.d.ts +31 -0
- package/dist/ChatId.d.ts.map +1 -0
- package/dist/ChatId.js +36 -0
- package/dist/ChatId.js.map +1 -0
- package/dist/ChatLog.d.ts +109 -0
- package/dist/ChatLog.d.ts.map +1 -0
- package/dist/ChatLog.js +177 -0
- package/dist/ChatLog.js.map +1 -0
- package/dist/ChatView.d.ts +408 -0
- package/dist/ChatView.d.ts.map +1 -0
- package/dist/ChatView.js +443 -0
- package/dist/ChatView.js.map +1 -0
- package/dist/Chats.d.ts +110 -0
- package/dist/Chats.d.ts.map +1 -0
- package/dist/Chats.js +98 -0
- package/dist/Chats.js.map +1 -0
- package/dist/Compaction.d.ts +105 -0
- package/dist/Compaction.d.ts.map +1 -0
- package/dist/Compaction.js +126 -0
- package/dist/Compaction.js.map +1 -0
- package/dist/CurrentChat.d.ts +40 -0
- package/dist/CurrentChat.d.ts.map +1 -0
- package/dist/CurrentChat.js +33 -0
- package/dist/CurrentChat.js.map +1 -0
- package/dist/InputId.d.ts +38 -0
- package/dist/InputId.d.ts.map +1 -0
- package/dist/InputId.js +43 -0
- package/dist/InputId.js.map +1 -0
- package/dist/Instructions.d.ts +56 -0
- package/dist/Instructions.d.ts.map +1 -0
- package/dist/Instructions.js +50 -0
- package/dist/Instructions.js.map +1 -0
- package/dist/Memory.d.ts +190 -0
- package/dist/Memory.d.ts.map +1 -0
- package/dist/Memory.js +216 -0
- package/dist/Memory.js.map +1 -0
- package/dist/RunId.d.ts +41 -0
- package/dist/RunId.d.ts.map +1 -0
- package/dist/RunId.js +46 -0
- package/dist/RunId.js.map +1 -0
- package/dist/Skill.d.ts +72 -0
- package/dist/Skill.d.ts.map +1 -0
- package/dist/Skill.js +58 -0
- package/dist/Skill.js.map +1 -0
- package/dist/Specialist.d.ts +267 -0
- package/dist/Specialist.d.ts.map +1 -0
- package/dist/Specialist.js +119 -0
- package/dist/Specialist.js.map +1 -0
- package/dist/ToolDiscovery.d.ts +107 -0
- package/dist/ToolDiscovery.d.ts.map +1 -0
- package/dist/ToolDiscovery.js +140 -0
- package/dist/ToolDiscovery.js.map +1 -0
- package/dist/ToolFailure.d.ts +66 -0
- package/dist/ToolFailure.d.ts.map +1 -0
- package/dist/ToolFailure.js +79 -0
- package/dist/ToolFailure.js.map +1 -0
- package/dist/_virtual/_rolldown/runtime.js +13 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +24 -0
- package/dist/internal/chat.js +177 -0
- package/dist/internal/chat.js.map +1 -0
- package/dist/internal/events.d.ts +46 -0
- package/dist/internal/events.d.ts.map +1 -0
- package/dist/internal/events.js +65 -0
- package/dist/internal/events.js.map +1 -0
- package/dist/internal/history.js +147 -0
- package/dist/internal/history.js.map +1 -0
- package/dist/internal/instructions.js +11 -0
- package/dist/internal/instructions.js.map +1 -0
- package/dist/internal/loop.d.ts +73 -0
- package/dist/internal/loop.d.ts.map +1 -0
- package/dist/internal/loop.js +131 -0
- package/dist/internal/loop.js.map +1 -0
- package/dist/internal/schema.js +14 -0
- package/dist/internal/schema.js.map +1 -0
- package/dist/internal/skills.js +28 -0
- package/dist/internal/skills.js.map +1 -0
- package/dist/internal/specialists.js +272 -0
- package/dist/internal/specialists.js.map +1 -0
- package/dist/internal/telemetry.js +85 -0
- package/dist/internal/telemetry.js.map +1 -0
- package/dist/internal/toolkit.d.ts +62 -0
- package/dist/internal/toolkit.d.ts.map +1 -0
- package/dist/internal/toolkit.js +108 -0
- package/dist/internal/toolkit.js.map +1 -0
- package/dist/internal/tools.js +113 -0
- package/dist/internal/tools.js.map +1 -0
- package/dist/internal/turns.d.ts +64 -0
- package/dist/internal/turns.d.ts.map +1 -0
- package/dist/internal/turns.js +214 -0
- package/dist/internal/turns.js.map +1 -0
- package/dist/internal/uuid.js +45 -0
- package/dist/internal/uuid.js.map +1 -0
- package/dist/internal/writer.d.ts +12 -0
- package/dist/internal/writer.d.ts.map +1 -0
- package/package.json +52 -3
- package/src/AppEvent.ts +134 -0
- package/src/Assistant.ts +589 -0
- package/src/AssistantActivity.ts +150 -0
- package/src/AssistantEngine.ts +343 -0
- package/src/AssistantEvent.ts +569 -0
- package/src/AssistantLoop.ts +209 -0
- package/src/AssistantToolCall.ts +40 -0
- package/src/Block.ts +84 -0
- package/src/BlockKit.ts +298 -0
- package/src/ChatId.ts +36 -0
- package/src/ChatLog.ts +247 -0
- package/src/ChatView.ts +587 -0
- package/src/Chats.ts +122 -0
- package/src/Compaction.ts +207 -0
- package/src/CurrentChat.ts +47 -0
- package/src/InputId.ts +43 -0
- package/src/Instructions.ts +72 -0
- package/src/Memory.ts +276 -0
- package/src/RunId.ts +46 -0
- package/src/Skill.ts +91 -0
- package/src/Specialist.ts +355 -0
- package/src/ToolDiscovery.ts +179 -0
- package/src/ToolFailure.ts +74 -0
- package/src/index.ts +120 -0
- package/src/internal/chat.ts +323 -0
- package/src/internal/events.ts +100 -0
- package/src/internal/history.ts +294 -0
- package/src/internal/instructions.ts +9 -0
- package/src/internal/loop.ts +274 -0
- package/src/internal/schema.ts +25 -0
- package/src/internal/skills.ts +37 -0
- package/src/internal/specialists.ts +410 -0
- package/src/internal/telemetry.ts +125 -0
- package/src/internal/toolkit.ts +188 -0
- package/src/internal/tools.ts +159 -0
- package/src/internal/turns.ts +410 -0
- package/src/internal/uuid.ts +50 -0
- package/src/internal/writer.ts +12 -0
package/dist/ChatView.js
ADDED
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
import { __exportAll } from "./_virtual/_rolldown/runtime.js";
|
|
2
|
+
import { InputId } from "./InputId.js";
|
|
3
|
+
import { Attachment, isLibraryEvent } from "./AssistantEvent.js";
|
|
4
|
+
import { toJson } from "./internal/schema.js";
|
|
5
|
+
import { apply as apply$1, empty as empty$1 } from "./AssistantActivity.js";
|
|
6
|
+
import { apply as apply$2 } from "./BlockKit.js";
|
|
7
|
+
import * as Schema from "effect/Schema";
|
|
8
|
+
import * as Stream from "effect/Stream";
|
|
9
|
+
import { dual } from "effect/Function";
|
|
10
|
+
import * as Match from "effect/Match";
|
|
11
|
+
//#region src/ChatView.ts
|
|
12
|
+
/**
|
|
13
|
+
* This module provides the view of a chat as its user sees it. A chat client receives this view
|
|
14
|
+
* and nothing else.
|
|
15
|
+
*
|
|
16
|
+
* A {@link View} holds the chat's messages with their blocks, the chat's
|
|
17
|
+
* {@link Activity}, and whether the last turn failed or the chat was deleted. It never contains
|
|
18
|
+
* the model's output, tool calls or results, what the model read, the app's own events, or why a
|
|
19
|
+
* turn failed. The server streams {@link Update}s by piping the assistant's `subscribe` through
|
|
20
|
+
* {@link updates}, and the client folds them with {@link apply}, starting from {@link empty}.
|
|
21
|
+
* On the server, {@link fromEntries} builds a whole view from a chat's log.
|
|
22
|
+
*
|
|
23
|
+
* @since 0.1.0
|
|
24
|
+
*/
|
|
25
|
+
var ChatView_exports = /* @__PURE__ */ __exportAll({
|
|
26
|
+
Activity: () => Activity,
|
|
27
|
+
AssistantMessage: () => AssistantMessage,
|
|
28
|
+
Block: () => Block,
|
|
29
|
+
BlockStatus: () => BlockStatus,
|
|
30
|
+
Message: () => Message,
|
|
31
|
+
SpecialistWorking: () => SpecialistWorking,
|
|
32
|
+
Update: () => Update,
|
|
33
|
+
UserMessage: () => UserMessage,
|
|
34
|
+
View: () => View,
|
|
35
|
+
apply: () => apply,
|
|
36
|
+
empty: () => empty,
|
|
37
|
+
fromEntries: () => fromEntries,
|
|
38
|
+
isIdle: () => isIdle,
|
|
39
|
+
updates: () => updates
|
|
40
|
+
});
|
|
41
|
+
/**
|
|
42
|
+
* Schema for the status of a block. A block is `"open"` while it accepts an answer, `"answered"`
|
|
43
|
+
* once the user has used it, and `"closed"` when it closed without an answer because the user
|
|
44
|
+
* answered in words or moved on.
|
|
45
|
+
*
|
|
46
|
+
* @category schemas
|
|
47
|
+
* @since 0.1.0
|
|
48
|
+
*/
|
|
49
|
+
const BlockStatus = Schema.Literals([
|
|
50
|
+
"open",
|
|
51
|
+
"answered",
|
|
52
|
+
"closed"
|
|
53
|
+
]);
|
|
54
|
+
/**
|
|
55
|
+
* Schema for a block on an assistant's message. It holds the block's kind, its props (JSON, as
|
|
56
|
+
* the block's props schema encodes them), its status, and the user's answer (JSON, as the block's
|
|
57
|
+
* answer schema encodes it).
|
|
58
|
+
*
|
|
59
|
+
* @category schemas
|
|
60
|
+
* @since 0.1.0
|
|
61
|
+
*/
|
|
62
|
+
const Block = Schema.Struct({
|
|
63
|
+
kind: Schema.String,
|
|
64
|
+
props: Schema.Json,
|
|
65
|
+
status: BlockStatus,
|
|
66
|
+
answer: Schema.optional(Schema.Json)
|
|
67
|
+
});
|
|
68
|
+
/**
|
|
69
|
+
* Schema for a message the user sent. `at` is when it was stored, in epoch
|
|
70
|
+
* milliseconds.
|
|
71
|
+
*
|
|
72
|
+
* @category schemas
|
|
73
|
+
* @since 0.1.0
|
|
74
|
+
*/
|
|
75
|
+
const UserMessage = Schema.TaggedStruct("UserMessage", {
|
|
76
|
+
id: InputId,
|
|
77
|
+
text: Schema.String,
|
|
78
|
+
attachments: Schema.optional(Schema.Array(Attachment)),
|
|
79
|
+
at: Schema.Finite
|
|
80
|
+
});
|
|
81
|
+
/**
|
|
82
|
+
* Schema for a message the assistant sent the user, including its block if it has one.
|
|
83
|
+
*
|
|
84
|
+
* @category schemas
|
|
85
|
+
* @since 0.1.0
|
|
86
|
+
*/
|
|
87
|
+
const AssistantMessage = Schema.TaggedStruct("AssistantMessage", {
|
|
88
|
+
id: Schema.String,
|
|
89
|
+
text: Schema.String,
|
|
90
|
+
at: Schema.Finite,
|
|
91
|
+
block: Schema.optional(Block)
|
|
92
|
+
});
|
|
93
|
+
/**
|
|
94
|
+
* Schema for a message in the chat, sent by either the user or the assistant.
|
|
95
|
+
*
|
|
96
|
+
* @category schemas
|
|
97
|
+
* @since 0.1.0
|
|
98
|
+
*/
|
|
99
|
+
const Message = Schema.Union([UserMessage, AssistantMessage]);
|
|
100
|
+
/**
|
|
101
|
+
* Schema for a specialist that is working for the assistant. It holds the specialist's name and
|
|
102
|
+
* when it started, in epoch milliseconds.
|
|
103
|
+
*
|
|
104
|
+
* @category schemas
|
|
105
|
+
* @since 0.1.0
|
|
106
|
+
*/
|
|
107
|
+
const SpecialistWorking = Schema.Struct({
|
|
108
|
+
specialist: Schema.String,
|
|
109
|
+
since: Schema.Finite
|
|
110
|
+
});
|
|
111
|
+
/**
|
|
112
|
+
* Schema for what is working in the chat.
|
|
113
|
+
*
|
|
114
|
+
* `working` is true while the assistant runs a turn, or when a turn is about to start for
|
|
115
|
+
* something that arrived. `specialists` lists the specialists that are working. When the chat is
|
|
116
|
+
* idle, nothing is working, even if a workflow is waiting on a timer or on the user.
|
|
117
|
+
*
|
|
118
|
+
* @category schemas
|
|
119
|
+
* @since 0.1.0
|
|
120
|
+
*/
|
|
121
|
+
const Activity = Schema.Struct({
|
|
122
|
+
working: Schema.Boolean,
|
|
123
|
+
specialists: Schema.Array(SpecialistWorking)
|
|
124
|
+
});
|
|
125
|
+
/**
|
|
126
|
+
* Schema for a chat as its user sees it. It holds the chat's messages, what is working in it,
|
|
127
|
+
* whether the last turn failed, and whether the chat was deleted. `seq` is the position in the
|
|
128
|
+
* chat's log that the view is up to.
|
|
129
|
+
*
|
|
130
|
+
* @category schemas
|
|
131
|
+
* @since 0.1.0
|
|
132
|
+
*/
|
|
133
|
+
const View = Schema.Struct({
|
|
134
|
+
seq: Schema.Int,
|
|
135
|
+
messages: Schema.Array(Message),
|
|
136
|
+
activity: Activity,
|
|
137
|
+
/** Whether the last turn failed with no message after it. */
|
|
138
|
+
failed: Schema.Boolean,
|
|
139
|
+
/** Whether the chat was deleted. A deleted chat accepts nothing more, and its stream ends. */
|
|
140
|
+
deleted: Schema.Boolean
|
|
141
|
+
});
|
|
142
|
+
/**
|
|
143
|
+
* Schema for a change to a chat's view.
|
|
144
|
+
*
|
|
145
|
+
* An update holds each message that is new or changed (for example, a block answered or
|
|
146
|
+
* closed), in full. It also holds the current activity, whether the last turn failed,
|
|
147
|
+
* and whether the chat was deleted. `seq` is the position in the chat's log that the update is up
|
|
148
|
+
* to.
|
|
149
|
+
*
|
|
150
|
+
* @category schemas
|
|
151
|
+
* @since 0.1.0
|
|
152
|
+
*/
|
|
153
|
+
const Update = Schema.Struct({
|
|
154
|
+
seq: Schema.Int,
|
|
155
|
+
messages: Schema.Array(Message),
|
|
156
|
+
activity: Activity,
|
|
157
|
+
failed: Schema.Boolean,
|
|
158
|
+
deleted: Schema.Boolean
|
|
159
|
+
});
|
|
160
|
+
/**
|
|
161
|
+
* The view of a chat before anything has happened in it, with no messages and nothing working.
|
|
162
|
+
* Use it as the starting point when folding updates with {@link apply}.
|
|
163
|
+
*
|
|
164
|
+
* @category constructors
|
|
165
|
+
* @since 0.1.0
|
|
166
|
+
*/
|
|
167
|
+
const empty = {
|
|
168
|
+
seq: 0,
|
|
169
|
+
messages: [],
|
|
170
|
+
activity: {
|
|
171
|
+
working: false,
|
|
172
|
+
specialists: []
|
|
173
|
+
},
|
|
174
|
+
failed: false,
|
|
175
|
+
deleted: false
|
|
176
|
+
};
|
|
177
|
+
/**
|
|
178
|
+
* Returns the view after applying an update.
|
|
179
|
+
*
|
|
180
|
+
* **Details**
|
|
181
|
+
*
|
|
182
|
+
* Each message in the update replaces the message with the same id, in place, and new messages
|
|
183
|
+
* are added after the rest. The update's activity, `failed`, and `deleted` replace the view's.
|
|
184
|
+
* Applying an update twice changes nothing, so a client that resubscribes from the start can fold
|
|
185
|
+
* again and get the same view.
|
|
186
|
+
*
|
|
187
|
+
* @category combinators
|
|
188
|
+
* @since 0.1.0
|
|
189
|
+
*/
|
|
190
|
+
const apply = (view, update) => {
|
|
191
|
+
const changed = new Map(update.messages.map((message) => [message.id, message]));
|
|
192
|
+
const known = new Set(view.messages.map((message) => message.id));
|
|
193
|
+
return {
|
|
194
|
+
seq: Math.max(view.seq, update.seq),
|
|
195
|
+
messages: [...view.messages.map((message) => changed.get(message.id) ?? message), ...update.messages.filter((message) => !known.has(message.id))],
|
|
196
|
+
activity: update.activity,
|
|
197
|
+
failed: update.failed,
|
|
198
|
+
deleted: update.deleted
|
|
199
|
+
};
|
|
200
|
+
};
|
|
201
|
+
/**
|
|
202
|
+
* Returns whether nothing is working in the chat: no turn is running or about to start, and no
|
|
203
|
+
* specialist is working.
|
|
204
|
+
*
|
|
205
|
+
* @category guards
|
|
206
|
+
* @since 0.1.0
|
|
207
|
+
*/
|
|
208
|
+
const isIdle = (view) => !view.activity.working && view.activity.specialists.length === 0;
|
|
209
|
+
/**
|
|
210
|
+
* Returns a chat's view, built from the entries of its log.
|
|
211
|
+
*
|
|
212
|
+
* **When to use**
|
|
213
|
+
*
|
|
214
|
+
* Use it in server-side code, for example to return a whole chat at once. A client gets the view
|
|
215
|
+
* as a stream from {@link updates} instead.
|
|
216
|
+
*
|
|
217
|
+
* **Example** (Building a chat's view from its log)
|
|
218
|
+
*
|
|
219
|
+
* ```ts
|
|
220
|
+
* import { type ChatId, ChatLog, ChatView } from "effect-assistant"
|
|
221
|
+
* import * as Effect from "effect/Effect"
|
|
222
|
+
*
|
|
223
|
+
* const viewOf = (chatId: ChatId.ChatId) =>
|
|
224
|
+
* ChatLog.ChatLog.use((log) => log.read(chatId)).pipe(
|
|
225
|
+
* Effect.map((entries) => ChatView.fromEntries(entries))
|
|
226
|
+
* )
|
|
227
|
+
* ```
|
|
228
|
+
*
|
|
229
|
+
* @see {@link updates} for a stream of changes instead of a whole view
|
|
230
|
+
*
|
|
231
|
+
* @category constructors
|
|
232
|
+
* @since 0.1.0
|
|
233
|
+
*/
|
|
234
|
+
const fromEntries = (entries, options) => {
|
|
235
|
+
const fold = folding(options?.wakes ?? /* @__PURE__ */ new Set());
|
|
236
|
+
for (const entry of entries) fold.step(entry);
|
|
237
|
+
return fold.view;
|
|
238
|
+
};
|
|
239
|
+
const activityOf = (activity) => ({
|
|
240
|
+
working: activity.turnSince !== void 0 || activity.queued,
|
|
241
|
+
specialists: [...activity.specialists.values()].map((run) => ({
|
|
242
|
+
specialist: run.specialist,
|
|
243
|
+
since: run.since
|
|
244
|
+
}))
|
|
245
|
+
});
|
|
246
|
+
const sameActivity = (a, b) => a.working === b.working && a.specialists.length === b.specialists.length && a.specialists.every((run, i) => {
|
|
247
|
+
const other = b.specialists[i];
|
|
248
|
+
return other !== void 0 && run.specialist === other.specialist && run.since === other.since;
|
|
249
|
+
});
|
|
250
|
+
const folding = (wakes) => {
|
|
251
|
+
let seq = 0;
|
|
252
|
+
let failed = false;
|
|
253
|
+
let deleted = false;
|
|
254
|
+
let activity = empty$1;
|
|
255
|
+
let blocks = /* @__PURE__ */ new Map();
|
|
256
|
+
const messages = /* @__PURE__ */ new Map();
|
|
257
|
+
const sent = /* @__PURE__ */ new Map();
|
|
258
|
+
const blockOf = (message) => {
|
|
259
|
+
if (message.block === void 0) return void 0;
|
|
260
|
+
const state = blocks.get(message.id);
|
|
261
|
+
const status = state === void 0 || state.open ? "open" : state.answer === void 0 ? "closed" : "answered";
|
|
262
|
+
const block = {
|
|
263
|
+
kind: message.block.kind,
|
|
264
|
+
props: toJson(message.block.props),
|
|
265
|
+
status
|
|
266
|
+
};
|
|
267
|
+
return state?.answer === void 0 ? block : {
|
|
268
|
+
...block,
|
|
269
|
+
answer: toJson(state.answer.value)
|
|
270
|
+
};
|
|
271
|
+
};
|
|
272
|
+
const viewOf = (message, at) => {
|
|
273
|
+
const block = blockOf(message);
|
|
274
|
+
return block === void 0 ? AssistantMessage.make({
|
|
275
|
+
id: message.id,
|
|
276
|
+
text: message.text,
|
|
277
|
+
at
|
|
278
|
+
}) : AssistantMessage.make({
|
|
279
|
+
id: message.id,
|
|
280
|
+
text: message.text,
|
|
281
|
+
at,
|
|
282
|
+
block
|
|
283
|
+
});
|
|
284
|
+
};
|
|
285
|
+
const touched = (event, at) => Match.value(event).pipe(Match.tagsExhaustive({
|
|
286
|
+
UserMessage: (message) => {
|
|
287
|
+
messages.set(message.id, message.attachments === void 0 ? UserMessage.make({
|
|
288
|
+
id: message.id,
|
|
289
|
+
text: message.text,
|
|
290
|
+
at
|
|
291
|
+
}) : UserMessage.make({
|
|
292
|
+
id: message.id,
|
|
293
|
+
text: message.text,
|
|
294
|
+
attachments: message.attachments,
|
|
295
|
+
at
|
|
296
|
+
}));
|
|
297
|
+
failed = false;
|
|
298
|
+
return [message.id];
|
|
299
|
+
},
|
|
300
|
+
AssistantMessage: (message) => {
|
|
301
|
+
sent.set(message.id, message);
|
|
302
|
+
messages.set(message.id, viewOf(message, at));
|
|
303
|
+
failed = false;
|
|
304
|
+
return [message.id, ...message.closes ?? []];
|
|
305
|
+
},
|
|
306
|
+
BlockAnswer: (answer) => {
|
|
307
|
+
failed = false;
|
|
308
|
+
return [answer.messageId];
|
|
309
|
+
},
|
|
310
|
+
BlocksClosed: (closed) => closed.messageIds,
|
|
311
|
+
TurnFailed: () => {
|
|
312
|
+
failed = true;
|
|
313
|
+
return [];
|
|
314
|
+
},
|
|
315
|
+
ModelOutput: () => [],
|
|
316
|
+
ToolStarted: () => [],
|
|
317
|
+
ToolResult: () => [],
|
|
318
|
+
TurnStarted: () => [],
|
|
319
|
+
TurnCompleted: () => [],
|
|
320
|
+
SpecialistStarted: () => [],
|
|
321
|
+
SpecialistFinished: () => [],
|
|
322
|
+
ChatDeleted: () => {
|
|
323
|
+
deleted = true;
|
|
324
|
+
return [];
|
|
325
|
+
}
|
|
326
|
+
}));
|
|
327
|
+
const step = (entry) => {
|
|
328
|
+
const before = {
|
|
329
|
+
activity: activityOf(activity),
|
|
330
|
+
failed,
|
|
331
|
+
deleted
|
|
332
|
+
};
|
|
333
|
+
const { event } = entry;
|
|
334
|
+
seq = entry.seq;
|
|
335
|
+
activity = apply$1(activity, entry, { wakes });
|
|
336
|
+
if (!isLibraryEvent(event)) {
|
|
337
|
+
const now = activityOf(activity);
|
|
338
|
+
return sameActivity(before.activity, now) ? void 0 : {
|
|
339
|
+
seq,
|
|
340
|
+
messages: [],
|
|
341
|
+
activity: now,
|
|
342
|
+
failed,
|
|
343
|
+
deleted
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
blocks = apply$2(blocks, event);
|
|
347
|
+
const changed = [...new Set(touched(event, entry.at))].flatMap((id) => {
|
|
348
|
+
const message = sent.get(id);
|
|
349
|
+
const current = messages.get(id);
|
|
350
|
+
if (message === void 0 || current === void 0) return current === void 0 ? [] : [current];
|
|
351
|
+
const next = viewOf(message, current.at);
|
|
352
|
+
messages.set(id, next);
|
|
353
|
+
return [next];
|
|
354
|
+
});
|
|
355
|
+
const now = activityOf(activity);
|
|
356
|
+
return changed.length === 0 && failed === before.failed && deleted === before.deleted && sameActivity(before.activity, now) ? void 0 : {
|
|
357
|
+
seq,
|
|
358
|
+
messages: changed,
|
|
359
|
+
activity: now,
|
|
360
|
+
failed,
|
|
361
|
+
deleted
|
|
362
|
+
};
|
|
363
|
+
};
|
|
364
|
+
return {
|
|
365
|
+
step,
|
|
366
|
+
get view() {
|
|
367
|
+
return {
|
|
368
|
+
seq,
|
|
369
|
+
messages: [...messages.values()],
|
|
370
|
+
activity: activityOf(activity),
|
|
371
|
+
failed,
|
|
372
|
+
deleted
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
};
|
|
377
|
+
const merge = (updates) => {
|
|
378
|
+
const last = updates.at(-1);
|
|
379
|
+
if (last === void 0) return void 0;
|
|
380
|
+
const messages = /* @__PURE__ */ new Map();
|
|
381
|
+
for (const update of updates) for (const message of update.messages) messages.set(message.id, message);
|
|
382
|
+
return {
|
|
383
|
+
seq: last.seq,
|
|
384
|
+
messages: [...messages.values()],
|
|
385
|
+
activity: last.activity,
|
|
386
|
+
failed: last.failed,
|
|
387
|
+
deleted: last.deleted
|
|
388
|
+
};
|
|
389
|
+
};
|
|
390
|
+
const wakesOf = (definition) => new Set(definition.events.flatMap((event) => event.wakes ? [event.tag] : []));
|
|
391
|
+
/**
|
|
392
|
+
* Returns the stream of view updates that a chat's entries produce, for a chat client.
|
|
393
|
+
*
|
|
394
|
+
* **Details**
|
|
395
|
+
*
|
|
396
|
+
* The entries must stream from the start of the chat's log, as `assistant.subscribe(chatId)` does
|
|
397
|
+
* without `after`. The fold reads every entry, because a message's view depends on earlier
|
|
398
|
+
* entries: a block is answered or closed later. `after` only skips
|
|
399
|
+
* emitting the updates up to that `seq`, so a client that has the view up to a `seq` receives only
|
|
400
|
+
* what changed since. Pass `after` here, not to `subscribe`: a subscription that starts after a
|
|
401
|
+
* `seq` gives the fold an incomplete log, and the updates it produces are wrong.
|
|
402
|
+
*
|
|
403
|
+
* The definition tells the fold which of the app's events wake a turn, so the view shows a turn as
|
|
404
|
+
* about to start after one. Each chunk of entries becomes one update; the stored entries, for
|
|
405
|
+
* example, arrive as one chunk.
|
|
406
|
+
*
|
|
407
|
+
* **Example** (Streaming a chat's view to a client)
|
|
408
|
+
*
|
|
409
|
+
* ```ts
|
|
410
|
+
* import { Assistant, type ChatId, ChatView } from "effect-assistant"
|
|
411
|
+
* import * as Stream from "effect/Stream"
|
|
412
|
+
*
|
|
413
|
+
* const MyAssistant = Assistant.make("my-assistant")
|
|
414
|
+
*
|
|
415
|
+
* const watch = (chatId: ChatId.ChatId, after: number) =>
|
|
416
|
+
* Stream.unwrap(
|
|
417
|
+
* MyAssistant.useSync((assistant) =>
|
|
418
|
+
* assistant.subscribe(chatId).pipe(ChatView.updates(MyAssistant, { after }))
|
|
419
|
+
* )
|
|
420
|
+
* )
|
|
421
|
+
* ```
|
|
422
|
+
*
|
|
423
|
+
* @see {@link apply} for folding the updates into a view on the client
|
|
424
|
+
*
|
|
425
|
+
* @category utils
|
|
426
|
+
* @since 0.1.0
|
|
427
|
+
*/
|
|
428
|
+
const updates = dual((args) => Stream.isStream(args[0]), (entries, definition, options) => {
|
|
429
|
+
const after = options?.after ?? 0;
|
|
430
|
+
const wakes = wakesOf(definition);
|
|
431
|
+
return entries.pipe(Stream.mapAccumArray(() => folding(wakes), (fold, chunk) => {
|
|
432
|
+
const changed = chunk.flatMap((entry) => {
|
|
433
|
+
const update = fold.step(entry);
|
|
434
|
+
return update === void 0 || entry.seq <= after ? [] : [update];
|
|
435
|
+
});
|
|
436
|
+
const merged = merge(changed);
|
|
437
|
+
return [fold, merged === void 0 ? [] : [merged]];
|
|
438
|
+
}));
|
|
439
|
+
});
|
|
440
|
+
//#endregion
|
|
441
|
+
export { Activity, AssistantMessage, Block, BlockStatus, ChatView_exports, Message, SpecialistWorking, Update, UserMessage, View, apply, empty, fromEntries, isIdle, updates };
|
|
442
|
+
|
|
443
|
+
//# sourceMappingURL=ChatView.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChatView.js","names":["AssistantEvent.Attachment","AssistantActivity.empty","AssistantActivity.apply","AssistantEvent.isLibraryEvent","BlockKit.apply"],"sources":["../src/ChatView.ts"],"sourcesContent":["/**\n * This module provides the view of a chat as its user sees it. A chat client receives this view\n * and nothing else.\n *\n * A {@link View} holds the chat's messages with their blocks, the chat's\n * {@link Activity}, and whether the last turn failed or the chat was deleted. It never contains\n * the model's output, tool calls or results, what the model read, the app's own events, or why a\n * turn failed. The server streams {@link Update}s by piping the assistant's `subscribe` through\n * {@link updates}, and the client folds them with {@link apply}, starting from {@link empty}.\n * On the server, {@link fromEntries} builds a whole view from a chat's log.\n *\n * @since 0.1.0\n */\nimport { dual } from \"effect/Function\"\nimport * as Match from \"effect/Match\"\nimport * as Schema from \"effect/Schema\"\nimport * as Stream from \"effect/Stream\"\nimport * as AssistantActivity from \"./AssistantActivity.ts\"\nimport * as AssistantEvent from \"./AssistantEvent.ts\"\nimport * as BlockKit from \"./BlockKit.ts\"\nimport { InputId } from \"./InputId.ts\"\nimport { toJson } from \"./internal/schema.ts\"\n\n/**\n * Schema for the status of a block. A block is `\"open\"` while it accepts an answer, `\"answered\"`\n * once the user has used it, and `\"closed\"` when it closed without an answer because the user\n * answered in words or moved on.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const BlockStatus = Schema.Literals([\"open\", \"answered\", \"closed\"])\n\n/**\n * The status of a block.\n *\n * @category models\n * @since 0.1.0\n */\nexport type BlockStatus = typeof BlockStatus.Type\n\n/**\n * Schema for a block on an assistant's message. It holds the block's kind, its props (JSON, as\n * the block's props schema encodes them), its status, and the user's answer (JSON, as the block's\n * answer schema encodes it).\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const Block = Schema.Struct({\n kind: Schema.String,\n props: Schema.Json,\n status: BlockStatus,\n answer: Schema.optional(Schema.Json)\n})\n\n/**\n * Represents a block on an assistant's message.\n *\n * @category models\n * @since 0.1.0\n */\nexport type Block = typeof Block.Type\n\n/**\n * Schema for a message the user sent. `at` is when it was stored, in epoch\n * milliseconds.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const UserMessage = Schema.TaggedStruct(\"UserMessage\", {\n id: InputId,\n text: Schema.String,\n attachments: Schema.optional(Schema.Array(AssistantEvent.Attachment)),\n at: Schema.Finite\n})\n\n/**\n * Represents a message the user sent.\n *\n * @category models\n * @since 0.1.0\n */\nexport type UserMessage = typeof UserMessage.Type\n\n/**\n * Schema for a message the assistant sent the user, including its block if it has one.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const AssistantMessage = Schema.TaggedStruct(\"AssistantMessage\", {\n id: Schema.String,\n text: Schema.String,\n at: Schema.Finite,\n block: Schema.optional(Block)\n})\n\n/**\n * Represents a message the assistant sent the user.\n *\n * @category models\n * @since 0.1.0\n */\nexport type AssistantMessage = typeof AssistantMessage.Type\n\n/**\n * Schema for a message in the chat, sent by either the user or the assistant.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const Message = Schema.Union([UserMessage, AssistantMessage])\n\n/**\n * Represents a message in the chat.\n *\n * @category models\n * @since 0.1.0\n */\nexport type Message = typeof Message.Type\n\n/**\n * Schema for a specialist that is working for the assistant. It holds the specialist's name and\n * when it started, in epoch milliseconds.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const SpecialistWorking = Schema.Struct({\n specialist: Schema.String,\n since: Schema.Finite\n})\n\n/**\n * Represents a specialist that is working for the assistant.\n *\n * @category models\n * @since 0.1.0\n */\nexport type SpecialistWorking = typeof SpecialistWorking.Type\n\n/**\n * Schema for what is working in the chat.\n *\n * `working` is true while the assistant runs a turn, or when a turn is about to start for\n * something that arrived. `specialists` lists the specialists that are working. When the chat is\n * idle, nothing is working, even if a workflow is waiting on a timer or on the user.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const Activity = Schema.Struct({\n working: Schema.Boolean,\n specialists: Schema.Array(SpecialistWorking)\n})\n\n/**\n * Represents what is working in the chat.\n *\n * @category models\n * @since 0.1.0\n */\nexport type Activity = typeof Activity.Type\n\n/**\n * Schema for a chat as its user sees it. It holds the chat's messages, what is working in it,\n * whether the last turn failed, and whether the chat was deleted. `seq` is the position in the\n * chat's log that the view is up to.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const View = Schema.Struct({\n seq: Schema.Int,\n messages: Schema.Array(Message),\n activity: Activity,\n /** Whether the last turn failed with no message after it. */\n failed: Schema.Boolean,\n /** Whether the chat was deleted. A deleted chat accepts nothing more, and its stream ends. */\n deleted: Schema.Boolean\n})\n\n/**\n * Represents a chat as its user sees it.\n *\n * @category models\n * @since 0.1.0\n */\nexport type View = typeof View.Type\n\n/**\n * Schema for a change to a chat's view.\n *\n * An update holds each message that is new or changed (for example, a block answered or\n * closed), in full. It also holds the current activity, whether the last turn failed,\n * and whether the chat was deleted. `seq` is the position in the chat's log that the update is up\n * to.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const Update = Schema.Struct({\n seq: Schema.Int,\n messages: Schema.Array(Message),\n activity: Activity,\n failed: Schema.Boolean,\n deleted: Schema.Boolean\n})\n\n/**\n * Represents a change to a chat's view.\n *\n * @category models\n * @since 0.1.0\n */\nexport type Update = typeof Update.Type\n\n/**\n * The view of a chat before anything has happened in it, with no messages and nothing working.\n * Use it as the starting point when folding updates with {@link apply}.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const empty: View = {\n seq: 0,\n messages: [],\n activity: { working: false, specialists: [] },\n failed: false,\n deleted: false\n}\n\n/**\n * Returns the view after applying an update.\n *\n * **Details**\n *\n * Each message in the update replaces the message with the same id, in place, and new messages\n * are added after the rest. The update's activity, `failed`, and `deleted` replace the view's.\n * Applying an update twice changes nothing, so a client that resubscribes from the start can fold\n * again and get the same view.\n *\n * @category combinators\n * @since 0.1.0\n */\nexport const apply = (view: View, update: Update): View => {\n const changed = new Map(update.messages.map((message) => [message.id, message]))\n const known = new Set(view.messages.map((message) => message.id))\n\n return {\n seq: Math.max(view.seq, update.seq),\n messages: [\n ...view.messages.map((message) => changed.get(message.id) ?? message),\n ...update.messages.filter((message) => !known.has(message.id))\n ],\n activity: update.activity,\n failed: update.failed,\n deleted: update.deleted\n }\n}\n\n/**\n * Returns whether nothing is working in the chat: no turn is running or about to start, and no\n * specialist is working.\n *\n * @category guards\n * @since 0.1.0\n */\nexport const isIdle = (view: View): boolean => !view.activity.working && view.activity.specialists.length === 0\n\n/**\n * Represents the options for building a view from a chat's log.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Options {\n /**\n * The tags of the app's events that wake a turn. After such an event, the view shows a turn as\n * about to start. Defaults to none.\n */\n readonly wakes?: ReadonlySet<string> | undefined\n}\n\n/**\n * Returns a chat's view, built from the entries of its log.\n *\n * **When to use**\n *\n * Use it in server-side code, for example to return a whole chat at once. A client gets the view\n * as a stream from {@link updates} instead.\n *\n * **Example** (Building a chat's view from its log)\n *\n * ```ts\n * import { type ChatId, ChatLog, ChatView } from \"effect-assistant\"\n * import * as Effect from \"effect/Effect\"\n *\n * const viewOf = (chatId: ChatId.ChatId) =>\n * ChatLog.ChatLog.use((log) => log.read(chatId)).pipe(\n * Effect.map((entries) => ChatView.fromEntries(entries))\n * )\n * ```\n *\n * @see {@link updates} for a stream of changes instead of a whole view\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const fromEntries = (entries: ReadonlyArray<AssistantEvent.AnyEntry>, options?: Options): View => {\n const fold = folding(options?.wakes ?? new Set())\n\n for (const entry of entries) fold.step(entry)\n\n return fold.view\n}\n\ninterface Folding {\n readonly step: (entry: AssistantEvent.AnyEntry) => Update | undefined\n readonly view: View\n}\n\nconst activityOf = (activity: AssistantActivity.State): Activity => ({\n working: activity.turnSince !== undefined || activity.queued,\n specialists: [...activity.specialists.values()].map((run) => ({\n specialist: run.specialist,\n since: run.since\n }))\n})\n\nconst sameActivity = (a: Activity, b: Activity) =>\n a.working === b.working &&\n a.specialists.length === b.specialists.length &&\n a.specialists.every((run, i) => {\n const other = b.specialists[i]\n\n return other !== undefined && run.specialist === other.specialist && run.since === other.since\n })\n\nconst folding = (wakes: ReadonlySet<string>): Folding => {\n let seq = 0\n let failed = false\n let deleted = false\n let activity = AssistantActivity.empty\n let blocks: ReadonlyMap<string, BlockKit.State> = new Map()\n // The messages in order, and the assistant's as stored: their blocks change as the chat goes on.\n const messages = new Map<string, Message>()\n const sent = new Map<string, AssistantEvent.AssistantMessage>()\n\n const blockOf = (message: AssistantEvent.AssistantMessage): Block | undefined => {\n if (message.block === undefined) return undefined\n\n const state = blocks.get(message.id)\n const status = state === undefined || state.open ? \"open\" : state.answer === undefined ? \"closed\" : \"answered\"\n const block: Block = { kind: message.block.kind, props: toJson(message.block.props), status }\n\n return state?.answer === undefined ? block : { ...block, answer: toJson(state.answer.value) }\n }\n\n const viewOf = (message: AssistantEvent.AssistantMessage, at: number): AssistantMessage => {\n const block = blockOf(message)\n\n return block === undefined\n ? AssistantMessage.make({ id: message.id, text: message.text, at })\n : AssistantMessage.make({ id: message.id, text: message.text, at, block })\n }\n\n const touched = (event: AssistantEvent.AssistantEvent, at: number): ReadonlyArray<string> =>\n Match.value(event).pipe(\n Match.tagsExhaustive({\n UserMessage: (message) => {\n messages.set(\n message.id,\n message.attachments === undefined\n ? UserMessage.make({ id: message.id, text: message.text, at })\n : UserMessage.make({\n id: message.id,\n text: message.text,\n attachments: message.attachments,\n at\n })\n )\n failed = false\n\n return [message.id]\n },\n AssistantMessage: (message) => {\n sent.set(message.id, message)\n messages.set(message.id, viewOf(message, at))\n failed = false\n\n return [message.id, ...(message.closes ?? [])]\n },\n BlockAnswer: (answer) => {\n failed = false\n\n return [answer.messageId]\n },\n BlocksClosed: (closed) => closed.messageIds,\n TurnFailed: () => {\n failed = true\n\n return []\n },\n ModelOutput: () => [],\n ToolStarted: () => [],\n ToolResult: () => [],\n TurnStarted: () => [],\n TurnCompleted: () => [],\n SpecialistStarted: () => [],\n SpecialistFinished: () => [],\n ChatDeleted: () => {\n deleted = true\n\n return []\n }\n })\n )\n\n const step = (entry: AssistantEvent.AnyEntry): Update | undefined => {\n const before = { activity: activityOf(activity), failed, deleted }\n const { event } = entry\n\n seq = entry.seq\n activity = AssistantActivity.apply(activity, entry, { wakes })\n\n if (!AssistantEvent.isLibraryEvent(event)) {\n const now = activityOf(activity)\n\n return sameActivity(before.activity, now) ? undefined : { seq, messages: [], activity: now, failed, deleted }\n }\n\n blocks = BlockKit.apply(blocks, event)\n\n const changed = [...new Set(touched(event, entry.at))].flatMap((id) => {\n const message = sent.get(id)\n const current = messages.get(id)\n\n if (message === undefined || current === undefined) return current === undefined ? [] : [current]\n\n const next = viewOf(message, current.at)\n messages.set(id, next)\n\n return [next]\n })\n\n const now = activityOf(activity)\n\n return changed.length === 0 &&\n failed === before.failed &&\n deleted === before.deleted &&\n sameActivity(before.activity, now)\n ? undefined\n : { seq, messages: changed, activity: now, failed, deleted }\n }\n\n return {\n step,\n get view() {\n return { seq, messages: [...messages.values()], activity: activityOf(activity), failed, deleted }\n }\n }\n}\n\nconst merge = (updates: ReadonlyArray<Update>): Update | undefined => {\n const last = updates.at(-1)\n\n if (last === undefined) return undefined\n\n const messages = new Map<string, Message>()\n\n for (const update of updates) for (const message of update.messages) messages.set(message.id, message)\n\n return {\n seq: last.seq,\n messages: [...messages.values()],\n activity: last.activity,\n failed: last.failed,\n deleted: last.deleted\n }\n}\n\n/**\n * Represents an assistant's definition as {@link updates} reads it: the app's events it\n * registered, and whether each one wakes a turn. An assistant made with `Assistant.make` fits it.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface Definition {\n readonly events: ReadonlyArray<{ readonly tag: string; readonly wakes: boolean }>\n}\n\n/**\n * Represents the options accepted by {@link updates}.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface UpdatesOptions {\n /**\n * The `seq` up to which the client already has the view. The result contains only the updates\n * after it. Defaults to `0`, every update.\n */\n readonly after?: number | undefined\n}\n\nconst wakesOf = (definition: Definition): ReadonlySet<string> =>\n new Set(definition.events.flatMap((event) => (event.wakes ? [event.tag] : [])))\n\n/**\n * Returns the stream of view updates that a chat's entries produce, for a chat client.\n *\n * **Details**\n *\n * The entries must stream from the start of the chat's log, as `assistant.subscribe(chatId)` does\n * without `after`. The fold reads every entry, because a message's view depends on earlier\n * entries: a block is answered or closed later. `after` only skips\n * emitting the updates up to that `seq`, so a client that has the view up to a `seq` receives only\n * what changed since. Pass `after` here, not to `subscribe`: a subscription that starts after a\n * `seq` gives the fold an incomplete log, and the updates it produces are wrong.\n *\n * The definition tells the fold which of the app's events wake a turn, so the view shows a turn as\n * about to start after one. Each chunk of entries becomes one update; the stored entries, for\n * example, arrive as one chunk.\n *\n * **Example** (Streaming a chat's view to a client)\n *\n * ```ts\n * import { Assistant, type ChatId, ChatView } from \"effect-assistant\"\n * import * as Stream from \"effect/Stream\"\n *\n * const MyAssistant = Assistant.make(\"my-assistant\")\n *\n * const watch = (chatId: ChatId.ChatId, after: number) =>\n * Stream.unwrap(\n * MyAssistant.useSync((assistant) =>\n * assistant.subscribe(chatId).pipe(ChatView.updates(MyAssistant, { after }))\n * )\n * )\n * ```\n *\n * @see {@link apply} for folding the updates into a view on the client\n *\n * @category utils\n * @since 0.1.0\n */\nexport const updates: {\n (\n definition: Definition,\n options?: UpdatesOptions\n ): <E, R>(entries: Stream.Stream<AssistantEvent.AnyEntry, E, R>) => Stream.Stream<Update, E, R>\n <E, R>(\n entries: Stream.Stream<AssistantEvent.AnyEntry, E, R>,\n definition: Definition,\n options?: UpdatesOptions\n ): Stream.Stream<Update, E, R>\n} = dual(\n (args) => Stream.isStream(args[0]),\n <E, R>(\n entries: Stream.Stream<AssistantEvent.AnyEntry, E, R>,\n definition: Definition,\n options?: UpdatesOptions\n ): Stream.Stream<Update, E, R> => {\n const after = options?.after ?? 0\n const wakes = wakesOf(definition)\n\n return entries.pipe(\n Stream.mapAccumArray(\n () => folding(wakes),\n (fold, chunk) => {\n const changed = chunk.flatMap((entry) => {\n const update = fold.step(entry)\n\n return update === undefined || entry.seq <= after ? [] : [update]\n })\n\n const merged = merge(changed)\n\n return [fold, merged === undefined ? [] : [merged]]\n }\n )\n )\n }\n)\n"],"mappings":"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"}
|
package/dist/Chats.d.ts
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { ChatId } from "./ChatId.js";
|
|
2
|
+
import * as Context from "effect/Context";
|
|
3
|
+
import * as Effect from "effect/Effect";
|
|
4
|
+
import * as Schema from "effect/Schema";
|
|
5
|
+
declare namespace Chats_d_exports {
|
|
6
|
+
export { ChatRecord, Chats, GetChatError, Reason, Service };
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Schema for the reason a chat can't be used. `"NotFound"` means the app doesn't know the chat,
|
|
10
|
+
* and `"Deleted"` means the app deleted it.
|
|
11
|
+
*
|
|
12
|
+
* @category schemas
|
|
13
|
+
* @since 0.1.0
|
|
14
|
+
*/
|
|
15
|
+
export declare const Reason: Schema.Literals<readonly ["NotFound", "Deleted"]>;
|
|
16
|
+
/**
|
|
17
|
+
* The reason a chat can't be used.
|
|
18
|
+
*
|
|
19
|
+
* @category models
|
|
20
|
+
* @since 0.1.0
|
|
21
|
+
*/
|
|
22
|
+
export type Reason = typeof Reason.Type;
|
|
23
|
+
declare const GetChatError_base: Schema.Class<GetChatError, Schema.Struct<{
|
|
24
|
+
readonly _tag: Schema.tag<"GetChatError">;
|
|
25
|
+
readonly chatId: Schema.brand<Schema.TemplateLiteral<readonly ["chat:", Schema.String]>, "~effect-assistant/ChatId">;
|
|
26
|
+
readonly reason: Schema.Literals<readonly ["NotFound", "Deleted"]>;
|
|
27
|
+
}>, import("effect/Cause").YieldableError>;
|
|
28
|
+
/**
|
|
29
|
+
* Represents the error that `get` fails with when a chat can't be used. It carries the chat's id
|
|
30
|
+
* and the {@link Reason}. An input for such a chat isn't stored.
|
|
31
|
+
*
|
|
32
|
+
* **Example** (Failing for an unknown chat)
|
|
33
|
+
*
|
|
34
|
+
* ```ts
|
|
35
|
+
* if (row === undefined) return yield* new GetChatError({ chatId, reason: "NotFound" })
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* @category errors
|
|
39
|
+
* @since 0.1.0
|
|
40
|
+
*/
|
|
41
|
+
export declare class GetChatError extends GetChatError_base {}
|
|
42
|
+
/**
|
|
43
|
+
* Represents what the library needs to know about a chat, which is its id. The app's own record
|
|
44
|
+
* can carry more.
|
|
45
|
+
*
|
|
46
|
+
* @category models
|
|
47
|
+
* @since 0.1.0
|
|
48
|
+
*/
|
|
49
|
+
export interface ChatRecord {
|
|
50
|
+
readonly chatId: ChatId;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Represents the app's chats, as the `Chats` service exposes them.
|
|
54
|
+
*
|
|
55
|
+
* @category models
|
|
56
|
+
* @since 0.1.0
|
|
57
|
+
*/
|
|
58
|
+
export interface Service {
|
|
59
|
+
/** Returns the chat, or fails with a `GetChatError` that says why it can't be used. */
|
|
60
|
+
readonly get: (chatId: ChatId) => Effect.Effect<ChatRecord, GetChatError>;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Service that tells the library which of the app's chats exist.
|
|
64
|
+
*
|
|
65
|
+
* **Details**
|
|
66
|
+
*
|
|
67
|
+
* By default every chat exists from its first input, and a chat is deleted only when its log
|
|
68
|
+
* says so (`ChatDeleted`). Provide your own implementation to have the app decide. Provide it
|
|
69
|
+
* where you provide the `ChatLog`, so the engine and your server see the same one.
|
|
70
|
+
*
|
|
71
|
+
* **Example** (Reading chats from the app's own table)
|
|
72
|
+
*
|
|
73
|
+
* ```ts
|
|
74
|
+
* import { type ChatId, Chats } from "effect-assistant"
|
|
75
|
+
* import * as Effect from "effect/Effect"
|
|
76
|
+
* import * as Layer from "effect/Layer"
|
|
77
|
+
* import * as SqlClient from "effect/sql/SqlClient"
|
|
78
|
+
*
|
|
79
|
+
* const ChatsLive = Layer.effect(
|
|
80
|
+
* Chats.Chats,
|
|
81
|
+
* Effect.gen(function* () {
|
|
82
|
+
* const sql = yield* SqlClient.SqlClient
|
|
83
|
+
*
|
|
84
|
+
* return {
|
|
85
|
+
* get: Effect.fn(function* (chatId: ChatId.ChatId) {
|
|
86
|
+
* const [row] = yield* sql<{ readonly deleted_at: number | null }>`
|
|
87
|
+
* SELECT deleted_at FROM chats WHERE chat_id = ${chatId}
|
|
88
|
+
* `.pipe(Effect.orDie)
|
|
89
|
+
*
|
|
90
|
+
* if (row === undefined) {
|
|
91
|
+
* return yield* new Chats.GetChatError({ chatId, reason: "NotFound" })
|
|
92
|
+
* }
|
|
93
|
+
* if (row.deleted_at !== null) {
|
|
94
|
+
* return yield* new Chats.GetChatError({ chatId, reason: "Deleted" })
|
|
95
|
+
* }
|
|
96
|
+
*
|
|
97
|
+
* return { chatId }
|
|
98
|
+
* })
|
|
99
|
+
* }
|
|
100
|
+
* })
|
|
101
|
+
* )
|
|
102
|
+
* ```
|
|
103
|
+
*
|
|
104
|
+
* @category services
|
|
105
|
+
* @since 0.1.0
|
|
106
|
+
*/
|
|
107
|
+
export declare const Chats: Context.Reference<Service>;
|
|
108
|
+
//#endregion
|
|
109
|
+
export { Chats_d_exports };
|
|
110
|
+
//# sourceMappingURL=Chats.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Chats.d.ts","names":[],"sources":["../src/Chats.ts"],"mappings":";;;;;;;;;;;;;;qBAuBa,QAAM,OAAA;;;;;;;YAQP,gBAAgB,OAAO;;;;;;;;;;;;;;;;;;;qBAetB,qBAAqB;;;;;;;;iBAajB;WACN,QAAQ;;;;;;;;iBASF;;WAEN,MAAM,QAAQ,WAAW,OAAO,OAAO,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAgDjD,OAAO,QAAQ,UAAU"}
|