zeitlich 0.2.2 → 0.2.4
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 +34 -31
- package/dist/index.cjs +330 -399
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -43
- package/dist/index.d.ts +24 -43
- package/dist/index.js +301 -373
- package/dist/index.js.map +1 -1
- package/dist/{workflow-BQf5EfNN.d.cts → workflow-PjeURKw4.d.cts} +265 -258
- package/dist/{workflow-BQf5EfNN.d.ts → workflow-PjeURKw4.d.ts} +265 -258
- package/dist/workflow.cjs +223 -281
- package/dist/workflow.cjs.map +1 -1
- package/dist/workflow.d.cts +2 -3
- package/dist/workflow.d.ts +2 -3
- package/dist/workflow.js +198 -258
- package/dist/workflow.js.map +1 -1
- package/package.json +3 -2
- package/src/activities.ts +0 -32
- package/src/index.ts +14 -11
- package/src/lib/model-invoker.ts +7 -1
- package/src/lib/session.ts +54 -109
- package/src/lib/thread-manager.ts +45 -37
- package/src/lib/tool-router.ts +148 -108
- package/src/lib/types.ts +35 -26
- package/src/tools/ask-user-question/handler.ts +5 -5
- package/src/tools/ask-user-question/tool.ts +3 -2
- package/src/tools/bash/bash.test.ts +12 -12
- package/src/tools/bash/handler.ts +5 -5
- package/src/tools/bash/tool.ts +3 -2
- package/src/tools/edit/handler.ts +78 -123
- package/src/tools/edit/tool.ts +3 -2
- package/src/tools/glob/handler.ts +17 -48
- package/src/tools/glob/tool.ts +3 -2
- package/src/tools/grep/tool.ts +3 -2
- package/src/tools/{read → read-file}/tool.ts +3 -2
- package/src/tools/{task → subagent}/handler.ts +18 -31
- package/src/tools/{task → subagent}/tool.ts +13 -20
- package/src/tools/task-create/handler.ts +5 -11
- package/src/tools/task-create/tool.ts +3 -2
- package/src/tools/task-get/handler.ts +5 -10
- package/src/tools/task-get/tool.ts +3 -2
- package/src/tools/task-list/handler.ts +5 -10
- package/src/tools/task-list/tool.ts +3 -2
- package/src/tools/task-update/handler.ts +5 -12
- package/src/tools/task-update/tool.ts +3 -2
- package/src/tools/{write → write-file}/tool.ts +5 -6
- package/src/workflow.ts +24 -21
package/src/activities.ts
CHANGED
|
@@ -2,18 +2,14 @@ import type Redis from "ioredis";
|
|
|
2
2
|
import { createThreadManager } from "./lib/thread-manager";
|
|
3
3
|
import type { ToolResultConfig } from "./lib/types";
|
|
4
4
|
import {
|
|
5
|
-
type AIMessage,
|
|
6
|
-
mapStoredMessageToChatMessage,
|
|
7
5
|
type MessageContent,
|
|
8
6
|
type StoredMessage,
|
|
9
7
|
} from "@langchain/core/messages";
|
|
10
|
-
import type { RawToolCall } from "./lib/tool-router";
|
|
11
8
|
/**
|
|
12
9
|
* Shared Zeitlich activities - thread management and message handling
|
|
13
10
|
* Note: runAgent is workflow-specific and should be created per-workflow
|
|
14
11
|
*/
|
|
15
12
|
export interface ZeitlichSharedActivities {
|
|
16
|
-
appendSystemMessage(threadId: string, content: string): Promise<void>;
|
|
17
13
|
/**
|
|
18
14
|
* Append a tool result to the thread.
|
|
19
15
|
* Handles JSON serialization and optional cache points.
|
|
@@ -25,11 +21,6 @@ export interface ZeitlichSharedActivities {
|
|
|
25
21
|
*/
|
|
26
22
|
initializeThread(threadId: string): Promise<void>;
|
|
27
23
|
|
|
28
|
-
/**
|
|
29
|
-
* Append a system message to a thread.
|
|
30
|
-
*/
|
|
31
|
-
appendSystemMessage(threadId: string, content: string): Promise<void>;
|
|
32
|
-
|
|
33
24
|
/**
|
|
34
25
|
* Append messages to a thread.
|
|
35
26
|
*/
|
|
@@ -46,11 +37,6 @@ export interface ZeitlichSharedActivities {
|
|
|
46
37
|
content: string | MessageContent
|
|
47
38
|
): Promise<void>;
|
|
48
39
|
|
|
49
|
-
/**
|
|
50
|
-
* Extract raw tool calls from a stored message.
|
|
51
|
-
* Returns unvalidated tool calls - use toolRegistry.parseToolCall() to validate.
|
|
52
|
-
*/
|
|
53
|
-
parseToolCalls(storedMessage: StoredMessage): Promise<RawToolCall[]>;
|
|
54
40
|
}
|
|
55
41
|
|
|
56
42
|
/**
|
|
@@ -62,14 +48,6 @@ export interface ZeitlichSharedActivities {
|
|
|
62
48
|
*/
|
|
63
49
|
export function createSharedActivities(redis: Redis): ZeitlichSharedActivities {
|
|
64
50
|
return {
|
|
65
|
-
async appendSystemMessage(
|
|
66
|
-
threadId: string,
|
|
67
|
-
content: string
|
|
68
|
-
): Promise<void> {
|
|
69
|
-
const thread = createThreadManager({ redis, threadId });
|
|
70
|
-
await thread.appendSystemMessage(content);
|
|
71
|
-
},
|
|
72
|
-
|
|
73
51
|
async appendToolResult(config: ToolResultConfig): Promise<void> {
|
|
74
52
|
const { threadId, toolCallId, content } = config;
|
|
75
53
|
const thread = createThreadManager({ redis, threadId });
|
|
@@ -98,15 +76,5 @@ export function createSharedActivities(redis: Redis): ZeitlichSharedActivities {
|
|
|
98
76
|
await thread.appendHumanMessage(content);
|
|
99
77
|
},
|
|
100
78
|
|
|
101
|
-
async parseToolCalls(storedMessage: StoredMessage): Promise<RawToolCall[]> {
|
|
102
|
-
const message = mapStoredMessageToChatMessage(storedMessage) as AIMessage;
|
|
103
|
-
const toolCalls = message.tool_calls ?? [];
|
|
104
|
-
|
|
105
|
-
return toolCalls.map((toolCall) => ({
|
|
106
|
-
id: toolCall.id,
|
|
107
|
-
name: toolCall.name,
|
|
108
|
-
args: toolCall.args,
|
|
109
|
-
}));
|
|
110
|
-
},
|
|
111
79
|
};
|
|
112
80
|
}
|
package/src/index.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* @example
|
|
10
10
|
* ```typescript
|
|
11
11
|
* // In your activities file
|
|
12
|
-
* import { invokeModel,
|
|
12
|
+
* import { invokeModel, createGlobHandler } from '@bead-ai/zeitlich';
|
|
13
13
|
*
|
|
14
14
|
* // In your worker file
|
|
15
15
|
* import { ZeitlichPlugin } from '@bead-ai/zeitlich';
|
|
@@ -28,25 +28,28 @@ export type { ZeitlichPluginOptions } from "./plugin";
|
|
|
28
28
|
export { createSharedActivities } from "./activities";
|
|
29
29
|
export type { ZeitlichSharedActivities } from "./activities";
|
|
30
30
|
|
|
31
|
+
// Auto-append wrapper for large tool results (activity-side only)
|
|
32
|
+
export { withAutoAppend } from "./lib/tool-router";
|
|
33
|
+
|
|
31
34
|
// Model invocation (requires Redis, LangChain)
|
|
32
35
|
export { invokeModel } from "./lib/model-invoker";
|
|
33
36
|
export type { InvokeModelConfig } from "./lib/model-invoker";
|
|
34
37
|
|
|
35
38
|
// Tool handlers (activity implementations)
|
|
36
|
-
//
|
|
37
|
-
export {
|
|
38
|
-
export {
|
|
39
|
+
// All handlers follow the factory pattern: createXHandler(deps) => handler(args)
|
|
40
|
+
export { createAskUserQuestionHandler } from "./tools/ask-user-question/handler";
|
|
41
|
+
export { createGlobHandler } from "./tools/glob/handler";
|
|
39
42
|
|
|
40
|
-
export {
|
|
41
|
-
export type {
|
|
42
|
-
EditResult,
|
|
43
|
-
EditHandlerResponse,
|
|
44
|
-
EditHandlerOptions,
|
|
45
|
-
} from "./tools/edit/handler";
|
|
43
|
+
export { createEditHandler } from "./tools/edit/handler";
|
|
46
44
|
|
|
47
|
-
export {
|
|
45
|
+
export { createBashHandler } from "./tools/bash/handler";
|
|
48
46
|
|
|
49
47
|
export { toTree } from "./lib/fs";
|
|
50
48
|
|
|
51
49
|
export { getStateQuery } from "./lib/state-manager";
|
|
52
50
|
export { createThreadManager } from "./lib/thread-manager";
|
|
51
|
+
export type {
|
|
52
|
+
BaseThreadManager,
|
|
53
|
+
ThreadManager,
|
|
54
|
+
ThreadManagerConfig,
|
|
55
|
+
} from "./lib/thread-manager";
|
package/src/lib/model-invoker.ts
CHANGED
|
@@ -63,9 +63,15 @@ export async function invokeModel({
|
|
|
63
63
|
|
|
64
64
|
await thread.append([response.toDict()]);
|
|
65
65
|
|
|
66
|
+
const toolCalls = response.tool_calls ?? [];
|
|
67
|
+
|
|
66
68
|
return {
|
|
67
69
|
message: response.toDict(),
|
|
68
|
-
|
|
70
|
+
rawToolCalls: toolCalls.map((tc) => ({
|
|
71
|
+
id: tc.id,
|
|
72
|
+
name: tc.name,
|
|
73
|
+
args: tc.args,
|
|
74
|
+
})),
|
|
69
75
|
usage: {
|
|
70
76
|
input_tokens: response.usage_metadata?.input_tokens,
|
|
71
77
|
output_tokens: response.usage_metadata?.output_tokens,
|
package/src/lib/session.ts
CHANGED
|
@@ -1,36 +1,23 @@
|
|
|
1
1
|
import { proxyActivities } from "@temporalio/workflow";
|
|
2
2
|
import type { ZeitlichSharedActivities } from "../activities";
|
|
3
3
|
import type {
|
|
4
|
+
ThreadOps,
|
|
4
5
|
ZeitlichAgentConfig,
|
|
5
6
|
SessionStartHook,
|
|
6
7
|
SessionEndHook,
|
|
7
8
|
SessionExitReason,
|
|
8
|
-
SubagentConfig,
|
|
9
9
|
} from "./types";
|
|
10
10
|
import { type AgentStateManager, type JsonSerializable } from "./state-manager";
|
|
11
11
|
import {
|
|
12
12
|
createToolRouter,
|
|
13
|
-
type ParsedToolCall,
|
|
14
13
|
type ParsedToolCallUnion,
|
|
15
|
-
type RawToolCall,
|
|
16
14
|
type ToolMap,
|
|
17
15
|
} from "./tool-router";
|
|
18
|
-
import type { StoredMessage } from "@langchain/core/messages";
|
|
19
|
-
import { createTaskTool, type TaskToolSchemaType } from "../tools/task/tool";
|
|
20
16
|
|
|
21
|
-
export interface ZeitlichSession {
|
|
17
|
+
export interface ZeitlichSession<M = unknown> {
|
|
22
18
|
runSession<T extends JsonSerializable<T>>(args: {
|
|
23
19
|
stateManager: AgentStateManager<T>;
|
|
24
|
-
}): Promise<
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
async function resolvePrompt(
|
|
28
|
-
prompt: string | (() => string | Promise<string>)
|
|
29
|
-
): Promise<string> {
|
|
30
|
-
if (typeof prompt === "function") {
|
|
31
|
-
return prompt();
|
|
32
|
-
}
|
|
33
|
-
return prompt;
|
|
20
|
+
}): Promise<M | null>;
|
|
34
21
|
}
|
|
35
22
|
|
|
36
23
|
/**
|
|
@@ -43,48 +30,24 @@ export interface SessionLifecycleHooks {
|
|
|
43
30
|
onSessionEnd?: SessionEndHook;
|
|
44
31
|
}
|
|
45
32
|
|
|
46
|
-
export const createSession = async <T extends ToolMap>({
|
|
33
|
+
export const createSession = async <T extends ToolMap, M = unknown>({
|
|
47
34
|
threadId,
|
|
48
35
|
agentName,
|
|
49
36
|
maxTurns = 50,
|
|
50
37
|
metadata = {},
|
|
51
38
|
runAgent,
|
|
52
|
-
|
|
53
|
-
instructionsPrompt,
|
|
39
|
+
threadOps,
|
|
54
40
|
buildContextMessage,
|
|
55
|
-
buildFileTree = async (): Promise<string> => "",
|
|
56
41
|
subagents,
|
|
57
42
|
tools = {} as T,
|
|
58
43
|
processToolsInParallel = true,
|
|
59
|
-
buildInTools = {},
|
|
60
44
|
hooks = {},
|
|
61
|
-
}: ZeitlichAgentConfig<T>): Promise<ZeitlichSession
|
|
62
|
-
const {
|
|
63
|
-
initializeThread,
|
|
64
|
-
appendHumanMessage,
|
|
65
|
-
parseToolCalls,
|
|
66
|
-
appendToolResult,
|
|
67
|
-
appendSystemMessage,
|
|
68
|
-
} = proxyActivities<ZeitlichSharedActivities>({
|
|
69
|
-
startToCloseTimeout: "30m",
|
|
70
|
-
retry: {
|
|
71
|
-
maximumAttempts: 6,
|
|
72
|
-
initialInterval: "5s",
|
|
73
|
-
maximumInterval: "15m",
|
|
74
|
-
backoffCoefficient: 4,
|
|
75
|
-
},
|
|
76
|
-
heartbeatTimeout: "5m",
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
const fileTree = await buildFileTree();
|
|
80
|
-
|
|
45
|
+
}: ZeitlichAgentConfig<T, M>): Promise<ZeitlichSession<M>> => {
|
|
81
46
|
const toolRouter = createToolRouter({
|
|
82
47
|
tools,
|
|
83
|
-
appendToolResult,
|
|
48
|
+
appendToolResult: threadOps.appendToolResult,
|
|
84
49
|
threadId,
|
|
85
50
|
hooks,
|
|
86
|
-
buildInTools,
|
|
87
|
-
fileTree,
|
|
88
51
|
subagents,
|
|
89
52
|
parallel: processToolsInParallel,
|
|
90
53
|
});
|
|
@@ -106,7 +69,7 @@ export const createSession = async <T extends ToolMap>({
|
|
|
106
69
|
};
|
|
107
70
|
|
|
108
71
|
return {
|
|
109
|
-
runSession: async ({ stateManager }): Promise<
|
|
72
|
+
runSession: async ({ stateManager }): Promise<M | null> => {
|
|
110
73
|
if (hooks.onSessionStart) {
|
|
111
74
|
await hooks.onSessionStart({
|
|
112
75
|
threadId,
|
|
@@ -116,15 +79,8 @@ export const createSession = async <T extends ToolMap>({
|
|
|
116
79
|
}
|
|
117
80
|
stateManager.setTools(toolRouter.getToolDefinitions());
|
|
118
81
|
|
|
119
|
-
await initializeThread(threadId);
|
|
120
|
-
await
|
|
121
|
-
threadId,
|
|
122
|
-
[
|
|
123
|
-
await resolvePrompt(baseSystemPrompt),
|
|
124
|
-
await resolvePrompt(instructionsPrompt),
|
|
125
|
-
].join("\n")
|
|
126
|
-
);
|
|
127
|
-
await appendHumanMessage(threadId, await buildContextMessage());
|
|
82
|
+
await threadOps.initializeThread(threadId);
|
|
83
|
+
await threadOps.appendHumanMessage(threadId, await buildContextMessage());
|
|
128
84
|
|
|
129
85
|
let exitReason: SessionExitReason = "completed";
|
|
130
86
|
|
|
@@ -137,38 +93,29 @@ export const createSession = async <T extends ToolMap>({
|
|
|
137
93
|
stateManager.incrementTurns();
|
|
138
94
|
const currentTurn = stateManager.getTurns();
|
|
139
95
|
|
|
140
|
-
const { message,
|
|
96
|
+
const { message, rawToolCalls } = await runAgent({
|
|
141
97
|
threadId,
|
|
142
98
|
agentName,
|
|
143
99
|
metadata,
|
|
144
100
|
});
|
|
145
101
|
|
|
146
|
-
if (stopReason === "end_turn") {
|
|
147
|
-
stateManager.complete();
|
|
148
|
-
exitReason = "completed";
|
|
149
|
-
return message;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
102
|
// No tools configured - treat any non-end_turn as completed
|
|
153
|
-
if (!toolRouter.hasTools()) {
|
|
103
|
+
if (!toolRouter.hasTools() || rawToolCalls.length === 0) {
|
|
154
104
|
stateManager.complete();
|
|
155
105
|
exitReason = "completed";
|
|
156
106
|
return message;
|
|
157
107
|
}
|
|
158
108
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
// Parse tool calls, catching schema errors and returning them to the agent
|
|
109
|
+
// Parse all tool calls uniformly through the router
|
|
162
110
|
const parsedToolCalls: ParsedToolCallUnion<T>[] = [];
|
|
163
|
-
for (const tc of rawToolCalls
|
|
164
|
-
(tc: RawToolCall) => tc.name !== "Task"
|
|
165
|
-
)) {
|
|
111
|
+
for (const tc of rawToolCalls) {
|
|
166
112
|
try {
|
|
167
113
|
parsedToolCalls.push(toolRouter.parseToolCall(tc));
|
|
168
114
|
} catch (error) {
|
|
169
|
-
await appendToolResult({
|
|
115
|
+
await threadOps.appendToolResult({
|
|
170
116
|
threadId,
|
|
171
117
|
toolCallId: tc.id ?? "",
|
|
118
|
+
toolName: tc.name,
|
|
172
119
|
content: JSON.stringify({
|
|
173
120
|
error: `Invalid tool call for "${tc.name}": ${error instanceof Error ? error.message : String(error)}`,
|
|
174
121
|
}),
|
|
@@ -176,47 +123,10 @@ export const createSession = async <T extends ToolMap>({
|
|
|
176
123
|
}
|
|
177
124
|
}
|
|
178
125
|
|
|
179
|
-
const taskToolCalls: ParsedToolCall<
|
|
180
|
-
"Task",
|
|
181
|
-
TaskToolSchemaType<SubagentConfig[]>
|
|
182
|
-
>[] = [];
|
|
183
|
-
if (subagents && subagents.length > 0) {
|
|
184
|
-
for (const tc of rawToolCalls.filter(
|
|
185
|
-
(tc: RawToolCall) => tc.name === "Task"
|
|
186
|
-
)) {
|
|
187
|
-
try {
|
|
188
|
-
const parsedArgs = createTaskTool(subagents).schema.parse(
|
|
189
|
-
tc.args
|
|
190
|
-
);
|
|
191
|
-
taskToolCalls.push({
|
|
192
|
-
id: tc.id ?? "",
|
|
193
|
-
name: tc.name,
|
|
194
|
-
args: parsedArgs,
|
|
195
|
-
} as ParsedToolCall<
|
|
196
|
-
"Task",
|
|
197
|
-
TaskToolSchemaType<SubagentConfig[]>
|
|
198
|
-
>);
|
|
199
|
-
} catch (error) {
|
|
200
|
-
await appendToolResult({
|
|
201
|
-
threadId,
|
|
202
|
-
toolCallId: tc.id ?? "",
|
|
203
|
-
content: JSON.stringify({
|
|
204
|
-
error: `Invalid tool call for "Task": ${error instanceof Error ? error.message : String(error)}`,
|
|
205
|
-
}),
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
126
|
// Hooks can call stateManager.waitForInput() to pause the session
|
|
212
|
-
await toolRouter.processToolCalls(
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
>[],
|
|
216
|
-
{
|
|
217
|
-
turn: currentTurn,
|
|
218
|
-
}
|
|
219
|
-
);
|
|
127
|
+
await toolRouter.processToolCalls(parsedToolCalls, {
|
|
128
|
+
turn: currentTurn,
|
|
129
|
+
});
|
|
220
130
|
|
|
221
131
|
if (stateManager.getStatus() === "WAITING_FOR_INPUT") {
|
|
222
132
|
exitReason = "waiting_for_input";
|
|
@@ -240,3 +150,38 @@ export const createSession = async <T extends ToolMap>({
|
|
|
240
150
|
},
|
|
241
151
|
};
|
|
242
152
|
};
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Proxy the default ZeitlichSharedActivities as ThreadOps<StoredMessage>.
|
|
156
|
+
* Call this in workflow code for the standard LangChain/StoredMessage setup.
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* ```typescript
|
|
160
|
+
* const session = await createSession({
|
|
161
|
+
* threadOps: proxyDefaultThreadOps(),
|
|
162
|
+
* // ...
|
|
163
|
+
* });
|
|
164
|
+
* ```
|
|
165
|
+
*/
|
|
166
|
+
export function proxyDefaultThreadOps(
|
|
167
|
+
options?: Parameters<typeof proxyActivities>[0]
|
|
168
|
+
): ThreadOps {
|
|
169
|
+
const activities = proxyActivities<ZeitlichSharedActivities>(
|
|
170
|
+
options ?? {
|
|
171
|
+
startToCloseTimeout: "30m",
|
|
172
|
+
retry: {
|
|
173
|
+
maximumAttempts: 6,
|
|
174
|
+
initialInterval: "5s",
|
|
175
|
+
maximumInterval: "15m",
|
|
176
|
+
backoffCoefficient: 4,
|
|
177
|
+
},
|
|
178
|
+
heartbeatTimeout: "5m",
|
|
179
|
+
}
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
initializeThread: activities.initializeThread,
|
|
184
|
+
appendHumanMessage: activities.appendHumanMessage,
|
|
185
|
+
appendToolResult: activities.appendToolResult,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
type MessageContent,
|
|
8
8
|
type MessageStructure,
|
|
9
9
|
type StoredMessage,
|
|
10
|
-
SystemMessage,
|
|
11
10
|
ToolMessage,
|
|
12
11
|
} from "@langchain/core/messages";
|
|
13
12
|
import { v4 as uuidv4 } from "uuid";
|
|
@@ -24,28 +23,31 @@ function getThreadKey(threadId: string, key: string): string {
|
|
|
24
23
|
*/
|
|
25
24
|
export type ToolMessageContent = $InferMessageContent<MessageStructure, "tool">;
|
|
26
25
|
|
|
27
|
-
export interface ThreadManagerConfig {
|
|
26
|
+
export interface ThreadManagerConfig<T = StoredMessage> {
|
|
28
27
|
redis: Redis;
|
|
29
28
|
threadId: string;
|
|
30
29
|
/** Thread key, defaults to 'messages' */
|
|
31
30
|
key?: string;
|
|
31
|
+
/** Custom serializer, defaults to JSON.stringify */
|
|
32
|
+
serialize?: (message: T) => string;
|
|
33
|
+
/** Custom deserializer, defaults to JSON.parse */
|
|
34
|
+
deserialize?: (raw: string) => T;
|
|
32
35
|
}
|
|
33
36
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
appendSystemMessage(content: string): Promise<void>;
|
|
37
|
+
/** Generic thread manager for any message type */
|
|
38
|
+
export interface BaseThreadManager<T> {
|
|
37
39
|
/** Initialize an empty thread */
|
|
38
40
|
initialize(): Promise<void>;
|
|
39
41
|
/** Load all messages from the thread */
|
|
40
|
-
load(): Promise<
|
|
42
|
+
load(): Promise<T[]>;
|
|
41
43
|
/** Append messages to the thread */
|
|
42
|
-
append(messages:
|
|
44
|
+
append(messages: T[]): Promise<void>;
|
|
43
45
|
/** Delete the thread */
|
|
44
46
|
delete(): Promise<void>;
|
|
47
|
+
}
|
|
45
48
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
+
/** Thread manager with StoredMessage convenience helpers */
|
|
50
|
+
export interface ThreadManager extends BaseThreadManager<StoredMessage> {
|
|
49
51
|
/** Create a HumanMessage (returns StoredMessage for storage) */
|
|
50
52
|
createHumanMessage(content: string | MessageContent): StoredMessage;
|
|
51
53
|
|
|
@@ -74,26 +76,38 @@ export interface ThreadManager {
|
|
|
74
76
|
|
|
75
77
|
/**
|
|
76
78
|
* Creates a thread manager for handling conversation state in Redis.
|
|
79
|
+
* Without generic args, returns a full ThreadManager with StoredMessage helpers.
|
|
80
|
+
* With a custom type T, returns a BaseThreadManager<T>.
|
|
77
81
|
*/
|
|
78
|
-
export function createThreadManager(
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
+
export function createThreadManager(config: ThreadManagerConfig): ThreadManager;
|
|
83
|
+
export function createThreadManager<T>(
|
|
84
|
+
config: ThreadManagerConfig<T>
|
|
85
|
+
): BaseThreadManager<T>;
|
|
86
|
+
export function createThreadManager<T>(
|
|
87
|
+
config: ThreadManagerConfig<T>
|
|
88
|
+
): BaseThreadManager<T> {
|
|
89
|
+
const {
|
|
90
|
+
redis,
|
|
91
|
+
threadId,
|
|
92
|
+
key = "messages",
|
|
93
|
+
serialize = (m: T): string => JSON.stringify(m),
|
|
94
|
+
deserialize = (raw: string): T => JSON.parse(raw) as T,
|
|
95
|
+
} = config;
|
|
82
96
|
const redisKey = getThreadKey(threadId, key);
|
|
83
97
|
|
|
84
|
-
|
|
98
|
+
const base: BaseThreadManager<T> = {
|
|
85
99
|
async initialize(): Promise<void> {
|
|
86
100
|
await redis.del(redisKey);
|
|
87
101
|
},
|
|
88
102
|
|
|
89
|
-
async load(): Promise<
|
|
103
|
+
async load(): Promise<T[]> {
|
|
90
104
|
const data = await redis.lrange(redisKey, 0, -1);
|
|
91
|
-
return data.map(
|
|
105
|
+
return data.map(deserialize);
|
|
92
106
|
},
|
|
93
107
|
|
|
94
|
-
async append(messages:
|
|
108
|
+
async append(messages: T[]): Promise<void> {
|
|
95
109
|
if (messages.length > 0) {
|
|
96
|
-
await redis.rpush(redisKey, ...messages.map(
|
|
110
|
+
await redis.rpush(redisKey, ...messages.map(serialize));
|
|
97
111
|
await redis.expire(redisKey, THREAD_TTL_SECONDS);
|
|
98
112
|
}
|
|
99
113
|
},
|
|
@@ -101,7 +115,11 @@ export function createThreadManager(
|
|
|
101
115
|
async delete(): Promise<void> {
|
|
102
116
|
await redis.del(redisKey);
|
|
103
117
|
},
|
|
118
|
+
};
|
|
104
119
|
|
|
120
|
+
// If no custom serialize/deserialize were provided and T defaults to StoredMessage,
|
|
121
|
+
// the overload guarantees the caller gets ThreadManager with convenience helpers.
|
|
122
|
+
const helpers = {
|
|
105
123
|
createHumanMessage(content: string | MessageContent): StoredMessage {
|
|
106
124
|
return new HumanMessage({
|
|
107
125
|
id: uuidv4(),
|
|
@@ -131,39 +149,29 @@ export function createThreadManager(
|
|
|
131
149
|
toolCallId: string
|
|
132
150
|
): StoredMessage {
|
|
133
151
|
return new ToolMessage({
|
|
134
|
-
// Cast needed due to langchain type compatibility
|
|
135
152
|
content: content as MessageContent,
|
|
136
153
|
tool_call_id: toolCallId,
|
|
137
154
|
}).toDict();
|
|
138
155
|
},
|
|
139
156
|
|
|
140
|
-
createSystemMessage(content: string): StoredMessage {
|
|
141
|
-
return new SystemMessage({
|
|
142
|
-
content,
|
|
143
|
-
}).toDict();
|
|
144
|
-
},
|
|
145
|
-
|
|
146
|
-
async appendSystemMessage(content: string): Promise<void> {
|
|
147
|
-
const message = this.createSystemMessage(content);
|
|
148
|
-
await this.append([message]);
|
|
149
|
-
},
|
|
150
|
-
|
|
151
157
|
async appendHumanMessage(content: string | MessageContent): Promise<void> {
|
|
152
|
-
const message =
|
|
153
|
-
await
|
|
158
|
+
const message = helpers.createHumanMessage(content);
|
|
159
|
+
await (base as BaseThreadManager<StoredMessage>).append([message]);
|
|
154
160
|
},
|
|
155
161
|
|
|
156
162
|
async appendToolMessage(
|
|
157
163
|
content: ToolMessageContent,
|
|
158
164
|
toolCallId: string
|
|
159
165
|
): Promise<void> {
|
|
160
|
-
const message =
|
|
161
|
-
await
|
|
166
|
+
const message = helpers.createToolMessage(content, toolCallId);
|
|
167
|
+
await (base as BaseThreadManager<StoredMessage>).append([message]);
|
|
162
168
|
},
|
|
163
169
|
|
|
164
170
|
async appendAIMessage(content: string | MessageContent): Promise<void> {
|
|
165
|
-
const message =
|
|
166
|
-
await
|
|
171
|
+
const message = helpers.createAIMessage(content as string);
|
|
172
|
+
await (base as BaseThreadManager<StoredMessage>).append([message]);
|
|
167
173
|
},
|
|
168
174
|
};
|
|
175
|
+
|
|
176
|
+
return Object.assign(base, helpers);
|
|
169
177
|
}
|