skylos 4.50.0__py3-none-win_amd64.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.
- skylos/__init__.py +39 -0
- skylos/adapters/__init__.py +8 -0
- skylos/adapters/base.py +11 -0
- skylos/adapters/litellm_adapter.py +528 -0
- skylos/agents/__init__.py +1 -0
- skylos/agents/center.py +1326 -0
- skylos/agents/evaluation/__init__.py +35 -0
- skylos/agents/evaluation/_assertion_helpers.py +41 -0
- skylos/agents/evaluation/_loader_support.py +246 -0
- skylos/agents/evaluation/_openai_response.py +297 -0
- skylos/agents/evaluation/_openai_transport.py +254 -0
- skylos/agents/evaluation/_runner_evidence.py +284 -0
- skylos/agents/evaluation/_tool_evaluator.py +324 -0
- skylos/agents/evaluation/evaluator.py +316 -0
- skylos/agents/evaluation/evidence.py +170 -0
- skylos/agents/evaluation/loader.py +548 -0
- skylos/agents/evaluation/observation_loader.py +238 -0
- skylos/agents/evaluation/openai_chat.py +516 -0
- skylos/agents/evaluation/runner.py +680 -0
- skylos/agents/evaluation/schema.py +222 -0
- skylos/agents/payload.py +242 -0
- skylos/agents/service.py +585 -0
- skylos/agents/triage_learner.py +326 -0
- skylos/analysis/__init__.py +1 -0
- skylos/analysis/architecture.py +599 -0
- skylos/analysis/architecture_findings.py +224 -0
- skylos/analysis/architecture_layers.py +206 -0
- skylos/analysis/architecture_support.py +73 -0
- skylos/analysis/ast_cache.py +238 -0
- skylos/analysis/ast_mask.py +161 -0
- skylos/analysis/attribute_refs.py +286 -0
- skylos/analysis/circular_deps.py +475 -0
- skylos/analysis/control_flow.py +218 -0
- skylos/analysis/documented_conventions.py +405 -0
- skylos/analysis/errors.py +71 -0
- skylos/analysis/file_processing.py +565 -0
- skylos/analysis/file_worker.py +584 -0
- skylos/analysis/finding_filter.py +68 -0
- skylos/analysis/implicit_refs.py +172 -0
- skylos/analysis/known_patterns.py +671 -0
- skylos/analysis/module_reachability.py +249 -0
- skylos/analysis/penalties.py +1616 -0
- skylos/analysis/pyproject_entrypoints.py +60 -0
- skylos/analysis/typescript_architecture.py +217 -0
- skylos/analyzer.py +6217 -0
- skylos/api/__init__.py +3356 -0
- skylos/api/_ai_detection.py +177 -0
- skylos/api/_artifacts.py +434 -0
- skylos/api/_contract_check.py +104 -0
- skylos/api/_findings.py +289 -0
- skylos/api/_payloads.py +307 -0
- skylos/api/_pending_uploads.py +599 -0
- skylos/api/_scan_coverage.py +277 -0
- skylos/api/_snippets.py +45 -0
- skylos/api/_source_revision.py +171 -0
- skylos/api/_upload_contract.py +182 -0
- skylos/api/_upload_paths.py +243 -0
- skylos/api/_upload_preflight.py +227 -0
- skylos/api/_upload_transport.py +563 -0
- skylos/api/_urls.py +181 -0
- skylos/api/upload_contract/v1.json +173 -0
- skylos/audit/__init__.py +1 -0
- skylos/audit/candidates.py +967 -0
- skylos/audit/ci.py +204 -0
- skylos/audit/export.py +1243 -0
- skylos/audit/freshness.py +448 -0
- skylos/audit/investigator_tools/__init__.py +48 -0
- skylos/audit/investigator_tools/catalog.py +181 -0
- skylos/audit/investigator_tools/models.py +106 -0
- skylos/audit/investigator_tools/operations.py +278 -0
- skylos/audit/investigator_tools/rendering.py +53 -0
- skylos/audit/investigator_tools/safety.py +69 -0
- skylos/audit/investigator_tools/session.py +262 -0
- skylos/audit/investigator_tools/validation.py +50 -0
- skylos/audit/polyglot.py +428 -0
- skylos/audit/processor.py +1841 -0
- skylos/audit/redaction.py +83 -0
- skylos/audit/revalidation/__init__.py +1 -0
- skylos/audit/revalidation/constants.py +74 -0
- skylos/audit/revalidation/evidence.py +357 -0
- skylos/audit/revalidation/policy.py +304 -0
- skylos/audit/revalidation/verifiers.py +442 -0
- skylos/audit/revalidator.py +303 -0
- skylos/audit/store.py +868 -0
- skylos/audit/types.py +522 -0
- skylos/benchmarks/__init__.py +1 -0
- skylos/benchmarks/_jev_dead_code_dataset.py +658 -0
- skylos/benchmarks/_jev_dead_code_golden.py +168 -0
- skylos/benchmarks/_jev_dead_code_report.py +355 -0
- skylos/benchmarks/_jev_dead_code_types.py +33 -0
- skylos/benchmarks/agent_behavior.py +117 -0
- skylos/benchmarks/agent_review.py +958 -0
- skylos/benchmarks/ai_code_defects.py +1489 -0
- skylos/benchmarks/corpus_ci.py +227 -0
- skylos/benchmarks/dead_code.py +1084 -0
- skylos/benchmarks/deep_audit_logic.py +1144 -0
- skylos/benchmarks/demo_deadcode.py +171 -0
- skylos/benchmarks/framework_corpus.py +409 -0
- skylos/benchmarks/framework_corpus_schema.py +195 -0
- skylos/benchmarks/jev_compare.py +577 -0
- skylos/benchmarks/jev_dead_code.py +488 -0
- skylos/benchmarks/quality.py +527 -0
- skylos/benchmarks/security.py +764 -0
- skylos/benchmarks/verify_benchmark.py +116 -0
- skylos/benchmarks/verify_benchmark_eval.py +310 -0
- skylos/benchmarks/verify_benchmark_report.py +208 -0
- skylos/benchmarks/verify_benchmark_runner.py +273 -0
- skylos/cicd/__init__.py +1 -0
- skylos/cicd/approval.py +924 -0
- skylos/cicd/approval_dependencies.py +1378 -0
- skylos/cicd/approval_github.py +242 -0
- skylos/cicd/evidence.py +994 -0
- skylos/cicd/init_setup.py +331 -0
- skylos/cicd/merge_group.py +478 -0
- skylos/cicd/policy.py +141 -0
- skylos/cicd/review.py +1859 -0
- skylos/cicd/risk_passport.py +363 -0
- skylos/cicd/scope_receipt.py +167 -0
- skylos/cicd/workflow.py +955 -0
- skylos/cli.py +5997 -0
- skylos/cli_core/__init__.py +0 -0
- skylos/cli_core/dispatch.py +141 -0
- skylos/cli_core/main_parser.py +523 -0
- skylos/cloud/__init__.py +1 -0
- skylos/cloud/credentials.py +72 -0
- skylos/cloud/gitlab.py +352 -0
- skylos/cloud/login.py +488 -0
- skylos/cloud/plan_names.py +20 -0
- skylos/cloud/project_context.py +54 -0
- skylos/cloud/shadow_upload.py +524 -0
- skylos/cloud/sync.py +1215 -0
- skylos/cloud/sync_setup.py +331 -0
- skylos/cloud/sync_state.py +294 -0
- skylos/cloud/upload_manifest.py +138 -0
- skylos/cloud/windows_sync.py +447 -0
- skylos/commands/__init__.py +0 -0
- skylos/commands/agent_replay_cmd.py +270 -0
- skylos/commands/agent_standards_policy.py +146 -0
- skylos/commands/agent_test_cmd.py +580 -0
- skylos/commands/agent_verify_cmd.py +765 -0
- skylos/commands/badge_cmd.py +42 -0
- skylos/commands/baseline_cmd.py +159 -0
- skylos/commands/cache_cmd.py +127 -0
- skylos/commands/check_standards_cmd.py +238 -0
- skylos/commands/cicd_cmd.py +655 -0
- skylos/commands/clean_cmd.py +448 -0
- skylos/commands/compare_cmd.py +675 -0
- skylos/commands/contract_cmd.py +256 -0
- skylos/commands/credits_cmd.py +81 -0
- skylos/commands/debt_cmd.py +452 -0
- skylos/commands/defend_cmd.py +862 -0
- skylos/commands/discover_cmd.py +86 -0
- skylos/commands/doctor_cmd.py +408 -0
- skylos/commands/doctor_hooks.py +513 -0
- skylos/commands/done_cmd.py +273 -0
- skylos/commands/feedback_cmd.py +173 -0
- skylos/commands/hook_attribution.py +481 -0
- skylos/commands/hook_cmd.py +2656 -0
- skylos/commands/hook_git_bypass.py +606 -0
- skylos/commands/hook_install_parse.py +1654 -0
- skylos/commands/hook_install_policy.py +97 -0
- skylos/commands/hook_policy.py +488 -0
- skylos/commands/hook_registry_context.py +354 -0
- skylos/commands/hook_shell_commands.py +303 -0
- skylos/commands/hook_shell_filters.py +743 -0
- skylos/commands/hook_shell_grep.py +323 -0
- skylos/commands/hook_shell_lex.py +306 -0
- skylos/commands/hook_shell_reads.py +740 -0
- skylos/commands/image_cmd.py +389 -0
- skylos/commands/ingest_cmd.py +99 -0
- skylos/commands/init_cmd.py +121 -0
- skylos/commands/install_hooks_cmd.py +572 -0
- skylos/commands/install_standards_cmd.py +296 -0
- skylos/commands/key_cmd.py +222 -0
- skylos/commands/lint_cmd.py +48 -0
- skylos/commands/login_cmd.py +11 -0
- skylos/commands/mcp_cmd.py +42 -0
- skylos/commands/preflight_cmd.py +428 -0
- skylos/commands/project_cmd.py +5 -0
- skylos/commands/provenance_cmd.py +184 -0
- skylos/commands/review_cmd.py +606 -0
- skylos/commands/risk_review_cmd.py +569 -0
- skylos/commands/rules_cmd.py +517 -0
- skylos/commands/sbom_cmd.py +115 -0
- skylos/commands/scan_cmd.py +1995 -0
- skylos/commands/sonar_cmd.py +80 -0
- skylos/commands/suite_cmd.py +563 -0
- skylos/commands/sync_cmd.py +5 -0
- skylos/commands/trivy_image_cmd.py +219 -0
- skylos/commands/upload_cmd.py +170 -0
- skylos/commands/verify_cmd.py +413 -0
- skylos/commands/verify_verdict_cmd.py +544 -0
- skylos/commands/warm_cache_cmd.py +65 -0
- skylos/commands/whitelist_cmd.py +113 -0
- skylos/commands/whoami_cmd.py +25 -0
- skylos/config.py +1120 -0
- skylos/constants.py +230 -0
- skylos/contracts/__init__.py +49 -0
- skylos/contracts/loader.py +363 -0
- skylos/contracts/metadata.py +211 -0
- skylos/contracts/routes.py +424 -0
- skylos/contracts/schema.py +64 -0
- skylos/core/__init__.py +1 -0
- skylos/core/api_symbol_truth.py +390 -0
- skylos/core/baseline.py +198 -0
- skylos/core/baseline_source.py +162 -0
- skylos/core/ci_env.py +131 -0
- skylos/core/cli_shared.py +299 -0
- skylos/core/contribution_events.py +157 -0
- skylos/core/contribution_settings.py +61 -0
- skylos/core/evidence_contract.py +587 -0
- skylos/core/fast.py +28 -0
- skylos/core/fast_paths.py +81 -0
- skylos/core/file_discovery.py +494 -0
- skylos/core/gate_incomplete.py +720 -0
- skylos/core/gate_outcome.py +194 -0
- skylos/core/gatekeeper.py +1331 -0
- skylos/core/generated_files.py +389 -0
- skylos/core/git_context.py +243 -0
- skylos/core/git_safety.py +52 -0
- skylos/core/go_api_surface.py +576 -0
- skylos/core/go_module_manifest.py +133 -0
- skylos/core/grep_cache.py +255 -0
- skylos/core/grep_search_state.py +78 -0
- skylos/core/grep_verify.py +936 -0
- skylos/core/grep_verify_common.py +3018 -0
- skylos/core/grep_verify_language_strategies.py +404 -0
- skylos/core/grep_verify_parallel.py +90 -0
- skylos/core/grep_verify_python_strategy.py +527 -0
- skylos/core/grep_verify_strategies.py +131 -0
- skylos/core/java_api_surface.py +508 -0
- skylos/core/js_api_surface.py +502 -0
- skylos/core/js_api_surface_exports.py +685 -0
- skylos/core/js_api_surface_members.py +485 -0
- skylos/core/js_api_surface_utils.py +207 -0
- skylos/core/js_ast.py +109 -0
- skylos/core/linter.py +33 -0
- skylos/core/pr_impact.py +224 -0
- skylos/core/python_api_surface.py +704 -0
- skylos/core/python_distributions.py +311 -0
- skylos/core/python_requirements.py +671 -0
- skylos/core/reference_index.py +213 -0
- skylos/core/reference_index_store.py +335 -0
- skylos/core/report_context.py +473 -0
- skylos/core/result_cache.py +562 -0
- skylos/core/review_context.py +1215 -0
- skylos/core/review_decisions.py +3892 -0
- skylos/core/safe_cache_io.py +776 -0
- skylos/core/sca_baseline.py +279 -0
- skylos/core/sca_coverage.py +218 -0
- skylos/core/suite.py +408 -0
- skylos/core/test_paths.py +201 -0
- skylos/core/tracer.py +215 -0
- skylos/core/verification_coverage.py +153 -0
- skylos/core/verification_registry.py +476 -0
- skylos/core/verify_change_schema.py +819 -0
- skylos/deadcode/__init__.py +1 -0
- skylos/deadcode/_reachability_bindings.py +228 -0
- skylos/deadcode/_reachability_graph.py +249 -0
- skylos/deadcode/_reachability_receivers.py +188 -0
- skylos/deadcode/_reachability_scopes.py +62 -0
- skylos/deadcode/_reachability_visitor.py +648 -0
- skylos/deadcode/browser_refs.py +747 -0
- skylos/deadcode/collect.py +19 -0
- skylos/deadcode/confidence.py +228 -0
- skylos/deadcode/config_entrypoints.py +312 -0
- skylos/deadcode/django_framework_liveness.py +425 -0
- skylos/deadcode/django_template_liveness.py +269 -0
- skylos/deadcode/evidence.py +934 -0
- skylos/deadcode/external_protocols.py +625 -0
- skylos/deadcode/finding_evidence.py +142 -0
- skylos/deadcode/framework_liveness.py +452 -0
- skylos/deadcode/java_fxml_refs.py +314 -0
- skylos/deadcode/liveness.py +761 -0
- skylos/deadcode/plugin_registry.py +606 -0
- skylos/deadcode/python_ast.py +35 -0
- skylos/deadcode/reachability.py +431 -0
- skylos/deadcode/review_proof.py +345 -0
- skylos/deadcode/signature_contracts.py +556 -0
- skylos/deadcode/web_framework_ast.py +69 -0
- skylos/deadcode/web_framework_liveness.py +684 -0
- skylos/debt/__init__.py +73 -0
- skylos/debt/advisor.py +291 -0
- skylos/debt/baseline.py +467 -0
- skylos/debt/engine.py +350 -0
- skylos/debt/policy.py +173 -0
- skylos/debt/report.py +389 -0
- skylos/debt/result.py +137 -0
- skylos/debt/scoring.py +300 -0
- skylos/defend/__init__.py +38 -0
- skylos/defend/attestation.py +240 -0
- skylos/defend/engine.py +83 -0
- skylos/defend/frameworks.py +439 -0
- skylos/defend/owasp.py +318 -0
- skylos/defend/plugin.py +64 -0
- skylos/defend/plugins/__init__.py +39 -0
- skylos/defend/plugins/cost_controls.py +39 -0
- skylos/defend/plugins/input_length_limit.py +38 -0
- skylos/defend/plugins/logging_present.py +35 -0
- skylos/defend/plugins/model_pinned.py +45 -0
- skylos/defend/plugins/no_dangerous_sink.py +38 -0
- skylos/defend/plugins/output_pii_filter.py +38 -0
- skylos/defend/plugins/output_validation.py +89 -0
- skylos/defend/plugins/prompt_delimiter.py +38 -0
- skylos/defend/plugins/rag_context_isolation.py +38 -0
- skylos/defend/plugins/rate_limiting.py +39 -0
- skylos/defend/plugins/tool_schema_present.py +41 -0
- skylos/defend/plugins/tool_scope.py +45 -0
- skylos/defend/plugins/untrusted_input_to_prompt.py +43 -0
- skylos/defend/policy.py +149 -0
- skylos/defend/report.py +670 -0
- skylos/defend/result.py +68 -0
- skylos/defend/scoring.py +125 -0
- skylos/discover/__init__.py +15 -0
- skylos/discover/detector.py +1600 -0
- skylos/discover/detector_typescript.py +633 -0
- skylos/discover/graph.py +97 -0
- skylos/discover/integration.py +77 -0
- skylos/discover/report.py +66 -0
- skylos/discover/semantics/__init__.py +5 -0
- skylos/discover/semantics/python_output_flow.py +1029 -0
- skylos/discover/semantics/vocabulary.py +38 -0
- skylos/discover/taint.py +209 -0
- skylos/done/__init__.py +10 -0
- skylos/done/answer_sites.py +2402 -0
- skylos/done/base.py +525 -0
- skylos/done/checks.py +1649 -0
- skylos/done/ci_checks.py +1262 -0
- skylos/done/config.py +370 -0
- skylos/done/engine.py +139 -0
- skylos/done/expected_answers.py +1231 -0
- skylos/done/feature_removal.py +600 -0
- skylos/done/inventory.py +1483 -0
- skylos/done/js_inventory.py +2408 -0
- skylos/done/js_test_config.py +1800 -0
- skylos/done/lint_config.py +2107 -0
- skylos/done/mutation.py +779 -0
- skylos/done/orphans.py +1538 -0
- skylos/done/preexisting.py +1498 -0
- skylos/done/pytest_probe.py +410 -0
- skylos/done/receipt.py +971 -0
- skylos/done/runner.py +1085 -0
- skylos/done/security_controls.py +126 -0
- skylos/done/session.py +698 -0
- skylos/done/special_cases.py +931 -0
- skylos/done/suppressions.py +1281 -0
- skylos/done/test_config.py +2263 -0
- skylos/engines/__init__.py +0 -0
- skylos/engines/go/skylos-go.exe +0 -0
- skylos/engines/go_contract.py +47 -0
- skylos/engines/go_runner.py +202 -0
- skylos/entry.py +156 -0
- skylos/integrations/__init__.py +1 -0
- skylos/integrations/ingest.py +344 -0
- skylos/integrations/shadow.py +2239 -0
- skylos/integrations/sonar.py +155 -0
- skylos/integrations/trivy_image.py +566 -0
- skylos/llm/__init__.py +0 -0
- skylos/llm/_grounding.py +466 -0
- skylos/llm/agents.py +808 -0
- skylos/llm/analyzer.py +713 -0
- skylos/llm/cleanup_orchestrator.py +572 -0
- skylos/llm/cleanup_standards.md +60 -0
- skylos/llm/completion_diagnostics.py +54 -0
- skylos/llm/context.py +772 -0
- skylos/llm/dead_code_verifier.py +123 -0
- skylos/llm/executor.py +360 -0
- skylos/llm/feedback.py +186 -0
- skylos/llm/finding_evidence.py +343 -0
- skylos/llm/finding_verification.py +270 -0
- skylos/llm/graph.py +606 -0
- skylos/llm/harness/__init__.py +73 -0
- skylos/llm/harness/ai_defect_challenge.py +343 -0
- skylos/llm/harness/ai_defect_challenge_decisions.py +270 -0
- skylos/llm/harness/ai_defect_challenge_models.py +98 -0
- skylos/llm/harness/ai_defect_challenge_probes.py +138 -0
- skylos/llm/harness/ai_defect_challenge_prompt.py +62 -0
- skylos/llm/harness/ai_defect_challenge_proof.py +205 -0
- skylos/llm/harness/guards.py +42 -0
- skylos/llm/harness/replay.py +1043 -0
- skylos/llm/harness/runner.py +417 -0
- skylos/llm/harness/tools.py +121 -0
- skylos/llm/harness/trace.py +218 -0
- skylos/llm/harness/types.py +253 -0
- skylos/llm/harness/verification.py +109 -0
- skylos/llm/investigator/__init__.py +53 -0
- skylos/llm/investigator/actions.py +155 -0
- skylos/llm/investigator/adapter.py +103 -0
- skylos/llm/investigator/evidence.py +264 -0
- skylos/llm/investigator/findings.py +343 -0
- skylos/llm/investigator/models.py +186 -0
- skylos/llm/investigator/orchestrator.py +564 -0
- skylos/llm/investigator/prompts.py +95 -0
- skylos/llm/investigator/protocol.py +363 -0
- skylos/llm/investigator/repository_tools.py +130 -0
- skylos/llm/investigator/reviewer_packs.py +385 -0
- skylos/llm/investigator/source_bounds.py +12 -0
- skylos/llm/jev_triage.py +216 -0
- skylos/llm/liveness.py +323 -0
- skylos/llm/merger.py +236 -0
- skylos/llm/orchestrator.py +371 -0
- skylos/llm/planner.py +176 -0
- skylos/llm/prompts.py +569 -0
- skylos/llm/repo_activation.py +472 -0
- skylos/llm/review/__init__.py +5 -0
- skylos/llm/review/refuters.py +423 -0
- skylos/llm/review/routing.py +330 -0
- skylos/llm/review/static_findings.py +491 -0
- skylos/llm/runtime.py +96 -0
- skylos/llm/schemas.py +469 -0
- skylos/llm/security_taskflow.py +1062 -0
- skylos/llm/security_verifier.py +682 -0
- skylos/llm/threat_trace.py +642 -0
- skylos/llm/ui.py +501 -0
- skylos/llm/validator.py +413 -0
- skylos/llm/verification/__init__.py +1 -0
- skylos/llm/verification/candidate_selection.py +218 -0
- skylos/llm/verification/completion_phases.py +422 -0
- skylos/llm/verification/entry_points.py +496 -0
- skylos/llm/verification/jev_precheck.py +219 -0
- skylos/llm/verification/llm.py +417 -0
- skylos/llm/verification/phases.py +154 -0
- skylos/llm/verification/runtime.py +47 -0
- skylos/llm/verification/suppression_audit.py +173 -0
- skylos/llm/verification/survivors.py +491 -0
- skylos/llm/verification/types.py +74 -0
- skylos/llm/verification/verify_findings.py +271 -0
- skylos/llm/verify_orchestrator.py +2930 -0
- skylos/pipeline.py +1254 -0
- skylos/plugins/__init__.py +1 -0
- skylos/plugins/pytest_unused_fixtures.py +188 -0
- skylos/preflight/__init__.py +315 -0
- skylos/preflight/evaluator.py +714 -0
- skylos/preflight/inspector.py +1636 -0
- skylos/preflight/models.py +305 -0
- skylos/preflight/profile.py +58 -0
- skylos/remediation/__init__.py +1 -0
- skylos/remediation/codemods.py +389 -0
- skylos/remediation/fixgen.py +593 -0
- skylos/remediation/regression_tests.py +250 -0
- skylos/remediation/safety.py +35 -0
- skylos/reporting/__init__.py +1 -0
- skylos/reporting/architecture_result.py +412 -0
- skylos/reporting/assessment.py +373 -0
- skylos/reporting/assessment_display.py +286 -0
- skylos/reporting/attribution_evidence.py +591 -0
- skylos/reporting/container_sarif.py +111 -0
- skylos/reporting/dead_code_result.py +337 -0
- skylos/reporting/finding_categories.py +23 -0
- skylos/reporting/finding_types.py +176 -0
- skylos/reporting/github_annotations.py +162 -0
- skylos/reporting/gitlab.py +469 -0
- skylos/reporting/grader.py +356 -0
- skylos/reporting/llm_report.py +304 -0
- skylos/reporting/provenance.py +1180 -0
- skylos/reporting/report_scope.py +126 -0
- skylos/reporting/result_builder.py +598 -0
- skylos/reporting/rollups.py +162 -0
- skylos/reporting/sarif.py +663 -0
- skylos/reporting/sbom.py +331 -0
- skylos/reporting/sca_summary.py +152 -0
- skylos/reporting/spdx.py +292 -0
- skylos/rules/__init__.py +0 -0
- skylos/rules/ai_defect/__init__.py +47 -0
- skylos/rules/ai_defect/api_signature_hallucination.py +932 -0
- skylos/rules/ai_defect/api_surface_drift.py +144 -0
- skylos/rules/ai_defect/assertion_weakening.py +1155 -0
- skylos/rules/ai_defect/ci_permission_expansion.py +612 -0
- skylos/rules/ai_defect/dependency_bump_scan.py +273 -0
- skylos/rules/ai_defect/dependency_hallucination.py +3655 -0
- skylos/rules/ai_defect/dependency_truth.py +108 -0
- skylos/rules/ai_defect/dependency_version_bump.py +798 -0
- skylos/rules/ai_defect/diff_dependencies.py +956 -0
- skylos/rules/ai_defect/go_api_hallucination.py +479 -0
- skylos/rules/ai_defect/guardrails/__init__.py +41 -0
- skylos/rules/ai_defect/guardrails/core.py +297 -0
- skylos/rules/ai_defect/guardrails/markers.py +615 -0
- skylos/rules/ai_defect/guardrails/reviewer.py +601 -0
- skylos/rules/ai_defect/guardrails/scan.py +251 -0
- skylos/rules/ai_defect/install_command.py +598 -0
- skylos/rules/ai_defect/installed_modules_cache.py +84 -0
- skylos/rules/ai_defect/java_api_hallucination.py +501 -0
- skylos/rules/ai_defect/java_api_hallucination_reporting.py +226 -0
- skylos/rules/ai_defect/java_api_hallucination_resolution.py +209 -0
- skylos/rules/ai_defect/js_api_hallucination.py +524 -0
- skylos/rules/ai_defect/js_api_namespace_references.py +150 -0
- skylos/rules/ai_defect/js_api_reference_model.py +90 -0
- skylos/rules/ai_defect/js_api_references.py +401 -0
- skylos/rules/ai_defect/manifest_dependency_hallucination.py +2262 -0
- skylos/rules/ai_defect/module_facts_index.py +249 -0
- skylos/rules/ai_defect/phantom_call.py +92 -0
- skylos/rules/ai_defect/phantom_decorator.py +98 -0
- skylos/rules/ai_defect/phantom_refs.py +2616 -0
- skylos/rules/ai_defect/pipreqs_import_mapping.txt +1164 -0
- skylos/rules/ai_defect/pypi_wheel_modules.py +544 -0
- skylos/rules/ai_defect/python_api_hallucination.py +665 -0
- skylos/rules/ai_defect/test_impact.py +342 -0
- skylos/rules/base.py +23 -0
- skylos/rules/catalog.py +1492 -0
- skylos/rules/compliance.py +185 -0
- skylos/rules/config/__init__.py +3 -0
- skylos/rules/config/cicd/__init__.py +4 -0
- skylos/rules/config/cicd/ai_agent_expressions.py +661 -0
- skylos/rules/config/cicd/ai_agent_versions.py +347 -0
- skylos/rules/config/cicd/ai_agents.py +1838 -0
- skylos/rules/config/cicd/github_actions.py +2518 -0
- skylos/rules/config/cicd/gitlab_ci.py +1062 -0
- skylos/rules/config/cicd/runner_labels.py +202 -0
- skylos/rules/config/cicd/yaml_source.py +267 -0
- skylos/rules/config/container/__init__.py +3 -0
- skylos/rules/config/container/dockerfile.py +438 -0
- skylos/rules/config/deployment/__init__.py +3 -0
- skylos/rules/config/deployment/exposure.py +2752 -0
- skylos/rules/config/edge/__init__.py +12 -0
- skylos/rules/config/edge/docker_compose.py +538 -0
- skylos/rules/config/edge/systemd.py +562 -0
- skylos/rules/config/findings.py +38 -0
- skylos/rules/config/gpu/__init__.py +3 -0
- skylos/rules/config/gpu/compatibility.py +2299 -0
- skylos/rules/config/registry.py +147 -0
- skylos/rules/custom.py +388 -0
- skylos/rules/danger/__init__.py +0 -0
- skylos/rules/danger/calls.py +641 -0
- skylos/rules/danger/danger.py +649 -0
- skylos/rules/danger/danger_access/__init__.py +0 -0
- skylos/rules/danger/danger_access/access_flow.py +67 -0
- skylos/rules/danger/danger_agent/__init__.py +2 -0
- skylos/rules/danger/danger_agent/tool_privilege.py +365 -0
- skylos/rules/danger/danger_cmd/__init__.py +0 -0
- skylos/rules/danger/danger_cmd/cmd_flow.py +90 -0
- skylos/rules/danger/danger_cors/__init__.py +0 -0
- skylos/rules/danger/danger_cors/cors_flow.py +185 -0
- skylos/rules/danger/danger_fs/__init__.py +0 -0
- skylos/rules/danger/danger_fs/path_flow.py +1115 -0
- skylos/rules/danger/danger_fs/pytest_paths.py +355 -0
- skylos/rules/danger/danger_jwt/__init__.py +0 -0
- skylos/rules/danger/danger_jwt/jwt_flow.py +72 -0
- skylos/rules/danger/danger_jwt/jwt_provenance.py +676 -0
- skylos/rules/danger/danger_llm/__init__.py +2 -0
- skylos/rules/danger/danger_llm/consumption.py +357 -0
- skylos/rules/danger/danger_llm/llm_flow.py +384 -0
- skylos/rules/danger/danger_llm/sensitivity.py +96 -0
- skylos/rules/danger/danger_llm/utils.py +196 -0
- skylos/rules/danger/danger_mcp/__init__.py +0 -0
- skylos/rules/danger/danger_mcp/mcp_flow.py +355 -0
- skylos/rules/danger/danger_ml/__init__.py +1 -0
- skylos/rules/danger/danger_ml/model_deserialization.py +363 -0
- skylos/rules/danger/danger_net/__init__.py +0 -0
- skylos/rules/danger/danger_net/ssrf_flow.py +2334 -0
- skylos/rules/danger/danger_redirect/__init__.py +0 -0
- skylos/rules/danger/danger_redirect/redirect_flow.py +161 -0
- skylos/rules/danger/danger_sql/__init__.py +0 -0
- skylos/rules/danger/danger_sql/sql_flow.py +980 -0
- skylos/rules/danger/danger_sql/sql_raw_flow.py +223 -0
- skylos/rules/danger/danger_sql/sqlalchemy_provenance.py +311 -0
- skylos/rules/danger/danger_web/__init__.py +0 -0
- skylos/rules/danger/danger_web/xss_flow.py +274 -0
- skylos/rules/danger/danger_webhook/__init__.py +1 -0
- skylos/rules/danger/danger_webhook/webhook_flow.py +239 -0
- skylos/rules/danger/module_namespace.py +485 -0
- skylos/rules/danger/taint.py +293 -0
- skylos/rules/danger/untrusted_sources.py +907 -0
- skylos/rules/quality/__init__.py +0 -0
- skylos/rules/quality/_function_defaults.py +208 -0
- skylos/rules/quality/_object_auth.py +758 -0
- skylos/rules/quality/_protocols.py +754 -0
- skylos/rules/quality/_readability.py +602 -0
- skylos/rules/quality/async_blocking.py +138 -0
- skylos/rules/quality/class_size.py +212 -0
- skylos/rules/quality/clones.py +551 -0
- skylos/rules/quality/code_health.py +221 -0
- skylos/rules/quality/cohesion.py +435 -0
- skylos/rules/quality/complexity.py +280 -0
- skylos/rules/quality/concurrency.py +348 -0
- skylos/rules/quality/coupling.py +477 -0
- skylos/rules/quality/logic.py +63 -0
- skylos/rules/quality/logic_foundation.py +2036 -0
- skylos/rules/quality/logic_maintainability.py +857 -0
- skylos/rules/quality/logic_security.py +2138 -0
- skylos/rules/quality/nesting.py +117 -0
- skylos/rules/quality/performance.py +315 -0
- skylos/rules/quality/policy.py +324 -0
- skylos/rules/quality/practices.py +624 -0
- skylos/rules/quality/regression.py +929 -0
- skylos/rules/quality/standards.py +216 -0
- skylos/rules/quality/structure.py +141 -0
- skylos/rules/quality/unreachable.py +229 -0
- skylos/rules/quality/unused_deps.py +742 -0
- skylos/rules/sca/__init__.py +0 -0
- skylos/rules/sca/advisory_metadata.py +310 -0
- skylos/rules/sca/bun_lockfile.py +289 -0
- skylos/rules/sca/licenses.py +982 -0
- skylos/rules/sca/lockfile_types.py +42 -0
- skylos/rules/sca/npm_lockfile.py +520 -0
- skylos/rules/sca/osv_client.py +356 -0
- skylos/rules/sca/pipfile_lockfile.py +239 -0
- skylos/rules/sca/pnpm_lockfile.py +1070 -0
- skylos/rules/sca/poetry_lockfile.py +466 -0
- skylos/rules/sca/publisher_changes.py +317 -0
- skylos/rules/sca/reachability.py +132 -0
- skylos/rules/sca/uv_lockfile.py +586 -0
- skylos/rules/sca/vulnerability_scanner.py +2182 -0
- skylos/rules/sca/yarn_lockfile.py +502 -0
- skylos/rules/secret_shapes.py +617 -0
- skylos/rules/secrets.py +4544 -0
- skylos/rules/vibe_dictionary.py +426 -0
- skylos/scale/__init__.py +0 -0
- skylos/scale/parallel_static.py +411 -0
- skylos/security/__init__.py +1 -0
- skylos/security/agent_instruction_paths.py +148 -0
- skylos/security/auth_regressions.py +352 -0
- skylos/security/canonicalize.py +166 -0
- skylos/security/command_guard.py +90 -0
- skylos/security/command_guard_exfil.py +680 -0
- skylos/security/command_guard_parse.py +350 -0
- skylos/security/command_guard_paths.py +227 -0
- skylos/security/command_guard_policy.py +376 -0
- skylos/security/command_guard_types.py +58 -0
- skylos/security/contracts.py +467 -0
- skylos/security/injection_scanner.py +585 -0
- skylos/security/regression_diff.py +146 -0
- skylos/security/regression_gate.py +86 -0
- skylos/ui/__init__.py +1 -0
- skylos/ui/clipboard.py +75 -0
- skylos/ui/dead_code_evidence.py +239 -0
- skylos/ui/help.py +626 -0
- skylos/ui/nudge.py +189 -0
- skylos/ui/quality_advice.py +35 -0
- skylos/ui/rich_report.py +1097 -0
- skylos/ui/scope_notes.py +107 -0
- skylos/ui/security_evidence.py +81 -0
- skylos/ui/terminal_report.py +873 -0
- skylos/ui/tour.py +131 -0
- skylos/ui/tui.py +918 -0
- skylos/verdict.py +459 -0
- skylos/verification/__init__.py +1 -0
- skylos/verification/behavior.py +786 -0
- skylos/verification/changes.py +92 -0
- skylos/verification/comparison.py +470 -0
- skylos/verification/context.py +440 -0
- skylos/verification/explanations.py +160 -0
- skylos/verification/refactor.py +460 -0
- skylos/verification/render.py +248 -0
- skylos/verification/windows_snapshot.py +448 -0
- skylos/verify_change.py +828 -0
- skylos/visitors/__init__.py +0 -0
- skylos/visitors/base.py +3143 -0
- skylos/visitors/framework_aware.py +1078 -0
- skylos/visitors/languages/__init__.py +0 -0
- skylos/visitors/languages/cpp/__init__.py +117 -0
- skylos/visitors/languages/cpp/core.py +199 -0
- skylos/visitors/languages/csharp/__init__.py +82 -0
- skylos/visitors/languages/csharp/_lex.py +144 -0
- skylos/visitors/languages/csharp/core.py +793 -0
- skylos/visitors/languages/csharp/danger.py +640 -0
- skylos/visitors/languages/csharp/quality.py +121 -0
- skylos/visitors/languages/csharp/reachability.py +286 -0
- skylos/visitors/languages/dart/__init__.py +89 -0
- skylos/visitors/languages/dart/core.py +750 -0
- skylos/visitors/languages/dart/danger.py +248 -0
- skylos/visitors/languages/go/__init__.py +5 -0
- skylos/visitors/languages/go/go.py +241 -0
- skylos/visitors/languages/go/quality.py +217 -0
- skylos/visitors/languages/java/__init__.py +90 -0
- skylos/visitors/languages/java/core.py +715 -0
- skylos/visitors/languages/java/danger.py +1855 -0
- skylos/visitors/languages/java/flow.py +2345 -0
- skylos/visitors/languages/java/properties.py +239 -0
- skylos/visitors/languages/java/quality.py +286 -0
- skylos/visitors/languages/java/source_helpers.py +359 -0
- skylos/visitors/languages/kotlin/__init__.py +104 -0
- skylos/visitors/languages/kotlin/_lex.py +35 -0
- skylos/visitors/languages/kotlin/core.py +284 -0
- skylos/visitors/languages/php/__init__.py +103 -0
- skylos/visitors/languages/php/blade.py +61 -0
- skylos/visitors/languages/php/core.py +1438 -0
- skylos/visitors/languages/php/danger.py +214 -0
- skylos/visitors/languages/php/laravel.py +351 -0
- skylos/visitors/languages/rust/__init__.py +89 -0
- skylos/visitors/languages/rust/core.py +1656 -0
- skylos/visitors/languages/rust/danger.py +267 -0
- skylos/visitors/languages/shell/__init__.py +80 -0
- skylos/visitors/languages/shell/danger.py +582 -0
- skylos/visitors/languages/statement_scan.py +162 -0
- skylos/visitors/languages/typescript/__init__.py +372 -0
- skylos/visitors/languages/typescript/analysis.py +2270 -0
- skylos/visitors/languages/typescript/conventions.py +305 -0
- skylos/visitors/languages/typescript/core.py +1537 -0
- skylos/visitors/languages/typescript/danger.py +10784 -0
- skylos/visitors/languages/typescript/esbuild_static.py +764 -0
- skylos/visitors/languages/typescript/express_taint.py +881 -0
- skylos/visitors/languages/typescript/framework.py +553 -0
- skylos/visitors/languages/typescript/grammar_gaps.py +646 -0
- skylos/visitors/languages/typescript/nextjs.py +520 -0
- skylos/visitors/languages/typescript/quality.py +647 -0
- skylos/visitors/languages/typescript/quality_signals.py +2464 -0
- skylos/visitors/languages/typescript/resolve.py +681 -0
- skylos/visitors/languages/typescript/safe_glob.py +83 -0
- skylos/visitors/languages/typescript/security_flow.py +3564 -0
- skylos/visitors/languages/typescript/security_proofs.py +3651 -0
- skylos/visitors/languages/typescript/source_lines.py +19 -0
- skylos/visitors/languages/typescript/tool_configs.py +68 -0
- skylos/visitors/languages/typescript/type_safety.py +633 -0
- skylos/visitors/languages/typescript/workspace.py +567 -0
- skylos/visitors/registration_decorators.py +169 -0
- skylos/visitors/test_aware.py +76 -0
- skylos-4.50.0.dist-info/METADATA +1357 -0
- skylos-4.50.0.dist-info/RECORD +717 -0
- skylos-4.50.0.dist-info/WHEEL +5 -0
- skylos-4.50.0.dist-info/entry_points.txt +2 -0
- skylos-4.50.0.dist-info/licenses/AUTHORS.md +1 -0
- skylos-4.50.0.dist-info/licenses/LICENSE +190 -0
- skylos-4.50.0.dist-info/top_level.txt +2 -0
- skylos_mcp/__init__.py +0 -0
- skylos_mcp/__main__.py +4 -0
- skylos_mcp/auth.py +340 -0
- skylos_mcp/server.py +2095 -0
skylos/__init__.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from importlib import metadata
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _version_from_pyproject() -> str | None:
|
|
7
|
+
pyproject = Path(__file__).resolve().parent.parent / "pyproject.toml"
|
|
8
|
+
try:
|
|
9
|
+
contents = pyproject.read_text(encoding="utf-8")
|
|
10
|
+
except OSError:
|
|
11
|
+
return None
|
|
12
|
+
match = re.search(r'(?m)^version\s*=\s*"([^"]+)"', contents)
|
|
13
|
+
return match.group(1) if match else None
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _resolve_version() -> str:
|
|
17
|
+
local_version = _version_from_pyproject()
|
|
18
|
+
if local_version:
|
|
19
|
+
return local_version
|
|
20
|
+
try:
|
|
21
|
+
return metadata.version("skylos")
|
|
22
|
+
except metadata.PackageNotFoundError:
|
|
23
|
+
return "0+unknown"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
__version__ = _resolve_version()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def analyze(*args, **kwargs):
|
|
30
|
+
from .analyzer import analyze as _analyze
|
|
31
|
+
|
|
32
|
+
return _analyze(*args, **kwargs)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def debug_test():
|
|
36
|
+
return "debug-ok"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
__all__ = ["analyze", "debug_test", "__version__"]
|
skylos/adapters/base.py
ADDED
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import time
|
|
3
|
+
from .base import BaseAdapter
|
|
4
|
+
from skylos.cloud.credentials import get_key, PROVIDERS
|
|
5
|
+
from skylos.llm.completion_diagnostics import (
|
|
6
|
+
OUTPUT_BUDGET_FINISH_REASON,
|
|
7
|
+
OUTPUT_BUDGET_SIGNAL,
|
|
8
|
+
normalize_finish_reason,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class LiteLLMAdapter(BaseAdapter):
|
|
13
|
+
MAX_DIAGNOSTIC_COUNT = 1_000_000_000
|
|
14
|
+
RETRYABLE_ERROR_SNIPPETS = (
|
|
15
|
+
"connection error",
|
|
16
|
+
"connection refused",
|
|
17
|
+
"failed to establish a new connection",
|
|
18
|
+
"name or service not known",
|
|
19
|
+
"nodename nor servname provided",
|
|
20
|
+
"timed out",
|
|
21
|
+
"timeout",
|
|
22
|
+
"service unavailable",
|
|
23
|
+
"internalservererror",
|
|
24
|
+
"server error",
|
|
25
|
+
"overloaded",
|
|
26
|
+
"rate limit",
|
|
27
|
+
"ratelimit",
|
|
28
|
+
"429",
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
model,
|
|
34
|
+
api_key=None,
|
|
35
|
+
api_base=None,
|
|
36
|
+
provider=None,
|
|
37
|
+
enable_cache=True,
|
|
38
|
+
max_tokens=None,
|
|
39
|
+
timeout=None,
|
|
40
|
+
retry_attempts=3,
|
|
41
|
+
temperature=0.0,
|
|
42
|
+
reasoning_effort=None,
|
|
43
|
+
):
|
|
44
|
+
super().__init__(model, api_key)
|
|
45
|
+
self.enable_cache = enable_cache
|
|
46
|
+
self.max_tokens = max_tokens
|
|
47
|
+
self.timeout = timeout
|
|
48
|
+
self.retry_attempts = max(1, int(retry_attempts or 1))
|
|
49
|
+
self.temperature = temperature
|
|
50
|
+
self.reasoning_effort = reasoning_effort
|
|
51
|
+
self.last_usage = {
|
|
52
|
+
"prompt_tokens": 0,
|
|
53
|
+
"completion_tokens": 0,
|
|
54
|
+
"total_tokens": 0,
|
|
55
|
+
}
|
|
56
|
+
self.last_completion_diagnostic = self._empty_completion_diagnostic()
|
|
57
|
+
self.total_usage = {
|
|
58
|
+
"prompt_tokens": 0,
|
|
59
|
+
"completion_tokens": 0,
|
|
60
|
+
"total_tokens": 0,
|
|
61
|
+
}
|
|
62
|
+
self.explicit_provider = (provider or "").strip().lower() or None
|
|
63
|
+
|
|
64
|
+
os.environ.setdefault("LITELLM_LOCAL_MODEL_COST_MAP", "true")
|
|
65
|
+
try:
|
|
66
|
+
import litellm
|
|
67
|
+
|
|
68
|
+
self.litellm = litellm
|
|
69
|
+
self.litellm.drop_params = True
|
|
70
|
+
except ImportError:
|
|
71
|
+
raise ImportError(
|
|
72
|
+
"LiteLLM is required for this feature. "
|
|
73
|
+
"Install it with: pip install skylos[llm]"
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
self.api_base = api_base or os.getenv("SKYLOS_LLM_BASE_URL")
|
|
77
|
+
self._resolve_api_key()
|
|
78
|
+
|
|
79
|
+
def _detect_provider(self):
|
|
80
|
+
if self.explicit_provider:
|
|
81
|
+
return self.explicit_provider
|
|
82
|
+
|
|
83
|
+
model = (self.model or "").lower()
|
|
84
|
+
|
|
85
|
+
if model.startswith("ollama/"):
|
|
86
|
+
return "ollama"
|
|
87
|
+
if "claude" in model:
|
|
88
|
+
return "anthropic"
|
|
89
|
+
if model.startswith("gemini/"):
|
|
90
|
+
return "google"
|
|
91
|
+
if model.startswith("mistral/"):
|
|
92
|
+
return "mistral"
|
|
93
|
+
if model.startswith("groq/"):
|
|
94
|
+
return "groq"
|
|
95
|
+
|
|
96
|
+
return "openai"
|
|
97
|
+
|
|
98
|
+
def _is_local(self):
|
|
99
|
+
model = (self.model or "").strip().lower()
|
|
100
|
+
if model.startswith("ollama/"):
|
|
101
|
+
return True
|
|
102
|
+
|
|
103
|
+
base_url = (self.api_base or "").strip().lower()
|
|
104
|
+
if base_url:
|
|
105
|
+
if "localhost" in base_url:
|
|
106
|
+
return True
|
|
107
|
+
if "127.0.0.1" in base_url:
|
|
108
|
+
return True
|
|
109
|
+
|
|
110
|
+
return False
|
|
111
|
+
|
|
112
|
+
def _is_anthropic(self):
|
|
113
|
+
return self._detect_provider() == "anthropic"
|
|
114
|
+
|
|
115
|
+
def _get_provider_env_var(self, provider):
|
|
116
|
+
if not provider:
|
|
117
|
+
return None
|
|
118
|
+
return PROVIDERS.get(provider)
|
|
119
|
+
|
|
120
|
+
def _resolve_api_key(self):
|
|
121
|
+
if self._is_local():
|
|
122
|
+
return
|
|
123
|
+
|
|
124
|
+
if self.api_key:
|
|
125
|
+
self._set_provider_env_var_if_missing(self.api_key)
|
|
126
|
+
return
|
|
127
|
+
|
|
128
|
+
provider = self._detect_provider()
|
|
129
|
+
env_var = self._get_provider_env_var(provider)
|
|
130
|
+
|
|
131
|
+
if env_var:
|
|
132
|
+
env_value = os.getenv(env_var)
|
|
133
|
+
if env_value:
|
|
134
|
+
self.api_key = env_value
|
|
135
|
+
return
|
|
136
|
+
|
|
137
|
+
keyring_key = get_key(provider)
|
|
138
|
+
if keyring_key:
|
|
139
|
+
if env_var:
|
|
140
|
+
os.environ[env_var] = keyring_key
|
|
141
|
+
self.api_key = keyring_key
|
|
142
|
+
return
|
|
143
|
+
|
|
144
|
+
def _set_provider_env_var_if_missing(self, key):
|
|
145
|
+
if not key:
|
|
146
|
+
return
|
|
147
|
+
|
|
148
|
+
provider = self._detect_provider()
|
|
149
|
+
env_var = self._get_provider_env_var(provider)
|
|
150
|
+
|
|
151
|
+
if not env_var:
|
|
152
|
+
return
|
|
153
|
+
|
|
154
|
+
existing = os.getenv(env_var)
|
|
155
|
+
if existing:
|
|
156
|
+
return
|
|
157
|
+
|
|
158
|
+
os.environ[env_var] = key
|
|
159
|
+
|
|
160
|
+
def _missing_key_message(self):
|
|
161
|
+
provider = self._detect_provider()
|
|
162
|
+
env_var = self._get_provider_env_var(provider)
|
|
163
|
+
|
|
164
|
+
if env_var:
|
|
165
|
+
return (
|
|
166
|
+
"No API key found for provider '{}'.\n"
|
|
167
|
+
"Set {} or run 'skylos key' and select '{}'."
|
|
168
|
+
).format(provider, env_var, provider)
|
|
169
|
+
|
|
170
|
+
return (
|
|
171
|
+
"No API key found for provider '{}'.\nRun 'skylos key' and select '{}'."
|
|
172
|
+
).format(provider, provider)
|
|
173
|
+
|
|
174
|
+
def _looks_like_auth_error(self, message):
|
|
175
|
+
if not message:
|
|
176
|
+
return False
|
|
177
|
+
|
|
178
|
+
msg = message.lower()
|
|
179
|
+
|
|
180
|
+
if "unauthorized" in msg:
|
|
181
|
+
return True
|
|
182
|
+
if "invalid api key" in msg:
|
|
183
|
+
return True
|
|
184
|
+
if "incorrect api key" in msg:
|
|
185
|
+
return True
|
|
186
|
+
if "authentication" in msg:
|
|
187
|
+
return True
|
|
188
|
+
if "401" in msg:
|
|
189
|
+
return True
|
|
190
|
+
if "403" in msg:
|
|
191
|
+
return True
|
|
192
|
+
|
|
193
|
+
return False
|
|
194
|
+
|
|
195
|
+
def _looks_like_connection_error(self, message):
|
|
196
|
+
if not message:
|
|
197
|
+
return False
|
|
198
|
+
|
|
199
|
+
msg = message.lower()
|
|
200
|
+
|
|
201
|
+
if "connection refused" in msg:
|
|
202
|
+
return True
|
|
203
|
+
if "failed to establish a new connection" in msg:
|
|
204
|
+
return True
|
|
205
|
+
if "name or service not known" in msg:
|
|
206
|
+
return True
|
|
207
|
+
if "nodename nor servname provided" in msg:
|
|
208
|
+
return True
|
|
209
|
+
if "timed out" in msg:
|
|
210
|
+
return True
|
|
211
|
+
|
|
212
|
+
return False
|
|
213
|
+
|
|
214
|
+
def _should_retry_exception(self, exc):
|
|
215
|
+
msg = str(exc or "").lower()
|
|
216
|
+
if not msg:
|
|
217
|
+
return False
|
|
218
|
+
return any(snippet in msg for snippet in self.RETRYABLE_ERROR_SNIPPETS)
|
|
219
|
+
|
|
220
|
+
def _retry_delay(self, attempt):
|
|
221
|
+
return min(0.5 * (2**attempt), 2.0)
|
|
222
|
+
|
|
223
|
+
def reset_usage(self):
|
|
224
|
+
self.last_usage = {
|
|
225
|
+
"prompt_tokens": 0,
|
|
226
|
+
"completion_tokens": 0,
|
|
227
|
+
"total_tokens": 0,
|
|
228
|
+
}
|
|
229
|
+
self._reset_completion_diagnostic()
|
|
230
|
+
self.total_usage = {
|
|
231
|
+
"prompt_tokens": 0,
|
|
232
|
+
"completion_tokens": 0,
|
|
233
|
+
"total_tokens": 0,
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
def _normalize_usage(self, usage):
|
|
237
|
+
if usage is None:
|
|
238
|
+
return {
|
|
239
|
+
"prompt_tokens": 0,
|
|
240
|
+
"completion_tokens": 0,
|
|
241
|
+
"total_tokens": 0,
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if isinstance(usage, dict):
|
|
245
|
+
prompt = int(usage.get("prompt_tokens") or 0)
|
|
246
|
+
completion = int(usage.get("completion_tokens") or 0)
|
|
247
|
+
total = int(usage.get("total_tokens") or (prompt + completion))
|
|
248
|
+
return {
|
|
249
|
+
"prompt_tokens": max(prompt, 0),
|
|
250
|
+
"completion_tokens": max(completion, 0),
|
|
251
|
+
"total_tokens": max(total, 0),
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
prompt = int(getattr(usage, "prompt_tokens", 0) or 0)
|
|
255
|
+
completion = int(getattr(usage, "completion_tokens", 0) or 0)
|
|
256
|
+
total = int(getattr(usage, "total_tokens", 0) or (prompt + completion))
|
|
257
|
+
return {
|
|
258
|
+
"prompt_tokens": max(prompt, 0),
|
|
259
|
+
"completion_tokens": max(completion, 0),
|
|
260
|
+
"total_tokens": max(total, 0),
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
def _record_usage(self, response):
|
|
264
|
+
usage = self._normalize_usage(getattr(response, "usage", None))
|
|
265
|
+
self.last_usage = usage
|
|
266
|
+
for key, value in usage.items():
|
|
267
|
+
self.total_usage[key] += value
|
|
268
|
+
|
|
269
|
+
@staticmethod
|
|
270
|
+
def _field(value, name):
|
|
271
|
+
if isinstance(value, dict):
|
|
272
|
+
return value.get(name)
|
|
273
|
+
return getattr(value, name, None)
|
|
274
|
+
|
|
275
|
+
@classmethod
|
|
276
|
+
def _bounded_count(cls, value):
|
|
277
|
+
if isinstance(value, bool) or not isinstance(value, int | float):
|
|
278
|
+
return None
|
|
279
|
+
try:
|
|
280
|
+
count = int(value)
|
|
281
|
+
except (OverflowError, ValueError):
|
|
282
|
+
return None
|
|
283
|
+
if count < 0 or count > cls.MAX_DIAGNOSTIC_COUNT:
|
|
284
|
+
return None
|
|
285
|
+
return count
|
|
286
|
+
|
|
287
|
+
@classmethod
|
|
288
|
+
def _finish_reason(cls, choice):
|
|
289
|
+
return normalize_finish_reason(cls._field(choice, "finish_reason"))
|
|
290
|
+
|
|
291
|
+
@staticmethod
|
|
292
|
+
def _empty_completion_diagnostic():
|
|
293
|
+
return {
|
|
294
|
+
"finish_reason": None,
|
|
295
|
+
"content_chars": None,
|
|
296
|
+
"completion_tokens": None,
|
|
297
|
+
"reasoning_tokens": None,
|
|
298
|
+
"signal": None,
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
def _reset_completion_diagnostic(self):
|
|
302
|
+
self.last_completion_diagnostic = self._empty_completion_diagnostic()
|
|
303
|
+
|
|
304
|
+
def _record_completion_diagnostic(self, response, *, choice, content):
|
|
305
|
+
usage = self._field(response, "usage")
|
|
306
|
+
completion_details = self._field(usage, "completion_tokens_details")
|
|
307
|
+
completion_tokens = self._bounded_count(self._field(usage, "completion_tokens"))
|
|
308
|
+
reasoning_tokens = self._bounded_count(
|
|
309
|
+
self._field(completion_details, "reasoning_tokens")
|
|
310
|
+
)
|
|
311
|
+
finish_reason = self._finish_reason(choice)
|
|
312
|
+
normalized_content = content.strip() if isinstance(content, str) else ""
|
|
313
|
+
content_chars = len(normalized_content) if content is not None else 0
|
|
314
|
+
signal = (
|
|
315
|
+
OUTPUT_BUDGET_SIGNAL
|
|
316
|
+
if finish_reason == OUTPUT_BUDGET_FINISH_REASON
|
|
317
|
+
else None
|
|
318
|
+
)
|
|
319
|
+
self.last_completion_diagnostic = {
|
|
320
|
+
"finish_reason": finish_reason,
|
|
321
|
+
"content_chars": content_chars,
|
|
322
|
+
"completion_tokens": completion_tokens,
|
|
323
|
+
"reasoning_tokens": reasoning_tokens,
|
|
324
|
+
"signal": signal,
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
def _complete_once(self, system_prompt, user_prompt, response_format=None):
|
|
328
|
+
self._resolve_api_key()
|
|
329
|
+
|
|
330
|
+
if (not self._is_local()) and (not self.api_key):
|
|
331
|
+
return self._missing_key_message()
|
|
332
|
+
|
|
333
|
+
if self.enable_cache and self._is_anthropic():
|
|
334
|
+
messages = [
|
|
335
|
+
{
|
|
336
|
+
"role": "system",
|
|
337
|
+
"content": [
|
|
338
|
+
{
|
|
339
|
+
"type": "text",
|
|
340
|
+
"text": system_prompt,
|
|
341
|
+
"cache_control": {"type": "ephemeral"},
|
|
342
|
+
}
|
|
343
|
+
],
|
|
344
|
+
},
|
|
345
|
+
{"role": "user", "content": user_prompt},
|
|
346
|
+
]
|
|
347
|
+
else:
|
|
348
|
+
messages = [
|
|
349
|
+
{"role": "system", "content": system_prompt},
|
|
350
|
+
{"role": "user", "content": user_prompt},
|
|
351
|
+
]
|
|
352
|
+
|
|
353
|
+
kwargs = {
|
|
354
|
+
"model": self.model,
|
|
355
|
+
"messages": messages,
|
|
356
|
+
"temperature": self.temperature,
|
|
357
|
+
"api_key": self.api_key,
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if self.explicit_provider is not None:
|
|
361
|
+
# New model aliases may not exist in the bundled LiteLLM model map
|
|
362
|
+
# yet. Preserve the caller's explicit provider so a valid model
|
|
363
|
+
# such as ``gpt-5.4`` is routed without requiring a transport-only
|
|
364
|
+
# ``openai/`` prefix.
|
|
365
|
+
kwargs["custom_llm_provider"] = self.explicit_provider
|
|
366
|
+
|
|
367
|
+
if self.max_tokens is not None:
|
|
368
|
+
kwargs["max_tokens"] = self.max_tokens
|
|
369
|
+
if self.timeout is not None:
|
|
370
|
+
kwargs["timeout"] = self.timeout
|
|
371
|
+
if self.reasoning_effort is not None:
|
|
372
|
+
kwargs["reasoning_effort"] = self.reasoning_effort
|
|
373
|
+
|
|
374
|
+
if self.api_base:
|
|
375
|
+
kwargs["api_base"] = self.api_base
|
|
376
|
+
|
|
377
|
+
if self._is_local():
|
|
378
|
+
kwargs["api_key"] = "not-needed"
|
|
379
|
+
|
|
380
|
+
if response_format is not None:
|
|
381
|
+
kwargs["response_format"] = response_format
|
|
382
|
+
|
|
383
|
+
response = self.litellm.completion(**kwargs)
|
|
384
|
+
self._record_usage(response)
|
|
385
|
+
choices = self._field(response, "choices")
|
|
386
|
+
choice = choices[0]
|
|
387
|
+
message = self._field(choice, "message")
|
|
388
|
+
content = self._field(message, "content")
|
|
389
|
+
self._record_completion_diagnostic(
|
|
390
|
+
response,
|
|
391
|
+
choice=choice,
|
|
392
|
+
content=content,
|
|
393
|
+
)
|
|
394
|
+
if content is None:
|
|
395
|
+
return ""
|
|
396
|
+
if not isinstance(content, str):
|
|
397
|
+
raise TypeError("LiteLLM completion content must be text or null")
|
|
398
|
+
return content.strip()
|
|
399
|
+
|
|
400
|
+
def _stream_once(self, system_prompt, user_prompt):
|
|
401
|
+
self._resolve_api_key()
|
|
402
|
+
|
|
403
|
+
if (not self._is_local()) and (not self.api_key):
|
|
404
|
+
yield self._missing_key_message()
|
|
405
|
+
return
|
|
406
|
+
|
|
407
|
+
if self.enable_cache and self._is_anthropic():
|
|
408
|
+
messages = [
|
|
409
|
+
{
|
|
410
|
+
"role": "system",
|
|
411
|
+
"content": [
|
|
412
|
+
{
|
|
413
|
+
"type": "text",
|
|
414
|
+
"text": system_prompt,
|
|
415
|
+
"cache_control": {"type": "ephemeral"},
|
|
416
|
+
}
|
|
417
|
+
],
|
|
418
|
+
},
|
|
419
|
+
{"role": "user", "content": user_prompt},
|
|
420
|
+
]
|
|
421
|
+
else:
|
|
422
|
+
messages = [
|
|
423
|
+
{"role": "system", "content": system_prompt},
|
|
424
|
+
{"role": "user", "content": user_prompt},
|
|
425
|
+
]
|
|
426
|
+
|
|
427
|
+
kwargs = {
|
|
428
|
+
"model": self.model,
|
|
429
|
+
"messages": messages,
|
|
430
|
+
"temperature": self.temperature,
|
|
431
|
+
"stream": True,
|
|
432
|
+
"api_key": self.api_key,
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if self.explicit_provider is not None:
|
|
436
|
+
kwargs["custom_llm_provider"] = self.explicit_provider
|
|
437
|
+
|
|
438
|
+
if self.max_tokens is not None:
|
|
439
|
+
kwargs["max_tokens"] = self.max_tokens
|
|
440
|
+
if self.timeout is not None:
|
|
441
|
+
kwargs["timeout"] = self.timeout
|
|
442
|
+
if self.reasoning_effort is not None:
|
|
443
|
+
kwargs["reasoning_effort"] = self.reasoning_effort
|
|
444
|
+
|
|
445
|
+
if self.api_base:
|
|
446
|
+
kwargs["api_base"] = self.api_base
|
|
447
|
+
|
|
448
|
+
if self._is_local():
|
|
449
|
+
kwargs["api_key"] = "not-needed"
|
|
450
|
+
|
|
451
|
+
response = self.litellm.completion(**kwargs)
|
|
452
|
+
for chunk in response:
|
|
453
|
+
if chunk.choices[0].delta.content:
|
|
454
|
+
yield chunk.choices[0].delta.content
|
|
455
|
+
|
|
456
|
+
def _format_exception_message(self, exc):
|
|
457
|
+
text = str(exc)
|
|
458
|
+
|
|
459
|
+
if (not self._is_local()) and (not self.api_key):
|
|
460
|
+
return self._missing_key_message()
|
|
461
|
+
|
|
462
|
+
if self._looks_like_auth_error(text):
|
|
463
|
+
provider = self._detect_provider()
|
|
464
|
+
return "Error: {}\n\nRun 'skylos key' and select '{}'.".format(
|
|
465
|
+
text, provider
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
if self._looks_like_connection_error(text):
|
|
469
|
+
if self.api_base:
|
|
470
|
+
return "Error: {}\n\nCheck SKYLOS_LLM_BASE_URL / --base-url: {}".format(
|
|
471
|
+
text, self.api_base
|
|
472
|
+
)
|
|
473
|
+
return (
|
|
474
|
+
"Error: {}\n\n"
|
|
475
|
+
"If you're using a local LLM, set SKYLOS_LLM_BASE_URL "
|
|
476
|
+
"(e.g. http://localhost:11434/v1)."
|
|
477
|
+
).format(text)
|
|
478
|
+
|
|
479
|
+
return "Error: {}".format(text)
|
|
480
|
+
|
|
481
|
+
def complete(self, system_prompt, user_prompt, response_format=None):
|
|
482
|
+
# ``last_usage`` describes this completion only. Clear the previous
|
|
483
|
+
# response before attempting provider I/O so an error string cannot be
|
|
484
|
+
# charged with token counts left behind by an earlier successful call.
|
|
485
|
+
self.last_usage = {
|
|
486
|
+
"prompt_tokens": 0,
|
|
487
|
+
"completion_tokens": 0,
|
|
488
|
+
"total_tokens": 0,
|
|
489
|
+
}
|
|
490
|
+
self._reset_completion_diagnostic()
|
|
491
|
+
last_error = None
|
|
492
|
+
for attempt in range(self.retry_attempts):
|
|
493
|
+
self._reset_completion_diagnostic()
|
|
494
|
+
try:
|
|
495
|
+
return self._complete_once(
|
|
496
|
+
system_prompt,
|
|
497
|
+
user_prompt,
|
|
498
|
+
response_format=response_format,
|
|
499
|
+
)
|
|
500
|
+
except Exception as e:
|
|
501
|
+
self._reset_completion_diagnostic()
|
|
502
|
+
last_error = e
|
|
503
|
+
if (
|
|
504
|
+
not self._should_retry_exception(e)
|
|
505
|
+
or attempt == self.retry_attempts - 1
|
|
506
|
+
):
|
|
507
|
+
return self._format_exception_message(e)
|
|
508
|
+
time.sleep(self._retry_delay(attempt))
|
|
509
|
+
|
|
510
|
+
return self._format_exception_message(last_error)
|
|
511
|
+
|
|
512
|
+
def stream(self, system_prompt, user_prompt):
|
|
513
|
+
last_error = None
|
|
514
|
+
for attempt in range(self.retry_attempts):
|
|
515
|
+
try:
|
|
516
|
+
yield from self._stream_once(system_prompt, user_prompt)
|
|
517
|
+
return
|
|
518
|
+
except Exception as e:
|
|
519
|
+
last_error = e
|
|
520
|
+
if (
|
|
521
|
+
not self._should_retry_exception(e)
|
|
522
|
+
or attempt == self.retry_attempts - 1
|
|
523
|
+
):
|
|
524
|
+
yield self._format_exception_message(e)
|
|
525
|
+
return
|
|
526
|
+
time.sleep(self._retry_delay(attempt))
|
|
527
|
+
|
|
528
|
+
yield self._format_exception_message(last_error)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Agent center state, payload, service, and triage helpers."""
|