@amitdeshmukh/ax-crew 5.0.0 → 7.0.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/.claude/settings.local.json +7 -0
- package/CHANGELOG.md +40 -0
- package/README.md +143 -59
- package/dist/agents/agentConfig.d.ts +6 -6
- package/dist/agents/agentConfig.js +46 -124
- package/dist/agents/compose.d.ts +15 -0
- package/dist/agents/compose.js +58 -0
- package/dist/agents/index.d.ts +22 -4
- package/dist/agents/index.js +24 -5
- package/dist/functions/index.d.ts +11 -0
- package/dist/functions/index.js +11 -1
- package/dist/index.d.ts +25 -8
- package/dist/index.js +26 -1
- package/dist/types.d.ts +53 -6
- package/examples/basic-researcher-writer.ts +5 -3
- package/examples/mcp-agent.ts +20 -43
- package/examples/perplexityDeepSearch.ts +6 -5
- package/examples/providerArgs.ts +2 -1
- package/examples/search-tweets.ts +5 -4
- package/examples/solve-math-problem.ts +7 -5
- package/examples/streaming.ts +2 -2
- package/examples/telemetry-demo.ts +166 -0
- package/package.json +5 -4
- package/src/agents/agentConfig.ts +53 -142
- package/src/agents/compose.ts +80 -0
- package/src/agents/index.ts +30 -8
- package/src/functions/index.ts +11 -1
- package/src/index.ts +25 -11
- package/src/types.ts +59 -21
- package/tests/telemetry.test.ts +81 -0
- package/dist/config/index.d.ts +0 -5
- package/dist/config/index.js +0 -30
- package/src/config/index.ts +0 -40
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
|
|
2
|
+
import { AxCrew } from "../dist/index.js";
|
|
3
|
+
import { AxCrewFunctions } from "../dist/functions/index.js";
|
|
4
|
+
import type { AxCrewConfig } from "../dist/types.js";
|
|
5
|
+
import type { Provider } from "../dist/types.js";
|
|
6
|
+
import "dotenv/config";
|
|
7
|
+
|
|
8
|
+
// Import OpenTelemetry packages
|
|
9
|
+
// Note: In a real project, you would need to install these dependencies:
|
|
10
|
+
// npm install @opentelemetry/api @opentelemetry/sdk-trace-node @opentelemetry/sdk-metrics
|
|
11
|
+
// Optional: npm install @opentelemetry/exporter-jaeger (for Jaeger UI visualization)
|
|
12
|
+
import { metrics, trace } from "@opentelemetry/api";
|
|
13
|
+
import {
|
|
14
|
+
ConsoleSpanExporter,
|
|
15
|
+
SimpleSpanProcessor,
|
|
16
|
+
} from "@opentelemetry/sdk-trace-base";
|
|
17
|
+
import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
|
|
18
|
+
import {
|
|
19
|
+
ConsoleMetricExporter,
|
|
20
|
+
MeterProvider,
|
|
21
|
+
PeriodicExportingMetricReader,
|
|
22
|
+
} from "@opentelemetry/sdk-metrics";
|
|
23
|
+
|
|
24
|
+
// Optional Jaeger import - will be used if available
|
|
25
|
+
let JaegerExporter;
|
|
26
|
+
try {
|
|
27
|
+
JaegerExporter = (await import('@opentelemetry/exporter-jaeger')).JaegerExporter;
|
|
28
|
+
} catch (error) {
|
|
29
|
+
console.log('Jaeger exporter not available. Install with: npm install @opentelemetry/exporter-jaeger');
|
|
30
|
+
console.log('Traces will only be sent to console.');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// --- 1. Setup OpenTelemetry (Console + Optional Jaeger) ---
|
|
34
|
+
|
|
35
|
+
// Set up tracing to console and optionally Jaeger
|
|
36
|
+
const spanProcessors = [new SimpleSpanProcessor(new ConsoleSpanExporter())];
|
|
37
|
+
|
|
38
|
+
// Add Jaeger if available
|
|
39
|
+
if (JaegerExporter) {
|
|
40
|
+
try {
|
|
41
|
+
spanProcessors.push(new SimpleSpanProcessor(new JaegerExporter({
|
|
42
|
+
endpoint: 'http://localhost:14268/api/traces',
|
|
43
|
+
})));
|
|
44
|
+
console.log('Jaeger tracing enabled. View traces at: http://localhost:16686');
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.log('Failed to initialize Jaeger exporter:', error.message);
|
|
47
|
+
console.log('Continuing with console tracing only.');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const tracerProvider = new NodeTracerProvider({
|
|
52
|
+
spanProcessors
|
|
53
|
+
});
|
|
54
|
+
tracerProvider.register(); // This registers it as the global tracer provider
|
|
55
|
+
|
|
56
|
+
// Set up basic metrics to print to console
|
|
57
|
+
const meterProvider = new MeterProvider({
|
|
58
|
+
readers: [
|
|
59
|
+
new PeriodicExportingMetricReader({
|
|
60
|
+
exporter: new ConsoleMetricExporter(),
|
|
61
|
+
exportIntervalMillis: 5000, // Export every 5 seconds
|
|
62
|
+
}),
|
|
63
|
+
],
|
|
64
|
+
});
|
|
65
|
+
metrics.setGlobalMeterProvider(meterProvider);
|
|
66
|
+
|
|
67
|
+
// Get your tracer and meter instances
|
|
68
|
+
const tracer = trace.getTracer("ax-crew-example");
|
|
69
|
+
const meter = metrics.getMeter("ax-crew-example");
|
|
70
|
+
|
|
71
|
+
// --- 2. Define Crew Configuration ---
|
|
72
|
+
|
|
73
|
+
const crewConfig: AxCrewConfig = {
|
|
74
|
+
crew: [
|
|
75
|
+
{
|
|
76
|
+
name: "Researcher",
|
|
77
|
+
description: "Researches a topic using tools and provides a summary.",
|
|
78
|
+
signature: "topic:string -> facts:string[]",
|
|
79
|
+
// Agent 1 uses OpenAI
|
|
80
|
+
provider: "openai" as Provider,
|
|
81
|
+
providerKeyName: "OPENAI_API_KEY",
|
|
82
|
+
ai: {
|
|
83
|
+
model: "gpt-4o-mini",
|
|
84
|
+
temperature: 0.7,
|
|
85
|
+
},
|
|
86
|
+
// Give this agent access to a tool (function)
|
|
87
|
+
functions: ["CurrentDateTime"]
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
name: "Writer",
|
|
91
|
+
description: "Writes a blog post based on provided facts.",
|
|
92
|
+
signature: "facts:string[] -> blogPost:string",
|
|
93
|
+
// Agent 2 uses a different provider (e.g., Google Gemini)
|
|
94
|
+
provider: "google-gemini" as Provider,
|
|
95
|
+
providerKeyName: "GEMINI_API_KEY",
|
|
96
|
+
ai: {
|
|
97
|
+
model: "gemini-flash-latest",
|
|
98
|
+
temperature: 0.7,
|
|
99
|
+
},
|
|
100
|
+
// No tools for this agent, just pure generation
|
|
101
|
+
}
|
|
102
|
+
]
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// --- 3. Run the Crew ---
|
|
106
|
+
|
|
107
|
+
async function main() {
|
|
108
|
+
console.log("Starting AxCrew with Telemetry...");
|
|
109
|
+
|
|
110
|
+
// Initialize AxCrew with the telemetry options
|
|
111
|
+
const crew = new AxCrew(
|
|
112
|
+
crewConfig,
|
|
113
|
+
AxCrewFunctions,
|
|
114
|
+
undefined, // Use default crewId
|
|
115
|
+
{
|
|
116
|
+
telemetry: {
|
|
117
|
+
tracer,
|
|
118
|
+
meter
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
// Initialize agents
|
|
125
|
+
await crew.addAgent("Researcher");
|
|
126
|
+
await crew.addAgent("Writer");
|
|
127
|
+
|
|
128
|
+
const researcher = crew.agents!.get("Researcher")!;
|
|
129
|
+
const writer = crew.agents!.get("Writer")!;
|
|
130
|
+
|
|
131
|
+
// Step 1: Research
|
|
132
|
+
console.log("\n--- Step 1: Researching ---");
|
|
133
|
+
// This call will be traced, including the 'CurrentDateTime' tool usage
|
|
134
|
+
const researchResult = await researcher.forward({
|
|
135
|
+
topic: "The future of AI agents in 2025"
|
|
136
|
+
});
|
|
137
|
+
console.log("Research output:", researchResult.facts);
|
|
138
|
+
|
|
139
|
+
// Step 2: Writing
|
|
140
|
+
console.log("\n--- Step 2: Writing ---");
|
|
141
|
+
// This call will be traced under a different provider
|
|
142
|
+
const writerResult = await writer.forward({
|
|
143
|
+
facts: researchResult.facts
|
|
144
|
+
});
|
|
145
|
+
console.log("Blog Post:\n", writerResult.blogPost);
|
|
146
|
+
|
|
147
|
+
console.log("\n--- Done ---");
|
|
148
|
+
console.log("Check your console output above for OpenTelemetry traces and metrics.");
|
|
149
|
+
if (JaegerExporter) {
|
|
150
|
+
console.log("Check Jaeger UI at http://localhost:16686 for enhanced trace visualization.");
|
|
151
|
+
} else {
|
|
152
|
+
console.log("For enhanced visualization, install Jaeger: npm install @opentelemetry/exporter-jaeger");
|
|
153
|
+
console.log("Then run: docker run -d --name jaeger -p 16686:16686 -p 14268:14268 jaegertracing/all-in-one:latest");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Wait a moment for metrics to export before exiting
|
|
157
|
+
await new Promise(resolve => setTimeout(resolve, 6000));
|
|
158
|
+
|
|
159
|
+
} catch (error) {
|
|
160
|
+
console.error("Error running crew:", error);
|
|
161
|
+
} finally {
|
|
162
|
+
crew.destroy();
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
main().catch(console.error);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@amitdeshmukh/ax-crew",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "7.0.0",
|
|
5
5
|
"description": "Build and launch a crew of AI agents with shared state. Built with axllm.dev",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"scripts": {
|
|
12
12
|
"build": "rm -rf dist && tsc --outDir dist",
|
|
13
13
|
"release": "npm run build && npm publish --access public",
|
|
14
|
-
"test": "vitest",
|
|
14
|
+
"test": "vitest run",
|
|
15
15
|
"test:watch": "vitest watch",
|
|
16
16
|
"test:coverage": "vitest run --coverage",
|
|
17
17
|
"test:ui": "vitest --ui"
|
|
@@ -24,8 +24,9 @@
|
|
|
24
24
|
"uuid": "^10.0.0"
|
|
25
25
|
},
|
|
26
26
|
"peerDependencies": {
|
|
27
|
-
"@ax-llm/ax": "14.0.
|
|
28
|
-
"@ax-llm/ax-tools": "14.0.
|
|
27
|
+
"@ax-llm/ax": "^14.0.36",
|
|
28
|
+
"@ax-llm/ax-tools": "^14.0.36",
|
|
29
|
+
"@opentelemetry/api": "^1.9.0"
|
|
29
30
|
},
|
|
30
31
|
"devDependencies": {
|
|
31
32
|
"@testing-library/jest-dom": "^6.6.3",
|
|
@@ -1,20 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
// Import Ax factory and MCP transports (as exported by current package)
|
|
1
|
+
// Import Ax factory and MCP transports
|
|
3
2
|
import { ai, AxMCPClient, AxMCPHTTPSSETransport, AxMCPStreambleHTTPTransport, AxDefaultCostTracker } from '@ax-llm/ax'
|
|
4
|
-
import type {
|
|
5
|
-
AxAIAzureOpenAIArgs,
|
|
6
|
-
AxAIAnthropicArgs,
|
|
7
|
-
AxAIGoogleGeminiArgs,
|
|
8
|
-
AxAIOpenRouterArgs,
|
|
9
|
-
AxAIOllamaArgs
|
|
10
|
-
} from '@ax-llm/ax';
|
|
11
3
|
import type { AxFunction } from '@ax-llm/ax';
|
|
12
4
|
// STDIO transport from tools package
|
|
13
5
|
import { AxMCPStdioTransport } from '@ax-llm/ax-tools'
|
|
14
|
-
|
|
6
|
+
// Resolve env by provided key name
|
|
15
7
|
import type {
|
|
16
8
|
AgentConfig,
|
|
17
|
-
|
|
9
|
+
AxCrewConfig,
|
|
10
|
+
AxCrewOptions,
|
|
18
11
|
FunctionRegistryType,
|
|
19
12
|
MCPTransportConfig,
|
|
20
13
|
MCPStdioTransportConfig,
|
|
@@ -23,26 +16,6 @@ import type {
|
|
|
23
16
|
} from '../types.js';
|
|
24
17
|
import type { Provider } from '../types.js';
|
|
25
18
|
|
|
26
|
-
// Canonical provider slugs supported by ai() factory
|
|
27
|
-
const PROVIDER_CANONICAL = new Set([
|
|
28
|
-
'openai',
|
|
29
|
-
'anthropic',
|
|
30
|
-
'google-gemini',
|
|
31
|
-
'mistral',
|
|
32
|
-
'groq',
|
|
33
|
-
'cohere',
|
|
34
|
-
'together',
|
|
35
|
-
'deepseek',
|
|
36
|
-
'ollama',
|
|
37
|
-
'huggingface',
|
|
38
|
-
'openrouter',
|
|
39
|
-
'azure-openai',
|
|
40
|
-
'reka',
|
|
41
|
-
'x-grok'
|
|
42
|
-
]);
|
|
43
|
-
|
|
44
|
-
// Provider type lives in src/types.ts
|
|
45
|
-
|
|
46
19
|
// Type guard to check if config is stdio transport
|
|
47
20
|
export function isStdioTransport(config: MCPTransportConfig): config is MCPStdioTransportConfig {
|
|
48
21
|
return 'command' in config;
|
|
@@ -69,53 +42,7 @@ function isConstructor<T>(func: any): func is { new (...args: any[]): T } {
|
|
|
69
42
|
return typeof func === 'function' && 'prototype' in func && 'toFunction' in func.prototype;
|
|
70
43
|
}
|
|
71
44
|
|
|
72
|
-
|
|
73
|
-
* Provides a user-friendly error message for JSON parsing errors
|
|
74
|
-
*/
|
|
75
|
-
const getFormattedJSONError = (error: Error, fileContents: string): string => {
|
|
76
|
-
if (error instanceof SyntaxError) {
|
|
77
|
-
const match = error.message.match(/position (\d+)/);
|
|
78
|
-
const position = match ? parseInt(match[1]) : -1;
|
|
79
|
-
|
|
80
|
-
if (position !== -1) {
|
|
81
|
-
const lines = fileContents.split('\n');
|
|
82
|
-
let currentPos = 0;
|
|
83
|
-
let errorLine = 0;
|
|
84
|
-
let errorColumn = 0;
|
|
85
|
-
|
|
86
|
-
// Find the line and column of the error
|
|
87
|
-
for (let i = 0; i < lines.length; i++) {
|
|
88
|
-
if (currentPos + lines[i].length >= position) {
|
|
89
|
-
errorLine = i + 1;
|
|
90
|
-
errorColumn = position - currentPos + 1;
|
|
91
|
-
break;
|
|
92
|
-
}
|
|
93
|
-
currentPos += lines[i].length + 1; // +1 for the newline character
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const contextLines = lines.slice(Math.max(0, errorLine - 3), errorLine + 2)
|
|
97
|
-
.map((line, idx) => `${errorLine - 2 + idx}: ${line}`).join('\n');
|
|
98
|
-
|
|
99
|
-
return `JSON Parse Error in your agent configuration:
|
|
100
|
-
|
|
101
|
-
Error near line ${errorLine}, column ${errorColumn}
|
|
102
|
-
|
|
103
|
-
Context:
|
|
104
|
-
${contextLines}
|
|
105
|
-
|
|
106
|
-
Common issues to check:
|
|
107
|
-
- Missing or extra commas between properties
|
|
108
|
-
- Missing quotes around property names
|
|
109
|
-
- Unmatched brackets or braces
|
|
110
|
-
- Invalid JSON values
|
|
111
|
-
- Trailing commas (not allowed in JSON)
|
|
112
|
-
|
|
113
|
-
Original error: ${error.message}`;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return `Error parsing agent configuration: ${error.message}`;
|
|
118
|
-
};
|
|
45
|
+
// Removed file/JSON parse helpers to keep browser-safe
|
|
119
46
|
|
|
120
47
|
const initializeMCPServers = async (agentConfigData: AgentConfig): Promise<AxFunction[]> => {
|
|
121
48
|
const mcpServers = agentConfigData.mcpServers;
|
|
@@ -158,32 +85,16 @@ const initializeMCPServers = async (agentConfigData: AgentConfig): Promise<AxFun
|
|
|
158
85
|
};
|
|
159
86
|
|
|
160
87
|
/**
|
|
161
|
-
*
|
|
162
|
-
* @param {
|
|
88
|
+
* Returns the AxCrew config from a direct JSON object. Browser-safe.
|
|
89
|
+
* @param {CrewConfig} input - A JSON object with crew configuration.
|
|
163
90
|
* @returns {Object} The parsed crew config.
|
|
164
91
|
* @throws Will throw an error if reading/parsing fails.
|
|
165
92
|
*/
|
|
166
|
-
const parseCrewConfig = (input:
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
// Handle file path input
|
|
170
|
-
const fileContents = fs.readFileSync(input, 'utf8');
|
|
171
|
-
const parsedConfig = JSON.parse(fileContents) as { crew: AgentConfig[] };
|
|
172
|
-
return parsedConfig;
|
|
173
|
-
} else {
|
|
174
|
-
// Handle direct JSON object input
|
|
175
|
-
return input;
|
|
176
|
-
}
|
|
177
|
-
} catch (e) {
|
|
178
|
-
if (e instanceof Error) {
|
|
179
|
-
if (typeof input === 'string') {
|
|
180
|
-
const formattedError = getFormattedJSONError(e, fs.readFileSync(input, 'utf8'));
|
|
181
|
-
throw new Error(formattedError);
|
|
182
|
-
}
|
|
183
|
-
throw new Error(`Error parsing agent configuration: ${e.message}`);
|
|
184
|
-
}
|
|
185
|
-
throw e;
|
|
93
|
+
const parseCrewConfig = (input: AxCrewConfig): { crew: AgentConfig[] } => {
|
|
94
|
+
if (!input || typeof input !== 'object' || !Array.isArray((input as any).crew)) {
|
|
95
|
+
throw new Error('Invalid crew configuration: expected an object with a crew array');
|
|
186
96
|
}
|
|
97
|
+
return input as { crew: AgentConfig[] };
|
|
187
98
|
};
|
|
188
99
|
|
|
189
100
|
/**
|
|
@@ -191,7 +102,7 @@ const parseCrewConfig = (input: CrewConfigInput): { crew: AgentConfig[] } => {
|
|
|
191
102
|
* and creates an instance of the Agent with the appropriate settings.
|
|
192
103
|
*
|
|
193
104
|
* @param {string} agentName - The identifier for the AI agent to be initialized.
|
|
194
|
-
* @param {
|
|
105
|
+
* @param {AxCrewConfig} crewConfig - A JSON object with crew configuration.
|
|
195
106
|
* @param {FunctionRegistryType} functions - The functions available to the agent.
|
|
196
107
|
* @param {Object} state - The state object for the agent.
|
|
197
108
|
* @returns {Object} An object containing the Agents AI instance, its name, description, signature, functions and subAgentList.
|
|
@@ -200,9 +111,10 @@ const parseCrewConfig = (input: CrewConfigInput): { crew: AgentConfig[] } => {
|
|
|
200
111
|
*/
|
|
201
112
|
const parseAgentConfig = async (
|
|
202
113
|
agentName: string,
|
|
203
|
-
crewConfig:
|
|
114
|
+
crewConfig: AxCrewConfig,
|
|
204
115
|
functions: FunctionRegistryType,
|
|
205
|
-
state: Record<string, any
|
|
116
|
+
state: Record<string, any>,
|
|
117
|
+
options?: AxCrewOptions
|
|
206
118
|
) => {
|
|
207
119
|
try {
|
|
208
120
|
// Retrieve the parameters for the specified AI agent from config
|
|
@@ -211,20 +123,17 @@ const parseAgentConfig = async (
|
|
|
211
123
|
throw new Error(`AI agent with name ${agentName} is not configured`);
|
|
212
124
|
}
|
|
213
125
|
|
|
214
|
-
//
|
|
215
|
-
const lower = agentConfigData.provider.toLowerCase();
|
|
216
|
-
if (!PROVIDER_CANONICAL.has(lower)) {
|
|
217
|
-
throw new Error(`AI provider ${agentConfigData.provider} is not supported. Use one of: ${Array.from(PROVIDER_CANONICAL).join(', ')}`);
|
|
218
|
-
}
|
|
126
|
+
// Normalize provider slug to lowercase and validate via Ax factory
|
|
127
|
+
const lower = String(agentConfigData.provider).toLowerCase() as Provider;
|
|
219
128
|
const provider = lower as Provider;
|
|
220
129
|
|
|
221
|
-
//
|
|
130
|
+
// Resolve API key from user-supplied environment variable name
|
|
222
131
|
let apiKey = '';
|
|
223
132
|
if (agentConfigData.providerKeyName) {
|
|
224
|
-
|
|
225
|
-
|
|
133
|
+
const keyName = agentConfigData.providerKeyName;
|
|
134
|
+
apiKey = resolveApiKey(keyName) || '';
|
|
226
135
|
if (!apiKey) {
|
|
227
|
-
throw new Error(`API key for provider ${agentConfigData.provider} is not set in environment
|
|
136
|
+
throw new Error(`API key '${keyName}' for provider ${agentConfigData.provider} is not set in environment`);
|
|
228
137
|
}
|
|
229
138
|
} else {
|
|
230
139
|
throw new Error(`Provider key name is missing in the agent configuration`);
|
|
@@ -242,7 +151,10 @@ const parseAgentConfig = async (
|
|
|
242
151
|
debug: agentConfigData.debug || false,
|
|
243
152
|
...agentConfigData.options,
|
|
244
153
|
// Attach default cost tracker so usage/costs are recorded by provider layer
|
|
245
|
-
trackers: [costTracker]
|
|
154
|
+
trackers: [costTracker],
|
|
155
|
+
// Inject telemetry if provided
|
|
156
|
+
tracer: options?.telemetry?.tracer,
|
|
157
|
+
meter: options?.telemetry?.meter
|
|
246
158
|
}
|
|
247
159
|
};
|
|
248
160
|
if (agentConfigData.apiURL) {
|
|
@@ -253,37 +165,19 @@ const parseAgentConfig = async (
|
|
|
253
165
|
throw new Error(`Invalid apiURL provided: ${agentConfigData.apiURL}`);
|
|
254
166
|
}
|
|
255
167
|
}
|
|
256
|
-
// Forward provider-specific arguments
|
|
168
|
+
// Forward provider-specific arguments as-is; let Ax validate/ignore as needed
|
|
257
169
|
const providerArgs = (agentConfigData as any).providerArgs;
|
|
258
|
-
if (
|
|
259
|
-
type AzureArgs = Pick<AxAIAzureOpenAIArgs<string>, 'resourceName' | 'deploymentName' | 'version'>;
|
|
260
|
-
const az: Partial<AzureArgs> = providerArgs ?? {};
|
|
261
|
-
// If users supplied apiURL instead of resourceName, accept it (Ax supports full URL as resourceName)
|
|
262
|
-
if (!az.resourceName && agentConfigData.apiURL) {
|
|
263
|
-
az.resourceName = agentConfigData.apiURL as any;
|
|
264
|
-
}
|
|
265
|
-
Object.assign(aiArgs, az);
|
|
266
|
-
} else if (provider === 'anthropic') {
|
|
267
|
-
type AnthropicArgs = Pick<AxAIAnthropicArgs<string>, 'projectId' | 'region'>;
|
|
268
|
-
const an: Partial<AnthropicArgs> = providerArgs ?? {};
|
|
269
|
-
Object.assign(aiArgs, an);
|
|
270
|
-
} else if (provider === 'google-gemini') {
|
|
271
|
-
type GeminiArgs = Pick<AxAIGoogleGeminiArgs<string>, 'projectId' | 'region' | 'endpointId'>;
|
|
272
|
-
const g: Partial<GeminiArgs> = providerArgs ?? {};
|
|
273
|
-
Object.assign(aiArgs, g);
|
|
274
|
-
} else if (provider === 'openrouter') {
|
|
275
|
-
type OpenRouterArgs = Pick<AxAIOpenRouterArgs<string>, 'referer' | 'title'>;
|
|
276
|
-
const o: Partial<OpenRouterArgs> = providerArgs ?? {};
|
|
277
|
-
Object.assign(aiArgs, o);
|
|
278
|
-
} else if (provider === 'ollama') {
|
|
279
|
-
type OllamaArgs = Pick<AxAIOllamaArgs<string>, 'url'>;
|
|
280
|
-
const ol: Partial<OllamaArgs> = providerArgs ?? {};
|
|
281
|
-
Object.assign(aiArgs, ol);
|
|
282
|
-
} else if (providerArgs && typeof providerArgs === 'object') {
|
|
283
|
-
// Generic pass-through for other providers if needed in the future
|
|
170
|
+
if (providerArgs && typeof providerArgs === 'object') {
|
|
284
171
|
Object.assign(aiArgs, providerArgs);
|
|
285
172
|
}
|
|
286
|
-
|
|
173
|
+
// Validate provider by attempting instantiation; Ax will throw on unknown providers
|
|
174
|
+
let aiInstance;
|
|
175
|
+
try {
|
|
176
|
+
aiInstance = ai(aiArgs);
|
|
177
|
+
} catch (e) {
|
|
178
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
179
|
+
throw new Error(`Unsupported provider '${provider}': ${msg}`);
|
|
180
|
+
}
|
|
287
181
|
|
|
288
182
|
// If an mcpServers config is provided in the agent config, convert to functions
|
|
289
183
|
const mcpFunctions = await initializeMCPServers(agentConfigData);
|
|
@@ -328,4 +222,21 @@ const parseAgentConfig = async (
|
|
|
328
222
|
export {
|
|
329
223
|
parseAgentConfig,
|
|
330
224
|
parseCrewConfig
|
|
331
|
-
};
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
function resolveApiKey(varName: string): string | undefined {
|
|
228
|
+
try {
|
|
229
|
+
// Prefer Node env when available
|
|
230
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
231
|
+
// @ts-ignore
|
|
232
|
+
if (typeof process !== 'undefined' && process?.env) {
|
|
233
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
234
|
+
// @ts-ignore
|
|
235
|
+
return process.env[varName];
|
|
236
|
+
}
|
|
237
|
+
// Fallback: allow global exposure in browser builds (e.g., injected at runtime)
|
|
238
|
+
return (globalThis as any)?.[varName];
|
|
239
|
+
} catch {
|
|
240
|
+
return undefined;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { ai } from '@ax-llm/ax';
|
|
2
|
+
import type { AxAI } from '@ax-llm/ax';
|
|
3
|
+
import type { AxCrewConfig } from '../types.js';
|
|
4
|
+
|
|
5
|
+
type BuildProviderArgs = {
|
|
6
|
+
provider: string;
|
|
7
|
+
apiKey: string;
|
|
8
|
+
config: any;
|
|
9
|
+
apiURL?: string;
|
|
10
|
+
providerArgs?: Record<string, unknown>;
|
|
11
|
+
options?: Record<string, unknown>;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function instantiateProvider({
|
|
15
|
+
provider,
|
|
16
|
+
apiKey,
|
|
17
|
+
config,
|
|
18
|
+
apiURL,
|
|
19
|
+
providerArgs,
|
|
20
|
+
options,
|
|
21
|
+
}: BuildProviderArgs): AxAI<any> {
|
|
22
|
+
const args: any = { name: provider as any, apiKey, config, options };
|
|
23
|
+
if (apiURL) args.apiURL = apiURL;
|
|
24
|
+
if (providerArgs && typeof providerArgs === 'object') Object.assign(args, providerArgs);
|
|
25
|
+
return ai(args) as unknown as AxAI<any>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function buildProvidersFromConfig(cfg: AxCrewConfig): AxAI<any>[] {
|
|
29
|
+
const services: AxAI<any>[] = [];
|
|
30
|
+
for (const agent of cfg.crew) {
|
|
31
|
+
const apiKeyName = agent.providerKeyName;
|
|
32
|
+
if (!apiKeyName) throw new Error(`Provider key name is missing for agent ${agent.name}`);
|
|
33
|
+
const apiKey = resolveApiKey(apiKeyName) || '';
|
|
34
|
+
if (!apiKey) throw new Error(`API key '${apiKeyName}' not set for agent ${agent.name}`);
|
|
35
|
+
|
|
36
|
+
const service = instantiateProvider({
|
|
37
|
+
provider: String(agent.provider).toLowerCase(),
|
|
38
|
+
apiKey,
|
|
39
|
+
config: agent.ai,
|
|
40
|
+
apiURL: agent.apiURL,
|
|
41
|
+
providerArgs: (agent as any).providerArgs,
|
|
42
|
+
options: agent.options,
|
|
43
|
+
});
|
|
44
|
+
services.push(service);
|
|
45
|
+
}
|
|
46
|
+
return services;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
// Provider discovery helpers consolidated here (previously in src/providers.ts)
|
|
51
|
+
export function discoverProvidersFromConfig(cfg: AxCrewConfig): string[] {
|
|
52
|
+
const providers = new Set<string>();
|
|
53
|
+
for (const agent of cfg.crew) {
|
|
54
|
+
providers.add(String(agent.provider).toLowerCase());
|
|
55
|
+
}
|
|
56
|
+
return Array.from(providers);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function listSelectableProviders(cfg: AxCrewConfig): string[] {
|
|
60
|
+
return discoverProvidersFromConfig(cfg);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function resolveApiKey(varName: string): string | undefined {
|
|
64
|
+
try {
|
|
65
|
+
// Prefer Node env when available
|
|
66
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
67
|
+
// @ts-ignore
|
|
68
|
+
if (typeof process !== 'undefined' && process?.env) {
|
|
69
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
70
|
+
// @ts-ignore
|
|
71
|
+
return process.env[varName];
|
|
72
|
+
}
|
|
73
|
+
// Fallback: allow global exposure in browser builds (e.g., injected at runtime)
|
|
74
|
+
return (globalThis as any)?.[varName];
|
|
75
|
+
} catch {
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
package/src/agents/index.ts
CHANGED
|
@@ -14,7 +14,8 @@ import type {
|
|
|
14
14
|
StateInstance,
|
|
15
15
|
FunctionRegistryType,
|
|
16
16
|
UsageCost,
|
|
17
|
-
|
|
17
|
+
AxCrewConfig,
|
|
18
|
+
AxCrewOptions,
|
|
18
19
|
MCPTransportConfig,
|
|
19
20
|
} from "../types.js";
|
|
20
21
|
|
|
@@ -260,10 +261,27 @@ class StatefulAxAgent extends AxAgent<any, any> {
|
|
|
260
261
|
}
|
|
261
262
|
|
|
262
263
|
/**
|
|
263
|
-
*
|
|
264
|
+
* AxCrew orchestrates a set of Ax agents that share state,
|
|
265
|
+
* tools (functions), optional MCP servers, streaming, and a built-in metrics
|
|
266
|
+
* registry for tokens, requests, and estimated cost.
|
|
267
|
+
*
|
|
268
|
+
* Typical usage:
|
|
269
|
+
* const crew = new AxCrew(config, AxCrewFunctions)
|
|
270
|
+
* await crew.addAllAgents()
|
|
271
|
+
* const planner = crew.agents?.get("Planner")
|
|
272
|
+
* const res = await planner?.forward({ task: "Plan something" })
|
|
273
|
+
*
|
|
274
|
+
* Key behaviors:
|
|
275
|
+
* - Validates and instantiates agents from a config-first model
|
|
276
|
+
* - Shares a mutable state object across all agents in the crew
|
|
277
|
+
* - Supports sub-agents and a function registry per agent
|
|
278
|
+
* - Tracks per-agent and crew-level metrics via MetricsRegistry
|
|
279
|
+
* - Provides helpers to add agents (individually, a subset, or all) and
|
|
280
|
+
* to reset metrics/costs when needed
|
|
264
281
|
*/
|
|
265
282
|
class AxCrew {
|
|
266
|
-
private crewConfig:
|
|
283
|
+
private crewConfig: AxCrewConfig;
|
|
284
|
+
private options?: AxCrewOptions;
|
|
267
285
|
functionsRegistry: FunctionRegistryType = {};
|
|
268
286
|
crewId: string;
|
|
269
287
|
agents: Map<string, StatefulAxAgent> | null;
|
|
@@ -271,14 +289,16 @@ class AxCrew {
|
|
|
271
289
|
|
|
272
290
|
/**
|
|
273
291
|
* Creates an instance of AxCrew.
|
|
274
|
-
* @param {
|
|
292
|
+
* @param {AxCrewConfig} crewConfig - JSON object with crew configuration.
|
|
275
293
|
* @param {FunctionRegistryType} [functionsRegistry={}] - The registry of functions to use in the crew.
|
|
294
|
+
* @param {AxCrewOptions} [options] - Optional settings for the crew (e.g., telemetry).
|
|
276
295
|
* @param {string} [crewId=uuidv4()] - The unique identifier for the crew.
|
|
277
296
|
*/
|
|
278
297
|
constructor(
|
|
279
|
-
crewConfig:
|
|
298
|
+
crewConfig: AxCrewConfig,
|
|
280
299
|
functionsRegistry: FunctionRegistryType = {},
|
|
281
|
-
|
|
300
|
+
options?: AxCrewOptions,
|
|
301
|
+
crewId: string = uuidv4(),
|
|
282
302
|
) {
|
|
283
303
|
// Basic validation of crew configuration
|
|
284
304
|
if (!crewConfig || typeof crewConfig !== 'object' || !('crew' in crewConfig)) {
|
|
@@ -286,7 +306,7 @@ class AxCrew {
|
|
|
286
306
|
}
|
|
287
307
|
|
|
288
308
|
// Validate each agent in the crew
|
|
289
|
-
crewConfig.crew.forEach(agent => {
|
|
309
|
+
crewConfig.crew.forEach((agent: any) => {
|
|
290
310
|
if (!agent.name || agent.name.trim() === '') {
|
|
291
311
|
throw new Error('Agent name cannot be empty');
|
|
292
312
|
}
|
|
@@ -295,6 +315,7 @@ class AxCrew {
|
|
|
295
315
|
this.crewConfig = crewConfig;
|
|
296
316
|
this.functionsRegistry = functionsRegistry;
|
|
297
317
|
this.crewId = crewId;
|
|
318
|
+
this.options = options;
|
|
298
319
|
this.agents = new Map<string, StatefulAxAgent>();
|
|
299
320
|
this.state = createState(crewId);
|
|
300
321
|
// Make crewId discoverable to metrics
|
|
@@ -313,7 +334,8 @@ class AxCrew {
|
|
|
313
334
|
agentName,
|
|
314
335
|
this.crewConfig,
|
|
315
336
|
this.functionsRegistry,
|
|
316
|
-
this.state
|
|
337
|
+
this.state,
|
|
338
|
+
this.options
|
|
317
339
|
);
|
|
318
340
|
|
|
319
341
|
// Destructure with type assertion
|
package/src/functions/index.ts
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import { CurrentDateTime, DaysBetweenDates } from './dateTime.js';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Built-in function registry for AxCrew agents.
|
|
5
|
+
*
|
|
6
|
+
* Contains common utility tools/functions that can be referenced by name from
|
|
7
|
+
* agent configs (e.g., "functions": ["CurrentDateTime", "DaysBetweenDates"]).
|
|
8
|
+
* You can pass this object to the AxCrew constructor or merge with your
|
|
9
|
+
* own registry.
|
|
10
|
+
* Example:
|
|
11
|
+
* const crew = new AxCrew(config, AxCrewFunctions); or
|
|
12
|
+
* const crew = new AxCrew(config, { ...AxCrewFunctions, ...myFunctions });
|
|
13
|
+
*/
|
|
4
14
|
const AxCrewFunctions = {
|
|
5
15
|
CurrentDateTime,
|
|
6
16
|
DaysBetweenDates
|