Skip to content

feat(agents): OpenAI-compatible endpoints for Pi via vault custom connections#5345

Merged
mmabrouk merged 3 commits into
release/v0.105.4from
feat/pi-openai-compatible-models
Jul 17, 2026
Merged

feat(agents): OpenAI-compatible endpoints for Pi via vault custom connections#5345
mmabrouk merged 3 commits into
release/v0.105.4from
feat/pi-openai-compatible-models

Conversation

@mmabrouk

Copy link
Copy Markdown
Member

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 fell
back 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 /run schema
change.

Changes

The service formalizes the existing custom kind as an OpenAI-compatible Chat Completions
endpoint. A provider-less named connection with kind=custom resolves to provider openai,
deployment custom, and its key in OPENAI_API_KEY. An explicit provider family is preserved, so
existing Anthropic gateway connections are unchanged. A new harness_allows_pair table decides
which resolved (provider, deployment) pairs a harness accepts. Pi plus openai plus custom is
newly allowed; arbitrary provider plus custom on Pi is still rejected. Named Agenta connections
now defer provider acceptance until after the vault record resolves, so the pre-resolve check
validates only the connection mode. pi_core and pi_agenta publish deployments
["direct", "custom"].

The connection {mode, slug} reaches the runner through model_ref threading in
adapters/harnesses.py for the Pi and Agenta harnesses. It was not threading for a named custom
connection, which live end-to-end testing surfaced. The slug is connection identity now, not a
provider family.

The runner writes Pi's models.json from a new pure builder
(services/runner/src/engines/sandbox_agent/pi-model-config.ts). It applies only for Pi plus
provider openai plus deployment custom plus connection mode agenta.

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 apiKey is an environment reference, so no secret lands on disk. Locally the runner writes the
file 0600 and atomically into an isolated per-run Pi dir that carries no operator auth.json, so
a plan run never authenticates with the operator's login. On Daytona it uploads the file to
DAYTONA_PI_DIR before session creation. A no-plan run removes any stale file best-effort. When a
plan exists, the runner requests the fully qualified <slug>/<model> id, which fixes a suffix
collision that could silently route a request to api.openai.com.

Fail-loud throughout. An unusable base URL raises EndpointResolutionError, a new subclass that
returns HTTP 422 (as do UnsupportedProviderError and UnsupportedDeploymentError; without an
explicit status_code these fell through to 500). The connection never resolves with
endpoint=None, so the harness cannot fall back to a default endpoint. Builder, write, and upload
failures are terminal.

Scope / risk

  • The prompt/completion path, the vault storage schema, and the /run wire shape are untouched.
    No migration.
  • The fail-loud endpoint guard is not Pi-only. Any custom deployment with an unusable base URL
    now fails loudly, including a Claude Anthropic gateway. Existing gateways with a valid base URL
    are unaffected.
  • model_ref threading now puts the connection {mode, slug} on the wire for Pi and Agenta, which
    changes the config fingerprint used for warm-session reuse. Expect a one-time cold restart of
    parked self_managed sessions on first deploy. Steady state is unchanged.
  • The runner change ships in services/runner, so a runner image rebuild is needed on deploy.
  • Claude is left unchanged on purpose. It does not consume the slug and does not read a Pi
    models.json.

How to QA

  1. Store a custom connection in the vault with a public HTTPS OpenAI-compatible base URL, a key,
    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.
  2. Run the same on Daytona. Expected: the same result; models.json is uploaded to
    DAYTONA_PI_DIR and the sandbox carries no operator auth.json.
  3. Store a second custom connection with the same model id but a different host. Run it. Expected:
    the request routes to its own host, proving the slug keys the provider.
  4. Edge case, URL-less connection: create a kind=custom connection 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.
  5. Edge case, suffix collision: pick a custom model whose id suffix matches a built-in OpenAI
    model. Expected: the request uses the <slug>/<model> id and reaches the custom host, not
    api.openai.com.
  6. Regression, Claude: run an existing Anthropic direct and an existing Anthropic gateway
    connection. Expected: both pass unchanged.

Test commands:

cd sdks/python && uv run pytest oss/tests/pytest/unit/agents/ -q
cd services/runner && pnpm run test:unit

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

@dosubot dosubot Bot added the size:XXL This PR changes 1000+ lines, ignoring generated files. label Jul 16, 2026
@vercel

vercel Bot commented Jul 16, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agenta-documentation Ready Ready Preview, Comment Jul 17, 2026 8:31pm

Request Review

@dosubot dosubot Bot added the enhancement New feature or request label Jul 16, 2026
@mmabrouk

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This change adds OpenAI-compatible custom connection support for Pi. It normalizes and validates custom connections, generates isolated models.json configuration, uploads it for Daytona runs, selects fully qualified model IDs, fails on invalid endpoints or configuration, and adds SDK, runner, and integration coverage.

Changes

Pi OpenAI-compatible custom models

