effect-assistant 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Assistant.d.ts +77 -37
- package/dist/Assistant.d.ts.map +1 -1
- package/dist/Assistant.js +11 -4
- package/dist/Assistant.js.map +1 -1
- package/dist/AssistantActivity.d.ts +10 -16
- package/dist/AssistantActivity.d.ts.map +1 -1
- package/dist/AssistantActivity.js +11 -4
- package/dist/AssistantActivity.js.map +1 -1
- package/dist/AssistantEngine.d.ts +39 -1
- package/dist/AssistantEngine.d.ts.map +1 -1
- package/dist/AssistantEngine.js +81 -28
- package/dist/AssistantEngine.js.map +1 -1
- package/dist/AssistantEvent.d.ts +104 -11
- package/dist/AssistantEvent.d.ts.map +1 -1
- package/dist/AssistantEvent.js +88 -13
- package/dist/AssistantEvent.js.map +1 -1
- package/dist/AssistantLoop.d.ts +4 -2
- package/dist/AssistantLoop.d.ts.map +1 -1
- package/dist/AssistantLoop.js +4 -2
- package/dist/AssistantLoop.js.map +1 -1
- package/dist/BlockKit.d.ts +4 -4
- package/dist/BlockKit.js +1 -1
- package/dist/BlockKit.js.map +1 -1
- package/dist/ChatLog.js +1 -1
- package/dist/Compaction.d.ts +7 -3
- package/dist/Compaction.d.ts.map +1 -1
- package/dist/Compaction.js +19 -8
- package/dist/Compaction.js.map +1 -1
- package/dist/CurrentChat.d.ts +2 -1
- package/dist/CurrentChat.d.ts.map +1 -1
- package/dist/CurrentChat.js.map +1 -1
- package/dist/Instructions.js +1 -1
- package/dist/Skill.d.ts +86 -11
- package/dist/Skill.d.ts.map +1 -1
- package/dist/Skill.js +93 -18
- package/dist/Skill.js.map +1 -1
- package/dist/Specialist.d.ts +7 -7
- package/dist/Specialist.d.ts.map +1 -1
- package/dist/Specialist.js.map +1 -1
- package/dist/Timeline.d.ts +516 -0
- package/dist/Timeline.d.ts.map +1 -0
- package/dist/Timeline.js +631 -0
- package/dist/Timeline.js.map +1 -0
- package/dist/ToolApproval.d.ts +146 -0
- package/dist/ToolApproval.d.ts.map +1 -0
- package/dist/ToolApproval.js +201 -0
- package/dist/ToolApproval.js.map +1 -0
- package/dist/ToolDiscovery.d.ts +6 -4
- package/dist/ToolDiscovery.d.ts.map +1 -1
- package/dist/ToolDiscovery.js +5 -4
- package/dist/ToolDiscovery.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +4 -4
- package/dist/internal/chat.js +9 -7
- package/dist/internal/chat.js.map +1 -1
- package/dist/internal/events.d.ts +15 -16
- package/dist/internal/events.d.ts.map +1 -1
- package/dist/internal/events.js +29 -28
- package/dist/internal/events.js.map +1 -1
- package/dist/internal/history.js +53 -22
- package/dist/internal/history.js.map +1 -1
- package/dist/internal/input.js +7 -0
- package/dist/internal/input.js.map +1 -0
- package/dist/internal/loop.d.ts +10 -5
- package/dist/internal/loop.d.ts.map +1 -1
- package/dist/internal/loop.js +55 -22
- package/dist/internal/loop.js.map +1 -1
- package/dist/internal/specialists.js +10 -16
- package/dist/internal/specialists.js.map +1 -1
- package/dist/internal/toolkit.d.ts +5 -7
- package/dist/internal/toolkit.d.ts.map +1 -1
- package/dist/internal/toolkit.js +28 -18
- package/dist/internal/toolkit.js.map +1 -1
- package/dist/internal/tools.js +1 -1
- package/dist/internal/turns.d.ts.map +1 -1
- package/dist/internal/turns.js +35 -42
- package/dist/internal/turns.js.map +1 -1
- package/package.json +1 -1
- package/src/Assistant.ts +80 -40
- package/src/AssistantActivity.ts +11 -18
- package/src/AssistantEngine.ts +110 -43
- package/src/AssistantEvent.ts +106 -14
- package/src/AssistantLoop.ts +4 -2
- package/src/BlockKit.ts +6 -6
- package/src/Compaction.ts +25 -4
- package/src/CurrentChat.ts +2 -1
- package/src/Skill.ts +133 -23
- package/src/Specialist.ts +9 -9
- package/src/Timeline.ts +947 -0
- package/src/ToolApproval.ts +249 -0
- package/src/ToolDiscovery.ts +8 -3
- package/src/index.ts +10 -10
- package/src/internal/chat.ts +15 -5
- package/src/internal/events.ts +43 -43
- package/src/internal/history.ts +94 -32
- package/src/internal/input.ts +3 -0
- package/src/internal/loop.ts +120 -34
- package/src/internal/specialists.ts +7 -12
- package/src/internal/toolkit.ts +47 -30
- package/src/internal/turns.ts +52 -58
- package/dist/AppEvent.d.ts +0 -111
- package/dist/AppEvent.d.ts.map +0 -1
- package/dist/AppEvent.js +0 -71
- package/dist/AppEvent.js.map +0 -1
- package/dist/ChatView.d.ts +0 -408
- package/dist/ChatView.d.ts.map +0 -1
- package/dist/ChatView.js +0 -443
- package/dist/ChatView.js.map +0 -1
- package/dist/internal/skills.js +0 -28
- package/dist/internal/skills.js.map +0 -1
- package/src/AppEvent.ts +0 -134
- package/src/ChatView.ts +0 -587
- package/src/internal/skills.ts +0 -37
package/dist/internal/toolkit.js
CHANGED
|
@@ -2,6 +2,7 @@ import { toJson } from "./schema.js";
|
|
|
2
2
|
import { AssistantToolCall } from "../AssistantToolCall.js";
|
|
3
3
|
import { ToolFailure } from "../ToolFailure.js";
|
|
4
4
|
import * as Effect from "effect/Effect";
|
|
5
|
+
import * as Predicate from "effect/Predicate";
|
|
5
6
|
import * as Schema from "effect/Schema";
|
|
6
7
|
import * as Stream from "effect/Stream";
|
|
7
8
|
import * as Option from "effect/Option";
|
|
@@ -27,8 +28,20 @@ const failure = (result) => ({
|
|
|
27
28
|
const hasTool = (toolkit, name) => Object.hasOwn(toolkit.tools, name);
|
|
28
29
|
/** @internal */
|
|
29
30
|
const needingApproval = (definitions) => definitions.flatMap((tool) => tool.needsApproval === void 0 || tool.needsApproval === false ? [] : [tool.name]);
|
|
30
|
-
|
|
31
|
-
const
|
|
31
|
+
const toolNamed = (toolkit, name) => Object.hasOwn(toolkit.tools, name) ? toolkit.tools[name] : void 0;
|
|
32
|
+
const needsApprovalNative = Effect.fnUntraced(function* (toolkit, call, history) {
|
|
33
|
+
const tool = toolNamed(toolkit, call.name);
|
|
34
|
+
const needs = tool?.needsApproval;
|
|
35
|
+
if (tool === void 0 || needs === void 0 || Predicate.isBoolean(needs)) return needs === true;
|
|
36
|
+
return yield* Effect.gen(function* () {
|
|
37
|
+
const params = yield* Schema.decodeUnknownEffect(tool.parametersSchema)(call.params);
|
|
38
|
+
const decided = needs(params, {
|
|
39
|
+
toolCallId: call.id,
|
|
40
|
+
messages: history
|
|
41
|
+
});
|
|
42
|
+
return Effect.isEffect(decided) ? yield* decided : decided;
|
|
43
|
+
}).pipe(Effect.orElseSucceed(() => false));
|
|
44
|
+
});
|
|
32
45
|
const isParams = (params) => params !== void 0;
|
|
33
46
|
/** @internal */
|
|
34
47
|
const runNative = Effect.fnUntraced(function* (toolkit, call) {
|
|
@@ -54,43 +67,39 @@ const runNative = Effect.fnUntraced(function* (toolkit, call) {
|
|
|
54
67
|
/** @internal */
|
|
55
68
|
const fromWithHandler = (toolkit) => ({
|
|
56
69
|
definitions: Object.values(toolkit.tools),
|
|
57
|
-
run: (call) => runNative(toolkit, call)
|
|
70
|
+
run: (call) => runNative(toolkit, call),
|
|
71
|
+
needsApproval: (call, history) => needsApprovalNative(toolkit, call, history)
|
|
58
72
|
});
|
|
59
73
|
/** @internal */
|
|
60
74
|
const fromToolkit = (toolkit) => ({
|
|
61
75
|
definitions: Object.values(toolkit.tools),
|
|
62
76
|
run: Effect.fnUntraced(function* (call) {
|
|
63
77
|
return yield* runNative(yield* toolkit, call);
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
/** @internal */
|
|
67
|
-
const withContext = (toolkit, handlers) => ({
|
|
68
|
-
definitions: Object.values(toolkit.tools),
|
|
69
|
-
run: Effect.fnUntraced(function* (call) {
|
|
70
|
-
const context = yield* Effect.context();
|
|
71
|
-
const resolved = yield* Effect.provideContext(toolkit, yield* toolkit.toHandlers(handlers(context)));
|
|
72
|
-
return yield* runNative(resolved, call);
|
|
73
|
-
})
|
|
78
|
+
}),
|
|
79
|
+
needsApproval: (call, history) => needsApprovalNative(toolkit, call, history)
|
|
74
80
|
});
|
|
75
81
|
/** @internal */
|
|
76
82
|
const fromToolkitOrEmpty = (toolkit) => toolkit === void 0 ? empty : fromToolkit(toolkit);
|
|
77
83
|
/** @internal */
|
|
78
84
|
const empty = {
|
|
79
85
|
definitions: [],
|
|
80
|
-
run: (call) => Effect.succeed(failed(`No tool named ${call.name}.`, "ToolNotFoundError"))
|
|
86
|
+
run: (call) => Effect.succeed(failed(`No tool named ${call.name}.`, "ToolNotFoundError")),
|
|
87
|
+
needsApproval: () => Effect.succeed(false)
|
|
81
88
|
};
|
|
82
89
|
/** @internal */
|
|
83
90
|
const merge = (self, that) => {
|
|
84
91
|
const inSelf = (call) => self.definitions.some((tool) => tool.name === call.name);
|
|
85
92
|
return {
|
|
86
93
|
definitions: [...self.definitions, ...that.definitions],
|
|
87
|
-
run: (call) => inSelf(call) ? self.run(call) : that.run(call)
|
|
94
|
+
run: (call) => inSelf(call) ? self.run(call) : that.run(call),
|
|
95
|
+
needsApproval: (call, history) => inSelf(call) ? self.needsApproval(call, history) : that.needsApproval(call, history)
|
|
88
96
|
};
|
|
89
97
|
};
|
|
90
98
|
/** @internal */
|
|
91
99
|
const provide = (tools, context) => ({
|
|
92
100
|
definitions: tools.definitions,
|
|
93
|
-
run: (call) => Effect.provideContext(tools.run(call), context)
|
|
101
|
+
run: (call) => Effect.provideContext(tools.run(call), context),
|
|
102
|
+
needsApproval: (call, history) => Effect.provideContext(tools.needsApproval(call, history), context)
|
|
94
103
|
});
|
|
95
104
|
/** @internal */
|
|
96
105
|
const duplicateNames = (tools) => {
|
|
@@ -100,9 +109,10 @@ const duplicateNames = (tools) => {
|
|
|
100
109
|
/** @internal */
|
|
101
110
|
const runOnly = (tools, names, message) => ({
|
|
102
111
|
definitions: tools.definitions,
|
|
103
|
-
run: (call) => names.includes(call.name) ? tools.run(call) : Effect.succeed(failed(message, "NotNow"))
|
|
112
|
+
run: (call) => names.includes(call.name) ? tools.run(call) : Effect.succeed(failed(message, "NotNow")),
|
|
113
|
+
needsApproval: () => Effect.succeed(false)
|
|
104
114
|
});
|
|
105
115
|
//#endregion
|
|
106
|
-
export { duplicateNames, empty, failed, fromToolkit, fromToolkitOrEmpty, fromWithHandler, merge, needingApproval,
|
|
116
|
+
export { duplicateNames, empty, failed, fromToolkit, fromToolkitOrEmpty, fromWithHandler, merge, needingApproval, provide, runNative, runOnly };
|
|
107
117
|
|
|
108
118
|
//# 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 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 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}\n\n/** @internal */\nexport type ToolServices<T extends Record<string, Tool.Any>> = Exclude<\n Tool.HandlerServices<T[keyof T]>,\n AssistantToolCall\n>\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// Approval would park a call until the user decides, which this library doesn't do: the model calls a tool with\n// final values, and a user's own commitment goes through the app's API from a block.\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/** @internal */\nexport const needsApprovalMessage = (names: ReadonlyArray<string>): string =>\n `${names.join(\", \")} ${names.length === 1 ? \"sets\" : \"set\"} needsApproval, and effect-assistant doesn't run tools that wait for approval. Remove needsApproval: let the model call the tool with final values, or show a block whose button calls your app's API, which does the work and submits an app event that wakes the chat.`\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})\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})\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})\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 }\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})\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})\n"],"mappings":"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"}
|
|
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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"}
|
package/dist/internal/tools.js
CHANGED
|
@@ -4,8 +4,8 @@ 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 * as Predicate from "effect/Predicate";
|
|
8
7
|
import * as Effect from "effect/Effect";
|
|
8
|
+
import * as Predicate from "effect/Predicate";
|
|
9
9
|
import * as Schema from "effect/Schema";
|
|
10
10
|
import * as Option from "effect/Option";
|
|
11
11
|
import * as Tool from "effect/ai/Tool";
|
|
@@ -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;;
|
|
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"}
|
package/dist/internal/turns.js
CHANGED
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
import { ToolResult, TurnCompleted, TurnFailed, TurnStarted, isLibraryEvent } from "../AssistantEvent.js";
|
|
2
|
+
import { fromEntries } from "../ToolApproval.js";
|
|
3
|
+
import { decodeEntries } from "./events.js";
|
|
2
4
|
import { toJsonText } from "./schema.js";
|
|
3
|
-
import {
|
|
4
|
-
import { fromEntries, mayHaveRunOutput, notRunOutput, owesDelivery, owesReply, unansweredCalls, unhandledInputs, unreadAfter, userText } from "./history.js";
|
|
5
|
+
import { fromEntries as fromEntries$1, mayHaveRunOutput, notRunOutput, owesDelivery, owesReply, unansweredCalls, unhandledInputs, unreadAfter, userText } from "./history.js";
|
|
5
6
|
import { agentInput, agentOutput, agentSpan, currentTraceId } from "./telemetry.js";
|
|
6
7
|
import { duplicateNames, fromToolkitOrEmpty, merge, provide, runOnly } from "./toolkit.js";
|
|
7
8
|
import { makeMessaging, sendUserMessageName, waitToolName } from "./tools.js";
|
|
8
|
-
import { TurnTools, run as run$1 } from "./loop.js";
|
|
9
|
-
import {
|
|
9
|
+
import { TurnApprovals, TurnTools, run as run$1 } from "./loop.js";
|
|
10
|
+
import { toolkit } from "./specialists.js";
|
|
10
11
|
import { protocol } from "../Instructions.js";
|
|
11
|
-
import { toTools } from "./skills.js";
|
|
12
12
|
import { mapText } from "./instructions.js";
|
|
13
|
-
import { toolkit } from "./specialists.js";
|
|
14
|
-
import * as Predicate from "effect/Predicate";
|
|
15
13
|
import "effect/Context";
|
|
16
14
|
import * as Effect from "effect/Effect";
|
|
15
|
+
import * as Predicate from "effect/Predicate";
|
|
17
16
|
import * as Schema from "effect/Schema";
|
|
18
17
|
import * as Stream from "effect/Stream";
|
|
19
18
|
import * as Exit from "effect/Exit";
|
|
20
19
|
import * as Option from "effect/Option";
|
|
20
|
+
import * as Response from "effect/ai/Response";
|
|
21
|
+
import * as Ref from "effect/Ref";
|
|
21
22
|
import * as Cause from "effect/Cause";
|
|
22
23
|
import { identity } from "effect/Function";
|
|
23
24
|
//#region src/internal/turns.ts
|
|
@@ -55,27 +56,17 @@ const failureMessage = (cause) => Option.match(Schema.decodeUnknownOption(Provid
|
|
|
55
56
|
onNone: () => Cause.pretty(cause),
|
|
56
57
|
onSome: ({ _tag, raw }) => `${_tag ?? "Error"}: ${Predicate.isString(raw) ? raw : toJsonText(raw)}`
|
|
57
58
|
});
|
|
59
|
+
const approvedCalls = (entries) => [...fromEntries(entries).values()].flatMap(({ request, status, outcome }) => status === "approved" && outcome === void 0 ? [Response.makePart("tool-call", {
|
|
60
|
+
id: request.callId,
|
|
61
|
+
name: request.tool,
|
|
62
|
+
params: request.params,
|
|
63
|
+
providerExecuted: false
|
|
64
|
+
})] : []);
|
|
58
65
|
const lastSeq = (entries, otherwise) => entries.at(-1)?.seq ?? otherwise;
|
|
59
|
-
const toHistory = (viewed, app) => {
|
|
60
|
-
const out = [];
|
|
61
|
-
let run = [];
|
|
62
|
-
const flush = () => {
|
|
63
|
-
out.push(...fromEntries(run, (event) => read(app, event)));
|
|
64
|
-
run = [];
|
|
65
|
-
};
|
|
66
|
-
for (const item of viewed) if (isEntry(item)) run.push(item);
|
|
67
|
-
else {
|
|
68
|
-
flush();
|
|
69
|
-
out.push(item);
|
|
70
|
-
}
|
|
71
|
-
flush();
|
|
72
|
-
return out;
|
|
73
|
-
};
|
|
74
|
-
const isEntry = (item) => Predicate.hasProperty(item, "seq");
|
|
75
66
|
const closeOpenCalls = Effect.fnUntraced(function* (chat, startSeq) {
|
|
76
67
|
const entries = (yield* chat.entries).filter(({ seq }) => seq > startSeq);
|
|
77
68
|
const started = new Set(entries.flatMap(({ event }) => Predicate.isTagged(event, "ToolStarted") ? [event.callId] : []));
|
|
78
|
-
const open = unansweredCalls(fromEntries(entries
|
|
69
|
+
const open = unansweredCalls(fromEntries$1(entries));
|
|
79
70
|
if (open.length === 0) return;
|
|
80
71
|
yield* chat.append(open.map((call) => ToolResult.make({
|
|
81
72
|
callId: call.id,
|
|
@@ -91,9 +82,9 @@ const closeOpenCalls = Effect.fnUntraced(function* (chat, startSeq) {
|
|
|
91
82
|
})));
|
|
92
83
|
});
|
|
93
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);
|
|
94
|
-
const finishTurn = Effect.fnUntraced(function* (chat, read,
|
|
85
|
+
const finishTurn = Effect.fnUntraced(function* (chat, read, ranLoop) {
|
|
95
86
|
const entries = yield* chat.entries;
|
|
96
|
-
const end = owesReply(entries, read) && !unreadAfter(entries, read
|
|
87
|
+
const end = ranLoop && owesReply(entries, read) && !unreadAfter(entries, read) ? TurnFailed.make({
|
|
97
88
|
throughSeq: read,
|
|
98
89
|
message: silentTurnMessage
|
|
99
90
|
}) : TurnCompleted.make({ throughSeq: read });
|
|
@@ -107,14 +98,15 @@ const failTurn = Effect.fnUntraced(function* (chat, cause) {
|
|
|
107
98
|
message: failureMessage(cause)
|
|
108
99
|
})]);
|
|
109
100
|
});
|
|
110
|
-
const makeTurnApi = (registration, chat, startSeq, checkOpen) => {
|
|
101
|
+
const makeTurnApi = (registration, chat, startSeq, checkOpen, ranLoop) => {
|
|
111
102
|
const { definition, builtins } = registration;
|
|
112
103
|
const { app } = definition;
|
|
104
|
+
const running = (stream) => Stream.unwrap(Ref.set(ranLoop, true).pipe(Effect.as(stream)));
|
|
113
105
|
const history = Effect.fnUntraced(function* (view = identity) {
|
|
114
106
|
yield* checkOpen;
|
|
115
107
|
const all = yield* chat.entries;
|
|
116
108
|
return {
|
|
117
|
-
messages:
|
|
109
|
+
messages: fromEntries$1(view(decodeEntries(app, all))),
|
|
118
110
|
throughSeq: lastSeq(all, startSeq)
|
|
119
111
|
};
|
|
120
112
|
});
|
|
@@ -122,7 +114,7 @@ const makeTurnApi = (registration, chat, startSeq, checkOpen) => {
|
|
|
122
114
|
const replyPass = (options) => Stream.unwrap(Effect.gen(function* () {
|
|
123
115
|
const all = yield* chat.entries;
|
|
124
116
|
const through = handledThrough(all, startSeq);
|
|
125
|
-
if (unreadAfter(all, through
|
|
117
|
+
if (unreadAfter(all, through)) return Stream.empty;
|
|
126
118
|
const owed = owesReply(all, through);
|
|
127
119
|
const undelivered = !owed && owesDelivery(all, through);
|
|
128
120
|
if (!owed && !undelivered) return Stream.empty;
|
|
@@ -134,24 +126,24 @@ const makeTurnApi = (registration, chat, startSeq, checkOpen) => {
|
|
|
134
126
|
yield* checkOpen;
|
|
135
127
|
const entries = yield* chat.entries;
|
|
136
128
|
return {
|
|
137
|
-
messages: [...
|
|
129
|
+
messages: [...fromEntries$1(options.view(decodeEntries(app, entries))), userText(nudge)],
|
|
138
130
|
throughSeq: lastSeq(entries, startSeq)
|
|
139
131
|
};
|
|
140
132
|
}),
|
|
141
133
|
maxSteps: 2
|
|
142
|
-
});
|
|
134
|
+
}).pipe(Stream.provideService(TurnApprovals, Option.none()));
|
|
143
135
|
}));
|
|
144
|
-
const replyIfOwed = (options) => replyPass({
|
|
136
|
+
const replyIfOwed = (options) => running(replyPass({
|
|
145
137
|
instructions: instructions(options.instructions),
|
|
146
138
|
toolkit: builtins,
|
|
147
139
|
view: identity,
|
|
148
140
|
nudge: options.nudge
|
|
149
|
-
});
|
|
141
|
+
}));
|
|
150
142
|
const prepare = (options) => {
|
|
151
|
-
const
|
|
143
|
+
const own = fromToolkitOrEmpty(options.toolkit);
|
|
152
144
|
return {
|
|
153
|
-
system:
|
|
154
|
-
toolkit: merge(
|
|
145
|
+
system: instructions(options.instructions),
|
|
146
|
+
toolkit: merge(builtins, own),
|
|
155
147
|
view: options.history ?? identity
|
|
156
148
|
};
|
|
157
149
|
};
|
|
@@ -169,12 +161,12 @@ const makeTurnApi = (registration, chat, startSeq, checkOpen) => {
|
|
|
169
161
|
history: history(view),
|
|
170
162
|
maxSteps: options.maxSteps ?? 16
|
|
171
163
|
});
|
|
172
|
-
return Stream.concat(loop, replyPass({
|
|
164
|
+
return running(Stream.concat(loop, replyPass({
|
|
173
165
|
instructions: system,
|
|
174
166
|
toolkit,
|
|
175
167
|
view,
|
|
176
168
|
nudge: options.replyNudge
|
|
177
|
-
}));
|
|
169
|
+
})));
|
|
178
170
|
}
|
|
179
171
|
};
|
|
180
172
|
};
|
|
@@ -182,7 +174,7 @@ const makeTurnApi = (registration, chat, startSeq, checkOpen) => {
|
|
|
182
174
|
const run = Effect.fnUntraced(function* (options) {
|
|
183
175
|
const { chat } = options;
|
|
184
176
|
const before = yield* chat.entries;
|
|
185
|
-
yield* agentInput(unhandledInputs(before
|
|
177
|
+
yield* agentInput(unhandledInputs(before));
|
|
186
178
|
const assistant = options.registration.definition.name;
|
|
187
179
|
const traceId = yield* currentTraceId;
|
|
188
180
|
const started = yield* chat.append([TurnStarted.make(Option.match(traceId, {
|
|
@@ -193,12 +185,13 @@ const run = Effect.fnUntraced(function* (options) {
|
|
|
193
185
|
})
|
|
194
186
|
}))]);
|
|
195
187
|
const startSeq = lastSeq(started, 0);
|
|
196
|
-
const
|
|
188
|
+
const ranLoop = yield* Ref.make(false);
|
|
189
|
+
const turn = makeTurnApi(options.registration, chat, startSeq, options.checkOpen, ranLoop);
|
|
197
190
|
const record = (event) => chat.append([event]);
|
|
198
|
-
yield* options.handler(turn).pipe(Stream.runForEach(record), Effect.onExit((exit) => Exit.hasInterrupts(exit) ? Effect.void : closeOpenCalls(chat, startSeq)), Effect.provideService(TurnTools, Option.some(provide(options.registration.builtins, options.services))), Effect.provideContext(options.services), Effect.matchCauseEffect({
|
|
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({
|
|
199
192
|
onSuccess: Effect.fnUntraced(function* () {
|
|
200
193
|
const all = yield* chat.entries;
|
|
201
|
-
yield* finishTurn(chat, handledThrough(all, startSeq),
|
|
194
|
+
yield* finishTurn(chat, handledThrough(all, startSeq), yield* Ref.get(ranLoop));
|
|
202
195
|
}),
|
|
203
196
|
onFailure: (cause) => failTurn(chat, cause)
|
|
204
197
|
}));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"turns.js","names":["Toolkits.merge","Tools.makeMessaging","Specialists.toolkit","History.fromEntries","AppEvents.read","History.unansweredCalls","History.mayHaveRunOutput","History.notRunOutput","AssistantEvent.isLibraryEvent","History.owesReply","History.unreadAfter","AppEvents.decodeEntries","Instructions.protocol","History.owesDelivery","Loop.run","Toolkits.runOnly","Tools.sendUserMessageName","Tools.waitToolName","History.userText","Skill.withCatalog","Skills.toTools","Toolkits.fromToolkitOrEmpty","Toolkits.duplicateNames","Telemetry.agentInput","History.unhandledInputs","Telemetry.currentTraceId","Loop.TurnTools","Toolkits.provide","Telemetry.agentOutput","Telemetry.agentSpan"],"sources":["../../src/internal/turns.ts"],"sourcesContent":["import type * as LanguageModel from \"effect/ai/LanguageModel\"\nimport type * as Tool from \"effect/ai/Tool\"\nimport type * as Prompt from \"effect/ai/Prompt\"\nimport * as Cause from \"effect/Cause\"\nimport * as Context from \"effect/Context\"\nimport * as Effect from \"effect/Effect\"\nimport * as Exit from \"effect/Exit\"\nimport { identity } from \"effect/Function\"\nimport * as Option from \"effect/Option\"\nimport * as Predicate from \"effect/Predicate\"\nimport * as Schema from \"effect/Schema\"\nimport type * as Scope from \"effect/Scope\"\nimport * as Stream from \"effect/Stream\"\nimport type * as Assistant from \"../Assistant.ts\"\nimport type { ChatId } from \"../ChatId.ts\"\nimport * as AssistantEvent from \"../AssistantEvent.ts\"\nimport type * as AssistantLoop from \"../AssistantLoop.ts\"\nimport * as Loop from \"./loop.ts\"\nimport type * as BlockKit from \"../BlockKit.ts\"\nimport type { CurrentChat } from \"../CurrentChat.ts\"\nimport * as Instructions from \"../Instructions.ts\"\nimport * as Skill from \"../Skill.ts\"\nimport * as Skills from \"./skills.ts\"\nimport type * as Specialist from \"../Specialist.ts\"\nimport * as AppEvents from \"./events.ts\"\nimport * as History from \"./history.ts\"\nimport { mapText } from \"./instructions.ts\"\nimport { toJsonText } from \"./schema.ts\"\nimport * as Specialists from \"./specialists.ts\"\nimport * as Telemetry from \"./telemetry.ts\"\nimport * as Toolkits from \"./toolkit.ts\"\nimport type { Writer } from \"./writer.ts\"\nimport * as Tools from \"./tools.ts\"\n\n/** @internal */\nexport const silentTurnMessage = \"I finished without sending a reply.\"\n\n/** @internal */\nexport const defaultReplyNudge =\n \"You haven't replied to the user's latest message yet. Reply now with send_user_message.\"\n\n/** @internal */\nexport const deliveryNudge =\n \"You told the user you're on it, but stopped without giving them the result. Send it now with send_user_message; if part of it is still running, say what and that you'll report back. If they already have everything, call wait.\"\n\n/** @internal */\nexport interface Definition {\n readonly name: string\n readonly blocks: BlockKit.BlockKit | undefined\n readonly specialists: ReadonlyArray<Specialist.Any>\n readonly app: AppEvents.AppEvents\n}\n\n// Once its build ran the layer's services are provided, so its streams need only what a turn provides.\n/** @internal */\nexport type TurnHandler = (\n turn: Assistant.Turn<any>\n) => Stream.Stream<AssistantLoop.Event, unknown, Assistant.TurnServices | LanguageModel.LanguageModel>\n\n// The chat `CurrentChat` gives, with its entries typed as the assistant's decode returns them: the assistant's typed\n// chat is this one, so it needs no check of its own.\n/** @internal */\nexport interface TypedChat {\n readonly chatId: ChatId\n readonly entries: Effect.Effect<AppEvents.DecodedEntries>\n readonly append: CurrentChat[\"Service\"][\"append\"]\n}\n\n/** @internal */\nexport interface Registration {\n readonly definition: Definition\n readonly model: Context.Context<LanguageModel.LanguageModel>\n readonly open: (chat: TypedChat) => Effect.Effect<TurnHandler, never, Scope.Scope | CurrentChat>\n readonly specialists: ReadonlyArray<{\n readonly specialist: Specialist.Any\n readonly built: Specialist.Built\n }>\n readonly builtins: Toolkits.Tools<Assistant.TurnServices>\n}\n\n/** @internal */\nexport const register = Effect.fnUntraced(function* (\n definition: Definition,\n options: { readonly open: Registration[\"open\"] }\n) {\n const model = yield* Effect.context<LanguageModel.LanguageModel>()\n\n const specialists = yield* Effect.forEach(\n definition.specialists,\n Effect.fnUntraced(function* (specialist) {\n const built = yield* Effect.serviceOption(specialist.Handler)\n\n if (Option.isNone(built)) {\n return yield* Effect.die(\n `No handler for the ${specialist.name} specialist: provide ${specialist.name}.toLayer.`\n )\n }\n\n return { specialist, built: built.value }\n })\n )\n\n const builtins = Toolkits.merge(\n yield* Tools.makeMessaging(definition.blocks),\n yield* Specialists.toolkit(definition.specialists)\n )\n\n const registration: Registration = {\n definition,\n model,\n open: options.open,\n specialists,\n builtins\n }\n\n return registration\n})\n\nconst ProviderError = Schema.Struct({ _tag: Schema.optional(Schema.String), raw: Schema.Unknown })\n\n// Provider errors keep their response body.\nconst failureMessage = (cause: Cause.Cause<unknown>): string =>\n Option.match(Schema.decodeUnknownOption(ProviderError)(Cause.squash(cause)), {\n onNone: () => Cause.pretty(cause),\n onSome: ({ _tag, raw }) => `${_tag ?? \"Error\"}: ${Predicate.isString(raw) ? raw : toJsonText(raw)}`\n })\n\nconst lastSeq = (entries: ReadonlyArray<AssistantEvent.AnyEntry>, otherwise: number) => entries.at(-1)?.seq ?? otherwise\n\nconst toHistory = (\n viewed: ReadonlyArray<Assistant.Entry<any> | Prompt.Message>,\n app: AppEvents.AppEvents\n): ReadonlyArray<Prompt.Message> => {\n const out: Array<Prompt.Message> = []\n let run: Array<AssistantEvent.AnyEntry> = []\n\n const flush = () => {\n out.push(...History.fromEntries(run, (event) => AppEvents.read(app, event)))\n run = []\n }\n\n for (const item of viewed) {\n if (isEntry(item)) run.push(item)\n else {\n flush()\n out.push(item)\n }\n }\n\n flush()\n\n return out\n}\n\nconst isEntry = (item: Assistant.Entry<any> | Prompt.Message): item is Assistant.Entry<any> =>\n Predicate.hasProperty(item, \"seq\")\n\n// The handler's stream can end between a step's output and its tools, or while they run. An interrupted turn (a\n// shutdown) leaves its calls open instead: the next turn runs them again.\nconst closeOpenCalls = Effect.fnUntraced(function* (chat: Writer, startSeq: number) {\n const entries = (yield* chat.entries).filter(({ seq }) => seq > startSeq)\n\n const started = new Set(\n entries.flatMap(({ event }) => (Predicate.isTagged(event, \"ToolStarted\") ? [event.callId] : []))\n )\n\n const open = History.unansweredCalls(History.fromEntries(entries, () => undefined))\n\n if (open.length === 0) return\n\n yield* chat.append(\n open.map((call) =>\n AssistantEvent.ToolResult.make({\n callId: call.id,\n tool: call.name,\n outcome: \"failure\",\n ...(started.has(call.id)\n ? { result: History.mayHaveRunOutput, reason: \"MayHaveRun\" }\n : { result: History.notRunOutput, reason: \"NotRun\" })\n })\n )\n )\n})\n\n// The inputs before the turn's `TurnStarted` (at `startSeq`) woke it; past that, what its model outputs record\n// reading.\nconst handledThrough = (entries: ReadonlyArray<AssistantEvent.AnyEntry>, startSeq: number) =>\n entries.reduce(\n (through, { seq, event }) =>\n seq > startSeq &&\n AssistantEvent.isLibraryEvent(event) &&\n Predicate.isTagged(event, \"ModelOutput\") &&\n event.seenThroughSeq !== undefined\n ? Math.max(through, event.seenThroughSeq)\n : through,\n startSeq\n )\n\n// Fails visibly when the user's latest input got no reply; an input that arrived after what its model read gets the\n// next turn, which replies.\nconst finishTurn = Effect.fnUntraced(function* (chat: Writer, read: number, wakes: ReadonlySet<string>) {\n const entries = yield* chat.entries\n\n const end =\n History.owesReply(entries, read) && !History.unreadAfter(entries, read, wakes)\n ? AssistantEvent.TurnFailed.make({ throughSeq: read, message: silentTurnMessage })\n : AssistantEvent.TurnCompleted.make({ throughSeq: read })\n\n yield* chat.append([end])\n})\n\n// Everything stored so far counts as read, so a failing input isn't retried forever.\nconst failTurn = Effect.fnUntraced(function* (chat: Writer, cause: Cause.Cause<unknown>) {\n yield* Effect.logError(\"assistant turn failed\", cause)\n\n const last = lastSeq(yield* chat.entries, 0)\n\n yield* chat.append([AssistantEvent.TurnFailed.make({ throughSeq: last, message: failureMessage(cause) })])\n})\n\nconst makeTurnApi = (\n registration: Registration,\n chat: Writer,\n startSeq: number,\n checkOpen: Effect.Effect<void>\n): Assistant.Turn<any> => {\n const { definition, builtins } = registration\n const { app } = definition\n\n // With how far into the chat it goes: the step records it on the model's output.\n // Read before every model call: a chat the app deleted meanwhile stops here.\n const history = Effect.fnUntraced(function* (view: Assistant.HistoryView<any> = identity) {\n yield* checkOpen\n\n const all = yield* chat.entries\n\n const read: AssistantLoop.ChatHistory = {\n messages: toHistory(view(AppEvents.decodeEntries(app, all)), app),\n throughSeq: lastSeq(all, startSeq)\n }\n\n return read\n })\n\n const instructions = (given: string | Instructions.Resolved) =>\n mapText(given, (text) => `${Instructions.protocol}\\n\\n${text}`)\n\n // The loop's own prompt and tools, so the prompt stays cached, with the nudge at the end.\n const replyPass = <R>(options: {\n readonly instructions: Instructions.Resolved\n readonly toolkit: Toolkits.Tools<R>\n readonly view: Assistant.HistoryView<any>\n readonly nudge: string | undefined\n }) =>\n Stream.unwrap(\n Effect.gen(function* () {\n const all = yield* chat.entries\n const through = handledThrough(all, startSeq)\n\n // Something arrived after what the loop's model read: the next turn reads it with the full loop, and replies.\n if (History.unreadAfter(all, through, app.wakes)) return Stream.empty\n\n const owed = History.owesReply(all, through)\n const undelivered = !owed && History.owesDelivery(all, through)\n\n if (!owed && !undelivered) return Stream.empty\n\n const nudge = undelivered ? deliveryNudge : (options.nudge ?? defaultReplyNudge)\n\n return Loop.run({\n instructions: options.instructions,\n // A reply that's owed can only be sent; a result it may have given may also be for nobody.\n toolkit: Toolkits.runOnly(\n options.toolkit,\n owed ? [Tools.sendUserMessageName] : [Tools.sendUserMessageName, Tools.waitToolName],\n \"Not now: reply to the user with send_user_message.\"\n ),\n history: Effect.gen(function* () {\n yield* checkOpen\n\n const entries = yield* chat.entries\n\n return {\n messages: [\n ...toHistory(options.view(AppEvents.decodeEntries(app, entries)), app),\n History.userText(nudge)\n ],\n throughSeq: lastSeq(entries, startSeq)\n }\n }),\n // A second step, for a reply after a call it was refused.\n maxSteps: 2\n })\n })\n )\n\n const replyIfOwed = (options: Assistant.ReplyOptions) =>\n replyPass({\n instructions: instructions(options.instructions),\n toolkit: builtins,\n view: identity,\n nudge: options.nudge\n })\n\n const prepare = <T extends Record<string, Tool.Any>, RS>(options: Assistant.LoopOptions<T, RS, any>) => {\n const skills = options.skills ?? []\n\n return {\n system: mapText(instructions(options.instructions), (text) => Skill.withCatalog(text, skills)),\n toolkit: Toolkits.merge(\n Toolkits.merge(builtins, Skills.toTools(skills)),\n Toolkits.fromToolkitOrEmpty(options.toolkit)\n ),\n view: options.history ?? identity\n }\n }\n\n return {\n chatId: chat.chatId,\n history,\n instructions,\n replyIfOwed,\n loop: (options) => {\n const { system, toolkit, view } = prepare(options)\n const twice = Toolkits.duplicateNames(toolkit)\n\n const loop =\n twice.length > 0\n ? Stream.die(`Tool names used twice, like one named after a built-in: ${twice.join(\", \")}`)\n : Loop.run({\n instructions: system,\n toolkit,\n history: history(view),\n maxSteps: options.maxSteps ?? 16\n })\n\n return Stream.concat(loop, replyPass({ instructions: system, toolkit, view, nudge: options.replyNudge }))\n }\n }\n}\n\n// Every event the handler's loop emits is recorded before the loop is pulled again.\n/** @internal */\nexport const run = Effect.fnUntraced(\n function* (options: {\n readonly registration: Registration\n readonly handler: TurnHandler\n readonly chat: Writer\n readonly services: Context.Context<Assistant.TurnServices | LanguageModel.LanguageModel>\n // A chat the app deleted is deleted here, and the turn stops.\n readonly checkOpen: Effect.Effect<void>\n }) {\n const { chat } = options\n const before = yield* chat.entries\n\n yield* Telemetry.agentInput(History.unhandledInputs(before, options.registration.definition.app.wakes))\n\n const assistant = options.registration.definition.name\n const traceId = yield* Telemetry.currentTraceId\n\n const started = yield* chat.append([\n AssistantEvent.TurnStarted.make(\n Option.match(traceId, {\n onNone: () => ({ assistant }),\n onSome: (id) => ({ assistant, traceId: id })\n })\n )\n ])\n\n const startSeq = lastSeq(started, 0)\n const turn = makeTurnApi(options.registration, chat, startSeq, options.checkOpen)\n\n const record = (event: AssistantLoop.Event) => chat.append([event])\n\n yield* options.handler(turn).pipe(\n Stream.runForEach(record),\n Effect.onExit((exit) => (Exit.hasInterrupts(exit) ? Effect.void : closeOpenCalls(chat, startSeq))),\n // A loop the app writes gets the assistant's own tools too, with this turn's services.\n Effect.provideService(\n Loop.TurnTools,\n Option.some(Toolkits.provide(options.registration.builtins, options.services))\n ),\n Effect.provideContext(options.services),\n Effect.matchCauseEffect({\n onSuccess: Effect.fnUntraced(function* () {\n const all = yield* chat.entries\n\n yield* finishTurn(chat, handledThrough(all, startSeq), options.registration.definition.app.wakes)\n }),\n onFailure: (cause) => failTurn(chat, cause)\n })\n )\n\n // The trace's output is what the user saw this turn, and a failure if any.\n const shown = (yield* chat.entries).flatMap(({ event, seq }) =>\n seq > startSeq && (Predicate.isTagged(event, \"AssistantMessage\") || Predicate.isTagged(event, \"TurnFailed\"))\n ? [event]\n : []\n )\n\n yield* Telemetry.agentOutput(shown)\n },\n (effect, options) =>\n effect.pipe(\n Telemetry.agentSpan(options.registration.definition.name, {\n chatId: options.chat.chatId,\n root: true\n })\n )\n)\n"],"mappings":"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"}
|
|
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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"}
|
package/package.json
CHANGED