feat(agents): OpenAI-compatible endpoints for Pi via vault custom connections#5345
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
@coderabbitai review |
✅ Action performedReview finished.
|
📝 WalkthroughWalkthroughThis change adds OpenAI-compatible custom connection support for Pi. It normalizes and validates custom connections, generates isolated ChangesPi OpenAI-compatible custom models
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant Client
participant SDKResolver
participant SandboxAgent
participant PiConfig
participant Daytona
Client->>SDKResolver: submit named custom connection
SDKResolver->>SDKResolver: resolve provider, deployment, endpoint, and secret name
SDKResolver-->>SandboxAgent: resolved run request
SandboxAgent->>PiConfig: build and serialize models.json
PiConfig-->>SandboxAgent: Pi model configuration
SandboxAgent->>Daytona: upload models.json before session creation
Daytona-->>SandboxAgent: prepared Pi environment
SandboxAgent->>Daytona: apply qualified slug/model identifier
Possibly related issues
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
sdks/python/agenta/sdk/agents/handler.py (1)
150-154: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider refining the error message for valid providers on unsupported deployments.
If a provider is generally supported by the harness (e.g., Anthropic for Pi) but is rejected for the resolved deployment (e.g., the
customdeployment) viaharness_allows_pair, this logic falls through to raise anUnsupportedProviderError. The resulting error message ("provider 'X' is not supported") might confuse users who know the provider is inherently supported directly.Consider raising a distinct
UnsupportedPairErroror refining the error message in the future to clarify that the combination of the provider and deployment is not supported.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: ff208924-7dd7-4965-badb-96f7ff89e1f7
📒 Files selected for processing (35)
docs/design/agent-workflows/documentation/adapters/pi.mddocs/design/agent-workflows/interfaces/cross-service/service-to-vault-and-tool-providers.mddocs/design/agent-workflows/interfaces/in-service/model-connection-resolution.mddocs/design/agent-workflows/projects/pi-openai-compatible-models/README.mddocs/design/agent-workflows/projects/pi-openai-compatible-models/context.mddocs/design/agent-workflows/projects/pi-openai-compatible-models/design.mddocs/design/agent-workflows/projects/pi-openai-compatible-models/open-issues.mddocs/design/agent-workflows/projects/pi-openai-compatible-models/plan.mddocs/design/agent-workflows/projects/pi-openai-compatible-models/qa.mddocs/design/agent-workflows/projects/pi-openai-compatible-models/research.mddocs/design/agent-workflows/projects/pi-openai-compatible-models/status.mdsdks/python/agenta/sdk/agents/__init__.pysdks/python/agenta/sdk/agents/adapters/harnesses.pysdks/python/agenta/sdk/agents/capabilities.pysdks/python/agenta/sdk/agents/connections/__init__.pysdks/python/agenta/sdk/agents/connections/errors.pysdks/python/agenta/sdk/agents/handler.pysdks/python/agenta/sdk/agents/platform/connections.pysdks/python/oss/tests/pytest/integration/agents/recordings/e2-pi-custom-openai-compatible.jsonsdks/python/oss/tests/pytest/integration/agents/test_custom_connection_replay.pysdks/python/oss/tests/pytest/unit/agents/connections/test_capabilities.pysdks/python/oss/tests/pytest/unit/agents/platform/test_connections_http.pysdks/python/oss/tests/pytest/unit/agents/test_agent_composition_seam.pysdks/python/oss/tests/pytest/unit/agents/test_harness_adapters.pysdks/python/oss/tests/pytest/unit/agents/test_invoke_failure_status.pyservices/runner/src/engines/sandbox_agent.tsservices/runner/src/engines/sandbox_agent/daytona.tsservices/runner/src/engines/sandbox_agent/pi-assets.tsservices/runner/src/engines/sandbox_agent/pi-model-config.tsservices/runner/tests/unit/sandbox-agent-daytona.test.tsservices/runner/tests/unit/sandbox-agent-model.test.tsservices/runner/tests/unit/sandbox-agent-orchestration.test.tsservices/runner/tests/unit/sandbox-agent-pi-assets.test.tsservices/runner/tests/unit/sandbox-agent-pi-model-config.test.tsservices/runner/tests/unit/session-pool.test.ts
…, and replay test Claude-Session: https://claude.ai/code/session_01AYBi6cYuQCR87dAD3ZdPWc
… test The default resolve stub in test_invoke_handler.py returned a successful runtime_provided ResolvedConnection pinned to provider 'openai'. With the new post-resolve harness capability gate this is rejected on the claude harness (openai is not a claude-consumable provider), breaking test_invoke_cross_harness_same_body_divergent_configs. Make the stub raise ConnectionResolutionError instead, exercising the real no-connection degraded path that every harness tolerates, which is what these response-body/lifecycle/cross-harness tests are meant to check.
81636d6 to
4ef236b
Compare
Railway Preview Environment
Updated at 2026-07-17T22:42:43.275Z |
…position v0.105.4 (#5345) added the OpenAI-compatible custom-connection model-config plan inside acquireEnvironment in the old monolith. This decomposition moved acquireEnvironment into environment.ts + environment-setup.ts, so on rebase that release logic is threaded through the new split: the plan is built and localModelConfigUnwritable computed in prepareEnvironmentSetup and returned in its bundle; the fail-loud/fail-closed throws, the Daytona asset piModelConfig arg, and the fully-qualified wantedModel selection live in acquireEnvironment. Behavior is identical to #5345.
feat(agents): OpenAI-compatible endpoints for Pi via vault custom connections
Context
You could store a custom connection (a base URL, a key, and a model id) in the vault, pick it for
a Pi agent, and the run would fail before it reached the model. A connection whose kind was not a
known provider family never passed the harness capability gate. Even when it did, the base URL was
dropped: the runner never wrote Pi a
models.json, so Pi had no idea the endpoint existed and fellback to its default provider. OpenAI-compatible endpoints (an Ollama gateway, an in-house proxy,
any self-named Chat Completions endpoint) could not run on Pi at all.
This change makes that path work end to end, with no vault schema change and no
/runschemachange.
Changes
The service formalizes the existing
customkind as an OpenAI-compatible Chat Completionsendpoint. A provider-less named connection with
kind=customresolves to provideropenai,deployment
custom, and its key inOPENAI_API_KEY. An explicit provider family is preserved, soexisting Anthropic gateway connections are unchanged. A new
harness_allows_pairtable decideswhich resolved
(provider, deployment)pairs a harness accepts. Pi plusopenaipluscustomisnewly allowed; arbitrary provider plus
customon Pi is still rejected. Named Agenta connectionsnow defer provider acceptance until after the vault record resolves, so the pre-resolve check
validates only the connection mode.
pi_coreandpi_agentapublish deployments["direct", "custom"].The connection
{mode, slug}reaches the runner throughmodel_refthreading inadapters/harnesses.pyfor the Pi and Agenta harnesses. It was not threading for a named customconnection, which live end-to-end testing surfaced. The slug is connection identity now, not a
provider family.
The runner writes Pi's
models.jsonfrom a new pure builder(
services/runner/src/engines/sandbox_agent/pi-model-config.ts). It applies only for Pi plusprovider
openaiplus deploymentcustomplus connection modeagenta.Before, a custom base URL never reached Pi. After, the runner writes:
{ "providers": { "my-ollama-gateway": { "baseUrl": "https://gateway.internal/v1", "api": "openai-completions", "apiKey": "$OPENAI_API_KEY", "models": [{ "id": "llama-3.1-70b" }] } } }The
apiKeyis an environment reference, so no secret lands on disk. Locally the runner writes thefile
0600and atomically into an isolated per-run Pi dir that carries no operatorauth.json, soa plan run never authenticates with the operator's login. On Daytona it uploads the file to
DAYTONA_PI_DIRbefore session creation. A no-plan run removes any stale file best-effort. When aplan exists, the runner requests the fully qualified
<slug>/<model>id, which fixes a suffixcollision that could silently route a request to
api.openai.com.Fail-loud throughout. An unusable base URL raises
EndpointResolutionError, a new subclass thatreturns HTTP 422 (as do
UnsupportedProviderErrorandUnsupportedDeploymentError; without anexplicit
status_codethese fell through to 500). The connection never resolves withendpoint=None, so the harness cannot fall back to a default endpoint. Builder, write, and uploadfailures are terminal.
Scope / risk
/runwire shape are untouched.No migration.
customdeployment with an unusable base URLnow fails loudly, including a Claude Anthropic gateway. Existing gateways with a valid base URL
are unaffected.
model_refthreading now puts the connection{mode, slug}on the wire for Pi and Agenta, whichchanges the config fingerprint used for warm-session reuse. Expect a one-time cold restart of
parked
self_managedsessions on first deploy. Steady state is unchanged.services/runner, so a runner image rebuild is needed on deploy.models.json.How to QA
and one model id. Pick it for a Pi agent and run. Expected: the run reaches the configured host,
not
api.openai.com, and the reply comes from the configured model.models.jsonis uploaded toDAYTONA_PI_DIRand the sandbox carries no operatorauth.json.the request routes to its own host, proving the slug keys the provider.
kind=customconnection with no base URL and run it.Expected: the run fails with HTTP 422 and an error naming the connection slug, not a 500 and not
a silent fallback.
model. Expected: the request uses the
<slug>/<model>id and reaches the custom host, notapi.openai.com.connection. Expected: both pass unchanged.
Test commands:
Evidence: SDK agents suite 669 passed, runner suite 1190 passed plus wire golden fixtures. Live
end-to-end on the EE dev stack (2026-07-14 and 2026-07-15) passed the happy path local and on
Daytona, the 422 fail-loud paths, and the Pi and Claude regression runs.
https://claude.ai/code/session_01AYBi6cYuQCR87dAD3ZdPWc