@townco/ui 0.1.114 → 0.1.116

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.
@@ -158,11 +158,36 @@ export declare function useChatMessages(client: AcpClient | null, startSession:
158
158
  };
159
159
  })[] | undefined;
160
160
  isStreaming?: boolean | undefined;
161
+ context_size?: {
162
+ systemPromptTokens: number;
163
+ toolOverheadTokens?: number | undefined;
164
+ mcpOverheadTokens?: number | undefined;
165
+ userMessagesTokens: number;
166
+ assistantMessagesTokens: number;
167
+ toolInputTokens: number;
168
+ toolResultsTokens: number;
169
+ totalEstimated: number;
170
+ llmReportedInputTokens?: number | undefined;
171
+ modelContextWindow?: number | undefined;
172
+ } | undefined;
161
173
  _meta?: {
174
+ [x: string]: unknown;
162
175
  semanticName?: string | undefined;
163
176
  agentDefinitionName?: string | undefined;
164
177
  currentActivity?: string | undefined;
165
178
  statusGenerating?: boolean | undefined;
179
+ context_size?: {
180
+ systemPromptTokens: number;
181
+ toolOverheadTokens?: number | undefined;
182
+ mcpOverheadTokens?: number | undefined;
183
+ userMessagesTokens: number;
184
+ assistantMessagesTokens: number;
185
+ toolInputTokens: number;
186
+ toolResultsTokens: number;
187
+ totalEstimated: number;
188
+ llmReportedInputTokens?: number | undefined;
189
+ modelContextWindow?: number | undefined;
190
+ } | undefined;
166
191
  } | undefined;
167
192
  }[] | undefined;
168
193
  subagentStreaming?: boolean | undefined;
