effect-assistant 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +22 -2
  3. package/dist/AppEvent.d.ts +111 -0
  4. package/dist/AppEvent.d.ts.map +1 -0
  5. package/dist/AppEvent.js +71 -0
  6. package/dist/AppEvent.js.map +1 -0
  7. package/dist/Assistant.d.ts +403 -0
  8. package/dist/Assistant.d.ts.map +1 -0
  9. package/dist/Assistant.js +126 -0
  10. package/dist/Assistant.js.map +1 -0
  11. package/dist/AssistantActivity.d.ts +85 -0
  12. package/dist/AssistantActivity.d.ts.map +1 -0
  13. package/dist/AssistantActivity.js +105 -0
  14. package/dist/AssistantActivity.js.map +1 -0
  15. package/dist/AssistantEngine.d.ts +116 -0
  16. package/dist/AssistantEngine.d.ts.map +1 -0
  17. package/dist/AssistantEngine.js +255 -0
  18. package/dist/AssistantEngine.js.map +1 -0
  19. package/dist/AssistantEvent.d.ts +551 -0
  20. package/dist/AssistantEvent.d.ts.map +1 -0
  21. package/dist/AssistantEvent.js +473 -0
  22. package/dist/AssistantEvent.js.map +1 -0
  23. package/dist/AssistantLoop.d.ts +173 -0
  24. package/dist/AssistantLoop.d.ts.map +1 -0
  25. package/dist/AssistantLoop.js +111 -0
  26. package/dist/AssistantLoop.js.map +1 -0
  27. package/dist/AssistantToolCall.d.ts +32 -0
  28. package/dist/AssistantToolCall.d.ts.map +1 -0
  29. package/dist/AssistantToolCall.js +37 -0
  30. package/dist/AssistantToolCall.js.map +1 -0
  31. package/dist/Block.d.ts +69 -0
  32. package/dist/Block.d.ts.map +1 -0
  33. package/dist/Block.js +39 -0
  34. package/dist/Block.js.map +1 -0
  35. package/dist/BlockKit.d.ts +180 -0
  36. package/dist/BlockKit.d.ts.map +1 -0
  37. package/dist/BlockKit.js +148 -0
  38. package/dist/BlockKit.js.map +1 -0
  39. package/dist/ChatId.d.ts +31 -0
  40. package/dist/ChatId.d.ts.map +1 -0
  41. package/dist/ChatId.js +36 -0
  42. package/dist/ChatId.js.map +1 -0
  43. package/dist/ChatLog.d.ts +109 -0
  44. package/dist/ChatLog.d.ts.map +1 -0
  45. package/dist/ChatLog.js +177 -0
  46. package/dist/ChatLog.js.map +1 -0
  47. package/dist/ChatView.d.ts +408 -0
  48. package/dist/ChatView.d.ts.map +1 -0
  49. package/dist/ChatView.js +443 -0
  50. package/dist/ChatView.js.map +1 -0
  51. package/dist/Chats.d.ts +110 -0
  52. package/dist/Chats.d.ts.map +1 -0
  53. package/dist/Chats.js +98 -0
  54. package/dist/Chats.js.map +1 -0
  55. package/dist/Compaction.d.ts +105 -0
  56. package/dist/Compaction.d.ts.map +1 -0
  57. package/dist/Compaction.js +126 -0
  58. package/dist/Compaction.js.map +1 -0
  59. package/dist/CurrentChat.d.ts +40 -0
  60. package/dist/CurrentChat.d.ts.map +1 -0
  61. package/dist/CurrentChat.js +33 -0
  62. package/dist/CurrentChat.js.map +1 -0
  63. package/dist/InputId.d.ts +38 -0
  64. package/dist/InputId.d.ts.map +1 -0
  65. package/dist/InputId.js +43 -0
  66. package/dist/InputId.js.map +1 -0
  67. package/dist/Instructions.d.ts +56 -0
  68. package/dist/Instructions.d.ts.map +1 -0
  69. package/dist/Instructions.js +50 -0
  70. package/dist/Instructions.js.map +1 -0
  71. package/dist/Memory.d.ts +190 -0
  72. package/dist/Memory.d.ts.map +1 -0
  73. package/dist/Memory.js +216 -0
  74. package/dist/Memory.js.map +1 -0
  75. package/dist/RunId.d.ts +41 -0
  76. package/dist/RunId.d.ts.map +1 -0
  77. package/dist/RunId.js +46 -0
  78. package/dist/RunId.js.map +1 -0
  79. package/dist/Skill.d.ts +72 -0
  80. package/dist/Skill.d.ts.map +1 -0
  81. package/dist/Skill.js +58 -0
  82. package/dist/Skill.js.map +1 -0
  83. package/dist/Specialist.d.ts +267 -0
  84. package/dist/Specialist.d.ts.map +1 -0
  85. package/dist/Specialist.js +119 -0
  86. package/dist/Specialist.js.map +1 -0
  87. package/dist/ToolDiscovery.d.ts +107 -0
  88. package/dist/ToolDiscovery.d.ts.map +1 -0
  89. package/dist/ToolDiscovery.js +140 -0
  90. package/dist/ToolDiscovery.js.map +1 -0
  91. package/dist/ToolFailure.d.ts +66 -0
  92. package/dist/ToolFailure.d.ts.map +1 -0
  93. package/dist/ToolFailure.js +79 -0
  94. package/dist/ToolFailure.js.map +1 -0
  95. package/dist/_virtual/_rolldown/runtime.js +13 -0
  96. package/dist/index.d.ts +24 -0
  97. package/dist/index.js +24 -0
  98. package/dist/internal/chat.js +177 -0
  99. package/dist/internal/chat.js.map +1 -0
  100. package/dist/internal/events.d.ts +46 -0
  101. package/dist/internal/events.d.ts.map +1 -0
  102. package/dist/internal/events.js +65 -0
  103. package/dist/internal/events.js.map +1 -0
  104. package/dist/internal/history.js +147 -0
  105. package/dist/internal/history.js.map +1 -0
  106. package/dist/internal/instructions.js +11 -0
  107. package/dist/internal/instructions.js.map +1 -0
  108. package/dist/internal/loop.d.ts +73 -0
  109. package/dist/internal/loop.d.ts.map +1 -0
  110. package/dist/internal/loop.js +131 -0
  111. package/dist/internal/loop.js.map +1 -0
  112. package/dist/internal/schema.js +14 -0
  113. package/dist/internal/schema.js.map +1 -0
  114. package/dist/internal/skills.js +28 -0
  115. package/dist/internal/skills.js.map +1 -0
  116. package/dist/internal/specialists.js +272 -0
  117. package/dist/internal/specialists.js.map +1 -0
  118. package/dist/internal/telemetry.js +85 -0
  119. package/dist/internal/telemetry.js.map +1 -0
  120. package/dist/internal/toolkit.d.ts +62 -0
  121. package/dist/internal/toolkit.d.ts.map +1 -0
  122. package/dist/internal/toolkit.js +108 -0
  123. package/dist/internal/toolkit.js.map +1 -0
  124. package/dist/internal/tools.js +113 -0
  125. package/dist/internal/tools.js.map +1 -0
  126. package/dist/internal/turns.d.ts +64 -0
  127. package/dist/internal/turns.d.ts.map +1 -0
  128. package/dist/internal/turns.js +214 -0
  129. package/dist/internal/turns.js.map +1 -0
  130. package/dist/internal/uuid.js +45 -0
  131. package/dist/internal/uuid.js.map +1 -0
  132. package/dist/internal/writer.d.ts +12 -0
  133. package/dist/internal/writer.d.ts.map +1 -0
  134. package/package.json +52 -3
  135. package/src/AppEvent.ts +134 -0
  136. package/src/Assistant.ts +589 -0
  137. package/src/AssistantActivity.ts +150 -0
  138. package/src/AssistantEngine.ts +343 -0
  139. package/src/AssistantEvent.ts +569 -0
  140. package/src/AssistantLoop.ts +209 -0
  141. package/src/AssistantToolCall.ts +40 -0
  142. package/src/Block.ts +84 -0
  143. package/src/BlockKit.ts +298 -0
  144. package/src/ChatId.ts +36 -0
  145. package/src/ChatLog.ts +247 -0
  146. package/src/ChatView.ts +587 -0
  147. package/src/Chats.ts +122 -0
  148. package/src/Compaction.ts +207 -0
  149. package/src/CurrentChat.ts +47 -0
  150. package/src/InputId.ts +43 -0
  151. package/src/Instructions.ts +72 -0
  152. package/src/Memory.ts +276 -0
  153. package/src/RunId.ts +46 -0
  154. package/src/Skill.ts +91 -0
  155. package/src/Specialist.ts +355 -0
  156. package/src/ToolDiscovery.ts +179 -0
  157. package/src/ToolFailure.ts +74 -0
  158. package/src/index.ts +120 -0
  159. package/src/internal/chat.ts +323 -0
  160. package/src/internal/events.ts +100 -0
  161. package/src/internal/history.ts +294 -0
  162. package/src/internal/instructions.ts +9 -0
  163. package/src/internal/loop.ts +274 -0
  164. package/src/internal/schema.ts +25 -0
  165. package/src/internal/skills.ts +37 -0
  166. package/src/internal/specialists.ts +410 -0
  167. package/src/internal/telemetry.ts +125 -0
  168. package/src/internal/toolkit.ts +188 -0
  169. package/src/internal/tools.ts +159 -0
  170. package/src/internal/turns.ts +410 -0
  171. package/src/internal/uuid.ts +50 -0
  172. package/src/internal/writer.ts +12 -0
