openspeechapi 0.2.6__tar.gz → 0.2.8__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.8}/PKG-INFO +1 -1
  2. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/__init__.py +1 -1
  3. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/core/models.py +19 -0
  4. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/providers/stt/iflytek.py +214 -77
  5. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/pyproject.toml +1 -1
  6. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/.dockerignore +0 -0
  7. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/.env.example +0 -0
  8. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/.github/workflows/ci.yml +0 -0
  9. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/.gitignore +0 -0
  10. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/.tmp/audio/en.aiff +0 -0
  11. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/.tmp/audio/en_16k.wav +0 -0
  12. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/.tmp/audio/en_16k_pad6.wav +0 -0
  13. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/.tmp/audio/en_long.aiff +0 -0
  14. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/.tmp/audio/en_long_16k.wav +0 -0
  15. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/.tmp/audio/en_mid.aiff +0 -0
  16. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/.tmp/audio/en_mid_16k.wav +0 -0
  17. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/.tmp/audio/zh.aiff +0 -0
  18. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/.tmp/audio/zh_16k.wav +0 -0
  19. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/.tmp/openspeech-8600.log +0 -0
  20. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/.tmp/openspeech-serve.log +0 -0
  21. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/.tmp/webui-server.log +0 -0
  22. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/.tmp/webui-server.pid +0 -0
  23. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/.tmp/wlk12101.log +0 -0
  24. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/.tmp/wlk12101.pid +0 -0
  25. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/.tmp/wlk12102.log +0 -0
  26. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/.tmp/wlk12102.pid +0 -0
  27. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/AGENTS.md +0 -0
  28. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/CLAUDE.md +0 -0
  29. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/Dockerfile +0 -0
  30. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/README.md +0 -0
  31. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/aibox-script/aibox-1.0.0-SNAPSHOT-stdout.log +0 -0
  32. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/aibox-script/aibox.2026-04-02.log +0 -0
  33. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/aibox-script/com.user.restart-jar.plist +0 -0
  34. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/aibox-script/restart-jar.sh +0 -0
  35. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/aibox-script.tar.gz +0 -0
  36. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/docker-compose.yml +0 -0
  37. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/docs/architecture/local-engine-manager.md +0 -0
  38. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/docs/architecture/logging-spec.md +0 -0
  39. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/docs/architecture/stt-engineering-optimization-guide.md +0 -0
  40. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/docs/architecture/stt-streaming-spec.md +0 -0
  41. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/docs/architecture/webui-phase-a.md +0 -0
  42. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/docs/engines/fish-speech-docker.md +0 -0
  43. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/docs/engines/fish-speech-native.md +0 -0
  44. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/docs/engines/stt-native-models.md +0 -0
  45. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/docs/superpowers/plans/2026-04-01-phase1-implementation.md +0 -0
  46. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/docs/superpowers/plans/2026-04-11-macos-native-tts-stt.md +0 -0
  47. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/docs/superpowers/specs/2026-04-01-openspeech-api-design.md +0 -0
  48. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/docs/superpowers/specs/2026-04-03-hot-lazy-loading.md +0 -0
  49. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/docs/superpowers/specs/2026-04-03-phase2-protocol-layer.md +0 -0
  50. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/docs/superpowers/specs/2026-04-03-phase3-production.md +0 -0
  51. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/docs/superpowers/specs/2026-04-11-macos-native-tts-stt-design.md +0 -0
  52. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/docs/superpowers/specs/2026-04-12-cloud-providers-webui-design.md +0 -0
  53. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/docs/superpowers/specs/2026-04-15-streaming-tts-stt-fixes-display-names.md +0 -0
  54. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/docs/superpowers/specs/2026-04-16-provider-management-engines-rename.md +0 -0
  55. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/examples/client_stt.py +0 -0
  56. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/examples/client_tts.py +0 -0
  57. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/examples/stt_simple.py +0 -0
  58. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/examples/tts_simple.py +0 -0
  59. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/__main__.py +0 -0
  60. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/cli.py +0 -0
  61. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/client/__init__.py +0 -0
  62. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/client/client.py +0 -0
  63. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/config.py +0 -0
  64. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/core/__init__.py +0 -0
  65. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/core/base.py +0 -0
  66. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/core/enums.py +0 -0
  67. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/core/registry.py +0 -0
  68. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/core/settings.py +0 -0
  69. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/demo.py +0 -0
  70. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/dispatch/__init__.py +0 -0
  71. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/dispatch/context.py +0 -0
  72. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/dispatch/dispatcher.py +0 -0
  73. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/dispatch/executors/__init__.py +0 -0
  74. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/dispatch/executors/base.py +0 -0
  75. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/dispatch/executors/in_process.py +0 -0
  76. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/dispatch/executors/remote.py +0 -0
  77. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/dispatch/executors/subprocess_exec.py +0 -0
  78. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/dispatch/fanout.py +0 -0
  79. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/dispatch/filters.py +0 -0
  80. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/dispatch/lifecycle.py +0 -0
  81. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/dispatch/watcher.py +0 -0
  82. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/engine_catalog.py +0 -0
  83. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/engine_registry.yaml +0 -0
  84. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/exceptions.py +0 -0
  85. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/factory.py +0 -0
  86. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/local_engines/__init__.py +0 -0
  87. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/local_engines/aim_resolver.py +0 -0
  88. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/local_engines/backends/__init__.py +0 -0
  89. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/local_engines/backends/docker_backend.py +0 -0
  90. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/local_engines/backends/native_backend.py +0 -0
  91. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/local_engines/base.py +0 -0
  92. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/local_engines/engines/__init__.py +0 -0
  93. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/local_engines/engines/faster_whisper.py +0 -0
  94. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/local_engines/engines/fish_speech.py +0 -0
  95. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/local_engines/engines/sherpa_onnx.py +0 -0
  96. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/local_engines/engines/whisper.py +0 -0
  97. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/local_engines/engines/whisperlivekit.py +0 -0
  98. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/local_engines/manager.py +0 -0
  99. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/local_engines/models.py +0 -0
  100. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/local_engines/progress.py +0 -0
  101. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/local_engines/registry.py +0 -0
  102. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/local_engines/task_store.py +0 -0
  103. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/local_engines/tasks.py +0 -0
  104. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/logging_config.py +0 -0
  105. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/observe/__init__.py +0 -0
  106. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/observe/base.py +0 -0
  107. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/observe/debug.py +0 -0
  108. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/observe/latency.py +0 -0
  109. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/observe/metrics.py +0 -0
  110. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/observe/tracing.py +0 -0
  111. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/observe/usage.py +0 -0
  112. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/providers/__init__.py +0 -0
  113. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/providers/_template.py +0 -0
  114. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/providers/stt/__init__.py +0 -0
  115. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/providers/stt/alibaba.py +0 -0
  116. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/providers/stt/assemblyai.py +0 -0
  117. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/providers/stt/azure_speech.py +0 -0
  118. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/providers/stt/baidu.py +0 -0
  119. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/providers/stt/deepgram.py +0 -0
  120. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/providers/stt/elevenlabs.py +0 -0
  121. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/providers/stt/faster_whisper.py +0 -0
  122. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/providers/stt/google_cloud.py +0 -0
  123. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/providers/stt/macos_speech.py +0 -0
  124. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/providers/stt/openai.py +0 -0
  125. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/providers/stt/sherpa_onnx.py +0 -0
  126. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/providers/stt/tencent.py +0 -0
  127. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/providers/stt/volcengine.py +0 -0
  128. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/providers/stt/whisper.py +0 -0
  129. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/providers/stt/whisperlivekit.py +0 -0
  130. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/providers/stt/windows_speech.py +0 -0
  131. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/providers/tts/__init__.py +0 -0
  132. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/providers/tts/alibaba.py +0 -0
  133. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/providers/tts/azure_speech.py +0 -0
  134. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/providers/tts/baidu.py +0 -0
  135. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/providers/tts/coqui.py +0 -0
  136. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/providers/tts/cosyvoice.py +0 -0
  137. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/providers/tts/deepgram.py +0 -0
  138. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/providers/tts/elevenlabs.py +0 -0
  139. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/providers/tts/fish_speech.py +0 -0
  140. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/providers/tts/google_cloud.py +0 -0
  141. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/providers/tts/iflytek.py +0 -0
  142. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/providers/tts/macos_say.py +0 -0
  143. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/providers/tts/minimax.py +0 -0
  144. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/providers/tts/openai.py +0 -0
  145. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/providers/tts/piper.py +0 -0
  146. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/providers/tts/tencent.py +0 -0
  147. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/providers/tts/volcengine.py +0 -0
  148. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/providers/tts/windows_sapi.py +0 -0
  149. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/server/__init__.py +0 -0
  150. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/server/app.py +0 -0
  151. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/server/auth.py +0 -0
  152. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/server/middleware.py +0 -0
  153. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/server/routes/__init__.py +0 -0
  154. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/server/routes/management.py +0 -0
  155. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/server/routes/stt.py +0 -0
  156. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/server/routes/tts.py +0 -0
  157. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/server/routes/webui.py +0 -0
  158. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/server/webui/app.js +0 -0
  159. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/server/webui/index.html +0 -0
  160. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/server/webui/styles.css +0 -0
  161. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/server/ws/__init__.py +0 -0
  162. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/server/ws/stt_stream.py +0 -0
  163. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/server/ws/tts_stream.py +0 -0
  164. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/telemetry/__init__.py +0 -0
  165. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/telemetry/perf.py +0 -0
  166. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/utils/__init__.py +0 -0
  167. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/utils/audio_converter.py +0 -0
  168. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/utils/audio_playback.py +0 -0
  169. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/openspeechapi/vendor_registry.yaml +0 -0
  170. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/output/output.wav +0 -0
  171. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/output.wav +0 -0
  172. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/providers.example.yaml +0 -0
  173. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/scripts/engines/cloud/install.sh +0 -0
  174. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/scripts/engines/faster-whisper/native/install.sh +0 -0
  175. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/scripts/engines/fish-speech/native/install.sh +0 -0
  176. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/scripts/engines/macos-stt/install.sh +0 -0
  177. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/scripts/engines/macos-stt/macos_stt.swift +0 -0
  178. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/scripts/engines/macos-stt/request_auth.swift +0 -0
  179. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/scripts/engines/sherpa-onnx/native/install.sh +0 -0
  180. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/scripts/engines/sherpa-onnx/native/run_streaming_server.py +0 -0
  181. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/scripts/engines/whisper/native/install.sh +0 -0
  182. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/scripts/engines/whisperlivekit/native/install.sh +0 -0
  183. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/__init__.py +0 -0
  184. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/conftest.py +0 -0
  185. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/e2e/__init__.py +0 -0
  186. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/e2e/conftest.py +0 -0
  187. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/e2e/test_fanout_e2e.py +0 -0
  188. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/e2e/test_faster_whisper_e2e.py +0 -0
  189. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/e2e/test_openai_e2e.py +0 -0
  190. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/e2e/test_webui_e2e.py +0 -0
  191. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/fixtures/hello.wav +0 -0
  192. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/integration/__init__.py +0 -0
  193. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/integration/test_fanout_integration.py +0 -0
  194. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/integration/test_in_process_integration.py +0 -0
  195. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/integration/test_server_client.py +0 -0
  196. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/unit/__init__.py +0 -0
  197. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/unit/test_aim_resolver.py +0 -0
  198. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/unit/test_audio_converter.py +0 -0
  199. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/unit/test_audio_playback.py +0 -0
  200. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/unit/test_base.py +0 -0
  201. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/unit/test_cli.py +0 -0
  202. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/unit/test_cli_engine.py +0 -0
  203. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/unit/test_client.py +0 -0
  204. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/unit/test_config.py +0 -0
  205. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/unit/test_context.py +0 -0
  206. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/unit/test_debug_observer.py +0 -0
  207. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/unit/test_dispatcher.py +0 -0
  208. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/unit/test_docker_backend_progress.py +0 -0
  209. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/unit/test_engine_registry.py +0 -0
  210. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/unit/test_enums.py +0 -0
  211. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/unit/test_executor_base.py +0 -0
  212. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/unit/test_fanout.py +0 -0
  213. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/unit/test_filters.py +0 -0
  214. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/unit/test_hot_reload.py +0 -0
  215. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/unit/test_in_process.py +0 -0
  216. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/unit/test_latency_observer.py +0 -0
  217. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/unit/test_lifecycle.py +0 -0
  218. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/unit/test_local_engine_task_store.py +0 -0
  219. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/unit/test_local_engines_manager.py +0 -0
  220. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/unit/test_logging.py +0 -0
  221. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/unit/test_metrics_observer.py +0 -0
  222. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/unit/test_models.py +0 -0
  223. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/unit/test_native_backend.py +0 -0
  224. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/unit/test_observer_base.py +0 -0
  225. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/unit/test_plugin_mechanism.py +0 -0
  226. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/unit/test_providers/__init__.py +0 -0
  227. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/unit/test_providers/test_cloud_providers.py +0 -0
  228. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/unit/test_providers/test_elevenlabs_stt.py +0 -0
  229. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/unit/test_providers/test_macos_say.py +0 -0
  230. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/unit/test_providers/test_macos_speech.py +0 -0
  231. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/unit/test_providers/test_openai_base_url.py +0 -0
  232. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/unit/test_providers/test_openai_stt.py +0 -0
  233. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/unit/test_providers/test_openai_tts.py +0 -0
  234. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/unit/test_providers/test_sherpa_onnx_stt.py +0 -0
  235. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/unit/test_providers/test_stt_stubs.py +0 -0
  236. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/unit/test_providers/test_tts_stubs.py +0 -0
  237. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/unit/test_providers/test_whisperlivekit_stt.py +0 -0
  238. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/unit/test_registry.py +0 -0
  239. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/unit/test_remote.py +0 -0
  240. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/unit/test_server/__init__.py +0 -0
  241. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/unit/test_server/test_auth.py +0 -0
  242. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/unit/test_server/test_config_api.py +0 -0
  243. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/unit/test_server/test_routes.py +0 -0
  244. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/unit/test_server/test_websocket.py +0 -0
  245. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/unit/test_subprocess.py +0 -0
  246. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/tests/unit/test_usage_observer.py +0 -0
  247. {openspeechapi-0.2.6 → openspeechapi-0.2.8}/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.8
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.8"
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,16 @@ class IflytekSTT(STTProvider):
209
347
 
