openspeechapi 0.2.4__tar.gz → 0.2.6__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.
Files changed (247) hide show
  1. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/PKG-INFO +1 -1
  2. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/__init__.py +1 -1
  3. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/logging_config.py +16 -2
  4. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/providers/stt/iflytek.py +29 -8
  5. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/providers/tts/iflytek.py +59 -22
  6. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/providers.example.yaml +15 -2
  7. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/pyproject.toml +1 -1
  8. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/unit/test_logging.py +32 -0
  9. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/.dockerignore +0 -0
  10. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/.env.example +0 -0
  11. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/.github/workflows/ci.yml +0 -0
  12. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/.gitignore +0 -0
  13. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/.tmp/audio/en.aiff +0 -0
  14. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/.tmp/audio/en_16k.wav +0 -0
  15. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/.tmp/audio/en_16k_pad6.wav +0 -0
  16. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/.tmp/audio/en_long.aiff +0 -0
  17. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/.tmp/audio/en_long_16k.wav +0 -0
  18. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/.tmp/audio/en_mid.aiff +0 -0
  19. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/.tmp/audio/en_mid_16k.wav +0 -0
  20. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/.tmp/audio/zh.aiff +0 -0
  21. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/.tmp/audio/zh_16k.wav +0 -0
  22. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/.tmp/openspeech-8600.log +0 -0
  23. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/.tmp/openspeech-serve.log +0 -0
  24. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/.tmp/webui-server.log +0 -0
  25. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/.tmp/webui-server.pid +0 -0
  26. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/.tmp/wlk12101.log +0 -0
  27. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/.tmp/wlk12101.pid +0 -0
  28. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/.tmp/wlk12102.log +0 -0
  29. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/.tmp/wlk12102.pid +0 -0
  30. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/AGENTS.md +0 -0
  31. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/CLAUDE.md +0 -0
  32. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/Dockerfile +0 -0
  33. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/README.md +0 -0
  34. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/aibox-script/aibox-1.0.0-SNAPSHOT-stdout.log +0 -0
  35. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/aibox-script/aibox.2026-04-02.log +0 -0
  36. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/aibox-script/com.user.restart-jar.plist +0 -0
  37. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/aibox-script/restart-jar.sh +0 -0
  38. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/aibox-script.tar.gz +0 -0
  39. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/docker-compose.yml +0 -0
  40. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/docs/architecture/local-engine-manager.md +0 -0
  41. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/docs/architecture/logging-spec.md +0 -0
  42. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/docs/architecture/stt-engineering-optimization-guide.md +0 -0
  43. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/docs/architecture/stt-streaming-spec.md +0 -0
  44. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/docs/architecture/webui-phase-a.md +0 -0
  45. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/docs/engines/fish-speech-docker.md +0 -0
  46. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/docs/engines/fish-speech-native.md +0 -0
  47. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/docs/engines/stt-native-models.md +0 -0
  48. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/docs/superpowers/plans/2026-04-01-phase1-implementation.md +0 -0
  49. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/docs/superpowers/plans/2026-04-11-macos-native-tts-stt.md +0 -0
  50. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/docs/superpowers/specs/2026-04-01-openspeech-api-design.md +0 -0
  51. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/docs/superpowers/specs/2026-04-03-hot-lazy-loading.md +0 -0
  52. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/docs/superpowers/specs/2026-04-03-phase2-protocol-layer.md +0 -0
  53. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/docs/superpowers/specs/2026-04-03-phase3-production.md +0 -0
  54. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/docs/superpowers/specs/2026-04-11-macos-native-tts-stt-design.md +0 -0
  55. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/docs/superpowers/specs/2026-04-12-cloud-providers-webui-design.md +0 -0
  56. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/docs/superpowers/specs/2026-04-15-streaming-tts-stt-fixes-display-names.md +0 -0
  57. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/docs/superpowers/specs/2026-04-16-provider-management-engines-rename.md +0 -0
  58. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/examples/client_stt.py +0 -0
  59. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/examples/client_tts.py +0 -0
  60. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/examples/stt_simple.py +0 -0
  61. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/examples/tts_simple.py +0 -0
  62. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/__main__.py +0 -0
  63. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/cli.py +0 -0
  64. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/client/__init__.py +0 -0
  65. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/client/client.py +0 -0
  66. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/config.py +0 -0
  67. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/core/__init__.py +0 -0
  68. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/core/base.py +0 -0
  69. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/core/enums.py +0 -0
  70. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/core/models.py +0 -0
  71. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/core/registry.py +0 -0
  72. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/core/settings.py +0 -0
  73. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/demo.py +0 -0
  74. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/dispatch/__init__.py +0 -0
  75. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/dispatch/context.py +0 -0
  76. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/dispatch/dispatcher.py +0 -0
  77. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/dispatch/executors/__init__.py +0 -0
  78. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/dispatch/executors/base.py +0 -0
  79. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/dispatch/executors/in_process.py +0 -0
  80. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/dispatch/executors/remote.py +0 -0
  81. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/dispatch/executors/subprocess_exec.py +0 -0
  82. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/dispatch/fanout.py +0 -0
  83. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/dispatch/filters.py +0 -0
  84. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/dispatch/lifecycle.py +0 -0
  85. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/dispatch/watcher.py +0 -0
  86. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/engine_catalog.py +0 -0
  87. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/engine_registry.yaml +0 -0
  88. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/exceptions.py +0 -0
  89. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/factory.py +0 -0
  90. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/local_engines/__init__.py +0 -0
  91. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/local_engines/aim_resolver.py +0 -0
  92. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/local_engines/backends/__init__.py +0 -0
  93. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/local_engines/backends/docker_backend.py +0 -0
  94. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/local_engines/backends/native_backend.py +0 -0
  95. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/local_engines/base.py +0 -0
  96. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/local_engines/engines/__init__.py +0 -0
  97. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/local_engines/engines/faster_whisper.py +0 -0
  98. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/local_engines/engines/fish_speech.py +0 -0
  99. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/local_engines/engines/sherpa_onnx.py +0 -0
  100. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/local_engines/engines/whisper.py +0 -0
  101. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/local_engines/engines/whisperlivekit.py +0 -0
  102. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/local_engines/manager.py +0 -0
  103. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/local_engines/models.py +0 -0
  104. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/local_engines/progress.py +0 -0
  105. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/local_engines/registry.py +0 -0
  106. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/local_engines/task_store.py +0 -0
  107. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/local_engines/tasks.py +0 -0
  108. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/observe/__init__.py +0 -0
  109. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/observe/base.py +0 -0
  110. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/observe/debug.py +0 -0
  111. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/observe/latency.py +0 -0
  112. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/observe/metrics.py +0 -0
  113. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/observe/tracing.py +0 -0
  114. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/observe/usage.py +0 -0
  115. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/providers/__init__.py +0 -0
  116. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/providers/_template.py +0 -0
  117. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/providers/stt/__init__.py +0 -0
  118. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/providers/stt/alibaba.py +0 -0
  119. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/providers/stt/assemblyai.py +0 -0
  120. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/providers/stt/azure_speech.py +0 -0
  121. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/providers/stt/baidu.py +0 -0
  122. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/providers/stt/deepgram.py +0 -0
  123. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/providers/stt/elevenlabs.py +0 -0
  124. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/providers/stt/faster_whisper.py +0 -0
  125. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/providers/stt/google_cloud.py +0 -0
  126. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/providers/stt/macos_speech.py +0 -0
  127. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/providers/stt/openai.py +0 -0
  128. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/providers/stt/sherpa_onnx.py +0 -0
  129. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/providers/stt/tencent.py +0 -0
  130. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/providers/stt/volcengine.py +0 -0
  131. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/providers/stt/whisper.py +0 -0
  132. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/providers/stt/whisperlivekit.py +0 -0
  133. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/providers/stt/windows_speech.py +0 -0
  134. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/providers/tts/__init__.py +0 -0
  135. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/providers/tts/alibaba.py +0 -0
  136. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/providers/tts/azure_speech.py +0 -0
  137. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/providers/tts/baidu.py +0 -0
  138. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/providers/tts/coqui.py +0 -0
  139. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/providers/tts/cosyvoice.py +0 -0
  140. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/providers/tts/deepgram.py +0 -0
  141. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/providers/tts/elevenlabs.py +0 -0
  142. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/providers/tts/fish_speech.py +0 -0
  143. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/providers/tts/google_cloud.py +0 -0
  144. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/providers/tts/macos_say.py +0 -0
  145. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/providers/tts/minimax.py +0 -0
  146. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/providers/tts/openai.py +0 -0
  147. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/providers/tts/piper.py +0 -0
  148. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/providers/tts/tencent.py +0 -0
  149. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/providers/tts/volcengine.py +0 -0
  150. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/providers/tts/windows_sapi.py +0 -0
  151. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/server/__init__.py +0 -0
  152. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/server/app.py +0 -0
  153. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/server/auth.py +0 -0
  154. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/server/middleware.py +0 -0
  155. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/server/routes/__init__.py +0 -0
  156. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/server/routes/management.py +0 -0
  157. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/server/routes/stt.py +0 -0
  158. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/server/routes/tts.py +0 -0
  159. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/server/routes/webui.py +0 -0
  160. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/server/webui/app.js +0 -0
  161. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/server/webui/index.html +0 -0
  162. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/server/webui/styles.css +0 -0
  163. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/server/ws/__init__.py +0 -0
  164. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/server/ws/stt_stream.py +0 -0
  165. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/server/ws/tts_stream.py +0 -0
  166. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/telemetry/__init__.py +0 -0
  167. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/telemetry/perf.py +0 -0
  168. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/utils/__init__.py +0 -0
  169. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/utils/audio_converter.py +0 -0
  170. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/utils/audio_playback.py +0 -0
  171. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/openspeechapi/vendor_registry.yaml +0 -0
  172. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/output/output.wav +0 -0
  173. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/output.wav +0 -0
  174. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/scripts/engines/cloud/install.sh +0 -0
  175. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/scripts/engines/faster-whisper/native/install.sh +0 -0
  176. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/scripts/engines/fish-speech/native/install.sh +0 -0
  177. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/scripts/engines/macos-stt/install.sh +0 -0
  178. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/scripts/engines/macos-stt/macos_stt.swift +0 -0
  179. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/scripts/engines/macos-stt/request_auth.swift +0 -0
  180. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/scripts/engines/sherpa-onnx/native/install.sh +0 -0
  181. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/scripts/engines/sherpa-onnx/native/run_streaming_server.py +0 -0
  182. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/scripts/engines/whisper/native/install.sh +0 -0
  183. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/scripts/engines/whisperlivekit/native/install.sh +0 -0
  184. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/__init__.py +0 -0
  185. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/conftest.py +0 -0
  186. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/e2e/__init__.py +0 -0
  187. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/e2e/conftest.py +0 -0
  188. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/e2e/test_fanout_e2e.py +0 -0
  189. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/e2e/test_faster_whisper_e2e.py +0 -0
  190. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/e2e/test_openai_e2e.py +0 -0
  191. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/e2e/test_webui_e2e.py +0 -0
  192. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/fixtures/hello.wav +0 -0
  193. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/integration/__init__.py +0 -0
  194. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/integration/test_fanout_integration.py +0 -0
  195. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/integration/test_in_process_integration.py +0 -0
  196. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/integration/test_server_client.py +0 -0
  197. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/unit/__init__.py +0 -0
  198. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/unit/test_aim_resolver.py +0 -0
  199. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/unit/test_audio_converter.py +0 -0
  200. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/unit/test_audio_playback.py +0 -0
  201. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/unit/test_base.py +0 -0
  202. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/unit/test_cli.py +0 -0
  203. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/unit/test_cli_engine.py +0 -0
  204. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/unit/test_client.py +0 -0
  205. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/unit/test_config.py +0 -0
  206. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/unit/test_context.py +0 -0
  207. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/unit/test_debug_observer.py +0 -0
  208. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/unit/test_dispatcher.py +0 -0
  209. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/unit/test_docker_backend_progress.py +0 -0
  210. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/unit/test_engine_registry.py +0 -0
  211. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/unit/test_enums.py +0 -0
  212. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/unit/test_executor_base.py +0 -0
  213. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/unit/test_fanout.py +0 -0
  214. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/unit/test_filters.py +0 -0
  215. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/unit/test_hot_reload.py +0 -0
  216. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/unit/test_in_process.py +0 -0
  217. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/unit/test_latency_observer.py +0 -0
  218. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/unit/test_lifecycle.py +0 -0
  219. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/unit/test_local_engine_task_store.py +0 -0
  220. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/unit/test_local_engines_manager.py +0 -0
  221. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/unit/test_metrics_observer.py +0 -0
  222. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/unit/test_models.py +0 -0
  223. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/unit/test_native_backend.py +0 -0
  224. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/unit/test_observer_base.py +0 -0
  225. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/unit/test_plugin_mechanism.py +0 -0
  226. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/unit/test_providers/__init__.py +0 -0
  227. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/unit/test_providers/test_cloud_providers.py +0 -0
  228. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/unit/test_providers/test_elevenlabs_stt.py +0 -0
  229. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/unit/test_providers/test_macos_say.py +0 -0
  230. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/unit/test_providers/test_macos_speech.py +0 -0
  231. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/unit/test_providers/test_openai_base_url.py +0 -0
  232. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/unit/test_providers/test_openai_stt.py +0 -0
  233. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/unit/test_providers/test_openai_tts.py +0 -0
  234. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/unit/test_providers/test_sherpa_onnx_stt.py +0 -0
  235. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/unit/test_providers/test_stt_stubs.py +0 -0
  236. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/unit/test_providers/test_tts_stubs.py +0 -0
  237. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/unit/test_providers/test_whisperlivekit_stt.py +0 -0
  238. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/unit/test_registry.py +0 -0
  239. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/unit/test_remote.py +0 -0
  240. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/unit/test_server/__init__.py +0 -0
  241. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/unit/test_server/test_auth.py +0 -0
  242. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/unit/test_server/test_config_api.py +0 -0
  243. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/unit/test_server/test_routes.py +0 -0
  244. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/unit/test_server/test_websocket.py +0 -0
  245. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/unit/test_subprocess.py +0 -0
  246. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/unit/test_usage_observer.py +0 -0
  247. {openspeechapi-0.2.4 → openspeechapi-0.2.6}/tests/unit/test_watcher.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openspeechapi
3
- Version: 0.2.4
3
+ Version: 0.2.6
4
4
  Summary: Unified speech interface for STT/TTS providers
5
5
  Requires-Python: >=3.11
6
6
  Requires-Dist: httpx>=0.27
@@ -1,6 +1,6 @@
1
1
  """OpenSpeechAPI — Unified speech interface for STT/TTS providers."""
2
2
 
3
- __version__ = "0.2.4"
3
+ __version__ = "0.2.6"
4
4
 
5
5
  from openspeechapi.config import load_config
6
6
  from openspeechapi.core.base import SpeechProvider, STTProvider, TTSProvider
@@ -185,10 +185,24 @@ def bind_context(
185
185
  if v is not None:
186
186
  bindings[k] = v
187
187
 
188
+ # NOTE: we drive logger.contextualize manually instead of using its
189
+ # ``with`` form so we can swallow the cleanup-time ValueError that
190
+ # loguru raises when bind_context wraps a ``yield`` in an async
191
+ # generator and that generator is closed from a different asyncio
192
+ # Context (e.g. the consumer task gets cancelled). The token loguru
193
+ # tries to reset was set in the original Context and Python forbids
194
+ # cross-context reset. The bindings are scoped to the closing
195
+ # Context anyway, so dropping the reset is harmless — Python GCs the
196
+ # vanishing Context's vars regardless.
197
+ contextualizer = logger.contextualize(**bindings)
198
+ contextualizer.__enter__()
188
199
  try:
189
- with logger.contextualize(**bindings):
190
- yield bindings
200
+ yield bindings
191
201
  finally:
202
+ try:
203
+ contextualizer.__exit__(None, None, None)
204
+ except ValueError:
205
+ pass
192
206
  for var, token in reversed(resets):
193
207
  try:
194
208
  var.reset(token)
@@ -52,6 +52,26 @@ class IflytekSTTSettings(BaseSettings):
52
52
  # via ``speech_providers.yaml`` so different sites can pick their
53
53
  # own latency-vs-tolerance trade-off.
54
54
  vad_eos: int = 2000
55
+ # ``ltc`` — sentence-level timestamp granularity sent in the
56
+ # business block of the IAT request (1 = sentence segments only;
57
+ # 2 = + word boundaries; 3 = + character boundaries). Java's
58
+ # ``AsrConfig.ltc`` defaults to 3; we mirror that so downstream
59
+ # consumers expecting per-character timing offsets keep working.
60
+ # Lower values shave a few bytes per response and slightly reduce
61
+ # post-processing work for callers that don't use the timestamps.
62
+ ltc: int = 3
63
+ # ``ws_host`` / ``ws_path`` — iFlytek IAT WebSocket endpoint. The
64
+ # default ``iat-api.xfyun.cn`` is the global endpoint; multi-region
65
+ # deployments (e.g. directed-domain endpoints such as
66
+ # ``ws-api-dx.xfyun.cn``) override these in yaml or via env var
67
+ # so the WS URL never requires a code change.
68
+ ws_host: str = "iat-api.xfyun.cn"
69
+ ws_path: str = "/v2/iat"
70
+ # ``timeout_secs`` — connect / read timeout for the underlying
71
+ # httpx AsyncClient. Java's AsrConfig defaults to 15s; we mirror
72
+ # that. Lower if the network has aggressive proxies, higher only
73
+ # if the iFlytek endpoint is consistently slow to handshake.
74
+ timeout_secs: int = 15
55
75
 
56
76
 
57
77
  # iFlytek expects the full locale tag; common ISO short codes need to
@@ -92,9 +112,6 @@ class IflytekSTT(STTProvider):
92
112
  "language": ["zh_cn", "en_us", "ja_jp", "ko_kr", "ru-ru"],
93
113
  }
