agentnode-sdk 0.14.0__tar.gz → 0.15.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/CHANGELOG.md +44 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/PKG-INFO +1 -1
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/__init__.py +1 -1
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/config.py +9 -1
- agentnode_sdk-0.15.0/agentnode_sdk/llm_providers.py +116 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/runtimes/agent_runner.py +65 -44
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/pyproject.toml +1 -1
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_config.py +31 -0
- agentnode_sdk-0.15.0/tests/test_llm_providers.py +79 -0
- agentnode_sdk-0.15.0/tests/test_llm_vault_binding.py +306 -0
- agentnode_sdk-0.14.0/tests/test_llm_vault_binding.py +0 -143
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/.env.example +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/.gitignore +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/README.md +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/REGISTRY_SIGNING_ACTIVATION.md +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/REGISTRY_SIGNING_SPEC.md +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/THREAT_MODEL.md +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/TRUST_STACK.md +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode.lock +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/_fileutil.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/async_client.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/capability_graph.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/capability_taxonomy.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/cli/__init__.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/cli/__main__.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/cli/audit.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/cli/auth.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/cli/cassette_audit.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/cli/commands.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/cli/complements.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/cli/init.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/cli/main.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/cli/mcp_commands.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/cli/mcp_status.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/cli/mcp_submit.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/cli/mcp_verify.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/cli/output.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/cli/publish.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/cli/record_cases.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/cli/sandbox_commands.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/cli/serve.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/cli/setup_wizard.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/cli/smart_run.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/cli/templates.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/cli/validate.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/cli/verify_local.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/client.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/compatibility.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/credential_handle.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/credential_resolver.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/credential_store.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/detect.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/exceptions.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/guard.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/input_guard.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/installer.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/key_status.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/lock_integrity.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/mcp_server.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/models.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/planner.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/policy.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/references.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/registry_trust.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/resolve.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/resource_provider.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/risk_profile.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/run_log.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/runner.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/runtime.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/runtimes/__init__.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/runtimes/agent_llm_broker.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/runtimes/agent_llm_policy.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/runtimes/agent_sandbox.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/runtimes/mcp_runner.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/runtimes/python_runner.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/runtimes/remote_runner.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/sandbox/__init__.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/sandbox/agent_container_wrapper.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/sandbox/agent_rpc.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/sandbox/agent_session.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/sandbox/backend.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/sandbox/container_backend.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/sandbox/policy.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/sandbox/types.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/signature.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/signing_key.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/agentnode_sdk/skill.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/sandbox-image/Dockerfile +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/sandbox-image/README.md +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/scripts/analyze_scores.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/scripts/batch_verify.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/scripts/ci_smoke_test.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/scripts/generate_compatibility_artifacts.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/scripts/verify_toolcalls.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/scripts/weekly_retest.sh +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/spikes/agent_sandbox_routing/README.md +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/spikes/agent_sandbox_routing/__init__.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/spikes/agent_sandbox_routing/container_agent_wrapper.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/spikes/agent_sandbox_routing/fake_llm.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/spikes/agent_sandbox_routing/host_driver.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/spikes/agent_sandbox_routing/trivial_agent.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/__init__.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/conftest.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_agent_llm_broker.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_agent_llm_policy.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_agent_rpc.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_agent_runner.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_agent_sandbox_e2e.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_agent_sandbox_routing.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_agent_sandbox_spike.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_agent_session_container.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_async_client.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_audit_ux.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_auth_cli_vault.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_auto_upgrade_policy.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_cli.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_cli_lock.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_cli_run_resolution.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_client.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_client_json_guard.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_client_sprint_b.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_credential_handle.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_credential_integration.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_credential_resolver.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_credential_store.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_credential_vault.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_detect.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_detect_and_install.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_e2e_runtime.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_edge_cases.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_guard.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_guard_check.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_guard_config_cache.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_guard_policy.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_guard_preview.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_guard_schema.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_guard_set.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_guard_status.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_guard_tool_override.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_guard_tool_override_audit.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_guard_tool_override_cli.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_guard_ux.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_input_guard.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_input_guard_escalation.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_install_hardening.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_installer_sprint_b.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_intelligence.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_key_status.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_llm_binding.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_llm_call_runlog.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_lock_integrity.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_lock_runtime.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_mcp_audit.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_mcp_doctor.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_mcp_sandbox.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_mcp_server.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_observability.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_planner.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_policy.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_policy_integration.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_prompt_specs.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_provider_matrix.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_publish.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_references.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_registry_trust.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_remote_hardening.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_remote_runner.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_resource_provider.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_resource_specs.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_risk_profile.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_run_log.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_runner.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_runtime.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_runtime_audit.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_sandbox_backend.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_sandbox_doctor.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_sandbox_e2e.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_sandbox_gate.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_security_hardening.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_setup_wizard.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_signature.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_signing_key.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_skill.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_smart.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_stability.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_toolpack_sandbox.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_v02.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_validate.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/test_validate_skill.py +0 -0
- {agentnode_sdk-0.14.0 → agentnode_sdk-0.15.0}/tests/validation_lockfile.json +0 -0
|
@@ -1,5 +1,49 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.15.0 — Generic OpenAI-compatible LLM providers
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **Generic OpenAI-compatible provider registry.** The LLM runtime can now
|
|
8
|
+
bind any endpoint that speaks the OpenAI-compatible protocol. Custom
|
|
9
|
+
endpoints are plain config entries — `llm.providers.<name>` with
|
|
10
|
+
`base_url`, `model`, and optionally `api_key_env`/`requires_key` — no code
|
|
11
|
+
changes needed per provider.
|
|
12
|
+
- **Built-in presets** for OpenRouter, DeepSeek, Mistral, Qwen, Gemini
|
|
13
|
+
(Google's OpenAI-compatible endpoint) and Ollama, each with the canonical
|
|
14
|
+
base URL, API-key environment variable, and a sensible default model
|
|
15
|
+
(overridable via `llm.providers.<name>.model`).
|
|
16
|
+
- **Ollama as a key-free local provider** — the first agent path with no
|
|
17
|
+
account and no per-token cost. Opt-in only: select it explicitly with
|
|
18
|
+
`agentnode config set llm.default_provider ollama` (or configure it under
|
|
19
|
+
`llm.providers`); AgentNode never probes or binds it by surprise.
|
|
20
|
+
- Per-provider credentials: each provider's environment variable (e.g.
|
|
21
|
+
`DEEPSEEK_API_KEY`, `GEMINI_API_KEY`) and stored vault credentials both
|
|
22
|
+
work; the environment always overrides the stored key.
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- The OpenRouter binding now goes through the shared provider registry
|
|
27
|
+
(same behavior, including the namespaced default model — one code path
|
|
28
|
+
instead of a special case).
|
|
29
|
+
- The sandboxed-agent LLM broker automatically inherits every compatible
|
|
30
|
+
provider: keys stay host-side, the container still sees only RPC
|
|
31
|
+
completions, and the C1/C2 access policy applies unchanged.
|
|
32
|
+
|
|
33
|
+
### Hardened
|
|
34
|
+
|
|
35
|
+
- No provider keys in logs or error messages (client failures are reported
|
|
36
|
+
by exception type only).
|
|
37
|
+
- A custom provider without a configured model is skipped instead of
|
|
38
|
+
guessing — a wrong default model would fail confusingly at call time.
|
|
39
|
+
- Keyless providers are never attempted unless explicitly selected or
|
|
40
|
+
configured.
|
|
41
|
+
|
|
42
|
+
### BREAKING / Upgrade Notes
|
|
43
|
+
|
|
44
|
+
- None. Existing OpenAI/Anthropic setups (env vars, vault, per-agent config)
|
|
45
|
+
behave identically; the new providers are purely additive.
|
|
46
|
+
|
|
3
47
|
## 0.14.0 — Setup wizard: guided credentials + sandbox onboarding
|
|
4
48
|
|
|
5
49
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentnode-sdk
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.15.0
|
|
4
4
|
Summary: Python SDK for AgentNode — the open upgrade and discovery infrastructure for AI agents.
|
|
5
5
|
Project-URL: Homepage, https://agentnode.net
|
|
6
6
|
Project-URL: Repository, https://github.com/agentnode-ai/agentnode
|
|
@@ -46,6 +46,7 @@ DEFAULTS: dict[str, Any] = {
|
|
|
46
46
|
},
|
|
47
47
|
"llm": {
|
|
48
48
|
"default_provider": "openai",
|
|
49
|
+
"providers": {},
|
|
49
50
|
},
|
|
50
51
|
}
|
|
51
52
|
|
|
@@ -68,7 +69,10 @@ VALID_VALUES: dict[str, tuple[str, ...]] = {
|
|
|
68
69
|
"guard.compute": ("allow", "prompt", "deny"),
|
|
69
70
|
"guard.unknown": ("allow", "prompt", "deny"),
|
|
70
71
|
"agent_sandbox.enabled": ("true", "false"),
|
|
71
|
-
"llm.default_provider": (
|
|
72
|
+
"llm.default_provider": (
|
|
73
|
+
"openai", "anthropic", "openrouter", "deepseek", "mistral", "qwen",
|
|
74
|
+
"gemini", "ollama",
|
|
75
|
+
),
|
|
72
76
|
}
|
|
73
77
|
|
|
74
78
|
|
|
@@ -160,6 +164,10 @@ def _merge_defaults(data: dict) -> dict[str, Any]:
|
|
|
160
164
|
for k in ("default_provider",):
|
|
161
165
|
if k in data["llm"]:
|
|
162
166
|
cfg["llm"][k] = data["llm"][k]
|
|
167
|
+
# llm.providers holds nested endpoint specs (base_url/model/...) —
|
|
168
|
+
# pass through verbatim, like agent_sandbox.llm above.
|
|
169
|
+
if isinstance(data["llm"].get("providers"), dict):
|
|
170
|
+
cfg["llm"]["providers"] = data["llm"]["providers"]
|
|
163
171
|
return cfg
|
|
164
172
|
|
|
165
173
|
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""OpenAI-compatible LLM provider registry (Endpoint-A).
|
|
2
|
+
|
|
3
|
+
One shared source of truth for the LLM endpoints AgentNode can bind: built-in
|
|
4
|
+
presets (OpenRouter, DeepSeek, Mistral, Qwen, Ollama — plus the official
|
|
5
|
+
OpenAI/Anthropic entries) and user-defined entries/overrides from
|
|
6
|
+
``llm.providers.<name>`` in ~/.agentnode/config.json.
|
|
7
|
+
|
|
8
|
+
The runtime (``_auto_detect_llm``) builds standard OpenAI-compatible bindings
|
|
9
|
+
from these specs, so the host LLM broker — and therefore sandboxed agents —
|
|
10
|
+
inherit every provider automatically, with the key staying host-side and no
|
|
11
|
+
broker/sandbox change.
|
|
12
|
+
|
|
13
|
+
Compat presets MUST carry a default model: the broker's bare OpenAI default
|
|
14
|
+
("gpt-4o-mini") would 404 on most compatible endpoints. ``requires_key:
|
|
15
|
+
False`` providers (Ollama) are bound only on explicit selection — never by
|
|
16
|
+
probing.
|
|
17
|
+
"""
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
# Built-in presets. base_url=None means the provider SDK's official endpoint.
|
|
23
|
+
# default_model="" lets the broker pick its own official default.
|
|
24
|
+
KNOWN_PROVIDERS: dict[str, dict[str, Any]] = {
|
|
25
|
+
"openai": {
|
|
26
|
+
"base_url": None,
|
|
27
|
+
"api_key_env": "OPENAI_API_KEY",
|
|
28
|
+
"default_model": "",
|
|
29
|
+
"requires_key": True,
|
|
30
|
+
},
|
|
31
|
+
"anthropic": {
|
|
32
|
+
"base_url": None,
|
|
33
|
+
"api_key_env": "ANTHROPIC_API_KEY",
|
|
34
|
+
"default_model": "",
|
|
35
|
+
"requires_key": True,
|
|
36
|
+
},
|
|
37
|
+
"openrouter": {
|
|
38
|
+
"base_url": "https://openrouter.ai/api/v1",
|
|
39
|
+
"api_key_env": "OPENROUTER_API_KEY",
|
|
40
|
+
"default_model": "openai/gpt-4o-mini",
|
|
41
|
+
"requires_key": True,
|
|
42
|
+
},
|
|
43
|
+
"deepseek": {
|
|
44
|
+
"base_url": "https://api.deepseek.com/v1",
|
|
45
|
+
"api_key_env": "DEEPSEEK_API_KEY",
|
|
46
|
+
"default_model": "deepseek-chat",
|
|
47
|
+
"requires_key": True,
|
|
48
|
+
},
|
|
49
|
+
"mistral": {
|
|
50
|
+
"base_url": "https://api.mistral.ai/v1",
|
|
51
|
+
"api_key_env": "MISTRAL_API_KEY",
|
|
52
|
+
"default_model": "mistral-small-latest",
|
|
53
|
+
"requires_key": True,
|
|
54
|
+
},
|
|
55
|
+
"qwen": {
|
|
56
|
+
"base_url": "https://dashscope-intl.aliyuncs.com/compatible-mode/v1",
|
|
57
|
+
"api_key_env": "DASHSCOPE_API_KEY",
|
|
58
|
+
"default_model": "qwen-plus",
|
|
59
|
+
"requires_key": True,
|
|
60
|
+
},
|
|
61
|
+
"gemini": {
|
|
62
|
+
# Google's own OpenAI-compatible endpoint (trailing slash required).
|
|
63
|
+
"base_url": "https://generativelanguage.googleapis.com/v1beta/openai/",
|
|
64
|
+
"api_key_env": "GEMINI_API_KEY",
|
|
65
|
+
"default_model": "gemini-2.0-flash",
|
|
66
|
+
"requires_key": True,
|
|
67
|
+
},
|
|
68
|
+
"ollama": {
|
|
69
|
+
# key-free local inference: the first agent path with no account and
|
|
70
|
+
# no per-token cost. Bound ONLY when explicitly selected (no probing).
|
|
71
|
+
"base_url": "http://localhost:11434/v1",
|
|
72
|
+
"api_key_env": "OLLAMA_API_KEY",
|
|
73
|
+
"default_model": "llama3.2",
|
|
74
|
+
"requires_key": False,
|
|
75
|
+
},
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
_SPEC_KEYS = ("base_url", "api_key_env", "default_model", "requires_key")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _config_providers(host_config: dict | None) -> dict:
|
|
82
|
+
llm = (host_config or {}).get("llm") or {}
|
|
83
|
+
providers = llm.get("providers")
|
|
84
|
+
return providers if isinstance(providers, dict) else {}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def resolve_provider_spec(name: str, host_config: dict | None = None) -> dict | None:
|
|
88
|
+
"""Effective spec for ``name`` = preset merged with the config override
|
|
89
|
+
(``llm.providers.<name>``), or None for an unknown name without a config
|
|
90
|
+
entry. ``model`` is accepted as a friendlier config alias for
|
|
91
|
+
``default_model``."""
|
|
92
|
+
name = (name or "").lower().strip()
|
|
93
|
+
preset = KNOWN_PROVIDERS.get(name)
|
|
94
|
+
override = _config_providers(host_config).get(name)
|
|
95
|
+
if preset is None and not isinstance(override, dict):
|
|
96
|
+
return None
|
|
97
|
+
|
|
98
|
+
spec: dict[str, Any] = dict(preset) if preset else {
|
|
99
|
+
"base_url": None, "api_key_env": "", "default_model": "", "requires_key": True,
|
|
100
|
+
}
|
|
101
|
+
if isinstance(override, dict):
|
|
102
|
+
for k in _SPEC_KEYS:
|
|
103
|
+
if k in override:
|
|
104
|
+
spec[k] = override[k]
|
|
105
|
+
if "model" in override:
|
|
106
|
+
spec["default_model"] = override["model"]
|
|
107
|
+
return spec
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def known_provider_names(host_config: dict | None = None) -> list[str]:
|
|
111
|
+
"""Preset names first (stable order), then custom config-defined names."""
|
|
112
|
+
names = list(KNOWN_PROVIDERS)
|
|
113
|
+
for n in _config_providers(host_config):
|
|
114
|
+
if n not in names:
|
|
115
|
+
names.append(n)
|
|
116
|
+
return names
|
|
@@ -241,64 +241,83 @@ def _create_llm_from_config(llm_config: dict) -> dict | None:
|
|
|
241
241
|
return None
|
|
242
242
|
|
|
243
243
|
|
|
244
|
-
def
|
|
245
|
-
"""Build an LLM binding from the credential store
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
244
|
+
def _llm_binding_from_providers() -> dict | None:
|
|
245
|
+
"""Build an LLM binding from the provider registry + credential store
|
|
246
|
+
(UX-2 vault, generalized by Endpoint-A to any OpenAI-compatible endpoint:
|
|
247
|
+
OpenRouter/DeepSeek/Mistral/Qwen/Ollama/custom ``llm.providers.*``).
|
|
248
|
+
|
|
249
|
+
LAST resolution step — the bare OPENAI/ANTHROPIC env vars and per-agent
|
|
250
|
+
config were already checked by the earlier steps. Within this step the
|
|
251
|
+
key resolves per provider env-var-first (explicit/CI intent), then vault.
|
|
252
|
+
Keyless providers (e.g. ollama) bind ONLY when explicitly selected via
|
|
253
|
+
``llm.default_provider`` or configured under ``llm.providers`` — never by
|
|
254
|
+
probing. The key stays host-side; failures are logged by exception TYPE
|
|
251
255
|
only (never the message — it could carry context)."""
|
|
256
|
+
import os
|
|
257
|
+
|
|
252
258
|
try:
|
|
253
259
|
from agentnode_sdk.config import load_config
|
|
254
|
-
|
|
260
|
+
host_cfg = load_config() or {}
|
|
255
261
|
except Exception:
|
|
256
|
-
|
|
257
|
-
|
|
262
|
+
host_cfg = {}
|
|
263
|
+
llm_cfg = host_cfg.get("llm") or {}
|
|
264
|
+
default = llm_cfg.get("default_provider", "openai")
|
|
265
|
+
configured = llm_cfg.get("providers") if isinstance(llm_cfg.get("providers"), dict) else {}
|
|
258
266
|
|
|
259
267
|
try:
|
|
260
268
|
from agentnode_sdk.credential_store import get_llm_api_key
|
|
261
269
|
except Exception:
|
|
262
270
|
return None
|
|
271
|
+
from agentnode_sdk.llm_providers import known_provider_names, resolve_provider_spec
|
|
263
272
|
|
|
264
|
-
for
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
key = None
|
|
269
|
-
if not key:
|
|
273
|
+
order = [default] + [n for n in known_provider_names(host_cfg) if n != default]
|
|
274
|
+
for name in order:
|
|
275
|
+
spec = resolve_provider_spec(name, host_cfg)
|
|
276
|
+
if spec is None:
|
|
270
277
|
continue
|
|
271
|
-
|
|
278
|
+
|
|
279
|
+
env_var = spec.get("api_key_env") or ""
|
|
280
|
+
key = os.environ.get(env_var) if env_var else None # env beats vault
|
|
281
|
+
if not key:
|
|
272
282
|
try:
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
283
|
+
key = get_llm_api_key(name)
|
|
284
|
+
except Exception:
|
|
285
|
+
key = None
|
|
286
|
+
|
|
287
|
+
if spec.get("requires_key", True):
|
|
288
|
+
if not key:
|
|
289
|
+
continue
|
|
290
|
+
elif not key and not (name == default or name in configured):
|
|
291
|
+
# keyless provider without explicit selection: never bind by surprise
|
|
292
|
+
continue
|
|
293
|
+
|
|
294
|
+
if name == "anthropic":
|
|
280
295
|
try:
|
|
281
296
|
from anthropic import Anthropic
|
|
282
297
|
return {"client": Anthropic(api_key=key), "provider": "anthropic", "model": ""}
|
|
283
298
|
except ImportError:
|
|
284
|
-
logger.debug("
|
|
299
|
+
logger.debug("key for anthropic set but anthropic package not installed")
|
|
285
300
|
except Exception as exc:
|
|
286
|
-
logger.debug("Failed to create Anthropic client
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
301
|
+
logger.debug("Failed to create Anthropic client: %s", type(exc).__name__)
|
|
302
|
+
continue
|
|
303
|
+
|
|
304
|
+
base_url = spec.get("base_url")
|
|
305
|
+
model = spec.get("default_model") or ""
|
|
306
|
+
if base_url and not model:
|
|
307
|
+
# compat endpoint without a model: never guess — the broker's bare
|
|
308
|
+
# OpenAI default would 404 on most compatible providers.
|
|
309
|
+
logger.debug("provider %s has a base_url but no model configured — skipped", name)
|
|
310
|
+
continue
|
|
311
|
+
try:
|
|
312
|
+
from openai import OpenAI
|
|
313
|
+
kwargs: dict = {"api_key": key or "ollama"}
|
|
314
|
+
if base_url:
|
|
315
|
+
kwargs["base_url"] = base_url
|
|
316
|
+
return {"client": OpenAI(**kwargs), "provider": "openai", "model": model}
|
|
317
|
+
except ImportError:
|
|
318
|
+
logger.debug("key for %s set but openai package not installed", name)
|
|
319
|
+
except Exception as exc:
|
|
320
|
+
logger.debug("Failed to create client for %s: %s", name, type(exc).__name__)
|
|
302
321
|
return None
|
|
303
322
|
|
|
304
323
|
|
|
@@ -310,8 +329,10 @@ def _auto_detect_llm(slug: str = "") -> dict | None:
|
|
|
310
329
|
2. ~/.agentnode/.env (loaded into env, no overwrite)
|
|
311
330
|
3. OPENAI_API_KEY -> OpenAI client
|
|
312
331
|
4. ANTHROPIC_API_KEY -> Anthropic client
|
|
313
|
-
5.
|
|
314
|
-
|
|
332
|
+
5. Provider registry + credential store (vault): openai/anthropic plus
|
|
333
|
+
any OpenAI-compatible endpoint (openrouter/deepseek/mistral/qwen/
|
|
334
|
+
ollama/custom ``llm.providers.*``) — env always overrides the store
|
|
335
|
+
(UX-2/Endpoint-A)
|
|
315
336
|
|
|
316
337
|
Returns a dict-style LLM binding {client, provider, model} or None.
|
|
317
338
|
"""
|
|
@@ -349,7 +370,7 @@ def _auto_detect_llm(slug: str = "") -> dict | None:
|
|
|
349
370
|
except Exception as exc:
|
|
350
371
|
logger.debug("Failed to create Anthropic client: %s", exc)
|
|
351
372
|
|
|
352
|
-
return
|
|
373
|
+
return _llm_binding_from_providers()
|
|
353
374
|
|
|
354
375
|
|
|
355
376
|
# ---------------------------------------------------------------------------
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "agentnode-sdk"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.15.0"
|
|
8
8
|
description = "Python SDK for AgentNode — the open upgrade and discovery infrastructure for AI agents."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -343,3 +343,34 @@ class TestAgentSandboxConfigSection:
|
|
|
343
343
|
assert loaded["guard"]["delete"] == "deny"
|
|
344
344
|
assert loaded["guard"]["read"] == "allow" # defaults intact
|
|
345
345
|
assert loaded["trust"]["minimum_trust_level"] == "verified"
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
# --- llm.providers section (Endpoint-A) ---
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
class TestLlmProvidersConfigSection:
|
|
352
|
+
def test_defaults_include_empty_providers(self):
|
|
353
|
+
cfg = default_config()
|
|
354
|
+
assert cfg["llm"]["providers"] == {}
|
|
355
|
+
|
|
356
|
+
def test_providers_survive_load(self, isolated_config):
|
|
357
|
+
providers = {"myvllm": {"base_url": "http://10.0.0.5:8000/v1",
|
|
358
|
+
"model": "llama", "requires_key": False}}
|
|
359
|
+
isolated_config.parent.mkdir(parents=True, exist_ok=True)
|
|
360
|
+
isolated_config.write_text(
|
|
361
|
+
json.dumps({"llm": {"default_provider": "deepseek",
|
|
362
|
+
"providers": providers}}), encoding="utf-8")
|
|
363
|
+
loaded = load_config()
|
|
364
|
+
assert loaded["llm"]["providers"] == providers
|
|
365
|
+
assert loaded["llm"]["default_provider"] == "deepseek"
|
|
366
|
+
|
|
367
|
+
def test_set_value_accepts_new_provider_names(self):
|
|
368
|
+
cfg = default_config()
|
|
369
|
+
for name in ("deepseek", "mistral", "qwen", "ollama"):
|
|
370
|
+
set_value(cfg, "llm.default_provider", name)
|
|
371
|
+
assert cfg["llm"]["default_provider"] == name
|
|
372
|
+
|
|
373
|
+
def test_set_value_rejects_garbage_provider(self):
|
|
374
|
+
cfg = default_config()
|
|
375
|
+
with pytest.raises(ValueError):
|
|
376
|
+
set_value(cfg, "llm.default_provider", "skynet")
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Endpoint-A — the OpenAI-compatible provider registry."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from agentnode_sdk.llm_providers import (
|
|
5
|
+
KNOWN_PROVIDERS,
|
|
6
|
+
known_provider_names,
|
|
7
|
+
resolve_provider_spec,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_presets_are_complete():
|
|
12
|
+
for name, spec in KNOWN_PROVIDERS.items():
|
|
13
|
+
for key in ("base_url", "api_key_env", "default_model", "requires_key"):
|
|
14
|
+
assert key in spec, f"{name} missing {key}"
|
|
15
|
+
# compat endpoints (base_url set) MUST carry a model — the broker's
|
|
16
|
+
# bare OpenAI default would 404 there
|
|
17
|
+
if spec["base_url"]:
|
|
18
|
+
assert spec["default_model"], f"{name} needs a default model"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_expected_preset_names():
|
|
22
|
+
for name in ("openai", "anthropic", "openrouter", "deepseek",
|
|
23
|
+
"mistral", "qwen", "gemini", "ollama"):
|
|
24
|
+
assert name in KNOWN_PROVIDERS
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_gemini_uses_googles_openai_compat_endpoint():
|
|
28
|
+
spec = resolve_provider_spec("gemini")
|
|
29
|
+
assert spec["base_url"] == "https://generativelanguage.googleapis.com/v1beta/openai/"
|
|
30
|
+
assert spec["base_url"].endswith("/") # trailing slash required
|
|
31
|
+
assert spec["api_key_env"] == "GEMINI_API_KEY"
|
|
32
|
+
assert spec["default_model"] == "gemini-2.0-flash"
|
|
33
|
+
assert spec["requires_key"] is True
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_ollama_is_keyless_localhost():
|
|
37
|
+
spec = resolve_provider_spec("ollama")
|
|
38
|
+
assert spec["requires_key"] is False
|
|
39
|
+
assert spec["base_url"] == "http://localhost:11434/v1"
|
|
40
|
+
assert spec["default_model"] # never empty (broker default 404s)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_resolve_preset_without_config():
|
|
44
|
+
spec = resolve_provider_spec("deepseek")
|
|
45
|
+
assert spec["base_url"] == "https://api.deepseek.com/v1"
|
|
46
|
+
assert spec["api_key_env"] == "DEEPSEEK_API_KEY"
|
|
47
|
+
assert spec["default_model"] == "deepseek-chat"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_config_override_beats_preset():
|
|
51
|
+
cfg = {"llm": {"providers": {"deepseek": {
|
|
52
|
+
"model": "deepseek-reasoner", "base_url": "https://proxy.local/v1"}}}}
|
|
53
|
+
spec = resolve_provider_spec("deepseek", cfg)
|
|
54
|
+
assert spec["default_model"] == "deepseek-reasoner" # "model" alias works
|
|
55
|
+
assert spec["base_url"] == "https://proxy.local/v1"
|
|
56
|
+
assert spec["api_key_env"] == "DEEPSEEK_API_KEY" # preset rest intact
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_custom_provider_from_config_only():
|
|
60
|
+
cfg = {"llm": {"providers": {"myvllm": {
|
|
61
|
+
"base_url": "http://10.0.0.5:8000/v1", "model": "llama",
|
|
62
|
+
"requires_key": False}}}}
|
|
63
|
+
spec = resolve_provider_spec("myvllm", cfg)
|
|
64
|
+
assert spec["base_url"] == "http://10.0.0.5:8000/v1"
|
|
65
|
+
assert spec["default_model"] == "llama"
|
|
66
|
+
assert spec["requires_key"] is False
|
|
67
|
+
assert spec["api_key_env"] == ""
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_unknown_provider_is_none():
|
|
71
|
+
assert resolve_provider_spec("nope") is None
|
|
72
|
+
assert resolve_provider_spec("") is None
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_known_names_include_custom():
|
|
76
|
+
cfg = {"llm": {"providers": {"myvllm": {"base_url": "x", "model": "m"}}}}
|
|
77
|
+
names = known_provider_names(cfg)
|
|
78
|
+
assert names.index("openai") == 0 # presets first, stable order
|
|
79
|
+
assert "myvllm" in names
|