@assemble-dev/sdk 0.0.1
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 +3925 -0
- package/dist/index.d.cts +588 -0
- package/dist/index.d.ts +588 -0
- package/dist/index.js +3877 -0
- package/package.json +38 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,3925 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
agent: () => agent,
|
|
24
|
+
applyTraceEventToRunState: () => applyTraceEventToRunState,
|
|
25
|
+
buildHostedTraceUrl: () => buildHostedTraceUrl,
|
|
26
|
+
buildMissingKeyMessage: () => buildMissingKeyMessage,
|
|
27
|
+
buildPipelineTeamWorkflowConfig: () => buildPipelineTeamWorkflowConfig,
|
|
28
|
+
buildReviewPanelWorkflowConfig: () => buildReviewPanelWorkflowConfig,
|
|
29
|
+
buildSupervisorTeamWorkflowConfig: () => buildSupervisorTeamWorkflowConfig,
|
|
30
|
+
compareRunToReplay: () => compareRunToReplay,
|
|
31
|
+
createArtifactStore: () => createArtifactStore,
|
|
32
|
+
createConsoleSink: () => createConsoleSink,
|
|
33
|
+
createInMemoryMemoryProvider: () => createInMemoryMemoryProvider,
|
|
34
|
+
createInitialRunState: () => createInitialRunState,
|
|
35
|
+
createJsonFileMemoryProvider: () => createJsonFileMemoryProvider,
|
|
36
|
+
createJsonlSink: () => createJsonlSink,
|
|
37
|
+
createPlatformTraceSink: () => createPlatformTraceSink,
|
|
38
|
+
createThreadMemory: () => createThreadMemory,
|
|
39
|
+
createTraceBus: () => createTraceBus,
|
|
40
|
+
defaultParallelMergedArtifactKey: () => defaultParallelMergedArtifactKey,
|
|
41
|
+
defaultRunStateDirectory: () => defaultRunStateDirectory,
|
|
42
|
+
defaultSupervisorMaxSteps: () => defaultSupervisorMaxSteps,
|
|
43
|
+
defaultTraceDirectory: () => defaultTraceDirectory,
|
|
44
|
+
defaultWorkflowTopology: () => defaultWorkflowTopology,
|
|
45
|
+
evaluateToolPolicies: () => evaluateToolPolicies,
|
|
46
|
+
executeToolCall: () => executeToolCall,
|
|
47
|
+
finishWorkflow: () => finishWorkflow,
|
|
48
|
+
formatTraceEventLine: () => formatTraceEventLine,
|
|
49
|
+
loadReplaySource: () => loadReplaySource,
|
|
50
|
+
loadRunStateFile: () => loadRunStateFile,
|
|
51
|
+
maxReplayOutputDiffCount: () => maxReplayOutputDiffCount,
|
|
52
|
+
policy: () => policy,
|
|
53
|
+
replayRun: () => replayRun,
|
|
54
|
+
resolvePlatformConfig: () => resolvePlatformConfig,
|
|
55
|
+
resolveRunStateFilePath: () => resolveRunStateFilePath,
|
|
56
|
+
resolveTraceFilePath: () => resolveTraceFilePath,
|
|
57
|
+
resolveWorkflowMemoryConfig: () => resolveWorkflowMemoryConfig,
|
|
58
|
+
resumeRun: () => resumeRun,
|
|
59
|
+
runAgent: () => runAgent,
|
|
60
|
+
runWorkflow: () => runWorkflow,
|
|
61
|
+
team: () => team,
|
|
62
|
+
tool: () => tool,
|
|
63
|
+
workflow: () => workflow
|
|
64
|
+
});
|
|
65
|
+
module.exports = __toCommonJS(index_exports);
|
|
66
|
+
|
|
67
|
+
// src/agents/agent-definition.ts
|
|
68
|
+
function agent(config) {
|
|
69
|
+
return Object.freeze({
|
|
70
|
+
...config,
|
|
71
|
+
maxSteps: config.maxSteps ?? 1
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// src/agents/agent-runner.ts
|
|
76
|
+
var import_errors5 = require("@assemble-dev/shared-types/errors");
|
|
77
|
+
var import_errors6 = require("@assemble-dev/shared-utils/errors");
|
|
78
|
+
|
|
79
|
+
// src/agents/agent-messages.ts
|
|
80
|
+
function serializeAgentInputToText(input) {
|
|
81
|
+
return typeof input === "string" ? input : JSON.stringify(input);
|
|
82
|
+
}
|
|
83
|
+
async function buildAgentChatMessages(definition, input, threadMemory) {
|
|
84
|
+
const priorThreadMessages = threadMemory === void 0 ? [] : await threadMemory.listThreadMessages();
|
|
85
|
+
return [
|
|
86
|
+
{ role: "system", content: definition.instructions },
|
|
87
|
+
...priorThreadMessages.map(mapRunMessageToChatMessage),
|
|
88
|
+
{ role: "user", content: serializeAgentInputToText(input) }
|
|
89
|
+
];
|
|
90
|
+
}
|
|
91
|
+
function mapRunMessageToChatMessage(message) {
|
|
92
|
+
if (message.toolName === void 0) {
|
|
93
|
+
return { role: message.role, content: message.content };
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
role: message.role,
|
|
97
|
+
content: message.content,
|
|
98
|
+
toolName: message.toolName
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
async function appendAgentExchangeToThreadMemory(threadMemory, agentName, input, assistantText, now) {
|
|
102
|
+
if (threadMemory === void 0) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
await threadMemory.appendThreadMessage({
|
|
106
|
+
role: "user",
|
|
107
|
+
content: serializeAgentInputToText(input),
|
|
108
|
+
timestamp: now().toISOString()
|
|
109
|
+
});
|
|
110
|
+
await threadMemory.appendThreadMessage({
|
|
111
|
+
role: "assistant",
|
|
112
|
+
content: assistantText,
|
|
113
|
+
agentName,
|
|
114
|
+
timestamp: now().toISOString()
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// src/agents/agent-output-generation.ts
|
|
119
|
+
var import_errors3 = require("@assemble-dev/shared-types/errors");
|
|
120
|
+
var import_errors4 = require("@assemble-dev/shared-utils/errors");
|
|
121
|
+
var import_zod = require("zod");
|
|
122
|
+
|
|
123
|
+
// src/agents/agent-model-timeout.ts
|
|
124
|
+
var import_errors = require("@assemble-dev/shared-types/errors");
|
|
125
|
+
var import_errors2 = require("@assemble-dev/shared-utils/errors");
|
|
126
|
+
var agentTimeoutErrorCode = "AGENT_TIMEOUT";
|
|
127
|
+
async function raceModelCallAgainstTimeout(modelCall, timeoutMs, agentName) {
|
|
128
|
+
if (timeoutMs === void 0) {
|
|
129
|
+
return await modelCall;
|
|
130
|
+
}
|
|
131
|
+
return await new Promise((resolve, reject) => {
|
|
132
|
+
const timeoutHandle = setTimeout(() => {
|
|
133
|
+
reject(buildAgentTimeoutError(agentName, timeoutMs));
|
|
134
|
+
}, timeoutMs);
|
|
135
|
+
void modelCall.then(
|
|
136
|
+
(modelResult) => {
|
|
137
|
+
clearTimeout(timeoutHandle);
|
|
138
|
+
resolve(modelResult);
|
|
139
|
+
},
|
|
140
|
+
(modelError) => {
|
|
141
|
+
clearTimeout(timeoutHandle);
|
|
142
|
+
reject(modelError);
|
|
143
|
+
}
|
|
144
|
+
);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
function buildAgentTimeoutError(agentName, timeoutMs) {
|
|
148
|
+
return new import_errors2.AppError({
|
|
149
|
+
code: import_errors.AppErrorCode.InternalServerError,
|
|
150
|
+
message: `Agent "${agentName}" timed out after ${timeoutMs}ms`,
|
|
151
|
+
statusCode: 504,
|
|
152
|
+
details: {
|
|
153
|
+
errorCode: agentTimeoutErrorCode,
|
|
154
|
+
timeoutMs
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// src/agents/agent-output-generation.ts
|
|
160
|
+
async function generateAgentOutput(definition, input, threadMemory) {
|
|
161
|
+
const messages = await buildAgentChatMessages(definition, input, threadMemory);
|
|
162
|
+
const outputSchema = definition.outputSchema;
|
|
163
|
+
if (outputSchema === void 0) {
|
|
164
|
+
const completion = await raceModelCallAgainstTimeout(
|
|
165
|
+
definition.model.generateChatCompletion({ messages }),
|
|
166
|
+
definition.timeoutMs,
|
|
167
|
+
definition.name
|
|
168
|
+
);
|
|
169
|
+
return {
|
|
170
|
+
output: completion.content,
|
|
171
|
+
rawText: completion.content,
|
|
172
|
+
tokenUsage: completion.tokenUsage
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
return await generateStructuredAgentOutput(definition, outputSchema, messages);
|
|
176
|
+
}
|
|
177
|
+
async function generateStructuredAgentOutput(definition, outputSchema, messages) {
|
|
178
|
+
try {
|
|
179
|
+
return await requestStructuredAgentOutput(definition, outputSchema, messages);
|
|
180
|
+
} catch (error) {
|
|
181
|
+
if (!(error instanceof import_errors4.AppError) || error.code !== import_errors3.AppErrorCode.ValidationFailed) {
|
|
182
|
+
throw error;
|
|
183
|
+
}
|
|
184
|
+
return await retryStructuredAgentOutput(definition, outputSchema, messages, error);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
async function retryStructuredAgentOutput(definition, outputSchema, messages, validationError) {
|
|
188
|
+
const retryMessages = [
|
|
189
|
+
...messages,
|
|
190
|
+
buildSchemaCorrectionMessage(validationError.message)
|
|
191
|
+
];
|
|
192
|
+
try {
|
|
193
|
+
return await requestStructuredAgentOutput(definition, outputSchema, retryMessages);
|
|
194
|
+
} catch (retryError) {
|
|
195
|
+
if (!(retryError instanceof import_errors4.AppError) || retryError.code !== import_errors3.AppErrorCode.ValidationFailed) {
|
|
196
|
+
throw retryError;
|
|
197
|
+
}
|
|
198
|
+
throw new import_errors4.AppError({
|
|
199
|
+
code: import_errors3.AppErrorCode.ValidationFailed,
|
|
200
|
+
message: `Agent "${definition.name}" structured output failed schema validation after one retry: ${retryError.message}`,
|
|
201
|
+
statusCode: 422,
|
|
202
|
+
cause: retryError
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
async function requestStructuredAgentOutput(definition, outputSchema, messages) {
|
|
207
|
+
try {
|
|
208
|
+
const structuredResult = await raceModelCallAgainstTimeout(
|
|
209
|
+
definition.model.generateStructuredOutput({
|
|
210
|
+
messages,
|
|
211
|
+
schema: outputSchema
|
|
212
|
+
}),
|
|
213
|
+
definition.timeoutMs,
|
|
214
|
+
definition.name
|
|
215
|
+
);
|
|
216
|
+
return {
|
|
217
|
+
output: structuredResult.value,
|
|
218
|
+
rawText: JSON.stringify(structuredResult.value),
|
|
219
|
+
tokenUsage: structuredResult.tokenUsage
|
|
220
|
+
};
|
|
221
|
+
} catch (error) {
|
|
222
|
+
if (error instanceof import_zod.z.ZodError) {
|
|
223
|
+
throw new import_errors4.AppError({
|
|
224
|
+
code: import_errors3.AppErrorCode.ValidationFailed,
|
|
225
|
+
message: `Structured output failed schema validation: ${error.message}`,
|
|
226
|
+
statusCode: 422
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
throw error;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
function buildSchemaCorrectionMessage(validationErrorMessage) {
|
|
233
|
+
return {
|
|
234
|
+
role: "user",
|
|
235
|
+
content: `The previous response failed schema validation: ${validationErrorMessage}. Respond again with JSON that matches the required schema exactly.`
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// src/agents/agent-trace-events.ts
|
|
240
|
+
var import_json = require("@assemble-dev/shared-types/json");
|
|
241
|
+
var import_ids = require("@assemble-dev/shared-utils/ids");
|
|
242
|
+
function buildAgentEventBase(eventContext, parentEventId) {
|
|
243
|
+
const runState = eventContext.traceBus.getRunState();
|
|
244
|
+
return {
|
|
245
|
+
id: (0, import_ids.generateTraceEventId)(),
|
|
246
|
+
runId: runState.runId,
|
|
247
|
+
workflowName: runState.workflowName,
|
|
248
|
+
timestamp: eventContext.now().toISOString(),
|
|
249
|
+
parentEventId,
|
|
250
|
+
redacted: false
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
function emitAgentStartedEvent(eventContext, input, parentEventId) {
|
|
254
|
+
return eventContext.traceBus.emitTraceEvent({
|
|
255
|
+
...buildAgentEventBase(eventContext, parentEventId),
|
|
256
|
+
type: "agent.started",
|
|
257
|
+
agentName: eventContext.agentName,
|
|
258
|
+
input
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
function emitAgentCompletedEvent(eventContext, startedEventId, output, latencyMs) {
|
|
262
|
+
eventContext.traceBus.emitTraceEvent({
|
|
263
|
+
...buildAgentEventBase(eventContext, startedEventId),
|
|
264
|
+
type: "agent.completed",
|
|
265
|
+
agentName: eventContext.agentName,
|
|
266
|
+
output: import_json.JsonValueSchema.parse(output),
|
|
267
|
+
latencyMs
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
function emitAgentFailedEvent(eventContext, parentEventId, error, latencyMs) {
|
|
271
|
+
eventContext.traceBus.emitTraceEvent({
|
|
272
|
+
...buildAgentEventBase(eventContext, parentEventId),
|
|
273
|
+
type: "agent.failed",
|
|
274
|
+
agentName: eventContext.agentName,
|
|
275
|
+
error: {
|
|
276
|
+
message: error.message,
|
|
277
|
+
code: resolveAgentTraceErrorCode(error)
|
|
278
|
+
},
|
|
279
|
+
latencyMs
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
function resolveAgentTraceErrorCode(error) {
|
|
283
|
+
const detailsErrorCode = error.details?.errorCode;
|
|
284
|
+
return typeof detailsErrorCode === "string" ? detailsErrorCode : error.code;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// src/agents/agent-runner.ts
|
|
288
|
+
async function runAgent(runInput) {
|
|
289
|
+
const now = runInput.now ?? (() => /* @__PURE__ */ new Date());
|
|
290
|
+
const eventContext = {
|
|
291
|
+
traceBus: runInput.traceBus,
|
|
292
|
+
agentName: runInput.definition.name,
|
|
293
|
+
now
|
|
294
|
+
};
|
|
295
|
+
const startedAtMs = now().getTime();
|
|
296
|
+
const validatedInput = validateAgentInput(runInput, eventContext, startedAtMs);
|
|
297
|
+
const startedEvent = emitAgentStartedEvent(
|
|
298
|
+
eventContext,
|
|
299
|
+
validatedInput,
|
|
300
|
+
runInput.parentEventId ?? null
|
|
301
|
+
);
|
|
302
|
+
invokeAgentStartHook(runInput.definition, validatedInput);
|
|
303
|
+
try {
|
|
304
|
+
return await completeAgentRun(
|
|
305
|
+
runInput,
|
|
306
|
+
eventContext,
|
|
307
|
+
validatedInput,
|
|
308
|
+
startedEvent.id,
|
|
309
|
+
startedAtMs
|
|
310
|
+
);
|
|
311
|
+
} catch (error) {
|
|
312
|
+
const appError = mapCaughtErrorToAppError(
|
|
313
|
+
error instanceof Error ? error : new Error(String(error))
|
|
314
|
+
);
|
|
315
|
+
emitAgentFailedEvent(
|
|
316
|
+
eventContext,
|
|
317
|
+
startedEvent.id,
|
|
318
|
+
appError,
|
|
319
|
+
now().getTime() - startedAtMs
|
|
320
|
+
);
|
|
321
|
+
invokeAgentErrorHook(runInput.definition, appError);
|
|
322
|
+
throw appError;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
async function completeAgentRun(runInput, eventContext, validatedInput, startedEventId, startedAtMs) {
|
|
326
|
+
const generation = await generateAgentOutput(
|
|
327
|
+
runInput.definition,
|
|
328
|
+
validatedInput,
|
|
329
|
+
runInput.threadMemory
|
|
330
|
+
);
|
|
331
|
+
await appendAgentExchangeToThreadMemory(
|
|
332
|
+
runInput.threadMemory,
|
|
333
|
+
runInput.definition.name,
|
|
334
|
+
validatedInput,
|
|
335
|
+
generation.rawText,
|
|
336
|
+
eventContext.now
|
|
337
|
+
);
|
|
338
|
+
const latencyMs = eventContext.now().getTime() - startedAtMs;
|
|
339
|
+
emitAgentCompletedEvent(eventContext, startedEventId, generation.output, latencyMs);
|
|
340
|
+
invokeAgentCompleteHook(runInput.definition, generation.output);
|
|
341
|
+
return {
|
|
342
|
+
output: generation.output,
|
|
343
|
+
rawText: generation.rawText,
|
|
344
|
+
tokenUsage: generation.tokenUsage,
|
|
345
|
+
latencyMs,
|
|
346
|
+
agentCallEventId: startedEventId
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
function validateAgentInput(runInput, eventContext, startedAtMs) {
|
|
350
|
+
const inputSchema = runInput.definition.inputSchema;
|
|
351
|
+
if (inputSchema === void 0) {
|
|
352
|
+
return runInput.input;
|
|
353
|
+
}
|
|
354
|
+
const parsedInput = inputSchema.safeParse(runInput.input);
|
|
355
|
+
if (parsedInput.success) {
|
|
356
|
+
return parsedInput.data;
|
|
357
|
+
}
|
|
358
|
+
const validationError = new import_errors6.AppError({
|
|
359
|
+
code: import_errors5.AppErrorCode.ValidationFailed,
|
|
360
|
+
message: `Agent "${runInput.definition.name}" input failed schema validation: ${parsedInput.error.message}`,
|
|
361
|
+
statusCode: 422
|
|
362
|
+
});
|
|
363
|
+
emitAgentFailedEvent(
|
|
364
|
+
eventContext,
|
|
365
|
+
runInput.parentEventId ?? null,
|
|
366
|
+
validationError,
|
|
367
|
+
eventContext.now().getTime() - startedAtMs
|
|
368
|
+
);
|
|
369
|
+
invokeAgentErrorHook(runInput.definition, validationError);
|
|
370
|
+
throw validationError;
|
|
371
|
+
}
|
|
372
|
+
function mapCaughtErrorToAppError(error) {
|
|
373
|
+
if (error instanceof import_errors6.AppError) {
|
|
374
|
+
return error;
|
|
375
|
+
}
|
|
376
|
+
return new import_errors6.AppError({
|
|
377
|
+
code: import_errors5.AppErrorCode.InternalServerError,
|
|
378
|
+
message: error.message,
|
|
379
|
+
statusCode: 500,
|
|
380
|
+
cause: error
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
function invokeAgentStartHook(definition, input) {
|
|
384
|
+
invokeAgentHookSafely(
|
|
385
|
+
() => definition.onAgentStart?.({ agentName: definition.name, input })
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
function invokeAgentCompleteHook(definition, output) {
|
|
389
|
+
invokeAgentHookSafely(
|
|
390
|
+
() => definition.onAgentComplete?.({ agentName: definition.name, output })
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
function invokeAgentErrorHook(definition, error) {
|
|
394
|
+
invokeAgentHookSafely(
|
|
395
|
+
() => definition.onAgentError?.({ agentName: definition.name, error })
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
function invokeAgentHookSafely(invokeHook) {
|
|
399
|
+
try {
|
|
400
|
+
invokeHook();
|
|
401
|
+
} catch {
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// src/tools/tool-definition.ts
|
|
407
|
+
var import_zod2 = require("zod");
|
|
408
|
+
var ToolRetryConfigSchema = import_zod2.z.object({
|
|
409
|
+
maxAttempts: import_zod2.z.number().int().min(1)
|
|
410
|
+
});
|
|
411
|
+
var ToolDefinitionOptionsSchema = import_zod2.z.object({
|
|
412
|
+
name: import_zod2.z.string().min(1),
|
|
413
|
+
description: import_zod2.z.string().min(1),
|
|
414
|
+
allowedAgentNames: import_zod2.z.array(import_zod2.z.string().min(1)).optional(),
|
|
415
|
+
requiresApproval: import_zod2.z.boolean().optional(),
|
|
416
|
+
retry: ToolRetryConfigSchema.optional(),
|
|
417
|
+
timeoutMs: import_zod2.z.number().int().positive().optional(),
|
|
418
|
+
sensitiveFieldNames: import_zod2.z.array(import_zod2.z.string().min(1)).optional(),
|
|
419
|
+
dryRun: import_zod2.z.boolean().optional()
|
|
420
|
+
});
|
|
421
|
+
var defaultToolRetryConfig = Object.freeze({
|
|
422
|
+
maxAttempts: 1
|
|
423
|
+
});
|
|
424
|
+
function tool(config) {
|
|
425
|
+
const parsedOptions = ToolDefinitionOptionsSchema.parse({
|
|
426
|
+
name: config.name,
|
|
427
|
+
description: config.description,
|
|
428
|
+
allowedAgentNames: config.allowedAgentNames,
|
|
429
|
+
requiresApproval: config.requiresApproval,
|
|
430
|
+
retry: config.retry,
|
|
431
|
+
timeoutMs: config.timeoutMs,
|
|
432
|
+
sensitiveFieldNames: config.sensitiveFieldNames,
|
|
433
|
+
dryRun: config.dryRun
|
|
434
|
+
});
|
|
435
|
+
return Object.freeze({
|
|
436
|
+
name: parsedOptions.name,
|
|
437
|
+
description: parsedOptions.description,
|
|
438
|
+
inputSchema: config.inputSchema,
|
|
439
|
+
outputSchema: config.outputSchema ?? null,
|
|
440
|
+
execute: config.execute,
|
|
441
|
+
allowedAgentNames: parsedOptions.allowedAgentNames ?? null,
|
|
442
|
+
requiresApproval: parsedOptions.requiresApproval ?? false,
|
|
443
|
+
retry: parsedOptions.retry ?? defaultToolRetryConfig,
|
|
444
|
+
timeoutMs: parsedOptions.timeoutMs ?? null,
|
|
445
|
+
sensitiveFieldNames: parsedOptions.sensitiveFieldNames ?? [],
|
|
446
|
+
dryRun: parsedOptions.dryRun ?? false
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// src/tools/tool-broker.ts
|
|
451
|
+
var import_errors7 = require("@assemble-dev/shared-types/errors");
|
|
452
|
+
var import_errors8 = require("@assemble-dev/shared-utils/errors");
|
|
453
|
+
var import_shared_utils2 = require("@assemble-dev/shared-utils");
|
|
454
|
+
var import_redaction2 = require("@assemble-dev/shared-utils/redaction");
|
|
455
|
+
|
|
456
|
+
// src/policies/policy-engine.ts
|
|
457
|
+
var import_runs = require("@assemble-dev/shared-types/runs");
|
|
458
|
+
|
|
459
|
+
// src/policies/tool-policy.ts
|
|
460
|
+
var toolPolicyEffectNames = [
|
|
461
|
+
"block",
|
|
462
|
+
"requireApproval",
|
|
463
|
+
"redact",
|
|
464
|
+
"logOnly"
|
|
465
|
+
];
|
|
466
|
+
|
|
467
|
+
// src/policies/policy-engine.ts
|
|
468
|
+
var defaultPolicyName = "default-policy";
|
|
469
|
+
var policyActionPrecedence = [
|
|
470
|
+
"block",
|
|
471
|
+
"requireApproval",
|
|
472
|
+
"redact",
|
|
473
|
+
"logOnly",
|
|
474
|
+
"allow"
|
|
475
|
+
];
|
|
476
|
+
function evaluateToolPolicies(input) {
|
|
477
|
+
const applicablePolicies = input.policies.filter(
|
|
478
|
+
(policy2) => doesPolicyApplyToTool(policy2, input.context.toolName)
|
|
479
|
+
);
|
|
480
|
+
const [firstPolicy, ...remainingPolicies] = applicablePolicies;
|
|
481
|
+
if (firstPolicy === void 0) {
|
|
482
|
+
return buildDefaultAllowEvaluation(input.context, input.timestamp);
|
|
483
|
+
}
|
|
484
|
+
const firstDecision = evaluateSingleToolPolicy(
|
|
485
|
+
firstPolicy,
|
|
486
|
+
input.context,
|
|
487
|
+
input.timestamp
|
|
488
|
+
);
|
|
489
|
+
const remainingDecisions = remainingPolicies.map(
|
|
490
|
+
(policy2) => evaluateSingleToolPolicy(policy2, input.context, input.timestamp)
|
|
491
|
+
);
|
|
492
|
+
const strongestDecision = resolveStrongestPolicyDecision(
|
|
493
|
+
firstDecision,
|
|
494
|
+
remainingDecisions
|
|
495
|
+
);
|
|
496
|
+
return {
|
|
497
|
+
action: strongestDecision.action,
|
|
498
|
+
reason: strongestDecision.reason,
|
|
499
|
+
policyName: strongestDecision.policyName,
|
|
500
|
+
decisions: [firstDecision, ...remainingDecisions]
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
function doesPolicyApplyToTool(policy2, toolName) {
|
|
504
|
+
if (policy2.appliesToToolNames === void 0) {
|
|
505
|
+
return true;
|
|
506
|
+
}
|
|
507
|
+
return policy2.appliesToToolNames.includes(toolName);
|
|
508
|
+
}
|
|
509
|
+
function evaluateSingleToolPolicy(policy2, context, timestamp) {
|
|
510
|
+
for (const effectName of toolPolicyEffectNames) {
|
|
511
|
+
const triggeredOutcome = executeToolPolicyRule(policy2, effectName, context);
|
|
512
|
+
if (triggeredOutcome !== null) {
|
|
513
|
+
return buildPolicyDecision({
|
|
514
|
+
policyName: policy2.name,
|
|
515
|
+
action: triggeredOutcome.action,
|
|
516
|
+
reason: triggeredOutcome.reason,
|
|
517
|
+
context,
|
|
518
|
+
timestamp
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
return buildPolicyDecision({
|
|
523
|
+
policyName: policy2.name,
|
|
524
|
+
action: "allow",
|
|
525
|
+
reason: "no rules matched",
|
|
526
|
+
context,
|
|
527
|
+
timestamp
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
function executeToolPolicyRule(policy2, effectName, context) {
|
|
531
|
+
const evaluateRule = policy2[effectName];
|
|
532
|
+
if (evaluateRule === void 0) {
|
|
533
|
+
return null;
|
|
534
|
+
}
|
|
535
|
+
try {
|
|
536
|
+
const ruleResult = evaluateRule(context);
|
|
537
|
+
if (ruleResult === false) {
|
|
538
|
+
return null;
|
|
539
|
+
}
|
|
540
|
+
if (ruleResult === true) {
|
|
541
|
+
return {
|
|
542
|
+
action: effectName,
|
|
543
|
+
reason: buildGeneratedEffectReason(effectName, policy2.name)
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
return {
|
|
547
|
+
action: effectName,
|
|
548
|
+
reason: ruleResult
|
|
549
|
+
};
|
|
550
|
+
} catch (caughtError) {
|
|
551
|
+
const errorMessage = caughtError instanceof Error ? caughtError.message : String(caughtError);
|
|
552
|
+
return {
|
|
553
|
+
action: "block",
|
|
554
|
+
reason: `blocked by policy ${policy2.name}: ${effectName} rule threw: ${errorMessage}`
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
function buildGeneratedEffectReason(effectName, policyName) {
|
|
559
|
+
const reasonsByEffectName = {
|
|
560
|
+
block: `blocked by policy ${policyName}`,
|
|
561
|
+
requireApproval: `approval required by policy ${policyName}`,
|
|
562
|
+
redact: `redacted by policy ${policyName}`,
|
|
563
|
+
logOnly: `logged by policy ${policyName}`
|
|
564
|
+
};
|
|
565
|
+
return reasonsByEffectName[effectName];
|
|
566
|
+
}
|
|
567
|
+
function buildPolicyDecision(input) {
|
|
568
|
+
return import_runs.PolicyDecisionSchema.parse({
|
|
569
|
+
policyName: input.policyName,
|
|
570
|
+
action: input.action,
|
|
571
|
+
agentName: input.context.agentName,
|
|
572
|
+
toolName: input.context.toolName,
|
|
573
|
+
reason: input.reason,
|
|
574
|
+
timestamp: input.timestamp
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
function buildDefaultAllowEvaluation(context, timestamp) {
|
|
578
|
+
const defaultDecision = buildPolicyDecision({
|
|
579
|
+
policyName: defaultPolicyName,
|
|
580
|
+
action: "allow",
|
|
581
|
+
reason: `no policies matched tool ${context.toolName}`,
|
|
582
|
+
context,
|
|
583
|
+
timestamp
|
|
584
|
+
});
|
|
585
|
+
return {
|
|
586
|
+
action: defaultDecision.action,
|
|
587
|
+
reason: defaultDecision.reason,
|
|
588
|
+
policyName: defaultDecision.policyName,
|
|
589
|
+
decisions: [defaultDecision]
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
function resolveStrongestPolicyDecision(firstDecision, remainingDecisions) {
|
|
593
|
+
let strongestDecision = firstDecision;
|
|
594
|
+
for (const decision of remainingDecisions) {
|
|
595
|
+
const decisionRank = policyActionPrecedence.indexOf(decision.action);
|
|
596
|
+
const strongestRank = policyActionPrecedence.indexOf(
|
|
597
|
+
strongestDecision.action
|
|
598
|
+
);
|
|
599
|
+
if (decisionRank < strongestRank) {
|
|
600
|
+
strongestDecision = decision;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
return strongestDecision;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// src/policies/policy-evaluated-event.ts
|
|
607
|
+
var import_trace_events = require("@assemble-dev/shared-types/trace-events");
|
|
608
|
+
function buildPolicyEvaluatedEvent(input) {
|
|
609
|
+
return import_trace_events.PolicyEvaluatedEventSchema.parse({
|
|
610
|
+
type: "policy.evaluated",
|
|
611
|
+
id: input.id,
|
|
612
|
+
runId: input.runId,
|
|
613
|
+
workflowName: input.workflowName,
|
|
614
|
+
timestamp: input.timestamp,
|
|
615
|
+
parentEventId: input.parentEventId,
|
|
616
|
+
redacted: input.redacted,
|
|
617
|
+
policyName: input.policyName,
|
|
618
|
+
action: input.action,
|
|
619
|
+
reason: input.reason,
|
|
620
|
+
toolName: input.toolName,
|
|
621
|
+
agentName: input.agentName
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
function buildPolicyEvaluatedEvents(input) {
|
|
625
|
+
return input.decisionEvents.map(
|
|
626
|
+
(decisionEvent) => buildPolicyEvaluatedEvent({
|
|
627
|
+
id: decisionEvent.id,
|
|
628
|
+
runId: input.runId,
|
|
629
|
+
workflowName: input.workflowName,
|
|
630
|
+
timestamp: input.timestamp,
|
|
631
|
+
parentEventId: input.parentEventId,
|
|
632
|
+
redacted: input.redacted,
|
|
633
|
+
policyName: decisionEvent.decision.policyName,
|
|
634
|
+
action: decisionEvent.decision.action,
|
|
635
|
+
reason: decisionEvent.decision.reason,
|
|
636
|
+
toolName: input.toolName,
|
|
637
|
+
agentName: input.agentName
|
|
638
|
+
})
|
|
639
|
+
);
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
// src/tools/tool-execution.ts
|
|
643
|
+
function buildToolValidationFailureMessage(target, toolName, issues) {
|
|
644
|
+
const issueSummary = issues.map((issue) => issue.message).join("; ");
|
|
645
|
+
return `${target} validation failed for tool ${toolName}: ${issueSummary}`;
|
|
646
|
+
}
|
|
647
|
+
async function runToolExecutionAttempts(toolDefinition, input) {
|
|
648
|
+
let lastError = {
|
|
649
|
+
code: "EXECUTION_FAILED",
|
|
650
|
+
message: `tool ${toolDefinition.name} failed before executing`
|
|
651
|
+
};
|
|
652
|
+
for (let attempt = 1; attempt <= toolDefinition.retry.maxAttempts; attempt += 1) {
|
|
653
|
+
try {
|
|
654
|
+
return { output: await executeToolAttemptWithTimeout(toolDefinition, input) };
|
|
655
|
+
} catch (caughtError) {
|
|
656
|
+
lastError = {
|
|
657
|
+
code: "EXECUTION_FAILED",
|
|
658
|
+
message: caughtError instanceof Error ? caughtError.message : String(caughtError)
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
return { error: lastError };
|
|
663
|
+
}
|
|
664
|
+
function validateToolExecutionOutput(toolDefinition, output) {
|
|
665
|
+
if (toolDefinition.outputSchema === null) {
|
|
666
|
+
return { output };
|
|
667
|
+
}
|
|
668
|
+
const parsedOutput = toolDefinition.outputSchema.safeParse(output);
|
|
669
|
+
if (parsedOutput.success) {
|
|
670
|
+
return { output: parsedOutput.data };
|
|
671
|
+
}
|
|
672
|
+
return {
|
|
673
|
+
error: {
|
|
674
|
+
code: "VALIDATION_FAILED",
|
|
675
|
+
message: buildToolValidationFailureMessage(
|
|
676
|
+
"output",
|
|
677
|
+
toolDefinition.name,
|
|
678
|
+
parsedOutput.error.issues
|
|
679
|
+
)
|
|
680
|
+
}
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
async function executeToolAttemptWithTimeout(toolDefinition, input) {
|
|
684
|
+
const timeoutMs = toolDefinition.timeoutMs;
|
|
685
|
+
if (timeoutMs === null) {
|
|
686
|
+
return await toolDefinition.execute(input);
|
|
687
|
+
}
|
|
688
|
+
return await new Promise((resolveAttempt, rejectAttempt) => {
|
|
689
|
+
const timeoutHandle = setTimeout(() => {
|
|
690
|
+
rejectAttempt(
|
|
691
|
+
new Error(
|
|
692
|
+
`tool ${toolDefinition.name} timed out after ${timeoutMs}ms`
|
|
693
|
+
)
|
|
694
|
+
);
|
|
695
|
+
}, timeoutMs);
|
|
696
|
+
const settleWithOutput = (output) => {
|
|
697
|
+
clearTimeout(timeoutHandle);
|
|
698
|
+
resolveAttempt(output);
|
|
699
|
+
};
|
|
700
|
+
const settleWithError = (executionError) => {
|
|
701
|
+
clearTimeout(timeoutHandle);
|
|
702
|
+
rejectAttempt(executionError);
|
|
703
|
+
};
|
|
704
|
+
void toolDefinition.execute(input).then(settleWithOutput, settleWithError);
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// src/tools/tool-trace-events.ts
|
|
709
|
+
var import_shared_utils = require("@assemble-dev/shared-utils");
|
|
710
|
+
var import_redaction = require("@assemble-dev/shared-utils/redaction");
|
|
711
|
+
function buildToolEventBase(context, parentEventId, redacted) {
|
|
712
|
+
return {
|
|
713
|
+
id: (0, import_shared_utils.generateTraceEventId)(),
|
|
714
|
+
runId: context.runId,
|
|
715
|
+
workflowName: context.workflowName,
|
|
716
|
+
timestamp: context.now().toISOString(),
|
|
717
|
+
parentEventId,
|
|
718
|
+
redacted
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
function emitToolRequestedEvent(context, input, mode) {
|
|
722
|
+
const redaction = (0, import_redaction.redactTracePayload)(input, {
|
|
723
|
+
mode,
|
|
724
|
+
sensitiveFieldNames: context.sensitiveFieldNames
|
|
725
|
+
});
|
|
726
|
+
const emittedEvent = context.traceBus.emitTraceEvent({
|
|
727
|
+
...buildToolEventBase(context, context.parentEventId, redaction.redacted),
|
|
728
|
+
type: "tool.requested",
|
|
729
|
+
toolName: context.toolName,
|
|
730
|
+
agentName: context.agentName,
|
|
731
|
+
input: redaction.payload
|
|
732
|
+
});
|
|
733
|
+
return emittedEvent.id;
|
|
734
|
+
}
|
|
735
|
+
function emitToolBlockedEvent(context, requestEventId, policyName, reason) {
|
|
736
|
+
context.traceBus.emitTraceEvent({
|
|
737
|
+
...buildToolEventBase(context, requestEventId, false),
|
|
738
|
+
type: "tool.blocked",
|
|
739
|
+
toolName: context.toolName,
|
|
740
|
+
agentName: context.agentName,
|
|
741
|
+
policyName,
|
|
742
|
+
reason
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
function emitToolCompletedEvent(context, requestEventId, output, latencyMs) {
|
|
746
|
+
const redaction = (0, import_redaction.redactTracePayload)(output, {
|
|
747
|
+
mode: context.redactionMode,
|
|
748
|
+
sensitiveFieldNames: context.sensitiveFieldNames
|
|
749
|
+
});
|
|
750
|
+
context.traceBus.emitTraceEvent({
|
|
751
|
+
...buildToolEventBase(context, requestEventId, redaction.redacted),
|
|
752
|
+
type: "tool.completed",
|
|
753
|
+
toolName: context.toolName,
|
|
754
|
+
agentName: context.agentName,
|
|
755
|
+
output: redaction.payload,
|
|
756
|
+
latencyMs
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
function emitToolFailedEvent(context, requestEventId, error, latencyMs) {
|
|
760
|
+
const redaction = (0, import_redaction.redactTracePayload)(error.message, {
|
|
761
|
+
mode: context.redactionMode,
|
|
762
|
+
sensitiveFieldNames: context.sensitiveFieldNames
|
|
763
|
+
});
|
|
764
|
+
const message = typeof redaction.payload === "string" ? redaction.payload : JSON.stringify(redaction.payload);
|
|
765
|
+
context.traceBus.emitTraceEvent({
|
|
766
|
+
...buildToolEventBase(context, requestEventId, redaction.redacted),
|
|
767
|
+
type: "tool.failed",
|
|
768
|
+
toolName: context.toolName,
|
|
769
|
+
agentName: context.agentName,
|
|
770
|
+
error: { message, code: error.code },
|
|
771
|
+
latencyMs
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
function emitToolApprovedEvent(context, toolRequestEventId, approvalId) {
|
|
775
|
+
const emittedEvent = context.traceBus.emitTraceEvent({
|
|
776
|
+
...buildToolEventBase(context, toolRequestEventId, false),
|
|
777
|
+
type: "tool.approved",
|
|
778
|
+
toolName: context.toolName,
|
|
779
|
+
agentName: context.agentName,
|
|
780
|
+
approvalId
|
|
781
|
+
});
|
|
782
|
+
return emittedEvent.id;
|
|
783
|
+
}
|
|
784
|
+
function emitApprovalRequiredEvent(context, toolRequestEventId, approvalId, reason) {
|
|
785
|
+
context.traceBus.emitTraceEvent({
|
|
786
|
+
...buildToolEventBase(context, toolRequestEventId, false),
|
|
787
|
+
type: "approval.required",
|
|
788
|
+
approvalId,
|
|
789
|
+
toolName: context.toolName,
|
|
790
|
+
agentName: context.agentName,
|
|
791
|
+
reason
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// src/tools/tool-execution-outcome.ts
|
|
796
|
+
async function executeToolAndEmitOutcome(context, toolDefinition, input, requestEventId, mockExecution) {
|
|
797
|
+
const startedAtMs = context.now().getTime();
|
|
798
|
+
if (toolDefinition.dryRun || mockExecution !== void 0) {
|
|
799
|
+
return completeToolCallWithoutExecution(
|
|
800
|
+
context,
|
|
801
|
+
toolDefinition,
|
|
802
|
+
input,
|
|
803
|
+
requestEventId,
|
|
804
|
+
mockExecution,
|
|
805
|
+
startedAtMs
|
|
806
|
+
);
|
|
807
|
+
}
|
|
808
|
+
const executionResult = await runToolExecutionAttempts(toolDefinition, input);
|
|
809
|
+
if ("error" in executionResult) {
|
|
810
|
+
return failToolCallAfterRequest(
|
|
811
|
+
context,
|
|
812
|
+
requestEventId,
|
|
813
|
+
executionResult.error,
|
|
814
|
+
calculateElapsedMs(context, startedAtMs)
|
|
815
|
+
);
|
|
816
|
+
}
|
|
817
|
+
const validatedOutput = validateToolExecutionOutput(
|
|
818
|
+
toolDefinition,
|
|
819
|
+
executionResult.output
|
|
820
|
+
);
|
|
821
|
+
if ("error" in validatedOutput) {
|
|
822
|
+
return failToolCallAfterRequest(
|
|
823
|
+
context,
|
|
824
|
+
requestEventId,
|
|
825
|
+
validatedOutput.error,
|
|
826
|
+
calculateElapsedMs(context, startedAtMs)
|
|
827
|
+
);
|
|
828
|
+
}
|
|
829
|
+
const latencyMs = calculateElapsedMs(context, startedAtMs);
|
|
830
|
+
emitToolCompletedEvent(context, requestEventId, validatedOutput.output, latencyMs);
|
|
831
|
+
return {
|
|
832
|
+
status: "completed",
|
|
833
|
+
output: validatedOutput.output,
|
|
834
|
+
toolRequestEventId: requestEventId,
|
|
835
|
+
latencyMs
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
function completeToolCallWithoutExecution(context, toolDefinition, input, requestEventId, mockExecution, startedAtMs) {
|
|
839
|
+
const output = mockExecution !== void 0 ? mockExecution(input) : { dryRun: true, toolName: toolDefinition.name };
|
|
840
|
+
const latencyMs = calculateElapsedMs(context, startedAtMs);
|
|
841
|
+
emitToolCompletedEvent(context, requestEventId, output, latencyMs);
|
|
842
|
+
return {
|
|
843
|
+
status: "completed",
|
|
844
|
+
output,
|
|
845
|
+
toolRequestEventId: requestEventId,
|
|
846
|
+
latencyMs
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
function failToolCallAfterRequest(context, requestEventId, error, latencyMs) {
|
|
850
|
+
emitToolFailedEvent(context, requestEventId, error, latencyMs);
|
|
851
|
+
return { status: "failed", error };
|
|
852
|
+
}
|
|
853
|
+
function calculateElapsedMs(context, startedAtMs) {
|
|
854
|
+
return Math.max(0, context.now().getTime() - startedAtMs);
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
// src/tools/tool-broker.ts
|
|
858
|
+
var agentAllowlistPolicyName = "agent-allowlist";
|
|
859
|
+
var staticApprovalReason = "tool requires approval";
|
|
860
|
+
async function executeToolCall(callInput) {
|
|
861
|
+
const context = buildToolBrokerContext(callInput);
|
|
862
|
+
const parsedInput = callInput.tool.inputSchema.safeParse(callInput.input);
|
|
863
|
+
if (!parsedInput.success) {
|
|
864
|
+
return failToolCallForInvalidInput(
|
|
865
|
+
context,
|
|
866
|
+
callInput.input,
|
|
867
|
+
parsedInput.error.issues
|
|
868
|
+
);
|
|
869
|
+
}
|
|
870
|
+
if (callInput.grantedApproval !== void 0) {
|
|
871
|
+
return await executeGrantedApprovalToolCall(
|
|
872
|
+
context,
|
|
873
|
+
callInput,
|
|
874
|
+
parsedInput.data,
|
|
875
|
+
callInput.grantedApproval
|
|
876
|
+
);
|
|
877
|
+
}
|
|
878
|
+
if (!isAgentAllowedToUseTool(callInput.tool, callInput.agentName)) {
|
|
879
|
+
return blockToolCallForDisallowedAgent(context, parsedInput.data);
|
|
880
|
+
}
|
|
881
|
+
const evaluation = evaluatePoliciesForToolCall(
|
|
882
|
+
context,
|
|
883
|
+
callInput.policies,
|
|
884
|
+
parsedInput.data
|
|
885
|
+
);
|
|
886
|
+
if (evaluation !== null && evaluation.action === "block") {
|
|
887
|
+
return blockToolCallByPolicy(context, parsedInput.data, evaluation);
|
|
888
|
+
}
|
|
889
|
+
const requestEventId = emitToolRequestedEvent(
|
|
890
|
+
context,
|
|
891
|
+
parsedInput.data,
|
|
892
|
+
resolveInputRedactionMode(context, evaluation)
|
|
893
|
+
);
|
|
894
|
+
const approvalReason = resolveApprovalReason(callInput.tool, evaluation);
|
|
895
|
+
if (approvalReason !== null) {
|
|
896
|
+
return requireApprovalForToolCall(context, requestEventId, approvalReason);
|
|
897
|
+
}
|
|
898
|
+
return await executeToolAndEmitOutcome(
|
|
899
|
+
context,
|
|
900
|
+
callInput.tool,
|
|
901
|
+
parsedInput.data,
|
|
902
|
+
requestEventId,
|
|
903
|
+
callInput.mockExecution
|
|
904
|
+
);
|
|
905
|
+
}
|
|
906
|
+
function buildToolBrokerContext(callInput) {
|
|
907
|
+
const runState = callInput.traceBus.getRunState();
|
|
908
|
+
return {
|
|
909
|
+
toolName: callInput.tool.name,
|
|
910
|
+
agentName: callInput.agentName,
|
|
911
|
+
runId: runState.runId,
|
|
912
|
+
workflowName: runState.workflowName,
|
|
913
|
+
parentEventId: callInput.parentEventId ?? null,
|
|
914
|
+
traceBus: callInput.traceBus,
|
|
915
|
+
now: callInput.now ?? (() => /* @__PURE__ */ new Date()),
|
|
916
|
+
redactionMode: (0, import_redaction2.resolveRedactionMode)(callInput.redactionMode),
|
|
917
|
+
sensitiveFieldNames: callInput.tool.sensitiveFieldNames
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
function isAgentAllowedToUseTool(toolDefinition, agentName) {
|
|
921
|
+
if (toolDefinition.allowedAgentNames === null) {
|
|
922
|
+
return true;
|
|
923
|
+
}
|
|
924
|
+
return toolDefinition.allowedAgentNames.includes(agentName);
|
|
925
|
+
}
|
|
926
|
+
function resolveInputRedactionMode(context, evaluation) {
|
|
927
|
+
if (evaluation !== null && evaluation.action === "redact") {
|
|
928
|
+
return "redacted";
|
|
929
|
+
}
|
|
930
|
+
return context.redactionMode;
|
|
931
|
+
}
|
|
932
|
+
function resolveApprovalReason(toolDefinition, evaluation) {
|
|
933
|
+
if (evaluation !== null && evaluation.action === "requireApproval") {
|
|
934
|
+
return evaluation.reason;
|
|
935
|
+
}
|
|
936
|
+
if (toolDefinition.requiresApproval) {
|
|
937
|
+
return staticApprovalReason;
|
|
938
|
+
}
|
|
939
|
+
return null;
|
|
940
|
+
}
|
|
941
|
+
function evaluatePoliciesForToolCall(context, policies, input) {
|
|
942
|
+
if (policies === void 0) {
|
|
943
|
+
return null;
|
|
944
|
+
}
|
|
945
|
+
const timestamp = context.now().toISOString();
|
|
946
|
+
const evaluation = evaluateToolPolicies({
|
|
947
|
+
policies,
|
|
948
|
+
context: {
|
|
949
|
+
toolName: context.toolName,
|
|
950
|
+
agentName: context.agentName,
|
|
951
|
+
input
|
|
952
|
+
},
|
|
953
|
+
timestamp
|
|
954
|
+
});
|
|
955
|
+
const policyEvents = buildPolicyEvaluatedEvents({
|
|
956
|
+
runId: context.runId,
|
|
957
|
+
workflowName: context.workflowName,
|
|
958
|
+
timestamp,
|
|
959
|
+
parentEventId: context.parentEventId,
|
|
960
|
+
redacted: false,
|
|
961
|
+
toolName: context.toolName,
|
|
962
|
+
agentName: context.agentName,
|
|
963
|
+
decisionEvents: evaluation.decisions.map((decision) => ({
|
|
964
|
+
id: (0, import_shared_utils2.generateTraceEventId)(),
|
|
965
|
+
decision
|
|
966
|
+
}))
|
|
967
|
+
});
|
|
968
|
+
for (const policyEvent of policyEvents) {
|
|
969
|
+
context.traceBus.emitTraceEvent(policyEvent);
|
|
970
|
+
}
|
|
971
|
+
return evaluation;
|
|
972
|
+
}
|
|
973
|
+
function failToolCallForInvalidInput(context, rawInput, issues) {
|
|
974
|
+
const requestEventId = emitToolRequestedEvent(
|
|
975
|
+
context,
|
|
976
|
+
rawInput,
|
|
977
|
+
context.redactionMode
|
|
978
|
+
);
|
|
979
|
+
const error = {
|
|
980
|
+
code: "VALIDATION_FAILED",
|
|
981
|
+
message: buildToolValidationFailureMessage(
|
|
982
|
+
"input",
|
|
983
|
+
context.toolName,
|
|
984
|
+
issues
|
|
985
|
+
)
|
|
986
|
+
};
|
|
987
|
+
emitToolFailedEvent(context, requestEventId, error, 0);
|
|
988
|
+
return { status: "failed", error };
|
|
989
|
+
}
|
|
990
|
+
function blockToolCallForDisallowedAgent(context, input) {
|
|
991
|
+
const reason = `agent ${context.agentName} is not allowed to use tool ${context.toolName}`;
|
|
992
|
+
const requestEventId = emitToolRequestedEvent(
|
|
993
|
+
context,
|
|
994
|
+
input,
|
|
995
|
+
context.redactionMode
|
|
996
|
+
);
|
|
997
|
+
emitToolBlockedEvent(context, requestEventId, agentAllowlistPolicyName, reason);
|
|
998
|
+
return { status: "blocked", reason };
|
|
999
|
+
}
|
|
1000
|
+
function blockToolCallByPolicy(context, input, evaluation) {
|
|
1001
|
+
const requestEventId = emitToolRequestedEvent(
|
|
1002
|
+
context,
|
|
1003
|
+
input,
|
|
1004
|
+
context.redactionMode
|
|
1005
|
+
);
|
|
1006
|
+
emitToolBlockedEvent(
|
|
1007
|
+
context,
|
|
1008
|
+
requestEventId,
|
|
1009
|
+
evaluation.policyName,
|
|
1010
|
+
evaluation.reason
|
|
1011
|
+
);
|
|
1012
|
+
return { status: "blocked", reason: evaluation.reason };
|
|
1013
|
+
}
|
|
1014
|
+
function requireApprovalForToolCall(context, toolRequestEventId, reason) {
|
|
1015
|
+
const approvalId = (0, import_shared_utils2.generateApprovalId)();
|
|
1016
|
+
emitApprovalRequiredEvent(context, toolRequestEventId, approvalId, reason);
|
|
1017
|
+
return { status: "awaiting_approval", approvalId, toolRequestEventId };
|
|
1018
|
+
}
|
|
1019
|
+
function ensureGrantedApprovalIsKnown(context, grantedApproval) {
|
|
1020
|
+
const runApproval = context.traceBus.getRunState().approvals.find((approval) => approval.id === grantedApproval.approvalId);
|
|
1021
|
+
if (runApproval === void 0 || runApproval.status === "rejected") {
|
|
1022
|
+
throw new import_errors8.AppError({
|
|
1023
|
+
code: import_errors7.AppErrorCode.Forbidden,
|
|
1024
|
+
message: `granted approval "${grantedApproval.approvalId}" does not match a grantable approval for run "${context.runId}"; the tool call cannot use this grant`,
|
|
1025
|
+
statusCode: 403,
|
|
1026
|
+
details: {
|
|
1027
|
+
approvalId: grantedApproval.approvalId,
|
|
1028
|
+
toolName: context.toolName,
|
|
1029
|
+
agentName: context.agentName,
|
|
1030
|
+
runId: context.runId
|
|
1031
|
+
}
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
async function executeGrantedApprovalToolCall(context, callInput, input, grantedApproval) {
|
|
1036
|
+
ensureGrantedApprovalIsKnown(context, grantedApproval);
|
|
1037
|
+
if (!isAgentAllowedToUseTool(callInput.tool, callInput.agentName)) {
|
|
1038
|
+
return blockToolCallForDisallowedAgent(context, input);
|
|
1039
|
+
}
|
|
1040
|
+
const evaluation = evaluatePoliciesForToolCall(
|
|
1041
|
+
context,
|
|
1042
|
+
callInput.policies,
|
|
1043
|
+
input
|
|
1044
|
+
);
|
|
1045
|
+
if (evaluation !== null && evaluation.action === "block") {
|
|
1046
|
+
return blockToolCallByPolicy(context, input, evaluation);
|
|
1047
|
+
}
|
|
1048
|
+
const approvedEventId = emitToolApprovedEvent(
|
|
1049
|
+
context,
|
|
1050
|
+
grantedApproval.toolRequestEventId,
|
|
1051
|
+
grantedApproval.approvalId
|
|
1052
|
+
);
|
|
1053
|
+
const requestEventId = grantedApproval.toolRequestEventId ?? approvedEventId;
|
|
1054
|
+
return await executeToolAndEmitOutcome(
|
|
1055
|
+
context,
|
|
1056
|
+
callInput.tool,
|
|
1057
|
+
input,
|
|
1058
|
+
requestEventId,
|
|
1059
|
+
callInput.mockExecution
|
|
1060
|
+
);
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
// src/teams/team-definition.ts
|
|
1064
|
+
var import_errors9 = require("@assemble-dev/shared-types/errors");
|
|
1065
|
+
var import_errors10 = require("@assemble-dev/shared-utils/errors");
|
|
1066
|
+
function team(config) {
|
|
1067
|
+
ensureTeamNameIsPresent(config.name);
|
|
1068
|
+
ensureTeamHasAgents(config.name, config.agents);
|
|
1069
|
+
ensureTeamAgentNamesAreUnique(config.name, config.agents);
|
|
1070
|
+
return Object.freeze({
|
|
1071
|
+
name: config.name,
|
|
1072
|
+
description: config.description ?? "",
|
|
1073
|
+
agents: Object.freeze([...config.agents]),
|
|
1074
|
+
agentNames: Object.freeze(config.agents.map((member) => member.name))
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
1077
|
+
function ensureTeamNameIsPresent(name) {
|
|
1078
|
+
if (name.trim() !== "") {
|
|
1079
|
+
return;
|
|
1080
|
+
}
|
|
1081
|
+
throw new import_errors10.AppError({
|
|
1082
|
+
code: import_errors9.AppErrorCode.BadRequest,
|
|
1083
|
+
message: "team name must not be empty",
|
|
1084
|
+
statusCode: 400
|
|
1085
|
+
});
|
|
1086
|
+
}
|
|
1087
|
+
function ensureTeamHasAgents(teamName, agents) {
|
|
1088
|
+
if (agents.length > 0) {
|
|
1089
|
+
return;
|
|
1090
|
+
}
|
|
1091
|
+
throw new import_errors10.AppError({
|
|
1092
|
+
code: import_errors9.AppErrorCode.BadRequest,
|
|
1093
|
+
message: `team "${teamName}" must include at least one agent`,
|
|
1094
|
+
statusCode: 400
|
|
1095
|
+
});
|
|
1096
|
+
}
|
|
1097
|
+
function ensureTeamAgentNamesAreUnique(teamName, agents) {
|
|
1098
|
+
const seenAgentNames = /* @__PURE__ */ new Set();
|
|
1099
|
+
for (const member of agents) {
|
|
1100
|
+
if (seenAgentNames.has(member.name)) {
|
|
1101
|
+
throw new import_errors10.AppError({
|
|
1102
|
+
code: import_errors9.AppErrorCode.BadRequest,
|
|
1103
|
+
message: `team "${teamName}" contains duplicate agent name "${member.name}"`,
|
|
1104
|
+
statusCode: 400
|
|
1105
|
+
});
|
|
1106
|
+
}
|
|
1107
|
+
seenAgentNames.add(member.name);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
// src/teams/team-presets.ts
|
|
1112
|
+
function buildPipelineTeamWorkflowConfig(input) {
|
|
1113
|
+
return {
|
|
1114
|
+
name: input.name,
|
|
1115
|
+
inputSchema: input.inputSchema,
|
|
1116
|
+
outputSchema: input.outputSchema,
|
|
1117
|
+
agents: [...input.team.agents],
|
|
1118
|
+
topology: { kind: "pipeline" }
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
function buildSupervisorTeamWorkflowConfig(input) {
|
|
1122
|
+
return {
|
|
1123
|
+
name: input.name,
|
|
1124
|
+
inputSchema: input.inputSchema,
|
|
1125
|
+
outputSchema: input.outputSchema,
|
|
1126
|
+
agents: [...input.team.agents],
|
|
1127
|
+
topology: {
|
|
1128
|
+
kind: "supervisor",
|
|
1129
|
+
supervisorName: input.supervisorName,
|
|
1130
|
+
route: input.route,
|
|
1131
|
+
maxSteps: input.maxSteps
|
|
1132
|
+
}
|
|
1133
|
+
};
|
|
1134
|
+
}
|
|
1135
|
+
function buildReviewPanelWorkflowConfig(input) {
|
|
1136
|
+
return {
|
|
1137
|
+
name: input.name,
|
|
1138
|
+
inputSchema: input.inputSchema,
|
|
1139
|
+
agents: [...input.team.agents],
|
|
1140
|
+
topology: { kind: "parallel" }
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
// src/policies/policy-definition.ts
|
|
1145
|
+
var import_errors11 = require("@assemble-dev/shared-types/errors");
|
|
1146
|
+
var import_errors12 = require("@assemble-dev/shared-utils/errors");
|
|
1147
|
+
function policy(config) {
|
|
1148
|
+
ensurePolicyNameIsPresent(config.name);
|
|
1149
|
+
ensurePolicyDefinesAtLeastOneRule(config);
|
|
1150
|
+
return Object.freeze({ ...config });
|
|
1151
|
+
}
|
|
1152
|
+
function ensurePolicyNameIsPresent(name) {
|
|
1153
|
+
if (name.trim() !== "") {
|
|
1154
|
+
return;
|
|
1155
|
+
}
|
|
1156
|
+
throw new import_errors12.AppError({
|
|
1157
|
+
code: import_errors11.AppErrorCode.BadRequest,
|
|
1158
|
+
message: "policy name must not be empty",
|
|
1159
|
+
statusCode: 400
|
|
1160
|
+
});
|
|
1161
|
+
}
|
|
1162
|
+
function ensurePolicyDefinesAtLeastOneRule(config) {
|
|
1163
|
+
const hasRule = toolPolicyEffectNames.some(
|
|
1164
|
+
(effectName) => config[effectName] !== void 0
|
|
1165
|
+
);
|
|
1166
|
+
if (hasRule) {
|
|
1167
|
+
return;
|
|
1168
|
+
}
|
|
1169
|
+
throw new import_errors12.AppError({
|
|
1170
|
+
code: import_errors11.AppErrorCode.BadRequest,
|
|
1171
|
+
message: `policy "${config.name}" must define at least one rule (${toolPolicyEffectNames.join(", ")})`,
|
|
1172
|
+
statusCode: 400
|
|
1173
|
+
});
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
// src/workflows/workflow-runtime.ts
|
|
1177
|
+
var import_errors23 = require("@assemble-dev/shared-types/errors");
|
|
1178
|
+
var import_ids3 = require("@assemble-dev/shared-utils/ids");
|
|
1179
|
+
var import_errors24 = require("@assemble-dev/shared-utils/errors");
|
|
1180
|
+
|
|
1181
|
+
// src/memory/artifact-store.ts
|
|
1182
|
+
var import_json2 = require("@assemble-dev/shared-types/json");
|
|
1183
|
+
var import_runs2 = require("@assemble-dev/shared-types/runs");
|
|
1184
|
+
function buildRunArtifactsMemoryKey(runId) {
|
|
1185
|
+
return `run:${runId}:artifacts`;
|
|
1186
|
+
}
|
|
1187
|
+
function mapRunArtifactToJsonValue(artifact) {
|
|
1188
|
+
const artifactValue = {
|
|
1189
|
+
key: artifact.key,
|
|
1190
|
+
value: artifact.value,
|
|
1191
|
+
createdAt: artifact.createdAt
|
|
1192
|
+
};
|
|
1193
|
+
if (artifact.producedByAgentName !== void 0) {
|
|
1194
|
+
artifactValue.producedByAgentName = artifact.producedByAgentName;
|
|
1195
|
+
}
|
|
1196
|
+
return artifactValue;
|
|
1197
|
+
}
|
|
1198
|
+
async function loadRunArtifactsRecord(provider, memoryKey) {
|
|
1199
|
+
const storedArtifacts = await provider.getMemoryValue(memoryKey);
|
|
1200
|
+
if (storedArtifacts === null) {
|
|
1201
|
+
return {};
|
|
1202
|
+
}
|
|
1203
|
+
return import_json2.JsonObjectSchema.parse(storedArtifacts);
|
|
1204
|
+
}
|
|
1205
|
+
function createArtifactStore(options) {
|
|
1206
|
+
const memoryKey = buildRunArtifactsMemoryKey(options.runId);
|
|
1207
|
+
return {
|
|
1208
|
+
async saveRunArtifact(artifact) {
|
|
1209
|
+
const artifactsRecord = await loadRunArtifactsRecord(
|
|
1210
|
+
options.provider,
|
|
1211
|
+
memoryKey
|
|
1212
|
+
);
|
|
1213
|
+
artifactsRecord[artifact.key] = mapRunArtifactToJsonValue(artifact);
|
|
1214
|
+
await options.provider.setMemoryValue(memoryKey, artifactsRecord);
|
|
1215
|
+
},
|
|
1216
|
+
async getRunArtifact(key) {
|
|
1217
|
+
const artifactsRecord = await loadRunArtifactsRecord(
|
|
1218
|
+
options.provider,
|
|
1219
|
+
memoryKey
|
|
1220
|
+
);
|
|
1221
|
+
const storedArtifact = artifactsRecord[key];
|
|
1222
|
+
if (storedArtifact === void 0) {
|
|
1223
|
+
return null;
|
|
1224
|
+
}
|
|
1225
|
+
return import_runs2.RunArtifactSchema.parse(storedArtifact);
|
|
1226
|
+
},
|
|
1227
|
+
async listRunArtifacts() {
|
|
1228
|
+
const artifactsRecord = await loadRunArtifactsRecord(
|
|
1229
|
+
options.provider,
|
|
1230
|
+
memoryKey
|
|
1231
|
+
);
|
|
1232
|
+
return Object.values(artifactsRecord).map(
|
|
1233
|
+
(storedArtifact) => import_runs2.RunArtifactSchema.parse(storedArtifact)
|
|
1234
|
+
);
|
|
1235
|
+
}
|
|
1236
|
+
};
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
// src/memory/memory-provider.ts
|
|
1240
|
+
function buildAppendedMemoryValue(existingValue, appendedValue) {
|
|
1241
|
+
if (existingValue === void 0) {
|
|
1242
|
+
return [appendedValue];
|
|
1243
|
+
}
|
|
1244
|
+
if (Array.isArray(existingValue)) {
|
|
1245
|
+
return [...existingValue, appendedValue];
|
|
1246
|
+
}
|
|
1247
|
+
return [existingValue, appendedValue];
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
// src/memory/in-memory-provider.ts
|
|
1251
|
+
function cloneJsonValue(value) {
|
|
1252
|
+
return structuredClone(value);
|
|
1253
|
+
}
|
|
1254
|
+
function createInMemoryMemoryProvider() {
|
|
1255
|
+
const memoryEntries = /* @__PURE__ */ new Map();
|
|
1256
|
+
return {
|
|
1257
|
+
async getMemoryValue(key) {
|
|
1258
|
+
const storedValue = memoryEntries.get(key);
|
|
1259
|
+
if (storedValue === void 0) {
|
|
1260
|
+
return await Promise.resolve(null);
|
|
1261
|
+
}
|
|
1262
|
+
return await Promise.resolve(cloneJsonValue(storedValue));
|
|
1263
|
+
},
|
|
1264
|
+
async setMemoryValue(key, value) {
|
|
1265
|
+
memoryEntries.set(key, cloneJsonValue(value));
|
|
1266
|
+
await Promise.resolve();
|
|
1267
|
+
},
|
|
1268
|
+
async appendMemoryValue(key, value) {
|
|
1269
|
+
memoryEntries.set(
|
|
1270
|
+
key,
|
|
1271
|
+
buildAppendedMemoryValue(memoryEntries.get(key), cloneJsonValue(value))
|
|
1272
|
+
);
|
|
1273
|
+
await Promise.resolve();
|
|
1274
|
+
},
|
|
1275
|
+
async listMemoryKeys() {
|
|
1276
|
+
return await Promise.resolve([...memoryEntries.keys()]);
|
|
1277
|
+
}
|
|
1278
|
+
};
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
// src/memory/memory-config.ts
|
|
1282
|
+
function resolveWorkflowMemoryConfig(config) {
|
|
1283
|
+
if (config !== void 0) {
|
|
1284
|
+
return { provider: config.provider };
|
|
1285
|
+
}
|
|
1286
|
+
return { provider: createInMemoryMemoryProvider() };
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
// src/memory/thread-memory.ts
|
|
1290
|
+
var import_runs3 = require("@assemble-dev/shared-types/runs");
|
|
1291
|
+
function buildThreadMessagesMemoryKey(threadKey) {
|
|
1292
|
+
return `thread:${threadKey}:messages`;
|
|
1293
|
+
}
|
|
1294
|
+
function mapRunMessageToJsonValue(message) {
|
|
1295
|
+
const messageValue = {
|
|
1296
|
+
role: message.role,
|
|
1297
|
+
content: message.content,
|
|
1298
|
+
timestamp: message.timestamp
|
|
1299
|
+
};
|
|
1300
|
+
if (message.agentName !== void 0) {
|
|
1301
|
+
messageValue.agentName = message.agentName;
|
|
1302
|
+
}
|
|
1303
|
+
if (message.toolName !== void 0) {
|
|
1304
|
+
messageValue.toolName = message.toolName;
|
|
1305
|
+
}
|
|
1306
|
+
return messageValue;
|
|
1307
|
+
}
|
|
1308
|
+
function createThreadMemory(options) {
|
|
1309
|
+
const memoryKey = buildThreadMessagesMemoryKey(options.threadKey);
|
|
1310
|
+
return {
|
|
1311
|
+
async appendThreadMessage(message) {
|
|
1312
|
+
await options.provider.appendMemoryValue(
|
|
1313
|
+
memoryKey,
|
|
1314
|
+
mapRunMessageToJsonValue(message)
|
|
1315
|
+
);
|
|
1316
|
+
},
|
|
1317
|
+
async listThreadMessages() {
|
|
1318
|
+
const storedMessages = await options.provider.getMemoryValue(memoryKey);
|
|
1319
|
+
if (storedMessages === null) {
|
|
1320
|
+
return [];
|
|
1321
|
+
}
|
|
1322
|
+
return import_runs3.RunMessageSchema.array().parse(storedMessages);
|
|
1323
|
+
}
|
|
1324
|
+
};
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
// src/tracing/trace-bus.ts
|
|
1328
|
+
var import_trace_events2 = require("@assemble-dev/shared-types/trace-events");
|
|
1329
|
+
|
|
1330
|
+
// src/tracing/run-state-error-mapper.ts
|
|
1331
|
+
function mapEventError(error, timestamp, agentName, toolName) {
|
|
1332
|
+
return {
|
|
1333
|
+
message: error.message,
|
|
1334
|
+
code: error.code,
|
|
1335
|
+
agentName,
|
|
1336
|
+
toolName,
|
|
1337
|
+
timestamp
|
|
1338
|
+
};
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
// src/tracing/run-state-agent-tool-reducers.ts
|
|
1342
|
+
function applyAgentStarted(state, event) {
|
|
1343
|
+
const agentCall = {
|
|
1344
|
+
id: event.id,
|
|
1345
|
+
agentName: event.agentName,
|
|
1346
|
+
status: "running",
|
|
1347
|
+
input: event.input,
|
|
1348
|
+
output: null,
|
|
1349
|
+
error: null,
|
|
1350
|
+
startedAt: event.timestamp,
|
|
1351
|
+
completedAt: null,
|
|
1352
|
+
latencyMs: null
|
|
1353
|
+
};
|
|
1354
|
+
return { ...state, agentCalls: [...state.agentCalls, agentCall] };
|
|
1355
|
+
}
|
|
1356
|
+
function applyAgentCompleted(state, event) {
|
|
1357
|
+
return updateAgentCall(state, event.parentEventId, event.agentName, {
|
|
1358
|
+
status: "completed",
|
|
1359
|
+
output: event.output,
|
|
1360
|
+
completedAt: event.timestamp,
|
|
1361
|
+
latencyMs: event.latencyMs
|
|
1362
|
+
});
|
|
1363
|
+
}
|
|
1364
|
+
function applyAgentFailed(state, event) {
|
|
1365
|
+
const failedState = updateAgentCall(
|
|
1366
|
+
state,
|
|
1367
|
+
event.parentEventId,
|
|
1368
|
+
event.agentName,
|
|
1369
|
+
{
|
|
1370
|
+
status: "failed",
|
|
1371
|
+
error: mapEventError(event.error, event.timestamp, event.agentName),
|
|
1372
|
+
completedAt: event.timestamp,
|
|
1373
|
+
latencyMs: event.latencyMs
|
|
1374
|
+
}
|
|
1375
|
+
);
|
|
1376
|
+
return {
|
|
1377
|
+
...failedState,
|
|
1378
|
+
errors: [
|
|
1379
|
+
...failedState.errors,
|
|
1380
|
+
mapEventError(event.error, event.timestamp, event.agentName)
|
|
1381
|
+
]
|
|
1382
|
+
};
|
|
1383
|
+
}
|
|
1384
|
+
function applyToolRequested(state, event) {
|
|
1385
|
+
const toolCall = {
|
|
1386
|
+
id: event.id,
|
|
1387
|
+
toolName: event.toolName,
|
|
1388
|
+
agentName: event.agentName,
|
|
1389
|
+
status: "requested",
|
|
1390
|
+
input: event.input,
|
|
1391
|
+
output: null,
|
|
1392
|
+
error: null,
|
|
1393
|
+
requestedAt: event.timestamp,
|
|
1394
|
+
completedAt: null,
|
|
1395
|
+
latencyMs: null
|
|
1396
|
+
};
|
|
1397
|
+
return { ...state, toolCalls: [...state.toolCalls, toolCall] };
|
|
1398
|
+
}
|
|
1399
|
+
function applyToolBlocked(state, event) {
|
|
1400
|
+
return applyToolStatus(state, event.parentEventId, event.toolName, {
|
|
1401
|
+
status: "blocked",
|
|
1402
|
+
completedAt: event.timestamp
|
|
1403
|
+
});
|
|
1404
|
+
}
|
|
1405
|
+
function applyToolCompleted(state, event) {
|
|
1406
|
+
return applyToolStatus(state, event.parentEventId, event.toolName, {
|
|
1407
|
+
status: "completed",
|
|
1408
|
+
output: event.output,
|
|
1409
|
+
completedAt: event.timestamp,
|
|
1410
|
+
latencyMs: event.latencyMs
|
|
1411
|
+
});
|
|
1412
|
+
}
|
|
1413
|
+
function applyToolFailed(state, event) {
|
|
1414
|
+
return applyToolStatus(state, event.parentEventId, event.toolName, {
|
|
1415
|
+
status: "failed",
|
|
1416
|
+
error: mapEventError(event.error, event.timestamp, void 0, event.toolName),
|
|
1417
|
+
completedAt: event.timestamp,
|
|
1418
|
+
latencyMs: event.latencyMs
|
|
1419
|
+
});
|
|
1420
|
+
}
|
|
1421
|
+
function updateAgentCall(state, parentEventId, agentName, update) {
|
|
1422
|
+
const targetIndex = findCallIndexByParentThenPredicate(
|
|
1423
|
+
state.agentCalls,
|
|
1424
|
+
parentEventId,
|
|
1425
|
+
(call) => call.agentName === agentName && call.status === "running"
|
|
1426
|
+
);
|
|
1427
|
+
if (targetIndex === -1) {
|
|
1428
|
+
return state;
|
|
1429
|
+
}
|
|
1430
|
+
const agentCalls = state.agentCalls.map(
|
|
1431
|
+
(call, index) => index === targetIndex ? { ...call, ...update } : call
|
|
1432
|
+
);
|
|
1433
|
+
return { ...state, agentCalls };
|
|
1434
|
+
}
|
|
1435
|
+
function applyToolStatus(state, parentEventId, toolName, update) {
|
|
1436
|
+
const targetIndex = findCallIndexByParentThenPredicate(
|
|
1437
|
+
state.toolCalls,
|
|
1438
|
+
parentEventId,
|
|
1439
|
+
(call) => call.toolName === toolName && (call.status === "requested" || call.status === "approved")
|
|
1440
|
+
);
|
|
1441
|
+
if (targetIndex === -1) {
|
|
1442
|
+
return state;
|
|
1443
|
+
}
|
|
1444
|
+
const toolCalls = state.toolCalls.map(
|
|
1445
|
+
(call, index) => index === targetIndex ? { ...call, ...update } : call
|
|
1446
|
+
);
|
|
1447
|
+
return { ...state, toolCalls };
|
|
1448
|
+
}
|
|
1449
|
+
function findCallIndexByParentThenPredicate(calls, parentEventId, fallbackPredicate) {
|
|
1450
|
+
if (parentEventId !== null) {
|
|
1451
|
+
const parentIndex = findLastIndex(
|
|
1452
|
+
calls,
|
|
1453
|
+
(call) => call.id === parentEventId
|
|
1454
|
+
);
|
|
1455
|
+
if (parentIndex !== -1) {
|
|
1456
|
+
return parentIndex;
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
return findLastIndex(calls, fallbackPredicate);
|
|
1460
|
+
}
|
|
1461
|
+
function findLastIndex(items, predicate) {
|
|
1462
|
+
for (let index = items.length - 1; index >= 0; index -= 1) {
|
|
1463
|
+
const item = items[index];
|
|
1464
|
+
if (item !== void 0 && predicate(item)) {
|
|
1465
|
+
return index;
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
return -1;
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
// src/tracing/run-state-approval-routing-reducers.ts
|
|
1472
|
+
function applyPolicyEvaluated(state, event) {
|
|
1473
|
+
return {
|
|
1474
|
+
...state,
|
|
1475
|
+
policyDecisions: [
|
|
1476
|
+
...state.policyDecisions,
|
|
1477
|
+
{
|
|
1478
|
+
policyName: event.policyName,
|
|
1479
|
+
action: event.action,
|
|
1480
|
+
agentName: event.agentName,
|
|
1481
|
+
toolName: event.toolName,
|
|
1482
|
+
reason: event.reason,
|
|
1483
|
+
timestamp: event.timestamp
|
|
1484
|
+
}
|
|
1485
|
+
]
|
|
1486
|
+
};
|
|
1487
|
+
}
|
|
1488
|
+
function applyRouteSelected(state, event) {
|
|
1489
|
+
return {
|
|
1490
|
+
...state,
|
|
1491
|
+
routingDecisions: [
|
|
1492
|
+
...state.routingDecisions,
|
|
1493
|
+
{
|
|
1494
|
+
supervisorName: event.supervisorName,
|
|
1495
|
+
selectedAgentName: event.selectedAgentName,
|
|
1496
|
+
candidateAgentNames: event.candidateAgentNames,
|
|
1497
|
+
reason: event.reason,
|
|
1498
|
+
timestamp: event.timestamp
|
|
1499
|
+
}
|
|
1500
|
+
]
|
|
1501
|
+
};
|
|
1502
|
+
}
|
|
1503
|
+
function applyApprovalRequired(state, event) {
|
|
1504
|
+
return {
|
|
1505
|
+
...state,
|
|
1506
|
+
status: "awaiting_approval",
|
|
1507
|
+
approvals: [
|
|
1508
|
+
...state.approvals,
|
|
1509
|
+
{
|
|
1510
|
+
id: event.approvalId,
|
|
1511
|
+
status: "pending",
|
|
1512
|
+
toolCallId: event.parentEventId,
|
|
1513
|
+
toolName: event.toolName,
|
|
1514
|
+
agentName: event.agentName,
|
|
1515
|
+
reason: event.reason,
|
|
1516
|
+
requestedAt: event.timestamp,
|
|
1517
|
+
decidedAt: null,
|
|
1518
|
+
decidedBy: null
|
|
1519
|
+
}
|
|
1520
|
+
]
|
|
1521
|
+
};
|
|
1522
|
+
}
|
|
1523
|
+
function applyApprovalDecision(state, event, status, reason) {
|
|
1524
|
+
const approvals = state.approvals.map(
|
|
1525
|
+
(approval) => approval.id === event.approvalId ? {
|
|
1526
|
+
...approval,
|
|
1527
|
+
status,
|
|
1528
|
+
reason: reason ?? approval.reason,
|
|
1529
|
+
decidedAt: event.timestamp,
|
|
1530
|
+
decidedBy: event.decidedBy
|
|
1531
|
+
} : approval
|
|
1532
|
+
);
|
|
1533
|
+
const hasPendingApprovals = approvals.some(
|
|
1534
|
+
(approval) => approval.status === "pending"
|
|
1535
|
+
);
|
|
1536
|
+
return {
|
|
1537
|
+
...state,
|
|
1538
|
+
approvals,
|
|
1539
|
+
status: hasPendingApprovals ? "awaiting_approval" : "running"
|
|
1540
|
+
};
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
// src/tracing/run-state-reducer.ts
|
|
1544
|
+
function createInitialRunState(input) {
|
|
1545
|
+
return {
|
|
1546
|
+
runId: input.runId,
|
|
1547
|
+
workflowName: input.workflowName,
|
|
1548
|
+
status: "pending",
|
|
1549
|
+
input: input.input,
|
|
1550
|
+
messages: [],
|
|
1551
|
+
agentCalls: [],
|
|
1552
|
+
toolCalls: [],
|
|
1553
|
+
policyDecisions: [],
|
|
1554
|
+
routingDecisions: [],
|
|
1555
|
+
approvals: [],
|
|
1556
|
+
artifacts: [],
|
|
1557
|
+
errors: [],
|
|
1558
|
+
metrics: {
|
|
1559
|
+
totalLatencyMs: null,
|
|
1560
|
+
tokenUsage: null,
|
|
1561
|
+
agentCallCount: 0,
|
|
1562
|
+
toolCallCount: 0,
|
|
1563
|
+
policyDecisionCount: 0,
|
|
1564
|
+
approvalCount: 0
|
|
1565
|
+
},
|
|
1566
|
+
finalOutput: null,
|
|
1567
|
+
createdAt: input.createdAt,
|
|
1568
|
+
startedAt: null,
|
|
1569
|
+
completedAt: null
|
|
1570
|
+
};
|
|
1571
|
+
}
|
|
1572
|
+
function applyTraceEventToRunState(state, event) {
|
|
1573
|
+
const nextState = reduceTraceEvent(state, event);
|
|
1574
|
+
return {
|
|
1575
|
+
...nextState,
|
|
1576
|
+
metrics: {
|
|
1577
|
+
...nextState.metrics,
|
|
1578
|
+
agentCallCount: nextState.agentCalls.length,
|
|
1579
|
+
toolCallCount: nextState.toolCalls.length,
|
|
1580
|
+
policyDecisionCount: nextState.policyDecisions.length,
|
|
1581
|
+
approvalCount: nextState.approvals.length
|
|
1582
|
+
}
|
|
1583
|
+
};
|
|
1584
|
+
}
|
|
1585
|
+
function reduceTraceEvent(state, event) {
|
|
1586
|
+
switch (event.type) {
|
|
1587
|
+
case "run.started":
|
|
1588
|
+
return applyRunStarted(state, event);
|
|
1589
|
+
case "run.completed":
|
|
1590
|
+
return applyRunCompleted(state, event);
|
|
1591
|
+
case "run.failed":
|
|
1592
|
+
return applyRunFailed(state, event);
|
|
1593
|
+
case "agent.started":
|
|
1594
|
+
return applyAgentStarted(state, event);
|
|
1595
|
+
case "agent.completed":
|
|
1596
|
+
return applyAgentCompleted(state, event);
|
|
1597
|
+
case "agent.failed":
|
|
1598
|
+
return applyAgentFailed(state, event);
|
|
1599
|
+
case "tool.requested":
|
|
1600
|
+
return applyToolRequested(state, event);
|
|
1601
|
+
case "tool.approved":
|
|
1602
|
+
return applyToolStatus(state, event.parentEventId, event.toolName, {
|
|
1603
|
+
status: "approved"
|
|
1604
|
+
});
|
|
1605
|
+
case "tool.blocked":
|
|
1606
|
+
return applyToolBlocked(state, event);
|
|
1607
|
+
case "tool.completed":
|
|
1608
|
+
return applyToolCompleted(state, event);
|
|
1609
|
+
case "tool.failed":
|
|
1610
|
+
return applyToolFailed(state, event);
|
|
1611
|
+
case "policy.evaluated":
|
|
1612
|
+
return applyPolicyEvaluated(state, event);
|
|
1613
|
+
case "route.selected":
|
|
1614
|
+
return applyRouteSelected(state, event);
|
|
1615
|
+
case "approval.required":
|
|
1616
|
+
return applyApprovalRequired(state, event);
|
|
1617
|
+
case "approval.approved":
|
|
1618
|
+
return applyApprovalDecision(state, event, "approved", null);
|
|
1619
|
+
case "approval.rejected":
|
|
1620
|
+
return applyApprovalDecision(state, event, "rejected", event.reason);
|
|
1621
|
+
case "eval.started":
|
|
1622
|
+
case "eval.completed":
|
|
1623
|
+
case "replay.started":
|
|
1624
|
+
case "replay.completed":
|
|
1625
|
+
return state;
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
function applyRunStarted(state, event) {
|
|
1629
|
+
return {
|
|
1630
|
+
...state,
|
|
1631
|
+
status: "running",
|
|
1632
|
+
input: event.input,
|
|
1633
|
+
startedAt: event.timestamp
|
|
1634
|
+
};
|
|
1635
|
+
}
|
|
1636
|
+
function applyRunCompleted(state, event) {
|
|
1637
|
+
return {
|
|
1638
|
+
...state,
|
|
1639
|
+
status: "completed",
|
|
1640
|
+
finalOutput: event.output,
|
|
1641
|
+
completedAt: event.timestamp,
|
|
1642
|
+
metrics: { ...state.metrics, totalLatencyMs: event.latencyMs }
|
|
1643
|
+
};
|
|
1644
|
+
}
|
|
1645
|
+
function applyRunFailed(state, event) {
|
|
1646
|
+
return {
|
|
1647
|
+
...state,
|
|
1648
|
+
status: "failed",
|
|
1649
|
+
completedAt: event.timestamp,
|
|
1650
|
+
errors: [...state.errors, mapEventError(event.error, event.timestamp)],
|
|
1651
|
+
metrics: { ...state.metrics, totalLatencyMs: event.latencyMs }
|
|
1652
|
+
};
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
// src/tracing/trace-bus.ts
|
|
1656
|
+
function buildSinkFailure(sinkName, operation, thrownError) {
|
|
1657
|
+
return { sinkName, operation, error: thrownError };
|
|
1658
|
+
}
|
|
1659
|
+
function reportSinkFailure(onSinkError, failure) {
|
|
1660
|
+
if (onSinkError !== void 0) {
|
|
1661
|
+
onSinkError(failure);
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
function writeTraceEventToSinkSafely(sink, event, onSinkError) {
|
|
1665
|
+
try {
|
|
1666
|
+
Promise.resolve(sink.writeTraceEvent(event)).catch(
|
|
1667
|
+
(thrownError) => {
|
|
1668
|
+
reportSinkFailure(
|
|
1669
|
+
onSinkError,
|
|
1670
|
+
buildSinkFailure(sink.name, "write", thrownError)
|
|
1671
|
+
);
|
|
1672
|
+
}
|
|
1673
|
+
);
|
|
1674
|
+
} catch (thrownError) {
|
|
1675
|
+
reportSinkFailure(
|
|
1676
|
+
onSinkError,
|
|
1677
|
+
buildSinkFailure(
|
|
1678
|
+
sink.name,
|
|
1679
|
+
"write",
|
|
1680
|
+
thrownError instanceof Error ? thrownError : new Error(String(thrownError))
|
|
1681
|
+
)
|
|
1682
|
+
);
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
async function flushSinkSafely(sink, onSinkError) {
|
|
1686
|
+
if (sink.flush === void 0) {
|
|
1687
|
+
return;
|
|
1688
|
+
}
|
|
1689
|
+
try {
|
|
1690
|
+
await sink.flush();
|
|
1691
|
+
} catch (thrownError) {
|
|
1692
|
+
reportSinkFailure(
|
|
1693
|
+
onSinkError,
|
|
1694
|
+
buildSinkFailure(
|
|
1695
|
+
sink.name,
|
|
1696
|
+
"flush",
|
|
1697
|
+
thrownError instanceof Error ? thrownError : new Error(String(thrownError))
|
|
1698
|
+
)
|
|
1699
|
+
);
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
function createTraceBus(busInput) {
|
|
1703
|
+
const now = busInput.now ?? (() => /* @__PURE__ */ new Date());
|
|
1704
|
+
const events = [];
|
|
1705
|
+
let runState = busInput.initialRunState ?? createInitialRunState({
|
|
1706
|
+
runId: busInput.runId,
|
|
1707
|
+
workflowName: busInput.workflowName,
|
|
1708
|
+
input: busInput.input,
|
|
1709
|
+
createdAt: now().toISOString()
|
|
1710
|
+
});
|
|
1711
|
+
function emitTraceEvent(event) {
|
|
1712
|
+
const validatedEvent = import_trace_events2.TraceEventSchema.parse(event);
|
|
1713
|
+
events.push(validatedEvent);
|
|
1714
|
+
runState = applyTraceEventToRunState(runState, validatedEvent);
|
|
1715
|
+
for (const sink of busInput.sinks) {
|
|
1716
|
+
writeTraceEventToSinkSafely(sink, validatedEvent, busInput.onSinkError);
|
|
1717
|
+
}
|
|
1718
|
+
return validatedEvent;
|
|
1719
|
+
}
|
|
1720
|
+
async function flushSinks() {
|
|
1721
|
+
for (const sink of busInput.sinks) {
|
|
1722
|
+
await flushSinkSafely(sink, busInput.onSinkError);
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
function closeSinks() {
|
|
1726
|
+
for (const sink of busInput.sinks) {
|
|
1727
|
+
if (sink.close !== void 0) {
|
|
1728
|
+
sink.close();
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
return {
|
|
1733
|
+
emitTraceEvent,
|
|
1734
|
+
getRunState: () => runState,
|
|
1735
|
+
listTraceEvents: () => [...events],
|
|
1736
|
+
flushSinks,
|
|
1737
|
+
closeSinks
|
|
1738
|
+
};
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
// src/workflows/workflow-approval.ts
|
|
1742
|
+
var import_errors17 = require("@assemble-dev/shared-types/errors");
|
|
1743
|
+
var import_json3 = require("@assemble-dev/shared-types/json");
|
|
1744
|
+
var import_errors18 = require("@assemble-dev/shared-utils/errors");
|
|
1745
|
+
var import_zod3 = require("zod");
|
|
1746
|
+
|
|
1747
|
+
// src/workflows/run-state-store.ts
|
|
1748
|
+
var import_node_fs = require("fs");
|
|
1749
|
+
var import_node_path = require("path");
|
|
1750
|
+
var import_runs4 = require("@assemble-dev/shared-types/runs");
|
|
1751
|
+
var defaultRunStateDirectory = (0, import_node_path.join)(".assemble", "runs");
|
|
1752
|
+
function resolveRunStateFilePath(runId, directory = defaultRunStateDirectory) {
|
|
1753
|
+
return (0, import_node_path.join)(directory, `${runId}.json`);
|
|
1754
|
+
}
|
|
1755
|
+
function saveRunStateFile(input) {
|
|
1756
|
+
const directory = input.directory ?? defaultRunStateDirectory;
|
|
1757
|
+
const filePath = resolveRunStateFilePath(input.runState.runId, directory);
|
|
1758
|
+
const validatedRunState = import_runs4.RunStateSchema.parse(input.runState);
|
|
1759
|
+
(0, import_node_fs.mkdirSync)(directory, { recursive: true });
|
|
1760
|
+
(0, import_node_fs.writeFileSync)(filePath, JSON.stringify(validatedRunState, null, 2), "utf8");
|
|
1761
|
+
return filePath;
|
|
1762
|
+
}
|
|
1763
|
+
function loadRunStateFile(input) {
|
|
1764
|
+
const filePath = resolveRunStateFilePath(input.runId, input.directory);
|
|
1765
|
+
const rawContents = (0, import_node_fs.readFileSync)(filePath, "utf8");
|
|
1766
|
+
return import_runs4.RunStateSchema.parse(JSON.parse(rawContents));
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
// src/workflows/workflow-runtime-context.ts
|
|
1770
|
+
async function persistWorkflowRunState(context) {
|
|
1771
|
+
if (!context.persistRunState) {
|
|
1772
|
+
return;
|
|
1773
|
+
}
|
|
1774
|
+
saveRunStateFile({
|
|
1775
|
+
runState: await buildWorkflowRunState(context),
|
|
1776
|
+
directory: context.runStateDirectory
|
|
1777
|
+
});
|
|
1778
|
+
}
|
|
1779
|
+
async function buildWorkflowRunState(context) {
|
|
1780
|
+
return {
|
|
1781
|
+
...context.traceBus.getRunState(),
|
|
1782
|
+
artifacts: await context.artifactStore.listRunArtifacts()
|
|
1783
|
+
};
|
|
1784
|
+
}
|
|
1785
|
+
function buildWorkflowEventContext(context) {
|
|
1786
|
+
return {
|
|
1787
|
+
traceBus: context.traceBus,
|
|
1788
|
+
runId: context.runId,
|
|
1789
|
+
workflowName: context.definition.name,
|
|
1790
|
+
now: context.now
|
|
1791
|
+
};
|
|
1792
|
+
}
|
|
1793
|
+
function calculateWorkflowElapsedMs(context) {
|
|
1794
|
+
return Math.max(0, context.now().getTime() - context.startedAtMs);
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
// src/workflows/workflow-step-execution.ts
|
|
1798
|
+
var import_errors13 = require("@assemble-dev/shared-types/errors");
|
|
1799
|
+
var import_errors14 = require("@assemble-dev/shared-utils/errors");
|
|
1800
|
+
|
|
1801
|
+
// src/workflows/workflow-trace-events.ts
|
|
1802
|
+
var import_ids2 = require("@assemble-dev/shared-utils/ids");
|
|
1803
|
+
function buildWorkflowEventBase(eventContext, parentEventId) {
|
|
1804
|
+
return {
|
|
1805
|
+
id: (0, import_ids2.generateTraceEventId)(),
|
|
1806
|
+
runId: eventContext.runId,
|
|
1807
|
+
workflowName: eventContext.workflowName,
|
|
1808
|
+
timestamp: eventContext.now().toISOString(),
|
|
1809
|
+
parentEventId,
|
|
1810
|
+
redacted: false
|
|
1811
|
+
};
|
|
1812
|
+
}
|
|
1813
|
+
function emitRunStartedEvent(eventContext, input) {
|
|
1814
|
+
return eventContext.traceBus.emitTraceEvent({
|
|
1815
|
+
...buildWorkflowEventBase(eventContext, null),
|
|
1816
|
+
type: "run.started",
|
|
1817
|
+
input
|
|
1818
|
+
});
|
|
1819
|
+
}
|
|
1820
|
+
function emitRunCompletedEvent(eventContext, runStartedEventId, output, latencyMs) {
|
|
1821
|
+
eventContext.traceBus.emitTraceEvent({
|
|
1822
|
+
...buildWorkflowEventBase(eventContext, runStartedEventId),
|
|
1823
|
+
type: "run.completed",
|
|
1824
|
+
output,
|
|
1825
|
+
latencyMs
|
|
1826
|
+
});
|
|
1827
|
+
}
|
|
1828
|
+
function emitRouteSelectedEvent(eventContext, runStartedEventId, routeSelection) {
|
|
1829
|
+
eventContext.traceBus.emitTraceEvent({
|
|
1830
|
+
...buildWorkflowEventBase(eventContext, runStartedEventId),
|
|
1831
|
+
type: "route.selected",
|
|
1832
|
+
supervisorName: routeSelection.supervisorName,
|
|
1833
|
+
selectedAgentName: routeSelection.selectedAgentName,
|
|
1834
|
+
candidateAgentNames: routeSelection.candidateAgentNames,
|
|
1835
|
+
reason: routeSelection.reason
|
|
1836
|
+
});
|
|
1837
|
+
}
|
|
1838
|
+
function emitApprovalApprovedEvent(eventContext, decision) {
|
|
1839
|
+
eventContext.traceBus.emitTraceEvent({
|
|
1840
|
+
...buildWorkflowEventBase(eventContext, decision.parentEventId),
|
|
1841
|
+
type: "approval.approved",
|
|
1842
|
+
approvalId: decision.approvalId,
|
|
1843
|
+
decidedBy: decision.decidedBy
|
|
1844
|
+
});
|
|
1845
|
+
}
|
|
1846
|
+
function emitApprovalRejectedEvent(eventContext, decision, reason) {
|
|
1847
|
+
eventContext.traceBus.emitTraceEvent({
|
|
1848
|
+
...buildWorkflowEventBase(eventContext, decision.parentEventId),
|
|
1849
|
+
type: "approval.rejected",
|
|
1850
|
+
approvalId: decision.approvalId,
|
|
1851
|
+
decidedBy: decision.decidedBy,
|
|
1852
|
+
reason
|
|
1853
|
+
});
|
|
1854
|
+
}
|
|
1855
|
+
function emitRunFailedEvent(eventContext, runStartedEventId, error, latencyMs) {
|
|
1856
|
+
eventContext.traceBus.emitTraceEvent({
|
|
1857
|
+
...buildWorkflowEventBase(eventContext, runStartedEventId),
|
|
1858
|
+
type: "run.failed",
|
|
1859
|
+
error,
|
|
1860
|
+
latencyMs
|
|
1861
|
+
});
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
// src/workflows/workflow-step-execution.ts
|
|
1865
|
+
async function executeWorkflowStep(context, stepIndex, stepAgent, stepInput) {
|
|
1866
|
+
try {
|
|
1867
|
+
const agentResult = await runAgent({
|
|
1868
|
+
definition: stepAgent,
|
|
1869
|
+
input: stepInput,
|
|
1870
|
+
traceBus: context.traceBus,
|
|
1871
|
+
parentEventId: context.runStartedEventId,
|
|
1872
|
+
threadMemory: context.threadMemory,
|
|
1873
|
+
now: context.now
|
|
1874
|
+
});
|
|
1875
|
+
await saveWorkflowStepArtifact(
|
|
1876
|
+
context,
|
|
1877
|
+
stepIndex,
|
|
1878
|
+
stepAgent.name,
|
|
1879
|
+
agentResult.output
|
|
1880
|
+
);
|
|
1881
|
+
return agentResult.output;
|
|
1882
|
+
} catch (thrownError) {
|
|
1883
|
+
const stepError = mapCaughtWorkflowErrorToAppError(
|
|
1884
|
+
thrownError instanceof Error ? thrownError : new Error(String(thrownError))
|
|
1885
|
+
);
|
|
1886
|
+
return await resolveWorkflowStepFailure(
|
|
1887
|
+
context,
|
|
1888
|
+
stepIndex,
|
|
1889
|
+
stepAgent.name,
|
|
1890
|
+
stepError,
|
|
1891
|
+
stepInput
|
|
1892
|
+
);
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
async function resolveWorkflowStepFailure(context, stepIndex, agentName, stepError, previousStepOutput) {
|
|
1896
|
+
if (context.definition.continueOnAgentFailure) {
|
|
1897
|
+
return previousStepOutput;
|
|
1898
|
+
}
|
|
1899
|
+
const failureError = new import_errors14.AppError({
|
|
1900
|
+
code: stepError.code,
|
|
1901
|
+
message: `step ${stepIndex} (${agentName}) failed: ${stepError.message}`,
|
|
1902
|
+
statusCode: stepError.statusCode,
|
|
1903
|
+
details: { stepIndex, agentName, reason: stepError.message },
|
|
1904
|
+
cause: stepError
|
|
1905
|
+
});
|
|
1906
|
+
await failWorkflowRun(context, failureError);
|
|
1907
|
+
throw failureError;
|
|
1908
|
+
}
|
|
1909
|
+
function shouldStopWorkflowRun(context, stepIndex) {
|
|
1910
|
+
if (context.definition.stopCondition === null) {
|
|
1911
|
+
return false;
|
|
1912
|
+
}
|
|
1913
|
+
return context.definition.stopCondition({
|
|
1914
|
+
state: context.traceBus.getRunState(),
|
|
1915
|
+
stepIndex
|
|
1916
|
+
});
|
|
1917
|
+
}
|
|
1918
|
+
async function saveWorkflowStepArtifact(context, stepIndex, agentName, stepOutput) {
|
|
1919
|
+
await context.artifactStore.saveRunArtifact({
|
|
1920
|
+
key: `step-${stepIndex}:${agentName}`,
|
|
1921
|
+
value: stepOutput,
|
|
1922
|
+
producedByAgentName: agentName,
|
|
1923
|
+
createdAt: context.now().toISOString()
|
|
1924
|
+
});
|
|
1925
|
+
}
|
|
1926
|
+
async function failWorkflowRun(context, failureError) {
|
|
1927
|
+
emitRunFailedEvent(
|
|
1928
|
+
buildWorkflowEventContext(context),
|
|
1929
|
+
context.runStartedEventId,
|
|
1930
|
+
{ message: failureError.message, code: failureError.code },
|
|
1931
|
+
calculateWorkflowElapsedMs(context)
|
|
1932
|
+
);
|
|
1933
|
+
await persistWorkflowRunState(context);
|
|
1934
|
+
await context.traceBus.flushSinks();
|
|
1935
|
+
}
|
|
1936
|
+
function mapCaughtWorkflowErrorToAppError(thrownError) {
|
|
1937
|
+
if (thrownError instanceof import_errors14.AppError) {
|
|
1938
|
+
return thrownError;
|
|
1939
|
+
}
|
|
1940
|
+
return new import_errors14.AppError({
|
|
1941
|
+
code: import_errors13.AppErrorCode.InternalServerError,
|
|
1942
|
+
message: thrownError.message,
|
|
1943
|
+
statusCode: 500,
|
|
1944
|
+
cause: thrownError
|
|
1945
|
+
});
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
// src/workflows/workflow-tool-execution.ts
|
|
1949
|
+
var import_errors15 = require("@assemble-dev/shared-types/errors");
|
|
1950
|
+
var import_errors16 = require("@assemble-dev/shared-utils/errors");
|
|
1951
|
+
async function executeWorkflowTool(toolInput) {
|
|
1952
|
+
const runtimeContext = toolInput.runtimeContext;
|
|
1953
|
+
const workflowTool = findWorkflowToolByName(
|
|
1954
|
+
runtimeContext.definition,
|
|
1955
|
+
toolInput.toolName
|
|
1956
|
+
);
|
|
1957
|
+
const workflowPolicies = runtimeContext.definition.policies;
|
|
1958
|
+
const outcome = await executeToolCall({
|
|
1959
|
+
tool: workflowTool,
|
|
1960
|
+
agentName: toolInput.agentName,
|
|
1961
|
+
input: toolInput.input,
|
|
1962
|
+
policies: workflowPolicies.length > 0 ? [...workflowPolicies] : void 0,
|
|
1963
|
+
traceBus: runtimeContext.traceBus,
|
|
1964
|
+
parentEventId: runtimeContext.parentEventId ?? void 0,
|
|
1965
|
+
now: runtimeContext.now,
|
|
1966
|
+
grantedApproval: toolInput.grantedApproval
|
|
1967
|
+
});
|
|
1968
|
+
if (toolInput.throwOnUnsuccessfulOutcome === false) {
|
|
1969
|
+
return outcome;
|
|
1970
|
+
}
|
|
1971
|
+
return ensureWorkflowToolOutcomeSucceeded(toolInput, outcome);
|
|
1972
|
+
}
|
|
1973
|
+
function findWorkflowToolByName(definition, toolName) {
|
|
1974
|
+
const workflowTool = definition.tools.find(
|
|
1975
|
+
(candidateTool) => candidateTool.name === toolName
|
|
1976
|
+
);
|
|
1977
|
+
if (workflowTool !== void 0) {
|
|
1978
|
+
return workflowTool;
|
|
1979
|
+
}
|
|
1980
|
+
const registeredToolNames = definition.tools.map((registeredTool) => registeredTool.name).join(", ");
|
|
1981
|
+
throw new import_errors16.AppError({
|
|
1982
|
+
code: import_errors15.AppErrorCode.NotFound,
|
|
1983
|
+
message: `tool "${toolName}" is not registered on workflow "${definition.name}"; registered tools: [${registeredToolNames}]. Add the tool to the workflow definition before calling it`,
|
|
1984
|
+
statusCode: 404,
|
|
1985
|
+
details: { toolName, workflowName: definition.name }
|
|
1986
|
+
});
|
|
1987
|
+
}
|
|
1988
|
+
function ensureWorkflowToolOutcomeSucceeded(toolInput, outcome) {
|
|
1989
|
+
const stepIndex = toolInput.runtimeContext.stepIndex;
|
|
1990
|
+
if (outcome.status === "blocked") {
|
|
1991
|
+
throw new import_errors16.AppError({
|
|
1992
|
+
code: import_errors15.AppErrorCode.Forbidden,
|
|
1993
|
+
message: `tool "${toolInput.toolName}" was blocked at step ${stepIndex} for agent "${toolInput.agentName}": ${outcome.reason}`,
|
|
1994
|
+
statusCode: 403,
|
|
1995
|
+
details: { toolName: toolInput.toolName, stepIndex, reason: outcome.reason }
|
|
1996
|
+
});
|
|
1997
|
+
}
|
|
1998
|
+
if (outcome.status === "failed") {
|
|
1999
|
+
throw new import_errors16.AppError({
|
|
2000
|
+
code: mapToolCallErrorCodeToAppErrorCode(outcome.error.code),
|
|
2001
|
+
message: `tool "${toolInput.toolName}" failed at step ${stepIndex} for agent "${toolInput.agentName}": ${outcome.error.message}`,
|
|
2002
|
+
statusCode: outcome.error.code === "VALIDATION_FAILED" ? 422 : 500,
|
|
2003
|
+
details: {
|
|
2004
|
+
toolName: toolInput.toolName,
|
|
2005
|
+
stepIndex,
|
|
2006
|
+
reason: outcome.error.message
|
|
2007
|
+
}
|
|
2008
|
+
});
|
|
2009
|
+
}
|
|
2010
|
+
return outcome;
|
|
2011
|
+
}
|
|
2012
|
+
function mapToolCallErrorCodeToAppErrorCode(toolCallErrorCode) {
|
|
2013
|
+
return toolCallErrorCode === "VALIDATION_FAILED" ? import_errors15.AppErrorCode.ValidationFailed : import_errors15.AppErrorCode.InternalServerError;
|
|
2014
|
+
}
|
|
2015
|
+
|
|
2016
|
+
// src/workflows/workflow-approval.ts
|
|
2017
|
+
var pendingApprovalArtifactKey = "workflow:pending-approval";
|
|
2018
|
+
var PendingWorkflowApprovalSchema = import_zod3.z.object({
|
|
2019
|
+
stepIndex: import_zod3.z.number().int().nonnegative(),
|
|
2020
|
+
agentName: import_zod3.z.string().min(1),
|
|
2021
|
+
toolName: import_zod3.z.string().min(1),
|
|
2022
|
+
toolInput: import_json3.JsonValueSchema,
|
|
2023
|
+
approvalId: import_zod3.z.string().min(1),
|
|
2024
|
+
toolRequestEventId: import_zod3.z.string().nullable(),
|
|
2025
|
+
agentOutput: import_json3.JsonValueSchema,
|
|
2026
|
+
runStartedEventId: import_zod3.z.string().min(1)
|
|
2027
|
+
});
|
|
2028
|
+
function buildBoundToolArtifactKey(stepIndex, agentName, toolName) {
|
|
2029
|
+
return `step-${stepIndex}:${agentName}:tool:${toolName}`;
|
|
2030
|
+
}
|
|
2031
|
+
function buildWorkflowStepContext(context, stepIndex) {
|
|
2032
|
+
return {
|
|
2033
|
+
definition: context.definition,
|
|
2034
|
+
traceBus: context.traceBus,
|
|
2035
|
+
stepIndex,
|
|
2036
|
+
parentEventId: context.runStartedEventId,
|
|
2037
|
+
artifactStore: context.artifactStore,
|
|
2038
|
+
threadMemory: context.threadMemory,
|
|
2039
|
+
now: context.now
|
|
2040
|
+
};
|
|
2041
|
+
}
|
|
2042
|
+
async function applyAgentToolBindingAfterStep(context, stepIndex, agentName, agentOutput) {
|
|
2043
|
+
const binding = findAgentToolBinding(context, agentName);
|
|
2044
|
+
if (binding === null) {
|
|
2045
|
+
return { status: "continue" };
|
|
2046
|
+
}
|
|
2047
|
+
const toolInput = binding.buildToolInput({
|
|
2048
|
+
state: await buildWorkflowRunState(context),
|
|
2049
|
+
stepIndex,
|
|
2050
|
+
agentOutput
|
|
2051
|
+
});
|
|
2052
|
+
const outcome = await executeBoundToolOrFailRun(
|
|
2053
|
+
context,
|
|
2054
|
+
stepIndex,
|
|
2055
|
+
binding,
|
|
2056
|
+
toolInput
|
|
2057
|
+
);
|
|
2058
|
+
if (outcome.status === "awaiting_approval") {
|
|
2059
|
+
return await pauseWorkflowRunForApproval(context, {
|
|
2060
|
+
stepIndex,
|
|
2061
|
+
agentName,
|
|
2062
|
+
toolName: binding.toolName,
|
|
2063
|
+
toolInput,
|
|
2064
|
+
approvalId: outcome.approvalId,
|
|
2065
|
+
toolRequestEventId: outcome.toolRequestEventId,
|
|
2066
|
+
agentOutput,
|
|
2067
|
+
runStartedEventId: context.runStartedEventId
|
|
2068
|
+
});
|
|
2069
|
+
}
|
|
2070
|
+
if (outcome.status === "completed") {
|
|
2071
|
+
await saveBoundToolArtifact(
|
|
2072
|
+
context,
|
|
2073
|
+
stepIndex,
|
|
2074
|
+
agentName,
|
|
2075
|
+
binding.toolName,
|
|
2076
|
+
outcome.output
|
|
2077
|
+
);
|
|
2078
|
+
}
|
|
2079
|
+
return { status: "continue" };
|
|
2080
|
+
}
|
|
2081
|
+
async function executeApprovedWorkflowTool(context, pendingApproval) {
|
|
2082
|
+
const outcome = await executeBoundToolOrFailRun(
|
|
2083
|
+
context,
|
|
2084
|
+
pendingApproval.stepIndex,
|
|
2085
|
+
{
|
|
2086
|
+
agentName: pendingApproval.agentName,
|
|
2087
|
+
toolName: pendingApproval.toolName,
|
|
2088
|
+
buildToolInput: () => pendingApproval.toolInput
|
|
2089
|
+
},
|
|
2090
|
+
pendingApproval.toolInput,
|
|
2091
|
+
{
|
|
2092
|
+
approvalId: pendingApproval.approvalId,
|
|
2093
|
+
toolRequestEventId: pendingApproval.toolRequestEventId
|
|
2094
|
+
}
|
|
2095
|
+
);
|
|
2096
|
+
if (outcome.status === "completed") {
|
|
2097
|
+
await saveBoundToolArtifact(
|
|
2098
|
+
context,
|
|
2099
|
+
pendingApproval.stepIndex,
|
|
2100
|
+
pendingApproval.agentName,
|
|
2101
|
+
pendingApproval.toolName,
|
|
2102
|
+
outcome.output
|
|
2103
|
+
);
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
async function executeBoundToolOrFailRun(context, stepIndex, binding, toolInput, grantedApproval) {
|
|
2107
|
+
try {
|
|
2108
|
+
const outcome = await executeWorkflowTool({
|
|
2109
|
+
runtimeContext: buildWorkflowStepContext(context, stepIndex),
|
|
2110
|
+
toolName: binding.toolName,
|
|
2111
|
+
agentName: binding.agentName,
|
|
2112
|
+
input: toolInput,
|
|
2113
|
+
grantedApproval
|
|
2114
|
+
});
|
|
2115
|
+
if (outcome.status === "completed") {
|
|
2116
|
+
return { status: "completed", output: outcome.output };
|
|
2117
|
+
}
|
|
2118
|
+
if (outcome.status === "awaiting_approval") {
|
|
2119
|
+
return outcome;
|
|
2120
|
+
}
|
|
2121
|
+
throw new import_errors18.AppError({
|
|
2122
|
+
code: import_errors17.AppErrorCode.InternalServerError,
|
|
2123
|
+
message: `tool "${binding.toolName}" returned unexpected outcome "${outcome.status}" at step ${stepIndex} for agent "${binding.agentName}"`,
|
|
2124
|
+
statusCode: 500,
|
|
2125
|
+
details: { toolName: binding.toolName, stepIndex }
|
|
2126
|
+
});
|
|
2127
|
+
} catch (thrownError) {
|
|
2128
|
+
const bindingError = thrownError instanceof import_errors18.AppError ? thrownError : new import_errors18.AppError({
|
|
2129
|
+
code: import_errors17.AppErrorCode.InternalServerError,
|
|
2130
|
+
message: `tool "${binding.toolName}" threw at step ${stepIndex} for agent "${binding.agentName}": ${String(thrownError)}`,
|
|
2131
|
+
statusCode: 500,
|
|
2132
|
+
details: { toolName: binding.toolName, stepIndex }
|
|
2133
|
+
});
|
|
2134
|
+
await failWorkflowRun(context, bindingError);
|
|
2135
|
+
throw bindingError;
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
2138
|
+
async function pauseWorkflowRunForApproval(context, pendingApproval) {
|
|
2139
|
+
await context.artifactStore.saveRunArtifact({
|
|
2140
|
+
key: pendingApprovalArtifactKey,
|
|
2141
|
+
value: PendingWorkflowApprovalSchema.parse(pendingApproval),
|
|
2142
|
+
producedByAgentName: pendingApproval.agentName,
|
|
2143
|
+
createdAt: context.now().toISOString()
|
|
2144
|
+
});
|
|
2145
|
+
await persistWorkflowRunState(context);
|
|
2146
|
+
await context.traceBus.flushSinks();
|
|
2147
|
+
return { status: "paused", approvalId: pendingApproval.approvalId };
|
|
2148
|
+
}
|
|
2149
|
+
async function saveBoundToolArtifact(context, stepIndex, agentName, toolName, toolOutput) {
|
|
2150
|
+
await context.artifactStore.saveRunArtifact({
|
|
2151
|
+
key: buildBoundToolArtifactKey(stepIndex, agentName, toolName),
|
|
2152
|
+
value: toolOutput,
|
|
2153
|
+
producedByAgentName: agentName,
|
|
2154
|
+
createdAt: context.now().toISOString()
|
|
2155
|
+
});
|
|
2156
|
+
}
|
|
2157
|
+
function findAgentToolBinding(context, agentName) {
|
|
2158
|
+
const binding = context.definition.agentToolBindings.find(
|
|
2159
|
+
(candidateBinding) => candidateBinding.agentName === agentName
|
|
2160
|
+
);
|
|
2161
|
+
return binding ?? null;
|
|
2162
|
+
}
|
|
2163
|
+
|
|
2164
|
+
// src/workflows/workflow-parallel-execution.ts
|
|
2165
|
+
var import_errors19 = require("@assemble-dev/shared-types/errors");
|
|
2166
|
+
var import_errors20 = require("@assemble-dev/shared-utils/errors");
|
|
2167
|
+
|
|
2168
|
+
// src/workflows/workflow-topology.ts
|
|
2169
|
+
var import_zod4 = require("zod");
|
|
2170
|
+
var finishWorkflow = "__finish__";
|
|
2171
|
+
var defaultSupervisorMaxSteps = 10;
|
|
2172
|
+
var defaultParallelMergedArtifactKey = "parallel:merged";
|
|
2173
|
+
var PipelineTopologySchema = import_zod4.z.object({
|
|
2174
|
+
kind: import_zod4.z.literal("pipeline")
|
|
2175
|
+
});
|
|
2176
|
+
var SupervisorTopologySchema = import_zod4.z.object({
|
|
2177
|
+
kind: import_zod4.z.literal("supervisor"),
|
|
2178
|
+
supervisorName: import_zod4.z.string().min(1),
|
|
2179
|
+
route: import_zod4.z.custom(
|
|
2180
|
+
(value) => typeof value === "function"
|
|
2181
|
+
),
|
|
2182
|
+
maxSteps: import_zod4.z.number().int().min(1).optional(),
|
|
2183
|
+
stopWhen: import_zod4.z.custom((value) => typeof value === "function").optional()
|
|
2184
|
+
});
|
|
2185
|
+
var ParallelTopologySchema = import_zod4.z.object({
|
|
2186
|
+
kind: import_zod4.z.literal("parallel"),
|
|
2187
|
+
agentNames: import_zod4.z.array(import_zod4.z.string().min(1)).min(1).optional(),
|
|
2188
|
+
mergedArtifactKey: import_zod4.z.string().min(1).optional()
|
|
2189
|
+
});
|
|
2190
|
+
var WorkflowTopologySchema = import_zod4.z.discriminatedUnion("kind", [
|
|
2191
|
+
PipelineTopologySchema,
|
|
2192
|
+
SupervisorTopologySchema,
|
|
2193
|
+
ParallelTopologySchema
|
|
2194
|
+
]);
|
|
2195
|
+
var defaultWorkflowTopology = Object.freeze({
|
|
2196
|
+
kind: "pipeline"
|
|
2197
|
+
});
|
|
2198
|
+
|
|
2199
|
+
// src/workflows/workflow-parallel-execution.ts
|
|
2200
|
+
function buildParallelThreadKey(runId, agentName) {
|
|
2201
|
+
return `${runId}:${agentName}`;
|
|
2202
|
+
}
|
|
2203
|
+
async function executeParallelSteps(context, topology, start) {
|
|
2204
|
+
const fanOutAgents = await resolveParallelFanOutAgents(context, topology);
|
|
2205
|
+
const branchContext = {
|
|
2206
|
+
...context,
|
|
2207
|
+
artifactStore: buildSerializedArtifactStore(context.artifactStore)
|
|
2208
|
+
};
|
|
2209
|
+
const branchPromises = fanOutAgents.map(
|
|
2210
|
+
async (fanOutAgent, fanOutIndex) => await executeParallelBranch(
|
|
2211
|
+
branchContext,
|
|
2212
|
+
fanOutIndex,
|
|
2213
|
+
fanOutAgent,
|
|
2214
|
+
start.input
|
|
2215
|
+
)
|
|
2216
|
+
);
|
|
2217
|
+
const branchResults = await collectSettledParallelBranchResults(
|
|
2218
|
+
branchPromises
|
|
2219
|
+
);
|
|
2220
|
+
await ensureAllParallelBranchesSucceeded(context, branchResults);
|
|
2221
|
+
const mergedOutput = mergeParallelBranchOutputs(branchResults);
|
|
2222
|
+
await saveMergedParallelArtifact(context, topology, mergedOutput);
|
|
2223
|
+
await persistWorkflowRunState(context);
|
|
2224
|
+
return { status: "completed", output: mergedOutput };
|
|
2225
|
+
}
|
|
2226
|
+
function buildSerializedArtifactStore(artifactStore) {
|
|
2227
|
+
let pendingWrite = Promise.resolve();
|
|
2228
|
+
return {
|
|
2229
|
+
async saveRunArtifact(artifact) {
|
|
2230
|
+
pendingWrite = pendingWrite.then(
|
|
2231
|
+
async () => await artifactStore.saveRunArtifact(artifact)
|
|
2232
|
+
);
|
|
2233
|
+
return await pendingWrite;
|
|
2234
|
+
},
|
|
2235
|
+
async getRunArtifact(key) {
|
|
2236
|
+
return await artifactStore.getRunArtifact(key);
|
|
2237
|
+
},
|
|
2238
|
+
async listRunArtifacts() {
|
|
2239
|
+
return await artifactStore.listRunArtifacts();
|
|
2240
|
+
}
|
|
2241
|
+
};
|
|
2242
|
+
}
|
|
2243
|
+
async function collectSettledParallelBranchResults(branchPromises) {
|
|
2244
|
+
const branchResults = [];
|
|
2245
|
+
for (const branchPromise of branchPromises) {
|
|
2246
|
+
branchResults.push(await branchPromise);
|
|
2247
|
+
}
|
|
2248
|
+
return branchResults;
|
|
2249
|
+
}
|
|
2250
|
+
async function resolveParallelFanOutAgents(context, topology) {
|
|
2251
|
+
if (topology.agentNames === void 0) {
|
|
2252
|
+
return [...context.definition.agents];
|
|
2253
|
+
}
|
|
2254
|
+
const fanOutAgents = [];
|
|
2255
|
+
for (const agentName of topology.agentNames) {
|
|
2256
|
+
const fanOutAgent = context.definition.agents.find(
|
|
2257
|
+
(agentDefinition) => agentDefinition.name === agentName
|
|
2258
|
+
);
|
|
2259
|
+
if (fanOutAgent === void 0) {
|
|
2260
|
+
return await failParallelRunForUnknownAgent(context, agentName);
|
|
2261
|
+
}
|
|
2262
|
+
fanOutAgents.push(fanOutAgent);
|
|
2263
|
+
}
|
|
2264
|
+
return fanOutAgents;
|
|
2265
|
+
}
|
|
2266
|
+
async function failParallelRunForUnknownAgent(context, agentName) {
|
|
2267
|
+
const validAgentNames = context.definition.agents.map((agentDefinition) => agentDefinition.name).join(", ");
|
|
2268
|
+
const unknownAgentError = new import_errors20.AppError({
|
|
2269
|
+
code: import_errors19.AppErrorCode.BadRequest,
|
|
2270
|
+
message: `workflow "${context.definition.name}" parallel topology selected unknown agent "${agentName}"; valid agent names: ${validAgentNames}`,
|
|
2271
|
+
statusCode: 400,
|
|
2272
|
+
details: {
|
|
2273
|
+
workflowName: context.definition.name,
|
|
2274
|
+
agentName,
|
|
2275
|
+
validAgentNames
|
|
2276
|
+
}
|
|
2277
|
+
});
|
|
2278
|
+
await failWorkflowRun(context, unknownAgentError);
|
|
2279
|
+
throw unknownAgentError;
|
|
2280
|
+
}
|
|
2281
|
+
async function executeParallelBranch(context, fanOutIndex, fanOutAgent, workflowInput) {
|
|
2282
|
+
try {
|
|
2283
|
+
const agentResult = await runAgent({
|
|
2284
|
+
definition: fanOutAgent,
|
|
2285
|
+
input: workflowInput,
|
|
2286
|
+
traceBus: context.traceBus,
|
|
2287
|
+
parentEventId: context.runStartedEventId,
|
|
2288
|
+
threadMemory: createThreadMemory({
|
|
2289
|
+
provider: context.memoryProvider,
|
|
2290
|
+
threadKey: buildParallelThreadKey(context.runId, fanOutAgent.name)
|
|
2291
|
+
}),
|
|
2292
|
+
now: context.now
|
|
2293
|
+
});
|
|
2294
|
+
await saveParallelBranchArtifact(
|
|
2295
|
+
context,
|
|
2296
|
+
fanOutIndex,
|
|
2297
|
+
fanOutAgent.name,
|
|
2298
|
+
agentResult.output
|
|
2299
|
+
);
|
|
2300
|
+
await applyParallelAgentToolBinding(
|
|
2301
|
+
context,
|
|
2302
|
+
fanOutIndex,
|
|
2303
|
+
fanOutAgent.name,
|
|
2304
|
+
agentResult.output
|
|
2305
|
+
);
|
|
2306
|
+
return {
|
|
2307
|
+
status: "completed",
|
|
2308
|
+
agentName: fanOutAgent.name,
|
|
2309
|
+
output: agentResult.output
|
|
2310
|
+
};
|
|
2311
|
+
} catch (thrownError) {
|
|
2312
|
+
const branchError = thrownError instanceof Error ? thrownError : new Error(String(thrownError));
|
|
2313
|
+
return {
|
|
2314
|
+
status: "failed",
|
|
2315
|
+
agentName: fanOutAgent.name,
|
|
2316
|
+
error: mapParallelBranchErrorToAppError(branchError)
|
|
2317
|
+
};
|
|
2318
|
+
}
|
|
2319
|
+
}
|
|
2320
|
+
async function applyParallelAgentToolBinding(context, fanOutIndex, agentName, agentOutput) {
|
|
2321
|
+
const bindingOutcome = await applyAgentToolBindingAfterStep(
|
|
2322
|
+
context,
|
|
2323
|
+
fanOutIndex,
|
|
2324
|
+
agentName,
|
|
2325
|
+
agentOutput
|
|
2326
|
+
);
|
|
2327
|
+
if (bindingOutcome.status !== "paused") {
|
|
2328
|
+
return;
|
|
2329
|
+
}
|
|
2330
|
+
throw new import_errors20.AppError({
|
|
2331
|
+
code: import_errors19.AppErrorCode.Conflict,
|
|
2332
|
+
message: `workflow "${context.definition.name}" agent "${agentName}" has a tool binding that requires approval, which is not supported inside parallel fan-out; run this agent in a pipeline or supervisor topology, or remove the approval requirement from the bound tool`,
|
|
2333
|
+
statusCode: 409,
|
|
2334
|
+
details: {
|
|
2335
|
+
workflowName: context.definition.name,
|
|
2336
|
+
agentName,
|
|
2337
|
+
fanOutIndex,
|
|
2338
|
+
approvalId: bindingOutcome.approvalId
|
|
2339
|
+
}
|
|
2340
|
+
});
|
|
2341
|
+
}
|
|
2342
|
+
async function ensureAllParallelBranchesSucceeded(context, branchResults) {
|
|
2343
|
+
const failures = branchResults.filter(
|
|
2344
|
+
(branchResult) => branchResult.status === "failed"
|
|
2345
|
+
);
|
|
2346
|
+
const firstFailure = failures[0];
|
|
2347
|
+
if (firstFailure === void 0) {
|
|
2348
|
+
return;
|
|
2349
|
+
}
|
|
2350
|
+
const failedAgentNames = failures.map((failure) => failure.agentName).join(", ");
|
|
2351
|
+
const failureReasons = failures.map((failure) => `${failure.agentName}: ${failure.error.message}`).join("; ");
|
|
2352
|
+
const parallelError = new import_errors20.AppError({
|
|
2353
|
+
code: firstFailure.error.code,
|
|
2354
|
+
message: `workflow "${context.definition.name}" parallel fan-out failed for agent(s) [${failedAgentNames}]: ${failureReasons}`,
|
|
2355
|
+
statusCode: firstFailure.error.statusCode,
|
|
2356
|
+
details: {
|
|
2357
|
+
workflowName: context.definition.name,
|
|
2358
|
+
failedAgentNames,
|
|
2359
|
+
failedAgentCount: failures.length
|
|
2360
|
+
},
|
|
2361
|
+
cause: firstFailure.error
|
|
2362
|
+
});
|
|
2363
|
+
await failWorkflowRun(context, parallelError);
|
|
2364
|
+
throw parallelError;
|
|
2365
|
+
}
|
|
2366
|
+
function mergeParallelBranchOutputs(branchResults) {
|
|
2367
|
+
const mergedOutput = {};
|
|
2368
|
+
for (const branchResult of branchResults) {
|
|
2369
|
+
if (branchResult.status === "completed") {
|
|
2370
|
+
mergedOutput[branchResult.agentName] = branchResult.output;
|
|
2371
|
+
}
|
|
2372
|
+
}
|
|
2373
|
+
return mergedOutput;
|
|
2374
|
+
}
|
|
2375
|
+
async function saveParallelBranchArtifact(context, fanOutIndex, agentName, branchOutput) {
|
|
2376
|
+
await context.artifactStore.saveRunArtifact({
|
|
2377
|
+
key: `step-${fanOutIndex}:${agentName}`,
|
|
2378
|
+
value: branchOutput,
|
|
2379
|
+
producedByAgentName: agentName,
|
|
2380
|
+
createdAt: context.now().toISOString()
|
|
2381
|
+
});
|
|
2382
|
+
}
|
|
2383
|
+
async function saveMergedParallelArtifact(context, topology, mergedOutput) {
|
|
2384
|
+
await context.artifactStore.saveRunArtifact({
|
|
2385
|
+
key: topology.mergedArtifactKey ?? defaultParallelMergedArtifactKey,
|
|
2386
|
+
value: mergedOutput,
|
|
2387
|
+
createdAt: context.now().toISOString()
|
|
2388
|
+
});
|
|
2389
|
+
}
|
|
2390
|
+
function mapParallelBranchErrorToAppError(branchError) {
|
|
2391
|
+
if (branchError instanceof import_errors20.AppError) {
|
|
2392
|
+
return branchError;
|
|
2393
|
+
}
|
|
2394
|
+
return new import_errors20.AppError({
|
|
2395
|
+
code: import_errors19.AppErrorCode.InternalServerError,
|
|
2396
|
+
message: branchError.message,
|
|
2397
|
+
statusCode: 500,
|
|
2398
|
+
cause: branchError
|
|
2399
|
+
});
|
|
2400
|
+
}
|
|
2401
|
+
|
|
2402
|
+
// src/workflows/workflow-supervisor-execution.ts
|
|
2403
|
+
var import_errors21 = require("@assemble-dev/shared-types/errors");
|
|
2404
|
+
var import_errors22 = require("@assemble-dev/shared-utils/errors");
|
|
2405
|
+
async function executeSupervisorSteps(context, topology, start) {
|
|
2406
|
+
let loopState = {
|
|
2407
|
+
stepIndex: start.startStepIndex,
|
|
2408
|
+
lastAgentName: start.lastAgentName,
|
|
2409
|
+
lastOutput: start.lastAgentName === null ? null : start.input,
|
|
2410
|
+
currentOutput: start.input
|
|
2411
|
+
};
|
|
2412
|
+
for (; ; ) {
|
|
2413
|
+
const routeContext = buildWorkflowRouteContext(context, loopState);
|
|
2414
|
+
if (topology.stopWhen !== void 0 && topology.stopWhen(routeContext)) {
|
|
2415
|
+
return { status: "completed", output: loopState.currentOutput };
|
|
2416
|
+
}
|
|
2417
|
+
const routeDecision = await resolveSupervisorRouteDecision(
|
|
2418
|
+
context,
|
|
2419
|
+
topology,
|
|
2420
|
+
routeContext
|
|
2421
|
+
);
|
|
2422
|
+
if (routeDecision.agentName === finishWorkflow) {
|
|
2423
|
+
return { status: "completed", output: loopState.currentOutput };
|
|
2424
|
+
}
|
|
2425
|
+
const stepOutput = await executeSupervisorStep(
|
|
2426
|
+
context,
|
|
2427
|
+
topology,
|
|
2428
|
+
loopState,
|
|
2429
|
+
routeDecision
|
|
2430
|
+
);
|
|
2431
|
+
const bindingOutcome = await applyAgentToolBindingAfterStep(
|
|
2432
|
+
context,
|
|
2433
|
+
loopState.stepIndex,
|
|
2434
|
+
routeDecision.agentName,
|
|
2435
|
+
stepOutput
|
|
2436
|
+
);
|
|
2437
|
+
if (bindingOutcome.status === "paused") {
|
|
2438
|
+
return bindingOutcome;
|
|
2439
|
+
}
|
|
2440
|
+
if (shouldStopWorkflowRun(context, loopState.stepIndex)) {
|
|
2441
|
+
return { status: "completed", output: stepOutput };
|
|
2442
|
+
}
|
|
2443
|
+
loopState = {
|
|
2444
|
+
stepIndex: loopState.stepIndex + 1,
|
|
2445
|
+
lastAgentName: routeDecision.agentName,
|
|
2446
|
+
lastOutput: stepOutput,
|
|
2447
|
+
currentOutput: stepOutput
|
|
2448
|
+
};
|
|
2449
|
+
}
|
|
2450
|
+
}
|
|
2451
|
+
async function executeSupervisorStep(context, topology, loopState, routeDecision) {
|
|
2452
|
+
const selectedAgent = await resolveRoutedWorkflowAgent(
|
|
2453
|
+
context,
|
|
2454
|
+
topology,
|
|
2455
|
+
routeDecision,
|
|
2456
|
+
loopState.stepIndex
|
|
2457
|
+
);
|
|
2458
|
+
await ensureSupervisorStepBudget(context, topology, loopState.stepIndex);
|
|
2459
|
+
emitRouteSelectedEvent(
|
|
2460
|
+
buildWorkflowEventContext(context),
|
|
2461
|
+
context.runStartedEventId,
|
|
2462
|
+
{
|
|
2463
|
+
supervisorName: topology.supervisorName,
|
|
2464
|
+
selectedAgentName: selectedAgent.name,
|
|
2465
|
+
candidateAgentNames: listWorkflowAgentNames(context),
|
|
2466
|
+
reason: routeDecision.reason
|
|
2467
|
+
}
|
|
2468
|
+
);
|
|
2469
|
+
const stepOutput = await executeWorkflowStep(
|
|
2470
|
+
context,
|
|
2471
|
+
loopState.stepIndex,
|
|
2472
|
+
selectedAgent,
|
|
2473
|
+
loopState.currentOutput
|
|
2474
|
+
);
|
|
2475
|
+
await persistWorkflowRunState(context);
|
|
2476
|
+
return stepOutput;
|
|
2477
|
+
}
|
|
2478
|
+
async function resolveSupervisorRouteDecision(context, topology, routeContext) {
|
|
2479
|
+
try {
|
|
2480
|
+
const routeTarget = await topology.route(routeContext);
|
|
2481
|
+
return normalizeWorkflowRouteTarget(routeTarget);
|
|
2482
|
+
} catch (thrownError) {
|
|
2483
|
+
const causeError = thrownError instanceof Error ? thrownError : new Error(String(thrownError));
|
|
2484
|
+
return await failSupervisorRun(
|
|
2485
|
+
context,
|
|
2486
|
+
new import_errors22.AppError({
|
|
2487
|
+
code: import_errors21.AppErrorCode.InternalServerError,
|
|
2488
|
+
message: `workflow "${context.definition.name}" supervisor "${topology.supervisorName}" route function threw at step ${routeContext.stepIndex}: ${causeError.message}; fix the route function to return an agent name or finishWorkflow`,
|
|
2489
|
+
statusCode: 500,
|
|
2490
|
+
details: {
|
|
2491
|
+
workflowName: context.definition.name,
|
|
2492
|
+
supervisorName: topology.supervisorName,
|
|
2493
|
+
stepIndex: routeContext.stepIndex,
|
|
2494
|
+
reason: causeError.message
|
|
2495
|
+
},
|
|
2496
|
+
cause: causeError
|
|
2497
|
+
})
|
|
2498
|
+
);
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
async function resolveRoutedWorkflowAgent(context, topology, routeDecision, stepIndex) {
|
|
2502
|
+
const selectedAgent = context.definition.agents.find(
|
|
2503
|
+
(agentDefinition) => agentDefinition.name === routeDecision.agentName
|
|
2504
|
+
);
|
|
2505
|
+
if (selectedAgent !== void 0) {
|
|
2506
|
+
return selectedAgent;
|
|
2507
|
+
}
|
|
2508
|
+
const validAgentNames = listWorkflowAgentNames(context).join(", ");
|
|
2509
|
+
return await failSupervisorRun(
|
|
2510
|
+
context,
|
|
2511
|
+
new import_errors22.AppError({
|
|
2512
|
+
code: import_errors21.AppErrorCode.BadRequest,
|
|
2513
|
+
message: `workflow "${context.definition.name}" supervisor "${topology.supervisorName}" routed to unknown agent "${routeDecision.agentName}" at step ${stepIndex}; valid agent names: ${validAgentNames}`,
|
|
2514
|
+
statusCode: 400,
|
|
2515
|
+
details: {
|
|
2516
|
+
workflowName: context.definition.name,
|
|
2517
|
+
supervisorName: topology.supervisorName,
|
|
2518
|
+
selectedAgentName: routeDecision.agentName,
|
|
2519
|
+
validAgentNames,
|
|
2520
|
+
stepIndex
|
|
2521
|
+
}
|
|
2522
|
+
})
|
|
2523
|
+
);
|
|
2524
|
+
}
|
|
2525
|
+
async function ensureSupervisorStepBudget(context, topology, stepIndex) {
|
|
2526
|
+
const maxSteps = topology.maxSteps ?? defaultSupervisorMaxSteps;
|
|
2527
|
+
if (stepIndex < maxSteps) {
|
|
2528
|
+
return;
|
|
2529
|
+
}
|
|
2530
|
+
await failSupervisorRun(
|
|
2531
|
+
context,
|
|
2532
|
+
new import_errors22.AppError({
|
|
2533
|
+
code: import_errors21.AppErrorCode.Conflict,
|
|
2534
|
+
message: `workflow "${context.definition.name}" supervisor "${topology.supervisorName}" exceeded ${maxSteps} steps; return finishWorkflow from the route function, add a stopWhen condition, or increase maxSteps`,
|
|
2535
|
+
statusCode: 409,
|
|
2536
|
+
details: {
|
|
2537
|
+
workflowName: context.definition.name,
|
|
2538
|
+
supervisorName: topology.supervisorName,
|
|
2539
|
+
maxSteps
|
|
2540
|
+
}
|
|
2541
|
+
})
|
|
2542
|
+
);
|
|
2543
|
+
}
|
|
2544
|
+
async function failSupervisorRun(context, supervisorError) {
|
|
2545
|
+
await failWorkflowRun(context, supervisorError);
|
|
2546
|
+
throw supervisorError;
|
|
2547
|
+
}
|
|
2548
|
+
function normalizeWorkflowRouteTarget(routeTarget) {
|
|
2549
|
+
if (typeof routeTarget === "string") {
|
|
2550
|
+
return {
|
|
2551
|
+
agentName: routeTarget,
|
|
2552
|
+
reason: `route function selected ${routeTarget}`
|
|
2553
|
+
};
|
|
2554
|
+
}
|
|
2555
|
+
return routeTarget;
|
|
2556
|
+
}
|
|
2557
|
+
function listWorkflowAgentNames(context) {
|
|
2558
|
+
return context.definition.agents.map(
|
|
2559
|
+
(agentDefinition) => agentDefinition.name
|
|
2560
|
+
);
|
|
2561
|
+
}
|
|
2562
|
+
function buildWorkflowRouteContext(context, loopState) {
|
|
2563
|
+
return {
|
|
2564
|
+
state: context.traceBus.getRunState(),
|
|
2565
|
+
stepIndex: loopState.stepIndex,
|
|
2566
|
+
lastAgentName: loopState.lastAgentName,
|
|
2567
|
+
lastOutput: loopState.lastOutput
|
|
2568
|
+
};
|
|
2569
|
+
}
|
|
2570
|
+
|
|
2571
|
+
// src/workflows/workflow-runtime.ts
|
|
2572
|
+
async function runWorkflow(runInput) {
|
|
2573
|
+
const validatedInput = validateWorkflowInput(
|
|
2574
|
+
runInput.definition,
|
|
2575
|
+
runInput.input
|
|
2576
|
+
);
|
|
2577
|
+
const context = createWorkflowRuntimeContext(
|
|
2578
|
+
runInput.definition,
|
|
2579
|
+
validatedInput,
|
|
2580
|
+
runInput.options
|
|
2581
|
+
);
|
|
2582
|
+
await persistWorkflowRunState(context);
|
|
2583
|
+
return await continueWorkflowRunFromStep(context, {
|
|
2584
|
+
input: validatedInput,
|
|
2585
|
+
startStepIndex: 0,
|
|
2586
|
+
lastAgentName: null
|
|
2587
|
+
});
|
|
2588
|
+
}
|
|
2589
|
+
async function continueWorkflowRunFromStep(context, start) {
|
|
2590
|
+
const outcome = await executeWorkflowTopologySteps(context, start);
|
|
2591
|
+
if (outcome.status === "paused") {
|
|
2592
|
+
return await buildAwaitingApprovalRunResult(context, outcome.approvalId);
|
|
2593
|
+
}
|
|
2594
|
+
return await completeWorkflowRun(context, outcome.output);
|
|
2595
|
+
}
|
|
2596
|
+
async function executeWorkflowTopologySteps(context, start) {
|
|
2597
|
+
const topology = context.definition.topology;
|
|
2598
|
+
if (topology.kind === "supervisor") {
|
|
2599
|
+
return await executeSupervisorSteps(context, topology, start);
|
|
2600
|
+
}
|
|
2601
|
+
if (topology.kind === "parallel") {
|
|
2602
|
+
return await executeParallelSteps(context, topology, start);
|
|
2603
|
+
}
|
|
2604
|
+
return await executePipelineSteps(context, start);
|
|
2605
|
+
}
|
|
2606
|
+
function validateWorkflowInput(definition, input) {
|
|
2607
|
+
const parsedInput = definition.inputSchema.safeParse(input);
|
|
2608
|
+
if (parsedInput.success) {
|
|
2609
|
+
return parsedInput.data;
|
|
2610
|
+
}
|
|
2611
|
+
throw new import_errors24.AppError({
|
|
2612
|
+
code: import_errors23.AppErrorCode.ValidationFailed,
|
|
2613
|
+
message: `workflow "${definition.name}" input failed schema validation: ${parsedInput.error.message}`,
|
|
2614
|
+
statusCode: 422
|
|
2615
|
+
});
|
|
2616
|
+
}
|
|
2617
|
+
function createWorkflowRuntimeContext(definition, validatedInput, options) {
|
|
2618
|
+
const now = options?.now ?? (() => /* @__PURE__ */ new Date());
|
|
2619
|
+
const runId = options?.runId ?? (0, import_ids3.generateRunId)();
|
|
2620
|
+
const startedAtMs = now().getTime();
|
|
2621
|
+
const traceBus = createTraceBus({
|
|
2622
|
+
runId,
|
|
2623
|
+
workflowName: definition.name,
|
|
2624
|
+
input: validatedInput,
|
|
2625
|
+
sinks: options?.sinks ?? [],
|
|
2626
|
+
now
|
|
2627
|
+
});
|
|
2628
|
+
const memoryConfig = resolveWorkflowMemoryConfig(definition.memory ?? void 0);
|
|
2629
|
+
const runStartedEvent = emitRunStartedEvent(
|
|
2630
|
+
{ traceBus, runId, workflowName: definition.name, now },
|
|
2631
|
+
validatedInput
|
|
2632
|
+
);
|
|
2633
|
+
return {
|
|
2634
|
+
definition,
|
|
2635
|
+
traceBus,
|
|
2636
|
+
runId,
|
|
2637
|
+
runStartedEventId: runStartedEvent.id,
|
|
2638
|
+
threadMemory: createThreadMemory({
|
|
2639
|
+
provider: memoryConfig.provider,
|
|
2640
|
+
threadKey: runId
|
|
2641
|
+
}),
|
|
2642
|
+
memoryProvider: memoryConfig.provider,
|
|
2643
|
+
artifactStore: createArtifactStore({
|
|
2644
|
+
provider: memoryConfig.provider,
|
|
2645
|
+
runId
|
|
2646
|
+
}),
|
|
2647
|
+
now,
|
|
2648
|
+
startedAtMs,
|
|
2649
|
+
persistRunState: options?.persistRunState ?? true,
|
|
2650
|
+
runStateDirectory: options?.runStateDirectory ?? defaultRunStateDirectory
|
|
2651
|
+
};
|
|
2652
|
+
}
|
|
2653
|
+
async function executePipelineSteps(context, start) {
|
|
2654
|
+
const stepAgents = context.definition.agents;
|
|
2655
|
+
let currentStepInput = start.input;
|
|
2656
|
+
for (let stepIndex = start.startStepIndex; stepIndex < stepAgents.length; stepIndex += 1) {
|
|
2657
|
+
const stepAgent = stepAgents[stepIndex];
|
|
2658
|
+
if (stepAgent === void 0) {
|
|
2659
|
+
break;
|
|
2660
|
+
}
|
|
2661
|
+
currentStepInput = await executeWorkflowStep(
|
|
2662
|
+
context,
|
|
2663
|
+
stepIndex,
|
|
2664
|
+
stepAgent,
|
|
2665
|
+
currentStepInput
|
|
2666
|
+
);
|
|
2667
|
+
await persistWorkflowRunState(context);
|
|
2668
|
+
const bindingOutcome = await applyAgentToolBindingAfterStep(
|
|
2669
|
+
context,
|
|
2670
|
+
stepIndex,
|
|
2671
|
+
stepAgent.name,
|
|
2672
|
+
currentStepInput
|
|
2673
|
+
);
|
|
2674
|
+
if (bindingOutcome.status === "paused") {
|
|
2675
|
+
return bindingOutcome;
|
|
2676
|
+
}
|
|
2677
|
+
if (stepIndex < stepAgents.length - 1 && shouldStopWorkflowRun(context, stepIndex)) {
|
|
2678
|
+
break;
|
|
2679
|
+
}
|
|
2680
|
+
}
|
|
2681
|
+
return { status: "completed", output: currentStepInput };
|
|
2682
|
+
}
|
|
2683
|
+
async function buildAwaitingApprovalRunResult(context, approvalId) {
|
|
2684
|
+
return {
|
|
2685
|
+
status: "awaiting_approval",
|
|
2686
|
+
runId: context.runId,
|
|
2687
|
+
approvalId,
|
|
2688
|
+
state: await buildWorkflowRunState(context),
|
|
2689
|
+
events: context.traceBus.listTraceEvents()
|
|
2690
|
+
};
|
|
2691
|
+
}
|
|
2692
|
+
async function completeWorkflowRun(context, lastStepOutput) {
|
|
2693
|
+
const finalOutput = await validateWorkflowOutput(context, lastStepOutput);
|
|
2694
|
+
emitRunCompletedEvent(
|
|
2695
|
+
buildWorkflowEventContext(context),
|
|
2696
|
+
context.runStartedEventId,
|
|
2697
|
+
finalOutput,
|
|
2698
|
+
calculateWorkflowElapsedMs(context)
|
|
2699
|
+
);
|
|
2700
|
+
await persistWorkflowRunState(context);
|
|
2701
|
+
await context.traceBus.flushSinks();
|
|
2702
|
+
return {
|
|
2703
|
+
status: "completed",
|
|
2704
|
+
runId: context.runId,
|
|
2705
|
+
output: finalOutput,
|
|
2706
|
+
state: await buildWorkflowRunState(context),
|
|
2707
|
+
events: context.traceBus.listTraceEvents()
|
|
2708
|
+
};
|
|
2709
|
+
}
|
|
2710
|
+
async function validateWorkflowOutput(context, lastStepOutput) {
|
|
2711
|
+
if (context.definition.outputSchema === null) {
|
|
2712
|
+
return lastStepOutput;
|
|
2713
|
+
}
|
|
2714
|
+
const parsedOutput = context.definition.outputSchema.safeParse(lastStepOutput);
|
|
2715
|
+
if (parsedOutput.success) {
|
|
2716
|
+
return parsedOutput.data;
|
|
2717
|
+
}
|
|
2718
|
+
const validationError = new import_errors24.AppError({
|
|
2719
|
+
code: import_errors23.AppErrorCode.ValidationFailed,
|
|
2720
|
+
message: `workflow "${context.definition.name}" output failed schema validation: ${parsedOutput.error.message}`,
|
|
2721
|
+
statusCode: 422
|
|
2722
|
+
});
|
|
2723
|
+
await failWorkflowRun(context, validationError);
|
|
2724
|
+
throw validationError;
|
|
2725
|
+
}
|
|
2726
|
+
|
|
2727
|
+
// src/workflows/workflow-definition.ts
|
|
2728
|
+
function workflow(config) {
|
|
2729
|
+
const definition = Object.freeze({
|
|
2730
|
+
name: config.name,
|
|
2731
|
+
inputSchema: config.inputSchema,
|
|
2732
|
+
outputSchema: config.outputSchema ?? null,
|
|
2733
|
+
agents: Object.freeze([...config.agents]),
|
|
2734
|
+
tools: Object.freeze([...config.tools ?? []]),
|
|
2735
|
+
agentToolBindings: Object.freeze([...config.agentToolBindings ?? []]),
|
|
2736
|
+
topology: config.topology ?? defaultWorkflowTopology,
|
|
2737
|
+
policies: Object.freeze([...config.policies ?? []]),
|
|
2738
|
+
memory: config.memory ?? null,
|
|
2739
|
+
stopCondition: config.stopCondition ?? null,
|
|
2740
|
+
continueOnAgentFailure: config.continueOnAgentFailure ?? false,
|
|
2741
|
+
run: async (input, options) => {
|
|
2742
|
+
return await runWorkflow({ definition, input, options });
|
|
2743
|
+
}
|
|
2744
|
+
});
|
|
2745
|
+
return definition;
|
|
2746
|
+
}
|
|
2747
|
+
|
|
2748
|
+
// src/workflows/resume-run.ts
|
|
2749
|
+
var import_node_fs2 = require("fs");
|
|
2750
|
+
var import_errors25 = require("@assemble-dev/shared-types/errors");
|
|
2751
|
+
var import_errors26 = require("@assemble-dev/shared-utils/errors");
|
|
2752
|
+
async function resumeRun(resumeInput) {
|
|
2753
|
+
const directory = resumeInput.options?.runStateDirectory ?? defaultRunStateDirectory;
|
|
2754
|
+
const persistedState = loadPersistedRunStateForResume(
|
|
2755
|
+
resumeInput.runId,
|
|
2756
|
+
directory
|
|
2757
|
+
);
|
|
2758
|
+
const pendingApproval = findPendingWorkflowApproval(persistedState);
|
|
2759
|
+
const context = await createResumedWorkflowRuntimeContext(
|
|
2760
|
+
resumeInput.definition,
|
|
2761
|
+
persistedState,
|
|
2762
|
+
pendingApproval,
|
|
2763
|
+
resumeInput.options
|
|
2764
|
+
);
|
|
2765
|
+
if (!resumeInput.decision.approved) {
|
|
2766
|
+
return await rejectPendingWorkflowApproval(
|
|
2767
|
+
context,
|
|
2768
|
+
pendingApproval,
|
|
2769
|
+
resumeInput.decision
|
|
2770
|
+
);
|
|
2771
|
+
}
|
|
2772
|
+
return await approvePendingWorkflowApproval(
|
|
2773
|
+
context,
|
|
2774
|
+
pendingApproval,
|
|
2775
|
+
resumeInput.decision
|
|
2776
|
+
);
|
|
2777
|
+
}
|
|
2778
|
+
function loadPersistedRunStateForResume(runId, directory) {
|
|
2779
|
+
const filePath = resolveRunStateFilePath(runId, directory);
|
|
2780
|
+
if (!(0, import_node_fs2.existsSync)(filePath)) {
|
|
2781
|
+
throw new import_errors26.AppError({
|
|
2782
|
+
code: import_errors25.AppErrorCode.NotFound,
|
|
2783
|
+
message: `run state file not found at "${filePath}" for run "${runId}"; the run was never persisted or the runStateDirectory is wrong`,
|
|
2784
|
+
statusCode: 404,
|
|
2785
|
+
details: { runId, filePath }
|
|
2786
|
+
});
|
|
2787
|
+
}
|
|
2788
|
+
const persistedState = loadRunStateFile({ runId, directory });
|
|
2789
|
+
if (persistedState.status !== "awaiting_approval") {
|
|
2790
|
+
throw new import_errors26.AppError({
|
|
2791
|
+
code: import_errors25.AppErrorCode.Conflict,
|
|
2792
|
+
message: `run "${runId}" has status "${persistedState.status}" and cannot be resumed; only runs with status "awaiting_approval" can be resumed`,
|
|
2793
|
+
statusCode: 409,
|
|
2794
|
+
details: { runId, status: persistedState.status }
|
|
2795
|
+
});
|
|
2796
|
+
}
|
|
2797
|
+
return persistedState;
|
|
2798
|
+
}
|
|
2799
|
+
function findPendingWorkflowApproval(persistedState) {
|
|
2800
|
+
const pendingArtifact = persistedState.artifacts.find(
|
|
2801
|
+
(artifact) => artifact.key === pendingApprovalArtifactKey
|
|
2802
|
+
);
|
|
2803
|
+
if (pendingArtifact === void 0) {
|
|
2804
|
+
throw new import_errors26.AppError({
|
|
2805
|
+
code: import_errors25.AppErrorCode.Conflict,
|
|
2806
|
+
message: `run "${persistedState.runId}" is awaiting approval but has no "${pendingApprovalArtifactKey}" artifact; the persisted state is incomplete and the run cannot be resumed`,
|
|
2807
|
+
statusCode: 409,
|
|
2808
|
+
details: { runId: persistedState.runId }
|
|
2809
|
+
});
|
|
2810
|
+
}
|
|
2811
|
+
return PendingWorkflowApprovalSchema.parse(pendingArtifact.value);
|
|
2812
|
+
}
|
|
2813
|
+
async function createResumedWorkflowRuntimeContext(definition, persistedState, pendingApproval, options) {
|
|
2814
|
+
const now = options?.now ?? (() => /* @__PURE__ */ new Date());
|
|
2815
|
+
const traceBus = createTraceBus({
|
|
2816
|
+
runId: persistedState.runId,
|
|
2817
|
+
workflowName: definition.name,
|
|
2818
|
+
input: persistedState.input,
|
|
2819
|
+
sinks: options?.sinks ?? [],
|
|
2820
|
+
now,
|
|
2821
|
+
initialRunState: persistedState
|
|
2822
|
+
});
|
|
2823
|
+
const memoryConfig = resolveWorkflowMemoryConfig(definition.memory ?? void 0);
|
|
2824
|
+
const artifactStore = createArtifactStore({
|
|
2825
|
+
provider: memoryConfig.provider,
|
|
2826
|
+
runId: persistedState.runId
|
|
2827
|
+
});
|
|
2828
|
+
for (const artifact of persistedState.artifacts) {
|
|
2829
|
+
await artifactStore.saveRunArtifact(artifact);
|
|
2830
|
+
}
|
|
2831
|
+
return {
|
|
2832
|
+
definition,
|
|
2833
|
+
traceBus,
|
|
2834
|
+
runId: persistedState.runId,
|
|
2835
|
+
runStartedEventId: pendingApproval.runStartedEventId,
|
|
2836
|
+
threadMemory: createThreadMemory({
|
|
2837
|
+
provider: memoryConfig.provider,
|
|
2838
|
+
threadKey: persistedState.runId
|
|
2839
|
+
}),
|
|
2840
|
+
memoryProvider: memoryConfig.provider,
|
|
2841
|
+
artifactStore,
|
|
2842
|
+
now,
|
|
2843
|
+
startedAtMs: resolveResumedRunStartedAtMs(persistedState, now),
|
|
2844
|
+
persistRunState: options?.persistRunState ?? true,
|
|
2845
|
+
runStateDirectory: options?.runStateDirectory ?? defaultRunStateDirectory
|
|
2846
|
+
};
|
|
2847
|
+
}
|
|
2848
|
+
function resolveResumedRunStartedAtMs(persistedState, now) {
|
|
2849
|
+
if (persistedState.startedAt === null) {
|
|
2850
|
+
return now().getTime();
|
|
2851
|
+
}
|
|
2852
|
+
return new Date(persistedState.startedAt).getTime();
|
|
2853
|
+
}
|
|
2854
|
+
async function rejectPendingWorkflowApproval(context, pendingApproval, decision) {
|
|
2855
|
+
const rejectionReason = decision.reason ?? "approval rejected";
|
|
2856
|
+
emitApprovalRejectedEvent(
|
|
2857
|
+
buildWorkflowEventContext(context),
|
|
2858
|
+
{
|
|
2859
|
+
approvalId: pendingApproval.approvalId,
|
|
2860
|
+
decidedBy: decision.approvedBy,
|
|
2861
|
+
parentEventId: pendingApproval.toolRequestEventId
|
|
2862
|
+
},
|
|
2863
|
+
rejectionReason
|
|
2864
|
+
);
|
|
2865
|
+
await failWorkflowRun(
|
|
2866
|
+
context,
|
|
2867
|
+
new import_errors26.AppError({
|
|
2868
|
+
code: import_errors25.AppErrorCode.Forbidden,
|
|
2869
|
+
message: `tool "${pendingApproval.toolName}" at step ${pendingApproval.stepIndex} was rejected by "${decision.approvedBy}": ${rejectionReason}`,
|
|
2870
|
+
statusCode: 403,
|
|
2871
|
+
details: {
|
|
2872
|
+
toolName: pendingApproval.toolName,
|
|
2873
|
+
stepIndex: pendingApproval.stepIndex,
|
|
2874
|
+
decidedBy: decision.approvedBy,
|
|
2875
|
+
reason: rejectionReason
|
|
2876
|
+
}
|
|
2877
|
+
})
|
|
2878
|
+
);
|
|
2879
|
+
await context.traceBus.flushSinks();
|
|
2880
|
+
return {
|
|
2881
|
+
status: "rejected",
|
|
2882
|
+
runId: context.runId,
|
|
2883
|
+
state: await buildWorkflowRunState(context),
|
|
2884
|
+
events: context.traceBus.listTraceEvents()
|
|
2885
|
+
};
|
|
2886
|
+
}
|
|
2887
|
+
async function approvePendingWorkflowApproval(context, pendingApproval, decision) {
|
|
2888
|
+
emitApprovalApprovedEvent(buildWorkflowEventContext(context), {
|
|
2889
|
+
approvalId: pendingApproval.approvalId,
|
|
2890
|
+
decidedBy: decision.approvedBy,
|
|
2891
|
+
parentEventId: pendingApproval.toolRequestEventId
|
|
2892
|
+
});
|
|
2893
|
+
await executeApprovedWorkflowTool(context, pendingApproval);
|
|
2894
|
+
const result = await continueWorkflowRunFromStep(context, {
|
|
2895
|
+
input: pendingApproval.agentOutput,
|
|
2896
|
+
startStepIndex: pendingApproval.stepIndex + 1,
|
|
2897
|
+
lastAgentName: pendingApproval.agentName
|
|
2898
|
+
});
|
|
2899
|
+
await context.traceBus.flushSinks();
|
|
2900
|
+
return result;
|
|
2901
|
+
}
|
|
2902
|
+
|
|
2903
|
+
// src/memory/json-file-provider.ts
|
|
2904
|
+
var import_node_fs3 = require("fs");
|
|
2905
|
+
var import_node_path2 = require("path");
|
|
2906
|
+
var import_json4 = require("@assemble-dev/shared-types/json");
|
|
2907
|
+
function parseMemoryFileContents(rawContents) {
|
|
2908
|
+
try {
|
|
2909
|
+
const parsedContents = import_json4.JsonObjectSchema.parse(JSON.parse(rawContents));
|
|
2910
|
+
return new Map(Object.entries(parsedContents));
|
|
2911
|
+
} catch {
|
|
2912
|
+
return /* @__PURE__ */ new Map();
|
|
2913
|
+
}
|
|
2914
|
+
}
|
|
2915
|
+
function loadMemoryEntriesFromFile(filePath) {
|
|
2916
|
+
if (!(0, import_node_fs3.existsSync)(filePath)) {
|
|
2917
|
+
return /* @__PURE__ */ new Map();
|
|
2918
|
+
}
|
|
2919
|
+
return parseMemoryFileContents((0, import_node_fs3.readFileSync)(filePath, "utf8"));
|
|
2920
|
+
}
|
|
2921
|
+
function writeMemoryEntriesToFile(filePath, memoryEntries) {
|
|
2922
|
+
(0, import_node_fs3.mkdirSync)((0, import_node_path2.dirname)(filePath), { recursive: true });
|
|
2923
|
+
(0, import_node_fs3.writeFileSync)(
|
|
2924
|
+
filePath,
|
|
2925
|
+
JSON.stringify(Object.fromEntries(memoryEntries)),
|
|
2926
|
+
"utf8"
|
|
2927
|
+
);
|
|
2928
|
+
}
|
|
2929
|
+
function createJsonFileMemoryProvider(options) {
|
|
2930
|
+
let memoryEntries = null;
|
|
2931
|
+
const ensureMemoryEntriesLoaded = () => {
|
|
2932
|
+
if (memoryEntries === null) {
|
|
2933
|
+
memoryEntries = loadMemoryEntriesFromFile(options.filePath);
|
|
2934
|
+
}
|
|
2935
|
+
return memoryEntries;
|
|
2936
|
+
};
|
|
2937
|
+
return {
|
|
2938
|
+
async getMemoryValue(key) {
|
|
2939
|
+
return await Promise.resolve(
|
|
2940
|
+
ensureMemoryEntriesLoaded().get(key) ?? null
|
|
2941
|
+
);
|
|
2942
|
+
},
|
|
2943
|
+
async setMemoryValue(key, value) {
|
|
2944
|
+
const loadedEntries = ensureMemoryEntriesLoaded();
|
|
2945
|
+
loadedEntries.set(key, value);
|
|
2946
|
+
writeMemoryEntriesToFile(options.filePath, loadedEntries);
|
|
2947
|
+
await Promise.resolve();
|
|
2948
|
+
},
|
|
2949
|
+
async appendMemoryValue(key, value) {
|
|
2950
|
+
const loadedEntries = ensureMemoryEntriesLoaded();
|
|
2951
|
+
loadedEntries.set(key, buildAppendedMemoryValue(loadedEntries.get(key), value));
|
|
2952
|
+
writeMemoryEntriesToFile(options.filePath, loadedEntries);
|
|
2953
|
+
await Promise.resolve();
|
|
2954
|
+
},
|
|
2955
|
+
async listMemoryKeys() {
|
|
2956
|
+
return await Promise.resolve([...ensureMemoryEntriesLoaded().keys()]);
|
|
2957
|
+
}
|
|
2958
|
+
};
|
|
2959
|
+
}
|
|
2960
|
+
|
|
2961
|
+
// src/tracing/console-sink.ts
|
|
2962
|
+
var ansiCodes = {
|
|
2963
|
+
reset: "\x1B[0m",
|
|
2964
|
+
dim: "\x1B[2m",
|
|
2965
|
+
green: "\x1B[32m",
|
|
2966
|
+
red: "\x1B[31m",
|
|
2967
|
+
yellow: "\x1B[33m",
|
|
2968
|
+
blue: "\x1B[34m",
|
|
2969
|
+
magenta: "\x1B[35m",
|
|
2970
|
+
cyan: "\x1B[36m"
|
|
2971
|
+
};
|
|
2972
|
+
var colorByEventCategory = {
|
|
2973
|
+
run: "magenta",
|
|
2974
|
+
agent: "blue",
|
|
2975
|
+
tool: "cyan",
|
|
2976
|
+
policy: "yellow",
|
|
2977
|
+
route: "yellow",
|
|
2978
|
+
approval: "yellow",
|
|
2979
|
+
eval: "green",
|
|
2980
|
+
replay: "green"
|
|
2981
|
+
};
|
|
2982
|
+
function createConsoleSink(options = {}) {
|
|
2983
|
+
const writeLine = options.writeLine ?? ((line) => process.stdout.write(`${line}
|
|
2984
|
+
`));
|
|
2985
|
+
const useColor = options.useColor ?? true;
|
|
2986
|
+
return {
|
|
2987
|
+
name: "console",
|
|
2988
|
+
writeTraceEvent(event) {
|
|
2989
|
+
writeLine(formatTraceEventLine(event, useColor));
|
|
2990
|
+
}
|
|
2991
|
+
};
|
|
2992
|
+
}
|
|
2993
|
+
function formatTraceEventLine(event, useColor) {
|
|
2994
|
+
const time = event.timestamp.slice(11, 23);
|
|
2995
|
+
const category = event.type.split(".")[0] ?? "run";
|
|
2996
|
+
const detail = formatTraceEventDetail(event);
|
|
2997
|
+
const suffix = detail === "" ? "" : ` ${detail}`;
|
|
2998
|
+
if (!useColor) {
|
|
2999
|
+
return `[${time}] ${event.type}${suffix}`;
|
|
3000
|
+
}
|
|
3001
|
+
const color = ansiCodes[colorByEventCategory[category] ?? "blue"];
|
|
3002
|
+
const failed = event.type.endsWith(".failed") || event.type.endsWith(".blocked");
|
|
3003
|
+
const typeColor = failed ? ansiCodes.red : color;
|
|
3004
|
+
return `${ansiCodes.dim}[${time}]${ansiCodes.reset} ${typeColor}${event.type}${ansiCodes.reset}${suffix}`;
|
|
3005
|
+
}
|
|
3006
|
+
function formatTraceEventDetail(event) {
|
|
3007
|
+
const parts = [];
|
|
3008
|
+
if ("agentName" in event && event.agentName !== void 0) {
|
|
3009
|
+
parts.push(`agent=${event.agentName}`);
|
|
3010
|
+
}
|
|
3011
|
+
if ("toolName" in event && event.toolName !== void 0) {
|
|
3012
|
+
parts.push(`tool=${event.toolName}`);
|
|
3013
|
+
}
|
|
3014
|
+
if ("selectedAgentName" in event) {
|
|
3015
|
+
parts.push(`selected=${event.selectedAgentName}`);
|
|
3016
|
+
}
|
|
3017
|
+
if ("action" in event) {
|
|
3018
|
+
parts.push(`action=${event.action}`);
|
|
3019
|
+
}
|
|
3020
|
+
if ("approvalId" in event) {
|
|
3021
|
+
parts.push(`approval=${event.approvalId}`);
|
|
3022
|
+
}
|
|
3023
|
+
if ("latencyMs" in event) {
|
|
3024
|
+
parts.push(`${Math.round(event.latencyMs)}ms`);
|
|
3025
|
+
}
|
|
3026
|
+
if ("error" in event) {
|
|
3027
|
+
parts.push(`error="${event.error.message}"`);
|
|
3028
|
+
}
|
|
3029
|
+
if ("reason" in event) {
|
|
3030
|
+
parts.push(`reason="${event.reason}"`);
|
|
3031
|
+
}
|
|
3032
|
+
return parts.join(" ");
|
|
3033
|
+
}
|
|
3034
|
+
|
|
3035
|
+
// src/tracing/jsonl-sink.ts
|
|
3036
|
+
var import_node_fs4 = require("fs");
|
|
3037
|
+
var import_node_path3 = require("path");
|
|
3038
|
+
var defaultTraceDirectory = (0, import_node_path3.join)(".assemble", "runs");
|
|
3039
|
+
function resolveTraceFilePath(runId, directory = defaultTraceDirectory) {
|
|
3040
|
+
return (0, import_node_path3.join)(directory, `${runId}.jsonl`);
|
|
3041
|
+
}
|
|
3042
|
+
function createJsonlSink(options) {
|
|
3043
|
+
const directory = options.directory ?? defaultTraceDirectory;
|
|
3044
|
+
const filePath = resolveTraceFilePath(options.runId, directory);
|
|
3045
|
+
(0, import_node_fs4.mkdirSync)(directory, { recursive: true });
|
|
3046
|
+
return {
|
|
3047
|
+
name: "jsonl",
|
|
3048
|
+
writeTraceEvent(event) {
|
|
3049
|
+
(0, import_node_fs4.appendFileSync)(filePath, `${JSON.stringify(event)}
|
|
3050
|
+
`, "utf8");
|
|
3051
|
+
}
|
|
3052
|
+
};
|
|
3053
|
+
}
|
|
3054
|
+
|
|
3055
|
+
// src/platform/platform-trace-sink.ts
|
|
3056
|
+
var import_redaction4 = require("@assemble-dev/shared-utils/redaction");
|
|
3057
|
+
|
|
3058
|
+
// src/platform/platform-config.ts
|
|
3059
|
+
var import_node_fs5 = require("fs");
|
|
3060
|
+
var import_node_os = require("os");
|
|
3061
|
+
var import_node_path4 = require("path");
|
|
3062
|
+
var import_zod5 = require("zod");
|
|
3063
|
+
var defaultPlatformBaseUrl = "https://api.assemble.dev";
|
|
3064
|
+
var defaultPlatformDashboardUrl = "https://app.assemble.dev";
|
|
3065
|
+
var PlatformEnvSchema = import_zod5.z.object({
|
|
3066
|
+
ASSEMBLE_API_KEY: import_zod5.z.string().min(1).optional(),
|
|
3067
|
+
ASSEMBLE_BASE_URL: import_zod5.z.string().min(1).optional(),
|
|
3068
|
+
ASSEMBLE_DASHBOARD_URL: import_zod5.z.string().min(1).optional()
|
|
3069
|
+
});
|
|
3070
|
+
var PlatformConfigFileSchema = import_zod5.z.object({
|
|
3071
|
+
apiKey: import_zod5.z.string().min(1).optional(),
|
|
3072
|
+
baseUrl: import_zod5.z.string().min(1).optional()
|
|
3073
|
+
});
|
|
3074
|
+
var PlatformConfigSchema = import_zod5.z.object({
|
|
3075
|
+
apiKey: import_zod5.z.string().min(1).nullable(),
|
|
3076
|
+
baseUrl: import_zod5.z.string().min(1),
|
|
3077
|
+
dashboardUrl: import_zod5.z.string().min(1)
|
|
3078
|
+
});
|
|
3079
|
+
function resolvePlatformConfigFilePath(homeDirectory) {
|
|
3080
|
+
return (0, import_node_path4.join)(homeDirectory, ".assemble", "config.json");
|
|
3081
|
+
}
|
|
3082
|
+
function parsePlatformEnvironment(environmentVariables) {
|
|
3083
|
+
const parsedEnvironment = PlatformEnvSchema.safeParse(environmentVariables);
|
|
3084
|
+
if (parsedEnvironment.success) {
|
|
3085
|
+
return parsedEnvironment.data;
|
|
3086
|
+
}
|
|
3087
|
+
return {};
|
|
3088
|
+
}
|
|
3089
|
+
function isMissingConfigFileError(failure) {
|
|
3090
|
+
return "code" in failure && failure.code === "ENOENT";
|
|
3091
|
+
}
|
|
3092
|
+
function readPlatformConfigFile(homeDirectory) {
|
|
3093
|
+
const configFilePath = resolvePlatformConfigFilePath(homeDirectory);
|
|
3094
|
+
try {
|
|
3095
|
+
const rawContents = (0, import_node_fs5.readFileSync)(configFilePath, "utf8");
|
|
3096
|
+
return PlatformConfigFileSchema.parse(JSON.parse(rawContents));
|
|
3097
|
+
} catch (caughtError) {
|
|
3098
|
+
const failure = caughtError instanceof Error ? caughtError : new Error(String(caughtError));
|
|
3099
|
+
if (!isMissingConfigFileError(failure)) {
|
|
3100
|
+
console.error(
|
|
3101
|
+
`assemble platform config: failed to load config file at ${configFilePath}: ${failure.message}`
|
|
3102
|
+
);
|
|
3103
|
+
}
|
|
3104
|
+
return {};
|
|
3105
|
+
}
|
|
3106
|
+
}
|
|
3107
|
+
function resolvePlatformConfig(options) {
|
|
3108
|
+
const environment = parsePlatformEnvironment(
|
|
3109
|
+
options?.environmentVariables ?? process.env
|
|
3110
|
+
);
|
|
3111
|
+
const configFile = readPlatformConfigFile(
|
|
3112
|
+
options?.homeDirectory ?? (0, import_node_os.homedir)()
|
|
3113
|
+
);
|
|
3114
|
+
return PlatformConfigSchema.parse({
|
|
3115
|
+
apiKey: options?.apiKey ?? environment.ASSEMBLE_API_KEY ?? configFile.apiKey ?? null,
|
|
3116
|
+
baseUrl: options?.baseUrl ?? environment.ASSEMBLE_BASE_URL ?? configFile.baseUrl ?? defaultPlatformBaseUrl,
|
|
3117
|
+
dashboardUrl: options?.dashboardUrl ?? environment.ASSEMBLE_DASHBOARD_URL ?? defaultPlatformDashboardUrl
|
|
3118
|
+
});
|
|
3119
|
+
}
|
|
3120
|
+
|
|
3121
|
+
// src/platform/redact-trace-event.ts
|
|
3122
|
+
var import_trace_events3 = require("@assemble-dev/shared-types/trace-events");
|
|
3123
|
+
var import_redaction3 = require("@assemble-dev/shared-utils/redaction");
|
|
3124
|
+
function buildErrorJsonPayload(error) {
|
|
3125
|
+
const payload = { message: error.message };
|
|
3126
|
+
if (error.code !== void 0) {
|
|
3127
|
+
payload.code = error.code;
|
|
3128
|
+
}
|
|
3129
|
+
if (error.stack !== void 0) {
|
|
3130
|
+
payload.stack = error.stack;
|
|
3131
|
+
}
|
|
3132
|
+
return payload;
|
|
3133
|
+
}
|
|
3134
|
+
function redactTraceEventReason(reason, mode, sensitiveFieldNames) {
|
|
3135
|
+
const stringPreservingMode = mode === "metadata-only" ? "redacted" : mode;
|
|
3136
|
+
const outcome = (0, import_redaction3.redactTracePayload)(reason, {
|
|
3137
|
+
mode: stringPreservingMode,
|
|
3138
|
+
sensitiveFieldNames
|
|
3139
|
+
});
|
|
3140
|
+
if (typeof outcome.payload === "string") {
|
|
3141
|
+
return {
|
|
3142
|
+
reason: outcome.payload,
|
|
3143
|
+
redacted: outcome.payload !== reason
|
|
3144
|
+
};
|
|
3145
|
+
}
|
|
3146
|
+
return { reason: import_redaction3.redactedValue, redacted: true };
|
|
3147
|
+
}
|
|
3148
|
+
function redactTraceEventError(error, mode, sensitiveFieldNames) {
|
|
3149
|
+
const outcome = (0, import_redaction3.redactTracePayload)(buildErrorJsonPayload(error), {
|
|
3150
|
+
mode,
|
|
3151
|
+
sensitiveFieldNames
|
|
3152
|
+
});
|
|
3153
|
+
const parsedError = import_trace_events3.TraceEventErrorSchema.safeParse(outcome.payload);
|
|
3154
|
+
if (parsedError.success) {
|
|
3155
|
+
return { error: parsedError.data, redacted: outcome.redacted };
|
|
3156
|
+
}
|
|
3157
|
+
return { error: { message: import_redaction3.redactedValue }, redacted: true };
|
|
3158
|
+
}
|
|
3159
|
+
function redactTraceEventPayloads(event, mode, sensitiveFieldNames) {
|
|
3160
|
+
const resolvedMode = (0, import_redaction3.resolveRedactionMode)(mode);
|
|
3161
|
+
const redactedEvent = { ...event };
|
|
3162
|
+
let payloadWasRedacted = false;
|
|
3163
|
+
if ("input" in redactedEvent) {
|
|
3164
|
+
const outcome = (0, import_redaction3.redactTracePayload)(redactedEvent.input, {
|
|
3165
|
+
mode: resolvedMode,
|
|
3166
|
+
sensitiveFieldNames
|
|
3167
|
+
});
|
|
3168
|
+
redactedEvent.input = outcome.payload;
|
|
3169
|
+
payloadWasRedacted = payloadWasRedacted || outcome.redacted;
|
|
3170
|
+
}
|
|
3171
|
+
if ("output" in redactedEvent) {
|
|
3172
|
+
const outcome = (0, import_redaction3.redactTracePayload)(redactedEvent.output, {
|
|
3173
|
+
mode: resolvedMode,
|
|
3174
|
+
sensitiveFieldNames
|
|
3175
|
+
});
|
|
3176
|
+
redactedEvent.output = outcome.payload;
|
|
3177
|
+
payloadWasRedacted = payloadWasRedacted || outcome.redacted;
|
|
3178
|
+
}
|
|
3179
|
+
if ("reason" in redactedEvent) {
|
|
3180
|
+
const outcome = redactTraceEventReason(
|
|
3181
|
+
redactedEvent.reason,
|
|
3182
|
+
resolvedMode,
|
|
3183
|
+
sensitiveFieldNames
|
|
3184
|
+
);
|
|
3185
|
+
redactedEvent.reason = outcome.reason;
|
|
3186
|
+
payloadWasRedacted = payloadWasRedacted || outcome.redacted;
|
|
3187
|
+
}
|
|
3188
|
+
if ("error" in redactedEvent) {
|
|
3189
|
+
const outcome = redactTraceEventError(
|
|
3190
|
+
redactedEvent.error,
|
|
3191
|
+
resolvedMode,
|
|
3192
|
+
sensitiveFieldNames
|
|
3193
|
+
);
|
|
3194
|
+
redactedEvent.error = outcome.error;
|
|
3195
|
+
payloadWasRedacted = payloadWasRedacted || outcome.redacted;
|
|
3196
|
+
}
|
|
3197
|
+
return import_trace_events3.TraceEventSchema.parse({
|
|
3198
|
+
...redactedEvent,
|
|
3199
|
+
redacted: redactedEvent.redacted || payloadWasRedacted || resolvedMode !== "full"
|
|
3200
|
+
});
|
|
3201
|
+
}
|
|
3202
|
+
|
|
3203
|
+
// src/platform/platform-trace-sink.ts
|
|
3204
|
+
var defaultTraceBatchSize = 20;
|
|
3205
|
+
var defaultTraceQueueSize = 1e3;
|
|
3206
|
+
function resolveTraceIngestUrl(baseUrl) {
|
|
3207
|
+
return `${baseUrl}/v1/ingest/trace-events`;
|
|
3208
|
+
}
|
|
3209
|
+
function buildPlatformSinkContext(options) {
|
|
3210
|
+
return {
|
|
3211
|
+
ingestUrl: resolveTraceIngestUrl(options.baseUrl ?? defaultPlatformBaseUrl),
|
|
3212
|
+
apiKey: options.apiKey,
|
|
3213
|
+
runId: options.runId,
|
|
3214
|
+
workflowName: options.workflowName,
|
|
3215
|
+
redactionMode: (0, import_redaction4.resolveRedactionMode)(options.redactionMode),
|
|
3216
|
+
sensitiveFieldNames: options.sensitiveFieldNames,
|
|
3217
|
+
batchSize: options.batchSize ?? defaultTraceBatchSize,
|
|
3218
|
+
maxQueueSize: options.maxQueueSize ?? defaultTraceQueueSize,
|
|
3219
|
+
fetchImplementation: options.fetchImplementation ?? fetch,
|
|
3220
|
+
onUploadError: options.onUploadError,
|
|
3221
|
+
queuedEvents: [],
|
|
3222
|
+
uploadStats: {
|
|
3223
|
+
uploadedEventCount: 0,
|
|
3224
|
+
droppedEventCount: 0,
|
|
3225
|
+
failedBatchCount: 0
|
|
3226
|
+
},
|
|
3227
|
+
uploadPromiseChain: Promise.resolve(),
|
|
3228
|
+
isClosed: false
|
|
3229
|
+
};
|
|
3230
|
+
}
|
|
3231
|
+
function reportUploadError(context, uploadError) {
|
|
3232
|
+
if (context.onUploadError !== void 0) {
|
|
3233
|
+
context.onUploadError(uploadError);
|
|
3234
|
+
}
|
|
3235
|
+
}
|
|
3236
|
+
function buildTraceEventBatchRequest(context, eventsToUpload) {
|
|
3237
|
+
return {
|
|
3238
|
+
method: "POST",
|
|
3239
|
+
headers: {
|
|
3240
|
+
authorization: `Bearer ${context.apiKey}`,
|
|
3241
|
+
"content-type": "application/json"
|
|
3242
|
+
},
|
|
3243
|
+
body: JSON.stringify({
|
|
3244
|
+
runId: context.runId,
|
|
3245
|
+
workflowName: context.workflowName,
|
|
3246
|
+
events: eventsToUpload
|
|
3247
|
+
})
|
|
3248
|
+
};
|
|
3249
|
+
}
|
|
3250
|
+
async function attemptTraceEventBatchUpload(context, eventsToUpload) {
|
|
3251
|
+
try {
|
|
3252
|
+
const response = await context.fetchImplementation(
|
|
3253
|
+
context.ingestUrl,
|
|
3254
|
+
buildTraceEventBatchRequest(context, eventsToUpload)
|
|
3255
|
+
);
|
|
3256
|
+
if (response.ok) {
|
|
3257
|
+
return true;
|
|
3258
|
+
}
|
|
3259
|
+
reportUploadError(
|
|
3260
|
+
context,
|
|
3261
|
+
new Error(`trace event upload failed with status ${response.status}`)
|
|
3262
|
+
);
|
|
3263
|
+
return false;
|
|
3264
|
+
} catch (caughtError) {
|
|
3265
|
+
reportUploadError(
|
|
3266
|
+
context,
|
|
3267
|
+
caughtError instanceof Error ? caughtError : new Error(String(caughtError))
|
|
3268
|
+
);
|
|
3269
|
+
return false;
|
|
3270
|
+
}
|
|
3271
|
+
}
|
|
3272
|
+
async function uploadTraceEventBatchWithSingleRetry(context, eventsToUpload) {
|
|
3273
|
+
const uploadedOnFirstAttempt = await attemptTraceEventBatchUpload(
|
|
3274
|
+
context,
|
|
3275
|
+
eventsToUpload
|
|
3276
|
+
);
|
|
3277
|
+
const uploaded = uploadedOnFirstAttempt || await attemptTraceEventBatchUpload(context, eventsToUpload);
|
|
3278
|
+
if (uploaded) {
|
|
3279
|
+
context.uploadStats.uploadedEventCount += eventsToUpload.length;
|
|
3280
|
+
return;
|
|
3281
|
+
}
|
|
3282
|
+
context.uploadStats.failedBatchCount += 1;
|
|
3283
|
+
context.uploadStats.droppedEventCount += eventsToUpload.length;
|
|
3284
|
+
}
|
|
3285
|
+
function enqueueTraceEventBatchUpload(context, eventsToUpload) {
|
|
3286
|
+
context.uploadPromiseChain = context.uploadPromiseChain.then(async () => {
|
|
3287
|
+
await uploadTraceEventBatchWithSingleRetry(context, eventsToUpload);
|
|
3288
|
+
});
|
|
3289
|
+
}
|
|
3290
|
+
function dropOldestQueuedEventsBeyondLimit(context) {
|
|
3291
|
+
while (context.queuedEvents.length > context.maxQueueSize) {
|
|
3292
|
+
context.queuedEvents.shift();
|
|
3293
|
+
context.uploadStats.droppedEventCount += 1;
|
|
3294
|
+
}
|
|
3295
|
+
}
|
|
3296
|
+
function scheduleFullBatchUploads(context) {
|
|
3297
|
+
while (context.queuedEvents.length >= context.batchSize) {
|
|
3298
|
+
enqueueTraceEventBatchUpload(
|
|
3299
|
+
context,
|
|
3300
|
+
context.queuedEvents.splice(0, context.batchSize)
|
|
3301
|
+
);
|
|
3302
|
+
}
|
|
3303
|
+
}
|
|
3304
|
+
function writeTraceEventToPlatformQueue(context, event) {
|
|
3305
|
+
if (context.isClosed) {
|
|
3306
|
+
return;
|
|
3307
|
+
}
|
|
3308
|
+
try {
|
|
3309
|
+
context.queuedEvents.push(
|
|
3310
|
+
redactTraceEventPayloads(
|
|
3311
|
+
event,
|
|
3312
|
+
context.redactionMode,
|
|
3313
|
+
context.sensitiveFieldNames
|
|
3314
|
+
)
|
|
3315
|
+
);
|
|
3316
|
+
} catch (caughtError) {
|
|
3317
|
+
context.uploadStats.droppedEventCount += 1;
|
|
3318
|
+
reportUploadError(
|
|
3319
|
+
context,
|
|
3320
|
+
caughtError instanceof Error ? caughtError : new Error(String(caughtError))
|
|
3321
|
+
);
|
|
3322
|
+
return;
|
|
3323
|
+
}
|
|
3324
|
+
dropOldestQueuedEventsBeyondLimit(context);
|
|
3325
|
+
scheduleFullBatchUploads(context);
|
|
3326
|
+
}
|
|
3327
|
+
async function flushQueuedTraceEvents(context) {
|
|
3328
|
+
if (context.queuedEvents.length > 0) {
|
|
3329
|
+
enqueueTraceEventBatchUpload(
|
|
3330
|
+
context,
|
|
3331
|
+
context.queuedEvents.splice(0, context.queuedEvents.length)
|
|
3332
|
+
);
|
|
3333
|
+
}
|
|
3334
|
+
await context.uploadPromiseChain;
|
|
3335
|
+
}
|
|
3336
|
+
function createPlatformTraceSink(options) {
|
|
3337
|
+
const context = buildPlatformSinkContext(options);
|
|
3338
|
+
return {
|
|
3339
|
+
name: "platform",
|
|
3340
|
+
writeTraceEvent: (event) => {
|
|
3341
|
+
writeTraceEventToPlatformQueue(context, event);
|
|
3342
|
+
},
|
|
3343
|
+
flush: async () => {
|
|
3344
|
+
await flushQueuedTraceEvents(context);
|
|
3345
|
+
},
|
|
3346
|
+
close: () => {
|
|
3347
|
+
context.isClosed = true;
|
|
3348
|
+
},
|
|
3349
|
+
getUploadStats: () => ({ ...context.uploadStats })
|
|
3350
|
+
};
|
|
3351
|
+
}
|
|
3352
|
+
|
|
3353
|
+
// src/platform/platform-messages.ts
|
|
3354
|
+
var platformKeySignupUrl = "https://app.assemble.dev/app/keys";
|
|
3355
|
+
function buildMissingKeyMessage() {
|
|
3356
|
+
return `Get a free developer key to see this trace hosted \u2192 ${platformKeySignupUrl}`;
|
|
3357
|
+
}
|
|
3358
|
+
function buildHostedTraceUrl(input) {
|
|
3359
|
+
return `${input.dashboardUrl}/app/runs/${input.runId}`;
|
|
3360
|
+
}
|
|
3361
|
+
|
|
3362
|
+
// src/replay/replay-engine.ts
|
|
3363
|
+
var import_ids4 = require("@assemble-dev/shared-utils/ids");
|
|
3364
|
+
|
|
3365
|
+
// src/replay/replay-plan.ts
|
|
3366
|
+
var import_errors29 = require("@assemble-dev/shared-types/errors");
|
|
3367
|
+
var import_errors30 = require("@assemble-dev/shared-utils/errors");
|
|
3368
|
+
|
|
3369
|
+
// src/replay/replay-mocks.ts
|
|
3370
|
+
var import_providers = require("@assemble-dev/providers");
|
|
3371
|
+
var import_errors27 = require("@assemble-dev/shared-types/errors");
|
|
3372
|
+
var import_errors28 = require("@assemble-dev/shared-utils/errors");
|
|
3373
|
+
function buildReplayToolMocks(events) {
|
|
3374
|
+
const queuesByToolName = /* @__PURE__ */ new Map();
|
|
3375
|
+
for (const event of events) {
|
|
3376
|
+
if (event.type !== "tool.completed") {
|
|
3377
|
+
continue;
|
|
3378
|
+
}
|
|
3379
|
+
const queue = queuesByToolName.get(event.toolName) ?? {
|
|
3380
|
+
remaining: [],
|
|
3381
|
+
recordedCount: 0
|
|
3382
|
+
};
|
|
3383
|
+
queue.remaining.push(event.output);
|
|
3384
|
+
queue.recordedCount += 1;
|
|
3385
|
+
queuesByToolName.set(event.toolName, queue);
|
|
3386
|
+
}
|
|
3387
|
+
return {
|
|
3388
|
+
takeNextRecordedToolOutput(toolName) {
|
|
3389
|
+
const queue = queuesByToolName.get(toolName);
|
|
3390
|
+
const nextOutput = queue?.remaining.shift();
|
|
3391
|
+
if (nextOutput === void 0) {
|
|
3392
|
+
throw buildReplayTraceExhaustionError(
|
|
3393
|
+
"tool",
|
|
3394
|
+
toolName,
|
|
3395
|
+
"tool.completed",
|
|
3396
|
+
queue?.recordedCount ?? 0
|
|
3397
|
+
);
|
|
3398
|
+
}
|
|
3399
|
+
return nextOutput;
|
|
3400
|
+
}
|
|
3401
|
+
};
|
|
3402
|
+
}
|
|
3403
|
+
function buildReplayAgentModels(events) {
|
|
3404
|
+
const scriptedResponsesByAgentName = /* @__PURE__ */ new Map();
|
|
3405
|
+
for (const event of events) {
|
|
3406
|
+
if (event.type !== "agent.completed") {
|
|
3407
|
+
continue;
|
|
3408
|
+
}
|
|
3409
|
+
const scriptedResponses = scriptedResponsesByAgentName.get(event.agentName) ?? [];
|
|
3410
|
+
scriptedResponses.push(serializeRecordedAgentOutput(event.output));
|
|
3411
|
+
scriptedResponsesByAgentName.set(event.agentName, scriptedResponses);
|
|
3412
|
+
}
|
|
3413
|
+
const adaptersByAgentName = /* @__PURE__ */ new Map();
|
|
3414
|
+
return {
|
|
3415
|
+
resolveReplayModelForAgent(agentName) {
|
|
3416
|
+
const existingAdapter = adaptersByAgentName.get(agentName);
|
|
3417
|
+
if (existingAdapter !== void 0) {
|
|
3418
|
+
return existingAdapter;
|
|
3419
|
+
}
|
|
3420
|
+
const adapter = buildScriptedReplayModel(
|
|
3421
|
+
agentName,
|
|
3422
|
+
scriptedResponsesByAgentName.get(agentName) ?? []
|
|
3423
|
+
);
|
|
3424
|
+
adaptersByAgentName.set(agentName, adapter);
|
|
3425
|
+
return adapter;
|
|
3426
|
+
}
|
|
3427
|
+
};
|
|
3428
|
+
}
|
|
3429
|
+
function buildScriptedReplayModel(agentName, scriptedResponses) {
|
|
3430
|
+
const recordedCount = scriptedResponses.length;
|
|
3431
|
+
return (0, import_providers.mockModel)({
|
|
3432
|
+
name: `replay:${agentName}`,
|
|
3433
|
+
scriptedResponses: [...scriptedResponses],
|
|
3434
|
+
respond: () => {
|
|
3435
|
+
throw buildReplayTraceExhaustionError(
|
|
3436
|
+
"agent",
|
|
3437
|
+
agentName,
|
|
3438
|
+
"agent.completed",
|
|
3439
|
+
recordedCount
|
|
3440
|
+
);
|
|
3441
|
+
}
|
|
3442
|
+
});
|
|
3443
|
+
}
|
|
3444
|
+
function serializeRecordedAgentOutput(output) {
|
|
3445
|
+
return typeof output === "string" ? output : JSON.stringify(output);
|
|
3446
|
+
}
|
|
3447
|
+
function buildReplayTraceExhaustionError(kind, name, eventType, recordedCount) {
|
|
3448
|
+
return new import_errors28.AppError({
|
|
3449
|
+
code: import_errors27.AppErrorCode.Conflict,
|
|
3450
|
+
message: `replay requested recorded output ${recordedCount + 1} for ${kind} "${name}" but the source trace only holds ${recordedCount} "${eventType}" event(s) for it; the replay diverged from the source run, and a replay may only consume outputs the source run recorded. Record a new source run that covers this call, replay from an earlier step, or disable mocking for this ${kind}`,
|
|
3451
|
+
statusCode: 409,
|
|
3452
|
+
details: { kind, name, recordedCount }
|
|
3453
|
+
});
|
|
3454
|
+
}
|
|
3455
|
+
|
|
3456
|
+
// src/replay/replay-plan.ts
|
|
3457
|
+
function buildReplayStartPlan(definition, sourceRunId, source, fromStepIndex) {
|
|
3458
|
+
if (fromStepIndex === 0) {
|
|
3459
|
+
return {
|
|
3460
|
+
startInput: validateReplayStartInput(definition, sourceRunId, source.state),
|
|
3461
|
+
lastAgentName: null,
|
|
3462
|
+
fromEventId: null,
|
|
3463
|
+
replayEvents: source.events,
|
|
3464
|
+
seedSteps: []
|
|
3465
|
+
};
|
|
3466
|
+
}
|
|
3467
|
+
const startedEvents = source.events.filter(
|
|
3468
|
+
(event) => event.type === "agent.started"
|
|
3469
|
+
);
|
|
3470
|
+
const completedEvents = source.events.filter(
|
|
3471
|
+
(event) => event.type === "agent.completed"
|
|
3472
|
+
);
|
|
3473
|
+
const fromStepEvent = startedEvents[fromStepIndex];
|
|
3474
|
+
const previousStepEvent = completedEvents[fromStepIndex - 1];
|
|
3475
|
+
if (fromStepEvent === void 0 || previousStepEvent === void 0) {
|
|
3476
|
+
throw buildFromStepOutOfRangeError(
|
|
3477
|
+
sourceRunId,
|
|
3478
|
+
fromStepIndex,
|
|
3479
|
+
startedEvents.length
|
|
3480
|
+
);
|
|
3481
|
+
}
|
|
3482
|
+
return {
|
|
3483
|
+
startInput: previousStepEvent.output,
|
|
3484
|
+
lastAgentName: previousStepEvent.agentName,
|
|
3485
|
+
fromEventId: fromStepEvent.id,
|
|
3486
|
+
replayEvents: source.events.slice(source.events.indexOf(fromStepEvent)),
|
|
3487
|
+
seedSteps: buildReplaySeedSteps(
|
|
3488
|
+
sourceRunId,
|
|
3489
|
+
startedEvents,
|
|
3490
|
+
completedEvents,
|
|
3491
|
+
fromStepIndex
|
|
3492
|
+
)
|
|
3493
|
+
};
|
|
3494
|
+
}
|
|
3495
|
+
function buildReplaySeedSteps(sourceRunId, startedEvents, completedEvents, fromStepIndex) {
|
|
3496
|
+
const seedSteps = [];
|
|
3497
|
+
for (let stepIndex = 0; stepIndex < fromStepIndex; stepIndex += 1) {
|
|
3498
|
+
const startedEvent = startedEvents[stepIndex];
|
|
3499
|
+
const completedEvent = completedEvents[stepIndex];
|
|
3500
|
+
if (startedEvent === void 0 || completedEvent === void 0) {
|
|
3501
|
+
throw buildFromStepOutOfRangeError(
|
|
3502
|
+
sourceRunId,
|
|
3503
|
+
fromStepIndex,
|
|
3504
|
+
startedEvents.length
|
|
3505
|
+
);
|
|
3506
|
+
}
|
|
3507
|
+
seedSteps.push({
|
|
3508
|
+
agentName: completedEvent.agentName,
|
|
3509
|
+
input: startedEvent.input,
|
|
3510
|
+
output: completedEvent.output
|
|
3511
|
+
});
|
|
3512
|
+
}
|
|
3513
|
+
return seedSteps;
|
|
3514
|
+
}
|
|
3515
|
+
function buildFromStepOutOfRangeError(sourceRunId, fromStepIndex, recordedStepCount) {
|
|
3516
|
+
return new import_errors30.AppError({
|
|
3517
|
+
code: import_errors29.AppErrorCode.BadRequest,
|
|
3518
|
+
message: `fromStepIndex ${fromStepIndex} is out of range for source run "${sourceRunId}"; the source trace recorded ${recordedStepCount} completed agent step(s), so fromStepIndex must be between 0 and ${Math.max(0, recordedStepCount - 1)}`,
|
|
3519
|
+
statusCode: 400,
|
|
3520
|
+
details: { sourceRunId, fromStepIndex, recordedStepCount }
|
|
3521
|
+
});
|
|
3522
|
+
}
|
|
3523
|
+
function validateReplayStartInput(definition, sourceRunId, sourceState) {
|
|
3524
|
+
const parsedInput = definition.inputSchema.safeParse(sourceState.input);
|
|
3525
|
+
if (parsedInput.success) {
|
|
3526
|
+
return parsedInput.data;
|
|
3527
|
+
}
|
|
3528
|
+
throw new import_errors30.AppError({
|
|
3529
|
+
code: import_errors29.AppErrorCode.ValidationFailed,
|
|
3530
|
+
message: `source run "${sourceRunId}" input failed workflow "${definition.name}" input schema validation: ${parsedInput.error.message}`,
|
|
3531
|
+
statusCode: 422,
|
|
3532
|
+
details: { sourceRunId, workflowName: definition.name }
|
|
3533
|
+
});
|
|
3534
|
+
}
|
|
3535
|
+
function buildMockedReplayDefinition(definition, replayEvents, mockTools) {
|
|
3536
|
+
const agentModels = buildReplayAgentModels(replayEvents);
|
|
3537
|
+
const toolMocks = buildReplayToolMocks(replayEvents);
|
|
3538
|
+
return workflow({
|
|
3539
|
+
name: definition.name,
|
|
3540
|
+
inputSchema: definition.inputSchema,
|
|
3541
|
+
outputSchema: definition.outputSchema ?? void 0,
|
|
3542
|
+
agents: definition.agents.map((agentDefinition) => ({
|
|
3543
|
+
...agentDefinition,
|
|
3544
|
+
model: agentModels.resolveReplayModelForAgent(agentDefinition.name)
|
|
3545
|
+
})),
|
|
3546
|
+
tools: definition.tools.map(
|
|
3547
|
+
(toolDefinition) => mockTools ? {
|
|
3548
|
+
...toolDefinition,
|
|
3549
|
+
execute: async () => await Promise.resolve(
|
|
3550
|
+
toolMocks.takeNextRecordedToolOutput(toolDefinition.name)
|
|
3551
|
+
)
|
|
3552
|
+
} : toolDefinition
|
|
3553
|
+
),
|
|
3554
|
+
agentToolBindings: [...definition.agentToolBindings],
|
|
3555
|
+
topology: definition.topology,
|
|
3556
|
+
policies: [...definition.policies],
|
|
3557
|
+
memory: definition.memory ?? void 0,
|
|
3558
|
+
stopCondition: definition.stopCondition ?? void 0,
|
|
3559
|
+
continueOnAgentFailure: definition.continueOnAgentFailure
|
|
3560
|
+
});
|
|
3561
|
+
}
|
|
3562
|
+
|
|
3563
|
+
// src/replay/replay-source.ts
|
|
3564
|
+
var import_node_fs6 = require("fs");
|
|
3565
|
+
var import_errors31 = require("@assemble-dev/shared-types/errors");
|
|
3566
|
+
var import_trace_events4 = require("@assemble-dev/shared-types/trace-events");
|
|
3567
|
+
var import_errors32 = require("@assemble-dev/shared-utils/errors");
|
|
3568
|
+
function loadReplaySource(input) {
|
|
3569
|
+
const runStateDirectory = input.runStateDirectory ?? defaultRunStateDirectory;
|
|
3570
|
+
const traceDirectory = input.traceDirectory ?? defaultTraceDirectory;
|
|
3571
|
+
return {
|
|
3572
|
+
state: loadSourceRunState(input.runId, runStateDirectory),
|
|
3573
|
+
events: loadSourceTraceEvents(input.runId, traceDirectory)
|
|
3574
|
+
};
|
|
3575
|
+
}
|
|
3576
|
+
function loadSourceRunState(runId, directory) {
|
|
3577
|
+
const filePath = resolveRunStateFilePath(runId, directory);
|
|
3578
|
+
if (!(0, import_node_fs6.existsSync)(filePath)) {
|
|
3579
|
+
throw new import_errors32.AppError({
|
|
3580
|
+
code: import_errors31.AppErrorCode.NotFound,
|
|
3581
|
+
message: `run state file not found at "${filePath}" for run "${runId}"; the run was never persisted or the runStateDirectory is wrong`,
|
|
3582
|
+
statusCode: 404,
|
|
3583
|
+
details: { runId, filePath }
|
|
3584
|
+
});
|
|
3585
|
+
}
|
|
3586
|
+
return loadRunStateFile({ runId, directory });
|
|
3587
|
+
}
|
|
3588
|
+
function loadSourceTraceEvents(runId, directory) {
|
|
3589
|
+
const filePath = resolveTraceFilePath(runId, directory);
|
|
3590
|
+
if (!(0, import_node_fs6.existsSync)(filePath)) {
|
|
3591
|
+
throw new import_errors32.AppError({
|
|
3592
|
+
code: import_errors31.AppErrorCode.NotFound,
|
|
3593
|
+
message: `trace file not found at "${filePath}" for run "${runId}"; run the workflow with a jsonl sink or fix the traceDirectory`,
|
|
3594
|
+
statusCode: 404,
|
|
3595
|
+
details: { runId, filePath }
|
|
3596
|
+
});
|
|
3597
|
+
}
|
|
3598
|
+
const lines = (0, import_node_fs6.readFileSync)(filePath, "utf8").split("\n");
|
|
3599
|
+
const events = [];
|
|
3600
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
3601
|
+
const line = lines[lineIndex];
|
|
3602
|
+
if (line === void 0 || line.trim().length === 0) {
|
|
3603
|
+
continue;
|
|
3604
|
+
}
|
|
3605
|
+
events.push(parseTraceEventLine(line, lineIndex + 1, filePath));
|
|
3606
|
+
}
|
|
3607
|
+
return events;
|
|
3608
|
+
}
|
|
3609
|
+
function parseTraceEventLine(line, lineNumber, filePath) {
|
|
3610
|
+
try {
|
|
3611
|
+
return import_trace_events4.TraceEventSchema.parse(JSON.parse(line));
|
|
3612
|
+
} catch (thrownError) {
|
|
3613
|
+
const reason = thrownError instanceof Error ? thrownError.message : String(thrownError);
|
|
3614
|
+
throw new import_errors32.AppError({
|
|
3615
|
+
code: import_errors31.AppErrorCode.ValidationFailed,
|
|
3616
|
+
message: `trace file "${filePath}" has an invalid trace event on line ${lineNumber}: ${reason}`,
|
|
3617
|
+
statusCode: 422,
|
|
3618
|
+
details: { filePath, lineNumber }
|
|
3619
|
+
});
|
|
3620
|
+
}
|
|
3621
|
+
}
|
|
3622
|
+
|
|
3623
|
+
// src/replay/replay-engine.ts
|
|
3624
|
+
async function replayRun(replayInput) {
|
|
3625
|
+
const settings = resolveReplaySettings(replayInput.options);
|
|
3626
|
+
const source = loadReplaySource({
|
|
3627
|
+
runId: replayInput.sourceRunId,
|
|
3628
|
+
runStateDirectory: settings.runStateDirectory,
|
|
3629
|
+
traceDirectory: settings.traceDirectory
|
|
3630
|
+
});
|
|
3631
|
+
const plan = buildReplayStartPlan(
|
|
3632
|
+
replayInput.definition,
|
|
3633
|
+
replayInput.sourceRunId,
|
|
3634
|
+
source,
|
|
3635
|
+
settings.fromStepIndex
|
|
3636
|
+
);
|
|
3637
|
+
const replayDefinition = buildMockedReplayDefinition(
|
|
3638
|
+
replayInput.definition,
|
|
3639
|
+
plan.replayEvents,
|
|
3640
|
+
settings.mockTools
|
|
3641
|
+
);
|
|
3642
|
+
const context = createReplayRuntimeContext(
|
|
3643
|
+
replayDefinition,
|
|
3644
|
+
source,
|
|
3645
|
+
settings,
|
|
3646
|
+
replayInput.sourceRunId,
|
|
3647
|
+
plan.fromEventId
|
|
3648
|
+
);
|
|
3649
|
+
await persistWorkflowRunState(context);
|
|
3650
|
+
await seedReplayContextFromSource(context, source, plan, settings.fromStepIndex);
|
|
3651
|
+
const runResult = await continueWorkflowRunFromStep(context, {
|
|
3652
|
+
input: plan.startInput,
|
|
3653
|
+
startStepIndex: settings.fromStepIndex,
|
|
3654
|
+
lastAgentName: plan.lastAgentName
|
|
3655
|
+
});
|
|
3656
|
+
return await finishReplayRun(context, replayInput.sourceRunId, runResult);
|
|
3657
|
+
}
|
|
3658
|
+
function resolveReplaySettings(options) {
|
|
3659
|
+
return {
|
|
3660
|
+
fromStepIndex: options?.fromStepIndex ?? 0,
|
|
3661
|
+
mockTools: options?.mockTools ?? true,
|
|
3662
|
+
sinks: options?.sinks ?? [],
|
|
3663
|
+
runStateDirectory: options?.runStateDirectory ?? defaultRunStateDirectory,
|
|
3664
|
+
traceDirectory: options?.traceDirectory ?? defaultTraceDirectory,
|
|
3665
|
+
now: options?.now ?? (() => /* @__PURE__ */ new Date()),
|
|
3666
|
+
replayRunId: options?.replayRunId ?? null
|
|
3667
|
+
};
|
|
3668
|
+
}
|
|
3669
|
+
function createReplayRuntimeContext(replayDefinition, source, settings, sourceRunId, fromEventId) {
|
|
3670
|
+
const runId = settings.replayRunId ?? (0, import_ids4.generateRunId)();
|
|
3671
|
+
const traceBus = createTraceBus({
|
|
3672
|
+
runId,
|
|
3673
|
+
workflowName: replayDefinition.name,
|
|
3674
|
+
input: source.state.input,
|
|
3675
|
+
sinks: settings.sinks,
|
|
3676
|
+
now: settings.now
|
|
3677
|
+
});
|
|
3678
|
+
emitReplayStartedEvent(traceBus, runId, replayDefinition.name, settings.now, {
|
|
3679
|
+
sourceRunId,
|
|
3680
|
+
fromEventId
|
|
3681
|
+
});
|
|
3682
|
+
const runStartedEvent = emitRunStartedEvent(
|
|
3683
|
+
{ traceBus, runId, workflowName: replayDefinition.name, now: settings.now },
|
|
3684
|
+
source.state.input
|
|
3685
|
+
);
|
|
3686
|
+
const memoryConfig = resolveWorkflowMemoryConfig(
|
|
3687
|
+
replayDefinition.memory ?? void 0
|
|
3688
|
+
);
|
|
3689
|
+
return {
|
|
3690
|
+
definition: replayDefinition,
|
|
3691
|
+
traceBus,
|
|
3692
|
+
runId,
|
|
3693
|
+
runStartedEventId: runStartedEvent.id,
|
|
3694
|
+
threadMemory: createThreadMemory({
|
|
3695
|
+
provider: memoryConfig.provider,
|
|
3696
|
+
threadKey: runId
|
|
3697
|
+
}),
|
|
3698
|
+
memoryProvider: memoryConfig.provider,
|
|
3699
|
+
artifactStore: createArtifactStore({
|
|
3700
|
+
provider: memoryConfig.provider,
|
|
3701
|
+
runId
|
|
3702
|
+
}),
|
|
3703
|
+
now: settings.now,
|
|
3704
|
+
startedAtMs: settings.now().getTime(),
|
|
3705
|
+
persistRunState: true,
|
|
3706
|
+
runStateDirectory: settings.runStateDirectory
|
|
3707
|
+
};
|
|
3708
|
+
}
|
|
3709
|
+
function emitReplayStartedEvent(traceBus, runId, workflowName, now, replayLink) {
|
|
3710
|
+
traceBus.emitTraceEvent({
|
|
3711
|
+
id: (0, import_ids4.generateTraceEventId)(),
|
|
3712
|
+
runId,
|
|
3713
|
+
workflowName,
|
|
3714
|
+
timestamp: now().toISOString(),
|
|
3715
|
+
parentEventId: null,
|
|
3716
|
+
redacted: false,
|
|
3717
|
+
type: "replay.started",
|
|
3718
|
+
sourceRunId: replayLink.sourceRunId,
|
|
3719
|
+
fromEventId: replayLink.fromEventId
|
|
3720
|
+
});
|
|
3721
|
+
}
|
|
3722
|
+
async function seedReplayContextFromSource(context, source, plan, fromStepIndex) {
|
|
3723
|
+
if (fromStepIndex === 0) {
|
|
3724
|
+
return;
|
|
3725
|
+
}
|
|
3726
|
+
for (const artifact of source.state.artifacts) {
|
|
3727
|
+
const artifactStep = parseArtifactStepIndex(artifact.key);
|
|
3728
|
+
if (artifactStep !== null && artifactStep < fromStepIndex) {
|
|
3729
|
+
await context.artifactStore.saveRunArtifact(artifact);
|
|
3730
|
+
}
|
|
3731
|
+
}
|
|
3732
|
+
for (const seedStep of plan.seedSteps) {
|
|
3733
|
+
await appendAgentExchangeToThreadMemory(
|
|
3734
|
+
context.threadMemory,
|
|
3735
|
+
seedStep.agentName,
|
|
3736
|
+
seedStep.input,
|
|
3737
|
+
serializeAgentInputToText(seedStep.output),
|
|
3738
|
+
context.now
|
|
3739
|
+
);
|
|
3740
|
+
}
|
|
3741
|
+
}
|
|
3742
|
+
function parseArtifactStepIndex(artifactKey) {
|
|
3743
|
+
const match = /^step-(\d+):/.exec(artifactKey);
|
|
3744
|
+
const capturedStepIndex = match?.[1];
|
|
3745
|
+
if (capturedStepIndex === void 0) {
|
|
3746
|
+
return null;
|
|
3747
|
+
}
|
|
3748
|
+
return Number(capturedStepIndex);
|
|
3749
|
+
}
|
|
3750
|
+
async function finishReplayRun(context, sourceRunId, runResult) {
|
|
3751
|
+
context.traceBus.emitTraceEvent({
|
|
3752
|
+
id: (0, import_ids4.generateTraceEventId)(),
|
|
3753
|
+
runId: context.runId,
|
|
3754
|
+
workflowName: context.definition.name,
|
|
3755
|
+
timestamp: context.now().toISOString(),
|
|
3756
|
+
parentEventId: context.runStartedEventId,
|
|
3757
|
+
redacted: false,
|
|
3758
|
+
type: "replay.completed",
|
|
3759
|
+
sourceRunId,
|
|
3760
|
+
latencyMs: calculateWorkflowElapsedMs(context)
|
|
3761
|
+
});
|
|
3762
|
+
await context.traceBus.flushSinks();
|
|
3763
|
+
return {
|
|
3764
|
+
status: runResult.status,
|
|
3765
|
+
runId: context.runId,
|
|
3766
|
+
sourceRunId,
|
|
3767
|
+
output: runResult.status === "completed" ? runResult.output : null,
|
|
3768
|
+
approvalId: runResult.status === "awaiting_approval" ? runResult.approvalId : null,
|
|
3769
|
+
state: await buildWorkflowRunState(context),
|
|
3770
|
+
events: context.traceBus.listTraceEvents()
|
|
3771
|
+
};
|
|
3772
|
+
}
|
|
3773
|
+
|
|
3774
|
+
// src/replay/replay-comparison.ts
|
|
3775
|
+
var maxReplayOutputDiffCount = 50;
|
|
3776
|
+
function compareRunToReplay(input) {
|
|
3777
|
+
const outputDiff = collectJsonLeafDiffs(
|
|
3778
|
+
"$",
|
|
3779
|
+
input.original.finalOutput,
|
|
3780
|
+
input.replay.finalOutput,
|
|
3781
|
+
[]
|
|
3782
|
+
);
|
|
3783
|
+
return {
|
|
3784
|
+
statusMatches: input.original.status === input.replay.status,
|
|
3785
|
+
originalStatus: input.original.status,
|
|
3786
|
+
replayStatus: input.replay.status,
|
|
3787
|
+
outputMatches: outputDiff.length === 0,
|
|
3788
|
+
outputDiff,
|
|
3789
|
+
latency: compareRunLatency(input.original, input.replay),
|
|
3790
|
+
tokenUsage: {
|
|
3791
|
+
original: input.original.metrics.tokenUsage,
|
|
3792
|
+
replay: input.replay.metrics.tokenUsage
|
|
3793
|
+
},
|
|
3794
|
+
routingMatches: compareRoutingSequences(input.original, input.replay)
|
|
3795
|
+
};
|
|
3796
|
+
}
|
|
3797
|
+
function compareRunLatency(original, replay) {
|
|
3798
|
+
const originalMs = original.metrics.totalLatencyMs;
|
|
3799
|
+
const replayMs = replay.metrics.totalLatencyMs;
|
|
3800
|
+
return {
|
|
3801
|
+
originalMs,
|
|
3802
|
+
replayMs,
|
|
3803
|
+
deltaMs: originalMs !== null && replayMs !== null ? replayMs - originalMs : null
|
|
3804
|
+
};
|
|
3805
|
+
}
|
|
3806
|
+
function compareRoutingSequences(original, replay) {
|
|
3807
|
+
const originalSequence = original.routingDecisions.map(
|
|
3808
|
+
(decision) => decision.selectedAgentName
|
|
3809
|
+
);
|
|
3810
|
+
const replaySequence = replay.routingDecisions.map(
|
|
3811
|
+
(decision) => decision.selectedAgentName
|
|
3812
|
+
);
|
|
3813
|
+
if (originalSequence.length !== replaySequence.length) {
|
|
3814
|
+
return false;
|
|
3815
|
+
}
|
|
3816
|
+
return originalSequence.every(
|
|
3817
|
+
(selectedAgentName, index) => selectedAgentName === replaySequence[index]
|
|
3818
|
+
);
|
|
3819
|
+
}
|
|
3820
|
+
function collectJsonLeafDiffs(path, original, replay, diffs) {
|
|
3821
|
+
if (diffs.length >= maxReplayOutputDiffCount) {
|
|
3822
|
+
return diffs;
|
|
3823
|
+
}
|
|
3824
|
+
if (isJsonObject(original) && isJsonObject(replay)) {
|
|
3825
|
+
return collectJsonObjectDiffs(path, original, replay, diffs);
|
|
3826
|
+
}
|
|
3827
|
+
if (Array.isArray(original) && Array.isArray(replay)) {
|
|
3828
|
+
return collectJsonArrayDiffs(path, original, replay, diffs);
|
|
3829
|
+
}
|
|
3830
|
+
if (!jsonValuesEqual(original, replay)) {
|
|
3831
|
+
diffs.push({ path, original, replay });
|
|
3832
|
+
}
|
|
3833
|
+
return diffs;
|
|
3834
|
+
}
|
|
3835
|
+
function collectJsonObjectDiffs(path, original, replay, diffs) {
|
|
3836
|
+
const keys = [
|
|
3837
|
+
.../* @__PURE__ */ new Set([...Object.keys(original), ...Object.keys(replay)])
|
|
3838
|
+
].sort();
|
|
3839
|
+
for (const key of keys) {
|
|
3840
|
+
collectJsonLeafDiffs(
|
|
3841
|
+
`${path}.${key}`,
|
|
3842
|
+
original[key] ?? null,
|
|
3843
|
+
replay[key] ?? null,
|
|
3844
|
+
diffs
|
|
3845
|
+
);
|
|
3846
|
+
}
|
|
3847
|
+
return diffs;
|
|
3848
|
+
}
|
|
3849
|
+
function collectJsonArrayDiffs(path, original, replay, diffs) {
|
|
3850
|
+
const length = Math.max(original.length, replay.length);
|
|
3851
|
+
for (let index = 0; index < length; index += 1) {
|
|
3852
|
+
collectJsonLeafDiffs(
|
|
3853
|
+
`${path}[${index}]`,
|
|
3854
|
+
original[index] ?? null,
|
|
3855
|
+
replay[index] ?? null,
|
|
3856
|
+
diffs
|
|
3857
|
+
);
|
|
3858
|
+
}
|
|
3859
|
+
return diffs;
|
|
3860
|
+
}
|
|
3861
|
+
function jsonValuesEqual(original, replay) {
|
|
3862
|
+
if (original === replay) {
|
|
3863
|
+
return true;
|
|
3864
|
+
}
|
|
3865
|
+
if (Array.isArray(original) && Array.isArray(replay)) {
|
|
3866
|
+
return original.length === replay.length && original.every(
|
|
3867
|
+
(item, index) => jsonValuesEqual(item, replay[index] ?? null)
|
|
3868
|
+
);
|
|
3869
|
+
}
|
|
3870
|
+
if (isJsonObject(original) && isJsonObject(replay)) {
|
|
3871
|
+
const originalKeys = Object.keys(original).sort();
|
|
3872
|
+
const replayKeys = Object.keys(replay).sort();
|
|
3873
|
+
return originalKeys.length === replayKeys.length && originalKeys.every(
|
|
3874
|
+
(key, index) => key === replayKeys[index] && jsonValuesEqual(original[key] ?? null, replay[key] ?? null)
|
|
3875
|
+
);
|
|
3876
|
+
}
|
|
3877
|
+
return false;
|
|
3878
|
+
}
|
|
3879
|
+
function isJsonObject(value) {
|
|
3880
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3881
|
+
}
|
|
3882
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
3883
|
+
0 && (module.exports = {
|
|
3884
|
+
agent,
|
|
3885
|
+
applyTraceEventToRunState,
|
|
3886
|
+
buildHostedTraceUrl,
|
|
3887
|
+
buildMissingKeyMessage,
|
|
3888
|
+
buildPipelineTeamWorkflowConfig,
|
|
3889
|
+
buildReviewPanelWorkflowConfig,
|
|
3890
|
+
buildSupervisorTeamWorkflowConfig,
|
|
3891
|
+
compareRunToReplay,
|
|
3892
|
+
createArtifactStore,
|
|
3893
|
+
createConsoleSink,
|
|
3894
|
+
createInMemoryMemoryProvider,
|
|
3895
|
+
createInitialRunState,
|
|
3896
|
+
createJsonFileMemoryProvider,
|
|
3897
|
+
createJsonlSink,
|
|
3898
|
+
createPlatformTraceSink,
|
|
3899
|
+
createThreadMemory,
|
|
3900
|
+
createTraceBus,
|
|
3901
|
+
defaultParallelMergedArtifactKey,
|
|
3902
|
+
defaultRunStateDirectory,
|
|
3903
|
+
defaultSupervisorMaxSteps,
|
|
3904
|
+
defaultTraceDirectory,
|
|
3905
|
+
defaultWorkflowTopology,
|
|
3906
|
+
evaluateToolPolicies,
|
|
3907
|
+
executeToolCall,
|
|
3908
|
+
finishWorkflow,
|
|
3909
|
+
formatTraceEventLine,
|
|
3910
|
+
loadReplaySource,
|
|
3911
|
+
loadRunStateFile,
|
|
3912
|
+
maxReplayOutputDiffCount,
|
|
3913
|
+
policy,
|
|
3914
|
+
replayRun,
|
|
3915
|
+
resolvePlatformConfig,
|
|
3916
|
+
resolveRunStateFilePath,
|
|
3917
|
+
resolveTraceFilePath,
|
|
3918
|
+
resolveWorkflowMemoryConfig,
|
|
3919
|
+
resumeRun,
|
|
3920
|
+
runAgent,
|
|
3921
|
+
runWorkflow,
|
|
3922
|
+
team,
|
|
3923
|
+
tool,
|
|
3924
|
+
workflow
|
|
3925
|
+
});
|