@@ -0,0 +1,28 @@
1
+ import type { SubagentMessage } from "../schemas/tool-call.js";
2
+ export interface UseSubagentStreamOptions {
3
+ /** Sub-agent HTTP port */
4
+ port: number;
5
+ /** Sub-agent session ID */
6
+ sessionId: string;
7
+ /** Base host (defaults to localhost) */
8
+ host?: string;
9
+ }
10
+ export interface UseSubagentStreamReturn {
11
+ /** Accumulated messages from the sub-agent */
12
+ messages: SubagentMessage[];
13
+ /** Whether the stream is currently active */
14
+ isStreaming: boolean;
15
+ /** Error message if connection failed */
16
+ error: string | null;
17
+ }
18
+ /**
19
+ * Hook to connect directly to a sub-agent's SSE endpoint and stream messages.
20
+ *
21
+ * This hook:
22
+ * - Connects to the sub-agent's HTTP server at the given port
23
+ * - Subscribes to the /events SSE endpoint with the session ID
24
+ * - Parses incoming session/update notifications
25
+ * - Extracts text chunks and tool calls
26
+ * - Returns accumulated messages for display
27
+ */
28
+ export declare function useSubagentStream(options: UseSubagentStreamOptions | null): UseSubagentStreamReturn;
@@ -0,0 +1,256 @@
1
+ import { createLogger } from "@townco/core";
2
+ import { useCallback, useEffect, useRef, useState } from "react";
3
+ const logger = createLogger("subagent-stream");
4
+ /**
5
+ * Hook to connect directly to a sub-agent's SSE endpoint and stream messages.
6
+ *
7
+ * This hook:
8
+ * - Connects to the sub-agent's HTTP server at the given port
9
+ * - Subscribes to the /events SSE endpoint with the session ID
10
+ * - Parses incoming session/update notifications
11
+ * - Extracts text chunks and tool calls
12
+ * - Returns accumulated messages for display
13
+ */
14
+ export function useSubagentStream(options) {
15
+ const [messages, setMessages] = useState([]);
16
+ // Start as streaming=true if options provided, since we're about to connect
17
+ const [_isStreaming, setIsStreaming] = useState(!!options);
18
+ const [hasCompleted, setHasCompleted] = useState(false);
19
+ const [error, setError] = useState(null);
20
+ const abortControllerRef = useRef(null);
21
+ const currentMessageRef = useRef(null);
22
+ const updateTimeoutRef = useRef(null);
23
+ // Throttled update to prevent excessive re-renders
24
+ const scheduleUpdate = useCallback(() => {
25
+ // If there's already a pending timeout, let it handle the update
26
+ // (it will read the latest currentMessageRef value)
27
+ if (updateTimeoutRef.current)
28
+ return;
29
+ updateTimeoutRef.current = setTimeout(() => {
30
+ updateTimeoutRef.current = null;
31
+ if (currentMessageRef.current) {
32
+ setMessages([{ ...currentMessageRef.current }]);
33
+ }
34
+ }, 250); // Batch updates every 250ms
35
+ }, []);
36
+ // Process incoming SSE message from sub-agent
37
+ // Defined BEFORE connectToSubagent so it's available in the closure
38
+ const processSSEMessage = useCallback((data) => {
39
+ try {
40
+ const message = JSON.parse(data);
41
+ logger.debug("Processing SSE message", {
42
+ method: message.method,
43
+ hasParams: !!message.params,
44
+ });
45
+ // Check if this is a session/update notification
46
+ if (message.method === "session/update" && message.params?.update) {
47
+ const update = message.params.update;
48
+ logger.debug("Got session update", {
49
+ sessionUpdate: update.sessionUpdate,
50
+ });
51
+ if (update.sessionUpdate === "agent_message_chunk") {
52
+ // Handle text chunk
53
+ const content = update.content;
54
+ if (content?.type === "text" && typeof content.text === "string") {
55
+ if (currentMessageRef.current) {
56
+ currentMessageRef.current.content += content.text;
57
+ // Add to contentBlocks - append to last text block or create new one
58
+ const blocks = currentMessageRef.current.contentBlocks ?? [];
59
+ const lastBlock = blocks[blocks.length - 1];
60
+ if (lastBlock && lastBlock.type === "text") {
61
+ lastBlock.text += content.text;
62
+ }
63
+ else {
64
+ blocks.push({ type: "text", text: content.text });
65
+ }
66
+ currentMessageRef.current.contentBlocks = blocks;
67
+ scheduleUpdate();
68
+ }
69
+ }
70
+ }
71
+ else if (update.sessionUpdate === "tool_call") {
72
+ // Handle new tool call
73
+ const toolCall = {
74
+ id: update.toolCallId ?? `tc-${Date.now()}`,
75
+ title: update.title ?? "Tool call",
76
+ prettyName: update._meta?.prettyName,
77
+ icon: update._meta?.icon,
78
+ status: update.status ?? "pending",
79
+ content: [],
80
+ };
81
+ if (currentMessageRef.current) {
82
+ currentMessageRef.current.toolCalls = [
83
+ ...(currentMessageRef.current.toolCalls ?? []),
84
+ toolCall,
85
+ ];
86
+ // Add to contentBlocks for interleaved display
87
+ const blocks = currentMessageRef.current.contentBlocks ?? [];
88
+ blocks.push({ type: "tool_call", toolCall });
89
+ currentMessageRef.current.contentBlocks = blocks;
90
+ scheduleUpdate();
91
+ }
92
+ }
93
+ else if (update.sessionUpdate === "tool_call_update") {
94
+ // Handle tool call update (status change, completion)
95
+ if (currentMessageRef.current?.toolCalls) {
96
+ const toolCallId = update.toolCallId;
97
+ const updateToolCall = (tc) => tc.id === toolCallId
98
+ ? {
99
+ ...tc,
100
+ status: update.status ?? tc.status,
101
+ content: update.content ?? tc.content,
102
+ }
103
+ : tc;
104
+ currentMessageRef.current.toolCalls =
105
+ currentMessageRef.current.toolCalls.map(updateToolCall);
106
+ // Also update in contentBlocks
107
+ if (currentMessageRef.current.contentBlocks) {
108
+ currentMessageRef.current.contentBlocks =
109
+ currentMessageRef.current.contentBlocks.map((block) => block.type === "tool_call"
110
+ ? { ...block, toolCall: updateToolCall(block.toolCall) }
111
+ : block);
112
+ }
113
+ scheduleUpdate();
114
+ }
115
+ }
116
+ }
117
+ }
118
+ catch (err) {
119
+ logger.error("Failed to parse sub-agent SSE message", {
120
+ error: err instanceof Error ? err.message : String(err),
121
+ });
122
+ }
123
+ }, [scheduleUpdate]);
124
+ const connectToSubagent = useCallback(async (port, sessionId, host, protocol) => {
125
+ const baseUrl = `${protocol}//${host}:${port}`;
126
+ logger.info("Connecting to sub-agent SSE", { baseUrl, sessionId });
127
+ setIsStreaming(true);
128
+ setError(null);
129
+ // Create abort controller for cleanup
130
+ const abortController = new AbortController();
131
+ abortControllerRef.current = abortController;
132
+ try {
133
+ logger.info("Fetching SSE endpoint", {
134
+ url: `${baseUrl}/events`,
135
+ sessionId,
136
+ });
137
+ const response = await fetch(`${baseUrl}/events`, {
138
+ method: "GET",
139
+ headers: {
140
+ "X-Session-ID": sessionId,
141
+ },
142
+ signal: abortController.signal,
143
+ });
144
+ logger.info("SSE response received", {
145
+ status: response.status,
146
+ ok: response.ok,
147
+ });
148
+ if (!response.ok) {
149
+ throw new Error(`SSE connection failed: HTTP ${response.status}`);
150
+ }
151
+ if (!response.body) {
152
+ throw new Error("Response body is null");
153
+ }
154
+ logger.info("Sub-agent SSE connection opened, starting to read stream");
155
+ // Read the SSE stream
156
+ const reader = response.body.getReader();
157
+ const decoder = new TextDecoder();
158
+ let buffer = "";
159
+ // Initialize current message
160
+ currentMessageRef.current = {
161
+ id: `subagent-${Date.now()}`,
162
+ content: "",
163
+ toolCalls: [],
164
+ contentBlocks: [],
165
+ isStreaming: true,
166
+ };
167
+ setMessages([currentMessageRef.current]);
168
+ while (true) {
169
+ const { done, value } = await reader.read();
170
+ if (done) {
171
+ logger.debug("Sub-agent SSE stream closed");
172
+ break;
173
+ }
174
+ // Decode the chunk and add to buffer
175
+ buffer += decoder.decode(value, { stream: true });
176
+ // Process complete SSE messages
177
+ const lines = buffer.split("\n");
178
+ buffer = lines.pop() || ""; // Keep incomplete line in buffer
179
+ let currentEvent = { event: "message", data: "" };
180
+ for (const line of lines) {
181
+ if (line.startsWith("event:")) {
182
+ currentEvent.event = line.substring(6).trim();
183
+ }
184
+ else if (line.startsWith("data:")) {
185
+ currentEvent.data = line.substring(5).trim();
186
+ }
187
+ else if (line === "") {
188
+ // Empty line signals end of event
189
+ if (currentEvent.event === "message" && currentEvent.data) {
190
+ processSSEMessage(currentEvent.data);
191
+ }
192
+ // Reset for next event
193
+ currentEvent = { event: "message", data: "" };
194
+ }
195
+ }
196
+ }
197
+ }
198
+ catch (err) {
199
+ if (err instanceof Error && err.name === "AbortError") {
200
+ logger.debug("Sub-agent SSE stream aborted");
201
+ }
202
+ else {
203
+ const errorMessage = err instanceof Error
204
+ ? err.message
205
+ : "Failed to connect to sub-agent";
206
+ logger.error("Sub-agent SSE error", { error: errorMessage });
207
+ setError(errorMessage);
208
+ }
209
+ }
210
+ finally {
211
+ // Mark streaming as complete
212
+ if (currentMessageRef.current) {
213
+ currentMessageRef.current.isStreaming = false;
214
+ setMessages((prev) => prev.map((m) => m.id === currentMessageRef.current?.id
215
+ ? { ...m, isStreaming: false }
216
+ : m));
217
+ }
218
+ setHasCompleted(true);
219
+ setIsStreaming(false);
220
+ abortControllerRef.current = null;
221
+ logger.debug("Sub-agent stream completed");
222
+ }
223
+ }, [processSSEMessage]);
224
+ // Extract values from options (memoized to avoid dependency issues)
225
+ const port = options?.port;
226
+ const sessionId = options?.sessionId;
227
+ const host = options?.host ??
228
+ (typeof window !== "undefined" ? window.location.hostname : "localhost");
229
+ const protocol = typeof window !== "undefined" ? window.location.protocol : "http:";
230
+ // Connect when options change
231
+ useEffect(() => {
232
+ if (!port || !sessionId) {
233
+ return;
234
+ }
235
+ // Reset state for new connection
236
+ setMessages([]);
237
+ setError(null);
238
+ setHasCompleted(false);
239
+ setIsStreaming(true);
240
+ connectToSubagent(port, sessionId, host, protocol);
241
+ // Cleanup on unmount or options change
242
+ return () => {
243
+ if (abortControllerRef.current) {
244
+ abortControllerRef.current.abort();
245
+ abortControllerRef.current = null;
246
+ }
247
+ if (updateTimeoutRef.current) {
248
+ clearTimeout(updateTimeoutRef.current);
249
+ updateTimeoutRef.current = null;
250
+ }
251
+ };
252
+ }, [port, sessionId, host, protocol, connectToSubagent]);
253
+ // Derive streaming status: streaming if we haven't completed yet
254
+ const effectiveIsStreaming = !hasCompleted;
255
+ return { messages, isStreaming: effectiveIsStreaming, error };
256
+ }
@@ -155,11 +155,36 @@ export declare function useToolCalls(client: AcpClient | null): {
155
155
  };
