@cuylabs/agent-runtime-dapr 4.9.0 → 5.0.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/README.md CHANGED
@@ -118,6 +118,20 @@ tool still executes through `agent.getHost()`.
118
118
 
119
119
  For the full explanation, see [Tool Hosts In Durable Workflows](docs/tool-hosts.md).
120
120
 
121
+ ## Durable Context Compaction
122
+
123
+ Durable turns compact at the same model-step boundary as direct `Agent.chat()`,
124
+ but they do it through workflow state instead of the in-process chat loop.
125
+ Before a `model-step` activity runs, the workflow can call a
126
+ `context-compaction` activity. That activity applies the agent's normal
127
+ compaction policy to the serialized workflow messages, persists the compaction
128
+ entry to session storage, returns the compacted message snapshots, and the
129
+ workflow checkpoints `context-compaction-finish` before continuing.
130
+
131
+ This keeps the durable path crash-safe without importing direct-loop internals:
132
+ direct execution uses `ChatModelStepSnapshot`; Dapr durable execution uses
133
+ `AgentWorkflowTurnState.messages` and `AgentWorkflowModelStepPlan`.
134
+
121
135
  ## Quick Start
122
136
 
123
137
  ### Step 1: Define your agent
@@ -4,7 +4,10 @@ import {
4
4
  } from "./chunk-HQLQRXU5.js";
5
5
 
6
6
  // src/dispatch/runtime.ts
7
- import { ensureNonEmpty as ensureNonEmpty2, mergeInspection, sleep } from "@cuylabs/agent-core";
7
+ import { ensureNonEmpty as ensureNonEmpty3, mergeInspection, sleep } from "@cuylabs/agent-core";
8
+
9
+ // src/dispatch/targets.ts
10
+ import { ensureNonEmpty as ensureNonEmpty2 } from "@cuylabs/agent-core";
8
11
 
9
12
  // src/host/invoker.ts
10
13
  var DEFAULT_DAPR_HTTP_ENDPOINT = "http://127.0.0.1:3500";
@@ -163,121 +166,7 @@ async function invokeRemoteAgentRun(invoker, appId, request, methodPath = "agent
163
166
  return response.data;
164
167
  }
165
168
 
166
- // src/dispatch/runtime.ts
167
- var DEFAULT_KEY_PREFIX = "agent-runtime:dispatch:";
168
- var STORED_KIND = "@cuylabs/agent-runtime-dapr/dispatch-record";
169
- var STORED_VERSION = 1;
170
- var DEFAULT_INDEX_UPDATE_RETRIES = 4;
171
- var DEFAULT_POLL_INTERVAL_MS = 500;
172
- function cloneRecord(record) {
173
- return structuredClone(record);
174
- }
175
- function toStoredRecord(record) {
176
- return {
177
- kind: STORED_KIND,
178
- version: STORED_VERSION,
179
- record
180
- };
181
- }
182
- function fromStoredRecord(value) {
183
- if (!value || typeof value !== "object") {
184
- return void 0;
185
- }
186
- const envelope = value;
187
- if (envelope.kind !== STORED_KIND || envelope.version !== STORED_VERSION || !envelope.record) {
188
- return void 0;
189
- }
190
- return cloneRecord(envelope.record);
191
- }
192
- function normalizeStatuses(status) {
193
- if (!status) {
194
- return void 0;
195
- }
196
- return new Set(Array.isArray(status) ? status : [status]);
197
- }
198
- function createDaprDispatchRecordWriter(options) {
199
- const client = new DaprSidecarClient(options);
200
- const agentId = ensureNonEmpty2(options.agentId, "agentId");
201
- const keyPrefix = ensureNonEmpty2(
202
- options.keyPrefix ?? DEFAULT_KEY_PREFIX,
203
- "keyPrefix"
204
- );
205
- function recordKey(id) {
206
- return `${keyPrefix}records/${agentId}/${id}`;
207
- }
208
- function globalIndexKey() {
209
- return `${keyPrefix}record-index/${agentId}`;
210
- }
211
- function sessionIndexKey(sessionId) {
212
- return `${keyPrefix}session-index/${agentId}/${sessionId}`;
213
- }
214
- async function readIndex(key) {
215
- const entry = await client.getStateEntry(key);
216
- if (entry.value === void 0) {
217
- return { exists: false };
218
- }
219
- if (!Array.isArray(entry.value)) {
220
- return { ids: [], etag: entry.etag, exists: true };
221
- }
222
- return {
223
- ids: [
224
- ...new Set(
225
- entry.value.filter(
226
- (item) => typeof item === "string"
227
- )
228
- )
229
- ],
230
- etag: entry.etag,
231
- exists: true
232
- };
233
- }
234
- async function writeIndex(key, ids, etag) {
235
- await client.saveState(key, [...new Set(ids)], {
236
- ...etag ? { etag } : {},
237
- concurrency: etag ? "first-write" : void 0
238
- });
239
- }
240
- async function updateIndex(key, updater) {
241
- for (let attempt = 0; attempt < DEFAULT_INDEX_UPDATE_RETRIES; attempt += 1) {
242
- const current = await readIndex(key);
243
- const next = updater(current.ids ?? []);
244
- if (!next) {
245
- return;
246
- }
247
- try {
248
- await writeIndex(key, next, current.etag);
249
- return;
250
- } catch (error) {
251
- if (isDaprConflictError(error) && attempt + 1 < DEFAULT_INDEX_UPDATE_RETRIES) {
252
- continue;
253
- }
254
- throw error;
255
- }
256
- }
257
- }
258
- async function addRecordToIndex(key, id) {
259
- await updateIndex(key, (ids) => {
260
- if (ids.includes(id)) {
261
- return void 0;
262
- }
263
- return [...ids, id];
264
- });
265
- }
266
- return {
267
- async saveStartedRecord(record) {
268
- await client.saveState(recordKey(record.id), toStoredRecord(record), {
269
- concurrency: "first-write"
270
- });
271
- await addRecordToIndex(globalIndexKey(), record.id);
272
- if (record.parentSessionId) {
273
- await addRecordToIndex(
274
- sessionIndexKey(record.parentSessionId),
275
- record.id
276
- );
277
- }
278
- }
279
- };
280
- }
169
+ // src/dispatch/targets.ts
281
170
  function mapWorkflowStatus(status) {
282
171
  switch (status) {
283
172
  case "COMPLETED":
@@ -494,10 +383,126 @@ function createRemoteAgentDispatchTarget(options) {
494
383
  }
495
384
  };
496
385
  }
386
+
387
+ // src/dispatch/runtime.ts
388
+ var DEFAULT_KEY_PREFIX = "agent-runtime:dispatch:";
389
+ var STORED_KIND = "@cuylabs/agent-runtime-dapr/dispatch-record";
390
+ var STORED_VERSION = 1;
391
+ var DEFAULT_INDEX_UPDATE_RETRIES = 4;
392
+ var DEFAULT_POLL_INTERVAL_MS = 500;
393
+ function cloneRecord(record) {
394
+ return structuredClone(record);
395
+ }
396
+ function toStoredRecord(record) {
397
+ return {
398
+ kind: STORED_KIND,
399
+ version: STORED_VERSION,
400
+ record
401
+ };
402
+ }
403
+ function fromStoredRecord(value) {
404
+ if (!value || typeof value !== "object") {
405
+ return void 0;
406
+ }
407
+ const envelope = value;
408
+ if (envelope.kind !== STORED_KIND || envelope.version !== STORED_VERSION || !envelope.record) {
409
+ return void 0;
410
+ }
411
+ return cloneRecord(envelope.record);
412
+ }
413
+ function normalizeStatuses(status) {
414
+ if (!status) {
415
+ return void 0;
416
+ }
417
+ return new Set(Array.isArray(status) ? status : [status]);
418
+ }
419
+ function createDaprDispatchRecordWriter(options) {
420
+ const client = new DaprSidecarClient(options);
421
+ const agentId = ensureNonEmpty3(options.agentId, "agentId");
422
+ const keyPrefix = ensureNonEmpty3(
423
+ options.keyPrefix ?? DEFAULT_KEY_PREFIX,
424
+ "keyPrefix"
425
+ );
426
+ function recordKey(id) {
427
+ return `${keyPrefix}records/${agentId}/${id}`;
428
+ }
429
+ function globalIndexKey() {
430
+ return `${keyPrefix}record-index/${agentId}`;
431
+ }
432
+ function sessionIndexKey(sessionId) {
433
+ return `${keyPrefix}session-index/${agentId}/${sessionId}`;
434
+ }
435
+ async function readIndex(key) {
436
+ const entry = await client.getStateEntry(key);
437
+ if (entry.value === void 0) {
438
+ return { exists: false };
439
+ }
440
+ if (!Array.isArray(entry.value)) {
441
+ return { ids: [], etag: entry.etag, exists: true };
442
+ }
443
+ return {
444
+ ids: [
445
+ ...new Set(
446
+ entry.value.filter(
447
+ (item) => typeof item === "string"
448
+ )
449
+ )
450
+ ],
451
+ etag: entry.etag,
452
+ exists: true
453
+ };
454
+ }
455
+ async function writeIndex(key, ids, etag) {
456
+ await client.saveState(key, [...new Set(ids)], {
457
+ ...etag ? { etag } : {},
458
+ concurrency: etag ? "first-write" : void 0
459
+ });
460
+ }
461
+ async function updateIndex(key, updater) {
462
+ for (let attempt = 0; attempt < DEFAULT_INDEX_UPDATE_RETRIES; attempt += 1) {
463
+ const current = await readIndex(key);
464
+ const next = updater(current.ids ?? []);
465
+ if (!next) {
466
+ return;
467
+ }
468
+ try {
469
+ await writeIndex(key, next, current.etag);
470
+ return;
471
+ } catch (error) {
472
+ if (isDaprConflictError(error) && attempt + 1 < DEFAULT_INDEX_UPDATE_RETRIES) {
473
+ continue;
474
+ }
475
+ throw error;
476
+ }
477
+ }
478
+ }
479
+ async function addRecordToIndex(key, id) {
480
+ await updateIndex(key, (ids) => {
481
+ if (ids.includes(id)) {
482
+ return void 0;
483
+ }
484
+ return [...ids, id];
485
+ });
486
+ }
487
+ return {
488
+ async saveStartedRecord(record) {
489
+ await client.saveState(recordKey(record.id), toStoredRecord(record), {
490
+ concurrency: "first-write"
491
+ });
492
+ await addRecordToIndex(globalIndexKey(), record.id);
493
+ if (record.parentSessionId) {
494
+ await addRecordToIndex(
495
+ sessionIndexKey(record.parentSessionId),
496
+ record.id
497
+ );
498
+ }
499
+ }
500
+ };
501
+ }
497
502
  function createDaprDispatchRuntime(options) {
498
503
  const client = new DaprSidecarClient(options);
499
- const agentId = ensureNonEmpty2(options.agentId, "agentId");
500
- const keyPrefix = ensureNonEmpty2(
504
+ const agentId = ensureNonEmpty3(options.agentId, "agentId");
505
+ const keyPrefix = ensureNonEmpty3(
501
506
  options.keyPrefix ?? DEFAULT_KEY_PREFIX,
502
507
  "keyPrefix"
503
508
  );
@@ -677,7 +682,7 @@ function createDaprDispatchRuntime(options) {
677
682
  },
678
683
  async start(input) {
679
684
  const target = targets.get(
680
- ensureNonEmpty2(input.targetType, "targetType")
685
+ ensureNonEmpty3(input.targetType, "targetType")
681
686
  );
682
687
  if (!target) {
683
688
  throw new Error(`Unknown dispatch target "${input.targetType}".`);
@@ -685,7 +690,7 @@ function createDaprDispatchRuntime(options) {
685
690
  const id = createId();
686
691
  const startedAt = now();
687
692
  const title = input.title?.trim() || `Dispatch: ${target.name}`;
688
- const brief = ensureNonEmpty2(input.brief, "brief");
693
+ const brief = ensureNonEmpty3(input.brief, "brief");
689
694
  const launched = await target.start({
690
695
  id,
691
696
  brief,
@@ -736,7 +741,7 @@ function createDaprDispatchRuntime(options) {
736
741
  `No target registered for dispatch target "${current.targetType}".`
737
742
  );
738
743
  }
739
- const nextMessage = ensureNonEmpty2(message, "message");
744
+ const nextMessage = ensureNonEmpty3(message, "message");
740
745
  await target.redirect({
741
746
  record: cloneRecord(current),
742
747
  message: nextMessage
@@ -992,9 +997,9 @@ function createDaprCompositeDispatchExecutor(options) {
992
997
  export {
993
998
  DaprServiceInvoker,
994
999
  invokeRemoteAgentRun,
995
- createDaprDispatchRecordWriter,
996
1000
  createWorkflowDispatchTarget,
997
1001
  createRemoteAgentDispatchTarget,
1002
+ createDaprDispatchRecordWriter,
998
1003
  createDaprDispatchRuntime,
999
1004
  readWorkflowCompletion,
1000
1005
  createDaprWorkflowDispatchExecutor,