-
Notifications
You must be signed in to change notification settings - Fork 4.4k
Expand file tree
/
Copy pathexecutor.ts
More file actions
200 lines (172 loc) · 5.43 KB
/
executor.ts
File metadata and controls
200 lines (172 loc) · 5.43 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
import type { ChatHistoryItem } from "core";
import { services } from "../services/index.js";
import { serviceContainer } from "../services/ServiceContainer.js";
import type { ToolPermissionServiceState } from "../services/ToolPermissionService.js";
import { ModelServiceState, SERVICE_NAMES } from "../services/types.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 || "";
// Combine base system message with agent-specific prompt
if (agentPrompt) {
return `${baseMessage}\n\n${agentPrompt}`;
}
return baseMessage;
}
/**
* Execute a subagent in a child session
*/
// eslint-disable-next-line complexity
export async function executeSubAgent(
options: SubAgentExecutionOptions,
): Promise<SubAgentResult> {
const { agent: subAgent, prompt, abortController, onOutputUpdate } = options;
const mainAgentPermissionsState =
await serviceContainer.get<ToolPermissionServiceState>(
SERVICE_NAMES.TOOL_PERMISSIONS,
);
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");
}
// 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[];
const escapeHandler = () => {
abortController.abort();
chatHistory.push({
message: {
role: "user",
content: "Subagent execution was cancelled by the user.",
},
contextItems: [],
});
};
escapeEvents.on("user-escape", escapeHandler);
try {
let accumulatedOutput = "";
// 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 {
if (escapeHandler) {
escapeEvents.removeListener("user-escape", escapeHandler);
}
// 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,
};
}
}