Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
192 changes: 192 additions & 0 deletions packages/types/src/a2a.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
import { z } from "zod"

// ============================================================================
// A2A Agent Configuration (stored in a2a_settings.json)
// ============================================================================

/**
* Configuration for an A2A agent, stored in settings files.
* Follows the same pattern as MCP server configuration.
*/
export const a2aAgentConfigSchema = z.object({
/** The URL endpoint for the A2A agent */
url: z.string().url(),
/** Optional custom headers for authentication (JSON key-value pairs) */
headers: z.record(z.string()).optional(),
/** Whether this agent is disabled */
disabled: z.boolean().optional(),
/** Timeout in seconds for task operations (default: 300 for long-running tasks) */
timeout: z.number().min(1).max(7200).optional().default(300),
/** Human-readable description of what this agent does */
description: z.string().optional(),
})

export type A2aAgentConfig = z.infer<typeof a2aAgentConfigSchema>

/**
* The full A2A settings file schema (maps agent names to configs).
*/
export const a2aSettingsSchema = z.object({
a2aAgents: z.record(z.string(), a2aAgentConfigSchema).default({}),
})

export type A2aSettings = z.infer<typeof a2aSettingsSchema>

// ============================================================================
// A2A Agent Card (fetched from the agent's /.well-known/agent.json)
// ============================================================================

/**
* Represents an A2A Agent Card as defined in the A2A protocol spec.
* Contains metadata about an agent's capabilities.
*/
export type A2aAgentCard = {
name: string
description?: string
url: string
version?: string
capabilities?: {
streaming?: boolean
pushNotifications?: boolean
stateTransitionHistory?: boolean
}
skills?: A2aSkill[]
}

export type A2aSkill = {
id: string
name: string
description?: string
tags?: string[]
examples?: string[]
}

// ============================================================================
// A2A Task Types (JSON-RPC based protocol)
// ============================================================================

/**
* A2A message part - text, file, or data.
*/
export type A2aTextPart = {
type: "text"
text: string
}

export type A2aFilePart = {
type: "file"
file: {
name?: string
mimeType?: string
bytes?: string // base64 encoded
uri?: string
}
}

export type A2aDataPart = {
type: "data"
data: Record<string, unknown>
}

export type A2aPart = A2aTextPart | A2aFilePart | A2aDataPart

/**
* A2A Message - a message in a task conversation.
*/
export type A2aMessage = {
role: "user" | "agent"
parts: A2aPart[]
metadata?: Record<string, unknown>
}

/**
* A2A Artifact - output produced by an agent.
*/
export type A2aArtifact = {
name?: string
description?: string
parts: A2aPart[]
index?: number
append?: boolean
lastChunk?: boolean
metadata?: Record<string, unknown>
}

/**
* A2A Task state as defined in the protocol.
*/
export const a2aTaskStates = ["submitted", "working", "input-required", "completed", "canceled", "failed"] as const

export type A2aTaskState = (typeof a2aTaskStates)[number]

/**
* A2A Task Status
*/
export type A2aTaskStatus = {
state: A2aTaskState
message?: A2aMessage
timestamp?: string
}

/**
* A2A Task - represents a delegated unit of work.
*/
export type A2aTask = {
id: string
sessionId?: string
status: A2aTaskStatus
history?: A2aMessage[]
artifacts?: A2aArtifact[]
metadata?: Record<string, unknown>
}

// ============================================================================
// A2A JSON-RPC Types
// ============================================================================

export type A2aJsonRpcRequest = {
jsonrpc: "2.0"
id: string | number
method: string
params?: Record<string, unknown>
}

export type A2aJsonRpcResponse = {
jsonrpc: "2.0"
id: string | number
result?: A2aTask
error?: {
code: number
message: string
data?: unknown
}
}

// ============================================================================
// A2A Agent Runtime State (for UI display)
// ============================================================================

/**
* Runtime state of an A2A agent, used for display in the UI.
* Mirrors the pattern of McpServer type.
*/
export type A2aAgent = {
name: string
config: string // JSON stringified config
status: "connected" | "connecting" | "disconnected"
error?: string
disabled?: boolean
agentCard?: A2aAgentCard
activeTasks?: A2aTask[]
source?: "global" | "project"
projectPath?: string
}

/**
* ClineAsk type for A2A agent delegation approval.
*/
export type ClineAskDelegateToAgent = {
type: "delegate_to_agent"
agentName: string
message: string
}
1 change: 1 addition & 0 deletions packages/types/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./a2a.js"
export * from "./api.js"
export * from "./cli.js"
export * from "./cloud.js"
Expand Down
2 changes: 1 addition & 1 deletion packages/types/src/mode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ export const DEFAULT_MODES: readonly ModeConfig[] = [
whenToUse:
"Use this mode when you need to write, modify, or refactor code. Ideal for implementing features, fixing bugs, creating new files, or making code improvements across any programming language or framework.",
description: "Write, modify, and refactor code",
groups: ["read", "edit", "command", "mcp"],
groups: ["read", "edit", "command", "mcp", "a2a"],
},
{
slug: "ask",
Expand Down
3 changes: 2 additions & 1 deletion packages/types/src/tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { z } from "zod"
* ToolGroup
*/

export const toolGroups = ["read", "edit", "command", "mcp", "modes"] as const
export const toolGroups = ["read", "edit", "command", "mcp", "a2a", "modes"] as const

export const toolGroupsSchema = z.enum(toolGroups)

Expand Down Expand Up @@ -46,6 +46,7 @@ export const toolNames = [
"skill",
"generate_image",
"custom_tool",
"delegate_to_agent",
] as const

export const toolNamesSchema = z.enum(toolNames)
Expand Down
18 changes: 18 additions & 0 deletions src/core/assistant-message/NativeToolCallParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,15 @@ export class NativeToolCallParser {
}
break

case "delegate_to_agent":
if (partialArgs.agent_name !== undefined || partialArgs.message !== undefined) {
nativeArgs = {
agent_name: partialArgs.agent_name,
message: partialArgs.message,
}
}
break

case "apply_patch":
if (partialArgs.patch !== undefined) {
nativeArgs = {
Expand Down Expand Up @@ -921,6 +930,15 @@ export class NativeToolCallParser {
}
break

case "delegate_to_agent":
if (args.agent_name !== undefined && args.message !== undefined) {
nativeArgs = {
agent_name: args.agent_name,
message: args.message,
} as NativeArgsFor<TName>
}
break

case "access_mcp_resource":
if (args.server_name !== undefined && args.uri !== undefined) {
nativeArgs = {
Expand Down
8 changes: 8 additions & 0 deletions src/core/assistant-message/presentAssistantMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { updateTodoListTool } from "../tools/UpdateTodoListTool"
import { runSlashCommandTool } from "../tools/RunSlashCommandTool"
import { skillTool } from "../tools/SkillTool"
import { generateImageTool } from "../tools/GenerateImageTool"
import { delegateToAgentTool } from "../tools/DelegateToAgentTool"
import { applyDiffTool as applyDiffToolClass } from "../tools/ApplyDiffTool"
import { isValidToolName, validateToolUse } from "../tools/validateToolUse"
import { codebaseSearchTool } from "../tools/CodebaseSearchTool"
Expand Down Expand Up @@ -789,6 +790,13 @@ export async function presentAssistantMessage(cline: Task) {
pushToolResult,
})
break
case "delegate_to_agent":
await delegateToAgentTool.handle(cline, block as ToolUse<"delegate_to_agent">, {
askApproval,
handleError,
pushToolResult,
})
break
case "ask_followup_question":
await askFollowupQuestionTool.handle(cline, block as ToolUse<"ask_followup_question">, {
askApproval,
Expand Down
15 changes: 14 additions & 1 deletion src/core/prompts/sections/capabilities.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { McpHub } from "../../../services/mcp/McpHub"
import { A2aHub } from "../../../services/a2a/A2aHub"

export function getCapabilitiesSection(cwd: string, mcpHub?: McpHub): string {
export function getCapabilitiesSection(cwd: string, mcpHub?: McpHub, a2aHub?: A2aHub): string {
return `====

CAPABILITIES
Expand All @@ -11,6 +12,18 @@ CAPABILITIES
mcpHub
? `
- You have access to MCP servers that may provide additional tools and resources. Each server may provide different capabilities that you can use to accomplish tasks more effectively.
`
: ""
}${
a2aHub && a2aHub.getAgents().length > 0
? `
- You have access to A2A (Agent-to-Agent) agents that you can delegate tasks to. These are external agents that can handle specialized work. Use the delegate_to_agent tool to send tasks to them. Available agents: ${a2aHub
.getAgents()
.map((a) => {
const desc = a.agentCard?.description ?? ""
return `"${a.name}"${desc ? ` (${desc})` : ""}`
})
.join(", ")}
`
: ""
}`
Expand Down
11 changes: 10 additions & 1 deletion src/core/prompts/system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { formatLanguage } from "../../shared/language"
import { isEmpty } from "../../utils/object"

import { McpHub } from "../../services/mcp/McpHub"
import { A2aHub } from "../../services/a2a/A2aHub"
import { CodeIndexManager } from "../../services/code-index/manager"
import { SkillsManager } from "../../services/skills/SkillsManager"

Expand Down Expand Up @@ -55,6 +56,7 @@ async function generatePrompt(
todoList?: TodoItem[],
modelId?: string,
skillsManager?: SkillsManager,
a2aHub?: A2aHub,
): Promise<string> {
if (!context) {
throw new Error("Extension context is required for generating system prompt")
Expand All @@ -69,6 +71,11 @@ async function generatePrompt(
const hasMcpServers = mcpHub && mcpHub.getServers().length > 0
const shouldIncludeMcp = hasMcpGroup && hasMcpServers

// Check if A2A functionality should be included
const hasA2aGroup = modeConfig.groups.some((groupEntry) => getGroupName(groupEntry) === "a2a")
const hasA2aAgents = a2aHub && a2aHub.getAgents().length > 0
const shouldIncludeA2a = hasA2aGroup && hasA2aAgents

const codeIndexManager = CodeIndexManager.getInstance(context, cwd)

// Tool calling is native-only.
Expand All @@ -90,7 +97,7 @@ ${getSharedToolUseSection()}${toolsCatalog}

${getToolUseGuidelinesSection()}

${getCapabilitiesSection(cwd, shouldIncludeMcp ? mcpHub : undefined)}
${getCapabilitiesSection(cwd, shouldIncludeMcp ? mcpHub : undefined, shouldIncludeA2a ? a2aHub : undefined)}

${modesSection}
${skillsSection ? `\n${skillsSection}` : ""}
Expand Down Expand Up @@ -126,6 +133,7 @@ export const SYSTEM_PROMPT = async (
todoList?: TodoItem[],
modelId?: string,
skillsManager?: SkillsManager,
a2aHub?: A2aHub,
): Promise<string> => {
if (!context) {
throw new Error("Extension context is required for generating system prompt")
Expand Down Expand Up @@ -154,5 +162,6 @@ export const SYSTEM_PROMPT = async (
todoList,
modelId,
skillsManager,
a2aHub,
)
}
33 changes: 33 additions & 0 deletions src/core/prompts/tools/native-tools/delegate_to_agent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type OpenAI from "openai"

const DELEGATE_TO_AGENT_DESCRIPTION = `Delegate a task to an external A2A (Agent-to-Agent) agent. Use this tool when you need to send work to a specialized agent that can handle a specific task. The agent will process the request and return results. This runs as a background operation - you can continue with other work while waiting for the agent to respond.

Parameters:
- agent_name: (required) The name of the A2A agent to delegate to, as configured in the A2A settings.
- message: (required) The task description or message to send to the agent. Be specific about what you need the agent to do.`

const delegate_to_agent: OpenAI.Chat.ChatCompletionTool = {
type: "function",
function: {
name: "delegate_to_agent",
description: DELEGATE_TO_AGENT_DESCRIPTION,
parameters: {
type: "object",
properties: {
agent_name: {
type: "string",
description: "The name of the A2A agent to delegate to, as configured in the A2A settings.",
},
message: {
type: "string",
description:
"The task description or message to send to the agent. Be specific about what you need the agent to do.",
},
},
required: ["agent_name", "message"],
additionalProperties: false,
},
},
}

export default delegate_to_agent
Loading
Loading