@openhex-ai/agent-sdk 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +9 -0
- package/README.md +207 -0
- package/dist/index-DOE19uln.d.ts +93 -0
- package/dist/index.d.ts +1218 -0
- package/dist/index.js +987 -0
- package/dist/index.js.map +1 -0
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/index.js +17 -0
- package/dist/tools/index.js.map +1 -0
- package/package.json +60 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
LICENSE — PLACEHOLDER (to be finalized before the first public release)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Openhex.
|
|
4
|
+
|
|
5
|
+
This is a scaffold. The final license terms for @openhex/agent-sdk have not
|
|
6
|
+
yet been decided. Do not redistribute until this file is replaced with the
|
|
7
|
+
chosen license (e.g. an OSS license such as MIT/Apache-2.0, or commercial
|
|
8
|
+
terms). Tracking: replace this placeholder in the MR that makes the package
|
|
9
|
+
publishable.
|
package/README.md
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# @openhex-ai/agent-sdk
|
|
2
|
+
|
|
3
|
+
Build AI agents on the **Openhex** platform. The same agent loop, built-in
|
|
4
|
+
tools, hooks, sessions, and MCP support that power Openhex agents — exposed as a
|
|
5
|
+
programmable TypeScript library.
|
|
6
|
+
|
|
7
|
+
> **Status.** The **chat API** and the **workspace API** are implemented —
|
|
8
|
+
> both speak the exact protocol the platform exposes (the user-side apps'
|
|
9
|
+
> conversations protocol and the published `/api/v2/workspaces/*` surface).
|
|
10
|
+
> The local `query()` agent loop is still a scaffold (throws
|
|
11
|
+
> `NotImplementedError`) and lands in a follow-up MR.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @openhex-ai/agent-sdk
|
|
17
|
+
# or
|
|
18
|
+
pnpm add @openhex-ai/agent-sdk
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Authentication
|
|
22
|
+
|
|
23
|
+
Get an API key (`mysta_…`) from the Openhex console, then set it as an
|
|
24
|
+
environment variable (or pass `{ apiKey }` to the client):
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
export OPENHEX_API_KEY=mysta_...
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Chat with an agent
|
|
31
|
+
|
|
32
|
+
The chat API mirrors the user-side apps' conversations protocol: a message
|
|
33
|
+
creates (or continues) a conversation and routes it to the agent
|
|
34
|
+
(auto-provisioning the agent's pod on first contact); the reply streams
|
|
35
|
+
back over SSE until the turn completes.
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
import { OpenhexClient } from '@openhex-ai/agent-sdk';
|
|
39
|
+
|
|
40
|
+
const client = new OpenhexClient({ agentId: '…', /* apiKey from env */ });
|
|
41
|
+
|
|
42
|
+
// Simplest: send and await the aggregated turn (creates a conversation).
|
|
43
|
+
const turn = await client.sendMessage('Summarize the latest support tickets');
|
|
44
|
+
console.log(turn.text);
|
|
45
|
+
console.log(turn.toolCalls); // tools the agent used
|
|
46
|
+
console.log(turn.conversationId); // reuse to continue the chat
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Multi-turn conversations
|
|
50
|
+
|
|
51
|
+
A `Conversation` remembers its id so follow-ups keep context:
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
const convo = client.conversation();
|
|
55
|
+
await convo.send('Remember my project is called Orbit.');
|
|
56
|
+
const turn = await convo.send('What is my project called?'); // → "Orbit"
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Streaming
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import { extractText, isTurnComplete } from '@openhex-ai/agent-sdk';
|
|
63
|
+
|
|
64
|
+
for await (const record of client.runTurn('Group them by theme')) {
|
|
65
|
+
if (record.sender === 'assistant') process.stdout.write(extractText(record));
|
|
66
|
+
if (isTurnComplete(record)) break;
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Low-level protocol
|
|
71
|
+
|
|
72
|
+
`client.chat` exposes the wire 1:1 when you need full control:
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
// POST /conversations/send — create (targetAgentIds) or continue (conversationId)
|
|
76
|
+
const { conversationId, userEventId } = await client.chat.send({
|
|
77
|
+
message: 'hello',
|
|
78
|
+
targetAgentIds: [agentId],
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// GET /conversations/:id/stream — resumable (auto-reconnects with backoff).
|
|
82
|
+
// Resume from a record's `id`, or from `userEventId` to capture just the reply.
|
|
83
|
+
for await (const record of client.chat.stream(conversationId, { lastEventId: userEventId })) {
|
|
84
|
+
console.log(record.sender, record.raw.type);
|
|
85
|
+
if (record.raw.type === 'result') break;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
await client.chat.interrupt(conversationId); // POST /conversations/:id/interrupt
|
|
89
|
+
const { entries } = await client.chat.messages(conversationId); // full history
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Each stream record is `{ id, seq, sender, event, timestamp, sessionId, raw }`
|
|
93
|
+
where `raw` carries the agent-runtime event (`{ type: 'assistant', message: { content: […] } }`,
|
|
94
|
+
`{ type: 'result' }`, etc.) — identical to what the app's chat UI consumes.
|
|
95
|
+
|
|
96
|
+
## Workspaces
|
|
97
|
+
|
|
98
|
+
A **workspace** is a billing + membership boundary you (a service provider /
|
|
99
|
+
partner) own. `client.workspaces` wraps the published `/api/v2/workspaces/*`
|
|
100
|
+
API 1:1 — provision members, mint member sessions, grant credits, and read
|
|
101
|
+
reporting. It speaks the same contract documented at
|
|
102
|
+
`/api/v2/workspaces/openapi.json`.
|
|
103
|
+
|
|
104
|
+
Auth is the `Bearer` token the client is configured with:
|
|
105
|
+
|
|
106
|
+
| Token | Unlocks |
|
|
107
|
+
| --- | --- |
|
|
108
|
+
| Workspace API key (`sk_ws_…`) | `whoami`, member provision/suspend, sessions, member list, credit grants |
|
|
109
|
+
| Owner user JWT | the above **plus** reporting (`ledger`, `insights`, member ledger) and API-key management |
|
|
110
|
+
| none | the public SMS-signup endpoints (`sendSmsCode` / `verifySmsCode`) |
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
import { OpenhexClient } from '@openhex-ai/agent-sdk';
|
|
114
|
+
|
|
115
|
+
// A partner backend authenticates with its workspace API key.
|
|
116
|
+
const client = new OpenhexClient({ apiKey: 'sk_ws_…' });
|
|
117
|
+
|
|
118
|
+
// Confirm which workspace the key belongs to.
|
|
119
|
+
const me = await client.workspaces.whoami();
|
|
120
|
+
|
|
121
|
+
// A slug-bound handle drops the slug from every call.
|
|
122
|
+
const ws = client.workspace(me.slug);
|
|
123
|
+
|
|
124
|
+
// Provision a member (idempotent on your sp_user_ref), grant credits,
|
|
125
|
+
// then mint a session JWT for the member's client.
|
|
126
|
+
const member = await ws.provisionMember({ sp_user_ref: 'user-42', display_name: 'Ada' });
|
|
127
|
+
await ws.grantCredits(member.member_id, { amount: 500, idempotency_key: `bonus:${member.member_id}` });
|
|
128
|
+
const session = await ws.mintSession({ sp_user_ref: 'user-42', ttl_seconds: 3600 });
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Public SMS signup (no auth — requires `public_sms_signup_enabled` on the workspace):
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
const pub = new OpenhexClient({ apiKey: 'unused' }); // token ignored on these routes
|
|
135
|
+
await pub.workspaces.sendSmsCode('acme', { phone: '13800000000' });
|
|
136
|
+
const session = await pub.workspaces.verifySmsCode('acme', { phone: '13800000000', code: '123456' });
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
> The admin-side routes (workspace ledger, insights, per-member ledger,
|
|
140
|
+
> API-key management, and the 4 workspace-lifecycle CRUD endpoints) are
|
|
141
|
+
> intentionally **not** part of this client. They power the SP admin
|
|
142
|
+
> webapp and aren't in the published partner spec — they'd 401 with a
|
|
143
|
+
> workspace API key anyway.
|
|
144
|
+
|
|
145
|
+
## Local agent loop (scaffold)
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
import { query } from '@openhex-ai/agent-sdk';
|
|
149
|
+
|
|
150
|
+
for await (const message of query({
|
|
151
|
+
prompt: 'Summarize the latest support tickets',
|
|
152
|
+
options: { allowedTools: ['Read', 'WebSearch'] },
|
|
153
|
+
})) {
|
|
154
|
+
if (message.type === 'result') console.log(message.result);
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Custom tools
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
import { query } from '@openhex-ai/agent-sdk';
|
|
162
|
+
import { tool, createSdkMcpServer } from '@openhex-ai/agent-sdk/tools';
|
|
163
|
+
import { z } from 'zod';
|
|
164
|
+
|
|
165
|
+
const getWeather = tool(
|
|
166
|
+
'get_weather',
|
|
167
|
+
'Look up the weather for a city',
|
|
168
|
+
z.object({ city: z.string() }),
|
|
169
|
+
async ({ city }) => ({ content: [{ type: 'text', text: `Sunny in ${city}` }] })
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
const q = query({
|
|
173
|
+
prompt: "What's the weather in Tokyo?",
|
|
174
|
+
options: {
|
|
175
|
+
mcpServers: { local: createSdkMcpServer({ name: 'local', tools: [getWeather] }) },
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Concepts
|
|
181
|
+
|
|
182
|
+
| Concept | Where |
|
|
183
|
+
| --------------------------- | -------------------------------------------------------- |
|
|
184
|
+
| Chat with a published agent | `client.sendMessage()`, `client.runTurn()`, `client.chat.*` |
|
|
185
|
+
| Resumable record stream | `client.chat.stream({ lastEventId })` |
|
|
186
|
+
| One-shot / streaming runs | `query()` (scaffold) |
|
|
187
|
+
| Stateful client + sessions | `OpenhexClient` |
|
|
188
|
+
| Built-in & custom tools | `options.allowedTools`, `tool()`, `createSdkMcpServer()` |
|
|
189
|
+
| Lifecycle hooks | `options.hooks`, `hookMatcher()` |
|
|
190
|
+
| Subagents | `options.agents` |
|
|
191
|
+
| External integrations (MCP) | `options.mcpServers` |
|
|
192
|
+
| Permissions | `options.permissionMode`, `options.canUseTool` |
|
|
193
|
+
|
|
194
|
+
The surface intentionally mirrors the [Claude Agent SDK](https://code.claude.com/docs/en/agent-sdk/overview)
|
|
195
|
+
so the mental model transfers directly.
|
|
196
|
+
|
|
197
|
+
## Development
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
pnpm --filter @openhex-ai/agent-sdk build # bundle to dist/ via tsup
|
|
201
|
+
pnpm --filter @openhex-ai/agent-sdk typecheck # tsc --noEmit
|
|
202
|
+
pnpm --filter @openhex-ai/agent-sdk test # jest
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## License
|
|
206
|
+
|
|
207
|
+
See [LICENSE](./LICENSE).
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Tool definitions for the Openhex Agent SDK.
|
|
5
|
+
*
|
|
6
|
+
* Two kinds of tools exist:
|
|
7
|
+
* 1. Built-in tools the platform executes (Read, Bash, WebSearch, ...).
|
|
8
|
+
* Reference these by name in {@link AgentOptions.allowedTools}.
|
|
9
|
+
* 2. Custom in-process tools you define with {@link tool} and expose
|
|
10
|
+
* through an in-process MCP server ({@link createSdkMcpServer}).
|
|
11
|
+
*
|
|
12
|
+
* NOTE: scaffold only — the executor that actually runs custom tools is
|
|
13
|
+
* implemented in a follow-up MR.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** Names of the tools the platform ships with, available out of the box. */
|
|
17
|
+
type BuiltinToolName = 'Read' | 'Write' | 'Edit' | 'Bash' | 'Glob' | 'Grep' | 'WebSearch' | 'WebFetch' | 'Agent' | 'AskUserQuestion';
|
|
18
|
+
/** Either a known built-in tool or any custom/MCP tool name. */
|
|
19
|
+
type ToolName = BuiltinToolName | (string & {});
|
|
20
|
+
/** Value returned from a custom tool handler. */
|
|
21
|
+
interface ToolResult {
|
|
22
|
+
content: Array<{
|
|
23
|
+
type: 'text';
|
|
24
|
+
text: string;
|
|
25
|
+
}>;
|
|
26
|
+
isError?: boolean;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* A custom tool: a name, a Zod-typed input schema, and an async handler.
|
|
30
|
+
* Construct via {@link tool} rather than building this by hand.
|
|
31
|
+
*/
|
|
32
|
+
interface SdkToolDefinition<TSchema extends z.ZodTypeAny = z.ZodTypeAny> {
|
|
33
|
+
name: string;
|
|
34
|
+
description: string;
|
|
35
|
+
inputSchema: TSchema;
|
|
36
|
+
handler: (input: z.infer<TSchema>, extra: {
|
|
37
|
+
signal?: AbortSignal;
|
|
38
|
+
}) => Promise<ToolResult> | ToolResult;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* A schema-erased tool definition, used wherever tools are stored in a
|
|
42
|
+
* heterogeneous collection. `tool()` returns the precisely-typed
|
|
43
|
+
* {@link SdkToolDefinition}; collections widen to this so an array can
|
|
44
|
+
* mix tools with different input schemas (Zod generics are invariant in
|
|
45
|
+
* the handler's parameter position, so the precise type can't be kept).
|
|
46
|
+
*/
|
|
47
|
+
type AnySdkToolDefinition = SdkToolDefinition<any>;
|
|
48
|
+
/** An in-process MCP server bundling one or more {@link SdkToolDefinition}s. */
|
|
49
|
+
interface SdkMcpServer {
|
|
50
|
+
type: 'sdk';
|
|
51
|
+
name: string;
|
|
52
|
+
version: string;
|
|
53
|
+
tools: AnySdkToolDefinition[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Helpers for defining custom in-process tools.
|
|
58
|
+
*
|
|
59
|
+
* Available as a sub-path import:
|
|
60
|
+
* import { tool, createSdkMcpServer } from '@openhex-ai/agent-sdk/tools';
|
|
61
|
+
*
|
|
62
|
+
* NOTE: scaffold only — `tool` and `createSdkMcpServer` build the typed
|
|
63
|
+
* definitions today; the executor that runs them inside the agent loop
|
|
64
|
+
* lands in a follow-up MR.
|
|
65
|
+
*/
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Define a custom tool with a Zod-typed input schema and an async
|
|
69
|
+
* handler. Bundle the result into a server with {@link createSdkMcpServer}
|
|
70
|
+
* and pass it via `options.mcpServers`.
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* const getWeather = tool(
|
|
74
|
+
* 'get_weather',
|
|
75
|
+
* 'Look up the weather for a city',
|
|
76
|
+
* z.object({ city: z.string() }),
|
|
77
|
+
* async ({ city }) => ({ content: [{ type: 'text', text: `Sunny in ${city}` }] }),
|
|
78
|
+
* );
|
|
79
|
+
*/
|
|
80
|
+
declare function tool<TSchema extends z.ZodTypeAny>(name: string, description: string, inputSchema: TSchema, handler: (input: z.infer<TSchema>, extra: {
|
|
81
|
+
signal?: AbortSignal;
|
|
82
|
+
}) => Promise<ToolResult> | ToolResult): SdkToolDefinition<TSchema>;
|
|
83
|
+
/**
|
|
84
|
+
* Bundle one or more {@link tool} definitions into an in-process MCP
|
|
85
|
+
* server that the agent loop can call without spawning a subprocess.
|
|
86
|
+
*/
|
|
87
|
+
declare function createSdkMcpServer(config: {
|
|
88
|
+
name: string;
|
|
89
|
+
version?: string;
|
|
90
|
+
tools: AnySdkToolDefinition[];
|
|
91
|
+
}): SdkMcpServer;
|
|
92
|
+
|
|
93
|
+
export { type AnySdkToolDefinition as A, type BuiltinToolName as B, type SdkMcpServer as S, type ToolName as T, type SdkToolDefinition as a, type ToolResult as b, createSdkMcpServer as c, tool as t };
|