@simulacra-ai/core 0.0.3 → 0.0.4
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/index.cjs +2293 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1631 -0
- package/dist/index.d.ts +1631 -10
- package/dist/index.js +2232 -8
- package/dist/index.js.map +1 -1
- package/package.json +10 -4
- package/dist/checkpoints/default-summarization-strategy.d.ts +0 -19
- package/dist/checkpoints/default-summarization-strategy.d.ts.map +0 -1
- package/dist/checkpoints/default-summarization-strategy.js +0 -69
- package/dist/checkpoints/default-summarization-strategy.js.map +0 -1
- package/dist/checkpoints/index.d.ts +0 -3
- package/dist/checkpoints/index.d.ts.map +0 -1
- package/dist/checkpoints/index.js +0 -3
- package/dist/checkpoints/index.js.map +0 -1
- package/dist/checkpoints/types.d.ts +0 -32
- package/dist/checkpoints/types.d.ts.map +0 -1
- package/dist/checkpoints/types.js +0 -2
- package/dist/checkpoints/types.js.map +0 -1
- package/dist/context-transformers/checkpoint-context-transformer.d.ts +0 -19
- package/dist/context-transformers/checkpoint-context-transformer.d.ts.map +0 -1
- package/dist/context-transformers/checkpoint-context-transformer.js +0 -38
- package/dist/context-transformers/checkpoint-context-transformer.js.map +0 -1
- package/dist/context-transformers/composite-context-transformer.d.ts +0 -31
- package/dist/context-transformers/composite-context-transformer.d.ts.map +0 -1
- package/dist/context-transformers/composite-context-transformer.js +0 -41
- package/dist/context-transformers/composite-context-transformer.js.map +0 -1
- package/dist/context-transformers/index.d.ts +0 -6
- package/dist/context-transformers/index.d.ts.map +0 -1
- package/dist/context-transformers/index.js +0 -6
- package/dist/context-transformers/index.js.map +0 -1
- package/dist/context-transformers/noop-context-transformer.d.ts +0 -24
- package/dist/context-transformers/noop-context-transformer.d.ts.map +0 -1
- package/dist/context-transformers/noop-context-transformer.js +0 -26
- package/dist/context-transformers/noop-context-transformer.js.map +0 -1
- package/dist/context-transformers/tool-context-transformer.d.ts +0 -26
- package/dist/context-transformers/tool-context-transformer.d.ts.map +0 -1
- package/dist/context-transformers/tool-context-transformer.js +0 -52
- package/dist/context-transformers/tool-context-transformer.js.map +0 -1
- package/dist/context-transformers/types.d.ts +0 -65
- package/dist/context-transformers/types.d.ts.map +0 -1
- package/dist/context-transformers/types.js +0 -2
- package/dist/context-transformers/types.js.map +0 -1
- package/dist/conversations/conversation.d.ts +0 -152
- package/dist/conversations/conversation.d.ts.map +0 -1
- package/dist/conversations/conversation.js +0 -648
- package/dist/conversations/conversation.js.map +0 -1
- package/dist/conversations/index.d.ts +0 -5
- package/dist/conversations/index.d.ts.map +0 -1
- package/dist/conversations/index.js +0 -5
- package/dist/conversations/index.js.map +0 -1
- package/dist/conversations/stream-listener.d.ts +0 -37
- package/dist/conversations/stream-listener.d.ts.map +0 -1
- package/dist/conversations/stream-listener.js +0 -68
- package/dist/conversations/stream-listener.js.map +0 -1
- package/dist/conversations/token-tracker.d.ts +0 -59
- package/dist/conversations/token-tracker.d.ts.map +0 -1
- package/dist/conversations/token-tracker.js +0 -98
- package/dist/conversations/token-tracker.js.map +0 -1
- package/dist/conversations/types.d.ts +0 -346
- package/dist/conversations/types.d.ts.map +0 -1
- package/dist/conversations/types.js +0 -2
- package/dist/conversations/types.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/policies/composite-policy.d.ts +0 -29
- package/dist/policies/composite-policy.d.ts.map +0 -1
- package/dist/policies/composite-policy.js +0 -77
- package/dist/policies/composite-policy.js.map +0 -1
- package/dist/policies/default-policy.d.ts +0 -7
- package/dist/policies/default-policy.d.ts.map +0 -1
- package/dist/policies/default-policy.js +0 -14
- package/dist/policies/default-policy.js.map +0 -1
- package/dist/policies/index.d.ts +0 -8
- package/dist/policies/index.d.ts.map +0 -1
- package/dist/policies/index.js +0 -8
- package/dist/policies/index.js.map +0 -1
- package/dist/policies/noop-policy.d.ts +0 -21
- package/dist/policies/noop-policy.d.ts.map +0 -1
- package/dist/policies/noop-policy.js +0 -36
- package/dist/policies/noop-policy.js.map +0 -1
- package/dist/policies/rate-limit-policy.d.ts +0 -44
- package/dist/policies/rate-limit-policy.d.ts.map +0 -1
- package/dist/policies/rate-limit-policy.js +0 -98
- package/dist/policies/rate-limit-policy.js.map +0 -1
- package/dist/policies/retry-policy.d.ts +0 -42
- package/dist/policies/retry-policy.d.ts.map +0 -1
- package/dist/policies/retry-policy.js +0 -114
- package/dist/policies/retry-policy.js.map +0 -1
- package/dist/policies/token-limit-policy.d.ts +0 -76
- package/dist/policies/token-limit-policy.d.ts.map +0 -1
- package/dist/policies/token-limit-policy.js +0 -134
- package/dist/policies/token-limit-policy.js.map +0 -1
- package/dist/policies/types.d.ts +0 -49
- package/dist/policies/types.d.ts.map +0 -1
- package/dist/policies/types.js +0 -9
- package/dist/policies/types.js.map +0 -1
- package/dist/tools/index.d.ts +0 -2
- package/dist/tools/index.d.ts.map +0 -1
- package/dist/tools/index.js +0 -2
- package/dist/tools/index.js.map +0 -1
- package/dist/tools/types.d.ts +0 -180
- package/dist/tools/types.d.ts.map +0 -1
- package/dist/tools/types.js +0 -2
- package/dist/tools/types.js.map +0 -1
- package/dist/utils/async.d.ts +0 -125
- package/dist/utils/async.d.ts.map +0 -1
- package/dist/utils/async.js +0 -217
- package/dist/utils/async.js.map +0 -1
- package/dist/utils/index.d.ts +0 -4
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/index.js +0 -4
- package/dist/utils/index.js.map +0 -1
- package/dist/utils/object.d.ts +0 -80
- package/dist/utils/object.d.ts.map +0 -1
- package/dist/utils/object.js +0 -189
- package/dist/utils/object.js.map +0 -1
- package/dist/utils/types.d.ts +0 -17
- package/dist/utils/types.d.ts.map +0 -1
- package/dist/utils/types.js +0 -2
- package/dist/utils/types.js.map +0 -1
- package/dist/workflows/index.d.ts +0 -4
- package/dist/workflows/index.d.ts.map +0 -1
- package/dist/workflows/index.js +0 -4
- package/dist/workflows/index.js.map +0 -1
- package/dist/workflows/types.d.ts +0 -70
- package/dist/workflows/types.d.ts.map +0 -1
- package/dist/workflows/types.js +0 -2
- package/dist/workflows/types.js.map +0 -1
- package/dist/workflows/workflow-manager.d.ts +0 -74
- package/dist/workflows/workflow-manager.d.ts.map +0 -1
- package/dist/workflows/workflow-manager.js +0 -165
- package/dist/workflows/workflow-manager.js.map +0 -1
- package/dist/workflows/workflow.d.ts +0 -117
- package/dist/workflows/workflow.d.ts.map +0 -1
- package/dist/workflows/workflow.js +0 -355
- package/dist/workflows/workflow.js.map +0 -1
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,2293 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name);
|
|
9
|
+
var __typeError = (msg) => {
|
|
10
|
+
throw TypeError(msg);
|
|
11
|
+
};
|
|
12
|
+
var __export = (target, all) => {
|
|
13
|
+
for (var name in all)
|
|
14
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
15
|
+
};
|
|
16
|
+
var __copyProps = (to, from, except, desc) => {
|
|
17
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
18
|
+
for (let key of __getOwnPropNames(from))
|
|
19
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
20
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
21
|
+
}
|
|
22
|
+
return to;
|
|
23
|
+
};
|
|
24
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
25
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
26
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
27
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
28
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
29
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
30
|
+
mod
|
|
31
|
+
));
|
|
32
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
33
|
+
var __using = (stack, value, async) => {
|
|
34
|
+
if (value != null) {
|
|
35
|
+
if (typeof value !== "object" && typeof value !== "function") __typeError("Object expected");
|
|
36
|
+
var dispose, inner;
|
|
37
|
+
if (async) dispose = value[__knownSymbol("asyncDispose")];
|
|
38
|
+
if (dispose === void 0) {
|
|
39
|
+
dispose = value[__knownSymbol("dispose")];
|
|
40
|
+
if (async) inner = dispose;
|
|
41
|
+
}
|
|
42
|
+
if (typeof dispose !== "function") __typeError("Object not disposable");
|
|
43
|
+
if (inner) dispose = function() {
|
|
44
|
+
try {
|
|
45
|
+
inner.call(this);
|
|
46
|
+
} catch (e) {
|
|
47
|
+
return Promise.reject(e);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
stack.push([async, dispose, value]);
|
|
51
|
+
} else if (async) {
|
|
52
|
+
stack.push([async]);
|
|
53
|
+
}
|
|
54
|
+
return value;
|
|
55
|
+
};
|
|
56
|
+
var __callDispose = (stack, error, hasError) => {
|
|
57
|
+
var E = typeof SuppressedError === "function" ? SuppressedError : function(e, s, m, _) {
|
|
58
|
+
return _ = Error(m), _.name = "SuppressedError", _.error = e, _.suppressed = s, _;
|
|
59
|
+
};
|
|
60
|
+
var fail = (e) => error = hasError ? new E(e, error, "An error was suppressed during disposal") : (hasError = true, e);
|
|
61
|
+
var next = (it) => {
|
|
62
|
+
while (it = stack.pop()) {
|
|
63
|
+
try {
|
|
64
|
+
var result = it[1] && it[1].call(it[2]);
|
|
65
|
+
if (it[0]) return Promise.resolve(result).then(next, (e) => (fail(e), next()));
|
|
66
|
+
} catch (e) {
|
|
67
|
+
fail(e);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (hasError) throw error;
|
|
71
|
+
};
|
|
72
|
+
return next();
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// src/index.ts
|
|
76
|
+
var index_exports = {};
|
|
77
|
+
__export(index_exports, {
|
|
78
|
+
CancellationToken: () => CancellationToken,
|
|
79
|
+
CancellationTokenSource: () => CancellationTokenSource,
|
|
80
|
+
CheckpointContextTransformer: () => CheckpointContextTransformer,
|
|
81
|
+
CompositeContextTransformer: () => CompositeContextTransformer,
|
|
82
|
+
CompositePolicy: () => CompositePolicy,
|
|
83
|
+
Conversation: () => Conversation,
|
|
84
|
+
DefaultSummarizationStrategy: () => DefaultSummarizationStrategy,
|
|
85
|
+
NoopContextTransformer: () => NoopContextTransformer,
|
|
86
|
+
NoopPolicy: () => NoopPolicy,
|
|
87
|
+
OperationCanceledError: () => OperationCanceledError,
|
|
88
|
+
Policy: () => Policy,
|
|
89
|
+
RateLimitPolicy: () => RateLimitPolicy,
|
|
90
|
+
RetryPolicy: () => RetryPolicy,
|
|
91
|
+
StreamListener: () => StreamListener,
|
|
92
|
+
TokenLimitPolicy: () => TokenLimitPolicy,
|
|
93
|
+
TokenTracker: () => TokenTracker,
|
|
94
|
+
ToolContextTransformer: () => ToolContextTransformer,
|
|
95
|
+
Workflow: () => Workflow,
|
|
96
|
+
WorkflowManager: () => WorkflowManager,
|
|
97
|
+
deep_merge: () => deep_merge,
|
|
98
|
+
defaultRetryable: () => defaultRetryable,
|
|
99
|
+
getDefaultPolicy: () => getDefaultPolicy,
|
|
100
|
+
peek_generator: () => peek_generator,
|
|
101
|
+
sleep: () => sleep,
|
|
102
|
+
undefined_if_empty: () => undefined_if_empty
|
|
103
|
+
});
|
|
104
|
+
module.exports = __toCommonJS(index_exports);
|
|
105
|
+
|
|
106
|
+
// src/checkpoints/default-summarization-strategy.ts
|
|
107
|
+
var DefaultSummarizationStrategy = class {
|
|
108
|
+
/**
|
|
109
|
+
* Builds the summarization prompt from the conversation context. Assembles
|
|
110
|
+
* the previous checkpoint summary, system prompt, and recent messages into a
|
|
111
|
+
* structured text block, then appends instructions asking the model to produce
|
|
112
|
+
* a condensed summary suitable for continuing the conversation.
|
|
113
|
+
*/
|
|
114
|
+
build_prompt(context) {
|
|
115
|
+
const sections = [];
|
|
116
|
+
if (context.previous_checkpoint) {
|
|
117
|
+
sections.push(`## Previous Checkpoint Summary
|
|
118
|
+
${context.previous_checkpoint.summary}`);
|
|
119
|
+
}
|
|
120
|
+
if (context.system) {
|
|
121
|
+
sections.push(`## System Prompt
|
|
122
|
+
${context.system}`);
|
|
123
|
+
}
|
|
124
|
+
if (context.messages.length > 0) {
|
|
125
|
+
sections.push(`## Conversation
|
|
126
|
+
${this.#serialize_messages(context.messages)}`);
|
|
127
|
+
}
|
|
128
|
+
sections.push(
|
|
129
|
+
[
|
|
130
|
+
"## Instructions",
|
|
131
|
+
"Summarize the above conversation concisely. Preserve:",
|
|
132
|
+
"- Key decisions and their rationale",
|
|
133
|
+
"- Current state of any in-progress work",
|
|
134
|
+
"- Important facts, names, and values established",
|
|
135
|
+
"- Tool outcomes",
|
|
136
|
+
"- Any explicit user preferences or instructions",
|
|
137
|
+
"",
|
|
138
|
+
"Omit redundant back-and-forth and superseded plans.",
|
|
139
|
+
"Format as a structured briefing the model can use to continue the conversation seamlessly."
|
|
140
|
+
].join("\n")
|
|
141
|
+
);
|
|
142
|
+
return [
|
|
143
|
+
{
|
|
144
|
+
role: "user",
|
|
145
|
+
content: [{ type: "text", text: sections.join("\n\n") }]
|
|
146
|
+
}
|
|
147
|
+
];
|
|
148
|
+
}
|
|
149
|
+
#serialize_messages(messages) {
|
|
150
|
+
return messages.map((m) => {
|
|
151
|
+
const role = m.role === "user" ? "User" : "Assistant";
|
|
152
|
+
const parts = m.content.map((c) => {
|
|
153
|
+
switch (c.type) {
|
|
154
|
+
case "text":
|
|
155
|
+
return c.text;
|
|
156
|
+
case "thinking":
|
|
157
|
+
return `[Thinking: ${c.thought}]`;
|
|
158
|
+
case "tool":
|
|
159
|
+
return `[Called tool: ${c.tool}]`;
|
|
160
|
+
case "tool_result":
|
|
161
|
+
return `[Tool ${c.tool} returned: ${JSON.stringify(c.result)}]`;
|
|
162
|
+
default:
|
|
163
|
+
return void 0;
|
|
164
|
+
}
|
|
165
|
+
}).filter(Boolean);
|
|
166
|
+
return `${role}: ${parts.join("\n")}`;
|
|
167
|
+
}).join("\n\n");
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
// src/conversations/conversation.ts
|
|
172
|
+
var import_node_crypto = __toESM(require("crypto"), 1);
|
|
173
|
+
var import_node_events2 = __toESM(require("events"), 1);
|
|
174
|
+
var import_promises = require("timers/promises");
|
|
175
|
+
|
|
176
|
+
// src/context-transformers/checkpoint-context-transformer.ts
|
|
177
|
+
var CheckpointContextTransformer = class {
|
|
178
|
+
/**
|
|
179
|
+
* Replaces all messages before the checkpoint boundary with a synthetic user
|
|
180
|
+
* message containing the checkpoint summary. Messages after the boundary are
|
|
181
|
+
* kept intact. Returns messages unchanged if no checkpoint is active.
|
|
182
|
+
*/
|
|
183
|
+
async transform_prompt(messages, context) {
|
|
184
|
+
const checkpoint = context?.checkpoint;
|
|
185
|
+
if (!checkpoint) {
|
|
186
|
+
return messages;
|
|
187
|
+
}
|
|
188
|
+
const boundary = messages.findIndex((m) => m.id === checkpoint.message_id);
|
|
189
|
+
if (boundary === -1) {
|
|
190
|
+
return messages;
|
|
191
|
+
}
|
|
192
|
+
const summary_message = {
|
|
193
|
+
role: "user",
|
|
194
|
+
timestamp: Date.now(),
|
|
195
|
+
content: [{ type: "text", timestamp: Date.now(), text: checkpoint.summary }]
|
|
196
|
+
};
|
|
197
|
+
const boundary_message = messages[boundary];
|
|
198
|
+
if (boundary_message.role === "user") {
|
|
199
|
+
return [summary_message, ...messages.slice(boundary + 1)];
|
|
200
|
+
}
|
|
201
|
+
return [summary_message, ...messages.slice(boundary)];
|
|
202
|
+
}
|
|
203
|
+
/** No-op. Returns the message unchanged. */
|
|
204
|
+
transform_completion(message) {
|
|
205
|
+
return Promise.resolve(message);
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
// src/context-transformers/composite-context-transformer.ts
|
|
210
|
+
var CompositeContextTransformer = class {
|
|
211
|
+
#transformers;
|
|
212
|
+
/**
|
|
213
|
+
* Creates a new composite context transformer.
|
|
214
|
+
*
|
|
215
|
+
* @param transformers - The transformers to compose.
|
|
216
|
+
*/
|
|
217
|
+
constructor(transformers) {
|
|
218
|
+
this.#transformers = transformers;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Transforms prompt messages by applying all transformers in sequence.
|
|
222
|
+
*
|
|
223
|
+
* @param messages - The messages to transform.
|
|
224
|
+
* @returns A promise that resolves to the transformed messages.
|
|
225
|
+
*/
|
|
226
|
+
async transform_prompt(messages, context) {
|
|
227
|
+
for (const transformer of this.#transformers) {
|
|
228
|
+
messages = await transformer.transform_prompt(messages, context);
|
|
229
|
+
}
|
|
230
|
+
return messages;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Transforms a completion message by applying all transformers in sequence.
|
|
234
|
+
*
|
|
235
|
+
* @param message - The assistant message to transform.
|
|
236
|
+
* @returns A promise that resolves to the transformed message.
|
|
237
|
+
*/
|
|
238
|
+
async transform_completion(message) {
|
|
239
|
+
for (const transformer of this.#transformers) {
|
|
240
|
+
message = await transformer.transform_completion(message);
|
|
241
|
+
}
|
|
242
|
+
return message;
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
// src/context-transformers/tool-context-transformer.ts
|
|
247
|
+
var ToolContextTransformer = class {
|
|
248
|
+
/**
|
|
249
|
+
* Transforms prompt messages by removing unreferenced tool invocations.
|
|
250
|
+
*
|
|
251
|
+
* @param messages - The messages to transform.
|
|
252
|
+
* @returns A promise that resolves to the transformed messages.
|
|
253
|
+
*/
|
|
254
|
+
transform_prompt(messages) {
|
|
255
|
+
const tool_ids = [];
|
|
256
|
+
const message_context = [];
|
|
257
|
+
for (const message of messages.toReversed()) {
|
|
258
|
+
tool_ids.push(
|
|
259
|
+
...message.content.filter((c) => c.type === "tool_result").map((c) => c.tool_request_id)
|
|
260
|
+
);
|
|
261
|
+
const cleaned_message = {
|
|
262
|
+
...message,
|
|
263
|
+
content: message.content.filter(
|
|
264
|
+
(c) => c.type !== "tool" || tool_ids.includes(c.tool_request_id)
|
|
265
|
+
)
|
|
266
|
+
};
|
|
267
|
+
if (cleaned_message.content.length) {
|
|
268
|
+
message_context.unshift(cleaned_message);
|
|
269
|
+
} else if (message.role === "assistant") {
|
|
270
|
+
message_context.unshift({
|
|
271
|
+
...message,
|
|
272
|
+
content: [{ type: "text", text: "" }]
|
|
273
|
+
});
|
|
274
|
+
} else if (message.role === "user") {
|
|
275
|
+
message_context.unshift({
|
|
276
|
+
...message,
|
|
277
|
+
content: [{ type: "text", text: "" }]
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return Promise.resolve(message_context);
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Returns the completion message unchanged.
|
|
285
|
+
*
|
|
286
|
+
* @param message - The assistant message to pass through.
|
|
287
|
+
* @returns A promise that resolves to the same message.
|
|
288
|
+
*/
|
|
289
|
+
transform_completion(message) {
|
|
290
|
+
return Promise.resolve(message);
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
// src/policies/types.ts
|
|
295
|
+
var Policy = class {
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
// src/policies/noop-policy.ts
|
|
299
|
+
var NoopPolicy = class _NoopPolicy extends Policy {
|
|
300
|
+
/**
|
|
301
|
+
* Executes a function with no additional policy logic.
|
|
302
|
+
*
|
|
303
|
+
* @template TResult - The return type of the function.
|
|
304
|
+
* @template TArgs - The argument types of the function.
|
|
305
|
+
* @param cancellation_token - Token for cancelling the operation.
|
|
306
|
+
* @param fn - The function to execute.
|
|
307
|
+
* @param args - Arguments to pass to the function.
|
|
308
|
+
* @returns A promise that resolves to the policy result.
|
|
309
|
+
*/
|
|
310
|
+
async execute(cancellation_token, fn, ...args) {
|
|
311
|
+
try {
|
|
312
|
+
cancellation_token.throw_if_cancellation_requested();
|
|
313
|
+
return {
|
|
314
|
+
result: true,
|
|
315
|
+
value: await Promise.race([fn(...args), cancellation_token.await_cancellation()]),
|
|
316
|
+
metadata: { policy: _NoopPolicy.name }
|
|
317
|
+
};
|
|
318
|
+
} catch (error) {
|
|
319
|
+
return {
|
|
320
|
+
result: false,
|
|
321
|
+
error,
|
|
322
|
+
metadata: { policy: _NoopPolicy.name }
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
// src/policies/composite-policy.ts
|
|
329
|
+
var CompositePolicy = class _CompositePolicy extends Policy {
|
|
330
|
+
#policies;
|
|
331
|
+
/**
|
|
332
|
+
* Creates a new composite policy.
|
|
333
|
+
*
|
|
334
|
+
* @param policies - The policies to compose. If empty, uses a NoopPolicy.
|
|
335
|
+
*/
|
|
336
|
+
constructor(...policies) {
|
|
337
|
+
super();
|
|
338
|
+
this.#policies = policies.length > 0 ? policies : [new NoopPolicy()];
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Executes a function with all composed policies applied.
|
|
342
|
+
*
|
|
343
|
+
* @template TResult - The return type of the function.
|
|
344
|
+
* @template TArgs - The argument types of the function.
|
|
345
|
+
* @param cancellation_token - Token for cancelling the operation.
|
|
346
|
+
* @param fn - The function to execute.
|
|
347
|
+
* @param args - Arguments to pass to the function.
|
|
348
|
+
* @returns A promise that resolves to the policy result.
|
|
349
|
+
*/
|
|
350
|
+
async execute(cancellation_token, fn, ...args) {
|
|
351
|
+
let current_fn = fn;
|
|
352
|
+
const policy_metadata = {};
|
|
353
|
+
const execution_order = [];
|
|
354
|
+
for (let i = this.#policies.length - 1; i >= 0; i--) {
|
|
355
|
+
cancellation_token.throw_if_cancellation_requested();
|
|
356
|
+
const policy = this.#policies[i];
|
|
357
|
+
const policy_name = `${policy.constructor.name}[${i}]`;
|
|
358
|
+
execution_order.push(policy_name);
|
|
359
|
+
const wrapped_fn = current_fn;
|
|
360
|
+
current_fn = async (...wrapped_args) => {
|
|
361
|
+
const policy_result = await policy.execute(cancellation_token, wrapped_fn, ...wrapped_args);
|
|
362
|
+
policy_metadata[policy_name] = policy_result.metadata;
|
|
363
|
+
if (policy_result.result) {
|
|
364
|
+
return policy_result.value;
|
|
365
|
+
} else {
|
|
366
|
+
throw policy_result.error;
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
try {
|
|
371
|
+
return {
|
|
372
|
+
result: true,
|
|
373
|
+
value: await current_fn(...args),
|
|
374
|
+
metadata: {
|
|
375
|
+
policy: _CompositePolicy.name,
|
|
376
|
+
policies: policy_metadata,
|
|
377
|
+
execution_order: [...execution_order].reverse(),
|
|
378
|
+
policy_count: this.#policies.length
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
} catch (error) {
|
|
382
|
+
return {
|
|
383
|
+
result: false,
|
|
384
|
+
error,
|
|
385
|
+
metadata: {
|
|
386
|
+
policy: _CompositePolicy.name,
|
|
387
|
+
policies: policy_metadata,
|
|
388
|
+
execution_order: [...execution_order].reverse(),
|
|
389
|
+
policy_count: this.#policies.length
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
// src/utils/async.ts
|
|
397
|
+
var import_node_events = __toESM(require("events"), 1);
|
|
398
|
+
var CancellationToken = class _CancellationToken {
|
|
399
|
+
#source;
|
|
400
|
+
/**
|
|
401
|
+
* Creates a new cancellation token.
|
|
402
|
+
*
|
|
403
|
+
* @param source - Optional source that controls this token.
|
|
404
|
+
*/
|
|
405
|
+
constructor(source) {
|
|
406
|
+
this.#source = source;
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Whether cancellation has been requested.
|
|
410
|
+
*/
|
|
411
|
+
get is_cancellation_requested() {
|
|
412
|
+
return !!this.#source?.is_cancelled;
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Registers a one-time handler for cancellation.
|
|
416
|
+
*
|
|
417
|
+
* @param event - The event name (always "cancel").
|
|
418
|
+
* @param handler - The callback to invoke when cancelled.
|
|
419
|
+
*/
|
|
420
|
+
once(event, handler) {
|
|
421
|
+
this.#source?.once(event, handler);
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Removes a cancellation handler.
|
|
425
|
+
*
|
|
426
|
+
* @param event - The event name (always "cancel").
|
|
427
|
+
* @param handler - The callback to remove.
|
|
428
|
+
*/
|
|
429
|
+
off(event, handler) {
|
|
430
|
+
this.#source?.off(event, handler);
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Throws an error if cancellation has been requested.
|
|
434
|
+
*/
|
|
435
|
+
throw_if_cancellation_requested() {
|
|
436
|
+
if (this.is_cancellation_requested) {
|
|
437
|
+
throw new OperationCanceledError(this);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Creates a promise that rejects when cancellation is requested.
|
|
442
|
+
*
|
|
443
|
+
* @returns A promise that never resolves but rejects on cancellation.
|
|
444
|
+
*/
|
|
445
|
+
await_cancellation() {
|
|
446
|
+
return new Promise(
|
|
447
|
+
(_, reject) => this.once("cancel", () => reject(new OperationCanceledError(this)))
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Creates an empty cancellation token that can never be cancelled.
|
|
452
|
+
*
|
|
453
|
+
* @returns A new cancellation token with no source.
|
|
454
|
+
*/
|
|
455
|
+
static empty() {
|
|
456
|
+
return new _CancellationToken();
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
var CancellationTokenSource = class {
|
|
460
|
+
#event_emitter = new import_node_events.default();
|
|
461
|
+
#is_cancelled = false;
|
|
462
|
+
#is_disposed = false;
|
|
463
|
+
#token;
|
|
464
|
+
/**
|
|
465
|
+
* Disposes the cancellation token source.
|
|
466
|
+
*
|
|
467
|
+
* This method is called automatically when using the `using` keyword.
|
|
468
|
+
*/
|
|
469
|
+
[Symbol.dispose]() {
|
|
470
|
+
if (this.#is_disposed) {
|
|
471
|
+
throw new Error("invalid state");
|
|
472
|
+
}
|
|
473
|
+
this.#is_disposed = true;
|
|
474
|
+
this.#event_emitter.removeAllListeners();
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Gets a cancellation token associated with this source.
|
|
478
|
+
*/
|
|
479
|
+
get token() {
|
|
480
|
+
if (!this.#token) {
|
|
481
|
+
this.#token = new CancellationToken(this);
|
|
482
|
+
}
|
|
483
|
+
return this.#token;
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Whether cancellation has been triggered.
|
|
487
|
+
*/
|
|
488
|
+
get is_cancelled() {
|
|
489
|
+
return this.#is_cancelled;
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Registers a one-time handler for cancellation.
|
|
493
|
+
*
|
|
494
|
+
* @param event - The event name (always "cancel").
|
|
495
|
+
* @param handler - The callback to invoke when cancelled.
|
|
496
|
+
*/
|
|
497
|
+
once(event, handler) {
|
|
498
|
+
if (this.#is_cancelled) {
|
|
499
|
+
throw new OperationCanceledError(new CancellationToken(this));
|
|
500
|
+
}
|
|
501
|
+
if (this.#is_disposed) {
|
|
502
|
+
throw new Error("invalid state");
|
|
503
|
+
}
|
|
504
|
+
this.#event_emitter.once(event, handler);
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Removes a cancellation handler.
|
|
508
|
+
*
|
|
509
|
+
* @param event - The event name (always "cancel").
|
|
510
|
+
* @param handler - The callback to remove.
|
|
511
|
+
*/
|
|
512
|
+
off(event, handler) {
|
|
513
|
+
this.#event_emitter.off(event, handler);
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* Triggers cancellation for all associated tokens.
|
|
517
|
+
*/
|
|
518
|
+
cancel() {
|
|
519
|
+
if (this.#is_cancelled) {
|
|
520
|
+
throw new OperationCanceledError(new CancellationToken(this));
|
|
521
|
+
}
|
|
522
|
+
this.#is_cancelled = true;
|
|
523
|
+
this.#event_emitter.emit("cancel");
|
|
524
|
+
}
|
|
525
|
+
};
|
|
526
|
+
var OperationCanceledError = class extends Error {
|
|
527
|
+
#token;
|
|
528
|
+
/**
|
|
529
|
+
* Creates a new operation cancelled error.
|
|
530
|
+
*
|
|
531
|
+
* @param token - The cancellation token associated with this error.
|
|
532
|
+
*/
|
|
533
|
+
constructor(token) {
|
|
534
|
+
super("The operation was cancelled", { cause: "cancelled" });
|
|
535
|
+
this.#token = token;
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* The cancellation token associated with this error.
|
|
539
|
+
*/
|
|
540
|
+
get token() {
|
|
541
|
+
return this.#token;
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
function sleep(ms, cancellation_token) {
|
|
545
|
+
return new Promise((resolve, reject) => {
|
|
546
|
+
if (cancellation_token) {
|
|
547
|
+
const token = cancellation_token;
|
|
548
|
+
const on_cancel = () => {
|
|
549
|
+
clearTimeout(timeout);
|
|
550
|
+
reject(new OperationCanceledError(token));
|
|
551
|
+
};
|
|
552
|
+
const on_timeout = () => {
|
|
553
|
+
token.off("cancel", on_cancel);
|
|
554
|
+
resolve();
|
|
555
|
+
};
|
|
556
|
+
const timeout = setTimeout(on_timeout, ms);
|
|
557
|
+
if (token.is_cancellation_requested) {
|
|
558
|
+
clearTimeout(timeout);
|
|
559
|
+
reject(new OperationCanceledError(token));
|
|
560
|
+
} else {
|
|
561
|
+
token.once("cancel", on_cancel);
|
|
562
|
+
}
|
|
563
|
+
} else {
|
|
564
|
+
setTimeout(resolve, ms);
|
|
565
|
+
}
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
async function peek_generator(generator) {
|
|
569
|
+
const iterator = generator[Symbol.asyncIterator]();
|
|
570
|
+
let result = await iterator.next();
|
|
571
|
+
const wrapper = (async function* () {
|
|
572
|
+
while (true) {
|
|
573
|
+
if (result.done) {
|
|
574
|
+
break;
|
|
575
|
+
}
|
|
576
|
+
yield result.value;
|
|
577
|
+
result = await iterator.next();
|
|
578
|
+
}
|
|
579
|
+
})();
|
|
580
|
+
return {
|
|
581
|
+
peeked_value: result.value,
|
|
582
|
+
generator: wrapper
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// src/policies/retry-policy.ts
|
|
587
|
+
var RETRYABLE_NETWORK_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
588
|
+
"ECONNRESET",
|
|
589
|
+
"ECONNREFUSED",
|
|
590
|
+
"ECONNABORTED",
|
|
591
|
+
"ETIMEDOUT",
|
|
592
|
+
"ENETUNREACH",
|
|
593
|
+
"EHOSTUNREACH",
|
|
594
|
+
"EPIPE",
|
|
595
|
+
"EAI_AGAIN",
|
|
596
|
+
"UND_ERR_CONNECT_TIMEOUT",
|
|
597
|
+
"UND_ERR_SOCKET"
|
|
598
|
+
]);
|
|
599
|
+
var RETRYABLE_HTTP_STATUS_CODES = /* @__PURE__ */ new Set([408, 429, 500, 502, 503, 504, 529]);
|
|
600
|
+
function defaultRetryable(result) {
|
|
601
|
+
const error = result.error;
|
|
602
|
+
if (error !== null && error !== void 0 && typeof error === "object" && "status" in error) {
|
|
603
|
+
const status = error.status;
|
|
604
|
+
if (typeof status === "number") {
|
|
605
|
+
return RETRYABLE_HTTP_STATUS_CODES.has(status);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
if (error !== null && error !== void 0 && typeof error === "object") {
|
|
609
|
+
const code = error.code;
|
|
610
|
+
if (typeof code === "string" && RETRYABLE_NETWORK_ERROR_CODES.has(code)) {
|
|
611
|
+
return true;
|
|
612
|
+
}
|
|
613
|
+
const cause = error.cause;
|
|
614
|
+
if (cause !== null && cause !== void 0 && typeof cause === "object") {
|
|
615
|
+
const causeCode = cause.code;
|
|
616
|
+
if (typeof causeCode === "string" && RETRYABLE_NETWORK_ERROR_CODES.has(causeCode)) {
|
|
617
|
+
return true;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
if (error instanceof Error) {
|
|
622
|
+
const msg = error.message.toLowerCase();
|
|
623
|
+
if (msg.includes("timeout") || msg.includes("econnreset") || msg.includes("econnrefused") || msg.includes("socket hang up") || msg.includes("network error") || msg.includes("fetch failed")) {
|
|
624
|
+
return true;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
return false;
|
|
628
|
+
}
|
|
629
|
+
var RetryPolicy = class _RetryPolicy extends Policy {
|
|
630
|
+
#options;
|
|
631
|
+
/**
|
|
632
|
+
* Creates a new retry policy.
|
|
633
|
+
*
|
|
634
|
+
* @param options - The retry configuration options.
|
|
635
|
+
*/
|
|
636
|
+
constructor(options) {
|
|
637
|
+
super();
|
|
638
|
+
this.#options = { ...options, retryable: options.retryable ?? defaultRetryable };
|
|
639
|
+
}
|
|
640
|
+
/**
|
|
641
|
+
* Executes a function with retry logic applied.
|
|
642
|
+
*
|
|
643
|
+
* @template TResult - The return type of the function.
|
|
644
|
+
* @template TArgs - The argument types of the function.
|
|
645
|
+
* @param cancellation_token - Token for cancelling the operation.
|
|
646
|
+
* @param fn - The function to execute.
|
|
647
|
+
* @param args - Arguments to pass to the function.
|
|
648
|
+
* @returns A promise that resolves to the policy result.
|
|
649
|
+
*/
|
|
650
|
+
async execute(cancellation_token, fn, ...args) {
|
|
651
|
+
let attempt = 1;
|
|
652
|
+
let backoff_ms = this.#options.initial_backoff_ms;
|
|
653
|
+
const metadata = {
|
|
654
|
+
policy: _RetryPolicy.name,
|
|
655
|
+
attempts: 0,
|
|
656
|
+
...this.#options
|
|
657
|
+
};
|
|
658
|
+
while (true) {
|
|
659
|
+
cancellation_token.throw_if_cancellation_requested();
|
|
660
|
+
try {
|
|
661
|
+
const value = await Promise.race([fn(...args), cancellation_token.await_cancellation()]);
|
|
662
|
+
metadata.attempts = attempt;
|
|
663
|
+
return {
|
|
664
|
+
result: true,
|
|
665
|
+
value,
|
|
666
|
+
metadata
|
|
667
|
+
};
|
|
668
|
+
} catch (error) {
|
|
669
|
+
metadata.attempts = attempt;
|
|
670
|
+
const result = {
|
|
671
|
+
result: false,
|
|
672
|
+
error,
|
|
673
|
+
metadata
|
|
674
|
+
};
|
|
675
|
+
if (attempt >= this.#options.max_attempts || !this.#options.retryable(result)) {
|
|
676
|
+
return result;
|
|
677
|
+
}
|
|
678
|
+
await sleep(backoff_ms, cancellation_token);
|
|
679
|
+
metadata.lastError = error;
|
|
680
|
+
metadata.lastBackoffMs = backoff_ms;
|
|
681
|
+
backoff_ms *= this.#options.backoff_factor;
|
|
682
|
+
attempt++;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
};
|
|
687
|
+
|
|
688
|
+
// src/policies/default-policy.ts
|
|
689
|
+
function getDefaultPolicy() {
|
|
690
|
+
return new CompositePolicy(
|
|
691
|
+
new RetryPolicy({
|
|
692
|
+
max_attempts: 3,
|
|
693
|
+
initial_backoff_ms: 1e3,
|
|
694
|
+
backoff_factor: 2
|
|
695
|
+
})
|
|
696
|
+
);
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// src/conversations/stream-listener.ts
|
|
700
|
+
var StreamListener = class {
|
|
701
|
+
#listener;
|
|
702
|
+
#is_disposed = false;
|
|
703
|
+
/**
|
|
704
|
+
* Creates a new stream listener.
|
|
705
|
+
*
|
|
706
|
+
* @param listener - The callback to invoke with stream events.
|
|
707
|
+
*/
|
|
708
|
+
constructor(listener) {
|
|
709
|
+
this.#listener = listener;
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* Disposes the stream listener.
|
|
713
|
+
*
|
|
714
|
+
* This method is called automatically when using the `using` keyword.
|
|
715
|
+
*/
|
|
716
|
+
[Symbol.dispose]() {
|
|
717
|
+
this.#is_disposed = true;
|
|
718
|
+
}
|
|
719
|
+
/**
|
|
720
|
+
* Creates a StreamReceiver that forwards all events to the listener.
|
|
721
|
+
*
|
|
722
|
+
* @returns A new stream receiver instance.
|
|
723
|
+
*/
|
|
724
|
+
create_receiver() {
|
|
725
|
+
const receiver = {
|
|
726
|
+
start_message: (...args) => this.#receive({ event: "start_message", payload: args }),
|
|
727
|
+
update_message: (...args) => this.#receive({ event: "update_message", payload: args }),
|
|
728
|
+
complete_message: (...args) => this.#receive({ event: "complete_message", payload: args }),
|
|
729
|
+
start_content: (...args) => this.#receive({ event: "start_content", payload: args }),
|
|
730
|
+
update_content: (...args) => this.#receive({ event: "update_content", payload: args }),
|
|
731
|
+
complete_content: (...args) => this.#receive({ event: "complete_content", payload: args }),
|
|
732
|
+
error: (...args) => this.#receive({ event: "error", payload: args }),
|
|
733
|
+
before_request: (...args) => this.#receive({ event: "before_request", payload: args }),
|
|
734
|
+
request_raw: (...args) => this.#receive({ event: "request_raw", payload: args }),
|
|
735
|
+
response_raw: (...args) => this.#receive({ event: "response_raw", payload: args }),
|
|
736
|
+
stream_raw: (...args) => this.#receive({ event: "stream_raw", payload: args }),
|
|
737
|
+
cancel: (...args) => this.#receive({ event: "cancel", payload: args })
|
|
738
|
+
};
|
|
739
|
+
return receiver;
|
|
740
|
+
}
|
|
741
|
+
#receive = (payload) => {
|
|
742
|
+
if (this.#is_disposed) {
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
try {
|
|
746
|
+
const result = this.#listener(payload);
|
|
747
|
+
if (result && typeof result === "object" && "catch" in result) {
|
|
748
|
+
result.catch((error) => {
|
|
749
|
+
if (!this.#is_disposed && payload.event !== "error") {
|
|
750
|
+
this.#listener({ event: "error", payload: [error] });
|
|
751
|
+
}
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
} catch (error) {
|
|
755
|
+
if (!this.#is_disposed && payload.event !== "error") {
|
|
756
|
+
this.#listener({ event: "error", payload: [error] });
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
};
|
|
760
|
+
};
|
|
761
|
+
|
|
762
|
+
// src/conversations/conversation.ts
|
|
763
|
+
var Conversation = class _Conversation {
|
|
764
|
+
#event_emitter;
|
|
765
|
+
#provider;
|
|
766
|
+
#policy;
|
|
767
|
+
#context_transformer;
|
|
768
|
+
#summarization_strategy;
|
|
769
|
+
#id = import_node_crypto.default.randomUUID();
|
|
770
|
+
#state = "idle";
|
|
771
|
+
#system;
|
|
772
|
+
#toolkit = [];
|
|
773
|
+
#messages = [];
|
|
774
|
+
#checkpoint_state;
|
|
775
|
+
#is_checkpoint = false;
|
|
776
|
+
/**
|
|
777
|
+
* Creates a new conversation instance.
|
|
778
|
+
*
|
|
779
|
+
* @param provider - The model provider for executing requests.
|
|
780
|
+
* @param policy - The policy for controlling request execution (default: retry + rate limiting).
|
|
781
|
+
* @param context_transformer - Transformer for modifying messages before sending (default: ToolContextTransformer + CheckpointContextTransformer).
|
|
782
|
+
* @param summarization_strategy - Strategy for generating checkpoint summaries (default: DefaultSummarizationStrategy).
|
|
783
|
+
*/
|
|
784
|
+
constructor(provider, policy = getDefaultPolicy(), context_transformer = new CompositeContextTransformer([
|
|
785
|
+
new ToolContextTransformer(),
|
|
786
|
+
new CheckpointContextTransformer()
|
|
787
|
+
]), summarization_strategy = new DefaultSummarizationStrategy()) {
|
|
788
|
+
this.#event_emitter = new import_node_events2.default();
|
|
789
|
+
this.#event_emitter.setMaxListeners(0);
|
|
790
|
+
this.#provider = provider;
|
|
791
|
+
this.#policy = policy;
|
|
792
|
+
this.#context_transformer = context_transformer;
|
|
793
|
+
this.#summarization_strategy = summarization_strategy;
|
|
794
|
+
}
|
|
795
|
+
/**
|
|
796
|
+
* The unique identifier for this conversation.
|
|
797
|
+
*/
|
|
798
|
+
get id() {
|
|
799
|
+
return this.#id;
|
|
800
|
+
}
|
|
801
|
+
set id(value) {
|
|
802
|
+
this.#id = value;
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* The current state of the conversation.
|
|
806
|
+
*/
|
|
807
|
+
get state() {
|
|
808
|
+
return this.#state;
|
|
809
|
+
}
|
|
810
|
+
/**
|
|
811
|
+
* The system prompt for this conversation.
|
|
812
|
+
*/
|
|
813
|
+
get system() {
|
|
814
|
+
return this.#system;
|
|
815
|
+
}
|
|
816
|
+
set system(value) {
|
|
817
|
+
this.#system = value;
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* The collection of tools available to the model in this conversation.
|
|
821
|
+
*/
|
|
822
|
+
get toolkit() {
|
|
823
|
+
return Object.freeze([...this.#toolkit]);
|
|
824
|
+
}
|
|
825
|
+
set toolkit(value) {
|
|
826
|
+
if (this.#state === "disposed") {
|
|
827
|
+
throw new Error("invalid state");
|
|
828
|
+
}
|
|
829
|
+
this.#toolkit = [...value];
|
|
830
|
+
}
|
|
831
|
+
/**
|
|
832
|
+
* The conversation message history.
|
|
833
|
+
*/
|
|
834
|
+
get messages() {
|
|
835
|
+
return Object.freeze([...this.#messages.map((m) => Object.freeze({ ...m }))]);
|
|
836
|
+
}
|
|
837
|
+
/**
|
|
838
|
+
* The most recent checkpoint state, containing the summary text and the ID
|
|
839
|
+
* of the last message included in the checkpoint. Available after a
|
|
840
|
+
* successful call to `checkpoint()`. Used by CheckpointContextTransformer
|
|
841
|
+
* to replace pre-checkpoint messages with the summary on subsequent prompts.
|
|
842
|
+
*/
|
|
843
|
+
get checkpoint_state() {
|
|
844
|
+
return this.#checkpoint_state ? Object.freeze({ ...this.#checkpoint_state }) : void 0;
|
|
845
|
+
}
|
|
846
|
+
set checkpoint_state(value) {
|
|
847
|
+
this.#checkpoint_state = value;
|
|
848
|
+
}
|
|
849
|
+
/**
|
|
850
|
+
* True when this conversation is an ephemeral child spawned to generate a
|
|
851
|
+
* checkpoint summary. Checkpoint sessions are short-lived, receive the
|
|
852
|
+
* conversation history as a summarization prompt, and are disposed after
|
|
853
|
+
* the summary is produced.
|
|
854
|
+
*/
|
|
855
|
+
get is_checkpoint() {
|
|
856
|
+
return this.#is_checkpoint;
|
|
857
|
+
}
|
|
858
|
+
/**
|
|
859
|
+
* Disposes the conversation and releases all resources.
|
|
860
|
+
*
|
|
861
|
+
* This method is called automatically when using the `using` keyword.
|
|
862
|
+
* Emits a dispose event and transitions to the disposed state.
|
|
863
|
+
*/
|
|
864
|
+
[Symbol.dispose]() {
|
|
865
|
+
if (this.state === "disposed") {
|
|
866
|
+
throw new Error("invalid state");
|
|
867
|
+
}
|
|
868
|
+
const previous = this.#state;
|
|
869
|
+
this.#event_emitter.emit("dispose", this);
|
|
870
|
+
this.#state = "disposed";
|
|
871
|
+
this.#event_emitter.emit("state_change", { current: this.#state, previous }, this);
|
|
872
|
+
this.#event_emitter.removeAllListeners();
|
|
873
|
+
}
|
|
874
|
+
/**
|
|
875
|
+
* Sends a text prompt to the model.
|
|
876
|
+
*
|
|
877
|
+
* @param prompt - The text prompt to send.
|
|
878
|
+
* @returns A promise that resolves with the request data, or undefined if cancelled.
|
|
879
|
+
*/
|
|
880
|
+
async prompt(prompt) {
|
|
881
|
+
return await this.send_message([
|
|
882
|
+
{
|
|
883
|
+
timestamp: Date.now(),
|
|
884
|
+
type: "text",
|
|
885
|
+
text: prompt
|
|
886
|
+
}
|
|
887
|
+
]);
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
890
|
+
* Sends a message with custom content to the model.
|
|
891
|
+
*
|
|
892
|
+
* @param contents - The content blocks to include in the message.
|
|
893
|
+
* @returns A promise that resolves with the request data, or undefined if cancelled.
|
|
894
|
+
*/
|
|
895
|
+
async send_message(contents) {
|
|
896
|
+
if (this.state !== "idle") {
|
|
897
|
+
throw new Error("invalid state");
|
|
898
|
+
}
|
|
899
|
+
let prompt_message = {
|
|
900
|
+
timestamp: Date.now(),
|
|
901
|
+
role: "user",
|
|
902
|
+
content: contents.map((c) => ({
|
|
903
|
+
timestamp: Date.now(),
|
|
904
|
+
...c
|
|
905
|
+
})).map((c) => ({
|
|
906
|
+
...c,
|
|
907
|
+
id: c.id ?? import_node_crypto.default.createHash("sha1").update(JSON.stringify(c)).digest("hex")
|
|
908
|
+
}))
|
|
909
|
+
};
|
|
910
|
+
prompt_message = {
|
|
911
|
+
...prompt_message,
|
|
912
|
+
id: prompt_message.id ?? import_node_crypto.default.createHash("sha1").update(JSON.stringify(prompt_message)).digest("hex")
|
|
913
|
+
};
|
|
914
|
+
let request_id;
|
|
915
|
+
let result;
|
|
916
|
+
let policy_result;
|
|
917
|
+
try {
|
|
918
|
+
var _stack = [];
|
|
919
|
+
try {
|
|
920
|
+
const stream_listener = new StreamListener(async ({ event, payload }) => {
|
|
921
|
+
switch (event) {
|
|
922
|
+
case "start_message": {
|
|
923
|
+
const [{ message, usage }] = payload;
|
|
924
|
+
this.#set_state("streaming_response");
|
|
925
|
+
this.#event_emitter.emit("message_start", { request_id, usage, message }, this);
|
|
926
|
+
break;
|
|
927
|
+
}
|
|
928
|
+
case "update_message": {
|
|
929
|
+
const [{ message, usage }] = payload;
|
|
930
|
+
this.#event_emitter.emit("message_update", { request_id, usage, message }, this);
|
|
931
|
+
break;
|
|
932
|
+
}
|
|
933
|
+
case "complete_message": {
|
|
934
|
+
const [data] = payload;
|
|
935
|
+
let message = identify_message(data.message);
|
|
936
|
+
for (const t of this.#provider.context_transformers ?? []) {
|
|
937
|
+
if (t.transform_completion) {
|
|
938
|
+
message = await t.transform_completion(message);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
message = await this.#context_transformer.transform_completion(message);
|
|
942
|
+
const has_tool_calls = message.content.some((c) => c.type === "tool");
|
|
943
|
+
const stop_reason = has_tool_calls ? "tool_use" : data.stop_reason;
|
|
944
|
+
this.#set_state("idle");
|
|
945
|
+
this.#messages.push(prompt_message, message);
|
|
946
|
+
this.#event_emitter.emit(
|
|
947
|
+
"message_complete",
|
|
948
|
+
{ request_id, ...data, message, stop_reason },
|
|
949
|
+
this
|
|
950
|
+
);
|
|
951
|
+
break;
|
|
952
|
+
}
|
|
953
|
+
case "start_content": {
|
|
954
|
+
const [data] = payload;
|
|
955
|
+
this.#event_emitter.emit("content_start", { request_id, ...data }, this);
|
|
956
|
+
break;
|
|
957
|
+
}
|
|
958
|
+
case "update_content": {
|
|
959
|
+
const [data] = payload;
|
|
960
|
+
this.#event_emitter.emit("content_update", { request_id, ...data }, this);
|
|
961
|
+
break;
|
|
962
|
+
}
|
|
963
|
+
case "complete_content": {
|
|
964
|
+
const [data] = payload;
|
|
965
|
+
this.#event_emitter.emit("content_complete", { request_id, ...data }, this);
|
|
966
|
+
break;
|
|
967
|
+
}
|
|
968
|
+
case "error": {
|
|
969
|
+
stream_listener[Symbol.dispose]();
|
|
970
|
+
const [error] = payload;
|
|
971
|
+
if (this.state !== "disposed") {
|
|
972
|
+
this.#set_state("idle");
|
|
973
|
+
}
|
|
974
|
+
this.#event_emitter.emit(
|
|
975
|
+
"request_error",
|
|
976
|
+
{ request_id, message: prompt_message, error },
|
|
977
|
+
{},
|
|
978
|
+
this
|
|
979
|
+
);
|
|
980
|
+
break;
|
|
981
|
+
}
|
|
982
|
+
case "before_request": {
|
|
983
|
+
const [data] = payload;
|
|
984
|
+
this.#event_emitter.emit("before_request", { data }, this);
|
|
985
|
+
break;
|
|
986
|
+
}
|
|
987
|
+
case "request_raw": {
|
|
988
|
+
const [data] = payload;
|
|
989
|
+
this.#event_emitter.emit("raw_request", { request_id, data }, this);
|
|
990
|
+
break;
|
|
991
|
+
}
|
|
992
|
+
case "response_raw": {
|
|
993
|
+
const [data] = payload;
|
|
994
|
+
this.#event_emitter.emit("raw_response", { request_id, data }, this);
|
|
995
|
+
break;
|
|
996
|
+
}
|
|
997
|
+
case "stream_raw": {
|
|
998
|
+
const [data] = payload;
|
|
999
|
+
this.#event_emitter.emit("raw_stream", { request_id, data }, this);
|
|
1000
|
+
break;
|
|
1001
|
+
}
|
|
1002
|
+
case "cancel": {
|
|
1003
|
+
stream_listener[Symbol.dispose]();
|
|
1004
|
+
if (this.state !== "disposed") {
|
|
1005
|
+
this.#set_state("idle");
|
|
1006
|
+
}
|
|
1007
|
+
break;
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
});
|
|
1011
|
+
const source = __using(_stack, new CancellationTokenSource());
|
|
1012
|
+
const on_state_change = ({ current }) => {
|
|
1013
|
+
if (current === "stopping") {
|
|
1014
|
+
if (!source.is_cancelled) {
|
|
1015
|
+
source.cancel();
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
if (current === "idle" || current === "disposed") {
|
|
1019
|
+
this.off("state_change", on_state_change);
|
|
1020
|
+
}
|
|
1021
|
+
};
|
|
1022
|
+
this.on("state_change", on_state_change);
|
|
1023
|
+
const transformed_messages = await this.#get_prompt_context([
|
|
1024
|
+
...this.#messages,
|
|
1025
|
+
prompt_message
|
|
1026
|
+
]);
|
|
1027
|
+
const tool_definitions = this.#toolkit.map((t) => t.get_definition());
|
|
1028
|
+
const result_promise = this.#policy.execute(source.token, async () => {
|
|
1029
|
+
await this.#provider.execute_request(
|
|
1030
|
+
{
|
|
1031
|
+
messages: transformed_messages,
|
|
1032
|
+
tools: tool_definitions,
|
|
1033
|
+
system: this.#system
|
|
1034
|
+
},
|
|
1035
|
+
stream_listener.create_receiver(),
|
|
1036
|
+
source.token
|
|
1037
|
+
);
|
|
1038
|
+
return { message: prompt_message };
|
|
1039
|
+
});
|
|
1040
|
+
this.#event_emitter.emit("prompt_send", { message: prompt_message }, this);
|
|
1041
|
+
this.#set_state("awaiting_response");
|
|
1042
|
+
policy_result = await Promise.race([
|
|
1043
|
+
result_promise,
|
|
1044
|
+
new Promise((resolve) => {
|
|
1045
|
+
const on_state_change2 = ({ current }) => {
|
|
1046
|
+
if (current === "stopping") {
|
|
1047
|
+
stream_listener[Symbol.dispose]();
|
|
1048
|
+
resolve(void 0);
|
|
1049
|
+
} else if (current === "disposed") {
|
|
1050
|
+
stream_listener[Symbol.dispose]();
|
|
1051
|
+
resolve(void 0);
|
|
1052
|
+
} else if (current === "streaming_response") {
|
|
1053
|
+
this.off("state_change", on_state_change2);
|
|
1054
|
+
}
|
|
1055
|
+
};
|
|
1056
|
+
this.on("state_change", on_state_change2);
|
|
1057
|
+
})
|
|
1058
|
+
]);
|
|
1059
|
+
request_id = request_id ?? import_node_crypto.default.randomUUID();
|
|
1060
|
+
result = {
|
|
1061
|
+
message: prompt_message,
|
|
1062
|
+
request_id
|
|
1063
|
+
};
|
|
1064
|
+
if (policy_result) {
|
|
1065
|
+
if (!policy_result.result) {
|
|
1066
|
+
throw policy_result.error;
|
|
1067
|
+
}
|
|
1068
|
+
result = { ...result, ...policy_result.value };
|
|
1069
|
+
this.#event_emitter.emit("request_success", result, policy_result.metadata ?? {}, this);
|
|
1070
|
+
} else {
|
|
1071
|
+
if (this.#state !== "disposed") {
|
|
1072
|
+
this.#set_state("idle");
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
} catch (_) {
|
|
1076
|
+
var _error = _, _hasError = true;
|
|
1077
|
+
} finally {
|
|
1078
|
+
__callDispose(_stack, _error, _hasError);
|
|
1079
|
+
}
|
|
1080
|
+
} catch (error) {
|
|
1081
|
+
this.#event_emitter.emit(
|
|
1082
|
+
"request_error",
|
|
1083
|
+
{ request_id, message: prompt_message, error },
|
|
1084
|
+
{},
|
|
1085
|
+
this
|
|
1086
|
+
);
|
|
1087
|
+
if (this.#state !== "disposed") {
|
|
1088
|
+
this.#set_state("idle");
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
return result;
|
|
1092
|
+
}
|
|
1093
|
+
/**
|
|
1094
|
+
* Registers a one-time event listener.
|
|
1095
|
+
*
|
|
1096
|
+
* @template E - The event name type.
|
|
1097
|
+
* @param event_name - The name of the event to listen for.
|
|
1098
|
+
* @param listener - The callback function to invoke when the event occurs.
|
|
1099
|
+
*/
|
|
1100
|
+
once(event_name, listener) {
|
|
1101
|
+
this.#event_emitter.once(event_name, listener);
|
|
1102
|
+
}
|
|
1103
|
+
/**
|
|
1104
|
+
* Registers a persistent event listener.
|
|
1105
|
+
*
|
|
1106
|
+
* @template E - The event name type.
|
|
1107
|
+
* @param event_name - The name of the event to listen for.
|
|
1108
|
+
* @param listener - The callback function to invoke when the event occurs.
|
|
1109
|
+
*/
|
|
1110
|
+
on(event_name, listener) {
|
|
1111
|
+
this.#event_emitter.on(event_name, listener);
|
|
1112
|
+
}
|
|
1113
|
+
/**
|
|
1114
|
+
* Removes an event listener.
|
|
1115
|
+
*
|
|
1116
|
+
* @template E - The event name type.
|
|
1117
|
+
* @param event_name - The name of the event.
|
|
1118
|
+
* @param listener - The callback function to remove.
|
|
1119
|
+
*/
|
|
1120
|
+
off(event_name, listener) {
|
|
1121
|
+
this.#event_emitter.off(event_name, listener);
|
|
1122
|
+
}
|
|
1123
|
+
/**
|
|
1124
|
+
* Cancels an in-progress model response.
|
|
1125
|
+
*
|
|
1126
|
+
* Transitions the conversation to the "stopping" state, which triggers
|
|
1127
|
+
* cancellation of the underlying request.
|
|
1128
|
+
*/
|
|
1129
|
+
cancel_response() {
|
|
1130
|
+
if (this.state === "idle" || this.state === "disposed") {
|
|
1131
|
+
throw new Error("invalid state");
|
|
1132
|
+
}
|
|
1133
|
+
this.#set_state("stopping");
|
|
1134
|
+
}
|
|
1135
|
+
/**
|
|
1136
|
+
* Clears all messages from the conversation history.
|
|
1137
|
+
*
|
|
1138
|
+
* Can only be called when the conversation is idle.
|
|
1139
|
+
*/
|
|
1140
|
+
clear() {
|
|
1141
|
+
if (this.state !== "idle") {
|
|
1142
|
+
throw new Error("invalid state");
|
|
1143
|
+
}
|
|
1144
|
+
this.#messages = [];
|
|
1145
|
+
this.#set_state("idle");
|
|
1146
|
+
}
|
|
1147
|
+
/**
|
|
1148
|
+
* Loads messages into the conversation history.
|
|
1149
|
+
*
|
|
1150
|
+
* @param messages - The messages to load.
|
|
1151
|
+
*/
|
|
1152
|
+
load(messages) {
|
|
1153
|
+
if (this.state !== "idle") {
|
|
1154
|
+
throw new Error("invalid state");
|
|
1155
|
+
}
|
|
1156
|
+
this.#messages = [...messages.map((m) => Object.freeze({ ...m }))];
|
|
1157
|
+
}
|
|
1158
|
+
/**
|
|
1159
|
+
* Creates a child conversation that inherits configuration from this conversation.
|
|
1160
|
+
*
|
|
1161
|
+
* Child events are propagated to the parent as "child_event" events.
|
|
1162
|
+
*
|
|
1163
|
+
* @param fork_session - Whether to copy the current message history to the child.
|
|
1164
|
+
* @param id - Optional custom ID for the child conversation.
|
|
1165
|
+
* @param system_prompt - Optional system prompt override for the child.
|
|
1166
|
+
* @param is_checkpoint - Whether this child is a checkpoint summarization session.
|
|
1167
|
+
* @returns The newly created child conversation.
|
|
1168
|
+
*/
|
|
1169
|
+
spawn_child(fork_session, id, system_prompt, is_checkpoint) {
|
|
1170
|
+
const child = new _Conversation(
|
|
1171
|
+
this.#provider.clone(),
|
|
1172
|
+
this.#policy,
|
|
1173
|
+
this.#context_transformer,
|
|
1174
|
+
this.#summarization_strategy
|
|
1175
|
+
);
|
|
1176
|
+
if (id) {
|
|
1177
|
+
child.id = id;
|
|
1178
|
+
}
|
|
1179
|
+
if (system_prompt) {
|
|
1180
|
+
child.system = system_prompt;
|
|
1181
|
+
}
|
|
1182
|
+
if (fork_session) {
|
|
1183
|
+
child.load(this.#messages);
|
|
1184
|
+
}
|
|
1185
|
+
if (is_checkpoint) {
|
|
1186
|
+
child.#is_checkpoint = true;
|
|
1187
|
+
}
|
|
1188
|
+
this.#attach_child_event(child, "state_change");
|
|
1189
|
+
this.#attach_child_event(child, "checkpoint_begin");
|
|
1190
|
+
this.#attach_child_event(child, "checkpoint_complete");
|
|
1191
|
+
this.#attach_child_event(child, "prompt_send");
|
|
1192
|
+
this.#attach_child_event(child, "message_start");
|
|
1193
|
+
this.#attach_child_event(child, "message_update");
|
|
1194
|
+
this.#attach_child_event(child, "message_complete");
|
|
1195
|
+
this.#attach_child_event(child, "content_start");
|
|
1196
|
+
this.#attach_child_event(child, "content_update");
|
|
1197
|
+
this.#attach_child_event(child, "content_complete");
|
|
1198
|
+
this.#attach_child_event(child, "request_success");
|
|
1199
|
+
this.#attach_child_event(child, "request_error");
|
|
1200
|
+
this.#attach_child_event(child, "before_request");
|
|
1201
|
+
this.#attach_child_event(child, "raw_request");
|
|
1202
|
+
this.#attach_child_event(child, "raw_response");
|
|
1203
|
+
this.#attach_child_event(child, "raw_stream");
|
|
1204
|
+
this.#attach_child_event(child, "create_child");
|
|
1205
|
+
this.#attach_child_event(child, "child_event");
|
|
1206
|
+
this.#attach_child_event(child, "lifecycle_error");
|
|
1207
|
+
this.#attach_child_event(child, "dispose");
|
|
1208
|
+
this.#event_emitter.emit("create_child", child, this);
|
|
1209
|
+
return child;
|
|
1210
|
+
}
|
|
1211
|
+
/**
|
|
1212
|
+
* Creates a checkpoint summarizing the conversation so far.
|
|
1213
|
+
*
|
|
1214
|
+
* Spawns an ephemeral child conversation, sends the checkpoint prompt,
|
|
1215
|
+
* and stores the resulting summary as the new checkpoint state.
|
|
1216
|
+
*
|
|
1217
|
+
* Requires a CheckpointContextTransformer (or composite that includes one)
|
|
1218
|
+
* in the context transformer pipeline for the checkpoint state to take effect
|
|
1219
|
+
* on subsequent prompts.
|
|
1220
|
+
*
|
|
1221
|
+
* @param config - Optional checkpoint configuration.
|
|
1222
|
+
* @returns The new checkpoint state.
|
|
1223
|
+
*/
|
|
1224
|
+
async checkpoint(config) {
|
|
1225
|
+
if (this.state !== "idle") {
|
|
1226
|
+
throw new Error("invalid state");
|
|
1227
|
+
}
|
|
1228
|
+
if (this.#messages.length === 0) {
|
|
1229
|
+
throw new Error("no messages to checkpoint");
|
|
1230
|
+
}
|
|
1231
|
+
const messages_since = this.#get_messages_since_checkpoint();
|
|
1232
|
+
if (messages_since.length === 0) {
|
|
1233
|
+
throw new Error("no new messages since last checkpoint");
|
|
1234
|
+
}
|
|
1235
|
+
const prompt_messages = this.#summarization_strategy.build_prompt({
|
|
1236
|
+
session_id: this.#id,
|
|
1237
|
+
messages: messages_since,
|
|
1238
|
+
previous_checkpoint: this.#checkpoint_state,
|
|
1239
|
+
system: this.#system,
|
|
1240
|
+
context: config?.context
|
|
1241
|
+
});
|
|
1242
|
+
const child = this.spawn_child(false, void 0, void 0, true);
|
|
1243
|
+
this.#event_emitter.emit("checkpoint_begin", child, this);
|
|
1244
|
+
try {
|
|
1245
|
+
if (prompt_messages.length > 1) {
|
|
1246
|
+
child.load(prompt_messages.slice(0, -1));
|
|
1247
|
+
}
|
|
1248
|
+
const last = prompt_messages[prompt_messages.length - 1];
|
|
1249
|
+
if (last.role !== "user") {
|
|
1250
|
+
throw new Error("checkpoint prompt strategy must return a user message last");
|
|
1251
|
+
}
|
|
1252
|
+
const response_promise = new Promise((resolve, reject) => {
|
|
1253
|
+
child.once("message_complete", ({ message }) => resolve(message));
|
|
1254
|
+
child.once(
|
|
1255
|
+
"request_error",
|
|
1256
|
+
({ error }) => reject(error instanceof Error ? error : new Error(String(error)))
|
|
1257
|
+
);
|
|
1258
|
+
});
|
|
1259
|
+
await child.send_message(last.content);
|
|
1260
|
+
const response = await response_promise;
|
|
1261
|
+
const summary = response.content.filter((c) => c.type === "text").map((c) => c.text).join("\n");
|
|
1262
|
+
if (!summary) {
|
|
1263
|
+
throw new Error("checkpoint produced an empty summary");
|
|
1264
|
+
}
|
|
1265
|
+
const boundary = this.#messages[this.#messages.length - 1];
|
|
1266
|
+
if (!boundary.id) {
|
|
1267
|
+
throw new Error("boundary message has no id");
|
|
1268
|
+
}
|
|
1269
|
+
const state = {
|
|
1270
|
+
message_id: boundary.id,
|
|
1271
|
+
summary
|
|
1272
|
+
};
|
|
1273
|
+
this.#checkpoint_state = state;
|
|
1274
|
+
this.#event_emitter.emit("checkpoint_complete", state, this);
|
|
1275
|
+
return state;
|
|
1276
|
+
} finally {
|
|
1277
|
+
if (child.state !== "disposed") {
|
|
1278
|
+
child[Symbol.dispose]();
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
#get_messages_since_checkpoint() {
|
|
1283
|
+
const checkpointState = this.#checkpoint_state;
|
|
1284
|
+
if (!checkpointState) {
|
|
1285
|
+
return [...this.#messages];
|
|
1286
|
+
}
|
|
1287
|
+
const idx = this.#messages.findIndex((m) => m.id === checkpointState.message_id);
|
|
1288
|
+
if (idx === -1) {
|
|
1289
|
+
return [...this.#messages];
|
|
1290
|
+
}
|
|
1291
|
+
return this.#messages.slice(idx + 1);
|
|
1292
|
+
}
|
|
1293
|
+
#set_state(value) {
|
|
1294
|
+
if (this.#state === "disposed" || value === "disposed") {
|
|
1295
|
+
throw new Error("invalid state");
|
|
1296
|
+
}
|
|
1297
|
+
if (this.#state !== value) {
|
|
1298
|
+
const previous = this.#state;
|
|
1299
|
+
this.#state = value;
|
|
1300
|
+
this.#event_emitter.emit(
|
|
1301
|
+
"state_change",
|
|
1302
|
+
{
|
|
1303
|
+
current: value,
|
|
1304
|
+
previous
|
|
1305
|
+
},
|
|
1306
|
+
this
|
|
1307
|
+
);
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
async #get_prompt_context(messages) {
|
|
1311
|
+
for (const t of this.#provider.context_transformers ?? []) {
|
|
1312
|
+
if (t.transform_prompt) {
|
|
1313
|
+
messages = await t.transform_prompt(messages);
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
const context = { checkpoint: this.#checkpoint_state };
|
|
1317
|
+
return await this.#context_transformer.transform_prompt(messages, context);
|
|
1318
|
+
}
|
|
1319
|
+
#attach_child_event(child, event_name) {
|
|
1320
|
+
const handler = (...args) => {
|
|
1321
|
+
this.#event_emitter.emit(
|
|
1322
|
+
"child_event",
|
|
1323
|
+
{
|
|
1324
|
+
event_name,
|
|
1325
|
+
event_args: args
|
|
1326
|
+
},
|
|
1327
|
+
this
|
|
1328
|
+
);
|
|
1329
|
+
};
|
|
1330
|
+
child.on(event_name, handler);
|
|
1331
|
+
child.once("dispose", () => (0, import_promises.setImmediate)(() => child.off(event_name, handler)));
|
|
1332
|
+
}
|
|
1333
|
+
};
|
|
1334
|
+
function identify_message(message) {
|
|
1335
|
+
const content = message.content?.map(identify_content);
|
|
1336
|
+
const id = message.id ?? import_node_crypto.default.createHash("sha1").update(JSON.stringify({ ...message, content })).digest("hex");
|
|
1337
|
+
return {
|
|
1338
|
+
timestamp: Date.now(),
|
|
1339
|
+
...message,
|
|
1340
|
+
content,
|
|
1341
|
+
id
|
|
1342
|
+
};
|
|
1343
|
+
}
|
|
1344
|
+
function identify_content(content) {
|
|
1345
|
+
const id = content.id ?? import_node_crypto.default.createHash("sha1").update(JSON.stringify(content)).digest("hex");
|
|
1346
|
+
return {
|
|
1347
|
+
timestamp: Date.now(),
|
|
1348
|
+
...content,
|
|
1349
|
+
id
|
|
1350
|
+
};
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
// src/conversations/token-tracker.ts
|
|
1354
|
+
var import_node_events3 = require("events");
|
|
1355
|
+
var TokenTracker = class {
|
|
1356
|
+
#event_emitter = new import_node_events3.EventEmitter();
|
|
1357
|
+
#conversation;
|
|
1358
|
+
#stats = {
|
|
1359
|
+
last_request: {
|
|
1360
|
+
input: 0,
|
|
1361
|
+
output: 0
|
|
1362
|
+
},
|
|
1363
|
+
total: {
|
|
1364
|
+
input: 0,
|
|
1365
|
+
output: 0
|
|
1366
|
+
}
|
|
1367
|
+
};
|
|
1368
|
+
/**
|
|
1369
|
+
* Creates a new token tracker.
|
|
1370
|
+
*
|
|
1371
|
+
* @param conversation - The conversation to track.
|
|
1372
|
+
*/
|
|
1373
|
+
constructor(conversation) {
|
|
1374
|
+
this.#conversation = conversation;
|
|
1375
|
+
conversation.on("message_complete", this.#handle_event_with_usage);
|
|
1376
|
+
conversation.on("child_event", this.#handle_child_event);
|
|
1377
|
+
}
|
|
1378
|
+
/**
|
|
1379
|
+
* Disposes the token tracker and removes event listeners.
|
|
1380
|
+
*
|
|
1381
|
+
* This method is called automatically when using the `using` keyword.
|
|
1382
|
+
*/
|
|
1383
|
+
[Symbol.dispose]() {
|
|
1384
|
+
this.#conversation.off("message_complete", this.#handle_event_with_usage);
|
|
1385
|
+
this.#conversation.off("child_event", this.#handle_child_event);
|
|
1386
|
+
this.#event_emitter.removeAllListeners();
|
|
1387
|
+
}
|
|
1388
|
+
/**
|
|
1389
|
+
* The current token statistics.
|
|
1390
|
+
*/
|
|
1391
|
+
get stats() {
|
|
1392
|
+
return Object.freeze({ ...this.#stats });
|
|
1393
|
+
}
|
|
1394
|
+
/**
|
|
1395
|
+
* Registers an event listener for statistics updates.
|
|
1396
|
+
*
|
|
1397
|
+
* @param event - The event name (always "stats_update").
|
|
1398
|
+
* @param listener - The callback to invoke when statistics update.
|
|
1399
|
+
*/
|
|
1400
|
+
on(event, listener) {
|
|
1401
|
+
this.#event_emitter.on(event, listener);
|
|
1402
|
+
}
|
|
1403
|
+
/**
|
|
1404
|
+
* Removes an event listener for statistics updates.
|
|
1405
|
+
*
|
|
1406
|
+
* @param event - The event name (always "stats_update").
|
|
1407
|
+
* @param listener - The callback to remove.
|
|
1408
|
+
*/
|
|
1409
|
+
off(event, listener) {
|
|
1410
|
+
this.#event_emitter.off(event, listener);
|
|
1411
|
+
}
|
|
1412
|
+
#handle_event_with_usage = ({ usage }) => {
|
|
1413
|
+
this.#process_usage(usage);
|
|
1414
|
+
};
|
|
1415
|
+
#handle_child_event = (event_data) => {
|
|
1416
|
+
const { event_name, event_args } = event_data;
|
|
1417
|
+
if (event_name === "message_complete") {
|
|
1418
|
+
const [event_data2] = event_args;
|
|
1419
|
+
if (event_data2 && "usage" in event_data2) {
|
|
1420
|
+
this.#process_usage(event_data2.usage);
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
if (event_name === "child_event") {
|
|
1424
|
+
const [child_event_data] = event_args;
|
|
1425
|
+
this.#handle_child_event(child_event_data);
|
|
1426
|
+
}
|
|
1427
|
+
};
|
|
1428
|
+
#process_usage = (usage) => {
|
|
1429
|
+
if (usage) {
|
|
1430
|
+
const input_tokens = usage.input_tokens || 0;
|
|
1431
|
+
const output_tokens = usage.output_tokens || 0;
|
|
1432
|
+
this.#stats = {
|
|
1433
|
+
last_request: {
|
|
1434
|
+
input: input_tokens,
|
|
1435
|
+
output: output_tokens
|
|
1436
|
+
},
|
|
1437
|
+
total: {
|
|
1438
|
+
input: this.stats.total.input + input_tokens,
|
|
1439
|
+
output: this.stats.total.output + output_tokens
|
|
1440
|
+
}
|
|
1441
|
+
};
|
|
1442
|
+
this.#event_emitter.emit("stats_update", this.stats);
|
|
1443
|
+
}
|
|
1444
|
+
};
|
|
1445
|
+
};
|
|
1446
|
+
|
|
1447
|
+
// src/context-transformers/noop-context-transformer.ts
|
|
1448
|
+
var NoopContextTransformer = class {
|
|
1449
|
+
/**
|
|
1450
|
+
* Returns the prompt messages unchanged.
|
|
1451
|
+
*
|
|
1452
|
+
* @param messages - The messages to pass through.
|
|
1453
|
+
* @returns A promise that resolves to the same messages.
|
|
1454
|
+
*/
|
|
1455
|
+
transform_prompt(messages) {
|
|
1456
|
+
return Promise.resolve(messages);
|
|
1457
|
+
}
|
|
1458
|
+
/**
|
|
1459
|
+
* Returns the completion message unchanged.
|
|
1460
|
+
*
|
|
1461
|
+
* @param message - The assistant message to pass through.
|
|
1462
|
+
* @returns A promise that resolves to the same message.
|
|
1463
|
+
*/
|
|
1464
|
+
transform_completion(message) {
|
|
1465
|
+
return Promise.resolve(message);
|
|
1466
|
+
}
|
|
1467
|
+
};
|
|
1468
|
+
|
|
1469
|
+
// src/policies/rate-limit-policy.ts
|
|
1470
|
+
var RateLimitPolicy = class _RateLimitPolicy extends Policy {
|
|
1471
|
+
#options;
|
|
1472
|
+
#requests = [];
|
|
1473
|
+
/**
|
|
1474
|
+
* Creates a new rate limit policy.
|
|
1475
|
+
*
|
|
1476
|
+
* @param options - The rate limiting configuration.
|
|
1477
|
+
*/
|
|
1478
|
+
constructor(options) {
|
|
1479
|
+
super();
|
|
1480
|
+
this.#options = options;
|
|
1481
|
+
}
|
|
1482
|
+
/**
|
|
1483
|
+
* Attaches this policy to a conversation to track its requests.
|
|
1484
|
+
*
|
|
1485
|
+
* @param conversation - The conversation to monitor.
|
|
1486
|
+
*/
|
|
1487
|
+
attach(conversation) {
|
|
1488
|
+
conversation.on("request_success", this.#on_request_success);
|
|
1489
|
+
conversation.on("child_event", this.#on_child_event);
|
|
1490
|
+
conversation.once("dispose", () => {
|
|
1491
|
+
conversation.off("request_success", this.#on_request_success);
|
|
1492
|
+
conversation.off("child_event", this.#on_child_event);
|
|
1493
|
+
});
|
|
1494
|
+
}
|
|
1495
|
+
/**
|
|
1496
|
+
* Executes a function with rate limiting applied.
|
|
1497
|
+
*
|
|
1498
|
+
* @template TResult - The return type of the function.
|
|
1499
|
+
* @template TArgs - The argument types of the function.
|
|
1500
|
+
* @param cancellation_token - Token for cancelling the operation.
|
|
1501
|
+
* @param fn - The function to execute.
|
|
1502
|
+
* @param args - Arguments to pass to the function.
|
|
1503
|
+
* @returns A promise that resolves to the policy result.
|
|
1504
|
+
*/
|
|
1505
|
+
async execute(cancellation_token, fn, ...args) {
|
|
1506
|
+
const now = Date.now();
|
|
1507
|
+
this.#requests = this.#requests.filter(
|
|
1508
|
+
(timestamp) => now - timestamp < this.#options.period_ms
|
|
1509
|
+
);
|
|
1510
|
+
const current_usage = this.#requests.length;
|
|
1511
|
+
const metadata = {
|
|
1512
|
+
policy: _RateLimitPolicy.name,
|
|
1513
|
+
limit: this.#options.limit,
|
|
1514
|
+
period_ms: this.#options.period_ms,
|
|
1515
|
+
current_usage
|
|
1516
|
+
};
|
|
1517
|
+
const wait_time = this.#calculate_wait_time(
|
|
1518
|
+
current_usage,
|
|
1519
|
+
this.#options.limit,
|
|
1520
|
+
this.#options.period_ms
|
|
1521
|
+
);
|
|
1522
|
+
if (wait_time > 0) {
|
|
1523
|
+
metadata.wait_time_ms = wait_time;
|
|
1524
|
+
await sleep(wait_time, cancellation_token);
|
|
1525
|
+
}
|
|
1526
|
+
cancellation_token.throw_if_cancellation_requested();
|
|
1527
|
+
try {
|
|
1528
|
+
const value = await Promise.race([fn(...args), cancellation_token.await_cancellation()]);
|
|
1529
|
+
return {
|
|
1530
|
+
result: true,
|
|
1531
|
+
value,
|
|
1532
|
+
metadata
|
|
1533
|
+
};
|
|
1534
|
+
} catch (error) {
|
|
1535
|
+
return {
|
|
1536
|
+
result: false,
|
|
1537
|
+
error,
|
|
1538
|
+
metadata
|
|
1539
|
+
};
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
#calculate_wait_time(current_usage, limit, period_ms) {
|
|
1543
|
+
if (current_usage < limit) {
|
|
1544
|
+
return 0;
|
|
1545
|
+
}
|
|
1546
|
+
const requests_over_limit = current_usage - limit + 1;
|
|
1547
|
+
const time_per_request = period_ms / limit;
|
|
1548
|
+
return requests_over_limit * time_per_request;
|
|
1549
|
+
}
|
|
1550
|
+
#on_request_success = () => {
|
|
1551
|
+
const now = Date.now();
|
|
1552
|
+
this.#requests.push(now);
|
|
1553
|
+
};
|
|
1554
|
+
#on_child_event = (event_data) => {
|
|
1555
|
+
const { event_name, event_args } = event_data;
|
|
1556
|
+
if (event_name === "request_success") {
|
|
1557
|
+
this.#on_request_success();
|
|
1558
|
+
}
|
|
1559
|
+
if (event_name === "child_event") {
|
|
1560
|
+
const [child_event_data] = event_args;
|
|
1561
|
+
this.#on_child_event(child_event_data);
|
|
1562
|
+
}
|
|
1563
|
+
};
|
|
1564
|
+
};
|
|
1565
|
+
|
|
1566
|
+
// src/policies/token-limit-policy.ts
|
|
1567
|
+
var TokenLimitPolicy = class _TokenLimitPolicy extends Policy {
|
|
1568
|
+
#options;
|
|
1569
|
+
#usage = [];
|
|
1570
|
+
/**
|
|
1571
|
+
* Creates a new token limit policy.
|
|
1572
|
+
*
|
|
1573
|
+
* @param options - The token limit configuration.
|
|
1574
|
+
*/
|
|
1575
|
+
constructor(options) {
|
|
1576
|
+
super();
|
|
1577
|
+
this.#options = options;
|
|
1578
|
+
}
|
|
1579
|
+
/**
|
|
1580
|
+
* Executes a function with token limit enforcement applied.
|
|
1581
|
+
*
|
|
1582
|
+
* @template TResult - The return type of the function.
|
|
1583
|
+
* @template TArgs - The argument types of the function.
|
|
1584
|
+
* @param cancellation_token - Token for cancelling the operation.
|
|
1585
|
+
* @param fn - The function to execute.
|
|
1586
|
+
* @param args - Arguments to pass to the function.
|
|
1587
|
+
* @returns A promise that resolves to the policy result.
|
|
1588
|
+
*/
|
|
1589
|
+
async execute(cancellation_token, fn, ...args) {
|
|
1590
|
+
const now = Date.now();
|
|
1591
|
+
this.#usage = this.#usage.filter((record) => now - record.timestamp < this.#options.period_ms);
|
|
1592
|
+
const input_tokens = this.#usage.reduce((sum, record) => sum + record.input_tokens, 0);
|
|
1593
|
+
const output_tokens = this.#usage.reduce((sum, record) => sum + record.output_tokens, 0);
|
|
1594
|
+
const total_tokens = input_tokens + output_tokens;
|
|
1595
|
+
let wait_time;
|
|
1596
|
+
const options = this.#options;
|
|
1597
|
+
if ("total_tokens_per_period" in options) {
|
|
1598
|
+
const total_token_wait_time = this.#calculate_wait_time(
|
|
1599
|
+
total_tokens,
|
|
1600
|
+
options.total_tokens_per_period
|
|
1601
|
+
);
|
|
1602
|
+
wait_time = total_token_wait_time;
|
|
1603
|
+
} else {
|
|
1604
|
+
const input_token_wait_time = this.#calculate_wait_time(
|
|
1605
|
+
input_tokens,
|
|
1606
|
+
options.input_tokens_per_period
|
|
1607
|
+
);
|
|
1608
|
+
const output_token_wait_time = this.#calculate_wait_time(
|
|
1609
|
+
output_tokens,
|
|
1610
|
+
options.output_tokens_per_period
|
|
1611
|
+
);
|
|
1612
|
+
wait_time = Math.max(input_token_wait_time, output_token_wait_time);
|
|
1613
|
+
}
|
|
1614
|
+
const metadata = {
|
|
1615
|
+
policy: _TokenLimitPolicy.name,
|
|
1616
|
+
...this.#options,
|
|
1617
|
+
current_period_start: now - this.#options.period_ms,
|
|
1618
|
+
current_period_end: now,
|
|
1619
|
+
input_tokens,
|
|
1620
|
+
output_tokens,
|
|
1621
|
+
total_tokens,
|
|
1622
|
+
wait_time,
|
|
1623
|
+
recent_usage_count: this.#usage.length,
|
|
1624
|
+
recent_usage: this.#usage
|
|
1625
|
+
};
|
|
1626
|
+
if ("total_tokens_per_period" in options) {
|
|
1627
|
+
metadata.total_token_wait_time = wait_time;
|
|
1628
|
+
} else {
|
|
1629
|
+
metadata.input_token_wait_time = this.#calculate_wait_time(
|
|
1630
|
+
input_tokens,
|
|
1631
|
+
options.input_tokens_per_period
|
|
1632
|
+
);
|
|
1633
|
+
metadata.output_token_wait_time = this.#calculate_wait_time(
|
|
1634
|
+
output_tokens,
|
|
1635
|
+
options.output_tokens_per_period
|
|
1636
|
+
);
|
|
1637
|
+
}
|
|
1638
|
+
if (wait_time > 0) {
|
|
1639
|
+
metadata.wait_time_ms = wait_time;
|
|
1640
|
+
await sleep(wait_time, cancellation_token);
|
|
1641
|
+
}
|
|
1642
|
+
cancellation_token.throw_if_cancellation_requested();
|
|
1643
|
+
try {
|
|
1644
|
+
const value = await Promise.race([fn(...args), cancellation_token.await_cancellation()]);
|
|
1645
|
+
return {
|
|
1646
|
+
result: true,
|
|
1647
|
+
value,
|
|
1648
|
+
metadata
|
|
1649
|
+
};
|
|
1650
|
+
} catch (error) {
|
|
1651
|
+
return {
|
|
1652
|
+
result: false,
|
|
1653
|
+
error,
|
|
1654
|
+
metadata
|
|
1655
|
+
};
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
/**
|
|
1659
|
+
* Attaches this policy to a conversation to track its token usage.
|
|
1660
|
+
*
|
|
1661
|
+
* @param conversation - The conversation to monitor.
|
|
1662
|
+
*/
|
|
1663
|
+
attach(conversation) {
|
|
1664
|
+
conversation.on("message_complete", this.#on_message_complete);
|
|
1665
|
+
conversation.on("child_event", this.#on_child_event);
|
|
1666
|
+
conversation.once("dispose", () => {
|
|
1667
|
+
conversation.off("message_complete", this.#on_message_complete);
|
|
1668
|
+
conversation.off("child_event", this.#on_child_event);
|
|
1669
|
+
});
|
|
1670
|
+
}
|
|
1671
|
+
#on_message_complete = ({ usage }) => {
|
|
1672
|
+
const input_tokens = usage?.input_tokens || 0;
|
|
1673
|
+
const output_tokens = usage?.output_tokens || 0;
|
|
1674
|
+
this.#usage.push({
|
|
1675
|
+
timestamp: Date.now(),
|
|
1676
|
+
input_tokens,
|
|
1677
|
+
output_tokens
|
|
1678
|
+
});
|
|
1679
|
+
};
|
|
1680
|
+
#on_child_event = (event_data) => {
|
|
1681
|
+
const { event_name, event_args } = event_data;
|
|
1682
|
+
if (event_name === "message_complete") {
|
|
1683
|
+
const [event_data2] = event_args;
|
|
1684
|
+
if (event_data2) {
|
|
1685
|
+
this.#on_message_complete(event_data2);
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
if (event_name === "child_event") {
|
|
1689
|
+
const [child_event_data] = event_args;
|
|
1690
|
+
this.#on_child_event(child_event_data);
|
|
1691
|
+
}
|
|
1692
|
+
};
|
|
1693
|
+
#calculate_wait_time(current_tokens, tokens_per_period) {
|
|
1694
|
+
if (current_tokens < tokens_per_period) {
|
|
1695
|
+
return 0;
|
|
1696
|
+
}
|
|
1697
|
+
const now = Date.now();
|
|
1698
|
+
const oldest = this.#usage[0];
|
|
1699
|
+
if (!oldest) {
|
|
1700
|
+
return 0;
|
|
1701
|
+
}
|
|
1702
|
+
return Math.max(0, oldest.timestamp + this.#options.period_ms - now);
|
|
1703
|
+
}
|
|
1704
|
+
};
|
|
1705
|
+
|
|
1706
|
+
// src/workflows/workflow.ts
|
|
1707
|
+
var import_node_crypto2 = require("crypto");
|
|
1708
|
+
var import_node_events4 = require("events");
|
|
1709
|
+
var import_promises2 = require("timers/promises");
|
|
1710
|
+
var Workflow = class _Workflow {
|
|
1711
|
+
#event_emitter;
|
|
1712
|
+
#conversation;
|
|
1713
|
+
#context_data;
|
|
1714
|
+
#parent;
|
|
1715
|
+
#messages = [];
|
|
1716
|
+
#queued_messages = [];
|
|
1717
|
+
#id = (0, import_node_crypto2.randomUUID)();
|
|
1718
|
+
#state = "idle";
|
|
1719
|
+
#tool_context;
|
|
1720
|
+
/**
|
|
1721
|
+
* Creates a new workflow instance.
|
|
1722
|
+
*
|
|
1723
|
+
* @param conversation - The conversation to manage.
|
|
1724
|
+
* @param options - Optional configuration.
|
|
1725
|
+
* @param options.context_data - Custom data to pass to tools via ToolContext.
|
|
1726
|
+
* @param options.parent - Parent workflow if this is a child workflow.
|
|
1727
|
+
*/
|
|
1728
|
+
constructor(conversation, options) {
|
|
1729
|
+
this.#event_emitter = new import_node_events4.EventEmitter();
|
|
1730
|
+
this.#event_emitter.setMaxListeners(0);
|
|
1731
|
+
this.#conversation = conversation;
|
|
1732
|
+
this.#context_data = options?.context_data ?? {};
|
|
1733
|
+
this.#parent = options?.parent;
|
|
1734
|
+
this.#conversation.on("prompt_send", this.#on_prompt_send);
|
|
1735
|
+
this.#conversation.on("message_complete", this.#on_message_complete);
|
|
1736
|
+
this.#conversation.on("request_error", this.#on_request_error);
|
|
1737
|
+
this.#conversation.once("dispose", this.#on_conversation_dispose);
|
|
1738
|
+
this.#parent?.once("workflow_end", this.#on_parent_workflow_end);
|
|
1739
|
+
}
|
|
1740
|
+
/**
|
|
1741
|
+
* The unique identifier for this workflow.
|
|
1742
|
+
*/
|
|
1743
|
+
get id() {
|
|
1744
|
+
return this.#id;
|
|
1745
|
+
}
|
|
1746
|
+
set id(value) {
|
|
1747
|
+
this.#id = value;
|
|
1748
|
+
}
|
|
1749
|
+
/**
|
|
1750
|
+
* The current state of the workflow.
|
|
1751
|
+
*/
|
|
1752
|
+
get state() {
|
|
1753
|
+
return this.#state;
|
|
1754
|
+
}
|
|
1755
|
+
/**
|
|
1756
|
+
* The conversation being managed by this workflow.
|
|
1757
|
+
*/
|
|
1758
|
+
get conversation() {
|
|
1759
|
+
return this.#conversation;
|
|
1760
|
+
}
|
|
1761
|
+
/**
|
|
1762
|
+
* The parent workflow, if this is a child workflow.
|
|
1763
|
+
*/
|
|
1764
|
+
get parent() {
|
|
1765
|
+
return this.#parent;
|
|
1766
|
+
}
|
|
1767
|
+
/**
|
|
1768
|
+
* The message history tracked by this workflow.
|
|
1769
|
+
*/
|
|
1770
|
+
get messages() {
|
|
1771
|
+
return Object.freeze([...this.#messages]);
|
|
1772
|
+
}
|
|
1773
|
+
/**
|
|
1774
|
+
* Messages queued for sending after the current response completes.
|
|
1775
|
+
*/
|
|
1776
|
+
get queued_messages() {
|
|
1777
|
+
return [...this.#queued_messages];
|
|
1778
|
+
}
|
|
1779
|
+
/**
|
|
1780
|
+
* Disposes the workflow and releases all resources.
|
|
1781
|
+
*
|
|
1782
|
+
* This method is called automatically when using the `using` keyword.
|
|
1783
|
+
*/
|
|
1784
|
+
[Symbol.dispose]() {
|
|
1785
|
+
this.#conversation.off("prompt_send", this.#on_prompt_send);
|
|
1786
|
+
this.#conversation.off("message_complete", this.#on_message_complete);
|
|
1787
|
+
this.#conversation.off("request_error", this.#on_request_error);
|
|
1788
|
+
this.#conversation.off("dispose", this.#on_conversation_dispose);
|
|
1789
|
+
this.#parent?.off("workflow_end", this.#on_parent_workflow_end);
|
|
1790
|
+
this.#event_emitter.emit("dispose", this);
|
|
1791
|
+
this.#set_state("disposed");
|
|
1792
|
+
this.#event_emitter.removeAllListeners();
|
|
1793
|
+
}
|
|
1794
|
+
/**
|
|
1795
|
+
* Registers a one-time event listener.
|
|
1796
|
+
*
|
|
1797
|
+
* @template E - The event name type.
|
|
1798
|
+
* @param event - The name of the event to listen for.
|
|
1799
|
+
* @param listener - The callback function to invoke when the event occurs.
|
|
1800
|
+
* @returns This workflow instance for chaining.
|
|
1801
|
+
*/
|
|
1802
|
+
once(event, listener) {
|
|
1803
|
+
if (this.#state === "disposed") {
|
|
1804
|
+
throw new Error("invalid state");
|
|
1805
|
+
}
|
|
1806
|
+
this.#event_emitter.once(event, listener);
|
|
1807
|
+
return this;
|
|
1808
|
+
}
|
|
1809
|
+
/**
|
|
1810
|
+
* Registers a persistent event listener.
|
|
1811
|
+
*
|
|
1812
|
+
* @template E - The event name type.
|
|
1813
|
+
* @param event - The name of the event to listen for.
|
|
1814
|
+
* @param listener - The callback function to invoke when the event occurs.
|
|
1815
|
+
* @returns This workflow instance for chaining.
|
|
1816
|
+
*/
|
|
1817
|
+
on(event, listener) {
|
|
1818
|
+
if (this.#state === "disposed") {
|
|
1819
|
+
throw new Error("invalid state");
|
|
1820
|
+
}
|
|
1821
|
+
this.#event_emitter.on(event, listener);
|
|
1822
|
+
return this;
|
|
1823
|
+
}
|
|
1824
|
+
/**
|
|
1825
|
+
* Removes an event listener.
|
|
1826
|
+
*
|
|
1827
|
+
* @template E - The event name type.
|
|
1828
|
+
* @param event - The name of the event.
|
|
1829
|
+
* @param listener - The callback function to remove.
|
|
1830
|
+
* @returns This workflow instance for chaining.
|
|
1831
|
+
*/
|
|
1832
|
+
off(event, listener) {
|
|
1833
|
+
this.#event_emitter.off(event, listener);
|
|
1834
|
+
return this;
|
|
1835
|
+
}
|
|
1836
|
+
/**
|
|
1837
|
+
* Starts the workflow execution.
|
|
1838
|
+
*
|
|
1839
|
+
* @param message - Optional initial user message to begin the workflow.
|
|
1840
|
+
*/
|
|
1841
|
+
start(message) {
|
|
1842
|
+
if (this.#state !== "idle") {
|
|
1843
|
+
throw new Error("invalid state");
|
|
1844
|
+
}
|
|
1845
|
+
if (message) {
|
|
1846
|
+
this.#messages.push(message);
|
|
1847
|
+
this.#event_emitter.emit("workflow_update", this);
|
|
1848
|
+
}
|
|
1849
|
+
this.#set_state("busy");
|
|
1850
|
+
}
|
|
1851
|
+
/**
|
|
1852
|
+
* Cancels the workflow execution.
|
|
1853
|
+
*
|
|
1854
|
+
* This will cancel any in-progress model response and emit a workflow_end event.
|
|
1855
|
+
*/
|
|
1856
|
+
cancel() {
|
|
1857
|
+
if (this.#state === "disposed") {
|
|
1858
|
+
throw new Error("invalid state");
|
|
1859
|
+
}
|
|
1860
|
+
if (this.#conversation.state === "awaiting_response" || this.#conversation.state === "streaming_response") {
|
|
1861
|
+
this.#conversation.cancel_response();
|
|
1862
|
+
}
|
|
1863
|
+
this.#set_state("idle");
|
|
1864
|
+
this.#event_emitter.emit("workflow_end", { reason: "cancel" }, this);
|
|
1865
|
+
this[Symbol.dispose]();
|
|
1866
|
+
}
|
|
1867
|
+
/**
|
|
1868
|
+
* Creates a child workflow with a different conversation.
|
|
1869
|
+
*
|
|
1870
|
+
* Child events are propagated to the parent as "child_workflow_event" events.
|
|
1871
|
+
*
|
|
1872
|
+
* @param conversation - The conversation for the child workflow.
|
|
1873
|
+
* @param id - Optional custom ID for the child workflow.
|
|
1874
|
+
* @param context_data - Additional context data to merge with this workflow's context data.
|
|
1875
|
+
* @returns The newly created child workflow.
|
|
1876
|
+
*/
|
|
1877
|
+
spawn_child(conversation, id, context_data) {
|
|
1878
|
+
if (this.#state === "disposed") {
|
|
1879
|
+
throw new Error("invalid state");
|
|
1880
|
+
}
|
|
1881
|
+
const merged_context = context_data ? { ...this.#context_data, ...context_data } : this.#context_data;
|
|
1882
|
+
const child = new _Workflow(conversation, { context_data: merged_context, parent: this });
|
|
1883
|
+
if (id) {
|
|
1884
|
+
child.id = id;
|
|
1885
|
+
}
|
|
1886
|
+
this.#attach_child_event(child, "state_change");
|
|
1887
|
+
this.#attach_child_event(child, "workflow_update");
|
|
1888
|
+
this.#attach_child_event(child, "workflow_end");
|
|
1889
|
+
this.#attach_child_event(child, "child_workflow_begin");
|
|
1890
|
+
this.#attach_child_event(child, "child_workflow_event");
|
|
1891
|
+
this.#attach_child_event(child, "message_queued");
|
|
1892
|
+
this.#attach_child_event(child, "message_dequeued");
|
|
1893
|
+
this.#attach_child_event(child, "queue_cleared");
|
|
1894
|
+
this.#attach_child_event(child, "lifecycle_error");
|
|
1895
|
+
this.#attach_child_event(child, "dispose");
|
|
1896
|
+
this.#event_emitter.emit("child_workflow_begin", child, this);
|
|
1897
|
+
return child;
|
|
1898
|
+
}
|
|
1899
|
+
/**
|
|
1900
|
+
* Adds a message to the queue for sending after the current response completes.
|
|
1901
|
+
*
|
|
1902
|
+
* @param message - The text message to queue.
|
|
1903
|
+
*/
|
|
1904
|
+
queue_message(message) {
|
|
1905
|
+
if (this.#state === "disposed") {
|
|
1906
|
+
throw new Error("invalid state");
|
|
1907
|
+
}
|
|
1908
|
+
this.#queued_messages.push(message);
|
|
1909
|
+
this.#event_emitter.emit("message_queued", message, this);
|
|
1910
|
+
}
|
|
1911
|
+
/**
|
|
1912
|
+
* Clears all queued messages.
|
|
1913
|
+
*/
|
|
1914
|
+
clear_queue() {
|
|
1915
|
+
if (this.#state === "disposed") {
|
|
1916
|
+
throw new Error("invalid state");
|
|
1917
|
+
}
|
|
1918
|
+
this.#queued_messages.length = 0;
|
|
1919
|
+
this.#event_emitter.emit("queue_cleared", this);
|
|
1920
|
+
}
|
|
1921
|
+
#set_state(state) {
|
|
1922
|
+
if (this.#state === state) {
|
|
1923
|
+
return;
|
|
1924
|
+
}
|
|
1925
|
+
const previous = this.state;
|
|
1926
|
+
this.#state = state;
|
|
1927
|
+
this.#event_emitter.emit("state_change", { previous, current: this.#state }, this);
|
|
1928
|
+
}
|
|
1929
|
+
async #execute_tools(message) {
|
|
1930
|
+
const tool_calls = message.content.filter(
|
|
1931
|
+
(c) => c.type === "tool"
|
|
1932
|
+
);
|
|
1933
|
+
const batches = [];
|
|
1934
|
+
let current_batch = [];
|
|
1935
|
+
for (const call of tool_calls) {
|
|
1936
|
+
const tool_class = this.conversation.toolkit.find(
|
|
1937
|
+
(t) => t.get_definition().name === call.tool
|
|
1938
|
+
);
|
|
1939
|
+
const is_parallel = tool_class?.get_definition().parallelizable !== false;
|
|
1940
|
+
if (!is_parallel) {
|
|
1941
|
+
if (current_batch.length > 0) {
|
|
1942
|
+
batches.push(current_batch);
|
|
1943
|
+
current_batch = [];
|
|
1944
|
+
}
|
|
1945
|
+
batches.push([call]);
|
|
1946
|
+
} else {
|
|
1947
|
+
current_batch.push(call);
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
if (current_batch.length > 0) {
|
|
1951
|
+
batches.push(current_batch);
|
|
1952
|
+
}
|
|
1953
|
+
const results = [];
|
|
1954
|
+
for (const batch of batches) {
|
|
1955
|
+
const batch_results = await Promise.all(
|
|
1956
|
+
batch.map(async (call) => {
|
|
1957
|
+
const tool_class = this.conversation.toolkit.find(
|
|
1958
|
+
(t) => t.get_definition().name === call.tool
|
|
1959
|
+
);
|
|
1960
|
+
if (tool_class) {
|
|
1961
|
+
if (!this.#tool_context) {
|
|
1962
|
+
this.#tool_context = {
|
|
1963
|
+
...this.#context_data,
|
|
1964
|
+
conversation: this.conversation,
|
|
1965
|
+
workflow: this
|
|
1966
|
+
};
|
|
1967
|
+
}
|
|
1968
|
+
const context = this.#tool_context;
|
|
1969
|
+
const tool = new tool_class(context);
|
|
1970
|
+
try {
|
|
1971
|
+
const result = await tool.execute(call.params);
|
|
1972
|
+
return {
|
|
1973
|
+
type: "tool_result",
|
|
1974
|
+
tool: call.tool,
|
|
1975
|
+
tool_request_id: call.tool_request_id,
|
|
1976
|
+
result
|
|
1977
|
+
};
|
|
1978
|
+
} catch (error) {
|
|
1979
|
+
const message2 = error instanceof Error ? error.message : "Tool execution failed";
|
|
1980
|
+
return {
|
|
1981
|
+
type: "tool_result",
|
|
1982
|
+
tool: call.tool,
|
|
1983
|
+
tool_request_id: call.tool_request_id,
|
|
1984
|
+
result: { result: false, message: message2 }
|
|
1985
|
+
};
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
return {
|
|
1989
|
+
type: "tool_result",
|
|
1990
|
+
tool: call.tool,
|
|
1991
|
+
tool_request_id: call.tool_request_id,
|
|
1992
|
+
result: { result: false, message: "invalid tool" }
|
|
1993
|
+
};
|
|
1994
|
+
})
|
|
1995
|
+
);
|
|
1996
|
+
results.push(...batch_results);
|
|
1997
|
+
if (this.#state !== "busy") {
|
|
1998
|
+
return;
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
2001
|
+
await this.conversation.send_message(results);
|
|
2002
|
+
}
|
|
2003
|
+
#attach_child_event(child, event_name) {
|
|
2004
|
+
const handler = (...args) => {
|
|
2005
|
+
this.#event_emitter.emit(
|
|
2006
|
+
"child_workflow_event",
|
|
2007
|
+
{
|
|
2008
|
+
event_name,
|
|
2009
|
+
event_args: args
|
|
2010
|
+
},
|
|
2011
|
+
this
|
|
2012
|
+
);
|
|
2013
|
+
};
|
|
2014
|
+
child.on(event_name, handler);
|
|
2015
|
+
child.once("dispose", () => (0, import_promises2.setImmediate)(() => child.off(event_name, handler)));
|
|
2016
|
+
}
|
|
2017
|
+
#on_prompt_send = ({ message }) => {
|
|
2018
|
+
this.#messages.push(message);
|
|
2019
|
+
this.#event_emitter.emit("workflow_update", this);
|
|
2020
|
+
};
|
|
2021
|
+
#on_message_complete = async (event) => {
|
|
2022
|
+
try {
|
|
2023
|
+
this.#messages.push(event.message);
|
|
2024
|
+
this.#event_emitter.emit("workflow_update", this);
|
|
2025
|
+
if (event.stop_reason === "tool_use") {
|
|
2026
|
+
await this.#execute_tools(event.message);
|
|
2027
|
+
} else {
|
|
2028
|
+
const queued_message = this.#queued_messages.shift();
|
|
2029
|
+
if (queued_message) {
|
|
2030
|
+
this.#event_emitter.emit("message_dequeued", queued_message, this);
|
|
2031
|
+
await this.#conversation.prompt(queued_message);
|
|
2032
|
+
} else {
|
|
2033
|
+
this.#set_state("idle");
|
|
2034
|
+
this.#event_emitter.emit("workflow_end", { reason: "complete" }, this);
|
|
2035
|
+
this[Symbol.dispose]();
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
} catch {
|
|
2039
|
+
this.#set_state("idle");
|
|
2040
|
+
this.#event_emitter.emit("workflow_end", { reason: "error" }, this);
|
|
2041
|
+
this[Symbol.dispose]();
|
|
2042
|
+
}
|
|
2043
|
+
};
|
|
2044
|
+
#on_request_error = () => {
|
|
2045
|
+
this.#set_state("idle");
|
|
2046
|
+
this.#event_emitter.emit("workflow_end", { reason: "error" }, this);
|
|
2047
|
+
this[Symbol.dispose]();
|
|
2048
|
+
};
|
|
2049
|
+
#on_conversation_dispose = () => this[Symbol.dispose]();
|
|
2050
|
+
#on_parent_workflow_end = (event) => {
|
|
2051
|
+
if (event.reason === "cancel") {
|
|
2052
|
+
this.cancel();
|
|
2053
|
+
}
|
|
2054
|
+
};
|
|
2055
|
+
};
|
|
2056
|
+
|
|
2057
|
+
// src/workflows/workflow-manager.ts
|
|
2058
|
+
var import_node_events5 = require("events");
|
|
2059
|
+
var import_promises3 = require("timers/promises");
|
|
2060
|
+
var WorkflowManager = class {
|
|
2061
|
+
#event_emitter = new import_node_events5.EventEmitter();
|
|
2062
|
+
#conversation;
|
|
2063
|
+
#context_data;
|
|
2064
|
+
#state = "idle";
|
|
2065
|
+
#current_workflow;
|
|
2066
|
+
/**
|
|
2067
|
+
* Creates a new workflow manager instance.
|
|
2068
|
+
*
|
|
2069
|
+
* @param conversation - The conversation to manage workflows for.
|
|
2070
|
+
* @param options - Optional configuration.
|
|
2071
|
+
* @param options.context_data - Custom data to pass to tools via ToolContext.
|
|
2072
|
+
*/
|
|
2073
|
+
constructor(conversation, options) {
|
|
2074
|
+
this.#conversation = conversation;
|
|
2075
|
+
this.#context_data = options?.context_data ?? {};
|
|
2076
|
+
this.#conversation.on("prompt_send", this.#on_prompt_send);
|
|
2077
|
+
this.#conversation.on("checkpoint_begin", this.#on_checkpoint_begin);
|
|
2078
|
+
this.#conversation.once("dispose", this.#on_conversation_dispose);
|
|
2079
|
+
}
|
|
2080
|
+
/**
|
|
2081
|
+
* The current state of the workflow manager.
|
|
2082
|
+
*/
|
|
2083
|
+
get state() {
|
|
2084
|
+
return this.#state;
|
|
2085
|
+
}
|
|
2086
|
+
/**
|
|
2087
|
+
* The conversation being managed.
|
|
2088
|
+
*/
|
|
2089
|
+
get conversation() {
|
|
2090
|
+
return this.#conversation;
|
|
2091
|
+
}
|
|
2092
|
+
/**
|
|
2093
|
+
* The currently active workflow, if any.
|
|
2094
|
+
*/
|
|
2095
|
+
get current_workflow() {
|
|
2096
|
+
return this.#current_workflow;
|
|
2097
|
+
}
|
|
2098
|
+
/**
|
|
2099
|
+
* Disposes the workflow manager and releases all resources.
|
|
2100
|
+
*
|
|
2101
|
+
* This method is called automatically when using the `using` keyword.
|
|
2102
|
+
*/
|
|
2103
|
+
[Symbol.dispose]() {
|
|
2104
|
+
this.#conversation.off("prompt_send", this.#on_prompt_send);
|
|
2105
|
+
this.#conversation.off("checkpoint_begin", this.#on_checkpoint_begin);
|
|
2106
|
+
this.#conversation.off("dispose", this.#on_conversation_dispose);
|
|
2107
|
+
this.#event_emitter.emit("dispose", this);
|
|
2108
|
+
this.#set_state("disposed");
|
|
2109
|
+
this.#event_emitter.removeAllListeners();
|
|
2110
|
+
}
|
|
2111
|
+
/**
|
|
2112
|
+
* Registers a one-time event listener.
|
|
2113
|
+
*
|
|
2114
|
+
* @template E - The event name type.
|
|
2115
|
+
* @param event - The name of the event to listen for.
|
|
2116
|
+
* @param listener - The callback function to invoke when the event occurs.
|
|
2117
|
+
* @returns This workflow manager instance for chaining.
|
|
2118
|
+
*/
|
|
2119
|
+
once(event, listener) {
|
|
2120
|
+
if (this.#state === "disposed") {
|
|
2121
|
+
throw new Error("invalid state");
|
|
2122
|
+
}
|
|
2123
|
+
this.#event_emitter.once(event, listener);
|
|
2124
|
+
return this;
|
|
2125
|
+
}
|
|
2126
|
+
/**
|
|
2127
|
+
* Registers a persistent event listener.
|
|
2128
|
+
*
|
|
2129
|
+
* @template E - The event name type.
|
|
2130
|
+
* @param event - The name of the event to listen for.
|
|
2131
|
+
* @param listener - The callback function to invoke when the event occurs.
|
|
2132
|
+
* @returns This workflow manager instance for chaining.
|
|
2133
|
+
*/
|
|
2134
|
+
on(event, listener) {
|
|
2135
|
+
if (this.#state === "disposed") {
|
|
2136
|
+
throw new Error("invalid state");
|
|
2137
|
+
}
|
|
2138
|
+
this.#event_emitter.on(event, listener);
|
|
2139
|
+
return this;
|
|
2140
|
+
}
|
|
2141
|
+
/**
|
|
2142
|
+
* Removes an event listener.
|
|
2143
|
+
*
|
|
2144
|
+
* @template E - The event name type.
|
|
2145
|
+
* @param event - The name of the event.
|
|
2146
|
+
* @param listener - The callback function to remove.
|
|
2147
|
+
* @returns This workflow manager instance for chaining.
|
|
2148
|
+
*/
|
|
2149
|
+
off(event, listener) {
|
|
2150
|
+
this.#event_emitter.off(event, listener);
|
|
2151
|
+
return this;
|
|
2152
|
+
}
|
|
2153
|
+
/**
|
|
2154
|
+
* Manually starts a workflow.
|
|
2155
|
+
*
|
|
2156
|
+
* This is typically not needed as workflows are started automatically when messages are sent.
|
|
2157
|
+
*/
|
|
2158
|
+
start_workflow() {
|
|
2159
|
+
if (this.#state !== "idle") {
|
|
2160
|
+
throw new Error("invalid state");
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2163
|
+
#on_prompt_send = ({ message }) => {
|
|
2164
|
+
if (this.#current_workflow) {
|
|
2165
|
+
return;
|
|
2166
|
+
}
|
|
2167
|
+
this.#current_workflow = new Workflow(this.#conversation, { context_data: this.#context_data });
|
|
2168
|
+
this.#attach_workflow_event(this.#current_workflow, "state_change");
|
|
2169
|
+
this.#attach_workflow_event(this.#current_workflow, "workflow_update");
|
|
2170
|
+
this.#attach_workflow_event(this.#current_workflow, "workflow_end");
|
|
2171
|
+
this.#attach_workflow_event(this.#current_workflow, "child_workflow_begin");
|
|
2172
|
+
this.#attach_workflow_event(this.#current_workflow, "child_workflow_event");
|
|
2173
|
+
this.#attach_workflow_event(this.#current_workflow, "message_queued");
|
|
2174
|
+
this.#attach_workflow_event(this.#current_workflow, "message_dequeued");
|
|
2175
|
+
this.#attach_workflow_event(this.#current_workflow, "queue_cleared");
|
|
2176
|
+
this.#attach_workflow_event(this.#current_workflow, "lifecycle_error");
|
|
2177
|
+
this.#attach_workflow_event(this.#current_workflow, "dispose");
|
|
2178
|
+
this.#current_workflow.once("dispose", () => {
|
|
2179
|
+
this.#current_workflow = void 0;
|
|
2180
|
+
this.#set_state("idle");
|
|
2181
|
+
});
|
|
2182
|
+
this.#set_state("busy");
|
|
2183
|
+
this.#event_emitter.emit("workflow_begin", this.#current_workflow, this);
|
|
2184
|
+
this.#current_workflow.start(message);
|
|
2185
|
+
};
|
|
2186
|
+
#set_state(state) {
|
|
2187
|
+
if (this.#state === state) {
|
|
2188
|
+
return;
|
|
2189
|
+
}
|
|
2190
|
+
const previous = this.state;
|
|
2191
|
+
this.#state = state;
|
|
2192
|
+
this.#event_emitter.emit("state_change", { previous, current: this.#state }, this);
|
|
2193
|
+
}
|
|
2194
|
+
#attach_workflow_event(workflow, event_name) {
|
|
2195
|
+
const handler = (...args) => {
|
|
2196
|
+
this.#event_emitter.emit(
|
|
2197
|
+
"workflow_event",
|
|
2198
|
+
{
|
|
2199
|
+
event_name,
|
|
2200
|
+
event_args: args
|
|
2201
|
+
},
|
|
2202
|
+
this
|
|
2203
|
+
);
|
|
2204
|
+
};
|
|
2205
|
+
workflow.on(event_name, handler);
|
|
2206
|
+
workflow.once("dispose", () => (0, import_promises3.setImmediate)(() => workflow.off(event_name, handler)));
|
|
2207
|
+
}
|
|
2208
|
+
#on_checkpoint_begin = (child) => {
|
|
2209
|
+
this.#set_state("busy");
|
|
2210
|
+
child.once("dispose", () => {
|
|
2211
|
+
this.#set_state("idle");
|
|
2212
|
+
});
|
|
2213
|
+
};
|
|
2214
|
+
#on_conversation_dispose = () => this[Symbol.dispose]();
|
|
2215
|
+
};
|
|
2216
|
+
|
|
2217
|
+
// src/utils/object.ts
|
|
2218
|
+
function defined_keys(obj) {
|
|
2219
|
+
return Object.keys(obj).filter((key) => obj[key] !== void 0);
|
|
2220
|
+
}
|
|
2221
|
+
function undefined_if_empty(obj) {
|
|
2222
|
+
if (!obj) {
|
|
2223
|
+
return obj;
|
|
2224
|
+
}
|
|
2225
|
+
if (Array.isArray(obj)) {
|
|
2226
|
+
return obj.length === 0 ? void 0 : obj;
|
|
2227
|
+
}
|
|
2228
|
+
if (typeof obj === "object") {
|
|
2229
|
+
return defined_keys(obj).length === 0 ? void 0 : obj;
|
|
2230
|
+
}
|
|
2231
|
+
return obj;
|
|
2232
|
+
}
|
|
2233
|
+
function deep_merge(original, supplemental) {
|
|
2234
|
+
if (supplemental === void 0 || supplemental === null) {
|
|
2235
|
+
return original;
|
|
2236
|
+
}
|
|
2237
|
+
if (original === void 0 || original === null) {
|
|
2238
|
+
return supplemental;
|
|
2239
|
+
}
|
|
2240
|
+
if (typeof original === "object" && typeof supplemental === "object") {
|
|
2241
|
+
if (Array.isArray(original)) {
|
|
2242
|
+
if (!Array.isArray(supplemental)) {
|
|
2243
|
+
throw new Error("type mismatch");
|
|
2244
|
+
}
|
|
2245
|
+
return [...original, ...supplemental];
|
|
2246
|
+
} else {
|
|
2247
|
+
if (Array.isArray(supplemental)) {
|
|
2248
|
+
throw new Error("type mismatch");
|
|
2249
|
+
}
|
|
2250
|
+
const result = { ...original };
|
|
2251
|
+
for (const [key, value] of Object.entries(supplemental)) {
|
|
2252
|
+
result[key] = deep_merge(result[key], value);
|
|
2253
|
+
}
|
|
2254
|
+
return result;
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
if (typeof supplemental !== typeof original) {
|
|
2258
|
+
throw new Error("type mismatch");
|
|
2259
|
+
}
|
|
2260
|
+
if (typeof supplemental === "string" || typeof supplemental === "number" || typeof supplemental === "boolean") {
|
|
2261
|
+
return supplemental;
|
|
2262
|
+
}
|
|
2263
|
+
throw new Error("unsupported type");
|
|
2264
|
+
}
|
|
2265
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
2266
|
+
0 && (module.exports = {
|
|
2267
|
+
CancellationToken,
|
|
2268
|
+
CancellationTokenSource,
|
|
2269
|
+
CheckpointContextTransformer,
|
|
2270
|
+
CompositeContextTransformer,
|
|
2271
|
+
CompositePolicy,
|
|
2272
|
+
Conversation,
|
|
2273
|
+
DefaultSummarizationStrategy,
|
|
2274
|
+
NoopContextTransformer,
|
|
2275
|
+
NoopPolicy,
|
|
2276
|
+
OperationCanceledError,
|
|
2277
|
+
Policy,
|
|
2278
|
+
RateLimitPolicy,
|
|
2279
|
+
RetryPolicy,
|
|
2280
|
+
StreamListener,
|
|
2281
|
+
TokenLimitPolicy,
|
|
2282
|
+
TokenTracker,
|
|
2283
|
+
ToolContextTransformer,
|
|
2284
|
+
Workflow,
|
|
2285
|
+
WorkflowManager,
|
|
2286
|
+
deep_merge,
|
|
2287
|
+
defaultRetryable,
|
|
2288
|
+
getDefaultPolicy,
|
|
2289
|
+
peek_generator,
|
|
2290
|
+
sleep,
|
|
2291
|
+
undefined_if_empty
|
|
2292
|
+
});
|
|
2293
|
+
//# sourceMappingURL=index.cjs.map
|