Skip to content

Commit 0fd8b6e

Browse files
authored
feat: #478 add isEnabled to handoffs & agents as tools (#525)
1 parent 2020757 commit 0fd8b6e

File tree

19 files changed

+757
-23
lines changed

19 files changed

+757
-23
lines changed

.changeset/loud-bags-enter.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@openai/agents-realtime': patch
3+
'@openai/agents-core': patch
4+
---
5+
6+
feat: #478 add isEnabled to handoffs & agents as tools

examples/agent-patterns/README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
# Agent Pattern Examples
32

43
This directory contains small scripts that demonstrate different agent patterns.
@@ -8,6 +7,10 @@ Run them with `pnpm` using the commands shown below.
87
```bash
98
pnpm examples:agents-as-tools
109
```
10+
- `agents-as-tools-conditional.ts` – Enable language tools based on user preference.
11+
```bash
12+
pnpm examples:agents-as-tools-conditional
13+
```
1114
- `deterministic.ts` – Fixed agent flow with gating and quality checks.
1215
```bash
1316
pnpm examples:deterministic
@@ -52,4 +55,3 @@ Run them with `pnpm` using the commands shown below.
5255
```bash
5356
pnpm -F agent-patterns start:streaming-guardrails
5457
```
55-
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import { Agent, RunContext, run, withTrace } from '@openai/agents';
2+
import { createInterface } from 'node:readline/promises';
3+
4+
const introduction =
5+
`Agents-as-Tools with Conditional Enabling\n\n` +
6+
'This demonstrates how language response tools are dynamically enabled based on user preferences.\n';
7+
8+
type LanguagePreference = 'spanish_only' | 'french_spanish' | 'european';
9+
10+
type AppContext = {
11+
languagePreference: LanguagePreference;
12+
};
13+
14+
function frenchSpanishEnabled({
15+
runContext,
16+
}: {
17+
runContext: RunContext<AppContext>;
18+
}) {
19+
return (
20+
runContext.context.languagePreference === 'french_spanish' ||
21+
runContext.context.languagePreference === 'european'
22+
);
23+
}
24+
25+
function europeanEnabled({
26+
runContext,
27+
}: {
28+
runContext: RunContext<AppContext>;
29+
}) {
30+
return runContext.context.languagePreference === 'european';
31+
}
32+
33+
const spanishAgent = new Agent<AppContext>({
34+
name: 'spanish_agent',
35+
instructions:
36+
"You respond in Spanish. Always reply to the user's question in Spanish.",
37+
});
38+
39+
const frenchAgent = new Agent<AppContext>({
40+
name: 'french_agent',
41+
instructions:
42+
"You respond in French. Always reply to the user's question in French.",
43+
});
44+
45+
const italianAgent = new Agent<AppContext>({
46+
name: 'italian_agent',
47+
instructions:
48+
"You respond in Italian. Always reply to the user's question in Italian.",
49+
});
50+
51+
const orchestrator = new Agent<AppContext>({
52+
name: 'orchestrator',
53+
instructions: [
54+
'You are a multilingual assistant. You use the tools given to you to respond to users.',
55+
'You must call all available tools to provide responses in different languages.',
56+
'You never respond in languages yourself, you always use the provided tools.',
57+
].join(' '),
58+
tools: [
59+
spanishAgent.asTool({
60+
toolName: 'respond_spanish',
61+
toolDescription: "Respond to the user's question in Spanish.",
62+
isEnabled: true,
63+
}),
64+
frenchAgent.asTool({
65+
toolName: 'respond_french',
66+
toolDescription: "Respond to the user's question in French.",
67+
isEnabled: frenchSpanishEnabled,
68+
}),
69+
italianAgent.asTool({
70+
toolName: 'respond_italian',
71+
toolDescription: "Respond to the user's question in Italian.",
72+
isEnabled: europeanEnabled,
73+
}),
74+
],
75+
});
76+
77+
async function main() {
78+
const rl = createInterface({ input: process.stdin, output: process.stdout });
79+
80+
try {
81+
console.log(introduction);
82+
console.log('Choose language preference:');
83+
console.log('1. Spanish only (1 tool)');
84+
console.log('2. French and Spanish (2 tools)');
85+
console.log('3. European languages (3 tools)');
86+
87+
const choice = (await rl.question('\nSelect option (1-3): ')).trim();
88+
const preferenceMap: Record<string, LanguagePreference> = {
89+
'1': 'spanish_only',
90+
'2': 'french_spanish',
91+
'3': 'european',
92+
};
93+
const languagePreference = preferenceMap[choice] ?? 'spanish_only';
94+
95+
const runContext = new RunContext<AppContext>({ languagePreference });
96+
const availableTools = await orchestrator.getAllTools(runContext);
97+
const toolNames = availableTools.map((tool) => tool.name).join(', ');
98+
99+
console.log(`\nLanguage preference: ${languagePreference}`);
100+
console.log(
101+
toolNames
102+
? `Available tools: ${toolNames} (the model sees ${availableTools.length} tools).`
103+
: 'Available tools: none.',
104+
);
105+
106+
const userRequest = await rl.question(
107+
'\nAsk a question and see responses in the available languages:\n',
108+
);
109+
110+
if (!userRequest.trim()) {
111+
console.log('No question provided, exiting.');
112+
return;
113+
}
114+
115+
console.log('\nProcessing request...');
116+
await withTrace('Conditional tool access', async () => {
117+
const result = await run(orchestrator, userRequest, {
118+
context: runContext,
119+
});
120+
console.log(`\nResponse:\n${result.finalOutput}`);
121+
});
122+
} finally {
123+
await rl.close();
124+
}
125+
}
126+
127+
if (require.main === module) {
128+
main().catch((error) => {
129+
console.error('Error:', error);
130+
process.exitCode = 1;
131+
});
132+
}

examples/agent-patterns/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"scripts": {
1010
"build-check": "tsc --noEmit",
1111
"start:agents-as-tools": "tsx agents-as-tools.ts",
12+
"start:agents-as-tools-conditional": "tsx agents-as-tools-conditional.ts",
1213
"start:deterministic": "tsx deterministic.ts",
1314
"start:forcing-tool-use": "tsx forcing-tool-use.ts -t default",
1415
"start:human-in-the-loop-stream": "tsx human-in-the-loop-stream.ts",

examples/handoffs/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,9 @@ pnpm -F handoffs start
77
```
88