94
114
 
95
- _WS_HOST = "iat-api.xfyun.cn"
96
- _WS_PATH = "/v2/iat"
97
-
98
115
  def __init__(self, settings: IflytekSTTSettings | None = None) -> None:
99
116
  self.settings = settings or IflytekSTTSettings()
100
117
  self._client: httpx.AsyncClient | None = None
@@ -106,7 +123,7 @@ class IflytekSTT(STTProvider):
106
123
 
107
124
  async def start(self) -> None:
108
125
  if self._client is None:
109
- self._client = httpx.AsyncClient(timeout=60.0)
126
+ self._client = httpx.AsyncClient(timeout=float(self.settings.timeout_secs))
110
127
  self._owns_client = True
111
128
  # Surface the effective language (after alias mapping) and
112
129
  # vad_eos at startup so deployments can verify the iFlytek model
@@ -142,10 +159,12 @@ class IflytekSTT(STTProvider):
142
159
  now = datetime.now(tz=timezone.utc)
143
160
  date = formatdate(timeval=now.timestamp(), localtime=False, usegmt=True)
144
161
 
162
+ host = self.settings.ws_host
163
+ path = self.settings.ws_path
145
164
  signature_origin = (
146
- f"host: {self._WS_HOST}\n"
165
+ f"host: {host}\n"
147
166
  f"date: {date}\n"
148
- f"GET {self._WS_PATH} HTTP/1.1"
167
+ f"GET {path} HTTP/1.1"
149
168
  )
