@amitdeshmukh/ax-crew 4.1.2 → 6.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/CHANGELOG.md +43 -0
- package/README.md +8 -51
- package/dist/agents/agentConfig.d.ts +6 -6
- package/dist/agents/agentConfig.js +45 -96
- package/dist/agents/agentUseCosts.d.ts +14 -0
- package/dist/agents/agentUseCosts.js +14 -0
- package/dist/agents/compose.d.ts +15 -0
- package/dist/agents/compose.js +58 -0
- package/dist/agents/index.d.ts +42 -5
- package/dist/agents/index.js +41 -4
- 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/metrics/registry.d.ts +27 -0
- package/dist/metrics/registry.js +27 -0
- package/dist/types.d.ts +45 -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 +42 -0
- package/examples/search-tweets.ts +5 -4
- package/examples/solve-math-problem.ts +7 -5
- package/examples/streaming.ts +2 -2
- package/package.json +4 -4
- package/src/agents/agentConfig.ts +49 -106
- package/src/agents/agentUseCosts.ts +14 -0
- package/src/agents/compose.ts +80 -0
- package/src/agents/index.ts +44 -7
- package/src/functions/index.ts +11 -1
- package/src/index.ts +25 -11
- package/src/metrics/registry.ts +27 -0
- package/src/types.ts +49 -21
- package/dist/config/index.d.ts +0 -5
- package/dist/config/index.js +0 -30
- package/src/config/index.ts +0 -40
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@amitdeshmukh/ax-crew",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "6.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,8 @@
|
|
|
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
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@testing-library/jest-dom": "^6.6.3",
|
|
@@ -1,13 +1,12 @@
|
|
|
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
3
|
import type { AxFunction } from '@ax-llm/ax';
|
|
5
4
|
// STDIO transport from tools package
|
|
6
5
|
import { AxMCPStdioTransport } from '@ax-llm/ax-tools'
|
|
7
|
-
|
|
6
|
+
// Resolve env by provided key name
|
|
8
7
|
import type {
|
|
9
8
|
AgentConfig,
|
|
10
|
-
|
|
9
|
+
AxCrewConfig,
|
|
11
10
|
FunctionRegistryType,
|
|
12
11
|
MCPTransportConfig,
|
|
13
12
|
MCPStdioTransportConfig,
|
|
@@ -16,26 +15,6 @@ import type {
|
|
|
16
15
|
} from '../types.js';
|
|
17
16
|
import type { Provider } from '../types.js';
|
|
18
17
|
|
|
19
|
-
// Canonical provider slugs supported by ai() factory
|
|
20
|
-
const PROVIDER_CANONICAL = new Set([
|
|
21
|
-
'openai',
|
|
22
|
-
'anthropic',
|
|
23
|
-
'google-gemini',
|
|
24
|
-
'mistral',
|
|
25
|
-
'groq',
|
|
26
|
-
'cohere',
|
|
27
|
-
'together',
|
|
28
|
-
'deepseek',
|
|
29
|
-
'ollama',
|
|
30
|
-
'huggingface',
|
|
31
|
-
'openrouter',
|
|
32
|
-
'azure-openai',
|
|
33
|
-
'reka',
|
|
34
|
-
'x-grok'
|
|
35
|
-
]);
|
|
36
|
-
|
|
37
|
-
// Provider type lives in src/types.ts
|
|
38
|
-
|
|
39
18
|
// Type guard to check if config is stdio transport
|
|
40
19
|
export function isStdioTransport(config: MCPTransportConfig): config is MCPStdioTransportConfig {
|
|
41
20
|
return 'command' in config;
|
|
@@ -62,53 +41,7 @@ function isConstructor<T>(func: any): func is { new (...args: any[]): T } {
|
|
|
62
41
|
return typeof func === 'function' && 'prototype' in func && 'toFunction' in func.prototype;
|
|
63
42
|
}
|
|
64
43
|
|
|
65
|
-
|
|
66
|
-
* Provides a user-friendly error message for JSON parsing errors
|
|
67
|
-
*/
|
|
68
|
-
const getFormattedJSONError = (error: Error, fileContents: string): string => {
|
|
69
|
-
if (error instanceof SyntaxError) {
|
|
70
|
-
const match = error.message.match(/position (\d+)/);
|
|
71
|
-
const position = match ? parseInt(match[1]) : -1;
|
|
72
|
-
|
|
73
|
-
if (position !== -1) {
|
|
74
|
-
const lines = fileContents.split('\n');
|
|
75
|
-
let currentPos = 0;
|
|
76
|
-
let errorLine = 0;
|
|
77
|
-
let errorColumn = 0;
|
|
78
|
-
|
|
79
|
-
// Find the line and column of the error
|
|
80
|
-
for (let i = 0; i < lines.length; i++) {
|
|
81
|
-
if (currentPos + lines[i].length >= position) {
|
|
82
|
-
errorLine = i + 1;
|
|
83
|
-
errorColumn = position - currentPos + 1;
|
|
84
|
-
break;
|
|
85
|
-
}
|
|
86
|
-
currentPos += lines[i].length + 1; // +1 for the newline character
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const contextLines = lines.slice(Math.max(0, errorLine - 3), errorLine + 2)
|
|
90
|
-
.map((line, idx) => `${errorLine - 2 + idx}: ${line}`).join('\n');
|
|
91
|
-
|
|
92
|
-
return `JSON Parse Error in your agent configuration:
|
|
93
|
-
|
|
94
|
-
Error near line ${errorLine}, column ${errorColumn}
|
|
95
|
-
|
|
96
|
-
Context:
|
|
97
|
-
${contextLines}
|
|
98
|
-
|
|
99
|
-
Common issues to check:
|
|
100
|
-
- Missing or extra commas between properties
|
|
101
|
-
- Missing quotes around property names
|
|
102
|
-
- Unmatched brackets or braces
|
|
103
|
-
- Invalid JSON values
|
|
104
|
-
- Trailing commas (not allowed in JSON)
|
|
105
|
-
|
|
106
|
-
Original error: ${error.message}`;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return `Error parsing agent configuration: ${error.message}`;
|
|
111
|
-
};
|
|
44
|
+
// Removed file/JSON parse helpers to keep browser-safe
|
|
112
45
|
|
|
113
46
|
const initializeMCPServers = async (agentConfigData: AgentConfig): Promise<AxFunction[]> => {
|
|
114
47
|
const mcpServers = agentConfigData.mcpServers;
|
|
@@ -151,32 +84,16 @@ const initializeMCPServers = async (agentConfigData: AgentConfig): Promise<AxFun
|
|
|
151
84
|
};
|
|
152
85
|
|
|
153
86
|
/**
|
|
154
|
-
*
|
|
155
|
-
* @param {
|
|
87
|
+
* Returns the AxCrew config from a direct JSON object. Browser-safe.
|
|
88
|
+
* @param {CrewConfig} input - A JSON object with crew configuration.
|
|
156
89
|
* @returns {Object} The parsed crew config.
|
|
157
90
|
* @throws Will throw an error if reading/parsing fails.
|
|
158
91
|
*/
|
|
159
|
-
const parseCrewConfig = (input:
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
// Handle file path input
|
|
163
|
-
const fileContents = fs.readFileSync(input, 'utf8');
|
|
164
|
-
const parsedConfig = JSON.parse(fileContents) as { crew: AgentConfig[] };
|
|
165
|
-
return parsedConfig;
|
|
166
|
-
} else {
|
|
167
|
-
// Handle direct JSON object input
|
|
168
|
-
return input;
|
|
169
|
-
}
|
|
170
|
-
} catch (e) {
|
|
171
|
-
if (e instanceof Error) {
|
|
172
|
-
if (typeof input === 'string') {
|
|
173
|
-
const formattedError = getFormattedJSONError(e, fs.readFileSync(input, 'utf8'));
|
|
174
|
-
throw new Error(formattedError);
|
|
175
|
-
}
|
|
176
|
-
throw new Error(`Error parsing agent configuration: ${e.message}`);
|
|
177
|
-
}
|
|
178
|
-
throw e;
|
|
92
|
+
const parseCrewConfig = (input: AxCrewConfig): { crew: AgentConfig[] } => {
|
|
93
|
+
if (!input || typeof input !== 'object' || !Array.isArray((input as any).crew)) {
|
|
94
|
+
throw new Error('Invalid crew configuration: expected an object with a crew array');
|
|
179
95
|
}
|
|
96
|
+
return input as { crew: AgentConfig[] };
|
|
180
97
|
};
|
|
181
98
|
|
|
182
99
|
/**
|
|
@@ -184,7 +101,7 @@ const parseCrewConfig = (input: CrewConfigInput): { crew: AgentConfig[] } => {
|
|
|
184
101
|
* and creates an instance of the Agent with the appropriate settings.
|
|
185
102
|
*
|
|
186
103
|
* @param {string} agentName - The identifier for the AI agent to be initialized.
|
|
187
|
-
* @param {
|
|
104
|
+
* @param {AxCrewConfig} crewConfig - A JSON object with crew configuration.
|
|
188
105
|
* @param {FunctionRegistryType} functions - The functions available to the agent.
|
|
189
106
|
* @param {Object} state - The state object for the agent.
|
|
190
107
|
* @returns {Object} An object containing the Agents AI instance, its name, description, signature, functions and subAgentList.
|
|
@@ -193,7 +110,7 @@ const parseCrewConfig = (input: CrewConfigInput): { crew: AgentConfig[] } => {
|
|
|
193
110
|
*/
|
|
194
111
|
const parseAgentConfig = async (
|
|
195
112
|
agentName: string,
|
|
196
|
-
crewConfig:
|
|
113
|
+
crewConfig: AxCrewConfig,
|
|
197
114
|
functions: FunctionRegistryType,
|
|
198
115
|
state: Record<string, any>
|
|
199
116
|
) => {
|
|
@@ -204,20 +121,17 @@ const parseAgentConfig = async (
|
|
|
204
121
|
throw new Error(`AI agent with name ${agentName} is not configured`);
|
|
205
122
|
}
|
|
206
123
|
|
|
207
|
-
//
|
|
208
|
-
const lower = agentConfigData.provider.toLowerCase();
|
|
209
|
-
if (!PROVIDER_CANONICAL.has(lower)) {
|
|
210
|
-
throw new Error(`AI provider ${agentConfigData.provider} is not supported. Use one of: ${Array.from(PROVIDER_CANONICAL).join(', ')}`);
|
|
211
|
-
}
|
|
124
|
+
// Normalize provider slug to lowercase and validate via Ax factory
|
|
125
|
+
const lower = String(agentConfigData.provider).toLowerCase() as Provider;
|
|
212
126
|
const provider = lower as Provider;
|
|
213
127
|
|
|
214
|
-
//
|
|
128
|
+
// Resolve API key from user-supplied environment variable name
|
|
215
129
|
let apiKey = '';
|
|
216
130
|
if (agentConfigData.providerKeyName) {
|
|
217
|
-
|
|
218
|
-
|
|
131
|
+
const keyName = agentConfigData.providerKeyName;
|
|
132
|
+
apiKey = resolveApiKey(keyName) || '';
|
|
219
133
|
if (!apiKey) {
|
|
220
|
-
throw new Error(`API key for provider ${agentConfigData.provider} is not set in environment
|
|
134
|
+
throw new Error(`API key '${keyName}' for provider ${agentConfigData.provider} is not set in environment`);
|
|
221
135
|
}
|
|
222
136
|
} else {
|
|
223
137
|
throw new Error(`Provider key name is missing in the agent configuration`);
|
|
@@ -246,7 +160,19 @@ const parseAgentConfig = async (
|
|
|
246
160
|
throw new Error(`Invalid apiURL provided: ${agentConfigData.apiURL}`);
|
|
247
161
|
}
|
|
248
162
|
}
|
|
249
|
-
|
|
163
|
+
// Forward provider-specific arguments as-is; let Ax validate/ignore as needed
|
|
164
|
+
const providerArgs = (agentConfigData as any).providerArgs;
|
|
165
|
+
if (providerArgs && typeof providerArgs === 'object') {
|
|
166
|
+
Object.assign(aiArgs, providerArgs);
|
|
167
|
+
}
|
|
168
|
+
// Validate provider by attempting instantiation; Ax will throw on unknown providers
|
|
169
|
+
let aiInstance;
|
|
170
|
+
try {
|
|
171
|
+
aiInstance = ai(aiArgs);
|
|
172
|
+
} catch (e) {
|
|
173
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
174
|
+
throw new Error(`Unsupported provider '${provider}': ${msg}`);
|
|
175
|
+
}
|
|
250
176
|
|
|
251
177
|
// If an mcpServers config is provided in the agent config, convert to functions
|
|
252
178
|
const mcpFunctions = await initializeMCPServers(agentConfigData);
|
|
@@ -291,4 +217,21 @@ const parseAgentConfig = async (
|
|
|
291
217
|
export {
|
|
292
218
|
parseAgentConfig,
|
|
293
219
|
parseCrewConfig
|
|
294
|
-
};
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
function resolveApiKey(varName: string): string | undefined {
|
|
223
|
+
try {
|
|
224
|
+
// Prefer Node env when available
|
|
225
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
226
|
+
// @ts-ignore
|
|
227
|
+
if (typeof process !== 'undefined' && process?.env) {
|
|
228
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
229
|
+
// @ts-ignore
|
|
230
|
+
return process.env[varName];
|
|
231
|
+
}
|
|
232
|
+
// Fallback: allow global exposure in browser builds (e.g., injected at runtime)
|
|
233
|
+
return (globalThis as any)?.[varName];
|
|
234
|
+
} catch {
|
|
235
|
+
return undefined;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
@@ -15,6 +15,10 @@ import type {
|
|
|
15
15
|
export class StateFulAxAgentUsage {
|
|
16
16
|
static STATE_KEY_PREFIX = 'agent_usage_';
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Compute usage costs given a model usage record and model pricing info.
|
|
20
|
+
* Returns null if inputs are invalid. Token-based costs are computed with high precision.
|
|
21
|
+
*/
|
|
18
22
|
static calculateCost(modelUsage: ModelUsage, modelInfo: ModelInfo): UsageCost | null {
|
|
19
23
|
// Handle both direct properties and nested tokens structure
|
|
20
24
|
const promptTokens = (modelUsage as any).tokens?.promptTokens ?? modelUsage.promptTokens;
|
|
@@ -59,6 +63,10 @@ export class StateFulAxAgentUsage {
|
|
|
59
63
|
}
|
|
60
64
|
}
|
|
61
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Persist or aggregate the cost for an agent in the shared crew state.
|
|
68
|
+
* No-op if cost is null.
|
|
69
|
+
*/
|
|
62
70
|
static trackCostInState(agentName: string, cost: UsageCost | null, state: StateInstance) {
|
|
63
71
|
// If cost is null, skip tracking
|
|
64
72
|
if (!cost) return;
|
|
@@ -90,6 +98,9 @@ export class StateFulAxAgentUsage {
|
|
|
90
98
|
}
|
|
91
99
|
}
|
|
92
100
|
|
|
101
|
+
/**
|
|
102
|
+
* Aggregate and return total costs across all agents from the shared crew state.
|
|
103
|
+
*/
|
|
93
104
|
static getAggregatedCosts(state: StateInstance): AggregatedCosts {
|
|
94
105
|
const allState = state.getAll();
|
|
95
106
|
const agentCosts: Record<string, UsageCost> = {};
|
|
@@ -128,6 +139,9 @@ export class StateFulAxAgentUsage {
|
|
|
128
139
|
};
|
|
129
140
|
}
|
|
130
141
|
|
|
142
|
+
/**
|
|
143
|
+
* Remove all stored per-agent costs from the shared crew state.
|
|
144
|
+
*/
|
|
131
145
|
static resetCosts(state: StateInstance) {
|
|
132
146
|
const allState = state.getAll();
|
|
133
147
|
Object.keys(allState).forEach(key => {
|
|
@@ -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,7 @@ import type {
|
|
|
14
14
|
StateInstance,
|
|
15
15
|
FunctionRegistryType,
|
|
16
16
|
UsageCost,
|
|
17
|
-
|
|
17
|
+
AxCrewConfig,
|
|
18
18
|
MCPTransportConfig,
|
|
19
19
|
} from "../types.js";
|
|
20
20
|
|
|
@@ -238,10 +238,20 @@ class StatefulAxAgent extends AxAgent<any, any> {
|
|
|
238
238
|
getAccumulatedCosts(): UsageCost | null { return null; }
|
|
239
239
|
|
|
240
240
|
// Metrics API for this agent
|
|
241
|
+
/**
|
|
242
|
+
* Get the current metrics snapshot for this agent.
|
|
243
|
+
* Includes request counts, error rates, token usage, estimated USD cost, and function call stats.
|
|
244
|
+
*
|
|
245
|
+
* @returns A metrics snapshot scoped to this agent within its crew.
|
|
246
|
+
*/
|
|
241
247
|
getMetrics() {
|
|
242
248
|
const crewId = (this.state as any)?.crewId || (this.state.get?.('crewId')) || 'default';
|
|
243
249
|
return MetricsRegistry.snapshot({ crewId, agent: this.agentName } as any);
|
|
244
250
|
}
|
|
251
|
+
/**
|
|
252
|
+
* Reset all tracked metrics for this agent (does not affect other agents).
|
|
253
|
+
* Call this to start fresh measurement windows for the agent.
|
|
254
|
+
*/
|
|
245
255
|
resetMetrics(): void {
|
|
246
256
|
const crewId = (this.state as any)?.crewId || (this.state.get?.('crewId')) || 'default';
|
|
247
257
|
MetricsRegistry.reset({ crewId, agent: this.agentName } as any);
|
|
@@ -250,10 +260,26 @@ class StatefulAxAgent extends AxAgent<any, any> {
|
|
|
250
260
|
}
|
|
251
261
|
|
|
252
262
|
/**
|
|
253
|
-
*
|
|
263
|
+
* AxCrew orchestrates a set of Ax agents that share state,
|
|
264
|
+
* tools (functions), optional MCP servers, streaming, and a built-in metrics
|
|
265
|
+
* registry for tokens, requests, and estimated cost.
|
|
266
|
+
*
|
|
267
|
+
* Typical usage:
|
|
268
|
+
* const crew = new AxCrew(config, AxCrewFunctions)
|
|
269
|
+
* await crew.addAllAgents()
|
|
270
|
+
* const planner = crew.agents?.get("Planner")
|
|
271
|
+
* const res = await planner?.forward({ task: "Plan something" })
|
|
272
|
+
*
|
|
273
|
+
* Key behaviors:
|
|
274
|
+
* - Validates and instantiates agents from a config-first model
|
|
275
|
+
* - Shares a mutable state object across all agents in the crew
|
|
276
|
+
* - Supports sub-agents and a function registry per agent
|
|
277
|
+
* - Tracks per-agent and crew-level metrics via MetricsRegistry
|
|
278
|
+
* - Provides helpers to add agents (individually, a subset, or all) and
|
|
279
|
+
* to reset metrics/costs when needed
|
|
254
280
|
*/
|
|
255
281
|
class AxCrew {
|
|
256
|
-
private crewConfig:
|
|
282
|
+
private crewConfig: AxCrewConfig;
|
|
257
283
|
functionsRegistry: FunctionRegistryType = {};
|
|
258
284
|
crewId: string;
|
|
259
285
|
agents: Map<string, StatefulAxAgent> | null;
|
|
@@ -261,12 +287,12 @@ class AxCrew {
|
|
|
261
287
|
|
|
262
288
|
/**
|
|
263
289
|
* Creates an instance of AxCrew.
|
|
264
|
-
* @param {
|
|
290
|
+
* @param {AxCrewConfig} crewConfig - JSON object with crew configuration.
|
|
265
291
|
* @param {FunctionRegistryType} [functionsRegistry={}] - The registry of functions to use in the crew.
|
|
266
292
|
* @param {string} [crewId=uuidv4()] - The unique identifier for the crew.
|
|
267
293
|
*/
|
|
268
294
|
constructor(
|
|
269
|
-
crewConfig:
|
|
295
|
+
crewConfig: AxCrewConfig,
|
|
270
296
|
functionsRegistry: FunctionRegistryType = {},
|
|
271
297
|
crewId: string = uuidv4()
|
|
272
298
|
) {
|
|
@@ -276,7 +302,7 @@ class AxCrew {
|
|
|
276
302
|
}
|
|
277
303
|
|
|
278
304
|
// Validate each agent in the crew
|
|
279
|
-
crewConfig.crew.forEach(agent => {
|
|
305
|
+
crewConfig.crew.forEach((agent: any) => {
|
|
280
306
|
if (!agent.name || agent.name.trim() === '') {
|
|
281
307
|
throw new Error('Agent name cannot be empty');
|
|
282
308
|
}
|
|
@@ -499,7 +525,8 @@ class AxCrew {
|
|
|
499
525
|
|
|
500
526
|
|
|
501
527
|
/**
|
|
502
|
-
* Resets all cost tracking for the crew
|
|
528
|
+
* Resets all cost and usage tracking for the entire crew.
|
|
529
|
+
* Also calls each agent's `resetUsage` (if available) and clears crew-level metrics.
|
|
503
530
|
*/
|
|
504
531
|
resetCosts(): void {
|
|
505
532
|
// Reset AxAgent built-in usage and our metrics registry
|
|
@@ -513,9 +540,19 @@ class AxCrew {
|
|
|
513
540
|
}
|
|
514
541
|
|
|
515
542
|
// Metrics API
|
|
543
|
+
/**
|
|
544
|
+
* Get an aggregate metrics snapshot for the entire crew.
|
|
545
|
+
* Sums requests, errors, tokens, and estimated cost across all agents in the crew.
|
|
546
|
+
*
|
|
547
|
+
* @returns Crew-level metrics snapshot.
|
|
548
|
+
*/
|
|
516
549
|
getCrewMetrics() {
|
|
517
550
|
return MetricsRegistry.snapshotCrew(this.crewId);
|
|
518
551
|
}
|
|
552
|
+
/**
|
|
553
|
+
* Reset all tracked metrics for the entire crew.
|
|
554
|
+
* Use to clear totals before a new measurement period.
|
|
555
|
+
*/
|
|
519
556
|
resetCrewMetrics(): void {
|
|
520
557
|
MetricsRegistry.reset({ crewId: this.crewId });
|
|
521
558
|
}
|
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
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AxCrew } from './agents/index.js';
|
|
2
2
|
import { AxCrewFunctions } from './functions/index.js';
|
|
3
|
-
import type {
|
|
3
|
+
import type { AxCrewConfig, AgentConfig } from './types.js';
|
|
4
4
|
|
|
5
5
|
import type {
|
|
6
6
|
UsageCost,
|
|
@@ -9,28 +9,42 @@ import type {
|
|
|
9
9
|
StateInstance,
|
|
10
10
|
FunctionRegistryType
|
|
11
11
|
} from './types.js';
|
|
12
|
+
/**
|
|
13
|
+
* Metrics types and helpers for request counts, token usage, and estimated cost.
|
|
14
|
+
*
|
|
15
|
+
* Re-exports the metrics module for convenience:
|
|
16
|
+
* - Types: TokenUsage, MetricsSnapshot, etc.
|
|
17
|
+
* - Namespace: MetricsRegistry (record/snapshot/reset helpers)
|
|
18
|
+
*/
|
|
12
19
|
export * from './metrics/index.js';
|
|
20
|
+
/**
|
|
21
|
+
* MetricsRegistry provides functions to record requests, tokens, and cost,
|
|
22
|
+
* and to snapshot/reset metrics at agent or crew granularity.
|
|
23
|
+
*/
|
|
13
24
|
export { MetricsRegistry } from './metrics/index.js';
|
|
14
25
|
|
|
15
|
-
// Main AxCrew configuration interface
|
|
16
26
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
|
|
27
|
+
* Create and manage a crew of Ax agents that share state and metrics.
|
|
28
|
+
* See the `AxCrew` class for full documentation.
|
|
29
|
+
*/
|
|
30
|
+
const _AxCrew: typeof AxCrew = AxCrew;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Built-in function registry with common tools that can be referenced by name
|
|
34
|
+
* from agent configs, or extended with your own functions.
|
|
20
35
|
*/
|
|
21
|
-
|
|
22
|
-
crew: AgentConfig[];
|
|
23
|
-
}
|
|
36
|
+
const _AxCrewFunctions: typeof AxCrewFunctions = AxCrewFunctions;
|
|
24
37
|
|
|
25
38
|
export {
|
|
26
|
-
AxCrew
|
|
27
|
-
|
|
39
|
+
/** See class JSDoc on the `AxCrew` implementation. */
|
|
40
|
+
_AxCrew as AxCrew,
|
|
41
|
+
/** Built-in function registry; see file docs in `src/functions/index.ts`. */
|
|
42
|
+
_AxCrewFunctions as AxCrewFunctions,
|
|
28
43
|
FunctionRegistryType,
|
|
29
44
|
// Type exports
|
|
30
45
|
type AggregatedMetrics,
|
|
31
46
|
type AggregatedCosts,
|
|
32
47
|
type AgentConfig,
|
|
33
|
-
type CrewConfigInput,
|
|
34
48
|
type AxCrewConfig,
|
|
35
49
|
type StateInstance,
|
|
36
50
|
type UsageCost,
|
package/src/metrics/registry.ts
CHANGED
|
@@ -44,6 +44,12 @@ function getOrInit(labels: LabelKeys): Counters {
|
|
|
44
44
|
return c;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Record a completed request.
|
|
49
|
+
* @param labels Crew/agent/provider/model identifiers
|
|
50
|
+
* @param streaming Whether this was a streaming request
|
|
51
|
+
* @param durationMs Duration in milliseconds
|
|
52
|
+
*/
|
|
47
53
|
export function recordRequest(labels: LabelKeys, streaming: boolean, durationMs: number) {
|
|
48
54
|
const c = getOrInit(labels);
|
|
49
55
|
c.requests += 1;
|
|
@@ -52,17 +58,26 @@ export function recordRequest(labels: LabelKeys, streaming: boolean, durationMs:
|
|
|
52
58
|
c.durationCount += 1;
|
|
53
59
|
}
|
|
54
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Record an error occurrence for the given labels.
|
|
63
|
+
*/
|
|
55
64
|
export function recordError(labels: LabelKeys) {
|
|
56
65
|
const c = getOrInit(labels);
|
|
57
66
|
c.errors += 1;
|
|
58
67
|
}
|
|
59
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Record token usage for a request (prompt and completion).
|
|
71
|
+
*/
|
|
60
72
|
export function recordTokens(labels: LabelKeys, usage: TokenUsage) {
|
|
61
73
|
const c = getOrInit(labels);
|
|
62
74
|
c.inputTokens += usage.promptTokens || 0;
|
|
63
75
|
c.outputTokens += usage.completionTokens || 0;
|
|
64
76
|
}
|
|
65
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Add estimated cost (USD) to the cumulative total for the labels.
|
|
80
|
+
*/
|
|
66
81
|
export function recordEstimatedCost(labels: LabelKeys, usd: number) {
|
|
67
82
|
const c = getOrInit(labels);
|
|
68
83
|
const current = new Big(c.estimatedCostUSD || 0);
|
|
@@ -70,12 +85,18 @@ export function recordEstimatedCost(labels: LabelKeys, usd: number) {
|
|
|
70
85
|
c.estimatedCostUSD = Number(current.plus(addition));
|
|
71
86
|
}
|
|
72
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Record a function call invocation and add its latency to totals.
|
|
90
|
+
*/
|
|
73
91
|
export function recordFunctionCall(labels: LabelKeys, latencyMs: number) {
|
|
74
92
|
const c = getOrInit(labels);
|
|
75
93
|
c.functionCalls += 1;
|
|
76
94
|
c.functionLatencyMs += latencyMs || 0;
|
|
77
95
|
}
|
|
78
96
|
|
|
97
|
+
/**
|
|
98
|
+
* Get a metrics snapshot for specific labels (crew + agent + optional provider/model).
|
|
99
|
+
*/
|
|
79
100
|
export function snapshot(labels: LabelKeys): MetricsSnapshot {
|
|
80
101
|
const c = getOrInit(labels);
|
|
81
102
|
const totalTokens = c.inputTokens + c.outputTokens;
|
|
@@ -103,6 +124,9 @@ export function snapshot(labels: LabelKeys): MetricsSnapshot {
|
|
|
103
124
|
};
|
|
104
125
|
}
|
|
105
126
|
|
|
127
|
+
/**
|
|
128
|
+
* Reset metrics for specific labels, or clear all if no labels provided.
|
|
129
|
+
*/
|
|
106
130
|
export function reset(labels?: LabelKeys) {
|
|
107
131
|
if (!labels) {
|
|
108
132
|
store.clear();
|
|
@@ -112,6 +136,9 @@ export function reset(labels?: LabelKeys) {
|
|
|
112
136
|
store.delete(k);
|
|
113
137
|
}
|
|
114
138
|
|
|
139
|
+
/**
|
|
140
|
+
* Aggregate a crew-wide metrics snapshot across all agents in the crew.
|
|
141
|
+
*/
|
|
115
142
|
export function snapshotCrew(crewId: string): MetricsSnapshot {
|
|
116
143
|
const empty: Counters = {
|
|
117
144
|
requests: 0,
|