Layer / File(s) Summary
Workflow and contract design
docs/design/agent-workflows/...
Design, implementation, QA, research, status, and open-issue documents define custom connection semantics, capability gating, Pi configuration, lifecycle behavior, and validation coverage.
SDK resolution and capability enforcement
sdks/python/agenta/sdk/agents/..., sdks/python/oss/tests/pytest/...
Provider-less custom connections resolve to openai; resolved provider/deployment pairs are checked; unusable endpoints raise typed 422 errors; model references are preserved through harness adapters.
Pi model planning and asset materialization
services/runner/src/engines/sandbox_agent/...
The runner builds secret-free Pi plans, writes isolated local models.json files, uploads or removes Daytona configuration, avoids operator credentials for managed runs, and fails before session creation when required configuration fails.
Runner model and lifecycle validation
services/runner/tests/unit/...
Tests cover plan validation and serialization, local and Daytona materialization, qualified model selection, stale configuration cleanup, and session invalidation after connection or credential changes.

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
Loading

Possibly related issues

Possibly related PRs

  • Agenta-AI/agenta#5156: Modifies the runner’s acquireEnvironment flow, which this change extends with Pi model configuration preparation.
  • Agenta-AI/agenta#5240: Overlaps with strict runner model resolution and failure for unresolved Pi model identifiers.
  • Agenta-AI/agenta#5298: Overlaps with Pi sandbox asset preparation and managed credential isolation.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 49.21% which is insufficient. The required threshold is 60.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title is concise and accurately summarizes the main change: Pi support for OpenAI-compatible custom vault connections.
Description check ✅ Passed The description is directly related and matches the implemented Pi, vault, and runner changes.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/pi-openai-compatible-models

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
sdks/python/agenta/sdk/agents/handler.py (1)

150-154: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider 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 custom deployment) via harness_allows_pair, this logic falls through to raise an UnsupportedProviderError. The resulting error message ("provider 'X' is not supported") might confuse users who know the provider is inherently supported directly.

Consider raising a distinct UnsupportedPairError or 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

📥 Commits

Reviewing files that changed from the base of the PR and between 650d4ed and 81636d6.

📒 Files selected for processing (35)
  • docs/design/agent-workflows/documentation/adapters/pi.md
  • docs/design/agent-workflows/interfaces/cross-service/service-to-vault-and-tool-providers.md
  • docs/design/agent-workflows/interfaces/in-service/model-connection-resolution.md
  • docs/design/agent-workflows/projects/pi-openai-compatible-models/README.md
  • docs/design/agent-workflows/projects/pi-openai-compatible-models/context.md
  • docs/design/agent-workflows/projects/pi-openai-compatible-models/design.md
  • docs/design/agent-workflows/projects/pi-openai-compatible-models/open-issues.md
  • docs/design/agent-workflows/projects/pi-openai-compatible-models/plan.md
  • docs/design/agent-workflows/projects/pi-openai-compatible-models/qa.md
  • docs/design/agent-workflows/projects/pi-openai-compatible-models/research.md
  • docs/design/agent-workflows/projects/pi-openai-compatible-models/status.md
  • sdks/python/agenta/sdk/agents/__init__.py
  • sdks/python/agenta/sdk/agents/adapters/harnesses.py
  • sdks/python/agenta/sdk/agents/capabilities.py
  • sdks/python/agenta/sdk/agents/connections/__init__.py
  • sdks/python/agenta/sdk/agents/connections/errors.py
  • sdks/python/agenta/sdk/agents/handler.py
  • sdks/python/agenta/sdk/agents/platform/connections.py
  • sdks/python/oss/tests/pytest/integration/agents/recordings/e2-pi-custom-openai-compatible.json
  • sdks/python/oss/tests/pytest/integration/agents/test_custom_connection_replay.py
  • sdks/python/oss/tests/pytest/unit/agents/connections/test_capabilities.py
  • sdks/python/oss/tests/pytest/unit/agents/platform/test_connections_http.py
  • sdks/python/oss/tests/pytest/unit/agents/test_agent_composition_seam.py
  • sdks/python/oss/tests/pytest/unit/agents/test_harness_adapters.py
  • sdks/python/oss/tests/pytest/unit/agents/test_invoke_failure_status.py
  • services/runner/src/engines/sandbox_agent.ts
  • services/runner/src/engines/sandbox_agent/daytona.ts
  • services/runner/src/engines/sandbox_agent/pi-assets.ts
  • services/runner/src/engines/sandbox_agent/pi-model-config.ts
  • services/runner/tests/unit/sandbox-agent-daytona.test.ts
  • services/runner/tests/unit/sandbox-agent-model.test.ts
  • services/runner/tests/unit/sandbox-agent-orchestration.test.ts
  • services/runner/tests/unit/sandbox-agent-pi-assets.test.ts
  • services/runner/tests/unit/sandbox-agent-pi-model-config.test.ts
  • services/runner/tests/unit/session-pool.test.ts

mmabrouk added 3 commits July 17, 2026 20:24
… 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.
@github-actions

github-actions Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Railway Preview Environment

Status Destroyed (PR closed)

Updated at 2026-07-17T22:42:43.275Z

@mmabrouk
mmabrouk changed the base branch from main to release/v0.105.4 July 17, 2026 22:01
@mmabrouk
mmabrouk merged commit 93d71ee into release/v0.105.4 Jul 17, 2026
120 of 123 checks passed
mmabrouk added a commit that referenced this pull request Jul 18, 2026
…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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant