Idiomatic TypeScript transpilation of the OpenHands Python agent-sdk.
0.3.3 is the native OpenAI tool-completion parity release of the fresh TypeScript transpilation. It covers the core SDK surfaces needed to build and run agent loops locally, passes usable Agent tools through OpenAI Chat Completions and Responses, and documents the main architecture 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.
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
ToolDefinitionpropagation, and provider-owned OpenAI chat completions/Responses, Anthropic, and Gemini request/response mapping - event schemas and
eventsToMessagesconversion, 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.
- 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-tabis 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-styleLLMobjects 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 GenerateContent — 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
LLMProfilerecords 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
openhandsservice. 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.
| 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 |
npm install @smolpaws/openhands-agentFor local development in this repo:
npm install
npm run typecheck
npm run lint
npm test
npm run build
npm run test:examplesimport {
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);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.
| 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 |
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 |
Work is tracked with Beads (bd). The source of truth is .beads/issues.jsonl.
bd list --status open
bd show openhands-agent-1MIT