zeitlich 0.2.45 → 0.2.46

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.
Files changed (89) hide show
  1. package/README.md +78 -10
  2. package/dist/{activities-CrN-ghLo.d.ts → activities-Bm4TLTid.d.ts} +22 -2
  3. package/dist/{activities-Coafq5zr.d.cts → activities-CyeiqK_f.d.cts} +22 -2
  4. package/dist/adapters/thread/anthropic/index.cjs +171 -65
  5. package/dist/adapters/thread/anthropic/index.cjs.map +1 -1
  6. package/dist/adapters/thread/anthropic/index.d.cts +19 -4
  7. package/dist/adapters/thread/anthropic/index.d.ts +19 -4
  8. package/dist/adapters/thread/anthropic/index.js +171 -65
  9. package/dist/adapters/thread/anthropic/index.js.map +1 -1
  10. package/dist/adapters/thread/anthropic/workflow.cjs +3 -1
  11. package/dist/adapters/thread/anthropic/workflow.cjs.map +1 -1
  12. package/dist/adapters/thread/anthropic/workflow.d.cts +4 -4
  13. package/dist/adapters/thread/anthropic/workflow.d.ts +4 -4
  14. package/dist/adapters/thread/anthropic/workflow.js +3 -1
  15. package/dist/adapters/thread/anthropic/workflow.js.map +1 -1
  16. package/dist/adapters/thread/google-genai/index.cjs +171 -69
  17. package/dist/adapters/thread/google-genai/index.cjs.map +1 -1
  18. package/dist/adapters/thread/google-genai/index.d.cts +5 -4
  19. package/dist/adapters/thread/google-genai/index.d.ts +5 -4
  20. package/dist/adapters/thread/google-genai/index.js +171 -69
  21. package/dist/adapters/thread/google-genai/index.js.map +1 -1
  22. package/dist/adapters/thread/google-genai/workflow.cjs +3 -1
  23. package/dist/adapters/thread/google-genai/workflow.cjs.map +1 -1
  24. package/dist/adapters/thread/google-genai/workflow.d.cts +5 -4
  25. package/dist/adapters/thread/google-genai/workflow.d.ts +5 -4
  26. package/dist/adapters/thread/google-genai/workflow.js +3 -1
  27. package/dist/adapters/thread/google-genai/workflow.js.map +1 -1
  28. package/dist/adapters/thread/langchain/index.cjs +170 -66
  29. package/dist/adapters/thread/langchain/index.cjs.map +1 -1
  30. package/dist/adapters/thread/langchain/index.d.cts +18 -4
  31. package/dist/adapters/thread/langchain/index.d.ts +18 -4
  32. package/dist/adapters/thread/langchain/index.js +170 -66
  33. package/dist/adapters/thread/langchain/index.js.map +1 -1
  34. package/dist/adapters/thread/langchain/workflow.cjs +3 -1
  35. package/dist/adapters/thread/langchain/workflow.cjs.map +1 -1
  36. package/dist/adapters/thread/langchain/workflow.d.cts +4 -4
  37. package/dist/adapters/thread/langchain/workflow.d.ts +4 -4
  38. package/dist/adapters/thread/langchain/workflow.js +3 -1
  39. package/dist/adapters/thread/langchain/workflow.js.map +1 -1
  40. package/dist/cold-store-BC5L5Z8A.d.cts +117 -0
  41. package/dist/cold-store-CFHwemBJ.d.ts +117 -0
  42. package/dist/index.cjs +226 -27
  43. package/dist/index.cjs.map +1 -1
  44. package/dist/index.d.cts +138 -8
  45. package/dist/index.d.ts +138 -8
  46. package/dist/index.js +220 -28
  47. package/dist/index.js.map +1 -1
  48. package/dist/{proxy-Bf7uI-Hw.d.cts → proxy-BxFyd6cg.d.cts} +1 -1
  49. package/dist/{proxy-COqA95FW.d.ts → proxy-Cskmj4Yx.d.ts} +1 -1
  50. package/dist/{thread-manager-BsLO3Fgc.d.cts → thread-manager-9tezUcLW.d.cts} +8 -2
  51. package/dist/{thread-manager-Bi1XlbpJ.d.ts → thread-manager-B-zy3xrs.d.ts} +8 -2
  52. package/dist/{thread-manager-wRVVBFgj.d.cts → thread-manager-D33SUmZa.d.cts} +8 -2
  53. package/dist/{thread-manager-BhkOyQ1I.d.ts → thread-manager-DduoSkvJ.d.ts} +8 -2
  54. package/dist/{types-CdALEF3z.d.cts → types-CnuN9T6t.d.cts} +22 -0
  55. package/dist/{types-ChAy_jSP.d.ts → types-CwN6_tAL.d.ts} +22 -0
  56. package/dist/{types-BkX4HLzi.d.ts → types-L5bvbF-n.d.ts} +17 -1
  57. package/dist/{types-C66-BVBr.d.cts → types-oxt8GN97.d.cts} +17 -1
  58. package/dist/{workflow-BwT5EybR.d.ts → workflow-B1TOcHbt.d.ts} +33 -2
  59. package/dist/{workflow-DMmiaw6w.d.cts → workflow-DIaIV7L2.d.cts} +33 -2
  60. package/dist/workflow.cjs +14 -1
  61. package/dist/workflow.cjs.map +1 -1
  62. package/dist/workflow.d.cts +2 -2
  63. package/dist/workflow.d.ts +2 -2
  64. package/dist/workflow.js +14 -1
  65. package/dist/workflow.js.map +1 -1
  66. package/package.json +6 -1
  67. package/src/adapters/thread/anthropic/activities.ts +72 -36
  68. package/src/adapters/thread/anthropic/thread-manager.ts +9 -1
  69. package/src/adapters/thread/google-genai/activities.ts +64 -40
  70. package/src/adapters/thread/google-genai/thread-manager.ts +9 -1
  71. package/src/adapters/thread/langchain/activities.ts +63 -36
  72. package/src/adapters/thread/langchain/thread-manager.ts +9 -1
  73. package/src/index.ts +20 -1
  74. package/src/lib/session/session-edge-cases.integration.test.ts +12 -0
  75. package/src/lib/session/session.integration.test.ts +138 -0
  76. package/src/lib/session/session.ts +29 -0
  77. package/src/lib/session/types.ts +22 -0
  78. package/src/lib/thread/cold-store.test.ts +193 -0
  79. package/src/lib/thread/cold-store.ts +250 -0
  80. package/src/lib/thread/index.ts +32 -0
  81. package/src/lib/thread/keys.ts +20 -0
  82. package/src/lib/thread/manager.ts +16 -27
  83. package/src/lib/thread/proxy.ts +2 -0
  84. package/src/lib/thread/snapshot.test.ts +443 -0
  85. package/src/lib/thread/snapshot.ts +163 -0
  86. package/src/lib/thread/test-utils.ts +228 -0
  87. package/src/lib/thread/tiered.test.ts +281 -0
  88. package/src/lib/thread/tiered.ts +135 -0
  89. package/src/lib/thread/types.ts +16 -0
