openspeechapi 0.2.6__tar.gz → 0.2.7__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.6 → openspeechapi-0.2.7}/PKG-INFO +1 -1
  2. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/__init__.py +1 -1
  3. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/core/models.py +19 -0
  4. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/providers/stt/iflytek.py +197 -77
  5. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/pyproject.toml +1 -1
  6. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/.dockerignore +0 -0
  7. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/.env.example +0 -0
  8. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/.github/workflows/ci.yml +0 -0
  9. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/.gitignore +0 -0
  10. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/.tmp/audio/en.aiff +0 -0
  11. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/.tmp/audio/en_16k.wav +0 -0
  12. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/.tmp/audio/en_16k_pad6.wav +0 -0
  13. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/.tmp/audio/en_long.aiff +0 -0
  14. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/.tmp/audio/en_long_16k.wav +0 -0
  15. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/.tmp/audio/en_mid.aiff +0 -0
  16. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/.tmp/audio/en_mid_16k.wav +0 -0
  17. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/.tmp/audio/zh.aiff +0 -0
  18. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/.tmp/audio/zh_16k.wav +0 -0
  19. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/.tmp/openspeech-8600.log +0 -0
  20. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/.tmp/openspeech-serve.log +0 -0
  21. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/.tmp/webui-server.log +0 -0
  22. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/.tmp/webui-server.pid +0 -0
  23. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/.tmp/wlk12101.log +0 -0
  24. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/.tmp/wlk12101.pid +0 -0
  25. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/.tmp/wlk12102.log +0 -0
  26. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/.tmp/wlk12102.pid +0 -0
  27. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/AGENTS.md +0 -0
  28. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/CLAUDE.md +0 -0
  29. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/Dockerfile +0 -0
  30. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/README.md +0 -0
  31. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/aibox-script/aibox-1.0.0-SNAPSHOT-stdout.log +0 -0
  32. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/aibox-script/aibox.2026-04-02.log +0 -0
  33. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/aibox-script/com.user.restart-jar.plist +0 -0
  34. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/aibox-script/restart-jar.sh +0 -0
  35. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/aibox-script.tar.gz +0 -0
  36. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/docker-compose.yml +0 -0
  37. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/docs/architecture/local-engine-manager.md +0 -0
  38. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/docs/architecture/logging-spec.md +0 -0
  39. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/docs/architecture/stt-engineering-optimization-guide.md +0 -0
  40. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/docs/architecture/stt-streaming-spec.md +0 -0
  41. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/docs/architecture/webui-phase-a.md +0 -0
  42. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/docs/engines/fish-speech-docker.md +0 -0
  43. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/docs/engines/fish-speech-native.md +0 -0
  44. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/docs/engines/stt-native-models.md +0 -0
  45. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/docs/superpowers/plans/2026-04-01-phase1-implementation.md +0 -0
  46. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/docs/superpowers/plans/2026-04-11-macos-native-tts-stt.md +0 -0
  47. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/docs/superpowers/specs/2026-04-01-openspeech-api-design.md +0 -0
  48. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/docs/superpowers/specs/2026-04-03-hot-lazy-loading.md +0 -0
  49. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/docs/superpowers/specs/2026-04-03-phase2-protocol-layer.md +0 -0
  50. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/docs/superpowers/specs/2026-04-03-phase3-production.md +0 -0
  51. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/docs/superpowers/specs/2026-04-11-macos-native-tts-stt-design.md +0 -0
  52. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/docs/superpowers/specs/2026-04-12-cloud-providers-webui-design.md +0 -0
  53. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/docs/superpowers/specs/2026-04-15-streaming-tts-stt-fixes-display-names.md +0 -0
  54. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/docs/superpowers/specs/2026-04-16-provider-management-engines-rename.md +0 -0
  55. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/examples/client_stt.py +0 -0
  56. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/examples/client_tts.py +0 -0
  57. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/examples/stt_simple.py +0 -0
  58. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/examples/tts_simple.py +0 -0
  59. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/__main__.py +0 -0
  60. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/cli.py +0 -0
  61. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/client/__init__.py +0 -0
  62. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/client/client.py +0 -0
  63. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/config.py +0 -0
  64. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/core/__init__.py +0 -0
  65. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/core/base.py +0 -0
  66. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/core/enums.py +0 -0
  67. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/core/registry.py +0 -0
  68. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/core/settings.py +0 -0
  69. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/demo.py +0 -0
  70. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/dispatch/__init__.py +0 -0
  71. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/dispatch/context.py +0 -0
  72. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/dispatch/dispatcher.py +0 -0
  73. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/dispatch/executors/__init__.py +0 -0
  74. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/dispatch/executors/base.py +0 -0
  75. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/dispatch/executors/in_process.py +0 -0
  76. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/dispatch/executors/remote.py +0 -0
  77. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/dispatch/executors/subprocess_exec.py +0 -0
  78. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/dispatch/fanout.py +0 -0
  79. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/dispatch/filters.py +0 -0
  80. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/dispatch/lifecycle.py +0 -0
  81. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/dispatch/watcher.py +0 -0
  82. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/engine_catalog.py +0 -0
  83. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/engine_registry.yaml +0 -0
  84. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/exceptions.py +0 -0
  85. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/factory.py +0 -0
  86. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/local_engines/__init__.py +0 -0
  87. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/local_engines/aim_resolver.py +0 -0
  88. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/local_engines/backends/__init__.py +0 -0
  89. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/local_engines/backends/docker_backend.py +0 -0
  90. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/local_engines/backends/native_backend.py +0 -0
  91. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/local_engines/base.py +0 -0
  92. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/local_engines/engines/__init__.py +0 -0
  93. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/local_engines/engines/faster_whisper.py +0 -0
  94. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/local_engines/engines/fish_speech.py +0 -0
  95. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/local_engines/engines/sherpa_onnx.py +0 -0
  96. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/local_engines/engines/whisper.py +0 -0
  97. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/local_engines/engines/whisperlivekit.py +0 -0
  98. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/local_engines/manager.py +0 -0
  99. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/local_engines/models.py +0 -0
  100. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/local_engines/progress.py +0 -0
  101. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/local_engines/registry.py +0 -0
  102. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/local_engines/task_store.py +0 -0
  103. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/local_engines/tasks.py +0 -0
  104. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/logging_config.py +0 -0
  105. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/observe/__init__.py +0 -0
  106. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/observe/base.py +0 -0
  107. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/observe/debug.py +0 -0
  108. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/observe/latency.py +0 -0
  109. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/observe/metrics.py +0 -0
  110. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/observe/tracing.py +0 -0
  111. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/observe/usage.py +0 -0
  112. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/providers/__init__.py +0 -0
  113. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/providers/_template.py +0 -0
  114. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/providers/stt/__init__.py +0 -0
  115. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/providers/stt/alibaba.py +0 -0
  116. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/providers/stt/assemblyai.py +0 -0
  117. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/providers/stt/azure_speech.py +0 -0
  118. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/providers/stt/baidu.py +0 -0
  119. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/providers/stt/deepgram.py +0 -0
  120. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/providers/stt/elevenlabs.py +0 -0
  121. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/providers/stt/faster_whisper.py +0 -0
  122. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/providers/stt/google_cloud.py +0 -0
  123. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/providers/stt/macos_speech.py +0 -0
  124. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/providers/stt/openai.py +0 -0
  125. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/providers/stt/sherpa_onnx.py +0 -0
  126. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/providers/stt/tencent.py +0 -0
  127. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/providers/stt/volcengine.py +0 -0
  128. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/providers/stt/whisper.py +0 -0
  129. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/providers/stt/whisperlivekit.py +0 -0
  130. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/providers/stt/windows_speech.py +0 -0
  131. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/providers/tts/__init__.py +0 -0
  132. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/providers/tts/alibaba.py +0 -0
  133. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/providers/tts/azure_speech.py +0 -0
  134. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/providers/tts/baidu.py +0 -0
  135. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/providers/tts/coqui.py +0 -0
  136. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/providers/tts/cosyvoice.py +0 -0
  137. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/providers/tts/deepgram.py +0 -0
  138. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/providers/tts/elevenlabs.py +0 -0
  139. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/providers/tts/fish_speech.py +0 -0
  140. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/providers/tts/google_cloud.py +0 -0
  141. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/providers/tts/iflytek.py +0 -0
  142. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/providers/tts/macos_say.py +0 -0
  143. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/providers/tts/minimax.py +0 -0
  144. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/providers/tts/openai.py +0 -0
  145. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/providers/tts/piper.py +0 -0
  146. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/providers/tts/tencent.py +0 -0
  147. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/providers/tts/volcengine.py +0 -0
  148. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/providers/tts/windows_sapi.py +0 -0
  149. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/server/__init__.py +0 -0
  150. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/server/app.py +0 -0
  151. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/server/auth.py +0 -0
  152. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/server/middleware.py +0 -0
  153. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/server/routes/__init__.py +0 -0
  154. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/server/routes/management.py +0 -0
  155. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/server/routes/stt.py +0 -0
  156. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/server/routes/tts.py +0 -0
  157. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/server/routes/webui.py +0 -0
  158. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/server/webui/app.js +0 -0
  159. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/server/webui/index.html +0 -0
  160. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/server/webui/styles.css +0 -0
  161. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/server/ws/__init__.py +0 -0
  162. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/server/ws/stt_stream.py +0 -0
  163. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/server/ws/tts_stream.py +0 -0
  164. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/telemetry/__init__.py +0 -0
  165. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/telemetry/perf.py +0 -0
  166. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/utils/__init__.py +0 -0
  167. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/utils/audio_converter.py +0 -0
  168. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/utils/audio_playback.py +0 -0
  169. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/openspeechapi/vendor_registry.yaml +0 -0
  170. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/output/output.wav +0 -0
  171. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/output.wav +0 -0
  172. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/providers.example.yaml +0 -0
  173. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/scripts/engines/cloud/install.sh +0 -0
  174. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/scripts/engines/faster-whisper/native/install.sh +0 -0
  175. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/scripts/engines/fish-speech/native/install.sh +0 -0
  176. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/scripts/engines/macos-stt/install.sh +0 -0
  177. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/scripts/engines/macos-stt/macos_stt.swift +0 -0
  178. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/scripts/engines/macos-stt/request_auth.swift +0 -0
  179. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/scripts/engines/sherpa-onnx/native/install.sh +0 -0
  180. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/scripts/engines/sherpa-onnx/native/run_streaming_server.py +0 -0
  181. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/scripts/engines/whisper/native/install.sh +0 -0
  182. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/scripts/engines/whisperlivekit/native/install.sh +0 -0
  183. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/__init__.py +0 -0
  184. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/conftest.py +0 -0
  185. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/e2e/__init__.py +0 -0
  186. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/e2e/conftest.py +0 -0
  187. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/e2e/test_fanout_e2e.py +0 -0
  188. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/e2e/test_faster_whisper_e2e.py +0 -0
  189. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/e2e/test_openai_e2e.py +0 -0
  190. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/e2e/test_webui_e2e.py +0 -0
  191. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/fixtures/hello.wav +0 -0
  192. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/integration/__init__.py +0 -0
  193. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/integration/test_fanout_integration.py +0 -0
  194. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/integration/test_in_process_integration.py +0 -0
  195. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/integration/test_server_client.py +0 -0
  196. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/unit/__init__.py +0 -0
  197. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/unit/test_aim_resolver.py +0 -0
  198. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/unit/test_audio_converter.py +0 -0
  199. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/unit/test_audio_playback.py +0 -0
  200. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/unit/test_base.py +0 -0
  201. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/unit/test_cli.py +0 -0
  202. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/unit/test_cli_engine.py +0 -0
  203. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/unit/test_client.py +0 -0
  204. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/unit/test_config.py +0 -0
  205. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/unit/test_context.py +0 -0
  206. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/unit/test_debug_observer.py +0 -0
  207. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/unit/test_dispatcher.py +0 -0
  208. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/unit/test_docker_backend_progress.py +0 -0
  209. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/unit/test_engine_registry.py +0 -0
  210. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/unit/test_enums.py +0 -0
  211. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/unit/test_executor_base.py +0 -0
  212. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/unit/test_fanout.py +0 -0
  213. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/unit/test_filters.py +0 -0
  214. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/unit/test_hot_reload.py +0 -0
  215. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/unit/test_in_process.py +0 -0
  216. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/unit/test_latency_observer.py +0 -0
  217. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/unit/test_lifecycle.py +0 -0
  218. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/unit/test_local_engine_task_store.py +0 -0
  219. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/unit/test_local_engines_manager.py +0 -0
  220. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/unit/test_logging.py +0 -0
  221. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/unit/test_metrics_observer.py +0 -0
  222. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/unit/test_models.py +0 -0
  223. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/unit/test_native_backend.py +0 -0
  224. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/unit/test_observer_base.py +0 -0
  225. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/unit/test_plugin_mechanism.py +0 -0
  226. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/unit/test_providers/__init__.py +0 -0
  227. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/unit/test_providers/test_cloud_providers.py +0 -0
  228. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/unit/test_providers/test_elevenlabs_stt.py +0 -0
  229. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/unit/test_providers/test_macos_say.py +0 -0
  230. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/unit/test_providers/test_macos_speech.py +0 -0
  231. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/unit/test_providers/test_openai_base_url.py +0 -0
  232. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/unit/test_providers/test_openai_stt.py +0 -0
  233. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/unit/test_providers/test_openai_tts.py +0 -0
  234. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/unit/test_providers/test_sherpa_onnx_stt.py +0 -0
  235. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/unit/test_providers/test_stt_stubs.py +0 -0
  236. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/unit/test_providers/test_tts_stubs.py +0 -0
  237. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/unit/test_providers/test_whisperlivekit_stt.py +0 -0
  238. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/unit/test_registry.py +0 -0
  239. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/unit/test_remote.py +0 -0
  240. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/unit/test_server/__init__.py +0 -0
  241. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/unit/test_server/test_auth.py +0 -0
  242. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/unit/test_server/test_config_api.py +0 -0
  243. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/unit/test_server/test_routes.py +0 -0
  244. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/unit/test_server/test_websocket.py +0 -0
  245. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/unit/test_subprocess.py +0 -0
  246. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/tests/unit/test_usage_observer.py +0 -0
  247. {openspeechapi-0.2.6 → openspeechapi-0.2.7}/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.6
