effect-assistant 0.0.0-stage → 0.2.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 +551 -0
- package/dist/AssistantEvent.d.ts.map +1 -0
- package/dist/AssistantEvent.js +473 -0
- package/dist/AssistantEvent.js.map +1 -0
- package/dist/AssistantLoop.d.ts +173 -0
- package/dist/AssistantLoop.d.ts.map +1 -0
- package/dist/AssistantLoop.js +111 -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 +180 -0
- package/dist/BlockKit.d.ts.map +1 -0
- package/dist/BlockKit.js +148 -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 +408 -0
- package/dist/ChatView.d.ts.map +1 -0
- package/dist/ChatView.js +443 -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 +126 -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/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 +107 -0
- package/dist/ToolDiscovery.d.ts.map +1 -0
- package/dist/ToolDiscovery.js +140 -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 +24 -0
- package/dist/index.js +24 -0
- package/dist/internal/chat.js +177 -0
- package/dist/internal/chat.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 +147 -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 +131 -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 +272 -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 +62 -0
- package/dist/internal/toolkit.d.ts.map +1 -0
- package/dist/internal/toolkit.js +108 -0
- package/dist/internal/toolkit.js.map +1 -0
- package/dist/internal/tools.js +113 -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 +214 -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 +52 -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 +569 -0
- package/src/AssistantLoop.ts +209 -0
- package/src/AssistantToolCall.ts +40 -0
- package/src/Block.ts +84 -0
- package/src/BlockKit.ts +298 -0
- package/src/ChatId.ts +36 -0
- package/src/ChatLog.ts +247 -0
- package/src/ChatView.ts +587 -0
- package/src/Chats.ts +122 -0
- package/src/Compaction.ts +207 -0
- package/src/CurrentChat.ts +47 -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 +179 -0
- package/src/ToolFailure.ts +74 -0
- package/src/index.ts +120 -0
- package/src/internal/chat.ts +323 -0
- package/src/internal/events.ts +100 -0
- package/src/internal/history.ts +294 -0
- package/src/internal/instructions.ts +9 -0
- package/src/internal/loop.ts +274 -0
- package/src/internal/schema.ts +25 -0
- package/src/internal/skills.ts +37 -0
- package/src/internal/specialists.ts +410 -0
- package/src/internal/telemetry.ts +125 -0
- package/src/internal/toolkit.ts +188 -0
- package/src/internal/tools.ts +159 -0
- package/src/internal/turns.ts +410 -0
- package/src/internal/uuid.ts +50 -0
- package/src/internal/writer.ts +12 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.d.ts","names":[],"sources":["../../src/internal/events.ts"],"mappings":";;;;;;;;;;iBASiB;WACN,OAAO,oBAAoB;WAC3B,OAAO;;UAGR;WACC,QAAQ;WACR,QAAQ;aAA2B;;;;qBAIjC,OAAI,QAAY,cAAc,SAAgB;;qBAe9C,SAAM,KAAA,WAAA;WAAwE;;;;;;qBAY9E,eAAY,KAAS,WAAS;WAAoB;;;qBAIlD,cAAW,KAAS,WAAS,OAAS,aAA0B,OAAO,OAAO;;qBAgB9E,gBAAa,KACnB,WAAS,SACL,cAAc,cACtB,cAAc;;YAGL,iBAAiB,kBAAkB;;qBAWlC,OAAI,KAAS,WAAS;WAAoB"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { StoredAppEvent, isLibraryEvent } from "../AssistantEvent.js";
|
|
2
|
+
import { toJsonText } from "./schema.js";
|
|
3
|
+
import * as Effect from "effect/Effect";
|
|
4
|
+
import * as Schema from "effect/Schema";
|
|
5
|
+
import * as Option from "effect/Option";
|
|
6
|
+
//#region src/internal/events.ts
|
|
7
|
+
/** @internal */
|
|
8
|
+
const make = (events) => {
|
|
9
|
+
const byTag = new Map(events.map((event) => [event.tag, {
|
|
10
|
+
schema: event.schema,
|
|
11
|
+
render: event.render
|
|
12
|
+
}]));
|
|
13
|
+
for (const tag of byTag.keys()) if (isLibraryEvent({ _tag: tag })) throw new Error(`${tag} is one of the library's events.`);
|
|
14
|
+
if (byTag.size !== events.length) throw new Error("An assistant's events have different tags.");
|
|
15
|
+
return {
|
|
16
|
+
byTag,
|
|
17
|
+
wakes: new Set(events.flatMap((event) => event.wakes ? [event.tag] : []))
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
/** @internal */
|
|
21
|
+
const encode = Effect.fnUntraced(function* (app, event) {
|
|
22
|
+
const registered = app.byTag.get(event._tag);
|
|
23
|
+
if (registered === void 0) return yield* Effect.die(`${event._tag} isn't one of this assistant's events.`);
|
|
24
|
+
const json = yield* Schema.encodeUnknownEffect(Schema.toCodecJson(registered.schema))(event);
|
|
25
|
+
return yield* Schema.decodeUnknownEffect(StoredAppEvent)(json);
|
|
26
|
+
}, Effect.orDie);
|
|
27
|
+
/** @internal */
|
|
28
|
+
const isRegistered = (app, event) => app.byTag.has(event._tag);
|
|
29
|
+
/** @internal */
|
|
30
|
+
const decodeEntry = (app, entry) => {
|
|
31
|
+
const { event } = entry;
|
|
32
|
+
if (isLibraryEvent(event)) return Option.some({
|
|
33
|
+
...toEntry(entry),
|
|
34
|
+
event
|
|
35
|
+
});
|
|
36
|
+
const registered = app.byTag.get(event._tag);
|
|
37
|
+
if (registered === void 0) return Option.none();
|
|
38
|
+
return Option.map(Schema.decodeUnknownOption(Schema.toCodecJson(registered.schema))(event), (decoded) => ({
|
|
39
|
+
...toEntry(entry),
|
|
40
|
+
event: decoded
|
|
41
|
+
}));
|
|
42
|
+
};
|
|
43
|
+
/** @internal */
|
|
44
|
+
const decodeEntries = (app, entries) => entries.flatMap((entry) => Option.toArray(decodeEntry(app, entry)));
|
|
45
|
+
const decodeStored = Schema.decodeUnknownOption(StoredAppEvent);
|
|
46
|
+
const toEntry = (entry) => ({
|
|
47
|
+
chatId: entry.chatId,
|
|
48
|
+
seq: entry.seq,
|
|
49
|
+
at: entry.at
|
|
50
|
+
});
|
|
51
|
+
/** @internal */
|
|
52
|
+
const read = (app, event) => {
|
|
53
|
+
const registered = app.byTag.get(event._tag);
|
|
54
|
+
if (registered?.render === void 0) return void 0;
|
|
55
|
+
if (registered.render !== "json") return registered.render(event);
|
|
56
|
+
const stored = Schema.encodeUnknownOption(Schema.toCodecJson(registered.schema))(event).pipe(Option.flatMap(decodeStored));
|
|
57
|
+
return Option.match(stored, {
|
|
58
|
+
onNone: () => void 0,
|
|
59
|
+
onSome: ({ _tag, ...fields }) => `<${_tag}>\n${toJsonText(fields)}\n</${_tag}>`
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
//#endregion
|
|
63
|
+
export { decodeEntries, decodeEntry, encode, isRegistered, make, read };
|
|
64
|
+
|
|
65
|
+
//# sourceMappingURL=events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.js","names":["AssistantEvent.isLibraryEvent","AssistantEvent.StoredAppEvent"],"sources":["../../src/internal/events.ts"],"sourcesContent":["import * as Effect from \"effect/Effect\"\nimport * as Option from \"effect/Option\"\nimport * as Schema from \"effect/Schema\"\nimport type * as AppEvent from \"../AppEvent.ts\"\nimport type * as Assistant from \"../Assistant.ts\"\nimport * as AssistantEvent from \"../AssistantEvent.ts\"\nimport { toJsonText } from \"./schema.ts\"\n\n/** @internal */\nexport interface AppEvents {\n readonly byTag: ReadonlyMap<string, Registered>\n readonly wakes: ReadonlySet<string>\n}\n\ninterface Registered {\n readonly schema: AppEvent.EventSchema\n readonly render: AppEvent.Render<{ readonly _tag: string }> | undefined\n}\n\n/** @internal */\nexport const make = (events: ReadonlyArray<AppEvent.Any>): AppEvents => {\n const byTag = new Map(\n events.map((event): [string, Registered] => [event.tag, { schema: event.schema, render: event.render }])\n )\n\n for (const tag of byTag.keys()) {\n if (AssistantEvent.isLibraryEvent({ _tag: tag })) throw new Error(`${tag} is one of the library's events.`)\n }\n\n if (byTag.size !== events.length) throw new Error(\"An assistant's events have different tags.\")\n\n return { byTag, wakes: new Set(events.flatMap((event) => (event.wakes ? [event.tag] : []))) }\n}\n\n/** @internal */\nexport const encode = Effect.fnUntraced(function* (app: AppEvents, event: { readonly _tag: string }) {\n const registered = app.byTag.get(event._tag)\n\n // The types only let registered events through, so one with no schema here is a defect.\n if (registered === undefined) return yield* Effect.die(`${event._tag} isn't one of this assistant's events.`)\n\n const json = yield* Schema.encodeUnknownEffect(Schema.toCodecJson(registered.schema))(event)\n\n return yield* Schema.decodeUnknownEffect(AssistantEvent.StoredAppEvent)(json)\n}, Effect.orDie)\n\n/** @internal */\nexport const isRegistered = (app: AppEvents, event: { readonly _tag: string }): boolean => app.byTag.has(event._tag)\n\n// An app event that doesn't decode (say, one from an older version of its schema) is left out.\n/** @internal */\nexport const decodeEntry = (app: AppEvents, entry: AssistantEvent.AnyEntry): Option.Option<Assistant.Entry<any>> => {\n const { event } = entry\n\n if (AssistantEvent.isLibraryEvent(event)) return Option.some({ ...toEntry(entry), event })\n\n const registered = app.byTag.get(event._tag)\n\n if (registered === undefined) return Option.none()\n\n return Option.map(Schema.decodeUnknownOption(Schema.toCodecJson(registered.schema))(event), (decoded) => ({\n ...toEntry(entry),\n event: decoded\n }))\n}\n\n/** @internal */\nexport const decodeEntries = (\n app: AppEvents,\n entries: ReadonlyArray<AssistantEvent.AnyEntry>\n): ReadonlyArray<Assistant.Entry<any>> => entries.flatMap((entry) => Option.toArray(decodeEntry(app, entry)))\n\n/** @internal */\nexport type DecodedEntries = ReturnType<typeof decodeEntries>\n\nconst decodeStored = Schema.decodeUnknownOption(AssistantEvent.StoredAppEvent)\n\nconst toEntry = (entry: AssistantEvent.AnyEntry) => ({\n chatId: entry.chatId,\n seq: entry.seq,\n at: entry.at\n})\n\n/** @internal */\nexport const read = (app: AppEvents, event: { readonly _tag: string }): string | undefined => {\n const registered = app.byTag.get(event._tag)\n\n if (registered?.render === undefined) return undefined\n\n if (registered.render !== \"json\") return registered.render(event)\n\n const stored = Schema.encodeUnknownOption(Schema.toCodecJson(registered.schema))(event).pipe(\n Option.flatMap(decodeStored)\n )\n\n return Option.match(stored, {\n onNone: () => undefined,\n onSome: ({ _tag, ...fields }) => `<${_tag}>\\n${toJsonText(fields)}\\n</${_tag}>`\n })\n}\n"],"mappings":";;;;;;;AAoBA,MAAa,QAAQ,WAAmD;CACtE,MAAM,QAAQ,IAAI,IAChB,OAAO,KAAK,UAAgC,CAAC,MAAM,KAAK;EAAE,QAAQ,MAAM;EAAQ,QAAQ,MAAM;CAAO,CAAC,CAAC,CACzG;CAEA,KAAK,MAAM,OAAO,MAAM,KAAK,GAC3B,IAAIA,eAA8B,EAAE,MAAM,IAAI,CAAC,GAAG,MAAM,IAAI,MAAM,GAAG,IAAI,iCAAiC;CAG5G,IAAI,MAAM,SAAS,OAAO,QAAQ,MAAM,IAAI,MAAM,4CAA4C;CAE9F,OAAO;EAAE;EAAO,OAAO,IAAI,IAAI,OAAO,SAAS,UAAW,MAAM,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,CAAE,CAAC;CAAE;AAC9F;;AAGA,MAAa,SAAS,OAAO,WAAW,WAAW,KAAgB,OAAkC;CACnG,MAAM,aAAa,IAAI,MAAM,IAAI,MAAM,IAAI;CAG3C,IAAI,eAAe,KAAA,GAAW,OAAO,OAAO,OAAO,IAAI,GAAG,MAAM,KAAK,uCAAuC;CAE5G,MAAM,OAAO,OAAO,OAAO,oBAAoB,OAAO,YAAY,WAAW,MAAM,CAAC,CAAC,CAAC,KAAK;CAE3F,OAAO,OAAO,OAAO,oBAAoBC,cAA6B,CAAC,CAAC,IAAI;AAC9E,GAAG,OAAO,KAAK;;AAGf,MAAa,gBAAgB,KAAgB,UAA8C,IAAI,MAAM,IAAI,MAAM,IAAI;;AAInH,MAAa,eAAe,KAAgB,UAAwE;CAClH,MAAM,EAAE,UAAU;CAElB,IAAID,eAA8B,KAAK,GAAG,OAAO,OAAO,KAAK;EAAE,GAAG,QAAQ,KAAK;EAAG;CAAM,CAAC;CAEzF,MAAM,aAAa,IAAI,MAAM,IAAI,MAAM,IAAI;CAE3C,IAAI,eAAe,KAAA,GAAW,OAAO,OAAO,KAAK;CAEjD,OAAO,OAAO,IAAI,OAAO,oBAAoB,OAAO,YAAY,WAAW,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,aAAa;EACxG,GAAG,QAAQ,KAAK;EAChB,OAAO;CACT,EAAE;AACJ;;AAGA,MAAa,iBACX,KACA,YACwC,QAAQ,SAAS,UAAU,OAAO,QAAQ,YAAY,KAAK,KAAK,CAAC,CAAC;AAK5G,MAAM,eAAe,OAAO,oBAAoBC,cAA6B;AAE7E,MAAM,WAAW,WAAoC;CACnD,QAAQ,MAAM;CACd,KAAK,MAAM;CACX,IAAI,MAAM;AACZ;;AAGA,MAAa,QAAQ,KAAgB,UAAyD;CAC5F,MAAM,aAAa,IAAI,MAAM,IAAI,MAAM,IAAI;CAE3C,IAAI,YAAY,WAAW,KAAA,GAAW,OAAO,KAAA;CAE7C,IAAI,WAAW,WAAW,QAAQ,OAAO,WAAW,OAAO,KAAK;CAEhE,MAAM,SAAS,OAAO,oBAAoB,OAAO,YAAY,WAAW,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,KACtF,OAAO,QAAQ,YAAY,CAC7B;CAEA,OAAO,OAAO,MAAM,QAAQ;EAC1B,cAAc,KAAA;EACd,SAAS,EAAE,MAAM,GAAG,aAAa,IAAI,KAAK,KAAK,WAAW,MAAM,EAAE,MAAM,KAAK;CAC/E,CAAC;AACH"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { fromKey } from "./uuid.js";
|
|
2
|
+
import { isInput as isInput$1, isLibraryEvent, isTurnEnd, isUserInput, isWaking, textWithAttachments } from "../AssistantEvent.js";
|
|
3
|
+
import { toJson, toJsonText } from "./schema.js";
|
|
4
|
+
import * as Predicate from "effect/Predicate";
|
|
5
|
+
import * as Schema from "effect/Schema";
|
|
6
|
+
import * as Option from "effect/Option";
|
|
7
|
+
import * as Response from "effect/ai/Response";
|
|
8
|
+
import * as Prompt from "effect/ai/Prompt";
|
|
9
|
+
import * as Match from "effect/Match";
|
|
10
|
+
//#region src/internal/history.ts
|
|
11
|
+
/** @internal */
|
|
12
|
+
const userText = (text) => Prompt.userMessage({ content: [Prompt.textPart({ text })] });
|
|
13
|
+
/** @internal */
|
|
14
|
+
const toolMessage = (result) => Prompt.toolMessage({ content: [Prompt.toolResultPart({
|
|
15
|
+
id: result.callId,
|
|
16
|
+
name: result.tool,
|
|
17
|
+
isFailure: result.outcome === "failure",
|
|
18
|
+
result: result.result,
|
|
19
|
+
providerExecuted: false
|
|
20
|
+
})] });
|
|
21
|
+
/** @internal */
|
|
22
|
+
const unansweredCalls = (history) => {
|
|
23
|
+
const answered = new Set(history.flatMap((message) => message.role === "tool" ? message.content.flatMap((part) => part.type === "tool-result" ? [part.id] : []) : []));
|
|
24
|
+
return history.flatMap((message) => message.role === "assistant" ? message.content.flatMap((part) => part.type === "tool-call" && !part.providerExecuted && !answered.has(part.id) ? [Response.makePart("tool-call", {
|
|
25
|
+
id: part.id,
|
|
26
|
+
name: part.name,
|
|
27
|
+
params: toJson(part.params),
|
|
28
|
+
providerExecuted: false,
|
|
29
|
+
metadata: part.options
|
|
30
|
+
})] : []) : []);
|
|
31
|
+
};
|
|
32
|
+
/** @internal */
|
|
33
|
+
const notRunOutput = "Not run: the turn ended before this call ran.";
|
|
34
|
+
/** @internal */
|
|
35
|
+
const mayHaveRunOutput = "Stopped: the turn ended while this call ran. It may have done its work; check before doing it again.";
|
|
36
|
+
const ago = (millis) => {
|
|
37
|
+
const seconds = Math.max(0, Math.round(millis / 1e3));
|
|
38
|
+
return seconds < 90 ? `${seconds} seconds` : `${Math.round(seconds / 60)} minutes`;
|
|
39
|
+
};
|
|
40
|
+
const finishedText = (entries, index, finished) => {
|
|
41
|
+
const at = entries[index];
|
|
42
|
+
const isStart = ({ event }) => isLibraryEvent(event) && Predicate.isTagged(event, "SpecialistStarted") && event.runId === finished.runId;
|
|
43
|
+
const from = entries.slice(0, index).findLastIndex(isStart);
|
|
44
|
+
const started = from === -1 ? void 0 : entries[from];
|
|
45
|
+
const wrote = (started === void 0 ? [] : entries.slice(from + 1, index)).some(({ event }) => Predicate.isTagged(event, "UserMessage"));
|
|
46
|
+
const lines = [
|
|
47
|
+
"A report from a specialist you started. The user hasn't seen it.",
|
|
48
|
+
started === void 0 ? `You started it for: ${finished.goal}` : `You started it ${ago(at.at - started.at)} ago for: ${finished.goal}`,
|
|
49
|
+
wrote ? "The user has written since you started it." : "The user hasn't written since you started it.",
|
|
50
|
+
Match.value(finished.status).pipe(Match.when("answered", () => `Report:\n${finished.report ?? ""}`), Match.when("failed", () => `It failed: ${finished.error ?? ""}`), Match.when("cut_off", () => "A restart cut it off before it finished. Spawn it again if it's still needed."), Match.when("stopped", () => "You stopped it."), Match.exhaustive)
|
|
51
|
+
];
|
|
52
|
+
return `<specialist name="${finished.specialist}" run="${finished.runId}" status="${finished.status}">\n${lines.join("\n")}\n</specialist>`;
|
|
53
|
+
};
|
|
54
|
+
const answerText = (answer) => `<answer message="${answer.messageId}" kind="${answer.kind}">\nThe user answered with the block on your message ${answer.messageId}.\n${toJsonText(answer.value)}\n</answer>`;
|
|
55
|
+
const resultText = (result) => `<result tool="${result.tool}" call="${result.callId}" outcome="${result.outcome}">\nThe result of a call from earlier in this chat.\n${toJsonText(result.result)}\n</result>`;
|
|
56
|
+
/** @internal */
|
|
57
|
+
const fromEntries = (entries, readApp) => {
|
|
58
|
+
const history = [];
|
|
59
|
+
const held = [];
|
|
60
|
+
const open = /* @__PURE__ */ new Set();
|
|
61
|
+
const called = /* @__PURE__ */ new Set();
|
|
62
|
+
const flushIfClosed = () => {
|
|
63
|
+
if (open.size === 0) history.push(...held.splice(0));
|
|
64
|
+
};
|
|
65
|
+
const nextSeen = (from) => {
|
|
66
|
+
const next = entries.slice(from + 1).find(({ event }) => Predicate.isTagged(event, "ModelOutput"))?.event;
|
|
67
|
+
return next !== void 0 && isLibraryEvent(next) && Predicate.isTagged(next, "ModelOutput") ? next.seenThroughSeq : void 0;
|
|
68
|
+
};
|
|
69
|
+
const input = (index, item) => {
|
|
70
|
+
const seen = nextSeen(index);
|
|
71
|
+
if (open.size > 0 || seen !== void 0 && seen < entries[index].seq) held.push(item);
|
|
72
|
+
else history.push(item);
|
|
73
|
+
};
|
|
74
|
+
entries.forEach(({ event }, index) => {
|
|
75
|
+
if (!isLibraryEvent(event)) {
|
|
76
|
+
const text = readApp(event);
|
|
77
|
+
if (text !== void 0) input(index, userText(text));
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
Match.value(event).pipe(Match.tagsExhaustive({
|
|
81
|
+
UserMessage: (message) => input(index, userText(textWithAttachments(message.text, message.attachments))),
|
|
82
|
+
BlockAnswer: (answer) => input(index, userText(answerText(answer))),
|
|
83
|
+
ModelOutput: (output) => {
|
|
84
|
+
history.push(...Prompt.fromResponseParts(output.parts).content);
|
|
85
|
+
for (const part of output.parts) if (part.type === "tool-call") {
|
|
86
|
+
open.add(part.id);
|
|
87
|
+
called.add(part.id);
|
|
88
|
+
}
|
|
89
|
+
flushIfClosed();
|
|
90
|
+
},
|
|
91
|
+
ToolResult: (result) => {
|
|
92
|
+
if (!called.has(result.callId)) return input(index, userText(resultText(result)));
|
|
93
|
+
history.push(toolMessage(result));
|
|
94
|
+
open.delete(result.callId);
|
|
95
|
+
flushIfClosed();
|
|
96
|
+
},
|
|
97
|
+
AssistantMessage: () => void 0,
|
|
98
|
+
ToolStarted: () => void 0,
|
|
99
|
+
TurnStarted: () => void 0,
|
|
100
|
+
TurnCompleted: () => void 0,
|
|
101
|
+
TurnFailed: () => void 0,
|
|
102
|
+
SpecialistFinished: (finished) => input(index, userText(finishedText(entries, index, finished))),
|
|
103
|
+
SpecialistStarted: () => void 0,
|
|
104
|
+
BlocksClosed: () => void 0,
|
|
105
|
+
ChatDeleted: () => void 0
|
|
106
|
+
}));
|
|
107
|
+
});
|
|
108
|
+
return [...history, ...held];
|
|
109
|
+
};
|
|
110
|
+
/** @internal */
|
|
111
|
+
const isInput = (event, wakes) => isInput$1(event) || !isLibraryEvent(event) && wakes.has(event._tag);
|
|
112
|
+
/** @internal */
|
|
113
|
+
const handledThrough = (entries) => {
|
|
114
|
+
const last = entries.findLast(({ event }) => isTurnEnd(event))?.event;
|
|
115
|
+
return last !== void 0 && isTurnEnd(last) ? last.throughSeq : 0;
|
|
116
|
+
};
|
|
117
|
+
/** @internal */
|
|
118
|
+
const unhandledInputs = (entries, wakes) => {
|
|
119
|
+
const seen = handledThrough(entries);
|
|
120
|
+
return entries.flatMap(({ event, seq }) => seq > seen && isInput(event, wakes) && !(isInput$1(event) && !isWaking(event)) ? [event] : []);
|
|
121
|
+
};
|
|
122
|
+
/** @internal */
|
|
123
|
+
const needsTurn = (entries, wakes) => unhandledInputs(entries, wakes).length > 0;
|
|
124
|
+
/** @internal */
|
|
125
|
+
const owesReply = (entries, throughSeq) => {
|
|
126
|
+
const latest = entries.findLast(({ event, seq }) => seq <= throughSeq && isInput$1(event) && isUserInput(event));
|
|
127
|
+
return latest !== void 0 && !entries.some(({ event, seq }) => seq > latest.seq && (Predicate.isTagged(event, "AssistantMessage") || Predicate.isTagged(event, "TurnFailed")));
|
|
128
|
+
};
|
|
129
|
+
const sentToWaitOn = Schema.decodeUnknownOption(Schema.Struct({ wait: Schema.Literal(false) }));
|
|
130
|
+
const progressMessages = (entries) => new Set(entries.flatMap(({ event }) => isLibraryEvent(event) && Predicate.isTagged(event, "ModelOutput") ? event.parts.flatMap((part) => part.type === "tool-call" && part.name === "send_user_message" && Option.isSome(sentToWaitOn(part.params)) ? [fromKey(`message:${part.id}`)] : []) : []));
|
|
131
|
+
/** @internal */
|
|
132
|
+
const owesDelivery = (entries, throughSeq) => {
|
|
133
|
+
const latest = entries.findLast(({ event, seq }) => seq <= throughSeq && isInput$1(event) && isUserInput(event));
|
|
134
|
+
if (latest === void 0) return false;
|
|
135
|
+
const after = entries.flatMap(({ seq, event }) => seq > latest.seq && isLibraryEvent(event) ? [event] : []);
|
|
136
|
+
const messages = after.flatMap((event) => Predicate.isTagged(event, "AssistantMessage") ? [event] : []);
|
|
137
|
+
const progress = progressMessages(entries);
|
|
138
|
+
const started = after.flatMap((event) => Predicate.isTagged(event, "SpecialistStarted") ? [event.runId] : []);
|
|
139
|
+
const finished = new Set(entries.flatMap(({ event }) => isLibraryEvent(event) && Predicate.isTagged(event, "SpecialistFinished") ? [event.runId] : []));
|
|
140
|
+
return messages.length > 0 && messages.every((message) => message.block === void 0 && progress.has(message.id)) && !after.some((event) => Predicate.isTagged(event, "TurnFailed")) && started.every((runId) => finished.has(runId));
|
|
141
|
+
};
|
|
142
|
+
/** @internal */
|
|
143
|
+
const unreadAfter = (entries, throughSeq, wakes) => entries.some(({ event, seq }) => seq > throughSeq && isInput(event, wakes));
|
|
144
|
+
//#endregion
|
|
145
|
+
export { fromEntries, handledThrough, isInput, mayHaveRunOutput, needsTurn, notRunOutput, owesDelivery, owesReply, toolMessage, unansweredCalls, unhandledInputs, unreadAfter, userText };
|
|
146
|
+
|
|
147
|
+
//# sourceMappingURL=history.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"history.js","names":["AssistantEvent.isLibraryEvent","AssistantEvent.textWithAttachments","AssistantEvent.isInput","AssistantEvent.isTurnEnd","AssistantEvent.isWaking","AssistantEvent.isUserInput","Uuid.fromKey"],"sources":["../../src/internal/history.ts"],"sourcesContent":["import * as Prompt from \"effect/ai/Prompt\"\nimport * as Response from \"effect/ai/Response\"\nimport * as Match from \"effect/Match\"\nimport * as Option from \"effect/Option\"\nimport * as Predicate from \"effect/Predicate\"\nimport * as Schema from \"effect/Schema\"\nimport * as AssistantEvent from \"../AssistantEvent.ts\"\nimport { toJson, toJsonText } from \"./schema.ts\"\nimport * as Uuid from \"./uuid.ts\"\n\n/** @internal */\nexport const userText = (text: string): Prompt.UserMessage =>\n Prompt.userMessage({ content: [Prompt.textPart({ text })] })\n\n/** @internal */\nexport const toolMessage = (result: AssistantEvent.ToolResult): Prompt.ToolMessage =>\n Prompt.toolMessage({\n content: [\n Prompt.toolResultPart({\n id: result.callId,\n name: result.tool,\n isFailure: result.outcome === \"failure\",\n result: result.result,\n providerExecuted: false\n })\n ]\n })\n\n// A call with no result is one a crash cut off.\n/** @internal */\nexport const unansweredCalls = (history: ReadonlyArray<Prompt.Message>): ReadonlyArray<AssistantEvent.ToolCallPart> => {\n const answered = new Set(\n history.flatMap((message) =>\n message.role === \"tool\" ? message.content.flatMap((part) => (part.type === \"tool-result\" ? [part.id] : [])) : []\n )\n )\n\n return history.flatMap((message) =>\n message.role === \"assistant\"\n ? message.content.flatMap((part) =>\n part.type === \"tool-call\" && !part.providerExecuted && !answered.has(part.id)\n ? [\n Response.makePart(\"tool-call\", {\n id: part.id,\n name: part.name,\n params: toJson(part.params),\n providerExecuted: false,\n metadata: part.options\n })\n ]\n : []\n )\n : []\n )\n}\n\n/** @internal */\nexport const notRunOutput = \"Not run: the turn ended before this call ran.\"\n\n/** @internal */\nexport const mayHaveRunOutput =\n \"Stopped: the turn ended while this call ran. It may have done its work; check before doing it again.\"\n\nconst ago = (millis: number) => {\n const seconds = Math.max(0, Math.round(millis / 1000))\n\n return seconds < 90 ? `${seconds} seconds` : `${Math.round(seconds / 60)} minutes`\n}\n\nconst finishedText = (\n entries: ReadonlyArray<AssistantEvent.AnyEntry>,\n index: number,\n finished: AssistantEvent.SpecialistFinished\n) => {\n const at = entries[index]!\n\n const isStart = ({ event }: AssistantEvent.AnyEntry) =>\n AssistantEvent.isLibraryEvent(event) &&\n Predicate.isTagged(event, \"SpecialistStarted\") &&\n event.runId === finished.runId\n\n const from = entries.slice(0, index).findLastIndex(isStart)\n const started = from === -1 ? undefined : entries[from]\n const since = started === undefined ? [] : entries.slice(from + 1, index)\n const wrote = since.some(({ event }) => Predicate.isTagged(event, \"UserMessage\"))\n\n const lines = [\n \"A report from a specialist you started. The user hasn't seen it.\",\n started === undefined\n ? `You started it for: ${finished.goal}`\n : `You started it ${ago(at.at - started.at)} ago for: ${finished.goal}`,\n wrote ? \"The user has written since you started it.\" : \"The user hasn't written since you started it.\",\n Match.value(finished.status).pipe(\n Match.when(\"answered\", () => `Report:\\n${finished.report ?? \"\"}`),\n Match.when(\"failed\", () => `It failed: ${finished.error ?? \"\"}`),\n Match.when(\"cut_off\", () => \"A restart cut it off before it finished. Spawn it again if it's still needed.\"),\n Match.when(\"stopped\", () => \"You stopped it.\"),\n Match.exhaustive\n )\n ]\n\n return `<specialist name=\"${finished.specialist}\" run=\"${finished.runId}\" status=\"${finished.status}\">\\n${lines.join(\n \"\\n\"\n )}\\n</specialist>`\n}\n\nconst answerText = (answer: AssistantEvent.BlockAnswer) =>\n `<answer message=\"${answer.messageId}\" kind=\"${answer.kind}\">\\nThe user answered with the block on your message ${answer.messageId}.\\n${toJsonText(answer.value)}\\n</answer>`\n\nconst resultText = (result: AssistantEvent.ToolResult) =>\n `<result tool=\"${result.tool}\" call=\"${result.callId}\" outcome=\"${result.outcome}\">\\nThe result of a call from earlier in this chat.\\n${toJsonText(result.result)}\\n</result>`\n\n// In the order the model saw the entries, not the order they were stored.\n/** @internal */\nexport const fromEntries = (\n entries: ReadonlyArray<AssistantEvent.AnyEntry>,\n readApp: (event: { readonly _tag: string }) => string | undefined\n): ReadonlyArray<Prompt.Message> => {\n const history: Array<Prompt.Message> = []\n const held: Array<Prompt.Message> = []\n const open = new Set<string>()\n const called = new Set<string>()\n\n const flushIfClosed = () => {\n if (open.size === 0) history.push(...held.splice(0))\n }\n\n const nextSeen = (from: number) => {\n const next = entries.slice(from + 1).find(({ event }) => Predicate.isTagged(event, \"ModelOutput\"))?.event\n\n return next !== undefined && AssistantEvent.isLibraryEvent(next) && Predicate.isTagged(next, \"ModelOutput\")\n ? next.seenThroughSeq\n : undefined\n }\n\n const input = (index: number, item: Prompt.Message) => {\n const seen = nextSeen(index)\n\n if (open.size > 0 || (seen !== undefined && seen < entries[index]!.seq)) held.push(item)\n else history.push(item)\n }\n\n entries.forEach(({ event }, index) => {\n if (!AssistantEvent.isLibraryEvent(event)) {\n const text = readApp(event)\n\n if (text !== undefined) input(index, userText(text))\n\n return\n }\n\n Match.value(event).pipe(\n Match.tagsExhaustive({\n UserMessage: (message) =>\n input(index, userText(AssistantEvent.textWithAttachments(message.text, message.attachments))),\n BlockAnswer: (answer) => input(index, userText(answerText(answer))),\n ModelOutput: (output) => {\n history.push(...Prompt.fromResponseParts(output.parts).content)\n\n for (const part of output.parts) {\n if (part.type === \"tool-call\") {\n open.add(part.id)\n called.add(part.id)\n }\n }\n\n flushIfClosed()\n },\n ToolResult: (result) => {\n // A view that starts after the call (since a summary, say) keeps its result as text: a tool result needs its\n // call right before it.\n if (!called.has(result.callId)) return input(index, userText(resultText(result)))\n\n history.push(toolMessage(result))\n open.delete(result.callId)\n flushIfClosed()\n },\n AssistantMessage: () => undefined,\n ToolStarted: () => undefined,\n TurnStarted: () => undefined,\n TurnCompleted: () => undefined,\n TurnFailed: () => undefined,\n SpecialistFinished: (finished) => input(index, userText(finishedText(entries, index, finished))),\n // Every run starts in a call of the model's own (a spawn or a follow-up), whose result says so.\n SpecialistStarted: () => undefined,\n BlocksClosed: () => undefined,\n ChatDeleted: () => undefined\n })\n )\n })\n\n return [...history, ...held]\n}\n\n/** @internal */\nexport const isInput = (event: { readonly _tag: string }, wakes: ReadonlySet<string>): boolean =>\n AssistantEvent.isInput(event) || (!AssistantEvent.isLibraryEvent(event) && wakes.has(event._tag))\n\n/** @internal */\nexport const handledThrough = (entries: ReadonlyArray<AssistantEvent.AnyEntry>): number => {\n const last = entries.findLast(({ event }) => AssistantEvent.isTurnEnd(event))?.event\n\n return last !== undefined && AssistantEvent.isTurnEnd(last) ? last.throughSeq : 0\n}\n\n// A run the assistant stopped isn't an input here: it wakes nothing.\n/** @internal */\nexport const unhandledInputs = (\n entries: ReadonlyArray<AssistantEvent.AnyEntry>,\n wakes: ReadonlySet<string>\n): ReadonlyArray<{ readonly _tag: string }> => {\n const seen = handledThrough(entries)\n\n return entries.flatMap(({ event, seq }) =>\n seq > seen && isInput(event, wakes) && !(AssistantEvent.isInput(event) && !AssistantEvent.isWaking(event))\n ? [event]\n : []\n )\n}\n\n/** @internal */\nexport const needsTurn = (entries: ReadonlyArray<AssistantEvent.AnyEntry>, wakes: ReadonlySet<string>): boolean =>\n unhandledInputs(entries, wakes).length > 0\n\n// A failure the user was shown counts as a reply.\n/** @internal */\nexport const owesReply = (entries: ReadonlyArray<AssistantEvent.AnyEntry>, throughSeq: number): boolean => {\n const latest = entries.findLast(\n ({ event, seq }) => seq <= throughSeq && AssistantEvent.isInput(event) && AssistantEvent.isUserInput(event)\n )\n\n return (\n latest !== undefined &&\n !entries.some(\n ({ event, seq }) =>\n seq > latest.seq && (Predicate.isTagged(event, \"AssistantMessage\") || Predicate.isTagged(event, \"TurnFailed\"))\n )\n )\n}\n\nconst sentToWaitOn = Schema.decodeUnknownOption(Schema.Struct({ wait: Schema.Literal(false) }))\n\n// Messages sent with `wait: false` are progress while the model kept working, like \"On it\".\nconst progressMessages = (entries: ReadonlyArray<AssistantEvent.AnyEntry>): ReadonlySet<string> =>\n new Set(\n entries.flatMap(({ event }) =>\n AssistantEvent.isLibraryEvent(event) && Predicate.isTagged(event, \"ModelOutput\")\n ? event.parts.flatMap((part) =>\n part.type === \"tool-call\" && part.name === \"send_user_message\" && Option.isSome(sentToWaitOn(part.params))\n ? [Uuid.fromKey(`message:${part.id}`)]\n : []\n )\n : []\n )\n )\n\n// The model said it was on it and stopped without the result: only progress messages since the user's latest input.\n// A specialist still working doesn't count, since its report is what comes next.\n/** @internal */\nexport const owesDelivery = (entries: ReadonlyArray<AssistantEvent.AnyEntry>, throughSeq: number): boolean => {\n const latest = entries.findLast(\n ({ event, seq }) => seq <= throughSeq && AssistantEvent.isInput(event) && AssistantEvent.isUserInput(event)\n )\n\n if (latest === undefined) return false\n\n const after = entries.flatMap(({ seq, event }) =>\n seq > latest.seq && AssistantEvent.isLibraryEvent(event) ? [event] : []\n )\n\n const messages = after.flatMap((event) => (Predicate.isTagged(event, \"AssistantMessage\") ? [event] : []))\n const progress = progressMessages(entries)\n const started = after.flatMap((event) => (Predicate.isTagged(event, \"SpecialistStarted\") ? [event.runId] : []))\n\n const finished = new Set(\n entries.flatMap(({ event }) =>\n AssistantEvent.isLibraryEvent(event) && Predicate.isTagged(event, \"SpecialistFinished\") ? [event.runId] : []\n )\n )\n\n return (\n messages.length > 0 &&\n messages.every((message) => message.block === undefined && progress.has(message.id)) &&\n !after.some((event) => Predicate.isTagged(event, \"TurnFailed\")) &&\n started.every((runId) => finished.has(runId))\n )\n}\n\n/** @internal */\nexport const unreadAfter = (\n entries: ReadonlyArray<AssistantEvent.AnyEntry>,\n throughSeq: number,\n wakes: ReadonlySet<string>\n): boolean => entries.some(({ event, seq }) => seq > throughSeq && isInput(event, wakes))\n"],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as Predicate from "effect/Predicate";
|
|
2
|
+
//#region src/internal/instructions.ts
|
|
3
|
+
/** @internal */
|
|
4
|
+
const mapText = (instructions, f) => Predicate.isString(instructions) ? { text: f(instructions) } : {
|
|
5
|
+
...instructions,
|
|
6
|
+
text: f(instructions.text)
|
|
7
|
+
};
|
|
8
|
+
//#endregion
|
|
9
|
+
export { mapText };
|
|
10
|
+
|
|
11
|
+
//# sourceMappingURL=instructions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"instructions.js","names":[],"sources":["../../src/internal/instructions.ts"],"sourcesContent":["import * as Predicate from \"effect/Predicate\"\nimport type * as Instructions from \"../Instructions.ts\"\n\n/** @internal */\nexport const mapText = (\n instructions: string | Instructions.Resolved,\n f: (text: string) => string\n): Instructions.Resolved =>\n Predicate.isString(instructions) ? { text: f(instructions) } : { ...instructions, text: f(instructions.text) }\n"],"mappings":";;;AAIA,MAAa,WACX,cACA,MAEA,UAAU,SAAS,YAAY,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,IAAI;CAAE,GAAG;CAAc,MAAM,EAAE,aAAa,IAAI;AAAE"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { AssistantEvent_d_exports, ModelOutput, ToolResult, ToolStarted } from "../AssistantEvent.js";
|
|
2
|
+
import { Instructions_d_exports, Resolved } from "../Instructions.js";
|
|
3
|
+
import { Tools, toolkit_d_exports } from "./toolkit.js";
|
|
4
|
+
import * as Context from "effect/Context";
|
|
5
|
+
import * as Effect from "effect/Effect";
|
|
6
|
+
import * as Stream from "effect/Stream";
|
|
7
|
+
import * as Data from "effect/Data";
|
|
8
|
+
import * as Option from "effect/Option";
|
|
9
|
+
import * as Prompt from "effect/ai/Prompt";
|
|
10
|
+
import * as LanguageModel from "effect/ai/LanguageModel";
|
|
11
|
+
import * as AiError from "effect/ai/AiError";
|
|
12
|
+
declare namespace loop_d_exports {
|
|
13
|
+
export { ChatHistory, Event, Options, Step, StepOptions, StepResult, TurnTools, loop, next, run, step, stop, value };
|
|
14
|
+
}
|
|
15
|
+
/** @internal */
|
|
16
|
+
export type Event = ModelOutput | ToolStarted | ToolResult;
|
|
17
|
+
/** @internal */
|
|
18
|
+
export declare const TurnTools: Context.Reference<Option.Option<Tools<never>>>;
|
|
19
|
+
/** @internal */
|
|
20
|
+
export interface ChatHistory {
|
|
21
|
+
readonly messages: ReadonlyArray<Prompt.Message>;
|
|
22
|
+
readonly throughSeq: number;
|
|
23
|
+
}
|
|
24
|
+
/** @internal */
|
|
25
|
+
export interface StepOptions<R = never> {
|
|
26
|
+
readonly instructions: string | Resolved;
|
|
27
|
+
readonly toolkit?: Tools<R> | undefined;
|
|
28
|
+
readonly history: Effect.Effect<ReadonlyArray<Prompt.Message> | ChatHistory>;
|
|
29
|
+
readonly toolChoice?: LanguageModel.ToolChoice<string> | undefined;
|
|
30
|
+
}
|
|
31
|
+
/** @internal */
|
|
32
|
+
export interface Options<R = never> extends StepOptions<R> {
|
|
33
|
+
readonly maxSteps?: number | undefined;
|
|
34
|
+
}
|
|
35
|
+
/** @internal */
|
|
36
|
+
export interface StepResult {
|
|
37
|
+
/** The model's output; unset when the step only ran calls a crash cut off. */
|
|
38
|
+
readonly output: ModelOutput | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* Whether the model is done: it made no tool calls, or ended its turn (`wait`, or a message
|
|
41
|
+
* with `wait: true`) and none of the step's calls failed.
|
|
42
|
+
*/
|
|
43
|
+
readonly done: boolean;
|
|
44
|
+
/**
|
|
45
|
+
* The history's length with this step's output and results: a longer one got something new meanwhile.
|
|
46
|
+
*/
|
|
47
|
+
readonly historyLength: number;
|
|
48
|
+
}
|
|
49
|
+
/** @internal */
|
|
50
|
+
export type Step<A, S> = Data.TaggedEnum<{
|
|
51
|
+
Value: {
|
|
52
|
+
readonly value: A;
|
|
53
|
+
};
|
|
54
|
+
Next: {
|
|
55
|
+
readonly state: S;
|
|
56
|
+
};
|
|
57
|
+
Stop: Record<never, never>;
|
|
58
|
+
}>;
|
|
59
|
+
/** @internal */
|
|
60
|
+
export declare const value: <A>(value: A) => Step<A, never>;
|
|
61
|
+
/** @internal */
|
|
62
|
+
export declare const next: <S>(state: S) => Stream.Stream<Step<never, S>>;
|
|
63
|
+
/** @internal */
|
|
64
|
+
export declare const stop: <S = never>() => Stream.Stream<Step<never, S>>;
|
|
65
|
+
/** @internal */
|
|
66
|
+
export declare const loop: <S, A, E, R>(initial: S, body: (state: S) => Stream.Stream<Step<A, S>, E, R>) => Stream.Stream<A, E, R>;
|
|
67
|
+
/** @internal */
|
|
68
|
+
export declare const step: <R, S, E2 = never, R2 = never>(options: StepOptions<R>, then: (result: StepResult) => Stream.Stream<Step<never, S>, E2, R2>) => Stream.Stream<Step<Event, S>, AiError.AiError | E2, LanguageModel.LanguageModel | R | R2>;
|
|
69
|
+
/** @internal */
|
|
70
|
+
export declare const run: <R = never>(options: Options<R>) => Stream.Stream<Event, AiError.AiError, LanguageModel.LanguageModel | R>;
|
|
71
|
+
//#endregion
|
|
72
|
+
export { loop_d_exports };
|
|
73
|
+
//# sourceMappingURL=loop.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loop.d.ts","names":[],"sources":["../../src/internal/loop.ts"],"mappings":";;;;;;;;;;;;;;;YAuBY,QAAQ,cAA6B,cAA6B;;qBAKjE,WAAS,QAAA,UAAA,OAAA,OAAA;;iBAKL;WACN,UAAU,cAAc,OAAO;WAC/B;;;iBAUM,YAAY;WAClB,uBAAuB;WACvB,UAAU,MAAe;WACzB,SAAS,OAAO,OAAO,cAAc,OAAO,WAAW;WACvD,aAAa,cAAc;;;iBAIrB,QAAQ,mBAAmB,YAAY;WAC7C;;;iBAKM;;WAEN,QAAQ;;;;;WAKR;;;;WAIA;;;YAmEC,KAAK,GAAG,KAAK,KAAK;EAC5B;aAAkB,OAAO;;EACzB;aAAiB,OAAO;;EACxB,MAAM;;;qBAUK,QAAS,GAAC,OAAS,MAAI,KAAK;;qBAG5B,OAAQ,GAAC,OAAS,MAAI,OAAO,OAAO,YAAY;;qBAIhD,OAAQ,gBAAc,OAAO,OAAO,YAAY;;qBAOhD,OAAQ,GAAG,GAAG,GAAG,GAAC,SACpB,GAAC,OACH,OAAO,MAAM,OAAO,OAAO,KAAK,GAAG,IAAI,GAAG,OAChD,OAAO,OAAO,GAAG,GAAG;;qBAmBV,OAAQ,GAAG,GAAG,YAAY,YAAU,SACtC,YAAY,IAAE,OAChB,QAAQ,eAAe,OAAO,OAAO,YAAY,IAAI,IAAI,QAC/D,OAAO,OAAO,KAAK,OAAO,IAAI,QAAQ,UAAU,IAAI,cAAc,gBAAgB,IAAI;;qBA4D5E,MAAO,WAAS,SAClB,QAAQ,OAChB,OAAO,OAAO,OAAO,QAAQ,SAAS,cAAc,gBAAgB"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { ModelOutput, ModelPart, ToolResult, ToolStarted } from "../AssistantEvent.js";
|
|
2
|
+
import { unansweredCalls } from "./history.js";
|
|
3
|
+
import { chatSpan, toolOutput, toolSpan } from "./telemetry.js";
|
|
4
|
+
import { empty, needingApproval, needsApprovalMessage } from "./toolkit.js";
|
|
5
|
+
import { isTurnEnding } from "./tools.js";
|
|
6
|
+
import * as Predicate from "effect/Predicate";
|
|
7
|
+
import * as Context from "effect/Context";
|
|
8
|
+
import * as Effect from "effect/Effect";
|
|
9
|
+
import * as Schema from "effect/Schema";
|
|
10
|
+
import * as Stream from "effect/Stream";
|
|
11
|
+
import * as Data from "effect/Data";
|
|
12
|
+
import * as Option from "effect/Option";
|
|
13
|
+
import * as Clock from "effect/Clock";
|
|
14
|
+
import * as Prompt from "effect/ai/Prompt";
|
|
15
|
+
import * as LanguageModel from "effect/ai/LanguageModel";
|
|
16
|
+
import * as Model from "effect/ai/Model";
|
|
17
|
+
//#region src/internal/loop.ts
|
|
18
|
+
/** @internal */
|
|
19
|
+
const TurnTools = Context.Reference("effect-assistant/TurnTools", { defaultValue: Option.none });
|
|
20
|
+
const readHistory = (read) => "messages" in read ? {
|
|
21
|
+
messages: read.messages,
|
|
22
|
+
throughSeq: read.throughSeq
|
|
23
|
+
} : {
|
|
24
|
+
messages: read,
|
|
25
|
+
throughSeq: void 0
|
|
26
|
+
};
|
|
27
|
+
const requestToolkit = (definitions) => ({
|
|
28
|
+
tools: Object.fromEntries(definitions.map((tool) => [tool.name, tool])),
|
|
29
|
+
handle: () => Effect.succeed(Stream.empty)
|
|
30
|
+
});
|
|
31
|
+
const generate = (prompt, definitions, toolChoice) => LanguageModel.generateText({
|
|
32
|
+
prompt,
|
|
33
|
+
toolkit: requestToolkit(definitions),
|
|
34
|
+
toolChoice
|
|
35
|
+
});
|
|
36
|
+
const decodePart = Schema.decodeUnknownOption(ModelPart);
|
|
37
|
+
const modelParts = (content) => content.flatMap((part) => Option.toArray(decodePart(part)).filter((decoded) => !(decoded.type === "tool-call" && decoded.providerExecuted)));
|
|
38
|
+
const toolCalls = (parts) => parts.flatMap((part) => part.type === "tool-call" ? [part] : []);
|
|
39
|
+
const runCall = Effect.fnUntraced(function* (tools, call) {
|
|
40
|
+
const result = yield* Effect.gen(function* () {
|
|
41
|
+
const result = yield* tools.run(call);
|
|
42
|
+
yield* toolOutput(result.result);
|
|
43
|
+
return result;
|
|
44
|
+
}).pipe(toolSpan(call));
|
|
45
|
+
return ToolResult.make({
|
|
46
|
+
callId: call.id,
|
|
47
|
+
tool: call.name,
|
|
48
|
+
...result
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
const runCalls = (tools, calls, then) => Stream.fromIterable(calls.map((call) => value(ToolStarted.make({
|
|
52
|
+
callId: call.id,
|
|
53
|
+
tool: call.name
|
|
54
|
+
})))).pipe(Stream.concat(Stream.unwrap(Effect.gen(function* () {
|
|
55
|
+
const done = yield* Effect.forEach(calls, (call) => runCall(tools, call), { concurrency: "unbounded" });
|
|
56
|
+
return Stream.fromIterable(done.map(value)).pipe(Stream.concat(then(done.some(isFailure))));
|
|
57
|
+
}))));
|
|
58
|
+
const isFailure = (result) => result.outcome === "failure";
|
|
59
|
+
const Steps = Data.taggedEnum();
|
|
60
|
+
/** @internal */
|
|
61
|
+
const value = (value) => Steps.Value({ value });
|
|
62
|
+
/** @internal */
|
|
63
|
+
const next = (state) => Stream.succeed(Steps.Next({ state }));
|
|
64
|
+
/** @internal */
|
|
65
|
+
const stop = () => Stream.succeed(Steps.Stop());
|
|
66
|
+
const isValue = (step) => Predicate.isTagged(step, "Value");
|
|
67
|
+
/** @internal */
|
|
68
|
+
const loop = (initial, body) => {
|
|
69
|
+
const go = (state) => Stream.suspend(() => {
|
|
70
|
+
let after = Option.none();
|
|
71
|
+
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, {
|
|
72
|
+
onNone: () => Stream.empty,
|
|
73
|
+
onSome: go
|
|
74
|
+
}))));
|
|
75
|
+
});
|
|
76
|
+
return go(initial);
|
|
77
|
+
};
|
|
78
|
+
/** @internal */
|
|
79
|
+
const step = (options, then) => {
|
|
80
|
+
const toolkit = options.toolkit ?? empty;
|
|
81
|
+
const instructions = Predicate.isString(options.instructions) ? { text: options.instructions } : options.instructions;
|
|
82
|
+
const after = (result) => Stream.suspend(() => then(result));
|
|
83
|
+
const needingApproval$1 = needingApproval(toolkit.definitions);
|
|
84
|
+
if (needingApproval$1.length > 0) return Stream.die(needsApprovalMessage(needingApproval$1));
|
|
85
|
+
return Stream.unwrap(Effect.gen(function* () {
|
|
86
|
+
const { messages: history, throughSeq } = readHistory(yield* options.history);
|
|
87
|
+
const cutOff = unansweredCalls(history);
|
|
88
|
+
if (cutOff.length > 0) return runCalls(toolkit, cutOff, () => after({
|
|
89
|
+
output: void 0,
|
|
90
|
+
done: false,
|
|
91
|
+
historyLength: history.length + cutOff.length
|
|
92
|
+
}));
|
|
93
|
+
const prompt = Prompt.fromMessages([Prompt.systemMessage({ content: instructions.text }), ...history]);
|
|
94
|
+
const name = yield* Effect.serviceOption(Model.ModelName);
|
|
95
|
+
const started = yield* Clock.currentTimeMillis;
|
|
96
|
+
const response = yield* generate(prompt, toolkit.definitions, options.toolChoice).pipe(chatSpan(prompt, instructions.attributes));
|
|
97
|
+
const metadata = response.content.find((part) => part.type === "response-metadata");
|
|
98
|
+
const output = ModelOutput.make({
|
|
99
|
+
parts: modelParts(response.content),
|
|
100
|
+
seenThroughSeq: throughSeq,
|
|
101
|
+
model: metadata?.modelId ?? Option.getOrUndefined(name),
|
|
102
|
+
responseId: metadata?.id,
|
|
103
|
+
usage: response.usage,
|
|
104
|
+
durationMillis: (yield* Clock.currentTimeMillis) - started
|
|
105
|
+
});
|
|
106
|
+
const calls = toolCalls(output.parts);
|
|
107
|
+
const historyLength = history.length + Prompt.fromResponseParts(output.parts).content.length + calls.length;
|
|
108
|
+
return Stream.make(value(output)).pipe(Stream.concat(runCalls(toolkit, calls, (failed) => after({
|
|
109
|
+
output,
|
|
110
|
+
done: calls.length === 0 || !failed && calls.some(isTurnEnding),
|
|
111
|
+
historyLength
|
|
112
|
+
}))));
|
|
113
|
+
}));
|
|
114
|
+
};
|
|
115
|
+
/** @internal */
|
|
116
|
+
const run = (options) => {
|
|
117
|
+
const maxSteps = options.maxSteps ?? 16;
|
|
118
|
+
const afterDone = (steps, historyLength) => Stream.unwrap(Effect.gen(function* () {
|
|
119
|
+
const read = yield* options.history;
|
|
120
|
+
return readHistory(read).messages.length > historyLength ? next(steps) : stop();
|
|
121
|
+
}));
|
|
122
|
+
return loop(0, (steps) => step(options, (result) => {
|
|
123
|
+
const taken = result.output === void 0 ? steps : steps + 1;
|
|
124
|
+
if (taken >= maxSteps) return stop();
|
|
125
|
+
return result.done ? afterDone(taken, result.historyLength) : next(taken);
|
|
126
|
+
}));
|
|
127
|
+
};
|
|
128
|
+
//#endregion
|
|
129
|
+
export { TurnTools, loop, next, run, step, stop, value };
|
|
130
|
+
|
|
131
|
+
//# sourceMappingURL=loop.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loop.js","names":["AssistantEvent.ModelPart","Telemetry.toolOutput","Telemetry.toolSpan","Toolkits.empty","needingApproval","Toolkits.needingApproval","Toolkits.needsApprovalMessage","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 type * as Instructions from \"../Instructions.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 = AssistantEvent.ModelOutput | AssistantEvent.ToolStarted | AssistantEvent.ToolResult\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 tools: Object.fromEntries(definitions.map((tool) => [tool.name, tool])),\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\n// Every `ToolStarted` is stored before any call runs. `then` is told whether one of the calls failed, so the model\n// has to read the results.\nconst runCalls = <R, S, E, R2>(\n tools: Toolkits.Tools<R>,\n calls: ReadonlyArray<AssistantEvent.ToolCallPart>,\n then: (failed: boolean) => Stream.Stream<Step<never, S>, E, R2>\n): Stream.Stream<Step<Event, S>, E, R | R2> =>\n Stream.fromIterable(\n calls.map((call) => value(AssistantEvent.ToolStarted.make({ callId: call.id, tool: call.name })))\n ).pipe(\n Stream.concat(\n Stream.unwrap(\n Effect.gen(function* () {\n const done = yield* Effect.forEach(calls, (call) => runCall(tools, call), { concurrency: \"unbounded\" })\n\n return Stream.fromIterable(done.map(value)).pipe(Stream.concat(then(done.some(isFailure))))\n })\n )\n )\n )\n\nconst isFailure = (result: AssistantEvent.ToolResult) => result.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 const needingApproval = Toolkits.needingApproval(toolkit.definitions)\n\n if (needingApproval.length > 0) return Stream.die(Toolkits.needsApprovalMessage(needingApproval))\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 if (cutOff.length > 0) {\n return runCalls(toolkit, cutOff, () =>\n after({ output: undefined, done: false, historyLength: history.length + cutOff.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.\n return Stream.make(value(output)).pipe(\n Stream.concat(\n runCalls(toolkit, calls, (failed) =>\n after({\n output,\n done: calls.length === 0 || (!failed && 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"}
|