effect-assistant 0.1.0 → 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 (80) hide show
  1. package/README.md +1 -1
  2. package/dist/AssistantEvent.d.ts +3 -66
  3. package/dist/AssistantEvent.d.ts.map +1 -1
  4. package/dist/AssistantEvent.js +1 -51
  5. package/dist/AssistantEvent.js.map +1 -1
  6. package/dist/AssistantLoop.d.ts +7 -5
  7. package/dist/AssistantLoop.d.ts.map +1 -1
  8. package/dist/AssistantLoop.js +6 -3
  9. package/dist/AssistantLoop.js.map +1 -1
  10. package/dist/Block.js.map +1 -1
  11. package/dist/BlockKit.d.ts +11 -15
  12. package/dist/BlockKit.d.ts.map +1 -1
  13. package/dist/BlockKit.js +3 -6
  14. package/dist/BlockKit.js.map +1 -1
  15. package/dist/ChatView.d.ts +7 -58
  16. package/dist/ChatView.d.ts.map +1 -1
  17. package/dist/ChatView.js +15 -68
  18. package/dist/ChatView.js.map +1 -1
  19. package/dist/Compaction.d.ts +2 -2
  20. package/dist/Compaction.d.ts.map +1 -1
  21. package/dist/Compaction.js +6 -13
  22. package/dist/Compaction.js.map +1 -1
  23. package/dist/ToolDiscovery.d.ts +4 -5
  24. package/dist/ToolDiscovery.d.ts.map +1 -1
  25. package/dist/ToolDiscovery.js +5 -4
  26. package/dist/ToolDiscovery.js.map +1 -1
  27. package/dist/ToolFailure.d.ts +1 -1
  28. package/dist/ToolFailure.js +1 -1
  29. package/dist/ToolFailure.js.map +1 -1
  30. package/dist/index.d.ts +1 -3
  31. package/dist/index.js +3 -5
  32. package/dist/internal/chat.js +6 -31
  33. package/dist/internal/chat.js.map +1 -1
  34. package/dist/internal/history.js +2 -40
  35. package/dist/internal/history.js.map +1 -1
  36. package/dist/internal/loop.d.ts +2 -2
  37. package/dist/internal/loop.d.ts.map +1 -1
  38. package/dist/internal/loop.js +20 -55
  39. package/dist/internal/loop.js.map +1 -1
  40. package/dist/internal/specialists.js +2 -3
  41. package/dist/internal/specialists.js.map +1 -1
  42. package/dist/internal/toolkit.d.ts +6 -20
  43. package/dist/internal/toolkit.d.ts.map +1 -1
  44. package/dist/internal/toolkit.js +10 -31
  45. package/dist/internal/toolkit.js.map +1 -1
  46. package/dist/internal/tools.js +1 -20
  47. package/dist/internal/tools.js.map +1 -1
  48. package/dist/internal/turns.d.ts.map +1 -1
  49. package/dist/internal/turns.js +2 -9
  50. package/dist/internal/turns.js.map +1 -1
  51. package/package.json +3 -2
  52. package/src/AssistantEvent.ts +0 -66
  53. package/src/AssistantLoop.ts +7 -5
  54. package/src/Block.ts +1 -1
  55. package/src/BlockKit.ts +12 -25
  56. package/src/ChatView.ts +10 -81
  57. package/src/Compaction.ts +2 -19
  58. package/src/ToolDiscovery.ts +5 -13
  59. package/src/ToolFailure.ts +1 -1
  60. package/src/index.ts +0 -10
  61. package/src/internal/chat.ts +4 -53
  62. package/src/internal/history.ts +2 -74
  63. package/src/internal/loop.ts +31 -101
  64. package/src/internal/specialists.ts +3 -7
  65. package/src/internal/toolkit.ts +17 -60
  66. package/src/internal/tools.ts +2 -53
  67. package/src/internal/turns.ts +7 -26
  68. package/dist/Decision.d.ts +0 -145
  69. package/dist/Decision.d.ts.map +0 -1
  70. package/dist/Decision.js +0 -178
  71. package/dist/Decision.js.map +0 -1
  72. package/dist/DecisionId.d.ts +0 -37
  73. package/dist/DecisionId.d.ts.map +0 -1
  74. package/dist/DecisionId.js +0 -44
  75. package/dist/DecisionId.js.map +0 -1
  76. package/dist/internal/decisions.js +0 -66
  77. package/dist/internal/decisions.js.map +0 -1
  78. package/src/Decision.ts +0 -241
  79. package/src/DecisionId.ts +0 -46
  80. package/src/internal/decisions.ts +0 -131
@@ -1,5 +1,4 @@
1
1
  import { AssistantToolCall } from "./AssistantToolCall.js";
2
- import { ToolServices, toolkit_d_exports } from "./internal/toolkit.js";
3
2
  import * as Schema from "effect/Schema";
4
3
  import * as Tool from "effect/ai/Tool";
5
4
  import * as Toolkit from "effect/ai/Toolkit";