156
156
  })[] | undefined;
157
157
  isStreaming?: boolean | undefined;
158
+ context_size?: {
159
+ systemPromptTokens: number;
160
+ toolOverheadTokens?: number | undefined;
161
+ mcpOverheadTokens?: number | undefined;
162
+ userMessagesTokens: number;
163
+ assistantMessagesTokens: number;
164
+ toolInputTokens: number;
165
+ toolResultsTokens: number;
166
+ totalEstimated: number;
167
+ llmReportedInputTokens?: number | undefined;
168
+ modelContextWindow?: number | undefined;
169
+ } | undefined;
158
170
  _meta?: {
171
+ [x: string]: unknown;
159
172
  semanticName?: string | undefined;
160
173
  agentDefinitionName?: string | undefined;
161
174
  currentActivity?: string | undefined;
162
175
  statusGenerating?: boolean | undefined;
176
+ context_size?: {
177
+ systemPromptTokens: number;
178
+ toolOverheadTokens?: number | undefined;
179
+ mcpOverheadTokens?: number | undefined;
180
+ userMessagesTokens: number;
181
+ assistantMessagesTokens: number;
182
+ toolInputTokens: number;
183
+ toolResultsTokens: number;
184
+ totalEstimated: number;
185
+ llmReportedInputTokens?: number | undefined;
186
+ modelContextWindow?: number | undefined;
187
+ } | undefined;
163
188
  } | undefined;
164
189
  }[] | undefined;
165
190
  subagentStreaming?: boolean | undefined;
@@ -312,11 +337,36 @@ export declare function useToolCalls(client: AcpClient | null): {
312
337
  };
313
338
  })[] | undefined;
314
339
  isStreaming?: boolean | undefined;
340
+ context_size?: {
341
+ systemPromptTokens: number;
342
+ toolOverheadTokens?: number | undefined;
343
+ mcpOverheadTokens?: number | undefined;
344
+ userMessagesTokens: number;
345
+ assistantMessagesTokens: number;
346
+ toolInputTokens: number;
347
+ toolResultsTokens: number;
348
+ totalEstimated: number;
349
+ llmReportedInputTokens?: number | undefined;
350
+ modelContextWindow?: number | undefined;
351
+ } | undefined;
315
352
  _meta?: {
353
+ [x: string]: unknown;
316
354
  semanticName?: string | undefined;
317
355
  agentDefinitionName?: string | undefined;
318
356
  currentActivity?: string | undefined;
319
357
  statusGenerating?: boolean | undefined;
358
+ context_size?: {
359
+ systemPromptTokens: number;
360
+ toolOverheadTokens?: number | undefined;
361
+ mcpOverheadTokens?: number | undefined;
362
+ userMessagesTokens: number;
363
+ assistantMessagesTokens: number;
364
+ toolInputTokens: number;
365
+ toolResultsTokens: number;
366
+ totalEstimated: number;
367
+ llmReportedInputTokens?: number | undefined;
368
+ modelContextWindow?: number | undefined;
369
+ } | undefined;
320
370
  } | undefined;
321
371
  }[] | undefined;
322
372
  subagentStreaming?: boolean | undefined;
@@ -237,12 +237,36 @@ export declare const DisplayMessage: z.ZodObject<{
237
237
  }, z.core.$strip>;
238
238
  }, z.core.$strip>], "type">>>;
239
239
  isStreaming: z.ZodOptional<z.ZodBoolean>;
240
+ context_size: z.ZodOptional<z.ZodObject<{
241
+ systemPromptTokens: z.ZodNumber;
242
+ toolOverheadTokens: z.ZodOptional<z.ZodNumber>;
243
+ mcpOverheadTokens: z.ZodOptional<z.ZodNumber>;
244
+ userMessagesTokens: z.ZodNumber;
245
+ assistantMessagesTokens: z.ZodNumber;
246
+ toolInputTokens: z.ZodNumber;
247
+ toolResultsTokens: z.ZodNumber;
248
+ totalEstimated: z.ZodNumber;
249
+ llmReportedInputTokens: z.ZodOptional<z.ZodNumber>;
250
+ modelContextWindow: z.ZodOptional<z.ZodNumber>;
251
+ }, z.core.$strip>>;
240
252
  _meta: z.ZodOptional<z.ZodObject<{
241
253
  semanticName: z.ZodOptional<z.ZodString>;
242
254
  agentDefinitionName: z.ZodOptional<z.ZodString>;
243
255
  currentActivity: z.ZodOptional<z.ZodString>;
244
256
  statusGenerating: z.ZodOptional<z.ZodBoolean>;
245
- }, z.core.$strip>>;
257
+ context_size: z.ZodOptional<z.ZodObject<{
258
+ systemPromptTokens: z.ZodNumber;
259
+ toolOverheadTokens: z.ZodOptional<z.ZodNumber>;
260
+ mcpOverheadTokens: z.ZodOptional<z.ZodNumber>;
261
+ userMessagesTokens: z.ZodNumber;
262
+ assistantMessagesTokens: z.ZodNumber;
263
+ toolInputTokens: z.ZodNumber;
264
+ toolResultsTokens: z.ZodNumber;
265
+ totalEstimated: z.ZodNumber;
266
+ llmReportedInputTokens: z.ZodOptional<z.ZodNumber>;
267
+ modelContextWindow: z.ZodOptional<z.ZodNumber>;
268
+ }, z.core.$strip>>;
269
+ }, z.core.$loose>>;
246
270
  }, z.core.$strip>>>;
247
271
  subagentStreaming: z.ZodOptional<z.ZodBoolean>;
248
272
  subagentCompleted: z.ZodOptional<z.ZodBoolean>;
@@ -508,12 +532,36 @@ export declare const ChatSessionState: z.ZodObject<{
508
532
  }, z.core.$strip>;
509
533
  }, z.core.$strip>], "type">>>;
510
534
  isStreaming: z.ZodOptional<z.ZodBoolean>;
