diff --git a/.stainless.yml b/.stainless.yml new file mode 100644 index 00000000..0bb51b4a --- /dev/null +++ b/.stainless.yml @@ -0,0 +1,105 @@ +# Stainless SDK Configuration for EventRelay +# ============================================= +# This file configures the Stainless SDK generator to produce +# type-safe Python and TypeScript clients from the EventRelay OpenAPI spec. +# +# Documentation: https://stainlessapi.com/docs/configuration +# OpenAPI spec: openapi/eventrelay.openapi.json + +organization: + name: eventrelay + +# Source OpenAPI specification +openapi: + path: openapi/eventrelay.openapi.json + +# SDK targets to generate +targets: + python: + package_name: eventrelay-sdk + project_name: eventrelay-sdk + production_repo: groupthinking/eventrelay-python + + typescript: + package_name: "@eventrelay/sdk" + production_repo: groupthinking/eventrelay-node + +# API resource groupings — map paths to SDK resource classes +resources: + videos: + methods: + process: + type: post + endpoint: POST /api/v1/videos/process + get_status: + type: get + endpoint: GET /api/v1/videos/{job_id}/status + list: + type: get + endpoint: GET /api/v1/videos + retrieve: + type: get + endpoint: GET /api/v1/videos/{video_id} + delete: + type: delete + endpoint: DELETE /api/v1/cache/{video_id} + process_markdown: + type: post + endpoint: POST /api/v1/process-video-markdown + + events: + methods: + extract: + type: post + endpoint: POST /api/v1/events/extract + + agents: + methods: + dispatch: + type: post + endpoint: POST /api/v1/agents/dispatch + get_status: + type: get + endpoint: GET /api/v1/agents/{agent_id}/status + + transcript: + methods: + action: + type: post + endpoint: POST /api/v1/transcript-action + + health: + methods: + check: + type: get + endpoint: GET /api/v1/health + detailed: + type: get + endpoint: GET /api/v1/health/detailed + + chat: + methods: + send: + type: post + endpoint: POST /api/v1/chat + +# Authentication configuration +environments: + production: + url: https://api.uvai.io + development: + url: http://localhost:8000 + +# Pagination configuration +pagination: + type: offset + page_param: page + page_size_param: page_size + +# Retry configuration +retries: + default: 2 + status_codes: [429, 500, 502, 503, 504] + +# Timeout defaults (seconds) +timeout: 60.0 diff --git a/openapi/eventrelay.openapi.json b/openapi/eventrelay.openapi.json new file mode 100644 index 00000000..c5668084 --- /dev/null +++ b/openapi/eventrelay.openapi.json @@ -0,0 +1,4428 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "YouTube Extension API", + "description": "\n ## UVAI Platform API\n\n **Architecture Features:**\n - \ud83c\udfd7\ufe0f **Service-Oriented Architecture** with dependency injection\n - \ud83d\udccb **API Versioning** for backward compatibility\n - \ud83d\udd04 **Real-time WebSocket** communication\n - \ud83d\udcca **Comprehensive Monitoring** and health checks\n - \ud83d\ude80 **Production-ready** with proper error handling\n\n **Core Capabilities:**\n - **Video Processing**: AI-powered analysis of YouTube videos\n - **Markdown Generation**: Automated learning guides and summaries\n - **Video-to-Software**: Convert videos into deployable applications\n - **Caching System**: Intelligent caching for improved performance\n - **Real-time Communication**: WebSocket support for live updates\n\n **API Versions:**\n - **v1**: Current stable API with all core features\n - **Legacy**: Backward compatibility with original endpoints\n\n **MCP Integration:**\n - Multi-modal Content Processing with agent orchestration\n - Support for multiple LLM providers (Gemini, Claude, GPT-4)\n - Real-time video analysis and action generation\n ", + "version": "2.0.0", + "x-logo": { + "url": "https://uvai.io/logo.png" + } + }, + "paths": { + "/api/v1/health": { + "get": { + "tags": [ + "API v1" + ], + "summary": "Health Check", + "description": "Get basic health status of the API and its components", + "operationId": "health_check_v1_api_v1_health_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HealthResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "429": { + "description": "Rate Limited", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/v1/health/detailed": { + "get": { + "tags": [ + "API v1" + ], + "summary": "Detailed Health Check", + "description": "Get comprehensive health status including external connectors", + "operationId": "detailed_health_check_v1_api_v1_health_detailed_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "additionalProperties": true, + "type": "object", + "title": "Response Detailed Health Check V1 Api V1 Health Detailed Get" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "429": { + "description": "Rate Limited", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/v1/capabilities": { + "get": { + "tags": [ + "API v1" + ], + "summary": "Model capabilities status", + "description": "Report availability info for video processing models", + "operationId": "get_capabilities_v1_api_v1_capabilities_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "additionalProperties": true, + "type": "object", + "title": "Response Get Capabilities V1 Api V1 Capabilities Get" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "429": { + "description": "Rate Limited", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/v1/hybrid/cache": { + "post": { + "tags": [ + "API v1" + ], + "summary": "Create Gemini cache session", + "description": "Create a reusable Gemini cache entry via the hybrid processor.", + "operationId": "create_gemini_cache_api_v1_hybrid_cache_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GeminiCacheRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GeminiCacheResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "429": { + "description": "Rate Limited", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/v1/hybrid/batch": { + "post": { + "tags": [ + "API v1" + ], + "summary": "Submit Gemini batch job", + "description": "Submit a batch generateContent request and optionally wait for completion.", + "operationId": "submit_gemini_batch_api_v1_hybrid_batch_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GeminiBatchRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GeminiBatchResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "429": { + "description": "Rate Limited", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/v1/hybrid/ephemeral-token": { + "post": { + "tags": [ + "API v1" + ], + "summary": "Create Gemini ephemeral token", + "description": "Generate a short-lived token suitable for client-side uploads.", + "operationId": "create_ephemeral_token_api_v1_hybrid_ephemeral_token_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GeminiTokenRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GeminiTokenResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "429": { + "description": "Rate Limited", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/v1/transcript-action": { + "post": { + "tags": [ + "API v1" + ], + "summary": "Extract transcript and produce deployable action plan", + "description": "Runs the transcript-to-action workflow, producing summaries, project scaffolds, and task boards.", + "operationId": "run_transcript_action_api_v1_transcript_action_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TranscriptActionRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TranscriptActionResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "429": { + "description": "Rate Limited", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/v1/chat": { + "post": { + "tags": [ + "API v1" + ], + "summary": "Chat with AI Assistant", + "description": "Send a message to the AI assistant for help with video processing", + "operationId": "chat_v1_api_v1_chat_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChatRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChatResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "429": { + "description": "Rate Limited", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/v1/process-video": { + "post": { + "tags": [ + "API v1" + ], + "summary": "Process Video", + "description": "Process a YouTube video and extract information", + "operationId": "process_video_v1_api_v1_process_video_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VideoProcessingRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "429": { + "description": "Rate Limited", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/v1/process-video-markdown": { + "post": { + "tags": [ + "API v1" + ], + "summary": "Process Video to Markdown", + "description": "Process a YouTube video and generate markdown analysis with caching", + "operationId": "process_video_markdown_v1_api_v1_process_video_markdown_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MarkdownRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MarkdownResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "429": { + "description": "Rate Limited", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/v1/video-to-software": { + "post": { + "tags": [ + "API v1" + ], + "summary": "Convert Video to Software", + "description": "Process a YouTube video and generate deployable software application", + "operationId": "video_to_software_v1_api_v1_video_to_software_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VideoToSoftwareRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VideoToSoftwareResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "429": { + "description": "Rate Limited", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/v1/cache/stats": { + "get": { + "tags": [ + "API v1" + ], + "summary": "Get Cache Statistics", + "description": "Get comprehensive statistics about cached video processing results", + "operationId": "get_cache_stats_v1_api_v1_cache_stats_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CacheStats" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "429": { + "description": "Rate Limited", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/v1/cache/{video_id}": { + "get": { + "tags": [ + "API v1" + ], + "summary": "Get Cached Video Analysis", + "description": "Retrieve cached analysis for a specific video by ID", + "operationId": "get_cached_video_v1_api_v1_cache__video_id__get", + "parameters": [ + { + "name": "video_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Video Id" + } + }, + { + "name": "format", + "in": "query", + "required": false, + "schema": { + "type": "string", + "default": "markdown", + "title": "Format" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "429": { + "description": "Rate Limited", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + }, + "delete": { + "tags": [ + "API v1" + ], + "summary": "Clear Video Cache", + "description": "Clear cached results for a specific video", + "operationId": "clear_video_cache_v1_api_v1_cache__video_id__delete", + "parameters": [ + { + "name": "video_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Video Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "429": { + "description": "Rate Limited", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/v1/cache": { + "delete": { + "tags": [ + "API v1" + ], + "summary": "Clear All Cache", + "description": "Clear all cached video processing results", + "operationId": "clear_all_cache_v1_api_v1_cache_delete", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "429": { + "description": "Rate Limited", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/v1/videos": { + "get": { + "tags": [ + "API v1" + ], + "summary": "List Processed Videos", + "description": "Get summary list of all processed videos", + "operationId": "list_videos_v1_api_v1_videos_get", + "parameters": [ + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 50, + "title": "Limit" + } + }, + { + "name": "offset", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 0, + "title": "Offset" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "title": "Response List Videos V1 Api V1 Videos Get" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "429": { + "description": "Rate Limited", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/v1/videos/{video_id}": { + "get": { + "tags": [ + "API v1" + ], + "summary": "Get Video Details", + "description": "Get detailed information for a specific processed video", + "operationId": "get_video_detail_v1_api_v1_videos__video_id__get", + "parameters": [ + { + "name": "video_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Video Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "429": { + "description": "Rate Limited", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/v1/learning-log": { + "get": { + "tags": [ + "API v1" + ], + "summary": "Get Learning Log", + "description": "Get learning log entries from processed videos", + "operationId": "get_learning_log_v1_api_v1_learning_log_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "additionalProperties": true, + "type": "object" + }, + "type": "array", + "title": "Response Get Learning Log V1 Api V1 Learning Log Get" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "429": { + "description": "Rate Limited", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/v1/actions/{video_id}": { + "get": { + "tags": [ + "API v1" + ], + "summary": "List actions for a video", + "description": "Retrieve actions generated for a specific processed video", + "operationId": "get_actions_by_video_v1_api_v1_actions__video_id__get", + "parameters": [ + { + "name": "video_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Video Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "429": { + "description": "Rate Limited", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/v1/actions/{action_id}": { + "put": { + "tags": [ + "API v1" + ], + "summary": "Update action status", + "description": "Update action completion status or metadata", + "operationId": "update_action_v1_api_v1_actions__action_id__put", + "parameters": [ + { + "name": "action_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Action Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "title": "Payload" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "429": { + "description": "Rate Limited", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/v1/feedback": { + "post": { + "tags": [ + "API v1" + ], + "summary": "Submit Feedback", + "description": "Submit feedback about video processing results or the service", + "operationId": "submit_feedback_v1_api_v1_feedback_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FeedbackRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FeedbackResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "429": { + "description": "Rate Limited", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/v1/metrics": { + "get": { + "tags": [ + "API v1" + ], + "summary": "Get Metrics", + "description": "Get system metrics in Prometheus format", + "operationId": "get_metrics_v1_api_v1_metrics_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + }, + "text/plain": {} + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "429": { + "description": "Rate Limited", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/v1/performance/alert": { + "post": { + "tags": [ + "API v1" + ], + "summary": "Ingest frontend performance alert", + "operationId": "ingest_performance_alert_v1_api_v1_performance_alert_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "additionalProperties": true, + "type": "object", + "title": "Payload" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "429": { + "description": "Rate Limited", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/v1/performance/report": { + "post": { + "tags": [ + "API v1" + ], + "summary": "Ingest frontend performance report", + "operationId": "ingest_performance_report_v1_api_v1_performance_report_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "additionalProperties": true, + "type": "object", + "title": "Report" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "429": { + "description": "Rate Limited", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/v1/videos/process": { + "post": { + "tags": [ + "API v1", + "Videos" + ], + "summary": "Start async video processing", + "description": "Create a background video-processing job and return immediately.", + "operationId": "start_video_processing_api_v1_videos_process_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VideoProcessJobRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "429": { + "description": "Rate Limited", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/v1/videos/{job_id}/status": { + "get": { + "tags": [ + "API v1", + "Videos" + ], + "summary": "Poll video processing status", + "description": "Return the current status of a video-processing job.", + "operationId": "get_video_job_status_api_v1_videos__job_id__status_get", + "parameters": [ + { + "name": "job_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Job Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "429": { + "description": "Rate Limited", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/v1/events/extract": { + "post": { + "tags": [ + "API v1", + "Events" + ], + "summary": "Extract events from transcript", + "description": "Extract actionable events from a transcript or completed job.", + "operationId": "extract_events_api_v1_events_extract_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EventExtractRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "429": { + "description": "Rate Limited", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/v1/agents/dispatch": { + "post": { + "tags": [ + "API v1", + "Agents" + ], + "summary": "Dispatch agents for extracted events", + "description": "Dispatch specialist agents to act on extracted events.", + "operationId": "dispatch_agents_api_v1_agents_dispatch_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentDispatchRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "429": { + "description": "Rate Limited", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/v1/agents/{agent_id}/status": { + "get": { + "tags": [ + "API v1", + "Agents" + ], + "summary": "Get agent execution status", + "description": "Return the current status of an agent execution.", + "operationId": "get_agent_status_api_v1_agents__agent_id__status_get", + "parameters": [ + { + "name": "agent_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Agent Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "429": { + "description": "Rate Limited", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/v1/agents/a2a/send": { + "post": { + "tags": [ + "API v1", + "Agents" + ], + "summary": "Send an A2A message between agents", + "description": "Send a context-share or tool-request message between agents.", + "operationId": "send_a2a_message_api_v1_agents_a2a_send_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "additionalProperties": true, + "type": "object", + "title": "Body", + "default": {} + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "429": { + "description": "Rate Limited", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/v1/agents/a2a/log": { + "get": { + "tags": [ + "API v1", + "Agents" + ], + "summary": "Get A2A message log", + "description": "Return recent A2A inter-agent messages.", + "operationId": "get_a2a_log_api_v1_agents_a2a_log_get", + "parameters": [ + { + "name": "conversation_id", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Conversation 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/ApiResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "429": { + "description": "Rate Limited", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/mcp": { + "post": { + "summary": "Handle Mcp Request", + "description": "Bridge endpoint to translate Frontend MCP JSON-RPC calls.\nMOCK IMPLEMENTATION to verify integration contract without heavy ML dependencies.", + "operationId": "handle_mcp_request_mcp_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MCPRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/health": { + "get": { + "summary": "Legacy Health", + "description": "Legacy health endpoint - redirects to v1", + "operationId": "legacy_health_health_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + } + } + } + }, + "/api/chat": { + "post": { + "summary": "Legacy Chat", + "description": "Legacy chat endpoint - redirects to v1 with data preservation", + "operationId": "legacy_chat_api_chat_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "additionalProperties": true, + "type": "object", + "title": "Request" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/process-video-markdown": { + "post": { + "summary": "Legacy Process Video Markdown", + "description": "Legacy markdown processing endpoint", + "operationId": "legacy_process_video_markdown_api_process_video_markdown_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "additionalProperties": true, + "type": "object", + "title": "Request" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/process-video": { + "post": { + "summary": "Legacy Process Video", + "description": "Legacy video processing endpoint", + "operationId": "legacy_process_video_api_process_video_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "additionalProperties": true, + "type": "object", + "title": "Request" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/metrics": { + "get": { + "summary": "Legacy Metrics", + "description": "Legacy metrics endpoint", + "operationId": "legacy_metrics_metrics_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + } + } + } + }, + "/connectors/health": { + "get": { + "summary": "Legacy Connectors Health", + "description": "Legacy connector health endpoint", + "operationId": "legacy_connectors_health_connectors_health_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + } + } + } + }, + "/api/cache/{video_id}": { + "delete": { + "summary": "Legacy Clear Video Cache", + "description": "Legacy cache clearing endpoint", + "operationId": "legacy_clear_video_cache_api_cache__video_id__delete", + "parameters": [ + { + "name": "video_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "Video Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/cache/stats": { + "get": { + "summary": "Legacy Cache Stats", + "description": "Legacy cache stats endpoint", + "operationId": "legacy_cache_stats_api_cache_stats_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + } + } + } + }, + "/system/info": { + "get": { + "summary": "System Info", + "description": "Get comprehensive system information", + "operationId": "system_info_system_info_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + } + } + } + } + }, + "components": { + "schemas": { + "AgentDispatchRequest": { + "properties": { + "job_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Job Id" + }, + "events": { + "items": { + "additionalProperties": true, + "type": "object" + }, + "type": "array", + "title": "Events" + }, + "transcript": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Transcript", + "description": "Transcript text \u2014 events will be auto-extracted if events list is empty" + }, + "agent_types": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Agent Types", + "description": "Specific agent types to dispatch" + } + }, + "type": "object", + "title": "AgentDispatchRequest", + "description": "Request to dispatch agents for a set of events." + }, + "ApiResponse": { + "properties": { + "status": { + "type": "string", + "title": "Status", + "description": "'success' or 'error'" + }, + "data": { + "anyOf": [ + {}, + { + "type": "null" + } + ], + "title": "Data", + "description": "Response payload" + }, + "error": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Error", + "description": "Error message (when status='error')" + }, + "detail": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Detail", + "description": "Additional error detail" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "title": "Timestamp" + }, + "request_id": { + "type": "string", + "title": "Request Id" + } + }, + "type": "object", + "required": [ + "status" + ], + "title": "ApiResponse", + "description": "Standardized API response wrapper used by all endpoints." + }, + "CacheStats": { + "properties": { + "total_cached_videos": { + "type": "integer", + "title": "Total Cached Videos", + "description": "Total cached videos" + }, + "categories": { + "additionalProperties": true, + "type": "object", + "title": "Categories", + "description": "Cache by category" + }, + "total_size_mb": { + "type": "number", + "title": "Total Size Mb", + "description": "Total cache size in MB" + }, + "oldest_cache": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Oldest Cache", + "description": "Oldest cache entry timestamp" + }, + "newest_cache": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Newest Cache", + "description": "Newest cache entry timestamp" + } + }, + "type": "object", + "required": [ + "total_cached_videos", + "categories", + "total_size_mb" + ], + "title": "CacheStats", + "description": "Response model for cache statistics", + "example": { + "categories": { + "education": { + "count": 15, + "size_mb": 25.3 + }, + "technology": { + "count": 27, + "size_mb": 41.7 + } + }, + "newest_cache": "2024-01-01T14:30:00Z", + "oldest_cache": "2024-01-01T10:00:00Z", + "total_cached_videos": 42, + "total_size_mb": 67.0 + } + }, + "ChatRequest": { + "properties": { + "query": { + "type": "string", + "maxLength": 2000, + "minLength": 1, + "title": "Query", + "description": "User message" + }, + "video_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Video Id", + "description": "Video identifier" + }, + "video_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Video Url", + "description": "Video URL" + }, + "context": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Context", + "description": "Chat context", + "default": "tooltip-assistant" + }, + "session_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Session Id", + "description": "Session identifier", + "default": "default" + }, + "history": { + "anyOf": [ + { + "items": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "History", + "description": "Chat history" + } + }, + "type": "object", + "required": [ + "query" + ], + "title": "ChatRequest", + "description": "Request model for chat endpoint", + "example": { + "context": "tooltip-assistant", + "query": "How can I process a YouTube video?", + "session_id": "user123", + "video_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ" + } + }, + "ChatResponse": { + "properties": { + "response": { + "type": "string", + "title": "Response", + "description": "AI assistant response" + }, + "status": { + "type": "string", + "title": "Status", + "description": "Response status" + }, + "session_id": { + "type": "string", + "title": "Session Id", + "description": "Session identifier" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "title": "Timestamp", + "description": "Response timestamp" + } + }, + "type": "object", + "required": [ + "response", + "status", + "session_id", + "timestamp" + ], + "title": "ChatResponse", + "description": "Response model for chat endpoint", + "example": { + "response": "I can help you process YouTube videos for analysis...", + "session_id": "user123", + "status": "success", + "timestamp": "2024-01-01T12:00:00Z" + } + }, + "ErrorResponse": { + "properties": { + "error": { + "type": "string", + "title": "Error", + "description": "Error message" + }, + "detail": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Detail", + "description": "Error details" + }, + "error_type": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Error Type", + "description": "Error type/category" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "title": "Timestamp", + "description": "Error timestamp" + }, + "path": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Path", + "description": "Request path" + } + }, + "type": "object", + "required": [ + "error", + "timestamp" + ], + "title": "ErrorResponse", + "description": "Standard error response model", + "example": { + "detail": "Invalid YouTube URL format", + "error": "Validation error", + "error_type": "validation_error", + "path": "/api/v1/process-video", + "timestamp": "2024-01-01T12:00:00Z" + } + }, + "EventExtractRequest": { + "properties": { + "job_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Job Id", + "description": "Job ID from video processing" + }, + "transcript": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Transcript", + "description": "Raw transcript text" + }, + "video_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Video Url" + } + }, + "type": "object", + "title": "EventExtractRequest", + "description": "Request to extract events from a transcript." + }, + "FeedbackRequest": { + "properties": { + "video_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Video Id", + "description": "Related video ID" + }, + "feedback_type": { + "type": "string", + "title": "Feedback Type", + "description": "Type of feedback" + }, + "rating": { + "anyOf": [ + { + "type": "integer", + "maximum": 5.0, + "minimum": 1.0 + }, + { + "type": "null" + } + ], + "title": "Rating", + "description": "Rating (1-5)" + }, + "comment": { + "anyOf": [ + { + "type": "string", + "maxLength": 1000 + }, + { + "type": "null" + } + ], + "title": "Comment", + "description": "Feedback comment" + }, + "user_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "User Id", + "description": "User identifier" + }, + "metadata": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Metadata", + "description": "Additional metadata", + "default": {} + } + }, + "type": "object", + "required": [ + "feedback_type" + ], + "title": "FeedbackRequest", + "description": "Request model for feedback submission", + "example": { + "comment": "Excellent video analysis results!", + "feedback_type": "quality", + "metadata": { + "source": "web_interface" + }, + "rating": 5, + "user_id": "user123", + "video_id": "jNQXAC9IVRw" + } + }, + "FeedbackResponse": { + "properties": { + "status": { + "type": "string", + "title": "Status", + "description": "Submission status" + }, + "message": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Message", + "description": "Response message" + }, + "feedback_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Feedback Id", + "description": "Feedback identifier" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "title": "Timestamp", + "description": "Submission timestamp" + } + }, + "type": "object", + "required": [ + "status", + "timestamp" + ], + "title": "FeedbackResponse", + "description": "Response model for feedback submission", + "example": { + "feedback_id": "fb123456", + "message": "Thank you for your feedback!", + "status": "ok", + "timestamp": "2024-01-01T12:00:00Z" + } + }, + "GeminiBatchRequest": { + "properties": { + "requests": { + "items": { + "additionalProperties": true, + "type": "object" + }, + "type": "array", + "minItems": 1, + "title": "Requests", + "description": "List of generateContent requests" + }, + "model_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Model Name", + "description": "Optional model override" + }, + "wait": { + "type": "boolean", + "title": "Wait", + "description": "Wait for completion before returning", + "default": false + }, + "poll_interval": { + "anyOf": [ + { + "type": "number", + "minimum": 0.1 + }, + { + "type": "null" + } + ], + "title": "Poll Interval", + "description": "Polling interval when waiting", + "default": 5.0 + }, + "timeout": { + "anyOf": [ + { + "type": "number", + "minimum": 1.0 + }, + { + "type": "null" + } + ], + "title": "Timeout", + "description": "Maximum wait time in seconds", + "default": 600.0 + }, + "batch_params": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Batch Params", + "description": "Additional Gemini batch parameters" + } + }, + "type": "object", + "required": [ + "requests" + ], + "title": "GeminiBatchRequest", + "description": "Request payload for Gemini batch submission" + }, + "GeminiBatchResponse": { + "properties": { + "success": { + "type": "boolean", + "title": "Success" + }, + "operation": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Operation" + }, + "result": { + "anyOf": [ + {}, + { + "type": "null" + } + ], + "title": "Result" + }, + "completed": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "title": "Completed" + }, + "error": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Error" + }, + "latency": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "title": "Latency" + } + }, + "type": "object", + "required": [ + "success" + ], + "title": "GeminiBatchResponse", + "description": "Response payload for Gemini batch submission" + }, + "GeminiCacheRequest": { + "properties": { + "contents": { + "anyOf": [ + { + "type": "string" + }, + { + "additionalProperties": true, + "type": "object" + }, + { + "items": {}, + "type": "array" + } + ], + "title": "Contents", + "description": "Prompt or content payload to cache" + }, + "model_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Model Name", + "description": "Specific model to use for caching" + }, + "ttl_seconds": { + "type": "integer", + "minimum": 60.0, + "title": "Ttl Seconds", + "description": "Cache time-to-live in seconds", + "default": 3600 + }, + "display_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Display Name", + "description": "Friendly name for the cache entry" + }, + "generation_params": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Generation Params", + "description": "Additional Gemini parameters" + } + }, + "type": "object", + "required": [ + "contents" + ], + "title": "GeminiCacheRequest", + "description": "Request payload for Gemini cache creation" + }, + "GeminiCacheResponse": { + "properties": { + "success": { + "type": "boolean", + "title": "Success" + }, + "cache": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Cache" + }, + "error": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Error" + }, + "latency": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "title": "Latency" + } + }, + "type": "object", + "required": [ + "success" + ], + "title": "GeminiCacheResponse", + "description": "Response payload for Gemini cache creation" + }, + "GeminiTokenRequest": { + "properties": { + "model_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Model Name", + "description": "Model alias to scope the token" + }, + "audience": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Audience", + "description": "Audience claim for the token" + }, + "ttl_seconds": { + "anyOf": [ + { + "type": "integer", + "minimum": 60.0 + }, + { + "type": "null" + } + ], + "title": "Ttl Seconds", + "description": "Token time-to-live in seconds" + }, + "token_params": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Token Params", + "description": "Additional token parameters" + } + }, + "type": "object", + "title": "GeminiTokenRequest", + "description": "Request payload for Gemini ephemeral token creation" + }, + "GeminiTokenResponse": { + "properties": { + "success": { + "type": "boolean", + "title": "Success" + }, + "token": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Token" + }, + "error": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Error" + }, + "latency": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "title": "Latency" + } + }, + "type": "object", + "required": [ + "success" + ], + "title": "GeminiTokenResponse", + "description": "Response payload for Gemini ephemeral token creation" + }, + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail" + } + }, + "type": "object", + "title": "HTTPValidationError" + }, + "HealthResponse": { + "properties": { + "status": { + "type": "string", + "title": "Status", + "description": "Overall health status" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "title": "Timestamp", + "description": "Health check timestamp" + }, + "version": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Version", + "description": "API version" + }, + "components": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Components", + "description": "Component health details", + "default": {} + } + }, + "type": "object", + "required": [ + "status", + "timestamp" + ], + "title": "HealthResponse", + "description": "Response model for health checks", + "example": { + "components": { + "cache": "healthy", + "video_processor": "available" + }, + "status": "healthy", + "timestamp": "2024-01-01T12:00:00Z", + "version": "2.0.0" + } + }, + "MCPRequest": { + "properties": { + "method": { + "type": "string", + "title": "Method" + }, + "params": { + "additionalProperties": true, + "type": "object", + "title": "Params" + } + }, + "type": "object", + "required": [ + "method", + "params" + ], + "title": "MCPRequest" + }, + "MarkdownRequest": { + "properties": { + "video_url": { + "type": "string", + "title": "Video Url", + "description": "YouTube video URL" + }, + "force_regenerate": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "title": "Force Regenerate", + "description": "Force cache regeneration", + "default": false + } + }, + "type": "object", + "required": [ + "video_url" + ], + "title": "MarkdownRequest", + "description": "Request model for markdown processing", + "example": { + "force_regenerate": false, + "video_url": "https://www.youtube.com/watch?v=jNQXAC9IVRw" + } + }, + "MarkdownResponse": { + "properties": { + "video_id": { + "type": "string", + "title": "Video Id", + "description": "YouTube video ID" + }, + "video_url": { + "type": "string", + "title": "Video Url", + "description": "Original video URL" + }, + "metadata": { + "additionalProperties": true, + "type": "object", + "title": "Metadata", + "description": "Video metadata" + }, + "markdown_content": { + "type": "string", + "title": "Markdown Content", + "description": "Generated markdown content" + }, + "cached": { + "type": "boolean", + "title": "Cached", + "description": "Whether result was cached" + }, + "save_path": { + "type": "string", + "title": "Save Path", + "description": "File save path" + }, + "processing_time": { + "type": "string", + "title": "Processing Time", + "description": "Processing duration" + }, + "status": { + "type": "string", + "title": "Status", + "description": "Processing status" + } + }, + "type": "object", + "required": [ + "video_id", + "video_url", + "metadata", + "markdown_content", + "cached", + "save_path", + "processing_time", + "status" + ], + "title": "MarkdownResponse", + "description": "Response model for markdown processing", + "example": { + "cached": false, + "markdown_content": "# Sample Video Analysis\n\n...", + "metadata": { + "duration": "3:32", + "title": "Sample Video" + }, + "processing_time": "15.3s", + "save_path": "/path/to/analysis.md", + "status": "success", + "video_id": "jNQXAC9IVRw", + "video_url": "https://www.youtube.com/watch?v=jNQXAC9IVRw" + } + }, + "TranscriptActionRequest": { + "properties": { + "video_url": { + "type": "string", + "title": "Video Url", + "description": "YouTube video URL" + }, + "language": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Language", + "description": "Preferred transcript language", + "default": "en" + }, + "transcript_text": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Transcript Text", + "description": "Optional pre-fetched transcript text" + }, + "video_options": { + "anyOf": [ + { + "$ref": "#/components/schemas/VideoClipOptions" + }, + { + "type": "null" + } + ], + "description": "Optional Gemini video metadata controls (clip window, fps, resolution)" + } + }, + "type": "object", + "required": [ + "video_url" + ], + "title": "TranscriptActionRequest", + "description": "Request model for transcript-to-action workflow" + }, + "TranscriptActionResponse": { + "properties": { + "success": { + "type": "boolean", + "title": "Success" + }, + "video_url": { + "type": "string", + "title": "Video Url" + }, + "metadata": { + "additionalProperties": true, + "type": "object", + "title": "Metadata" + }, + "transcript": { + "additionalProperties": true, + "type": "object", + "title": "Transcript" + }, + "outputs": { + "additionalProperties": true, + "type": "object", + "title": "Outputs" + }, + "errors": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Errors" + }, + "orchestration_meta": { + "additionalProperties": true, + "type": "object", + "title": "Orchestration Meta" + } + }, + "type": "object", + "required": [ + "success", + "video_url", + "metadata", + "transcript", + "outputs", + "orchestration_meta" + ], + "title": "TranscriptActionResponse", + "description": "Response model for transcript-to-action workflow" + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + } + ] + }, + "type": "array", + "title": "Location" + }, + "msg": { + "type": "string", + "title": "Message" + }, + "type": { + "type": "string", + "title": "Error Type" + }, + "input": { + "title": "Input" + }, + "ctx": { + "type": "object", + "title": "Context" + } + }, + "type": "object", + "required": [ + "loc", + "msg", + "type" + ], + "title": "ValidationError" + }, + "VideoClipOptions": { + "properties": { + "start_seconds": { + "anyOf": [ + { + "type": "number", + "minimum": 0.0 + }, + { + "type": "null" + } + ], + "title": "Start Seconds", + "description": "Start offset (seconds) when requesting Gemini video processing" + }, + "end_seconds": { + "anyOf": [ + { + "type": "number", + "exclusiveMinimum": 0.0 + }, + { + "type": "null" + } + ], + "title": "End Seconds", + "description": "End offset (seconds); must be greater than start_seconds when provided" + }, + "fps": { + "anyOf": [ + { + "type": "number", + "maximum": 30.0, + "exclusiveMinimum": 0.0 + }, + { + "type": "null" + } + ], + "title": "Fps", + "description": "Sampling rate for Gemini video frames; defaults to API standard when omitted" + } + }, + "type": "object", + "title": "VideoClipOptions", + "description": "Optional video clipping and sampling controls for Gemini processing." + }, + "VideoProcessJobRequest": { + "properties": { + "video_url": { + "type": "string", + "title": "Video Url", + "description": "YouTube video URL" + }, + "language": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Language", + "description": "Transcript language", + "default": "en" + }, + "options": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Options" + } + }, + "type": "object", + "required": [ + "video_url" + ], + "title": "VideoProcessJobRequest", + "description": "Request to start async video processing." + }, + "VideoProcessingRequest": { + "properties": { + "video_url": { + "type": "string", + "title": "Video Url", + "description": "YouTube video URL" + }, + "options": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Options", + "description": "Processing options", + "default": {} + } + }, + "type": "object", + "required": [ + "video_url" + ], + "title": "VideoProcessingRequest", + "description": "Request model for video processing", + "example": { + "options": { + "include_transcript": true, + "quality": "high" + }, + "video_url": "https://www.youtube.com/watch?v=jNQXAC9IVRw" + } + }, + "VideoToSoftwareRequest": { + "properties": { + "url": { + "type": "string", + "title": "Url", + "description": "YouTube video URL" + }, + "project_type": { + "type": "string", + "title": "Project Type", + "description": "Project type (web, api, ml, mobile)", + "default": "web" + }, + "deployment_target": { + "type": "string", + "title": "Deployment Target", + "description": "Deployment platform", + "default": "vercel" + }, + "features": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Features", + "description": "Additional features to implement", + "default": [] + } + }, + "type": "object", + "required": [ + "url" + ], + "title": "VideoToSoftwareRequest", + "description": "Request model for video-to-software conversion", + "example": { + "deployment_target": "vercel", + "features": [ + "responsive_design", + "dark_mode" + ], + "project_type": "web", + "video_url": "https://www.youtube.com/watch?v=bMknfKXIFA8" + } + }, + "VideoToSoftwareResponse": { + "properties": { + "video_url": { + "type": "string", + "title": "Video Url", + "description": "Original video URL" + }, + "project_name": { + "type": "string", + "title": "Project Name", + "description": "Generated project name" + }, + "project_type": { + "type": "string", + "title": "Project Type", + "description": "Project type" + }, + "deployment_target": { + "type": "string", + "title": "Deployment Target", + "description": "Deployment target" + }, + "live_url": { + "type": "string", + "title": "Live Url", + "description": "Live deployment URL" + }, + "github_repo": { + "type": "string", + "title": "Github Repo", + "description": "GitHub repository URL" + }, + "build_status": { + "type": "string", + "title": "Build Status", + "description": "Build status" + }, + "processing_time": { + "type": "string", + "title": "Processing Time", + "description": "Total processing time" + }, + "features_implemented": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Features Implemented", + "description": "Implemented features" + }, + "video_analysis": { + "additionalProperties": true, + "type": "object", + "title": "Video Analysis", + "description": "Video analysis results" + }, + "code_generation": { + "additionalProperties": true, + "type": "object", + "title": "Code Generation", + "description": "Code generation details" + }, + "deployment": { + "additionalProperties": true, + "type": "object", + "title": "Deployment", + "description": "Deployment information" + }, + "status": { + "type": "string", + "title": "Status", + "description": "Overall status" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "title": "Timestamp", + "description": "Completion timestamp" + } + }, + "type": "object", + "required": [ + "video_url", + "project_name", + "project_type", + "deployment_target", + "live_url", + "github_repo", + "build_status", + "processing_time", + "features_implemented", + "video_analysis", + "code_generation", + "deployment", + "status", + "timestamp" + ], + "title": "VideoToSoftwareResponse", + "description": "Response model for video-to-software conversion", + "example": { + "build_status": "completed", + "code_generation": { + "framework": "React" + }, + "deployment": { + "status": "success" + }, + "deployment_target": "vercel", + "features_implemented": [ + "responsive_design", + "dark_mode" + ], + "github_repo": "https://github.com/user/sample-video-app", + "live_url": "https://sample-video-app.vercel.app", + "processing_time": "45.2s", + "project_name": "sample-video-app", + "project_type": "web", + "status": "success", + "timestamp": "2024-01-01T12:00:00Z", + "video_analysis": { + "status": "success" + }, + "video_url": "https://www.youtube.com/watch?v=bMknfKXIFA8" + } + } + }, + "securitySchemes": { + "ApiKeyAuth": { + "type": "apiKey", + "in": "header", + "name": "X-API-Key" + } + } + }, + "servers": [ + { + "url": "http://localhost:8000", + "description": "Development server" + }, + { + "url": "https://api.uvai.io", + "description": "Production server" + } + ], + "tags": [ + { + "name": "API v1", + "description": "Version 1 of the API - current stable release" + }, + { + "name": "Health", + "description": "Health check and monitoring endpoints" + }, + { + "name": "Video Processing", + "description": "Core video processing and analysis endpoints" + }, + { + "name": "Cache Management", + "description": "Cache control and statistics endpoints" + }, + { + "name": "Data & Analytics", + "description": "Data retrieval and analytics endpoints" + } + ] +} \ No newline at end of file diff --git a/scripts/export_openapi.py b/scripts/export_openapi.py new file mode 100644 index 00000000..e4e61643 --- /dev/null +++ b/scripts/export_openapi.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 +""" +Export OpenAPI Specification +============================ + +Exports the EventRelay FastAPI application's OpenAPI schema to a JSON file. +This spec is used as input for Stainless SDK generation. + +Usage: + python scripts/export_openapi.py + python scripts/export_openapi.py --output openapi/eventrelay.openapi.json + python scripts/export_openapi.py --format yaml +""" + +from __future__ import annotations + +import argparse +import json +import os +import sys +from pathlib import Path + +# Ensure src/ is on the path so package imports resolve +_repo_root = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(_repo_root / "src")) + +# Set minimal env defaults required for import +os.environ.setdefault("DATABASE_URL", "sqlite:///./tmp_export.db") + + +def _get_openapi_schema() -> dict: + """Import the FastAPI app and return its OpenAPI schema dict.""" + try: + from youtube_extension.backend.main import app # type: ignore[import] + except Exception as exc: + print(f"ERROR: Failed to import FastAPI app: {exc}", file=sys.stderr) + sys.exit(1) + + return app.openapi() + + +def export_json(schema: dict, output_path: Path) -> None: + output_path.parent.mkdir(parents=True, exist_ok=True) + with output_path.open("w", encoding="utf-8") as fh: + json.dump(schema, fh, indent=2, default=str) + print(f"OpenAPI JSON written to {output_path}") + + +def export_yaml(schema: dict, output_path: Path) -> None: + try: + import yaml # type: ignore[import] + except ImportError: + print("ERROR: PyYAML is required for YAML export. Run: pip install PyYAML", file=sys.stderr) + sys.exit(1) + + output_path.parent.mkdir(parents=True, exist_ok=True) + with output_path.open("w", encoding="utf-8") as fh: + yaml.dump(schema, fh, allow_unicode=True, sort_keys=False) + print(f"OpenAPI YAML written to {output_path}") + + +def main() -> None: + parser = argparse.ArgumentParser(description="Export EventRelay OpenAPI spec") + parser.add_argument( + "--output", + type=Path, + default=Path("openapi/eventrelay.openapi.json"), + help="Destination file path (default: openapi/eventrelay.openapi.json)", + ) + parser.add_argument( + "--format", + choices=["json", "yaml"], + default="json", + help="Output format (default: json)", + ) + args = parser.parse_args() + + schema = _get_openapi_schema() + + if args.format == "yaml": + yaml_path = args.output.with_suffix(".yaml") if args.output.suffix == ".json" else args.output + export_yaml(schema, yaml_path) + else: + export_json(schema, args.output) + + +if __name__ == "__main__": + main() diff --git a/sdk/python/README.md b/sdk/python/README.md new file mode 100644 index 00000000..5cf33a23 --- /dev/null +++ b/sdk/python/README.md @@ -0,0 +1,73 @@ +# EventRelay Python SDK + +Type-safe Python client for the [EventRelay API](https://github.com/groupthinking/EventRelay). + +Generated via [Stainless](https://stainlessapi.com) from the EventRelay OpenAPI spec. + +## Installation + +```bash +pip install eventrelay-sdk +``` + +## Quick Start + +```python +from eventrelay_sdk import EventRelayClient + +client = EventRelayClient( + api_key="your-api-key", # optional; reads EVENTRELAY_API_KEY from env + base_url="https://api.uvai.io", # optional; defaults to production +) + +# Process a YouTube video +job = client.videos.process(video_url="https://www.youtube.com/watch?v=auJzb1D-fag") +print(job.job_id) + +# Poll status +status = client.videos.get_status(job_id=job.job_id) +print(status.status) + +# Extract events from transcript +events = client.events.extract(transcript="The speaker discussed building a React app...") +for event in events.events: + print(event.type, event.title) + +# Dispatch agents for events +dispatch = client.agents.dispatch(events=[e.model_dump() for e in events.events]) +for execution in dispatch.executions: + print(execution.agent_type, execution.status) +``` + +## Async Usage + +```python +import asyncio +from eventrelay_sdk import AsyncEventRelayClient + +async def main(): + async with AsyncEventRelayClient(api_key="...") as client: + job = await client.videos.process( + video_url="https://www.youtube.com/watch?v=auJzb1D-fag" + ) + print(job.job_id) + +asyncio.run(main()) +``` + +## Resources + +- **`client.videos`** — Process YouTube videos, poll job status, manage video library +- **`client.events`** — Extract structured events from transcripts +- **`client.agents`** — Dispatch and monitor AI agents +- **`client.transcript`** — Transcript-action workflow +- **`client.chat`** — Conversational AI assistant +- **`client.health`** — Health and readiness checks + +## Publishing to PyPI + +```bash +cd sdk/python +python -m build +twine upload dist/* +``` diff --git a/sdk/python/eventrelay_sdk/__init__.py b/sdk/python/eventrelay_sdk/__init__.py new file mode 100644 index 00000000..de24083e --- /dev/null +++ b/sdk/python/eventrelay_sdk/__init__.py @@ -0,0 +1,54 @@ +""" +EventRelay Python SDK +===================== + +Type-safe Python client for the EventRelay API. +Auto-generated via Stainless from openapi/eventrelay.openapi.json. + +Usage:: + + from eventrelay_sdk import EventRelayClient + + client = EventRelayClient(api_key="...", base_url="http://localhost:8000") + job = client.videos.process(video_url="https://www.youtube.com/watch?v=auJzb1D-fag") +""" + +from __future__ import annotations + +from .client import AsyncEventRelayClient, EventRelayClient +from .types import ( + AgentDispatchRequest, + AgentDispatchResponse, + AgentStatusResponse, + ChatRequest, + ChatResponse, + EventExtractRequest, + EventExtractResponse, + HealthResponse, + TranscriptActionRequest, + TranscriptActionResponse, + VideoJobStatusResponse, + VideoProcessJobRequest, + VideoProcessJobResponse, +) + +__all__ = [ + "EventRelayClient", + "AsyncEventRelayClient", + # Request / Response types + "VideoProcessJobRequest", + "VideoProcessJobResponse", + "VideoJobStatusResponse", + "EventExtractRequest", + "EventExtractResponse", + "AgentDispatchRequest", + "AgentDispatchResponse", + "AgentStatusResponse", + "TranscriptActionRequest", + "TranscriptActionResponse", + "ChatRequest", + "ChatResponse", + "HealthResponse", +] + +__version__ = "0.1.0" diff --git a/sdk/python/eventrelay_sdk/client.py b/sdk/python/eventrelay_sdk/client.py new file mode 100644 index 00000000..4a1dc0c8 --- /dev/null +++ b/sdk/python/eventrelay_sdk/client.py @@ -0,0 +1,232 @@ +""" +EventRelay API clients — synchronous and asynchronous. + +Both clients expose the same resource-based interface:: + + client.videos → :class:`VideosResource` + client.events → :class:`EventsResource` + client.agents → :class:`AgentsResource` + client.transcript → :class:`TranscriptResource` + client.chat → :class:`ChatResource` + client.health → :class:`HealthResource` +""" + +from __future__ import annotations + +import os +from typing import Any, Optional + +import httpx + +from .resources.agents import AgentsResource, AsyncAgentsResource +from .resources.chat import AsyncChatResource, ChatResource +from .resources.events import AsyncEventsResource, EventsResource +from .resources.health import AsyncHealthResource, HealthResource +from .resources.transcript import AsyncTranscriptResource, TranscriptResource +from .resources.videos import AsyncVideosResource, VideosResource + +_DEFAULT_BASE_URL = "https://api.uvai.io" +_DEFAULT_TIMEOUT = 60.0 +_DEFAULT_MAX_RETRIES = 2 +_RETRY_STATUS_CODES = frozenset({429, 500, 502, 503, 504}) + + +class EventRelayClient: + """Synchronous EventRelay API client. + + Args: + api_key: API key for authentication. Falls back to the + ``EVENTRELAY_API_KEY`` environment variable when omitted. + base_url: Base URL of the EventRelay API server. + Defaults to ``https://api.uvai.io``. + timeout: HTTP timeout in seconds (default 60). + max_retries: Number of retries on transient errors (default 2). + http_client: Optional pre-configured :class:`httpx.Client` instance. + """ + + def __init__( + self, + *, + api_key: Optional[str] = None, + base_url: str = _DEFAULT_BASE_URL, + timeout: float = _DEFAULT_TIMEOUT, + max_retries: int = _DEFAULT_MAX_RETRIES, + http_client: Optional[httpx.Client] = None, + ) -> None: + self._api_key = api_key or os.environ.get("EVENTRELAY_API_KEY", "") + self._base_url = base_url.rstrip("/") + self._timeout = timeout + self._max_retries = max_retries + self._http_client = http_client or httpx.Client(timeout=timeout) + + # Resource accessors + self.videos = VideosResource(self) + self.events = EventsResource(self) + self.agents = AgentsResource(self) + self.transcript = TranscriptResource(self) + self.chat = ChatResource(self) + self.health = HealthResource(self) + + # ------------------------------------------------------------------ + # Internal helpers + # ------------------------------------------------------------------ + + def _headers(self) -> dict[str, str]: + headers: dict[str, str] = {"Content-Type": "application/json"} + if self._api_key: + headers["X-API-Key"] = self._api_key + return headers + + def _url(self, path: str) -> str: + return f"{self._base_url}{path}" + + def _request(self, method: str, path: str, **kwargs: Any) -> Any: + url = self._url(path) + headers = self._headers() + attempt = 0 + last_exc: Exception = RuntimeError("No attempts made") + while attempt <= self._max_retries: + try: + response = self._http_client.request( + method, url, headers=headers, **kwargs + ) + if response.status_code in _RETRY_STATUS_CODES and attempt < self._max_retries: + attempt += 1 + continue + response.raise_for_status() + return response.json() + except httpx.HTTPStatusError as exc: + last_exc = exc + if exc.response.status_code not in _RETRY_STATUS_CODES: + raise + attempt += 1 + except httpx.RequestError as exc: + last_exc = exc + attempt += 1 + raise last_exc + + def _get(self, path: str, **kwargs: Any) -> Any: + return self._request("GET", path, **kwargs) + + def _post(self, path: str, **kwargs: Any) -> Any: + return self._request("POST", path, **kwargs) + + def _delete(self, path: str, **kwargs: Any) -> Any: + return self._request("DELETE", path, **kwargs) + + def _put(self, path: str, **kwargs: Any) -> Any: + return self._request("PUT", path, **kwargs) + + # ------------------------------------------------------------------ + # Context manager + # ------------------------------------------------------------------ + + def __enter__(self) -> "EventRelayClient": + return self + + def __exit__(self, *args: Any) -> None: + self.close() + + def close(self) -> None: + """Close the underlying HTTP client.""" + self._http_client.close() + + +class AsyncEventRelayClient: + """Asynchronous EventRelay API client. + + Args: + api_key: API key for authentication. Falls back to the + ``EVENTRELAY_API_KEY`` environment variable when omitted. + base_url: Base URL of the EventRelay API server. + timeout: HTTP timeout in seconds (default 60). + max_retries: Number of retries on transient errors (default 2). + http_client: Optional pre-configured :class:`httpx.AsyncClient` instance. + """ + + def __init__( + self, + *, + api_key: Optional[str] = None, + base_url: str = _DEFAULT_BASE_URL, + timeout: float = _DEFAULT_TIMEOUT, + max_retries: int = _DEFAULT_MAX_RETRIES, + http_client: Optional[httpx.AsyncClient] = None, + ) -> None: + self._api_key = api_key or os.environ.get("EVENTRELAY_API_KEY", "") + self._base_url = base_url.rstrip("/") + self._timeout = timeout + self._max_retries = max_retries + self._http_client = http_client or httpx.AsyncClient(timeout=timeout) + + # Resource accessors + self.videos = AsyncVideosResource(self) + self.events = AsyncEventsResource(self) + self.agents = AsyncAgentsResource(self) + self.transcript = AsyncTranscriptResource(self) + self.chat = AsyncChatResource(self) + self.health = AsyncHealthResource(self) + + # ------------------------------------------------------------------ + # Internal helpers + # ------------------------------------------------------------------ + + def _headers(self) -> dict[str, str]: + headers: dict[str, str] = {"Content-Type": "application/json"} + if self._api_key: + headers["X-API-Key"] = self._api_key + return headers + + def _url(self, path: str) -> str: + return f"{self._base_url}{path}" + + async def _request(self, method: str, path: str, **kwargs: Any) -> Any: + url = self._url(path) + headers = self._headers() + attempt = 0 + last_exc: Exception = RuntimeError("No attempts made") + while attempt <= self._max_retries: + try: + response = await self._http_client.request( + method, url, headers=headers, **kwargs + ) + if response.status_code in _RETRY_STATUS_CODES and attempt < self._max_retries: + attempt += 1 + continue + response.raise_for_status() + return response.json() + except httpx.HTTPStatusError as exc: + last_exc = exc + if exc.response.status_code not in _RETRY_STATUS_CODES: + raise + attempt += 1 + except httpx.RequestError as exc: + last_exc = exc + attempt += 1 + raise last_exc + + async def _get(self, path: str, **kwargs: Any) -> Any: + return await self._request("GET", path, **kwargs) + + async def _post(self, path: str, **kwargs: Any) -> Any: + return await self._request("POST", path, **kwargs) + + async def _delete(self, path: str, **kwargs: Any) -> Any: + return await self._request("DELETE", path, **kwargs) + + async def _put(self, path: str, **kwargs: Any) -> Any: + return await self._request("PUT", path, **kwargs) + + # ------------------------------------------------------------------ + # Async context manager + # ------------------------------------------------------------------ + + async def __aenter__(self) -> "AsyncEventRelayClient": + return self + + async def __aexit__(self, *args: Any) -> None: + await self.aclose() + + async def aclose(self) -> None: + """Close the underlying async HTTP client.""" + await self._http_client.aclose() diff --git a/sdk/python/eventrelay_sdk/resources/__init__.py b/sdk/python/eventrelay_sdk/resources/__init__.py new file mode 100644 index 00000000..4f0c60c6 --- /dev/null +++ b/sdk/python/eventrelay_sdk/resources/__init__.py @@ -0,0 +1,23 @@ +"""Resource sub-modules for the EventRelay SDK.""" + +from .agents import AgentsResource, AsyncAgentsResource +from .chat import AsyncChatResource, ChatResource +from .events import AsyncEventsResource, EventsResource +from .health import AsyncHealthResource, HealthResource +from .transcript import AsyncTranscriptResource, TranscriptResource +from .videos import AsyncVideosResource, VideosResource + +__all__ = [ + "VideosResource", + "AsyncVideosResource", + "EventsResource", + "AsyncEventsResource", + "AgentsResource", + "AsyncAgentsResource", + "TranscriptResource", + "AsyncTranscriptResource", + "ChatResource", + "AsyncChatResource", + "HealthResource", + "AsyncHealthResource", +] diff --git a/sdk/python/eventrelay_sdk/resources/agents.py b/sdk/python/eventrelay_sdk/resources/agents.py new file mode 100644 index 00000000..ad129677 --- /dev/null +++ b/sdk/python/eventrelay_sdk/resources/agents.py @@ -0,0 +1,98 @@ +"""Agents resource — dispatch and monitor AI agents.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Optional + +from ..types import AgentDispatchRequest, AgentDispatchResponse, AgentStatusResponse + +if TYPE_CHECKING: + pass + + +class AgentsResource: + """Synchronous agents resource.""" + + def __init__(self, client: Any) -> None: + self._client = client + + def dispatch( + self, + *, + events: Optional[list[dict[str, Any]]] = None, + transcript: Optional[str] = None, + job_id: Optional[str] = None, + agent_types: Optional[list[str]] = None, + ) -> AgentDispatchResponse: + """Dispatch AI agents for the given events. + + When ``events`` is empty or omitted, and ``transcript`` is provided, + the backend will auto-extract events before dispatching. + + Args: + events: Pre-extracted event dicts (from :meth:`EventsResource.extract`). + transcript: Raw transcript — events will be extracted automatically. + job_id: Job ID whose events should be used. + agent_types: Restrict dispatch to specific agent type identifiers. + + Returns: + :class:`AgentDispatchResponse` with a list of + :class:`AgentExecution` objects, each containing an ``agent_id``. + """ + payload = AgentDispatchRequest( + events=events or [], + transcript=transcript, + job_id=job_id, + agent_types=agent_types, + ) + response = self._client._post( + "/api/v1/agents/dispatch", + json=payload.model_dump(exclude_none=True), + ) + return AgentDispatchResponse.model_validate(response) + + def get_status(self, agent_id: str) -> AgentStatusResponse: + """Retrieve the current status of a dispatched agent. + + Args: + agent_id: The ``agent_id`` returned inside an + :class:`AgentExecution` by :meth:`dispatch`. + + Returns: + :class:`AgentStatusResponse` with status and optional result. + """ + response = self._client._get(f"/api/v1/agents/{agent_id}/status") + return AgentStatusResponse.model_validate(response) + + +class AsyncAgentsResource: + """Asynchronous agents resource.""" + + def __init__(self, client: Any) -> None: + self._client = client + + async def dispatch( + self, + *, + events: Optional[list[dict[str, Any]]] = None, + transcript: Optional[str] = None, + job_id: Optional[str] = None, + agent_types: Optional[list[str]] = None, + ) -> AgentDispatchResponse: + """Async version of :meth:`AgentsResource.dispatch`.""" + payload = AgentDispatchRequest( + events=events or [], + transcript=transcript, + job_id=job_id, + agent_types=agent_types, + ) + response = await self._client._post( + "/api/v1/agents/dispatch", + json=payload.model_dump(exclude_none=True), + ) + return AgentDispatchResponse.model_validate(response) + + async def get_status(self, agent_id: str) -> AgentStatusResponse: + """Async version of :meth:`AgentsResource.get_status`.""" + response = await self._client._get(f"/api/v1/agents/{agent_id}/status") + return AgentStatusResponse.model_validate(response) diff --git a/sdk/python/eventrelay_sdk/resources/chat.py b/sdk/python/eventrelay_sdk/resources/chat.py new file mode 100644 index 00000000..b0e8b2b9 --- /dev/null +++ b/sdk/python/eventrelay_sdk/resources/chat.py @@ -0,0 +1,86 @@ +"""Chat resource — conversational AI assistant.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Optional + +from ..types import ChatRequest, ChatResponse + +if TYPE_CHECKING: + pass + + +class ChatResource: + """Synchronous chat resource.""" + + def __init__(self, client: Any) -> None: + self._client = client + + def send( + self, + query: str, + *, + video_id: Optional[str] = None, + video_url: Optional[str] = None, + context: str = "tooltip-assistant", + session_id: str = "default", + history: Optional[list[dict[str, str]]] = None, + ) -> ChatResponse: + """Send a message to the AI assistant. + + Args: + query: User message (max 2000 characters). + video_id: YouTube video ID for context. + video_url: YouTube video URL for context. + context: Context identifier (default ``"tooltip-assistant"``). + session_id: Conversation session identifier. + history: Previous conversation turns. + + Returns: + :class:`ChatResponse` with the assistant's reply. + """ + payload = ChatRequest( + query=query, + video_id=video_id, + video_url=video_url, + context=context, + session_id=session_id, + history=history, + ) + response = self._client._post( + "/api/v1/chat", + json=payload.model_dump(exclude_none=True), + ) + return ChatResponse.model_validate(response) + + +class AsyncChatResource: + """Asynchronous chat resource.""" + + def __init__(self, client: Any) -> None: + self._client = client + + async def send( + self, + query: str, + *, + video_id: Optional[str] = None, + video_url: Optional[str] = None, + context: str = "tooltip-assistant", + session_id: str = "default", + history: Optional[list[dict[str, str]]] = None, + ) -> ChatResponse: + """Async version of :meth:`ChatResource.send`.""" + payload = ChatRequest( + query=query, + video_id=video_id, + video_url=video_url, + context=context, + session_id=session_id, + history=history, + ) + response = await self._client._post( + "/api/v1/chat", + json=payload.model_dump(exclude_none=True), + ) + return ChatResponse.model_validate(response) diff --git a/sdk/python/eventrelay_sdk/resources/events.py b/sdk/python/eventrelay_sdk/resources/events.py new file mode 100644 index 00000000..65053ae0 --- /dev/null +++ b/sdk/python/eventrelay_sdk/resources/events.py @@ -0,0 +1,69 @@ +"""Events resource — extract structured events from transcripts.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Optional + +from ..types import EventExtractRequest, EventExtractResponse + +if TYPE_CHECKING: + pass + + +class EventsResource: + """Synchronous events resource.""" + + def __init__(self, client: Any) -> None: + self._client = client + + def extract( + self, + *, + transcript: Optional[str] = None, + job_id: Optional[str] = None, + video_url: Optional[str] = None, + ) -> EventExtractResponse: + """Extract structured events from a transcript. + + Provide at least one of ``transcript``, ``job_id``, or ``video_url``. + + Args: + transcript: Raw transcript text to extract events from. + job_id: Job ID of a previously processed video whose transcript + will be retrieved from the backend. + video_url: YouTube URL — the backend will fetch the transcript. + + Returns: + :class:`EventExtractResponse` containing a list of + :class:`ExtractedEvent` objects. + """ + payload = EventExtractRequest( + transcript=transcript, job_id=job_id, video_url=video_url + ) + response = self._client._post( + "/api/v1/events/extract", json=payload.model_dump(exclude_none=True) + ) + return EventExtractResponse.model_validate(response) + + +class AsyncEventsResource: + """Asynchronous events resource.""" + + def __init__(self, client: Any) -> None: + self._client = client + + async def extract( + self, + *, + transcript: Optional[str] = None, + job_id: Optional[str] = None, + video_url: Optional[str] = None, + ) -> EventExtractResponse: + """Async version of :meth:`EventsResource.extract`.""" + payload = EventExtractRequest( + transcript=transcript, job_id=job_id, video_url=video_url + ) + response = await self._client._post( + "/api/v1/events/extract", json=payload.model_dump(exclude_none=True) + ) + return EventExtractResponse.model_validate(response) diff --git a/sdk/python/eventrelay_sdk/resources/health.py b/sdk/python/eventrelay_sdk/resources/health.py new file mode 100644 index 00000000..97d2e7f8 --- /dev/null +++ b/sdk/python/eventrelay_sdk/resources/health.py @@ -0,0 +1,52 @@ +"""Health resource — API health and readiness checks.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +from ..types import HealthResponse + +if TYPE_CHECKING: + pass + + +class HealthResource: + """Synchronous health resource.""" + + def __init__(self, client: Any) -> None: + self._client = client + + def check(self) -> HealthResponse: + """Perform a basic health check. + + Returns: + :class:`HealthResponse` with ``status`` field. + """ + response = self._client._get("/api/v1/health") + return HealthResponse.model_validate(response) + + def detailed(self) -> HealthResponse: + """Perform a detailed health check including all sub-services. + + Returns: + :class:`HealthResponse` with ``services`` breakdown. + """ + response = self._client._get("/api/v1/health/detailed") + return HealthResponse.model_validate(response) + + +class AsyncHealthResource: + """Asynchronous health resource.""" + + def __init__(self, client: Any) -> None: + self._client = client + + async def check(self) -> HealthResponse: + """Async version of :meth:`HealthResource.check`.""" + response = await self._client._get("/api/v1/health") + return HealthResponse.model_validate(response) + + async def detailed(self) -> HealthResponse: + """Async version of :meth:`HealthResource.detailed`.""" + response = await self._client._get("/api/v1/health/detailed") + return HealthResponse.model_validate(response) diff --git a/sdk/python/eventrelay_sdk/resources/transcript.py b/sdk/python/eventrelay_sdk/resources/transcript.py new file mode 100644 index 00000000..bea03812 --- /dev/null +++ b/sdk/python/eventrelay_sdk/resources/transcript.py @@ -0,0 +1,67 @@ +"""Transcript resource — run transcript-action workflows.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Optional + +from ..types import TranscriptActionRequest, TranscriptActionResponse + +if TYPE_CHECKING: + pass + + +class TranscriptResource: + """Synchronous transcript resource.""" + + def __init__(self, client: Any) -> None: + self._client = client + + def action( + self, + video_url: str, + *, + action: Optional[str] = None, + options: Optional[dict[str, Any]] = None, + ) -> TranscriptActionResponse: + """Run the transcript-action workflow for a YouTube video. + + Args: + video_url: YouTube video URL. + action: Specific action to perform on the transcript. + options: Additional processing options. + + Returns: + :class:`TranscriptActionResponse` with transcript and actions. + """ + payload = TranscriptActionRequest( + video_url=video_url, action=action, options=options or {} + ) + response = self._client._post( + "/api/v1/transcript-action", + json=payload.model_dump(exclude_none=True), + ) + return TranscriptActionResponse.model_validate(response) + + +class AsyncTranscriptResource: + """Asynchronous transcript resource.""" + + def __init__(self, client: Any) -> None: + self._client = client + + async def action( + self, + video_url: str, + *, + action: Optional[str] = None, + options: Optional[dict[str, Any]] = None, + ) -> TranscriptActionResponse: + """Async version of :meth:`TranscriptResource.action`.""" + payload = TranscriptActionRequest( + video_url=video_url, action=action, options=options or {} + ) + response = await self._client._post( + "/api/v1/transcript-action", + json=payload.model_dump(exclude_none=True), + ) + return TranscriptActionResponse.model_validate(response) diff --git a/sdk/python/eventrelay_sdk/resources/videos.py b/sdk/python/eventrelay_sdk/resources/videos.py new file mode 100644 index 00000000..fe3dc470 --- /dev/null +++ b/sdk/python/eventrelay_sdk/resources/videos.py @@ -0,0 +1,114 @@ +"""Videos resource — process YouTube videos and manage the video library.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Optional + +from ..types import VideoJobStatusResponse, VideoProcessJobRequest, VideoProcessJobResponse + +if TYPE_CHECKING: + import httpx + + +class VideosResource: + """Synchronous videos resource.""" + + def __init__(self, client: Any) -> None: + self._client = client + + def process( + self, + video_url: str, + *, + language: str = "en", + options: Optional[dict[str, Any]] = None, + ) -> VideoProcessJobResponse: + """Submit a YouTube video for async processing. + + Args: + video_url: Full YouTube video URL. + language: Transcript language code (default ``"en"``). + options: Optional processing overrides. + + Returns: + :class:`VideoProcessJobResponse` containing the ``job_id``. + """ + payload = VideoProcessJobRequest( + video_url=video_url, language=language, options=options or {} + ) + response = self._client._post( + "/api/v1/videos/process", json=payload.model_dump() + ) + return VideoProcessJobResponse.model_validate(response) + + def get_status(self, *, job_id: str) -> VideoJobStatusResponse: + """Poll processing status for a given job. + + Args: + job_id: The ``job_id`` returned by :meth:`process`. + + Returns: + :class:`VideoJobStatusResponse` with current status and progress. + """ + response = self._client._get(f"/api/v1/videos/{job_id}/status") + return VideoJobStatusResponse.model_validate(response) + + def list(self) -> list[dict[str, Any]]: + """List all processed videos in the library.""" + return self._client._get("/api/v1/videos") # type: ignore[return-value] + + def retrieve(self, video_id: str) -> dict[str, Any]: + """Retrieve metadata for a single video. + + Args: + video_id: YouTube video ID (11-character string). + """ + return self._client._get(f"/api/v1/videos/{video_id}") # type: ignore[return-value] + + def delete(self, video_id: str) -> dict[str, Any]: + """Remove a processed video from the cache. + + Args: + video_id: YouTube video ID. + """ + return self._client._delete(f"/api/v1/cache/{video_id}") # type: ignore[return-value] + + +class AsyncVideosResource: + """Asynchronous videos resource.""" + + def __init__(self, client: Any) -> None: + self._client = client + + async def process( + self, + video_url: str, + *, + language: str = "en", + options: Optional[dict[str, Any]] = None, + ) -> VideoProcessJobResponse: + """Async version of :meth:`VideosResource.process`.""" + payload = VideoProcessJobRequest( + video_url=video_url, language=language, options=options or {} + ) + response = await self._client._post( + "/api/v1/videos/process", json=payload.model_dump() + ) + return VideoProcessJobResponse.model_validate(response) + + async def get_status(self, *, job_id: str) -> VideoJobStatusResponse: + """Async version of :meth:`VideosResource.get_status`.""" + response = await self._client._get(f"/api/v1/videos/{job_id}/status") + return VideoJobStatusResponse.model_validate(response) + + async def list(self) -> list[dict[str, Any]]: + """Async version of :meth:`VideosResource.list`.""" + return await self._client._get("/api/v1/videos") # type: ignore[return-value] + + async def retrieve(self, video_id: str) -> dict[str, Any]: + """Async version of :meth:`VideosResource.retrieve`.""" + return await self._client._get(f"/api/v1/videos/{video_id}") # type: ignore[return-value] + + async def delete(self, video_id: str) -> dict[str, Any]: + """Async version of :meth:`VideosResource.delete`.""" + return await self._client._delete(f"/api/v1/cache/{video_id}") # type: ignore[return-value] diff --git a/sdk/python/eventrelay_sdk/types.py b/sdk/python/eventrelay_sdk/types.py new file mode 100644 index 00000000..2c630ba6 --- /dev/null +++ b/sdk/python/eventrelay_sdk/types.py @@ -0,0 +1,227 @@ +""" +Pydantic models mirroring the EventRelay API request/response schemas. + +These are derived from openapi/eventrelay.openapi.json and kept in sync +with the backend models defined in: + src/youtube_extension/backend/api/v1/models.py +""" + +from __future__ import annotations + +from datetime import datetime +from enum import Enum +from typing import Any, Optional + +from pydantic import BaseModel, Field + + +# --------------------------------------------------------------------------- +# Enums +# --------------------------------------------------------------------------- + + +class JobStatus(str, Enum): + pending = "pending" + downloading = "downloading" + transcribing = "transcribing" + extracting = "extracting" + complete = "complete" + failed = "failed" + + +class AgentStatus(str, Enum): + queued = "queued" + running = "running" + complete = "complete" + failed = "failed" + + +# --------------------------------------------------------------------------- +# Video Processing +# --------------------------------------------------------------------------- + + +class VideoProcessJobRequest(BaseModel): + """Start async video processing for a YouTube video.""" + + video_url: str = Field(..., description="YouTube video URL") + language: Optional[str] = Field("en", description="Transcript language") + options: Optional[dict[str, Any]] = Field(default_factory=dict) + + +class VideoProcessJobResponse(BaseModel): + """Returned when a video processing job is created.""" + + job_id: str + video_url: str + status: JobStatus = JobStatus.pending + + +class VideoJobStatusResponse(BaseModel): + """Returned when polling a video job's status.""" + + job_id: str + status: JobStatus + progress: float = Field(0.0, ge=0.0, le=100.0) + video_url: Optional[str] = None + transcript: Optional[str] = None + metadata: Optional[dict[str, Any]] = None + error: Optional[str] = None + + +# --------------------------------------------------------------------------- +# Event Extraction +# --------------------------------------------------------------------------- + + +class EventExtractRequest(BaseModel): + """Request to extract events from a transcript.""" + + job_id: Optional[str] = Field(None, description="Job ID from video processing") + transcript: Optional[str] = Field(None, description="Raw transcript text") + video_url: Optional[str] = None + + +class ExtractedEvent(BaseModel): + """A single event extracted from a transcript.""" + + id: str + type: str = Field(..., description="action | mention | topic | insight") + title: str + description: Optional[str] = None + timestamp: Optional[str] = Field(None, description="Time in video, e.g. '02:15'") + confidence: float = Field(1.0, ge=0.0, le=1.0) + + +class EventExtractResponse(BaseModel): + """Response containing extracted events.""" + + job_id: Optional[str] = None + events: list[ExtractedEvent] = Field(default_factory=list) + event_count: int = 0 + + +# --------------------------------------------------------------------------- +# Agent Dispatch +# --------------------------------------------------------------------------- + + +class AgentDispatchRequest(BaseModel): + """Request to dispatch AI agents for a set of events.""" + + job_id: Optional[str] = None + events: list[dict[str, Any]] = Field(default_factory=list) + transcript: Optional[str] = Field( + None, + description="Transcript text — events will be auto-extracted when events list is empty", + ) + agent_types: Optional[list[str]] = Field( + None, description="Specific agent types to dispatch" + ) + + +class AgentExecution(BaseModel): + """Status of a single dispatched agent execution.""" + + agent_id: str + agent_type: str + status: AgentStatus = AgentStatus.queued + progress: float = Field(0.0, ge=0.0, le=100.0) + event_id: Optional[str] = None + result: Optional[dict[str, Any]] = None + error: Optional[str] = None + + +class AgentDispatchResponse(BaseModel): + """Response after dispatching agents.""" + + dispatch_id: str + executions: list[AgentExecution] = Field(default_factory=list) + + +class AgentStatusResponse(BaseModel): + """Status of a single agent execution.""" + + agent_id: str + agent_type: str + status: AgentStatus + progress: float = 0.0 + result: Optional[dict[str, Any]] = None + error: Optional[str] = None + + +# --------------------------------------------------------------------------- +# Transcript Action +# --------------------------------------------------------------------------- + + +class TranscriptActionRequest(BaseModel): + """Request to run the transcript-action workflow.""" + + video_url: str = Field(..., description="YouTube video URL") + action: Optional[str] = Field(None, description="Specific action to perform") + options: Optional[dict[str, Any]] = Field(default_factory=dict) + + +class TranscriptActionResponse(BaseModel): + """Response from the transcript-action workflow.""" + + video_url: str + transcript: Optional[dict[str, Any]] = None + actions: list[dict[str, Any]] = Field(default_factory=list) + status: str = "success" + + +# --------------------------------------------------------------------------- +# Chat +# --------------------------------------------------------------------------- + + +class ChatRequest(BaseModel): + """Request to the conversational AI assistant.""" + + query: str = Field(..., min_length=1, max_length=2000, description="User message") + video_id: Optional[str] = None + video_url: Optional[str] = None + context: Optional[str] = "tooltip-assistant" + session_id: Optional[str] = "default" + history: Optional[list[dict[str, str]]] = None + + +class ChatResponse(BaseModel): + """Response from the conversational AI assistant.""" + + response: str + status: str + session_id: str + timestamp: datetime + + +# --------------------------------------------------------------------------- +# Health +# --------------------------------------------------------------------------- + + +class HealthResponse(BaseModel): + """Health check response.""" + + status: str + version: Optional[str] = None + timestamp: Optional[datetime] = None + services: Optional[dict[str, Any]] = None + + +# --------------------------------------------------------------------------- +# Generic API wrapper +# --------------------------------------------------------------------------- + + +class ApiResponse(BaseModel): + """Standardized API response envelope returned by some endpoints.""" + + status: str + data: Optional[Any] = None + error: Optional[str] = None + detail: Optional[str] = None + timestamp: Optional[datetime] = None + request_id: Optional[str] = None diff --git a/sdk/python/pyproject.toml b/sdk/python/pyproject.toml new file mode 100644 index 00000000..22719947 --- /dev/null +++ b/sdk/python/pyproject.toml @@ -0,0 +1,46 @@ +[build-system] +requires = ["setuptools>=61.0", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "eventrelay-sdk" +version = "0.1.0" +description = "Type-safe Python SDK for the EventRelay API" +readme = "README.md" +requires-python = ">=3.9" +license = {text = "MIT"} +authors = [ + {name = "EventRelay Team", email = "team@uvai.io"}, +] +keywords = ["eventrelay", "youtube", "ai", "sdk", "api-client"] +classifiers = [ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Topic :: Software Development :: Libraries :: Python Modules", +] +dependencies = [ + "httpx>=0.25.0", + "pydantic>=2.0.0", +] + +[project.optional-dependencies] +dev = [ + "pytest>=7.4.0", + "pytest-asyncio>=0.21.0", + "respx>=0.20.0", +] + +[project.urls] +Homepage = "https://github.com/groupthinking/EventRelay" +Documentation = "https://github.com/groupthinking/EventRelay/tree/main/sdk/python" +Repository = "https://github.com/groupthinking/EventRelay" + +[tool.setuptools.packages.find] +where = ["."] +include = ["eventrelay_sdk*"] diff --git a/sdk/typescript/README.md b/sdk/typescript/README.md new file mode 100644 index 00000000..7c6d8b4f --- /dev/null +++ b/sdk/typescript/README.md @@ -0,0 +1,74 @@ +# EventRelay TypeScript SDK + +Type-safe TypeScript/JavaScript client for the [EventRelay API](https://github.com/groupthinking/EventRelay). + +Generated via [Stainless](https://stainlessapi.com) from the EventRelay OpenAPI spec. + +## Installation + +```bash +npm install @eventrelay/sdk +# or +yarn add @eventrelay/sdk +``` + +## Quick Start + +```ts +import { EventRelayClient } from "@eventrelay/sdk"; + +const client = new EventRelayClient({ + apiKey: "your-api-key", // optional; reads EVENTRELAY_API_KEY from env + baseUrl: "https://api.uvai.io", // optional; defaults to production +}); + +// Process a YouTube video +const job = await client.videos.process({ + video_url: "https://www.youtube.com/watch?v=auJzb1D-fag", +}); +console.log(job.job_id); + +// Poll status +const status = await client.videos.getStatus(job.job_id); +console.log(status.status); + +// Extract events from transcript +const events = await client.events.extract({ + transcript: "The speaker discussed building a React app...", +}); +for (const event of events.events) { + console.log(event.type, event.title); +} + +// Dispatch agents for events +const dispatch = await client.agents.dispatch({ events: events.events }); +for (const execution of dispatch.executions) { + console.log(execution.agent_type, execution.status); +} +``` + +## Resources + +| Resource | Description | +|---|---| +| `client.videos` | Process YouTube videos, poll job status, manage video library | +| `client.events` | Extract structured events from transcripts | +| `client.agents` | Dispatch and monitor AI agents | +| `client.transcript` | Transcript-action workflow | +| `client.chat` | Conversational AI assistant | +| `client.health` | Health and readiness checks | + +## Building + +```bash +npm install +npm run build +``` + +## Publishing to npm + +```bash +cd sdk/typescript +npm run build +npm publish --access public +``` diff --git a/sdk/typescript/package-lock.json b/sdk/typescript/package-lock.json new file mode 100644 index 00000000..b14c8865 --- /dev/null +++ b/sdk/typescript/package-lock.json @@ -0,0 +1,3850 @@ +{ + "name": "@eventrelay/sdk", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@eventrelay/sdk", + "version": "0.1.0", + "license": "MIT", + "devDependencies": { + "@types/jest": "^29.0.0", + "@types/node": "^20.0.0", + "jest": "^29.0.0", + "ts-jest": "^29.0.0", + "typescript": "^5.3.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.10", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", + "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.14", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", + "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/node": { + "version": "20.19.37", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.37.tgz", + "integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.9", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.9.tgz", + "integrity": "sha512-OZd0e2mU11ClX8+IdXe3r0dbqMEznRiT4TfbhYIbcRPZkqJ7Qwer8ij3GZAmLsRKa+II9V1v5czCkvmHH3XZBg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001780", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001780.tgz", + "integrity": "sha512-llngX0E7nQci5BPJDqoZSbuZ5Bcs9F5db7EtgfwBerX9XGtkkiO4NwfDDIRzHTTwcYC8vC7bmeUEPGrKlR/TkQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", + "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", + "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.321", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.321.tgz", + "integrity": "sha512-L2C7Q279W2D/J4PLZLk7sebOILDSWos7bMsMNN06rK482umHUrh/3lM8G7IlHFOYip2oAg5nha1rCMxr/rs6ZQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.36", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", + "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-jest": { + "version": "29.4.6", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.6.tgz", + "integrity": "sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bs-logger": "^0.2.6", + "fast-json-stable-stringify": "^2.1.0", + "handlebars": "^4.7.8", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.7.3", + "type-fest": "^4.41.0", + "yargs-parser": "^21.1.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0 || ^30.0.0", + "@jest/types": "^29.0.0 || ^30.0.0", + "babel-jest": "^29.0.0 || ^30.0.0", + "jest": "^29.0.0 || ^30.0.0", + "jest-util": "^29.0.0 || ^30.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jest-util": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-jest/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/sdk/typescript/package.json b/sdk/typescript/package.json new file mode 100644 index 00000000..c321eac9 --- /dev/null +++ b/sdk/typescript/package.json @@ -0,0 +1,41 @@ +{ + "name": "@eventrelay/sdk", + "version": "0.1.0", + "description": "Type-safe TypeScript/JavaScript SDK for the EventRelay API", + "main": "dist/index.js", + "module": "dist/index.mjs", + "types": "dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/index.mjs", + "require": "./dist/index.js", + "types": "./dist/index.d.ts" + } + }, + "files": ["dist", "README.md"], + "scripts": { + "build": "tsc", + "test": "jest", + "lint": "eslint src --ext .ts", + "prepublishOnly": "npm run build" + }, + "keywords": ["eventrelay", "youtube", "ai", "sdk", "api-client"], + "author": "EventRelay Team ", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/groupthinking/EventRelay" + }, + "dependencies": {}, + "devDependencies": { + "typescript": "^5.3.0", + "@types/node": "^20.0.0", + "jest": "^29.0.0", + "@types/jest": "^29.0.0", + "ts-jest": "^29.0.0" + }, + "peerDependencies": {}, + "engines": { + "node": ">=18.0.0" + } +} diff --git a/sdk/typescript/src/client.ts b/sdk/typescript/src/client.ts new file mode 100644 index 00000000..732d25b6 --- /dev/null +++ b/sdk/typescript/src/client.ts @@ -0,0 +1,178 @@ +/** + * EventRelay API client for TypeScript / JavaScript. + * + * Generated via Stainless from openapi/eventrelay.openapi.json. + */ + +import type { EventRelayClientOptions } from "./types"; +import { VideosResource } from "./resources/videos"; +import { EventsResource } from "./resources/events"; +import { AgentsResource } from "./resources/agents"; +import { TranscriptResource } from "./resources/transcript"; +import { ChatResource } from "./resources/chat"; +import { HealthResource } from "./resources/health"; + +const DEFAULT_BASE_URL = "https://api.uvai.io"; +const DEFAULT_TIMEOUT_MS = 60_000; +const DEFAULT_MAX_RETRIES = 2; +const RETRY_STATUS_CODES = new Set([429, 500, 502, 503, 504]); + +/** + * Perform a fetch with retry logic and raise on HTTP errors. + * + * @internal + */ +export async function fetchWithRetry( + url: string, + init: RequestInit, + maxRetries: number, + timeout: number +): Promise { + let attempt = 0; + let lastError: unknown; + + while (attempt <= maxRetries) { + const controller = new AbortController(); + const timerId = setTimeout(() => controller.abort(), timeout); + + try { + const response = await fetch(url, { + ...init, + signal: controller.signal, + }); + clearTimeout(timerId); + + if (RETRY_STATUS_CODES.has(response.status) && attempt < maxRetries) { + attempt++; + continue; + } + + if (!response.ok) { + const body = await response.text().catch(() => ""); + throw new EventRelayAPIError(response.status, response.statusText, body); + } + + return response.json(); + } catch (err) { + clearTimeout(timerId); + lastError = err; + if (err instanceof EventRelayAPIError && !RETRY_STATUS_CODES.has(err.statusCode)) { + throw err; + } + attempt++; + } + } + + throw lastError; +} + +/** Thrown when the EventRelay API returns a non-2xx HTTP status. */ +export class EventRelayAPIError extends Error { + constructor( + public readonly statusCode: number, + public readonly statusText: string, + public readonly body: string + ) { + super(`EventRelay API Error ${statusCode}: ${statusText}`); + this.name = "EventRelayAPIError"; + } +} + +/** + * EventRelay API client. + * + * @example + * ```ts + * import { EventRelayClient } from "@eventrelay/sdk"; + * + * const client = new EventRelayClient({ apiKey: "..." }); + * + * const job = await client.videos.process({ + * video_url: "https://www.youtube.com/watch?v=auJzb1D-fag", + * }); + * console.log(job.job_id); + * ``` + */ +export class EventRelayClient { + private readonly _apiKey: string; + private readonly _baseUrl: string; + private readonly _timeout: number; + private readonly _maxRetries: number; + + /** Process YouTube videos and manage the video library. */ + readonly videos: VideosResource; + /** Extract structured events from transcripts. */ + readonly events: EventsResource; + /** Dispatch and monitor AI agents. */ + readonly agents: AgentsResource; + /** Transcript-action workflows. */ + readonly transcript: TranscriptResource; + /** Conversational AI assistant. */ + readonly chat: ChatResource; + /** API health and readiness checks. */ + readonly health: HealthResource; + + constructor(options: EventRelayClientOptions = {}) { + this._apiKey = + options.apiKey ?? + (typeof process !== "undefined" ? process.env.EVENTRELAY_API_KEY ?? "" : ""); + this._baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, ""); + this._timeout = options.timeout ?? DEFAULT_TIMEOUT_MS; + this._maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES; + + this.videos = new VideosResource(this); + this.events = new EventsResource(this); + this.agents = new AgentsResource(this); + this.transcript = new TranscriptResource(this); + this.chat = new ChatResource(this); + this.health = new HealthResource(this); + } + + /** @internal */ + _headers(): Record { + const headers: Record = { "Content-Type": "application/json" }; + if (this._apiKey) { + headers["X-API-Key"] = this._apiKey; + } + return headers; + } + + /** @internal */ + _url(path: string): string { + return `${this._baseUrl}${path}`; + } + + /** @internal */ + async _get(path: string): Promise { + return fetchWithRetry( + this._url(path), + { method: "GET", headers: this._headers() }, + this._maxRetries, + this._timeout + ); + } + + /** @internal */ + async _post(path: string, body: unknown): Promise { + return fetchWithRetry( + this._url(path), + { + method: "POST", + headers: this._headers(), + body: JSON.stringify(body), + }, + this._maxRetries, + this._timeout + ); + } + + /** @internal */ + async _delete(path: string): Promise { + return fetchWithRetry( + this._url(path), + { method: "DELETE", headers: this._headers() }, + this._maxRetries, + this._timeout + ); + } +} diff --git a/sdk/typescript/src/index.ts b/sdk/typescript/src/index.ts new file mode 100644 index 00000000..fee3b3ea --- /dev/null +++ b/sdk/typescript/src/index.ts @@ -0,0 +1,61 @@ +/** + * EventRelay SDK — TypeScript/JavaScript entry point. + * + * @example + * ```ts + * import { EventRelayClient } from "@eventrelay/sdk"; + * + * const client = new EventRelayClient({ + * apiKey: "your-api-key", + * baseUrl: "http://localhost:8000", + * }); + * + * // Process a YouTube video + * const job = await client.videos.process({ + * video_url: "https://www.youtube.com/watch?v=auJzb1D-fag", + * }); + * console.log(job.job_id); + * + * // Extract events from transcript + * const events = await client.events.extract({ + * transcript: "The speaker discussed building a React app...", + * }); + * console.log(events.events); + * + * // Dispatch agents + * const dispatch = await client.agents.dispatch({ events: events.events }); + * console.log(dispatch.executions); + * ``` + */ + +export { EventRelayClient, EventRelayAPIError } from "./client"; +export type { EventRelayClientOptions } from "./types"; + +// Request / response types +export type { + JobStatus, + AgentStatus, + VideoProcessJobRequest, + VideoProcessJobResponse, + VideoJobStatusResponse, + EventExtractRequest, + ExtractedEvent, + EventExtractResponse, + AgentDispatchRequest, + AgentExecution, + AgentDispatchResponse, + AgentStatusResponse, + TranscriptActionRequest, + TranscriptActionResponse, + ChatRequest, + ChatResponse, + HealthResponse, +} from "./types"; + +// Resource classes (for advanced use-cases / sub-classing) +export { VideosResource } from "./resources/videos"; +export { EventsResource } from "./resources/events"; +export { AgentsResource } from "./resources/agents"; +export { TranscriptResource } from "./resources/transcript"; +export { ChatResource } from "./resources/chat"; +export { HealthResource } from "./resources/health"; diff --git a/sdk/typescript/src/resources/agents.ts b/sdk/typescript/src/resources/agents.ts new file mode 100644 index 00000000..db3d086a --- /dev/null +++ b/sdk/typescript/src/resources/agents.ts @@ -0,0 +1,37 @@ +/** + * Agents resource — dispatch and monitor AI agents. + */ + +import type { EventRelayClient } from "../client"; +import type { + AgentDispatchRequest, + AgentDispatchResponse, + AgentStatusResponse, +} from "../types"; + +export class AgentsResource { + constructor(private readonly _client: EventRelayClient) {} + + /** + * Dispatch AI agents for the given events. + * + * When `events` is empty and `transcript` is provided, the backend will + * auto-extract events before dispatching. + * + * @param params - Dispatch parameters. + * @returns `AgentDispatchResponse` with a list of `AgentExecution` objects. + */ + async dispatch(params: AgentDispatchRequest): Promise { + return this._client._post("/api/v1/agents/dispatch", params) as Promise; + } + + /** + * Retrieve the current status of a dispatched agent. + * + * @param agentId - The `agent_id` returned inside an `AgentExecution` by {@link dispatch}. + * @returns `AgentStatusResponse` with status and optional result. + */ + async getStatus(agentId: string): Promise { + return this._client._get(`/api/v1/agents/${agentId}/status`) as Promise; + } +} diff --git a/sdk/typescript/src/resources/chat.ts b/sdk/typescript/src/resources/chat.ts new file mode 100644 index 00000000..5c146e8f --- /dev/null +++ b/sdk/typescript/src/resources/chat.ts @@ -0,0 +1,20 @@ +/** + * Chat resource — conversational AI assistant. + */ + +import type { EventRelayClient } from "../client"; +import type { ChatRequest, ChatResponse } from "../types"; + +export class ChatResource { + constructor(private readonly _client: EventRelayClient) {} + + /** + * Send a message to the AI assistant. + * + * @param params - `query` is required (max 2000 chars). + * @returns `ChatResponse` with the assistant's reply. + */ + async send(params: ChatRequest): Promise { + return this._client._post("/api/v1/chat", params) as Promise; + } +} diff --git a/sdk/typescript/src/resources/events.ts b/sdk/typescript/src/resources/events.ts new file mode 100644 index 00000000..c0aa7c4a --- /dev/null +++ b/sdk/typescript/src/resources/events.ts @@ -0,0 +1,22 @@ +/** + * Events resource — extract structured events from transcripts. + */ + +import type { EventRelayClient } from "../client"; +import type { EventExtractRequest, EventExtractResponse } from "../types"; + +export class EventsResource { + constructor(private readonly _client: EventRelayClient) {} + + /** + * Extract structured events from a transcript. + * + * Provide at least one of `transcript`, `jobId`, or `videoUrl`. + * + * @param params - Extraction parameters. + * @returns `EventExtractResponse` containing a list of `ExtractedEvent` objects. + */ + async extract(params: EventExtractRequest): Promise { + return this._client._post("/api/v1/events/extract", params) as Promise; + } +} diff --git a/sdk/typescript/src/resources/health.ts b/sdk/typescript/src/resources/health.ts new file mode 100644 index 00000000..46ea4fb4 --- /dev/null +++ b/sdk/typescript/src/resources/health.ts @@ -0,0 +1,28 @@ +/** + * Health resource — API health and readiness checks. + */ + +import type { EventRelayClient } from "../client"; +import type { HealthResponse } from "../types"; + +export class HealthResource { + constructor(private readonly _client: EventRelayClient) {} + + /** + * Perform a basic health check. + * + * @returns `HealthResponse` with `status` field. + */ + async check(): Promise { + return this._client._get("/api/v1/health") as Promise; + } + + /** + * Perform a detailed health check including all sub-services. + * + * @returns `HealthResponse` with `services` breakdown. + */ + async detailed(): Promise { + return this._client._get("/api/v1/health/detailed") as Promise; + } +} diff --git a/sdk/typescript/src/resources/transcript.ts b/sdk/typescript/src/resources/transcript.ts new file mode 100644 index 00000000..68e8899b --- /dev/null +++ b/sdk/typescript/src/resources/transcript.ts @@ -0,0 +1,23 @@ +/** + * Transcript resource — run transcript-action workflows. + */ + +import type { EventRelayClient } from "../client"; +import type { TranscriptActionRequest, TranscriptActionResponse } from "../types"; + +export class TranscriptResource { + constructor(private readonly _client: EventRelayClient) {} + + /** + * Run the transcript-action workflow for a YouTube video. + * + * @param params - `video_url` is required. + * @returns `TranscriptActionResponse` with transcript and actions. + */ + async action(params: TranscriptActionRequest): Promise { + return this._client._post( + "/api/v1/transcript-action", + params + ) as Promise; + } +} diff --git a/sdk/typescript/src/resources/videos.ts b/sdk/typescript/src/resources/videos.ts new file mode 100644 index 00000000..33f960be --- /dev/null +++ b/sdk/typescript/src/resources/videos.ts @@ -0,0 +1,59 @@ +/** + * Videos resource — process YouTube videos and manage the video library. + */ + +import type { EventRelayClient } from "../client"; +import type { + VideoProcessJobRequest, + VideoProcessJobResponse, + VideoJobStatusResponse, +} from "../types"; + +export class VideosResource { + constructor(private readonly _client: EventRelayClient) {} + + /** + * Submit a YouTube video for async processing. + * + * @param params - `video_url` is required; `language` and `options` are optional. + * @returns `VideoProcessJobResponse` containing the `job_id`. + */ + async process(params: VideoProcessJobRequest): Promise { + return this._client._post("/api/v1/videos/process", params) as Promise; + } + + /** + * Poll processing status for a given job. + * + * @param jobId - The `job_id` returned by {@link process}. + * @returns `VideoJobStatusResponse` with current status and progress. + */ + async getStatus(jobId: string): Promise { + return this._client._get(`/api/v1/videos/${jobId}/status`) as Promise; + } + + /** + * List all processed videos in the library. + */ + async list(): Promise { + return this._client._get("/api/v1/videos") as Promise; + } + + /** + * Retrieve metadata for a single video. + * + * @param videoId - YouTube video ID (11-character string). + */ + async retrieve(videoId: string): Promise { + return this._client._get(`/api/v1/videos/${videoId}`); + } + + /** + * Remove a processed video from the cache. + * + * @param videoId - YouTube video ID. + */ + async delete(videoId: string): Promise { + return this._client._delete(`/api/v1/cache/${videoId}`); + } +} diff --git a/sdk/typescript/src/types.ts b/sdk/typescript/src/types.ts new file mode 100644 index 00000000..f5b42610 --- /dev/null +++ b/sdk/typescript/src/types.ts @@ -0,0 +1,181 @@ +/** + * EventRelay SDK — shared TypeScript types. + * + * These types mirror the Pydantic models in the backend + * (src/youtube_extension/backend/api/v1/models.py) and are derived from + * openapi/eventrelay.openapi.json. + */ + +// --------------------------------------------------------------------------- +// Enums +// --------------------------------------------------------------------------- + +export type JobStatus = + | "pending" + | "downloading" + | "transcribing" + | "extracting" + | "complete" + | "failed"; + +export type AgentStatus = "queued" | "running" | "complete" | "failed"; + +// --------------------------------------------------------------------------- +// Video Processing +// --------------------------------------------------------------------------- + +export interface VideoProcessJobRequest { + /** Full YouTube video URL */ + video_url: string; + /** Transcript language code (default "en") */ + language?: string; + /** Optional processing overrides */ + options?: Record; +} + +export interface VideoProcessJobResponse { + job_id: string; + video_url: string; + status: JobStatus; +} + +export interface VideoJobStatusResponse { + job_id: string; + status: JobStatus; + /** Progress percentage 0–100 */ + progress: number; + video_url?: string; + transcript?: string; + metadata?: Record; + error?: string; +} + +// --------------------------------------------------------------------------- +// Event Extraction +// --------------------------------------------------------------------------- + +export interface EventExtractRequest { + /** Job ID from video processing */ + job_id?: string; + /** Raw transcript text */ + transcript?: string; + video_url?: string; +} + +export interface ExtractedEvent { + id: string; + /** "action" | "mention" | "topic" | "insight" */ + type: string; + title: string; + description?: string; + /** Time in video, e.g. "02:15" */ + timestamp?: string; + /** Confidence score 0–1 */ + confidence: number; +} + +export interface EventExtractResponse { + job_id?: string; + events: ExtractedEvent[]; + event_count: number; +} + +// --------------------------------------------------------------------------- +// Agent Dispatch +// --------------------------------------------------------------------------- + +export interface AgentDispatchRequest { + job_id?: string; + events?: Record[]; + /** Transcript — events will be auto-extracted when events is empty */ + transcript?: string; + /** Restrict dispatch to specific agent type identifiers */ + agent_types?: string[]; +} + +export interface AgentExecution { + agent_id: string; + agent_type: string; + status: AgentStatus; + progress: number; + event_id?: string; + result?: Record; + error?: string; +} + +export interface AgentDispatchResponse { + dispatch_id: string; + executions: AgentExecution[]; +} + +export interface AgentStatusResponse { + agent_id: string; + agent_type: string; + status: AgentStatus; + progress: number; + result?: Record; + error?: string; +} + +// --------------------------------------------------------------------------- +// Transcript Action +// --------------------------------------------------------------------------- + +export interface TranscriptActionRequest { + video_url: string; + action?: string; + options?: Record; +} + +export interface TranscriptActionResponse { + video_url: string; + transcript?: Record; + actions: Record[]; + status: string; +} + +// --------------------------------------------------------------------------- +// Chat +// --------------------------------------------------------------------------- + +export interface ChatRequest { + query: string; + video_id?: string; + video_url?: string; + context?: string; + session_id?: string; + history?: Array>; +} + +export interface ChatResponse { + response: string; + status: string; + session_id: string; + timestamp: string; +} + +// --------------------------------------------------------------------------- +// Health +// --------------------------------------------------------------------------- + +export interface HealthResponse { + status: string; + version?: string; + timestamp?: string; + services?: Record; +} + +// --------------------------------------------------------------------------- +// Client configuration +// --------------------------------------------------------------------------- + +export interface EventRelayClientOptions { + /** API key — falls back to EVENTRELAY_API_KEY env variable */ + apiKey?: string; + /** Base URL (default: https://api.uvai.io) */ + baseUrl?: string; + /** HTTP timeout in milliseconds (default: 60000) */ + timeout?: number; + /** Max retry attempts on transient errors (default: 2) */ + maxRetries?: number; +} diff --git a/sdk/typescript/tsconfig.json b/sdk/typescript/tsconfig.json new file mode 100644 index 00000000..a1b726de --- /dev/null +++ b/sdk/typescript/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "CommonJS", + "moduleResolution": "node", + "lib": ["ES2020"], + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "**/*.test.ts"] +} diff --git a/src/youtube_extension.egg-info/SOURCES.txt b/src/youtube_extension.egg-info/SOURCES.txt index ab3d0c6a..ec4e6bf7 100644 --- a/src/youtube_extension.egg-info/SOURCES.txt +++ b/src/youtube_extension.egg-info/SOURCES.txt @@ -90,6 +90,11 @@ src/uvai/api/__init__.py src/uvai/api/main.py src/uvai/api/server.py src/uvai/api/v1/services/issue_tracker.py +src/uvai/ml/__init__.py +src/uvai/ml/serve.py +src/uvai/ml/models/__init__.py +src/uvai/ml/models/action_priority_ranker.py +src/uvai/ml/models/transcript_quality_scorer.py src/uvai/security_protocol/__init__.py src/uvai/security_protocol/encode_video.py src/uvai/security_protocol/generate_keys.py @@ -246,4 +251,6 @@ src/youtube_extension/videopack/io.py src/youtube_extension/videopack/provenance.py src/youtube_extension/videopack/schema.py src/youtube_extension/videopack/validate.py -src/youtube_extension/videopack/versioning.py \ No newline at end of file +src/youtube_extension/videopack/versioning.py +tests/test_code_generator.py +tests/test_ml_models.py \ No newline at end of file diff --git a/src/youtube_extension.egg-info/top_level.txt b/src/youtube_extension.egg-info/top_level.txt index 937ce021..83fe3d67 100644 --- a/src/youtube_extension.egg-info/top_level.txt +++ b/src/youtube_extension.egg-info/top_level.txt @@ -3,6 +3,7 @@ analysis backend connectors core +dataconnect-generated integration mcp shared diff --git a/tests/test_sdk_python.py b/tests/test_sdk_python.py new file mode 100644 index 00000000..5ccd7751 --- /dev/null +++ b/tests/test_sdk_python.py @@ -0,0 +1,447 @@ +""" +Tests for the EventRelay Python SDK. + +These tests validate the SDK types and client logic without requiring +a running EventRelay server, using httpx's mock transport. +""" + +from __future__ import annotations + +import json +import sys +from pathlib import Path + +import pytest + +# Make the SDK importable without installation +sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "sdk" / "python")) + +import httpx + +from eventrelay_sdk import ( + AsyncEventRelayClient, + EventRelayClient, +) +from eventrelay_sdk.types import ( + AgentDispatchRequest, + AgentDispatchResponse, + AgentExecution, + AgentStatus, + AgentStatusResponse, + ChatRequest, + ChatResponse, + EventExtractRequest, + EventExtractResponse, + ExtractedEvent, + HealthResponse, + JobStatus, + TranscriptActionRequest, + TranscriptActionResponse, + VideoJobStatusResponse, + VideoProcessJobRequest, + VideoProcessJobResponse, +) + + +# --------------------------------------------------------------------------- +# Fixtures +# --------------------------------------------------------------------------- + +TEST_VIDEO_URL = "https://www.youtube.com/watch?v=auJzb1D-fag" +TEST_JOB_ID = "job_abc123" +TEST_AGENT_ID = "agent_def456" +TEST_DISPATCH_ID = "dsp_ghi789" +TEST_API_KEY = "test-api-key" +TEST_BASE_URL = "http://localhost:8000" + + +def _mock_transport(responses: dict[tuple[str, str], dict]) -> httpx.MockTransport: + """Build an httpx MockTransport from a (method, path) → response dict.""" + + def handler(request: httpx.Request) -> httpx.Response: + key = (request.method, request.url.path) + if key in responses: + return httpx.Response(200, json=responses[key]) + return httpx.Response(404, json={"detail": "Not found"}) + + return httpx.MockTransport(handler) + + +# --------------------------------------------------------------------------- +# Type model tests +# --------------------------------------------------------------------------- + + +class TestVideoModels: + def test_video_process_request_valid(self) -> None: + req = VideoProcessJobRequest(video_url=TEST_VIDEO_URL) + assert req.video_url == TEST_VIDEO_URL + assert req.language == "en" + + def test_video_process_response_parse(self) -> None: + data = {"job_id": TEST_JOB_ID, "video_url": TEST_VIDEO_URL, "status": "pending"} + resp = VideoProcessJobResponse.model_validate(data) + assert resp.job_id == TEST_JOB_ID + assert resp.status == JobStatus.pending + + def test_video_job_status_response_parse(self) -> None: + data = { + "job_id": TEST_JOB_ID, + "status": "complete", + "progress": 100.0, + "transcript": "Hello world", + } + resp = VideoJobStatusResponse.model_validate(data) + assert resp.status == JobStatus.complete + assert resp.progress == 100.0 + + +class TestEventModels: + def test_event_extract_request(self) -> None: + req = EventExtractRequest(transcript="Speaker discussed React hooks.") + assert req.transcript == "Speaker discussed React hooks." + assert req.job_id is None + + def test_extracted_event_parse(self) -> None: + data = { + "id": "evt_001", + "type": "action", + "title": "Build weather app", + "confidence": 0.95, + } + event = ExtractedEvent.model_validate(data) + assert event.type == "action" + assert event.confidence == 0.95 + + def test_event_extract_response_parse(self) -> None: + data = { + "job_id": TEST_JOB_ID, + "events": [ + {"id": "evt_001", "type": "topic", "title": "React", "confidence": 1.0} + ], + "event_count": 1, + } + resp = EventExtractResponse.model_validate(data) + assert resp.event_count == 1 + assert resp.events[0].title == "React" + + +class TestAgentModels: + def test_agent_dispatch_request(self) -> None: + req = AgentDispatchRequest( + events=[{"id": "evt_001", "type": "action", "title": "Build app"}], + agent_types=["code_generator"], + ) + assert len(req.events) == 1 + assert req.agent_types == ["code_generator"] + + def test_agent_execution_parse(self) -> None: + data = { + "agent_id": TEST_AGENT_ID, + "agent_type": "code_generator", + "status": "queued", + "progress": 0.0, + } + execution = AgentExecution.model_validate(data) + assert execution.status == AgentStatus.queued + + def test_agent_dispatch_response_parse(self) -> None: + data = { + "dispatch_id": TEST_DISPATCH_ID, + "executions": [ + { + "agent_id": TEST_AGENT_ID, + "agent_type": "code_generator", + "status": "queued", + "progress": 0.0, + } + ], + } + resp = AgentDispatchResponse.model_validate(data) + assert resp.dispatch_id == TEST_DISPATCH_ID + assert len(resp.executions) == 1 + + def test_agent_status_response_parse(self) -> None: + data = { + "agent_id": TEST_AGENT_ID, + "agent_type": "code_generator", + "status": "complete", + "progress": 100.0, + } + resp = AgentStatusResponse.model_validate(data) + assert resp.status == AgentStatus.complete + + +class TestHealthModels: + def test_health_response_parse(self) -> None: + data = {"status": "healthy", "version": "2.0.0"} + resp = HealthResponse.model_validate(data) + assert resp.status == "healthy" + assert resp.version == "2.0.0" + + +class TestChatModels: + def test_chat_request(self) -> None: + req = ChatRequest(query="What is this video about?") + assert req.query == "What is this video about?" + assert req.context == "tooltip-assistant" + + +class TestTranscriptModels: + def test_transcript_action_request(self) -> None: + req = TranscriptActionRequest(video_url=TEST_VIDEO_URL) + assert req.video_url == TEST_VIDEO_URL + + def test_transcript_action_response_parse(self) -> None: + data = { + "video_url": TEST_VIDEO_URL, + "transcript": {"text": "Hello world"}, + "actions": [], + "status": "success", + } + resp = TranscriptActionResponse.model_validate(data) + assert resp.status == "success" + + +# --------------------------------------------------------------------------- +# Synchronous client tests (using mock transport) +# --------------------------------------------------------------------------- + + +class TestEventRelayClient: + def _make_client(self, routes: dict) -> EventRelayClient: + transport = _mock_transport(routes) + http = httpx.Client(transport=transport) + return EventRelayClient( + api_key=TEST_API_KEY, + base_url=TEST_BASE_URL, + http_client=http, + ) + + def test_client_default_base_url(self) -> None: + client = EventRelayClient() + assert "uvai.io" in client._base_url + + def test_client_custom_base_url(self) -> None: + client = EventRelayClient(base_url="http://localhost:9000") + assert client._base_url == "http://localhost:9000" + + def test_client_strips_trailing_slash(self) -> None: + client = EventRelayClient(base_url="http://localhost:8000/") + assert not client._base_url.endswith("/") + + def test_client_api_key_in_headers(self) -> None: + client = EventRelayClient(api_key="secret-key") + assert client._headers()["X-API-Key"] == "secret-key" + + def test_client_no_api_key_header_absent(self) -> None: + client = EventRelayClient(api_key="") + assert "X-API-Key" not in client._headers() + + def test_videos_process(self) -> None: + routes = { + ("POST", "/api/v1/videos/process"): { + "job_id": TEST_JOB_ID, + "video_url": TEST_VIDEO_URL, + "status": "pending", + } + } + client = self._make_client(routes) + result = client.videos.process(TEST_VIDEO_URL) + assert isinstance(result, VideoProcessJobResponse) + assert result.job_id == TEST_JOB_ID + assert result.status == JobStatus.pending + + def test_videos_get_status(self) -> None: + routes = { + ("GET", f"/api/v1/videos/{TEST_JOB_ID}/status"): { + "job_id": TEST_JOB_ID, + "status": "complete", + "progress": 100.0, + } + } + client = self._make_client(routes) + result = client.videos.get_status(job_id=TEST_JOB_ID) + assert isinstance(result, VideoJobStatusResponse) + assert result.status == JobStatus.complete + + def test_events_extract(self) -> None: + routes = { + ("POST", "/api/v1/events/extract"): { + "job_id": TEST_JOB_ID, + "events": [ + {"id": "evt_001", "type": "action", "title": "Build app", "confidence": 0.9} + ], + "event_count": 1, + } + } + client = self._make_client(routes) + result = client.events.extract(transcript="Build a React app from scratch.") + assert isinstance(result, EventExtractResponse) + assert result.event_count == 1 + assert result.events[0].type == "action" + + def test_agents_dispatch(self) -> None: + routes = { + ("POST", "/api/v1/agents/dispatch"): { + "dispatch_id": TEST_DISPATCH_ID, + "executions": [ + { + "agent_id": TEST_AGENT_ID, + "agent_type": "code_generator", + "status": "queued", + "progress": 0.0, + } + ], + } + } + client = self._make_client(routes) + result = client.agents.dispatch( + events=[{"id": "evt_001", "type": "action", "title": "Build app"}] + ) + assert isinstance(result, AgentDispatchResponse) + assert result.dispatch_id == TEST_DISPATCH_ID + assert len(result.executions) == 1 + + def test_agents_get_status(self) -> None: + routes = { + ("GET", f"/api/v1/agents/{TEST_AGENT_ID}/status"): { + "agent_id": TEST_AGENT_ID, + "agent_type": "code_generator", + "status": "complete", + "progress": 100.0, + } + } + client = self._make_client(routes) + result = client.agents.get_status(TEST_AGENT_ID) + assert isinstance(result, AgentStatusResponse) + assert result.status == AgentStatus.complete + + def test_health_check(self) -> None: + routes = {("GET", "/api/v1/health"): {"status": "healthy"}} + client = self._make_client(routes) + result = client.health.check() + assert isinstance(result, HealthResponse) + assert result.status == "healthy" + + def test_health_detailed(self) -> None: + routes = { + ("GET", "/api/v1/health/detailed"): { + "status": "healthy", + "services": {"database": "ok", "cache": "ok"}, + } + } + client = self._make_client(routes) + result = client.health.detailed() + assert isinstance(result, HealthResponse) + assert result.services is not None + + def test_client_context_manager(self) -> None: + routes = {("GET", "/api/v1/health"): {"status": "healthy"}} + with self._make_client(routes) as client: + result = client.health.check() + assert result.status == "healthy" + + def test_client_retry_on_500(self) -> None: + """Client should retry on 500 and succeed on subsequent attempt.""" + call_count = 0 + + def handler(request: httpx.Request) -> httpx.Response: + nonlocal call_count + call_count += 1 + if call_count < 2: + return httpx.Response(500) + return httpx.Response(200, json={"status": "healthy"}) + + transport = httpx.MockTransport(handler) + http = httpx.Client(transport=transport) + client = EventRelayClient( + api_key=TEST_API_KEY, + base_url=TEST_BASE_URL, + http_client=http, + max_retries=2, + ) + result = client.health.check() + assert result.status == "healthy" + assert call_count == 2 + + def test_client_raises_on_404(self) -> None: + """Client should raise immediately on non-retryable error codes.""" + + def handler(request: httpx.Request) -> httpx.Response: + return httpx.Response(404, json={"detail": "Not found"}) + + transport = httpx.MockTransport(handler) + http = httpx.Client(transport=transport) + client = EventRelayClient(base_url=TEST_BASE_URL, http_client=http) + with pytest.raises(httpx.HTTPStatusError): + client.health.check() + + +# --------------------------------------------------------------------------- +# Async client tests +# --------------------------------------------------------------------------- + + +class TestAsyncEventRelayClient: + def _make_client(self, routes: dict) -> AsyncEventRelayClient: + transport = _mock_transport(routes) + http = httpx.AsyncClient(transport=transport) + return AsyncEventRelayClient( + api_key=TEST_API_KEY, + base_url=TEST_BASE_URL, + http_client=http, + ) + + @pytest.mark.asyncio + async def test_async_health_check(self) -> None: + routes = {("GET", "/api/v1/health"): {"status": "healthy"}} + client = self._make_client(routes) + result = await client.health.check() + assert result.status == "healthy" + + @pytest.mark.asyncio + async def test_async_videos_process(self) -> None: + routes = { + ("POST", "/api/v1/videos/process"): { + "job_id": TEST_JOB_ID, + "video_url": TEST_VIDEO_URL, + "status": "pending", + } + } + client = self._make_client(routes) + result = await client.videos.process(TEST_VIDEO_URL) + assert result.job_id == TEST_JOB_ID + + @pytest.mark.asyncio + async def test_async_context_manager(self) -> None: + routes = {("GET", "/api/v1/health"): {"status": "healthy"}} + async with self._make_client(routes) as client: + result = await client.health.check() + assert result.status == "healthy" + + @pytest.mark.asyncio + async def test_async_events_extract(self) -> None: + routes = { + ("POST", "/api/v1/events/extract"): { + "events": [ + {"id": "evt_001", "type": "topic", "title": "React", "confidence": 1.0} + ], + "event_count": 1, + } + } + client = self._make_client(routes) + result = await client.events.extract(transcript="Intro to React.") + assert len(result.events) == 1 + + @pytest.mark.asyncio + async def test_async_agents_dispatch(self) -> None: + routes = { + ("POST", "/api/v1/agents/dispatch"): { + "dispatch_id": TEST_DISPATCH_ID, + "executions": [], + } + } + client = self._make_client(routes) + result = await client.agents.dispatch(transcript="Build a React app.") + assert result.dispatch_id == TEST_DISPATCH_ID