@@ -15,8 +14,8 @@ declare namespace ToolDiscovery_d_exports {
15
14
  * the tools' handlers. A catalog tool's services are `execute_tool`'s, so the turn provides them
16
15
  * as it does for any tool.
17
16
  *
18
- * `execute_tool` doesn't run a tool that needs the user's decision; keep such tools in the loop's
19
- * toolkit directly.
17
+ * A catalog tool with Effect's `needsApproval` set fails the layer when it is built, as it would
18
+ * fail a loop: the library doesn't run calls that wait for approval.
20
19
  *
21
20
  * **Example** (Finding rarely used tools when needed)
22
21
  *
@@ -51,7 +50,7 @@ export declare const make: <T extends Record<string, Tool.Any>>(_catalog: Toolki
51
50
  readonly success: Schema.Codec<Schema.Json, Schema.Json, never, never>;
52
51
  readonly failure: Schema.Codec<Schema.Json, Schema.Json, never, never>;
53
52
  readonly failureMode: "error";
54
- }, AssistantToolCall | ToolServices<T>>;
53
+ }, AssistantToolCall | Exclude<Tool.HandlerServices<T[keyof T]>, AssistantToolCall>>;
55
54
  readonly search_tools: Tool.Tool<"search_tools", {
56
55
  readonly parameters: Schema.Struct<{
57
56
  readonly query: Schema.String;
@@ -89,7 +88,7 @@ export declare const layer: <T extends Record<string, Tool.Any>>(catalog: Toolki
89
88
  readonly success: Schema.Codec<Schema.Json, Schema.Json, never, never>;
90
89
  readonly failure: Schema.Codec<Schema.Json, Schema.Json, never, never>;
91
90
  readonly failureMode: "error";
92
- }, AssistantToolCall | ToolServices<T>>;
91
+ }, AssistantToolCall | Exclude<Tool.HandlerServices<T[keyof T]>, AssistantToolCall>>;
93
92
  readonly search_tools: Tool.Tool<"search_tools", {
94
93
  readonly parameters: Schema.Struct<{
95
94
  readonly query: Schema.String;
@@ -1 +1 @@
1
- {"version":3,"file":"ToolDiscovery.d.ts","names":[],"sources":["../src/ToolDiscovery.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBA2Ha,OAAQ,UAAU,eAAe,KAAK,MAAI,UAE3C,QAAQ,QAAQ,OAAE,QAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAkBjB,QAAS,UAAU,eAAe,KAAK,MAAI,SAAW,QAAQ,QAAQ,8BAAE,MAAA,KAAA;;;;;;;;;;;;;;;;;;;;;;WA2ClF,QAAA,KAAA,YAAA,2BAAA"}
1
+ {"version":3,"file":"ToolDiscovery.d.ts","names":[],"sources":["../src/ToolDiscovery.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBA2Ha,OAAQ,UAAU,eAAe,KAAK,MAAI,UAE3C,QAAQ,QAAQ,OAAE,QAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAkBjB,QAAS,UAAU,eAAe,KAAK,MAAI,SAAW,QAAQ,QAAQ,8BAAE,MAAA,KAAA;;;;;;;;;;;;;;;;;;;;;;WAmClF,QAAA,KAAA,YAAA,2BAAA"}
@@ -1,7 +1,7 @@
1
1
  import { __exportAll } from "./_virtual/_rolldown/runtime.js";
2
2
  import { toJson } from "./internal/schema.js";
3
3
  import { AssistantToolCall } from "./AssistantToolCall.js";
4
- import { failed, needsDecision, runNative } from "./internal/toolkit.js";
4
+ import { needingApproval, needsApprovalMessage, runNative } from "./internal/toolkit.js";
5
5
  import * as Effect from "effect/Effect";
6
6
  import * as Schema from "effect/Schema";
7
7
  import * as Response from "effect/ai/Response";
@@ -67,8 +67,8 @@ const requiring = () => (tool) => tool;
67
67
  * the tools' handlers. A catalog tool's services are `execute_tool`'s, so the turn provides them
68
68
  * as it does for any tool.
69
69
  *
70
- * `execute_tool` doesn't run a tool that needs the user's decision; keep such tools in the loop's
71
- * toolkit directly.
70
+ * A catalog tool with Effect's `needsApproval` set fails the layer when it is built, as it would
71
+ * fail a loop: the library doesn't run calls that wait for approval.
72
72
  *
73
73
  * **Example** (Finding rarely used tools when needed)
74
74
  *
@@ -113,6 +113,8 @@ const make = (_catalog) => Toolkit.make(SearchTools, requiring()(ExecuteTool));
113
113
  const layer = (catalog) => make(catalog).toLayer(Effect.gen(function* () {
114
114
  const resolved = yield* catalog;
115
115
  const definitions = Object.values(resolved.tools);
116
+ const needingApproval$1 = needingApproval(definitions);
117
+ if (needingApproval$1.length > 0) return yield* Effect.die(needsApprovalMessage(needingApproval$1));
116
118
  return {
117
119
  search_tools: ({ query }) => Effect.sync(() => search(definitions, query, 5).map((tool) => ({
118
120
  name: tool.name,
@@ -127,7 +129,6 @@ const layer = (catalog) => make(catalog).toLayer(Effect.gen(function* () {
127
129
  params: input,
128
130
  providerExecuted: false
129
131
  });
130
- if (yield* needsDecision(resolved, call, [])) return yield* Effect.fail(failed(`${name} needs the user's decision, so it can't run through execute_tool.`, "NeedsDecision").result);
131
132
  const { outcome, result } = yield* runNative(resolved, call);
132
133
  return outcome === "failure" ? yield* Effect.fail(result) : result;
133
134
  })
@@ -1 +1 @@
1
- {"version":3,"file":"ToolDiscovery.js","names":["Toolkits.needsDecision","Toolkits.failed","Toolkits.runNative"],"sources":["../src/ToolDiscovery.ts"],"sourcesContent":["/**\n * Turns a catalog toolkit into two tools, so the prompt doesn't carry every tool definition.\n *\n * `search_tools` finds tools by what the model needs to do and returns their names, descriptions,\n * and input schemas. `execute_tool` validates an input and runs one of them. {@link make} returns\n * the two tools as a native toolkit, and {@link layer} their handlers.\n *\n * @since 0.1.0\n */\nimport * as Response from \"effect/ai/Response\"\nimport * as Tool from \"effect/ai/Tool\"\nimport * as Toolkit from \"effect/ai/Toolkit\"\nimport * as Effect from \"effect/Effect\"\nimport * as Schema from \"effect/Schema\"\nimport { AssistantToolCall } from \"./AssistantToolCall.ts\"\nimport { toJson } from \"./internal/schema.ts\"\nimport * as Toolkits from \"./internal/toolkit.ts\"\n\nconst words = (text: string): ReadonlySet<string> =>\n new Set(\n text\n .toLowerCase()\n .split(/[^a-z0-9]+/)\n .filter((word) => word.length >= 3)\n )\n\nconst score = (query: ReadonlySet<string>, text: string): number => {\n const target = [...words(text)]\n\n const matches = (word: string) =>\n target.some((t) => t === word || (word.length >= 4 && (t.startsWith(word) || word.startsWith(t))))\n\n return [...query].filter(matches).length\n}\n\n// Everything when nothing matches, so the model can pick or search again.\nconst search = (tools: ReadonlyArray<Tool.Any>, query: string, limit: number): ReadonlyArray<Tool.Any> => {\n const queryWords = words(query)\n\n const ranked = tools\n .flatMap((tool) => {\n const hits = score(queryWords, `${tool.name} ${Tool.getDescription(tool) ?? \"\"}`)\n\n return hits > 0 ? [{ tool, score: hits }] : []\n })\n .sort((a, b) => b.score - a.score)\n\n return (ranked.length > 0 ? ranked.map((hit) => hit.tool) : tools).slice(0, limit)\n}\n\nconst SearchTools = Tool.make(\"search_tools\", {\n description:\n \"Search for more tools by what you need to do. Returns their names, descriptions, and input schemas; call one with execute_tool.\",\n parameters: Schema.Struct({\n query: Schema.String.annotate({ description: \"What you need to do, in a few words.\" })\n }),\n success: Schema.Array(\n Schema.Struct({\n name: Schema.String,\n description: Schema.optional(Schema.String),\n inputSchema: Schema.Json\n })\n )\n})\n\nconst ExecuteTool = Tool.make(\"execute_tool\", {\n description: \"Run a tool found with search_tools. Pass its name and an input matching its input schema.\",\n parameters: Schema.Struct({\n name: Schema.String,\n input: Schema.Record(Schema.String, Schema.Json)\n }),\n success: Schema.Json,\n // The tool's failure is this call's, so the loop sees it failed (a `wait: true` message doesn't\n // end the turn).\n failure: Schema.Json,\n dependencies: [AssistantToolCall]\n})\n\n// A tool's requirements are a covariant phantom type, so one that needs nothing is one that needs\n// any.\nconst requiring =\n <R>() =>\n <N extends string, C extends (Tool.Any extends Tool.Tool<string, infer K, unknown> ? K : never), R0>(\n tool: Tool.Tool<N, C, R0>\n ): Tool.Tool<N, C, R0 | R> =>\n tool\n\n/**\n * Creates the `search_tools` and `execute_tool` tools over a catalog toolkit, as a native toolkit.\n *\n * **Details**\n *\n * Merge the toolkit into the loop's, and provide {@link layer} for the same catalog, which has\n * the tools' handlers. A catalog tool's services are `execute_tool`'s, so the turn provides them\n * as it does for any tool.\n *\n * `execute_tool` doesn't run a tool that needs the user's decision; keep such tools in the loop's\n * toolkit directly.\n *\n * **Example** (Finding rarely used tools when needed)\n *\n * ```ts\n * import { ToolDiscovery } from \"effect-assistant\"\n * import * as Toolkit from \"effect/ai/Toolkit\"\n * import * as Layer from \"effect/Layer\"\n *\n * // OfficeTools is a big toolkit, found when needed. CommonTools stays in the prompt.\n * const OfficeDiscovery = ToolDiscovery.make(OfficeTools)\n * const OfficeDiscoveryLive = ToolDiscovery.layer(OfficeTools).pipe(Layer.provide(OfficeToolsLive))\n *\n * const MyAssistantLive = MyAssistant.toLayer((turn) =>\n * turn.loop({\n * instructions,\n * toolkit: Toolkit.merge(CommonTools, OfficeDiscovery)\n * })\n * ).pipe(Layer.provide([CommonToolsLive, OfficeDiscoveryLive]))\n * ```\n *\n * @see {@link layer} for the tools' handlers\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const make = <T extends Record<string, Tool.Any>>(\n // Only its type is used here, for `execute_tool`'s services: `layer` runs its tools.\n _catalog: Toolkit.Toolkit<T>\n) => Toolkit.make(SearchTools, requiring<Toolkits.ToolServices<T>>()(ExecuteTool))\n\n/**\n * Creates the layer with the handlers of the `search_tools` and `execute_tool` tools over a\n * catalog toolkit.\n *\n * **Details**\n *\n * The layer needs the catalog's handlers. It provides the handlers of the toolkit that\n * {@link make} returns for the same catalog: handlers are keyed by tool name, so the two calls\n * don't need to share a value. Give both the same catalog.\n *\n * @see {@link make} for the toolkit and an example\n *\n * @category layers\n * @since 0.1.0\n */\nexport const layer = <T extends Record<string, Tool.Any>>(catalog: Toolkit.Toolkit<T>) =>\n make(catalog).toLayer(\n Effect.gen(function* () {\n const resolved = yield* catalog\n const definitions = Object.values(resolved.tools)\n\n return {\n search_tools: ({ query }) =>\n Effect.sync(() =>\n search(definitions, query, 5).map((tool) => ({\n name: tool.name,\n description: Tool.getDescription(tool),\n inputSchema: toJson(Tool.getJsonSchema(tool))\n }))\n ),\n execute_tool: Effect.fnUntraced(function* ({ name, input }) {\n // Named after the call that runs it: the same id when a crash runs that call again.\n const outer = yield* AssistantToolCall\n\n const call = Response.makePart(\"tool-call\", {\n id: `${outer.callId}:${name}`,\n name,\n params: input,\n providerExecuted: false\n })\n\n // A call that needs the user's decision parks only as a call the model made itself,\n // where it can ask for it.\n if (yield* Toolkits.needsDecision(resolved, call, [])) {\n return yield* Effect.fail(\n Toolkits.failed(\n `${name} needs the user's decision, so it can't run through execute_tool.`,\n \"NeedsDecision\"\n ).result\n )\n }\n\n const { outcome, result } = yield* Toolkits.runNative(resolved, call)\n\n return outcome === \"failure\" ? yield* Effect.fail(result) : result\n })\n }\n })\n )\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAkBA,MAAM,SAAS,SACb,IAAI,IACF,KACG,YAAY,CAAC,CACb,MAAM,YAAY,CAAC,CACnB,QAAQ,SAAS,KAAK,UAAU,CAAC,CACtC;AAEF,MAAM,SAAS,OAA4B,SAAyB;CAClE,MAAM,SAAS,CAAC,GAAG,MAAM,IAAI,CAAC;CAE9B,MAAM,WAAW,SACf,OAAO,MAAM,MAAM,MAAM,QAAS,KAAK,UAAU,MAAM,EAAE,WAAW,IAAI,KAAK,KAAK,WAAW,CAAC,EAAG;CAEnG,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC;AACpC;AAGA,MAAM,UAAU,OAAgC,OAAe,UAA2C;CACxG,MAAM,aAAa,MAAM,KAAK;CAE9B,MAAM,SAAS,MACZ,SAAS,SAAS;EACjB,MAAM,OAAO,MAAM,YAAY,GAAG,KAAK,KAAK,GAAG,KAAK,eAAe,IAAI,KAAK,IAAI;EAEhF,OAAO,OAAO,IAAI,CAAC;GAAE;GAAM,OAAO;EAAK,CAAC,IAAI,CAAC;CAC/C,CAAC,CAAC,CACD,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;CAEnC,QAAQ,OAAO,SAAS,IAAI,OAAO,KAAK,QAAQ,IAAI,IAAI,IAAI,MAAA,CAAO,MAAM,GAAG,KAAK;AACnF;AAEA,MAAM,cAAc,KAAK,KAAK,gBAAgB;CAC5C,aACE;CACF,YAAY,OAAO,OAAO,EACxB,OAAO,OAAO,OAAO,SAAS,EAAE,aAAa,uCAAuC,CAAC,EACvF,CAAC;CACD,SAAS,OAAO,MACd,OAAO,OAAO;EACZ,MAAM,OAAO;EACb,aAAa,OAAO,SAAS,OAAO,MAAM;EAC1C,aAAa,OAAO;CACtB,CAAC,CACH;AACF,CAAC;AAED,MAAM,cAAc,KAAK,KAAK,gBAAgB;CAC5C,aAAa;CACb,YAAY,OAAO,OAAO;EACxB,MAAM,OAAO;EACb,OAAO,OAAO,OAAO,OAAO,QAAQ,OAAO,IAAI;CACjD,CAAC;CACD,SAAS,OAAO;CAGhB,SAAS,OAAO;CAChB,cAAc,CAAC,iBAAiB;AAClC,CAAC;AAID,MAAM,mBAGF,SAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCJ,MAAa,QAEX,aACG,QAAQ,KAAK,aAAa,UAAoC,CAAC,CAAC,WAAW,CAAC;;;;;;;;;;;;;;;;AAiBjF,MAAa,SAA6C,YACxD,KAAK,OAAO,CAAC,CAAC,QACZ,OAAO,IAAI,aAAa;CACtB,MAAM,WAAW,OAAO;CACxB,MAAM,cAAc,OAAO,OAAO,SAAS,KAAK;CAEhD,OAAO;EACL,eAAe,EAAE,YACf,OAAO,WACL,OAAO,aAAa,OAAO,CAAC,CAAC,CAAC,KAAK,UAAU;GAC3C,MAAM,KAAK;GACX,aAAa,KAAK,eAAe,IAAI;GACrC,aAAa,OAAO,KAAK,cAAc,IAAI,CAAC;EAC9C,EAAE,CACJ;EACF,cAAc,OAAO,WAAW,WAAW,EAAE,MAAM,SAAS;GAE1D,MAAM,QAAQ,OAAO;GAErB,MAAM,OAAO,SAAS,SAAS,aAAa;IAC1C,IAAI,GAAG,MAAM,OAAO,GAAG;IACvB;IACA,QAAQ;IACR,kBAAkB;GACpB,CAAC;GAID,IAAI,OAAOA,cAAuB,UAAU,MAAM,CAAC,CAAC,GAClD,OAAO,OAAO,OAAO,KACnBC,OACE,GAAG,KAAK,oEACR,eACF,CAAC,CAAC,MACJ;GAGF,MAAM,EAAE,SAAS,WAAW,OAAOC,UAAmB,UAAU,IAAI;GAEpE,OAAO,YAAY,YAAY,OAAO,OAAO,KAAK,MAAM,IAAI;EAC9D,CAAC;CACH;AACF,CAAC,CACH"}
1
+ {"version":3,"file":"ToolDiscovery.js","names":["needingApproval","Toolkits.needingApproval","Toolkits.needsApprovalMessage","Toolkits.runNative"],"sources":["../src/ToolDiscovery.ts"],"sourcesContent":["/**\n * Turns a catalog toolkit into two tools, so the prompt doesn't carry every tool definition.\n *\n * `search_tools` finds tools by what the model needs to do and returns their names, descriptions,\n * and input schemas. `execute_tool` validates an input and runs one of them. {@link make} returns\n * the two tools as a native toolkit, and {@link layer} their handlers.\n *\n * @since 0.1.0\n */\nimport * as Response from \"effect/ai/Response\"\nimport * as Tool from \"effect/ai/Tool\"\nimport * as Toolkit from \"effect/ai/Toolkit\"\nimport * as Effect from \"effect/Effect\"\nimport * as Schema from \"effect/Schema\"\nimport { AssistantToolCall } from \"./AssistantToolCall.ts\"\nimport { toJson } from \"./internal/schema.ts\"\nimport * as Toolkits from \"./internal/toolkit.ts\"\n\nconst words = (text: string): ReadonlySet<string> =>\n new Set(\n text\n .toLowerCase()\n .split(/[^a-z0-9]+/)\n .filter((word) => word.length >= 3)\n )\n\nconst score = (query: ReadonlySet<string>, text: string): number => {\n const target = [...words(text)]\n\n const matches = (word: string) =>\n target.some((t) => t === word || (word.length >= 4 && (t.startsWith(word) || word.startsWith(t))))\n\n return [...query].filter(matches).length\n}\n\n// Everything when nothing matches, so the model can pick or search again.\nconst search = (tools: ReadonlyArray<Tool.Any>, query: string, limit: number): ReadonlyArray<Tool.Any> => {\n const queryWords = words(query)\n\n const ranked = tools\n .flatMap((tool) => {\n const hits = score(queryWords, `${tool.name} ${Tool.getDescription(tool) ?? \"\"}`)\n\n return hits > 0 ? [{ tool, score: hits }] : []\n })\n .sort((a, b) => b.score - a.score)\n\n return (ranked.length > 0 ? ranked.map((hit) => hit.tool) : tools).slice(0, limit)\n}\n\nconst SearchTools = Tool.make(\"search_tools\", {\n description:\n \"Search for more tools by what you need to do. Returns their names, descriptions, and input schemas; call one with execute_tool.\",\n parameters: Schema.Struct({\n query: Schema.String.annotate({ description: \"What you need to do, in a few words.\" })\n }),\n success: Schema.Array(\n Schema.Struct({\n name: Schema.String,\n description: Schema.optional(Schema.String),\n inputSchema: Schema.Json\n })\n )\n})\n\nconst ExecuteTool = Tool.make(\"execute_tool\", {\n description: \"Run a tool found with search_tools. Pass its name and an input matching its input schema.\",\n parameters: Schema.Struct({\n name: Schema.String,\n input: Schema.Record(Schema.String, Schema.Json)\n }),\n success: Schema.Json,\n // The tool's failure is this call's, so the loop sees it failed (a `wait: true` message doesn't\n // end the turn).\n failure: Schema.Json,\n dependencies: [AssistantToolCall]\n})\n\n// A tool's requirements are a covariant phantom type, so one that needs nothing is one that needs\n// any.\nconst requiring =\n <R>() =>\n <N extends string, C extends (Tool.Any extends Tool.Tool<string, infer K, unknown> ? K : never), R0>(\n tool: Tool.Tool<N, C, R0>\n ): Tool.Tool<N, C, R0 | R> =>\n tool\n\n/**\n * Creates the `search_tools` and `execute_tool` tools over a catalog toolkit, as a native toolkit.\n *\n * **Details**\n *\n * Merge the toolkit into the loop's, and provide {@link layer} for the same catalog, which has\n * the tools' handlers. A catalog tool's services are `execute_tool`'s, so the turn provides them\n * as it does for any tool.\n *\n * A catalog tool with Effect's `needsApproval` set fails the layer when it is built, as it would\n * fail a loop: the library doesn't run calls that wait for approval.\n *\n * **Example** (Finding rarely used tools when needed)\n *\n * ```ts\n * import { ToolDiscovery } from \"effect-assistant\"\n * import * as Toolkit from \"effect/ai/Toolkit\"\n * import * as Layer from \"effect/Layer\"\n *\n * // OfficeTools is a big toolkit, found when needed. CommonTools stays in the prompt.\n * const OfficeDiscovery = ToolDiscovery.make(OfficeTools)\n * const OfficeDiscoveryLive = ToolDiscovery.layer(OfficeTools).pipe(Layer.provide(OfficeToolsLive))\n *\n * const MyAssistantLive = MyAssistant.toLayer((turn) =>\n * turn.loop({\n * instructions,\n * toolkit: Toolkit.merge(CommonTools, OfficeDiscovery)\n * })\n * ).pipe(Layer.provide([CommonToolsLive, OfficeDiscoveryLive]))\n * ```\n *\n * @see {@link layer} for the tools' handlers\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const make = <T extends Record<string, Tool.Any>>(\n // Only its type is used here, for `execute_tool`'s services: `layer` runs its tools.\n _catalog: Toolkit.Toolkit<T>\n) => Toolkit.make(SearchTools, requiring<Toolkits.ToolServices<T>>()(ExecuteTool))\n\n/**\n * Creates the layer with the handlers of the `search_tools` and `execute_tool` tools over a\n * catalog toolkit.\n *\n * **Details**\n *\n * The layer needs the catalog's handlers. It provides the handlers of the toolkit that\n * {@link make} returns for the same catalog: handlers are keyed by tool name, so the two calls\n * don't need to share a value. Give both the same catalog.\n *\n * @see {@link make} for the toolkit and an example\n *\n * @category layers\n * @since 0.1.0\n */\nexport const layer = <T extends Record<string, Tool.Any>>(catalog: Toolkit.Toolkit<T>) =>\n make(catalog).toLayer(\n Effect.gen(function* () {\n const resolved = yield* catalog\n const definitions = Object.values(resolved.tools)\n const needingApproval = Toolkits.needingApproval(definitions)\n\n if (needingApproval.length > 0) return yield* Effect.die(Toolkits.needsApprovalMessage(needingApproval))\n\n return {\n search_tools: ({ query }) =>\n Effect.sync(() =>\n search(definitions, query, 5).map((tool) => ({\n name: tool.name,\n description: Tool.getDescription(tool),\n inputSchema: toJson(Tool.getJsonSchema(tool))\n }))\n ),\n execute_tool: Effect.fnUntraced(function* ({ name, input }) {\n // Named after the call that runs it: the same id when a crash runs that call again.\n const outer = yield* AssistantToolCall\n\n const call = Response.makePart(\"tool-call\", {\n id: `${outer.callId}:${name}`,\n name,\n params: input,\n providerExecuted: false\n })\n\n const { outcome, result } = yield* Toolkits.runNative(resolved, call)\n\n return outcome === \"failure\" ? yield* Effect.fail(result) : result\n })\n }\n })\n )\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAkBA,MAAM,SAAS,SACb,IAAI,IACF,KACG,YAAY,CAAC,CACb,MAAM,YAAY,CAAC,CACnB,QAAQ,SAAS,KAAK,UAAU,CAAC,CACtC;AAEF,MAAM,SAAS,OAA4B,SAAyB;CAClE,MAAM,SAAS,CAAC,GAAG,MAAM,IAAI,CAAC;CAE9B,MAAM,WAAW,SACf,OAAO,MAAM,MAAM,MAAM,QAAS,KAAK,UAAU,MAAM,EAAE,WAAW,IAAI,KAAK,KAAK,WAAW,CAAC,EAAG;CAEnG,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC;AACpC;AAGA,MAAM,UAAU,OAAgC,OAAe,UAA2C;CACxG,MAAM,aAAa,MAAM,KAAK;CAE9B,MAAM,SAAS,MACZ,SAAS,SAAS;EACjB,MAAM,OAAO,MAAM,YAAY,GAAG,KAAK,KAAK,GAAG,KAAK,eAAe,IAAI,KAAK,IAAI;EAEhF,OAAO,OAAO,IAAI,CAAC;GAAE;GAAM,OAAO;EAAK,CAAC,IAAI,CAAC;CAC/C,CAAC,CAAC,CACD,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;CAEnC,QAAQ,OAAO,SAAS,IAAI,OAAO,KAAK,QAAQ,IAAI,IAAI,IAAI,MAAA,CAAO,MAAM,GAAG,KAAK;AACnF;AAEA,MAAM,cAAc,KAAK,KAAK,gBAAgB;CAC5C,aACE;CACF,YAAY,OAAO,OAAO,EACxB,OAAO,OAAO,OAAO,SAAS,EAAE,aAAa,uCAAuC,CAAC,EACvF,CAAC;CACD,SAAS,OAAO,MACd,OAAO,OAAO;EACZ,MAAM,OAAO;EACb,aAAa,OAAO,SAAS,OAAO,MAAM;EAC1C,aAAa,OAAO;CACtB,CAAC,CACH;AACF,CAAC;AAED,MAAM,cAAc,KAAK,KAAK,gBAAgB;CAC5C,aAAa;CACb,YAAY,OAAO,OAAO;EACxB,MAAM,OAAO;EACb,OAAO,OAAO,OAAO,OAAO,QAAQ,OAAO,IAAI;CACjD,CAAC;CACD,SAAS,OAAO;CAGhB,SAAS,OAAO;CAChB,cAAc,CAAC,iBAAiB;AAClC,CAAC;AAID,MAAM,mBAGF,SAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCJ,MAAa,QAEX,aACG,QAAQ,KAAK,aAAa,UAAoC,CAAC,CAAC,WAAW,CAAC;;;;;;;;;;;;;;;;AAiBjF,MAAa,SAA6C,YACxD,KAAK,OAAO,CAAC,CAAC,QACZ,OAAO,IAAI,aAAa;CACtB,MAAM,WAAW,OAAO;CACxB,MAAM,cAAc,OAAO,OAAO,SAAS,KAAK;CAChD,MAAMA,oBAAkBC,gBAAyB,WAAW;CAE5D,IAAID,kBAAgB,SAAS,GAAG,OAAO,OAAO,OAAO,IAAIE,qBAA8BF,iBAAe,CAAC;CAEvG,OAAO;EACL,eAAe,EAAE,YACf,OAAO,WACL,OAAO,aAAa,OAAO,CAAC,CAAC,CAAC,KAAK,UAAU;GAC3C,MAAM,KAAK;GACX,aAAa,KAAK,eAAe,IAAI;GACrC,aAAa,OAAO,KAAK,cAAc,IAAI,CAAC;EAC9C,EAAE,CACJ;EACF,cAAc,OAAO,WAAW,WAAW,EAAE,MAAM,SAAS;GAE1D,MAAM,QAAQ,OAAO;GAErB,MAAM,OAAO,SAAS,SAAS,aAAa;IAC1C,IAAI,GAAG,MAAM,OAAO,GAAG;IACvB;IACA,QAAQ;IACR,kBAAkB;GACpB,CAAC;GAED,MAAM,EAAE,SAAS,WAAW,OAAOG,UAAmB,UAAU,IAAI;GAEpE,OAAO,YAAY,YAAY,OAAO,OAAO,KAAK,MAAM,IAAI;EAC9D,CAAC;CACH;AACF,CAAC,CACH"}
@@ -13,7 +13,7 @@ declare const ToolFailure_base: Schema.Class<ToolFailure, Schema.Struct<{
13
13
  *
14
14
  * It holds a message that says what went wrong and, optionally, a `reason` the model can tell
15
15
  * apart, such as `"NotFound"` or `"OverBudget"`. The library's own reasons are PascalCase, like
16
- * `"NeedsDecision"`.
16
+ * `"ToolNotFoundError"`.
17
17
  *
18
18
  * @category errors
19
19
  * @since 0.1.0
@@ -20,7 +20,7 @@ var ToolFailure_exports = /* @__PURE__ */ __exportAll({
20
20
  *
21
21
  * It holds a message that says what went wrong and, optionally, a `reason` the model can tell
22
22
  * apart, such as `"NotFound"` or `"OverBudget"`. The library's own reasons are PascalCase, like
23
- * `"NeedsDecision"`.
23
+ * `"ToolNotFoundError"`.
24
24
  *
25
25
  * @category errors
26
26
  * @since 0.1.0
@@ -1 +1 @@
1
- {"version":3,"file":"ToolFailure.js","names":[],"sources":["../src/ToolFailure.ts"],"sourcesContent":["/**\n * Defines the failure a tool returns to the model.\n *\n * A {@link ToolFailure} carries a message the model reads and adapts to, such as \"No office named\n * Paris\", instead of failing the turn. Tools declare it as their `failure` schema and fail with\n * {@link fail}.\n *\n * @since 0.1.0\n */\nimport * as Effect from \"effect/Effect\"\nimport * as Schema from \"effect/Schema\"\n\n/**\n * Represents a tool's failure, written for the model.\n *\n * It holds a message that says what went wrong and, optionally, a `reason` the model can tell\n * apart, such as `\"NotFound\"` or `\"OverBudget\"`. The library's own reasons are PascalCase, like\n * `\"NeedsDecision\"`.\n *\n * @category errors\n * @since 0.1.0\n */\nexport class ToolFailure extends Schema.Error<ToolFailure>(\"effect-assistant/ToolFailure/ToolFailure\")({\n _tag: Schema.tag(\"ToolFailure\"),\n message: Schema.String,\n reason: Schema.optional(Schema.String)\n}) {}\n\n/**\n * Fails a tool's handler with a message for the model.\n *\n * **Details**\n *\n * The tool must declare `failure: ToolFailure.ToolFailure`. The model reads the failure as the\n * call's result, with its `reason` when one is given.\n *\n * **Example** (Failing a tool call the model can correct)\n *\n * ```ts\n * import { ToolFailure } from \"effect-assistant\"\n * import * as Tool from \"effect/ai/Tool\"\n * import * as Toolkit from \"effect/ai/Toolkit\"\n * import * as Effect from \"effect/Effect\"\n * import * as Schema from \"effect/Schema\"\n *\n * const budgets = new Map([[\"Oslo\", 1200]])\n *\n * const BudgetTools = Toolkit.make(\n * Tool.make(\"office_budget\", {\n * description: \"The budget left for an office.\",\n * parameters: Schema.Struct({ office: Schema.String }),\n * success: Schema.Number,\n * failure: ToolFailure.ToolFailure\n * })\n * )\n *\n * const BudgetToolsLive = BudgetTools.toLayer({\n * office_budget: ({ office }) =>\n * Effect.fromNullishOr(budgets.get(office)).pipe(\n * Effect.catch(() => ToolFailure.fail(`No office named ${office}.`, { reason: \"NotFound\" }))\n * )\n * })\n * ```\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const fail = (\n message: string,\n options?: { readonly reason?: string | undefined }\n): Effect.Effect<never, ToolFailure> =>\n Effect.fail(\n options?.reason === undefined ? new ToolFailure({ message }) : new ToolFailure({ message, reason: options.reason })\n )\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAsBA,IAAa,cAAb,cAAiC,OAAO,MAAmB,0CAA0C,CAAC,CAAC;CACrG,MAAM,OAAO,IAAI,aAAa;CAC9B,SAAS,OAAO;CAChB,QAAQ,OAAO,SAAS,OAAO,MAAM;AACvC,CAAC,CAAC,CAAC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCJ,MAAa,QACX,SACA,YAEA,OAAO,KACL,SAAS,WAAW,KAAA,IAAY,IAAI,YAAY,EAAE,QAAQ,CAAC,IAAI,IAAI,YAAY;CAAE;CAAS,QAAQ,QAAQ;AAAO,CAAC,CACpH"}
1
+ {"version":3,"file":"ToolFailure.js","names":[],"sources":["../src/ToolFailure.ts"],"sourcesContent":["/**\n * Defines the failure a tool returns to the model.\n *\n * A {@link ToolFailure} carries a message the model reads and adapts to, such as \"No office named\n * Paris\", instead of failing the turn. Tools declare it as their `failure` schema and fail with\n * {@link fail}.\n *\n * @since 0.1.0\n */\nimport * as Effect from \"effect/Effect\"\nimport * as Schema from \"effect/Schema\"\n\n/**\n * Represents a tool's failure, written for the model.\n *\n * It holds a message that says what went wrong and, optionally, a `reason` the model can tell\n * apart, such as `\"NotFound\"` or `\"OverBudget\"`. The library's own reasons are PascalCase, like\n * `\"ToolNotFoundError\"`.\n *\n * @category errors\n * @since 0.1.0\n */\nexport class ToolFailure extends Schema.Error<ToolFailure>(\"effect-assistant/ToolFailure/ToolFailure\")({\n _tag: Schema.tag(\"ToolFailure\"),\n message: Schema.String,\n reason: Schema.optional(Schema.String)\n}) {}\n\n/**\n * Fails a tool's handler with a message for the model.\n *\n * **Details**\n *\n * The tool must declare `failure: ToolFailure.ToolFailure`. The model reads the failure as the\n * call's result, with its `reason` when one is given.\n *\n * **Example** (Failing a tool call the model can correct)\n *\n * ```ts\n * import { ToolFailure } from \"effect-assistant\"\n * import * as Tool from \"effect/ai/Tool\"\n * import * as Toolkit from \"effect/ai/Toolkit\"\n * import * as Effect from \"effect/Effect\"\n * import * as Schema from \"effect/Schema\"\n *\n * const budgets = new Map([[\"Oslo\", 1200]])\n *\n * const BudgetTools = Toolkit.make(\n * Tool.make(\"office_budget\", {\n * description: \"The budget left for an office.\",\n * parameters: Schema.Struct({ office: Schema.String }),\n * success: Schema.Number,\n * failure: ToolFailure.ToolFailure\n * })\n * )\n *\n * const BudgetToolsLive = BudgetTools.toLayer({\n * office_budget: ({ office }) =>\n * Effect.fromNullishOr(budgets.get(office)).pipe(\n * Effect.catch(() => ToolFailure.fail(`No office named ${office}.`, { reason: \"NotFound\" }))\n * )\n * })\n * ```\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const fail = (\n message: string,\n options?: { readonly reason?: string | undefined }\n): Effect.Effect<never, ToolFailure> =>\n Effect.fail(\n options?.reason === undefined ? new ToolFailure({ message }) : new ToolFailure({ message, reason: options.reason })\n )\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAsBA,IAAa,cAAb,cAAiC,OAAO,MAAmB,0CAA0C,CAAC,CAAC;CACrG,MAAM,OAAO,IAAI,aAAa;CAC9B,SAAS,OAAO;CAChB,QAAQ,OAAO,SAAS,OAAO,MAAM;AACvC,CAAC,CAAC,CAAC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCJ,MAAa,QACX,SACA,YAEA,OAAO,KACL,SAAS,WAAW,KAAA,IAAY,IAAI,YAAY,EAAE,QAAQ,CAAC,IAAI,IAAI,YAAY;CAAE;CAAS,QAAQ,QAAQ;AAAO,CAAC,CACpH"}
package/dist/index.d.ts CHANGED
@@ -18,9 +18,7 @@ import { Assistant_d_exports } from "./Assistant.js";
18
18
  import { AssistantActivity_d_exports } from "./AssistantActivity.js";
19
19
  import { Compaction_d_exports } from "./Compaction.js";
20
20
  import { InputId_d_exports } from "./InputId.js";
21
- import { Decision_d_exports } from "./Decision.js";
22
- import { DecisionId_d_exports } from "./DecisionId.js";
23
21
  import { ToolFailure_d_exports } from "./ToolFailure.js";
24
22
  import { Memory_d_exports } from "./Memory.js";
25
23
  import { ToolDiscovery_d_exports } from "./ToolDiscovery.js";
26
- export { AppEvent_d_exports as AppEvent, Assistant_d_exports as Assistant, AssistantActivity_d_exports as AssistantActivity, AssistantEngine_d_exports as AssistantEngine, AssistantEvent_d_exports as AssistantEvent, AssistantLoop_d_exports as AssistantLoop, AssistantToolCall_d_exports as AssistantToolCall, Block_d_exports as Block, BlockKit_d_exports as BlockKit, ChatId_d_exports as ChatId, ChatLog_d_exports as ChatLog, ChatView_d_exports as ChatView, Chats_d_exports as Chats, Compaction_d_exports as Compaction, CurrentChat_d_exports as CurrentChat, Decision_d_exports as Decision, DecisionId_d_exports as DecisionId, InputId_d_exports as InputId, Instructions_d_exports as Instructions, Memory_d_exports as Memory, RunId_d_exports as RunId, Skill_d_exports as Skill, Specialist_d_exports as Specialist, ToolDiscovery_d_exports as ToolDiscovery, ToolFailure_d_exports as ToolFailure };
24
+ export { AppEvent_d_exports as AppEvent, Assistant_d_exports as Assistant, AssistantActivity_d_exports as AssistantActivity, AssistantEngine_d_exports as AssistantEngine, AssistantEvent_d_exports as AssistantEvent, AssistantLoop_d_exports as AssistantLoop, AssistantToolCall_d_exports as AssistantToolCall, Block_d_exports as Block, BlockKit_d_exports as BlockKit, ChatId_d_exports as ChatId, ChatLog_d_exports as ChatLog, ChatView_d_exports as ChatView, Chats_d_exports as Chats, Compaction_d_exports as Compaction, CurrentChat_d_exports as CurrentChat, InputId_d_exports as InputId, Instructions_d_exports as Instructions, Memory_d_exports as Memory, RunId_d_exports as RunId, Skill_d_exports as Skill, Specialist_d_exports as Specialist, ToolDiscovery_d_exports as ToolDiscovery, ToolFailure_d_exports as ToolFailure };
package/dist/index.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import { AppEvent_exports } from "./AppEvent.js";
2
2
  import { ChatId_exports } from "./ChatId.js";
3
- import { DecisionId_exports } from "./DecisionId.js";
4
3
  import { InputId_exports } from "./InputId.js";
5
4
  import { RunId_exports } from "./RunId.js";
6
5
  import { AssistantEvent_exports } from "./AssistantEvent.js";
@@ -8,9 +7,6 @@ import { ChatLog_exports } from "./ChatLog.js";
8
7
  import { CurrentChat_exports } from "./CurrentChat.js";
9
8
  import { Chats_exports } from "./Chats.js";
10
9
  import { Specialist_exports } from "./Specialist.js";
11
- import { Block_exports } from "./Block.js";
12
- import { Decision_exports } from "./Decision.js";
13
- import { BlockKit_exports } from "./BlockKit.js";
14
10
  import { AssistantToolCall_exports } from "./AssistantToolCall.js";
15
11
  import { ToolFailure_exports } from "./ToolFailure.js";
16
12
  import { Skill_exports } from "./Skill.js";
@@ -19,8 +15,10 @@ import { AssistantEngine_exports } from "./AssistantEngine.js";
19
15
  import { Assistant_exports } from "./Assistant.js";
20
16
  import { AssistantActivity_exports } from "./AssistantActivity.js";
21
17
  import { AssistantLoop_exports } from "./AssistantLoop.js";
18
+ import { Block_exports } from "./Block.js";
19
+ import { BlockKit_exports } from "./BlockKit.js";
22
20
  import { ChatView_exports } from "./ChatView.js";
23
21
  import { Compaction_exports } from "./Compaction.js";
24
22
  import { Memory_exports } from "./Memory.js";
25
23
  import { ToolDiscovery_exports } from "./ToolDiscovery.js";
26
- export { AppEvent_exports as AppEvent, 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, ChatView_exports as ChatView, Chats_exports as Chats, Compaction_exports as Compaction, CurrentChat_exports as CurrentChat, Decision_exports as Decision, DecisionId_exports as DecisionId, InputId_exports as InputId, Instructions_exports as Instructions, Memory_exports as Memory, RunId_exports as RunId, Skill_exports as Skill, Specialist_exports as Specialist, ToolDiscovery_exports as ToolDiscovery, ToolFailure_exports as ToolFailure };
24
+ export { AppEvent_exports as AppEvent, 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, ChatView_exports as ChatView, 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, ToolDiscovery_exports as ToolDiscovery, ToolFailure_exports as ToolFailure };
@@ -1,9 +1,8 @@
1
- import { ChatDeleted, DecisionResolved, isInput, isLibraryEvent } from "../AssistantEvent.js";
1
+ import { ChatDeleted, isInput, isLibraryEvent } from "../AssistantEvent.js";
2
2
  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 { TurnDecisions, approvedCalls, unresolved, verdict } from "./decisions.js";
7
6
  import { decodeEntries, encode, isRegistered } from "./events.js";
8
7
  import { needsTurn } from "./history.js";
9
8
  import { makeRunner } from "./specialists.js";
@@ -13,7 +12,6 @@ import * as Context from "effect/Context";
13
12
  import * as Effect from "effect/Effect";
14
13
  import * as Stream from "effect/Stream";
15
14
  import * as Exit from "effect/Exit";
16
- import * as Option from "effect/Option";
17
15
  import * as Scope from "effect/Scope";
18
16
  import * as Ref from "effect/Ref";
19
17
  import * as Cause from "effect/Cause";
@@ -60,7 +58,6 @@ const make = Effect.fnUntraced(function* (options) {
60
58
  if (initial.some(isChatDeleted)) return deletedChat(initial);
61
59
  const channel = yield* makeChannelPubSub;
62
60
  const writes = yield* Semaphore.make(1);
63
- const storing = yield* Semaphore.make(1);
64
61
  const turnsLock = yield* Semaphore.make(1);
65
62
  const holding = yield* makeHolding(options.keepAlive);
66
63
  const deleted = yield* Deferred.make();
@@ -93,36 +90,17 @@ const make = Effect.fnUntraced(function* (options) {
93
90
  entries: Effect.map(chat.entries, (all) => decodeEntries(app, all)),
94
91
  append: (...events) => Effect.asVoid(Effect.flatMap(Effect.forEach(events, toStored), chat.append))
95
92
  };
96
- const resolve = Effect.fnUntraced(function* (decided, answer) {
97
- yield* chat.append(decided.map(({ request, outcome }) => DecisionResolved.make({
98
- id: request.id,
99
- outcome,
100
- answer
101
- })));
102
- });
103
93
  const checkAvailable = chats.get(chatId).pipe(Effect.as(true), Effect.catchTag("GetChatError", (unavailable) => unavailable.reason === "Deleted" ? Effect.as(Effect.suspend(() => remove), false) : Effect.succeed(false)));
104
94
  const store = Effect.fnUntraced(function* (input) {
105
95
  if (yield* isDeleted) return yield* refused(input, "is deleted");
106
96
  if (!(yield* checkAvailable)) return yield* refused(input, "is unavailable");
107
- const verdict$2 = yield* Semaphore.withPermit(writes)(Effect.gen(function* () {
108
- if (yield* isDeleted) {
109
- yield* refused(input, "is deleted");
110
- return Option.none();
111
- }
112
- const stored = yield* Ref.get(entries);
97
+ yield* Semaphore.withPermit(writes)(Effect.gen(function* () {
98
+ if (yield* isDeleted) return yield* refused(input, "is deleted");
113
99
  const id = idOf(input);
114
- if (id !== void 0 && stored.some(({ event }) => idOf(event) === id)) return Option.none();
115
- const verdict$1 = verdict(stored, input);
116
- if (Predicate.isTagged(verdict$1, "Refuse")) {
117
- yield* Effect.logWarning(`assistant chat refused ${input._tag}: ${verdict$1.reason}`);
118
- return Option.none();
119
- }
100
+ if (id !== void 0 && (yield* Ref.get(entries)).some(({ event }) => idOf(event) === id)) return;
120
101
  yield* write([input]);
121
- return Option.some(verdict$1);
122
102
  }));
123
- if (Option.isSome(verdict$2) && Predicate.isTagged(verdict$2.value, "Decide")) yield* resolve(verdict$2.value.decided, verdict$2.value.answer);
124
- }, Semaphore.withPermit(storing));
125
- yield* Effect.forEach(unresolved(yield* Ref.get(entries)), (decided) => resolve([decided], decided.answer), { discard: true });
103
+ });
126
104
  const handler = yield* inWorking(Effect.provideService(registration.open(handle), CurrentChat, handle));
127
105
  const specialists = yield* Effect.forEach(registration.specialists, Effect.fnUntraced(function* ({ specialist, built }) {
128
106
  return {
@@ -156,10 +134,7 @@ const make = Effect.fnUntraced(function* (options) {
156
134
  yield* inBackground(drain);
157
135
  })
158
136
  }));
159
- const services = Context.make(CurrentChat, handle).pipe(Context.add(Runner, runner), Context.add(TurnDecisions, Option.some({ approved: Effect.gen(function* () {
160
- const all = yield* chat.entries;
161
- return approvedCalls(all);
162
- }) })), Context.merge(registration.model));
137
+ const services = Context.make(CurrentChat, handle).pipe(Context.add(Runner, runner), Context.merge(registration.model));
163
138
  const remove = Effect.gen(function* () {
164
139
  if (!(yield* Semaphore.withPermit(writes)(Effect.gen(function* () {
165
140
  if (yield* isDeleted) return false;
@@ -1 +1 @@
1
- {"version":3,"file":"chat.js","names":["AssistantEvent.isLibraryEvent","AssistantEvent.isInput","Chats.Chats","AppEvents.isRegistered","AppEvents.encode","AppEvents.decodeEntries","verdict","Decisions.verdict","Decisions.unresolved","History.needsTurn","Turns.run","Specialists.makeRunner","SpecialistService.Runner","Decisions.TurnDecisions","Decisions.approvedCalls"],"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 Option from \"effect/Option\"\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 type { InputId } from \"../InputId.ts\"\nimport * as Chats from \"../Chats.ts\"\nimport * as SpecialistService from \"../Specialist.ts\"\nimport * as Decisions from \"./decisions.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 // Inputs are stored one at a time with the decisions they make: an answer checks the decisions the last one made.\n const storing = 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 // An approved call runs at the turn's next step.\n const resolve = Effect.fnUntraced(function* (decided: ReadonlyArray<Decisions.Decided>, answer: InputId) {\n yield* chat.append(\n decided.map(({ request, outcome }) => AssistantEvent.DecisionResolved.make({ id: request.id, outcome, answer }))\n )\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 // Under `storing`: an answer's decisions are resolved before the next input is stored.\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 const verdict = yield* Semaphore.withPermit(writes)(\n Effect.gen(function* () {\n if (yield* isDeleted) {\n yield* refused(input, \"is deleted\")\n\n return Option.none()\n }\n\n const stored = yield* Ref.get(entries)\n const id = idOf(input)\n\n if (id !== undefined && stored.some(({ event }) => idOf(event) === id)) return Option.none()\n\n const verdict = Decisions.verdict(stored, input)\n\n if (Predicate.isTagged(verdict, \"Refuse\")) {\n yield* Effect.logWarning(`assistant chat refused ${input._tag}: ${verdict.reason}`)\n\n return Option.none()\n }\n\n yield* write([input])\n\n return Option.some(verdict)\n })\n )\n\n if (Option.isSome(verdict) && Predicate.isTagged(verdict.value, \"Decide\")) {\n yield* resolve(verdict.value.decided, verdict.value.answer)\n }\n }, Semaphore.withPermit(storing))\n\n // A restart between storing an answer and resolving it: resolve it now, before anything reads the chat.\n yield* Effect.forEach(\n Decisions.unresolved(yield* Ref.get(entries)),\n (decided) => resolve([decided], decided.answer),\n { discard: true }\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.add(\n Decisions.TurnDecisions,\n Option.some({\n approved: Effect.gen(function* () {\n const all = yield* chat.entries\n\n return Decisions.approvedCalls(all)\n })\n })\n ),\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","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,6 +1,5 @@
1
1
  import { fromKey } from "./uuid.js";
2
2
  import { isInput as isInput$1, isLibraryEvent, isTurnEnd, isUserInput, isWaking, textWithAttachments } from "../AssistantEvent.js";
3
- import { Answer } from "../Decision.js";
4
3
  import { toJson, toJsonText } from "./schema.js";
5
4
  import * as Predicate from "effect/Predicate";
6
5
  import * as Schema from "effect/Schema";
@@ -53,39 +52,12 @@ const finishedText = (entries, index, finished) => {
53
52
  return `<specialist name="${finished.specialist}" run="${finished.runId}" status="${finished.status}">\n${lines.join("\n")}\n</specialist>`;
54
53
  };
55
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>`;
56
- /** @internal */
57
- const parkedResult = (request, origin) => Prompt.toolMessage({ content: [Prompt.toolResultPart({
58
- id: origin.callId,
59
- name: origin.tool,
60
- isFailure: false,
61
- result: {
62
- status: "waiting_for_decision",
63
- decisionId: request.id,
64
- next: "Not run: it runs only if the user approves it. Send them a message with a decision block for this decisionId that says what they approve (the call's key facts). Their answer runs it, or declines it."
65
- },
66
- providerExecuted: false
67
- })] });
68
- const whatDecided = (request, outcome) => outcome === "approved" ? `approved: ${request.origin.tool} runs now, and its result follows.` : `declined: ${request.origin.tool} didn't run, and won't.`;
69
- const decodeDecisionAnswer = Schema.decodeUnknownOption(Answer);
70
- const decisionAnswerText = (answer, requests) => Option.match(decodeDecisionAnswer(answer.value), {
71
- onNone: () => answerText(answer),
72
- onSome: ({ decisions }) => {
73
- const lines = Object.entries(decisions).map(([id, outcome]) => {
74
- const request = requests.get(id);
75
- return `- ${id}: ${request === void 0 ? outcome : whatDecided(request, outcome)}`;
76
- });
77
- return `<answer message="${answer.messageId}" kind="${answer.kind}">\nThe user decided with the block on your message ${answer.messageId}:\n${lines.join("\n")}\n</answer>`;
78
- }
79
- });
80
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>`;
81
- const approvedResultText = (request, tool, result) => `<decision id="${request.id}" tool="${tool}" outcome="approved">\nThe user approved it, and ${tool} ran: ${result.outcome}.\n${toJsonText(result.result)}\n</decision>`;
82
56
  /** @internal */
83
57
  const fromEntries = (entries, readApp) => {
84
58
  const history = [];
85
59
  const held = [];
86
60
  const open = /* @__PURE__ */ new Set();
87
- const requests = /* @__PURE__ */ new Map();
88
- const parked = /* @__PURE__ */ new Map();
89
61
  const called = /* @__PURE__ */ new Set();
90
62
  const flushIfClosed = () => {
91
63
  if (open.size === 0) history.push(...held.splice(0));
@@ -107,7 +79,7 @@ const fromEntries = (entries, readApp) => {
107
79
  }
108
80
  Match.value(event).pipe(Match.tagsExhaustive({
109
81
  UserMessage: (message) => input(index, userText(textWithAttachments(message.text, message.attachments))),
110
- BlockAnswer: (answer) => input(index, userText(answer.kind === "decision" ? decisionAnswerText(answer, requests) : answerText(answer))),
82
+ BlockAnswer: (answer) => input(index, userText(answerText(answer))),
111
83
  ModelOutput: (output) => {
112
84
  history.push(...Prompt.fromResponseParts(output.parts).content);
113
85
  for (const part of output.parts) if (part.type === "tool-call") {
@@ -117,21 +89,11 @@ const fromEntries = (entries, readApp) => {
117
89
  flushIfClosed();
118
90
  },
119
91
  ToolResult: (result) => {
120
- const request = parked.get(result.callId);
121
- if (request !== void 0) return input(index, userText(approvedResultText(request, result.tool, result)));
122
92
  if (!called.has(result.callId)) return input(index, userText(resultText(result)));
123
93
  history.push(toolMessage(result));
124
94
  open.delete(result.callId);
125
95
  flushIfClosed();
126
96
  },
127
- DecisionRequested: (request) => {
128
- requests.set(request.id, request);
129
- parked.set(request.origin.callId, request);
130
- history.push(parkedResult(request, request.origin));
131
- open.delete(request.origin.callId);
132
- flushIfClosed();
133
- },
134
- DecisionResolved: () => void 0,
135
97
  AssistantMessage: () => void 0,
136
98
  ToolStarted: () => void 0,
137
99
  TurnStarted: () => void 0,
@@ -180,6 +142,6 @@ const owesDelivery = (entries, throughSeq) => {
180
142
  /** @internal */
181
143
  const unreadAfter = (entries, throughSeq, wakes) => entries.some(({ event, seq }) => seq > throughSeq && isInput(event, wakes));
182
144
  //#endregion
183
- export { fromEntries, handledThrough, isInput, mayHaveRunOutput, needsTurn, notRunOutput, owesDelivery, owesReply, parkedResult, toolMessage, unansweredCalls, unhandledInputs, unreadAfter, userText };
145
+ export { fromEntries, handledThrough, isInput, mayHaveRunOutput, needsTurn, notRunOutput, owesDelivery, owesReply, toolMessage, unansweredCalls, unhandledInputs, unreadAfter, userText };
184
146
 
185
147
  //# sourceMappingURL=history.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"history.js","names":["AssistantEvent.isLibraryEvent","Decision.Answer","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 * as Decision from \"../Decision.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\n/** @internal */\nexport const parkedResult = (\n request: AssistantEvent.DecisionRequested,\n origin: AssistantEvent.CallOrigin\n): Prompt.ToolMessage =>\n Prompt.toolMessage({\n content: [\n Prompt.toolResultPart({\n id: origin.callId,\n name: origin.tool,\n isFailure: false,\n result: {\n status: \"waiting_for_decision\",\n decisionId: request.id,\n next: \"Not run: it runs only if the user approves it. Send them a message with a decision block for this decisionId that says what they approve (the call's key facts). Their answer runs it, or declines it.\"\n },\n providerExecuted: false\n })\n ]\n })\n\nconst whatDecided = (request: AssistantEvent.DecisionRequested, outcome: AssistantEvent.DecisionOutcome) =>\n outcome === \"approved\"\n ? `approved: ${request.origin.tool} runs now, and its result follows.`\n : `declined: ${request.origin.tool} didn't run, and won't.`\n\nconst decodeDecisionAnswer = Schema.decodeUnknownOption(Decision.Answer)\n\nconst decisionAnswerText = (\n answer: AssistantEvent.BlockAnswer,\n requests: ReadonlyMap<string, AssistantEvent.DecisionRequested>\n) =>\n Option.match(decodeDecisionAnswer(answer.value), {\n onNone: () => answerText(answer),\n onSome: ({ decisions }) => {\n const lines = Object.entries(decisions).map(([id, outcome]) => {\n const request = requests.get(id)\n\n return `- ${id}: ${request === undefined ? outcome : whatDecided(request, outcome)}`\n })\n\n return `<answer message=\"${answer.messageId}\" kind=\"${answer.kind}\">\\nThe user decided with the block on your message ${answer.messageId}:\\n${lines.join(\"\\n\")}\\n</answer>`\n }\n })\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 approvedResultText = (\n request: AssistantEvent.DecisionRequested,\n tool: string,\n result: AssistantEvent.ToolResult\n) =>\n `<decision id=\"${request.id}\" tool=\"${tool}\" outcome=\"approved\">\\nThe user approved it, and ${tool} ran: ${result.outcome}.\\n${toJsonText(result.result)}\\n</decision>`\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 requests = new Map<string, AssistantEvent.DecisionRequested>()\n // Parked calls by call id: their result came with the parking, so a later one reads as the decision's.\n const parked = new Map<string, AssistantEvent.DecisionRequested>()\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) =>\n input(\n index,\n userText(answer.kind === Decision.kind ? decisionAnswerText(answer, requests) : answerText(answer))\n ),\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 const request = parked.get(result.callId)\n\n if (request !== undefined) return input(index, userText(approvedResultText(request, result.tool, result)))\n\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. An approved call's result can come long after the call was parked.\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 DecisionRequested: (request) => {\n requests.set(request.id, request)\n parked.set(request.origin.callId, request)\n history.push(parkedResult(request, request.origin))\n open.delete(request.origin.callId)\n flushIfClosed()\n },\n DecisionResolved: () => undefined,\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.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,4 +1,4 @@
1
- import { AssistantEvent_d_exports, DecisionRequested, ModelOutput, ToolResult, ToolStarted } from "../AssistantEvent.js";
1
+ import { AssistantEvent_d_exports, ModelOutput, ToolResult, ToolStarted } from "../AssistantEvent.js";
2
2
  import { Instructions_d_exports, Resolved } from "../Instructions.js";
3
3
  import { Tools, toolkit_d_exports } from "./toolkit.js";
4
4
  import * as Context from "effect/Context";
@@ -13,7 +13,7 @@ declare namespace loop_d_exports {
13
13
  export { ChatHistory, Event, Options, Step, StepOptions, StepResult, TurnTools, loop, next, run, step, stop, value };
14
14
  }
15
15
  /** @internal */
16
- export type Event = ModelOutput | ToolStarted | ToolResult | DecisionRequested;
16
+ export type Event = ModelOutput | ToolStarted | ToolResult;
17
17
  /** @internal */
18
18
  export declare const TurnTools: Context.Reference<Option.Option<Tools<never>>>;
19
19
  /** @internal */
@@ -1 +1 @@
1
- {"version":3,"file":"loop.d.ts","names":[],"sources":["../../src/internal/loop.ts"],"mappings":";;;;;;;;;;;;;;;YAyBY,QACR,cACA,cACA,aACA;;qBAKS,WAAS,QAAA,UAAA,OAAA,OAAA;;iBAKL;WACN,UAAU,cAAc,OAAO;WAC/B;;;iBAUM,YAAY;WAClB,uBAAuB;WACvB,UAAU,MAAe;WACzB,SAAS,OAAO,OAAO,cAAc,OAAO,WAAW;WACvD,aAAa,cAAc;;;iBAIrB,QAAQ,mBAAmB,YAAY;WAC7C;;;iBAKM;;WAEN,QAAQ;;;;;WAKR;;;;WAIA;;;YAmHC,KAAK,GAAG,KAAK,KAAK;EAC5B;aAAkB,OAAO;;EACzB;aAAiB,OAAO;;EACxB,MAAM;;;qBAUK,QAAS,GAAC,OAAS,MAAI,KAAK;;qBAG5B,OAAQ,GAAC,OAAS,MAAI,OAAO,OAAO,YAAY;;qBAIhD,OAAQ,gBAAc,OAAO,OAAO,YAAY;;qBAOhD,OAAQ,GAAG,GAAG,GAAG,GAAC,SACpB,GAAC,OACH,OAAO,MAAM,OAAO,OAAO,KAAK,GAAG,IAAI,GAAG,OAChD,OAAO,OAAO,GAAG,GAAG;;qBAmBV,OAAQ,GAAG,GAAG,YAAY,YAAU,SACtC,YAAY,IAAE,OAChB,QAAQ,eAAe,OAAO,OAAO,YAAY,IAAI,IAAI,QAC/D,OAAO,OAAO,KAAK,OAAO,IAAI,QAAQ,UAAU,IAAI,cAAc,gBAAgB,IAAI;;qBA4E5E,MAAO,WAAS,SAClB,QAAQ,OAChB,OAAO,OAAO,OAAO,QAAQ,SAAS,cAAc,gBAAgB"}
1
+ {"version":3,"file":"loop.d.ts","names":[],"sources":["../../src/internal/loop.ts"],"mappings":";;;;;;;;;;;;;;;YAuBY,QAAQ,cAA6B,cAA6B;;qBAKjE,WAAS,QAAA,UAAA,OAAA,OAAA;;iBAKL;WACN,UAAU,cAAc,OAAO;WAC/B;;;iBAUM,YAAY;WAClB,uBAAuB;WACvB,UAAU,MAAe;WACzB,SAAS,OAAO,OAAO,cAAc,OAAO,WAAW;WACvD,aAAa,cAAc;;;iBAIrB,QAAQ,mBAAmB,YAAY;WAC7C;;;iBAKM;;WAEN,QAAQ;;;;;WAKR;;;;WAIA;;;YAmEC,KAAK,GAAG,KAAK,KAAK;EAC5B;aAAkB,OAAO;;EACzB;aAAiB,OAAO;;EACxB,MAAM;;;qBAUK,QAAS,GAAC,OAAS,MAAI,KAAK;;qBAG5B,OAAQ,GAAC,OAAS,MAAI,OAAO,OAAO,YAAY;;qBAIhD,OAAQ,gBAAc,OAAO,OAAO,YAAY;;qBAOhD,OAAQ,GAAG,GAAG,GAAG,GAAC,SACpB,GAAC,OACH,OAAO,MAAM,OAAO,OAAO,KAAK,GAAG,IAAI,GAAG,OAChD,OAAO,OAAO,GAAG,GAAG;;qBAmBV,OAAQ,GAAG,GAAG,YAAY,YAAU,SACtC,YAAY,IAAE,OAChB,QAAQ,eAAe,OAAO,OAAO,YAAY,IAAI,IAAI,QAC/D,OAAO,OAAO,KAAK,OAAO,IAAI,QAAQ,UAAU,IAAI,cAAc,gBAAgB,IAAI;;qBA4D5E,MAAO,WAAS,SAClB,QAAQ,OAChB,OAAO,OAAO,OAAO,QAAQ,SAAS,cAAc,gBAAgB"}