alysis-code 0.13.0__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.
- alysis_code/__init__.py +3 -0
- alysis_code/__main__.py +4 -0
- alysis_code/_build_info.py +14 -0
- alysis_code/account_login.py +468 -0
- alysis_code/agent/README.md +35 -0
- alysis_code/agent/__init__.py +11 -0
- alysis_code/agent/acceptance_contract.py +2217 -0
- alysis_code/agent/blast_radius.py +1403 -0
- alysis_code/agent/cache_keepalive.py +227 -0
- alysis_code/agent/completion_certificate.py +366 -0
- alysis_code/agent/completion_gate.py +306 -0
- alysis_code/agent/empty_response_stall.py +403 -0
- alysis_code/agent/errors.py +28 -0
- alysis_code/agent/llm_calls.py +475 -0
- alysis_code/agent/mutation_classification.py +227 -0
- alysis_code/agent/prompt_context.py +2508 -0
- alysis_code/agent/read_ledger.py +253 -0
- alysis_code/agent/regression_baseline.py +642 -0
- alysis_code/agent/reproduction_first.py +610 -0
- alysis_code/agent/sensitive_output.py +629 -0
- alysis_code/agent/session.py +3218 -0
- alysis_code/agent/steering.py +191 -0
- alysis_code/agent/subagent_execution.py +5177 -0
- alysis_code/agent/subagent_workspace.py +666 -0
- alysis_code/agent/tools_assembly.py +4728 -0
- alysis_code/agent/turn/__init__.py +101 -0
- alysis_code/agent/turn/core.py +8483 -0
- alysis_code/agent/turn/events.py +113 -0
- alysis_code/agent/turn/exploration.py +590 -0
- alysis_code/agent/turn/interventions.py +65 -0
- alysis_code/agent/turn/read_cache.py +420 -0
- alysis_code/agent/turn/snapshot.py +179 -0
- alysis_code/agent/turn_contract.py +661 -0
- alysis_code/agent/turn_path.py +129 -0
- alysis_code/agent/verification.py +2885 -0
- alysis_code/agent/verification_commands.py +512 -0
- alysis_code/agent/verification_evidence.py +738 -0
- alysis_code/agent_loop.py +693 -0
- alysis_code/agent_runtimes/__init__.py +51 -0
- alysis_code/agent_runtimes/base.py +114 -0
- alysis_code/agent_runtimes/builtins.py +129 -0
- alysis_code/agent_runtimes/codex_cli.py +664 -0
- alysis_code/agent_runtimes/host.py +263 -0
- alysis_code/agent_runtimes/registry.py +64 -0
- alysis_code/agent_runtimes/service.py +150 -0
- alysis_code/agentbox_client.py +416 -0
- alysis_code/agentbox_integration.py +310 -0
- alysis_code/alysis_cloud.py +152 -0
- alysis_code/approval_scope.py +276 -0
- alysis_code/assets/README.md +33 -0
- alysis_code/assets/__init__.py +126 -0
- alysis_code/assets/asset_read_core.py +281 -0
- alysis_code/assets/budget_allocator.py +456 -0
- alysis_code/assets/comprehender.py +759 -0
- alysis_code/assets/index.py +654 -0
- alysis_code/assets/ingestion.py +275 -0
- alysis_code/assets/legacy_migration.py +413 -0
- alysis_code/assets/models.py +263 -0
- alysis_code/assets/ocr.py +239 -0
- alysis_code/assets/owl/ascii/f-000.txt +13 -0
- alysis_code/assets/owl/ascii/f-001.txt +13 -0
- alysis_code/assets/owl/ascii/f-002.txt +13 -0
- alysis_code/assets/owl/ascii/f-003.txt +13 -0
- alysis_code/assets/owl/ascii/f-004.txt +13 -0
- alysis_code/assets/owl/ascii/f-005.txt +13 -0
- alysis_code/assets/owl/ascii/f-006.txt +13 -0
- alysis_code/assets/owl/ascii/f-007.txt +13 -0
- alysis_code/assets/owl/ascii/f-008.txt +13 -0
- alysis_code/assets/owl/ascii/f-009.txt +13 -0
- alysis_code/assets/owl/ascii/f-010.txt +13 -0
- alysis_code/assets/owl/ascii/f-011.txt +13 -0
- alysis_code/assets/owl/ascii/f-012.txt +13 -0
- alysis_code/assets/owl/ascii/f-013.txt +13 -0
- alysis_code/assets/owl/ascii/f-014.txt +13 -0
- alysis_code/assets/owl/ascii/f-015.txt +13 -0
- alysis_code/assets/owl/ascii/f-016.txt +13 -0
- alysis_code/assets/owl/ascii/f-017.txt +13 -0
- alysis_code/assets/owl/ascii/f-018.txt +13 -0
- alysis_code/assets/owl/ascii/f-019.txt +13 -0
- alysis_code/assets/owl/ascii/f-020.txt +13 -0
- alysis_code/assets/owl/index.html +98 -0
- alysis_code/assets/owl/show-owl.sh +761 -0
- alysis_code/assets/paths.py +49 -0
- alysis_code/assets/plan_binding.py +326 -0
- alysis_code/assets/planner_context.py +466 -0
- alysis_code/assets/planner_tools.py +184 -0
- alysis_code/assets/prompts.py +101 -0
- alysis_code/assets/replanner_context.py +239 -0
- alysis_code/assets/surface.py +521 -0
- alysis_code/assets/untrusted_content.py +48 -0
- alysis_code/assets/usage_logger.py +94 -0
- alysis_code/assets/worker_mirror.py +428 -0
- alysis_code/assets/worker_section.py +303 -0
- alysis_code/assets/worker_tools.py +468 -0
- alysis_code/atomic_io.py +83 -0
- alysis_code/auth_diagnostics.py +272 -0
- alysis_code/background_runner.py +366 -0
- alysis_code/branding.py +270 -0
- alysis_code/budget_policy.py +390 -0
- alysis_code/build_identity.py +465 -0
- alysis_code/builtin_hooks/__init__.py +7 -0
- alysis_code/builtin_hooks/notify_done_windows.py +65 -0
- alysis_code/bwrap_etc.py +76 -0
- alysis_code/cancellation.py +41 -0
- alysis_code/capabilities.py +137 -0
- alysis_code/chatgpt_codex_static_provider.py +133 -0
- alysis_code/cli.py +51 -0
- alysis_code/cli_impl/__init__.py +1 -0
- alysis_code/cli_impl/assets_cli.py +537 -0
- alysis_code/cli_impl/assets_modal.py +412 -0
- alysis_code/cli_impl/chat/__init__.py +156 -0
- alysis_code/cli_impl/chat/commands.py +2616 -0
- alysis_code/cli_impl/chat/loop.py +4508 -0
- alysis_code/cli_impl/chat/mid_turn_policy.py +125 -0
- alysis_code/cli_impl/chat/rendering.py +444 -0
- alysis_code/cli_impl/chat/state.py +124 -0
- alysis_code/cli_impl/chat_resume.py +830 -0
- alysis_code/cli_impl/chat_slash_completer.py +258 -0
- alysis_code/cli_impl/commands/__init__.py +11 -0
- alysis_code/cli_impl/commands/_shared.py +89 -0
- alysis_code/cli_impl/commands/auth.py +623 -0
- alysis_code/cli_impl/commands/chat_resume_helpers.py +1531 -0
- alysis_code/cli_impl/commands/chat_state.py +158 -0
- alysis_code/cli_impl/commands/chat_status.py +1248 -0
- alysis_code/cli_impl/commands/chat_terminal.py +942 -0
- alysis_code/cli_impl/commands/chat_tui_panels.py +1018 -0
- alysis_code/cli_impl/commands/cli_common.py +1223 -0
- alysis_code/cli_impl/commands/cli_surface.py +77 -0
- alysis_code/cli_impl/commands/config.py +131 -0
- alysis_code/cli_impl/commands/conventions.py +85 -0
- alysis_code/cli_impl/commands/execution_helpers.py +350 -0
- alysis_code/cli_impl/commands/extensions.py +401 -0
- alysis_code/cli_impl/commands/forge.py +1282 -0
- alysis_code/cli_impl/commands/forge_asset_view.py +121 -0
- alysis_code/cli_impl/commands/forge_helpers.py +1215 -0
- alysis_code/cli_impl/commands/hooks.py +737 -0
- alysis_code/cli_impl/commands/ide_bridge.py +31 -0
- alysis_code/cli_impl/commands/mcp.py +700 -0
- alysis_code/cli_impl/commands/profile.py +453 -0
- alysis_code/cli_impl/commands/prompt_helpers.py +307 -0
- alysis_code/cli_impl/commands/report.py +88 -0
- alysis_code/cli_impl/commands/root.py +1118 -0
- alysis_code/cli_impl/commands/sandbox.py +184 -0
- alysis_code/cli_impl/commands/server.py +54 -0
- alysis_code/cli_impl/commands/sessions.py +252 -0
- alysis_code/cli_impl/commands/skills.py +404 -0
- alysis_code/cli_impl/commands/startup.py +946 -0
- alysis_code/cli_impl/commands/tools.py +335 -0
- alysis_code/cli_impl/commands/update.py +364 -0
- alysis_code/cli_impl/commands/welcome.py +972 -0
- alysis_code/cli_impl/config_menu.py +3882 -0
- alysis_code/cli_impl/forge.py +4509 -0
- alysis_code/cli_impl/forge_recovery.py +485 -0
- alysis_code/cli_impl/setup_wizard.py +2409 -0
- alysis_code/cli_impl/tui/__init__.py +58 -0
- alysis_code/cli_impl/tui/app.py +4551 -0
- alysis_code/cli_impl/tui/config.py +32 -0
- alysis_code/cli_impl/tui/config_flow.py +2754 -0
- alysis_code/cli_impl/tui/config_overlay.py +566 -0
- alysis_code/cli_impl/tui/content.py +78 -0
- alysis_code/cli_impl/tui/footer.py +218 -0
- alysis_code/cli_impl/tui/forge_status.py +136 -0
- alysis_code/cli_impl/tui/markdown.py +244 -0
- alysis_code/cli_impl/tui/owl.py +109 -0
- alysis_code/cli_impl/tui/plan_meta.py +477 -0
- alysis_code/cli_impl/tui/setup_app.py +519 -0
- alysis_code/cli_impl/tui/setup_flow.py +1622 -0
- alysis_code/cli_impl/tui/state.py +101 -0
- alysis_code/cli_impl/tui/subagent_identity.py +66 -0
- alysis_code/cli_impl/tui/subagent_panel.py +186 -0
- alysis_code/cli_impl/tui/surface.py +796 -0
- alysis_code/cli_impl/tui/transcript.py +514 -0
- alysis_code/cli_impl/tui/update_prompt.py +79 -0
- alysis_code/cli_impl/tui/workspace_guard.py +384 -0
- alysis_code/clipboard.py +172 -0
- alysis_code/code_review.py +1211 -0
- alysis_code/compaction/__init__.py +28 -0
- alysis_code/compaction/conversation_compactor.py +2932 -0
- alysis_code/compaction/importance.py +177 -0
- alysis_code/compaction/settings.py +297 -0
- alysis_code/compaction/tool_output_offload.py +447 -0
- alysis_code/config.py +3509 -0
- alysis_code/conflict_auto_resolver.py +895 -0
- alysis_code/context/__init__.py +1 -0
- alysis_code/context/tool_schema_budgeter.py +220 -0
- alysis_code/crash_diagnostics.py +282 -0
- alysis_code/custom_tools/README.md +34 -0
- alysis_code/custom_tools/__init__.py +43 -0
- alysis_code/custom_tools/discovery.py +903 -0
- alysis_code/custom_tools/runtime.py +1516 -0
- alysis_code/custom_tools/session.py +227 -0
- alysis_code/custom_tools/trust.py +232 -0
- alysis_code/diff_paths.py +113 -0
- alysis_code/direction_change.py +293 -0
- alysis_code/dispatch_timing.py +306 -0
- alysis_code/durable_service_manager.py +1236 -0
- alysis_code/edit_discipline.py +659 -0
- alysis_code/error_text.py +73 -0
- alysis_code/execution_budget.py +411 -0
- alysis_code/execution_context.py +915 -0
- alysis_code/execution_deadline.py +1065 -0
- alysis_code/execution_shared.py +1904 -0
- alysis_code/extensions/README.md +30 -0
- alysis_code/extensions/__init__.py +93 -0
- alysis_code/extensions/activation.py +138 -0
- alysis_code/extensions/install.py +1436 -0
- alysis_code/extensions/manifest.py +487 -0
- alysis_code/extensions/models.py +74 -0
- alysis_code/extensions/paths.py +56 -0
- alysis_code/extensions/registry.json +4 -0
- alysis_code/extensions/registry.py +52 -0
- alysis_code/extensions/state.py +83 -0
- alysis_code/extensions/workspace_trust.py +101 -0
- alysis_code/failed_task_evidence.py +369 -0
- alysis_code/failure_category.py +315 -0
- alysis_code/feedback_report.py +1647 -0
- alysis_code/file_classification.py +485 -0
- alysis_code/forge.py +2064 -0
- alysis_code/forge_completion.py +362 -0
- alysis_code/forge_events.py +475 -0
- alysis_code/frontmatter_utils.py +95 -0
- alysis_code/git_evidence.py +1181 -0
- alysis_code/git_ops.py +560 -0
- alysis_code/git_safe.py +62 -0
- alysis_code/git_worktrees.py +190 -0
- alysis_code/hooks/README.md +33 -0
- alysis_code/hooks/__init__.py +67 -0
- alysis_code/hooks/audit.py +171 -0
- alysis_code/hooks/config.py +225 -0
- alysis_code/hooks/dispatcher.py +1110 -0
- alysis_code/hooks/models.py +447 -0
- alysis_code/hooks/trust.py +202 -0
- alysis_code/host_actions.py +543 -0
- alysis_code/host_browser.py +103 -0
- alysis_code/ide/__init__.py +5 -0
- alysis_code/ide/activity_events.py +399 -0
- alysis_code/ide/approvals.py +337 -0
- alysis_code/ide/artifacts.py +153 -0
- alysis_code/ide/browser_egress_proxy.py +1076 -0
- alysis_code/ide/cdp_websocket_transport.py +1192 -0
- alysis_code/ide/change_ledger.py +1721 -0
- alysis_code/ide/context_blocks.py +979 -0
- alysis_code/ide/event_stream.py +531 -0
- alysis_code/ide/forge_protocol.py +3112 -0
- alysis_code/ide/forge_request_ledger.py +737 -0
- alysis_code/ide/health.py +965 -0
- alysis_code/ide/managed_browser.py +2251 -0
- alysis_code/ide/management_protocol.py +3414 -0
- alysis_code/ide/mcp_oauth_coordinator.py +744 -0
- alysis_code/ide/mcp_oauth_lifecycle.py +1504 -0
- alysis_code/ide/prompt_queue.py +1070 -0
- alysis_code/ide/protocol.py +191 -0
- alysis_code/ide/resumable_swarm.py +1543 -0
- alysis_code/ide/session_search.py +295 -0
- alysis_code/ide/stdio_bridge.py +9935 -0
- alysis_code/ide/structured_state.py +1579 -0
- alysis_code/ide/swarm_protocol.py +816 -0
- alysis_code/integration_gate.py +506 -0
- alysis_code/interactive_input_guard.py +39 -0
- alysis_code/interactive_plan_mode.py +26 -0
- alysis_code/internal_artifacts.py +179 -0
- alysis_code/knowledge_base.py +1409 -0
- alysis_code/knowledge_capture.py +1190 -0
- alysis_code/knowledge_librarian.py +605 -0
- alysis_code/language_policy.py +34 -0
- alysis_code/litellm_static_provider.py +535 -0
- alysis_code/llm/__init__.py +1 -0
- alysis_code/llm/anthropic_messages.py +2288 -0
- alysis_code/llm/base.py +71 -0
- alysis_code/llm/cache_capabilities.py +985 -0
- alysis_code/llm/cache_control_blocks.py +244 -0
- alysis_code/llm/cache_policy.py +388 -0
- alysis_code/llm/factory.py +373 -0
- alysis_code/llm/gemini_generate_content.py +2652 -0
- alysis_code/llm/gemini_interactions.py +739 -0
- alysis_code/llm/metadata.py +450 -0
- alysis_code/llm/openai_compat.py +2947 -0
- alysis_code/llm/openai_responses.py +2604 -0
- alysis_code/llm/protocols.py +609 -0
- alysis_code/llm/provider_limits.py +525 -0
- alysis_code/llm/request_plan.py +389 -0
- alysis_code/llm/request_shape.py +238 -0
- alysis_code/llm/streaming.py +108 -0
- alysis_code/llm/temperature_compat.py +78 -0
- alysis_code/llm/types.py +195 -0
- alysis_code/llm/usage_normalization.py +222 -0
- alysis_code/llm_error_display.py +315 -0
- alysis_code/logging_redaction.py +326 -0
- alysis_code/managed_host_deadline.py +196 -0
- alysis_code/mcp/README.md +33 -0
- alysis_code/mcp/__init__.py +24 -0
- alysis_code/mcp/client.py +1137 -0
- alysis_code/mcp/config.py +597 -0
- alysis_code/mcp/errors.py +113 -0
- alysis_code/mcp/forge_scope.py +154 -0
- alysis_code/mcp/jsonrpc.py +214 -0
- alysis_code/mcp/manager.py +2308 -0
- alysis_code/mcp/models.py +666 -0
- alysis_code/mcp/oauth.py +972 -0
- alysis_code/mcp/oauth_runtime.py +310 -0
- alysis_code/mcp/oauth_store.py +276 -0
- alysis_code/mcp/prompts.py +329 -0
- alysis_code/mcp/resources.py +295 -0
- alysis_code/mcp/roots.py +106 -0
- alysis_code/mcp/server_requests.py +75 -0
- alysis_code/mcp/token_store.py +859 -0
- alysis_code/mcp/transport_http.py +1338 -0
- alysis_code/mcp/transport_stdio.py +1267 -0
- alysis_code/mcp/untrusted_content.py +119 -0
- alysis_code/merge_conflict_reviewer.py +729 -0
- alysis_code/model_catalog/__init__.py +1 -0
- alysis_code/model_catalog/chatgpt_codex_subscription_snapshot.json +186 -0
- alysis_code/model_catalog/litellm_model_prices_snapshot.json +44715 -0
- alysis_code/model_catalog/litellm_model_prices_snapshot.meta.json +17 -0
- alysis_code/model_metadata_policy.py +223 -0
- alysis_code/model_metadata_utils.py +103 -0
- alysis_code/model_registry.py +1420 -0
- alysis_code/model_router.py +147 -0
- alysis_code/permission_policy.py +1016 -0
- alysis_code/personas.py +451 -0
- alysis_code/pipeline_facts.py +233 -0
- alysis_code/plan_assistant.py +4763 -0
- alysis_code/plan_mode.py +393 -0
- alysis_code/plan_reconciliation.py +1228 -0
- alysis_code/plan_repair.py +652 -0
- alysis_code/plan_validation.py +1099 -0
- alysis_code/planning_constraints.py +904 -0
- alysis_code/policy.py +95 -0
- alysis_code/preview_server.py +457 -0
- alysis_code/process_reaping.py +566 -0
- alysis_code/profile_presets.py +1834 -0
- alysis_code/profiles.py +666 -0
- alysis_code/provider_auth/__init__.py +29 -0
- alysis_code/provider_auth/base.py +99 -0
- alysis_code/provider_auth/openai_codex.py +951 -0
- alysis_code/provider_auth/registry.py +76 -0
- alysis_code/provider_auth/store.py +125 -0
- alysis_code/provider_diagnostics.py +1209 -0
- alysis_code/provider_model_catalog.py +685 -0
- alysis_code/provider_telemetry.py +1699 -0
- alysis_code/provider_url.py +75 -0
- alysis_code/reasoning_contracts.py +911 -0
- alysis_code/remote_sync.py +350 -0
- alysis_code/replanning.py +1195 -0
- alysis_code/repo_scan.py +1152 -0
- alysis_code/request_estimation.py +296 -0
- alysis_code/review_gate.py +617 -0
- alysis_code/run_lock.py +1141 -0
- alysis_code/run_outcome.py +58 -0
- alysis_code/run_provenance.py +774 -0
- alysis_code/run_state.py +445 -0
- alysis_code/runtime_artifacts.py +116 -0
- alysis_code/runtime_context_features.py +78 -0
- alysis_code/runtime_kind.py +52 -0
- alysis_code/safety/__init__.py +11 -0
- alysis_code/safety/mcp_sanitize.py +29 -0
- alysis_code/safety/safe_http.py +297 -0
- alysis_code/safety/subagent_report.py +184 -0
- alysis_code/sandbox_doctor.py +682 -0
- alysis_code/sandbox_runner.py +1025 -0
- alysis_code/sandbox_settings.py +423 -0
- alysis_code/serialized_paths.py +355 -0
- alysis_code/server/__init__.py +3 -0
- alysis_code/server/app.py +367 -0
- alysis_code/server/auth.py +34 -0
- alysis_code/server/job_config.py +30 -0
- alysis_code/server/settings.py +215 -0
- alysis_code/server/store.py +193 -0
- alysis_code/server/worker_runner.py +657 -0
- alysis_code/service_persistence.py +355 -0
- alysis_code/session_artifacts.py +108 -0
- alysis_code/session_metrics.py +331 -0
- alysis_code/session_store.py +624 -0
- alysis_code/skills/README.md +34 -0
- alysis_code/skills/__init__.py +104 -0
- alysis_code/skills/conventions.py +84 -0
- alysis_code/skills/discovery.py +176 -0
- alysis_code/skills/eval_models.py +232 -0
- alysis_code/skills/eval_runner.py +372 -0
- alysis_code/skills/evals.py +1344 -0
- alysis_code/skills/install.py +293 -0
- alysis_code/skills/loader.py +118 -0
- alysis_code/skills/matching.py +103 -0
- alysis_code/skills/models.py +71 -0
- alysis_code/skills/paths.py +56 -0
- alysis_code/skills/prompting.py +500 -0
- alysis_code/skills/scaffold.py +142 -0
- alysis_code/skills/state.py +441 -0
- alysis_code/skills/transactions.py +125 -0
- alysis_code/skills/validation.py +304 -0
- alysis_code/step_budget.py +238 -0
- alysis_code/subagent_labels.py +49 -0
- alysis_code/subagents.py +1072 -0
- alysis_code/surface/__init__.py +80 -0
- alysis_code/surface/base.py +305 -0
- alysis_code/surface/console.py +387 -0
- alysis_code/surface/events.py +372 -0
- alysis_code/surface/hidden_surface.py +529 -0
- alysis_code/surface/noop_surface.py +219 -0
- alysis_code/surface/rich_surface.py +1555 -0
- alysis_code/surface/styles.py +67 -0
- alysis_code/surface/theme.py +455 -0
- alysis_code/surface/types.py +100 -0
- alysis_code/swarm_backend.py +926 -0
- alysis_code/swarm_orchestrator.py +4020 -0
- alysis_code/swarm_scheduler.py +441 -0
- alysis_code/swarm_trace.py +429 -0
- alysis_code/swarm_worker.py +2119 -0
- alysis_code/swarm_write_guard.py +348 -0
- alysis_code/task_dependencies.py +170 -0
- alysis_code/task_readiness.py +992 -0
- alysis_code/task_scope.py +2148 -0
- alysis_code/terminal_manager.py +762 -0
- alysis_code/terminal_ownership.py +460 -0
- alysis_code/text_normalization.py +30 -0
- alysis_code/token_budget.py +97 -0
- alysis_code/tools/README.md +34 -0
- alysis_code/tools/__init__.py +1 -0
- alysis_code/tools/artifacts.py +127 -0
- alysis_code/tools/availability.py +188 -0
- alysis_code/tools/fs.py +1456 -0
- alysis_code/tools/git.py +461 -0
- alysis_code/tools/history.py +229 -0
- alysis_code/tools/http_timeout.py +78 -0
- alysis_code/tools/image_generation.py +552 -0
- alysis_code/tools/registry.py +2936 -0
- alysis_code/tools/repo_map.py +476 -0
- alysis_code/tools/search.py +563 -0
- alysis_code/tools/shell.py +135 -0
- alysis_code/tools/symbols.py +1350 -0
- alysis_code/tools/test_discovery.py +643 -0
- alysis_code/tools/web.py +482 -0
- alysis_code/tools/web_search.py +2012 -0
- alysis_code/tools/web_search_dashscope.py +557 -0
- alysis_code/tools/web_search_ddgs.py +221 -0
- alysis_code/tools/web_search_provider_adapters.py +1429 -0
- alysis_code/tools/web_search_tavily.py +194 -0
- alysis_code/updates.py +933 -0
- alysis_code/usage_tracker.py +1990 -0
- alysis_code/verification_command_analysis.py +1004 -0
- alysis_code/verification_contract.py +574 -0
- alysis_code/verification_failure_summary.py +273 -0
- alysis_code/verification_repair.py +385 -0
- alysis_code/verify_gate.py +3129 -0
- alysis_code/web_research.py +1872 -0
- alysis_code/web_search_adapters.py +66 -0
- alysis_code/web_search_policy.py +27 -0
- alysis_code/workspace_binding.py +389 -0
- alysis_code/workspace_binding_ui.py +408 -0
- alysis_code/workspace_context.py +273 -0
- alysis_code/workspace_isolation.py +138 -0
- alysis_code/workspace_provisioning.py +455 -0
- alysis_code-0.13.0.dist-info/METADATA +507 -0
- alysis_code-0.13.0.dist-info/RECORD +458 -0
- alysis_code-0.13.0.dist-info/WHEEL +4 -0
- alysis_code-0.13.0.dist-info/entry_points.txt +3 -0
- alysis_code-0.13.0.dist-info/licenses/LICENSE +176 -0
- alysis_code-0.13.0.dist-info/licenses/NOTICE +4 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from collections.abc import Iterable, Iterator
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from .types import LLMError
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(frozen=True)
|
|
12
|
+
class SSEFrame:
|
|
13
|
+
"""A parsed Server-Sent Events frame.
|
|
14
|
+
|
|
15
|
+
The parser intentionally keeps this structure provider-neutral so native
|
|
16
|
+
Responses, Messages, and GenerateContent streaming paths can share it.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
event: str
|
|
20
|
+
data: str
|
|
21
|
+
event_id: str | None = None
|
|
22
|
+
retry: int | None = None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def iter_sse_frames(
|
|
26
|
+
lines: Iterable[str | bytes],
|
|
27
|
+
*,
|
|
28
|
+
done_sentinels: tuple[str, ...] = ("[DONE]",),
|
|
29
|
+
include_done: bool = False,
|
|
30
|
+
) -> Iterator[SSEFrame]:
|
|
31
|
+
"""Yield SSE frames from an iterable of response lines.
|
|
32
|
+
|
|
33
|
+
Handles `event:`, multi-line `data:`, comments, blank-line frame
|
|
34
|
+
boundaries, optional `id:` / `retry:`, and `[DONE]`-style sentinels.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
event = "message"
|
|
38
|
+
event_id: str | None = None
|
|
39
|
+
retry: int | None = None
|
|
40
|
+
data_lines: list[str] = []
|
|
41
|
+
|
|
42
|
+
def _flush() -> SSEFrame | None:
|
|
43
|
+
nonlocal event, event_id, retry, data_lines
|
|
44
|
+
if not data_lines and event == "message" and event_id is None and retry is None:
|
|
45
|
+
return None
|
|
46
|
+
frame = SSEFrame(
|
|
47
|
+
event=event or "message",
|
|
48
|
+
data="\n".join(data_lines),
|
|
49
|
+
event_id=event_id,
|
|
50
|
+
retry=retry,
|
|
51
|
+
)
|
|
52
|
+
event = "message"
|
|
53
|
+
event_id = None
|
|
54
|
+
retry = None
|
|
55
|
+
data_lines = []
|
|
56
|
+
return frame
|
|
57
|
+
|
|
58
|
+
for raw_line in lines:
|
|
59
|
+
if isinstance(raw_line, bytes):
|
|
60
|
+
line = raw_line.decode("utf-8", errors="replace")
|
|
61
|
+
else:
|
|
62
|
+
line = raw_line
|
|
63
|
+
line = line.rstrip("\r\n")
|
|
64
|
+
if line == "":
|
|
65
|
+
frame = _flush()
|
|
66
|
+
if frame is None:
|
|
67
|
+
continue
|
|
68
|
+
if frame.data.strip() in done_sentinels and not include_done:
|
|
69
|
+
return
|
|
70
|
+
yield frame
|
|
71
|
+
continue
|
|
72
|
+
if line.startswith(":"):
|
|
73
|
+
continue
|
|
74
|
+
|
|
75
|
+
if ":" in line:
|
|
76
|
+
field, value = line.split(":", 1)
|
|
77
|
+
if value.startswith(" "):
|
|
78
|
+
value = value[1:]
|
|
79
|
+
else:
|
|
80
|
+
field, value = line, ""
|
|
81
|
+
|
|
82
|
+
if field == "event":
|
|
83
|
+
event = value
|
|
84
|
+
elif field == "data":
|
|
85
|
+
data_lines.append(value)
|
|
86
|
+
elif field == "id":
|
|
87
|
+
event_id = value
|
|
88
|
+
elif field == "retry":
|
|
89
|
+
try:
|
|
90
|
+
retry = int(value)
|
|
91
|
+
except ValueError:
|
|
92
|
+
retry = None
|
|
93
|
+
|
|
94
|
+
frame = _flush()
|
|
95
|
+
if frame is not None:
|
|
96
|
+
if frame.data.strip() in done_sentinels and not include_done:
|
|
97
|
+
return
|
|
98
|
+
yield frame
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def parse_sse_json_frame(frame: SSEFrame, *, stream_name: str) -> Any:
|
|
102
|
+
"""Parse a frame's JSON payload with a deterministic provider error."""
|
|
103
|
+
|
|
104
|
+
try:
|
|
105
|
+
return json.loads(frame.data)
|
|
106
|
+
except json.JSONDecodeError as exc:
|
|
107
|
+
event_suffix = f" event={frame.event!r}" if frame.event else ""
|
|
108
|
+
raise LLMError(f"{stream_name} emitted malformed JSON{event_suffix}: {exc.msg}") from exc
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
ANTHROPIC_DEPRECATED_SAMPLING_PARAMETERS = "anthropic_sampling_parameters_deprecated"
|
|
6
|
+
GEMINI_3_DEFAULT_TEMPERATURE = "gemini_3_default_temperature"
|
|
7
|
+
DEEPSEEK_THINKING_TEMPERATURE_UNSUPPORTED = "deepseek_thinking_temperature_unsupported"
|
|
8
|
+
QWEN_QVQ_DEFAULT_TEMPERATURE = "qwen_qvq_default_temperature"
|
|
9
|
+
KIMI_FIXED_TEMPERATURE = "kimi_fixed_temperature"
|
|
10
|
+
|
|
11
|
+
_CLAUDE_OPUS_VERSION_RE = re.compile(r"claude[-_.]opus[-_.](?P<major>\d+)(?:[-_.](?P<minor>\d+))?")
|
|
12
|
+
_CLAUDE_SONNET_VERSION_RE = re.compile(
|
|
13
|
+
r"claude[-_.]sonnet[-_.](?P<major>\d+)(?:[-_.](?P<minor>\d+))?"
|
|
14
|
+
)
|
|
15
|
+
_GEMINI_3_RE = re.compile(r"(?:^|[/.:_-])gemini[-_.]?3(?:[-_.]|$)")
|
|
16
|
+
_DEEPSEEK_V4_RE = re.compile(r"(?:^|[/.:_-])deepseek[-_.]v?4(?:[-_.]|$)")
|
|
17
|
+
_QWEN_QVQ_RE = re.compile(r"(?:^|[/.:_-])qvq(?:[-_.]|$)")
|
|
18
|
+
_CURRENT_KIMI_RE = re.compile(r"(?:^|[/.:_-])kimi[-_.]?k(?:3|2[-_.]?(?:6|7))(?:[-_.]|$)")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def documented_temperature_omit_reason(
|
|
22
|
+
model: str,
|
|
23
|
+
*,
|
|
24
|
+
provider_key: str | None = None,
|
|
25
|
+
thinking_enabled: bool | None = None,
|
|
26
|
+
) -> str | None:
|
|
27
|
+
"""Return the documented reason an optional temperature must be omitted.
|
|
28
|
+
|
|
29
|
+
The checks deliberately use model-family patterns rather than a closed list of
|
|
30
|
+
snapshots so provider aliases, gateway-qualified names, and later compatible
|
|
31
|
+
releases receive the same safe request shape.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
normalized_model = str(model or "").strip().casefold()
|
|
35
|
+
if not normalized_model:
|
|
36
|
+
return None
|
|
37
|
+
|
|
38
|
+
opus = _CLAUDE_OPUS_VERSION_RE.search(normalized_model)
|
|
39
|
+
if opus is not None:
|
|
40
|
+
major = int(opus.group("major"))
|
|
41
|
+
minor_text = opus.group("minor")
|
|
42
|
+
minor = int(minor_text) if minor_text is not None else None
|
|
43
|
+
# Pre-4.6 snapshot IDs use a date after the major version (for example
|
|
44
|
+
# claude-opus-4-20250514). Do not mistake that date for a minor release.
|
|
45
|
+
if major > 4 or (major == 4 and minor is not None and 7 <= minor < 100):
|
|
46
|
+
return ANTHROPIC_DEPRECATED_SAMPLING_PARAMETERS
|
|
47
|
+
|
|
48
|
+
sonnet = _CLAUDE_SONNET_VERSION_RE.search(normalized_model)
|
|
49
|
+
if sonnet is not None and int(sonnet.group("major")) >= 5:
|
|
50
|
+
return ANTHROPIC_DEPRECATED_SAMPLING_PARAMETERS
|
|
51
|
+
|
|
52
|
+
if _GEMINI_3_RE.search(normalized_model):
|
|
53
|
+
return GEMINI_3_DEFAULT_TEMPERATURE
|
|
54
|
+
|
|
55
|
+
if _QWEN_QVQ_RE.search(normalized_model):
|
|
56
|
+
return QWEN_QVQ_DEFAULT_TEMPERATURE
|
|
57
|
+
|
|
58
|
+
normalized_provider = str(provider_key or "").strip().casefold()
|
|
59
|
+
if normalized_provider == "moonshot" and _CURRENT_KIMI_RE.search(normalized_model):
|
|
60
|
+
return KIMI_FIXED_TEMPERATURE
|
|
61
|
+
if (
|
|
62
|
+
normalized_provider == "deepseek"
|
|
63
|
+
and thinking_enabled is not False
|
|
64
|
+
and _DEEPSEEK_V4_RE.search(normalized_model)
|
|
65
|
+
):
|
|
66
|
+
return DEEPSEEK_THINKING_TEMPERATURE_UNSUPPORTED
|
|
67
|
+
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
__all__ = [
|
|
72
|
+
"ANTHROPIC_DEPRECATED_SAMPLING_PARAMETERS",
|
|
73
|
+
"DEEPSEEK_THINKING_TEMPERATURE_UNSUPPORTED",
|
|
74
|
+
"GEMINI_3_DEFAULT_TEMPERATURE",
|
|
75
|
+
"KIMI_FIXED_TEMPERATURE",
|
|
76
|
+
"QWEN_QVQ_DEFAULT_TEMPERATURE",
|
|
77
|
+
"documented_temperature_omit_reason",
|
|
78
|
+
]
|
alysis_code/llm/types.py
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, replace
|
|
4
|
+
from enum import StrEnum
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from ..error_text import sanitize_error_text_for_output
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class LLMError(RuntimeError):
|
|
11
|
+
"""Provider/model error whose public message is safe to persist and display."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, message: object = "") -> None:
|
|
14
|
+
super().__init__(sanitize_error_text_for_output(message))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class LLMStreamNoProgressError(LLMError):
|
|
18
|
+
"""A streaming attempt produced no meaningful provider payload in time."""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class UsageConfidence(StrEnum):
|
|
22
|
+
AUTHORITATIVE = "authoritative"
|
|
23
|
+
REPORTED = "reported"
|
|
24
|
+
ESTIMATED = "estimated"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class UsageSource(StrEnum):
|
|
28
|
+
PROVIDER_RESPONSE = "provider_response"
|
|
29
|
+
PROVIDER_COUNT = "provider_count"
|
|
30
|
+
LOCAL_ESTIMATE = "local_estimate"
|
|
31
|
+
MIXED = "mixed"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class BillingMode(StrEnum):
|
|
35
|
+
"""How the selected route is paid for from the user's perspective."""
|
|
36
|
+
|
|
37
|
+
METERED_API = "metered_api"
|
|
38
|
+
SUBSCRIPTION = "subscription"
|
|
39
|
+
INCLUDED = "included"
|
|
40
|
+
LOCAL = "local"
|
|
41
|
+
UNKNOWN = "unknown"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class CostSource(StrEnum):
|
|
45
|
+
"""Provenance of a per-call dollar amount."""
|
|
46
|
+
|
|
47
|
+
PROVIDER_REPORTED = "provider_reported"
|
|
48
|
+
CATALOG_ESTIMATE = "catalog_estimate"
|
|
49
|
+
SUBSCRIPTION = "subscription"
|
|
50
|
+
INCLUDED = "included"
|
|
51
|
+
LOCAL = "local"
|
|
52
|
+
UNKNOWN = "unknown"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class ReasoningOutputKind(StrEnum):
|
|
56
|
+
"""Visibility class for provider-produced reasoning output.
|
|
57
|
+
|
|
58
|
+
``SUMMARY`` is safe, provider-generated explanatory text intended for
|
|
59
|
+
display. ``PROVIDER_REASONING`` is a provider-visible reasoning channel
|
|
60
|
+
that may contain a much more detailed chain and must not be rendered by
|
|
61
|
+
the default chat trace.
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
SUMMARY = "summary"
|
|
65
|
+
PROVIDER_REASONING = "provider_reasoning"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class AssistantResponsePhase(StrEnum):
|
|
69
|
+
"""Provider-declared lifecycle phase for assistant-visible output."""
|
|
70
|
+
|
|
71
|
+
COMMENTARY = "commentary"
|
|
72
|
+
FINAL_ANSWER = "final_answer"
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass(frozen=True)
|
|
76
|
+
class ReasoningOutput:
|
|
77
|
+
"""Normalized reasoning material returned alongside an assistant answer."""
|
|
78
|
+
|
|
79
|
+
text: str
|
|
80
|
+
kind: ReasoningOutputKind
|
|
81
|
+
provider: str | None = None
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@dataclass(frozen=True)
|
|
85
|
+
class UsageContract:
|
|
86
|
+
"""Protocol-level guarantees after provider usage has been normalized."""
|
|
87
|
+
|
|
88
|
+
response_usage_confidence: UsageConfidence = UsageConfidence.REPORTED
|
|
89
|
+
normalized_output_includes_reasoning: bool = True
|
|
90
|
+
input_token_count_strategy: str = "none"
|
|
91
|
+
billing_mode: BillingMode = BillingMode.METERED_API
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def response_usage_authoritative(self) -> bool:
|
|
95
|
+
return self.response_usage_confidence == UsageConfidence.AUTHORITATIVE
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def supports_input_token_count(self) -> bool:
|
|
99
|
+
return self.input_token_count_strategy != "none"
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@dataclass(frozen=True)
|
|
103
|
+
class InputTokenCount:
|
|
104
|
+
input_tokens: int
|
|
105
|
+
source: UsageSource = UsageSource.PROVIDER_COUNT
|
|
106
|
+
confidence: UsageConfidence = UsageConfidence.AUTHORITATIVE
|
|
107
|
+
raw_provider_usage: dict[str, Any] | None = None
|
|
108
|
+
|
|
109
|
+
def __post_init__(self) -> None:
|
|
110
|
+
if (
|
|
111
|
+
isinstance(self.input_tokens, bool)
|
|
112
|
+
or not isinstance(self.input_tokens, int)
|
|
113
|
+
or self.input_tokens < 0
|
|
114
|
+
):
|
|
115
|
+
raise ValueError("input token count must be a non-negative integer")
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@dataclass(frozen=True)
|
|
119
|
+
class ToolCall:
|
|
120
|
+
id: str
|
|
121
|
+
name: str
|
|
122
|
+
arguments: dict[str, Any]
|
|
123
|
+
provider_metadata: dict[str, Any] | None = None
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@dataclass(frozen=True)
|
|
127
|
+
class LLMUsage:
|
|
128
|
+
prompt_tokens: int | None
|
|
129
|
+
completion_tokens: int | None
|
|
130
|
+
total_tokens: int | None
|
|
131
|
+
cached_prompt_tokens: int | None = None
|
|
132
|
+
input_tokens_uncached: int | None = None
|
|
133
|
+
# True when the uncached figure was computed from other reported counts
|
|
134
|
+
# rather than sent by the provider, so display can avoid claiming it as
|
|
135
|
+
# provider-reported.
|
|
136
|
+
input_tokens_uncached_derived: bool = False
|
|
137
|
+
cache_read_input_tokens: int | None = None
|
|
138
|
+
cache_creation_input_tokens: int | None = None
|
|
139
|
+
cache_creation_5m_input_tokens: int | None = None
|
|
140
|
+
cache_creation_1h_input_tokens: int | None = None
|
|
141
|
+
reasoning_tokens: int | None = None
|
|
142
|
+
provider_cost_usd: float | None = None
|
|
143
|
+
provider_cost_currency: str | None = None
|
|
144
|
+
raw_provider_usage: dict[str, Any] | None = None
|
|
145
|
+
source: UsageSource = UsageSource.PROVIDER_RESPONSE
|
|
146
|
+
confidence: UsageConfidence = UsageConfidence.REPORTED
|
|
147
|
+
output_includes_reasoning: bool = True
|
|
148
|
+
total_includes_reasoning: bool = True
|
|
149
|
+
|
|
150
|
+
def normalized(self) -> LLMUsage:
|
|
151
|
+
"""Return the provider-independent invariant used by accounting.
|
|
152
|
+
|
|
153
|
+
After normalization, completion/output and total include reasoning,
|
|
154
|
+
while ``reasoning_tokens`` remains an informational subset.
|
|
155
|
+
"""
|
|
156
|
+
reasoning = self.reasoning_tokens
|
|
157
|
+
if reasoning is None or reasoning <= 0:
|
|
158
|
+
return replace(
|
|
159
|
+
self,
|
|
160
|
+
output_includes_reasoning=True,
|
|
161
|
+
total_includes_reasoning=True,
|
|
162
|
+
)
|
|
163
|
+
completion = self.completion_tokens
|
|
164
|
+
total = self.total_tokens
|
|
165
|
+
if completion is not None and not self.output_includes_reasoning:
|
|
166
|
+
completion += reasoning
|
|
167
|
+
if total is not None and not self.total_includes_reasoning:
|
|
168
|
+
total += reasoning
|
|
169
|
+
return replace(
|
|
170
|
+
self,
|
|
171
|
+
completion_tokens=completion,
|
|
172
|
+
total_tokens=total,
|
|
173
|
+
output_includes_reasoning=True,
|
|
174
|
+
total_includes_reasoning=True,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
@property
|
|
178
|
+
def cache_read_tokens(self) -> int | None:
|
|
179
|
+
return (
|
|
180
|
+
self.cache_read_input_tokens
|
|
181
|
+
if self.cache_read_input_tokens is not None
|
|
182
|
+
else self.cached_prompt_tokens
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
@dataclass(frozen=True)
|
|
187
|
+
class LLMResponse:
|
|
188
|
+
content: str
|
|
189
|
+
tool_calls: list[ToolCall]
|
|
190
|
+
raw: dict[str, Any]
|
|
191
|
+
response_model: str | None = None
|
|
192
|
+
usage: LLMUsage | None = None
|
|
193
|
+
provider_metadata: dict[str, Any] | None = None
|
|
194
|
+
reasoning: tuple[ReasoningOutput, ...] = ()
|
|
195
|
+
assistant_phase: AssistantResponsePhase | None = None
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"""Normalize usage payloads shared by OpenAI-compatible provider routes.
|
|
2
|
+
|
|
3
|
+
Compatibility describes the transport envelope, not the provider's usage
|
|
4
|
+
schema. This module deliberately decodes documented *shapes* instead of
|
|
5
|
+
maintaining a provider-name switch: gateways and first-party endpoints often
|
|
6
|
+
surface the same cache fields, while a single gateway can return several
|
|
7
|
+
upstream schemas.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import copy
|
|
13
|
+
from collections.abc import Mapping
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
from .types import LLMUsage
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _non_negative_int(value: Any) -> int | None:
|
|
20
|
+
if isinstance(value, bool):
|
|
21
|
+
return None
|
|
22
|
+
try:
|
|
23
|
+
parsed = int(value) if value is not None else None
|
|
24
|
+
except (TypeError, ValueError):
|
|
25
|
+
return None
|
|
26
|
+
return parsed if parsed is None or parsed >= 0 else None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _non_negative_float(value: Any) -> float | None:
|
|
30
|
+
if isinstance(value, bool):
|
|
31
|
+
return None
|
|
32
|
+
try:
|
|
33
|
+
parsed = float(value) if value is not None else None
|
|
34
|
+
except (TypeError, ValueError):
|
|
35
|
+
return None
|
|
36
|
+
return parsed if parsed is None or parsed >= 0 else None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _mapping(value: Any) -> Mapping[str, Any]:
|
|
40
|
+
return value if isinstance(value, Mapping) else {}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _first_int(*values: Any) -> int | None:
|
|
44
|
+
for value in values:
|
|
45
|
+
parsed = _non_negative_int(value)
|
|
46
|
+
if parsed is not None:
|
|
47
|
+
return parsed
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _provider_cost_usd(
|
|
52
|
+
raw: Mapping[str, Any], *, provider_key: str | None
|
|
53
|
+
) -> tuple[float | None, str | None]:
|
|
54
|
+
"""Return only provider amounts whose unit is explicitly USD-compatible.
|
|
55
|
+
|
|
56
|
+
Perplexity-style payloads identify ``total_cost`` and may name the
|
|
57
|
+
currency. OpenRouter documents its numeric ``usage.cost`` as the charged
|
|
58
|
+
account-credit amount; OpenRouter credits are USD-denominated. A generic
|
|
59
|
+
numeric ``cost`` is otherwise ambiguous and is intentionally ignored.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
cost = raw.get("cost")
|
|
63
|
+
if isinstance(cost, Mapping):
|
|
64
|
+
currency = str(cost.get("currency") or "USD").strip().upper()
|
|
65
|
+
if currency != "USD":
|
|
66
|
+
return None, currency or None
|
|
67
|
+
total = _non_negative_float(cost.get("total_cost"))
|
|
68
|
+
if total is None:
|
|
69
|
+
total = _non_negative_float(cost.get("total"))
|
|
70
|
+
return total, "USD" if total is not None else None
|
|
71
|
+
|
|
72
|
+
normalized_provider = str(provider_key or "").strip().lower()
|
|
73
|
+
if normalized_provider == "openrouter":
|
|
74
|
+
amount = _non_negative_float(cost)
|
|
75
|
+
if amount is not None:
|
|
76
|
+
return amount, "USD"
|
|
77
|
+
|
|
78
|
+
currency = str(raw.get("currency") or "").strip().upper()
|
|
79
|
+
if currency in {"", "USD"}:
|
|
80
|
+
amount = _non_negative_float(raw.get("total_cost"))
|
|
81
|
+
if amount is not None:
|
|
82
|
+
return amount, "USD"
|
|
83
|
+
return None, currency or None
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def parse_compatible_usage(
|
|
87
|
+
raw: Any,
|
|
88
|
+
*,
|
|
89
|
+
provider_key: str | None = None,
|
|
90
|
+
responses_shape: bool = False,
|
|
91
|
+
) -> LLMUsage | None:
|
|
92
|
+
"""Decode common provider usage variants without inventing absent values."""
|
|
93
|
+
|
|
94
|
+
if not isinstance(raw, Mapping):
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
prompt_details = _mapping(raw.get("prompt_tokens_details"))
|
|
98
|
+
input_details = _mapping(raw.get("input_tokens_details"))
|
|
99
|
+
completion_details = _mapping(raw.get("completion_tokens_details"))
|
|
100
|
+
output_details = _mapping(raw.get("output_tokens_details"))
|
|
101
|
+
token_group = _mapping(raw.get("tokens"))
|
|
102
|
+
billed_units = _mapping(raw.get("billed_units"))
|
|
103
|
+
|
|
104
|
+
if responses_shape:
|
|
105
|
+
prompt = _first_int(
|
|
106
|
+
raw.get("input_tokens"), raw.get("prompt_tokens"), token_group.get("input_tokens")
|
|
107
|
+
)
|
|
108
|
+
completion = _first_int(
|
|
109
|
+
raw.get("output_tokens"),
|
|
110
|
+
raw.get("completion_tokens"),
|
|
111
|
+
token_group.get("output_tokens"),
|
|
112
|
+
)
|
|
113
|
+
else:
|
|
114
|
+
prompt = _first_int(
|
|
115
|
+
raw.get("prompt_tokens"), raw.get("input_tokens"), token_group.get("input_tokens")
|
|
116
|
+
)
|
|
117
|
+
completion = _first_int(
|
|
118
|
+
raw.get("completion_tokens"),
|
|
119
|
+
raw.get("output_tokens"),
|
|
120
|
+
token_group.get("output_tokens"),
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
cache_read = _first_int(
|
|
124
|
+
prompt_details.get("cached_tokens"),
|
|
125
|
+
input_details.get("cached_tokens"),
|
|
126
|
+
prompt_details.get("cache_read_input_tokens"),
|
|
127
|
+
input_details.get("cache_read_input_tokens"),
|
|
128
|
+
raw.get("cache_read_input_tokens"),
|
|
129
|
+
raw.get("cached_prompt_tokens"),
|
|
130
|
+
raw.get("cached_tokens"),
|
|
131
|
+
raw.get("prompt_cache_hit_tokens"),
|
|
132
|
+
raw.get("cached_prompt_text_tokens"),
|
|
133
|
+
)
|
|
134
|
+
cache_write = _first_int(
|
|
135
|
+
prompt_details.get("cache_write_tokens"),
|
|
136
|
+
input_details.get("cache_write_tokens"),
|
|
137
|
+
prompt_details.get("cache_creation_input_tokens"),
|
|
138
|
+
input_details.get("cache_creation_input_tokens"),
|
|
139
|
+
raw.get("cache_creation_input_tokens"),
|
|
140
|
+
raw.get("cache_write_tokens"),
|
|
141
|
+
)
|
|
142
|
+
cache_creation = _mapping(raw.get("cache_creation"))
|
|
143
|
+
cache_creation_5m = _first_int(
|
|
144
|
+
cache_creation.get("ephemeral_5m_input_tokens"),
|
|
145
|
+
prompt_details.get("cache_creation_5m_input_tokens"),
|
|
146
|
+
input_details.get("cache_creation_5m_input_tokens"),
|
|
147
|
+
)
|
|
148
|
+
cache_creation_1h = _first_int(
|
|
149
|
+
cache_creation.get("ephemeral_1h_input_tokens"),
|
|
150
|
+
prompt_details.get("cache_creation_1h_input_tokens"),
|
|
151
|
+
input_details.get("cache_creation_1h_input_tokens"),
|
|
152
|
+
)
|
|
153
|
+
if cache_write is None:
|
|
154
|
+
write_parts = [
|
|
155
|
+
value for value in (cache_creation_5m, cache_creation_1h) if value is not None
|
|
156
|
+
]
|
|
157
|
+
if write_parts:
|
|
158
|
+
cache_write = sum(write_parts)
|
|
159
|
+
|
|
160
|
+
cache_miss = _first_int(
|
|
161
|
+
raw.get("prompt_cache_miss_tokens"),
|
|
162
|
+
raw.get("input_tokens_uncached"),
|
|
163
|
+
raw.get("uncached_prompt_tokens"),
|
|
164
|
+
prompt_details.get("uncached_tokens"),
|
|
165
|
+
input_details.get("uncached_tokens"),
|
|
166
|
+
)
|
|
167
|
+
cache_miss_derived = False
|
|
168
|
+
if prompt is None and cache_read is not None and cache_miss is not None:
|
|
169
|
+
prompt = cache_read + cache_miss + (cache_write or 0)
|
|
170
|
+
if (
|
|
171
|
+
cache_miss is None
|
|
172
|
+
and prompt is not None
|
|
173
|
+
and (cache_read is not None or cache_write is not None)
|
|
174
|
+
):
|
|
175
|
+
# Implied by the counts the provider did send — usable, but not itself
|
|
176
|
+
# a reported figure.
|
|
177
|
+
cache_miss = max(0, prompt - (cache_read or 0) - (cache_write or 0))
|
|
178
|
+
cache_miss_derived = True
|
|
179
|
+
|
|
180
|
+
reasoning = _first_int(
|
|
181
|
+
output_details.get("reasoning_tokens"),
|
|
182
|
+
completion_details.get("reasoning_tokens"),
|
|
183
|
+
raw.get("reasoning_tokens"),
|
|
184
|
+
)
|
|
185
|
+
total = _first_int(raw.get("total_tokens"))
|
|
186
|
+
provider_cost_usd, provider_cost_currency = _provider_cost_usd(raw, provider_key=provider_key)
|
|
187
|
+
|
|
188
|
+
# Cohere's native schema distinguishes context tokens from billed units.
|
|
189
|
+
# Preserve the provider's context counts above and retain billed units only
|
|
190
|
+
# in raw_provider_usage; substituting them would corrupt context accounting.
|
|
191
|
+
has_billed_units = bool(billed_units)
|
|
192
|
+
if (
|
|
193
|
+
prompt is None
|
|
194
|
+
and completion is None
|
|
195
|
+
and total is None
|
|
196
|
+
and cache_read is None
|
|
197
|
+
and cache_write is None
|
|
198
|
+
and reasoning is None
|
|
199
|
+
and provider_cost_usd is None
|
|
200
|
+
and not has_billed_units
|
|
201
|
+
):
|
|
202
|
+
return None
|
|
203
|
+
|
|
204
|
+
return LLMUsage(
|
|
205
|
+
prompt_tokens=prompt,
|
|
206
|
+
completion_tokens=completion,
|
|
207
|
+
total_tokens=total,
|
|
208
|
+
cached_prompt_tokens=cache_read,
|
|
209
|
+
input_tokens_uncached=cache_miss,
|
|
210
|
+
input_tokens_uncached_derived=cache_miss_derived,
|
|
211
|
+
cache_read_input_tokens=cache_read,
|
|
212
|
+
cache_creation_input_tokens=cache_write,
|
|
213
|
+
cache_creation_5m_input_tokens=cache_creation_5m,
|
|
214
|
+
cache_creation_1h_input_tokens=cache_creation_1h,
|
|
215
|
+
reasoning_tokens=reasoning,
|
|
216
|
+
provider_cost_usd=provider_cost_usd,
|
|
217
|
+
provider_cost_currency=provider_cost_currency,
|
|
218
|
+
raw_provider_usage=copy.deepcopy(dict(raw)),
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
__all__ = ["parse_compatible_usage"]
|