Skip to content
This repository was archived by the owner on Sep 7, 2026. It is now read-only.

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

Merged
hieptl merged 7 commits into
mainfrom
codex/cloud-client-abstraction
Jul 16, 2026
Merged

hieptl merged 7 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 OpenHands/software-agent-sdk#4751

@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

@hieptl hieptl self-assigned this Jul 16, 2026

@hieptl hieptl left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thank you! 🙏

@hieptl
hieptl merged commit d14ea20 into main Jul 16, 2026
11 checks passed
@openhands-release-bot openhands-release-bot Bot added the released: v1.33.0 Shipped in v1.33.0 label Jul 16, 2026
@openhands-release-bot

Copy link
Copy Markdown
Contributor

🚀 Released in v1.33.0.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

released: v1.33.0 Shipped in v1.33.0 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

3 participants