




























@@ -606,6 +606,61 @@ describe("VoiceCallWebhookServer replay handling", () => {
606606}
607607});
608608609+it.each(["outbound-api", "outbound-dial"] as const)(
610+"returns realtime TwiML for %s twilio TwiML fetches",
611+async (direction) => {
612+const parseWebhookEvent = vi.fn(() => ({ events: [], statusCode: 200 }));
613+const buildTwiMLPayload = vi.fn(() => ({
614+statusCode: 200,
615+headers: { "Content-Type": "text/xml" },
616+body: '<Response><Connect><Stream url="wss://example.test/voice/stream/realtime/token" /></Connect></Response>',
617+}));
618+const twilioProvider: VoiceCallProvider = {
619+ ...provider,
620+name: "twilio",
621+verifyWebhook: () => ({ ok: true, verifiedRequestKey: "twilio:req:rt-outbound" }),
622+ parseWebhookEvent,
623+};
624+const { manager, processEvent } = createManager([]);
625+const config = createConfig({
626+provider: "twilio",
627+inboundPolicy: "disabled",
628+realtime: {
629+enabled: true,
630+streamPath: "/voice/stream/realtime",
631+tools: [],
632+providers: {},
633+},
634+});
635+const server = new VoiceCallWebhookServer(config, manager, twilioProvider);
636+server.setRealtimeHandler({
637+ buildTwiMLPayload,
638+getStreamPathPattern: () => "/voice/stream/realtime",
639+handleWebSocketUpgrade: () => {},
640+registerToolHandler: () => {},
641+setPublicUrl: () => {},
642+} as unknown as RealtimeCallHandler);
643+644+try {
645+const baseUrl = await server.start();
646+const response = await postWebhookFormWithHeaders(
647+server,
648+baseUrl,
649+`CallSid=CA123&Direction=${direction}&CallStatus=in-progress&From=%2B15550001111&To=%2B15550002222`,
650+{ "x-twilio-signature": "sig" },
651+);
652+653+expect(response.status).toBe(200);
654+expect(await response.text()).toContain("<Connect><Stream");
655+expect(buildTwiMLPayload).toHaveBeenCalledTimes(1);
656+expect(parseWebhookEvent).not.toHaveBeenCalled();
657+expect(processEvent).not.toHaveBeenCalled();
658+} finally {
659+await server.stop();
660+}
661+},
662+);
663+609664it("rejects non-allowlisted inbound realtime calls before creating a stream token", async () => {
610665const buildTwiMLPayload = vi.fn(() => ({
611666statusCode: 200,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。