@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.
@@ -0,0 +1,277 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+ import { O as OpenhexClient, c as AgentChatClient, C as ChatStreamRecord } from '../client-1lMkhPMb.js';
4
+ import '../tools-DWFaPtFE.js';
5
+ import 'zod';
6
+
7
+ /**
8
+ * Public types for the React chat layer (`@openhex-ai/agent-sdk/react`).
9
+ *
10
+ * These describe the props of {@link useOpenhexChat}, {@link ChatBox} and
11
+ * {@link ChatWidget}. They deliberately stay framework-thin: a message is a
12
+ * flat, render-ready shape, and the connection options map straight onto the
13
+ * underlying {@link AgentChatClient}.
14
+ */
15
+
16
+ /** Role of a rendered chat bubble. */
17
+ type ChatRole = 'user' | 'assistant' | 'system';
18
+ /** A tool call the agent made during a turn (surfaced for optional display). */
19
+ interface ChatToolCall {
20
+ id?: string;
21
+ name: string;
22
+ input: Record<string, unknown>;
23
+ }
24
+ /**
25
+ * A single, render-ready chat message. This is the flattened view the hook
26
+ * maintains — the raw stream records are aggregated into `text` so the UI
27
+ * never has to reach into `record.raw`.
28
+ */
29
+ interface ChatMessage {
30
+ /** Stable client-side id (used as the React key). */
31
+ id: string;
32
+ role: ChatRole;
33
+ /** Aggregated text of the message so far. */
34
+ text: string;
35
+ /** Epoch millis when the bubble was created. */
36
+ createdAt: number;
37
+ /** True while the assistant reply is still streaming in. */
38
+ streaming?: boolean;
39
+ /** True for an assistant bubble that has been sent but has no text yet. */
40
+ pending?: boolean;
41
+ /** True when the turn failed (network / server / idle timeout). */
42
+ error?: boolean;
43
+ /** Tool calls observed during the turn, if any. */
44
+ toolCalls?: ChatToolCall[];
45
+ }
46
+ /** Connection status of the underlying stream. */
47
+ type ChatStatus = 'idle' | 'connecting' | 'streaming' | 'error';
48
+ /**
49
+ * How the hook reaches the platform. Provide EITHER a ready-made `client`
50
+ * (full control, e.g. an `OpenhexClient` from your own backend proxy), OR
51
+ * inline connection fields.
52
+ *
53
+ * Browser-safe auth: never ship a `mysta_…` API key to the browser. Mint a
54
+ * short-lived member **session token** on your backend
55
+ * (`client.workspaces.mintSession(...)`) and hand it to the widget via
56
+ * `token` (static) or `getToken` (refreshable — called before every request,
57
+ * so it can silently rotate an expiring token).
58
+ */
59
+ interface OpenhexChatConnection {
60
+ /**
61
+ * A pre-built client. When set, all other connection fields are ignored.
62
+ * Accepts an {@link OpenhexClient} or a bare {@link AgentChatClient}.
63
+ */
64
+ client?: OpenhexClient | AgentChatClient;
65
+ /** Platform API base URL. Defaults to the SDK's production default. */
66
+ baseUrl?: string;
67
+ /** Static bearer token (member session JWT). Use `getToken` to refresh. */
68
+ token?: string;
69
+ /**
70
+ * Returns a fresh bearer token before each request. Preferred in the
71
+ * browser: lets you refresh an expiring session without re-mounting.
72
+ */
73
+ getToken?: () => string | Promise<string>;
74
+ /** `X-Login-Type` header (matches the web/app clients). */
75
+ loginType?: string;
76
+ /** Service-account id to impersonate via `X-Act-As` (caller must own it). */
77
+ actAs?: string;
78
+ /** Override the global fetch (testing / custom runtimes). */
79
+ fetch?: typeof fetch;
80
+ }
81
+ /** Options for {@link useOpenhexChat}. */
82
+ interface UseOpenhexChatOptions extends OpenhexChatConnection {
83
+ /**
84
+ * Agent to route NEW conversations to. Required unless `conversationId`
85
+ * (resume) is given or the supplied `client` already has a default agent.
86
+ */
87
+ agentId?: string;
88
+ /** Resume an existing conversation instead of creating one. */
89
+ conversationId?: string;
90
+ /** Display name to label the user in the conversation log. */
91
+ senderName?: string;
92
+ /** Avatar URL stored alongside the user's messages. */
93
+ senderAvatar?: string;
94
+ /**
95
+ * Load prior history when `conversationId` is provided (default true).
96
+ * Ignored for brand-new conversations.
97
+ */
98
+ loadHistory?: boolean;
99
+ /** Abort a turn if no event arrives for this long (ms). Default 180_000. */
100
+ idleTimeoutMs?: number;
101
+ /** Called when a turn ends (success). Receives the finished assistant message. */
102
+ onTurnComplete?: (message: ChatMessage) => void;
103
+ /** Called on any turn error (network / server / idle timeout). */
104
+ onError?: (error: Error) => void;
105
+ }
106
+ /** Return value of {@link useOpenhexChat}. */
107
+ interface UseOpenhexChatResult {
108
+ /** The conversation so far, oldest first. */
109
+ messages: ChatMessage[];
110
+ /** Coarse connection status. */
111
+ status: ChatStatus;
112
+ /** The last error, cleared on the next successful send. */
113
+ error: Error | null;
114
+ /** The conversation id, once the first message has been sent. */
115
+ conversationId: string | undefined;
116
+ /** True while an agent turn is in flight. */
117
+ isResponding: boolean;
118
+ /** Send a user message and stream the reply into `messages`. */
119
+ send: (text: string) => Promise<void>;
120
+ /** Interrupt the in-flight turn (server-side + stops streaming). */
121
+ interrupt: () => void;
122
+ /** Resend the last user message after an error. */
123
+ retry: () => void;
124
+ /** Clear all messages and forget the conversation id. */
125
+ clear: () => void;
126
+ }
127
+ /** Color theme for the chat components. `auto` follows `prefers-color-scheme`. */
128
+ type ChatTheme = 'light' | 'dark' | 'auto';
129
+ /** Presentation props shared by {@link ChatBox} and {@link ChatWidget}. */
130
+ interface ChatBoxProps extends UseOpenhexChatOptions {
131
+ /** Header title (agent name). Omit the header entirely with `header={false}`. */
132
+ title?: string;
133
+ /** Header subtitle / status line. */
134
+ subtitle?: string;
135
+ /** Composer placeholder text. */
136
+ placeholder?: string;
137
+ /** First assistant bubble shown before any turn (display-only, not sent). */
138
+ greeting?: string;
139
+ /** Avatar URL shown in the header and next to the agent. */
140
+ avatarUrl?: string;
141
+ /** Color theme. Default `light`. */
142
+ theme?: ChatTheme;
143
+ /** Accent color (buttons, user bubbles, links). Any CSS color. */
144
+ accentColor?: string;
145
+ /** Explicit height (number → px). Defaults to filling the parent. */
146
+ height?: number | string;
147
+ /** Explicit width (number → px). */
148
+ width?: number | string;
149
+ /** Extra class on the root element. */
150
+ className?: string;
151
+ /** Replace the header node, or hide it with `false`. */
152
+ header?: ReactNode | false;
153
+ /** Node shown when there are no messages (overrides `greeting`). */
154
+ emptyState?: ReactNode;
155
+ /** Small print under the composer. Set `false` to hide. */
156
+ footer?: ReactNode | false;
157
+ /** Disable input (read-only view). */
158
+ disabled?: boolean;
159
+ /** Inject the bundled stylesheet (default true). Set false to self-style. */
160
+ injectStyles?: boolean;
161
+ /** Show the tool calls the agent made under each reply (default false). */
162
+ showToolCalls?: boolean;
163
+ }
164
+ /** Props for the floating {@link ChatWidget}. */
165
+ interface ChatWidgetProps extends ChatBoxProps {
166
+ /** Corner to anchor the launcher + panel. Default `bottom-right`. */
167
+ position?: 'bottom-right' | 'bottom-left';
168
+ /** Replace the launcher button's icon. */
169
+ launcherIcon?: ReactNode;
170
+ /** Accessible label for the launcher button. Default "Open chat". */
171
+ launcherLabel?: string;
172
+ /** Start open (uncontrolled). Default false. */
173
+ defaultOpen?: boolean;
174
+ /** Controlled open state. Pair with {@link onOpenChange}. */
175
+ open?: boolean;
176
+ /** Called when the open state should change (controlled or uncontrolled). */
177
+ onOpenChange?: (open: boolean) => void;
178
+ }
179
+
180
+ declare function ChatWidget(props: ChatWidgetProps): react_jsx_runtime.JSX.Element;
181
+
182
+ declare function ChatBox(props: ChatBoxProps): react_jsx_runtime.JSX.Element;
183
+
184
+ declare function useOpenhexChat(options: UseOpenhexChatOptions): UseOpenhexChatResult;
185
+
186
+ /** Render a message body as a lightweight, safe Markdown subset. */
187
+ declare function Markdown({ text }: {
188
+ text: string;
189
+ }): react_jsx_runtime.JSX.Element;
190
+
191
+ /** Fold records (oldest first) into chat bubbles, dropping empty ones. */
192
+ declare function foldRecords(records: ChatStreamRecord[], idPrefix?: string): ChatMessage[];
193
+
194
+ /**
195
+ * Browser-safe client resolution for the React chat layer.
196
+ *
197
+ * Turns the loose {@link OpenhexChatConnection} props into a concrete
198
+ * {@link AgentChatClient}. The important bit is `getToken`: rather than
199
+ * baking a token into the client at construction (the core `HttpClient`
200
+ * reads `apiKey` once), we wrap `fetch` so a fresh token is injected on the
201
+ * `Authorization` header of every request. That lets a browser widget rotate
202
+ * an expiring member-session JWT without re-mounting.
203
+ */
204
+
205
+ /**
206
+ * Resolve the {@link OpenhexChatConnection} props into an
207
+ * {@link AgentChatClient}. Throws if neither a client nor any credential is
208
+ * provided.
209
+ */
210
+ declare function resolveChatClient(conn: OpenhexChatConnection): AgentChatClient;
211
+
212
+ /**
213
+ * A tiny, dependency-free, XSS-safe Markdown subset for chat bubbles.
214
+ *
215
+ * We deliberately do NOT pull in `react-markdown` & friends (the internal
216
+ * webapp does) — an embeddable widget must stay light and safe. This parses a
217
+ * useful subset into a plain data tree; the renderer maps it to React
218
+ * elements, so every piece of text goes through React's escaping and no HTML
219
+ * is ever injected. Link hrefs are sanitized to http/https/mailto.
220
+ *
221
+ * Supported: fenced code blocks, paragraphs, `inline code`, **bold**,
222
+ * *italic* / _italic_, [links](url), and bare URL autolinking.
223
+ */
224
+ /** An inline span inside a paragraph. */
225
+ type Span = {
226
+ type: 'text';
227
+ text: string;
228
+ } | {
229
+ type: 'code';
230
+ text: string;
231
+ } | {
232
+ type: 'bold';
233
+ text: string;
234
+ } | {
235
+ type: 'italic';
236
+ text: string;
237
+ } | {
238
+ type: 'link';
239
+ text: string;
240
+ href: string;
241
+ };
242
+ /** A block-level node. */
243
+ type Block = {
244
+ type: 'code';
245
+ text: string;
246
+ lang?: string;
247
+ } | {
248
+ type: 'heading';
249
+ level: number;
250
+ spans: Span[];
251
+ } | {
252
+ type: 'paragraph';
253
+ spans: Span[];
254
+ } | {
255
+ type: 'list';
256
+ ordered: boolean;
257
+ start: number;
258
+ items: Span[][];
259
+ } | {
260
+ type: 'blockquote';
261
+ spans: Span[];
262
+ } | {
263
+ type: 'hr';
264
+ };
265
+ /** Only allow safe URL schemes; everything else becomes plain text. */
266
+ declare function sanitizeHref(href: string): string | null;
267
+ /** Parse a single line/segment of text into inline spans. */
268
+ declare function parseInline(input: string): Span[];
269
+ /** Parse a whole message into block-level nodes. */
270
+ declare function parseMarkdown(input: string): Block[];
271
+
272
+ declare const STYLE_ELEMENT_ID = "ohx-chat-styles";
273
+ declare const CHAT_CSS = "\n.ohx-root {\n --ohx-accent: #4f46e5;\n --ohx-accent-contrast: #ffffff;\n --ohx-bg: #ffffff;\n --ohx-surface: #f7f7f8;\n --ohx-fg: #1f2328;\n --ohx-muted: #6b7280;\n --ohx-border: #e5e7eb;\n --ohx-user-bg: var(--ohx-accent);\n --ohx-user-fg: var(--ohx-accent-contrast);\n --ohx-assistant-bg: #f1f2f4;\n --ohx-assistant-fg: #1f2328;\n --ohx-radius: 16px;\n --ohx-font: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"PingFang SC\", \"Microsoft YaHei\", sans-serif;\n display: flex;\n flex-direction: column;\n box-sizing: border-box;\n background: var(--ohx-bg);\n color: var(--ohx-fg);\n font-family: var(--ohx-font);\n font-size: 14px;\n line-height: 1.5;\n border: 1px solid var(--ohx-border);\n border-radius: var(--ohx-radius);\n overflow: hidden;\n min-height: 0;\n}\n.ohx-root[data-theme=\"dark\"] {\n --ohx-bg: #1b1c1f;\n --ohx-surface: #242629;\n --ohx-fg: #e8eaed;\n --ohx-muted: #9aa0a6;\n --ohx-border: #34363b;\n --ohx-assistant-bg: #2a2c30;\n --ohx-assistant-fg: #e8eaed;\n}\n.ohx-root *, .ohx-root *::before, .ohx-root *::after { box-sizing: border-box; }\n\n.ohx-header {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 12px 14px;\n border-bottom: 1px solid var(--ohx-border);\n background: var(--ohx-bg);\n flex: none;\n}\n.ohx-avatar {\n width: 34px; height: 34px; border-radius: 50%;\n object-fit: cover; flex: none;\n background: var(--ohx-surface);\n}\n.ohx-header-text { display: flex; flex-direction: column; min-width: 0; flex: 1; }\n.ohx-title { font-weight: 600; font-size: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n.ohx-subtitle { font-size: 12px; color: var(--ohx-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n.ohx-icon-btn {\n display: inline-flex; align-items: center; justify-content: center;\n width: 30px; height: 30px; border: none; border-radius: 8px;\n background: transparent; color: var(--ohx-muted); cursor: pointer;\n}\n.ohx-icon-btn:hover { background: var(--ohx-surface); color: var(--ohx-fg); }\n\n.ohx-messages {\n flex: 1 1 auto;\n min-height: 0;\n overflow-y: auto;\n padding: 16px;\n display: flex;\n flex-direction: column;\n gap: 10px;\n background: var(--ohx-bg);\n scroll-behavior: smooth;\n}\n.ohx-empty {\n margin: auto; text-align: center; color: var(--ohx-muted);\n font-size: 13px; padding: 24px; max-width: 80%;\n}\n\n.ohx-row { display: flex; width: 100%; }\n.ohx-row.user { justify-content: flex-end; }\n.ohx-row.assistant, .ohx-row.system { justify-content: flex-start; }\n.ohx-bubble {\n max-width: 82%;\n padding: 9px 13px;\n border-radius: 14px;\n word-break: break-word;\n overflow-wrap: anywhere;\n}\n.ohx-row.user .ohx-bubble {\n background: var(--ohx-user-bg); color: var(--ohx-user-fg);\n border-bottom-right-radius: 4px;\n}\n.ohx-row.assistant .ohx-bubble {\n background: var(--ohx-assistant-bg); color: var(--ohx-assistant-fg);\n border-bottom-left-radius: 4px;\n}\n.ohx-bubble.error { border: 1px solid #ef4444; }\n.ohx-bubble .ohx-p { margin: 0 0 6px; }\n.ohx-bubble .ohx-p:last-child { margin-bottom: 0; }\n.ohx-bubble a { color: inherit; text-decoration: underline; }\n.ohx-row.assistant .ohx-bubble a { color: var(--ohx-accent); }\n.ohx-bubble > :first-child { margin-top: 0; }\n.ohx-bubble > :last-child { margin-bottom: 0; }\n.ohx-bubble .ohx-h { font-weight: 600; line-height: 1.3; margin: 12px 0 6px; }\n.ohx-bubble .ohx-h1 { font-size: 1.3em; }\n.ohx-bubble .ohx-h2 { font-size: 1.18em; }\n.ohx-bubble .ohx-h3 { font-size: 1.08em; }\n.ohx-bubble .ohx-h4, .ohx-bubble .ohx-h5, .ohx-bubble .ohx-h6 { font-size: 1em; }\n.ohx-bubble .ohx-ul, .ohx-bubble .ohx-ol { margin: 6px 0; padding-left: 1.35em; }\n.ohx-bubble .ohx-ul { list-style: disc; }\n.ohx-bubble .ohx-ol { list-style: decimal; }\n.ohx-bubble .ohx-ul li, .ohx-bubble .ohx-ol li { margin: 3px 0; }\n.ohx-bubble .ohx-ul li::marker, .ohx-bubble .ohx-ol li::marker { color: var(--ohx-muted); }\n.ohx-bubble .ohx-hr { border: none; border-top: 1px solid var(--ohx-border); margin: 12px 0; }\n.ohx-bubble .ohx-quote {\n margin: 6px 0; padding: 2px 0 2px 12px;\n border-left: 3px solid var(--ohx-border); color: var(--ohx-muted);\n}\n.ohx-code {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 0.9em; background: rgba(127,127,127,0.16);\n padding: 1px 5px; border-radius: 5px;\n}\n.ohx-pre {\n margin: 6px 0; padding: 10px 12px; border-radius: 10px;\n background: rgba(127,127,127,0.14); overflow-x: auto;\n}\n.ohx-pre code {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 0.86em; white-space: pre; background: none; padding: 0;\n}\n.ohx-tools { margin-top: 6px; display: flex; flex-wrap: wrap; gap: 4px; }\n.ohx-tool {\n font-size: 11px; color: var(--ohx-muted);\n background: rgba(127,127,127,0.12); border-radius: 6px; padding: 2px 7px;\n font-family: ui-monospace, monospace;\n}\n\n.ohx-typing { display: inline-flex; gap: 4px; padding: 3px 2px; align-items: center; }\n.ohx-typing span {\n width: 6px; height: 6px; border-radius: 50%;\n background: var(--ohx-muted); opacity: 0.5;\n animation: ohx-blink 1.2s infinite ease-in-out both;\n}\n.ohx-typing span:nth-child(2) { animation-delay: 0.18s; }\n.ohx-typing span:nth-child(3) { animation-delay: 0.36s; }\n@keyframes ohx-blink { 0%, 80%, 100% { transform: scale(0.7); opacity: 0.3; } 40% { transform: scale(1); opacity: 0.9; } }\n\n.ohx-error-bar {\n display: flex; align-items: center; justify-content: space-between; gap: 8px;\n margin: 0 16px 8px; padding: 8px 12px; font-size: 12px;\n color: #b91c1c; background: rgba(239,68,68,0.1);\n border: 1px solid rgba(239,68,68,0.3); border-radius: 8px;\n}\n.ohx-root[data-theme=\"dark\"] .ohx-error-bar { color: #fca5a5; }\n.ohx-retry {\n border: none; background: transparent; color: inherit; cursor: pointer;\n text-decoration: underline; font-weight: 600; font-size: 12px; white-space: nowrap;\n}\n\n.ohx-composer {\n display: flex; align-items: flex-end; gap: 8px;\n padding: 10px 12px; border-top: 1px solid var(--ohx-border);\n background: var(--ohx-bg); flex: none;\n}\n.ohx-textarea {\n flex: 1; resize: none; border: 1px solid var(--ohx-border);\n border-radius: 12px; padding: 9px 12px; max-height: 140px;\n font-family: inherit; font-size: 14px; line-height: 1.4;\n color: var(--ohx-fg); background: var(--ohx-surface); outline: none;\n}\n.ohx-textarea:focus { border-color: var(--ohx-accent); }\n.ohx-textarea::placeholder { color: var(--ohx-muted); }\n.ohx-send {\n display: inline-flex; align-items: center; justify-content: center;\n width: 38px; height: 38px; flex: none; border: none; border-radius: 12px;\n background: var(--ohx-accent); color: var(--ohx-accent-contrast); cursor: pointer;\n transition: opacity 0.15s;\n}\n.ohx-send:disabled { opacity: 0.45; cursor: not-allowed; }\n.ohx-send.stop { background: var(--ohx-surface); color: var(--ohx-fg); border: 1px solid var(--ohx-border); }\n.ohx-footer {\n text-align: center; font-size: 10px; color: var(--ohx-muted);\n padding: 0 0 8px; background: var(--ohx-bg); flex: none;\n}\n.ohx-footer a { color: var(--ohx-muted); }\n\n/* ---- Floating widget ---- */\n.ohx-launcher {\n position: fixed; z-index: 2147483000;\n width: 56px; height: 56px; border-radius: 50%; border: none;\n background: var(--ohx-accent, #4f46e5); color: #fff; cursor: pointer;\n box-shadow: 0 6px 24px rgba(0,0,0,0.22);\n display: inline-flex; align-items: center; justify-content: center;\n transition: transform 0.15s;\n}\n.ohx-launcher:hover { transform: scale(1.06); }\n.ohx-launcher.bottom-right { right: 20px; bottom: 20px; }\n.ohx-launcher.bottom-left { left: 20px; bottom: 20px; }\n.ohx-panel {\n position: fixed; z-index: 2147483000;\n width: 380px; height: min(620px, calc(100vh - 110px));\n box-shadow: 0 12px 40px rgba(0,0,0,0.24);\n border-radius: 18px;\n overflow: hidden;\n animation: ohx-pop 0.16s ease-out;\n}\n.ohx-panel.bottom-right { right: 20px; bottom: 88px; }\n.ohx-panel.bottom-left { left: 20px; bottom: 88px; }\n/* Inside a panel the wrapper owns the shape + shadow, so the ChatBox fills\n it squared (no double border / radius). */\n.ohx-root.ohx-panel-inner { border: none; border-radius: 0; height: 100%; width: 100%; }\n@keyframes ohx-pop { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }\n\n@media (max-width: 480px) {\n .ohx-panel {\n right: 0; left: 0; bottom: 0; top: 0;\n width: 100vw; height: 100vh; height: 100dvh;\n border-radius: 0;\n }\n .ohx-launcher.open-hidden { display: none; }\n}\n";
274
+ /** Inject the stylesheet once (idempotent). Pass `enabled=false` to skip. */
275
+ declare function useInjectStyles(enabled: boolean): void;
276
+
277
+ export { type Block, CHAT_CSS, ChatBox, type ChatBoxProps, type ChatMessage, type ChatRole, type ChatStatus, type ChatTheme, type ChatToolCall, ChatWidget, type ChatWidgetProps, Markdown, type OpenhexChatConnection, STYLE_ELEMENT_ID, type Span, type UseOpenhexChatOptions, type UseOpenhexChatResult, foldRecords, parseInline, parseMarkdown, resolveChatClient, sanitizeHref, useInjectStyles, useOpenhexChat };