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.
Files changed (127) hide show
  1. package/README.md +26 -23
  2. package/dist/adapters/sandbox/daytona/index.cjs.map +1 -1
  3. package/dist/adapters/sandbox/daytona/index.d.cts +3 -3
  4. package/dist/adapters/sandbox/daytona/index.d.ts +3 -3
  5. package/dist/adapters/sandbox/daytona/index.js.map +1 -1
  6. package/dist/adapters/sandbox/daytona/workflow.d.cts +2 -2
  7. package/dist/adapters/sandbox/daytona/workflow.d.ts +2 -2
  8. package/dist/adapters/sandbox/e2b/index.cjs.map +1 -1
  9. package/dist/adapters/sandbox/e2b/index.d.cts +1 -1
  10. package/dist/adapters/sandbox/e2b/index.d.ts +1 -1
  11. package/dist/adapters/sandbox/e2b/index.js.map +1 -1
  12. package/dist/adapters/sandbox/e2b/workflow.d.cts +1 -1
  13. package/dist/adapters/sandbox/e2b/workflow.d.ts +1 -1
  14. package/dist/adapters/thread/anthropic/index.cjs +60 -55
  15. package/dist/adapters/thread/anthropic/index.cjs.map +1 -1
  16. package/dist/adapters/thread/anthropic/index.d.cts +20 -15
  17. package/dist/adapters/thread/anthropic/index.d.ts +20 -15
  18. package/dist/adapters/thread/anthropic/index.js +60 -55
  19. package/dist/adapters/thread/anthropic/index.js.map +1 -1
  20. package/dist/adapters/thread/anthropic/workflow.d.cts +7 -7
  21. package/dist/adapters/thread/anthropic/workflow.d.ts +7 -7
  22. package/dist/adapters/thread/google-genai/index.cjs +135 -66
  23. package/dist/adapters/thread/google-genai/index.cjs.map +1 -1
  24. package/dist/adapters/thread/google-genai/index.d.cts +200 -26
  25. package/dist/adapters/thread/google-genai/index.d.ts +200 -26
  26. package/dist/adapters/thread/google-genai/index.js +135 -66
  27. package/dist/adapters/thread/google-genai/index.js.map +1 -1
  28. package/dist/adapters/thread/google-genai/workflow.d.cts +8 -8
  29. package/dist/adapters/thread/google-genai/workflow.d.ts +8 -8
  30. package/dist/adapters/thread/langchain/index.cjs +67 -55
  31. package/dist/adapters/thread/langchain/index.cjs.map +1 -1
  32. package/dist/adapters/thread/langchain/index.d.cts +20 -15
  33. package/dist/adapters/thread/langchain/index.d.ts +20 -15
  34. package/dist/adapters/thread/langchain/index.js +67 -55
  35. package/dist/adapters/thread/langchain/index.js.map +1 -1
  36. package/dist/adapters/thread/langchain/workflow.d.cts +7 -7
  37. package/dist/adapters/thread/langchain/workflow.d.ts +7 -7
  38. package/dist/{cold-store-DKMAO1Dd.d.ts → cold-store-DyHodfAB.d.ts} +1 -1
  39. package/dist/{cold-store-CkWoNtMh.d.cts → cold-store-YOx9nmgR.d.cts} +1 -1
  40. package/dist/index.cjs +15050 -420
  41. package/dist/index.cjs.map +1 -1
  42. package/dist/index.d.cts +79 -83
  43. package/dist/index.d.ts +79 -83
  44. package/dist/index.js +15051 -417
  45. package/dist/index.js.map +1 -1
  46. package/dist/{proxy-B7CWEV-T.d.cts → proxy-2htgGQrc.d.cts} +1 -1
  47. package/dist/{proxy-ByFHMVRX.d.ts → proxy-CmiTP4pp.d.ts} +1 -1
  48. package/dist/{thread-manager-nK-WcFzM.d.ts → thread-manager-BJ5pz5Cx.d.cts} +6 -7
  49. package/dist/{thread-manager-7AW4rhfu.d.ts → thread-manager-BQAbrYXH.d.cts} +6 -7
  50. package/dist/{thread-manager-Cibe0X5m.d.cts → thread-manager-CcvltOuq.d.ts} +6 -7
  51. package/dist/{thread-manager-B9rtMEVn.d.cts → thread-manager-DHAbncHX.d.ts} +6 -7
  52. package/dist/{types-gVa5XCWD.d.ts → types-BQvXWcft.d.ts} +1 -1
  53. package/dist/{types-XUUFvrJ9.d.cts → types-BjdqxKYp.d.cts} +709 -709
  54. package/dist/{types-CJ7tCdl6.d.ts → types-D8W5TnSa.d.cts} +3 -3
  55. package/dist/{types-CJ7tCdl6.d.cts → types-D8W5TnSa.d.ts} +3 -3
  56. package/dist/{types-DO4Tkwxo.d.ts → types-DEbkLA06.d.ts} +3 -3
  57. package/dist/{types-DeVNWqlb.d.ts → types-DiI7mZhI.d.ts} +709 -709
  58. package/dist/{types-BR-k7h0e.d.cts → types-N_LTWe4b.d.cts} +3 -3
  59. package/dist/{types-CjY93AWZ.d.cts → types-OEN1xrFg.d.cts} +1 -1
  60. package/dist/{workflow-uhOIj9D-.d.ts → workflow-CcgD6EUB.d.cts} +34 -3
  61. package/dist/{workflow-KbGsxpfh.d.cts → workflow-DBjPOKBr.d.ts} +34 -3
  62. package/dist/workflow.cjs +15008 -377
  63. package/dist/workflow.cjs.map +1 -1
  64. package/dist/workflow.d.cts +3 -3
  65. package/dist/workflow.d.ts +3 -3
  66. package/dist/workflow.js +15009 -374
  67. package/dist/workflow.js.map +1 -1
  68. package/package.json +10 -37
  69. package/src/adapters/thread/anthropic/activities.test.ts +115 -0
  70. package/src/adapters/thread/anthropic/activities.ts +11 -19
  71. package/src/adapters/thread/anthropic/fork-transform.test.ts +17 -11
  72. package/src/adapters/thread/anthropic/model-invoker.test.ts +54 -3
  73. package/src/adapters/thread/anthropic/model-invoker.ts +11 -1
  74. package/src/adapters/thread/anthropic/thread-manager.test.ts +2 -2
  75. package/src/adapters/thread/anthropic/thread-manager.ts +3 -4
  76. package/src/adapters/thread/google-genai/activities.test.ts +162 -0
  77. package/src/adapters/thread/google-genai/activities.ts +38 -15
  78. package/src/adapters/thread/google-genai/fork-transform.test.ts +17 -11
  79. package/src/adapters/thread/google-genai/model-invoker.test.ts +386 -0
  80. package/src/adapters/thread/google-genai/model-invoker.ts +118 -23
  81. package/src/adapters/thread/google-genai/thread-manager.test.ts +2 -2
  82. package/src/adapters/thread/google-genai/thread-manager.ts +3 -4
  83. package/src/adapters/thread/langchain/activities.test.ts +88 -0
  84. package/src/adapters/thread/langchain/activities.ts +15 -12
  85. package/src/adapters/thread/langchain/fork-transform.test.ts +17 -11
  86. package/src/adapters/thread/langchain/model-invoker.test.ts +74 -0
  87. package/src/adapters/thread/langchain/model-invoker.ts +16 -3
  88. package/src/adapters/thread/langchain/thread-manager.test.ts +2 -2
  89. package/src/adapters/thread/langchain/thread-manager.ts +3 -4
  90. package/src/index.ts +2 -2
  91. package/src/lib/sandbox/capability-types.test.ts +2 -2
  92. package/src/lib/sandbox/manager.ts +2 -6
  93. package/src/lib/sandbox/sandbox.test.ts +1 -1
  94. package/src/lib/sandbox/types.ts +2 -2
  95. package/src/lib/session/session.integration.test.ts +92 -0
  96. package/src/lib/session/session.ts +23 -11
  97. package/src/lib/thread/keys.test.ts +9 -9
  98. package/src/lib/thread/keys.ts +1 -1
  99. package/src/lib/thread/manager.test.ts +24 -14
  100. package/src/lib/thread/manager.ts +19 -23
  101. package/src/lib/thread/snapshot.test.ts +51 -43
  102. package/src/lib/thread/snapshot.ts +54 -32
  103. package/src/lib/thread/test-utils.ts +106 -59
  104. package/src/lib/thread/tiered.test.ts +1 -1
  105. package/src/lib/thread/types.ts +2 -2
  106. package/src/lib/tool-router/router.integration.test.ts +44 -0
  107. package/src/lib/tool-router/router.ts +140 -32
  108. package/src/lib/workflow.ts +49 -0
  109. package/src/{adapters/sandbox/inmemory/proxy.ts → test-utils/in-memory-sandbox-proxy.ts} +5 -16
  110. package/src/{adapters/sandbox/inmemory/index.ts → test-utils/in-memory-sandbox.ts} +11 -3
  111. package/src/tools/bash/bash.test.ts +1 -1
  112. package/src/tools/edit/handler.test.ts +1 -1
  113. package/tsup.config.ts +2 -4
  114. package/dist/activities-7OcT_vdR.d.cts +0 -162
  115. package/dist/activities-zG_FBoY2.d.ts +0 -162
  116. package/dist/adapters/sandbox/inmemory/index.cjs +0 -214
  117. package/dist/adapters/sandbox/inmemory/index.cjs.map +0 -1
  118. package/dist/adapters/sandbox/inmemory/index.d.cts +0 -40
  119. package/dist/adapters/sandbox/inmemory/index.d.ts +0 -40
  120. package/dist/adapters/sandbox/inmemory/index.js +0 -211
  121. package/dist/adapters/sandbox/inmemory/index.js.map +0 -1
  122. package/dist/adapters/sandbox/inmemory/workflow.cjs +0 -36
  123. package/dist/adapters/sandbox/inmemory/workflow.cjs.map +0 -1
  124. package/dist/adapters/sandbox/inmemory/workflow.d.cts +0 -27
  125. package/dist/adapters/sandbox/inmemory/workflow.d.ts +0 -27
  126. package/dist/adapters/sandbox/inmemory/workflow.js +0 -34
  127. package/dist/adapters/sandbox/inmemory/workflow.js.map +0 -1
@@ -1,31 +1,82 @@
1
- export { A as ADAPTER_ID, a as AdapterId } from '../../../adapter-id-BB-mmrts.js';
2
- import { G as GoogleGenAIThreadManagerHooks } from '../../../activities-zG_FBoY2.js';
3
- export { a as GoogleGenAIAdapter, b as GoogleGenAIAdapterConfig, c as GoogleGenAIContent, d as GoogleGenAIInvocationPayload, e as GoogleGenAIThreadManager, f as GoogleGenAIThreadManagerConfig, g as GoogleGenAIThreadOps, h as GoogleGenAIToolResponse, S as StoredContent, i as createGoogleGenAIAdapter, j as createGoogleGenAIThreadManager } from '../../../activities-zG_FBoY2.js';
4
- import Redis from 'ioredis';
5
- import { GoogleGenAI, Content } from '@google/genai';
6
- import { M as ModelInvokerConfig, A as AgentResponse } from '../../../types-DeVNWqlb.js';
7
- import '../../../cold-store-DKMAO1Dd.js';
8
- import '@aws-sdk/client-s3';
9
- import '../../../types-DO4Tkwxo.js';
1
+ import { A as ADAPTER_ID } from '../../../adapter-id-BB-mmrts.js';
2
+ export { a as AdapterId } from '../../../adapter-id-BB-mmrts.js';
3
+ import { RedisClientType } from 'redis';
4
+ import { Part, Content, GoogleGenAI, GenerateContentConfig } from '@google/genai';
5
+ import { c as ModelInvokerConfig, d as AgentResponse, M as ModelInvoker, a as PrefixedThreadOps, S as ScopedPrefix, R as RouterContext, b as ToolHandlerResponse, A as ActivityToolHandler } from '../../../types-DiI7mZhI.js';
6
+ import { C as ColdThreadStore } from '../../../cold-store-DyHodfAB.js';
7
+ import { T as ThreadManagerHooks, P as ProviderThreadManager } from '../../../types-DEbkLA06.js';
10
8
  import '@temporalio/workflow';
