Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/sixty-hotels-care.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@openai/agents-realtime': patch
---

Add backgroundResult as an option to return tool results without triggering a new response
4 changes: 2 additions & 2 deletions .changeset/tiny-maps-begin.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
"@openai/agents-core": patch
"@openai/agents-openai": patch
'@openai/agents-core': patch
'@openai/agents-openai': patch
---

Fix #374 add connector support
7 changes: 7 additions & 0 deletions .changeset/two-spiders-end.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@openai/agents-extensions': minor
'@openai/agents-realtime': minor
'@openai/agents-core': minor
---

moving realtime to the new GA API and add MCP support
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ Before submitting changes, ensure all checks pass:
```bash
pnpm -r build-check
```
NEVER USE `-w` or other watch modes.
- Run the full test suite:
```bash
CI=1 pnpm test
Expand Down
7 changes: 4 additions & 3 deletions docs/src/content/docs/guides/voice-agents/quickstart.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,12 @@ import thinClientExample from '../../../../../../examples/docs/voice-agents/thin
As this application will run in the user's browser, we need a secure way to connect to the model through the Realtime API. For this we can use an [ephemeral client key](https://platform.openai.com/docs/guides/realtime#creating-an-ephemeral-token) that should be generated on your backend server. For testing purposes you can also generate a key using `curl` and your regular OpenAI API key.

```bash
curl -X POST https://api.openai.com/v1/realtime/sessions \
curl -X POST https://api.openai.com/v1/realtime/client_secrets \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-4o-realtime-preview-2025-06-03"
"type": "realtime",
"model": "gpt-realtime"
}'
```

Expand All @@ -78,7 +79,7 @@ import thinClientExample from '../../../../../../examples/docs/voice-agents/thin
import { RealtimeSession } from '@openai/agents-realtime';

const session = new RealtimeSession(agent, {
model: 'gpt-4o-realtime-preview-2025-06-03',
model: 'gpt-realtime',
});
```

Expand Down
7 changes: 4 additions & 3 deletions docs/src/content/docs/ja/guides/voice-agents/quickstart.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,12 @@ import thinClientExample from '../../../../../../../examples/docs/voice-agents/t
このアプリケーションは ユーザー のブラウザで実行されるため、Realtime API を通じてモデルに安全に接続する必要があります。そのために、バックエンド サーバー で生成する [ephemeral client key](https://platform.openai.com/docs/guides/realtime#creating-an-ephemeral-token) を使用できます。テスト目的では、`curl` と通常の OpenAI API キーを使ってキーを生成することもできます。

```bash
curl -X POST https://api.openai.com/v1/realtime/sessions \
curl -X POST https://api.openai.com/v1/realtime/client_secrets \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-4o-realtime-preview-2025-06-03"
"type": "realtime",
"model": "gpt-realtime"
}'
```

Expand All @@ -78,7 +79,7 @@ import thinClientExample from '../../../../../../../examples/docs/voice-agents/t
import { RealtimeSession } from '@openai/agents-realtime';

const session = new RealtimeSession(agent, {
model: 'gpt-4o-realtime-preview-2025-06-03',
model: 'gpt-realtime',
});
```

Expand Down
2 changes: 1 addition & 1 deletion examples/docs/voice-agents/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ export const agent = new RealtimeAgent({
});

export const session = new RealtimeSession(agent, {
model: 'gpt-4o-realtime-preview-2025-06-03',
model: 'gpt-realtime',
});
2 changes: 1 addition & 1 deletion examples/docs/voice-agents/configureSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const agent = new RealtimeAgent({
});

const session = new RealtimeSession(agent, {
model: 'gpt-4o-realtime-preview-2025-06-03',
model: 'gpt-realtime',
config: {
inputAudioFormat: 'pcm16',
outputAudioFormat: 'pcm16',
Expand Down
2 changes: 1 addition & 1 deletion examples/docs/voice-agents/createSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const agent = new RealtimeAgent({
async function main() {
// define which agent you want to start your session with
const session = new RealtimeSession(agent, {
model: 'gpt-4o-realtime-preview-2025-06-03',
model: 'gpt-realtime',
});
// start your session
await session.connect({ apiKey: '<your api key>' });
Expand Down
2 changes: 1 addition & 1 deletion examples/docs/voice-agents/sendMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const agent = new RealtimeAgent({
});

const session = new RealtimeSession(agent, {
model: 'gpt-4o-realtime-preview-2025-06-03',
model: 'gpt-realtime',
});

session.sendMessage('Hello, how are you?');
2 changes: 1 addition & 1 deletion examples/docs/voice-agents/transportEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const agent = new RealtimeAgent({
});

const session = new RealtimeSession(agent, {
model: 'gpt-4o-realtime-preview-2025-06-03',
model: 'gpt-realtime',
});

session.transport.on('*', (event) => {
Expand Down
2 changes: 1 addition & 1 deletion examples/docs/voice-agents/turnDetection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { RealtimeSession } from '@openai/agents/realtime';
import { agent } from './agent';

const session = new RealtimeSession(agent, {
model: 'gpt-4o-realtime-preview-2025-06-03',
model: 'gpt-realtime',
config: {
turnDetection: {
type: 'semantic_vad',
Expand Down
2 changes: 1 addition & 1 deletion examples/docs/voice-agents/updateHistory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const agent = new RealtimeAgent({
});

const session = new RealtimeSession(agent, {
model: 'gpt-4o-realtime-preview-2025-06-03',
model: 'gpt-realtime',
});

await session.connect({ apiKey: '<client-api-key>' });
Expand Down
2 changes: 1 addition & 1 deletion examples/docs/voice-agents/websocketSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const myRecordedArrayBuffer = new ArrayBuffer(0);

const wsSession = new RealtimeSession(agent, {
transport: 'websocket',
model: 'gpt-4o-realtime-preview-2025-06-03',
model: 'gpt-realtime',
});
await wsSession.connect({ apiKey: process.env.OPENAI_API_KEY! });

Expand Down
2 changes: 1 addition & 1 deletion examples/realtime-demo/token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ async function generateToken() {
});

const session = await openai.beta.realtime.sessions.create({
model: 'gpt-4o-realtime-preview',
model: 'gpt-realtime',
});

console.log(session.client_secret.value);
Expand Down
65 changes: 49 additions & 16 deletions examples/realtime-next/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@ import {
OutputGuardrailTripwireTriggered,
RealtimeItem,
RealtimeContextData,
backgroundResult,
} from '@openai/agents/realtime';
import { useEffect, useRef, useState } from 'react';
import { z } from 'zod';
import { handleRefundRequest } from './server/backendAgent';
import { getToken } from './server/token';
import { handleRefundRequest } from './server/backendAgent.action';
import { getToken } from './server/token.action';
import { App } from '@/components/App';
import { hostedMcpTool } from '@openai/agents';
import { CameraCapture } from '@/components/CameraCapture';

const params = z.object({
request: z.string(),
Expand All @@ -36,7 +39,7 @@ const weatherTool = tool({
location: z.string(),
}),
execute: async ({ location }) => {
return `The weather in ${location} is sunny.`;
return backgroundResult(`The weather in ${location} is sunny.`);
},
});

Expand Down Expand Up @@ -67,9 +70,15 @@ const weatherExpert = new RealtimeAgent({

const agent = new RealtimeAgent({
name: 'Greeter',
instructions:
'You are a greeter. Always greet the user with a "top of the morning" at the start of the conversation. When you use a tool always first say what you are about to do.',
tools: [refundBackchannel, secretTool],
instructions: 'You are a greeter',
tools: [
refundBackchannel,
secretTool,
hostedMcpTool({
serverLabel: 'deepwiki',
}),
weatherTool,
],
handoffs: [weatherExpert],
});

Expand Down Expand Up @@ -97,17 +106,29 @@ export default function Home() {

const [events, setEvents] = useState<TransportEvent[]>([]);
const [history, setHistory] = useState<RealtimeItem[]>([]);
const [mcpTools, setMcpTools] = useState<string[]>([]);

useEffect(() => {
session.current = new RealtimeSession(agent, {
model: 'gpt-realtime',
outputGuardrails: guardrails,
outputGuardrailSettings: {
debounceTextLength: 200,
},
config: {
audio: {
output: {
voice: 'cedar',
},
},
},
});
session.current.on('transport_event', (event) => {
setEvents((events) => [...events, event]);
});
session.current.on('mcp_tools_changed', (tools) => {
setMcpTools(tools.map((t) => t.name));
});
session.current.on(
'guardrail_tripped',
(_context, _agent, guardrailError) => {
Expand All @@ -122,7 +143,7 @@ export default function Home() {
(_context, _agent, approvalRequest) => {
// You'll be prompted when making the tool call that requires approval in web browser.
const approved = confirm(
`Approve tool call to ${approvalRequest.tool.name} with parameters:\n ${JSON.stringify(approvalRequest.tool.parameters, null, 2)}?`,
`Approve tool call to ${approvalRequest.approvalItem.rawItem.name} with parameters:\n ${JSON.stringify(approvalRequest.approvalItem.rawItem.arguments, null, 2)}?`,
);
if (approved) {
session.current?.approve(approvalRequest.approvalItem);
Expand Down Expand Up @@ -161,14 +182,26 @@ export default function Home() {
}

return (
<App
isConnected={isConnected}
isMuted={isMuted}
toggleMute={toggleMute}
connect={connect}
history={history}
outputGuardrailResult={outputGuardrailResult}
events={events}
/>
<div className="relative">
<App
isConnected={isConnected}
isMuted={isMuted}
toggleMute={toggleMute}
connect={connect}
history={history}
outputGuardrailResult={outputGuardrailResult}
events={events}
mcpTools={mcpTools}
/>
<div className="fixed bottom-4 right-4 z-50">
<CameraCapture
disabled={!isConnected}
onCapture={(dataUrl) => {
if (!session.current) return;
session.current.addImage(dataUrl, { triggerResponse: false });
}}
/>
</div>
</div>
);
}
4 changes: 2 additions & 2 deletions examples/realtime-next/src/app/raw-client/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { TransportEvent, OpenAIRealtimeWebRTC } from '@openai/agents/realtime';
import { useEffect, useRef, useState } from 'react';
import { getToken } from '../server/token';
import { getToken } from '../server/token.action';
import { App } from '@/components/App';

export default function Home() {
Expand Down Expand Up @@ -32,7 +32,7 @@ export default function Home() {
model: 'gpt-4o-mini-realtime-preview',
initialSessionConfig: {
instructions: 'Speak like a pirate',
voice: 'ash',
voice: 'marin',
modalities: ['text', 'audio'],
inputAudioFormat: 'pcm16',
outputAudioFormat: 'pcm16',
Expand Down
63 changes: 63 additions & 0 deletions examples/realtime-next/src/app/server/token.action.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
'use server';

export async function getToken() {
const apiKey = process.env.OPENAI_API_KEY;
if (!apiKey) {
throw new Error('Missing OPENAI_API_KEY environment variable.');
}

const response = await fetch(
'https://api.openai.com/v1/realtime/client_secrets',
{
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
session: {
type: 'realtime',
model: 'gpt-realtime',
tools: [
{
type: 'mcp',
server_label: 'deepwiki',
server_url: 'https://mcp.deepwiki.com/sse',
require_approval: 'always',
},
{
type: 'mcp',
server_label: 'dnd',
server_url: 'https://dmcp-server.deno.dev/sse',
require_approval: 'always',
},
],
// tracing: {
// workflow_name: 'Realtime Next Demo',
// },
},
}),
},
);

if (!response.ok) {
let detail = '';
try {
const errJson = await response.json();
detail = JSON.stringify(errJson);
} catch {
detail = await response.text();
}
throw new Error(
`Failed to create ephemeral client secret: ${response.status} ${response.statusText}${detail ? ` - ${detail}` : ''}`,
);
}

const clientSecret: {
value: string;
expires_at: number;
session: Record<string, unknown>;
} = await response.json();

return clientSecret.value;
}
18 changes: 0 additions & 18 deletions examples/realtime-next/src/app/server/token.tsx

This file was deleted.

Loading