zeitlich 0.2.48 → 0.2.50

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 (126) hide show
  1. package/README.md +26 -23
  2. package/dist/{activities-DCaIPQBT.d.ts → activities-IuOIvPHO.d.ts} +6 -6
  3. package/dist/{activities-BlQR5gX4.d.cts → activities-cIlq1y1y.d.cts} +6 -6
  4. package/dist/adapters/sandbox/daytona/index.cjs.map +1 -1
  5. package/dist/adapters/sandbox/daytona/index.d.cts +3 -3
  6. package/dist/adapters/sandbox/daytona/index.d.ts +3 -3
  7. package/dist/adapters/sandbox/daytona/index.js.map +1 -1
  8. package/dist/adapters/sandbox/daytona/workflow.d.cts +2 -2
  9. package/dist/adapters/sandbox/daytona/workflow.d.ts +2 -2
  10. package/dist/adapters/sandbox/e2b/index.cjs.map +1 -1
  11. package/dist/adapters/sandbox/e2b/index.d.cts +1 -1
  12. package/dist/adapters/sandbox/e2b/index.d.ts +1 -1
  13. package/dist/adapters/sandbox/e2b/index.js.map +1 -1
  14. package/dist/adapters/sandbox/e2b/workflow.d.cts +1 -1
  15. package/dist/adapters/sandbox/e2b/workflow.d.ts +1 -1
  16. package/dist/adapters/thread/anthropic/index.cjs +45 -42
  17. package/dist/adapters/thread/anthropic/index.cjs.map +1 -1
  18. package/dist/adapters/thread/anthropic/index.d.cts +10 -10
  19. package/dist/adapters/thread/anthropic/index.d.ts +10 -10
  20. package/dist/adapters/thread/anthropic/index.js +45 -42
  21. package/dist/adapters/thread/anthropic/index.js.map +1 -1
  22. package/dist/adapters/thread/anthropic/workflow.d.cts +7 -7
  23. package/dist/adapters/thread/anthropic/workflow.d.ts +7 -7
  24. package/dist/adapters/thread/google-genai/index.cjs +117 -54
  25. package/dist/adapters/thread/google-genai/index.cjs.map +1 -1
  26. package/dist/adapters/thread/google-genai/index.d.cts +27 -23
  27. package/dist/adapters/thread/google-genai/index.d.ts +27 -23
  28. package/dist/adapters/thread/google-genai/index.js +117 -54
  29. package/dist/adapters/thread/google-genai/index.js.map +1 -1
  30. package/dist/adapters/thread/google-genai/workflow.d.cts +8 -8
  31. package/dist/adapters/thread/google-genai/workflow.d.ts +8 -8
  32. package/dist/adapters/thread/langchain/index.cjs +45 -42
  33. package/dist/adapters/thread/langchain/index.cjs.map +1 -1
  34. package/dist/adapters/thread/langchain/index.d.cts +10 -10
  35. package/dist/adapters/thread/langchain/index.d.ts +10 -10
  36. package/dist/adapters/thread/langchain/index.js +45 -42
  37. package/dist/adapters/thread/langchain/index.js.map +1 -1
  38. package/dist/adapters/thread/langchain/workflow.d.cts +7 -7
  39. package/dist/adapters/thread/langchain/workflow.d.ts +7 -7
  40. package/dist/{cold-store-UL13Sstw.d.cts → cold-store-C0uvYTSi.d.cts} +1 -1
  41. package/dist/{cold-store-aD4TSKlU.d.ts → cold-store-CCnZYWjx.d.ts} +1 -1
  42. package/dist/index.cjs +15063 -405
  43. package/dist/index.cjs.map +1 -1
  44. package/dist/index.d.cts +79 -83
  45. package/dist/index.d.ts +79 -83
  46. package/dist/index.js +15064 -402
  47. package/dist/index.js.map +1 -1
  48. package/dist/{proxy-BAty3CWM.d.cts → proxy-BVznA2_p.d.cts} +1 -1
  49. package/dist/{proxy-mbnwBhHw.d.ts → proxy-C4J1pNUk.d.ts} +1 -1
  50. package/dist/{thread-manager-CICj68PI.d.ts → thread-manager-BqjzWsP7.d.ts} +4 -4
  51. package/dist/{thread-manager-R6c3lnJy.d.cts → thread-manager-CzIs47uG.d.cts} +4 -4
  52. package/dist/{thread-manager-DsXvJ5cJ.d.cts → thread-manager-Dzl1fHhV.d.cts} +4 -4
  53. package/dist/{thread-manager-DtEtbUkp.d.ts → thread-manager-SkSWRPRc.d.ts} +4 -4
  54. package/dist/{types-gVa5XCWD.d.ts → types-BQvXWcft.d.ts} +1 -1
  55. package/dist/{types-DF4wzWQG.d.ts → types-CbPnU4RM.d.ts} +3 -3
  56. package/dist/{types-CJ7tCdl6.d.cts → types-D8W5TnSa.d.cts} +3 -3
  57. package/dist/{types-CJ7tCdl6.d.ts → types-D8W5TnSa.d.ts} +3 -3
  58. package/dist/{types-DwBYd0ij.d.ts → types-DZnUqCAP.d.cts} +709 -686
  59. package/dist/{types-CjY93AWZ.d.cts → types-OEN1xrFg.d.cts} +1 -1
  60. package/dist/{types-DWeyCTYK.d.cts → types-YNesmGKV.d.ts} +709 -686
  61. package/dist/{types-DDLPnxBh.d.cts → types-d2RvEP6v.d.cts} +3 -3
  62. package/dist/{workflow-DdaU7_j4.d.ts → workflow-B3oTe2_D.d.cts} +34 -3
  63. package/dist/{workflow-DVNPR7eX.d.cts → workflow-Bkzg0cjB.d.ts} +34 -3
  64. package/dist/workflow.cjs +15021 -362
  65. package/dist/workflow.cjs.map +1 -1
  66. package/dist/workflow.d.cts +3 -3
  67. package/dist/workflow.d.ts +3 -3
  68. package/dist/workflow.js +15022 -359
  69. package/dist/workflow.js.map +1 -1
  70. package/package.json +10 -37
  71. package/src/adapters/thread/anthropic/activities.ts +1 -1
  72. package/src/adapters/thread/anthropic/fork-transform.test.ts +17 -11
  73. package/src/adapters/thread/anthropic/model-invoker.test.ts +4 -3
  74. package/src/adapters/thread/anthropic/model-invoker.ts +1 -1
  75. package/src/adapters/thread/anthropic/thread-manager.test.ts +2 -2
  76. package/src/adapters/thread/anthropic/thread-manager.ts +1 -1
  77. package/src/adapters/thread/google-genai/activities.ts +1 -1
  78. package/src/adapters/thread/google-genai/fork-transform.test.ts +17 -11
  79. package/src/adapters/thread/google-genai/model-invoker.test.ts +337 -0
  80. package/src/adapters/thread/google-genai/model-invoker.ts +107 -23
  81. package/src/adapters/thread/google-genai/thread-manager.test.ts +2 -2
  82. package/src/adapters/thread/google-genai/thread-manager.ts +1 -1
  83. package/src/adapters/thread/langchain/activities.ts +1 -1
  84. package/src/adapters/thread/langchain/fork-transform.test.ts +17 -11
  85. package/src/adapters/thread/langchain/model-invoker.ts +1 -1
  86. package/src/adapters/thread/langchain/thread-manager.test.ts +2 -2
  87. package/src/adapters/thread/langchain/thread-manager.ts +1 -1
  88. package/src/index.ts +2 -2
  89. package/src/lib/sandbox/capability-types.test.ts +2 -2
  90. package/src/lib/sandbox/manager.ts +2 -6
  91. package/src/lib/sandbox/sandbox.test.ts +1 -1
  92. package/src/lib/sandbox/types.ts +2 -2
  93. package/src/lib/session/session.integration.test.ts +92 -0
  94. package/src/lib/session/session.ts +23 -0
  95. package/src/lib/subagent/handler.ts +23 -0
  96. package/src/lib/subagent/subagent.integration.test.ts +198 -0
  97. package/src/lib/thread/keys.test.ts +9 -9
  98. package/src/lib/thread/keys.ts +1 -1
  99. package/src/lib/thread/manager.test.ts +24 -14
  100. package/src/lib/thread/manager.ts +19 -23
  101. package/src/lib/thread/snapshot.test.ts +51 -43
  102. package/src/lib/thread/snapshot.ts +54 -32
  103. package/src/lib/thread/test-utils.ts +106 -59
  104. package/src/lib/thread/tiered.test.ts +1 -1
  105. package/src/lib/thread/types.ts +2 -2
  106. package/src/lib/tool-router/router.integration.test.ts +44 -0
  107. package/src/lib/tool-router/router.ts +149 -33
  108. package/src/lib/tool-router/types.ts +23 -0
  109. package/src/lib/workflow.ts +49 -0
  110. package/src/{adapters/sandbox/inmemory/proxy.ts → test-utils/in-memory-sandbox-proxy.ts} +5 -16
  111. package/src/{adapters/sandbox/inmemory/index.ts → test-utils/in-memory-sandbox.ts} +11 -3
  112. package/src/tools/bash/bash.test.ts +1 -1
  113. package/src/tools/edit/handler.test.ts +1 -1
  114. package/tsup.config.ts +2 -4
  115. package/dist/adapters/sandbox/inmemory/index.cjs +0 -214
  116. package/dist/adapters/sandbox/inmemory/index.cjs.map +0 -1
  117. package/dist/adapters/sandbox/inmemory/index.d.cts +0 -40
  118. package/dist/adapters/sandbox/inmemory/index.d.ts +0 -40
  119. package/dist/adapters/sandbox/inmemory/index.js +0 -211
  120. package/dist/adapters/sandbox/inmemory/index.js.map +0 -1
  121. package/dist/adapters/sandbox/inmemory/workflow.cjs +0 -36
  122. package/dist/adapters/sandbox/inmemory/workflow.cjs.map +0 -1
  123. package/dist/adapters/sandbox/inmemory/workflow.d.cts +0 -27
  124. package/dist/adapters/sandbox/inmemory/workflow.d.ts +0 -27
  125. package/dist/adapters/sandbox/inmemory/workflow.js +0 -34
  126. package/dist/adapters/sandbox/inmemory/workflow.js.map +0 -1
