@openhex-ai/agent-sdk 0.0.1 → 0.1.0

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/README.md CHANGED
@@ -4,11 +4,11 @@ Build AI agents on the **Openhex** platform. The same agent loop, built-in
4
4
  tools, hooks, sessions, and MCP support that power Openhex agents — exposed as a
5
5
  programmable TypeScript library.
6
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.
7
+ > **Status.** The **chat API**, the **React chat UI** (`/react`), and the
8
+ > **workspace API** are implemented — all speak the exact protocol the platform
9
+ > exposes (the user-side apps' conversations protocol and the published
10
+ > `/api/v2/workspaces/*` surface). The local `query()` agent loop is still a
11
+ > scaffold (throws `NotImplementedError`) and lands in a follow-up MR.
12
12
 
13
13
  ## Install
14
14
 
@@ -37,12 +37,12 @@ back over SSE until the turn completes.
37
37
  ```ts
38
38
  import { OpenhexClient } from '@openhex-ai/agent-sdk';
39
39
 
40
- const client = new OpenhexClient({ agentId: '…', /* apiKey from env */ });
40
+ const client = new OpenhexClient({ agentId: '…' /* apiKey from env */ });
41
41
 
42
42
  // Simplest: send and await the aggregated turn (creates a conversation).
43
43
  const turn = await client.sendMessage('Summarize the latest support tickets');
44
44
  console.log(turn.text);
45
- console.log(turn.toolCalls); // tools the agent used
45
+ console.log(turn.toolCalls); // tools the agent used
46
46
  console.log(turn.conversationId); // reuse to continue the chat
47
47
  ```
48
48
 
@@ -85,7 +85,7 @@ for await (const record of client.chat.stream(conversationId, { lastEventId: use
85
85
  if (record.raw.type === 'result') break;
86
86
  }
87
87
 
88
- await client.chat.interrupt(conversationId); // POST /conversations/:id/interrupt
88
+ await client.chat.interrupt(conversationId); // POST /conversations/:id/interrupt
89
89
  const { entries } = await client.chat.messages(conversationId); // full history
90
90
  ```
91
91
 
@@ -93,6 +93,97 @@ Each stream record is `{ id, seq, sender, event, timestamp, sessionId, raw }`
93
93
  where `raw` carries the agent-runtime event (`{ type: 'assistant', message: { content: […] } }`,
94
94
  `{ type: 'result' }`, etc.) — identical to what the app's chat UI consumes.
95
95
 
96
+ ## React chat UI (`/react`)
97
+
98
+ Drop a chat box onto any page and it talks to your agent over the same
99
+ protocol. The React layer is a **separate entry point** so the core SDK stays
100
+ framework-free; `react` / `react-dom` are peer dependencies and the styles are
101
+ self-contained (no CSS import, no build step).
102
+
103
+ ```bash
104
+ npm install @openhex-ai/agent-sdk react react-dom
105
+ ```
106
+
107
+ ### Turnkey floating widget
108
+
109
+ ```tsx
110
+ import { ChatWidget } from '@openhex-ai/agent-sdk/react';
111
+
112
+ export function App() {
113
+ return (
114
+ <ChatWidget
115
+ agentId="ce9382ad-…"
116
+ token={sessionToken} // a member session token from YOUR backend
117
+ title="HYROX 备赛教练"
118
+ greeting="嗨!我是你的 HYROX 备赛教练,问我任何训练问题吧。"
119
+ accentColor="#0f766e"
120
+ theme="auto" // light | dark | auto
121
+ />
122
+ );
123
+ }
124
+ ```
125
+
126
+ That renders a launcher bubble (bottom-right) that opens a panel — a corner
127
+ panel on desktop, full-screen on mobile. Use `<ChatBox>` instead to embed the
128
+ panel inline in your own layout (it fills its parent).
129
+
130
+ ### Browser-safe auth — never ship an API key
131
+
132
+ A `mysta_…` API key is a **secret**; it must never reach the browser. Mint a
133
+ short-lived **member session token** on your backend and hand it to the widget:
134
+
135
+ ```ts
136
+ // —— your backend (Node) ——
137
+ import { OpenhexClient } from '@openhex-ai/agent-sdk';
138
+ const openhex = new OpenhexClient({ apiKey: process.env.OPENHEX_API_KEY }); // sk_ws_… or owner JWT
139
+ app.post('/chat-token', async (req, res) => {
140
+ const { token } = await openhex.workspace('acme').mintSession({
141
+ sp_user_ref: req.user.id,
142
+ ttl_seconds: 3600,
143
+ });
144
+ res.json({ token });
145
+ });
146
+ ```
147
+
148
+ ```tsx
149
+ // —— your frontend ——
150
+ <ChatWidget
151
+ agentId="…"
152
+ getToken={async () => (await fetch('/chat-token', { method: 'POST' })).json().then(r => r.token)}
153
+ />
154
+ ```
155
+
156
+ `getToken` is called before every request, so an expiring token refreshes
157
+ transparently. Pass a static `token` if you mint one per page load, or a fully
158
+ built `client` (an `OpenhexClient`) for total control.
159
+
160
+ ### Headless hook — bring your own UI
161
+
162
+ `useOpenhexChat` owns the conversation state (messages, streaming, interrupt,
163
+ retry) so you can render whatever you like:
164
+
165
+ ```tsx
166
+ import { useOpenhexChat } from '@openhex-ai/agent-sdk/react';
167
+
168
+ function MyChat() {
169
+ const { messages, send, isResponding, interrupt } = useOpenhexChat({
170
+ agentId: '…',
171
+ getToken,
172
+ });
173
+ // …render `messages`, call `send(text)` / `interrupt()`
174
+ }
175
+ ```
176
+
177
+ | Prop | Purpose |
178
+ | ----------------------------------------------- | ---------------------------------------------------------- |
179
+ | `agentId` | agent to route a new conversation to |
180
+ | `conversationId` | resume an existing conversation (loads history) |
181
+ | `token` / `getToken` | member session token (static or refreshing) |
182
+ | `client` | a pre-built `OpenhexClient` / `AgentChatClient` (advanced) |
183
+ | `theme` / `accentColor` | one-prop theming (`light` \| `dark` \| `auto`) |
184
+ | `greeting`, `title`, `avatarUrl`, `placeholder` | presentation |
185
+ | `injectStyles={false}` | opt out of the bundled CSS and style it yourself |
186
+
96
187
  ## Workspaces
97
188
 
98
189
  A **workspace** is a billing + membership boundary you (a service provider /
@@ -103,11 +194,11 @@ reporting. It speaks the same contract documented at
103
194
 
104
195
  Auth is the `Bearer` token the client is configured with:
105
196
 
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`) |
197
+ | Token | Unlocks |
198
+ | ----------------------------- | ----------------------------------------------------------------------------------------- |
199
+ | Workspace API key (`sk_ws_…`) | `whoami`, member provision/suspend, sessions, member list, credit grants |
200
+ | Owner user JWT | the above **plus** reporting (`ledger`, `insights`, member ledger) and API-key management |
201
+ | none | the public SMS-signup endpoints (`sendSmsCode` / `verifySmsCode`) |
111
202
 
112
203
  ```ts
113
204
  import { OpenhexClient } from '@openhex-ai/agent-sdk';
@@ -124,7 +215,10 @@ const ws = client.workspace(me.slug);
124
215
  // Provision a member (idempotent on your sp_user_ref), grant credits,
125
216
  // then mint a session JWT for the member's client.
126
217
  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}` });
