effect-assistant 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Assistant.d.ts +10 -10
- package/dist/Assistant.d.ts.map +1 -1
- package/dist/Assistant.js +1 -1
- package/dist/Assistant.js.map +1 -1
- package/dist/AssistantActivity.d.ts +2 -2
- package/dist/AssistantActivity.d.ts.map +1 -1
- package/dist/AssistantActivity.js +5 -5
- package/dist/AssistantActivity.js.map +1 -1
- package/dist/AssistantEngine.d.ts +3 -3
- package/dist/AssistantEngine.js +2 -2
- package/dist/AssistantEngine.js.map +1 -1
- package/dist/AssistantEvent.d.ts +8 -8
- package/dist/AssistantEvent.js +7 -7
- package/dist/AssistantEvent.js.map +1 -1
- package/dist/AssistantLoop.d.ts +2 -2
- package/dist/AssistantLoop.js.map +1 -1
- package/dist/ChatLog.d.ts +9 -9
- package/dist/ChatLog.d.ts.map +1 -1
- package/dist/ChatLog.js +21 -21
- package/dist/ChatLog.js.map +1 -1
- package/dist/Compaction.d.ts +10 -10
- package/dist/Compaction.d.ts.map +1 -1
- package/dist/Compaction.js +16 -16
- package/dist/Compaction.js.map +1 -1
- package/dist/Memory.d.ts +1 -1
- package/dist/Memory.js +1 -1
- package/dist/Memory.js.map +1 -1
- package/dist/Skill.d.ts +2 -2
- package/dist/Skill.js +2 -2
- package/dist/Skill.js.map +1 -1
- package/dist/Specialist.d.ts +17 -16
- package/dist/Specialist.d.ts.map +1 -1
- package/dist/Specialist.js +5 -5
- package/dist/Specialist.js.map +1 -1
- package/dist/Timeline.d.ts +9 -9
- package/dist/Timeline.js +16 -16
- package/dist/Timeline.js.map +1 -1
- package/dist/ToolApproval.d.ts +1 -1
- package/dist/ToolApproval.js +1 -1
- package/dist/ToolApproval.js.map +1 -1
- package/dist/ToolDiscovery.d.ts +1 -1
- package/dist/ToolDiscovery.js +1 -1
- package/dist/ToolDiscovery.js.map +1 -1
- package/dist/internal/chat.js +4 -4
- package/dist/internal/chat.js.map +1 -1
- package/dist/internal/events.js +1 -1
- package/dist/internal/events.js.map +1 -1
- package/dist/internal/history.js +23 -23
- package/dist/internal/history.js.map +1 -1
- package/dist/internal/loop.d.ts +1 -1
- package/dist/internal/loop.js +4 -4
- package/dist/internal/loop.js.map +1 -1
- package/dist/internal/specialists.js +6 -6
- package/dist/internal/specialists.js.map +1 -1
- package/dist/internal/turns.d.ts.map +1 -1
- package/dist/internal/turns.js +18 -18
- package/dist/internal/turns.js.map +1 -1
- package/package.json +1 -1
- package/src/Assistant.ts +11 -11
- package/src/AssistantActivity.ts +9 -7
- package/src/AssistantEngine.ts +3 -3
- package/src/AssistantEvent.ts +8 -8
- package/src/AssistantLoop.ts +3 -3
- package/src/ChatLog.ts +31 -26
- package/src/Compaction.ts +21 -21
- package/src/Memory.ts +1 -1
- package/src/Skill.ts +2 -2
- package/src/Specialist.ts +19 -18
- package/src/Timeline.ts +22 -22
- package/src/ToolApproval.ts +1 -1
- package/src/ToolDiscovery.ts +1 -1
- package/src/internal/chat.ts +4 -4
- package/src/internal/events.ts +1 -1
- package/src/internal/history.ts +32 -28
- package/src/internal/loop.ts +5 -5
- package/src/internal/specialists.ts +7 -7
- package/src/internal/turns.ts +28 -26
|
@@ -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 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
|
|
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 sequence: entry.sequence,\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,UAAU,MAAM;CAChB,IAAI,MAAM;AACZ"}
|
package/dist/internal/history.js
CHANGED
|
@@ -66,7 +66,7 @@ const awaitingMessage = (request) => Prompt.toolMessage({ content: [Prompt.toolR
|
|
|
66
66
|
result: awaitingApprovalOutput,
|
|
67
67
|
providerExecuted: false
|
|
68
68
|
})] });
|
|
69
|
-
const isEntry = (item) => Predicate.hasProperty(item, "
|
|
69
|
+
const isEntry = (item) => Predicate.hasProperty(item, "sequence");
|
|
70
70
|
/** @internal */
|
|
71
71
|
const fromEntries = (items) => {
|
|
72
72
|
const entries = items.filter(isEntry);
|
|
@@ -81,26 +81,26 @@ const fromEntries = (items) => {
|
|
|
81
81
|
};
|
|
82
82
|
const nextSeen = (from) => {
|
|
83
83
|
const next = entries.slice(from + 1).find(({ event }) => Predicate.isTagged(event, "ModelOutput"))?.event;
|
|
84
|
-
return next !== void 0 && isLibraryEvent(next) && Predicate.isTagged(next, "ModelOutput") ? next.
|
|
84
|
+
return next !== void 0 && isLibraryEvent(next) && Predicate.isTagged(next, "ModelOutput") ? next.seenThroughSequence : void 0;
|
|
85
85
|
};
|
|
86
|
-
const input = (index,
|
|
86
|
+
const input = (index, sequence, item) => {
|
|
87
87
|
const seen = nextSeen(index);
|
|
88
|
-
if (open.size > 0 || seen !== void 0 && seen <
|
|
88
|
+
if (open.size > 0 || seen !== void 0 && seen < sequence) held.push(item);
|
|
89
89
|
else history.push(item);
|
|
90
90
|
};
|
|
91
91
|
let index = -1;
|
|
92
92
|
for (const item of items) {
|
|
93
93
|
if (!isEntry(item)) {
|
|
94
|
-
input(index, (entries[index]?.
|
|
94
|
+
input(index, (entries[index]?.sequence ?? 0) + .5, item);
|
|
95
95
|
continue;
|
|
96
96
|
}
|
|
97
97
|
index += 1;
|
|
98
|
-
const { event,
|
|
98
|
+
const { event, sequence } = item;
|
|
99
99
|
const at = index;
|
|
100
100
|
if (!isLibraryEvent(event)) continue;
|
|
101
101
|
Match.value(event).pipe(Match.tagsExhaustive({
|
|
102
|
-
UserMessage: (message) => input(at,
|
|
103
|
-
BlockAnswer: (answer) => input(at,
|
|
102
|
+
UserMessage: (message) => input(at, sequence, userText(textWithAttachments(message.text, message.attachments))),
|
|
103
|
+
BlockAnswer: (answer) => input(at, sequence, userText(answerText(answer))),
|
|
104
104
|
ModelOutput: (output) => {
|
|
105
105
|
history.push(...Prompt.fromResponseParts(output.parts).content);
|
|
106
106
|
for (const part of output.parts) if (part.type === "tool-call") {
|
|
@@ -110,8 +110,8 @@ const fromEntries = (items) => {
|
|
|
110
110
|
flushIfClosed();
|
|
111
111
|
},
|
|
112
112
|
ToolResult: (result) => {
|
|
113
|
-
if (requests.has(result.callId)) return input(at,
|
|
114
|
-
if (!called.has(result.callId)) return input(at,
|
|
113
|
+
if (requests.has(result.callId)) return input(at, sequence, userText(approvedText(result)));
|
|
114
|
+
if (!called.has(result.callId)) return input(at, sequence, userText(resultText(result)));
|
|
115
115
|
history.push(toolMessage(result));
|
|
116
116
|
open.delete(result.callId);
|
|
117
117
|
flushIfClosed();
|
|
@@ -121,18 +121,18 @@ const fromEntries = (items) => {
|
|
|
121
121
|
TurnStarted: () => void 0,
|
|
122
122
|
TurnCompleted: () => void 0,
|
|
123
123
|
TurnFailed: () => void 0,
|
|
124
|
-
SpecialistFinished: (finished) => input(at,
|
|
124
|
+
SpecialistFinished: (finished) => input(at, sequence, userText(finishedText(entries, at, finished))),
|
|
125
125
|
SpecialistStarted: () => void 0,
|
|
126
126
|
BlocksClosed: () => void 0,
|
|
127
127
|
ApprovalRequested: (request) => {
|
|
128
128
|
requests.set(request.callId, request);
|
|
129
|
-
if (!called.has(request.callId)) return input(at,
|
|
129
|
+
if (!called.has(request.callId)) return input(at, sequence, userText(awaitingText(request)));
|
|
130
130
|
history.push(awaitingMessage(request));
|
|
131
131
|
open.delete(request.callId);
|
|
132
132
|
flushIfClosed();
|
|
133
133
|
},
|
|
134
|
-
ApprovalDecision: (decision) => decision.decision === "rejected" ? input(at,
|
|
135
|
-
ApprovalsExpired: (expired) => expired.callIds.forEach((callId) => input(at,
|
|
134
|
+
ApprovalDecision: (decision) => decision.decision === "rejected" ? input(at, sequence, userText(rejectedText(decision, toolOf(decision.callId)))) : void 0,
|
|
135
|
+
ApprovalsExpired: (expired) => expired.callIds.forEach((callId) => input(at, sequence, userText(expiredText(callId, toolOf(callId))))),
|
|
136
136
|
ChatDeleted: () => void 0
|
|
137
137
|
}));
|
|
138
138
|
}
|
|
@@ -143,27 +143,27 @@ const isInput = (event) => isInput$1(event) || isAppInput(event);
|
|
|
143
143
|
/** @internal */
|
|
144
144
|
const handledThrough = (entries) => {
|
|
145
145
|
const last = entries.findLast(({ event }) => isTurnEnd(event))?.event;
|
|
146
|
-
return last !== void 0 && isTurnEnd(last) ? last.
|
|
146
|
+
return last !== void 0 && isTurnEnd(last) ? last.throughSequence : 0;
|
|
147
147
|
};
|
|
148
148
|
/** @internal */
|
|
149
149
|
const unhandledInputs = (entries) => {
|
|
150
150
|
const seen = handledThrough(entries);
|
|
151
|
-
return entries.flatMap(({ event,
|
|
151
|
+
return entries.flatMap(({ event, sequence }) => sequence > seen && isInput(event) && !(isInput$1(event) && !isWaking(event)) ? [event] : []);
|
|
152
152
|
};
|
|
153
153
|
/** @internal */
|
|
154
154
|
const needsTurn = (entries) => unhandledInputs(entries).length > 0;
|
|
155
155
|
/** @internal */
|
|
156
|
-
const owesReply = (entries,
|
|
157
|
-
const latest = entries.findLast(({ event,
|
|
158
|
-
return latest !== void 0 && !entries.some(({ event,
|
|
156
|
+
const owesReply = (entries, throughSequence) => {
|
|
157
|
+
const latest = entries.findLast(({ event, sequence }) => sequence <= throughSequence && isInput$1(event) && isUserInput(event));
|
|
158
|
+
return latest !== void 0 && !entries.some(({ event, sequence }) => sequence > latest.sequence && (Predicate.isTagged(event, "AssistantMessage") || Predicate.isTagged(event, "TurnFailed") || Predicate.isTagged(event, "ApprovalRequested")));
|
|
159
159
|
};
|
|
160
160
|
const sentToWaitOn = Schema.decodeUnknownOption(Schema.Struct({ wait: Schema.Literal(false) }));
|
|
161
161
|
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}`)] : []) : []));
|
|
162
162
|
/** @internal */
|
|
163
|
-
const owesDelivery = (entries,
|
|
164
|
-
const latest = entries.findLast(({ event,
|
|
163
|
+
const owesDelivery = (entries, throughSequence) => {
|
|
164
|
+
const latest = entries.findLast(({ event, sequence }) => sequence <= throughSequence && isInput$1(event) && isUserInput(event));
|
|
165
165
|
if (latest === void 0) return false;
|
|
166
|
-
const after = entries.flatMap(({
|
|
166
|
+
const after = entries.flatMap(({ sequence, event }) => sequence > latest.sequence && isLibraryEvent(event) ? [event] : []);
|
|
167
167
|
const messages = after.flatMap((event) => Predicate.isTagged(event, "AssistantMessage") ? [event] : []);
|
|
168
168
|
const progress = progressMessages(entries);
|
|
169
169
|
const started = after.flatMap((event) => Predicate.isTagged(event, "SpecialistStarted") ? [event.runId] : []);
|
|
@@ -171,7 +171,7 @@ const owesDelivery = (entries, throughSeq) => {
|
|
|
171
171
|
return messages.length > 0 && messages.every((message) => message.block === void 0 && progress.has(message.id)) && !after.some((event) => Predicate.isTagged(event, "TurnFailed") || Predicate.isTagged(event, "ApprovalRequested")) && started.every((runId) => finished.has(runId));
|
|
172
172
|
};
|
|
173
173
|
/** @internal */
|
|
174
|
-
const unreadAfter = (entries,
|
|
174
|
+
const unreadAfter = (entries, throughSequence) => entries.some(({ event, sequence }) => sequence > throughSequence && isInput(event));
|
|
175
175
|
//#endregion
|
|
176
176
|
export { awaitingApprovalOutput, fromEntries, handledThrough, isInput, mayHaveRunOutput, needsTurn, notRunOutput, owesDelivery, owesReply, toolMessage, unansweredCalls, unhandledInputs, unreadAfter, userText };
|
|
177
177
|
|
|
@@ -1 +1 @@
|
|
|
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\n/** @internal */\nexport const awaitingApprovalOutput =\n \"Waiting for the user's approval: they see this call and approve or reject it. Its outcome arrives later in this chat; don't call it again meanwhile.\"\n\nconst awaitingText = (request: AssistantEvent.ApprovalRequested) =>\n `<approval tool=\"${request.tool}\" call=\"${request.callId}\" status=\"pending\">\\nYour call to ${request.tool} from earlier in this chat waits for the user's approval.\\n${toJsonText(request.params)}\\n</approval>`\n\nconst approvedText = (result: AssistantEvent.ToolResult) =>\n `<result tool=\"${result.tool}\" call=\"${result.callId}\" outcome=\"${result.outcome}\">\\nThe user approved your call to ${result.tool} from earlier in this chat, and it ran.\\n${toJsonText(result.result)}\\n</result>`\n\nconst rejectedText = (decision: AssistantEvent.ApprovalDecision, tool: string) =>\n `<approval tool=\"${tool}\" call=\"${decision.callId}\" decision=\"rejected\">\\nThe user rejected your call to ${tool}; it didn't run.${decision.reason === undefined ? \"\" : `\\nTheir reason: ${decision.reason}`}\\n</approval>`\n\nconst expiredText = (callId: string, tool: string) =>\n `<approval tool=\"${tool}\" call=\"${callId}\" status=\"expired\">\\nThe user moved on without approving your call to ${tool}; it didn't run.\\n</approval>`\n\nconst awaitingMessage = (request: AssistantEvent.ApprovalRequested): Prompt.ToolMessage =>\n Prompt.toolMessage({\n content: [\n Prompt.toolResultPart({\n id: request.callId,\n name: request.tool,\n isFailure: false,\n result: awaitingApprovalOutput,\n providerExecuted: false\n })\n ]\n })\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 // Calls that waited for approval: the placeholder answered them, so what came of them later is an input.\n const requests = new Map<string, AssistantEvent.ApprovalRequested>()\n const toolOf = (callId: string) => requests.get(callId)?.tool ?? \"a tool\"\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 if (requests.has(result.callId)) return input(at, seq, userText(approvedText(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.\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 // The call's result, for the provider's pairing; what the user decides comes later.\n ApprovalRequested: (request) => {\n requests.set(request.callId, request)\n\n if (!called.has(request.callId)) return input(at, seq, userText(awaitingText(request)))\n\n history.push(awaitingMessage(request))\n open.delete(request.callId)\n flushIfClosed()\n },\n // An approved call's result says so.\n ApprovalDecision: (decision) =>\n decision.decision === \"rejected\"\n ? input(at, seq, userText(rejectedText(decision, toolOf(decision.callId))))\n : undefined,\n ApprovalsExpired: (expired) =>\n expired.callIds.forEach((callId) => input(at, seq, userText(expiredText(callId, toolOf(callId))))),\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, and so does a call they're asked to approve.\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 &&\n (Predicate.isTagged(event, \"AssistantMessage\") ||\n Predicate.isTagged(event, \"TurnFailed\") ||\n Predicate.isTagged(event, \"ApprovalRequested\"))\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 // A call waiting for the user's approval is what comes next, like a specialist's report.\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\") || Predicate.isTagged(event, \"ApprovalRequested\")) &&\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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"}
|
|
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\n/** @internal */\nexport const awaitingApprovalOutput =\n \"Waiting for the user's approval: they see this call and approve or reject it. Its outcome arrives later in this chat; don't call it again meanwhile.\"\n\nconst awaitingText = (request: AssistantEvent.ApprovalRequested) =>\n `<approval tool=\"${request.tool}\" call=\"${request.callId}\" status=\"pending\">\\nYour call to ${request.tool} from earlier in this chat waits for the user's approval.\\n${toJsonText(request.params)}\\n</approval>`\n\nconst approvedText = (result: AssistantEvent.ToolResult) =>\n `<result tool=\"${result.tool}\" call=\"${result.callId}\" outcome=\"${result.outcome}\">\\nThe user approved your call to ${result.tool} from earlier in this chat, and it ran.\\n${toJsonText(result.result)}\\n</result>`\n\nconst rejectedText = (decision: AssistantEvent.ApprovalDecision, tool: string) =>\n `<approval tool=\"${tool}\" call=\"${decision.callId}\" decision=\"rejected\">\\nThe user rejected your call to ${tool}; it didn't run.${decision.reason === undefined ? \"\" : `\\nTheir reason: ${decision.reason}`}\\n</approval>`\n\nconst expiredText = (callId: string, tool: string) =>\n `<approval tool=\"${tool}\" call=\"${callId}\" status=\"expired\">\\nThe user moved on without approving your call to ${tool}; it didn't run.\\n</approval>`\n\nconst awaitingMessage = (request: AssistantEvent.ApprovalRequested): Prompt.ToolMessage =>\n Prompt.toolMessage({\n content: [\n Prompt.toolResultPart({\n id: request.callId,\n name: request.tool,\n isFailure: false,\n result: awaitingApprovalOutput,\n providerExecuted: false\n })\n ]\n })\n\nconst isEntry = (item: AssistantEvent.AnyEntry | Prompt.Message): item is AssistantEvent.AnyEntry =>\n Predicate.hasProperty(item, \"sequence\")\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 // Calls that waited for approval: the placeholder answered them, so what came of them later is an input.\n const requests = new Map<string, AssistantEvent.ApprovalRequested>()\n const toolOf = (callId: string) => requests.get(callId)?.tool ?? \"a tool\"\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.seenThroughSequence\n : undefined\n }\n\n // `index` is the last entry at or before the item, and `sequence` where the item stands.\n const input = (index: number, sequence: number, item: Prompt.Message) => {\n const seen = nextSeen(index)\n\n if (open.size > 0 || (seen !== undefined && seen < sequence)) 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]?.sequence ?? 0) + 0.5, item)\n\n continue\n }\n\n index += 1\n\n const { event, sequence } = 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, sequence, userText(AssistantEvent.textWithAttachments(message.text, message.attachments))),\n BlockAnswer: (answer) => input(at, sequence, 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 if (requests.has(result.callId)) return input(at, sequence, userText(approvedText(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.\n if (!called.has(result.callId)) return input(at, sequence, 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, sequence, 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 // The call's result, for the provider's pairing; what the user decides comes later.\n ApprovalRequested: (request) => {\n requests.set(request.callId, request)\n\n if (!called.has(request.callId)) return input(at, sequence, userText(awaitingText(request)))\n\n history.push(awaitingMessage(request))\n open.delete(request.callId)\n flushIfClosed()\n },\n // An approved call's result says so.\n ApprovalDecision: (decision) =>\n decision.decision === \"rejected\"\n ? input(at, sequence, userText(rejectedText(decision, toolOf(decision.callId))))\n : undefined,\n ApprovalsExpired: (expired) =>\n expired.callIds.forEach((callId) => input(at, sequence, userText(expiredText(callId, toolOf(callId))))),\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.throughSequence : 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, sequence }) =>\n sequence > seen && isInput(event) && !(AssistantEvent.isInput(event) && !AssistantEvent.isWaking(event))\n ? [event]\n : []\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, and so does a call they're asked to approve.\n/** @internal */\nexport const owesReply = (entries: ReadonlyArray<AssistantEvent.AnyEntry>, throughSequence: number): boolean => {\n const latest = entries.findLast(\n ({ event, sequence }) =>\n sequence <= throughSequence && AssistantEvent.isInput(event) && AssistantEvent.isUserInput(event)\n )\n\n return (\n latest !== undefined &&\n !entries.some(\n ({ event, sequence }) =>\n sequence > latest.sequence &&\n (Predicate.isTagged(event, \"AssistantMessage\") ||\n Predicate.isTagged(event, \"TurnFailed\") ||\n Predicate.isTagged(event, \"ApprovalRequested\"))\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>, throughSequence: number): boolean => {\n const latest = entries.findLast(\n ({ event, sequence }) =>\n sequence <= throughSequence && AssistantEvent.isInput(event) && AssistantEvent.isUserInput(event)\n )\n\n if (latest === undefined) return false\n\n const after = entries.flatMap(({ sequence, event }) =>\n sequence > latest.sequence && 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 // A call waiting for the user's approval is what comes next, like a specialist's report.\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\") || Predicate.isTagged(event, \"ApprovalRequested\")) &&\n started.every((runId) => finished.has(runId))\n )\n}\n\n/** @internal */\nexport const unreadAfter = (entries: ReadonlyArray<AssistantEvent.AnyEntry>, throughSequence: number): boolean =>\n entries.some(({ event, sequence }) => sequence > throughSequence && isInput(event))\n"],"mappings":"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"}
|
package/dist/internal/loop.d.ts
CHANGED
|
@@ -23,7 +23,7 @@ export declare const TurnTools: Context.Reference<Option.Option<Tools<never>>>;
|
|
|
23
23
|
/** @internal */
|
|
24
24
|
export interface ChatHistory {
|
|
25
25
|
readonly messages: ReadonlyArray<Prompt.Message>;
|
|
26
|
-
readonly
|
|
26
|
+
readonly throughSequence: number;
|
|
27
27
|
}
|
|
28
28
|
/** @internal */
|
|
29
29
|
export interface StepOptions<R = never> {
|
package/dist/internal/loop.js
CHANGED
|
@@ -21,10 +21,10 @@ const TurnApprovals = Context.Reference("effect-assistant/TurnApprovals", { defa
|
|
|
21
21
|
const TurnTools = Context.Reference("effect-assistant/TurnTools", { defaultValue: Option.none });
|
|
22
22
|
const readHistory = (read) => "messages" in read ? {
|
|
23
23
|
messages: read.messages,
|
|
24
|
-
|
|
24
|
+
throughSequence: read.throughSequence
|
|
25
25
|
} : {
|
|
26
26
|
messages: read,
|
|
27
|
-
|
|
27
|
+
throughSequence: void 0
|
|
28
28
|
};
|
|
29
29
|
const requestToolkit = (definitions) => ({
|
|
30
30
|
tools: Object.fromEntries(definitions.map((tool) => [tool.name, tool.needsApproval === void 0 || tool.needsApproval === false ? tool : tool.setNeedsApproval(false)])),
|
|
@@ -108,7 +108,7 @@ const step = (options, then) => {
|
|
|
108
108
|
const instructions = Predicate.isString(options.instructions) ? { text: options.instructions } : options.instructions;
|
|
109
109
|
const after = (result) => Stream.suspend(() => then(result));
|
|
110
110
|
return Stream.unwrap(Effect.gen(function* () {
|
|
111
|
-
const { messages: history,
|
|
111
|
+
const { messages: history, throughSequence } = readHistory(yield* options.history);
|
|
112
112
|
const cutOff = unansweredCalls(history);
|
|
113
113
|
const approvals = yield* TurnApprovals;
|
|
114
114
|
const approved = Option.isNone(approvals) ? [] : (yield* approvals.value.approved).filter((call) => toolkit.definitions.some((tool) => tool.name === call.name) && !cutOff.some((cut) => cut.id === call.id));
|
|
@@ -133,7 +133,7 @@ const step = (options, then) => {
|
|
|
133
133
|
const metadata = response.content.find((part) => part.type === "response-metadata");
|
|
134
134
|
const output = ModelOutput.make({
|
|
135
135
|
parts: modelParts(response.content),
|
|
136
|
-
|
|
136
|
+
seenThroughSequence: throughSequence,
|
|
137
137
|
model: metadata?.modelId ?? Option.getOrUndefined(name),
|
|
138
138
|
responseId: metadata?.id,
|
|
139
139
|
usage: response.usage,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loop.js","names":["AssistantEvent.ModelPart","Telemetry.toolOutput","Telemetry.toolSpan","Toolkits.failed","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 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, isUserMessage } from \"./tools.ts\"\n\n/** @internal */\nexport type Event =\n | AssistantEvent.ModelOutput\n | AssistantEvent.ToolStarted\n | AssistantEvent.ToolResult\n | AssistantEvent.ApprovalRequested\n\n// Only a turn's loop can wait for the user's approval, and runs the calls they approved. A turn's specialists inherit\n// its context, so their runs set it back to none.\n/** @internal */\nexport const TurnApprovals = Context.Reference<\n Option.Option<{ readonly approved: Effect.Effect<ReadonlyArray<AssistantEvent.ToolCallPart>> }>\n>(\"effect-assistant/TurnApprovals\", { defaultValue: Option.none })\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, one of its calls waits for the user's\n * approval, or it ended its turn (`wait`, or a message with `wait: true`) and none of the step's\n * 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. It also decides which\n// calls wait for approval: the model call would ask for approvals of its own.\nconst requestToolkit = (definitions: ReadonlyArray<Tool.Any>): Toolkit.WithHandler<Record<string, Tool.Any>> => ({\n tools: Object.fromEntries(\n definitions.map((tool) => [\n tool.name,\n tool.needsApproval === undefined || tool.needsApproval === false ? tool : tool.setNeedsApproval(false)\n ])\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\n// A call that needs approval waits for the user in a turn. A specialist's run can't ask them, so its call fails.\nconst awaitApproval = Effect.fnUntraced(function* (call: AssistantEvent.ToolCallPart) {\n if (Option.isNone(yield* TurnApprovals)) {\n return AssistantEvent.ToolResult.make({\n callId: call.id,\n tool: call.name,\n ...Toolkits.failed(\n `${call.name} needs the user's approval, and only the assistant can ask for it. Don't run it: say in your answer what needs approving, with the details.`,\n \"NeedsApproval\"\n )\n })\n }\n\n return AssistantEvent.ApprovalRequested.make({ callId: call.id, tool: call.name, params: call.params })\n})\n\ninterface Pending {\n readonly call: AssistantEvent.ToolCallPart\n /** The user approved it: it runs without asking again. */\n readonly approved: boolean\n}\n\ninterface Ran {\n /** One of the calls failed, so the model has to read the results. */\n readonly failed: boolean\n /** One of the calls waits for the user's approval, so the turn ends. */\n readonly waiting: boolean\n}\n\n// The approval shows the call, not what it does: the user learns that from the assistant's message beside it.\nconst unexplained = (call: AssistantEvent.ToolCallPart) =>\n AssistantEvent.ToolResult.make({\n callId: call.id,\n tool: call.name,\n ...Toolkits.failed(\n `Not sent for approval: the user would see buttons without knowing what they approve. In one step, send them a message with send_user_message saying what ${call.name} will do (what, to whom, how many), and call ${call.name} again.`,\n \"ApprovalNeedsMessage\"\n )\n })\n\n// Every `ToolStarted` is stored before any call runs; a call that waits for approval doesn't start.\nconst runCalls = <R, S, E, R2>(\n tools: Toolkits.Tools<R>,\n pending: ReadonlyArray<Pending>,\n history: ReadonlyArray<Prompt.Message>,\n then: (ran: Ran) => 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 waits = yield* Effect.forEach(pending, ({ call, approved }) =>\n approved ? Effect.succeed(false) : tools.needsApproval(call, history)\n )\n\n const started = pending.flatMap(({ call }, i) =>\n waits[i] === true ? [] : [value(AssistantEvent.ToolStarted.make({ callId: call.id, tool: call.name }))]\n )\n\n return Stream.fromIterable(started).pipe(\n Stream.concat(\n Stream.unwrap(\n Effect.gen(function* () {\n const explained = pending.some(({ call }) => isUserMessage(call))\n\n const done = yield* Effect.forEach(\n pending,\n ({ call }, i): Effect.Effect<Event, never, R> =>\n waits[i] !== true\n ? runCall(tools, call)\n : explained\n ? awaitApproval(call)\n : Effect.succeed(unexplained(call)),\n { concurrency: \"unbounded\" }\n )\n\n return Stream.fromIterable(done.map(value)).pipe(\n Stream.concat(\n then({\n failed: done.some(isFailure),\n waiting: done.some((event) => Predicate.isTagged(event, \"ApprovalRequested\"))\n })\n )\n )\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 const approvals = yield* TurnApprovals\n\n // Calls the user approved: they run as the model made them, with the same ids, before the model goes on. Not in\n // a loop without their tools.\n const approved = Option.isNone(approvals)\n ? []\n : (yield* approvals.value.approved).filter(\n (call) =>\n toolkit.definitions.some((tool) => tool.name === call.name) && !cutOff.some((cut) => cut.id === call.id)\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, ({ waiting }) =>\n after({ output: undefined, done: waiting, 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. A call\n // that waits for approval does: the turn waits for the user.\n return Stream.make(value(output)).pipe(\n Stream.concat(\n runCalls(\n toolkit,\n calls.map((call) => ({ call, approved: false })),\n history,\n ({ failed, waiting }) =>\n after({\n output,\n done: calls.length === 0 || waiting || (!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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"}
|
|
1
|
+
{"version":3,"file":"loop.js","names":["AssistantEvent.ModelPart","Telemetry.toolOutput","Telemetry.toolSpan","Toolkits.failed","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 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, isUserMessage } from \"./tools.ts\"\n\n/** @internal */\nexport type Event =\n | AssistantEvent.ModelOutput\n | AssistantEvent.ToolStarted\n | AssistantEvent.ToolResult\n | AssistantEvent.ApprovalRequested\n\n// Only a turn's loop can wait for the user's approval, and runs the calls they approved. A turn's specialists inherit\n// its context, so their runs set it back to none.\n/** @internal */\nexport const TurnApprovals = Context.Reference<\n Option.Option<{ readonly approved: Effect.Effect<ReadonlyArray<AssistantEvent.ToolCallPart>> }>\n>(\"effect-assistant/TurnApprovals\", { defaultValue: Option.none })\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 throughSequence: number\n}\n\nconst readHistory = (read: ReadonlyArray<Prompt.Message> | ChatHistory) =>\n \"messages\" in read\n ? { messages: read.messages, throughSequence: read.throughSequence }\n : { messages: read, throughSequence: 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, one of its calls waits for the user's\n * approval, or it ended its turn (`wait`, or a message with `wait: true`) and none of the step's\n * 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. It also decides which\n// calls wait for approval: the model call would ask for approvals of its own.\nconst requestToolkit = (definitions: ReadonlyArray<Tool.Any>): Toolkit.WithHandler<Record<string, Tool.Any>> => ({\n tools: Object.fromEntries(\n definitions.map((tool) => [\n tool.name,\n tool.needsApproval === undefined || tool.needsApproval === false ? tool : tool.setNeedsApproval(false)\n ])\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\n// A call that needs approval waits for the user in a turn. A specialist's run can't ask them, so its call fails.\nconst awaitApproval = Effect.fnUntraced(function* (call: AssistantEvent.ToolCallPart) {\n if (Option.isNone(yield* TurnApprovals)) {\n return AssistantEvent.ToolResult.make({\n callId: call.id,\n tool: call.name,\n ...Toolkits.failed(\n `${call.name} needs the user's approval, and only the assistant can ask for it. Don't run it: say in your answer what needs approving, with the details.`,\n \"NeedsApproval\"\n )\n })\n }\n\n return AssistantEvent.ApprovalRequested.make({ callId: call.id, tool: call.name, params: call.params })\n})\n\ninterface Pending {\n readonly call: AssistantEvent.ToolCallPart\n /** The user approved it: it runs without asking again. */\n readonly approved: boolean\n}\n\ninterface Ran {\n /** One of the calls failed, so the model has to read the results. */\n readonly failed: boolean\n /** One of the calls waits for the user's approval, so the turn ends. */\n readonly waiting: boolean\n}\n\n// The approval shows the call, not what it does: the user learns that from the assistant's message beside it.\nconst unexplained = (call: AssistantEvent.ToolCallPart) =>\n AssistantEvent.ToolResult.make({\n callId: call.id,\n tool: call.name,\n ...Toolkits.failed(\n `Not sent for approval: the user would see buttons without knowing what they approve. In one step, send them a message with send_user_message saying what ${call.name} will do (what, to whom, how many), and call ${call.name} again.`,\n \"ApprovalNeedsMessage\"\n )\n })\n\n// Every `ToolStarted` is stored before any call runs; a call that waits for approval doesn't start.\nconst runCalls = <R, S, E, R2>(\n tools: Toolkits.Tools<R>,\n pending: ReadonlyArray<Pending>,\n history: ReadonlyArray<Prompt.Message>,\n then: (ran: Ran) => 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 waits = yield* Effect.forEach(pending, ({ call, approved }) =>\n approved ? Effect.succeed(false) : tools.needsApproval(call, history)\n )\n\n const started = pending.flatMap(({ call }, i) =>\n waits[i] === true ? [] : [value(AssistantEvent.ToolStarted.make({ callId: call.id, tool: call.name }))]\n )\n\n return Stream.fromIterable(started).pipe(\n Stream.concat(\n Stream.unwrap(\n Effect.gen(function* () {\n const explained = pending.some(({ call }) => isUserMessage(call))\n\n const done = yield* Effect.forEach(\n pending,\n ({ call }, i): Effect.Effect<Event, never, R> =>\n waits[i] !== true\n ? runCall(tools, call)\n : explained\n ? awaitApproval(call)\n : Effect.succeed(unexplained(call)),\n { concurrency: \"unbounded\" }\n )\n\n return Stream.fromIterable(done.map(value)).pipe(\n Stream.concat(\n then({\n failed: done.some(isFailure),\n waiting: done.some((event) => Predicate.isTagged(event, \"ApprovalRequested\"))\n })\n )\n )\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, throughSequence } = 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 const approvals = yield* TurnApprovals\n\n // Calls the user approved: they run as the model made them, with the same ids, before the model goes on. Not in\n // a loop without their tools.\n const approved = Option.isNone(approvals)\n ? []\n : (yield* approvals.value.approved).filter(\n (call) =>\n toolkit.definitions.some((tool) => tool.name === call.name) && !cutOff.some((cut) => cut.id === call.id)\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, ({ waiting }) =>\n after({ output: undefined, done: waiting, 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 seenThroughSequence: throughSequence,\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. A call\n // that waits for approval does: the turn waits for the user.\n return Stream.make(value(output)).pipe(\n Stream.concat(\n runCalls(\n toolkit,\n calls.map((call) => ({ call, approved: false })),\n history,\n ({ failed, waiting }) =>\n after({\n output,\n done: calls.length === 0 || waiting || (!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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"}
|
|
@@ -44,16 +44,16 @@ const runBrief = Effect.fnUntraced(function* (handler, options) {
|
|
|
44
44
|
yield* add(yield* Queue.clear(inbox));
|
|
45
45
|
return yield* Ref.get(history);
|
|
46
46
|
});
|
|
47
|
-
const
|
|
47
|
+
const turn = {
|
|
48
48
|
runId: options.runId,
|
|
49
49
|
chatId: options.chatId,
|
|
50
50
|
brief: options.brief,
|
|
51
51
|
history: current,
|
|
52
|
-
|
|
53
|
-
instructions:
|
|
54
|
-
toolkit: fromToolkitOrEmpty(
|
|
52
|
+
run: (run$1) => run({
|
|
53
|
+
instructions: run$1.instructions,
|
|
54
|
+
toolkit: fromToolkitOrEmpty(run$1.toolkit),
|
|
55
55
|
history: current,
|
|
56
|
-
maxSteps:
|
|
56
|
+
maxSteps: run$1.maxSteps ?? 8
|
|
57
57
|
}).pipe(Stream.provideService(TurnApprovals, Option.none()))
|
|
58
58
|
};
|
|
59
59
|
const record = Effect.fnUntraced(function* (event) {
|
|
@@ -63,7 +63,7 @@ const runBrief = Effect.fnUntraced(function* (handler, options) {
|
|
|
63
63
|
const text = finalText(event);
|
|
64
64
|
if (text.length > 0) yield* Ref.set(answer, text);
|
|
65
65
|
});
|
|
66
|
-
yield* Stream.runForEach(handler(
|
|
66
|
+
yield* Stream.runForEach(handler(turn), record);
|
|
67
67
|
const text = yield* Ref.get(answer);
|
|
68
68
|
yield* agentOutput(text);
|
|
69
69
|
return text;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"specialists.js","names":["run","Loop.run","Toolkits.fromToolkitOrEmpty","Loop.TurnApprovals","Telemetry.agentOutput","Telemetry.agentSpan","InputId.fromKey","RunId.RunId","ToolFailure.ToolFailure","Specialist.Runner","ToolFailure.fail","Toolkits.empty","Toolkits.fromWithHandler","Loop.TurnTools","Specialist.SpecialistError","RunId.make"],"sources":["../../src/internal/specialists.ts"],"sourcesContent":["import type * as LanguageModel from \"effect/ai/LanguageModel\"\nimport * as Prompt from \"effect/ai/Prompt\"\nimport * as Tool from \"effect/ai/Tool\"\nimport * as Toolkit from \"effect/ai/Toolkit\"\nimport * as Cause from \"effect/Cause\"\nimport * as Context from \"effect/Context\"\nimport * as Effect from \"effect/Effect\"\nimport * as Exit from \"effect/Exit\"\nimport * as Fiber from \"effect/Fiber\"\nimport * as FiberMap from \"effect/FiberMap\"\nimport * as FiberSet from \"effect/FiberSet\"\nimport * as Option from \"effect/Option\"\nimport * as Predicate from \"effect/Predicate\"\nimport * as Queue from \"effect/Queue\"\nimport * as Ref from \"effect/Ref\"\nimport * as Schema from \"effect/Schema\"\nimport * as Semaphore from \"effect/Semaphore\"\nimport * as Stream from \"effect/Stream\"\nimport * as AssistantEvent from \"../AssistantEvent.ts\"\nimport type * as AssistantLoop from \"../AssistantLoop.ts\"\nimport * as Loop from \"./loop.ts\"\nimport { CurrentChat } from \"../CurrentChat.ts\"\nimport * as InputId from \"../InputId.ts\"\nimport * as RunId from \"../RunId.ts\"\nimport * as Specialist from \"../Specialist.ts\"\nimport * as ToolFailure from \"../ToolFailure.ts\"\nimport { toolMessage, userText } from \"./history.ts\"\nimport * as Telemetry from \"./telemetry.ts\"\nimport * as Toolkits from \"./toolkit.ts\"\nimport type { Writer } from \"./writer.ts\"\n\n/** @internal */\nexport type RunHandler = (run: Specialist.Run) => Stream.Stream<AssistantLoop.Event, unknown, Specialist.RunServices>\n\n/** @internal */\nexport const briefText = (brief: Specialist.Brief): string =>\n [\n `Goal: ${brief.goal}`,\n brief.context && `Context: ${brief.context}`,\n brief.successCriteria && `Done when: ${brief.successCriteria}`,\n brief.report && `Report back: ${brief.report}`,\n brief.limits && `Limits: ${brief.limits}`\n ]\n .filter(Boolean)\n .join(\"\\n\")\n\nconst finalText = (output: AssistantEvent.ModelOutput): string =>\n output.parts\n .flatMap((part) => (part.type === \"text\" ? [part.text] : []))\n .join(\"\")\n .trim()\n\n// The answer is the last text the model wrote.\n/** @internal */\nexport const runBrief = Effect.fnUntraced(\n function* (\n handler: RunHandler,\n options: {\n readonly specialist: Specialist.Any\n readonly runId: RunId.RunId\n readonly brief: Specialist.Brief\n readonly history: Ref.Ref<ReadonlyArray<Prompt.Message>>\n readonly inbox: Queue.Queue<Prompt.Message>\n readonly chatId: Writer[\"chatId\"]\n }\n ) {\n const { history, inbox } = options\n const answer = yield* Ref.make(\"\")\n const add = (messages: ReadonlyArray<Prompt.Message>) => Ref.update(history, (all) => [...all, ...messages])\n\n // Read before every model call, when no tool calls are open: messages sent meanwhile join it now.\n const current = Effect.gen(function* () {\n yield* add(yield* Queue.clear(inbox))\n\n return yield* Ref.get(history)\n })\n\n const run: Specialist.Run = {\n runId: options.runId,\n chatId: options.chatId,\n brief: options.brief,\n history: current,\n // A run can't wait for the user's approval: a call that needs it fails, and the run reads why.\n loop: (loop) =>\n Loop.run({\n instructions: loop.instructions,\n toolkit: Toolkits.fromToolkitOrEmpty(loop.toolkit),\n history: current,\n maxSteps: loop.maxSteps ?? 8\n }).pipe(Stream.provideService(Loop.TurnApprovals, Option.none()))\n }\n\n // A run lives in memory, so there's nothing to run again after a crash: `ToolStarted` isn't kept.\n const record = Effect.fnUntraced(function* (event: AssistantLoop.Event) {\n if (Predicate.isTagged(event, \"ToolStarted\") || Predicate.isTagged(event, \"ApprovalRequested\")) return\n\n if (Predicate.isTagged(event, \"ToolResult\")) return yield* add([toolMessage(event)])\n\n yield* add(Prompt.fromResponseParts(event.parts).content)\n const text = finalText(event)\n\n if (text.length > 0) yield* Ref.set(answer, text)\n })\n\n yield* Stream.runForEach(handler(run), record)\n\n const text = yield* Ref.get(answer)\n\n yield* Telemetry.agentOutput(text)\n\n return text\n },\n (effect, _handler, options) => effect.pipe(Telemetry.agentSpan(options.specialist.name, { chatId: options.chatId }))\n)\n\ntype Outcome =\n | { readonly status: \"answered\"; readonly report: string }\n | { readonly status: \"failed\"; readonly error: string }\n | { readonly status: \"stopped\" | \"cut_off\" }\n\n// Only `stop` interrupts a brief's loop alone; a failure or a defect failed it.\nconst unanswered = (cause: Cause.Cause<unknown>): Outcome =>\n Cause.hasInterruptsOnly(cause) ? { status: \"stopped\" } : { status: \"failed\", error: String(Cause.squash(cause)) }\n\n// The id is fixed per run and brief, so a replay is stored once.\n/** @internal */\nexport const finished = (\n specialist: string,\n run: { readonly runId: RunId.RunId; readonly brief: number; readonly goal: string },\n outcome: Outcome\n): AssistantEvent.SpecialistFinished =>\n AssistantEvent.SpecialistFinished.make({\n id: InputId.fromKey(`${run.runId}:${run.brief}`),\n runId: run.runId,\n specialist,\n brief: run.brief,\n goal: run.goal,\n ...outcome\n })\n\nconst RunIdInput = RunId.RunId.annotate({ description: \"The runId spawn_specialist returned.\" })\n\nconst RunStatus = <const Status extends string>(status: ReadonlyArray<Status>) =>\n Schema.Struct({ runId: RunId.RunId, status: Schema.Literals(status) })\n\nconst MessageSpecialist = Tool.make(\"message_specialist\", {\n description:\n \"Send more to one of your specialists: new instructions for a running one (it reads them at its next step), or a follow-up for a finished one (it continues with what it already found).\",\n parameters: Schema.Struct({ runId: RunIdInput, message: Schema.String }),\n success: RunStatus([\"steered\", \"continued\"]),\n failure: ToolFailure.ToolFailure,\n dependencies: [Specialist.Runner]\n})\n\nconst StopSpecialist = Tool.make(\"stop_specialist\", {\n description:\n \"Stop one of your running specialists, for example when the user changed their mind. It posts no answer.\",\n parameters: Schema.Struct({ runId: RunIdInput }),\n success: RunStatus([\"stopped\"]),\n failure: ToolFailure.ToolFailure,\n dependencies: [Specialist.Runner]\n})\n\nconst refused = <A, R>(effect: Effect.Effect<A, Specialist.SpecialistError, R>) =>\n Effect.catch(effect, (error) => ToolFailure.fail(error.message, { reason: error.reason }))\n\n/** @internal */\nexport const toolkit = Effect.fnUntraced(function* (\n specialists: ReadonlyArray<Specialist.Any>\n): Effect.fn.Return<Toolkits.Tools<Specialist.Runner>> {\n if (specialists.length === 0) return Toolkits.empty\n\n const catalog = specialists.map((specialist) => `- ${specialist.name}: ${specialist.description}`).join(\"\\n\")\n\n const SpawnSpecialist = Tool.make(\"spawn_specialist\", {\n description: `Start a specialist on a job. It works in the background and its report arrives as a <specialist> input, often within seconds. Write a self-contained brief: it can't see this chat. For a quick question, just call wait; for slow work, tell the user in one line first.\\nPass one of these as specialist:\\n${catalog}`,\n parameters: Schema.Struct({\n specialist: Schema.Literals(specialists.map((specialist) => specialist.name)).annotate({\n description: \"The specialist to start, by name.\"\n }),\n ...Specialist.Brief.fields\n }),\n success: RunStatus([\"started\"]),\n failure: ToolFailure.ToolFailure,\n dependencies: [Specialist.Runner]\n })\n\n const SpecialistTools = Toolkit.make(SpawnSpecialist, MessageSpecialist, StopSpecialist)\n\n const handlers = yield* SpecialistTools.toHandlers({\n spawn_specialist: Effect.fnUntraced(function* ({ specialist, ...brief }) {\n const runId = yield* refused(Specialist.Runner.use((runner) => runner.spawn({ specialist, brief })))\n\n return { runId, status: \"started\" }\n }),\n message_specialist: Effect.fnUntraced(function* ({ runId, message }) {\n const status = yield* refused(Specialist.Runner.use((runner) => runner.message(runId, message)))\n\n return { runId, status }\n }),\n stop_specialist: Effect.fnUntraced(function* ({ runId }) {\n yield* refused(Specialist.Runner.use((runner) => runner.stop(runId)))\n\n return { runId, status: \"stopped\" }\n })\n })\n\n return Toolkits.fromWithHandler(yield* Effect.provideContext(SpecialistTools, handlers))\n})\n\n// How long a finished run stays available for follow-ups.\nconst warm = \"15 minutes\"\n\ninterface Run {\n readonly runId: RunId.RunId\n readonly specialist: Specialist.Any\n readonly handler: RunHandler\n readonly goal: string\n readonly brief: Specialist.Brief\n readonly history: Ref.Ref<ReadonlyArray<Prompt.Message>>\n // Messages sent while it works; the loop reads them at its next step.\n readonly inbox: Queue.Queue<Prompt.Message>\n // Counts follow-ups too; numbers the reports.\n readonly briefs: Ref.Ref<number>\n // Read and changed under `lock`.\n readonly busy: Ref.Ref<boolean>\n // Orders a message against the run finishing, so none is dropped or starts a second loop.\n readonly lock: Semaphore.Semaphore\n}\n\n/** @internal */\nexport const makeRunner = Effect.fnUntraced(function* (options: {\n readonly chat: Writer\n readonly handle: CurrentChat[\"Service\"]\n readonly specialists: ReadonlyArray<{\n readonly specialist: Specialist.Any\n readonly handler: RunHandler\n }>\n readonly model: Context.Context<LanguageModel.LanguageModel>\n // An interruption keeps the hold.\n readonly holding: (effect: Effect.Effect<void>) => Effect.Effect<void>\n readonly report: (finished: AssistantEvent.SpecialistFinished) => Effect.Effect<void>\n}) {\n const { chat } = options\n const runs = new Map<string, Run>()\n const byName = new Map(options.specialists.map((entry) => [entry.specialist.name, entry]))\n // The brief loops working now, and the finished runs' countdowns to being forgotten.\n const working = yield* FiberMap.make<string, Outcome>()\n const forgetting = yield* FiberMap.make<string>()\n const background = yield* FiberSet.make()\n\n const report = Effect.fnUntraced(function* (run: Run, outcome: Outcome) {\n const brief = yield* Ref.get(run.briefs)\n\n yield* options.report(finished(run.specialist.name, { runId: run.runId, brief, goal: run.goal }, outcome))\n })\n\n const once = (run: Run) =>\n runBrief(run.handler, {\n specialist: run.specialist,\n runId: run.runId,\n brief: run.brief,\n history: run.history,\n inbox: run.inbox,\n chatId: chat.chatId\n }).pipe(\n Effect.provideContext(Context.merge(options.model, services)),\n // Spawned from a turn, a run would inherit the turn's own tools: a run can't message the user.\n Effect.provideService(Loop.TurnTools, Option.none()),\n Effect.matchCause({\n onSuccess: (answer): Outcome => ({ status: \"answered\", report: answer }),\n onFailure: unanswered\n })\n )\n\n // A message that arrived after the last read: keep working before reporting.\n const untilCaughtUp = Effect.fnUntraced(function* (run: Run) {\n let outcome = yield* once(run)\n\n while ((yield* Queue.size(run.inbox)) > 0) outcome = yield* once(run)\n\n return outcome\n })\n\n const work = Effect.fnUntraced(\n function* (run: Run) {\n yield* chat.append([\n AssistantEvent.SpecialistStarted.make({\n runId: run.runId,\n specialist: run.specialist.name,\n goal: run.goal\n })\n ])\n\n let finished = false\n\n while (!finished) {\n const exit = yield* Fiber.await(yield* FiberMap.run(working, run.runId, untilCaughtUp(run)))\n const outcome = Exit.isSuccess(exit) ? exit.value : unanswered(exit.cause)\n\n finished = yield* run.lock.withPermit(\n Effect.gen(function* () {\n // A message steered in after the loop's last read: work on it before reporting.\n if (outcome.status !== \"stopped\" && (yield* Queue.size(run.inbox)) > 0) return false\n\n yield* report(run, outcome)\n yield* Ref.set(run.busy, false)\n yield* FiberMap.run(\n forgetting,\n run.runId,\n Effect.gen(function* () {\n yield* Effect.sleep(warm)\n runs.delete(run.runId)\n })\n )\n\n return true\n })\n )\n }\n },\n // A shutdown interrupts the whole run: no turn can start now, so the report waits for the chat's next start.\n (effect, run) => Effect.onInterrupt(effect, () => report(run, { status: \"cut_off\" }))\n )\n\n const launch = Effect.fnUntraced(function* (run: Run) {\n yield* FiberMap.remove(forgetting, run.runId)\n yield* FiberSet.run(background, options.holding(work(run)))\n })\n\n // Under the run's lock.\n const send = Effect.fnUntraced(function* (run: Run, text: string): Effect.fn.Return<\"steered\" | \"continued\"> {\n yield* Ref.update(run.briefs, (n) => n + 1)\n\n if (yield* Ref.get(run.busy)) {\n yield* Queue.offer(run.inbox, userText(text))\n\n return \"steered\"\n }\n\n yield* Ref.update(run.history, (all) => [...all, userText(text)])\n yield* Ref.set(run.busy, true)\n yield* launch(run)\n\n return \"continued\"\n })\n\n const runner = Specialist.Runner.of({\n spawn: Effect.fnUntraced(function* (request: Specialist.SpawnRequest) {\n const found = byName.get(request.specialist)\n\n if (found === undefined) {\n return yield* new Specialist.SpecialistError({\n reason: \"UnknownSpecialist\",\n message: `No specialist named ${request.specialist}.`\n })\n }\n\n const run: Run = {\n runId: yield* RunId.make(),\n specialist: found.specialist,\n handler: found.handler,\n goal: request.brief.goal,\n brief: request.brief,\n history: yield* Ref.make<ReadonlyArray<Prompt.Message>>([userText(briefText(request.brief))]),\n inbox: yield* Queue.unbounded<Prompt.Message>(),\n briefs: yield* Ref.make(1),\n busy: yield* Ref.make(true),\n lock: yield* Semaphore.make(1)\n }\n\n runs.set(run.runId, run)\n yield* launch(run)\n\n return run.runId\n }),\n message: Effect.fnUntraced(function* (\n runId: RunId.RunId,\n text: string\n ): Effect.fn.Return<\"steered\" | \"continued\", Specialist.SpecialistError> {\n const run = runs.get(runId)\n\n if (run === undefined) {\n return yield* new Specialist.SpecialistError({\n reason: \"RunNotFound\",\n message: `Run ${runId} is no longer available. Spawn a new one with the full context.`\n })\n }\n\n return yield* run.lock.withPermit(send(run, text))\n }),\n stop: Effect.fnUntraced(function* (runId: RunId.RunId) {\n if (!(yield* FiberMap.has(working, runId))) {\n return yield* new Specialist.SpecialistError({ reason: \"NotRunning\", message: `Run ${runId} isn't running.` })\n }\n\n yield* FiberMap.remove(working, runId)\n })\n })\n\n // What a run's handler gets: its chat, this runner, and (merged in `once`) the model.\n const services = Context.make(CurrentChat, options.handle).pipe(Context.add(Specialist.Runner, runner))\n\n return runner\n})\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"specialists.js","names":["Loop.run","run","Toolkits.fromToolkitOrEmpty","Loop.TurnApprovals","Telemetry.agentOutput","Telemetry.agentSpan","InputId.fromKey","RunId.RunId","ToolFailure.ToolFailure","Specialist.Runner","ToolFailure.fail","Toolkits.empty","Toolkits.fromWithHandler","Loop.TurnTools","Specialist.SpecialistError","RunId.make"],"sources":["../../src/internal/specialists.ts"],"sourcesContent":["import type * as LanguageModel from \"effect/ai/LanguageModel\"\nimport * as Prompt from \"effect/ai/Prompt\"\nimport * as Tool from \"effect/ai/Tool\"\nimport * as Toolkit from \"effect/ai/Toolkit\"\nimport * as Cause from \"effect/Cause\"\nimport * as Context from \"effect/Context\"\nimport * as Effect from \"effect/Effect\"\nimport * as Exit from \"effect/Exit\"\nimport * as Fiber from \"effect/Fiber\"\nimport * as FiberMap from \"effect/FiberMap\"\nimport * as FiberSet from \"effect/FiberSet\"\nimport * as Option from \"effect/Option\"\nimport * as Predicate from \"effect/Predicate\"\nimport * as Queue from \"effect/Queue\"\nimport * as Ref from \"effect/Ref\"\nimport * as Schema from \"effect/Schema\"\nimport * as Semaphore from \"effect/Semaphore\"\nimport * as Stream from \"effect/Stream\"\nimport * as AssistantEvent from \"../AssistantEvent.ts\"\nimport type * as AssistantLoop from \"../AssistantLoop.ts\"\nimport * as Loop from \"./loop.ts\"\nimport { CurrentChat } from \"../CurrentChat.ts\"\nimport * as InputId from \"../InputId.ts\"\nimport * as RunId from \"../RunId.ts\"\nimport * as Specialist from \"../Specialist.ts\"\nimport * as ToolFailure from \"../ToolFailure.ts\"\nimport { toolMessage, userText } from \"./history.ts\"\nimport * as Telemetry from \"./telemetry.ts\"\nimport * as Toolkits from \"./toolkit.ts\"\nimport type { Writer } from \"./writer.ts\"\n\n/** @internal */\nexport type RunHandler = (turn: Specialist.Turn) => Stream.Stream<AssistantLoop.Event, unknown, Specialist.TurnServices>\n\n/** @internal */\nexport const briefText = (brief: Specialist.Brief): string =>\n [\n `Goal: ${brief.goal}`,\n brief.context && `Context: ${brief.context}`,\n brief.successCriteria && `Done when: ${brief.successCriteria}`,\n brief.report && `Report back: ${brief.report}`,\n brief.limits && `Limits: ${brief.limits}`\n ]\n .filter(Boolean)\n .join(\"\\n\")\n\nconst finalText = (output: AssistantEvent.ModelOutput): string =>\n output.parts\n .flatMap((part) => (part.type === \"text\" ? [part.text] : []))\n .join(\"\")\n .trim()\n\n// The answer is the last text the model wrote.\n/** @internal */\nexport const runBrief = Effect.fnUntraced(\n function* (\n handler: RunHandler,\n options: {\n readonly specialist: Specialist.Any\n readonly runId: RunId.RunId\n readonly brief: Specialist.Brief\n readonly history: Ref.Ref<ReadonlyArray<Prompt.Message>>\n readonly inbox: Queue.Queue<Prompt.Message>\n readonly chatId: Writer[\"chatId\"]\n }\n ) {\n const { history, inbox } = options\n const answer = yield* Ref.make(\"\")\n const add = (messages: ReadonlyArray<Prompt.Message>) => Ref.update(history, (all) => [...all, ...messages])\n\n // Read before every model call, when no tool calls are open: messages sent meanwhile join it now.\n const current = Effect.gen(function* () {\n yield* add(yield* Queue.clear(inbox))\n\n return yield* Ref.get(history)\n })\n\n const turn: Specialist.Turn = {\n runId: options.runId,\n chatId: options.chatId,\n brief: options.brief,\n history: current,\n // A run can't wait for the user's approval: a call that needs it fails, and the run reads why.\n run: (run) =>\n Loop.run({\n instructions: run.instructions,\n toolkit: Toolkits.fromToolkitOrEmpty(run.toolkit),\n history: current,\n maxSteps: run.maxSteps ?? 8\n }).pipe(Stream.provideService(Loop.TurnApprovals, Option.none()))\n }\n\n // A run lives in memory, so there's nothing to run again after a crash: `ToolStarted` isn't kept.\n const record = Effect.fnUntraced(function* (event: AssistantLoop.Event) {\n if (Predicate.isTagged(event, \"ToolStarted\") || Predicate.isTagged(event, \"ApprovalRequested\")) return\n\n if (Predicate.isTagged(event, \"ToolResult\")) return yield* add([toolMessage(event)])\n\n yield* add(Prompt.fromResponseParts(event.parts).content)\n const text = finalText(event)\n\n if (text.length > 0) yield* Ref.set(answer, text)\n })\n\n yield* Stream.runForEach(handler(turn), record)\n\n const text = yield* Ref.get(answer)\n\n yield* Telemetry.agentOutput(text)\n\n return text\n },\n (effect, _handler, options) => effect.pipe(Telemetry.agentSpan(options.specialist.name, { chatId: options.chatId }))\n)\n\ntype Outcome =\n | { readonly status: \"answered\"; readonly report: string }\n | { readonly status: \"failed\"; readonly error: string }\n | { readonly status: \"stopped\" | \"cut_off\" }\n\n// Only `stop` interrupts a brief's loop alone; a failure or a defect failed it.\nconst unanswered = (cause: Cause.Cause<unknown>): Outcome =>\n Cause.hasInterruptsOnly(cause) ? { status: \"stopped\" } : { status: \"failed\", error: String(Cause.squash(cause)) }\n\n// The id is fixed per run and brief, so a replay is stored once.\n/** @internal */\nexport const finished = (\n specialist: string,\n run: { readonly runId: RunId.RunId; readonly brief: number; readonly goal: string },\n outcome: Outcome\n): AssistantEvent.SpecialistFinished =>\n AssistantEvent.SpecialistFinished.make({\n id: InputId.fromKey(`${run.runId}:${run.brief}`),\n runId: run.runId,\n specialist,\n brief: run.brief,\n goal: run.goal,\n ...outcome\n })\n\nconst RunIdInput = RunId.RunId.annotate({ description: \"The runId spawn_specialist returned.\" })\n\nconst RunStatus = <const Status extends string>(status: ReadonlyArray<Status>) =>\n Schema.Struct({ runId: RunId.RunId, status: Schema.Literals(status) })\n\nconst MessageSpecialist = Tool.make(\"message_specialist\", {\n description:\n \"Send more to one of your specialists: new instructions for a running one (it reads them at its next step), or a follow-up for a finished one (it continues with what it already found).\",\n parameters: Schema.Struct({ runId: RunIdInput, message: Schema.String }),\n success: RunStatus([\"steered\", \"continued\"]),\n failure: ToolFailure.ToolFailure,\n dependencies: [Specialist.Runner]\n})\n\nconst StopSpecialist = Tool.make(\"stop_specialist\", {\n description:\n \"Stop one of your running specialists, for example when the user changed their mind. It posts no answer.\",\n parameters: Schema.Struct({ runId: RunIdInput }),\n success: RunStatus([\"stopped\"]),\n failure: ToolFailure.ToolFailure,\n dependencies: [Specialist.Runner]\n})\n\nconst refused = <A, R>(effect: Effect.Effect<A, Specialist.SpecialistError, R>) =>\n Effect.catch(effect, (error) => ToolFailure.fail(error.message, { reason: error.reason }))\n\n/** @internal */\nexport const toolkit = Effect.fnUntraced(function* (\n specialists: ReadonlyArray<Specialist.Any>\n): Effect.fn.Return<Toolkits.Tools<Specialist.Runner>> {\n if (specialists.length === 0) return Toolkits.empty\n\n const catalog = specialists.map((specialist) => `- ${specialist.name}: ${specialist.description}`).join(\"\\n\")\n\n const SpawnSpecialist = Tool.make(\"spawn_specialist\", {\n description: `Start a specialist on a job. It works in the background and its report arrives as a <specialist> input, often within seconds. Write a self-contained brief: it can't see this chat. For a quick question, just call wait; for slow work, tell the user in one line first.\\nPass one of these as specialist:\\n${catalog}`,\n parameters: Schema.Struct({\n specialist: Schema.Literals(specialists.map((specialist) => specialist.name)).annotate({\n description: \"The specialist to start, by name.\"\n }),\n ...Specialist.Brief.fields\n }),\n success: RunStatus([\"started\"]),\n failure: ToolFailure.ToolFailure,\n dependencies: [Specialist.Runner]\n })\n\n const SpecialistTools = Toolkit.make(SpawnSpecialist, MessageSpecialist, StopSpecialist)\n\n const handlers = yield* SpecialistTools.toHandlers({\n spawn_specialist: Effect.fnUntraced(function* ({ specialist, ...brief }) {\n const runId = yield* refused(Specialist.Runner.use((runner) => runner.spawn({ specialist, brief })))\n\n return { runId, status: \"started\" }\n }),\n message_specialist: Effect.fnUntraced(function* ({ runId, message }) {\n const status = yield* refused(Specialist.Runner.use((runner) => runner.message(runId, message)))\n\n return { runId, status }\n }),\n stop_specialist: Effect.fnUntraced(function* ({ runId }) {\n yield* refused(Specialist.Runner.use((runner) => runner.stop(runId)))\n\n return { runId, status: \"stopped\" }\n })\n })\n\n return Toolkits.fromWithHandler(yield* Effect.provideContext(SpecialistTools, handlers))\n})\n\n// How long a finished run stays available for follow-ups.\nconst warm = \"15 minutes\"\n\ninterface Run {\n readonly runId: RunId.RunId\n readonly specialist: Specialist.Any\n readonly handler: RunHandler\n readonly goal: string\n readonly brief: Specialist.Brief\n readonly history: Ref.Ref<ReadonlyArray<Prompt.Message>>\n // Messages sent while it works; the loop reads them at its next step.\n readonly inbox: Queue.Queue<Prompt.Message>\n // Counts follow-ups too; numbers the reports.\n readonly briefs: Ref.Ref<number>\n // Read and changed under `lock`.\n readonly busy: Ref.Ref<boolean>\n // Orders a message against the run finishing, so none is dropped or starts a second loop.\n readonly lock: Semaphore.Semaphore\n}\n\n/** @internal */\nexport const makeRunner = Effect.fnUntraced(function* (options: {\n readonly chat: Writer\n readonly handle: CurrentChat[\"Service\"]\n readonly specialists: ReadonlyArray<{\n readonly specialist: Specialist.Any\n readonly handler: RunHandler\n }>\n readonly model: Context.Context<LanguageModel.LanguageModel>\n // An interruption keeps the hold.\n readonly holding: (effect: Effect.Effect<void>) => Effect.Effect<void>\n readonly report: (finished: AssistantEvent.SpecialistFinished) => Effect.Effect<void>\n}) {\n const { chat } = options\n const runs = new Map<string, Run>()\n const byName = new Map(options.specialists.map((entry) => [entry.specialist.name, entry]))\n // The brief loops working now, and the finished runs' countdowns to being forgotten.\n const working = yield* FiberMap.make<string, Outcome>()\n const forgetting = yield* FiberMap.make<string>()\n const background = yield* FiberSet.make()\n\n const report = Effect.fnUntraced(function* (run: Run, outcome: Outcome) {\n const brief = yield* Ref.get(run.briefs)\n\n yield* options.report(finished(run.specialist.name, { runId: run.runId, brief, goal: run.goal }, outcome))\n })\n\n const once = (run: Run) =>\n runBrief(run.handler, {\n specialist: run.specialist,\n runId: run.runId,\n brief: run.brief,\n history: run.history,\n inbox: run.inbox,\n chatId: chat.chatId\n }).pipe(\n Effect.provideContext(Context.merge(options.model, services)),\n // Spawned from a turn, a run would inherit the turn's own tools: a run can't message the user.\n Effect.provideService(Loop.TurnTools, Option.none()),\n Effect.matchCause({\n onSuccess: (answer): Outcome => ({ status: \"answered\", report: answer }),\n onFailure: unanswered\n })\n )\n\n // A message that arrived after the last read: keep working before reporting.\n const untilCaughtUp = Effect.fnUntraced(function* (run: Run) {\n let outcome = yield* once(run)\n\n while ((yield* Queue.size(run.inbox)) > 0) outcome = yield* once(run)\n\n return outcome\n })\n\n const work = Effect.fnUntraced(\n function* (run: Run) {\n yield* chat.append([\n AssistantEvent.SpecialistStarted.make({\n runId: run.runId,\n specialist: run.specialist.name,\n goal: run.goal\n })\n ])\n\n let finished = false\n\n while (!finished) {\n const exit = yield* Fiber.await(yield* FiberMap.run(working, run.runId, untilCaughtUp(run)))\n const outcome = Exit.isSuccess(exit) ? exit.value : unanswered(exit.cause)\n\n finished = yield* run.lock.withPermit(\n Effect.gen(function* () {\n // A message steered in after the loop's last read: work on it before reporting.\n if (outcome.status !== \"stopped\" && (yield* Queue.size(run.inbox)) > 0) return false\n\n yield* report(run, outcome)\n yield* Ref.set(run.busy, false)\n yield* FiberMap.run(\n forgetting,\n run.runId,\n Effect.gen(function* () {\n yield* Effect.sleep(warm)\n runs.delete(run.runId)\n })\n )\n\n return true\n })\n )\n }\n },\n // A shutdown interrupts the whole run: no turn can start now, so the report waits for the chat's next start.\n (effect, run) => Effect.onInterrupt(effect, () => report(run, { status: \"cut_off\" }))\n )\n\n const launch = Effect.fnUntraced(function* (run: Run) {\n yield* FiberMap.remove(forgetting, run.runId)\n yield* FiberSet.run(background, options.holding(work(run)))\n })\n\n // Under the run's lock.\n const send = Effect.fnUntraced(function* (run: Run, text: string): Effect.fn.Return<\"steered\" | \"continued\"> {\n yield* Ref.update(run.briefs, (n) => n + 1)\n\n if (yield* Ref.get(run.busy)) {\n yield* Queue.offer(run.inbox, userText(text))\n\n return \"steered\"\n }\n\n yield* Ref.update(run.history, (all) => [...all, userText(text)])\n yield* Ref.set(run.busy, true)\n yield* launch(run)\n\n return \"continued\"\n })\n\n const runner = Specialist.Runner.of({\n spawn: Effect.fnUntraced(function* (request: Specialist.SpawnRequest) {\n const found = byName.get(request.specialist)\n\n if (found === undefined) {\n return yield* new Specialist.SpecialistError({\n reason: \"UnknownSpecialist\",\n message: `No specialist named ${request.specialist}.`\n })\n }\n\n const run: Run = {\n runId: yield* RunId.make(),\n specialist: found.specialist,\n handler: found.handler,\n goal: request.brief.goal,\n brief: request.brief,\n history: yield* Ref.make<ReadonlyArray<Prompt.Message>>([userText(briefText(request.brief))]),\n inbox: yield* Queue.unbounded<Prompt.Message>(),\n briefs: yield* Ref.make(1),\n busy: yield* Ref.make(true),\n lock: yield* Semaphore.make(1)\n }\n\n runs.set(run.runId, run)\n yield* launch(run)\n\n return run.runId\n }),\n message: Effect.fnUntraced(function* (\n runId: RunId.RunId,\n text: string\n ): Effect.fn.Return<\"steered\" | \"continued\", Specialist.SpecialistError> {\n const run = runs.get(runId)\n\n if (run === undefined) {\n return yield* new Specialist.SpecialistError({\n reason: \"RunNotFound\",\n message: `Run ${runId} is no longer available. Spawn a new one with the full context.`\n })\n }\n\n return yield* run.lock.withPermit(send(run, text))\n }),\n stop: Effect.fnUntraced(function* (runId: RunId.RunId) {\n if (!(yield* FiberMap.has(working, runId))) {\n return yield* new Specialist.SpecialistError({ reason: \"NotRunning\", message: `Run ${runId} isn't running.` })\n }\n\n yield* FiberMap.remove(working, runId)\n })\n })\n\n // What a run's handler gets: its chat, this runner, and (merged in `once`) the model.\n const services = Context.make(CurrentChat, options.handle).pipe(Context.add(Specialist.Runner, runner))\n\n return runner\n})\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"turns.d.ts","names":[],"sources":["../../src/internal/turns.ts"],"mappings":";;;;;;;;;;;;;;;;;;qBAmCa;;qBAGA;;qBAIA;;iBAII;WACN;WACA,QAAQ;WACR,aAAa,cAAc;WAC3B,KAAK;;;YAKJ,eACV,MAAM,cACH,OAAO,OAAO,gBAA8B,eAAyB,cAAc;;iBAKvE;WACN,QAAQ;WACR,SAAS,OAAO,OAAO;WACvB,QAAQ;;;iBAIF;WACN,YAAY;WACZ,OAAO,QAAQ,QAAQ,cAAc;WACrC,OAAO,MAAM,cAAc,OAAO,OAAO,oBAAoB,MAAM,QAAQ;WAC3E,aAAa;aACX,YAAY;aACZ,OAAO;;WAET,UAAU,MAAe;;;qBAIvB,WAAQ,YAAA,YAAA;WAEO,MAAA;MAiC1B,OAAA,OAAA,qBAAA,cAAA;;
|
|
1
|
+
{"version":3,"file":"turns.d.ts","names":[],"sources":["../../src/internal/turns.ts"],"mappings":";;;;;;;;;;;;;;;;;;qBAmCa;;qBAGA;;qBAIA;;iBAII;WACN;WACA,QAAQ;WACR,aAAa,cAAc;WAC3B,KAAK;;;YAKJ,eACV,MAAM,cACH,OAAO,OAAO,gBAA8B,eAAyB,cAAc;;iBAKvE;WACN,QAAQ;WACR,SAAS,OAAO,OAAO;WACvB,QAAQ;;;iBAIF;WACN,YAAY;WACZ,OAAO,QAAQ,QAAQ,cAAc;WACrC,OAAO,MAAM,cAAc,OAAO,OAAO,oBAAoB,MAAM,QAAQ;WAC3E,aAAa;aACX,YAAY;aACZ,OAAO;;WAET,UAAU,MAAe;;;qBAIvB,WAAQ,YAAA,YAAA;WAEO,MAAA;MAiC1B,OAAA,OAAA,qBAAA,cAAA;;qBA+NW,MAAG;WAEW,cAAA;WACL,SAAA;WACH,MAAA;WACI,UAAA,QAAQ,QAAQ,eAAyB,cAAc;WAEtD,WAAA,OAAO;MA+D9B,OAAA"}
|