@amitdeshmukh/ax-crew 9.0.0 → 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 +11 -0
- package/dist/agents/agentConfig.d.ts +1 -0
- package/dist/agents/agentConfig.js +8 -3
- package/dist/agents/compose.d.ts +1 -1
- package/dist/agents/compose.js +12 -6
- package/dist/agents/crew.js +2 -1
- package/dist/agents/statefulAgent.d.ts +4 -0
- package/dist/agents/statefulAgent.js +7 -2
- package/dist/types.d.ts +12 -0
- package/examples/google-cloud-token-refresh.ts +67 -0
- package/examples/graphjin-database-agent.ts +26 -16
- package/package.json +1 -1
- package/src/agents/agentConfig.ts +8 -4
- package/src/agents/compose.ts +10 -5
- package/src/agents/crew.ts +2 -1
- package/src/agents/statefulAgent.ts +11 -2
- package/src/skills/axcrew-agent-config.md +41 -1
- package/src/skills/axcrew-providers.md +35 -3
- package/src/types.ts +13 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
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
|
+
|
|
8
|
+
## [9.0.1] - 2026-03-29
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- **Agent-level forward options now respected**: Config options like `maxSteps`, `showThoughts`, and `thinkingTokenBudget` defined in agent config `options` were parsed but never passed to `AxGen.forward()`. They are now merged as defaults under caller-supplied options.
|
|
12
|
+
- **Removed hardcoded `contextCache` breakpoint**: The `cacheBreakpoint: 'after-examples'` setting on AxGen is removed, as it is no longer needed after prompt caching changes in 9.0.0.
|
|
13
|
+
|
|
3
14
|
## [9.0.0] - 2026-03-25
|
|
4
15
|
|
|
5
16
|
### Breaking Changes
|
|
@@ -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
|
|
115
|
+
// Resolve API key: direct apiKey (string or function) takes precedence over providerKeyName
|
|
116
116
|
let apiKey = '';
|
|
117
|
-
if (agentConfigData.
|
|
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(`
|
|
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);
|
|
@@ -201,6 +205,7 @@ const parseAgentConfig = async (agentName, crewConfig, functions, state, options
|
|
|
201
205
|
tracker: costTracker,
|
|
202
206
|
deferredTools: agentConfigData.deferredTools,
|
|
203
207
|
debug: agentConfigData.options?.debug ?? agentConfigData.debug ?? false,
|
|
208
|
+
forwardOptions: agentConfigData.options,
|
|
204
209
|
};
|
|
205
210
|
}
|
|
206
211
|
catch (error) {
|
package/dist/agents/compose.d.ts
CHANGED
|
@@ -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>;
|
package/dist/agents/compose.js
CHANGED
|
@@ -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
|
-
|
|
14
|
-
if (
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if (
|
|
18
|
-
|
|
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/agents/crew.js
CHANGED
|
@@ -56,7 +56,7 @@ class AxCrew {
|
|
|
56
56
|
createAgent = async (agentName) => {
|
|
57
57
|
try {
|
|
58
58
|
const agentConfig = await parseAgentConfig(agentName, this.crewConfig, this.functionsRegistry, this.crewState, this.options);
|
|
59
|
-
const { ai, name, executionMode, axAgentOptions, description, signature, functions, subAgentNames, examples, tracker } = agentConfig;
|
|
59
|
+
const { ai, name, executionMode, axAgentOptions, description, signature, functions, subAgentNames, examples, tracker, forwardOptions } = agentConfig;
|
|
60
60
|
// Get subagents for the AI agent
|
|
61
61
|
const subAgents = subAgentNames.map((subAgentName) => {
|
|
62
62
|
if (!this.agents?.get(subAgentName)) {
|
|
@@ -128,6 +128,7 @@ class AxCrew {
|
|
|
128
128
|
agents: uniqueSubAgents,
|
|
129
129
|
examples,
|
|
130
130
|
debug: agentConfig.debug,
|
|
131
|
+
forwardOptions,
|
|
131
132
|
}, agentState);
|
|
132
133
|
agent.costTracker = tracker;
|
|
133
134
|
if (deferredManager.isActive) {
|
|
@@ -16,6 +16,8 @@ export interface ParsedAgentConfig {
|
|
|
16
16
|
subAgentNames: string[];
|
|
17
17
|
examples?: Array<Record<string, any>>;
|
|
18
18
|
tracker?: any;
|
|
19
|
+
/** Agent-level forward options (maxSteps, showThoughts, thinkingTokenBudget, etc.) used as defaults when the caller doesn't supply them. */
|
|
20
|
+
forwardOptions?: Record<string, any>;
|
|
19
21
|
}
|
|
20
22
|
declare class StatefulAxAgent extends AxAgent<any, any> {
|
|
21
23
|
crewState: StateInstance;
|
|
@@ -27,6 +29,7 @@ declare class StatefulAxAgent extends AxAgent<any, any> {
|
|
|
27
29
|
private costTracker?;
|
|
28
30
|
private debugEnabled;
|
|
29
31
|
private deferredToolManager?;
|
|
32
|
+
private agentForwardOptions?;
|
|
30
33
|
private static readonly modernAxAgentRuntime;
|
|
31
34
|
private aceConfig?;
|
|
32
35
|
private aceOptimizer?;
|
|
@@ -45,6 +48,7 @@ declare class StatefulAxAgent extends AxAgent<any, any> {
|
|
|
45
48
|
examples?: Array<Record<string, any>> | undefined;
|
|
46
49
|
mcpServers?: Record<string, MCPTransportConfig> | undefined;
|
|
47
50
|
debug?: boolean;
|
|
51
|
+
forwardOptions?: Record<string, any>;
|
|
48
52
|
}>, state: StateInstance);
|
|
49
53
|
/**
|
|
50
54
|
* @deprecated Use setExamplesCompat() to avoid Ax runtime version coupling.
|
|
@@ -11,6 +11,7 @@ class StatefulAxAgent extends AxAgent {
|
|
|
11
11
|
costTracker;
|
|
12
12
|
debugEnabled = false;
|
|
13
13
|
deferredToolManager;
|
|
14
|
+
agentForwardOptions;
|
|
14
15
|
static modernAxAgentRuntime = typeof AxAgent?.prototype?.getFunction === "function" &&
|
|
15
16
|
typeof AxAgent?.prototype?.setExamples !== "function";
|
|
16
17
|
// ACE-related optional state
|
|
@@ -88,6 +89,7 @@ class StatefulAxAgent extends AxAgent {
|
|
|
88
89
|
this.agentDefinition = effectiveDefinition;
|
|
89
90
|
this.executionMode = options.executionMode ?? "axgen";
|
|
90
91
|
this.debugEnabled = debug ?? false;
|
|
92
|
+
this.agentForwardOptions = options.forwardOptions;
|
|
91
93
|
// Convert sub-agents to callable functions so AxGen can invoke them as tools
|
|
92
94
|
const subAgentFunctions = resolvedAgents
|
|
93
95
|
.map(agent => {
|
|
@@ -102,7 +104,6 @@ class StatefulAxAgent extends AxAgent {
|
|
|
102
104
|
this.axGenProgram = new AxGen(options.signature, {
|
|
103
105
|
description: effectiveDefinition,
|
|
104
106
|
functions: [...resolvedFunctions, ...subAgentFunctions],
|
|
105
|
-
contextCache: { cacheBreakpoint: 'after-examples' },
|
|
106
107
|
});
|
|
107
108
|
for (const agent of resolvedAgents) {
|
|
108
109
|
try {
|
|
@@ -177,7 +178,11 @@ class StatefulAxAgent extends AxAgent {
|
|
|
177
178
|
throw new Error(`No AI instance is configured for agent "${this.agentName}"`);
|
|
178
179
|
}
|
|
179
180
|
const values = (calledWithAI ? second : first);
|
|
180
|
-
const
|
|
181
|
+
const callerOptions = (calledWithAI ? third : second);
|
|
182
|
+
// Merge agent-level forward options as defaults under caller-supplied options
|
|
183
|
+
const options = this.agentForwardOptions
|
|
184
|
+
? { ...this.agentForwardOptions, ...callerOptions }
|
|
185
|
+
: callerOptions;
|
|
181
186
|
return { ai, values, options, calledWithAI };
|
|
182
187
|
}
|
|
183
188
|
/** Merge deferred tool step hooks into forward options */
|
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);
|
|
@@ -50,11 +50,10 @@ STRATEGY:
|
|
|
50
50
|
providerKeyName: "ANTHROPIC_API_KEY",
|
|
51
51
|
ai: {
|
|
52
52
|
model: "claude-sonnet-4-6",
|
|
53
|
-
|
|
54
|
-
stream: false
|
|
53
|
+
stream: true
|
|
55
54
|
},
|
|
56
55
|
options: {
|
|
57
|
-
debug:
|
|
56
|
+
debug: false,
|
|
58
57
|
},
|
|
59
58
|
mcpServers: {
|
|
60
59
|
"graphjin": {
|
|
@@ -75,10 +74,10 @@ Delegate each question to the most relevant agent in a SINGLE call — do not br
|
|
|
75
74
|
The sub-agent will handle all the steps internally. Your job is to route and synthesize, not to decompose.
|
|
76
75
|
If multiple agents are needed, call them and combine their answers.`,
|
|
77
76
|
signature: 'question:string "a question to be answered" -> answer:string "the answer to the question"',
|
|
78
|
-
provider: "
|
|
79
|
-
providerKeyName: "
|
|
77
|
+
provider: "google-gemini",
|
|
78
|
+
providerKeyName: "GEMINI_API_KEY",
|
|
80
79
|
ai: {
|
|
81
|
-
model: "
|
|
80
|
+
model: "gemini-3-flash-preview",
|
|
82
81
|
maxTokens: 2000,
|
|
83
82
|
temperature: 0,
|
|
84
83
|
stream: false
|
|
@@ -94,10 +93,11 @@ If multiple agents are needed, call them and combine their answers.`,
|
|
|
94
93
|
// Create a new instance of AxCrew with the config
|
|
95
94
|
const crew = new AxCrew(config as AxCrewConfig);
|
|
96
95
|
|
|
97
|
-
const userQuery = "
|
|
96
|
+
const userQuery = "what are the 10 products with the most number of refund requests and what are the top complaints against each of them";
|
|
98
97
|
|
|
99
98
|
console.log(`\nQuestion: ${userQuery}`);
|
|
100
99
|
|
|
100
|
+
|
|
101
101
|
const main = async (): Promise<void> => {
|
|
102
102
|
const timers: Record<string, number> = {};
|
|
103
103
|
|
|
@@ -107,25 +107,35 @@ const main = async (): Promise<void> => {
|
|
|
107
107
|
await crew.addAllAgents();
|
|
108
108
|
timers["setup"] = performance.now() - t0;
|
|
109
109
|
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
throw new Error("Failed to initialize ManagerAgent");
|
|
110
|
+
const dbAgent = crew.agents?.get("DatabaseAgent");
|
|
111
|
+
if (!dbAgent) {
|
|
112
|
+
throw new Error("Failed to initialize DatabaseAgent");
|
|
114
113
|
}
|
|
115
114
|
|
|
116
|
-
// --- Query phase ---
|
|
115
|
+
// --- Query phase with streaming thoughts ---
|
|
117
116
|
console.log("\n--- Starting query ---\n");
|
|
118
117
|
const t1 = performance.now();
|
|
119
118
|
|
|
120
|
-
const
|
|
121
|
-
question: userQuery,
|
|
122
|
-
|
|
119
|
+
const stream = dbAgent.streamingForward(
|
|
120
|
+
{ question: userQuery },
|
|
121
|
+
{ showThoughts: true, thinkingTokenBudget: "low" }
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
let answer = '';
|
|
125
|
+
for await (const chunk of stream) {
|
|
126
|
+
if (chunk.delta && 'thought' in chunk.delta) {
|
|
127
|
+
process.stdout.write(chunk.delta.thought as string);
|
|
128
|
+
}
|
|
129
|
+
if (chunk.delta && 'answer' in chunk.delta) {
|
|
130
|
+
answer += chunk.delta.answer ?? '';
|
|
131
|
+
}
|
|
132
|
+
}
|
|
123
133
|
|
|
124
134
|
timers["query"] = performance.now() - t1;
|
|
125
135
|
timers["total"] = performance.now() - t0;
|
|
126
136
|
|
|
127
137
|
// --- Results ---
|
|
128
|
-
console.log(`\
|
|
138
|
+
console.log(`\n\n--- Answer ---\n${answer}`);
|
|
129
139
|
|
|
130
140
|
// --- Timing & Metrics ---
|
|
131
141
|
console.log("\n--- Performance ---");
|
package/package.json
CHANGED
|
@@ -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
|
|
147
|
-
let apiKey = '';
|
|
148
|
-
if (agentConfigData.
|
|
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(`
|
|
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()
|
|
@@ -237,6 +240,7 @@ const parseAgentConfig = async (
|
|
|
237
240
|
tracker: costTracker,
|
|
238
241
|
deferredTools: (agentConfigData as any).deferredTools,
|
|
239
242
|
debug: (agentConfigData as any).options?.debug ?? (agentConfigData as any).debug ?? false,
|
|
243
|
+
forwardOptions: (agentConfigData as any).options,
|
|
240
244
|
};
|
|
241
245
|
} catch (error) {
|
|
242
246
|
if (error instanceof Error) {
|
package/src/agents/compose.ts
CHANGED
|
@@ -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
|
-
|
|
32
|
-
if (
|
|
33
|
-
|
|
34
|
-
|
|
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(),
|
package/src/agents/crew.ts
CHANGED
|
@@ -93,7 +93,7 @@ class AxCrew {
|
|
|
93
93
|
this.options
|
|
94
94
|
);
|
|
95
95
|
|
|
96
|
-
const { ai, name, executionMode, axAgentOptions, description, signature, functions, subAgentNames, examples, tracker } = agentConfig;
|
|
96
|
+
const { ai, name, executionMode, axAgentOptions, description, signature, functions, subAgentNames, examples, tracker, forwardOptions } = agentConfig;
|
|
97
97
|
|
|
98
98
|
// Get subagents for the AI agent
|
|
99
99
|
const subAgents = subAgentNames.map((subAgentName: string) => {
|
|
@@ -182,6 +182,7 @@ class AxCrew {
|
|
|
182
182
|
agents: uniqueSubAgents,
|
|
183
183
|
examples,
|
|
184
184
|
debug: (agentConfig as any).debug,
|
|
185
|
+
forwardOptions,
|
|
185
186
|
},
|
|
186
187
|
agentState as StateInstance
|
|
187
188
|
);
|
|
@@ -42,6 +42,8 @@ export interface ParsedAgentConfig {
|
|
|
42
42
|
subAgentNames: string[];
|
|
43
43
|
examples?: Array<Record<string, any>>;
|
|
44
44
|
tracker?: any;
|
|
45
|
+
/** Agent-level forward options (maxSteps, showThoughts, thinkingTokenBudget, etc.) used as defaults when the caller doesn't supply them. */
|
|
46
|
+
forwardOptions?: Record<string, any>;
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
// Extend the AxAgent class from ax-llm
|
|
@@ -55,6 +57,7 @@ class StatefulAxAgent extends AxAgent<any, any> {
|
|
|
55
57
|
private costTracker?: any;
|
|
56
58
|
private debugEnabled: boolean = false;
|
|
57
59
|
private deferredToolManager?: DeferredToolManager;
|
|
60
|
+
private agentForwardOptions?: Record<string, any>;
|
|
58
61
|
private static readonly modernAxAgentRuntime =
|
|
59
62
|
typeof (AxAgent as any)?.prototype?.getFunction === "function" &&
|
|
60
63
|
typeof (AxAgent as any)?.prototype?.setExamples !== "function";
|
|
@@ -82,6 +85,7 @@ class StatefulAxAgent extends AxAgent<any, any> {
|
|
|
82
85
|
examples?: Array<Record<string, any>> | undefined;
|
|
83
86
|
mcpServers?: Record<string, MCPTransportConfig> | undefined;
|
|
84
87
|
debug?: boolean;
|
|
88
|
+
forwardOptions?: Record<string, any>;
|
|
85
89
|
}>,
|
|
86
90
|
state: StateInstance
|
|
87
91
|
) {
|
|
@@ -162,6 +166,7 @@ class StatefulAxAgent extends AxAgent<any, any> {
|
|
|
162
166
|
this.agentDefinition = effectiveDefinition;
|
|
163
167
|
this.executionMode = options.executionMode ?? "axgen";
|
|
164
168
|
this.debugEnabled = debug ?? false;
|
|
169
|
+
this.agentForwardOptions = options.forwardOptions;
|
|
165
170
|
// Convert sub-agents to callable functions so AxGen can invoke them as tools
|
|
166
171
|
const subAgentFunctions: AxFunction[] = resolvedAgents
|
|
167
172
|
.map(agent => {
|
|
@@ -173,7 +178,6 @@ class StatefulAxAgent extends AxAgent<any, any> {
|
|
|
173
178
|
this.axGenProgram = new AxGen(options.signature as any, {
|
|
174
179
|
description: effectiveDefinition,
|
|
175
180
|
functions: [...resolvedFunctions, ...subAgentFunctions],
|
|
176
|
-
contextCache: { cacheBreakpoint: 'after-examples' },
|
|
177
181
|
} as any);
|
|
178
182
|
|
|
179
183
|
for (const agent of resolvedAgents) {
|
|
@@ -270,7 +274,12 @@ class StatefulAxAgent extends AxAgent<any, any> {
|
|
|
270
274
|
}
|
|
271
275
|
|
|
272
276
|
const values = (calledWithAI ? second : first) as Record<string, any>;
|
|
273
|
-
const
|
|
277
|
+
const callerOptions = (calledWithAI ? third : second) as Readonly<TOptions> | undefined;
|
|
278
|
+
|
|
279
|
+
// Merge agent-level forward options as defaults under caller-supplied options
|
|
280
|
+
const options = this.agentForwardOptions
|
|
281
|
+
? ({ ...this.agentForwardOptions, ...callerOptions } as Readonly<TOptions>)
|
|
282
|
+
: callerOptions;
|
|
274
283
|
|
|
275
284
|
return { ai, values, options, calledWithAI };
|
|
276
285
|
}
|
|
@@ -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 --
|
|
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
|
|
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;
|