11
9
  import '@temporalio/common/lib/interfaces';
12
10
  import 'zod';
13
- import '../../../types-CJ7tCdl6.js';
11
+ import '../../../types-D8W5TnSa.js';
14
12
  import '@temporalio/common';
13
+ import '@aws-sdk/client-s3';
14
+
15
+ /** SDK-native content type for Google GenAI human messages */
16
+ type GoogleGenAIContent = string | Part[];
17
+ /** SDK-native content type for Google GenAI system instructions */
18
+ type GoogleGenAISystemContent = string | Part[];
19
+ /** A Content with a unique ID for idempotent Redis storage */
20
+ interface StoredContent {
21
+ id: string;
22
+ content: Content;
23
+ }
24
+ type GoogleGenAIThreadManagerHooks = ThreadManagerHooks<StoredContent, Content>;
25
+ interface GoogleGenAIThreadManagerConfig {
26
+ redis: RedisClientType;
27
+ threadId: string;
28
+ /** Thread key, defaults to 'messages' */
29
+ key?: string;
30
+ hooks?: GoogleGenAIThreadManagerHooks;
31
+ /**
32
+ * Redis TTL for the thread's keys; defaults to 90 days. Use a shorter
33
+ * value (hours) with a cold tier.
34
+ */
35
+ ttlSeconds?: number;
36
+ }
37
+ /** Prepared payload ready to send to the Google GenAI API */
38
+ interface GoogleGenAIInvocationPayload {
39
+ contents: Content[];
40
+ systemInstruction?: Part[];
41
+ }
42
+ /** Thread manager with Google GenAI Content convenience helpers */
43
+ interface GoogleGenAIThreadManager extends ProviderThreadManager<StoredContent, GoogleGenAIContent, GoogleGenAIToolResponse, GoogleGenAISystemContent> {
44
+ appendModelContent(id: string, parts: Part[]): Promise<void>;
45
+ prepareForInvocation(): Promise<GoogleGenAIInvocationPayload>;
46
+ }
47
+ /**
48
+ * Creates a Google GenAI-specific thread manager that stores StoredContent
49
+ * instances in Redis and provides convenience helpers for creating and
50
+ * appending typed Content messages.
51
+ */
52
+ declare function createGoogleGenAIThreadManager(config: GoogleGenAIThreadManagerConfig): GoogleGenAIThreadManager;
15
53
 
16
54
  interface GoogleGenAIModelInvokerConfig {
17
- redis: Redis;
55
+ redis: RedisClientType;
18
56
  client: GoogleGenAI;
19
57
  model: string;
20
58
  hooks?: GoogleGenAIThreadManagerHooks;
59
+ /**
60
+ * Redis TTL for the thread's keys; defaults to 90 days. Use a shorter
61
+ * value (hours) with a cold tier. Distinct from `cache.ttlSeconds`
62
+ * (server-side context caching).
63
+ */
64
+ ttlSeconds?: number;
65
+ /** Passed through to `generateContentStream().config`.
66
+ * `systemInstruction`, `tools`, and `abortSignal` are managed by the
67
+ * invoker and will override any values set here. */
68
+ config?: GenerateContentConfig;
69
+ /** Caches the first `splitIndex` messages server-side (with
70
+ * `systemInstruction`, `tools`, and `toolConfig`). Skipped when
71
+ * `contents.length <= splitIndex`. */
72
+ cache?: {
73
+ splitIndex: number;
74
+ /** Default: 300. */
75
+ ttlSeconds?: number;
76
+ };
21
77
  }
