zeitlich 0.2.25 → 0.2.27
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/dist/activities-DE3_q9yq.d.ts +140 -0
- package/dist/activities-p8PDlRIK.d.cts +140 -0
- package/dist/adapters/sandbox/virtual/index.cjs.map +1 -1
- package/dist/adapters/sandbox/virtual/index.d.cts +8 -7
- package/dist/adapters/sandbox/virtual/index.d.ts +8 -7
- package/dist/adapters/sandbox/virtual/index.js.map +1 -1
- package/dist/adapters/sandbox/virtual/workflow.d.cts +3 -2
- package/dist/adapters/sandbox/virtual/workflow.d.ts +3 -2
- package/dist/adapters/thread/anthropic/index.cjs +363 -0
- package/dist/adapters/thread/anthropic/index.cjs.map +1 -0
- package/dist/adapters/thread/anthropic/index.d.cts +151 -0
- package/dist/adapters/thread/anthropic/index.d.ts +151 -0
- package/dist/adapters/thread/anthropic/index.js +358 -0
- package/dist/adapters/thread/anthropic/index.js.map +1 -0
- package/dist/adapters/thread/anthropic/workflow.cjs +38 -0
- package/dist/adapters/thread/anthropic/workflow.cjs.map +1 -0
- package/dist/adapters/thread/anthropic/workflow.d.cts +37 -0
- package/dist/adapters/thread/anthropic/workflow.d.ts +37 -0
- package/dist/adapters/thread/anthropic/workflow.js +36 -0
- package/dist/adapters/thread/anthropic/workflow.js.map +1 -0
- package/dist/adapters/thread/google-genai/index.cjs +102 -99
- package/dist/adapters/thread/google-genai/index.cjs.map +1 -1
- package/dist/adapters/thread/google-genai/index.d.cts +14 -113
- package/dist/adapters/thread/google-genai/index.d.ts +14 -113
- package/dist/adapters/thread/google-genai/index.js +103 -99
- package/dist/adapters/thread/google-genai/index.js.map +1 -1
- package/dist/adapters/thread/google-genai/workflow.cjs +9 -4
- package/dist/adapters/thread/google-genai/workflow.cjs.map +1 -1
- package/dist/adapters/thread/google-genai/workflow.d.cts +10 -5
- package/dist/adapters/thread/google-genai/workflow.d.ts +10 -5
- package/dist/adapters/thread/google-genai/workflow.js +9 -4
- package/dist/adapters/thread/google-genai/workflow.js.map +1 -1
- package/dist/adapters/thread/langchain/index.cjs +73 -63
- package/dist/adapters/thread/langchain/index.cjs.map +1 -1
- package/dist/adapters/thread/langchain/index.d.cts +39 -40
- package/dist/adapters/thread/langchain/index.d.ts +39 -40
- package/dist/adapters/thread/langchain/index.js +73 -64
- package/dist/adapters/thread/langchain/index.js.map +1 -1
- package/dist/adapters/thread/langchain/workflow.cjs +9 -4
- package/dist/adapters/thread/langchain/workflow.cjs.map +1 -1
- package/dist/adapters/thread/langchain/workflow.d.cts +10 -5
- package/dist/adapters/thread/langchain/workflow.d.ts +10 -5
- package/dist/adapters/thread/langchain/workflow.js +9 -4
- package/dist/adapters/thread/langchain/workflow.js.map +1 -1
- package/dist/index.cjs +27 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -12
- package/dist/index.d.ts +13 -12
- package/dist/index.js +28 -11
- package/dist/index.js.map +1 -1
- package/dist/proxy-BK1ydQt0.d.ts +24 -0
- package/dist/proxy-BMAsMHdp.d.cts +24 -0
- package/dist/{queries-DwBe2CAA.d.ts → queries-BCgJ9Sr5.d.ts} +1 -1
- package/dist/{queries-BYGBImeC.d.cts → queries-DwnE2bu3.d.cts} +1 -1
- package/dist/thread-manager-Bh9x847n.d.ts +31 -0
- package/dist/thread-manager-BlHua5_v.d.cts +39 -0
- package/dist/thread-manager-Bz8txKKj.d.cts +31 -0
- package/dist/thread-manager-dzaJHQEA.d.ts +39 -0
- package/dist/types-BfIQABzu.d.cts +73 -0
- package/dist/types-CIkYBoF8.d.ts +73 -0
- package/dist/{types-hmferhc2.d.ts → types-CvJyXDYt.d.ts} +44 -123
- package/dist/{types-LVKmCNds.d.ts → types-DFUNSYbj.d.ts} +1 -1
- package/dist/{types-Bf8KV0Ci.d.cts → types-DRnz-OZp.d.cts} +1 -1
- package/dist/{types-7PeMi1bD.d.cts → types-DSOefLpY.d.cts} +44 -123
- package/dist/{types-D_igp10o.d.cts → types-mCVxKIZb.d.cts} +233 -137
- package/dist/{types-D_igp10o.d.ts → types-mCVxKIZb.d.ts} +233 -137
- package/dist/workflow.cjs +25 -9
- package/dist/workflow.cjs.map +1 -1
- package/dist/workflow.d.cts +11 -11
- package/dist/workflow.d.ts +11 -11
- package/dist/workflow.js +26 -10
- package/dist/workflow.js.map +1 -1
- package/package.json +26 -1
- package/src/adapters/sandbox/virtual/with-virtual-sandbox.ts +8 -3
- package/src/adapters/thread/anthropic/activities.ts +226 -0
- package/src/adapters/thread/anthropic/index.ts +44 -0
- package/src/adapters/thread/anthropic/model-invoker.ts +129 -0
- package/src/adapters/thread/anthropic/proxy.ts +33 -0
- package/src/adapters/thread/anthropic/thread-manager.test.ts +137 -0
- package/src/adapters/thread/anthropic/thread-manager.ts +202 -0
- package/src/adapters/thread/google-genai/activities.ts +110 -33
- package/src/adapters/thread/google-genai/index.ts +3 -1
- package/src/adapters/thread/google-genai/model-invoker.ts +13 -42
- package/src/adapters/thread/google-genai/proxy.ts +6 -34
- package/src/adapters/thread/google-genai/thread-manager.test.ts +159 -0
- package/src/adapters/thread/google-genai/thread-manager.ts +96 -105
- package/src/adapters/thread/langchain/activities.ts +56 -21
- package/src/adapters/thread/langchain/hooks.ts +37 -0
- package/src/adapters/thread/langchain/index.ts +6 -1
- package/src/adapters/thread/langchain/model-invoker.ts +13 -12
- package/src/adapters/thread/langchain/proxy.ts +6 -34
- package/src/adapters/thread/langchain/thread-manager.test.ts +144 -0
- package/src/adapters/thread/langchain/thread-manager.ts +55 -98
- package/src/index.ts +5 -1
- package/src/lib/activity.ts +4 -3
- package/src/lib/hooks/types.ts +12 -12
- package/src/lib/model/types.ts +2 -0
- package/src/lib/session/session-edge-cases.integration.test.ts +24 -6
- package/src/lib/session/session.ts +18 -14
- package/src/lib/session/types.ts +31 -14
- package/src/lib/subagent/handler.ts +15 -8
- package/src/lib/subagent/types.ts +3 -2
- package/src/lib/thread/index.ts +3 -0
- package/src/lib/thread/manager.ts +4 -7
- package/src/lib/thread/proxy.ts +57 -0
- package/src/lib/thread/types.ts +44 -0
- package/src/lib/tool-router/auto-append-sandbox.integration.test.ts +54 -0
- package/src/lib/tool-router/auto-append.ts +5 -2
- package/src/lib/tool-router/router-edge-cases.integration.test.ts +9 -5
- package/src/lib/tool-router/router.ts +13 -7
- package/src/lib/tool-router/types.ts +20 -13
- package/src/lib/tool-router/with-sandbox.ts +4 -3
- package/src/lib/types.ts +7 -14
- package/src/workflow.ts +0 -4
- package/tsup.config.ts +5 -0
- package/dist/types-35POpVfa.d.cts +0 -40
- package/dist/types-35POpVfa.d.ts +0 -40
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import type { Content } from "@google/genai";
|
|
3
|
+
import type { StoredContent } from "./thread-manager";
|
|
4
|
+
import { createGoogleGenAIThreadManager } from "./thread-manager";
|
|
5
|
+
|
|
6
|
+
function createMockRedis(stored: StoredContent[]) {
|
|
7
|
+
return {
|
|
8
|
+
exists: vi.fn().mockResolvedValue(1),
|
|
9
|
+
lrange: vi.fn().mockResolvedValue(stored.map((m) => JSON.stringify(m))),
|
|
10
|
+
del: vi.fn().mockResolvedValue(1),
|
|
11
|
+
set: vi.fn().mockResolvedValue("OK"),
|
|
12
|
+
rpush: vi.fn().mockResolvedValue(1),
|
|
13
|
+
expire: vi.fn().mockResolvedValue(1),
|
|
14
|
+
eval: vi.fn().mockResolvedValue(1),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const systemContent: StoredContent = {
|
|
19
|
+
id: "sys-1",
|
|
20
|
+
content: { role: "system", parts: [{ text: "You are helpful." }] },
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const userContent: StoredContent = {
|
|
24
|
+
id: "msg-1",
|
|
25
|
+
content: { role: "user", parts: [{ text: "Hello" }] },
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const modelContent: StoredContent = {
|
|
29
|
+
id: "msg-2",
|
|
30
|
+
content: { role: "model", parts: [{ text: "Hi there!" }] },
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
describe("Google GenAI thread manager hooks", () => {
|
|
34
|
+
describe("onPrepareMessage", () => {
|
|
35
|
+
it("transforms stored messages before system extraction and merge", async () => {
|
|
36
|
+
const hook = vi.fn((msg: StoredContent) => {
|
|
37
|
+
if (msg.content.role === "system") return msg;
|
|
38
|
+
return {
|
|
39
|
+
...msg,
|
|
40
|
+
content: {
|
|
41
|
+
...msg.content,
|
|
42
|
+
parts: [{ text: `[modified] ${msg.content.parts?.[0]?.text ?? ""}` }],
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const redis = createMockRedis([systemContent, userContent, modelContent]);
|
|
48
|
+
const tm = createGoogleGenAIThreadManager({
|
|
49
|
+
redis: redis as never,
|
|
50
|
+
threadId: "t1",
|
|
51
|
+
hooks: { onPrepareMessage: hook },
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const { contents, systemInstruction } = await tm.prepareForInvocation();
|
|
55
|
+
|
|
56
|
+
expect(hook).toHaveBeenCalledTimes(3);
|
|
57
|
+
expect(hook).toHaveBeenCalledWith(systemContent, 0, [systemContent, userContent, modelContent]);
|
|
58
|
+
expect(systemInstruction).toBe("You are helpful.");
|
|
59
|
+
expect(contents[0]?.parts?.[0]?.text).toBe("[modified] Hello");
|
|
60
|
+
expect(contents[1]?.parts?.[0]?.text).toBe("[modified] Hi there!");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("is not called when not configured", async () => {
|
|
64
|
+
const redis = createMockRedis([userContent]);
|
|
65
|
+
const tm = createGoogleGenAIThreadManager({
|
|
66
|
+
redis: redis as never,
|
|
67
|
+
threadId: "t1",
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const { contents } = await tm.prepareForInvocation();
|
|
71
|
+
expect(contents).toHaveLength(1);
|
|
72
|
+
expect(contents[0]?.parts?.[0]?.text).toBe("Hello");
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe("onPreparedMessage", () => {
|
|
77
|
+
it("transforms SDK-native Content after merge", async () => {
|
|
78
|
+
const hook = vi.fn((msg: Content) => ({
|
|
79
|
+
...msg,
|
|
80
|
+
parts: [{ text: `[post] ${msg.parts?.[0]?.text ?? ""}` }],
|
|
81
|
+
}));
|
|
82
|
+
|
|
83
|
+
const redis = createMockRedis([userContent, modelContent]);
|
|
84
|
+
const tm = createGoogleGenAIThreadManager({
|
|
85
|
+
redis: redis as never,
|
|
86
|
+
threadId: "t1",
|
|
87
|
+
hooks: { onPreparedMessage: hook },
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const { contents } = await tm.prepareForInvocation();
|
|
91
|
+
|
|
92
|
+
expect(hook).toHaveBeenCalledTimes(2);
|
|
93
|
+
expect(contents[0]?.parts?.[0]?.text).toBe("[post] Hello");
|
|
94
|
+
expect(contents[1]?.parts?.[0]?.text).toBe("[post] Hi there!");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("receives the full prepared contents array", async () => {
|
|
98
|
+
const hook = vi.fn((msg: Content) => msg);
|
|
99
|
+
|
|
100
|
+
const redis = createMockRedis([userContent, modelContent]);
|
|
101
|
+
const tm = createGoogleGenAIThreadManager({
|
|
102
|
+
redis: redis as never,
|
|
103
|
+
threadId: "t1",
|
|
104
|
+
hooks: { onPreparedMessage: hook },
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
await tm.prepareForInvocation();
|
|
108
|
+
|
|
109
|
+
const args = hook.mock.calls[0] as unknown as [Content, number, Content[]];
|
|
110
|
+
expect(args[2]).toHaveLength(2);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe("both hooks combined", () => {
|
|
115
|
+
it("runs onPrepareMessage before onPreparedMessage", async () => {
|
|
116
|
+
const order: string[] = [];
|
|
117
|
+
|
|
118
|
+
const redis = createMockRedis([userContent]);
|
|
119
|
+
const tm = createGoogleGenAIThreadManager({
|
|
120
|
+
redis: redis as never,
|
|
121
|
+
threadId: "t1",
|
|
122
|
+
hooks: {
|
|
123
|
+
onPrepareMessage: (msg) => {
|
|
124
|
+
order.push("pre");
|
|
125
|
+
return msg;
|
|
126
|
+
},
|
|
127
|
+
onPreparedMessage: (msg) => {
|
|
128
|
+
order.push("post");
|
|
129
|
+
return msg;
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
await tm.prepareForInvocation();
|
|
135
|
+
expect(order).toEqual(["pre", "post"]);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("onPreparedMessage sees results of onPrepareMessage", async () => {
|
|
139
|
+
const redis = createMockRedis([userContent]);
|
|
140
|
+
const tm = createGoogleGenAIThreadManager({
|
|
141
|
+
redis: redis as never,
|
|
142
|
+
threadId: "t1",
|
|
143
|
+
hooks: {
|
|
144
|
+
onPrepareMessage: (msg) => ({
|
|
145
|
+
...msg,
|
|
146
|
+
content: { ...msg.content, parts: [{ text: "replaced" }] },
|
|
147
|
+
}),
|
|
148
|
+
onPreparedMessage: (msg) => {
|
|
149
|
+
expect(msg.parts?.[0]?.text).toBe("replaced");
|
|
150
|
+
return msg;
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const { contents } = await tm.prepareForInvocation();
|
|
156
|
+
expect(contents[0]?.parts?.[0]?.text).toBe("replaced");
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
});
|
|
@@ -2,10 +2,14 @@ import type Redis from "ioredis";
|
|
|
2
2
|
import type { Content, Part } from "@google/genai";
|
|
3
3
|
import {
|
|
4
4
|
createThreadManager,
|
|
5
|
-
type
|
|
5
|
+
type ProviderThreadManager,
|
|
6
6
|
type ThreadManagerConfig,
|
|
7
|
+
type ThreadManagerHooks,
|
|
7
8
|
} from "../../../lib/thread";
|
|
8
|
-
import type {
|
|
9
|
+
import type { GoogleGenAIToolResponse } from "./activities";
|
|
10
|
+
|
|
11
|
+
/** SDK-native content type for Google GenAI human messages */
|
|
12
|
+
export type GoogleGenAIContent = string | Part[];
|
|
9
13
|
|
|
10
14
|
/** A Content with a unique ID for idempotent Redis storage */
|
|
11
15
|
export interface StoredContent {
|
|
@@ -13,78 +17,65 @@ export interface StoredContent {
|
|
|
13
17
|
content: Content;
|
|
14
18
|
}
|
|
15
19
|
|
|
20
|
+
export type GoogleGenAIThreadManagerHooks = ThreadManagerHooks<StoredContent, Content>;
|
|
21
|
+
|
|
16
22
|
export interface GoogleGenAIThreadManagerConfig {
|
|
17
23
|
redis: Redis;
|
|
18
24
|
threadId: string;
|
|
19
25
|
/** Thread key, defaults to 'messages' */
|
|
20
26
|
key?: string;
|
|
27
|
+
hooks?: GoogleGenAIThreadManagerHooks;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Prepared payload ready to send to the Google GenAI API */
|
|
31
|
+
export interface GoogleGenAIInvocationPayload {
|
|
32
|
+
contents: Content[];
|
|
33
|
+
systemInstruction?: string;
|
|
21
34
|
}
|
|
22
35
|
|
|
23
36
|
/** Thread manager with Google GenAI Content convenience helpers */
|
|
24
|
-
export interface GoogleGenAIThreadManager
|
|
25
|
-
|
|
26
|
-
id: string,
|
|
27
|
-
content: string | MessageContent
|
|
28
|
-
): StoredContent;
|
|
29
|
-
createSystemContent(id: string, content: string): StoredContent;
|
|
30
|
-
createModelContent(id: string, parts: Part[]): StoredContent;
|
|
31
|
-
createToolResponseContent(
|
|
32
|
-
id: string,
|
|
33
|
-
toolCallId: string,
|
|
34
|
-
toolName: string,
|
|
35
|
-
content: ToolMessageContent
|
|
36
|
-
): StoredContent;
|
|
37
|
-
appendUserMessage(
|
|
38
|
-
id: string,
|
|
39
|
-
content: string | MessageContent
|
|
40
|
-
): Promise<void>;
|
|
41
|
-
appendSystemMessage(id: string, content: string): Promise<void>;
|
|
37
|
+
export interface GoogleGenAIThreadManager
|
|
38
|
+
extends ProviderThreadManager<StoredContent, GoogleGenAIContent, GoogleGenAIToolResponse> {
|
|
42
39
|
appendModelContent(id: string, parts: Part[]): Promise<void>;
|
|
43
|
-
|
|
44
|
-
id: string,
|
|
45
|
-
toolCallId: string,
|
|
46
|
-
toolName: string,
|
|
47
|
-
content: ToolMessageContent
|
|
48
|
-
): Promise<void>;
|
|
40
|
+
prepareForInvocation(): Promise<GoogleGenAIInvocationPayload>;
|
|
49
41
|
}
|
|
50
42
|
|
|
51
43
|
function storedContentId(msg: StoredContent): string {
|
|
52
44
|
return msg.id;
|
|
53
45
|
}
|
|
54
46
|
|
|
55
|
-
/**
|
|
56
|
-
|
|
57
|
-
content: string | MessageContent
|
|
58
|
-
): Part[] {
|
|
47
|
+
/** Normalise content into Part[] */
|
|
48
|
+
function toParts(content: GoogleGenAIContent): Part[] {
|
|
59
49
|
if (typeof content === "string") {
|
|
60
50
|
return [{ text: content }];
|
|
61
51
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
52
|
+
return content;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Convert a string or object into a Record suitable for functionResponse.response */
|
|
56
|
+
function toFunctionResponse(content: string | Record<string, unknown>): Record<string, unknown> {
|
|
57
|
+
if (typeof content === "object") {
|
|
58
|
+
return content;
|
|
69
59
|
}
|
|
70
|
-
return
|
|
60
|
+
return { result: content };
|
|
71
61
|
}
|
|
72
62
|
|
|
73
|
-
/**
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
63
|
+
/**
|
|
64
|
+
* Merge consecutive Content objects sharing the same role.
|
|
65
|
+
* The Gemini API requires alternating user/model turns; without
|
|
66
|
+
* merging, multiple sequential tool-result messages would violate this.
|
|
67
|
+
*/
|
|
68
|
+
function mergeConsecutiveContents(contents: Content[]): Content[] {
|
|
69
|
+
const merged: Content[] = [];
|
|
70
|
+
for (const content of contents) {
|
|
71
|
+
const last = merged[merged.length - 1];
|
|
72
|
+
if (last && last.role === content.role) {
|
|
73
|
+
last.parts = [...(last.parts ?? []), ...(content.parts ?? [])];
|
|
74
|
+
} else {
|
|
75
|
+
merged.push({ ...content, parts: [...(content.parts ?? [])] });
|
|
85
76
|
}
|
|
86
77
|
}
|
|
87
|
-
return
|
|
78
|
+
return merged;
|
|
88
79
|
}
|
|
89
80
|
|
|
90
81
|
/**
|
|
@@ -93,7 +84,7 @@ function parseToolResponse(
|
|
|
93
84
|
* appending typed Content messages.
|
|
94
85
|
*/
|
|
95
86
|
export function createGoogleGenAIThreadManager(
|
|
96
|
-
config: GoogleGenAIThreadManagerConfig
|
|
87
|
+
config: GoogleGenAIThreadManagerConfig,
|
|
97
88
|
): GoogleGenAIThreadManager {
|
|
98
89
|
const baseConfig: ThreadManagerConfig<StoredContent> = {
|
|
99
90
|
redis: config.redis,
|
|
@@ -104,79 +95,79 @@ export function createGoogleGenAIThreadManager(
|
|
|
104
95
|
|
|
105
96
|
const base = createThreadManager(baseConfig);
|
|
106
97
|
|
|
107
|
-
const helpers = {
|
|
108
|
-
|
|
98
|
+
const helpers: Omit<GoogleGenAIThreadManager, keyof typeof base> = {
|
|
99
|
+
async appendUserMessage(
|
|
109
100
|
id: string,
|
|
110
|
-
content:
|
|
111
|
-
):
|
|
112
|
-
|
|
101
|
+
content: GoogleGenAIContent,
|
|
102
|
+
): Promise<void> {
|
|
103
|
+
await base.append([{
|
|
113
104
|
id,
|
|
114
|
-
content: { role: "user", parts:
|
|
115
|
-
};
|
|
105
|
+
content: { role: "user", parts: toParts(content) },
|
|
106
|
+
}]);
|
|
116
107
|
},
|
|
117
108
|
|
|
118
|
-
|
|
119
|
-
|
|
109
|
+
async appendSystemMessage(id: string, content: string): Promise<void> {
|
|
110
|
+
await base.initialize();
|
|
111
|
+
await base.append([{
|
|
120
112
|
id,
|
|
121
113
|
content: { role: "system", parts: [{ text: content }] },
|
|
122
|
-
};
|
|
114
|
+
}]);
|
|
123
115
|
},
|
|
124
116
|
|
|
125
|
-
|
|
126
|
-
|
|
117
|
+
async appendModelContent(id: string, parts: Part[]): Promise<void> {
|
|
118
|
+
await base.append([{
|
|
127
119
|
id,
|
|
128
120
|
content: { role: "model", parts },
|
|
129
|
-
};
|
|
121
|
+
}]);
|
|
130
122
|
},
|
|
131
123
|
|
|
132
|
-
|
|
124
|
+
async appendToolResult(
|
|
133
125
|
id: string,
|
|
134
126
|
toolCallId: string,
|
|
135
127
|
toolName: string,
|
|
136
|
-
content:
|
|
137
|
-
): StoredContent {
|
|
138
|
-
return {
|
|
139
|
-
id,
|
|
140
|
-
content: {
|
|
141
|
-
role: "user",
|
|
142
|
-
parts: [
|
|
143
|
-
{
|
|
144
|
-
functionResponse: {
|
|
145
|
-
id: toolCallId,
|
|
146
|
-
name: toolName,
|
|
147
|
-
response: parseToolResponse(content),
|
|
148
|
-
},
|
|
149
|
-
},
|
|
150
|
-
],
|
|
151
|
-
},
|
|
152
|
-
};
|
|
153
|
-
},
|
|
154
|
-
|
|
155
|
-
async appendUserMessage(
|
|
156
|
-
id: string,
|
|
157
|
-
content: string | MessageContent
|
|
128
|
+
content: GoogleGenAIToolResponse,
|
|
158
129
|
): Promise<void> {
|
|
159
|
-
|
|
160
|
-
|
|
130
|
+
const parts: Part[] = Array.isArray(content)
|
|
131
|
+
? content as Part[]
|
|
132
|
+
: [{
|
|
133
|
+
functionResponse: {
|
|
134
|
+
id: toolCallId,
|
|
135
|
+
name: toolName,
|
|
136
|
+
response: toFunctionResponse(content),
|
|
137
|
+
},
|
|
138
|
+
}];
|
|
161
139
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
140
|
+
await base.append([{
|
|
141
|
+
id,
|
|
142
|
+
content: { role: "user", parts },
|
|
143
|
+
}]);
|
|
165
144
|
},
|
|
166
145
|
|
|
167
|
-
async
|
|
168
|
-
await base.
|
|
169
|
-
|
|
146
|
+
async prepareForInvocation(): Promise<GoogleGenAIInvocationPayload> {
|
|
147
|
+
const stored = await base.load();
|
|
148
|
+
const { onPrepareMessage, onPreparedMessage } = config.hooks ?? {};
|
|
149
|
+
const mapped = onPrepareMessage
|
|
150
|
+
? stored.map((msg, i) => onPrepareMessage(msg, i, stored))
|
|
151
|
+
: stored;
|
|
152
|
+
|
|
153
|
+
let systemInstruction: string | undefined;
|
|
154
|
+
const conversationContents: Content[] = [];
|
|
155
|
+
|
|
156
|
+
for (const item of mapped) {
|
|
157
|
+
if (item.content.role === "system") {
|
|
158
|
+
systemInstruction = item.content.parts?.[0]?.text;
|
|
159
|
+
} else {
|
|
160
|
+
conversationContents.push(item.content);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
170
163
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
helpers.createToolResponseContent(id, toolCallId, toolName, content),
|
|
179
|
-
]);
|
|
164
|
+
const contents = mergeConsecutiveContents(conversationContents);
|
|
165
|
+
return {
|
|
166
|
+
contents: onPreparedMessage
|
|
167
|
+
? contents.map((msg, i) => onPreparedMessage(msg, i, contents))
|
|
168
|
+
: contents,
|
|
169
|
+
...(systemInstruction ? { systemInstruction } : {}),
|
|
170
|
+
};
|
|
180
171
|
},
|
|
181
172
|
};
|
|
182
173
|
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type Redis from "ioredis";
|
|
2
2
|
import type { ToolResultConfig } from "../../../lib/types";
|
|
3
3
|
import type { MessageContent } from "@langchain/core/messages";
|
|
4
|
+
import type {
|
|
5
|
+
ActivityToolHandler,
|
|
6
|
+
RouterContext,
|
|
7
|
+
ToolHandlerResponse,
|
|
8
|
+
} from "../../../lib/tool-router/types";
|
|
4
9
|
import type {
|
|
5
10
|
ThreadOps,
|
|
6
11
|
PrefixedThreadOps,
|
|
@@ -9,21 +14,35 @@ import type {
|
|
|
9
14
|
import type { ModelInvoker } from "../../../lib/model";
|
|
10
15
|
import type { StoredMessage } from "@langchain/core/messages";
|
|
11
16
|
import type { BaseChatModel } from "@langchain/core/language_models/chat_models";
|
|
12
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
createLangChainThreadManager,
|
|
19
|
+
type LangChainContent,
|
|
20
|
+
type LangChainThreadManagerHooks,
|
|
21
|
+
} from "./thread-manager";
|
|
13
22
|
import { createLangChainModelInvoker } from "./model-invoker";
|
|
14
23
|
|
|
15
24
|
const ADAPTER_PREFIX = "langChain" as const;
|
|
16
25
|
|
|
17
26
|
export type LangChainThreadOps<TScope extends string = ""> =
|
|
18
|
-
PrefixedThreadOps<ScopedPrefix<TScope, typeof ADAPTER_PREFIX
|
|
27
|
+
PrefixedThreadOps<ScopedPrefix<TScope, typeof ADAPTER_PREFIX>, LangChainContent>;
|
|
19
28
|
|
|
20
29
|
export interface LangChainAdapterConfig {
|
|
21
30
|
redis: Redis;
|
|
22
31
|
/** Optional default model — if omitted, use `createModelInvoker()` to create invokers later */
|
|
23
32
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
24
33
|
model?: BaseChatModel<any>;
|
|
34
|
+
hooks?: LangChainThreadManagerHooks;
|
|
25
35
|
}
|
|
26
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Tool response type accepted by the LangChain adapter.
|
|
39
|
+
*
|
|
40
|
+
* Content is passed directly to `ToolMessage` as `MessageContent`.
|
|
41
|
+
* Handlers can return a string or an array of content blocks
|
|
42
|
+
* (e.g. `{ type: "text", text: "..." }`, `{ type: "image_url", image_url: { ... } }`).
|
|
43
|
+
*/
|
|
44
|
+
export type LangChainToolResponse = MessageContent;
|
|
45
|
+
|
|
27
46
|
export interface LangChainAdapter {
|
|
28
47
|
/** Model invoker using the default model (only available when `model` was provided) */
|
|
29
48
|
invoker: ModelInvoker<StoredMessage>;
|
|
@@ -42,8 +61,19 @@ export interface LangChainAdapter {
|
|
|
42
61
|
* ```
|
|
43
62
|
*/
|
|
44
63
|
createActivities<S extends string = "">(
|
|
45
|
-
scope?: S
|
|
64
|
+
scope?: S,
|
|
46
65
|
): LangChainThreadOps<S>;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Identity wrapper that types a tool handler for this adapter.
|
|
69
|
+
* Constrains `toolResponse` to {@link LangChainToolResponse}.
|
|
70
|
+
*/
|
|
71
|
+
wrapHandler<TArgs, TResult, TContext extends RouterContext = RouterContext>(
|
|
72
|
+
handler: (
|
|
73
|
+
args: TArgs,
|
|
74
|
+
context: TContext,
|
|
75
|
+
) => Promise<ToolHandlerResponse<TResult, LangChainToolResponse>>,
|
|
76
|
+
): ActivityToolHandler<TArgs, TResult, TContext, LangChainToolResponse>;
|
|
47
77
|
}
|
|
48
78
|
|
|
49
79
|
/**
|
|
@@ -82,54 +112,58 @@ export interface LangChainAdapter {
|
|
|
82
112
|
* ```
|
|
83
113
|
*/
|
|
84
114
|
export function createLangChainAdapter(
|
|
85
|
-
config: LangChainAdapterConfig
|
|
115
|
+
config: LangChainAdapterConfig,
|
|
86
116
|
): LangChainAdapter {
|
|
87
117
|
const { redis } = config;
|
|
88
118
|
|
|
89
|
-
const threadOps: ThreadOps = {
|
|
90
|
-
async initializeThread(threadId: string): Promise<void> {
|
|
91
|
-
const thread = createLangChainThreadManager({ redis, threadId });
|
|
119
|
+
const threadOps: ThreadOps<LangChainContent> = {
|
|
120
|
+
async initializeThread(threadId: string, threadKey?: string): Promise<void> {
|
|
121
|
+
const thread = createLangChainThreadManager({ redis, threadId, key: threadKey });
|
|
92
122
|
await thread.initialize();
|
|
93
123
|
},
|
|
94
124
|
|
|
95
125
|
async appendHumanMessage(
|
|
96
126
|
threadId: string,
|
|
97
127
|
id: string,
|
|
98
|
-
content:
|
|
128
|
+
content: LangChainContent,
|
|
129
|
+
threadKey?: string,
|
|
99
130
|
): Promise<void> {
|
|
100
|
-
const thread = createLangChainThreadManager({ redis, threadId });
|
|
101
|
-
await thread.
|
|
131
|
+
const thread = createLangChainThreadManager({ redis, threadId, key: threadKey });
|
|
132
|
+
await thread.appendUserMessage(id, content);
|
|
102
133
|
},
|
|
103
134
|
|
|
104
135
|
async appendSystemMessage(
|
|
105
136
|
threadId: string,
|
|
106
137
|
id: string,
|
|
107
|
-
content: string
|
|
138
|
+
content: string,
|
|
139
|
+
threadKey?: string,
|
|
108
140
|
): Promise<void> {
|
|
109
|
-
const thread = createLangChainThreadManager({ redis, threadId });
|
|
141
|
+
const thread = createLangChainThreadManager({ redis, threadId, key: threadKey });
|
|
110
142
|
await thread.appendSystemMessage(id, content);
|
|
111
143
|
},
|
|
112
144
|
|
|
113
145
|
async appendToolResult(id: string, cfg: ToolResultConfig): Promise<void> {
|
|
114
|
-
const { threadId, toolCallId, content } = cfg;
|
|
115
|
-
const thread = createLangChainThreadManager({ redis, threadId });
|
|
116
|
-
await thread.
|
|
146
|
+
const { threadId, threadKey, toolCallId, content } = cfg;
|
|
147
|
+
const thread = createLangChainThreadManager({ redis, threadId, key: threadKey });
|
|
148
|
+
await thread.appendToolResult(id, toolCallId, "", content);
|
|
117
149
|
},
|
|
118
150
|
|
|
119
151
|
async forkThread(
|
|
120
152
|
sourceThreadId: string,
|
|
121
|
-
targetThreadId: string
|
|
153
|
+
targetThreadId: string,
|
|
154
|
+
threadKey?: string,
|
|
122
155
|
): Promise<void> {
|
|
123
156
|
const thread = createLangChainThreadManager({
|
|
124
157
|
redis,
|
|
125
158
|
threadId: sourceThreadId,
|
|
159
|
+
key: threadKey,
|
|
126
160
|
});
|
|
127
161
|
await thread.fork(targetThreadId);
|
|
128
162
|
},
|
|
129
163
|
};
|
|
130
164
|
|
|
131
165
|
function createActivities<S extends string = "">(
|
|
132
|
-
scope?: S
|
|
166
|
+
scope?: S,
|
|
133
167
|
): LangChainThreadOps<S> {
|
|
134
168
|
const prefix = scope
|
|
135
169
|
? `${ADAPTER_PREFIX}${scope.charAt(0).toUpperCase()}${scope.slice(1)}`
|
|
@@ -137,22 +171,22 @@ export function createLangChainAdapter(
|
|
|
137
171
|
const cap = (s: string): string =>
|
|
138
172
|
s.charAt(0).toUpperCase() + s.slice(1);
|
|
139
173
|
return Object.fromEntries(
|
|
140
|
-
Object.entries(threadOps).map(([k, v]) => [`${prefix}${cap(k)}`, v])
|
|
174
|
+
Object.entries(threadOps).map(([k, v]) => [`${prefix}${cap(k)}`, v]),
|
|
141
175
|
) as LangChainThreadOps<S>;
|
|
142
176
|
}
|
|
143
177
|
|
|
144
178
|
const makeInvoker = (
|
|
145
179
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
146
|
-
model: BaseChatModel<any
|
|
180
|
+
model: BaseChatModel<any>,
|
|
147
181
|
): ModelInvoker<StoredMessage> =>
|
|
148
|
-
createLangChainModelInvoker({ redis, model });
|
|
182
|
+
createLangChainModelInvoker({ redis, model, hooks: config.hooks });
|
|
149
183
|
|
|
150
184
|
const invoker: ModelInvoker<StoredMessage> = config.model
|
|
151
185
|
? makeInvoker(config.model)
|
|
152
186
|
: () => {
|
|
153
187
|
throw new Error(
|
|
154
188
|
"No default model provided to createLangChainAdapter. " +
|
|
155
|
-
"Either pass `model` in the config or use `createModelInvoker(model)` instead."
|
|
189
|
+
"Either pass `model` in the config or use `createModelInvoker(model)` instead.",
|
|
156
190
|
);
|
|
157
191
|
};
|
|
158
192
|
|
|
@@ -160,5 +194,6 @@ export function createLangChainAdapter(
|
|
|
160
194
|
createActivities,
|
|
161
195
|
invoker,
|
|
162
196
|
createModelInvoker: makeInvoker,
|
|
197
|
+
wrapHandler: (handler) => handler,
|
|
163
198
|
};
|
|
164
199
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { BaseMessage, MessageContent } from "@langchain/core/messages";
|
|
2
|
+
|
|
3
|
+
type ContentBlock = MessageContent extends (infer U)[] | string ? U : never;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Creates an `onPreparedMessage` hook that appends a cache-point content
|
|
7
|
+
* block to the last message in the thread.
|
|
8
|
+
*
|
|
9
|
+
* Skips appending if the last message already contains a block with the
|
|
10
|
+
* same `type`.
|
|
11
|
+
*/
|
|
12
|
+
export function appendCachePoint(
|
|
13
|
+
block: ContentBlock,
|
|
14
|
+
): (message: BaseMessage, index: number, messages: readonly BaseMessage[]) => BaseMessage {
|
|
15
|
+
return (message, index, messages) => {
|
|
16
|
+
if (index !== messages.length - 1) {
|
|
17
|
+
return message;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const { content } = message;
|
|
21
|
+
|
|
22
|
+
if (Array.isArray(content)) {
|
|
23
|
+
if (content.some((b) => b.type === block.type)) {
|
|
24
|
+
return message;
|
|
25
|
+
}
|
|
26
|
+
message.content = [...content, block];
|
|
27
|
+
return message;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (typeof content === "string") {
|
|
31
|
+
message.content = [{ type: "text", text: content }, block] satisfies MessageContent;
|
|
32
|
+
return message;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return message;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
@@ -21,6 +21,7 @@ export {
|
|
|
21
21
|
type LangChainAdapter,
|
|
22
22
|
type LangChainAdapterConfig,
|
|
23
23
|
type LangChainThreadOps,
|
|
24
|
+
type LangChainToolResponse,
|
|
24
25
|
} from "./activities";
|
|
25
26
|
|
|
26
27
|
// Thread manager
|
|
@@ -28,7 +29,8 @@ export {
|
|
|
28
29
|
createLangChainThreadManager,
|
|
29
30
|
type LangChainThreadManager,
|
|
30
31
|
type LangChainThreadManagerConfig,
|
|
31
|
-
type
|
|
32
|
+
type LangChainContent,
|
|
33
|
+
type LangChainInvocationPayload,
|
|
32
34
|
} from "./thread-manager";
|
|
33
35
|
|
|
34
36
|
// Model invoker (for advanced use — prefer adapter.createModelInvoker)
|
|
@@ -37,3 +39,6 @@ export {
|
|
|
37
39
|
invokeLangChainModel,
|
|
38
40
|
type LangChainModelInvokerConfig,
|
|
39
41
|
} from "./model-invoker";
|
|
42
|
+
|
|
43
|
+
// Hooks / utilities
|
|
44
|
+
export { appendCachePoint } from "./hooks";
|