diff --git a/.changeset/brave-aliens-study.md b/.changeset/brave-aliens-study.md new file mode 100644 index 00000000..cf03f8f8 --- /dev/null +++ b/.changeset/brave-aliens-study.md @@ -0,0 +1,5 @@ +--- +'@openai/agents-realtime': patch +--- + +fix: #639 Type issue with realtime agent handoffs diff --git a/packages/agents-realtime/src/realtimeAgent.ts b/packages/agents-realtime/src/realtimeAgent.ts index e170e6b6..82a58187 100644 --- a/packages/agents-realtime/src/realtimeAgent.ts +++ b/packages/agents-realtime/src/realtimeAgent.ts @@ -9,7 +9,7 @@ import { RealtimeContextData } from './realtimeSession'; export type RealtimeAgentConfiguration = Partial< Omit< - AgentConfiguration, + AgentConfiguration, TextOutput>, | 'model' | 'handoffs' | 'modelSettings' @@ -29,7 +29,10 @@ export type RealtimeAgentConfiguration = Partial< /** * Any other `RealtimeAgent` instances the agent is able to hand off to. */ - handoffs?: (RealtimeAgent | Handoff)[]; + handoffs?: ( + | RealtimeAgent + | Handoff, TextOutput> + )[]; /** * The voice intended to be used by the agent. If another agent already spoke during the * RealtimeSession, changing the voice during a handoff will fail. diff --git a/packages/agents-realtime/test/realtimeAgentHandoffs.test.ts b/packages/agents-realtime/test/realtimeAgentHandoffs.test.ts new file mode 100644 index 00000000..b3f40a80 --- /dev/null +++ b/packages/agents-realtime/test/realtimeAgentHandoffs.test.ts @@ -0,0 +1,107 @@ +import { describe, expect, it } from 'vitest'; +import { z } from 'zod'; +import { + RealtimeAgent, + tool, + RealtimeContextData, + type RealtimeAgentConfiguration, +} from '../src'; + +describe('RealtimeAgent handoffs', () => { + it('accepts handoffs sharing the session context', () => { + type SessionContext = { userId: string }; + + const specialist = new RealtimeAgent({ + name: 'specialist', + }); + + const mainAgent = new RealtimeAgent({ + name: 'main', + handoffs: [specialist], + }); + + expect(mainAgent.handoffs).toEqual([specialist]); + }); + + it('accepts handoffs with default context parameters', () => { + const specialist = new RealtimeAgent({ + name: 'specialist', + }); + + const mainAgent = new RealtimeAgent({ + name: 'main', + handoffs: [specialist], + }); + + expect(mainAgent.handoffs).toEqual([specialist]); + }); + + it('supports tool definitions without RealtimeContextData', () => { + type SessionContext = { userId: string }; + const parameters = z.object({ message: z.string() }); + const echoTool = tool({ + name: 'echo', + description: 'Echo the user id with the provided message.', + parameters, + execute: async ({ message }, runContext) => { + // if you want to access history data, the type parameter must be RealtimeContextData + // console.log(runContext?.context?.history); + return `${runContext?.context?.userId}: ${message}`; + }, + }); + + const agent = new RealtimeAgent({ + name: 'Tool Agent', + tools: [echoTool], + }); + expect(agent.tools).toContain(echoTool); + }); + + it('supports tool definitions that rely on RealtimeContextData', () => { + type SessionContext = { userId: string }; + const parameters = z.object({ message: z.string() }); + const echoTool = tool< + typeof parameters, + RealtimeContextData + >({ + name: 'echo', + description: 'Echo the user id with the provided message.', + parameters, + execute: async ({ message }, runContext) => { + // if you want to access history data, the type parameter must be RealtimeContextData + console.log(runContext?.context?.history); + return `${runContext?.context?.userId}: ${message}`; + }, + }); + + const agent = new RealtimeAgent({ + name: 'Tool Agent', + tools: [echoTool], + }); + expect(agent.tools).toContain(echoTool); + }); + + it('rejects handoffs with incompatible session contexts', () => { + type SessionContext = { userId: string }; + type OtherContext = { language: string }; + + const specialist = new RealtimeAgent({ + name: 'specialist', + }); + + const validConfig: RealtimeAgentConfiguration = { + name: 'main', + handoffs: [specialist], + }; + + expect(validConfig.handoffs).toEqual([specialist]); + + const invalidConfig: RealtimeAgentConfiguration = { + name: 'incompatible', + // @ts-expect-error - mismatched handoff context should not be allowed + handoffs: [specialist], + }; + + expect(invalidConfig).toBeDefined(); + }); +});