zeitlich 0.2.49 → 0.2.51
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 +26 -23
- package/dist/adapters/sandbox/daytona/index.cjs.map +1 -1
- package/dist/adapters/sandbox/daytona/index.d.cts +3 -3
- package/dist/adapters/sandbox/daytona/index.d.ts +3 -3
- package/dist/adapters/sandbox/daytona/index.js.map +1 -1
- package/dist/adapters/sandbox/daytona/workflow.d.cts +2 -2
- package/dist/adapters/sandbox/daytona/workflow.d.ts +2 -2
- package/dist/adapters/sandbox/e2b/index.cjs.map +1 -1
- package/dist/adapters/sandbox/e2b/index.d.cts +1 -1
- package/dist/adapters/sandbox/e2b/index.d.ts +1 -1
- package/dist/adapters/sandbox/e2b/index.js.map +1 -1
- package/dist/adapters/sandbox/e2b/workflow.d.cts +1 -1
- package/dist/adapters/sandbox/e2b/workflow.d.ts +1 -1
- package/dist/adapters/thread/anthropic/index.cjs +60 -55
- package/dist/adapters/thread/anthropic/index.cjs.map +1 -1
- package/dist/adapters/thread/anthropic/index.d.cts +20 -15
- package/dist/adapters/thread/anthropic/index.d.ts +20 -15
- package/dist/adapters/thread/anthropic/index.js +60 -55
- package/dist/adapters/thread/anthropic/index.js.map +1 -1
- package/dist/adapters/thread/anthropic/workflow.d.cts +7 -7
- package/dist/adapters/thread/anthropic/workflow.d.ts +7 -7
- package/dist/adapters/thread/google-genai/index.cjs +135 -66
- package/dist/adapters/thread/google-genai/index.cjs.map +1 -1
- package/dist/adapters/thread/google-genai/index.d.cts +200 -26
- package/dist/adapters/thread/google-genai/index.d.ts +200 -26
- package/dist/adapters/thread/google-genai/index.js +135 -66
- package/dist/adapters/thread/google-genai/index.js.map +1 -1
- package/dist/adapters/thread/google-genai/workflow.d.cts +8 -8
- package/dist/adapters/thread/google-genai/workflow.d.ts +8 -8
- package/dist/adapters/thread/langchain/index.cjs +67 -55
- package/dist/adapters/thread/langchain/index.cjs.map +1 -1
- package/dist/adapters/thread/langchain/index.d.cts +20 -15
- package/dist/adapters/thread/langchain/index.d.ts +20 -15
- package/dist/adapters/thread/langchain/index.js +67 -55
- package/dist/adapters/thread/langchain/index.js.map +1 -1
- package/dist/adapters/thread/langchain/workflow.d.cts +7 -7
- package/dist/adapters/thread/langchain/workflow.d.ts +7 -7
- package/dist/{cold-store-DKMAO1Dd.d.ts → cold-store-DyHodfAB.d.ts} +1 -1
- package/dist/{cold-store-CkWoNtMh.d.cts → cold-store-YOx9nmgR.d.cts} +1 -1
- package/dist/index.cjs +15050 -420
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +79 -83
- package/dist/index.d.ts +79 -83
- package/dist/index.js +15051 -417
- package/dist/index.js.map +1 -1
- package/dist/{proxy-B7CWEV-T.d.cts → proxy-2htgGQrc.d.cts} +1 -1
- package/dist/{proxy-ByFHMVRX.d.ts → proxy-CmiTP4pp.d.ts} +1 -1
- package/dist/{thread-manager-nK-WcFzM.d.ts → thread-manager-BJ5pz5Cx.d.cts} +6 -7
- package/dist/{thread-manager-7AW4rhfu.d.ts → thread-manager-BQAbrYXH.d.cts} +6 -7
- package/dist/{thread-manager-Cibe0X5m.d.cts → thread-manager-CcvltOuq.d.ts} +6 -7
- package/dist/{thread-manager-B9rtMEVn.d.cts → thread-manager-DHAbncHX.d.ts} +6 -7
- package/dist/{types-gVa5XCWD.d.ts → types-BQvXWcft.d.ts} +1 -1
- package/dist/{types-XUUFvrJ9.d.cts → types-BjdqxKYp.d.cts} +709 -709
- package/dist/{types-CJ7tCdl6.d.ts → types-D8W5TnSa.d.cts} +3 -3
- package/dist/{types-CJ7tCdl6.d.cts → types-D8W5TnSa.d.ts} +3 -3
- package/dist/{types-DO4Tkwxo.d.ts → types-DEbkLA06.d.ts} +3 -3
- package/dist/{types-DeVNWqlb.d.ts → types-DiI7mZhI.d.ts} +709 -709
- package/dist/{types-BR-k7h0e.d.cts → types-N_LTWe4b.d.cts} +3 -3
- package/dist/{types-CjY93AWZ.d.cts → types-OEN1xrFg.d.cts} +1 -1
- package/dist/{workflow-uhOIj9D-.d.ts → workflow-CcgD6EUB.d.cts} +34 -3
- package/dist/{workflow-KbGsxpfh.d.cts → workflow-DBjPOKBr.d.ts} +34 -3
- package/dist/workflow.cjs +15008 -377
- package/dist/workflow.cjs.map +1 -1
- package/dist/workflow.d.cts +3 -3
- package/dist/workflow.d.ts +3 -3
- package/dist/workflow.js +15009 -374
- package/dist/workflow.js.map +1 -1
- package/package.json +10 -37
- package/src/adapters/thread/anthropic/activities.test.ts +115 -0
- package/src/adapters/thread/anthropic/activities.ts +11 -19
- package/src/adapters/thread/anthropic/fork-transform.test.ts +17 -11
- package/src/adapters/thread/anthropic/model-invoker.test.ts +54 -3
- package/src/adapters/thread/anthropic/model-invoker.ts +11 -1
- package/src/adapters/thread/anthropic/thread-manager.test.ts +2 -2
- package/src/adapters/thread/anthropic/thread-manager.ts +3 -4
- package/src/adapters/thread/google-genai/activities.test.ts +162 -0
- package/src/adapters/thread/google-genai/activities.ts +38 -15
- package/src/adapters/thread/google-genai/fork-transform.test.ts +17 -11
- package/src/adapters/thread/google-genai/model-invoker.test.ts +386 -0
- package/src/adapters/thread/google-genai/model-invoker.ts +118 -23
- package/src/adapters/thread/google-genai/thread-manager.test.ts +2 -2
- package/src/adapters/thread/google-genai/thread-manager.ts +3 -4
- package/src/adapters/thread/langchain/activities.test.ts +88 -0
- package/src/adapters/thread/langchain/activities.ts +15 -12
- package/src/adapters/thread/langchain/fork-transform.test.ts +17 -11
- package/src/adapters/thread/langchain/model-invoker.test.ts +74 -0
- package/src/adapters/thread/langchain/model-invoker.ts +16 -3
- package/src/adapters/thread/langchain/thread-manager.test.ts +2 -2
- package/src/adapters/thread/langchain/thread-manager.ts +3 -4
- package/src/index.ts +2 -2
- package/src/lib/sandbox/capability-types.test.ts +2 -2
- package/src/lib/sandbox/manager.ts +2 -6
- package/src/lib/sandbox/sandbox.test.ts +1 -1
- package/src/lib/sandbox/types.ts +2 -2
- package/src/lib/session/session.integration.test.ts +92 -0
- package/src/lib/session/session.ts +23 -11
- package/src/lib/thread/keys.test.ts +9 -9
- package/src/lib/thread/keys.ts +1 -1
- package/src/lib/thread/manager.test.ts +24 -14
- package/src/lib/thread/manager.ts +19 -23
- package/src/lib/thread/snapshot.test.ts +51 -43
- package/src/lib/thread/snapshot.ts +54 -32
- package/src/lib/thread/test-utils.ts +106 -59
- package/src/lib/thread/tiered.test.ts +1 -1
- package/src/lib/thread/types.ts +2 -2
- package/src/lib/tool-router/router.integration.test.ts +44 -0
- package/src/lib/tool-router/router.ts +140 -32
- package/src/lib/workflow.ts +49 -0
- package/src/{adapters/sandbox/inmemory/proxy.ts → test-utils/in-memory-sandbox-proxy.ts} +5 -16
- package/src/{adapters/sandbox/inmemory/index.ts → test-utils/in-memory-sandbox.ts} +11 -3
- package/src/tools/bash/bash.test.ts +1 -1
- package/src/tools/edit/handler.test.ts +1 -1
- package/tsup.config.ts +2 -4
- package/dist/activities-7OcT_vdR.d.cts +0 -162
- package/dist/activities-zG_FBoY2.d.ts +0 -162
- package/dist/adapters/sandbox/inmemory/index.cjs +0 -214
- package/dist/adapters/sandbox/inmemory/index.cjs.map +0 -1
- package/dist/adapters/sandbox/inmemory/index.d.cts +0 -40
- package/dist/adapters/sandbox/inmemory/index.d.ts +0 -40
- package/dist/adapters/sandbox/inmemory/index.js +0 -211
- package/dist/adapters/sandbox/inmemory/index.js.map +0 -1
- package/dist/adapters/sandbox/inmemory/workflow.cjs +0 -36
- package/dist/adapters/sandbox/inmemory/workflow.cjs.map +0 -1
- package/dist/adapters/sandbox/inmemory/workflow.d.cts +0 -27
- package/dist/adapters/sandbox/inmemory/workflow.d.ts +0 -27
- package/dist/adapters/sandbox/inmemory/workflow.js +0 -34
- package/dist/adapters/sandbox/inmemory/workflow.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zeitlich",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.51",
|
|
4
4
|
"description": "[EXPERIMENTAL] An opinionated AI agent implementation for Temporal",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -97,26 +97,6 @@
|
|
|
97
97
|
"default": "./dist/adapters/thread/anthropic/workflow.js"
|
|
98
98
|
}
|
|
99
99
|
},
|
|
100
|
-
"./adapters/sandbox/inmemory": {
|
|
101
|
-
"import": {
|
|
102
|
-
"types": "./dist/adapters/sandbox/inmemory/index.d.ts",
|
|
103
|
-
"default": "./dist/adapters/sandbox/inmemory/index.js"
|
|
104
|
-
},
|
|
105
|
-
"require": {
|
|
106
|
-
"types": "./dist/adapters/sandbox/inmemory/index.d.ts",
|
|
107
|
-
"default": "./dist/adapters/sandbox/inmemory/index.js"
|
|
108
|
-
}
|
|
109
|
-
},
|
|
110
|
-
"./adapters/sandbox/inmemory/workflow": {
|
|
111
|
-
"import": {
|
|
112
|
-
"types": "./dist/adapters/sandbox/inmemory/workflow.d.ts",
|
|
113
|
-
"default": "./dist/adapters/sandbox/inmemory/workflow.js"
|
|
114
|
-
},
|
|
115
|
-
"require": {
|
|
116
|
-
"types": "./dist/adapters/sandbox/inmemory/workflow.d.ts",
|
|
117
|
-
"default": "./dist/adapters/sandbox/inmemory/workflow.js"
|
|
118
|
-
}
|
|
119
|
-
},
|
|
120
100
|
"./adapters/sandbox/daytona": {
|
|
121
101
|
"import": {
|
|
122
102
|
"types": "./dist/adapters/sandbox/daytona/index.d.ts",
|
|
@@ -201,22 +181,22 @@
|
|
|
201
181
|
"node": ">=18"
|
|
202
182
|
},
|
|
203
183
|
"devDependencies": {
|
|
204
|
-
"@anthropic-ai/sdk": "^0.
|
|
184
|
+
"@anthropic-ai/sdk": "^0.100.1",
|
|
205
185
|
"@aws-sdk/client-s3": "^3.1000.0",
|
|
206
186
|
"@aws-sdk/lib-storage": "^3.1000.0",
|
|
207
|
-
"@daytonaio/sdk": "^0.
|
|
187
|
+
"@daytonaio/sdk": "^0.184.0",
|
|
208
188
|
"@e2b/code-interpreter": "^2.3.3",
|
|
209
189
|
"@eslint/js": "^10.0.1",
|
|
210
|
-
"@google/genai": "^
|
|
190
|
+
"@google/genai": "^2.7.0",
|
|
211
191
|
"@langchain/core": "^1.1.48",
|
|
212
192
|
"@temporalio/common": "^1.17.2",
|
|
213
193
|
"@temporalio/envconfig": "^1.17.2",
|
|
214
194
|
"@temporalio/worker": "^1.17.2",
|
|
215
195
|
"@temporalio/workflow": "^1.17.2",
|
|
216
196
|
"@types/node": "^25.3.3",
|
|
217
|
-
"eslint": "^10.
|
|
197
|
+
"eslint": "^10.4.1",
|
|
218
198
|
"husky": "^9.1.7",
|
|
219
|
-
"just-bash": "^
|
|
199
|
+
"just-bash": "^3.0.1",
|
|
220
200
|
"prettier": "^3.8.1",
|
|
221
201
|
"release-please": "^17.3.0",
|
|
222
202
|
"tsup": "^8.5.1",
|
|
@@ -225,19 +205,18 @@
|
|
|
225
205
|
"vitest": "^4.0.18"
|
|
226
206
|
},
|
|
227
207
|
"peerDependencies": {
|
|
228
|
-
"@anthropic-ai/sdk": ">=0.
|
|
208
|
+
"@anthropic-ai/sdk": ">=0.100.0",
|
|
229
209
|
"@aws-sdk/client-s3": ">=3.700.0",
|
|
230
210
|
"@aws-sdk/lib-storage": ">=3.700.0",
|
|
231
211
|
"@daytonaio/sdk": ">=0.153.0",
|
|
232
212
|
"@e2b/code-interpreter": "^2.3.3",
|
|
233
|
-
"@google/genai": "
|
|
213
|
+
"@google/genai": ">=2.5.0",
|
|
234
214
|
"@langchain/core": ">=1.0.0",
|
|
235
215
|
"@temporalio/common": ">=1.16.0 <2.0.0",
|
|
236
216
|
"@temporalio/envconfig": ">=1.16.0 <2.0.0",
|
|
237
217
|
"@temporalio/worker": ">=1.16.0 <2.0.0",
|
|
238
218
|
"@temporalio/workflow": ">=1.16.0 <2.0.0",
|
|
239
|
-
"
|
|
240
|
-
"just-bash": ">=2.0.0"
|
|
219
|
+
"redis": ">=4.6.0"
|
|
241
220
|
},
|
|
242
221
|
"peerDependenciesMeta": {
|
|
243
222
|
"@daytonaio/sdk": {
|
|
@@ -263,17 +242,11 @@
|
|
|
263
242
|
},
|
|
264
243
|
"@temporalio/worker": {
|
|
265
244
|
"optional": true
|
|
266
|
-
},
|
|
267
|
-
"just-bash": {
|
|
268
|
-
"optional": true
|
|
269
245
|
}
|
|
270
246
|
},
|
|
271
247
|
"type": "module",
|
|
272
248
|
"bugs": {
|
|
273
249
|
"url": "https://github.com/bead-ai/zeitlich/issues"
|
|
274
250
|
},
|
|
275
|
-
"homepage": "https://github.com/bead-ai/zeitlich#readme"
|
|
276
|
-
"dependencies": {
|
|
277
|
-
"zod": "^4.3.6"
|
|
278
|
-
}
|
|
251
|
+
"homepage": "https://github.com/bead-ai/zeitlich#readme"
|
|
279
252
|
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import type Anthropic from "@anthropic-ai/sdk";
|
|
3
|
+
import { createAnthropicAdapter } from "./activities";
|
|
4
|
+
import type { StoredMessage } from "./thread-manager";
|
|
5
|
+
import { THREAD_TTL_SECONDS } from "../../../lib/thread/keys";
|
|
6
|
+
|
|
7
|
+
function createMockRedis(stored: StoredMessage[]) {
|
|
8
|
+
return {
|
|
9
|
+
exists: vi.fn().mockResolvedValue(1),
|
|
10
|
+
lRange: vi.fn().mockResolvedValue(stored.map((m) => JSON.stringify(m))),
|
|
11
|
+
lTrim: vi.fn().mockResolvedValue("OK"),
|
|
12
|
+
del: vi.fn().mockResolvedValue(1),
|
|
13
|
+
set: vi.fn().mockResolvedValue("OK"),
|
|
14
|
+
rPush: vi.fn().mockResolvedValue(1),
|
|
15
|
+
expire: vi.fn().mockResolvedValue(1),
|
|
16
|
+
eval: vi.fn().mockResolvedValue(1),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function createMockClient() {
|
|
21
|
+
const finalMessage: Anthropic.Messages.Message = {
|
|
22
|
+
id: "msg-response",
|
|
23
|
+
type: "message",
|
|
24
|
+
role: "assistant",
|
|
25
|
+
container: null,
|
|
26
|
+
model: "claude-test",
|
|
27
|
+
content: [{ type: "text", text: "ok", citations: null }],
|
|
28
|
+
stop_details: null,
|
|
29
|
+
stop_reason: "end_turn",
|
|
30
|
+
stop_sequence: null,
|
|
31
|
+
usage: {
|
|
32
|
+
cache_creation: null,
|
|
33
|
+
cache_creation_input_tokens: null,
|
|
34
|
+
cache_read_input_tokens: null,
|
|
35
|
+
inference_geo: null,
|
|
36
|
+
input_tokens: 1,
|
|
37
|
+
output_tokens: 1,
|
|
38
|
+
server_tool_use: null,
|
|
39
|
+
service_tier: null,
|
|
40
|
+
output_tokens_details: null,
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
const stream = {
|
|
44
|
+
async *[Symbol.asyncIterator]() {},
|
|
45
|
+
finalMessage: vi.fn().mockResolvedValue(finalMessage),
|
|
46
|
+
};
|
|
47
|
+
return { messages: { stream: vi.fn().mockReturnValue(stream) } };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Tail stored under the `assistantMessageId`, so the invoker's
|
|
51
|
+
// `truncateFromId` trims it and re-stamps the surviving list key's TTL.
|
|
52
|
+
const retriedThread: StoredMessage[] = [
|
|
53
|
+
{ id: "msg-1", message: { role: "user", content: "hi" } },
|
|
54
|
+
{ id: "assistant-1", message: { role: "assistant", content: "prior" } },
|
|
55
|
+
];
|
|
56
|
+
const listKey = "messages:thread:thread-1";
|
|
57
|
+
const metaKey = "messages:meta:thread:thread-1";
|
|
58
|
+
const invokerCall = {
|
|
59
|
+
threadId: "thread-1",
|
|
60
|
+
assistantMessageId: "assistant-1",
|
|
61
|
+
state: { tools: [] } as never,
|
|
62
|
+
agentName: "TestAgent",
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
describe("createAnthropicAdapter — TTL propagation", () => {
|
|
66
|
+
it("forwards adapter ttlSeconds to a created invoker's writes", async () => {
|
|
67
|
+
const redis = createMockRedis(retriedThread);
|
|
68
|
+
const client = createMockClient();
|
|
69
|
+
const adapter = createAnthropicAdapter({
|
|
70
|
+
redis: redis as never,
|
|
71
|
+
client: client as never,
|
|
72
|
+
ttlSeconds: 3600,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
await adapter.createModelInvoker("claude-test")(invokerCall);
|
|
76
|
+
|
|
77
|
+
expect(redis.expire).toHaveBeenCalledWith(listKey, 3600);
|
|
78
|
+
expect(redis.expire).not.toHaveBeenCalledWith(listKey, THREAD_TTL_SECONDS);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("forwards adapter ttlSeconds to thread-op writes", async () => {
|
|
82
|
+
const redis = createMockRedis([]);
|
|
83
|
+
const client = createMockClient();
|
|
84
|
+
const adapter = createAnthropicAdapter({
|
|
85
|
+
redis: redis as never,
|
|
86
|
+
client: client as never,
|
|
87
|
+
ttlSeconds: 3600,
|
|
88
|
+
});
|
|
89
|
+
const acts = adapter.createActivities() as unknown as Record<
|
|
90
|
+
string,
|
|
91
|
+
(threadId: string, threadKey?: string) => Promise<void>
|
|
92
|
+
>;
|
|
93
|
+
const initialize = Object.entries(acts).find(([k]) =>
|
|
94
|
+
k.endsWith("InitializeThread")
|
|
95
|
+
)?.[1];
|
|
96
|
+
if (!initialize) throw new Error("initializeThread activity not found");
|
|
97
|
+
|
|
98
|
+
await initialize("thread-1");
|
|
99
|
+
|
|
100
|
+
expect(redis.set).toHaveBeenCalledWith(metaKey, "1", { EX: 3600 });
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("defaults to THREAD_TTL_SECONDS when adapter ttlSeconds is omitted", async () => {
|
|
104
|
+
const redis = createMockRedis(retriedThread);
|
|
105
|
+
const client = createMockClient();
|
|
106
|
+
const adapter = createAnthropicAdapter({
|
|
107
|
+
redis: redis as never,
|
|
108
|
+
client: client as never,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
await adapter.createModelInvoker("claude-test")(invokerCall);
|
|
112
|
+
|
|
113
|
+
expect(redis.expire).toHaveBeenCalledWith(listKey, THREAD_TTL_SECONDS);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type Redis from "
|
|
1
|
+
import type { RedisClientType as Redis } from "redis";
|
|
2
2
|
import type Anthropic from "@anthropic-ai/sdk";
|
|
3
3
|
import type { ToolResultConfig } from "../../../lib/types";
|
|
4
4
|
import type { PersistedThreadState } from "../../../lib/state/types";
|
|
@@ -57,9 +57,8 @@ export interface AnthropicAdapterConfig {
|
|
|
57
57
|
*/
|
|
58
58
|
coldStore?: ColdThreadStore;
|
|
59
59
|
/**
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* is typically more appropriate.
|
|
60
|
+
* Redis TTL for the thread's keys; defaults to 90 days. Use a shorter
|
|
61
|
+
* value (hours) with a cold tier.
|
|
63
62
|
*/
|
|
64
63
|
ttlSeconds?: number;
|
|
65
64
|
}
|
|
@@ -160,32 +159,26 @@ export function createAnthropicAdapter(
|
|
|
160
159
|
): AnthropicAdapter {
|
|
161
160
|
const { redis, client } = config;
|
|
162
161
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
* `coldStore` / `ttlSeconds` configuration controls every Redis
|
|
168
|
-
* write the adapter does.
|
|
169
|
-
*/
|
|
170
|
-
const baseExtras = {
|
|
162
|
+
// Single source for the adapter's `redis` handle and configured TTL, spread
|
|
163
|
+
// into every internal thread manager so all of them share one configuration.
|
|
164
|
+
const base = {
|
|
165
|
+
redis,
|
|
171
166
|
...(config.ttlSeconds !== undefined && { ttlSeconds: config.ttlSeconds }),
|
|
172
167
|
};
|
|
173
168
|
|
|
174
169
|
const makeProviderThread = (threadId: string, threadKey?: string) =>
|
|
175
170
|
createAnthropicThreadManager({
|
|
176
|
-
|
|
171
|
+
...base,
|
|
177
172
|
threadId,
|
|
178
173
|
key: threadKey,
|
|
179
|
-
...baseExtras,
|
|
180
174
|
});
|
|
181
175
|
|
|
182
176
|
const makeTieredBase = (threadId: string, threadKey?: string) =>
|
|
183
177
|
createTieredThreadManager<StoredMessage>({
|
|
184
|
-
|
|
178
|
+
...base,
|
|
185
179
|
threadId,
|
|
186
180
|
key: threadKey,
|
|
187
181
|
idOf: storedMessageId,
|
|
188
|
-
...baseExtras,
|
|
189
182
|
...(config.coldStore && { coldStore: config.coldStore }),
|
|
190
183
|
});
|
|
191
184
|
|
|
@@ -240,11 +233,10 @@ export function createAnthropicAdapter(
|
|
|
240
233
|
threadKey?: string
|
|
241
234
|
): Promise<void> {
|
|
242
235
|
const thread = createAnthropicThreadManager({
|
|
243
|
-
|
|
236
|
+
...base,
|
|
244
237
|
threadId: sourceThreadId,
|
|
245
238
|
key: threadKey,
|
|
246
239
|
hooks: config.hooks,
|
|
247
|
-
...baseExtras,
|
|
248
240
|
});
|
|
249
241
|
await thread.fork(targetThreadId);
|
|
250
242
|
},
|
|
@@ -304,7 +296,7 @@ export function createAnthropicAdapter(
|
|
|
304
296
|
promptCache?: AnthropicPromptCacheConfig
|
|
305
297
|
): ModelInvoker<Anthropic.Messages.Message> => {
|
|
306
298
|
const invokerConfig: AnthropicModelInvokerConfig = {
|
|
307
|
-
|
|
299
|
+
...base,
|
|
308
300
|
client,
|
|
309
301
|
model,
|
|
310
302
|
...(maxTokens !== undefined ? { maxTokens } : {}),
|
|
@@ -12,32 +12,32 @@ function createStatefulRedis() {
|
|
|
12
12
|
const strings = new Map<string, string>();
|
|
13
13
|
|
|
14
14
|
return {
|
|
15
|
-
exists: vi.fn(async (
|
|
16
|
-
keys.reduce(
|
|
15
|
+
exists: vi.fn(async (keys: string | string[]) =>
|
|
16
|
+
(Array.isArray(keys) ? keys : [keys]).reduce(
|
|
17
17
|
(acc, k) => acc + (lists.has(k) || strings.has(k) ? 1 : 0),
|
|
18
18
|
0
|
|
19
19
|
)
|
|
20
20
|
),
|
|
21
|
-
|
|
21
|
+
lRange: vi.fn(async (key: string, start: number, stop: number) => {
|
|
22
22
|
const list = lists.get(key) ?? [];
|
|
23
23
|
const end = stop === -1 ? list.length : stop + 1;
|
|
24
24
|
return list.slice(start, end);
|
|
25
25
|
}),
|
|
26
|
-
|
|
26
|
+
rPush: vi.fn(async (key: string, element: string | string[]) => {
|
|
27
27
|
const list = lists.get(key) ?? [];
|
|
28
|
-
list.push(...
|
|
28
|
+
list.push(...(Array.isArray(element) ? element : [element]));
|
|
29
29
|
lists.set(key, list);
|
|
30
30
|
return list.length;
|
|
31
31
|
}),
|
|
32
|
-
|
|
32
|
+
lTrim: vi.fn(async (key: string, start: number, stop: number) => {
|
|
33
33
|
const list = lists.get(key) ?? [];
|
|
34
34
|
const end = stop === -1 ? list.length : stop + 1;
|
|
35
35
|
lists.set(key, list.slice(start, end));
|
|
36
36
|
return "OK";
|
|
37
37
|
}),
|
|
38
|
-
del: vi.fn(async (
|
|
38
|
+
del: vi.fn(async (keys: string | string[]) => {
|
|
39
39
|
let removed = 0;
|
|
40
|
-
for (const k of keys) {
|
|
40
|
+
for (const k of Array.isArray(keys) ? keys : [keys]) {
|
|
41
41
|
if (lists.delete(k)) removed++;
|
|
42
42
|
if (strings.delete(k)) removed++;
|
|
43
43
|
}
|
|
@@ -49,10 +49,16 @@ function createStatefulRedis() {
|
|
|
49
49
|
}),
|
|
50
50
|
get: vi.fn(async (key: string) => strings.get(key) ?? null),
|
|
51
51
|
expire: vi.fn(async (_key: string, _ttl: number) => 1),
|
|
52
|
-
|
|
52
|
+
lLen: vi.fn(async (key: string) => (lists.get(key) ?? []).length),
|
|
53
53
|
eval: vi.fn(
|
|
54
|
-
async (
|
|
55
|
-
|
|
54
|
+
async (
|
|
55
|
+
_script: string,
|
|
56
|
+
options: { keys?: string[]; arguments?: string[] }
|
|
57
|
+
) => {
|
|
58
|
+
const keys = options.keys ?? [];
|
|
59
|
+
const argv = options.arguments ?? [];
|
|
60
|
+
const [dedupKey, listKey] = keys;
|
|
61
|
+
const serialised = argv.slice(1);
|
|
56
62
|
if (!dedupKey || !listKey) return 0;
|
|
57
63
|
if (strings.has(dedupKey)) return 0;
|
|
58
64
|
const list = lists.get(listKey) ?? [];
|
|
@@ -2,15 +2,16 @@ import { describe, expect, it, vi } from "vitest";
|
|
|
2
2
|
import type Anthropic from "@anthropic-ai/sdk";
|
|
3
3
|
import { createAnthropicModelInvoker } from "./model-invoker";
|
|
4
4
|
import type { StoredMessage } from "./thread-manager";
|
|
5
|
+
import { THREAD_TTL_SECONDS } from "../../../lib/thread/keys";
|
|
5
6
|
|
|
6
7
|
function createMockRedis(stored: StoredMessage[]) {
|
|
7
8
|
return {
|
|
8
9
|
exists: vi.fn().mockResolvedValue(1),
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
lRange: vi.fn().mockResolvedValue(stored.map((m) => JSON.stringify(m))),
|
|
11
|
+
lTrim: vi.fn().mockResolvedValue("OK"),
|
|
11
12
|
del: vi.fn().mockResolvedValue(1),
|
|
12
13
|
set: vi.fn().mockResolvedValue("OK"),
|
|
13
|
-
|
|
14
|
+
rPush: vi.fn().mockResolvedValue(1),
|
|
14
15
|
expire: vi.fn().mockResolvedValue(1),
|
|
15
16
|
eval: vi.fn().mockResolvedValue(1),
|
|
16
17
|
};
|
|
@@ -36,6 +37,7 @@ function createMockClient() {
|
|
|
36
37
|
output_tokens: 1,
|
|
37
38
|
server_tool_use: null,
|
|
38
39
|
service_tier: null,
|
|
40
|
+
output_tokens_details: null,
|
|
39
41
|
},
|
|
40
42
|
};
|
|
41
43
|
const stream = {
|
|
@@ -108,3 +110,52 @@ describe("createAnthropicModelInvoker prompt caching", () => {
|
|
|
108
110
|
expect(params?.messages[0]?.content).toBe("hello");
|
|
109
111
|
});
|
|
110
112
|
});
|
|
113
|
+
|
|
114
|
+
describe("createAnthropicModelInvoker thread TTL", () => {
|
|
115
|
+
// The tail message is stored under `assistant-1`, so the invoker's
|
|
116
|
+
// `truncateFromId(assistant-1)` trims it and re-stamps the surviving
|
|
117
|
+
// list key's TTL.
|
|
118
|
+
const retriedThread: StoredMessage[] = [
|
|
119
|
+
{ id: "msg-1", message: { role: "user", content: "hi" } },
|
|
120
|
+
{ id: "assistant-1", message: { role: "assistant", content: "prior" } },
|
|
121
|
+
];
|
|
122
|
+
const listKey = "messages:thread:thread-1";
|
|
123
|
+
const invokerConfig = {
|
|
124
|
+
threadId: "thread-1",
|
|
125
|
+
assistantMessageId: "assistant-1",
|
|
126
|
+
state: { tools: [] } as never,
|
|
127
|
+
agentName: "Agent",
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
it("re-stamps trimmed hot keys at the configured ttlSeconds", async () => {
|
|
131
|
+
const redis = createMockRedis(retriedThread);
|
|
132
|
+
const { client } = createMockClient();
|
|
133
|
+
const invoker = createAnthropicModelInvoker({
|
|
134
|
+
redis: redis as never,
|
|
135
|
+
client: client as never,
|
|
136
|
+
model: "claude-test",
|
|
137
|
+
ttlSeconds: 3600,
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
await invoker(invokerConfig);
|
|
141
|
+
|
|
142
|
+
expect(redis.lTrim).toHaveBeenCalledWith(listKey, 0, 0);
|
|
143
|
+
expect(redis.expire).toHaveBeenCalledWith(listKey, 3600);
|
|
144
|
+
expect(redis.expire).not.toHaveBeenCalledWith(listKey, THREAD_TTL_SECONDS);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("defaults to THREAD_TTL_SECONDS when ttlSeconds is omitted", async () => {
|
|
148
|
+
const redis = createMockRedis(retriedThread);
|
|
149
|
+
const { client } = createMockClient();
|
|
150
|
+
const invoker = createAnthropicModelInvoker({
|
|
151
|
+
redis: redis as never,
|
|
152
|
+
client: client as never,
|
|
153
|
+
model: "claude-test",
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
await invoker(invokerConfig);
|
|
157
|
+
|
|
158
|
+
expect(redis.lTrim).toHaveBeenCalledWith(listKey, 0, 0);
|
|
159
|
+
expect(redis.expire).toHaveBeenCalledWith(listKey, THREAD_TTL_SECONDS);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type Redis from "
|
|
1
|
+
import type { RedisClientType as Redis } from "redis";
|
|
2
2
|
import type Anthropic from "@anthropic-ai/sdk";
|
|
3
3
|
import type { SerializableToolDefinition } from "../../../lib/types";
|
|
4
4
|
import type { AgentResponse, ModelInvokerConfig } from "../../../lib/model";
|
|
@@ -25,6 +25,11 @@ export interface AnthropicModelInvokerConfig {
|
|
|
25
25
|
*/
|
|
26
26
|
promptCache?: AnthropicPromptCacheConfig;
|
|
27
27
|
hooks?: AnthropicThreadManagerHooks;
|
|
28
|
+
/**
|
|
29
|
+
* Redis TTL for the thread's keys; defaults to 90 days. Use a shorter
|
|
30
|
+
* value (hours) with a cold tier.
|
|
31
|
+
*/
|
|
32
|
+
ttlSeconds?: number;
|
|
28
33
|
}
|
|
29
34
|
|
|
30
35
|
function toAnthropicTools(
|
|
@@ -68,6 +73,7 @@ export function createAnthropicModelInvoker({
|
|
|
68
73
|
maxTokens = 16384,
|
|
69
74
|
promptCache,
|
|
70
75
|
hooks,
|
|
76
|
+
ttlSeconds,
|
|
71
77
|
}: AnthropicModelInvokerConfig) {
|
|
72
78
|
return async function invokeAnthropicModel(
|
|
73
79
|
config: ModelInvokerConfig
|
|
@@ -80,6 +86,7 @@ export function createAnthropicModelInvoker({
|
|
|
80
86
|
threadId,
|
|
81
87
|
key: threadKey,
|
|
82
88
|
hooks,
|
|
89
|
+
...(ttlSeconds !== undefined && { ttlSeconds }),
|
|
83
90
|
});
|
|
84
91
|
// Truncate the thread starting at the id the assistant message
|
|
85
92
|
// will be stored under. On the happy path this is a no-op; on a
|
|
@@ -150,6 +157,7 @@ export async function invokeAnthropicModel({
|
|
|
150
157
|
maxTokens,
|
|
151
158
|
promptCache,
|
|
152
159
|
hooks,
|
|
160
|
+
ttlSeconds,
|
|
153
161
|
config,
|
|
154
162
|
}: {
|
|
155
163
|
redis: Redis;
|
|
@@ -158,6 +166,7 @@ export async function invokeAnthropicModel({
|
|
|
158
166
|
maxTokens?: number;
|
|
159
167
|
promptCache?: AnthropicPromptCacheConfig;
|
|
160
168
|
hooks?: AnthropicThreadManagerHooks;
|
|
169
|
+
ttlSeconds?: number;
|
|
161
170
|
config: ModelInvokerConfig;
|
|
162
171
|
}): Promise<AgentResponse<Anthropic.Messages.Message>> {
|
|
163
172
|
const invoker = createAnthropicModelInvoker({
|
|
@@ -167,6 +176,7 @@ export async function invokeAnthropicModel({
|
|
|
167
176
|
maxTokens,
|
|
168
177
|
promptCache,
|
|
169
178
|
hooks,
|
|
179
|
+
...(ttlSeconds !== undefined && { ttlSeconds }),
|
|
170
180
|
});
|
|
171
181
|
return invoker(config);
|
|
172
182
|
}
|
|
@@ -5,10 +5,10 @@ import { createAnthropicThreadManager } from "./thread-manager";
|
|
|
5
5
|
function createMockRedis(stored: StoredMessage[]) {
|
|
6
6
|
return {
|
|
7
7
|
exists: vi.fn().mockResolvedValue(1),
|
|
8
|
-
|
|
8
|
+
lRange: vi.fn().mockResolvedValue(stored.map((m) => JSON.stringify(m))),
|
|
9
9
|
del: vi.fn().mockResolvedValue(1),
|
|
10
10
|
set: vi.fn().mockResolvedValue("OK"),
|
|
11
|
-
|
|
11
|
+
rPush: vi.fn().mockResolvedValue(1),
|
|
12
12
|
expire: vi.fn().mockResolvedValue(1),
|
|
13
13
|
eval: vi.fn().mockResolvedValue(1),
|
|
14
14
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type Redis from "
|
|
1
|
+
import type { RedisClientType as Redis } from "redis";
|
|
2
2
|
import type Anthropic from "@anthropic-ai/sdk";
|
|
3
3
|
import type { JsonValue } from "../../../lib/state/types";
|
|
4
4
|
import { createThreadManager } from "../../../lib/thread/manager";
|
|
@@ -36,9 +36,8 @@ export interface AnthropicThreadManagerConfig {
|
|
|
36
36
|
key?: string;
|
|
37
37
|
hooks?: AnthropicThreadManagerHooks;
|
|
38
38
|
/**
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* typically more appropriate.
|
|
39
|
+
* Redis TTL for the thread's keys; defaults to 90 days. Use a shorter
|
|
40
|
+
* value (hours) with a cold tier.
|
|
42
41
|
*/
|
|
43
42
|
ttlSeconds?: number;
|
|
44
43
|
}
|