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.
Files changed (117) hide show
  1. package/dist/activities-DE3_q9yq.d.ts +140 -0
  2. package/dist/activities-p8PDlRIK.d.cts +140 -0
  3. package/dist/adapters/sandbox/virtual/index.cjs.map +1 -1
  4. package/dist/adapters/sandbox/virtual/index.d.cts +8 -7
  5. package/dist/adapters/sandbox/virtual/index.d.ts +8 -7
  6. package/dist/adapters/sandbox/virtual/index.js.map +1 -1
  7. package/dist/adapters/sandbox/virtual/workflow.d.cts +3 -2
  8. package/dist/adapters/sandbox/virtual/workflow.d.ts +3 -2
  9. package/dist/adapters/thread/anthropic/index.cjs +363 -0
  10. package/dist/adapters/thread/anthropic/index.cjs.map +1 -0
  11. package/dist/adapters/thread/anthropic/index.d.cts +151 -0
  12. package/dist/adapters/thread/anthropic/index.d.ts +151 -0
  13. package/dist/adapters/thread/anthropic/index.js +358 -0
  14. package/dist/adapters/thread/anthropic/index.js.map +1 -0
  15. package/dist/adapters/thread/anthropic/workflow.cjs +38 -0
  16. package/dist/adapters/thread/anthropic/workflow.cjs.map +1 -0
  17. package/dist/adapters/thread/anthropic/workflow.d.cts +37 -0
  18. package/dist/adapters/thread/anthropic/workflow.d.ts +37 -0
  19. package/dist/adapters/thread/anthropic/workflow.js +36 -0
  20. package/dist/adapters/thread/anthropic/workflow.js.map +1 -0
  21. package/dist/adapters/thread/google-genai/index.cjs +102 -99
  22. package/dist/adapters/thread/google-genai/index.cjs.map +1 -1
  23. package/dist/adapters/thread/google-genai/index.d.cts +14 -113
  24. package/dist/adapters/thread/google-genai/index.d.ts +14 -113
  25. package/dist/adapters/thread/google-genai/index.js +103 -99
  26. package/dist/adapters/thread/google-genai/index.js.map +1 -1
  27. package/dist/adapters/thread/google-genai/workflow.cjs +9 -4
  28. package/dist/adapters/thread/google-genai/workflow.cjs.map +1 -1
  29. package/dist/adapters/thread/google-genai/workflow.d.cts +10 -5
  30. package/dist/adapters/thread/google-genai/workflow.d.ts +10 -5
  31. package/dist/adapters/thread/google-genai/workflow.js +9 -4
  32. package/dist/adapters/thread/google-genai/workflow.js.map +1 -1
  33. package/dist/adapters/thread/langchain/index.cjs +73 -63
  34. package/dist/adapters/thread/langchain/index.cjs.map +1 -1
  35. package/dist/adapters/thread/langchain/index.d.cts +39 -40
  36. package/dist/adapters/thread/langchain/index.d.ts +39 -40
  37. package/dist/adapters/thread/langchain/index.js +73 -64
  38. package/dist/adapters/thread/langchain/index.js.map +1 -1
  39. package/dist/adapters/thread/langchain/workflow.cjs +9 -4
  40. package/dist/adapters/thread/langchain/workflow.cjs.map +1 -1
  41. package/dist/adapters/thread/langchain/workflow.d.cts +10 -5
  42. package/dist/adapters/thread/langchain/workflow.d.ts +10 -5
  43. package/dist/adapters/thread/langchain/workflow.js +9 -4
  44. package/dist/adapters/thread/langchain/workflow.js.map +1 -1
  45. package/dist/index.cjs +27 -10
  46. package/dist/index.cjs.map +1 -1
  47. package/dist/index.d.cts +13 -12
  48. package/dist/index.d.ts +13 -12
  49. package/dist/index.js +28 -11
  50. package/dist/index.js.map +1 -1
  51. package/dist/proxy-BK1ydQt0.d.ts +24 -0
  52. package/dist/proxy-BMAsMHdp.d.cts +24 -0
  53. package/dist/{queries-DwBe2CAA.d.ts → queries-BCgJ9Sr5.d.ts} +1 -1
  54. package/dist/{queries-BYGBImeC.d.cts → queries-DwnE2bu3.d.cts} +1 -1
  55. package/dist/thread-manager-Bh9x847n.d.ts +31 -0
  56. package/dist/thread-manager-BlHua5_v.d.cts +39 -0
  57. package/dist/thread-manager-Bz8txKKj.d.cts +31 -0
  58. package/dist/thread-manager-dzaJHQEA.d.ts +39 -0
  59. package/dist/types-BfIQABzu.d.cts +73 -0
  60. package/dist/types-CIkYBoF8.d.ts +73 -0
  61. package/dist/{types-hmferhc2.d.ts → types-CvJyXDYt.d.ts} +44 -123
  62. package/dist/{types-LVKmCNds.d.ts → types-DFUNSYbj.d.ts} +1 -1
  63. package/dist/{types-Bf8KV0Ci.d.cts → types-DRnz-OZp.d.cts} +1 -1
  64. package/dist/{types-7PeMi1bD.d.cts → types-DSOefLpY.d.cts} +44 -123
  65. package/dist/{types-D_igp10o.d.cts → types-mCVxKIZb.d.cts} +233 -137
  66. package/dist/{types-D_igp10o.d.ts → types-mCVxKIZb.d.ts} +233 -137
  67. package/dist/workflow.cjs +25 -9
  68. package/dist/workflow.cjs.map +1 -1
  69. package/dist/workflow.d.cts +11 -11
  70. package/dist/workflow.d.ts +11 -11
  71. package/dist/workflow.js +26 -10
  72. package/dist/workflow.js.map +1 -1
  73. package/package.json +26 -1
  74. package/src/adapters/sandbox/virtual/with-virtual-sandbox.ts +8 -3
  75. package/src/adapters/thread/anthropic/activities.ts +226 -0
  76. package/src/adapters/thread/anthropic/index.ts +44 -0
  77. package/src/adapters/thread/anthropic/model-invoker.ts +129 -0
  78. package/src/adapters/thread/anthropic/proxy.ts +33 -0
  79. package/src/adapters/thread/anthropic/thread-manager.test.ts +137 -0
  80. package/src/adapters/thread/anthropic/thread-manager.ts +202 -0
  81. package/src/adapters/thread/google-genai/activities.ts +110 -33
  82. package/src/adapters/thread/google-genai/index.ts +3 -1
  83. package/src/adapters/thread/google-genai/model-invoker.ts +13 -42
  84. package/src/adapters/thread/google-genai/proxy.ts +6 -34
  85. package/src/adapters/thread/google-genai/thread-manager.test.ts +159 -0
  86. package/src/adapters/thread/google-genai/thread-manager.ts +96 -105
  87. package/src/adapters/thread/langchain/activities.ts +56 -21
  88. package/src/adapters/thread/langchain/hooks.ts +37 -0
  89. package/src/adapters/thread/langchain/index.ts +6 -1
  90. package/src/adapters/thread/langchain/model-invoker.ts +13 -12
  91. package/src/adapters/thread/langchain/proxy.ts +6 -34
  92. package/src/adapters/thread/langchain/thread-manager.test.ts +144 -0
  93. package/src/adapters/thread/langchain/thread-manager.ts +55 -98
  94. package/src/index.ts +5 -1
  95. package/src/lib/activity.ts +4 -3
  96. package/src/lib/hooks/types.ts +12 -12
  97. package/src/lib/model/types.ts +2 -0
  98. package/src/lib/session/session-edge-cases.integration.test.ts +24 -6
  99. package/src/lib/session/session.ts +18 -14
  100. package/src/lib/session/types.ts +31 -14
  101. package/src/lib/subagent/handler.ts +15 -8
  102. package/src/lib/subagent/types.ts +3 -2
  103. package/src/lib/thread/index.ts +3 -0
  104. package/src/lib/thread/manager.ts +4 -7
  105. package/src/lib/thread/proxy.ts +57 -0
  106. package/src/lib/thread/types.ts +44 -0
  107. package/src/lib/tool-router/auto-append-sandbox.integration.test.ts +54 -0
  108. package/src/lib/tool-router/auto-append.ts +5 -2
  109. package/src/lib/tool-router/router-edge-cases.integration.test.ts +9 -5
  110. package/src/lib/tool-router/router.ts +13 -7
  111. package/src/lib/tool-router/types.ts +20 -13
  112. package/src/lib/tool-router/with-sandbox.ts +4 -3
  113. package/src/lib/types.ts +7 -14
  114. package/src/workflow.ts +0 -4
  115. package/tsup.config.ts +5 -0
  116. package/dist/types-35POpVfa.d.cts +0 -40
  117. package/dist/types-35POpVfa.d.ts +0 -40
