Skip to content

feat(client): add SubAgentsClient for POST /api/sub-agents#253

Merged
VascoSch92 merged 3 commits into
mainfrom
vasco/add-sub-agents-endpoint
Jul 3, 2026
Merged

feat(client): add SubAgentsClient for POST /api/sub-agents#253
VascoSch92 merged 3 commits into
mainfrom
vasco/add-sub-agents-endpoint

Conversation

@VascoSch92

Copy link
Copy Markdown
Member

Summary

The agent-server exposes a read-only POST /api/sub-agents endpoint (added in software-agent-sdk #3911, released in v1.31.0) that lists the file-based and built-in sub-agents (delegate agents) available to a workspace. It mirrors POST /api/skills. This endpoint was missing from the TypeScript client — this PR adds it.

What's included

  • SubAgentsClient (src/client/sub-agents-client.ts) with getSubAgents(request)POST /api/sub-agents, following the same shape as SkillsClient/PluginsClient.
  • Types (src/models/api.ts): SubAgentsRequest, SubAgentInfo, SubAgentsResponse, and SubAgentLevel. SubAgentInfo is a lossless mirror of the server's AgentDefinition view — every frontmatter field plus the discovered level/source, is_builtin, and the inline system_prompt.
  • Wiring: manager.subAgents on ConversationManager (constructed + closed alongside the other clients); exported from clients.ts and the model types from index.ts.

Tests

  • Unit (api-clients.test.ts): manager.subAgents namespace assertion; request/response mapping to /api/sub-agents (incl. API-key header, full SubAgentInfo payload); default empty-body behavior.
  • Integration (deterministic-api.integration.test.ts):
    • The bundled read test now also exercises getSubAgents (builtin discovery), asserting the contract shape.
    • A dedicated test writes a project-level agent into the workspace and verifies the endpoint discovers it losslessly (name/level/source/tools/system_prompt), then cleans up.

Verification

  • npm test — 273 unit tests pass.
  • Integration tests run and pass against the pinned ghcr.io/openhands/agent-server:1.31.0-python image (the project-level discovery test confirms the endpoint end-to-end). Note: the frozen image ships no built-in agent files, so the builtin path returns an empty list — the assertions account for this.
  • tsc --noEmit, eslint, and prettier --check all clean.

Add a client for the agent-server's read-only POST /api/sub-agents
endpoint, which lists the file-based and built-in sub-agents (delegate
agents) available to a workspace. The endpoint mirrors POST /api/skills
and was missing from the TypeScript client.

- SubAgentsClient.getSubAgents(request) posts to /api/sub-agents
- SubAgentsRequest / SubAgentInfo / SubAgentsResponse types mirror the
  server's lossless AgentDefinition view (all frontmatter fields plus
  level, source, is_builtin, and the inline system_prompt)
- Expose manager.subAgents on ConversationManager; export the client and
  types from clients.ts and index.ts
- Unit tests for request/response mapping and the default empty body
- Integration test that discovers a project-level sub-agent end-to-end
@github-actions github-actions Bot added the type: feat A new feature label Jul 2, 2026
@VascoSch92 VascoSch92 requested a review from enyst July 2, 2026 21:29
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Endpoint audit

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

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

❌ Not on agent-server (gated, 6)

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

  • DELETE /api/meta-profiles/{}
  • GET /api/meta-profiles
  • GET /api/meta-profiles/{}
  • 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 (10)

  • GET /
  • GET /api/conversations
  • GET /api/conversations/{}/events
  • GET /api/conversations/{}/workspace
  • GET /api/conversations/{}/workspace/{}
  • GET /api/file/archive
  • GET /api/init
  • POST /api/conversations/{}/load_plugin
  • POST /api/conversations/{}/navigate
  • POST /api/init

@enyst

enyst commented Jul 3, 2026

Copy link
Copy Markdown
Member

@OpenHands why did the CI fail? “HttpError: HTTP request failed (500 Internal Server Error): {"detail":"Internal Server Error","exception":"500: Failed to activate profile"}”

/codereview

@openhands-ai

openhands-ai Bot commented Jul 3, 2026

Copy link
Copy Markdown

I'm on it! enyst can track my progress at all-hands.dev

enyst commented Jul 3, 2026

Copy link
Copy Markdown
Member

I checked the failed integration-test job. The /api/sub-agents calls themselves passed; the failure is a side effect from the new project-level sub-agent integration test.

What happened:

  • The new test writes .openhands/agents/<agent>.md directly into the mounted host workspace with Node fs.
  • That creates /tmp/agent-workspace/.openhands as the GitHub runner user with default non-world-writable directory permissions.
  • Later, the existing profile round-trip test calls POST /api/profiles/<name>/activate.
  • Inside the container, agent-server tries to update/chmod workspace/.openhands, but the container user does not own the host-created directory.
  • The server log shows the real cause just before the 500:
    • Failed to set permissions on workspace/.openhands: [Errno 1] Operation not permitted: 'workspace/.openhands'
    • Failed to activate profile - file I/O error

So the CI failure is not from SubAgentsClient request/response mapping itself; it is test pollution/permission coupling caused by creating .openhands from the host side before the profile activation test runs.

Likely fixes: create the test agent file through the agent-server/workspace path as the container user, or explicitly make/chown/chmod the host-created .openhands directory so the container user can write to it, and clean up the directory after the sub-agent test.

This comment was created by an AI agent (OpenHands) on behalf of the user.

…y test

The project-level sub-agent discovery test writes
`.openhands/agents/<name>.md` into the workspace from the host (the
test runner). Its cleanup only unlinked the agent file, leaving the
`.openhands/` directory behind, owned by the host user.

The agent-server container runs as a different user, so a later test
(profile round-trip) failed when the server tried to chmod
`workspace/.openhands` during profile activation:

    Failed to set permissions on workspace/.openhands:
    [Errno 1] Operation not permitted
    -> 500 Failed to activate profile

Add a `removeWorkspacePath` helper and use it to remove the whole
`.openhands` tree the test creates, restoring the workspace so the
server can create and own `.openhands` itself.
@OpenHands OpenHands deleted a comment from openhands-ai Bot Jul 3, 2026
@VascoSch92

Copy link
Copy Markdown
Member Author

@enyst if you have time :)

@VascoSch92 VascoSch92 merged commit dd17a4e into main Jul 3, 2026
11 checks passed
This was referenced Jul 3, 2026
@openhands-release-bot openhands-release-bot Bot added the released: v1.32.0 Shipped in v1.32.0 label Jul 7, 2026
@openhands-release-bot

Copy link
Copy Markdown
Contributor

🚀 Released in v1.32.0.

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

Labels

released: v1.32.0 Shipped in v1.32.0 type: feat A new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants