wave-agent-sdk 0.0.1 → 0.0.3
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/agent.d.ts +37 -3
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +82 -5
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/managers/aiManager.d.ts +7 -1
- package/dist/managers/aiManager.d.ts.map +1 -1
- package/dist/managers/aiManager.js +11 -5
- package/dist/managers/messageManager.d.ts +8 -0
- package/dist/managers/messageManager.d.ts.map +1 -1
- package/dist/managers/messageManager.js +26 -2
- package/dist/managers/skillManager.d.ts +4 -5
- package/dist/managers/skillManager.d.ts.map +1 -1
- package/dist/managers/skillManager.js +6 -82
- package/dist/managers/subagentManager.d.ts +96 -0
- package/dist/managers/subagentManager.d.ts.map +1 -0
- package/dist/managers/subagentManager.js +261 -0
- package/dist/managers/toolManager.d.ts +33 -1
- package/dist/managers/toolManager.d.ts.map +1 -1
- package/dist/managers/toolManager.js +43 -5
- package/dist/services/aiService.d.ts +5 -0
- package/dist/services/aiService.d.ts.map +1 -1
- package/dist/services/aiService.js +58 -28
- package/dist/services/session.d.ts.map +1 -1
- package/dist/services/session.js +4 -0
- package/dist/tools/grepTool.d.ts.map +1 -1
- package/dist/tools/grepTool.js +8 -6
- package/dist/tools/readTool.d.ts.map +1 -1
- package/dist/tools/readTool.js +36 -6
- package/dist/tools/skillTool.d.ts +8 -0
- package/dist/tools/skillTool.d.ts.map +1 -0
- package/dist/tools/skillTool.js +72 -0
- package/dist/tools/taskTool.d.ts +8 -0
- package/dist/tools/taskTool.d.ts.map +1 -0
- package/dist/tools/taskTool.js +109 -0
- package/dist/tools/todoWriteTool.d.ts +6 -0
- package/dist/tools/todoWriteTool.d.ts.map +1 -0
- package/dist/tools/todoWriteTool.js +203 -0
- package/dist/types.d.ts +65 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +16 -0
- package/dist/utils/configResolver.d.ts +38 -0
- package/dist/utils/configResolver.d.ts.map +1 -0
- package/dist/utils/configResolver.js +106 -0
- package/dist/utils/configValidator.d.ts +36 -0
- package/dist/utils/configValidator.d.ts.map +1 -0
- package/dist/utils/configValidator.js +78 -0
- package/dist/utils/constants.d.ts +10 -0
- package/dist/utils/constants.d.ts.map +1 -1
- package/dist/utils/constants.js +10 -0
- package/dist/utils/fileFormat.d.ts +17 -0
- package/dist/utils/fileFormat.d.ts.map +1 -0
- package/dist/utils/fileFormat.js +35 -0
- package/dist/utils/messageOperations.d.ts +18 -0
- package/dist/utils/messageOperations.d.ts.map +1 -1
- package/dist/utils/messageOperations.js +43 -0
- package/dist/utils/subagentParser.d.ts +19 -0
- package/dist/utils/subagentParser.d.ts.map +1 -0
- package/dist/utils/subagentParser.js +159 -0
- package/package.json +11 -15
- package/src/agent.ts +130 -9
- package/src/index.ts +0 -1
- package/src/managers/aiManager.ts +22 -10
- package/src/managers/messageManager.ts +55 -1
- package/src/managers/skillManager.ts +7 -96
- package/src/managers/subagentManager.ts +368 -0
- package/src/managers/toolManager.ts +50 -5
- package/src/services/aiService.ts +92 -36
- package/src/services/session.ts +5 -0
- package/src/tools/grepTool.ts +9 -6
- package/src/tools/readTool.ts +40 -6
- package/src/tools/skillTool.ts +82 -0
- package/src/tools/taskTool.ts +128 -0
- package/src/tools/todoWriteTool.ts +232 -0
- package/src/types.ts +85 -1
- package/src/utils/configResolver.ts +142 -0
- package/src/utils/configValidator.ts +133 -0
- package/src/utils/constants.ts +10 -0
- package/src/utils/fileFormat.ts +40 -0
- package/src/utils/messageOperations.ts +80 -0
- package/src/utils/subagentParser.ts +223 -0
|
@@ -5,12 +5,40 @@ import {
|
|
|
5
5
|
ChatCompletionMessageParam,
|
|
6
6
|
ChatCompletionFunctionTool,
|
|
7
7
|
} from "openai/resources.js";
|
|
8
|
-
import {
|
|
8
|
+
import type { GatewayConfig, ModelConfig } from "../types.js";
|
|
9
|
+
import * as os from "os";
|
|
10
|
+
import * as fs from "fs";
|
|
11
|
+
import * as path from "path";
|
|
9
12
|
|
|
10
13
|
/**
|
|
11
|
-
*
|
|
14
|
+
* Check if a directory is a git repository
|
|
15
|
+
* @param dirPath Directory path to check
|
|
16
|
+
* @returns "Yes" if it's a git repo, "No" otherwise
|
|
12
17
|
*/
|
|
13
|
-
|
|
18
|
+
function isGitRepository(dirPath: string): string {
|
|
19
|
+
try {
|
|
20
|
+
// Check if .git directory exists in current directory or any parent directory
|
|
21
|
+
let currentPath = path.resolve(dirPath);
|
|
22
|
+
while (currentPath !== path.dirname(currentPath)) {
|
|
23
|
+
const gitPath = path.join(currentPath, ".git");
|
|
24
|
+
if (fs.existsSync(gitPath)) {
|
|
25
|
+
return "Yes";
|
|
26
|
+
}
|
|
27
|
+
currentPath = path.dirname(currentPath);
|
|
28
|
+
}
|
|
29
|
+
return "No";
|
|
30
|
+
} catch {
|
|
31
|
+
return "No";
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* OpenAI model configuration type, based on OpenAI parameters but excluding messages
|
|
37
|
+
*/
|
|
38
|
+
type OpenAIModelConfig = Omit<
|
|
39
|
+
ChatCompletionCreateParamsNonStreaming,
|
|
40
|
+
"messages"
|
|
41
|
+
>;
|
|
14
42
|
|
|
15
43
|
/**
|
|
16
44
|
* Get specific configuration parameters based on model name
|
|
@@ -20,9 +48,9 @@ type ModelConfig = Omit<ChatCompletionCreateParamsNonStreaming, "messages">;
|
|
|
20
48
|
*/
|
|
21
49
|
function getModelConfig(
|
|
22
50
|
modelName: string,
|
|
23
|
-
baseConfig: Partial<
|
|
24
|
-
):
|
|
25
|
-
const config:
|
|
51
|
+
baseConfig: Partial<OpenAIModelConfig> = {},
|
|
52
|
+
): OpenAIModelConfig {
|
|
53
|
+
const config: OpenAIModelConfig = {
|
|
26
54
|
model: modelName,
|
|
27
55
|
stream: false,
|
|
28
56
|
...baseConfig,
|
|
@@ -37,13 +65,12 @@ function getModelConfig(
|
|
|
37
65
|
return config;
|
|
38
66
|
}
|
|
39
67
|
|
|
40
|
-
// Initialize OpenAI client with environment variables
|
|
41
|
-
const openai = new OpenAI({
|
|
42
|
-
apiKey: process.env.AIGW_TOKEN,
|
|
43
|
-
baseURL: process.env.AIGW_URL,
|
|
44
|
-
});
|
|
45
|
-
|
|
46
68
|
export interface CallAgentOptions {
|
|
69
|
+
// Resolved configuration
|
|
70
|
+
gatewayConfig: GatewayConfig;
|
|
71
|
+
modelConfig: ModelConfig;
|
|
72
|
+
|
|
73
|
+
// Existing parameters (preserved)
|
|
47
74
|
messages: ChatCompletionMessageParam[];
|
|
48
75
|
sessionId?: string;
|
|
49
76
|
abortSignal?: AbortSignal;
|
|
@@ -67,28 +94,46 @@ export interface CallAgentResult {
|
|
|
67
94
|
export async function callAgent(
|
|
68
95
|
options: CallAgentOptions,
|
|
69
96
|
): Promise<CallAgentResult> {
|
|
70
|
-
const {
|
|
71
|
-
|
|
97
|
+
const {
|
|
98
|
+
gatewayConfig,
|
|
99
|
+
modelConfig,
|
|
100
|
+
messages,
|
|
101
|
+
abortSignal,
|
|
102
|
+
memory,
|
|
103
|
+
workdir,
|
|
104
|
+
tools,
|
|
105
|
+
model,
|
|
106
|
+
systemPrompt,
|
|
107
|
+
} = options;
|
|
72
108
|
|
|
73
109
|
try {
|
|
74
|
-
//
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
systemContent = systemPrompt;
|
|
80
|
-
} else {
|
|
81
|
-
// Use default system prompt
|
|
82
|
-
systemContent = `You are an interactive CLI tool that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user.
|
|
110
|
+
// Create OpenAI client with injected configuration
|
|
111
|
+
const openai = new OpenAI({
|
|
112
|
+
apiKey: gatewayConfig.apiKey,
|
|
113
|
+
baseURL: gatewayConfig.baseURL,
|
|
114
|
+
});
|
|
83
115
|
|
|
84
|
-
|
|
85
|
-
|
|
116
|
+
// Build system prompt content
|
|
117
|
+
let systemContent =
|
|
118
|
+
systemPrompt ||
|
|
119
|
+
`You are an interactive CLI tool that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user.`;
|
|
120
|
+
|
|
121
|
+
// Always add environment information
|
|
122
|
+
systemContent += `
|
|
123
|
+
|
|
124
|
+
Here is useful information about the environment you are running in:
|
|
125
|
+
<env>
|
|
126
|
+
Working directory: ${workdir}
|
|
127
|
+
Is directory a git repo: ${isGitRepository(workdir)}
|
|
128
|
+
Platform: ${os.platform()}
|
|
129
|
+
OS Version: ${os.type()} ${os.release()}
|
|
130
|
+
Today's date: ${new Date().toISOString().split("T")[0]}
|
|
131
|
+
</env>
|
|
86
132
|
`;
|
|
87
133
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
134
|
+
// If there is memory content, add it to the system prompt
|
|
135
|
+
if (memory && memory.trim()) {
|
|
136
|
+
systemContent += `\n## Memory Context\n\nThe following is important context and memory from previous interactions:\n\n${memory}`;
|
|
92
137
|
}
|
|
93
138
|
|
|
94
139
|
// Add system prompt
|
|
@@ -101,15 +146,15 @@ ${workdir}
|
|
|
101
146
|
const openaiMessages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] =
|
|
102
147
|
[systemMessage, ...messages];
|
|
103
148
|
|
|
104
|
-
// Get model configuration
|
|
105
|
-
const
|
|
149
|
+
// Get model configuration - use injected modelConfig with optional override
|
|
150
|
+
const openaiModelConfig = getModelConfig(model || modelConfig.agentModel, {
|
|
106
151
|
temperature: 0,
|
|
107
152
|
max_completion_tokens: 32768,
|
|
108
153
|
});
|
|
109
154
|
|
|
110
155
|
// Prepare API call parameters
|
|
111
156
|
const createParams: ChatCompletionCreateParamsNonStreaming = {
|
|
112
|
-
...
|
|
157
|
+
...openaiModelConfig,
|
|
113
158
|
messages: openaiMessages,
|
|
114
159
|
};
|
|
115
160
|
|
|
@@ -160,6 +205,11 @@ ${workdir}
|
|
|
160
205
|
}
|
|
161
206
|
|
|
162
207
|
export interface CompressMessagesOptions {
|
|
208
|
+
// Resolved configuration
|
|
209
|
+
gatewayConfig: GatewayConfig;
|
|
210
|
+
modelConfig: ModelConfig;
|
|
211
|
+
|
|
212
|
+
// Existing parameters
|
|
163
213
|
messages: ChatCompletionMessageParam[];
|
|
164
214
|
abortSignal?: AbortSignal;
|
|
165
215
|
}
|
|
@@ -167,10 +217,16 @@ export interface CompressMessagesOptions {
|
|
|
167
217
|
export async function compressMessages(
|
|
168
218
|
options: CompressMessagesOptions,
|
|
169
219
|
): Promise<string> {
|
|
170
|
-
const { messages, abortSignal } = options;
|
|
220
|
+
const { gatewayConfig, modelConfig, messages, abortSignal } = options;
|
|
221
|
+
|
|
222
|
+
// Create OpenAI client with injected configuration
|
|
223
|
+
const openai = new OpenAI({
|
|
224
|
+
apiKey: gatewayConfig.apiKey,
|
|
225
|
+
baseURL: gatewayConfig.baseURL,
|
|
226
|
+
});
|
|
171
227
|
|
|
172
|
-
// Get model configuration
|
|
173
|
-
const
|
|
228
|
+
// Get model configuration - use injected fast model
|
|
229
|
+
const openaiModelConfig = getModelConfig(modelConfig.fastModel, {
|
|
174
230
|
temperature: 0.1,
|
|
175
231
|
max_tokens: 1500,
|
|
176
232
|
});
|
|
@@ -178,7 +234,7 @@ export async function compressMessages(
|
|
|
178
234
|
try {
|
|
179
235
|
const response = await openai.chat.completions.create(
|
|
180
236
|
{
|
|
181
|
-
...
|
|
237
|
+
...openaiModelConfig,
|
|
182
238
|
messages: [
|
|
183
239
|
{
|
|
184
240
|
role: "system",
|
package/src/services/session.ts
CHANGED
package/src/tools/grepTool.ts
CHANGED
|
@@ -71,7 +71,7 @@ export const grepTool: ToolPlugin = {
|
|
|
71
71
|
head_limit: {
|
|
72
72
|
type: "number",
|
|
73
73
|
description:
|
|
74
|
-
'Limit output to first N lines/entries, equivalent to "| head -N". Works across all output modes: content (limits output lines), files_with_matches (limits file paths), count (limits count entries).
|
|
74
|
+
'Limit output to first N lines/entries, equivalent to "| head -N". Works across all output modes: content (limits output lines), files_with_matches (limits file paths), count (limits count entries). Defaults to 100 to prevent excessive token usage.',
|
|
75
75
|
},
|
|
76
76
|
multiline: {
|
|
77
77
|
type: "boolean",
|
|
@@ -209,12 +209,15 @@ export const grepTool: ToolPlugin = {
|
|
|
209
209
|
};
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
-
// Apply head_limit
|
|
212
|
+
// Apply head_limit with default fallback
|
|
213
213
|
let finalOutput = output;
|
|
214
214
|
let lines = output.split("\n");
|
|
215
215
|
|
|
216
|
-
|
|
217
|
-
|
|
216
|
+
// Set default head_limit if not specified to prevent excessive token usage
|
|
217
|
+
const effectiveHeadLimit = headLimit || 100;
|
|
218
|
+
|
|
219
|
+
if (lines.length > effectiveHeadLimit) {
|
|
220
|
+
lines = lines.slice(0, effectiveHeadLimit);
|
|
218
221
|
finalOutput = lines.join("\n");
|
|
219
222
|
}
|
|
220
223
|
|
|
@@ -230,8 +233,8 @@ export const grepTool: ToolPlugin = {
|
|
|
230
233
|
shortResult = `Found ${totalLines} matching line${totalLines === 1 ? "" : "s"}`;
|
|
231
234
|
}
|
|
232
235
|
|
|
233
|
-
if (
|
|
234
|
-
shortResult += ` (showing first ${
|
|
236
|
+
if (effectiveHeadLimit && totalLines > effectiveHeadLimit) {
|
|
237
|
+
shortResult += ` (showing first ${effectiveHeadLimit})`;
|
|
235
238
|
}
|
|
236
239
|
|
|
237
240
|
return {
|
package/src/tools/readTool.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { readFile } from "fs/promises";
|
|
2
2
|
import type { ToolPlugin, ToolResult, ToolContext } from "./types.js";
|
|
3
3
|
import { resolvePath, getDisplayPath } from "../utils/path.js";
|
|
4
|
+
import {
|
|
5
|
+
isBinaryDocument,
|
|
6
|
+
getBinaryDocumentError,
|
|
7
|
+
} from "../utils/fileFormat.js";
|
|
4
8
|
|
|
5
9
|
/**
|
|
6
10
|
* Read Tool Plugin - Read file content
|
|
@@ -12,7 +16,7 @@ export const readTool: ToolPlugin = {
|
|
|
12
16
|
function: {
|
|
13
17
|
name: "Read",
|
|
14
18
|
description:
|
|
15
|
-
"Reads a file from the local filesystem. You can access any file directly by using this tool.\nAssume this tool is able to read all files on the machine. If the User provides a path to a file assume that path is valid. It is okay to read a file that does not exist; an error will be returned.\n\nUsage:\n- The file_path parameter must be an absolute path, not a relative path\n- By default, it reads up to 2000 lines starting from the beginning of the file\n- You can optionally specify a line offset and limit (especially handy for long files), but it's recommended to read the whole file by not providing these parameters\n- Any lines longer than 2000 characters will be truncated\n- Results are returned using cat -n format, with line numbers starting at 1\n- This tool allows Claude Code to read images (eg PNG, JPG, etc). When reading an image file the contents are presented visually as Claude Code is a multimodal LLM.\n- This tool can read
|
|
19
|
+
"Reads a file from the local filesystem. You can access any file directly by using this tool.\nAssume this tool is able to read all files on the machine. If the User provides a path to a file assume that path is valid. It is okay to read a file that does not exist; an error will be returned.\n\nUsage:\n- The file_path parameter must be an absolute path, not a relative path\n- By default, it reads up to 2000 lines starting from the beginning of the file\n- You can optionally specify a line offset and limit (especially handy for long files), but it's recommended to read the whole file by not providing these parameters\n- Any lines longer than 2000 characters will be truncated\n- Results are returned using cat -n format, with line numbers starting at 1\n- This tool allows Claude Code to read images (eg PNG, JPG, etc). When reading an image file the contents are presented visually as Claude Code is a multimodal LLM.\n- This tool can read Jupyter notebooks (.ipynb files) and returns all cells with their outputs, combining code, text, and visualizations.\n- You have the capability to call multiple tools in a single response. It is always better to speculatively read multiple files as a batch that are potentially useful.\n- You will regularly be asked to read screenshots. If the user provides a path to a screenshot ALWAYS use this tool to view the file at the path. This tool will work with all temporary file paths like /var/folders/123/abc/T/TemporaryItems/NSIRD_screencaptureui_ZfB1tD/Screenshot.png\n- If you read a file that exists but has empty contents you will receive a system reminder warning in place of file contents.\n- Binary document formats (PDF, DOC, DOCX, XLS, XLSX, PPT, PPTX) are not supported and will return an error.",
|
|
16
20
|
parameters: {
|
|
17
21
|
type: "object",
|
|
18
22
|
properties: {
|
|
@@ -51,6 +55,15 @@ export const readTool: ToolPlugin = {
|
|
|
51
55
|
};
|
|
52
56
|
}
|
|
53
57
|
|
|
58
|
+
// Check for binary document formats
|
|
59
|
+
if (isBinaryDocument(filePath)) {
|
|
60
|
+
return {
|
|
61
|
+
success: false,
|
|
62
|
+
content: "",
|
|
63
|
+
error: getBinaryDocumentError(filePath),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
54
67
|
try {
|
|
55
68
|
// Note: New Read tool requires absolute paths, so we don't use resolvePath
|
|
56
69
|
// But for compatibility, if it's not an absolute path, we still try to resolve
|
|
@@ -71,8 +84,19 @@ export const readTool: ToolPlugin = {
|
|
|
71
84
|
};
|
|
72
85
|
}
|
|
73
86
|
|
|
74
|
-
|
|
87
|
+
// Check content size limit (100KB)
|
|
88
|
+
const MAX_CONTENT_SIZE = 100 * 1024; // 100KB
|
|
89
|
+
let contentToProcess = fileContent;
|
|
90
|
+
let contentTruncated = false;
|
|
91
|
+
|
|
92
|
+
if (fileContent.length > MAX_CONTENT_SIZE) {
|
|
93
|
+
contentToProcess = fileContent.substring(0, MAX_CONTENT_SIZE);
|
|
94
|
+
contentTruncated = true;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const lines = contentToProcess.split("\n");
|
|
75
98
|
const totalLines = lines.length;
|
|
99
|
+
const originalTotalLines = fileContent.split("\n").length;
|
|
76
100
|
|
|
77
101
|
// Handle offset and limit
|
|
78
102
|
let startLine = 1;
|
|
@@ -117,7 +141,10 @@ export const readTool: ToolPlugin = {
|
|
|
117
141
|
|
|
118
142
|
// Add file information header
|
|
119
143
|
let content = `File: ${filePath}\n`;
|
|
120
|
-
if (
|
|
144
|
+
if (contentTruncated) {
|
|
145
|
+
content += `Content truncated at ${MAX_CONTENT_SIZE} bytes\n`;
|
|
146
|
+
content += `Lines ${startLine}-${endLine} of ${totalLines} (original file: ${originalTotalLines} lines)\n`;
|
|
147
|
+
} else if (startLine > 1 || endLine < totalLines) {
|
|
121
148
|
content += `Lines ${startLine}-${endLine} of ${totalLines}\n`;
|
|
122
149
|
} else {
|
|
123
150
|
content += `Total lines: ${totalLines}\n`;
|
|
@@ -126,15 +153,22 @@ export const readTool: ToolPlugin = {
|
|
|
126
153
|
content += formattedContent;
|
|
127
154
|
|
|
128
155
|
// If only showing partial content, add prompt
|
|
129
|
-
if (endLine < totalLines) {
|
|
156
|
+
if (endLine < totalLines || contentTruncated) {
|
|
130
157
|
content += `\n${"─".repeat(50)}\n`;
|
|
131
|
-
|
|
158
|
+
if (contentTruncated) {
|
|
159
|
+
content += `... content truncated due to size limit (${MAX_CONTENT_SIZE} bytes)`;
|
|
160
|
+
if (endLine < totalLines) {
|
|
161
|
+
content += ` and ${totalLines - endLine} more lines not shown`;
|
|
162
|
+
}
|
|
163
|
+
} else {
|
|
164
|
+
content += `... ${totalLines - endLine} more lines not shown`;
|
|
165
|
+
}
|
|
132
166
|
}
|
|
133
167
|
|
|
134
168
|
return {
|
|
135
169
|
success: true,
|
|
136
170
|
content,
|
|
137
|
-
shortResult: `Read ${selectedLines.length} lines${totalLines > 2000 ? " (truncated)" : ""}`,
|
|
171
|
+
shortResult: `Read ${selectedLines.length} lines${totalLines > 2000 || contentTruncated ? " (truncated)" : ""}`,
|
|
138
172
|
};
|
|
139
173
|
} catch (error) {
|
|
140
174
|
return {
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { ToolPlugin, ToolResult } from "./types.js";
|
|
2
|
+
import type { SkillManager } from "../managers/skillManager.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Create a skill tool plugin that uses the provided SkillManager
|
|
6
|
+
* Note: SkillManager should be initialized before calling this function
|
|
7
|
+
*/
|
|
8
|
+
export function createSkillTool(skillManager: SkillManager): ToolPlugin {
|
|
9
|
+
const getToolDescription = (): string => {
|
|
10
|
+
const availableSkills = skillManager.getAvailableSkills();
|
|
11
|
+
|
|
12
|
+
if (availableSkills.length === 0) {
|
|
13
|
+
return "Invoke a Wave skill by name. Skills are user-defined automation templates that can be personal or project-specific. No skills are currently available.";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const skillList = availableSkills
|
|
17
|
+
.map(
|
|
18
|
+
(skill) => `• **${skill.name}** (${skill.type}): ${skill.description}`,
|
|
19
|
+
)
|
|
20
|
+
.join("\n");
|
|
21
|
+
|
|
22
|
+
return `Invoke a Wave skill by name. Skills are user-defined automation templates that can be personal or project-specific.\n\nAvailable skills:\n${skillList}`;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
name: "skill",
|
|
27
|
+
config: {
|
|
28
|
+
type: "function",
|
|
29
|
+
function: {
|
|
30
|
+
name: "skill",
|
|
31
|
+
description: getToolDescription(),
|
|
32
|
+
parameters: {
|
|
33
|
+
type: "object",
|
|
34
|
+
properties: {
|
|
35
|
+
skill_name: {
|
|
36
|
+
type: "string",
|
|
37
|
+
description: "Name of the skill to invoke",
|
|
38
|
+
enum: skillManager
|
|
39
|
+
.getAvailableSkills()
|
|
40
|
+
.map((skill) => skill.name),
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
required: ["skill_name"],
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
execute: async (args: Record<string, unknown>): Promise<ToolResult> => {
|
|
48
|
+
try {
|
|
49
|
+
// Validate arguments
|
|
50
|
+
const skillName = args.skill_name as string;
|
|
51
|
+
if (!skillName || typeof skillName !== "string") {
|
|
52
|
+
return {
|
|
53
|
+
success: false,
|
|
54
|
+
content: "",
|
|
55
|
+
error: "skill_name parameter is required and must be a string",
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Execute the skill
|
|
60
|
+
const result = await skillManager.executeSkill({
|
|
61
|
+
skill_name: skillName,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
success: true,
|
|
66
|
+
content: result.content,
|
|
67
|
+
shortResult: `Invoked skill: ${skillName}`,
|
|
68
|
+
};
|
|
69
|
+
} catch (error) {
|
|
70
|
+
return {
|
|
71
|
+
success: false,
|
|
72
|
+
content: "",
|
|
73
|
+
error: error instanceof Error ? error.message : String(error),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
formatCompactParams: (params: Record<string, unknown>) => {
|
|
78
|
+
const skillName = params.skill_name as string;
|
|
79
|
+
return skillName || "unknown-skill";
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import type { ToolPlugin, ToolResult } from "./types.js";
|
|
2
|
+
import type { SubagentManager } from "../managers/subagentManager.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Create a task tool plugin that uses the provided SubagentManager
|
|
6
|
+
* Note: SubagentManager should be initialized before calling this function
|
|
7
|
+
*/
|
|
8
|
+
export function createTaskTool(subagentManager: SubagentManager): ToolPlugin {
|
|
9
|
+
// Get available subagents from the initialized subagent manager
|
|
10
|
+
const availableSubagents = subagentManager.getConfigurations();
|
|
11
|
+
const subagentList = availableSubagents
|
|
12
|
+
.map((config) => `- ${config.name}: ${config.description}`)
|
|
13
|
+
.join("\n");
|
|
14
|
+
|
|
15
|
+
const description = `Delegate a task to a specialized subagent. Use this when you need specialized expertise or want to break down complex work into focused subtasks.
|
|
16
|
+
|
|
17
|
+
Available subagents:
|
|
18
|
+
${subagentList || "No subagents configured"}`;
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
name: "Task",
|
|
22
|
+
config: {
|
|
23
|
+
type: "function",
|
|
24
|
+
function: {
|
|
25
|
+
name: "Task",
|
|
26
|
+
description,
|
|
27
|
+
parameters: {
|
|
28
|
+
type: "object",
|
|
29
|
+
properties: {
|
|
30
|
+
description: {
|
|
31
|
+
type: "string",
|
|
32
|
+
description:
|
|
33
|
+
"A clear, concise description of what needs to be accomplished",
|
|
34
|
+
},
|
|
35
|
+
prompt: {
|
|
36
|
+
type: "string",
|
|
37
|
+
description:
|
|
38
|
+
"The specific instructions or prompt to send to the subagent",
|
|
39
|
+
},
|
|
40
|
+
subagent_type: {
|
|
41
|
+
type: "string",
|
|
42
|
+
description: `The type or name of subagent to use. Available options: ${availableSubagents.map((c) => c.name).join(", ") || "none"}`,
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
required: ["description", "prompt", "subagent_type"],
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
execute: async (args: Record<string, unknown>): Promise<ToolResult> => {
|
|
51
|
+
// Input validation
|
|
52
|
+
const description = args.description as string;
|
|
53
|
+
const prompt = args.prompt as string;
|
|
54
|
+
const subagent_type = args.subagent_type as string;
|
|
55
|
+
|
|
56
|
+
if (!description || typeof description !== "string") {
|
|
57
|
+
return {
|
|
58
|
+
success: false,
|
|
59
|
+
content: "",
|
|
60
|
+
error: "description parameter is required and must be a string",
|
|
61
|
+
shortResult: "Task delegation failed",
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!prompt || typeof prompt !== "string") {
|
|
66
|
+
return {
|
|
67
|
+
success: false,
|
|
68
|
+
content: "",
|
|
69
|
+
error: "prompt parameter is required and must be a string",
|
|
70
|
+
shortResult: "Task delegation failed",
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!subagent_type || typeof subagent_type !== "string") {
|
|
75
|
+
return {
|
|
76
|
+
success: false,
|
|
77
|
+
content: "",
|
|
78
|
+
error: "subagent_type parameter is required and must be a string",
|
|
79
|
+
shortResult: "Task delegation failed",
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
// Subagent selection logic with explicit name matching only
|
|
85
|
+
const configuration = await subagentManager.findSubagent(subagent_type);
|
|
86
|
+
|
|
87
|
+
if (!configuration) {
|
|
88
|
+
// Error handling for nonexistent subagents with available subagents listing
|
|
89
|
+
const allConfigs = subagentManager.getConfigurations();
|
|
90
|
+
const availableNames = allConfigs.map((c) => c.name).join(", ");
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
success: false,
|
|
94
|
+
content: "",
|
|
95
|
+
error: `No subagent found matching "${subagent_type}". Available subagents: ${availableNames || "none"}`,
|
|
96
|
+
shortResult: "Subagent not found",
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Create subagent instance and execute task
|
|
101
|
+
const instance = await subagentManager.createInstance(
|
|
102
|
+
configuration,
|
|
103
|
+
description,
|
|
104
|
+
);
|
|
105
|
+
const response = await subagentManager.executeTask(instance, prompt);
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
success: true,
|
|
109
|
+
content: response,
|
|
110
|
+
shortResult: `Task completed by ${configuration.name}`,
|
|
111
|
+
};
|
|
112
|
+
} catch (error) {
|
|
113
|
+
return {
|
|
114
|
+
success: false,
|
|
115
|
+
content: "",
|
|
116
|
+
error: `Task delegation failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
117
|
+
shortResult: "Delegation error",
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
formatCompactParams: (params: Record<string, unknown>) => {
|
|
123
|
+
const subagent_type = params.subagent_type as string;
|
|
124
|
+
const description = params.description as string;
|
|
125
|
+
return `${subagent_type || "unknown"}: ${description || "no description"}`;
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
}
|