99
`types.ts` demonstrates typed outputs. A triage agent inspects the message and hands off to either `firstAgent` or `secondAgent`, each with their own Zod schema for structured output. The script logs which agent produced the final result.
10+
11+
`is-enabled.ts` demonstrates gating handoffs with feature-like preferences. Run it with:
12+
13+
```bash
14+
pnpm -F handoffs start:is-enabled
15+
```

examples/handoffs/is-enabled.ts

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import { Agent, RunContext, handoff, run, withTrace } from '@openai/agents';
2+
import { createInterface } from 'node:readline/promises';
3+
4+
const introduction =
5+
`Handoffs with Conditional Enabling\n\n` +
6+
'This routes conversations to language specialists based on user preferences.\n';
7+
8+
type LanguagePreference = 'spanish_only' | 'french_spanish' | 'european';
9+
10+
type AppContext = {
11+
languagePreference: LanguagePreference;
12+
};
13+
14+
function frenchSpanishEnabled({
15+
runContext,
16+
}: {
17+
runContext: RunContext<AppContext>;
18+
}) {
19+
return (
20+
runContext.context.languagePreference === 'french_spanish' ||
21+
runContext.context.languagePreference === 'european'
22+
);
23+
}
24+
25+
function europeanEnabled({
26+
runContext,
27+
}: {
28+
runContext: RunContext<AppContext>;
29+
}) {
30+
return runContext.context.languagePreference === 'european';
31+
}
32+
33+
const spanishAgent = new Agent<AppContext>({
34+
name: 'spanish_agent',
35+
instructions:
36+
"You respond in Spanish. Always reply to the user's question in Spanish.",
37+
handoffDescription: 'Spanish speaking specialist.',
38+
});
39+
40+
const frenchAgent = new Agent<AppContext>({
41+
name: 'french_agent',
42+
instructions:
43+
"You respond in French. Always reply to the user's question in French.",
44+
handoffDescription: 'French speaking specialist.',
45+
});
46+
47+
const italianAgent = new Agent<AppContext>({
48+
name: 'italian_agent',
49+
instructions:
50+
"You respond in Italian. Always reply to the user's question in Italian.",
51+
handoffDescription: 'Italian speaking specialist.',
52+
});
53+
54+
const spanishHandoff = handoff(spanishAgent, {
55+
toolDescriptionOverride:
56+
'Transfer the conversation to the Spanish specialist.',
57+
});
58+
59+
const frenchHandoff = handoff(frenchAgent, {
60+
isEnabled: frenchSpanishEnabled,
61+
toolDescriptionOverride:
62+
'Transfer the conversation to the French specialist.',
63+
});
64+
65+
const italianHandoff = handoff(italianAgent, {
66+
isEnabled: europeanEnabled,
67+
toolDescriptionOverride:
68+
'Transfer the conversation to the Italian specialist.',
69+
});
70+
71+
const triageAgent = new Agent<AppContext>({
72+
name: 'triage_agent',
73+
instructions: [
74+
'You triage multilingual conversations.',
75+
'When a specialist handoff is available you immediately transfer the conversation.',
76+
'If no specialist is available you answer in English and explain the limitation.',
77+
].join(' '),
78+
handoffs: [spanishHandoff, frenchHandoff, italianHandoff],
79+
});
80+
81+
async function main() {
82+
const rl = createInterface({ input: process.stdin, output: process.stdout });
83+
84+
try {
85+
console.log(introduction);
86+
console.log('Choose language preference:');
87+
console.log('1. Spanish only (handoff to Spanish agent)');
88+
console.log(
89+
'2. French and Spanish (handoffs to Spanish and French agents)',
90+
);
91+
console.log('3. European languages (handoffs to all three agents)');
92+
93+
const choice = (await rl.question('\nSelect option (1-3): ')).trim();
94+
const preferenceMap: Record<string, LanguagePreference> = {
95+
'1': 'spanish_only',
96+
'2': 'french_spanish',
97+
'3': 'european',
98+
};
99+
const languagePreference = preferenceMap[choice] ?? 'spanish_only';
100+
101+
const runContext = new RunContext<AppContext>({ languagePreference });
102+
const enabledHandoffs = await triageAgent.getEnabledHandoffs(runContext);
103+
const handoffNames = enabledHandoffs
104+
.map((handoffItem) => handoffItem.toolName)
105+
.join(', ');
106+
107+
console.log(`\nLanguage preference: ${languagePreference}`);
108+
console.log(
109+
handoffNames
110+
? `Available handoffs: ${handoffNames} (the model sees ${enabledHandoffs.length} options).`
111+
: 'Available handoffs: none.',
112+
);
113+
114+
const userRequest = await rl.question(
115+
'\nAsk a question and see which specialist handles the conversation:\n',
116+
);
117+
118+
if (!userRequest.trim()) {
119+
console.log('No question provided, exiting.');
120+
return;
121+
}
122+
123+
console.log('\nProcessing request...');
124+
await withTrace('Conditional handoff access', async () => {
125+
const result = await run(triageAgent, userRequest, {
126+
context: runContext,
127+
});
128+
const finalAgentName = result.lastAgent?.name ?? triageAgent.name;
129+
console.log(`\nFinal agent: ${finalAgentName}`);
130+
console.log(`\nResponse:\n${result.finalOutput}`);
131+
});
132+
} finally {
133+
await rl.close();
134+
}
135+
}
136+
137+
if (require.main === module) {
138+
main().catch((error) => {
139+
console.error('Error:', error);
140+
process.exitCode = 1;
141+
});
142+
}

examples/handoffs/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"scripts": {
1010
"build-check": "tsc --noEmit",
1111
"start": "tsx index.ts",
12-
"start:types": "tsx types.ts"
12+
"start:types": "tsx types.ts",
13+
"start:is-enabled": "tsx is-enabled.ts"
1314
}
1415
}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"lint:fix": "eslint --fix",
2222
"examples:basic": "pnpm -F basic start",
2323
"examples:agents-as-tools": "pnpm -F agent-patterns start:agents-as-tools",
24+
"examples:agents-as-tools-conditional": "pnpm -F agent-patterns start:agents-as-tools-conditional",
2425
"examples:deterministic": "pnpm -F agent-patterns start:deterministic",
2526
"examples:parallelization": "pnpm -F agent-patterns start:parallelization",
2627
"examples:human-in-the-loop": "pnpm -F agent-patterns start:human-in-the-loop",

0 commit comments

Comments
 (0)