@townco/agent 0.1.52 → 0.1.54

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 (40) hide show
  1. package/dist/acp-server/adapter.d.ts +18 -0
  2. package/dist/acp-server/adapter.js +258 -19
  3. package/dist/acp-server/http.js +39 -1
  4. package/dist/acp-server/session-storage.d.ts +18 -1
  5. package/dist/acp-server/session-storage.js +25 -0
  6. package/dist/definition/index.d.ts +2 -2
  7. package/dist/definition/index.js +1 -0
  8. package/dist/runner/agent-runner.d.ts +11 -2
  9. package/dist/runner/langchain/index.d.ts +0 -1
  10. package/dist/runner/langchain/index.js +265 -64
  11. package/dist/runner/langchain/tools/generate_image.d.ts +28 -0
  12. package/dist/runner/langchain/tools/generate_image.js +135 -0
  13. package/dist/runner/langchain/tools/subagent.d.ts +6 -1
  14. package/dist/runner/langchain/tools/subagent.js +12 -2
  15. package/dist/runner/tools.d.ts +19 -2
  16. package/dist/runner/tools.js +9 -0
  17. package/dist/telemetry/index.js +7 -1
  18. package/dist/templates/index.d.ts +3 -0
  19. package/dist/templates/index.js +26 -4
  20. package/dist/tsconfig.tsbuildinfo +1 -1
  21. package/dist/utils/context-size-calculator.d.ts +9 -4
  22. package/dist/utils/context-size-calculator.js +23 -6
  23. package/dist/utils/tool-overhead-calculator.d.ts +30 -0
  24. package/dist/utils/tool-overhead-calculator.js +54 -0
  25. package/package.json +7 -6
  26. package/templates/index.ts +36 -5
  27. package/dist/check-jaeger.d.ts +0 -5
  28. package/dist/check-jaeger.js +0 -82
  29. package/dist/run-subagents.d.ts +0 -9
  30. package/dist/run-subagents.js +0 -110
  31. package/dist/runner/langchain/custom-stream-types.d.ts +0 -36
  32. package/dist/runner/langchain/custom-stream-types.js +0 -23
  33. package/dist/runner/langchain/tools/bash.d.ts +0 -14
  34. package/dist/runner/langchain/tools/bash.js +0 -135
  35. package/dist/scaffold/link-local.d.ts +0 -1
  36. package/dist/scaffold/link-local.js +0 -54
  37. package/dist/test-telemetry.d.ts +0 -5
  38. package/dist/test-telemetry.js +0 -88
  39. package/dist/utils/logger.d.ts +0 -39
  40. package/dist/utils/logger.js +0 -175
@@ -24,7 +24,23 @@ export declare class AgentAcpAdapter implements acp.Agent {
24
24
  private agentVersion;
25
25
  private agentDescription;
26
26
  private agentSuggestedPrompts;
27
+ private currentToolOverheadTokens;
28
+ private currentMcpOverheadTokens;
27
29
  constructor(agent: AgentRunner, connection: acp.AgentSideConnection, agentDir?: string, agentName?: string);
30
+ /**
31
+ * Extract tool metadata from the agent definition for exposing to clients.
32
+ * This provides basic info about available tools without loading them fully.
33
+ */
34
+ private getToolsMetadata;
35
+ /**
36
+ * Extract MCP metadata from the agent definition for exposing to clients.
37
+ */
38
+ private getMcpsMetadata;
39
+ /**
40
+ * Extract subagent metadata from the Task tool if present.
41
+ * This provides info about subagents configured via makeSubagentsTool.
42
+ */
43
+ private getSubagentsMetadata;
28
44
  /**
29
45
  * Helper to save session to disk
30
46
  * Call this after any modification to session.messages or session.context
@@ -36,10 +52,12 @@ export declare class AgentAcpAdapter implements acp.Agent {
36
52
  authenticate(_params: acp.AuthenticateRequest): Promise<acp.AuthenticateResponse | undefined>;
37
53
  setSessionMode(_params: acp.SetSessionModeRequest): Promise<acp.SetSessionModeResponse>;
38
54
  prompt(params: acp.PromptRequest): Promise<acp.PromptResponse>;
55
+ private _promptImpl;
39
56
  /**
40
57
  * Execute hooks if configured for this agent
41
58
  * Returns new context entries that should be appended to session.context
42
59
  */
43
60
  private executeHooksIfConfigured;
61
+ private _executeHooksImpl;
44
62
  cancel(params: acp.CancelNotification): Promise<void>;
45
63
  }
