effect-assistant 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +22 -2
- package/dist/AppEvent.d.ts +111 -0
- package/dist/AppEvent.d.ts.map +1 -0
- package/dist/AppEvent.js +71 -0
- package/dist/AppEvent.js.map +1 -0
- package/dist/Assistant.d.ts +403 -0
- package/dist/Assistant.d.ts.map +1 -0
- package/dist/Assistant.js +126 -0
- package/dist/Assistant.js.map +1 -0
- package/dist/AssistantActivity.d.ts +85 -0
- package/dist/AssistantActivity.d.ts.map +1 -0
- package/dist/AssistantActivity.js +105 -0
- package/dist/AssistantActivity.js.map +1 -0
- package/dist/AssistantEngine.d.ts +116 -0
- package/dist/AssistantEngine.d.ts.map +1 -0
- package/dist/AssistantEngine.js +255 -0
- package/dist/AssistantEngine.js.map +1 -0
- package/dist/AssistantEvent.d.ts +614 -0
- package/dist/AssistantEvent.d.ts.map +1 -0
- package/dist/AssistantEvent.js +523 -0
- package/dist/AssistantEvent.js.map +1 -0
- package/dist/AssistantLoop.d.ts +171 -0
- package/dist/AssistantLoop.d.ts.map +1 -0
- package/dist/AssistantLoop.js +108 -0
- package/dist/AssistantLoop.js.map +1 -0
- package/dist/AssistantToolCall.d.ts +32 -0
- package/dist/AssistantToolCall.d.ts.map +1 -0
- package/dist/AssistantToolCall.js +37 -0
- package/dist/AssistantToolCall.js.map +1 -0
- package/dist/Block.d.ts +69 -0
- package/dist/Block.d.ts.map +1 -0
- package/dist/Block.js +39 -0
- package/dist/Block.js.map +1 -0
- package/dist/BlockKit.d.ts +184 -0
- package/dist/BlockKit.d.ts.map +1 -0
- package/dist/BlockKit.js +151 -0
- package/dist/BlockKit.js.map +1 -0
- package/dist/ChatId.d.ts +31 -0
- package/dist/ChatId.d.ts.map +1 -0
- package/dist/ChatId.js +36 -0
- package/dist/ChatId.js.map +1 -0
- package/dist/ChatLog.d.ts +109 -0
- package/dist/ChatLog.d.ts.map +1 -0
- package/dist/ChatLog.js +177 -0
- package/dist/ChatLog.js.map +1 -0
- package/dist/ChatView.d.ts +459 -0
- package/dist/ChatView.d.ts.map +1 -0
- package/dist/ChatView.js +496 -0
- package/dist/ChatView.js.map +1 -0
- package/dist/Chats.d.ts +110 -0
- package/dist/Chats.d.ts.map +1 -0
- package/dist/Chats.js +98 -0
- package/dist/Chats.js.map +1 -0
- package/dist/Compaction.d.ts +105 -0
- package/dist/Compaction.d.ts.map +1 -0
- package/dist/Compaction.js +133 -0
- package/dist/Compaction.js.map +1 -0
- package/dist/CurrentChat.d.ts +40 -0
- package/dist/CurrentChat.d.ts.map +1 -0
- package/dist/CurrentChat.js +33 -0
- package/dist/CurrentChat.js.map +1 -0
- package/dist/Decision.d.ts +145 -0
- package/dist/Decision.d.ts.map +1 -0
- package/dist/Decision.js +178 -0
- package/dist/Decision.js.map +1 -0
- package/dist/DecisionId.d.ts +37 -0
- package/dist/DecisionId.d.ts.map +1 -0
- package/dist/DecisionId.js +44 -0
- package/dist/DecisionId.js.map +1 -0
- package/dist/InputId.d.ts +38 -0
- package/dist/InputId.d.ts.map +1 -0
- package/dist/InputId.js +43 -0
- package/dist/InputId.js.map +1 -0
- package/dist/Instructions.d.ts +56 -0
- package/dist/Instructions.d.ts.map +1 -0
- package/dist/Instructions.js +50 -0
- package/dist/Instructions.js.map +1 -0
- package/dist/Memory.d.ts +190 -0
- package/dist/Memory.d.ts.map +1 -0
- package/dist/Memory.js +216 -0
- package/dist/Memory.js.map +1 -0
- package/dist/RunId.d.ts +41 -0
- package/dist/RunId.d.ts.map +1 -0
- package/dist/RunId.js +46 -0
- package/dist/RunId.js.map +1 -0
- package/dist/Skill.d.ts +72 -0
- package/dist/Skill.d.ts.map +1 -0
- package/dist/Skill.js +58 -0
- package/dist/Skill.js.map +1 -0
- package/dist/Specialist.d.ts +267 -0
- package/dist/Specialist.d.ts.map +1 -0
- package/dist/Specialist.js +119 -0
- package/dist/Specialist.js.map +1 -0
- package/dist/ToolDiscovery.d.ts +108 -0
- package/dist/ToolDiscovery.d.ts.map +1 -0
- package/dist/ToolDiscovery.js +139 -0
- package/dist/ToolDiscovery.js.map +1 -0
- package/dist/ToolFailure.d.ts +66 -0
- package/dist/ToolFailure.d.ts.map +1 -0
- package/dist/ToolFailure.js +79 -0
- package/dist/ToolFailure.js.map +1 -0
- package/dist/_virtual/_rolldown/runtime.js +13 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +26 -0
- package/dist/internal/chat.js +202 -0
- package/dist/internal/chat.js.map +1 -0
- package/dist/internal/decisions.js +66 -0
- package/dist/internal/decisions.js.map +1 -0
- package/dist/internal/events.d.ts +46 -0
- package/dist/internal/events.d.ts.map +1 -0
- package/dist/internal/events.js +65 -0
- package/dist/internal/events.js.map +1 -0
- package/dist/internal/history.js +185 -0
- package/dist/internal/history.js.map +1 -0
- package/dist/internal/instructions.js +11 -0
- package/dist/internal/instructions.js.map +1 -0
- package/dist/internal/loop.d.ts +73 -0
- package/dist/internal/loop.d.ts.map +1 -0
- package/dist/internal/loop.js +166 -0
- package/dist/internal/loop.js.map +1 -0
- package/dist/internal/schema.js +14 -0
- package/dist/internal/schema.js.map +1 -0
- package/dist/internal/skills.js +28 -0
- package/dist/internal/skills.js.map +1 -0
- package/dist/internal/specialists.js +273 -0
- package/dist/internal/specialists.js.map +1 -0
- package/dist/internal/telemetry.js +85 -0
- package/dist/internal/telemetry.js.map +1 -0
- package/dist/internal/toolkit.d.ts +76 -0
- package/dist/internal/toolkit.d.ts.map +1 -0
- package/dist/internal/toolkit.js +129 -0
- package/dist/internal/toolkit.js.map +1 -0
- package/dist/internal/tools.js +132 -0
- package/dist/internal/tools.js.map +1 -0
- package/dist/internal/turns.d.ts +64 -0
- package/dist/internal/turns.d.ts.map +1 -0
- package/dist/internal/turns.js +221 -0
- package/dist/internal/turns.js.map +1 -0
- package/dist/internal/uuid.js +45 -0
- package/dist/internal/uuid.js.map +1 -0
- package/dist/internal/writer.d.ts +12 -0
- package/dist/internal/writer.d.ts.map +1 -0
- package/package.json +51 -3
- package/src/AppEvent.ts +134 -0
- package/src/Assistant.ts +589 -0
- package/src/AssistantActivity.ts +150 -0
- package/src/AssistantEngine.ts +343 -0
- package/src/AssistantEvent.ts +635 -0
- package/src/AssistantLoop.ts +207 -0
- package/src/AssistantToolCall.ts +40 -0
- package/src/Block.ts +84 -0
- package/src/BlockKit.ts +311 -0
- package/src/ChatId.ts +36 -0
- package/src/ChatLog.ts +247 -0
- package/src/ChatView.ts +658 -0
- package/src/Chats.ts +122 -0
- package/src/Compaction.ts +224 -0
- package/src/CurrentChat.ts +47 -0
- package/src/Decision.ts +241 -0
- package/src/DecisionId.ts +46 -0
- package/src/InputId.ts +43 -0
- package/src/Instructions.ts +72 -0
- package/src/Memory.ts +276 -0
- package/src/RunId.ts +46 -0
- package/src/Skill.ts +91 -0
- package/src/Specialist.ts +355 -0
- package/src/ToolDiscovery.ts +187 -0
- package/src/ToolFailure.ts +74 -0
- package/src/index.ts +130 -0
- package/src/internal/chat.ts +372 -0
- package/src/internal/decisions.ts +131 -0
- package/src/internal/events.ts +100 -0
- package/src/internal/history.ts +366 -0
- package/src/internal/instructions.ts +9 -0
- package/src/internal/loop.ts +344 -0
- package/src/internal/schema.ts +25 -0
- package/src/internal/skills.ts +37 -0
- package/src/internal/specialists.ts +414 -0
- package/src/internal/telemetry.ts +125 -0
- package/src/internal/toolkit.ts +231 -0
- package/src/internal/tools.ts +210 -0
- package/src/internal/turns.ts +429 -0
- package/src/internal/uuid.ts +50 -0
- package/src/internal/writer.ts +12 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { fromCallId } from "../DecisionId.js";
|
|
2
|
+
import { CallOrigin, DecisionRequested, ModelOutput, ModelPart, ToolResult, ToolStarted } from "../AssistantEvent.js";
|
|
3
|
+
import { TurnDecisions } from "./decisions.js";
|
|
4
|
+
import { unansweredCalls } from "./history.js";
|
|
5
|
+
import { chatSpan, toolOutput, toolSpan } from "./telemetry.js";
|
|
6
|
+
import { empty, failed, needsDecisionOf } from "./toolkit.js";
|
|
7
|
+
import { isTurnEnding } from "./tools.js";
|
|
8
|
+
import * as Predicate from "effect/Predicate";
|
|
9
|
+
import * as Context from "effect/Context";
|
|
10
|
+
import * as Effect from "effect/Effect";
|
|
11
|
+
import * as Schema from "effect/Schema";
|
|
12
|
+
import * as Stream from "effect/Stream";
|
|
13
|
+
import * as Data from "effect/Data";
|
|
14
|
+
import * as Option from "effect/Option";
|
|
15
|
+
import * as Clock from "effect/Clock";
|
|
16
|
+
import * as Prompt from "effect/ai/Prompt";
|
|
17
|
+
import * as LanguageModel from "effect/ai/LanguageModel";
|
|
18
|
+
import * as Model from "effect/ai/Model";
|
|
19
|
+
//#region src/internal/loop.ts
|
|
20
|
+
/** @internal */
|
|
21
|
+
const TurnTools = Context.Reference("effect-assistant/TurnTools", { defaultValue: Option.none });
|
|
22
|
+
const readHistory = (read) => "messages" in read ? {
|
|
23
|
+
messages: read.messages,
|
|
24
|
+
throughSeq: read.throughSeq
|
|
25
|
+
} : {
|
|
26
|
+
messages: read,
|
|
27
|
+
throughSeq: void 0
|
|
28
|
+
};
|
|
29
|
+
const requestToolkit = (definitions) => ({
|
|
30
|
+
tools: Object.fromEntries(definitions.map((tool) => [tool.name, tool.needsApproval === void 0 ? tool : tool.setNeedsApproval(false)])),
|
|
31
|
+
handle: () => Effect.succeed(Stream.empty)
|
|
32
|
+
});
|
|
33
|
+
const generate = (prompt, definitions, toolChoice) => LanguageModel.generateText({
|
|
34
|
+
prompt,
|
|
35
|
+
toolkit: requestToolkit(definitions),
|
|
36
|
+
toolChoice
|
|
37
|
+
});
|
|
38
|
+
const decodePart = Schema.decodeUnknownOption(ModelPart);
|
|
39
|
+
const modelParts = (content) => content.flatMap((part) => Option.toArray(decodePart(part)).filter((decoded) => !(decoded.type === "tool-call" && decoded.providerExecuted)));
|
|
40
|
+
const toolCalls = (parts) => parts.flatMap((part) => part.type === "tool-call" ? [part] : []);
|
|
41
|
+
const runCall = Effect.fnUntraced(function* (tools, call) {
|
|
42
|
+
const result = yield* Effect.gen(function* () {
|
|
43
|
+
const result = yield* tools.run(call);
|
|
44
|
+
yield* toolOutput(result.result);
|
|
45
|
+
return result;
|
|
46
|
+
}).pipe(toolSpan(call));
|
|
47
|
+
return ToolResult.make({
|
|
48
|
+
callId: call.id,
|
|
49
|
+
tool: call.name,
|
|
50
|
+
...result
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
const park = Effect.fnUntraced(function* (call) {
|
|
54
|
+
if (Option.isNone(yield* TurnDecisions)) return ToolResult.make({
|
|
55
|
+
callId: call.id,
|
|
56
|
+
tool: call.name,
|
|
57
|
+
...failed(`${call.name} needs the user's decision, and only the assistant can ask them. Don't run it: say in your answer what needs deciding, with the details.`, "NeedsDecision")
|
|
58
|
+
});
|
|
59
|
+
return DecisionRequested.make({
|
|
60
|
+
id: fromCallId(call.id),
|
|
61
|
+
origin: CallOrigin.make({
|
|
62
|
+
callId: call.id,
|
|
63
|
+
tool: call.name,
|
|
64
|
+
params: call.params
|
|
65
|
+
})
|
|
66
|
+
});
|
|
67
|
+
}, (effect, call) => effect.pipe(toolSpan(call)));
|
|
68
|
+
const runCalls = (tools, pending, history, then) => Stream.unwrap(Effect.gen(function* () {
|
|
69
|
+
const parks = yield* Effect.forEach(pending, ({ call, approved }) => approved ? Effect.succeed(false) : needsDecisionOf(tools, call, history));
|
|
70
|
+
const started = pending.flatMap(({ call }, i) => parks[i] ? [] : [value(ToolStarted.make({
|
|
71
|
+
callId: call.id,
|
|
72
|
+
tool: call.name
|
|
73
|
+
}))]);
|
|
74
|
+
const results = Effect.forEach(pending, ({ call }, i) => parks[i] ? park(call) : runCall(tools, call), { concurrency: "unbounded" });
|
|
75
|
+
const reread = (done) => done.some((event) => Predicate.isTagged(event, "DecisionRequested") || isFailure(event));
|
|
76
|
+
return Stream.fromIterable(started).pipe(Stream.concat(Stream.unwrap(Effect.gen(function* () {
|
|
77
|
+
const done = yield* results;
|
|
78
|
+
return Stream.fromIterable(done.map(value)).pipe(Stream.concat(then(reread(done))));
|
|
79
|
+
}))));
|
|
80
|
+
}));
|
|
81
|
+
const isFailure = (event) => Predicate.isTagged(event, "ToolResult") && event.outcome === "failure";
|
|
82
|
+
const Steps = Data.taggedEnum();
|
|
83
|
+
/** @internal */
|
|
84
|
+
const value = (value) => Steps.Value({ value });
|
|
85
|
+
/** @internal */
|
|
86
|
+
const next = (state) => Stream.succeed(Steps.Next({ state }));
|
|
87
|
+
/** @internal */
|
|
88
|
+
const stop = () => Stream.succeed(Steps.Stop());
|
|
89
|
+
const isValue = (step) => Predicate.isTagged(step, "Value");
|
|
90
|
+
/** @internal */
|
|
91
|
+
const loop = (initial, body) => {
|
|
92
|
+
const go = (state) => Stream.suspend(() => {
|
|
93
|
+
let after = Option.none();
|
|
94
|
+
return body(state).pipe(Stream.tap((step) => Effect.sync(() => after = Predicate.isTagged(step, "Next") ? Option.some(step.state) : after)), Stream.filter(isValue), Stream.map((step) => step.value), Stream.concat(Stream.suspend(() => Option.match(after, {
|
|
95
|
+
onNone: () => Stream.empty,
|
|
96
|
+
onSome: go
|
|
97
|
+
}))));
|
|
98
|
+
});
|
|
99
|
+
return go(initial);
|
|
100
|
+
};
|
|
101
|
+
/** @internal */
|
|
102
|
+
const step = (options, then) => {
|
|
103
|
+
const toolkit = options.toolkit ?? empty;
|
|
104
|
+
const instructions = Predicate.isString(options.instructions) ? { text: options.instructions } : options.instructions;
|
|
105
|
+
const after = (result) => Stream.suspend(() => then(result));
|
|
106
|
+
return Stream.unwrap(Effect.gen(function* () {
|
|
107
|
+
const { messages: history, throughSeq } = readHistory(yield* options.history);
|
|
108
|
+
const cutOff = unansweredCalls(history);
|
|
109
|
+
const decisions = yield* TurnDecisions;
|
|
110
|
+
const approved = Option.isNone(decisions) ? [] : (yield* decisions.value.approved).filter((call) => toolkit.definitions.some((tool) => tool.name === call.name));
|
|
111
|
+
if (cutOff.length > 0 || approved.length > 0) {
|
|
112
|
+
const pending = [...cutOff.map((call) => ({
|
|
113
|
+
call,
|
|
114
|
+
approved: false
|
|
115
|
+
})), ...approved.map((call) => ({
|
|
116
|
+
call,
|
|
117
|
+
approved: true
|
|
118
|
+
}))];
|
|
119
|
+
return runCalls(toolkit, pending, history, () => after({
|
|
120
|
+
output: void 0,
|
|
121
|
+
done: false,
|
|
122
|
+
historyLength: history.length + pending.length
|
|
123
|
+
}));
|
|
124
|
+
}
|
|
125
|
+
const prompt = Prompt.fromMessages([Prompt.systemMessage({ content: instructions.text }), ...history]);
|
|
126
|
+
const name = yield* Effect.serviceOption(Model.ModelName);
|
|
127
|
+
const started = yield* Clock.currentTimeMillis;
|
|
128
|
+
const response = yield* generate(prompt, toolkit.definitions, options.toolChoice).pipe(chatSpan(prompt, instructions.attributes));
|
|
129
|
+
const metadata = response.content.find((part) => part.type === "response-metadata");
|
|
130
|
+
const output = ModelOutput.make({
|
|
131
|
+
parts: modelParts(response.content),
|
|
132
|
+
seenThroughSeq: throughSeq,
|
|
133
|
+
model: metadata?.modelId ?? Option.getOrUndefined(name),
|
|
134
|
+
responseId: metadata?.id,
|
|
135
|
+
usage: response.usage,
|
|
136
|
+
durationMillis: (yield* Clock.currentTimeMillis) - started
|
|
137
|
+
});
|
|
138
|
+
const calls = toolCalls(output.parts);
|
|
139
|
+
const historyLength = history.length + Prompt.fromResponseParts(output.parts).content.length + calls.length;
|
|
140
|
+
return Stream.make(value(output)).pipe(Stream.concat(runCalls(toolkit, calls.map((call) => ({
|
|
141
|
+
call,
|
|
142
|
+
approved: false
|
|
143
|
+
})), history, (reread) => after({
|
|
144
|
+
output,
|
|
145
|
+
done: calls.length === 0 || !reread && calls.some(isTurnEnding),
|
|
146
|
+
historyLength
|
|
147
|
+
}))));
|
|
148
|
+
}));
|
|
149
|
+
};
|
|
150
|
+
/** @internal */
|
|
151
|
+
const run = (options) => {
|
|
152
|
+
const maxSteps = options.maxSteps ?? 16;
|
|
153
|
+
const afterDone = (steps, historyLength) => Stream.unwrap(Effect.gen(function* () {
|
|
154
|
+
const read = yield* options.history;
|
|
155
|
+
return readHistory(read).messages.length > historyLength ? next(steps) : stop();
|
|
156
|
+
}));
|
|
157
|
+
return loop(0, (steps) => step(options, (result) => {
|
|
158
|
+
const taken = result.output === void 0 ? steps : steps + 1;
|
|
159
|
+
if (taken >= maxSteps) return stop();
|
|
160
|
+
return result.done ? afterDone(taken, result.historyLength) : next(taken);
|
|
161
|
+
}));
|
|
162
|
+
};
|
|
163
|
+
//#endregion
|
|
164
|
+
export { TurnTools, loop, next, run, step, stop, value };
|
|
165
|
+
|
|
166
|
+
//# sourceMappingURL=loop.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loop.js","names":["AssistantEvent.ModelPart","Telemetry.toolOutput","Telemetry.toolSpan","Decisions.TurnDecisions","Toolkits.failed","DecisionId.fromCallId","Toolkits.needsDecisionOf","Toolkits.empty","History.unansweredCalls","Telemetry.chatSpan"],"sources":["../../src/internal/loop.ts"],"sourcesContent":["import type * as AiError from \"effect/ai/AiError\"\nimport * as LanguageModel from \"effect/ai/LanguageModel\"\nimport * as Model from \"effect/ai/Model\"\nimport * as Prompt from \"effect/ai/Prompt\"\nimport type * as Response from \"effect/ai/Response\"\nimport type * as Tool from \"effect/ai/Tool\"\nimport type * as Toolkit from \"effect/ai/Toolkit\"\nimport * as Clock from \"effect/Clock\"\nimport * as Context from \"effect/Context\"\nimport * as Data from \"effect/Data\"\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 * as AssistantEvent from \"../AssistantEvent.ts\"\nimport * as DecisionId from \"../DecisionId.ts\"\nimport type * as Instructions from \"../Instructions.ts\"\nimport * as Decisions from \"./decisions.ts\"\nimport * as History from \"./history.ts\"\nimport * as Telemetry from \"./telemetry.ts\"\nimport * as Toolkits from \"./toolkit.ts\"\nimport { isTurnEnding } from \"./tools.ts\"\n\n/** @internal */\nexport type Event =\n | AssistantEvent.ModelOutput\n | AssistantEvent.ToolStarted\n | AssistantEvent.ToolResult\n | AssistantEvent.DecisionRequested\n\n// The assistant's own tools with the turn's services, so an app's own loop in a turn can message the user. A\n// specialist's run has none.\n/** @internal */\nexport const TurnTools = Context.Reference<Option.Option<Toolkits.Tools>>(\"effect-assistant/TurnTools\", {\n defaultValue: Option.none\n})\n\n/** @internal */\nexport interface ChatHistory {\n readonly messages: ReadonlyArray<Prompt.Message>\n readonly throughSeq: number\n}\n\nconst readHistory = (read: ReadonlyArray<Prompt.Message> | ChatHistory) =>\n \"messages\" in read\n ? { messages: read.messages, throughSeq: read.throughSeq }\n : { messages: read, throughSeq: undefined }\n\n// `AssistantLoop` takes native toolkits; turns and specialist runs come here with their tools already merged.\n/** @internal */\nexport interface StepOptions<R = never> {\n readonly instructions: string | Instructions.Resolved\n readonly toolkit?: Toolkits.Tools<R> | undefined\n readonly history: Effect.Effect<ReadonlyArray<Prompt.Message> | ChatHistory>\n readonly toolChoice?: LanguageModel.ToolChoice<string> | undefined\n}\n\n/** @internal */\nexport interface Options<R = never> extends StepOptions<R> {\n readonly maxSteps?: number | undefined\n}\n\n// Its member docs show through `AssistantLoop.StepResult`.\n/** @internal */\nexport interface StepResult {\n /** The model's output; unset when the step only ran calls a crash cut off. */\n readonly output: AssistantEvent.ModelOutput | undefined\n /**\n * Whether the model is done: it made no tool calls, or ended its turn (`wait`, or a message\n * with `wait: true`) and none of the step's calls failed.\n */\n readonly done: boolean\n /**\n * The history's length with this step's output and results: a longer one got something new meanwhile.\n */\n readonly historyLength: number\n}\n\n// The loop runs the calls itself, after storing each as started, so the model call runs none.\nconst requestToolkit = (definitions: ReadonlyArray<Tool.Any>): Toolkit.WithHandler<Record<string, Tool.Any>> => ({\n // The loop decides which calls wait for the user's decision; the model call would ask for approvals of its own.\n tools: Object.fromEntries(\n definitions.map((tool) => [tool.name, tool.needsApproval === undefined ? tool : tool.setNeedsApproval(false)])\n ),\n handle: () => Effect.succeed(Stream.empty)\n})\n\n// Tool call resolution stays on with a toolkit that runs nothing: with it off, a call whose params don't fit its\n// tool's schema fails the whole model call, and here the model reads why instead.\nconst generate = (\n prompt: Prompt.Prompt,\n definitions: ReadonlyArray<Tool.Any>,\n toolChoice: LanguageModel.ToolChoice<string> | undefined\n): Effect.Effect<\n LanguageModel.GenerateTextResponse<Record<string, Tool.Any>, \"opaque\">,\n AiError.AiError,\n LanguageModel.LanguageModel\n> => LanguageModel.generateText({ prompt, toolkit: requestToolkit(definitions), toolChoice })\n\nconst decodePart = Schema.decodeUnknownOption(AssistantEvent.ModelPart)\n\nconst modelParts = (content: ReadonlyArray<Response.AllParts<Record<string, Tool.Any>>>) =>\n content.flatMap((part) =>\n Option.toArray(decodePart(part)).filter((decoded) => !(decoded.type === \"tool-call\" && decoded.providerExecuted))\n )\n\nconst toolCalls = (parts: ReadonlyArray<AssistantEvent.ModelPart>): ReadonlyArray<AssistantEvent.ToolCallPart> =>\n parts.flatMap((part) => (part.type === \"tool-call\" ? [part] : []))\n\nconst runCall = Effect.fnUntraced(function* <R>(tools: Toolkits.Tools<R>, call: AssistantEvent.ToolCallPart) {\n const result = yield* Effect.gen(function* () {\n const result = yield* tools.run(call)\n\n yield* Telemetry.toolOutput(result.result)\n\n return result\n }).pipe(Telemetry.toolSpan(call))\n\n return AssistantEvent.ToolResult.make({ callId: call.id, tool: call.name, ...result })\n})\n\ninterface Pending {\n readonly call: AssistantEvent.ToolCallPart\n readonly approved: boolean\n}\n\n// Only a turn parks: a specialist's call fails instead.\nconst park = Effect.fnUntraced(\n function* (call: AssistantEvent.ToolCallPart) {\n if (Option.isNone(yield* Decisions.TurnDecisions)) {\n return AssistantEvent.ToolResult.make({\n callId: call.id,\n tool: call.name,\n ...Toolkits.failed(\n `${call.name} needs the user's decision, and only the assistant can ask them. Don't run it: say in your answer what needs deciding, with the details.`,\n \"NeedsDecision\"\n )\n })\n }\n\n return AssistantEvent.DecisionRequested.make({\n id: DecisionId.fromCallId(call.id),\n origin: AssistantEvent.CallOrigin.make({ callId: call.id, tool: call.name, params: call.params })\n })\n },\n (effect, call) => effect.pipe(Telemetry.toolSpan(call))\n)\n\n// Every `ToolStarted` is stored before any call runs. `then` is told whether the model has to read the results: one\n// failed, or waits for the user's decision.\nconst runCalls = <R, S, E, R2>(\n tools: Toolkits.Tools<R>,\n pending: ReadonlyArray<Pending>,\n history: ReadonlyArray<Prompt.Message>,\n then: (reread: boolean) => Stream.Stream<Step<never, S>, E, R2>\n): Stream.Stream<Step<Event, S>, E, R | R2> =>\n Stream.unwrap(\n Effect.gen(function* () {\n const parks = yield* Effect.forEach(pending, ({ call, approved }) =>\n approved ? Effect.succeed(false) : Toolkits.needsDecisionOf(tools, call, history)\n )\n\n const started = pending.flatMap(({ call }, i) =>\n parks[i] ? [] : [value(AssistantEvent.ToolStarted.make({ callId: call.id, tool: call.name }))]\n )\n\n const results = Effect.forEach(pending, ({ call }, i) => (parks[i] ? park(call) : runCall(tools, call)), {\n concurrency: \"unbounded\"\n })\n\n const reread = (done: ReadonlyArray<Event>) =>\n done.some((event) => Predicate.isTagged(event, \"DecisionRequested\") || isFailure(event))\n\n return Stream.fromIterable(started).pipe(\n Stream.concat(\n Stream.unwrap(\n Effect.gen(function* () {\n const done = yield* results\n\n return Stream.fromIterable(done.map(value)).pipe(Stream.concat(then(reread(done))))\n })\n )\n )\n )\n })\n )\n\nconst isFailure = (event: Event) => Predicate.isTagged(event, \"ToolResult\") && event.outcome === \"failure\"\n\n/** @internal */\nexport type Step<A, S> = Data.TaggedEnum<{\n Value: { readonly value: A }\n Next: { readonly state: S }\n Stop: Record<never, never>\n}>\n\ninterface StepDefinition extends Data.TaggedEnum.WithGenerics<2> {\n readonly taggedEnum: Step<this[\"A\"], this[\"B\"]>\n}\n\nconst Steps = Data.taggedEnum<StepDefinition>()\n\n/** @internal */\nexport const value = <A>(value: A): Step<A, never> => Steps.Value({ value })\n\n/** @internal */\nexport const next = <S>(state: S): Stream.Stream<Step<never, S>> => Stream.succeed(Steps.Next({ state }))\n\n/** @internal */\n// oxlint-disable-next-line no-unnecessary-type-parameters -- `stop<number>()` types a loop's state where inference can't.\nexport const stop = <S = never>(): Stream.Stream<Step<never, S>> => Stream.succeed(Steps.Stop())\n\nconst isValue = <A, S>(step: Step<A, S>): step is Data.TaggedEnum.Value<Step<A, S>, \"Value\"> =>\n Predicate.isTagged(step, \"Value\")\n\n// A body that ends without `next` or `stop` stops too.\n/** @internal */\nexport const loop = <S, A, E, R>(\n initial: S,\n body: (state: S) => Stream.Stream<Step<A, S>, E, R>\n): Stream.Stream<A, E, R> => {\n const go = (state: S): Stream.Stream<A, E, R> =>\n Stream.suspend(() => {\n let after: Option.Option<S> = Option.none()\n\n return body(state).pipe(\n Stream.tap((step) =>\n Effect.sync(() => (after = Predicate.isTagged(step, \"Next\") ? Option.some(step.state) : after))\n ),\n Stream.filter(isValue),\n Stream.map((step) => step.value),\n Stream.concat(Stream.suspend(() => Option.match(after, { onNone: () => Stream.empty, onSome: go })))\n )\n })\n\n return go(initial)\n}\n\n/** @internal */\nexport const step = <R, S, E2 = never, R2 = never>(\n options: StepOptions<R>,\n then: (result: StepResult) => Stream.Stream<Step<never, S>, E2, R2>\n): Stream.Stream<Step<Event, S>, AiError.AiError | E2, LanguageModel.LanguageModel | R | R2> => {\n const toolkit = options.toolkit ?? Toolkits.empty\n const instructions = Predicate.isString(options.instructions) ? { text: options.instructions } : options.instructions\n\n const after = (result: StepResult) => Stream.suspend(() => then(result))\n\n return Stream.unwrap(\n Effect.gen(function* () {\n const { messages: history, throughSeq } = readHistory(yield* options.history)\n // Calls a crash cut off, with no result: they run again, with the same ids, before the model goes on.\n const cutOff = History.unansweredCalls(history)\n\n // Calls the user approved: they run as the model made them, before the model goes on. Not in a loop without them.\n const decisions = yield* Decisions.TurnDecisions\n\n const approved = Option.isNone(decisions)\n ? []\n : (yield* decisions.value.approved).filter((call) =>\n toolkit.definitions.some((tool) => tool.name === call.name)\n )\n\n if (cutOff.length > 0 || approved.length > 0) {\n const pending = [\n ...cutOff.map((call) => ({ call, approved: false })),\n ...approved.map((call) => ({ call, approved: true }))\n ]\n\n return runCalls(toolkit, pending, history, () =>\n after({ output: undefined, done: false, historyLength: history.length + pending.length })\n )\n }\n\n const prompt = Prompt.fromMessages([Prompt.systemMessage({ content: instructions.text }), ...history])\n const name = yield* Effect.serviceOption(Model.ModelName)\n const started = yield* Clock.currentTimeMillis\n\n const response = yield* generate(prompt, toolkit.definitions, options.toolChoice).pipe(\n Telemetry.chatSpan(prompt, instructions.attributes)\n )\n\n const metadata = response.content.find((part) => part.type === \"response-metadata\")\n\n const output = AssistantEvent.ModelOutput.make({\n parts: modelParts(response.content),\n seenThroughSeq: throughSeq,\n model: metadata?.modelId ?? Option.getOrUndefined(name),\n responseId: metadata?.id,\n usage: response.usage,\n durationMillis: (yield* Clock.currentTimeMillis) - started\n })\n\n const calls = toolCalls(output.parts)\n const historyLength = history.length + Prompt.fromResponseParts(output.parts).content.length + calls.length\n\n // A failed call doesn't end the turn: a message that failed never reached the user, so the model reads why. Nor\n // does a parked one: the model asks the user for the decision it waits for.\n return Stream.make(value(output)).pipe(\n Stream.concat(\n runCalls(\n toolkit,\n calls.map((call) => ({ call, approved: false })),\n history,\n (reread) =>\n after({\n output,\n done: calls.length === 0 || (!reread && calls.some(isTurnEnding)),\n historyLength\n })\n )\n )\n )\n })\n )\n}\n\n/** @internal */\nexport const run = <R = never>(\n options: Options<R>\n): Stream.Stream<Event, AiError.AiError, LanguageModel.LanguageModel | R> => {\n const maxSteps = options.maxSteps ?? 16\n\n // Done: stop, unless something arrived while the model was working; then it reads that first.\n const afterDone = (steps: number, historyLength: number) =>\n Stream.unwrap(\n Effect.gen(function* () {\n const read = yield* options.history\n\n return readHistory(read).messages.length > historyLength ? next(steps) : stop<number>()\n })\n )\n\n return loop(0, (steps: number) =>\n step(options, (result) => {\n const taken = result.output === undefined ? steps : steps + 1\n\n if (taken >= maxSteps) return stop<number>()\n\n return result.done ? afterDone(taken, result.historyLength) : next(taken)\n })\n )\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as Schema from "effect/Schema";
|
|
2
|
+
import * as Option from "effect/Option";
|
|
3
|
+
import { flow } from "effect/Function";
|
|
4
|
+
//#region src/internal/schema.ts
|
|
5
|
+
const JsonText = Schema.fromJsonString(Schema.Unknown);
|
|
6
|
+
/** @internal */
|
|
7
|
+
const toJsonText = flow(Schema.encodeUnknownOption(JsonText), Option.getOrElse(() => ""));
|
|
8
|
+
const decodeJsonText = Schema.decodeOption(Schema.fromJsonString(Schema.Json));
|
|
9
|
+
/** @internal */
|
|
10
|
+
const toJson = flow(Schema.encodeUnknownOption(JsonText), Option.flatMap(decodeJsonText), Option.getOrElse(() => null));
|
|
11
|
+
//#endregion
|
|
12
|
+
export { toJson, toJsonText };
|
|
13
|
+
|
|
14
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","names":[],"sources":["../../src/internal/schema.ts"],"sourcesContent":["import { flow } from \"effect/Function\"\nimport * as Option from \"effect/Option\"\nimport * as Schema from \"effect/Schema\"\n\nconst JsonText = Schema.fromJsonString(Schema.Unknown)\n\n// A value with no JSON form (`undefined`, a cycle) is empty text rather than a defect.\n/** @internal */\nexport const toJsonText = flow(\n Schema.encodeUnknownOption(JsonText),\n Option.getOrElse(() => \"\")\n)\n\n/** @internal */\nexport const isJsonText = (text: string): boolean => Option.isSome(Schema.decodeOption(JsonText)(text))\n\nconst decodeJsonText = Schema.decodeOption(Schema.fromJsonString(Schema.Json))\n\n// As `JSON.stringify` writes it (a `Date` as its ISO string, a class as its fields), or `null` when it has none.\n/** @internal */\nexport const toJson = flow(\n Schema.encodeUnknownOption(JsonText),\n Option.flatMap(decodeJsonText),\n Option.getOrElse((): Schema.Json => null)\n)\n"],"mappings":";;;;AAIA,MAAM,WAAW,OAAO,eAAe,OAAO,OAAO;;AAIrD,MAAa,aAAa,KACxB,OAAO,oBAAoB,QAAQ,GACnC,OAAO,gBAAgB,EAAE,CAC3B;AAKA,MAAM,iBAAiB,OAAO,aAAa,OAAO,eAAe,OAAO,IAAI,CAAC;;AAI7E,MAAa,SAAS,KACpB,OAAO,oBAAoB,QAAQ,GACnC,OAAO,QAAQ,cAAc,GAC7B,OAAO,gBAA6B,IAAI,CAC1C"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { ToolFailure, fail } from "../ToolFailure.js";
|
|
2
|
+
import { empty, withContext } from "./toolkit.js";
|
|
3
|
+
import { resolve } from "../Instructions.js";
|
|
4
|
+
import * as Effect from "effect/Effect";
|
|
5
|
+
import * as Schema from "effect/Schema";
|
|
6
|
+
import * as Tool from "effect/ai/Tool";
|
|
7
|
+
import * as Toolkit from "effect/ai/Toolkit";
|
|
8
|
+
//#region src/internal/skills.ts
|
|
9
|
+
/** @internal */
|
|
10
|
+
const toTools = (skills) => {
|
|
11
|
+
if (skills.length === 0) return empty;
|
|
12
|
+
const byName = new Map(skills.map((skill) => [skill.name, skill]));
|
|
13
|
+
const LoadSkill = Tool.make("load_skill", {
|
|
14
|
+
description: "Load a skill's instructions by name, in the same step as your first lookups (not in a step of its own). Follow them for the rest of the task.",
|
|
15
|
+
parameters: Schema.Struct({ name: Schema.Literals(skills.map((skill) => skill.name)) }),
|
|
16
|
+
success: Schema.String,
|
|
17
|
+
failure: ToolFailure
|
|
18
|
+
});
|
|
19
|
+
return withContext(Toolkit.make(LoadSkill), (context) => ({ load_skill: Effect.fnUntraced(function* ({ name }) {
|
|
20
|
+
const skill = byName.get(name);
|
|
21
|
+
if (skill === void 0) return yield* fail(`No skill named ${name}.`);
|
|
22
|
+
return (yield* resolve(skill.instructions).pipe(Effect.provideContext(context))).text;
|
|
23
|
+
}) }));
|
|
24
|
+
};
|
|
25
|
+
//#endregion
|
|
26
|
+
export { toTools };
|
|
27
|
+
|
|
28
|
+
//# sourceMappingURL=skills.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skills.js","names":["Toolkits.empty","ToolFailure.ToolFailure","Toolkits.withContext","ToolFailure.fail","Instructions.resolve"],"sources":["../../src/internal/skills.ts"],"sourcesContent":["import * as Tool from \"effect/ai/Tool\"\nimport * as Toolkit from \"effect/ai/Toolkit\"\nimport type * as Context from \"effect/Context\"\nimport * as Effect from \"effect/Effect\"\nimport * as Schema from \"effect/Schema\"\nimport * as Instructions from \"../Instructions.ts\"\nimport type { Skill } from \"../Skill.ts\"\nimport * as ToolFailure from \"../ToolFailure.ts\"\nimport * as Toolkits from \"./toolkit.ts\"\n\n/** @internal */\nexport const toTools = <R>(skills: ReadonlyArray<Skill<R>>): Toolkits.Tools<R> => {\n if (skills.length === 0) return Toolkits.empty\n\n const byName = new Map(skills.map((skill) => [skill.name, skill]))\n\n const LoadSkill = Tool.make(\"load_skill\", {\n description:\n \"Load a skill's instructions by name, in the same step as your first lookups (not in a step of its own). Follow them for the rest of the task.\",\n parameters: Schema.Struct({ name: Schema.Literals(skills.map((skill) => skill.name)) }),\n success: Schema.String,\n failure: ToolFailure.ToolFailure\n })\n\n // A skill's instructions resolve with the services of the loop that loads it.\n return Toolkits.withContext(Toolkit.make(LoadSkill), (context: Context.Context<R>) => ({\n load_skill: Effect.fnUntraced(function* ({ name }) {\n const skill = byName.get(name)\n\n if (skill === undefined) return yield* ToolFailure.fail(`No skill named ${name}.`)\n\n const resolved = yield* Instructions.resolve(skill.instructions).pipe(Effect.provideContext(context))\n\n return resolved.text\n })\n }))\n}\n"],"mappings":";;;;;;;;;AAWA,MAAa,WAAc,WAAuD;CAChF,IAAI,OAAO,WAAW,GAAG,OAAOA;CAEhC,MAAM,SAAS,IAAI,IAAI,OAAO,KAAK,UAAU,CAAC,MAAM,MAAM,KAAK,CAAC,CAAC;CAEjE,MAAM,YAAY,KAAK,KAAK,cAAc;EACxC,aACE;EACF,YAAY,OAAO,OAAO,EAAE,MAAM,OAAO,SAAS,OAAO,KAAK,UAAU,MAAM,IAAI,CAAC,EAAE,CAAC;EACtF,SAAS,OAAO;EAChB,SAASC;CACX,CAAC;CAGD,OAAOC,YAAqB,QAAQ,KAAK,SAAS,IAAI,aAAiC,EACrF,YAAY,OAAO,WAAW,WAAW,EAAE,QAAQ;EACjD,MAAM,QAAQ,OAAO,IAAI,IAAI;EAE7B,IAAI,UAAU,KAAA,GAAW,OAAO,OAAOC,KAAiB,kBAAkB,KAAK,EAAE;EAIjF,QAAO,OAFiBC,QAAqB,MAAM,YAAY,CAAC,CAAC,KAAK,OAAO,eAAe,OAAO,CAAC,EAAA,CAEpF;CAClB,CAAC,EACH,EAAE;AACJ"}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { fromKey } from "../InputId.js";
|
|
2
|
+
import { RunId, make } from "../RunId.js";
|
|
3
|
+
import { SpecialistFinished, SpecialistStarted } from "../AssistantEvent.js";
|
|
4
|
+
import { CurrentChat } from "../CurrentChat.js";
|
|
5
|
+
import { Brief, Runner, SpecialistError } from "../Specialist.js";
|
|
6
|
+
import { TurnDecisions } from "./decisions.js";
|
|
7
|
+
import { toolMessage, userText } from "./history.js";
|
|
8
|
+
import { agentOutput, agentSpan } from "./telemetry.js";
|
|
9
|
+
import { ToolFailure, fail } from "../ToolFailure.js";
|
|
10
|
+
import { empty, fromToolkitOrEmpty, fromWithHandler, merge } from "./toolkit.js";
|
|
11
|
+
import { TurnTools, run } from "./loop.js";
|
|
12
|
+
import { withCatalog } from "../Skill.js";
|
|
13
|
+
import { toTools } from "./skills.js";
|
|
14
|
+
import { mapText } from "./instructions.js";
|
|
15
|
+
import * as Predicate from "effect/Predicate";
|
|
16
|
+
import * as Context from "effect/Context";
|
|
17
|
+
import * as Effect from "effect/Effect";
|
|
18
|
+
import * as Schema from "effect/Schema";
|
|
19
|
+
import * as Stream from "effect/Stream";
|
|
20
|
+
import * as Exit from "effect/Exit";
|
|
21
|
+
import * as Option from "effect/Option";
|
|
22
|
+
import * as Ref from "effect/Ref";
|
|
23
|
+
import * as Cause from "effect/Cause";
|
|
24
|
+
import * as Fiber from "effect/Fiber";
|
|
25
|
+
import * as FiberSet from "effect/FiberSet";
|
|
26
|
+
import * as Semaphore from "effect/Semaphore";
|
|
27
|
+
import * as Prompt from "effect/ai/Prompt";
|
|
28
|
+
import * as Tool from "effect/ai/Tool";
|
|
29
|
+
import * as Toolkit from "effect/ai/Toolkit";
|
|
30
|
+
import * as FiberMap from "effect/FiberMap";
|
|
31
|
+
import * as Queue from "effect/Queue";
|
|
32
|
+
//#region src/internal/specialists.ts
|
|
33
|
+
/** @internal */
|
|
34
|
+
const briefText = (brief) => [
|
|
35
|
+
`Goal: ${brief.goal}`,
|
|
36
|
+
brief.context && `Context: ${brief.context}`,
|
|
37
|
+
brief.successCriteria && `Done when: ${brief.successCriteria}`,
|
|
38
|
+
brief.report && `Report back: ${brief.report}`,
|
|
39
|
+
brief.limits && `Limits: ${brief.limits}`
|
|
40
|
+
].filter(Boolean).join("\n");
|
|
41
|
+
const finalText = (output) => output.parts.flatMap((part) => part.type === "text" ? [part.text] : []).join("").trim();
|
|
42
|
+
/** @internal */
|
|
43
|
+
const runBrief = Effect.fnUntraced(function* (handler, options) {
|
|
44
|
+
const { history, inbox } = options;
|
|
45
|
+
const answer = yield* Ref.make("");
|
|
46
|
+
const add = (messages) => Ref.update(history, (all) => [...all, ...messages]);
|
|
47
|
+
const current = Effect.gen(function* () {
|
|
48
|
+
yield* add(yield* Queue.clear(inbox));
|
|
49
|
+
return yield* Ref.get(history);
|
|
50
|
+
});
|
|
51
|
+
const run$1 = {
|
|
52
|
+
runId: options.runId,
|
|
53
|
+
chatId: options.chatId,
|
|
54
|
+
brief: options.brief,
|
|
55
|
+
history: current,
|
|
56
|
+
loop: (loop) => {
|
|
57
|
+
const skills = loop.skills ?? [];
|
|
58
|
+
return run({
|
|
59
|
+
instructions: mapText(loop.instructions, (text) => withCatalog(text, skills)),
|
|
60
|
+
toolkit: merge(toTools(skills), fromToolkitOrEmpty(loop.toolkit)),
|
|
61
|
+
history: current,
|
|
62
|
+
maxSteps: loop.maxSteps ?? 8
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
const record = Effect.fnUntraced(function* (event) {
|
|
67
|
+
if (Predicate.isTagged(event, "ToolStarted") || Predicate.isTagged(event, "DecisionRequested")) return;
|
|
68
|
+
if (Predicate.isTagged(event, "ToolResult")) return yield* add([toolMessage(event)]);
|
|
69
|
+
yield* add(Prompt.fromResponseParts(event.parts).content);
|
|
70
|
+
const text = finalText(event);
|
|
71
|
+
if (text.length > 0) yield* Ref.set(answer, text);
|
|
72
|
+
});
|
|
73
|
+
yield* Stream.runForEach(handler(run$1), record);
|
|
74
|
+
const text = yield* Ref.get(answer);
|
|
75
|
+
yield* agentOutput(text);
|
|
76
|
+
return text;
|
|
77
|
+
}, (effect, _handler, options) => effect.pipe(agentSpan(options.specialist.name, { chatId: options.chatId })));
|
|
78
|
+
const unanswered = (cause) => Cause.hasInterruptsOnly(cause) ? { status: "stopped" } : {
|
|
79
|
+
status: "failed",
|
|
80
|
+
error: String(Cause.squash(cause))
|
|
81
|
+
};
|
|
82
|
+
/** @internal */
|
|
83
|
+
const finished = (specialist, run, outcome) => SpecialistFinished.make({
|
|
84
|
+
id: fromKey(`${run.runId}:${run.brief}`),
|
|
85
|
+
runId: run.runId,
|
|
86
|
+
specialist,
|
|
87
|
+
brief: run.brief,
|
|
88
|
+
goal: run.goal,
|
|
89
|
+
...outcome
|
|
90
|
+
});
|
|
91
|
+
const RunIdInput = RunId.annotate({ description: "The runId spawn_specialist returned." });
|
|
92
|
+
const RunStatus = (status) => Schema.Struct({
|
|
93
|
+
runId: RunId,
|
|
94
|
+
status: Schema.Literals(status)
|
|
95
|
+
});
|
|
96
|
+
const MessageSpecialist = Tool.make("message_specialist", {
|
|
97
|
+
description: "Send more to one of your specialists: new instructions for a running one (it reads them at its next step), or a follow-up for a finished one (it continues with what it already found).",
|
|
98
|
+
parameters: Schema.Struct({
|
|
99
|
+
runId: RunIdInput,
|
|
100
|
+
message: Schema.String
|
|
101
|
+
}),
|
|
102
|
+
success: RunStatus(["steered", "continued"]),
|
|
103
|
+
failure: ToolFailure,
|
|
104
|
+
dependencies: [Runner]
|
|
105
|
+
});
|
|
106
|
+
const StopSpecialist = Tool.make("stop_specialist", {
|
|
107
|
+
description: "Stop one of your running specialists, for example when the user changed their mind. It posts no answer.",
|
|
108
|
+
parameters: Schema.Struct({ runId: RunIdInput }),
|
|
109
|
+
success: RunStatus(["stopped"]),
|
|
110
|
+
failure: ToolFailure,
|
|
111
|
+
dependencies: [Runner]
|
|
112
|
+
});
|
|
113
|
+
const refused = (effect) => Effect.catch(effect, (error) => fail(error.message, { reason: error.reason }));
|
|
114
|
+
/** @internal */
|
|
115
|
+
const toolkit = Effect.fnUntraced(function* (specialists) {
|
|
116
|
+
if (specialists.length === 0) return empty;
|
|
117
|
+
const catalog = specialists.map((specialist) => `- ${specialist.name}: ${specialist.description}`).join("\n");
|
|
118
|
+
const SpawnSpecialist = Tool.make("spawn_specialist", {
|
|
119
|
+
description: `Start a specialist on a job. It works in the background and its report arrives as a <specialist> input, often within seconds. Write a self-contained brief: it can't see this chat. For a quick question, just call wait; for slow work, tell the user in one line first.\nPass one of these as specialist:\n${catalog}`,
|
|
120
|
+
parameters: Schema.Struct({
|
|
121
|
+
specialist: Schema.Literals(specialists.map((specialist) => specialist.name)).annotate({ description: "The specialist to start, by name." }),
|
|
122
|
+
...Brief.fields
|
|
123
|
+
}),
|
|
124
|
+
success: RunStatus(["started"]),
|
|
125
|
+
failure: ToolFailure,
|
|
126
|
+
dependencies: [Runner]
|
|
127
|
+
});
|
|
128
|
+
const SpecialistTools = Toolkit.make(SpawnSpecialist, MessageSpecialist, StopSpecialist);
|
|
129
|
+
const handlers = yield* SpecialistTools.toHandlers({
|
|
130
|
+
spawn_specialist: Effect.fnUntraced(function* ({ specialist, ...brief }) {
|
|
131
|
+
return {
|
|
132
|
+
runId: yield* refused(Runner.use((runner) => runner.spawn({
|
|
133
|
+
specialist,
|
|
134
|
+
brief
|
|
135
|
+
}))),
|
|
136
|
+
status: "started"
|
|
137
|
+
};
|
|
138
|
+
}),
|
|
139
|
+
message_specialist: Effect.fnUntraced(function* ({ runId, message }) {
|
|
140
|
+
return {
|
|
141
|
+
runId,
|
|
142
|
+
status: yield* refused(Runner.use((runner) => runner.message(runId, message)))
|
|
143
|
+
};
|
|
144
|
+
}),
|
|
145
|
+
stop_specialist: Effect.fnUntraced(function* ({ runId }) {
|
|
146
|
+
yield* refused(Runner.use((runner) => runner.stop(runId)));
|
|
147
|
+
return {
|
|
148
|
+
runId,
|
|
149
|
+
status: "stopped"
|
|
150
|
+
};
|
|
151
|
+
})
|
|
152
|
+
});
|
|
153
|
+
return fromWithHandler(yield* Effect.provideContext(SpecialistTools, handlers));
|
|
154
|
+
});
|
|
155
|
+
const warm = "15 minutes";
|
|
156
|
+
/** @internal */
|
|
157
|
+
const makeRunner = Effect.fnUntraced(function* (options) {
|
|
158
|
+
const { chat } = options;
|
|
159
|
+
const runs = /* @__PURE__ */ new Map();
|
|
160
|
+
const byName = new Map(options.specialists.map((entry) => [entry.specialist.name, entry]));
|
|
161
|
+
const working = yield* FiberMap.make();
|
|
162
|
+
const forgetting = yield* FiberMap.make();
|
|
163
|
+
const background = yield* FiberSet.make();
|
|
164
|
+
const report = Effect.fnUntraced(function* (run, outcome) {
|
|
165
|
+
const brief = yield* Ref.get(run.briefs);
|
|
166
|
+
yield* options.report(finished(run.specialist.name, {
|
|
167
|
+
runId: run.runId,
|
|
168
|
+
brief,
|
|
169
|
+
goal: run.goal
|
|
170
|
+
}, outcome));
|
|
171
|
+
});
|
|
172
|
+
const once = (run) => runBrief(run.handler, {
|
|
173
|
+
specialist: run.specialist,
|
|
174
|
+
runId: run.runId,
|
|
175
|
+
brief: run.brief,
|
|
176
|
+
history: run.history,
|
|
177
|
+
inbox: run.inbox,
|
|
178
|
+
chatId: chat.chatId
|
|
179
|
+
}).pipe(Effect.provideContext(Context.merge(options.model, services)), Effect.provideService(TurnDecisions, Option.none()), Effect.provideService(TurnTools, Option.none()), Effect.matchCause({
|
|
180
|
+
onSuccess: (answer) => ({
|
|
181
|
+
status: "answered",
|
|
182
|
+
report: answer
|
|
183
|
+
}),
|
|
184
|
+
onFailure: unanswered
|
|
185
|
+
}));
|
|
186
|
+
const untilCaughtUp = Effect.fnUntraced(function* (run) {
|
|
187
|
+
let outcome = yield* once(run);
|
|
188
|
+
while ((yield* Queue.size(run.inbox)) > 0) outcome = yield* once(run);
|
|
189
|
+
return outcome;
|
|
190
|
+
});
|
|
191
|
+
const work = Effect.fnUntraced(function* (run) {
|
|
192
|
+
yield* chat.append([SpecialistStarted.make({
|
|
193
|
+
runId: run.runId,
|
|
194
|
+
specialist: run.specialist.name,
|
|
195
|
+
goal: run.goal
|
|
196
|
+
})]);
|
|
197
|
+
let finished = false;
|
|
198
|
+
while (!finished) {
|
|
199
|
+
const exit = yield* Fiber.await(yield* FiberMap.run(working, run.runId, untilCaughtUp(run)));
|
|
200
|
+
const outcome = Exit.isSuccess(exit) ? exit.value : unanswered(exit.cause);
|
|
201
|
+
finished = yield* run.lock.withPermit(Effect.gen(function* () {
|
|
202
|
+
if (outcome.status !== "stopped" && (yield* Queue.size(run.inbox)) > 0) return false;
|
|
203
|
+
yield* report(run, outcome);
|
|
204
|
+
yield* Ref.set(run.busy, false);
|
|
205
|
+
yield* FiberMap.run(forgetting, run.runId, Effect.gen(function* () {
|
|
206
|
+
yield* Effect.sleep(warm);
|
|
207
|
+
runs.delete(run.runId);
|
|
208
|
+
}));
|
|
209
|
+
return true;
|
|
210
|
+
}));
|
|
211
|
+
}
|
|
212
|
+
}, (effect, run) => Effect.onInterrupt(effect, () => report(run, { status: "cut_off" })));
|
|
213
|
+
const launch = Effect.fnUntraced(function* (run) {
|
|
214
|
+
yield* FiberMap.remove(forgetting, run.runId);
|
|
215
|
+
yield* FiberSet.run(background, options.holding(work(run)));
|
|
216
|
+
});
|
|
217
|
+
const send = Effect.fnUntraced(function* (run, text) {
|
|
218
|
+
yield* Ref.update(run.briefs, (n) => n + 1);
|
|
219
|
+
if (yield* Ref.get(run.busy)) {
|
|
220
|
+
yield* Queue.offer(run.inbox, userText(text));
|
|
221
|
+
return "steered";
|
|
222
|
+
}
|
|
223
|
+
yield* Ref.update(run.history, (all) => [...all, userText(text)]);
|
|
224
|
+
yield* Ref.set(run.busy, true);
|
|
225
|
+
yield* launch(run);
|
|
226
|
+
return "continued";
|
|
227
|
+
});
|
|
228
|
+
const runner = Runner.of({
|
|
229
|
+
spawn: Effect.fnUntraced(function* (request) {
|
|
230
|
+
const found = byName.get(request.specialist);
|
|
231
|
+
if (found === void 0) return yield* new SpecialistError({
|
|
232
|
+
reason: "UnknownSpecialist",
|
|
233
|
+
message: `No specialist named ${request.specialist}.`
|
|
234
|
+
});
|
|
235
|
+
const run = {
|
|
236
|
+
runId: yield* make(),
|
|
237
|
+
specialist: found.specialist,
|
|
238
|
+
handler: found.handler,
|
|
239
|
+
goal: request.brief.goal,
|
|
240
|
+
brief: request.brief,
|
|
241
|
+
history: yield* Ref.make([userText(briefText(request.brief))]),
|
|
242
|
+
inbox: yield* Queue.unbounded(),
|
|
243
|
+
briefs: yield* Ref.make(1),
|
|
244
|
+
busy: yield* Ref.make(true),
|
|
245
|
+
lock: yield* Semaphore.make(1)
|
|
246
|
+
};
|
|
247
|
+
runs.set(run.runId, run);
|
|
248
|
+
yield* launch(run);
|
|
249
|
+
return run.runId;
|
|
250
|
+
}),
|
|
251
|
+
message: Effect.fnUntraced(function* (runId, text) {
|
|
252
|
+
const run = runs.get(runId);
|
|
253
|
+
if (run === void 0) return yield* new SpecialistError({
|
|
254
|
+
reason: "RunNotFound",
|
|
255
|
+
message: `Run ${runId} is no longer available. Spawn a new one with the full context.`
|
|
256
|
+
});
|
|
257
|
+
return yield* run.lock.withPermit(send(run, text));
|
|
258
|
+
}),
|
|
259
|
+
stop: Effect.fnUntraced(function* (runId) {
|
|
260
|
+
if (!(yield* FiberMap.has(working, runId))) return yield* new SpecialistError({
|
|
261
|
+
reason: "NotRunning",
|
|
262
|
+
message: `Run ${runId} isn't running.`
|
|
263
|
+
});
|
|
264
|
+
yield* FiberMap.remove(working, runId);
|
|
265
|
+
})
|
|
266
|
+
});
|
|
267
|
+
const services = Context.make(CurrentChat, options.handle).pipe(Context.add(Runner, runner));
|
|
268
|
+
return runner;
|
|
269
|
+
});
|
|
270
|
+
//#endregion
|
|
271
|
+
export { briefText, finished, makeRunner, runBrief, toolkit };
|
|
272
|
+
|
|
273
|
+
//# sourceMappingURL=specialists.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"specialists.js","names":["run","Loop.run","Skill.withCatalog","Toolkits.merge","Skills.toTools","Toolkits.fromToolkitOrEmpty","Telemetry.agentOutput","Telemetry.agentSpan","InputId.fromKey","RunId.RunId","ToolFailure.ToolFailure","Specialist.Runner","ToolFailure.fail","Toolkits.empty","Toolkits.fromWithHandler","Decisions.TurnDecisions","Loop.TurnTools","Specialist.SpecialistError","RunId.make"],"sources":["../../src/internal/specialists.ts"],"sourcesContent":["import type * as LanguageModel from \"effect/ai/LanguageModel\"\nimport * as Prompt from \"effect/ai/Prompt\"\nimport * as Tool from \"effect/ai/Tool\"\nimport * as Toolkit from \"effect/ai/Toolkit\"\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 * as Fiber from \"effect/Fiber\"\nimport * as FiberMap from \"effect/FiberMap\"\nimport * as FiberSet from \"effect/FiberSet\"\nimport * as Option from \"effect/Option\"\nimport * as Predicate from \"effect/Predicate\"\nimport * as Queue from \"effect/Queue\"\nimport * as Ref from \"effect/Ref\"\nimport * as Schema from \"effect/Schema\"\nimport * as Semaphore from \"effect/Semaphore\"\nimport * as Stream from \"effect/Stream\"\nimport * as AssistantEvent from \"../AssistantEvent.ts\"\nimport type * as AssistantLoop from \"../AssistantLoop.ts\"\nimport * as Loop from \"./loop.ts\"\nimport { CurrentChat } from \"../CurrentChat.ts\"\nimport * as InputId from \"../InputId.ts\"\nimport * as RunId from \"../RunId.ts\"\nimport * as Skill from \"../Skill.ts\"\nimport * as Skills from \"./skills.ts\"\nimport * as Specialist from \"../Specialist.ts\"\nimport * as ToolFailure from \"../ToolFailure.ts\"\nimport * as Decisions from \"./decisions.ts\"\nimport { toolMessage, userText } from \"./history.ts\"\nimport { mapText } from \"./instructions.ts\"\nimport * as Telemetry from \"./telemetry.ts\"\nimport * as Toolkits from \"./toolkit.ts\"\nimport type { Writer } from \"./writer.ts\"\n\n/** @internal */\nexport type RunHandler = (run: Specialist.Run) => Stream.Stream<AssistantLoop.Event, unknown, Specialist.RunServices>\n\n/** @internal */\nexport const briefText = (brief: Specialist.Brief): string =>\n [\n `Goal: ${brief.goal}`,\n brief.context && `Context: ${brief.context}`,\n brief.successCriteria && `Done when: ${brief.successCriteria}`,\n brief.report && `Report back: ${brief.report}`,\n brief.limits && `Limits: ${brief.limits}`\n ]\n .filter(Boolean)\n .join(\"\\n\")\n\nconst finalText = (output: AssistantEvent.ModelOutput): string =>\n output.parts\n .flatMap((part) => (part.type === \"text\" ? [part.text] : []))\n .join(\"\")\n .trim()\n\n// The answer is the last text the model wrote.\n/** @internal */\nexport const runBrief = Effect.fnUntraced(\n function* (\n handler: RunHandler,\n options: {\n readonly specialist: Specialist.Any\n readonly runId: RunId.RunId\n readonly brief: Specialist.Brief\n readonly history: Ref.Ref<ReadonlyArray<Prompt.Message>>\n readonly inbox: Queue.Queue<Prompt.Message>\n readonly chatId: Writer[\"chatId\"]\n }\n ) {\n const { history, inbox } = options\n const answer = yield* Ref.make(\"\")\n const add = (messages: ReadonlyArray<Prompt.Message>) => Ref.update(history, (all) => [...all, ...messages])\n\n // Read before every model call, when no tool calls are open: messages sent meanwhile join it now.\n const current = Effect.gen(function* () {\n yield* add(yield* Queue.clear(inbox))\n\n return yield* Ref.get(history)\n })\n\n const run: Specialist.Run = {\n runId: options.runId,\n chatId: options.chatId,\n brief: options.brief,\n history: current,\n loop: (loop) => {\n const skills = loop.skills ?? []\n\n return Loop.run({\n instructions: mapText(loop.instructions, (text) => Skill.withCatalog(text, skills)),\n toolkit: Toolkits.merge(Skills.toTools(skills), Toolkits.fromToolkitOrEmpty(loop.toolkit)),\n history: current,\n maxSteps: loop.maxSteps ?? 8\n })\n }\n }\n\n // A run lives in memory, so there's nothing to run again after a crash: `ToolStarted` isn't kept. A run parks no\n // call (it has no user to ask), so there's no `DecisionRequested` either.\n const record = Effect.fnUntraced(function* (event: AssistantLoop.Event) {\n if (Predicate.isTagged(event, \"ToolStarted\") || Predicate.isTagged(event, \"DecisionRequested\")) return\n\n if (Predicate.isTagged(event, \"ToolResult\")) return yield* add([toolMessage(event)])\n\n yield* add(Prompt.fromResponseParts(event.parts).content)\n const text = finalText(event)\n\n if (text.length > 0) yield* Ref.set(answer, text)\n })\n\n yield* Stream.runForEach(handler(run), record)\n\n const text = yield* Ref.get(answer)\n\n yield* Telemetry.agentOutput(text)\n\n return text\n },\n (effect, _handler, options) => effect.pipe(Telemetry.agentSpan(options.specialist.name, { chatId: options.chatId }))\n)\n\ntype Outcome =\n | { readonly status: \"answered\"; readonly report: string }\n | { readonly status: \"failed\"; readonly error: string }\n | { readonly status: \"stopped\" | \"cut_off\" }\n\n// Only `stop` interrupts a brief's loop alone; a failure or a defect failed it.\nconst unanswered = (cause: Cause.Cause<unknown>): Outcome =>\n Cause.hasInterruptsOnly(cause) ? { status: \"stopped\" } : { status: \"failed\", error: String(Cause.squash(cause)) }\n\n// The id is fixed per run and brief, so a replay is stored once.\n/** @internal */\nexport const finished = (\n specialist: string,\n run: { readonly runId: RunId.RunId; readonly brief: number; readonly goal: string },\n outcome: Outcome\n): AssistantEvent.SpecialistFinished =>\n AssistantEvent.SpecialistFinished.make({\n id: InputId.fromKey(`${run.runId}:${run.brief}`),\n runId: run.runId,\n specialist,\n brief: run.brief,\n goal: run.goal,\n ...outcome\n })\n\nconst RunIdInput = RunId.RunId.annotate({ description: \"The runId spawn_specialist returned.\" })\n\nconst RunStatus = <const Status extends string>(status: ReadonlyArray<Status>) =>\n Schema.Struct({ runId: RunId.RunId, status: Schema.Literals(status) })\n\nconst MessageSpecialist = Tool.make(\"message_specialist\", {\n description:\n \"Send more to one of your specialists: new instructions for a running one (it reads them at its next step), or a follow-up for a finished one (it continues with what it already found).\",\n parameters: Schema.Struct({ runId: RunIdInput, message: Schema.String }),\n success: RunStatus([\"steered\", \"continued\"]),\n failure: ToolFailure.ToolFailure,\n dependencies: [Specialist.Runner]\n})\n\nconst StopSpecialist = Tool.make(\"stop_specialist\", {\n description:\n \"Stop one of your running specialists, for example when the user changed their mind. It posts no answer.\",\n parameters: Schema.Struct({ runId: RunIdInput }),\n success: RunStatus([\"stopped\"]),\n failure: ToolFailure.ToolFailure,\n dependencies: [Specialist.Runner]\n})\n\nconst refused = <A, R>(effect: Effect.Effect<A, Specialist.SpecialistError, R>) =>\n Effect.catch(effect, (error) => ToolFailure.fail(error.message, { reason: error.reason }))\n\n/** @internal */\nexport const toolkit = Effect.fnUntraced(function* (\n specialists: ReadonlyArray<Specialist.Any>\n): Effect.fn.Return<Toolkits.Tools<Specialist.Runner>> {\n if (specialists.length === 0) return Toolkits.empty\n\n const catalog = specialists.map((specialist) => `- ${specialist.name}: ${specialist.description}`).join(\"\\n\")\n\n const SpawnSpecialist = Tool.make(\"spawn_specialist\", {\n description: `Start a specialist on a job. It works in the background and its report arrives as a <specialist> input, often within seconds. Write a self-contained brief: it can't see this chat. For a quick question, just call wait; for slow work, tell the user in one line first.\\nPass one of these as specialist:\\n${catalog}`,\n parameters: Schema.Struct({\n specialist: Schema.Literals(specialists.map((specialist) => specialist.name)).annotate({\n description: \"The specialist to start, by name.\"\n }),\n ...Specialist.Brief.fields\n }),\n success: RunStatus([\"started\"]),\n failure: ToolFailure.ToolFailure,\n dependencies: [Specialist.Runner]\n })\n\n const SpecialistTools = Toolkit.make(SpawnSpecialist, MessageSpecialist, StopSpecialist)\n\n const handlers = yield* SpecialistTools.toHandlers({\n spawn_specialist: Effect.fnUntraced(function* ({ specialist, ...brief }) {\n const runId = yield* refused(Specialist.Runner.use((runner) => runner.spawn({ specialist, brief })))\n\n return { runId, status: \"started\" }\n }),\n message_specialist: Effect.fnUntraced(function* ({ runId, message }) {\n const status = yield* refused(Specialist.Runner.use((runner) => runner.message(runId, message)))\n\n return { runId, status }\n }),\n stop_specialist: Effect.fnUntraced(function* ({ runId }) {\n yield* refused(Specialist.Runner.use((runner) => runner.stop(runId)))\n\n return { runId, status: \"stopped\" }\n })\n })\n\n return Toolkits.fromWithHandler(yield* Effect.provideContext(SpecialistTools, handlers))\n})\n\n// How long a finished run stays available for follow-ups.\nconst warm = \"15 minutes\"\n\ninterface Run {\n readonly runId: RunId.RunId\n readonly specialist: Specialist.Any\n readonly handler: RunHandler\n readonly goal: string\n readonly brief: Specialist.Brief\n readonly history: Ref.Ref<ReadonlyArray<Prompt.Message>>\n // Messages sent while it works; the loop reads them at its next step.\n readonly inbox: Queue.Queue<Prompt.Message>\n // Counts follow-ups too; numbers the reports.\n readonly briefs: Ref.Ref<number>\n // Read and changed under `lock`.\n readonly busy: Ref.Ref<boolean>\n // Orders a message against the run finishing, so none is dropped or starts a second loop.\n readonly lock: Semaphore.Semaphore\n}\n\n/** @internal */\nexport const makeRunner = Effect.fnUntraced(function* (options: {\n readonly chat: Writer\n readonly handle: CurrentChat[\"Service\"]\n readonly specialists: ReadonlyArray<{\n readonly specialist: Specialist.Any\n readonly handler: RunHandler\n }>\n readonly model: Context.Context<LanguageModel.LanguageModel>\n // An interruption keeps the hold.\n readonly holding: (effect: Effect.Effect<void>) => Effect.Effect<void>\n readonly report: (finished: AssistantEvent.SpecialistFinished) => Effect.Effect<void>\n}) {\n const { chat } = options\n const runs = new Map<string, Run>()\n const byName = new Map(options.specialists.map((entry) => [entry.specialist.name, entry]))\n // The brief loops working now, and the finished runs' countdowns to being forgotten.\n const working = yield* FiberMap.make<string, Outcome>()\n const forgetting = yield* FiberMap.make<string>()\n const background = yield* FiberSet.make()\n\n const report = Effect.fnUntraced(function* (run: Run, outcome: Outcome) {\n const brief = yield* Ref.get(run.briefs)\n\n yield* options.report(finished(run.specialist.name, { runId: run.runId, brief, goal: run.goal }, outcome))\n })\n\n const once = (run: Run) =>\n runBrief(run.handler, {\n specialist: run.specialist,\n runId: run.runId,\n brief: run.brief,\n history: run.history,\n inbox: run.inbox,\n chatId: chat.chatId\n }).pipe(\n Effect.provideContext(Context.merge(options.model, services)),\n // Spawned from a turn, a run would inherit the turn's decisions: it can't park a call or run an approved one.\n Effect.provideService(Decisions.TurnDecisions, Option.none()),\n // Nor its own tools: a run can't message the user.\n Effect.provideService(Loop.TurnTools, Option.none()),\n Effect.matchCause({\n onSuccess: (answer): Outcome => ({ status: \"answered\", report: answer }),\n onFailure: unanswered\n })\n )\n\n // A message that arrived after the last read: keep working before reporting.\n const untilCaughtUp = Effect.fnUntraced(function* (run: Run) {\n let outcome = yield* once(run)\n\n while ((yield* Queue.size(run.inbox)) > 0) outcome = yield* once(run)\n\n return outcome\n })\n\n const work = Effect.fnUntraced(\n function* (run: Run) {\n yield* chat.append([\n AssistantEvent.SpecialistStarted.make({\n runId: run.runId,\n specialist: run.specialist.name,\n goal: run.goal\n })\n ])\n\n let finished = false\n\n while (!finished) {\n const exit = yield* Fiber.await(yield* FiberMap.run(working, run.runId, untilCaughtUp(run)))\n const outcome = Exit.isSuccess(exit) ? exit.value : unanswered(exit.cause)\n\n finished = yield* run.lock.withPermit(\n Effect.gen(function* () {\n // A message steered in after the loop's last read: work on it before reporting.\n if (outcome.status !== \"stopped\" && (yield* Queue.size(run.inbox)) > 0) return false\n\n yield* report(run, outcome)\n yield* Ref.set(run.busy, false)\n yield* FiberMap.run(\n forgetting,\n run.runId,\n Effect.gen(function* () {\n yield* Effect.sleep(warm)\n runs.delete(run.runId)\n })\n )\n\n return true\n })\n )\n }\n },\n // A shutdown interrupts the whole run: no turn can start now, so the report waits for the chat's next start.\n (effect, run) => Effect.onInterrupt(effect, () => report(run, { status: \"cut_off\" }))\n )\n\n const launch = Effect.fnUntraced(function* (run: Run) {\n yield* FiberMap.remove(forgetting, run.runId)\n yield* FiberSet.run(background, options.holding(work(run)))\n })\n\n // Under the run's lock.\n const send = Effect.fnUntraced(function* (run: Run, text: string): Effect.fn.Return<\"steered\" | \"continued\"> {\n yield* Ref.update(run.briefs, (n) => n + 1)\n\n if (yield* Ref.get(run.busy)) {\n yield* Queue.offer(run.inbox, userText(text))\n\n return \"steered\"\n }\n\n yield* Ref.update(run.history, (all) => [...all, userText(text)])\n yield* Ref.set(run.busy, true)\n yield* launch(run)\n\n return \"continued\"\n })\n\n const runner = Specialist.Runner.of({\n spawn: Effect.fnUntraced(function* (request: Specialist.SpawnRequest) {\n const found = byName.get(request.specialist)\n\n if (found === undefined) {\n return yield* new Specialist.SpecialistError({\n reason: \"UnknownSpecialist\",\n message: `No specialist named ${request.specialist}.`\n })\n }\n\n const run: Run = {\n runId: yield* RunId.make(),\n specialist: found.specialist,\n handler: found.handler,\n goal: request.brief.goal,\n brief: request.brief,\n history: yield* Ref.make<ReadonlyArray<Prompt.Message>>([userText(briefText(request.brief))]),\n inbox: yield* Queue.unbounded<Prompt.Message>(),\n briefs: yield* Ref.make(1),\n busy: yield* Ref.make(true),\n lock: yield* Semaphore.make(1)\n }\n\n runs.set(run.runId, run)\n yield* launch(run)\n\n return run.runId\n }),\n message: Effect.fnUntraced(function* (\n runId: RunId.RunId,\n text: string\n ): Effect.fn.Return<\"steered\" | \"continued\", Specialist.SpecialistError> {\n const run = runs.get(runId)\n\n if (run === undefined) {\n return yield* new Specialist.SpecialistError({\n reason: \"RunNotFound\",\n message: `Run ${runId} is no longer available. Spawn a new one with the full context.`\n })\n }\n\n return yield* run.lock.withPermit(send(run, text))\n }),\n stop: Effect.fnUntraced(function* (runId: RunId.RunId) {\n if (!(yield* FiberMap.has(working, runId))) {\n return yield* new Specialist.SpecialistError({ reason: \"NotRunning\", message: `Run ${runId} isn't running.` })\n }\n\n yield* FiberMap.remove(working, runId)\n })\n })\n\n // What a run's handler gets: its chat, this runner, and (merged in `once`) the model.\n const services = Context.make(CurrentChat, options.handle).pipe(Context.add(Specialist.Runner, runner))\n\n return runner\n})\n"],"mappings":"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"}
|