218
+ await ws.grantCredits(member.member_id, {
219
+ amount: 500,
220
+ idempotency_key: `bonus:${member.member_id}`,
221
+ });
128
222
  const session = await ws.mintSession({ sp_user_ref: 'user-42', ttl_seconds: 3600 });
129
223
  ```
130
224
 
@@ -133,7 +227,10 @@ Public SMS signup (no auth — requires `public_sms_signup_enabled` on the works
133
227
  ```ts
134
228
  const pub = new OpenhexClient({ apiKey: 'unused' }); // token ignored on these routes
135
229
  await pub.workspaces.sendSmsCode('acme', { phone: '13800000000' });
136
- const session = await pub.workspaces.verifySmsCode('acme', { phone: '13800000000', code: '123456' });
230
+ const session = await pub.workspaces.verifySmsCode('acme', {
231
+ phone: '13800000000',
232
+ code: '123456',
233
+ });
137
234
  ```
138
235
 
139
236
  > The admin-side routes (workspace ledger, insights, per-member ledger,
@@ -179,17 +276,18 @@ const q = query({
179
276
 
180
277
  ## Concepts
181
278
 
182
- | Concept | Where |
183
- | --------------------------- | -------------------------------------------------------- |
279
+ | Concept | Where |
280
+ | --------------------------- | ----------------------------------------------------------- |
184
281
  | 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` |
282
+ | Embeddable chat UI | `<ChatWidget>`, `<ChatBox>`, `useOpenhexChat()` (`/react`) |
283
+ | Resumable record stream | `client.chat.stream({ lastEventId })` |
284
+ | One-shot / streaming runs | `query()` (scaffold) |
285
+ | Stateful client + sessions | `OpenhexClient` |
286
+ | Built-in & custom tools | `options.allowedTools`, `tool()`, `createSdkMcpServer()` |
287
+ | Lifecycle hooks | `options.hooks`, `hookMatcher()` |
288
+ | Subagents | `options.agents` |
289
+ | External integrations (MCP) | `options.mcpServers` |
290
+ | Permissions | `options.permissionMode`, `options.canUseTool` |
193
291
 
194
292
  The surface intentionally mirrors the [Claude Agent SDK](https://code.claude.com/docs/en/agent-sdk/overview)
195
293
  so the mental model transfers directly.