@@ -1,6 +1,8 @@
1
1
  import * as acp from "@agentclientprotocol/sdk";
2
+ import { context, trace } from "@opentelemetry/api";
2
3
  import { createLogger } from "@townco/core";
3
4
  import { HookExecutor, loadHookCallback } from "../runner/hooks";
5
+ import { telemetry } from "../telemetry/index.js";
4
6
  import { calculateContextSize, } from "../utils/context-size-calculator.js";
5
7
  import { countToolResultTokens } from "../utils/token-counter.js";
6
8
  import { SessionStorage, } from "./session-storage.js";
@@ -103,6 +105,8 @@ export class AgentAcpAdapter {
103
105
  agentVersion;
104
106
  agentDescription;
105
107
  agentSuggestedPrompts;
108
+ currentToolOverheadTokens = 0; // Track tool overhead for current turn
109
+ currentMcpOverheadTokens = 0; // Track MCP overhead for current turn
106
110
  constructor(agent, connection, agentDir, agentName) {
107
111
  this.connection = connection;
108
112
  this.sessions = new Map();
@@ -130,6 +134,82 @@ export class AgentAcpAdapter {
130
134
  sessionStoragePath: this.storage ? `${agentDir}/.sessions` : null,
131
135
  });
132
136
  }
137
+ /**
138
+ * Extract tool metadata from the agent definition for exposing to clients.
139
+ * This provides basic info about available tools without loading them fully.
140
+ */
141
+ getToolsMetadata() {
142
+ const tools = this.agent.definition.tools ?? [];
143
+ return tools.map((tool) => {
144
+ if (typeof tool === "string") {
145
+ // Built-in tool - return basic info
146
+ return { name: tool, description: `Built-in tool: ${tool}` };
147
+ }
148
+ else if (tool.type === "direct") {
149
+ return {
150
+ name: tool.name,
151
+ description: tool.description,
152
+ ...(tool.prettyName ? { prettyName: tool.prettyName } : {}),
153
+ ...(tool.icon ? { icon: tool.icon } : {}),
154
+ };
155
+ }
156
+ else if (tool.type === "filesystem") {
157
+ return {
158
+ name: "filesystem",
159
+ description: "File system access tools",
160
+ };
161
+ }
162
+ else if (tool.type === "custom") {
163
+ // Custom tools from module paths - extract name from path
164
+ const pathParts = tool.modulePath.split("/");
165
+ const fileName = pathParts[pathParts.length - 1] ?? "custom";
166
+ const name = fileName.replace(/\.(ts|js)$/, "");
167
+ return {
168
+ name,
169
+ description: `Custom tool from ${tool.modulePath}`,
170
+ };
171
+ }
172
+ return { name: "unknown", description: "Unknown tool type" };
173
+ });
174
+ }
175
+ /**
176
+ * Extract MCP metadata from the agent definition for exposing to clients.
177
+ */
178
+ getMcpsMetadata() {
179
+ const mcps = this.agent.definition.mcps ?? [];
180
+ return mcps.map((mcp) => {
181
+ if (typeof mcp === "string") {
182
+ return {
183
+ name: mcp,
184
+ transport: "http", // String configs are resolved to HTTP proxy
185
+ };
186
+ }
187
+ return {
188
+ name: mcp.name,
189
+ transport: mcp.transport,
190
+ };
191
+ });
192
+ }
193
+ /**
194
+ * Extract subagent metadata from the Task tool if present.
195
+ * This provides info about subagents configured via makeSubagentsTool.
196
+ */
197
+ getSubagentsMetadata() {
198
+ const tools = this.agent.definition.tools ?? [];
199
+ for (const tool of tools) {
200
+ // Look for direct tools with subagentConfigs (Task tools created by makeSubagentsTool)
201
+ if (typeof tool === "object" &&
202
+ tool.type === "direct" &&
203
+ "subagentConfigs" in tool &&
204
+ Array.isArray(tool.subagentConfigs)) {
205
+ return tool.subagentConfigs.map((config) => ({
206
+ name: config.agentName,
207
+ description: config.description,
208
+ }));
209
+ }
210
+ }
211
+ return [];
212
+ }
133
213
  /**
134
214
  * Helper to save session to disk
135
215
  * Call this after any modification to session.messages or session.context
@@ -166,19 +246,23 @@ export class AgentAcpAdapter {
166
246
  ...(this.agentDisplayName ? { title: this.agentDisplayName } : {}),
167
247
  };
168
248
  }
169
- // Pass description and suggestedPrompts via _meta extension point
170
- // since Implementation doesn't support these fields
171
- if (this.agentDescription || this.agentSuggestedPrompts) {
172
- response._meta = {
173
- ...response._meta,
174
- ...(this.agentDescription
175
- ? { agentDescription: this.agentDescription }
176
- : {}),
177
- ...(this.agentSuggestedPrompts
178
- ? { suggestedPrompts: this.agentSuggestedPrompts }
179
- : {}),
180
- };
181
- }
249
+ // Pass extended agent info via _meta extension point
250
+ // since ACP Implementation type doesn't support these fields directly
251
+ const toolsMetadata = this.getToolsMetadata();
252
+ const mcpsMetadata = this.getMcpsMetadata();
253
+ const subagentsMetadata = this.getSubagentsMetadata();
254
+ response._meta = {
255
+ ...response._meta,
256
+ ...(this.agentDescription
257
+ ? { agentDescription: this.agentDescription }
258
+ : {}),
259
+ ...(this.agentSuggestedPrompts
260
+ ? { suggestedPrompts: this.agentSuggestedPrompts }
261
+ : {}),
262
+ ...(toolsMetadata.length > 0 ? { tools: toolsMetadata } : {}),
263
+ ...(mcpsMetadata.length > 0 ? { mcps: mcpsMetadata } : {}),
264
+ ...(subagentsMetadata.length > 0 ? { subagents: subagentsMetadata } : {}),
265
+ };
182
266
  return response;
183
267
  }
184
268
  async newSession(params) {
@@ -234,6 +318,14 @@ export class AgentAcpAdapter {
234
318
  }
235
319
  else if (block.type === "tool_call") {
236
320
  // Replay tool call directly in final state (skip pending → completed transitions)
321
+ const replayMeta = {
322
+ isReplay: true,
323
+ ...(block.prettyName ? { prettyName: block.prettyName } : {}),
324
+ ...(block.icon ? { icon: block.icon } : {}),
325
+ ...(block.subline ? { subline: block.subline } : {}),
326
+ ...(block.batchId ? { batchId: block.batchId } : {}),
327
+ ...block._meta,
328
+ };
237
329
  this.connection.sessionUpdate({
238
330
  sessionId: params.sessionId,
239
331
  update: {
@@ -243,6 +335,7 @@ export class AgentAcpAdapter {
243
335
  kind: block.kind,
244
336
  status: block.status, // Use final status directly
245
337
  rawInput: block.rawInput,
338
+ _meta: replayMeta,
246
339
  },
247
340
  });
248
341
  // If there's output, emit tool_output event for the UI to display
@@ -324,6 +417,22 @@ export class AgentAcpAdapter {
324
417
  return {};
325
418
  }
326
419
  async prompt(params) {
420
+ const promptSpan = telemetry.startSpan("adapter.prompt", {
421
+ "session.id": params.sessionId,
422
+ });
423
+ const spanContext = promptSpan
424
+ ? trace.setSpan(context.active(), promptSpan)
425
+ : context.active();
426
+ return context.with(spanContext, async () => {
427
+ try {
428
+ return await this._promptImpl(params);
429
+ }
430
+ finally {
431
+ telemetry.endSpan(promptSpan);
432
+ }
433
+ });
434
+ }
435
+ async _promptImpl(params) {
327
436
  let session = this.sessions.get(params.sessionId);
328
437
  // If session not found (e.g., after server restart), create a new one
329
438
  if (!session) {
@@ -338,9 +447,34 @@ export class AgentAcpAdapter {
338
447
  }
339
448
  session.pendingPrompt?.abort();
340
449
  session.pendingPrompt = new AbortController();
450
+ // Reset tool overhead for new turn (will be set by harness)
451
+ this.currentToolOverheadTokens = 0;
452
+ this.currentMcpOverheadTokens = 0;
341
453
  // Generate a unique messageId for this assistant response
342
454
  const messageId = Math.random().toString(36).substring(2);
343
- // Extract and store the user message
455
+ // Convert prompt content blocks to session storage format
456
+ const userContentBlocks = params.prompt.map((block) => {
457
+ if (block.type === "text") {
458
+ return { type: "text", text: block.text };
459
+ }
460
+ else if (block.type === "image") {
461
+ // Preserve image block with all its data
462
+ const imgBlock = block;
463
+ const imageBlock = { type: "image" };
464
+ if (imgBlock.source)
465
+ imageBlock.source = imgBlock.source;
466
+ if (imgBlock.url)
467
+ imageBlock.url = imgBlock.url;
468
+ if (imgBlock.data)
469
+ imageBlock.data = imgBlock.data;
470
+ if (imgBlock.mimeType)
471
+ imageBlock.mimeType = imgBlock.mimeType;
472
+ return imageBlock;
473
+ }
474
+ // Fallback for unknown types - convert to text
475
+ return { type: "text", text: JSON.stringify(block) };
476
+ });
477
+ // Extract text for logging
344
478
  const userMessageText = params.prompt
345
479
  .filter((p) => p.type === "text")
346
480
  .map((p) => p.text)
@@ -354,7 +488,7 @@ export class AgentAcpAdapter {
354
488
  if (!this.noSession) {
355
489
  const userMessage = {
356
490
  role: "user",
357
- content: [{ type: "text", text: userMessageText }],
491
+ content: userContentBlocks,
358
492
  timestamp: new Date().toISOString(),
359
493
  };
360
494
  session.messages.push(userMessage);
@@ -397,7 +531,9 @@ export class AgentAcpAdapter {
397
531
  }
398
532
  }
399
533
  // Calculate context size - no LLM call yet, so only estimated values
400
- const context_size = calculateContextSize(contextMessages, this.agent.definition.systemPrompt ?? undefined, undefined);
534
+ const context_size = calculateContextSize(contextMessages, this.agent.definition.systemPrompt ?? undefined, undefined, // No LLM-reported tokens yet
535
+ this.currentToolOverheadTokens, // Include tool overhead
536
+ this.currentMcpOverheadTokens);
401
537
  const contextSnapshot = createContextSnapshot(session.messages.length, new Date().toISOString(), previousContext, context_size);
402
538
  session.context.push(contextSnapshot);
403
539
  await this.saveSessionToDisk(params.sessionId, session);
@@ -461,6 +597,20 @@ export class AgentAcpAdapter {
461
597
  let iterResult = await generator.next();
462
598
  while (!iterResult.done) {
463
599
  const msg = iterResult.value;
600
+ // Capture tool overhead info if provided by harness
601
+ if ("sessionUpdate" in msg &&
602
+ msg.sessionUpdate === "tool_overhead_info") {
603
+ const overheadInfo = msg;
604
+ this.currentToolOverheadTokens = overheadInfo.toolOverheadTokens;
605
+ this.currentMcpOverheadTokens = overheadInfo.mcpOverheadTokens;
606
+ logger.debug("Received tool overhead info from harness", {
607
+ toolOverheadTokens: this.currentToolOverheadTokens,
608
+ mcpOverheadTokens: this.currentMcpOverheadTokens,
609
+ });
610
+ // Don't send this update to client, it's internal metadata
611
+ iterResult = await generator.next();
612
+ continue;
613
+ }
464
614
  // Extract and accumulate token usage from message chunks
465
615
  if ("sessionUpdate" in msg &&
466
616
  msg.sessionUpdate === "agent_message_chunk" &&
@@ -516,10 +666,50 @@ export class AgentAcpAdapter {
516
666
  if ("sessionUpdate" in msg && msg.sessionUpdate === "tool_call") {
517
667
  flushPendingText();
518
668
  const toolCallMsg = msg;
669
+ // Extract prettyName, icon, and batchId from _meta
670
+ const prettyName = toolCallMsg._meta &&
671
+ typeof toolCallMsg._meta === "object" &&
672
+ "prettyName" in toolCallMsg._meta
673
+ ? String(toolCallMsg._meta.prettyName)
674
+ : undefined;
675
+ const icon = toolCallMsg._meta &&
676
+ typeof toolCallMsg._meta === "object" &&
677
+ "icon" in toolCallMsg._meta
678
+ ? String(toolCallMsg._meta.icon)
679
+ : undefined;
680
+ const batchId = toolCallMsg._meta &&
681
+ typeof toolCallMsg._meta === "object" &&
682
+ "batchId" in toolCallMsg._meta
683
+ ? String(toolCallMsg._meta.batchId)
684
+ : undefined;
685
+ // Extract subline from _meta, or compute it for todo_write from rawInput
686
+ let subline = toolCallMsg._meta &&
687
+ typeof toolCallMsg._meta === "object" &&
688
+ "subline" in toolCallMsg._meta
689
+ ? String(toolCallMsg._meta.subline)
690
+ : undefined;
691
+ // For todo_write, compute subline from in_progress item's activeForm
692
+ if (!subline &&
693
+ toolCallMsg.title === "todo_write" &&
694
+ toolCallMsg.rawInput &&
695
+ "todos" in toolCallMsg.rawInput &&
696
+ Array.isArray(toolCallMsg.rawInput.todos)) {
697
+ const inProgressItem = toolCallMsg.rawInput.todos.find((todo) => todo.status === "in_progress");
698
+ if (inProgressItem &&
699
+ typeof inProgressItem === "object" &&
700
+ "activeForm" in inProgressItem &&
701
+ typeof inProgressItem.activeForm === "string") {
702
+ subline = inProgressItem.activeForm;
703
+ }
704
+ }
519
705
  const toolCall = {
520
706
  type: "tool_call",
521
707
  id: toolCallMsg.toolCallId || `tool_${Date.now()}`,
708
+ ...(batchId ? { batchId } : {}),
522
709
  title: toolCallMsg.title || "Tool",
710
+ ...(prettyName ? { prettyName } : {}),
711
+ ...(icon ? { icon } : {}),
712
+ ...(subline ? { subline } : {}),
523
713
  kind: toolCallMsg.kind || "other",
524
714
  status: toolCallMsg.status || "pending",
525
715
  startedAt: Date.now(),
@@ -659,7 +849,9 @@ export class AgentAcpAdapter {
659
849
  }
660
850
  }
661
851
  // Calculate context size - tool result is now in the message, but hasn't been sent to LLM yet
662
- const context_size = calculateContextSize(contextMessages, this.agent.definition.systemPrompt ?? undefined, undefined);
852
+ const context_size = calculateContextSize(contextMessages, this.agent.definition.systemPrompt ?? undefined, undefined, // Tool result hasn't been sent to LLM yet, so no new LLM-reported tokens
853
+ this.currentToolOverheadTokens, // Include tool overhead
854
+ this.currentMcpOverheadTokens);
663
855
  // Create snapshot with a pointer to the partial message (not a full copy!)
664
856
  const midTurnSnapshot = {
665
857
  timestamp: new Date().toISOString(),
@@ -691,8 +883,35 @@ export class AgentAcpAdapter {
691
883
  }
692
884
  // The agent may emit extended types (like tool_output) that aren't in ACP SDK yet
693
885
  // The http transport will handle routing these appropriately
694
- // Add context input tokens to messages with token usage metadata
886
+ // Enhance todo_write tool_call with subline showing in-progress item
695
887
  let enhancedMsg = msg;
888
+ if ("sessionUpdate" in msg &&
889
+ msg.sessionUpdate === "tool_call" &&
890
+ "title" in msg &&
891
+ msg.title === "todo_write" &&
892
+ "rawInput" in msg &&
893
+ msg.rawInput &&
894
+ typeof msg.rawInput === "object" &&
895
+ "todos" in msg.rawInput &&
896
+ Array.isArray(msg.rawInput.todos)) {
897
+ // Find the in_progress item and extract its activeForm
898
+ const inProgressItem = msg.rawInput.todos.find((todo) => todo.status === "in_progress");
899
+ if (inProgressItem &&
900
+ typeof inProgressItem === "object" &&
901
+ "activeForm" in inProgressItem &&
902
+ typeof inProgressItem.activeForm === "string") {
903
+ enhancedMsg = {
904
+ ...msg,
905
+ _meta: {
906
+ ...(typeof msg._meta === "object" && msg._meta !== null
907
+ ? msg._meta
908
+ : {}),
909
+ subline: inProgressItem.activeForm,
910
+ },
911
+ };
912
+ }
913
+ }
914
+ // Add context input tokens to messages with token usage metadata
696
915
  if (!this.noSession &&
697
916
  "_meta" in msg &&
698
917
  msg._meta &&
@@ -779,7 +998,9 @@ export class AgentAcpAdapter {
779
998
  }
780
999
  }
781
1000
  // Calculate context size with LLM-reported tokens from this turn
782
- const context_size = calculateContextSize(contextMessages, this.agent.definition.systemPrompt ?? undefined, turnTokenUsage.inputTokens);
1001
+ const context_size = calculateContextSize(contextMessages, this.agent.definition.systemPrompt ?? undefined, turnTokenUsage.inputTokens, // Final LLM-reported tokens from this turn
1002
+ this.currentToolOverheadTokens, // Include tool overhead
1003
+ this.currentMcpOverheadTokens);
783
1004
  const contextSnapshot = createContextSnapshot(session.messages.length, new Date().toISOString(), previousContext, context_size);
784
1005
  session.context.push(contextSnapshot);
785
1006
  await this.saveSessionToDisk(params.sessionId, session);
@@ -799,6 +1020,24 @@ export class AgentAcpAdapter {
799
1020
  if (this.noSession || !hooks || hooks.length === 0) {
800
1021
  return [];
801
1022
  }
1023
+ const hookSpan = telemetry.startSpan("adapter.executeHooks", {
1024
+ "hooks.executionPoint": executionPoint,
1025
+ "hooks.count": hooks.length,
1026
+ "session.id": sessionId,
1027
+ });
1028
+ const spanContext = hookSpan
1029
+ ? trace.setSpan(context.active(), hookSpan)
1030
+ : context.active();
1031
+ return context.with(spanContext, async () => {
1032
+ try {
1033
+ return await this._executeHooksImpl(session, sessionId, executionPoint, hooks);
1034
+ }
1035
+ finally {
1036
+ telemetry.endSpan(hookSpan);
1037
+ }
1038
+ });
1039
+ }
1040
+ async _executeHooksImpl(session, sessionId, executionPoint, hooks) {
802
1041
  logger.info(`Executing hooks at ${executionPoint}`, {
803
1042
  hooksLength: hooks.length,
804
1043
  contextEntries: session.context.length,
@@ -1,5 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
- import { join } from "node:path";
2
+ import { join, resolve } from "node:path";
3
3
  import { gzipSync } from "node:zlib";
4
4
  import * as acp from "@agentclientprotocol/sdk";
5
5
  import { PGlite } from "@electric-sql/pglite";
@@ -264,6 +264,44 @@ export function makeHttpTransport(agent, agentDir, agentName) {
264
264
  allowMethods: ["GET", "POST", "OPTIONS"],
265
265
  }));
266
266
  app.get("/health", (c) => c.json({ ok: true }));
267
+ // Serve static files from agent directory (for generated images, etc.)
268
+ if (agentDir) {
269
+ app.get("/static/*", async (c) => {
270
+ const path = c.req.path.replace(/^\/static\//, "");
271
+ const filePath = join(agentDir, path);
272
+ // Security check: ensure the file is within the agent directory
273
+ const normalizedPath = resolve(filePath);
274
+ const normalizedAgentDir = resolve(agentDir);
275
+ if (!normalizedPath.startsWith(normalizedAgentDir)) {
276
+ logger.warn("Attempted to access file outside agent directory", {
277
+ path,
278
+ filePath,
279
+ agentDir,
280
+ });
281
+ return c.json({ error: "Invalid path" }, 403);
282
+ }
283
+ try {
284
+ const file = Bun.file(filePath);
285
+ const exists = await file.exists();
286
+ if (!exists) {
287
+ logger.warn("Static file not found", { path, filePath });
288
+ return c.json({ error: "File not found" }, 404);
289
+ }
290
+ const buffer = await file.arrayBuffer();
291
+ const contentType = file.type || "application/octet-stream";
292
+ return new Response(buffer, {
293
+ headers: {
294
+ "Content-Type": contentType,
295
+ "Cache-Control": "public, max-age=31536000",
296
+ },
297
+ });
298
+ }
299
+ catch (error) {
300
+ logger.error("Error serving static file", { error, path, filePath });
301
+ return c.json({ error: "Internal server error" }, 500);
302
+ }
303
+ });
304
+ }
267
305
  app.get("/events", (c) => {
268
306
  const sessionId = c.req.header("X-Session-ID");
269
307
  if (!sessionId) {
@@ -5,10 +5,25 @@ export interface TextBlock {
5
5
  type: "text";
6
6
  text: string;
7
7
  }
8
+ export interface ImageBlock {
9
+ type: "image";
10
+ source?: {
11
+ type: "base64";
12
+ media_type: "image/jpeg" | "image/png" | "image/gif" | "image/webp";
13
+ data: string;
14
+ } | undefined;
15
+ url?: string | undefined;
16
+ data?: string | undefined;
17
+ mimeType?: string | undefined;
18
+ }
8
19
  export interface ToolCallBlock {
9
20
  type: "tool_call";
10
21
  id: string;
22
+ batchId?: string | undefined;
11
23
  title: string;
24
+ prettyName?: string | undefined;
25
+ icon?: string | undefined;
26
+ subline?: string | undefined;
12
27
  kind: "read" | "edit" | "delete" | "move" | "search" | "execute" | "think" | "fetch" | "switch_mode" | "other";
13
28
  status: "pending" | "in_progress" | "completed" | "failed";
14
29
  rawInput?: Record<string, unknown> | undefined;
@@ -23,7 +38,7 @@ export interface ToolCallBlock {
23
38
  finalTokens?: number;
24
39
  };
25
40
  }
26
- export type ContentBlock = TextBlock | ToolCallBlock;
41
+ export type ContentBlock = TextBlock | ImageBlock | ToolCallBlock;
27
42
  /**
28
43
  * Session message format stored in files
29
44
  */
