effect-assistant 0.1.0 → 0.3.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/README.md +1 -1
- package/dist/Assistant.d.ts +74 -28
- package/dist/Assistant.d.ts.map +1 -1
- package/dist/Assistant.js +11 -4
- package/dist/Assistant.js.map +1 -1
- package/dist/AssistantActivity.d.ts +10 -16
- package/dist/AssistantActivity.d.ts.map +1 -1
- package/dist/AssistantActivity.js +11 -4
- package/dist/AssistantActivity.js.map +1 -1
- package/dist/AssistantEngine.d.ts +39 -1
- package/dist/AssistantEngine.d.ts.map +1 -1
- package/dist/AssistantEngine.js +81 -28
- package/dist/AssistantEngine.js.map +1 -1
- package/dist/AssistantEvent.d.ts +52 -70
- package/dist/AssistantEvent.d.ts.map +1 -1
- package/dist/AssistantEvent.js +28 -57
- package/dist/AssistantEvent.js.map +1 -1
- package/dist/AssistantLoop.d.ts +7 -5
- package/dist/AssistantLoop.d.ts.map +1 -1
- package/dist/AssistantLoop.js +6 -3
- package/dist/AssistantLoop.js.map +1 -1
- package/dist/Block.js.map +1 -1
- package/dist/BlockKit.d.ts +14 -18
- package/dist/BlockKit.d.ts.map +1 -1
- package/dist/BlockKit.js +4 -7
- package/dist/BlockKit.js.map +1 -1
- package/dist/ChatLog.js +1 -1
- package/dist/Compaction.d.ts +8 -4
- package/dist/Compaction.d.ts.map +1 -1
- package/dist/Compaction.js +12 -15
- package/dist/Compaction.js.map +1 -1
- package/dist/CurrentChat.d.ts +2 -1
- package/dist/CurrentChat.d.ts.map +1 -1
- package/dist/CurrentChat.js.map +1 -1
- package/dist/Instructions.js +1 -1
- package/dist/Skill.d.ts +33 -0
- package/dist/Skill.d.ts.map +1 -1
- package/dist/Skill.js +27 -1
- package/dist/Skill.js.map +1 -1
- package/dist/Specialist.d.ts +4 -1
- package/dist/Specialist.d.ts.map +1 -1
- package/dist/Specialist.js.map +1 -1
- package/dist/Timeline.d.ts +482 -0
- package/dist/Timeline.d.ts.map +1 -0
- package/dist/Timeline.js +572 -0
- package/dist/Timeline.js.map +1 -0
- package/dist/ToolDiscovery.d.ts +4 -5
- package/dist/ToolDiscovery.d.ts.map +1 -1
- package/dist/ToolDiscovery.js +5 -4
- package/dist/ToolDiscovery.js.map +1 -1
- package/dist/ToolFailure.d.ts +1 -1
- package/dist/ToolFailure.js +1 -1
- package/dist/ToolFailure.js.map +1 -1
- package/dist/index.d.ts +2 -5
- package/dist/index.js +4 -7
- package/dist/internal/chat.js +11 -37
- package/dist/internal/chat.js.map +1 -1
- package/dist/internal/events.d.ts +15 -16
- package/dist/internal/events.d.ts.map +1 -1
- package/dist/internal/events.js +29 -28
- package/dist/internal/events.js.map +1 -1
- package/dist/internal/history.js +26 -58
- package/dist/internal/history.js.map +1 -1
- package/dist/internal/input.js +7 -0
- package/dist/internal/input.js.map +1 -0
- package/dist/internal/loop.d.ts +2 -2
- package/dist/internal/loop.d.ts.map +1 -1
- package/dist/internal/loop.js +21 -56
- package/dist/internal/loop.js.map +1 -1
- package/dist/internal/skills.js +6 -1
- package/dist/internal/skills.js.map +1 -1
- package/dist/internal/specialists.js +7 -7
- package/dist/internal/specialists.js.map +1 -1
- package/dist/internal/toolkit.d.ts +6 -20
- package/dist/internal/toolkit.d.ts.map +1 -1
- package/dist/internal/toolkit.js +10 -31
- package/dist/internal/toolkit.js.map +1 -1
- package/dist/internal/tools.js +2 -21
- package/dist/internal/tools.js.map +1 -1
- package/dist/internal/turns.d.ts.map +1 -1
- package/dist/internal/turns.js +24 -43
- package/dist/internal/turns.js.map +1 -1
- package/package.json +3 -2
- package/src/Assistant.ts +75 -29
- package/src/AssistantActivity.ts +11 -18
- package/src/AssistantEngine.ts +110 -43
- package/src/AssistantEvent.ts +46 -74
- package/src/AssistantLoop.ts +7 -5
- package/src/Block.ts +1 -1
- package/src/BlockKit.ts +18 -31
- package/src/Compaction.ts +8 -21
- package/src/CurrentChat.ts +2 -1
- package/src/Skill.ts +34 -1
- package/src/Specialist.ts +4 -1
- package/src/Timeline.ts +861 -0
- package/src/ToolDiscovery.ts +5 -13
- package/src/ToolFailure.ts +1 -1
- package/src/index.ts +5 -20
- package/src/internal/chat.ts +8 -57
- package/src/internal/events.ts +43 -43
- package/src/internal/history.ts +36 -101
- package/src/internal/input.ts +3 -0
- package/src/internal/loop.ts +31 -101
- package/src/internal/skills.ts +11 -0
- package/src/internal/specialists.ts +6 -9
- package/src/internal/toolkit.ts +17 -60
- package/src/internal/tools.ts +2 -53
- package/src/internal/turns.ts +37 -79
- package/dist/AppEvent.d.ts +0 -111
- package/dist/AppEvent.d.ts.map +0 -1
- package/dist/AppEvent.js +0 -71
- package/dist/AppEvent.js.map +0 -1
- package/dist/ChatView.d.ts +0 -459
- package/dist/ChatView.d.ts.map +0 -1
- package/dist/ChatView.js +0 -496
- package/dist/ChatView.js.map +0 -1
- package/dist/Decision.d.ts +0 -145
- package/dist/Decision.d.ts.map +0 -1
- package/dist/Decision.js +0 -178
- package/dist/Decision.js.map +0 -1
- package/dist/DecisionId.d.ts +0 -37
- package/dist/DecisionId.d.ts.map +0 -1
- package/dist/DecisionId.js +0 -44
- package/dist/DecisionId.js.map +0 -1
- package/dist/internal/decisions.js +0 -66
- package/dist/internal/decisions.js.map +0 -1
- package/src/AppEvent.ts +0 -134
- package/src/ChatView.ts +0 -658
- package/src/Decision.ts +0 -241
- package/src/DecisionId.ts +0 -46
- package/src/internal/decisions.ts +0 -131
|
@@ -1 +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
|
|
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 Predicate from \"effect/Predicate\"\nimport * as Record from \"effect/Record\"\nimport * as Schema from \"effect/Schema\"\nimport * as SchemaAST from \"effect/SchemaAST\"\nimport type * as Assistant from \"../Assistant.ts\"\nimport * as AssistantEvent from \"../AssistantEvent.ts\"\nimport { inputKey } from \"./input.ts\"\n\n/** @internal */\nexport interface AppEvents {\n readonly byTag: ReadonlyMap<string, AssistantEvent.AppEventSchema>\n}\n\n/** @internal */\nexport const tagOf = (schema: AssistantEvent.AppEventSchema): string => {\n const ast = schema.fields._tag.ast\n\n if (SchemaAST.isLiteral(ast) && Predicate.isString(ast.literal)) return ast.literal\n\n throw new Error(\"An app event is a tagged struct or class.\")\n}\n\n/** @internal */\nexport const make = (events: ReadonlyArray<AssistantEvent.AppEventSchema>): AppEvents => {\n const byTag = new Map(events.map((schema): [string, AssistantEvent.AppEventSchema] => [tagOf(schema), schema]))\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 }\n}\n\n/** @internal */\nexport const encode = Effect.fnUntraced(function* (app: AppEvents, event: { readonly _tag: string }) {\n const schema = 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 (schema === undefined) return yield* Effect.die(`${event._tag} isn't one of this assistant's events.`)\n\n const json = yield* Schema.encodeUnknownEffect(Schema.toCodecJson(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// The app's schema decodes the event as the app wrote it.\nconst unmarked = (event: { readonly _tag: string }) =>\n Predicate.hasProperty(event, inputKey) ? Record.remove(event, inputKey) : event\n\n// An event as the log stores it, decoded with its schema.\n/** @internal */\nexport const decodeWith = (\n schema: AssistantEvent.AppEventSchema,\n event: { readonly _tag: string }\n): Option.Option<{ readonly _tag: string }> => Schema.decodeUnknownOption(Schema.toCodecJson(schema))(unmarked(event))\n\nconst decodeEvent = (app: AppEvents, event: { readonly _tag: string }): Option.Option<{ readonly _tag: string }> => {\n const schema = app.byTag.get(event._tag)\n\n return schema === undefined ? Option.none() : decodeWith(schema, event)\n}\n\n// What came in through `submit`, from outside a turn, is an input: it wakes a turn.\n/** @internal */\nexport const markInput = (event: AssistantEvent.StoredAppEvent): AssistantEvent.StoredAppEvent => ({\n ...event,\n [inputKey]: true\n})\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 return Option.map(decodeEvent(app, event), (decoded) => ({ ...toEntry(entry), event: decoded }))\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 toEntry = (entry: AssistantEvent.AnyEntry) => ({\n chatId: entry.chatId,\n seq: entry.seq,\n at: entry.at\n})\n"],"mappings":";;;;;;;;;;AAgBA,MAAa,SAAS,WAAkD;CACtE,MAAM,MAAM,OAAO,OAAO,KAAK;CAE/B,IAAI,UAAU,UAAU,GAAG,KAAK,UAAU,SAAS,IAAI,OAAO,GAAG,OAAO,IAAI;CAE5E,MAAM,IAAI,MAAM,2CAA2C;AAC7D;;AAGA,MAAa,QAAQ,WAAoE;CACvF,MAAM,QAAQ,IAAI,IAAI,OAAO,KAAK,WAAoD,CAAC,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC;CAE9G,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,MAAM;AACjB;;AAGA,MAAa,SAAS,OAAO,WAAW,WAAW,KAAgB,OAAkC;CACnG,MAAM,SAAS,IAAI,MAAM,IAAI,MAAM,IAAI;CAGvC,IAAI,WAAW,KAAA,GAAW,OAAO,OAAO,OAAO,IAAI,GAAG,MAAM,KAAK,uCAAuC;CAExG,MAAM,OAAO,OAAO,OAAO,oBAAoB,OAAO,YAAY,MAAM,CAAC,CAAC,CAAC,KAAK;CAEhF,OAAO,OAAO,OAAO,oBAAoBC,cAA6B,CAAC,CAAC,IAAI;AAC9E,GAAG,OAAO,KAAK;;AAGf,MAAa,gBAAgB,KAAgB,UAA8C,IAAI,MAAM,IAAI,MAAM,IAAI;AAGnH,MAAM,YAAY,UAChB,UAAU,YAAY,OAAA,yBAAe,IAAI,OAAO,OAAO,OAAO,QAAQ,IAAI;;AAI5E,MAAa,cACX,QACA,UAC6C,OAAO,oBAAoB,OAAO,YAAY,MAAM,CAAC,CAAC,CAAC,SAAS,KAAK,CAAC;AAErH,MAAM,eAAe,KAAgB,UAA+E;CAClH,MAAM,SAAS,IAAI,MAAM,IAAI,MAAM,IAAI;CAEvC,OAAO,WAAW,KAAA,IAAY,OAAO,KAAK,IAAI,WAAW,QAAQ,KAAK;AACxE;;AAIA,MAAa,aAAa,WAAyE;CACjG,GAAG;EACF,WAAW;AACd;;AAIA,MAAa,eAAe,KAAgB,UAAwE;CAClH,MAAM,EAAE,UAAU;CAElB,IAAID,eAA8B,KAAK,GAAG,OAAO,OAAO,KAAK;EAAE,GAAG,QAAQ,KAAK;EAAG;CAAM,CAAC;CAEzF,OAAO,OAAO,IAAI,YAAY,KAAK,KAAK,IAAI,aAAa;EAAE,GAAG,QAAQ,KAAK;EAAG,OAAO;CAAQ,EAAE;AACjG;;AAGA,MAAa,iBACX,KACA,YACwC,QAAQ,SAAS,UAAU,OAAO,QAAQ,YAAY,KAAK,KAAK,CAAC,CAAC;AAK5G,MAAM,WAAW,WAAoC;CACnD,QAAQ,MAAM;CACd,KAAK,MAAM;CACX,IAAI,MAAM;AACZ"}
|
package/dist/internal/history.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { fromKey } from "./uuid.js";
|
|
2
|
-
import { isInput as isInput$1, isLibraryEvent, isTurnEnd, isUserInput, isWaking, textWithAttachments } from "../AssistantEvent.js";
|
|
3
|
-
import { Answer } from "../Decision.js";
|
|
2
|
+
import { isAppInput, isInput as isInput$1, isLibraryEvent, isTurnEnd, isUserInput, isWaking, textWithAttachments } from "../AssistantEvent.js";
|
|
4
3
|
import { toJson, toJsonText } from "./schema.js";
|
|
5
4
|
import * as Predicate from "effect/Predicate";
|
|
6
5
|
import * as Schema from "effect/Schema";
|
|
@@ -53,39 +52,14 @@ const finishedText = (entries, index, finished) => {
|
|
|
53
52
|
return `<specialist name="${finished.specialist}" run="${finished.runId}" status="${finished.status}">\n${lines.join("\n")}\n</specialist>`;
|
|
54
53
|
};
|
|
55
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>`;
|
|
56
|
-
/** @internal */
|
|
57
|
-
const parkedResult = (request, origin) => Prompt.toolMessage({ content: [Prompt.toolResultPart({
|
|
58
|
-
id: origin.callId,
|
|
59
|
-
name: origin.tool,
|
|
60
|
-
isFailure: false,
|
|
61
|
-
result: {
|
|
62
|
-
status: "waiting_for_decision",
|
|
63
|
-
decisionId: request.id,
|
|
64
|
-
next: "Not run: it runs only if the user approves it. Send them a message with a decision block for this decisionId that says what they approve (the call's key facts). Their answer runs it, or declines it."
|
|
65
|
-
},
|
|
66
|
-
providerExecuted: false
|
|
67
|
-
})] });
|
|
68
|
-
const whatDecided = (request, outcome) => outcome === "approved" ? `approved: ${request.origin.tool} runs now, and its result follows.` : `declined: ${request.origin.tool} didn't run, and won't.`;
|
|
69
|
-
const decodeDecisionAnswer = Schema.decodeUnknownOption(Answer);
|
|
70
|
-
const decisionAnswerText = (answer, requests) => Option.match(decodeDecisionAnswer(answer.value), {
|
|
71
|
-
onNone: () => answerText(answer),
|
|
72
|
-
onSome: ({ decisions }) => {
|
|
73
|
-
const lines = Object.entries(decisions).map(([id, outcome]) => {
|
|
74
|
-
const request = requests.get(id);
|
|
75
|
-
return `- ${id}: ${request === void 0 ? outcome : whatDecided(request, outcome)}`;
|
|
76
|
-
});
|
|
77
|
-
return `<answer message="${answer.messageId}" kind="${answer.kind}">\nThe user decided with the block on your message ${answer.messageId}:\n${lines.join("\n")}\n</answer>`;
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
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>`;
|
|
81
|
-
const
|
|
56
|
+
const isEntry = (item) => Predicate.hasProperty(item, "seq");
|
|
82
57
|
/** @internal */
|
|
83
|
-
const fromEntries = (
|
|
58
|
+
const fromEntries = (items) => {
|
|
59
|
+
const entries = items.filter(isEntry);
|
|
84
60
|
const history = [];
|
|
85
61
|
const held = [];
|
|
86
62
|
const open = /* @__PURE__ */ new Set();
|
|
87
|
-
const requests = /* @__PURE__ */ new Map();
|
|
88
|
-
const parked = /* @__PURE__ */ new Map();
|
|
89
63
|
const called = /* @__PURE__ */ new Set();
|
|
90
64
|
const flushIfClosed = () => {
|
|
91
65
|
if (open.size === 0) history.push(...held.splice(0));
|
|
@@ -94,20 +68,24 @@ const fromEntries = (entries, readApp) => {
|
|
|
94
68
|
const next = entries.slice(from + 1).find(({ event }) => Predicate.isTagged(event, "ModelOutput"))?.event;
|
|
95
69
|
return next !== void 0 && isLibraryEvent(next) && Predicate.isTagged(next, "ModelOutput") ? next.seenThroughSeq : void 0;
|
|
96
70
|
};
|
|
97
|
-
const input = (index, item) => {
|
|
71
|
+
const input = (index, seq, item) => {
|
|
98
72
|
const seen = nextSeen(index);
|
|
99
|
-
if (open.size > 0 || seen !== void 0 && seen <
|
|
73
|
+
if (open.size > 0 || seen !== void 0 && seen < seq) held.push(item);
|
|
100
74
|
else history.push(item);
|
|
101
75
|
};
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
76
|
+
let index = -1;
|
|
77
|
+
for (const item of items) {
|
|
78
|
+
if (!isEntry(item)) {
|
|
79
|
+
input(index, (entries[index]?.seq ?? 0) + .5, item);
|
|
80
|
+
continue;
|
|
107
81
|
}
|
|
82
|
+
index += 1;
|
|
83
|
+
const { event, seq } = item;
|
|
84
|
+
const at = index;
|
|
85
|
+
if (!isLibraryEvent(event)) continue;
|
|
108
86
|
Match.value(event).pipe(Match.tagsExhaustive({
|
|
109
|
-
UserMessage: (message) => input(
|
|
110
|
-
BlockAnswer: (answer) => input(
|
|
87
|
+
UserMessage: (message) => input(at, seq, userText(textWithAttachments(message.text, message.attachments))),
|
|
88
|
+
BlockAnswer: (answer) => input(at, seq, userText(answerText(answer))),
|
|
111
89
|
ModelOutput: (output) => {
|
|
112
90
|
history.push(...Prompt.fromResponseParts(output.parts).content);
|
|
113
91
|
for (const part of output.parts) if (part.type === "tool-call") {
|
|
@@ -117,48 +95,38 @@ const fromEntries = (entries, readApp) => {
|
|
|
117
95
|
flushIfClosed();
|
|
118
96
|
},
|
|
119
97
|
ToolResult: (result) => {
|
|
120
|
-
|
|
121
|
-
if (request !== void 0) return input(index, userText(approvedResultText(request, result.tool, result)));
|
|
122
|
-
if (!called.has(result.callId)) return input(index, userText(resultText(result)));
|
|
98
|
+
if (!called.has(result.callId)) return input(at, seq, userText(resultText(result)));
|
|
123
99
|
history.push(toolMessage(result));
|
|
124
100
|
open.delete(result.callId);
|
|
125
101
|
flushIfClosed();
|
|
126
102
|
},
|
|
127
|
-
DecisionRequested: (request) => {
|
|
128
|
-
requests.set(request.id, request);
|
|
129
|
-
parked.set(request.origin.callId, request);
|
|
130
|
-
history.push(parkedResult(request, request.origin));
|
|
131
|
-
open.delete(request.origin.callId);
|
|
132
|
-
flushIfClosed();
|
|
133
|
-
},
|
|
134
|
-
DecisionResolved: () => void 0,
|
|
135
103
|
AssistantMessage: () => void 0,
|
|
136
104
|
ToolStarted: () => void 0,
|
|
137
105
|
TurnStarted: () => void 0,
|
|
138
106
|
TurnCompleted: () => void 0,
|
|
139
107
|
TurnFailed: () => void 0,
|
|
140
|
-
SpecialistFinished: (finished) => input(
|
|
108
|
+
SpecialistFinished: (finished) => input(at, seq, userText(finishedText(entries, at, finished))),
|
|
141
109
|
SpecialistStarted: () => void 0,
|
|
142
110
|
BlocksClosed: () => void 0,
|
|
143
111
|
ChatDeleted: () => void 0
|
|
144
112
|
}));
|
|
145
|
-
}
|
|
113
|
+
}
|
|
146
114
|
return [...history, ...held];
|
|
147
115
|
};
|
|
148
116
|
/** @internal */
|
|
149
|
-
const isInput = (event
|
|
117
|
+
const isInput = (event) => isInput$1(event) || isAppInput(event);
|
|
150
118
|
/** @internal */
|
|
151
119
|
const handledThrough = (entries) => {
|
|
152
120
|
const last = entries.findLast(({ event }) => isTurnEnd(event))?.event;
|
|
153
121
|
return last !== void 0 && isTurnEnd(last) ? last.throughSeq : 0;
|
|
154
122
|
};
|
|
155
123
|
/** @internal */
|
|
156
|
-
const unhandledInputs = (entries
|
|
124
|
+
const unhandledInputs = (entries) => {
|
|
157
125
|
const seen = handledThrough(entries);
|
|
158
|
-
return entries.flatMap(({ event, seq }) => seq > seen && isInput(event
|
|
126
|
+
return entries.flatMap(({ event, seq }) => seq > seen && isInput(event) && !(isInput$1(event) && !isWaking(event)) ? [event] : []);
|
|
159
127
|
};
|
|
160
128
|
/** @internal */
|
|
161
|
-
const needsTurn = (entries
|
|
129
|
+
const needsTurn = (entries) => unhandledInputs(entries).length > 0;
|
|
162
130
|
/** @internal */
|
|
163
131
|
const owesReply = (entries, throughSeq) => {
|
|
164
132
|
const latest = entries.findLast(({ event, seq }) => seq <= throughSeq && isInput$1(event) && isUserInput(event));
|
|
@@ -178,8 +146,8 @@ const owesDelivery = (entries, throughSeq) => {
|
|
|
178
146
|
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));
|
|
179
147
|
};
|
|
180
148
|
/** @internal */
|
|
181
|
-
const unreadAfter = (entries, throughSeq
|
|
149
|
+
const unreadAfter = (entries, throughSeq) => entries.some(({ event, seq }) => seq > throughSeq && isInput(event));
|
|
182
150
|
//#endregion
|
|
183
|
-
export { fromEntries, handledThrough, isInput, mayHaveRunOutput, needsTurn, notRunOutput, owesDelivery, owesReply,
|
|
151
|
+
export { fromEntries, handledThrough, isInput, mayHaveRunOutput, needsTurn, notRunOutput, owesDelivery, owesReply, toolMessage, unansweredCalls, unhandledInputs, unreadAfter, userText };
|
|
184
152
|
|
|
185
153
|
//# sourceMappingURL=history.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"history.js","names":["AssistantEvent.isLibraryEvent","Decision.Answer","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 * as Decision from \"../Decision.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\n/** @internal */\nexport const parkedResult = (\n request: AssistantEvent.DecisionRequested,\n origin: AssistantEvent.CallOrigin\n): Prompt.ToolMessage =>\n Prompt.toolMessage({\n content: [\n Prompt.toolResultPart({\n id: origin.callId,\n name: origin.tool,\n isFailure: false,\n result: {\n status: \"waiting_for_decision\",\n decisionId: request.id,\n next: \"Not run: it runs only if the user approves it. Send them a message with a decision block for this decisionId that says what they approve (the call's key facts). Their answer runs it, or declines it.\"\n },\n providerExecuted: false\n })\n ]\n })\n\nconst whatDecided = (request: AssistantEvent.DecisionRequested, outcome: AssistantEvent.DecisionOutcome) =>\n outcome === \"approved\"\n ? `approved: ${request.origin.tool} runs now, and its result follows.`\n : `declined: ${request.origin.tool} didn't run, and won't.`\n\nconst decodeDecisionAnswer = Schema.decodeUnknownOption(Decision.Answer)\n\nconst decisionAnswerText = (\n answer: AssistantEvent.BlockAnswer,\n requests: ReadonlyMap<string, AssistantEvent.DecisionRequested>\n) =>\n Option.match(decodeDecisionAnswer(answer.value), {\n onNone: () => answerText(answer),\n onSome: ({ decisions }) => {\n const lines = Object.entries(decisions).map(([id, outcome]) => {\n const request = requests.get(id)\n\n return `- ${id}: ${request === undefined ? outcome : whatDecided(request, outcome)}`\n })\n\n return `<answer message=\"${answer.messageId}\" kind=\"${answer.kind}\">\\nThe user decided with the block on your message ${answer.messageId}:\\n${lines.join(\"\\n\")}\\n</answer>`\n }\n })\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\nconst approvedResultText = (\n request: AssistantEvent.DecisionRequested,\n tool: string,\n result: AssistantEvent.ToolResult\n) =>\n `<decision id=\"${request.id}\" tool=\"${tool}\" outcome=\"approved\">\\nThe user approved it, and ${tool} ran: ${result.outcome}.\\n${toJsonText(result.result)}\\n</decision>`\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 requests = new Map<string, AssistantEvent.DecisionRequested>()\n // Parked calls by call id: their result came with the parking, so a later one reads as the decision's.\n const parked = new Map<string, AssistantEvent.DecisionRequested>()\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) =>\n input(\n index,\n userText(answer.kind === Decision.kind ? decisionAnswerText(answer, requests) : answerText(answer))\n ),\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 const request = parked.get(result.callId)\n\n if (request !== undefined) return input(index, userText(approvedResultText(request, result.tool, result)))\n\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. An approved call's result can come long after the call was parked.\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 DecisionRequested: (request) => {\n requests.set(request.id, request)\n parked.set(request.origin.callId, request)\n history.push(parkedResult(request, request.origin))\n open.delete(request.origin.callId)\n flushIfClosed()\n },\n DecisionResolved: () => undefined,\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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"}
|
|
1
|
+
{"version":3,"file":"history.js","names":["AssistantEvent.isLibraryEvent","AssistantEvent.textWithAttachments","AssistantEvent.isInput","AssistantEvent.isAppInput","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\nconst isEntry = (item: AssistantEvent.AnyEntry | Prompt.Message): item is AssistantEvent.AnyEntry =>\n Predicate.hasProperty(item, \"seq\")\n\n// In the order the model saw the entries, not the order they were stored. A message a history view put in stands\n// where it is, right after the entry before it, and waits like an input for a call's result or for the model's next\n// read; the app's own events reach the model only as such messages.\n/** @internal */\nexport const fromEntries = (\n items: ReadonlyArray<AssistantEvent.AnyEntry | Prompt.Message>\n): ReadonlyArray<Prompt.Message> => {\n const entries = items.filter(isEntry)\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 // `index` is the last entry at or before the item, and `seq` where the item stands.\n const input = (index: number, seq: number, item: Prompt.Message) => {\n const seen = nextSeen(index)\n\n if (open.size > 0 || (seen !== undefined && seen < seq)) held.push(item)\n else history.push(item)\n }\n\n let index = -1\n\n for (const item of items) {\n if (!isEntry(item)) {\n input(index, (entries[index]?.seq ?? 0) + 0.5, item)\n\n continue\n }\n\n index += 1\n\n const { event, seq } = item\n const at = index\n\n if (!AssistantEvent.isLibraryEvent(event)) continue\n\n Match.value(event).pipe(\n Match.tagsExhaustive({\n UserMessage: (message) =>\n input(at, seq, userText(AssistantEvent.textWithAttachments(message.text, message.attachments))),\n BlockAnswer: (answer) => input(at, seq, 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(at, seq, 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(at, seq, userText(finishedText(entries, at, 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 }): boolean =>\n AssistantEvent.isInput(event) || AssistantEvent.isAppInput(event)\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): ReadonlyArray<{ readonly _tag: string }> => {\n const seen = handledThrough(entries)\n\n return entries.flatMap(({ event, seq }) =>\n seq > seen && isInput(event) && !(AssistantEvent.isInput(event) && !AssistantEvent.isWaking(event)) ? [event] : []\n )\n}\n\n/** @internal */\nexport const needsTurn = (entries: ReadonlyArray<AssistantEvent.AnyEntry>): boolean =>\n unhandledInputs(entries).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 = (entries: ReadonlyArray<AssistantEvent.AnyEntry>, throughSeq: number): boolean =>\n entries.some(({ event, seq }) => seq > throughSeq && isInput(event))\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"input.js","names":[],"sources":["../../src/internal/input.ts"],"sourcesContent":["// Marks a stored app event that came in through the assistant's `submit`: an input, which wakes a turn.\n/** @internal */\nexport const inputKey = \"~effect-assistant/input\"\n"],"mappings":";;AAEA,MAAa,WAAW"}
|
package/dist/internal/loop.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AssistantEvent_d_exports,
|
|
1
|
+
import { AssistantEvent_d_exports, ModelOutput, ToolResult, ToolStarted } from "../AssistantEvent.js";
|
|
2
2
|
import { Instructions_d_exports, Resolved } from "../Instructions.js";
|
|
3
3
|
import { Tools, toolkit_d_exports } from "./toolkit.js";
|
|
4
4
|
import * as Context from "effect/Context";
|
|
@@ -13,7 +13,7 @@ declare namespace loop_d_exports {
|
|
|
13
13
|
export { ChatHistory, Event, Options, Step, StepOptions, StepResult, TurnTools, loop, next, run, step, stop, value };
|
|
14
14
|
}
|
|
15
15
|
/** @internal */
|
|
16
|
-
export type Event = ModelOutput | ToolStarted | ToolResult
|
|
16
|
+
export type Event = ModelOutput | ToolStarted | ToolResult;
|
|
17
17
|
/** @internal */
|
|
18
18
|
export declare const TurnTools: Context.Reference<Option.Option<Tools<never>>>;
|
|
19
19
|
/** @internal */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loop.d.ts","names":[],"sources":["../../src/internal/loop.ts"],"mappings":";;;;;;;;;;;;;;;
|
|
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"}
|
package/dist/internal/loop.js
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { CallOrigin, DecisionRequested, ModelOutput, ModelPart, ToolResult, ToolStarted } from "../AssistantEvent.js";
|
|
3
|
-
import { TurnDecisions } from "./decisions.js";
|
|
1
|
+
import { ModelOutput, ModelPart, ToolResult, ToolStarted } from "../AssistantEvent.js";
|
|
4
2
|
import { unansweredCalls } from "./history.js";
|
|
5
3
|
import { chatSpan, toolOutput, toolSpan } from "./telemetry.js";
|
|
6
|
-
import { empty,
|
|
4
|
+
import { empty, needingApproval, needsApprovalMessage } from "./toolkit.js";
|
|
7
5
|
import { isTurnEnding } from "./tools.js";
|
|
8
|
-
import * as Predicate from "effect/Predicate";
|
|
9
6
|
import * as Context from "effect/Context";
|
|
10
7
|
import * as Effect from "effect/Effect";
|
|
8
|
+
import * as Predicate from "effect/Predicate";
|
|
11
9
|
import * as Schema from "effect/Schema";
|
|
12
10
|
import * as Stream from "effect/Stream";
|
|
13
11
|
import * as Data from "effect/Data";
|
|
@@ -27,7 +25,7 @@ const readHistory = (read) => "messages" in read ? {
|
|
|
27
25
|
throughSeq: void 0
|
|
28
26
|
};
|
|
29
27
|
const requestToolkit = (definitions) => ({
|
|
30
|
-
tools: Object.fromEntries(definitions.map((tool) => [tool.name, tool
|
|
28
|
+
tools: Object.fromEntries(definitions.map((tool) => [tool.name, tool])),
|
|
31
29
|
handle: () => Effect.succeed(Stream.empty)
|
|
32
30
|
});
|
|
33
31
|
const generate = (prompt, definitions, toolChoice) => LanguageModel.generateText({
|
|
@@ -50,35 +48,14 @@ const runCall = Effect.fnUntraced(function* (tools, call) {
|
|
|
50
48
|
...result
|
|
51
49
|
});
|
|
52
50
|
});
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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";
|
|
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";
|
|
82
59
|
const Steps = Data.taggedEnum();
|
|
83
60
|
/** @internal */
|
|
84
61
|
const value = (value) => Steps.Value({ value });
|
|
@@ -103,25 +80,16 @@ const step = (options, then) => {
|
|
|
103
80
|
const toolkit = options.toolkit ?? empty;
|
|
104
81
|
const instructions = Predicate.isString(options.instructions) ? { text: options.instructions } : options.instructions;
|
|
105
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));
|
|
106
85
|
return Stream.unwrap(Effect.gen(function* () {
|
|
107
86
|
const { messages: history, throughSeq } = readHistory(yield* options.history);
|
|
108
87
|
const cutOff = unansweredCalls(history);
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
}
|
|
88
|
+
if (cutOff.length > 0) return runCalls(toolkit, cutOff, () => after({
|
|
89
|
+
output: void 0,
|
|
90
|
+
done: false,
|
|
91
|
+
historyLength: history.length + cutOff.length
|
|
92
|
+
}));
|
|
125
93
|
const prompt = Prompt.fromMessages([Prompt.systemMessage({ content: instructions.text }), ...history]);
|
|
126
94
|
const name = yield* Effect.serviceOption(Model.ModelName);
|
|
127
95
|
const started = yield* Clock.currentTimeMillis;
|
|
@@ -137,12 +105,9 @@ const step = (options, then) => {
|
|
|
137
105
|
});
|
|
138
106
|
const calls = toolCalls(output.parts);
|
|
139
107
|
const historyLength = history.length + Prompt.fromResponseParts(output.parts).content.length + calls.length;
|
|
140
|
-
return Stream.make(value(output)).pipe(Stream.concat(runCalls(toolkit, calls
|
|
141
|
-
call,
|
|
142
|
-
approved: false
|
|
143
|
-
})), history, (reread) => after({
|
|
108
|
+
return Stream.make(value(output)).pipe(Stream.concat(runCalls(toolkit, calls, (failed) => after({
|
|
144
109
|
output,
|
|
145
|
-
done: calls.length === 0 || !
|
|
110
|
+
done: calls.length === 0 || !failed && calls.some(isTurnEnding),
|
|
146
111
|
historyLength
|
|
147
112
|
}))));
|
|
148
113
|
}));
|
|
@@ -1 +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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"}
|
|
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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"}
|
package/dist/internal/skills.js
CHANGED
|
@@ -7,6 +7,11 @@ import * as Tool from "effect/ai/Tool";
|
|
|
7
7
|
import * as Toolkit from "effect/ai/Toolkit";
|
|
8
8
|
//#region src/internal/skills.ts
|
|
9
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 */
|
|
10
15
|
const toTools = (skills) => {
|
|
11
16
|
if (skills.length === 0) return empty;
|
|
12
17
|
const byName = new Map(skills.map((skill) => [skill.name, skill]));
|
|
@@ -23,6 +28,6 @@ const toTools = (skills) => {
|
|
|
23
28
|
}) }));
|
|
24
29
|
};
|
|
25
30
|
//#endregion
|
|
26
|
-
export { toTools };
|
|
31
|
+
export { available, toTools };
|
|
27
32
|
|
|
28
33
|
//# sourceMappingURL=skills.js.map
|
|
@@ -1 +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":";;;;;;;;;
|
|
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"}
|