code-muse 0.0.1__py3-none-any.whl

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 (394) hide show
  1. code_muse/__init__.py +26 -0
  2. code_muse/__main__.py +10 -0
  3. code_muse/agents/__init__.py +31 -0
  4. code_muse/agents/_builder.py +214 -0
  5. code_muse/agents/_compaction.py +506 -0
  6. code_muse/agents/_diagnostics.py +171 -0
  7. code_muse/agents/_history.py +382 -0
  8. code_muse/agents/_key_listeners.py +148 -0
  9. code_muse/agents/_non_streaming_render.py +148 -0
  10. code_muse/agents/_runtime.py +596 -0
  11. code_muse/agents/agent_creator_agent.py +603 -0
  12. code_muse/agents/agent_helios.py +47 -0
  13. code_muse/agents/agent_manager.py +740 -0
  14. code_muse/agents/agent_muse.py +78 -0
  15. code_muse/agents/agent_planning.py +44 -0
  16. code_muse/agents/agent_qa_melpomene.py +207 -0
  17. code_muse/agents/base_agent.py +194 -0
  18. code_muse/agents/event_stream_handler.py +361 -0
  19. code_muse/agents/json_agent.py +201 -0
  20. code_muse/agents/prompt_v3.py +521 -0
  21. code_muse/agents/subagent_stream_handler.py +273 -0
  22. code_muse/callbacks.py +941 -0
  23. code_muse/chatgpt_codex_client.py +333 -0
  24. code_muse/claude_cache_client.py +853 -0
  25. code_muse/cli_runner/__init__.py +319 -0
  26. code_muse/cli_runner/args.py +63 -0
  27. code_muse/cli_runner/loop.py +510 -0
  28. code_muse/cli_runner/resume.py +72 -0
  29. code_muse/cli_runner/runner.py +161 -0
  30. code_muse/command_line/__init__.py +1 -0
  31. code_muse/command_line/add_model_menu.py +1331 -0
  32. code_muse/command_line/agent_menu.py +674 -0
  33. code_muse/command_line/attachments.py +397 -0
  34. code_muse/command_line/autosave_menu.py +709 -0
  35. code_muse/command_line/clipboard.py +528 -0
  36. code_muse/command_line/colors_menu.py +530 -0
  37. code_muse/command_line/command_handler.py +262 -0
  38. code_muse/command_line/command_registry.py +150 -0
  39. code_muse/command_line/config_commands.py +711 -0
  40. code_muse/command_line/core_commands.py +740 -0
  41. code_muse/command_line/diff_menu.py +865 -0
  42. code_muse/command_line/file_path_completion.py +73 -0
  43. code_muse/command_line/load_context_completion.py +57 -0
  44. code_muse/command_line/model_picker_completion.py +512 -0
  45. code_muse/command_line/model_settings_menu.py +983 -0
  46. code_muse/command_line/onboarding_slides.py +162 -0
  47. code_muse/command_line/onboarding_wizard.py +337 -0
  48. code_muse/command_line/pagination.py +41 -0
  49. code_muse/command_line/pin_command_completion.py +329 -0
  50. code_muse/command_line/prompt_toolkit_completion.py +886 -0
  51. code_muse/command_line/session_commands.py +304 -0
  52. code_muse/command_line/shell_passthrough.py +145 -0
  53. code_muse/command_line/skills_completion.py +158 -0
  54. code_muse/command_line/types.py +18 -0
  55. code_muse/command_line/uc_menu.py +908 -0
  56. code_muse/command_line/utils.py +105 -0
  57. code_muse/command_line/wiggum_state.py +77 -0
  58. code_muse/config.py +1138 -0
  59. code_muse/config_agent.py +168 -0
  60. code_muse/config_appearance.py +241 -0
  61. code_muse/config_model.py +357 -0
  62. code_muse/config_security.py +73 -0
  63. code_muse/error_logging.py +132 -0
  64. code_muse/evals/__init__.py +35 -0
  65. code_muse/evals/eval_helpers.py +81 -0
  66. code_muse/evals/eval_runner.py +299 -0
  67. code_muse/evals/sample_evals/__init__.py +1 -0
  68. code_muse/evals/sample_evals/eval_frugal_reads.py +59 -0
  69. code_muse/evals/sample_evals/eval_memory_planning.py +31 -0
  70. code_muse/evals/sample_evals/eval_shell_efficiency.py +39 -0
  71. code_muse/evals/sample_evals/eval_tool_masking.py +33 -0
  72. code_muse/fs_scan_cache/__init__.py +31 -0
  73. code_muse/fs_scan_cache/invalidation_hooks.py +89 -0
  74. code_muse/fs_scan_cache/scan_cache_core.cpython-314-darwin.so +0 -0
  75. code_muse/fs_scan_cache/scan_cache_core.pyx +203 -0
  76. code_muse/fs_scan_cache/tool_integration.py +309 -0
  77. code_muse/fs_scan_cache/ttl_policy.py +44 -0
  78. code_muse/gemini_code_assist.py +383 -0
  79. code_muse/gemini_model.py +838 -0
  80. code_muse/hook_engine/README.md +105 -0
  81. code_muse/hook_engine/__init__.py +21 -0
  82. code_muse/hook_engine/aliases.py +153 -0
  83. code_muse/hook_engine/engine.py +221 -0
  84. code_muse/hook_engine/executor.py +347 -0
  85. code_muse/hook_engine/matcher.py +154 -0
  86. code_muse/hook_engine/models.py +245 -0
  87. code_muse/hook_engine/registry.py +114 -0
  88. code_muse/hook_engine/trust.py +268 -0
  89. code_muse/hook_engine/validator.py +144 -0
  90. code_muse/http_utils.py +360 -0
  91. code_muse/keymap.py +128 -0
  92. code_muse/list_filtering.py +26 -0
  93. code_muse/main.py +10 -0
  94. code_muse/messaging/__init__.py +259 -0
  95. code_muse/messaging/bus.py +621 -0
  96. code_muse/messaging/commands.py +166 -0
  97. code_muse/messaging/markdown_patches.py +57 -0
  98. code_muse/messaging/message_queue.py +397 -0
  99. code_muse/messaging/messages.py +591 -0
  100. code_muse/messaging/queue_console.py +269 -0
  101. code_muse/messaging/renderers.py +308 -0
  102. code_muse/messaging/rich_renderer.py +1158 -0
  103. code_muse/messaging/shimmer.py +154 -0
  104. code_muse/messaging/spinner/__init__.py +87 -0
  105. code_muse/messaging/spinner/console_spinner.py +250 -0
  106. code_muse/messaging/spinner/spinner_base.py +82 -0
  107. code_muse/messaging/subagent_console.py +458 -0
  108. code_muse/model_factory.py +1203 -0
  109. code_muse/model_switching.py +59 -0
  110. code_muse/model_utils.py +156 -0
  111. code_muse/models.json +66 -0
  112. code_muse/models_cache/__init__.py +26 -0
  113. code_muse/models_cache/blocking_lru_cache.py +98 -0
  114. code_muse/models_cache/cache_writer.py +86 -0
  115. code_muse/models_cache/sha256_hash.cpython-314-darwin.so +0 -0
  116. code_muse/models_cache/sha256_hash.pyx +34 -0
  117. code_muse/models_cache/startup_integration.py +75 -0
  118. code_muse/models_dev_api.json +1 -0
  119. code_muse/models_dev_parser.py +590 -0
  120. code_muse/motion.py +126 -0
  121. code_muse/plugins/__init__.py +471 -0
  122. code_muse/plugins/agent_skills/__init__.py +32 -0
  123. code_muse/plugins/agent_skills/config.py +176 -0
  124. code_muse/plugins/agent_skills/discovery.py +309 -0
  125. code_muse/plugins/agent_skills/downloader.py +389 -0
  126. code_muse/plugins/agent_skills/installer.py +19 -0
  127. code_muse/plugins/agent_skills/metadata.py +293 -0
  128. code_muse/plugins/agent_skills/prompt_builder.py +66 -0
  129. code_muse/plugins/agent_skills/register_callbacks.py +298 -0
  130. code_muse/plugins/agent_skills/remote_catalog.py +320 -0
  131. code_muse/plugins/agent_skills/skill_catalog.py +254 -0
  132. code_muse/plugins/agent_skills/skills_install_menu.py +690 -0
  133. code_muse/plugins/agent_skills/skills_menu.py +791 -0
  134. code_muse/plugins/autonomous_memory/__init__.py +39 -0
  135. code_muse/plugins/autonomous_memory/bm25_scorer.cpython-314-darwin.so +0 -0
  136. code_muse/plugins/autonomous_memory/bm25_scorer.cpython-314-x86_64-linux-gnu.so +0 -0
  137. code_muse/plugins/autonomous_memory/bm25_scorer.pyx +291 -0
  138. code_muse/plugins/autonomous_memory/consolidation.py +82 -0
  139. code_muse/plugins/autonomous_memory/extraction.py +382 -0
  140. code_muse/plugins/autonomous_memory/lease_lock.py +105 -0
  141. code_muse/plugins/autonomous_memory/memory_injection.py +59 -0
  142. code_muse/plugins/autonomous_memory/register_callbacks.py +268 -0
  143. code_muse/plugins/autonomous_memory/secret_scanner.py +62 -0
  144. code_muse/plugins/autonomous_memory/session_scanner.py +163 -0
  145. code_muse/plugins/aws_bedrock/__init__.py +14 -0
  146. code_muse/plugins/aws_bedrock/config.py +99 -0
  147. code_muse/plugins/aws_bedrock/register_callbacks.py +241 -0
  148. code_muse/plugins/aws_bedrock/utils.py +153 -0
  149. code_muse/plugins/azure_foundry/README.md +238 -0
  150. code_muse/plugins/azure_foundry/__init__.py +15 -0
  151. code_muse/plugins/azure_foundry/config.py +125 -0
  152. code_muse/plugins/azure_foundry/discovery.py +187 -0
  153. code_muse/plugins/azure_foundry/register_callbacks.py +495 -0
  154. code_muse/plugins/azure_foundry/token.py +180 -0
  155. code_muse/plugins/azure_foundry/utils.py +345 -0
  156. code_muse/plugins/build_filter/__init__.py +1 -0
  157. code_muse/plugins/build_filter/register_callbacks.py +201 -0
  158. code_muse/plugins/build_filter/strategies/__init__.py +1 -0
  159. code_muse/plugins/build_filter/strategies/build.py +397 -0
  160. code_muse/plugins/chatgpt_oauth/__init__.py +6 -0
  161. code_muse/plugins/chatgpt_oauth/config.py +52 -0
  162. code_muse/plugins/chatgpt_oauth/oauth_flow.py +338 -0
  163. code_muse/plugins/chatgpt_oauth/register_callbacks.py +172 -0
  164. code_muse/plugins/chatgpt_oauth/test_plugin.py +301 -0
  165. code_muse/plugins/chatgpt_oauth/utils.py +538 -0
  166. code_muse/plugins/checkpointing/__init__.py +29 -0
  167. code_muse/plugins/checkpointing/checkpoint_hook.py +51 -0
  168. code_muse/plugins/checkpointing/conversation_snapshots.py +117 -0
  169. code_muse/plugins/checkpointing/register_callbacks.py +51 -0
  170. code_muse/plugins/checkpointing/restore_command.py +263 -0
  171. code_muse/plugins/checkpointing/rewind_shortcut.py +88 -0
  172. code_muse/plugins/checkpointing/shadow_git.py +90 -0
  173. code_muse/plugins/claude_code_hooks/__init__.py +1 -0
  174. code_muse/plugins/claude_code_hooks/config.py +188 -0
  175. code_muse/plugins/claude_code_hooks/register_callbacks.py +208 -0
  176. code_muse/plugins/claude_code_oauth/README.md +167 -0
  177. code_muse/plugins/claude_code_oauth/SETUP.md +93 -0
  178. code_muse/plugins/claude_code_oauth/__init__.py +25 -0
  179. code_muse/plugins/claude_code_oauth/config.py +52 -0
  180. code_muse/plugins/claude_code_oauth/fast_mode.py +124 -0
  181. code_muse/plugins/claude_code_oauth/prompt_handler.py +63 -0
  182. code_muse/plugins/claude_code_oauth/register_callbacks.py +547 -0
  183. code_muse/plugins/claude_code_oauth/test_fast_mode.py +165 -0
  184. code_muse/plugins/claude_code_oauth/test_plugin.py +283 -0
  185. code_muse/plugins/claude_code_oauth/token_refresh_heartbeat.py +237 -0
  186. code_muse/plugins/claude_code_oauth/utils.py +664 -0
  187. code_muse/plugins/copilot_auth/__init__.py +11 -0
  188. code_muse/plugins/copilot_auth/config.py +91 -0
  189. code_muse/plugins/copilot_auth/reasoning_client.py +409 -0
  190. code_muse/plugins/copilot_auth/register_callbacks.py +461 -0
  191. code_muse/plugins/copilot_auth/utils.py +584 -0
  192. code_muse/plugins/custom_commands/__init__.py +14 -0
  193. code_muse/plugins/custom_commands/args_injection.py +82 -0
  194. code_muse/plugins/custom_commands/command_discovery.py +89 -0
  195. code_muse/plugins/custom_commands/command_toml_schema.py +71 -0
  196. code_muse/plugins/custom_commands/register_callbacks.py +176 -0
  197. code_muse/plugins/customizable_commands/__init__.py +0 -0
  198. code_muse/plugins/customizable_commands/register_callbacks.py +136 -0
  199. code_muse/plugins/destructive_command_guard/__init__.py +14 -0
  200. code_muse/plugins/destructive_command_guard/detector.py +375 -0
  201. code_muse/plugins/destructive_command_guard/register_callbacks.py +148 -0
  202. code_muse/plugins/example_custom_command/README.md +280 -0
  203. code_muse/plugins/example_custom_command/register_callbacks.py +51 -0
  204. code_muse/plugins/file_permission_handler/__init__.py +4 -0
  205. code_muse/plugins/file_permission_handler/register_callbacks.py +441 -0
  206. code_muse/plugins/filter_engine/__init__.py +30 -0
  207. code_muse/plugins/filter_engine/classifier.py +153 -0
  208. code_muse/plugins/filter_engine/content_detector.py +184 -0
  209. code_muse/plugins/filter_engine/dispatcher.py +244 -0
  210. code_muse/plugins/filter_engine/register_callbacks.py +188 -0
  211. code_muse/plugins/filter_engine/registry.py +279 -0
  212. code_muse/plugins/filter_engine/strategies/__init__.py +8 -0
  213. code_muse/plugins/filter_engine/strategies/ast_compressor.cpython-314-darwin.so +0 -0
  214. code_muse/plugins/filter_engine/strategies/ast_compressor.cpython-314-x86_64-linux-gnu.so +0 -0
  215. code_muse/plugins/filter_engine/strategies/ast_compressor.pyx +348 -0
  216. code_muse/plugins/filter_engine/strategies/ast_parser.py +167 -0
  217. code_muse/plugins/filter_engine/strategies/code.cpython-314-darwin.so +0 -0
  218. code_muse/plugins/filter_engine/strategies/code.cpython-314-x86_64-linux-gnu.so +0 -0
  219. code_muse/plugins/filter_engine/strategies/code.pyx +584 -0
  220. code_muse/plugins/filter_engine/strategies/git.cpython-314-darwin.so +0 -0
  221. code_muse/plugins/filter_engine/strategies/git.cpython-314-x86_64-linux-gnu.so +0 -0
  222. code_muse/plugins/filter_engine/strategies/git.pyx +438 -0
  223. code_muse/plugins/filter_engine/strategies/json_compressor.cpython-314-darwin.so +0 -0
  224. code_muse/plugins/filter_engine/strategies/json_compressor.pyx +253 -0
  225. code_muse/plugins/filter_engine/strategies/json_patterns.cpython-314-darwin.so +0 -0
  226. code_muse/plugins/filter_engine/strategies/json_patterns.pyx +178 -0
  227. code_muse/plugins/filter_engine/strategies/lint.cpython-314-darwin.so +0 -0
  228. code_muse/plugins/filter_engine/strategies/lint.cpython-314-x86_64-linux-gnu.so +0 -0
  229. code_muse/plugins/filter_engine/strategies/lint.pyx +626 -0
  230. code_muse/plugins/filter_engine/strategies/test.cpython-314-darwin.so +0 -0
  231. code_muse/plugins/filter_engine/strategies/test.cpython-314-x86_64-linux-gnu.so +0 -0
  232. code_muse/plugins/filter_engine/strategies/test.pyx +431 -0
  233. code_muse/plugins/filter_engine/verbosity.py +63 -0
  234. code_muse/plugins/force_push_guard/__init__.py +5 -0
  235. code_muse/plugins/force_push_guard/detector.py +96 -0
  236. code_muse/plugins/force_push_guard/register_callbacks.py +144 -0
  237. code_muse/plugins/force_push_guard/test_detector.py +143 -0
  238. code_muse/plugins/frontend_emitter/__init__.py +25 -0
  239. code_muse/plugins/frontend_emitter/emitter.py +121 -0
  240. code_muse/plugins/frontend_emitter/register_callbacks.py +259 -0
  241. code_muse/plugins/gac/__init__.py +4 -0
  242. code_muse/plugins/gac/git_ops.py +136 -0
  243. code_muse/plugins/gac/prompt.py +191 -0
  244. code_muse/plugins/gac/register_callbacks.py +82 -0
  245. code_muse/plugins/hook_creator/__init__.py +1 -0
  246. code_muse/plugins/hook_creator/register_callbacks.py +34 -0
  247. code_muse/plugins/hook_manager/__init__.py +1 -0
  248. code_muse/plugins/hook_manager/config.py +289 -0
  249. code_muse/plugins/hook_manager/hooks_menu.py +563 -0
  250. code_muse/plugins/hook_manager/register_callbacks.py +227 -0
  251. code_muse/plugins/hook_monitor/register_callbacks.py +36 -0
  252. code_muse/plugins/mindpack/__init__.py +0 -0
  253. code_muse/plugins/mindpack/factory.py +930 -0
  254. code_muse/plugins/mindpack/judge.py +573 -0
  255. code_muse/plugins/mindpack/memory.py +100 -0
  256. code_muse/plugins/mindpack/mindpack_menu.py +1552 -0
  257. code_muse/plugins/mindpack/orchestration.py +605 -0
  258. code_muse/plugins/mindpack/register_callbacks.py +175 -0
  259. code_muse/plugins/mindpack/schemas.py +358 -0
  260. code_muse/plugins/mindpack/tools.py +387 -0
  261. code_muse/plugins/oauth_muse_html.py +226 -0
  262. code_muse/plugins/ollama_setup/__init__.py +5 -0
  263. code_muse/plugins/ollama_setup/completer.py +36 -0
  264. code_muse/plugins/ollama_setup/register_callbacks.py +410 -0
  265. code_muse/plugins/plan_command/__init__.py +0 -0
  266. code_muse/plugins/plan_command/register_callbacks.py +206 -0
  267. code_muse/plugins/plan_mode/__init__.py +37 -0
  268. code_muse/plugins/plan_mode/mode_cycling.py +40 -0
  269. code_muse/plugins/plan_mode/plan_generation.py +68 -0
  270. code_muse/plugins/plan_mode/plan_hooks.py +74 -0
  271. code_muse/plugins/plan_mode/plan_mode_tools.py +138 -0
  272. code_muse/plugins/plan_mode/register_callbacks.py +121 -0
  273. code_muse/plugins/plugin_trust/register_callbacks.py +140 -0
  274. code_muse/plugins/policy_engine/__init__.py +46 -0
  275. code_muse/plugins/policy_engine/approval_flow_integration.py +59 -0
  276. code_muse/plugins/policy_engine/policy_evaluator.py +75 -0
  277. code_muse/plugins/policy_engine/policy_file_discovery.py +90 -0
  278. code_muse/plugins/policy_engine/policy_toml_schema.py +115 -0
  279. code_muse/plugins/policy_engine/register_callbacks.py +112 -0
  280. code_muse/plugins/pop_command/__init__.py +1 -0
  281. code_muse/plugins/pop_command/register_callbacks.py +189 -0
  282. code_muse/plugins/prompt_newline/__init__.py +13 -0
  283. code_muse/plugins/prompt_newline/config.py +19 -0
  284. code_muse/plugins/prompt_newline/register_callbacks.py +159 -0
  285. code_muse/plugins/safety_status/__init__.py +0 -0
  286. code_muse/plugins/safety_status/register_callbacks.py +113 -0
  287. code_muse/plugins/semantic_compression/__init__.py +6 -0
  288. code_muse/plugins/semantic_compression/compressor.py +295 -0
  289. code_muse/plugins/semantic_compression/config.py +123 -0
  290. code_muse/plugins/semantic_compression/register_callbacks.py +320 -0
  291. code_muse/plugins/shell_minimizer/__init__.py +50 -0
  292. code_muse/plugins/shell_minimizer/builtin_filters.toml +393 -0
  293. code_muse/plugins/shell_minimizer/pipeline.py +556 -0
  294. code_muse/plugins/shell_minimizer/primitives.py +482 -0
  295. code_muse/plugins/shell_minimizer/register_callbacks.py +276 -0
  296. code_muse/plugins/shell_safety/__init__.py +6 -0
  297. code_muse/plugins/shell_safety/agent_shell_safety.py +69 -0
  298. code_muse/plugins/shell_safety/command_cache.py +149 -0
  299. code_muse/plugins/shell_safety/register_callbacks.py +202 -0
  300. code_muse/plugins/synthetic_status/__init__.py +1 -0
  301. code_muse/plugins/synthetic_status/register_callbacks.py +128 -0
  302. code_muse/plugins/synthetic_status/status_api.py +145 -0
  303. code_muse/plugins/token_caching/__init__.py +21 -0
  304. code_muse/plugins/token_caching/cache_hit_tracking.py +128 -0
  305. code_muse/plugins/token_caching/cacheable_prefix_detection.py +28 -0
  306. code_muse/plugins/token_caching/register_callbacks.py +54 -0
  307. code_muse/plugins/token_caching/stats_display.py +35 -0
  308. code_muse/plugins/token_tracking/__init__.py +26 -0
  309. code_muse/plugins/token_tracking/database.py +381 -0
  310. code_muse/plugins/token_tracking/edit_analyzer.py +97 -0
  311. code_muse/plugins/token_tracking/record.py +55 -0
  312. code_muse/plugins/token_tracking/register_callbacks.py +277 -0
  313. code_muse/plugins/token_tracking/reports.py +329 -0
  314. code_muse/plugins/universal_constructor/__init__.py +13 -0
  315. code_muse/plugins/universal_constructor/models.py +136 -0
  316. code_muse/plugins/universal_constructor/register_callbacks.py +47 -0
  317. code_muse/plugins/universal_constructor/registry.py +390 -0
  318. code_muse/plugins/universal_constructor/runner.py +474 -0
  319. code_muse/plugins/universal_constructor/safety.py +440 -0
  320. code_muse/plugins/universal_constructor/sandbox.py +584 -0
  321. code_muse/provider_identity.py +105 -0
  322. code_muse/pydantic_patches.py +410 -0
  323. code_muse/reopenable_async_client.py +233 -0
  324. code_muse/round_robin_model.py +151 -0
  325. code_muse/secret_storage.py +74 -0
  326. code_muse/security/__init__.py +1 -0
  327. code_muse/security/redaction.cpython-314-darwin.so +0 -0
  328. code_muse/security/redaction.cpython-314-x86_64-linux-gnu.so +0 -0
  329. code_muse/security/redaction.pyx +135 -0
  330. code_muse/session_storage.py +565 -0
  331. code_muse/status_display.py +261 -0
  332. code_muse/stream_parser/__init__.py +76 -0
  333. code_muse/stream_parser/assistant_text_parser.py +90 -0
  334. code_muse/stream_parser/citation_parser.py +76 -0
  335. code_muse/stream_parser/inline_hidden_tag_parser.py +236 -0
  336. code_muse/stream_parser/proposed_plan_parser.py +158 -0
  337. code_muse/stream_parser/stream_text_chunk.py +23 -0
  338. code_muse/stream_parser/stream_text_parser.py +27 -0
  339. code_muse/stream_parser/tagged_line_parser.cpython-314-darwin.so +0 -0
  340. code_muse/stream_parser/tagged_line_parser.pyx +251 -0
  341. code_muse/stream_parser/utf8_stream_parser.cpython-314-darwin.so +0 -0
  342. code_muse/stream_parser/utf8_stream_parser.pyx +206 -0
  343. code_muse/summarization_agent.py +308 -0
  344. code_muse/terminal_utils.cpython-314-darwin.so +0 -0
  345. code_muse/terminal_utils.cpython-314-x86_64-linux-gnu.so +0 -0
  346. code_muse/terminal_utils.pyx +483 -0
  347. code_muse/tools/__init__.py +459 -0
  348. code_muse/tools/agent_tools.py +613 -0
  349. code_muse/tools/ask_user_question/__init__.py +26 -0
  350. code_muse/tools/ask_user_question/constants.py +73 -0
  351. code_muse/tools/ask_user_question/demo_tui.py +55 -0
  352. code_muse/tools/ask_user_question/handler.py +232 -0
  353. code_muse/tools/ask_user_question/models.py +302 -0
  354. code_muse/tools/ask_user_question/registration.py +37 -0
  355. code_muse/tools/ask_user_question/renderers.py +336 -0
  356. code_muse/tools/ask_user_question/terminal_ui.py +327 -0
  357. code_muse/tools/ask_user_question/theme.py +156 -0
  358. code_muse/tools/ask_user_question/tui_loop.py +422 -0
  359. code_muse/tools/background_jobs.py +99 -0
  360. code_muse/tools/browser/__init__.py +37 -0
  361. code_muse/tools/browser/browser_control.py +289 -0
  362. code_muse/tools/browser/browser_interactions.py +545 -0
  363. code_muse/tools/browser/browser_locators.py +640 -0
  364. code_muse/tools/browser/browser_manager.py +376 -0
  365. code_muse/tools/browser/browser_navigation.py +251 -0
  366. code_muse/tools/browser/browser_screenshot.py +180 -0
  367. code_muse/tools/browser/browser_scripts.py +462 -0
  368. code_muse/tools/browser/browser_workflows.py +222 -0
  369. code_muse/tools/chrome_cdp/__init__.py +1070 -0
  370. code_muse/tools/chrome_cdp/register_callbacks.py +61 -0
  371. code_muse/tools/command_runner.py +1401 -0
  372. code_muse/tools/common.py +1407 -0
  373. code_muse/tools/display.py +87 -0
  374. code_muse/tools/file_modifications.py +1099 -0
  375. code_muse/tools/file_operations.py +860 -0
  376. code_muse/tools/image_tools.py +185 -0
  377. code_muse/tools/meetin_proxy/__init__.py +243 -0
  378. code_muse/tools/meetin_proxy/capture_addon.py +82 -0
  379. code_muse/tools/meetin_proxy/proxy_manager.py +326 -0
  380. code_muse/tools/meetin_proxy/register_callbacks.py +45 -0
  381. code_muse/tools/path_policy.py +219 -0
  382. code_muse/tools/skills_tools.py +586 -0
  383. code_muse/tools/subagent_context.py +158 -0
  384. code_muse/tools/tools_content.py +50 -0
  385. code_muse/tools/universal_constructor.py +965 -0
  386. code_muse/uvx_detection.py +241 -0
  387. code_muse/version_checker.py +86 -0
  388. code_muse-0.0.1.data/data/code_muse/models.json +66 -0
  389. code_muse-0.0.1.data/data/code_muse/models_dev_api.json +1 -0
  390. code_muse-0.0.1.dist-info/METADATA +845 -0
  391. code_muse-0.0.1.dist-info/RECORD +394 -0
  392. code_muse-0.0.1.dist-info/WHEEL +4 -0
  393. code_muse-0.0.1.dist-info/entry_points.txt +2 -0
  394. code_muse-0.0.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,838 @@
1
+ """Standalone Gemini Model for pydantic_ai - no google-genai dependency.
2
+
3
+ This module provides a custom Model implementation that uses Google's
4
+ Generative Language API directly via httpx, without the bloated google-genai
5
+ SDK dependency.
6
+ """
7
+
8
+ import base64
9
+ import json
10
+ import logging
11
+ import uuid
12
+ from collections.abc import AsyncIterator
13
+ from contextlib import asynccontextmanager
14
+ from dataclasses import dataclass, field
15
+ from datetime import UTC, datetime
16
+ from typing import Any
17
+
18
+ import httpx
19
+ from pydantic_ai._run_context import RunContext
20
+ from pydantic_ai.messages import (
21
+ ModelMessage,
22
+ ModelRequest,
23
+ ModelResponse,
24
+ ModelResponsePart,
25
+ ModelResponseStreamEvent,
26
+ RetryPromptPart,
27
+ SystemPromptPart,
28
+ TextPart,
29
+ ThinkingPart,
30
+ ToolCallPart,
31
+ ToolReturnPart,
32
+ UserPromptPart,
33
+ )
34
+ from pydantic_ai.models import Model, ModelRequestParameters, StreamedResponse
35
+ from pydantic_ai.settings import ModelSettings
36
+ from pydantic_ai.tools import ToolDefinition
37
+ from pydantic_ai.usage import RequestUsage
38
+
39
+ logger = logging.getLogger(__name__)
40
+
41
+ # Bypass thought signature for Gemini when no pending signature is available.
42
+ # This allows function calls to work with thinking models.
43
+ BYPASS_THOUGHT_SIGNATURE = "context_engineering_is_the_way_to_go"
44
+
45
+
46
+ def generate_tool_call_id() -> str:
47
+ """Generate a unique tool call ID."""
48
+ return str(uuid.uuid4())
49
+
50
+
51
+ def _flatten_union_to_object_gemini(union_items: list, defs: dict, resolve_fn) -> dict:
52
+ """Flatten a union of object types into a single object with all properties.
53
+
54
+ For discriminated unions like EditFilePayload, we merge all object types
55
+ into one with all properties (Gemini doesn't support anyOf/oneOf).
56
+ """
57
+ import copy as copy_module
58
+
59
+ merged_properties = {}
60
+ has_string_type = False
61
+
62
+ for item in union_items:
63
+ if not isinstance(item, dict):
64
+ continue
65
+
66
+ # Resolve $ref first
67
+ if "$ref" in item:
68
+ ref_path = item["$ref"]
69
+ ref_name = None
70
+ if ref_path.startswith("#/$defs/"):
71
+ ref_name = ref_path[8:]
72
+ elif ref_path.startswith("#/definitions/"):
73
+ ref_name = ref_path[14:]
74
+ if ref_name and ref_name in defs:
75
+ item = copy_module.deepcopy(defs[ref_name])
76
+ else:
77
+ continue
78
+
79
+ if item.get("type") == "string":
80
+ has_string_type = True
81
+ continue
82
+
83
+ if item.get("type") == "null":
84
+ continue
85
+
86
+ if item.get("type") == "object" or "properties" in item:
87
+ props = item.get("properties", {})
88
+ for prop_name, prop_schema in props.items():
89
+ if prop_name not in merged_properties:
90
+ merged_properties[prop_name] = resolve_fn(
91
+ copy_module.deepcopy(prop_schema)
92
+ )
93
+
94
+ if not merged_properties:
95
+ return {"type": "string"} if has_string_type else {"type": "object"}
96
+
97
+ return {
98
+ "type": "object",
99
+ "properties": merged_properties,
100
+ }
101
+
102
+
103
+ def _sanitize_schema_for_gemini(schema: dict) -> dict:
104
+ """Sanitize JSON schema for Gemini API compatibility.
105
+
106
+ Removes/transforms fields that Gemini doesn't support:
107
+ - $defs, definitions, $schema, $id
108
+ - additionalProperties
109
+ - $ref (inlined)
110
+ - anyOf/oneOf/allOf (flattened - Gemini doesn't support unions!)
111
+ - For unions of objects: merges into single object with all properties
112
+ - For simple unions (string | null): picks first non-null type
113
+ """
114
+ import copy
115
+
116
+ if not isinstance(schema, dict):
117
+ return schema
118
+
119
+ # Make a deep copy to avoid modifying original
120
+ schema = copy.deepcopy(schema)
121
+
122
+ # Extract $defs for reference resolution
123
+ defs = schema.pop("$defs", schema.pop("definitions", {}))
124
+
125
+ def resolve_refs(obj):
126
+ """Recursively resolve $ref references and clean schema."""
127
+ if isinstance(obj, dict):
128
+ # Handle anyOf/oneOf unions
129
+ for union_key in ["anyOf", "oneOf"]:
130
+ if union_key in obj:
131
+ union = obj[union_key]
132
+ if isinstance(union, list):
133
+ # Check if this is a complex union of objects
134
+ object_count = 0
135
+ has_refs = False
136
+ for item in union:
137
+ if isinstance(item, dict):
138
+ if "$ref" in item:
139
+ has_refs = True
140
+ object_count += 1
141
+ elif (
142
+ item.get("type") == "object" or "properties" in item
143
+ ):
144
+ object_count += 1
145
+
146
+ # If multiple objects or has refs, flatten to single object
147
+ if object_count > 1 or has_refs:
148
+ flattened = _flatten_union_to_object_gemini(
149
+ union, defs, resolve_refs
150
+ )
151
+ if "description" in obj:
152
+ flattened["description"] = obj["description"]
153
+ return flattened
154
+
155
+ # Simple union - pick first non-null type
156
+ for item in union:
157
+ if isinstance(item, dict) and item.get("type") != "null":
158
+ result = dict(item)
159
+ if "description" in obj:
160
+ result["description"] = obj["description"]
161
+ return resolve_refs(result)
162
+
163
+ # Handle allOf by merging all schemas
164
+ if "allOf" in obj:
165
+ all_of = obj["allOf"]
166
+ if isinstance(all_of, list):
167
+ merged = {}
168
+ merged_properties = {}
169
+ for item in all_of:
170
+ if isinstance(item, dict):
171
+ resolved_item = resolve_refs(item)
172
+ if "properties" in resolved_item:
173
+ merged_properties.update(
174
+ resolved_item.pop("properties")
175
+ )
176
+ merged.update(resolved_item)
177
+ if merged_properties:
178
+ merged["properties"] = merged_properties
179
+ for k, v in obj.items():
180
+ if k != "allOf":
181
+ merged[k] = v
182
+ return resolve_refs(merged)
183
+
184
+ # Check for $ref
185
+ if "$ref" in obj:
186
+ ref_path = obj["$ref"]
187
+ ref_name = None
188
+
189
+ # Parse ref like "#/$defs/SomeType" or "#/definitions/SomeType"
190
+ if ref_path.startswith("#/$defs/"):
191
+ ref_name = ref_path[8:]
192
+ elif ref_path.startswith("#/definitions/"):
193
+ ref_name = ref_path[14:]
194
+
195
+ if ref_name and ref_name in defs:
196
+ resolved = resolve_refs(copy.deepcopy(defs[ref_name]))
197
+ other_props = {k: v for k, v in obj.items() if k != "$ref"}
198
+ if other_props:
199
+ resolved.update(resolve_refs(other_props))
200
+ return resolved
201
+ else:
202
+ return {"type": "object"}
203
+
204
+ # Recursively process and transform
205
+ result = {}
206
+ for key, value in obj.items():
207
+ # Skip unsupported fields
208
+ if key in (
209
+ "$defs",
210
+ "definitions",
211
+ "$schema",
212
+ "$id",
213
+ "additionalProperties",
214
+ "default",
215
+ "examples",
216
+ "const",
217
+ "anyOf", # Skip any remaining union types
218
+ "oneOf",
219
+ "allOf",
220
+ ):
221
+ continue
222
+
223
+ result[key] = resolve_refs(value)
224
+ return result
225
+ elif isinstance(obj, list):
226
+ return [resolve_refs(item) for item in obj]
227
+ else:
228
+ return obj
229
+
230
+ return resolve_refs(schema)
231
+
232
+
233
+ class GeminiModel(Model):
234
+ """Standalone Model implementation for Google's Generative Language API.
235
+
236
+ Uses httpx directly instead of google-genai SDK.
237
+ """
238
+
239
+ def __init__(
240
+ self,
241
+ model_name: str,
242
+ api_key: str,
243
+ base_url: str = "https://generativelanguage.googleapis.com/v1beta",
244
+ http_client: httpx.AsyncClient | None = None,
245
+ ):
246
+ self._model_name = model_name
247
+ self.api_key = api_key
248
+ self._base_url = base_url.rstrip("/")
249
+ self._http_client = http_client
250
+ self._owns_client = http_client is None
251
+
252
+ @property
253
+ def model_name(self) -> str:
254
+ """Return the model name."""
255
+ return self._model_name
256
+
257
+ @property
258
+ def base_url(self) -> str:
259
+ """Return the base URL for the API."""
260
+ return self._base_url
261
+
262
+ @property
263
+ def system(self) -> str:
264
+ """Return the provider system identifier."""
265
+ return "google"
266
+
267
+ def _get_instructions(
268
+ self,
269
+ messages: list,
270
+ model_request_parameters,
271
+ ) -> str | None:
272
+ """Get additional instructions to prepend to system prompt.
273
+
274
+ This is a compatibility method for pydantic-ai interface.
275
+ Override in subclasses to inject custom instructions.
276
+ """
277
+ return None
278
+
279
+ def prepare_request(
280
+ self,
281
+ model_settings: ModelSettings | None,
282
+ model_request_parameters,
283
+ ) -> tuple:
284
+ """Prepare request by normalizing settings.
285
+
286
+ This is a compatibility method for pydantic-ai interface.
287
+ """
288
+ return model_settings, model_request_parameters
289
+
290
+ async def _get_client(self) -> httpx.AsyncClient:
291
+ """Get or create HTTP client."""
292
+ if self._http_client is None:
293
+ self._http_client = httpx.AsyncClient(timeout=180)
294
+ return self._http_client
295
+
296
+ async def _close_client(self) -> None:
297
+ """Close HTTP client if we own it."""
298
+ if self._owns_client and self._http_client is not None:
299
+ await self._http_client.aclose()
300
+ self._http_client = None
301
+
302
+ def _get_headers(self) -> dict[str, str]:
303
+ """Get HTTP headers for the request."""
304
+ return {
305
+ "Content-Type": "application/json",
306
+ "Accept": "application/json",
307
+ "x-goog-api-key": self.api_key,
308
+ }
309
+
310
+ async def _map_user_prompt(self, part: UserPromptPart) -> list[dict[str, Any]]:
311
+ """Map a user prompt part to Gemini format."""
312
+ parts = []
313
+
314
+ if isinstance(part.content, str):
315
+ parts.append({"text": part.content})
316
+ elif isinstance(part.content, list):
317
+ for item in part.content:
318
+ if isinstance(item, str):
319
+ parts.append({"text": item})
320
+ elif hasattr(item, "media_type") and hasattr(item, "data"):
321
+ # Handle file/image content
322
+ data = item.data
323
+ if isinstance(data, bytes):
324
+ data = base64.b64encode(data).decode("utf-8")
325
+ parts.append(
326
+ {
327
+ "inline_data": {
328
+ "mime_type": item.media_type,
329
+ "data": data,
330
+ }
331
+ }
332
+ )
333
+ else:
334
+ parts.append({"text": str(item)})
335
+ else:
336
+ parts.append({"text": str(part.content)})
337
+
338
+ return parts
339
+
340
+ async def _map_messages(
341
+ self,
342
+ messages: list[ModelMessage],
343
+ model_request_parameters: ModelRequestParameters,
344
+ ) -> tuple[dict[str, Any] | None, list[dict[str, Any]]]:
345
+ """Map pydantic-ai messages to Gemini API format."""
346
+ contents: list[dict[str, Any]] = []
347
+ system_parts: list[dict[str, Any]] = []
348
+
349
+ for m in messages:
350
+ if isinstance(m, ModelRequest):
351
+ message_parts: list[dict[str, Any]] = []
352
+
353
+ for part in m.parts:
354
+ if isinstance(part, SystemPromptPart):
355
+ system_parts.append({"text": part.content})
356
+ elif isinstance(part, UserPromptPart):
357
+ mapped_parts = await self._map_user_prompt(part)
358
+ message_parts.extend(mapped_parts)
359
+ elif isinstance(part, ToolReturnPart):
360
+ message_parts.append(
361
+ {
362
+ "function_response": {
363
+ "name": part.tool_name,
364
+ "response": part.model_response_object(),
365
+ "id": part.tool_call_id,
366
+ }
367
+ }
368
+ )
369
+ elif isinstance(part, RetryPromptPart):
370
+ if part.tool_name is None:
371
+ message_parts.append({"text": part.model_response()})
372
+ else:
373
+ message_parts.append(
374
+ {
375
+ "function_response": {
376
+ "name": part.tool_name,
377
+ "response": {"error": part.model_response()},
378
+ "id": part.tool_call_id,
379
+ }
380
+ }
381
+ )
382
+
383
+ if message_parts:
384
+ # Merge with previous user message if exists
385
+ if contents and contents[-1].get("role") == "user":
386
+ contents[-1]["parts"].extend(message_parts)
387
+ else:
388
+ contents.append({"role": "user", "parts": message_parts})
389
+
390
+ elif isinstance(m, ModelResponse):
391
+ model_parts = self._map_model_response(m)
392
+ if model_parts:
393
+ # Merge with previous model message if exists
394
+ if contents and contents[-1].get("role") == "model":
395
+ contents[-1]["parts"].extend(model_parts["parts"])
396
+ else:
397
+ contents.append(model_parts)
398
+
399
+ # Ensure at least one content
400
+ if not contents:
401
+ contents = [{"role": "user", "parts": [{"text": ""}]}]
402
+
403
+ # Get any injected instructions
404
+ instructions = self._get_instructions(messages, model_request_parameters)
405
+ if instructions:
406
+ system_parts.insert(0, {"text": instructions})
407
+
408
+ # Build system instruction
409
+ system_instruction = None
410
+ if system_parts:
411
+ system_instruction = {"role": "user", "parts": system_parts}
412
+
413
+ return system_instruction, contents
414
+
415
+ def _map_model_response(self, m: ModelResponse) -> dict[str, Any] | None:
416
+ """Map a ModelResponse to Gemini content format.
417
+
418
+ For Gemini thinking models, we need to track thought signatures from
419
+ ThinkingParts and apply them to subsequent function_call parts.
420
+ """
421
+ parts: list[dict[str, Any]] = []
422
+ pending_signature: str | None = None
423
+
424
+ for item in m.parts:
425
+ if isinstance(item, ToolCallPart):
426
+ part_dict: dict[str, Any] = {
427
+ "function_call": {
428
+ "name": item.tool_name,
429
+ "args": item.args_as_dict(),
430
+ "id": item.tool_call_id,
431
+ }
432
+ }
433
+ # Gemini thinking models REQUIRE thoughtSignature on function calls
434
+ # Use pending signature from thinking or bypass signature
435
+ part_dict["thoughtSignature"] = (
436
+ pending_signature
437
+ if pending_signature is not None
438
+ else BYPASS_THOUGHT_SIGNATURE
439
+ )
440
+ parts.append(part_dict)
441
+ elif isinstance(item, TextPart):
442
+ part_dict = {"text": item.content}
443
+ # Apply pending signature to text parts too if present
444
+ if pending_signature is not None:
445
+ part_dict["thoughtSignature"] = pending_signature
446
+ pending_signature = None
447
+ parts.append(part_dict)
448
+ elif isinstance(item, ThinkingPart):
449
+ if item.content:
450
+ part_dict = {"text": item.content, "thought": True}
451
+ if item.signature:
452
+ part_dict["thoughtSignature"] = item.signature
453
+ # Store signature for subsequent parts
454
+ pending_signature = item.signature
455
+ else:
456
+ # No signature on thinking part, use bypass
457
+ pending_signature = BYPASS_THOUGHT_SIGNATURE
458
+ parts.append(part_dict)
459
+
460
+ if not parts:
461
+ return None
462
+ return {"role": "model", "parts": parts}
463
+
464
+ def _build_tools(self, tools: list[ToolDefinition]) -> list[dict[str, Any]]:
465
+ """Build tool definitions for the API."""
466
+ function_declarations = []
467
+
468
+ for tool in tools:
469
+ func_decl: dict[str, Any] = {
470
+ "name": tool.name,
471
+ "description": tool.description or "",
472
+ }
473
+ if tool.parameters_json_schema:
474
+ # Sanitize schema for Gemini compatibility
475
+ func_decl["parameters"] = _sanitize_schema_for_gemini(
476
+ tool.parameters_json_schema
477
+ )
478
+ function_declarations.append(func_decl)
479
+
480
+ return [{"functionDeclarations": function_declarations}]
481
+
482
+ def _build_generation_config(
483
+ self, model_settings: ModelSettings | None
484
+ ) -> dict[str, Any]:
485
+ """Build generation config from model settings."""
486
+ config: dict[str, Any] = {}
487
+
488
+ if model_settings:
489
+ # ModelSettings is a TypedDict, so use .get() for all access
490
+ temperature = model_settings.get("temperature")
491
+ if temperature is not None:
492
+ config["temperature"] = temperature
493
+
494
+ top_p = model_settings.get("top_p")
495
+ if top_p is not None:
496
+ config["topP"] = top_p
497
+
498
+ max_tokens = model_settings.get("max_tokens")
499
+ if max_tokens is not None:
500
+ config["maxOutputTokens"] = max_tokens
501
+
502
+ # Handle Gemini 3 Pro thinking settings
503
+ thinking_enabled = model_settings.get("thinking_enabled")
504
+ thinking_level = model_settings.get("thinking_level")
505
+
506
+ # Build thinkingConfig if thinking settings are present
507
+ if thinking_enabled is False:
508
+ # Disable thinking by not including thinkingConfig
509
+ pass
510
+ elif thinking_level is not None:
511
+ # Gemini 3 Pro uses thinkingLevel with values "low" or "high"
512
+ # includeThoughts=True is required to surface the thinking in the response
513
+ config["thinkingConfig"] = {
514
+ "thinkingLevel": thinking_level,
515
+ "includeThoughts": True,
516
+ }
517
+
518
+ return config
519
+
520
+ async def request(
521
+ self,
522
+ messages: list[ModelMessage],
523
+ model_settings: ModelSettings | None,
524
+ model_request_parameters: ModelRequestParameters,
525
+ ) -> ModelResponse:
526
+ """Make a non-streaming request to the Gemini API."""
527
+ system_instruction, contents = await self._map_messages(
528
+ messages, model_request_parameters
529
+ )
530
+
531
+ # Build request body
532
+ body: dict[str, Any] = {"contents": contents}
533
+
534
+ gen_config = self._build_generation_config(model_settings)
535
+ if gen_config:
536
+ body["generationConfig"] = gen_config
537
+ if system_instruction:
538
+ body["systemInstruction"] = system_instruction
539
+
540
+ # Add tools
541
+ if model_request_parameters.function_tools:
542
+ body["tools"] = self._build_tools(model_request_parameters.function_tools)
543
+
544
+ # Make request
545
+ client = await self._get_client()
546
+ url = f"{self._base_url}/models/{self._model_name}:generateContent"
547
+ headers = self._get_headers()
548
+
549
+ response = await client.post(url, json=body, headers=headers)
550
+
551
+ if response.status_code != 200:
552
+ raise RuntimeError(
553
+ f"Gemini API error {response.status_code}: {response.text}"
554
+ )
555
+
556
+ data = response.json()
557
+ return self._parse_response(data)
558
+
559
+ def _parse_response(self, data: dict[str, Any]) -> ModelResponse:
560
+ """Parse the Gemini API response."""
561
+ candidates = data.get("candidates", [])
562
+ if not candidates:
563
+ return ModelResponse(
564
+ parts=[TextPart(content="")],
565
+ model_name=self._model_name,
566
+ usage=RequestUsage(),
567
+ )
568
+
569
+ candidate = candidates[0]
570
+ content = candidate.get("content", {})
571
+ parts = content.get("parts", [])
572
+
573
+ response_parts: list[ModelResponsePart] = []
574
+
575
+ for part in parts:
576
+ if part.get("thought") and part.get("text") is not None:
577
+ # Thinking part
578
+ signature = part.get("thoughtSignature")
579
+ response_parts.append(
580
+ ThinkingPart(content=part["text"], signature=signature)
581
+ )
582
+ elif "text" in part:
583
+ response_parts.append(TextPart(content=part["text"]))
584
+ elif "functionCall" in part:
585
+ fc = part["functionCall"]
586
+ response_parts.append(
587
+ ToolCallPart(
588
+ tool_name=fc["name"],
589
+ args=fc.get("args", {}),
590
+ tool_call_id=fc.get("id") or generate_tool_call_id(),
591
+ )
592
+ )
593
+
594
+ # Extract usage
595
+ usage_meta = data.get("usageMetadata", {})
596
+ usage = RequestUsage(
597
+ input_tokens=usage_meta.get("promptTokenCount", 0),
598
+ output_tokens=usage_meta.get("candidatesTokenCount", 0),
599
+ )
600
+
601
+ return ModelResponse(
602
+ parts=response_parts,
603
+ model_name=self._model_name,
604
+ usage=usage,
605
+ provider_response_id=data.get("requestId"),
606
+ provider_name=self.system,
607
+ )
608
+
609
+ @asynccontextmanager
610
+ async def request_stream(
611
+ self,
612
+ messages: list[ModelMessage],
613
+ model_settings: ModelSettings | None,
614
+ model_request_parameters: ModelRequestParameters,
615
+ run_context: RunContext[Any] | None = None,
616
+ ) -> AsyncIterator[StreamedResponse]:
617
+ """Make a streaming request to the Gemini API."""
618
+ system_instruction, contents = await self._map_messages(
619
+ messages, model_request_parameters
620
+ )
621
+
622
+ # Build request body
623
+ body: dict[str, Any] = {"contents": contents}
624
+
625
+ gen_config = self._build_generation_config(model_settings)
626
+ if gen_config:
627
+ body["generationConfig"] = gen_config
628
+ if system_instruction:
629
+ body["systemInstruction"] = system_instruction
630
+
631
+ # Add tools
632
+ if model_request_parameters.function_tools:
633
+ body["tools"] = self._build_tools(model_request_parameters.function_tools)
634
+ body["toolConfig"] = {
635
+ "functionCallingConfig": {
636
+ "mode": "AUTO",
637
+ "streamFunctionCallArguments": True,
638
+ }
639
+ }
640
+
641
+ # Make streaming request
642
+ client = await self._get_client()
643
+ url = (
644
+ f"{self._base_url}/models/{self._model_name}:streamGenerateContent?alt=sse"
645
+ )
646
+ headers = self._get_headers()
647
+
648
+ async def stream_chunks() -> AsyncIterator[dict[str, Any]]:
649
+ async with client.stream(
650
+ "POST", url, json=body, headers=headers
651
+ ) as response:
652
+ if response.status_code != 200:
653
+ text = await response.aread()
654
+ raise RuntimeError(
655
+ f"Gemini API error {response.status_code}: {text.decode()}"
656
+ )
657
+
658
+ async for line in response.aiter_lines():
659
+ line = line.strip()
660
+ if not line:
661
+ continue
662
+ if line.startswith("data: "):
663
+ json_str = line[6:]
664
+ if json_str:
665
+ try:
666
+ yield json.loads(json_str)
667
+ except json.JSONDecodeError:
668
+ continue
669
+
670
+ yield GeminiStreamingResponse(
671
+ model_request_parameters=model_request_parameters,
672
+ _chunks=stream_chunks(),
673
+ _model_name_str=self._model_name,
674
+ _provider_name_str=self.system,
675
+ _provider_url_str=self._base_url,
676
+ )
677
+
678
+
679
+ _MISSING = object()
680
+
681
+
682
+ def _extract_partial_value(p_arg: dict) -> Any:
683
+ for key in [
684
+ "stringValue",
685
+ "numberValue",
686
+ "boolValue",
687
+ "nullValue",
688
+ "structValue",
689
+ "listValue",
690
+ ]:
691
+ if key in p_arg:
692
+ val = p_arg[key]
693
+ if key == "nullValue":
694
+ return None
695
+ return val
696
+ return _MISSING
697
+
698
+
699
+ def _apply_json_path(target: dict, path: str, value: Any):
700
+ parts = path.split(".")
701
+ curr = target
702
+ for i, part in enumerate(parts):
703
+ if "[" in part and part.endswith("]"):
704
+ key, idx_str = part.split("[")
705
+ idx = int(idx_str[:-1])
706
+ if key not in curr:
707
+ curr[key] = []
708
+ while len(curr[key]) <= idx:
709
+ curr[key].append(None)
710
+
711
+ if i == len(parts) - 1:
712
+ curr[key][idx] = value
713
+ else:
714
+ if curr[key][idx] is None:
715
+ curr[key][idx] = {}
716
+ curr = curr[key][idx]
717
+ else:
718
+ if i == len(parts) - 1:
719
+ curr[part] = value
720
+ else:
721
+ if part not in curr or curr[part] is None:
722
+ curr[part] = {}
723
+ curr = curr[part]
724
+
725
+
726
+ @dataclass
727
+ class GeminiStreamingResponse(StreamedResponse):
728
+ """Streaming response handler for Gemini API."""
729
+
730
+ _chunks: AsyncIterator[dict[str, Any]]
731
+ _model_name_str: str
732
+ _provider_name_str: str = "google"
733
+ _provider_url_str: str | None = None
734
+ _timestamp_val: datetime = field(default_factory=lambda: datetime.now(UTC))
735
+ _current_tool_call_id: str | None = None
736
+ _current_tool_name: str | None = None
737
+ _current_vendor_part_id: uuid.UUID | None = None
738
+ _current_args: dict[str, Any] = field(default_factory=dict)
739
+
740
+ async def _get_event_iterator(self) -> AsyncIterator[ModelResponseStreamEvent]:
741
+ """Process streaming chunks and yield events."""
742
+ async for chunk in self._chunks:
743
+ # Extract usage
744
+ usage_meta = chunk.get("usageMetadata", {})
745
+ if usage_meta:
746
+ self._usage = RequestUsage(
747
+ input_tokens=usage_meta.get("promptTokenCount", 0),
748
+ output_tokens=usage_meta.get("candidatesTokenCount", 0),
749
+ )
750
+
751
+ # Extract response ID
752
+ if chunk.get("responseId"):
753
+ self.provider_response_id = chunk["responseId"]
754
+
755
+ candidates = chunk.get("candidates", [])
756
+ if not candidates:
757
+ continue
758
+
759
+ candidate = candidates[0]
760
+ content = candidate.get("content", {})
761
+ parts = content.get("parts", [])
762
+
763
+ for part in parts:
764
+ # Handle thinking part
765
+ if part.get("thought") and part.get("text") is not None:
766
+ for event in self._parts_manager.handle_thinking_delta(
767
+ vendor_part_id=None,
768
+ content=part["text"],
769
+ ):
770
+ yield event
771
+
772
+ # Handle regular text
773
+ elif part.get("text") is not None and not part.get("thought"):
774
+ text = part["text"]
775
+ if len(text) == 0:
776
+ continue
777
+ for event in self._parts_manager.handle_text_delta(
778
+ vendor_part_id=None,
779
+ content=text,
780
+ ):
781
+ yield event
782
+
783
+ # Handle function call
784
+ elif part.get("functionCall"):
785
+ fc = part["functionCall"]
786
+
787
+ # Check if it's a new function call
788
+ if fc.get("name"):
789
+ self._current_tool_name = fc["name"]
790
+ self._current_tool_call_id = (
791
+ fc.get("id") or generate_tool_call_id()
792
+ )
793
+ self._current_vendor_part_id = uuid.uuid4()
794
+ self._current_args = {}
795
+
796
+ delta_args = {}
797
+ # Handle partial arguments if present
798
+ if "partialArgs" in fc:
799
+ for p_arg in fc["partialArgs"]:
800
+ json_path = p_arg.get("jsonPath")
801
+ if json_path and json_path.startswith("$."):
802
+ value = _extract_partial_value(p_arg)
803
+ if value is not _MISSING:
804
+ _apply_json_path(
805
+ self._current_args, json_path[2:], value
806
+ )
807
+ _apply_json_path(delta_args, json_path[2:], value)
808
+
809
+ elif "args" in fc:
810
+ delta_args = fc["args"]
811
+ self._current_args.update(fc["args"])
812
+
813
+ # Yield delta event if we have a current part ID
814
+ if self._current_vendor_part_id:
815
+ event = self._parts_manager.handle_tool_call_delta(
816
+ vendor_part_id=self._current_vendor_part_id,
817
+ tool_name=self._current_tool_name,
818
+ args=delta_args,
819
+ tool_call_id=self._current_tool_call_id,
820
+ )
821
+ if event is not None:
822
+ yield event
823
+
824
+ @property
825
+ def model_name(self) -> str:
826
+ return self._model_name_str
827
+
828
+ @property
829
+ def provider_name(self) -> str | None:
830
+ return self._provider_name_str
831
+
832
+ @property
833
+ def provider_url(self) -> str | None:
834
+ return self._provider_url_str
835
+
836
+ @property
837
+ def timestamp(self) -> datetime:
838
+ return self._timestamp_val