@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.
- package/dist/acp-server/adapter.d.ts +16 -0
- package/dist/acp-server/adapter.js +230 -16
- package/dist/acp-server/cli.d.ts +1 -3
- package/dist/acp-server/http.js +39 -1
- package/dist/acp-server/session-storage.d.ts +16 -1
- package/dist/acp-server/session-storage.js +23 -0
- package/dist/bin.js +0 -0
- package/dist/definition/index.d.ts +2 -2
- package/dist/definition/index.js +1 -0
- package/dist/runner/agent-runner.d.ts +7 -2
- package/dist/runner/index.d.ts +1 -3
- package/dist/runner/langchain/index.js +178 -38
- package/dist/runner/langchain/tools/generate_image.d.ts +28 -0
- package/dist/runner/langchain/tools/generate_image.js +135 -0
- package/dist/runner/langchain/tools/subagent.d.ts +6 -1
- package/dist/runner/langchain/tools/subagent.js +12 -2
- package/dist/runner/tools.d.ts +19 -2
- package/dist/runner/tools.js +9 -0
- package/dist/telemetry/index.js +7 -1
- package/dist/templates/index.d.ts +3 -0
- package/dist/templates/index.js +26 -4
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -6
- package/templates/index.ts +36 -5
- package/dist/definition/mcp.d.ts +0 -0
- package/dist/definition/mcp.js +0 -0
- package/dist/definition/tools/todo.d.ts +0 -49
- package/dist/definition/tools/todo.js +0 -80
- package/dist/definition/tools/web_search.d.ts +0 -4
- package/dist/definition/tools/web_search.js +0 -26
- package/dist/dev-agent/index.d.ts +0 -2
- package/dist/dev-agent/index.js +0 -18
- package/dist/example.d.ts +0 -2
- package/dist/example.js +0 -19
- package/dist/scaffold/link-local.d.ts +0 -1
- package/dist/scaffold/link-local.js +0 -54
- package/dist/utils/__tests__/tool-overhead-calculator.test.d.ts +0 -1
- package/dist/utils/__tests__/tool-overhead-calculator.test.js +0 -153
- package/dist/utils/logger.d.ts +0 -39
- 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 {
|
|
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 !==
|
|
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
|
-
|
|
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
|
|
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:
|
|
396
|
+
content: promptContent,
|
|
336
397
|
});
|
|
337
398
|
}
|
|
338
399
|
else {
|
|
339
400
|
// Fallback: No context history, use just the prompt
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
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
|
-
|
|
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
|
-
|
|
497
|
+
let prettyName = matchingTool?.prettyName;
|
|
431
498
|
const icon = matchingTool?.icon;
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
|
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  " +
|
|
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: \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
|
|
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
|
|
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:
|
|
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);
|
package/dist/runner/tools.d.ts
CHANGED
|
@@ -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 {};
|
package/dist/runner/tools.js
CHANGED
|
@@ -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([
|
package/dist/telemetry/index.js
CHANGED
|
@@ -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;
|