sunholo 0.145.0__tar.gz → 0.145.1__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 (206) hide show
  1. {sunholo-0.145.0/src/sunholo.egg-info → sunholo-0.145.1}/PKG-INFO +2 -2
  2. {sunholo-0.145.0 → sunholo-0.145.1}/pyproject.toml +3 -3
  3. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/invoke/__init__.py +1 -1
  4. {sunholo-0.145.0 → sunholo-0.145.1/src/sunholo.egg-info}/PKG-INFO +2 -2
  5. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo.egg-info/SOURCES.txt +2 -3
  6. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo.egg-info/requires.txt +1 -0
  7. sunholo-0.145.1/tests/test_async_task_runner.py +355 -0
  8. sunholo-0.145.0/tests/test_vac_routes_fastapi.py +0 -226
  9. sunholo-0.145.0/tests/test_vac_routes_mcp.py +0 -398
  10. {sunholo-0.145.0 → sunholo-0.145.1}/LICENSE.txt +0 -0
  11. {sunholo-0.145.0 → sunholo-0.145.1}/MANIFEST.in +0 -0
  12. {sunholo-0.145.0 → sunholo-0.145.1}/README.md +0 -0
  13. {sunholo-0.145.0 → sunholo-0.145.1}/setup.cfg +0 -0
  14. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/__init__.py +0 -0
  15. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/a2a/__init__.py +0 -0
  16. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/a2a/agent_card.py +0 -0
  17. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/a2a/task_manager.py +0 -0
  18. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/a2a/vac_a2a_agent.py +0 -0
  19. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/agents/__init__.py +0 -0
  20. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/agents/chat_history.py +0 -0
  21. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/agents/dispatch_to_qa.py +0 -0
  22. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/agents/fastapi/__init__.py +0 -0
  23. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/agents/fastapi/base.py +0 -0
  24. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/agents/fastapi/qna_routes.py +0 -0
  25. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/agents/fastapi/vac_routes.py +0 -0
  26. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/agents/flask/__init__.py +0 -0
  27. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/agents/flask/base.py +0 -0
  28. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/agents/flask/vac_routes.py +0 -0
  29. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/agents/langserve.py +0 -0
  30. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/agents/pubsub.py +0 -0
  31. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/agents/route.py +0 -0
  32. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/agents/special_commands.py +0 -0
  33. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/agents/swagger.py +0 -0
  34. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/archive/__init__.py +0 -0
  35. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/archive/archive.py +0 -0
  36. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/auth/__init__.py +0 -0
  37. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/auth/gcloud.py +0 -0
  38. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/auth/refresh.py +0 -0
  39. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/auth/run.py +0 -0
  40. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/azure/__init__.py +0 -0
  41. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/azure/auth.py +0 -0
  42. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/azure/blobs.py +0 -0
  43. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/azure/event_grid.py +0 -0
  44. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/bots/__init__.py +0 -0
  45. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/bots/discord.py +0 -0
  46. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/bots/github_webhook.py +0 -0
  47. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/bots/webapp.py +0 -0
  48. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/chunker/__init__.py +0 -0
  49. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/chunker/azure.py +0 -0
  50. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/chunker/doc_handling.py +0 -0
  51. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/chunker/encode_metadata.py +0 -0
  52. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/chunker/images.py +0 -0
  53. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/chunker/loaders.py +0 -0
  54. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/chunker/message_data.py +0 -0
  55. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/chunker/pdfs.py +0 -0
  56. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/chunker/process_chunker_data.py +0 -0
  57. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/chunker/publish.py +0 -0
  58. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/chunker/pubsub.py +0 -0
  59. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/chunker/splitter.py +0 -0
  60. {sunholo-0.145.0/src/sunholo/templates/system_services → sunholo-0.145.1/src/sunholo/cli}/__init__.py +0 -0
  61. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/cli/chat_vac.py +0 -0
  62. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/cli/cli.py +0 -0
  63. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/cli/cli_init.py +0 -0
  64. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/cli/configs.py +0 -0
  65. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/cli/deploy.py +0 -0
  66. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/cli/embedder.py +0 -0
  67. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/cli/merge_texts.py +0 -0
  68. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/cli/run_proxy.py +0 -0
  69. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/cli/sun_rich.py +0 -0
  70. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/cli/swagger.py +0 -0
  71. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/cli/vertex.py +0 -0
  72. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/components/__init__.py +0 -0
  73. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/components/llm.py +0 -0
  74. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/components/retriever.py +0 -0
  75. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/components/vectorstore.py +0 -0
  76. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/custom_logging.py +0 -0
  77. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/database/__init__.py +0 -0
  78. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/database/alloydb.py +0 -0
  79. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/database/alloydb_client.py +0 -0
  80. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/database/database.py +0 -0
  81. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/database/lancedb.py +0 -0
  82. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/database/sql/sb/create_function.sql +0 -0
  83. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/database/sql/sb/create_function_time.sql +0 -0
  84. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/database/sql/sb/create_table.sql +0 -0
  85. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/database/sql/sb/delete_source_row.sql +0 -0
  86. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/database/sql/sb/return_sources.sql +0 -0
  87. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/database/sql/sb/setup.sql +0 -0
  88. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/database/static_dbs.py +0 -0
  89. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/database/uuid.py +0 -0
  90. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/discovery_engine/__init__.py +0 -0
  91. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/discovery_engine/chunker_handler.py +0 -0
  92. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/discovery_engine/cli.py +0 -0
  93. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/discovery_engine/create_new.py +0 -0
  94. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/discovery_engine/discovery_engine_client.py +0 -0
  95. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/discovery_engine/get_ai_search_chunks.py +0 -0
  96. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/embedder/__init__.py +0 -0
  97. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/embedder/embed_chunk.py +0 -0
  98. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/embedder/embed_metadata.py +0 -0
  99. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/excel/__init__.py +0 -0
  100. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/excel/plugin.py +0 -0
  101. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/gcs/__init__.py +0 -0
  102. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/gcs/add_file.py +0 -0
  103. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/gcs/download_folder.py +0 -0
  104. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/gcs/download_gcs_text.py +0 -0
  105. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/gcs/download_url.py +0 -0
  106. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/gcs/extract_and_sign.py +0 -0
  107. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/gcs/metadata.py +0 -0
  108. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/genai/__init__.py +0 -0
  109. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/genai/file_handling.py +0 -0
  110. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/genai/genaiv2.py +0 -0
  111. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/genai/images.py +0 -0
  112. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/genai/init.py +0 -0
  113. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/genai/process_funcs_cls.py +0 -0
  114. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/genai/safety.py +0 -0
  115. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/invoke/async_class.py +0 -0
  116. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/invoke/async_task_runner.py +0 -0
  117. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/invoke/direct_vac_func.py +0 -0
  118. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/invoke/invoke_vac_utils.py +0 -0
  119. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/langchain_types.py +0 -0
  120. {sunholo-0.145.0/src/sunholo/templates/project → sunholo-0.145.1/src/sunholo/langfuse}/__init__.py +0 -0
  121. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/langfuse/callback.py +0 -0
  122. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/langfuse/evals.py +0 -0
  123. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/langfuse/prompts.py +0 -0
  124. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/llamaindex/__init__.py +0 -0
  125. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/llamaindex/get_files.py +0 -0
  126. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/llamaindex/import_files.py +0 -0
  127. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/llamaindex/llamaindex_class.py +0 -0
  128. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/llamaindex/user_history.py +0 -0
  129. {sunholo-0.145.0/src/sunholo/templates/agent/tools → sunholo-0.145.1/src/sunholo/lookup}/__init__.py +0 -0
  130. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/lookup/model_lookup.yaml +0 -0
  131. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/mcp/__init__.py +0 -0
  132. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/mcp/cli.py +0 -0
  133. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/mcp/cli_fastmcp.py +0 -0
  134. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/mcp/extensible_mcp_server.py +0 -0
  135. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/mcp/mcp_manager.py +0 -0
  136. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/mcp/sse_utils.py +0 -0
  137. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/mcp/stdio_http_bridge.py +0 -0
  138. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/mcp/vac_mcp_server.py +0 -0
  139. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/mcp/vac_mcp_server_fastmcp.py +0 -0
  140. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/mcp/vac_tools.py +0 -0
  141. {sunholo-0.145.0/src/sunholo/templates/agent → sunholo-0.145.1/src/sunholo/ollama}/__init__.py +0 -0
  142. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/ollama/ollama_images.py +0 -0
  143. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/pubsub/__init__.py +0 -0
  144. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/pubsub/process_pubsub.py +0 -0
  145. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/pubsub/pubsub_manager.py +0 -0
  146. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/qna/__init__.py +0 -0
  147. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/qna/parsers.py +0 -0
  148. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/qna/retry.py +0 -0
  149. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/senses/__init__.py +0 -0
  150. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/senses/stream_voice.py +0 -0
  151. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/streaming/__init__.py +0 -0
  152. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/streaming/content_buffer.py +0 -0
  153. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/streaming/langserve.py +0 -0
  154. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/streaming/stream_lookup.py +0 -0
  155. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/streaming/streaming.py +0 -0
  156. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/summarise/__init__.py +0 -0
  157. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/summarise/summarise.py +0 -0
  158. {sunholo-0.145.0/src/sunholo/ollama → sunholo-0.145.1/src/sunholo/templates/agent}/__init__.py +0 -0
  159. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/templates/agent/agent_service.py +0 -0
  160. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/templates/agent/app.py +0 -0
  161. {sunholo-0.145.0/src/sunholo/templates/project → sunholo-0.145.1/src/sunholo/templates/agent}/my_log.py +0 -0
  162. {sunholo-0.145.0/src/sunholo/lookup → sunholo-0.145.1/src/sunholo/templates/agent/tools}/__init__.py +0 -0
  163. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/templates/agent/tools/your_agent.py +0 -0
  164. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/templates/agent/vac_service.py +0 -0
  165. {sunholo-0.145.0/src/sunholo/langfuse → sunholo-0.145.1/src/sunholo/templates/project}/__init__.py +0 -0
  166. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/templates/project/app.py +0 -0
  167. {sunholo-0.145.0/src/sunholo/templates/agent → sunholo-0.145.1/src/sunholo/templates/project}/my_log.py +0 -0
  168. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/templates/project/vac_service.py +0 -0
  169. {sunholo-0.145.0/src/sunholo/cli → sunholo-0.145.1/src/sunholo/templates/system_services}/__init__.py +0 -0
  170. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/templates/system_services/app.py +0 -0
  171. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/templates/system_services/my_log.py +0 -0
  172. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/terraform/__init__.py +0 -0
  173. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/terraform/tfvars_editor.py +0 -0
  174. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/tools/__init__.py +0 -0
  175. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/tools/web_browser.py +0 -0
  176. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/utils/__init__.py +0 -0
  177. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/utils/api_key.py +0 -0
  178. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/utils/big_context.py +0 -0
  179. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/utils/config.py +0 -0
  180. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/utils/config_class.py +0 -0
  181. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/utils/config_schema.py +0 -0
  182. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/utils/gcp.py +0 -0
  183. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/utils/gcp_project.py +0 -0
  184. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/utils/mime.py +0 -0
  185. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/utils/parsers.py +0 -0
  186. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/utils/proto_convert.py +0 -0
  187. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/utils/timedelta.py +0 -0
  188. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/utils/user_ids.py +0 -0
  189. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/utils/version.py +0 -0
  190. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/vertex/__init__.py +0 -0
  191. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/vertex/extensions_call.py +0 -0
  192. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/vertex/extensions_class.py +0 -0
  193. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/vertex/genai_functions.py +0 -0
  194. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/vertex/init.py +0 -0
  195. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/vertex/memory_tools.py +0 -0
  196. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/vertex/safety.py +0 -0
  197. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo/vertex/type_dict_to_json.py +0 -0
  198. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo.egg-info/dependency_links.txt +0 -0
  199. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo.egg-info/entry_points.txt +0 -0
  200. {sunholo-0.145.0 → sunholo-0.145.1}/src/sunholo.egg-info/top_level.txt +0 -0
  201. {sunholo-0.145.0 → sunholo-0.145.1}/tests/test_async.py +0 -0
  202. {sunholo-0.145.0 → sunholo-0.145.1}/tests/test_async_genai2.py +0 -0
  203. {sunholo-0.145.0 → sunholo-0.145.1}/tests/test_chat_history.py +0 -0
  204. {sunholo-0.145.0 → sunholo-0.145.1}/tests/test_config.py +0 -0
  205. {sunholo-0.145.0 → sunholo-0.145.1}/tests/test_genai2.py +0 -0
  206. {sunholo-0.145.0 → sunholo-0.145.1}/tests/test_unstructured.py +0 -0