@@ -813,6 +813,204 @@ describe("createSubagentHandler", () => {
813
813
  expect(workflowInput.thread).toBeUndefined();
814
814
  });
815
815
 
816
+ // --- persistThreadState: parent slice flush before child loads ---
817
+
818
+ it("calls persistThreadState before executeChild when forking parent's thread", async () => {
819
+ const { executeChild } = await import("@temporalio/workflow");
820
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
821
+ const persistThreadState = vi.fn(async () => undefined);
822
+
823
+ const subagent: SubagentConfig = {
824
+ agentName: "parent-fork-persist",
825
+ description: "Forks parent thread",
826
+ workflow: mockWorkflow(),
827
+ thread: "fork",
828
+ newThreadSource: "from-parent",
829
+ };
830
+
831
+ const { handler } = createSubagentHandler([subagent]);
832
+
833
+ await handler(
834
+ {
835
+ subagent: "parent-fork-persist",
836
+ description: "test",
837
+ prompt: "test",
838
+ },
839
+ {
840
+ threadId: "parent-t",
841
+ toolCallId: "tc",
842
+ toolName: "Subagent",
843
+ persistThreadState,
844
+ }
845
+ );
846
+
847
+ expect(persistThreadState).toHaveBeenCalledTimes(1);
848
+ const lastExecOrder =
849
+ execMock.mock.invocationCallOrder[
850
+ execMock.mock.invocationCallOrder.length - 1
851
+ ] ?? Infinity;
852
+ expect(persistThreadState.mock.invocationCallOrder[0]).toBeLessThan(
853
+ lastExecOrder
854
+ );
855
+ });
856
+
857
+ it("calls persistThreadState before executeChild when continuing parent's thread", async () => {
858
+ const { executeChild } = await import("@temporalio/workflow");
859
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
860
+ const persistThreadState = vi.fn(async () => undefined);
861
+
862
+ const subagent: SubagentConfig = {
863
+ agentName: "parent-continue-persist",
864
+ description: "Continues parent thread",
865
+ workflow: mockWorkflow(),
866
+ thread: "continue",
867
+ newThreadSource: "from-parent",
868
+ };
869
+
870
+ const { handler } = createSubagentHandler([subagent]);
871
+
872
+ await handler(
873
+ {
874
+ subagent: "parent-continue-persist",
875
+ description: "test",
876
+ prompt: "test",
877
+ },
878
+ {
879
+ threadId: "parent-t",
880
+ toolCallId: "tc",
881
+ toolName: "Subagent",
882
+ persistThreadState,
883
+ }
884
+ );
885
+
886
+ expect(persistThreadState).toHaveBeenCalledTimes(1);
887
+ const lastExecOrder =
888
+ execMock.mock.invocationCallOrder[
889
+ execMock.mock.invocationCallOrder.length - 1
890
+ ] ?? Infinity;
891
+ expect(persistThreadState.mock.invocationCallOrder[0]).toBeLessThan(
892
+ lastExecOrder
893
+ );
894
+ });
895
+
896
+ it("calls persistThreadState when args.threadId points at the parent's thread", async () => {
897
+ const persistThreadState = vi.fn(async () => undefined);
898
+
899
+ const subagent: SubagentConfig = {
900
+ agentName: "explicit-parent",
901
+ description: "Explicit parent threadId",
902
+ workflow: mockWorkflow(),
903
+ thread: "continue",
904
+ };
905
+
906
+ const { handler } = createSubagentHandler([subagent]);
907
+
908
+ await handler(
909
+ {
910
+ subagent: "explicit-parent",
911
+ description: "test",
912
+ prompt: "test",
913
+ threadId: "parent-t",
914
+ },
915
+ {
916
+ threadId: "parent-t",
917
+ toolCallId: "tc",
918
+ toolName: "Subagent",
919
+ persistThreadState,
920
+ }
921
+ );
922
+
923
+ expect(persistThreadState).toHaveBeenCalledTimes(1);
924
+ });
925
+
926
+ it("does not call persistThreadState when child thread is independent of the parent", async () => {
927
+ const persistThreadState = vi.fn(async () => undefined);
928
+
929
+ const subagent: SubagentConfig = {
930
+ agentName: "independent-thread",
931
+ description: "Continues a sibling thread",
932
+ workflow: mockWorkflow(),
933
+ thread: "continue",
934
+ };
935
+
936
+ const { handler } = createSubagentHandler([subagent]);
937
+
938
+ await handler(
939
+ {
940
+ subagent: "independent-thread",
941
+ description: "test",
942
+ prompt: "test",
943
+ threadId: "sibling-thread",
944
+ },
945
+ {
946
+ threadId: "parent-t",
947
+ toolCallId: "tc",
948
+ toolName: "Subagent",
949
+ persistThreadState,
950
+ }
951
+ );
952
+
953
+ expect(persistThreadState).not.toHaveBeenCalled();
954
+ });
955
+
956
+ it("does not call persistThreadState when the child starts a fresh thread", async () => {
957
+ const persistThreadState = vi.fn(async () => undefined);
958
+
959
+ const subagent: SubagentConfig = {
960
+ agentName: "fresh-thread",
961
+ description: "Always starts fresh",
962
+ workflow: mockWorkflow(),
963
+ };
964
+
965
+ const { handler } = createSubagentHandler([subagent]);
966
+
967
+ await handler(
968
+ { subagent: "fresh-thread", description: "test", prompt: "test" },
969
+ {
970
+ threadId: "parent-t",
971
+ toolCallId: "tc",
972
+ toolName: "Subagent",
973
+ persistThreadState,
974
+ }
975
+ );
976
+
977
+ expect(persistThreadState).not.toHaveBeenCalled();
978
+ });
979
+
980
+ it("still spawns the child when persistThreadState throws", async () => {
981
+ const { executeChild } = await import("@temporalio/workflow");
982
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
983
+ const persistThreadState = vi.fn(async () => {
984
+ throw new Error("redis down");
985
+ });
986
+ const callsBefore = execMock.mock.calls.length;
987
+
988
+ const subagent: SubagentConfig = {
989
+ agentName: "persist-fails",
990
+ description: "Persist failure should not block child",
991
+ workflow: mockWorkflow(),
992
+ thread: "fork",
993
+ newThreadSource: "from-parent",
994
+ };
995
+
996
+ const { handler } = createSubagentHandler([subagent]);
997
+
998
+ await expect(
999
+ handler(
1000
+ { subagent: "persist-fails", description: "test", prompt: "test" },
1001
+ {
1002
+ threadId: "parent-t",
1003
+ toolCallId: "tc",
1004
+ toolName: "Subagent",
1005
+ persistThreadState,
1006
+ }
1007
+ )
1008
+ ).resolves.toBeDefined();
1009
+
1010
+ expect(persistThreadState).toHaveBeenCalledTimes(1);
1011
+ expect(execMock.mock.calls.length).toBe(callsBefore + 1);
1012
+ });
1013
+
816
1014
  // --- Sandbox continuation ---