535
+ context_size: z.ZodOptional<z.ZodObject<{
536
+ systemPromptTokens: z.ZodNumber;
537
+ toolOverheadTokens: z.ZodOptional<z.ZodNumber>;
538
+ mcpOverheadTokens: z.ZodOptional<z.ZodNumber>;
539
+ userMessagesTokens: z.ZodNumber;
540
+ assistantMessagesTokens: z.ZodNumber;
541
+ toolInputTokens: z.ZodNumber;
542
+ toolResultsTokens: z.ZodNumber;
543
+ totalEstimated: z.ZodNumber;
544
+ llmReportedInputTokens: z.ZodOptional<z.ZodNumber>;
545
+ modelContextWindow: z.ZodOptional<z.ZodNumber>;
546
+ }, z.core.$strip>>;
511
547
  _meta: z.ZodOptional<z.ZodObject<{
512
548
  semanticName: z.ZodOptional<z.ZodString>;
513
549
  agentDefinitionName: z.ZodOptional<z.ZodString>;
514
550
  currentActivity: z.ZodOptional<z.ZodString>;
515
551
  statusGenerating: z.ZodOptional<z.ZodBoolean>;
516
- }, z.core.$strip>>;
552
+ context_size: z.ZodOptional<z.ZodObject<{
553
+ systemPromptTokens: z.ZodNumber;
554
+ toolOverheadTokens: z.ZodOptional<z.ZodNumber>;
555
+ mcpOverheadTokens: z.ZodOptional<z.ZodNumber>;
556
+ userMessagesTokens: z.ZodNumber;
557
+ assistantMessagesTokens: z.ZodNumber;
558
+ toolInputTokens: z.ZodNumber;
559
+ toolResultsTokens: z.ZodNumber;
560
+ totalEstimated: z.ZodNumber;
561
+ llmReportedInputTokens: z.ZodOptional<z.ZodNumber>;
562
+ modelContextWindow: z.ZodOptional<z.ZodNumber>;
563
+ }, z.core.$strip>>;
564
+ }, z.core.$loose>>;
517
565
  }, z.core.$strip>>>;
518
566
  subagentStreaming: z.ZodOptional<z.ZodBoolean>;
519
567
  subagentCompleted: z.ZodOptional<z.ZodBoolean>;
@@ -1,4 +1,20 @@
1
1
  import { z } from "zod";
2
+ /**
3
+ * Context size breakdown (as provided by agent metadata).
4
+ * Stored on message metadata as `_meta.context_size` (or sometimes `context_size`).
5
+ */
6
+ export declare const ContextSizeSchema: z.ZodObject<{
7
+ systemPromptTokens: z.ZodNumber;
8
+ toolOverheadTokens: z.ZodOptional<z.ZodNumber>;
9
+ mcpOverheadTokens: z.ZodOptional<z.ZodNumber>;
10
+ userMessagesTokens: z.ZodNumber;
11
+ assistantMessagesTokens: z.ZodNumber;
12
+ toolInputTokens: z.ZodNumber;
13
+ toolResultsTokens: z.ZodNumber;
14
+ totalEstimated: z.ZodNumber;
15
+ llmReportedInputTokens: z.ZodOptional<z.ZodNumber>;
16
+ modelContextWindow: z.ZodOptional<z.ZodNumber>;
17
+ }, z.core.$strip>;
2
18
  /**
3
19
  * Tool call status lifecycle
4
20
  */
@@ -262,12 +278,36 @@ export declare const SubagentMessageSchema: z.ZodObject<{
262
278
  }, z.core.$strip>;
263
279
  }, z.core.$strip>], "type">>>;
264
280
  isStreaming: z.ZodOptional<z.ZodBoolean>;
281
+ context_size: z.ZodOptional<z.ZodObject<{
282
+ systemPromptTokens: z.ZodNumber;
283
+ toolOverheadTokens: z.ZodOptional<z.ZodNumber>;
284
+ mcpOverheadTokens: z.ZodOptional<z.ZodNumber>;
285
+ userMessagesTokens: z.ZodNumber;
286
+ assistantMessagesTokens: z.ZodNumber;
287
+ toolInputTokens: z.ZodNumber;
288
+ toolResultsTokens: z.ZodNumber;
289
+ totalEstimated: z.ZodNumber;
290
+ llmReportedInputTokens: z.ZodOptional<z.ZodNumber>;
291
+ modelContextWindow: z.ZodOptional<z.ZodNumber>;
292
+ }, z.core.$strip>>;
265
293
  _meta: z.ZodOptional<z.ZodObject<{
266
294
  semanticName: z.ZodOptional<z.ZodString>;
267
295
  agentDefinitionName: z.ZodOptional<z.ZodString>;
268
296
  currentActivity: z.ZodOptional<z.ZodString>;
269
297
  statusGenerating: z.ZodOptional<z.ZodBoolean>;
270
- }, z.core.$strip>>;
298
+ context_size: z.ZodOptional<z.ZodObject<{
299
+ systemPromptTokens: z.ZodNumber;
300
+ toolOverheadTokens: z.ZodOptional<z.ZodNumber>;
301
+ mcpOverheadTokens: z.ZodOptional<z.ZodNumber>;
302
+ userMessagesTokens: z.ZodNumber;
303
+ assistantMessagesTokens: z.ZodNumber;
304
+ toolInputTokens: z.ZodNumber;
305
+ toolResultsTokens: z.ZodNumber;
306
+ totalEstimated: z.ZodNumber;
307
+ llmReportedInputTokens: z.ZodOptional<z.ZodNumber>;
308
+ modelContextWindow: z.ZodOptional<z.ZodNumber>;
309
+ }, z.core.$strip>>;
310
+ }, z.core.$loose>>;
271
311
  }, z.core.$strip>;
272
312
  export type SubagentMessage = z.infer<typeof SubagentMessageSchema>;