150
169
  signature_sha = hmac.new(
151
170
  self.settings.api_secret.encode("utf-8"),
@@ -165,9 +184,9 @@ class IflytekSTT(STTProvider):
165
184
  ).decode("utf-8")
166
185
 
167
186
  params = urllib.parse.urlencode(
168
- {"authorization": authorization, "date": date, "host": self._WS_HOST}
187
+ {"authorization": authorization, "date": date, "host": host}
169
188
  )
170
- return f"wss://{self._WS_HOST}{self._WS_PATH}?{params}"
189
+ return f"wss://{host}{path}?{params}"
171
190
 
172
191
  async def transcribe(
173
192
  self, audio: AudioData, opts: STTOptions | None = None
@@ -228,6 +247,7 @@ class IflytekSTT(STTProvider):
228
247
  "language": canon,
229
248
  "domain": "iat",
230
249
  "vad_eos": eos,
250
+ "ltc": self.settings.ltc,
231
251
  }
232
252
  if canon == "zh_cn":
233
253
  business["accent"] = "mandarin"
@@ -364,6 +384,7 @@ class IflytekSTT(STTProvider):
364
384
  "domain": "iat",
365
385
  "dwa": "wpgs",
366
386
  "vad_eos": eos,
387
+ "ltc": self.settings.ltc,
367
388
  }