22
78
  /**
23
- * Creates a Google GenAI model invoker that satisfies the generic
24
- * `ModelInvoker<Content>` contract.
25
- *
26
- * Internally streams the response and emits Temporal heartbeats on each
27
- * chunk so that long-running LLM calls remain visible to the scheduler.
28
- * The caller is responsible for appending the response to the thread.
79
+ * The caller is responsible for appending the returned response to the thread.
29
80
  *
30
81
  * @example
31
82
  * ```typescript
@@ -43,18 +94,141 @@ interface GoogleGenAIModelInvokerConfig {
43
94
  * return { ...createRunAgentActivity(client, invoker, "myAgent") };
44
95
  * ```
45
96
  */
46
- declare function createGoogleGenAIModelInvoker({ redis, client, model, hooks, }: GoogleGenAIModelInvokerConfig): (config: ModelInvokerConfig) => Promise<AgentResponse<Content>>;
47
- /**
48
- * Standalone function for one-shot Google GenAI model invocation.
49
- * Convenience wrapper around createGoogleGenAIModelInvoker for cases
50
- * where you don't need to reuse the invoker.
51
- */
52
- declare function invokeGoogleGenAIModel({ redis, client, model, hooks, config, }: {
53
- redis: Redis;
97
+ declare function createGoogleGenAIModelInvoker({ redis, client, model, hooks, ttlSeconds, config: generationConfig, cache: cacheConfig, }: GoogleGenAIModelInvokerConfig): (config: ModelInvokerConfig) => Promise<AgentResponse<Content>>;
98
+ declare function invokeGoogleGenAIModel({ redis, client, model, hooks, ttlSeconds, config, generationConfig, cache, }: {
99
+ redis: RedisClientType;
54
100
  client: GoogleGenAI;
55
101
  model: string;
56
102
  hooks?: GoogleGenAIThreadManagerHooks;
103
+ ttlSeconds?: number;
57
104
  config: ModelInvokerConfig;
105
+ generationConfig?: GenerateContentConfig;
106
+ cache?: GoogleGenAIModelInvokerConfig["cache"];
58
107
  }): Promise<AgentResponse<Content>>;
59
108
 
60
- export { type GoogleGenAIModelInvokerConfig, createGoogleGenAIModelInvoker, invokeGoogleGenAIModel };
109
+ type GoogleGenAIThreadOps<TScope extends string = ""> = PrefixedThreadOps<ScopedPrefix<TScope, typeof ADAPTER_ID>, GoogleGenAIContent>;
110
+ interface GoogleGenAIAdapterConfig {
111
+ redis: RedisClientType;
112
+ client?: GoogleGenAI;
113
+ /** Default model name (e.g. 'gemini-2.5-flash'). If omitted, use `createModelInvoker()` */
114
+ model?: string;
115
+ hooks?: GoogleGenAIThreadManagerHooks;
116
+ /**
117
+ * Optional durable cold tier (e.g. S3, R2, GCS). When provided,
118
+ * the session hydrates the thread on entry (`continue`/`fork`) and
119
+ * flushes it on every exit path. When omitted, the adapter is
120
+ * Redis-only and `hydrateThread`/`flushThread` activities are no-ops.
121
+ */
122
+ coldStore?: ColdThreadStore;
123
+ /**
124
+ * Redis TTL for the thread's keys; defaults to 90 days. Use a shorter
125
+ * value (hours) with a cold tier.
126
+ */
127
+ ttlSeconds?: number;
128
+ /**
129
+ * Default generation config forwarded to every invoker the adapter
130
+ * builds (`invoker` and `createModelInvoker`). `systemInstruction`,
131
+ * `tools`, and `abortSignal` are managed by the invoker and override
132
+ * any values set here.
133
+ */
134
+ generationConfig?: GenerateContentConfig;
135
+ /**
136
+ * Default server-side context caching config forwarded to every
137
+ * invoker the adapter builds. See {@link createGoogleGenAIModelInvoker}.
138
+ */
139
+ cache?: GoogleGenAIModelInvokerConfig["cache"];
140
+ }
141
+ /**
142
+ * Tool response type accepted by the Google GenAI adapter.
143
+ *
144
+ * Handlers can return:
145
+ * - **`string`** — plain text, wrapped in a `functionResponse` part.
146
+ * - **`Record<string, unknown>`** — structured object used as `functionResponse.response`.
147
+ * - **`Part[]`** — pre-built parts used directly as `Content.parts`.
148
+ * The handler is responsible for building correct Part objects (e.g. `functionResponse`,
149
+ * `inlineData`, `text`). Use `context.toolCallId` and `context.toolName` to construct
150
+ * `functionResponse` parts.
151
+ *
152
+ * @example
153
+ * ```typescript
154
+ * adapter.wrapHandler(async (args, ctx) => ({
155
+ * toolResponse: [
156
+ * { functionResponse: { id: ctx.toolCallId, name: ctx.toolName, response: { result: "done" } } },
157
+ * { inlineData: { data: base64, mimeType: "image/png" } },
158
+ * ],
159
+ * data: null,
160
+ * }));
161
+ * ```
162
+ */
163
+ type GoogleGenAIToolResponse = string | Record<string, unknown> | Part[];
164
+ interface GoogleGenAIAdapter {
165
+ /** Model invoker using the default model (only available when `model` was provided) */
166
+ invoker: ModelInvoker<Content>;
167
+ /** Create an invoker for a specific model name (for multi-model setups) */
168
+ createModelInvoker(model: string, client: GoogleGenAI): ModelInvoker<Content>;
169
+ /**
170
+ * Create prefixed thread activities for registration on the worker.
171
+ *
172
+ * @param scope - Workflow name appended to the adapter prefix.
173
+ * Use different scopes for the main agent vs subagents to avoid collisions.
174
+ *
175
+ * @example
176
+ * ```typescript
177
+ * adapter.createActivities("codingAgent")
178
+ * // → { googleGenAICodingAgentInitializeThread, googleGenAICodingAgentAppendHumanMessage, … }
179
+ *
180
+ * adapter.createActivities("researchAgent")
181
+ * // → { googleGenAIResearchAgentInitializeThread, … }
182
+ * ```
183
+ */
184
+ createActivities<S extends string = "">(scope?: S): GoogleGenAIThreadOps<S>;
185
+ /**
186
+ * Identity wrapper that types a tool handler for this adapter.
187
+ * Constrains `toolResponse` to {@link GoogleGenAIToolResponse}.
188
+ */
189
+ wrapHandler<TArgs, TResult, TContext extends RouterContext = RouterContext>(handler: (args: TArgs, context: TContext) => Promise<ToolHandlerResponse<TResult, GoogleGenAIToolResponse>>): ActivityToolHandler<TArgs, TResult, TContext, GoogleGenAIToolResponse>;
190
+ }
191
+ /**
192
+ * Creates a Google GenAI adapter that bundles thread operations and model
193
+ * invocation using the `@google/genai` SDK.
194
+ *
195
+ * Use `createActivities(scope)` to register scoped thread operations as
196
+ * Temporal activities on the worker. The `invoker` (or invokers created via
197
+ * `createModelInvoker`) should be wrapped with `createRunAgentActivity`.
198
+ *
199
+ * @example
200
+ * ```typescript
201
+ * import { createGoogleGenAIAdapter } from 'zeitlich/adapters/thread/google-genai';
202
+ * import { createRunAgentActivity } from 'zeitlich';
203
+ * import { GoogleGenAI } from '@google/genai';
204
+ *
205
+ * const client = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
206
+ * const adapter = createGoogleGenAIAdapter({ redis, client, model: 'gemini-2.5-flash' });
207
+ *
208
+ * export function createActivities(temporalClient: WorkflowClient) {
209
+ * return {
210
+ * ...adapter.createActivities("codingAgent"),
211
+ * ...createRunAgentActivity(temporalClient, adapter.invoker, "codingAgent"),
212
+ * };
213
+ * }
214
+ * ```
215
+ *
216
+ * @example Multi-agent worker (main + subagent share the adapter)
217
+ * ```typescript
218
+ * export function createActivities(temporalClient: WorkflowClient) {
219
+ * return {
220
+ * ...adapter.createActivities("codingAgent"),
221
+ * ...adapter.createActivities("researchAgent"),
222
+ * ...createRunAgentActivity(temporalClient, adapter.invoker, "codingAgent"),
223
+ * ...createRunAgentActivity(
224
+ * temporalClient,
225
+ * adapter.createModelInvoker('gemini-2.5-pro', client),
226
+ * "researchAgent",
227
+ * ),
228
+ * };
229
+ * }
230
+ * ```
231
+ */
232
+ declare function createGoogleGenAIAdapter(config: GoogleGenAIAdapterConfig): GoogleGenAIAdapter;
233
+
234
+ export { ADAPTER_ID, type GoogleGenAIAdapter, type GoogleGenAIAdapterConfig, type GoogleGenAIContent, type GoogleGenAIInvocationPayload, type GoogleGenAIModelInvokerConfig, type GoogleGenAIThreadManager, type GoogleGenAIThreadManagerConfig, type GoogleGenAIThreadOps, type GoogleGenAIToolResponse, type StoredContent, createGoogleGenAIAdapter, createGoogleGenAIModelInvoker, createGoogleGenAIThreadManager, invokeGoogleGenAIModel };
@@ -1,3 +1,4 @@
1
+ import { randomBytes } from 'crypto';
1
2
  import { Context } from '@temporalio/activity';
2
3
 
3
4
  // src/adapters/thread/google-genai/adapter-id.ts
@@ -53,11 +54,11 @@ function createThreadManager(config) {
53
54
  return {
54
55
  async initialize() {
55
56
  await redis.del(redisKey);
56
- await redis.set(metaKey, "1", "EX", ttlSeconds);
57
+ await redis.set(metaKey, "1", { EX: ttlSeconds });
57
58
  },
58
59
  async load() {
59
60
  await assertThreadExists();
60
- const data = await redis.lrange(redisKey, 0, -1);
61
+ const data = await redis.lRange(redisKey, 0, -1);
61
62
  return data.map(deserialize);
62
63
  },
63
64
  async append(messages) {
@@ -65,22 +66,18 @@ function createThreadManager(config) {
65
66
  await assertThreadExists();
66
67
  if (idOf) {
67
68
  const dedupId = messages.map(idOf).join(":");
68
- await redis.eval(
69
- APPEND_IDEMPOTENT_SCRIPT,
70
- 2,
71
- dedupKey(dedupId),
72
- redisKey,
73
- String(ttlSeconds),
74
- ...messages.map(serialize)
75
- );
69
+ await redis.eval(APPEND_IDEMPOTENT_SCRIPT, {
70
+ keys: [dedupKey(dedupId), redisKey],
71
+ arguments: [String(ttlSeconds), ...messages.map(serialize)]
72
+ });
76
73
  } else {
77
- await redis.rpush(redisKey, ...messages.map(serialize));
74
+ await redis.rPush(redisKey, messages.map(serialize));
78
75
  await redis.expire(redisKey, ttlSeconds);
79
76
  }
80
77
  },
81
78
  async fork(newThreadId) {
82
79
  await assertThreadExists();
83
- const data = await redis.lrange(redisKey, 0, -1);
80
+ const data = await redis.lRange(redisKey, 0, -1);
84
81
  const stateRaw = await redis.get(stateKey);
85
82
  const forked = createThreadManager({
86
83
  ...config,
@@ -89,12 +86,12 @@ function createThreadManager(config) {
89
86
  await forked.initialize();
90
87
  if (data.length > 0) {
91
88
  const newKey = getThreadListKey(key, newThreadId);
92
- await redis.rpush(newKey, ...data);
89
+ await redis.rPush(newKey, data);
93
90
  await redis.expire(newKey, ttlSeconds);
94
91
  }
95
92
  if (stateRaw != null) {
96
93
  const newStateKey = getThreadStateKey(key, newThreadId);
97
- await redis.set(newStateKey, stateRaw, "EX", ttlSeconds);
94
+ await redis.set(newStateKey, stateRaw, { EX: ttlSeconds });
98
95
  }
99
96
  return forked;
100
97
  },
@@ -105,20 +102,20 @@ function createThreadManager(config) {
105
102
  "replaceAll requires the thread manager to be configured with `idOf`"
106
103
  );
107
104
  }
108
- const existing = await redis.lrange(redisKey, 0, -1);
105
+ const existing = await redis.lRange(redisKey, 0, -1);
109
106
  const existingIds = existing.map((raw) => idOf(deserialize(raw))).filter((id) => typeof id === "string");
110
107
  await redis.del(redisKey);
111
108
  if (existingIds.length > 0) {
112
- await redis.del(...existingIds.map(dedupKey));
109
+ await redis.del(existingIds.map(dedupKey));
113
110
  }
114
111
  if (messages.length > 0) {
115
- await redis.rpush(redisKey, ...messages.map(serialize));
112
+ await redis.rPush(redisKey, messages.map(serialize));
116
113
  await redis.expire(redisKey, ttlSeconds);
117
114
  }
118
115
  await redis.expire(metaKey, ttlSeconds);
119
116
  },
120
117
  async delete() {
121
- await redis.del(redisKey, metaKey, stateKey);
118
+ await redis.del([redisKey, metaKey, stateKey]);
122
119
  },
123
120
  async loadState() {
124
121
  const raw = await redis.get(stateKey);
@@ -127,14 +124,14 @@ function createThreadManager(config) {
127
124
  },
128
125
  async saveState(state) {
129
126
  await assertThreadExists();
130
- await redis.set(stateKey, JSON.stringify(state), "EX", ttlSeconds);
127
+ await redis.set(stateKey, JSON.stringify(state), { EX: ttlSeconds });
131
128
  },
132
129
  async deleteState() {
133
130
  await redis.del(stateKey);
134
131
  },
135
132
  async length() {
136
133
  await assertThreadExists();
137
- return redis.llen(redisKey);
134
+ return redis.lLen(redisKey);
138
135
  },
139
136
  async truncateFromId(messageId) {
140
137
  await assertThreadExists();
@@ -143,7 +140,7 @@ function createThreadManager(config) {
143
140
  "truncateFromId requires the thread manager to be configured with `idOf`"
144
141
  );
145
142
  }
146
- const data = await redis.lrange(redisKey, 0, -1);
143
+ const data = await redis.lRange(redisKey, 0, -1);
147
144
  let idx = -1;
148
145
  const removedIds = [];
149
146
  for (let i = 0; i < data.length; i++) {
@@ -158,11 +155,11 @@ function createThreadManager(config) {
158
155
  await redis.del(redisKey);
159
156
  await redis.expire(metaKey, ttlSeconds);
160
157
  } else {
161
- await redis.ltrim(redisKey, 0, idx - 1);
158
+ await redis.lTrim(redisKey, 0, idx - 1);
162
159
  await redis.expire(redisKey, ttlSeconds);
163
160
  }
164
161
  if (removedIds.length > 0) {
165
- await redis.del(...removedIds.map(dedupKey));
162
+ await redis.del(removedIds.map(dedupKey));
166
163
  }
167
164
  }
168
165
  };
@@ -177,7 +174,7 @@ async function encodeSnapshot(config) {
177
174
  }
178
175
  const listKey = getThreadListKey(threadKey, threadId);
179
176
  const stateKey = getThreadStateKey(threadKey, threadId);
180
- const messages = await redis.lrange(listKey, 0, -1);
177
+ const messages = await redis.lRange(listKey, 0, -1);
181
178
  const stateRaw = await redis.get(stateKey);
182
179
  const state = stateRaw == null ? null : JSON.parse(stateRaw);
183
180
  const dedupIds = idOf ? messages.map(idOf) : [];
@@ -197,31 +194,38 @@ async function applySnapshot(config) {
197
194
  }
198
195
  const listKey = getThreadListKey(threadKey, threadId);
199
196
  const stateKey = getThreadStateKey(threadKey, threadId);
200
- await redis.del(listKey, stateKey);
201
- const pipeline = redis.pipeline();
197
+ await redis.del([listKey, stateKey]);
198
+ const pipeline = redis.multi();
202
199
  if (snapshot.messages.length > 0) {
203
- pipeline.rpush(listKey, ...snapshot.messages);
200
+ pipeline.rPush(listKey, snapshot.messages);
204
201
  pipeline.expire(listKey, ttlSeconds);
205
202
  }
206
203
  if (snapshot.state != null) {
207
- pipeline.set(stateKey, JSON.stringify(snapshot.state), "EX", ttlSeconds);
204
+ pipeline.set(stateKey, JSON.stringify(snapshot.state), { EX: ttlSeconds });
208
205
  }
209
206
  for (const id of snapshot.dedupIds) {
210
- pipeline.set(getThreadDedupKey(threadId, id), "1", "EX", ttlSeconds);
207
+ pipeline.set(getThreadDedupKey(threadId, id), "1", { EX: ttlSeconds });
211
208
  }
212
- const results = await pipeline.exec();
213
- if (results) {
214
- const firstErr = results.find(([err]) => err)?.[0] ?? null;
215
- if (firstErr) {
216
- await redis.del(
217
- listKey,
218
- stateKey,
219
- ...snapshot.dedupIds.map((id) => getThreadDedupKey(threadId, id))
220
- ).catch(() => void 0);
221
- throw firstErr;
222
- }
209
+ try {
210
+ await pipeline.execAsPipeline();
211
+ } catch (err) {
212
+ await redis.del([
213
+ listKey,
214
+ stateKey,
215
+ ...snapshot.dedupIds.map((id) => getThreadDedupKey(threadId, id))
216
+ ]).catch(() => void 0);
217
+ throw firstPipelineError(err);
218
+ }
219
+ await redis.set(metaKey, "1", { EX: ttlSeconds });
220
+ }
221
+ function firstPipelineError(err) {
222
+ if (err != null && typeof err === "object" && "replies" in err && Array.isArray(err.replies)) {
223
+ const firstErr = err.replies.find(
224
+ (r) => r instanceof Error
225
+ );
226
+ if (firstErr) return firstErr;
223
227
  }
224
- await redis.set(metaKey, "1", "EX", ttlSeconds);
228
+ return err;
225
229
  }
226
230
  async function clearHotTier(config) {
227
231
  const { redis, threadKey, threadId, dedupIds = [] } = config;
@@ -231,7 +235,7 @@ async function clearHotTier(config) {
231
235
  getThreadStateKey(threadKey, threadId),
232
236
  ...dedupIds.map((id) => getThreadDedupKey(threadId, id))
233
237
  ];
234
- await redis.del(...keys);
238
+ await redis.del(keys);
235
239
  }
236
240
 
237
241
  // src/lib/thread/tiered.ts
@@ -429,7 +433,10 @@ function createGoogleGenAIModelInvoker({
429
433
  redis,
430
434
  client,
431
435
  model,
432
- hooks
436
+ hooks,
437
+ ttlSeconds,
438
+ config: generationConfig,
439
+ cache: cacheConfig
433
440
  }) {
434
441
  return async function invokeGoogleGenAIModel2(config) {
435
442
  const { threadId, threadKey, state, assistantMessageId } = config;
@@ -438,18 +445,64 @@ function createGoogleGenAIModelInvoker({
438
445
  redis,
439
446
  threadId,
440
447
  key: threadKey,
441
- hooks
448
+ hooks,
449
+ ...ttlSeconds !== void 0 && { ttlSeconds }
442
450
  });
443
451
  await thread.truncateFromId(assistantMessageId);
444
452
  const { contents, systemInstruction } = await thread.prepareForInvocation();
445
453
  const functionDeclarations = toFunctionDeclarations(state.tools);
446
454
  const tools = functionDeclarations.length > 0 ? [{ functionDeclarations }] : void 0;
455
+ const {
456
+ systemInstruction: _si,
457
+ tools: _t,
458
+ abortSignal: _as,
459
+ cachedContent: callerCachedContent,
460
+ toolConfig: callerToolConfig,
461
+ ...callerConfig
462
+ } = generationConfig ?? {};
463
+ let liveContents = contents;
464
+ let cachedContentName;
465
+ let cachedWriteTokens;
466
+ if (cacheConfig && cacheConfig.splitIndex > 0 && contents.length > cacheConfig.splitIndex) {
467
+ liveContents = contents.slice(cacheConfig.splitIndex);
468
+ const ttl = cacheConfig.ttlSeconds ?? 300;
469
+ const cacheRedisKey = `${threadKey ?? "messages"}:gemini-cache:${model}:${cacheConfig.splitIndex}:thread:${threadId}`;
470
+ cachedContentName = await redis.get(cacheRedisKey) ?? void 0;
471
+ if (!cachedContentName) {
472
+ const cacheInstance = await client.caches.create({
473
+ model,
474
+ config: {
475
+ contents: contents.slice(0, cacheConfig.splitIndex),
476
+ ...systemInstruction ? { systemInstruction } : {},
477
+ ...tools ? { tools } : {},
478
+ ...callerToolConfig ? { toolConfig: callerToolConfig } : {},
479
+ ttl: `${ttl}s`,
480
+ abortSignal: signal
481
+ }
482
+ });
483
+ if (!cacheInstance?.name) {
484
+ throw new Error("Gemini cache creation did not return a cache name");
485
+ }
486
+ cachedContentName = cacheInstance.name;
487
+ cachedWriteTokens = cacheInstance.usageMetadata?.totalTokenCount ?? void 0;
488
+ const redisTtl = ttl - 5;
489
+ if (redisTtl > 0) {
490
+ await redis.set(cacheRedisKey, cachedContentName, { EX: redisTtl });
491
+ }
492
+ }
493
+ }
447
494
  const stream = await client.models.generateContentStream({
448
495
  model,
449
- contents,
496
+ contents: liveContents,
450
497
  config: {
451
- ...systemInstruction ? { systemInstruction } : {},
452
- ...tools ? { tools } : {},
498
+ ...callerConfig,
499
+ ...cachedContentName ? { cachedContent: cachedContentName } : {
500
+ ...callerCachedContent ? { cachedContent: callerCachedContent } : {
501
+ ...systemInstruction ? { systemInstruction } : {},
502
+ ...tools ? { tools } : {}
503
+ },
504
+ ...callerToolConfig ? { toolConfig: callerToolConfig } : {}
505
+ },
453
506
  abortSignal: signal
454
507
  }
455
508
  });
@@ -463,19 +516,27 @@ function createGoogleGenAIModelInvoker({
463
516
  if (!lastChunk) {
464
517
  throw new Error("Google GenAI stream ended without producing any chunks");
465
518
  }
519
+ for (const part of allParts) {
520
+ if (part.functionCall && !part.functionCall.id) {
521
+ part.functionCall.id = randomBytes(8).toString("hex");
522
+ }
523
+ }
466
524
  const modelContent = { role: "model", parts: allParts };
467
- const functionCalls = lastChunk.functionCalls ?? [];
468
525
  return {
469
526
  message: modelContent,
470
- rawToolCalls: functionCalls.map((fc) => ({
471
- id: fc.id,
472
- name: fc.name ?? "",
473
- args: fc.args ?? {}
527
+ rawToolCalls: allParts.filter(
528
+ (p) => !!p.functionCall
529
+ ).map((p) => ({
530
+ id: p.functionCall.id,
531
+ name: p.functionCall.name ?? "",
532
+ args: p.functionCall.args ?? {}
474
533
  })),
475
534
  usage: {
476
535
  inputTokens: lastChunk.usageMetadata?.promptTokenCount,
477
536
  outputTokens: lastChunk.usageMetadata?.candidatesTokenCount,
478
- cachedReadTokens: lastChunk.usageMetadata?.cachedContentTokenCount
537
+ cachedWriteTokens,
538
+ cachedReadTokens: lastChunk.usageMetadata?.cachedContentTokenCount,
539
+ reasonTokens: lastChunk.usageMetadata?.thoughtsTokenCount
479
540
  }
480
541
  };
481
542
  };
@@ -485,13 +546,19 @@ async function invokeGoogleGenAIModel({
485
546
  client,
486
547
  model,
487
548
  hooks,
488
- config
549
+ ttlSeconds,
550
+ config,
551
+ generationConfig,
552
+ cache
489
553
  }) {
490
554
  const invoker = createGoogleGenAIModelInvoker({
491
555
  redis,
492
556
  client,
493
557
  model,
494
- hooks
558
+ hooks,
559
+ ...ttlSeconds !== void 0 && { ttlSeconds },
560
+ config: generationConfig,
561
+ cache
495
562
  });
496
563
  return invoker(config);
497
564
  }
@@ -499,21 +566,20 @@ async function invokeGoogleGenAIModel({
499
566
  // src/adapters/thread/google-genai/activities.ts
500
567
  function createGoogleGenAIAdapter(config) {
501
568
  const { redis } = config;
502
- const baseExtras = {
569
+ const base = {
570
+ redis,
503
571
  ...config.ttlSeconds !== void 0 && { ttlSeconds: config.ttlSeconds }
504
572
  };
505
573
  const makeProviderThread = (threadId, threadKey) => createGoogleGenAIThreadManager({
506
- redis,
574
+ ...base,
507
575
  threadId,
508
- key: threadKey,
509
- ...baseExtras
576
+ key: threadKey
510
577
  });
511
578
  const makeTieredBase = (threadId, threadKey) => createTieredThreadManager({
512
- redis,
579
+ ...base,
513
580
  threadId,
514
581
  key: threadKey,
515
582
  idOf: storedContentId,
516
- ...baseExtras,
517
583
  ...config.coldStore && { coldStore: config.coldStore }
518
584
  });
519
585
  const threadOps = {
@@ -545,11 +611,10 @@ function createGoogleGenAIAdapter(config) {
545
611
  },
546
612
  async forkThread(sourceThreadId, targetThreadId, threadKey) {
547
613
  const thread = createGoogleGenAIThreadManager({
548
- redis,
614
+ ...base,
549
615
  threadId: sourceThreadId,
550
616
  key: threadKey,
551
- hooks: config.hooks,
552
- ...baseExtras
617
+ hooks: config.hooks
553
618
  });
554
619
  await thread.fork(targetThreadId);
555
620
  },
@@ -582,10 +647,14 @@ function createGoogleGenAIAdapter(config) {
582
647
  );
583
648
  }
584
649
  const makeInvoker = (model, client) => createGoogleGenAIModelInvoker({
585
- redis,
650
+ ...base,
586
651
  client,
587
652
  model,
588
- hooks: config.hooks
653
+ hooks: config.hooks,
654
+ ...config.generationConfig !== void 0 && {
655
+ config: config.generationConfig
656
+ },
657
+ ...config.cache !== void 0 && { cache: config.cache }
589
658
  });
590
659
  const invoker = config.model && config.client ? makeInvoker(config.model, config.client) : (() => {
591
660
  throw new Error(