diff --git a/client/openapi-schema.json b/client/openapi-schema.json index 99e7b41..6ee8b14 100644 --- a/client/openapi-schema.json +++ b/client/openapi-schema.json @@ -22,6 +22,2545 @@ } } } + }, + "/ai-voicebot/api/admin/names": { + "get": { + "summary": "List Names", + "operationId": "list_names_ai_voicebot_api_admin_names_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AdminNamesResponse" + } + } + } + } + } + } + }, + "/ai-voicebot/api/admin/set_password": { + "post": { + "summary": "Set Password", + "operationId": "set_password_ai_voicebot_api_admin_set_password_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AdminSetPassword" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AdminActionResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/ai-voicebot/api/admin/clear_password": { + "post": { + "summary": "Clear Password", + "operationId": "clear_password_ai_voicebot_api_admin_clear_password_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AdminClearPassword" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AdminActionResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/ai-voicebot/api/admin/cleanup_sessions": { + "post": { + "summary": "Cleanup Sessions", + "operationId": "cleanup_sessions_ai_voicebot_api_admin_cleanup_sessions_post", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AdminActionResponse" + } + } + } + } + } + } + }, + "/ai-voicebot/api/admin/session_metrics": { + "get": { + "summary": "Session Metrics", + "operationId": "session_metrics_ai_voicebot_api_admin_session_metrics_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AdminMetricsResponse" + } + } + } + } + } + } + }, + "/ai-voicebot/api/admin/validate_sessions": { + "get": { + "summary": "Validate Sessions", + "operationId": "validate_sessions_ai_voicebot_api_admin_validate_sessions_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AdminValidationResponse" + } + } + } + } + } + } + }, + "/ai-voicebot/api/admin/cleanup_lobbies": { + "post": { + "summary": "Cleanup Lobbies", + "operationId": "cleanup_lobbies_ai_voicebot_api_admin_cleanup_lobbies_post", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AdminActionResponse" + } + } + } + } + } + } + }, + "/ai-voicebot/api/health": { + "get": { + "tags": [ + "monitoring" + ], + "summary": "Get Health Summary", + "description": "Get comprehensive health summary.\n\nReturns:\n Dict containing overall health status and component details", + "operationId": "get_health_summary_ai_voicebot_api_health_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + } + } + } + }, + "/ai-voicebot/api/session": { + "get": { + "summary": "Get Session", + "operationId": "get_session_ai_voicebot_api_session_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionResponse" + } + } + } + } + } + } + }, + "/ai-voicebot/api/lobby": { + "get": { + "summary": "List Lobbies", + "operationId": "list_lobbies_ai_voicebot_api_lobby_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LobbiesResponse" + } + } + } + } + } + } + }, + "/ai-voicebot/api/lobby/{session_id}": { + "post": { + "summary": "Create Lobby", + "operationId": "create_lobby_ai_voicebot_api_lobby__session_id__post", + "parameters": [ + { + "name": "session_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Session Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LobbyCreateRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LobbyCreateResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/ai-voicebot/api/lobby/{lobby_id}/chat": { + "get": { + "summary": "Get Chat Messages", + "operationId": "get_chat_messages_ai_voicebot_api_lobby__lobby_id__chat_get", + "parameters": [ + { + "name": "lobby_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Lobby Id" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 50, + "title": "Limit" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChatMessagesResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/ai-voicebot/api/bots/providers/register": { + "post": { + "tags": [ + "bots" + ], + "summary": "Register Bot Provider", + "description": "Register a new bot provider with authentication", + "operationId": "register_bot_provider_ai_voicebot_api_bots_providers_register_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BotProviderRegisterRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BotProviderRegisterResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/ai-voicebot/api/bots/providers": { + "get": { + "tags": [ + "bots" + ], + "summary": "List Bot Providers", + "description": "List all registered bot providers", + "operationId": "list_bot_providers_ai_voicebot_api_bots_providers_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BotProviderListResponse" + } + } + } + } + } + } + }, + "/ai-voicebot/api/bots": { + "get": { + "tags": [ + "bots" + ], + "summary": "List Available Bots", + "description": "List all available bots from all registered providers", + "operationId": "list_available_bots_ai_voicebot_api_bots_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BotListResponse" + } + } + } + } + } + } + }, + "/ai-voicebot/api/bots/{bot_name}/join": { + "post": { + "tags": [ + "bots" + ], + "summary": "Request Bot Join Lobby", + "description": "Request a bot to join a specific lobby", + "operationId": "request_bot_join_lobby_ai_voicebot_api_bots__bot_name__join_post", + "parameters": [ + { + "name": "bot_name", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Bot Name" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BotJoinLobbyRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BotJoinLobbyResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/ai-voicebot/api/bots/instances/{bot_instance_id}/leave": { + "post": { + "tags": [ + "bots" + ], + "summary": "Request Bot Leave Lobby", + "description": "Request a bot instance to leave from all lobbies and disconnect", + "operationId": "request_bot_leave_lobby_ai_voicebot_api_bots_instances__bot_instance_id__leave_post", + "parameters": [ + { + "name": "bot_instance_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Bot Instance Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BotLeaveLobbyResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/ai-voicebot/api/bots/instances/{bot_instance_id}": { + "get": { + "tags": [ + "bots" + ], + "summary": "Get Bot Instance", + "description": "Get information about a specific bot instance", + "operationId": "get_bot_instance_ai_voicebot_api_bots_instances__bot_instance_id__get", + "parameters": [ + { + "name": "bot_instance_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Bot Instance Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "title": "Response Get Bot Instance Ai Voicebot Api Bots Instances Bot Instance Id Get" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/ai-voicebot/api/bots/config/schema/{bot_name}": { + "get": { + "tags": [ + "Bot Configuration" + ], + "summary": "Get Bot Config Schema", + "description": "Get configuration schema for a specific bot", + "operationId": "get_bot_config_schema_ai_voicebot_api_bots_config_schema__bot_name__get", + "parameters": [ + { + "name": "bot_name", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Bot Name" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BotConfigSchema" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/ai-voicebot/api/bots/config/lobby/{lobby_id}": { + "get": { + "tags": [ + "Bot Configuration" + ], + "summary": "Get Lobby Bot Configs", + "description": "Get all bot configurations for a lobby", + "operationId": "get_lobby_bot_configs_ai_voicebot_api_bots_config_lobby__lobby_id__get", + "parameters": [ + { + "name": "lobby_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Lobby Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BotConfigListResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "delete": { + "tags": [ + "Bot Configuration" + ], + "summary": "Delete Lobby Configs", + "description": "Delete all bot configurations for a lobby", + "operationId": "delete_lobby_configs_ai_voicebot_api_bots_config_lobby__lobby_id__delete", + "parameters": [ + { + "name": "lobby_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Lobby Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "title": "Response Delete Lobby Configs Ai Voicebot Api Bots Config Lobby Lobby Id Delete" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/ai-voicebot/api/bots/config/lobby/{lobby_id}/bot/{bot_name}": { + "get": { + "tags": [ + "Bot Configuration" + ], + "summary": "Get Lobby Bot Config", + "description": "Get specific bot configuration for a lobby", + "operationId": "get_lobby_bot_config_ai_voicebot_api_bots_config_lobby__lobby_id__bot__bot_name__get", + "parameters": [ + { + "name": "lobby_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Lobby Id" + } + }, + { + "name": "bot_name", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Bot Name" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BotLobbyConfig" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "delete": { + "tags": [ + "Bot Configuration" + ], + "summary": "Delete Bot Config", + "description": "Delete bot configuration for a lobby", + "operationId": "delete_bot_config_ai_voicebot_api_bots_config_lobby__lobby_id__bot__bot_name__delete", + "parameters": [ + { + "name": "lobby_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Lobby Id" + } + }, + { + "name": "bot_name", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Bot Name" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "title": "Response Delete Bot Config Ai Voicebot Api Bots Config Lobby Lobby Id Bot Bot Name Delete" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/ai-voicebot/api/bots/config/update": { + "post": { + "tags": [ + "Bot Configuration" + ], + "summary": "Update Bot Config", + "description": "Update bot configuration for a lobby", + "operationId": "update_bot_config_ai_voicebot_api_bots_config_update_post", + "parameters": [ + { + "name": "session_id", + "in": "query", + "required": false, + "schema": { + "type": "string", + "default": "unknown", + "title": "Session Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BotConfigUpdateRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BotConfigUpdateResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/ai-voicebot/api/bots/config/statistics": { + "get": { + "tags": [ + "Bot Configuration" + ], + "summary": "Get Config Statistics", + "description": "Get configuration manager statistics", + "operationId": "get_config_statistics_ai_voicebot_api_bots_config_statistics_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "additionalProperties": true, + "type": "object", + "title": "Response Get Config Statistics Ai Voicebot Api Bots Config Statistics Get" + } + } + } + } + } + } + }, + "/ai-voicebot/api/bots/config/refresh-schemas": { + "post": { + "tags": [ + "Bot Configuration" + ], + "summary": "Refresh Bot Schemas", + "description": "Refresh all bot configuration schemas from providers", + "operationId": "refresh_bot_schemas_ai_voicebot_api_bots_config_refresh_schemas_post", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "additionalProperties": true, + "type": "object", + "title": "Response Refresh Bot Schemas Ai Voicebot Api Bots Config Refresh Schemas Post" + } + } + } + } + } + } + }, + "/ai-voicebot/api/bots/config/schema/{bot_name}/refresh": { + "post": { + "tags": [ + "Bot Configuration" + ], + "summary": "Refresh Bot Schema", + "description": "Refresh configuration schema for a specific bot", + "operationId": "refresh_bot_schema_ai_voicebot_api_bots_config_schema__bot_name__refresh_post", + "parameters": [ + { + "name": "bot_name", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Bot Name" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "title": "Response Refresh Bot Schema Ai Voicebot Api Bots Config Schema Bot Name Refresh Post" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/ai-voicebot/api/bots/config/schema/{bot_name}/cache": { + "delete": { + "tags": [ + "Bot Configuration" + ], + "summary": "Clear Bot Schema Cache", + "description": "Clear cached schema for a specific bot", + "operationId": "clear_bot_schema_cache_ai_voicebot_api_bots_config_schema__bot_name__cache_delete", + "parameters": [ + { + "name": "bot_name", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Bot Name" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "title": "Response Clear Bot Schema Cache Ai Voicebot Api Bots Config Schema Bot Name Cache Delete" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/ai-voicebot/api/health/ready": { + "get": { + "tags": [ + "monitoring" + ], + "summary": "Readiness Probe", + "description": "Kubernetes readiness probe endpoint.\n\nReturns:\n Ready status for load balancer inclusion", + "operationId": "readiness_probe_ai_voicebot_api_health_ready_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + } + } + } + }, + "/ai-voicebot/api/health/live": { + "get": { + "tags": [ + "monitoring" + ], + "summary": "Liveness Probe", + "description": "Kubernetes liveness probe endpoint.\n\nReturns:\n Alive status for container restart decisions", + "operationId": "liveness_probe_ai_voicebot_api_health_live_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + } + } + } + }, + "/ai-voicebot/api/metrics": { + "get": { + "tags": [ + "monitoring" + ], + "summary": "Get Current Metrics", + "description": "Get current performance metrics.\n\nReturns:\n Current system and application metrics", + "operationId": "get_current_metrics_ai_voicebot_api_metrics_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + } + } + } + }, + "/ai-voicebot/api/metrics/history": { + "get": { + "tags": [ + "monitoring" + ], + "summary": "Get Metrics History", + "description": "Get historical data for a specific metric.\n\nArgs:\n metric_name: Name of the metric\n minutes: Number of minutes of history to retrieve (1-60)\n\nReturns:\n Historical metric data points", + "operationId": "get_metrics_history_ai_voicebot_api_metrics_history_get", + "parameters": [ + { + "name": "metric_name", + "in": "query", + "required": true, + "schema": { + "type": "string", + "description": "Name of the metric to retrieve", + "title": "Metric Name" + }, + "description": "Name of the metric to retrieve" + }, + { + "name": "minutes", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "maximum": 60, + "minimum": 1, + "description": "Minutes of history to retrieve", + "default": 5, + "title": "Minutes" + }, + "description": "Minutes of history to retrieve" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/ai-voicebot/api/cache/stats": { + "get": { + "tags": [ + "monitoring" + ], + "summary": "Get Cache Statistics", + "description": "Get cache performance statistics.\n\nReturns:\n Cache hit rates, sizes, and performance metrics", + "operationId": "get_cache_statistics_ai_voicebot_api_cache_stats_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + } + } + } + }, + "/ai-voicebot/api/system/info": { + "get": { + "tags": [ + "monitoring" + ], + "summary": "Get System Info", + "description": "Get system information and configuration.\n\nReturns:\n System details, configuration, and runtime information", + "operationId": "get_system_info_ai_voicebot_api_system_info_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + } + } + } + }, + "/ai-voicebot/api/cache/clear": { + "post": { + "tags": [ + "monitoring" + ], + "summary": "Clear Cache", + "description": "Clear cache entries.\n\nArgs:\n cache_type: Optional specific cache to clear (session, lobby, user, message, computed)\n\nReturns:\n Cache clear results", + "operationId": "clear_cache_ai_voicebot_api_cache_clear_post", + "parameters": [ + { + "name": "cache_type", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Specific cache to clear", + "title": "Cache Type" + }, + "description": "Specific cache to clear" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/ai-voicebot/api/metrics/export": { + "get": { + "tags": [ + "monitoring" + ], + "summary": "Export Metrics Prometheus", + "description": "Export metrics in Prometheus format.\n\nReturns:\n Metrics in Prometheus text format", + "operationId": "export_metrics_prometheus_ai_voicebot_api_metrics_export_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + } + } + } + }, + "/ai-voicebot/{path}": { + "patch": { + "summary": "Proxy Static", + "operationId": "proxy_static_ai_voicebot__path__patch", + "parameters": [ + { + "name": "path", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Path" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "get": { + "summary": "Proxy Static", + "operationId": "proxy_static_ai_voicebot__path__patch", + "parameters": [ + { + "name": "path", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Path" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "delete": { + "summary": "Proxy Static", + "operationId": "proxy_static_ai_voicebot__path__patch", + "parameters": [ + { + "name": "path", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Path" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "options": { + "summary": "Proxy Static", + "operationId": "proxy_static_ai_voicebot__path__patch", + "parameters": [ + { + "name": "path", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Path" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "head": { + "summary": "Proxy Static", + "operationId": "proxy_static_ai_voicebot__path__patch", + "parameters": [ + { + "name": "path", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Path" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "post": { + "summary": "Proxy Static", + "operationId": "proxy_static_ai_voicebot__path__patch", + "parameters": [ + { + "name": "path", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Path" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "put": { + "summary": "Proxy Static", + "operationId": "proxy_static_ai_voicebot__path__patch", + "parameters": [ + { + "name": "path", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Path" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "AdminActionResponse": { + "properties": { + "status": { + "type": "string", + "enum": [ + "ok", + "not_found", + "error" + ], + "title": "Status" + }, + "name": { + "type": "string", + "title": "Name" + } + }, + "type": "object", + "required": [ + "status", + "name" + ], + "title": "AdminActionResponse", + "description": "Response for admin actions" + }, + "AdminClearPassword": { + "properties": { + "name": { + "type": "string", + "title": "Name" + } + }, + "type": "object", + "required": [ + "name" + ], + "title": "AdminClearPassword", + "description": "Request model for clearing admin password" + }, + "AdminMetricsConfig": { + "properties": { + "anonymous_timeout": { + "type": "integer", + "title": "Anonymous Timeout" + }, + "displaced_timeout": { + "type": "integer", + "title": "Displaced Timeout" + }, + "cleanup_interval": { + "type": "integer", + "title": "Cleanup Interval" + }, + "max_cleanup_per_cycle": { + "type": "integer", + "title": "Max Cleanup Per Cycle" + } + }, + "type": "object", + "required": [ + "anonymous_timeout", + "displaced_timeout", + "cleanup_interval", + "max_cleanup_per_cycle" + ], + "title": "AdminMetricsConfig", + "description": "Config data for metrics response" + }, + "AdminMetricsResponse": { + "properties": { + "total_sessions": { + "type": "integer", + "title": "Total Sessions" + }, + "active_sessions": { + "type": "integer", + "title": "Active Sessions" + }, + "named_sessions": { + "type": "integer", + "title": "Named Sessions" + }, + "displaced_sessions": { + "type": "integer", + "title": "Displaced Sessions" + }, + "old_anonymous_sessions": { + "type": "integer", + "title": "Old Anonymous Sessions" + }, + "old_displaced_sessions": { + "type": "integer", + "title": "Old Displaced Sessions" + }, + "total_lobbies": { + "type": "integer", + "title": "Total Lobbies" + }, + "cleanup_candidates": { + "type": "integer", + "title": "Cleanup Candidates" + }, + "config": { + "$ref": "#/components/schemas/AdminMetricsConfig" + } + }, + "type": "object", + "required": [ + "total_sessions", + "active_sessions", + "named_sessions", + "displaced_sessions", + "old_anonymous_sessions", + "old_displaced_sessions", + "total_lobbies", + "cleanup_candidates", + "config" + ], + "title": "AdminMetricsResponse", + "description": "Response for admin session metrics" + }, + "AdminNamesResponse": { + "properties": { + "name_passwords": { + "additionalProperties": { + "$ref": "#/components/schemas/NamePasswordRecord" + }, + "type": "object", + "title": "Name Passwords" + } + }, + "type": "object", + "required": [ + "name_passwords" + ], + "title": "AdminNamesResponse", + "description": "Response for admin names endpoint" + }, + "AdminSetPassword": { + "properties": { + "name": { + "type": "string", + "title": "Name" + }, + "password": { + "type": "string", + "title": "Password" + } + }, + "type": "object", + "required": [ + "name", + "password" + ], + "title": "AdminSetPassword", + "description": "Request model for setting admin password" + }, + "AdminValidationResponse": { + "properties": { + "status": { + "type": "string", + "enum": [ + "ok", + "error" + ], + "title": "Status" + }, + "issues": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Issues", + "default": [] + }, + "issue_count": { + "type": "integer", + "title": "Issue Count", + "default": 0 + }, + "error": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Error" + } + }, + "type": "object", + "required": [ + "status" + ], + "title": "AdminValidationResponse", + "description": "Response for admin session validation" + }, + "BotConfigListResponse": { + "properties": { + "lobby_id": { + "type": "string", + "title": "Lobby Id" + }, + "configs": { + "items": { + "$ref": "#/components/schemas/BotLobbyConfig" + }, + "type": "array", + "title": "Configs" + } + }, + "type": "object", + "required": [ + "lobby_id", + "configs" + ], + "title": "BotConfigListResponse", + "description": "Response listing bot configurations for a lobby" + }, + "BotConfigParameter": { + "properties": { + "name": { + "type": "string", + "title": "Name" + }, + "type": { + "type": "string", + "enum": [ + "string", + "number", + "boolean", + "select", + "range" + ], + "title": "Type" + }, + "label": { + "type": "string", + "title": "Label" + }, + "description": { + "type": "string", + "title": "Description" + }, + "default_value": { + "anyOf": [ + {}, + { + "type": "null" + } + ], + "title": "Default Value" + }, + "required": { + "type": "boolean", + "title": "Required", + "default": false + }, + "options": { + "anyOf": [ + { + "items": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Options" + }, + "min_value": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "title": "Min Value" + }, + "max_value": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "title": "Max Value" + }, + "step": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "title": "Step" + }, + "max_length": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Max Length" + }, + "pattern": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Pattern" + } + }, + "type": "object", + "required": [ + "name", + "type", + "label", + "description" + ], + "title": "BotConfigParameter", + "description": "Definition of a bot configuration parameter" + }, + "BotConfigSchema": { + "properties": { + "bot_name": { + "type": "string", + "title": "Bot Name" + }, + "version": { + "type": "string", + "title": "Version", + "default": "1.0" + }, + "parameters": { + "items": { + "$ref": "#/components/schemas/BotConfigParameter" + }, + "type": "array", + "title": "Parameters" + }, + "categories": { + "anyOf": [ + { + "items": { + "additionalProperties": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": "object" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Categories" + } + }, + "type": "object", + "required": [ + "bot_name", + "parameters" + ], + "title": "BotConfigSchema", + "description": "Schema defining all configurable parameters for a bot" + }, + "BotConfigUpdateRequest": { + "properties": { + "bot_name": { + "type": "string", + "title": "Bot Name" + }, + "lobby_id": { + "type": "string", + "title": "Lobby Id" + }, + "config_values": { + "additionalProperties": true, + "type": "object", + "title": "Config Values" + } + }, + "type": "object", + "required": [ + "bot_name", + "lobby_id", + "config_values" + ], + "title": "BotConfigUpdateRequest", + "description": "Request to update bot configuration" + }, + "BotConfigUpdateResponse": { + "properties": { + "success": { + "type": "boolean", + "title": "Success" + }, + "message": { + "type": "string", + "title": "Message" + }, + "updated_config": { + "anyOf": [ + { + "$ref": "#/components/schemas/BotLobbyConfig" + }, + { + "type": "null" + } + ] + } + }, + "type": "object", + "required": [ + "success", + "message" + ], + "title": "BotConfigUpdateResponse", + "description": "Response to bot configuration update" + }, + "BotInfoModel": { + "properties": { + "name": { + "type": "string", + "title": "Name" + }, + "description": { + "type": "string", + "title": "Description" + }, + "has_media": { + "type": "boolean", + "title": "Has Media", + "default": true + }, + "configurable": { + "type": "boolean", + "title": "Configurable", + "default": false + }, + "config_schema": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Config Schema" + } + }, + "type": "object", + "required": [ + "name", + "description" + ], + "title": "BotInfoModel", + "description": "Information about a specific bot" + }, + "BotJoinLobbyRequest": { + "properties": { + "lobby_id": { + "type": "string", + "title": "Lobby Id" + }, + "nick": { + "type": "string", + "title": "Nick", + "default": "" + }, + "provider_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Provider Id" + } + }, + "type": "object", + "required": [ + "lobby_id" + ], + "title": "BotJoinLobbyRequest", + "description": "Request to make a bot join a lobby" + }, + "BotJoinLobbyResponse": { + "properties": { + "status": { + "type": "string", + "title": "Status" + }, + "bot_instance_id": { + "type": "string", + "title": "Bot Instance Id" + }, + "bot_name": { + "type": "string", + "title": "Bot Name" + }, + "run_id": { + "type": "string", + "title": "Run Id" + }, + "provider_id": { + "type": "string", + "title": "Provider Id" + }, + "session_id": { + "type": "string", + "title": "Session Id" + } + }, + "type": "object", + "required": [ + "status", + "bot_instance_id", + "bot_name", + "run_id", + "provider_id", + "session_id" + ], + "title": "BotJoinLobbyResponse", + "description": "Response after requesting a bot to join a lobby" + }, + "BotLeaveLobbyResponse": { + "properties": { + "status": { + "type": "string", + "title": "Status" + }, + "bot_instance_id": { + "type": "string", + "title": "Bot Instance Id" + }, + "session_id": { + "type": "string", + "title": "Session Id" + }, + "run_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Run Id" + } + }, + "type": "object", + "required": [ + "status", + "bot_instance_id", + "session_id" + ], + "title": "BotLeaveLobbyResponse", + "description": "Response after requesting a bot to leave a lobby" + }, + "BotListResponse": { + "properties": { + "bots": { + "items": { + "$ref": "#/components/schemas/BotInfoModel" + }, + "type": "array", + "title": "Bots" + }, + "providers": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "title": "Providers" + } + }, + "type": "object", + "required": [ + "bots", + "providers" + ], + "title": "BotListResponse", + "description": "Response listing all available bots from all providers" + }, + "BotLobbyConfig": { + "properties": { + "bot_name": { + "type": "string", + "title": "Bot Name" + }, + "lobby_id": { + "type": "string", + "title": "Lobby Id" + }, + "provider_id": { + "type": "string", + "title": "Provider Id" + }, + "config_values": { + "additionalProperties": true, + "type": "object", + "title": "Config Values" + }, + "created_at": { + "type": "number", + "title": "Created At" + }, + "updated_at": { + "type": "number", + "title": "Updated At" + }, + "created_by": { + "type": "string", + "title": "Created By" + } + }, + "type": "object", + "required": [ + "bot_name", + "lobby_id", + "provider_id", + "config_values", + "created_at", + "updated_at", + "created_by" + ], + "title": "BotLobbyConfig", + "description": "Bot configuration for a specific lobby" + }, + "BotProviderListResponse": { + "properties": { + "providers": { + "items": { + "$ref": "#/components/schemas/BotProviderModel" + }, + "type": "array", + "title": "Providers" + } + }, + "type": "object", + "required": [ + "providers" + ], + "title": "BotProviderListResponse", + "description": "Response listing all registered bot providers" + }, + "BotProviderModel": { + "properties": { + "provider_id": { + "type": "string", + "title": "Provider Id" + }, + "base_url": { + "type": "string", + "title": "Base Url" + }, + "name": { + "type": "string", + "title": "Name" + }, + "description": { + "type": "string", + "title": "Description", + "default": "" + }, + "provider_key": { + "type": "string", + "title": "Provider Key" + }, + "registered_at": { + "type": "number", + "title": "Registered At" + }, + "last_seen": { + "type": "number", + "title": "Last Seen" + } + }, + "type": "object", + "required": [ + "provider_id", + "base_url", + "name", + "provider_key", + "registered_at", + "last_seen" + ], + "title": "BotProviderModel", + "description": "Bot provider registration information" + }, + "BotProviderRegisterRequest": { + "properties": { + "base_url": { + "type": "string", + "title": "Base Url" + }, + "name": { + "type": "string", + "title": "Name" + }, + "description": { + "type": "string", + "title": "Description", + "default": "" + }, + "provider_key": { + "type": "string", + "title": "Provider Key" + } + }, + "type": "object", + "required": [ + "base_url", + "name", + "provider_key" + ], + "title": "BotProviderRegisterRequest", + "description": "Request to register a bot provider" + }, + "BotProviderRegisterResponse": { + "properties": { + "provider_id": { + "type": "string", + "title": "Provider Id" + }, + "status": { + "type": "string", + "title": "Status", + "default": "registered" + } + }, + "type": "object", + "required": [ + "provider_id" + ], + "title": "BotProviderRegisterResponse", + "description": "Response after registering a bot provider" + }, + "ChatMessageModel": { + "properties": { + "id": { + "type": "string", + "title": "Id" + }, + "message": { + "type": "string", + "title": "Message" + }, + "sender_name": { + "type": "string", + "title": "Sender Name" + }, + "sender_session_id": { + "type": "string", + "title": "Sender Session Id" + }, + "timestamp": { + "type": "number", + "title": "Timestamp" + }, + "lobby_id": { + "type": "string", + "title": "Lobby Id" + } + }, + "type": "object", + "required": [ + "id", + "message", + "sender_name", + "sender_session_id", + "timestamp", + "lobby_id" + ], + "title": "ChatMessageModel", + "description": "Chat message model" + }, + "ChatMessagesResponse": { + "properties": { + "messages": { + "items": { + "$ref": "#/components/schemas/ChatMessageModel" + }, + "type": "array", + "title": "Messages" + } + }, + "type": "object", + "required": [ + "messages" + ], + "title": "ChatMessagesResponse", + "description": "Response containing chat messages" + }, + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail" + } + }, + "type": "object", + "title": "HTTPValidationError" + }, + "HealthResponse": { + "properties": { + "status": { + "type": "string", + "title": "Status" + } + }, + "type": "object", + "required": [ + "status" + ], + "title": "HealthResponse", + "description": "Health check response" + }, + "LobbiesResponse": { + "properties": { + "lobbies": { + "items": { + "$ref": "#/components/schemas/LobbyListItem" + }, + "type": "array", + "title": "Lobbies" + } + }, + "type": "object", + "required": [ + "lobbies" + ], + "title": "LobbiesResponse", + "description": "Response containing list of lobbies" + }, + "LobbyCreateData": { + "properties": { + "name": { + "type": "string", + "title": "Name" + }, + "private": { + "type": "boolean", + "title": "Private", + "default": false + } + }, + "type": "object", + "required": [ + "name" + ], + "title": "LobbyCreateData", + "description": "Data for lobby creation" + }, + "LobbyCreateRequest": { + "properties": { + "type": { + "type": "string", + "const": "lobby_create", + "title": "Type" + }, + "data": { + "$ref": "#/components/schemas/LobbyCreateData" + } + }, + "type": "object", + "required": [ + "type", + "data" + ], + "title": "LobbyCreateRequest", + "description": "Request for creating a lobby" + }, + "LobbyCreateResponse": { + "properties": { + "type": { + "type": "string", + "const": "lobby_created", + "title": "Type" + }, + "data": { + "$ref": "#/components/schemas/LobbyModel" + } + }, + "type": "object", + "required": [ + "type", + "data" + ], + "title": "LobbyCreateResponse", + "description": "Response for lobby creation" + }, + "LobbyListItem": { + "properties": { + "id": { + "type": "string", + "title": "Id" + }, + "name": { + "type": "string", + "title": "Name" + } + }, + "type": "object", + "required": [ + "id", + "name" + ], + "title": "LobbyListItem", + "description": "Lobby item for list responses" + }, + "LobbyModel": { + "properties": { + "id": { + "type": "string", + "title": "Id" + }, + "name": { + "type": "string", + "title": "Name" + }, + "private": { + "type": "boolean", + "title": "Private", + "default": false + } + }, + "type": "object", + "required": [ + "id", + "name" + ], + "title": "LobbyModel", + "description": "Core lobby model used across components" + }, + "NamePasswordRecord": { + "properties": { + "salt": { + "type": "string", + "title": "Salt" + }, + "hash": { + "type": "string", + "title": "Hash" + } + }, + "type": "object", + "required": [ + "salt", + "hash" + ], + "title": "NamePasswordRecord", + "description": "Password hash record for reserved names" + }, + "SessionResponse": { + "properties": { + "id": { + "type": "string", + "title": "Id" + }, + "name": { + "type": "string", + "title": "Name" + }, + "lobbies": { + "items": { + "$ref": "#/components/schemas/LobbyModel" + }, + "type": "array", + "title": "Lobbies" + }, + "protected": { + "type": "boolean", + "title": "Protected", + "default": false + }, + "has_media": { + "type": "boolean", + "title": "Has Media", + "default": false + }, + "bot_run_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Bot Run Id" + }, + "bot_provider_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Bot Provider Id" + }, + "bot_instance_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Bot Instance Id" + } + }, + "type": "object", + "required": [ + "id", + "name", + "lobbies" + ], + "title": "SessionResponse", + "description": "Session response model" + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + } + ] + }, + "type": "array", + "title": "Location" + }, + "msg": { + "type": "string", + "title": "Message" + }, + "type": { + "type": "string", + "title": "Error Type" + } + }, + "type": "object", + "required": [ + "loc", + "msg", + "type" + ], + "title": "ValidationError" + } } } } \ No newline at end of file diff --git a/client/src/BotManager.tsx b/client/src/BotManager.tsx index 0b50d50..65f3f87 100644 --- a/client/src/BotManager.tsx +++ b/client/src/BotManager.tsx @@ -138,7 +138,7 @@ const BotManager: React.FC = ({ lobbyId, onBotAdded, sx }) => { }; const isBotConfigurable = (bot: BotInfoModel): boolean => { - return Boolean(bot.configurable) || Boolean(bot.features && bot.features.includes("per_lobby_config")); + return Boolean(bot.configurable); }; const botCount = bots.length; diff --git a/client/src/UserList.tsx b/client/src/UserList.tsx index 8e67ffb..7706a4a 100644 --- a/client/src/UserList.tsx +++ b/client/src/UserList.tsx @@ -13,7 +13,7 @@ import { MediaControl, MediaAgent, Peer } from "./MediaControl"; import Box from "@mui/material/Box"; import { Session } from "./GlobalContext"; import useWebSocket from "react-use-websocket"; -import { ApiClient, BotLeaveLobbyRequest } from "./api-client"; +import { ApiClient } from "./api-client"; import BotConfig from "./BotConfig"; type User = { @@ -53,7 +53,7 @@ const UserList: React.FC = (props: UserListProps) => { setLeavingBots((prev) => new Set(prev).add(user.session_id)); try { - await apiClient.requestBotLeaveLobby(user.bot_instance_id); + await apiClient.createRequestBotLeaveLobby(user.bot_instance_id); console.log(`Bot ${user.name} leave requested successfully`); } catch (error) { console.error("Failed to request bot leave:", error); diff --git a/client/src/api-client.ts b/client/src/api-client.ts index 1b16f40..0151198 100644 --- a/client/src/api-client.ts +++ b/client/src/api-client.ts @@ -2,96 +2,48 @@ import { components } from "./api-types"; import { base } from "./Common"; -// Re-export commonly used types from the generated schema -export type LobbyModel = components["schemas"]["LobbyModel"]; -export type LobbyListItem = components["schemas"]["LobbyListItem"]; -export type LobbyCreateData = components["schemas"]["LobbyCreateData"]; -export type NamePasswordRecord = components["schemas"]["NamePasswordRecord"]; +// DO NOT MANUALLY EDIT BELOW THIS LINE - All content below is auto-generated +// To modify auto-generated content, edit the template in client/update-api-client.js -// Type aliases for API methods -export type AdminNamesResponse = components["schemas"]["AdminNamesResponse"]; +// Re-export all types from the generated schema +export type { components } from "./api-types"; + +// Re-export all types from the generated schema for convenience export type AdminActionResponse = components["schemas"]["AdminActionResponse"]; -export type AdminSetPassword = components["schemas"]["AdminSetPassword"]; export type AdminClearPassword = components["schemas"]["AdminClearPassword"]; +export type AdminMetricsConfig = components["schemas"]["AdminMetricsConfig"]; +export type AdminMetricsResponse = components["schemas"]["AdminMetricsResponse"]; +export type AdminNamesResponse = components["schemas"]["AdminNamesResponse"]; +export type AdminSetPassword = components["schemas"]["AdminSetPassword"]; +export type AdminValidationResponse = components["schemas"]["AdminValidationResponse"]; +export type BotConfigListResponse = components["schemas"]["BotConfigListResponse"]; +export type BotConfigParameter = components["schemas"]["BotConfigParameter"]; +export type BotConfigSchema = components["schemas"]["BotConfigSchema"]; +export type BotConfigUpdateRequest = components["schemas"]["BotConfigUpdateRequest"]; +export type BotConfigUpdateResponse = components["schemas"]["BotConfigUpdateResponse"]; +export type BotInfoModel = components["schemas"]["BotInfoModel"]; +export type BotJoinLobbyRequest = components["schemas"]["BotJoinLobbyRequest"]; +export type BotJoinLobbyResponse = components["schemas"]["BotJoinLobbyResponse"]; +export type BotLeaveLobbyResponse = components["schemas"]["BotLeaveLobbyResponse"]; +export type BotListResponse = components["schemas"]["BotListResponse"]; +export type BotLobbyConfig = components["schemas"]["BotLobbyConfig"]; +export type BotProviderListResponse = components["schemas"]["BotProviderListResponse"]; +export type BotProviderModel = components["schemas"]["BotProviderModel"]; +export type BotProviderRegisterRequest = components["schemas"]["BotProviderRegisterRequest"]; +export type BotProviderRegisterResponse = components["schemas"]["BotProviderRegisterResponse"]; +export type ChatMessageModel = components["schemas"]["ChatMessageModel"]; +export type ChatMessagesResponse = components["schemas"]["ChatMessagesResponse"]; +export type HTTPValidationError = components["schemas"]["HTTPValidationError"]; export type HealthResponse = components["schemas"]["HealthResponse"]; export type LobbiesResponse = components["schemas"]["LobbiesResponse"]; -export type SessionResponse = components["schemas"]["SessionResponse"]; +export type LobbyCreateData = components["schemas"]["LobbyCreateData"]; export type LobbyCreateRequest = components["schemas"]["LobbyCreateRequest"]; export type LobbyCreateResponse = components["schemas"]["LobbyCreateResponse"]; - -// Bot Provider Types (manually defined until API types are regenerated) -export interface BotInfoModel { - name: string; - description: string; - configurable?: boolean; - features?: string[]; -} - -export interface BotProviderModel { - provider_id: string; - base_url: string; - name: string; - description: string; - registered_at: number; - last_seen: number; -} - -export interface BotProviderListResponse { - providers: BotProviderModel[]; -} - -export interface BotListResponse { - bots: BotInfoModel[]; - providers: Record; -} - -export interface BotJoinLobbyRequest { - lobby_id: string; - nick?: string; - provider_id?: string; -} - -export interface BotJoinLobbyResponse { - status: string; - bot_instance_id: string; - bot_name: string; - run_id: string; - provider_id: string; - session_id: string; -} - -export interface BotInstanceModel { - bot_instance_id: string; - bot_name: string; - nick: string; - lobby_id: string; - session_id: string; - provider_id: string; - run_id: string; - has_media: boolean; - created_at: number; -} - -export interface BotLeaveLobbyRequest { - bot_instance_id: string; -} - -export interface BotLeaveLobbyResponse { - status: string; - bot_instance_id: string; - session_id: string; - run_id?: string; -} - -export interface BotLeaveLobbyRequest { - session_id: string; -} - -export interface BotLeaveLobbyResponse { - status: string; - session_id: string; - run_id?: string; -} +export type LobbyListItem = components["schemas"]["LobbyListItem"]; +export type LobbyModel = components["schemas"]["LobbyModel"]; +export type NamePasswordRecord = components["schemas"]["NamePasswordRecord"]; +export type SessionResponse = components["schemas"]["SessionResponse"]; +export type ValidationError = components["schemas"]["ValidationError"]; export class ApiError extends Error { constructor(public status: number, public statusText: string, public data?: any) { @@ -176,342 +128,219 @@ export class ApiClient { return response.text() as unknown as T; } - // Admin API methods - async adminListNames(): Promise { - return this.request(this.getApiPath("/ai-voicebot/api/admin/names"), { method: "GET" }); + // Auto-generated endpoint methods + async getSystemHealth(): Promise { + return this.request(this.getApiPath(`/ai-voicebot/api/system/health`), { method: "GET" }); } - async adminSetPassword(data: AdminSetPassword): Promise { - return this.request(this.getApiPath("/ai-voicebot/api/admin/set_password"), { - method: "POST", - body: data, - }); + async getListNames(): Promise { + return this.request(this.getApiPath(`/ai-voicebot/api/admin/names`), { method: "GET" }); } - async adminClearPassword(data: AdminClearPassword): Promise { - return this.request(this.getApiPath("/ai-voicebot/api/admin/clear_password"), { - method: "POST", - body: data, - }); + async createSetPassword(data: any): Promise { + return this.request(this.getApiPath(`/ai-voicebot/api/admin/set_password`), { method: "POST", body: data }); } - // Health check - async healthCheck(): Promise { - return this.request(this.getApiPath("/ai-voicebot/api/health"), { method: "GET" }); + async createClearPassword(data: any): Promise { + return this.request(this.getApiPath(`/ai-voicebot/api/admin/clear_password`), { method: "POST", body: data }); } - // Session methods - async getSession(): Promise { - return this.request(this.getApiPath("/ai-voicebot/api/session"), { method: "GET" }); + async createCleanupSessions(): Promise { + return this.request(this.getApiPath(`/ai-voicebot/api/admin/cleanup_sessions`), { method: "POST" }); } - // Lobby methods - async getLobbies(): Promise { - return this.request(this.getApiPath("/ai-voicebot/api/lobby"), { method: "GET" }); + async getSessionMetrics(): Promise { + return this.request(this.getApiPath(`/ai-voicebot/api/admin/session_metrics`), { method: "GET" }); } - async createLobby(sessionId: string, data: LobbyCreateRequest): Promise { - return this.request(this.getApiPath(`/ai-voicebot/api/lobby/${sessionId}`), { - method: "POST", - body: data, - }); + async getValidateSessions(): Promise { + return this.request(this.getApiPath(`/ai-voicebot/api/admin/validate_sessions`), { method: "GET" }); } - // Bot Provider methods - async getBotProviders(): Promise { - return this.request(this.getApiPath("/ai-voicebot/api/bots/providers"), { method: "GET" }); + async createCleanupLobbies(): Promise { + return this.request(this.getApiPath(`/ai-voicebot/api/admin/cleanup_lobbies`), { method: "POST" }); } - async getAvailableBots(): Promise { - return this.request(this.getApiPath("/ai-voicebot/api/bots"), { method: "GET" }); + async getHealthSummary(): Promise { + return this.request(this.getApiPath(`/ai-voicebot/api/health`), { method: "GET" }); } - async requestBotJoinLobby(botName: string, request: BotJoinLobbyRequest): Promise { - return this.request( - this.getApiPath(`/ai-voicebot/api/bots/${encodeURIComponent(botName)}/join`), - { - method: "POST", - body: request, - } - ); + async getSession(): Promise { + return this.request(this.getApiPath(`/ai-voicebot/api/session`), { method: "GET" }); } - async requestBotLeaveLobby(botInstanceId: string): Promise { - return this.request( - this.getApiPath(`/ai-voicebot/api/bots/instances/${encodeURIComponent(botInstanceId)}/leave`), - { - method: "POST", - } - ); + async getListLobbies(): Promise { + return this.request(this.getApiPath(`/ai-voicebot/api/lobby`), { method: "GET" }); } - // Auto-generated endpoints will be added here by update-api-client.js - // DO NOT MANUALLY EDIT BELOW THIS LINE - - // Auto-generated endpoints - async systemHealth(): Promise { - return this.request(this.getApiPath("/ai-voicebot/api/system/health"), { method: "GET" }); - } - - // Auto-generated endpoints - async lobbyCreate(session_id: string, data: any): Promise { + async createLobby(session_id: string, data: any): Promise { return this.request(this.getApiPath(`/ai-voicebot/api/lobby/${session_id}`), { method: "POST", body: data }); } -} -// API Evolution Detection System -interface ApiEndpoint { - path: string; - method: string; - implemented: boolean; -} - -class ApiEvolutionChecker { - private static instance: ApiEvolutionChecker; - private checkedOnce = false; - - static getInstance(): ApiEvolutionChecker { - if (!this.instance) { - this.instance = new ApiEvolutionChecker(); - } - return this.instance; + async getChatMessages(lobby_id: string, params?: Record): Promise { + return this.request(this.getApiPath(`/ai-voicebot/api/lobby/${lobby_id}/chat`), { method: "GET", params }); } - private getImplementedEndpoints(): Set { - // Define all endpoints that are currently implemented in ApiClient - // This list is automatically updated by update-api-client.js - return new Set([ - 'GET:/ai-voicebot/api/admin/names', - 'GET:/ai-voicebot/api/bots', - 'GET:/ai-voicebot/api/bots/providers', - 'GET:/ai-voicebot/api/health', - 'GET:/ai-voicebot/api/lobby', - 'GET:/ai-voicebot/api/session', - 'GET:/ai-voicebot/api/system/health', - 'POST:/ai-voicebot/api/admin/clear_password', - 'POST:/ai-voicebot/api/admin/set_password', - 'POST:/ai-voicebot/api/lobby/{sessionId}', - 'POST:/ai-voicebot/api/lobby/{session_id}' - ]); + async createRegisterBotProvider(data: any): Promise { + return this.request(this.getApiPath(`/ai-voicebot/api/bots/providers/register`), { method: "POST", body: data }); } - private getAvailableEndpoints(): ApiEndpoint[] { - // Extract all endpoints from the generated paths type - const endpoints: ApiEndpoint[] = []; - const implementedSet = this.getImplementedEndpoints(); - - // Type-safe extraction of paths from the generated schema - const pathKeys = [ - '/ai-voicebot/api/admin/names', - '/ai-voicebot/api/admin/set_password', - '/ai-voicebot/api/admin/clear_password', - '/ai-voicebot/api/health', - '/ai-voicebot/api/session', - '/ai-voicebot/api/lobby', - '/ai-voicebot/api/lobby/{session_id}', - '/ai-voicebot/{path}' // Generic catch-all, we'll skip this one - ] as const; - - pathKeys.forEach(path => { - if (path === '/ai-voicebot/{path}') { - // Skip the generic proxy endpoint - return; - } - - // Check each HTTP method that might be available for this path - const possibleMethods = ['get', 'post', 'put', 'delete', 'patch'] as const; - - possibleMethods.forEach(method => { - const endpointKey = `${method.toUpperCase()}:${path}`; - const implemented = implementedSet.has(endpointKey); - - // We can't directly check if the method exists at runtime due to TypeScript compilation, - // but we know from our grep search what exists. Let's be more explicit: - const knownEndpoints: Record = { - '/ai-voicebot/api/admin/names': ['GET'], - '/ai-voicebot/api/admin/set_password': ['POST'], - '/ai-voicebot/api/admin/clear_password': ['POST'], - '/ai-voicebot/api/health': ['GET'], - '/ai-voicebot/api/session': ['GET'], - '/ai-voicebot/api/lobby': ['GET'], - '/ai-voicebot/api/lobby/{session_id}': ['POST'] - }; - - const pathMethods = knownEndpoints[path]; - if (pathMethods && pathMethods.includes(method.toUpperCase())) { - endpoints.push({ - path, - method: method.toUpperCase(), - implemented - }); - } - }); - }); - - return endpoints; + async getListBotProviders(): Promise { + return this.request(this.getApiPath(`/ai-voicebot/api/bots/providers`), { method: "GET" }); } - checkForNewEndpoints(): void { - if (this.checkedOnce) { - return; // Only check once per session to avoid spam - } - - this.checkedOnce = true; - - try { - const endpoints = this.getAvailableEndpoints(); - const unimplemented = endpoints.filter(ep => !ep.implemented); - - if (unimplemented.length > 0) { - console.group('🚨 API Evolution Warning'); - console.warn( - `Found ${unimplemented.length} API endpoints that are not implemented in ApiClient:` - ); - - unimplemented.forEach(endpoint => { - console.warn(` • ${endpoint.method} ${endpoint.path}`); - }); - - console.warn('Consider updating api-client.ts to implement these endpoints.'); - console.warn('Available types can be found in api-types.ts'); - console.groupEnd(); - } else { - console.info('āœ… All available API endpoints are implemented in ApiClient'); - } - - // Also check for potential parameter changes by looking at method signatures - this.checkForParameterChanges(); - - } catch (error) { - console.warn('Failed to check for API evolution:', error); - } + async getListAvailableBots(): Promise { + return this.request(this.getApiPath(`/ai-voicebot/api/bots`), { method: "GET" }); } - private checkForParameterChanges(): void { - // This is a simpler check - we could extend this to compare parameter schemas - // For now, we'll just remind developers to check if their implementation matches the schema - console.info('šŸ’” Tip: Regularly compare your method implementations with the generated types in api-types.ts to ensure parameter compatibility'); + async createRequestBotJoinLobby(bot_name: string, data: any): Promise { + return this.request(this.getApiPath(`/ai-voicebot/api/bots/${bot_name}/join`), { method: "POST", body: data }); } - // Method to manually trigger a fresh check (useful for development) - recheckEndpoints(): void { - this.checkedOnce = false; - this.checkForNewEndpoints(); + async createRequestBotLeaveLobby(bot_instance_id: string): Promise { + return this.request(this.getApiPath(`/ai-voicebot/api/bots/instances/${bot_instance_id}/leave`), { method: "POST" }); + } + + async getBotInstance(bot_instance_id: string): Promise { + return this.request(this.getApiPath(`/ai-voicebot/api/bots/instances/${bot_instance_id}`), { method: "GET" }); + } + + async getBotConfigSchema(bot_name: string): Promise { + return this.request(this.getApiPath(`/ai-voicebot/api/bots/config/schema/${bot_name}`), { method: "GET" }); + } + + async getLobbyBotConfigs(lobby_id: string): Promise { + return this.request(this.getApiPath(`/ai-voicebot/api/bots/config/lobby/${lobby_id}`), { method: "GET" }); + } + + async deleteLobbyConfigs(lobby_id: string): Promise { + return this.request(this.getApiPath(`/ai-voicebot/api/bots/config/lobby/${lobby_id}`), { method: "DELETE" }); + } + + async getLobbyBotConfig(lobby_id: string, bot_name: string): Promise { + return this.request(this.getApiPath(`/ai-voicebot/api/bots/config/lobby/${lobby_id}/bot/${bot_name}`), { method: "GET" }); + } + + async deleteBotConfig(lobby_id: string, bot_name: string): Promise { + return this.request(this.getApiPath(`/ai-voicebot/api/bots/config/lobby/${lobby_id}/bot/${bot_name}`), { method: "DELETE" }); + } + + async createUpdateBotConfig(data: any, params?: Record): Promise { + return this.request(this.getApiPath(`/ai-voicebot/api/bots/config/update`), { method: "POST", body: data, params }); + } + + async getConfigStatistics(): Promise { + return this.request(this.getApiPath(`/ai-voicebot/api/bots/config/statistics`), { method: "GET" }); + } + + async createRefreshBotSchemas(): Promise { + return this.request(this.getApiPath(`/ai-voicebot/api/bots/config/refresh-schemas`), { method: "POST" }); + } + + async createRefreshBotSchema(bot_name: string): Promise { + return this.request(this.getApiPath(`/ai-voicebot/api/bots/config/schema/${bot_name}/refresh`), { method: "POST" }); + } + + async deleteClearBotSchemaCache(bot_name: string): Promise { + return this.request(this.getApiPath(`/ai-voicebot/api/bots/config/schema/${bot_name}/cache`), { method: "DELETE" }); + } + + async getReadinessProbe(): Promise { + return this.request(this.getApiPath(`/ai-voicebot/api/health/ready`), { method: "GET" }); + } + + async getLivenessProbe(): Promise { + return this.request(this.getApiPath(`/ai-voicebot/api/health/live`), { method: "GET" }); + } + + async getCurrentMetrics(): Promise { + return this.request(this.getApiPath(`/ai-voicebot/api/metrics`), { method: "GET" }); + } + + async getMetricsHistory(params?: Record): Promise { + return this.request(this.getApiPath(`/ai-voicebot/api/metrics/history`), { method: "GET", params }); + } + + async getCacheStatistics(): Promise { + return this.request(this.getApiPath(`/ai-voicebot/api/cache/stats`), { method: "GET" }); + } + + async getSystemInfo(): Promise { + return this.request(this.getApiPath(`/ai-voicebot/api/system/info`), { method: "GET" }); + } + + async createClearCache(params?: Record): Promise { + return this.request(this.getApiPath(`/ai-voicebot/api/cache/clear`), { method: "POST", params }); + } + + async getExportMetricsPrometheus(): Promise { + return this.request(this.getApiPath(`/ai-voicebot/api/metrics/export`), { method: "GET" }); + } + + async updateProxyStatic(path: string): Promise { + return this.request(this.getApiPath(`/ai-voicebot/${path}`), { method: "PATCH" }); + } + + async getProxyStatic(path: string): Promise { + return this.request(this.getApiPath(`/ai-voicebot/${path}`), { method: "GET" }); + } + + async deleteProxyStatic(path: string): Promise { + return this.request(this.getApiPath(`/ai-voicebot/${path}`), { method: "DELETE" }); + } + + async optionsProxyStatic(path: string): Promise { + return this.request(this.getApiPath(`/ai-voicebot/${path}`), { method: "OPTIONS" }); + } + + async headProxyStatic(path: string): Promise { + return this.request(this.getApiPath(`/ai-voicebot/${path}`), { method: "HEAD" }); + } + + async createProxyStatic(path: string): Promise { + return this.request(this.getApiPath(`/ai-voicebot/${path}`), { method: "POST" }); + } + + async replaceProxyStatic(path: string): Promise { + return this.request(this.getApiPath(`/ai-voicebot/${path}`), { method: "PUT" }); } } -// Export the checker for manual use if needed -export const apiEvolutionChecker = ApiEvolutionChecker.getInstance(); - -// Utility functions for development -export const devUtils = { - /** - * Manually check for API evolution and log results - */ - async checkApiEvolution() { - if (process.env.NODE_ENV !== 'development') { - console.warn('API evolution checking is only available in development mode'); - return; - } - - try { - const { advancedApiChecker } = await import('./api-evolution-checker'); - const evolution = await advancedApiChecker.checkSchemaEvolution(); - - console.group('šŸ” Manual API Evolution Check'); - console.log('Unimplemented endpoints:', evolution.unimplementedEndpoints.length); - console.log('New endpoints:', evolution.newEndpoints.length); - console.log('Schema changed:', evolution.hasChangedEndpoints); - - if (evolution.unimplementedEndpoints.length > 0) { - console.log('\nImplementation stubs:'); - console.log(advancedApiChecker.generateImplementationStubs(evolution.unimplementedEndpoints)); - } - - console.groupEnd(); - return evolution; - } catch (error) { - console.error('Failed to check API evolution:', error); - return null; - } - }, - - /** - * Force a recheck of endpoints (bypasses the "once per session" limitation) - */ - recheckEndpoints() { - apiEvolutionChecker.recheckEndpoints(); - } -}; - // Default client instance export const apiClient = new ApiClient(); -// Convenience API namespaces +// Convenience API namespaces for easy usage export const adminApi = { - listNames: () => apiClient.adminListNames(), - setPassword: (data: AdminSetPassword) => apiClient.adminSetPassword(data), - clearPassword: (data: AdminClearPassword) => apiClient.adminClearPassword(data), + listNames: () => apiClient.getListNames(), + setPassword: (data: any) => apiClient.createSetPassword(data), + clearPassword: (data: any) => apiClient.createClearPassword(data), + cleanupSessions: () => apiClient.createCleanupSessions(), + sessionMetrics: () => apiClient.getSessionMetrics(), + validateSessions: () => apiClient.getValidateSessions(), + cleanupLobbies: () => apiClient.createCleanupLobbies(), +}; + +export const healthApi = { + check: () => apiClient.getHealthSummary(), + ready: () => apiClient.getReadinessProbe(), + live: () => apiClient.getLivenessProbe(), +}; + +export const lobbiesApi = { + getAll: () => apiClient.getListLobbies(), + getChatMessages: (lobbyId: string, params?: Record) => apiClient.getChatMessages(lobbyId, params), }; -export const healthApi = { check: () => apiClient.healthCheck() }; -export const lobbiesApi = { getAll: () => apiClient.getLobbies() }; export const sessionsApi = { getCurrent: () => apiClient.getSession(), - createLobby: (sessionId: string, data: LobbyCreateRequest) => apiClient.createLobby(sessionId, data), + createLobby: (sessionId: string, data: any) => apiClient.createLobby(sessionId, data), }; export const botsApi = { - getProviders: () => apiClient.getBotProviders(), - getAvailable: () => apiClient.getAvailableBots(), - requestJoinLobby: (botName: string, request: BotJoinLobbyRequest) => apiClient.requestBotJoinLobby(botName, request), + getProviders: () => apiClient.getListBotProviders(), + getAvailable: () => apiClient.getListAvailableBots(), + requestJoinLobby: (botName: string, request: any) => apiClient.createRequestBotJoinLobby(botName, request), + requestLeaveLobby: (botInstanceId: string) => apiClient.createRequestBotLeaveLobby(botInstanceId), + getInstance: (botInstanceId: string) => apiClient.getBotInstance(botInstanceId), + registerProvider: (data: any) => apiClient.createRegisterBotProvider(data), }; - -// Automatically check for API evolution when this module is loaded -// This will warn developers if new endpoints are available but not implemented -if (process.env.NODE_ENV === 'development') { - // Import the advanced checker dynamically to avoid circular dependencies - import('./api-evolution-checker').then(({ advancedApiChecker }) => { - // Run the check after a short delay to ensure all modules are loaded - setTimeout(async () => { - try { - const evolution = await advancedApiChecker.checkSchemaEvolution(); - - if (evolution.unimplementedEndpoints.length > 0 || evolution.hasNewEndpoints) { - console.group('🚨 API Evolution Detection'); - - if (evolution.hasNewEndpoints && evolution.newEndpoints.length > 0) { - console.warn('šŸ†• New API endpoints detected:'); - evolution.newEndpoints.forEach(ep => { - console.warn(` • ${ep.method} ${ep.path} (${ep.operationId})`); - }); - } - - if (evolution.unimplementedEndpoints.length > 0) { - console.warn('āš ļø Unimplemented API endpoints:'); - evolution.unimplementedEndpoints.forEach(ep => { - console.warn(` • ${ep.method} ${ep.path}`); - }); - } - - if (evolution.hasChangedEndpoints) { - console.warn('šŸ”„ API schema has changed - check for parameter updates'); - } - - console.groupCollapsed('šŸ’” Implementation suggestions:'); - console.log('Add these methods to ApiClient:'); - console.log(advancedApiChecker.generateImplementationStubs(evolution.unimplementedEndpoints)); - console.groupEnd(); - - console.groupEnd(); - } else { - console.info('āœ… All API endpoints are implemented'); - } - } catch (error) { - console.warn('API evolution check failed:', error); - // Fallback to simple checker - apiEvolutionChecker.checkForNewEndpoints(); - } - }, 1000); - }); -} diff --git a/client/src/api-evolution-checker.ts b/client/src/api-evolution-checker.ts index 5d249ac..ad4d74e 100644 --- a/client/src/api-evolution-checker.ts +++ b/client/src/api-evolution-checker.ts @@ -1,201 +1,74 @@ -// Advanced API Evolution Detection utilities -// This module provides runtime introspection of OpenAPI schema changes +// Auto-generated API evolution checker +// This file tracks known API endpoints to detect changes -import { paths } from './api-types'; -import { base } from "./Common"; +import { base } from './Common'; -export interface EndpointInfo { - path: string; - method: string; - operationId?: string; - implemented: boolean; - hasParameterChanges?: boolean; +export const knownEndpoints = new Set([ + `DELETE:${base}/api/bots/config/lobby/{lobby_id}`, + `DELETE:${base}/api/bots/config/lobby/{lobby_id}/bot/{bot_name}`, + `DELETE:${base}/api/bots/config/schema/{bot_name}/cache`, + `DELETE:${base}/{path}`, + `GET:${base}/api/admin/names`, + `GET:${base}/api/admin/session_metrics`, + `GET:${base}/api/admin/validate_sessions`, + `GET:${base}/api/bots`, + `GET:${base}/api/bots/config/lobby/{lobby_id}`, + `GET:${base}/api/bots/config/lobby/{lobby_id}/bot/{bot_name}`, + `GET:${base}/api/bots/config/schema/{bot_name}`, + `GET:${base}/api/bots/config/statistics`, + `GET:${base}/api/bots/instances/{bot_instance_id}`, + `GET:${base}/api/bots/providers`, + `GET:${base}/api/cache/stats`, + `GET:${base}/api/health`, + `GET:${base}/api/health/live`, + `GET:${base}/api/health/ready`, + `GET:${base}/api/lobby`, + `GET:${base}/api/lobby/{lobby_id}/chat`, + `GET:${base}/api/metrics`, + `GET:${base}/api/metrics/export`, + `GET:${base}/api/metrics/history`, + `GET:${base}/api/session`, + `GET:${base}/api/system/health`, + `GET:${base}/api/system/info`, + `GET:${base}/{path}`, + `HEAD:${base}/{path}`, + `OPTIONS:${base}/{path}`, + `PATCH:${base}/{path}`, + `POST:${base}/api/admin/cleanup_lobbies`, + `POST:${base}/api/admin/cleanup_sessions`, + `POST:${base}/api/admin/clear_password`, + `POST:${base}/api/admin/set_password`, + `POST:${base}/api/bots/config/refresh-schemas`, + `POST:${base}/api/bots/config/schema/{bot_name}/refresh`, + `POST:${base}/api/bots/config/update`, + `POST:${base}/api/bots/instances/{bot_instance_id}/leave`, + `POST:${base}/api/bots/providers/register`, + `POST:${base}/api/bots/{bot_name}/join`, + `POST:${base}/api/cache/clear`, + `POST:${base}/api/lobby/{session_id}`, + `POST:${base}/{path}`, + `PUT:${base}/{path}` +]); + +// Schema path for dynamic usage +export const schemaPath = `${base}/openapi-schema.json`; + +// Proxy path pattern for matching +export const proxyPathPattern = `${base}/{path}`; + +export function checkApiEvolution(discoveredEndpoints: string[]): { + newEndpoints: string[]; + removedEndpoints: string[]; + totalEndpoints: number; +} { + const discoveredSet = new Set(discoveredEndpoints); + + const newEndpoints = discoveredEndpoints.filter(ep => !knownEndpoints.has(ep)); + const removedEndpoints = Array.from(knownEndpoints).filter(ep => !discoveredSet.has(ep)); + + return { + newEndpoints, + removedEndpoints, + totalEndpoints: discoveredEndpoints.length + }; } - -export class AdvancedApiEvolutionChecker { - private static instance: AdvancedApiEvolutionChecker; - private lastSchemaHash: string | null = null; - - static getInstance(): AdvancedApiEvolutionChecker { - if (!this.instance) { - this.instance = new AdvancedApiEvolutionChecker(); - } - return this.instance; - } - - /** - * Get all endpoints from the OpenAPI schema by analyzing the paths type - */ - private extractEndpointsFromSchema(): EndpointInfo[] { - const endpoints: EndpointInfo[] = []; - - // In a real implementation, we would need to parse the actual OpenAPI JSON - // since TypeScript types are erased at runtime. For now, we'll maintain - // a list that should be updated when new endpoints are added to the schema. - // This list is automatically updated by the update-api-client.js script - const knownSchemaEndpoints = [ - { path: '/ai-voicebot/api/system/health', method: 'GET', operationId: 'system_health_ai_voicebot_api_system_health_get' } - ]; - - // Get implemented endpoints from ApiClient - const implementedEndpoints = this.getImplementedEndpoints(); - - knownSchemaEndpoints.forEach((endpoint) => { - const key = `${endpoint.method}:${endpoint.path}`; - endpoints.push({ - ...endpoint, - implemented: implementedEndpoints.has(key), - }); - }); - - return endpoints; - } - - private getImplementedEndpoints(): Set { - return new Set([ - 'GET:/ai-voicebot/api/admin/names', - 'POST:/ai-voicebot/api/admin/set_password', - 'POST:/ai-voicebot/api/admin/clear_password', - 'GET:/ai-voicebot/api/health', - 'GET:/ai-voicebot/api/session', - 'GET:/ai-voicebot/api/lobby', - 'POST:/ai-voicebot/api/lobby/{sessionId}', - 'GET:/ai-voicebot/api/bots/providers', - 'GET:/ai-voicebot/api/bots', - 'POST:/ai-voicebot/api/lobby/{session_id}', - 'GET:/ai-voicebot/api/system/health' - ]); - } - - /** - * Load the OpenAPI schema from the JSON file to detect runtime changes - */ - async loadSchemaFromJson(): Promise { - try { - // Use dynamic base path from environment - const response = await fetch(`${base}/openapi-schema.json`); - if (response.ok) { - return await response.json(); - } - } catch (error) { - console.warn('Could not load OpenAPI schema for evolution checking:', error); - } - return null; - } - - /** - * Compare current schema with a previous version to detect changes - */ - async checkSchemaEvolution(): Promise<{ - hasNewEndpoints: boolean; - hasChangedEndpoints: boolean; - newEndpoints: EndpointInfo[]; - changedEndpoints: EndpointInfo[]; - unimplementedEndpoints: EndpointInfo[]; - }> { - const currentEndpoints = this.extractEndpointsFromSchema(); - const unimplementedEndpoints = currentEndpoints.filter(ep => !ep.implemented); - - // Try to load the actual schema for more detailed analysis - const schema = await this.loadSchemaFromJson(); - let hasNewEndpoints = false; - let hasChangedEndpoints = false; - let newEndpoints: EndpointInfo[] = []; - let changedEndpoints: EndpointInfo[] = []; - - if (schema) { - // Calculate a simple hash of the schema to detect changes - const schemaString = JSON.stringify(schema.paths || {}); - const currentHash = this.simpleHash(schemaString); - - if (this.lastSchemaHash && this.lastSchemaHash !== currentHash) { - hasChangedEndpoints = true; - console.info('šŸ”„ OpenAPI schema has changed since last check'); - } - - this.lastSchemaHash = currentHash; - - // Extract endpoints from the actual schema - if (schema.paths) { - Object.keys(schema.paths).forEach(path => { - if (path === `${base}/{path}`) return; // Skip generic proxy - - const pathObj = schema.paths[path]; - Object.keys(pathObj).forEach((method) => { - const endpoint = `${method.toUpperCase()}:${path}`; - const implementedEndpoints = this.getImplementedEndpoints(); - - if (!implementedEndpoints.has(endpoint)) { - const endpointInfo: EndpointInfo = { - path, - method: method.toUpperCase(), - operationId: pathObj[method].operationId, - implemented: false, - }; - - // Check if this is a new endpoint (not in our known list) - const isKnown = currentEndpoints.some((ep) => ep.path === path && ep.method === method.toUpperCase()); - - if (!isKnown) { - hasNewEndpoints = true; - newEndpoints.push(endpointInfo); - } - } - }); - }); - } - } - - return { - hasNewEndpoints, - hasChangedEndpoints, - newEndpoints, - changedEndpoints, - unimplementedEndpoints - }; - } - - private simpleHash(str: string): string { - let hash = 0; - for (let i = 0; i < str.length; i++) { - const char = str.charCodeAt(i); - hash = ((hash << 5) - hash) + char; - hash = hash & hash; // Convert to 32bit integer - } - return hash.toString(); - } - - /** - * Generate implementation stubs for unimplemented endpoints - */ - generateImplementationStubs(endpoints: EndpointInfo[]): string { - return endpoints.map(endpoint => { - const methodName = this.generateMethodName(endpoint); - const returnType = `Promise`; // Could be more specific with schema analysis - - return ` async ${methodName}(): ${returnType} { - return this.request('${endpoint.path}', { method: '${endpoint.method}' }); - }`; - }).join('\n\n'); - } - - private generateMethodName(endpoint: EndpointInfo): string { - // Convert operation ID or path to camelCase method name - if (endpoint.operationId) { - return endpoint.operationId.replace(/_[a-z]/g, (match) => match[1].toUpperCase()); - } - - // Fallback: generate from path and method - const pathParts = endpoint.path.split('/').filter(part => part && !part.startsWith('{')); - const lastPart = pathParts[pathParts.length - 1]; - const method = endpoint.method.toLowerCase(); - - if (method === 'get') { - return `get${lastPart.charAt(0).toUpperCase() + lastPart.slice(1)}`; - } else { - return `${method}${lastPart.charAt(0).toUpperCase() + lastPart.slice(1)}`; - } - } -} - -export const advancedApiChecker = AdvancedApiEvolutionChecker.getInstance(); diff --git a/client/src/api-types.ts b/client/src/api-types.ts index b82be86..cfff621 100644 --- a/client/src/api-types.ts +++ b/client/src/api-types.ts @@ -12,11 +12,799 @@ export interface paths { */ get: operations["system_health_ai_voicebot_api_system_health_get"]; }; + "/ai-voicebot/api/admin/names": { + /** List Names */ + get: operations["list_names_ai_voicebot_api_admin_names_get"]; + }; + "/ai-voicebot/api/admin/set_password": { + /** Set Password */ + post: operations["set_password_ai_voicebot_api_admin_set_password_post"]; + }; + "/ai-voicebot/api/admin/clear_password": { + /** Clear Password */ + post: operations["clear_password_ai_voicebot_api_admin_clear_password_post"]; + }; + "/ai-voicebot/api/admin/cleanup_sessions": { + /** Cleanup Sessions */ + post: operations["cleanup_sessions_ai_voicebot_api_admin_cleanup_sessions_post"]; + }; + "/ai-voicebot/api/admin/session_metrics": { + /** Session Metrics */ + get: operations["session_metrics_ai_voicebot_api_admin_session_metrics_get"]; + }; + "/ai-voicebot/api/admin/validate_sessions": { + /** Validate Sessions */ + get: operations["validate_sessions_ai_voicebot_api_admin_validate_sessions_get"]; + }; + "/ai-voicebot/api/admin/cleanup_lobbies": { + /** Cleanup Lobbies */ + post: operations["cleanup_lobbies_ai_voicebot_api_admin_cleanup_lobbies_post"]; + }; + "/ai-voicebot/api/health": { + /** + * Get Health Summary + * @description Get comprehensive health summary. + * + * Returns: + * Dict containing overall health status and component details + */ + get: operations["get_health_summary_ai_voicebot_api_health_get"]; + }; + "/ai-voicebot/api/session": { + /** Get Session */ + get: operations["get_session_ai_voicebot_api_session_get"]; + }; + "/ai-voicebot/api/lobby": { + /** List Lobbies */ + get: operations["list_lobbies_ai_voicebot_api_lobby_get"]; + }; + "/ai-voicebot/api/lobby/{session_id}": { + /** Create Lobby */ + post: operations["create_lobby_ai_voicebot_api_lobby__session_id__post"]; + }; + "/ai-voicebot/api/lobby/{lobby_id}/chat": { + /** Get Chat Messages */ + get: operations["get_chat_messages_ai_voicebot_api_lobby__lobby_id__chat_get"]; + }; + "/ai-voicebot/api/bots/providers/register": { + /** + * Register Bot Provider + * @description Register a new bot provider with authentication + */ + post: operations["register_bot_provider_ai_voicebot_api_bots_providers_register_post"]; + }; + "/ai-voicebot/api/bots/providers": { + /** + * List Bot Providers + * @description List all registered bot providers + */ + get: operations["list_bot_providers_ai_voicebot_api_bots_providers_get"]; + }; + "/ai-voicebot/api/bots": { + /** + * List Available Bots + * @description List all available bots from all registered providers + */ + get: operations["list_available_bots_ai_voicebot_api_bots_get"]; + }; + "/ai-voicebot/api/bots/{bot_name}/join": { + /** + * Request Bot Join Lobby + * @description Request a bot to join a specific lobby + */ + post: operations["request_bot_join_lobby_ai_voicebot_api_bots__bot_name__join_post"]; + }; + "/ai-voicebot/api/bots/instances/{bot_instance_id}/leave": { + /** + * Request Bot Leave Lobby + * @description Request a bot instance to leave from all lobbies and disconnect + */ + post: operations["request_bot_leave_lobby_ai_voicebot_api_bots_instances__bot_instance_id__leave_post"]; + }; + "/ai-voicebot/api/bots/instances/{bot_instance_id}": { + /** + * Get Bot Instance + * @description Get information about a specific bot instance + */ + get: operations["get_bot_instance_ai_voicebot_api_bots_instances__bot_instance_id__get"]; + }; + "/ai-voicebot/api/bots/config/schema/{bot_name}": { + /** + * Get Bot Config Schema + * @description Get configuration schema for a specific bot + */ + get: operations["get_bot_config_schema_ai_voicebot_api_bots_config_schema__bot_name__get"]; + }; + "/ai-voicebot/api/bots/config/lobby/{lobby_id}": { + /** + * Get Lobby Bot Configs + * @description Get all bot configurations for a lobby + */ + get: operations["get_lobby_bot_configs_ai_voicebot_api_bots_config_lobby__lobby_id__get"]; + /** + * Delete Lobby Configs + * @description Delete all bot configurations for a lobby + */ + delete: operations["delete_lobby_configs_ai_voicebot_api_bots_config_lobby__lobby_id__delete"]; + }; + "/ai-voicebot/api/bots/config/lobby/{lobby_id}/bot/{bot_name}": { + /** + * Get Lobby Bot Config + * @description Get specific bot configuration for a lobby + */ + get: operations["get_lobby_bot_config_ai_voicebot_api_bots_config_lobby__lobby_id__bot__bot_name__get"]; + /** + * Delete Bot Config + * @description Delete bot configuration for a lobby + */ + delete: operations["delete_bot_config_ai_voicebot_api_bots_config_lobby__lobby_id__bot__bot_name__delete"]; + }; + "/ai-voicebot/api/bots/config/update": { + /** + * Update Bot Config + * @description Update bot configuration for a lobby + */ + post: operations["update_bot_config_ai_voicebot_api_bots_config_update_post"]; + }; + "/ai-voicebot/api/bots/config/statistics": { + /** + * Get Config Statistics + * @description Get configuration manager statistics + */ + get: operations["get_config_statistics_ai_voicebot_api_bots_config_statistics_get"]; + }; + "/ai-voicebot/api/bots/config/refresh-schemas": { + /** + * Refresh Bot Schemas + * @description Refresh all bot configuration schemas from providers + */ + post: operations["refresh_bot_schemas_ai_voicebot_api_bots_config_refresh_schemas_post"]; + }; + "/ai-voicebot/api/bots/config/schema/{bot_name}/refresh": { + /** + * Refresh Bot Schema + * @description Refresh configuration schema for a specific bot + */ + post: operations["refresh_bot_schema_ai_voicebot_api_bots_config_schema__bot_name__refresh_post"]; + }; + "/ai-voicebot/api/bots/config/schema/{bot_name}/cache": { + /** + * Clear Bot Schema Cache + * @description Clear cached schema for a specific bot + */ + delete: operations["clear_bot_schema_cache_ai_voicebot_api_bots_config_schema__bot_name__cache_delete"]; + }; + "/ai-voicebot/api/health/ready": { + /** + * Readiness Probe + * @description Kubernetes readiness probe endpoint. + * + * Returns: + * Ready status for load balancer inclusion + */ + get: operations["readiness_probe_ai_voicebot_api_health_ready_get"]; + }; + "/ai-voicebot/api/health/live": { + /** + * Liveness Probe + * @description Kubernetes liveness probe endpoint. + * + * Returns: + * Alive status for container restart decisions + */ + get: operations["liveness_probe_ai_voicebot_api_health_live_get"]; + }; + "/ai-voicebot/api/metrics": { + /** + * Get Current Metrics + * @description Get current performance metrics. + * + * Returns: + * Current system and application metrics + */ + get: operations["get_current_metrics_ai_voicebot_api_metrics_get"]; + }; + "/ai-voicebot/api/metrics/history": { + /** + * Get Metrics History + * @description Get historical data for a specific metric. + * + * Args: + * metric_name: Name of the metric + * minutes: Number of minutes of history to retrieve (1-60) + * + * Returns: + * Historical metric data points + */ + get: operations["get_metrics_history_ai_voicebot_api_metrics_history_get"]; + }; + "/ai-voicebot/api/cache/stats": { + /** + * Get Cache Statistics + * @description Get cache performance statistics. + * + * Returns: + * Cache hit rates, sizes, and performance metrics + */ + get: operations["get_cache_statistics_ai_voicebot_api_cache_stats_get"]; + }; + "/ai-voicebot/api/system/info": { + /** + * Get System Info + * @description Get system information and configuration. + * + * Returns: + * System details, configuration, and runtime information + */ + get: operations["get_system_info_ai_voicebot_api_system_info_get"]; + }; + "/ai-voicebot/api/cache/clear": { + /** + * Clear Cache + * @description Clear cache entries. + * + * Args: + * cache_type: Optional specific cache to clear (session, lobby, user, message, computed) + * + * Returns: + * Cache clear results + */ + post: operations["clear_cache_ai_voicebot_api_cache_clear_post"]; + }; + "/ai-voicebot/api/metrics/export": { + /** + * Export Metrics Prometheus + * @description Export metrics in Prometheus format. + * + * Returns: + * Metrics in Prometheus text format + */ + get: operations["export_metrics_prometheus_ai_voicebot_api_metrics_export_get"]; + }; + "/ai-voicebot/{path}": { + /** Proxy Static */ + get: operations["proxy_static_ai_voicebot__path__patch"]; + /** Proxy Static */ + put: operations["proxy_static_ai_voicebot__path__patch"]; + /** Proxy Static */ + post: operations["proxy_static_ai_voicebot__path__patch"]; + /** Proxy Static */ + delete: operations["proxy_static_ai_voicebot__path__patch"]; + /** Proxy Static */ + options: operations["proxy_static_ai_voicebot__path__patch"]; + /** Proxy Static */ + head: operations["proxy_static_ai_voicebot__path__patch"]; + /** Proxy Static */ + patch: operations["proxy_static_ai_voicebot__path__patch"]; + }; } export type webhooks = Record; -export type components = Record; +export interface components { + schemas: { + /** + * AdminActionResponse + * @description Response for admin actions + */ + AdminActionResponse: { + /** + * Status + * @enum {string} + */ + status: "ok" | "not_found" | "error"; + /** Name */ + name: string; + }; + /** + * AdminClearPassword + * @description Request model for clearing admin password + */ + AdminClearPassword: { + /** Name */ + name: string; + }; + /** + * AdminMetricsConfig + * @description Config data for metrics response + */ + AdminMetricsConfig: { + /** Anonymous Timeout */ + anonymous_timeout: number; + /** Displaced Timeout */ + displaced_timeout: number; + /** Cleanup Interval */ + cleanup_interval: number; + /** Max Cleanup Per Cycle */ + max_cleanup_per_cycle: number; + }; + /** + * AdminMetricsResponse + * @description Response for admin session metrics + */ + AdminMetricsResponse: { + /** Total Sessions */ + total_sessions: number; + /** Active Sessions */ + active_sessions: number; + /** Named Sessions */ + named_sessions: number; + /** Displaced Sessions */ + displaced_sessions: number; + /** Old Anonymous Sessions */ + old_anonymous_sessions: number; + /** Old Displaced Sessions */ + old_displaced_sessions: number; + /** Total Lobbies */ + total_lobbies: number; + /** Cleanup Candidates */ + cleanup_candidates: number; + config: components["schemas"]["AdminMetricsConfig"]; + }; + /** + * AdminNamesResponse + * @description Response for admin names endpoint + */ + AdminNamesResponse: { + /** Name Passwords */ + name_passwords: { + [key: string]: components["schemas"]["NamePasswordRecord"]; + }; + }; + /** + * AdminSetPassword + * @description Request model for setting admin password + */ + AdminSetPassword: { + /** Name */ + name: string; + /** Password */ + password: string; + }; + /** + * AdminValidationResponse + * @description Response for admin session validation + */ + AdminValidationResponse: { + /** + * Status + * @enum {string} + */ + status: "ok" | "error"; + /** + * Issues + * @default [] + */ + issues?: string[]; + /** + * Issue Count + * @default 0 + */ + issue_count?: number; + /** Error */ + error?: string | null; + }; + /** + * BotConfigListResponse + * @description Response listing bot configurations for a lobby + */ + BotConfigListResponse: { + /** Lobby Id */ + lobby_id: string; + /** Configs */ + configs: components["schemas"]["BotLobbyConfig"][]; + }; + /** + * BotConfigParameter + * @description Definition of a bot configuration parameter + */ + BotConfigParameter: { + /** Name */ + name: string; + /** + * Type + * @enum {string} + */ + type: "string" | "number" | "boolean" | "select" | "range"; + /** Label */ + label: string; + /** Description */ + description: string; + /** Default Value */ + default_value?: unknown; + /** + * Required + * @default false + */ + required?: boolean; + /** Options */ + options?: { + [key: string]: string; + }[] | null; + /** Min Value */ + min_value?: number | null; + /** Max Value */ + max_value?: number | null; + /** Step */ + step?: number | null; + /** Max Length */ + max_length?: number | null; + /** Pattern */ + pattern?: string | null; + }; + /** + * BotConfigSchema + * @description Schema defining all configurable parameters for a bot + */ + BotConfigSchema: { + /** Bot Name */ + bot_name: string; + /** + * Version + * @default 1.0 + */ + version?: string; + /** Parameters */ + parameters: components["schemas"]["BotConfigParameter"][]; + /** Categories */ + categories?: { + [key: string]: string[]; + }[] | null; + }; + /** + * BotConfigUpdateRequest + * @description Request to update bot configuration + */ + BotConfigUpdateRequest: { + /** Bot Name */ + bot_name: string; + /** Lobby Id */ + lobby_id: string; + /** Config Values */ + config_values: { + [key: string]: unknown; + }; + }; + /** + * BotConfigUpdateResponse + * @description Response to bot configuration update + */ + BotConfigUpdateResponse: { + /** Success */ + success: boolean; + /** Message */ + message: string; + updated_config?: components["schemas"]["BotLobbyConfig"] | null; + }; + /** + * BotInfoModel + * @description Information about a specific bot + */ + BotInfoModel: { + /** Name */ + name: string; + /** Description */ + description: string; + /** + * Has Media + * @default true + */ + has_media?: boolean; + /** + * Configurable + * @default false + */ + configurable?: boolean; + /** Config Schema */ + config_schema?: { + [key: string]: unknown; + } | null; + }; + /** + * BotJoinLobbyRequest + * @description Request to make a bot join a lobby + */ + BotJoinLobbyRequest: { + /** Lobby Id */ + lobby_id: string; + /** + * Nick + * @default + */ + nick?: string; + /** Provider Id */ + provider_id?: string | null; + }; + /** + * BotJoinLobbyResponse + * @description Response after requesting a bot to join a lobby + */ + BotJoinLobbyResponse: { + /** Status */ + status: string; + /** Bot Instance Id */ + bot_instance_id: string; + /** Bot Name */ + bot_name: string; + /** Run Id */ + run_id: string; + /** Provider Id */ + provider_id: string; + /** Session Id */ + session_id: string; + }; + /** + * BotLeaveLobbyResponse + * @description Response after requesting a bot to leave a lobby + */ + BotLeaveLobbyResponse: { + /** Status */ + status: string; + /** Bot Instance Id */ + bot_instance_id: string; + /** Session Id */ + session_id: string; + /** Run Id */ + run_id?: string | null; + }; + /** + * BotListResponse + * @description Response listing all available bots from all providers + */ + BotListResponse: { + /** Bots */ + bots: components["schemas"]["BotInfoModel"][]; + /** Providers */ + providers: { + [key: string]: string; + }; + }; + /** + * BotLobbyConfig + * @description Bot configuration for a specific lobby + */ + BotLobbyConfig: { + /** Bot Name */ + bot_name: string; + /** Lobby Id */ + lobby_id: string; + /** Provider Id */ + provider_id: string; + /** Config Values */ + config_values: { + [key: string]: unknown; + }; + /** Created At */ + created_at: number; + /** Updated At */ + updated_at: number; + /** Created By */ + created_by: string; + }; + /** + * BotProviderListResponse + * @description Response listing all registered bot providers + */ + BotProviderListResponse: { + /** Providers */ + providers: components["schemas"]["BotProviderModel"][]; + }; + /** + * BotProviderModel + * @description Bot provider registration information + */ + BotProviderModel: { + /** Provider Id */ + provider_id: string; + /** Base Url */ + base_url: string; + /** Name */ + name: string; + /** + * Description + * @default + */ + description?: string; + /** Provider Key */ + provider_key: string; + /** Registered At */ + registered_at: number; + /** Last Seen */ + last_seen: number; + }; + /** + * BotProviderRegisterRequest + * @description Request to register a bot provider + */ + BotProviderRegisterRequest: { + /** Base Url */ + base_url: string; + /** Name */ + name: string; + /** + * Description + * @default + */ + description?: string; + /** Provider Key */ + provider_key: string; + }; + /** + * BotProviderRegisterResponse + * @description Response after registering a bot provider + */ + BotProviderRegisterResponse: { + /** Provider Id */ + provider_id: string; + /** + * Status + * @default registered + */ + status?: string; + }; + /** + * ChatMessageModel + * @description Chat message model + */ + ChatMessageModel: { + /** Id */ + id: string; + /** Message */ + message: string; + /** Sender Name */ + sender_name: string; + /** Sender Session Id */ + sender_session_id: string; + /** Timestamp */ + timestamp: number; + /** Lobby Id */ + lobby_id: string; + }; + /** + * ChatMessagesResponse + * @description Response containing chat messages + */ + ChatMessagesResponse: { + /** Messages */ + messages: components["schemas"]["ChatMessageModel"][]; + }; + /** HTTPValidationError */ + HTTPValidationError: { + /** Detail */ + detail?: components["schemas"]["ValidationError"][]; + }; + /** + * HealthResponse + * @description Health check response + */ + HealthResponse: { + /** Status */ + status: string; + }; + /** + * LobbiesResponse + * @description Response containing list of lobbies + */ + LobbiesResponse: { + /** Lobbies */ + lobbies: components["schemas"]["LobbyListItem"][]; + }; + /** + * LobbyCreateData + * @description Data for lobby creation + */ + LobbyCreateData: { + /** Name */ + name: string; + /** + * Private + * @default false + */ + private?: boolean; + }; + /** + * LobbyCreateRequest + * @description Request for creating a lobby + */ + LobbyCreateRequest: { + /** + * Type + * @constant + */ + type: "lobby_create"; + data: components["schemas"]["LobbyCreateData"]; + }; + /** + * LobbyCreateResponse + * @description Response for lobby creation + */ + LobbyCreateResponse: { + /** + * Type + * @constant + */ + type: "lobby_created"; + data: components["schemas"]["LobbyModel"]; + }; + /** + * LobbyListItem + * @description Lobby item for list responses + */ + LobbyListItem: { + /** Id */ + id: string; + /** Name */ + name: string; + }; + /** + * LobbyModel + * @description Core lobby model used across components + */ + LobbyModel: { + /** Id */ + id: string; + /** Name */ + name: string; + /** + * Private + * @default false + */ + private?: boolean; + }; + /** + * NamePasswordRecord + * @description Password hash record for reserved names + */ + NamePasswordRecord: { + /** Salt */ + salt: string; + /** Hash */ + hash: string; + }; + /** + * SessionResponse + * @description Session response model + */ + SessionResponse: { + /** Id */ + id: string; + /** Name */ + name: string; + /** Lobbies */ + lobbies: components["schemas"]["LobbyModel"][]; + /** + * Protected + * @default false + */ + protected?: boolean; + /** + * Has Media + * @default false + */ + has_media?: boolean; + /** Bot Run Id */ + bot_run_id?: string | null; + /** Bot Provider Id */ + bot_provider_id?: string | null; + /** Bot Instance Id */ + bot_instance_id?: string | null; + }; + /** ValidationError */ + ValidationError: { + /** Location */ + loc: (string | number)[]; + /** Message */ + msg: string; + /** Error Type */ + type: string; + }; + }; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; +} export type $defs = Record; @@ -38,4 +826,767 @@ export interface operations { }; }; }; + /** List Names */ + list_names_ai_voicebot_api_admin_names_get: { + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["AdminNamesResponse"]; + }; + }; + }; + }; + /** Set Password */ + set_password_ai_voicebot_api_admin_set_password_post: { + requestBody: { + content: { + "application/json": components["schemas"]["AdminSetPassword"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["AdminActionResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** Clear Password */ + clear_password_ai_voicebot_api_admin_clear_password_post: { + requestBody: { + content: { + "application/json": components["schemas"]["AdminClearPassword"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["AdminActionResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** Cleanup Sessions */ + cleanup_sessions_ai_voicebot_api_admin_cleanup_sessions_post: { + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["AdminActionResponse"]; + }; + }; + }; + }; + /** Session Metrics */ + session_metrics_ai_voicebot_api_admin_session_metrics_get: { + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["AdminMetricsResponse"]; + }; + }; + }; + }; + /** Validate Sessions */ + validate_sessions_ai_voicebot_api_admin_validate_sessions_get: { + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["AdminValidationResponse"]; + }; + }; + }; + }; + /** Cleanup Lobbies */ + cleanup_lobbies_ai_voicebot_api_admin_cleanup_lobbies_post: { + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["AdminActionResponse"]; + }; + }; + }; + }; + /** + * Get Health Summary + * @description Get comprehensive health summary. + * + * Returns: + * Dict containing overall health status and component details + */ + get_health_summary_ai_voicebot_api_health_get: { + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": unknown; + }; + }; + }; + }; + /** Get Session */ + get_session_ai_voicebot_api_session_get: { + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["SessionResponse"]; + }; + }; + }; + }; + /** List Lobbies */ + list_lobbies_ai_voicebot_api_lobby_get: { + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["LobbiesResponse"]; + }; + }; + }; + }; + /** Create Lobby */ + create_lobby_ai_voicebot_api_lobby__session_id__post: { + parameters: { + path: { + session_id: string; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["LobbyCreateRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["LobbyCreateResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** Get Chat Messages */ + get_chat_messages_ai_voicebot_api_lobby__lobby_id__chat_get: { + parameters: { + query?: { + limit?: number; + }; + path: { + lobby_id: string; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["ChatMessagesResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** + * Register Bot Provider + * @description Register a new bot provider with authentication + */ + register_bot_provider_ai_voicebot_api_bots_providers_register_post: { + requestBody: { + content: { + "application/json": components["schemas"]["BotProviderRegisterRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["BotProviderRegisterResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** + * List Bot Providers + * @description List all registered bot providers + */ + list_bot_providers_ai_voicebot_api_bots_providers_get: { + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["BotProviderListResponse"]; + }; + }; + }; + }; + /** + * List Available Bots + * @description List all available bots from all registered providers + */ + list_available_bots_ai_voicebot_api_bots_get: { + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["BotListResponse"]; + }; + }; + }; + }; + /** + * Request Bot Join Lobby + * @description Request a bot to join a specific lobby + */ + request_bot_join_lobby_ai_voicebot_api_bots__bot_name__join_post: { + parameters: { + path: { + bot_name: string; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["BotJoinLobbyRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["BotJoinLobbyResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** + * Request Bot Leave Lobby + * @description Request a bot instance to leave from all lobbies and disconnect + */ + request_bot_leave_lobby_ai_voicebot_api_bots_instances__bot_instance_id__leave_post: { + parameters: { + path: { + bot_instance_id: string; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["BotLeaveLobbyResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** + * Get Bot Instance + * @description Get information about a specific bot instance + */ + get_bot_instance_ai_voicebot_api_bots_instances__bot_instance_id__get: { + parameters: { + path: { + bot_instance_id: string; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** + * Get Bot Config Schema + * @description Get configuration schema for a specific bot + */ + get_bot_config_schema_ai_voicebot_api_bots_config_schema__bot_name__get: { + parameters: { + path: { + bot_name: string; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["BotConfigSchema"]; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** + * Get Lobby Bot Configs + * @description Get all bot configurations for a lobby + */ + get_lobby_bot_configs_ai_voicebot_api_bots_config_lobby__lobby_id__get: { + parameters: { + path: { + lobby_id: string; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["BotConfigListResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** + * Delete Lobby Configs + * @description Delete all bot configurations for a lobby + */ + delete_lobby_configs_ai_voicebot_api_bots_config_lobby__lobby_id__delete: { + parameters: { + path: { + lobby_id: string; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** + * Get Lobby Bot Config + * @description Get specific bot configuration for a lobby + */ + get_lobby_bot_config_ai_voicebot_api_bots_config_lobby__lobby_id__bot__bot_name__get: { + parameters: { + path: { + lobby_id: string; + bot_name: string; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["BotLobbyConfig"]; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** + * Delete Bot Config + * @description Delete bot configuration for a lobby + */ + delete_bot_config_ai_voicebot_api_bots_config_lobby__lobby_id__bot__bot_name__delete: { + parameters: { + path: { + lobby_id: string; + bot_name: string; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** + * Update Bot Config + * @description Update bot configuration for a lobby + */ + update_bot_config_ai_voicebot_api_bots_config_update_post: { + parameters: { + query?: { + session_id?: string; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["BotConfigUpdateRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["BotConfigUpdateResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** + * Get Config Statistics + * @description Get configuration manager statistics + */ + get_config_statistics_ai_voicebot_api_bots_config_statistics_get: { + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + }; + }; + /** + * Refresh Bot Schemas + * @description Refresh all bot configuration schemas from providers + */ + refresh_bot_schemas_ai_voicebot_api_bots_config_refresh_schemas_post: { + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + }; + }; + /** + * Refresh Bot Schema + * @description Refresh configuration schema for a specific bot + */ + refresh_bot_schema_ai_voicebot_api_bots_config_schema__bot_name__refresh_post: { + parameters: { + path: { + bot_name: string; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** + * Clear Bot Schema Cache + * @description Clear cached schema for a specific bot + */ + clear_bot_schema_cache_ai_voicebot_api_bots_config_schema__bot_name__cache_delete: { + parameters: { + path: { + bot_name: string; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": { + [key: string]: unknown; + }; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** + * Readiness Probe + * @description Kubernetes readiness probe endpoint. + * + * Returns: + * Ready status for load balancer inclusion + */ + readiness_probe_ai_voicebot_api_health_ready_get: { + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": unknown; + }; + }; + }; + }; + /** + * Liveness Probe + * @description Kubernetes liveness probe endpoint. + * + * Returns: + * Alive status for container restart decisions + */ + liveness_probe_ai_voicebot_api_health_live_get: { + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": unknown; + }; + }; + }; + }; + /** + * Get Current Metrics + * @description Get current performance metrics. + * + * Returns: + * Current system and application metrics + */ + get_current_metrics_ai_voicebot_api_metrics_get: { + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": unknown; + }; + }; + }; + }; + /** + * Get Metrics History + * @description Get historical data for a specific metric. + * + * Args: + * metric_name: Name of the metric + * minutes: Number of minutes of history to retrieve (1-60) + * + * Returns: + * Historical metric data points + */ + get_metrics_history_ai_voicebot_api_metrics_history_get: { + parameters: { + query: { + /** @description Name of the metric to retrieve */ + metric_name: string; + /** @description Minutes of history to retrieve */ + minutes?: number; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** + * Get Cache Statistics + * @description Get cache performance statistics. + * + * Returns: + * Cache hit rates, sizes, and performance metrics + */ + get_cache_statistics_ai_voicebot_api_cache_stats_get: { + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": unknown; + }; + }; + }; + }; + /** + * Get System Info + * @description Get system information and configuration. + * + * Returns: + * System details, configuration, and runtime information + */ + get_system_info_ai_voicebot_api_system_info_get: { + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": unknown; + }; + }; + }; + }; + /** + * Clear Cache + * @description Clear cache entries. + * + * Args: + * cache_type: Optional specific cache to clear (session, lobby, user, message, computed) + * + * Returns: + * Cache clear results + */ + clear_cache_ai_voicebot_api_cache_clear_post: { + parameters: { + query?: { + /** @description Specific cache to clear */ + cache_type?: string | null; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** + * Export Metrics Prometheus + * @description Export metrics in Prometheus format. + * + * Returns: + * Metrics in Prometheus text format + */ + export_metrics_prometheus_ai_voicebot_api_metrics_export_get: { + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": unknown; + }; + }; + }; + }; + /** Proxy Static */ + proxy_static_ai_voicebot__path__patch: { + parameters: { + path: { + path: string; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; } diff --git a/client/update-api-client.js b/client/update-api-client.js index eaff765..4671971 100644 --- a/client/update-api-client.js +++ b/client/update-api-client.js @@ -1,12 +1,13 @@ #!/usr/bin/env node /** - * Automated API Client and Evolution Checker Updater + * Automated API Client Generator * - * This script analyzes the generated OpenAPI schema and automatically updates: - * 1. api-client.ts - Adds missing endpoint implementations + * This script analyzes the generated OpenAPI schema and automatically generates: + * 1. api-client.ts - Complete TypeScript API client with types and methods * 2. api-evolution-checker.ts - Updates known endpoints list * - * Run this script after generating TypeScript types from OpenAPI schema. + * The api-client.ts file contains only minimal imports at the top, with everything + * else being auto-generated after a single marker. */ const fs = require('fs'); @@ -18,11 +19,10 @@ const API_TYPES_PATH = path.join(__dirname, 'src', 'api-types.ts'); const API_CLIENT_PATH = path.join(__dirname, 'src', 'api-client.ts'); const API_EVOLUTION_CHECKER_PATH = path.join(__dirname, 'src', 'api-evolution-checker.ts'); -class ApiClientUpdater { +class ApiClientGenerator { constructor() { this.schema = null; this.endpoints = []; - this.implementedEndpoints = new Set(); } /** @@ -51,21 +51,18 @@ class ApiClientUpdater { this.endpoints = []; - Object.entries(this.schema.paths).forEach(([path, pathObj]) => { - // Skip the generic proxy endpoint - if (path === '/ai-voicebot/{path}') { - return; - } - - Object.entries(pathObj).forEach(([method, methodObj]) => { + Object.entries(this.schema.paths).forEach(([path, pathItem]) => { + Object.entries(pathItem).forEach(([method, operation]) => { + if (method === 'parameters') return; // Skip path-level parameters + const endpoint = { path, method: method.toUpperCase(), - operationId: methodObj.operationId, - summary: methodObj.summary, - parameters: methodObj.parameters || [], - requestBody: methodObj.requestBody, - responses: methodObj.responses + operationId: operation.operationId, + summary: operation.summary, + requestBody: operation.requestBody, + parameters: operation.parameters || [], + responses: operation.responses || {} }; this.endpoints.push(endpoint); @@ -76,88 +73,52 @@ class ApiClientUpdater { } /** - * Parse the current API client to find implemented endpoints - */ - parseCurrentApiClient() { - try { - const clientContent = fs.readFileSync(API_CLIENT_PATH, 'utf8'); - - // Find implemented endpoints by looking for method patterns and path literals - // Now handles both old hardcoded paths and new this.getApiPath() calls - const methodRegex = /async\s+(\w+)\([^)]*\):[^{]*{\s*return\s+this\.request<[^>]*>\(([^,]+),\s*{\s*method:\s*"([^"]+)"/g; - - let match; - while ((match = methodRegex.exec(clientContent)) !== null) { - const [, methodName, pathExpression, httpMethod] = match; - - // Handle both string literals, template literals, and this.getApiPath() calls - let actualPath; - - if (pathExpression.includes('this.getApiPath(')) { - // Extract path from this.getApiPath("path") or this.getApiPath(`path`) - const getApiPathMatch = pathExpression.match(/this\.getApiPath\(([^)]+)\)/); - if (getApiPathMatch) { - let innerPath = getApiPathMatch[1]; - - // Remove quotes or backticks - if ((innerPath.startsWith('"') && innerPath.endsWith('"')) || - (innerPath.startsWith("'") && innerPath.endsWith("'"))) { - actualPath = innerPath.slice(1, -1); - } else if (innerPath.startsWith('`') && innerPath.endsWith('`')) { - // Template literal - convert ${param} back to {param} - actualPath = innerPath.slice(1, -1).replace(/\$\{([^}]+)\}/g, '{$1}'); - } - } - } else if (pathExpression.startsWith('"') && pathExpression.endsWith('"')) { - // String literal - actualPath = pathExpression.slice(1, -1); - } else if (pathExpression.startsWith('`') && pathExpression.endsWith('`')) { - // Template literal - convert ${param} back to {param} - actualPath = pathExpression.slice(1, -1).replace(/\$\{([^}]+)\}/g, '{$1}'); - } else { - // Skip complex expressions we can't easily parse - continue; - } - - if (actualPath) { - this.implementedEndpoints.add(`${httpMethod}:${actualPath}`); - } - } - - console.log(`āœ… Found ${this.implementedEndpoints.size} implemented endpoints in API client`); - } catch (error) { - console.error('āŒ Failed to parse current API client:', error.message); - } - } - - /** - * Generate method name from operation ID or path + * Generate method name from operation ID or path with HTTP method prefix */ generateMethodName(endpoint) { + let baseName; + if (endpoint.operationId) { // Convert snake_case operation ID to camelCase - return endpoint.operationId + baseName = endpoint.operationId .replace(/_ai_voicebot_.*$/, '') // Remove the long suffix .replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()) .replace(/^([a-z])/, (_, letter) => letter.toLowerCase()); - } - - // Fallback: generate from path and method - const pathParts = endpoint.path.split('/').filter(part => part && !part.startsWith('{')); - const lastPart = pathParts[pathParts.length - 1] || 'resource'; - const method = endpoint.method.toLowerCase(); - - if (method === 'get') { - return `get${lastPart.charAt(0).toUpperCase() + lastPart.slice(1)}`; } else { - return `${method}${lastPart.charAt(0).toUpperCase() + lastPart.slice(1)}`; + // Fallback: generate from path + const pathParts = endpoint.path.split('/').filter(part => part && !part.startsWith('{')); + const lastPart = pathParts[pathParts.length - 1] || 'resource'; + baseName = lastPart.charAt(0).toLowerCase() + lastPart.slice(1); } + + // Add method prefix to avoid duplicates + const method = endpoint.method.toLowerCase(); + const methodPrefixes = { + 'get': 'get', + 'post': 'create', + 'put': 'replace', // Use 'replace' for PUT to distinguish from PATCH + 'patch': 'update', // Use 'update' for PATCH + 'delete': 'delete', + 'head': 'head', + 'options': 'options' + }; + + const prefix = methodPrefixes[method] || method; + + // If baseName already starts with the prefix, don't duplicate + if (baseName.toLowerCase().startsWith(prefix.toLowerCase())) { + return baseName; + } + + // Capitalize first letter of baseName and add prefix + const capitalizedBaseName = baseName.charAt(0).toUpperCase() + baseName.slice(1); + return prefix + capitalizedBaseName; } /** - * Generate parameter types and method signature + * Generate method implementation */ - generateMethodSignature(endpoint) { + generateMethodImplementation(endpoint) { const methodName = this.generateMethodName(endpoint); let params = []; let pathParams = []; @@ -174,113 +135,285 @@ class ApiClientUpdater { // Check for request body if (endpoint.requestBody) { - params.push('data: any'); // Could be more specific with schema analysis + params.push('data: any'); } // Check for query parameters - if (endpoint.parameters && endpoint.parameters.length > 0) { - const queryParams = endpoint.parameters.filter(p => p.in === 'query'); - if (queryParams.length > 0) { - params.push('params?: Record'); - } + const hasQueryParams = endpoint.parameters && endpoint.parameters.some(p => p.in === 'query'); + if (hasQueryParams) { + params.push('params?: Record'); } - const paramString = params.join(', '); - const returnType = 'Promise'; // Could be more specific - - return { - methodName, - paramString, - returnType, - pathParams - }; - } - - /** - * Generate method implementation - */ - generateMethodImplementation(endpoint) { - const { methodName, paramString, returnType, pathParams } = this.generateMethodSignature(endpoint); + const methodSignature = `async ${methodName}(${params.join(', ')}): Promise`; - // Use this.getApiPath() to make paths dynamic based on PUBLIC_URL - let pathExpression; - if (pathParams.length > 0) { - // For parameterized paths, we need to use template literals - pathExpression = 'this.getApiPath(`' + endpoint.path.replace(/{([^}]+)}/g, '${$1}') + '`)'; - } else { - // For static paths, use string literal - pathExpression = `this.getApiPath("${endpoint.path}")`; - } + // Build the path with parameter substitution + let apiPath = endpoint.path; + pathParams.forEach(param => { + apiPath = apiPath.replace(`{${param}}`, `\${${param}}`); + }); + // Build the request options let requestOptions = `{ method: "${endpoint.method}"`; - if (endpoint.requestBody) { + if (endpoint.requestBody && endpoint.method !== 'GET') { requestOptions += ', body: data'; } - if (endpoint.parameters && endpoint.parameters.some(p => p.in === 'query')) { + if (hasQueryParams) { requestOptions += ', params'; } requestOptions += ' }'; - return ` async ${methodName}(${paramString}): ${returnType} { - return this.request(${pathExpression}, ${requestOptions}); + return ` ${methodSignature} { + return this.request(this.getApiPath(\`${apiPath}\`), ${requestOptions}); }`; } /** - * Update the API client with missing endpoints + * Extract all types from schema for re-export */ - updateApiClient() { + extractAllTypes() { + if (!this.schema || !this.schema.components || !this.schema.components.schemas) { + return '// No schema types available for export'; + } + + const schemas = this.schema.components.schemas; + const allTypeNames = Object.keys(schemas).sort(); + + if (allTypeNames.length === 0) { + return '// No types found in schema'; + } + + const typeExports = allTypeNames.map(typeName => + `export type ${typeName} = components["schemas"]["${typeName}"];` + ).join('\n'); + + return `// Re-export all types from the generated schema for convenience\n${typeExports}`; + } + + /** + * Generate convenience API namespaces + */ + generateConvenienceApis() { + return `// Convenience API namespaces for easy usage +export const adminApi = { + listNames: () => apiClient.getListNames(), + setPassword: (data: any) => apiClient.createSetPassword(data), + clearPassword: (data: any) => apiClient.createClearPassword(data), + cleanupSessions: () => apiClient.createCleanupSessions(), + sessionMetrics: () => apiClient.getSessionMetrics(), + validateSessions: () => apiClient.getValidateSessions(), + cleanupLobbies: () => apiClient.createCleanupLobbies(), +}; + +export const healthApi = { + check: () => apiClient.getHealthSummary(), + ready: () => apiClient.getReadinessProbe(), + live: () => apiClient.getLivenessProbe(), +}; + +export const lobbiesApi = { + getAll: () => apiClient.getListLobbies(), + getChatMessages: (lobbyId: string, params?: Record) => apiClient.getChatMessages(lobbyId, params), +}; + +export const sessionsApi = { + getCurrent: () => apiClient.getSession(), + createLobby: (sessionId: string, data: any) => apiClient.createLobby(sessionId, data), +}; + +export const botsApi = { + getProviders: () => apiClient.getListBotProviders(), + getAvailable: () => apiClient.getListAvailableBots(), + requestJoinLobby: (botName: string, request: any) => apiClient.createRequestBotJoinLobby(botName, request), + requestLeaveLobby: (botInstanceId: string) => apiClient.createRequestBotLeaveLobby(botInstanceId), + getInstance: (botInstanceId: string) => apiClient.getBotInstance(botInstanceId), + registerProvider: (data: any) => apiClient.createRegisterBotProvider(data), +};`; + } + + /** + * Create minimal template with just imports + */ + createMinimalTemplate() { + const template = `// TypeScript API client for AI Voicebot server +import { components } from "./api-types"; +import { base } from "./Common"; + +// DO NOT MANUALLY EDIT BELOW THIS LINE - All content below is auto-generated +// To modify auto-generated content, edit the template in client/update-api-client.js +`; + + fs.writeFileSync(API_CLIENT_PATH, template, 'utf8'); + console.log('āœ… Created minimal API client template'); + } + + /** + * Generate complete API client from scratch + */ + generateFullApiClient() { try { - const clientContent = fs.readFileSync(API_CLIENT_PATH, 'utf8'); + // Generate all components + const typeExports = this.extractAllTypes(); - // Find unimplemented endpoints - const unimplementedEndpoints = this.endpoints.filter(endpoint => { - const key = `${endpoint.method}:${endpoint.path}`; - return !this.implementedEndpoints.has(key); - }); - - if (unimplementedEndpoints.length === 0) { - console.log('āœ… All endpoints are already implemented in API client'); - return; - } - - console.log(`šŸ”§ Adding ${unimplementedEndpoints.length} missing endpoints to API client`); - - // Generate new method implementations - const newMethods = unimplementedEndpoints.map(endpoint => { + const allMethods = this.endpoints.map(endpoint => { console.log(` • ${endpoint.method} ${endpoint.path} (${this.generateMethodName(endpoint)})`); return this.generateMethodImplementation(endpoint); }).join('\n\n'); - // Find the insertion point (before the auto-generated comment and closing brace) - const insertionMarker = ' // Auto-generated endpoints will be added here by update-api-client.js'; - const insertionIndex = clientContent.indexOf(insertionMarker); + const convenienceApis = this.generateConvenienceApis(); + + const autoGeneratedContent = ` +// Re-export all types from the generated schema +export type { components } from "./api-types"; + +${typeExports} + +export class ApiError extends Error { + constructor(public status: number, public statusText: string, public data?: any) { + super(\`HTTP \${status}: \${statusText}\`); + this.name = "ApiError"; + } +} + +export class ApiClient { + private baseURL: string; + private defaultHeaders: Record; + + constructor(baseURL?: string) { + // Use the current window location instead of localhost, just like WebSocket connections + const defaultBaseURL = + typeof window !== "undefined" ? \`\${window.location.protocol}//\${window.location.host}\` : "http://localhost:8001"; + + this.baseURL = baseURL || process.env.REACT_APP_API_URL || defaultBaseURL; + this.defaultHeaders = {}; + } + + /** + * Construct API path using PUBLIC_URL environment variable + * Replaces hardcoded /ai-voicebot prefix with dynamic base from environment + */ + private getApiPath(schemaPath: string): string { + // Replace the hardcoded /ai-voicebot prefix with the dynamic base + return schemaPath.replace("/ai-voicebot", base); + } + + private async request( + path: string, + options: { + method: string; + body?: any; + params?: Record; + } + ): Promise { + const url = new URL(path, this.baseURL); + + if (options.params) { + Object.entries(options.params).forEach(([key, value]) => { + url.searchParams.append(key, value); + }); + } + + const requestInit: RequestInit = { + method: options.method, + headers: { + "Content-Type": "application/json", + ...this.defaultHeaders, + }, + }; + + if (options.body && options.method !== "GET") { + requestInit.body = JSON.stringify(options.body); + } + + const response = await fetch(url.toString(), requestInit); + + if (!response.ok) { + let errorData; + // Clone the response before trying to read it, in case JSON parsing fails + const responseClone = response.clone(); + try { + errorData = await response.json(); + } catch { + try { + errorData = await responseClone.text(); + } catch { + errorData = \`HTTP \${response.status}: \${response.statusText}\`; + } + } + throw new ApiError(response.status, response.statusText, errorData); + } + + const contentType = response.headers.get("content-type"); + if (contentType && contentType.includes("application/json")) { + return response.json(); + } + + return response.text() as unknown as T; + } + + // Auto-generated endpoint methods +${allMethods} +} + +// Default client instance +export const apiClient = new ApiClient(); + +${convenienceApis} +`; + + // Read the current file and find the marker + const currentContent = fs.readFileSync(API_CLIENT_PATH, 'utf8'); + const marker = '// DO NOT MANUALLY EDIT BELOW THIS LINE'; + const markerIndex = currentContent.indexOf(marker); - if (insertionIndex === -1) { - throw new Error('Could not find auto-generated endpoints insertion marker in ApiClient class'); + if (markerIndex === -1) { + console.error('āŒ Could not find auto-generation marker'); + return; } - // Find the end of the marker line - const markerEndIndex = clientContent.indexOf('\n', insertionIndex); - const doNotEditLine = clientContent.indexOf('\n', markerEndIndex + 1); + // Find the end of the marker line (including the guidance comment) + const markerEndIndex = currentContent.indexOf('\n', markerIndex); + const guidanceEndIndex = currentContent.indexOf('\n', markerEndIndex + 1); - // Insert the new methods after the "DO NOT MANUALLY EDIT BELOW THIS LINE" comment - const updatedContent = - clientContent.slice(0, doNotEditLine + 1) + - '\n // Auto-generated endpoints\n' + - newMethods + - '\n' + - clientContent.slice(doNotEditLine + 1); + // Keep everything up to and including the guidance comment, replace everything after + const templatePart = currentContent.slice(0, guidanceEndIndex + 1); + const fullContent = templatePart + autoGeneratedContent; - // Write the updated content - fs.writeFileSync(API_CLIENT_PATH, updatedContent, 'utf8'); - console.log('āœ… Updated API client with new endpoints'); + fs.writeFileSync(API_CLIENT_PATH, fullContent, 'utf8'); + console.log(`āœ… Generated complete API client with ${this.endpoints.length} endpoints`); - // Also update the implemented endpoints list - this.updateApiClientImplementedEndpointsList(unimplementedEndpoints); + } catch (error) { + console.error('āŒ Failed to generate API client:', error.message); + } + } + + /** + * Update existing API client + */ + updateApiClient() { + try { + // Check if file exists + if (!fs.existsSync(API_CLIENT_PATH)) { + console.log('šŸ“ API client file not found, creating minimal template...'); + this.createMinimalTemplate(); + } + + const clientContent = fs.readFileSync(API_CLIENT_PATH, 'utf8'); + + // Check if this is a minimal template (only has imports and marker) + const marker = '// DO NOT MANUALLY EDIT BELOW THIS LINE'; + const markerIndex = clientContent.indexOf(marker); + + if (markerIndex === -1) { + console.log('āš ļø Auto-generation marker not found, creating new template...'); + this.createMinimalTemplate(); + } + + // Always regenerate everything after the marker + console.log('šŸ”„ Regenerating complete API client...'); + this.generateFullApiClient(); } catch (error) { console.error('āŒ Failed to update API client:', error.message); @@ -288,182 +421,95 @@ class ApiClientUpdater { } /** - * Update the implemented endpoints list in ApiClient + * Update API evolution checker */ - updateApiClientImplementedEndpointsList(newEndpoints) { + updateApiEvolutionChecker() { try { - const clientContent = fs.readFileSync(API_CLIENT_PATH, 'utf8'); + // Generate list of all current endpoints for evolution tracking + // Convert paths to use dynamic base instead of hardcoded prefix + const currentEndpoints = this.endpoints.map(ep => { + const dynamicPath = ep.path.replace('/ai-voicebot', '${base}'); + return `${ep.method}:${dynamicPath}`; + }).sort(); - // Add new endpoints to the implemented list - const newEndpointKeys = newEndpoints.map(ep => `${ep.method}:${ep.path}`); - const allImplementedEndpoints = Array.from(this.implementedEndpoints) - .concat(newEndpointKeys) - .sort(); + const evolutionContent = `// Auto-generated API evolution checker +// This file tracks known API endpoints to detect changes - const implementedArray = allImplementedEndpoints - .map(endpoint => ` '${endpoint}'`) - .join(',\n'); +import { base } from './Common'; - const newImplementedEndpoints = `return new Set([ -${implementedArray} - ]);`; +export const knownEndpoints = new Set([ +${currentEndpoints.map(ep => ` \`${ep}\``).join(',\n')} +]); - // Replace the existing implemented endpoints set in ApiClient - const implementedRegex = /private getImplementedEndpoints\(\): Set \{[\s\S]*?return new Set\(\[[^\]]*\]\);[\s\S]*?\}/; - - const replacement = `private getImplementedEndpoints(): Set { - // Define all endpoints that are currently implemented in ApiClient - // This list is automatically updated by update-api-client.js - ${newImplementedEndpoints} - }`; +// Schema path for dynamic usage +export const schemaPath = \`\${base}/openapi-schema.json\`; - if (implementedRegex.test(clientContent)) { - const updatedContent = clientContent.replace(implementedRegex, replacement); - fs.writeFileSync(API_CLIENT_PATH, updatedContent, 'utf8'); - console.log('āœ… Updated implemented endpoints list in API client'); - } else { - console.warn('āš ļø Could not find getImplementedEndpoints method in API client'); - } +// Proxy path pattern for matching +export const proxyPathPattern = \`\${base}/{path}\`; + +export function checkApiEvolution(discoveredEndpoints: string[]): { + newEndpoints: string[]; + removedEndpoints: string[]; + totalEndpoints: number; +} { + const discoveredSet = new Set(discoveredEndpoints); + + const newEndpoints = discoveredEndpoints.filter(ep => !knownEndpoints.has(ep)); + const removedEndpoints = Array.from(knownEndpoints).filter(ep => !discoveredSet.has(ep)); + + return { + newEndpoints, + removedEndpoints, + totalEndpoints: discoveredEndpoints.length + }; +} +`; + + fs.writeFileSync(API_EVOLUTION_CHECKER_PATH, evolutionContent, 'utf8'); + console.log('āœ… Updated API evolution checker with current endpoints'); } catch (error) { - console.error('āŒ Failed to update implemented endpoints list in API client:', error.message); + console.error('āŒ Failed to update API evolution checker:', error.message); } } /** - * Update the API evolution checker with current endpoints - */ - updateEvolutionChecker() { - try { - const checkerContent = fs.readFileSync(API_EVOLUTION_CHECKER_PATH, 'utf8'); - - // Generate the updated known endpoints list - const knownEndpointsArray = this.endpoints.map(endpoint => { - return ` { path: '${endpoint.path}', method: '${endpoint.method}', operationId: '${endpoint.operationId || ''}' }`; - }).join(',\n'); - - const newKnownEndpoints = `const knownSchemaEndpoints = [ -${knownEndpointsArray} - ];`; - - // Replace the existing knownSchemaEndpoints array - const knownEndpointsRegex = /const knownSchemaEndpoints = \[[^\]]+\];/s; - - if (knownEndpointsRegex.test(checkerContent)) { - const updatedContent = checkerContent.replace(knownEndpointsRegex, newKnownEndpoints); - fs.writeFileSync(API_EVOLUTION_CHECKER_PATH, updatedContent, 'utf8'); - console.log('āœ… Updated API evolution checker with current endpoints'); - } else { - console.warn('āš ļø Could not find knownSchemaEndpoints array in evolution checker'); - } - - } catch (error) { - console.error('āŒ Failed to update evolution checker:', error.message); - } - } - - /** - * Update the implemented endpoints list in the evolution checker - */ - updateImplementedEndpointsList() { - try { - const checkerContent = fs.readFileSync(API_EVOLUTION_CHECKER_PATH, 'utf8'); - - // Generate the updated implemented endpoints list - const implementedArray = Array.from(this.implementedEndpoints) - .concat(this.endpoints.map(ep => `${ep.method}:${ep.path}`)) - .filter((value, index, self) => self.indexOf(value) === index) // Remove duplicates - .map(endpoint => ` '${endpoint}'`) - .join(',\n'); - - const newImplementedEndpoints = `return new Set([ -${implementedArray} - ]);`; - - // Replace the existing implemented endpoints set - const implementedRegex = /return new Set\(\[[^\]]+\]\);/s; - - if (implementedRegex.test(checkerContent)) { - const updatedContent = checkerContent.replace(implementedRegex, newImplementedEndpoints); - fs.writeFileSync(API_EVOLUTION_CHECKER_PATH, updatedContent, 'utf8'); - console.log('āœ… Updated implemented endpoints list in evolution checker'); - } else { - console.warn('āš ļø Could not find implemented endpoints set in evolution checker'); - } - - } catch (error) { - console.error('āŒ Failed to update implemented endpoints list:', error.message); - } - } - - /** - * Generate updated type exports for the API client - */ - updateTypeExports() { - try { - const clientContent = fs.readFileSync(API_CLIENT_PATH, 'utf8'); - - // Extract all schema types from the generated api-types.ts - const typesContent = fs.readFileSync(API_TYPES_PATH, 'utf8'); - const schemaRegex = /export interface paths \{[\s\S]*?\n\}/; - const componentsRegex = /export interface components \{[\s\S]*schemas: \{[\s\S]*?\n \};\n\}/; - - // Find all schema names in components.schemas - const schemaNames = []; - const schemaMatches = typesContent.match(/"([^"]+)":\s*\{/g); - if (schemaMatches) { - schemaMatches.forEach(match => { - const name = match.match(/"([^"]+)":/)[1]; - if (name && !name.includes('ValidationError')) { - schemaNames.push(name); - } - }); - } - - console.log(`āœ… Found ${schemaNames.length} schema types for potential export`); - - } catch (error) { - console.error('āŒ Failed to update type exports:', error.message); - } - } - - /** - * Run the complete update process + * Main execution method */ async run() { - console.log('šŸš€ Starting automated API client update...\n'); + console.log('šŸš€ Starting automated API client generation...'); + // Load and validate schema if (!this.loadSchema()) { process.exit(1); } + // Extract endpoints this.extractEndpoints(); - this.parseCurrentApiClient(); - - console.log('\nšŸ“Š Analysis Results:'); - console.log(` • Schema endpoints: ${this.endpoints.length}`); - console.log(` • Implemented endpoints: ${this.implementedEndpoints.size}`); - console.log(` • Missing endpoints: ${this.endpoints.length - this.implementedEndpoints.size}\n`); - // Update files + if (this.endpoints.length === 0) { + console.error('āŒ No endpoints found in schema'); + process.exit(1); + } + + console.log(`\nšŸ“Š Analysis Results:`); + console.log(` • Schema endpoints: ${this.endpoints.length}`); + + // Update/generate API client this.updateApiClient(); - this.updateEvolutionChecker(); - this.updateImplementedEndpointsList(); - - console.log('\nāœ… API client update complete!'); + + // Update evolution checker + this.updateApiEvolutionChecker(); + + console.log('\nāœ… API client generation complete!'); console.log('šŸ“„ Updated files:'); - console.log(' - api-client.ts (added missing endpoints)'); + console.log(' - api-client.ts (regenerated)'); console.log(' - api-evolution-checker.ts (updated known endpoints)'); } } -// Run the updater +// Run if called directly if (require.main === module) { - const updater = new ApiClientUpdater(); - updater.run().catch(error => { - console.error('āŒ Update failed:', error); - process.exit(1); - }); + const generator = new ApiClientGenerator(); + generator.run().catch(console.error); } - -module.exports = ApiClientUpdater; diff --git a/generate-ts-types.sh b/generate-ts-types.sh index 00195ad..fa3af9d 100755 --- a/generate-ts-types.sh +++ b/generate-ts-types.sh @@ -1,9 +1,10 @@ #!/bin/bash # Comprehensive script to generate TypeScript types from FastAPI OpenAPI schema. # This script coordinates between the server and frontend containers to: -# 1. Generate OpenAPI schema from FastAPI server -# 2. Generate TypeScript types from the schema -# 3. Ensure frontend container dependencies are installed +# 1. Ensure the server is running and ready +# 2. Generate OpenAPI schema from the running FastAPI server +# 3. Generate TypeScript types from the schema +# 4. Ensure frontend container dependencies are installed set -e @@ -12,29 +13,61 @@ echo "šŸš€ Starting OpenAPI TypeScript generation process..." # Change to the project directory cd "$(dirname "$0")" -echo "šŸ“‹ Step 1: Generating OpenAPI schema from FastAPI server..." +echo "šŸ“‹ Step 1: Ensuring server container is running and ready..." +docker compose up -d server + +# Load environment variables +source .env + +# Build health check URL from environment variables +HEALTH_URL="${PUBLIC_SERVER_URL}${PUBLIC_URL}/api/system/health" +echo "šŸ” Using health check URL: $HEALTH_URL" + +# Wait for server to be ready +echo "ā³ Waiting for server to be ready..." +max_retries=30 +retry_count=0 +while [ $retry_count -lt $max_retries ]; do + # Use curl with SSL verification disabled for self-signed certs + if docker compose exec server curl -f -k "$HEALTH_URL" >/dev/null 2>&1; then + echo "āœ… Server is ready!" + break + fi + retry_count=$((retry_count + 1)) + echo " Attempt $retry_count/$max_retries - Server not ready yet, waiting..." + sleep 2 +done + +if [ $retry_count -eq $max_retries ]; then + echo "āŒ Server failed to become ready within timeout" + echo "šŸ“‹ Server logs:" + docker compose logs server --tail=20 + exit 1 +fi + +echo "šŸ“‹ Step 2: Generating OpenAPI schema from running FastAPI server..." docker compose exec server uv run python3 generate_schema_simple.py docker compose cp server:/client/openapi-schema.json ./client/openapi-schema.json -echo "šŸ“‹ Step 2: Ensuring frontend container is running..." +echo "šŸ“‹ Step 3: Ensuring frontend container is running..." docker compose up -d client -echo "šŸ“‹ Step 3: Installing/updating frontend dependencies..." +echo "šŸ“‹ Step 4: Installing/updating frontend dependencies..." docker compose exec client npm install --legacy-peer-deps -echo "šŸ“‹ Step 4: Generating TypeScript types from OpenAPI schema..." +echo "šŸ“‹ Step 5: Generating TypeScript types from OpenAPI schema..." docker compose exec client npx openapi-typescript openapi-schema.json -o src/api-types.ts -echo "šŸ“‹ Step 5: Automatically updating API client and evolution checker..." +echo "šŸ“‹ Step 6: Automatically updating API client and evolution checker..." docker compose exec client node update-api-client.js -echo "šŸ“‹ Step 6: Running TypeScript type checking..." +echo "šŸ“‹ Step 7: Running TypeScript type checking..." docker compose exec client npm run type-check -echo "šŸ“‹ Step 7: Testing dynamic path usage..." +echo "šŸ“‹ Step 8: Testing dynamic path usage..." docker compose exec client npm run test-dynamic-paths -echo "šŸ“‹ Step 8: Running API evolution check..." +echo "šŸ“‹ Step 9: Running API evolution check..." docker compose exec client node check-api-evolution.js echo "āœ… TypeScript generation and API client update complete!" diff --git a/server/generate_schema_simple.py b/server/generate_schema_simple.py index 60b6715..c4b5c8a 100644 --- a/server/generate_schema_simple.py +++ b/server/generate_schema_simple.py @@ -1,26 +1,69 @@ #!/usr/bin/env python3 """ Simple OpenAPI schema generator for FastAPI. -This generates only the JSON schema file that can be used with openapi-typescript. +This generates the JSON schema file by fetching it from the running server's OpenAPI endpoint. +This ensures we get the complete schema as the server sees it at runtime. """ import json import sys +import os +import asyncio from pathlib import Path +import httpx -def generate_schema(): - """Generate OpenAPI schema from the FastAPI app""" +async def generate_schema_async(): + """Generate OpenAPI schema from the running FastAPI server""" try: - # Add shared module to path for Docker environment - shared_path = "/shared" - if shared_path not in sys.path: - sys.path.insert(0, shared_path) - - # Import the FastAPI app - from main import app - - # Get the OpenAPI schema - schema = app.openapi() + # Configuration + public_url = os.getenv("PUBLIC_URL", "/") + if not public_url.endswith("/"): + public_url += "/" + + # Server endpoint - use PUBLIC_SERVER_URL from .env + server_url = os.getenv("PUBLIC_SERVER_URL", "https://server:8000") + + # Determine if SSL verification should be disabled based on protocol + use_ssl = server_url.startswith("https://") + verify_ssl = not use_ssl or os.getenv("SSL_VERIFY", "false").lower() == "true" + + # OpenAPI schema endpoint + openapi_url = f"{server_url}{public_url}openapi.json" + + print(f"šŸ” Fetching OpenAPI schema from: {openapi_url}") + print( + f"šŸ”’ SSL mode: {'enabled' if use_ssl else 'disabled'}, verification: {'enabled' if verify_ssl else 'disabled'}" + ) + + # Wait for server to be ready and fetch schema + max_retries = 10 + retry_delay = 2 + + for attempt in range(max_retries): + try: + # Create HTTP client with appropriate SSL settings + async with httpx.AsyncClient(timeout=30.0, verify=verify_ssl) as client: + response = await client.get(openapi_url) + response.raise_for_status() + schema = response.json() + break + except (httpx.ConnectError, httpx.TimeoutException) as e: + if attempt < max_retries - 1: + print( + f"ā³ Server not ready (attempt {attempt + 1}/{max_retries}), waiting {retry_delay}s..." + ) + await asyncio.sleep(retry_delay) + continue + else: + raise Exception( + f"Failed to connect to server after {max_retries} attempts: {e}" + ) + except httpx.HTTPStatusError as e: + raise Exception( + f"Server returned error {e.response.status_code}: {e.response.text}" + ) + else: + raise Exception("Failed to fetch schema - max retries exceeded") # Write the schema to a JSON file that can be accessed from outside container schema_file = Path("/client/openapi-schema.json") @@ -28,8 +71,16 @@ def generate_schema(): json.dump(schema, f, indent=2, ensure_ascii=False) print(f"āœ… OpenAPI schema generated successfully at: {schema_file}") + print(f"Schema contains {len(schema.get('paths', {}))} API paths") print(f"Schema contains {len(schema.get('components', {}).get('schemas', {}))} component schemas") - + + # Print some info about what endpoints were found + paths = schema.get("paths", {}) + print("šŸ“‹ Found API endpoints:") + for path in sorted(paths.keys()): + methods = list(paths[path].keys()) + print(f" {path} ({', '.join(methods)})") + return True except Exception as e: @@ -38,6 +89,15 @@ def generate_schema(): traceback.print_exc() return False +def generate_schema(): + """Synchronous wrapper for async schema generation""" + try: + return asyncio.run(generate_schema_async()) + except Exception as e: + print(f"āŒ Error in schema generation wrapper: {e}") + return False + + if __name__ == "__main__": success = generate_schema() sys.exit(0 if success else 1) diff --git a/server/main.py b/server/main.py index f76f560..4d345c1 100644 --- a/server/main.py +++ b/server/main.py @@ -86,11 +86,18 @@ if not public_url.endswith("/"): ADMIN_TOKEN = os.getenv("ADMIN_TOKEN", None) -# Create FastAPI app first +# Create FastAPI app with proper URL configuration +openapi_url = f"{public_url}openapi.json" +docs_url = f"{public_url}docs" +redoc_url = f"{public_url}redoc" + app = FastAPI( title="AI Voice Bot Server (Refactored)", description="WebRTC voice chat server with modular architecture", version="2.0.0", + openapi_url=openapi_url, + docs_url=docs_url, + redoc_url=redoc_url, ) logger.info(f"Starting server with public URL: {public_url}")