3
+ Version: 0.2.7
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.6"
3
+ __version__ = "0.2.7"
4
4
 
5
5
  from openspeechapi.config import load_config
6
6
  from openspeechapi.core.base import SpeechProvider, STTProvider, TTSProvider
@@ -60,6 +60,25 @@ class STTOptions:
60
60
  # voice assistant. Providers that don't support VAD finalization
61
61
  # (Whisper, Faster-Whisper) silently ignore this field.
62
62
  vad_eos: int | None = None
63
+ # ── iFlytek IAT pass-through (matches Java AsrServiceImpl) ───────
64
+ # Wallex's Java AsrService forwards the client-supplied
65
+ # ``audio.common`` / ``audio.business`` / extra ``audio.data``
66
+ # fields verbatim to iFlytek's WS, treating the panel as the
67
+ # source of truth for ASR parameters. The Python pipeline now
68
+ # mirrors that contract: when these fields are non-None, the
69
+ # iFlytek provider uses them as the basis for the WS first frame
70
+ # (with ``setdefault`` fallback to its own settings for any keys
71
+ # the client omitted) instead of building the blocks purely from
72
+ # ``speech_providers.yaml``. ``None`` preserves the existing
73
+ # yaml-driven behaviour. Other STT providers ignore these fields.
74
+ iflytek_common: dict | None = None
75
+ iflytek_business: dict | None = None
76
+ # Extra fields to merge into the iFlytek ``data`` block beyond the
77
+ # canonical ``status``/``format``/``encoding``/``audio`` quadruple
78
+ # (e.g. panel-supplied ``data_type``). Keys that collide with the
79
+ # canonical set are preserved (the provider's defaults still win,
80
+ # since the canonical set is required by the IAT spec).
81
+ iflytek_data_extras: dict | None = None
63
82
 
