effect-assistant 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +22 -2
  3. package/dist/AppEvent.d.ts +111 -0
  4. package/dist/AppEvent.d.ts.map +1 -0
  5. package/dist/AppEvent.js +71 -0
  6. package/dist/AppEvent.js.map +1 -0
  7. package/dist/Assistant.d.ts +403 -0
  8. package/dist/Assistant.d.ts.map +1 -0
  9. package/dist/Assistant.js +126 -0
  10. package/dist/Assistant.js.map +1 -0
  11. package/dist/AssistantActivity.d.ts +85 -0
  12. package/dist/AssistantActivity.d.ts.map +1 -0
  13. package/dist/AssistantActivity.js +105 -0
  14. package/dist/AssistantActivity.js.map +1 -0
  15. package/dist/AssistantEngine.d.ts +116 -0
  16. package/dist/AssistantEngine.d.ts.map +1 -0
  17. package/dist/AssistantEngine.js +255 -0
  18. package/dist/AssistantEngine.js.map +1 -0
  19. package/dist/AssistantEvent.d.ts +614 -0
  20. package/dist/AssistantEvent.d.ts.map +1 -0
  21. package/dist/AssistantEvent.js +523 -0
  22. package/dist/AssistantEvent.js.map +1 -0
  23. package/dist/AssistantLoop.d.ts +171 -0
  24. package/dist/AssistantLoop.d.ts.map +1 -0
  25. package/dist/AssistantLoop.js +108 -0
  26. package/dist/AssistantLoop.js.map +1 -0
  27. package/dist/AssistantToolCall.d.ts +32 -0
  28. package/dist/AssistantToolCall.d.ts.map +1 -0
  29. package/dist/AssistantToolCall.js +37 -0
  30. package/dist/AssistantToolCall.js.map +1 -0
  31. package/dist/Block.d.ts +69 -0
  32. package/dist/Block.d.ts.map +1 -0
  33. package/dist/Block.js +39 -0
  34. package/dist/Block.js.map +1 -0
  35. package/dist/BlockKit.d.ts +184 -0
  36. package/dist/BlockKit.d.ts.map +1 -0
  37. package/dist/BlockKit.js +151 -0
  38. package/dist/BlockKit.js.map +1 -0
  39. package/dist/ChatId.d.ts +31 -0
  40. package/dist/ChatId.d.ts.map +1 -0
  41. package/dist/ChatId.js +36 -0
  42. package/dist/ChatId.js.map +1 -0
  43. package/dist/ChatLog.d.ts +109 -0
  44. package/dist/ChatLog.d.ts.map +1 -0
  45. package/dist/ChatLog.js +177 -0
  46. package/dist/ChatLog.js.map +1 -0
  47. package/dist/ChatView.d.ts +459 -0
  48. package/dist/ChatView.d.ts.map +1 -0
  49. package/dist/ChatView.js +496 -0
  50. package/dist/ChatView.js.map +1 -0
  51. package/dist/Chats.d.ts +110 -0
  52. package/dist/Chats.d.ts.map +1 -0
  53. package/dist/Chats.js +98 -0
  54. package/dist/Chats.js.map +1 -0
  55. package/dist/Compaction.d.ts +105 -0
  56. package/dist/Compaction.d.ts.map +1 -0
  57. package/dist/Compaction.js +133 -0
  58. package/dist/Compaction.js.map +1 -0
  59. package/dist/CurrentChat.d.ts +40 -0
  60. package/dist/CurrentChat.d.ts.map +1 -0
  61. package/dist/CurrentChat.js +33 -0
  62. package/dist/CurrentChat.js.map +1 -0
  63. package/dist/Decision.d.ts +145 -0
  64. package/dist/Decision.d.ts.map +1 -0
  65. package/dist/Decision.js +178 -0
  66. package/dist/Decision.js.map +1 -0
  67. package/dist/DecisionId.d.ts +37 -0
  68. package/dist/DecisionId.d.ts.map +1 -0
  69. package/dist/DecisionId.js +44 -0
  70. package/dist/DecisionId.js.map +1 -0
  71. package/dist/InputId.d.ts +38 -0
  72. package/dist/InputId.d.ts.map +1 -0
  73. package/dist/InputId.js +43 -0
  74. package/dist/InputId.js.map +1 -0
  75. package/dist/Instructions.d.ts +56 -0
  76. package/dist/Instructions.d.ts.map +1 -0
  77. package/dist/Instructions.js +50 -0
  78. package/dist/Instructions.js.map +1 -0
  79. package/dist/Memory.d.ts +190 -0
  80. package/dist/Memory.d.ts.map +1 -0
  81. package/dist/Memory.js +216 -0
  82. package/dist/Memory.js.map +1 -0
  83. package/dist/RunId.d.ts +41 -0
  84. package/dist/RunId.d.ts.map +1 -0
  85. package/dist/RunId.js +46 -0
  86. package/dist/RunId.js.map +1 -0
  87. package/dist/Skill.d.ts +72 -0
  88. package/dist/Skill.d.ts.map +1 -0
  89. package/dist/Skill.js +58 -0
  90. package/dist/Skill.js.map +1 -0
  91. package/dist/Specialist.d.ts +267 -0
  92. package/dist/Specialist.d.ts.map +1 -0
  93. package/dist/Specialist.js +119 -0
  94. package/dist/Specialist.js.map +1 -0
  95. package/dist/ToolDiscovery.d.ts +108 -0
  96. package/dist/ToolDiscovery.d.ts.map +1 -0
  97. package/dist/ToolDiscovery.js +139 -0
  98. package/dist/ToolDiscovery.js.map +1 -0
  99. package/dist/ToolFailure.d.ts +66 -0
  100. package/dist/ToolFailure.d.ts.map +1 -0
  101. package/dist/ToolFailure.js +79 -0
  102. package/dist/ToolFailure.js.map +1 -0
  103. package/dist/_virtual/_rolldown/runtime.js +13 -0
  104. package/dist/index.d.ts +26 -0
  105. package/dist/index.js +26 -0
  106. package/dist/internal/chat.js +202 -0
  107. package/dist/internal/chat.js.map +1 -0
  108. package/dist/internal/decisions.js +66 -0
  109. package/dist/internal/decisions.js.map +1 -0
  110. package/dist/internal/events.d.ts +46 -0
  111. package/dist/internal/events.d.ts.map +1 -0
  112. package/dist/internal/events.js +65 -0
  113. package/dist/internal/events.js.map +1 -0
  114. package/dist/internal/history.js +185 -0
  115. package/dist/internal/history.js.map +1 -0
  116. package/dist/internal/instructions.js +11 -0
  117. package/dist/internal/instructions.js.map +1 -0
  118. package/dist/internal/loop.d.ts +73 -0
  119. package/dist/internal/loop.d.ts.map +1 -0
  120. package/dist/internal/loop.js +166 -0
  121. package/dist/internal/loop.js.map +1 -0
  122. package/dist/internal/schema.js +14 -0
  123. package/dist/internal/schema.js.map +1 -0
  124. package/dist/internal/skills.js +28 -0
  125. package/dist/internal/skills.js.map +1 -0
  126. package/dist/internal/specialists.js +273 -0
  127. package/dist/internal/specialists.js.map +1 -0
  128. package/dist/internal/telemetry.js +85 -0
  129. package/dist/internal/telemetry.js.map +1 -0
  130. package/dist/internal/toolkit.d.ts +76 -0
  131. package/dist/internal/toolkit.d.ts.map +1 -0
  132. package/dist/internal/toolkit.js +129 -0
  133. package/dist/internal/toolkit.js.map +1 -0
  134. package/dist/internal/tools.js +132 -0
  135. package/dist/internal/tools.js.map +1 -0
  136. package/dist/internal/turns.d.ts +64 -0
  137. package/dist/internal/turns.d.ts.map +1 -0
  138. package/dist/internal/turns.js +221 -0
  139. package/dist/internal/turns.js.map +1 -0
  140. package/dist/internal/uuid.js +45 -0
  141. package/dist/internal/uuid.js.map +1 -0
  142. package/dist/internal/writer.d.ts +12 -0
  143. package/dist/internal/writer.d.ts.map +1 -0
  144. package/package.json +51 -3
  145. package/src/AppEvent.ts +134 -0
  146. package/src/Assistant.ts +589 -0
  147. package/src/AssistantActivity.ts +150 -0
  148. package/src/AssistantEngine.ts +343 -0
  149. package/src/AssistantEvent.ts +635 -0
  150. package/src/AssistantLoop.ts +207 -0
  151. package/src/AssistantToolCall.ts +40 -0
  152. package/src/Block.ts +84 -0
  153. package/src/BlockKit.ts +311 -0
  154. package/src/ChatId.ts +36 -0
  155. package/src/ChatLog.ts +247 -0
  156. package/src/ChatView.ts +658 -0
  157. package/src/Chats.ts +122 -0
  158. package/src/Compaction.ts +224 -0
  159. package/src/CurrentChat.ts +47 -0
  160. package/src/Decision.ts +241 -0
  161. package/src/DecisionId.ts +46 -0
  162. package/src/InputId.ts +43 -0
  163. package/src/Instructions.ts +72 -0
  164. package/src/Memory.ts +276 -0
  165. package/src/RunId.ts +46 -0
  166. package/src/Skill.ts +91 -0
  167. package/src/Specialist.ts +355 -0
  168. package/src/ToolDiscovery.ts +187 -0
  169. package/src/ToolFailure.ts +74 -0
  170. package/src/index.ts +130 -0
  171. package/src/internal/chat.ts +372 -0
  172. package/src/internal/decisions.ts +131 -0
  173. package/src/internal/events.ts +100 -0
  174. package/src/internal/history.ts +366 -0
  175. package/src/internal/instructions.ts +9 -0
  176. package/src/internal/loop.ts +344 -0
  177. package/src/internal/schema.ts +25 -0
  178. package/src/internal/skills.ts +37 -0
  179. package/src/internal/specialists.ts +414 -0
  180. package/src/internal/telemetry.ts +125 -0
  181. package/src/internal/toolkit.ts +231 -0
  182. package/src/internal/tools.ts +210 -0
  183. package/src/internal/turns.ts +429 -0
  184. package/src/internal/uuid.ts +50 -0
  185. package/src/internal/writer.ts +12 -0
