vis-agent 0.3.4.dev8048__tar.gz → 0.3.4.dev8058__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 (158) hide show
  1. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/PKG-INFO +1 -1
  2. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/contracts/schema/evals.json +24 -2
  3. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/contracts/schema/gateway.json +35 -15
  4. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/pyproject.toml +1 -1
  5. vis_agent-0.3.4.dev8058/release-tag +1 -0
  6. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/src/blockether/vis/engine/__init__.py +5 -0
  7. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/src/blockether/vis/engine/_client.py +27 -0
  8. vis_agent-0.3.4.dev8058/src/blockether/vis/engine/_speech.py +289 -0
  9. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/src/blockether/vis/evals/_gateway.py +10 -0
  10. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/src/blockether/vis/evals/compare.py +100 -25
  11. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/src/blockether/vis/evals/stats.py +7 -5
  12. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/src/blockether/vis/extension.py +7 -1
  13. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_api_guides.py +1 -1
  14. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_contracts.py +8 -2
  15. vis_agent-0.3.4.dev8058/tests/test_evals_compare.py +217 -0
  16. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_evals_gateway.py +5 -0
  17. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_evals_stats.py +120 -1
  18. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_evals_tasks.py +2 -1
  19. vis_agent-0.3.4.dev8058/tests/test_speech.py +151 -0
  20. vis_agent-0.3.4.dev8048/release-tag +0 -1
  21. vis_agent-0.3.4.dev8048/tests/test_evals_compare.py +0 -53
  22. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/.gitignore +0 -0
  23. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/LICENSE +0 -0
  24. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/README.md +0 -0
  25. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/contracts/fixtures/activity-arguments.json +0 -0
  26. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/contracts/fixtures/activity-cases.json +0 -0
  27. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/contracts/fixtures/activity-copy.json +0 -0
  28. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/contracts/fixtures/activity-execution.json +0 -0
  29. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/contracts/fixtures/activity-groups.json +0 -0
  30. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/contracts/fixtures/activity-read-session.json +0 -0
  31. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/contracts/fixtures/activity-reads.json +0 -0
  32. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/contracts/fixtures/activity-results.json +0 -0
  33. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/contracts/fixtures/activity-stream.json +0 -0
  34. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/contracts/fixtures/activity-tables.json +0 -0
  35. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/contracts/fixtures/activity.json +0 -0
  36. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/contracts/fixtures/diff.json +0 -0
  37. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/contracts/fixtures/live-primitives.json +0 -0
  38. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/contracts/fixtures/session-health.json +0 -0
  39. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/contracts/fixtures/view.json +0 -0
  40. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/contracts/schema/activity.json +0 -0
  41. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/contracts/schema/agents.json +0 -0
  42. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/contracts/schema/automations.json +0 -0
  43. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/contracts/schema/common.json +0 -0
  44. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/contracts/schema/config.json +0 -0
  45. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/contracts/schema/content.json +0 -0
  46. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/contracts/schema/council.json +0 -0
  47. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/contracts/schema/diff.json +0 -0
  48. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/contracts/schema/provider.json +0 -0
  49. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/contracts/schema/rooms.json +0 -0
  50. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/contracts/schema/symbol.json +0 -0
  51. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/contracts/schema/toggle.json +0 -0
  52. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/contracts/schema/view.json +0 -0
  53. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/examples/greeter/tests/test_greeter.py +0 -0
  54. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/hatch_build.py +0 -0
  55. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/src/blockether/vis/__init__.py +0 -0
  56. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/src/blockether/vis/_contracts.py +0 -0
  57. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/src/blockether/vis/_outside.py +0 -0
  58. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/src/blockether/vis/_wire.py +0 -0
  59. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/src/blockether/vis/activity.py +0 -0
  60. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/src/blockether/vis/engine/_agent.py +0 -0
  61. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/src/blockether/vis/engine/_agents.py +0 -0
  62. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/src/blockether/vis/engine/_council.py +0 -0
  63. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/src/blockether/vis/engine/_extensions.py +0 -0
  64. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/src/blockether/vis/engine/_host.py +0 -0
  65. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/src/blockether/vis/engine/_install.py +0 -0
  66. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/src/blockether/vis/engine/_local.py +0 -0
  67. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/src/blockether/vis/engine/_models.py +0 -0
  68. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/src/blockether/vis/evals/__init__.py +0 -0
  69. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/src/blockether/vis/evals/agent.py +0 -0
  70. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/src/blockether/vis/evals/bench.py +0 -0
  71. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/src/blockether/vis/evals/check.py +0 -0
  72. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/src/blockether/vis/evals/environment.py +0 -0
  73. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/src/blockether/vis/evals/grade.py +0 -0
  74. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/src/blockether/vis/evals/harbor.py +0 -0
  75. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/src/blockether/vis/evals/judge-rubric.toml +0 -0
  76. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/src/blockether/vis/evals/judge.py +0 -0
  77. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/src/blockether/vis/evals/labels.py +0 -0
  78. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/src/blockether/vis/evals/leaderboard.py +0 -0
  79. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/src/blockether/vis/evals/lic.py +0 -0
  80. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/src/blockether/vis/evals/longmemeval.py +0 -0
  81. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/src/blockether/vis/evals/mrcr.py +0 -0
  82. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/src/blockether/vis/evals/repeats.py +0 -0
  83. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/src/blockether/vis/evals/report.py +0 -0
  84. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/src/blockether/vis/evals/reports.py +0 -0
  85. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/src/blockether/vis/evals/rounds.py +0 -0
  86. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/src/blockether/vis/evals/stages.py +0 -0
  87. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/src/blockether/vis/evals/trajectory.py +0 -0
  88. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/src/blockether/vis/extension_package.py +0 -0
  89. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/src/blockether/vis/py.typed +0 -0
  90. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/src/blockether/vis/rooms.py +0 -0
  91. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/src/blockether/vis/views.py +0 -0
  92. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/conftest.py +0 -0
  93. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/evals_atif.py +0 -0
  94. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_activity.py +0 -0
  95. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_agent.py +0 -0
  96. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_agent_extension_lifecycle.py +0 -0
  97. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_agent_extensions.py +0 -0
  98. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_agents.py +0 -0
  99. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_attachments.py +0 -0
  100. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_authoring.py +0 -0
  101. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_catalog.py +0 -0
  102. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_catalog_recipe.py +0 -0
  103. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_ci_report_recipe.py +0 -0
  104. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_client.py +0 -0
  105. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_client_extensions_engine.py +0 -0
  106. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_code_quality_recipe.py +0 -0
  107. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_council.py +0 -0
  108. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_declarations.py +0 -0
  109. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_engine.py +0 -0
  110. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_engine_docstrings.py +0 -0
  111. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_evals_agent.py +0 -0
  112. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_evals_bench.py +0 -0
  113. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_evals_check.py +0 -0
  114. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_evals_environment.py +0 -0
  115. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_evals_grade.py +0 -0
  116. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_evals_guards.py +0 -0
  117. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_evals_harbor.py +0 -0
  118. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_evals_judge.py +0 -0
  119. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_evals_labels.py +0 -0
  120. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_evals_lic.py +0 -0
  121. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_evals_longmemeval.py +0 -0
  122. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_evals_mrcr.py +0 -0
  123. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_evals_repeats.py +0 -0
  124. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_evals_report.py +0 -0
  125. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_evals_rounds.py +0 -0
  126. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_evals_stages.py +0 -0
  127. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_evals_trajectory.py +0 -0
  128. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_extension_package.py +0 -0
  129. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_extension_reference.py +0 -0
  130. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_gateway_performance.py +0 -0
  131. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_goals.py +0 -0
  132. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_install.py +0 -0
  133. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_live_primitives.py +0 -0
  134. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_live_wait.py +0 -0
  135. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_local.py +0 -0
  136. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_method_typing.py +0 -0
  137. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_models.py +0 -0
  138. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_monitor_recipe.py +0 -0
  139. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_namespace.py +0 -0
  140. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_outside.py +0 -0
  141. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_providers.py +0 -0
  142. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_read_session.py +0 -0
  143. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_record_arguments.py +0 -0
  144. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_registration.py +0 -0
  145. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_repair_hooks.py +0 -0
  146. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_result_sequence.py +0 -0
  147. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_rooms.py +0 -0
  148. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_scoped_settings.py +0 -0
  149. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_sdk_bootstrap.py +0 -0
  150. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_sdk_guide.py +0 -0
  151. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_sdk_typing.py +0 -0
  152. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_shortcuts_live.py +0 -0
  153. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_structured_live.py +0 -0
  154. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_symbol_contract.py +0 -0
  155. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_testing.py +0 -0
  156. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_text_list_arguments.py +0 -0
  157. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_tools.py +0 -0
  158. {vis_agent-0.3.4.dev8048 → vis_agent-0.3.4.dev8058}/tests/test_views.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: vis-agent
3
- Version: 0.3.4.dev8048
3
+ Version: 0.3.4.dev8058
4
4
  Summary: The Vis Python SDK — author extensions and control local or remote engines.
5
5
  Project-URL: Homepage, https://vis.blockether.com
6
6
  Project-URL: Documentation, https://vis.blockether.com
@@ -149,6 +149,12 @@
149
149
  },
150
150
  "candidate_name": {
151
151
  "type": "string"
152
+ },
153
+ "margin_points": {
154
+ "description": "The smallest change in points that matters. It splits a result without a clear change into equivalent, not worse, not better or inconclusive.",
155
+ "type": "number",
156
+ "exclusiveMinimum": 0,
157
+ "maximum": 100
152
158
  }
153
159
  },
154
160
  "required": [
@@ -649,7 +655,16 @@
649
655
  "maxItems": 2
650
656
  },
651
657
  "verdict": {
652
- "type": "string"
658
+ "type": "string",
659
+ "enum": [
660
+ "better",
661
+ "worse",
662
+ "no measurable difference",
663
+ "equivalent",
664
+ "not worse",
665
+ "not better",
666
+ "inconclusive"
667
+ ]
653
668
  },
654
669
  "detectable_change_points": {
655
670
  "type": "number"
@@ -683,6 +698,12 @@
683
698
  "items": {
684
699
  "type": "string"
685
700
  }
701
+ },
702
+ "margin_points": {
703
+ "type": [
704
+ "number",
705
+ "null"
706
+ ]
686
707
  }
