-
Notifications
You must be signed in to change notification settings - Fork 4k
feat: subagents #9128
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
uinstinct
wants to merge
35
commits into
continuedev:main
Choose a base branch
from
uinstinct:sub-agents
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+675
−12
Open
feat: subagents #9128
Changes from 20 commits
Commits
Show all changes
35 commits
Select commit
Hold shift + click to select a range
32af25f
feat: subagents
uinstinct a78be7c
support streaming into tool call output
uinstinct 675378f
rename `subagent_type` to `subagent_name`
uinstinct 1a06d69
refactor subagent namings
uinstinct c2af5a9
allow all tools inside subagents
uinstinct 1c448c4
fix: resolve circular dependency in subagent tool
continue[bot] 87fd9b6
fix: resolve circular dependency in subagent executor
continue[bot] 6a5f80b
fix: break circular dependency by extracting tool names
continue[bot] 26bef77
fix: correct built-in tool names list
continue[bot] 2003128
fix: correct import order in ToolPermissionService
continue[bot] bcdf012
Merge branch 'main' into sub-agents
uinstinct 85eb249
wip: use the first found apply model for subagent
uinstinct 0cc7a16
get subagents from config
uinstinct 53becf1
remove debug statements
uinstinct 70d5be8
rename builtInAgents to get-agents
uinstinct 08465a6
add `subagent` in model roles
uinstinct b427287
Merge branch 'main' into sub-agents
uinstinct 5abe556
pass modelstate to read subagents
uinstinct 27ac8cc
include model roles subagent
uinstinct 8381812
allow escaping of subagent tool using event emit
uinstinct d510de9
remove builtintoolnames
uinstinct 8e02122
cleanup listener after execution
uinstinct c366fa8
fix lint
uinstinct 3bdac8d
move subagent behind beta
uinstinct a48bb94
fix more lints
uinstinct 6182a3f
restore toolpermissionservice changes
uinstinct fc5e22f
fix cyclical import
uinstinct 25b08e0
fix gitaiintegration test
uinstinct 66fe98f
try to fix import issues for tests
uinstinct eb77a58
use subagent tool meta instead of calling the func
uinstinct 3fbb428
add subagent tests
uinstinct 2b347b6
use dynamic to prevent cyclical dependency
uinstinct c2d928d
fix subagent tests
uinstinct d55a138
decouple allbuiltins and subagent tool meta
uinstinct 72da793
skip dynamic imports
uinstinct File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,209 @@ | ||
| import type { ChatHistoryItem } from "core"; | ||
|
|
||
| import { | ||
| ModelServiceState, | ||
| SERVICE_NAMES, | ||
| serviceContainer, | ||
| services, | ||
| } from "../services/index.js"; | ||
| import { ToolPermissionServiceState } from "../services/ToolPermissionService.js"; | ||
| import { streamChatResponse } from "../stream/streamChatResponse.js"; | ||
| import { escapeEvents } from "../util/cli.js"; | ||
| import { logger } from "../util/logger.js"; | ||
|
|
||
| /** | ||
| * Options for executing a subagent | ||
| */ | ||
| export interface SubAgentExecutionOptions { | ||
| agent: ModelServiceState; | ||
| prompt: string; | ||
| parentSessionId: string; | ||
| abortController: AbortController; | ||
| onOutputUpdate?: (output: string) => void; | ||
| } | ||
|
|
||
| /** | ||
| * Result from executing a subagent | ||
| */ | ||
| export interface SubAgentResult { | ||
| success: boolean; | ||
| response: string; | ||
| error?: string; | ||
| } | ||
|
|
||
| /** | ||
| * Build system message for the agent | ||
| */ | ||
| async function buildAgentSystemMessage( | ||
| agent: ModelServiceState, | ||
| services: any, | ||
| ): Promise<string> { | ||
| const baseMessage = services.systemMessage | ||
| ? await services.systemMessage.getSystemMessage( | ||
| services.toolPermissions.getState().currentMode, | ||
| ) | ||
| : ""; | ||
|
|
||
| const agentPrompt = agent.model?.chatOptions?.baseSystemMessage || ""; | ||
uinstinct marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| // Combine base system message with agent-specific prompt | ||
| if (agentPrompt) { | ||
| return `${baseMessage}\n\n${agentPrompt}`; | ||
| } | ||
|
|
||
| return baseMessage; | ||
| } | ||
|
|
||
| /** | ||
| * Execute a subagent in a child session | ||
| */ | ||
| export async function executeSubAgent( | ||
| options: SubAgentExecutionOptions, | ||
| ): Promise<SubAgentResult> { | ||
| const mainAgentPermissionsState = | ||
| await serviceContainer.get<ToolPermissionServiceState>( | ||
| SERVICE_NAMES.TOOL_PERMISSIONS, | ||
| ); | ||
|
|
||
| const { agent: subAgent, prompt, abortController, onOutputUpdate } = options; | ||
|
|
||
| try { | ||
| logger.debug("Starting subagent execution", { | ||
| agent: subAgent.model?.name, | ||
| }); | ||
|
|
||
| const { model, llmApi } = subAgent; | ||
| if (!model || !llmApi) { | ||
| throw new Error("Model or LLM API not available"); | ||
| } | ||
|
|
||
| // allow all tools for now | ||
| // todo: eventually we want to show the same prompt in a dialog whether asking whether that tool call is allowed or not | ||
|
|
||
| serviceContainer.set<ToolPermissionServiceState>( | ||
uinstinct marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| SERVICE_NAMES.TOOL_PERMISSIONS, | ||
| { | ||
| ...mainAgentPermissionsState, | ||
| permissions: { | ||
| policies: [{ tool: "*", permission: "allow" }], | ||
| }, | ||
| }, | ||
| ); | ||
|
|
||
| // Build agent system message | ||
| const systemMessage = await buildAgentSystemMessage(subAgent, services); | ||
|
|
||
| // Store original system message function | ||
| const originalGetSystemMessage = services.systemMessage?.getSystemMessage; | ||
|
|
||
| // Store original ChatHistoryService ready state | ||
| const chatHistorySvc = services.chatHistory; | ||
| const originalIsReady = | ||
| chatHistorySvc && typeof chatHistorySvc.isReady === "function" | ||
| ? chatHistorySvc.isReady | ||
| : undefined; | ||
|
|
||
| // Override system message for this execution | ||
| if (services.systemMessage) { | ||
| services.systemMessage.getSystemMessage = async () => systemMessage; | ||
| } | ||
|
|
||
| // Temporarily disable ChatHistoryService to prevent it from interfering with child session | ||
| if (chatHistorySvc && originalIsReady) { | ||
| chatHistorySvc.isReady = () => false; | ||
| } | ||
|
|
||
| const chatHistory = [ | ||
| { | ||
| message: { | ||
| role: "user", | ||
| content: prompt, | ||
| }, | ||
| contextItems: [], | ||
| }, | ||
| ] as ChatHistoryItem[]; | ||
|
|
||
| try { | ||
| let accumulatedOutput = ""; | ||
|
|
||
| escapeEvents.on("user-escape", () => { | ||
uinstinct marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| abortController.abort(); | ||
| chatHistory.push({ | ||
| message: { | ||
| role: "user", | ||
| content: "Subagent execution was cancelled by the user.", | ||
| }, | ||
| contextItems: [], | ||
| }); | ||
| }); | ||
|
|
||
| // Execute the chat stream with child session | ||
| await streamChatResponse( | ||
| chatHistory, | ||
| model, | ||
| llmApi, | ||
| abortController, | ||
| { | ||
| onContent: (content: string) => { | ||
| accumulatedOutput += content; | ||
| if (onOutputUpdate) { | ||
| onOutputUpdate(accumulatedOutput); | ||
| } | ||
| }, | ||
| onToolResult: (result: string) => { | ||
| // todo: skip tool outputs - show tool names and params | ||
| accumulatedOutput += `\n\n${result}`; | ||
| if (onOutputUpdate) { | ||
| onOutputUpdate(accumulatedOutput); | ||
| } | ||
| }, | ||
| }, | ||
| false, // Not compacting | ||
| ); | ||
|
|
||
| // The last message (mostly) contains the important output to be submitted back to the main agent | ||
| const lastMessage = chatHistory.at(-1); | ||
| const response = | ||
| typeof lastMessage?.message?.content === "string" | ||
| ? lastMessage.message.content | ||
| : ""; | ||
|
|
||
| logger.debug("Subagent execution completed", { | ||
| agent: model?.name, | ||
| responseLength: response.length, | ||
| }); | ||
|
|
||
| return { | ||
| success: true, | ||
| response, | ||
| }; | ||
| } finally { | ||
| // Restore original system message function | ||
| if (services.systemMessage && originalGetSystemMessage) { | ||
| services.systemMessage.getSystemMessage = originalGetSystemMessage; | ||
| } | ||
|
|
||
| // Restore original ChatHistoryService ready state | ||
| if (chatHistorySvc && originalIsReady) { | ||
| chatHistorySvc.isReady = originalIsReady; | ||
| } | ||
|
|
||
| // Restore original main agent tool permissions | ||
| serviceContainer.set<ToolPermissionServiceState>( | ||
| SERVICE_NAMES.TOOL_PERMISSIONS, | ||
| mainAgentPermissionsState, | ||
| ); | ||
| } | ||
| } catch (error: any) { | ||
| logger.error("Subagent execution failed", { | ||
| agent: subAgent.model?.name, | ||
| error: error.message, | ||
| }); | ||
|
|
||
| return { | ||
| success: false, | ||
| response: "", | ||
| error: error.message, | ||
| }; | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| import { ModelServiceState } from "../services/index.js"; | ||
| import { ModelService } from "../services/ModelService.js"; | ||
|
|
||
| /** | ||
| * Get an agent by name | ||
| */ | ||
| export function getSubagent(modelState: ModelServiceState, name: string) { | ||
| return ( | ||
| ModelService.getSubagentModels(modelState).find( | ||
| (model) => model.model.name === name, | ||
| ) ?? null | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Generate dynamic tool description listing available agents | ||
| */ | ||
| export function generateSubagentToolDescription( | ||
| modelState: ModelServiceState, | ||
| ): string { | ||
| const agentList = ModelService.getSubagentModels(modelState) | ||
| .map( | ||
| (subagentModel) => | ||
| ` - ${subagentModel.model.name}: ${subagentModel.model.chatOptions?.baseSystemMessage}`, | ||
uinstinct marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ) | ||
| .join("\n"); | ||
|
|
||
| // todo: refine this prompt later | ||
| return `Launch a specialized subagent to handle a specific task. | ||
|
|
||
| Here are the available subagents: | ||
| ${agentList} | ||
| `; | ||
| } | ||
|
|
||
| export function getAgentNames(modelState: ModelServiceState): string[] { | ||
| return ModelService.getSubagentModels(modelState).map( | ||
| (model) => model.model.name, | ||
| ); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| /** | ||
| * List of built-in tool names | ||
| * Kept separate from allBuiltIns.ts to avoid circular dependencies | ||
| */ | ||
| export const BUILT_IN_TOOL_NAMES = [ | ||
uinstinct marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| "Read", | ||
| "Edit", | ||
| "MultiEdit", | ||
| "Write", | ||
| "List", | ||
| "Search", | ||
| "Bash", | ||
| "Fetch", | ||
| "Checklist", | ||
| "Subagent", | ||
| "Exit", | ||
| "ReportFailure", | ||
| "UploadArtifact", | ||
| ] as const; | ||
|
|
||
| export type BuiltInToolName = (typeof BUILT_IN_TOOL_NAMES)[number]; | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.