zeitlich 0.2.2 → 0.2.4

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 (46) hide show
  1. package/README.md +34 -31
  2. package/dist/index.cjs +330 -399
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +24 -43
  5. package/dist/index.d.ts +24 -43
  6. package/dist/index.js +301 -373
  7. package/dist/index.js.map +1 -1
  8. package/dist/{workflow-BQf5EfNN.d.cts → workflow-PjeURKw4.d.cts} +265 -258
  9. package/dist/{workflow-BQf5EfNN.d.ts → workflow-PjeURKw4.d.ts} +265 -258
  10. package/dist/workflow.cjs +223 -281
  11. package/dist/workflow.cjs.map +1 -1
  12. package/dist/workflow.d.cts +2 -3
  13. package/dist/workflow.d.ts +2 -3
  14. package/dist/workflow.js +198 -258
  15. package/dist/workflow.js.map +1 -1
  16. package/package.json +3 -2
  17. package/src/activities.ts +0 -32
  18. package/src/index.ts +14 -11
  19. package/src/lib/model-invoker.ts +7 -1
  20. package/src/lib/session.ts +54 -109
  21. package/src/lib/thread-manager.ts +45 -37
  22. package/src/lib/tool-router.ts +148 -108
  23. package/src/lib/types.ts +35 -26
  24. package/src/tools/ask-user-question/handler.ts +5 -5
  25. package/src/tools/ask-user-question/tool.ts +3 -2
  26. package/src/tools/bash/bash.test.ts +12 -12
  27. package/src/tools/bash/handler.ts +5 -5
  28. package/src/tools/bash/tool.ts +3 -2
  29. package/src/tools/edit/handler.ts +78 -123
  30. package/src/tools/edit/tool.ts +3 -2
  31. package/src/tools/glob/handler.ts +17 -48
  32. package/src/tools/glob/tool.ts +3 -2
  33. package/src/tools/grep/tool.ts +3 -2
  34. package/src/tools/{read → read-file}/tool.ts +3 -2
  35. package/src/tools/{task → subagent}/handler.ts +18 -31
  36. package/src/tools/{task → subagent}/tool.ts +13 -20
  37. package/src/tools/task-create/handler.ts +5 -11
  38. package/src/tools/task-create/tool.ts +3 -2
  39. package/src/tools/task-get/handler.ts +5 -10
  40. package/src/tools/task-get/tool.ts +3 -2
  41. package/src/tools/task-list/handler.ts +5 -10
  42. package/src/tools/task-list/tool.ts +3 -2
  43. package/src/tools/task-update/handler.ts +5 -12
  44. package/src/tools/task-update/tool.ts +3 -2
  45. package/src/tools/{write → write-file}/tool.ts +5 -6
  46. package/src/workflow.ts +24 -21
package/src/activities.ts CHANGED
@@ -2,18 +2,14 @@ import type Redis from "ioredis";
2
2
  import { createThreadManager } from "./lib/thread-manager";
3
3
  import type { ToolResultConfig } from "./lib/types";
4
4
  import {
5
- type AIMessage,
6
- mapStoredMessageToChatMessage,
7
5
  type MessageContent,
8
6
  type StoredMessage,
9
7
  } from "@langchain/core/messages";
10
- import type { RawToolCall } from "./lib/tool-router";
11
8
  /**
12
9
  * Shared Zeitlich activities - thread management and message handling
13
10
  * Note: runAgent is workflow-specific and should be created per-workflow
14
11
  */
15
12
  export interface ZeitlichSharedActivities {
16
- appendSystemMessage(threadId: string, content: string): Promise<void>;
17
13
  /**
18
14
  * Append a tool result to the thread.
19
15
  * Handles JSON serialization and optional cache points.
@@ -25,11 +21,6 @@ export interface ZeitlichSharedActivities {
25
21
  */
26
22
  initializeThread(threadId: string): Promise<void>;
27
23
 
28
- /**
29
- * Append a system message to a thread.
30
- */
31
- appendSystemMessage(threadId: string, content: string): Promise<void>;
32
-
33
24
  /**
34
25
  * Append messages to a thread.
35
26
  */
@@ -46,11 +37,6 @@ export interface ZeitlichSharedActivities {
46
37
  content: string | MessageContent
47
38
  ): Promise<void>;
48
39
 
49
- /**
50
- * Extract raw tool calls from a stored message.
51
- * Returns unvalidated tool calls - use toolRegistry.parseToolCall() to validate.
52
- */
53
- parseToolCalls(storedMessage: StoredMessage): Promise<RawToolCall[]>;
54
40
  }
55
41
 
56
42
  /**
@@ -62,14 +48,6 @@ export interface ZeitlichSharedActivities {
62
48
  */
63
49
  export function createSharedActivities(redis: Redis): ZeitlichSharedActivities {
64
50
  return {
65
- async appendSystemMessage(
66
- threadId: string,
67
- content: string
68
- ): Promise<void> {
69
- const thread = createThreadManager({ redis, threadId });
70
- await thread.appendSystemMessage(content);
71
- },
72
-
73
51
  async appendToolResult(config: ToolResultConfig): Promise<void> {
74
52
  const { threadId, toolCallId, content } = config;
75
53
  const thread = createThreadManager({ redis, threadId });
@@ -98,15 +76,5 @@ export function createSharedActivities(redis: Redis): ZeitlichSharedActivities {
98
76
  await thread.appendHumanMessage(content);
99
77
  },
100
78
 
101
- async parseToolCalls(storedMessage: StoredMessage): Promise<RawToolCall[]> {
102
- const message = mapStoredMessageToChatMessage(storedMessage) as AIMessage;
103
- const toolCalls = message.tool_calls ?? [];
104
-
105
- return toolCalls.map((toolCall) => ({
106
- id: toolCall.id,
107
- name: toolCall.name,
108
- args: toolCall.args,
109
- }));
110
- },
111
79
  };
112
80
  }
package/src/index.ts CHANGED
@@ -9,7 +9,7 @@
9
9
  * @example
10
10
  * ```typescript
11
11
  * // In your activities file
12
- * import { invokeModel, globHandler } from '@bead-ai/zeitlich';
12
+ * import { invokeModel, createGlobHandler } from '@bead-ai/zeitlich';
13
13
  *
14
14
  * // In your worker file
15
15
  * import { ZeitlichPlugin } from '@bead-ai/zeitlich';
@@ -28,25 +28,28 @@ export type { ZeitlichPluginOptions } from "./plugin";
28
28
  export { createSharedActivities } from "./activities";
29
29
  export type { ZeitlichSharedActivities } from "./activities";
30
30
 
31
+ // Auto-append wrapper for large tool results (activity-side only)
32
+ export { withAutoAppend } from "./lib/tool-router";
33
+
31
34
  // Model invocation (requires Redis, LangChain)
32
35
  export { invokeModel } from "./lib/model-invoker";
33
36
  export type { InvokeModelConfig } from "./lib/model-invoker";
34
37
 
35
38
  // Tool handlers (activity implementations)
36
- // These are direct functions that accept scopedNodes per-call for dynamic file trees
37
- export { handleAskUserQuestionToolResult } from "./tools/ask-user-question/handler";
38
- export { globHandler } from "./tools/glob/handler";
39
+ // All handlers follow the factory pattern: createXHandler(deps) => handler(args)
40
+ export { createAskUserQuestionHandler } from "./tools/ask-user-question/handler";
41
+ export { createGlobHandler } from "./tools/glob/handler";
39
42
 
40
- export { editHandler } from "./tools/edit/handler";
41
- export type {
42
- EditResult,
43
- EditHandlerResponse,
44
- EditHandlerOptions,
45
- } from "./tools/edit/handler";
43
+ export { createEditHandler } from "./tools/edit/handler";
46
44
 
47
- export { handleBashTool } from "./tools/bash/handler";
45
+ export { createBashHandler } from "./tools/bash/handler";
48
46
 
49
47
  export { toTree } from "./lib/fs";
50
48
 
51
49
  export { getStateQuery } from "./lib/state-manager";
52
50
  export { createThreadManager } from "./lib/thread-manager";
51
+ export type {
52
+ BaseThreadManager,
53
+ ThreadManager,
54
+ ThreadManagerConfig,
55
+ } from "./lib/thread-manager";
@@ -63,9 +63,15 @@ export async function invokeModel({
63
63
 
64
64
  await thread.append([response.toDict()]);
65
65
 
66
+ const toolCalls = response.tool_calls ?? [];
67
+
66
68
  return {
67
69
  message: response.toDict(),
68
- stopReason: (response.response_metadata?.stop_reason as string) ?? null,
70
+ rawToolCalls: toolCalls.map((tc) => ({
71
+ id: tc.id,
72
+ name: tc.name,
73
+ args: tc.args,
74
+ })),
69
75
  usage: {
70
76
  input_tokens: response.usage_metadata?.input_tokens,
71
77
  output_tokens: response.usage_metadata?.output_tokens,
@@ -1,36 +1,23 @@
1
1
  import { proxyActivities } from "@temporalio/workflow";
2
2
  import type { ZeitlichSharedActivities } from "../activities";
3
3
  import type {
4
+ ThreadOps,
4
5
  ZeitlichAgentConfig,
5
6
  SessionStartHook,
6
7
  SessionEndHook,
7
8
  SessionExitReason,
8
- SubagentConfig,
9
9
  } from "./types";
10
10
  import { type AgentStateManager, type JsonSerializable } from "./state-manager";
11
11
  import {
12
12
  createToolRouter,
13
- type ParsedToolCall,
14
13
  type ParsedToolCallUnion,
15
- type RawToolCall,
16
14
  type ToolMap,
17
15
  } from "./tool-router";
18
- import type { StoredMessage } from "@langchain/core/messages";
19
- import { createTaskTool, type TaskToolSchemaType } from "../tools/task/tool";
20
16
 
21
- export interface ZeitlichSession {
17
+ export interface ZeitlichSession<M = unknown> {
22
18
  runSession<T extends JsonSerializable<T>>(args: {
23
19
  stateManager: AgentStateManager<T>;
24
- }): Promise<StoredMessage | null>;
25
- }
26
-
27
- async function resolvePrompt(
28
- prompt: string | (() => string | Promise<string>)
29
- ): Promise<string> {
30
- if (typeof prompt === "function") {
31
- return prompt();
32
- }
33
- return prompt;
20
+ }): Promise<M | null>;
34
21
  }
35
22
 
36
23
  /**
@@ -43,48 +30,24 @@ export interface SessionLifecycleHooks {
43
30
  onSessionEnd?: SessionEndHook;
44
31
  }
45
32
 
46
- export const createSession = async <T extends ToolMap>({
33
+ export const createSession = async <T extends ToolMap, M = unknown>({
47
34
  threadId,
48
35
  agentName,
49
36
  maxTurns = 50,
50
37
  metadata = {},
51
38
  runAgent,
52
- baseSystemPrompt,
53
- instructionsPrompt,
39
+ threadOps,
54
40
  buildContextMessage,
55
- buildFileTree = async (): Promise<string> => "",
56
41
  subagents,
57
42
  tools = {} as T,
58
43
  processToolsInParallel = true,
59
- buildInTools = {},
60
44
  hooks = {},
61
- }: ZeitlichAgentConfig<T>): Promise<ZeitlichSession> => {
62
- const {
63
- initializeThread,
64
- appendHumanMessage,
65
- parseToolCalls,
66
- appendToolResult,
67
- appendSystemMessage,
68
- } = proxyActivities<ZeitlichSharedActivities>({
69
- startToCloseTimeout: "30m",
70
- retry: {
71
- maximumAttempts: 6,
72
- initialInterval: "5s",
73
- maximumInterval: "15m",
74
- backoffCoefficient: 4,
75
- },
76
- heartbeatTimeout: "5m",
77
- });
78
-
79
- const fileTree = await buildFileTree();
80
-
45
+ }: ZeitlichAgentConfig<T, M>): Promise<ZeitlichSession<M>> => {
81
46
  const toolRouter = createToolRouter({
82
47
  tools,
83
- appendToolResult,
48
+ appendToolResult: threadOps.appendToolResult,
84
49
  threadId,
85
50
  hooks,
86
- buildInTools,
87
- fileTree,
88
51
  subagents,
89
52
  parallel: processToolsInParallel,
90
53
  });
@@ -106,7 +69,7 @@ export const createSession = async <T extends ToolMap>({
106
69
  };
107
70
 
108
71
  return {
109
- runSession: async ({ stateManager }): Promise<StoredMessage | null> => {
72
+ runSession: async ({ stateManager }): Promise<M | null> => {
110
73
  if (hooks.onSessionStart) {
111
74
  await hooks.onSessionStart({
112
75
  threadId,
@@ -116,15 +79,8 @@ export const createSession = async <T extends ToolMap>({
116
79
  }
117
80
  stateManager.setTools(toolRouter.getToolDefinitions());
118
81
 
119
- await initializeThread(threadId);
120
- await appendSystemMessage(
121
- threadId,
122
- [
123
- await resolvePrompt(baseSystemPrompt),
124
- await resolvePrompt(instructionsPrompt),
125
- ].join("\n")
126
- );
127
- await appendHumanMessage(threadId, await buildContextMessage());
82
+ await threadOps.initializeThread(threadId);
83
+ await threadOps.appendHumanMessage(threadId, await buildContextMessage());
128
84
 
129
85
  let exitReason: SessionExitReason = "completed";
130
86
 
@@ -137,38 +93,29 @@ export const createSession = async <T extends ToolMap>({
137
93
  stateManager.incrementTurns();
138
94
  const currentTurn = stateManager.getTurns();
139
95
 
140
- const { message, stopReason } = await runAgent({
96
+ const { message, rawToolCalls } = await runAgent({
141
97
  threadId,
142
98
  agentName,
143
99
  metadata,
144
100
  });
145
101
 
146
- if (stopReason === "end_turn") {
147
- stateManager.complete();
148
- exitReason = "completed";
149
- return message;
150
- }
151
-
152
102
  // No tools configured - treat any non-end_turn as completed
153
- if (!toolRouter.hasTools()) {
103
+ if (!toolRouter.hasTools() || rawToolCalls.length === 0) {
154
104
  stateManager.complete();
155
105
  exitReason = "completed";
156
106
  return message;
157
107
  }
158
108
 
159
- const rawToolCalls: RawToolCall[] = await parseToolCalls(message);
160
-
161
- // Parse tool calls, catching schema errors and returning them to the agent
109
+ // Parse all tool calls uniformly through the router
162
110
  const parsedToolCalls: ParsedToolCallUnion<T>[] = [];
163
- for (const tc of rawToolCalls.filter(
164
- (tc: RawToolCall) => tc.name !== "Task"
165
- )) {
111
+ for (const tc of rawToolCalls) {
166
112
  try {
167
113
  parsedToolCalls.push(toolRouter.parseToolCall(tc));
168
114
  } catch (error) {
169
- await appendToolResult({
115
+ await threadOps.appendToolResult({
170
116
  threadId,
171
117
  toolCallId: tc.id ?? "",
118
+ toolName: tc.name,
172
119
  content: JSON.stringify({
173
120
  error: `Invalid tool call for "${tc.name}": ${error instanceof Error ? error.message : String(error)}`,
174
121
  }),
@@ -176,47 +123,10 @@ export const createSession = async <T extends ToolMap>({
176
123
  }
177
124
  }
178
125
 
179
- const taskToolCalls: ParsedToolCall<
180
- "Task",
181
- TaskToolSchemaType<SubagentConfig[]>
182
- >[] = [];
183
- if (subagents && subagents.length > 0) {
184
- for (const tc of rawToolCalls.filter(
185
- (tc: RawToolCall) => tc.name === "Task"
186
- )) {
187
- try {
188
- const parsedArgs = createTaskTool(subagents).schema.parse(
189
- tc.args
190
- );
191
- taskToolCalls.push({
192
- id: tc.id ?? "",
193
- name: tc.name,
194
- args: parsedArgs,
195
- } as ParsedToolCall<
196
- "Task",
197
- TaskToolSchemaType<SubagentConfig[]>
198
- >);
199
- } catch (error) {
200
- await appendToolResult({
201
- threadId,
202
- toolCallId: tc.id ?? "",
203
- content: JSON.stringify({
204
- error: `Invalid tool call for "Task": ${error instanceof Error ? error.message : String(error)}`,
205
- }),
206
- });
207
- }
208
- }
209
- }
210
-
211
126
  // Hooks can call stateManager.waitForInput() to pause the session
212
- await toolRouter.processToolCalls(
213
- [...parsedToolCalls, ...taskToolCalls] as ParsedToolCallUnion<
214
- T & { Task: TaskToolSchemaType<SubagentConfig[]> }
215
- >[],
216
- {
217
- turn: currentTurn,
218
- }
219
- );
127
+ await toolRouter.processToolCalls(parsedToolCalls, {
128
+ turn: currentTurn,
129
+ });
220
130
 
221
131
  if (stateManager.getStatus() === "WAITING_FOR_INPUT") {
222
132
  exitReason = "waiting_for_input";
@@ -240,3 +150,38 @@ export const createSession = async <T extends ToolMap>({
240
150
  },
241
151
  };
242
152
  };
153
+
154
+ /**
155
+ * Proxy the default ZeitlichSharedActivities as ThreadOps<StoredMessage>.
156
+ * Call this in workflow code for the standard LangChain/StoredMessage setup.
157
+ *
158
+ * @example
159
+ * ```typescript
160
+ * const session = await createSession({
161
+ * threadOps: proxyDefaultThreadOps(),
162
+ * // ...
163
+ * });
164
+ * ```
165
+ */
166
+ export function proxyDefaultThreadOps(
167
+ options?: Parameters<typeof proxyActivities>[0]
168
+ ): ThreadOps {
169
+ const activities = proxyActivities<ZeitlichSharedActivities>(
170
+ options ?? {
171
+ startToCloseTimeout: "30m",
172
+ retry: {
173
+ maximumAttempts: 6,
174
+ initialInterval: "5s",
175
+ maximumInterval: "15m",
176
+ backoffCoefficient: 4,
177
+ },
178
+ heartbeatTimeout: "5m",
179
+ }
180
+ );
181
+
182
+ return {
183
+ initializeThread: activities.initializeThread,
184
+ appendHumanMessage: activities.appendHumanMessage,
185
+ appendToolResult: activities.appendToolResult,
186
+ };
187
+ }
@@ -7,7 +7,6 @@ import {
7
7
  type MessageContent,
8
8
  type MessageStructure,
9
9
  type StoredMessage,
10
- SystemMessage,
11
10
  ToolMessage,
12
11
  } from "@langchain/core/messages";
13
12
  import { v4 as uuidv4 } from "uuid";
@@ -24,28 +23,31 @@ function getThreadKey(threadId: string, key: string): string {
24
23
  */
25
24
  export type ToolMessageContent = $InferMessageContent<MessageStructure, "tool">;
26
25
 
27
- export interface ThreadManagerConfig {
26
+ export interface ThreadManagerConfig<T = StoredMessage> {
28
27
  redis: Redis;
29
28
  threadId: string;
30
29
  /** Thread key, defaults to 'messages' */
31
30
  key?: string;
31
+ /** Custom serializer, defaults to JSON.stringify */
32
+ serialize?: (message: T) => string;
33
+ /** Custom deserializer, defaults to JSON.parse */
34
+ deserialize?: (raw: string) => T;
32
35
  }
33
36
 
34
- export interface ThreadManager {
35
- /** Append a system message to the thread */
36
- appendSystemMessage(content: string): Promise<void>;
37
+ /** Generic thread manager for any message type */
38
+ export interface BaseThreadManager<T> {
37
39
  /** Initialize an empty thread */
38
40
  initialize(): Promise<void>;
39
41
  /** Load all messages from the thread */
40
- load(): Promise<StoredMessage[]>;
42
+ load(): Promise<T[]>;
41
43
  /** Append messages to the thread */
42
- append(messages: StoredMessage[]): Promise<void>;
44
+ append(messages: T[]): Promise<void>;
43
45
  /** Delete the thread */
44
46
  delete(): Promise<void>;
47
+ }
45
48
 
46
- /** Create a SystemMessage (returns StoredMessage for storage) */
47
- createSystemMessage(content: string): StoredMessage;
48
-
49
+ /** Thread manager with StoredMessage convenience helpers */
50
+ export interface ThreadManager extends BaseThreadManager<StoredMessage> {
49
51
  /** Create a HumanMessage (returns StoredMessage for storage) */
50
52
  createHumanMessage(content: string | MessageContent): StoredMessage;
51
53
 
@@ -74,26 +76,38 @@ export interface ThreadManager {
74
76
 
75
77
  /**
76
78
  * Creates a thread manager for handling conversation state in Redis.
79
+ * Without generic args, returns a full ThreadManager with StoredMessage helpers.
80
+ * With a custom type T, returns a BaseThreadManager<T>.
77
81
  */
78
- export function createThreadManager(
79
- config: ThreadManagerConfig
80
- ): ThreadManager {
81
- const { redis, threadId, key = "messages" } = config;
82
+ export function createThreadManager(config: ThreadManagerConfig): ThreadManager;
83
+ export function createThreadManager<T>(
84
+ config: ThreadManagerConfig<T>
85
+ ): BaseThreadManager<T>;
86
+ export function createThreadManager<T>(
87
+ config: ThreadManagerConfig<T>
88
+ ): BaseThreadManager<T> {
89
+ const {
90
+ redis,
91
+ threadId,
92
+ key = "messages",
93
+ serialize = (m: T): string => JSON.stringify(m),
94
+ deserialize = (raw: string): T => JSON.parse(raw) as T,
95
+ } = config;
82
96
  const redisKey = getThreadKey(threadId, key);
83
97
 
84
- return {
98
+ const base: BaseThreadManager<T> = {
85
99
  async initialize(): Promise<void> {
86
100
  await redis.del(redisKey);
87
101
  },
88
102
 
89
- async load(): Promise<StoredMessage[]> {
103
+ async load(): Promise<T[]> {
90
104
  const data = await redis.lrange(redisKey, 0, -1);
91
- return data.map((item) => JSON.parse(item) as StoredMessage);
105
+ return data.map(deserialize);
92
106
  },
93
107
 
94
- async append(messages: StoredMessage[]): Promise<void> {
108
+ async append(messages: T[]): Promise<void> {
95
109
  if (messages.length > 0) {
96
- await redis.rpush(redisKey, ...messages.map((m) => JSON.stringify(m)));
110
+ await redis.rpush(redisKey, ...messages.map(serialize));
97
111
  await redis.expire(redisKey, THREAD_TTL_SECONDS);
98
112
  }
99
113
  },
@@ -101,7 +115,11 @@ export function createThreadManager(
101
115
  async delete(): Promise<void> {
102
116
  await redis.del(redisKey);
103
117
  },
118
+ };
104
119
 
120
+ // If no custom serialize/deserialize were provided and T defaults to StoredMessage,
121
+ // the overload guarantees the caller gets ThreadManager with convenience helpers.
122
+ const helpers = {
105
123
  createHumanMessage(content: string | MessageContent): StoredMessage {
106
124
  return new HumanMessage({
107
125
  id: uuidv4(),
@@ -131,39 +149,29 @@ export function createThreadManager(
131
149
  toolCallId: string
132
150
  ): StoredMessage {
133
151
  return new ToolMessage({
134
- // Cast needed due to langchain type compatibility
135
152
  content: content as MessageContent,
136
153
  tool_call_id: toolCallId,
137
154
  }).toDict();
138
155
  },
139
156
 
140
- createSystemMessage(content: string): StoredMessage {
141
- return new SystemMessage({
142
- content,
143
- }).toDict();
144
- },
145
-
146
- async appendSystemMessage(content: string): Promise<void> {
147
- const message = this.createSystemMessage(content);
148
- await this.append([message]);
149
- },
150
-
151
157
  async appendHumanMessage(content: string | MessageContent): Promise<void> {
152
- const message = this.createHumanMessage(content);
153
- await this.append([message]);
158
+ const message = helpers.createHumanMessage(content);
159
+ await (base as BaseThreadManager<StoredMessage>).append([message]);
154
160
  },
155
161
 
156
162
  async appendToolMessage(
157
163
  content: ToolMessageContent,
158
164
  toolCallId: string
159
165
  ): Promise<void> {
160
- const message = this.createToolMessage(content, toolCallId);
161
- await this.append([message]);
166
+ const message = helpers.createToolMessage(content, toolCallId);
167
+ await (base as BaseThreadManager<StoredMessage>).append([message]);
162
168
  },
163
169
 
164
170
  async appendAIMessage(content: string | MessageContent): Promise<void> {
165
- const message = this.createAIMessage(content);
166
- await this.append([message]);
171
+ const message = helpers.createAIMessage(content as string);
172
+ await (base as BaseThreadManager<StoredMessage>).append([message]);
167
173
  },
168
174
  };
175
+
176
+ return Object.assign(base, helpers);
169
177
  }