@amitdeshmukh/ax-crew 5.0.0 → 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/dist/index.js CHANGED
@@ -1,5 +1,30 @@
1
1
  import { AxCrew } from './agents/index.js';
2
2
  import { AxCrewFunctions } from './functions/index.js';
3
+ /**
4
+ * Metrics types and helpers for request counts, token usage, and estimated cost.
5
+ *
6
+ * Re-exports the metrics module for convenience:
7
+ * - Types: TokenUsage, MetricsSnapshot, etc.
8
+ * - Namespace: MetricsRegistry (record/snapshot/reset helpers)
9
+ */
3
10
  export * from './metrics/index.js';
11
+ /**
12
+ * MetricsRegistry provides functions to record requests, tokens, and cost,
13
+ * and to snapshot/reset metrics at agent or crew granularity.
14
+ */
4
15
  export { MetricsRegistry } from './metrics/index.js';
5
- export { AxCrew, AxCrewFunctions, };
16
+ /**
17
+ * Create and manage a crew of Ax agents that share state and metrics.
18
+ * See the `AxCrew` class for full documentation.
19
+ */
20
+ const _AxCrew = AxCrew;
21
+ /**
22
+ * Built-in function registry with common tools that can be referenced by name
23
+ * from agent configs, or extended with your own functions.
24
+ */
25
+ const _AxCrewFunctions = AxCrewFunctions;
26
+ export {
27
+ /** See class JSDoc on the `AxCrew` implementation. */
28
+ _AxCrew as AxCrew,
29
+ /** Built-in function registry; see file docs in `src/functions/index.ts`. */
30
+ _AxCrewFunctions as AxCrewFunctions, };
package/dist/types.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import type { AxFunction, AxSignature, AxModelConfig, AxMCPStreamableHTTPTransportOptions, AxProgramForwardOptions } from '@ax-llm/ax';
2
- export type Provider = 'openai' | 'anthropic' | 'google-gemini' | 'mistral' | 'groq' | 'cohere' | 'together' | 'deepseek' | 'ollama' | 'huggingface' | 'openrouter' | 'azure-openai' | 'reka' | 'x-grok';
1
+ import type { AxFunction, AxSignature, AxModelConfig, AxMCPStreamableHTTPTransportOptions, AxProgramForwardOptions, AxAIArgs } from '@ax-llm/ax';
2
+ export type Provider = AxAIArgs<any>['name'];
3
3
  /**
4
4
  * A state instance that is shared between agents.
5
5
  * This can be used to store data that becomes available to all agents and functions in an out-of-band manner.
@@ -177,9 +177,43 @@ interface AgentConfig {
177
177
  mcpServers?: Record<string, MCPTransportConfig>;
178
178
  }
179
179
  /**
180
- * The input type for the agent config. This can be a path to a JSON file or a JSON object.
180
+ * The configuration object for an AxCrew instance.
181
+ *
182
+ * @property {AgentConfig[]} crew - The agents that make up the crew.
183
+ * @example
184
+ * const config: AxCrewConfig = {
185
+ * crew: [
186
+ * {
187
+ * name: "Agent1",
188
+ * description: "Agent 1 description",
189
+ * signature: "signature",
190
+ * provider: "provider",
191
+ * providerKeyName: "providerKeyName",
192
+ * ai: {
193
+ * model: "model",
194
+ * temperature: 0,
195
+ * },
196
+ * options: {
197
+ * debug: true,
198
+ * },
199
+ * functions: ["function1", "function2"],
200
+ * agents: ["agent2"],
201
+ * },
202
+ * {
203
+ * name: "Agent2",
204
+ * description: "Agent 2 description",
205
+ * signature: "signature",
206
+ * provider: "provider",
207
+ * providerKeyName: "providerKeyName",
208
+ * ai: {
209
+ * model: "model",
210
+ * temperature: 0,
211
+ * }
212
+ * ]
213
+ * }
214
+ * const crew = new AxCrew(config);
181
215
  */
182
- type CrewConfigInput = string | {
216
+ interface AxCrewConfig {
183
217
  crew: AgentConfig[];
184
- };
185
- export { type AgentConfig, type CrewConfigInput, type AggregatedMetrics, type StateInstance, type FunctionRegistryType, type MCPStdioTransportConfig, type MCPHTTPSSETransportConfig, type MCPStreamableHTTPTransportConfig, type MCPTransportConfig, type ModelUsage, type ModelInfo, type UsageCost, type AggregatedCosts };
218
+ }
219
+ export { type AgentConfig, type AxCrewConfig, type AggregatedMetrics, type StateInstance, type FunctionRegistryType, type MCPStdioTransportConfig, type MCPHTTPSSETransportConfig, type MCPStreamableHTTPTransportConfig, type MCPTransportConfig, type ModelUsage, type ModelInfo, type UsageCost, type AggregatedCosts };
@@ -2,6 +2,8 @@ import { AxCrew } from "../dist/index.js";
2
2
  import { AxCrewFunctions } from "../dist/functions/index.js";
3
3
  import type { AxCrewConfig } from "../dist/index.js";
4
4
  import type { Provider } from "../dist/types.js";
5
+ import dotenv from "dotenv";
6
+ dotenv.config();
5
7
 
6
8
  // Example agent configuration
7
9
  const agentConfig: AxCrewConfig = {
@@ -26,10 +28,10 @@ const agentConfig: AxCrewConfig = {
26
28
  name: "writer",
27
29
  description: "A writing agent that creates content",
28
30
  signature: "topic:string -> article:string",
29
- provider: "anthropic" as Provider,
30
- providerKeyName: "ANTHROPIC_API_KEY",
31
+ provider: "google-gemini" as Provider,
32
+ providerKeyName: "GEMINI_API_KEY",
31
33
  ai: {
32
- model: "claude-3-haiku-20240307",
34
+ model: "gemini-2.5-flash-lite",
33
35
  maxTokens: 4000,
34
36
  stream: true
35
37
  },
@@ -1,4 +1,5 @@
1
- import { AxCrew, AxCrewConfig } from "../dist/index.js";
1
+ import { AxCrew } from "../dist/index.js";
2
+ import type { AxCrewConfig } from "../dist/types.js";
2
3
 
3
4
  import dotenv from "dotenv";
4
5
  dotenv.config();
@@ -7,31 +8,23 @@ dotenv.config();
7
8
  const config = {
8
9
  crew: [
9
10
  {
10
- name: "MapsAgent",
11
- description: "A specialized agent with access to Google Maps APIs that can: geocode addresses to coordinates and vice versa, search for and get details about places, calculate travel distances and times between multiple locations, provide elevation data, and generate navigation directions between points.",
12
- prompt: "You are a precise geospatial assistant with expert knowledge of Google Maps APIs. Answer user queries by combining place search, geocoding, distance matrix, elevation, and directions. Provide concise, actionable answers, include travel mode assumptions, and cite uncertainties. When location is ambiguous, ask a brief clarifying question before proceeding.",
13
- signature: 'userQuery:string "a question to be answered" -> answer:string "the answer to the question"',
14
- provider: "anthropic",
15
- providerKeyName: "ANTHROPIC_API_KEY",
11
+ name: "Context7DocsAgent",
12
+ description: "A specialized agent with access to Context7 Docs APIs that can: search for and get details about API docs",
13
+ signature: 'apiDocQuery:string "a question to be answered" -> apiDocAnswer:string "the answer to the question"',
14
+ provider: "google-gemini",
15
+ providerKeyName: "GEMINI_API_KEY",
16
16
  ai: {
17
- model: "claude-3-5-sonnet-latest",
17
+ model: "gemini-2.5-pro",
18
18
  temperature: 0,
19
- maxTokens: 1000,
20
- stream: true
19
+ stream: false
21
20
  },
22
21
  options: {
23
22
  debug: true
24
23
  },
25
24
  "mcpServers": {
26
- "google-maps": {
25
+ "context7": {
27
26
  "command": "npx",
28
- "args": [
29
- "-y",
30
- "@modelcontextprotocol/server-google-maps"
31
- ],
32
- "env": {
33
- "GOOGLE_MAPS_API_KEY": process.env.GOOGLE_MAPS_API_KEY
34
- }
27
+ "args": ["-y", "@upstash/context7-mcp", "--api-key", process.env.CONTEXT7_API_KEY]
35
28
  }
36
29
  },
37
30
  },
@@ -41,35 +34,19 @@ const config = {
41
34
  prompt: "You are a manager agent that orchestrates tools and sub-agents. Read the user's objective, optionally produce a short plan, then call the MapsAgent when geospatial knowledge is needed. Keep answers direct and avoid extraneous commentary.",
42
35
  signature:
43
36
  'question:string "a question to be answered" -> answer:string "the answer to the question"',
44
- provider: "openai",
45
- providerKeyName: "OPENAI_API_KEY",
37
+ provider: "google-gemini",
38
+ providerKeyName: "GEMINI_API_KEY",
46
39
  ai: {
47
- model: "gpt-4o-mini",
40
+ model: "gemini-2.5-pro",
48
41
  maxTokens: 1000,
49
42
  temperature: 0,
50
- stream: true
43
+ stream: false
51
44
  },
52
45
  options: {
53
46
  debug: true,
54
47
  },
55
- agents: ["MapsAgent"]
56
- },
57
- {
58
- name: "MathAgent",
59
- description: "Solves math problems",
60
- signature:
61
- 'mathProblem:string "a sentence describing a math problem to be solved using Python code" -> solution:string "the answer to the math problem"',
62
- provider: "google-gemini",
63
- providerKeyName: "GEMINI_API_KEY",
64
- ai: {
65
- model: "gemini-1.5-pro",
66
- temperature: 0,
67
- stream: true
68
- },
69
- options: {
70
- debug: false,
71
- },
72
- },
48
+ agents: ["Context7DocsAgent"]
49
+ }
73
50
  ],
74
51
  };
75
52
 
@@ -81,9 +58,9 @@ await crew.addAllAgents();
81
58
 
82
59
  // Get agent instances
83
60
  const managerAgent = crew.agents?.get("ManagerAgent");
84
- const mapsAgent = crew.agents?.get("MapsAgent");
61
+ const context7DocsAgent = crew.agents?.get("Context7DocsAgent");
85
62
 
86
- const userQuery: string = "Are there any cool bars around the Eiffel Tower in Paris within 5 min walking distance";
63
+ const userQuery: string = "How do i create an agent in the @amitdeshmukh/ax-crew framework and configure it to use an MCP server? Give me a concrete example.";
87
64
 
88
65
  console.log(`\n\nQuestion: ${userQuery}`);
89
66
 
@@ -97,7 +74,7 @@ const main = async (): Promise<void> => {
97
74
  // Print metrics
98
75
  console.log("\nMetrics:\n+++++++++++++++++++++++++++++++++");
99
76
  console.log("Manager Agent Metrics:", JSON.stringify((managerAgent as any)?.getMetrics?.(), null, 2));
100
- console.log("Maps Agent Metrics:", JSON.stringify((mapsAgent as any)?.getMetrics?.(), null, 2));
77
+ console.log("Context7 Docs Agent Metrics:", JSON.stringify((context7DocsAgent as any)?.getMetrics?.(), null, 2));
101
78
  console.log("Crew Metrics:", JSON.stringify((crew as any)?.getCrewMetrics?.(), null, 2));
102
79
  };
103
80
 
@@ -1,19 +1,20 @@
1
1
  import { AxCrew } from "../dist/index.js";
2
+ import type { AxCrewConfig } from "../dist/types.js";
2
3
 
3
4
  import dotenv from "dotenv";
4
5
  dotenv.config();
5
6
 
6
7
  // Define the crew configuration
7
- const config = {
8
+ const config: AxCrewConfig = {
8
9
  crew: [
9
10
  {
10
11
  name: "DeepResearchAgent",
11
12
  description: "A specialized agent that performs deep research using perplexity",
12
- signature: 'researchTopic:string "a topic of interest" -> result:string "The result of the research"',
13
- provider: "openai",
14
- providerKeyName: "OPENAI_API_KEY",
13
+ signature: 'researchTopic:string "a topic of interest" -> researchResult:string "The result of the research"',
14
+ provider: "google-gemini",
15
+ providerKeyName: "GEMINI_API_KEY",
15
16
  ai: {
16
- model: "gpt-4.1",
17
+ model: "gemini-2.5-flash-lite",
17
18
  temperature: 0.1,
18
19
  },
19
20
  options: {
@@ -1,4 +1,5 @@
1
- import { AxCrew, AxCrewConfig } from "../dist/index.js";
1
+ import { AxCrew } from "../dist/index.js";
2
+ import type { AxCrewConfig } from "../dist/types.js";
2
3
 
3
4
  const crewConfig: AxCrewConfig = {
4
5
  crew: [
@@ -1,15 +1,16 @@
1
1
  import { AxCrew } from "../dist/index.js";
2
+ import type { AxCrewConfig } from "../dist/types.js";
2
3
 
3
4
  import dotenv from "dotenv";
4
5
  dotenv.config();
5
6
 
6
7
  // Define the crew configuration
7
- const config = {
8
+ const config: AxCrewConfig = {
8
9
  crew: [
9
10
  {
10
11
  name: "XSearchAgent",
11
12
  description: "A specialized agent that can search X (Twitter) posts for the latest news and updates about specific topics, people, or events. It can find trending posts, recent tweets, and real-time information from X platform.",
12
- signature: 'searchQuery:string "a search query" -> result:string "the response to the user query citing relevant sources including X posts and other web sources"',
13
+ signature: 'searchQuery:string "a search query" -> searchResults:string "the response to the user query citing relevant sources including X posts and other web sources"',
13
14
  provider: "grok",
14
15
  providerKeyName: "GROK_API_KEY",
15
16
  ai: {
@@ -55,8 +56,8 @@ const main = async (): Promise<void> => {
55
56
  if (response) {
56
57
  try {
57
58
  for await (const chunk of response) {
58
- if (chunk.delta && typeof chunk.delta === 'object' && 'results' in chunk.delta) {
59
- process.stdout.write(chunk.delta.results);
59
+ if (chunk.delta && typeof chunk.delta === 'object' && 'searchResults' in chunk.delta) {
60
+ process.stdout.write(chunk.delta.searchResults);
60
61
  }
61
62
  }
62
63
  console.log('\n');
@@ -1,5 +1,7 @@
1
1
  import { AxCrew } from "../dist/index.js";
2
2
  import type { AxCrewConfig } from "../src/index.js";
3
+ import dotenv from "dotenv";
4
+ dotenv.config();
3
5
 
4
6
  // Define the crew configuration
5
7
  const config: AxCrewConfig = {
@@ -12,11 +14,11 @@ const config: AxCrewConfig = {
12
14
  provider: "google-gemini",
13
15
  providerKeyName: "GEMINI_API_KEY",
14
16
  ai: {
15
- model: "gemini-1.5-pro",
17
+ model: "gemini-2.5-flash-lite",
16
18
  temperature: 0,
17
19
  },
18
20
  options: {
19
- debug: false,
21
+ debug: true,
20
22
  codeExecution: true,
21
23
  },
22
24
  },
@@ -25,10 +27,10 @@ const config: AxCrewConfig = {
25
27
  description: "Completes a user specified task",
26
28
  signature:
27
29
  'question:string "a question to be answered" -> answer:string "the answer to the question"',
28
- provider: "openai",
29
- providerKeyName: "OPENAI_API_KEY",
30
+ provider: "google-gemini",
31
+ providerKeyName: "GEMINI_API_KEY",
30
32
  ai: {
31
- model: "gpt-4o-mini",
33
+ model: "gemini-2.5-flash-lite",
32
34
  maxTokens: 1000,
33
35
  temperature: 0,
34
36
  },
@@ -15,7 +15,7 @@ const config: AxCrewConfig = {
15
15
  provider: "google-gemini",
16
16
  providerKeyName: "GEMINI_API_KEY",
17
17
  ai: {
18
- model: "gemini-2.0-flash",
18
+ model: "gemini-2.5-flash",
19
19
  maxTokens: 1000,
20
20
  temperature: 0,
21
21
  },
@@ -33,7 +33,7 @@ const config: AxCrewConfig = {
33
33
  provider: "google-gemini",
34
34
  providerKeyName: "GEMINI_API_KEY",
35
35
  ai: {
36
- model: "gemini-1.5-pro",
36
+ model: "gemini-2.5-pro",
37
37
  temperature: 0,
38
38
  },
39
39
  options: {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@amitdeshmukh/ax-crew",
4
- "version": "5.0.0",
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.16",
28
- "@ax-llm/ax-tools": "14.0.16"
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,20 +1,12 @@
1
- import fs from 'fs';
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
- import { PROVIDER_API_KEYS } from '../config/index.js';
6
+ // Resolve env by provided key name
15
7
  import type {
16
8
  AgentConfig,
17
- CrewConfigInput,
9
+ AxCrewConfig,
18
10
  FunctionRegistryType,
19
11
  MCPTransportConfig,
20
12
  MCPStdioTransportConfig,
@@ -23,26 +15,6 @@ import type {
23
15
  } from '../types.js';
24
16
  import type { Provider } from '../types.js';
25
17
 
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
18
  // Type guard to check if config is stdio transport
47
19
  export function isStdioTransport(config: MCPTransportConfig): config is MCPStdioTransportConfig {
48
20
  return 'command' in config;
@@ -69,53 +41,7 @@ function isConstructor<T>(func: any): func is { new (...args: any[]): T } {
69
41
  return typeof func === 'function' && 'prototype' in func && 'toFunction' in func.prototype;
70
42
  }
71
43
 
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
- };
44
+ // Removed file/JSON parse helpers to keep browser-safe
119
45
 
120
46
  const initializeMCPServers = async (agentConfigData: AgentConfig): Promise<AxFunction[]> => {
121
47
  const mcpServers = agentConfigData.mcpServers;
@@ -158,32 +84,16 @@ const initializeMCPServers = async (agentConfigData: AgentConfig): Promise<AxFun
158
84
  };
159
85
 
160
86
  /**
161
- * Parses and returns the AxCrew config from either a JSON config file or a direct JSON object.
162
- * @param {CrewConfigInput} input - Either a path to the agent config json file or a JSON object with crew configuration.
87
+ * Returns the AxCrew config from a direct JSON object. Browser-safe.
88
+ * @param {CrewConfig} input - A JSON object with crew configuration.
163
89
  * @returns {Object} The parsed crew config.
164
90
  * @throws Will throw an error if reading/parsing fails.
165
91
  */
166
- const parseCrewConfig = (input: CrewConfigInput): { crew: AgentConfig[] } => {
167
- try {
168
- if (typeof input === 'string') {
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;
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');
186
95
  }
96
+ return input as { crew: AgentConfig[] };
187
97
  };
188
98
 
189
99
  /**
@@ -191,7 +101,7 @@ const parseCrewConfig = (input: CrewConfigInput): { crew: AgentConfig[] } => {
191
101
  * and creates an instance of the Agent with the appropriate settings.
192
102
  *
193
103
  * @param {string} agentName - The identifier for the AI agent to be initialized.
194
- * @param {CrewConfigInput} crewConfig - Either a file path to the JSON configuration or a JSON object with crew configuration.
104
+ * @param {AxCrewConfig} crewConfig - A JSON object with crew configuration.
195
105
  * @param {FunctionRegistryType} functions - The functions available to the agent.
196
106
  * @param {Object} state - The state object for the agent.
197
107
  * @returns {Object} An object containing the Agents AI instance, its name, description, signature, functions and subAgentList.
@@ -200,7 +110,7 @@ const parseCrewConfig = (input: CrewConfigInput): { crew: AgentConfig[] } => {
200
110
  */
201
111
  const parseAgentConfig = async (
202
112
  agentName: string,
203
- crewConfig: CrewConfigInput,
113
+ crewConfig: AxCrewConfig,
204
114
  functions: FunctionRegistryType,
205
115
  state: Record<string, any>
206
116
  ) => {
@@ -211,20 +121,17 @@ const parseAgentConfig = async (
211
121
  throw new Error(`AI agent with name ${agentName} is not configured`);
212
122
  }
213
123
 
214
- // Enforce canonical provider slug
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
- }
124
+ // Normalize provider slug to lowercase and validate via Ax factory
125
+ const lower = String(agentConfigData.provider).toLowerCase() as Provider;
219
126
  const provider = lower as Provider;
220
127
 
221
- // If an API Key property is present, get the API key for the AI agent from the environment variables
128
+ // Resolve API key from user-supplied environment variable name
222
129
  let apiKey = '';
223
130
  if (agentConfigData.providerKeyName) {
224
- apiKey = PROVIDER_API_KEYS[agentConfigData.providerKeyName] || '';
225
-
131
+ const keyName = agentConfigData.providerKeyName;
132
+ apiKey = resolveApiKey(keyName) || '';
226
133
  if (!apiKey) {
227
- throw new Error(`API key for provider ${agentConfigData.provider} is not set in environment variables`);
134
+ throw new Error(`API key '${keyName}' for provider ${agentConfigData.provider} is not set in environment`);
228
135
  }
229
136
  } else {
230
137
  throw new Error(`Provider key name is missing in the agent configuration`);
@@ -253,37 +160,19 @@ const parseAgentConfig = async (
253
160
  throw new Error(`Invalid apiURL provided: ${agentConfigData.apiURL}`);
254
161
  }
255
162
  }
256
- // Forward provider-specific arguments with type-safety for Azure OpenAI
163
+ // Forward provider-specific arguments as-is; let Ax validate/ignore as needed
257
164
  const providerArgs = (agentConfigData as any).providerArgs;
258
- if (provider === 'azure-openai') {
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
165
+ if (providerArgs && typeof providerArgs === 'object') {
284
166
  Object.assign(aiArgs, providerArgs);
285
167
  }
286
- const aiInstance = ai(aiArgs);
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
+ }
287
176
 
288
177
  // If an mcpServers config is provided in the agent config, convert to functions
289
178
  const mcpFunctions = await initializeMCPServers(agentConfigData);
@@ -328,4 +217,21 @@ const parseAgentConfig = async (
328
217
  export {
329
218
  parseAgentConfig,
330
219
  parseCrewConfig
331
- };
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
+ }