From 83f05ab1466d7fa1825b30eb87a267aa1da9ff26 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Fri, 27 Mar 2026 00:07:38 +0530 Subject: [PATCH 1/4] fix: send auth credentials with validate service keys --- api/routes/user.py | 12 ++++++++-- api/routes/webrtc_signaling.py | 2 +- api/services/configuration/check_validity.py | 24 +++++++++++++++++-- api/services/mps_service_key_client.py | 11 ++++++--- api/services/quota_service.py | 10 ++++++++ .../telephony/providers/telnyx_provider.py | 7 +++--- .../[workflowId]/run/[runId]/BrowserCall.tsx | 11 +++++++-- .../[runId]/components/ApiKeyErrorDialog.tsx | 18 +++++++------- .../run/[runId]/hooks/useWebSocketRTC.tsx | 12 +++++++--- 9 files changed, 83 insertions(+), 24 deletions(-) diff --git a/api/routes/user.py b/api/routes/user.py index ffba4ca7..60eec6b9 100644 --- a/api/routes/user.py +++ b/api/routes/user.py @@ -125,7 +125,11 @@ async def update_user_configurations( try: validator = UserConfigurationValidator() - await validator.validate(user_configurations) + await validator.validate( + user_configurations, + organization_id=user.selected_organization_id, + created_by=user.provider_id, + ) except ValueError as e: raise HTTPException(status_code=422, detail=e.args[0]) @@ -163,7 +167,11 @@ async def validate_user_configurations( ): validator = UserConfigurationValidator() try: - status = await validator.validate(configurations) + status = await validator.validate( + configurations, + organization_id=user.selected_organization_id, + created_by=user.provider_id, + ) await db_client.update_user_configuration_last_validated_at(user.id) return status except ValueError as e: diff --git a/api/routes/webrtc_signaling.py b/api/routes/webrtc_signaling.py index bb5372e3..0be108aa 100644 --- a/api/routes/webrtc_signaling.py +++ b/api/routes/webrtc_signaling.py @@ -228,7 +228,7 @@ class SignalingManager: { "type": "error", "payload": { - "error_type": "quota_exceeded", + "error_type": quota_result.error_code, "message": quota_result.error_message, }, } diff --git a/api/services/configuration/check_validity.py b/api/services/configuration/check_validity.py index 399ec281..fc9035d1 100644 --- a/api/services/configuration/check_validity.py +++ b/api/services/configuration/check_validity.py @@ -14,6 +14,12 @@ from api.schemas.user_configuration import ( from api.services.configuration.registry import ServiceConfig, ServiceProviders from api.services.mps_service_key_client import mps_service_key_client +AuthContext = TypedDict( + "AuthContext", + {"organization_id": Optional[int], "created_by": Optional[str]}, + total=False, +) + class APIKeyStatus(TypedDict): model: str @@ -43,7 +49,16 @@ class UserConfigurationValidator: ServiceProviders.SELF_HOSTED.value: self._check_self_hosted_api_key, } - async def validate(self, configuration: UserConfiguration) -> APIKeyStatusResponse: + async def validate( + self, + configuration: UserConfiguration, + organization_id: Optional[int] = None, + created_by: Optional[str] = None, + ) -> APIKeyStatusResponse: + self._auth_context: AuthContext = { + "organization_id": organization_id, + "created_by": created_by, + } status_list = [] status_list.extend(self._validate_service(configuration.llm, "llm")) @@ -165,7 +180,12 @@ class UserConfigurationValidator: "You provided a Dograh API key (dgr...) instead of a service key. " "Please use a service key (mps...)." ) - return mps_service_key_client.validate_service_key(api_key) + auth = getattr(self, "_auth_context", {}) + return mps_service_key_client.validate_service_key( + api_key, + organization_id=auth.get("organization_id"), + created_by=auth.get("created_by"), + ) def _check_sarvam_api_key(self, model: str, api_key: str) -> bool: return True diff --git a/api/services/mps_service_key_client.py b/api/services/mps_service_key_client.py index 62452ba9..17fa19ad 100644 --- a/api/services/mps_service_key_client.py +++ b/api/services/mps_service_key_client.py @@ -276,7 +276,7 @@ class MPSServiceKeyClient: "remaining_credits": data.get("remaining_credits", 0.0), } else: - logger.error( + logger.warning( f"Failed to check service key usage: {response.status_code} - {response.text}" ) raise httpx.HTTPStatusError( @@ -416,7 +416,12 @@ class MPSServiceKeyClient: response=response, ) - def validate_service_key(self, service_key: str) -> bool: + def validate_service_key( + self, + service_key: str, + organization_id: Optional[int] = None, + created_by: Optional[str] = None, + ) -> bool: """ Synchronously validate a Dograh service key by checking usage via MPS. @@ -427,7 +432,7 @@ class MPSServiceKeyClient: response = client.post( f"{self.base_url}/api/v1/service-keys/usage", json={"service_key": service_key}, - headers=self._get_headers(), + headers=self._get_headers(organization_id, created_by), ) return response.status_code == 200 except Exception: diff --git a/api/services/quota_service.py b/api/services/quota_service.py index 397ff79c..6a9b4c48 100644 --- a/api/services/quota_service.py +++ b/api/services/quota_service.py @@ -20,6 +20,7 @@ class QuotaCheckResult: has_quota: bool error_message: str = "" + error_code: str = "" async def check_dograh_quota(user: UserModel) -> QuotaCheckResult: @@ -76,6 +77,7 @@ async def check_dograh_quota(user: UserModel) -> QuotaCheckResult: ) return QuotaCheckResult( has_quota=False, + error_code="quota_exceeded", error_message=( "You have exhausted your trial credits. " "Please email founders@dograh.com for additional Dograh credits " @@ -89,8 +91,16 @@ async def check_dograh_quota(user: UserModel) -> QuotaCheckResult: ) except Exception as e: logger.error(f"Failed to check quota for Dograh key: {str(e)}") + error_str = str(e) + if "404" in error_str or "not found" in error_str.lower(): + return QuotaCheckResult( + has_quota=False, + error_code="invalid_service_key", + error_message="You have invalid keys in your model configuration. Please validate the service keys.", + ) return QuotaCheckResult( has_quota=False, + error_code="quota_check_failed", error_message="Could not verify Dograh credits. Please try again.", ) diff --git a/api/services/telephony/providers/telnyx_provider.py b/api/services/telephony/providers/telnyx_provider.py index 44aefed8..9b33cac4 100644 --- a/api/services/telephony/providers/telnyx_provider.py +++ b/api/services/telephony/providers/telnyx_provider.py @@ -6,9 +6,12 @@ inline WebSocket media streaming. import json import random -from typing import Any, Dict, List, Optional, TYPE_CHECKING +from typing import TYPE_CHECKING, Any, Dict, List, Optional import aiohttp +from fastapi import HTTPException +from loguru import logger + from api.enums import WorkflowRunMode from api.services.telephony.base import ( CallInitiationResult, @@ -16,8 +19,6 @@ from api.services.telephony.base import ( TelephonyProvider, ) from api.utils.common import get_backend_endpoints -from fastapi import HTTPException -from loguru import logger if TYPE_CHECKING: from fastapi import WebSocket diff --git a/ui/src/app/workflow/[workflowId]/run/[runId]/BrowserCall.tsx b/ui/src/app/workflow/[workflowId]/run/[runId]/BrowserCall.tsx index eae1ea64..fb1eb489 100644 --- a/ui/src/app/workflow/[workflowId]/run/[runId]/BrowserCall.tsx +++ b/ui/src/app/workflow/[workflowId]/run/[runId]/BrowserCall.tsx @@ -43,6 +43,7 @@ const BrowserCall = ({ workflowId, workflowRunId, initialContextVariables }: { apiKeyModalOpen, setApiKeyModalOpen, apiKeyError, + apiKeyErrorCode, workflowConfigError, workflowConfigModalOpen, setWorkflowConfigModalOpen, @@ -91,10 +92,14 @@ const BrowserCall = ({ workflowId, workflowRunId, initialContextVariables }: { }; }, [isCompleted, auth.isAuthenticated, workflowId, workflowRunId]); - const navigateToApiKeys = () => { + const navigateToCredits = () => { router.push('/api-keys'); }; + const navigateToModelConfig = () => { + router.push('/model-configurations'); + }; + const navigateToWorkflow = () => { router.push(`/workflow/${workflowId}`) } @@ -161,7 +166,9 @@ const BrowserCall = ({ workflowId, workflowRunId, initialContextVariables }: { open={apiKeyModalOpen} onOpenChange={setApiKeyModalOpen} error={apiKeyError} - onNavigateToApiKeys={navigateToApiKeys} + errorCode={apiKeyErrorCode} + onNavigateToCredits={navigateToCredits} + onNavigateToModelConfig={navigateToModelConfig} /> void; error: string | null; - onNavigateToApiKeys: () => void; + errorCode: string | null; + onNavigateToCredits: () => void; + onNavigateToModelConfig: () => void; } export const ApiKeyErrorDialog = ({ open, onOpenChange, error, - onNavigateToApiKeys + errorCode, + onNavigateToCredits, + onNavigateToModelConfig, }: ApiKeyErrorDialogProps) => { - // Check if this is a quota error based on the error message - const isQuotaError = error?.toLowerCase().includes('insufficient') || - error?.toLowerCase().includes('credits') || - error?.toLowerCase().includes('quota'); + const isQuotaError = errorCode === 'quota_exceeded'; const title = isQuotaError ? "Insufficient Credits" : "API Configuration Error"; const icon = isQuotaError ? : ; - const buttonText = isQuotaError ? "Add Credits" : "Go to API Keys Settings"; + const buttonText = isQuotaError ? "Add Credits" : "Go to Model Configurations"; + const onNavigate = isQuotaError ? onNavigateToCredits : onNavigateToModelConfig; return ( @@ -51,7 +53,7 @@ export const ApiKeyErrorDialog = ({ - diff --git a/ui/src/app/workflow/[workflowId]/run/[runId]/hooks/useWebSocketRTC.tsx b/ui/src/app/workflow/[workflowId]/run/[runId]/hooks/useWebSocketRTC.tsx index a2b4733c..908cbb9d 100644 --- a/ui/src/app/workflow/[workflowId]/run/[runId]/hooks/useWebSocketRTC.tsx +++ b/ui/src/app/workflow/[workflowId]/run/[runId]/hooks/useWebSocketRTC.tsx @@ -42,6 +42,7 @@ export const useWebSocketRTC = ({ workflowId, workflowRunId, accessToken, initia const [isCompleted, setIsCompleted] = useState(false); const [apiKeyModalOpen, setApiKeyModalOpen] = useState(false); const [apiKeyError, setApiKeyError] = useState(null); + const [apiKeyErrorCode, setApiKeyErrorCode] = useState(null); const [workflowConfigModalOpen, setWorkflowConfigModalOpen] = useState(false); const [workflowConfigError, setWorkflowConfigError] = useState(null); const [isStarting, setIsStarting] = useState(false); @@ -264,12 +265,15 @@ export const useWebSocketRTC = ({ workflowId, workflowRunId, accessToken, initia break; case 'error': - // Check if this is a quota exceeded error - if (message.payload?.error_type === 'quota_exceeded') { + // Check if this is a quota/service key error + if (message.payload?.error_type === 'quota_exceeded' || + message.payload?.error_type === 'invalid_service_key' || + message.payload?.error_type === 'quota_check_failed') { // Log as info since it's a handled business logic case - logger.info('Quota exceeded, showing user dialog:', message.payload.message); + logger.info('Quota/service key error, showing user dialog:', message.payload.message); // Set error state for display + setApiKeyErrorCode(message.payload.error_type); setApiKeyError(message.payload.message || 'Service quota exceeded'); setApiKeyModalOpen(true); @@ -545,6 +549,7 @@ export const useWebSocketRTC = ({ workflowId, workflowRunId, accessToken, initia if (response.error) { setApiKeyModalOpen(true); + setApiKeyErrorCode('invalid_api_key'); let msg = 'API Key Error'; const detail = (response.error as unknown as { detail?: { errors: { model: string; message: string }[] } }).detail; if (Array.isArray(detail)) { @@ -685,6 +690,7 @@ export const useWebSocketRTC = ({ workflowId, workflowRunId, accessToken, initia apiKeyModalOpen, setApiKeyModalOpen, apiKeyError, + apiKeyErrorCode, workflowConfigError, workflowConfigModalOpen, setWorkflowConfigModalOpen, From 1c333d4a64cebd312e94e0794ffc6e1e2d474f2a Mon Sep 17 00:00:00 2001 From: Sabiha Khan <87858386+chewwbaka@users.noreply.github.com> Date: Fri, 27 Mar 2026 00:10:25 +0530 Subject: [PATCH 2/4] chore(main): release dograh 1.19.2 (#211) --- .release-please-manifest.json | 2 +- CHANGELOG.md | 7 +++++++ api/pyproject.toml | 2 +- ui/package.json | 2 +- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 4a0860ea..17ee2187 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "1.19.1" + ".": "1.19.2" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 0286df43..14fa0ae1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [1.19.2](https://github.com/dograh-hq/dograh/compare/dograh-v1.19.1...dograh-v1.19.2) (2026-03-26) + + +### Bug Fixes + +* send auth credentials with validate service keys ([83f05ab](https://github.com/dograh-hq/dograh/commit/83f05ab1466d7fa1825b30eb87a267aa1da9ff26)) + ## [1.19.1](https://github.com/dograh-hq/dograh/compare/dograh-v1.19.0...dograh-v1.19.1) (2026-03-26) diff --git a/api/pyproject.toml b/api/pyproject.toml index ca1dab93..0a9c3141 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -1,5 +1,5 @@ [project] name = "dograh-api" -version = "1.19.1" +version = "1.19.2" description = "Backend API for Dograh voice AI platform" requires-python = ">=3.12" diff --git a/ui/package.json b/ui/package.json index 6ba1184d..48a32735 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,6 +1,6 @@ { "name": "ui", - "version": "1.19.1", + "version": "1.19.2", "private": true, "scripts": { "dev": "NODE_OPTIONS='--enable-source-maps' next dev --turbopack", From 0b5fd107fabd7627a3506e43ebb28feb1655ac3a Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Fri, 27 Mar 2026 17:31:17 +0530 Subject: [PATCH 3/4] feat: date range in download report --- api/db/campaign_client.py | 27 +++-- api/routes/campaign.py | 10 +- api/services/campaign/report.py | 13 ++- ui/src/app/campaigns/[campaignId]/page.tsx | 121 ++++++++++++++++++++- ui/src/client/sdk.gen.ts | 3 +- ui/src/client/types.gen.ts | 11 +- 6 files changed, 166 insertions(+), 19 deletions(-) diff --git a/api/db/campaign_client.py b/api/db/campaign_client.py index d7cb9784..861f98c2 100644 --- a/api/db/campaign_client.py +++ b/api/db/campaign_client.py @@ -365,12 +365,29 @@ class CampaignClient(BaseDBClient): result = await session.execute(query) return list(result.scalars().all()) - async def get_completed_runs_for_report(self, campaign_id: int) -> list: + async def get_completed_runs_for_report( + self, + campaign_id: int, + start_date: Optional[datetime] = None, + end_date: Optional[datetime] = None, + ) -> list: """Get completed workflow runs for campaign report CSV. Returns rows with only the columns needed for report generation. """ async with self.async_session() as session: + conditions = [ + WorkflowRunModel.campaign_id == campaign_id, + WorkflowRunModel.is_completed.is_(True), + WorkflowRunModel.cost_info["call_duration_seconds"] + .as_string() + .isnot(None), + ] + if start_date is not None: + conditions.append(WorkflowRunModel.created_at >= start_date) + if end_date is not None: + conditions.append(WorkflowRunModel.created_at <= end_date) + query = ( select( WorkflowRunModel.id, @@ -381,13 +398,7 @@ class CampaignClient(BaseDBClient): WorkflowRunModel.logs, WorkflowRunModel.public_access_token, ) - .where( - WorkflowRunModel.campaign_id == campaign_id, - WorkflowRunModel.is_completed.is_(True), - WorkflowRunModel.cost_info["call_duration_seconds"] - .as_string() - .isnot(None), - ) + .where(*conditions) .order_by(WorkflowRunModel.created_at.desc()) ) result = await session.execute(query) diff --git a/api/routes/campaign.py b/api/routes/campaign.py index 722223b4..070503cd 100644 --- a/api/routes/campaign.py +++ b/api/routes/campaign.py @@ -706,13 +706,21 @@ async def get_campaign_source_download_url( async def download_campaign_report( campaign_id: int, user: UserModel = Depends(get_user), + start_date: Optional[datetime] = Query( + None, description="Filter runs created on or after this datetime (ISO 8601)" + ), + end_date: Optional[datetime] = Query( + None, description="Filter runs created on or before this datetime (ISO 8601)" + ), ) -> StreamingResponse: """Download a CSV report of completed campaign runs.""" campaign = await db_client.get_campaign(campaign_id, user.selected_organization_id) if not campaign: raise HTTPException(status_code=404, detail="Campaign not found") - output, filename = await generate_campaign_report_csv(campaign_id) + output, filename = await generate_campaign_report_csv( + campaign_id, start_date=start_date, end_date=end_date + ) return StreamingResponse( output, diff --git a/api/services/campaign/report.py b/api/services/campaign/report.py index e9e9a0cd..f351966a 100644 --- a/api/services/campaign/report.py +++ b/api/services/campaign/report.py @@ -1,6 +1,7 @@ import csv import io -from typing import Any, List +from datetime import datetime +from typing import Any, List, Optional from api.constants import BACKEND_API_ENDPOINT from api.db import db_client @@ -27,12 +28,18 @@ def _collect_extracted_variable_keys(runs: List[Any]) -> list[str]: return list(keys) -async def generate_campaign_report_csv(campaign_id: int) -> tuple[io.StringIO, str]: +async def generate_campaign_report_csv( + campaign_id: int, + start_date: Optional[datetime] = None, + end_date: Optional[datetime] = None, +) -> tuple[io.StringIO, str]: """Generate a CSV report for a campaign. Returns a tuple of (csv_output, filename). """ - runs = await db_client.get_completed_runs_for_report(campaign_id) + runs = await db_client.get_completed_runs_for_report( + campaign_id, start_date=start_date, end_date=end_date + ) # Collect dynamic extracted variable columns extracted_var_keys = _collect_extracted_variable_keys(runs) diff --git a/ui/src/app/campaigns/[campaignId]/page.tsx b/ui/src/app/campaigns/[campaignId]/page.tsx index d8a5544c..b2d50422 100644 --- a/ui/src/app/campaigns/[campaignId]/page.tsx +++ b/ui/src/app/campaigns/[campaignId]/page.tsx @@ -1,6 +1,7 @@ "use client"; -import { ArrowLeft, Check, Clock, Download, Pause, Pencil, Play, RefreshCw, X } from 'lucide-react'; +import { format } from 'date-fns'; +import { ArrowLeft, CalendarIcon, Check, Clock, Download, Pause, Pencil, Play, RefreshCw, X } from 'lucide-react'; import { useParams, useRouter, useSearchParams } from 'next/navigation'; import { useCallback, useEffect, useState } from 'react'; import { toast } from 'sonner'; @@ -16,7 +17,11 @@ import { import type { CampaignResponse } from '@/client/types.gen'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; +import { Calendar } from '@/components/ui/calendar'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; import { Separator } from '@/components/ui/separator'; import { CampaignRuns } from '@/components/workflow-runs'; import { useAuth } from '@/lib/auth'; @@ -43,6 +48,13 @@ export default function CampaignDetailPage() { const [isExecutingAction, setIsExecutingAction] = useState(false); const [isDownloadingReport, setIsDownloadingReport] = useState(false); + // Report date range state + const [reportStartDate, setReportStartDate] = useState(undefined); + const [reportStartTime, setReportStartTime] = useState('00:00'); + const [reportEndDate, setReportEndDate] = useState(undefined); + const [reportEndTime, setReportEndTime] = useState('23:59'); + const [isReportPopoverOpen, setIsReportPopoverOpen] = useState(false); + // Fetch campaign details const fetchCampaign = useCallback(async () => { if (!user) return; @@ -113,16 +125,33 @@ export default function CampaignDetailPage() { } }; + // Build ISO datetime string from date + time + const buildDateTime = (date: Date | undefined, time: string): string | undefined => { + if (!date) return undefined; + const [hours, minutes] = time.split(':').map(Number); + const combined = new Date(date); + combined.setHours(hours, minutes, 0, 0); + return combined.toISOString(); + }; + // Handle download report const handleDownloadReport = async () => { if (!user) return; setIsDownloadingReport(true); + setIsReportPopoverOpen(false); try { const accessToken = await getAccessToken(); + const startDate = buildDateTime(reportStartDate, reportStartTime); + const endDate = buildDateTime(reportEndDate, reportEndTime); + const response = await downloadCampaignReportApiV1CampaignCampaignIdReportGet({ path: { campaign_id: campaignId, }, + query: { + start_date: startDate, + end_date: endDate, + }, headers: { 'Authorization': `Bearer ${accessToken}`, }, @@ -150,6 +179,13 @@ export default function CampaignDetailPage() { } }; + const handleClearDateRange = () => { + setReportStartDate(undefined); + setReportStartTime('00:00'); + setReportEndDate(undefined); + setReportEndTime('23:59'); + }; + // Handle start campaign const handleStart = async () => { if (!user) return; @@ -368,10 +404,85 @@ export default function CampaignDetailPage() {
- + + + + + +
+
Filter by date range
+
+
+ +
+ + + + + + reportEndDate ? date > reportEndDate : false} + /> + + + setReportStartTime(e.target.value)} + className="w-[100px] h-8 text-xs" + /> +
+
+
+ +
+ + + + + + reportStartDate ? date < reportStartDate : false} + /> + + + setReportEndTime(e.target.value)} + className="w-[100px] h-8 text-xs" + /> +
+
+
+ +
+ + +
+
+
+
{renderActionButton()}
diff --git a/ui/src/client/sdk.gen.ts b/ui/src/client/sdk.gen.ts index c607a94a..8e07b243 100644 --- a/ui/src/client/sdk.gen.ts +++ b/ui/src/client/sdk.gen.ts @@ -3,7 +3,8 @@ import { type Client, formDataBodySerializer,type Options as ClientOptions, type TDataShape } from '@hey-api/client-fetch'; import { client as _heyApiClient } from './client.gen'; -import type { ArchiveApiKeyApiV1UserApiKeysApiKeyIdDeleteData, ArchiveApiKeyApiV1UserApiKeysApiKeyIdDeleteError, ArchiveApiKeyApiV1UserApiKeysApiKeyIdDeleteResponse, ArchiveServiceKeyApiV1UserServiceKeysServiceKeyIdDeleteData, ArchiveServiceKeyApiV1UserServiceKeysServiceKeyIdDeleteError, CompleteTransferFunctionCallApiV1TelephonyTransferResultTransferIdPostData, CompleteTransferFunctionCallApiV1TelephonyTransferResultTransferIdPostError, CreateApiKeyApiV1UserApiKeysPostData, CreateApiKeyApiV1UserApiKeysPostError, CreateApiKeyApiV1UserApiKeysPostResponse, CreateCampaignApiV1CampaignCreatePostData, CreateCampaignApiV1CampaignCreatePostError, CreateCampaignApiV1CampaignCreatePostResponse, CreateCredentialApiV1CredentialsPostData, CreateCredentialApiV1CredentialsPostError, CreateCredentialApiV1CredentialsPostResponse, CreateLoadTestApiV1LooptalkLoadTestsPostData, CreateLoadTestApiV1LooptalkLoadTestsPostError, CreateLoadTestApiV1LooptalkLoadTestsPostResponse, CreateOrUpdateEmbedTokenApiV1WorkflowWorkflowIdEmbedTokenPostData, CreateOrUpdateEmbedTokenApiV1WorkflowWorkflowIdEmbedTokenPostError, CreateOrUpdateEmbedTokenApiV1WorkflowWorkflowIdEmbedTokenPostResponse, CreateRecordingApiV1WorkflowRecordingsPostData, CreateRecordingApiV1WorkflowRecordingsPostError, CreateRecordingApiV1WorkflowRecordingsPostResponse, CreateServiceKeyApiV1UserServiceKeysPostData, CreateServiceKeyApiV1UserServiceKeysPostError, CreateServiceKeyApiV1UserServiceKeysPostResponse, CreateSessionApiV1IntegrationSessionPostData, CreateSessionApiV1IntegrationSessionPostError, CreateSessionApiV1IntegrationSessionPostResponse, CreateTestSessionApiV1LooptalkTestSessionsPostData, CreateTestSessionApiV1LooptalkTestSessionsPostError, CreateTestSessionApiV1LooptalkTestSessionsPostResponse, CreateToolApiV1ToolsPostData, CreateToolApiV1ToolsPostError, CreateToolApiV1ToolsPostResponse, CreateWorkflowApiV1WorkflowCreateDefinitionPostData, CreateWorkflowApiV1WorkflowCreateDefinitionPostError, CreateWorkflowApiV1WorkflowCreateDefinitionPostResponse, CreateWorkflowFromTemplateApiV1WorkflowCreateTemplatePostData, CreateWorkflowFromTemplateApiV1WorkflowCreateTemplatePostError, CreateWorkflowFromTemplateApiV1WorkflowCreateTemplatePostResponse, CreateWorkflowRunApiV1WorkflowWorkflowIdRunsPostData, CreateWorkflowRunApiV1WorkflowWorkflowIdRunsPostError, CreateWorkflowRunApiV1WorkflowWorkflowIdRunsPostResponse, DeactivateEmbedTokenApiV1WorkflowWorkflowIdEmbedTokenDeleteData, DeactivateEmbedTokenApiV1WorkflowWorkflowIdEmbedTokenDeleteError, DeactivateEmbedTokenApiV1WorkflowWorkflowIdEmbedTokenDeleteResponse, DeleteCredentialApiV1CredentialsCredentialUuidDeleteData, DeleteCredentialApiV1CredentialsCredentialUuidDeleteError, DeleteCredentialApiV1CredentialsCredentialUuidDeleteResponse, DeleteDocumentApiV1KnowledgeBaseDocumentsDocumentUuidDeleteData, DeleteDocumentApiV1KnowledgeBaseDocumentsDocumentUuidDeleteError, DeleteLangfuseCredentialsApiV1OrganizationsLangfuseCredentialsDeleteData, DeleteLangfuseCredentialsApiV1OrganizationsLangfuseCredentialsDeleteError, DeleteRecordingApiV1WorkflowRecordingsRecordingIdDeleteData, DeleteRecordingApiV1WorkflowRecordingsRecordingIdDeleteError, DeleteToolApiV1ToolsToolUuidDeleteData, DeleteToolApiV1ToolsToolUuidDeleteError, DeleteToolApiV1ToolsToolUuidDeleteResponse, DownloadCampaignReportApiV1CampaignCampaignIdReportGetData, DownloadCampaignReportApiV1CampaignCampaignIdReportGetError, DownloadWorkflowArtifactApiV1PublicDownloadWorkflowTokenArtifactTypeGetData, DownloadWorkflowArtifactApiV1PublicDownloadWorkflowTokenArtifactTypeGetError, DuplicateWorkflowEndpointApiV1WorkflowWorkflowIdDuplicatePostData, DuplicateWorkflowEndpointApiV1WorkflowWorkflowIdDuplicatePostError, DuplicateWorkflowEndpointApiV1WorkflowWorkflowIdDuplicatePostResponse, DuplicateWorkflowTemplateApiV1WorkflowTemplatesDuplicatePostData, DuplicateWorkflowTemplateApiV1WorkflowTemplatesDuplicatePostError, DuplicateWorkflowTemplateApiV1WorkflowTemplatesDuplicatePostResponse, GetActiveTestsApiV1LooptalkActiveTestsGetData, GetActiveTestsApiV1LooptalkActiveTestsGetError, GetApiKeysApiV1UserApiKeysGetData, GetApiKeysApiV1UserApiKeysGetError, GetApiKeysApiV1UserApiKeysGetResponse, GetAuthUserApiV1UserAuthUserGetData, GetAuthUserApiV1UserAuthUserGetError, GetAuthUserApiV1UserAuthUserGetResponse, GetCampaignApiV1CampaignCampaignIdGetData, GetCampaignApiV1CampaignCampaignIdGetError, GetCampaignApiV1CampaignCampaignIdGetResponse, GetCampaignDefaultsApiV1OrganizationsCampaignDefaultsGetData, GetCampaignDefaultsApiV1OrganizationsCampaignDefaultsGetError, GetCampaignDefaultsApiV1OrganizationsCampaignDefaultsGetResponse, GetCampaignProgressApiV1CampaignCampaignIdProgressGetData, GetCampaignProgressApiV1CampaignCampaignIdProgressGetError, GetCampaignProgressApiV1CampaignCampaignIdProgressGetResponse, GetCampaignRunsApiV1CampaignCampaignIdRunsGetData, GetCampaignRunsApiV1CampaignCampaignIdRunsGetError, GetCampaignRunsApiV1CampaignCampaignIdRunsGetResponse, GetCampaignsApiV1CampaignGetData, GetCampaignsApiV1CampaignGetError, GetCampaignsApiV1CampaignGetResponse, GetCampaignSourceDownloadUrlApiV1CampaignCampaignIdSourceDownloadUrlGetData, GetCampaignSourceDownloadUrlApiV1CampaignCampaignIdSourceDownloadUrlGetError, GetCampaignSourceDownloadUrlApiV1CampaignCampaignIdSourceDownloadUrlGetResponse, GetCredentialApiV1CredentialsCredentialUuidGetData, GetCredentialApiV1CredentialsCredentialUuidGetError, GetCredentialApiV1CredentialsCredentialUuidGetResponse, GetCurrentPeriodUsageApiV1OrganizationsUsageCurrentPeriodGetData, GetCurrentPeriodUsageApiV1OrganizationsUsageCurrentPeriodGetError, GetCurrentPeriodUsageApiV1OrganizationsUsageCurrentPeriodGetResponse, GetCurrentUserApiV1AuthMeGetData, GetCurrentUserApiV1AuthMeGetError, GetCurrentUserApiV1AuthMeGetResponse, GetDailyReportApiV1OrganizationsReportsDailyGetData, GetDailyReportApiV1OrganizationsReportsDailyGetError, GetDailyReportApiV1OrganizationsReportsDailyGetResponse, GetDailyRunsDetailApiV1OrganizationsReportsDailyRunsGetData, GetDailyRunsDetailApiV1OrganizationsReportsDailyRunsGetError, GetDailyRunsDetailApiV1OrganizationsReportsDailyRunsGetResponse, GetDailyUsageBreakdownApiV1OrganizationsUsageDailyBreakdownGetData, GetDailyUsageBreakdownApiV1OrganizationsUsageDailyBreakdownGetError, GetDailyUsageBreakdownApiV1OrganizationsUsageDailyBreakdownGetResponse, GetDefaultConfigurationsApiV1UserConfigurationsDefaultsGetData, GetDefaultConfigurationsApiV1UserConfigurationsDefaultsGetResponse, GetDocumentApiV1KnowledgeBaseDocumentsDocumentUuidGetData, GetDocumentApiV1KnowledgeBaseDocumentsDocumentUuidGetError, GetDocumentApiV1KnowledgeBaseDocumentsDocumentUuidGetResponse, GetEmbedConfigApiV1PublicEmbedConfigTokenGetData, GetEmbedConfigApiV1PublicEmbedConfigTokenGetError, GetEmbedConfigApiV1PublicEmbedConfigTokenGetResponse, GetEmbedTokenApiV1WorkflowWorkflowIdEmbedTokenGetData, GetEmbedTokenApiV1WorkflowWorkflowIdEmbedTokenGetError, GetEmbedTokenApiV1WorkflowWorkflowIdEmbedTokenGetResponse, GetFileMetadataApiV1S3FileMetadataGetData, GetFileMetadataApiV1S3FileMetadataGetError, GetFileMetadataApiV1S3FileMetadataGetResponse, GetIntegrationAccessTokenApiV1IntegrationIntegrationIdAccessTokenGetData, GetIntegrationAccessTokenApiV1IntegrationIntegrationIdAccessTokenGetError, GetIntegrationAccessTokenApiV1IntegrationIntegrationIdAccessTokenGetResponse, GetIntegrationsApiV1IntegrationGetData, GetIntegrationsApiV1IntegrationGetError, GetIntegrationsApiV1IntegrationGetResponse, GetLangfuseCredentialsApiV1OrganizationsLangfuseCredentialsGetData, GetLangfuseCredentialsApiV1OrganizationsLangfuseCredentialsGetError, GetLangfuseCredentialsApiV1OrganizationsLangfuseCredentialsGetResponse, GetLoadTestStatsApiV1LooptalkLoadTestsLoadTestGroupIdStatsGetData, GetLoadTestStatsApiV1LooptalkLoadTestsLoadTestGroupIdStatsGetError, GetLoadTestStatsApiV1LooptalkLoadTestsLoadTestGroupIdStatsGetResponse, GetMpsCreditsApiV1OrganizationsUsageMpsCreditsGetData, GetMpsCreditsApiV1OrganizationsUsageMpsCreditsGetError, GetMpsCreditsApiV1OrganizationsUsageMpsCreditsGetResponse, GetPresignedUploadUrlApiV1S3PresignedUploadUrlPostData, GetPresignedUploadUrlApiV1S3PresignedUploadUrlPostError, GetPresignedUploadUrlApiV1S3PresignedUploadUrlPostResponse, GetPublicTurnCredentialsApiV1PublicEmbedTurnCredentialsSessionTokenGetData, GetPublicTurnCredentialsApiV1PublicEmbedTurnCredentialsSessionTokenGetError, GetPublicTurnCredentialsApiV1PublicEmbedTurnCredentialsSessionTokenGetResponse, GetServiceKeysApiV1UserServiceKeysGetData, GetServiceKeysApiV1UserServiceKeysGetError, GetServiceKeysApiV1UserServiceKeysGetResponse, GetSignedUrlApiV1S3SignedUrlGetData, GetSignedUrlApiV1S3SignedUrlGetError, GetSignedUrlApiV1S3SignedUrlGetResponse, GetTelephonyConfigurationApiV1OrganizationsTelephonyConfigGetData, GetTelephonyConfigurationApiV1OrganizationsTelephonyConfigGetError, GetTelephonyConfigurationApiV1OrganizationsTelephonyConfigGetResponse, GetTestSessionApiV1LooptalkTestSessionsTestSessionIdGetData, GetTestSessionApiV1LooptalkTestSessionsTestSessionIdGetError, GetTestSessionApiV1LooptalkTestSessionsTestSessionIdGetResponse, GetTestSessionConversationApiV1LooptalkTestSessionsTestSessionIdConversationGetData, GetTestSessionConversationApiV1LooptalkTestSessionsTestSessionIdConversationGetError, GetToolApiV1ToolsToolUuidGetData, GetToolApiV1ToolsToolUuidGetError, GetToolApiV1ToolsToolUuidGetResponse, GetTurnCredentialsApiV1TurnCredentialsGetData, GetTurnCredentialsApiV1TurnCredentialsGetError, GetTurnCredentialsApiV1TurnCredentialsGetResponse, GetUploadUrlApiV1KnowledgeBaseUploadUrlPostData, GetUploadUrlApiV1KnowledgeBaseUploadUrlPostError, GetUploadUrlApiV1KnowledgeBaseUploadUrlPostResponse, GetUploadUrlApiV1WorkflowRecordingsUploadUrlPostData, GetUploadUrlApiV1WorkflowRecordingsUploadUrlPostError, GetUploadUrlApiV1WorkflowRecordingsUploadUrlPostResponse, GetUsageHistoryApiV1OrganizationsUsageRunsGetData, GetUsageHistoryApiV1OrganizationsUsageRunsGetError, GetUsageHistoryApiV1OrganizationsUsageRunsGetResponse, GetUserConfigurationsApiV1UserConfigurationsUserGetData, GetUserConfigurationsApiV1UserConfigurationsUserGetError, GetUserConfigurationsApiV1UserConfigurationsUserGetResponse, GetVoicesApiV1UserConfigurationsVoicesProviderGetData, GetVoicesApiV1UserConfigurationsVoicesProviderGetError, GetVoicesApiV1UserConfigurationsVoicesProviderGetResponse, GetWorkflowApiV1WorkflowFetchWorkflowIdGetData, GetWorkflowApiV1WorkflowFetchWorkflowIdGetError, GetWorkflowApiV1WorkflowFetchWorkflowIdGetResponse, GetWorkflowCountApiV1WorkflowCountGetData, GetWorkflowCountApiV1WorkflowCountGetError, GetWorkflowCountApiV1WorkflowCountGetResponse, GetWorkflowOptionsApiV1OrganizationsReportsWorkflowsGetData, GetWorkflowOptionsApiV1OrganizationsReportsWorkflowsGetError, GetWorkflowOptionsApiV1OrganizationsReportsWorkflowsGetResponse, GetWorkflowRunApiV1WorkflowWorkflowIdRunsRunIdGetData, GetWorkflowRunApiV1WorkflowWorkflowIdRunsRunIdGetError, GetWorkflowRunApiV1WorkflowWorkflowIdRunsRunIdGetResponse, GetWorkflowRunsApiV1SuperuserWorkflowRunsGetData, GetWorkflowRunsApiV1SuperuserWorkflowRunsGetError, GetWorkflowRunsApiV1SuperuserWorkflowRunsGetResponse, GetWorkflowRunsApiV1WorkflowWorkflowIdRunsGetData, GetWorkflowRunsApiV1WorkflowWorkflowIdRunsGetError, GetWorkflowRunsApiV1WorkflowWorkflowIdRunsGetResponse, GetWorkflowsApiV1WorkflowFetchGetData, GetWorkflowsApiV1WorkflowFetchGetError, GetWorkflowsApiV1WorkflowFetchGetResponse, GetWorkflowsSummaryApiV1WorkflowSummaryGetData, GetWorkflowsSummaryApiV1WorkflowSummaryGetError, GetWorkflowsSummaryApiV1WorkflowSummaryGetResponse, GetWorkflowTemplatesApiV1WorkflowTemplatesGetData, GetWorkflowTemplatesApiV1WorkflowTemplatesGetResponse, HandleCloudonixCdrApiV1TelephonyCloudonixCdrPostData, HandleCloudonixStatusCallbackApiV1TelephonyCloudonixStatusCallbackWorkflowRunIdPostData, HandleCloudonixStatusCallbackApiV1TelephonyCloudonixStatusCallbackWorkflowRunIdPostError, HandleInboundFallbackApiV1TelephonyInboundFallbackPostData, HandleInboundTelephonyApiV1TelephonyInboundWorkflowIdPostData, HandleInboundTelephonyApiV1TelephonyInboundWorkflowIdPostError, HandleTelnyxEventsApiV1TelephonyTelnyxEventsWorkflowRunIdPostData, HandleTelnyxEventsApiV1TelephonyTelnyxEventsWorkflowRunIdPostError,HandleTwilioStatusCallbackApiV1TelephonyTwilioStatusCallbackWorkflowRunIdPostData, HandleTwilioStatusCallbackApiV1TelephonyTwilioStatusCallbackWorkflowRunIdPostError, HandleVobizHangupCallbackApiV1TelephonyVobizHangupCallbackWorkflowRunIdPostData, HandleVobizHangupCallbackApiV1TelephonyVobizHangupCallbackWorkflowRunIdPostError, HandleVobizHangupCallbackByWorkflowApiV1TelephonyVobizHangupCallbackWorkflowWorkflowIdPostData, HandleVobizHangupCallbackByWorkflowApiV1TelephonyVobizHangupCallbackWorkflowWorkflowIdPostError, HandleVobizRingCallbackApiV1TelephonyVobizRingCallbackWorkflowRunIdPostData, HandleVobizRingCallbackApiV1TelephonyVobizRingCallbackWorkflowRunIdPostError, HandleVonageEventsApiV1TelephonyVonageEventsWorkflowRunIdPostData, HandleVonageEventsApiV1TelephonyVonageEventsWorkflowRunIdPostError, HealthApiV1HealthGetData, HealthApiV1HealthGetResponse,ImpersonateApiV1SuperuserImpersonatePostData, ImpersonateApiV1SuperuserImpersonatePostError, ImpersonateApiV1SuperuserImpersonatePostResponse, InitializeEmbedSessionApiV1PublicEmbedInitPostData, InitializeEmbedSessionApiV1PublicEmbedInitPostError, InitializeEmbedSessionApiV1PublicEmbedInitPostResponse, InitiateCallApiV1PublicAgentUuidPostData, InitiateCallApiV1PublicAgentUuidPostError, InitiateCallApiV1PublicAgentUuidPostResponse, InitiateCallApiV1TelephonyInitiateCallPostData, InitiateCallApiV1TelephonyInitiateCallPostError, InitiateCallTransferApiV1TelephonyCallTransferPostData, InitiateCallTransferApiV1TelephonyCallTransferPostError, ListCredentialsApiV1CredentialsGetData, ListCredentialsApiV1CredentialsGetError, ListCredentialsApiV1CredentialsGetResponse, ListDocumentsApiV1KnowledgeBaseDocumentsGetData, ListDocumentsApiV1KnowledgeBaseDocumentsGetError, ListDocumentsApiV1KnowledgeBaseDocumentsGetResponse, ListRecordingsApiV1WorkflowRecordingsGetData, ListRecordingsApiV1WorkflowRecordingsGetError, ListRecordingsApiV1WorkflowRecordingsGetResponse, ListTestSessionsApiV1LooptalkTestSessionsGetData, ListTestSessionsApiV1LooptalkTestSessionsGetError, ListTestSessionsApiV1LooptalkTestSessionsGetResponse, ListToolsApiV1ToolsGetData, ListToolsApiV1ToolsGetError, ListToolsApiV1ToolsGetResponse, LoginApiV1AuthLoginPostData, LoginApiV1AuthLoginPostError, LoginApiV1AuthLoginPostResponse, OptionsConfigApiV1PublicEmbedConfigTokenOptionsData, OptionsConfigApiV1PublicEmbedConfigTokenOptionsError, OptionsInitApiV1PublicEmbedInitOptionsData, OptionsTurnCredentialsApiV1PublicEmbedTurnCredentialsSessionTokenOptionsData, OptionsTurnCredentialsApiV1PublicEmbedTurnCredentialsSessionTokenOptionsError, PauseCampaignApiV1CampaignCampaignIdPausePostData, PauseCampaignApiV1CampaignCampaignIdPausePostError, PauseCampaignApiV1CampaignCampaignIdPausePostResponse, ProcessDocumentApiV1KnowledgeBaseProcessDocumentPostData, ProcessDocumentApiV1KnowledgeBaseProcessDocumentPostError, ProcessDocumentApiV1KnowledgeBaseProcessDocumentPostResponse, ReactivateApiKeyApiV1UserApiKeysApiKeyIdReactivatePutData, ReactivateApiKeyApiV1UserApiKeysApiKeyIdReactivatePutError, ReactivateApiKeyApiV1UserApiKeysApiKeyIdReactivatePutResponse, ReactivateServiceKeyApiV1UserServiceKeysServiceKeyIdReactivatePutData, ReactivateServiceKeyApiV1UserServiceKeysServiceKeyIdReactivatePutError, ResumeCampaignApiV1CampaignCampaignIdResumePostData, ResumeCampaignApiV1CampaignCampaignIdResumePostError, ResumeCampaignApiV1CampaignCampaignIdResumePostResponse, SaveLangfuseCredentialsApiV1OrganizationsLangfuseCredentialsPostData, SaveLangfuseCredentialsApiV1OrganizationsLangfuseCredentialsPostError, SaveTelephonyConfigurationApiV1OrganizationsTelephonyConfigPostData, SaveTelephonyConfigurationApiV1OrganizationsTelephonyConfigPostError, SearchChunksApiV1KnowledgeBaseSearchPostData, SearchChunksApiV1KnowledgeBaseSearchPostError, SearchChunksApiV1KnowledgeBaseSearchPostResponse, SignupApiV1AuthSignupPostData, SignupApiV1AuthSignupPostError, SignupApiV1AuthSignupPostResponse, StartCampaignApiV1CampaignCampaignIdStartPostData, StartCampaignApiV1CampaignCampaignIdStartPostError, StartCampaignApiV1CampaignCampaignIdStartPostResponse, StartTestSessionApiV1LooptalkTestSessionsTestSessionIdStartPostData, StartTestSessionApiV1LooptalkTestSessionsTestSessionIdStartPostError, StopTestSessionApiV1LooptalkTestSessionsTestSessionIdStopPostData, StopTestSessionApiV1LooptalkTestSessionsTestSessionIdStopPostError, TranscribeAudioApiV1WorkflowRecordingsTranscribePostData, TranscribeAudioApiV1WorkflowRecordingsTranscribePostError, UnarchiveToolApiV1ToolsToolUuidUnarchivePostData, UnarchiveToolApiV1ToolsToolUuidUnarchivePostError, UnarchiveToolApiV1ToolsToolUuidUnarchivePostResponse, UpdateCampaignApiV1CampaignCampaignIdPatchData, UpdateCampaignApiV1CampaignCampaignIdPatchError, UpdateCampaignApiV1CampaignCampaignIdPatchResponse, UpdateCredentialApiV1CredentialsCredentialUuidPutData, UpdateCredentialApiV1CredentialsCredentialUuidPutError, UpdateCredentialApiV1CredentialsCredentialUuidPutResponse, UpdateIntegrationApiV1IntegrationIntegrationIdPutData, UpdateIntegrationApiV1IntegrationIntegrationIdPutError, UpdateIntegrationApiV1IntegrationIntegrationIdPutResponse, UpdateToolApiV1ToolsToolUuidPutData, UpdateToolApiV1ToolsToolUuidPutError, UpdateToolApiV1ToolsToolUuidPutResponse, UpdateUserConfigurationsApiV1UserConfigurationsUserPutData, UpdateUserConfigurationsApiV1UserConfigurationsUserPutError, UpdateUserConfigurationsApiV1UserConfigurationsUserPutResponse, UpdateWorkflowApiV1WorkflowWorkflowIdPutData, UpdateWorkflowApiV1WorkflowWorkflowIdPutError, UpdateWorkflowApiV1WorkflowWorkflowIdPutResponse, UpdateWorkflowStatusApiV1WorkflowWorkflowIdStatusPutData, UpdateWorkflowStatusApiV1WorkflowWorkflowIdStatusPutError, UpdateWorkflowStatusApiV1WorkflowWorkflowIdStatusPutResponse, ValidateUserConfigurationsApiV1UserConfigurationsUserValidateGetData, ValidateUserConfigurationsApiV1UserConfigurationsUserValidateGetError, ValidateUserConfigurationsApiV1UserConfigurationsUserValidateGetResponse, ValidateWorkflowApiV1WorkflowWorkflowIdValidatePostData, ValidateWorkflowApiV1WorkflowWorkflowIdValidatePostError, ValidateWorkflowApiV1WorkflowWorkflowIdValidatePostResponse } from './types.gen'; +import type { ArchiveApiKeyApiV1UserApiKeysApiKeyIdDeleteData, ArchiveApiKeyApiV1UserApiKeysApiKeyIdDeleteError, ArchiveApiKeyApiV1UserApiKeysApiKeyIdDeleteResponse, ArchiveServiceKeyApiV1UserServiceKeysServiceKeyIdDeleteData, ArchiveServiceKeyApiV1UserServiceKeysServiceKeyIdDeleteError, CompleteTransferFunctionCallApiV1TelephonyTransferResultTransferIdPostData, CompleteTransferFunctionCallApiV1TelephonyTransferResultTransferIdPostError, CreateApiKeyApiV1UserApiKeysPostData, CreateApiKeyApiV1UserApiKeysPostError, CreateApiKeyApiV1UserApiKeysPostResponse, CreateCampaignApiV1CampaignCreatePostData, CreateCampaignApiV1CampaignCreatePostError, CreateCampaignApiV1CampaignCreatePostResponse, CreateCredentialApiV1CredentialsPostData, CreateCredentialApiV1CredentialsPostError, CreateCredentialApiV1CredentialsPostResponse, CreateLoadTestApiV1LooptalkLoadTestsPostData, CreateLoadTestApiV1LooptalkLoadTestsPostError, CreateLoadTestApiV1LooptalkLoadTestsPostResponse, CreateOrUpdateEmbedTokenApiV1WorkflowWorkflowIdEmbedTokenPostData, CreateOrUpdateEmbedTokenApiV1WorkflowWorkflowIdEmbedTokenPostError, CreateOrUpdateEmbedTokenApiV1WorkflowWorkflowIdEmbedTokenPostResponse, CreateRecordingApiV1WorkflowRecordingsPostData, CreateRecordingApiV1WorkflowRecordingsPostError, CreateRecordingApiV1WorkflowRecordingsPostResponse, CreateServiceKeyApiV1UserServiceKeysPostData, CreateServiceKeyApiV1UserServiceKeysPostError, CreateServiceKeyApiV1UserServiceKeysPostResponse, CreateSessionApiV1IntegrationSessionPostData, CreateSessionApiV1IntegrationSessionPostError, CreateSessionApiV1IntegrationSessionPostResponse, CreateTestSessionApiV1LooptalkTestSessionsPostData, CreateTestSessionApiV1LooptalkTestSessionsPostError, CreateTestSessionApiV1LooptalkTestSessionsPostResponse, CreateToolApiV1ToolsPostData, CreateToolApiV1ToolsPostError, CreateToolApiV1ToolsPostResponse, CreateWorkflowApiV1WorkflowCreateDefinitionPostData, CreateWorkflowApiV1WorkflowCreateDefinitionPostError, CreateWorkflowApiV1WorkflowCreateDefinitionPostResponse, CreateWorkflowFromTemplateApiV1WorkflowCreateTemplatePostData, CreateWorkflowFromTemplateApiV1WorkflowCreateTemplatePostError, CreateWorkflowFromTemplateApiV1WorkflowCreateTemplatePostResponse, CreateWorkflowRunApiV1WorkflowWorkflowIdRunsPostData, CreateWorkflowRunApiV1WorkflowWorkflowIdRunsPostError, CreateWorkflowRunApiV1WorkflowWorkflowIdRunsPostResponse, DeactivateEmbedTokenApiV1WorkflowWorkflowIdEmbedTokenDeleteData, DeactivateEmbedTokenApiV1WorkflowWorkflowIdEmbedTokenDeleteError, DeactivateEmbedTokenApiV1WorkflowWorkflowIdEmbedTokenDeleteResponse, DeleteCredentialApiV1CredentialsCredentialUuidDeleteData, DeleteCredentialApiV1CredentialsCredentialUuidDeleteError, DeleteCredentialApiV1CredentialsCredentialUuidDeleteResponse, DeleteDocumentApiV1KnowledgeBaseDocumentsDocumentUuidDeleteData, DeleteDocumentApiV1KnowledgeBaseDocumentsDocumentUuidDeleteError, DeleteLangfuseCredentialsApiV1OrganizationsLangfuseCredentialsDeleteData, DeleteLangfuseCredentialsApiV1OrganizationsLangfuseCredentialsDeleteError, DeleteRecordingApiV1WorkflowRecordingsRecordingIdDeleteData, DeleteRecordingApiV1WorkflowRecordingsRecordingIdDeleteError, DeleteToolApiV1ToolsToolUuidDeleteData, DeleteToolApiV1ToolsToolUuidDeleteError, DeleteToolApiV1ToolsToolUuidDeleteResponse, DownloadCampaignReportApiV1CampaignCampaignIdReportGetData, DownloadCampaignReportApiV1CampaignCampaignIdReportGetError, DownloadWorkflowArtifactApiV1PublicDownloadWorkflowTokenArtifactTypeGetData, DownloadWorkflowArtifactApiV1PublicDownloadWorkflowTokenArtifactTypeGetError, DuplicateWorkflowEndpointApiV1WorkflowWorkflowIdDuplicatePostData, DuplicateWorkflowEndpointApiV1WorkflowWorkflowIdDuplicatePostError, DuplicateWorkflowEndpointApiV1WorkflowWorkflowIdDuplicatePostResponse, DuplicateWorkflowTemplateApiV1WorkflowTemplatesDuplicatePostData, DuplicateWorkflowTemplateApiV1WorkflowTemplatesDuplicatePostError, DuplicateWorkflowTemplateApiV1WorkflowTemplatesDuplicatePostResponse, GetActiveTestsApiV1LooptalkActiveTestsGetData, GetActiveTestsApiV1LooptalkActiveTestsGetError, GetApiKeysApiV1UserApiKeysGetData, GetApiKeysApiV1UserApiKeysGetError, GetApiKeysApiV1UserApiKeysGetResponse, GetAuthUserApiV1UserAuthUserGetData, GetAuthUserApiV1UserAuthUserGetError, GetAuthUserApiV1UserAuthUserGetResponse, GetCampaignApiV1CampaignCampaignIdGetData, GetCampaignApiV1CampaignCampaignIdGetError, GetCampaignApiV1CampaignCampaignIdGetResponse, GetCampaignDefaultsApiV1OrganizationsCampaignDefaultsGetData, GetCampaignDefaultsApiV1OrganizationsCampaignDefaultsGetError, GetCampaignDefaultsApiV1OrganizationsCampaignDefaultsGetResponse, GetCampaignProgressApiV1CampaignCampaignIdProgressGetData, GetCampaignProgressApiV1CampaignCampaignIdProgressGetError, GetCampaignProgressApiV1CampaignCampaignIdProgressGetResponse, GetCampaignRunsApiV1CampaignCampaignIdRunsGetData, GetCampaignRunsApiV1CampaignCampaignIdRunsGetError, GetCampaignRunsApiV1CampaignCampaignIdRunsGetResponse, GetCampaignsApiV1CampaignGetData, GetCampaignsApiV1CampaignGetError, GetCampaignsApiV1CampaignGetResponse, GetCampaignSourceDownloadUrlApiV1CampaignCampaignIdSourceDownloadUrlGetData, GetCampaignSourceDownloadUrlApiV1CampaignCampaignIdSourceDownloadUrlGetError, GetCampaignSourceDownloadUrlApiV1CampaignCampaignIdSourceDownloadUrlGetResponse, GetCredentialApiV1CredentialsCredentialUuidGetData, GetCredentialApiV1CredentialsCredentialUuidGetError, GetCredentialApiV1CredentialsCredentialUuidGetResponse, GetCurrentPeriodUsageApiV1OrganizationsUsageCurrentPeriodGetData, GetCurrentPeriodUsageApiV1OrganizationsUsageCurrentPeriodGetError, GetCurrentPeriodUsageApiV1OrganizationsUsageCurrentPeriodGetResponse, GetCurrentUserApiV1AuthMeGetData, GetCurrentUserApiV1AuthMeGetError, GetCurrentUserApiV1AuthMeGetResponse, GetDailyReportApiV1OrganizationsReportsDailyGetData, GetDailyReportApiV1OrganizationsReportsDailyGetError, GetDailyReportApiV1OrganizationsReportsDailyGetResponse, GetDailyRunsDetailApiV1OrganizationsReportsDailyRunsGetData, GetDailyRunsDetailApiV1OrganizationsReportsDailyRunsGetError, GetDailyRunsDetailApiV1OrganizationsReportsDailyRunsGetResponse, GetDailyUsageBreakdownApiV1OrganizationsUsageDailyBreakdownGetData, GetDailyUsageBreakdownApiV1OrganizationsUsageDailyBreakdownGetError, GetDailyUsageBreakdownApiV1OrganizationsUsageDailyBreakdownGetResponse, GetDefaultConfigurationsApiV1UserConfigurationsDefaultsGetData, GetDefaultConfigurationsApiV1UserConfigurationsDefaultsGetResponse, GetDocumentApiV1KnowledgeBaseDocumentsDocumentUuidGetData, GetDocumentApiV1KnowledgeBaseDocumentsDocumentUuidGetError, GetDocumentApiV1KnowledgeBaseDocumentsDocumentUuidGetResponse, GetEmbedConfigApiV1PublicEmbedConfigTokenGetData, GetEmbedConfigApiV1PublicEmbedConfigTokenGetError, GetEmbedConfigApiV1PublicEmbedConfigTokenGetResponse, GetEmbedTokenApiV1WorkflowWorkflowIdEmbedTokenGetData, GetEmbedTokenApiV1WorkflowWorkflowIdEmbedTokenGetError, GetEmbedTokenApiV1WorkflowWorkflowIdEmbedTokenGetResponse, GetFileMetadataApiV1S3FileMetadataGetData, GetFileMetadataApiV1S3FileMetadataGetError, GetFileMetadataApiV1S3FileMetadataGetResponse, GetIntegrationAccessTokenApiV1IntegrationIntegrationIdAccessTokenGetData, GetIntegrationAccessTokenApiV1IntegrationIntegrationIdAccessTokenGetError, GetIntegrationAccessTokenApiV1IntegrationIntegrationIdAccessTokenGetResponse, GetIntegrationsApiV1IntegrationGetData, GetIntegrationsApiV1IntegrationGetError, GetIntegrationsApiV1IntegrationGetResponse, GetLangfuseCredentialsApiV1OrganizationsLangfuseCredentialsGetData, GetLangfuseCredentialsApiV1OrganizationsLangfuseCredentialsGetError, GetLangfuseCredentialsApiV1OrganizationsLangfuseCredentialsGetResponse, GetLoadTestStatsApiV1LooptalkLoadTestsLoadTestGroupIdStatsGetData, GetLoadTestStatsApiV1LooptalkLoadTestsLoadTestGroupIdStatsGetError, GetLoadTestStatsApiV1LooptalkLoadTestsLoadTestGroupIdStatsGetResponse, GetMpsCreditsApiV1OrganizationsUsageMpsCreditsGetData, GetMpsCreditsApiV1OrganizationsUsageMpsCreditsGetError, GetMpsCreditsApiV1OrganizationsUsageMpsCreditsGetResponse, GetPresignedUploadUrlApiV1S3PresignedUploadUrlPostData, GetPresignedUploadUrlApiV1S3PresignedUploadUrlPostError, GetPresignedUploadUrlApiV1S3PresignedUploadUrlPostResponse, GetPublicTurnCredentialsApiV1PublicEmbedTurnCredentialsSessionTokenGetData, GetPublicTurnCredentialsApiV1PublicEmbedTurnCredentialsSessionTokenGetError, GetPublicTurnCredentialsApiV1PublicEmbedTurnCredentialsSessionTokenGetResponse, GetServiceKeysApiV1UserServiceKeysGetData, GetServiceKeysApiV1UserServiceKeysGetError, GetServiceKeysApiV1UserServiceKeysGetResponse, GetSignedUrlApiV1S3SignedUrlGetData, GetSignedUrlApiV1S3SignedUrlGetError, GetSignedUrlApiV1S3SignedUrlGetResponse, GetTelephonyConfigurationApiV1OrganizationsTelephonyConfigGetData, GetTelephonyConfigurationApiV1OrganizationsTelephonyConfigGetError, GetTelephonyConfigurationApiV1OrganizationsTelephonyConfigGetResponse, GetTestSessionApiV1LooptalkTestSessionsTestSessionIdGetData, GetTestSessionApiV1LooptalkTestSessionsTestSessionIdGetError, GetTestSessionApiV1LooptalkTestSessionsTestSessionIdGetResponse, GetTestSessionConversationApiV1LooptalkTestSessionsTestSessionIdConversationGetData, GetTestSessionConversationApiV1LooptalkTestSessionsTestSessionIdConversationGetError, GetToolApiV1ToolsToolUuidGetData, GetToolApiV1ToolsToolUuidGetError, GetToolApiV1ToolsToolUuidGetResponse, GetTurnCredentialsApiV1TurnCredentialsGetData, GetTurnCredentialsApiV1TurnCredentialsGetError, GetTurnCredentialsApiV1TurnCredentialsGetResponse, GetUploadUrlApiV1KnowledgeBaseUploadUrlPostData, GetUploadUrlApiV1KnowledgeBaseUploadUrlPostError, GetUploadUrlApiV1KnowledgeBaseUploadUrlPostResponse, GetUploadUrlApiV1WorkflowRecordingsUploadUrlPostData, GetUploadUrlApiV1WorkflowRecordingsUploadUrlPostError, GetUploadUrlApiV1WorkflowRecordingsUploadUrlPostResponse, GetUsageHistoryApiV1OrganizationsUsageRunsGetData, GetUsageHistoryApiV1OrganizationsUsageRunsGetError, GetUsageHistoryApiV1OrganizationsUsageRunsGetResponse, GetUserConfigurationsApiV1UserConfigurationsUserGetData, GetUserConfigurationsApiV1UserConfigurationsUserGetError, GetUserConfigurationsApiV1UserConfigurationsUserGetResponse, GetVoicesApiV1UserConfigurationsVoicesProviderGetData, GetVoicesApiV1UserConfigurationsVoicesProviderGetError, GetVoicesApiV1UserConfigurationsVoicesProviderGetResponse, GetWorkflowApiV1WorkflowFetchWorkflowIdGetData, GetWorkflowApiV1WorkflowFetchWorkflowIdGetError, GetWorkflowApiV1WorkflowFetchWorkflowIdGetResponse, GetWorkflowCountApiV1WorkflowCountGetData, GetWorkflowCountApiV1WorkflowCountGetError, GetWorkflowCountApiV1WorkflowCountGetResponse, GetWorkflowOptionsApiV1OrganizationsReportsWorkflowsGetData, GetWorkflowOptionsApiV1OrganizationsReportsWorkflowsGetError, GetWorkflowOptionsApiV1OrganizationsReportsWorkflowsGetResponse, GetWorkflowRunApiV1WorkflowWorkflowIdRunsRunIdGetData, GetWorkflowRunApiV1WorkflowWorkflowIdRunsRunIdGetError, GetWorkflowRunApiV1WorkflowWorkflowIdRunsRunIdGetResponse, GetWorkflowRunsApiV1SuperuserWorkflowRunsGetData, GetWorkflowRunsApiV1SuperuserWorkflowRunsGetError, GetWorkflowRunsApiV1SuperuserWorkflowRunsGetResponse, GetWorkflowRunsApiV1WorkflowWorkflowIdRunsGetData, GetWorkflowRunsApiV1WorkflowWorkflowIdRunsGetError, GetWorkflowRunsApiV1WorkflowWorkflowIdRunsGetResponse, GetWorkflowsApiV1WorkflowFetchGetData, GetWorkflowsApiV1WorkflowFetchGetError, GetWorkflowsApiV1WorkflowFetchGetResponse, GetWorkflowsSummaryApiV1WorkflowSummaryGetData, GetWorkflowsSummaryApiV1WorkflowSummaryGetError, GetWorkflowsSummaryApiV1WorkflowSummaryGetResponse, GetWorkflowTemplatesApiV1WorkflowTemplatesGetData, GetWorkflowTemplatesApiV1WorkflowTemplatesGetResponse, HandleCloudonixCdrApiV1TelephonyCloudonixCdrPostData, HandleCloudonixStatusCallbackApiV1TelephonyCloudonixStatusCallbackWorkflowRunIdPostData, HandleCloudonixStatusCallbackApiV1TelephonyCloudonixStatusCallbackWorkflowRunIdPostError, HandleInboundFallbackApiV1TelephonyInboundFallbackPostData, HandleInboundTelephonyApiV1TelephonyInboundWorkflowIdPostData, HandleInboundTelephonyApiV1TelephonyInboundWorkflowIdPostError, HandleTelnyxEventsApiV1TelephonyTelnyxEventsWorkflowRunIdPostData, HandleTelnyxEventsApiV1TelephonyTelnyxEventsWorkflowRunIdPostError, HandleTwilioStatusCallbackApiV1TelephonyTwilioStatusCallbackWorkflowRunIdPostData, HandleTwilioStatusCallbackApiV1TelephonyTwilioStatusCallbackWorkflowRunIdPostError, HandleVobizHangupCallbackApiV1TelephonyVobizHangupCallbackWorkflowRunIdPostData, HandleVobizHangupCallbackApiV1TelephonyVobizHangupCallbackWorkflowRunIdPostError, HandleVobizHangupCallbackByWorkflowApiV1TelephonyVobizHangupCallbackWorkflowWorkflowIdPostData, HandleVobizHangupCallbackByWorkflowApiV1TelephonyVobizHangupCallbackWorkflowWorkflowIdPostError, HandleVobizRingCallbackApiV1TelephonyVobizRingCallbackWorkflowRunIdPostData, HandleVobizRingCallbackApiV1TelephonyVobizRingCallbackWorkflowRunIdPostError, HandleVonageEventsApiV1TelephonyVonageEventsWorkflowRunIdPostData, HandleVonageEventsApiV1TelephonyVonageEventsWorkflowRunIdPostError, HealthApiV1HealthGetData, HealthApiV1HealthGetResponse,ImpersonateApiV1SuperuserImpersonatePostData, ImpersonateApiV1SuperuserImpersonatePostError, ImpersonateApiV1SuperuserImpersonatePostResponse, InitializeEmbedSessionApiV1PublicEmbedInitPostData, InitializeEmbedSessionApiV1PublicEmbedInitPostError, InitializeEmbedSessionApiV1PublicEmbedInitPostResponse, InitiateCallApiV1PublicAgentUuidPostData, InitiateCallApiV1PublicAgentUuidPostError, InitiateCallApiV1PublicAgentUuidPostResponse, InitiateCallApiV1TelephonyInitiateCallPostData, InitiateCallApiV1TelephonyInitiateCallPostError, InitiateCallTransferApiV1TelephonyCallTransferPostData, InitiateCallTransferApiV1TelephonyCallTransferPostError, ListCredentialsApiV1CredentialsGetData, ListCredentialsApiV1CredentialsGetError, ListCredentialsApiV1CredentialsGetResponse, ListDocumentsApiV1KnowledgeBaseDocumentsGetData, ListDocumentsApiV1KnowledgeBaseDocumentsGetError, ListDocumentsApiV1KnowledgeBaseDocumentsGetResponse, ListRecordingsApiV1WorkflowRecordingsGetData, ListRecordingsApiV1WorkflowRecordingsGetError, ListRecordingsApiV1WorkflowRecordingsGetResponse, ListTestSessionsApiV1LooptalkTestSessionsGetData, ListTestSessionsApiV1LooptalkTestSessionsGetError, ListTestSessionsApiV1LooptalkTestSessionsGetResponse, ListToolsApiV1ToolsGetData, ListToolsApiV1ToolsGetError, ListToolsApiV1ToolsGetResponse, LoginApiV1AuthLoginPostData, LoginApiV1AuthLoginPostError, LoginApiV1AuthLoginPostResponse, OptionsConfigApiV1PublicEmbedConfigTokenOptionsData, OptionsConfigApiV1PublicEmbedConfigTokenOptionsError, OptionsInitApiV1PublicEmbedInitOptionsData, OptionsTurnCredentialsApiV1PublicEmbedTurnCredentialsSessionTokenOptionsData, OptionsTurnCredentialsApiV1PublicEmbedTurnCredentialsSessionTokenOptionsError, PauseCampaignApiV1CampaignCampaignIdPausePostData, PauseCampaignApiV1CampaignCampaignIdPausePostError, PauseCampaignApiV1CampaignCampaignIdPausePostResponse, ProcessDocumentApiV1KnowledgeBaseProcessDocumentPostData, ProcessDocumentApiV1KnowledgeBaseProcessDocumentPostError, ProcessDocumentApiV1KnowledgeBaseProcessDocumentPostResponse, ReactivateApiKeyApiV1UserApiKeysApiKeyIdReactivatePutData, ReactivateApiKeyApiV1UserApiKeysApiKeyIdReactivatePutError, ReactivateApiKeyApiV1UserApiKeysApiKeyIdReactivatePutResponse, ReactivateServiceKeyApiV1UserServiceKeysServiceKeyIdReactivatePutData, ReactivateServiceKeyApiV1UserServiceKeysServiceKeyIdReactivatePutError, ResumeCampaignApiV1CampaignCampaignIdResumePostData, ResumeCampaignApiV1CampaignCampaignIdResumePostError, ResumeCampaignApiV1CampaignCampaignIdResumePostResponse, SaveLangfuseCredentialsApiV1OrganizationsLangfuseCredentialsPostData, SaveLangfuseCredentialsApiV1OrganizationsLangfuseCredentialsPostError, SaveTelephonyConfigurationApiV1OrganizationsTelephonyConfigPostData, SaveTelephonyConfigurationApiV1OrganizationsTelephonyConfigPostError, SearchChunksApiV1KnowledgeBaseSearchPostData, SearchChunksApiV1KnowledgeBaseSearchPostError, SearchChunksApiV1KnowledgeBaseSearchPostResponse, SignupApiV1AuthSignupPostData, SignupApiV1AuthSignupPostError, SignupApiV1AuthSignupPostResponse, StartCampaignApiV1CampaignCampaignIdStartPostData, StartCampaignApiV1CampaignCampaignIdStartPostError, StartCampaignApiV1CampaignCampaignIdStartPostResponse, StartTestSessionApiV1LooptalkTestSessionsTestSessionIdStartPostData, StartTestSessionApiV1LooptalkTestSessionsTestSessionIdStartPostError, StopTestSessionApiV1LooptalkTestSessionsTestSessionIdStopPostData, StopTestSessionApiV1LooptalkTestSessionsTestSessionIdStopPostError, TranscribeAudioApiV1WorkflowRecordingsTranscribePostData, TranscribeAudioApiV1WorkflowRecordingsTranscribePostError, UnarchiveToolApiV1ToolsToolUuidUnarchivePostData, UnarchiveToolApiV1ToolsToolUuidUnarchivePostError, UnarchiveToolApiV1ToolsToolUuidUnarchivePostResponse, UpdateCampaignApiV1CampaignCampaignIdPatchData, UpdateCampaignApiV1CampaignCampaignIdPatchError, UpdateCampaignApiV1CampaignCampaignIdPatchResponse, UpdateCredentialApiV1CredentialsCredentialUuidPutData, UpdateCredentialApiV1CredentialsCredentialUuidPutError, UpdateCredentialApiV1CredentialsCredentialUuidPutResponse, UpdateIntegrationApiV1IntegrationIntegrationIdPutData, UpdateIntegrationApiV1IntegrationIntegrationIdPutError, UpdateIntegrationApiV1IntegrationIntegrationIdPutResponse, UpdateToolApiV1ToolsToolUuidPutData, UpdateToolApiV1ToolsToolUuidPutError, UpdateToolApiV1ToolsToolUuidPutResponse, UpdateUserConfigurationsApiV1UserConfigurationsUserPutData, UpdateUserConfigurationsApiV1UserConfigurationsUserPutError, UpdateUserConfigurationsApiV1UserConfigurationsUserPutResponse, UpdateWorkflowApiV1WorkflowWorkflowIdPutData, UpdateWorkflowApiV1WorkflowWorkflowIdPutError, UpdateWorkflowApiV1WorkflowWorkflowIdPutResponse, UpdateWorkflowStatusApiV1WorkflowWorkflowIdStatusPutData, UpdateWorkflowStatusApiV1WorkflowWorkflowIdStatusPutError, UpdateWorkflowStatusApiV1WorkflowWorkflowIdStatusPutResponse, ValidateUserConfigurationsApiV1UserConfigurationsUserValidateGetData, ValidateUserConfigurationsApiV1UserConfigurationsUserValidateGetError, ValidateUserConfigurationsApiV1UserConfigurationsUserValidateGetResponse, ValidateWorkflowApiV1WorkflowWorkflowIdValidatePostData, ValidateWorkflowApiV1WorkflowWorkflowIdValidatePostError, ValidateWorkflowApiV1WorkflowWorkflowIdValidatePostResponse } from './types.gen'; + export type Options = ClientOptions & { /** * You can provide a client instance returned by `createClient()` instead of diff --git a/ui/src/client/types.gen.ts b/ui/src/client/types.gen.ts index b4ff8e33..7076eaf3 100644 --- a/ui/src/client/types.gen.ts +++ b/ui/src/client/types.gen.ts @@ -3274,7 +3274,16 @@ export type DownloadCampaignReportApiV1CampaignCampaignIdReportGetData = { path: { campaign_id: number; }; - query?: never; + query?: { + /** + * Filter runs created on or after this datetime (ISO 8601) + */ + start_date?: string | null; + /** + * Filter runs created on or before this datetime (ISO 8601) + */ + end_date?: string | null; + }; url: '/api/v1/campaign/{campaign_id}/report'; }; From 9bc2ffc193968b8b4c5ccb17bb0a3622af96ac3a Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Fri, 27 Mar 2026 19:20:05 +0530 Subject: [PATCH 4/4] fix: add disposition codes in workflows --- api/db/workflow_client.py | 2 +- api/tests/test_add_call_disposition_code.py | 85 +++++++++++++++++++++ ui/src/components/flow/edges/CustomEdge.tsx | 20 ++++- 3 files changed, 104 insertions(+), 3 deletions(-) create mode 100644 api/tests/test_add_call_disposition_code.py diff --git a/api/db/workflow_client.py b/api/db/workflow_client.py index 17b67e9f..8d2d4063 100644 --- a/api/db/workflow_client.py +++ b/api/db/workflow_client.py @@ -465,7 +465,7 @@ class WorkflowClient(BaseDBClient): return existing = workflow.call_disposition_codes or {} - codes = existing.get("disposition_codes", []) + codes = list(existing.get("disposition_codes", [])) if disposition_code in codes: return diff --git a/api/tests/test_add_call_disposition_code.py b/api/tests/test_add_call_disposition_code.py new file mode 100644 index 00000000..9d04832a --- /dev/null +++ b/api/tests/test_add_call_disposition_code.py @@ -0,0 +1,85 @@ +"""Test that add_call_disposition_code correctly persists changes. + +The bug: `codes` is a reference to the list inside the JSON column value. +Calling `codes.append()` mutates the in-memory column value in-place. +When SQLAlchemy compares old vs new on commit, it sees them as equal +because the old value was already mutated — so the change is silently dropped. +""" + +import asyncio +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest + +from api.db.workflow_client import WorkflowClient + + +def _make_workflow_stub(initial_disposition_codes): + """Create a mock workflow that behaves like a SQLAlchemy model instance. + + Tracks attribute assignments so we can verify the new value is genuinely + different from the original (which is what SQLAlchemy needs to detect a change). + """ + workflow = MagicMock() + # Store the initial value and track what gets assigned + workflow.call_disposition_codes = initial_disposition_codes + workflow._assigned_values = {} + + original_setattr = type(workflow).__setattr__ + + def tracking_setattr(self, name, value): + if name == "call_disposition_codes": + self._assigned_values[name] = value + original_setattr(self, name, value) + + type(workflow).__setattr__ = tracking_setattr + return workflow + + +@pytest.fixture +def client(): + with patch("api.db.workflow_client.BaseDBClient.__init__", return_value=None): + c = WorkflowClient() + c.async_session = MagicMock() + return c + + +def test_disposition_code_new_value_is_not_same_reference(client): + """The assigned list must NOT be the same object as the original. + + If it is, SQLAlchemy won't detect the change because old == new + (the old was mutated in-place). + """ + initial_codes = {"disposition_codes": ["existing_code"]} + original_list = initial_codes["disposition_codes"] + + workflow = MagicMock() + workflow.call_disposition_codes = initial_codes + + # Mock the session and query + mock_session = AsyncMock() + mock_result = MagicMock() + mock_result.scalars.return_value.first.return_value = workflow + mock_session.execute = AsyncMock(return_value=mock_result) + mock_session.commit = AsyncMock() + mock_session.__aenter__ = AsyncMock(return_value=mock_session) + mock_session.__aexit__ = AsyncMock(return_value=False) + + client.async_session = MagicMock(return_value=mock_session) + + asyncio.get_event_loop().run_until_complete( + client.add_call_disposition_code(workflow_id=1, disposition_code="new_code") + ) + + # Verify the disposition code was added + assigned = workflow.call_disposition_codes + assert "new_code" in assigned["disposition_codes"] + + # THE CRITICAL CHECK: the list inside the assigned value must be a *different* + # object from the original list. If it's the same object, SQLAlchemy's change + # detection won't work because the "old" value was mutated in-place. + assert assigned["disposition_codes"] is not original_list, ( + "The assigned disposition_codes list is the same object as the original. " + "This means SQLAlchemy won't detect the change because the old value " + "was mutated in-place via list.append()." + ) diff --git a/ui/src/components/flow/edges/CustomEdge.tsx b/ui/src/components/flow/edges/CustomEdge.tsx index 0d2fc3c3..9965e2b2 100644 --- a/ui/src/components/flow/edges/CustomEdge.tsx +++ b/ui/src/components/flow/edges/CustomEdge.tsx @@ -36,10 +36,26 @@ const EdgeDetailsDialog = ({ open, onOpenChange, data, onSave }: EdgeDetailsDial } }, [data, open]); - const handleSave = () => { + const handleSave = useCallback(() => { onSave({ condition: condition, label: label, transition_speech: transitionSpeech || undefined }); onOpenChange(false); - }; + }, [condition, label, transitionSpeech, onSave, onOpenChange]); + + // Handle Cmd+S / Ctrl+S keyboard shortcut to save + useEffect(() => { + if (!open) return; + + const handleKeyDown = (e: KeyboardEvent) => { + if ((e.metaKey || e.ctrlKey) && e.key === 's') { + e.preventDefault(); + e.stopImmediatePropagation(); + handleSave(); + } + }; + + window.addEventListener('keydown', handleKeyDown, true); + return () => window.removeEventListener('keydown', handleKeyDown, true); + }, [open, handleSave]); return (