effect-assistant 0.4.0 → 0.6.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 (102) hide show
  1. package/dist/Assistant.d.ts +10 -10
  2. package/dist/Assistant.d.ts.map +1 -1
  3. package/dist/Assistant.js +1 -1
  4. package/dist/Assistant.js.map +1 -1
  5. package/dist/AssistantActivity.d.ts +2 -2
  6. package/dist/AssistantActivity.d.ts.map +1 -1
  7. package/dist/AssistantActivity.js +5 -5
  8. package/dist/AssistantActivity.js.map +1 -1
  9. package/dist/AssistantEngine.d.ts +3 -3
  10. package/dist/AssistantEngine.js +2 -2
  11. package/dist/AssistantEngine.js.map +1 -1
  12. package/dist/AssistantEvent.d.ts +19 -8
  13. package/dist/AssistantEvent.d.ts.map +1 -1
  14. package/dist/AssistantEvent.js +20 -9
  15. package/dist/AssistantEvent.js.map +1 -1
  16. package/dist/AssistantLoop.d.ts +2 -2
  17. package/dist/AssistantLoop.js.map +1 -1
  18. package/dist/Block.d.ts +63 -1
  19. package/dist/Block.d.ts.map +1 -1
  20. package/dist/Block.js +63 -2
  21. package/dist/Block.js.map +1 -1
  22. package/dist/BlockKit.d.ts +25 -3
  23. package/dist/BlockKit.d.ts.map +1 -1
  24. package/dist/BlockKit.js +14 -12
  25. package/dist/BlockKit.js.map +1 -1
  26. package/dist/ChatLog.d.ts +9 -9
  27. package/dist/ChatLog.d.ts.map +1 -1
  28. package/dist/ChatLog.js +21 -21
  29. package/dist/ChatLog.js.map +1 -1
  30. package/dist/Compaction.d.ts +10 -10
  31. package/dist/Compaction.d.ts.map +1 -1
  32. package/dist/Compaction.js +19 -19
  33. package/dist/Compaction.js.map +1 -1
  34. package/dist/Memory.d.ts +1 -1
  35. package/dist/Memory.js +1 -1
  36. package/dist/Memory.js.map +1 -1
  37. package/dist/Skill.d.ts +1 -1
  38. package/dist/Skill.js +1 -1
  39. package/dist/Skill.js.map +1 -1
  40. package/dist/Specialist.d.ts +1 -1
  41. package/dist/Specialist.js +1 -1
  42. package/dist/Specialist.js.map +1 -1
  43. package/dist/Timeline.d.ts +13 -9
  44. package/dist/Timeline.d.ts.map +1 -1
  45. package/dist/Timeline.js +35 -23
  46. package/dist/Timeline.js.map +1 -1
  47. package/dist/ToolApproval.d.ts +1 -1
  48. package/dist/ToolApproval.js +1 -1
  49. package/dist/ToolApproval.js.map +1 -1
  50. package/dist/ToolDiscovery.d.ts +1 -1
  51. package/dist/ToolDiscovery.js +1 -1
  52. package/dist/ToolDiscovery.js.map +1 -1
  53. package/dist/ToolResult.d.ts +56 -0
  54. package/dist/ToolResult.d.ts.map +1 -0
  55. package/dist/ToolResult.js +49 -0
  56. package/dist/ToolResult.js.map +1 -0
  57. package/dist/index.d.ts +2 -1
  58. package/dist/index.js +4 -3
  59. package/dist/internal/chat.js +4 -4
  60. package/dist/internal/chat.js.map +1 -1
  61. package/dist/internal/events.js +1 -1
  62. package/dist/internal/events.js.map +1 -1
  63. package/dist/internal/history.js +23 -23
  64. package/dist/internal/history.js.map +1 -1
  65. package/dist/internal/loop.d.ts +1 -1
  66. package/dist/internal/loop.d.ts.map +1 -1
  67. package/dist/internal/loop.js +12 -6
  68. package/dist/internal/loop.js.map +1 -1
  69. package/dist/internal/toolkit.d.ts +7 -1
  70. package/dist/internal/toolkit.d.ts.map +1 -1
  71. package/dist/internal/toolkit.js +47 -4
  72. package/dist/internal/toolkit.js.map +1 -1
  73. package/dist/internal/tools.js +39 -6
  74. package/dist/internal/tools.js.map +1 -1
  75. package/dist/internal/turns.d.ts.map +1 -1
  76. package/dist/internal/turns.js +24 -21
  77. package/dist/internal/turns.js.map +1 -1
  78. package/package.json +1 -1
  79. package/src/Assistant.ts +11 -11
  80. package/src/AssistantActivity.ts +9 -7
  81. package/src/AssistantEngine.ts +3 -3
  82. package/src/AssistantEvent.ts +21 -10
  83. package/src/AssistantLoop.ts +3 -3
  84. package/src/Block.ts +82 -1
  85. package/src/BlockKit.ts +37 -4
  86. package/src/ChatLog.ts +31 -26
  87. package/src/Compaction.ts +21 -21
  88. package/src/Memory.ts +1 -1
  89. package/src/Skill.ts +1 -1
  90. package/src/Specialist.ts +1 -1
  91. package/src/Timeline.ts +38 -25
  92. package/src/ToolApproval.ts +1 -1
  93. package/src/ToolDiscovery.ts +1 -1
  94. package/src/ToolResult.ts +80 -0
  95. package/src/index.ts +5 -0
  96. package/src/internal/chat.ts +4 -4
  97. package/src/internal/events.ts +1 -1
  98. package/src/internal/history.ts +32 -28
  99. package/src/internal/loop.ts +24 -7
  100. package/src/internal/toolkit.ts +55 -5
  101. package/src/internal/tools.ts +81 -5
  102. package/src/internal/turns.ts +35 -29
@@ -1,6 +1,9 @@
1
+ import { fromKey } from "./uuid.js";
1
2
  import { toJson } from "./schema.js";
2
3
  import { AssistantToolCall } from "../AssistantToolCall.js";
3
4
  import { ToolFailure } from "../ToolFailure.js";
5
+ import { ModelView } from "../ToolResult.js";
6
+ import * as Context from "effect/Context";
4
7
  import * as Effect from "effect/Effect";
5
8
  import * as Predicate from "effect/Predicate";
6
9
  import * as Schema from "effect/Schema";
@@ -52,11 +55,23 @@ const runNative = Effect.fnUntraced(function* (toolkit, call) {
52
55
  return yield* Effect.gen(function* () {
53
56
  const results = yield* toolkit.handle(name, params, call.id);
54
57
  const last = yield* Stream.runLast(Stream.filter(results, (result) => !result.preliminary));
58
+ const view = Option.flatMap(Option.fromUndefinedOr(toolNamed(toolkit, call.name)), (tool) => Context.getOption(tool.annotations, ModelView));
55
59
  return Option.match(last, {
56
60
  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)
61
+ onSome: (result) => {
62
+ if (result.isFailure) return failure(toJson(result.encodedResult));
63
+ const full = toJson(result.encodedResult);
64
+ return Option.match(view, {
65
+ onNone: () => ({
66
+ outcome: "success",
67
+ result: full
68
+ }),
69
+ onSome: (read) => ({
70
+ outcome: "success",
71
+ result: toJson(read(result.result)),
72
+ full
73
+ })
74
+ });
60
75
  }
61
76
  });
62
77
  }).pipe(Effect.catch((error) => Effect.succeed(AiError.isAiError(error) ? failed(error.reason.message, error.reason._tag) : failure(toJson(error)))), Effect.provideService(AssistantToolCall, {
@@ -96,6 +111,34 @@ const merge = (self, that) => {
96
111
  };
97
112
  };
98
113
  /** @internal */
114
+ const resultIdOf = (callId) => `r-${fromKey(`result:${callId}`).slice(0, 8)}`;
115
+ const decodeJsonObject = Schema.decodeUnknownOption(Schema.Record(Schema.String, Schema.Json));
116
+ const withResultId = (result, resultId) => {
117
+ if (result.outcome === "failure") return result;
118
+ const read = result.result;
119
+ const fields = Array.isArray(read) ? Option.none() : decodeJsonObject(read);
120
+ return {
121
+ ...result,
122
+ result: Option.match(fields, {
123
+ onNone: () => ({
124
+ resultId,
125
+ result: read
126
+ }),
127
+ onSome: (object) => ({
128
+ resultId,
129
+ ...object
130
+ })
131
+ }),
132
+ full: result.full ?? read,
133
+ resultId
134
+ };
135
+ };
136
+ /** @internal */
137
+ const showing = (tools, names) => names.size === 0 ? tools : {
138
+ ...tools,
139
+ run: (call) => names.has(call.name) ? Effect.map(tools.run(call), (result) => withResultId(result, resultIdOf(call.id))) : tools.run(call)
140
+ };
141
+ /** @internal */
99
142
  const provide = (tools, context) => ({
100
143
  definitions: tools.definitions,
101
144
  run: (call) => Effect.provideContext(tools.run(call), context),
@@ -113,6 +156,6 @@ const runOnly = (tools, names, message) => ({
113
156
  needsApproval: () => Effect.succeed(false)
114
157
  });
115
158
  //#endregion
116
- export { duplicateNames, empty, failed, fromToolkit, fromToolkitOrEmpty, fromWithHandler, merge, needingApproval, provide, runNative, runOnly };
159
+ export { duplicateNames, empty, failed, fromToolkit, fromToolkitOrEmpty, fromWithHandler, merge, needingApproval, provide, resultIdOf, runNative, runOnly, showing };
117
160
 
118
161
  //# sourceMappingURL=toolkit.js.map
@@ -1 +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 // Whether the call waits for the user's approval instead of running: its tool's `needsApproval`.\n readonly needsApproval: (\n call: AssistantEvent.ToolCallPart,\n history: ReadonlyArray<Prompt.Message>\n ) => Effect.Effect<boolean, never, R>\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 ask `needsApproval`.\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/** @internal */\nexport const needingApproval = (definitions: ReadonlyArray<Tool.Any>): ReadonlyArray<string> =>\n definitions.flatMap((tool) => (tool.needsApproval === undefined || tool.needsApproval === false ? [] : [tool.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\n// Params that don't fit the tool don't wait: the call runs, and fails with why.\nconst needsApprovalNative = 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 const needs = tool?.needsApproval\n\n if (tool === undefined || needs === undefined || Predicate.isBoolean(needs)) return needs === true\n\n return yield* Effect.gen(function* () {\n const params = yield* Schema.decodeUnknownEffect(tool.parametersSchema)(call.params)\n const decided = needs(params, { toolCallId: call.id, messages: history })\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 needsApproval: (call, history) => needsApprovalNative(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 needsApproval: (call, history) => needsApprovalNative(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 needsApproval: () => Effect.succeed(false)\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 needsApproval: (call, history) =>\n inSelf(call) ? self.needsApproval(call, history) : that.needsApproval(call, history)\n }\n}\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 needsApproval: (call, history) => Effect.provideContext(tools.needsApproval(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 // Refused like any other call it doesn't run.\n needsApproval: () => Effect.succeed(false)\n})\n"],"mappings":"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"}
1
+ {"version":3,"file":"toolkit.js","names":["ToolFailure.ToolFailure","Uuid.fromKey"],"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 * 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 { ModelView } from \"../ToolResult.ts\"\nimport { toJson } from \"./schema.ts\"\nimport * as Uuid from \"./uuid.ts\"\n\n/** @internal */\nexport interface CallResult {\n readonly outcome: AssistantEvent.ToolOutcome\n readonly result: Schema.Json\n readonly reason?: string | undefined\n readonly full?: Schema.Json | undefined\n readonly resultId?: 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 // Whether the call waits for the user's approval instead of running: its tool's `needsApproval`.\n readonly needsApproval: (\n call: AssistantEvent.ToolCallPart,\n history: ReadonlyArray<Prompt.Message>\n ) => Effect.Effect<boolean, never, R>\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 ask `needsApproval`.\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/** @internal */\nexport const needingApproval = (definitions: ReadonlyArray<Tool.Any>): ReadonlyArray<string> =>\n definitions.flatMap((tool) => (tool.needsApproval === undefined || tool.needsApproval === false ? [] : [tool.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\n// Params that don't fit the tool don't wait: the call runs, and fails with why.\nconst needsApprovalNative = 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 const needs = tool?.needsApproval\n\n if (tool === undefined || needs === undefined || Predicate.isBoolean(needs)) return needs === true\n\n return yield* Effect.gen(function* () {\n const params = yield* Schema.decodeUnknownEffect(tool.parametersSchema)(call.params)\n const decided = needs(params, { toolCallId: call.id, messages: history })\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 const view = Option.flatMap(Option.fromUndefinedOr(toolNamed(toolkit, call.name)), (tool) =>\n Context.getOption(tool.annotations, ModelView)\n )\n\n return Option.match(last, {\n onNone: () => failed(`${call.name} returned no result.`, \"NoResult\"),\n onSome: (result): CallResult => {\n if (result.isFailure) return failure(toJson(result.encodedResult))\n\n const full = toJson(result.encodedResult)\n\n return Option.match(view, {\n onNone: () => ({ outcome: \"success\", result: full }),\n onSome: (read) => ({ outcome: \"success\", result: toJson(read(result.result)), full })\n })\n }\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 needsApproval: (call, history) => needsApprovalNative(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 needsApproval: (call, history) => needsApprovalNative(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 needsApproval: () => Effect.succeed(false)\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 needsApproval: (call, history) =>\n inSelf(call) ? self.needsApproval(call, history) : that.needsApproval(call, history)\n }\n}\n\n// Short, for the model to copy, and from the call: a call run again after a crash keeps its id.\n/** @internal */\nexport const resultIdOf = (callId: string): string => `r-${Uuid.fromKey(`result:${callId}`).slice(0, 8)}`\n\nconst decodeJsonObject = Schema.decodeUnknownOption(Schema.Record(Schema.String, Schema.Json))\n\nconst withResultId = (result: CallResult, resultId: string): CallResult => {\n if (result.outcome === \"failure\") return result\n\n const read = result.result\n const fields = Array.isArray(read) ? Option.none() : decodeJsonObject(read)\n\n return {\n ...result,\n result: Option.match(fields, {\n onNone: () => ({ resultId, result: read }),\n onSome: (object) => ({ resultId, ...object })\n }),\n full: result.full ?? read,\n resultId\n }\n}\n\n// The results of tools the assistant's blocks show get an id the model passes to show one.\n/** @internal */\nexport const showing = <R>(tools: Tools<R>, names: ReadonlySet<string>): Tools<R> =>\n names.size === 0\n ? tools\n : {\n ...tools,\n run: (call) =>\n names.has(call.name)\n ? Effect.map(tools.run(call), (result) => withResultId(result, resultIdOf(call.id)))\n : tools.run(call)\n }\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 needsApproval: (call, history) => Effect.provideContext(tools.needsApproval(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 // Refused like any other call it doesn't run.\n needsApproval: () => Effect.succeed(false)\n})\n"],"mappings":"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"}
@@ -4,6 +4,7 @@ import { CurrentChat } from "../CurrentChat.js";
4
4
  import { AssistantToolCall } from "../AssistantToolCall.js";
5
5
  import { ToolFailure, fail } from "../ToolFailure.js";
6
6
  import { fromWithHandler } from "./toolkit.js";
7
+ import { isToolResult } from "../Block.js";
7
8
  import * as Effect from "effect/Effect";
8
9
  import * as Predicate from "effect/Predicate";
9
10
  import * as Schema from "effect/Schema";
@@ -22,6 +23,9 @@ const Wait = Tool.make(waitToolName, {
22
23
  const description = "Send the user a chat message. It's the only way they see anything you write: text outside it is private to you.";
23
24
  const WaitFlag = Schema.Boolean.annotate({ description: "true when this is your last message for now: your turn ends with it, as with the wait tool. false when you keep working after it." });
24
25
  const endsWithMessage = Schema.decodeUnknownOption(Schema.Struct({ wait: Schema.Literal(true) }));
26
+ const messageText = Schema.decodeUnknownOption(Schema.Struct({ text: Schema.String }));
27
+ /** @internal */
28
+ const isUserMessage = (call) => call.name === "send_user_message" && Option.exists(messageText(call.params), ({ text }) => text.trim() !== "");
25
29
  /** @internal */
26
30
  const isTurnEnding = (call) => call.name === "wait" || call.name === "send_user_message" && Option.isSome(endsWithMessage(call.params));
27
31
  const messageId = AssistantToolCall.useSync((call) => fromKey(`message:${call.callId}`));
@@ -53,9 +57,22 @@ const plainHandlers = PlainTools.toHandlers({
53
57
  const plainTools = Effect.gen(function* () {
54
58
  return yield* Effect.provideContext(PlainTools, yield* plainHandlers);
55
59
  });
60
+ /** @internal */
61
+ const toolResultKind = "tool_result";
62
+ const ToolResultRef = Schema.Struct({ resultId: Schema.String.annotate({ description: "The resultId of the tool result to show." }) });
63
+ const decodeRef = Schema.decodeUnknownOption(ToolResultRef);
64
+ const shownResult = Effect.fnUntraced(function* (resultId, shown) {
65
+ const result = (yield* (yield* CurrentChat).entries).flatMap(({ event }) => isLibraryEvent(event) && Predicate.isTagged(event, "ToolResult") && event.resultId === resultId ? [event] : []).at(-1);
66
+ if (result === void 0 || result.outcome === "failure") return yield* fail(`No tool result ${resultId} to show: pass the resultId of a result you got in this chat.`, { reason: "ResultNotFound" });
67
+ if (!shown.has(result.tool)) return yield* fail(`${result.tool} results can't be shown.`, { reason: "ResultNotShown" });
68
+ return result;
69
+ });
56
70
  const blockTools = Effect.fnUntraced(function* (kit) {
57
- const catalog = kit.blocks.map((block) => `- ${block.kind}: ${block.description}`).join("\n");
58
- const kinds = kit.blocks.map((block) => block.kind);
71
+ const asked = kit.blocks.filter((block) => !isToolResult(block));
72
+ const shown = new Set(kit.blocks.flatMap((block) => isToolResult(block) ? [block.tool.name] : []));
73
+ const catalog = [...asked.map((block) => `- ${block.kind}: ${block.description}`), ...shown.size === 0 ? [] : [`- ${toolResultKind}: a tool's result, shown the way the app draws it (found items as cards, a download as a button). Results you can show have a resultId: pass it as the props. From: ${[...shown].join(", ")}.`]].join("\n");
74
+ const kinds = [...asked.map((block) => block.kind), ...shown.size === 0 ? [] : [toolResultKind]];
75
+ const props = [...asked.map((block) => block.props), ...shown.size === 0 ? [] : [ToolResultRef]];
59
76
  const BlockMessage = Tool.make(sendUserMessageName, {
60
77
  description: `${description}
61
78
  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.
@@ -66,7 +83,7 @@ ${catalog}`,
66
83
  wait: WaitFlag,
67
84
  block: Schema.optional(Schema.Struct({
68
85
  kind: Schema.Literals(kinds),
69
- props: Schema.Union(kit.blocks.map((block) => block.props))
86
+ props: Schema.Union(props)
70
87
  })),
71
88
  closes: Schema.optional(Schema.Array(Schema.String)).annotate({ description: "Ids of your earlier messages whose block the user just answered by typing instead of using it. Those blocks close. Leave it out otherwise: a block stays open when the user talks about something else." })
72
89
  }),
@@ -89,8 +106,24 @@ ${catalog}`,
89
106
  }));
90
107
  return "sent";
91
108
  }
92
- const props = kit.byKind.get(block.kind)?.props;
93
- if (props === void 0 || Option.isNone(Schema.decodeUnknownOption(props)(block.props))) return yield* fail(`Those props don't fit a ${block.kind} block.`);
109
+ if (block.kind === "tool_result") {
110
+ const ref = decodeRef(block.props);
111
+ if (Option.isNone(ref)) return yield* fail(`A ${toolResultKind} block's props are { resultId }.`);
112
+ const result = yield* shownResult(ref.value.resultId, shown);
113
+ yield* append(AssistantMessage.make({
114
+ id,
115
+ text,
116
+ block: MessageBlock.make({
117
+ kind: result.tool,
118
+ props: {},
119
+ resultId: ref.value.resultId
120
+ }),
121
+ ...closed
122
+ }));
123
+ return `sent as message ${id}`;
124
+ }
125
+ const schema = kit.byKind.get(block.kind)?.props;
126
+ if (schema === void 0 || Option.isNone(Schema.decodeUnknownOption(schema)(block.props))) return yield* fail(`Those props don't fit a ${block.kind} block.`);
94
127
  yield* append(AssistantMessage.make({
95
128
  id,
96
129
  text,
@@ -108,6 +141,6 @@ const makeMessaging = Effect.fnUntraced(function* (kit) {
108
141
  return fromWithHandler(yield* blockTools(kit));
109
142
  });
110
143
  //#endregion
111
- export { isTurnEnding, makeMessaging, sendUserMessageName, waitToolName };
144
+ export { isTurnEnding, isUserMessage, makeMessaging, sendUserMessageName, toolResultKind, waitToolName };
112
145
 
113
146
  //# sourceMappingURL=tools.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tools.js","names":["Uuid.fromKey","AssistantEvent.isLibraryEvent","ToolFailure.ToolFailure","ToolFailure.fail","Toolkits.fromWithHandler"],"sources":["../../src/internal/tools.ts"],"sourcesContent":["import * as Tool from \"effect/ai/Tool\"\nimport * as Toolkit from \"effect/ai/Toolkit\"\nimport * as Effect from \"effect/Effect\"\nimport * as Option from \"effect/Option\"\nimport * as Predicate from \"effect/Predicate\"\nimport * as Schema from \"effect/Schema\"\nimport * as AssistantEvent from \"../AssistantEvent.ts\"\nimport { AssistantToolCall } from \"../AssistantToolCall.ts\"\nimport type * as BlockKit from \"../BlockKit.ts\"\nimport { CurrentChat } from \"../CurrentChat.ts\"\nimport * as ToolFailure from \"../ToolFailure.ts\"\nimport * as Toolkits from \"./toolkit.ts\"\nimport * as Uuid from \"./uuid.ts\"\n\n/** @internal */\nexport const waitToolName = \"wait\"\n\n/** @internal */\nexport const sendUserMessageName = \"send_user_message\"\n\nconst Wait = Tool.make(waitToolName, {\n description:\n \"End your turn and wait for the next message or update, when you have nothing more to do right now and no message to send (otherwise set wait on send_user_message).\",\n success: Schema.String\n})\n\nconst description =\n \"Send the user a chat message. It's the only way they see anything you write: text outside it is private to you.\"\n\nconst WaitFlag = Schema.Boolean.annotate({\n description:\n \"true when this is your last message for now: your turn ends with it, as with the wait tool. false when you keep working after it.\"\n})\n\nconst endsWithMessage = Schema.decodeUnknownOption(Schema.Struct({ wait: Schema.Literal(true) }))\n\n/** @internal */\nexport const isTurnEnding = (call: AssistantEvent.ToolCallPart): boolean =>\n call.name === waitToolName || (call.name === sendUserMessageName && Option.isSome(endsWithMessage(call.params)))\n\n// From the call: a call cut off by a crash runs again with the same id, and finds its message already sent.\nconst messageId = AssistantToolCall.useSync((call) => Uuid.fromKey(`message:${call.callId}`))\n\nconst append = Effect.fnUntraced(function* (message: AssistantEvent.AssistantMessage) {\n const chat = yield* CurrentChat\n const entries = yield* chat.entries\n\n const sent = entries.some(\n ({ event }) =>\n AssistantEvent.isLibraryEvent(event) && Predicate.isTagged(event, \"AssistantMessage\") && event.id === message.id\n )\n\n if (!sent) yield* chat.append(message)\n})\n\nconst PlainMessage = Tool.make(sendUserMessageName, {\n description,\n parameters: Schema.Struct({ text: Schema.String, wait: WaitFlag }),\n success: Schema.String,\n failure: ToolFailure.ToolFailure,\n dependencies: [CurrentChat, AssistantToolCall]\n})\n\nconst PlainTools = Toolkit.make(PlainMessage, Wait)\n\nconst plainHandlers = PlainTools.toHandlers({\n wait: () => Effect.succeed(\"waiting\"),\n send_user_message: Effect.fnUntraced(function* ({ text }) {\n yield* append(AssistantEvent.AssistantMessage.make({ id: yield* messageId, text }))\n\n return \"sent\"\n })\n})\n\nconst plainTools = Effect.gen(function* () {\n return yield* Effect.provideContext(PlainTools, yield* plainHandlers)\n})\n\nconst blockTools = Effect.fnUntraced(function* (kit: BlockKit.BlockKit) {\n const catalog = kit.blocks.map((block) => `- ${block.kind}: ${block.description}`).join(\"\\n\")\n const kinds = kit.blocks.map((block) => block.kind)\n\n const BlockMessage = Tool.make(sendUserMessageName, {\n description: `${description}\nYou can add a block to show UI with the message, e.g. a few options to pick from or a yes/no, when that makes answering easier than typing. Plain text is fine too. Using it sends you an <answer>; the user may also just type their answer instead, and then you pass that message's id in closes.\nBlocks:\n${catalog}`,\n // Props are checked against their kind's schema by the handler; the model sees every kind's in the parameters.\n parameters: Schema.Struct({\n text: Schema.String,\n wait: WaitFlag,\n block: Schema.optional(\n Schema.Struct({\n kind: Schema.Literals(kinds),\n props: Schema.Union(kit.blocks.map((block) => block.props))\n })\n ),\n closes: Schema.optional(Schema.Array(Schema.String)).annotate({\n description:\n \"Ids of your earlier messages whose block the user just answered by typing instead of using it. Those blocks close. Leave it out otherwise: a block stays open when the user talks about something else.\"\n })\n }),\n success: Schema.String,\n failure: ToolFailure.ToolFailure,\n dependencies: [CurrentChat, AssistantToolCall]\n })\n\n const BlockTools = Toolkit.make(BlockMessage, Wait)\n\n const handlers = yield* BlockTools.toHandlers({\n wait: () => Effect.succeed(\"waiting\"),\n send_user_message: Effect.fnUntraced(function* ({ text, block, closes }) {\n if (text.trim() === \"\") {\n return yield* ToolFailure.fail(\n \"The message has no text: the user sees only what you send here, and a block alone is an empty bubble. Send it again with the words they should read (a short lead-in for the block).\",\n { reason: \"EmptyMessage\" }\n )\n }\n\n const id = yield* messageId\n const closed = closes === undefined || closes.length === 0 ? {} : { closes }\n\n if (block === undefined) {\n yield* append(AssistantEvent.AssistantMessage.make({ id, text, ...closed }))\n\n return \"sent\"\n }\n\n const props = kit.byKind.get(block.kind)?.props\n\n if (props === undefined || Option.isNone(Schema.decodeUnknownOption(props)(block.props))) {\n return yield* ToolFailure.fail(`Those props don't fit a ${block.kind} block.`)\n }\n\n yield* append(\n AssistantEvent.AssistantMessage.make({\n id,\n text,\n block: AssistantEvent.MessageBlock.make(block),\n ...closed\n })\n )\n\n return `sent as message ${id}`\n })\n })\n\n return yield* Effect.provideContext(BlockTools, handlers)\n})\n\n/** @internal */\nexport const makeMessaging: (kit: BlockKit.BlockKit | undefined) => Effect.Effect<Toolkits.Tools<CurrentChat>> =\n Effect.fnUntraced(function* (kit) {\n if (kit === undefined || kit.blocks.length === 0) {\n return Toolkits.fromWithHandler(yield* plainTools)\n }\n\n return Toolkits.fromWithHandler(yield* blockTools(kit))\n })\n"],"mappings":"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"}
1
+ {"version":3,"file":"tools.js","names":["Uuid.fromKey","AssistantEvent.isLibraryEvent","ToolFailure.ToolFailure","ToolFailure.fail","Block.isToolResult","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 Block from \"../Block.ts\"\nimport type * as BlockKit from \"../BlockKit.ts\"\nimport { CurrentChat } from \"../CurrentChat.ts\"\nimport * as ToolFailure from \"../ToolFailure.ts\"\nimport * as Toolkits from \"./toolkit.ts\"\nimport * as Uuid from \"./uuid.ts\"\n\n/** @internal */\nexport const waitToolName = \"wait\"\n\n/** @internal */\nexport const sendUserMessageName = \"send_user_message\"\n\nconst Wait = Tool.make(waitToolName, {\n description:\n \"End your turn and wait for the next message or update, when you have nothing more to do right now and no message to send (otherwise set wait on send_user_message).\",\n success: Schema.String\n})\n\nconst description =\n \"Send the user a chat message. It's the only way they see anything you write: text outside it is private to you.\"\n\nconst WaitFlag = Schema.Boolean.annotate({\n description:\n \"true when this is your last message for now: your turn ends with it, as with the wait tool. false when you keep working after it.\"\n})\n\nconst endsWithMessage = Schema.decodeUnknownOption(Schema.Struct({ wait: Schema.Literal(true) }))\n\nconst messageText = Schema.decodeUnknownOption(Schema.Struct({ text: Schema.String }))\n\n// A message the user reads: the call's text isn't empty.\n/** @internal */\nexport const isUserMessage = (call: AssistantEvent.ToolCallPart): boolean =>\n call.name === sendUserMessageName && Option.exists(messageText(call.params), ({ text }) => text.trim() !== \"\")\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\n/** @internal */\nexport const toolResultKind = \"tool_result\"\n\nconst ToolResultRef = Schema.Struct({\n resultId: Schema.String.annotate({ description: \"The resultId of the tool result to show.\" })\n})\n\nconst decodeRef = Schema.decodeUnknownOption(ToolResultRef)\n\n// The latest successful result with that id, of a tool the kit shows.\nconst shownResult = Effect.fnUntraced(function* (resultId: string, shown: ReadonlySet<string>) {\n const entries = yield* (yield* CurrentChat).entries\n\n const result = entries\n .flatMap(({ event }) =>\n AssistantEvent.isLibraryEvent(event) && Predicate.isTagged(event, \"ToolResult\") && event.resultId === resultId\n ? [event]\n : []\n )\n .at(-1)\n\n if (result === undefined || result.outcome === \"failure\") {\n return yield* ToolFailure.fail(\n `No tool result ${resultId} to show: pass the resultId of a result you got in this chat.`,\n { reason: \"ResultNotFound\" }\n )\n }\n\n if (!shown.has(result.tool)) {\n return yield* ToolFailure.fail(`${result.tool} results can't be shown.`, { reason: \"ResultNotShown\" })\n }\n\n return result\n})\n\nconst blockTools = Effect.fnUntraced(function* (kit: BlockKit.BlockKit) {\n const asked = kit.blocks.filter((block) => !Block.isToolResult(block))\n const shown = new Set(kit.blocks.flatMap((block) => (Block.isToolResult(block) ? [block.tool.name] : [])))\n\n const catalog = [\n ...asked.map((block) => `- ${block.kind}: ${block.description}`),\n ...(shown.size === 0\n ? []\n : [\n `- ${toolResultKind}: a tool's result, shown the way the app draws it (found items as cards, a download as a button). Results you can show have a resultId: pass it as the props. From: ${[...shown].join(\", \")}.`\n ])\n ].join(\"\\n\")\n\n const kinds = [...asked.map((block) => block.kind), ...(shown.size === 0 ? [] : [toolResultKind])]\n const props = [...asked.map((block) => block.props), ...(shown.size === 0 ? [] : [ToolResultRef])]\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(props)\n })\n ),\n closes: Schema.optional(Schema.Array(Schema.String)).annotate({\n description:\n \"Ids of your earlier messages whose block the user just answered by typing instead of using it. Those blocks close. Leave it out otherwise: a block stays open when the user talks about something else.\"\n })\n }),\n success: Schema.String,\n failure: ToolFailure.ToolFailure,\n dependencies: [CurrentChat, AssistantToolCall]\n })\n\n const BlockTools = Toolkit.make(BlockMessage, Wait)\n\n const handlers = yield* BlockTools.toHandlers({\n wait: () => Effect.succeed(\"waiting\"),\n send_user_message: Effect.fnUntraced(function* ({ text, block, closes }) {\n if (text.trim() === \"\") {\n return yield* ToolFailure.fail(\n \"The message has no text: the user sees only what you send here, and a block alone is an empty bubble. Send it again with the words they should read (a short lead-in for the block).\",\n { reason: \"EmptyMessage\" }\n )\n }\n\n const id = yield* messageId\n const closed = closes === undefined || closes.length === 0 ? {} : { closes }\n\n if (block === undefined) {\n yield* append(AssistantEvent.AssistantMessage.make({ id, text, ...closed }))\n\n return \"sent\"\n }\n\n if (block.kind === toolResultKind) {\n const ref = decodeRef(block.props)\n\n if (Option.isNone(ref)) {\n return yield* ToolFailure.fail(`A ${toolResultKind} block's props are { resultId }.`)\n }\n\n const result = yield* shownResult(ref.value.resultId, shown)\n\n yield* append(\n AssistantEvent.AssistantMessage.make({\n id,\n text,\n block: AssistantEvent.MessageBlock.make({ kind: result.tool, props: {}, resultId: ref.value.resultId }),\n ...closed\n })\n )\n\n return `sent as message ${id}`\n }\n\n const schema = kit.byKind.get(block.kind)?.props\n\n if (schema === undefined || Option.isNone(Schema.decodeUnknownOption(schema)(block.props))) {\n return yield* ToolFailure.fail(`Those props don't fit a ${block.kind} block.`)\n }\n\n yield* append(\n AssistantEvent.AssistantMessage.make({\n id,\n text,\n block: AssistantEvent.MessageBlock.make(block),\n ...closed\n })\n )\n\n return `sent as message ${id}`\n })\n })\n\n return yield* Effect.provideContext(BlockTools, handlers)\n})\n\n/** @internal */\nexport const makeMessaging: (kit: BlockKit.BlockKit | undefined) => Effect.Effect<Toolkits.Tools<CurrentChat>> =\n Effect.fnUntraced(function* (kit) {\n if (kit === undefined || kit.blocks.length === 0) {\n return Toolkits.fromWithHandler(yield* plainTools)\n }\n\n return Toolkits.fromWithHandler(yield* blockTools(kit))\n })\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"turns.d.ts","names":[],"sources":["../../src/internal/turns.ts"],"mappings":";;;;;;;;;;;;;;;;;;qBAmCa;;qBAGA;;qBAIA;;iBAII;WACN;WACA,QAAQ;WACR,aAAa,cAAc;WAC3B,KAAK;;;YAKJ,eACV,MAAM,cACH,OAAO,OAAO,gBAA8B,eAAyB,cAAc;;iBAKvE;WACN,QAAQ;WACR,SAAS,OAAO,OAAO;WACvB,QAAQ;;;iBAIF;WACN,YAAY;WACZ,OAAO,QAAQ,QAAQ,cAAc;WACrC,OAAO,MAAM,cAAc,OAAO,OAAO,oBAAoB,MAAM,QAAQ;WAC3E,aAAa;aACX,YAAY;aACZ,OAAO;;WAET,UAAU,MAAe;;;qBAIvB,WAAQ,YAAA,YAAA;WAEO,MAAA;MAiC1B,OAAA,OAAA,qBAAA,cAAA;;qBA0NW,MAAG;WAEW,cAAA;WACL,SAAA;WACH,MAAA;WACI,UAAA,QAAQ,QAAQ,eAAyB,cAAc;WAEtD,WAAA,OAAO;MA8D9B,OAAA"}
1
+ {"version":3,"file":"turns.d.ts","names":[],"sources":["../../src/internal/turns.ts"],"mappings":";;;;;;;;;;;;;;;;;;qBAmCa;;qBAGA;;qBAIA;;iBAII;WACN;WACA,QAAQ;WACR,aAAa,cAAc;WAC3B,KAAK;;;YAKJ,eACV,MAAM,cACH,OAAO,OAAO,gBAA8B,eAAyB,cAAc;;iBAKvE;WACN,QAAQ;WACR,SAAS,OAAO,OAAO;WACvB,QAAQ;;;iBAIF;WACN,YAAY;WACZ,OAAO,QAAQ,QAAQ,cAAc;WACrC,OAAO,MAAM,cAAc,OAAO,OAAO,oBAAoB,MAAM,QAAQ;WAC3E,aAAa;aACX,YAAY;aACZ,OAAO;;WAET,UAAU,MAAe;;;qBAIvB,WAAQ,YAAA,YAAA;WAEO,MAAA;MAiC1B,OAAA,OAAA,qBAAA,cAAA;;qBA+NW,MAAG;WAEW,cAAA;WACL,SAAA;WACH,MAAA;WACI,UAAA,QAAQ,QAAQ,eAAyB,cAAc;WAEtD,WAAA,OAAO;MA+D9B,OAAA"}
@@ -4,10 +4,11 @@ import { decodeEntries } from "./events.js";
4
4
  import { toJsonText } from "./schema.js";
5
5
  import { fromEntries as fromEntries$1, mayHaveRunOutput, notRunOutput, owesDelivery, owesReply, unansweredCalls, unhandledInputs, unreadAfter, userText } from "./history.js";
6
6
  import { agentInput, agentOutput, agentSpan, currentTraceId } from "./telemetry.js";
7
- import { duplicateNames, fromToolkitOrEmpty, merge, provide, runOnly } from "./toolkit.js";
7
+ import { duplicateNames, fromToolkitOrEmpty, merge, provide, runOnly, showing } from "./toolkit.js";
8
8
  import { makeMessaging, sendUserMessageName, waitToolName } from "./tools.js";
9
9
  import { TurnApprovals, TurnTools, run as run$1 } from "./loop.js";
10
10
  import { toolkit } from "./specialists.js";
11
+ import { toolNames } from "../BlockKit.js";
11
12
  import { protocol } from "../Instructions.js";
12
13
  import { mapText } from "./instructions.js";
13
14
  import "effect/Context";
@@ -62,9 +63,9 @@ const approvedCalls = (entries) => [...fromEntries(entries).values()].flatMap(({
62
63
  params: request.params,
63
64
  providerExecuted: false
64
65
  })] : []);
65
- const lastSeq = (entries, otherwise) => entries.at(-1)?.seq ?? otherwise;
66
- const closeOpenCalls = Effect.fnUntraced(function* (chat, startSeq) {
67
- const entries = (yield* chat.entries).filter(({ seq }) => seq > startSeq);
66
+ const lastSequence = (entries, otherwise) => entries.at(-1)?.sequence ?? otherwise;
67
+ const closeOpenCalls = Effect.fnUntraced(function* (chat, startSequence) {
68
+ const entries = (yield* chat.entries).filter(({ sequence }) => sequence > startSequence);
68
69
  const started = new Set(entries.flatMap(({ event }) => Predicate.isTagged(event, "ToolStarted") ? [event.callId] : []));
69
70
  const open = unansweredCalls(fromEntries$1(entries));
70
71
  if (open.length === 0) return;
@@ -81,39 +82,40 @@ const closeOpenCalls = Effect.fnUntraced(function* (chat, startSeq) {
81
82
  }
82
83
  })));
83
84
  });
84
- const handledThrough = (entries, startSeq) => entries.reduce((through, { seq, event }) => seq > startSeq && isLibraryEvent(event) && Predicate.isTagged(event, "ModelOutput") && event.seenThroughSeq !== void 0 ? Math.max(through, event.seenThroughSeq) : through, startSeq);
85
+ const handledThrough = (entries, startSequence) => entries.reduce((through, { sequence, event }) => sequence > startSequence && isLibraryEvent(event) && Predicate.isTagged(event, "ModelOutput") && event.seenThroughSequence !== void 0 ? Math.max(through, event.seenThroughSequence) : through, startSequence);
85
86
  const finishTurn = Effect.fnUntraced(function* (chat, read, ranLoop) {
86
87
  const entries = yield* chat.entries;
87
88
  const end = ranLoop && owesReply(entries, read) && !unreadAfter(entries, read) ? TurnFailed.make({
88
- throughSeq: read,
89
+ throughSequence: read,
89
90
  message: silentTurnMessage
90
- }) : TurnCompleted.make({ throughSeq: read });
91
+ }) : TurnCompleted.make({ throughSequence: read });
91
92
  yield* chat.append([end]);
92
93
  });
93
94
  const failTurn = Effect.fnUntraced(function* (chat, cause) {
94
95
  yield* Effect.logError("assistant turn failed", cause);
95
- const last = lastSeq(yield* chat.entries, 0);
96
+ const last = lastSequence(yield* chat.entries, 0);
96
97
  yield* chat.append([TurnFailed.make({
97
- throughSeq: last,
98
+ throughSequence: last,
98
99
  message: failureMessage(cause)
99
100
  })]);
100
101
  });
101
- const makeTurnApi = (registration, chat, startSeq, checkOpen, ranLoop) => {
102
+ const makeTurnApi = (registration, chat, startSequence, checkOpen, ranLoop) => {
102
103
  const { definition, builtins } = registration;
103
104
  const { app } = definition;
105
+ const shownTools = toolNames(definition.blocks);
104
106
  const running = (stream) => Stream.unwrap(Ref.set(ranLoop, true).pipe(Effect.as(stream)));
105
107
  const history = Effect.fnUntraced(function* (view = identity) {
106
108
  yield* checkOpen;
107
109
  const all = yield* chat.entries;
108
110
  return {
109
111
  messages: fromEntries$1(view(decodeEntries(app, all))),
110
- throughSeq: lastSeq(all, startSeq)
112
+ throughSequence: lastSequence(all, startSequence)
111
113
  };
112
114
  });
113
115
  const instructions = (given) => mapText(given, (text) => `${protocol}\n\n${text}`);
114
116
  const replyPass = (options) => Stream.unwrap(Effect.gen(function* () {
115
117
  const all = yield* chat.entries;
116
- const through = handledThrough(all, startSeq);
118
+ const through = handledThrough(all, startSequence);
117
119
  if (unreadAfter(all, through)) return Stream.empty;
118
120
  const owed = owesReply(all, through);
119
121
  const undelivered = !owed && owesDelivery(all, through);
@@ -127,10 +129,11 @@ const makeTurnApi = (registration, chat, startSeq, checkOpen, ranLoop) => {
127
129
  const entries = yield* chat.entries;
128
130
  return {
129
131
  messages: [...fromEntries$1(options.view(decodeEntries(app, entries))), userText(nudge)],
130
- throughSeq: lastSeq(entries, startSeq)
132
+ throughSequence: lastSequence(entries, startSequence)
131
133
  };
132
134
  }),
133
- maxSteps: 2
135
+ maxSteps: 2,
136
+ toolChoice: "required"
134
137
  }).pipe(Stream.provideService(TurnApprovals, Option.none()));
135
138
  }));
136
139
  const replyIfOwed = (options) => running(replyPass({
@@ -140,7 +143,7 @@ const makeTurnApi = (registration, chat, startSeq, checkOpen, ranLoop) => {
140
143
  nudge: options.nudge
141
144
  }));
142
145
  const prepare = (options) => {
143
- const own = fromToolkitOrEmpty(options.toolkit);
146
+ const own = showing(fromToolkitOrEmpty(options.toolkit), shownTools);
144
147
  return {
145
148
  system: instructions(options.instructions),
146
149
  toolkit: merge(builtins, own),
@@ -152,7 +155,7 @@ const makeTurnApi = (registration, chat, startSeq, checkOpen, ranLoop) => {
152
155
  history,
153
156
  instructions,
154
157
  replyIfOwed,
155
- loop: (options) => {
158
+ run: (options) => {
156
159
  const { system, toolkit, view } = prepare(options);
157
160
  const twice = duplicateNames(toolkit);
158
161
  const loop = twice.length > 0 ? Stream.die(`Tool names used twice, like one named after a built-in: ${twice.join(", ")}`) : run$1({
@@ -184,18 +187,18 @@ const run = Effect.fnUntraced(function* (options) {
184
187
  traceId: id
185
188
  })
186
189
  }))]);
187
- const startSeq = lastSeq(started, 0);
190
+ const startSequence = lastSequence(started, 0);
188
191
  const ranLoop = yield* Ref.make(false);
189
- const turn = makeTurnApi(options.registration, chat, startSeq, options.checkOpen, ranLoop);
192
+ const turn = makeTurnApi(options.registration, chat, startSequence, options.checkOpen, ranLoop);
190
193
  const record = (event) => chat.append([event]);
191
- yield* options.handler(turn).pipe(Stream.runForEach(record), Effect.onExit((exit) => Exit.hasInterrupts(exit) ? Effect.void : closeOpenCalls(chat, startSeq)), Effect.provideService(TurnTools, Option.some(provide(options.registration.builtins, options.services))), Effect.provideService(TurnApprovals, Option.some({ approved: Effect.map(chat.entries, approvedCalls) })), Effect.provideContext(options.services), Effect.matchCauseEffect({
194
+ yield* options.handler(turn).pipe(Stream.runForEach(record), Effect.onExit((exit) => Exit.hasInterrupts(exit) ? Effect.void : closeOpenCalls(chat, startSequence)), Effect.provideService(TurnTools, Option.some(provide(options.registration.builtins, options.services))), Effect.provideService(TurnApprovals, Option.some({ approved: Effect.map(chat.entries, approvedCalls) })), Effect.provideContext(options.services), Effect.matchCauseEffect({
192
195
  onSuccess: Effect.fnUntraced(function* () {
193
196
  const all = yield* chat.entries;
194
- yield* finishTurn(chat, handledThrough(all, startSeq), yield* Ref.get(ranLoop));
197
+ yield* finishTurn(chat, handledThrough(all, startSequence), yield* Ref.get(ranLoop));
195
198
  }),
196
199
  onFailure: (cause) => failTurn(chat, cause)
197
200
  }));
198
- const shown = (yield* chat.entries).flatMap(({ event, seq }) => seq > startSeq && (Predicate.isTagged(event, "AssistantMessage") || Predicate.isTagged(event, "TurnFailed")) ? [event] : []);
201
+ const shown = (yield* chat.entries).flatMap(({ event, sequence }) => sequence > startSequence && (Predicate.isTagged(event, "AssistantMessage") || Predicate.isTagged(event, "TurnFailed")) ? [event] : []);
199
202
  yield* agentOutput(shown);
200
203
  }, (effect, options) => effect.pipe(agentSpan(options.registration.definition.name, {
201
204
  chatId: options.chat.chatId,
@@ -1 +1 @@
1
- {"version":3,"file":"turns.js","names":["Toolkits.merge","Tools.makeMessaging","Specialists.toolkit","ToolApproval.fromEntries","History.unansweredCalls","History.fromEntries","History.mayHaveRunOutput","History.notRunOutput","AssistantEvent.isLibraryEvent","History.owesReply","History.unreadAfter","AppEvents.decodeEntries","Instructions.protocol","History.owesDelivery","Loop.run","Toolkits.runOnly","Tools.sendUserMessageName","Tools.waitToolName","History.userText","Loop.TurnApprovals","Toolkits.fromToolkitOrEmpty","Toolkits.duplicateNames","Telemetry.agentInput","History.unhandledInputs","Telemetry.currentTraceId","Loop.TurnTools","Toolkits.provide","Telemetry.agentOutput","Telemetry.agentSpan"],"sources":["../../src/internal/turns.ts"],"sourcesContent":["import type * as LanguageModel from \"effect/ai/LanguageModel\"\nimport * as Response from \"effect/ai/Response\"\nimport type * as Tool from \"effect/ai/Tool\"\nimport * as Cause from \"effect/Cause\"\nimport * as Context from \"effect/Context\"\nimport * as Effect from \"effect/Effect\"\nimport * as Exit from \"effect/Exit\"\nimport { identity } from \"effect/Function\"\nimport * as Option from \"effect/Option\"\nimport * as Predicate from \"effect/Predicate\"\nimport * as Ref from \"effect/Ref\"\nimport * as Schema from \"effect/Schema\"\nimport type * as Scope from \"effect/Scope\"\nimport * as Stream from \"effect/Stream\"\nimport type * as Assistant from \"../Assistant.ts\"\nimport type { ChatId } from \"../ChatId.ts\"\nimport * as AssistantEvent from \"../AssistantEvent.ts\"\nimport type * as AssistantLoop from \"../AssistantLoop.ts\"\nimport * as Loop from \"./loop.ts\"\nimport type * as BlockKit from \"../BlockKit.ts\"\nimport type { CurrentChat } from \"../CurrentChat.ts\"\nimport * as Instructions from \"../Instructions.ts\"\nimport * as ToolApproval from \"../ToolApproval.ts\"\nimport type * as Specialist from \"../Specialist.ts\"\nimport * as AppEvents from \"./events.ts\"\nimport * as History from \"./history.ts\"\nimport { mapText } from \"./instructions.ts\"\nimport { toJsonText } from \"./schema.ts\"\nimport * as Specialists from \"./specialists.ts\"\nimport * as Telemetry from \"./telemetry.ts\"\nimport * as Toolkits from \"./toolkit.ts\"\nimport type { Writer } from \"./writer.ts\"\nimport * as Tools from \"./tools.ts\"\n\n/** @internal */\nexport const silentTurnMessage = \"I finished without sending a reply.\"\n\n/** @internal */\nexport const defaultReplyNudge =\n \"You haven't replied to the user's latest message yet. Reply now with send_user_message.\"\n\n/** @internal */\nexport const deliveryNudge =\n \"You told the user you're on it, but stopped without giving them the result. Send it now with send_user_message; if part of it is still running, say what and that you'll report back. If they already have everything, call wait.\"\n\n/** @internal */\nexport interface Definition {\n readonly name: string\n readonly blocks: BlockKit.BlockKit | undefined\n readonly specialists: ReadonlyArray<Specialist.Any>\n readonly app: AppEvents.AppEvents\n}\n\n// Once its build ran the layer's services are provided, so its streams need only what a turn provides.\n/** @internal */\nexport type TurnHandler = (\n turn: Assistant.Turn<any>\n) => Stream.Stream<AssistantLoop.Event, unknown, Assistant.TurnServices | LanguageModel.LanguageModel>\n\n// The chat `CurrentChat` gives, with its entries typed as the assistant's decode returns them: the assistant's typed\n// chat is this one, so it needs no check of its own.\n/** @internal */\nexport interface TypedChat {\n readonly chatId: ChatId\n readonly entries: Effect.Effect<AppEvents.DecodedEntries>\n readonly append: CurrentChat[\"Service\"][\"append\"]\n}\n\n/** @internal */\nexport interface Registration {\n readonly definition: Definition\n readonly model: Context.Context<LanguageModel.LanguageModel>\n readonly open: (chat: TypedChat) => Effect.Effect<TurnHandler, never, Scope.Scope | CurrentChat>\n readonly specialists: ReadonlyArray<{\n readonly specialist: Specialist.Any\n readonly built: Specialist.Built\n }>\n readonly builtins: Toolkits.Tools<Assistant.TurnServices>\n}\n\n/** @internal */\nexport const register = Effect.fnUntraced(function* (\n definition: Definition,\n options: { readonly open: Registration[\"open\"] }\n) {\n const model = yield* Effect.context<LanguageModel.LanguageModel>()\n\n const specialists = yield* Effect.forEach(\n definition.specialists,\n Effect.fnUntraced(function* (specialist) {\n const built = yield* Effect.serviceOption(specialist.Handler)\n\n if (Option.isNone(built)) {\n return yield* Effect.die(\n `No handler for the ${specialist.name} specialist: provide ${specialist.name}.toLayer.`\n )\n }\n\n return { specialist, built: built.value }\n })\n )\n\n const builtins = Toolkits.merge(\n yield* Tools.makeMessaging(definition.blocks),\n yield* Specialists.toolkit(definition.specialists)\n )\n\n const registration: Registration = {\n definition,\n model,\n open: options.open,\n specialists,\n builtins\n }\n\n return registration\n})\n\nconst ProviderError = Schema.Struct({ _tag: Schema.optional(Schema.String), raw: Schema.Unknown })\n\n// Provider errors keep their response body.\nconst failureMessage = (cause: Cause.Cause<unknown>): string =>\n Option.match(Schema.decodeUnknownOption(ProviderError)(Cause.squash(cause)), {\n onNone: () => Cause.pretty(cause),\n onSome: ({ _tag, raw }) => `${_tag ?? \"Error\"}: ${Predicate.isString(raw) ? raw : toJsonText(raw)}`\n })\n\n// Approved, and not run yet: also one a crash cut off while it ran, which runs again with the same id.\nconst approvedCalls = (entries: ReadonlyArray<AssistantEvent.AnyEntry>): ReadonlyArray<AssistantEvent.ToolCallPart> =>\n [...ToolApproval.fromEntries(entries).values()].flatMap(({ request, status, outcome }) =>\n status === \"approved\" && outcome === undefined\n ? [\n Response.makePart(\"tool-call\", {\n id: request.callId,\n name: request.tool,\n params: request.params,\n providerExecuted: false\n })\n ]\n : []\n )\n\nconst lastSeq = (entries: ReadonlyArray<AssistantEvent.AnyEntry>, otherwise: number) => entries.at(-1)?.seq ?? otherwise\n\n// The handler's stream can end between a step's output and its tools, or while they run. An interrupted turn (a\n// shutdown) leaves its calls open instead: the next turn runs them again.\nconst closeOpenCalls = Effect.fnUntraced(function* (chat: Writer, startSeq: number) {\n const entries = (yield* chat.entries).filter(({ seq }) => seq > startSeq)\n\n const started = new Set(\n entries.flatMap(({ event }) => (Predicate.isTagged(event, \"ToolStarted\") ? [event.callId] : []))\n )\n\n const open = History.unansweredCalls(History.fromEntries(entries))\n\n if (open.length === 0) return\n\n yield* chat.append(\n open.map((call) =>\n AssistantEvent.ToolResult.make({\n callId: call.id,\n tool: call.name,\n outcome: \"failure\",\n ...(started.has(call.id)\n ? { result: History.mayHaveRunOutput, reason: \"MayHaveRun\" }\n : { result: History.notRunOutput, reason: \"NotRun\" })\n })\n )\n )\n})\n\n// The inputs before the turn's `TurnStarted` (at `startSeq`) woke it; past that, what its model outputs record\n// reading.\nconst handledThrough = (entries: ReadonlyArray<AssistantEvent.AnyEntry>, startSeq: number) =>\n entries.reduce(\n (through, { seq, event }) =>\n seq > startSeq &&\n AssistantEvent.isLibraryEvent(event) &&\n Predicate.isTagged(event, \"ModelOutput\") &&\n event.seenThroughSeq !== undefined\n ? Math.max(through, event.seenThroughSeq)\n : through,\n startSeq\n )\n\n// A turn that ran the loop fails visibly when the user's latest input got no reply; an input that arrived after what\n// its model read gets the next turn, which replies. A handler that ended without the loop decided that itself.\nconst finishTurn = Effect.fnUntraced(function* (chat: Writer, read: number, ranLoop: boolean) {\n const entries = yield* chat.entries\n\n const end =\n ranLoop && History.owesReply(entries, read) && !History.unreadAfter(entries, read)\n ? AssistantEvent.TurnFailed.make({ throughSeq: read, message: silentTurnMessage })\n : AssistantEvent.TurnCompleted.make({ throughSeq: read })\n\n yield* chat.append([end])\n})\n\n// Everything stored so far counts as read, so a failing input isn't retried forever.\nconst failTurn = Effect.fnUntraced(function* (chat: Writer, cause: Cause.Cause<unknown>) {\n yield* Effect.logError(\"assistant turn failed\", cause)\n\n const last = lastSeq(yield* chat.entries, 0)\n\n yield* chat.append([AssistantEvent.TurnFailed.make({ throughSeq: last, message: failureMessage(cause) })])\n})\n\nconst makeTurnApi = (\n registration: Registration,\n chat: Writer,\n startSeq: number,\n checkOpen: Effect.Effect<void>,\n ranLoop: Ref.Ref<boolean>\n): Assistant.Turn<any> => {\n const { definition, builtins } = registration\n const { app } = definition\n\n const running = <A, E, R>(stream: Stream.Stream<A, E, R>) =>\n Stream.unwrap(Ref.set(ranLoop, true).pipe(Effect.as(stream)))\n\n // With how far into the chat it goes: the step records it on the model's output.\n // Read before every model call: a chat the app deleted meanwhile stops here.\n const history = Effect.fnUntraced(function* (view: Assistant.HistoryView<any> = identity) {\n yield* checkOpen\n\n const all = yield* chat.entries\n\n const read: AssistantLoop.ChatHistory = {\n messages: History.fromEntries(view(AppEvents.decodeEntries(app, all))),\n throughSeq: lastSeq(all, startSeq)\n }\n\n return read\n })\n\n const instructions = (given: string | Instructions.Resolved) =>\n mapText(given, (text) => `${Instructions.protocol}\\n\\n${text}`)\n\n // The loop's own prompt and tools, so the prompt stays cached, with the nudge at the end.\n const replyPass = <R>(options: {\n readonly instructions: Instructions.Resolved\n readonly toolkit: Toolkits.Tools<R>\n readonly view: Assistant.HistoryView<any>\n readonly nudge: string | undefined\n }) =>\n Stream.unwrap(\n Effect.gen(function* () {\n const all = yield* chat.entries\n const through = handledThrough(all, startSeq)\n\n // Something arrived after what the loop's model read: the next turn reads it with the full loop, and replies.\n if (History.unreadAfter(all, through)) return Stream.empty\n\n const owed = History.owesReply(all, through)\n const undelivered = !owed && History.owesDelivery(all, through)\n\n if (!owed && !undelivered) return Stream.empty\n\n const nudge = undelivered ? deliveryNudge : (options.nudge ?? defaultReplyNudge)\n\n return Loop.run({\n instructions: options.instructions,\n // A reply that's owed can only be sent; a result it may have given may also be for nobody.\n toolkit: Toolkits.runOnly(\n options.toolkit,\n owed ? [Tools.sendUserMessageName] : [Tools.sendUserMessageName, Tools.waitToolName],\n \"Not now: reply to the user with send_user_message.\"\n ),\n history: Effect.gen(function* () {\n yield* checkOpen\n\n const entries = yield* chat.entries\n\n return {\n messages: [\n ...History.fromEntries(options.view(AppEvents.decodeEntries(app, entries))),\n History.userText(nudge)\n ],\n throughSeq: lastSeq(entries, startSeq)\n }\n }),\n // A second step, for a reply after a call it was refused.\n maxSteps: 2\n // The loop ran the approved calls; a pass that can only reply runs none.\n }).pipe(Stream.provideService(Loop.TurnApprovals, Option.none()))\n })\n )\n\n const replyIfOwed = (options: Assistant.ReplyOptions) =>\n running(\n replyPass({\n instructions: instructions(options.instructions),\n toolkit: builtins,\n view: identity,\n nudge: options.nudge\n })\n )\n\n const prepare = <T extends Record<string, Tool.Any>>(options: Assistant.LoopOptions<T, any>) => {\n const own = Toolkits.fromToolkitOrEmpty(options.toolkit)\n\n return {\n system: instructions(options.instructions),\n toolkit: Toolkits.merge(builtins, own),\n view: options.history ?? identity\n }\n }\n\n return {\n chatId: chat.chatId,\n history,\n instructions,\n replyIfOwed,\n loop: (options) => {\n const { system, toolkit, view } = prepare(options)\n const twice = Toolkits.duplicateNames(toolkit)\n\n const loop =\n twice.length > 0\n ? Stream.die(`Tool names used twice, like one named after a built-in: ${twice.join(\", \")}`)\n : Loop.run({\n instructions: system,\n toolkit,\n history: history(view),\n maxSteps: options.maxSteps ?? 16\n })\n\n return running(Stream.concat(loop, replyPass({ instructions: system, toolkit, view, nudge: options.replyNudge })))\n }\n }\n}\n\n// Every event the handler's loop emits is recorded before the loop is pulled again.\n/** @internal */\nexport const run = Effect.fnUntraced(\n function* (options: {\n readonly registration: Registration\n readonly handler: TurnHandler\n readonly chat: Writer\n readonly services: Context.Context<Assistant.TurnServices | LanguageModel.LanguageModel>\n // A chat the app deleted is deleted here, and the turn stops.\n readonly checkOpen: Effect.Effect<void>\n }) {\n const { chat } = options\n const before = yield* chat.entries\n\n yield* Telemetry.agentInput(History.unhandledInputs(before))\n\n const assistant = options.registration.definition.name\n const traceId = yield* Telemetry.currentTraceId\n\n const started = yield* chat.append([\n AssistantEvent.TurnStarted.make(\n Option.match(traceId, {\n onNone: () => ({ assistant }),\n onSome: (id) => ({ assistant, traceId: id })\n })\n )\n ])\n\n const startSeq = lastSeq(started, 0)\n const ranLoop = yield* Ref.make(false)\n const turn = makeTurnApi(options.registration, chat, startSeq, options.checkOpen, ranLoop)\n\n const record = (event: AssistantLoop.Event) => chat.append([event])\n\n yield* options.handler(turn).pipe(\n Stream.runForEach(record),\n Effect.onExit((exit) => (Exit.hasInterrupts(exit) ? Effect.void : closeOpenCalls(chat, startSeq))),\n // A loop the app writes gets the assistant's own tools too, with this turn's services.\n Effect.provideService(\n Loop.TurnTools,\n Option.some(Toolkits.provide(options.registration.builtins, options.services))\n ),\n // A turn's loop waits for the user's approval, and runs the calls they approved.\n Effect.provideService(Loop.TurnApprovals, Option.some({ approved: Effect.map(chat.entries, approvedCalls) })),\n Effect.provideContext(options.services),\n Effect.matchCauseEffect({\n onSuccess: Effect.fnUntraced(function* () {\n const all = yield* chat.entries\n\n yield* finishTurn(chat, handledThrough(all, startSeq), yield* Ref.get(ranLoop))\n }),\n onFailure: (cause) => failTurn(chat, cause)\n })\n )\n\n // The trace's output is what the user saw this turn, and a failure if any.\n const shown = (yield* chat.entries).flatMap(({ event, seq }) =>\n seq > startSeq && (Predicate.isTagged(event, \"AssistantMessage\") || Predicate.isTagged(event, \"TurnFailed\"))\n ? [event]\n : []\n )\n\n yield* Telemetry.agentOutput(shown)\n },\n (effect, options) =>\n effect.pipe(\n Telemetry.agentSpan(options.registration.definition.name, {\n chatId: options.chat.chatId,\n root: true\n })\n )\n)\n"],"mappings":"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"}
1
+ {"version":3,"file":"turns.js","names":["Toolkits.merge","Tools.makeMessaging","Specialists.toolkit","ToolApproval.fromEntries","History.unansweredCalls","History.fromEntries","History.mayHaveRunOutput","History.notRunOutput","AssistantEvent.isLibraryEvent","History.owesReply","History.unreadAfter","BlockKit.toolNames","AppEvents.decodeEntries","Instructions.protocol","History.owesDelivery","Loop.run","Toolkits.runOnly","Tools.sendUserMessageName","Tools.waitToolName","History.userText","Loop.TurnApprovals","Toolkits.showing","Toolkits.fromToolkitOrEmpty","Toolkits.duplicateNames","Telemetry.agentInput","History.unhandledInputs","Telemetry.currentTraceId","Loop.TurnTools","Toolkits.provide","Telemetry.agentOutput","Telemetry.agentSpan"],"sources":["../../src/internal/turns.ts"],"sourcesContent":["import type * as LanguageModel from \"effect/ai/LanguageModel\"\nimport * as Response from \"effect/ai/Response\"\nimport type * as Tool from \"effect/ai/Tool\"\nimport * as Cause from \"effect/Cause\"\nimport * as Context from \"effect/Context\"\nimport * as Effect from \"effect/Effect\"\nimport * as Exit from \"effect/Exit\"\nimport { identity } from \"effect/Function\"\nimport * as Option from \"effect/Option\"\nimport * as Predicate from \"effect/Predicate\"\nimport * as Ref from \"effect/Ref\"\nimport * as Schema from \"effect/Schema\"\nimport type * as Scope from \"effect/Scope\"\nimport * as Stream from \"effect/Stream\"\nimport type * as Assistant from \"../Assistant.ts\"\nimport type { ChatId } from \"../ChatId.ts\"\nimport * as AssistantEvent from \"../AssistantEvent.ts\"\nimport type * as AssistantLoop from \"../AssistantLoop.ts\"\nimport * as Loop from \"./loop.ts\"\nimport * as BlockKit from \"../BlockKit.ts\"\nimport type { CurrentChat } from \"../CurrentChat.ts\"\nimport * as Instructions from \"../Instructions.ts\"\nimport * as ToolApproval from \"../ToolApproval.ts\"\nimport type * as Specialist from \"../Specialist.ts\"\nimport * as AppEvents from \"./events.ts\"\nimport * as History from \"./history.ts\"\nimport { mapText } from \"./instructions.ts\"\nimport { toJsonText } from \"./schema.ts\"\nimport * as Specialists from \"./specialists.ts\"\nimport * as Telemetry from \"./telemetry.ts\"\nimport * as Toolkits from \"./toolkit.ts\"\nimport type { Writer } from \"./writer.ts\"\nimport * as Tools from \"./tools.ts\"\n\n/** @internal */\nexport const silentTurnMessage = \"I finished without sending a reply.\"\n\n/** @internal */\nexport const defaultReplyNudge =\n \"You haven't replied to the user's latest message yet. Reply now with send_user_message.\"\n\n/** @internal */\nexport const deliveryNudge =\n \"You told the user you're on it, but stopped without giving them the result. Send it now with send_user_message; if part of it is still running, say what and that you'll report back. If they already have everything, call wait.\"\n\n/** @internal */\nexport interface Definition {\n readonly name: string\n readonly blocks: BlockKit.BlockKit | undefined\n readonly specialists: ReadonlyArray<Specialist.Any>\n readonly app: AppEvents.AppEvents\n}\n\n// Once its build ran the layer's services are provided, so its streams need only what a turn provides.\n/** @internal */\nexport type TurnHandler = (\n turn: Assistant.Turn<any>\n) => Stream.Stream<AssistantLoop.Event, unknown, Assistant.TurnServices | LanguageModel.LanguageModel>\n\n// The chat `CurrentChat` gives, with its entries typed as the assistant's decode returns them: the assistant's typed\n// chat is this one, so it needs no check of its own.\n/** @internal */\nexport interface TypedChat {\n readonly chatId: ChatId\n readonly entries: Effect.Effect<AppEvents.DecodedEntries>\n readonly append: CurrentChat[\"Service\"][\"append\"]\n}\n\n/** @internal */\nexport interface Registration {\n readonly definition: Definition\n readonly model: Context.Context<LanguageModel.LanguageModel>\n readonly open: (chat: TypedChat) => Effect.Effect<TurnHandler, never, Scope.Scope | CurrentChat>\n readonly specialists: ReadonlyArray<{\n readonly specialist: Specialist.Any\n readonly built: Specialist.Built\n }>\n readonly builtins: Toolkits.Tools<Assistant.TurnServices>\n}\n\n/** @internal */\nexport const register = Effect.fnUntraced(function* (\n definition: Definition,\n options: { readonly open: Registration[\"open\"] }\n) {\n const model = yield* Effect.context<LanguageModel.LanguageModel>()\n\n const specialists = yield* Effect.forEach(\n definition.specialists,\n Effect.fnUntraced(function* (specialist) {\n const built = yield* Effect.serviceOption(specialist.Handler)\n\n if (Option.isNone(built)) {\n return yield* Effect.die(\n `No handler for the ${specialist.name} specialist: provide ${specialist.name}.toLayer.`\n )\n }\n\n return { specialist, built: built.value }\n })\n )\n\n const builtins = Toolkits.merge(\n yield* Tools.makeMessaging(definition.blocks),\n yield* Specialists.toolkit(definition.specialists)\n )\n\n const registration: Registration = {\n definition,\n model,\n open: options.open,\n specialists,\n builtins\n }\n\n return registration\n})\n\nconst ProviderError = Schema.Struct({ _tag: Schema.optional(Schema.String), raw: Schema.Unknown })\n\n// Provider errors keep their response body.\nconst failureMessage = (cause: Cause.Cause<unknown>): string =>\n Option.match(Schema.decodeUnknownOption(ProviderError)(Cause.squash(cause)), {\n onNone: () => Cause.pretty(cause),\n onSome: ({ _tag, raw }) => `${_tag ?? \"Error\"}: ${Predicate.isString(raw) ? raw : toJsonText(raw)}`\n })\n\n// Approved, and not run yet: also one a crash cut off while it ran, which runs again with the same id.\nconst approvedCalls = (entries: ReadonlyArray<AssistantEvent.AnyEntry>): ReadonlyArray<AssistantEvent.ToolCallPart> =>\n [...ToolApproval.fromEntries(entries).values()].flatMap(({ request, status, outcome }) =>\n status === \"approved\" && outcome === undefined\n ? [\n Response.makePart(\"tool-call\", {\n id: request.callId,\n name: request.tool,\n params: request.params,\n providerExecuted: false\n })\n ]\n : []\n )\n\nconst lastSequence = (entries: ReadonlyArray<AssistantEvent.AnyEntry>, otherwise: number) =>\n entries.at(-1)?.sequence ?? otherwise\n\n// The handler's stream can end between a step's output and its tools, or while they run. An interrupted turn (a\n// shutdown) leaves its calls open instead: the next turn runs them again.\nconst closeOpenCalls = Effect.fnUntraced(function* (chat: Writer, startSequence: number) {\n const entries = (yield* chat.entries).filter(({ sequence }) => sequence > startSequence)\n\n const started = new Set(\n entries.flatMap(({ event }) => (Predicate.isTagged(event, \"ToolStarted\") ? [event.callId] : []))\n )\n\n const open = History.unansweredCalls(History.fromEntries(entries))\n\n if (open.length === 0) return\n\n yield* chat.append(\n open.map((call) =>\n AssistantEvent.ToolResult.make({\n callId: call.id,\n tool: call.name,\n outcome: \"failure\",\n ...(started.has(call.id)\n ? { result: History.mayHaveRunOutput, reason: \"MayHaveRun\" }\n : { result: History.notRunOutput, reason: \"NotRun\" })\n })\n )\n )\n})\n\n// The inputs before the turn's `TurnStarted` (at `startSequence`) woke it; past that, what its model outputs record\n// reading.\nconst handledThrough = (entries: ReadonlyArray<AssistantEvent.AnyEntry>, startSequence: number) =>\n entries.reduce(\n (through, { sequence, event }) =>\n sequence > startSequence &&\n AssistantEvent.isLibraryEvent(event) &&\n Predicate.isTagged(event, \"ModelOutput\") &&\n event.seenThroughSequence !== undefined\n ? Math.max(through, event.seenThroughSequence)\n : through,\n startSequence\n )\n\n// A turn that ran the loop fails visibly when the user's latest input got no reply; an input that arrived after what\n// its model read gets the next turn, which replies. A handler that ended without the loop decided that itself.\nconst finishTurn = Effect.fnUntraced(function* (chat: Writer, read: number, ranLoop: boolean) {\n const entries = yield* chat.entries\n\n const end =\n ranLoop && History.owesReply(entries, read) && !History.unreadAfter(entries, read)\n ? AssistantEvent.TurnFailed.make({ throughSequence: read, message: silentTurnMessage })\n : AssistantEvent.TurnCompleted.make({ throughSequence: read })\n\n yield* chat.append([end])\n})\n\n// Everything stored so far counts as read, so a failing input isn't retried forever.\nconst failTurn = Effect.fnUntraced(function* (chat: Writer, cause: Cause.Cause<unknown>) {\n yield* Effect.logError(\"assistant turn failed\", cause)\n\n const last = lastSequence(yield* chat.entries, 0)\n\n yield* chat.append([AssistantEvent.TurnFailed.make({ throughSequence: last, message: failureMessage(cause) })])\n})\n\nconst makeTurnApi = (\n registration: Registration,\n chat: Writer,\n startSequence: number,\n checkOpen: Effect.Effect<void>,\n ranLoop: Ref.Ref<boolean>\n): Assistant.Turn<any> => {\n const { definition, builtins } = registration\n const { app } = definition\n const shownTools = BlockKit.toolNames(definition.blocks)\n\n const running = <A, E, R>(stream: Stream.Stream<A, E, R>) =>\n Stream.unwrap(Ref.set(ranLoop, true).pipe(Effect.as(stream)))\n\n // With how far into the chat it goes: the step records it on the model's output.\n // Read before every model call: a chat the app deleted meanwhile stops here.\n const history = Effect.fnUntraced(function* (view: Assistant.HistoryView<any> = identity) {\n yield* checkOpen\n\n const all = yield* chat.entries\n\n const read: AssistantLoop.ChatHistory = {\n messages: History.fromEntries(view(AppEvents.decodeEntries(app, all))),\n throughSequence: lastSequence(all, startSequence)\n }\n\n return read\n })\n\n const instructions = (given: string | Instructions.Resolved) =>\n mapText(given, (text) => `${Instructions.protocol}\\n\\n${text}`)\n\n // The loop's own prompt and tools, so the prompt stays cached, with the nudge at the end.\n const replyPass = <R>(options: {\n readonly instructions: Instructions.Resolved\n readonly toolkit: Toolkits.Tools<R>\n readonly view: Assistant.HistoryView<any>\n readonly nudge: string | undefined\n }) =>\n Stream.unwrap(\n Effect.gen(function* () {\n const all = yield* chat.entries\n const through = handledThrough(all, startSequence)\n\n // Something arrived after what the loop's model read: the next turn reads it with the full loop, and replies.\n if (History.unreadAfter(all, through)) return Stream.empty\n\n const owed = History.owesReply(all, through)\n const undelivered = !owed && History.owesDelivery(all, through)\n\n if (!owed && !undelivered) return Stream.empty\n\n const nudge = undelivered ? deliveryNudge : (options.nudge ?? defaultReplyNudge)\n\n return Loop.run({\n instructions: options.instructions,\n // A reply that's owed can only be sent; a result it may have given may also be for nobody.\n toolkit: Toolkits.runOnly(\n options.toolkit,\n owed ? [Tools.sendUserMessageName] : [Tools.sendUserMessageName, Tools.waitToolName],\n \"Not now: reply to the user with send_user_message.\"\n ),\n history: Effect.gen(function* () {\n yield* checkOpen\n\n const entries = yield* chat.entries\n\n return {\n messages: [\n ...History.fromEntries(options.view(AppEvents.decodeEntries(app, entries))),\n History.userText(nudge)\n ],\n throughSequence: lastSequence(entries, startSequence)\n }\n }),\n // A second step, for a reply after a call it was refused.\n maxSteps: 2,\n // The pass runs because the model wrote text of its own instead of replying, which reaches nobody: it has to\n // call a tool now. Changing the choice costs a read of the history at full price, on these rare turns only.\n toolChoice: \"required\"\n // The loop ran the approved calls; a pass that can only reply runs none.\n }).pipe(Stream.provideService(Loop.TurnApprovals, Option.none()))\n })\n )\n\n const replyIfOwed = (options: Assistant.ReplyOptions) =>\n running(\n replyPass({\n instructions: instructions(options.instructions),\n toolkit: builtins,\n view: identity,\n nudge: options.nudge\n })\n )\n\n const prepare = <T extends Record<string, Tool.Any>>(options: Assistant.RunOptions<T, any>) => {\n const own = Toolkits.showing(Toolkits.fromToolkitOrEmpty(options.toolkit), shownTools)\n\n return {\n system: instructions(options.instructions),\n toolkit: Toolkits.merge(builtins, own),\n view: options.history ?? identity\n }\n }\n\n return {\n chatId: chat.chatId,\n history,\n instructions,\n replyIfOwed,\n run: (options) => {\n const { system, toolkit, view } = prepare(options)\n const twice = Toolkits.duplicateNames(toolkit)\n\n const loop =\n twice.length > 0\n ? Stream.die(`Tool names used twice, like one named after a built-in: ${twice.join(\", \")}`)\n : Loop.run({\n instructions: system,\n toolkit,\n history: history(view),\n maxSteps: options.maxSteps ?? 16\n })\n\n return running(Stream.concat(loop, replyPass({ instructions: system, toolkit, view, nudge: options.replyNudge })))\n }\n }\n}\n\n// Every event the handler's loop emits is recorded before the loop is pulled again.\n/** @internal */\nexport const run = Effect.fnUntraced(\n function* (options: {\n readonly registration: Registration\n readonly handler: TurnHandler\n readonly chat: Writer\n readonly services: Context.Context<Assistant.TurnServices | LanguageModel.LanguageModel>\n // A chat the app deleted is deleted here, and the turn stops.\n readonly checkOpen: Effect.Effect<void>\n }) {\n const { chat } = options\n const before = yield* chat.entries\n\n yield* Telemetry.agentInput(History.unhandledInputs(before))\n\n const assistant = options.registration.definition.name\n const traceId = yield* Telemetry.currentTraceId\n\n const started = yield* chat.append([\n AssistantEvent.TurnStarted.make(\n Option.match(traceId, {\n onNone: () => ({ assistant }),\n onSome: (id) => ({ assistant, traceId: id })\n })\n )\n ])\n\n const startSequence = lastSequence(started, 0)\n const ranLoop = yield* Ref.make(false)\n const turn = makeTurnApi(options.registration, chat, startSequence, options.checkOpen, ranLoop)\n\n const record = (event: AssistantLoop.Event) => chat.append([event])\n\n yield* options.handler(turn).pipe(\n Stream.runForEach(record),\n Effect.onExit((exit) => (Exit.hasInterrupts(exit) ? Effect.void : closeOpenCalls(chat, startSequence))),\n // A loop the app writes gets the assistant's own tools too, with this turn's services.\n Effect.provideService(\n Loop.TurnTools,\n Option.some(Toolkits.provide(options.registration.builtins, options.services))\n ),\n // A turn's loop waits for the user's approval, and runs the calls they approved.\n Effect.provideService(Loop.TurnApprovals, Option.some({ approved: Effect.map(chat.entries, approvedCalls) })),\n Effect.provideContext(options.services),\n Effect.matchCauseEffect({\n onSuccess: Effect.fnUntraced(function* () {\n const all = yield* chat.entries\n\n yield* finishTurn(chat, handledThrough(all, startSequence), yield* Ref.get(ranLoop))\n }),\n onFailure: (cause) => failTurn(chat, cause)\n })\n )\n\n // The trace's output is what the user saw this turn, and a failure if any.\n const shown = (yield* chat.entries).flatMap(({ event, sequence }) =>\n sequence > startSequence &&\n (Predicate.isTagged(event, \"AssistantMessage\") || Predicate.isTagged(event, \"TurnFailed\"))\n ? [event]\n : []\n )\n\n yield* Telemetry.agentOutput(shown)\n },\n (effect, options) =>\n effect.pipe(\n Telemetry.agentSpan(options.registration.definition.name, {\n chatId: options.chat.chatId,\n root: true\n })\n )\n)\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "effect-assistant",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "A colleague assistant on Effect: an append-only event log per chat, an interruptible agent loop, specialists, skills, and UI blocks",
5
5
  "license": "MIT",
6
6
  "repository": {