687
708
  },
688
709
  "required": [
@@ -698,7 +719,8 @@
698
719
  "worse",
699
720
  "better",
700
721
  "unpaired",
701
- "caveats"
722
+ "caveats",
723
+ "margin_points"
702
724
  ],
703
725
  "additionalProperties": false
704
726
  },
@@ -1437,7 +1437,8 @@
1437
1437
  "operations": {
1438
1438
  "post": {
1439
1439
  "request": "json",
1440
- "response": "json"
1440
+ "response": "json",
1441
+ "extension": "background"
1441
1442
  }
1442
1443
  },
1443
1444
  "path": "/v1/speech"
@@ -1447,11 +1448,13 @@
1447
1448
  "operations": {
1448
1449
  "delete": {
1449
1450
  "request": "none",
1450
- "response": "json"
1451
+ "response": "json",
1452
+ "extension": "background"
1451
1453
  },
1452
1454
  "get": {
1453
1455
  "request": "none",
1454
- "response": "json"
1456
+ "response": "json",
1457
+ "extension": "background"
1455
1458
  }
1456
1459
  },
1457
1460
  "path": "/v1/speech/jobs/:job-id"
@@ -1461,7 +1464,8 @@
1461
1464
  "operations": {
1462
1465
  "get": {
1463
1466
  "request": "none",
1464
- "response": "binary"
1467
+ "response": "binary",
1468
+ "extension": "background"
1465
1469
  }
1466
1470
  },
1467
1471
  "path": "/v1/speech/jobs/:job-id/audio"
@@ -1481,11 +1485,13 @@
1481
1485
  "operations": {
1482
1486
  "get": {
1483
1487
  "request": "none",
1484
- "response": "json"
1488
+ "response": "json",
1489
+ "extension": "background"
1485
1490
  },
1486
1491
  "post": {
1487
1492
  "request": "none",
1488
- "response": "json"
1493
+ "response": "json",
1494
+ "extension": "callback"
1489
1495
  }
1490
1496
  },
1491
1497
  "path": "/v1/speech/model"
@@ -1495,11 +1501,13 @@
1495
1501
  "operations": {
1496
1502
  "get": {
1497
1503
  "request": "none",
1498
- "response": "json"
1504
+ "response": "json",
1505
+ "extension": "background"
1499
1506
  },
1500
1507
  "post": {
1501
1508
  "request": "binary",
1502
- "response": "json"
1509
+ "response": "json",
1510
+ "extension": "callback"
1503
1511
  }
1504
1512
  },
1505
1513
  "path": "/v1/speech/voices"
@@ -1509,7 +1517,13 @@
1509
1517
  "operations": {
1510
1518
  "delete": {
1511
1519
  "request": "none",
1512
- "response": "json"
1520
+ "response": "json",
1521
+ "extension": "callback"
1522
+ },
1523
+ "patch": {
1524
+ "request": "json",
1525
+ "response": "json",
1526
+ "extension": "callback"
1513
1527
  }
1514
1528
  },
1515
1529
  "path": "/v1/speech/voices/:voice-id"
@@ -1519,7 +1533,8 @@
1519
1533
  "operations": {
1520
1534
  "get": {
1521
1535
  "request": "none",
1522
- "response": "binary"
1536
+ "response": "binary",
1537
+ "extension": "background"
1523
1538
  }
1524
1539
  },
1525
1540
  "path": "/v1/speech/voices/:voice-id/sample"
@@ -1601,7 +1616,8 @@
1601
1616
  "operations": {
1602
1617
  "post": {
1603
1618
  "request": "binary",
1604
- "response": "json"
1619
+ "response": "json",
1620
+ "extension": "background"
1605
1621
  }
1606
1622
  },
1607
1623
  "path": "/v1/voice"
@@ -1611,11 +1627,13 @@
1611
1627
  "operations": {
1612
1628
  "delete": {
1613
1629
  "request": "none",
1614
- "response": "json"
1630
+ "response": "json",
1631
+ "extension": "background"
1615
1632
  },
1616
1633
  "get": {
1617
1634
  "request": "none",
1618
- "response": "json"
1635
+ "response": "json",
1636
+ "extension": "background"
1619
1637
  }
1620
1638
  },
1621
1639
  "path": "/v1/voice/jobs/:job-id"
@@ -1635,11 +1653,13 @@
1635
1653
  "operations": {
1636
1654
  "get": {
1637
1655
  "request": "none",
1638
- "response": "json"
1656
+ "response": "json",
1657
+ "extension": "background"
1639
1658
  },
1640
1659
  "post": {
1641
1660
  "request": "none",
1642
- "response": "json"
1661
+ "response": "json",
1662
+ "extension": "callback"
1643
1663
  }
1644
1664
  },
1645
1665
  "path": "/v1/voice/model"
@@ -8,7 +8,7 @@ build-backend = "hatchling.build"
8
8
 
9
9
  [project]
10
10
  name = "vis-agent"
11
- version = "0.3.4.dev8048"
11
+ version = "0.3.4.dev8058"
12
12
  description = "The Vis Python SDK — author extensions and control local or remote engines."
