effect-assistant 0.3.0 → 0.5.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 +5 -11
- package/dist/Assistant.d.ts.map +1 -1
- package/dist/Assistant.js.map +1 -1
- package/dist/AssistantEvent.d.ts +66 -7
- package/dist/AssistantEvent.d.ts.map +1 -1
- package/dist/AssistantEvent.js +74 -9
- 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/Block.d.ts +63 -1
- package/dist/Block.d.ts.map +1 -1
- package/dist/Block.js +63 -2
- package/dist/Block.js.map +1 -1
- package/dist/BlockKit.d.ts +25 -3
- package/dist/BlockKit.d.ts.map +1 -1
- package/dist/BlockKit.js +14 -12
- package/dist/BlockKit.js.map +1 -1
- package/dist/Compaction.d.ts +1 -1
- package/dist/Compaction.d.ts.map +1 -1
- package/dist/Compaction.js +11 -4
- package/dist/Compaction.js.map +1 -1
- package/dist/Skill.d.ts +85 -43
- package/dist/Skill.d.ts.map +1 -1
- package/dist/Skill.js +93 -44
- package/dist/Skill.js.map +1 -1
- package/dist/Specialist.d.ts +6 -9
- package/dist/Specialist.d.ts.map +1 -1
- package/dist/Specialist.js.map +1 -1
- package/dist/Timeline.d.ts +44 -6
- package/dist/Timeline.d.ts.map +1 -1
- package/dist/Timeline.js +87 -16
- package/dist/Timeline.js.map +1 -1
- 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/ToolResult.d.ts +56 -0
- package/dist/ToolResult.d.ts.map +1 -0
- package/dist/ToolResult.js +49 -0
- package/dist/ToolResult.js.map +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +6 -4
- package/dist/internal/chat.js +4 -1
- package/dist/internal/chat.js.map +1 -1
- package/dist/internal/history.js +28 -3
- package/dist/internal/history.js.map +1 -1
- package/dist/internal/loop.d.ts +10 -5
- package/dist/internal/loop.d.ts.map +1 -1
- package/dist/internal/loop.js +60 -21
- package/dist/internal/loop.js.map +1 -1
- package/dist/internal/specialists.js +9 -16
- package/dist/internal/specialists.js.map +1 -1
- package/dist/internal/toolkit.d.ts +11 -7
- package/dist/internal/toolkit.d.ts.map +1 -1
- package/dist/internal/toolkit.js +74 -21
- package/dist/internal/toolkit.js.map +1 -1
- package/dist/internal/tools.js +39 -6
- package/dist/internal/tools.js.map +1 -1
- package/dist/internal/turns.d.ts.map +1 -1
- package/dist/internal/turns.js +24 -16
- package/dist/internal/turns.js.map +1 -1
- package/package.json +1 -1
- package/src/Assistant.ts +7 -13
- package/src/AssistantEvent.ts +73 -8
- package/src/AssistantLoop.ts +4 -2
- package/src/Block.ts +82 -1
- package/src/BlockKit.ts +37 -4
- package/src/Compaction.ts +19 -2
- package/src/Skill.ts +134 -57
- package/src/Specialist.ts +8 -11
- package/src/Timeline.ts +120 -21
- package/src/ToolApproval.ts +249 -0
- package/src/ToolDiscovery.ts +8 -3
- package/src/ToolResult.ts +80 -0
- package/src/index.ts +10 -0
- package/src/internal/chat.ts +11 -1
- package/src/internal/history.ts +58 -3
- package/src/internal/loop.ts +138 -35
- package/src/internal/specialists.ts +7 -13
- package/src/internal/toolkit.ts +102 -35
- package/src/internal/tools.ts +81 -5
- package/src/internal/turns.ts +31 -10
- package/dist/internal/skills.js +0 -33
- package/dist/internal/skills.js.map +0 -1
- package/src/internal/skills.ts +0 -48
package/src/internal/turns.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type * as LanguageModel from "effect/ai/LanguageModel"
|
|
2
|
+
import * as Response from "effect/ai/Response"
|
|
2
3
|
import type * as Tool from "effect/ai/Tool"
|
|
3
4
|
import * as Cause from "effect/Cause"
|
|
4
5
|
import * as Context from "effect/Context"
|
|
@@ -16,11 +17,10 @@ import type { ChatId } from "../ChatId.ts"
|
|
|
16
17
|
import * as AssistantEvent from "../AssistantEvent.ts"
|
|
17
18
|
import type * as AssistantLoop from "../AssistantLoop.ts"
|
|
18
19
|
import * as Loop from "./loop.ts"
|
|
19
|
-
import
|
|
20
|
+
import * as BlockKit from "../BlockKit.ts"
|
|
20
21
|
import type { CurrentChat } from "../CurrentChat.ts"
|
|
21
22
|
import * as Instructions from "../Instructions.ts"
|
|
22
|
-
import * as
|
|
23
|
-
import * as Skills from "./skills.ts"
|
|
23
|
+
import * as ToolApproval from "../ToolApproval.ts"
|
|
24
24
|
import type * as Specialist from "../Specialist.ts"
|
|
25
25
|
import * as AppEvents from "./events.ts"
|
|
26
26
|
import * as History from "./history.ts"
|
|
@@ -125,6 +125,21 @@ const failureMessage = (cause: Cause.Cause<unknown>): string =>
|
|
|
125
125
|
onSome: ({ _tag, raw }) => `${_tag ?? "Error"}: ${Predicate.isString(raw) ? raw : toJsonText(raw)}`
|
|
126
126
|
})
|
|
127
127
|
|
|
128
|
+
// Approved, and not run yet: also one a crash cut off while it ran, which runs again with the same id.
|
|
129
|
+
const approvedCalls = (entries: ReadonlyArray<AssistantEvent.AnyEntry>): ReadonlyArray<AssistantEvent.ToolCallPart> =>
|
|
130
|
+
[...ToolApproval.fromEntries(entries).values()].flatMap(({ request, status, outcome }) =>
|
|
131
|
+
status === "approved" && outcome === undefined
|
|
132
|
+
? [
|
|
133
|
+
Response.makePart("tool-call", {
|
|
134
|
+
id: request.callId,
|
|
135
|
+
name: request.tool,
|
|
136
|
+
params: request.params,
|
|
137
|
+
providerExecuted: false
|
|
138
|
+
})
|
|
139
|
+
]
|
|
140
|
+
: []
|
|
141
|
+
)
|
|
142
|
+
|
|
128
143
|
const lastSeq = (entries: ReadonlyArray<AssistantEvent.AnyEntry>, otherwise: number) => entries.at(-1)?.seq ?? otherwise
|
|
129
144
|
|
|
130
145
|
// The handler's stream can end between a step's output and its tools, or while they run. An interrupted turn (a
|
|
@@ -199,6 +214,7 @@ const makeTurnApi = (
|
|
|
199
214
|
): Assistant.Turn<any> => {
|
|
200
215
|
const { definition, builtins } = registration
|
|
201
216
|
const { app } = definition
|
|
217
|
+
const shownTools = BlockKit.toolNames(definition.blocks)
|
|
202
218
|
|
|
203
219
|
const running = <A, E, R>(stream: Stream.Stream<A, E, R>) =>
|
|
204
220
|
Stream.unwrap(Ref.set(ranLoop, true).pipe(Effect.as(stream)))
|
|
@@ -265,8 +281,12 @@ const makeTurnApi = (
|
|
|
265
281
|
}
|
|
266
282
|
}),
|
|
267
283
|
// A second step, for a reply after a call it was refused.
|
|
268
|
-
maxSteps: 2
|
|
269
|
-
|
|
284
|
+
maxSteps: 2,
|
|
285
|
+
// The pass runs because the model wrote text of its own instead of replying, which reaches nobody: it has to
|
|
286
|
+
// call a tool now. Changing the choice costs a read of the history at full price, on these rare turns only.
|
|
287
|
+
toolChoice: "required"
|
|
288
|
+
// The loop ran the approved calls; a pass that can only reply runs none.
|
|
289
|
+
}).pipe(Stream.provideService(Loop.TurnApprovals, Option.none()))
|
|
270
290
|
})
|
|
271
291
|
)
|
|
272
292
|
|
|
@@ -280,13 +300,12 @@ const makeTurnApi = (
|
|
|
280
300
|
})
|
|
281
301
|
)
|
|
282
302
|
|
|
283
|
-
const prepare = <T extends Record<string, Tool.Any
|
|
284
|
-
const own = Toolkits.fromToolkitOrEmpty(options.toolkit)
|
|
285
|
-
const skills = Skills.available(options.skills ?? [], [...builtins.definitions, ...own.definitions])
|
|
303
|
+
const prepare = <T extends Record<string, Tool.Any>>(options: Assistant.LoopOptions<T, any>) => {
|
|
304
|
+
const own = Toolkits.showing(Toolkits.fromToolkitOrEmpty(options.toolkit), shownTools)
|
|
286
305
|
|
|
287
306
|
return {
|
|
288
|
-
system:
|
|
289
|
-
toolkit: Toolkits.merge(
|
|
307
|
+
system: instructions(options.instructions),
|
|
308
|
+
toolkit: Toolkits.merge(builtins, own),
|
|
290
309
|
view: options.history ?? identity
|
|
291
310
|
}
|
|
292
311
|
}
|
|
@@ -357,6 +376,8 @@ export const run = Effect.fnUntraced(
|
|
|
357
376
|
Loop.TurnTools,
|
|
358
377
|
Option.some(Toolkits.provide(options.registration.builtins, options.services))
|
|
359
378
|
),
|
|
379
|
+
// A turn's loop waits for the user's approval, and runs the calls they approved.
|
|
380
|
+
Effect.provideService(Loop.TurnApprovals, Option.some({ approved: Effect.map(chat.entries, approvedCalls) })),
|
|
360
381
|
Effect.provideContext(options.services),
|
|
361
382
|
Effect.matchCauseEffect({
|
|
362
383
|
onSuccess: Effect.fnUntraced(function* () {
|
package/dist/internal/skills.js
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
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 available = (skills, definitions) => {
|
|
11
|
-
const names = new Set(definitions.map((tool) => tool.name));
|
|
12
|
-
return skills.filter((skill) => (skill.tools ?? []).every((tool) => names.has(tool.name)));
|
|
13
|
-
};
|
|
14
|
-
/** @internal */
|
|
15
|
-
const toTools = (skills) => {
|
|
16
|
-
if (skills.length === 0) return empty;
|
|
17
|
-
const byName = new Map(skills.map((skill) => [skill.name, skill]));
|
|
18
|
-
const LoadSkill = Tool.make("load_skill", {
|
|
19
|
-
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.",
|
|
20
|
-
parameters: Schema.Struct({ name: Schema.Literals(skills.map((skill) => skill.name)) }),
|
|
21
|
-
success: Schema.String,
|
|
22
|
-
failure: ToolFailure
|
|
23
|
-
});
|
|
24
|
-
return withContext(Toolkit.make(LoadSkill), (context) => ({ load_skill: Effect.fnUntraced(function* ({ name }) {
|
|
25
|
-
const skill = byName.get(name);
|
|
26
|
-
if (skill === void 0) return yield* fail(`No skill named ${name}.`);
|
|
27
|
-
return (yield* resolve(skill.instructions).pipe(Effect.provideContext(context))).text;
|
|
28
|
-
}) }));
|
|
29
|
-
};
|
|
30
|
-
//#endregion
|
|
31
|
-
export { available, toTools };
|
|
32
|
-
|
|
33
|
-
//# sourceMappingURL=skills.js.map
|
|
@@ -1 +0,0 @@
|
|
|
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// A skill whose tools the loop doesn't have would send the model after calls it can't make.\n/** @internal */\nexport const available = <R>(\n skills: ReadonlyArray<Skill<R>>,\n definitions: ReadonlyArray<Tool.Any>\n): ReadonlyArray<Skill<R>> => {\n const names = new Set(definitions.map((tool) => tool.name))\n\n return skills.filter((skill) => (skill.tools ?? []).every((tool) => names.has(tool.name)))\n}\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":";;;;;;;;;AAYA,MAAa,aACX,QACA,gBAC4B;CAC5B,MAAM,QAAQ,IAAI,IAAI,YAAY,KAAK,SAAS,KAAK,IAAI,CAAC;CAE1D,OAAO,OAAO,QAAQ,WAAW,MAAM,SAAS,CAAC,EAAA,CAAG,OAAO,SAAS,MAAM,IAAI,KAAK,IAAI,CAAC,CAAC;AAC3F;;AAGA,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"}
|
package/src/internal/skills.ts
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import * as Tool from "effect/ai/Tool"
|
|
2
|
-
import * as Toolkit from "effect/ai/Toolkit"
|
|
3
|
-
import type * as Context from "effect/Context"
|
|
4
|
-
import * as Effect from "effect/Effect"
|
|
5
|
-
import * as Schema from "effect/Schema"
|
|
6
|
-
import * as Instructions from "../Instructions.ts"
|
|
7
|
-
import type { Skill } from "../Skill.ts"
|
|
8
|
-
import * as ToolFailure from "../ToolFailure.ts"
|
|
9
|
-
import * as Toolkits from "./toolkit.ts"
|
|
10
|
-
|
|
11
|
-
// A skill whose tools the loop doesn't have would send the model after calls it can't make.
|
|
12
|
-
/** @internal */
|
|
13
|
-
export const available = <R>(
|
|
14
|
-
skills: ReadonlyArray<Skill<R>>,
|
|
15
|
-
definitions: ReadonlyArray<Tool.Any>
|
|
16
|
-
): ReadonlyArray<Skill<R>> => {
|
|
17
|
-
const names = new Set(definitions.map((tool) => tool.name))
|
|
18
|
-
|
|
19
|
-
return skills.filter((skill) => (skill.tools ?? []).every((tool) => names.has(tool.name)))
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/** @internal */
|
|
23
|
-
export const toTools = <R>(skills: ReadonlyArray<Skill<R>>): Toolkits.Tools<R> => {
|
|
24
|
-
if (skills.length === 0) return Toolkits.empty
|
|
25
|
-
|
|
26
|
-
const byName = new Map(skills.map((skill) => [skill.name, skill]))
|
|
27
|
-
|
|
28
|
-
const LoadSkill = Tool.make("load_skill", {
|
|
29
|
-
description:
|
|
30
|
-
"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.",
|
|
31
|
-
parameters: Schema.Struct({ name: Schema.Literals(skills.map((skill) => skill.name)) }),
|
|
32
|
-
success: Schema.String,
|
|
33
|
-
failure: ToolFailure.ToolFailure
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
// A skill's instructions resolve with the services of the loop that loads it.
|
|
37
|
-
return Toolkits.withContext(Toolkit.make(LoadSkill), (context: Context.Context<R>) => ({
|
|
38
|
-
load_skill: Effect.fnUntraced(function* ({ name }) {
|
|
39
|
-
const skill = byName.get(name)
|
|
40
|
-
|
|
41
|
-
if (skill === undefined) return yield* ToolFailure.fail(`No skill named ${name}.`)
|
|
42
|
-
|
|
43
|
-
const resolved = yield* Instructions.resolve(skill.instructions).pipe(Effect.provideContext(context))
|
|
44
|
-
|
|
45
|
-
return resolved.text
|
|
46
|
-
})
|
|
47
|
-
}))
|
|
48
|
-
}
|