@@ -0,0 +1,113 @@
1
+ import { fromKey } from "./uuid.js";
2
+ import { AssistantMessage, MessageBlock, isLibraryEvent } from "../AssistantEvent.js";
3
+ import { CurrentChat } from "../CurrentChat.js";
4
+ import { AssistantToolCall } from "../AssistantToolCall.js";
5
+ import { ToolFailure, fail } from "../ToolFailure.js";
6
+ import { fromWithHandler } from "./toolkit.js";
7
+ import * as Predicate from "effect/Predicate";
8
+ import * as Effect from "effect/Effect";
9
+ import * as Schema from "effect/Schema";
10
+ import * as Option from "effect/Option";
11
+ import * as Tool from "effect/ai/Tool";
12
+ import * as Toolkit from "effect/ai/Toolkit";
13
+ //#region src/internal/tools.ts
14
+ /** @internal */
15
+ const waitToolName = "wait";
16
+ /** @internal */
17
+ const sendUserMessageName = "send_user_message";
18
+ const Wait = Tool.make(waitToolName, {
19
+ description: "End your turn and wait for the next message or update, when you have nothing more to do right now and no message to send (otherwise set wait on send_user_message).",
20
+ success: Schema.String
21
+ });
22
+ const description = "Send the user a chat message. It's the only way they see anything you write: text outside it is private to you.";
23
+ const WaitFlag = Schema.Boolean.annotate({ description: "true when this is your last message for now: your turn ends with it, as with the wait tool. false when you keep working after it." });
24
+ const endsWithMessage = Schema.decodeUnknownOption(Schema.Struct({ wait: Schema.Literal(true) }));
25
+ /** @internal */
26
+ const isTurnEnding = (call) => call.name === "wait" || call.name === "send_user_message" && Option.isSome(endsWithMessage(call.params));
27
+ const messageId = AssistantToolCall.useSync((call) => fromKey(`message:${call.callId}`));
28
+ const append = Effect.fnUntraced(function* (message) {
29
+ const chat = yield* CurrentChat;
30
+ if (!(yield* chat.entries).some(({ event }) => isLibraryEvent(event) && Predicate.isTagged(event, "AssistantMessage") && event.id === message.id)) yield* chat.append(message);
31
+ });
32
+ const PlainMessage = Tool.make(sendUserMessageName, {
33
+ description,
34
+ parameters: Schema.Struct({
35
+ text: Schema.String,
36
+ wait: WaitFlag
37
+ }),
38
+ success: Schema.String,
39
+ failure: ToolFailure,
40
+ dependencies: [CurrentChat, AssistantToolCall]
41
+ });
42
+ const PlainTools = Toolkit.make(PlainMessage, Wait);
43
+ const plainHandlers = PlainTools.toHandlers({
44
+ wait: () => Effect.succeed("waiting"),
45
+ send_user_message: Effect.fnUntraced(function* ({ text }) {
46
+ yield* append(AssistantMessage.make({
47
+ id: yield* messageId,
48
+ text
49
+ }));
50
+ return "sent";
51
+ })
52
+ });
53
+ const plainTools = Effect.gen(function* () {
54
+ return yield* Effect.provideContext(PlainTools, yield* plainHandlers);
55
+ });
56
+ const blockTools = Effect.fnUntraced(function* (kit) {
57
+ const catalog = kit.blocks.map((block) => `- ${block.kind}: ${block.description}`).join("\n");
58
+ const kinds = kit.blocks.map((block) => block.kind);
59
+ const BlockMessage = Tool.make(sendUserMessageName, {
60
+ description: `${description}
61
+ You can add a block to show UI with the message, e.g. a few options to pick from or a yes/no, when that makes answering easier than typing. Plain text is fine too. Using it sends you an <answer>; the user may also just type their answer instead, and then you pass that message's id in closes.
62
+ Blocks:
63
+ ${catalog}`,
64
+ parameters: Schema.Struct({
65
+ text: Schema.String,
66
+ wait: WaitFlag,
67
+ block: Schema.optional(Schema.Struct({
68
+ kind: Schema.Literals(kinds),
69
+ props: Schema.Union(kit.blocks.map((block) => block.props))
70
+ })),
71
+ closes: Schema.optional(Schema.Array(Schema.String)).annotate({ description: "Ids of your earlier messages whose block the user just answered by typing instead of using it. Those blocks close. Leave it out otherwise: a block stays open when the user talks about something else." })
72
+ }),
73
+ success: Schema.String,
74
+ failure: ToolFailure,
75
+ dependencies: [CurrentChat, AssistantToolCall]
76
+ });
77
+ const BlockTools = Toolkit.make(BlockMessage, Wait);
78
+ const handlers = yield* BlockTools.toHandlers({
79
+ wait: () => Effect.succeed("waiting"),
80
+ send_user_message: Effect.fnUntraced(function* ({ text, block, closes }) {
81
+ if (text.trim() === "") return yield* fail("The message has no text: the user sees only what you send here, and a block alone is an empty bubble. Send it again with the words they should read (a short lead-in for the block).", { reason: "EmptyMessage" });
82
+ const id = yield* messageId;
83
+ const closed = closes === void 0 || closes.length === 0 ? {} : { closes };
84
+ if (block === void 0) {
85
+ yield* append(AssistantMessage.make({
86
+ id,
87
+ text,
88
+ ...closed
89
+ }));
90
+ return "sent";
91
+ }
92
+ const props = kit.byKind.get(block.kind)?.props;
93
+ if (props === void 0 || Option.isNone(Schema.decodeUnknownOption(props)(block.props))) return yield* fail(`Those props don't fit a ${block.kind} block.`);
94
+ yield* append(AssistantMessage.make({
95
+ id,
96
+ text,
97
+ block: MessageBlock.make(block),
98
+ ...closed
99
+ }));
100
+ return `sent as message ${id}`;
101
+ })
102
+ });
103
+ return yield* Effect.provideContext(BlockTools, handlers);
104
+ });
105
+ /** @internal */
106
+ const makeMessaging = Effect.fnUntraced(function* (kit) {
107
+ if (kit === void 0 || kit.blocks.length === 0) return fromWithHandler(yield* plainTools);
108
+ return fromWithHandler(yield* blockTools(kit));
109
+ });
110
+ //#endregion
111
+ export { isTurnEnding, makeMessaging, sendUserMessageName, waitToolName };
112
+
113
+ //# sourceMappingURL=tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.js","names":["Uuid.fromKey","AssistantEvent.isLibraryEvent","ToolFailure.ToolFailure","ToolFailure.fail","Toolkits.fromWithHandler"],"sources":["../../src/internal/tools.ts"],"sourcesContent":["import * as Tool from \"effect/ai/Tool\"\nimport * as Toolkit from \"effect/ai/Toolkit\"\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 { AssistantToolCall } from \"../AssistantToolCall.ts\"\nimport type * as BlockKit from \"../BlockKit.ts\"\nimport { CurrentChat } from \"../CurrentChat.ts\"\nimport * as ToolFailure from \"../ToolFailure.ts\"\nimport * as Toolkits from \"./toolkit.ts\"\nimport * as Uuid from \"./uuid.ts\"\n\n/** @internal */\nexport const waitToolName = \"wait\"\n\n/** @internal */\nexport const sendUserMessageName = \"send_user_message\"\n\nconst Wait = Tool.make(waitToolName, {\n description:\n \"End your turn and wait for the next message or update, when you have nothing more to do right now and no message to send (otherwise set wait on send_user_message).\",\n success: Schema.String\n})\n\nconst description =\n \"Send the user a chat message. It's the only way they see anything you write: text outside it is private to you.\"\n\nconst WaitFlag = Schema.Boolean.annotate({\n description:\n \"true when this is your last message for now: your turn ends with it, as with the wait tool. false when you keep working after it.\"\n})\n\nconst endsWithMessage = Schema.decodeUnknownOption(Schema.Struct({ wait: Schema.Literal(true) }))\n\n/** @internal */\nexport const isTurnEnding = (call: AssistantEvent.ToolCallPart): boolean =>\n call.name === waitToolName || (call.name === sendUserMessageName && Option.isSome(endsWithMessage(call.params)))\n\n// From the call: a call cut off by a crash runs again with the same id, and finds its message already sent.\nconst messageId = AssistantToolCall.useSync((call) => Uuid.fromKey(`message:${call.callId}`))\n\nconst append = Effect.fnUntraced(function* (message: AssistantEvent.AssistantMessage) {\n const chat = yield* CurrentChat\n const entries = yield* chat.entries\n\n const sent = entries.some(\n ({ event }) =>\n AssistantEvent.isLibraryEvent(event) && Predicate.isTagged(event, \"AssistantMessage\") && event.id === message.id\n )\n\n if (!sent) yield* chat.append(message)\n})\n\nconst PlainMessage = Tool.make(sendUserMessageName, {\n description,\n parameters: Schema.Struct({ text: Schema.String, wait: WaitFlag }),\n success: Schema.String,\n failure: ToolFailure.ToolFailure,\n dependencies: [CurrentChat, AssistantToolCall]\n})\n\nconst PlainTools = Toolkit.make(PlainMessage, Wait)\n\nconst plainHandlers = PlainTools.toHandlers({\n wait: () => Effect.succeed(\"waiting\"),\n send_user_message: Effect.fnUntraced(function* ({ text }) {\n yield* append(AssistantEvent.AssistantMessage.make({ id: yield* messageId, text }))\n\n return \"sent\"\n })\n})\n\nconst plainTools = Effect.gen(function* () {\n return yield* Effect.provideContext(PlainTools, yield* plainHandlers)\n})\n\nconst blockTools = Effect.fnUntraced(function* (kit: BlockKit.BlockKit) {\n const catalog = kit.blocks.map((block) => `- ${block.kind}: ${block.description}`).join(\"\\n\")\n const kinds = kit.blocks.map((block) => block.kind)\n\n const BlockMessage = Tool.make(sendUserMessageName, {\n description: `${description}\nYou can add a block to show UI with the message, e.g. a few options to pick from or a yes/no, when that makes answering easier than typing. Plain text is fine too. Using it sends you an <answer>; the user may also just type their answer instead, and then you pass that message's id in closes.\nBlocks:\n${catalog}`,\n // Props are checked against their kind's schema by the handler; the model sees every kind's in the parameters.\n parameters: Schema.Struct({\n text: Schema.String,\n wait: WaitFlag,\n block: Schema.optional(\n Schema.Struct({\n kind: Schema.Literals(kinds),\n props: Schema.Union(kit.blocks.map((block) => block.props))\n })\n ),\n closes: Schema.optional(Schema.Array(Schema.String)).annotate({\n description:\n \"Ids of your earlier messages whose block the user just answered by typing instead of using it. Those blocks close. Leave it out otherwise: a block stays open when the user talks about something else.\"\n })\n }),\n success: Schema.String,\n failure: ToolFailure.ToolFailure,\n dependencies: [CurrentChat, AssistantToolCall]\n })\n\n const BlockTools = Toolkit.make(BlockMessage, Wait)\n\n const handlers = yield* BlockTools.toHandlers({\n wait: () => Effect.succeed(\"waiting\"),\n send_user_message: Effect.fnUntraced(function* ({ text, block, closes }) {\n if (text.trim() === \"\") {\n return yield* ToolFailure.fail(\n \"The message has no text: the user sees only what you send here, and a block alone is an empty bubble. Send it again with the words they should read (a short lead-in for the block).\",\n { reason: \"EmptyMessage\" }\n )\n }\n\n const id = yield* messageId\n const closed = closes === undefined || closes.length === 0 ? {} : { closes }\n\n if (block === undefined) {\n yield* append(AssistantEvent.AssistantMessage.make({ id, text, ...closed }))\n\n return \"sent\"\n }\n\n const props = kit.byKind.get(block.kind)?.props\n\n if (props === undefined || Option.isNone(Schema.decodeUnknownOption(props)(block.props))) {\n return yield* ToolFailure.fail(`Those props don't fit a ${block.kind} block.`)\n }\n\n yield* append(\n AssistantEvent.AssistantMessage.make({\n id,\n text,\n block: AssistantEvent.MessageBlock.make(block),\n ...closed\n })\n )\n\n return `sent as message ${id}`\n })\n })\n\n return yield* Effect.provideContext(BlockTools, handlers)\n})\n\n/** @internal */\nexport const makeMessaging: (kit: BlockKit.BlockKit | undefined) => Effect.Effect<Toolkits.Tools<CurrentChat>> =\n Effect.fnUntraced(function* (kit) {\n if (kit === undefined || kit.blocks.length === 0) {\n return Toolkits.fromWithHandler(yield* plainTools)\n }\n\n return Toolkits.fromWithHandler(yield* blockTools(kit))\n })\n"],"mappings":"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"}
@@ -0,0 +1,64 @@
1
+ import { ChatId } from "../ChatId.js";
2
+ import { Tools, toolkit_d_exports } from "./toolkit.js";
3
+ import { AssistantLoop_d_exports, Event } from "../AssistantLoop.js";
4
+ import { CurrentChat } from "../CurrentChat.js";
5
+ import { BlockKit, BlockKit_d_exports } from "../BlockKit.js";
6
+ import { Any, Built, Specialist_d_exports } from "../Specialist.js";
7
+ import { AppEvents, DecodedEntries, events_d_exports } from "./events.js";
8
+ import { Writer } from "./writer.js";
9
+ import { Assistant_d_exports, Turn, TurnServices } from "../Assistant.js";
10
+ import * as Context from "effect/Context";
11
+ import * as Effect from "effect/Effect";
12
+ import * as Stream from "effect/Stream";
13
+ import * as Scope from "effect/Scope";
14
+ import * as LanguageModel from "effect/ai/LanguageModel";
15
+ declare namespace turns_d_exports {
16
+ export { Definition, Registration, TurnHandler, TypedChat, defaultReplyNudge, deliveryNudge, register, run, silentTurnMessage };
17
+ }
18
+ /** @internal */
19
+ export declare const silentTurnMessage = "I finished without sending a reply.";
20
+ /** @internal */
21
+ export declare const defaultReplyNudge = "You haven't replied to the user's latest message yet. Reply now with send_user_message.";
22
+ /** @internal */
23
+ export declare const deliveryNudge = "You told the user you're on it, but stopped without giving them the result. Send it now with send_user_message; if part of it is still running, say what and that you'll report back. If they already have everything, call wait.";
24
+ /** @internal */
25
+ export interface Definition {
26
+ readonly name: string;
27
+ readonly blocks: BlockKit | undefined;
28
+ readonly specialists: ReadonlyArray<Any>;
29
+ readonly app: AppEvents;
30
+ }
31
+ /** @internal */
32
+ export type TurnHandler = (turn: Turn<any>) => Stream.Stream<Event, unknown, TurnServices | LanguageModel.LanguageModel>;
33
+ /** @internal */
34
+ export interface TypedChat {
35
+ readonly chatId: ChatId;
36
+ readonly entries: Effect.Effect<DecodedEntries>;
37
+ readonly append: CurrentChat["Service"]["append"];
38
+ }
39
+ /** @internal */
40
+ export interface Registration {
41
+ readonly definition: Definition;
42
+ readonly model: Context.Context<LanguageModel.LanguageModel>;
43
+ readonly open: (chat: TypedChat) => Effect.Effect<TurnHandler, never, Scope.Scope | CurrentChat>;
44
+ readonly specialists: ReadonlyArray<{
45
+ readonly specialist: Any;
46
+ readonly built: Built;
47
+ }>;
48
+ readonly builtins: Tools<TurnServices>;
49
+ }
50
+ /** @internal */
51
+ export declare const register: (definition: Definition, options: {
52
+ readonly open: Registration["open"];
53
+ }) => Effect.Effect<Registration, never, LanguageModel.LanguageModel>;
54
+ /** @internal */
55
+ export declare const run: (options: {
56
+ readonly registration: Registration;
57
+ readonly handler: TurnHandler;
58
+ readonly chat: Writer;
59
+ readonly services: Context.Context<TurnServices | LanguageModel.LanguageModel>;
60
+ readonly checkOpen: Effect.Effect<void>;
61
+ }) => Effect.Effect<void, never, never>;
62
+ //#endregion
63
+ export { turns_d_exports };
64
+ //# sourceMappingURL=turns.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"turns.d.ts","names":[],"sources":["../../src/internal/turns.ts"],"mappings":";;;;;;;;;;;;;;;;;;qBAmCa;;qBAGA;;qBAIA;;iBAII;WACN;WACA,QAAQ;WACR,aAAa,cAAc;WAC3B,KAAK;;;YAKJ,eACV,MAAM,cACH,OAAO,OAAO,gBAA8B,eAAyB,cAAc;;iBAKvE;WACN,QAAQ;WACR,SAAS,OAAO,OAAO;WACvB,QAAQ;;;iBAIF;WACN,YAAY;WACZ,OAAO,QAAQ,QAAQ,cAAc;WACrC,OAAO,MAAM,cAAc,OAAO,OAAO,oBAAoB,MAAM,QAAQ;WAC3E,aAAa;aACX,YAAY;aACZ,OAAO;;WAET,UAAU,MAAe;;;qBAIvB,WAAQ,YAAA,YAAA;WAEO,MAAA;MAiC1B,OAAA,OAAA,qBAAA,cAAA;;qBAmOW,MAAG;WAEW,cAAA;WACL,SAAA;WACH,MAAA;WACI,UAAA,QAAQ,QAAQ,eAAyB,cAAc;WAEtD,WAAA,OAAO;MA2D9B,OAAA"}
@@ -0,0 +1,214 @@
1
+ import { ToolResult, TurnCompleted, TurnFailed, TurnStarted, isLibraryEvent } from "../AssistantEvent.js";
2
+ import { toJsonText } from "./schema.js";
3
+ import { decodeEntries, read } from "./events.js";
4
+ import { fromEntries, mayHaveRunOutput, notRunOutput, owesDelivery, owesReply, unansweredCalls, unhandledInputs, unreadAfter, userText } from "./history.js";
5
+ import { agentInput, agentOutput, agentSpan, currentTraceId } from "./telemetry.js";
6
+ import { duplicateNames, fromToolkitOrEmpty, merge, provide, runOnly } from "./toolkit.js";
7
+ import { makeMessaging, sendUserMessageName, waitToolName } from "./tools.js";
8
+ import { TurnTools, run as run$1 } from "./loop.js";
9
+ import { withCatalog } from "../Skill.js";
10
+ import { protocol } from "../Instructions.js";
11
+ import { toTools } from "./skills.js";
12
+ import { mapText } from "./instructions.js";
13
+ import { toolkit } from "./specialists.js";
14
+ import * as Predicate from "effect/Predicate";
15
+ import "effect/Context";
16
+ import * as Effect from "effect/Effect";
17
+ import * as Schema from "effect/Schema";
18
+ import * as Stream from "effect/Stream";
19
+ import * as Exit from "effect/Exit";
20
+ import * as Option from "effect/Option";
21
+ import * as Cause from "effect/Cause";
22
+ import { identity } from "effect/Function";
23
+ //#region src/internal/turns.ts
24
+ /** @internal */
25
+ const silentTurnMessage = "I finished without sending a reply.";
26
+ /** @internal */
27
+ const defaultReplyNudge = "You haven't replied to the user's latest message yet. Reply now with send_user_message.";
28
+ /** @internal */
29
+ const deliveryNudge = "You told the user you're on it, but stopped without giving them the result. Send it now with send_user_message; if part of it is still running, say what and that you'll report back. If they already have everything, call wait.";
30
+ /** @internal */
31
+ const register = Effect.fnUntraced(function* (definition, options) {
32
+ const model = yield* Effect.context();
33
+ const specialists = yield* Effect.forEach(definition.specialists, Effect.fnUntraced(function* (specialist) {
34
+ const built = yield* Effect.serviceOption(specialist.Handler);
35
+ if (Option.isNone(built)) return yield* Effect.die(`No handler for the ${specialist.name} specialist: provide ${specialist.name}.toLayer.`);
36
+ return {
37
+ specialist,
38
+ built: built.value
39
+ };
40
+ }));
41
+ const builtins = merge(yield* makeMessaging(definition.blocks), yield* toolkit(definition.specialists));
42
+ return {
43
+ definition,
44
+ model,
45
+ open: options.open,
46
+ specialists,
47
+ builtins
48
+ };
49
+ });
50
+ const ProviderError = Schema.Struct({
51
+ _tag: Schema.optional(Schema.String),
52
+ raw: Schema.Unknown
53
+ });
54
+ const failureMessage = (cause) => Option.match(Schema.decodeUnknownOption(ProviderError)(Cause.squash(cause)), {
55
+ onNone: () => Cause.pretty(cause),
56
+ onSome: ({ _tag, raw }) => `${_tag ?? "Error"}: ${Predicate.isString(raw) ? raw : toJsonText(raw)}`
57
+ });
58
+ const lastSeq = (entries, otherwise) => entries.at(-1)?.seq ?? otherwise;
59
+ const toHistory = (viewed, app) => {
60
+ const out = [];
61
+ let run = [];
62
+ const flush = () => {
63
+ out.push(...fromEntries(run, (event) => read(app, event)));
64
+ run = [];
65
+ };
66
+ for (const item of viewed) if (isEntry(item)) run.push(item);
67
+ else {
68
+ flush();
69
+ out.push(item);
70
+ }
71
+ flush();
72
+ return out;
73
+ };
74
+ const isEntry = (item) => Predicate.hasProperty(item, "seq");
75
+ const closeOpenCalls = Effect.fnUntraced(function* (chat, startSeq) {
76
+ const entries = (yield* chat.entries).filter(({ seq }) => seq > startSeq);
77
+ const started = new Set(entries.flatMap(({ event }) => Predicate.isTagged(event, "ToolStarted") ? [event.callId] : []));
78
+ const open = unansweredCalls(fromEntries(entries, () => void 0));
79
+ if (open.length === 0) return;
80
+ yield* chat.append(open.map((call) => ToolResult.make({
81
+ callId: call.id,
82
+ tool: call.name,
83
+ outcome: "failure",
84
+ ...started.has(call.id) ? {
85
+ result: mayHaveRunOutput,
86
+ reason: "MayHaveRun"
87
+ } : {
88
+ result: notRunOutput,
89
+ reason: "NotRun"
90
+ }
91
+ })));
92
+ });
93
+ const handledThrough = (entries, startSeq) => entries.reduce((through, { seq, event }) => seq > startSeq && isLibraryEvent(event) && Predicate.isTagged(event, "ModelOutput") && event.seenThroughSeq !== void 0 ? Math.max(through, event.seenThroughSeq) : through, startSeq);
94
+ const finishTurn = Effect.fnUntraced(function* (chat, read, wakes) {
95
+ const entries = yield* chat.entries;
96
+ const end = owesReply(entries, read) && !unreadAfter(entries, read, wakes) ? TurnFailed.make({
97
+ throughSeq: read,
98
+ message: silentTurnMessage
99
+ }) : TurnCompleted.make({ throughSeq: read });
100
+ yield* chat.append([end]);
101
+ });
102
+ const failTurn = Effect.fnUntraced(function* (chat, cause) {
103
+ yield* Effect.logError("assistant turn failed", cause);
104
+ const last = lastSeq(yield* chat.entries, 0);
105
+ yield* chat.append([TurnFailed.make({
106
+ throughSeq: last,
107
+ message: failureMessage(cause)
108
+ })]);
109
+ });
110
+ const makeTurnApi = (registration, chat, startSeq, checkOpen) => {
111
+ const { definition, builtins } = registration;
112
+ const { app } = definition;
113
+ const history = Effect.fnUntraced(function* (view = identity) {
114
+ yield* checkOpen;
115
+ const all = yield* chat.entries;
116
+ return {
117
+ messages: toHistory(view(decodeEntries(app, all)), app),
118
+ throughSeq: lastSeq(all, startSeq)
119
+ };
120
+ });
121
+ const instructions = (given) => mapText(given, (text) => `${protocol}\n\n${text}`);
122
+ const replyPass = (options) => Stream.unwrap(Effect.gen(function* () {
123
+ const all = yield* chat.entries;
124
+ const through = handledThrough(all, startSeq);
125
+ if (unreadAfter(all, through, app.wakes)) return Stream.empty;
126
+ const owed = owesReply(all, through);
127
+ const undelivered = !owed && owesDelivery(all, through);
128
+ if (!owed && !undelivered) return Stream.empty;
129
+ const nudge = undelivered ? deliveryNudge : options.nudge ?? "You haven't replied to the user's latest message yet. Reply now with send_user_message.";
130
+ return run$1({
131
+ instructions: options.instructions,
132
+ toolkit: runOnly(options.toolkit, owed ? [sendUserMessageName] : [sendUserMessageName, waitToolName], "Not now: reply to the user with send_user_message."),
133
+ history: Effect.gen(function* () {
134
+ yield* checkOpen;
135
+ const entries = yield* chat.entries;
136
+ return {
137
+ messages: [...toHistory(options.view(decodeEntries(app, entries)), app), userText(nudge)],
138
+ throughSeq: lastSeq(entries, startSeq)
139
+ };
140
+ }),
141
+ maxSteps: 2
142
+ });
143
+ }));
144
+ const replyIfOwed = (options) => replyPass({
145
+ instructions: instructions(options.instructions),
146
+ toolkit: builtins,
147
+ view: identity,
148
+ nudge: options.nudge
149
+ });
150
+ const prepare = (options) => {
151
+ const skills = options.skills ?? [];
152
+ return {
153
+ system: mapText(instructions(options.instructions), (text) => withCatalog(text, skills)),
154
+ toolkit: merge(merge(builtins, toTools(skills)), fromToolkitOrEmpty(options.toolkit)),
155
+ view: options.history ?? identity
156
+ };
157
+ };
158
+ return {
159
+ chatId: chat.chatId,
160
+ history,
161
+ instructions,
162
+ replyIfOwed,
163
+ loop: (options) => {
164
+ const { system, toolkit, view } = prepare(options);
165
+ const twice = duplicateNames(toolkit);
166
+ const loop = twice.length > 0 ? Stream.die(`Tool names used twice, like one named after a built-in: ${twice.join(", ")}`) : run$1({
167
+ instructions: system,
168
+ toolkit,
169
+ history: history(view),
170
+ maxSteps: options.maxSteps ?? 16
171
+ });
172
+ return Stream.concat(loop, replyPass({
173
+ instructions: system,
174
+ toolkit,
175
+ view,
176
+ nudge: options.replyNudge
177
+ }));
178
+ }
179
+ };
180
+ };
181
+ /** @internal */
182
+ const run = Effect.fnUntraced(function* (options) {
183
+ const { chat } = options;
184
+ const before = yield* chat.entries;
185
+ yield* agentInput(unhandledInputs(before, options.registration.definition.app.wakes));
186
+ const assistant = options.registration.definition.name;
187
+ const traceId = yield* currentTraceId;
188
+ const started = yield* chat.append([TurnStarted.make(Option.match(traceId, {
189
+ onNone: () => ({ assistant }),
190
+ onSome: (id) => ({
191
+ assistant,
192
+ traceId: id
193
+ })
194
+ }))]);
195
+ const startSeq = lastSeq(started, 0);
196
+ const turn = makeTurnApi(options.registration, chat, startSeq, options.checkOpen);
197
+ const record = (event) => chat.append([event]);
198
+ yield* options.handler(turn).pipe(Stream.runForEach(record), Effect.onExit((exit) => Exit.hasInterrupts(exit) ? Effect.void : closeOpenCalls(chat, startSeq)), Effect.provideService(TurnTools, Option.some(provide(options.registration.builtins, options.services))), Effect.provideContext(options.services), Effect.matchCauseEffect({
199
+ onSuccess: Effect.fnUntraced(function* () {
200
+ const all = yield* chat.entries;
201
+ yield* finishTurn(chat, handledThrough(all, startSeq), options.registration.definition.app.wakes);
202
+ }),
203
+ onFailure: (cause) => failTurn(chat, cause)
204
+ }));
205
+ const shown = (yield* chat.entries).flatMap(({ event, seq }) => seq > startSeq && (Predicate.isTagged(event, "AssistantMessage") || Predicate.isTagged(event, "TurnFailed")) ? [event] : []);
206
+ yield* agentOutput(shown);
207
+ }, (effect, options) => effect.pipe(agentSpan(options.registration.definition.name, {
208
+ chatId: options.chat.chatId,
209
+ root: true
210
+ })));
211
+ //#endregion
212
+ export { defaultReplyNudge, deliveryNudge, register, run, silentTurnMessage };
213
+
214
+ //# sourceMappingURL=turns.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"turns.js","names":["Toolkits.merge","Tools.makeMessaging","Specialists.toolkit","History.fromEntries","AppEvents.read","History.unansweredCalls","History.mayHaveRunOutput","History.notRunOutput","AssistantEvent.isLibraryEvent","History.owesReply","History.unreadAfter","AppEvents.decodeEntries","Instructions.protocol","History.owesDelivery","Loop.run","Toolkits.runOnly","Tools.sendUserMessageName","Tools.waitToolName","History.userText","Skill.withCatalog","Skills.toTools","Toolkits.fromToolkitOrEmpty","Toolkits.duplicateNames","Telemetry.agentInput","History.unhandledInputs","Telemetry.currentTraceId","Loop.TurnTools","Toolkits.provide","Telemetry.agentOutput","Telemetry.agentSpan"],"sources":["../../src/internal/turns.ts"],"sourcesContent":["import type * as LanguageModel from \"effect/ai/LanguageModel\"\nimport type * as Tool from \"effect/ai/Tool\"\nimport type * as Prompt from \"effect/ai/Prompt\"\nimport * as Cause from \"effect/Cause\"\nimport * as Context from \"effect/Context\"\nimport * as Effect from \"effect/Effect\"\nimport * as Exit from \"effect/Exit\"\nimport { identity } from \"effect/Function\"\nimport * as Option from \"effect/Option\"\nimport * as Predicate from \"effect/Predicate\"\nimport * as Schema from \"effect/Schema\"\nimport type * as Scope from \"effect/Scope\"\nimport * as Stream from \"effect/Stream\"\nimport type * as Assistant from \"../Assistant.ts\"\nimport type { ChatId } from \"../ChatId.ts\"\nimport * as AssistantEvent from \"../AssistantEvent.ts\"\nimport type * as AssistantLoop from \"../AssistantLoop.ts\"\nimport * as Loop from \"./loop.ts\"\nimport type * as BlockKit from \"../BlockKit.ts\"\nimport type { CurrentChat } from \"../CurrentChat.ts\"\nimport * as Instructions from \"../Instructions.ts\"\nimport * as Skill from \"../Skill.ts\"\nimport * as Skills from \"./skills.ts\"\nimport type * as Specialist from \"../Specialist.ts\"\nimport * as AppEvents from \"./events.ts\"\nimport * as History from \"./history.ts\"\nimport { mapText } from \"./instructions.ts\"\nimport { toJsonText } from \"./schema.ts\"\nimport * as Specialists from \"./specialists.ts\"\nimport * as Telemetry from \"./telemetry.ts\"\nimport * as Toolkits from \"./toolkit.ts\"\nimport type { Writer } from \"./writer.ts\"\nimport * as Tools from \"./tools.ts\"\n\n/** @internal */\nexport const silentTurnMessage = \"I finished without sending a reply.\"\n\n/** @internal */\nexport const defaultReplyNudge =\n \"You haven't replied to the user's latest message yet. Reply now with send_user_message.\"\n\n/** @internal */\nexport const deliveryNudge =\n \"You told the user you're on it, but stopped without giving them the result. Send it now with send_user_message; if part of it is still running, say what and that you'll report back. If they already have everything, call wait.\"\n\n/** @internal */\nexport interface Definition {\n readonly name: string\n readonly blocks: BlockKit.BlockKit | undefined\n readonly specialists: ReadonlyArray<Specialist.Any>\n readonly app: AppEvents.AppEvents\n}\n\n// Once its build ran the layer's services are provided, so its streams need only what a turn provides.\n/** @internal */\nexport type TurnHandler = (\n turn: Assistant.Turn<any>\n) => Stream.Stream<AssistantLoop.Event, unknown, Assistant.TurnServices | LanguageModel.LanguageModel>\n\n// The chat `CurrentChat` gives, with its entries typed as the assistant's decode returns them: the assistant's typed\n// chat is this one, so it needs no check of its own.\n/** @internal */\nexport interface TypedChat {\n readonly chatId: ChatId\n readonly entries: Effect.Effect<AppEvents.DecodedEntries>\n readonly append: CurrentChat[\"Service\"][\"append\"]\n}\n\n/** @internal */\nexport interface Registration {\n readonly definition: Definition\n readonly model: Context.Context<LanguageModel.LanguageModel>\n readonly open: (chat: TypedChat) => Effect.Effect<TurnHandler, never, Scope.Scope | CurrentChat>\n readonly specialists: ReadonlyArray<{\n readonly specialist: Specialist.Any\n readonly built: Specialist.Built\n }>\n readonly builtins: Toolkits.Tools<Assistant.TurnServices>\n}\n\n/** @internal */\nexport const register = Effect.fnUntraced(function* (\n definition: Definition,\n options: { readonly open: Registration[\"open\"] }\n) {\n const model = yield* Effect.context<LanguageModel.LanguageModel>()\n\n const specialists = yield* Effect.forEach(\n definition.specialists,\n Effect.fnUntraced(function* (specialist) {\n const built = yield* Effect.serviceOption(specialist.Handler)\n\n if (Option.isNone(built)) {\n return yield* Effect.die(\n `No handler for the ${specialist.name} specialist: provide ${specialist.name}.toLayer.`\n )\n }\n\n return { specialist, built: built.value }\n })\n )\n\n const builtins = Toolkits.merge(\n yield* Tools.makeMessaging(definition.blocks),\n yield* Specialists.toolkit(definition.specialists)\n )\n\n const registration: Registration = {\n definition,\n model,\n open: options.open,\n specialists,\n builtins\n }\n\n return registration\n})\n\nconst ProviderError = Schema.Struct({ _tag: Schema.optional(Schema.String), raw: Schema.Unknown })\n\n// Provider errors keep their response body.\nconst failureMessage = (cause: Cause.Cause<unknown>): string =>\n Option.match(Schema.decodeUnknownOption(ProviderError)(Cause.squash(cause)), {\n onNone: () => Cause.pretty(cause),\n onSome: ({ _tag, raw }) => `${_tag ?? \"Error\"}: ${Predicate.isString(raw) ? raw : toJsonText(raw)}`\n })\n\nconst lastSeq = (entries: ReadonlyArray<AssistantEvent.AnyEntry>, otherwise: number) => entries.at(-1)?.seq ?? otherwise\n\nconst toHistory = (\n viewed: ReadonlyArray<Assistant.Entry<any> | Prompt.Message>,\n app: AppEvents.AppEvents\n): ReadonlyArray<Prompt.Message> => {\n const out: Array<Prompt.Message> = []\n let run: Array<AssistantEvent.AnyEntry> = []\n\n const flush = () => {\n out.push(...History.fromEntries(run, (event) => AppEvents.read(app, event)))\n run = []\n }\n\n for (const item of viewed) {\n if (isEntry(item)) run.push(item)\n else {\n flush()\n out.push(item)\n }\n }\n\n flush()\n\n return out\n}\n\nconst isEntry = (item: Assistant.Entry<any> | Prompt.Message): item is Assistant.Entry<any> =>\n Predicate.hasProperty(item, \"seq\")\n\n// The handler's stream can end between a step's output and its tools, or while they run. An interrupted turn (a\n// shutdown) leaves its calls open instead: the next turn runs them again.\nconst closeOpenCalls = Effect.fnUntraced(function* (chat: Writer, startSeq: number) {\n const entries = (yield* chat.entries).filter(({ seq }) => seq > startSeq)\n\n const started = new Set(\n entries.flatMap(({ event }) => (Predicate.isTagged(event, \"ToolStarted\") ? [event.callId] : []))\n )\n\n const open = History.unansweredCalls(History.fromEntries(entries, () => undefined))\n\n if (open.length === 0) return\n\n yield* chat.append(\n open.map((call) =>\n AssistantEvent.ToolResult.make({\n callId: call.id,\n tool: call.name,\n outcome: \"failure\",\n ...(started.has(call.id)\n ? { result: History.mayHaveRunOutput, reason: \"MayHaveRun\" }\n : { result: History.notRunOutput, reason: \"NotRun\" })\n })\n )\n )\n})\n\n// The inputs before the turn's `TurnStarted` (at `startSeq`) woke it; past that, what its model outputs record\n// reading.\nconst handledThrough = (entries: ReadonlyArray<AssistantEvent.AnyEntry>, startSeq: number) =>\n entries.reduce(\n (through, { seq, event }) =>\n seq > startSeq &&\n AssistantEvent.isLibraryEvent(event) &&\n Predicate.isTagged(event, \"ModelOutput\") &&\n event.seenThroughSeq !== undefined\n ? Math.max(through, event.seenThroughSeq)\n : through,\n startSeq\n )\n\n// Fails visibly when the user's latest input got no reply; an input that arrived after what its model read gets the\n// next turn, which replies.\nconst finishTurn = Effect.fnUntraced(function* (chat: Writer, read: number, wakes: ReadonlySet<string>) {\n const entries = yield* chat.entries\n\n const end =\n History.owesReply(entries, read) && !History.unreadAfter(entries, read, wakes)\n ? AssistantEvent.TurnFailed.make({ throughSeq: read, message: silentTurnMessage })\n : AssistantEvent.TurnCompleted.make({ throughSeq: read })\n\n yield* chat.append([end])\n})\n\n// Everything stored so far counts as read, so a failing input isn't retried forever.\nconst failTurn = Effect.fnUntraced(function* (chat: Writer, cause: Cause.Cause<unknown>) {\n yield* Effect.logError(\"assistant turn failed\", cause)\n\n const last = lastSeq(yield* chat.entries, 0)\n\n yield* chat.append([AssistantEvent.TurnFailed.make({ throughSeq: last, message: failureMessage(cause) })])\n})\n\nconst makeTurnApi = (\n registration: Registration,\n chat: Writer,\n startSeq: number,\n checkOpen: Effect.Effect<void>\n): Assistant.Turn<any> => {\n const { definition, builtins } = registration\n const { app } = definition\n\n // With how far into the chat it goes: the step records it on the model's output.\n // Read before every model call: a chat the app deleted meanwhile stops here.\n const history = Effect.fnUntraced(function* (view: Assistant.HistoryView<any> = identity) {\n yield* checkOpen\n\n const all = yield* chat.entries\n\n const read: AssistantLoop.ChatHistory = {\n messages: toHistory(view(AppEvents.decodeEntries(app, all)), app),\n throughSeq: lastSeq(all, startSeq)\n }\n\n return read\n })\n\n const instructions = (given: string | Instructions.Resolved) =>\n mapText(given, (text) => `${Instructions.protocol}\\n\\n${text}`)\n\n // The loop's own prompt and tools, so the prompt stays cached, with the nudge at the end.\n const replyPass = <R>(options: {\n readonly instructions: Instructions.Resolved\n readonly toolkit: Toolkits.Tools<R>\n readonly view: Assistant.HistoryView<any>\n readonly nudge: string | undefined\n }) =>\n Stream.unwrap(\n Effect.gen(function* () {\n const all = yield* chat.entries\n const through = handledThrough(all, startSeq)\n\n // Something arrived after what the loop's model read: the next turn reads it with the full loop, and replies.\n if (History.unreadAfter(all, through, app.wakes)) return Stream.empty\n\n const owed = History.owesReply(all, through)\n const undelivered = !owed && History.owesDelivery(all, through)\n\n if (!owed && !undelivered) return Stream.empty\n\n const nudge = undelivered ? deliveryNudge : (options.nudge ?? defaultReplyNudge)\n\n return Loop.run({\n instructions: options.instructions,\n // A reply that's owed can only be sent; a result it may have given may also be for nobody.\n toolkit: Toolkits.runOnly(\n options.toolkit,\n owed ? [Tools.sendUserMessageName] : [Tools.sendUserMessageName, Tools.waitToolName],\n \"Not now: reply to the user with send_user_message.\"\n ),\n history: Effect.gen(function* () {\n yield* checkOpen\n\n const entries = yield* chat.entries\n\n return {\n messages: [\n ...toHistory(options.view(AppEvents.decodeEntries(app, entries)), app),\n History.userText(nudge)\n ],\n throughSeq: lastSeq(entries, startSeq)\n }\n }),\n // A second step, for a reply after a call it was refused.\n maxSteps: 2\n })\n })\n )\n\n const replyIfOwed = (options: Assistant.ReplyOptions) =>\n replyPass({\n instructions: instructions(options.instructions),\n toolkit: builtins,\n view: identity,\n nudge: options.nudge\n })\n\n const prepare = <T extends Record<string, Tool.Any>, RS>(options: Assistant.LoopOptions<T, RS, any>) => {\n const skills = options.skills ?? []\n\n return {\n system: mapText(instructions(options.instructions), (text) => Skill.withCatalog(text, skills)),\n toolkit: Toolkits.merge(\n Toolkits.merge(builtins, Skills.toTools(skills)),\n Toolkits.fromToolkitOrEmpty(options.toolkit)\n ),\n view: options.history ?? identity\n }\n }\n\n return {\n chatId: chat.chatId,\n history,\n instructions,\n replyIfOwed,\n loop: (options) => {\n const { system, toolkit, view } = prepare(options)\n const twice = Toolkits.duplicateNames(toolkit)\n\n const loop =\n twice.length > 0\n ? Stream.die(`Tool names used twice, like one named after a built-in: ${twice.join(\", \")}`)\n : Loop.run({\n instructions: system,\n toolkit,\n history: history(view),\n maxSteps: options.maxSteps ?? 16\n })\n\n return Stream.concat(loop, replyPass({ instructions: system, toolkit, view, nudge: options.replyNudge }))\n }\n }\n}\n\n// Every event the handler's loop emits is recorded before the loop is pulled again.\n/** @internal */\nexport const run = Effect.fnUntraced(\n function* (options: {\n readonly registration: Registration\n readonly handler: TurnHandler\n readonly chat: Writer\n readonly services: Context.Context<Assistant.TurnServices | LanguageModel.LanguageModel>\n // A chat the app deleted is deleted here, and the turn stops.\n readonly checkOpen: Effect.Effect<void>\n }) {\n const { chat } = options\n const before = yield* chat.entries\n\n yield* Telemetry.agentInput(History.unhandledInputs(before, options.registration.definition.app.wakes))\n\n const assistant = options.registration.definition.name\n const traceId = yield* Telemetry.currentTraceId\n\n const started = yield* chat.append([\n AssistantEvent.TurnStarted.make(\n Option.match(traceId, {\n onNone: () => ({ assistant }),\n onSome: (id) => ({ assistant, traceId: id })\n })\n )\n ])\n\n const startSeq = lastSeq(started, 0)\n const turn = makeTurnApi(options.registration, chat, startSeq, options.checkOpen)\n\n const record = (event: AssistantLoop.Event) => chat.append([event])\n\n yield* options.handler(turn).pipe(\n Stream.runForEach(record),\n Effect.onExit((exit) => (Exit.hasInterrupts(exit) ? Effect.void : closeOpenCalls(chat, startSeq))),\n // A loop the app writes gets the assistant's own tools too, with this turn's services.\n Effect.provideService(\n Loop.TurnTools,\n Option.some(Toolkits.provide(options.registration.builtins, options.services))\n ),\n Effect.provideContext(options.services),\n Effect.matchCauseEffect({\n onSuccess: Effect.fnUntraced(function* () {\n const all = yield* chat.entries\n\n yield* finishTurn(chat, handledThrough(all, startSeq), options.registration.definition.app.wakes)\n }),\n onFailure: (cause) => failTurn(chat, cause)\n })\n )\n\n // The trace's output is what the user saw this turn, and a failure if any.\n const shown = (yield* chat.entries).flatMap(({ event, seq }) =>\n seq > startSeq && (Predicate.isTagged(event, \"AssistantMessage\") || Predicate.isTagged(event, \"TurnFailed\"))\n ? [event]\n : []\n )\n\n yield* Telemetry.agentOutput(shown)\n },\n (effect, options) =>\n effect.pipe(\n Telemetry.agentSpan(options.registration.definition.name, {\n chatId: options.chat.chatId,\n root: true\n })\n )\n)\n"],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Schema from "effect/Schema";
3
+ import * as Random from "effect/Random";
4
+ //#region src/internal/uuid.ts
5
+ /** @internal */
6
+ const Uuid = Schema.String.check(Schema.isUUID());
7
+ const hex = (bytes) => {
8
+ const digits = bytes.map((byte) => byte.toString(16).padStart(2, "0")).join("");
9
+ return `${digits.slice(0, 8)}-${digits.slice(8, 12)}-${digits.slice(12, 16)}-${digits.slice(16, 20)}-${digits.slice(20)}`;
10
+ };
11
+ const format = (bytes, version) => {
12
+ const out = [...bytes];
13
+ out[6] = (out[6] ?? 0) & 15 | version << 4;
14
+ out[8] = (out[8] ?? 0) & 63 | 128;
15
+ return hex(out);
16
+ };
17
+ /** @internal */
18
+ const make = Effect.gen(function* () {
19
+ const bytes = yield* Effect.forEach(Array.from({ length: 16 }), () => Random.nextIntBetween(0, 255));
20
+ return format(bytes, 4);
21
+ });
22
+ const fnv1a = (key, round) => {
23
+ let hash = 2166136261 ^ round;
24
+ for (let i = 0; i < key.length; i++) hash = Math.imul(hash ^ key.charCodeAt(i), 16777619);
25
+ return hash >>> 0;
26
+ };
27
+ /** @internal */
28
+ const fromKey = (key) => format([
29
+ 0,
30
+ 1,
31
+ 2,
32
+ 3
33
+ ].flatMap((round) => {
34
+ const hash = fnv1a(key, round);
35
+ return [
36
+ hash >>> 24,
37
+ hash >>> 16 & 255,
38
+ hash >>> 8 & 255,
39
+ hash & 255
40
+ ];
41
+ }), 8);
42
+ //#endregion
43
+ export { Uuid, fromKey, make };
44
+
45
+ //# sourceMappingURL=uuid.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uuid.js","names":[],"sources":["../../src/internal/uuid.ts"],"sourcesContent":["import * as Effect from \"effect/Effect\"\nimport * as Random from \"effect/Random\"\nimport * as Schema from \"effect/Schema\"\n\n/** @internal */\nexport const Uuid = Schema.String.check(Schema.isUUID())\n\nconst hex = (bytes: ReadonlyArray<number>): string => {\n const digits = bytes.map((byte) => byte.toString(16).padStart(2, \"0\")).join(\"\")\n\n return `${digits.slice(0, 8)}-${digits.slice(8, 12)}-${digits.slice(12, 16)}-${digits.slice(16, 20)}-${digits.slice(20)}`\n}\n\n// RFC 9562 version nibble and variant bits.\nconst format = (bytes: ReadonlyArray<number>, version: number): string => {\n const out = [...bytes]\n\n out[6] = ((out[6] ?? 0) & 0x0f) | (version << 4)\n out[8] = ((out[8] ?? 0) & 0x3f) | 0x80\n\n return hex(out)\n}\n\n// From the `Random` service, so a seeded test gets the same ids every run.\n/** @internal */\nexport const make: Effect.Effect<string> = Effect.gen(function* () {\n const bytes = yield* Effect.forEach(Array.from({ length: 16 }), () => Random.nextIntBetween(0, 255))\n\n return format(bytes, 4)\n})\n\nconst fnv1a = (key: string, round: number): number => {\n let hash = 0x811c9dc5 ^ round\n\n for (let i = 0; i < key.length; i++) hash = Math.imul(hash ^ key.charCodeAt(i), 0x01000193)\n\n return hash >>> 0\n}\n\n// Version 8: the same key gives the same UUID. Not a secret, only unlikely to collide: four 32-bit FNV-1a hashes.\n/** @internal */\nexport const fromKey = (key: string): string =>\n format(\n [0, 1, 2, 3].flatMap((round) => {\n const hash = fnv1a(key, round)\n\n return [hash >>> 24, (hash >>> 16) & 0xff, (hash >>> 8) & 0xff, hash & 0xff]\n }),\n 8\n )\n"],"mappings":";;;;;AAKA,MAAa,OAAO,OAAO,OAAO,MAAM,OAAO,OAAO,CAAC;AAEvD,MAAM,OAAO,UAAyC;CACpD,MAAM,SAAS,MAAM,KAAK,SAAS,KAAK,SAAS,EAAE,CAAC,CAAC,SAAS,GAAG,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE;CAE9E,OAAO,GAAG,OAAO,MAAM,GAAG,CAAC,EAAE,GAAG,OAAO,MAAM,GAAG,EAAE,EAAE,GAAG,OAAO,MAAM,IAAI,EAAE,EAAE,GAAG,OAAO,MAAM,IAAI,EAAE,EAAE,GAAG,OAAO,MAAM,EAAE;AACxH;AAGA,MAAM,UAAU,OAA8B,YAA4B;CACxE,MAAM,MAAM,CAAC,GAAG,KAAK;CAErB,IAAI,MAAO,IAAI,MAAM,KAAK,KAAS,WAAW;CAC9C,IAAI,MAAO,IAAI,MAAM,KAAK,KAAQ;CAElC,OAAO,IAAI,GAAG;AAChB;;AAIA,MAAa,OAA8B,OAAO,IAAI,aAAa;CACjE,MAAM,QAAQ,OAAO,OAAO,QAAQ,MAAM,KAAK,EAAE,QAAQ,GAAG,CAAC,SAAS,OAAO,eAAe,GAAG,GAAG,CAAC;CAEnG,OAAO,OAAO,OAAO,CAAC;AACxB,CAAC;AAED,MAAM,SAAS,KAAa,UAA0B;CACpD,IAAI,OAAO,aAAa;CAExB,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK,OAAO,KAAK,KAAK,OAAO,IAAI,WAAW,CAAC,GAAG,QAAU;CAE1F,OAAO,SAAS;AAClB;;AAIA,MAAa,WAAW,QACtB,OACE;CAAC;CAAG;CAAG;CAAG;AAAC,CAAC,CAAC,SAAS,UAAU;CAC9B,MAAM,OAAO,MAAM,KAAK,KAAK;CAE7B,OAAO;EAAC,SAAS;EAAK,SAAS,KAAM;EAAO,SAAS,IAAK;EAAM,OAAO;CAAI;AAC7E,CAAC,GACD,CACF"}
@@ -0,0 +1,12 @@
1
+ import { ChatId } from "../ChatId.js";
2
+ import { AssistantEvent, AssistantEvent_d_exports, Entry, StoredAppEvent } from "../AssistantEvent.js";
3
+ import * as Effect from "effect/Effect";
4
+ //#region src/internal/writer.d.ts
5
+ /** @internal */
6
+ export interface Writer {
7
+ readonly chatId: ChatId;
8
+ readonly entries: Effect.Effect<ReadonlyArray<Entry>>;
9
+ readonly append: (events: ReadonlyArray<AssistantEvent | StoredAppEvent>) => Effect.Effect<ReadonlyArray<Entry>>;
10
+ }
11
+ //#endregion
12
+ //# sourceMappingURL=writer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"writer.d.ts","names":[],"sources":["../../src/internal/writer.ts"],"mappings":";;;;;iBAKiB;WACN,QAAQ;WACR,SAAS,OAAO,OAAO,cAAc;WACrC,SACP,QAAQ,cAAc,iBAAgC,oBACnD,OAAO,OAAO,cAAc"}
package/package.json CHANGED
@@ -1,6 +1,55 @@
1
1
  {
2
2
  "name": "effect-assistant",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.2.0",
4
+ "description": "A colleague assistant on Effect: an append-only event log per chat, an interruptible agent loop, specialists, skills, and UI blocks",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/dbrxnds/effect-assistant.git",
9
+ "directory": "packages/effect-assistant"
10
+ },
11
+ "files": [
12
+ "src/**/*.ts",
13
+ "dist/**/*.js",
14
+ "dist/**/*.js.map",
15
+ "dist/**/*.d.ts",
16
+ "dist/**/*.d.ts.map"
17
+ ],
18
+ "type": "module",
19
+ "sideEffects": [],
20
+ "exports": {
21
+ "./package.json": "./package.json",
22
+ ".": {
23
+ "types": "./dist/index.d.ts",
24
+ "default": "./dist/index.js"
25
+ },
26
+ "./*": {
27
+ "types": "./dist/*.d.ts",
28
+ "default": "./dist/*.js"
29
+ },
30
+ "./internal/*": null
31
+ },
32
+ "publishConfig": {
33
+ "access": "public",
34
+ "provenance": true
35
+ },
36
+ "devDependencies": {
37
+ "@effect/ai-anthropic": "4.0.0",
38
+ "@effect/sql-sqlite-node": "4.0.0",
39
+ "@effect/vitest": "4.0.0",
40
+ "@types/node": "^22.15.3",
41
+ "effect": "4.0.0",
42
+ "typescript": "7.0.2",
43
+ "vite-plus": "1.0.0-rc.0",
44
+ "vitest": "5.0.1"
45
+ },
46
+ "peerDependencies": {
47
+ "effect": "^4.0.0"
48
+ },
49
+ "scripts": {
50
+ "build": "vp pack",
51
+ "test": "vp test run --exclude test/contract.test.ts",
52
+ "test:contract": "vp test run test/contract.test.ts"
53
+ },
54
+ "types": "./dist/index.d.ts"
6
55
  }