368
389
  if canon == "zh_cn":
369
390
  business["accent"] = "mandarin"
@@ -29,16 +29,22 @@ class IflytekTTSSettings(BaseSettings):
29
29
  voice: str = "xiaoyan"
30
30
  speed: int = 50
31
31
  # Audio output encoding requested from iFlytek.
32
- # - "lame": MP3 frames (default; smaller, but caller must decode)
33
- # - "raw": 16-bit PCM @ 16 kHz mono, big-endian L16 (drop-in
34
- # playable as raw PCM; required by callers that wrap the
35
- # bytes in a fixed-format wire envelope and assume PCM,
36
- # e.g. wallex's RESP_VOICE which advertises
37
- # encoding=raw/bitDepth=16/sampleRate=16000 to the
38
- # panel — feeding MP3 bytes through that envelope plays
39
- # back as pure noise on the speaker).
32
+ # - "lame": MP3 frames (default; smaller, but caller must decode)
33
+ # - "raw": 16-bit PCM @ 16 kHz mono, big-endian L16 (drop-in
34
+ # playable as raw PCM; required by callers that wrap the
35
+ # bytes in a fixed-format wire envelope and assume PCM,
36
+ # e.g. wallex's RESP_VOICE which advertises
37
+ # encoding=raw/bitDepth=16/sampleRate=16000 to the
38
+ # panel — feeding MP3 bytes through that envelope plays
39
+ # back as pure noise on the speaker).
40
+ # - "speex"/"speex-wb-7": Speex narrowband / wideband (low-bitrate,
41
+ # used by some embedded Wallex panels with constrained
42
+ # uplink). Requires ``speex_size`` to declare the frame
43
+ # size iFlytek should produce. Caller must run a Speex
44
+ # decoder; not auto-handled by browsers.
40
45
  # Default stays "lame" for backward-compat; deployments that need