64
83
 
65
84
  @dataclass
@@ -72,6 +72,34 @@ class IflytekSTTSettings(BaseSettings):
72
72
  # that. Lower if the network has aggressive proxies, higher only
73
73
  # if the iFlytek endpoint is consistently slow to handshake.
74
74
  timeout_secs: int = 15
75
+ # ── Java AsrConfig parity (used as setdefault fallbacks) ────────
76
+ # When a wallex client (panel) supplies ``audio.business``/
77
+ # ``audio.common`` per-frame, those values flow through via
78
+ # ``STTOptions.iflytek_business``/``STTOptions.iflytek_common`` and
79
+ # become the WS first frame body. The settings below act as
80
+ # ``setdefault`` fallbacks for keys the client omits, mirroring
81
+ # Java ``AsrConfig``'s field set so the two implementations
82
+ # produce identical wire frames given the same panel payload.
83
+ #
84
+ # ``domain`` — iFlytek IAT domain. Java default ``iat``; a few
85
+ # vertical models (``medical`` / ``tv``) exist but most
86
+ # deployments stay on the general one.
87
+ domain: str = "iat"
88
+ # ``accent`` — only meaningful when ``language=="zh_cn"`` (selects
89
+ # mandarin vs. cantonese etc.). Java sends ``mandarin`` blindly;
90
+ # we keep the same default so the WS frame matches Java byte-for-
91
+ # byte when the panel omits ``business.accent``. iFlytek treats
92
+ # it as a no-op for non-Chinese language codes.
93
+ accent: str = "mandarin"
94
+ # ``dwa`` — dynamic word adjustment / wpgs (实时纠错). Java's
95
+ # default ``wpgs`` is the realtime-correction mode panels rely on
96
+ # for the partial-result protocol described in
97
+ # ``stt-streaming-spec.md``. Empty disables it.
98
+ dwa: str = "wpgs"
99
+ # ``sample_rate`` — required by the IAT directed-domain endpoint
100
+ # (``ws-api-dx.xfyun.cn``) which expects it in ``business``. Java
101
+ # AsrConfig.sampleRate=16000.
102
+ sample_rate: int = 16000
75
103
 