210
348
  result_texts: list[str] = []
211
349
 
212
- async with websockets.connect(url) as ws:
350
+ # ``ws = await websockets.connect(...)`` returns the connection
351
+ # object directly. Re-using it as ``async with ws:`` worked on
352
+ # older websockets releases that exposed ``__aenter__`` /
353
+ # ``__aexit__`` on ``WebSocketClientProtocol``, but recent
354
+ # versions removed that re-entry alias and the only supported
355
+ # context-manager pattern is ``async with websockets.connect(...)``.
356
+ # Use try/finally with an explicit close so this stays correct
357
+ # across both old and new websockets.
358
+ ws = await self._connect_with_retry()
359
+ try:
213
360
  # Send audio in chunks with interleaved receive
214
361
  total = len(audio_bytes)
215
362
  offset = 0
@@ -228,47 +375,36 @@ class IflytekSTT(STTProvider):
228
375
  frame_data = base64.b64encode(chunk).decode("utf-8")
229
376
 
230
377
  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"
378
+ # First frame: panel-supplied common/business win;
379
+ # batch path doesn't carry wpgs (no streaming
380
+ # protocol) so include_dwa=False.
381
+ common, business = self._build_first_frame_blocks(
382
+ opts, include_dwa=False,
383
+ )
384
+ data_block = self._build_data_block(
385
+ status=0, audio_b64=frame_data, opts=opts,
386
+ )
254
387
  msg = {
255
- "common": {"app_id": self.settings.app_id},
388
+ "common": common,
256
389
  "business": business,
257
- "data": {
258
- "status": 0,
259
- "format": "audio/L16;rate=16000",
260
- "encoding": "raw",
261
- "audio": frame_data,
262
- },
390
+ "data": data_block,
263
391
  }
