@amitdeshmukh/ax-crew 9.0.1 → 9.0.2

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 CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## [9.0.2] - 2026-03-30
4
+
5
+ ### Added
6
+ - **Dynamic API key support**: `AgentConfig` now accepts an `apiKey` field as a `string` or `() => Promise<string>`. This enables dynamic credential refresh for providers that support it (Google Gemini, Anthropic). When set, `apiKey` takes precedence over `providerKeyName`. Useful for Google Cloud hosted models where access tokens must be refreshed periodically.
7
+
3
8
  ## [9.0.1] - 2026-03-29
4
9
 
5
10
  ### Fixed
@@ -112,9 +112,13 @@ const parseAgentConfig = async (agentName, crewConfig, functions, state, options
112
112
  // Normalize provider slug to lowercase and validate via Ax factory
113
113
  const lower = String(agentConfigData.provider).toLowerCase();
114
114
  const provider = lower;
115
- // Resolve API key from user-supplied environment variable name
115
+ // Resolve API key: direct apiKey (string or function) takes precedence over providerKeyName
116
116
  let apiKey = '';
117
- if (agentConfigData.providerKeyName) {
117
+ if (agentConfigData.apiKey) {
118
+ // Direct apiKey provided — use as-is (supports string or async function for token refresh)
119
+ apiKey = agentConfigData.apiKey;
120
+ }
121
+ else if (agentConfigData.providerKeyName) {
118
122
  const keyName = agentConfigData.providerKeyName;
119
123
  apiKey = resolveApiKey(keyName) || '';
120
124
  if (!apiKey) {
@@ -122,7 +126,7 @@ const parseAgentConfig = async (agentName, crewConfig, functions, state, options
122
126
  }
123
127
  }
124
128
  else {
125
- throw new Error(`Provider key name is missing in the agent configuration`);
129
+ throw new Error(`Either apiKey or providerKeyName must be provided in the agent configuration`);
126
130
  }
127
131
  // Create a cost tracker instance and pass to ai()
128
132
  const costTracker = new AxDefaultCostTracker((agentConfigData.options?.costTracking) || undefined);
@@ -2,7 +2,7 @@ import type { AxAI } from '@ax-llm/ax';
2
2
  import type { AxCrewConfig } from '../types.js';
3
3
  type BuildProviderArgs = {
4
4
  provider: string;
5
- apiKey: string;
5
+ apiKey: string | (() => Promise<string>);
6
6
  config: any;
7
7
  apiURL?: string;
8
8
  providerArgs?: Record<string, unknown>;
@@ -10,12 +10,18 @@ export function instantiateProvider({ provider, apiKey, config, apiURL, provider
10
10
  export function buildProvidersFromConfig(cfg) {
11
11
  const services = [];
12
12
  for (const agent of cfg.crew) {
13
- const apiKeyName = agent.providerKeyName;
14
- if (!apiKeyName)
15
- throw new Error(`Provider key name is missing for agent ${agent.name}`);
16
- const apiKey = resolveApiKey(apiKeyName) || '';
17
- if (!apiKey)
18
- throw new Error(`API key '${apiKeyName}' not set for agent ${agent.name}`);
13
+ let apiKey = '';
14
+ if (agent.apiKey) {
15
+ apiKey = agent.apiKey;
16
+ }
17
+ else if (agent.providerKeyName) {
18
+ apiKey = resolveApiKey(agent.providerKeyName) || '';
19
+ if (!apiKey)
20
+ throw new Error(`API key '${agent.providerKeyName}' not set for agent ${agent.name}`);
21
+ }
22
+ else {
23
+ throw new Error(`Either apiKey or providerKeyName must be provided for agent ${agent.name}`);
24
+ }
19
25
  const service = instantiateProvider({
20
26
  provider: String(agent.provider).toLowerCase(),
21
27
  apiKey,
package/dist/types.d.ts CHANGED
@@ -1,5 +1,10 @@
1
1
  import type { AxFunction, AxSignature, AxModelConfig, AxMCPStreamableHTTPTransportOptions, AxProgramForwardOptions, AxAIArgs, AxAgentOptions } from '@ax-llm/ax';
2
2
  export type Provider = AxAIArgs<any>['name'];
3
+ /**
4
+ * An async function that returns an API key string.
5
+ * Useful for providers that support dynamic token refresh (e.g., Google Cloud, Anthropic).
6
+ */
7
+ export type ApiKeyFunction = () => Promise<string>;
3
8
  export type AgentExecutionMode = 'axagent' | 'axgen';
4
9
  export type AxCrewAxAgentOptions = Partial<Omit<AxAgentOptions, 'agents' | 'functions' | 'contextFields'>> & {
5
10
  contextFields?: AxAgentOptions['contextFields'];
@@ -231,6 +236,13 @@ interface AgentConfig {
231
236
  prompt?: string;
232
237
  signature: string | AxSignature;
233
238
  provider: Provider;
239
+ /**
240
+ * Direct API key as a string or an async function that returns one.
241
+ * When a function is provided, it is called by the provider on each request,
242
+ * enabling dynamic credential refresh (e.g., Google Cloud access tokens).
243
+ * Takes precedence over `providerKeyName` when both are set.
244
+ */
245
+ apiKey?: string | ApiKeyFunction;
234
246
  providerKeyName?: string;
235
247
  ai: AxModelConfig & {
236
248
  model: string;
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Example: Using Google Cloud Vertex AI with dynamic token refresh.
3
+ *
4
+ * When running Gemini models on Google Cloud (Vertex AI), you need
5
+ * a short-lived access token instead of a static API key. Pass an
6
+ * async function as `apiKey` and ax-crew will call it each time
7
+ * the provider needs credentials.
8
+ *
9
+ * Prerequisites:
10
+ * npm install google-auth-library
11
+ * # Authenticate via: gcloud auth application-default login
12
+ */
13
+ import { AxCrew } from "../dist/index.js";
14
+ import type { AxCrewConfig } from "../dist/types.js";
15
+ import { GoogleAuth } from "google-auth-library";
16
+
17
+ // Set up Google Auth — uses Application Default Credentials
18
+ const googleAuth = new GoogleAuth({
19
+ scopes: ["https://www.googleapis.com/auth/cloud-platform"],
20
+ });
21
+
22
+ // Async function that returns a fresh access token
23
+ const getGoogleToken = async (): Promise<string> => {
24
+ const client = await googleAuth.getClient();
25
+ const response = await client.getAccessToken();
26
+ if (!response.token) {
27
+ throw new Error("Failed to get Google access token");
28
+ }
29
+ return response.token;
30
+ };
31
+
32
+ const crewConfig: AxCrewConfig = {
33
+ crew: [
34
+ {
35
+ name: "GeminiAgent",
36
+ description: "Agent using Google Cloud Vertex AI with dynamic token refresh",
37
+ provider: "google-gemini",
38
+ apiKey: getGoogleToken, // async function — called on each request
39
+ signature: "userQuery:string -> answer:string",
40
+ ai: {
41
+ model: "gemini-2.0-flash",
42
+ temperature: 0,
43
+ },
44
+ providerArgs: {
45
+ projectId: "your-gcp-project-id",
46
+ region: "global",
47
+ },
48
+ },
49
+ ],
50
+ };
51
+
52
+ async function main() {
53
+ const crew = new AxCrew(crewConfig);
54
+ await crew.addAllAgents();
55
+
56
+ const agent = crew.agents?.get("GeminiAgent");
57
+ const response = await agent?.forward({
58
+ userQuery: "What is the capital of France?",
59
+ });
60
+
61
+ console.log(response?.answer);
62
+ console.log(agent?.getAccumulatedCosts());
63
+
64
+ crew.destroy();
65
+ }
66
+
67
+ 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": "9.0.1",
4
+ "version": "9.0.2",
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",
@@ -143,16 +143,19 @@ const parseAgentConfig = async (
143
143
  const lower = String(agentConfigData.provider).toLowerCase() as Provider;
144
144
  const provider = lower as Provider;
145
145
 
146
- // Resolve API key from user-supplied environment variable name
147
- let apiKey = '';
148
- if (agentConfigData.providerKeyName) {
146
+ // Resolve API key: direct apiKey (string or function) takes precedence over providerKeyName
147
+ let apiKey: string | (() => Promise<string>) = '';
148
+ if (agentConfigData.apiKey) {
149
+ // Direct apiKey provided — use as-is (supports string or async function for token refresh)
150
+ apiKey = agentConfigData.apiKey;
151
+ } else if (agentConfigData.providerKeyName) {
149
152
  const keyName = agentConfigData.providerKeyName;
150
153
  apiKey = resolveApiKey(keyName) || '';
151
154
  if (!apiKey) {
152
155
  throw new Error(`API key '${keyName}' for provider ${agentConfigData.provider} is not set in environment`);
153
156
  }
154
157
  } else {
155
- throw new Error(`Provider key name is missing in the agent configuration`);
158
+ throw new Error(`Either apiKey or providerKeyName must be provided in the agent configuration`);
156
159
  }
157
160
 
158
161
  // Create a cost tracker instance and pass to ai()
@@ -4,7 +4,7 @@ import type { AxCrewConfig } from '../types.js';
4
4
 
5
5
  type BuildProviderArgs = {
6
6
  provider: string;
7
- apiKey: string;
7
+ apiKey: string | (() => Promise<string>);
8
8
  config: any;
9
9
  apiURL?: string;
10
10
  providerArgs?: Record<string, unknown>;
@@ -28,10 +28,15 @@ export function instantiateProvider({
28
28
  export function buildProvidersFromConfig(cfg: AxCrewConfig): AxAI<any>[] {
29
29
  const services: AxAI<any>[] = [];
30
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}`);
31
+ let apiKey: string | (() => Promise<string>) = '';
32
+ if (agent.apiKey) {
33
+ apiKey = agent.apiKey;
34
+ } else if (agent.providerKeyName) {
35
+ apiKey = resolveApiKey(agent.providerKeyName) || '';
36
+ if (!apiKey) throw new Error(`API key '${agent.providerKeyName}' not set for agent ${agent.name}`);
37
+ } else {
38
+ throw new Error(`Either apiKey or providerKeyName must be provided for agent ${agent.name}`);
39
+ }
35
40
 
36
41
  const service = instantiateProvider({
37
42
  provider: String(agent.provider).toLowerCase(),
@@ -25,6 +25,7 @@ interface AgentConfig {
25
25
  };
26
26
 
27
27
  // Optional
28
+ apiKey?: string | (() => Promise<string>); // Direct API key or async token-refresh function
28
29
  providerKeyName?: string; // Env var name for API key (e.g. "OPENAI_API_KEY")
29
30
  apiURL?: string; // Custom API endpoint (e.g. ollama on localhost)
30
31
  providerArgs?: Record<string, unknown>; // Provider-specific args forwarded to Ax factory
@@ -90,6 +91,44 @@ The `providerKeyName` field specifies which environment variable holds the API k
90
91
  }
91
92
  ```
92
93
 
94
+ ## Dynamic API Key (Token Refresh)
95
+
96
+ For providers that support it (Google Gemini, Anthropic), `apiKey` can be an async function that returns a fresh token on each call. This is useful for Google Cloud hosted models where access tokens expire.
97
+
98
+ ```ts
99
+ import { GoogleAuth } from "google-auth-library";
100
+
101
+ const googleAuth = new GoogleAuth({
102
+ scopes: ["https://www.googleapis.com/auth/cloud-platform"],
103
+ });
104
+
105
+ const getGoogleToken = async (): Promise<string> => {
106
+ const client = await googleAuth.getClient();
107
+ const response = await client.getAccessToken();
108
+ if (!response.token) throw new Error("Failed to get Google token");
109
+ return response.token;
110
+ };
111
+
112
+ const config: AxCrewConfig = {
113
+ crew: [
114
+ {
115
+ name: "GeminiAgent",
116
+ description: "Agent using Google Cloud Vertex AI with token refresh",
117
+ provider: "google-gemini",
118
+ apiKey: getGoogleToken, // called on each request to get a fresh token
119
+ signature: "userQuery:string -> answer:string",
120
+ ai: { model: "gemini-2.0-flash", temperature: 0 },
121
+ providerArgs: {
122
+ projectId: "your-gcp-project-id",
123
+ region: "global",
124
+ },
125
+ }
126
+ ]
127
+ };
128
+ ```
129
+
130
+ When `apiKey` is set, `providerKeyName` is not required.
131
+
93
132
  ## Azure OpenAI Example
94
133
 
95
134
  Use `providerArgs` for provider-specific configuration:
@@ -175,10 +214,11 @@ Supported types: `string`, `number`, `boolean`, `string[]`, `number[]`, etc.
175
214
  - Do NOT omit `name`, `description`, `signature`, `provider`, or `ai.model` -- all are required.
176
215
  - Do NOT set `definition` to less than 100 characters if you provide it (Ax enforces this minimum).
177
216
  - Do NOT confuse `options.stream` (forward option) with `ai.stream` (AI-level streaming config).
178
- - Do NOT use `providerArgs` for API keys; use `providerKeyName` instead.
217
+ - Do NOT use `providerArgs` for API keys; use `apiKey` or `providerKeyName` instead.
179
218
  - Do NOT list sub-agents in `agents` that haven't been added to the crew before the parent agent.
180
219
 
181
220
  ## References
182
221
 
183
222
  - [basic-researcher-writer.ts](examples/basic-researcher-writer.ts)
184
223
  - [providerArgs.ts](examples/providerArgs.ts)
224
+ - [google-cloud-token-refresh.ts](examples/google-cloud-token-refresh.ts)
@@ -22,6 +22,7 @@ type Provider = AxAIArgs<any>['name'];
22
22
  ```ts
23
23
  interface AgentConfig {
24
24
  provider: Provider; // e.g. "openai"
25
+ apiKey?: string | (() => Promise<string>); // direct key or async token-refresh fn
25
26
  providerKeyName?: string; // env var name, e.g. "OPENAI_API_KEY"
26
27
  ai: AxModelConfig & { model: string }; // model name + temperature, maxTokens, etc.
27
28
  apiURL?: string; // custom endpoint (ollama, proxies)
@@ -30,6 +31,36 @@ interface AgentConfig {
30
31
  }
31
32
  ```
32
33
 
34
+ ## Dynamic API Key (Token Refresh)
35
+
36
+ For providers that support it (Google Gemini, Anthropic), `apiKey` can be an async function returning a fresh token. When `apiKey` is set, `providerKeyName` is not required.
37
+
38
+ ```ts
39
+ import { GoogleAuth } from "google-auth-library";
40
+
41
+ const googleAuth = new GoogleAuth({
42
+ scopes: ["https://www.googleapis.com/auth/cloud-platform"],
43
+ });
44
+
45
+ const getGoogleToken = async (): Promise<string> => {
46
+ const client = await googleAuth.getClient();
47
+ const response = await client.getAccessToken();
48
+ if (!response.token) throw new Error("Failed to get Google token");
49
+ return response.token;
50
+ };
51
+
52
+ {
53
+ name: "GeminiAgent",
54
+ provider: "google-gemini",
55
+ apiKey: getGoogleToken,
56
+ ai: { model: "gemini-2.0-flash", temperature: 0 },
57
+ providerArgs: {
58
+ projectId: "your-gcp-project-id",
59
+ region: "global",
60
+ },
61
+ }
62
+ ```
63
+
33
64
  ## Provider-Specific Configuration
34
65
 
35
66
  ### Azure OpenAI
@@ -194,11 +225,11 @@ main().catch(console.error);
194
225
 
195
226
  ## Do Not Generate
196
227
 
197
- - Do NOT hardcode API keys in config -- always use `providerKeyName` which reads from `process.env`.
198
- - Do NOT use `providerArgs` for non-Azure providers unless the Ax factory documents it -- standard providers only need `provider`, `providerKeyName`, `ai`, and optionally `apiURL`.
228
+ - Do NOT hardcode API keys in config -- use `providerKeyName` (env var) or `apiKey` (string or async function).
229
+ - Do NOT use `providerArgs` for non-Azure providers unless the Ax factory documents it -- standard providers only need `provider`, `apiKey` or `providerKeyName`, `ai`, and optionally `apiURL`.
199
230
  - Do NOT confuse `options.searchParameters` (Grok-specific forward option) with `mcpServers` (external tool servers).
200
231
  - Do NOT set `provider: "perplexity"` -- Perplexity is accessed via an MCP server, not as a native Ax provider.
201
- - Do NOT omit `providerKeyName` -- the crew will throw at initialization if the env var is missing.
232
+ - Do NOT omit both `apiKey` and `providerKeyName` -- the crew requires at least one to resolve credentials.
202
233
 
203
234
  ## References
204
235
 
@@ -206,4 +237,5 @@ main().catch(console.error);
206
237
  - [search-tweets.ts](examples/search-tweets.ts) (Grok/xAI)
207
238
  - [perplexityDeepSearch.ts](examples/perplexityDeepSearch.ts) (Perplexity MCP)
208
239
  - [write-post-and-publish-to-wordpress.ts](examples/write-post-and-publish-to-wordpress.ts) (mixed providers)
240
+ - [google-cloud-token-refresh.ts](examples/google-cloud-token-refresh.ts) (dynamic API key)
209
241
  - [src/agents/compose.ts](src/agents/compose.ts)
package/src/types.ts CHANGED
@@ -11,6 +11,12 @@ import type {
11
11
  // Provider ids are derived from Ax's factory arg type so new providers added in Ax
12
12
  // are picked up at compile time without updating AxCrew.
13
13
  export type Provider = AxAIArgs<any>['name'];
14
+
15
+ /**
16
+ * An async function that returns an API key string.
17
+ * Useful for providers that support dynamic token refresh (e.g., Google Cloud, Anthropic).
18
+ */
19
+ export type ApiKeyFunction = () => Promise<string>;
14
20
  export type AgentExecutionMode = 'axagent' | 'axgen';
15
21
  export type AxCrewAxAgentOptions =
16
22
  Partial<Omit<AxAgentOptions, 'agents' | 'functions' | 'contextFields'>> & {
@@ -261,6 +267,13 @@ interface AgentConfig {
261
267
  prompt?: string;
262
268
  signature: string | AxSignature;
263
269
  provider: Provider;
270
+ /**
271
+ * Direct API key as a string or an async function that returns one.
272
+ * When a function is provided, it is called by the provider on each request,
273
+ * enabling dynamic credential refresh (e.g., Google Cloud access tokens).
274
+ * Takes precedence over `providerKeyName` when both are set.
275
+ */
276
+ apiKey?: string | ApiKeyFunction;
264
277
  providerKeyName?: string;
265
278
  ai: AxModelConfig & { model: string };
266
279
  debug?: boolean;