76
104
 
77
105
  # iFlytek expects the full locale tag; common ISO short codes need to
@@ -188,6 +216,117 @@ class IflytekSTT(STTProvider):
188
216
  )
189
217
  return f"wss://{host}{path}?{params}"
190
218
 
219
+ def _build_first_frame_blocks(
220
+ self,
221
+ opts: STTOptions | None,
222
+ *,
223
+ include_dwa: bool,
224
+ ) -> tuple[dict, dict]:
225
+ """Build the ``common`` / ``business`` blocks for the WS first frame.
226
+
227
+ Mirrors Java ``AsrServiceImpl.sendToAsr`` semantics: when
228
+ ``opts.iflytek_common``/``opts.iflytek_business`` is provided
229
+ (typically by wallex relaying the panel's per-frame
230
+ ``audio.common`` / ``audio.business``), those dicts are the
231
+ source of truth. We only ``setdefault`` keys the client omitted,
232
+ falling back to ``self.settings`` so a panel that misses a
233
+ single field doesn't get a malformed frame.
234
+
235
+ ``include_dwa`` differs between ``transcribe()`` (batch — no
236
+ wpgs because there's no streaming protocol) and
237
+ ``transcribe_stream()`` (always wpgs).
238
+ """
239
+ canon = _canonical_language(self.settings.language)
240
+ eos = (opts.vad_eos
241
+ if opts is not None and opts.vad_eos is not None
242
+ else self.settings.vad_eos)
243
+
244
+ # ── business block ─────────────────────────────────────────
245
+ if opts is not None and opts.iflytek_business:
246
+ # Panel-supplied is authoritative; copy then fill missing
247
+ # keys from yaml so we never send a partial frame.
248
+ business = dict(opts.iflytek_business)
249
+ else:
250
+ business = {}
251
+
252
+ business.setdefault("language", canon)
253
+ business.setdefault("domain", self.settings.domain)
254
+ business.setdefault("vad_eos", eos)
255
+ business.setdefault("ltc", self.settings.ltc)
256
+ if include_dwa:
257
+ business.setdefault("dwa", self.settings.dwa)
258
+ # ``accent`` is only meaningful for the Chinese model. Java
259
+ # sends ``mandarin`` blindly; we keep that for byte-for-byte
260
+ # parity when the panel omits it AND language is zh_cn. For
261
+ # other languages we leave it out entirely (sending it is a
262
+ # no-op on iFlytek's side but confuses log readers).
263
+ if "accent" not in business and canon == "zh_cn":
264
+ business["accent"] = self.settings.accent
265
+
266
+ # ── common block ──────────────────────────────────────────
267
+ if opts is not None and opts.iflytek_common:
268
+ common = dict(opts.iflytek_common)
269
+ else:
270
+ common = {}
271
+ common.setdefault("app_id", self.settings.app_id)
272
+
273
+ return common, business
274
+
275
+ @staticmethod
276
+ def _build_data_block(
277
+ *, status: int, audio_b64: str, opts: STTOptions | None,
278
+ ) -> dict:
279
+ """Assemble the ``data`` block, merging panel-supplied extras.
280
+
281
+ Canonical keys (``status``/``format``/``encoding``/``audio``)
282
+ always win over ``iflytek_data_extras`` because the IAT spec
283
+ requires them in a specific shape; extras like the panel's
284
+ ``data_type`` flow through.
285
+ """
286
+ if opts is not None and opts.iflytek_data_extras:
287
+ data = dict(opts.iflytek_data_extras)
288
+ else:
289
+ data = {}
290
+ data["status"] = status
291
+ data["format"] = "audio/L16;rate=16000"
292
+ data["encoding"] = "raw"
293
+ data["audio"] = audio_b64
294
+ return data
295
+
296
+ async def _connect_with_retry(self) -> "websockets.ClientConnection":
297
+ """Connect to iFlytek IAT WS with backoff, mirroring Java parity.
298
+
299
+ Java ``AsrServiceImpl.connectWithRetry`` does 4 attempts with
300
+ 300/600/1200ms backoff before giving up. The previous Python
301
+ path was one-shot: a single TCP/handshake hiccup surfaced as a
302
+ hard ASR failure. Aligning the retry budget keeps wallex's
303
+ Python and Java front-ends behaviourally interchangeable on
304
+ flaky links.
305
+ """
306
+ backoffs = (0.3, 0.6, 1.2) # delays AFTER attempts 1, 2, 3
307
+ last_exc: Exception | None = None
308
+ for attempt in range(4):
309
+ try:
310
+ url = self._build_auth_url()
311
+ ws = await websockets.connect(url)
312
+ if attempt > 0:
313
+ logger.info(
314
+ "{}: WS connected on attempt {}/4",
315
+ self.name, attempt + 1,
316
+ )
317
+ return ws
318
+ except Exception as e: # noqa: BLE001 — retry boundary
319
+ last_exc = e
320
+ logger.warning(
321
+ "{}: WS connect failed (attempt {}/4): {}",
322
+ self.name, attempt + 1, e,
323
+ )
324
+ if attempt < len(backoffs):
325
+ await asyncio.sleep(backoffs[attempt])
326
+ raise RuntimeError(
327
+ f"iFlytek STT connect failed after 4 attempts: {last_exc}"
328
+ )
329
+
191
330
  async def transcribe(
192
331
  self, audio: AudioData, opts: STTOptions | None = None
193
332
  ) -> Transcription:
@@ -196,7 +335,6 @@ class IflytekSTT(STTProvider):
196
335
  logger.info("{}: request received, audio={} bytes", self.name, len(audio.data))
197
336
  _t0 = time.perf_counter()
198
337
 
199
- url = self._build_auth_url()
200
338
  audio_bytes = audio.data
201
339
  # iFlytek recommends ~40ms per frame at 16kHz 16bit mono = 1280 bytes.
202
340
  # Use larger frames (8000 bytes = ~250ms) with pacing to avoid server
@@ -209,7 +347,8 @@ class IflytekSTT(STTProvider):
209
347
 
210
348
  result_texts: list[str] = []
211
349
 
212
- async with websockets.connect(url) as ws:
350
+ ws = await self._connect_with_retry()
351
+ async with ws:
213
352
  # Send audio in chunks with interleaved receive
214
353
  total = len(audio_bytes)
215
354
  offset = 0
@@ -228,47 +367,36 @@ class IflytekSTT(STTProvider):
228
367
  frame_data = base64.b64encode(chunk).decode("utf-8")
229
368
 
230
369
  if status == 0:
231
- # First frame includes common and business params.
232
- # ``accent="mandarin"`` is only meaningful for the
233
- # Chinese model; sending it on en_us / ja_jp / etc.
234
- # is a wire-level no-op on iFlytek's side but
235
- # confuses anyone reading the request body, so
236
- # gate it on the canonical language.
237
- canon = _canonical_language(self.settings.language)
238
- # Per-call override (``opts.vad_eos``) trumps the
239
- # provider default. Wallex routes the panel's
240
- # ``parameter.iat.eos`` through here so a kiosk
241
- # can ship a tighter or looser silence threshold
242
- # than the deployment yaml.
243
- eos = (opts.vad_eos
244
- if opts is not None and opts.vad_eos is not None
245
- else self.settings.vad_eos)
246
- business = {
247
- "language": canon,
248
- "domain": "iat",
249
- "vad_eos": eos,
250
- "ltc": self.settings.ltc,
251
- }
252
- if canon == "zh_cn":
253
- business["accent"] = "mandarin"
370
+ # First frame: panel-supplied common/business win;
371
+ # batch path doesn't carry wpgs (no streaming
372
+ # protocol) so include_dwa=False.
373
+ common, business = self._build_first_frame_blocks(
374
+ opts, include_dwa=False,
375
+ )
376
+ data_block = self._build_data_block(
377
+ status=0, audio_b64=frame_data, opts=opts,
378
+ )
254
379
  msg = {
255
- "common": {"app_id": self.settings.app_id},
380
+ "common": common,
256
381
  "business": business,
257
- "data": {
258
- "status": 0,
259
- "format": "audio/L16;rate=16000",
260
- "encoding": "raw",
261
- "audio": frame_data,
262
- },
382
+ "data": data_block,
263
383
  }
