From c72c1818e6d04bf0a55430e364d4fab37c9ac584 Mon Sep 17 00:00:00 2001 From: saher Date: Mon, 25 May 2026 18:03:01 +0530 Subject: [PATCH 1/3] VAPI-3165 Add Refer BXML verb implementation and tests --- models/bxml/verbs/Refer.ts | 38 +++++++++++++++++++ models/bxml/verbs/index.ts | 1 + tests/unit/models/bxml/verbs/Refer.test.ts | 43 ++++++++++++++++++++++ 3 files changed, 82 insertions(+) create mode 100644 models/bxml/verbs/Refer.ts create mode 100644 tests/unit/models/bxml/verbs/Refer.test.ts diff --git a/models/bxml/verbs/Refer.ts b/models/bxml/verbs/Refer.ts new file mode 100644 index 0000000..71ec247 --- /dev/null +++ b/models/bxml/verbs/Refer.ts @@ -0,0 +1,38 @@ +import { NestableVerb } from '../NestableVerb'; +import { SipUri } from './SipUri'; + +export interface ReferAttributes { + referCompleteUrl?: string; + referCompleteMethod?: string; + tag?: string; +} + +/** + * @export + * @class Refer + * @extends {NestableVerb} + * Represents a Refer BXML verb. + * NOTE: On success the call is terminated — the remote SIP endpoint redirects away from Bandwidth. + * Recovery BXML in referCompleteUrl only makes sense for failure handling. + */ +export class Refer extends NestableVerb { + attributes: ReferAttributes; + + /** + * Creates an instance of Refer + * @param {ReferAttributes} attributes The attributes to add to the element + * @param {SipUri} sipUri The SipUri child element (required for a valid Refer) + */ + constructor(attributes?: ReferAttributes, sipUri?: SipUri) { + super('Refer', undefined, attributes, sipUri ? [sipUri] : undefined); + } + + /** + * Set the SipUri for this Refer verb + * @param {SipUri} sipUri The SipUri to refer to + */ + setSipUri(sipUri: SipUri): void { + this.nestedVerbs = [sipUri]; + } +} + diff --git a/models/bxml/verbs/index.ts b/models/bxml/verbs/index.ts index 4dd67a4..ceda920 100644 --- a/models/bxml/verbs/index.ts +++ b/models/bxml/verbs/index.ts @@ -12,6 +12,7 @@ export * from './PhoneNumber'; export * from './PlayAudio'; export * from './Record'; export * from './Redirect'; +export * from './Refer'; export * from './ResumeRecording'; export * from './Ring'; export * from './SendDtmf'; diff --git a/tests/unit/models/bxml/verbs/Refer.test.ts b/tests/unit/models/bxml/verbs/Refer.test.ts new file mode 100644 index 0000000..1dbef21 --- /dev/null +++ b/tests/unit/models/bxml/verbs/Refer.test.ts @@ -0,0 +1,43 @@ +import { Refer, ReferAttributes } from '../../../../../models/bxml/verbs/Refer'; +import { SipUri } from '../../../../../models/bxml/verbs/SipUri'; + +describe('Refer', () => { + test('should generate Refer XML with SipUri and all attributes', () => { + const attributes: ReferAttributes = { + referCompleteUrl: 'https://example.com/handleRefer', + referCompleteMethod: 'POST', + tag: 'my-tag', + }; + const sipUri = new SipUri('sip:alice@atlanta.example.com'); + const refer = new Refer(attributes, sipUri); + + const xml = refer.toBxml(); + expect(xml).toContain('sip:alice@atlanta.example.com'); + expect(xml).toContain(''); + }); + + test('should generate Refer XML with no attributes', () => { + const sipUri = new SipUri('sip:bob@biloxi.example.com'); + const refer = new Refer(undefined, sipUri); + + const xml = refer.toBxml(); + expect(xml).toContain(''); + expect(xml).toContain('sip:bob@biloxi.example.com'); + }); + + test('setSipUri should replace the nested SipUri', () => { + const sipUri1 = new SipUri('sip:alice@atlanta.example.com'); + const sipUri2 = new SipUri('sip:bob@biloxi.example.com'); + const refer = new Refer({}, sipUri1); + + refer.setSipUri(sipUri2); + const xml = refer.toBxml(); + expect(xml).not.toContain('alice'); + expect(xml).toContain('sip:bob@biloxi.example.com'); + }); +}); + From 95909f73bbae17767017af53629778cee3e5a126 Mon Sep 17 00:00:00 2001 From: saher Date: Mon, 15 Jun 2026 16:31:04 +0530 Subject: [PATCH 2/3] VAPI-3165 Update bandwidth.yaml and models for sip refer --- .openapi-generator/FILES | 4 ++ README.md | 2 + bandwidth.yml | 64 ++++++++++++++++++++++- docs/ReferCallStatus.md | 11 ++++ docs/ReferCompleteCallback.md | 49 ++++++++++++++++++ models/index.ts | 2 + models/refer-call-status.ts | 29 +++++++++++ models/refer-complete-callback.ts | 84 +++++++++++++++++++++++++++++++ 8 files changed, 243 insertions(+), 2 deletions(-) create mode 100644 docs/ReferCallStatus.md create mode 100644 docs/ReferCompleteCallback.md create mode 100644 models/refer-call-status.ts create mode 100644 models/refer-complete-callback.ts diff --git a/.openapi-generator/FILES b/.openapi-generator/FILES index 7fd34a1..60ca996 100644 --- a/.openapi-generator/FILES +++ b/.openapi-generator/FILES @@ -181,6 +181,8 @@ docs/RecordingTranscriptions.md docs/RecordingsApi.md docs/RedirectCallback.md docs/RedirectMethodEnum.md +docs/ReferCallStatus.md +docs/ReferCompleteCallback.md docs/SipConnectionMetadata.md docs/SipCredentials.md docs/SmsMessageContent.md @@ -385,6 +387,8 @@ models/recording-transcription-metadata.ts models/recording-transcriptions.ts models/redirect-callback.ts models/redirect-method-enum.ts +models/refer-call-status.ts +models/refer-complete-callback.ts models/sip-connection-metadata.ts models/sip-credentials.ts models/sms-message-content.ts diff --git a/README.md b/README.md index 4eb50e1..f380eda 100644 --- a/README.md +++ b/README.md @@ -321,6 +321,8 @@ Class | Method | HTTP request | Description - [RecordingTranscriptions](docs/RecordingTranscriptions.md) - [RedirectCallback](docs/RedirectCallback.md) - [RedirectMethodEnum](docs/RedirectMethodEnum.md) + - [ReferCallStatus](docs/ReferCallStatus.md) + - [ReferCompleteCallback](docs/ReferCompleteCallback.md) - [SipConnectionMetadata](docs/SipConnectionMetadata.md) - [SipCredentials](docs/SipCredentials.md) - [SmsMessageContent](docs/SmsMessageContent.md) diff --git a/bandwidth.yml b/bandwidth.yml index 5927a5e..db2df63 100644 --- a/bandwidth.yml +++ b/bandwidth.yml @@ -5231,6 +5231,66 @@ components: $ref: '#/components/schemas/errorMessage' errorId: $ref: '#/components/schemas/errorId' + referCompleteCallback: + type: object + description: >- + The Refer Complete event is fired when the verb finishes + executing. This is sent to the referCompleteUrl specified on the + verb, and the BXML returned in it is executed on the call. + properties: + eventType: + $ref: '#/components/schemas/eventType' + eventTime: + $ref: '#/components/schemas/eventTime' + accountId: + $ref: '#/components/schemas/accountId' + applicationId: + $ref: '#/components/schemas/applicationId1' + from: + $ref: '#/components/schemas/from' + to: + $ref: '#/components/schemas/to' + direction: + $ref: '#/components/schemas/callDirectionEnum' + callId: + $ref: '#/components/schemas/callId' + callUrl: + $ref: '#/components/schemas/callUrl' + startTime: + $ref: '#/components/schemas/startTime' + answerTime: + $ref: '#/components/schemas/answerTime' + tag: + $ref: '#/components/schemas/tag1' + referCallStatus: + $ref: '#/components/schemas/referCallStatus' + referSipResponseCode: + $ref: '#/components/schemas/referSipResponseCode' + notifySipResponseCode: + $ref: '#/components/schemas/notifySipResponseCode' + referCallStatus: + type: string + description: >- + The outcome of the REFER attempt. Possible values are success or + failure. + enum: + - success + - failure + example: success + referSipResponseCode: + type: integer + nullable: true + description: >- + The SIP response code received for the REFER request (e.g. 202, 405, + 603). Absent when not applicable. + example: 202 + notifySipResponseCode: + type: integer + nullable: true + description: >- + The SIP response code received in the NOTIFY message (e.g. 200, 404, + 486). Absent on REFER rejection or NOTIFY timeout. + example: 200 eventType: type: string description: >- @@ -5239,8 +5299,8 @@ components: conferenceRedirect, conferenceMemberJoin, conferenceMemberExit, conferenceCompleted, conferenceRecordingAvailable, disconnect, dtmf, gather, initiate, machineDetectionComplete, recordingComplete, - recordingAvailable, redirect, transcriptionAvailable, transferAnswer, - transferComplete, transferDisconnect. + recordingAvailable, redirect, referComplete, transcriptionAvailable, + transferAnswer, transferComplete, transferDisconnect. example: bridgeComplete eventTime: type: string diff --git a/docs/ReferCallStatus.md b/docs/ReferCallStatus.md new file mode 100644 index 0000000..8bb584f --- /dev/null +++ b/docs/ReferCallStatus.md @@ -0,0 +1,11 @@ +# ReferCallStatus + +The outcome of the REFER attempt. Possible values are success or failure. + +## Enum + +* `Success` (value: `'success'`) + +* `Failure` (value: `'failure'`) + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/docs/ReferCompleteCallback.md b/docs/ReferCompleteCallback.md new file mode 100644 index 0000000..dff137b --- /dev/null +++ b/docs/ReferCompleteCallback.md @@ -0,0 +1,49 @@ +# ReferCompleteCallback + +The Refer Complete event is fired when the verb finishes executing. This is sent to the referCompleteUrl specified on the verb, and the BXML returned in it is executed on the call. + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**eventType** | **string** | The event type, value can be one of the following: answer, bridgeComplete, bridgeTargetComplete, conferenceCreated, conferenceRedirect, conferenceMemberJoin, conferenceMemberExit, conferenceCompleted, conferenceRecordingAvailable, disconnect, dtmf, gather, initiate, machineDetectionComplete, recordingComplete, recordingAvailable, redirect, referComplete, transcriptionAvailable, transferAnswer, transferComplete, transferDisconnect. | [optional] [default to undefined] +**eventTime** | **string** | The approximate UTC date and time when the event was generated by the Bandwidth server, in ISO 8601 format. This may not be exactly the time of event execution. | [optional] [default to undefined] +**accountId** | **string** | The user account associated with the call. | [optional] [default to undefined] +**applicationId** | **string** | The id of the application associated with the call. | [optional] [default to undefined] +**from** | **string** | The provided identifier of the caller. Must be a phone number in E.164 format (e.g. +15555555555). | [optional] [default to undefined] +**to** | **string** | The phone number that received the call, in E.164 format (e.g. +15555555555). | [optional] [default to undefined] +**direction** | [**CallDirectionEnum**](CallDirectionEnum.md) | | [optional] [default to undefined] +**callId** | **string** | The call id associated with the event. | [optional] [default to undefined] +**callUrl** | **string** | The URL of the call associated with the event. | [optional] [default to undefined] +**startTime** | **string** | Time the call was started, in ISO 8601 format. | [optional] [default to undefined] +**answerTime** | **string** | Time the call was answered, in ISO 8601 format. | [optional] [default to undefined] +**tag** | **string** | (optional) The tag specified on call creation. If no tag was specified or it was previously cleared, this field will not be present. | [optional] [default to undefined] +**referCallStatus** | [**ReferCallStatus**](ReferCallStatus.md) | | [optional] [default to undefined] +**referSipResponseCode** | **number** | The SIP response code received for the REFER request (e.g. 202, 405, 603). Absent when not applicable. | [optional] [default to undefined] +**notifySipResponseCode** | **number** | The SIP response code received in the NOTIFY message (e.g. 200, 404, 486). Absent on REFER rejection or NOTIFY timeout. | [optional] [default to undefined] + +## Example + +```typescript +import { ReferCompleteCallback } from 'bandwidth-sdk'; + +const instance: ReferCompleteCallback = { + eventType, + eventTime, + accountId, + applicationId, + from, + to, + direction, + callId, + callUrl, + startTime, + answerTime, + tag, + referCallStatus, + referSipResponseCode, + notifySipResponseCode, +}; +``` + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/models/index.ts b/models/index.ts index bb6e465..5af7cb5 100644 --- a/models/index.ts +++ b/models/index.ts @@ -154,6 +154,8 @@ export * from './recording-transcription-metadata'; export * from './recording-transcriptions'; export * from './redirect-callback'; export * from './redirect-method-enum'; +export * from './refer-call-status'; +export * from './refer-complete-callback'; export * from './sip-connection-metadata'; export * from './sip-credentials'; export * from './sms-message-content'; diff --git a/models/refer-call-status.ts b/models/refer-call-status.ts new file mode 100644 index 0000000..81c8292 --- /dev/null +++ b/models/refer-call-status.ts @@ -0,0 +1,29 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Bandwidth + * Bandwidth\'s Communication APIs + * + * The version of the OpenAPI document: 1.0.0 + * Contact: letstalk@bandwidth.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + + +/** + * The outcome of the REFER attempt. Possible values are success or failure. + */ + +export const ReferCallStatus = { + Success: 'success', + Failure: 'failure' +} as const; + +export type ReferCallStatus = typeof ReferCallStatus[keyof typeof ReferCallStatus]; + + + diff --git a/models/refer-complete-callback.ts b/models/refer-complete-callback.ts new file mode 100644 index 0000000..a073737 --- /dev/null +++ b/models/refer-complete-callback.ts @@ -0,0 +1,84 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Bandwidth + * Bandwidth\'s Communication APIs + * + * The version of the OpenAPI document: 1.0.0 + * Contact: letstalk@bandwidth.com + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +// May contain unused imports in some cases +// @ts-ignore +import type { CallDirectionEnum } from './call-direction-enum'; +// May contain unused imports in some cases +// @ts-ignore +import type { ReferCallStatus } from './refer-call-status'; + +/** + * The Refer Complete event is fired when the verb finishes executing. This is sent to the referCompleteUrl specified on the verb, and the BXML returned in it is executed on the call. + */ +export interface ReferCompleteCallback { + /** + * The event type, value can be one of the following: answer, bridgeComplete, bridgeTargetComplete, conferenceCreated, conferenceRedirect, conferenceMemberJoin, conferenceMemberExit, conferenceCompleted, conferenceRecordingAvailable, disconnect, dtmf, gather, initiate, machineDetectionComplete, recordingComplete, recordingAvailable, redirect, referComplete, transcriptionAvailable, transferAnswer, transferComplete, transferDisconnect. + */ + 'eventType'?: string; + /** + * The approximate UTC date and time when the event was generated by the Bandwidth server, in ISO 8601 format. This may not be exactly the time of event execution. + */ + 'eventTime'?: string; + /** + * The user account associated with the call. + */ + 'accountId'?: string; + /** + * The id of the application associated with the call. + */ + 'applicationId'?: string; + /** + * The provided identifier of the caller. Must be a phone number in E.164 format (e.g. +15555555555). + */ + 'from'?: string; + /** + * The phone number that received the call, in E.164 format (e.g. +15555555555). + */ + 'to'?: string; + 'direction'?: CallDirectionEnum; + /** + * The call id associated with the event. + */ + 'callId'?: string; + /** + * The URL of the call associated with the event. + */ + 'callUrl'?: string; + /** + * Time the call was started, in ISO 8601 format. + */ + 'startTime'?: string; + /** + * Time the call was answered, in ISO 8601 format. + */ + 'answerTime'?: string | null; + /** + * (optional) The tag specified on call creation. If no tag was specified or it was previously cleared, this field will not be present. + */ + 'tag'?: string | null; + 'referCallStatus'?: ReferCallStatus; + /** + * The SIP response code received for the REFER request (e.g. 202, 405, 603). Absent when not applicable. + */ + 'referSipResponseCode'?: number | null; + /** + * The SIP response code received in the NOTIFY message (e.g. 200, 404, 486). Absent on REFER rejection or NOTIFY timeout. + */ + 'notifySipResponseCode'?: number | null; +} + + + From 785428ca7c9c12b1ea15a28fb3a045a8429c2203 Mon Sep 17 00:00:00 2001 From: saher Date: Tue, 23 Jun 2026 17:45:43 +0530 Subject: [PATCH 3/3] VAPI-3165 fixed review comments --- models/bxml/verbs/Refer.ts | 14 +++++++------- models/bxml/verbs/ReferSipUri.ts | 22 ++++++++++++++++++++++ models/bxml/verbs/index.ts | 1 + tests/unit/models/bxml/verbs/Refer.test.ts | 17 ++++++++--------- 4 files changed, 38 insertions(+), 16 deletions(-) create mode 100644 models/bxml/verbs/ReferSipUri.ts diff --git a/models/bxml/verbs/Refer.ts b/models/bxml/verbs/Refer.ts index 71ec247..97d8340 100644 --- a/models/bxml/verbs/Refer.ts +++ b/models/bxml/verbs/Refer.ts @@ -1,5 +1,5 @@ import { NestableVerb } from '../NestableVerb'; -import { SipUri } from './SipUri'; +import { ReferSipUri } from './ReferSipUri'; export interface ReferAttributes { referCompleteUrl?: string; @@ -20,19 +20,19 @@ export class Refer extends NestableVerb { /** * Creates an instance of Refer + * @param {ReferSipUri} sipUri The SipUri child element (required — spec mandates exactly one) * @param {ReferAttributes} attributes The attributes to add to the element - * @param {SipUri} sipUri The SipUri child element (required for a valid Refer) */ - constructor(attributes?: ReferAttributes, sipUri?: SipUri) { - super('Refer', undefined, attributes, sipUri ? [sipUri] : undefined); + constructor(sipUri: ReferSipUri, attributes?: ReferAttributes) { + super('Refer', undefined, attributes, [sipUri]); } /** * Set the SipUri for this Refer verb - * @param {SipUri} sipUri The SipUri to refer to + * @param {ReferSipUri} sipUri The SipUri to refer to */ - setSipUri(sipUri: SipUri): void { + setSipUri(sipUri: ReferSipUri): void { + // Replaces the single required SipUri child — allows exactly one. this.nestedVerbs = [sipUri]; } } - diff --git a/models/bxml/verbs/ReferSipUri.ts b/models/bxml/verbs/ReferSipUri.ts new file mode 100644 index 0000000..1f26b83 --- /dev/null +++ b/models/bxml/verbs/ReferSipUri.ts @@ -0,0 +1,22 @@ +import { Verb } from '../Verb'; + +/** + * @export + * @class ReferSipUri + * @extends {Verb} + * Represents a SipUri child element scoped to the Refer verb. + * Unlike the Transfer-scoped SipUri, this accepts only a URI — no + * transferAnswerUrl, uui, username/password, or other Transfer attributes. + */ +export class ReferSipUri extends Verb { + uri: string; + + /** + * Creates an instance of ReferSipUri + * @param {string} uri The SIP URI to refer to (must start with sip:) + */ + constructor(uri: string) { + super('SipUri', uri); + } +} + diff --git a/models/bxml/verbs/index.ts b/models/bxml/verbs/index.ts index ceda920..0740998 100644 --- a/models/bxml/verbs/index.ts +++ b/models/bxml/verbs/index.ts @@ -13,6 +13,7 @@ export * from './PlayAudio'; export * from './Record'; export * from './Redirect'; export * from './Refer'; +export * from './ReferSipUri'; export * from './ResumeRecording'; export * from './Ring'; export * from './SendDtmf'; diff --git a/tests/unit/models/bxml/verbs/Refer.test.ts b/tests/unit/models/bxml/verbs/Refer.test.ts index 1dbef21..f763f4b 100644 --- a/tests/unit/models/bxml/verbs/Refer.test.ts +++ b/tests/unit/models/bxml/verbs/Refer.test.ts @@ -1,5 +1,5 @@ import { Refer, ReferAttributes } from '../../../../../models/bxml/verbs/Refer'; -import { SipUri } from '../../../../../models/bxml/verbs/SipUri'; +import { ReferSipUri } from '../../../../../models/bxml/verbs/ReferSipUri'; describe('Refer', () => { test('should generate Refer XML with SipUri and all attributes', () => { @@ -8,8 +8,8 @@ describe('Refer', () => { referCompleteMethod: 'POST', tag: 'my-tag', }; - const sipUri = new SipUri('sip:alice@atlanta.example.com'); - const refer = new Refer(attributes, sipUri); + const sipUri = new ReferSipUri('sip:alice@atlanta.example.com'); + const refer = new Refer(sipUri, attributes); const xml = refer.toBxml(); expect(xml).toContain(' { }); test('should generate Refer XML with no attributes', () => { - const sipUri = new SipUri('sip:bob@biloxi.example.com'); - const refer = new Refer(undefined, sipUri); + const sipUri = new ReferSipUri('sip:bob@biloxi.example.com'); + const refer = new Refer(sipUri); const xml = refer.toBxml(); expect(xml).toContain(''); @@ -30,9 +30,9 @@ describe('Refer', () => { }); test('setSipUri should replace the nested SipUri', () => { - const sipUri1 = new SipUri('sip:alice@atlanta.example.com'); - const sipUri2 = new SipUri('sip:bob@biloxi.example.com'); - const refer = new Refer({}, sipUri1); + const sipUri1 = new ReferSipUri('sip:alice@atlanta.example.com'); + const sipUri2 = new ReferSipUri('sip:bob@biloxi.example.com'); + const refer = new Refer(sipUri1); refer.setSipUri(sipUri2); const xml = refer.toBxml(); @@ -40,4 +40,3 @@ describe('Refer', () => { expect(xml).toContain('sip:bob@biloxi.example.com'); }); }); -