zeitlich 0.2.13 → 0.2.14

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 (135) hide show
  1. package/README.md +49 -38
  2. package/dist/adapters/sandbox/daytona/index.cjs +205 -0
  3. package/dist/adapters/sandbox/daytona/index.cjs.map +1 -0
  4. package/dist/adapters/sandbox/daytona/index.d.cts +86 -0
  5. package/dist/adapters/sandbox/daytona/index.d.ts +86 -0
  6. package/dist/adapters/sandbox/daytona/index.js +202 -0
  7. package/dist/adapters/sandbox/daytona/index.js.map +1 -0
  8. package/dist/adapters/sandbox/inmemory/index.cjs +174 -0
  9. package/dist/adapters/sandbox/inmemory/index.cjs.map +1 -0
  10. package/dist/adapters/sandbox/inmemory/index.d.cts +28 -0
  11. package/dist/adapters/sandbox/inmemory/index.d.ts +28 -0
  12. package/dist/adapters/sandbox/inmemory/index.js +172 -0
  13. package/dist/adapters/sandbox/inmemory/index.js.map +1 -0
  14. package/dist/adapters/sandbox/virtual/index.cjs +405 -0
  15. package/dist/adapters/sandbox/virtual/index.cjs.map +1 -0
  16. package/dist/adapters/sandbox/virtual/index.d.cts +85 -0
  17. package/dist/adapters/sandbox/virtual/index.d.ts +85 -0
  18. package/dist/adapters/sandbox/virtual/index.js +400 -0
  19. package/dist/adapters/sandbox/virtual/index.js.map +1 -0
  20. package/dist/adapters/thread/google-genai/index.cjs +284 -0
  21. package/dist/adapters/thread/google-genai/index.cjs.map +1 -0
  22. package/dist/adapters/thread/google-genai/index.d.cts +145 -0
  23. package/dist/adapters/thread/google-genai/index.d.ts +145 -0
  24. package/dist/adapters/thread/google-genai/index.js +278 -0
  25. package/dist/adapters/thread/google-genai/index.js.map +1 -0
  26. package/dist/adapters/{langchain → thread/langchain}/index.cjs +7 -9
  27. package/dist/adapters/thread/langchain/index.cjs.map +1 -0
  28. package/dist/adapters/{langchain → thread/langchain}/index.d.cts +17 -21
  29. package/dist/adapters/{langchain → thread/langchain}/index.d.ts +17 -21
  30. package/dist/adapters/{langchain → thread/langchain}/index.js +7 -9
  31. package/dist/adapters/thread/langchain/index.js.map +1 -0
  32. package/dist/index.cjs +816 -545
  33. package/dist/index.cjs.map +1 -1
  34. package/dist/index.d.cts +235 -74
  35. package/dist/index.d.ts +235 -74
  36. package/dist/index.js +804 -540
  37. package/dist/index.js.map +1 -1
  38. package/dist/types-B4C9txdq.d.ts +389 -0
  39. package/dist/{thread-manager-qc0g5Rvd.d.cts → types-B9ljZewB.d.cts} +1 -6
  40. package/dist/{thread-manager-qc0g5Rvd.d.ts → types-B9ljZewB.d.ts} +1 -6
  41. package/dist/types-BMXzv7TN.d.cts +476 -0
  42. package/dist/types-BMXzv7TN.d.ts +476 -0
  43. package/dist/types-BVP87m_W.d.cts +121 -0
  44. package/dist/types-CDubRtad.d.cts +115 -0
  45. package/dist/types-CDubRtad.d.ts +115 -0
  46. package/dist/types-CwwgQ_9H.d.ts +121 -0
  47. package/dist/types-GpMU4b0w.d.cts +389 -0
  48. package/dist/workflow.cjs +444 -318
  49. package/dist/workflow.cjs.map +1 -1
  50. package/dist/workflow.d.cts +271 -222
  51. package/dist/workflow.d.ts +271 -222
  52. package/dist/workflow.js +440 -316
  53. package/dist/workflow.js.map +1 -1
  54. package/package.json +59 -6
  55. package/src/adapters/sandbox/daytona/filesystem.ts +136 -0
  56. package/src/adapters/sandbox/daytona/index.ts +149 -0
  57. package/src/adapters/sandbox/daytona/types.ts +34 -0
  58. package/src/adapters/sandbox/inmemory/index.ts +213 -0
  59. package/src/adapters/sandbox/virtual/filesystem.ts +345 -0
  60. package/src/adapters/sandbox/virtual/index.ts +88 -0
  61. package/src/adapters/sandbox/virtual/mutations.ts +38 -0
  62. package/src/adapters/sandbox/virtual/provider.ts +101 -0
  63. package/src/adapters/sandbox/virtual/tree.ts +82 -0
  64. package/src/adapters/sandbox/virtual/types.ts +127 -0
  65. package/src/adapters/sandbox/virtual/virtual-sandbox.test.ts +523 -0
  66. package/src/adapters/sandbox/virtual/with-virtual-sandbox.ts +91 -0
  67. package/src/adapters/thread/google-genai/activities.ts +121 -0
  68. package/src/adapters/thread/google-genai/index.ts +41 -0
  69. package/src/adapters/thread/google-genai/model-invoker.ts +154 -0
  70. package/src/adapters/thread/google-genai/thread-manager.ts +169 -0
  71. package/src/adapters/{langchain → thread/langchain}/activities.ts +11 -15
  72. package/src/adapters/{langchain → thread/langchain}/index.ts +1 -1
  73. package/src/adapters/{langchain → thread/langchain}/model-invoker.ts +15 -18
  74. package/src/adapters/{langchain → thread/langchain}/thread-manager.ts +1 -1
  75. package/src/index.ts +32 -24
  76. package/src/lib/activity.ts +87 -0
  77. package/src/lib/hooks/index.ts +11 -0
  78. package/src/lib/hooks/types.ts +98 -0
  79. package/src/lib/model/helpers.ts +6 -0
  80. package/src/lib/model/index.ts +13 -0
  81. package/src/lib/{model-invoker.ts → model/types.ts} +18 -1
  82. package/src/lib/sandbox/index.ts +19 -0
  83. package/src/lib/sandbox/manager.ts +76 -0
  84. package/src/lib/sandbox/sandbox.test.ts +158 -0
  85. package/src/lib/{fs.ts → sandbox/tree.ts} +6 -6
  86. package/src/lib/sandbox/types.ts +164 -0
  87. package/src/lib/session/index.ts +11 -0
  88. package/src/lib/{session.ts → session/session.ts} +76 -48
  89. package/src/lib/session/types.ts +93 -0
  90. package/src/lib/skills/fs-provider.ts +16 -15
  91. package/src/lib/skills/handler.ts +31 -0
  92. package/src/lib/skills/index.ts +5 -1
  93. package/src/lib/skills/register.ts +20 -0
  94. package/src/lib/skills/tool.ts +47 -0
  95. package/src/lib/state/index.ts +9 -0
  96. package/src/lib/{state-manager.ts → state/manager.ts} +10 -147
  97. package/src/lib/state/types.ts +134 -0
  98. package/src/lib/subagent/define.ts +71 -0
  99. package/src/lib/subagent/handler.ts +99 -0
  100. package/src/lib/subagent/index.ts +13 -0
  101. package/src/lib/subagent/register.ts +53 -0
  102. package/src/lib/subagent/tool.ts +80 -0
  103. package/src/lib/subagent/types.ts +92 -0
  104. package/src/lib/thread/index.ts +7 -0
  105. package/src/lib/{thread-manager.ts → thread/manager.ts} +1 -33
  106. package/src/lib/thread/types.ts +33 -0
  107. package/src/lib/tool-router/auto-append.ts +55 -0
  108. package/src/lib/tool-router/index.ts +41 -0
  109. package/src/lib/tool-router/router.ts +462 -0
  110. package/src/lib/tool-router/types.ts +478 -0
  111. package/src/lib/tool-router/with-sandbox.ts +70 -0
  112. package/src/lib/types.ts +5 -382
  113. package/src/tools/bash/bash.test.ts +53 -55
  114. package/src/tools/bash/handler.ts +23 -51
  115. package/src/tools/edit/handler.ts +67 -81
  116. package/src/tools/glob/handler.ts +60 -17
  117. package/src/tools/read-file/handler.ts +67 -0
  118. package/src/tools/read-skill/handler.ts +1 -31
  119. package/src/tools/read-skill/tool.ts +5 -47
  120. package/src/tools/subagent/handler.ts +1 -100
  121. package/src/tools/subagent/tool.ts +5 -93
  122. package/src/tools/task-create/handler.ts +1 -1
  123. package/src/tools/task-get/handler.ts +1 -1
  124. package/src/tools/task-list/handler.ts +1 -1
  125. package/src/tools/task-update/handler.ts +1 -1
  126. package/src/tools/write-file/handler.ts +47 -0
  127. package/src/workflow.ts +88 -47
  128. package/tsup.config.ts +8 -1
  129. package/dist/adapters/langchain/index.cjs.map +0 -1
  130. package/dist/adapters/langchain/index.js.map +0 -1
  131. package/dist/model-invoker-y_zlyMqu.d.cts +0 -892
  132. package/dist/model-invoker-y_zlyMqu.d.ts +0 -892
  133. package/src/lib/tool-router.ts +0 -977
  134. package/src/lib/workflow-helpers.ts +0 -50
  135. /package/src/lib/{thread-id.ts → thread/id.ts} +0 -0
@@ -1,19 +1,16 @@
1
1
  import type Redis from "ioredis";
2
- import type { AgentResponse } from "../../lib/types";
3
- import type { ModelInvokerConfig } from "../../lib/model-invoker";
2
+ import type { AgentResponse } from "../../../lib/model";
3
+ import type { ModelInvokerConfig } from "../../../lib/model";
4
4
  import { mapStoredMessagesToChatMessages } from "@langchain/core/messages";
5
5
  import type { StoredMessage } from "@langchain/core/messages";
6
6
  import { v4 as uuidv4 } from "uuid";
7
- import type {
8
- BaseChatModel,
9
- BaseChatModelCallOptions,
10
- BindToolsInput,
11
- } from "@langchain/core/language_models/chat_models";
7
+ import type { BaseChatModel } from "@langchain/core/language_models/chat_models";
12
8
  import { createLangChainThreadManager } from "./thread-manager";
13
9
 
14
- export interface LangChainModelInvokerConfig {
10
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
+ export interface LangChainModelInvokerConfig<TModel extends BaseChatModel<any> = BaseChatModel<any>> {
15
12
  redis: Redis;
16
- model: BaseChatModel<BaseChatModelCallOptions & { tools?: BindToolsInput }>;
13
+ model: TModel;
17
14
  }
18
15
 
19
16
  /**
@@ -25,21 +22,20 @@ export interface LangChainModelInvokerConfig {
25
22
  *
26
23
  * @example
27
24
  * ```typescript
28
- * import { createLangChainModelInvoker } from 'zeitlich/adapters/langchain';
25
+ * import { createLangChainModelInvoker } from 'zeitlich/adapters/thread/langchain';
29
26
  * import { createRunAgentActivity } from 'zeitlich';
30
27
  * import { ChatAnthropic } from '@langchain/anthropic';
31
28
  *
32
29
  * const model = new ChatAnthropic({ model: "claude-sonnet-4-6" });
33
30
  * const invoker = createLangChainModelInvoker({ redis, model });
34
31
  *
35
- * // Wrap with createRunAgentActivity to use as runAgent activity:
36
32
  * return { runAgent: createRunAgentActivity(client, invoker) };
37
33
  * ```
38
34
  */
39
- export function createLangChainModelInvoker({
40
- redis,
41
- model,
42
- }: LangChainModelInvokerConfig) {
35
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
36
+ export function createLangChainModelInvoker<TModel extends BaseChatModel<any> = BaseChatModel<any>>(
37
+ { redis, model }: LangChainModelInvokerConfig<TModel>,
38
+ ) {
43
39
  return async function invokeLangChainModel(
44
40
  config: ModelInvokerConfig
45
41
  ): Promise<AgentResponse<StoredMessage>> {
@@ -54,7 +50,7 @@ export function createLangChainModelInvoker({
54
50
  {
55
51
  runName: agentName,
56
52
  runId,
57
- metadata: { thread_id: threadId, ...metadata },
53
+ metadata: { thread_id: `${agentName}-${threadId}`, ...metadata },
58
54
  tools: state.tools,
59
55
  }
60
56
  );
@@ -88,14 +84,15 @@ export function createLangChainModelInvoker({
88
84
  * Convenience wrapper around createLangChainModelInvoker for cases where
89
85
  * you don't need to reuse the invoker.
90
86
  */
91
- export async function invokeLangChainModel({
87
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
88
+ export async function invokeLangChainModel<TModel extends BaseChatModel<any> = BaseChatModel<any>>({
92
89
  redis,
93
90
  model,
94
91
  config,
95
92
  }: {
96
93
  redis: Redis;
97
94
  config: ModelInvokerConfig;
98
- model: BaseChatModel<BaseChatModelCallOptions & { tools?: BindToolsInput }>;
95
+ model: TModel;
99
96
  }): Promise<AgentResponse<StoredMessage>> {
100
97
  const invoker = createLangChainModelInvoker({ redis, model });
101
98
  return invoker(config);
@@ -14,7 +14,7 @@ import {
14
14
  createThreadManager,
15
15
  type BaseThreadManager,
16
16
  type ThreadManagerConfig,
17
- } from "../../lib/thread-manager";
17
+ } from "../../../lib/thread";
18
18
 
19
19
  export type LangChainToolMessageContent = $InferMessageContent<
20
20
  MessageStructure,
package/src/index.ts CHANGED
@@ -3,20 +3,25 @@
3
3
  *
4
4
  * Import from `zeitlich` in activity files and worker setup.
5
5
  * For LangChain-specific adapters (model invoker, thread manager, adapter),
6
- * import from `zeitlich/adapters/langchain`.
6
+ * import from `zeitlich/adapters/thread/langchain`.
7
7
  * For workflow code, use `zeitlich/workflow` instead.
8
8
  *
9
9
  * @example
10
10
  * ```typescript
11
11
  * // In your activities file
12
12
  * import {
13
- * createBashHandler,
14
- * createAskUserQuestionHandler,
13
+ * SandboxManager,
14
+ * withSandbox,
15
+ * bashHandler,
16
+ * editHandler,
15
17
  * toTree,
16
18
  * } from 'zeitlich';
17
19
  *
20
+ * // In-memory sandbox adapter
21
+ * import { InMemorySandboxProvider } from 'zeitlich/adapters/sandbox/inmemory';
22
+ *
18
23
  * // LangChain adapter
19
- * import { createLangChainAdapter } from 'zeitlich/adapters/langchain';
24
+ * import { createLangChainAdapter } from 'zeitlich/adapters/thread/langchain';
20
25
  * ```
21
26
  */
22
27
 
@@ -24,34 +29,37 @@
24
29
  // (Activities can use these too)
25
30
  export * from "./workflow";
26
31
 
32
+ // Skills (activity-side: filesystem provider uses node:path)
33
+ export { FileSystemSkillProvider } from "./lib/skills/fs-provider";
34
+
27
35
  // Thread manager (generic, framework-agnostic)
28
- export { createThreadManager } from "./lib/thread-manager";
29
- export type {
30
- BaseThreadManager,
31
- ThreadManagerConfig,
32
- } from "./lib/thread-manager";
36
+ export { createThreadManager } from "./lib/thread";
37
+ export type { BaseThreadManager, ThreadManagerConfig } from "./lib/thread";
33
38
 
34
39
  // Model invoker contract (framework-agnostic)
35
- export type { ModelInvoker, ModelInvokerConfig } from "./lib/model-invoker";
40
+ export type { ModelInvoker, ModelInvokerConfig } from "./lib/model";
36
41
 
37
- // Auto-append wrapper for large tool results (activity-side only)
38
- export { withAutoAppend } from "./lib/tool-router";
42
+ // Activity-side handler wrappers
43
+ export { withAutoAppend, withSandbox } from "./lib/tool-router";
44
+ export type { SandboxContext } from "./lib/tool-router";
39
45
 
40
- // Workflow state helpers (requires Temporal client)
46
+ // Activity-side wrappers (requires Temporal client)
41
47
  export {
42
48
  queryParentWorkflowState,
43
49
  createRunAgentActivity,
44
- } from "./lib/workflow-helpers";
45
-
46
- // Tool handlers (activity implementations)
47
- // All handlers follow the factory pattern: createXHandler(deps) => handler(args)
48
- export { createGlobHandler } from "./tools/glob/handler";
49
-
50
- export { createEditHandler } from "./tools/edit/handler";
50
+ withParentWorkflowState,
51
+ } from "./lib/activity";
52
+ export type { AgentStateContext } from "./lib/activity";
51
53
 
52
- export { createBashHandler } from "./tools/bash/handler";
54
+ // Sandbox (activity-side: manager)
55
+ export { SandboxManager } from "./lib/sandbox/manager";
53
56
 
54
- export { toTree } from "./lib/fs";
57
+ // Tool handlers (activity implementations)
58
+ // Wrap sandbox handlers with withSandbox(manager, handler) at registration time
59
+ export { bashHandler } from "./tools/bash/handler";
60
+ export { editHandler } from "./tools/edit/handler";
61
+ export { globHandler } from "./tools/glob/handler";
62
+ export { readFileHandler } from "./tools/read-file/handler";
63
+ export { writeFileHandler } from "./tools/write-file/handler";
55
64
 
56
- // Skills (activity-side: filesystem provider)
57
- export { FileSystemSkillProvider } from "./lib/skills/fs-provider";
65
+ export { toTree } from "./lib/sandbox";
@@ -0,0 +1,87 @@
1
+ import { Context } from "@temporalio/activity";
2
+ import type { WorkflowClient } from "@temporalio/client";
3
+ import type { BaseAgentState, RunAgentConfig } from "./types";
4
+ import type {
5
+ ActivityToolHandler,
6
+ RouterContext,
7
+ ToolHandlerResponse,
8
+ } from "./tool-router/types";
9
+
10
+ /**
11
+ * Query the parent workflow's state from within an activity.
12
+ * Resolves the workflow handle from the current activity context.
13
+ */
14
+ export async function queryParentWorkflowState<T>(
15
+ client: WorkflowClient
16
+ ): Promise<T> {
17
+ const { workflowExecution } = Context.current().info;
18
+ const handle = client.getHandle(
19
+ workflowExecution.workflowId,
20
+ workflowExecution.runId
21
+ );
22
+ return handle.query<T>("getAgentState");
23
+ }
24
+
25
+ /**
26
+ * Wraps a handler into a `RunAgentActivity` by auto-fetching the parent
27
+ * workflow's agent state before each invocation.
28
+ *
29
+ * @example
30
+ * ```typescript
31
+ * import { createRunAgentActivity } from 'zeitlich';
32
+ * import { createLangChainModelInvoker } from 'zeitlich/adapters/thread/langchain';
33
+ *
34
+ * const invoker = createLangChainModelInvoker({ redis, model });
35
+ * return { runAgent: createRunAgentActivity(client, invoker) };
36
+ * ```
37
+ */
38
+ export function createRunAgentActivity<R, S extends BaseAgentState = BaseAgentState>(
39
+ client: WorkflowClient,
40
+ handler: (config: RunAgentConfig & { state: S }) => Promise<R>,
41
+ ): (config: RunAgentConfig) => Promise<R> {
42
+ return async (config: RunAgentConfig) => {
43
+ const state = await queryParentWorkflowState<S>(client);
44
+ return handler({ ...config, state });
45
+ };
46
+ }
47
+
48
+ /**
49
+ * Context injected into tool handlers created via {@link withParentWorkflowState}.
50
+ */
51
+ export interface AgentStateContext<S extends BaseAgentState = BaseAgentState> extends RouterContext {
52
+ state: S;
53
+ }
54
+
55
+ /**
56
+ * Wraps a tool handler into an `ActivityToolHandler` by auto-fetching the
57
+ * parent workflow's agent state before each invocation.
58
+ *
59
+ * @typeParam S - Custom agent state type (defaults to `BaseAgentState`)
60
+ *
61
+ * @example
62
+ * ```typescript
63
+ * import { withParentWorkflowState, type AgentStateContext } from 'zeitlich';
64
+ *
65
+ * // With custom state:
66
+ * interface MyState extends BaseAgentState { customField: string }
67
+ * const myHandler = withParentWorkflowState<MyArgs, MyResult, MyState>(
68
+ * client,
69
+ * async (args, ctx) => {
70
+ * console.log(ctx.state.customField);
71
+ * return { toolResponse: 'done', data: null };
72
+ * },
73
+ * );
74
+ * ```
75
+ */
76
+ export function withParentWorkflowState<TArgs, TResult, S extends BaseAgentState = BaseAgentState>(
77
+ client: WorkflowClient,
78
+ handler: (
79
+ args: TArgs,
80
+ context: AgentStateContext<S>,
81
+ ) => Promise<ToolHandlerResponse<TResult>>,
82
+ ): ActivityToolHandler<TArgs, TResult> {
83
+ return async (args, context) => {
84
+ const state = await queryParentWorkflowState<S>(client);
85
+ return handler(args, { ...context, state });
86
+ };
87
+ }
@@ -0,0 +1,11 @@
1
+ export type {
2
+ SessionStartHookContext,
3
+ SessionStartHook,
4
+ SessionEndHookContext,
5
+ SessionEndHook,
6
+ PreHumanMessageAppendHookContext,
7
+ PreHumanMessageAppendHook,
8
+ PostHumanMessageAppendHookContext,
9
+ PostHumanMessageAppendHook,
10
+ Hooks,
11
+ } from "./types";
@@ -0,0 +1,98 @@
1
+ import type { MessageContent, SessionExitReason } from "../types";
2
+ import type {
3
+ ToolMap,
4
+ ToolRouterHooks,
5
+ } from "../tool-router/types";
6
+
7
+ // ============================================================================
8
+ // Session Lifecycle Hooks
9
+ // ============================================================================
10
+
11
+ /**
12
+ * Context for SessionStart hook - called when session begins
13
+ */
14
+ export interface SessionStartHookContext {
15
+ threadId: string;
16
+ agentName: string;
17
+ metadata: Record<string, unknown>;
18
+ }
19
+
20
+ /**
21
+ * SessionStart hook - called when session begins
22
+ */
23
+ export type SessionStartHook = (
24
+ ctx: SessionStartHookContext
25
+ ) => void | Promise<void>;
26
+
27
+ /**
28
+ * Context for SessionEnd hook - called when session ends
29
+ */
30
+ export interface SessionEndHookContext {
31
+ threadId: string;
32
+ agentName: string;
33
+ exitReason: SessionExitReason;
34
+ turns: number;
35
+ metadata: Record<string, unknown>;
36
+ }
37
+
38
+ /**
39
+ * SessionEnd hook - called when session ends
40
+ */
41
+ export type SessionEndHook = (
42
+ ctx: SessionEndHookContext
43
+ ) => void | Promise<void>;
44
+
45
+ // ============================================================================
46
+ // Message Lifecycle Hooks
47
+ // ============================================================================
48
+
49
+ /**
50
+ * Context for PreHumanMessageAppend hook - called before each human message is appended to the thread
51
+ */
52
+ export interface PreHumanMessageAppendHookContext {
53
+ message: MessageContent;
54
+ threadId: string;
55
+ }
56
+
57
+ /**
58
+ * PreHumanMessageAppend hook - called before each human message is appended to the thread
59
+ */
60
+ export type PreHumanMessageAppendHook = (
61
+ ctx: PreHumanMessageAppendHookContext
62
+ ) => void | Promise<void>;
63
+
64
+ /**
65
+ * Context for PostHumanMessageAppend hook - called after each human message is appended to the thread
66
+ */
67
+ export interface PostHumanMessageAppendHookContext {
68
+ message: MessageContent;
69
+ threadId: string;
70
+ }
71
+
72
+ /**
73
+ * PostHumanMessageAppend hook - called after each human message is appended to the thread
74
+ */
75
+ export type PostHumanMessageAppendHook = (
76
+ ctx: PostHumanMessageAppendHookContext
77
+ ) => void | Promise<void>;
78
+
79
+ // ============================================================================
80
+ // Combined Hooks Interface
81
+ // ============================================================================
82
+
83
+ /**
84
+ * Full hooks interface for a session — combines tool execution hooks
85
+ * (consumed by the router) with session/message lifecycle hooks
86
+ * (consumed directly by the session).
87
+ */
88
+ export interface Hooks<T extends ToolMap, TResult = unknown>
89
+ extends ToolRouterHooks<T, TResult> {
90
+ /** Called before each human message is appended to the thread */
91
+ onPreHumanMessageAppend?: PreHumanMessageAppendHook;
92
+ /** Called after each human message is appended to the thread */
93
+ onPostHumanMessageAppend?: PostHumanMessageAppendHook;
94
+ /** Called when session starts */
95
+ onSessionStart?: SessionStartHook;
96
+ /** Called when session ends */
97
+ onSessionEnd?: SessionEndHook;
98
+ }
@@ -0,0 +1,6 @@
1
+ export {
2
+ queryParentWorkflowState,
3
+ createRunAgentActivity,
4
+ withParentWorkflowState,
5
+ } from "../activity";
6
+ export type { AgentStateContext } from "../activity";
@@ -0,0 +1,13 @@
1
+ export {
2
+ queryParentWorkflowState,
3
+ createRunAgentActivity,
4
+ withParentWorkflowState,
5
+ } from "./helpers";
6
+ export type { AgentStateContext } from "./helpers";
7
+
8
+ export type {
9
+ AgentResponse,
10
+ RunAgentActivity,
11
+ ModelInvokerConfig,
12
+ ModelInvoker,
13
+ } from "./types";
@@ -1,4 +1,21 @@
1
- import type { AgentResponse, BaseAgentState } from "./types";
1
+ import type { TokenUsage, BaseAgentState, RunAgentConfig } from "../types";
2
+ import type { RawToolCall } from "../tool-router/types";
3
+
4
+ /**
5
+ * Agent response from LLM invocation
6
+ */
7
+ export interface AgentResponse<M = unknown> {
8
+ message: M;
9
+ rawToolCalls: RawToolCall[];
10
+ usage?: TokenUsage;
11
+ }
12
+
13
+ /**
14
+ * Type signature for workflow-specific runAgent activity
15
+ */
16
+ export type RunAgentActivity<M = unknown> = (
17
+ config: RunAgentConfig
18
+ ) => Promise<AgentResponse<M>>;
2
19
 
3
20
  /**
4
21
  * Configuration passed to a ModelInvoker.
@@ -0,0 +1,19 @@
1
+ export { SandboxManager } from "./manager";
2
+ export { toTree } from "./tree";
3
+ export type {
4
+ Sandbox,
5
+ SandboxCapabilities,
6
+ SandboxCreateOptions,
7
+ SandboxFileSystem,
8
+ SandboxOps,
9
+ SandboxProvider,
10
+ SandboxSnapshot,
11
+ ExecOptions,
12
+ ExecResult,
13
+ DirentEntry,
14
+ FileStat,
15
+ } from "./types";
16
+ export {
17
+ SandboxNotFoundError,
18
+ SandboxNotSupportedError,
19
+ } from "./types";
@@ -0,0 +1,76 @@
1
+ import type {
2
+ Sandbox,
3
+ SandboxCreateOptions,
4
+ SandboxOps,
5
+ SandboxProvider,
6
+ SandboxSnapshot,
7
+ } from "./types";
8
+
9
+ /**
10
+ * Stateless facade over a {@link SandboxProvider}.
11
+ *
12
+ * Delegates all lifecycle operations to the provider, which is responsible
13
+ * for its own instance management strategy (e.g. in-memory map, remote API).
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * const manager = new SandboxManager(new InMemorySandboxProvider());
18
+ * const activities = {
19
+ * ...manager.createActivities(),
20
+ * bashHandler: withSandbox(manager, bashHandler),
21
+ * };
22
+ * ```
23
+ */
24
+ export class SandboxManager<
25
+ TOptions extends SandboxCreateOptions = SandboxCreateOptions,
26
+ TSandbox extends Sandbox = Sandbox,
27
+ > {
28
+ constructor(private provider: SandboxProvider<TOptions, TSandbox>) {}
29
+
30
+ async create(
31
+ options?: TOptions
32
+ ): Promise<{ sandboxId: string; stateUpdate?: Record<string, unknown> }> {
33
+ const { sandbox, stateUpdate } = await this.provider.create(options);
34
+ return { sandboxId: sandbox.id, ...(stateUpdate && { stateUpdate }) };
35
+ }
36
+
37
+ async getSandbox(id: string): Promise<TSandbox> {
38
+ return this.provider.get(id);
39
+ }
40
+
41
+ async destroy(id: string): Promise<void> {
42
+ await this.provider.destroy(id);
43
+ }
44
+
45
+ async snapshot(id: string): Promise<SandboxSnapshot> {
46
+ return this.provider.snapshot(id);
47
+ }
48
+
49
+ async restore(snapshot: SandboxSnapshot): Promise<string> {
50
+ const sandbox = await this.provider.restore(snapshot);
51
+ return sandbox.id;
52
+ }
53
+
54
+ /**
55
+ * Returns Temporal activity functions matching {@link SandboxOps}.
56
+ * Spread these into your worker's activity map.
57
+ */
58
+ createActivities(): SandboxOps<TOptions> {
59
+ return {
60
+ createSandbox: async (
61
+ options?: TOptions
62
+ ): Promise<{
63
+ sandboxId: string;
64
+ stateUpdate?: Record<string, unknown>;
65
+ }> => {
66
+ return this.create(options);
67
+ },
68
+ destroySandbox: async (sandboxId: string): Promise<void> => {
69
+ await this.destroy(sandboxId);
70
+ },
71
+ snapshotSandbox: async (sandboxId: string): Promise<SandboxSnapshot> => {
72
+ return this.snapshot(sandboxId);
73
+ },
74
+ };
75
+ }
76
+ }
@@ -0,0 +1,158 @@
1
+ import { describe, expect, it, beforeEach } from "vitest";
2
+ import { SandboxManager } from "./manager";
3
+ import { InMemorySandboxProvider } from "../../adapters/sandbox/inmemory/index";
4
+ import { SandboxNotFoundError } from "./types";
5
+
6
+ describe("SandboxManager", () => {
7
+ let manager: SandboxManager;
8
+
9
+ beforeEach(() => {
10
+ manager = new SandboxManager(new InMemorySandboxProvider());
11
+ });
12
+
13
+ it("creates a sandbox and returns an id", async () => {
14
+ const { sandboxId } = await manager.create();
15
+ expect(sandboxId).toBeTruthy();
16
+ const sandbox = await manager.getSandbox(sandboxId);
17
+ expect(sandbox.id).toBe(sandboxId);
18
+ });
19
+
20
+ it("creates a sandbox with a custom id", async () => {
21
+ const { sandboxId } = await manager.create({ id: "my-sandbox" });
22
+ expect(sandboxId).toBe("my-sandbox");
23
+ });
24
+
25
+ it("gets an existing sandbox", async () => {
26
+ const { sandboxId } = await manager.create();
27
+ const sandbox = await manager.getSandbox(sandboxId);
28
+ expect(sandbox.id).toBe(sandboxId);
29
+ });
30
+
31
+ it("throws SandboxNotFoundError for unknown id", async () => {
32
+ await expect(manager.getSandbox("nonexistent")).rejects.toThrow(
33
+ SandboxNotFoundError,
34
+ );
35
+ });
36
+
37
+ it("destroys a sandbox", async () => {
38
+ const { sandboxId } = await manager.create();
39
+ await manager.getSandbox(sandboxId);
40
+ await manager.destroy(sandboxId);
41
+ await expect(manager.getSandbox(sandboxId)).rejects.toThrow(SandboxNotFoundError);
42
+ });
43
+
44
+ it("destroy is idempotent for unknown ids", async () => {
45
+ await expect(manager.destroy("nonexistent")).resolves.not.toThrow();
46
+ });
47
+
48
+ it("snapshots and restores a sandbox", async () => {
49
+ const { sandboxId } = await manager.create({
50
+ initialFiles: { "/data.txt": "hello" },
51
+ });
52
+ const sandbox = await manager.getSandbox(sandboxId);
53
+ await sandbox.fs.writeFile("/extra.txt", "world");
54
+
55
+ const snapshot = await manager.snapshot(sandboxId);
56
+ expect(snapshot.sandboxId).toBe(sandboxId);
57
+ expect(snapshot.providerId).toBe("inmemory");
58
+
59
+ await manager.destroy(sandboxId);
60
+ await expect(manager.getSandbox(sandboxId)).rejects.toThrow(SandboxNotFoundError);
61
+
62
+ const restoredId = await manager.restore(snapshot);
63
+ expect(restoredId).toBe(sandboxId);
64
+ const restored = await manager.getSandbox(restoredId);
65
+ const content = await restored.fs.readFile("/data.txt");
66
+ expect(content).toBe("hello");
67
+ const extra = await restored.fs.readFile("/extra.txt");
68
+ expect(extra).toBe("world");
69
+ });
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");
76
+
77
+ const { sandboxId } = await activities.createSandbox();
78
+ await expect(manager.getSandbox(sandboxId)).resolves.toBeTruthy();
79
+
80
+ await activities.destroySandbox(sandboxId);
81
+ await expect(manager.getSandbox(sandboxId)).rejects.toThrow(
82
+ SandboxNotFoundError,
83
+ );
84
+ });
85
+ });
86
+
87
+ describe("InMemorySandboxProvider", () => {
88
+ let manager: SandboxManager;
89
+
90
+ beforeEach(() => {
91
+ manager = new SandboxManager(new InMemorySandboxProvider());
92
+ });
93
+
94
+ it("creates sandbox with initial files", async () => {
95
+ const { sandboxId } = await manager.create({
96
+ initialFiles: {
97
+ "/src/index.ts": 'console.log("hello");',
98
+ "/README.md": "# Hello",
99
+ },
100
+ });
101
+ const sandbox = await manager.getSandbox(sandboxId);
102
+ const content = await sandbox.fs.readFile("/src/index.ts");
103
+ expect(content).toBe('console.log("hello");');
104
+ });
105
+
106
+ it("supports filesystem operations", async () => {
107
+ const { sandboxId } = await manager.create();
108
+ const { fs } = await manager.getSandbox(sandboxId);
109
+
110
+ await fs.writeFile("/test.txt", "hello");
111
+ expect(await fs.exists("/test.txt")).toBe(true);
112
+ expect(await fs.readFile("/test.txt")).toBe("hello");
113
+
114
+ await fs.appendFile("/test.txt", " world");
115
+ expect(await fs.readFile("/test.txt")).toBe("hello world");
116
+
117
+ await fs.mkdir("/mydir", { recursive: true });
118
+ expect(await fs.exists("/mydir")).toBe(true);
119
+
120
+ const stat = await fs.stat("/test.txt");
121
+ expect(stat.isFile).toBe(true);
122
+ expect(stat.isDirectory).toBe(false);
123
+ });
124
+
125
+ it("supports shell execution", async () => {
126
+ const { sandboxId } = await manager.create({
127
+ initialFiles: { "/data.txt": "hello world" },
128
+ });
129
+ const sandbox = await manager.getSandbox(sandboxId);
130
+
131
+ const result = await sandbox.exec("cat /data.txt");
132
+ expect(result.exitCode).toBe(0);
133
+ expect(result.stdout).toBe("hello world");
134
+ });
135
+
136
+ it("reports correct capabilities", async () => {
137
+ const { sandboxId } = await manager.create();
138
+ const sandbox = await manager.getSandbox(sandboxId);
139
+ expect(sandbox.capabilities).toEqual({
140
+ filesystem: true,
141
+ execution: true,
142
+ persistence: true,
143
+ });
144
+ });
145
+
146
+ it("readdirWithFileTypes works", async () => {
147
+ const { sandboxId } = await manager.create({
148
+ initialFiles: {
149
+ "/dir/a.txt": "a",
150
+ "/dir/b.txt": "b",
151
+ },
152
+ });
153
+ const { fs } = await manager.getSandbox(sandboxId);
154
+ const entries = await fs.readdirWithFileTypes("/dir");
155
+ expect(entries.length).toBe(2);
156
+ expect(entries.every((e) => e.isFile)).toBe(true);
157
+ });
158
+ });