41
- # PCM (wallex / direct hardware playback) override via yaml.
46
+ # PCM (wallex / direct hardware playback) or Speex (embedded panels)
47
+ # override via yaml.
42
48
  aue: str = "lame"
43
49
  # Output sample rate for raw PCM mode (only meaningful when
44
50
  # aue="raw"). 16000 matches what the panel and the iFlytek
@@ -50,6 +56,22 @@ class IflytekTTSSettings(BaseSettings):
50
56
  volume: int = 50
51
57
  # Pitch (0-100). Same rationale as volume.
52
58
  pitch: int = 50
59
+ # Speex frame size (only meaningful when aue startswith "speex").
60
+ # iFlytek expects an integer that selects a Speex bitrate / frame
61
+ # mode; ``0`` is "auto-pick by aue tag". Leave 0 unless the client
62
+ # decoder requires a specific frame size. Mirrors Java
63
+ # ``AsrConfig.speex-size`` / ``TtsConfig`` parameter.
64
+ speex_size: int = 0
65
+ # ``ws_host`` / ``ws_path`` — iFlytek TTS WebSocket endpoint.
66
+ # Override in yaml (or via ``OPENSPEECH_IFLYTEK_TTS_HOST`` env var)
67
+ # for region-specific endpoints. Default is the global endpoint.
68
+ ws_host: str = "tts-api.xfyun.cn"
69
+ ws_path: str = "/v2/tts"
70
+ # ``timeout_secs`` — connect / read timeout for the underlying
71
+ # httpx AsyncClient. Java's TtsConfig defaults to 8s; we mirror
72
+ # that for parity. Increase only when the iFlytek endpoint is
73
+ # consistently slow to handshake.
74
+ timeout_secs: int = 8
53
75
 
54
76
  class IflytekTTS(TTSProvider):
55
77
  name = "iflytek-tts"
@@ -66,12 +88,9 @@ class IflytekTTS(TTSProvider):
66
88
  # English assistant-style voices used by wallex deployments.
67
89
  "x4_enuk_ashleigh_assist",
68
90
  ],
69
- "aue": ["lame", "raw"],
91
+ "aue": ["lame", "raw", "speex", "speex-wb-7"],
70
92
  }
