diff --git a/api/routes/public_agent.py b/api/routes/public_agent.py index 0763e9b..b911da0 100644 --- a/api/routes/public_agent.py +++ b/api/routes/public_agent.py @@ -14,7 +14,10 @@ from pydantic import BaseModel from api.db import db_client from api.enums import TriggerState from api.services.quota_service import check_dograh_quota_by_user_id -from api.services.telephony.factory import get_default_telephony_provider +from api.services.telephony.factory import ( + get_default_telephony_provider, + get_telephony_provider_by_id, +) from api.utils.common import get_backend_endpoints router = APIRouter(prefix="/public/agent") @@ -25,6 +28,7 @@ class TriggerCallRequest(BaseModel): phone_number: str initial_context: Optional[dict] = None + telephony_configuration_id: int | None = None class TriggerCallResponse(BaseModel): @@ -114,14 +118,38 @@ async def _initiate_call( detail="Trigger not found in the published Agent", ) - # 6. Get telephony provider for the organization (using its default config). - try: - provider = await get_default_telephony_provider(trigger.organization_id) - except ValueError: - raise HTTPException( - status_code=400, - detail="Telephony provider not configured for this organization", + # 6. Get telephony provider — either the caller-specified config (validated + # against the trigger's org) or the org's default config. + if request.telephony_configuration_id is not None: + cfg = await db_client.get_telephony_configuration_for_org( + request.telephony_configuration_id, trigger.organization_id ) + if not cfg: + raise HTTPException( + status_code=404, detail="Telephony configuration not found" + ) + try: + provider = await get_telephony_provider_by_id( + cfg.id, trigger.organization_id + ) + except ValueError: + raise HTTPException( + status_code=400, + detail="Telephony provider not configured for this configuration", + ) + resolved_cfg_id = cfg.id + else: + try: + provider = await get_default_telephony_provider(trigger.organization_id) + except ValueError: + raise HTTPException( + status_code=400, + detail="Telephony provider not configured for this organization", + ) + default_cfg = await db_client.get_default_telephony_configuration( + trigger.organization_id + ) + resolved_cfg_id = default_cfg.id if default_cfg else None # Validate provider is configured if not provider.validate_config(): @@ -130,10 +158,6 @@ async def _initiate_call( detail="Telephony provider not configured for this organization", ) - default_cfg = await db_client.get_default_telephony_configuration( - trigger.organization_id - ) - # 7. Determine the workflow run mode based on provider type workflow_run_mode = provider.PROVIDER_NAME @@ -149,7 +173,7 @@ async def _initiate_call( "phone_number": request.phone_number, "agent_uuid": uuid, "trigger_mode": "test" if use_draft else "production", - "telephony_configuration_id": default_cfg.id if default_cfg else None, + "telephony_configuration_id": resolved_cfg_id, **(request.initial_context or {}), }, user_id=api_key.created_by, diff --git a/api/services/looptalk/core/pipeline_builder.py b/api/services/looptalk/core/pipeline_builder.py index 9d08b93..ee11613 100644 --- a/api/services/looptalk/core/pipeline_builder.py +++ b/api/services/looptalk/core/pipeline_builder.py @@ -27,11 +27,6 @@ from pipecat.pipeline.pipeline import Pipeline from pipecat.processors.aggregators.llm_response_universal import ( LLMContextAggregatorPair, ) -from pipecat.processors.filters.stt_mute_filter import ( - STTMuteConfig, - STTMuteFilter, - STTMuteStrategy, -) class LoopTalkPipelineBuilder: @@ -126,13 +121,6 @@ class LoopTalkPipelineBuilder: context_aggregator = LLMContextAggregatorPair(context) - # Create STT mute filter - stt_mute_filter = STTMuteFilter( - config=STTMuteConfig( - strategies={STTMuteStrategy.FIRST_SPEECH}, - ) - ) - # Create pipeline engine callback processor pipeline_engine_callback_processor = PipelineEngineCallbacksProcessor( max_call_duration_seconds=300, @@ -152,7 +140,6 @@ class LoopTalkPipelineBuilder: [ transport.input(), audio_streamer, # Stream audio to connected clients - stt_mute_filter, stt, transcript.user(), user_context_aggregator, diff --git a/api/services/workflow/node_specs/trigger.py b/api/services/workflow/node_specs/trigger.py index 5d8d97e..5d9883d 100644 --- a/api/services/workflow/node_specs/trigger.py +++ b/api/services/workflow/node_specs/trigger.py @@ -22,7 +22,15 @@ SPEC = NodeSpec( " • Test: `/api/v1/public/agent/test/` — runs " "the latest draft, useful for verifying changes before publishing. " "Falls back to the published agent when no draft exists.\n" - "Both require an API key in the `X-API-Key` header." + "Both require an API key in the `X-API-Key` header.\n" + "Request body fields:\n" + " • `phone_number` (string, required) — destination to dial.\n" + " • `initial_context` (object, optional) — merged into the run's " + "initial context.\n" + " • `telephony_configuration_id` (int, optional) — pick a specific " + "telephony configuration for the call. Must belong to the same " + "organization as the trigger. When omitted, the org's default " + "outbound configuration is used." ), category=NodeCategory.trigger, icon="Webhook", diff --git a/docs/api-reference/calls.mdx b/docs/api-reference/calls.mdx index 8bed841..c7149c8 100644 --- a/docs/api-reference/calls.mdx +++ b/docs/api-reference/calls.mdx @@ -6,7 +6,6 @@ description: "Initiate outbound calls and trigger agents via the API" | Method | Endpoint | Quick Link | |---|---|---| | `POST` | `/public/agent/{uuid}` | [Trigger an outbound call](/api-reference/calls/trigger) | -| `POST` | `/telephony/initiate-call` | [Initiate a call (authenticated)](/api-reference/calls/initiate) | | `GET` | `/workflow/{workflow_id}/runs/{run_id}` | [Retrieve call details](/api-reference/calls/get-run) | | `GET` | `/public/download/workflow/{token}/{artifact_type}` | [Download recordings and transcripts](/api-reference/calls/download) | | `POST` | `/telephony/inbound/{workflow_id}` | [Inbound call webhook](/api-reference/calls/inbound) | diff --git a/docs/api-reference/calls/initiate.mdx b/docs/api-reference/calls/initiate.mdx deleted file mode 100644 index 1b0bab1..0000000 --- a/docs/api-reference/calls/initiate.mdx +++ /dev/null @@ -1,7 +0,0 @@ ---- -title: "Initiate a Call (Authenticated)" -description: "Start an outbound call with more control than the public endpoint" -openapi: "POST /api/v1/telephony/initiate-call" ---- - -Use this endpoint when you need to specify a `workflow_run_id` to resume context from a previous run, or when you want to use the workflow's integer ID instead of its public UUID. diff --git a/docs/api-reference/calls/trigger.mdx b/docs/api-reference/calls/trigger.mdx index 5d52973..1d37e15 100644 --- a/docs/api-reference/calls/trigger.mdx +++ b/docs/api-reference/calls/trigger.mdx @@ -4,12 +4,14 @@ description: "Initiate an outbound call using an agent's public UUID" openapi: "POST /api/v1/public/agent/{uuid}" --- -The simplest way to initiate a call programmatically. The `uuid` is visible in the dashboard URL when viewing an agent. +The simplest way to initiate a call programmatically. The `uuid` comes from the [API Trigger node](/voice-agent/api-trigger) in your agent — add the node to your workflow and copy its auto-generated `trigger_path`. Use `workflow_run_id` from the response to later [retrieve call details](/api-reference/calls/get-run), recordings, and transcripts. Pass `initial_context` to inject runtime data as template variables into the agent's prompt. See [Using initial context](/api-reference/calls#using-initial-context). +Pass `telephony_configuration_id` to route the call through a specific telephony configuration instead of your organization's default. The id is shown on each row in **Telephony configurations** (`https://app.dograh.com/telephony-configurations` for hosted or `http://localhost:3010/telephony-configurations` for local). + Your telephony provider must be configured before outbound calls will connect. See [Telephony](/integrations/telephony/overview) for setup instructions. diff --git a/docs/docs.json b/docs/docs.json index 5439c02..085bdcd 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -202,7 +202,6 @@ "pages": [ "api-reference/calls", "api-reference/calls/trigger", - "api-reference/calls/initiate", "api-reference/calls/get-run", "api-reference/calls/download", "api-reference/calls/inbound" diff --git a/docs/voice-agent/api-trigger.mdx b/docs/voice-agent/api-trigger.mdx index ab84a31..df2e590 100644 --- a/docs/voice-agent/api-trigger.mdx +++ b/docs/voice-agent/api-trigger.mdx @@ -36,7 +36,7 @@ The request body, headers, and response shape are identical for both URLs. ## Making a request -Authenticate by passing your API key in the `X-API-Key` header. The request body requires a `phone_number` and accepts an optional `initial_context` object. +Authenticate by passing your API key in the `X-API-Key` header. The request body requires a `phone_number` and accepts optional `initial_context` and `telephony_configuration_id` fields. ```bash curl -X POST https://your-dograh-instance/api/v1/public/agent/{uuid} \ @@ -92,6 +92,19 @@ You can reference the user's name in your prompt as `{{initial_context.user.name See [Context & Variables](/core-concepts/context-and-variables) for more on how data flows through a call. +## Choosing a telephony configuration + +By default, calls are placed through your organization's default outbound [telephony configuration](/integrations/telephony/overview). To route a specific call through a different configuration — for example, to dial out from a regional number — pass `telephony_configuration_id` in the request body. + +```json +{ + "phone_number": "+14155550100", + "telephony_configuration_id": 42 +} +``` + +The id is shown on each row in **Telephony configurations** (`https://app.dograh.com/telephony-configurations` for hosted or `http://localhost:3010/telephony-configurations` for local). The configuration must belong to the same organization as the API Trigger; otherwise the request returns `404`. + For full endpoint details including all parameters and response fields, see the [API reference](/api-reference/calls/trigger). \ No newline at end of file diff --git a/ui/src/app/telephony-configurations/[configId]/page.tsx b/ui/src/app/telephony-configurations/[configId]/page.tsx index c58b730..583916e 100644 --- a/ui/src/app/telephony-configurations/[configId]/page.tsx +++ b/ui/src/app/telephony-configurations/[configId]/page.tsx @@ -2,6 +2,7 @@ import { ArrowLeft, + Copy, ExternalLink, Pencil, Plus, @@ -212,6 +213,20 @@ export default function TelephonyConfigurationDetailPage() { Updated {new Date(config.updated_at).toLocaleString()} +
{!config.is_default_outbound && ( diff --git a/ui/src/app/telephony-configurations/page.tsx b/ui/src/app/telephony-configurations/page.tsx index af7357d..0534c8e 100644 --- a/ui/src/app/telephony-configurations/page.tsx +++ b/ui/src/app/telephony-configurations/page.tsx @@ -2,6 +2,7 @@ import { ChevronRight, + Copy, ExternalLink, Pencil, Plus, @@ -198,6 +199,22 @@ export default function TelephonyConfigurationsPage() { {item.phone_number_count} phone{" "} {item.phone_number_count === 1 ? "number" : "numbers"} +
diff --git a/ui/src/client/types.gen.ts b/ui/src/client/types.gen.ts index 34d6466..6d3de9c 100644 --- a/ui/src/client/types.gen.ts +++ b/ui/src/client/types.gen.ts @@ -3714,6 +3714,10 @@ export type TriggerCallRequest = { initial_context?: { [key: string]: unknown; } | null; + /** + * Telephony Configuration Id + */ + telephony_configuration_id?: number | null; }; /** diff --git a/ui/src/components/telephony/ConfigFormDialog.tsx b/ui/src/components/telephony/ConfigFormDialog.tsx index 9ebdb83..d9ff6b9 100644 --- a/ui/src/components/telephony/ConfigFormDialog.tsx +++ b/ui/src/components/telephony/ConfigFormDialog.tsx @@ -1,6 +1,6 @@ "use client"; -import { ExternalLink } from "lucide-react"; +import { Copy, ExternalLink } from "lucide-react"; import { useEffect, useMemo, useState } from "react"; import { toast } from "sonner"; @@ -172,6 +172,26 @@ export function ConfigFormDialog({
+ {isEdit && existing && ( +
+ + +
+ )} +