273
313
  /**
@@ -449,12 +489,36 @@ export declare const ToolCallSchema: z.ZodObject<{
449
489
  }, z.core.$strip>;
450
490
  }, z.core.$strip>], "type">>>;
451
491
  isStreaming: z.ZodOptional<z.ZodBoolean>;
492
+ context_size: z.ZodOptional<z.ZodObject<{
493
+ systemPromptTokens: z.ZodNumber;
494
+ toolOverheadTokens: z.ZodOptional<z.ZodNumber>;
495
+ mcpOverheadTokens: z.ZodOptional<z.ZodNumber>;
496
+ userMessagesTokens: z.ZodNumber;
497
+ assistantMessagesTokens: z.ZodNumber;
498
+ toolInputTokens: z.ZodNumber;
499
+ toolResultsTokens: z.ZodNumber;
500
+ totalEstimated: z.ZodNumber;
501
+ llmReportedInputTokens: z.ZodOptional<z.ZodNumber>;
502
+ modelContextWindow: z.ZodOptional<z.ZodNumber>;
503
+ }, z.core.$strip>>;
452
504
  _meta: z.ZodOptional<z.ZodObject<{
453
505
  semanticName: z.ZodOptional<z.ZodString>;
454
506
  agentDefinitionName: z.ZodOptional<z.ZodString>;
455
507
  currentActivity: z.ZodOptional<z.ZodString>;
456
508
  statusGenerating: z.ZodOptional<z.ZodBoolean>;
457
- }, z.core.$strip>>;
509
+ context_size: z.ZodOptional<z.ZodObject<{
510
+ systemPromptTokens: z.ZodNumber;
511
+ toolOverheadTokens: z.ZodOptional<z.ZodNumber>;
512
+ mcpOverheadTokens: z.ZodOptional<z.ZodNumber>;
513
+ userMessagesTokens: z.ZodNumber;
514
+ assistantMessagesTokens: z.ZodNumber;
515
+ toolInputTokens: z.ZodNumber;
516
+ toolResultsTokens: z.ZodNumber;
517
+ totalEstimated: z.ZodNumber;
518
+ llmReportedInputTokens: z.ZodOptional<z.ZodNumber>;
519
+ modelContextWindow: z.ZodOptional<z.ZodNumber>;
520
+ }, z.core.$strip>>;
521
+ }, z.core.$loose>>;
458
522
  }, z.core.$strip>>>;
459
523
  subagentStreaming: z.ZodOptional<z.ZodBoolean>;
460
524
  subagentCompleted: z.ZodOptional<z.ZodBoolean>;
@@ -608,12 +672,36 @@ export declare const ToolCallUpdateSchema: z.ZodObject<{
608
672
  }, z.core.$strip>;
609
673
  }, z.core.$strip>], "type">>>;
610
674
  isStreaming: z.ZodOptional<z.ZodBoolean>;
675
+ context_size: z.ZodOptional<z.ZodObject<{
676
+ systemPromptTokens: z.ZodNumber;
677
+ toolOverheadTokens: z.ZodOptional<z.ZodNumber>;
678
+ mcpOverheadTokens: z.ZodOptional<z.ZodNumber>;
679
+ userMessagesTokens: z.ZodNumber;
680
+ assistantMessagesTokens: z.ZodNumber;
681
+ toolInputTokens: z.ZodNumber;
682
+ toolResultsTokens: z.ZodNumber;
683
+ totalEstimated: z.ZodNumber;
684
+ llmReportedInputTokens: z.ZodOptional<z.ZodNumber>;
685
+ modelContextWindow: z.ZodOptional<z.ZodNumber>;
686
+ }, z.core.$strip>>;
611
687
  _meta: z.ZodOptional<z.ZodObject<{
612
688
  semanticName: z.ZodOptional<z.ZodString>;
613
689
  agentDefinitionName: z.ZodOptional<z.ZodString>;
614
690
  currentActivity: z.ZodOptional<z.ZodString>;
615
691
  statusGenerating: z.ZodOptional<z.ZodBoolean>;
616
- }, z.core.$strip>>;
692
+ context_size: z.ZodOptional<z.ZodObject<{
693
+ systemPromptTokens: z.ZodNumber;
694
+ toolOverheadTokens: z.ZodOptional<z.ZodNumber>;
695
+ mcpOverheadTokens: z.ZodOptional<z.ZodNumber>;
696
+ userMessagesTokens: z.ZodNumber;
697
+ assistantMessagesTokens: z.ZodNumber;
698
+ toolInputTokens: z.ZodNumber;
699
+ toolResultsTokens: z.ZodNumber;
700
+ totalEstimated: z.ZodNumber;
701
+ llmReportedInputTokens: z.ZodOptional<z.ZodNumber>;
702
+ modelContextWindow: z.ZodOptional<z.ZodNumber>;
703
+ }, z.core.$strip>>;
704
+ }, z.core.$loose>>;
617
705
  }, z.core.$strip>>>;
618
706
  subagentCompleted: z.ZodOptional<z.ZodBoolean>;
619
707
  _meta: z.ZodOptional<z.ZodObject<{
@@ -1,4 +1,20 @@
1
1
  import { z } from "zod";
2
+ /**
3
+ * Context size breakdown (as provided by agent metadata).
4
+ * Stored on message metadata as `_meta.context_size` (or sometimes `context_size`).
5
+ */
6
+ export const ContextSizeSchema = z.object({
7
+ systemPromptTokens: z.number(),
8
+ toolOverheadTokens: z.number().optional(), // Optional for backward compatibility
9
+ mcpOverheadTokens: z.number().optional(), // Optional for backward compatibility
10
+ userMessagesTokens: z.number(),
11
+ assistantMessagesTokens: z.number(),
12
+ toolInputTokens: z.number(),
13
+ toolResultsTokens: z.number(),
14
+ totalEstimated: z.number(),
15
+ llmReportedInputTokens: z.number().optional(),
16
+ modelContextWindow: z.number().optional(), // Model's max context window
17
+ });
2
18
  /**
3
19
  * Tool call status lifecycle
4
20
  */
@@ -117,13 +133,19 @@ export const SubagentMessageSchema = z.object({
117
133
  /** Interleaved content blocks in arrival order */
118
134
  contentBlocks: z.array(SubagentContentBlockSchema).optional(),
119
135
  isStreaming: z.boolean().optional(),
136
+ /** Context size metadata (may be present at top-level in some payloads) */
137
+ context_size: ContextSizeSchema.optional(),
120
138
  _meta: z
121
139
  .object({
122
140
  semanticName: z.string().optional(),
123
141
  agentDefinitionName: z.string().optional(),
124
142
  currentActivity: z.string().optional(),
125
143
  statusGenerating: z.boolean().optional(),
144
+ /** Context size metadata (preferred location) */
145
+ context_size: ContextSizeSchema.optional(),
126
146
  })
147
+ // Preserve forward-compatible metadata fields
148
+ .passthrough()
127
149
  .optional(),
128
150
  });
129
151
  /**