@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.
@@ -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
+ };