effect-assistant 0.2.0 → 0.3.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/dist/Assistant.d.ts +74 -28
- package/dist/Assistant.d.ts.map +1 -1
- package/dist/Assistant.js +11 -4
- package/dist/Assistant.js.map +1 -1
- package/dist/AssistantActivity.d.ts +10 -16
- package/dist/AssistantActivity.d.ts.map +1 -1
- package/dist/AssistantActivity.js +11 -4
- package/dist/AssistantActivity.js.map +1 -1
- package/dist/AssistantEngine.d.ts +39 -1
- package/dist/AssistantEngine.d.ts.map +1 -1
- package/dist/AssistantEngine.js +81 -28
- package/dist/AssistantEngine.js.map +1 -1
- package/dist/AssistantEvent.d.ts +50 -5
- package/dist/AssistantEvent.d.ts.map +1 -1
- package/dist/AssistantEvent.js +28 -7
- package/dist/AssistantEvent.js.map +1 -1
- package/dist/BlockKit.d.ts +4 -4
- package/dist/BlockKit.js +1 -1
- package/dist/BlockKit.js.map +1 -1
- package/dist/ChatLog.js +1 -1
- package/dist/Compaction.d.ts +6 -2
- package/dist/Compaction.d.ts.map +1 -1
- package/dist/Compaction.js +6 -2
- package/dist/Compaction.js.map +1 -1
- package/dist/CurrentChat.d.ts +2 -1
- package/dist/CurrentChat.d.ts.map +1 -1
- package/dist/CurrentChat.js.map +1 -1
- package/dist/Instructions.js +1 -1
- package/dist/Skill.d.ts +33 -0
- package/dist/Skill.d.ts.map +1 -1
- package/dist/Skill.js +27 -1
- package/dist/Skill.js.map +1 -1
- package/dist/Specialist.d.ts +4 -1
- package/dist/Specialist.d.ts.map +1 -1
- package/dist/Specialist.js.map +1 -1
- package/dist/Timeline.d.ts +482 -0
- package/dist/Timeline.d.ts.map +1 -0
- package/dist/Timeline.js +572 -0
- package/dist/Timeline.js.map +1 -0
- package/dist/index.d.ts +2 -3
- package/dist/index.js +2 -3
- package/dist/internal/chat.js +5 -6
- package/dist/internal/chat.js.map +1 -1
- package/dist/internal/events.d.ts +15 -16
- package/dist/internal/events.d.ts.map +1 -1
- package/dist/internal/events.js +29 -28
- package/dist/internal/events.js.map +1 -1
- package/dist/internal/history.js +25 -19
- package/dist/internal/history.js.map +1 -1
- package/dist/internal/input.js +7 -0
- package/dist/internal/input.js.map +1 -0
- package/dist/internal/loop.js +1 -1
- package/dist/internal/skills.js +6 -1
- package/dist/internal/skills.js.map +1 -1
- package/dist/internal/specialists.js +5 -4
- package/dist/internal/specialists.js.map +1 -1
- package/dist/internal/tools.js +1 -1
- package/dist/internal/turns.d.ts.map +1 -1
- package/dist/internal/turns.js +23 -35
- package/dist/internal/turns.js.map +1 -1
- package/package.json +1 -1
- package/src/Assistant.ts +75 -29
- package/src/AssistantActivity.ts +11 -18
- package/src/AssistantEngine.ts +110 -43
- package/src/AssistantEvent.ts +46 -8
- package/src/BlockKit.ts +6 -6
- package/src/Compaction.ts +6 -2
- package/src/CurrentChat.ts +2 -1
- package/src/Skill.ts +34 -1
- package/src/Specialist.ts +4 -1
- package/src/Timeline.ts +861 -0
- package/src/index.ts +5 -10
- package/src/internal/chat.ts +4 -4
- package/src/internal/events.ts +43 -43
- package/src/internal/history.ts +36 -29
- package/src/internal/input.ts +3 -0
- package/src/internal/skills.ts +11 -0
- package/src/internal/specialists.ts +3 -2
- package/src/internal/turns.ts +30 -53
- package/dist/AppEvent.d.ts +0 -111
- package/dist/AppEvent.d.ts.map +0 -1
- package/dist/AppEvent.js +0 -71
- package/dist/AppEvent.js.map +0 -1
- package/dist/ChatView.d.ts +0 -408
- package/dist/ChatView.d.ts.map +0 -1
- package/dist/ChatView.js +0 -443
- package/dist/ChatView.js.map +0 -1
- package/src/AppEvent.ts +0 -134
- package/src/ChatView.ts +0 -587
package/dist/index.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { AppEvent_exports } from "./AppEvent.js";
|
|
2
1
|
import { ChatId_exports } from "./ChatId.js";
|
|
3
2
|
import { InputId_exports } from "./InputId.js";
|
|
4
3
|
import { RunId_exports } from "./RunId.js";
|
|
@@ -17,8 +16,8 @@ import { AssistantActivity_exports } from "./AssistantActivity.js";
|
|
|
17
16
|
import { AssistantLoop_exports } from "./AssistantLoop.js";
|
|
18
17
|
import { Block_exports } from "./Block.js";
|
|
19
18
|
import { BlockKit_exports } from "./BlockKit.js";
|
|
20
|
-
import { ChatView_exports } from "./ChatView.js";
|
|
21
19
|
import { Compaction_exports } from "./Compaction.js";
|
|
22
20
|
import { Memory_exports } from "./Memory.js";
|
|
21
|
+
import { Timeline_exports } from "./Timeline.js";
|
|
23
22
|
import { ToolDiscovery_exports } from "./ToolDiscovery.js";
|
|
24
|
-
export {
|
|
23
|
+
export { Assistant_exports as Assistant, AssistantActivity_exports as AssistantActivity, AssistantEngine_exports as AssistantEngine, AssistantEvent_exports as AssistantEvent, AssistantLoop_exports as AssistantLoop, AssistantToolCall_exports as AssistantToolCall, Block_exports as Block, BlockKit_exports as BlockKit, ChatId_exports as ChatId, ChatLog_exports as ChatLog, Chats_exports as Chats, Compaction_exports as Compaction, CurrentChat_exports as CurrentChat, InputId_exports as InputId, Instructions_exports as Instructions, Memory_exports as Memory, RunId_exports as RunId, Skill_exports as Skill, Specialist_exports as Specialist, Timeline_exports as Timeline, ToolDiscovery_exports as ToolDiscovery, ToolFailure_exports as ToolFailure };
|
package/dist/internal/chat.js
CHANGED
|
@@ -3,13 +3,13 @@ import { ChatLog } from "../ChatLog.js";
|
|
|
3
3
|
import { CurrentChat } from "../CurrentChat.js";
|
|
4
4
|
import { Chats } from "../Chats.js";
|
|
5
5
|
import { Runner } from "../Specialist.js";
|
|
6
|
-
import { decodeEntries, encode, isRegistered } from "./events.js";
|
|
6
|
+
import { decodeEntries, encode, isRegistered, markInput } from "./events.js";
|
|
7
7
|
import { needsTurn } from "./history.js";
|
|
8
8
|
import { makeRunner } from "./specialists.js";
|
|
9
9
|
import { run } from "./turns.js";
|
|
10
|
-
import * as Predicate from "effect/Predicate";
|
|
11
10
|
import * as Context from "effect/Context";
|
|
12
11
|
import * as Effect from "effect/Effect";
|
|
12
|
+
import * as Predicate from "effect/Predicate";
|
|
13
13
|
import * as Stream from "effect/Stream";
|
|
14
14
|
import * as Exit from "effect/Exit";
|
|
15
15
|
import * as Scope from "effect/Scope";
|
|
@@ -51,7 +51,6 @@ const refused = (input, why) => Effect.logDebug(`assistant chat ${why}: ${input.
|
|
|
51
51
|
const make = Effect.fnUntraced(function* (options) {
|
|
52
52
|
const { registration, chatId } = options;
|
|
53
53
|
const { app } = registration.definition;
|
|
54
|
-
const { wakes } = app;
|
|
55
54
|
const storage = yield* ChatLog;
|
|
56
55
|
const chats = yield* Chats;
|
|
57
56
|
const initial = yield* storage.read(chatId);
|
|
@@ -98,7 +97,7 @@ const make = Effect.fnUntraced(function* (options) {
|
|
|
98
97
|
if (yield* isDeleted) return yield* refused(input, "is deleted");
|
|
99
98
|
const id = idOf(input);
|
|
100
99
|
if (id !== void 0 && (yield* Ref.get(entries)).some(({ event }) => idOf(event) === id)) return;
|
|
101
|
-
yield* write([input]);
|
|
100
|
+
yield* write([isLibraryEvent(input) ? input : markInput(input)]);
|
|
102
101
|
}));
|
|
103
102
|
});
|
|
104
103
|
const handler = yield* inWorking(Effect.provideService(registration.open(handle), CurrentChat, handle));
|
|
@@ -115,7 +114,7 @@ const make = Effect.fnUntraced(function* (options) {
|
|
|
115
114
|
if (Exit.isFailure(exit) && !Cause.hasInterruptsOnly(exit.cause)) return yield* Effect.failCause(exit.cause);
|
|
116
115
|
}, Effect.orDie);
|
|
117
116
|
const drain = Semaphore.withPermit(turnsLock)(Effect.gen(function* () {
|
|
118
|
-
while (!(yield* isDeleted) && needsTurn(yield* chat.entries
|
|
117
|
+
while (!(yield* isDeleted) && needsTurn(yield* chat.entries)) yield* run({
|
|
119
118
|
registration,
|
|
120
119
|
handler,
|
|
121
120
|
chat,
|
|
@@ -158,7 +157,7 @@ const make = Effect.fnUntraced(function* (options) {
|
|
|
158
157
|
yield* inBackground(drain);
|
|
159
158
|
}),
|
|
160
159
|
resume: Effect.gen(function* () {
|
|
161
|
-
if (needsTurn(yield* chat.entries
|
|
160
|
+
if (needsTurn(yield* chat.entries)) yield* inBackground(drain);
|
|
162
161
|
}),
|
|
163
162
|
stream: (after) => Stream.unwrap(Effect.gen(function* () {
|
|
164
163
|
const subscription = yield* PubSub.subscribe(channel);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat.js","names":["AssistantEvent.isLibraryEvent","AssistantEvent.isInput","Chats.Chats","AppEvents.isRegistered","AppEvents.encode","AppEvents.decodeEntries","History.needsTurn","Turns.run","Specialists.makeRunner","SpecialistService.Runner"],"sources":["../../src/internal/chat.ts"],"sourcesContent":["import * as Cause from \"effect/Cause\"\nimport * as Context from \"effect/Context\"\nimport * as Deferred from \"effect/Deferred\"\nimport * as Effect from \"effect/Effect\"\nimport * as Exit from \"effect/Exit\"\nimport * as Fiber from \"effect/Fiber\"\nimport * as FiberSet from \"effect/FiberSet\"\nimport * as Predicate from \"effect/Predicate\"\nimport * as PubSub from \"effect/PubSub\"\nimport * as Ref from \"effect/Ref\"\nimport * as Scope from \"effect/Scope\"\nimport * as Semaphore from \"effect/Semaphore\"\nimport * as Stream from \"effect/Stream\"\nimport * as SynchronizedRef from \"effect/SynchronizedRef\"\nimport * as AssistantEvent from \"../AssistantEvent.ts\"\nimport type { ChatId } from \"../ChatId.ts\"\nimport { ChatLog } from \"../ChatLog.ts\"\nimport { CurrentChat } from \"../CurrentChat.ts\"\nimport * as Chats from \"../Chats.ts\"\nimport * as SpecialistService from \"../Specialist.ts\"\nimport * as AppEvents from \"./events.ts\"\nimport * as History from \"./history.ts\"\nimport * as Specialists from \"./specialists.ts\"\nimport * as Turns from \"./turns.ts\"\nimport type { Writer } from \"./writer.ts\"\n\nconst idOf = (event: { readonly _tag: string }): string | undefined =>\n AssistantEvent.isLibraryEvent(event)\n ? AssistantEvent.isInput(event)\n ? event.id\n : undefined\n : Predicate.hasProperty(event, \"id\") && Predicate.isString(event.id)\n ? event.id\n : undefined\n\nconst makeChannelPubSub = Effect.acquireRelease(PubSub.unbounded<AssistantEvent.Entry>(), PubSub.shutdown)\n\nconst makeHolding = Effect.fnUntraced(function* (keepAlive: (enabled: boolean) => Effect.Effect<void>) {\n const holds = yield* SynchronizedRef.make(0)\n\n const acquire = SynchronizedRef.updateEffect(\n holds,\n Effect.fnUntraced(function* (n: number) {\n if (n !== 0) return n + 1\n\n yield* keepAlive(true)\n\n return 1\n })\n )\n\n const release = SynchronizedRef.updateEffect(\n holds,\n Effect.fnUntraced(function* (n: number) {\n if (n !== 1) return n - 1\n\n yield* keepAlive(false)\n\n return 0\n })\n )\n\n return (effect: Effect.Effect<void>) =>\n Effect.acquireUseRelease(\n acquire,\n () => effect,\n // An interrupted effect (a shutdown) keeps its hold, so the chat starts again and picks up where it was cut off.\n (_, exit) => (Exit.hasInterrupts(exit) ? Effect.void : release)\n )\n})\n\n/** @internal */\nexport interface Chat {\n readonly submit: (input: AssistantEvent.Input | AssistantEvent.StoredAppEvent) => Effect.Effect<void>\n readonly submitInBackground: (input: AssistantEvent.Input | AssistantEvent.StoredAppEvent) => Effect.Effect<void>\n // For inputs no turn has read, like a report stored during a shutdown.\n readonly resume: Effect.Effect<void>\n readonly stream: (after: number) => Stream.Stream<AssistantEvent.Entry>\n // Stops what works in the chat and ends its subscribers' streams; its log stays.\n readonly delete: Effect.Effect<void>\n}\n\nconst isChatDeleted = (entry: AssistantEvent.Entry) => Predicate.isTagged(entry.event, \"ChatDeleted\")\n\nconst deletedChat = (entries: ReadonlyArray<AssistantEvent.Entry>): Chat => ({\n submit: () => Effect.void,\n submitInBackground: () => Effect.void,\n resume: Effect.void,\n stream: (after) => Stream.fromIterable(entries.filter((entry) => entry.seq > after)),\n delete: Effect.void\n})\n\nconst refused = (input: { readonly _tag: string }, why: string) =>\n Effect.logDebug(`assistant chat ${why}: ${input._tag} not stored`)\n\n/** @internal */\nexport const make = Effect.fnUntraced(function* (options: {\n readonly registration: Turns.Registration\n readonly chatId: ChatId\n readonly keepAlive: (enabled: boolean) => Effect.Effect<void>\n}) {\n const { registration, chatId } = options\n const { app } = registration.definition\n const { wakes } = app\n const storage = yield* ChatLog\n const chats = yield* Chats.Chats\n const initial = yield* storage.read(chatId)\n\n if (initial.some(isChatDeleted)) return deletedChat(initial)\n\n const channel = yield* makeChannelPubSub\n const writes = yield* Semaphore.make(1)\n const turnsLock = yield* Semaphore.make(1)\n const holding = yield* makeHolding(options.keepAlive)\n const deleted = yield* Deferred.make<undefined>()\n // So deleting the chat stops all its work: its turns, runs, and the builds' resources.\n const working = yield* Scope.fork(yield* Effect.scope)\n const inWorking = Effect.provideService(Scope.Scope, working)\n const background = yield* inWorking(FiberSet.make())\n const requests = yield* inWorking(FiberSet.make())\n\n const isDeleted = Deferred.isDone(deleted)\n\n // The chat is its log's only writer, so it reads the log once and turns read memory.\n const entries = yield* Ref.make(initial)\n\n // Every writer goes through here, one at a time, so each append is numbered after the last. After a delete it\n // stores nothing.\n const write = Effect.fnUntraced(function* (\n events: ReadonlyArray<AssistantEvent.AssistantEvent | AssistantEvent.StoredAppEvent>\n ) {\n const unknown = events.find((event) => !AssistantEvent.isLibraryEvent(event) && !AppEvents.isRegistered(app, event))\n\n if (unknown !== undefined) {\n return yield* Effect.die(`${unknown._tag} isn't one of the ${registration.definition.name} assistant's events.`)\n }\n\n if (yield* isDeleted) return []\n\n return yield* stored(events)\n })\n\n // Uninterruptible: a stored append must reach memory and the subscribers too, or a turn misses it and replies twice.\n const stored = Effect.fnUntraced(function* (\n events: ReadonlyArray<AssistantEvent.AssistantEvent | AssistantEvent.StoredAppEvent>\n ) {\n const stored = yield* storage.append(chatId, events)\n\n yield* Ref.update(entries, (all) => [...all, ...stored])\n yield* PubSub.publishAll(channel, stored)\n\n return stored\n }, Effect.uninterruptible)\n\n const chat: Writer = {\n chatId,\n entries: Ref.get(entries),\n append: (events) => Semaphore.withPermit(writes)(write(events))\n }\n\n const toStored = (event: {\n readonly _tag: string\n }): Effect.Effect<AssistantEvent.AssistantEvent | AssistantEvent.StoredAppEvent> =>\n AssistantEvent.isLibraryEvent(event)\n ? Effect.succeed(event)\n : AppEvents.isRegistered(app, event)\n ? AppEvents.encode(app, event)\n : Effect.die(`${event._tag} isn't one of the ${registration.definition.name} assistant's events.`)\n\n // What handlers, tools, and runs see: the assistant's own events decoded, and encoded when appended.\n const handle: Turns.TypedChat = {\n chatId,\n entries: Effect.map(chat.entries, (all) => AppEvents.decodeEntries(app, all)),\n append: (...events) => Effect.asVoid(Effect.flatMap(Effect.forEach(events, toStored), chat.append))\n }\n\n // A chat the app deleted is deleted here too, which stops what works in it: a turn that checks stops there.\n const checkAvailable: Effect.Effect<boolean> = chats.get(chatId).pipe(\n Effect.as(true),\n Effect.catchTag(\"GetChatError\", (unavailable) =>\n unavailable.reason === \"Deleted\"\n ? Effect.as(\n Effect.suspend(() => remove),\n false\n )\n : Effect.succeed(false)\n )\n )\n\n const store = Effect.fnUntraced(function* (input: AssistantEvent.Input | AssistantEvent.StoredAppEvent) {\n if (yield* isDeleted) return yield* refused(input, \"is deleted\")\n\n if (!(yield* checkAvailable)) return yield* refused(input, \"is unavailable\")\n\n // Checked under the write lock: a retry that arrives while the first submit is storing is stored once.\n yield* Semaphore.withPermit(writes)(\n Effect.gen(function* () {\n if (yield* isDeleted) return yield* refused(input, \"is deleted\")\n\n const id = idOf(input)\n\n if (id !== undefined && (yield* Ref.get(entries)).some(({ event }) => idOf(event) === id)) return\n\n yield* write([input])\n })\n )\n })\n\n // The handlers' builds run in the chat's working scope: what they set up lives until the chat closes or is deleted.\n const handler = yield* inWorking(Effect.provideService(registration.open(handle), CurrentChat, handle))\n\n const specialists = yield* Effect.forEach(\n registration.specialists,\n Effect.fnUntraced(function* ({ specialist, built }) {\n const handler = yield* inWorking(Effect.provideService(built.open, CurrentChat, handle))\n\n return { specialist, handler }\n })\n )\n\n const inBackground = (effect: Effect.Effect<void>) => FiberSet.run(background, holding(effect)).pipe(Effect.asVoid)\n\n // A request's turns run in the working scope too, so a delete stops them; the request ends quietly then.\n const inRequest = Effect.fnUntraced(function* (effect: Effect.Effect<void>) {\n const fiber = yield* FiberSet.run(requests, effect)\n const exit = yield* Fiber.await(fiber).pipe(Effect.onInterrupt(() => Fiber.interrupt(fiber)))\n\n if (Exit.isFailure(exit) && !Cause.hasInterruptsOnly(exit.cause)) {\n return yield* Effect.failCause(exit.cause)\n }\n }, Effect.orDie)\n\n const drain: Effect.Effect<void> = Semaphore.withPermit(turnsLock)(\n Effect.gen(function* () {\n while (!(yield* isDeleted) && History.needsTurn(yield* chat.entries, wakes)) {\n yield* Turns.run({ registration, handler, chat, services, checkOpen: Effect.asVoid(checkAvailable) })\n }\n })\n )\n\n const runner = yield* inWorking(\n Specialists.makeRunner({\n chat,\n handle,\n specialists,\n model: registration.model,\n holding,\n report: Effect.fnUntraced(function* (finished) {\n yield* store(finished)\n yield* inBackground(drain)\n })\n })\n )\n\n const services = Context.make(CurrentChat, handle).pipe(\n Context.add(SpecialistService.Runner, runner),\n Context.merge(registration.model)\n )\n\n const remove = Effect.gen(function* () {\n // Under the write lock: an append in flight is stored before the `ChatDeleted`; none comes after.\n const first = yield* Semaphore.withPermit(writes)(\n Effect.gen(function* () {\n if (yield* isDeleted) return false\n\n yield* stored([AssistantEvent.ChatDeleted.make({})])\n\n return yield* Deferred.succeed(deleted, undefined)\n })\n ).pipe(Effect.uninterruptible)\n\n if (!first) return\n\n // Its own fiber: a tool that deletes its own chat is in the working scope, and closing it interrupts that tool.\n // Interrupted work keeps its hold (see `makeHolding`), so the chat lets go of it here.\n const closing = yield* Effect.forkDetach(\n Effect.gen(function* () {\n yield* Scope.close(working, Exit.void)\n yield* options.keepAlive(false)\n })\n )\n\n yield* Fiber.await(closing)\n })\n\n // A chat the app deleted while it was closed is deleted now, so its subscribers hear it.\n yield* checkAvailable\n\n const chatApi: Chat = {\n submit: Effect.fnUntraced(function* (input) {\n yield* store(input)\n yield* inRequest(drain)\n }),\n submitInBackground: Effect.fnUntraced(function* (input) {\n yield* store(input)\n yield* inBackground(drain)\n }),\n resume: Effect.gen(function* () {\n if (History.needsTurn(yield* chat.entries, wakes)) yield* inBackground(drain)\n }),\n stream: (after) =>\n Stream.unwrap(\n Effect.gen(function* () {\n // Subscribes before it reads, so nothing appended in between is missed.\n const subscription = yield* PubSub.subscribe(channel)\n const all = yield* chat.entries\n const stored = all.filter((entry) => entry.seq > after)\n const last = stored.at(-1)?.seq ?? after\n\n // Deleted: what the reader hasn't seen, then the end. Otherwise the stream ends with the `ChatDeleted`.\n if (all.some(isChatDeleted)) return Stream.fromIterable(stored)\n\n return Stream.concat(\n Stream.fromIterable(stored),\n Stream.fromSubscription(subscription).pipe(Stream.filter((entry) => entry.seq > last))\n ).pipe(Stream.takeUntil(isChatDeleted))\n })\n ),\n delete: remove\n }\n\n return chatApi\n})\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"chat.js","names":["AssistantEvent.isLibraryEvent","AssistantEvent.isInput","Chats.Chats","AppEvents.isRegistered","AppEvents.encode","AppEvents.decodeEntries","AppEvents.markInput","History.needsTurn","Turns.run","Specialists.makeRunner","SpecialistService.Runner"],"sources":["../../src/internal/chat.ts"],"sourcesContent":["import * as Cause from \"effect/Cause\"\nimport * as Context from \"effect/Context\"\nimport * as Deferred from \"effect/Deferred\"\nimport * as Effect from \"effect/Effect\"\nimport * as Exit from \"effect/Exit\"\nimport * as Fiber from \"effect/Fiber\"\nimport * as FiberSet from \"effect/FiberSet\"\nimport * as Predicate from \"effect/Predicate\"\nimport * as PubSub from \"effect/PubSub\"\nimport * as Ref from \"effect/Ref\"\nimport * as Scope from \"effect/Scope\"\nimport * as Semaphore from \"effect/Semaphore\"\nimport * as Stream from \"effect/Stream\"\nimport * as SynchronizedRef from \"effect/SynchronizedRef\"\nimport * as AssistantEvent from \"../AssistantEvent.ts\"\nimport type { ChatId } from \"../ChatId.ts\"\nimport { ChatLog } from \"../ChatLog.ts\"\nimport { CurrentChat } from \"../CurrentChat.ts\"\nimport * as Chats from \"../Chats.ts\"\nimport * as SpecialistService from \"../Specialist.ts\"\nimport * as AppEvents from \"./events.ts\"\nimport * as History from \"./history.ts\"\nimport * as Specialists from \"./specialists.ts\"\nimport * as Turns from \"./turns.ts\"\nimport type { Writer } from \"./writer.ts\"\n\nconst idOf = (event: { readonly _tag: string }): string | undefined =>\n AssistantEvent.isLibraryEvent(event)\n ? AssistantEvent.isInput(event)\n ? event.id\n : undefined\n : Predicate.hasProperty(event, \"id\") && Predicate.isString(event.id)\n ? event.id\n : undefined\n\nconst makeChannelPubSub = Effect.acquireRelease(PubSub.unbounded<AssistantEvent.Entry>(), PubSub.shutdown)\n\nconst makeHolding = Effect.fnUntraced(function* (keepAlive: (enabled: boolean) => Effect.Effect<void>) {\n const holds = yield* SynchronizedRef.make(0)\n\n const acquire = SynchronizedRef.updateEffect(\n holds,\n Effect.fnUntraced(function* (n: number) {\n if (n !== 0) return n + 1\n\n yield* keepAlive(true)\n\n return 1\n })\n )\n\n const release = SynchronizedRef.updateEffect(\n holds,\n Effect.fnUntraced(function* (n: number) {\n if (n !== 1) return n - 1\n\n yield* keepAlive(false)\n\n return 0\n })\n )\n\n return (effect: Effect.Effect<void>) =>\n Effect.acquireUseRelease(\n acquire,\n () => effect,\n // An interrupted effect (a shutdown) keeps its hold, so the chat starts again and picks up where it was cut off.\n (_, exit) => (Exit.hasInterrupts(exit) ? Effect.void : release)\n )\n})\n\n/** @internal */\nexport interface Chat {\n readonly submit: (input: AssistantEvent.Input | AssistantEvent.StoredAppEvent) => Effect.Effect<void>\n readonly submitInBackground: (input: AssistantEvent.Input | AssistantEvent.StoredAppEvent) => Effect.Effect<void>\n // For inputs no turn has read, like a report stored during a shutdown.\n readonly resume: Effect.Effect<void>\n readonly stream: (after: number) => Stream.Stream<AssistantEvent.Entry>\n // Stops what works in the chat and ends its subscribers' streams; its log stays.\n readonly delete: Effect.Effect<void>\n}\n\nconst isChatDeleted = (entry: AssistantEvent.Entry) => Predicate.isTagged(entry.event, \"ChatDeleted\")\n\nconst deletedChat = (entries: ReadonlyArray<AssistantEvent.Entry>): Chat => ({\n submit: () => Effect.void,\n submitInBackground: () => Effect.void,\n resume: Effect.void,\n stream: (after) => Stream.fromIterable(entries.filter((entry) => entry.seq > after)),\n delete: Effect.void\n})\n\nconst refused = (input: { readonly _tag: string }, why: string) =>\n Effect.logDebug(`assistant chat ${why}: ${input._tag} not stored`)\n\n/** @internal */\nexport const make = Effect.fnUntraced(function* (options: {\n readonly registration: Turns.Registration\n readonly chatId: ChatId\n readonly keepAlive: (enabled: boolean) => Effect.Effect<void>\n}) {\n const { registration, chatId } = options\n const { app } = registration.definition\n const storage = yield* ChatLog\n const chats = yield* Chats.Chats\n const initial = yield* storage.read(chatId)\n\n if (initial.some(isChatDeleted)) return deletedChat(initial)\n\n const channel = yield* makeChannelPubSub\n const writes = yield* Semaphore.make(1)\n const turnsLock = yield* Semaphore.make(1)\n const holding = yield* makeHolding(options.keepAlive)\n const deleted = yield* Deferred.make<undefined>()\n // So deleting the chat stops all its work: its turns, runs, and the builds' resources.\n const working = yield* Scope.fork(yield* Effect.scope)\n const inWorking = Effect.provideService(Scope.Scope, working)\n const background = yield* inWorking(FiberSet.make())\n const requests = yield* inWorking(FiberSet.make())\n\n const isDeleted = Deferred.isDone(deleted)\n\n // The chat is its log's only writer, so it reads the log once and turns read memory.\n const entries = yield* Ref.make(initial)\n\n // Every writer goes through here, one at a time, so each append is numbered after the last. After a delete it\n // stores nothing.\n const write = Effect.fnUntraced(function* (\n events: ReadonlyArray<AssistantEvent.AssistantEvent | AssistantEvent.StoredAppEvent>\n ) {\n const unknown = events.find((event) => !AssistantEvent.isLibraryEvent(event) && !AppEvents.isRegistered(app, event))\n\n if (unknown !== undefined) {\n return yield* Effect.die(`${unknown._tag} isn't one of the ${registration.definition.name} assistant's events.`)\n }\n\n if (yield* isDeleted) return []\n\n return yield* stored(events)\n })\n\n // Uninterruptible: a stored append must reach memory and the subscribers too, or a turn misses it and replies twice.\n const stored = Effect.fnUntraced(function* (\n events: ReadonlyArray<AssistantEvent.AssistantEvent | AssistantEvent.StoredAppEvent>\n ) {\n const stored = yield* storage.append(chatId, events)\n\n yield* Ref.update(entries, (all) => [...all, ...stored])\n yield* PubSub.publishAll(channel, stored)\n\n return stored\n }, Effect.uninterruptible)\n\n const chat: Writer = {\n chatId,\n entries: Ref.get(entries),\n append: (events) => Semaphore.withPermit(writes)(write(events))\n }\n\n const toStored = (event: {\n readonly _tag: string\n }): Effect.Effect<AssistantEvent.AssistantEvent | AssistantEvent.StoredAppEvent> =>\n AssistantEvent.isLibraryEvent(event)\n ? Effect.succeed(event)\n : AppEvents.isRegistered(app, event)\n ? AppEvents.encode(app, event)\n : Effect.die(`${event._tag} isn't one of the ${registration.definition.name} assistant's events.`)\n\n // What handlers, tools, and runs see: the assistant's own events decoded, and encoded when appended.\n const handle: Turns.TypedChat = {\n chatId,\n entries: Effect.map(chat.entries, (all) => AppEvents.decodeEntries(app, all)),\n append: (...events) => Effect.asVoid(Effect.flatMap(Effect.forEach(events, toStored), chat.append))\n }\n\n // A chat the app deleted is deleted here too, which stops what works in it: a turn that checks stops there.\n const checkAvailable: Effect.Effect<boolean> = chats.get(chatId).pipe(\n Effect.as(true),\n Effect.catchTag(\"GetChatError\", (unavailable) =>\n unavailable.reason === \"Deleted\"\n ? Effect.as(\n Effect.suspend(() => remove),\n false\n )\n : Effect.succeed(false)\n )\n )\n\n const store = Effect.fnUntraced(function* (input: AssistantEvent.Input | AssistantEvent.StoredAppEvent) {\n if (yield* isDeleted) return yield* refused(input, \"is deleted\")\n\n if (!(yield* checkAvailable)) return yield* refused(input, \"is unavailable\")\n\n // Checked under the write lock: a retry that arrives while the first submit is storing is stored once.\n yield* Semaphore.withPermit(writes)(\n Effect.gen(function* () {\n if (yield* isDeleted) return yield* refused(input, \"is deleted\")\n\n const id = idOf(input)\n\n if (id !== undefined && (yield* Ref.get(entries)).some(({ event }) => idOf(event) === id)) return\n\n // Stored as an input: it came in from outside a turn, so it wakes one, also after a restart.\n yield* write([AssistantEvent.isLibraryEvent(input) ? input : AppEvents.markInput(input)])\n })\n )\n })\n\n // The handlers' builds run in the chat's working scope: what they set up lives until the chat closes or is deleted.\n const handler = yield* inWorking(Effect.provideService(registration.open(handle), CurrentChat, handle))\n\n const specialists = yield* Effect.forEach(\n registration.specialists,\n Effect.fnUntraced(function* ({ specialist, built }) {\n const handler = yield* inWorking(Effect.provideService(built.open, CurrentChat, handle))\n\n return { specialist, handler }\n })\n )\n\n const inBackground = (effect: Effect.Effect<void>) => FiberSet.run(background, holding(effect)).pipe(Effect.asVoid)\n\n // A request's turns run in the working scope too, so a delete stops them; the request ends quietly then.\n const inRequest = Effect.fnUntraced(function* (effect: Effect.Effect<void>) {\n const fiber = yield* FiberSet.run(requests, effect)\n const exit = yield* Fiber.await(fiber).pipe(Effect.onInterrupt(() => Fiber.interrupt(fiber)))\n\n if (Exit.isFailure(exit) && !Cause.hasInterruptsOnly(exit.cause)) {\n return yield* Effect.failCause(exit.cause)\n }\n }, Effect.orDie)\n\n const drain: Effect.Effect<void> = Semaphore.withPermit(turnsLock)(\n Effect.gen(function* () {\n while (!(yield* isDeleted) && History.needsTurn(yield* chat.entries)) {\n yield* Turns.run({ registration, handler, chat, services, checkOpen: Effect.asVoid(checkAvailable) })\n }\n })\n )\n\n const runner = yield* inWorking(\n Specialists.makeRunner({\n chat,\n handle,\n specialists,\n model: registration.model,\n holding,\n report: Effect.fnUntraced(function* (finished) {\n yield* store(finished)\n yield* inBackground(drain)\n })\n })\n )\n\n const services = Context.make(CurrentChat, handle).pipe(\n Context.add(SpecialistService.Runner, runner),\n Context.merge(registration.model)\n )\n\n const remove = Effect.gen(function* () {\n // Under the write lock: an append in flight is stored before the `ChatDeleted`; none comes after.\n const first = yield* Semaphore.withPermit(writes)(\n Effect.gen(function* () {\n if (yield* isDeleted) return false\n\n yield* stored([AssistantEvent.ChatDeleted.make({})])\n\n return yield* Deferred.succeed(deleted, undefined)\n })\n ).pipe(Effect.uninterruptible)\n\n if (!first) return\n\n // Its own fiber: a tool that deletes its own chat is in the working scope, and closing it interrupts that tool.\n // Interrupted work keeps its hold (see `makeHolding`), so the chat lets go of it here.\n const closing = yield* Effect.forkDetach(\n Effect.gen(function* () {\n yield* Scope.close(working, Exit.void)\n yield* options.keepAlive(false)\n })\n )\n\n yield* Fiber.await(closing)\n })\n\n // A chat the app deleted while it was closed is deleted now, so its subscribers hear it.\n yield* checkAvailable\n\n const chatApi: Chat = {\n submit: Effect.fnUntraced(function* (input) {\n yield* store(input)\n yield* inRequest(drain)\n }),\n submitInBackground: Effect.fnUntraced(function* (input) {\n yield* store(input)\n yield* inBackground(drain)\n }),\n resume: Effect.gen(function* () {\n if (History.needsTurn(yield* chat.entries)) yield* inBackground(drain)\n }),\n stream: (after) =>\n Stream.unwrap(\n Effect.gen(function* () {\n // Subscribes before it reads, so nothing appended in between is missed.\n const subscription = yield* PubSub.subscribe(channel)\n const all = yield* chat.entries\n const stored = all.filter((entry) => entry.seq > after)\n const last = stored.at(-1)?.seq ?? after\n\n // Deleted: what the reader hasn't seen, then the end. Otherwise the stream ends with the `ChatDeleted`.\n if (all.some(isChatDeleted)) return Stream.fromIterable(stored)\n\n return Stream.concat(\n Stream.fromIterable(stored),\n Stream.fromSubscription(subscription).pipe(Stream.filter((entry) => entry.seq > last))\n ).pipe(Stream.takeUntil(isChatDeleted))\n })\n ),\n delete: remove\n }\n\n return chatApi\n})\n"],"mappings":"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"}
|
|
@@ -1,46 +1,45 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { AnyEntry, AssistantEvent_d_exports } from "../AssistantEvent.js";
|
|
1
|
+
import { AnyEntry, AppEventSchema, AssistantEvent_d_exports, StoredAppEvent } from "../AssistantEvent.js";
|
|
3
2
|
import { Assistant_d_exports, Entry } from "../Assistant.js";
|
|
4
3
|
import * as Effect from "effect/Effect";
|
|
5
4
|
import * as Schema from "effect/Schema";
|
|
6
5
|
import * as Option from "effect/Option";
|
|
7
6
|
declare namespace events_d_exports {
|
|
8
|
-
export { AppEvents, DecodedEntries, decodeEntries, decodeEntry, encode, isRegistered, make,
|
|
7
|
+
export { AppEvents, DecodedEntries, decodeEntries, decodeEntry, decodeWith, encode, isRegistered, make, markInput, tagOf };
|
|
9
8
|
}
|
|
10
9
|
/** @internal */
|
|
11
10
|
export interface AppEvents {
|
|
12
|
-
readonly byTag: ReadonlyMap<string,
|
|
13
|
-
readonly wakes: ReadonlySet<string>;
|
|
14
|
-
}
|
|
15
|
-
interface Registered {
|
|
16
|
-
readonly schema: EventSchema;
|
|
17
|
-
readonly render: Render<{
|
|
18
|
-
readonly _tag: string;
|
|
19
|
-
}> | undefined;
|
|
11
|
+
readonly byTag: ReadonlyMap<string, AppEventSchema>;
|
|
20
12
|
}
|
|
21
13
|
/** @internal */
|
|
22
|
-
export declare const
|
|
14
|
+
export declare const tagOf: (schema: AppEventSchema) => string;
|
|
15
|
+
/** @internal */
|
|
16
|
+
export declare const make: (events: ReadonlyArray<AppEventSchema>) => AppEvents;
|
|
23
17
|
/** @internal */
|
|
24
18
|
export declare const encode: (app: AppEvents, event: {
|
|
25
19
|
readonly _tag: string;
|
|
26
20
|
}) => Effect.Effect<{
|
|
27
21
|
readonly [x: string]: Schema.Json;
|
|
28
22
|
readonly _tag: string & import("effect/Brand").Brand<"~effect-assistant/AppTag">;
|
|
23
|
+
readonly "~effect-assistant/input"?: true | undefined;
|
|
29
24
|
}, never, never>;
|
|
30
25
|
/** @internal */
|
|
31
26
|
export declare const isRegistered: (app: AppEvents, event: {
|
|
32
27
|
readonly _tag: string;
|
|
33
28
|
}) => boolean;
|
|
34
29
|
/** @internal */
|
|
30
|
+
export declare const decodeWith: (schema: AppEventSchema, event: {
|
|
31
|
+
readonly _tag: string;
|
|
32
|
+
}) => Option.Option<{
|
|
33
|
+
readonly _tag: string;
|
|
34
|
+
}>;
|
|
35
|
+
/** @internal */
|
|
36
|
+
export declare const markInput: (event: StoredAppEvent) => StoredAppEvent;
|
|
37
|
+
/** @internal */
|
|
35
38
|
export declare const decodeEntry: (app: AppEvents, entry: AnyEntry) => Option.Option<Entry<any>>;
|
|
36
39
|
/** @internal */
|
|
37
40
|
export declare const decodeEntries: (app: AppEvents, entries: ReadonlyArray<AnyEntry>) => ReadonlyArray<Entry<any>>;
|
|
38
41
|
/** @internal */
|
|
39
42
|
export type DecodedEntries = ReturnType<typeof decodeEntries>;
|
|
40
|
-
/** @internal */
|
|
41
|
-
export declare const read: (app: AppEvents, event: {
|
|
42
|
-
readonly _tag: string;
|
|
43
|
-
}) => string | undefined;
|
|
44
43
|
//#endregion
|
|
45
44
|
export { events_d_exports };
|
|
46
45
|
//# sourceMappingURL=events.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events.d.ts","names":[],"sources":["../../src/internal/events.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"events.d.ts","names":[],"sources":["../../src/internal/events.ts"],"mappings":";;;;;;;;;iBAWiB;WACN,OAAO,oBAAoB;;;qBAIzB,QAAK,QAAY;;qBASjB,OAAI,QAAY,cAAc,oBAAiC;;qBAa/D,SAAM,KAAA,WAAA;WAAwE;;;;;;;qBAY9E,eAAY,KAAS,WAAS;WAAoB;;;qBAQlD,aAAU,QACb,gBAA6B;WACnB;MACjB,OAAO;WAAkB;;;qBAUf,YAAS,OAAW,mBAAgC;;qBAOpD,cAAW,KAAS,WAAS,OAAS,aAA0B,OAAO,OAAO;;qBAS9E,gBAAa,KACnB,WAAS,SACL,cAAc,cACtB,cAAc;;YAGL,iBAAiB,kBAAkB"}
|
package/dist/internal/events.js
CHANGED
|
@@ -1,31 +1,46 @@
|
|
|
1
|
+
import { inputKey } from "./input.js";
|
|
1
2
|
import { StoredAppEvent, isLibraryEvent } from "../AssistantEvent.js";
|
|
2
|
-
import { toJsonText } from "./schema.js";
|
|
3
3
|
import * as Effect from "effect/Effect";
|
|
4
|
+
import * as Predicate from "effect/Predicate";
|
|
4
5
|
import * as Schema from "effect/Schema";
|
|
5
6
|
import * as Option from "effect/Option";
|
|
7
|
+
import * as Record from "effect/Record";
|
|
8
|
+
import * as SchemaAST from "effect/SchemaAST";
|
|
6
9
|
//#region src/internal/events.ts
|
|
7
10
|
/** @internal */
|
|
11
|
+
const tagOf = (schema) => {
|
|
12
|
+
const ast = schema.fields._tag.ast;
|
|
13
|
+
if (SchemaAST.isLiteral(ast) && Predicate.isString(ast.literal)) return ast.literal;
|
|
14
|
+
throw new Error("An app event is a tagged struct or class.");
|
|
15
|
+
};
|
|
16
|
+
/** @internal */
|
|
8
17
|
const make = (events) => {
|
|
9
|
-
const byTag = new Map(events.map((
|
|
10
|
-
schema: event.schema,
|
|
11
|
-
render: event.render
|
|
12
|
-
}]));
|
|
18
|
+
const byTag = new Map(events.map((schema) => [tagOf(schema), schema]));
|
|
13
19
|
for (const tag of byTag.keys()) if (isLibraryEvent({ _tag: tag })) throw new Error(`${tag} is one of the library's events.`);
|
|
14
20
|
if (byTag.size !== events.length) throw new Error("An assistant's events have different tags.");
|
|
15
|
-
return {
|
|
16
|
-
byTag,
|
|
17
|
-
wakes: new Set(events.flatMap((event) => event.wakes ? [event.tag] : []))
|
|
18
|
-
};
|
|
21
|
+
return { byTag };
|
|
19
22
|
};
|
|
20
23
|
/** @internal */
|
|
21
24
|
const encode = Effect.fnUntraced(function* (app, event) {
|
|
22
|
-
const
|
|
23
|
-
if (
|
|
24
|
-
const json = yield* Schema.encodeUnknownEffect(Schema.toCodecJson(
|
|
25
|
+
const schema = app.byTag.get(event._tag);
|
|
26
|
+
if (schema === void 0) return yield* Effect.die(`${event._tag} isn't one of this assistant's events.`);
|
|
27
|
+
const json = yield* Schema.encodeUnknownEffect(Schema.toCodecJson(schema))(event);
|
|
25
28
|
return yield* Schema.decodeUnknownEffect(StoredAppEvent)(json);
|
|
26
29
|
}, Effect.orDie);
|
|
27
30
|
/** @internal */
|
|
28
31
|
const isRegistered = (app, event) => app.byTag.has(event._tag);
|
|
32
|
+
const unmarked = (event) => Predicate.hasProperty(event, "~effect-assistant/input") ? Record.remove(event, inputKey) : event;
|
|
33
|
+
/** @internal */
|
|
34
|
+
const decodeWith = (schema, event) => Schema.decodeUnknownOption(Schema.toCodecJson(schema))(unmarked(event));
|
|
35
|
+
const decodeEvent = (app, event) => {
|
|
36
|
+
const schema = app.byTag.get(event._tag);
|
|
37
|
+
return schema === void 0 ? Option.none() : decodeWith(schema, event);
|
|
38
|
+
};
|
|
39
|
+
/** @internal */
|
|
40
|
+
const markInput = (event) => ({
|
|
41
|
+
...event,
|
|
42
|
+
[inputKey]: true
|
|
43
|
+
});
|
|
29
44
|
/** @internal */
|
|
30
45
|
const decodeEntry = (app, entry) => {
|
|
31
46
|
const { event } = entry;
|
|
@@ -33,33 +48,19 @@ const decodeEntry = (app, entry) => {
|
|
|
33
48
|
...toEntry(entry),
|
|
34
49
|
event
|
|
35
50
|
});
|
|
36
|
-
|
|
37
|
-
if (registered === void 0) return Option.none();
|
|
38
|
-
return Option.map(Schema.decodeUnknownOption(Schema.toCodecJson(registered.schema))(event), (decoded) => ({
|
|
51
|
+
return Option.map(decodeEvent(app, event), (decoded) => ({
|
|
39
52
|
...toEntry(entry),
|
|
40
53
|
event: decoded
|
|
41
54
|
}));
|
|
42
55
|
};
|
|
43
56
|
/** @internal */
|
|
44
57
|
const decodeEntries = (app, entries) => entries.flatMap((entry) => Option.toArray(decodeEntry(app, entry)));
|
|
45
|
-
const decodeStored = Schema.decodeUnknownOption(StoredAppEvent);
|
|
46
58
|
const toEntry = (entry) => ({
|
|
47
59
|
chatId: entry.chatId,
|
|
48
60
|
seq: entry.seq,
|
|
49
61
|
at: entry.at
|
|
50
62
|
});
|
|
51
|
-
/** @internal */
|
|
52
|
-
const read = (app, event) => {
|
|
53
|
-
const registered = app.byTag.get(event._tag);
|
|
54
|
-
if (registered?.render === void 0) return void 0;
|
|
55
|
-
if (registered.render !== "json") return registered.render(event);
|
|
56
|
-
const stored = Schema.encodeUnknownOption(Schema.toCodecJson(registered.schema))(event).pipe(Option.flatMap(decodeStored));
|
|
57
|
-
return Option.match(stored, {
|
|
58
|
-
onNone: () => void 0,
|
|
59
|
-
onSome: ({ _tag, ...fields }) => `<${_tag}>\n${toJsonText(fields)}\n</${_tag}>`
|
|
60
|
-
});
|
|
61
|
-
};
|
|
62
63
|
//#endregion
|
|
63
|
-
export { decodeEntries, decodeEntry, encode, isRegistered, make,
|
|
64
|
+
export { decodeEntries, decodeEntry, decodeWith, encode, isRegistered, make, markInput, tagOf };
|
|
64
65
|
|
|
65
66
|
//# sourceMappingURL=events.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events.js","names":["AssistantEvent.isLibraryEvent","AssistantEvent.StoredAppEvent"],"sources":["../../src/internal/events.ts"],"sourcesContent":["import * as Effect from \"effect/Effect\"\nimport * as Option from \"effect/Option\"\nimport * as Schema from \"effect/Schema\"\nimport
|
|
1
|
+
{"version":3,"file":"events.js","names":["AssistantEvent.isLibraryEvent","AssistantEvent.StoredAppEvent"],"sources":["../../src/internal/events.ts"],"sourcesContent":["import * as Effect from \"effect/Effect\"\nimport * as Option from \"effect/Option\"\nimport * as Predicate from \"effect/Predicate\"\nimport * as Record from \"effect/Record\"\nimport * as Schema from \"effect/Schema\"\nimport * as SchemaAST from \"effect/SchemaAST\"\nimport type * as Assistant from \"../Assistant.ts\"\nimport * as AssistantEvent from \"../AssistantEvent.ts\"\nimport { inputKey } from \"./input.ts\"\n\n/** @internal */\nexport interface AppEvents {\n readonly byTag: ReadonlyMap<string, AssistantEvent.AppEventSchema>\n}\n\n/** @internal */\nexport const tagOf = (schema: AssistantEvent.AppEventSchema): string => {\n const ast = schema.fields._tag.ast\n\n if (SchemaAST.isLiteral(ast) && Predicate.isString(ast.literal)) return ast.literal\n\n throw new Error(\"An app event is a tagged struct or class.\")\n}\n\n/** @internal */\nexport const make = (events: ReadonlyArray<AssistantEvent.AppEventSchema>): AppEvents => {\n const byTag = new Map(events.map((schema): [string, AssistantEvent.AppEventSchema] => [tagOf(schema), schema]))\n\n for (const tag of byTag.keys()) {\n if (AssistantEvent.isLibraryEvent({ _tag: tag })) throw new Error(`${tag} is one of the library's events.`)\n }\n\n if (byTag.size !== events.length) throw new Error(\"An assistant's events have different tags.\")\n\n return { byTag }\n}\n\n/** @internal */\nexport const encode = Effect.fnUntraced(function* (app: AppEvents, event: { readonly _tag: string }) {\n const schema = app.byTag.get(event._tag)\n\n // The types only let registered events through, so one with no schema here is a defect.\n if (schema === undefined) return yield* Effect.die(`${event._tag} isn't one of this assistant's events.`)\n\n const json = yield* Schema.encodeUnknownEffect(Schema.toCodecJson(schema))(event)\n\n return yield* Schema.decodeUnknownEffect(AssistantEvent.StoredAppEvent)(json)\n}, Effect.orDie)\n\n/** @internal */\nexport const isRegistered = (app: AppEvents, event: { readonly _tag: string }): boolean => app.byTag.has(event._tag)\n\n// The app's schema decodes the event as the app wrote it.\nconst unmarked = (event: { readonly _tag: string }) =>\n Predicate.hasProperty(event, inputKey) ? Record.remove(event, inputKey) : event\n\n// An event as the log stores it, decoded with its schema.\n/** @internal */\nexport const decodeWith = (\n schema: AssistantEvent.AppEventSchema,\n event: { readonly _tag: string }\n): Option.Option<{ readonly _tag: string }> => Schema.decodeUnknownOption(Schema.toCodecJson(schema))(unmarked(event))\n\nconst decodeEvent = (app: AppEvents, event: { readonly _tag: string }): Option.Option<{ readonly _tag: string }> => {\n const schema = app.byTag.get(event._tag)\n\n return schema === undefined ? Option.none() : decodeWith(schema, event)\n}\n\n// What came in through `submit`, from outside a turn, is an input: it wakes a turn.\n/** @internal */\nexport const markInput = (event: AssistantEvent.StoredAppEvent): AssistantEvent.StoredAppEvent => ({\n ...event,\n [inputKey]: true\n})\n\n// An app event that doesn't decode (say, one from an older version of its schema) is left out.\n/** @internal */\nexport const decodeEntry = (app: AppEvents, entry: AssistantEvent.AnyEntry): Option.Option<Assistant.Entry<any>> => {\n const { event } = entry\n\n if (AssistantEvent.isLibraryEvent(event)) return Option.some({ ...toEntry(entry), event })\n\n return Option.map(decodeEvent(app, event), (decoded) => ({ ...toEntry(entry), event: decoded }))\n}\n\n/** @internal */\nexport const decodeEntries = (\n app: AppEvents,\n entries: ReadonlyArray<AssistantEvent.AnyEntry>\n): ReadonlyArray<Assistant.Entry<any>> => entries.flatMap((entry) => Option.toArray(decodeEntry(app, entry)))\n\n/** @internal */\nexport type DecodedEntries = ReturnType<typeof decodeEntries>\n\nconst toEntry = (entry: AssistantEvent.AnyEntry) => ({\n chatId: entry.chatId,\n seq: entry.seq,\n at: entry.at\n})\n"],"mappings":";;;;;;;;;;AAgBA,MAAa,SAAS,WAAkD;CACtE,MAAM,MAAM,OAAO,OAAO,KAAK;CAE/B,IAAI,UAAU,UAAU,GAAG,KAAK,UAAU,SAAS,IAAI,OAAO,GAAG,OAAO,IAAI;CAE5E,MAAM,IAAI,MAAM,2CAA2C;AAC7D;;AAGA,MAAa,QAAQ,WAAoE;CACvF,MAAM,QAAQ,IAAI,IAAI,OAAO,KAAK,WAAoD,CAAC,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC;CAE9G,KAAK,MAAM,OAAO,MAAM,KAAK,GAC3B,IAAIA,eAA8B,EAAE,MAAM,IAAI,CAAC,GAAG,MAAM,IAAI,MAAM,GAAG,IAAI,iCAAiC;CAG5G,IAAI,MAAM,SAAS,OAAO,QAAQ,MAAM,IAAI,MAAM,4CAA4C;CAE9F,OAAO,EAAE,MAAM;AACjB;;AAGA,MAAa,SAAS,OAAO,WAAW,WAAW,KAAgB,OAAkC;CACnG,MAAM,SAAS,IAAI,MAAM,IAAI,MAAM,IAAI;CAGvC,IAAI,WAAW,KAAA,GAAW,OAAO,OAAO,OAAO,IAAI,GAAG,MAAM,KAAK,uCAAuC;CAExG,MAAM,OAAO,OAAO,OAAO,oBAAoB,OAAO,YAAY,MAAM,CAAC,CAAC,CAAC,KAAK;CAEhF,OAAO,OAAO,OAAO,oBAAoBC,cAA6B,CAAC,CAAC,IAAI;AAC9E,GAAG,OAAO,KAAK;;AAGf,MAAa,gBAAgB,KAAgB,UAA8C,IAAI,MAAM,IAAI,MAAM,IAAI;AAGnH,MAAM,YAAY,UAChB,UAAU,YAAY,OAAA,yBAAe,IAAI,OAAO,OAAO,OAAO,QAAQ,IAAI;;AAI5E,MAAa,cACX,QACA,UAC6C,OAAO,oBAAoB,OAAO,YAAY,MAAM,CAAC,CAAC,CAAC,SAAS,KAAK,CAAC;AAErH,MAAM,eAAe,KAAgB,UAA+E;CAClH,MAAM,SAAS,IAAI,MAAM,IAAI,MAAM,IAAI;CAEvC,OAAO,WAAW,KAAA,IAAY,OAAO,KAAK,IAAI,WAAW,QAAQ,KAAK;AACxE;;AAIA,MAAa,aAAa,WAAyE;CACjG,GAAG;EACF,WAAW;AACd;;AAIA,MAAa,eAAe,KAAgB,UAAwE;CAClH,MAAM,EAAE,UAAU;CAElB,IAAID,eAA8B,KAAK,GAAG,OAAO,OAAO,KAAK;EAAE,GAAG,QAAQ,KAAK;EAAG;CAAM,CAAC;CAEzF,OAAO,OAAO,IAAI,YAAY,KAAK,KAAK,IAAI,aAAa;EAAE,GAAG,QAAQ,KAAK;EAAG,OAAO;CAAQ,EAAE;AACjG;;AAGA,MAAa,iBACX,KACA,YACwC,QAAQ,SAAS,UAAU,OAAO,QAAQ,YAAY,KAAK,KAAK,CAAC,CAAC;AAK5G,MAAM,WAAW,WAAoC;CACnD,QAAQ,MAAM;CACd,KAAK,MAAM;CACX,IAAI,MAAM;AACZ"}
|
package/dist/internal/history.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { fromKey } from "./uuid.js";
|
|
2
|
-
import { isInput as isInput$1, isLibraryEvent, isTurnEnd, isUserInput, isWaking, textWithAttachments } from "../AssistantEvent.js";
|
|
2
|
+
import { isAppInput, isInput as isInput$1, isLibraryEvent, isTurnEnd, isUserInput, isWaking, textWithAttachments } from "../AssistantEvent.js";
|
|
3
3
|
import { toJson, toJsonText } from "./schema.js";
|
|
4
4
|
import * as Predicate from "effect/Predicate";
|
|
5
5
|
import * as Schema from "effect/Schema";
|
|
@@ -53,8 +53,10 @@ const finishedText = (entries, index, finished) => {
|
|
|
53
53
|
};
|
|
54
54
|
const answerText = (answer) => `<answer message="${answer.messageId}" kind="${answer.kind}">\nThe user answered with the block on your message ${answer.messageId}.\n${toJsonText(answer.value)}\n</answer>`;
|
|
55
55
|
const resultText = (result) => `<result tool="${result.tool}" call="${result.callId}" outcome="${result.outcome}">\nThe result of a call from earlier in this chat.\n${toJsonText(result.result)}\n</result>`;
|
|
56
|
+
const isEntry = (item) => Predicate.hasProperty(item, "seq");
|
|
56
57
|
/** @internal */
|
|
57
|
-
const fromEntries = (
|
|
58
|
+
const fromEntries = (items) => {
|
|
59
|
+
const entries = items.filter(isEntry);
|
|
58
60
|
const history = [];
|
|
59
61
|
const held = [];
|
|
60
62
|
const open = /* @__PURE__ */ new Set();
|
|
@@ -66,20 +68,24 @@ const fromEntries = (entries, readApp) => {
|
|
|
66
68
|
const next = entries.slice(from + 1).find(({ event }) => Predicate.isTagged(event, "ModelOutput"))?.event;
|
|
67
69
|
return next !== void 0 && isLibraryEvent(next) && Predicate.isTagged(next, "ModelOutput") ? next.seenThroughSeq : void 0;
|
|
68
70
|
};
|
|
69
|
-
const input = (index, item) => {
|
|
71
|
+
const input = (index, seq, item) => {
|
|
70
72
|
const seen = nextSeen(index);
|
|
71
|
-
if (open.size > 0 || seen !== void 0 && seen <
|
|
73
|
+
if (open.size > 0 || seen !== void 0 && seen < seq) held.push(item);
|
|
72
74
|
else history.push(item);
|
|
73
75
|
};
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
let index = -1;
|
|
77
|
+
for (const item of items) {
|
|
78
|
+
if (!isEntry(item)) {
|
|
79
|
+
input(index, (entries[index]?.seq ?? 0) + .5, item);
|
|
80
|
+
continue;
|
|
79
81
|
}
|
|
82
|
+
index += 1;
|
|
83
|
+
const { event, seq } = item;
|
|
84
|
+
const at = index;
|
|
85
|
+
if (!isLibraryEvent(event)) continue;
|
|
80
86
|
Match.value(event).pipe(Match.tagsExhaustive({
|
|
81
|
-
UserMessage: (message) => input(
|
|
82
|
-
BlockAnswer: (answer) => input(
|
|
87
|
+
UserMessage: (message) => input(at, seq, userText(textWithAttachments(message.text, message.attachments))),
|
|
88
|
+
BlockAnswer: (answer) => input(at, seq, userText(answerText(answer))),
|
|
83
89
|
ModelOutput: (output) => {
|
|
84
90
|
history.push(...Prompt.fromResponseParts(output.parts).content);
|
|
85
91
|
for (const part of output.parts) if (part.type === "tool-call") {
|
|
@@ -89,7 +95,7 @@ const fromEntries = (entries, readApp) => {
|
|
|
89
95
|
flushIfClosed();
|
|
90
96
|
},
|
|
91
97
|
ToolResult: (result) => {
|
|
92
|
-
if (!called.has(result.callId)) return input(
|
|
98
|
+
if (!called.has(result.callId)) return input(at, seq, userText(resultText(result)));
|
|
93
99
|
history.push(toolMessage(result));
|
|
94
100
|
open.delete(result.callId);
|
|
95
101
|
flushIfClosed();
|
|
@@ -99,28 +105,28 @@ const fromEntries = (entries, readApp) => {
|
|
|
99
105
|
TurnStarted: () => void 0,
|
|
100
106
|
TurnCompleted: () => void 0,
|
|
101
107
|
TurnFailed: () => void 0,
|
|
102
|
-
SpecialistFinished: (finished) => input(
|
|
108
|
+
SpecialistFinished: (finished) => input(at, seq, userText(finishedText(entries, at, finished))),
|
|
103
109
|
SpecialistStarted: () => void 0,
|
|
104
110
|
BlocksClosed: () => void 0,
|
|
105
111
|
ChatDeleted: () => void 0
|
|
106
112
|
}));
|
|
107
|
-
}
|
|
113
|
+
}
|
|
108
114
|
return [...history, ...held];
|
|
109
115
|
};
|
|
110
116
|
/** @internal */
|
|
111
|
-
const isInput = (event
|
|
117
|
+
const isInput = (event) => isInput$1(event) || isAppInput(event);
|
|
112
118
|
/** @internal */
|
|
113
119
|
const handledThrough = (entries) => {
|
|
114
120
|
const last = entries.findLast(({ event }) => isTurnEnd(event))?.event;
|
|
115
121
|
return last !== void 0 && isTurnEnd(last) ? last.throughSeq : 0;
|
|
116
122
|
};
|
|
117
123
|
/** @internal */
|
|
118
|
-
const unhandledInputs = (entries
|
|
124
|
+
const unhandledInputs = (entries) => {
|
|
119
125
|
const seen = handledThrough(entries);
|
|
120
|
-
return entries.flatMap(({ event, seq }) => seq > seen && isInput(event
|
|
126
|
+
return entries.flatMap(({ event, seq }) => seq > seen && isInput(event) && !(isInput$1(event) && !isWaking(event)) ? [event] : []);
|
|
121
127
|
};
|
|
122
128
|
/** @internal */
|
|
123
|
-
const needsTurn = (entries
|
|
129
|
+
const needsTurn = (entries) => unhandledInputs(entries).length > 0;
|
|
124
130
|
/** @internal */
|
|
125
131
|
const owesReply = (entries, throughSeq) => {
|
|
126
132
|
const latest = entries.findLast(({ event, seq }) => seq <= throughSeq && isInput$1(event) && isUserInput(event));
|
|
@@ -140,7 +146,7 @@ const owesDelivery = (entries, throughSeq) => {
|
|
|
140
146
|
return messages.length > 0 && messages.every((message) => message.block === void 0 && progress.has(message.id)) && !after.some((event) => Predicate.isTagged(event, "TurnFailed")) && started.every((runId) => finished.has(runId));
|
|
141
147
|
};
|
|
142
148
|
/** @internal */
|
|
143
|
-
const unreadAfter = (entries, throughSeq
|
|
149
|
+
const unreadAfter = (entries, throughSeq) => entries.some(({ event, seq }) => seq > throughSeq && isInput(event));
|
|
144
150
|
//#endregion
|
|
145
151
|
export { fromEntries, handledThrough, isInput, mayHaveRunOutput, needsTurn, notRunOutput, owesDelivery, owesReply, toolMessage, unansweredCalls, unhandledInputs, unreadAfter, userText };
|
|
146
152
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"history.js","names":["AssistantEvent.isLibraryEvent","AssistantEvent.textWithAttachments","AssistantEvent.isInput","AssistantEvent.isTurnEnd","AssistantEvent.isWaking","AssistantEvent.isUserInput","Uuid.fromKey"],"sources":["../../src/internal/history.ts"],"sourcesContent":["import * as Prompt from \"effect/ai/Prompt\"\nimport * as Response from \"effect/ai/Response\"\nimport * as Match from \"effect/Match\"\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 { toJson, toJsonText } from \"./schema.ts\"\nimport * as Uuid from \"./uuid.ts\"\n\n/** @internal */\nexport const userText = (text: string): Prompt.UserMessage =>\n Prompt.userMessage({ content: [Prompt.textPart({ text })] })\n\n/** @internal */\nexport const toolMessage = (result: AssistantEvent.ToolResult): Prompt.ToolMessage =>\n Prompt.toolMessage({\n content: [\n Prompt.toolResultPart({\n id: result.callId,\n name: result.tool,\n isFailure: result.outcome === \"failure\",\n result: result.result,\n providerExecuted: false\n })\n ]\n })\n\n// A call with no result is one a crash cut off.\n/** @internal */\nexport const unansweredCalls = (history: ReadonlyArray<Prompt.Message>): ReadonlyArray<AssistantEvent.ToolCallPart> => {\n const answered = new Set(\n history.flatMap((message) =>\n message.role === \"tool\" ? message.content.flatMap((part) => (part.type === \"tool-result\" ? [part.id] : [])) : []\n )\n )\n\n return history.flatMap((message) =>\n message.role === \"assistant\"\n ? message.content.flatMap((part) =>\n part.type === \"tool-call\" && !part.providerExecuted && !answered.has(part.id)\n ? [\n Response.makePart(\"tool-call\", {\n id: part.id,\n name: part.name,\n params: toJson(part.params),\n providerExecuted: false,\n metadata: part.options\n })\n ]\n : []\n )\n : []\n )\n}\n\n/** @internal */\nexport const notRunOutput = \"Not run: the turn ended before this call ran.\"\n\n/** @internal */\nexport const mayHaveRunOutput =\n \"Stopped: the turn ended while this call ran. It may have done its work; check before doing it again.\"\n\nconst ago = (millis: number) => {\n const seconds = Math.max(0, Math.round(millis / 1000))\n\n return seconds < 90 ? `${seconds} seconds` : `${Math.round(seconds / 60)} minutes`\n}\n\nconst finishedText = (\n entries: ReadonlyArray<AssistantEvent.AnyEntry>,\n index: number,\n finished: AssistantEvent.SpecialistFinished\n) => {\n const at = entries[index]!\n\n const isStart = ({ event }: AssistantEvent.AnyEntry) =>\n AssistantEvent.isLibraryEvent(event) &&\n Predicate.isTagged(event, \"SpecialistStarted\") &&\n event.runId === finished.runId\n\n const from = entries.slice(0, index).findLastIndex(isStart)\n const started = from === -1 ? undefined : entries[from]\n const since = started === undefined ? [] : entries.slice(from + 1, index)\n const wrote = since.some(({ event }) => Predicate.isTagged(event, \"UserMessage\"))\n\n const lines = [\n \"A report from a specialist you started. The user hasn't seen it.\",\n started === undefined\n ? `You started it for: ${finished.goal}`\n : `You started it ${ago(at.at - started.at)} ago for: ${finished.goal}`,\n wrote ? \"The user has written since you started it.\" : \"The user hasn't written since you started it.\",\n Match.value(finished.status).pipe(\n Match.when(\"answered\", () => `Report:\\n${finished.report ?? \"\"}`),\n Match.when(\"failed\", () => `It failed: ${finished.error ?? \"\"}`),\n Match.when(\"cut_off\", () => \"A restart cut it off before it finished. Spawn it again if it's still needed.\"),\n Match.when(\"stopped\", () => \"You stopped it.\"),\n Match.exhaustive\n )\n ]\n\n return `<specialist name=\"${finished.specialist}\" run=\"${finished.runId}\" status=\"${finished.status}\">\\n${lines.join(\n \"\\n\"\n )}\\n</specialist>`\n}\n\nconst answerText = (answer: AssistantEvent.BlockAnswer) =>\n `<answer message=\"${answer.messageId}\" kind=\"${answer.kind}\">\\nThe user answered with the block on your message ${answer.messageId}.\\n${toJsonText(answer.value)}\\n</answer>`\n\nconst resultText = (result: AssistantEvent.ToolResult) =>\n `<result tool=\"${result.tool}\" call=\"${result.callId}\" outcome=\"${result.outcome}\">\\nThe result of a call from earlier in this chat.\\n${toJsonText(result.result)}\\n</result>`\n\n// In the order the model saw the entries, not the order they were stored.\n/** @internal */\nexport const fromEntries = (\n entries: ReadonlyArray<AssistantEvent.AnyEntry>,\n readApp: (event: { readonly _tag: string }) => string | undefined\n): ReadonlyArray<Prompt.Message> => {\n const history: Array<Prompt.Message> = []\n const held: Array<Prompt.Message> = []\n const open = new Set<string>()\n const called = new Set<string>()\n\n const flushIfClosed = () => {\n if (open.size === 0) history.push(...held.splice(0))\n }\n\n const nextSeen = (from: number) => {\n const next = entries.slice(from + 1).find(({ event }) => Predicate.isTagged(event, \"ModelOutput\"))?.event\n\n return next !== undefined && AssistantEvent.isLibraryEvent(next) && Predicate.isTagged(next, \"ModelOutput\")\n ? next.seenThroughSeq\n : undefined\n }\n\n const input = (index: number, item: Prompt.Message) => {\n const seen = nextSeen(index)\n\n if (open.size > 0 || (seen !== undefined && seen < entries[index]!.seq)) held.push(item)\n else history.push(item)\n }\n\n entries.forEach(({ event }, index) => {\n if (!AssistantEvent.isLibraryEvent(event)) {\n const text = readApp(event)\n\n if (text !== undefined) input(index, userText(text))\n\n return\n }\n\n Match.value(event).pipe(\n Match.tagsExhaustive({\n UserMessage: (message) =>\n input(index, userText(AssistantEvent.textWithAttachments(message.text, message.attachments))),\n BlockAnswer: (answer) => input(index, userText(answerText(answer))),\n ModelOutput: (output) => {\n history.push(...Prompt.fromResponseParts(output.parts).content)\n\n for (const part of output.parts) {\n if (part.type === \"tool-call\") {\n open.add(part.id)\n called.add(part.id)\n }\n }\n\n flushIfClosed()\n },\n ToolResult: (result) => {\n // A view that starts after the call (since a summary, say) keeps its result as text: a tool result needs its\n // call right before it.\n if (!called.has(result.callId)) return input(index, userText(resultText(result)))\n\n history.push(toolMessage(result))\n open.delete(result.callId)\n flushIfClosed()\n },\n AssistantMessage: () => undefined,\n ToolStarted: () => undefined,\n TurnStarted: () => undefined,\n TurnCompleted: () => undefined,\n TurnFailed: () => undefined,\n SpecialistFinished: (finished) => input(index, userText(finishedText(entries, index, finished))),\n // Every run starts in a call of the model's own (a spawn or a follow-up), whose result says so.\n SpecialistStarted: () => undefined,\n BlocksClosed: () => undefined,\n ChatDeleted: () => undefined\n })\n )\n })\n\n return [...history, ...held]\n}\n\n/** @internal */\nexport const isInput = (event: { readonly _tag: string }, wakes: ReadonlySet<string>): boolean =>\n AssistantEvent.isInput(event) || (!AssistantEvent.isLibraryEvent(event) && wakes.has(event._tag))\n\n/** @internal */\nexport const handledThrough = (entries: ReadonlyArray<AssistantEvent.AnyEntry>): number => {\n const last = entries.findLast(({ event }) => AssistantEvent.isTurnEnd(event))?.event\n\n return last !== undefined && AssistantEvent.isTurnEnd(last) ? last.throughSeq : 0\n}\n\n// A run the assistant stopped isn't an input here: it wakes nothing.\n/** @internal */\nexport const unhandledInputs = (\n entries: ReadonlyArray<AssistantEvent.AnyEntry>,\n wakes: ReadonlySet<string>\n): ReadonlyArray<{ readonly _tag: string }> => {\n const seen = handledThrough(entries)\n\n return entries.flatMap(({ event, seq }) =>\n seq > seen && isInput(event, wakes) && !(AssistantEvent.isInput(event) && !AssistantEvent.isWaking(event))\n ? [event]\n : []\n )\n}\n\n/** @internal */\nexport const needsTurn = (entries: ReadonlyArray<AssistantEvent.AnyEntry>, wakes: ReadonlySet<string>): boolean =>\n unhandledInputs(entries, wakes).length > 0\n\n// A failure the user was shown counts as a reply.\n/** @internal */\nexport const owesReply = (entries: ReadonlyArray<AssistantEvent.AnyEntry>, throughSeq: number): boolean => {\n const latest = entries.findLast(\n ({ event, seq }) => seq <= throughSeq && AssistantEvent.isInput(event) && AssistantEvent.isUserInput(event)\n )\n\n return (\n latest !== undefined &&\n !entries.some(\n ({ event, seq }) =>\n seq > latest.seq && (Predicate.isTagged(event, \"AssistantMessage\") || Predicate.isTagged(event, \"TurnFailed\"))\n )\n )\n}\n\nconst sentToWaitOn = Schema.decodeUnknownOption(Schema.Struct({ wait: Schema.Literal(false) }))\n\n// Messages sent with `wait: false` are progress while the model kept working, like \"On it\".\nconst progressMessages = (entries: ReadonlyArray<AssistantEvent.AnyEntry>): ReadonlySet<string> =>\n new Set(\n entries.flatMap(({ event }) =>\n AssistantEvent.isLibraryEvent(event) && Predicate.isTagged(event, \"ModelOutput\")\n ? event.parts.flatMap((part) =>\n part.type === \"tool-call\" && part.name === \"send_user_message\" && Option.isSome(sentToWaitOn(part.params))\n ? [Uuid.fromKey(`message:${part.id}`)]\n : []\n )\n : []\n )\n )\n\n// The model said it was on it and stopped without the result: only progress messages since the user's latest input.\n// A specialist still working doesn't count, since its report is what comes next.\n/** @internal */\nexport const owesDelivery = (entries: ReadonlyArray<AssistantEvent.AnyEntry>, throughSeq: number): boolean => {\n const latest = entries.findLast(\n ({ event, seq }) => seq <= throughSeq && AssistantEvent.isInput(event) && AssistantEvent.isUserInput(event)\n )\n\n if (latest === undefined) return false\n\n const after = entries.flatMap(({ seq, event }) =>\n seq > latest.seq && AssistantEvent.isLibraryEvent(event) ? [event] : []\n )\n\n const messages = after.flatMap((event) => (Predicate.isTagged(event, \"AssistantMessage\") ? [event] : []))\n const progress = progressMessages(entries)\n const started = after.flatMap((event) => (Predicate.isTagged(event, \"SpecialistStarted\") ? [event.runId] : []))\n\n const finished = new Set(\n entries.flatMap(({ event }) =>\n AssistantEvent.isLibraryEvent(event) && Predicate.isTagged(event, \"SpecialistFinished\") ? [event.runId] : []\n )\n )\n\n return (\n messages.length > 0 &&\n messages.every((message) => message.block === undefined && progress.has(message.id)) &&\n !after.some((event) => Predicate.isTagged(event, \"TurnFailed\")) &&\n started.every((runId) => finished.has(runId))\n )\n}\n\n/** @internal */\nexport const unreadAfter = (\n entries: ReadonlyArray<AssistantEvent.AnyEntry>,\n throughSeq: number,\n wakes: ReadonlySet<string>\n): boolean => entries.some(({ event, seq }) => seq > throughSeq && isInput(event, wakes))\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"history.js","names":["AssistantEvent.isLibraryEvent","AssistantEvent.textWithAttachments","AssistantEvent.isInput","AssistantEvent.isAppInput","AssistantEvent.isTurnEnd","AssistantEvent.isWaking","AssistantEvent.isUserInput","Uuid.fromKey"],"sources":["../../src/internal/history.ts"],"sourcesContent":["import * as Prompt from \"effect/ai/Prompt\"\nimport * as Response from \"effect/ai/Response\"\nimport * as Match from \"effect/Match\"\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 { toJson, toJsonText } from \"./schema.ts\"\nimport * as Uuid from \"./uuid.ts\"\n\n/** @internal */\nexport const userText = (text: string): Prompt.UserMessage =>\n Prompt.userMessage({ content: [Prompt.textPart({ text })] })\n\n/** @internal */\nexport const toolMessage = (result: AssistantEvent.ToolResult): Prompt.ToolMessage =>\n Prompt.toolMessage({\n content: [\n Prompt.toolResultPart({\n id: result.callId,\n name: result.tool,\n isFailure: result.outcome === \"failure\",\n result: result.result,\n providerExecuted: false\n })\n ]\n })\n\n// A call with no result is one a crash cut off.\n/** @internal */\nexport const unansweredCalls = (history: ReadonlyArray<Prompt.Message>): ReadonlyArray<AssistantEvent.ToolCallPart> => {\n const answered = new Set(\n history.flatMap((message) =>\n message.role === \"tool\" ? message.content.flatMap((part) => (part.type === \"tool-result\" ? [part.id] : [])) : []\n )\n )\n\n return history.flatMap((message) =>\n message.role === \"assistant\"\n ? message.content.flatMap((part) =>\n part.type === \"tool-call\" && !part.providerExecuted && !answered.has(part.id)\n ? [\n Response.makePart(\"tool-call\", {\n id: part.id,\n name: part.name,\n params: toJson(part.params),\n providerExecuted: false,\n metadata: part.options\n })\n ]\n : []\n )\n : []\n )\n}\n\n/** @internal */\nexport const notRunOutput = \"Not run: the turn ended before this call ran.\"\n\n/** @internal */\nexport const mayHaveRunOutput =\n \"Stopped: the turn ended while this call ran. It may have done its work; check before doing it again.\"\n\nconst ago = (millis: number) => {\n const seconds = Math.max(0, Math.round(millis / 1000))\n\n return seconds < 90 ? `${seconds} seconds` : `${Math.round(seconds / 60)} minutes`\n}\n\nconst finishedText = (\n entries: ReadonlyArray<AssistantEvent.AnyEntry>,\n index: number,\n finished: AssistantEvent.SpecialistFinished\n) => {\n const at = entries[index]!\n\n const isStart = ({ event }: AssistantEvent.AnyEntry) =>\n AssistantEvent.isLibraryEvent(event) &&\n Predicate.isTagged(event, \"SpecialistStarted\") &&\n event.runId === finished.runId\n\n const from = entries.slice(0, index).findLastIndex(isStart)\n const started = from === -1 ? undefined : entries[from]\n const since = started === undefined ? [] : entries.slice(from + 1, index)\n const wrote = since.some(({ event }) => Predicate.isTagged(event, \"UserMessage\"))\n\n const lines = [\n \"A report from a specialist you started. The user hasn't seen it.\",\n started === undefined\n ? `You started it for: ${finished.goal}`\n : `You started it ${ago(at.at - started.at)} ago for: ${finished.goal}`,\n wrote ? \"The user has written since you started it.\" : \"The user hasn't written since you started it.\",\n Match.value(finished.status).pipe(\n Match.when(\"answered\", () => `Report:\\n${finished.report ?? \"\"}`),\n Match.when(\"failed\", () => `It failed: ${finished.error ?? \"\"}`),\n Match.when(\"cut_off\", () => \"A restart cut it off before it finished. Spawn it again if it's still needed.\"),\n Match.when(\"stopped\", () => \"You stopped it.\"),\n Match.exhaustive\n )\n ]\n\n return `<specialist name=\"${finished.specialist}\" run=\"${finished.runId}\" status=\"${finished.status}\">\\n${lines.join(\n \"\\n\"\n )}\\n</specialist>`\n}\n\nconst answerText = (answer: AssistantEvent.BlockAnswer) =>\n `<answer message=\"${answer.messageId}\" kind=\"${answer.kind}\">\\nThe user answered with the block on your message ${answer.messageId}.\\n${toJsonText(answer.value)}\\n</answer>`\n\nconst resultText = (result: AssistantEvent.ToolResult) =>\n `<result tool=\"${result.tool}\" call=\"${result.callId}\" outcome=\"${result.outcome}\">\\nThe result of a call from earlier in this chat.\\n${toJsonText(result.result)}\\n</result>`\n\nconst isEntry = (item: AssistantEvent.AnyEntry | Prompt.Message): item is AssistantEvent.AnyEntry =>\n Predicate.hasProperty(item, \"seq\")\n\n// In the order the model saw the entries, not the order they were stored. A message a history view put in stands\n// where it is, right after the entry before it, and waits like an input for a call's result or for the model's next\n// read; the app's own events reach the model only as such messages.\n/** @internal */\nexport const fromEntries = (\n items: ReadonlyArray<AssistantEvent.AnyEntry | Prompt.Message>\n): ReadonlyArray<Prompt.Message> => {\n const entries = items.filter(isEntry)\n const history: Array<Prompt.Message> = []\n const held: Array<Prompt.Message> = []\n const open = new Set<string>()\n const called = new Set<string>()\n\n const flushIfClosed = () => {\n if (open.size === 0) history.push(...held.splice(0))\n }\n\n const nextSeen = (from: number) => {\n const next = entries.slice(from + 1).find(({ event }) => Predicate.isTagged(event, \"ModelOutput\"))?.event\n\n return next !== undefined && AssistantEvent.isLibraryEvent(next) && Predicate.isTagged(next, \"ModelOutput\")\n ? next.seenThroughSeq\n : undefined\n }\n\n // `index` is the last entry at or before the item, and `seq` where the item stands.\n const input = (index: number, seq: number, item: Prompt.Message) => {\n const seen = nextSeen(index)\n\n if (open.size > 0 || (seen !== undefined && seen < seq)) held.push(item)\n else history.push(item)\n }\n\n let index = -1\n\n for (const item of items) {\n if (!isEntry(item)) {\n input(index, (entries[index]?.seq ?? 0) + 0.5, item)\n\n continue\n }\n\n index += 1\n\n const { event, seq } = item\n const at = index\n\n if (!AssistantEvent.isLibraryEvent(event)) continue\n\n Match.value(event).pipe(\n Match.tagsExhaustive({\n UserMessage: (message) =>\n input(at, seq, userText(AssistantEvent.textWithAttachments(message.text, message.attachments))),\n BlockAnswer: (answer) => input(at, seq, userText(answerText(answer))),\n ModelOutput: (output) => {\n history.push(...Prompt.fromResponseParts(output.parts).content)\n\n for (const part of output.parts) {\n if (part.type === \"tool-call\") {\n open.add(part.id)\n called.add(part.id)\n }\n }\n\n flushIfClosed()\n },\n ToolResult: (result) => {\n // A view that starts after the call (since a summary, say) keeps its result as text: a tool result needs its\n // call right before it.\n if (!called.has(result.callId)) return input(at, seq, userText(resultText(result)))\n\n history.push(toolMessage(result))\n open.delete(result.callId)\n flushIfClosed()\n },\n AssistantMessage: () => undefined,\n ToolStarted: () => undefined,\n TurnStarted: () => undefined,\n TurnCompleted: () => undefined,\n TurnFailed: () => undefined,\n SpecialistFinished: (finished) => input(at, seq, userText(finishedText(entries, at, finished))),\n // Every run starts in a call of the model's own (a spawn or a follow-up), whose result says so.\n SpecialistStarted: () => undefined,\n BlocksClosed: () => undefined,\n ChatDeleted: () => undefined\n })\n )\n }\n\n return [...history, ...held]\n}\n\n/** @internal */\nexport const isInput = (event: { readonly _tag: string }): boolean =>\n AssistantEvent.isInput(event) || AssistantEvent.isAppInput(event)\n\n/** @internal */\nexport const handledThrough = (entries: ReadonlyArray<AssistantEvent.AnyEntry>): number => {\n const last = entries.findLast(({ event }) => AssistantEvent.isTurnEnd(event))?.event\n\n return last !== undefined && AssistantEvent.isTurnEnd(last) ? last.throughSeq : 0\n}\n\n// A run the assistant stopped isn't an input here: it wakes nothing.\n/** @internal */\nexport const unhandledInputs = (\n entries: ReadonlyArray<AssistantEvent.AnyEntry>\n): ReadonlyArray<{ readonly _tag: string }> => {\n const seen = handledThrough(entries)\n\n return entries.flatMap(({ event, seq }) =>\n seq > seen && isInput(event) && !(AssistantEvent.isInput(event) && !AssistantEvent.isWaking(event)) ? [event] : []\n )\n}\n\n/** @internal */\nexport const needsTurn = (entries: ReadonlyArray<AssistantEvent.AnyEntry>): boolean =>\n unhandledInputs(entries).length > 0\n\n// A failure the user was shown counts as a reply.\n/** @internal */\nexport const owesReply = (entries: ReadonlyArray<AssistantEvent.AnyEntry>, throughSeq: number): boolean => {\n const latest = entries.findLast(\n ({ event, seq }) => seq <= throughSeq && AssistantEvent.isInput(event) && AssistantEvent.isUserInput(event)\n )\n\n return (\n latest !== undefined &&\n !entries.some(\n ({ event, seq }) =>\n seq > latest.seq && (Predicate.isTagged(event, \"AssistantMessage\") || Predicate.isTagged(event, \"TurnFailed\"))\n )\n )\n}\n\nconst sentToWaitOn = Schema.decodeUnknownOption(Schema.Struct({ wait: Schema.Literal(false) }))\n\n// Messages sent with `wait: false` are progress while the model kept working, like \"On it\".\nconst progressMessages = (entries: ReadonlyArray<AssistantEvent.AnyEntry>): ReadonlySet<string> =>\n new Set(\n entries.flatMap(({ event }) =>\n AssistantEvent.isLibraryEvent(event) && Predicate.isTagged(event, \"ModelOutput\")\n ? event.parts.flatMap((part) =>\n part.type === \"tool-call\" && part.name === \"send_user_message\" && Option.isSome(sentToWaitOn(part.params))\n ? [Uuid.fromKey(`message:${part.id}`)]\n : []\n )\n : []\n )\n )\n\n// The model said it was on it and stopped without the result: only progress messages since the user's latest input.\n// A specialist still working doesn't count, since its report is what comes next.\n/** @internal */\nexport const owesDelivery = (entries: ReadonlyArray<AssistantEvent.AnyEntry>, throughSeq: number): boolean => {\n const latest = entries.findLast(\n ({ event, seq }) => seq <= throughSeq && AssistantEvent.isInput(event) && AssistantEvent.isUserInput(event)\n )\n\n if (latest === undefined) return false\n\n const after = entries.flatMap(({ seq, event }) =>\n seq > latest.seq && AssistantEvent.isLibraryEvent(event) ? [event] : []\n )\n\n const messages = after.flatMap((event) => (Predicate.isTagged(event, \"AssistantMessage\") ? [event] : []))\n const progress = progressMessages(entries)\n const started = after.flatMap((event) => (Predicate.isTagged(event, \"SpecialistStarted\") ? [event.runId] : []))\n\n const finished = new Set(\n entries.flatMap(({ event }) =>\n AssistantEvent.isLibraryEvent(event) && Predicate.isTagged(event, \"SpecialistFinished\") ? [event.runId] : []\n )\n )\n\n return (\n messages.length > 0 &&\n messages.every((message) => message.block === undefined && progress.has(message.id)) &&\n !after.some((event) => Predicate.isTagged(event, \"TurnFailed\")) &&\n started.every((runId) => finished.has(runId))\n )\n}\n\n/** @internal */\nexport const unreadAfter = (entries: ReadonlyArray<AssistantEvent.AnyEntry>, throughSeq: number): boolean =>\n entries.some(({ event, seq }) => seq > throughSeq && isInput(event))\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"input.js","names":[],"sources":["../../src/internal/input.ts"],"sourcesContent":["// Marks a stored app event that came in through the assistant's `submit`: an input, which wakes a turn.\n/** @internal */\nexport const inputKey = \"~effect-assistant/input\"\n"],"mappings":";;AAEA,MAAa,WAAW"}
|
package/dist/internal/loop.js
CHANGED
|
@@ -3,9 +3,9 @@ import { unansweredCalls } from "./history.js";
|
|
|
3
3
|
import { chatSpan, toolOutput, toolSpan } from "./telemetry.js";
|
|
4
4
|
import { empty, needingApproval, needsApprovalMessage } from "./toolkit.js";
|
|
5
5
|
import { isTurnEnding } from "./tools.js";
|
|
6
|
-
import * as Predicate from "effect/Predicate";
|
|
7
6
|
import * as Context from "effect/Context";
|
|
8
7
|
import * as Effect from "effect/Effect";
|
|
8
|
+
import * as Predicate from "effect/Predicate";
|
|
9
9
|
import * as Schema from "effect/Schema";
|
|
10
10
|
import * as Stream from "effect/Stream";
|
|
11
11
|
import * as Data from "effect/Data";
|
package/dist/internal/skills.js
CHANGED
|
@@ -7,6 +7,11 @@ import * as Tool from "effect/ai/Tool";
|
|
|
7
7
|
import * as Toolkit from "effect/ai/Toolkit";
|
|
8
8
|
//#region src/internal/skills.ts
|
|
9
9
|
/** @internal */
|
|
10
|
+
const available = (skills, definitions) => {
|
|
11
|
+
const names = new Set(definitions.map((tool) => tool.name));
|
|
12
|
+
return skills.filter((skill) => (skill.tools ?? []).every((tool) => names.has(tool.name)));
|
|
13
|
+
};
|
|
14
|
+
/** @internal */
|
|
10
15
|
const toTools = (skills) => {
|
|
11
16
|
if (skills.length === 0) return empty;
|
|
12
17
|
const byName = new Map(skills.map((skill) => [skill.name, skill]));
|
|
@@ -23,6 +28,6 @@ const toTools = (skills) => {
|
|
|
23
28
|
}) }));
|
|
24
29
|
};
|
|
25
30
|
//#endregion
|
|
26
|
-
export { toTools };
|
|
31
|
+
export { available, toTools };
|
|
27
32
|
|
|
28
33
|
//# sourceMappingURL=skills.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skills.js","names":["Toolkits.empty","ToolFailure.ToolFailure","Toolkits.withContext","ToolFailure.fail","Instructions.resolve"],"sources":["../../src/internal/skills.ts"],"sourcesContent":["import * as Tool from \"effect/ai/Tool\"\nimport * as Toolkit from \"effect/ai/Toolkit\"\nimport type * as Context from \"effect/Context\"\nimport * as Effect from \"effect/Effect\"\nimport * as Schema from \"effect/Schema\"\nimport * as Instructions from \"../Instructions.ts\"\nimport type { Skill } from \"../Skill.ts\"\nimport * as ToolFailure from \"../ToolFailure.ts\"\nimport * as Toolkits from \"./toolkit.ts\"\n\n/** @internal */\nexport const toTools = <R>(skills: ReadonlyArray<Skill<R>>): Toolkits.Tools<R> => {\n if (skills.length === 0) return Toolkits.empty\n\n const byName = new Map(skills.map((skill) => [skill.name, skill]))\n\n const LoadSkill = Tool.make(\"load_skill\", {\n description:\n \"Load a skill's instructions by name, in the same step as your first lookups (not in a step of its own). Follow them for the rest of the task.\",\n parameters: Schema.Struct({ name: Schema.Literals(skills.map((skill) => skill.name)) }),\n success: Schema.String,\n failure: ToolFailure.ToolFailure\n })\n\n // A skill's instructions resolve with the services of the loop that loads it.\n return Toolkits.withContext(Toolkit.make(LoadSkill), (context: Context.Context<R>) => ({\n load_skill: Effect.fnUntraced(function* ({ name }) {\n const skill = byName.get(name)\n\n if (skill === undefined) return yield* ToolFailure.fail(`No skill named ${name}.`)\n\n const resolved = yield* Instructions.resolve(skill.instructions).pipe(Effect.provideContext(context))\n\n return resolved.text\n })\n }))\n}\n"],"mappings":";;;;;;;;;
|
|
1
|
+
{"version":3,"file":"skills.js","names":["Toolkits.empty","ToolFailure.ToolFailure","Toolkits.withContext","ToolFailure.fail","Instructions.resolve"],"sources":["../../src/internal/skills.ts"],"sourcesContent":["import * as Tool from \"effect/ai/Tool\"\nimport * as Toolkit from \"effect/ai/Toolkit\"\nimport type * as Context from \"effect/Context\"\nimport * as Effect from \"effect/Effect\"\nimport * as Schema from \"effect/Schema\"\nimport * as Instructions from \"../Instructions.ts\"\nimport type { Skill } from \"../Skill.ts\"\nimport * as ToolFailure from \"../ToolFailure.ts\"\nimport * as Toolkits from \"./toolkit.ts\"\n\n// A skill whose tools the loop doesn't have would send the model after calls it can't make.\n/** @internal */\nexport const available = <R>(\n skills: ReadonlyArray<Skill<R>>,\n definitions: ReadonlyArray<Tool.Any>\n): ReadonlyArray<Skill<R>> => {\n const names = new Set(definitions.map((tool) => tool.name))\n\n return skills.filter((skill) => (skill.tools ?? []).every((tool) => names.has(tool.name)))\n}\n\n/** @internal */\nexport const toTools = <R>(skills: ReadonlyArray<Skill<R>>): Toolkits.Tools<R> => {\n if (skills.length === 0) return Toolkits.empty\n\n const byName = new Map(skills.map((skill) => [skill.name, skill]))\n\n const LoadSkill = Tool.make(\"load_skill\", {\n description:\n \"Load a skill's instructions by name, in the same step as your first lookups (not in a step of its own). Follow them for the rest of the task.\",\n parameters: Schema.Struct({ name: Schema.Literals(skills.map((skill) => skill.name)) }),\n success: Schema.String,\n failure: ToolFailure.ToolFailure\n })\n\n // A skill's instructions resolve with the services of the loop that loads it.\n return Toolkits.withContext(Toolkit.make(LoadSkill), (context: Context.Context<R>) => ({\n load_skill: Effect.fnUntraced(function* ({ name }) {\n const skill = byName.get(name)\n\n if (skill === undefined) return yield* ToolFailure.fail(`No skill named ${name}.`)\n\n const resolved = yield* Instructions.resolve(skill.instructions).pipe(Effect.provideContext(context))\n\n return resolved.text\n })\n }))\n}\n"],"mappings":";;;;;;;;;AAYA,MAAa,aACX,QACA,gBAC4B;CAC5B,MAAM,QAAQ,IAAI,IAAI,YAAY,KAAK,SAAS,KAAK,IAAI,CAAC;CAE1D,OAAO,OAAO,QAAQ,WAAW,MAAM,SAAS,CAAC,EAAA,CAAG,OAAO,SAAS,MAAM,IAAI,KAAK,IAAI,CAAC,CAAC;AAC3F;;AAGA,MAAa,WAAc,WAAuD;CAChF,IAAI,OAAO,WAAW,GAAG,OAAOA;CAEhC,MAAM,SAAS,IAAI,IAAI,OAAO,KAAK,UAAU,CAAC,MAAM,MAAM,KAAK,CAAC,CAAC;CAEjE,MAAM,YAAY,KAAK,KAAK,cAAc;EACxC,aACE;EACF,YAAY,OAAO,OAAO,EAAE,MAAM,OAAO,SAAS,OAAO,KAAK,UAAU,MAAM,IAAI,CAAC,EAAE,CAAC;EACtF,SAAS,OAAO;EAChB,SAASC;CACX,CAAC;CAGD,OAAOC,YAAqB,QAAQ,KAAK,SAAS,IAAI,aAAiC,EACrF,YAAY,OAAO,WAAW,WAAW,EAAE,QAAQ;EACjD,MAAM,QAAQ,OAAO,IAAI,IAAI;EAE7B,IAAI,UAAU,KAAA,GAAW,OAAO,OAAOC,KAAiB,kBAAkB,KAAK,EAAE;EAIjF,QAAO,OAFiBC,QAAqB,MAAM,YAAY,CAAC,CAAC,KAAK,OAAO,eAAe,OAAO,CAAC,EAAA,CAEpF;CAClB,CAAC,EACH,EAAE;AACJ"}
|