@@ -1,11 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sunholo
3
- Version: 0.145.0
3
+ Version: 0.145.1
4
4
  Summary: AI DevOps - a package to help deploy GenAI to the Cloud.
5
5
  Author-email: Holosun ApS <multivac@sunholo.com>
6
6
  License: Apache License, Version 2.0
7
7
  Project-URL: Homepage, https://github.com/sunholo-data/sunholo-py
8
- Project-URL: Download, https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.118.0.tar.gz
9
8
  Keywords: llms,devops,google_cloud_platform
10
9
  Classifier: Development Status :: 3 - Alpha
11
10
  Classifier: Intended Audience :: Developers
@@ -107,6 +106,7 @@ Requires-Dist: azure-storage-blob; extra == "azure"
107
106
  Provides-Extra: cli
108
107
  Requires-Dist: jsonschema>=4.21.1; extra == "cli"
109
108
  Requires-Dist: rich; extra == "cli"
109
+ Requires-Dist: fastmcp; extra == "cli"
110
110
  Provides-Extra: database
111
111
  Requires-Dist: asyncpg; extra == "database"
112
112
  Requires-Dist: supabase; extra == "database"
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "sunholo"
7
- version = "0.145.0"
7
+ version = "0.145.1"
8
8
  description = "AI DevOps - a package to help deploy GenAI to the Cloud."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -33,7 +33,6 @@ dependencies = [
33
33
 
34
34
  [project.urls]
35
35
  Homepage = "https://github.com/sunholo-data/sunholo-py"
36
- Download = "https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.118.0.tar.gz"
37
36
 
38
37
  [project.scripts]
39
38
  sunholo = "sunholo.cli.cli:main"
@@ -140,7 +139,8 @@ azure = [
140
139
  ]
141
140
  cli = [
142
141
  "jsonschema>=4.21.1",
143
- "rich"
142
+ "rich",
143
+ "fastmcp"
144
144
  ]
145
145
  database = [
146
146
  "asyncpg",
@@ -1,3 +1,3 @@
1
1
  from .invoke_vac_utils import invoke_vac
2
2
  from .direct_vac_func import direct_vac, direct_vac_stream, async_direct_vac, async_direct_vac_stream
3
- from .async_class import AsyncTaskRunner
3
+ from .async_task_runner import AsyncTaskRunner, CallbackContext, TaskConfig
@@ -1,11 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sunholo
3
- Version: 0.145.0
3
+ Version: 0.145.1
4
4
  Summary: AI DevOps - a package to help deploy GenAI to the Cloud.
5
5
  Author-email: Holosun ApS <multivac@sunholo.com>
6
6
  License: Apache License, Version 2.0
7
7
  Project-URL: Homepage, https://github.com/sunholo-data/sunholo-py
8
- Project-URL: Download, https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.118.0.tar.gz
9
8
  Keywords: llms,devops,google_cloud_platform
10
9
  Classifier: Development Status :: 3 - Alpha
11
10
  Classifier: Intended Audience :: Developers
@@ -107,6 +106,7 @@ Requires-Dist: azure-storage-blob; extra == "azure"
107
106
  Provides-Extra: cli
108
107
  Requires-Dist: jsonschema>=4.21.1; extra == "cli"
109
108
  Requires-Dist: rich; extra == "cli"
109
+ Requires-Dist: fastmcp; extra == "cli"
110
110
  Provides-Extra: database
111
111
  Requires-Dist: asyncpg; extra == "database"
112
112
  Requires-Dist: supabase; extra == "database"
@@ -195,9 +195,8 @@ src/sunholo/vertex/safety.py
195
195
  src/sunholo/vertex/type_dict_to_json.py
196
196
  tests/test_async.py
197
197
  tests/test_async_genai2.py
198
+ tests/test_async_task_runner.py
198
199
  tests/test_chat_history.py
199
200
  tests/test_config.py
200
201
  tests/test_genai2.py
201
- tests/test_unstructured.py
202
- tests/test_vac_routes_fastapi.py
203
- tests/test_vac_routes_mcp.py
202
+ tests/test_unstructured.py
@@ -86,6 +86,7 @@ azure-storage-blob
86
86
  [cli]
87
87
  jsonschema>=4.21.1
88
88
  rich
89
+ fastmcp
89
90
 
90
91
  [database]
91
92
  asyncpg
@@ -0,0 +1,355 @@
1
+ """
2
+ Tests for AsyncTaskRunner with default callbacks and enhanced features.
3
+ """
4
+
5
+ import asyncio
6
+ import pytest
7
+ from unittest.mock import AsyncMock, MagicMock
8
+ from sunholo.invoke.async_task_runner import AsyncTaskRunner, TaskConfig, CallbackContext
9
+ from tenacity import stop_after_attempt
10
+
11
+
12
+ # Test fixtures - async functions to use in tests
13
+ async def simple_task(value: str):
14
+ """Simple async task that returns a value."""
15
+ await asyncio.sleep(0.1)
16
+ return f"Result: {value}"
17
+
18
+
19
+ async def failing_task(value: str):
20
+ """Task that always fails for testing error handling."""
21
+ await asyncio.sleep(0.1)
22
+ raise ValueError(f"Intentional error: {value}")
23
+
24
+
25
+ async def slow_task(value: str, duration: float = 2.0):
26
+ """Task with configurable duration for timeout testing."""
27
+ await asyncio.sleep(duration)
28
+ return f"Slow result: {value}"
29
+
30
+
31
+ @pytest.mark.asyncio
32
+ async def test_default_callbacks_basic():
33
+ """Test that default callbacks populate shared_state correctly."""
34
+ runner = AsyncTaskRunner(verbose=False) # Quiet for tests
35
+
36
+ # Add some tasks
37
+ runner.add_task(simple_task, "test1")
38
+ runner.add_task(simple_task, "test2")
39
+
40
+ # Run and get results
41
+ results = await runner.get_aggregated_results()
42
+
43
+ # Check that default callbacks populated the state
44
+ assert 'results' in results
45
+ assert 'completed' in results
46
+ assert 'started' in results
47
+ assert 'errors' in results
48
+
49
+ # Check task results
50
+ assert 'simple_task' in results['results']
51
+ assert results['results']['simple_task'] == "Result: test2" # Last one wins with same name
52
+
53
+ # Check completed list
54
+ assert len(results['completed']) == 2
55
+ assert results['completed'].count('simple_task') == 2
56
+
57
+ # Check started list
58
+ assert len(results['started']) == 2
59
+
60
+ # No errors expected
61
+ assert len(results['errors']) == 0
62
+
63
+
64
+ @pytest.mark.asyncio
65
+ async def test_default_callbacks_with_errors():
66
+ """Test that default callbacks handle errors correctly."""
67
+ runner = AsyncTaskRunner(verbose=False)
68
+
69
+ # Add both successful and failing tasks
70
+ runner.add_task(simple_task, "success")
71
+ runner.add_task(failing_task, "failure")
72
+
73
+ results = await runner.get_aggregated_results()
74
+
75
+ # Check successful task
76
+ assert 'simple_task' in results['results']
77
+ assert results['results']['simple_task'] == "Result: success"
78
+
79
+ # Check error was captured
80
+ assert 'failing_task' in results['errors']
81
+ assert "Intentional error: failure" in results['errors']['failing_task']
82
+
83
+ # Check completed list (only successful task)
84
+ assert 'simple_task' in results['completed']
85
+
86
+ # Both tasks should have started
87
+ assert len(results['started']) == 2
88
+
89
+
90
+ @pytest.mark.asyncio
91
+ async def test_default_callbacks_with_retry():
92
+ """Test that default callbacks track retry attempts."""
93
+ runner = AsyncTaskRunner(
94
+ verbose=False,
95
+ retry_enabled=False # Global default
96
+ )
97
+
98
+ # Add task with retry enabled
99
+ runner.add_task(
100
+ failing_task,
101
+ "retry_test",
102
+ task_config=TaskConfig(
103
+ retry_enabled=True,
104
+ retry_kwargs={'stop': stop_after_attempt(3)}
105
+ )
106
+ )
107
+
108
+ results = await runner.get_aggregated_results()
109
+
110
+ # Check that retries were tracked
111
+ assert 'retries' in results
112
+ assert len(results['retries']) == 2 # Attempts 2 and 3 (not 1)
113
+ assert 'failing_task_attempt_2' in results['retries']
114
+ assert 'failing_task_attempt_3' in results['retries']
115
+
116
+ # Task should have error after all retries
117
+ assert 'failing_task' in results['errors']
118
+
119
+
120
+ @pytest.mark.asyncio
121
+ async def test_custom_callback_override():
122
+ """Test that custom callbacks override defaults correctly."""
123
+ custom_complete_called = []
124
+
125
+ async def custom_on_complete(ctx: CallbackContext):
126
+ """Custom completion handler for testing."""
127
+ custom_complete_called.append(ctx.task_name)
128
+ # Still populate state like default would
129
+ ctx.shared_state['results'][ctx.task_name] = f"CUSTOM: {ctx.result}"
130
+ ctx.shared_state['completed'].append(ctx.task_name)
131
+
132
+ runner = AsyncTaskRunner(
133
+ verbose=False,
134
+ callbacks={'on_task_complete': custom_on_complete}
135
+ # Other callbacks remain as defaults
136
+ )
137
+
138
+ runner.add_task(simple_task, "test")
139
+ results = await runner.get_aggregated_results()
140
+
141
+ # Check custom callback was called
142
+ assert len(custom_complete_called) == 1
143
+ assert custom_complete_called[0] == 'simple_task'
144
+
145
+ # Check custom result format
146
+ assert results['results']['simple_task'] == "CUSTOM: Result: test"
147
+
148
+ # Default callbacks should still work for other events
149
+ assert 'started' in results
150
+ assert 'simple_task' in results['started']
151
+
152
+
153
+ @pytest.mark.asyncio
154
+ async def test_no_default_callbacks():
155
+ """Test that disabling default callbacks works."""
156
+ runner = AsyncTaskRunner(
157
+ use_default_callbacks=False,
158
+ verbose=False
159
+ )
160
+
161
+ runner.add_task(simple_task, "test")
162
+ results = await runner.get_aggregated_results()
163
+
164
+ # State should be initialized but empty (no callbacks to populate it)
165
+ assert results == {
166
+ 'results': {},
167
+ 'errors': {},
168
+ 'completed': [],
169
+ 'started': [],
170
+ 'retries': [],
171
+ 'timed_out': []
172
+ }
173
+
174
+
175
+ @pytest.mark.asyncio
176
+ async def test_per_task_timeout_with_defaults():
177
+ """Test per-task timeout configuration with default callbacks."""
178
+ runner = AsyncTaskRunner(
179
+ timeout=1, # Default 1 second timeout
180
+ verbose=False
181
+ )
182
+
183
+ # This task should timeout with default
184
+ runner.add_task(slow_task, "timeout_task", 2.0) # Takes 2 seconds
185
+
186
+ # This task should complete with custom timeout
187
+ runner.add_task(
188
+ slow_task,
189
+ "complete_task",
190
+ 0.5, # Takes 0.5 seconds
191
+ task_config=TaskConfig(timeout=3) # 3 second timeout
192
+ )
193
+
194
+ results = await runner.get_aggregated_results()
195
+
196
+ # Check that one timed out
197
+ assert 'timed_out' in results
198
+ assert 'slow_task' in results['timed_out']
199
+
200
+ # Check that timeout was recorded as error
201
+ assert 'slow_task' in results['errors']
202
+ # The default callback stores "Timeout after Xs" or the error might be "Unknown error" if timeout wasn't caught properly
203
+ error_msg = results['errors'].get('slow_task', '').lower()
204
+ assert 'timeout' in error_msg or 'unknown' in error_msg
205
+
206
+ # The one with extended timeout should complete
207
+ assert 'slow_task' in results['results']
208
+ assert results['results']['slow_task'] == "Slow result: complete_task"
209
+
210
+
211
+ @pytest.mark.asyncio
212
+ async def test_shared_state_persistence():
213
+ """Test that shared_state is accessible and modifiable across callbacks."""
214
+ shared_state = {
215
+ 'custom_counter': 0,
216
+ 'task_order': []
217
+ }
218
+
219
+ async def counting_callback(ctx: CallbackContext):
220
+ """Callback that increments a counter."""
221
+ ctx.shared_state['custom_counter'] += 1
222
+ ctx.shared_state['task_order'].append(ctx.task_name)
223
+ # Also do the default behavior
224
+ ctx.shared_state.setdefault('results', {})[ctx.task_name] = ctx.result
225
+
226
+ runner = AsyncTaskRunner(
227
+ shared_state=shared_state,
228
+ callbacks={'on_task_complete': counting_callback},
229
+ verbose=False
230
+ )
231
+
232
+ runner.add_task(simple_task, "first")
233
+ runner.add_task(simple_task, "second")
234
+ runner.add_task(simple_task, "third")
235
+
236
+ results = await runner.get_aggregated_results()
237
+
238
+ # Check custom state was maintained
239
+ assert results['custom_counter'] == 3
240
+ assert len(results['task_order']) == 3
241
+ assert all(name == 'simple_task' for name in results['task_order'])
242
+
243
+ # Default keys should also be present
244
+ assert 'results' in results
245
+ assert 'errors' in results
246
+ assert 'completed' in results
247
+
248
+
249
+ @pytest.mark.asyncio
250
+ async def test_verbose_mode():
251
+ """Test that verbose mode affects shared_state population but not its behavior."""
252
+ # Test verbose=True (default)
253
+ runner_verbose = AsyncTaskRunner(verbose=True)
254
+ runner_verbose.add_task(simple_task, "verbose_test")
255
+ results_verbose = await runner_verbose.get_aggregated_results()
256
+
257
+ # Should have results regardless of verbose mode
258
+ assert 'simple_task' in results_verbose['results']
259
+ assert 'simple_task' in results_verbose['completed']
260
+
261
+ # Test verbose=False - should still work but quietly
262
+ runner_quiet = AsyncTaskRunner(verbose=False)
263
+ runner_quiet.add_task(simple_task, "quiet_test")
264
+ results_quiet = await runner_quiet.get_aggregated_results()
265
+
266
+ # Should have same results structure
267
+ assert 'simple_task' in results_quiet['results']
268
+ assert 'simple_task' in results_quiet['completed']
269
+
270
+ # Both should produce same result structure
271
+ assert set(results_verbose.keys()) == set(results_quiet.keys())
272
+
273
+
274
+ @pytest.mark.asyncio
275
+ async def test_multiple_tasks_same_name():
276
+ """Test behavior when multiple tasks have the same function name."""
277
+ runner = AsyncTaskRunner(verbose=False)
278
+
279
+ # Add multiple tasks with same function
280
+ runner.add_task(simple_task, "first")
281
+ runner.add_task(simple_task, "second")
282
+ runner.add_task(simple_task, "third")
283
+
284
+ results = await runner.get_aggregated_results()
285
+
286
+ # Results dict should have the last result (overwrites)
287
+ assert results['results']['simple_task'] == "Result: third"
288
+
289
+ # Completed list should have all three
290
+ assert results['completed'].count('simple_task') == 3
291
+
292
+ # Started list should have all three
293
+ assert results['started'].count('simple_task') == 3
294
+
295
+
296
+ @pytest.mark.asyncio
297
+ async def test_empty_runner():
298
+ """Test that runner works with no tasks."""
299
+ runner = AsyncTaskRunner(verbose=False)
300
+
301
+ # Run with no tasks
302
+ results = await runner.get_aggregated_results()
303
+
304
+ # Should return empty but initialized state
305
+ assert results == {
306
+ 'results': {},
307
+ 'errors': {},
308
+ 'completed': [],
309
+ 'started': [],
310
+ 'retries': [],
311
+ 'timed_out': []
312
+ }
313
+
314
+
315
+ @pytest.mark.asyncio
316
+ async def test_task_config_none_values():
317
+ """Test that TaskConfig with None values falls back to global settings."""
318
+ runner = AsyncTaskRunner(
319
+ timeout=5,
320
+ retry_enabled=True,
321
+ verbose=False
322
+ )
323
+
324
+ # Add task with partial config (None values should use globals)
325
+ runner.add_task(
326
+ simple_task,
327
+ "test",
328
+ task_config=TaskConfig(
329
+ timeout=None, # Should use global (5)
330
+ retry_enabled=None, # Should use global (True)
331
+ metadata={'custom': 'data'}
332
+ )
333
+ )
334
+
335
+ results = await runner.get_aggregated_results()
336
+
337
+ # Task should complete successfully
338
+ assert 'simple_task' in results['results']
339
+ assert results['results']['simple_task'] == "Result: test"
340
+
341
+
342
+ if __name__ == "__main__":
343
+ # Run tests with asyncio
344
+ asyncio.run(test_default_callbacks_basic())
345
+ asyncio.run(test_default_callbacks_with_errors())
346
+ asyncio.run(test_default_callbacks_with_retry())
347
+ asyncio.run(test_custom_callback_override())
348
+ asyncio.run(test_no_default_callbacks())
349
+ asyncio.run(test_per_task_timeout_with_defaults())
350
+ asyncio.run(test_shared_state_persistence())
351
+ asyncio.run(test_verbose_mode())
352
+ asyncio.run(test_multiple_tasks_same_name())
353
+ asyncio.run(test_empty_runner())
354
+ asyncio.run(test_task_config_none_values())
355
+ print("All tests passed!")
@@ -1,226 +0,0 @@
1
- # Copyright [2024] [Holosun ApS]
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
-
15
- import pytest
16
- import json
17
- import asyncio
18
- from unittest.mock import patch, MagicMock, AsyncMock
19
-
20
- # Skip entire test file if FastAPI is not available
21
- pytest.importorskip("fastapi")
22
- pytest.importorskip("httpx")
23
-
24
- from fastapi import FastAPI
25
- from fastapi.testclient import TestClient
26
- from httpx import AsyncClient, ASGITransport
27
-
28
- from src.sunholo.agents.fastapi.vac_routes import VACRoutesFastAPI, VACRequest
29
- from tests.fixtures.mock_interpreters import (
30
- mock_async_stream_interpreter,
31
- mock_sync_stream_interpreter,
32
- mock_async_vac_interpreter,
33
- mock_sync_vac_interpreter,
34
- mock_async_stream_with_heartbeat,
35
- mock_async_stream_with_error,
36
- mock_sync_stream_with_timeout
37
- )
38
-
39
-
40
- @pytest.fixture
41
- def fastapi_app_async():
42
- """Create a FastAPI app with async interpreters."""
43
- app = FastAPI()
44
- vac_routes = VACRoutesFastAPI(
45
- app,
46
- stream_interpreter=mock_async_stream_interpreter,
47
- vac_interpreter=mock_async_vac_interpreter,
48
- enable_mcp_server=False,
49
- add_langfuse_eval=False
50
- )
51
- return app
52
-
53
-
54
- @pytest.fixture
55
- def fastapi_app_sync():
56
- """Create a FastAPI app with sync interpreters."""
57
- app = FastAPI()
58
- vac_routes = VACRoutesFastAPI(
59
- app,
60
- stream_interpreter=mock_sync_stream_interpreter,
61
- vac_interpreter=mock_sync_vac_interpreter,
62
- enable_mcp_server=False,
63
- add_langfuse_eval=False
64
- )
65
- return app
66
-
67
-
68
- @pytest.fixture
69
- def test_client_async(fastapi_app_async):
70
- """Create test client for async app."""
71
- return TestClient(fastapi_app_async)
72
-
73
-
74
- @pytest.fixture
75
- def test_client_sync(fastapi_app_sync):
76
- """Create test client for sync app."""
77
- return TestClient(fastapi_app_sync)
78
-
79
-
80
- class TestVACRoutesFastAPI:
81
- """Test suite for VACRoutesFastAPI."""
82
-
83
- def test_health_endpoint(self, test_client_async):
84
- """Test health check endpoint."""
85
- response = test_client_async.get("/health")
86
- assert response.status_code == 200
87
- assert response.json() == {"status": "healthy"}
88
-
89
- def test_home_endpoint(self, test_client_async):
90
- """Test home endpoint."""
91
- response = test_client_async.get("/")
92
- assert response.status_code == 200
93
- assert response.json() == "OK"
94
-
95
- @pytest.mark.asyncio
96
- @patch('src.sunholo.agents.fastapi.vac_routes.ConfigManager')
97
- @patch('src.sunholo.agents.fastapi.vac_routes.extract_chat_history_async_cached')
98
- @patch('src.sunholo.agents.fastapi.vac_routes.archive_qa')
99
- async def test_async_streaming_plain(self, mock_archive, mock_extract, mock_config, test_client_async):
100
- """Test async streaming with plain text response."""
101
- # Setup mocks
102
- mock_config.return_value = MagicMock()
103
- mock_extract.return_value = []
104
- mock_archive.return_value = AsyncMock()
105
-
106
- # Make request
107
- request_data = {
108
- "user_input": "Test question",
109
- "chat_history": [],
110
- "stream_wait_time": 1,
111
- "stream_timeout": 10
112
- }
113
-
114
- response = test_client_async.post(
115
- "/vac/streaming/test_vac",
116
- json=request_data
117
- )
118
-
119
- assert response.status_code == 200
120
- assert response.headers["content-type"] == "text/plain; charset=utf-8"
121
-
122
- # Check streaming content
123
- content = response.text
124
- assert "Hello from async interpreter!" in content
125
- assert "Test question" in content
126
-
127
-
128
-
129
-
130
-
131
- def test_openai_health(self, test_client_async):
132
- """Test OpenAI health endpoint."""
133
- response = test_client_async.get("/openai/health")
134
- assert response.status_code == 200
135
- assert response.json() == {"message": "Success"}
136
-
137
-
138
-
139
- def test_vac_request_model(self):
140
- """Test VACRequest model validation."""
141
- # Valid request
142
- request = VACRequest(
143
- user_input="Test",
144
- chat_history=[],
145
- stream_wait_time=5,
146
- stream_timeout=30
147
- )
148
- assert request.user_input == "Test"
149
- assert request.stream_wait_time == 5
150
-
151
- # Default values
152
- request = VACRequest(user_input="Test")
153
- assert request.chat_history is None
154
- assert request.stream_wait_time == 7
155
- assert request.stream_timeout == 120
156
-
157
- @pytest.mark.asyncio
158
- async def test_streaming_with_heartbeat(self):
159
- """Test streaming with heartbeat tokens."""
160
- app = FastAPI()
161
- vac_routes = VACRoutesFastAPI(
162
- app,
163
- stream_interpreter=mock_async_stream_with_heartbeat,
164
- enable_mcp_server=False,
165
- add_langfuse_eval=False
166
- )
167
-
168
- async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
169
- request_data = {
170
- "user_input": "Test heartbeat",
171
- "chat_history": [],
172
- "stream_wait_time": 1,
173
- "stream_timeout": 10
174
- }
175
-
176
- with patch('src.sunholo.agents.fastapi.vac_routes.ConfigManager'):
177
- with patch('src.sunholo.agents.fastapi.vac_routes.extract_chat_history_async_cached', return_value=[]):
178
- with patch('src.sunholo.agents.fastapi.vac_routes.archive_qa', new_callable=AsyncMock):
179
- response = await client.post(
180
- "/vac/streaming/test_vac",
181
- json=request_data
182
- )
183
-
184
- assert response.status_code == 200
185
- # Heartbeat tokens should be processed
186
- content = response.text
187
- assert "Starting response..." in content
188
-
189
-
190
- def test_interpreter_detection(self):
191
- """Test that sync/async interpreter detection works correctly."""
192
- app = FastAPI()
193
-
194
- # Test async detection
195
- vac_routes_async = VACRoutesFastAPI(
196
- app,
197
- stream_interpreter=mock_async_stream_interpreter,
198
- vac_interpreter=mock_async_vac_interpreter
199
- )
200
- assert vac_routes_async.stream_is_async is True
201
- assert vac_routes_async.vac_is_async is True
202
-
203
- # Test sync detection
204
- vac_routes_sync = VACRoutesFastAPI(
205
- app,
206
- stream_interpreter=mock_sync_stream_interpreter,
207
- vac_interpreter=mock_sync_vac_interpreter
208
- )
209
- assert vac_routes_sync.stream_is_async is False
210
- assert vac_routes_sync.vac_is_async is False
211
-
212
- def test_mcp_server_disabled(self, test_client_async):
213
- """Test that MCP server endpoints return 404 when disabled."""
214
- response = test_client_async.post("/mcp")
215
- assert response.status_code == 404
216
-
217
- response = test_client_async.get("/mcp")
218
- assert response.status_code == 404
219
-
220
- def test_a2a_agent_disabled(self, test_client_async):
221
- """Test that A2A agent endpoints return 404 when disabled."""
222
- response = test_client_async.get("/.well-known/agent.json")
223
- assert response.status_code == 404
224
-
225
- response = test_client_async.post("/a2a/tasks/send")
226
- assert response.status_code == 404