zeitlich 0.2.7 → 0.2.8

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.
@@ -24,7 +24,11 @@ import type { MessageContent } from "@langchain/core/messages";
24
24
  export interface ZeitlichSession<M = unknown> {
25
25
  runSession<T extends JsonSerializable<T>>(args: {
26
26
  stateManager: AgentStateManager<T>;
27
- }): Promise<M | null>;
27
+ }): Promise<{
28
+ finalMessage: M | null;
29
+ exitReason: SessionExitReason;
30
+ usage: ReturnType<AgentStateManager<T>["getTotalUsage"]>;
31
+ }>;
28
32
  }
29
33
 
30
34
  /**
@@ -40,6 +44,7 @@ export interface SessionLifecycleHooks {
40
44
  export const createSession = async <T extends ToolMap, M = unknown>({
41
45
  threadId,
42
46
  agentName,
47
+ description,
43
48
  maxTurns = 50,
44
49
  metadata = {},
45
50
  runAgent,
@@ -86,7 +91,13 @@ export const createSession = async <T extends ToolMap, M = unknown>({
86
91
  };
87
92
 
88
93
  return {
89
- runSession: async ({ stateManager }): Promise<M | null> => {
94
+ runSession: async ({
95
+ stateManager,
96
+ }): Promise<{
97
+ finalMessage: M | null;
98
+ exitReason: SessionExitReason;
99
+ usage: ReturnType<typeof stateManager.getTotalUsage>;
100
+ }> => {
90
101
  setHandler(
91
102
  defineUpdate<unknown, [MessageContent]>(`add${agentName}Message`),
92
103
  async (message: MessageContent) => {
@@ -115,8 +126,6 @@ export const createSession = async <T extends ToolMap, M = unknown>({
115
126
  });
116
127
  }
117
128
 
118
- stateManager.setTools(toolRouter.getToolDefinitions());
119
-
120
129
  await initializeThread(threadId);
121
130
  if (appendSystemPrompt && systemPrompt && systemPrompt.trim() !== "") {
122
131
  await appendSystemMessage(threadId, systemPrompt);
@@ -134,17 +143,29 @@ export const createSession = async <T extends ToolMap, M = unknown>({
134
143
  stateManager.incrementTurns();
135
144
  const currentTurn = stateManager.getTurns();
136
145
 
137
- const { message, rawToolCalls } = await runAgent({
146
+ stateManager.setTools(toolRouter.getToolDefinitions());
147
+
148
+ const { message, rawToolCalls, usage } = await runAgent({
138
149
  threadId,
139
150
  agentName,
140
151
  metadata,
152
+ systemPrompt,
153
+ description,
141
154
  });
142
155
 
156
+ if (usage) {
157
+ stateManager.updateUsage(usage);
158
+ }
159
+
143
160
  // No tools configured - treat any non-end_turn as completed
144
161
  if (!toolRouter.hasTools() || rawToolCalls.length === 0) {
145
162
  stateManager.complete();
146
163
  exitReason = "completed";
147
- return message;
164
+ return {
165
+ finalMessage: message,
166
+ exitReason,
167
+ usage: stateManager.getTotalUsage(),
168
+ };
148
169
  }
149
170
 
150
171
  // Parse all tool calls uniformly through the router
@@ -165,9 +186,18 @@ export const createSession = async <T extends ToolMap, M = unknown>({
165
186
  }
166
187
 
167
188
  // Hooks can call stateManager.waitForInput() to pause the session
168
- await toolRouter.processToolCalls(parsedToolCalls, {
169
- turn: currentTurn,
170
- });
189
+ const toolCallResults = await toolRouter.processToolCalls(
190
+ parsedToolCalls,
191
+ {
192
+ turn: currentTurn,
193
+ }
194
+ );
195
+
196
+ for (const result of toolCallResults) {
197
+ if (result.usage) {
198
+ stateManager.updateUsage(result.usage);
199
+ }
200
+ }
171
201
 
172
202
  if (stateManager.getStatus() === "WAITING_FOR_INPUT") {
173
203
  const conditionMet = await condition(
@@ -195,7 +225,11 @@ export const createSession = async <T extends ToolMap, M = unknown>({
195
225
  await callSessionEnd(exitReason, stateManager.getTurns());
196
226
  }
197
227
 
198
- return null;
228
+ return {
229
+ finalMessage: null,
230
+ exitReason,
231
+ usage: stateManager.getTotalUsage(),
232
+ };
199
233
  },
200
234
  };
201
235
  };
@@ -114,6 +114,25 @@ export interface AgentStateManager<TCustom extends JsonSerializable<TCustom>> {
114
114
 
115
115
  /** Set the tools (converts Zod schemas to JSON Schema for serialization) */
116
116
  setTools(newTools: ToolDefinition[]): void;
117
+
118
+ /** Update the usage */
119
+ updateUsage(usage: {
120
+ inputTokens?: number;
121
+ outputTokens?: number;
122
+ cachedWriteTokens?: number;
123
+ cachedReadTokens?: number;
124
+ reasonTokens?: number;
125
+ }): void;
126
+
127
+ /** Get the total usage */
128
+ getTotalUsage(): {
129
+ totalInputTokens: number;
130
+ totalOutputTokens: number;
131
+ totalCachedWriteTokens: number;
132
+ totalCachedReadTokens: number;
133
+ totalReasonTokens: number;
134
+ turns: number;
135
+ };
117
136
  }
118
137
 
119
138
  /**
@@ -140,6 +159,11 @@ export function createAgentStateManager<
140
159
  let version = initialState?.version ?? 0;
141
160
  let turns = initialState?.turns ?? 0;
142
161
  let tools = initialState?.tools ?? [];
162
+ let totalInputTokens = 0;
163
+ let totalOutputTokens = 0;
164
+ let totalCachedWriteTokens = 0;
165
+ let totalCachedReadTokens = 0;
166
+ let totalReasonTokens = 0;
143
167
 
144
168
  // Tasks state
145
169
  const tasks = new Map<string, WorkflowTask>(initialState?.tasks);
@@ -283,6 +307,38 @@ export function createAgentStateManager<
283
307
  }
284
308
  return deleted;
285
309
  },
310
+
311
+ updateUsage(usage: {
312
+ inputTokens?: number;
313
+ outputTokens?: number;
314
+ cachedWriteTokens?: number;
315
+ cachedReadTokens?: number;
316
+ reasonTokens?: number;
317
+ }): void {
318
+ totalInputTokens += usage.inputTokens ?? 0;
319
+ totalOutputTokens += usage.outputTokens ?? 0;
320
+ totalCachedWriteTokens += usage.cachedWriteTokens ?? 0;
321
+ totalCachedReadTokens += usage.cachedReadTokens ?? 0;
322
+ totalReasonTokens += usage.reasonTokens ?? 0;
323
+ },
324
+
325
+ getTotalUsage(): {
326
+ totalInputTokens: number;
327
+ totalOutputTokens: number;
328
+ totalCachedWriteTokens: number;
329
+ totalCachedReadTokens: number;
330
+ totalReasonTokens: number;
331
+ turns: number;
332
+ } {
333
+ return {
334
+ totalInputTokens,
335
+ totalOutputTokens,
336
+ totalCachedWriteTokens,
337
+ totalCachedReadTokens,
338
+ totalReasonTokens,
339
+ turns,
340
+ };
341
+ },
286
342
  };
287
343
  }
288
344
 
@@ -6,6 +6,7 @@ import type {
6
6
  PreToolUseHookResult,
7
7
  SubagentConfig,
8
8
  SubagentHooks,
9
+ TokenUsage,
9
10
  ToolHooks,
10
11
  ToolResultConfig,
11
12
  } from "./types";
@@ -53,7 +54,7 @@ export interface ToolWithHandler<
53
54
  strict?: boolean;
54
55
  max_uses?: number;
55
56
  /** Whether this tool is available to the agent (default: true). Disabled tools are excluded from definitions and rejected at parse time. */
56
- enabled?: boolean;
57
+ enabled?: () => boolean;
57
58
  /** Per-tool lifecycle hooks (run in addition to global hooks) */
58
59
  hooks?: ToolHooks<z.infer<TSchema>, TResult>;
59
60
  }
@@ -76,7 +77,7 @@ export type ToolMap = Record<
76
77
  handler: ToolHandler<any, any, any>;
77
78
  strict?: boolean;
78
79
  max_uses?: number;
79
- enabled?: boolean;
80
+ enabled?: () => boolean;
80
81
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
81
82
  hooks?: ToolHooks<any, any>;
82
83
  }
@@ -153,6 +154,8 @@ export interface ToolHandlerResponse<TResult = null> {
153
154
  * payloads through Temporal's activity payload limit.
154
155
  */
155
156
  resultAppended?: boolean;
157
+ /** Token usage from the tool execution (e.g. child agent invocations) */
158
+ usage?: TokenUsage;
156
159
  }
157
160
 
158
161
  /**
@@ -228,6 +231,7 @@ export interface ToolCallResult<
228
231
  toolCallId: string;
229
232
  name: TName;
230
233
  data: TResult;
234
+ usage?: TokenUsage;
231
235
  }
232
236
 
233
237
  /**
@@ -412,16 +416,14 @@ export function createToolRouter<T extends ToolMap>(
412
416
  }
413
417
 
414
418
  /** Check if a tool is enabled (defaults to true when not specified) */
415
- const isEnabled = (tool: ToolMap[string]): boolean => tool.enabled !== false;
419
+ const isEnabled = (tool: ToolMap[string]): boolean =>
420
+ tool.enabled?.() ?? true;
416
421
 
417
422
  if (options.subagents) {
418
- const enabledSubagents = options.subagents.filter(
419
- (s) => s.enabled !== false
420
- );
421
- if (enabledSubagents.length > 0) {
423
+ if (options.subagents.length > 0) {
422
424
  // Build per-subagent hook dispatcher keyed by subagent name
423
425
  const subagentHooksMap = new Map<string, SubagentHooks>();
424
- for (const s of enabledSubagents) {
426
+ for (const s of options.subagents) {
425
427
  if (s.hooks) subagentHooksMap.set(s.agentName, s.hooks);
426
428
  }
427
429
 
@@ -429,8 +431,8 @@ export function createToolRouter<T extends ToolMap>(
429
431
  (args as SubagentArgs).subagent;
430
432
 
431
433
  toolMap.set("Subagent", {
432
- ...createSubagentTool(enabledSubagents),
433
- handler: createSubagentHandler(enabledSubagents),
434
+ ...createSubagentTool(options.subagents),
435
+ handler: createSubagentHandler(options.subagents),
434
436
  ...(subagentHooksMap.size > 0 && {
435
437
  hooks: {
436
438
  onPreToolUse: async (ctx): Promise<PreToolUseHookResult> => {
package/src/lib/types.ts CHANGED
@@ -32,6 +32,10 @@ export interface BaseAgentState {
32
32
  turns: number;
33
33
  tasks: Map<string, WorkflowTask>;
34
34
  systemPrompt: string;
35
+ totalInputTokens: number;
36
+ totalOutputTokens: number;
37
+ cachedWriteTokens: number;
38
+ cachedReadtTokens: number;
35
39
  }
36
40
 
37
41
  /**
@@ -50,17 +54,21 @@ export interface AgentFile {
50
54
  mimeType?: string;
51
55
  }
52
56
 
57
+ export interface TokenUsage {
58
+ inputTokens?: number;
59
+ outputTokens?: number;
60
+ cachedWriteTokens?: number;
61
+ cachedReadTokens?: number;
62
+ reasonTokens?: number;
63
+ }
64
+
53
65
  /**
54
66
  * Agent response from LLM invocation
55
67
  */
56
68
  export interface AgentResponse<M = StoredMessage> {
57
69
  message: M;
58
70
  rawToolCalls: RawToolCall[];
59
- usage?: {
60
- input_tokens?: number;
61
- output_tokens?: number;
62
- total_tokens?: number;
63
- };
71
+ usage?: TokenUsage;
64
72
  }
65
73
 
66
74
  /**
@@ -143,9 +151,10 @@ export interface SerializableToolDefinition {
143
151
  /**
144
152
  * Configuration passed to runAgent activity
145
153
  */
146
- export interface RunAgentConfig {
154
+ export interface RunAgentConfig extends AgentConfig {
155
+ /** The thread ID to use for the session */
147
156
  threadId: string;
148
- agentName: string;
157
+ /** Metadata for the session */
149
158
  metadata?: Record<string, unknown>;
150
159
  }
151
160
 
@@ -155,6 +164,7 @@ export interface RunAgentConfig {
155
164
  export type RunAgentActivity<M = StoredMessage> = (
156
165
  config: RunAgentConfig
157
166
  ) => Promise<AgentResponse<M>>;
167
+
158
168
  /**
159
169
  * Configuration for appending a tool result
160
170
  */
@@ -186,7 +196,7 @@ export interface SubagentConfig<TResult extends z.ZodType = z.ZodType> {
186
196
  /** Description shown to the parent agent explaining what this subagent does */
187
197
  description: string;
188
198
  /** Whether this subagent is available (default: true). Disabled subagents are excluded from the Subagent tool. */
189
- enabled?: boolean;
199
+ enabled?: () => boolean;
190
200
  /** Temporal workflow function or type name (used with executeChild) */
191
201
  workflow: string | Workflow;
192
202
  /** Optional task queue - defaults to parent's queue if not specified */
@@ -53,7 +53,7 @@ export function createSubagentHandler<
53
53
  taskQueue: config.taskQueue ?? parentTaskQueue,
54
54
  };
55
55
 
56
- const { toolResponse, data } =
56
+ const { toolResponse, data, usage } =
57
57
  typeof config.workflow === "string"
58
58
  ? await executeChild(config.workflow, childOpts)
59
59
  : await executeChild(config.workflow, childOpts);
@@ -66,6 +66,7 @@ export function createSubagentHandler<
66
66
  return {
67
67
  toolResponse,
68
68
  data: validated,
69
+ ...(usage && { usage }),
69
70
  };
70
71
  };
71
72
  }