@@ -0,0 +1,202 @@
1
+ import type Redis from "ioredis";
2
+ import type Anthropic from "@anthropic-ai/sdk";
3
+ import type { JsonValue } from "../../../lib/state/types";
4
+ import {
5
+ createThreadManager,
6
+ type ProviderThreadManager,
7
+ type ThreadManagerConfig,
8
+ type ThreadManagerHooks,
9
+ } from "../../../lib/thread";
10
+
11
+ /** SDK-native content type for Anthropic human messages */
12
+ export type AnthropicContent =
13
+ | string
14
+ | Anthropic.Messages.ContentBlockParam[];
15
+
16
+ /** A MessageParam with a unique ID for idempotent Redis storage */
17
+ export interface StoredMessage {
18
+ id: string;
19
+ message: Anthropic.Messages.MessageParam;
20
+ /** System messages are stored separately since Anthropic passes them via config */
21
+ isSystem?: boolean;
22
+ }
23
+
24
+ export type AnthropicThreadManagerHooks = ThreadManagerHooks<StoredMessage, Anthropic.Messages.MessageParam>;
25
+
26
+ export interface AnthropicThreadManagerConfig {
27
+ redis: Redis;
28
+ threadId: string;
29
+ /** Thread key, defaults to 'messages' */
30
+ key?: string;
31
+ hooks?: AnthropicThreadManagerHooks;
32
+ }
33
+
34
+ /** Prepared payload ready to send to the Anthropic API */
35
+ export interface AnthropicInvocationPayload {
36
+ messages: Anthropic.Messages.MessageParam[];
37
+ system?: string;
38
+ }
39
+
40
+ /** Thread manager with Anthropic MessageParam convenience helpers */
41
+ export interface AnthropicThreadManager
42
+ extends ProviderThreadManager<StoredMessage, AnthropicContent> {
43
+ appendAssistantMessage(
44
+ id: string,
45
+ content: Anthropic.Messages.ContentBlock[],
46
+ ): Promise<void>;
47
+ prepareForInvocation(): Promise<AnthropicInvocationPayload>;
48
+ }
49
+
50
+ function storedMessageId(msg: StoredMessage): string {
51
+ return msg.id;
52
+ }
53
+
54
+ /** Normalise content into an array of ContentBlockParam */
55
+ function toContentBlocks(
56
+ content: AnthropicContent,
57
+ ): Anthropic.Messages.ContentBlockParam[] {
58
+ if (typeof content === "string") {
59
+ return [{ type: "text", text: content }];
60
+ }
61
+ return content;
62
+ }
63
+
64
+ /**
65
+ * Merge consecutive messages with the same role.
66
+ * The Anthropic API requires alternating user/assistant turns; without
67
+ * merging, multiple sequential tool-result messages would violate this.
68
+ */
69
+ function mergeConsecutiveMessages(
70
+ messages: Anthropic.Messages.MessageParam[],
71
+ ): Anthropic.Messages.MessageParam[] {
72
+ const merged: Anthropic.Messages.MessageParam[] = [];
73
+ for (const msg of messages) {
74
+ const last = merged[merged.length - 1];
75
+ if (last && last.role === msg.role) {
76
+ const lastContent = Array.isArray(last.content)
77
+ ? last.content
78
+ : [{ type: "text" as const, text: last.content }];
79
+ const msgContent = Array.isArray(msg.content)
80
+ ? msg.content
81
+ : [{ type: "text" as const, text: msg.content }];
82
+ last.content = [...lastContent, ...msgContent];
83
+ } else {
84
+ merged.push({
85
+ ...msg,
86
+ content: Array.isArray(msg.content)
87
+ ? [...msg.content]
88
+ : msg.content,
89
+ });
90
+ }
91
+ }
92
+ return merged;
93
+ }
94
+
95
+ /**
96
+ * Creates an Anthropic-specific thread manager that stores StoredMessage
97
+ * instances in Redis and provides convenience helpers for creating and
98
+ * appending typed messages.
99
+ */
100
+ export function createAnthropicThreadManager(
101
+ config: AnthropicThreadManagerConfig,
102
+ ): AnthropicThreadManager {
103
+ const baseConfig: ThreadManagerConfig<StoredMessage> = {
104
+ redis: config.redis,
105
+ threadId: config.threadId,
106
+ key: config.key,
107
+ idOf: storedMessageId,
108
+ };
109
+
110
+ const base = createThreadManager(baseConfig);
111
+
112
+ const helpers: Omit<AnthropicThreadManager, keyof typeof base> = {
113
+ async appendUserMessage(
114
+ id: string,
115
+ content: AnthropicContent,
116
+ ): Promise<void> {
117
+ await base.append([{
118
+ id,
119
+ message: { role: "user", content: toContentBlocks(content) },
120
+ }]);
121
+ },
122
+
123
+ async appendSystemMessage(id: string, content: string): Promise<void> {
124
+ await base.initialize();
125
+ await base.append([{
126
+ id,
127
+ message: { role: "user", content },
128
+ isSystem: true,
129
+ }]);
130
+ },
131
+
132
+ async appendAssistantMessage(
133
+ id: string,
134
+ content: Anthropic.Messages.ContentBlock[],
135
+ ): Promise<void> {
136
+ await base.append([{
137
+ id,
138
+ message: {
139
+ role: "assistant",
140
+ content: content as unknown as Anthropic.Messages.ContentBlockParam[],
141
+ },
142
+ }]);
143
+ },
144
+
145
+ async appendToolResult(
146
+ id: string,
147
+ toolCallId: string,
148
+ _toolName: string,
149
+ content: JsonValue,
150
+ ): Promise<void> {
151
+ const toolContent =
152
+ typeof content === "string"
153
+ ? content
154
+ : Array.isArray(content)
155
+ ? (content as unknown as Anthropic.Messages.ToolResultBlockParam["content"])
156
+ : JSON.stringify(content);
157
+ await base.append([{
158
+ id,
159
+ message: {
160
+ role: "user",
161
+ content: [{
162
+ type: "tool_result" as const,
163
+ tool_use_id: toolCallId,
164
+ content: toolContent,
165
+ }],
166
+ },
167
+ }]);
168
+ },
169
+
170
+ async prepareForInvocation(): Promise<AnthropicInvocationPayload> {
171
+ const stored = await base.load();
172
+ const { onPrepareMessage, onPreparedMessage } = config.hooks ?? {};
173
+ const mapped = onPrepareMessage
174
+ ? stored.map((msg, i) => onPrepareMessage(msg, i, stored))
175
+ : stored;
176
+
177
+ let system: string | undefined;
178
+ const conversationMessages: Anthropic.Messages.MessageParam[] = [];
179
+
180
+ for (const item of mapped) {
181
+ if (item.isSystem) {
182
+ system =
183
+ typeof item.message.content === "string"
184
+ ? item.message.content
185
+ : undefined;
186
+ } else {
187
+ conversationMessages.push(item.message);
188
+ }
189
+ }
190
+
191
+ const messages = mergeConsecutiveMessages(conversationMessages);
192
+ return {
193
+ messages: onPreparedMessage
194
+ ? messages.map((msg, i) => onPreparedMessage(msg, i, messages))
195
+ : messages,
196
+ ...(system ? { system } : {}),
197
+ };
198
+ },
199
+ };
200
+
201
+ return Object.assign(base, helpers);
202
+ }
@@ -1,33 +1,69 @@
1
1
  import type Redis from "ioredis";