@@ -16,6 +16,9 @@ function getThreadMetaKey(threadKey, threadId) {
16
16
  function getThreadStateKey(threadKey, threadId) {
17
17
  return `${threadKey}:state:thread:${threadId}`;
18
18
  }
19
+ function getThreadDedupKey(threadId, dedupId) {
20
+ return `dedup:${dedupId}:thread:${threadId}`;
21
+ }
19
22
 
20
23
  // src/lib/thread/manager.ts
21
24
  var APPEND_IDEMPOTENT_SCRIPT = `
@@ -29,9 +32,6 @@ redis.call('EXPIRE', KEYS[2], tonumber(ARGV[1]))
29
32
  redis.call('SET', KEYS[1], '1', 'EX', tonumber(ARGV[1]))
30
33
  return 1
31
34
  `;
32
- function getDedupKey(threadId, id) {
33
- return `dedup:${id}:thread:${threadId}`;
34
- }
35
35
  function createThreadManager(config) {
36
36
  const {
37
37
  redis,
@@ -39,11 +39,13 @@ function createThreadManager(config) {
39
39
  key = "messages",
40
40
  serialize = (m) => JSON.stringify(m),
41
41
  deserialize = (raw) => JSON.parse(raw),
42
- idOf
42
+ idOf,
43
+ ttlSeconds = THREAD_TTL_SECONDS
43
44
  } = config;
44
45
  const redisKey = getThreadListKey(key, threadId);
45
46
  const metaKey = getThreadMetaKey(key, threadId);
46
47
  const stateKey = getThreadStateKey(key, threadId);
48
+ const dedupKey = (id) => getThreadDedupKey(threadId, id);
47
49
  async function assertThreadExists() {
48
50
  const exists = await redis.exists(metaKey);
49
51
  if (!exists) {
@@ -53,7 +55,7 @@ function createThreadManager(config) {
53
55
  return {
54
56
  async initialize() {
55
57
  await redis.del(redisKey);
56
- await redis.set(metaKey, "1", "EX", THREAD_TTL_SECONDS);
58
+ await redis.set(metaKey, "1", "EX", ttlSeconds);
57
59
  },
58
60
  async load() {
59
61
  await assertThreadExists();
@@ -65,18 +67,17 @@ function createThreadManager(config) {
65
67
  await assertThreadExists();
66
68
  if (idOf) {
67
69
  const dedupId = messages.map(idOf).join(":");
68
- const dedupKey = getDedupKey(threadId, dedupId);
69
70
  await redis.eval(
70
71
  APPEND_IDEMPOTENT_SCRIPT,
71
72
  2,
72
- dedupKey,
73
+ dedupKey(dedupId),
73
74
  redisKey,
74
- String(THREAD_TTL_SECONDS),
75
+ String(ttlSeconds),
75
76
  ...messages.map(serialize)
76
77
  );
77
78
  } else {
78
79
  await redis.rpush(redisKey, ...messages.map(serialize));
79
- await redis.expire(redisKey, THREAD_TTL_SECONDS);
80
+ await redis.expire(redisKey, ttlSeconds);
80
81
  }
81
82
  },
82
83
  async fork(newThreadId) {
@@ -91,11 +92,11 @@ function createThreadManager(config) {
91
92
  if (data.length > 0) {
92
93
  const newKey = getThreadListKey(key, newThreadId);
93
94
  await redis.rpush(newKey, ...data);
94
- await redis.expire(newKey, THREAD_TTL_SECONDS);
95
+ await redis.expire(newKey, ttlSeconds);
95
96
  }
96
97
  if (stateRaw != null) {
97
98
  const newStateKey = getThreadStateKey(key, newThreadId);
98
- await redis.set(newStateKey, stateRaw, "EX", THREAD_TTL_SECONDS);
99
+ await redis.set(newStateKey, stateRaw, "EX", ttlSeconds);
99
100
  }
100
101
  return forked;
101
102
  },
@@ -110,15 +111,13 @@ function createThreadManager(config) {
110
111
  const existingIds = existing.map((raw) => idOf(deserialize(raw))).filter((id) => typeof id === "string");
111
112
  await redis.del(redisKey);
112
113
  if (existingIds.length > 0) {
113
- await redis.del(
114
- ...existingIds.map((id) => getDedupKey(threadId, id))
115
- );
114
+ await redis.del(...existingIds.map(dedupKey));
116
115
  }
117
116
  if (messages.length > 0) {
118
117
  await redis.rpush(redisKey, ...messages.map(serialize));
119
- await redis.expire(redisKey, THREAD_TTL_SECONDS);
118
+ await redis.expire(redisKey, ttlSeconds);
120
119
  }
121
- await redis.expire(metaKey, THREAD_TTL_SECONDS);
120
+ await redis.expire(metaKey, ttlSeconds);
122
121
  },
123
122
  async delete() {
124
123
  await redis.del(redisKey, metaKey, stateKey);
@@ -130,12 +129,7 @@ function createThreadManager(config) {
130
129
  },
131
130
  async saveState(state) {
132
131
  await assertThreadExists();
133
- await redis.set(
134
- stateKey,
135
- JSON.stringify(state),
136
- "EX",
137
- THREAD_TTL_SECONDS
138
- );
132
+ await redis.set(stateKey, JSON.stringify(state), "EX", ttlSeconds);
139
133
  },
140
134
  async deleteState() {
141
135
  await redis.del(stateKey);
@@ -164,20 +158,133 @@ function createThreadManager(config) {
164
158
  if (idx === -1) return;
165
159
  if (idx === 0) {
166
160
  await redis.del(redisKey);
167
- await redis.expire(metaKey, THREAD_TTL_SECONDS);
161
+ await redis.expire(metaKey, ttlSeconds);
168
162
  } else {
169
163
  await redis.ltrim(redisKey, 0, idx - 1);
170
- await redis.expire(redisKey, THREAD_TTL_SECONDS);
164
+ await redis.expire(redisKey, ttlSeconds);
171
165
  }
172
166
  if (removedIds.length > 0) {
173
- await redis.del(
174
- ...removedIds.map((id) => getDedupKey(threadId, id))
175
- );
167
+ await redis.del(...removedIds.map(dedupKey));
176
168
  }
177
169
  }
178
170
  };
179
171
  }
180
172
 
173
+ // src/lib/thread/snapshot.ts
174
+ async function encodeSnapshot(config) {
175
+ const { redis, threadKey, threadId, idOf } = config;
176
+ const metaKey = getThreadMetaKey(threadKey, threadId);
177
+ if (await redis.exists(metaKey) === 0) {
178
+ return null;
179
+ }
180
+ const listKey = getThreadListKey(threadKey, threadId);
181
+ const stateKey = getThreadStateKey(threadKey, threadId);
182
+ const messages = await redis.lrange(listKey, 0, -1);
183
+ const stateRaw = await redis.get(stateKey);
184
+ const state = stateRaw == null ? null : JSON.parse(stateRaw);
185
+ const dedupIds = idOf ? messages.map(idOf) : [];
186
+ return { v: 1, messages, state, dedupIds };
187
+ }
188
+ async function applySnapshot(config) {
189
+ const {
190
+ redis,
191
+ threadKey,
192
+ threadId,
193
+ snapshot,
194
+ ttlSeconds = THREAD_TTL_SECONDS
195
+ } = config;
196
+ const metaKey = getThreadMetaKey(threadKey, threadId);
197
+ if (await redis.exists(metaKey) === 1) {
198
+ return;
199
+ }
200
+ const listKey = getThreadListKey(threadKey, threadId);
201
+ const stateKey = getThreadStateKey(threadKey, threadId);
202
+ await redis.del(listKey, stateKey);
203
+ const pipeline = redis.pipeline();
204
+ if (snapshot.messages.length > 0) {
205
+ pipeline.rpush(listKey, ...snapshot.messages);
206
+ pipeline.expire(listKey, ttlSeconds);
207
+ }
208
+ if (snapshot.state != null) {
209
+ pipeline.set(stateKey, JSON.stringify(snapshot.state), "EX", ttlSeconds);
210
+ }
211
+ for (const id of snapshot.dedupIds) {
212
+ pipeline.set(getThreadDedupKey(threadId, id), "1", "EX", ttlSeconds);
213
+ }
214
+ const results = await pipeline.exec();
215
+ if (results) {
216
+ const firstErr = results.find(([err]) => err)?.[0] ?? null;
217
+ if (firstErr) {
218
+ await redis.del(
219
+ listKey,
220
+ stateKey,
221
+ ...snapshot.dedupIds.map((id) => getThreadDedupKey(threadId, id))
222
+ ).catch(() => void 0);
223
+ throw firstErr;
224
+ }
225
+ }
226
+ await redis.set(metaKey, "1", "EX", ttlSeconds);
227
+ }
228
+ async function clearHotTier(config) {
229
+ const { redis, threadKey, threadId, dedupIds = [] } = config;
230
+ const keys = [
231
+ getThreadListKey(threadKey, threadId),
232
+ getThreadMetaKey(threadKey, threadId),
233
+ getThreadStateKey(threadKey, threadId),
234
+ ...dedupIds.map((id) => getThreadDedupKey(threadId, id))
235
+ ];
236
+ await redis.del(...keys);
237
+ }
238
+
239
+ // src/lib/thread/tiered.ts
240
+ function createTieredThreadManager(config) {
241
+ const {
242
+ redis,
243
+ threadId,
244
+ key = "messages",
245
+ coldStore,
246
+ idOf,
247
+ deserialize = (raw) => JSON.parse(raw),
248
+ ttlSeconds = THREAD_TTL_SECONDS
249
+ } = config;
250
+ const base = createThreadManager(config);
251
+ const rawIdOf = idOf ? (raw) => idOf(deserialize(raw)) : void 0;
252
+ return Object.assign(base, {
253
+ async hydrate() {
254
+ if (!coldStore) return;
255
+ const snapshot = await coldStore.read(key, threadId);
256
+ if (!snapshot) return;
257
+ await applySnapshot({
258
+ redis,
259
+ threadKey: key,
260
+ threadId,
261
+ snapshot,
262
+ ttlSeconds
263
+ });
264
+ },
265
+ async flush(opts) {
266
+ if (!coldStore) return;
267
+ const snapshot = await encodeSnapshot({
268
+ redis,
269
+ threadKey: key,
270
+ threadId,
271
+ ...rawIdOf ? { idOf: rawIdOf } : {}
272
+ });
273
+ if (!snapshot) return;
274
+ await coldStore.write(key, threadId, snapshot);
275
+ const deleteHot = opts?.deleteHot ?? true;
276
+ if (deleteHot) {
277
+ await clearHotTier({
278
+ redis,
279
+ threadKey: key,
280
+ threadId,
281
+ dedupIds: snapshot.dedupIds
282
+ });
283
+ }
284
+ }
285
+ });
286
+ }
287
+
181
288
  // src/adapters/thread/google-genai/thread-manager.ts
182
289
  function storedContentId(msg) {
183
290
  return msg.id;
@@ -211,7 +318,8 @@ function createGoogleGenAIThreadManager(config) {
211
318
  redis: config.redis,
212
319
  threadId: config.threadId,
213
320
  key: config.key,
214
- idOf: storedContentId
321
+ idOf: storedContentId,
322
+ ...config.ttlSeconds !== void 0 && { ttlSeconds: config.ttlSeconds }
215
323
  };
216
324
  const base = createThreadManager(baseConfig);
217
325
  const helpers = {
@@ -393,38 +501,39 @@ async function invokeGoogleGenAIModel({
393
501
  // src/adapters/thread/google-genai/activities.ts
394
502
  function createGoogleGenAIAdapter(config) {
395
503
  const { redis } = config;
504
+ const baseExtras = {
505
+ ...config.ttlSeconds !== void 0 && { ttlSeconds: config.ttlSeconds }
506
+ };
507
+ const makeProviderThread = (threadId, threadKey) => createGoogleGenAIThreadManager({
508
+ redis,
509
+ threadId,
510
+ key: threadKey,
511
+ ...baseExtras
512
+ });
513
+ const makeTieredBase = (threadId, threadKey) => createTieredThreadManager({
514
+ redis,
515
+ threadId,
516
+ key: threadKey,
517
+ idOf: storedContentId,
518
+ ...baseExtras,
519
+ ...config.coldStore && { coldStore: config.coldStore }
520
+ });
396
521
  const threadOps = {
397
522
  async initializeThread(threadId, threadKey) {
398
- const thread = createGoogleGenAIThreadManager({
399
- redis,
400
- threadId,
401
- key: threadKey
402
- });
523
+ const thread = makeProviderThread(threadId, threadKey);
403
524
  await thread.initialize();
404
525
  },
405
526
  async appendHumanMessage(threadId, id, content, threadKey) {
406
- const thread = createGoogleGenAIThreadManager({
407
- redis,
408
- threadId,
409
- key: threadKey
410
- });
527
+ const thread = makeProviderThread(threadId, threadKey);
411
528
  await thread.appendUserMessage(id, content);
412
529
  },
413
530
  async appendSystemMessage(threadId, id, content, threadKey) {
414
- const thread = createGoogleGenAIThreadManager({
415
- redis,
416
- threadId,
417
- key: threadKey
418
- });
531
+ const thread = makeProviderThread(threadId, threadKey);
419
532
  await thread.appendSystemMessage(id, content);
420
533
  },
421
534
  async appendToolResult(id, cfg) {
422
535
  const { threadId, threadKey, toolCallId, toolName, content } = cfg;
423
- const thread = createGoogleGenAIThreadManager({
424
- redis,
425
- threadId,
426
- key: threadKey
427
- });
536
+ const thread = makeProviderThread(threadId, threadKey);
428
537
  await thread.appendToolResult(
429
538
  id,
430
539
  toolCallId,
@@ -433,11 +542,7 @@ function createGoogleGenAIAdapter(config) {
433
542
  );
434
543
  },
435
544
  async appendAgentMessage(threadId, id, message, threadKey) {
436
- const thread = createGoogleGenAIThreadManager({
437
- redis,
438
- threadId,
439
- key: threadKey
440
- });
545
+ const thread = makeProviderThread(threadId, threadKey);
441
546
  await thread.appendModelContent(id, message.parts ?? []);
442
547
  },
443
548
  async forkThread(sourceThreadId, targetThreadId, threadKey) {
@@ -445,33 +550,30 @@ function createGoogleGenAIAdapter(config) {
445
550
  redis,
446
551
  threadId: sourceThreadId,
447
552
  key: threadKey,
448
- hooks: config.hooks
553
+ hooks: config.hooks,
554
+ ...baseExtras
449
555
  });
450
556
  await thread.fork(targetThreadId);
451
557
  },
452
558
  async truncateThread(threadId, messageId, threadKey) {
453
- const thread = createGoogleGenAIThreadManager({
454
- redis,
455
- threadId,
456
- key: threadKey
457
- });
559
+ const thread = makeProviderThread(threadId, threadKey);
458
560
  await thread.truncateFromId(messageId);
459
561
  },
460
562
  async loadThreadState(threadId, threadKey) {
461
- const thread = createGoogleGenAIThreadManager({
462
- redis,
463
- threadId,
464
- key: threadKey
465
- });
563
+ const thread = makeProviderThread(threadId, threadKey);
466
564
  return thread.loadState();
467
565
  },
468
566
  async saveThreadState(threadId, state, threadKey) {
469
- const thread = createGoogleGenAIThreadManager({
470
- redis,
471
- threadId,
472
- key: threadKey
473
- });
567
+ const thread = makeProviderThread(threadId, threadKey);
474
568
  await thread.saveState(state);
569
+ },
570
+ async hydrateThread(threadId, threadKey) {
571
+ if (!config.coldStore) return;
572
+ await makeTieredBase(threadId, threadKey).hydrate();
573
+ },
574
+ async flushThread(threadId, threadKey) {
575
+ if (!config.coldStore) return;
576
+ await makeTieredBase(threadId, threadKey).flush();
475
577
  }
476
578
  };
477
579
  function createActivities(scope) {