@@ -62,6 +77,8 @@ export interface ContextEntry {
62
77
  */
63
78
  context_size: {
64
79
  systemPromptTokens: number;
80
+ toolOverheadTokens?: number | undefined;
81
+ mcpOverheadTokens?: number | undefined;
65
82
  userMessagesTokens: number;
66
83
  assistantMessagesTokens: number;
67
84
  toolInputTokens: number;
@@ -8,10 +8,32 @@ const textBlockSchema = z.object({
8
8
  type: z.literal("text"),
9
9
  text: z.string(),
10
10
  });
11
+ const imageBlockSchema = z.object({
12
+ type: z.literal("image"),
13
+ source: z
14
+ .object({
15
+ type: z.literal("base64"),
16
+ media_type: z.enum([
17
+ "image/jpeg",
18
+ "image/png",
19
+ "image/gif",
20
+ "image/webp",
21
+ ]),
22
+ data: z.string(),
23
+ })
24
+ .optional(),
25
+ url: z.string().optional(),
26
+ data: z.string().optional(),
27
+ mimeType: z.string().optional(),
28
+ });
11
29
  const toolCallBlockSchema = z.object({
12
30
  type: z.literal("tool_call"),
13
31
  id: z.string(),
32
+ batchId: z.string().optional(),
14
33
  title: z.string(),
34
+ prettyName: z.string().optional(),
35
+ icon: z.string().optional(),
36
+ subline: z.string().optional(),
15
37
  kind: z.enum([
16
38
  "read",
17
39
  "edit",
@@ -33,6 +55,7 @@ const toolCallBlockSchema = z.object({
33
55
  });
34
56
  const contentBlockSchema = z.discriminatedUnion("type", [
35
57
  textBlockSchema,
58
+ imageBlockSchema,
36
59
  toolCallBlockSchema,
37
60
  ]);
38
61
  const sessionMessageSchema = z.object({
@@ -58,6 +81,8 @@ const contextEntrySchema = z.object({
58
81
  compactedUpTo: z.number().optional(),
59
82
  context_size: z.object({
60
83
  systemPromptTokens: z.number(),
84
+ toolOverheadTokens: z.number().optional(),
85
+ mcpOverheadTokens: z.number().optional(),
61
86
  userMessagesTokens: z.number(),
62
87
  assistantMessagesTokens: z.number(),
63
88
  toolInputTokens: z.number(),
@@ -11,7 +11,7 @@ export declare const McpConfigSchema: z.ZodUnion<readonly [z.ZodObject<{
11
11
  transport: z.ZodLiteral<"http">;
12
12
  url: z.ZodString;
13
13
  headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
14
- }, z.core.$strip>]>;
14
+ }, z.core.$strip>, z.ZodString]>;
15
15
  /** Hook configuration schema. */
16
16
  export declare const HookConfigSchema: z.ZodObject<{
17
17
  type: z.ZodEnum<{
@@ -59,7 +59,7 @@ export declare const AgentDefinitionSchema: z.ZodObject<{
59
59
  transport: z.ZodLiteral<"http">;
60
60
  url: z.ZodString;
61
61
  headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
62
- }, z.core.$strip>]>>>;
62
+ }, z.core.$strip>, z.ZodString]>>>;
63
63
  harnessImplementation: z.ZodOptional<z.ZodLiteral<"langchain">>;
64
64
  hooks: z.ZodOptional<z.ZodArray<z.ZodObject<{
65
65
  type: z.ZodEnum<{
@@ -23,6 +23,7 @@ const McpStreamableHttpConfigSchema = McpBaseConfigSchema.extend({
23
23
  export const McpConfigSchema = z.union([
24
24
  McpStdioConfigSchema,
25
25
  McpStreamableHttpConfigSchema,
26
+ z.string(),
26
27
  ]);
27
28
  /** Custom tool configuration schema. */
28
29
  const CustomToolSchema = z.object({
@@ -8,7 +8,7 @@ export declare const zAgentRunnerParams: z.ZodObject<{
8
8
  suggestedPrompts: z.ZodOptional<z.ZodArray<z.ZodString>>;
9
9
  systemPrompt: z.ZodNullable<z.ZodString>;
10
10
  model: z.ZodString;
11
- tools: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodLiteral<"todo_write">, z.ZodLiteral<"get_weather">, z.ZodLiteral<"web_search">, z.ZodLiteral<"filesystem">]>, z.ZodObject<{
11
+ tools: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodLiteral<"todo_write">, z.ZodLiteral<"get_weather">, z.ZodLiteral<"web_search">, z.ZodLiteral<"filesystem">, z.ZodLiteral<"generate_image">]>, z.ZodObject<{
12
12
  type: z.ZodLiteral<"custom">;
13
13
  modulePath: z.ZodString;
14
14
  }, z.core.$strip>, z.ZodObject<{
@@ -22,6 +22,11 @@ export declare const zAgentRunnerParams: z.ZodObject<{
22
22
  schema: z.ZodAny;
23
23
  prettyName: z.ZodOptional<z.ZodString>;
24
24
  icon: z.ZodOptional<z.ZodString>;
25
+ subagentConfigs: z.ZodOptional<z.ZodArray<z.ZodObject<{
26
+ agentName: z.ZodString;
27
+ description: z.ZodString;
28
+ displayName: z.ZodOptional<z.ZodString>;
29
+ }, z.core.$strip>>>;
25
30
  }, z.core.$strip>]>>>;
26
31
  mcps: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
27
32
  name: z.ZodString;
@@ -33,7 +38,7 @@ export declare const zAgentRunnerParams: z.ZodObject<{
33
38
  transport: z.ZodLiteral<"http">;
34
39
  url: z.ZodString;
35
40
  headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
36
- }, z.core.$strip>]>>>;
41
+ }, z.core.$strip>, z.ZodString]>>>;
37
42
  hooks: z.ZodOptional<z.ZodArray<z.ZodObject<{
38
43
  type: z.ZodEnum<{
39
44
  context_size: "context_size";
@@ -109,6 +114,10 @@ export type ExtendedSessionUpdate = (SessionNotification["update"] & {
109
114
  contextInputTokens?: number;
110
115
  [key: string]: unknown;
111
116
  };
117
+ } | {
118
+ sessionUpdate: "tool_overhead_info";
119
+ toolOverheadTokens: number;
120
+ mcpOverheadTokens: number;
112
121
  } | AgentMessageChunkWithTokens | HookNotificationUpdate;
113
122
  /** Describes an object that can run an agent definition */
114
123
  export interface AgentRunner {
@@ -10,7 +10,6 @@ type MakeLazy<T> = T extends LangchainTool ? () => T : never;
10
10
  export declare const TOOL_REGISTRY: Record<BuiltInToolType, LangchainTool | LazyLangchainTool | LazyLangchainTools>;
11
11
  export declare class LangchainAgent implements AgentRunner {
12
12
  definition: CreateAgentRunnerParams;
13
- private toolSpans;
14
13
  constructor(params: CreateAgentRunnerParams);
15
14
  invoke(req: InvokeRequest): AsyncGenerator<ExtendedSessionUpdate, PromptResponse, undefined>;
16
15
  }