zeitlich 0.2.21 → 0.2.22

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/README.md +70 -55
  2. package/dist/adapters/sandbox/daytona/index.cjs +3 -0
  3. package/dist/adapters/sandbox/daytona/index.cjs.map +1 -1
  4. package/dist/adapters/sandbox/daytona/index.d.cts +2 -1
  5. package/dist/adapters/sandbox/daytona/index.d.ts +2 -1
  6. package/dist/adapters/sandbox/daytona/index.js +3 -0
  7. package/dist/adapters/sandbox/daytona/index.js.map +1 -1
  8. package/dist/adapters/sandbox/daytona/workflow.cjs +32 -0
  9. package/dist/adapters/sandbox/daytona/workflow.cjs.map +1 -0
  10. package/dist/adapters/sandbox/daytona/workflow.d.cts +27 -0
  11. package/dist/adapters/sandbox/daytona/workflow.d.ts +27 -0
  12. package/dist/adapters/sandbox/daytona/workflow.js +30 -0
  13. package/dist/adapters/sandbox/daytona/workflow.js.map +1 -0
  14. package/dist/adapters/sandbox/inmemory/index.cjs +4 -1
  15. package/dist/adapters/sandbox/inmemory/index.cjs.map +1 -1
  16. package/dist/adapters/sandbox/inmemory/index.d.cts +3 -2
  17. package/dist/adapters/sandbox/inmemory/index.d.ts +3 -2
  18. package/dist/adapters/sandbox/inmemory/index.js +4 -1
  19. package/dist/adapters/sandbox/inmemory/index.js.map +1 -1
  20. package/dist/adapters/sandbox/inmemory/workflow.cjs +32 -0
  21. package/dist/adapters/sandbox/inmemory/workflow.cjs.map +1 -0
  22. package/dist/adapters/sandbox/inmemory/workflow.d.cts +25 -0
  23. package/dist/adapters/sandbox/inmemory/workflow.d.ts +25 -0
  24. package/dist/adapters/sandbox/inmemory/workflow.js +30 -0
  25. package/dist/adapters/sandbox/inmemory/workflow.js.map +1 -0
  26. package/dist/adapters/sandbox/virtual/index.cjs +3 -0
  27. package/dist/adapters/sandbox/virtual/index.cjs.map +1 -1
  28. package/dist/adapters/sandbox/virtual/index.d.cts +6 -4
  29. package/dist/adapters/sandbox/virtual/index.d.ts +6 -4
  30. package/dist/adapters/sandbox/virtual/index.js +3 -0
  31. package/dist/adapters/sandbox/virtual/index.js.map +1 -1
  32. package/dist/adapters/sandbox/virtual/workflow.cjs +32 -0
  33. package/dist/adapters/sandbox/virtual/workflow.cjs.map +1 -0
  34. package/dist/adapters/sandbox/virtual/workflow.d.cts +27 -0
  35. package/dist/adapters/sandbox/virtual/workflow.d.ts +27 -0
  36. package/dist/adapters/sandbox/virtual/workflow.js +30 -0
  37. package/dist/adapters/sandbox/virtual/workflow.js.map +1 -0
  38. package/dist/adapters/thread/google-genai/index.cjs +9 -1
  39. package/dist/adapters/thread/google-genai/index.cjs.map +1 -1
  40. package/dist/adapters/thread/google-genai/index.d.cts +30 -18
  41. package/dist/adapters/thread/google-genai/index.d.ts +30 -18
  42. package/dist/adapters/thread/google-genai/index.js +9 -1
  43. package/dist/adapters/thread/google-genai/index.js.map +1 -1
  44. package/dist/adapters/thread/google-genai/workflow.cjs +33 -0
  45. package/dist/adapters/thread/google-genai/workflow.cjs.map +1 -0
  46. package/dist/adapters/thread/google-genai/workflow.d.cts +32 -0
  47. package/dist/adapters/thread/google-genai/workflow.d.ts +32 -0
  48. package/dist/adapters/thread/google-genai/workflow.js +31 -0
  49. package/dist/adapters/thread/google-genai/workflow.js.map +1 -0
  50. package/dist/adapters/thread/langchain/index.cjs +9 -1
  51. package/dist/adapters/thread/langchain/index.cjs.map +1 -1
  52. package/dist/adapters/thread/langchain/index.d.cts +26 -15
  53. package/dist/adapters/thread/langchain/index.d.ts +26 -15
  54. package/dist/adapters/thread/langchain/index.js +9 -1
  55. package/dist/adapters/thread/langchain/index.js.map +1 -1
  56. package/dist/adapters/thread/langchain/workflow.cjs +33 -0
  57. package/dist/adapters/thread/langchain/workflow.cjs.map +1 -0
  58. package/dist/adapters/thread/langchain/workflow.d.cts +32 -0
  59. package/dist/adapters/thread/langchain/workflow.d.ts +32 -0
  60. package/dist/adapters/thread/langchain/workflow.js +31 -0
  61. package/dist/adapters/thread/langchain/workflow.js.map +1 -0
  62. package/dist/index.cjs +36 -34
  63. package/dist/index.cjs.map +1 -1
  64. package/dist/index.d.cts +35 -14
  65. package/dist/index.d.ts +35 -14
  66. package/dist/index.js +38 -34
  67. package/dist/index.js.map +1 -1
  68. package/dist/queries-Bw6WEPMw.d.cts +44 -0
  69. package/dist/queries-C27raDaB.d.ts +44 -0
  70. package/dist/{queries-CHa2iv_I.d.cts → types-BJ8itUAl.d.cts} +2 -43
  71. package/dist/{types-BkAYmc96.d.ts → types-C5bkx6kQ.d.ts} +33 -5
  72. package/dist/{types-CES_30qx.d.cts → types-ClsHhtwL.d.cts} +33 -5
  73. package/dist/{queries-6Avfh74U.d.ts → types-ENYCKFBk.d.ts} +2 -43
  74. package/dist/{types-BMRzfELQ.d.cts → types-HBosetv3.d.cts} +15 -1
  75. package/dist/{types-BMRzfELQ.d.ts → types-HBosetv3.d.ts} +15 -1
  76. package/dist/workflow.cjs +4 -30
  77. package/dist/workflow.cjs.map +1 -1
  78. package/dist/workflow.d.cts +13 -41
  79. package/dist/workflow.d.ts +13 -41
  80. package/dist/workflow.js +6 -30
  81. package/dist/workflow.js.map +1 -1
  82. package/package.json +53 -1
  83. package/src/adapters/sandbox/daytona/index.ts +4 -0
  84. package/src/adapters/sandbox/daytona/proxy.ts +55 -0
  85. package/src/adapters/sandbox/e2b/filesystem.ts +147 -0
  86. package/src/adapters/sandbox/e2b/index.ts +159 -0
  87. package/src/adapters/sandbox/e2b/types.ts +23 -0
  88. package/src/adapters/sandbox/inmemory/index.ts +5 -1
  89. package/src/adapters/sandbox/inmemory/proxy.ts +53 -0
  90. package/src/adapters/sandbox/virtual/provider.ts +5 -1
  91. package/src/adapters/sandbox/virtual/proxy.ts +52 -0
  92. package/src/adapters/thread/google-genai/activities.ts +51 -17
  93. package/src/adapters/thread/google-genai/index.ts +1 -0
  94. package/src/adapters/thread/google-genai/proxy.ts +61 -0
  95. package/src/adapters/thread/langchain/activities.ts +47 -14
  96. package/src/adapters/thread/langchain/index.ts +1 -0
  97. package/src/adapters/thread/langchain/proxy.ts +61 -0
  98. package/src/lib/sandbox/manager.ts +40 -6
  99. package/src/lib/sandbox/sandbox.test.ts +12 -11
  100. package/src/lib/sandbox/types.ts +18 -0
  101. package/src/lib/session/index.ts +3 -5
  102. package/src/lib/session/session-edge-cases.integration.test.ts +45 -34
  103. package/src/lib/session/session.integration.test.ts +40 -48
  104. package/src/lib/session/session.ts +4 -66
  105. package/src/lib/session/types.ts +32 -1
  106. package/src/lib/subagent/define.ts +1 -1
  107. package/src/lib/subagent/handler.ts +9 -2
  108. package/src/lib/subagent/index.ts +1 -0
  109. package/src/lib/subagent/subagent.integration.test.ts +62 -0
  110. package/src/lib/subagent/types.ts +7 -2
  111. package/src/lib/tool-router/router-edge-cases.integration.test.ts +4 -1
  112. package/src/lib/tool-router/router.integration.test.ts +4 -1
  113. package/src/lib/workflow.test.ts +19 -10
  114. package/src/lib/workflow.ts +4 -1
  115. package/src/tools/bash/bash.test.ts +16 -7
  116. package/src/workflow.ts +6 -14
  117. package/tsup.config.ts +6 -0
