sovara 0.2.4__tar.gz → 0.2.5__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.
- {sovara-0.2.4 → sovara-0.2.5}/PKG-INFO +2 -1
- {sovara-0.2.4 → sovara-0.2.5}/pyproject.toml +3 -2
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/runner/context_manager.py +10 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/runner/monkey_patching/api_parser.py +25 -27
- sovara-0.2.5/src/sovara/runner/monkey_patching/api_parsers/claude_code_messages_api_parser.py +47 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/runner/monkey_patching/edit_io_filters.py +14 -0
- sovara-0.2.5/src/sovara/runner/monkey_patching/patches/claude_sdk_patch.py +181 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/runner/monkey_patching/patching_utils.py +46 -10
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/runner/node_execution.py +28 -5
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/runner/runtime_client.py +56 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/app.py +2 -0
- sovara-0.2.5/src/sovara/server/claude_proxy_utils.py +277 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/database/llm_calls.py +26 -8
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/handlers/runner_handlers.py +2 -0
- sovara-0.2.5/src/sovara/server/routes/claude_proxy.py +426 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/routes/internal.py +21 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/services/trace_chat_service.py +3 -7
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/state.py +52 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara.egg-info/PKG-INFO +2 -1
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara.egg-info/SOURCES.txt +3 -2
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara.egg-info/requires.txt +1 -0
- sovara-0.2.4/src/sovara/runner/monkey_patching/api_parsers/claude_sdk_api_parser.py +0 -83
- sovara-0.2.4/src/sovara/runner/monkey_patching/api_parsers/langchain_tool_api_parser.py +0 -33
- sovara-0.2.4/src/sovara/runner/monkey_patching/patches/claude_sdk_patch.py +0 -335
- {sovara-0.2.4 → sovara-0.2.5}/LICENSE +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/README.md +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/docs/release/PYPI_DESC.md +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/setup.cfg +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/__init__.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/assets/SKILL.md +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/assets/__init__.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/assets/web_app/assets/AttachmentPreview-CFrNMdfC.css +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/assets/web_app/assets/AttachmentPreview-h01XSt_Q.js +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/assets/web_app/assets/DocumentPreviewModal-B9FumoYT.js +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/assets/web_app/assets/LayoutBreadcrumb-D8Jj9PNg.js +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/assets/web_app/assets/LayoutBreadcrumb-DopPRSia.css +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/assets/web_app/assets/OrgRoute-BcO5LcH1.js +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/assets/web_app/assets/OrgRoute-DIln18S1.css +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/assets/web_app/assets/PriorsRoute-BSsFm0pQ.css +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/assets/web_app/assets/PriorsRoute-CaMBZzP_.js +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/assets/web_app/assets/ProjectRoute-BCA5W9t8.js +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/assets/web_app/assets/ProjectRoute-DtQqJb1n.css +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/assets/web_app/assets/RunRoute-BWs7EUiD.css +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/assets/web_app/assets/RunRoute-rUSpBhsI.js +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/assets/web_app/assets/SettingsRoute-BtETGx6c.js +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/assets/web_app/assets/SettingsRoute-C4VU70zq.css +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/assets/web_app/assets/api-1mbBSn7v.css +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/assets/web_app/assets/api-tVJBLHUH.js +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/assets/web_app/assets/arrow_lr-ByI2ndPB.png +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/assets/web_app/assets/arrow_spiral_tr_bl-XHIpf338.png +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/assets/web_app/assets/codicon-BYm2YbZ6.ttf +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/assets/web_app/assets/eye-DljehhmM.js +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/assets/web_app/assets/index-BygN1cJK.js +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/assets/web_app/assets/index-veNT10PX.css +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/assets/web_app/assets/pdf.worker.min-qwK7q_zL.mjs +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/assets/web_app/assets/plus-CkqVCne8.js +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/assets/web_app/assets/prismTheme-Bx8cYzkm.js +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/assets/web_app/assets/sovara_wordmark-CjkpVR0a.png +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/assets/web_app/assets/tierState-Bq39H0kz.js +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/assets/web_app/assets/trash-2-DYeReTGL.js +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/assets/web_app/favicon.png +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/assets/web_app/index.html +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/assets/web_app/marketplace_icon.png +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/cli/__init__.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/cli/so_config.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/cli/so_record.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/cli/so_server.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/cli/so_tool.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/common/__init__.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/common/config.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/common/constants.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/common/custom_metrics.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/common/lifecycle.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/common/log_redaction.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/common/logger.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/common/project.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/common/runtime.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/common/user.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/common/utils.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/runner/__init__.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/runner/agent_runner.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/runner/monkey_patching/__init__.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/runner/monkey_patching/api_parsers/__init__.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/runner/monkey_patching/api_parsers/aiohttp_api_parser.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/runner/monkey_patching/api_parsers/httpx_api_parser.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/runner/monkey_patching/api_parsers/mcp_api_parser.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/runner/monkey_patching/api_parsers/requests_api_parser.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/runner/monkey_patching/api_parsers/sse_utils.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/runner/monkey_patching/api_parsers/urllib3_api_parser.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/runner/monkey_patching/apply_monkey_patches.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/runner/monkey_patching/async_streaming.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/runner/monkey_patching/endpoint_whitelist.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/runner/monkey_patching/node_naming.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/runner/monkey_patching/patches/__init__.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/runner/monkey_patching/patches/aiohttp_patch.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/runner/monkey_patching/patches/httpx_patch.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/runner/monkey_patching/patches/langchain_patch.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/runner/monkey_patching/patches/mcp_patches.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/runner/monkey_patching/patches/randomness_patch.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/runner/monkey_patching/patches/requests_patch.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/runner/monkey_patching/patches/urllib3_patch.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/runner/priors.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/runner/priors_pipeline.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/runner/run_log_capture.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/runner/string_matching.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/__init__.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/database/__init__.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/database/_shared.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/database/manager.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/database/priors.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/database/projects.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/database/runs.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/database/sqlite/__init__.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/database/sqlite/connection.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/database/sqlite/llm_calls.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/database/sqlite/priors.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/database/sqlite/projects.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/database/sqlite/runs.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/database/sqlite/schema.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/database/sqlite/users.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/database/users.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/graph_analysis/inference_server.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/graph_analysis/trace_chat/__init__.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/graph_analysis/trace_chat/cancel.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/graph_analysis/trace_chat/main.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/graph_analysis/trace_chat/tools/__init__.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/graph_analysis/trace_chat/tools/ask_step.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/graph_analysis/trace_chat/tools/edit_content.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/graph_analysis/trace_chat/tools/get_step_overview.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/graph_analysis/trace_chat/tools/get_step_snapshot.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/graph_analysis/trace_chat/tools/get_trace_overview.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/graph_analysis/trace_chat/tools/search.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/graph_analysis/trace_chat/tools/summarize_trace.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/graph_analysis/trace_chat/tools/verify.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/graph_analysis/trace_chat/utils/__init__.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/graph_analysis/trace_chat/utils/content_utils.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/graph_analysis/trace_chat/utils/context.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/graph_analysis/trace_chat/utils/edit_persist.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/graph_analysis/trace_chat/utils/step_content_view.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/graph_analysis/trace_chat/utils/step_ids.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/graph_analysis/trace_chat/utils/trace.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/graph_analysis/trace_chat/utils/verdict_persist.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/graph_models.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/handlers/__init__.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/handlers/cli_handlers.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/handlers/handler_utils.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/handlers/ui_handlers.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/llm_backend.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/llm_provider_catalog.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/llm_settings.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/llm_settings_verification.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/package_version.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/prior_display.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/priors_backend/__init__.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/priors_backend/constants.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/priors_backend/events.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/priors_backend/llm/__init__.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/priors_backend/llm/lesson_restructurer.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/priors_backend/llm/lesson_retriever.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/priors_backend/llm/lesson_summarizer.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/priors_backend/llm/lesson_validator.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/priors_backend/llm_client.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/priors_backend/logger.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/priors_backend/routes.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/priors_backend/storage.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/priors_scope.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/routes/__init__.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/routes/events.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/routes/runner.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/routes/ui.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/routes/ui_common.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/routes/ui_projects.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/routes/ui_runs.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/routes/ui_trace_chat.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/services/__init__.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/third_party_model_catalog.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara/server/web_app_assets.py +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara.egg-info/dependency_links.txt +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara.egg-info/entry_points.txt +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/src/sovara.egg-info/top_level.txt +0 -0
- {sovara-0.2.4 → sovara-0.2.5}/tests/test_graph_payloads.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sovara
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.5
|
|
4
4
|
Summary: A development tool for LLM call visualization and debugging
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -43,6 +43,7 @@ Requires-Dist: openai; extra == "examples"
|
|
|
43
43
|
Requires-Dist: anthropic; extra == "examples"
|
|
44
44
|
Requires-Dist: openai-agents; extra == "examples"
|
|
45
45
|
Requires-Dist: google-genai; extra == "examples"
|
|
46
|
+
Requires-Dist: claude-agent-sdk; extra == "examples"
|
|
46
47
|
Provides-Extra: docs
|
|
47
48
|
Requires-Dist: mkdocs-material>=9.5; extra == "docs"
|
|
48
49
|
Requires-Dist: mkdocs-minify-plugin; extra == "docs"
|
|
@@ -26,7 +26,7 @@ line-length = 100
|
|
|
26
26
|
|
|
27
27
|
[project]
|
|
28
28
|
name = "sovara"
|
|
29
|
-
version = "0.2.
|
|
29
|
+
version = "0.2.5"
|
|
30
30
|
description = "A development tool for LLM call visualization and debugging"
|
|
31
31
|
readme = "docs/release/PYPI_DESC.md"
|
|
32
32
|
requires-python = ">=3.10"
|
|
@@ -76,7 +76,8 @@ examples = [
|
|
|
76
76
|
"openai",
|
|
77
77
|
"anthropic",
|
|
78
78
|
"openai-agents",
|
|
79
|
-
"google-genai"
|
|
79
|
+
"google-genai",
|
|
80
|
+
"claude-agent-sdk"
|
|
80
81
|
]
|
|
81
82
|
|
|
82
83
|
docs = [
|
|
@@ -97,6 +97,16 @@ def sovara_launch(run_name="Workflow run"):
|
|
|
97
97
|
clear_run_timer(run_id)
|
|
98
98
|
|
|
99
99
|
|
|
100
|
+
@contextmanager
|
|
101
|
+
def bind_run_id(run_id: str | None):
|
|
102
|
+
"""Temporarily bind the active run id inside the current context."""
|
|
103
|
+
token = current_run_id.set(run_id)
|
|
104
|
+
try:
|
|
105
|
+
yield
|
|
106
|
+
finally:
|
|
107
|
+
current_run_id.reset(token)
|
|
108
|
+
|
|
109
|
+
|
|
100
110
|
def log(**metrics):
|
|
101
111
|
run_id = warn_if_inactive_run("log()")
|
|
102
112
|
if run_id is None:
|
|
@@ -27,19 +27,12 @@ from sovara.runner.monkey_patching.api_parsers.requests_api_parser import (
|
|
|
27
27
|
json_str_to_api_obj_requests,
|
|
28
28
|
json_str_to_original_inp_dict_requests,
|
|
29
29
|
)
|
|
30
|
-
from sovara.runner.monkey_patching.api_parsers.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
json_str_to_original_inp_dict_claude_sdk,
|
|
37
|
-
)
|
|
38
|
-
from sovara.runner.monkey_patching.api_parsers.langchain_tool_api_parser import (
|
|
39
|
-
func_kwargs_to_json_str_langchain_tool,
|
|
40
|
-
api_obj_to_json_str_langchain_tool,
|
|
41
|
-
json_str_to_api_obj_langchain_tool,
|
|
42
|
-
json_str_to_original_inp_dict_langchain_tool,
|
|
30
|
+
from sovara.runner.monkey_patching.api_parsers.claude_code_messages_api_parser import (
|
|
31
|
+
build_to_show_claude_code_messages_output,
|
|
32
|
+
func_kwargs_to_json_str_claude_code_messages,
|
|
33
|
+
api_obj_to_json_str_claude_code_messages,
|
|
34
|
+
json_str_to_api_obj_claude_code_messages,
|
|
35
|
+
json_str_to_original_inp_dict_claude_code_messages,
|
|
43
36
|
)
|
|
44
37
|
from sovara.runner.monkey_patching.api_parsers.urllib3_api_parser import (
|
|
45
38
|
func_kwargs_to_json_str_urllib3,
|
|
@@ -235,9 +228,11 @@ def func_kwargs_to_json_str(input_dict: Dict[str, Any], api_type: str) -> str:
|
|
|
235
228
|
elif api_type == "MCP.ClientSession.send_request":
|
|
236
229
|
complete_json_str = func_kwargs_to_json_str_mcp(input_dict)
|
|
237
230
|
elif api_type == "claude_agent_sdk.parse_message":
|
|
238
|
-
complete_json_str =
|
|
231
|
+
complete_json_str = json.dumps(input_dict)
|
|
232
|
+
elif api_type == "claude_code.messages":
|
|
233
|
+
complete_json_str = func_kwargs_to_json_str_claude_code_messages(input_dict)
|
|
239
234
|
elif api_type == "langchain_core.tools.BaseTool.run":
|
|
240
|
-
complete_json_str =
|
|
235
|
+
complete_json_str = json.dumps(input_dict)
|
|
241
236
|
elif api_type == "urllib3.HTTPConnectionPool.urlopen":
|
|
242
237
|
complete_json_str = func_kwargs_to_json_str_urllib3(input_dict)
|
|
243
238
|
else:
|
|
@@ -247,10 +242,7 @@ def func_kwargs_to_json_str(input_dict: Dict[str, Any], api_type: str) -> str:
|
|
|
247
242
|
raw_dict = json.loads(complete_json_str)
|
|
248
243
|
|
|
249
244
|
# Filter the dict to create the display version
|
|
250
|
-
|
|
251
|
-
to_show_dict = build_to_show_claude_sdk_input(raw_dict)
|
|
252
|
-
else:
|
|
253
|
-
to_show_dict = filter_dict(raw_dict)
|
|
245
|
+
to_show_dict = filter_dict(raw_dict)
|
|
254
246
|
|
|
255
247
|
# Construct the wrapped format
|
|
256
248
|
complete_dict = {"raw": raw_dict, "to_show": to_show_dict}
|
|
@@ -294,9 +286,11 @@ def json_str_to_original_inp_dict(json_str: str, input_dict: dict, api_type: str
|
|
|
294
286
|
elif api_type == "MCP.ClientSession.send_request":
|
|
295
287
|
return json_str_to_original_inp_dict_mcp(merged_json_str, input_dict)
|
|
296
288
|
elif api_type == "claude_agent_sdk.parse_message":
|
|
297
|
-
return
|
|
289
|
+
return json.loads(merged_json_str)
|
|
290
|
+
elif api_type == "claude_code.messages":
|
|
291
|
+
return json_str_to_original_inp_dict_claude_code_messages(merged_json_str, input_dict)
|
|
298
292
|
elif api_type == "langchain_core.tools.BaseTool.run":
|
|
299
|
-
return
|
|
293
|
+
return json.loads(merged_json_str)
|
|
300
294
|
elif api_type == "urllib3.HTTPConnectionPool.urlopen":
|
|
301
295
|
return json_str_to_original_inp_dict_urllib3(merged_json_str, input_dict)
|
|
302
296
|
else:
|
|
@@ -342,9 +336,11 @@ def api_obj_to_json_str(response_obj: Any, api_type: str) -> str:
|
|
|
342
336
|
elif api_type == "MCP.ClientSession.send_request":
|
|
343
337
|
complete_json_str = api_obj_to_json_str_mcp(response_obj)
|
|
344
338
|
elif api_type == "claude_agent_sdk.parse_message":
|
|
345
|
-
complete_json_str =
|
|
339
|
+
complete_json_str = json.dumps(response_obj)
|
|
340
|
+
elif api_type == "claude_code.messages":
|
|
341
|
+
complete_json_str = api_obj_to_json_str_claude_code_messages(response_obj)
|
|
346
342
|
elif api_type == "langchain_core.tools.BaseTool.run":
|
|
347
|
-
complete_json_str =
|
|
343
|
+
complete_json_str = json.dumps(response_obj)
|
|
348
344
|
elif api_type == "urllib3.HTTPConnectionPool.urlopen":
|
|
349
345
|
complete_json_str = api_obj_to_json_str_urllib3(response_obj)
|
|
350
346
|
else:
|
|
@@ -353,8 +349,8 @@ def api_obj_to_json_str(response_obj: Any, api_type: str) -> str:
|
|
|
353
349
|
# Parse the JSON string to get the raw dict
|
|
354
350
|
raw_dict = json.loads(complete_json_str)
|
|
355
351
|
# Filter the content dict
|
|
356
|
-
if api_type == "
|
|
357
|
-
to_show_dict =
|
|
352
|
+
if api_type == "claude_code.messages":
|
|
353
|
+
to_show_dict = build_to_show_claude_code_messages_output(raw_dict)
|
|
358
354
|
else:
|
|
359
355
|
to_show_dict = filter_dict(raw_dict)
|
|
360
356
|
# Create to_show with filtered content
|
|
@@ -394,9 +390,11 @@ def json_str_to_api_obj(new_output_text: str, api_type: str) -> Any:
|
|
|
394
390
|
elif api_type == "MCP.ClientSession.send_request":
|
|
395
391
|
return json_str_to_api_obj_mcp(merged_json_str)
|
|
396
392
|
elif api_type == "claude_agent_sdk.parse_message":
|
|
397
|
-
return
|
|
393
|
+
return json.loads(merged_json_str)
|
|
394
|
+
elif api_type == "claude_code.messages":
|
|
395
|
+
return json_str_to_api_obj_claude_code_messages(merged_json_str)
|
|
398
396
|
elif api_type == "langchain_core.tools.BaseTool.run":
|
|
399
|
-
return
|
|
397
|
+
return json.loads(merged_json_str)
|
|
400
398
|
elif api_type == "urllib3.HTTPConnectionPool.urlopen":
|
|
401
399
|
return json_str_to_api_obj_urllib3(merged_json_str)
|
|
402
400
|
else:
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""API parser helpers for ``claude_code.messages`` proxy calls."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
def build_to_show_claude_code_messages_input(raw_input: dict[str, Any]) -> dict[str, Any]:
|
|
9
|
+
body = raw_input.get("body")
|
|
10
|
+
if body is None:
|
|
11
|
+
return {}
|
|
12
|
+
return {"body": body}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def build_to_show_claude_code_messages_output(raw_output: dict[str, Any]) -> dict[str, Any]:
|
|
16
|
+
visible_output = {
|
|
17
|
+
key: value
|
|
18
|
+
for key, value in raw_output.items()
|
|
19
|
+
if not str(key).startswith("_")
|
|
20
|
+
}
|
|
21
|
+
from sovara.runner.monkey_patching.api_parser import filter_dict
|
|
22
|
+
|
|
23
|
+
return filter_dict(visible_output)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def func_kwargs_to_json_str_claude_code_messages(input_dict: dict[str, Any]) -> str:
|
|
27
|
+
return json.dumps({
|
|
28
|
+
"url": input_dict.get("url", ""),
|
|
29
|
+
"body": input_dict.get("body"),
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def api_obj_to_json_str_claude_code_messages(obj: Any) -> str:
|
|
34
|
+
if not isinstance(obj, dict):
|
|
35
|
+
obj = {"value": obj}
|
|
36
|
+
return json.dumps(obj)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def json_str_to_api_obj_claude_code_messages(json_str: str) -> Any:
|
|
40
|
+
return json.loads(json_str)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def json_str_to_original_inp_dict_claude_code_messages(json_str: str, input_dict: dict) -> dict:
|
|
44
|
+
payload = json.loads(json_str)
|
|
45
|
+
input_dict["url"] = payload.get("url", input_dict.get("url", ""))
|
|
46
|
+
input_dict["body"] = payload.get("body")
|
|
47
|
+
return input_dict
|
|
@@ -9,6 +9,16 @@ COMPILED_EDIT_IO_EXCLUDE_PATTERNS = [
|
|
|
9
9
|
re.compile(r"^max_tokens$"),
|
|
10
10
|
re.compile(r"^stream$"),
|
|
11
11
|
re.compile(r"^temperature$"),
|
|
12
|
+
re.compile(r"^metadata\.user_id$"),
|
|
13
|
+
re.compile(r"^thinking(\.|$)"),
|
|
14
|
+
re.compile(r"^context_management(\.|$)"),
|
|
15
|
+
re.compile(r"^output_config\.(?!effort$).+"),
|
|
16
|
+
re.compile(r"^body\.metadata\.user_id$"),
|
|
17
|
+
re.compile(r"^body\.thinking(\.|$)"),
|
|
18
|
+
re.compile(r"^body\.context_management(\.|$)"),
|
|
19
|
+
re.compile(r"^body\.output_config\.(?!effort$).+"),
|
|
20
|
+
re.compile(r"^body\.stream$"),
|
|
21
|
+
re.compile(r"^body\.messages\.\d+\.content\.\d+\.signature$"),
|
|
12
22
|
# Bedrock request tuning fields
|
|
13
23
|
re.compile(r"^body\.inferenceConfig\.(maxTokens|temperature)$"),
|
|
14
24
|
# content.* fields
|
|
@@ -30,7 +40,10 @@ COMPILED_EDIT_IO_EXCLUDE_PATTERNS = [
|
|
|
30
40
|
re.compile(r"^content\.max_output_tokens$"),
|
|
31
41
|
re.compile(r"^content\.max_tool_calls$"),
|
|
32
42
|
re.compile(r"^content\.createTime$"),
|
|
43
|
+
re.compile(r"^content\.frequency_penalty$"),
|
|
44
|
+
re.compile(r"^content\.incomplete_details(\.|$)"),
|
|
33
45
|
re.compile(r"^content\.parallel_tool_calls$"),
|
|
46
|
+
re.compile(r"^content\.presence_penalty$"),
|
|
34
47
|
re.compile(r"^content\.previous_response_id$"),
|
|
35
48
|
re.compile(r"^content\.prompt_cache"),
|
|
36
49
|
re.compile(r"^content\.reasoning\.(effort|summary)$"),
|
|
@@ -46,6 +59,7 @@ COMPILED_EDIT_IO_EXCLUDE_PATTERNS = [
|
|
|
46
59
|
re.compile(r"^content\.responseId$"),
|
|
47
60
|
re.compile(r"^content\.metrics(\.|$)"),
|
|
48
61
|
re.compile(r"^content\.stopReason$"),
|
|
62
|
+
re.compile(r"^content\.\d+\.signature$"),
|
|
49
63
|
# content.content.* fields
|
|
50
64
|
re.compile(r"^content\.content\.\d+\.(type|id|signature)$"),
|
|
51
65
|
re.compile(r"^content\.content\.\d+\.content\.\d+\.type$"),
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"""Patch Claude Agent SDK to route model traffic through the Sovara proxy."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import replace
|
|
6
|
+
from functools import wraps
|
|
7
|
+
import os
|
|
8
|
+
import uuid
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from sovara.common.constants import SOVARA_SERVER_URL
|
|
12
|
+
from sovara.common.logger import logger
|
|
13
|
+
from sovara.runner.context_manager import get_run_id
|
|
14
|
+
from sovara.runner.monkey_patching.patching_utils import capture_stack_trace
|
|
15
|
+
from sovara.runner.runtime_client import runtime_client
|
|
16
|
+
|
|
17
|
+
_DEFAULT_ANTHROPIC_BASE_URL = "https://api.anthropic.com"
|
|
18
|
+
_UNSUPPORTED_CUSTOM_TRANSPORT_MESSAGE = (
|
|
19
|
+
"Custom Claude Agent SDK transports are not supported yet under so-record. "
|
|
20
|
+
"Please reach out to support@sovara-labs.com."
|
|
21
|
+
)
|
|
22
|
+
_PROXY_LOOP_MESSAGE = (
|
|
23
|
+
"Claude Agent SDK ANTHROPIC_BASE_URL points at the Sovara Claude proxy itself. "
|
|
24
|
+
"Nested Claude proxy configuration is not supported under so-record."
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _replace_options_env(options: Any, env: dict[str, str]):
|
|
29
|
+
try:
|
|
30
|
+
return replace(options, env=env)
|
|
31
|
+
except Exception:
|
|
32
|
+
options.env = env
|
|
33
|
+
return options
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _merged_transport_env(options_env: dict[str, str] | None) -> dict[str, str]:
|
|
37
|
+
inherited_env = {key: value for key, value in os.environ.items() if key != "CLAUDECODE"}
|
|
38
|
+
return {
|
|
39
|
+
**inherited_env,
|
|
40
|
+
"CLAUDE_CODE_ENTRYPOINT": "sdk-py",
|
|
41
|
+
**(options_env or {}),
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _proxy_base_url(run_id: str, client_id: str) -> str:
|
|
46
|
+
return f"{SOVARA_SERVER_URL}/claude-proxy/runs/{run_id}/clients/{client_id}"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _is_sovara_proxy_base_url(base_url: str) -> bool:
|
|
50
|
+
return base_url.rstrip("/").startswith(f"{SOVARA_SERVER_URL.rstrip('/')}/claude-proxy/")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _ensure_supported_transport(transport: Any) -> None:
|
|
54
|
+
if transport is not None and get_run_id():
|
|
55
|
+
raise RuntimeError(_UNSUPPORTED_CUSTOM_TRANSPORT_MESSAGE)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _normalize_tool_result_content(content: Any) -> Any:
|
|
59
|
+
if content is None:
|
|
60
|
+
return ""
|
|
61
|
+
return content
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def claude_sdk_patch():
|
|
65
|
+
"""Apply Claude Agent SDK patches when running under ``so-record``."""
|
|
66
|
+
try:
|
|
67
|
+
from claude_agent_sdk._internal import client as internal_client
|
|
68
|
+
from claude_agent_sdk._internal import message_parser
|
|
69
|
+
from claude_agent_sdk._internal.transport.subprocess_cli import SubprocessCLITransport
|
|
70
|
+
from claude_agent_sdk import client as sdk_client
|
|
71
|
+
from claude_agent_sdk.client import ClaudeSDKClient
|
|
72
|
+
except ImportError:
|
|
73
|
+
logger.info("claude_agent_sdk not installed, skipping SDK patches")
|
|
74
|
+
return
|
|
75
|
+
|
|
76
|
+
try:
|
|
77
|
+
from claude_agent_sdk.types import AssistantMessage, ToolResultBlock, ToolUseBlock, UserMessage
|
|
78
|
+
except ImportError:
|
|
79
|
+
logger.info("claude_agent_sdk types unavailable, skipping SDK patches")
|
|
80
|
+
return
|
|
81
|
+
|
|
82
|
+
original_parse = message_parser.parse_message
|
|
83
|
+
original_process_query = internal_client.InternalClient.process_query
|
|
84
|
+
original_client_connect = ClaudeSDKClient.connect
|
|
85
|
+
original_transport_connect = SubprocessCLITransport.connect
|
|
86
|
+
|
|
87
|
+
@wraps(original_parse)
|
|
88
|
+
def patched_parse_message(data: dict) -> Any:
|
|
89
|
+
message = original_parse(data)
|
|
90
|
+
run_id = get_run_id()
|
|
91
|
+
if not run_id:
|
|
92
|
+
return message
|
|
93
|
+
|
|
94
|
+
stack_trace = capture_stack_trace()
|
|
95
|
+
try:
|
|
96
|
+
if isinstance(message, AssistantMessage):
|
|
97
|
+
for block in getattr(message, "content", []) or []:
|
|
98
|
+
if isinstance(block, ToolUseBlock):
|
|
99
|
+
runtime_client.register_claude_sdk_tool_use(
|
|
100
|
+
run_id,
|
|
101
|
+
block.id,
|
|
102
|
+
block.name,
|
|
103
|
+
getattr(block, "input", None),
|
|
104
|
+
stack_trace=stack_trace,
|
|
105
|
+
)
|
|
106
|
+
elif isinstance(message, UserMessage):
|
|
107
|
+
content = getattr(message, "content", None)
|
|
108
|
+
if isinstance(content, list):
|
|
109
|
+
for block in content:
|
|
110
|
+
if isinstance(block, ToolResultBlock):
|
|
111
|
+
runtime_client.finalize_claude_sdk_tool_result(
|
|
112
|
+
run_id,
|
|
113
|
+
block.tool_use_id,
|
|
114
|
+
_normalize_tool_result_content(getattr(block, "content", None)),
|
|
115
|
+
is_error=bool(getattr(block, "is_error", False)),
|
|
116
|
+
)
|
|
117
|
+
except Exception as exc:
|
|
118
|
+
logger.error("Error in claude_sdk_patch parse hook: %s", exc)
|
|
119
|
+
return message
|
|
120
|
+
|
|
121
|
+
@wraps(original_process_query)
|
|
122
|
+
async def patched_process_query(self, prompt, options, transport=None):
|
|
123
|
+
_ensure_supported_transport(transport)
|
|
124
|
+
async for message in original_process_query(self, prompt, options, transport=transport):
|
|
125
|
+
yield message
|
|
126
|
+
|
|
127
|
+
@wraps(original_client_connect)
|
|
128
|
+
async def patched_client_connect(self, prompt=None):
|
|
129
|
+
_ensure_supported_transport(getattr(self, "_custom_transport", None))
|
|
130
|
+
return await original_client_connect(self, prompt)
|
|
131
|
+
|
|
132
|
+
@wraps(original_transport_connect)
|
|
133
|
+
async def patched_transport_connect(self):
|
|
134
|
+
run_id = get_run_id()
|
|
135
|
+
if not run_id:
|
|
136
|
+
return await original_transport_connect(self)
|
|
137
|
+
|
|
138
|
+
original_env = dict(getattr(self._options, "env", {}) or {})
|
|
139
|
+
merged_env = _merged_transport_env(original_env)
|
|
140
|
+
if merged_env.get("SOVARA_CLAUDE_PROXY_ACTIVE") == "1":
|
|
141
|
+
return await original_transport_connect(self)
|
|
142
|
+
|
|
143
|
+
upstream_base_url = merged_env.get("ANTHROPIC_BASE_URL") or _DEFAULT_ANTHROPIC_BASE_URL
|
|
144
|
+
if _is_sovara_proxy_base_url(upstream_base_url):
|
|
145
|
+
raise RuntimeError(_PROXY_LOOP_MESSAGE)
|
|
146
|
+
client_id = getattr(self, "_sovara_claude_proxy_client_id", None)
|
|
147
|
+
if not client_id:
|
|
148
|
+
client_id = uuid.uuid4().hex
|
|
149
|
+
self._sovara_claude_proxy_client_id = client_id
|
|
150
|
+
|
|
151
|
+
runtime_client.register_claude_proxy_client(
|
|
152
|
+
run_id,
|
|
153
|
+
client_id,
|
|
154
|
+
upstream_base_url,
|
|
155
|
+
stack_trace=capture_stack_trace(),
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
proxy_env = {
|
|
159
|
+
**merged_env,
|
|
160
|
+
"ANTHROPIC_BASE_URL": _proxy_base_url(run_id, client_id),
|
|
161
|
+
"SOVARA_CLAUDE_PROXY_ACTIVE": "1",
|
|
162
|
+
}
|
|
163
|
+
self._options = _replace_options_env(self._options, proxy_env)
|
|
164
|
+
try:
|
|
165
|
+
return await original_transport_connect(self)
|
|
166
|
+
finally:
|
|
167
|
+
self._options = _replace_options_env(self._options, original_env)
|
|
168
|
+
|
|
169
|
+
message_parser.parse_message = patched_parse_message
|
|
170
|
+
internal_client.parse_message = patched_parse_message
|
|
171
|
+
sdk_client.parse_message = patched_parse_message
|
|
172
|
+
internal_client.InternalClient.process_query = patched_process_query
|
|
173
|
+
ClaudeSDKClient.connect = patched_client_connect
|
|
174
|
+
SubprocessCLITransport.connect = patched_transport_connect
|
|
175
|
+
|
|
176
|
+
logger.info("claude_agent_sdk patch applied")
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def clear_sdk_run_data(run_id: str):
|
|
180
|
+
"""Retained for compatibility with older cleanup paths."""
|
|
181
|
+
del run_id
|
|
@@ -64,6 +64,12 @@ def _extract_model_from_body(input_dict: Dict[str, Any], api_type: str) -> Optio
|
|
|
64
64
|
return "Redacted Reasoning"
|
|
65
65
|
return input_dict.get("tool_name") or input_dict.get("model")
|
|
66
66
|
|
|
67
|
+
elif api_type == "claude_code.messages":
|
|
68
|
+
body = input_dict.get("body")
|
|
69
|
+
if isinstance(body, dict):
|
|
70
|
+
return body.get("model")
|
|
71
|
+
return None
|
|
72
|
+
|
|
67
73
|
elif api_type == "langchain_core.tools.BaseTool.run":
|
|
68
74
|
return input_dict.get("tool_name")
|
|
69
75
|
|
|
@@ -188,6 +194,10 @@ def _extract_request_url_and_path(input_dict: Dict[str, Any], api_type: str) ->
|
|
|
188
194
|
return url, parsed.path
|
|
189
195
|
if api_type == "urllib3.HTTPConnectionPool.urlopen":
|
|
190
196
|
return input_dict.get("full_url"), input_dict.get("url")
|
|
197
|
+
if api_type == "claude_code.messages":
|
|
198
|
+
url = str(input_dict.get("url") or "")
|
|
199
|
+
parsed = urlparse(url)
|
|
200
|
+
return url, parsed.path
|
|
191
201
|
except (AttributeError, KeyError, TypeError):
|
|
192
202
|
return None, None
|
|
193
203
|
|
|
@@ -209,6 +219,9 @@ def get_node_kind(input_dict: Dict[str, Any], api_type: str) -> str:
|
|
|
209
219
|
if api_type == "claude_agent_sdk.parse_message":
|
|
210
220
|
return "tool" if input_dict.get("tool_name") else "llm"
|
|
211
221
|
|
|
222
|
+
if api_type == "claude_code.messages":
|
|
223
|
+
return "llm"
|
|
224
|
+
|
|
212
225
|
if api_type == "langchain_core.tools.BaseTool.run":
|
|
213
226
|
return "tool"
|
|
214
227
|
|
|
@@ -339,6 +352,36 @@ def send_graph_node_and_edges(
|
|
|
339
352
|
latency_seconds=None,
|
|
340
353
|
):
|
|
341
354
|
"""Send graph node and edge updates to the server."""
|
|
355
|
+
run_id = get_run_id()
|
|
356
|
+
node_msg = build_add_node_message(
|
|
357
|
+
run_id=run_id,
|
|
358
|
+
node_id=node_id,
|
|
359
|
+
input_dict=input_dict,
|
|
360
|
+
output_obj=output_obj,
|
|
361
|
+
source_node_ids=source_node_ids,
|
|
362
|
+
api_type=api_type,
|
|
363
|
+
stack_trace=stack_trace,
|
|
364
|
+
latency_seconds=latency_seconds,
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
try:
|
|
368
|
+
http_post("/runner/add-node", node_msg)
|
|
369
|
+
except Exception as e:
|
|
370
|
+
logger.error(f"Failed to send add_node: {e}")
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def build_add_node_message(
|
|
374
|
+
*,
|
|
375
|
+
run_id,
|
|
376
|
+
node_id,
|
|
377
|
+
input_dict,
|
|
378
|
+
output_obj,
|
|
379
|
+
source_node_ids,
|
|
380
|
+
api_type,
|
|
381
|
+
stack_trace,
|
|
382
|
+
latency_seconds=None,
|
|
383
|
+
):
|
|
384
|
+
"""Build the canonical add-node payload for graph updates."""
|
|
342
385
|
# Import here to avoid circular import
|
|
343
386
|
from sovara.runner.monkey_patching.api_parser import func_kwargs_to_json_str, api_obj_to_json_str
|
|
344
387
|
from sovara.runner.node_execution import extract_node_error
|
|
@@ -351,7 +394,6 @@ def send_graph_node_and_edges(
|
|
|
351
394
|
raw_node_name = get_node_name(input_dict, api_type)
|
|
352
395
|
label = get_node_label(input_dict, api_type)
|
|
353
396
|
node_kind = get_node_kind(input_dict, api_type)
|
|
354
|
-
run_id = get_run_id()
|
|
355
397
|
error_payload = extract_node_error(output_obj, api_type)
|
|
356
398
|
node_status = "error" if error_payload else "success"
|
|
357
399
|
error_message = error_payload.get("message") if isinstance(error_payload, dict) else None
|
|
@@ -378,10 +420,9 @@ def send_graph_node_and_edges(
|
|
|
378
420
|
if node_a != node_b and node_b in _graph_reachable_set[run_id][node_a]:
|
|
379
421
|
if output_contained_in_input(run_id, node_a, node_b):
|
|
380
422
|
nodes_to_remove.add(node_a)
|
|
381
|
-
|
|
423
|
+
filtered_source_node_ids = [n for n in source_node_ids if n not in nodes_to_remove]
|
|
382
424
|
|
|
383
|
-
|
|
384
|
-
node_msg = {
|
|
425
|
+
return {
|
|
385
426
|
"type": "add_node",
|
|
386
427
|
"run_id": run_id,
|
|
387
428
|
"node": {
|
|
@@ -396,10 +437,5 @@ def send_graph_node_and_edges(
|
|
|
396
437
|
"node_kind": node_kind,
|
|
397
438
|
"raw_node_name": raw_node_name,
|
|
398
439
|
},
|
|
399
|
-
"incoming_edges":
|
|
440
|
+
"incoming_edges": filtered_source_node_ids,
|
|
400
441
|
}
|
|
401
|
-
|
|
402
|
-
try:
|
|
403
|
-
http_post("/runner/add-node", node_msg)
|
|
404
|
-
except Exception as e:
|
|
405
|
-
logger.error(f"Failed to send add_node: {e}")
|
|
@@ -68,11 +68,9 @@ def record_prepared_call_exception(
|
|
|
68
68
|
if build_exception_output_func is None:
|
|
69
69
|
build_exception_output_func = build_exception_output
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
api_type=api_type,
|
|
75
|
-
)
|
|
71
|
+
# Support recorders that use either ``prepared_call`` or ``cache_result``
|
|
72
|
+
# for the first argument name.
|
|
73
|
+
record_exception_func(prepared_call, exc, api_type)
|
|
76
74
|
prepared_call.output = build_exception_output_func(exc, api_type, prepared_call.input_dict)
|
|
77
75
|
send_graph_node_and_edges_func(
|
|
78
76
|
node_id=prepared_call.node_uuid,
|
|
@@ -194,6 +192,23 @@ def extract_node_error(output_obj: Any, api_type: str) -> dict[str, Any] | None:
|
|
|
194
192
|
|
|
195
193
|
payload = _jsonable_payload(output_obj)
|
|
196
194
|
|
|
195
|
+
if api_type == "claude_code.messages":
|
|
196
|
+
if isinstance(payload, dict):
|
|
197
|
+
status = payload.get("_status_code")
|
|
198
|
+
if isinstance(status, int) and status >= 400:
|
|
199
|
+
details = {
|
|
200
|
+
key: value
|
|
201
|
+
for key, value in payload.items()
|
|
202
|
+
if not str(key).startswith("_")
|
|
203
|
+
}
|
|
204
|
+
return _http_error_payload(
|
|
205
|
+
api_type=api_type,
|
|
206
|
+
status=status,
|
|
207
|
+
reason=None,
|
|
208
|
+
details=details,
|
|
209
|
+
)
|
|
210
|
+
return None
|
|
211
|
+
|
|
197
212
|
if api_type == "MCP.ClientSession.send_request":
|
|
198
213
|
if isinstance(payload, dict) and (payload.get("isError") is True or payload.get("is_error") is True):
|
|
199
214
|
return _structured_error_payload(
|
|
@@ -288,4 +303,12 @@ def build_exception_output(exc: Exception, api_type: str, input_dict: dict[str,
|
|
|
288
303
|
request_url=input_dict.get("full_url"),
|
|
289
304
|
)
|
|
290
305
|
|
|
306
|
+
if api_type == "claude_code.messages":
|
|
307
|
+
return {
|
|
308
|
+
**error_payload,
|
|
309
|
+
"_stream": bool(input_dict.get("body", {}).get("stream")),
|
|
310
|
+
"_status_code": 599,
|
|
311
|
+
"_headers": {"content-type": "application/json"},
|
|
312
|
+
}
|
|
313
|
+
|
|
291
314
|
return error_payload
|
|
@@ -63,12 +63,67 @@ class RuntimeClient:
|
|
|
63
63
|
def get_run_scope(self, run_id: str) -> dict[str, Any] | None:
|
|
64
64
|
return http_post("/internal/runner/run-scope", {"run_id": run_id}).get("run")
|
|
65
65
|
|
|
66
|
+
def register_claude_proxy_client(
|
|
67
|
+
self,
|
|
68
|
+
run_id: str,
|
|
69
|
+
client_id: str,
|
|
70
|
+
upstream_base_url: str,
|
|
71
|
+
stack_trace: str | None = None,
|
|
72
|
+
) -> None:
|
|
73
|
+
http_post(
|
|
74
|
+
"/internal/runner/claude-proxy/register-client",
|
|
75
|
+
{
|
|
76
|
+
"run_id": run_id,
|
|
77
|
+
"client_id": client_id,
|
|
78
|
+
"upstream_base_url": upstream_base_url,
|
|
79
|
+
"stack_trace": stack_trace,
|
|
80
|
+
},
|
|
81
|
+
)
|
|
82
|
+
|
|
66
83
|
def add_prior_applied(self, prior_id: str, run_id: str, node_uuid: str | None = None) -> None:
|
|
67
84
|
http_post(
|
|
68
85
|
"/internal/runner/priors/track-applied",
|
|
69
86
|
{"prior_id": prior_id, "run_id": run_id, "node_uuid": node_uuid},
|
|
70
87
|
)
|
|
71
88
|
|
|
89
|
+
def register_claude_sdk_tool_use(
|
|
90
|
+
self,
|
|
91
|
+
run_id: str,
|
|
92
|
+
tool_use_id: str,
|
|
93
|
+
tool_name: str,
|
|
94
|
+
tool_input: Any,
|
|
95
|
+
*,
|
|
96
|
+
stack_trace: str | None = None,
|
|
97
|
+
) -> None:
|
|
98
|
+
http_post(
|
|
99
|
+
"/internal/runner/claude-sdk/tool-use",
|
|
100
|
+
{
|
|
101
|
+
"run_id": run_id,
|
|
102
|
+
"tool_use_id": tool_use_id,
|
|
103
|
+
"tool_name": tool_name,
|
|
104
|
+
"tool_input": tool_input,
|
|
105
|
+
"stack_trace": stack_trace,
|
|
106
|
+
},
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
def finalize_claude_sdk_tool_result(
|
|
110
|
+
self,
|
|
111
|
+
run_id: str,
|
|
112
|
+
tool_use_id: str,
|
|
113
|
+
tool_result: Any,
|
|
114
|
+
*,
|
|
115
|
+
is_error: bool = False,
|
|
116
|
+
) -> None:
|
|
117
|
+
http_post(
|
|
118
|
+
"/internal/runner/claude-sdk/tool-result",
|
|
119
|
+
{
|
|
120
|
+
"run_id": run_id,
|
|
121
|
+
"tool_use_id": tool_use_id,
|
|
122
|
+
"tool_result": tool_result,
|
|
123
|
+
"is_error": is_error,
|
|
124
|
+
},
|
|
125
|
+
)
|
|
126
|
+
|
|
72
127
|
def upsert_prior_retrieval(
|
|
73
128
|
self,
|
|
74
129
|
run_id: str,
|
|
@@ -127,6 +182,7 @@ class RuntimeClient:
|
|
|
127
182
|
"aiohttp.ClientSession._request",
|
|
128
183
|
"requests.Session.send",
|
|
129
184
|
"urllib3.HTTPConnectionPool.urlopen",
|
|
185
|
+
"claude_code.messages",
|
|
130
186
|
}
|
|
131
187
|
)
|
|
132
188
|
|
|
@@ -212,12 +212,14 @@ def create_app() -> FastAPI:
|
|
|
212
212
|
from sovara.server.routes.ui import router as ui_router
|
|
213
213
|
from sovara.server.routes.events import router as events_router
|
|
214
214
|
from sovara.server.routes.internal import router as internal_router
|
|
215
|
+
from sovara.server.routes.claude_proxy import router as claude_proxy_router
|
|
215
216
|
from sovara.server.priors_backend.routes import router as priors_router
|
|
216
217
|
|
|
217
218
|
app.include_router(runner_router)
|
|
218
219
|
app.include_router(ui_router)
|
|
219
220
|
app.include_router(events_router)
|
|
220
221
|
app.include_router(internal_router)
|
|
222
|
+
app.include_router(claude_proxy_router)
|
|
221
223
|
app.include_router(priors_router)
|
|
222
224
|
|
|
223
225
|
@app.get("/health")
|