@cuylabs/agent-runtime-dapr 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +260 -0
- package/dist/chunk-2CEICSJH.js +809 -0
- package/dist/chunk-2FMHOZLU.js +1997 -0
- package/dist/chunk-A34CHK2E.js +283 -0
- package/dist/chunk-DILON56B.js +668 -0
- package/dist/execution/index.d.ts +126 -0
- package/dist/execution/index.js +17 -0
- package/dist/host/index.d.ts +7 -0
- package/dist/host/index.js +27 -0
- package/dist/index-CKTP36vE.d.ts +533 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.js +65 -0
- package/dist/store-pRLGfYhN.d.ts +97 -0
- package/dist/workflow/index.d.ts +216 -0
- package/dist/workflow/index.js +23 -0
- package/dist/workflow-bridge-C8Z1yr0Y.d.ts +83 -0
- package/package.json +99 -0
|
@@ -0,0 +1,809 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DaprSidecarClient,
|
|
3
|
+
isDaprConflictError
|
|
4
|
+
} from "./chunk-A34CHK2E.js";
|
|
5
|
+
|
|
6
|
+
// src/execution/store.ts
|
|
7
|
+
var DEFAULT_KEY_PREFIX = "agent-runtime:execution:";
|
|
8
|
+
var STORED_EXECUTION_KIND = "@cuylabs/agent-runtime-dapr/execution";
|
|
9
|
+
var STORED_EXECUTION_CHECKPOINT_KIND = "@cuylabs/agent-runtime-dapr/execution-checkpoint";
|
|
10
|
+
var STORED_EXECUTION_VERSION = 1;
|
|
11
|
+
var DEFAULT_INDEX_UPDATE_RETRIES = 4;
|
|
12
|
+
function ensureNonEmpty(input, label) {
|
|
13
|
+
const trimmed = input.trim();
|
|
14
|
+
if (!trimmed) {
|
|
15
|
+
throw new Error(`${label} must not be empty`);
|
|
16
|
+
}
|
|
17
|
+
return trimmed;
|
|
18
|
+
}
|
|
19
|
+
function cloneRecord(value) {
|
|
20
|
+
return structuredClone(value);
|
|
21
|
+
}
|
|
22
|
+
function serializeError(error) {
|
|
23
|
+
return {
|
|
24
|
+
name: error.name,
|
|
25
|
+
message: error.message,
|
|
26
|
+
...error.stack ? { stack: error.stack } : {}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function toJsonSafe(value) {
|
|
30
|
+
if (value === void 0) {
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
const serialized = JSON.stringify(value, (_key, currentValue) => {
|
|
34
|
+
if (currentValue instanceof Error) {
|
|
35
|
+
return serializeError(currentValue);
|
|
36
|
+
}
|
|
37
|
+
return currentValue;
|
|
38
|
+
});
|
|
39
|
+
if (serialized === void 0) {
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
return JSON.parse(serialized);
|
|
43
|
+
}
|
|
44
|
+
function toSerializableContext(context) {
|
|
45
|
+
return {
|
|
46
|
+
...context.trigger ? { trigger: context.trigger } : {},
|
|
47
|
+
...context.fallbackSessionKey ? { fallbackSessionKey: context.fallbackSessionKey } : {}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function toSerializableSnapshot(snapshot) {
|
|
51
|
+
return {
|
|
52
|
+
sessionId: snapshot.sessionId,
|
|
53
|
+
response: snapshot.response,
|
|
54
|
+
usage: cloneRecord(snapshot.usage),
|
|
55
|
+
toolCalls: snapshot.toolCalls.map((toolCall) => ({
|
|
56
|
+
name: toolCall.name,
|
|
57
|
+
result: toJsonSafe(toolCall.result)
|
|
58
|
+
})),
|
|
59
|
+
eventCount: snapshot.eventCount,
|
|
60
|
+
...snapshot.activeStep !== void 0 ? { activeStep: snapshot.activeStep } : {},
|
|
61
|
+
...snapshot.lastEvent !== void 0 ? { lastEvent: toJsonSafe(snapshot.lastEvent) } : {},
|
|
62
|
+
...snapshot.error ? { error: snapshot.error } : {},
|
|
63
|
+
startedAt: snapshot.startedAt,
|
|
64
|
+
updatedAt: snapshot.updatedAt,
|
|
65
|
+
turnState: toJsonSafe(cloneRecord(snapshot.turnState))
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function toExecutionRecord(run, snapshot) {
|
|
69
|
+
return {
|
|
70
|
+
sessionId: run.sessionId,
|
|
71
|
+
payload: toJsonSafe(run.payload),
|
|
72
|
+
context: toSerializableContext(run.context),
|
|
73
|
+
status: "running",
|
|
74
|
+
startedAt: run.startedAt,
|
|
75
|
+
updatedAt: snapshot.updatedAt,
|
|
76
|
+
checkpointCount: 0,
|
|
77
|
+
snapshot: toSerializableSnapshot(snapshot)
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function buildCheckpointId(checkpoint) {
|
|
81
|
+
const createdAtMs = Date.parse(checkpoint.createdAt);
|
|
82
|
+
const safeTimestamp = Number.isFinite(createdAtMs) ? createdAtMs.toString() : checkpoint.createdAt.replace(/[^0-9]/g, "");
|
|
83
|
+
const paddedEventCount = String(checkpoint.snapshot.eventCount).padStart(
|
|
84
|
+
6,
|
|
85
|
+
"0"
|
|
86
|
+
);
|
|
87
|
+
return `${safeTimestamp}-${paddedEventCount}-${checkpoint.reason}`;
|
|
88
|
+
}
|
|
89
|
+
var DaprExecutionStore = class {
|
|
90
|
+
client;
|
|
91
|
+
keyPrefix;
|
|
92
|
+
constructor(options) {
|
|
93
|
+
this.client = new DaprSidecarClient(options);
|
|
94
|
+
this.keyPrefix = ensureNonEmpty(
|
|
95
|
+
options.keyPrefix ?? DEFAULT_KEY_PREFIX,
|
|
96
|
+
"keyPrefix"
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
async getExecution(sessionId) {
|
|
100
|
+
const value = await this.client.getState(
|
|
101
|
+
this.stateKeyForExecution(sessionId)
|
|
102
|
+
);
|
|
103
|
+
const decoded = this.decodeExecution(value);
|
|
104
|
+
return decoded ? cloneRecord(decoded) : void 0;
|
|
105
|
+
}
|
|
106
|
+
async listExecutions() {
|
|
107
|
+
const records = [];
|
|
108
|
+
let token;
|
|
109
|
+
do {
|
|
110
|
+
const response = await this.client.queryState({
|
|
111
|
+
filter: {
|
|
112
|
+
EQ: {
|
|
113
|
+
kind: STORED_EXECUTION_KIND
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
page: {
|
|
117
|
+
limit: 100,
|
|
118
|
+
...token ? { token } : {}
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
for (const result of response?.results ?? []) {
|
|
122
|
+
if (result.error || result.data === void 0) {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
const execution = this.decodeExecution(result.data);
|
|
126
|
+
if (execution) {
|
|
127
|
+
records.push(execution);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
token = response?.token?.trim() || void 0;
|
|
131
|
+
} while (token);
|
|
132
|
+
return records.map((record) => cloneRecord(record)).sort((left, right) => left.startedAt.localeCompare(right.startedAt));
|
|
133
|
+
}
|
|
134
|
+
async listCheckpoints(sessionId) {
|
|
135
|
+
const indexed = await this.listCheckpointsFromIndex(sessionId);
|
|
136
|
+
if (indexed) {
|
|
137
|
+
return indexed.map((record) => cloneRecord(record)).sort((left, right) => {
|
|
138
|
+
const createdAtSort = left.createdAt.localeCompare(right.createdAt);
|
|
139
|
+
if (createdAtSort !== 0) {
|
|
140
|
+
return createdAtSort;
|
|
141
|
+
}
|
|
142
|
+
return left.id.localeCompare(right.id);
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
const records = [];
|
|
146
|
+
let token;
|
|
147
|
+
do {
|
|
148
|
+
const response = await this.client.queryState({
|
|
149
|
+
filter: {
|
|
150
|
+
EQ: {
|
|
151
|
+
kind: STORED_EXECUTION_CHECKPOINT_KIND
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
page: {
|
|
155
|
+
limit: 200,
|
|
156
|
+
...token ? { token } : {}
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
for (const result of response?.results ?? []) {
|
|
160
|
+
if (result.error || result.data === void 0) {
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
const checkpoint = this.decodeCheckpoint(result.data);
|
|
164
|
+
if (checkpoint && checkpoint.sessionId === sessionId) {
|
|
165
|
+
records.push(checkpoint);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
token = response?.token?.trim() || void 0;
|
|
169
|
+
} while (token);
|
|
170
|
+
return records.map((record) => cloneRecord(record)).sort((left, right) => {
|
|
171
|
+
const createdAtSort = left.createdAt.localeCompare(right.createdAt);
|
|
172
|
+
if (createdAtSort !== 0) {
|
|
173
|
+
return createdAtSort;
|
|
174
|
+
}
|
|
175
|
+
return left.id.localeCompare(right.id);
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
async recordStart(run, snapshot) {
|
|
179
|
+
await this.writeExecution(toExecutionRecord(run, snapshot));
|
|
180
|
+
}
|
|
181
|
+
async recordCheckpoint(checkpoint) {
|
|
182
|
+
const record = {
|
|
183
|
+
id: buildCheckpointId(checkpoint),
|
|
184
|
+
sessionId: checkpoint.run.sessionId,
|
|
185
|
+
payload: toJsonSafe(checkpoint.run.payload),
|
|
186
|
+
context: toSerializableContext(checkpoint.run.context),
|
|
187
|
+
startedAt: checkpoint.run.startedAt,
|
|
188
|
+
reason: checkpoint.reason,
|
|
189
|
+
snapshot: toSerializableSnapshot(checkpoint.snapshot),
|
|
190
|
+
...checkpoint.event !== void 0 ? { event: toJsonSafe(checkpoint.event) } : {},
|
|
191
|
+
createdAt: checkpoint.createdAt
|
|
192
|
+
};
|
|
193
|
+
await this.writeCheckpoint(record);
|
|
194
|
+
await this.addCheckpointToIndex(record.sessionId, record.id).catch(() => {
|
|
195
|
+
});
|
|
196
|
+
const current = await this.getExecution(checkpoint.run.sessionId);
|
|
197
|
+
const next = current ?? toExecutionRecord(checkpoint.run, checkpoint.snapshot);
|
|
198
|
+
next.updatedAt = checkpoint.snapshot.updatedAt;
|
|
199
|
+
next.lastCheckpointReason = checkpoint.reason;
|
|
200
|
+
next.checkpointCount = (current?.checkpointCount ?? 0) + 1;
|
|
201
|
+
next.snapshot = toSerializableSnapshot(checkpoint.snapshot);
|
|
202
|
+
await this.writeExecution(next);
|
|
203
|
+
}
|
|
204
|
+
async recordCompletion(run, result, snapshot) {
|
|
205
|
+
const current = await this.getExecution(run.sessionId);
|
|
206
|
+
const next = current ?? toExecutionRecord(run, snapshot);
|
|
207
|
+
next.status = "completed";
|
|
208
|
+
next.updatedAt = snapshot.updatedAt;
|
|
209
|
+
next.completedAt = snapshot.updatedAt;
|
|
210
|
+
next.snapshot = toSerializableSnapshot(snapshot);
|
|
211
|
+
next.result = toJsonSafe(result);
|
|
212
|
+
delete next.error;
|
|
213
|
+
await this.writeExecution(next);
|
|
214
|
+
}
|
|
215
|
+
async recordError(run, error, snapshot) {
|
|
216
|
+
const current = await this.getExecution(run.sessionId);
|
|
217
|
+
const next = current ?? toExecutionRecord(run, snapshot);
|
|
218
|
+
next.status = "failed";
|
|
219
|
+
next.updatedAt = snapshot.updatedAt;
|
|
220
|
+
next.completedAt = snapshot.updatedAt;
|
|
221
|
+
next.snapshot = toSerializableSnapshot(snapshot);
|
|
222
|
+
next.error = serializeError(error);
|
|
223
|
+
delete next.result;
|
|
224
|
+
await this.writeExecution(next);
|
|
225
|
+
}
|
|
226
|
+
async cleanup(options = {}) {
|
|
227
|
+
const now = options.now ?? (() => Date.now());
|
|
228
|
+
const includeRunning = options.includeRunning ?? false;
|
|
229
|
+
const cutoff = options.maxAgeMs !== void 0 ? now() - options.maxAgeMs : void 0;
|
|
230
|
+
const executions = await this.listExecutions();
|
|
231
|
+
const eligible = executions.filter(
|
|
232
|
+
(execution) => includeRunning ? true : execution.status !== "running"
|
|
233
|
+
);
|
|
234
|
+
const toDelete = /* @__PURE__ */ new Set();
|
|
235
|
+
let deletedCheckpoints = 0;
|
|
236
|
+
let trimmedSessions = 0;
|
|
237
|
+
if (cutoff !== void 0) {
|
|
238
|
+
for (const execution of eligible) {
|
|
239
|
+
const referenceMs = Date.parse(execution.completedAt ?? execution.updatedAt);
|
|
240
|
+
if (Number.isFinite(referenceMs) && referenceMs < cutoff) {
|
|
241
|
+
toDelete.add(execution.sessionId);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
if (options.maxExecutions !== void 0 && Number.isFinite(options.maxExecutions) && options.maxExecutions >= 0) {
|
|
246
|
+
const normalizedMaxExecutions = Math.floor(options.maxExecutions);
|
|
247
|
+
const overflow = [...eligible].sort((left, right) => {
|
|
248
|
+
const leftMs = Date.parse(left.completedAt ?? left.updatedAt);
|
|
249
|
+
const rightMs = Date.parse(right.completedAt ?? right.updatedAt);
|
|
250
|
+
return rightMs - leftMs;
|
|
251
|
+
}).slice(normalizedMaxExecutions);
|
|
252
|
+
for (const execution of overflow) {
|
|
253
|
+
toDelete.add(execution.sessionId);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
const deletedExecutionIds = [];
|
|
257
|
+
for (const sessionId of toDelete) {
|
|
258
|
+
deletedCheckpoints += await this.deleteExecutionRecord(sessionId);
|
|
259
|
+
deletedExecutionIds.push(sessionId);
|
|
260
|
+
}
|
|
261
|
+
if (options.maxCheckpointsPerExecution !== void 0 && Number.isFinite(options.maxCheckpointsPerExecution) && options.maxCheckpointsPerExecution >= 0) {
|
|
262
|
+
const normalizedCheckpointLimit = Math.floor(options.maxCheckpointsPerExecution);
|
|
263
|
+
for (const execution of executions) {
|
|
264
|
+
if (toDelete.has(execution.sessionId)) {
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
if (!includeRunning && execution.status === "running") {
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
const checkpoints = await this.listCheckpoints(execution.sessionId);
|
|
271
|
+
if (checkpoints.length <= normalizedCheckpointLimit) {
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
const checkpointOverflow = checkpoints.slice(
|
|
275
|
+
0,
|
|
276
|
+
checkpoints.length - normalizedCheckpointLimit
|
|
277
|
+
);
|
|
278
|
+
const retained = checkpoints.slice(checkpoints.length - normalizedCheckpointLimit);
|
|
279
|
+
for (const checkpoint of checkpointOverflow) {
|
|
280
|
+
await this.deleteCheckpoint(checkpoint.sessionId, checkpoint.id);
|
|
281
|
+
}
|
|
282
|
+
deletedCheckpoints += checkpointOverflow.length;
|
|
283
|
+
trimmedSessions += 1;
|
|
284
|
+
await this.writeCheckpointIndex(
|
|
285
|
+
execution.sessionId,
|
|
286
|
+
retained.map((checkpoint) => checkpoint.id)
|
|
287
|
+
);
|
|
288
|
+
const updatedExecution = await this.getExecution(execution.sessionId);
|
|
289
|
+
if (updatedExecution) {
|
|
290
|
+
updatedExecution.checkpointCount = retained.length;
|
|
291
|
+
updatedExecution.lastCheckpointReason = retained.at(-1)?.reason;
|
|
292
|
+
await this.writeExecution(updatedExecution);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return {
|
|
297
|
+
deletedExecutions: deletedExecutionIds.length,
|
|
298
|
+
deletedExecutionIds,
|
|
299
|
+
deletedCheckpoints,
|
|
300
|
+
trimmedSessions
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
stateKeyForExecution(sessionId) {
|
|
304
|
+
return `${this.keyPrefix}runs/${sessionId}`;
|
|
305
|
+
}
|
|
306
|
+
stateKeyForCheckpoint(sessionId, checkpointId) {
|
|
307
|
+
return `${this.keyPrefix}checkpoints/${sessionId}/${checkpointId}`;
|
|
308
|
+
}
|
|
309
|
+
stateKeyForCheckpointIndex(sessionId) {
|
|
310
|
+
return `${this.keyPrefix}checkpoint-index/${sessionId}`;
|
|
311
|
+
}
|
|
312
|
+
async deleteExecutionRecord(sessionId) {
|
|
313
|
+
const checkpoints = await this.listCheckpoints(sessionId);
|
|
314
|
+
for (const checkpoint of checkpoints) {
|
|
315
|
+
await this.deleteCheckpoint(sessionId, checkpoint.id);
|
|
316
|
+
}
|
|
317
|
+
await this.client.deleteState(this.stateKeyForCheckpointIndex(sessionId));
|
|
318
|
+
await this.client.deleteState(this.stateKeyForExecution(sessionId));
|
|
319
|
+
return checkpoints.length;
|
|
320
|
+
}
|
|
321
|
+
async deleteCheckpoint(sessionId, checkpointId) {
|
|
322
|
+
await this.client.deleteState(this.stateKeyForCheckpoint(sessionId, checkpointId));
|
|
323
|
+
}
|
|
324
|
+
async readCheckpoint(sessionId, checkpointId) {
|
|
325
|
+
const value = await this.client.getState(
|
|
326
|
+
this.stateKeyForCheckpoint(sessionId, checkpointId)
|
|
327
|
+
);
|
|
328
|
+
const decoded = this.decodeCheckpoint(value);
|
|
329
|
+
return decoded ? cloneRecord(decoded) : void 0;
|
|
330
|
+
}
|
|
331
|
+
async readCheckpointIndex(sessionId) {
|
|
332
|
+
const entry = await this.client.getStateEntry(
|
|
333
|
+
this.stateKeyForCheckpointIndex(sessionId)
|
|
334
|
+
);
|
|
335
|
+
if (entry.value === void 0) {
|
|
336
|
+
return { exists: false };
|
|
337
|
+
}
|
|
338
|
+
if (!Array.isArray(entry.value)) {
|
|
339
|
+
return {
|
|
340
|
+
ids: [],
|
|
341
|
+
etag: entry.etag,
|
|
342
|
+
exists: true
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
return {
|
|
346
|
+
ids: [...new Set(entry.value.filter((item) => typeof item === "string"))],
|
|
347
|
+
etag: entry.etag,
|
|
348
|
+
exists: true
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
async writeCheckpointIndex(sessionId, ids, etag) {
|
|
352
|
+
await this.client.saveState(this.stateKeyForCheckpointIndex(sessionId), [...new Set(ids)], {
|
|
353
|
+
etag,
|
|
354
|
+
concurrency: etag ? "first-write" : void 0
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
async updateCheckpointIndex(sessionId, updater) {
|
|
358
|
+
for (let attempt = 0; attempt < DEFAULT_INDEX_UPDATE_RETRIES; attempt += 1) {
|
|
359
|
+
const current = await this.readCheckpointIndex(sessionId);
|
|
360
|
+
const next = updater(current.ids ?? []);
|
|
361
|
+
if (!next) {
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
try {
|
|
365
|
+
await this.writeCheckpointIndex(sessionId, next, current.etag);
|
|
366
|
+
return;
|
|
367
|
+
} catch (error) {
|
|
368
|
+
if (isDaprConflictError(error) && attempt + 1 < DEFAULT_INDEX_UPDATE_RETRIES) {
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
throw error;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
async addCheckpointToIndex(sessionId, checkpointId) {
|
|
376
|
+
await this.updateCheckpointIndex(sessionId, (ids) => {
|
|
377
|
+
if (ids.includes(checkpointId)) {
|
|
378
|
+
return void 0;
|
|
379
|
+
}
|
|
380
|
+
return [...ids, checkpointId];
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
async listCheckpointsFromIndex(sessionId) {
|
|
384
|
+
const current = await this.readCheckpointIndex(sessionId);
|
|
385
|
+
if (!current.exists) {
|
|
386
|
+
return void 0;
|
|
387
|
+
}
|
|
388
|
+
const records = await Promise.all(
|
|
389
|
+
(current.ids ?? []).map(
|
|
390
|
+
(checkpointId) => this.readCheckpoint(sessionId, checkpointId)
|
|
391
|
+
)
|
|
392
|
+
);
|
|
393
|
+
const present = records.filter(
|
|
394
|
+
(record) => Boolean(record)
|
|
395
|
+
);
|
|
396
|
+
if (present.length !== (current.ids ?? []).length) {
|
|
397
|
+
await this.writeCheckpointIndex(
|
|
398
|
+
sessionId,
|
|
399
|
+
present.map((record) => record.id),
|
|
400
|
+
current.etag
|
|
401
|
+
).catch(() => {
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
return present;
|
|
405
|
+
}
|
|
406
|
+
async writeExecution(record) {
|
|
407
|
+
const envelope = {
|
|
408
|
+
kind: STORED_EXECUTION_KIND,
|
|
409
|
+
version: STORED_EXECUTION_VERSION,
|
|
410
|
+
execution: record
|
|
411
|
+
};
|
|
412
|
+
await this.client.saveState(
|
|
413
|
+
this.stateKeyForExecution(record.sessionId),
|
|
414
|
+
envelope
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
async writeCheckpoint(record) {
|
|
418
|
+
const envelope = {
|
|
419
|
+
kind: STORED_EXECUTION_CHECKPOINT_KIND,
|
|
420
|
+
version: STORED_EXECUTION_VERSION,
|
|
421
|
+
checkpoint: record
|
|
422
|
+
};
|
|
423
|
+
await this.client.saveState(
|
|
424
|
+
this.stateKeyForCheckpoint(record.sessionId, record.id),
|
|
425
|
+
envelope
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
decodeExecution(value) {
|
|
429
|
+
if (!value || typeof value !== "object") {
|
|
430
|
+
return void 0;
|
|
431
|
+
}
|
|
432
|
+
const envelope = value;
|
|
433
|
+
if (envelope.kind === STORED_EXECUTION_KIND) {
|
|
434
|
+
if (envelope.version !== STORED_EXECUTION_VERSION) {
|
|
435
|
+
throw new Error(
|
|
436
|
+
`Unsupported Dapr execution record version: ${String(envelope.version)}`
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
if (envelope.execution && typeof envelope.execution === "object") {
|
|
440
|
+
return envelope.execution;
|
|
441
|
+
}
|
|
442
|
+
return void 0;
|
|
443
|
+
}
|
|
444
|
+
return void 0;
|
|
445
|
+
}
|
|
446
|
+
decodeCheckpoint(value) {
|
|
447
|
+
if (!value || typeof value !== "object") {
|
|
448
|
+
return void 0;
|
|
449
|
+
}
|
|
450
|
+
const envelope = value;
|
|
451
|
+
if (envelope.kind === STORED_EXECUTION_CHECKPOINT_KIND) {
|
|
452
|
+
if (envelope.version !== STORED_EXECUTION_VERSION) {
|
|
453
|
+
throw new Error(
|
|
454
|
+
`Unsupported Dapr execution checkpoint version: ${String(envelope.version)}`
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
if (envelope.checkpoint && typeof envelope.checkpoint === "object") {
|
|
458
|
+
return envelope.checkpoint;
|
|
459
|
+
}
|
|
460
|
+
return void 0;
|
|
461
|
+
}
|
|
462
|
+
return void 0;
|
|
463
|
+
}
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
// src/execution/observer.ts
|
|
467
|
+
var DaprExecutionObserver = class {
|
|
468
|
+
store;
|
|
469
|
+
constructor(options) {
|
|
470
|
+
this.store = options.store;
|
|
471
|
+
}
|
|
472
|
+
async onTaskStart(run, snapshot) {
|
|
473
|
+
await this.store.recordStart(run, snapshot);
|
|
474
|
+
}
|
|
475
|
+
async onCheckpoint(checkpoint) {
|
|
476
|
+
await this.store.recordCheckpoint(checkpoint);
|
|
477
|
+
}
|
|
478
|
+
async onTaskComplete(run, result, snapshot) {
|
|
479
|
+
await this.store.recordCompletion(run, result, snapshot);
|
|
480
|
+
}
|
|
481
|
+
async onTaskError(run, error, snapshot) {
|
|
482
|
+
await this.store.recordError(run, error, snapshot);
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
function createDaprExecutionObserver(options) {
|
|
486
|
+
return new DaprExecutionObserver(options);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// src/execution/logging.ts
|
|
490
|
+
function createDaprLoggingObserver(options = {}) {
|
|
491
|
+
const prefix = options.prefix ?? "[agent]";
|
|
492
|
+
const logger = options.logger ?? {
|
|
493
|
+
info: (msg) => console.log(msg),
|
|
494
|
+
warn: (msg) => console.warn(msg),
|
|
495
|
+
error: (msg) => console.error(msg)
|
|
496
|
+
};
|
|
497
|
+
const verbose = options.verbose ?? false;
|
|
498
|
+
function log(message) {
|
|
499
|
+
logger.info(`${prefix} ${message}`);
|
|
500
|
+
}
|
|
501
|
+
function formatUsage(snapshot) {
|
|
502
|
+
const { usage } = snapshot;
|
|
503
|
+
return `tokens=${usage.totalTokens} (in=${usage.inputTokens} out=${usage.outputTokens})`;
|
|
504
|
+
}
|
|
505
|
+
function formatToolCalls(snapshot) {
|
|
506
|
+
if (snapshot.toolCalls.length === 0) return "";
|
|
507
|
+
const names = snapshot.toolCalls.map((tc) => tc.name).join(", ");
|
|
508
|
+
return ` tools=[${names}]`;
|
|
509
|
+
}
|
|
510
|
+
return {
|
|
511
|
+
onTaskStart(run, _snapshot) {
|
|
512
|
+
log(
|
|
513
|
+
`task-start session=${run.sessionId} trigger=${run.context.trigger ?? "unknown"}`
|
|
514
|
+
);
|
|
515
|
+
},
|
|
516
|
+
...verbose ? {
|
|
517
|
+
onTaskEvent(run, event, snapshot) {
|
|
518
|
+
log(
|
|
519
|
+
`event type=${event.type} session=${run.sessionId} step=${snapshot.activeStep ?? 0}`
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
} : {},
|
|
523
|
+
onCheckpoint(checkpoint) {
|
|
524
|
+
log(
|
|
525
|
+
`checkpoint reason=${checkpoint.reason} session=${checkpoint.run.sessionId} ${formatUsage(checkpoint.snapshot)}${formatToolCalls(checkpoint.snapshot)}`
|
|
526
|
+
);
|
|
527
|
+
},
|
|
528
|
+
onTaskComplete(run, result, snapshot) {
|
|
529
|
+
const toolCount = snapshot.toolCalls.length;
|
|
530
|
+
const responsePreview = result.response.length > 80 ? result.response.slice(0, 80) + "\u2026" : result.response;
|
|
531
|
+
log(
|
|
532
|
+
`task-complete session=${run.sessionId} ${formatUsage(snapshot)} tool-calls=${toolCount} response="${responsePreview}"`
|
|
533
|
+
);
|
|
534
|
+
},
|
|
535
|
+
onTaskError(run, error, snapshot) {
|
|
536
|
+
(logger.error ?? logger.info)(
|
|
537
|
+
`${prefix} task-error session=${run.sessionId} ${formatUsage(snapshot)} error="${error.message}"`
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// src/execution/workflow-bridge.ts
|
|
544
|
+
var EMPTY_USAGE = {
|
|
545
|
+
inputTokens: 0,
|
|
546
|
+
outputTokens: 0,
|
|
547
|
+
totalTokens: 0
|
|
548
|
+
};
|
|
549
|
+
function buildRun(state, payload, trigger) {
|
|
550
|
+
return {
|
|
551
|
+
payload,
|
|
552
|
+
context: { trigger },
|
|
553
|
+
sessionId: state.sessionId,
|
|
554
|
+
startedAt: state.startedAt
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
function collectToolCalls(state) {
|
|
558
|
+
const toolCalls = [];
|
|
559
|
+
for (const msg of state.messages) {
|
|
560
|
+
if (msg.role === "tool") {
|
|
561
|
+
toolCalls.push({ name: msg.toolName, result: msg.content });
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
return toolCalls;
|
|
565
|
+
}
|
|
566
|
+
function buildSnapshot(state) {
|
|
567
|
+
return {
|
|
568
|
+
sessionId: state.sessionId,
|
|
569
|
+
response: state.finalResponse ?? state.lastModelStep?.text ?? "",
|
|
570
|
+
usage: state.usage ?? EMPTY_USAGE,
|
|
571
|
+
toolCalls: collectToolCalls(state),
|
|
572
|
+
eventCount: 0,
|
|
573
|
+
activeStep: state.step,
|
|
574
|
+
startedAt: state.startedAt,
|
|
575
|
+
updatedAt: state.updatedAt,
|
|
576
|
+
turnState: state.turnState ?? {}
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
function buildCheckpoint(reason, state, payload, trigger) {
|
|
580
|
+
return {
|
|
581
|
+
run: buildRun(state, payload, trigger),
|
|
582
|
+
reason,
|
|
583
|
+
snapshot: buildSnapshot(state),
|
|
584
|
+
createdAt: state.updatedAt
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
async function notifyAll(observers, fn) {
|
|
588
|
+
for (const observer of observers) {
|
|
589
|
+
try {
|
|
590
|
+
await fn(observer);
|
|
591
|
+
} catch {
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
function createWorkflowObserverBridge(options) {
|
|
596
|
+
const { observers } = options;
|
|
597
|
+
let payload = options.payload;
|
|
598
|
+
const trigger = options.trigger ?? "workflow";
|
|
599
|
+
if (observers.length === 0) {
|
|
600
|
+
return {
|
|
601
|
+
async notifyTaskStart() {
|
|
602
|
+
},
|
|
603
|
+
async notifyCheckpoint() {
|
|
604
|
+
},
|
|
605
|
+
async notifyTaskComplete() {
|
|
606
|
+
},
|
|
607
|
+
async notifyTaskError() {
|
|
608
|
+
},
|
|
609
|
+
updatePayload() {
|
|
610
|
+
},
|
|
611
|
+
getOtelContext() {
|
|
612
|
+
return void 0;
|
|
613
|
+
}
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
let taskStarted = false;
|
|
617
|
+
return {
|
|
618
|
+
async notifyTaskStart(state) {
|
|
619
|
+
if (taskStarted) return;
|
|
620
|
+
taskStarted = true;
|
|
621
|
+
const run = buildRun(state, payload, trigger);
|
|
622
|
+
const snapshot = buildSnapshot(state);
|
|
623
|
+
await notifyAll(observers, (o) => o.onTaskStart?.(run, snapshot));
|
|
624
|
+
},
|
|
625
|
+
async notifyCheckpoint(reason, state) {
|
|
626
|
+
const checkpoint = buildCheckpoint(reason, state, payload, trigger);
|
|
627
|
+
await notifyAll(observers, (o) => o.onCheckpoint?.(checkpoint));
|
|
628
|
+
},
|
|
629
|
+
async notifyTaskComplete(state) {
|
|
630
|
+
const run = buildRun(state, payload, trigger);
|
|
631
|
+
const snapshot = buildSnapshot(state);
|
|
632
|
+
const result = {
|
|
633
|
+
response: state.finalResponse ?? "",
|
|
634
|
+
sessionId: state.sessionId,
|
|
635
|
+
usage: state.usage ?? EMPTY_USAGE,
|
|
636
|
+
toolCalls: collectToolCalls(state)
|
|
637
|
+
};
|
|
638
|
+
await notifyAll(
|
|
639
|
+
observers,
|
|
640
|
+
(o) => o.onTaskComplete?.(run, result, snapshot)
|
|
641
|
+
);
|
|
642
|
+
},
|
|
643
|
+
async notifyTaskError(state, error) {
|
|
644
|
+
const run = buildRun(state, payload, trigger);
|
|
645
|
+
const snapshot = buildSnapshot(state);
|
|
646
|
+
await notifyAll(observers, (o) => o.onTaskError?.(run, error, snapshot));
|
|
647
|
+
},
|
|
648
|
+
updatePayload(newPayload) {
|
|
649
|
+
payload = newPayload;
|
|
650
|
+
},
|
|
651
|
+
getOtelContext(sessionId) {
|
|
652
|
+
for (const observer of observers) {
|
|
653
|
+
const ctx = observer.getOtelContext?.(sessionId);
|
|
654
|
+
if (ctx !== void 0) return ctx;
|
|
655
|
+
}
|
|
656
|
+
return void 0;
|
|
657
|
+
}
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// src/execution/telemetry.ts
|
|
662
|
+
var _otel = null;
|
|
663
|
+
function oiMime(v) {
|
|
664
|
+
const t = v.trimStart();
|
|
665
|
+
return t.startsWith("{") || t.startsWith("[") ? "application/json" : "text/plain";
|
|
666
|
+
}
|
|
667
|
+
async function getOtel() {
|
|
668
|
+
if (_otel) return _otel;
|
|
669
|
+
try {
|
|
670
|
+
_otel = await import("@opentelemetry/api");
|
|
671
|
+
return _otel;
|
|
672
|
+
} catch {
|
|
673
|
+
return null;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
function createOtelObserver(config = {}) {
|
|
677
|
+
const agentName = config.agentName ?? "agent";
|
|
678
|
+
const spanTimeoutMs = config.spanTimeoutMs ?? 5 * 60 * 1e3;
|
|
679
|
+
const turnSpans = /* @__PURE__ */ new Map();
|
|
680
|
+
let otel = null;
|
|
681
|
+
let tracer = null;
|
|
682
|
+
async function ensureTracer() {
|
|
683
|
+
if (tracer) return tracer;
|
|
684
|
+
otel = await getOtel();
|
|
685
|
+
if (!otel) return null;
|
|
686
|
+
tracer = otel.trace.getTracer("@cuylabs/agent-runtime-dapr");
|
|
687
|
+
return tracer;
|
|
688
|
+
}
|
|
689
|
+
function getUsageAttrs(usage) {
|
|
690
|
+
if (!usage) return {};
|
|
691
|
+
return {
|
|
692
|
+
"gen_ai.usage.input_tokens": usage.inputTokens ?? 0,
|
|
693
|
+
"gen_ai.usage.output_tokens": usage.outputTokens ?? 0
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
return {
|
|
697
|
+
async onTaskStart(run, _snapshot) {
|
|
698
|
+
const existing = turnSpans.get(run.sessionId);
|
|
699
|
+
if (existing) {
|
|
700
|
+
const inputVal2 = run.payload.message.slice(0, 4096);
|
|
701
|
+
existing.span.setAttributes({
|
|
702
|
+
"openinference.span.kind": "AGENT",
|
|
703
|
+
"gen_ai.agent.name": agentName,
|
|
704
|
+
"gen_ai.agent.session_id": run.sessionId,
|
|
705
|
+
"agent.trigger": run.context.trigger ?? "unknown",
|
|
706
|
+
"input.value": inputVal2,
|
|
707
|
+
"input.mime_type": oiMime(inputVal2)
|
|
708
|
+
});
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
const t = await ensureTracer();
|
|
712
|
+
if (!t) return;
|
|
713
|
+
const inputVal = run.payload.message.slice(0, 4096);
|
|
714
|
+
const span = t.startSpan("agent.turn", {
|
|
715
|
+
attributes: {
|
|
716
|
+
"openinference.span.kind": "AGENT",
|
|
717
|
+
"gen_ai.operation.name": "invoke_agent",
|
|
718
|
+
"gen_ai.agent.name": agentName,
|
|
719
|
+
"gen_ai.agent.session_id": run.sessionId,
|
|
720
|
+
"agent.trigger": run.context.trigger ?? "unknown",
|
|
721
|
+
"input.value": inputVal,
|
|
722
|
+
"input.mime_type": oiMime(inputVal)
|
|
723
|
+
}
|
|
724
|
+
});
|
|
725
|
+
const ctx = otel.trace.setSpan(otel.context.active(), span);
|
|
726
|
+
const timer = setTimeout(() => {
|
|
727
|
+
const entry = turnSpans.get(run.sessionId);
|
|
728
|
+
if (entry) {
|
|
729
|
+
entry.span.setStatus({
|
|
730
|
+
code: otel?.SpanStatusCode.ERROR ?? 2,
|
|
731
|
+
message: "Span timed out (possible leak \u2014 task never completed)"
|
|
732
|
+
});
|
|
733
|
+
entry.span.end();
|
|
734
|
+
turnSpans.delete(run.sessionId);
|
|
735
|
+
}
|
|
736
|
+
}, spanTimeoutMs);
|
|
737
|
+
turnSpans.set(run.sessionId, { span, ctx, timer });
|
|
738
|
+
},
|
|
739
|
+
onCheckpoint(checkpoint) {
|
|
740
|
+
const entry = turnSpans.get(checkpoint.run.sessionId);
|
|
741
|
+
if (!entry) return;
|
|
742
|
+
const reason = checkpoint.reason;
|
|
743
|
+
const attrs = {
|
|
744
|
+
"checkpoint.reason": reason
|
|
745
|
+
};
|
|
746
|
+
if (checkpoint.snapshot.activeStep !== void 0) {
|
|
747
|
+
attrs["checkpoint.step"] = checkpoint.snapshot.activeStep;
|
|
748
|
+
}
|
|
749
|
+
const usage = checkpoint.snapshot.usage;
|
|
750
|
+
if (usage) {
|
|
751
|
+
attrs["gen_ai.usage.input_tokens"] = usage.inputTokens ?? 0;
|
|
752
|
+
attrs["gen_ai.usage.output_tokens"] = usage.outputTokens ?? 0;
|
|
753
|
+
}
|
|
754
|
+
const toolCalls = checkpoint.snapshot.toolCalls;
|
|
755
|
+
if (toolCalls.length > 0) {
|
|
756
|
+
attrs["checkpoint.tools"] = toolCalls.map((tc) => tc.name).join(",");
|
|
757
|
+
}
|
|
758
|
+
entry.span.addEvent(`agent.checkpoint.${reason}`, attrs);
|
|
759
|
+
},
|
|
760
|
+
onTaskComplete(run, result, _snapshot) {
|
|
761
|
+
const entry = turnSpans.get(run.sessionId);
|
|
762
|
+
if (!entry) return;
|
|
763
|
+
if (entry.timer) clearTimeout(entry.timer);
|
|
764
|
+
entry.span.setAttributes({
|
|
765
|
+
...getUsageAttrs(result.usage),
|
|
766
|
+
"agent.tool_calls": result.toolCalls?.length ?? 0,
|
|
767
|
+
"agent.response.length": result.response?.length ?? 0
|
|
768
|
+
});
|
|
769
|
+
if (result.response) {
|
|
770
|
+
const outVal = result.response.slice(0, 4096);
|
|
771
|
+
entry.span.setAttribute("output.value", outVal);
|
|
772
|
+
entry.span.setAttribute("output.mime_type", oiMime(outVal));
|
|
773
|
+
}
|
|
774
|
+
entry.span.setStatus({ code: otel?.SpanStatusCode.OK ?? 1 });
|
|
775
|
+
entry.span.end();
|
|
776
|
+
turnSpans.delete(run.sessionId);
|
|
777
|
+
},
|
|
778
|
+
onTaskError(run, error, snapshot) {
|
|
779
|
+
const entry = turnSpans.get(run.sessionId);
|
|
780
|
+
if (!entry) return;
|
|
781
|
+
if (entry.timer) clearTimeout(entry.timer);
|
|
782
|
+
entry.span.setAttributes(getUsageAttrs(snapshot.usage));
|
|
783
|
+
entry.span.setStatus({
|
|
784
|
+
code: otel?.SpanStatusCode.ERROR ?? 2,
|
|
785
|
+
message: error.message
|
|
786
|
+
});
|
|
787
|
+
entry.span.recordException(error);
|
|
788
|
+
entry.span.end();
|
|
789
|
+
turnSpans.delete(run.sessionId);
|
|
790
|
+
},
|
|
791
|
+
getOtelContext(sessionId) {
|
|
792
|
+
return turnSpans.get(sessionId)?.ctx;
|
|
793
|
+
},
|
|
794
|
+
activateContext(sessionId, fn) {
|
|
795
|
+
const entry = turnSpans.get(sessionId);
|
|
796
|
+
if (!entry?.ctx || !otel) return fn();
|
|
797
|
+
return otel.context.with(entry.ctx, fn);
|
|
798
|
+
}
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
export {
|
|
803
|
+
DaprExecutionStore,
|
|
804
|
+
DaprExecutionObserver,
|
|
805
|
+
createDaprExecutionObserver,
|
|
806
|
+
createDaprLoggingObserver,
|
|
807
|
+
createWorkflowObserverBridge,
|
|
808
|
+
createOtelObserver
|
|
809
|
+
};
|