384
+ # Java parity: log the exact blocks we're about to
385
+ # ship to iFlytek. Debugging "wrong language /
386
+ # wrong endpoint" reports needs to see this from
387
+ # the log alone — Java's AsrServiceImpl prints the
388
+ # equivalent line at INFO.
389
+ logger.info(
390
+ "{}: ASR first frame business={}, common={}",
391
+ self.name,
392
+ json.dumps(business, ensure_ascii=False),
393
+ json.dumps(common, ensure_ascii=False),
394
+ )
264
395
  else:
265
396
  msg = {
266
- "data": {
267
- "status": status,
268
- "format": "audio/L16;rate=16000",
269
- "encoding": "raw",
270
- "audio": frame_data,
271
- }
397
+ "data": self._build_data_block(
398
+ status=status, audio_b64=frame_data, opts=opts,
399
+ )
272
400
  }
273
401
 
274
402
  await ws.send(json.dumps(msg))
@@ -340,7 +468,6 @@ class IflytekSTT(STTProvider):
340
468
  if self._client is None:
341
469
  raise RuntimeError("Provider not started — call start() first")
342
470
 
343
- url = self._build_auth_url()
344
471
  results: asyncio.Queue[Transcription | None] = asyncio.Queue()
345
472
  _t0 = time.perf_counter()
346
473
  _frames_sent = 0
@@ -352,7 +479,8 @@ class IflytekSTT(STTProvider):
352
479
  _sender_stop = asyncio.Event()
353
480
 
354
481
  logger.debug("{}: connecting to iFlytek WebSocket...", self.name)
355
- async with websockets.connect(url) as ws:
482
+ ws = await self._connect_with_retry()
483
+ async with ws:
356
484
  _t_connected = time.perf_counter()
357
485
  logger.info("{}: WS connected in {:.0f}ms", self.name,
358
486
  (_t_connected - _t0) * 1000)
@@ -370,43 +498,38 @@ class IflytekSTT(STTProvider):
370
498
  break
371
499
  frame_data = base64.b64encode(chunk).decode("utf-8")
372
500
  if is_first:
373
- # See transcribe() for rationale on
374
- # canonicalizing language and gating accent.
375
- canon = _canonical_language(self.settings.language)
376
- # Per-call ``opts.vad_eos`` (e.g. wallex
377
- # forwarding the panel's ``parameter.iat.eos``)
378
- # trumps the provider's configured default.
379
- eos = (opts.vad_eos
380
- if opts is not None and opts.vad_eos is not None
381
- else self.settings.vad_eos)
382
- business = {
383
- "language": canon,
384
- "domain": "iat",
385
- "dwa": "wpgs",
386
- "vad_eos": eos,
387
- "ltc": self.settings.ltc,
388
- }
389
- if canon == "zh_cn":
390
- business["accent"] = "mandarin"
501
+ # First frame: panel-supplied common/business win;
502
+ # streaming path always carries wpgs (see
503
+ # stt-streaming-spec.md realtime-correction
504
+ # protocol) so include_dwa=True.
505
+ common, business = self._build_first_frame_blocks(
506
+ opts, include_dwa=True,
507
+ )
508
+ data_block = self._build_data_block(
509
+ status=0, audio_b64=frame_data, opts=opts,
510
+ )
391
511
  msg = {
392
- "common": {"app_id": self.settings.app_id},
512
+ "common": common,
393
513
  "business": business,
394
- "data": {
395
- "status": 0,
396
- "format": "audio/L16;rate=16000",
397
- "encoding": "raw",
398
- "audio": frame_data,
399
- },
514
+ "data": data_block,
400
515
  }
516
+ # Java parity (AsrServiceImpl line 221): log
517
+ # the first-frame business + common at INFO so
518
+ # operators can verify which language/eos/dwa
519
+ # the panel actually requested without
520
+ # rebuilding the call from yaml + STTOptions.
521
+ logger.info(
522
+ "{}: ASR first frame business={}, common={}",
523
+ self.name,
524
+ json.dumps(business, ensure_ascii=False),
525
+ json.dumps(common, ensure_ascii=False),
526
+ )
401
527
  is_first = False
402
528
  else:
403
529
  msg = {
404
- "data": {
405
- "status": 1,
406
- "format": "audio/L16;rate=16000",
407
- "encoding": "raw",
408
- "audio": frame_data,
409
- }
530
+ "data": self._build_data_block(
531
+ status=1, audio_b64=frame_data, opts=opts,
532
+ )
410
533
  }
411
534
  await ws.send(json.dumps(msg))
412
535
  _frames_sent += 1
@@ -417,12 +540,9 @@ class IflytekSTT(STTProvider):
417
540
  # Send empty last frame to signal end (only if WS still open)
418
541
  if not _sender_stop.is_set():
419
542
  last_msg = {
420
- "data": {
421
- "status": 2,
422
- "format": "audio/L16;rate=16000",
423
- "encoding": "raw",
424
- "audio": "",
425
- }
543
+ "data": self._build_data_block(
544
+ status=2, audio_b64="", opts=opts,
545
+ )
426
546
  }
427
547
  await ws.send(json.dumps(last_msg))
428
548
  except websockets.exceptions.ConnectionClosed:
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "openspeechapi"
7
- version = "0.2.6"
7
+ version = "0.2.7"
8
8
  description = "Unified speech interface for STT/TTS providers"
9
9
  requires-python = ">=3.11"
10
10
  dependencies = [
File without changes
File without changes
File without changes
File without changes
File without changes