Skip to content
Closed
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
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

90 changes: 90 additions & 0 deletions src/__tests__/outbound-call-validation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import {
transformPhoneNumberOutput,
transformCallInput,
} from '../transformers/index.js';
import { CallInputSchema, PhoneNumberOutputSchema } from '../schemas/index.js';

describe('Outbound call validation and phone number provider exposure', () => {
describe('PhoneNumberOutputSchema includes provider field', () => {
test('schema should have a provider field', () => {
const shape = PhoneNumberOutputSchema.shape;
expect(shape).toHaveProperty('provider');
});
});

describe('transformPhoneNumberOutput exposes provider', () => {
test('should include provider field for a vapi phone number', () => {
const vapiPhoneNumber = {
id: 'pn-123',
name: 'My Vapi Number',
createdAt: '2025-01-01T00:00:00Z',
updatedAt: '2025-01-01T00:00:00Z',
number: '+15551234567',
status: 'active',
provider: 'vapi',
};

const result = transformPhoneNumberOutput(vapiPhoneNumber);
expect(result).toHaveProperty('provider');
expect(result.provider).toBe('vapi');
});

test('should include provider field for a twilio phone number', () => {
const twilioPhoneNumber = {
id: 'pn-456',
name: 'My Twilio Number',
createdAt: '2025-01-01T00:00:00Z',
updatedAt: '2025-01-01T00:00:00Z',
number: '+15559876543',
status: 'active',
provider: 'twilio',
};

const result = transformPhoneNumberOutput(twilioPhoneNumber);
expect(result).toHaveProperty('provider');
expect(result.provider).toBe('twilio');
});

test('should include provider field for a vonage phone number', () => {
const vonagePhoneNumber = {
id: 'pn-789',
name: 'My Vonage Number',
createdAt: '2025-01-01T00:00:00Z',
updatedAt: '2025-01-01T00:00:00Z',
number: '+15555555555',
status: 'active',
provider: 'vonage',
};

const result = transformPhoneNumberOutput(vonagePhoneNumber);
expect(result).toHaveProperty('provider');
expect(result.provider).toBe('vonage');
});

test('should default provider to "unknown" when not present on source', () => {
const phoneNumberNoProvider = {
id: 'pn-000',
name: 'Legacy Number',
createdAt: '2025-01-01T00:00:00Z',
updatedAt: '2025-01-01T00:00:00Z',
number: '+15550000000',
status: 'active',
};

const result = transformPhoneNumberOutput(phoneNumberNoProvider);
expect(result).toHaveProperty('provider');
expect(result.provider).toBe('unknown');
});
});

describe('CallInputSchema.phoneNumberId has outbound guidance in description', () => {
test('phoneNumberId description should mention Twilio or Vonage for outbound', () => {
const phoneNumberIdField = CallInputSchema.shape.phoneNumberId;
const description = phoneNumberIdField.description;
expect(description).toBeDefined();
expect(description!.toLowerCase()).toContain('twilio');
expect(description!.toLowerCase()).toContain('vonage');
expect(description!.toLowerCase()).toContain('outbound');
});
});
});
9 changes: 8 additions & 1 deletion src/schemas/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,9 @@ export const CallInputSchema = z.object({
phoneNumberId: z
.string()
.optional()
.describe('ID of the phone number to use for the call'),
.describe(
'ID of the phone number to use for the call. For outbound calls, this must be a Twilio or Vonage imported number. Vapi-provisioned numbers are inbound-only and cannot dial outbound. Use list_phone_numbers to check the provider field.'
),
customer: z
.object({
number: z.string().describe('Customer phone number'),
Expand Down Expand Up @@ -314,6 +316,11 @@ export const PhoneNumberOutputSchema = BaseResponseSchema.extend({
name: z.string().optional(),
phoneNumber: z.string(),
status: z.string(),
provider: z
.string()
.describe(
'Phone number provider (e.g. "vapi", "twilio", "vonage"). Vapi numbers are inbound-only. Twilio and Vonage numbers support outbound dialing.'
),
capabilities: z
.object({
sms: z.boolean().optional(),
Expand Down
2 changes: 1 addition & 1 deletion src/tools/call.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const registerCallTools = (

server.tool(
'create_call',
'Creates a outbound call',
'Creates an outbound call. Important: outbound calls require a Twilio or Vonage imported phone number. Vapi-provisioned numbers are inbound-only and will fail with a transport error if used for outbound dialing.',
CallInputSchema.shape,
createToolHandler(async (data) => {
const createCallDto = transformCallInput(data);
Expand Down
1 change: 1 addition & 0 deletions src/transformers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ export function transformPhoneNumberOutput(
updatedAt: phoneNumber.updatedAt,
phoneNumber: phoneNumber.number,
status: phoneNumber.status,
provider: phoneNumber.provider || 'unknown',
};
}

Expand Down
Loading