jaxl-python 0.0.38__tar.gz → 0.0.39__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/PKG-INFO +1 -1
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/examples/__init__.py +2 -0
- jaxl_python-0.0.39/examples/streaming_wav_file.py +95 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/_scm_version.py +2 -2
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/resources/apps.py +23 -16
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/resources/calls.py +56 -0
- jaxl_python-0.0.39/jaxl/api/resources/silence.py +151 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl_python.egg-info/PKG-INFO +1 -1
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl_python.egg-info/SOURCES.txt +1 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/tests/test_streaming_vad.py +17 -2
- jaxl_python-0.0.38/jaxl/api/resources/silence.py +0 -86
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/.github/workflows/sdk.yml +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/.gitignore +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/.isort.cfg +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/.pylintrc +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/.vscode/settings.json +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/LICENSE.md +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/README.md +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/SPECIFICATION.md +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/docs.sh +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/examples/README.md +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/examples/prompts/example.txt +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/examples/py.typed +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/examples/request_and_confirm_code_then_send_to_phone.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/examples/request_code_and_send_to_phone.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/examples/send_to_phone.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/examples/streaming_aiagent.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/examples/streaming_audio_chunks.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/examples/streaming_speech_segments.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/examples/streaming_transcriptions.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/index.md +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/__init__.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/_client.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/_sdk.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/base.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/cli.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/__init__.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/api/__init__.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/api/v1/__init__.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/api/v1/v1_app_organizations_list.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/api/v1/v1_app_organizations_providers_list.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/api/v1/v1_appusers_me_retrieve.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/api/v1/v1_calls_add_create.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/api/v1/v1_calls_audio_retrieve.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/api/v1/v1_calls_hangup_retrieve.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/api/v1/v1_calls_list.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/api/v1/v1_calls_messages_create.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/api/v1/v1_calls_report_retrieve.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/api/v1/v1_calls_retrieve.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/api/v1/v1_calls_tags_create.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/api/v1/v1_calls_token_create.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/api/v1/v1_calls_transfer_create.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/api/v1/v1_calls_tts_create.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/api/v1/v1_calls_usage_retrieve.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/api/v1/v1_campaign_list.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/api/v1/v1_campaign_upload_create.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/api/v1/v1_customer_consumables_retrieve.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/api/v1/v1_devices_attest_create.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/api/v1/v1_devices_list.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/api/v1/v1_integrations_create.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/api/v1/v1_ivr_create.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/api/v1/v1_ivr_list.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/api/v1/v1_ivr_options_create.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/api/v1/v1_ivr_options_list.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/api/v1/v1_ivr_options_partial_update.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/api/v1/v1_kyc_list.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/api/v1/v1_messages_list.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/api/v1/v1_phonenumbers_list.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/api/v1/v1_phonenumbers_partial_update.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/api/v1/v1_phonenumbers_search_retrieve.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/api/v2/__init__.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/api/v2/v2_app_organizations_employees_list.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/api/v2/v2_app_organizations_groups_list.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/api/v3/__init__.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/api/v3/v3_orders_subscriptions_list.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/client.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/errors.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/__init__.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/address_provider.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/address_provider_status_enum.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/analytic.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/app_price.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/app_user.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/available_phone_number.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/available_phone_number_capabilities.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/available_phone_number_provider_enum.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/call.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/call_add_request_request.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/call_audio_reason.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/call_cost.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/call_location_epoch.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/call_message_request_request.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/call_message_request_type_enum.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/call_metadata.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/call_report.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/call_report_reason.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/call_report_status_enum.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/call_tag_request.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/call_tag_response.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/call_token_request.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/call_token_response.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/call_transfer_request_request.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/call_tts_request_request.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/call_type_enum.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/call_usage_by_currency_response.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/call_usage_response.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/call_usage_stats_response.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/campaign.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/campaign_metadata.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/campaign_metadata_metadata.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/campaign_response.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/campaign_response_status_enum.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/campaign_stats.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/campaign_tag.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/campaign_upload_request.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/campaign_upload_request_options.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/campaign_upload_type_enum.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/campaign_window_request.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/can_user_resubscribe_plan.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/canceled_by_enum.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/capabilities.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/connection.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/content_type_enum.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/country.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/cta.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/cta_request.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/cta_type_enum.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/currency_enum.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/customer_consumable_total.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/customer_order_subscriptions_serializer_v2.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/customer_order_subscriptions_serializer_v2_status_enum.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/customer_provider_serializer_v2.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/device.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/device_attestation_error.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/device_attestation_error_reason_enum.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/device_attestation_response.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/dh_message.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/dh_message_attachment.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/dh_message_reaction.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/dh_message_type_enum.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/direction_enum.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/emoji.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/emoji_reaction.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/exotel_auth_request_request.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/id_enum.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/integrations_error_response.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/integrations_properties_request.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/integrations_request_provider_enum.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/integrations_request_request.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/integrations_response.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/intent_enum.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/invalid_provider_request.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/iso_country_enum.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/item.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/ivr_collection.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/ivr_collection_request.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/ivr_menu_request.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/ivr_menu_response.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/ivr_menu_response_status_enum.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/ivr_options_invalid_response.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/ivr_options_request.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/ivr_options_response.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/kyc.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/kyc_status_enum.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/kyc_upload_metadata.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/location.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/next_or_cta_request.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/order_status_enum.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/organization.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/organization_employee.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/organization_employee_preferences.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/organization_employee_status_enum.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/organization_group_inline.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/organization_group_response.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/organization_preferences.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/organization_provider.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/paginated_call_list.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/paginated_campaign_response_list.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/paginated_customer_order_subscriptions_serializer_v2_list.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/paginated_device_list.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/paginated_dh_message_list.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/paginated_ivr_menu_response_list.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/paginated_ivr_options_response_list.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/paginated_kyc_list.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/paginated_organization_employee_list.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/paginated_organization_group_response_list.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/paginated_organization_list.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/paginated_organization_provider_list.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/paginated_phone_number_list.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/patched_ivr_options_update_request.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/patched_phone_number_request.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/payment_gateway_fees_info.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/period_enum.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/phone_number.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/phone_number_attributes.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/phone_number_capabilities.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/phone_number_provider_enum.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/phone_number_search_response.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/phone_number_status_enum.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/plan.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/plan_cancel_info.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/plan_expiry_timestamp.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/plan_expiry_timestamp_type_enum.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/plan_extra_details.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/plan_item.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/plan_type.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/plan_type_cycle.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/platform_enum.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/product_group.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/proof.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/proof_status_enum.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/provider_status_enum.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/reaction_by.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/rental_currency_enum.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/resource_enum.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/shopify_auth_request_request.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/stripe_auth_request_request.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/user_agent.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/user_agent_browser.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/user_agent_device.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/user_agent_operating_system.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/user_agent_platform.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/user_identity.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/v1_app_organizations_list_status_item.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/v1_calls_list_direction.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/v1_calls_report_retrieve_date_range.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/v1_calls_report_retrieve_fields_item.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/v1_campaign_list_status_item.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/v1_customer_consumables_retrieve_currency.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/v1_ivr_list_duration.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/v1_kyc_list_iso_country.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/v1_kyc_list_provider_status_item.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/v1_kyc_list_resource.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/v1_kyc_list_status.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/v1_phonenumbers_list_additional_status_item.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/v1_phonenumbers_list_provider.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/v1_phonenumbers_list_status.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/v1_phonenumbers_search_retrieve_intent.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/v1_phonenumbers_search_retrieve_iso_country_code.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/v1_phonenumbers_search_retrieve_resource.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/v2_app_organizations_employees_list_status_item.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/v3_orders_subscriptions_list_currency.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/v3_orders_subscriptions_list_status_item.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/models/why_enum.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/types.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/py.typed +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/resources/__init__.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/resources/_constants.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/resources/accounts.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/resources/campaigns.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/resources/devices.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/resources/ivrs.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/resources/kycs.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/resources/members.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/resources/messages.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/resources/notifications.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/resources/orgs.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/resources/payments.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/resources/phones.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/resources/teams.py +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl_python.egg-info/dependency_links.txt +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl_python.egg-info/entry_points.txt +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl_python.egg-info/not-zip-safe +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl_python.egg-info/requires.txt +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl_python.egg-info/top_level.txt +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/mkdocs.yml +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/pyproject.toml +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/setup.cfg +0 -0
- {jaxl_python-0.0.38 → jaxl_python-0.0.39}/setup.gif +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: jaxl-python
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.39
|
|
4
4
|
Summary: Official Python SDK and CLI for interfacing with the Jaxl API. Manage calls, messages, IVRs, devices, teams, payments, campaigns, streaming transcriptions and AI Agents. Built on OpenAPI, it offers both generated API clients and Pythonic wrappers for ease of use.
|
|
5
5
|
Home-page: https://github.com/jaxl-innovations-private-limited/jaxl-python
|
|
6
6
|
Download-URL: https://github.com/jaxl-innovations-private-limited/jaxl-python/archive/main.zip
|
|
@@ -16,6 +16,7 @@ from .streaming_aiagent import JaxlAppStreamingAIAgent
|
|
|
16
16
|
from .streaming_audio_chunks import JaxlAppStreamingAudioChunk
|
|
17
17
|
from .streaming_speech_segments import JaxlAppStreamingSpeechSegment
|
|
18
18
|
from .streaming_transcriptions import JaxlAppStreamingTranscription
|
|
19
|
+
from .streaming_wav_file import JaxlAppStreamingWavFile
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
__all__ = [
|
|
@@ -26,4 +27,5 @@ __all__ = [
|
|
|
26
27
|
"JaxlAppStreamingSpeechSegment",
|
|
27
28
|
"JaxlAppStreamingTranscription",
|
|
28
29
|
"JaxlAppStreamingAIAgent",
|
|
30
|
+
"JaxlAppStreamingWavFile",
|
|
29
31
|
]
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Copyright (c) 2010-present by Jaxl Innovations Private Limited.
|
|
3
|
+
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms,
|
|
7
|
+
with or without modification, is strictly prohibited.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import asyncio
|
|
11
|
+
# pylint: disable=deprecated-module
|
|
12
|
+
import audioop
|
|
13
|
+
import os
|
|
14
|
+
import wave
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Dict
|
|
17
|
+
|
|
18
|
+
from jaxl.api.base import (
|
|
19
|
+
HANDLER_RESPONSE,
|
|
20
|
+
BaseJaxlApp,
|
|
21
|
+
JaxlWebhookRequest,
|
|
22
|
+
JaxlWebhookResponse,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
SAMPLE_RATE = 8000
|
|
27
|
+
SAMPLE_WIDTH = 2
|
|
28
|
+
CHANNELS = 1
|
|
29
|
+
FRAME_MS = 20
|
|
30
|
+
FRAME_BYTES = SAMPLE_RATE * SAMPLE_WIDTH * FRAME_MS // 1000
|
|
31
|
+
SEND_INTERVAL_S = 0.010 # Send faster than real-time to avoid SIP jitter.
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def load_wav_as_slin16_8k_mono(path: str) -> bytes:
|
|
35
|
+
with wave.open(path, "rb") as wav:
|
|
36
|
+
audio = wav.readframes(wav.getnframes())
|
|
37
|
+
sample_width = wav.getsampwidth()
|
|
38
|
+
channels = wav.getnchannels()
|
|
39
|
+
sample_rate = wav.getframerate()
|
|
40
|
+
|
|
41
|
+
if channels != CHANNELS:
|
|
42
|
+
audio = audioop.tomono(audio, sample_width, 0.5, 0.5)
|
|
43
|
+
channels = CHANNELS
|
|
44
|
+
if sample_width != SAMPLE_WIDTH:
|
|
45
|
+
audio = audioop.lin2lin(audio, sample_width, SAMPLE_WIDTH)
|
|
46
|
+
sample_width = SAMPLE_WIDTH
|
|
47
|
+
if sample_rate != SAMPLE_RATE:
|
|
48
|
+
audio, _ = audioop.ratecv(
|
|
49
|
+
audio,
|
|
50
|
+
sample_width,
|
|
51
|
+
channels,
|
|
52
|
+
sample_rate,
|
|
53
|
+
SAMPLE_RATE,
|
|
54
|
+
None,
|
|
55
|
+
)
|
|
56
|
+
return audio
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class JaxlAppStreamingWavFile(BaseJaxlApp):
|
|
60
|
+
"""Minimal incoming-call app that streams one WAV file over the live stream."""
|
|
61
|
+
|
|
62
|
+
def __init__(self) -> None:
|
|
63
|
+
self._playback_tasks: Dict[int, asyncio.Task[None]] = {}
|
|
64
|
+
self._wav_path = os.environ.get("JAXL_STREAMING_WAV_PATH", "example.wav")
|
|
65
|
+
self._audio = load_wav_as_slin16_8k_mono(self._wav_path)
|
|
66
|
+
|
|
67
|
+
async def handle_setup(self, req: JaxlWebhookRequest) -> HANDLER_RESPONSE:
|
|
68
|
+
return JaxlWebhookResponse(prompt=[], num_characters=-1)
|
|
69
|
+
|
|
70
|
+
async def handle_teardown(self, req: JaxlWebhookRequest) -> HANDLER_RESPONSE:
|
|
71
|
+
assert req.state is not None
|
|
72
|
+
self._cancel_playback(req.state.call_id)
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
async def on_stream_connect(self, call_id: int) -> None:
|
|
76
|
+
self._cancel_playback(call_id)
|
|
77
|
+
self._playback_tasks[call_id] = asyncio.create_task(self._stream_wav(call_id))
|
|
78
|
+
|
|
79
|
+
async def on_stream_disconnect(self, call_id: int) -> None:
|
|
80
|
+
self._cancel_playback(call_id)
|
|
81
|
+
|
|
82
|
+
def _cancel_playback(self, call_id: int) -> None:
|
|
83
|
+
task = self._playback_tasks.pop(call_id, None)
|
|
84
|
+
if task is not None:
|
|
85
|
+
task.cancel()
|
|
86
|
+
|
|
87
|
+
async def _stream_wav(self, call_id: int) -> None:
|
|
88
|
+
print(f"Streaming {Path(self._wav_path)} to call {call_id}")
|
|
89
|
+
for offset in range(0, len(self._audio), FRAME_BYTES):
|
|
90
|
+
chunk = self._audio[offset : offset + FRAME_BYTES]
|
|
91
|
+
if len(chunk) < FRAME_BYTES:
|
|
92
|
+
chunk += b"\x00" * (FRAME_BYTES - len(chunk))
|
|
93
|
+
if not await self.send_audio(call_id, chunk):
|
|
94
|
+
break
|
|
95
|
+
await asyncio.sleep(SEND_INTERVAL_S)
|
|
@@ -20,8 +20,7 @@ import tempfile
|
|
|
20
20
|
import uuid
|
|
21
21
|
import warnings
|
|
22
22
|
import wave
|
|
23
|
-
from
|
|
24
|
-
from typing import TYPE_CHECKING, Any, Deque, Dict, List, Optional, cast
|
|
23
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Optional, cast
|
|
25
24
|
|
|
26
25
|
from fastapi.websockets import WebSocketState
|
|
27
26
|
from starlette.websockets import WebSocketDisconnect
|
|
@@ -37,7 +36,7 @@ from jaxl.api.base import (
|
|
|
37
36
|
from jaxl.api.client.models.call_tag_response import CallTagResponse
|
|
38
37
|
from jaxl.api.client.types import Response
|
|
39
38
|
from jaxl.api.resources.accounts import accounts_me
|
|
40
|
-
from jaxl.api.resources.calls import
|
|
39
|
+
from jaxl.api.resources.calls import calls_ahangup, calls_atag_add
|
|
41
40
|
from jaxl.api.resources.silence import SilenceDetector
|
|
42
41
|
|
|
43
42
|
|
|
@@ -138,10 +137,10 @@ def _start_server(
|
|
|
138
137
|
wss: Dict[int, WebSocket] = {}
|
|
139
138
|
|
|
140
139
|
async def _add_tag(call_id: int, tag: str) -> Response[CallTagResponse]:
|
|
141
|
-
return
|
|
140
|
+
return await calls_atag_add({"call_id": call_id, "tag": tag})
|
|
142
141
|
|
|
143
142
|
async def _hangup(call_id: int) -> Response[Any]:
|
|
144
|
-
return
|
|
143
|
+
return await calls_ahangup({"call_id": call_id})
|
|
145
144
|
|
|
146
145
|
async def _clear_audio(call_id: int) -> bool:
|
|
147
146
|
try:
|
|
@@ -239,7 +238,6 @@ def _start_server(
|
|
|
239
238
|
speech_frame_threshold=vad_speech_frame_threshold,
|
|
240
239
|
)
|
|
241
240
|
speaking: bool = False
|
|
242
|
-
buffer: Deque[bytes] = deque(maxlen=4)
|
|
243
241
|
slin16s: List[bytes] = []
|
|
244
242
|
|
|
245
243
|
await ws.accept()
|
|
@@ -257,18 +255,22 @@ def _start_server(
|
|
|
257
255
|
# Invoke audio chunk handlers
|
|
258
256
|
await app.handle_audio_chunk(req, slin16)
|
|
259
257
|
# Detect start/end of speech
|
|
260
|
-
buffer.append(slin16)
|
|
261
258
|
change = sdetector.process(slin16)
|
|
262
|
-
current_frame_is_speech = sdetector.last_frame_is_speech
|
|
263
259
|
# Manage speech segments
|
|
264
260
|
if change is True:
|
|
265
261
|
speaking = change
|
|
266
262
|
await app.handle_speech_detection(state["call_id"], speaking)
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
263
|
+
# Pull the detector's preroll — this contains the
|
|
264
|
+
# trigger frames (the `speech_frame_threshold` speech
|
|
265
|
+
# frames that fired speech-start) plus a few earlier
|
|
266
|
+
# frames for consonant-onset context. Forwarding
|
|
267
|
+
# this preroll fixes the head-clipping bug where
|
|
268
|
+
# short utterances ("haan", "ji") were truncated.
|
|
269
|
+
preroll_bytes = sdetector.consume_preroll()
|
|
270
|
+
if preroll_bytes:
|
|
271
|
+
preroll_chunks = [preroll_bytes]
|
|
272
|
+
slin16s.extend(preroll_chunks)
|
|
273
|
+
await app.handle_speech_chunks(req, preroll_chunks)
|
|
272
274
|
elif change is False:
|
|
273
275
|
speaking = change
|
|
274
276
|
await app.handle_speech_detection(state["call_id"], speaking)
|
|
@@ -290,9 +292,14 @@ def _start_server(
|
|
|
290
292
|
else:
|
|
291
293
|
assert change is None
|
|
292
294
|
if speaking is True:
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
295
|
+
# Forward EVERY chunk during an active speech
|
|
296
|
+
# segment — including frames webrtcvad flagged
|
|
297
|
+
# as not-speech. Inter-word silences (e.g.
|
|
298
|
+
# between "haan" and "maam") are part of the
|
|
299
|
+
# utterance and dropping them corrupts prosody
|
|
300
|
+
# and confuses downstream ASR.
|
|
301
|
+
await app.handle_speech_chunks(req, [slin16])
|
|
302
|
+
slin16s.append(slin16)
|
|
296
303
|
else:
|
|
297
304
|
assert speaking is False
|
|
298
305
|
elif ev == "connected":
|
|
@@ -274,6 +274,18 @@ def calls_tag_add(args: Dict[str, Any]) -> Response[CallTagResponse]:
|
|
|
274
274
|
)
|
|
275
275
|
|
|
276
276
|
|
|
277
|
+
async def calls_atag_add(args: Dict[str, Any]) -> Response[CallTagResponse]:
|
|
278
|
+
return await v1_calls_tags_create.asyncio_detailed(
|
|
279
|
+
call_id=args["call_id"],
|
|
280
|
+
client=jaxl_api_client(
|
|
281
|
+
JaxlApiModule.CALL,
|
|
282
|
+
credentials=args.get("credentials", None),
|
|
283
|
+
auth_token=args.get("auth_token", None),
|
|
284
|
+
),
|
|
285
|
+
json_body=CallTagRequest(name=args["tag"]),
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
|
|
277
289
|
def calls_hangup(args: Dict[str, Any]) -> Response[Any]:
|
|
278
290
|
return v1_calls_hangup_retrieve.sync_detailed(
|
|
279
291
|
id=args["call_id"],
|
|
@@ -285,6 +297,17 @@ def calls_hangup(args: Dict[str, Any]) -> Response[Any]:
|
|
|
285
297
|
)
|
|
286
298
|
|
|
287
299
|
|
|
300
|
+
async def calls_ahangup(args: Dict[str, Any]) -> Response[Any]:
|
|
301
|
+
return await v1_calls_hangup_retrieve.asyncio_detailed(
|
|
302
|
+
id=args["call_id"],
|
|
303
|
+
client=jaxl_api_client(
|
|
304
|
+
JaxlApiModule.CALL,
|
|
305
|
+
credentials=args.get("credentials", None),
|
|
306
|
+
auth_token=args.get("auth_token", None),
|
|
307
|
+
),
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
|
|
288
311
|
def calls_transfer(args: Dict[str, Any]) -> Response[Any]:
|
|
289
312
|
return v1_calls_transfer_create.sync_detailed(
|
|
290
313
|
id=args["call_id"],
|
|
@@ -321,6 +344,30 @@ def calls_message(args: Dict[str, Any]) -> Response[Any]:
|
|
|
321
344
|
)
|
|
322
345
|
|
|
323
346
|
|
|
347
|
+
async def calls_amessage(args: Dict[str, Any]) -> Response[Any]:
|
|
348
|
+
return await v1_calls_messages_create.asyncio_detailed(
|
|
349
|
+
id=args["call_id"],
|
|
350
|
+
client=jaxl_api_client(
|
|
351
|
+
JaxlApiModule.CALL,
|
|
352
|
+
credentials=args.get("credentials", None),
|
|
353
|
+
auth_token=args.get("auth_token", None),
|
|
354
|
+
),
|
|
355
|
+
json_body=CallMessageRequestRequest(
|
|
356
|
+
text=encrypt(args["text"]),
|
|
357
|
+
timestamp=datetime.fromtimestamp(
|
|
358
|
+
args.get("epoch", None) or time.time(),
|
|
359
|
+
tz=timezone.utc,
|
|
360
|
+
),
|
|
361
|
+
why=WhyEnum[cast(str, args["direction"]).upper()],
|
|
362
|
+
type=(
|
|
363
|
+
CallMessageRequestTypeEnum.VALUE_1
|
|
364
|
+
if args["type"] == "chat"
|
|
365
|
+
else CallMessageRequestTypeEnum.VALUE_10
|
|
366
|
+
),
|
|
367
|
+
),
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
|
|
324
371
|
def calls_audio(args: Dict[str, Any]) -> Response[Any | CallAudioReason]:
|
|
325
372
|
assert "call_id" in args and "path" in args
|
|
326
373
|
response = v1_calls_audio_retrieve.sync_detailed(
|
|
@@ -611,11 +658,20 @@ class JaxlCallsSDK:
|
|
|
611
658
|
def add_tag(self, **kwargs: Any) -> Response[CallTagResponse]:
|
|
612
659
|
return calls_tag_add(kwargs)
|
|
613
660
|
|
|
661
|
+
async def aadd_tag(self, **kwargs: Any) -> Response[CallTagResponse]:
|
|
662
|
+
return await calls_atag_add(kwargs)
|
|
663
|
+
|
|
614
664
|
def hangup(self, **kwargs: Any) -> Response[Any]:
|
|
615
665
|
return calls_hangup(kwargs)
|
|
616
666
|
|
|
667
|
+
async def ahangup(self, **kwargs: Any) -> Response[Any]:
|
|
668
|
+
return await calls_ahangup(kwargs)
|
|
669
|
+
|
|
617
670
|
def message(self, **kwargs: Any) -> Response[Any]:
|
|
618
671
|
return calls_message(kwargs)
|
|
619
672
|
|
|
673
|
+
async def amessage(self, **kwargs: Any) -> Response[Any]:
|
|
674
|
+
return await calls_amessage(kwargs)
|
|
675
|
+
|
|
620
676
|
def audio(self, **kwargs: Any) -> Response[Any | CallAudioReason]:
|
|
621
677
|
return calls_audio(kwargs)
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Copyright (c) 2010-present by Jaxl Innovations Private Limited.
|
|
3
|
+
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms,
|
|
7
|
+
with or without modification, is strictly prohibited.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from collections import deque
|
|
11
|
+
from typing import Deque, Optional
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# pylint: disable=too-many-instance-attributes
|
|
15
|
+
class SilenceDetector:
|
|
16
|
+
"""Edge-triggered VAD wrapper around py-webrtcvad.
|
|
17
|
+
|
|
18
|
+
Streaming contract: feed PCM16 mono audio one chunk at a time via
|
|
19
|
+
``process()``. The method returns:
|
|
20
|
+
|
|
21
|
+
- ``True`` once on the speech-start edge
|
|
22
|
+
- ``False`` once on the speech-end edge
|
|
23
|
+
- ``None`` while no edge has been detected on this call
|
|
24
|
+
|
|
25
|
+
Preroll buffer (FIX for head clipping)
|
|
26
|
+
--------------------------------------
|
|
27
|
+
By design, ``speech_frame_threshold`` consecutive speech frames must
|
|
28
|
+
elapse before the detector declares speech-start. Those trigger
|
|
29
|
+
frames carry the actual onset of the user's utterance (e.g. the "h"
|
|
30
|
+
in "haan") and previously had no public surface for callers to
|
|
31
|
+
recover them — at best the caller kept a tiny rolling preroll that
|
|
32
|
+
was smaller than the trigger window, and the very first speech frame
|
|
33
|
+
was always lost.
|
|
34
|
+
|
|
35
|
+
The detector now maintains its own ``preroll`` deque that captures
|
|
36
|
+
every processed frame. When speech-start fires, the caller MUST call
|
|
37
|
+
``consume_preroll()`` to retrieve those frames and prepend them to
|
|
38
|
+
the audio it forwards to ASR. ``consume_preroll`` returns the bytes
|
|
39
|
+
AND clears the buffer, so subsequent calls within the same speech
|
|
40
|
+
segment are safe (return empty).
|
|
41
|
+
|
|
42
|
+
The preroll size is configurable but defaults to a slightly larger
|
|
43
|
+
window than ``speech_frame_threshold`` so the caller also receives a
|
|
44
|
+
small amount of audio captured just BEFORE the trigger window — this
|
|
45
|
+
catches consonant onsets that webrtcvad sometimes fails to flag as
|
|
46
|
+
speech (e.g. "h", "f", "s") but still belong to the utterance.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
# pylint: disable=too-many-positional-arguments
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
sample_rate: int = 8000,
|
|
53
|
+
frame_duration_ms: int = 20,
|
|
54
|
+
aggressiveness: int = 2,
|
|
55
|
+
silence_frame_threshold: int = 12, # ~240ms
|
|
56
|
+
speech_frame_threshold: int = 5, # ~100ms
|
|
57
|
+
preroll_frames: Optional[int] = None,
|
|
58
|
+
):
|
|
59
|
+
import webrtcvad
|
|
60
|
+
|
|
61
|
+
self.sample_rate = sample_rate
|
|
62
|
+
self.frame_duration_ms = frame_duration_ms
|
|
63
|
+
self.aggressiveness = aggressiveness
|
|
64
|
+
self.frame_size = int(self.sample_rate * self.frame_duration_ms / 1000)
|
|
65
|
+
self.vad = webrtcvad.Vad(self.aggressiveness)
|
|
66
|
+
|
|
67
|
+
self.speech_frame_threshold = speech_frame_threshold
|
|
68
|
+
self.silence_frame_threshold = silence_frame_threshold
|
|
69
|
+
|
|
70
|
+
# Preroll covers AT LEAST the trigger window plus a few extra frames
|
|
71
|
+
# of pre-trigger audio, so the caller never sees a truncated onset.
|
|
72
|
+
# Five extra frames (~100ms) is enough to recover voiceless
|
|
73
|
+
# consonants that VAD initially missed while keeping memory tiny.
|
|
74
|
+
if preroll_frames is None:
|
|
75
|
+
preroll_frames = speech_frame_threshold + 5
|
|
76
|
+
|
|
77
|
+
self.is_talking = False
|
|
78
|
+
self.speech_frames = 0
|
|
79
|
+
self.silence_frames = 0
|
|
80
|
+
self.buffer = b""
|
|
81
|
+
self.last_frame_is_speech: Optional[bool] = None
|
|
82
|
+
self._preroll: Deque[bytes] = deque(maxlen=preroll_frames)
|
|
83
|
+
|
|
84
|
+
def process(self, slin16: bytes) -> Optional[bool]:
|
|
85
|
+
"""Process PCM16 mono audio. Returns:
|
|
86
|
+
- True once on speech start
|
|
87
|
+
- False once on speech end
|
|
88
|
+
- None if no change
|
|
89
|
+
|
|
90
|
+
Side-effect: every fully formed frame is appended to the preroll
|
|
91
|
+
buffer. On the speech-start edge the caller should call
|
|
92
|
+
``consume_preroll()`` to retrieve the trigger frames.
|
|
93
|
+
"""
|
|
94
|
+
self.buffer += slin16
|
|
95
|
+
change = None
|
|
96
|
+
|
|
97
|
+
while len(self.buffer) >= self.frame_size * 2:
|
|
98
|
+
frame, self.buffer = (
|
|
99
|
+
self.buffer[: self.frame_size * 2],
|
|
100
|
+
self.buffer[self.frame_size * 2 :],
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# Always preserve the frame in the preroll. Bounded by maxlen,
|
|
104
|
+
# so memory is O(preroll_frames) regardless of call duration.
|
|
105
|
+
self._preroll.append(frame)
|
|
106
|
+
|
|
107
|
+
is_speech = self.vad.is_speech(frame, self.sample_rate)
|
|
108
|
+
self.last_frame_is_speech = is_speech
|
|
109
|
+
|
|
110
|
+
if is_speech:
|
|
111
|
+
self.speech_frames += 1
|
|
112
|
+
self.silence_frames = 0
|
|
113
|
+
if (
|
|
114
|
+
not self.is_talking
|
|
115
|
+
and self.speech_frames >= self.speech_frame_threshold
|
|
116
|
+
):
|
|
117
|
+
change = True # silence -> speech
|
|
118
|
+
self.is_talking = True
|
|
119
|
+
else:
|
|
120
|
+
self.silence_frames += 1
|
|
121
|
+
self.speech_frames = 0
|
|
122
|
+
if (
|
|
123
|
+
self.is_talking
|
|
124
|
+
and self.silence_frames >= self.silence_frame_threshold
|
|
125
|
+
):
|
|
126
|
+
change = False # speech -> silence
|
|
127
|
+
self.is_talking = False
|
|
128
|
+
|
|
129
|
+
return change
|
|
130
|
+
|
|
131
|
+
def consume_preroll(self) -> bytes:
|
|
132
|
+
"""Return the preroll buffer as concatenated PCM16 bytes and clear it.
|
|
133
|
+
|
|
134
|
+
Call this immediately after ``process()`` returns ``True`` (the
|
|
135
|
+
speech-start edge). The returned bytes contain the frames that
|
|
136
|
+
triggered the start plus a few preceding frames for context, and
|
|
137
|
+
should be prepended to the audio stream forwarded to ASR.
|
|
138
|
+
|
|
139
|
+
Subsequent calls within the same speech segment return ``b""``.
|
|
140
|
+
"""
|
|
141
|
+
out = b"".join(self._preroll)
|
|
142
|
+
self._preroll.clear()
|
|
143
|
+
return out
|
|
144
|
+
|
|
145
|
+
def reset(self) -> None:
|
|
146
|
+
self.buffer = b""
|
|
147
|
+
self.silence_frames = 0
|
|
148
|
+
self.speech_frames = 0
|
|
149
|
+
self.is_talking = False
|
|
150
|
+
self.last_frame_is_speech = None
|
|
151
|
+
self._preroll.clear()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: jaxl-python
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.39
|
|
4
4
|
Summary: Official Python SDK and CLI for interfacing with the Jaxl API. Manage calls, messages, IVRs, devices, teams, payments, campaigns, streaming transcriptions and AI Agents. Built on OpenAPI, it offers both generated API clients and Pythonic wrappers for ease of use.
|
|
5
5
|
Home-page: https://github.com/jaxl-innovations-private-limited/jaxl-python
|
|
6
6
|
Download-URL: https://github.com/jaxl-innovations-private-limited/jaxl-python/archive/main.zip
|
|
@@ -73,6 +73,11 @@ class StreamingVadTest(unittest.TestCase):
|
|
|
73
73
|
self.assertTrue(change)
|
|
74
74
|
|
|
75
75
|
def test_streaming_vad_preserves_preroll_with_earlier_speech_start(self) -> None:
|
|
76
|
+
"""When speech-start fires the pipeline must forward at least the
|
|
77
|
+
speech_frame_threshold trigger frames. Previously a rolling
|
|
78
|
+
deque(maxlen=4) clipped the very first speech frame; the silence
|
|
79
|
+
detector now exposes its own preroll covering ALL trigger frames.
|
|
80
|
+
"""
|
|
76
81
|
app = _StreamingCaptureApp()
|
|
77
82
|
server = _start_server(app, vad_speech_frame_threshold=5)
|
|
78
83
|
state = {
|
|
@@ -103,9 +108,19 @@ class StreamingVadTest(unittest.TestCase):
|
|
|
103
108
|
|
|
104
109
|
self.assertEqual(app.speech_detection_events[:2], [True, False])
|
|
105
110
|
self.assertGreaterEqual(len(app.speech_chunk_batches), 1)
|
|
106
|
-
|
|
111
|
+
# The first batch is the silence detector's preroll (concatenated
|
|
112
|
+
# PCM16 bytes covering all 5 trigger frames). It is delivered as a
|
|
113
|
+
# single bytes-list entry rather than per-frame, but must contain
|
|
114
|
+
# ALL 5 trigger frames worth of audio.
|
|
115
|
+
first_batch = app.speech_chunk_batches[0]
|
|
116
|
+
first_batch_bytes = b"".join(first_batch)
|
|
117
|
+
# 5 frames * 320 bytes/frame = 1600 bytes — the full trigger window.
|
|
118
|
+
self.assertGreaterEqual(len(first_batch_bytes), 5 * 320)
|
|
119
|
+
# Every byte in the preroll must come from the speech tone (0x01),
|
|
120
|
+
# not from earlier silence frames.
|
|
107
121
|
self.assertTrue(
|
|
108
|
-
all(
|
|
122
|
+
all(byte == 0x01 for byte in first_batch_bytes),
|
|
123
|
+
f"preroll contained non-speech bytes: {first_batch_bytes[:40]!r}",
|
|
109
124
|
)
|
|
110
125
|
|
|
111
126
|
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Copyright (c) 2010-present by Jaxl Innovations Private Limited.
|
|
3
|
-
|
|
4
|
-
All rights reserved.
|
|
5
|
-
|
|
6
|
-
Redistribution and use in source and binary forms,
|
|
7
|
-
with or without modification, is strictly prohibited.
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
from typing import Optional
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
# pylint: disable=too-many-instance-attributes
|
|
14
|
-
class SilenceDetector:
|
|
15
|
-
"""Edge-triggered VAD wrapper around py-webrtcvad."""
|
|
16
|
-
|
|
17
|
-
# pylint: disable=too-many-positional-arguments
|
|
18
|
-
def __init__(
|
|
19
|
-
self,
|
|
20
|
-
sample_rate: int = 8000,
|
|
21
|
-
frame_duration_ms: int = 20,
|
|
22
|
-
aggressiveness: int = 2,
|
|
23
|
-
silence_frame_threshold: int = 12, # ~240ms
|
|
24
|
-
speech_frame_threshold: int = 5, # ~100ms
|
|
25
|
-
):
|
|
26
|
-
import webrtcvad
|
|
27
|
-
|
|
28
|
-
self.sample_rate = sample_rate
|
|
29
|
-
self.frame_duration_ms = frame_duration_ms
|
|
30
|
-
self.aggressiveness = aggressiveness
|
|
31
|
-
self.frame_size = int(self.sample_rate * self.frame_duration_ms / 1000)
|
|
32
|
-
self.vad = webrtcvad.Vad(self.aggressiveness)
|
|
33
|
-
|
|
34
|
-
self.speech_frame_threshold = speech_frame_threshold
|
|
35
|
-
self.silence_frame_threshold = silence_frame_threshold
|
|
36
|
-
self.is_talking = False
|
|
37
|
-
self.speech_frames = 0
|
|
38
|
-
self.silence_frames = 0
|
|
39
|
-
self.buffer = b""
|
|
40
|
-
self.last_frame_is_speech: Optional[bool] = None
|
|
41
|
-
|
|
42
|
-
def process(self, slin16: bytes) -> Optional[bool]:
|
|
43
|
-
"""Process PCM16 mono audio. Returns:
|
|
44
|
-
- True once on speech start
|
|
45
|
-
- False once on speech end
|
|
46
|
-
- None if no change
|
|
47
|
-
"""
|
|
48
|
-
self.buffer += slin16
|
|
49
|
-
change = None
|
|
50
|
-
|
|
51
|
-
while len(self.buffer) >= self.frame_size * 2:
|
|
52
|
-
frame, self.buffer = (
|
|
53
|
-
self.buffer[: self.frame_size * 2],
|
|
54
|
-
self.buffer[self.frame_size * 2 :],
|
|
55
|
-
)
|
|
56
|
-
|
|
57
|
-
is_speech = self.vad.is_speech(frame, self.sample_rate)
|
|
58
|
-
self.last_frame_is_speech = is_speech
|
|
59
|
-
|
|
60
|
-
if is_speech:
|
|
61
|
-
self.speech_frames += 1
|
|
62
|
-
self.silence_frames = 0
|
|
63
|
-
if (
|
|
64
|
-
not self.is_talking
|
|
65
|
-
and self.speech_frames >= self.speech_frame_threshold
|
|
66
|
-
):
|
|
67
|
-
change = True # silence -> speech
|
|
68
|
-
self.is_talking = True
|
|
69
|
-
else:
|
|
70
|
-
self.silence_frames += 1
|
|
71
|
-
self.speech_frames = 0
|
|
72
|
-
if (
|
|
73
|
-
self.is_talking
|
|
74
|
-
and self.silence_frames >= self.silence_frame_threshold
|
|
75
|
-
):
|
|
76
|
-
change = False # speech -> silence
|
|
77
|
-
self.is_talking = False
|
|
78
|
-
|
|
79
|
-
return change
|
|
80
|
-
|
|
81
|
-
def reset(self) -> None:
|
|
82
|
-
self.buffer = b""
|
|
83
|
-
self.silence_frames = 0
|
|
84
|
-
self.speech_frames = 0
|
|
85
|
-
self.is_talking = False
|
|
86
|
-
self.last_frame_is_speech = None
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{jaxl_python-0.0.38 → jaxl_python-0.0.39}/examples/request_and_confirm_code_then_send_to_phone.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{jaxl_python-0.0.38 → jaxl_python-0.0.39}/jaxl/api/client/api/v1/v1_app_organizations_list.py
RENAMED
|
File without changes
|