@cuylabs/agent-core 0.4.0 → 0.6.0
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/README.md +81 -323
- package/dist/builder-BKkipazh.d.ts +34 -0
- package/dist/capabilities/index.d.ts +97 -0
- package/dist/capabilities/index.js +46 -0
- package/dist/chunk-3C4VKG4P.js +2149 -0
- package/dist/chunk-6TDTQJ4P.js +116 -0
- package/dist/chunk-7MUFEN4K.js +559 -0
- package/dist/chunk-BDBZ3SLK.js +745 -0
- package/dist/chunk-DWYX7ASF.js +26 -0
- package/dist/chunk-FG4MD5MU.js +54 -0
- package/dist/chunk-IVUJDISU.js +556 -0
- package/dist/chunk-LRHOS4ZN.js +584 -0
- package/dist/chunk-O2ZCFQL6.js +764 -0
- package/dist/chunk-P6YF7USR.js +182 -0
- package/dist/chunk-QAQADS4X.js +258 -0
- package/dist/chunk-QWFMX226.js +879 -0
- package/dist/{chunk-6VKLWNRE.js → chunk-SDSBEQXG.js} +1 -132
- package/dist/chunk-VBWWUHWI.js +724 -0
- package/dist/chunk-VEKUXUVF.js +41 -0
- package/dist/chunk-X635CM2F.js +305 -0
- package/dist/chunk-YUUJK53A.js +91 -0
- package/dist/chunk-ZXAKHMWH.js +283 -0
- package/dist/config-D2xeGEHK.d.ts +52 -0
- package/dist/context/index.d.ts +259 -0
- package/dist/context/index.js +26 -0
- package/dist/identifiers-BLUxFqV_.d.ts +12 -0
- package/dist/index-DZQJD_hp.d.ts +1067 -0
- package/dist/index-ipP3_ztp.d.ts +198 -0
- package/dist/index.d.ts +210 -5736
- package/dist/index.js +2132 -7767
- package/dist/mcp/index.d.ts +26 -0
- package/dist/mcp/index.js +14 -0
- package/dist/messages-BYWGn8TY.d.ts +110 -0
- package/dist/middleware/index.d.ts +8 -0
- package/dist/middleware/index.js +12 -0
- package/dist/models/index.d.ts +33 -0
- package/dist/models/index.js +12 -0
- package/dist/network-D76DS5ot.d.ts +5 -0
- package/dist/prompt/index.d.ts +225 -0
- package/dist/prompt/index.js +45 -0
- package/dist/reasoning/index.d.ts +71 -0
- package/dist/reasoning/index.js +47 -0
- package/dist/registry-CuRWWtcT.d.ts +164 -0
- package/dist/resolver-DOfZ-xuk.d.ts +254 -0
- package/dist/runner-G1wxEgac.d.ts +852 -0
- package/dist/runtime/index.d.ts +357 -0
- package/dist/runtime/index.js +64 -0
- package/dist/session-manager-Uawm2Le7.d.ts +274 -0
- package/dist/skill/index.d.ts +103 -0
- package/dist/skill/index.js +39 -0
- package/dist/storage/index.d.ts +167 -0
- package/dist/storage/index.js +50 -0
- package/dist/sub-agent/index.d.ts +14 -0
- package/dist/sub-agent/index.js +15 -0
- package/dist/tool/index.d.ts +174 -1
- package/dist/tool/index.js +12 -3
- package/dist/tool-DYp6-cC3.d.ts +239 -0
- package/dist/tool-pFAnJc5Y.d.ts +419 -0
- package/dist/tracker-DClqYqTj.d.ts +96 -0
- package/dist/tracking/index.d.ts +109 -0
- package/dist/tracking/index.js +20 -0
- package/dist/types-BWo810L_.d.ts +648 -0
- package/dist/types-CQaXbRsS.d.ts +47 -0
- package/dist/types-VQgymC1N.d.ts +156 -0
- package/package.json +89 -5
- package/dist/index-BlSTfS-W.d.ts +0 -470
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// src/models/resolver.ts
|
|
2
|
+
function parseKey(input) {
|
|
3
|
+
const [engineId, ...rest] = input.split("/");
|
|
4
|
+
if (!engineId || rest.length === 0) return null;
|
|
5
|
+
return { engineId, modelId: rest.join("/") };
|
|
6
|
+
}
|
|
7
|
+
function mergeSettings(base, override) {
|
|
8
|
+
return {
|
|
9
|
+
apiKey: override?.apiKey ?? base?.apiKey,
|
|
10
|
+
baseUrl: override?.baseUrl ?? base?.baseUrl,
|
|
11
|
+
headers: {
|
|
12
|
+
...base?.headers ?? {},
|
|
13
|
+
...override?.headers ?? {}
|
|
14
|
+
},
|
|
15
|
+
extra: {
|
|
16
|
+
...base?.extra ?? {},
|
|
17
|
+
...override?.extra ?? {}
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function settingsKey(settings, adapter, engineId) {
|
|
22
|
+
return JSON.stringify({
|
|
23
|
+
engineId,
|
|
24
|
+
adapter,
|
|
25
|
+
apiKey: settings.apiKey ?? "",
|
|
26
|
+
baseUrl: settings.baseUrl ?? "",
|
|
27
|
+
headers: settings.headers ?? {},
|
|
28
|
+
extra: settings.extra ?? {}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
function buildOptions(settings) {
|
|
32
|
+
const opts = { ...settings.extra ?? {} };
|
|
33
|
+
if (settings.apiKey) opts.apiKey = settings.apiKey;
|
|
34
|
+
if (settings.baseUrl) opts.baseURL = settings.baseUrl;
|
|
35
|
+
if (settings.headers && Object.keys(settings.headers).length > 0) opts.headers = settings.headers;
|
|
36
|
+
return opts;
|
|
37
|
+
}
|
|
38
|
+
async function createFactory(adapter, settings) {
|
|
39
|
+
const asModel = (m) => m;
|
|
40
|
+
const opts = buildOptions(settings);
|
|
41
|
+
switch (adapter) {
|
|
42
|
+
case "openai": {
|
|
43
|
+
const { createOpenAI } = await import("@ai-sdk/openai").catch(() => {
|
|
44
|
+
throw new Error(
|
|
45
|
+
`Provider "@ai-sdk/openai" is required for the "openai" adapter. Install it with: pnpm add @ai-sdk/openai`
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
const provider = createOpenAI(opts);
|
|
49
|
+
return (modelId) => provider.languageModel(modelId);
|
|
50
|
+
}
|
|
51
|
+
case "anthropic": {
|
|
52
|
+
const { createAnthropic } = await import("@ai-sdk/anthropic").catch(() => {
|
|
53
|
+
throw new Error(
|
|
54
|
+
`Provider "@ai-sdk/anthropic" is required for the "anthropic" adapter. Install it with: pnpm add @ai-sdk/anthropic`
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
const provider = createAnthropic(opts);
|
|
58
|
+
return (modelId) => provider.languageModel(modelId);
|
|
59
|
+
}
|
|
60
|
+
case "google": {
|
|
61
|
+
const { createGoogleGenerativeAI } = await import("@ai-sdk/google").catch(() => {
|
|
62
|
+
throw new Error(
|
|
63
|
+
`Provider "@ai-sdk/google" is required for the "google" adapter. Install it with: pnpm add @ai-sdk/google`
|
|
64
|
+
);
|
|
65
|
+
});
|
|
66
|
+
const provider = createGoogleGenerativeAI(opts);
|
|
67
|
+
return (modelId) => asModel(provider.languageModel(modelId));
|
|
68
|
+
}
|
|
69
|
+
case "openai-compatible": {
|
|
70
|
+
const { createOpenAICompatible } = await import("@ai-sdk/openai-compatible").catch(() => {
|
|
71
|
+
throw new Error(
|
|
72
|
+
`Provider "@ai-sdk/openai-compatible" is required for the "openai-compatible" adapter. Install it with: pnpm add @ai-sdk/openai-compatible`
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
const provider = createOpenAICompatible({
|
|
76
|
+
name: opts.name ?? "custom",
|
|
77
|
+
baseURL: opts.baseURL ?? "",
|
|
78
|
+
...opts.apiKey ? { apiKey: opts.apiKey } : {},
|
|
79
|
+
...opts.headers ? { headers: opts.headers } : {}
|
|
80
|
+
});
|
|
81
|
+
return (modelId) => provider.languageModel(modelId);
|
|
82
|
+
}
|
|
83
|
+
default:
|
|
84
|
+
throw new Error(`No factory registered for adapter: ${adapter}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function createResolver(directory) {
|
|
88
|
+
const factoryCache = /* @__PURE__ */ new Map();
|
|
89
|
+
return async (key) => {
|
|
90
|
+
const parsed = parseKey(key);
|
|
91
|
+
const entry = parsed ? void 0 : directory.entries?.[key];
|
|
92
|
+
const engineId = parsed?.engineId ?? entry?.engine;
|
|
93
|
+
const modelId = parsed?.modelId ?? entry?.id;
|
|
94
|
+
if (!engineId || !modelId) {
|
|
95
|
+
throw new Error(`Unknown model reference: ${key}`);
|
|
96
|
+
}
|
|
97
|
+
const engine = directory.engines[engineId];
|
|
98
|
+
if (!engine) {
|
|
99
|
+
throw new Error(`Unknown engine: ${engineId}`);
|
|
100
|
+
}
|
|
101
|
+
const settings = mergeSettings(engine.settings, entry?.settings);
|
|
102
|
+
if (engine.build) {
|
|
103
|
+
return engine.build(modelId, settings);
|
|
104
|
+
}
|
|
105
|
+
const cacheKey = settingsKey(settings, engine.adapter, engineId);
|
|
106
|
+
const cached = factoryCache.get(cacheKey);
|
|
107
|
+
if (cached) return cached(modelId);
|
|
108
|
+
const factory = await createFactory(engine.adapter, settings);
|
|
109
|
+
factoryCache.set(cacheKey, factory);
|
|
110
|
+
return factory(modelId);
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export {
|
|
115
|
+
createResolver
|
|
116
|
+
};
|
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Tool
|
|
3
|
+
} from "./chunk-P6YF7USR.js";
|
|
4
|
+
|
|
5
|
+
// src/sub-agent/types.ts
|
|
6
|
+
var DEFAULT_MAX_CONCURRENT = 6;
|
|
7
|
+
var DEFAULT_MAX_SPAWN_DEPTH = 2;
|
|
8
|
+
var DEFAULT_SESSION_TITLE_PREFIX = "Sub-agent";
|
|
9
|
+
|
|
10
|
+
// src/sub-agent/tracker.ts
|
|
11
|
+
var SubAgentTracker = class {
|
|
12
|
+
/** Active sub-agent handles by ID */
|
|
13
|
+
handles = /* @__PURE__ */ new Map();
|
|
14
|
+
/** Maximum concurrent sub-agents */
|
|
15
|
+
maxConcurrent;
|
|
16
|
+
/** Maximum nesting depth */
|
|
17
|
+
maxDepth;
|
|
18
|
+
/** Current depth in the spawn hierarchy */
|
|
19
|
+
currentDepth;
|
|
20
|
+
constructor(config) {
|
|
21
|
+
this.maxConcurrent = config?.maxConcurrent ?? DEFAULT_MAX_CONCURRENT;
|
|
22
|
+
this.maxDepth = config?.maxDepth ?? DEFAULT_MAX_SPAWN_DEPTH;
|
|
23
|
+
this.currentDepth = config?.currentDepth ?? 0;
|
|
24
|
+
}
|
|
25
|
+
// ==========================================================================
|
|
26
|
+
// Slot Management
|
|
27
|
+
// ==========================================================================
|
|
28
|
+
/** Number of currently active (running) sub-agents */
|
|
29
|
+
get activeCount() {
|
|
30
|
+
let count = 0;
|
|
31
|
+
for (const handle of this.handles.values()) {
|
|
32
|
+
if (handle.status.state === "running") count++;
|
|
33
|
+
}
|
|
34
|
+
return count;
|
|
35
|
+
}
|
|
36
|
+
/** Total tracked handles (including completed) */
|
|
37
|
+
get totalCount() {
|
|
38
|
+
return this.handles.size;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Check if a new sub-agent can be spawned.
|
|
42
|
+
* Returns an error message if not, undefined if OK.
|
|
43
|
+
*/
|
|
44
|
+
canSpawn() {
|
|
45
|
+
if (this.currentDepth >= this.maxDepth) {
|
|
46
|
+
return `Sub-agent depth limit reached (current: ${this.currentDepth}, max: ${this.maxDepth}). Complete the task yourself instead of delegating further.`;
|
|
47
|
+
}
|
|
48
|
+
if (this.activeCount >= this.maxConcurrent) {
|
|
49
|
+
return `Maximum concurrent sub-agents reached (${this.maxConcurrent}). Wait for an existing sub-agent to complete before spawning a new one.`;
|
|
50
|
+
}
|
|
51
|
+
return void 0;
|
|
52
|
+
}
|
|
53
|
+
/** Whether the current depth allows further spawning */
|
|
54
|
+
get canNest() {
|
|
55
|
+
return this.currentDepth + 1 < this.maxDepth;
|
|
56
|
+
}
|
|
57
|
+
// ==========================================================================
|
|
58
|
+
// Handle Management
|
|
59
|
+
// ==========================================================================
|
|
60
|
+
/** Register a new sub-agent handle */
|
|
61
|
+
register(handle) {
|
|
62
|
+
this.handles.set(handle.id, handle);
|
|
63
|
+
}
|
|
64
|
+
/** Get a handle by ID */
|
|
65
|
+
get(id) {
|
|
66
|
+
return this.handles.get(id);
|
|
67
|
+
}
|
|
68
|
+
/** Update a handle's status */
|
|
69
|
+
updateStatus(id, status) {
|
|
70
|
+
const handle = this.handles.get(id);
|
|
71
|
+
if (handle) {
|
|
72
|
+
handle.status = status;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/** Get all handles */
|
|
76
|
+
list() {
|
|
77
|
+
return Array.from(this.handles.values());
|
|
78
|
+
}
|
|
79
|
+
/** Get all running handles */
|
|
80
|
+
running() {
|
|
81
|
+
return this.list().filter((h) => h.status.state === "running");
|
|
82
|
+
}
|
|
83
|
+
/** Get all completed handles */
|
|
84
|
+
completed() {
|
|
85
|
+
return this.list().filter((h) => h.status.state === "completed");
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Cancel a running sub-agent.
|
|
89
|
+
* Signals the abort controller and updates status.
|
|
90
|
+
*/
|
|
91
|
+
cancel(id) {
|
|
92
|
+
const handle = this.handles.get(id);
|
|
93
|
+
if (!handle || handle.status.state !== "running") return false;
|
|
94
|
+
handle.abort.abort();
|
|
95
|
+
handle.status = { state: "cancelled" };
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Cancel all running sub-agents.
|
|
100
|
+
*/
|
|
101
|
+
cancelAll() {
|
|
102
|
+
for (const handle of this.handles.values()) {
|
|
103
|
+
if (handle.status.state === "running") {
|
|
104
|
+
handle.abort.abort();
|
|
105
|
+
handle.status = { state: "cancelled" };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Wait for a specific sub-agent to complete.
|
|
111
|
+
* Returns the result or throws if not found.
|
|
112
|
+
*/
|
|
113
|
+
async wait(id, timeoutMs) {
|
|
114
|
+
const handle = this.handles.get(id);
|
|
115
|
+
if (!handle) {
|
|
116
|
+
throw new Error(`Sub-agent not found: "${id}"`);
|
|
117
|
+
}
|
|
118
|
+
if (handle.status.state === "completed") {
|
|
119
|
+
return {
|
|
120
|
+
response: handle.status.response,
|
|
121
|
+
sessionId: handle.sessionId,
|
|
122
|
+
usage: handle.status.usage,
|
|
123
|
+
toolCalls: []
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
if (handle.status.state !== "running") {
|
|
127
|
+
throw new Error(
|
|
128
|
+
`Sub-agent "${id}" is in state "${handle.status.state}" and cannot be waited on.`
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
if (timeoutMs !== void 0 && timeoutMs > 0) {
|
|
132
|
+
const timeout = new Promise(
|
|
133
|
+
(_, reject) => setTimeout(
|
|
134
|
+
() => reject(new Error(`Timed out waiting for sub-agent "${id}" after ${timeoutMs}ms`)),
|
|
135
|
+
timeoutMs
|
|
136
|
+
)
|
|
137
|
+
);
|
|
138
|
+
return Promise.race([handle.promise, timeout]);
|
|
139
|
+
}
|
|
140
|
+
return handle.promise;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Wait for any one of the given sub-agents to complete.
|
|
144
|
+
* Returns the first result.
|
|
145
|
+
*/
|
|
146
|
+
async waitAny(ids, timeoutMs) {
|
|
147
|
+
const handles = ids.map((id) => this.handles.get(id)).filter((h) => h !== void 0);
|
|
148
|
+
if (handles.length === 0) {
|
|
149
|
+
throw new Error(`No sub-agents found for IDs: ${ids.join(", ")}`);
|
|
150
|
+
}
|
|
151
|
+
for (const handle of handles) {
|
|
152
|
+
if (handle.status.state === "completed") {
|
|
153
|
+
return {
|
|
154
|
+
id: handle.id,
|
|
155
|
+
result: {
|
|
156
|
+
response: handle.status.response,
|
|
157
|
+
sessionId: handle.sessionId,
|
|
158
|
+
usage: handle.status.usage,
|
|
159
|
+
toolCalls: []
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
const races = handles.filter((h) => h.status.state === "running").map(
|
|
165
|
+
(h) => h.promise.then((result) => ({ id: h.id, result }))
|
|
166
|
+
);
|
|
167
|
+
if (races.length === 0) {
|
|
168
|
+
throw new Error("No running sub-agents to wait on.");
|
|
169
|
+
}
|
|
170
|
+
if (timeoutMs !== void 0 && timeoutMs > 0) {
|
|
171
|
+
const timeout = new Promise(
|
|
172
|
+
(resolve) => setTimeout(() => resolve({ timedOut: true }), timeoutMs)
|
|
173
|
+
);
|
|
174
|
+
return Promise.race([...races, timeout]);
|
|
175
|
+
}
|
|
176
|
+
return Promise.race(races);
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Create a child tracker configuration for a nested sub-agent.
|
|
180
|
+
* Increments the depth counter.
|
|
181
|
+
*/
|
|
182
|
+
childConfig() {
|
|
183
|
+
return {
|
|
184
|
+
maxConcurrent: this.maxConcurrent,
|
|
185
|
+
maxDepth: this.maxDepth,
|
|
186
|
+
currentDepth: this.currentDepth + 1
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
// src/sub-agent/tool-factories.ts
|
|
192
|
+
import { z } from "zod";
|
|
193
|
+
|
|
194
|
+
// src/sub-agent/results.ts
|
|
195
|
+
function formatUsageLine(result) {
|
|
196
|
+
return result.usage ? `
|
|
197
|
+
Tokens: ${result.usage.inputTokens} in / ${result.usage.outputTokens} out` : "";
|
|
198
|
+
}
|
|
199
|
+
function formatToolCallsLine(result) {
|
|
200
|
+
return result.toolCalls.length > 0 ? `
|
|
201
|
+
Tool calls: ${result.toolCalls.map((tool) => tool.name).join(", ")}` : "";
|
|
202
|
+
}
|
|
203
|
+
function formatInvalidAgentTypeResult(agentType, validNames) {
|
|
204
|
+
return {
|
|
205
|
+
title: "Invalid agent type",
|
|
206
|
+
output: `Unknown agent type "${agentType}".
|
|
207
|
+
|
|
208
|
+
Available types: ${validNames.join(", ")}`,
|
|
209
|
+
metadata: {}
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
function formatSpawnBlockedResult(message) {
|
|
213
|
+
return {
|
|
214
|
+
title: "Cannot spawn sub-agent",
|
|
215
|
+
output: message,
|
|
216
|
+
metadata: {}
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
function formatSyncSubAgentResult(profileName, result) {
|
|
220
|
+
return {
|
|
221
|
+
title: `${profileName} completed`,
|
|
222
|
+
output: `<agent_result agent="${profileName}" session="${result.sessionId}">
|
|
223
|
+
` + result.response + `
|
|
224
|
+
</agent_result>` + formatUsageLine(result) + formatToolCallsLine(result),
|
|
225
|
+
metadata: {
|
|
226
|
+
sessionId: result.sessionId,
|
|
227
|
+
profile: profileName,
|
|
228
|
+
toolCalls: result.toolCalls.length
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
function formatSyncSubAgentErrorResult(profileName, error) {
|
|
233
|
+
return {
|
|
234
|
+
title: `${profileName} failed`,
|
|
235
|
+
output: `Sub-agent "${profileName}" encountered an error:
|
|
236
|
+
` + (error instanceof Error ? error.message : String(error)) + `
|
|
237
|
+
|
|
238
|
+
Consider handling the task directly.`,
|
|
239
|
+
metadata: { profile: profileName, error: true }
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
function formatAsyncSpawnedResult(agentId, profileName, sessionId) {
|
|
243
|
+
return {
|
|
244
|
+
title: `Spawned ${profileName}`,
|
|
245
|
+
output: `Sub-agent spawned successfully.
|
|
246
|
+
|
|
247
|
+
Agent ID: ${agentId}
|
|
248
|
+
Type: ${profileName}
|
|
249
|
+
Session: ${sessionId}
|
|
250
|
+
|
|
251
|
+
Use \`wait_agent\` with this ID to collect the result when ready.`,
|
|
252
|
+
metadata: {
|
|
253
|
+
agentId,
|
|
254
|
+
profile: profileName,
|
|
255
|
+
sessionId
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
function formatWaitResult(agentId, result, tracker) {
|
|
260
|
+
const handle = tracker.get(agentId);
|
|
261
|
+
const profileName = handle?.name ?? "agent";
|
|
262
|
+
const stillRunning = tracker.running().filter((candidate) => candidate.id !== agentId);
|
|
263
|
+
const runningNote = stillRunning.length > 0 ? `
|
|
264
|
+
|
|
265
|
+
Still running: ${stillRunning.map((candidate) => `${candidate.id} (${candidate.name})`).join(", ")}` : "";
|
|
266
|
+
return {
|
|
267
|
+
title: `${profileName} completed`,
|
|
268
|
+
output: `<agent_result agent="${profileName}" id="${agentId}" session="${result.sessionId}">
|
|
269
|
+
` + result.response + `
|
|
270
|
+
</agent_result>` + formatUsageLine(result) + formatToolCallsLine(result) + runningNote,
|
|
271
|
+
metadata: {
|
|
272
|
+
agentId,
|
|
273
|
+
profile: profileName,
|
|
274
|
+
sessionId: result.sessionId,
|
|
275
|
+
toolCalls: result.toolCalls.length
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
function formatWaitTimeoutResult(timeoutMs, runningIds) {
|
|
280
|
+
return {
|
|
281
|
+
title: "Wait timed out",
|
|
282
|
+
output: `Timed out after ${timeoutMs}ms. Still running: ${runningIds.join(", ")}.
|
|
283
|
+
|
|
284
|
+
Call \`wait_agent\` again to continue waiting, or \`close_agent\` to cancel.`,
|
|
285
|
+
metadata: { timedOut: true }
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
function formatWaitErrorResult(error) {
|
|
289
|
+
return {
|
|
290
|
+
title: "Wait failed",
|
|
291
|
+
output: error instanceof Error ? error.message : String(error),
|
|
292
|
+
metadata: { error: true }
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
function formatMissingAgentsResult(ids) {
|
|
296
|
+
return {
|
|
297
|
+
title: "Agent(s) not found",
|
|
298
|
+
output: `Unknown agent IDs: ${ids.join(", ")}`,
|
|
299
|
+
metadata: {}
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
function formatCloseMissingAgentResult(agentId) {
|
|
303
|
+
return {
|
|
304
|
+
title: "Agent not found",
|
|
305
|
+
output: `No sub-agent with ID "${agentId}".`,
|
|
306
|
+
metadata: {}
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
function formatCloseAlreadyResolvedResult(agentId, state) {
|
|
310
|
+
return {
|
|
311
|
+
title: `Agent already ${state}`,
|
|
312
|
+
output: `Sub-agent "${agentId}" is already ${state}. ` + (state === "completed" ? "Use `wait_agent` to retrieve its result." : "No action taken."),
|
|
313
|
+
metadata: { state }
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
function formatCancelledAgentResult(agentId, profileName) {
|
|
317
|
+
return {
|
|
318
|
+
title: `Cancelled ${profileName}`,
|
|
319
|
+
output: `Sub-agent "${agentId}" (${profileName}) has been cancelled.`,
|
|
320
|
+
metadata: {
|
|
321
|
+
agentId,
|
|
322
|
+
profile: profileName
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// src/sub-agent/execution.ts
|
|
328
|
+
var agentCounter = 0;
|
|
329
|
+
function generateAgentId(profileName) {
|
|
330
|
+
return `${profileName}-${++agentCounter}-${Date.now().toString(36)}`;
|
|
331
|
+
}
|
|
332
|
+
function toCompletedResult(result) {
|
|
333
|
+
return {
|
|
334
|
+
response: result.response,
|
|
335
|
+
sessionId: result.sessionId,
|
|
336
|
+
usage: {
|
|
337
|
+
inputTokens: result.usage?.inputTokens ?? 0,
|
|
338
|
+
outputTokens: result.usage?.outputTokens ?? 0,
|
|
339
|
+
totalTokens: result.usage?.totalTokens ?? 0
|
|
340
|
+
},
|
|
341
|
+
toolCalls: result.toolCalls
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
function buildChildAgent(parent, profile) {
|
|
345
|
+
const forkOptions = {};
|
|
346
|
+
if (profile.preset) {
|
|
347
|
+
forkOptions.preset = profile.preset;
|
|
348
|
+
}
|
|
349
|
+
if (profile.systemPrompt) {
|
|
350
|
+
forkOptions.systemPrompt = profile.systemPrompt;
|
|
351
|
+
}
|
|
352
|
+
if (profile.model) {
|
|
353
|
+
forkOptions.model = profile.model;
|
|
354
|
+
}
|
|
355
|
+
if (profile.maxSteps) {
|
|
356
|
+
forkOptions.maxSteps = profile.maxSteps;
|
|
357
|
+
}
|
|
358
|
+
if (profile.additionalMiddleware) {
|
|
359
|
+
forkOptions.additionalMiddleware = profile.additionalMiddleware;
|
|
360
|
+
}
|
|
361
|
+
const child = parent.fork(forkOptions);
|
|
362
|
+
if (profile.additionalTools && profile.additionalTools.length > 0) {
|
|
363
|
+
return parent.fork({
|
|
364
|
+
...forkOptions,
|
|
365
|
+
tools: [...child.getTools(), ...profile.additionalTools]
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
return child;
|
|
369
|
+
}
|
|
370
|
+
async function runSyncSubAgent(child, profile, task, title, context) {
|
|
371
|
+
try {
|
|
372
|
+
const result = await child.run({
|
|
373
|
+
parentSessionId: context.sessionId,
|
|
374
|
+
message: task,
|
|
375
|
+
title,
|
|
376
|
+
abort: context.abort
|
|
377
|
+
});
|
|
378
|
+
return formatSyncSubAgentResult(profile.name, toCompletedResult(result));
|
|
379
|
+
} catch (error) {
|
|
380
|
+
return formatSyncSubAgentErrorResult(profile.name, error);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
async function runAsyncSubAgent(child, profile, task, title, context, tracker) {
|
|
384
|
+
const id = generateAgentId(profile.name);
|
|
385
|
+
const sessionId = context.sessionId ? `${context.sessionId}:sub:${id}` : `sub:${id}`;
|
|
386
|
+
const abortController = new AbortController();
|
|
387
|
+
if (context.abort) {
|
|
388
|
+
context.abort.addEventListener("abort", () => abortController.abort(), {
|
|
389
|
+
once: true
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
const promise = child.run({
|
|
393
|
+
parentSessionId: context.sessionId,
|
|
394
|
+
message: task,
|
|
395
|
+
title,
|
|
396
|
+
abort: abortController.signal
|
|
397
|
+
}).then((result) => {
|
|
398
|
+
const completed = toCompletedResult(result);
|
|
399
|
+
tracker.updateStatus(id, {
|
|
400
|
+
state: "completed",
|
|
401
|
+
response: result.response,
|
|
402
|
+
usage: completed.usage
|
|
403
|
+
});
|
|
404
|
+
return completed;
|
|
405
|
+
}).catch((error) => {
|
|
406
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
407
|
+
tracker.updateStatus(id, { state: "errored", error: message });
|
|
408
|
+
throw error;
|
|
409
|
+
});
|
|
410
|
+
const handle = {
|
|
411
|
+
id,
|
|
412
|
+
name: profile.name,
|
|
413
|
+
sessionId,
|
|
414
|
+
status: { state: "running" },
|
|
415
|
+
promise,
|
|
416
|
+
abort: abortController
|
|
417
|
+
};
|
|
418
|
+
tracker.register(handle);
|
|
419
|
+
return formatAsyncSpawnedResult(id, profile.name, sessionId);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// src/sub-agent/tool-factories.ts
|
|
423
|
+
function getProfileNames(profiles) {
|
|
424
|
+
return profiles.map((profile) => profile.name);
|
|
425
|
+
}
|
|
426
|
+
function findProfile(profiles, agentType) {
|
|
427
|
+
return profiles.find((profile) => profile.name === agentType);
|
|
428
|
+
}
|
|
429
|
+
function buildInvokeDescription(profiles, isAsync) {
|
|
430
|
+
const profileList = profiles.map((profile) => ` - "${profile.name}": ${profile.description}`).join("\n");
|
|
431
|
+
const modeNote = isAsync ? "Returns an agent ID immediately. Use `wait_agent` to collect the result." : "Blocks until the sub-agent completes and returns the result directly.";
|
|
432
|
+
return `Delegate a focused task to a specialized sub-agent.
|
|
433
|
+
|
|
434
|
+
Available agent types:
|
|
435
|
+
${profileList}
|
|
436
|
+
|
|
437
|
+
${modeNote}
|
|
438
|
+
|
|
439
|
+
Guidelines:
|
|
440
|
+
- Use sub-agents for well-scoped, independent tasks
|
|
441
|
+
- Provide clear, self-contained instructions \u2014 the sub-agent has a fresh context
|
|
442
|
+
- Include any relevant file paths, function names, or context the sub-agent needs
|
|
443
|
+
- Mention the working directory structure if known (e.g. "code is in packages/agent-core/src")
|
|
444
|
+
- For parallel work, invoke multiple agents before waiting for results
|
|
445
|
+
- Sub-agents will retry on errors and explore alternatives \u2014 they are resilient`;
|
|
446
|
+
}
|
|
447
|
+
function createInvokeAgentTool(parent, config, tracker) {
|
|
448
|
+
const isAsync = config.async ?? false;
|
|
449
|
+
const titlePrefix = config.sessionTitlePrefix ?? DEFAULT_SESSION_TITLE_PREFIX;
|
|
450
|
+
const validNames = getProfileNames(config.profiles);
|
|
451
|
+
return Tool.define("invoke_agent", () => ({
|
|
452
|
+
description: buildInvokeDescription(config.profiles, isAsync),
|
|
453
|
+
parameters: z.object({
|
|
454
|
+
agent_type: z.string().describe(
|
|
455
|
+
`The type of specialized agent to use. Must be one of: ${validNames.join(", ")}`
|
|
456
|
+
),
|
|
457
|
+
task: z.string().describe(
|
|
458
|
+
"A detailed, self-contained description of the task. Include all context the sub-agent needs."
|
|
459
|
+
),
|
|
460
|
+
title: z.string().optional().describe("Short title for tracking (3-5 words)")
|
|
461
|
+
}),
|
|
462
|
+
execute: async (params, ctx) => {
|
|
463
|
+
const profile = findProfile(config.profiles, params.agent_type);
|
|
464
|
+
if (!profile) {
|
|
465
|
+
return formatInvalidAgentTypeResult(params.agent_type, validNames);
|
|
466
|
+
}
|
|
467
|
+
const spawnError = tracker.canSpawn();
|
|
468
|
+
if (spawnError) {
|
|
469
|
+
return formatSpawnBlockedResult(spawnError);
|
|
470
|
+
}
|
|
471
|
+
const child = buildChildAgent(parent, profile);
|
|
472
|
+
const title = params.title ?? `${titlePrefix}: ${profile.name}`;
|
|
473
|
+
const executionContext = { sessionId: ctx.sessionID, abort: ctx.abort };
|
|
474
|
+
return isAsync ? runAsyncSubAgent(
|
|
475
|
+
child,
|
|
476
|
+
profile,
|
|
477
|
+
params.task,
|
|
478
|
+
title,
|
|
479
|
+
executionContext,
|
|
480
|
+
tracker
|
|
481
|
+
) : runSyncSubAgent(child, profile, params.task, title, executionContext);
|
|
482
|
+
}
|
|
483
|
+
}));
|
|
484
|
+
}
|
|
485
|
+
function createWaitAgentTool(tracker) {
|
|
486
|
+
return Tool.define("wait_agent", {
|
|
487
|
+
description: "Wait for one or more sub-agents to complete and return their results. When multiple IDs are provided, returns whichever finishes first. Use a timeout to avoid blocking indefinitely.",
|
|
488
|
+
parameters: z.object({
|
|
489
|
+
ids: z.array(z.string()).min(1).describe("Agent IDs to wait on (from invoke_agent)"),
|
|
490
|
+
timeout_ms: z.number().optional().describe("Timeout in milliseconds (default: 60000, max: 600000)")
|
|
491
|
+
}),
|
|
492
|
+
execute: async (params) => {
|
|
493
|
+
const timeoutMs = Math.min(params.timeout_ms ?? 6e4, 6e5);
|
|
494
|
+
const missing = params.ids.filter((id) => !tracker.get(id));
|
|
495
|
+
if (missing.length > 0) {
|
|
496
|
+
return formatMissingAgentsResult(missing);
|
|
497
|
+
}
|
|
498
|
+
try {
|
|
499
|
+
if (params.ids.length === 1) {
|
|
500
|
+
const result = await tracker.wait(params.ids[0], timeoutMs);
|
|
501
|
+
return formatWaitResult(params.ids[0], result, tracker);
|
|
502
|
+
}
|
|
503
|
+
const outcome = await tracker.waitAny(params.ids, timeoutMs);
|
|
504
|
+
if ("timedOut" in outcome) {
|
|
505
|
+
const runningIds = params.ids.map((id) => tracker.get(id)).filter((handle) => handle?.status.state === "running").map((handle) => handle.id);
|
|
506
|
+
return formatWaitTimeoutResult(timeoutMs, runningIds);
|
|
507
|
+
}
|
|
508
|
+
return formatWaitResult(outcome.id, outcome.result, tracker);
|
|
509
|
+
} catch (error) {
|
|
510
|
+
return formatWaitErrorResult(error);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
function createCloseAgentTool(tracker) {
|
|
516
|
+
return Tool.define("close_agent", {
|
|
517
|
+
description: "Cancel a running sub-agent. Use when the result is no longer needed or the sub-agent is taking too long.",
|
|
518
|
+
parameters: z.object({
|
|
519
|
+
id: z.string().describe("Agent ID to cancel (from invoke_agent)")
|
|
520
|
+
}),
|
|
521
|
+
execute: async (params) => {
|
|
522
|
+
const handle = tracker.get(params.id);
|
|
523
|
+
if (!handle) {
|
|
524
|
+
return formatCloseMissingAgentResult(params.id);
|
|
525
|
+
}
|
|
526
|
+
if (handle.status.state !== "running") {
|
|
527
|
+
return formatCloseAlreadyResolvedResult(params.id, handle.status.state);
|
|
528
|
+
}
|
|
529
|
+
tracker.cancel(params.id);
|
|
530
|
+
return formatCancelledAgentResult(params.id, handle.name);
|
|
531
|
+
}
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// src/sub-agent/tools.ts
|
|
536
|
+
function createSubAgentTools(parent, config) {
|
|
537
|
+
if (config.profiles.length === 0) {
|
|
538
|
+
return [];
|
|
539
|
+
}
|
|
540
|
+
const currentDepth = config.currentDepth ?? 0;
|
|
541
|
+
const maxDepth = config.maxDepth ?? DEFAULT_MAX_SPAWN_DEPTH;
|
|
542
|
+
if (currentDepth >= maxDepth) {
|
|
543
|
+
return [];
|
|
544
|
+
}
|
|
545
|
+
const tracker = new SubAgentTracker(config);
|
|
546
|
+
const tools = [createInvokeAgentTool(parent, config, tracker)];
|
|
547
|
+
if (config.async) {
|
|
548
|
+
tools.push(createWaitAgentTool(tracker), createCloseAgentTool(tracker));
|
|
549
|
+
}
|
|
550
|
+
return tools;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
export {
|
|
554
|
+
DEFAULT_MAX_CONCURRENT,
|
|
555
|
+
DEFAULT_MAX_SPAWN_DEPTH,
|
|
556
|
+
DEFAULT_SESSION_TITLE_PREFIX,
|
|
557
|
+
SubAgentTracker,
|
|
558
|
+
createSubAgentTools
|
|
559
|
+
};
|