From 9c2ac47e693d4d9a7206f7a69e7ed035607461e1 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 2 Oct 2025 20:13:22 +0000 Subject: [PATCH 1/2] feat(api): add support for realtime calls --- .stats.yml | 8 +- MIGRATION.md | 1 + api.md | 9 + scripts/detect-breaking-changes | 1 + src/resources/realtime/calls.ts | 204 +++++++++++++++++++++ src/resources/realtime/client-secrets.ts | 6 + src/resources/realtime/index.ts | 1 + src/resources/realtime/realtime.ts | 11 ++ tests/api-resources/realtime/calls.test.ts | 98 ++++++++++ 9 files changed, 335 insertions(+), 4 deletions(-) create mode 100644 src/resources/realtime/calls.ts create mode 100644 tests/api-resources/realtime/calls.test.ts diff --git a/.stats.yml b/.stats.yml index 27f2ffc6d..4e40d470f 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 118 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/openai%2Fopenai-e205b1f2da6a1f2caa229efa9ede63f2d3d2fedeeb2dd6ed3d880bafdcb0ab88.yml -openapi_spec_hash: c8aee2469a749f6a838b40c57e4b7b06 -config_hash: 45dcba51451ba532959c020a0ddbf23c +configured_endpoints: 122 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/openai%2Fopenai-fadefdc7c7e30df47c09df323669b242ff90ee08e51f304175ace5274e0aab49.yml +openapi_spec_hash: 6d20f639d9ff8a097a34962da6218231 +config_hash: 902654e60f5d659f2bfcfd903e17c46d diff --git a/MIGRATION.md b/MIGRATION.md index 3fb14f8bc..96f665c17 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -133,6 +133,7 @@ client.example.list(undefined, { headers: { ... } }); - `client.batches.list()` - `client.responses.retrieve()` - `client.responses.inputItems.list()` +- `client.realtime.calls.reject()` - `client.conversations.create()` - `client.conversations.items.list()` - `client.evals.list()` diff --git a/api.md b/api.md index 6316bfeb3..b6e346562 100644 --- a/api.md +++ b/api.md @@ -897,6 +897,15 @@ Methods: - client.realtime.clientSecrets.create({ ...params }) -> ClientSecretCreateResponse +## Calls + +Methods: + +- client.realtime.calls.accept(callID, { ...params }) -> void +- client.realtime.calls.hangup(callID) -> void +- client.realtime.calls.refer(callID, { ...params }) -> void +- client.realtime.calls.reject(callID, { ...params }) -> void + # Conversations Types: diff --git a/scripts/detect-breaking-changes b/scripts/detect-breaking-changes index 85607de43..4f79a5486 100755 --- a/scripts/detect-breaking-changes +++ b/scripts/detect-breaking-changes @@ -46,6 +46,7 @@ TEST_PATHS=( tests/api-resources/responses/input-items.test.ts tests/api-resources/realtime/realtime.test.ts tests/api-resources/realtime/client-secrets.test.ts + tests/api-resources/realtime/calls.test.ts tests/api-resources/conversations/conversations.test.ts tests/api-resources/conversations/items.test.ts tests/api-resources/evals/evals.test.ts diff --git a/src/resources/realtime/calls.ts b/src/resources/realtime/calls.ts new file mode 100644 index 000000000..089855fc2 --- /dev/null +++ b/src/resources/realtime/calls.ts @@ -0,0 +1,204 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { APIResource } from '../../core/resource'; +import * as RealtimeAPI from './realtime'; +import * as ResponsesAPI from '../responses/responses'; +import { APIPromise } from '../../core/api-promise'; +import { buildHeaders } from '../../internal/headers'; +import { RequestOptions } from '../../internal/request-options'; +import { path } from '../../internal/utils/path'; + +export class Calls extends APIResource { + /** + * Accept an incoming SIP call and configure the realtime session that will handle + * it. + * + * @example + * ```ts + * await client.realtime.calls.accept('call_id', { + * type: 'realtime', + * }); + * ``` + */ + accept(callID: string, body: CallAcceptParams, options?: RequestOptions): APIPromise { + return this._client.post(path`/realtime/calls/${callID}/accept`, { + body, + ...options, + headers: buildHeaders([{ Accept: '*/*' }, options?.headers]), + }); + } + + /** + * End an active Realtime API call, whether it was initiated over SIP or WebRTC. + * + * @example + * ```ts + * await client.realtime.calls.hangup('call_id'); + * ``` + */ + hangup(callID: string, options?: RequestOptions): APIPromise { + return this._client.post(path`/realtime/calls/${callID}/hangup`, { + ...options, + headers: buildHeaders([{ Accept: '*/*' }, options?.headers]), + }); + } + + /** + * Transfer an active SIP call to a new destination using the SIP REFER verb. + * + * @example + * ```ts + * await client.realtime.calls.refer('call_id', { + * target_uri: 'tel:+14155550123', + * }); + * ``` + */ + refer(callID: string, body: CallReferParams, options?: RequestOptions): APIPromise { + return this._client.post(path`/realtime/calls/${callID}/refer`, { + body, + ...options, + headers: buildHeaders([{ Accept: '*/*' }, options?.headers]), + }); + } + + /** + * Decline an incoming SIP call by returning a SIP status code to the caller. + * + * @example + * ```ts + * await client.realtime.calls.reject('call_id'); + * ``` + */ + reject( + callID: string, + body: CallRejectParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { + return this._client.post(path`/realtime/calls/${callID}/reject`, { + body, + ...options, + headers: buildHeaders([{ Accept: '*/*' }, options?.headers]), + }); + } +} + +export interface CallAcceptParams { + /** + * The type of session to create. Always `realtime` for the Realtime API. + */ + type: 'realtime'; + + /** + * Configuration for input and output audio. + */ + audio?: RealtimeAPI.RealtimeAudioConfig; + + /** + * Additional fields to include in server outputs. + * + * `item.input_audio_transcription.logprobs`: Include logprobs for input audio + * transcription. + */ + include?: Array<'item.input_audio_transcription.logprobs'>; + + /** + * The default system instructions (i.e. system message) prepended to model calls. + * This field allows the client to guide the model on desired responses. The model + * can be instructed on response content and format, (e.g. "be extremely succinct", + * "act friendly", "here are examples of good responses") and on audio behavior + * (e.g. "talk quickly", "inject emotion into your voice", "laugh frequently"). The + * instructions are not guaranteed to be followed by the model, but they provide + * guidance to the model on the desired behavior. + * + * Note that the server sets default instructions which will be used if this field + * is not set and are visible in the `session.created` event at the start of the + * session. + */ + instructions?: string; + + /** + * Maximum number of output tokens for a single assistant response, inclusive of + * tool calls. Provide an integer between 1 and 4096 to limit output tokens, or + * `inf` for the maximum available tokens for a given model. Defaults to `inf`. + */ + max_output_tokens?: number | 'inf'; + + /** + * The Realtime model used for this session. + */ + model?: + | (string & {}) + | 'gpt-realtime' + | 'gpt-realtime-2025-08-28' + | 'gpt-4o-realtime-preview' + | 'gpt-4o-realtime-preview-2024-10-01' + | 'gpt-4o-realtime-preview-2024-12-17' + | 'gpt-4o-realtime-preview-2025-06-03' + | 'gpt-4o-mini-realtime-preview' + | 'gpt-4o-mini-realtime-preview-2024-12-17'; + + /** + * The set of modalities the model can respond with. It defaults to `["audio"]`, + * indicating that the model will respond with audio plus a transcript. `["text"]` + * can be used to make the model respond with text only. It is not possible to + * request both `text` and `audio` at the same time. + */ + output_modalities?: Array<'text' | 'audio'>; + + /** + * Reference to a prompt template and its variables. + * [Learn more](https://platform.openai.com/docs/guides/text?api-mode=responses#reusable-prompts). + */ + prompt?: ResponsesAPI.ResponsePrompt | null; + + /** + * How the model chooses tools. Provide one of the string modes or force a specific + * function/MCP tool. + */ + tool_choice?: RealtimeAPI.RealtimeToolChoiceConfig; + + /** + * Tools available to the model. + */ + tools?: RealtimeAPI.RealtimeToolsConfig; + + /** + * Realtime API can write session traces to the + * [Traces Dashboard](/logs?api=traces). Set to null to disable tracing. Once + * tracing is enabled for a session, the configuration cannot be modified. + * + * `auto` will create a trace for the session with default values for the workflow + * name, group id, and metadata. + */ + tracing?: RealtimeAPI.RealtimeTracingConfig | null; + + /** + * Controls how the realtime conversation is truncated prior to model inference. + * The default is `auto`. + */ + truncation?: RealtimeAPI.RealtimeTruncation; +} + +export interface CallReferParams { + /** + * URI that should appear in the SIP Refer-To header. Supports values like + * `tel:+14155550123` or `sip:agent@example.com`. + */ + target_uri: string; +} + +export interface CallRejectParams { + /** + * SIP response code to send back to the caller. Defaults to `603` (Decline) when + * omitted. + */ + status_code?: number; +} + +export declare namespace Calls { + export { + type CallAcceptParams as CallAcceptParams, + type CallReferParams as CallReferParams, + type CallRejectParams as CallRejectParams, + }; +} diff --git a/src/resources/realtime/client-secrets.ts b/src/resources/realtime/client-secrets.ts index 70abeabbf..6289663fe 100644 --- a/src/resources/realtime/client-secrets.ts +++ b/src/resources/realtime/client-secrets.ts @@ -10,6 +10,12 @@ import { RequestOptions } from '../../internal/request-options'; export class ClientSecrets extends APIResource { /** * Create a Realtime client secret with an associated session configuration. + * + * @example + * ```ts + * const clientSecret = + * await client.realtime.clientSecrets.create(); + * ``` */ create(body: ClientSecretCreateParams, options?: RequestOptions): APIPromise { return this._client.post('/realtime/client_secrets', { body, ...options }); diff --git a/src/resources/realtime/index.ts b/src/resources/realtime/index.ts index 777543853..197aa3b56 100644 --- a/src/resources/realtime/index.ts +++ b/src/resources/realtime/index.ts @@ -1,5 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +export { Calls, type CallAcceptParams, type CallReferParams, type CallRejectParams } from './calls'; export { ClientSecrets, type RealtimeSessionClientSecret, diff --git a/src/resources/realtime/realtime.ts b/src/resources/realtime/realtime.ts index 739d61a54..49d22a39d 100644 --- a/src/resources/realtime/realtime.ts +++ b/src/resources/realtime/realtime.ts @@ -3,6 +3,8 @@ import { APIResource } from '../../core/resource'; import * as RealtimeAPI from './realtime'; import * as Shared from '../shared'; +import * as CallsAPI from './calls'; +import { CallAcceptParams, CallReferParams, CallRejectParams, Calls } from './calls'; import * as ClientSecretsAPI from './client-secrets'; import { ClientSecretCreateParams, @@ -17,6 +19,7 @@ import * as ResponsesAPI from '../responses/responses'; export class Realtime extends APIResource { clientSecrets: ClientSecretsAPI.ClientSecrets = new ClientSecretsAPI.ClientSecrets(this._client); + calls: CallsAPI.Calls = new CallsAPI.Calls(this._client); } export interface AudioTranscription { @@ -4580,6 +4583,7 @@ export namespace TranscriptionSessionUpdatedEvent { } Realtime.ClientSecrets = ClientSecrets; +Realtime.Calls = Calls; export declare namespace Realtime { export { @@ -4694,4 +4698,11 @@ export declare namespace Realtime { type ClientSecretCreateResponse as ClientSecretCreateResponse, type ClientSecretCreateParams as ClientSecretCreateParams, }; + + export { + Calls as Calls, + type CallAcceptParams as CallAcceptParams, + type CallReferParams as CallReferParams, + type CallRejectParams as CallRejectParams, + }; } diff --git a/tests/api-resources/realtime/calls.test.ts b/tests/api-resources/realtime/calls.test.ts new file mode 100644 index 000000000..34675a134 --- /dev/null +++ b/tests/api-resources/realtime/calls.test.ts @@ -0,0 +1,98 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import OpenAI from 'openai'; + +const client = new OpenAI({ + apiKey: 'My API Key', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('resource calls', () => { + test('accept: only required params', async () => { + const responsePromise = client.realtime.calls.accept('call_id', { type: 'realtime' }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + test('accept: required and optional params', async () => { + const response = await client.realtime.calls.accept('call_id', { + type: 'realtime', + audio: { + input: { + format: { rate: 24000, type: 'audio/pcm' }, + noise_reduction: { type: 'near_field' }, + transcription: { language: 'language', model: 'whisper-1', prompt: 'prompt' }, + turn_detection: { + type: 'server_vad', + create_response: true, + idle_timeout_ms: 5000, + interrupt_response: true, + prefix_padding_ms: 0, + silence_duration_ms: 0, + threshold: 0, + }, + }, + output: { format: { rate: 24000, type: 'audio/pcm' }, speed: 0.25, voice: 'ash' }, + }, + include: ['item.input_audio_transcription.logprobs'], + instructions: 'instructions', + max_output_tokens: 0, + model: 'string', + output_modalities: ['text'], + prompt: { id: 'id', variables: { foo: 'string' }, version: 'version' }, + tool_choice: 'none', + tools: [{ description: 'description', name: 'name', parameters: {}, type: 'function' }], + tracing: 'auto', + truncation: 'auto', + }); + }); + + test('hangup', async () => { + const responsePromise = client.realtime.calls.hangup('call_id'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + test('refer: only required params', async () => { + const responsePromise = client.realtime.calls.refer('call_id', { target_uri: 'tel:+14155550123' }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + test('refer: required and optional params', async () => { + const response = await client.realtime.calls.refer('call_id', { target_uri: 'tel:+14155550123' }); + }); + + test('reject', async () => { + const responsePromise = client.realtime.calls.reject('call_id'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + test('reject: request options and params are passed correctly', async () => { + // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error + await expect( + client.realtime.calls.reject('call_id', { status_code: 486 }, { path: '/_stainless_unknown_path' }), + ).rejects.toThrow(OpenAI.NotFoundError); + }); +}); From 49fe03d696a73b8d0be50d3fa85fc7d5e704d50d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 2 Oct 2025 20:13:52 +0000 Subject: [PATCH 2/2] release: 6.1.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 8 ++++++++ jsr.json | 2 +- package.json | 2 +- src/version.ts | 2 +- 5 files changed, 12 insertions(+), 4 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 601e9bed3..8ace91523 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "6.0.1" + ".": "6.1.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index fdeefbe85..c9e656df0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 6.1.0 (2025-10-02) + +Full Changelog: [v6.0.1...v6.1.0](https://github.com/openai/openai-node/compare/v6.0.1...v6.1.0) + +### Features + +* **api:** add support for realtime calls ([5de9585](https://github.com/openai/openai-node/commit/5de958556679182dfbdce95b4db6b65ca742aa61)) + ## 6.0.1 (2025-10-01) Full Changelog: [v6.0.0...v6.0.1](https://github.com/openai/openai-node/compare/v6.0.0...v6.0.1) diff --git a/jsr.json b/jsr.json index 0a4195e83..a7819c2ba 100644 --- a/jsr.json +++ b/jsr.json @@ -1,6 +1,6 @@ { "name": "@openai/openai", - "version": "6.0.1", + "version": "6.1.0", "exports": { ".": "./index.ts", "./helpers/zod": "./helpers/zod.ts", diff --git a/package.json b/package.json index 892c6f29d..fc7599145 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "openai", - "version": "6.0.1", + "version": "6.1.0", "description": "The official TypeScript library for the OpenAI API", "author": "OpenAI ", "types": "dist/index.d.ts", diff --git a/src/version.ts b/src/version.ts index be1e964c4..444aa7c72 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const VERSION = '6.0.1'; // x-release-please-version +export const VERSION = '6.1.0'; // x-release-please-version