Teach-me · OpenHands

The typescript-client repo, and the proposal to fold it into the SDK

Graham Neubig proposed moving OpenHands/typescript-client into OpenHands/software-agent-sdk to ease releases. To have an opinion, you need to know what the client is, how it stays in sync with the Python SDK, who depends on it, how it relates to the existing openhands-workspace member, and what "the release process" actually is. This page teaches that, then quizzes you.

★ The one thing to remember: @openhands/typescript-client is the browser-side SDK that lets a web UI drive the OpenHands agent-server over HTTP/WebSocket. Its one real consumer is agent-canvas (plus vulnerability-fixer via a git dep). It mirrors the Python SDK's API, so the two must be released in lockstep — which is the whole motivation for the move.

1Background

Deep background for beginners — skip if you know what an "agent server" and an "SDK client" are

Safe to skip if OpenHands' client/server split is already familiar.

An agent here is an LLM in a loop: it reads a task, calls tools (run a bash command, edit a file, browse), reads the results, and repeats. In OpenHands that loop runs inside an agent-server — a backend process that owns a conversation, executes the tools in a sandbox/workspace, and streams back events.

A client SDK is a library that talks to that server so you don't hand-write HTTP calls. It wraps "create a conversation", "send this message", "run this bash command", "list the files" into typed function calls. OpenHands' primary SDK is in Python (software-agent-sdk). The typescript-client is the same idea for JavaScript/TypeScript callers — importantly, ones running in a browser.

The narrow background: what this repo is

The package is @openhands/typescript-client, described in its own manifest as the "TypeScript client for OpenHands Agent Server." typescript-client/package.json Its README says it plainly: it "mirrors the structure and functionality of the Python OpenHands Software Agent SDK, but only supports remote conversations."

Two properties matter for everything below:

  • It mirrors the Python SDK. Same shapes — Conversation, Agent, Workspace, ConversationManager, ServerClient, BashClient, HttpError. When the Python SDK / agent-server API changes, the TS client has to change to match.
  • It is browser-compatible by design. File operations use Blob, File, FormData — browser-native APIs, no Node fs. That is the reason it exists as a separate TS package instead of "just call the Python SDK": a React app in the browser can't import Python, but it can import this and talk to the server directly.

It is published to GitHub Packages (the npm registry at npm.pkg.github.com), and it is still labelled ALPHA in its own README.

ⓘ "Remote conversations only" means the client never runs the agent loop itself — it always points at an already-running agent-server. So it's a thin, typed, browser-safe wrapper over that server's API surface, not an agent runtime.

2The intuition: a hand-written mirror of the SDK, and no schema forcing them to agree

The important detail — the one the socket metaphor would hide — is how the TS client tracks the Python SDK. It is not generated. There is no OpenAPI spec, no codegen step, no @generated banner anywhere in src/. It is a hand-written re-implementation that mirrors the Python package module-for-module.

Lay the two side by side and it's a near one-to-one port. The Python SDK's modules — agent, conversation, events, llm, security, workspace — each have a TS twin, and every server resource gets a typed client under src/client/* (conversation-client.ts, bash-client.ts, file-client.ts, settings-client.ts, …). Transport is a plain http-client.ts for REST plus a websocket-client.ts for the event stream — the browser-safe equivalents of what the Python SDK does over its own HTTP/WS.

software-agent-sdk (Python) typescript-client (hand-ported) conversation/ RemoteConversation conversation/remote-conversation.ts events/ event stream events/websocket-client.ts agent / llm / security / workspace agent/ llm/ security/ workspace/ per-resource server calls client/*-client.ts (bash, file, …) hand-kept parallel — no codegen agent-server same REST + WebSocket contract agent-canvas (browser, imports TS)
Two hand-maintained parallels over one wire contract. Nothing mechanical keeps the dashed lines aligned — a human does, on every API change.

That's the actual problem the move addresses. Two implementations of one contract, maintained by hand, in separate repos with separate release triggers. When the agent-server API gains a field or renames an event, someone edits the Python side, ships it, and must remember to make the matching TS edit and cut a second release. Nothing in CI fails when they forget — the drift only surfaces at runtime, in canvas, as a deserialization error or a missing field. That is the classic cost of a hand-synced mirror split across repo boundaries: the invariant ("these two agree") is real but unenforced.

ⓘ Same-repo doesn't magically generate the client — it stays hand-written. But it collapses the coordination: the diff that changes the Python contract and the diff that updates the TS mirror land in one PR, reviewed together, released together. The invariant moves from "someone remembers" to "it's in the same changeset." (The deeper fix — generating the TS client from a shared schema — is a separate question this proposal doesn't settle.)

3Who actually uses it (checked, not guessed)

The proposal's risk is entirely "who breaks if the package's home or versioning changes?" So the real question is the dependency graph. Across the local clones and an OpenHands-org code search:

RepoUses typescript-client?How
agent-canvasYes — primary Pinned dep "@openhands/typescript-client": "1.28.0", imported across ~48 source files (websocket/conversation context, server + bash clients, MCP types, ACP providers). agent-canvas/package.json
vulnerability-fixerYes Via a git dependency: "…": "git+https://github.com/OpenHands/typescript-client.git" — not a pinned registry version.
automationNoNo package.json dep, no imports.
odieNoNo package.json dep, no imports.
ⓘ So the answer to "agent-canvas? automation? odie?" is: agent-canvas, yes and heavily; automation and odie, no. Plus vulnerability-fixer via git. That small blast radius is good news for the move — but the two consumers depend on it in different ways (a pinned registry version vs a git URL), so both mechanisms have to keep working.

4Its Python sibling: openhands-workspace is already this pattern

The best analogy isn't external — it's already a member of the SDK repo. openhands-workspace ("Docker and container-based workspace implementations") is, in part, a client to the agent-server too. Its RemoteWorkspace / APIRemoteWorkspace / CloudWorkspace classes use httpx to reach a remote agent-server and drive it. (The base RemoteWorkspace / RemoteWorkspaceMixin actually live in openhands-sdk openhands-sdk/…/workspace/remote/remote_workspace_mixin.py; openhands-workspace adds the concrete backends.)

So typescript-client is best understood as a sibling of the remote/client side of openhands-workspace — same job, different language, browser-safe. Both speak the agent-server's HTTP/WS contract. One caveat keeps the analogy honest:

openhands-workspace (Python)typescript-client (TS)
Provision a runtime (Docker / apptainer / cloud, sysbox, images, ports) Yes — inherently server-side Python No — a browser can't spin up containers
Talk to an already-running agent-server (REST + WS) Yes Yes — this is all it does
Runs in the browser No Yes — the reason it exists
★ So the SDK repo already ships an agent-server client as a workspace member (openhands-workspace). typescript-client is the same species — the remote-conversation client slice, ported to TS for the browser. That's the real case for the move: it's a sibling client that happens to be in a different language, so it belongs next to the others.

5The proposed move — and the one thing that can break

Today typescript-client releases on its own: pushing a v* tag runs release.yml, which builds and publishes to GitHub Packages. release.yml The Python SDK repo, meanwhile, publishes Python to PyPI and some binaries — it has no TS/npm publish step today.

"Ease the release process" means: put the TS client inside the SDK repo so one release cuts both the Python SDK and the matching TS client together — no more two-repo hand-coordination. The mirroring relationship becomes a same-repo invariant instead of a promise.

⚠ The gotcha: versioning is already out of sync. The repo's own package.json says version 0.1.1 and its newest git tag is v0.6.0 — but agent-canvas pins 1.28.0. Those are three different numbering worlds. Whatever the move does, the SDK-repo release must keep publishing @openhands/typescript-client to GitHub Packages at a version the consumers can still resolve — the pinned 1.28.0 in canvas and the git+https URL in vulnerability-fixer. If the package name, registry, or version scheme shifts silently, one or both consumers break at install time.

None of that is a reason not to do it. It's the checklist for doing it safely: keep the package name, keep publishing to GitHub Packages, and decide the version story (does it now share the SDK's version, and if so, how do existing 1.28.0 pins migrate?).

6Quiz

Six questions. Click an answer to see whether you got it and why.