Skip to content

feat(client): add missing agent-server endpoints and fix skills refresh#231

Closed
VascoSch92 wants to merge 3 commits into
mainfrom
vasco/add-agent-server-endpoints
Closed

feat(client): add missing agent-server endpoints and fix skills refresh#231
VascoSch92 wants to merge 3 commits into
mainfrom
vasco/add-agent-server-endpoints

Conversation

@VascoSch92

Copy link
Copy Markdown
Member

Summary

Adds client coverage for agent-server REST endpoints that the audit reported as missing (or that the client targeted incorrectly). Implementations follow the agent-server routers in OpenHands/software-agent-sdk (verified against tag v1.29.0 — the pinned integration image).

Endpoint Status before Change
GET /api/init missing new InitClient.getStatus()
POST /api/init missing new InitClient.initialize() (sends X-Init-API-Key)
GET /api/conversations/{id}/workspace missing new ConversationClient.getWorkspaceRoot()
GET /api/conversations/{id}/workspace/{path} missing new ConversationClient.getWorkspaceFile()
GET /api/conversations/{id}/events (batch) missing new ConversationClient.batchGetEvents()
GET /api/bash/bash_events/ (batch) missing new BashClient.batchGetEvents()
POST /api/skills/installed/{name}/refresh wrong path (/update) fixed SkillsClient.refreshSkill()/refresh
GET /api/conversations (batch) already implemented, undetected by audit made detectable
GET / already covered by ServerClient.getRoot() see note below

Details

  • InitClient (new src/client/init-client.ts) wraps the deferred-init / warm-pool endpoints. POST /api/init authenticates with the bootstrap X-Init-API-Key header (distinct from the per-session X-Session-API-Key). Exported from clients.ts, model types (InitState/InitStatus/InitRequest) from models/api.ts, and wired into ConversationManager as the init namespace.
  • Workspace static serving returns the raw bytes as a Blob (call .text() for HTML/text), matching FileClient.downloadTrajectory.
  • refreshSkill previously POSTed to /api/skills/installed/{name}/update, which the agent-server does not expose — the route is /refresh. The mocked unit test asserting the old URL is updated.
  • The batch methods (and the pre-existing getConversations) now type the response as (T | null)[] instead of Array<T | null>. This is behaviorally identical but lets the endpoint-audit path extractor — which can't parse the nested >> generic — recognize these calls.

Note on GET /

ServerClient.getRoot() already calls GET /, so the endpoint is functionally covered (the agent-server maps / to the same get_server_info handler as /server_info, which getServerInfo() also covers). The audit still lists it because its PATH_LIT matcher only recognizes paths beginning with /api|/server_info|/alive|/health|/ready; a bare / is structurally undetectable without changing the audit script, which is out of scope here.

Testing

  • Unit (src/__tests__/api-clients.test.ts, mocked fetch): new cases for the init namespace, BashClient.batchGetEvents, ConversationClient.batchGetEvents, the workspace serving methods, and InitClient get/post (header presence + omission). Full suite: 274 passing.
  • Integration (deterministic, no LLM, against the pinned agent-server image): contract guards for the batch-events endpoint (null-for-missing + 404 for unknown conversation), workspace file serving (round-trips a written file, 404 for a missing one), the deferred-init routes (404 in the image's non-deferred mode), and the skills /refresh route (404 for an uninstalled skill). bash.integration.test.ts also exercises the new server-side bash batch endpoint.
  • npm run build, lint (0 errors), and format:check all pass.

Cover agent-server REST endpoints that were unimplemented or mis-targeted by
the client (surfaced by the endpoint audit against the agent-server OpenAPI):

- GET/POST /api/init via a new InitClient (deferred-init / warm-pool servers),
  wired into ConversationManager as the `init` namespace.
- GET /api/conversations/{id}/workspace and /workspace/{path} static workspace
  file serving via ConversationClient.getWorkspaceRoot/getWorkspaceFile.
- GET /api/conversations/{id}/events server-side batch via
  ConversationClient.batchGetEvents (distinct from the per-id getEvents loop).
- GET /api/bash/bash_events/ server-side batch via BashClient.batchGetEvents.
- POST /api/skills/installed/{name}/refresh — corrects refreshSkill from the
  non-existent /update path to /refresh.

The batch return types use `(T | null)[]` instead of `Array<T | null>` so the
endpoint-audit's path extraction (which cannot parse the nested `>>` generic)
detects getConversations and the new batch methods.

Adds unit tests (mocked fetch) for every new method plus deterministic
integration contract guards against the pinned agent-server image.
@github-actions

Copy link
Copy Markdown
Contributor

Endpoint audit

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

Category Count
❌ Off-contract (not on agent-server) 7
  ⛔ no known backend 6
  ↗️ served by cloud 1
➕ Missing API (agent-server has, client lacks) 1
agent-server endpoints 104
client endpoints 108

❌ Not on agent-server (gated, 7)

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

  • DELETE /api/meta-profiles/{}
  • GET /api/meta-profiles
  • GET /api/meta-profiles/{}
  • POST /api/cloud-proxy
  • POST /api/meta-profiles/{}
  • POST /api/meta-profiles/{}/activate

↗️ served by cloud (1)

  • GET /api/shared-events/search

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

  • GET /

VascoSch92 and others added 2 commits June 24, 2026 10:14
The batchGetEvents methods sent event ids as query params, but the
agent-server's GET /api/conversations/{id}/events and GET
/api/bash/bash_events/ batch endpoints require the ids in the request
body, so the server returned 422 (missing required body).

fetch cannot send a body on a GET request (it throws), so route
GET-with-body requests through Node's http/https module while leaving the
fetch path untouched for every other request.

Update the conversation integration test to the real server contract:
unlike the bash endpoint, the conversation endpoint raises on unknown ids
rather than returning null, so round-trip a real event instead of
asserting missing-as-null. Update the unit tests to mock node:http and
assert the ids land in the body.
@VascoSch92

Copy link
Copy Markdown
Member Author

Closing in favour of splitting this into one minimal PR per endpoint/feature, each carrying its own unit and integration tests:

The GET-with-body HttpClient infrastructure needed by the two batch endpoints lives in both #245 and #246 (intentionally identical so they merge cleanly).

The two non-endpoint rows from this PR — the getConversations Array<T|null>(T|null)[] audit-detection tweak and the GET / note — were dropped from the split as they aren't endpoint additions.

@VascoSch92 VascoSch92 closed this Jun 29, 2026
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.

1 participant