392
+ # Java parity: log the exact blocks we're about to
393
+ # ship to iFlytek. Debugging "wrong language /
394
+ # wrong endpoint" reports needs to see this from
395
+ # the log alone — Java's AsrServiceImpl prints the
396
+ # equivalent line at INFO.
397
+ logger.info(
398
+ "{}: ASR first frame business={}, common={}",
399
+ self.name,
400
+ json.dumps(business, ensure_ascii=False),
401
+ json.dumps(common, ensure_ascii=False),
402
+ )
264
403
  else:
265
404
  msg = {
266
- "data": {
267
- "status": status,
268
- "format": "audio/L16;rate=16000",
269
- "encoding": "raw",
270
- "audio": frame_data,
271
- }
405
+ "data": self._build_data_block(
406
+ status=status, audio_b64=frame_data, opts=opts,
407
+ )
272
408
  }
273
409
 
274
410
  await ws.send(json.dumps(msg))
@@ -301,6 +437,10 @@ class IflytekSTT(STTProvider):
301
437
 
302
438
  if data.get("status") == 2:
303
439
  break
440
+ finally:
441
+ # Idempotent on websockets ≥ 11; safe to call even if the
442
+ # server already closed the socket.
443
+ await ws.close()
304
444
 
305
445
  result = Transcription(text="".join(result_texts))
306
446
  logger.info("{}: completed in {:.0f}ms, result={} chars", self.name, (time.perf_counter() - _t0) * 1000, len(result.text))
@@ -340,7 +480,6 @@ class IflytekSTT(STTProvider):
340
480
  if self._client is None:
341
481
  raise RuntimeError("Provider not started — call start() first")
342
482
 
343
- url = self._build_auth_url()
344
483
  results: asyncio.Queue[Transcription | None] = asyncio.Queue()
345
484
  _t0 = time.perf_counter()
346
485
  _frames_sent = 0
@@ -352,7 +491,10 @@ class IflytekSTT(STTProvider):
352
491
  _sender_stop = asyncio.Event()
353
492
 
354
493
  logger.debug("{}: connecting to iFlytek WebSocket...", self.name)
355
- async with websockets.connect(url) as ws:
494
+ # See ``transcribe`` for why this is try/finally + ``await
495
+ # ws.close()`` rather than ``async with ws``.
496
+ ws = await self._connect_with_retry()
497
+ try:
356
498
  _t_connected = time.perf_counter()
357
499
  logger.info("{}: WS connected in {:.0f}ms", self.name,
358
500
  (_t_connected - _t0) * 1000)
@@ -370,43 +512,38 @@ class IflytekSTT(STTProvider):
370
512
  break