71
93
 
72
- _WS_HOST = "tts-api.xfyun.cn"
73
- _WS_PATH = "/v2/tts"
74
-
75
94
  def __init__(self, settings: IflytekTTSSettings | None = None) -> None:
76
95
  self.settings = settings or IflytekTTSSettings()
77
96
  self._client: httpx.AsyncClient | None = None
@@ -83,7 +102,7 @@ class IflytekTTS(TTSProvider):
83
102
 
84
103
  async def start(self) -> None:
85
104
  if self._client is None:
86
- self._client = httpx.AsyncClient(timeout=60.0)
105
+ self._client = httpx.AsyncClient(timeout=float(self.settings.timeout_secs))
87
106
  self._owns_client = True
88
107
 
89
108
  async def stop(self) -> None:
@@ -99,10 +118,12 @@ class IflytekTTS(TTSProvider):
99
118
  now = datetime.now(tz=timezone.utc)
100
119
  date = formatdate(timeval=now.timestamp(), localtime=False, usegmt=True)
101
120
 
121
+ host = self.settings.ws_host
122
+ path = self.settings.ws_path
102
123
  signature_origin = (
103
- f"host: {self._WS_HOST}\n"
124
+ f"host: {host}\n"
104
125
  f"date: {date}\n"
105
- f"GET {self._WS_PATH} HTTP/1.1"
126
+ f"GET {path} HTTP/1.1"
106
127
  )
107
128
  signature_sha = hmac.new(
108
129
  self.settings.api_secret.encode("utf-8"),
@@ -122,9 +143,9 @@ class IflytekTTS(TTSProvider):
122
143
  ).decode("utf-8")
123
144
 
124
145
  params = urllib.parse.urlencode(
125
- {"authorization": authorization, "date": date, "host": self._WS_HOST}
146
+ {"authorization": authorization, "date": date, "host": host}
126
147
  )
127
- return f"wss://{self._WS_HOST}{self._WS_PATH}?{params}"
148
+ return f"wss://{host}{path}?{params}"
128
149
 
