Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions sdks/python/agenta/sdk/agents/adapters/sandbox_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ def __init__(
config: HarnessAgentTemplate,
*,
harness: HarnessType,
secrets: Optional[Mapping[str, str]],
trace: Optional[TraceContext],
run_context: Optional[RunContext],
session_id: Optional[str],
Expand All @@ -73,7 +72,6 @@ def __init__(
self._sandbox = sandbox
self._config = config
self._harness = harness
self._secrets = dict(secrets or {})
self._trace = trace
self._run_context = run_context
self._session_id = session_id
Expand All @@ -89,7 +87,6 @@ def _wire_payload(self, messages: Sequence[Message]) -> Dict[str, Any]:
sandbox=self._sandbox.sandbox_id,
config=self._config,
messages=messages,
secrets=self._secrets,
trace=self._trace,
run_context=self._run_context,
session_id=self._session_id,
Expand Down Expand Up @@ -170,7 +167,6 @@ async def create_session(
sandbox,
config,
harness=harness,
secrets=secrets,
trace=trace,
run_context=run_context,
session_id=session_id,
Expand Down
9 changes: 4 additions & 5 deletions sdks/python/agenta/sdk/agents/capabilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@

- **Pi** reaches eight Agenta-vault-mapped providers directly (the ones whose ``provider_key``
secret drives a Pi provider via its env-key map), plus ``openai-codex`` (OpenAI's ChatGPT/Codex
subscription), which Pi reaches through its own OAuth login rather than a vault key usable
under ``self_managed`` (and the ``agenta`` default's ``runtime_provided`` fallback). Pi also
subscription), which Pi reaches through its own OAuth login rather than a vault key, usable
under ``self_managed``. Pi also
reaches ~24 more providers that have no Agenta vault kind; those are out of scope unless a
``custom_provider`` secret is made for them, so they are not enumerated here. Pi's cloud
deployments (azure/bedrock/vertex) are *declared* but Pi *consumption* of them stages with the
Expand Down Expand Up @@ -58,9 +58,8 @@
# ``/login``), NOT an Agenta vault ``provider_key`` (no vault secret kind maps to it). ``self_managed``
# is broader than this one provider: it covers any way a harness signs itself in without an
# Agenta-stored key, including machine credentials such as environment variables. This provider's
# on-ramp under ``self_managed`` happens to be the subscription OAuth. It is also reachable under
# the ``agenta`` default's ``runtime_provided`` fallback, so it belongs in Pi's reachable providers
# even though it carries no vault key. Its model ids are carried explicitly below because they are
# on-ramp under ``self_managed`` happens to be the subscription OAuth. Its model ids are carried
# explicitly below because they are
# not in the litellm-derived ``supported_llm_models`` catalog. See
# ``docs/design/agent-workflows/projects/provider-model-auth/harness-provider-matrix.md`` and the
# subscription-sidecar recipe.
Expand Down
10 changes: 10 additions & 0 deletions sdks/python/agenta/sdk/agents/connections/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
AmbiguousConnectionError,
ConnectionNotFoundError,
ConnectionResolutionError,
InvalidConnectionConfigurationError,
MissingCredentialError,
MissingProviderError,
ProviderMismatchError,
UnsupportedConnectionModeError,
Expand All @@ -21,10 +23,13 @@
from .models import (
Connection,
CredentialMode,
CredentialUsage,
Deployment,
Endpoint,
EnvironmentCredentialBinding,
ModelRef,
ResolvedConnection,
ResolvedCredential,
RuntimeAuthContext,
)
from .resolver import EnvConnectionResolver, StaticConnectionResolver
Expand All @@ -33,10 +38,13 @@
# Contracts
"Connection",
"Endpoint",
"EnvironmentCredentialBinding",
"ModelRef",
"ResolvedConnection",
"ResolvedCredential",
"RuntimeAuthContext",
"CredentialMode",
"CredentialUsage",
"Deployment",
# Port + adapters
"ConnectionResolver",
Expand All @@ -45,7 +53,9 @@
# Errors
"AgentConnectionError",
"ConnectionResolutionError",
"InvalidConnectionConfigurationError",
"ConnectionNotFoundError",
"MissingCredentialError",
"MissingProviderError",
"AmbiguousConnectionError",
"ProviderMismatchError",
Expand Down
142 changes: 142 additions & 0 deletions sdks/python/agenta/sdk/agents/connections/endpoints.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
"""Effective model routes and credential-role classification."""

from __future__ import annotations

from typing import Dict, Iterable, List, Optional, Tuple
from urllib.parse import urlparse

from .errors import InvalidConnectionConfigurationError
from .models import Endpoint, ResolvedConnection, ResolvedCredential

_DIRECT_ENDPOINTS: Dict[str, str] = {
"openai": "https://api.openai.com/v1",
"anthropic": "https://api.anthropic.com",
"gemini": "https://generativelanguage.googleapis.com",
"mistral": "https://api.mistral.ai/v1",
"mistralai": "https://api.mistral.ai/v1",
"minimax": "https://api.minimax.io/v1",
"groq": "https://api.groq.com/openai/v1",
"together_ai": "https://api.together.xyz/v1",
"openrouter": "https://openrouter.ai/api/v1",
}
_NON_SECRET_ENV = {
"AWS_REGION",
"AWS_DEFAULT_REGION",
"GOOGLE_CLOUD_PROJECT",
"GOOGLE_CLOUD_LOCATION",
}
_LOCAL_USE_ENV = {
"AWS_ACCESS_KEY_ID",
"AWS_SECRET_ACCESS_KEY",
"AWS_SESSION_TOKEN",
"AWS_PROFILE",
"GOOGLE_APPLICATION_CREDENTIALS",
}


def effective_endpoint(
*,
provider: str,
deployment: str,
endpoint: Optional[Endpoint],
environment: Dict[str, str],
) -> Endpoint:
"""Return the exact HTTPS route used by this resolved provider deployment."""
if endpoint and endpoint.base_url:
resolved = endpoint
elif deployment == "direct" or deployment.lower() == provider.lower():
base_url = _DIRECT_ENDPOINTS.get(provider.lower())
if not base_url:
raise ValueError(
f"no effective endpoint is registered for provider '{provider}'"
)
resolved = Endpoint(base_url=base_url)
elif deployment == "bedrock":
region = environment.get("AWS_REGION") or environment.get("AWS_DEFAULT_REGION")
if not region:
raise ValueError("bedrock model connection requires an AWS region")
resolved = Endpoint(
base_url=f"https://bedrock-runtime.{region}.amazonaws.com", region=region
)
elif deployment in {"vertex", "vertex_ai"}:
location = environment.get("GOOGLE_CLOUD_LOCATION")
if not location:
raise ValueError("vertex model connection requires GOOGLE_CLOUD_LOCATION")
resolved = Endpoint(
base_url=f"https://{location}-aiplatform.googleapis.com", region=location
)
else:
raise ValueError(f"deployment '{deployment}' requires an explicit endpoint")

parsed = urlparse(resolved.base_url or "")
if parsed.scheme.lower() != "https" or not parsed.hostname:
raise ValueError("model connection endpoint must be an absolute HTTPS URL")
return resolved


def classify_environment(
values: Iterable[Tuple[str, str]],
) -> Tuple[List[ResolvedCredential], Dict[str, str]]:
"""Split provider environment into secret bindings and non-secret configuration."""
credentials: List[ResolvedCredential] = []
environment: Dict[str, str] = {}
for name, value in values:
if not name or not value:
raise ValueError(
"model connection bindings require non-empty names and values"
)
if name in _NON_SECRET_ENV:
environment[name] = value
continue
usage = "local_use" if name in _LOCAL_USE_ENV else "opaque_http"
credentials.append(
ResolvedCredential(
binding={"kind": "environment", "name": name},
value=value,
usage=usage,
)
)
return credentials, environment


def build_resolved_connection(
*,
provider: str,
model: str,
deployment: str = "direct",
credential_mode: str,
values: Dict[str, str],
endpoint: Optional[Endpoint] = None,
) -> ResolvedConnection:
"""Build a classified connection and attach the resolver-owned effective route."""
if deployment in {"vertex", "vertex_ai"} and values.get("GOOGLE_CLOUD_API_KEY"):
raise InvalidConnectionConfigurationError(
"Vertex API-key authentication is not supported by the agent connection contract"
)
credentials, environment = classify_environment(values.items())
if credential_mode == "env" and not credentials:
raise InvalidConnectionConfigurationError(
"credential_mode 'env' requires at least one usable credential"
)
try:
route = effective_endpoint(
provider=provider,
deployment=deployment,
endpoint=endpoint,
environment=environment,
)
except ValueError as exc:
# A runtime-owned login with no resolved credential does not need a credential host.
# Once Agenta supplies any credential, an indeterminate route is unsafe and fails loud.
if any(credential.usage == "opaque_http" for credential in credentials):
raise InvalidConnectionConfigurationError(str(exc)) from exc
route = None
return ResolvedConnection(
provider=provider,
model=model,
deployment=deployment,
credential_mode=credential_mode,
credentials=credentials,
environment=environment,
endpoint=route,
)
19 changes: 19 additions & 0 deletions sdks/python/agenta/sdk/agents/connections/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,25 @@ class ConnectionResolutionError(AgentConnectionError):
"""Raised when a connection cannot be resolved into a credential plan."""


class MissingCredentialError(ConnectionResolutionError):
"""Raised when an Agenta-managed connection has no usable credential."""

def __init__(self, *, provider: str, slug: Optional[str] = None) -> None:
subject = (
f"connection '{slug}'" if slug else f"provider '{provider}' connection"
)
super().__init__(
f"{subject} has no usable credential; configure a credential or select "
"self_managed authentication"
)
self.provider = provider
self.slug = slug


class InvalidConnectionConfigurationError(AgentConnectionError):
"""Raised when resolved routing and credentials form an unsafe combination."""


class ConnectionNotFoundError(ConnectionResolutionError):
"""Raised when a named connection (``mode == agenta`` + ``slug``) does not exist."""

Expand Down
Loading
Loading