effect-assistant 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +614 -0
- package/dist/AssistantEvent.d.ts.map +1 -0
- package/dist/AssistantEvent.js +523 -0
- package/dist/AssistantEvent.js.map +1 -0
- package/dist/AssistantLoop.d.ts +171 -0
- package/dist/AssistantLoop.d.ts.map +1 -0
- package/dist/AssistantLoop.js +108 -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 +184 -0
- package/dist/BlockKit.d.ts.map +1 -0
- package/dist/BlockKit.js +151 -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 +459 -0
- package/dist/ChatView.d.ts.map +1 -0
- package/dist/ChatView.js +496 -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 +133 -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/Decision.d.ts +145 -0
- package/dist/Decision.d.ts.map +1 -0
- package/dist/Decision.js +178 -0
- package/dist/Decision.js.map +1 -0
- package/dist/DecisionId.d.ts +37 -0
- package/dist/DecisionId.d.ts.map +1 -0
- package/dist/DecisionId.js +44 -0
- package/dist/DecisionId.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 +108 -0
- package/dist/ToolDiscovery.d.ts.map +1 -0
- package/dist/ToolDiscovery.js +139 -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 +26 -0
- package/dist/index.js +26 -0
- package/dist/internal/chat.js +202 -0
- package/dist/internal/chat.js.map +1 -0
- package/dist/internal/decisions.js +66 -0
- package/dist/internal/decisions.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 +185 -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 +166 -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 +273 -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 +76 -0
- package/dist/internal/toolkit.d.ts.map +1 -0
- package/dist/internal/toolkit.js +129 -0
- package/dist/internal/toolkit.js.map +1 -0
- package/dist/internal/tools.js +132 -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 +221 -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 +51 -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 +635 -0
- package/src/AssistantLoop.ts +207 -0
- package/src/AssistantToolCall.ts +40 -0
- package/src/Block.ts +84 -0
- package/src/BlockKit.ts +311 -0
- package/src/ChatId.ts +36 -0
- package/src/ChatLog.ts +247 -0
- package/src/ChatView.ts +658 -0
- package/src/Chats.ts +122 -0
- package/src/Compaction.ts +224 -0
- package/src/CurrentChat.ts +47 -0
- package/src/Decision.ts +241 -0
- package/src/DecisionId.ts +46 -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 +187 -0
- package/src/ToolFailure.ts +74 -0
- package/src/index.ts +130 -0
- package/src/internal/chat.ts +372 -0
- package/src/internal/decisions.ts +131 -0
- package/src/internal/events.ts +100 -0
- package/src/internal/history.ts +366 -0
- package/src/internal/instructions.ts +9 -0
- package/src/internal/loop.ts +344 -0
- package/src/internal/schema.ts +25 -0
- package/src/internal/skills.ts +37 -0
- package/src/internal/specialists.ts +414 -0
- package/src/internal/telemetry.ts +125 -0
- package/src/internal/toolkit.ts +231 -0
- package/src/internal/tools.ts +210 -0
- package/src/internal/turns.ts +429 -0
- package/src/internal/uuid.ts +50 -0
- package/src/internal/writer.ts +12 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { AnyEntry, AssistantEvent_d_exports, BlockAnswer } from "./AssistantEvent.js";
|
|
2
|
+
import { Any, Block_d_exports } from "./Block.js";
|
|
3
|
+
import { AssistantMessage, ChatView_d_exports, PendingDecision } from "./ChatView.js";
|
|
4
|
+
import { CurrentChat } from "./CurrentChat.js";
|
|
5
|
+
import * as Effect from "effect/Effect";
|
|
6
|
+
import * as Schema from "effect/Schema";
|
|
7
|
+
declare namespace BlockKit_d_exports {
|
|
8
|
+
export { BlockKit, RenderArgs, RenderOptions, Renderers, State, apply, closeStale, fromEntries, make, staleBlocks, toClient };
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Represents a catalog of blocks, with a lookup by kind.
|
|
12
|
+
*
|
|
13
|
+
* @category models
|
|
14
|
+
* @since 0.1.0
|
|
15
|
+
*/
|
|
16
|
+
export interface BlockKit<Blocks extends Any = Any> {
|
|
17
|
+
readonly blocks: ReadonlyArray<Blocks>;
|
|
18
|
+
readonly byKind: ReadonlyMap<string, Blocks>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Creates a kit from blocks.
|
|
22
|
+
*
|
|
23
|
+
* @category constructors
|
|
24
|
+
* @since 0.1.0
|
|
25
|
+
*/
|
|
26
|
+
export declare const make: <const Blocks extends ReadonlyArray<Any>>(blocks: Blocks) => BlockKit<Blocks[number]>;
|
|
27
|
+
type KindOf<B> = B extends {
|
|
28
|
+
readonly kind: infer K extends string;
|
|
29
|
+
} ? K : never;
|
|
30
|
+
type BlockOf<Kit extends BlockKit, K> = Extract<Kit["blocks"][number], {
|
|
31
|
+
readonly kind: K;
|
|
32
|
+
}>;
|
|
33
|
+
type PropsOf<B> = B extends {
|
|
34
|
+
readonly props: Schema.Top;
|
|
35
|
+
} ? B["props"]["Type"] : never;
|
|
36
|
+
type AnswerOf<B> = B extends {
|
|
37
|
+
readonly answer: infer A;
|
|
38
|
+
} ? (A extends Schema.Top ? A["Type"] : never) : never;
|
|
39
|
+
/**
|
|
40
|
+
* Represents the state of a message's block.
|
|
41
|
+
*
|
|
42
|
+
* @category models
|
|
43
|
+
* @since 0.1.0
|
|
44
|
+
*/
|
|
45
|
+
export interface State {
|
|
46
|
+
readonly open: boolean;
|
|
47
|
+
/** The user's answer, if they used the block. */
|
|
48
|
+
readonly answer: BlockAnswer | undefined;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Returns the block states after one more event.
|
|
52
|
+
*
|
|
53
|
+
* @category utils
|
|
54
|
+
* @since 0.1.0
|
|
55
|
+
*/
|
|
56
|
+
export declare const apply: (states: ReadonlyMap<string, State>, event: {
|
|
57
|
+
readonly _tag: string;
|
|
58
|
+
}) => ReadonlyMap<string, State>;
|
|
59
|
+
/**
|
|
60
|
+
* Returns the state of every message's block, by message id.
|
|
61
|
+
*
|
|
62
|
+
* @category utils
|
|
63
|
+
* @since 0.1.0
|
|
64
|
+
*/
|
|
65
|
+
export declare const fromEntries: (entries: ReadonlyArray<AnyEntry>) => ReadonlyMap<string, State>;
|
|
66
|
+
/**
|
|
67
|
+
* Returns the ids of the messages whose blocks are still open after the user typed the given
|
|
68
|
+
* number of messages without using them.
|
|
69
|
+
*
|
|
70
|
+
* **Details**
|
|
71
|
+
*
|
|
72
|
+
* These are the blocks {@link closeStale} closes. Decision blocks never go stale, since work waits
|
|
73
|
+
* on them and closing one would leave it waiting.
|
|
74
|
+
*
|
|
75
|
+
* @category utils
|
|
76
|
+
* @since 0.1.0
|
|
77
|
+
*/
|
|
78
|
+
export declare const staleBlocks: (entries: ReadonlyArray<AnyEntry>, options: {
|
|
79
|
+
readonly messages: number;
|
|
80
|
+
}) => ReadonlyArray<string>;
|
|
81
|
+
/**
|
|
82
|
+
* Closes the blocks the user moved on from, which are still open after the given number of typed
|
|
83
|
+
* messages.
|
|
84
|
+
*
|
|
85
|
+
* **Details**
|
|
86
|
+
*
|
|
87
|
+
* Run it from a turn, before or after its loop. It records a `BlocksClosed` event, so every
|
|
88
|
+
* client folds the blocks closed.
|
|
89
|
+
*
|
|
90
|
+
* @see {@link staleBlocks} for the blocks it closes
|
|
91
|
+
*
|
|
92
|
+
* @category utils
|
|
93
|
+
* @since 0.1.0
|
|
94
|
+
*/
|
|
95
|
+
export declare const closeStale: (options: {
|
|
96
|
+
readonly messages: number;
|
|
97
|
+
}) => Effect.Effect<void, never, CurrentChat>;
|
|
98
|
+
/**
|
|
99
|
+
* Represents what a renderer receives for one block.
|
|
100
|
+
*
|
|
101
|
+
* @category models
|
|
102
|
+
* @since 0.1.0
|
|
103
|
+
*/
|
|
104
|
+
export interface RenderArgs<B> {
|
|
105
|
+
readonly text: string;
|
|
106
|
+
readonly props: PropsOf<B>;
|
|
107
|
+
/**
|
|
108
|
+
* Whether the block is open. It is `false` once the block is answered, with the block or in
|
|
109
|
+
* words; show it read-only then.
|
|
110
|
+
*/
|
|
111
|
+
readonly open: boolean;
|
|
112
|
+
/** The user's answer, if they used the block. */
|
|
113
|
+
readonly answer: AnswerOf<B> | undefined;
|
|
114
|
+
/**
|
|
115
|
+
* For the decision block, the call each decision is for (its tool and params) and how it was
|
|
116
|
+
* decided. Empty for other blocks.
|
|
117
|
+
*/
|
|
118
|
+
readonly decisions: ReadonlyArray<PendingDecision>;
|
|
119
|
+
/** Sends an answer. Only call it while `open`. */
|
|
120
|
+
readonly respond: (answer: AnswerOf<B>) => void;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Represents a renderer for every kind in a kit.
|
|
124
|
+
*
|
|
125
|
+
* @category models
|
|
126
|
+
* @since 0.1.0
|
|
127
|
+
*/
|
|
128
|
+
export type Renderers<Kit extends BlockKit, Out> = { readonly [K in KindOf<Kit["blocks"][number]>]: (args: RenderArgs<BlockOf<Kit, K>>) => Out; };
|
|
129
|
+
/**
|
|
130
|
+
* Represents the options for rendering one message's block.
|
|
131
|
+
*
|
|
132
|
+
* @category models
|
|
133
|
+
* @since 0.1.0
|
|
134
|
+
*/
|
|
135
|
+
export interface RenderOptions {
|
|
136
|
+
/** Submits an answer, for example over RPC. */
|
|
137
|
+
readonly respond: (answer: BlockAnswer) => void;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Creates a client-side renderer for the blocks on a chat view's messages
|
|
141
|
+
* (`ChatView.AssistantMessage`).
|
|
142
|
+
*
|
|
143
|
+
* **Details**
|
|
144
|
+
*
|
|
145
|
+
* The renderers are type-checked, so every kind in the kit needs one, and each receives typed
|
|
146
|
+
* `props` and `answer`. A renderer's `respond` builds a `BlockAnswer` input, with the answer
|
|
147
|
+
* encoded by the block's answer schema, and passes it to the `respond` option to submit.
|
|
148
|
+
*
|
|
149
|
+
* The returned function returns `undefined` for a message without a block, or with a block it
|
|
150
|
+
* can't decode or has no renderer for.
|
|
151
|
+
*
|
|
152
|
+
* **Example** (Rendering blocks with React)
|
|
153
|
+
*
|
|
154
|
+
* ```tsx
|
|
155
|
+
* import { BlockKit, Decision } from "effect-assistant"
|
|
156
|
+
* import type { ReactNode } from "react"
|
|
157
|
+
*
|
|
158
|
+
* const Blocks = BlockKit.make([PickOne, Decision.block])
|
|
159
|
+
*
|
|
160
|
+
* const renderBlock = BlockKit.toClient<typeof Blocks, ReactNode>(Blocks, {
|
|
161
|
+
* pick_one: ({ props, open, respond }) =>
|
|
162
|
+
* props.options.map((option) => (
|
|
163
|
+
* <button key={option} disabled={!open} onClick={() => respond({ picked: option })}>
|
|
164
|
+
* {option}
|
|
165
|
+
* </button>
|
|
166
|
+
* )),
|
|
167
|
+
* decision: ({ props, open, respond }) => (
|
|
168
|
+
* <button disabled={!open} onClick={() => respond(Decision.answerAll(props, "approved"))}>
|
|
169
|
+
* {props.approveLabel ?? "Approve"}
|
|
170
|
+
* </button>
|
|
171
|
+
* )
|
|
172
|
+
* })
|
|
173
|
+
*
|
|
174
|
+
* // For each AssistantMessage in the chat's view:
|
|
175
|
+
* renderBlock(message, { respond: (answer) => submit(answer) })
|
|
176
|
+
* ```
|
|
177
|
+
*
|
|
178
|
+
* @category constructors
|
|
179
|
+
* @since 0.1.0
|
|
180
|
+
*/
|
|
181
|
+
export declare function toClient<Kit extends BlockKit, Out>(kit: Kit, renderers: Renderers<Kit, Out>): (message: AssistantMessage, options: RenderOptions) => Out | undefined;
|
|
182
|
+
//#endregion
|
|
183
|
+
export { BlockKit_d_exports };
|
|
184
|
+
//# sourceMappingURL=BlockKit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BlockKit.d.ts","names":[],"sources":["../src/BlockKit.ts"],"mappings":";;;;;;;;;;;;;;;iBA2BiB,SAAS,eAAe,MAAY;WAC1C,QAAQ,cAAc;WACtB,QAAQ,oBAAoB;;;;;;;;qBAS1B,aAAc,eAAe,cAAc,MAAU,QAAU,WAAS,SAAS;KAKzF,OAAO,KAAK;WAAqB,YAAY;IAAqB;KAElE,QAAQ,YAAY,UAAU,KAAK,QAAQ;WAAkC,MAAM;;KAEnF,QAAQ,KAAK;WAAqB,OAAO,OAAO;IAAQ;KAExD,SAAS,KAAK;WAAqB,cAAc;KAAO,UAAU,OAAO,MAAM;;;;;;;iBAQnE;WACN;;WAEA,QAAQ;;;;;;;;qBASN,QAAK,QACR,oBAAoB,QAAM;WAChB;MACjB,oBAAoB;;;;;;;qBAkCV,cAAW,SAAa,cAAc,cAA2B,oBAAoB;;;;;;;;;;;;;qBAoBrF,cAAW,SACb,cAAc,WAAwB;WAC3B;MACnB;;;;;;;;;;;;;;;qBAuCU,aAAU;WAAsD;MAK3E,OAAA,oBAAA;;;;;;;iBAQe,WAAW;WACjB;WACA,OAAO,QAAQ;;;;;WAKf;;WAEA,QAAQ,SAAS;;;;;WAKjB,WAAW,cAAc;;WAEzB,UAAU,QAAQ,SAAS;;;;;;;;YAS1B,UAAU,YAAY,UAAU,mBAChC,KAAK,OAAO,0BAA0B,MAAM,WAAW,QAAQ,KAAK,QAAQ;;;;;;;iBASvE;;WAEN,UAAU,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBA6Cb,SAAS,YAAY,UAAU,KAC7C,KAAK,KACL,WAAW,UAAU,KAAK,QACxB,SAAS,kBAA2B,SAAS,kBAAkB"}
|
package/dist/BlockKit.js
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { __exportAll } from "./_virtual/_rolldown/runtime.js";
|
|
2
|
+
import { fromKey } from "./InputId.js";
|
|
3
|
+
import { BlockAnswer, BlocksClosed, isLibraryEvent } from "./AssistantEvent.js";
|
|
4
|
+
import { CurrentChat } from "./CurrentChat.js";
|
|
5
|
+
import "./Decision.js";
|
|
6
|
+
import * as Predicate from "effect/Predicate";
|
|
7
|
+
import * as Effect from "effect/Effect";
|
|
8
|
+
import * as Schema from "effect/Schema";
|
|
9
|
+
import * as Option from "effect/Option";
|
|
10
|
+
//#region src/BlockKit.ts
|
|
11
|
+
/**
|
|
12
|
+
* Groups the blocks an assistant may put in its messages, and renders them on the client.
|
|
13
|
+
*
|
|
14
|
+
* A kit is created with {@link make} and passed to `Assistant.make`. {@link fromEntries} computes
|
|
15
|
+
* the state of every message's block from a chat's log, {@link closeStale} closes the blocks the
|
|
16
|
+
* user moved on from, and {@link toClient} creates a client-side renderer with one function per
|
|
17
|
+
* block kind.
|
|
18
|
+
*
|
|
19
|
+
* @since 0.1.0
|
|
20
|
+
*/
|
|
21
|
+
var BlockKit_exports = /* @__PURE__ */ __exportAll({
|
|
22
|
+
apply: () => apply,
|
|
23
|
+
closeStale: () => closeStale,
|
|
24
|
+
fromEntries: () => fromEntries,
|
|
25
|
+
make: () => make,
|
|
26
|
+
staleBlocks: () => staleBlocks,
|
|
27
|
+
toClient: () => toClient
|
|
28
|
+
});
|
|
29
|
+
/**
|
|
30
|
+
* Creates a kit from blocks.
|
|
31
|
+
*
|
|
32
|
+
* @category constructors
|
|
33
|
+
* @since 0.1.0
|
|
34
|
+
*/
|
|
35
|
+
const make = (blocks) => ({
|
|
36
|
+
blocks,
|
|
37
|
+
byKind: new Map(blocks.map((block) => [block.kind, block]))
|
|
38
|
+
});
|
|
39
|
+
/**
|
|
40
|
+
* Returns the block states after one more event.
|
|
41
|
+
*
|
|
42
|
+
* @category utils
|
|
43
|
+
* @since 0.1.0
|
|
44
|
+
*/
|
|
45
|
+
const apply = (states, event) => {
|
|
46
|
+
if (!isLibraryEvent(event)) return states;
|
|
47
|
+
const close = (ids) => {
|
|
48
|
+
const open = ids.filter((id) => states.get(id)?.open === true);
|
|
49
|
+
if (open.length === 0) return states;
|
|
50
|
+
const next = new Map(states);
|
|
51
|
+
for (const id of open) next.set(id, {
|
|
52
|
+
open: false,
|
|
53
|
+
answer: void 0
|
|
54
|
+
});
|
|
55
|
+
return next;
|
|
56
|
+
};
|
|
57
|
+
if (Predicate.isTagged(event, "BlocksClosed")) return close(event.messageIds);
|
|
58
|
+
if (Predicate.isTagged(event, "AssistantMessage")) {
|
|
59
|
+
const closed = close(event.closes ?? []);
|
|
60
|
+
return event.block === void 0 ? closed : new Map(closed).set(event.id, {
|
|
61
|
+
open: true,
|
|
62
|
+
answer: void 0
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
if (!Predicate.isTagged(event, "BlockAnswer") || states.get(event.messageId)?.open !== true) return states;
|
|
66
|
+
return new Map(states).set(event.messageId, {
|
|
67
|
+
open: false,
|
|
68
|
+
answer: event
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Returns the state of every message's block, by message id.
|
|
73
|
+
*
|
|
74
|
+
* @category utils
|
|
75
|
+
* @since 0.1.0
|
|
76
|
+
*/
|
|
77
|
+
const fromEntries = (entries) => {
|
|
78
|
+
let states = /* @__PURE__ */ new Map();
|
|
79
|
+
for (const entry of entries) states = apply(states, entry.event);
|
|
80
|
+
return states;
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Returns the ids of the messages whose blocks are still open after the user typed the given
|
|
84
|
+
* number of messages without using them.
|
|
85
|
+
*
|
|
86
|
+
* **Details**
|
|
87
|
+
*
|
|
88
|
+
* These are the blocks {@link closeStale} closes. Decision blocks never go stale, since work waits
|
|
89
|
+
* on them and closing one would leave it waiting.
|
|
90
|
+
*
|
|
91
|
+
* @category utils
|
|
92
|
+
* @since 0.1.0
|
|
93
|
+
*/
|
|
94
|
+
const staleBlocks = (entries, options) => {
|
|
95
|
+
return [...fromEntries(entries)].flatMap(([id, state]) => state.open ? [id] : []).filter((id) => {
|
|
96
|
+
const shown = entries.findIndex(({ event }) => isLibraryEvent(event) && Predicate.isTagged(event, "AssistantMessage") && event.id === id);
|
|
97
|
+
const message = entries[shown]?.event;
|
|
98
|
+
if (message === void 0 || isLibraryEvent(message) && Predicate.isTagged(message, "AssistantMessage") && message.block?.kind === "decision") return false;
|
|
99
|
+
return entries.slice(shown + 1).filter(({ event }) => Predicate.isTagged(event, "UserMessage")).length >= options.messages;
|
|
100
|
+
});
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* Closes the blocks the user moved on from, which are still open after the given number of typed
|
|
104
|
+
* messages.
|
|
105
|
+
*
|
|
106
|
+
* **Details**
|
|
107
|
+
*
|
|
108
|
+
* Run it from a turn, before or after its loop. It records a `BlocksClosed` event, so every
|
|
109
|
+
* client folds the blocks closed.
|
|
110
|
+
*
|
|
111
|
+
* @see {@link staleBlocks} for the blocks it closes
|
|
112
|
+
*
|
|
113
|
+
* @category utils
|
|
114
|
+
* @since 0.1.0
|
|
115
|
+
*/
|
|
116
|
+
const closeStale = Effect.fn(function* (options) {
|
|
117
|
+
const chat = yield* CurrentChat;
|
|
118
|
+
const stale = staleBlocks(yield* chat.entries, options);
|
|
119
|
+
if (stale.length > 0) yield* chat.append(BlocksClosed.make({ messageIds: stale }));
|
|
120
|
+
});
|
|
121
|
+
function toClient(kit, renderers) {
|
|
122
|
+
return (message, options) => {
|
|
123
|
+
if (message.block === void 0) return void 0;
|
|
124
|
+
const { kind } = message.block;
|
|
125
|
+
const block = kit.byKind.get(kind);
|
|
126
|
+
const render = renderers[kind];
|
|
127
|
+
if (block === void 0 || render === void 0) return void 0;
|
|
128
|
+
const props = Schema.decodeUnknownOption(block.props)(message.block.props);
|
|
129
|
+
if (Option.isNone(props)) return void 0;
|
|
130
|
+
const given = message.block.answer;
|
|
131
|
+
const answerCodec = block.answer === void 0 ? void 0 : Schema.toCodecJson(block.answer);
|
|
132
|
+
const answer = given === void 0 || answerCodec === void 0 ? Option.none() : Schema.decodeUnknownOption(answerCodec)(given);
|
|
133
|
+
return render({
|
|
134
|
+
text: message.text,
|
|
135
|
+
props: props.value,
|
|
136
|
+
open: message.block.status === "open",
|
|
137
|
+
answer: Option.getOrUndefined(answer),
|
|
138
|
+
decisions: message.block.decisions ?? [],
|
|
139
|
+
respond: (value) => options.respond(BlockAnswer.make({
|
|
140
|
+
id: fromKey(`${message.id}:answer`),
|
|
141
|
+
messageId: message.id,
|
|
142
|
+
kind,
|
|
143
|
+
value: answerCodec === void 0 ? value : Schema.encodeUnknownSync(answerCodec)(value)
|
|
144
|
+
}))
|
|
145
|
+
});
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
//#endregion
|
|
149
|
+
export { BlockKit_exports, apply, closeStale, fromEntries, make, staleBlocks, toClient };
|
|
150
|
+
|
|
151
|
+
//# sourceMappingURL=BlockKit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BlockKit.js","names":["AssistantEvent.isLibraryEvent","InputId.fromKey"],"sources":["../src/BlockKit.ts"],"sourcesContent":["/**\n * Groups the blocks an assistant may put in its messages, and renders them on the client.\n *\n * A kit is created with {@link make} and passed to `Assistant.make`. {@link fromEntries} computes\n * the state of every message's block from a chat's log, {@link closeStale} closes the blocks the\n * user moved on from, and {@link toClient} creates a client-side renderer with one function per\n * block kind.\n *\n * @since 0.1.0\n */\nimport * as Effect from \"effect/Effect\"\nimport * as Option from \"effect/Option\"\nimport * as Predicate from \"effect/Predicate\"\nimport * as Schema from \"effect/Schema\"\nimport * as AssistantEvent from \"./AssistantEvent.ts\"\nimport type * as Block from \"./Block.ts\"\nimport type * as ChatView from \"./ChatView.ts\"\nimport { CurrentChat } from \"./CurrentChat.ts\"\nimport * as Decision from \"./Decision.ts\"\nimport * as InputId from \"./InputId.ts\"\n\n/**\n * Represents a catalog of blocks, with a lookup by kind.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface BlockKit<Blocks extends Block.Any = Block.Any> {\n readonly blocks: ReadonlyArray<Blocks>\n readonly byKind: ReadonlyMap<string, Blocks>\n}\n\n/**\n * Creates a kit from blocks.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const make = <const Blocks extends ReadonlyArray<Block.Any>>(blocks: Blocks): BlockKit<Blocks[number]> => ({\n blocks,\n byKind: new Map(blocks.map((block) => [block.kind, block]))\n})\n\ntype KindOf<B> = B extends { readonly kind: infer K extends string } ? K : never\n\ntype BlockOf<Kit extends BlockKit, K> = Extract<Kit[\"blocks\"][number], { readonly kind: K }>\n\ntype PropsOf<B> = B extends { readonly props: Schema.Top } ? B[\"props\"][\"Type\"] : never\n\ntype AnswerOf<B> = B extends { readonly answer: infer A } ? (A extends Schema.Top ? A[\"Type\"] : never) : never\n\n/**\n * Represents the state of a message's block.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface State {\n readonly open: boolean\n /** The user's answer, if they used the block. */\n readonly answer: AssistantEvent.BlockAnswer | undefined\n}\n\n/**\n * Returns the block states after one more event.\n *\n * @category utils\n * @since 0.1.0\n */\nexport const apply = (\n states: ReadonlyMap<string, State>,\n event: { readonly _tag: string }\n): ReadonlyMap<string, State> => {\n if (!AssistantEvent.isLibraryEvent(event)) return states\n\n const close = (ids: ReadonlyArray<string>) => {\n const open = ids.filter((id) => states.get(id)?.open === true)\n\n if (open.length === 0) return states\n\n const next = new Map(states)\n\n for (const id of open) next.set(id, { open: false, answer: undefined })\n\n return next\n }\n\n if (Predicate.isTagged(event, \"BlocksClosed\")) return close(event.messageIds)\n\n if (Predicate.isTagged(event, \"AssistantMessage\")) {\n const closed = close(event.closes ?? [])\n\n return event.block === undefined ? closed : new Map(closed).set(event.id, { open: true, answer: undefined })\n }\n\n if (!Predicate.isTagged(event, \"BlockAnswer\") || states.get(event.messageId)?.open !== true) return states\n\n return new Map(states).set(event.messageId, { open: false, answer: event })\n}\n\n/**\n * Returns the state of every message's block, by message id.\n *\n * @category utils\n * @since 0.1.0\n */\nexport const fromEntries = (entries: ReadonlyArray<AssistantEvent.AnyEntry>): ReadonlyMap<string, State> => {\n let states: ReadonlyMap<string, State> = new Map()\n\n for (const entry of entries) states = apply(states, entry.event)\n\n return states\n}\n\n/**\n * Returns the ids of the messages whose blocks are still open after the user typed the given\n * number of messages without using them.\n *\n * **Details**\n *\n * These are the blocks {@link closeStale} closes. Decision blocks never go stale, since work waits\n * on them and closing one would leave it waiting.\n *\n * @category utils\n * @since 0.1.0\n */\nexport const staleBlocks = (\n entries: ReadonlyArray<AssistantEvent.AnyEntry>,\n options: { readonly messages: number }\n): ReadonlyArray<string> => {\n const open = [...fromEntries(entries)].flatMap(([id, state]) => (state.open ? [id] : []))\n\n return open.filter((id) => {\n const shown = entries.findIndex(\n ({ event }) =>\n AssistantEvent.isLibraryEvent(event) && Predicate.isTagged(event, \"AssistantMessage\") && event.id === id\n )\n\n const message = entries[shown]?.event\n\n if (\n message === undefined ||\n (AssistantEvent.isLibraryEvent(message) &&\n Predicate.isTagged(message, \"AssistantMessage\") &&\n message.block?.kind === Decision.kind)\n )\n return false\n\n const typed = entries.slice(shown + 1).filter(({ event }) => Predicate.isTagged(event, \"UserMessage\")).length\n\n return typed >= options.messages\n })\n}\n\n/**\n * Closes the blocks the user moved on from, which are still open after the given number of typed\n * messages.\n *\n * **Details**\n *\n * Run it from a turn, before or after its loop. It records a `BlocksClosed` event, so every\n * client folds the blocks closed.\n *\n * @see {@link staleBlocks} for the blocks it closes\n *\n * @category utils\n * @since 0.1.0\n */\nexport const closeStale = Effect.fn(function* (options: { readonly messages: number }) {\n const chat = yield* CurrentChat\n const stale = staleBlocks(yield* chat.entries, options)\n\n if (stale.length > 0) yield* chat.append(AssistantEvent.BlocksClosed.make({ messageIds: stale }))\n})\n\n/**\n * Represents what a renderer receives for one block.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface RenderArgs<B> {\n readonly text: string\n readonly props: PropsOf<B>\n /**\n * Whether the block is open. It is `false` once the block is answered, with the block or in\n * words; show it read-only then.\n */\n readonly open: boolean\n /** The user's answer, if they used the block. */\n readonly answer: AnswerOf<B> | undefined\n /**\n * For the decision block, the call each decision is for (its tool and params) and how it was\n * decided. Empty for other blocks.\n */\n readonly decisions: ReadonlyArray<ChatView.PendingDecision>\n /** Sends an answer. Only call it while `open`. */\n readonly respond: (answer: AnswerOf<B>) => void\n}\n\n/**\n * Represents a renderer for every kind in a kit.\n *\n * @category models\n * @since 0.1.0\n */\nexport type Renderers<Kit extends BlockKit, Out> = {\n readonly [K in KindOf<Kit[\"blocks\"][number]>]: (args: RenderArgs<BlockOf<Kit, K>>) => Out\n}\n\n/**\n * Represents the options for rendering one message's block.\n *\n * @category models\n * @since 0.1.0\n */\nexport interface RenderOptions {\n /** Submits an answer, for example over RPC. */\n readonly respond: (answer: AssistantEvent.BlockAnswer) => void\n}\n\n/**\n * Creates a client-side renderer for the blocks on a chat view's messages\n * (`ChatView.AssistantMessage`).\n *\n * **Details**\n *\n * The renderers are type-checked, so every kind in the kit needs one, and each receives typed\n * `props` and `answer`. A renderer's `respond` builds a `BlockAnswer` input, with the answer\n * encoded by the block's answer schema, and passes it to the `respond` option to submit.\n *\n * The returned function returns `undefined` for a message without a block, or with a block it\n * can't decode or has no renderer for.\n *\n * **Example** (Rendering blocks with React)\n *\n * ```tsx\n * import { BlockKit, Decision } from \"effect-assistant\"\n * import type { ReactNode } from \"react\"\n *\n * const Blocks = BlockKit.make([PickOne, Decision.block])\n *\n * const renderBlock = BlockKit.toClient<typeof Blocks, ReactNode>(Blocks, {\n * pick_one: ({ props, open, respond }) =>\n * props.options.map((option) => (\n * <button key={option} disabled={!open} onClick={() => respond({ picked: option })}>\n * {option}\n * </button>\n * )),\n * decision: ({ props, open, respond }) => (\n * <button disabled={!open} onClick={() => respond(Decision.answerAll(props, \"approved\"))}>\n * {props.approveLabel ?? \"Approve\"}\n * </button>\n * )\n * })\n *\n * // For each AssistantMessage in the chat's view:\n * renderBlock(message, { respond: (answer) => submit(answer) })\n * ```\n *\n * @category constructors\n * @since 0.1.0\n */\nexport function toClient<Kit extends BlockKit, Out>(\n kit: Kit,\n renderers: Renderers<Kit, Out>\n): (message: ChatView.AssistantMessage, options: RenderOptions) => Out | undefined\n// The overload above checks that every kind has a renderer; here each one takes any block's\n// arguments.\nexport function toClient<Out>(\n kit: BlockKit,\n renderers: Readonly<Record<string, (args: RenderArgs<Block.Any>) => Out>>\n): (message: ChatView.AssistantMessage, options: RenderOptions) => Out | undefined {\n return (message, options) => {\n if (message.block === undefined) return undefined\n\n const { kind } = message.block\n const block = kit.byKind.get(kind)\n const render = renderers[kind]\n\n if (block === undefined || render === undefined) return undefined\n\n const props = Schema.decodeUnknownOption(block.props)(message.block.props)\n\n if (Option.isNone(props)) return undefined\n\n const given = message.block.answer\n const answerCodec = block.answer === undefined ? undefined : Schema.toCodecJson(block.answer)\n\n const answer =\n given === undefined || answerCodec === undefined ? Option.none() : Schema.decodeUnknownOption(answerCodec)(given)\n\n return render({\n text: message.text,\n props: props.value,\n open: message.block.status === \"open\",\n answer: Option.getOrUndefined(answer),\n decisions: message.block.decisions ?? [],\n respond: (value) =>\n options.respond(\n AssistantEvent.BlockAnswer.make({\n // A block takes one answer: the same id every time, so a second click is stored once.\n id: InputId.fromKey(`${message.id}:answer`),\n messageId: message.id,\n kind,\n value: answerCodec === undefined ? value : Schema.encodeUnknownSync(answerCodec)(value)\n })\n )\n })\n }\n}\n"],"mappings":"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"}
|
package/dist/ChatId.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import * as Schema from "effect/Schema";
|
|
3
|
+
declare namespace ChatId_d_exports {
|
|
4
|
+
export { ChatId, make };
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Schema for chat ids of the form `` `chat:${uuid}` ``. The schema is branded, so a run id or a
|
|
8
|
+
* plain string can't be passed where a chat id is required.
|
|
9
|
+
*
|
|
10
|
+
* @category schemas
|
|
11
|
+
* @since 0.1.0
|
|
12
|
+
*/
|
|
13
|
+
export declare const ChatId: Schema.brand<Schema.TemplateLiteral<readonly ["chat:", Schema.String]>, "~effect-assistant/ChatId">;
|
|
14
|
+
/**
|
|
15
|
+
* Represents a chat id.
|
|
16
|
+
*
|
|
17
|
+
* @category models
|
|
18
|
+
* @since 0.1.0
|
|
19
|
+
*/
|
|
20
|
+
export type ChatId = typeof ChatId.Type;
|
|
21
|
+
/**
|
|
22
|
+
* Creates a new random chat id using Effect's `Random`. Seed `Random` in tests to get the same ids
|
|
23
|
+
* on every run.
|
|
24
|
+
*
|
|
25
|
+
* @category constructors
|
|
26
|
+
* @since 0.1.0
|
|
27
|
+
*/
|
|
28
|
+
export declare const make: () => Effect.Effect<ChatId>;
|
|
29
|
+
//#endregion
|
|
30
|
+
export { ChatId_d_exports };
|
|
31
|
+
//# sourceMappingURL=ChatId.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChatId.d.ts","names":[],"sources":["../src/ChatId.ts"],"mappings":";;;;;;;;;;;;qBAkBa,QAAM,OAAA,MAAA,OAAA,mCAAA,OAAA;;;;;;;YAQP,gBAAgB,OAAO;;;;;;;;qBAStB,YAAW,OAAO,OAAO"}
|
package/dist/ChatId.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { __exportAll } from "./_virtual/_rolldown/runtime.js";
|
|
2
|
+
import { Uuid, make as make$1 } from "./internal/uuid.js";
|
|
3
|
+
import * as Effect from "effect/Effect";
|
|
4
|
+
import * as Schema from "effect/Schema";
|
|
5
|
+
//#region src/ChatId.ts
|
|
6
|
+
/**
|
|
7
|
+
* This module provides the id of a chat, which is `chat:` followed by a UUID.
|
|
8
|
+
*
|
|
9
|
+
* A chat id keys everything about a chat: its log, its cluster entity, and its trace session.
|
|
10
|
+
*
|
|
11
|
+
* @since 0.1.0
|
|
12
|
+
*/
|
|
13
|
+
var ChatId_exports = /* @__PURE__ */ __exportAll({
|
|
14
|
+
ChatId: () => ChatId,
|
|
15
|
+
make: () => make
|
|
16
|
+
});
|
|
17
|
+
/**
|
|
18
|
+
* Schema for chat ids of the form `` `chat:${uuid}` ``. The schema is branded, so a run id or a
|
|
19
|
+
* plain string can't be passed where a chat id is required.
|
|
20
|
+
*
|
|
21
|
+
* @category schemas
|
|
22
|
+
* @since 0.1.0
|
|
23
|
+
*/
|
|
24
|
+
const ChatId = Schema.TemplateLiteral(["chat:", Uuid]).pipe(Schema.brand("~effect-assistant/ChatId"));
|
|
25
|
+
/**
|
|
26
|
+
* Creates a new random chat id using Effect's `Random`. Seed `Random` in tests to get the same ids
|
|
27
|
+
* on every run.
|
|
28
|
+
*
|
|
29
|
+
* @category constructors
|
|
30
|
+
* @since 0.1.0
|
|
31
|
+
*/
|
|
32
|
+
const make = () => Effect.map(make$1, (uuid) => ChatId.make(`chat:${uuid}`));
|
|
33
|
+
//#endregion
|
|
34
|
+
export { ChatId, ChatId_exports, make };
|
|
35
|
+
|
|
36
|
+
//# sourceMappingURL=ChatId.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChatId.js","names":["Uuid.Uuid","Uuid.make"],"sources":["../src/ChatId.ts"],"sourcesContent":["/**\n * This module provides the id of a chat, which is `chat:` followed by a UUID.\n *\n * A chat id keys everything about a chat: its log, its cluster entity, and its trace session.\n *\n * @since 0.1.0\n */\nimport * as Effect from \"effect/Effect\"\nimport * as Schema from \"effect/Schema\"\nimport * as Uuid from \"./internal/uuid.ts\"\n\n/**\n * Schema for chat ids of the form `` `chat:${uuid}` ``. The schema is branded, so a run id or a\n * plain string can't be passed where a chat id is required.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const ChatId = Schema.TemplateLiteral([\"chat:\", Uuid.Uuid]).pipe(Schema.brand(\"~effect-assistant/ChatId\"))\n\n/**\n * Represents a chat id.\n *\n * @category models\n * @since 0.1.0\n */\nexport type ChatId = typeof ChatId.Type\n\n/**\n * Creates a new random chat id using Effect's `Random`. Seed `Random` in tests to get the same ids\n * on every run.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const make = (): Effect.Effect<ChatId> => Effect.map(Uuid.make, (uuid) => ChatId.make(`chat:${uuid}`))\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAkBA,MAAa,SAAS,OAAO,gBAAgB,CAAC,SAASA,IAAS,CAAC,CAAC,CAAC,KAAK,OAAO,MAAM,0BAA0B,CAAC;;;;;;;;AAiBhH,MAAa,aAAoC,OAAO,IAAIC,SAAY,SAAS,OAAO,KAAK,QAAQ,MAAM,CAAC"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { ChatId } from "./ChatId.js";
|
|
2
|
+
import { AssistantEvent, AssistantEvent_d_exports, Entry, StoredAppEvent } from "./AssistantEvent.js";
|
|
3
|
+
import * as Context from "effect/Context";
|
|
4
|
+
import * as Effect from "effect/Effect";
|
|
5
|
+
import * as Layer from "effect/Layer";
|
|
6
|
+
import * as SqlClient from "effect/sql/SqlClient";
|
|
7
|
+
declare namespace ChatLog_d_exports {
|
|
8
|
+
export { ChatLog, ReadOptions, Storage, layerMemory, layerSql, make };
|
|
9
|
+
}
|
|
10
|
+
declare const ChatLog_base: Context.ServiceClass<ChatLog, "effect-assistant/ChatLog", {
|
|
11
|
+
/** Appends events to a chat and returns them as stored entries. */
|
|
12
|
+
readonly append: (chatId: ChatId, events: ReadonlyArray<AssistantEvent | StoredAppEvent>) => Effect.Effect<ReadonlyArray<Entry>>;
|
|
13
|
+
/** Reads a chat's stored entries in order, either all of them or only those after a `seq`. */
|
|
14
|
+
readonly read: (chatId: ChatId, options?: ReadOptions) => Effect.Effect<ReadonlyArray<Entry>>;
|
|
15
|
+
/**
|
|
16
|
+
* Returns the chats whose last entry ends neither a turn nor the chat. After a restart, these
|
|
17
|
+
* are the chats that may still owe a turn.
|
|
18
|
+
*/
|
|
19
|
+
readonly unfinished: Effect.Effect<ReadonlyArray<ChatId>>;
|
|
20
|
+
}>;
|
|
21
|
+
/**
|
|
22
|
+
* Service that stores each chat's events as an ordered list of entries.
|
|
23
|
+
*
|
|
24
|
+
* @category services
|
|
25
|
+
* @since 0.1.0
|
|
26
|
+
*/
|
|
27
|
+
export declare class ChatLog extends ChatLog_base {}
|
|
28
|
+
/**
|
|
29
|
+
* Represents the options that select which of a chat's entries to read.
|
|
30
|
+
*
|
|
31
|
+
* @category models
|
|
32
|
+
* @since 0.1.0
|
|
33
|
+
*/
|
|
34
|
+
export interface ReadOptions {
|
|
35
|
+
/** Reads only the entries after this `seq`. */
|
|
36
|
+
readonly after?: number | undefined;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Represents the storage that a `ChatLog` is built on.
|
|
40
|
+
*
|
|
41
|
+
* A storage reads a chat's entries, finds a chat's last `seq`, writes new entries, and finds every
|
|
42
|
+
* chat's last entry. Entries are never removed, so a deleted chat keeps its log, which ends in
|
|
43
|
+
* `ChatDeleted`.
|
|
44
|
+
*
|
|
45
|
+
* @category models
|
|
46
|
+
* @since 0.1.0
|
|
47
|
+
*/
|
|
48
|
+
export interface Storage {
|
|
49
|
+
readonly read: (chatId: ChatId, options?: ReadOptions) => Effect.Effect<ReadonlyArray<Entry>>;
|
|
50
|
+
readonly lastSeq: (chatId: ChatId) => Effect.Effect<number>;
|
|
51
|
+
readonly write: (chatId: ChatId, entries: ReadonlyArray<Entry>) => Effect.Effect<void>;
|
|
52
|
+
/** Returns the last entry of every chat. */
|
|
53
|
+
readonly lastEntries: Effect.Effect<ReadonlyArray<Entry>>;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Creates a `ChatLog` from a {@link Storage}. `append` numbers new entries after the chat's last
|
|
57
|
+
* `seq` and stamps them with the current time.
|
|
58
|
+
*
|
|
59
|
+
* @category constructors
|
|
60
|
+
* @since 0.1.0
|
|
61
|
+
*/
|
|
62
|
+
export declare const make: (storage: Storage) => ChatLog["Service"];
|
|
63
|
+
/**
|
|
64
|
+
* Layer that keeps every chat in memory. The log is lost when the process stops.
|
|
65
|
+
*
|
|
66
|
+
* @category layers
|
|
67
|
+
* @since 0.1.0
|
|
68
|
+
*/
|
|
69
|
+
export declare const layerMemory: Layer.Layer<ChatLog>;
|
|
70
|
+
/**
|
|
71
|
+
* Layer that stores chats in a SQL table named `chat_log`.
|
|
72
|
+
*
|
|
73
|
+
* **Details**
|
|
74
|
+
*
|
|
75
|
+
* The table must already exist. It has the columns `chat_id`, `seq`, `at`, and `event`, with the
|
|
76
|
+
* primary key `(chat_id, seq)`. The layer requires a `SqlClient`.
|
|
77
|
+
*
|
|
78
|
+
* **Example** (Storing chats in Postgres)
|
|
79
|
+
*
|
|
80
|
+
* ```ts
|
|
81
|
+
* import * as PgClient from "@effect/sql-pg/PgClient"
|
|
82
|
+
* import { ChatLog } from "effect-assistant"
|
|
83
|
+
* import * as Config from "effect/Config"
|
|
84
|
+
* import * as Layer from "effect/Layer"
|
|
85
|
+
*
|
|
86
|
+
* const ChatLogLive = ChatLog.layerSql.pipe(
|
|
87
|
+
* Layer.provide(PgClient.layerConfig({ url: Config.Redacted("DATABASE_URL") }))
|
|
88
|
+
* )
|
|
89
|
+
* ```
|
|
90
|
+
*
|
|
91
|
+
* The table, in Postgres:
|
|
92
|
+
*
|
|
93
|
+
* ```sql
|
|
94
|
+
* CREATE TABLE chat_log (
|
|
95
|
+
* chat_id TEXT NOT NULL,
|
|
96
|
+
* seq INTEGER NOT NULL,
|
|
97
|
+
* at DOUBLE PRECISION NOT NULL,
|
|
98
|
+
* event TEXT NOT NULL,
|
|
99
|
+
* PRIMARY KEY (chat_id, seq)
|
|
100
|
+
* );
|
|
101
|
+
* ```
|
|
102
|
+
*
|
|
103
|
+
* @category layers
|
|
104
|
+
* @since 0.1.0
|
|
105
|
+
*/
|
|
106
|
+
export declare const layerSql: Layer.Layer<ChatLog, never, SqlClient.SqlClient>;
|
|
107
|
+
//#endregion
|
|
108
|
+
export { ChatLog_d_exports };
|
|
109
|
+
//# sourceMappingURL=ChatLog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChatLog.d.ts","names":[],"sources":["../src/ChatLog.ts"],"mappings":";;;;;;;;;;;WA+BqB,SACf,QAAQ,QACR,QAAQ,cAAc,iBAAgC,oBACnD,OAAO,OAAO,cAAc;;WAElB,OAAC,QAAQ,QAAQ,UAAU,gBAAgB,OAAO,OAAO,cAAc;;;;;WAKjE,YAAA,OAAO,OAAO,cAAc;;;;;;;;qBAdxC,gBAAgB;;;;;;;iBAwBZ;;WAEN;;;;;;;;;;;;iBAaM;WACN,OAAO,QAAQ,QAAQ,UAAU,gBAAgB,OAAO,OAAO,cAAc;WAC7E,UAAU,QAAQ,WAAW,OAAO;WACpC,QAAQ,QAAQ,QAAQ,SAAS,cAAc,WAA0B,OAAO;;WAEhF,aAAa,OAAO,OAAO,cAAc;;;;;;;;;qBAUvC,OAAI,SAAa,YAAU;;;;;;;qBAiC3B,aAAa,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAwEzB,UAAU,MAAM,MAAM,gBAAgB,UAAU"}
|