Skip to content

Commit 642a79b

Browse files
authored
fix: #639 Type issue with realtime agent handoffs (#641)
1 parent 4c1192d commit 642a79b

File tree

3 files changed

+117
-2
lines changed

3 files changed

+117
-2
lines changed

.changeset/brave-aliens-study.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@openai/agents-realtime': patch
3+
---
4+
5+
fix: #639 Type issue with realtime agent handoffs

packages/agents-realtime/src/realtimeAgent.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { RealtimeContextData } from './realtimeSession';
99

1010
export type RealtimeAgentConfiguration<TContext = UnknownContext> = Partial<
1111
Omit<
12-
AgentConfiguration<TContext, TextOutput>,
12+
AgentConfiguration<RealtimeContextData<TContext>, TextOutput>,
1313
| 'model'
1414
| 'handoffs'
1515
| 'modelSettings'
@@ -29,7 +29,10 @@ export type RealtimeAgentConfiguration<TContext = UnknownContext> = Partial<
2929
/**
3030
* Any other `RealtimeAgent` instances the agent is able to hand off to.
3131
*/
32-
handoffs?: (RealtimeAgent | Handoff)[];
32+
handoffs?: (
33+
| RealtimeAgent<TContext>
34+
| Handoff<RealtimeContextData<TContext>, TextOutput>
35+
)[];
3336
/**
3437
* The voice intended to be used by the agent. If another agent already spoke during the
3538
* RealtimeSession, changing the voice during a handoff will fail.
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { describe, expect, it } from 'vitest';
2+
import { z } from 'zod';
3+
import {
4+
RealtimeAgent,
5+
tool,
6+
RealtimeContextData,
7+
type RealtimeAgentConfiguration,
8+
} from '../src';
9+
10+
describe('RealtimeAgent handoffs', () => {
11+
it('accepts handoffs sharing the session context', () => {
12+
type SessionContext = { userId: string };
13+
14+
const specialist = new RealtimeAgent<SessionContext>({
15+
name: 'specialist',
16+
});
17+
18+
const mainAgent = new RealtimeAgent<SessionContext>({
19+
name: 'main',
20+
handoffs: [specialist],
21+
});
22+
23+
expect(mainAgent.handoffs).toEqual([specialist]);
24+
});
25+
26+
it('accepts handoffs with default context parameters', () => {
27+
const specialist = new RealtimeAgent({
28+
name: 'specialist',
29+
});
30+
31+
const mainAgent = new RealtimeAgent({
32+
name: 'main',
33+
handoffs: [specialist],
34+
});
35+
36+
expect(mainAgent.handoffs).toEqual([specialist]);
37+
});
38+
39+
it('supports tool definitions without RealtimeContextData', () => {
40+
type SessionContext = { userId: string };
41+
const parameters = z.object({ message: z.string() });
42+
const echoTool = tool<typeof parameters, SessionContext>({
43+
name: 'echo',
44+
description: 'Echo the user id with the provided message.',
45+
parameters,
46+
execute: async ({ message }, runContext) => {
47+
// if you want to access history data, the type parameter must be RealtimeContextData<SessionContext>
48+
// console.log(runContext?.context?.history);
49+
return `${runContext?.context?.userId}: ${message}`;
50+
},
51+
});
52+
53+
const agent = new RealtimeAgent<SessionContext>({
54+
name: 'Tool Agent',
55+
tools: [echoTool],
56+
});
57+
expect(agent.tools).toContain(echoTool);
58+
});
59+
60+
it('supports tool definitions that rely on RealtimeContextData', () => {
61+
type SessionContext = { userId: string };
62+
const parameters = z.object({ message: z.string() });
63+
const echoTool = tool<
64+
typeof parameters,
65+
RealtimeContextData<SessionContext>
66+
>({
67+
name: 'echo',
68+
description: 'Echo the user id with the provided message.',
69+
parameters,
70+
execute: async ({ message }, runContext) => {
71+
// if you want to access history data, the type parameter must be RealtimeContextData<SessionContext>
72+
console.log(runContext?.context?.history);
73+
return `${runContext?.context?.userId}: ${message}`;
74+
},
75+
});
76+
77+
const agent = new RealtimeAgent<SessionContext>({
78+
name: 'Tool Agent',
79+
tools: [echoTool],
80+
});
81+
expect(agent.tools).toContain(echoTool);
82+
});
83+
84+
it('rejects handoffs with incompatible session contexts', () => {
85+
type SessionContext = { userId: string };
86+
type OtherContext = { language: string };
87+
88+
const specialist = new RealtimeAgent<SessionContext>({
89+
name: 'specialist',
90+
});
91+
92+
const validConfig: RealtimeAgentConfiguration<SessionContext> = {
93+
name: 'main',
94+
handoffs: [specialist],
95+
};
96+
97+
expect(validConfig.handoffs).toEqual([specialist]);
98+
99+
const invalidConfig: RealtimeAgentConfiguration<OtherContext> = {
100+
name: 'incompatible',
101+
// @ts-expect-error - mismatched handoff context should not be allowed
102+
handoffs: [specialist],
103+
};
104+
105+
expect(invalidConfig).toBeDefined();
106+
});
107+
});

0 commit comments

Comments
 (0)