129
150
  async def synthesize(
130
151
  self, text: str, opts: TTSOptions | None = None
@@ -138,8 +159,17 @@ class IflytekTTS(TTSProvider):
138
159
  # callers downstream may set wire-protocol encoding metadata from
139
160
  # this field, and a wrong tag on the bytes plays back as noise on
140
161
  # raw-PCM consumers.
141
- fmt = "pcm_s16le" if self.settings.aue == "raw" else "mp3"
142
- sr = self.settings.auf_rate if self.settings.aue == "raw" else 16000
162
+ aue = self.settings.aue
163
+ if aue == "raw":
164
+ fmt = "pcm_s16le"
165
+ sr = self.settings.auf_rate
166
+ elif aue.startswith("speex"):
167
+ # Speex narrowband is 8 kHz, wideband ("speex-wb-*") is 16 kHz.
168
+ fmt = "speex"
169
+ sr = 16000 if "wb" in aue else 8000
170
+ else:
171
+ fmt = "mp3"
172
+ sr = 16000
143
173
  logger.info(
144
174
  "iFlytek TTS: {} chunks, {} bytes total, format={}, sample_rate={}",
145
175
  len(parts), len(audio_bytes), fmt, sr,
@@ -191,7 +221,8 @@ class IflytekTTS(TTSProvider):
191
221
  "pitch": self.settings.pitch,
192
222
  "tte": "UTF8",
193
223
  }
194
- if self.settings.aue == "lame":
224
+ aue = self.settings.aue
225
+ if aue == "lame":
195
226
  # ``sfl=1`` (stream-frame-length) is an MP3-only knob that
196
227
  # tells iFlytek to emit per-frame audio rather than waiting
197
228
  # for the whole file. It has no meaning for raw PCM (raw is
@@ -199,11 +230,17 @@ class IflytekTTS(TTSProvider):
199
230
  # combo with a code 10005 "invalid parameter" — so we only
200
231
  # send it on the lame path.
201
232
  business["sfl"] = 1
202
- else:
233
+ elif aue == "raw":
203
234
  # Raw / L16 mode requires ``auf`` to declare the PCM
204
235
  # sample-rate iFlytek should produce. Java wallex sends
205
236
  # ``audio/L16;rate=16000`` here; we mirror that exactly.
206
237
  business["auf"] = f"audio/L16;rate={self.settings.auf_rate}"
238
+ elif aue.startswith("speex"):
239
+ # Speex narrowband / wideband. ``speex_size`` is the iFlytek
240
+ # frame-size selector (0 = engine default; non-zero values
241
+ # match the Java ``TtsConfig.speex-size`` parameter).
242
+ if self.settings.speex_size:
243
+ business["speex_size"] = self.settings.speex_size
207
244
  return {
208
245
  "common": {"app_id": self.settings.app_id},
209
246
  "business": business,
@@ -123,7 +123,12 @@ engines:
123
123
  # exec_mode: remote
124
124
  # preload: true
125
125
  # settings:
126
- # language: zh_cn
126
+ # language: zh_cn # zh_cn / en_us / ja_jp / ko_kr / ru-ru
127
+ # vad_eos: 2000 # ms of trailing silence before final
128
+ # ltc: 3 # 1 sentence / 2 +word / 3 +char timestamps
129
+ # ws_host: iat-api.xfyun.cn # override for region-specific endpoints
130
+ # ws_path: /v2/iat
131
+ # timeout_secs: 15
127
132
 
128
133
  # # pip install 'openspeechapi[faster-whisper-stt]'
129
134
  # faster_whisper_stt:
@@ -190,7 +195,15 @@ engines:
190
195
  # exec_mode: remote
191
196
  # settings:
192
197
  # voice: xiaoyan
193
- # speed: 50
198
+ # speed: 50 # 0-100
199
+ # volume: 50 # 0-100
200
+ # pitch: 50 # 0-100
201
+ # aue: lame # lame / raw / speex / speex-wb-7
202
+ # auf_rate: 16000 # only used when aue=raw (8000 / 16000 / 24000)
203
+ # speex_size: 0 # only used when aue startswith speex (0 = auto)
204
+ # ws_host: tts-api.xfyun.cn # override for region-specific endpoints
205
+ # ws_path: /v2/tts
206
+ # timeout_secs: 8
194
207
 
195
208
  # # pip install 'openspeechapi[piper-tts]'
196
209
  # piper_tts:
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "openspeechapi"
7
- version = "0.2.4"
7
+ version = "0.2.6"
8
8
  description = "Unified speech interface for STT/TTS providers"
9
9
  requires-python = ">=3.11"
10
10
  dependencies = [
@@ -96,6 +96,38 @@ def test_bind_context_sets_and_resets_request_id() -> None:
96
96
  assert get_request_id() is None
97
97
 
98
98
 
99
+ def test_bind_context_aclose_from_different_asyncio_context_does_not_raise() -> None:
100
+ """When bind_context wraps a yield in an async generator and the generator
101
+ is later closed from a different asyncio Context, loguru's
102
+ contextualize.__exit__ raises
103
+ ``ValueError("<Token ...> was created in a different Context")``.
104
+
105
+ Production trace (2026-05-06): openspeechapi/dispatch/dispatcher.py
106
+ ``_synthesize_stream`` holds bind_context across ``yield chunk``; when
107
+ the consumer's task is cancelled, athrow runs the finally in a new
108
+ Context and the unhandled ValueError pollutes the log.
109
+
110
+ bind_context must swallow that cleanup error.
111
+ """
112
+ import asyncio
113
+
114
+ async def gen():
115
+ with bind_context(request_id="rid", provider="prov"):
116
+ for i in range(3):
117
+ yield i
118
+
119
+ async def driver():
120
+ g = gen()
121
+ first = await g.__anext__()
122
+ assert first == 0
123
+ # Closing from a freshly-created task puts aclose in a different
124
+ # asyncio Context than where bind_context was entered. This is the
125
+ # exact mismatch that production hit.
126
+ await asyncio.create_task(g.aclose())
127
+
128
+ asyncio.run(driver())
129
+
130
+
99
131
  def test_bind_context_decorates_log_records(tmp_path: Path) -> None:
100
132
  configure_logging(level="DEBUG", format="json", log_dir=str(tmp_path))
101
133
  captured: list[dict] = []
File without changes
File without changes
File without changes
File without changes
File without changes