@economic/agents 2.3.2 → 2.3.6
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 +0 -2
- package/dist/providers.d.mts +5 -7
- package/dist/providers.mjs +11 -7
- package/package.json +9 -13
- package/dist/cli.d.mts +0 -1
- package/dist/cli.mjs +0 -561
package/README.md
CHANGED
package/dist/providers.d.mts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import * as _$_ai_sdk_anthropic_internal0 from "@ai-sdk/anthropic/internal";
|
|
2
1
|
import { AnthropicMessagesLanguageModel } from "@ai-sdk/anthropic/internal";
|
|
3
|
-
import * as _$_ai_sdk_google0 from "@ai-sdk/google";
|
|
4
2
|
import { GoogleGenerativeAIProvider } from "@ai-sdk/google";
|
|
5
3
|
import { AnthropicProvider } from "@ai-sdk/anthropic";
|
|
6
4
|
|
|
@@ -14,7 +12,7 @@ interface AnthropicVertexProviderOptions {
|
|
|
14
12
|
location?: string;
|
|
15
13
|
anthropicVersion?: string;
|
|
16
14
|
}
|
|
17
|
-
declare function
|
|
15
|
+
declare function createAnthropicProvider(options: AnthropicVertexProviderOptions): (modelId: AnthropicVertexModelId, metadata?: Record<string, unknown>) => AnthropicMessagesLanguageModel;
|
|
18
16
|
//#endregion
|
|
19
17
|
//#region src/providers/gemini.d.ts
|
|
20
18
|
type GeminiModelId = Parameters<GoogleGenerativeAIProvider>[0];
|
|
@@ -25,13 +23,13 @@ interface GeminiProviderOptions {
|
|
|
25
23
|
googleCloudProjectId: string;
|
|
26
24
|
location?: string;
|
|
27
25
|
}
|
|
28
|
-
declare function createGeminiProvider(options: GeminiProviderOptions): GoogleGenerativeAIProvider;
|
|
26
|
+
declare function createGeminiProvider(options: GeminiProviderOptions, metadata?: Record<string, unknown>): GoogleGenerativeAIProvider;
|
|
29
27
|
//#endregion
|
|
30
28
|
//#region src/providers/index.d.ts
|
|
31
29
|
interface AiGatewayVertexProvidersOptions extends AnthropicVertexProviderOptions, GeminiProviderOptions {}
|
|
32
30
|
declare function createAiGatewayVertexProviders(options: AiGatewayVertexProvidersOptions): {
|
|
33
|
-
anthropic: (modelId: AnthropicVertexModelId) =>
|
|
34
|
-
gemini:
|
|
31
|
+
anthropic: (modelId: AnthropicVertexModelId, metadata?: Record<string, unknown>) => import("@ai-sdk/anthropic/internal").AnthropicMessagesLanguageModel;
|
|
32
|
+
gemini: import("@ai-sdk/google").GoogleGenerativeAIProvider;
|
|
35
33
|
};
|
|
36
34
|
//#endregion
|
|
37
|
-
export { AiGatewayVertexProvidersOptions, type AnthropicVertexModelId, type AnthropicVertexProviderOptions, type GeminiModelId, type GeminiProviderOptions, createAiGatewayVertexProviders,
|
|
35
|
+
export { AiGatewayVertexProvidersOptions, type AnthropicVertexModelId, type AnthropicVertexProviderOptions, type GeminiModelId, type GeminiProviderOptions, createAiGatewayVertexProviders, createAnthropicProvider, createGeminiProvider };
|
package/dist/providers.mjs
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { AnthropicMessagesLanguageModel } from "@ai-sdk/anthropic/internal";
|
|
2
2
|
import { createGoogleGenerativeAI } from "@ai-sdk/google";
|
|
3
3
|
//#region src/providers/anthropic.ts
|
|
4
|
-
function
|
|
4
|
+
function createAnthropicProvider(options) {
|
|
5
5
|
const { anthropicVersion = "vertex-2023-10-16", cloudflareAccountId, cloudflareAiGatewayId, cloudflareApiToken, googleCloudProjectId, location = "europe-west1" } = options;
|
|
6
6
|
const anthropicBaseURL = `${`${`https://gateway.ai.cloudflare.com/v1/${cloudflareAccountId}/${cloudflareAiGatewayId}/`}google-vertex-ai/v1/projects/${googleCloudProjectId}/locations/${location}/publishers/`}anthropic/models/`;
|
|
7
|
-
return function anthropic(modelId) {
|
|
7
|
+
return function anthropic(modelId, metadata) {
|
|
8
|
+
const headers = { "cf-aig-authorization": `Bearer ${cloudflareApiToken}` };
|
|
9
|
+
if (metadata) headers["cf-aig-metadata"] = JSON.stringify(metadata);
|
|
8
10
|
return new AnthropicMessagesLanguageModel(modelId, {
|
|
9
11
|
provider: "vertex.anthropic.messages",
|
|
10
12
|
baseURL: anthropicBaseURL,
|
|
11
|
-
headers
|
|
13
|
+
headers,
|
|
12
14
|
buildRequestUrl: (baseURL, isStreaming) => `${baseURL}${modelId}:${isStreaming ? "streamRawPredict" : "rawPredict"}`,
|
|
13
15
|
transformRequestBody: (args) => {
|
|
14
16
|
const { model, ...rest } = args;
|
|
@@ -25,12 +27,14 @@ function createAnthropicVertexProvider(options) {
|
|
|
25
27
|
}
|
|
26
28
|
//#endregion
|
|
27
29
|
//#region src/providers/gemini.ts
|
|
28
|
-
function createGeminiProvider(options) {
|
|
30
|
+
function createGeminiProvider(options, metadata) {
|
|
29
31
|
const { cloudflareAccountId, cloudflareAiGatewayId, cloudflareApiToken, googleCloudProjectId, location = "europe-west1" } = options;
|
|
32
|
+
const headers = { "cf-aig-authorization": `Bearer ${cloudflareApiToken}` };
|
|
33
|
+
if (metadata) headers["cf-aig-metadata"] = JSON.stringify(metadata);
|
|
30
34
|
return createGoogleGenerativeAI({
|
|
31
35
|
apiKey: "unused",
|
|
32
36
|
baseURL: `https://gateway.ai.cloudflare.com/v1/${cloudflareAccountId}/${cloudflareAiGatewayId}/google-vertex-ai/v1/projects/${googleCloudProjectId}/locations/${location}/publishers/google/models`,
|
|
33
|
-
headers
|
|
37
|
+
headers,
|
|
34
38
|
name: "gateway.google.vertex-ai"
|
|
35
39
|
});
|
|
36
40
|
}
|
|
@@ -38,9 +42,9 @@ function createGeminiProvider(options) {
|
|
|
38
42
|
//#region src/providers/index.ts
|
|
39
43
|
function createAiGatewayVertexProviders(options) {
|
|
40
44
|
return {
|
|
41
|
-
anthropic:
|
|
45
|
+
anthropic: createAnthropicProvider(options),
|
|
42
46
|
gemini: createGeminiProvider(options)
|
|
43
47
|
};
|
|
44
48
|
}
|
|
45
49
|
//#endregion
|
|
46
|
-
export { createAiGatewayVertexProviders,
|
|
50
|
+
export { createAiGatewayVertexProviders, createAnthropicProvider, createGeminiProvider };
|
package/package.json
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@economic/agents",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.6",
|
|
4
4
|
"description": "A starter for creating a TypeScript package.",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"bin": {
|
|
7
|
-
"economic-agents": "./dist/cli.mjs"
|
|
8
|
-
},
|
|
9
6
|
"files": [
|
|
10
7
|
"dist",
|
|
11
8
|
"schema"
|
|
@@ -14,7 +11,6 @@
|
|
|
14
11
|
"types": "./dist/index.d.mts",
|
|
15
12
|
"exports": {
|
|
16
13
|
".": "./dist/index.mjs",
|
|
17
|
-
"./cli": "./dist/cli.mjs",
|
|
18
14
|
"./providers": "./dist/providers.mjs",
|
|
19
15
|
"./v1": "./dist/v1.mjs",
|
|
20
16
|
"./package.json": "./package.json"
|
|
@@ -29,17 +25,17 @@
|
|
|
29
25
|
"dependencies": {
|
|
30
26
|
"@ai-sdk/anthropic": "^3.0.77",
|
|
31
27
|
"@ai-sdk/google": "^3.0.73",
|
|
32
|
-
"@clack/prompts": "^1.2.0",
|
|
33
28
|
"@cloudflare/ai-chat": "^0.8.3",
|
|
34
|
-
"@cloudflare/think": "
|
|
29
|
+
"@cloudflare/think": ">=0.8.4",
|
|
35
30
|
"@opentelemetry/sdk-trace-base": "^2.7.1",
|
|
36
|
-
"agents": "
|
|
31
|
+
"agents": ">=0.14.3 <1.0.0",
|
|
37
32
|
"ai": "^6.0.197",
|
|
38
33
|
"jose": "^6.2.3",
|
|
39
34
|
"nanoid": "^5.1.11"
|
|
40
35
|
},
|
|
41
36
|
"devDependencies": {
|
|
42
37
|
"@cloudflare/workers-types": "^4.20260527.1",
|
|
38
|
+
"@rolldown/plugin-babel": "^0.2.3",
|
|
43
39
|
"@types/node": "^25.6.0",
|
|
44
40
|
"@typescript/native-preview": "7.0.0-dev.20260412.1",
|
|
45
41
|
"tsdown": "^0.22.0",
|
|
@@ -47,10 +43,10 @@
|
|
|
47
43
|
"vitest": "^4.1.4"
|
|
48
44
|
},
|
|
49
45
|
"peerDependencies": {
|
|
50
|
-
"@cloudflare/think": "
|
|
51
|
-
"@cloudflare/vite-plugin": "
|
|
52
|
-
"@cloudflare/worker-bundler": "
|
|
53
|
-
"agents": "
|
|
54
|
-
"vite": "
|
|
46
|
+
"@cloudflare/think": ">=0.8.4",
|
|
47
|
+
"@cloudflare/vite-plugin": ">=1.40.0",
|
|
48
|
+
"@cloudflare/worker-bundler": ">=0.2.0",
|
|
49
|
+
"agents": ">=0.14.3 <1.0.0",
|
|
50
|
+
"vite": ">=8.0.0"
|
|
55
51
|
}
|
|
56
52
|
}
|
package/dist/cli.d.mts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { };
|
package/dist/cli.mjs
DELETED
|
@@ -1,561 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import * as p from "@clack/prompts";
|
|
4
|
-
import fs from "node:fs";
|
|
5
|
-
//#region src/cli/utils.ts
|
|
6
|
-
function toKebabCase(str) {
|
|
7
|
-
return str.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").toLowerCase();
|
|
8
|
-
}
|
|
9
|
-
function toCamelCase(str) {
|
|
10
|
-
return str.replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "").replace(/^./, (c) => c.toLowerCase());
|
|
11
|
-
}
|
|
12
|
-
function toSnakeCase(str) {
|
|
13
|
-
return str.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/[-\s]+/g, "_").toLowerCase();
|
|
14
|
-
}
|
|
15
|
-
function ensureDir(dir) {
|
|
16
|
-
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
17
|
-
}
|
|
18
|
-
function writeFile(filePath, content) {
|
|
19
|
-
ensureDir(path.dirname(filePath));
|
|
20
|
-
fs.writeFileSync(filePath, content, "utf-8");
|
|
21
|
-
}
|
|
22
|
-
function fileExists(filePath) {
|
|
23
|
-
return fs.existsSync(filePath);
|
|
24
|
-
}
|
|
25
|
-
function findSkills(cwd) {
|
|
26
|
-
const skillsDir = path.join(cwd, "src", "skills");
|
|
27
|
-
if (!fs.existsSync(skillsDir)) return [];
|
|
28
|
-
return fs.readdirSync(skillsDir, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
|
|
29
|
-
}
|
|
30
|
-
function getSrcDir(cwd) {
|
|
31
|
-
return path.join(cwd, "src");
|
|
32
|
-
}
|
|
33
|
-
function readFile(filePath) {
|
|
34
|
-
return fs.readFileSync(filePath, "utf-8");
|
|
35
|
-
}
|
|
36
|
-
function globSync(dir, pattern, results = []) {
|
|
37
|
-
if (!fs.existsSync(dir)) return results;
|
|
38
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
39
|
-
for (const entry of entries) {
|
|
40
|
-
const fullPath = path.join(dir, entry.name);
|
|
41
|
-
if (entry.isDirectory()) {
|
|
42
|
-
if (entry.name !== "node_modules") globSync(fullPath, pattern, results);
|
|
43
|
-
} else if (pattern.test(entry.name)) results.push(fullPath);
|
|
44
|
-
}
|
|
45
|
-
return results;
|
|
46
|
-
}
|
|
47
|
-
const IMPORT_PATTERN = /import\s+\{[^}]*\b(ChatAgentHarness|ChatAgent|Agent)\b[^}]*\}\s+from\s+["']@economic\/agents["']/;
|
|
48
|
-
const CLASS_PATTERN = /class\s+(\w+)\s+extends\s+(ChatAgentHarness|ChatAgent|Agent)(?:<|[\s{])/;
|
|
49
|
-
function parseAgentFromContent(content, filePath) {
|
|
50
|
-
const importMatch = content.match(IMPORT_PATTERN);
|
|
51
|
-
if (!importMatch) return null;
|
|
52
|
-
const importedClasses = importMatch[0];
|
|
53
|
-
const classMatch = content.match(CLASS_PATTERN);
|
|
54
|
-
if (!classMatch) return null;
|
|
55
|
-
const [, className, baseClass] = classMatch;
|
|
56
|
-
if (!importedClasses.includes(baseClass)) return null;
|
|
57
|
-
return {
|
|
58
|
-
path: filePath,
|
|
59
|
-
className,
|
|
60
|
-
baseClass
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
function findAgentFiles(cwd) {
|
|
64
|
-
const tsFiles = globSync(getSrcDir(cwd), /\.tsx?$/);
|
|
65
|
-
const agents = [];
|
|
66
|
-
for (const filePath of tsFiles) {
|
|
67
|
-
const agent = parseAgentFromContent(readFile(filePath), filePath);
|
|
68
|
-
if (agent) agents.push(agent);
|
|
69
|
-
}
|
|
70
|
-
return agents;
|
|
71
|
-
}
|
|
72
|
-
//#endregion
|
|
73
|
-
//#region src/cli/templates/skill.ts
|
|
74
|
-
function generateSkillTemplate(options) {
|
|
75
|
-
const { name, camelName, description, toolImports, toolNames } = options;
|
|
76
|
-
return `import type { Skill } from "@economic/agents";
|
|
77
|
-
${toolImports.length > 0 ? toolImports.map((t) => `import { ${t} } from "./tools/${t}";`).join("\n") + "\n" : ""}
|
|
78
|
-
export const ${camelName}Skill = {
|
|
79
|
-
name: "${name}",
|
|
80
|
-
description: "${description}",
|
|
81
|
-
guidance:
|
|
82
|
-
"TODO: Add guidance for the LLM on how to use this skill's tools. " +
|
|
83
|
-
"Describe the workflow, when to call each tool, and how to present results.",
|
|
84
|
-
tools: {
|
|
85
|
-
${toolNames.length > 0 ? toolNames.map((t) => ` ${t},`).join("\n") : " // Add your tools here"}
|
|
86
|
-
},
|
|
87
|
-
} satisfies Skill;
|
|
88
|
-
`;
|
|
89
|
-
}
|
|
90
|
-
//#endregion
|
|
91
|
-
//#region src/cli/templates/tool.ts
|
|
92
|
-
function generateToolTemplate(options) {
|
|
93
|
-
const { snakeName, description, useContext } = options;
|
|
94
|
-
if (useContext) return `import { tool } from "ai";
|
|
95
|
-
import { z } from "zod";
|
|
96
|
-
import type { AgentToolContext } from "@economic/agents";
|
|
97
|
-
|
|
98
|
-
export const ${snakeName} = tool({
|
|
99
|
-
description: "${description}",
|
|
100
|
-
inputSchema: z.object({
|
|
101
|
-
// Add your input parameters here
|
|
102
|
-
// example: z.string().describe("Description of the parameter"),
|
|
103
|
-
}),
|
|
104
|
-
execute: async (input, { experimental_context }) => {
|
|
105
|
-
const ctx = experimental_context as AgentToolContext;
|
|
106
|
-
|
|
107
|
-
// Implement your tool logic here
|
|
108
|
-
return {
|
|
109
|
-
result: "TODO: implement ${snakeName}",
|
|
110
|
-
};
|
|
111
|
-
},
|
|
112
|
-
});
|
|
113
|
-
`;
|
|
114
|
-
return `import { tool } from "ai";
|
|
115
|
-
import { z } from "zod";
|
|
116
|
-
|
|
117
|
-
export const ${snakeName} = tool({
|
|
118
|
-
description: "${description}",
|
|
119
|
-
inputSchema: z.object({
|
|
120
|
-
// Add your input parameters here
|
|
121
|
-
// example: z.string().describe("Description of the parameter"),
|
|
122
|
-
}),
|
|
123
|
-
execute: async (input) => {
|
|
124
|
-
// Implement your tool logic here
|
|
125
|
-
return {
|
|
126
|
-
result: "TODO: implement ${snakeName}",
|
|
127
|
-
};
|
|
128
|
-
},
|
|
129
|
-
});
|
|
130
|
-
`;
|
|
131
|
-
}
|
|
132
|
-
//#endregion
|
|
133
|
-
//#region src/cli/codegen.ts
|
|
134
|
-
function addImport(content, importName, importPath) {
|
|
135
|
-
const importStatement = `import { ${importName} } from "${importPath}";`;
|
|
136
|
-
const lines = content.split("\n");
|
|
137
|
-
let lastRelativeImportIndex = -1;
|
|
138
|
-
for (let i = 0; i < lines.length; i++) {
|
|
139
|
-
const line = lines[i];
|
|
140
|
-
if (/^import\s+.*from\s+["']\.\//m.test(line)) lastRelativeImportIndex = i;
|
|
141
|
-
}
|
|
142
|
-
if (lastRelativeImportIndex === -1) {
|
|
143
|
-
for (let i = 0; i < lines.length; i++) if (/^import\s+/m.test(lines[i])) lastRelativeImportIndex = i;
|
|
144
|
-
}
|
|
145
|
-
if (lastRelativeImportIndex === -1) return importStatement + "\n" + content;
|
|
146
|
-
lines.splice(lastRelativeImportIndex + 1, 0, importStatement);
|
|
147
|
-
return lines.join("\n");
|
|
148
|
-
}
|
|
149
|
-
function hasComplexPattern(code) {
|
|
150
|
-
return /\.\.\./.test(code) || /\w+\s*\(/.test(code);
|
|
151
|
-
}
|
|
152
|
-
function addToArrayReturn(methodContent, itemName) {
|
|
153
|
-
const returnMatch = methodContent.match(/return\s*\[([^\]]*)\]/s);
|
|
154
|
-
if (!returnMatch) return null;
|
|
155
|
-
const [fullMatch, arrayContent] = returnMatch;
|
|
156
|
-
if (hasComplexPattern(arrayContent)) return null;
|
|
157
|
-
const trimmed = arrayContent.trim();
|
|
158
|
-
const newReturn = `return [${trimmed ? `${trimmed}, ${itemName}` : itemName}]`;
|
|
159
|
-
return methodContent.replace(fullMatch, newReturn);
|
|
160
|
-
}
|
|
161
|
-
function addToObjectReturn(methodContent, itemName) {
|
|
162
|
-
const returnMatch = methodContent.match(/return\s*\{([^}]*)\}/s);
|
|
163
|
-
if (!returnMatch) return null;
|
|
164
|
-
const [fullMatch, objectContent] = returnMatch;
|
|
165
|
-
if (hasComplexPattern(objectContent)) return null;
|
|
166
|
-
const trimmed = objectContent.trim();
|
|
167
|
-
const newReturn = `return {${trimmed ? `${trimmed}, ${itemName}` : ` ${itemName} `}}`;
|
|
168
|
-
return methodContent.replace(fullMatch, newReturn);
|
|
169
|
-
}
|
|
170
|
-
function findMethodBounds(content, methodName) {
|
|
171
|
-
const match = new RegExp(`(${methodName})\\s*\\([^)]*\\)\\s*(?::\\s*[^{]+)?\\s*\\{`, "g").exec(content);
|
|
172
|
-
if (!match) return null;
|
|
173
|
-
const start = match.index;
|
|
174
|
-
let braceCount = 0;
|
|
175
|
-
let inMethod = false;
|
|
176
|
-
let end = start;
|
|
177
|
-
for (let i = start; i < content.length; i++) if (content[i] === "{") {
|
|
178
|
-
braceCount++;
|
|
179
|
-
inMethod = true;
|
|
180
|
-
} else if (content[i] === "}") {
|
|
181
|
-
braceCount--;
|
|
182
|
-
if (inMethod && braceCount === 0) {
|
|
183
|
-
end = i + 1;
|
|
184
|
-
break;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
return {
|
|
188
|
-
start,
|
|
189
|
-
end
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
function updateHarnessMethod(content, methodName, itemName) {
|
|
193
|
-
const bounds = findMethodBounds(content, methodName);
|
|
194
|
-
if (!bounds) return null;
|
|
195
|
-
const methodContent = content.slice(bounds.start, bounds.end);
|
|
196
|
-
let newMethodContent;
|
|
197
|
-
if (methodName === "getSkills") newMethodContent = addToArrayReturn(methodContent, itemName);
|
|
198
|
-
else newMethodContent = addToObjectReturn(methodContent, itemName);
|
|
199
|
-
if (!newMethodContent) return null;
|
|
200
|
-
return content.slice(0, bounds.start) + newMethodContent + content.slice(bounds.end);
|
|
201
|
-
}
|
|
202
|
-
function findClassEnd(content, className) {
|
|
203
|
-
const match = new RegExp(`class\\s+${className}\\s+extends`).exec(content);
|
|
204
|
-
if (!match) return null;
|
|
205
|
-
let braceCount = 0;
|
|
206
|
-
let inClass = false;
|
|
207
|
-
for (let i = match.index; i < content.length; i++) if (content[i] === "{") {
|
|
208
|
-
braceCount++;
|
|
209
|
-
inClass = true;
|
|
210
|
-
} else if (content[i] === "}") {
|
|
211
|
-
braceCount--;
|
|
212
|
-
if (inClass && braceCount === 0) return i;
|
|
213
|
-
}
|
|
214
|
-
return null;
|
|
215
|
-
}
|
|
216
|
-
function addMethodToClass(content, className, methodName, itemName) {
|
|
217
|
-
const classEnd = findClassEnd(content, className);
|
|
218
|
-
if (classEnd === null) return null;
|
|
219
|
-
let methodCode;
|
|
220
|
-
if (methodName === "getSkills") methodCode = `\n ${methodName}() {\n return [${itemName}];\n }\n`;
|
|
221
|
-
else methodCode = `\n ${methodName}() {\n return { ${itemName} };\n }\n`;
|
|
222
|
-
return content.slice(0, classEnd) + methodCode + content.slice(classEnd);
|
|
223
|
-
}
|
|
224
|
-
function updateBuildLLMParams(content, propertyName, itemName) {
|
|
225
|
-
const paramsMatch = content.match(/buildLLMParams\s*\(\s*\{/);
|
|
226
|
-
if (!paramsMatch) return null;
|
|
227
|
-
const paramsStart = paramsMatch.index;
|
|
228
|
-
let braceCount = 0;
|
|
229
|
-
let paramsEnd = paramsStart;
|
|
230
|
-
let inParams = false;
|
|
231
|
-
for (let i = paramsStart; i < content.length; i++) if (content[i] === "{") {
|
|
232
|
-
braceCount++;
|
|
233
|
-
inParams = true;
|
|
234
|
-
} else if (content[i] === "}") {
|
|
235
|
-
braceCount--;
|
|
236
|
-
if (inParams && braceCount === 0) {
|
|
237
|
-
paramsEnd = i + 1;
|
|
238
|
-
break;
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
const paramsContent = content.slice(paramsStart, paramsEnd);
|
|
242
|
-
if (new RegExp(`${propertyName}\\s*:`).test(paramsContent)) {
|
|
243
|
-
const propertyLiteralPattern = new RegExp(`${propertyName}\\s*:\\s*(\\[[^\\]]*\\]|\\{[^}]*\\})`);
|
|
244
|
-
const propertyMatch = paramsContent.match(propertyLiteralPattern);
|
|
245
|
-
if (!propertyMatch) return null;
|
|
246
|
-
const [fullPropertyMatch, propertyValue] = propertyMatch;
|
|
247
|
-
if (hasComplexPattern(propertyValue)) return null;
|
|
248
|
-
let newPropertyValue;
|
|
249
|
-
if (propertyName === "skills") {
|
|
250
|
-
const inner = propertyValue.slice(1, -1).trim();
|
|
251
|
-
newPropertyValue = inner ? `[${inner}, ${itemName}]` : `[${itemName}]`;
|
|
252
|
-
} else {
|
|
253
|
-
const inner = propertyValue.slice(1, -1).trim();
|
|
254
|
-
newPropertyValue = inner ? `{ ${inner}, ${itemName} }` : `{ ${itemName} }`;
|
|
255
|
-
}
|
|
256
|
-
const newPropertyDecl = `${propertyName}: ${newPropertyValue}`;
|
|
257
|
-
const newParamsContent = paramsContent.replace(fullPropertyMatch, newPropertyDecl);
|
|
258
|
-
return content.slice(0, paramsStart) + newParamsContent + content.slice(paramsEnd);
|
|
259
|
-
}
|
|
260
|
-
const insertPoint = paramsStart + paramsMatch[0].length;
|
|
261
|
-
let newProperty;
|
|
262
|
-
if (propertyName === "skills") newProperty = `\n ${propertyName}: [${itemName}],`;
|
|
263
|
-
else newProperty = `\n ${propertyName}: { ${itemName} },`;
|
|
264
|
-
return content.slice(0, insertPoint) + newProperty + content.slice(insertPoint);
|
|
265
|
-
}
|
|
266
|
-
function registerSkill(agent, skillName, skillImportName, skillImportPath, agentFilePath) {
|
|
267
|
-
let content = readFile(agentFilePath);
|
|
268
|
-
const relativePath = path.relative(path.dirname(agentFilePath), skillImportPath).replace(/\.ts$/, "").replace(/^(?!\.)/, "./");
|
|
269
|
-
content = addImport(content, skillImportName, relativePath);
|
|
270
|
-
let updated = null;
|
|
271
|
-
if (agent.baseClass === "ChatAgentHarness") {
|
|
272
|
-
updated = updateHarnessMethod(content, "getSkills", skillImportName);
|
|
273
|
-
if (!updated) updated = addMethodToClass(content, agent.className, "getSkills", skillImportName);
|
|
274
|
-
} else updated = updateBuildLLMParams(content, "skills", skillImportName);
|
|
275
|
-
if (!updated) return {
|
|
276
|
-
success: false,
|
|
277
|
-
message: `Could not auto-register skill. Add manually:\n 1. Import: import { ${skillImportName} } from "${relativePath}";\n 2. Add ${skillImportName} to your skills array`
|
|
278
|
-
};
|
|
279
|
-
writeFile(agentFilePath, updated);
|
|
280
|
-
return {
|
|
281
|
-
success: true,
|
|
282
|
-
message: `Registered ${skillImportName} in ${path.basename(agentFilePath)}`
|
|
283
|
-
};
|
|
284
|
-
}
|
|
285
|
-
function registerTool(agent, toolName, toolImportName, toolImportPath, agentFilePath) {
|
|
286
|
-
let content = readFile(agentFilePath);
|
|
287
|
-
const relativePath = path.relative(path.dirname(agentFilePath), toolImportPath).replace(/\.ts$/, "").replace(/^(?!\.)/, "./");
|
|
288
|
-
content = addImport(content, toolImportName, relativePath);
|
|
289
|
-
let updated = null;
|
|
290
|
-
if (agent.baseClass === "ChatAgentHarness") {
|
|
291
|
-
updated = updateHarnessMethod(content, "getTools", toolImportName);
|
|
292
|
-
if (!updated) updated = addMethodToClass(content, agent.className, "getTools", toolImportName);
|
|
293
|
-
} else updated = updateBuildLLMParams(content, "tools", toolImportName);
|
|
294
|
-
if (!updated) return {
|
|
295
|
-
success: false,
|
|
296
|
-
message: `Could not auto-register tool. Add manually:\n 1. Import: import { ${toolImportName} } from "${relativePath}";\n 2. Add ${toolImportName} to your tools object`
|
|
297
|
-
};
|
|
298
|
-
writeFile(agentFilePath, updated);
|
|
299
|
-
return {
|
|
300
|
-
success: true,
|
|
301
|
-
message: `Registered ${toolImportName} in ${path.basename(agentFilePath)}`
|
|
302
|
-
};
|
|
303
|
-
}
|
|
304
|
-
//#endregion
|
|
305
|
-
//#region src/cli/commands/generate-skill.ts
|
|
306
|
-
async function generateSkill(name) {
|
|
307
|
-
const cwd = process.cwd();
|
|
308
|
-
const kebabName = toKebabCase(name);
|
|
309
|
-
const camelName = toCamelCase(name);
|
|
310
|
-
const skillDir = path.join(getSrcDir(cwd), "skills", kebabName);
|
|
311
|
-
const skillFile = path.join(skillDir, `${kebabName}.ts`);
|
|
312
|
-
p.intro(`Creating skill: ${kebabName}`);
|
|
313
|
-
if (fileExists(skillFile)) {
|
|
314
|
-
p.cancel(`Skill already exists at ${skillFile}`);
|
|
315
|
-
process.exit(1);
|
|
316
|
-
}
|
|
317
|
-
const answers = await p.group({
|
|
318
|
-
description: () => p.text({
|
|
319
|
-
message: "Skill description",
|
|
320
|
-
placeholder: "What does this skill do?",
|
|
321
|
-
validate: (value) => {
|
|
322
|
-
if (!value) return "Description is required";
|
|
323
|
-
}
|
|
324
|
-
}),
|
|
325
|
-
tools: () => p.text({
|
|
326
|
-
message: "Initial tools (comma-separated, leave empty to skip)",
|
|
327
|
-
placeholder: "e.g., calculate_tax, generate_invoice"
|
|
328
|
-
})
|
|
329
|
-
}, { onCancel: () => {
|
|
330
|
-
p.cancel("Operation cancelled");
|
|
331
|
-
process.exit(0);
|
|
332
|
-
} });
|
|
333
|
-
const toolNames = answers.tools ? answers.tools.split(",").map((t) => t.trim()).filter(Boolean).map(toSnakeCase) : [];
|
|
334
|
-
const toolDescriptions = {};
|
|
335
|
-
const toolUseContext = {};
|
|
336
|
-
if (toolNames.length > 0) {
|
|
337
|
-
p.note(`Configuring ${toolNames.length} tool(s)...`);
|
|
338
|
-
for (const toolName of toolNames) {
|
|
339
|
-
const desc = await p.text({
|
|
340
|
-
message: `Description for ${toolName}`,
|
|
341
|
-
placeholder: "What does this tool do?",
|
|
342
|
-
validate: (value) => {
|
|
343
|
-
if (!value) return "Description is required";
|
|
344
|
-
}
|
|
345
|
-
});
|
|
346
|
-
if (p.isCancel(desc)) {
|
|
347
|
-
p.cancel("Operation cancelled");
|
|
348
|
-
process.exit(0);
|
|
349
|
-
}
|
|
350
|
-
toolDescriptions[toolName] = desc;
|
|
351
|
-
const useContext = await p.confirm({
|
|
352
|
-
message: `Does ${toolName} need access to AgentToolContext?`,
|
|
353
|
-
initialValue: false
|
|
354
|
-
});
|
|
355
|
-
if (p.isCancel(useContext)) {
|
|
356
|
-
p.cancel("Operation cancelled");
|
|
357
|
-
process.exit(0);
|
|
358
|
-
}
|
|
359
|
-
toolUseContext[toolName] = useContext;
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
const spinner = p.spinner();
|
|
363
|
-
spinner.start("Creating files...");
|
|
364
|
-
writeFile(skillFile, generateSkillTemplate({
|
|
365
|
-
name: kebabName,
|
|
366
|
-
camelName,
|
|
367
|
-
description: answers.description,
|
|
368
|
-
toolImports: toolNames,
|
|
369
|
-
toolNames
|
|
370
|
-
}));
|
|
371
|
-
const createdFiles = [skillFile];
|
|
372
|
-
for (const toolName of toolNames) {
|
|
373
|
-
const toolFile = path.join(skillDir, "tools", `${toolName}.ts`);
|
|
374
|
-
writeFile(toolFile, generateToolTemplate({
|
|
375
|
-
snakeName: toolName,
|
|
376
|
-
description: toolDescriptions[toolName],
|
|
377
|
-
useContext: toolUseContext[toolName]
|
|
378
|
-
}));
|
|
379
|
-
createdFiles.push(toolFile);
|
|
380
|
-
}
|
|
381
|
-
spinner.stop("Files created");
|
|
382
|
-
p.note(createdFiles.map((f) => ` ${path.relative(cwd, f)}`).join("\n"), "Created files");
|
|
383
|
-
const skillImportName = `${camelName}Skill`;
|
|
384
|
-
const agents = findAgentFiles(cwd);
|
|
385
|
-
let selectedAgent = null;
|
|
386
|
-
if (agents.length === 1) selectedAgent = agents[0];
|
|
387
|
-
else if (agents.length > 1) {
|
|
388
|
-
const choice = await p.select({
|
|
389
|
-
message: "Which agent should this skill be registered with?",
|
|
390
|
-
options: agents.map((a) => ({
|
|
391
|
-
value: a.path,
|
|
392
|
-
label: `${a.className} (${path.relative(cwd, a.path)})`
|
|
393
|
-
}))
|
|
394
|
-
});
|
|
395
|
-
if (p.isCancel(choice)) {
|
|
396
|
-
p.outro(`Skill created. Add ${skillImportName} to your agent manually.`);
|
|
397
|
-
return;
|
|
398
|
-
}
|
|
399
|
-
selectedAgent = agents.find((a) => a.path === choice) ?? null;
|
|
400
|
-
}
|
|
401
|
-
if (selectedAgent) {
|
|
402
|
-
const result = registerSkill(selectedAgent, kebabName, skillImportName, skillFile, selectedAgent.path);
|
|
403
|
-
if (result.success) p.note(result.message, "Registered skill");
|
|
404
|
-
else p.note(result.message, "Manual registration required");
|
|
405
|
-
} else p.note(`Add ${skillImportName} to your agent's getSkills() method`, "No agent found");
|
|
406
|
-
p.outro(`Next steps:
|
|
407
|
-
1. Implement tool logic in tools/*.ts
|
|
408
|
-
2. Update the skill guidance in ${kebabName}.ts`);
|
|
409
|
-
}
|
|
410
|
-
//#endregion
|
|
411
|
-
//#region src/cli/commands/generate-tool.ts
|
|
412
|
-
async function generateTool(name) {
|
|
413
|
-
const cwd = process.cwd();
|
|
414
|
-
const snakeName = toSnakeCase(name);
|
|
415
|
-
const srcDir = getSrcDir(cwd);
|
|
416
|
-
p.intro(`Creating tool: ${snakeName}`);
|
|
417
|
-
const locationOptions = [{
|
|
418
|
-
value: "global",
|
|
419
|
-
label: "Global tool (src/tools/)"
|
|
420
|
-
}, ...findSkills(cwd).map((skill) => ({
|
|
421
|
-
value: `skill:${skill}`,
|
|
422
|
-
label: `In skill: ${skill}`
|
|
423
|
-
}))];
|
|
424
|
-
const answers = await p.group({
|
|
425
|
-
description: () => p.text({
|
|
426
|
-
message: "Tool description",
|
|
427
|
-
placeholder: "What does this tool do?",
|
|
428
|
-
validate: (value) => {
|
|
429
|
-
if (!value) return "Description is required";
|
|
430
|
-
}
|
|
431
|
-
}),
|
|
432
|
-
location: () => p.select({
|
|
433
|
-
message: "Where should this tool be created?",
|
|
434
|
-
options: locationOptions
|
|
435
|
-
}),
|
|
436
|
-
useContext: () => p.confirm({
|
|
437
|
-
message: "Does this tool need access to AgentToolContext?",
|
|
438
|
-
initialValue: false
|
|
439
|
-
})
|
|
440
|
-
}, { onCancel: () => {
|
|
441
|
-
p.cancel("Operation cancelled");
|
|
442
|
-
process.exit(0);
|
|
443
|
-
} });
|
|
444
|
-
let toolFile;
|
|
445
|
-
if (answers.location === "global") toolFile = path.join(srcDir, "tools", `${snakeName}.ts`);
|
|
446
|
-
else {
|
|
447
|
-
const skillName = answers.location.replace("skill:", "");
|
|
448
|
-
toolFile = path.join(srcDir, "skills", skillName, "tools", `${snakeName}.ts`);
|
|
449
|
-
}
|
|
450
|
-
if (fileExists(toolFile)) {
|
|
451
|
-
p.cancel(`Tool already exists at ${toolFile}`);
|
|
452
|
-
process.exit(1);
|
|
453
|
-
}
|
|
454
|
-
const spinner = p.spinner();
|
|
455
|
-
spinner.start("Creating file...");
|
|
456
|
-
const toolContent = generateToolTemplate({
|
|
457
|
-
snakeName,
|
|
458
|
-
description: answers.description,
|
|
459
|
-
useContext: answers.useContext
|
|
460
|
-
});
|
|
461
|
-
writeFile(toolFile, toolContent);
|
|
462
|
-
spinner.stop("File created");
|
|
463
|
-
const relativePath = path.relative(cwd, toolFile);
|
|
464
|
-
p.note(` ${relativePath}`, "Created file");
|
|
465
|
-
if (answers.location === "global") {
|
|
466
|
-
const agents = findAgentFiles(cwd);
|
|
467
|
-
let selectedAgent = null;
|
|
468
|
-
if (agents.length === 1) selectedAgent = agents[0];
|
|
469
|
-
else if (agents.length > 1) {
|
|
470
|
-
const choice = await p.select({
|
|
471
|
-
message: "Which agent should this tool be registered with?",
|
|
472
|
-
options: agents.map((a) => ({
|
|
473
|
-
value: a.path,
|
|
474
|
-
label: `${a.className} (${path.relative(cwd, a.path)})`
|
|
475
|
-
}))
|
|
476
|
-
});
|
|
477
|
-
if (p.isCancel(choice)) {
|
|
478
|
-
p.outro(`Tool created. Add ${snakeName} to your agent manually.`);
|
|
479
|
-
return;
|
|
480
|
-
}
|
|
481
|
-
selectedAgent = agents.find((a) => a.path === choice) ?? null;
|
|
482
|
-
}
|
|
483
|
-
if (selectedAgent) {
|
|
484
|
-
const result = registerTool(selectedAgent, snakeName, snakeName, toolFile, selectedAgent.path);
|
|
485
|
-
if (result.success) p.note(result.message, "Registered tool");
|
|
486
|
-
else p.note(result.message, "Manual registration required");
|
|
487
|
-
} else p.note(`Add ${snakeName} to your agent's getTools() method`, "No agent found");
|
|
488
|
-
p.outro(`Next step: Implement tool logic in ${relativePath}`);
|
|
489
|
-
} else {
|
|
490
|
-
const skillName = answers.location.replace("skill:", "");
|
|
491
|
-
const skillFile = path.join(srcDir, "skills", skillName, `${skillName}.ts`);
|
|
492
|
-
if (fileExists(skillFile)) {
|
|
493
|
-
let skillContent = readFile(skillFile);
|
|
494
|
-
const toolRelativePath = `./tools/${snakeName}`;
|
|
495
|
-
skillContent = addImport(skillContent, snakeName, toolRelativePath);
|
|
496
|
-
const toolsMatch = skillContent.match(/tools:\s*\{([^}]*)\}/s);
|
|
497
|
-
if (toolsMatch) {
|
|
498
|
-
const [fullMatch, toolsContent] = toolsMatch;
|
|
499
|
-
const trimmed = toolsContent.trim();
|
|
500
|
-
const newTools = `tools: {${trimmed ? `${trimmed},\n ${snakeName},` : `\n ${snakeName},\n `}}`;
|
|
501
|
-
skillContent = skillContent.replace(fullMatch, newTools);
|
|
502
|
-
writeFile(skillFile, skillContent);
|
|
503
|
-
p.note(`Added ${snakeName} to ${skillName}.ts`, "Registered tool");
|
|
504
|
-
} else p.note(`Add ${snakeName} to the tools object in src/skills/${skillName}/${skillName}.ts`, "Manual registration required");
|
|
505
|
-
}
|
|
506
|
-
p.outro(`Next step: Implement tool logic in ${relativePath}`);
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
//#endregion
|
|
510
|
-
//#region src/cli/index.ts
|
|
511
|
-
const args = process.argv.slice(2);
|
|
512
|
-
const [command, type, name] = args;
|
|
513
|
-
function printHelp() {
|
|
514
|
-
console.log(`
|
|
515
|
-
Usage: economic-agents <command> <type> <name>
|
|
516
|
-
|
|
517
|
-
Commands:
|
|
518
|
-
generate skill <name> Create a new skill with tools
|
|
519
|
-
generate tool <name> Create a new tool
|
|
520
|
-
|
|
521
|
-
Options:
|
|
522
|
-
--help, -h Show this help message
|
|
523
|
-
--version, -v Show version
|
|
524
|
-
`);
|
|
525
|
-
}
|
|
526
|
-
function printVersion() {
|
|
527
|
-
console.log("0.0.1");
|
|
528
|
-
}
|
|
529
|
-
async function main() {
|
|
530
|
-
if (args.includes("--help") || args.includes("-h")) {
|
|
531
|
-
printHelp();
|
|
532
|
-
process.exit(0);
|
|
533
|
-
}
|
|
534
|
-
if (args.includes("--version") || args.includes("-v")) {
|
|
535
|
-
printVersion();
|
|
536
|
-
process.exit(0);
|
|
537
|
-
}
|
|
538
|
-
if (command !== "generate") {
|
|
539
|
-
printHelp();
|
|
540
|
-
process.exit(1);
|
|
541
|
-
}
|
|
542
|
-
if (type === "skill") {
|
|
543
|
-
if (!name) {
|
|
544
|
-
console.error("Error: skill name is required");
|
|
545
|
-
process.exit(1);
|
|
546
|
-
}
|
|
547
|
-
await generateSkill(name);
|
|
548
|
-
} else if (type === "tool") {
|
|
549
|
-
if (!name) {
|
|
550
|
-
console.error("Error: tool name is required");
|
|
551
|
-
process.exit(1);
|
|
552
|
-
}
|
|
553
|
-
await generateTool(name);
|
|
554
|
-
} else {
|
|
555
|
-
console.error(`Error: unknown type "${type}". Use "skill" or "tool".`);
|
|
556
|
-
process.exit(1);
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
main();
|
|
560
|
-
//#endregion
|
|
561
|
-
export {};
|