@townco/agent 0.1.53 → 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 +16 -0
  2. package/dist/acp-server/adapter.js +230 -16
  3. package/dist/acp-server/cli.d.ts +1 -3
  4. package/dist/acp-server/http.js +39 -1
  5. package/dist/acp-server/session-storage.d.ts +16 -1
  6. package/dist/acp-server/session-storage.js +23 -0
  7. package/dist/bin.js +0 -0
  8. package/dist/definition/index.d.ts +2 -2
  9. package/dist/definition/index.js +1 -0
  10. package/dist/runner/agent-runner.d.ts +7 -2
  11. package/dist/runner/index.d.ts +1 -3
  12. package/dist/runner/langchain/index.js +178 -38
  13. package/dist/runner/langchain/tools/generate_image.d.ts +28 -0
  14. package/dist/runner/langchain/tools/generate_image.js +135 -0
  15. package/dist/runner/langchain/tools/subagent.d.ts +6 -1
  16. package/dist/runner/langchain/tools/subagent.js +12 -2
  17. package/dist/runner/tools.d.ts +19 -2
  18. package/dist/runner/tools.js +9 -0
  19. package/dist/telemetry/index.js +7 -1
  20. package/dist/templates/index.d.ts +3 -0
  21. package/dist/templates/index.js +26 -4
  22. package/dist/tsconfig.tsbuildinfo +1 -1
  23. package/package.json +7 -6
  24. package/templates/index.ts +36 -5
  25. package/dist/definition/mcp.d.ts +0 -0
  26. package/dist/definition/mcp.js +0 -0
  27. package/dist/definition/tools/todo.d.ts +0 -49
  28. package/dist/definition/tools/todo.js +0 -80
  29. package/dist/definition/tools/web_search.d.ts +0 -4
  30. package/dist/definition/tools/web_search.js +0 -26
  31. package/dist/dev-agent/index.d.ts +0 -2
  32. package/dist/dev-agent/index.js +0 -18
  33. package/dist/example.d.ts +0 -2
  34. package/dist/example.js +0 -19
  35. package/dist/scaffold/link-local.d.ts +0 -1
  36. package/dist/scaffold/link-local.js +0 -54
  37. package/dist/utils/__tests__/tool-overhead-calculator.test.d.ts +0 -1
  38. package/dist/utils/__tests__/tool-overhead-calculator.test.js +0 -153
  39. package/dist/utils/logger.d.ts +0 -39
  40. package/dist/utils/logger.js +0 -175
@@ -9,7 +9,8 @@ import { loadCustomToolModule, } from "../tool-loader.js";
9
9
  import { createModelFromString, detectProvider } from "./model-factory.js";
10
10
  import { makeOtelCallbacks } from "./otel-callbacks.js";
11
11
  import { makeFilesystemTools } from "./tools/filesystem";
12
- import { TASK_TOOL_NAME } from "./tools/subagent";
12
+ import { makeGenerateImageTool } from "./tools/generate_image";
13
+ import { SUBAGENT_TOOL_NAME } from "./tools/subagent";
13
14
  import { TODO_WRITE_TOOL_NAME, todoWrite } from "./tools/todo";
14
15
  import { makeWebSearchTools } from "./tools/web_search";
15
16
  const _logger = createLogger("agent-runner");
@@ -27,6 +28,7 @@ export const TOOL_REGISTRY = {
27
28
  get_weather: getWeather,
28
29
  web_search: () => makeWebSearchTools(),
29
30
  filesystem: () => makeFilesystemTools(process.cwd()),
31
+ generate_image: () => makeGenerateImageTool(),
30
32
  };
31
33
  // ============================================================================
32
34
  // Custom tool loading
@@ -74,6 +76,8 @@ export class LangchainAgent {
74
76
  totalTokens: 0,
75
77
  };
76
78
  const countedMessageIds = new Set();
79
+ // Track tool calls for which we've emitted preliminary notifications (from early tool_use blocks)
80
+ const preliminaryToolCallIds = new Set();
77
81
  // Start telemetry span for entire invocation
78
82
  const invocationSpan = telemetry.startSpan("agent.invoke", {
79
83
  "agent.model": this.definition.model,
@@ -286,7 +290,7 @@ export class LangchainAgent {
286
290
  // Filter tools if running in subagent mode
287
291
  const isSubagent = req.sessionMeta?.[SUBAGENT_MODE_KEY] === true;
288
292
  const filteredTools = isSubagent
289
- ? wrappedTools.filter((t) => t.name !== TODO_WRITE_TOOL_NAME && t.name !== TASK_TOOL_NAME)
293
+ ? wrappedTools.filter((t) => t.name !== TODO_WRITE_TOOL_NAME && t.name !== SUBAGENT_TOOL_NAME)
290
294
  : wrappedTools;
291
295
  // Wrap tools with tracing so each tool executes within its own span context.
292
296
  // This ensures subagent spans are children of the Task tool span.
@@ -314,35 +318,93 @@ export class LangchainAgent {
314
318
  const provider = detectProvider(this.definition.model);
315
319
  // Build messages from context history if available, otherwise use just the prompt
316
320
  let messages;
321
+ // Helper to convert content blocks to LangChain format
322
+ // LangChain expects image_url type with data URL, not Claude's native image+source format
323
+ const convertContentBlocks = (blocks) => {
324
+ // Check if we have any image blocks
325
+ const hasImages = blocks.some((block) => block.type === "image");
326
+ if (!hasImages) {
327
+ // Simple text-only message
328
+ return blocks
329
+ .filter((block) => block.type === "text")
330
+ .map((block) => block.text)
331
+ .join("");
332
+ }
333
+ // Multi-modal message with images - return as content block array
334
+ // LangChain uses image_url type with data URL format
335
+ return blocks
336
+ .map((block) => {
337
+ if (block.type === "text") {
338
+ return {
339
+ type: "text",
340
+ text: block.text,
341
+ };
342
+ }
343
+ else if (block.type === "image") {
344
+ // Extract base64 data and media type from various formats
345
+ let base64Data;
346
+ let mediaType = "image/png";
347
+ // Check if it has the source format (Claude API format)
348
+ if ("source" in block && block.source) {
349
+ base64Data = block.source.data;
350
+ mediaType = block.source.media_type || "image/png";
351
+ }
352
+ // ACP format: { type: "image", data: "...", mimeType: "..." }
353
+ else if ("data" in block && block.data) {
354
+ base64Data = block.data;
355
+ if (block.mimeType) {
356
+ const mt = block.mimeType.toLowerCase();
357
+ if (mt === "image/jpeg" || mt === "image/jpg") {
358
+ mediaType = "image/jpeg";
359
+ }
360
+ else if (mt === "image/png") {
361
+ mediaType = "image/png";
362
+ }
363
+ else if (mt === "image/gif") {
364
+ mediaType = "image/gif";
365
+ }
366
+ else if (mt === "image/webp") {
367
+ mediaType = "image/webp";
368
+ }
369
+ }
370
+ }
371
+ if (base64Data) {
372
+ // LangChain format: image_url with data URL
373
+ return {
374
+ type: "image_url",
375
+ image_url: {
376
+ url: `data:${mediaType};base64,${base64Data}`,
377
+ },
378
+ };
379
+ }
380
+ }
381
+ return null;
382
+ })
383
+ .filter(Boolean);
384
+ };
317
385
  if (req.contextMessages && req.contextMessages.length > 0) {
318
386
  // Use context messages (already resolved from context entries)
319
387
  // Convert to LangChain format
320
388
  messages = req.contextMessages.map((msg) => ({
321
389
  type: msg.role === "user" ? "human" : "ai",
322
- // Extract text from content blocks
323
- content: msg.content
324
- .filter((block) => block.type === "text")
325
- .map((block) => block.text)
326
- .join(""),
390
+ content: convertContentBlocks(msg.content),
327
391
  }));
328
392
  // Add the current prompt as the final human message
329
- const currentPromptText = req.prompt
330
- .filter((promptMsg) => promptMsg.type === "text")
331
- .map((promptMsg) => promptMsg.text)
332
- .join("\n");
393
+ const promptContent = convertContentBlocks(req.prompt);
333
394
  messages.push({
334
395
  type: "human",
335
- content: currentPromptText,
396
+ content: promptContent,
336
397
  });
337
398
  }
338
399
  else {
339
400
  // Fallback: No context history, use just the prompt
340
- messages = req.prompt
341
- .filter((promptMsg) => promptMsg.type === "text")
342
- .map((promptMsg) => ({
343
- type: "human",
344
- content: promptMsg.text,
345
- }));
401
+ const promptContent = convertContentBlocks(req.prompt);
402
+ messages = [
403
+ {
404
+ type: "human",
405
+ content: promptContent,
406
+ },
407
+ ];
346
408
  }
347
409
  // Create OTEL callbacks for instrumentation
348
410
  const otelCallbacks = makeOtelCallbacks({
@@ -391,7 +453,12 @@ export class LangchainAgent {
391
453
  turnTokenUsage.totalTokens += tokenUsage.totalTokens ?? 0;
392
454
  countedMessageIds.add(msg.id);
393
455
  }
394
- for (const toolCall of msg.tool_calls ?? []) {
456
+ // Generate a batch ID if there are multiple tool calls (parallel execution)
457
+ const toolCalls = msg.tool_calls ?? [];
458
+ const batchId = toolCalls.length > 1
459
+ ? `batch-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
460
+ : undefined;
461
+ for (const toolCall of toolCalls) {
395
462
  if (toolCall.id == null) {
396
463
  throw new Error(`Tool call is missing id: ${JSON.stringify(toolCall)}`);
397
464
  }
@@ -427,22 +494,61 @@ export class LangchainAgent {
427
494
  // continue;
428
495
  //}
429
496
  const matchingTool = finalTools.find((t) => t.name === toolCall.name);
430
- const prettyName = matchingTool?.prettyName;
497
+ let prettyName = matchingTool?.prettyName;
431
498
  const icon = matchingTool?.icon;
432
- yield {
433
- sessionUpdate: "tool_call",
434
- toolCallId: toolCall.id,
435
- title: toolCall.name,
436
- kind: "other",
437
- status: "pending",
438
- rawInput: toolCall.args,
439
- ...(tokenUsage ? { tokenUsage } : {}),
440
- _meta: {
441
- messageId: req.messageId,
442
- ...(prettyName ? { prettyName } : {}),
443
- ...(icon ? { icon } : {}),
444
- },
445
- };
499
+ // For the Task tool, use the displayName (or agentName as fallback) as the prettyName
500
+ if (toolCall.name === SUBAGENT_TOOL_NAME &&
501
+ toolCall.args &&
502
+ typeof toolCall.args === "object" &&
503
+ "agentName" in toolCall.args &&
504
+ typeof toolCall.args.agentName === "string") {
505
+ const agentName = toolCall.args.agentName;
506
+ // Look up displayName from subagentConfigs in the original tool definition
507
+ // (not from matchingTool, which is a LangChain tool without subagentConfigs)
508
+ const taskTool = this.definition.tools?.find((t) => typeof t === "object" &&
509
+ t.type === "direct" &&
510
+ t.name === SUBAGENT_TOOL_NAME);
511
+ const subagentConfigs = taskTool?.subagentConfigs;
512
+ const subagentConfig = subagentConfigs?.find((config) => config.agentName === agentName);
513
+ prettyName = subagentConfig?.displayName ?? agentName;
514
+ }
515
+ // Check if we already emitted a preliminary notification from early tool_use block
516
+ const alreadyEmittedPreliminary = preliminaryToolCallIds.has(toolCall.id);
517
+ if (alreadyEmittedPreliminary) {
518
+ // Update the existing preliminary notification with full details
519
+ yield {
520
+ sessionUpdate: "tool_call_update",
521
+ toolCallId: toolCall.id,
522
+ title: toolCall.name,
523
+ rawInput: toolCall.args,
524
+ ...(tokenUsage ? { tokenUsage } : {}),
525
+ _meta: {
526
+ messageId: req.messageId,
527
+ ...(prettyName ? { prettyName } : {}),
528
+ ...(icon ? { icon } : {}),
529
+ ...(batchId ? { batchId } : {}),
530
+ },
531
+ };
532
+ }
533
+ else {
534
+ // Emit full tool_call notification (fallback for non-streaming scenarios)
535
+ yield {
536
+ sessionUpdate: "tool_call",
537
+ toolCallId: toolCall.id,
538
+ title: toolCall.name,
539
+ kind: "other",
540
+ status: "pending",
541
+ rawInput: toolCall.args,
542
+ ...(tokenUsage ? { tokenUsage } : {}),
543
+ _meta: {
544
+ messageId: req.messageId,
545
+ ...(prettyName ? { prettyName } : {}),
546
+ ...(icon ? { icon } : {}),
547
+ ...(batchId ? { batchId } : {}),
548
+ },
549
+ };
550
+ }
551
+ // Always emit in_progress status update
446
552
  yield {
447
553
  sessionUpdate: "tool_call_update",
448
554
  toolCallId: toolCall.id,
@@ -556,10 +662,26 @@ export class LangchainAgent {
556
662
  yield msgToYield;
557
663
  }
558
664
  else if (part.type === "tool_use") {
559
- // We don't care about tool use chunks -- do nothing
665
+ // Emit early notification for tool use as soon as we detect it
666
+ // The tool_use block contains { type, id, name, input }
667
+ const toolUseBlock = part;
668
+ if (toolUseBlock.id &&
669
+ toolUseBlock.name &&
670
+ !preliminaryToolCallIds.has(toolUseBlock.id)) {
671
+ preliminaryToolCallIds.add(toolUseBlock.id);
672
+ yield {
673
+ sessionUpdate: "tool_call",
674
+ toolCallId: toolUseBlock.id,
675
+ title: toolUseBlock.name,
676
+ kind: "other",
677
+ status: "pending",
678
+ rawInput: {}, // Args not available yet
679
+ _meta: { messageId: req.messageId },
680
+ };
681
+ }
560
682
  }
561
683
  else if (part.type === "input_json_delta") {
562
- // We don't care about tool use input delta chunks -- do nothing
684
+ // Input JSON delta chunks - we don't process these as tool_call is already emitted
563
685
  }
564
686
  else {
565
687
  throw new Error(`Unhandled AIMessageChunk content block type: ${part.type}\n${JSON.stringify(part)}`);
@@ -576,14 +698,22 @@ export class LangchainAgent {
576
698
  // Skip tool_call_update for todo_write tools
577
699
  continue;
578
700
  }
579
- telemetry.log("info", "Tool call completed", {
701
+ // Check if the tool execution failed
702
+ // LangChain may set status: "error" OR the content may start with "Error:"
703
+ const contentLooksLikeError = typeof aiMessage.content === "string" &&
704
+ aiMessage.content.trim().startsWith("Error:");
705
+ const isError = aiMessage.status === "error" || contentLooksLikeError;
706
+ const status = isError ? "failed" : "completed";
707
+ telemetry.log(isError ? "error" : "info", `Tool call ${status}`, {
580
708
  toolCallId: aiMessage.tool_call_id,
709
+ ...(isError ? { error: aiMessage.content } : {}),
581
710
  });
582
711
  // Send status update (metadata only, no content)
583
712
  yield {
584
713
  sessionUpdate: "tool_call_update",
585
714
  toolCallId: aiMessage.tool_call_id,
586
- status: "completed",
715
+ status,
716
+ ...(isError ? { error: aiMessage.content } : {}),
587
717
  _meta: { messageId: req.messageId },
588
718
  };
589
719
  // Send tool output separately (via direct SSE, bypassing PostgreSQL NOTIFY)
@@ -645,6 +775,16 @@ const modelRequestSchema = z.object({
645
775
  });
646
776
  const makeMcpToolsClient = (mcpConfigs) => {
647
777
  const mcpServers = mcpConfigs?.map((config) => {
778
+ if (typeof config === "string") {
779
+ // Default to localhost:3000/mcp_proxy if not specified
780
+ const proxyUrl = process.env.MCP_PROXY_URL || "http://localhost:3000/mcp_proxy";
781
+ return [
782
+ config,
783
+ {
784
+ url: `${proxyUrl}?server=${config}`,
785
+ },
786
+ ];
787
+ }
648
788
  if (config.transport === "http") {
649
789
  return [
650
790
  config.name,
@@ -0,0 +1,28 @@
1
+ import { z } from "zod";
2
+ interface GenerateImageResult {
3
+ success: boolean;
4
+ filePath?: string | undefined;
5
+ fileName?: string | undefined;
6
+ imageUrl?: string | undefined;
7
+ textResponse?: string | undefined;
8
+ mimeType?: string | undefined;
9
+ error?: string | undefined;
10
+ }
11
+ export declare function makeGenerateImageTool(): import("langchain").DynamicStructuredTool<z.ZodObject<{
12
+ prompt: z.ZodString;
13
+ aspectRatio: z.ZodDefault<z.ZodOptional<z.ZodEnum<{
14
+ "1:1": "1:1";
15
+ "3:4": "3:4";
16
+ "4:3": "4:3";
17
+ "9:16": "9:16";
18
+ "16:9": "16:9";
19
+ "5:4": "5:4";
20
+ }>>>;
21
+ }, z.core.$strip>, {
22
+ prompt: string;
23
+ aspectRatio: "1:1" | "3:4" | "4:3" | "9:16" | "16:9" | "5:4";
24
+ }, {
25
+ prompt: string;
26
+ aspectRatio?: "1:1" | "3:4" | "4:3" | "9:16" | "16:9" | "5:4" | undefined;
27
+ }, GenerateImageResult>;
28
+ export {};
@@ -0,0 +1,135 @@
1
+ import { mkdir, writeFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { GoogleGenAI } from "@google/genai";
4
+ import { tool } from "langchain";
5
+ import { z } from "zod";
6
+ let _genaiClient = null;
7
+ function getGenAIClient() {
8
+ if (_genaiClient) {
9
+ return _genaiClient;
10
+ }
11
+ const apiKey = process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY;
12
+ if (!apiKey) {
13
+ throw new Error("GEMINI_API_KEY or GOOGLE_API_KEY environment variable is required to use the generate_image tool. " +
14
+ "Please set one of them to your Google AI API key.");
15
+ }
16
+ _genaiClient = new GoogleGenAI({ apiKey });
17
+ return _genaiClient;
18
+ }
19
+ export function makeGenerateImageTool() {
20
+ const generateImage = tool(async ({ prompt, aspectRatio = "1:1" }) => {
21
+ try {
22
+ const client = getGenAIClient();
23
+ // Use Gemini 3 Pro Image for image generation
24
+ // Note: imageConfig is a valid API option but not yet in the TypeScript types
25
+ // biome-ignore lint/suspicious/noExplicitAny: imageConfig not yet typed in @google/genai
26
+ const config = {
27
+ responseModalities: ["TEXT", "IMAGE"],
28
+ imageConfig: {
29
+ aspectRatio: aspectRatio,
30
+ },
31
+ };
32
+ const response = await client.models.generateContent({
33
+ model: "gemini-3-pro-image-preview",
34
+ contents: [{ text: prompt }],
35
+ config,
36
+ });
37
+ if (!response.candidates || response.candidates.length === 0) {
38
+ return {
39
+ success: false,
40
+ error: "No response from the model. The request may have been filtered.",
41
+ };
42
+ }
43
+ const candidate = response.candidates[0];
44
+ if (!candidate) {
45
+ return {
46
+ success: false,
47
+ error: "No candidate in the response.",
48
+ };
49
+ }
50
+ const parts = candidate.content?.parts;
51
+ if (!parts || parts.length === 0) {
52
+ return {
53
+ success: false,
54
+ error: "No content parts in the response.",
55
+ };
56
+ }
57
+ let imageData;
58
+ let textResponse;
59
+ let mimeType;
60
+ for (const part of parts) {
61
+ if (part.text) {
62
+ textResponse = part.text;
63
+ }
64
+ else if (part.inlineData) {
65
+ imageData = part.inlineData.data;
66
+ mimeType = part.inlineData.mimeType || "image/png";
67
+ }
68
+ }
69
+ if (!imageData) {
70
+ return {
71
+ success: false,
72
+ error: "No image was generated in the response.",
73
+ ...(textResponse ? { textResponse } : {}),
74
+ };
75
+ }
76
+ // Save image to disk in generated-images directory (relative to cwd)
77
+ const outputDir = join(process.cwd(), "generated-images");
78
+ await mkdir(outputDir, { recursive: true });
79
+ // Generate unique filename
80
+ const timestamp = Date.now();
81
+ const extension = mimeType === "image/jpeg" ? "jpg" : "png";
82
+ const fileName = `image-${timestamp}.${extension}`;
83
+ const filePath = join(outputDir, fileName);
84
+ // Save image to file
85
+ const buffer = Buffer.from(imageData, "base64");
86
+ await writeFile(filePath, buffer);
87
+ // Create URL for the static file server
88
+ // The agent HTTP server serves static files from the agent directory
89
+ const port = process.env.PORT || "3100";
90
+ const imageUrl = `http://localhost:${port}/static/generated-images/${fileName}`;
91
+ return {
92
+ success: true,
93
+ filePath,
94
+ fileName,
95
+ imageUrl,
96
+ ...(mimeType ? { mimeType } : {}),
97
+ ...(textResponse ? { textResponse } : {}),
98
+ };
99
+ }
100
+ catch (error) {
101
+ const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
102
+ return {
103
+ success: false,
104
+ error: `Image generation failed: ${errorMessage}`,
105
+ };
106
+ }
107
+ }, {
108
+ name: "GenerateImage",
109
+ description: "Generate an image based on a text prompt using Google's Gemini image generation model. " +
110
+ "Returns an imageUrl that can be displayed to the user. After calling this tool, " +
111
+ "include the imageUrl in your response as a markdown image like ![Description](imageUrl) " +
112
+ "so the user can see the generated image.\n" +
113
+ "- Creates images from detailed text descriptions\n" +
114
+ "- Supports various aspect ratios for different use cases\n" +
115
+ "- Be specific in prompts about style, composition, colors, and subjects\n" +
116
+ "\n" +
117
+ "Usage notes:\n" +
118
+ " - Provide detailed, specific prompts for best results\n" +
119
+ " - The generated image is saved and served via URL\n" +
120
+ " - Always display the result using markdown: ![description](imageUrl)\n",
121
+ schema: z.object({
122
+ prompt: z
123
+ .string()
124
+ .describe("A detailed description of the image to generate. Be specific about style, composition, colors, and subjects."),
125
+ aspectRatio: z
126
+ .enum(["1:1", "3:4", "4:3", "9:16", "16:9", "5:4"])
127
+ .optional()
128
+ .default("1:1")
129
+ .describe("The aspect ratio of the generated image."),
130
+ }),
131
+ });
132
+ generateImage.prettyName = "Generate Image";
133
+ generateImage.icon = "Image";
134
+ return generateImage;
135
+ }
@@ -2,19 +2,24 @@ import type { DirectTool } from "../../tools.js";
2
2
  /**
3
3
  * Name of the Task tool created by makeSubagentsTool
4
4
  */
5
- export declare const TASK_TOOL_NAME = "Task";
5
+ export declare const SUBAGENT_TOOL_NAME = "subagent";
6
6
  /**
7
7
  * Configuration for a single subagent - supports two variants:
8
8
  * 1. Agent name with optional working directory
9
9
  * 2. Direct path to agent's index.ts file
10
+ *
11
+ * The optional displayName field provides a human-readable name for the UI.
12
+ * If not provided, agentName will be used for display.
10
13
  */
11
14
  type SubagentConfig = {
12
15
  agentName: string;
13
16
  description: string;
17
+ displayName?: string;
14
18
  cwd?: string;
15
19
  } | {
16
20
  agentName: string;
17
21
  description: string;
22
+ displayName?: string;
18
23
  path: string;
19
24
  };
20
25
  /**
@@ -9,7 +9,7 @@ import { SUBAGENT_MODE_KEY } from "../../../acp-server/adapter.js";
9
9
  /**
10
10
  * Name of the Task tool created by makeSubagentsTool
11
11
  */
12
- export const TASK_TOOL_NAME = "Task";
12
+ export const SUBAGENT_TOOL_NAME = "subagent";
13
13
  /**
14
14
  * Creates a DirectTool that delegates work to one of multiple configured subagents.
15
15
  *
@@ -61,9 +61,17 @@ export function makeSubagentsTool(configs) {
61
61
  .map((config) => `"${config.agentName}": ${config.description}`)
62
62
  .join("\n");
63
63
  const agentNames = configs.map((c) => c.agentName);
64
+ // Extract subagent configs for metadata (agentName, description, displayName)
65
+ const subagentConfigs = configs.map((config) => ({
66
+ agentName: config.agentName,
67
+ description: config.description,
68
+ displayName: config.displayName,
69
+ }));
64
70
  return {
65
71
  type: "direct",
66
- name: TASK_TOOL_NAME,
72
+ name: SUBAGENT_TOOL_NAME,
73
+ prettyName: "Subagent",
74
+ icon: "BrainCircuit",
67
75
  description: `Launch a new agent to handle complex, multi-step tasks autonomously.
68
76
 
69
77
  The Task tool launches specialized agents (subprocesses) that autonomously handle complex tasks. Each agent type has specific capabilities and tools available to it.
@@ -129,6 +137,8 @@ assistant: "I'm going to use the Task tool to launch the greeting-responder agen
129
137
  .describe("The name of the subagent to use"),
130
138
  query: z.string().describe("The query or task to send to the subagent"),
131
139
  }),
140
+ // Expose subagent configs for metadata extraction by the adapter
141
+ subagentConfigs,
132
142
  fn: async (input) => {
133
143
  const { agentName, query } = input;
134
144
  const agent = agentMap.get(agentName);
@@ -1,6 +1,12 @@
1
1
  import { z } from "zod";
2
2
  /** Built-in tool types. */
3
- export declare const zBuiltInToolType: z.ZodUnion<readonly [z.ZodLiteral<"todo_write">, z.ZodLiteral<"get_weather">, z.ZodLiteral<"web_search">, z.ZodLiteral<"filesystem">]>;
3
+ export declare const zBuiltInToolType: z.ZodUnion<readonly [z.ZodLiteral<"todo_write">, z.ZodLiteral<"get_weather">, z.ZodLiteral<"web_search">, z.ZodLiteral<"filesystem">, z.ZodLiteral<"generate_image">]>;
4
+ /** Subagent configuration schema for Task tools. */
5
+ export declare const zSubagentConfig: z.ZodObject<{
6
+ agentName: z.ZodString;
7
+ description: z.ZodString;
8
+ displayName: z.ZodOptional<z.ZodString>;
9
+ }, z.core.$strip>;
4
10
  /** Direct tool object schema (for tools imported directly in code). */
5
11
  declare const zDirectTool: z.ZodObject<{
6
12
  type: z.ZodLiteral<"direct">;
@@ -10,9 +16,14 @@ declare const zDirectTool: z.ZodObject<{
10
16
  schema: z.ZodAny;
11
17
  prettyName: z.ZodOptional<z.ZodString>;
12
18
  icon: z.ZodOptional<z.ZodString>;
19
+ subagentConfigs: z.ZodOptional<z.ZodArray<z.ZodObject<{
20
+ agentName: z.ZodString;
21
+ description: z.ZodString;
22
+ displayName: z.ZodOptional<z.ZodString>;
23
+ }, z.core.$strip>>>;
13
24
  }, z.core.$strip>;
14
25
  /** Tool type - can be a built-in tool string or custom tool object. */
15
- export declare const zToolType: z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodLiteral<"todo_write">, z.ZodLiteral<"get_weather">, z.ZodLiteral<"web_search">, z.ZodLiteral<"filesystem">]>, z.ZodObject<{
26
+ export declare const zToolType: 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<{
16
27
  type: z.ZodLiteral<"custom">;
17
28
  modulePath: z.ZodString;
18
29
  }, z.core.$strip>, z.ZodObject<{
@@ -26,8 +37,14 @@ export declare const zToolType: z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodL
26
37
  schema: z.ZodAny;
27
38
  prettyName: z.ZodOptional<z.ZodString>;
28
39
  icon: z.ZodOptional<z.ZodString>;
40
+ subagentConfigs: z.ZodOptional<z.ZodArray<z.ZodObject<{
41
+ agentName: z.ZodString;
42
+ description: z.ZodString;
43
+ displayName: z.ZodOptional<z.ZodString>;
44
+ }, z.core.$strip>>>;
29
45
  }, z.core.$strip>]>;
30
46
  export type ToolType = z.infer<typeof zToolType>;
31
47
  export type BuiltInToolType = z.infer<typeof zBuiltInToolType>;
32
48
  export type DirectTool = z.infer<typeof zDirectTool>;
49
+ export type SubagentConfig = z.infer<typeof zSubagentConfig>;
33
50
  export {};
@@ -5,6 +5,7 @@ export const zBuiltInToolType = z.union([
5
5
  z.literal("get_weather"),
6
6
  z.literal("web_search"),
7
7
  z.literal("filesystem"),
8
+ z.literal("generate_image"),
8
9
  ]);
9
10
  /** Custom tool schema (loaded from module path). */
10
11
  const zCustomTool = z.object({
@@ -16,6 +17,12 @@ const zFilesystemTool = z.object({
16
17
  type: z.literal("filesystem"),
17
18
  working_directory: z.string().optional(),
18
19
  });
20
+ /** Subagent configuration schema for Task tools. */
21
+ export const zSubagentConfig = z.object({
22
+ agentName: z.string(),
23
+ description: z.string(),
24
+ displayName: z.string().optional(),
25
+ });
19
26
  /** Direct tool object schema (for tools imported directly in code). */
20
27
  const zDirectTool = z.object({
21
28
  type: z.literal("direct"),
@@ -25,6 +32,8 @@ const zDirectTool = z.object({
25
32
  schema: z.any(), // Accept any Zod schema
26
33
  prettyName: z.string().optional(),
27
34
  icon: z.string().optional(),
35
+ /** Subagent configurations (only present for Task tools created by makeSubagentsTool). */
36
+ subagentConfigs: z.array(zSubagentConfig).optional(),
28
37
  });
29
38
  /** Tool type - can be a built-in tool string or custom tool object. */
30
39
  export const zToolType = z.union([
@@ -3,7 +3,7 @@
3
3
  * Provides tracing and logging capabilities for agent operations
4
4
  */
5
5
  import { context, SpanStatusCode, trace, } from "@opentelemetry/api";
6
- import { logs } from "@opentelemetry/api-logs";
6
+ import { logs, SeverityNumber, } from "@opentelemetry/api-logs";
7
7
  class AgentTelemetry {
8
8
  tracer = null;
9
9
  logger = null;
@@ -115,7 +115,13 @@ class AgentTelemetry {
115
115
  if (!this.enabled || !this.logger) {
116
116
  return;
117
117
  }
118
+ const severityNumber = {
119
+ info: SeverityNumber.INFO,
120
+ warn: SeverityNumber.WARN,
121
+ error: SeverityNumber.ERROR,
122
+ }[level];
118
123
  this.logger.emit({
124
+ severityNumber,
119
125
  severityText: level.toUpperCase(),
120
126
  body: message,
121
127
  attributes: {
@@ -2,6 +2,9 @@ import type { AgentDefinition } from "../definition";
2
2
  export interface TemplateVars {
3
3
  name: string;
4
4
  model: string;
5
+ displayName?: string;
6
+ description?: string;
7
+ suggestedPrompts?: string[];
5
8
  tools: Array<string | {
6
9
  type: "custom";
7
10
  modulePath: string;