@@ -2,11 +2,20 @@ import type Redis from "ioredis";
2
2
  import type { GoogleGenAI, Content } from "@google/genai";
3
3
  import type { ToolResultConfig } from "../../../lib/types";
4
4
  import type { MessageContent } from "../../../lib/types";
5
- import type { ThreadOps } from "../../../lib/session/types";
5
+ import type {
6
+ ThreadOps,
7
+ PrefixedThreadOps,
8
+ ScopedPrefix,
9
+ } from "../../../lib/session/types";
6
10
  import type { ModelInvoker } from "../../../lib/model";
7
11
  import { createGoogleGenAIThreadManager } from "./thread-manager";
8
12
  import { createGoogleGenAIModelInvoker } from "./model-invoker";
9
13
 
14
+ const ADAPTER_PREFIX = "googleGenAI" as const;
15
+
16
+ export type GoogleGenAIThreadOps<TScope extends string = ""> =
17
+ PrefixedThreadOps<ScopedPrefix<TScope, typeof ADAPTER_PREFIX>>;
18
+
10
19
  export interface GoogleGenAIAdapterConfig {
11
20
  redis: Redis;
12
21
  client: GoogleGenAI;
@@ -15,21 +24,37 @@ export interface GoogleGenAIAdapterConfig {
15
24
  }
16
25
 
17
26
  export interface GoogleGenAIAdapter {
18
- /** Thread operations (register these as Temporal activities on the worker) */
19
- threadOps: ThreadOps;
20
27
  /** Model invoker using the default model (only available when `model` was provided) */
21
28
  invoker: ModelInvoker<Content>;
22
29
  /** Create an invoker for a specific model name (for multi-model setups) */
23
30
  createModelInvoker(model: string): ModelInvoker<Content>;
31
+ /**
32
+ * Create prefixed thread activities for registration on the worker.
33
+ *
34
+ * @param scope - Workflow name appended to the adapter prefix.
35
+ * Use different scopes for the main agent vs subagents to avoid collisions.
36
+ *
37
+ * @example
38
+ * ```typescript
39
+ * adapter.createActivities("codingAgent")
40
+ * // → { googleGenAICodingAgentInitializeThread, googleGenAICodingAgentAppendHumanMessage, … }
41
+ *
42
+ * adapter.createActivities("researchAgent")
43
+ * // → { googleGenAIResearchAgentInitializeThread, … }
44
+ * ```
45
+ */
46
+ createActivities<S extends string = "">(
47
+ scope?: S
48
+ ): GoogleGenAIThreadOps<S>;
24
49
  }
25
50
 
26
51
  /**
27
52
  * Creates a Google GenAI adapter that bundles thread operations and model
28
53
  * invocation using the `@google/genai` SDK.
29
54
  *
30
- * The returned `threadOps` should be registered as Temporal activities on
31
- * the worker. The `invoker` (or invokers created via `createModelInvoker`)
32
- * should be wrapped with `createRunAgentActivity` for per-agent activities.
55
+ * Use `createActivities(scope)` to register scoped thread operations as
56
+ * Temporal activities on the worker. The `invoker` (or invokers created via
57
+ * `createModelInvoker`) should be wrapped with `createRunAgentActivity`.
33
58
  *
34
59
  * @example
35
60
  * ```typescript
@@ -42,27 +67,23 @@ export interface GoogleGenAIAdapter {
42
67
  *
43
68
  * export function createActivities(temporalClient: WorkflowClient) {
44
69
  * return {
45
- * ...adapter.threadOps,
46
- * runAgent: createRunAgentActivity(temporalClient, adapter.invoker),
70
+ * ...adapter.createActivities("codingAgent"),
71
+ * runCodingAgent: createRunAgentActivity(temporalClient, adapter.invoker),
47
72
  * };
48
73
  * }
49
74
  * ```
50
75
  *
51
- * @example Multi-model setup
76
+ * @example Multi-agent worker (main + subagent share the adapter)
52
77
  * ```typescript
53
- * const adapter = createGoogleGenAIAdapter({ redis, client });
54
- *
55
78
  * export function createActivities(temporalClient: WorkflowClient) {
56
79
  * return {
57
- * ...adapter.threadOps,
80
+ * ...adapter.createActivities("codingAgent"),
81
+ * ...adapter.createActivities("researchAgent"),
82
+ * runCodingAgent: createRunAgentActivity(temporalClient, adapter.invoker),
58
83
  * runResearchAgent: createRunAgentActivity(
59
84
  * temporalClient,
60
85
  * adapter.createModelInvoker('gemini-2.5-pro'),
61
86
  * ),
62
- * runFastAgent: createRunAgentActivity(
63
- * temporalClient,
64
- * adapter.createModelInvoker('gemini-2.5-flash'),
65
- * ),
66
87
  * };
67
88
  * }
68
89
  * ```
@@ -114,6 +135,19 @@ export function createGoogleGenAIAdapter(
114
135
  },
115
136
  };
116
137
 
138
+ function createActivities<S extends string = "">(
139
+ scope?: S
140
+ ): GoogleGenAIThreadOps<S> {
141
+ const prefix = scope
142
+ ? `${ADAPTER_PREFIX}${scope.charAt(0).toUpperCase()}${scope.slice(1)}`
143
+ : ADAPTER_PREFIX;
144
+ const cap = (s: string): string =>
145
+ s.charAt(0).toUpperCase() + s.slice(1);
146
+ return Object.fromEntries(
147
+ Object.entries(threadOps).map(([k, v]) => [`${prefix}${cap(k)}`, v])
148
+ ) as GoogleGenAIThreadOps<S>;
149
+ }
150
+
117
151
  const makeInvoker = (model: string): ModelInvoker<Content> =>
118
152
  createGoogleGenAIModelInvoker({ redis, client, model });
119
153
 
@@ -127,7 +161,7 @@ export function createGoogleGenAIAdapter(
127
161
  }) as unknown as ModelInvoker<Content>);
128
162
 
129
163
  return {
130
- threadOps,
164
+ createActivities,
131
165
  invoker,
132
166
  createModelInvoker: makeInvoker,
133
167
  };
@@ -22,6 +22,7 @@ export {
22
22
  createGoogleGenAIAdapter,
23
23
  type GoogleGenAIAdapter,
24
24
  type GoogleGenAIAdapterConfig,
25
+ type GoogleGenAIThreadOps,
25
26
  } from "./activities";
26
27
 
27
28
  // Thread manager
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Workflow-safe proxy for Google GenAI thread operations.
3
+ *
4
+ * Import this from `zeitlich/adapters/thread/google-genai/workflow`
5
+ * in your Temporal workflow files.
6
+ *
7
+ * By default the scope is derived from `workflowInfo().workflowType`,
8
+ * so activities are automatically namespaced per workflow.
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { proxyGoogleGenAIThreadOps } from 'zeitlich/adapters/thread/google-genai/workflow';
13
+ *
14
+ * // Auto-scoped to the current workflow name
15
+ * const threadOps = proxyGoogleGenAIThreadOps();
16
+ *
17
+ * // Explicit scope override
18
+ * const threadOps = proxyGoogleGenAIThreadOps("customScope");
19
+ * ```
20
+ */
21
+ import {
22
+ proxyActivities,
23
+ workflowInfo,
24
+ type ActivityInterfaceFor,
25
+ } from "@temporalio/workflow";
26
+ import type { ThreadOps } from "../../../lib/session/types";
27
+
28
+ const ADAPTER_PREFIX = "googleGenAI";
29
+
30
+ export function proxyGoogleGenAIThreadOps(
31
+ 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>;
61
+ }
@@ -1,13 +1,22 @@
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 { ThreadOps } from "../../../lib/session/types";
4
+ import type {
5
+ ThreadOps,
6
+ PrefixedThreadOps,
7
+ ScopedPrefix,
8
+ } from "../../../lib/session/types";
5
9
  import type { ModelInvoker } from "../../../lib/model";
6
10
  import type { StoredMessage } from "@langchain/core/messages";
7
11
  import type { BaseChatModel } from "@langchain/core/language_models/chat_models";
8
12
  import { createLangChainThreadManager } from "./thread-manager";
9
13
  import { createLangChainModelInvoker } from "./model-invoker";
10
14
 
15
+ const ADAPTER_PREFIX = "langChain" as const;
16
+
17
+ export type LangChainThreadOps<TScope extends string = ""> =
18
+ PrefixedThreadOps<ScopedPrefix<TScope, typeof ADAPTER_PREFIX>>;
19
+
11
20
  export interface LangChainAdapterConfig {
12
21
  redis: Redis;
13
22
  /** Optional default model — if omitted, use `createModelInvoker()` to create invokers later */
@@ -16,22 +25,34 @@ export interface LangChainAdapterConfig {
16
25
  }
17
26
 
18
27
  export interface LangChainAdapter {
19
- /** Thread operations (register these as Temporal activities on the worker) */
20
- threadOps: ThreadOps;
21
28
  /** Model invoker using the default model (only available when `model` was provided) */
22
29
  invoker: ModelInvoker<StoredMessage>;
23
30
  /** Create an invoker for a specific model (for multi-model setups) */
24
31
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
25
32
  createModelInvoker(model: BaseChatModel<any>): ModelInvoker<StoredMessage>;
33
+ /**
34
+ * Create prefixed thread activities for registration on the worker.
35
+ *
36
+ * @param scope - Workflow name appended to the adapter prefix.
37
+ *
38
+ * @example
39
+ * ```typescript
40
+ * adapter.createActivities("codingAgent")
41
+ * // → { langChainCodingAgentInitializeThread, langChainCodingAgentAppendHumanMessage, … }
42
+ * ```
43
+ */
44
+ createActivities<S extends string = "">(
45
+ scope?: S
46
+ ): LangChainThreadOps<S>;
26
47
  }
27
48
 
28
49
  /**
29
50
  * Creates a LangChain adapter that bundles thread operations and model
30
51
  * invocation using a consistent message format (StoredMessage).
31
52
  *
32
- * The returned `threadOps` should be registered as Temporal activities on
33
- * the worker. The `invoker` (or invokers created via `createModelInvoker`)
34
- * should be wrapped with `createRunAgentActivity` for per-agent activities.
53
+ * Use `createActivities(scope)` to register scoped thread operations as
54
+ * Temporal activities on the worker. The `invoker` (or invokers created via
55
+ * `createModelInvoker`) should be wrapped with `createRunAgentActivity`.
35
56
  *
36
57
  * @example
37
58
  * ```typescript
@@ -42,21 +63,20 @@ export interface LangChainAdapter {
42
63
  *
43
64
  * export function createActivities(client: WorkflowClient) {
44
65
  * return {
45
- * ...adapter.threadOps,
46
- * runAgent: createRunAgentActivity(client, adapter.invoker),
66
+ * ...adapter.createActivities("codingAgent"),
67
+ * runCodingAgent: createRunAgentActivity(client, adapter.invoker),
47
68
  * };
48
69
  * }
49
70
  * ```
50
71
  *
51
- * @example Multi-model setup
72
+ * @example Multi-agent worker
52
73
  * ```typescript
53
- * const adapter = createLangChainAdapter({ redis });
54
- *
55
74
  * export function createActivities(client: WorkflowClient) {
56
75
  * return {
57
- * ...adapter.threadOps,
76
+ * ...adapter.createActivities("codingAgent"),
77
+ * ...adapter.createActivities("researchAgent"),
78
+ * runCodingAgent: createRunAgentActivity(client, adapter.invoker),
58
79
  * runResearchAgent: createRunAgentActivity(client, adapter.createModelInvoker(claude)),
59
- * runWriterAgent: createRunAgentActivity(client, adapter.createModelInvoker(gpt4)),
60
80
  * };
61
81
  * }
62
82
  * ```
@@ -108,6 +128,19 @@ export function createLangChainAdapter(
108
128
  },
109
129
  };
110
130
 
131
+ function createActivities<S extends string = "">(
132
+ scope?: S
133
+ ): LangChainThreadOps<S> {
134
+ const prefix = scope
135
+ ? `${ADAPTER_PREFIX}${scope.charAt(0).toUpperCase()}${scope.slice(1)}`
136
+ : ADAPTER_PREFIX;
137
+ const cap = (s: string): string =>
138
+ s.charAt(0).toUpperCase() + s.slice(1);
139
+ return Object.fromEntries(
140
+ Object.entries(threadOps).map(([k, v]) => [`${prefix}${cap(k)}`, v])
141
+ ) as LangChainThreadOps<S>;
142
+ }
143
+
111
144
  const makeInvoker = (
112
145
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
113
146
  model: BaseChatModel<any>
@@ -124,7 +157,7 @@ export function createLangChainAdapter(
124
157
  };
125
158
 
126
159
  return {
127
- threadOps,
160
+ createActivities,
128
161
  invoker,
129
162
  createModelInvoker: makeInvoker,
130
163
  };
@@ -20,6 +20,7 @@ export {
20
20
  createLangChainAdapter,
21
21
  type LangChainAdapter,
22
22
  type LangChainAdapterConfig,
23
+ type LangChainThreadOps,
23
24
  } from "./activities";
24
25
 
25
26
  // Thread manager
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Workflow-safe proxy for LangChain thread operations.
3
+ *
4
+ * Import this from `zeitlich/adapters/thread/langchain/workflow`
5
+ * in your Temporal workflow files.
6
+ *
7
+ * By default the scope is derived from `workflowInfo().workflowType`,
8
+ * so activities are automatically namespaced per workflow.
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { proxyLangChainThreadOps } from 'zeitlich/adapters/thread/langchain/workflow';
13
+ *
14
+ * // Auto-scoped to the current workflow name
15
+ * const threadOps = proxyLangChainThreadOps();
16
+ *
17
+ * // Explicit scope override
18
+ * const threadOps = proxyLangChainThreadOps("customScope");
19
+ * ```
20
+ */
21
+ import {
22
+ proxyActivities,
23
+ workflowInfo,
24
+ type ActivityInterfaceFor,
25
+ } from "@temporalio/workflow";
26
+ import type { ThreadOps } from "../../../lib/session/types";
27
+
28
+ const ADAPTER_PREFIX = "langChain";
29
+
30
+ export function proxyLangChainThreadOps(
31
+ 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>;
61
+ }
@@ -2,6 +2,7 @@ import type {
2
2
  Sandbox,
3
3
  SandboxCreateOptions,
4
4
  SandboxOps,
5
+ PrefixedSandboxOps,
5
6
  SandboxProvider,
6
7
  SandboxSnapshot,
7
8
  } from "./types";
@@ -16,16 +17,18 @@ import type {
16
17
  * ```typescript
17
18
  * const manager = new SandboxManager(new InMemorySandboxProvider());
18
19
  * const activities = {
19
- * ...manager.createActivities(),
20
+ * ...manager.createActivities("CodingAgent"),
20
21
  * bashHandler: withSandbox(manager, bashHandler),
21
22
  * };
23
+ * // registers: inMemoryCodingAgentCreateSandbox, …
22
24
  * ```
23
25
  */
24
26
  export class SandboxManager<
25
27
  TOptions extends SandboxCreateOptions = SandboxCreateOptions,
26
28
  TSandbox extends Sandbox = Sandbox,
29
+ TId extends string = string,
27
30
  > {
28
- constructor(private provider: SandboxProvider<TOptions, TSandbox>) {}
31
+ constructor(private provider: SandboxProvider<TOptions, TSandbox> & { readonly id: TId }) {}
29
32
 
30
33
  async create(
31
34
  options?: TOptions
@@ -51,12 +54,36 @@ export class SandboxManager<
51
54
  return sandbox.id;
52
55
  }
53
56
 
57
+ async fork(sandboxId: string): Promise<string> {
58
+ const sandbox = await this.provider.fork(sandboxId);
59
+ return sandbox.id;
60
+ }
61
+
54
62
  /**
55
- * Returns Temporal activity functions matching {@link SandboxOps}.
56
- * Spread these into your worker's activity map.
63
+ * Returns Temporal activity functions with prefixed names.
64
+ *
65
+ * The provider's `id` is automatically prepended, so you only need
66
+ * to pass the workflow/scope name. Use the matching `proxy*SandboxOps()`
67
+ * helper from the adapter's `/workflow` entrypoint on the workflow side.
68
+ *
69
+ * @param scope - Workflow name (appended to the provider id)
70
+ *
71
+ * @example
72
+ * ```typescript
73
+ * const manager = new SandboxManager(new InMemorySandboxProvider());
74
+ * manager.createActivities("CodingAgent");
75
+ * // registers: inMemoryCodingAgentCreateSandbox, inMemoryCodingAgentDestroySandbox, …
76
+ *
77
+ * const vmgr = new SandboxManager(new VirtualSandboxProvider(resolver));
78
+ * vmgr.createActivities("CodingAgent");
79
+ * // registers: virtualCodingAgentCreateSandbox, …
80
+ * ```
57
81
  */
58
- createActivities(): SandboxOps<TOptions> {
59
- return {
82
+ createActivities<S extends string>(
83
+ scope: S
84
+ ): PrefixedSandboxOps<`${TId}${Capitalize<S>}`, TOptions> {
85
+ const prefix = `${this.provider.id}${scope.charAt(0).toUpperCase()}${scope.slice(1)}`;
86
+ const ops: SandboxOps<TOptions> = {
60
87
  createSandbox: async (
61
88
  options?: TOptions
62
89
  ): Promise<{
@@ -71,6 +98,13 @@ export class SandboxManager<
71
98
  snapshotSandbox: async (sandboxId: string): Promise<SandboxSnapshot> => {
72
99
  return this.snapshot(sandboxId);
73
100
  },
101
+ forkSandbox: async (sandboxId: string): Promise<string> => {
102
+ return this.fork(sandboxId);
103
+ },
74
104
  };
105
+ const cap = (s: string): string => s.charAt(0).toUpperCase() + s.slice(1);
106
+ return Object.fromEntries(
107
+ Object.entries(ops).map(([k, v]) => [`${prefix}${cap(k)}`, v])
108
+ ) as PrefixedSandboxOps<`${TId}${Capitalize<S>}`, TOptions>;
75
109
  }
76
110
  }
@@ -1,10 +1,10 @@
1
1
  import { describe, expect, it, beforeEach } from "vitest";
2
2
  import { SandboxManager } from "./manager";
3
3
  import { InMemorySandboxProvider } from "../../adapters/sandbox/inmemory/index";
4
- import { SandboxNotFoundError } from "./types";
4
+ import { SandboxNotFoundError, type Sandbox, type SandboxCreateOptions } from "./types";
5
5
 
6
6
  describe("SandboxManager", () => {
7
- let manager: SandboxManager;
7
+ let manager: SandboxManager<SandboxCreateOptions, Sandbox, "inMemory">;
8
8
 
9
9
  beforeEach(() => {
10
10
  manager = new SandboxManager(new InMemorySandboxProvider());
@@ -54,7 +54,7 @@ describe("SandboxManager", () => {
54
54
 
55
55
  const snapshot = await manager.snapshot(sandboxId);
56
56
  expect(snapshot.sandboxId).toBe(sandboxId);
57
- expect(snapshot.providerId).toBe("inmemory");
57
+ expect(snapshot.providerId).toBe("inMemory");
58
58
 
59
59
  await manager.destroy(sandboxId);
60
60
  await expect(manager.getSandbox(sandboxId)).rejects.toThrow(SandboxNotFoundError);
@@ -68,16 +68,17 @@ describe("SandboxManager", () => {
68
68
  expect(extra).toBe("world");
69
69
  });
70
70
 
71
- it("createActivities returns SandboxOps-shaped object", async () => {
72
- const activities = manager.createActivities();
73
- expect(activities.createSandbox).toBeTypeOf("function");
74
- expect(activities.destroySandbox).toBeTypeOf("function");
75
- expect(activities.snapshotSandbox).toBeTypeOf("function");
71
+ it("createActivities returns prefixed SandboxOps-shaped object", async () => {
72
+ // provider.id is "inMemory", scope is "Test" → prefix "inMemoryTest"
73
+ const activities = manager.createActivities("Test");
74
+ expect(activities.inMemoryTestCreateSandbox).toBeTypeOf("function");
75
+ expect(activities.inMemoryTestDestroySandbox).toBeTypeOf("function");
76
+ expect(activities.inMemoryTestSnapshotSandbox).toBeTypeOf("function");
76
77
 
77
- const { sandboxId } = await activities.createSandbox();
78
+ const { sandboxId } = await activities.inMemoryTestCreateSandbox();
78
79
  await expect(manager.getSandbox(sandboxId)).resolves.toBeTruthy();
79
80
 
80
- await activities.destroySandbox(sandboxId);
81
+ await activities.inMemoryTestDestroySandbox(sandboxId);
81
82
  await expect(manager.getSandbox(sandboxId)).rejects.toThrow(
82
83
  SandboxNotFoundError,
83
84
  );
@@ -85,7 +86,7 @@ describe("SandboxManager", () => {
85
86
  });
86
87
 
87
88
  describe("InMemorySandboxProvider", () => {
88
- let manager: SandboxManager;
89
+ let manager: SandboxManager<SandboxCreateOptions, Sandbox, "inMemory">;
89
90
 
90
91
  beforeEach(() => {
91
92
  manager = new SandboxManager(new InMemorySandboxProvider());
@@ -127,6 +127,7 @@ export interface SandboxProvider<
127
127
  destroy(sandboxId: string): Promise<void>;
128
128
  snapshot(sandboxId: string): Promise<SandboxSnapshot>;
129
129
  restore(snapshot: SandboxSnapshot): Promise<Sandbox>;
130
+ fork(sandboxId: string): Promise<Sandbox>;
130
131
  }
131
132
 
132
133
  // ============================================================================
@@ -141,8 +142,25 @@ export interface SandboxOps<
141
142
  ): Promise<{ sandboxId: string; stateUpdate?: Record<string, unknown> }>;
142
143
  destroySandbox(sandboxId: string): Promise<void>;
143
144
  snapshotSandbox(sandboxId: string): Promise<SandboxSnapshot>;
145
+ forkSandbox(sandboxId: string): Promise<string>;
144
146
  }
145
147
 
148
+ /**
149
+ * Maps generic {@link SandboxOps} method names to adapter-prefixed names.
150
+ *
151
+ * @example
152
+ * ```typescript
153
+ * type InMemOps = PrefixedSandboxOps<"inMemory">;
154
+ * // → { inMemoryCreateSandbox, inMemoryDestroySandbox, inMemorySnapshotSandbox }
155
+ * ```
156
+ */
157
+ export type PrefixedSandboxOps<
158
+ TPrefix extends string,
159
+ TOptions extends SandboxCreateOptions = SandboxCreateOptions,
160
+ > = {
161
+ [K in keyof SandboxOps<TOptions> as `${TPrefix}${Capitalize<K & string>}`]: SandboxOps<TOptions>[K];
162
+ };
163
+
146
164
  // ============================================================================
147
165
  // Errors
148
166
  // ============================================================================
@@ -1,11 +1,9 @@
1
- export {
2
- createSession,
3
- proxyDefaultThreadOps,
4
- proxySandboxOps,
5
- } from "./session";
1
+ export { createSession } from "./session";
6
2
 
7
3
  export type {
8
4
  ThreadOps,
5
+ PrefixedThreadOps,
6
+ ScopedPrefix,
9
7
  SessionConfig,
10
8
  ZeitlichSession,
11
9
  } from "./types";