Skip to content

Repository files navigation

@smolpaws/openhands-agent

Idiomatic TypeScript transpilation of the OpenHands Python agent-sdk.

Status

0.3.3 established native OpenAI tool-completion parity. The current development line extends the same ToolDefinition flow through Anthropic Messages, Gemini Interactions, OpenRouter, LiteLLM-compatible endpoints, and custom OpenAI-compatible gateways. The main architecture is documented in docs/ARCHITECTURE.md:

  • zod-backed event, tool, settings, profile, and serialization models
  • profile-first LLM clients for OpenAI chat completions, OpenAI Responses, Anthropic, Gemini, and OpenAI-compatible profiles
  • local/remote conversation state, disk-backed event logs, agent loop, pending tool-call queue, parallel execution, restore, and stuck detection
  • context, condensers, skills, hooks, critics, file-based subagents, git helpers, MCP wrappers
  • concrete tools: terminal, file editor, glob, grep, task tracker, and injectable browser adapter

Intentional deviations from Python remain: no ACP runtime, security analyzers, risk scoring, confirmation gates, Python Cipher, or Python secret-storage split. The Python SecretRegistry surface maps to the current TypeScript SecretStore/keyring model.

Python SDK parity

This package is tracking the Python agent-sdk architecture while staying idiomatic TypeScript. The implemented surfaces currently include focused parity coverage for:

  • LLM message/content serialization, Agent-to-LLM ToolDefinition propagation, and provider-owned OpenAI chat completions/Responses, Anthropic Messages, and Gemini Interactions request/response mapping
  • event schemas and eventsToMessages conversion, including parallel tool-call batching behavior
  • conversation state, local/remote conversations, pause/resume, restore, parallel execution, and stuck detection
  • settings/profiles, profile-selected LLM field hygiene, provider/profile-scoped API key references, and keyring-backed secret storage
  • tools, workspace abstractions, git helpers, hooks, skills/context, MCP wrappers, critics, and file-based subagents

Accepted deviations are deliberate and should not be treated as missing work unless the product needs them later: ACP runtime execution, security/confirmation policy execution, and Python's older SecretRegistry API.

Goals

  • Idiomatic TypeScript. Not a literal line-by-line port. We respect the Python SDK's architectural choices and adapt them to TS conventions.
  • Type enforcement. Strict TypeScript everywhere; runtime validation via zod v4 (replacing pydantic), using its native z.toJSONSchema() for tool/settings schema generation.
  • Fresh transpilation. We do not copy existing code. The earlier TS attempt in oh-tab is outdated and serves only as a reference for product-level profile semantics, tooling, and tests.
  • Profile-first product LLM boundary. Product and REST callers select an LLMProfile; they do not pass raw Python-style LLM objects or loose model/provider fields. Low-level provider clients remain available as explicit advanced SDK/test building blocks.
  • API-native provider clients. Implement provider APIs as they actually are — OpenAI-compatible Chat Completions, OpenAI Responses, Anthropic Messages, and Gemini Interactions — rather than flattening provider-specific reasoning, caching, tool-call, and replay behavior into a leaky abstraction.
  • Host-owned profile persistence. This package validates and consumes LLMProfile records but does not choose a global local profile database/path. Host products persist profile JSON in their own settings stores and pass selected profiles to the SDK.
  • Lower-risk secret handling. Do not port Python's plaintext/local plus encrypted-at-rest remote secret stack. Persist secret references only; store actual secret values in the OS keyring under the openhands service. LLM API keys are provider-scoped by default, with per-profile overrides for cases like multiple proxy profiles for the same provider.
  • Tooling parity with oh-tab. Same npm/build/test stack (tsup, vitest, eslint type-checked) unless there's a good reason to diverge.

OpenAI-compatible native tools

providerId: 'openrouter', providerId: 'litellm_proxy', and custom OpenAI-compatible baseUrl profiles use OpenAIChatClient. Native tools are sent with the standard Chat Completions function shape, and tool calls/results use assistant tool_calls plus role: 'tool' messages. OpenRouter's standard endpoint and configurable LiteLLM-compatible/custom base URLs are covered by transport tests.

Compatibility here means the endpoint accepts the OpenAI Chat Completions dialect at <baseUrl>/chat/completions with bearer authentication. The SDK does not translate tools into an upstream provider's native Anthropic or Gemini dialect when that provider sits behind a proxy, and it does not guess nonstandard proxy payloads. Configure such gateways to expose the Chat Completions function-tool contract or provide a provider-specific adapter.

Tooling

Concern Choice
Language TypeScript 5.9, strict + extra safety flags
Runtime validation zod v4 (pydantic equivalent; native JSON Schema)
Bundler tsup (ESM + CJS)
Tests vitest
Lint eslint with recommended-type-checked

Install

npm install @smolpaws/openhands-agent

For local development in this repo:

npm install
npm run typecheck
npm run lint
npm test
npm run build
npm run test:examples

Quick start

import {
  Agent,
  ConversationState,
  FinishTool,
  LocalConversation,
  llmProfileSchema,
  messageSchema,
  type LLMClient,
} from '@smolpaws/openhands-agent';

const llm: LLMClient = {
  profile: llmProfileSchema.parse({ profileId: 'example', providerId: 'mock', model: 'mock' }),
  async complete() {
    return {
      message: messageSchema.parse({
        role: 'assistant',
        content: null,
        tool_calls: [
          {
            id: 'finish-1',
            name: 'finish',
            arguments: JSON.stringify({ message: 'Hello from TypeScript OpenHands.' }),
            origin: 'completion',
          },
        ],
      }),
      usage: null,
      raw: {},
    };
  },
};

const state = new ConversationState();
const conversation = new LocalConversation({
  agent: new Agent({ llm, tools: [FinishTool.create()] }),
  state,
});

conversation.sendMessage('Say hello and finish.');
await conversation.run();
console.log(state.executionStatus);

Examples

Runnable TypeScript examples live in examples/ and are checked by npm run test:examples. Real-LLM examples use examples/_shared/exampleProfile.ts: by default set OPENAI_API_KEY to run them against an OpenAI LLM profile, or set LLM_PROVIDER_ID/LLM_PROVIDER and the matching <PROVIDER>_API_KEY env var to exercise another provider. The helper stores keys under llmProviderSecretRef(profile.providerId), optionally overrides the model with OPENAI_MODEL or LLM_MODEL, and skips gracefully when no provider key is present. npm run live:gemini-tools is the opt-in Gemini native-tool smoke; Anthropic tool coverage is recorded-shape/unit-only and makes no live request by default.

Example Covers
hello-world.ts Real OpenAI profile completion through the shared env-backed example profile helper
native-openai-tools.ts Real OpenAI Responses read/edit/finish function calls through Agent tool dispatch
native-gemini-tools.ts Credential-gated Gemini Interactions tool dispatch; defaults to gemini-3.5-flash-lite
native-tool-serialization.ts Keyless comparison of one ToolDefinition across all four provider wire formats
tools.ts Concrete terminal, file editor, glob, grep, and task tracker tools
profiles-and-secrets.ts Provider/profile-scoped LLM API key references and secret store usage
agent-settings.ts Agent settings/profile validation and profile-selected raw LLM field cleanup
conversation-patterns.ts Real profile completion, pause/resume status, parallel tool execution, manual observation parsing, and stuck detection
skills-and-context.ts Agent context, static skills, and keyword-triggered skill suffixes
hooks.ts Hook config and pre-tool-use hook execution
mcp.ts MCP tool definitions, action argument sanitization, and observations
remote-workspace.ts Guarded remote workspace usage against an agent-server

Issue tracking

Work is tracked with Beads (bd). The source of truth is .beads/issues.jsonl.

bd list --status open
bd show openhands-agent-1

License

MIT

About

Idiomatic TypeScript transpilation of the OpenHands Python agent-sdk. npm: @smolpaws/openhands-agent

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages