@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 CHANGED
@@ -571,5 +571,3 @@ npm install
571
571
  npm test
572
572
  npm run build
573
573
  ```
574
-
575
- .
@@ -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 createAnthropicVertexProvider(options: AnthropicVertexProviderOptions): (modelId: AnthropicVertexModelId) => AnthropicMessagesLanguageModel;
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) => _$_ai_sdk_anthropic_internal0.AnthropicMessagesLanguageModel;
34
- gemini: _$_ai_sdk_google0.GoogleGenerativeAIProvider;
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, createAnthropicVertexProvider, createGeminiProvider };
35
+ export { AiGatewayVertexProvidersOptions, type AnthropicVertexModelId, type AnthropicVertexProviderOptions, type GeminiModelId, type GeminiProviderOptions, createAiGatewayVertexProviders, createAnthropicProvider, createGeminiProvider };
@@ -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 createAnthropicVertexProvider(options) {
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: { "cf-aig-authorization": `Bearer ${cloudflareApiToken}` },
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: { "cf-aig-authorization": `Bearer ${cloudflareApiToken}` },
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: createAnthropicVertexProvider(options),
45
+ anthropic: createAnthropicProvider(options),
42
46
  gemini: createGeminiProvider(options)
43
47
  };
44
48
  }
45
49
  //#endregion
46
- export { createAiGatewayVertexProviders, createAnthropicVertexProvider, createGeminiProvider };
50
+ export { createAiGatewayVertexProviders, createAnthropicProvider, createGeminiProvider };
package/package.json CHANGED
@@ -1,11 +1,8 @@
1
1
  {
2
2
  "name": "@economic/agents",
3
- "version": "2.3.2",
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": "^0.8.4",
29
+ "@cloudflare/think": ">=0.8.4",
35
30
  "@opentelemetry/sdk-trace-base": "^2.7.1",
36
- "agents": "^0.14.3",
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": "^0.8.4",
51
- "@cloudflare/vite-plugin": "^1.40.0",
52
- "@cloudflare/worker-bundler": "^0.2.0",
53
- "agents": "^0.14.3",
54
- "vite": "^8.0.0"
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 {};