2
- import type { GoogleGenAI, Content } from "@google/genai";
2
+ import type { GoogleGenAI, Content, Part } from "@google/genai";
3
3
  import type { ToolResultConfig } from "../../../lib/types";
4
- import type { MessageContent } from "../../../lib/types";
4
+ import type {
5
+ ActivityToolHandler,
6
+ RouterContext,
7
+ ToolHandlerResponse,
8
+ } from "../../../lib/tool-router/types";
5
9
  import type {
6
10
  ThreadOps,
7
11
  PrefixedThreadOps,
8
12
  ScopedPrefix,
9
13
  } from "../../../lib/session/types";
10
14
  import type { ModelInvoker } from "../../../lib/model";
11
- import { createGoogleGenAIThreadManager } from "./thread-manager";
15
+ import {
16
+ createGoogleGenAIThreadManager,
17
+ type GoogleGenAIContent,
18
+ type GoogleGenAIThreadManagerHooks,
19
+ } from "./thread-manager";
12
20
  import { createGoogleGenAIModelInvoker } from "./model-invoker";
13
21
 
14
22
  const ADAPTER_PREFIX = "googleGenAI" as const;
15
23
 
16
24
  export type GoogleGenAIThreadOps<TScope extends string = ""> =
17
- PrefixedThreadOps<ScopedPrefix<TScope, typeof ADAPTER_PREFIX>>;
25
+ PrefixedThreadOps<
26
+ ScopedPrefix<TScope, typeof ADAPTER_PREFIX>,
27
+ GoogleGenAIContent
28
+ >;
18
29
 
