From ee555f6764483e3b151e8a47a970ff7685ab1b43 Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Thu, 30 Jan 2025 17:36:37 -0300 Subject: [PATCH 1/6] vapi init --- .../vapi/actions/create-call/create-call.mjs | 48 ++ .../update-assistant-settings.mjs | 251 ++++++++++ .../vapi/actions/upload-file/upload-file.mjs | 27 + components/vapi/package.json | 2 +- .../new-conversation/new-conversation.mjs | 110 ++++ .../new-intent-detection.mjs | 140 ++++++ .../vapi/sources/new-message/new-message.mjs | 121 +++++ components/vapi/vapi.app.mjs | 474 +++++++++++++++++- 8 files changed, 1169 insertions(+), 4 deletions(-) create mode 100644 components/vapi/actions/create-call/create-call.mjs create mode 100644 components/vapi/actions/update-assistant-settings/update-assistant-settings.mjs create mode 100644 components/vapi/actions/upload-file/upload-file.mjs create mode 100644 components/vapi/sources/new-conversation/new-conversation.mjs create mode 100644 components/vapi/sources/new-intent-detection/new-intent-detection.mjs create mode 100644 components/vapi/sources/new-message/new-message.mjs diff --git a/components/vapi/actions/create-call/create-call.mjs b/components/vapi/actions/create-call/create-call.mjs new file mode 100644 index 0000000000000..38b28858f99d2 --- /dev/null +++ b/components/vapi/actions/create-call/create-call.mjs @@ -0,0 +1,48 @@ +import vapi from "../../vapi.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "vapi-create-call", + name: "Create Call", + description: "Starts a new conversation with an assistant. [See the documentation]()", + version: "0.0.{{ts}}", + type: "action", + props: { + vapi, + assistantId: { + propDefinition: [ + vapi, + "assistantId", + ], + }, + squadId: { + propDefinition: [ + vapi, + "squadId", + ], + }, + phoneNumberId: { + propDefinition: [ + vapi, + "phoneNumberId", + ], + }, + name: { + type: "string", + label: "Conversation Name", + description: "Name of the new conversation (optional)", + optional: true, + }, + customerId: { + type: "string", + label: "Customer ID", + description: "ID of the customer for the conversation (optional)", + optional: true, + }, + }, + async run({ $ }) { + const response = await this.vapi.startConversation(); + $.export("$summary", `Conversation created with ID ${response.id}`); + return response; + }, +}; diff --git a/components/vapi/actions/update-assistant-settings/update-assistant-settings.mjs b/components/vapi/actions/update-assistant-settings/update-assistant-settings.mjs new file mode 100644 index 0000000000000..79a173cb3a31e --- /dev/null +++ b/components/vapi/actions/update-assistant-settings/update-assistant-settings.mjs @@ -0,0 +1,251 @@ +import vapi from "../../vapi.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "vapi-update-assistant-settings", + name: "Update Assistant Settings", + description: "Updates the configuration settings for a specific assistant. [See the documentation]()", + version: "0.0.{{ts}}", + type: "action", + props: { + vapi, + assistantId: { + propDefinition: [ + "vapi", + "assistantId", + ], + }, + transcriber: { + propDefinition: [ + "vapi", + "transcriber", + ], + optional: true, + }, + model: { + propDefinition: [ + "vapi", + "model", + ], + optional: true, + }, + voice: { + propDefinition: [ + "vapi", + "voice", + ], + optional: true, + }, + firstMessage: { + propDefinition: [ + "vapi", + "firstMessage", + ], + optional: true, + }, + firstMessageMode: { + propDefinition: [ + "vapi", + "firstMessageMode", + ], + optional: true, + }, + hipaaEnabled: { + propDefinition: [ + "vapi", + "hipaaEnabled", + ], + optional: true, + }, + clientMessages: { + propDefinition: [ + "vapi", + "clientMessages", + ], + optional: true, + }, + serverMessages: { + propDefinition: [ + "vapi", + "serverMessages", + ], + optional: true, + }, + silenceTimeoutSeconds: { + propDefinition: [ + "vapi", + "silenceTimeoutSeconds", + ], + optional: true, + }, + backgroundSound: { + propDefinition: [ + "vapi", + "backgroundSound", + ], + optional: true, + }, + backgroundDenoisingEnabled: { + propDefinition: [ + "vapi", + "backgroundDenoisingEnabled", + ], + optional: true, + }, + modelOutputInMessagesEnabled: { + propDefinition: [ + "vapi", + "modelOutputInMessagesEnabled", + ], + optional: true, + }, + transportConfigurations: { + propDefinition: [ + "vapi", + "transportConfigurations", + ], + optional: true, + }, + credentials: { + propDefinition: [ + "vapi", + "credentials", + ], + optional: true, + }, + name: { + propDefinition: [ + "vapi", + "name", + ], + optional: true, + }, + voicemailDetection: { + propDefinition: [ + "vapi", + "voicemailDetection", + ], + optional: true, + }, + endCallMessage: { + propDefinition: [ + "vapi", + "endCallMessage", + ], + optional: true, + }, + endCallPhrases: { + propDefinition: [ + "vapi", + "endCallPhrases", + ], + optional: true, + }, + metadata: { + propDefinition: [ + "vapi", + "metadata", + ], + optional: true, + }, + analysisPlan: { + propDefinition: [ + "vapi", + "analysisPlan", + ], + optional: true, + }, + artifactPlan: { + propDefinition: [ + "vapi", + "artifactPlan", + ], + optional: true, + }, + messagePlan: { + propDefinition: [ + "vapi", + "messagePlan", + ], + optional: true, + }, + startSpeakingPlan: { + propDefinition: [ + "vapi", + "startSpeakingPlan", + ], + optional: true, + }, + stopSpeakingPlan: { + propDefinition: [ + "vapi", + "stopSpeakingPlan", + ], + optional: true, + }, + monitorPlan: { + propDefinition: [ + "vapi", + "monitorPlan", + ], + optional: true, + }, + credentialIds: { + propDefinition: [ + "vapi", + "credentialIds", + ], + optional: true, + }, + server: { + propDefinition: [ + "vapi", + "server", + ], + optional: true, + }, + }, + async run({ $ }) { + const data = {}; + const optionalProps = [ + "transcriber", + "model", + "voice", + "firstMessage", + "firstMessageMode", + "hipaaEnabled", + "clientMessages", + "serverMessages", + "silenceTimeoutSeconds", + "backgroundSound", + "backgroundDenoisingEnabled", + "modelOutputInMessagesEnabled", + "transportConfigurations", + "credentials", + "name", + "voicemailDetection", + "endCallMessage", + "endCallPhrases", + "metadata", + "analysisPlan", + "artifactPlan", + "messagePlan", + "startSpeakingPlan", + "stopSpeakingPlan", + "monitorPlan", + "credentialIds", + "server", + ]; + + for (const prop of optionalProps) { + if (this[prop] !== undefined && this[prop] !== null && this[prop] !== "") { + data[prop] = this[prop]; + } + } + + const assistantId = this.assistantId; + const response = await this.vapi.updateAssistant(assistantId, data); + $.export("$summary", `Updated assistant ${assistantId} successfully`); + return response; + }, +}; diff --git a/components/vapi/actions/upload-file/upload-file.mjs b/components/vapi/actions/upload-file/upload-file.mjs new file mode 100644 index 0000000000000..9b333908e13b5 --- /dev/null +++ b/components/vapi/actions/upload-file/upload-file.mjs @@ -0,0 +1,27 @@ +import vapi from "../../vapi.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "vapi-upload-file", + name: "Upload File", + description: "Uploads a new file. [See the documentation](https://docs.vapi.ai/api-reference)", + version: "0.0.{{ts}}", + type: "action", + props: { + vapi: { + type: "app", + app: "vapi", + }, + file: { + propDefinition: [ + vapi, + "file", + ], + }, + }, + async run({ $ }) { + const response = await this.vapi.uploadFile(this.file); + $.export("$summary", `File uploaded successfully with ID ${response.id} and status ${response.status}`); + return response; + }, +}; diff --git a/components/vapi/package.json b/components/vapi/package.json index 079d52bb54850..3a5043bb8cb21 100644 --- a/components/vapi/package.json +++ b/components/vapi/package.json @@ -12,4 +12,4 @@ "publishConfig": { "access": "public" } -} \ No newline at end of file +} diff --git a/components/vapi/sources/new-conversation/new-conversation.mjs b/components/vapi/sources/new-conversation/new-conversation.mjs new file mode 100644 index 0000000000000..071e7b482a567 --- /dev/null +++ b/components/vapi/sources/new-conversation/new-conversation.mjs @@ -0,0 +1,110 @@ +import { + axios, DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, +} from "@pipedream/platform"; +import vapi from "../../vapi.app.mjs"; + +export default { + key: "vapi-new-conversation", + name: "New Conversation Started", + description: "Emits a new event when a voicebot starts a conversation. [See the documentation]()", + version: "0.0.{{ts}}", + type: "source", + dedupe: "unique", + props: { + vapi, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + voicebotIds: { + propDefinition: [ + vapi, + "voicebotIds", + ], + optional: true, + }, + }, + hooks: { + async deploy() { + const lastRun = Date.now(); + await this.db.set("last_run_ts", lastRun); + + const params = { + limit: 50, + sort: "desc", + }; + + if (this.voicebotIds?.length) { + params.voicebotIds = this.voicebotIds.join(","); + } + + const conversations = await this.vapi._makeRequest({ + method: "GET", + path: "/conversation", + params, + }); + + conversations.reverse().forEach((convo) => { + this.$emit(convo, { + id: convo.id || convo.ts, + summary: `New Conversation: ${convo.id}`, + ts: convo.createdAt + ? Date.parse(convo.createdAt) + : lastRun, + }); + }); + }, + async activate() { + // No webhook subscription needed for polling + }, + async deactivate() { + // No webhook subscription to remove + }, + }, + async run() { + const lastRun = (await this.db.get("last_run_ts")) || Date.now(); + let newLastRun = lastRun; + + const params = { + createdAt_gt: lastRun, + limit: 100, + sort: "asc", + }; + + if (this.voicebotIds?.length) { + params.voicebotIds = this.voicebotIds.join(","); + } + + const conversations = await this.vapi._makeRequest({ + method: "GET", + path: "/conversation", + params, + }); + + for (const convo of conversations) { + this.$emit(convo, { + id: convo.id || convo.ts, + summary: `New Conversation: ${convo.id}`, + ts: convo.createdAt + ? Date.parse(convo.createdAt) + : Date.now(), + }); + + if (convo.createdAt) { + const convoTs = Date.parse(convo.createdAt); + if (convoTs > newLastRun) { + newLastRun = convoTs; + } + } else { + if (Date.now() > newLastRun) { + newLastRun = Date.now(); + } + } + } + + await this.db.set("last_run_ts", newLastRun); + }, +}; diff --git a/components/vapi/sources/new-intent-detection/new-intent-detection.mjs b/components/vapi/sources/new-intent-detection/new-intent-detection.mjs new file mode 100644 index 0000000000000..b638326da1b1a --- /dev/null +++ b/components/vapi/sources/new-intent-detection/new-intent-detection.mjs @@ -0,0 +1,140 @@ +import { + axios, DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, +} from "@pipedream/platform"; +import vapi from "../../vapi.app.mjs"; + +export default { + key: "vapi-new-intent-detection", + name: "New Intent Detected", + description: "Emit new event when a specific intent is detected during a conversation. [See the documentation]()", + version: "0.0.{{ts}}", + type: "source", + dedupe: "unique", + props: { + vapi: { + type: "app", + app: "vapi", + }, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + intentToMonitor: { + propDefinition: [ + vapi, + "intentToMonitor", + ], + }, + filterVoicebotId: { + propDefinition: [ + vapi, + "filterVoicebotId", + ], + optional: true, + }, + }, + hooks: { + async deploy() { + const lastTs = await this.db.get("last_ts") || new Date(0).toISOString(); + const params = { + createdAtGt: lastTs, + limit: 50, + sortOrder: "ASC", + }; + + if (this.filterVoicebotId && this.filterVoicebotId.length > 0) { + params.assistantId = this.filterVoicebotId; + } + + const logs = await this.vapi._makeRequest({ + method: "GET", + path: "/logs", + params, + }); + + // Sort logs by time ascending + const sortedLogs = logs.sort((a, b) => new Date(a.time) - new Date(b.time)); + let emittedCount = 0; + + for (const log of sortedLogs) { + if (emittedCount >= 50) break; + const intent = log.requestBody?.intent || log.responseBody?.intent; + + if (intent === this.intentToMonitor) { + this.$emit(log, { + id: log.id || log.time, + summary: `Intent detected: ${this.intentToMonitor}`, + ts: log.time + ? Date.parse(log.time) + : Date.now(), + }); + emittedCount++; + } + } + + // Update last_ts to the latest log's time + if (logs.length > 0) { + const latestLog = logs[logs.length - 1]; + await this.db.set( + "last_ts", + latestLog.time + ? new Date(latestLog.time).toISOString() + : new Date().toISOString(), + ); + } + }, + async activate() { + // No action needed for activate in polling source + }, + async deactivate() { + // No action needed for deactivate in polling source + }, + }, + async run() { + const lastTs = await this.db.get("last_ts") || new Date(0).toISOString(); + const params = { + createdAtGt: lastTs, + limit: 1000, + sortOrder: "ASC", + }; + + if (this.filterVoicebotId && this.filterVoicebotId.length > 0) { + params.assistantId = this.filterVoicebotId; + } + + const logs = await this.vapi._makeRequest({ + method: "GET", + path: "/logs", + params, + }); + + for (const log of logs) { + const intent = log.requestBody?.intent || log.responseBody?.intent; + + if (intent === this.intentToMonitor) { + const timestamp = log.time + ? Date.parse(log.time) + : Date.now(); + this.$emit(log, { + id: log.id || timestamp, + summary: `Intent detected: ${this.intentToMonitor}`, + ts: timestamp, + }); + } + } + + // Update last_ts to the latest log's time + if (logs.length > 0) { + const latestLog = logs[logs.length - 1]; + await this.db.set( + "last_ts", + latestLog.time + ? new Date(latestLog.time).toISOString() + : new Date().toISOString(), + ); + } + }, +}; diff --git a/components/vapi/sources/new-message/new-message.mjs b/components/vapi/sources/new-message/new-message.mjs new file mode 100644 index 0000000000000..c5643212216e1 --- /dev/null +++ b/components/vapi/sources/new-message/new-message.mjs @@ -0,0 +1,121 @@ +import { + axios, DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, +} from "@pipedream/platform"; +import vapi from "../../vapi.app.mjs"; + +export default { + key: "vapi-new-message", + name: "New Message", + description: "Emit new events when a voicebot sends or receives a message during a conversation. [See the documentation]()", + version: "0.0.{{ts}}", + type: "source", + dedupe: "unique", + props: { + vapi: { + type: "app", + app: "vapi", + }, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + voicebotIds: { + propDefinition: [ + "vapi", + "voicebotIds", + ], + }, + conversationId: { + propDefinition: [ + "vapi", + "conversationId", + ], + }, + }, + hooks: { + async deploy() { + const lastTs = (await this.db.get("lastTs")) || 0; + const params = { + type: "APIWebhookCallProvider", + webhookType: "message", + sortOrder: "DESC", + limit: 50, + }; + if (this.voicebotIds) { + params.assistantId = this.voicebotIds; + } + if (this.conversationId) { + params.callId = this.conversationId; + } + const logs = await this.vapi._makeRequest({ + method: "GET", + path: "/logs", + params, + }); + const sortedLogs = logs.results.sort((a, b) => new Date(a.time) - new Date(b.time)); + for (const log of sortedLogs) { + const ts = Date.parse(log.time) || Date.now(); + const id = log.assistantId + ? log.assistantId + : ts; + const summary = `New message from assistant ${log.assistantId || "Unknown"}`; + this.$emit(log, { + id, + summary, + ts, + }); + } + const latestTs = logs.results.length > 0 + ? Date.parse(logs.results[0].time) + : lastTs; + await this.db.set("lastTs", latestTs); + }, + async activate() { + // No webhook subscription needed for polling source + }, + async deactivate() { + // No webhook subscription to remove + }, + }, + async run() { + const lastTs = (await this.db.get("lastTs")) || 0; + const params = { + type: "APIWebhookCallProvider", + webhookType: "message", + sortOrder: "DESC", + limit: 100, + createdAtGt: new Date(lastTs).toISOString(), + }; + if (this.voicebotIds) { + params.assistantId = this.voicebotIds; + } + if (this.conversationId) { + params.callId = this.conversationId; + } + const logs = await this.vapi._makeRequest({ + method: "GET", + path: "/logs", + params, + }); + const sortedLogs = logs.results.sort((a, b) => new Date(a.time) - new Date(b.time)); + for (const log of sortedLogs) { + const ts = Date.parse(log.time) || Date.now(); + const id = log.assistantId + ? log.assistantId + : ts; + const summary = `New message from assistant ${log.assistantId || "Unknown"}`; + this.$emit(log, { + id, + summary, + ts, + }); + } + if (logs.results.length > 0) { + const latestTs = Date.parse(logs.results[0].time); + await this.db.set("lastTs", latestTs); + } + }, +}; diff --git a/components/vapi/vapi.app.mjs b/components/vapi/vapi.app.mjs index 97279f197b9f4..f0df46dffd54d 100644 --- a/components/vapi/vapi.app.mjs +++ b/components/vapi/vapi.app.mjs @@ -1,11 +1,479 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "vapi", - propDefinitions: {}, + version: "0.0.{{ts}}", + propDefinitions: { + // Emit new event when a voicebot starts a conversation + voicebotIds: { + type: "string[]", + label: "Voicebot IDs", + description: "Filter events by specific voicebot IDs (optional)", + optional: true, + async options() { + const assistants = await this.listAssistants(); + return assistants.map((assistant) => ({ + label: assistant.name, + value: assistant.id, + })); + }, + }, + // Emit new event when a voicebot sends/receives a message during a conversation + conversationId: { + type: "string", + label: "Conversation ID", + description: "Filter events by specific conversation ID (optional)", + optional: true, + }, + // Emit new event when a specific intent is detected during a conversation + intentToMonitor: { + type: "string", + label: "Intent to Monitor", + description: "Specify the intent to monitor during conversations", + }, + filterVoicebotId: { + type: "string[]", + label: "Filter by Voicebot ID", + description: "Optionally filter by specific voicebot IDs", + optional: true, + async options() { + const assistants = await this.listAssistants(); + return assistants.map((assistant) => ({ + label: assistant.name, + value: assistant.id, + })); + }, + }, + // Upload a new file + file: { + type: "any", + label: "File", + description: "The file to upload", + }, + // Start a new conversation with an assistant + name: { + type: "string", + label: "Conversation Name", + description: "Name of the new conversation (optional)", + optional: true, + }, + assistantId: { + type: "string", + label: "Assistant ID", + description: "ID of the assistant to start a conversation with or update (optional for starting, required for updating)", + optional: true, + async options() { + const assistants = await this.listAssistants(); + return assistants.map((assistant) => ({ + label: assistant.name, + value: assistant.id, + })); + }, + }, + squadId: { + type: "string", + label: "Squad ID", + description: "ID of the squad to assign to the conversation (optional)", + optional: true, + async options() { + const squads = await this.listSquads(); + return squads.map((squad) => ({ + label: squad.name, + value: squad.id, + })); + }, + }, + phoneNumberId: { + type: "string", + label: "Phone Number ID", + description: "ID of the phone number to use for the conversation (optional)", + optional: true, + async options() { + const phoneNumbers = await this.listPhoneNumbers(); + return phoneNumbers.map((phone) => ({ + label: phone.name || phone.id, + value: phone.id, + })); + }, + }, + customerId: { + type: "string", + label: "Customer ID", + description: "ID of the customer for the conversation (optional)", + optional: true, + }, + // Update configuration settings for a specific assistant + transcriber: { + type: "string", + label: "Transcriber", + description: "Options for the assistant’s transcriber (optional)", + optional: true, + async options() { + return [ + { + label: "Talkscriber", + value: "talkscriber", + }, + { + label: "Deepgram", + value: "deepgram", + }, + { + label: "AzureSpeech", + value: "azurespeech", + }, + ]; + }, + }, + model: { + type: "string", + label: "Model", + description: "Options for the assistant’s LLM (optional)", + optional: true, + async options() { + return [ + { + label: "Grok-Beta", + value: "grok-beta", + }, + { + label: "OpenAI-GPT-4", + value: "openai-gpt-4", + }, + { + label: "Anthropic-Claude", + value: "anthropic-claude", + }, + ]; + }, + }, + voice: { + type: "string", + label: "Voice", + description: "Options for the assistant’s voice (optional)", + optional: true, + async options() { + return [ + { + label: "Tavus Voice 1", + value: "tavus-voice-1", + }, + { + label: "ElevenLabs Voice A", + value: "elevenlabs-voice-a", + }, + { + label: "Deepgram Voice X", + value: "deepgram-voice-x", + }, + ]; + }, + }, + firstMessage: { + type: "string", + label: "First Message", + description: "The first message the assistant will say or a URL to an audio file (optional)", + optional: true, + }, + firstMessageMode: { + type: "string", + label: "First Message Mode", + description: "Mode for the first message (optional)", + optional: true, + async options() { + return [ + { + label: "Assistant Speaks First", + value: "assistant-speaks-first", + }, + { + label: "Assistant Waits for User", + value: "assistant-waits-for-user", + }, + { + label: "Assistant Speaks First with Model Generated Message", + value: "assistant-speaks-first-with-model-generated-message", + }, + ]; + }, + }, + hipaaEnabled: { + type: "boolean", + label: "HIPAA Enabled", + description: "Enable HIPAA compliance settings (optional)", + optional: true, + }, + clientMessages: { + type: "string[]", + label: "Client Messages", + description: "Messages to send to Client SDKs (optional)", + optional: true, + }, + serverMessages: { + type: "string[]", + label: "Server Messages", + description: "Messages to send to Server URL (optional)", + optional: true, + }, + silenceTimeoutSeconds: { + type: "integer", + label: "Silence Timeout Seconds", + description: "Seconds of silence before ending the call (optional, min:10, max:3600)", + optional: true, + min: 10, + max: 3600, + }, + backgroundSound: { + type: "string", + label: "Background Sound", + description: "Background sound in the call (optional)", + optional: true, + async options() { + return [ + { + label: "Office", + value: "office", + }, + { + label: "Off", + value: "off", + }, + ]; + }, + }, + backgroundDenoisingEnabled: { + type: "boolean", + label: "Background Denoising Enabled", + description: "Enable background noise filtering (optional)", + optional: true, + }, + modelOutputInMessagesEnabled: { + type: "boolean", + label: "Model Output in Messages Enabled", + description: "Use model’s output in conversation history (optional)", + optional: true, + }, + transportConfigurations: { + type: "string[]", + label: "Transport Configurations", + description: "Transport provider configurations (optional)", + optional: true, + }, + credentials: { + type: "string[]", + label: "Credentials", + description: "Dynamic credentials for assistant calls (optional)", + optional: true, + }, + name: { + type: "string", + label: "Name", + description: "Name of the assistant or conversation (optional)", + optional: true, + }, + voicemailDetection: { + type: "string", + label: "Voicemail Detection", + description: "Settings to configure voicemail detection (optional)", + optional: true, + async options() { + return [ + { + label: "Twilio", + value: "twilio", + }, + { + label: "None", + value: "none", + }, + ]; + }, + }, + endCallMessage: { + type: "string", + label: "End Call Message", + description: "Message to say when ending the call (optional)", + optional: true, + }, + endCallPhrases: { + type: "string[]", + label: "End Call Phrases", + description: "Phrases that trigger call termination (optional)", + optional: true, + }, + metadata: { + type: "object", + label: "Metadata", + description: "Metadata to store on the assistant (optional)", + optional: true, + }, + analysisPlan: { + type: "object", + label: "Analysis Plan", + description: "Plan for analysis of assistant’s calls (optional)", + optional: true, + }, + artifactPlan: { + type: "object", + label: "Artifact Plan", + description: "Plan for artifacts generated during calls (optional)", + optional: true, + }, + messagePlan: { + type: "object", + label: "Message Plan", + description: "Plan for predefined messages during the call (optional)", + optional: true, + }, + startSpeakingPlan: { + type: "object", + label: "Start Speaking Plan", + description: "Plan for when the assistant should start talking (optional)", + optional: true, + }, + stopSpeakingPlan: { + type: "object", + label: "Stop Speaking Plan", + description: "Plan for when the assistant should stop talking on interruption (optional)", + optional: true, + }, + monitorPlan: { + type: "object", + label: "Monitor Plan", + description: "Plan for real-time monitoring of calls (optional)", + optional: true, + }, + credentialIds: { + type: "string[]", + label: "Credential IDs", + description: "Credentials to use for assistant calls (optional)", + optional: true, + async options() { + const creds = await this.listCredentials(); + return creds.map((cred) => ({ + label: cred.name, + value: cred.id, + })); + }, + }, + server: { + type: "object", + label: "Server", + description: "Webhook server settings (optional)", + optional: true, + }, + }, methods: { - // this.$auth contains connected account data + // Existing method authKeys() { console.log(Object.keys(this.$auth)); }, + _baseUrl() { + return "https://api.vapi.ai"; + }, + async _makeRequest(opts = {}) { + const { + $, method = "GET", path = "/", headers, ...otherOpts + } = opts; + return axios($ || this, { + ...otherOpts, + method, + url: this._baseUrl() + path, + headers: { + ...headers, + Authorization: `Bearer ${this.$auth.api_token}`, + }, + }); + }, + // List Assistants + async listAssistants(opts = {}) { + return this._makeRequest({ + method: "GET", + path: "/assistant", + params: opts, + }); + }, + // List Squads + async listSquads(opts = {}) { + return this._makeRequest({ + method: "GET", + path: "/squad", + params: opts, + }); + }, + // List Phone Numbers + async listPhoneNumbers(opts = {}) { + return this._makeRequest({ + method: "GET", + path: "/phone-number", + params: opts, + }); + }, + // List Credentials + async listCredentials(opts = {}) { + return this._makeRequest({ + method: "GET", + path: "/credentials", + params: opts, + }); + }, + // Upload File + async uploadFile(fileData) { + const formData = new FormData(); + formData.append("file", fileData); + return this._makeRequest({ + method: "POST", + path: "/file", + data: formData, + headers: { + "Content-Type": "multipart/form-data", + }, + }); + }, + // Start Conversation + async startConversation(data) { + const conversationData = { + name: this.name, + assistantId: this.assistantId, + squadId: this.squadId, + phoneNumberId: this.phoneNumberId, + customerId: this.customerId, + }; + return this._makeRequest({ + method: "POST", + path: "/conversation", + data: conversationData, + }); + }, + // Update Assistant Configuration + async updateAssistant(id, data) { + return this._makeRequest({ + method: "PATCH", + path: `/assistant/${id}`, + data, + }); + }, + // Paginate Helper + async paginate(fn, opts = {}) { + let allResults = []; + let page = opts.page || 1; + let limit = opts.limit || 100; + let hasMore = true; + + while (hasMore) { + const response = await fn({ + ...opts, + page, + }); + allResults = allResults.concat(response.results); + if (response.results.length < limit) { + hasMore = false; + } else { + page += 1; + } + } + return allResults; + }, }, -}; \ No newline at end of file +}; From a5de3de310424196121c417fe55c05f63c47767b Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Mon, 3 Feb 2025 12:16:10 -0300 Subject: [PATCH 2/6] [Components] vapi #15134 Sources - New Conversation Actions - Create Call - Update Assistant Settings - Upload File --- .../vapi/actions/create-call/create-call.mjs | 37 +- .../update-assistant-settings.mjs | 314 +++++------ .../vapi/actions/upload-file/upload-file.mjs | 29 +- components/vapi/common/constants.mjs | 65 +++ components/vapi/common/utils.mjs | 48 ++ components/vapi/package.json | 5 +- components/vapi/sources/common/base.mjs | 58 ++ .../new-conversation/new-conversation.mjs | 112 +--- .../sources/new-conversation/test-event.mjs | 336 ++++++++++++ .../new-intent-detection.mjs | 140 ----- .../vapi/sources/new-message/new-message.mjs | 121 ----- components/vapi/vapi.app.mjs | 511 ++++-------------- 12 files changed, 826 insertions(+), 950 deletions(-) create mode 100644 components/vapi/common/constants.mjs create mode 100644 components/vapi/common/utils.mjs create mode 100644 components/vapi/sources/common/base.mjs create mode 100644 components/vapi/sources/new-conversation/test-event.mjs delete mode 100644 components/vapi/sources/new-intent-detection/new-intent-detection.mjs delete mode 100644 components/vapi/sources/new-message/new-message.mjs diff --git a/components/vapi/actions/create-call/create-call.mjs b/components/vapi/actions/create-call/create-call.mjs index 38b28858f99d2..bf30ca113b5c2 100644 --- a/components/vapi/actions/create-call/create-call.mjs +++ b/components/vapi/actions/create-call/create-call.mjs @@ -1,25 +1,33 @@ +import { ConfigurationError } from "@pipedream/platform"; import vapi from "../../vapi.app.mjs"; -import { axios } from "@pipedream/platform"; export default { key: "vapi-create-call", name: "Create Call", - description: "Starts a new conversation with an assistant. [See the documentation]()", - version: "0.0.{{ts}}", + description: "Starts a new conversation with an assistant. [See the documentation](https://docs.vapi.ai/api-reference/calls/create)", + version: "0.0.1", type: "action", props: { vapi, + name: { + type: "string", + label: "Conversation Name", + description: "Name of the new conversation", + optional: true, + }, assistantId: { propDefinition: [ vapi, "assistantId", ], + optional: true, }, squadId: { propDefinition: [ vapi, "squadId", ], + optional: true, }, phoneNumberId: { propDefinition: [ @@ -27,21 +35,28 @@ export default { "phoneNumberId", ], }, - name: { - type: "string", - label: "Conversation Name", - description: "Name of the new conversation (optional)", - optional: true, - }, customerId: { type: "string", label: "Customer ID", - description: "ID of the customer for the conversation (optional)", + description: "ID of the customer for the conversation", optional: true, }, }, async run({ $ }) { - const response = await this.vapi.startConversation(); + if (!this.assistantId && !this.squadId) { + throw new ConfigurationError("Need Either `Assistant Id` Or `Squad Id`"); + } + + const response = await this.vapi.startConversation({ + $, + data: { + assistantId: this.assistantId, + squadId: this.squadId, + phoneNumberId: this.phoneNumberId, + name: this.name, + customerId: this.customerId, + }, + }); $.export("$summary", `Conversation created with ID ${response.id}`); return response; }, diff --git a/components/vapi/actions/update-assistant-settings/update-assistant-settings.mjs b/components/vapi/actions/update-assistant-settings/update-assistant-settings.mjs index 79a173cb3a31e..e9e352fdeab83 100644 --- a/components/vapi/actions/update-assistant-settings/update-assistant-settings.mjs +++ b/components/vapi/actions/update-assistant-settings/update-assistant-settings.mjs @@ -1,251 +1,263 @@ +import { + BACKGROUND_SOUND, + CLIENT_MESSAGE_OPTIONS, + FIRST_MESSAGE_MODE_OPTIONS, + SERVER_MESSAGE_OPTIONS, +} from "../../common/constants.mjs"; +import { + clearObj, + parseObject, +} from "../../common/utils.mjs"; import vapi from "../../vapi.app.mjs"; -import { axios } from "@pipedream/platform"; export default { key: "vapi-update-assistant-settings", name: "Update Assistant Settings", - description: "Updates the configuration settings for a specific assistant. [See the documentation]()", - version: "0.0.{{ts}}", + description: "Updates the configuration settings for a specific assistant. [See the documentation](https://docs.vapi.ai/api-reference/assistants/update)", + version: "0.0.1", type: "action", props: { vapi, assistantId: { propDefinition: [ - "vapi", + vapi, "assistantId", ], }, transcriber: { - propDefinition: [ - "vapi", - "transcriber", - ], + type: "object", + label: "Transcriber", + description: "A formated JSON object for the assistant's transcriber. **Example: { \"provider\": \"talkscriber\", \"language\": \"en\", \"model\": \"whisper\" }**. [See the documentation](https://docs.vapi.ai/api-reference/assistants/update) for futher details", optional: true, }, model: { - propDefinition: [ - "vapi", - "model", - ], + type: "object", + label: "Model", + description: "A formated JSON object for the assistant's LLM. **Example: {\"provider\": \"xai\", \"model\": \"grok-beta\", \"emotionRecognitionEnabled\": true, \"knowledgeBase\": {\"server\": {\"url\": \"url\", \"timeoutSeconds\": 20}}, \"knowledgeBaseId\": \"model\", \"maxTokens\": 1.1, \"messages\": [{\"role\": \"assistant\"}], \"numFastTurns\": 1.1, \"temperature\": 1.1, \"toolIds\": [\"model\"], \"tools\": [{\"type\": \"transferCall\", \"async\": false}]}**. [See the documentation](https://docs.vapi.ai/api-reference/assistants/update) for futher details", optional: true, }, voice: { - propDefinition: [ - "vapi", - "voice", - ], + type: "object", + label: "Voice", + description: "A formated JSON object for the assistant's voice. **Example: {\"provider\":\"tavus\",\"voiceId\":\"r52da2535a\",\"callbackUrl\":\"voice\",\"chunkPlan\":{\"enabled\":true,\"minCharacters\":30,\"punctuationBoundaries\":[\"。\",\",\",\".\",\"!\",\"?\",\";\",\"،\",\"۔\",\"।\",\"॥\",\"|\",\"||\",\",\",\":\"],\"formatPlan\":{\"enabled\":true,\"numberToDigitsCutoff\":2025}},\"conversationName\":\"voice\",\"conversationalContext\":\"voice\",\"customGreeting\":\"voice\",\"fallbackPlan\":{\"voices\":[{\"provider\":\"tavus\",\"voiceId\":\"r52da2535a\"}]},\"personaId\":\"voice\",\"properties\":{\"maxCallDuration\":1.1,\"participantLeftTimeout\":1.1,\"participantAbsentTimeout\":1.1,\"enableRecording\":true,\"enableTranscription\":true,\"applyGreenscreen\":true,\"language\":\"language\",\"recordingS3BucketName\":\"recordingS3BucketName\",\"recordingS3BucketRegion\":\"recordingS3BucketRegion\",\"awsAssumeRoleArn\":\"awsAssumeRoleArn\"}}**. [See the documentation](https://docs.vapi.ai/api-reference/assistants/update) for futher details", optional: true, }, firstMessage: { - propDefinition: [ - "vapi", - "firstMessage", - ], + type: "string", + label: "First Message", + description: "The first message the assistant will say or a URL to an audio file. If unspecified, assistant will wait for user to speak and use the model to respond once they speak.", optional: true, }, firstMessageMode: { - propDefinition: [ - "vapi", - "firstMessageMode", - ], + type: "string", + label: "First Message Mode", + description: "Mode for the first message", optional: true, + options: FIRST_MESSAGE_MODE_OPTIONS, }, hipaaEnabled: { - propDefinition: [ - "vapi", - "hipaaEnabled", - ], + type: "boolean", + label: "HIPAA Enabled", + description: "When this is enabled, no logs, recordings, or transcriptions will be stored. At the end of the call, you will still receive an end-of-call-report message to store on your server.", optional: true, }, clientMessages: { - propDefinition: [ - "vapi", - "clientMessages", - ], + type: "string[]", + label: "Client Messages", + description: "These are the messages that will be sent to your Client SDKs", + options: CLIENT_MESSAGE_OPTIONS, optional: true, }, serverMessages: { - propDefinition: [ - "vapi", - "serverMessages", - ], + type: "string[]", + label: "Server Messages", + description: "These are the messages that will be sent to your Server URL", + options: SERVER_MESSAGE_OPTIONS, optional: true, }, silenceTimeoutSeconds: { - propDefinition: [ - "vapi", - "silenceTimeoutSeconds", - ], + type: "integer", + label: "Silence Timeout Seconds", + description: "How many seconds of silence to wait before ending the call. Defaults to 30", + optional: true, + min: 10, + max: 3600, + }, + maxDurationSeconds: { + type: "integer", + label: "Max Duration Seconds", + description: "This is the maximum number of seconds that the call will last. When the call reaches this duration, it will be ended. Defaults to 600 (10 minutes)", optional: true, + min: 10, + max: 43200, }, backgroundSound: { - propDefinition: [ - "vapi", - "backgroundSound", - ], + type: "string", + label: "Background Sound", + description: "This is the background sound in the call. Default for phone calls is 'office' and default for web calls is 'off'.", optional: true, + options: BACKGROUND_SOUND, }, backgroundDenoisingEnabled: { - propDefinition: [ - "vapi", - "backgroundDenoisingEnabled", - ], + type: "boolean", + label: "Background Denoising Enabled", + description: "This enables filtering of noise and background speech while the user is talking. Default false while in beta.", optional: true, }, modelOutputInMessagesEnabled: { - propDefinition: [ - "vapi", - "modelOutputInMessagesEnabled", - ], + type: "boolean", + label: "Model Output in Messages Enabled", + description: "This determines whether the model's output is used in conversation history rather than the transcription of assistant's speech. Default false while in beta.", optional: true, }, transportConfigurations: { - propDefinition: [ - "vapi", - "transportConfigurations", - ], + type: "string[]", + label: "Transport Configurations", + description: "These are the configurations to be passed to the transport providers of assistant's calls, like Twilio. You can store multiple configurations for different transport providers. For a call, only the configuration matching the call transport provider is used. **Example: [{\"provider\":\"twilio\",\"timeout\":60,\"record\":false,\"recordingChannels\":\"mono\"}]**. [See the documentation](https://docs.vapi.ai/api-reference/assistants/update) for futher details", optional: true, }, credentials: { - propDefinition: [ - "vapi", - "credentials", - ], + type: "string[]", + label: "Credentials", + description: "These are dynamic credentials that will be used for the assistant calls. By default, all the credentials are available for use in the call but you can supplement an additional credentials using this. Dynamic credentials override existing credentials. **Example: [{\"provider\":\"xai\",\"apiKey\":\"credentials\",\"name\":\"credentials\"}]**. [See the documentation](https://docs.vapi.ai/api-reference/assistants/update) for futher details", optional: true, }, name: { - propDefinition: [ - "vapi", - "name", - ], + type: "string", + label: "Name", + description: "Name of the assistant. This is required when you want to transfer between assistants in a call.", optional: true, }, voicemailDetection: { - propDefinition: [ - "vapi", - "voicemailDetection", - ], + type: "object", + label: "Voicemail Detection", + description: "These are the settings to configure or disable voicemail detection. Alternatively, voicemail detection can be configured using the model.tools=[VoicemailTool]. This uses Twilio's built-in detection while the VoicemailTool relies on the model to detect if a voicemail was reached. You can use neither of them, one of them, or both of them. By default, Twilio built-in detection is enabled while VoicemailTool is not. **Example: {\"provider\":\"twilio\",\"voicemailDetectionTypes\":[\"machine_end_beep\",\"machine_end_silence\"],\"enabled\":true,\"machineDetectionTimeout\":1.1,\"machineDetectionSpeechThreshold\":1.1,\"machineDetectionSpeechEndThreshold\":1.1,\"machineDetectionSilenceTimeout\":1.1}**. [See the documentation](https://docs.vapi.ai/api-reference/assistants/update) for futher details", + optional: true, + }, + voicemailMessage: { + type: "string", + label: "Voicemail Message", + description: "This is the message that the assistant will say if the call is forwarded to voicemail. If unspecified, it will hang up", optional: true, }, endCallMessage: { - propDefinition: [ - "vapi", - "endCallMessage", - ], + type: "string", + label: "End Call Message", + description: "This is the message that the assistant will say if it ends the call. If unspecified, it will hang up without saying anything", optional: true, }, endCallPhrases: { - propDefinition: [ - "vapi", - "endCallPhrases", - ], + type: "string[]", + label: "End Call Phrases", + description: "A list containing phrases that, if spoken by the assistant, will trigger the call to be hung up. Case insensitive.", optional: true, }, metadata: { - propDefinition: [ - "vapi", - "metadata", - ], + type: "object", + label: "Metadata", + description: "This is for metadata you want to store on the assistant.", optional: true, }, analysisPlan: { - propDefinition: [ - "vapi", - "analysisPlan", - ], + type: "object", + label: "Analysis Plan", + description: "This is the plan for analysis of assistant's calls. Stored in `call.analysis`. **Example: {\"summaryPlan\":{\"messages\":[{\"key\":\"value\"}],\"enabled\":true,\"timeoutSeconds\":1.1},\"structuredDataPlan\":{\"messages\":[{\"key\":\"value\"}],\"enabled\":true,\"schema\":{\"type\":\"string\"},\"timeoutSeconds\":1.1},\"successEvaluationPlan\":{\"rubric\":\"NumericScale\",\"messages\":[{\"key\":\"value\"}],\"enabled\":true,\"timeoutSeconds\":1.1}}**. [See the documentation](https://docs.vapi.ai/api-reference/assistants/update) for futher details", optional: true, }, artifactPlan: { - propDefinition: [ - "vapi", - "artifactPlan", - ], + type: "object", + label: "Artifact Plan", + description: "This is the plan for artifacts generated during assistant's calls. Stored in call.artifact. **Note:** `recordingEnabled` is currently at the root level. It will be moved to `artifactPlan` in the future, but will remain backwards compatible. **Example: {\"recordingEnabled\":true,\"videoRecordingEnabled\":false,\"transcriptPlan\":{\"enabled\":true,\"assistantName\":\"assistantName\",\"userName\":\"userName\"},\"recordingPath\":\"recordingPath\"}**. [See the documentation](https://docs.vapi.ai/api-reference/assistants/update) for futher details", optional: true, }, messagePlan: { - propDefinition: [ - "vapi", - "messagePlan", - ], + type: "object", + label: "Message Plan", + description: "This is the plan for static predefined messages that can be spoken by the assistant during the call, like idleMessages. **Note:** `firstMessage`, `voicemailMessage`, and `endCallMessage` are currently at the root level. They will be moved to ` ` in the future, but will remain backwards compatible. **Example: {\"idleMessages\":[\"idleMessages\"],\"idleMessageMaxSpokenCount\":1.1,\"idleTimeoutSeconds\":1.1}**. [See the documentation](https://docs.vapi.ai/api-reference/assistants/update) for futher details", optional: true, }, startSpeakingPlan: { - propDefinition: [ - "vapi", - "startSpeakingPlan", - ], + type: "object", + label: "Start Speaking Plan", + description: "This is the plan for when the assistant should start talking. **Example: {\"waitSeconds\":0.4,\"smartEndpointingEnabled\":false,\"customEndpointingRules\":[{\"type\":\"both\",\"assistantRegex\":\"customEndpointingRules\",\"customerRegex\":\"customEndpointingRules\",\"timeoutSeconds\":1.1}],\"transcriptionEndpointingPlan\":{\"onPunctuationSeconds\":0.1,\"onNoPunctuationSeconds\":1.5,\"onNumberSeconds\":0.5}}**. [See the documentation](https://docs.vapi.ai/api-reference/assistants/update) for futher details", optional: true, }, stopSpeakingPlan: { - propDefinition: [ - "vapi", - "stopSpeakingPlan", - ], + type: "object", + label: "Stop Speaking Plan", + description: "This is the plan for when assistant should stop talking on customer interruption. **Example: {\"numWords\":0,\"voiceSeconds\":0.2,\"backoffSeconds\":1}**. [See the documentation](https://docs.vapi.ai/api-reference/assistants/update) for futher details", optional: true, }, monitorPlan: { - propDefinition: [ - "vapi", - "monitorPlan", - ], + type: "object", + label: "Monitor Plan", + description: "This is the plan for real-time monitoring of the assistant's calls. **Note:** `serverMessages`, `clientMessages`, `serverUrl` and `serverUrlSecret` are currently at the root level but will be moved to `monitorPlan` in the future. Will remain backwards compatible. **Example: {\"listenEnabled\":false,\"controlEnabled\":false}**. [See the documentation](https://docs.vapi.ai/api-reference/assistants/update) for futher details", optional: true, }, credentialIds: { - propDefinition: [ - "vapi", - "credentialIds", - ], + type: "string[]", + label: "Credential IDs", + description: "These are the credentials that will be used for the assistant calls. By default, all the credentials are available for use in the call but you can provide a subset using this.", optional: true, }, server: { - propDefinition: [ - "vapi", - "server", - ], + type: "object", + label: "Server", + description: "This is where Vapi will send webhooks. You can find all webhooks available along with their shape in ServerMessage schema. **Example: {\"url\":\"url\",\"timeoutSeconds\":20,\"secret\":\"secret\",\"headers\":{\"key\":\"value\"}}**. [See the documentation](https://docs.vapi.ai/api-reference/assistants/update) for futher details", optional: true, }, }, async run({ $ }) { - const data = {}; - const optionalProps = [ - "transcriber", - "model", - "voice", - "firstMessage", - "firstMessageMode", - "hipaaEnabled", - "clientMessages", - "serverMessages", - "silenceTimeoutSeconds", - "backgroundSound", - "backgroundDenoisingEnabled", - "modelOutputInMessagesEnabled", - "transportConfigurations", - "credentials", - "name", - "voicemailDetection", - "endCallMessage", - "endCallPhrases", - "metadata", - "analysisPlan", - "artifactPlan", - "messagePlan", - "startSpeakingPlan", - "stopSpeakingPlan", - "monitorPlan", - "credentialIds", - "server", - ]; - - for (const prop of optionalProps) { - if (this[prop] !== undefined && this[prop] !== null && this[prop] !== "") { - data[prop] = this[prop]; - } - } + const { + vapi, + assistantId, + transcriber, + model, + voice, + clientMessages, + serverMessages, + transportConfigurations, + credentials, + voicemailDetection, + endCallPhrases, + metadata, + analysisPlan, + artifactPlan, + messagePlan, + startSpeakingPlan, + stopSpeakingPlan, + monitorPlan, + credentialIds, + server, + ...data + } = this; - const assistantId = this.assistantId; - const response = await this.vapi.updateAssistant(assistantId, data); - $.export("$summary", `Updated assistant ${assistantId} successfully`); + const response = await vapi.updateAssistant({ + $, + assistantId, + data: clearObj({ + ...data, + transcriber: parseObject(transcriber), + model: parseObject(model), + voice: parseObject(voice), + clientMessages: parseObject(clientMessages), + serverMessages: parseObject(serverMessages), + transportConfigurations: parseObject(transportConfigurations), + credentials: parseObject(credentials), + voicemailDetection: parseObject(voicemailDetection), + endCallPhrases: parseObject(endCallPhrases), + metadata: parseObject(metadata), + analysisPlan: parseObject(analysisPlan), + artifactPlan: parseObject(artifactPlan), + messagePlan: parseObject(messagePlan), + startSpeakingPlan: parseObject(startSpeakingPlan), + stopSpeakingPlan: parseObject(stopSpeakingPlan), + monitorPlan: parseObject(monitorPlan), + credentialIds: parseObject(credentialIds), + server: parseObject(server), + }), + }); + $.export("$summary", `Updated assistant ${this.assistantId} successfully`); return response; }, }; diff --git a/components/vapi/actions/upload-file/upload-file.mjs b/components/vapi/actions/upload-file/upload-file.mjs index 9b333908e13b5..cf6d9c49a77c2 100644 --- a/components/vapi/actions/upload-file/upload-file.mjs +++ b/components/vapi/actions/upload-file/upload-file.mjs @@ -1,26 +1,33 @@ +import FormData from "form-data"; +import fs from "fs"; +import { checkTmp } from "../../common/utils.mjs"; import vapi from "../../vapi.app.mjs"; -import { axios } from "@pipedream/platform"; export default { key: "vapi-upload-file", name: "Upload File", description: "Uploads a new file. [See the documentation](https://docs.vapi.ai/api-reference)", - version: "0.0.{{ts}}", + version: "0.0.1", type: "action", props: { - vapi: { - type: "app", - app: "vapi", - }, + vapi, file: { - propDefinition: [ - vapi, - "file", - ], + type: "string", + label: "File", + description: "The path to the file saved to the `/tmp` directory (e.g. `/tmp/example.txt`). [See the documentation](https://pipedream.com/docs/workflows/steps/code/nodejs/working-with-files/#the-tmp-directory).", }, }, async run({ $ }) { - const response = await this.vapi.uploadFile(this.file); + const formData = new FormData(); + const filePath = checkTmp(this.file); + + formData.append("file", fs.createReadStream(filePath)); + + const response = await this.vapi.uploadFile({ + $, + data: formData, + headers: formData.getHeaders(), + }); $.export("$summary", `File uploaded successfully with ID ${response.id} and status ${response.status}`); return response; }, diff --git a/components/vapi/common/constants.mjs b/components/vapi/common/constants.mjs new file mode 100644 index 0000000000000..98f73d4bb84be --- /dev/null +++ b/components/vapi/common/constants.mjs @@ -0,0 +1,65 @@ +export const LIMIT = 1000; + +export const FIRST_MESSAGE_MODE_OPTIONS = [ + { + label: "Assistant Speaks First", + value: "assistant-speaks-first", + }, + { + label: "Assistant Waits for User", + value: "assistant-waits-for-user", + }, + { + label: "Assistant Speaks First with Model Generated Message", + value: "assistant-speaks-first-with-model-generated-message", + }, +]; + +export const CLIENT_MESSAGE_OPTIONS = [ + "conversation-update", + "function-call", + "function-call-result", + "hang", + "language-changed", + "metadata", + "model-output", + "speech-update", + "status-update", + "transcript", + "tool-calls", + "tool-calls-result", + "transfer-update", + "user-interrupted", + "voice-input", +]; + +export const SERVER_MESSAGE_OPTIONS = [ + "conversation-update", + "end-of-call-report", + "function-call", + "hang", + "language-changed", + "language-change-detected", + "model-output", + "phone-call-control", + "speech-update", + "status-update", + "transcript", + "transcript[transcriptType=\"final\"]", + "tool-calls", + "transfer-destination-request", + "transfer-update", + "user-interrupted", + "voice-input", +]; + +export const BACKGROUND_SOUND = [ + { + label: "Office", + value: "office", + }, + { + label: "Off", + value: "off", + }, +]; diff --git a/components/vapi/common/utils.mjs b/components/vapi/common/utils.mjs new file mode 100644 index 0000000000000..9420d92839d7d --- /dev/null +++ b/components/vapi/common/utils.mjs @@ -0,0 +1,48 @@ +export const checkTmp = (filename) => { + if (!filename.startsWith("/tmp")) { + return `/tmp/${filename}`; + } + return filename; +}; + +export const parseObject = (obj) => { + if (!obj) return undefined; + + if (Array.isArray(obj)) { + return obj.map((item) => { + if (typeof item === "string") { + try { + return JSON.parse(item); + } catch (e) { + return item; + } + } + return item; + }); + } + if (typeof obj === "string") { + try { + return JSON.parse(obj); + } catch (e) { + return obj; + } + } + return obj; +}; + +export const clearObj = (obj) => { + return Object.entries(obj) + .filter(([ + , + v, + ]) => (v != null && v != "" && JSON.stringify(v) != "{}")) + .reduce((acc, [ + k, + v, + ]) => ({ + ...acc, + [k]: (!Array.isArray(v) && v === Object(v)) + ? clearObj(v) + : v, + }), {}); +}; diff --git a/components/vapi/package.json b/components/vapi/package.json index 3a5043bb8cb21..e167d480403ab 100644 --- a/components/vapi/package.json +++ b/components/vapi/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/vapi", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream Vapi Components", "main": "vapi.app.mjs", "keywords": [ @@ -11,5 +11,8 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3" } } diff --git a/components/vapi/sources/common/base.mjs b/components/vapi/sources/common/base.mjs new file mode 100644 index 0000000000000..77732a07557e5 --- /dev/null +++ b/components/vapi/sources/common/base.mjs @@ -0,0 +1,58 @@ +import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; +import { LIMIT } from "../../common/constants.mjs"; +import vapi from "../../vapi.app.mjs"; + +export default { + props: { + vapi, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + }, + methods: { + _getLastDate() { + return this.db.get("lastDate") || "1970-01-01"; + }, + _setLastDate(lastDate) { + this.db.set("lastDate", lastDate); + }, + async emitEvent(maxResults = false) { + const lastDate = this._getLastDate(); + const fn = this.getFunction(); + + const response = await fn({ + params: { + createdAtGt: lastDate, + limit: LIMIT, + }, + }); + + if (response.length) { + if (maxResults && (response.length > maxResults)) { + response.length = maxResults; + } + this._setLastDate(response[0].createdAt); + } + + for (const item of response.reverse()) { + this.$emit(item, { + id: item.id, + summary: this.getSummary(item), + ts: Date.parse(item.createdAt), + }); + } + }, + }, + hooks: { + async deploy() { + await this.emitEvent(25); + }, + }, + async run() { + await this.emitEvent(); + }, +}; diff --git a/components/vapi/sources/new-conversation/new-conversation.mjs b/components/vapi/sources/new-conversation/new-conversation.mjs index 071e7b482a567..4161a77b3bc9a 100644 --- a/components/vapi/sources/new-conversation/new-conversation.mjs +++ b/components/vapi/sources/new-conversation/new-conversation.mjs @@ -1,110 +1,22 @@ -import { - axios, DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, -} from "@pipedream/platform"; -import vapi from "../../vapi.app.mjs"; +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; export default { + ...common, key: "vapi-new-conversation", name: "New Conversation Started", - description: "Emits a new event when a voicebot starts a conversation. [See the documentation]()", - version: "0.0.{{ts}}", + description: "Emit new event when a voicebot starts a conversation.", + version: "0.0.1", type: "source", dedupe: "unique", - props: { - vapi, - db: "$.service.db", - timer: { - type: "$.interface.timer", - default: { - intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, - }, + methods: { + ...common.methods, + getFunction() { + return this.vapi.listCalls; }, - voicebotIds: { - propDefinition: [ - vapi, - "voicebotIds", - ], - optional: true, + getSummary(item) { + return `New Conversation: ${item.id}`; }, }, - hooks: { - async deploy() { - const lastRun = Date.now(); - await this.db.set("last_run_ts", lastRun); - - const params = { - limit: 50, - sort: "desc", - }; - - if (this.voicebotIds?.length) { - params.voicebotIds = this.voicebotIds.join(","); - } - - const conversations = await this.vapi._makeRequest({ - method: "GET", - path: "/conversation", - params, - }); - - conversations.reverse().forEach((convo) => { - this.$emit(convo, { - id: convo.id || convo.ts, - summary: `New Conversation: ${convo.id}`, - ts: convo.createdAt - ? Date.parse(convo.createdAt) - : lastRun, - }); - }); - }, - async activate() { - // No webhook subscription needed for polling - }, - async deactivate() { - // No webhook subscription to remove - }, - }, - async run() { - const lastRun = (await this.db.get("last_run_ts")) || Date.now(); - let newLastRun = lastRun; - - const params = { - createdAt_gt: lastRun, - limit: 100, - sort: "asc", - }; - - if (this.voicebotIds?.length) { - params.voicebotIds = this.voicebotIds.join(","); - } - - const conversations = await this.vapi._makeRequest({ - method: "GET", - path: "/conversation", - params, - }); - - for (const convo of conversations) { - this.$emit(convo, { - id: convo.id || convo.ts, - summary: `New Conversation: ${convo.id}`, - ts: convo.createdAt - ? Date.parse(convo.createdAt) - : Date.now(), - }); - - if (convo.createdAt) { - const convoTs = Date.parse(convo.createdAt); - if (convoTs > newLastRun) { - newLastRun = convoTs; - } - } else { - if (Date.now() > newLastRun) { - newLastRun = Date.now(); - } - } - } - - await this.db.set("last_run_ts", newLastRun); - }, + sampleEmit, }; diff --git a/components/vapi/sources/new-conversation/test-event.mjs b/components/vapi/sources/new-conversation/test-event.mjs new file mode 100644 index 0000000000000..39ba5530a6b00 --- /dev/null +++ b/components/vapi/sources/new-conversation/test-event.mjs @@ -0,0 +1,336 @@ +export default { + "id": "id", + "orgId": "orgId", + "createdAt": "2024-01-15T09:30:00Z", + "updatedAt": "2024-01-15T09:30:00Z", + "type": "inboundPhoneCall", + "costs": [ + { + "type": "analysis", + "analysisType": "summary", + "completionTokens": 1.1, + "cost": 1.1, + "model": { + "key": "value" + }, + "promptTokens": 1.1 + } + ], + "messages": [ + { + "role": "role", + "message": "message", + "time": 1.1, + "endTime": 1.1, + "secondsFromStart": 1.1 + } + ], + "phoneCallProvider": "twilio", + "phoneCallTransport": "sip", + "status": "queued", + "endedReason": "assistant-not-invalid", + "destination": { + "type": "sip", + "sipUri": "destination", + "description": "destination", + "message": "destination", + "sipHeaders": { + "key": "value" + }, + "transferPlan": { + "mode": "blind-transfer" + } + }, + "startedAt": "2024-01-15T09:30:00Z", + "endedAt": "2024-01-15T09:30:00Z", + "cost": 1.1, + "costBreakdown": { + "transport": 1.1, + "stt": 1.1, + "llm": 1.1, + "tts": 1.1, + "vapi": 1.1, + "total": 1.1, + "llmPromptTokens": 1.1, + "llmCompletionTokens": 1.1, + "ttsCharacters": 1.1 + }, + "artifactPlan": { + "recordingEnabled": true, + "videoRecordingEnabled": false, + "transcriptPlan": { + "enabled": true + }, + "recordingPath": "recordingPath" + }, + "analysis": { + "summary": "summary", + "structuredData": { + "key": "value" + }, + "successEvaluation": "successEvaluation" + }, + "monitor": { + "listenUrl": "listenUrl", + "controlUrl": "controlUrl" + }, + "artifact": { + "messages": [ + { + "role": "role", + "message": "message", + "time": 1.1, + "endTime": 1.1, + "secondsFromStart": 1.1 + } + ], + "messagesOpenAIFormatted": [ + { + "role": "assistant" + } + ], + "recordingUrl": "recordingUrl", + "stereoRecordingUrl": "stereoRecordingUrl", + "videoRecordingUrl": "videoRecordingUrl", + "videoRecordingStartDelaySeconds": 1.1, + "transcript": "transcript" + }, + "transport": { + "provider": "twilio", + "assistantVideoEnabled": true + }, + "phoneCallProviderId": "phoneCallProviderId", + "assistantId": "assistantId", + "assistant": { + "transcriber": { + "provider": "talkscriber" + }, + "model": { + "provider": "xai", + "model": "grok-beta" + }, + "voice": { + "provider": "tavus", + "voiceId": "r52da2535a" + }, + "firstMessage": "Hello! How can I help you today?", + "firstMessageMode": "assistant-speaks-first", + "hipaaEnabled": false, + "clientMessages": [ + "conversation-update", + "function-call", + "hang", + "model-output", + "speech-update", + "status-update", + "transfer-update", + "transcript", + "tool-calls", + "user-interrupted", + "voice-input" + ], + "serverMessages": [ + "conversation-update", + "end-of-call-report", + "function-call", + "hang", + "speech-update", + "status-update", + "tool-calls", + "transfer-destination-request", + "user-interrupted" + ], + "silenceTimeoutSeconds": 30, + "maxDurationSeconds": 600, + "backgroundSound": "off", + "backgroundDenoisingEnabled": false, + "modelOutputInMessagesEnabled": false, + "transportConfigurations": [ + { + "provider": "twilio", + "timeout": 60, + "record": false + } + ], + "credentials": [ + { + "provider": "xai", + "apiKey": "credentials" + } + ], + "name": "name", + "voicemailDetection": { + "provider": "twilio" + }, + "voicemailMessage": "voicemailMessage", + "endCallMessage": "endCallMessage", + "endCallPhrases": [ + "endCallPhrases" + ], + "metadata": { + "key": "value" + }, + "artifactPlan": { + "recordingEnabled": true, + "videoRecordingEnabled": false + }, + "startSpeakingPlan": { + "waitSeconds": 0.4, + "smartEndpointingEnabled": false + }, + "stopSpeakingPlan": { + "numWords": 0, + "voiceSeconds": 0.2, + "backoffSeconds": 1 + }, + "monitorPlan": { + "listenEnabled": false, + "controlEnabled": false + }, + "credentialIds": [ + "credentialIds" + ], + "server": { + "url": "url", + "timeoutSeconds": 20 + } + }, + "assistantOverrides": { + "transcriber": { + "provider": "talkscriber" + }, + "model": { + "provider": "xai", + "model": "grok-beta" + }, + "voice": { + "provider": "tavus", + "voiceId": "r52da2535a" + }, + "firstMessage": "Hello! How can I help you today?", + "firstMessageMode": "assistant-speaks-first", + "hipaaEnabled": false, + "clientMessages": [ + "conversation-update", + "function-call", + "hang", + "model-output", + "speech-update", + "status-update", + "transfer-update", + "transcript", + "tool-calls", + "user-interrupted", + "voice-input" + ], + "serverMessages": [ + "conversation-update", + "end-of-call-report", + "function-call", + "hang", + "speech-update", + "status-update", + "tool-calls", + "transfer-destination-request", + "user-interrupted" + ], + "silenceTimeoutSeconds": 30, + "maxDurationSeconds": 600, + "backgroundSound": "off", + "backgroundDenoisingEnabled": false, + "modelOutputInMessagesEnabled": false, + "transportConfigurations": [ + { + "provider": "twilio", + "timeout": 60, + "record": false + } + ], + "credentials": [ + { + "provider": "xai", + "apiKey": "credentials" + } + ], + "variableValues": { + "key": "value" + }, + "name": "name", + "voicemailDetection": { + "provider": "twilio" + }, + "voicemailMessage": "voicemailMessage", + "endCallMessage": "endCallMessage", + "endCallPhrases": [ + "endCallPhrases" + ], + "metadata": { + "key": "value" + }, + "artifactPlan": { + "recordingEnabled": true, + "videoRecordingEnabled": false + }, + "startSpeakingPlan": { + "waitSeconds": 0.4, + "smartEndpointingEnabled": false + }, + "stopSpeakingPlan": { + "numWords": 0, + "voiceSeconds": 0.2, + "backoffSeconds": 1 + }, + "monitorPlan": { + "listenEnabled": false, + "controlEnabled": false + }, + "credentialIds": [ + "credentialIds" + ], + "server": { + "url": "url", + "timeoutSeconds": 20 + } + }, + "squadId": "squadId", + "squad": { + "members": [ + {} + ], + "name": "name", + "membersOverrides": { + "firstMessage": "Hello! How can I help you today?", + "hipaaEnabled": false, + "silenceTimeoutSeconds": 30, + "maxDurationSeconds": 600, + "backgroundDenoisingEnabled": false, + "modelOutputInMessagesEnabled": false + } + }, + "phoneNumberId": "phoneNumberId", + "phoneNumber": { + "twilioAccountSid": "twilioAccountSid", + "twilioAuthToken": "twilioAuthToken", + "twilioPhoneNumber": "twilioPhoneNumber", + "fallbackDestination": { + "type": "sip", + "sipUri": "fallbackDestination" + }, + "name": "name", + "assistantId": "assistantId", + "squadId": "squadId", + "server": { + "url": "url", + "timeoutSeconds": 20 + } + }, + "customerId": "customerId", + "customer": { + "numberE164CheckEnabled": true, + "extension": "extension", + "number": "number", + "sipUri": "sipUri", + "name": "name" + }, + "name": "name" +} \ No newline at end of file diff --git a/components/vapi/sources/new-intent-detection/new-intent-detection.mjs b/components/vapi/sources/new-intent-detection/new-intent-detection.mjs deleted file mode 100644 index b638326da1b1a..0000000000000 --- a/components/vapi/sources/new-intent-detection/new-intent-detection.mjs +++ /dev/null @@ -1,140 +0,0 @@ -import { - axios, DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, -} from "@pipedream/platform"; -import vapi from "../../vapi.app.mjs"; - -export default { - key: "vapi-new-intent-detection", - name: "New Intent Detected", - description: "Emit new event when a specific intent is detected during a conversation. [See the documentation]()", - version: "0.0.{{ts}}", - type: "source", - dedupe: "unique", - props: { - vapi: { - type: "app", - app: "vapi", - }, - db: "$.service.db", - timer: { - type: "$.interface.timer", - default: { - intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, - }, - }, - intentToMonitor: { - propDefinition: [ - vapi, - "intentToMonitor", - ], - }, - filterVoicebotId: { - propDefinition: [ - vapi, - "filterVoicebotId", - ], - optional: true, - }, - }, - hooks: { - async deploy() { - const lastTs = await this.db.get("last_ts") || new Date(0).toISOString(); - const params = { - createdAtGt: lastTs, - limit: 50, - sortOrder: "ASC", - }; - - if (this.filterVoicebotId && this.filterVoicebotId.length > 0) { - params.assistantId = this.filterVoicebotId; - } - - const logs = await this.vapi._makeRequest({ - method: "GET", - path: "/logs", - params, - }); - - // Sort logs by time ascending - const sortedLogs = logs.sort((a, b) => new Date(a.time) - new Date(b.time)); - let emittedCount = 0; - - for (const log of sortedLogs) { - if (emittedCount >= 50) break; - const intent = log.requestBody?.intent || log.responseBody?.intent; - - if (intent === this.intentToMonitor) { - this.$emit(log, { - id: log.id || log.time, - summary: `Intent detected: ${this.intentToMonitor}`, - ts: log.time - ? Date.parse(log.time) - : Date.now(), - }); - emittedCount++; - } - } - - // Update last_ts to the latest log's time - if (logs.length > 0) { - const latestLog = logs[logs.length - 1]; - await this.db.set( - "last_ts", - latestLog.time - ? new Date(latestLog.time).toISOString() - : new Date().toISOString(), - ); - } - }, - async activate() { - // No action needed for activate in polling source - }, - async deactivate() { - // No action needed for deactivate in polling source - }, - }, - async run() { - const lastTs = await this.db.get("last_ts") || new Date(0).toISOString(); - const params = { - createdAtGt: lastTs, - limit: 1000, - sortOrder: "ASC", - }; - - if (this.filterVoicebotId && this.filterVoicebotId.length > 0) { - params.assistantId = this.filterVoicebotId; - } - - const logs = await this.vapi._makeRequest({ - method: "GET", - path: "/logs", - params, - }); - - for (const log of logs) { - const intent = log.requestBody?.intent || log.responseBody?.intent; - - if (intent === this.intentToMonitor) { - const timestamp = log.time - ? Date.parse(log.time) - : Date.now(); - this.$emit(log, { - id: log.id || timestamp, - summary: `Intent detected: ${this.intentToMonitor}`, - ts: timestamp, - }); - } - } - - // Update last_ts to the latest log's time - if (logs.length > 0) { - const latestLog = logs[logs.length - 1]; - await this.db.set( - "last_ts", - latestLog.time - ? new Date(latestLog.time).toISOString() - : new Date().toISOString(), - ); - } - }, -}; diff --git a/components/vapi/sources/new-message/new-message.mjs b/components/vapi/sources/new-message/new-message.mjs deleted file mode 100644 index c5643212216e1..0000000000000 --- a/components/vapi/sources/new-message/new-message.mjs +++ /dev/null @@ -1,121 +0,0 @@ -import { - axios, DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, -} from "@pipedream/platform"; -import vapi from "../../vapi.app.mjs"; - -export default { - key: "vapi-new-message", - name: "New Message", - description: "Emit new events when a voicebot sends or receives a message during a conversation. [See the documentation]()", - version: "0.0.{{ts}}", - type: "source", - dedupe: "unique", - props: { - vapi: { - type: "app", - app: "vapi", - }, - db: "$.service.db", - timer: { - type: "$.interface.timer", - default: { - intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, - }, - }, - voicebotIds: { - propDefinition: [ - "vapi", - "voicebotIds", - ], - }, - conversationId: { - propDefinition: [ - "vapi", - "conversationId", - ], - }, - }, - hooks: { - async deploy() { - const lastTs = (await this.db.get("lastTs")) || 0; - const params = { - type: "APIWebhookCallProvider", - webhookType: "message", - sortOrder: "DESC", - limit: 50, - }; - if (this.voicebotIds) { - params.assistantId = this.voicebotIds; - } - if (this.conversationId) { - params.callId = this.conversationId; - } - const logs = await this.vapi._makeRequest({ - method: "GET", - path: "/logs", - params, - }); - const sortedLogs = logs.results.sort((a, b) => new Date(a.time) - new Date(b.time)); - for (const log of sortedLogs) { - const ts = Date.parse(log.time) || Date.now(); - const id = log.assistantId - ? log.assistantId - : ts; - const summary = `New message from assistant ${log.assistantId || "Unknown"}`; - this.$emit(log, { - id, - summary, - ts, - }); - } - const latestTs = logs.results.length > 0 - ? Date.parse(logs.results[0].time) - : lastTs; - await this.db.set("lastTs", latestTs); - }, - async activate() { - // No webhook subscription needed for polling source - }, - async deactivate() { - // No webhook subscription to remove - }, - }, - async run() { - const lastTs = (await this.db.get("lastTs")) || 0; - const params = { - type: "APIWebhookCallProvider", - webhookType: "message", - sortOrder: "DESC", - limit: 100, - createdAtGt: new Date(lastTs).toISOString(), - }; - if (this.voicebotIds) { - params.assistantId = this.voicebotIds; - } - if (this.conversationId) { - params.callId = this.conversationId; - } - const logs = await this.vapi._makeRequest({ - method: "GET", - path: "/logs", - params, - }); - const sortedLogs = logs.results.sort((a, b) => new Date(a.time) - new Date(b.time)); - for (const log of sortedLogs) { - const ts = Date.parse(log.time) || Date.now(); - const id = log.assistantId - ? log.assistantId - : ts; - const summary = `New message from assistant ${log.assistantId || "Unknown"}`; - this.$emit(log, { - id, - summary, - ts, - }); - } - if (logs.results.length > 0) { - const latestTs = Date.parse(logs.results[0].time); - await this.db.set("lastTs", latestTs); - } - }, -}; diff --git a/components/vapi/vapi.app.mjs b/components/vapi/vapi.app.mjs index f0df46dffd54d..feaa573240422 100644 --- a/components/vapi/vapi.app.mjs +++ b/components/vapi/vapi.app.mjs @@ -1,479 +1,160 @@ import { axios } from "@pipedream/platform"; +import { LIMIT } from "./common/constants.mjs"; export default { type: "app", app: "vapi", - version: "0.0.{{ts}}", propDefinitions: { - // Emit new event when a voicebot starts a conversation - voicebotIds: { - type: "string[]", - label: "Voicebot IDs", - description: "Filter events by specific voicebot IDs (optional)", - optional: true, - async options() { - const assistants = await this.listAssistants(); - return assistants.map((assistant) => ({ - label: assistant.name, - value: assistant.id, - })); - }, - }, - // Emit new event when a voicebot sends/receives a message during a conversation - conversationId: { - type: "string", - label: "Conversation ID", - description: "Filter events by specific conversation ID (optional)", - optional: true, - }, - // Emit new event when a specific intent is detected during a conversation - intentToMonitor: { - type: "string", - label: "Intent to Monitor", - description: "Specify the intent to monitor during conversations", - }, - filterVoicebotId: { - type: "string[]", - label: "Filter by Voicebot ID", - description: "Optionally filter by specific voicebot IDs", - optional: true, - async options() { - const assistants = await this.listAssistants(); - return assistants.map((assistant) => ({ - label: assistant.name, - value: assistant.id, - })); - }, - }, - // Upload a new file - file: { - type: "any", - label: "File", - description: "The file to upload", - }, - // Start a new conversation with an assistant - name: { - type: "string", - label: "Conversation Name", - description: "Name of the new conversation (optional)", - optional: true, - }, assistantId: { type: "string", label: "Assistant ID", - description: "ID of the assistant to start a conversation with or update (optional for starting, required for updating)", - optional: true, + description: "ID of the assistant to start a conversation with or update", async options() { - const assistants = await this.listAssistants(); - return assistants.map((assistant) => ({ - label: assistant.name, - value: assistant.id, + const assistants = await this.listAssistants({ + params: { + limit: LIMIT, + }, + }); + return assistants.map(({ + id: value, name: label, + }) => ({ + label, + value, })); }, }, squadId: { type: "string", label: "Squad ID", - description: "ID of the squad to assign to the conversation (optional)", - optional: true, + description: "ID of the squad to assign to the conversation", async options() { - const squads = await this.listSquads(); - return squads.map((squad) => ({ - label: squad.name, - value: squad.id, + const squads = await this.listSquads({ + params: { + limit: LIMIT, + }, + }); + return squads.map(({ + id: value, name: label, + }) => ({ + label, + value, })); }, }, phoneNumberId: { type: "string", label: "Phone Number ID", - description: "ID of the phone number to use for the conversation (optional)", - optional: true, - async options() { - const phoneNumbers = await this.listPhoneNumbers(); - return phoneNumbers.map((phone) => ({ - label: phone.name || phone.id, - value: phone.id, - })); - }, - }, - customerId: { - type: "string", - label: "Customer ID", - description: "ID of the customer for the conversation (optional)", - optional: true, - }, - // Update configuration settings for a specific assistant - transcriber: { - type: "string", - label: "Transcriber", - description: "Options for the assistant’s transcriber (optional)", - optional: true, + description: "ID of the phone number to use for the conversation", async options() { - return [ - { - label: "Talkscriber", - value: "talkscriber", - }, - { - label: "Deepgram", - value: "deepgram", - }, - { - label: "AzureSpeech", - value: "azurespeech", + const phoneNumbers = await this.listPhoneNumbers({ + params: { + limit: LIMIT, }, - ]; - }, - }, - model: { - type: "string", - label: "Model", - description: "Options for the assistant’s LLM (optional)", - optional: true, - async options() { - return [ - { - label: "Grok-Beta", - value: "grok-beta", - }, - { - label: "OpenAI-GPT-4", - value: "openai-gpt-4", - }, - { - label: "Anthropic-Claude", - value: "anthropic-claude", - }, - ]; - }, - }, - voice: { - type: "string", - label: "Voice", - description: "Options for the assistant’s voice (optional)", - optional: true, - async options() { - return [ - { - label: "Tavus Voice 1", - value: "tavus-voice-1", - }, - { - label: "ElevenLabs Voice A", - value: "elevenlabs-voice-a", - }, - { - label: "Deepgram Voice X", - value: "deepgram-voice-x", - }, - ]; - }, - }, - firstMessage: { - type: "string", - label: "First Message", - description: "The first message the assistant will say or a URL to an audio file (optional)", - optional: true, - }, - firstMessageMode: { - type: "string", - label: "First Message Mode", - description: "Mode for the first message (optional)", - optional: true, - async options() { - return [ - { - label: "Assistant Speaks First", - value: "assistant-speaks-first", - }, - { - label: "Assistant Waits for User", - value: "assistant-waits-for-user", - }, - { - label: "Assistant Speaks First with Model Generated Message", - value: "assistant-speaks-first-with-model-generated-message", - }, - ]; - }, - }, - hipaaEnabled: { - type: "boolean", - label: "HIPAA Enabled", - description: "Enable HIPAA compliance settings (optional)", - optional: true, - }, - clientMessages: { - type: "string[]", - label: "Client Messages", - description: "Messages to send to Client SDKs (optional)", - optional: true, - }, - serverMessages: { - type: "string[]", - label: "Server Messages", - description: "Messages to send to Server URL (optional)", - optional: true, - }, - silenceTimeoutSeconds: { - type: "integer", - label: "Silence Timeout Seconds", - description: "Seconds of silence before ending the call (optional, min:10, max:3600)", - optional: true, - min: 10, - max: 3600, - }, - backgroundSound: { - type: "string", - label: "Background Sound", - description: "Background sound in the call (optional)", - optional: true, - async options() { - return [ - { - label: "Office", - value: "office", - }, - { - label: "Off", - value: "off", - }, - ]; - }, - }, - backgroundDenoisingEnabled: { - type: "boolean", - label: "Background Denoising Enabled", - description: "Enable background noise filtering (optional)", - optional: true, - }, - modelOutputInMessagesEnabled: { - type: "boolean", - label: "Model Output in Messages Enabled", - description: "Use model’s output in conversation history (optional)", - optional: true, - }, - transportConfigurations: { - type: "string[]", - label: "Transport Configurations", - description: "Transport provider configurations (optional)", - optional: true, - }, - credentials: { - type: "string[]", - label: "Credentials", - description: "Dynamic credentials for assistant calls (optional)", - optional: true, - }, - name: { - type: "string", - label: "Name", - description: "Name of the assistant or conversation (optional)", - optional: true, - }, - voicemailDetection: { - type: "string", - label: "Voicemail Detection", - description: "Settings to configure voicemail detection (optional)", - optional: true, - async options() { - return [ - { - label: "Twilio", - value: "twilio", - }, - { - label: "None", - value: "none", - }, - ]; - }, - }, - endCallMessage: { - type: "string", - label: "End Call Message", - description: "Message to say when ending the call (optional)", - optional: true, - }, - endCallPhrases: { - type: "string[]", - label: "End Call Phrases", - description: "Phrases that trigger call termination (optional)", - optional: true, - }, - metadata: { - type: "object", - label: "Metadata", - description: "Metadata to store on the assistant (optional)", - optional: true, - }, - analysisPlan: { - type: "object", - label: "Analysis Plan", - description: "Plan for analysis of assistant’s calls (optional)", - optional: true, - }, - artifactPlan: { - type: "object", - label: "Artifact Plan", - description: "Plan for artifacts generated during calls (optional)", - optional: true, - }, - messagePlan: { - type: "object", - label: "Message Plan", - description: "Plan for predefined messages during the call (optional)", - optional: true, - }, - startSpeakingPlan: { - type: "object", - label: "Start Speaking Plan", - description: "Plan for when the assistant should start talking (optional)", - optional: true, - }, - stopSpeakingPlan: { - type: "object", - label: "Stop Speaking Plan", - description: "Plan for when the assistant should stop talking on interruption (optional)", - optional: true, - }, - monitorPlan: { - type: "object", - label: "Monitor Plan", - description: "Plan for real-time monitoring of calls (optional)", - optional: true, - }, - credentialIds: { - type: "string[]", - label: "Credential IDs", - description: "Credentials to use for assistant calls (optional)", - optional: true, - async options() { - const creds = await this.listCredentials(); - return creds.map((cred) => ({ - label: cred.name, - value: cred.id, + }); + return phoneNumbers.map(({ + id: value, name: label, + }) => ({ + label, + value, })); }, }, - server: { - type: "object", - label: "Server", - description: "Webhook server settings (optional)", - optional: true, - }, }, methods: { - // Existing method - authKeys() { - console.log(Object.keys(this.$auth)); - }, _baseUrl() { return "https://api.vapi.ai"; }, - async _makeRequest(opts = {}) { - const { - $, method = "GET", path = "/", headers, ...otherOpts - } = opts; - return axios($ || this, { - ...otherOpts, - method, + _headers(headers = {}) { + return { + ...headers, + Authorization: `Bearer ${this.$auth.api_key}`, + }; + }, + _makeRequest({ + $ = this, headers, path, ...opts + }) { + return axios($, { url: this._baseUrl() + path, - headers: { - ...headers, - Authorization: `Bearer ${this.$auth.api_token}`, - }, + headers: this._headers(headers), + ...opts, }); }, - // List Assistants - async listAssistants(opts = {}) { + startConversation(opts = {}) { return this._makeRequest({ - method: "GET", - path: "/assistant", - params: opts, + method: "POST", + path: "/call", + ...opts, }); }, - // List Squads - async listSquads(opts = {}) { + listAssistants(opts = {}) { return this._makeRequest({ - method: "GET", - path: "/squad", - params: opts, + path: "/assistant", + ...opts, }); }, - // List Phone Numbers - async listPhoneNumbers(opts = {}) { + listCalls(opts = {}) { return this._makeRequest({ - method: "GET", - path: "/phone-number", - params: opts, + path: "/call", + ...opts, }); }, - // List Credentials - async listCredentials(opts = {}) { + listSquads(opts = {}) { return this._makeRequest({ - method: "GET", - path: "/credentials", - params: opts, + path: "/squad", + ...opts, }); }, - // Upload File - async uploadFile(fileData) { - const formData = new FormData(); - formData.append("file", fileData); + listPhoneNumbers(opts = {}) { return this._makeRequest({ - method: "POST", - path: "/file", - data: formData, - headers: { - "Content-Type": "multipart/form-data", - }, + path: "/phone-number", + ...opts, }); }, - // Start Conversation - async startConversation(data) { - const conversationData = { - name: this.name, - assistantId: this.assistantId, - squadId: this.squadId, - phoneNumberId: this.phoneNumberId, - customerId: this.customerId, - }; + updateAssistant({ + assistantId, ...opts + }) { return this._makeRequest({ - method: "POST", - path: "/conversation", - data: conversationData, + method: "PATCH", + path: `/assistant/${assistantId}`, + ...opts, }); }, - // Update Assistant Configuration - async updateAssistant(id, data) { + uploadFile(opts = {}) { return this._makeRequest({ - method: "PATCH", - path: `/assistant/${id}`, - data, + method: "POST", + path: "/file", + ...opts, }); }, - // Paginate Helper - async paginate(fn, opts = {}) { - let allResults = []; - let page = opts.page || 1; - let limit = opts.limit || 100; - let hasMore = true; + async *paginate({ + fn, params = {}, maxResults = null, ...opts + }) { + let hasMore = false; + let count = 0; + let page = 0; - while (hasMore) { - const response = await fn({ + do { + params.page = ++page; + const { + data, + meta: { + current_page, last_page, + }, + } = await fn({ + params, ...opts, - page, }); - allResults = allResults.concat(response.results); - if (response.results.length < limit) { - hasMore = false; - } else { - page += 1; + for (const d of data) { + yield d; + + if (maxResults && ++count === maxResults) { + return count; + } } - } - return allResults; + + hasMore = !(current_page == last_page); + + } while (hasMore); }, }, }; From 299fab074afd320735deef2bebfc07909bcf6655 Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Mon, 3 Feb 2025 12:19:44 -0300 Subject: [PATCH 3/6] pnpm update --- pnpm-lock.yaml | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0098536ba922b..6442e1fd9e477 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -292,8 +292,7 @@ importers: specifier: ^3.0.1 version: 3.0.3 - components/adobe_document_generation_api: - specifiers: {} + components/adobe_document_generation_api: {} components/adobe_pdf_services: dependencies: @@ -6518,8 +6517,7 @@ importers: specifier: ^3.0.3 version: 3.0.3 - components/microsoft_dynamics_365_sales: - specifiers: {} + components/microsoft_dynamics_365_sales: {} components/microsoft_entra_id: dependencies: @@ -8012,8 +8010,7 @@ importers: specifier: ^1.6.0 version: 1.6.6 - components/planhat: - specifiers: {} + components/planhat: {} components/planly: {} @@ -10507,8 +10504,7 @@ importers: components/syncro: {} - components/synthflow: - specifiers: {} + components/synthflow: {} components/t2m_url_shortener: {} @@ -11455,7 +11451,11 @@ importers: specifier: ^3.0.0 version: 3.0.3 - components/vapi: {} + components/vapi: + dependencies: + '@pipedream/platform': + specifier: ^3.0.3 + version: 3.0.3 components/vbout: dependencies: @@ -31760,6 +31760,8 @@ snapshots: '@putout/operator-filesystem': 5.0.0(putout@36.13.1(eslint@8.57.1)(typescript@5.6.3)) '@putout/operator-json': 2.2.0 putout: 36.13.1(eslint@8.57.1)(typescript@5.6.3) + transitivePeerDependencies: + - supports-color '@putout/operator-regexp@1.0.0(putout@36.13.1(eslint@8.57.1)(typescript@5.6.3))': dependencies: From aa0ea17b138146af4eb118bf71bdc49d54d3a52e Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Mon, 3 Feb 2025 15:33:55 -0300 Subject: [PATCH 4/6] some adjusts --- .../vapi/actions/create-call/create-call.mjs | 2 +- .../update-assistant-settings.mjs | 32 ++++++++++--------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/components/vapi/actions/create-call/create-call.mjs b/components/vapi/actions/create-call/create-call.mjs index bf30ca113b5c2..b229065b781e4 100644 --- a/components/vapi/actions/create-call/create-call.mjs +++ b/components/vapi/actions/create-call/create-call.mjs @@ -44,7 +44,7 @@ export default { }, async run({ $ }) { if (!this.assistantId && !this.squadId) { - throw new ConfigurationError("Need Either `Assistant Id` Or `Squad Id`"); + throw new ConfigurationError("Specify either `Assistant Id` or `Squad Id`"); } const response = await this.vapi.startConversation({ diff --git a/components/vapi/actions/update-assistant-settings/update-assistant-settings.mjs b/components/vapi/actions/update-assistant-settings/update-assistant-settings.mjs index e9e352fdeab83..fd41e471c477b 100644 --- a/components/vapi/actions/update-assistant-settings/update-assistant-settings.mjs +++ b/components/vapi/actions/update-assistant-settings/update-assistant-settings.mjs @@ -27,19 +27,19 @@ export default { transcriber: { type: "object", label: "Transcriber", - description: "A formated JSON object for the assistant's transcriber. **Example: { \"provider\": \"talkscriber\", \"language\": \"en\", \"model\": \"whisper\" }**. [See the documentation](https://docs.vapi.ai/api-reference/assistants/update) for futher details", + description: "A formatted JSON object for the assistant's transcriber. **Example: { \"provider\": \"talkscriber\", \"language\": \"en\", \"model\": \"whisper\" }**. [See the documentation](https://docs.vapi.ai/api-reference/assistants/update) for further details", optional: true, }, model: { type: "object", label: "Model", - description: "A formated JSON object for the assistant's LLM. **Example: {\"provider\": \"xai\", \"model\": \"grok-beta\", \"emotionRecognitionEnabled\": true, \"knowledgeBase\": {\"server\": {\"url\": \"url\", \"timeoutSeconds\": 20}}, \"knowledgeBaseId\": \"model\", \"maxTokens\": 1.1, \"messages\": [{\"role\": \"assistant\"}], \"numFastTurns\": 1.1, \"temperature\": 1.1, \"toolIds\": [\"model\"], \"tools\": [{\"type\": \"transferCall\", \"async\": false}]}**. [See the documentation](https://docs.vapi.ai/api-reference/assistants/update) for futher details", + description: "A formatted JSON object for the assistant's LLM. **Example: {\"provider\": \"xai\", \"model\": \"grok-beta\", \"emotionRecognitionEnabled\": true, \"knowledgeBase\": {\"server\": {\"url\": \"url\", \"timeoutSeconds\": 20}}, \"knowledgeBaseId\": \"model\", \"maxTokens\": 1.1, \"messages\": [{\"role\": \"assistant\"}], \"numFastTurns\": 1.1, \"temperature\": 1.1, \"toolIds\": [\"model\"], \"tools\": [{\"type\": \"transferCall\", \"async\": false}]}**. [See the documentation](https://docs.vapi.ai/api-reference/assistants/update) for further details", optional: true, }, voice: { type: "object", label: "Voice", - description: "A formated JSON object for the assistant's voice. **Example: {\"provider\":\"tavus\",\"voiceId\":\"r52da2535a\",\"callbackUrl\":\"voice\",\"chunkPlan\":{\"enabled\":true,\"minCharacters\":30,\"punctuationBoundaries\":[\"。\",\",\",\".\",\"!\",\"?\",\";\",\"،\",\"۔\",\"।\",\"॥\",\"|\",\"||\",\",\",\":\"],\"formatPlan\":{\"enabled\":true,\"numberToDigitsCutoff\":2025}},\"conversationName\":\"voice\",\"conversationalContext\":\"voice\",\"customGreeting\":\"voice\",\"fallbackPlan\":{\"voices\":[{\"provider\":\"tavus\",\"voiceId\":\"r52da2535a\"}]},\"personaId\":\"voice\",\"properties\":{\"maxCallDuration\":1.1,\"participantLeftTimeout\":1.1,\"participantAbsentTimeout\":1.1,\"enableRecording\":true,\"enableTranscription\":true,\"applyGreenscreen\":true,\"language\":\"language\",\"recordingS3BucketName\":\"recordingS3BucketName\",\"recordingS3BucketRegion\":\"recordingS3BucketRegion\",\"awsAssumeRoleArn\":\"awsAssumeRoleArn\"}}**. [See the documentation](https://docs.vapi.ai/api-reference/assistants/update) for futher details", + description: "A formatted JSON object for the assistant's voice. **Example: {\"provider\":\"tavus\",\"voiceId\":\"r52da2535a\",\"callbackUrl\":\"voice\",\"chunkPlan\":{\"enabled\":true,\"minCharacters\":30,\"punctuationBoundaries\":[\"。\",\",\",\".\",\"!\",\"?\",\";\",\"،\",\",\",\"।\",\"॥\",\"|\",\"||\",\",\",\":\"],\"formatPlan\":{\"enabled\":true,\"numberToDigitsCutoff\":2025}},\"conversationName\":\"voice\",\"conversationalContext\":\"voice\",\"customGreeting\":\"voice\",\"fallbackPlan\":{\"voices\":[{\"provider\":\"tavus\",\"voiceId\":\"r52da2535a\"}]},\"personaId\":\"voice\",\"properties\":{\"maxCallDuration\":1.1,\"participantLeftTimeout\":1.1,\"participantAbsentTimeout\":1.1,\"enableRecording\":true,\"enableTranscription\":true,\"applyGreenscreen\":true,\"language\":\"language\",\"recordingS3BucketName\":\"recordingS3BucketName\",\"recordingS3BucketRegion\":\"recordingS3BucketRegion\",\"awsAssumeRoleArn\":\"awsAssumeRoleArn\"}}**. [See the documentation](https://docs.vapi.ai/api-reference/assistants/update) for further details", optional: true, }, firstMessage: { @@ -78,16 +78,18 @@ export default { silenceTimeoutSeconds: { type: "integer", label: "Silence Timeout Seconds", - description: "How many seconds of silence to wait before ending the call. Defaults to 30", + description: "How many seconds of silence to wait before ending the call.", optional: true, + default: 30, min: 10, max: 3600, }, maxDurationSeconds: { type: "integer", label: "Max Duration Seconds", - description: "This is the maximum number of seconds that the call will last. When the call reaches this duration, it will be ended. Defaults to 600 (10 minutes)", + description: "This is the maximum number of seconds that the call will last. When the call reaches this duration, it will be ended.", optional: true, + default: 600, min: 10, max: 43200, }, @@ -113,13 +115,13 @@ export default { transportConfigurations: { type: "string[]", label: "Transport Configurations", - description: "These are the configurations to be passed to the transport providers of assistant's calls, like Twilio. You can store multiple configurations for different transport providers. For a call, only the configuration matching the call transport provider is used. **Example: [{\"provider\":\"twilio\",\"timeout\":60,\"record\":false,\"recordingChannels\":\"mono\"}]**. [See the documentation](https://docs.vapi.ai/api-reference/assistants/update) for futher details", + description: "These are the configurations to be passed to the transport providers of assistant's calls, like Twilio. You can store multiple configurations for different transport providers. For a call, only the configuration matching the call transport provider is used. **Example: [{\"provider\":\"twilio\",\"timeout\":60,\"record\":false,\"recordingChannels\":\"mono\"}]**. [See the documentation](https://docs.vapi.ai/api-reference/assistants/update) for further details", optional: true, }, credentials: { type: "string[]", label: "Credentials", - description: "These are dynamic credentials that will be used for the assistant calls. By default, all the credentials are available for use in the call but you can supplement an additional credentials using this. Dynamic credentials override existing credentials. **Example: [{\"provider\":\"xai\",\"apiKey\":\"credentials\",\"name\":\"credentials\"}]**. [See the documentation](https://docs.vapi.ai/api-reference/assistants/update) for futher details", + description: "These are dynamic credentials that will be used for the assistant calls. By default, all the credentials are available for use in the call but you can supplement an additional credentials using this. Dynamic credentials override existing credentials. **Example: [{\"provider\":\"xai\",\"apiKey\":\"credentials\",\"name\":\"credentials\"}]**. [See the documentation](https://docs.vapi.ai/api-reference/assistants/update) for further details", optional: true, }, name: { @@ -131,7 +133,7 @@ export default { voicemailDetection: { type: "object", label: "Voicemail Detection", - description: "These are the settings to configure or disable voicemail detection. Alternatively, voicemail detection can be configured using the model.tools=[VoicemailTool]. This uses Twilio's built-in detection while the VoicemailTool relies on the model to detect if a voicemail was reached. You can use neither of them, one of them, or both of them. By default, Twilio built-in detection is enabled while VoicemailTool is not. **Example: {\"provider\":\"twilio\",\"voicemailDetectionTypes\":[\"machine_end_beep\",\"machine_end_silence\"],\"enabled\":true,\"machineDetectionTimeout\":1.1,\"machineDetectionSpeechThreshold\":1.1,\"machineDetectionSpeechEndThreshold\":1.1,\"machineDetectionSilenceTimeout\":1.1}**. [See the documentation](https://docs.vapi.ai/api-reference/assistants/update) for futher details", + description: "These are the settings to configure or disable voicemail detection. Alternatively, voicemail detection can be configured using the model.tools=[VoicemailTool]. This uses Twilio's built-in detection while the VoicemailTool relies on the model to detect if a voicemail was reached. You can use neither of them, one of them, or both of them. By default, Twilio built-in detection is enabled while VoicemailTool is not. **Example: {\"provider\":\"twilio\",\"voicemailDetectionTypes\":[\"machine_end_beep\",\"machine_end_silence\"],\"enabled\":true,\"machineDetectionTimeout\":1.1,\"machineDetectionSpeechThreshold\":1.1,\"machineDetectionSpeechEndThreshold\":1.1,\"machineDetectionSilenceTimeout\":1.1}**. [See the documentation](https://docs.vapi.ai/api-reference/assistants/update) for further details", optional: true, }, voicemailMessage: { @@ -161,37 +163,37 @@ export default { analysisPlan: { type: "object", label: "Analysis Plan", - description: "This is the plan for analysis of assistant's calls. Stored in `call.analysis`. **Example: {\"summaryPlan\":{\"messages\":[{\"key\":\"value\"}],\"enabled\":true,\"timeoutSeconds\":1.1},\"structuredDataPlan\":{\"messages\":[{\"key\":\"value\"}],\"enabled\":true,\"schema\":{\"type\":\"string\"},\"timeoutSeconds\":1.1},\"successEvaluationPlan\":{\"rubric\":\"NumericScale\",\"messages\":[{\"key\":\"value\"}],\"enabled\":true,\"timeoutSeconds\":1.1}}**. [See the documentation](https://docs.vapi.ai/api-reference/assistants/update) for futher details", + description: "This is the plan for analysis of assistant's calls. Stored in `call.analysis`. **Example: {\"summaryPlan\":{\"messages\":[{\"key\":\"value\"}],\"enabled\":true,\"timeoutSeconds\":1.1},\"structuredDataPlan\":{\"messages\":[{\"key\":\"value\"}],\"enabled\":true,\"schema\":{\"type\":\"string\"},\"timeoutSeconds\":1.1},\"successEvaluationPlan\":{\"rubric\":\"NumericScale\",\"messages\":[{\"key\":\"value\"}],\"enabled\":true,\"timeoutSeconds\":1.1}}**. [See the documentation](https://docs.vapi.ai/api-reference/assistants/update) for further details", optional: true, }, artifactPlan: { type: "object", label: "Artifact Plan", - description: "This is the plan for artifacts generated during assistant's calls. Stored in call.artifact. **Note:** `recordingEnabled` is currently at the root level. It will be moved to `artifactPlan` in the future, but will remain backwards compatible. **Example: {\"recordingEnabled\":true,\"videoRecordingEnabled\":false,\"transcriptPlan\":{\"enabled\":true,\"assistantName\":\"assistantName\",\"userName\":\"userName\"},\"recordingPath\":\"recordingPath\"}**. [See the documentation](https://docs.vapi.ai/api-reference/assistants/update) for futher details", + description: "This is the plan for artifacts generated during assistant's calls. Stored in call.artifact. **Note:** `recordingEnabled` is currently at the root level. It will be moved to `artifactPlan` in the future, but will remain backwards compatible. **Example: {\"recordingEnabled\":true,\"videoRecordingEnabled\":false,\"transcriptPlan\":{\"enabled\":true,\"assistantName\":\"assistantName\",\"userName\":\"userName\"},\"recordingPath\":\"recordingPath\"}**. [See the documentation](https://docs.vapi.ai/api-reference/assistants/update) for further details", optional: true, }, messagePlan: { type: "object", label: "Message Plan", - description: "This is the plan for static predefined messages that can be spoken by the assistant during the call, like idleMessages. **Note:** `firstMessage`, `voicemailMessage`, and `endCallMessage` are currently at the root level. They will be moved to ` ` in the future, but will remain backwards compatible. **Example: {\"idleMessages\":[\"idleMessages\"],\"idleMessageMaxSpokenCount\":1.1,\"idleTimeoutSeconds\":1.1}**. [See the documentation](https://docs.vapi.ai/api-reference/assistants/update) for futher details", + description: "This is the plan for static predefined messages that can be spoken by the assistant during the call, like idleMessages. **Note:** `firstMessage`, `voicemailMessage`, and `endCallMessage` are currently at the root level. They will be moved to `messagePlan` in the future, but will remain backwards compatible. **Example: {\"idleMessages\":[\"idleMessages\"],\"idleMessageMaxSpokenCount\":1.1,\"idleTimeoutSeconds\":1.1}**. [See the documentation](https://docs.vapi.ai/api-reference/assistants/update) for further details", optional: true, }, startSpeakingPlan: { type: "object", label: "Start Speaking Plan", - description: "This is the plan for when the assistant should start talking. **Example: {\"waitSeconds\":0.4,\"smartEndpointingEnabled\":false,\"customEndpointingRules\":[{\"type\":\"both\",\"assistantRegex\":\"customEndpointingRules\",\"customerRegex\":\"customEndpointingRules\",\"timeoutSeconds\":1.1}],\"transcriptionEndpointingPlan\":{\"onPunctuationSeconds\":0.1,\"onNoPunctuationSeconds\":1.5,\"onNumberSeconds\":0.5}}**. [See the documentation](https://docs.vapi.ai/api-reference/assistants/update) for futher details", + description: "This is the plan for when the assistant should start talking. **Example: {\"waitSeconds\":0.4,\"smartEndpointingEnabled\":false,\"customEndpointingRules\":[{\"type\":\"both\",\"assistantRegex\":\"customEndpointingRules\",\"customerRegex\":\"customEndpointingRules\",\"timeoutSeconds\":1.1}],\"transcriptionEndpointingPlan\":{\"onPunctuationSeconds\":0.1,\"onNoPunctuationSeconds\":1.5,\"onNumberSeconds\":0.5}}**. [See the documentation](https://docs.vapi.ai/api-reference/assistants/update) for further details", optional: true, }, stopSpeakingPlan: { type: "object", label: "Stop Speaking Plan", - description: "This is the plan for when assistant should stop talking on customer interruption. **Example: {\"numWords\":0,\"voiceSeconds\":0.2,\"backoffSeconds\":1}**. [See the documentation](https://docs.vapi.ai/api-reference/assistants/update) for futher details", + description: "This is the plan for when assistant should stop talking on customer interruption. **Example: {\"numWords\":0,\"voiceSeconds\":0.2,\"backoffSeconds\":1}**. [See the documentation](https://docs.vapi.ai/api-reference/assistants/update) for further details", optional: true, }, monitorPlan: { type: "object", label: "Monitor Plan", - description: "This is the plan for real-time monitoring of the assistant's calls. **Note:** `serverMessages`, `clientMessages`, `serverUrl` and `serverUrlSecret` are currently at the root level but will be moved to `monitorPlan` in the future. Will remain backwards compatible. **Example: {\"listenEnabled\":false,\"controlEnabled\":false}**. [See the documentation](https://docs.vapi.ai/api-reference/assistants/update) for futher details", + description: "This is the plan for real-time monitoring of the assistant's calls. **Note:** `serverMessages`, `clientMessages`, `serverUrl` and `serverUrlSecret` are currently at the root level but will be moved to `monitorPlan` in the future. Will remain backwards compatible. **Example: {\"listenEnabled\":false,\"controlEnabled\":false}**. [See the documentation](https://docs.vapi.ai/api-reference/assistants/update) for further details", optional: true, }, credentialIds: { @@ -203,7 +205,7 @@ export default { server: { type: "object", label: "Server", - description: "This is where Vapi will send webhooks. You can find all webhooks available along with their shape in ServerMessage schema. **Example: {\"url\":\"url\",\"timeoutSeconds\":20,\"secret\":\"secret\",\"headers\":{\"key\":\"value\"}}**. [See the documentation](https://docs.vapi.ai/api-reference/assistants/update) for futher details", + description: "This is where Vapi will send webhooks. You can find all webhooks available along with their shape in ServerMessage schema. **Example: {\"url\":\"url\",\"timeoutSeconds\":20,\"secret\":\"secret\",\"headers\":{\"key\":\"value\"}}**. [See the documentation](https://docs.vapi.ai/api-reference/assistants/update) for further details", optional: true, }, }, From 848b8695e73ddad010462779283f915ebfd5ebe3 Mon Sep 17 00:00:00 2001 From: michelle0927 Date: Mon, 3 Feb 2025 14:54:29 -0500 Subject: [PATCH 5/6] add form-data dependency --- components/vapi/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/vapi/package.json b/components/vapi/package.json index e167d480403ab..ab186fdbc6448 100644 --- a/components/vapi/package.json +++ b/components/vapi/package.json @@ -13,6 +13,7 @@ "access": "public" }, "dependencies": { - "@pipedream/platform": "^3.0.3" + "@pipedream/platform": "^3.0.3", + "form-data": "^4.0.1" } } From 357283202e6479fba5e4446d482abbde3d460b78 Mon Sep 17 00:00:00 2001 From: michelle0927 Date: Mon, 3 Feb 2025 14:56:09 -0500 Subject: [PATCH 6/6] pnpm-lock.yaml --- pnpm-lock.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6442e1fd9e477..5b4a208d0116d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11456,6 +11456,9 @@ importers: '@pipedream/platform': specifier: ^3.0.3 version: 3.0.3 + form-data: + specifier: ^4.0.1 + version: 4.0.1 components/vbout: dependencies: