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.
@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 Nodefs. 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.
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.
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.
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:
| Repo | Uses typescript-client? | How |
|---|---|---|
| agent-canvas | Yes — 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-fixer | Yes | Via a git dependency: "…": "git+https://github.com/OpenHands/typescript-client.git"
— not a pinned registry version. |
| automation | No | No package.json dep, no imports. |
| odie | No | No package.json dep, no imports. |
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 |
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.
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.