371
513
  frame_data = base64.b64encode(chunk).decode("utf-8")
372
514
  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"
515
+ # First frame: panel-supplied common/business win;
516
+ # streaming path always carries wpgs (see
517
+ # stt-streaming-spec.md realtime-correction
518
+ # protocol) so include_dwa=True.
519
+ common, business = self._build_first_frame_blocks(
520
+ opts, include_dwa=True,
521
+ )
522
+ data_block = self._build_data_block(
523
+ status=0, audio_b64=frame_data, opts=opts,
524
+ )
391
525
  msg = {
392
- "common": {"app_id": self.settings.app_id},
526
+ "common": common,
393
527
  "business": business,
394
- "data": {
395
- "status": 0,
396
- "format": "audio/L16;rate=16000",
397
- "encoding": "raw",
398
- "audio": frame_data,
399
- },
528
+ "data": data_block,
400
529
  }
530
+ # Java parity (AsrServiceImpl line 221): log
531
+ # the first-frame business + common at INFO so
532
+ # operators can verify which language/eos/dwa
533
+ # the panel actually requested without
534
+ # rebuilding the call from yaml + STTOptions.
535
+ logger.info(
536
+ "{}: ASR first frame business={}, common={}",
537
+ self.name,
538
+ json.dumps(business, ensure_ascii=False),
539
+ json.dumps(common, ensure_ascii=False),
540
+ )
401
541
  is_first = False
402
542
  else:
403
543
  msg = {
404
- "data": {
405
- "status": 1,
406
- "format": "audio/L16;rate=16000",
407
- "encoding": "raw",
408
- "audio": frame_data,
409
- }
544
+ "data": self._build_data_block(
545
+ status=1, audio_b64=frame_data, opts=opts,
546
+ )
410
547
  }
411
548
  await ws.send(json.dumps(msg))
412
549
  _frames_sent += 1
@@ -417,12 +554,9 @@ class IflytekSTT(STTProvider):
417
554
  # Send empty last frame to signal end (only if WS still open)
418
555
  if not _sender_stop.is_set():
419
556
  last_msg = {
420
- "data": {
421
- "status": 2,
422
- "format": "audio/L16;rate=16000",
423
- "encoding": "raw",
424
- "audio": "",
425
- }
557
+ "data": self._build_data_block(
558
+ status=2, audio_b64="", opts=opts,
559
+ )
426
560
  }
427
561
  await ws.send(json.dumps(last_msg))
428
562
  except websockets.exceptions.ConnectionClosed:
@@ -544,3 +678,6 @@ class IflytekSTT(STTProvider):
544
678
  await task
545
679
  except websockets.exceptions.ConnectionClosed:
546
680
  pass
681
+ finally:
682
+ # Idempotent on websockets ≥ 11.
683
+ await ws.close()
@@ -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.8"
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