817
1015
 
818
1016
  it("does not pass sandbox when thread is fork (own sandbox)", async () => {
@@ -35,29 +35,29 @@ describe("createThreadManager ↔ public key helpers round-trip", () => {
35
35
  const redis = {
36
36
  exists: vi.fn(async (k: string) => (meta.has(k) ? 1 : 0)),
37
37
  set: vi.fn(
38
- async (k: string, v: string, _ex: string, ttl: number) => {
38
+ async (k: string, v: string, options?: { EX?: number }) => {
39
39
  meta.set(k, v);
40
- writtenMetaExpires.set(k, ttl);
40
+ if (options?.EX !== undefined) writtenMetaExpires.set(k, options.EX);
41
41
  return "OK";
42
42
  }
43
43
  ),
44
- del: vi.fn(async (...keys: string[]) => {
44
+ del: vi.fn(async (keys: string | string[]) => {
45
45
  let n = 0;
46
- for (const k of keys) {
46
+ for (const k of Array.isArray(keys) ? keys : [keys]) {
47
47
  if (store.delete(k)) n++;
48
48
  if (meta.delete(k)) n++;
49
49
  }
50
50
  return n;
51
51
  }),
52
- rpush: vi.fn(async (k: string, ...values: string[]) => {
52
+ rPush: vi.fn(async (k: string, element: string | string[]) => {
53
53
  const list = store.get(k) ?? [];
54
- list.push(...values);
54
+ list.push(...(Array.isArray(element) ? element : [element]));
55
55
  store.set(k, list);
56
56
  return list.length;
57
57
  }),
58
- lrange: vi.fn(async (k: string) => store.get(k) ?? []),
59
- llen: vi.fn(async (k: string) => (store.get(k) ?? []).length),
60
- ltrim: vi.fn(async () => "OK"),
58
+ lRange: vi.fn(async (k: string) => store.get(k) ?? []),
59
+ lLen: vi.fn(async (k: string) => (store.get(k) ?? []).length),
60
+ lTrim: vi.fn(async () => "OK"),
61
61
  expire: vi.fn(async (k: string, ttl: number) => {
62
62
  if (store.has(k)) writtenListExpires.set(k, ttl);
63
63
  if (meta.has(k)) writtenMetaExpires.set(k, ttl);
@@ -38,7 +38,7 @@ export const THREAD_TTL_SECONDS = 60 * 60 * 24 * 90;
38
38
  * Build the Redis list key that holds a thread's serialized messages.
39
39
  *
40
40
  * Mirrors the exact key used internally by zeitlich's thread manager,
41
- * so a consumer calling `redis.lrange(getThreadListKey(key, id), 0, -1)`
41
+ * so a consumer calling `redis.lRange(getThreadListKey(key, id), 0, -1)`
42
42
  * sees the same data the writer wrote.
43
43
  *
44
44
  * @param threadKey - Thread key (defaults to `"messages"` inside the
@@ -1,45 +1,53 @@
1
1
  import { describe, expect, it, beforeEach } from "vitest";
2
- import type Redis from "ioredis";
2
+ import type { RedisClientType } from "redis";
3
3
  import { createThreadManager } from "./manager";
4
4
  import type { PersistedThreadState } from "../state/types";
5
5
 
6
+ type Keys = string | string[];
7
+ const toKeys = (keys: Keys): string[] => (Array.isArray(keys) ? keys : [keys]);
8
+
6
9
  /**
7
- * Minimal in-memory Redis stub exposing just the commands used by
10
+ * Minimal in-memory node-redis stub exposing just the commands used by
8
11
  * `createThreadManager`'s state methods (get/set/del/exists/expire) plus
9
- * the list helpers needed for `initialize`.
12
+ * the list helpers needed for `initialize`/`fork`. Uses the node-redis
13
+ * (`redis`) camelCase API and array-or-variadic keys.
10
14
  */
11
- function createFakeRedis(): Redis {
15
+ function createFakeRedis(): RedisClientType {
12
16
  const store = new Map<string, string>();
13
17
 
14
18
  const redis = {
15
19
  async get(key: string): Promise<string | null> {
16
20
  return store.has(key) ? (store.get(key) as string) : null;
17
21
  },
18
- async set(key: string, value: string): Promise<"OK"> {
22
+ async set(
23
+ key: string,
24
+ value: string,
25
+ _options?: { EX?: number }
26
+ ): Promise<"OK"> {
19
27
  store.set(key, String(value));
20
28
  return "OK";
21
29
  },
22
- async del(...keys: string[]): Promise<number> {
30
+ async del(keys: Keys): Promise<number> {
23
31
  let removed = 0;
24
- for (const k of keys) {
32
+ for (const k of toKeys(keys)) {
25
33
  if (store.delete(k)) removed++;
26
34
  }
27
35
  return removed;
28
36
  },
29
- async exists(...keys: string[]): Promise<number> {
30
- return keys.reduce((acc, k) => acc + (store.has(k) ? 1 : 0), 0);
37
+ async exists(keys: Keys): Promise<number> {
38
+ return toKeys(keys).reduce((acc, k) => acc + (store.has(k) ? 1 : 0), 0);
31
39
  },
32
40
  async expire(_key: string, _ttl: number): Promise<number> {
33
41
  return 1;
34
42
  },
35
- async lrange(): Promise<string[]> {
43
+ async lRange(): Promise<string[]> {
36
44
  return [];
37
45
  },
38
- async rpush(): Promise<number> {
46
+ async rPush(): Promise<number> {
39
47
  return 0;
40
48
  },
41
49
  _store: store,
42
- } as unknown as Redis & { _store: Map<string, string> };
50
+ } as unknown as RedisClientType & { _store: Map<string, string> };
43
51
 
44
52
  return redis;
45
53
  }
@@ -64,10 +72,12 @@ const baseSlice: PersistedThreadState = {
64
72
  };
65
73
 
66
74
  describe("createThreadManager state persistence", () => {
67
- let redis: Redis & { _store: Map<string, string> };
75
+ let redis: RedisClientType & { _store: Map<string, string> };
68
76
 
69
77
  beforeEach(() => {
70
- redis = createFakeRedis() as Redis & { _store: Map<string, string> };
78
+ redis = createFakeRedis() as RedisClientType & {
79
+ _store: Map<string, string>;
80
+ };
71
81
  });
72
82
 
73
83
  async function initThread(threadId: string): Promise<void> {
@@ -60,12 +60,12 @@ export function createThreadManager<T>(
60
60
  return {
61
61
  async initialize(): Promise<void> {
62
62
  await redis.del(redisKey);
63
- await redis.set(metaKey, "1", "EX", ttlSeconds);
63
+ await redis.set(metaKey, "1", { EX: ttlSeconds });
64
64
  },
65
65
 
66
66
  async load(): Promise<T[]> {
67
67
  await assertThreadExists();
68
- const data = await redis.lrange(redisKey, 0, -1);
68
+ const data = await redis.lRange(redisKey, 0, -1);
69
69
  return data.map(deserialize);
70
70
  },
71
71
 
@@ -75,23 +75,19 @@ export function createThreadManager<T>(
75
75
 
76
76
  if (idOf) {
77
77
  const dedupId = messages.map(idOf).join(":");
78
- await redis.eval(
79
- APPEND_IDEMPOTENT_SCRIPT,
80
- 2,
81
- dedupKey(dedupId),
82
- redisKey,
83
- String(ttlSeconds),
84
- ...messages.map(serialize)
85
- );
78
+ await redis.eval(APPEND_IDEMPOTENT_SCRIPT, {
79
+ keys: [dedupKey(dedupId), redisKey],
80
+ arguments: [String(ttlSeconds), ...messages.map(serialize)],
81
+ });
86
82
  } else {
87
- await redis.rpush(redisKey, ...messages.map(serialize));
83
+ await redis.rPush(redisKey, messages.map(serialize));
88
84
  await redis.expire(redisKey, ttlSeconds);
89
85
  }
90
86
  },
91
87
 
92
88
  async fork(newThreadId: string): Promise<BaseThreadManager<T>> {
93
89
  await assertThreadExists();
94
- const data = await redis.lrange(redisKey, 0, -1);
90
+ const data = await redis.lRange(redisKey, 0, -1);
95
91
  const stateRaw = await redis.get(stateKey);
96
92
  const forked = createThreadManager({
97
93
  ...config,
@@ -100,12 +96,12 @@ export function createThreadManager<T>(
100
96
  await forked.initialize();
101
97
  if (data.length > 0) {
102
98
  const newKey = getThreadListKey(key, newThreadId);
103
- await redis.rpush(newKey, ...data);
99
+ await redis.rPush(newKey, data);
104
100
  await redis.expire(newKey, ttlSeconds);
105
101
  }
106
102
  if (stateRaw != null) {
107
103
  const newStateKey = getThreadStateKey(key, newThreadId);
108
- await redis.set(newStateKey, stateRaw, "EX", ttlSeconds);
104
+ await redis.set(newStateKey, stateRaw, { EX: ttlSeconds });
109
105
  }
110
106
  return forked;
111
107
  },
@@ -117,23 +113,23 @@ export function createThreadManager<T>(
117
113
  "replaceAll requires the thread manager to be configured with `idOf`"
118
114
  );
119
115
  }
120
- const existing = await redis.lrange(redisKey, 0, -1);
116
+ const existing = await redis.lRange(redisKey, 0, -1);
121
117
  const existingIds = existing
122
118
  .map((raw) => idOf(deserialize(raw)))
123
119
  .filter((id): id is string => typeof id === "string");
124
120
  await redis.del(redisKey);
125
121
  if (existingIds.length > 0) {
126
- await redis.del(...existingIds.map(dedupKey));
122
+ await redis.del(existingIds.map(dedupKey));
127
123
  }
128
124
  if (messages.length > 0) {
129
- await redis.rpush(redisKey, ...messages.map(serialize));
125
+ await redis.rPush(redisKey, messages.map(serialize));
130
126
  await redis.expire(redisKey, ttlSeconds);
131
127
  }
132
128
  await redis.expire(metaKey, ttlSeconds);
133
129
  },
134
130
 
135
131
  async delete(): Promise<void> {
136
- await redis.del(redisKey, metaKey, stateKey);
132
+ await redis.del([redisKey, metaKey, stateKey]);
137
133
  },
138
134
 
139
135
  async loadState(): Promise<PersistedThreadState | null> {
@@ -144,7 +140,7 @@ export function createThreadManager<T>(
144
140
 
145
141
  async saveState(state: PersistedThreadState): Promise<void> {
146
142
  await assertThreadExists();
147
- await redis.set(stateKey, JSON.stringify(state), "EX", ttlSeconds);
143
+ await redis.set(stateKey, JSON.stringify(state), { EX: ttlSeconds });
148
144
  },
149
145
 
150
146
  async deleteState(): Promise<void> {
@@ -153,7 +149,7 @@ export function createThreadManager<T>(
153
149
 
154
150
  async length(): Promise<number> {
155
151
  await assertThreadExists();
156
- return redis.llen(redisKey);
152
+ return redis.lLen(redisKey);
157
153
  },
158
154
 
159
155
  async truncateFromId(messageId: string): Promise<void> {
@@ -163,7 +159,7 @@ export function createThreadManager<T>(
163
159
  "truncateFromId requires the thread manager to be configured with `idOf`"
164
160
  );
165
161
  }
166
- const data = await redis.lrange(redisKey, 0, -1);
162
+ const data = await redis.lRange(redisKey, 0, -1);
167
163
  let idx = -1;
168
164
  const removedIds: string[] = [];
169
165
  for (let i = 0; i < data.length; i++) {
@@ -178,7 +174,7 @@ export function createThreadManager<T>(
178
174
  await redis.del(redisKey);
179
175
  await redis.expire(metaKey, ttlSeconds);
180
176
  } else {
181
- await redis.ltrim(redisKey, 0, idx - 1);
177
+ await redis.lTrim(redisKey, 0, idx - 1);
182
178
  await redis.expire(redisKey, ttlSeconds);
183
179
  }
184
180
  // Clear dedup markers for the removed messages so that a rewind
@@ -186,7 +182,7 @@ export function createThreadManager<T>(
186
182
  // re-append without the idempotent-append Lua script treating it
187
183
  // as a duplicate.
188
184
  if (removedIds.length > 0) {
189
- await redis.del(...removedIds.map(dedupKey));
185
+ await redis.del(removedIds.map(dedupKey));
190
186
  }
191
187
  },
192
188
  };