13
13
  readme = "README.md"
14
14
  requires-python = ">=3.12"
@@ -0,0 +1 @@
1
+ beta-e21d3544abdc993020fd27956ece8df3a4736c1e
@@ -36,6 +36,7 @@ from ._council import (
36
36
  from ._install import InstallError, ensure_installed, install
37
37
  from ._local import LocalEngine
38
38
  from ._models import Model, SessionModel
39
+ from ._speech import Speech, SpeechAudio, Transcript, Voice
39
40
 
40
41
  __all__ = [
41
42
  "Agent",
@@ -65,10 +66,14 @@ __all__ = [
65
66
  "Response",
66
67
  "Session",
67
68
  "SessionModel",
69
+ "Speech",
70
+ "SpeechAudio",
68
71
  "StructuredOutputError",
72
+ "Transcript",
69
73
  "TransportError",
70
74
  "Turn",
71
75
  "VisTimeout",
76
+ "Voice",
72
77
  "ensure_installed",
73
78
  "final_answer",
74
79
  "install",
@@ -29,6 +29,7 @@ from blockether.vis.views import InputView, LiveView, ViewEvent
29
29
 
30
30
  if TYPE_CHECKING:
31
31
  from ._agent import ResponseModel
32
+ from ._speech import Speech
32
33
 
33
34
  JSONValue: TypeAlias = (
34
35
  str | int | float | bool | None | list["JSONValue"] | dict[str, "JSONValue"]
@@ -401,6 +402,13 @@ class ExecutionLayer(ABC):
401
402
  except OSError:
402
403
  raise TransportError("gateway response interrupted") from None
403
404
 
405
+ @property
406
+ def speech(self) -> Speech:
407
+ """Return the `Speech` API: voices, synthesis and transcription."""
408
+ from ._speech import Speech
409
+
410
+ return Speech(self)
411
+
404
412
  def session(self, sid: str) -> Session:
405
413
  """Return a lightweight handle for an existing session ID.
406
414
 
@@ -2857,6 +2865,25 @@ class ExecutionLayer(ABC):
2857
2865
  )
2858
2866
  return response.json()
2859
2867
 
2868
+ def patch_speech_voice(
2869
+ self,
2870
+ voice_id: str,
2871
+ *,
2872
+ query: Query | None = None,
2873
+ timeout: float | None = None,
2874
+ body: JSONValue = None,
2875
+ ) -> JSONValue:
2876
+ """PATCH /v1/speech/voices/:voice-id — json response."""
2877
+ response = self._request(
2878
+ "PATCH",
2879
+ "/v1/speech/voices/:voice-id",
2880
+ path={"voice-id": voice_id},
2881
+ query=query,
2882
+ timeout=timeout,
2883
+ body=body,
2884
+ )
2885
+ return response.json()
2886
+
2860
2887
  def get_speech_voice_sample(
2861
2888
  self, voice_id: str, *, query: Query | None = None, timeout: float | None = None
2862
2889
  ) -> Response:
@@ -0,0 +1,289 @@
1
+ """Speech API: voices, synthesis and transcription through the gateway engines.
2
+
3
+ Use `ExecutionLayer.speech` from the SDK, or `vis.speech` inside an extension.
4
+ Both send the same canonical gateway requests.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import time
10
+ from dataclasses import dataclass
11
+ from pathlib import Path
12
+ from typing import TYPE_CHECKING, Any
13
+
14
+ from ._client import GatewayError, ProtocolError, VisTimeout
15
+
16
+ if TYPE_CHECKING:
17
+ from ._client import ExecutionLayer
18
+
19
+ CLONING_ENGINE = "pocket-tts-local"
20
+ """The engine that learns a voice from a recording."""
21
+
22
+ _UNSET: Any = object()
23
+ _POLL_SECONDS = 0.25
24
+
25
+
26
+ @dataclass(frozen=True, slots=True)
27
+ class Voice:
28
+ """One voice that a speaking engine can use."""
29
+
30
+ id: str
31
+ label: str
32
+ engine: str
33
+ language: str | None = None
34
+ is_imported: bool = False
35
+ clip_text: str | None = None
36
+ license: str | None = None
37
+
38
+
39
+ @dataclass(frozen=True, slots=True)
40
+ class SpeechAudio:
41
+ """Audio that an engine spoke, as the bytes of one file."""
42
+
43
+ content: bytes
44
+ media_type: str
45
+ engine: str | None
46
+ voice: str | None
47
+ seed: int | None
48
+
49
+ def save(self, path: str | Path) -> Path:
50
+ """Write the audio to `path`, create missing parents and return the path."""
51
+ target = Path(path)
52
+ target.parent.mkdir(parents=True, exist_ok=True)
53
+ target.write_bytes(self.content)
54
+ return target
55
+
56
+
57
+ @dataclass(frozen=True, slots=True)
58
+ class Transcript:
59
+ """The text that a transcription engine heard in a recording."""
60
+
61
+ text: str
62
+ engine: str | None
63
+ segments: tuple[dict[str, Any], ...] = ()
64
+
65
+
66
+ def _voice(entry: Any, engine: str) -> Voice:
67
+ if not isinstance(entry, dict) or not isinstance(entry.get("id"), str):
68
+ raise ProtocolError("invalid voice entry")
69
+ return Voice(
70
+ id=entry["id"],
71
+ label=str(entry.get("label") or entry["id"]),
72
+ engine=engine,
73
+ language=entry.get("language"),
74
+ is_imported=bool(entry.get("is_imported")),
75
+ clip_text=entry.get("clip_text"),
76
+ license=entry.get("license"),
77
+ )
78
+
79
+
80
+ def _object(reply: Any, what: str) -> dict[str, Any]:
81
+ """The JSON object of a gateway answer, or ProtocolError for another shape."""
82
+ if not isinstance(reply, dict):
83
+ raise ProtocolError(f"invalid {what} answer")
84
+ return reply
85
+
86
+
87
+ def _audio_bytes(audio: str | Path | bytes) -> bytes:
88
+ if isinstance(audio, bytes):
89
+ return audio
90
+ return Path(audio).expanduser().read_bytes()
91
+
92
+
93
+ def _media_type(headers: dict[str, str]) -> str:
94
+ for key, value in headers.items():
95
+ if key.lower() == "content-type" and value:
96
+ return value.split(";")[0].strip()
97
+ return "audio/wav"
98
+
99
+
100
+ def _engine_query(engine: str | None, **extra: str | None) -> dict[str, str] | None:
101
+ query = {k: v for k, v in {"engine": engine, **extra}.items() if v is not None}
102
+ return query or None
103
+
104
+
105
+ class Speech:
106
+ """Manage voices, speak text and transcribe audio with the gateway engines.
107
+
108
+ Without `engine`, a call uses the engine that the gateway selected. Voice changes
109
+ use the cloning engine `pocket-tts-local` by default. Long work runs as a gateway
110
+ job; these methods wait for it and return the result.
111
+ """
112
+
113
+ def __init__(self, layer: ExecutionLayer):
114
+ self._layer = layer
115
+
116
+ def voices(self, engine: str | None = None) -> list[Voice]:
117
+ """List the voices of `engine`, imported voices included."""
118
+ reply = _object(
119
+ self._layer.get_speech_voices(query=_engine_query(engine)), "voices"
120
+ )
121
+ name = str((reply.get("engine") or {}).get("id") or engine or "")
122
+ return [_voice(entry, name) for entry in reply.get("voices") or []]
123
+
124
+ def add_voice(
125
+ self,
126
+ recording: str | Path | bytes,
127
+ name: str,
128
+ *,
129
+ language: str | None = None,
130
+ transcript: str | None = None,
131
+ engine: str = CLONING_ENGINE,
132
+ ) -> Voice:
133
+ """Import a recording of one speaker as a voice and return it.
134
+
135
+ Use 3 to 30 seconds of clean speech. WAV works everywhere; the gateway converts
136
+ other containers when it can. `transcript` is what the clip says and improves
137
+ the clone. A voice with the same id is replaced, so this also replaces the audio
138
+ of an existing voice. Raises `GatewayError` 400 for an unusable recording and
139
+ 409 for an engine that cannot learn voices.
140
+ """
141
+ if not name or not name.strip():
142
+ raise ValueError("name must not be blank")
143
+ reply = _object(
144
+ self._layer.post_speech_voices(
145
+ content=_audio_bytes(recording),
146
+ query=_engine_query(engine, name=name, lang=language, text=transcript),
147
+ ),
148
+ "voice",
149
+ )
150
+ return _voice(reply.get("voice"), engine)
151
+
152
+ def update_voice(
153
+ self,
154
+ voice_id: str,
155
+ *,
156
+ name: str | None = _UNSET,
157
+ language: str | None = _UNSET,
158
+ transcript: str | None = _UNSET,
159
+ engine: str = CLONING_ENGINE,
160
+ ) -> Voice:
161
+ """Change the label, language or clip transcript of an imported voice.
162
+
163
+ An omitted argument keeps its value; `None` or "" removes it. The id stays the
164
+ same. Raises `GatewayError` 404 when the engine has no such imported voice.
165
+ """
166
+ body: dict[str, Any] = {
167
+ key: value
168
+ for key, value in (
169
+ ("name", name),
170
+ ("language", language),
171
+ ("text", transcript),
172
+ )
173
+ if value is not _UNSET
174
+ }
175
+ if not body:
176
+ raise ValueError("give name, language or transcript")
177
+ reply = _object(
178
+ self._layer.patch_speech_voice(
179
+ voice_id, body=body, query=_engine_query(engine)
180
+ ),
181
+ "voice",
182
+ )
183
+ return _voice(reply.get("voice"), engine)
184
+
185
+ def remove_voice(self, voice_id: str, *, engine: str = CLONING_ENGINE) -> bool:
186
+ """Delete an imported voice. Return False when the engine has no such voice."""
187
+ try:
188
+ self._layer.delete_speech_voice(voice_id, query=_engine_query(engine))
189
+ except GatewayError as error:
190
+ if error.status == 404:
191
+ return False
192
+ raise
193
+ return True
194
+
195
+ def voice_sample(self, voice_id: str, *, engine: str | None = None) -> bytes:
196
+ """Return a short audio sample of a voice. Raises `GatewayError` 404 without one."""
197
+ return self._layer.get_speech_voice_sample(
198
+ voice_id, query=_engine_query(engine)
199
+ ).content
200
+
201
+ def model(self, engine: str | None = None, *, prepare: bool = False) -> dict:
202
+ """Return the readiness of a speaking engine; `prepare=True` starts its download."""
203
+ query = _engine_query(engine)
204
+ if prepare:
205
+ return _object(self._layer.post_speech_model(query=query), "model")
206
+ return _object(self._layer.get_speech_model(query=query), "model")
207
+
208
+ def speak(
209
+ self,
210
+ text: str,
211
+ *,
212
+ voice: str | None = None,
213
+ engine: str | None = None,
214
+ seed: int | None = None,
215
+ timeout: float = 600,
216
+ ) -> SpeechAudio:
217
+ """Speak `text` and return the audio, usually 24 kHz mono WAV.
218
+
219
+ `seed` selects another take of the same text on engines that sample noise,
220
+ such as `vibevoice-local`. The gateway speaks at most 20000 characters at once.
221
+ Raises `GatewayError` 425 while the engine model is not ready and `VisTimeout`
222
+ when the job takes longer than `timeout` seconds.
223
+ """
224
+ if not text or not text.strip():
225
+ raise ValueError("text must not be blank")
226
+ if seed is not None and not (type(seed) is int and 0 <= seed <= 2147483647):
227
+ raise ValueError("seed must be an integer from 0 to 2147483647")
228
+ body: dict[str, Any] = {"text": text}
229
+ if voice is not None:
230
+ body["voice"] = voice
231
+ if seed is not None:
232
+ body["seed"] = seed
233
+ response = self._layer._request(
234
+ "POST", "/v1/speech", body=body, query=_engine_query(engine)
235
+ )
236
+ if response.status == 200:
237
+ return SpeechAudio(
238
+ response.content, _media_type(response.headers), engine, voice, seed
239
+ )
240
+ job = self._wait("speech", response.json(), timeout)
241
+ audio = self._layer.get_speech_job_audio(job["id"])
242
+ return SpeechAudio(
243
+ audio.content,
244
+ _media_type(audio.headers),
245
+ job.get("engine") or engine,
246
+ voice,
247
+ seed,
248
+ )
249
+
250
+ def transcribe(
251
+ self,
252
+ audio: str | Path | bytes,
253
+ *,
254
+ engine: str | None = None,
255
+ timeout: float = 600,
256
+ ) -> Transcript:
257
+ """Transcribe a 16-bit PCM WAV recording and return its text.
258
+
259
+ Engines include `parakeet-local` and `vibevoice-asr-local`. Raises `GatewayError`
260
+ 400 for audio that is not WAV and `VisTimeout` after `timeout` seconds.
261
+ """
262
+ job = self._layer.post_voice(
263
+ content=_audio_bytes(audio), query=_engine_query(engine)
264
+ )
265
+ job = self._wait("voice", job, timeout)
266
+ segments = job.get("segments") or ()
267
+ return Transcript(
268
+ str(job.get("text") or ""),
269
+ job.get("engine") or engine,
270
+ tuple(s for s in segments if isinstance(s, dict)),
271
+ )
272
+
273
+ def _wait(self, direction: str, job: Any, timeout: float) -> dict:
274
+ if not isinstance(job, dict) or not isinstance(job.get("id"), str):
275
+ raise ProtocolError(f"invalid {direction} job")
276
+ get = (
277
+ self._layer.get_speech_job
278
+ if direction == "speech"
279
+ else self._layer.get_voice_job
280
+ )
281
+ deadline = time.monotonic() + timeout
282
+ while not job.get("is_done"):
283
+ if time.monotonic() > deadline:
284
+ raise VisTimeout(f"{direction} job {job['id']} is still running")
285
+ time.sleep(_POLL_SECONDS)
286
+ job = get(job["id"])
287
+ if job.get("phase") == "failed":
288
+ raise RuntimeError(str(job.get("error") or f"{direction} job failed"))
289
+ return job
@@ -68,6 +68,15 @@ def _required_text(body: Mapping, key: str) -> str:
68
68
  return value
69
69
 
70
70
 
71
+ def _number(body: Mapping, key: str) -> float | None:
72
+ value = body.get(key)
73
+ if value is None:
74
+ return None
75
+ if isinstance(value, bool) or not isinstance(value, (int, float)):
76
+ raise ValueError(f"`{key}` must be a number.")
77
+ return float(value)
78
+
79
+
71
80
  def _cases(body: Mapping, key: str) -> dict[tuple[str, ...], tuple[int, int]]:
72
81
  rows = body.get(key)
73
82
  if not isinstance(rows, list):
@@ -114,6 +123,7 @@ def _compare(body: Mapping):
114
123
  _cases(body, "candidate"),
115
124
  baseline_name=_word(body, "baseline_name", "baseline"),
116
125
  candidate_name=_word(body, "candidate_name", "candidate"),
126
+ margin_points=_number(body, "margin_points"),
117
127
  )
118
128
 
119
129