@@ -0,0 +1,85 @@
1
+ import { ModelPart } from "../AssistantEvent.js";
2
+ import { toJsonText } from "./schema.js";
3
+ import * as Effect from "effect/Effect";
4
+ import * as Schema from "effect/Schema";
5
+ import * as Option from "effect/Option";
6
+ import * as Prompt from "effect/ai/Prompt";
7
+ import * as Model from "effect/ai/Model";
8
+ import * as Telemetry from "effect/ai/Telemetry";
9
+ import * as References from "effect/References";
10
+ import * as Tracer from "effect/Tracer";
11
+ //#region src/internal/telemetry.ts
12
+ /** @internal */
13
+ const agentSpan = (name, options) => Effect.withSpan(`invoke_agent ${name}`, {
14
+ kind: "internal",
15
+ root: options.root ?? false,
16
+ attributes: {
17
+ "gen_ai.operation.name": "invoke_agent",
18
+ "gen_ai.agent.name": name,
19
+ "gen_ai.conversation.id": options.chatId,
20
+ "session.id": options.chatId
21
+ }
22
+ });
23
+ /** @internal */
24
+ const currentTraceId = Effect.gen(function* () {
25
+ const tracer = yield* Tracer.Tracer;
26
+ if (!(yield* References.TracerEnabled) || tracer === Tracer.nativeTracer) return Option.none();
27
+ return Option.map(yield* Effect.option(Effect.currentSpan), (span) => span.traceId);
28
+ });
29
+ /** @internal */
30
+ const agentInput = (input) => Effect.annotateCurrentSpan("input.value", toJsonText(input));
31
+ /** @internal */
32
+ const agentOutput = (output) => Effect.annotateCurrentSpan("output.value", toJsonText(output));
33
+ const encodePrompt = Schema.encodeOption(Prompt.Prompt);
34
+ const encodeParts = Schema.encodeOption(Schema.Array(ModelPart));
35
+ const annotateChat = Effect.fnUntraced(function* (response, name) {
36
+ const span = yield* Effect.currentSpan;
37
+ const { usage } = response;
38
+ Telemetry.addGenAIAnnotations(span, {
39
+ operation: { name: "chat" },
40
+ request: { model: Option.getOrUndefined(name) },
41
+ response: { finishReasons: [response.finishReason] },
42
+ usage: {
43
+ inputTokens: usage.inputTokens.total,
44
+ outputTokens: usage.outputTokens.total
45
+ }
46
+ });
47
+ const completion = encodeParts(response.content.flatMap((part) => Option.toArray(Schema.decodeUnknownOption(ModelPart)(part))));
48
+ span.attribute("gen_ai.completion", toJsonText(Option.getOrUndefined(completion)));
49
+ if (usage.inputTokens.cacheRead !== void 0) span.attribute("gen_ai.usage.cache_read_input_tokens", usage.inputTokens.cacheRead);
50
+ if (usage.inputTokens.cacheWrite !== void 0) span.attribute("gen_ai.usage.cache_creation_input_tokens", usage.inputTokens.cacheWrite);
51
+ });
52
+ /** @internal */
53
+ const chatSpan = (prompt, attributes) => (self) => Effect.gen(function* () {
54
+ const name = yield* Effect.serviceOption(Model.ModelName);
55
+ return yield* Effect.gen(function* () {
56
+ const response = yield* self;
57
+ yield* annotateChat(response, name).pipe(Effect.ignore);
58
+ return response;
59
+ }).pipe(Effect.withSpan(Option.match(name, {
60
+ onNone: () => "chat",
61
+ onSome: (model) => `chat ${model}`
62
+ }), {
63
+ kind: "client",
64
+ attributes: {
65
+ "gen_ai.prompt": toJsonText(Option.map(encodePrompt(prompt), (encoded) => encoded.content)),
66
+ ...attributes
67
+ }
68
+ }));
69
+ });
70
+ /** @internal */
71
+ const toolSpan = (call) => Effect.withSpan(`execute_tool ${call.name}`, {
72
+ kind: "internal",
73
+ attributes: {
74
+ "gen_ai.operation.name": "execute_tool",
75
+ "gen_ai.tool.name": call.name,
76
+ "gen_ai.tool.call.id": call.id,
77
+ "input.value": toJsonText({ arguments: call.params })
78
+ }
79
+ });
80
+ /** @internal */
81
+ const toolOutput = (result) => Effect.annotateCurrentSpan("output.value", Schema.is(Schema.String)(result) ? result : toJsonText(result));
82
+ //#endregion
83
+ export { agentInput, agentOutput, agentSpan, chatSpan, currentTraceId, toolOutput, toolSpan };
84
+
85
+ //# sourceMappingURL=telemetry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry.js","names":["AssistantEvent.ModelPart"],"sources":["../../src/internal/telemetry.ts"],"sourcesContent":["import type * as LanguageModel from \"effect/ai/LanguageModel\"\nimport * as Model from \"effect/ai/Model\"\nimport * as Prompt from \"effect/ai/Prompt\"\nimport * as Telemetry from \"effect/ai/Telemetry\"\nimport type * as Tool from \"effect/ai/Tool\"\nimport * as Effect from \"effect/Effect\"\nimport * as Option from \"effect/Option\"\nimport * as References from \"effect/References\"\nimport * as Schema from \"effect/Schema\"\nimport * as Tracer from \"effect/Tracer\"\nimport * as AssistantEvent from \"../AssistantEvent.ts\"\nimport type { SpanAttributes } from \"../Instructions.ts\"\nimport { toJsonText } from \"./schema.ts\"\n\ntype Traced = string | ReadonlyArray<{ readonly _tag: string }>\n\n// Spans follow the OpenTelemetry GenAI conventions, so LLM tools (Langfuse, Phoenix, Datadog LLM Observability) can\n// pick the agent work out of a trace.\n/** @internal */\nexport const agentSpan = (name: string, options: { readonly chatId: string; readonly root?: boolean }) =>\n Effect.withSpan(`invoke_agent ${name}`, {\n kind: \"internal\",\n root: options.root ?? false,\n attributes: {\n \"gen_ai.operation.name\": \"invoke_agent\",\n \"gen_ai.agent.name\": name,\n \"gen_ai.conversation.id\": options.chatId,\n \"session.id\": options.chatId\n }\n })\n\n// None when tracing is off, or when the tracer is Effect's own, which exports nothing.\n/** @internal */\nexport const currentTraceId: Effect.Effect<Option.Option<string>> = Effect.gen(function* () {\n const tracer = yield* Tracer.Tracer\n\n if (!(yield* References.TracerEnabled) || tracer === Tracer.nativeTracer) return Option.none()\n\n return Option.map(yield* Effect.option(Effect.currentSpan), (span) => span.traceId)\n})\n\n/** @internal */\nexport const agentInput = (input: Traced) => Effect.annotateCurrentSpan(\"input.value\", toJsonText(input))\n\n/** @internal */\nexport const agentOutput = (output: Traced) => Effect.annotateCurrentSpan(\"output.value\", toJsonText(output))\n\nconst encodePrompt = Schema.encodeOption(Prompt.Prompt)\n\nconst encodeParts = Schema.encodeOption(Schema.Array(AssistantEvent.ModelPart))\n\nconst annotateChat = Effect.fnUntraced(function* <Tools extends Record<string, Tool.Any>>(\n response: LanguageModel.GenerateTextResponse<Tools, \"opaque\">,\n name: Option.Option<string>\n) {\n const span = yield* Effect.currentSpan\n const { usage } = response\n\n Telemetry.addGenAIAnnotations(span, {\n operation: { name: \"chat\" },\n request: { model: Option.getOrUndefined(name) },\n response: { finishReasons: [response.finishReason] },\n usage: {\n inputTokens: usage.inputTokens.total,\n outputTokens: usage.outputTokens.total\n }\n })\n\n const completion = encodeParts(\n response.content.flatMap((part) => Option.toArray(Schema.decodeUnknownOption(AssistantEvent.ModelPart)(part)))\n )\n\n span.attribute(\"gen_ai.completion\", toJsonText(Option.getOrUndefined(completion)))\n\n if (usage.inputTokens.cacheRead !== undefined) {\n span.attribute(\"gen_ai.usage.cache_read_input_tokens\", usage.inputTokens.cacheRead)\n }\n\n if (usage.inputTokens.cacheWrite !== undefined) {\n span.attribute(\"gen_ai.usage.cache_creation_input_tokens\", usage.inputTokens.cacheWrite)\n }\n})\n\n// Named after the model when the context has its name (`AnthropicLanguageModel.model(...)` provides it).\n/** @internal */\nexport const chatSpan =\n (prompt: Prompt.Prompt, attributes: SpanAttributes | undefined) =>\n <Tools extends Record<string, Tool.Any>, E, R>(\n self: Effect.Effect<LanguageModel.GenerateTextResponse<Tools, \"opaque\">, E, R>\n ): Effect.Effect<LanguageModel.GenerateTextResponse<Tools, \"opaque\">, E, R> =>\n Effect.gen(function* () {\n const name = yield* Effect.serviceOption(Model.ModelName)\n\n return yield* Effect.gen(function* () {\n const response = yield* self\n\n yield* annotateChat(response, name).pipe(Effect.ignore)\n\n return response\n }).pipe(\n Effect.withSpan(Option.match(name, { onNone: () => \"chat\", onSome: (model) => `chat ${model}` }), {\n kind: \"client\",\n attributes: {\n \"gen_ai.prompt\": toJsonText(Option.map(encodePrompt(prompt), (encoded) => encoded.content)),\n ...attributes\n }\n })\n )\n })\n\n/** @internal */\nexport const toolSpan = (call: AssistantEvent.ToolCallPart) =>\n Effect.withSpan(`execute_tool ${call.name}`, {\n kind: \"internal\",\n attributes: {\n \"gen_ai.operation.name\": \"execute_tool\",\n \"gen_ai.tool.name\": call.name,\n \"gen_ai.tool.call.id\": call.id,\n \"input.value\": toJsonText({ arguments: call.params })\n }\n })\n\n/** @internal */\nexport const toolOutput = (result: Schema.Json) =>\n Effect.annotateCurrentSpan(\"output.value\", Schema.is(Schema.String)(result) ? result : toJsonText(result))\n"],"mappings":";;;;;;;;;;;;AAmBA,MAAa,aAAa,MAAc,YACtC,OAAO,SAAS,gBAAgB,QAAQ;CACtC,MAAM;CACN,MAAM,QAAQ,QAAQ;CACtB,YAAY;EACV,yBAAyB;EACzB,qBAAqB;EACrB,0BAA0B,QAAQ;EAClC,cAAc,QAAQ;CACxB;AACF,CAAC;;AAIH,MAAa,iBAAuD,OAAO,IAAI,aAAa;CAC1F,MAAM,SAAS,OAAO,OAAO;CAE7B,IAAI,EAAE,OAAO,WAAW,kBAAkB,WAAW,OAAO,cAAc,OAAO,OAAO,KAAK;CAE7F,OAAO,OAAO,IAAI,OAAO,OAAO,OAAO,OAAO,WAAW,IAAI,SAAS,KAAK,OAAO;AACpF,CAAC;;AAGD,MAAa,cAAc,UAAkB,OAAO,oBAAoB,eAAe,WAAW,KAAK,CAAC;;AAGxG,MAAa,eAAe,WAAmB,OAAO,oBAAoB,gBAAgB,WAAW,MAAM,CAAC;AAE5G,MAAM,eAAe,OAAO,aAAa,OAAO,MAAM;AAEtD,MAAM,cAAc,OAAO,aAAa,OAAO,MAAMA,SAAwB,CAAC;AAE9E,MAAM,eAAe,OAAO,WAAW,WACrC,UACA,MACA;CACA,MAAM,OAAO,OAAO,OAAO;CAC3B,MAAM,EAAE,UAAU;CAElB,UAAU,oBAAoB,MAAM;EAClC,WAAW,EAAE,MAAM,OAAO;EAC1B,SAAS,EAAE,OAAO,OAAO,eAAe,IAAI,EAAE;EAC9C,UAAU,EAAE,eAAe,CAAC,SAAS,YAAY,EAAE;EACnD,OAAO;GACL,aAAa,MAAM,YAAY;GAC/B,cAAc,MAAM,aAAa;EACnC;CACF,CAAC;CAED,MAAM,aAAa,YACjB,SAAS,QAAQ,SAAS,SAAS,OAAO,QAAQ,OAAO,oBAAoBA,SAAwB,CAAC,CAAC,IAAI,CAAC,CAAC,CAC/G;CAEA,KAAK,UAAU,qBAAqB,WAAW,OAAO,eAAe,UAAU,CAAC,CAAC;CAEjF,IAAI,MAAM,YAAY,cAAc,KAAA,GAClC,KAAK,UAAU,wCAAwC,MAAM,YAAY,SAAS;CAGpF,IAAI,MAAM,YAAY,eAAe,KAAA,GACnC,KAAK,UAAU,4CAA4C,MAAM,YAAY,UAAU;AAE3F,CAAC;;AAID,MAAa,YACV,QAAuB,gBAEtB,SAEA,OAAO,IAAI,aAAa;CACtB,MAAM,OAAO,OAAO,OAAO,cAAc,MAAM,SAAS;CAExD,OAAO,OAAO,OAAO,IAAI,aAAa;EACpC,MAAM,WAAW,OAAO;EAExB,OAAO,aAAa,UAAU,IAAI,CAAC,CAAC,KAAK,OAAO,MAAM;EAEtD,OAAO;CACT,CAAC,CAAC,CAAC,KACD,OAAO,SAAS,OAAO,MAAM,MAAM;EAAE,cAAc;EAAQ,SAAS,UAAU,QAAQ;CAAQ,CAAC,GAAG;EAChG,MAAM;EACN,YAAY;GACV,iBAAiB,WAAW,OAAO,IAAI,aAAa,MAAM,IAAI,YAAY,QAAQ,OAAO,CAAC;GAC1F,GAAG;EACL;CACF,CAAC,CACH;AACF,CAAC;;AAGL,MAAa,YAAY,SACvB,OAAO,SAAS,gBAAgB,KAAK,QAAQ;CAC3C,MAAM;CACN,YAAY;EACV,yBAAyB;EACzB,oBAAoB,KAAK;EACzB,uBAAuB,KAAK;EAC5B,eAAe,WAAW,EAAE,WAAW,KAAK,OAAO,CAAC;CACtD;AACF,CAAC;;AAGH,MAAa,cAAc,WACzB,OAAO,oBAAoB,gBAAgB,OAAO,GAAG,OAAO,MAAM,CAAC,CAAC,MAAM,IAAI,SAAS,WAAW,MAAM,CAAC"}
@@ -0,0 +1,76 @@
1
+ import { AssistantEvent_d_exports, ToolCallPart, ToolOutcome } from "../AssistantEvent.js";
2
+ import { AssistantToolCall } from "../AssistantToolCall.js";
3
+ import * as Context from "effect/Context";
4
+ import * as Effect from "effect/Effect";
5
+ import * as Schema from "effect/Schema";
6
+ import * as Prompt from "effect/ai/Prompt";
7
+ import * as Tool from "effect/ai/Tool";
8
+ import * as Toolkit from "effect/ai/Toolkit";
9
+ declare namespace toolkit_d_exports {
10
+ export { CallResult, ToolServices, Tools, duplicateNames, empty, failed, fromToolkit, fromToolkitOrEmpty, fromWithHandler, merge, needsDecision, needsDecisionOf, provide, runNative, runOnly, withContext };
11
+ }
12
+ /** @internal */
13
+ export interface CallResult {
14
+ readonly outcome: ToolOutcome;
15
+ readonly result: Schema.Json;
16
+ readonly reason?: string | undefined;
17
+ }
18
+ /** @internal */
19
+ export interface Tools<out R = never> {
20
+ readonly definitions: ReadonlyArray<Tool.Any>;
21
+ readonly run: (call: ToolCallPart) => Effect.Effect<CallResult, never, R>;
22
+ readonly needsDecision?: ((call: ToolCallPart, history: ReadonlyArray<Prompt.Message>) => Effect.Effect<boolean, never, R>) | undefined;
23
+ }
24
+ /** @internal */
25
+ export type ToolServices<T extends Record<string, Tool.Any>> = Exclude<Tool.HandlerServices<T[keyof T]>, AssistantToolCall> | T[keyof T]["parametersSchema"]["DecodingServices"];
26
+ /** @internal */
27
+ export declare const failed: (message: string, reason: string) => CallResult;
28
+ /** @internal */
29
+ export declare const needsDecision: <T extends Record<string, Tool.Any>>(toolkit: {
30
+ readonly tools: T;
31
+ }, call: {
32
+ readonly type: "tool-call";
33
+ readonly id: string;
34
+ readonly params: Schema.Json;
35
+ readonly providerExecuted: boolean;
36
+ readonly "~effect/ai/Response/Part": "~effect/ai/Response/Part";
37
+ readonly metadata: {
38
+ readonly [x: string]: Schema.Json;
39
+ };
40
+ readonly name: string;
41
+ }, history: readonly Prompt.Message[]) => Effect.Effect<boolean, never, ToolServices<T>>;
42
+ /** @internal */
43
+ export declare const runNative: <T extends Record<string, Tool.Any>>(toolkit: Toolkit.WithHandler<T>, call: {
44
+ readonly type: "tool-call";
45
+ readonly id: string;
46
+ readonly params: Schema.Json;
47
+ readonly providerExecuted: boolean;
48
+ readonly "~effect/ai/Response/Part": "~effect/ai/Response/Part";
49
+ readonly metadata: {
50
+ readonly [x: string]: Schema.Json;
51
+ };
52
+ readonly name: string;
53
+ }) => Effect.Effect<CallResult, never, ToolServices<T>>;
54
+ /** @internal */
55
+ export declare const fromWithHandler: <T extends Record<string, Tool.Any>>(toolkit: Toolkit.WithHandler<T>) => Tools<ToolServices<T>>;
56
+ /** @internal */
57
+ export declare const fromToolkit: <T extends Record<string, Tool.Any>>(toolkit: Toolkit.Toolkit<T>) => Tools<ToolServices<T> | Tool.HandlersFor<T>>;
58
+ /** @internal */
59
+ export declare const withContext: <T extends Record<string, Tool.Any>, R>(toolkit: Toolkit.Toolkit<T>, handlers: (context: Context.Context<R>) => Toolkit.HandlersFrom<T>) => Tools<R | ToolServices<T>>;
60
+ /** @internal */
61
+ export declare const fromToolkitOrEmpty: <T extends Record<string, Tool.Any>>(toolkit: Toolkit.Toolkit<T> | undefined) => Tools<ToolServices<T> | Tool.HandlersFor<T>>;
62
+ /** @internal */
63
+ export declare const empty: Tools;
64
+ /** @internal */
65
+ export declare const merge: <A, B>(self: Tools<A>, that: Tools<B>) => Tools<A | B>;
66
+ /** @internal */
67
+ export declare const needsDecisionOf: <R>(tools: Tools<R>, call: ToolCallPart, history: ReadonlyArray<Prompt.Message>) => Effect.Effect<boolean, never, R>;
68
+ /** @internal */
69
+ export declare const provide: <R>(tools: Tools<R>, context: Context.Context<R>) => Tools;
70
+ /** @internal */
71
+ export declare const duplicateNames: (tools: Tools<unknown>) => ReadonlyArray<string>;
72
+ /** @internal */
73
+ export declare const runOnly: <R>(tools: Tools<R>, names: ReadonlyArray<string>, message: string) => Tools<R>;
74
+ //#endregion
75
+ export { toolkit_d_exports };
76
+ //# sourceMappingURL=toolkit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toolkit.d.ts","names":[],"sources":["../../src/internal/toolkit.ts"],"mappings":";;;;;;;;;;;;iBAgBiB;WACN,SAAS;WACT,QAAQ,OAAO;WACf;;;iBAKM,UAAU;WAChB,aAAa,cAAc,KAAK;WAChC,MAAM,MAAM,iBAAgC,OAAO,OAAO,mBAAmB;WAE7E,kBACH,MAAM,cAA6B,SAAS,cAAc,OAAO,aAAa,OAAO,uBAAuB;;;YAKxG,aAAa,UAAU,eAAe,KAAK,QACnD,QAAQ,KAAK,gBAAgB,QAAQ,KAAK,qBAE1C,QAAQ;;qBAKC,SAAM,iBAAmB,mBAAmB;;qBAkC5C,gBAA6C,UAAU,eAAe,KAAK,MAAI;WAC/D,OAAA;;;;;;;;;;;GAoB3B,kBAAA,OAAA,cAAA,OAAA,uBAAA,aAAA;;qBASW,YAAyC,UAAU,eAAe,KAAK,MAAI,SAAA,QAAA,YAAA,IAAA;;;;;;;;;;MAiCtF,OAAA,OAAA,mBAAA,aAAA;;qBAGW,kBAAmB,UAAU,eAAe,KAAK,MAAI,SACvD,QAAQ,YAAY,OAC5B,MAAM,aAAa;;qBAOT,cAAe,UAAU,eAAe,KAAK,MAAI,SACnD,QAAQ,QAAQ,OACxB,MAAM,aAAa,KAAK,KAAK,YAAY;;qBAU/B,cAAe,UAAU,eAAe,KAAK,MAAM,GAAC,SACtD,QAAQ,QAAQ,IAAE,WAChB,SAAS,QAAQ,QAAQ,OAAO,QAAQ,aAAa,OAC/D,MAAM,IAAI,aAAa;;qBAYb,qBAAsB,UAAU,eAAe,KAAK,MAAI,SAC1D,QAAQ,QAAQ,mBACxB,MAAM,aAAa,KAAK,KAAK,YAAY;;qBAG/B,OAAO;;qBAMP,QAAS,GAAG,GAAC,MAAQ,MAAM,IAAE,MAAQ,MAAM,OAAK,MAAM,IAAI;;qBAY1D,kBAAmB,GAAC,OACxB,MAAM,IAAE,MACT,cAA2B,SACxB,cAAc,OAAO,aAC7B,OAAO,uBAAuB;;qBAKpB,UAAW,GAAC,OAAS,MAAM,IAAE,SAAW,QAAQ,QAAQ,OAAK;;qBAO7D,iBAAc,OAAW,mBAAiB;;qBAQ1C,UAAW,GAAC,OAAS,MAAM,IAAE,OAAS,uBAAqB,oBAAoB,MAAM"}
@@ -0,0 +1,129 @@
1
+ import { toJson } from "./schema.js";
2
+ import { AssistantToolCall } from "../AssistantToolCall.js";
3
+ import { ToolFailure } from "../ToolFailure.js";
4
+ import * as Predicate from "effect/Predicate";
5
+ import * as Effect from "effect/Effect";
6
+ import * as Schema from "effect/Schema";
7
+ import * as Stream from "effect/Stream";
8
+ import * as Option from "effect/Option";
9
+ import * as AiError from "effect/ai/AiError";
10
+ //#region src/internal/toolkit.ts
11
+ const encodeFailure = Schema.encodeSync(ToolFailure);
12
+ /** @internal */
13
+ const failed = (message, reason) => ({
14
+ outcome: "failure",
15
+ result: toJson(encodeFailure(new ToolFailure({
16
+ message,
17
+ reason
18
+ }))),
19
+ reason
20
+ });
21
+ const decodeReason = Schema.decodeUnknownOption(Schema.Struct({ reason: Schema.String }));
22
+ const decodeTag = Schema.decodeUnknownOption(Schema.Struct({ _tag: Schema.String }));
23
+ const failure = (result) => ({
24
+ outcome: "failure",
25
+ result,
26
+ reason: Option.getOrUndefined(Option.orElse(Option.map(decodeReason(result), ({ reason }) => reason), () => Option.map(decodeTag(result), ({ _tag }) => _tag)))
27
+ });
28
+ const hasTool = (toolkit, name) => Object.hasOwn(toolkit.tools, name);
29
+ const toolNamed = (toolkit, name) => Object.hasOwn(toolkit.tools, name) ? toolkit.tools[name] : void 0;
30
+ const decodeParams = (tool, params) => Schema.decodeUnknownEffect(tool.parametersSchema)(params);
31
+ /** @internal */
32
+ const needsDecision = Effect.fnUntraced(function* (toolkit, call, history) {
33
+ const tool = toolNamed(toolkit, call.name);
34
+ if (tool === void 0) return false;
35
+ const needs = tool.needsApproval;
36
+ if (needs === void 0 || Predicate.isBoolean(needs)) return needs === true;
37
+ return yield* Effect.gen(function* () {
38
+ const decided = needs(yield* decodeParams(tool, call.params), {
39
+ toolCallId: call.id,
40
+ messages: history
41
+ });
42
+ return Effect.isEffect(decided) ? yield* decided : decided;
43
+ }).pipe(Effect.orElseSucceed(() => false));
44
+ });
45
+ const isParams = (params) => params !== void 0;
46
+ /** @internal */
47
+ const runNative = Effect.fnUntraced(function* (toolkit, call) {
48
+ const name = call.name;
49
+ if (!hasTool(toolkit, name)) return failed(`No tool named ${call.name}.`, "ToolNotFoundError");
50
+ const params = call.params;
51
+ if (!isParams(params)) return failed(`Invalid parameters for ${call.name}.`, "ToolParameterValidationError");
52
+ return yield* Effect.gen(function* () {
53
+ const results = yield* toolkit.handle(name, params, call.id);
54
+ const last = yield* Stream.runLast(Stream.filter(results, (result) => !result.preliminary));
55
+ return Option.match(last, {
56
+ onNone: () => failed(`${call.name} returned no result.`, "NoResult"),
57
+ onSome: (result) => result.isFailure ? failure(toJson(result.encodedResult)) : {
58
+ outcome: "success",
59
+ result: toJson(result.encodedResult)
60
+ }
61
+ });
62
+ }).pipe(Effect.catch((error) => Effect.succeed(AiError.isAiError(error) ? failed(error.reason.message, error.reason._tag) : failure(toJson(error)))), Effect.provideService(AssistantToolCall, {
63
+ callId: call.id,
64
+ tool: call.name
65
+ }));
66
+ });
67
+ /** @internal */
68
+ const fromWithHandler = (toolkit) => ({
69
+ definitions: Object.values(toolkit.tools),
70
+ run: (call) => runNative(toolkit, call),
71
+ needsDecision: (call, history) => needsDecision(toolkit, call, history)
72
+ });
73
+ /** @internal */
74
+ const fromToolkit = (toolkit) => ({
75
+ definitions: Object.values(toolkit.tools),
76
+ run: Effect.fnUntraced(function* (call) {
77
+ return yield* runNative(yield* toolkit, call);
78
+ }),
79
+ needsDecision: (call, history) => needsDecision(toolkit, call, history)
80
+ });
81
+ /** @internal */
82
+ const withContext = (toolkit, handlers) => ({
83
+ definitions: Object.values(toolkit.tools),
84
+ run: Effect.fnUntraced(function* (call) {
85
+ const context = yield* Effect.context();
86
+ const resolved = yield* Effect.provideContext(toolkit, yield* toolkit.toHandlers(handlers(context)));
87
+ return yield* runNative(resolved, call);
88
+ }),
89
+ needsDecision: (call, history) => needsDecision(toolkit, call, history)
90
+ });
91
+ /** @internal */
92
+ const fromToolkitOrEmpty = (toolkit) => toolkit === void 0 ? empty : fromToolkit(toolkit);
93
+ /** @internal */
94
+ const empty = {
95
+ definitions: [],
96
+ run: (call) => Effect.succeed(failed(`No tool named ${call.name}.`, "ToolNotFoundError"))
97
+ };
98
+ /** @internal */
99
+ const merge = (self, that) => {
100
+ const inSelf = (call) => self.definitions.some((tool) => tool.name === call.name);
101
+ return {
102
+ definitions: [...self.definitions, ...that.definitions],
103
+ run: (call) => inSelf(call) ? self.run(call) : that.run(call),
104
+ needsDecision: (call, history) => inSelf(call) ? needsDecisionOf(self, call, history) : needsDecisionOf(that, call, history)
105
+ };
106
+ };
107
+ /** @internal */
108
+ const needsDecisionOf = (tools, call, history) => tools.needsDecision === void 0 ? Effect.succeed(false) : tools.needsDecision(call, history);
109
+ /** @internal */
110
+ const provide = (tools, context) => ({
111
+ definitions: tools.definitions,
112
+ run: (call) => Effect.provideContext(tools.run(call), context),
113
+ needsDecision: (call, history) => Effect.provideContext(needsDecisionOf(tools, call, history), context)
114
+ });
115
+ /** @internal */
116
+ const duplicateNames = (tools) => {
117
+ const names = tools.definitions.map((tool) => tool.name);
118
+ return names.filter((name, i) => names.indexOf(name) !== i);
119
+ };
120
+ /** @internal */
121
+ const runOnly = (tools, names, message) => ({
122
+ definitions: tools.definitions,
123
+ run: (call) => names.includes(call.name) ? tools.run(call) : Effect.succeed(failed(message, "NotNow")),
124
+ needsDecision: (call, history) => names.includes(call.name) ? needsDecisionOf(tools, call, history) : Effect.succeed(false)
125
+ });
126
+ //#endregion
127
+ export { duplicateNames, empty, failed, fromToolkit, fromToolkitOrEmpty, fromWithHandler, merge, needsDecision, needsDecisionOf, provide, runNative, runOnly, withContext };
128
+
129
+ //# sourceMappingURL=toolkit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toolkit.js","names":["ToolFailure.ToolFailure"],"sources":["../../src/internal/toolkit.ts"],"sourcesContent":["import * as AiError from \"effect/ai/AiError\"\nimport type * as Prompt from \"effect/ai/Prompt\"\nimport type * as Tool from \"effect/ai/Tool\"\nimport type * as Toolkit from \"effect/ai/Toolkit\"\nimport type * as Context from \"effect/Context\"\nimport * as Effect from \"effect/Effect\"\nimport * as Option from \"effect/Option\"\nimport * as Predicate from \"effect/Predicate\"\nimport * as Schema from \"effect/Schema\"\nimport * as Stream from \"effect/Stream\"\nimport type * as AssistantEvent from \"../AssistantEvent.ts\"\nimport { AssistantToolCall } from \"../AssistantToolCall.ts\"\nimport * as ToolFailure from \"../ToolFailure.ts\"\nimport { toJson } from \"./schema.ts\"\n\n/** @internal */\nexport interface CallResult {\n readonly outcome: AssistantEvent.ToolOutcome\n readonly result: Schema.Json\n readonly reason?: string | undefined\n}\n\n// Apps give the loop native toolkits; these are what the loop makes of them, merged with its own.\n/** @internal */\nexport interface Tools<out R = never> {\n readonly definitions: ReadonlyArray<Tool.Any>\n readonly run: (call: AssistantEvent.ToolCallPart) => Effect.Effect<CallResult, never, R>\n // Unset: no call waits for the user's decision.\n readonly needsDecision?:\n | ((call: AssistantEvent.ToolCallPart, history: ReadonlyArray<Prompt.Message>) => Effect.Effect<boolean, never, R>)\n | undefined\n}\n\n/** @internal */\nexport type ToolServices<T extends Record<string, Tool.Any>> =\n | Exclude<Tool.HandlerServices<T[keyof T]>, AssistantToolCall>\n // Already in the handlers' services; stated for the loop, which decodes params to decide on a call.\n | T[keyof T][\"parametersSchema\"][\"DecodingServices\"]\n\nconst encodeFailure = Schema.encodeSync(ToolFailure.ToolFailure)\n\n/** @internal */\nexport const failed = (message: string, reason: string): CallResult => ({\n outcome: \"failure\",\n result: toJson(encodeFailure(new ToolFailure.ToolFailure({ message, reason }))),\n reason\n})\n\nconst decodeReason = Schema.decodeUnknownOption(Schema.Struct({ reason: Schema.String }))\n\nconst decodeTag = Schema.decodeUnknownOption(Schema.Struct({ _tag: Schema.String }))\n\nconst failure = (result: Schema.Json): CallResult => ({\n outcome: \"failure\",\n result,\n reason: Option.getOrUndefined(\n Option.orElse(\n Option.map(decodeReason(result), ({ reason }) => reason),\n () => Option.map(decodeTag(result), ({ _tag }) => _tag)\n )\n )\n})\n\nconst hasTool = <T extends Record<string, Tool.Any>>(\n toolkit: { readonly tools: T },\n name: PropertyKey\n): name is keyof T => Object.hasOwn(toolkit.tools, name)\n\n// With noUncheckedIndexedAccess, an index doesn't narrow on `hasTool`.\nconst toolNamed = (toolkit: { readonly tools: Record<string, Tool.Any> }, name: string): Tool.Any | undefined =>\n Object.hasOwn(toolkit.tools, name) ? toolkit.tools[name] : undefined\n\nconst decodeParams = (tool: Tool.Any, params: Schema.Json) => Schema.decodeUnknownEffect(tool.parametersSchema)(params)\n\n// Params that don't fit the tool don't park: the call runs, and fails with why.\n/** @internal */\nexport const needsDecision = Effect.fnUntraced(function* <T extends Record<string, Tool.Any>>(\n toolkit: { readonly tools: T },\n call: AssistantEvent.ToolCallPart,\n history: ReadonlyArray<Prompt.Message>\n): Effect.fn.Return<boolean, never, ToolServices<T>> {\n const tool = toolNamed(toolkit, call.name)\n\n if (tool === undefined) return false\n\n const needs = tool.needsApproval\n\n if (needs === undefined || Predicate.isBoolean(needs)) return needs === true\n\n return yield* Effect.gen(function* () {\n const decided = needs(yield* decodeParams(tool, call.params), {\n toolCallId: call.id,\n messages: history\n })\n\n return Effect.isEffect(decided) ? yield* decided : decided\n }).pipe(Effect.orElseSucceed(() => false))\n})\n\n// The toolkit decodes the params with the tool's schema before its handler sees them, and a call whose params don't\n// fit fails with a validation error the model reads: the model's JSON is what `handle` takes.\nconst isParams = <T>(params: unknown): params is Tool.ParametersEncoded<T> => params !== undefined\n\n// Every failure is the call's result, for the model to read. With `failureMode: \"return\"` the toolkit encodes the\n// tool's own failure with its schema first.\n/** @internal */\nexport const runNative = Effect.fnUntraced(function* <T extends Record<string, Tool.Any>>(\n toolkit: Toolkit.WithHandler<T>,\n call: AssistantEvent.ToolCallPart\n): Effect.fn.Return<CallResult, never, ToolServices<T>> {\n const name: PropertyKey = call.name\n\n if (!hasTool(toolkit, name)) return failed(`No tool named ${call.name}.`, \"ToolNotFoundError\")\n\n const params: unknown = call.params\n\n if (!isParams<T[keyof T]>(params)) {\n return failed(`Invalid parameters for ${call.name}.`, \"ToolParameterValidationError\")\n }\n\n return yield* Effect.gen(function* () {\n const results = yield* toolkit.handle<keyof T>(name, params, call.id)\n const last = yield* Stream.runLast(Stream.filter(results, (result) => !result.preliminary))\n\n return Option.match(last, {\n onNone: () => failed(`${call.name} returned no result.`, \"NoResult\"),\n onSome: (result): CallResult =>\n result.isFailure\n ? failure(toJson(result.encodedResult))\n : { outcome: \"success\", result: toJson(result.encodedResult) }\n })\n }).pipe(\n Effect.catch((error) =>\n Effect.succeed(\n AiError.isAiError(error) ? failed(error.reason.message, error.reason._tag) : failure(toJson(error))\n )\n ),\n Effect.provideService(AssistantToolCall, { callId: call.id, tool: call.name })\n )\n})\n\n/** @internal */\nexport const fromWithHandler = <T extends Record<string, Tool.Any>>(\n toolkit: Toolkit.WithHandler<T>\n): Tools<ToolServices<T>> => ({\n definitions: Object.values(toolkit.tools),\n run: (call) => runNative(toolkit, call),\n needsDecision: (call, history) => needsDecision(toolkit, call, history)\n})\n\n/** @internal */\nexport const fromToolkit = <T extends Record<string, Tool.Any>>(\n toolkit: Toolkit.Toolkit<T>\n): Tools<ToolServices<T> | Tool.HandlersFor<T>> => ({\n definitions: Object.values(toolkit.tools),\n run: Effect.fnUntraced(function* (call) {\n return yield* runNative(yield* toolkit, call)\n }),\n needsDecision: (call, history) => needsDecision(toolkit, call, history)\n})\n\n// The handlers use services of the loop they run in, so they're made for each call, with its context.\n/** @internal */\nexport const withContext = <T extends Record<string, Tool.Any>, R>(\n toolkit: Toolkit.Toolkit<T>,\n handlers: (context: Context.Context<R>) => Toolkit.HandlersFrom<T>\n): Tools<R | ToolServices<T>> => ({\n definitions: Object.values(toolkit.tools),\n run: Effect.fnUntraced(function* (call) {\n const context = yield* Effect.context<R>()\n const resolved = yield* Effect.provideContext(toolkit, yield* toolkit.toHandlers(handlers(context)))\n\n return yield* runNative(resolved, call)\n }),\n needsDecision: (call, history) => needsDecision(toolkit, call, history)\n})\n\n/** @internal */\nexport const fromToolkitOrEmpty = <T extends Record<string, Tool.Any>>(\n toolkit: Toolkit.Toolkit<T> | undefined\n): Tools<ToolServices<T> | Tool.HandlersFor<T>> => (toolkit === undefined ? empty : fromToolkit(toolkit))\n\n/** @internal */\nexport const empty: Tools = {\n definitions: [],\n run: (call) => Effect.succeed(failed(`No tool named ${call.name}.`, \"ToolNotFoundError\"))\n}\n\n/** @internal */\nexport const merge = <A, B>(self: Tools<A>, that: Tools<B>): Tools<A | B> => {\n const inSelf = (call: AssistantEvent.ToolCallPart) => self.definitions.some((tool) => tool.name === call.name)\n\n return {\n definitions: [...self.definitions, ...that.definitions],\n run: (call) => (inSelf(call) ? self.run(call) : that.run(call)),\n needsDecision: (call, history) =>\n inSelf(call) ? needsDecisionOf(self, call, history) : needsDecisionOf(that, call, history)\n }\n}\n\n/** @internal */\nexport const needsDecisionOf = <R>(\n tools: Tools<R>,\n call: AssistantEvent.ToolCallPart,\n history: ReadonlyArray<Prompt.Message>\n): Effect.Effect<boolean, never, R> =>\n tools.needsDecision === undefined ? Effect.succeed(false) : tools.needsDecision(call, history)\n\n// A turn's own services, for a loop the app writes.\n/** @internal */\nexport const provide = <R>(tools: Tools<R>, context: Context.Context<R>): Tools => ({\n definitions: tools.definitions,\n run: (call) => Effect.provideContext(tools.run(call), context),\n needsDecision: (call, history) => Effect.provideContext(needsDecisionOf(tools, call, history), context)\n})\n\n/** @internal */\nexport const duplicateNames = (tools: Tools<unknown>): ReadonlyArray<string> => {\n const names = tools.definitions.map((tool) => tool.name)\n\n return names.filter((name, i) => names.indexOf(name) !== i)\n}\n\n// The same tools are offered, so the prompt doesn't change.\n/** @internal */\nexport const runOnly = <R>(tools: Tools<R>, names: ReadonlyArray<string>, message: string): Tools<R> => ({\n definitions: tools.definitions,\n run: (call) => (names.includes(call.name) ? tools.run(call) : Effect.succeed(failed(message, \"NotNow\"))),\n needsDecision: (call, history) =>\n names.includes(call.name) ? needsDecisionOf(tools, call, history) : Effect.succeed(false)\n})\n"],"mappings":"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"}
@@ -0,0 +1,132 @@
1
+ import { fromKey } from "./uuid.js";
2
+ import { AssistantMessage, MessageBlock, isLibraryEvent } from "../AssistantEvent.js";
3
+ import { CurrentChat } from "../CurrentChat.js";
4
+ import { Props, fromEntries } from "../Decision.js";
5
+ import { fromEntries as fromEntries$1 } from "../BlockKit.js";
6
+ import { AssistantToolCall } from "../AssistantToolCall.js";
7
+ import { ToolFailure, fail } from "../ToolFailure.js";
8
+ import { fromWithHandler } from "./toolkit.js";
9
+ import * as Predicate from "effect/Predicate";
10
+ import * as Effect from "effect/Effect";
11
+ import * as Schema from "effect/Schema";
12
+ import * as Option from "effect/Option";
13
+ import * as Tool from "effect/ai/Tool";
14
+ import * as Toolkit from "effect/ai/Toolkit";
15
+ //#region src/internal/tools.ts
16
+ /** @internal */
17
+ const waitToolName = "wait";
18
+ /** @internal */
19
+ const sendUserMessageName = "send_user_message";
20
+ const Wait = Tool.make(waitToolName, {
21
+ description: "End your turn and wait for the next message or update, when you have nothing more to do right now and no message to send (otherwise set wait on send_user_message).",
22
+ success: Schema.String
23
+ });
24
+ const description = "Send the user a chat message. It's the only way they see anything you write: text outside it is private to you.";
25
+ const WaitFlag = Schema.Boolean.annotate({ description: "true when this is your last message for now: your turn ends with it, as with the wait tool. false when you keep working after it." });
26
+ const endsWithMessage = Schema.decodeUnknownOption(Schema.Struct({ wait: Schema.Literal(true) }));
27
+ /** @internal */
28
+ const isTurnEnding = (call) => call.name === "wait" || call.name === "send_user_message" && Option.isSome(endsWithMessage(call.params));
29
+ const messageId = AssistantToolCall.useSync((call) => fromKey(`message:${call.callId}`));
30
+ const append = Effect.fnUntraced(function* (message) {
31
+ const chat = yield* CurrentChat;
32
+ if (!(yield* chat.entries).some(({ event }) => isLibraryEvent(event) && Predicate.isTagged(event, "AssistantMessage") && event.id === message.id)) yield* chat.append(message);
33
+ });
34
+ const PlainMessage = Tool.make(sendUserMessageName, {
35
+ description,
36
+ parameters: Schema.Struct({
37
+ text: Schema.String,
38
+ wait: WaitFlag
39
+ }),
40
+ success: Schema.String,
41
+ failure: ToolFailure,
42
+ dependencies: [CurrentChat, AssistantToolCall]
43
+ });
44
+ const PlainTools = Toolkit.make(PlainMessage, Wait);
45
+ const plainHandlers = PlainTools.toHandlers({
46
+ wait: () => Effect.succeed("waiting"),
47
+ send_user_message: Effect.fnUntraced(function* ({ text }) {
48
+ yield* append(AssistantMessage.make({
49
+ id: yield* messageId,
50
+ text
51
+ }));
52
+ return "sent";
53
+ })
54
+ });
55
+ const plainTools = Effect.gen(function* () {
56
+ return yield* Effect.provideContext(PlainTools, yield* plainHandlers);
57
+ });
58
+ const decodeDecisionProps = Schema.decodeUnknownOption(Props);
59
+ const checkDecisions = Effect.fnUntraced(function* (ids) {
60
+ const states = fromEntries(yield* CurrentChat.use((chat) => chat.entries));
61
+ const notWaiting = ids.filter((id) => states.get(id)?.outcome !== void 0 || !states.has(id));
62
+ if (notWaiting.length > 0) return yield* fail(`Not waiting for the user's decision: ${notWaiting.join(", ")}. Use the ids of decisions that wait for one.`, { reason: "NotWaitingForDecision" });
63
+ });
64
+ const checkCloses = Effect.fnUntraced(function* (ids) {
65
+ const entries = yield* CurrentChat.use((chat) => chat.entries);
66
+ const states = fromEntries$1(entries);
67
+ const waiting = ids.filter((id) => entries.some(({ event }) => isLibraryEvent(event) && Predicate.isTagged(event, "AssistantMessage") && event.id === id && event.block?.kind === "decision" && states.get(id)?.open === true));
68
+ if (waiting.length > 0) return yield* fail(`${waiting.join(", ")} asks for a decision that still waits for the user: only their answer in the block closes it. Send the message again without ${waiting.length === 1 ? "that id" : "those ids"} in closes.`, { reason: "DecisionWaiting" });
69
+ });
70
+ const blockTools = Effect.fnUntraced(function* (kit) {
71
+ const catalog = kit.blocks.map((block) => `- ${block.kind}: ${block.description}`).join("\n");
72
+ const kinds = kit.blocks.map((block) => block.kind);
73
+ const BlockMessage = Tool.make(sendUserMessageName, {
74
+ description: `${description}
75
+ You can add a block to show UI with the message, e.g. a few options to pick from or a yes/no, when that makes answering easier than typing. Plain text is fine too. Using it sends you an <answer>; the user may also just type their answer instead, and then you pass that message's id in closes.
76
+ Blocks:
77
+ ${catalog}`,
78
+ parameters: Schema.Struct({
79
+ text: Schema.String,
80
+ wait: WaitFlag,
81
+ block: Schema.optional(Schema.Struct({
82
+ kind: Schema.Literals(kinds),
83
+ props: Schema.Union(kit.blocks.map((block) => block.props))
84
+ })),
85
+ closes: Schema.optional(Schema.Array(Schema.String)).annotate({ description: "Ids of your earlier messages whose block the user just answered by typing instead of using it. Those blocks close. Leave it out otherwise: a block stays open when the user talks about something else. A decision block never closes this way: only the user's answer in it decides." })
86
+ }),
87
+ success: Schema.String,
88
+ failure: ToolFailure,
89
+ dependencies: [CurrentChat, AssistantToolCall]
90
+ });
91
+ const BlockTools = Toolkit.make(BlockMessage, Wait);
92
+ const handlers = yield* BlockTools.toHandlers({
93
+ wait: () => Effect.succeed("waiting"),
94
+ send_user_message: Effect.fnUntraced(function* ({ text, block, closes }) {
95
+ if (text.trim() === "") return yield* fail("The message has no text: the user sees only what you send here, and a block alone is an empty bubble. Send it again with the words they should read (a short lead-in for the block).", { reason: "EmptyMessage" });
96
+ const id = yield* messageId;
97
+ const closed = closes === void 0 || closes.length === 0 ? {} : { closes };
98
+ if (closes !== void 0 && closes.length > 0) yield* checkCloses(closes);
99
+ if (block === void 0) {
100
+ yield* append(AssistantMessage.make({
101
+ id,
102
+ text,
103
+ ...closed
104
+ }));
105
+ return "sent";
106
+ }
107
+ const props = kit.byKind.get(block.kind)?.props;
108
+ if (props === void 0 || Option.isNone(Schema.decodeUnknownOption(props)(block.props))) return yield* fail(`Those props don't fit a ${block.kind} block.`);
109
+ if (block.kind === "decision") yield* checkDecisions(Option.match(decodeDecisionProps(block.props), {
110
+ onNone: () => [],
111
+ onSome: (decoded) => decoded.decisionIds
112
+ }));
113
+ yield* append(AssistantMessage.make({
114
+ id,
115
+ text,
116
+ block: MessageBlock.make(block),
117
+ ...closed
118
+ }));
119
+ return `sent as message ${id}`;
120
+ })
121
+ });
122
+ return yield* Effect.provideContext(BlockTools, handlers);
123
+ });
124
+ /** @internal */
125
+ const makeMessaging = Effect.fnUntraced(function* (kit) {
126
+ if (kit === void 0 || kit.blocks.length === 0) return fromWithHandler(yield* plainTools);
127
+ return fromWithHandler(yield* blockTools(kit));
128
+ });
129
+ //#endregion
130
+ export { isTurnEnding, makeMessaging, sendUserMessageName, waitToolName };
131
+
132
+ //# sourceMappingURL=tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.js","names":["Uuid.fromKey","AssistantEvent.isLibraryEvent","ToolFailure.ToolFailure","Decision.Props","Decision.fromEntries","ToolFailure.fail","BlockKit.fromEntries","Toolkits.fromWithHandler"],"sources":["../../src/internal/tools.ts"],"sourcesContent":["import * as Tool from \"effect/ai/Tool\"\nimport * as Toolkit from \"effect/ai/Toolkit\"\nimport * as Effect from \"effect/Effect\"\nimport * as Option from \"effect/Option\"\nimport * as Predicate from \"effect/Predicate\"\nimport * as Schema from \"effect/Schema\"\nimport * as AssistantEvent from \"../AssistantEvent.ts\"\nimport { AssistantToolCall } from \"../AssistantToolCall.ts\"\nimport * as BlockKit from \"../BlockKit.ts\"\nimport { CurrentChat } from \"../CurrentChat.ts\"\nimport * as Decision from \"../Decision.ts\"\nimport * as ToolFailure from \"../ToolFailure.ts\"\nimport * as Toolkits from \"./toolkit.ts\"\nimport * as Uuid from \"./uuid.ts\"\n\n/** @internal */\nexport const waitToolName = \"wait\"\n\n/** @internal */\nexport const sendUserMessageName = \"send_user_message\"\n\nconst Wait = Tool.make(waitToolName, {\n description:\n \"End your turn and wait for the next message or update, when you have nothing more to do right now and no message to send (otherwise set wait on send_user_message).\",\n success: Schema.String\n})\n\nconst description =\n \"Send the user a chat message. It's the only way they see anything you write: text outside it is private to you.\"\n\nconst WaitFlag = Schema.Boolean.annotate({\n description:\n \"true when this is your last message for now: your turn ends with it, as with the wait tool. false when you keep working after it.\"\n})\n\nconst endsWithMessage = Schema.decodeUnknownOption(Schema.Struct({ wait: Schema.Literal(true) }))\n\n/** @internal */\nexport const isTurnEnding = (call: AssistantEvent.ToolCallPart): boolean =>\n call.name === waitToolName || (call.name === sendUserMessageName && Option.isSome(endsWithMessage(call.params)))\n\n// From the call: a call cut off by a crash runs again with the same id, and finds its message already sent.\nconst messageId = AssistantToolCall.useSync((call) => Uuid.fromKey(`message:${call.callId}`))\n\nconst append = Effect.fnUntraced(function* (message: AssistantEvent.AssistantMessage) {\n const chat = yield* CurrentChat\n const entries = yield* chat.entries\n\n const sent = entries.some(\n ({ event }) =>\n AssistantEvent.isLibraryEvent(event) && Predicate.isTagged(event, \"AssistantMessage\") && event.id === message.id\n )\n\n if (!sent) yield* chat.append(message)\n})\n\nconst PlainMessage = Tool.make(sendUserMessageName, {\n description,\n parameters: Schema.Struct({ text: Schema.String, wait: WaitFlag }),\n success: Schema.String,\n failure: ToolFailure.ToolFailure,\n dependencies: [CurrentChat, AssistantToolCall]\n})\n\nconst PlainTools = Toolkit.make(PlainMessage, Wait)\n\nconst plainHandlers = PlainTools.toHandlers({\n wait: () => Effect.succeed(\"waiting\"),\n send_user_message: Effect.fnUntraced(function* ({ text }) {\n yield* append(AssistantEvent.AssistantMessage.make({ id: yield* messageId, text }))\n\n return \"sent\"\n })\n})\n\nconst plainTools = Effect.gen(function* () {\n return yield* Effect.provideContext(PlainTools, yield* plainHandlers)\n})\n\nconst decodeDecisionProps = Schema.decodeUnknownOption(Decision.Props)\n\nconst checkDecisions = Effect.fnUntraced(function* (ids: ReadonlyArray<string>) {\n const states = Decision.fromEntries(yield* CurrentChat.use((chat) => chat.entries))\n const notWaiting = ids.filter((id) => states.get(id)?.outcome !== undefined || !states.has(id))\n\n if (notWaiting.length > 0) {\n return yield* ToolFailure.fail(\n `Not waiting for the user's decision: ${notWaiting.join(\", \")}. Use the ids of decisions that wait for one.`,\n { reason: \"NotWaitingForDecision\" }\n )\n }\n})\n\n// A decision block closes when the user decides on it, not because the model says so: the order it asks about would\n// otherwise never be approved.\nconst checkCloses = Effect.fnUntraced(function* (ids: ReadonlyArray<string>) {\n const entries = yield* CurrentChat.use((chat) => chat.entries)\n const states = BlockKit.fromEntries(entries)\n\n const waiting = ids.filter((id) =>\n entries.some(\n ({ event }) =>\n AssistantEvent.isLibraryEvent(event) &&\n Predicate.isTagged(event, \"AssistantMessage\") &&\n event.id === id &&\n event.block?.kind === Decision.kind &&\n states.get(id)?.open === true\n )\n )\n\n if (waiting.length > 0) {\n return yield* ToolFailure.fail(\n `${waiting.join(\", \")} asks for a decision that still waits for the user: only their answer in the block closes it. Send the message again without ${waiting.length === 1 ? \"that id\" : \"those ids\"} in closes.`,\n { reason: \"DecisionWaiting\" }\n )\n }\n})\n\nconst blockTools = Effect.fnUntraced(function* (kit: BlockKit.BlockKit) {\n const catalog = kit.blocks.map((block) => `- ${block.kind}: ${block.description}`).join(\"\\n\")\n const kinds = kit.blocks.map((block) => block.kind)\n\n const BlockMessage = Tool.make(sendUserMessageName, {\n description: `${description}\nYou can add a block to show UI with the message, e.g. a few options to pick from or a yes/no, when that makes answering easier than typing. Plain text is fine too. Using it sends you an <answer>; the user may also just type their answer instead, and then you pass that message's id in closes.\nBlocks:\n${catalog}`,\n // Props are checked against their kind's schema by the handler; the model sees every kind's in the parameters.\n parameters: Schema.Struct({\n text: Schema.String,\n wait: WaitFlag,\n block: Schema.optional(\n Schema.Struct({\n kind: Schema.Literals(kinds),\n props: Schema.Union(kit.blocks.map((block) => block.props))\n })\n ),\n closes: Schema.optional(Schema.Array(Schema.String)).annotate({\n description:\n \"Ids of your earlier messages whose block the user just answered by typing instead of using it. Those blocks close. Leave it out otherwise: a block stays open when the user talks about something else. A decision block never closes this way: only the user's answer in it decides.\"\n })\n }),\n success: Schema.String,\n failure: ToolFailure.ToolFailure,\n dependencies: [CurrentChat, AssistantToolCall]\n })\n\n const BlockTools = Toolkit.make(BlockMessage, Wait)\n\n const handlers = yield* BlockTools.toHandlers({\n wait: () => Effect.succeed(\"waiting\"),\n send_user_message: Effect.fnUntraced(function* ({ text, block, closes }) {\n if (text.trim() === \"\") {\n return yield* ToolFailure.fail(\n \"The message has no text: the user sees only what you send here, and a block alone is an empty bubble. Send it again with the words they should read (a short lead-in for the block).\",\n { reason: \"EmptyMessage\" }\n )\n }\n\n const id = yield* messageId\n const closed = closes === undefined || closes.length === 0 ? {} : { closes }\n\n if (closes !== undefined && closes.length > 0) yield* checkCloses(closes)\n\n if (block === undefined) {\n yield* append(AssistantEvent.AssistantMessage.make({ id, text, ...closed }))\n\n return \"sent\"\n }\n\n const props = kit.byKind.get(block.kind)?.props\n\n if (props === undefined || Option.isNone(Schema.decodeUnknownOption(props)(block.props))) {\n return yield* ToolFailure.fail(`Those props don't fit a ${block.kind} block.`)\n }\n\n if (block.kind === Decision.kind) {\n yield* checkDecisions(\n Option.match(decodeDecisionProps(block.props), {\n onNone: () => [],\n onSome: (decoded) => decoded.decisionIds\n })\n )\n }\n\n yield* append(\n AssistantEvent.AssistantMessage.make({\n id,\n text,\n block: AssistantEvent.MessageBlock.make(block),\n ...closed\n })\n )\n\n return `sent as message ${id}`\n })\n })\n\n return yield* Effect.provideContext(BlockTools, handlers)\n})\n\n/** @internal */\nexport const makeMessaging: (kit: BlockKit.BlockKit | undefined) => Effect.Effect<Toolkits.Tools<CurrentChat>> =\n Effect.fnUntraced(function* (kit) {\n if (kit === undefined || kit.blocks.length === 0) {\n return Toolkits.fromWithHandler(yield* plainTools)\n }\n\n return Toolkits.fromWithHandler(yield* blockTools(kit))\n })\n"],"mappings":"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"}
@@ -0,0 +1,64 @@
1
+ import { ChatId } from "../ChatId.js";
2
+ import { Tools, toolkit_d_exports } from "./toolkit.js";
3
+ import { AssistantLoop_d_exports, Event } from "../AssistantLoop.js";
4
+ import { CurrentChat } from "../CurrentChat.js";
5
+ import { BlockKit, BlockKit_d_exports } from "../BlockKit.js";
6
+ import { Any, Built, Specialist_d_exports } from "../Specialist.js";
7
+ import { AppEvents, DecodedEntries, events_d_exports } from "./events.js";
8
+ import { Writer } from "./writer.js";
9
+ import { Assistant_d_exports, Turn, TurnServices } from "../Assistant.js";
10
+ import * as Context from "effect/Context";
11
+ import * as Effect from "effect/Effect";
12
+ import * as Stream from "effect/Stream";
13
+ import * as Scope from "effect/Scope";
14
+ import * as LanguageModel from "effect/ai/LanguageModel";
15
+ declare namespace turns_d_exports {
16
+ export { Definition, Registration, TurnHandler, TypedChat, defaultReplyNudge, deliveryNudge, register, run, silentTurnMessage };
17
+ }
18
+ /** @internal */
19
+ export declare const silentTurnMessage = "I finished without sending a reply.";
20
+ /** @internal */
21
+ export declare const defaultReplyNudge = "You haven't replied to the user's latest message yet. Reply now with send_user_message.";
22
+ /** @internal */
23
+ export declare const deliveryNudge = "You told the user you're on it, but stopped without giving them the result. Send it now with send_user_message; if part of it is still running, say what and that you'll report back. If they already have everything, call wait.";
24
+ /** @internal */
25
+ export interface Definition {
26
+ readonly name: string;
27
+ readonly blocks: BlockKit | undefined;
28
+ readonly specialists: ReadonlyArray<Any>;
29
+ readonly app: AppEvents;
30
+ }
31
+ /** @internal */
32
+ export type TurnHandler = (turn: Turn<any>) => Stream.Stream<Event, unknown, TurnServices | LanguageModel.LanguageModel>;
33
+ /** @internal */
34
+ export interface TypedChat {
35
+ readonly chatId: ChatId;
36
+ readonly entries: Effect.Effect<DecodedEntries>;
37
+ readonly append: CurrentChat["Service"]["append"];
38
+ }
39
+ /** @internal */
40
+ export interface Registration {
41
+ readonly definition: Definition;
42
+ readonly model: Context.Context<LanguageModel.LanguageModel>;
43
+ readonly open: (chat: TypedChat) => Effect.Effect<TurnHandler, never, Scope.Scope | CurrentChat>;
44
+ readonly specialists: ReadonlyArray<{
45
+ readonly specialist: Any;
46
+ readonly built: Built;
47
+ }>;
48
+ readonly builtins: Tools<TurnServices>;
49
+ }
50
+ /** @internal */
51
+ export declare const register: (definition: Definition, options: {
52
+ readonly open: Registration["open"];
53
+ }) => Effect.Effect<Registration, never, LanguageModel.LanguageModel>;
54
+ /** @internal */
55
+ export declare const run: (options: {
56
+ readonly registration: Registration;
57
+ readonly handler: TurnHandler;
58
+ readonly chat: Writer;
59
+ readonly services: Context.Context<TurnServices | LanguageModel.LanguageModel>;
60
+ readonly checkOpen: Effect.Effect<void>;
61
+ }) => Effect.Effect<void, never, never>;
62
+ //#endregion
63
+ export { turns_d_exports };
64
+ //# sourceMappingURL=turns.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"turns.d.ts","names":[],"sources":["../../src/internal/turns.ts"],"mappings":";;;;;;;;;;;;;;;;;;qBAqCa;;qBAGA;;qBAIA;;iBAII;WACN;WACA,QAAQ;WACR,aAAa,cAAc;WAC3B,KAAK;;;YAKJ,eACV,MAAM,cACH,OAAO,OAAO,gBAA8B,eAAyB,cAAc;;iBAKvE;WACN,QAAQ;WACR,SAAS,OAAO,OAAO;WACvB,QAAQ;;;iBAIF;WACN,YAAY;WACZ,OAAO,QAAQ,QAAQ,cAAc;WACrC,OAAO,MAAM,cAAc,OAAO,OAAO,oBAAoB,MAAM,QAAQ;WAC3E,aAAa;aACX,YAAY;aACZ,OAAO;;WAET,UAAU,MAAe;;;qBAIvB,WAAQ,YAAA,YAAA;WAEO,MAAA;MAuC1B,OAAA,OAAA,qBAAA,cAAA;;qBA8OW,MAAG;WAEW,cAAA;WACL,SAAA;WACH,MAAA;WACI,UAAA,QAAQ,QAAQ,eAAyB,cAAc;WAEtD,WAAA,OAAO;MA2D9B,OAAA"}