Skip to content

feat: add aggregate cloud and agent-server clients#267

Open
neubig wants to merge 4 commits into
mainfrom
codex/cloud-client-abstraction
Open

feat: add aggregate cloud and agent-server clients#267
neubig wants to merge 4 commits into
mainfrom
codex/cloud-client-abstraction

Conversation

@neubig

@neubig neubig commented Jul 7, 2026

Copy link
Copy Markdown
Member
  • A human has tested these changes.

Live evidence

Run on 2026-07-13 UTC on Linux 6.8.0-134-generic x86_64, Node v24.15.0, and npm 11.14.0. The exact reusable harness is committed as .pr/live_aggregate_clients.mjs.

The live Agent Server was ghcr.io/openhands/agent-server:1.35.0-python at digest sha256:8f2540d9025efb12e8958cfa5a565540e28f9b11c6df23c3ec7c9fb6c7bc878f, exposed only on 127.0.0.1:8010. Cloud calls targeted https://app.all-hands.dev; the harness records only method, host, sanitized path, status, and timing, never credentials or response secrets.

The identical committed harness was invoked against each checkout's built dist/clients.js:

CLIENTS_MODULE="$CHECKOUT/dist/clients.js" \
TESTED_COMMIT="$(git -C "$CHECKOUT" rev-parse HEAD)" \
AGENT_SERVER_URL=http://127.0.0.1:8010 \
CLOUD_API_KEY="$OPENHANDS_API_KEY" \
node .pr/live_aggregate_clients.mjs

Current main: aggregate APIs are unavailable

Commit: 941cc7e143c81961603107a9812e81d8c2bd077e

The built client exported none of OpenHandsClient, AgentServerClient, CloudClient, startDeviceFlow, or pollForToken. The same process proved existing endpoint clients still work against the real server:

{
  "exports": {
    "OpenHandsClient": false,
    "AgentServerClient": false,
    "CloudClient": false,
    "startDeviceFlow": false,
    "pollForToken": false
  },
  "legacy": {
    "health": "ok",
    "serverVersion": "1.35.0",
    "bashExitCode": 0,
    "bashStdout": "legacy-endpoint-ok"
  }
}

Sanitized trace: GET /health -> 200, GET /server_info -> 200, and POST /api/bash/execute_bash_command -> 200.

PR head: aggregate and legacy clients work against live endpoints

Commit: 95495399f14a766e6364b2619be4a3d43f011342

All five aggregate/device-flow exports were present. On Agent Server 1.35.0, both the legacy and aggregate namespaces returned health=ok; direct aggregate GET /health also returned ok; and the legacy and aggregate bash clients each executed their distinct printf command with exit code 0. Their sanitized request traces used the same /health, /server_info, and /api/bash/execute_bash_command endpoints.

The aggregate Cloud client then exercised real production read-only endpoints:

{
  "kind": "cloud",
  "authTypePresent": true,
  "orgIdPresent": true,
  "organizationCount": 2,
  "currentOrgPresent": true,
  "agentSettingsDerived": true,
  "conversationSettingsDerived": true,
  "httpError": {"name": "HttpError", "status": 405}
}

Transport failures also propagated: an aggregate request to unused local port 9 raised an Error containing request-failure context, while an unsupported live Cloud POST /api/keys/current produced HttpError status 405.

Complete live device flow and cleanup

The PR client started a real production device flow, authenticated the verification step using the already-authorized test account, polled through the PR's CloudClient, validated the returned bearer token with GET /api/keys/current, deleted that temporary device key, and confirmed the deleted token then returned 401:

{
  "startSucceeded": true,
  "verificationHost": "app.all-hands.dev",
  "completeUriPresent": true,
  "expiresIn": 600,
  "interval": 5,
  "authenticatedVerificationStatus": 200,
  "pollReturnedBearer": true,
  "returnedKeyRecognized": true,
  "returnedKeyCleanedUp": true
}

The sanitized sequence was POST /oauth/device/authorize -> 200, POST /oauth/device/verify-authenticated -> 200, POST /oauth/device/token -> 200, GET /api/keys/current -> 200, DELETE /api/keys/{id} -> 200, and a final GET /api/keys/current -> 401. No token or raw key was printed or retained.

npm run build passed locally. Fresh GitHub checks on the final head also passed for Node 22.12, Node 24.x, build, and the Docker-backed integration test.

Why

agent-canvas currently owns OpenHands Cloud transport, proxy, and device-flow auth code while using this package for agent-server APIs. This adds shared client primitives so browser clients can use the TypeScript client for both local agent-server and cloud backends without breaking the current endpoint-client API.

Summary

  • Added OpenHandsClient, AgentServerClient, and CloudClient aggregate clients while preserving existing named endpoint clients and exports.
  • Moved cloud API helpers and device-flow auth into the package, including direct cloud requests, runtime proxy envelopes, org scoping, blob responses, and cloud settings/profile/git/secrets/skills/conversation helpers.
  • Updated the dist rewrite step to emit standards-compliant JSON import attributes for copied JSON assets.
  • Merged current main so this PR is validated against the repository's current agent-server:1.34.0-python baseline.

Issue Number

N/A

How to Test

  • npm ci
  • npm run lint
  • npm run build
  • npm run test:coverage
  • npm run format:check
  • With the tracked agent-server image running on localhost:8010: npm run audit:endpoints and npm run test:integration:deterministic
  • Optional live Cloud smoke: import CloudClient from ./dist/clients.js and call read-only Cloud endpoints with OPENHANDS_API_KEY, logging only sanitized metadata.

Video/Screenshots

N/A - library/client API change.

Type

  • Bug fix
  • Feature
  • Refactor
  • Breaking change
  • Docs / chore

Notes

Stacked consumer PR: OpenHands/agent-canvas#1621 uses CloudClient and the shared device-flow helpers from this branch. That downstream PR is currently separate and has its own merge conflict/PR-description blocker; this package PR no longer depends on an unmerged base branch and its diff remains scoped to the six intended files.

Validation context: current main includes ACP registry changes from #272. Its companion SDK PR is OpenHands/software-agent-sdk#4056; the validate-acp-providers workflow uses that link when checking the merged PR context.

Closes #274

@neubig neubig changed the title Add aggregate cloud and agent-server clients feat: add aggregate cloud and agent-server clients Jul 7, 2026
@github-actions github-actions Bot added the type: feat A new feature label Jul 7, 2026
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Endpoint audit

❌ 40 off-contract call(s) — not on the agent-server · classifiers: cloud

Category Count
❌ Off-contract (not on agent-server) 40
  ⛔ no known backend 15
  ↗️ served by cloud 25
➕ Missing API (agent-server has, client lacks) 11
agent-server endpoints 121
client endpoints 148

❌ Not on agent-server (gated, 40)

⛔ (no known backend) — served by no backend we can see (15)

  • DELETE /api/automation/v1/{}
  • DELETE /api/meta-profiles/{}
  • GET /api/automation/health
  • GET /api/automation/v1
  • GET /api/automation/v1/{}
  • GET /api/automation/v1/{}/runs
  • GET /api/automation/v1/{}/tarball
  • GET /api/meta-profiles
  • GET /api/meta-profiles/{}
  • GET /api/v1/config/models/search{}
  • GET /api/v1/config/providers/search{}
  • PATCH /api/automation/v1/{}
  • POST /api/automation/v1/{}/dispatch
  • POST /api/meta-profiles/{}
  • POST /api/meta-profiles/{}/activate

↗️ served by cloud (25)

  • DELETE /api/v1/app-conversations/{}
  • DELETE /api/v1/secrets/{}
  • GET /api/keys/current
  • GET /api/organizations
  • GET /api/shared-events/search
  • GET /api/v1/app-conversations/search
  • GET /api/v1/app-conversations/{}/download
  • GET /api/v1/app-conversations/{}/file
  • GET /api/v1/git/branches/search
  • GET /api/v1/git/installations/search
  • GET /api/v1/git/repositories/search
  • GET /api/v1/secrets/search
  • GET /api/v1/settings
  • GET /api/v1/settings/agent-schema
  • GET /api/v1/settings/conversation-schema
  • GET /api/v1/skills/search
  • PATCH /api/v1/app-conversations/{}
  • POST /api/v1/app-conversations
  • POST /api/v1/app-conversations/{}/switch_acp_model
  • POST /api/v1/app-conversations/{}/switch_profile
  • POST /api/v1/sandboxes/{}/pause
  • POST /api/v1/sandboxes/{}/resume
  • POST /api/v1/secrets
  • POST /api/v1/settings
  • PUT /api/v1/secrets/{}

➕ Missing API — agent-server exposes it, client does not implement (11)

  • GET /
  • GET /api/conversations
  • GET /api/conversations/{}/events
  • GET /api/conversations/{}/workspace
  • GET /api/conversations/{}/workspace/{}
  • GET /api/file/archive
  • GET /api/git/commits
  • GET /api/git/commits/{}/changes
  • GET /api/init
  • POST /api/conversations/{}/load_plugin
  • POST /api/init

@neubig neubig force-pushed the codex/cloud-client-abstraction branch from ec3c082 to 11460fe Compare July 7, 2026 21:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type: feat A new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add aggregate cloud and agent-server clients

2 participants