19
30
  export interface GoogleGenAIAdapterConfig {
20
31
  redis: Redis;
21
- client: GoogleGenAI;
32
+ client?: GoogleGenAI;
22
33
  /** Default model name (e.g. 'gemini-2.5-flash'). If omitted, use `createModelInvoker()` */
23
34
  model?: string;
35
+ hooks?: GoogleGenAIThreadManagerHooks;
24
36
  }
25
37
 
38
+ /**
39
+ * Tool response type accepted by the Google GenAI adapter.
40
+ *
41
+ * Handlers can return:
42
+ * - **`string`** — plain text, wrapped in a `functionResponse` part.
43
+ * - **`Record<string, unknown>`** — structured object used as `functionResponse.response`.
44
+ * - **`Part[]`** — pre-built parts used directly as `Content.parts`.
45
+ * The handler is responsible for building correct Part objects (e.g. `functionResponse`,
46
+ * `inlineData`, `text`). Use `context.toolCallId` and `context.toolName` to construct
47
+ * `functionResponse` parts.
48
+ *
49
+ * @example
50
+ * ```typescript
51
+ * adapter.wrapHandler(async (args, ctx) => ({
52
+ * toolResponse: [
53
+ * { functionResponse: { id: ctx.toolCallId, name: ctx.toolName, response: { result: "done" } } },
54
+ * { inlineData: { data: base64, mimeType: "image/png" } },
55
+ * ],
56
+ * data: null,
57
+ * }));
58
+ * ```
59
+ */
60
+ export type GoogleGenAIToolResponse = string | Record<string, unknown> | Part[];
61
+
26
62
  export interface GoogleGenAIAdapter {
27
63
  /** Model invoker using the default model (only available when `model` was provided) */
28
64
  invoker: ModelInvoker<Content>;
29
65
  /** Create an invoker for a specific model name (for multi-model setups) */
30
- createModelInvoker(model: string): ModelInvoker<Content>;
66
+ createModelInvoker(model: string, client: GoogleGenAI): ModelInvoker<Content>;
31
67
  /**
32
68
  * Create prefixed thread activities for registration on the worker.
33
69
  *
@@ -43,9 +79,18 @@ export interface GoogleGenAIAdapter {
43
79
  * // → { googleGenAIResearchAgentInitializeThread, … }
44
80
  * ```
45
81
  */
46
- createActivities<S extends string = "">(
47
- scope?: S
48
- ): GoogleGenAIThreadOps<S>;
82
+ createActivities<S extends string = "">(scope?: S): GoogleGenAIThreadOps<S>;
83
+
84
+ /**
85
+ * Identity wrapper that types a tool handler for this adapter.
86
+ * Constrains `toolResponse` to {@link GoogleGenAIToolResponse}.
87
+ */
88
+ wrapHandler<TArgs, TResult, TContext extends RouterContext = RouterContext>(
89
+ handler: (
90
+ args: TArgs,
91
+ context: TContext
92
+ ) => Promise<ToolHandlerResponse<TResult, GoogleGenAIToolResponse>>
93
+ ): ActivityToolHandler<TArgs, TResult, TContext, GoogleGenAIToolResponse>;
49
94
  }
50
95
 
51
96
  /**
@@ -91,45 +136,73 @@ export interface GoogleGenAIAdapter {
91
136
  export function createGoogleGenAIAdapter(
92
137
  config: GoogleGenAIAdapterConfig
93
138
  ): GoogleGenAIAdapter {
94
- const { redis, client } = config;
139
+ const { redis } = config;
95
140
 
96
- const threadOps: ThreadOps = {
97
- async initializeThread(threadId: string): Promise<void> {
98
- const thread = createGoogleGenAIThreadManager({ redis, threadId });
141
+ const threadOps: ThreadOps<GoogleGenAIContent> = {
142
+ async initializeThread(
143
+ threadId: string,
144
+ threadKey?: string
145
+ ): Promise<void> {
146
+ const thread = createGoogleGenAIThreadManager({
147
+ redis,
148
+ threadId,
149
+ key: threadKey,
150
+ });
99
151
  await thread.initialize();
100
152
  },
101
153
 
102
154
  async appendHumanMessage(
103
155
  threadId: string,
104
156
  id: string,
105
- content: string | MessageContent
157
+ content: GoogleGenAIContent,
158
+ threadKey?: string
106
159
  ): Promise<void> {
107
- const thread = createGoogleGenAIThreadManager({ redis, threadId });
160
+ const thread = createGoogleGenAIThreadManager({
161
+ redis,
162
+ threadId,
163
+ key: threadKey,
164
+ });
108
165
  await thread.appendUserMessage(id, content);
109
166
  },
110
167
 
111
168
  async appendSystemMessage(
112
169
  threadId: string,
113
170
  id: string,
114
- content: string
171
+ content: string,
172
+ threadKey?: string
115
173
  ): Promise<void> {
116
- const thread = createGoogleGenAIThreadManager({ redis, threadId });
174
+ const thread = createGoogleGenAIThreadManager({
175
+ redis,
176
+ threadId,
177
+ key: threadKey,
178
+ });
117
179
  await thread.appendSystemMessage(id, content);
118
180
  },
119
181
 
120
182
  async appendToolResult(id: string, cfg: ToolResultConfig): Promise<void> {
121
- const { threadId, toolCallId, toolName, content } = cfg;
122
- const thread = createGoogleGenAIThreadManager({ redis, threadId });
123
- await thread.appendToolResult(id, toolCallId, toolName, content);
183
+ const { threadId, threadKey, toolCallId, toolName, content } = cfg;
184
+ const thread = createGoogleGenAIThreadManager({
185
+ redis,
186
+ threadId,
187
+ key: threadKey,
188
+ });
189
+ await thread.appendToolResult(
190
+ id,
191
+ toolCallId,
192
+ toolName,
193
+ content as GoogleGenAIToolResponse
194
+ );
124
195
  },
125
196
 
126
197
  async forkThread(
127
198
  sourceThreadId: string,
128
- targetThreadId: string
199
+ targetThreadId: string,
200
+ threadKey?: string
129
201
  ): Promise<void> {
130
202
  const thread = createGoogleGenAIThreadManager({
131
203
  redis,
132
204
  threadId: sourceThreadId,
205
+ key: threadKey,
133
206
  });
134
207
  await thread.fork(targetThreadId);
135
208
  },
@@ -141,28 +214,32 @@ export function createGoogleGenAIAdapter(
141
214
  const prefix = scope
142
215
  ? `${ADAPTER_PREFIX}${scope.charAt(0).toUpperCase()}${scope.slice(1)}`
143
216
  : ADAPTER_PREFIX;
144
- const cap = (s: string): string =>
145
- s.charAt(0).toUpperCase() + s.slice(1);
217
+ const cap = (s: string): string => s.charAt(0).toUpperCase() + s.slice(1);
146
218
  return Object.fromEntries(
147
219
  Object.entries(threadOps).map(([k, v]) => [`${prefix}${cap(k)}`, v])
148
220
  ) as GoogleGenAIThreadOps<S>;
149
221
  }
150
222
 
151
- const makeInvoker = (model: string): ModelInvoker<Content> =>
152
- createGoogleGenAIModelInvoker({ redis, client, model });
223
+ const makeInvoker = (
224
+ model: string,
225
+ client: GoogleGenAI
226
+ ): ModelInvoker<Content> =>
227
+ createGoogleGenAIModelInvoker({ redis, client, model, hooks: config.hooks });
153
228
 
154
- const invoker: ModelInvoker<Content> = config.model
155
- ? makeInvoker(config.model)
156
- : ((() => {
157
- throw new Error(
158
- "No default model provided to createGoogleGenAIAdapter. " +
159
- "Either pass `model` in the config or use `createModelInvoker(model)` instead."
160
- );
161
- }) as unknown as ModelInvoker<Content>);
229
+ const invoker: ModelInvoker<Content> =
230
+ config.model && config.client
231
+ ? makeInvoker(config.model, config.client)
232
+ : ((() => {
233
+ throw new Error(
234
+ "No default model provided to createGoogleGenAIAdapter. " +
235
+ "Either pass `model` in the config or use `createModelInvoker(model)` instead."
236
+ );
237
+ }) as unknown as ModelInvoker<Content>);
162
238
 
163
239
  return {
164
240
  createActivities,
165
241
  invoker,
166
242
  createModelInvoker: makeInvoker,
243
+ wrapHandler: (handler) => handler,
167
244
  };
168
245
  }
@@ -23,14 +23,16 @@ export {
23
23
  type GoogleGenAIAdapter,
24
24
  type GoogleGenAIAdapterConfig,
25
25
  type GoogleGenAIThreadOps,
26
+ type GoogleGenAIToolResponse,
26
27
  } from "./activities";
27
28
 
28
29
  // Thread manager
29
30
  export {
30
31
  createGoogleGenAIThreadManager,
31
- messageContentToParts,
32
32
  type GoogleGenAIThreadManager,
33
33
  type GoogleGenAIThreadManagerConfig,
34
+ type GoogleGenAIContent,
35
+ type GoogleGenAIInvocationPayload,
34
36
  type StoredContent,
35
37
  } from "./thread-manager";
36
38
 
@@ -1,19 +1,19 @@
1
1
  import type Redis from "ioredis";
2
2
  import type { GoogleGenAI, Content, FunctionDeclaration } from "@google/genai";
3
3
  import type { SerializableToolDefinition } from "../../../lib/types";
4
- import type { AgentResponse } from "../../../lib/model";
5
- import type { ModelInvokerConfig } from "../../../lib/model";
6
- import { createGoogleGenAIThreadManager } from "./thread-manager";
4
+ import type { AgentResponse, ModelInvokerConfig } from "../../../lib/model";
5
+ import { createGoogleGenAIThreadManager, type GoogleGenAIThreadManagerHooks } from "./thread-manager";
7
6
  import { v4 as uuidv4 } from "uuid";
8
7
 
9
8
  export interface GoogleGenAIModelInvokerConfig {
10
9
  redis: Redis;
11
10
  client: GoogleGenAI;
12
11
  model: string;
12
+ hooks?: GoogleGenAIThreadManagerHooks;
13
13
  }
14
14
 
15
15
  function toFunctionDeclarations(
16
- tools: SerializableToolDefinition[]
16
+ tools: SerializableToolDefinition[],
17
17
  ): FunctionDeclaration[] {
18
18
  return tools.map((t) => ({
19
19
  name: t.name,
@@ -22,24 +22,6 @@ function toFunctionDeclarations(
22
22
  }));
23
23
  }
24
24
 
25
- /**
26
- * Merge consecutive Content objects sharing the same role.
27
- * The Gemini API requires alternating user/model turns; without
28
- * merging, multiple sequential tool-result messages would violate this.
29
- */
30
- function mergeConsecutiveContents(contents: Content[]): Content[] {
31
- const merged: Content[] = [];
32
- for (const content of contents) {
33
- const last = merged[merged.length - 1];
34
- if (last && last.role === content.role) {
35
- last.parts = [...(last.parts ?? []), ...(content.parts ?? [])];
36
- } else {
37
- merged.push({ ...content, parts: [...(content.parts ?? [])] });
38
- }
39
- }
40
- return merged;
41
- }
42
-
43
25
  /**
44
26
  * Creates a Google GenAI model invoker that satisfies the generic
45
27
  * `ModelInvoker<Content>` contract.
@@ -68,29 +50,16 @@ export function createGoogleGenAIModelInvoker({
68
50
  redis,
69
51
  client,
70
52
  model,
53
+ hooks,
71
54
  }: GoogleGenAIModelInvokerConfig) {
72
55
  return async function invokeGoogleGenAIModel(
73
- config: ModelInvokerConfig
56
+ config: ModelInvokerConfig,
74
57
  ): Promise<AgentResponse<Content>> {
75
- const { threadId, state } = config;
76
-
77
- const thread = createGoogleGenAIThreadManager({ redis, threadId });
78
- const stored = await thread.load();
79
-
80
- // Separate system instructions from conversation content.
81
- // Google GenAI takes system instructions via config, not in the contents array.
82
- let systemInstruction: string | undefined;
83
- const conversationContents: Content[] = [];
84
-
85
- for (const item of stored) {
86
- if (item.content.role === "system") {
87
- systemInstruction = item.content.parts?.[0]?.text;
88
- } else {
89
- conversationContents.push(item.content);
90
- }
91
- }
58
+ const { threadId, threadKey, state } = config;
92
59
 
93
- const contents = mergeConsecutiveContents(conversationContents);
60
+ const thread = createGoogleGenAIThreadManager({ redis, threadId, key: threadKey, hooks });
61
+ const { contents, systemInstruction } =
62
+ await thread.prepareForInvocation();
94
63
 
95
64
  const functionDeclarations = toFunctionDeclarations(state.tools);
96
65
  const tools =
@@ -137,13 +106,15 @@ export async function invokeGoogleGenAIModel({
137
106
  redis,
138
107
  client,
139
108
  model,
109
+ hooks,
140
110
  config,
141
111
  }: {
142
112
  redis: Redis;
143
113
  client: GoogleGenAI;
144
114
  model: string;
115
+ hooks?: GoogleGenAIThreadManagerHooks;
145
116
  config: ModelInvokerConfig;
146
117
  }): Promise<AgentResponse<Content>> {
147
- const invoker = createGoogleGenAIModelInvoker({ redis, client, model });
118
+ const invoker = createGoogleGenAIModelInvoker({ redis, client, model, hooks });
148
119
  return invoker(config);
149
120
  }
@@ -18,44 +18,16 @@
18
18
  * const threadOps = proxyGoogleGenAIThreadOps("customScope");
19
19
  * ```
20
20
  */
21
- import {
22
- proxyActivities,
23
- workflowInfo,
24
- type ActivityInterfaceFor,
25
- } from "@temporalio/workflow";
21
+ import { type ActivityInterfaceFor } from "@temporalio/workflow";
26
22
  import type { ThreadOps } from "../../../lib/session/types";
23
+ import type { GoogleGenAIContent } from "./thread-manager";
24
+ import { createThreadOpsProxy } from "../../../lib/thread/proxy";
27
25
 
28
26
  const ADAPTER_PREFIX = "googleGenAI";
29
27
 
30
28
  export function proxyGoogleGenAIThreadOps(
31
29
  scope?: string,
32
- options?: Parameters<typeof proxyActivities>[0]
33
- ): ActivityInterfaceFor<ThreadOps> {
34
- const resolvedScope = scope ?? workflowInfo().workflowType;
35
-
36
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
37
- const acts = proxyActivities<Record<string, (...args: any[]) => any>>(
38
- options ?? {
39
- startToCloseTimeout: "10s",
40
- retry: {
41
- maximumAttempts: 6,
42
- initialInterval: "5s",
43
- maximumInterval: "15m",
44
- backoffCoefficient: 4,
45
- },
46
- }
47
- );
48
-
49
- const prefix =
50
- `${ADAPTER_PREFIX}${resolvedScope.charAt(0).toUpperCase()}${resolvedScope.slice(1)}`;
51
- const p = (key: string): string =>
52
- `${prefix}${key.charAt(0).toUpperCase()}${key.slice(1)}`;
53
-
54
- return {
55
- initializeThread: acts[p("initializeThread")],
56
- appendHumanMessage: acts[p("appendHumanMessage")],
57
- appendToolResult: acts[p("appendToolResult")],
58
- appendSystemMessage: acts[p("appendSystemMessage")],
59
- forkThread: acts[p("forkThread")],
60
- } as ActivityInterfaceFor<ThreadOps>;
30
+ options?: Parameters<typeof createThreadOpsProxy>[2],
31
+ ): ActivityInterfaceFor<ThreadOps<GoogleGenAIContent>> {
32
+ return createThreadOpsProxy(ADAPTER_PREFIX, scope, options) as ActivityInterfaceFor<ThreadOps<GoogleGenAIContent>>;
61
33
  }