sideagent 0.2.0 → 0.4.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/dist/index.js CHANGED
@@ -1,41 +1,65 @@
1
- // src/client/AgentChat.tsx
2
1
  import {
3
- AssistantRuntimeProvider,
4
- ComposerPrimitive,
5
- MessagePrimitive,
6
- ThreadPrimitive,
7
- useLocalRuntime
8
- } from "@assistant-ui/react";
2
+ PUBLISHABLE_KEY_HEADER,
3
+ parsePublishableKey,
4
+ publishableEndpoint
5
+ } from "./chunk-73FXXOAR.js";
6
+
7
+ // src/client/AgentChat.tsx
9
8
  import {
10
- CheckCircledIcon,
11
9
  ChatBubbleIcon,
12
- ChevronRightIcon,
13
- ArrowLeftIcon,
14
- ArrowUpIcon,
15
10
  CounterClockwiseClockIcon,
16
11
  Cross2Icon,
17
- CrossCircledIcon,
18
- ExternalLinkIcon,
19
- FileTextIcon,
20
- ExclamationTriangleIcon,
21
12
  OpenInNewWindowIcon,
22
13
  PlusIcon,
23
- ReloadIcon,
24
- TrashIcon,
25
14
  ViewVerticalIcon
26
15
  } from "@radix-ui/react-icons";
27
- import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
28
- import Markdown from "react-markdown";
29
- import remarkGfm from "remark-gfm";
16
+ import {
17
+ useCallback,
18
+ useContext as useContext3,
19
+ useEffect as useEffect2,
20
+ useMemo as useMemo2,
21
+ useRef as useRef2,
22
+ useState as useState3
23
+ } from "react";
30
24
 
31
- // src/client/hooks.ts
32
- function safely(fn) {
25
+ // src/client/Chat.tsx
26
+ import {
27
+ AssistantRuntimeProvider,
28
+ ComposerPrimitive,
29
+ ThreadPrimitive,
30
+ useLocalRuntime
31
+ } from "@assistant-ui/react";
32
+ import { ArrowUpIcon } from "@radix-ui/react-icons";
33
+ import { useMemo, useRef, useState } from "react";
34
+
35
+ // src/client/ApprovalCard.tsx
36
+ import { ChevronRightIcon, ExclamationTriangleIcon } from "@radix-ui/react-icons";
37
+
38
+ // src/client/format.ts
39
+ var pretty = (v) => {
33
40
  try {
34
- fn?.();
35
- } catch (err) {
36
- console.error("[sideagent] hook threw:", err);
41
+ return JSON.stringify(v, null, 2);
42
+ } catch {
43
+ return String(v);
37
44
  }
38
- }
45
+ };
46
+ var ago = (t) => {
47
+ const s = Math.round((t - Date.now()) / 1e3);
48
+ const rtf = new Intl.RelativeTimeFormat(void 0, { numeric: "auto" });
49
+ const a = Math.abs(s);
50
+ if (a < 60) return rtf.format(s, "second");
51
+ if (a < 3600) return rtf.format(Math.round(s / 60), "minute");
52
+ if (a < 86400) return rtf.format(Math.round(s / 3600), "hour");
53
+ return rtf.format(Math.round(s / 86400), "day");
54
+ };
55
+ var httpUrl = (u) => {
56
+ try {
57
+ const url = new URL(String(u));
58
+ return url.protocol === "http:" || url.protocol === "https:" ? url.toString() : null;
59
+ } catch {
60
+ return null;
61
+ }
62
+ };
39
63
 
40
64
  // src/client/results.tsx
41
65
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
@@ -113,364 +137,262 @@ function ResultView({ result }) {
113
137
  return /* @__PURE__ */ jsx("div", { children: formatCell(result) });
114
138
  }
115
139
 
116
- // src/client/styles.ts
117
- var CSS = `
118
- .sa-root{
119
- --sa-bg:var(--sideagent-bg,#ffffff);
120
- --sa-fg:var(--sideagent-fg,#171717);
121
- --sa-muted:var(--sideagent-muted,#6b6b6b);
122
- --sa-border:var(--sideagent-border,#e5e5e5);
123
- --sa-surface:var(--sideagent-surface,#f5f5f5);
124
- --sa-accent:var(--sideagent-accent,#171717);
125
- --sa-accent-fg:var(--sideagent-accent-fg,#ffffff);
126
- --sa-danger:var(--sideagent-danger,#b42318);
127
- --sa-radius:var(--sideagent-radius,10px);
128
- --sa-font:var(--sideagent-font,inherit);
129
- color-scheme:light;
140
+ // src/client/ApprovalCard.tsx
141
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
142
+ function summaryFields(args) {
143
+ const a = args && typeof args === "object" ? args : {};
144
+ const body = a.body && typeof a.body === "object" && !Array.isArray(a.body) ? a.body : {};
145
+ const { body: _body, ...rest } = a;
146
+ return Object.entries({ ...rest, ...body }).filter(([, v]) => ["string", "number", "boolean"].includes(typeof v)).map(([k, v]) => [humanize(k), String(v)]);
130
147
  }
131
- @media (prefers-color-scheme:dark){
132
- .sa-root[data-theme=auto]{
133
- --sa-bg:var(--sideagent-bg,#171717);--sa-fg:var(--sideagent-fg,#f5f5f5);--sa-muted:var(--sideagent-muted,#a3a3a3);
134
- --sa-border:var(--sideagent-border,#2e2e2e);--sa-surface:var(--sideagent-surface,#262626);
135
- --sa-accent:var(--sideagent-accent,#f5f5f5);--sa-accent-fg:var(--sideagent-accent-fg,#171717);color-scheme:dark}
148
+ function ApprovalCard({ event, onDecide }) {
149
+ const action = humanize(event.operationId).toLowerCase();
150
+ const fields = summaryFields(event.args);
151
+ return /* @__PURE__ */ jsxs2("div", { className: "sa-approval", role: "alertdialog", "aria-label": "Approval needed", children: [
152
+ /* @__PURE__ */ jsxs2("strong", { className: "sa-approval-title", children: [
153
+ /* @__PURE__ */ jsx2(ExclamationTriangleIcon, {}),
154
+ " Confirm before I continue"
155
+ ] }),
156
+ /* @__PURE__ */ jsxs2("p", { className: "sa-approval-text", children: [
157
+ "I'd like to ",
158
+ /* @__PURE__ */ jsx2("strong", { children: action }),
159
+ ". Nothing changes until you approve."
160
+ ] }),
161
+ fields.length > 0 && /* @__PURE__ */ jsx2("dl", { className: "sa-fields", children: fields.map(([k, v]) => /* @__PURE__ */ jsxs2("div", { className: "sa-field", children: [
162
+ /* @__PURE__ */ jsx2("dt", { children: k }),
163
+ /* @__PURE__ */ jsx2("dd", { children: v })
164
+ ] }, k)) }),
165
+ /* @__PURE__ */ jsxs2("details", { className: "sa-details", children: [
166
+ /* @__PURE__ */ jsxs2("summary", { children: [
167
+ /* @__PURE__ */ jsx2(ChevronRightIcon, {}),
168
+ " Details"
169
+ ] }),
170
+ /* @__PURE__ */ jsxs2("pre", { children: [
171
+ event.method,
172
+ " ",
173
+ event.path,
174
+ "\n",
175
+ pretty(event.args)
176
+ ] })
177
+ ] }),
178
+ /* @__PURE__ */ jsxs2("div", { className: "sa-row", children: [
179
+ /* @__PURE__ */ jsx2("button", { className: "sa-btn sa-btn-primary", onClick: () => onDecide("approve"), children: "Approve" }),
180
+ /* @__PURE__ */ jsx2("button", { className: "sa-btn", onClick: () => onDecide("reject"), children: "Reject" })
181
+ ] })
182
+ ] });
136
183
  }
137
- :is(.dark,[data-theme=dark],[data-bs-theme=dark]) .sa-root[data-theme=auto],
138
- .sa-root[data-theme=dark]{
139
- --sa-bg:var(--sideagent-bg,#171717);--sa-fg:var(--sideagent-fg,#f5f5f5);--sa-muted:var(--sideagent-muted,#a3a3a3);
140
- --sa-border:var(--sideagent-border,#2e2e2e);--sa-surface:var(--sideagent-surface,#262626);
141
- --sa-accent:var(--sideagent-accent,#f5f5f5);--sa-accent-fg:var(--sideagent-accent-fg,#171717);color-scheme:dark}
142
- .sa-root{font-family:var(--sa-font);font-size:14px;line-height:1.5;color:var(--sa-fg);box-sizing:border-box;-webkit-font-smoothing:antialiased}
143
- .sa-root *{box-sizing:border-box}
144
- .sa-panel{display:flex;flex-direction:column;background:var(--sa-bg);border:1px solid var(--sa-border);overflow:hidden}
145
- .sa-floating .sa-panel{position:fixed;right:16px;bottom:80px;width:min(400px,calc(100vw - 32px));height:min(600px,calc(100vh - 104px));border-radius:calc(var(--sa-radius) + 4px);box-shadow:0 8px 30px rgba(0,0,0,.12);z-index:2147483000}
146
- .sa-sidebar .sa-panel{position:fixed;top:0;right:0;bottom:0;width:min(400px,100vw);border-width:0 0 0 1px;z-index:2147483000}
147
- .sa-shell{display:flex;height:100dvh;width:100%;min-width:0}
148
- .sa-shell-main{flex:1;min-width:0;overflow:auto}
149
- .sa-docked{display:contents}
150
- .sa-docked .sa-panel{position:relative;flex:none;max-width:70vw;height:100%;border-width:0 0 0 1px;z-index:auto}
151
- .sa-resizer{position:absolute;top:0;bottom:0;left:-3px;width:7px;cursor:col-resize;touch-action:none;z-index:1}
152
- .sa-resizer::after{content:"";position:absolute;top:0;bottom:0;left:3px;width:2px;background:transparent;transition:background .15s}
153
- .sa-resizer:hover::after,.sa-resizer:focus-visible::after,.sa-resizing .sa-resizer::after{background:var(--sa-muted)}
154
- .sa-resizer:focus-visible{outline:none}
155
- .sa-resizing{user-select:none;cursor:col-resize}
156
- .sa-fab{position:fixed;right:16px;bottom:16px;width:48px;height:48px;border-radius:50%;border:0;background:var(--sa-accent);color:var(--sa-accent-fg);cursor:pointer;display:grid;place-items:center;box-shadow:0 4px 14px rgba(0,0,0,.2);z-index:2147483000}
157
- .sa-header{display:flex;align-items:center;gap:6px;padding:10px 12px;border-bottom:1px solid var(--sa-border)}
158
- .sa-title{font-weight:600;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
159
- .sa-btn{border:1px solid var(--sa-border);background:transparent;color:var(--sa-fg);border-radius:calc(var(--sa-radius) - 2px);padding:4px 9px;min-height:28px;font:inherit;font-size:12px;cursor:pointer}
160
- .sa-btn:hover{background:var(--sa-surface)}
161
- .sa-btn[aria-pressed=true]{background:var(--sa-surface);border-color:var(--sa-muted)}
162
- .sa-btn-primary{background:var(--sa-accent);color:var(--sa-accent-fg);border-color:var(--sa-accent)}
163
- .sa-btn-primary:hover{background:var(--sa-accent);opacity:.88}
164
- .sa-btn:disabled{opacity:.5;cursor:not-allowed}
165
- .sa-root :is(button,textarea):focus-visible{outline:2px solid var(--sa-fg);outline-offset:2px}
166
- .sa-viewport{flex:1;overflow-y:auto;padding:14px 12px;display:flex;flex-direction:column;gap:10px}
167
- .sa-empty{color:var(--sa-muted);text-align:center;margin:auto;padding:24px}
168
- .sa-msg{max-width:90%;white-space:pre-wrap;word-break:break-word}
169
- .sa-user{align-self:flex-end;background:var(--sa-surface);padding:7px 12px;border-radius:calc(var(--sa-radius) + 4px)}
170
- .sa-assistant{align-self:flex-start}
171
- .sa-md{white-space:normal;min-width:0}
172
- .sa-md>:first-child{margin-top:0}.sa-md>:last-child{margin-bottom:0}
173
- .sa-md :is(p,ul,ol,pre,table,blockquote){margin:0 0 .6em}
174
- .sa-md :is(ul,ol){padding-left:1.4em}
175
- .sa-md :is(h1,h2,h3,h4){font-size:1em;font-weight:600;margin:.8em 0 .3em}
176
- .sa-md a{color:inherit;text-decoration:underline}
177
- .sa-md code{font-family:ui-monospace,monospace;font-size:.9em;background:var(--sa-surface);padding:1px 4px;border-radius:4px}
178
- .sa-md pre{background:var(--sa-surface);padding:8px 10px;border-radius:calc(var(--sa-radius) - 2px);overflow-x:auto}
179
- .sa-md pre code{background:none;padding:0}
180
- .sa-md blockquote{border-left:3px solid var(--sa-border);padding-left:10px;color:var(--sa-muted);margin-left:0}
181
- .sa-md table{border-collapse:collapse;display:block;overflow-x:auto}
182
- .sa-md :is(th,td){border:1px solid var(--sa-border);padding:3px 8px;text-align:left}
183
- .sa-icon-btn{display:inline-grid;place-items:center;min-width:28px;padding:4px}
184
- .sa-icon-btn svg{width:15px;height:15px}
185
- .sa-tool{align-self:stretch;border:1px solid var(--sa-border);border-radius:var(--sa-radius);padding:8px 10px;font-size:13px;min-width:0}
186
- .sa-tool-head{display:flex;align-items:center;gap:6px;font-weight:600;font-size:12.5px;margin-bottom:6px}
187
- .sa-tool[data-state=done] .sa-tool-head svg{color:var(--sa-muted)}
188
- .sa-tool[data-state=error] .sa-tool-head,.sa-tool[data-state=rejected] .sa-tool-head{color:var(--sa-danger)}
189
- .sa-spin{animation:sa-rot 1s linear infinite}
190
- @keyframes sa-rot{to{transform:rotate(360deg)}}
191
- @media (prefers-reduced-motion:reduce){.sa-spin{animation:none}}
192
- .sa-table-wrap{overflow:auto;max-height:240px;border:1px solid var(--sa-border);border-radius:calc(var(--sa-radius) - 2px)}
193
- .sa-table{border-collapse:collapse;width:100%;font-size:12.5px}
194
- .sa-table th,.sa-table td{padding:5px 9px;text-align:left;white-space:nowrap;border-bottom:1px solid var(--sa-border)}
195
- .sa-table th{position:sticky;top:0;background:var(--sa-surface);font-weight:600;color:var(--sa-muted)}
196
- .sa-table tr:last-child td{border-bottom:0}
197
- .sa-table .sa-num{text-align:right;font-variant-numeric:tabular-nums}
198
- .sa-more{color:var(--sa-muted);font-size:12px;margin-top:4px}
199
- .sa-fields{margin:0;display:grid;gap:4px}
200
- .sa-field{display:grid;grid-template-columns:minmax(80px,35%) 1fr;gap:8px}
201
- .sa-field dt{color:var(--sa-muted)}
202
- .sa-field dd{margin:0;word-break:break-word}
203
- .sa-list{margin:0;padding-left:18px}
204
- .sa-sources{margin:0;padding:0;list-style:none;display:grid;gap:4px}
205
- .sa-sources li{display:flex;align-items:center;gap:6px}
206
- .sa-sources svg{flex:none;color:var(--sa-muted)}
207
- .sa-sources a{color:inherit;text-decoration:underline;text-underline-offset:2px;display:inline-flex;align-items:center;gap:4px}
208
- .sa-details{margin-top:6px}
209
- .sa-details summary{cursor:pointer;color:var(--sa-muted);font-size:12px;list-style:none;display:flex;align-items:center;gap:2px}
210
- .sa-details summary::-webkit-details-marker{display:none}
211
- .sa-details[open] summary svg{transform:rotate(90deg)}
212
- .sa-details pre{margin:6px 0 0;white-space:pre-wrap;word-break:break-word;max-height:160px;overflow:auto;font-size:11.5px}
213
- .sa-approval-title{display:flex;align-items:center;gap:6px}
214
- .sa-approval{margin:0 12px 8px;border:1px solid var(--sa-fg);border-radius:var(--sa-radius);padding:10px;background:var(--sa-surface)}
215
- .sa-approval pre{margin:6px 0;white-space:pre-wrap;word-break:break-word;max-height:120px;overflow:auto;font-size:12px}
216
- .sa-approval-text{margin:6px 0}
217
- .sa-approval .sa-fields{margin:0 0 6px}
218
- .sa-approval .sa-row{margin-top:8px}
219
- .sa-row{display:flex;gap:8px}
220
- .sa-composer{display:flex;flex-direction:column;gap:4px;margin:0 12px 12px;padding:10px 10px 8px 14px;border:1px solid var(--sa-border);border-radius:18px;background:var(--sa-surface)}
221
- .sa-input{width:100%;resize:none;border:0;background:transparent;padding:2px 0;font:inherit;color:inherit;max-height:160px}
222
- .sa-root .sa-input:focus-visible{outline:none}
223
- .sa-input::placeholder{color:var(--sa-muted)}
224
- .sa-composer-bar{display:flex;align-items:center;justify-content:space-between;gap:8px}
225
- .sa-hint{color:var(--sa-muted);font-size:12px;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
226
- .sa-send{flex:none;display:grid;place-items:center;width:32px;height:32px;border-radius:50%;border:0;background:var(--sa-accent);color:var(--sa-accent-fg);cursor:pointer}
227
- .sa-send:hover{opacity:.88}
228
- .sa-send:disabled{opacity:.35;cursor:not-allowed}
229
- .sa-send svg{width:16px;height:16px}
230
- .sa-chatwrap{display:flex;flex-direction:column;flex:1;min-height:0}
231
- .sa-chatwrap[hidden]{display:none}
232
- .sa-history{gap:6px}
233
- .sa-back{align-self:flex-start;display:inline-flex;align-items:center;gap:4px;margin-bottom:4px}
234
- .sa-thread{display:flex;gap:6px;align-items:stretch}
235
- .sa-thread-open{flex:1;min-width:0;text-align:left;border:1px solid var(--sa-border);background:transparent;color:inherit;border-radius:var(--sa-radius);padding:8px 10px;font:inherit;cursor:pointer;display:flex;flex-direction:column;gap:2px}
236
- .sa-thread-open:hover{background:var(--sa-surface)}
237
- .sa-thread-title{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
238
- .sa-error{color:var(--sa-danger)}
239
- `;
240
184
 
241
- // src/client/transport.ts
242
- async function* streamChat(body, opts) {
243
- const res = await fetch(opts.endpoint, {
244
- method: "POST",
245
- credentials: opts.credentials ?? "same-origin",
246
- headers: { "content-type": "application/json", ...opts.headers },
247
- body: JSON.stringify(body),
248
- signal: opts.signal
249
- });
250
- if (!res.ok || !res.body) {
251
- let message = `Request failed (${res.status})`;
252
- try {
253
- const j = await res.json();
254
- if (j?.error) message = String(j.error);
255
- } catch {
256
- }
257
- yield { type: "error", code: `http_${res.status}`, message };
258
- return;
259
- }
260
- const reader = res.body.getReader();
261
- const decoder = new TextDecoder();
262
- let buf = "";
263
- for (; ; ) {
264
- const { done, value } = await reader.read();
265
- if (done) break;
266
- buf += decoder.decode(value, { stream: true });
267
- let i;
268
- while ((i = buf.indexOf("\n\n")) >= 0) {
269
- const block = buf.slice(0, i);
270
- buf = buf.slice(i + 2);
271
- if (block.startsWith("data: ")) yield JSON.parse(block.slice(6));
272
- }
273
- }
274
- }
275
- async function callJson(body, opts) {
276
- const res = await fetch(opts.endpoint, {
277
- method: "POST",
278
- credentials: opts.credentials ?? "same-origin",
279
- headers: { "content-type": "application/json", ...opts.headers },
280
- body: JSON.stringify(body)
281
- });
282
- let data = null;
283
- try {
284
- data = await res.json();
285
- } catch {
286
- }
287
- return { status: res.status, data };
185
+ // src/client/context.ts
186
+ import { createContext, useContext } from "react";
187
+ var DockContext = createContext(false);
188
+ var AgentContext = createContext(null);
189
+ function useAgent() {
190
+ const ctx = useContext(AgentContext);
191
+ if (!ctx) throw new Error("useAgent must be used inside <AgentProvider>");
192
+ return ctx;
288
193
  }
194
+ var RenderersContext = createContext(void 0);
289
195
 
290
- // src/client/AgentChat.tsx
291
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
292
- var MODE_KEY = "sideagent:mode";
293
- var WIDTH_KEY = "sideagent:width";
294
- var threadKey = (endpoint) => `sideagent:thread:${endpoint}`;
295
- function storeGet(k) {
296
- try {
297
- return localStorage.getItem(k);
298
- } catch {
299
- return null;
300
- }
301
- }
302
- function storeSet(k, v) {
196
+ // src/client/hooks.ts
197
+ function safely(fn) {
303
198
  try {
304
- if (v === null) localStorage.removeItem(k);
305
- else localStorage.setItem(k, v);
306
- } catch {
199
+ fn?.();
200
+ } catch (err) {
201
+ console.error("[sideagent] hook threw:", err);
307
202
  }
308
203
  }
309
- function toInitialMessages(messages) {
310
- return (messages ?? []).map(
311
- (m) => m.role === "user" ? { role: "user", content: m.parts.map((p) => ({ type: "text", text: p.type === "text" ? p.text : "" })) } : {
312
- role: "assistant",
313
- content: m.parts.map(
314
- (p) => p.type === "text" ? { type: "text", text: p.text } : { type: "tool-call", toolCallId: p.callId, toolName: p.operationId, args: p.args ?? {}, argsText: pretty(p.args), result: p.result }
315
- ),
316
- status: { type: "complete", reason: "stop" }
317
- }
318
- );
319
- }
320
204
  function fireComplete(hooks, ev) {
321
205
  const oe = { operationId: ev.operationId, status: ev.status, args: ev.args, result: ev.result };
322
206
  safely(() => hooks?.onAnyOperation?.(oe));
323
207
  safely(() => hooks?.onOperationComplete?.[ev.operationId]?.(oe));
324
208
  for (const tag of ev.invalidates) safely(() => hooks?.onInvalidate?.(tag, oe));
325
209
  }
326
- var ago = (t) => {
327
- const s = Math.round((t - Date.now()) / 1e3);
328
- const rtf = new Intl.RelativeTimeFormat(void 0, { numeric: "auto" });
329
- const a = Math.abs(s);
330
- if (a < 60) return rtf.format(s, "second");
331
- if (a < 3600) return rtf.format(Math.round(s / 60), "minute");
332
- if (a < 86400) return rtf.format(Math.round(s / 3600), "hour");
333
- return rtf.format(Math.round(s / 86400), "day");
334
- };
335
- var MODES = ["floating", "sidebar"];
336
- var MIN_WIDTH = 300;
337
- var DEFAULT_WIDTH = 400;
338
- var maxWidth = () => Math.max(MIN_WIDTH, Math.min(760, Math.round(window.innerWidth * 0.7)));
339
- var clampWidth = (w) => Math.min(maxWidth(), Math.max(MIN_WIDTH, Math.round(w)));
340
- var DockContext = createContext(false);
341
- var AgentContext = createContext(null);
342
- function useAgent() {
343
- const ctx = useContext(AgentContext);
344
- if (!ctx) throw new Error("useAgent must be used inside <AgentProvider>");
345
- return ctx;
346
- }
347
- function AgentProvider({ children, className, style, ...chat }) {
348
- const [open, setOpen] = useState(chat.defaultOpen ?? false);
349
- const controls = useMemo(() => ({ open, setOpen }), [open]);
350
- return /* @__PURE__ */ jsx2(AgentContext.Provider, { value: controls, children: /* @__PURE__ */ jsx2(DockContext.Provider, { value: true, children: /* @__PURE__ */ jsxs2("div", { className: `sa-shell${className ? ` ${className}` : ""}`, style, children: [
351
- /* @__PURE__ */ jsx2("div", { className: "sa-shell-main", children }),
352
- /* @__PURE__ */ jsx2(AgentChat, { ...chat })
353
- ] }) }) });
354
- }
355
- function readMode(fallback) {
356
- try {
357
- const v = localStorage.getItem(MODE_KEY);
358
- return v && MODES.includes(v) ? v : fallback;
359
- } catch {
360
- return fallback;
361
- }
362
- }
363
- var pretty = (v) => {
364
- try {
365
- return JSON.stringify(v, null, 2);
366
- } catch {
367
- return String(v);
368
- }
369
- };
370
- var Text = ({ text }) => /* @__PURE__ */ jsx2("span", { children: text });
371
- var RenderersContext = createContext(void 0);
372
- var httpUrl = (u) => {
373
- try {
374
- const url = new URL(String(u));
375
- return url.protocol === "http:" || url.protocol === "https:" ? url.toString() : null;
376
- } catch {
377
- return null;
378
- }
379
- };
210
+
211
+ // src/client/Messages.tsx
212
+ import { MessagePrimitive } from "@assistant-ui/react";
213
+ import Markdown from "react-markdown";
214
+ import remarkGfm from "remark-gfm";
215
+
216
+ // src/client/ToolCard.tsx
217
+ import {
218
+ CheckCircledIcon,
219
+ ChevronRightIcon as ChevronRightIcon2,
220
+ CrossCircledIcon,
221
+ ExternalLinkIcon,
222
+ FileTextIcon,
223
+ ReloadIcon
224
+ } from "@radix-ui/react-icons";
225
+ import { useContext as useContext2 } from "react";
226
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
227
+ var SEARCH_TOOL = "search_knowledge";
380
228
  function Sources({ result }) {
381
229
  const sources = result?.sources ?? [];
382
- if (typeof result?.error === "string") return /* @__PURE__ */ jsx2("div", { className: "sa-error", children: result.error });
383
- if (!sources.length) return /* @__PURE__ */ jsx2("div", { className: "sa-more", children: "No relevant sources found." });
384
- return /* @__PURE__ */ jsx2("ul", { className: "sa-sources", children: sources.map((s) => {
230
+ if (typeof result?.error === "string") return /* @__PURE__ */ jsx3("div", { className: "sa-error", children: result.error });
231
+ if (!sources.length) return /* @__PURE__ */ jsx3("div", { className: "sa-more", children: "No relevant sources found." });
232
+ return /* @__PURE__ */ jsx3("ul", { className: "sa-sources", children: sources.map((s) => {
385
233
  const href = s.url ? httpUrl(s.url) : null;
386
- return /* @__PURE__ */ jsxs2("li", { children: [
387
- /* @__PURE__ */ jsx2(FileTextIcon, {}),
388
- href ? /* @__PURE__ */ jsxs2("a", { href, target: "_blank", rel: "noopener noreferrer", children: [
234
+ return /* @__PURE__ */ jsxs3("li", { children: [
235
+ /* @__PURE__ */ jsx3(FileTextIcon, {}),
236
+ href ? /* @__PURE__ */ jsxs3("a", { href, target: "_blank", rel: "noopener noreferrer", children: [
389
237
  s.title,
390
238
  " ",
391
- /* @__PURE__ */ jsx2(ExternalLinkIcon, {})
392
- ] }) : /* @__PURE__ */ jsx2("span", { children: s.title })
239
+ /* @__PURE__ */ jsx3(ExternalLinkIcon, {})
240
+ ] }) : /* @__PURE__ */ jsx3("span", { children: s.title })
393
241
  ] }, s.id);
394
242
  }) });
395
243
  }
396
244
  function ToolCard(props) {
397
- const renderers = useContext(RenderersContext);
245
+ const renderers = useContext2(RenderersContext);
398
246
  const { toolName, args, result } = props;
399
247
  const rejected = result?.rejected === true;
400
248
  const failed = !rejected && typeof result?.error === "string";
401
249
  const running = result === void 0;
402
250
  const custom = renderers?.[toolName];
403
- return /* @__PURE__ */ jsxs2("div", { className: "sa-tool", "data-state": running ? "running" : rejected ? "rejected" : failed ? "error" : "done", children: [
404
- /* @__PURE__ */ jsxs2("div", { className: "sa-tool-head", children: [
405
- running ? /* @__PURE__ */ jsx2(ReloadIcon, { className: "sa-spin" }) : rejected || failed ? /* @__PURE__ */ jsx2(CrossCircledIcon, {}) : /* @__PURE__ */ jsx2(CheckCircledIcon, {}),
406
- /* @__PURE__ */ jsx2("span", { children: toolName === "search_knowledge" ? "Sources" : humanize(toolName) })
251
+ return /* @__PURE__ */ jsxs3("div", { className: "sa-tool", "data-state": running ? "running" : rejected ? "rejected" : failed ? "error" : "done", children: [
252
+ /* @__PURE__ */ jsxs3("div", { className: "sa-tool-head", children: [
253
+ running ? /* @__PURE__ */ jsx3(ReloadIcon, { className: "sa-spin" }) : rejected || failed ? /* @__PURE__ */ jsx3(CrossCircledIcon, {}) : /* @__PURE__ */ jsx3(CheckCircledIcon, {}),
254
+ /* @__PURE__ */ jsx3("span", { children: toolName === SEARCH_TOOL ? "Sources" : humanize(toolName) })
407
255
  ] }),
408
- !running && (custom ? custom({ operationId: toolName, args, result }) : toolName === "search_knowledge" ? /* @__PURE__ */ jsx2(Sources, { result }) : /* @__PURE__ */ jsx2(ResultView, { result })),
409
- /* @__PURE__ */ jsxs2("details", { className: "sa-details", children: [
410
- /* @__PURE__ */ jsxs2("summary", { children: [
411
- /* @__PURE__ */ jsx2(ChevronRightIcon, {}),
256
+ !running && (custom ? custom({ operationId: toolName, args, result }) : toolName === SEARCH_TOOL ? /* @__PURE__ */ jsx3(Sources, { result }) : /* @__PURE__ */ jsx3(ResultView, { result })),
257
+ /* @__PURE__ */ jsxs3("details", { className: "sa-details", children: [
258
+ /* @__PURE__ */ jsxs3("summary", { children: [
259
+ /* @__PURE__ */ jsx3(ChevronRightIcon2, {}),
412
260
  " Details"
413
261
  ] }),
414
- /* @__PURE__ */ jsx2("pre", { children: pretty(args) }),
415
- !running && /* @__PURE__ */ jsx2("pre", { children: pretty(result) })
262
+ /* @__PURE__ */ jsx3("pre", { children: pretty(args) }),
263
+ !running && /* @__PURE__ */ jsx3("pre", { children: pretty(result) })
416
264
  ] })
417
265
  ] });
418
266
  }
267
+
268
+ // src/client/Messages.tsx
269
+ import { jsx as jsx4 } from "react/jsx-runtime";
270
+ var Text = ({ text }) => /* @__PURE__ */ jsx4("span", { children: text });
419
271
  var mdComponents = {
420
- a: ({ node: _n, ...p }) => /* @__PURE__ */ jsx2("a", { ...p, target: "_blank", rel: "noopener noreferrer nofollow" })
272
+ a: ({ node: _n, ...p }) => /* @__PURE__ */ jsx4("a", { ...p, target: "_blank", rel: "noopener noreferrer nofollow" })
421
273
  };
422
- var UserMessage = () => /* @__PURE__ */ jsx2(MessagePrimitive.Root, { className: "sa-msg sa-user", children: /* @__PURE__ */ jsx2(MessagePrimitive.Parts, { components: { Text } }) });
423
- var AssistantMessage = () => /* @__PURE__ */ jsx2(MessagePrimitive.Root, { style: { display: "contents" }, children: /* @__PURE__ */ jsx2(
274
+ var UserMessage = () => /* @__PURE__ */ jsx4(MessagePrimitive.Root, { className: "sa-msg sa-user", children: /* @__PURE__ */ jsx4(MessagePrimitive.Parts, { components: { Text } }) });
275
+ var AssistantMessage = () => /* @__PURE__ */ jsx4(MessagePrimitive.Root, { style: { display: "contents" }, children: /* @__PURE__ */ jsx4(
424
276
  MessagePrimitive.Parts,
425
277
  {
426
278
  components: {
427
- Text: ({ text }) => text ? /* @__PURE__ */ jsx2("div", { className: "sa-msg sa-assistant sa-md", children: /* @__PURE__ */ jsx2(Markdown, { remarkPlugins: [remarkGfm], components: mdComponents, children: text }) }) : null,
279
+ Text: ({ text }) => text ? /* @__PURE__ */ jsx4("div", { className: "sa-msg sa-assistant sa-md", children: /* @__PURE__ */ jsx4(Markdown, { remarkPlugins: [remarkGfm], components: mdComponents, children: text }) }) : null,
428
280
  tools: { Fallback: ToolCard }
429
281
  }
430
282
  }
431
- ) });
432
- function summaryFields(args) {
433
- const a = args && typeof args === "object" ? args : {};
434
- const body = a.body && typeof a.body === "object" && !Array.isArray(a.body) ? a.body : {};
435
- const { body: _body, ...rest } = a;
436
- return Object.entries({ ...rest, ...body }).filter(([, v]) => ["string", "number", "boolean"].includes(typeof v)).map(([k, v]) => [humanize(k), String(v)]);
283
+ ) });
284
+
285
+ // src/client/parts.ts
286
+ function upsertTool(parts, callId, patch) {
287
+ const i = parts.findIndex((p) => p.type === "tool-call" && p.toolCallId === callId);
288
+ if (i < 0)
289
+ return [...parts, { type: "tool-call", toolCallId: callId, toolName: "", args: {}, argsText: "{}", ...patch }];
290
+ return parts.map((p, j) => j === i ? { ...p, ...patch } : p);
291
+ }
292
+ var argsOf = (v) => v ?? {};
293
+ function applyEvent(parts, ev) {
294
+ switch (ev.type) {
295
+ case "text.delta": {
296
+ const tail = parts[parts.length - 1];
297
+ if (tail?.type === "text") return [...parts.slice(0, -1), { type: "text", text: tail.text + ev.text }];
298
+ return [...parts, { type: "text", text: ev.text }];
299
+ }
300
+ case "approval.request":
301
+ return upsertTool(parts, ev.callId, {
302
+ toolName: ev.operationId,
303
+ args: argsOf(ev.args),
304
+ argsText: pretty(ev.args)
305
+ });
306
+ case "tool.start":
307
+ return upsertTool(parts, ev.callId, { toolName: ev.operationId });
308
+ case "tool.rejected":
309
+ return upsertTool(parts, ev.callId, { toolName: ev.operationId, result: { rejected: true } });
310
+ case "tool.complete":
311
+ return upsertTool(parts, ev.callId, {
312
+ toolName: ev.operationId,
313
+ args: argsOf(ev.args),
314
+ argsText: pretty(ev.args),
315
+ result: ev.result ?? null
316
+ });
317
+ case "error":
318
+ return [...parts, { type: "text", text: `\u26A0 ${ev.message}` }];
319
+ default:
320
+ return [...parts];
321
+ }
322
+ }
323
+ function toInitialMessages(messages) {
324
+ return (messages ?? []).map(
325
+ (m) => m.role === "user" ? {
326
+ role: "user",
327
+ content: m.parts.map((p) => ({ type: "text", text: p.type === "text" ? p.text : "" }))
328
+ } : {
329
+ role: "assistant",
330
+ content: m.parts.map(
331
+ (p) => p.type === "text" ? { type: "text", text: p.text } : {
332
+ type: "tool-call",
333
+ toolCallId: p.callId,
334
+ toolName: p.operationId,
335
+ args: argsOf(p.args),
336
+ argsText: pretty(p.args),
337
+ result: p.result
338
+ }
339
+ ),
340
+ status: { type: "complete", reason: "stop" }
341
+ }
342
+ );
343
+ }
344
+
345
+ // src/client/transport.ts
346
+ async function* streamChat(body, opts) {
347
+ const res = await fetch(opts.endpoint, {
348
+ method: "POST",
349
+ credentials: opts.credentials ?? "same-origin",
350
+ headers: { "content-type": "application/json", ...opts.headers },
351
+ body: JSON.stringify(body),
352
+ signal: opts.signal
353
+ });
354
+ if (!res.ok || !res.body) {
355
+ let message = `Request failed (${res.status})`;
356
+ try {
357
+ const j = await res.json();
358
+ if (j?.error) message = String(j.error);
359
+ } catch {
360
+ }
361
+ yield { type: "error", code: `http_${res.status}`, message };
362
+ return;
363
+ }
364
+ const reader = res.body.getReader();
365
+ const decoder = new TextDecoder();
366
+ let buf = "";
367
+ for (; ; ) {
368
+ const { done, value } = await reader.read();
369
+ if (done) break;
370
+ buf += decoder.decode(value, { stream: true });
371
+ let i;
372
+ while ((i = buf.indexOf("\n\n")) >= 0) {
373
+ const block = buf.slice(0, i);
374
+ buf = buf.slice(i + 2);
375
+ if (block.startsWith("data: ")) yield JSON.parse(block.slice(6));
376
+ }
377
+ }
437
378
  }
438
- function ApprovalCard({ event, onDecide }) {
439
- const action = humanize(event.operationId).toLowerCase();
440
- const fields = summaryFields(event.args);
441
- return /* @__PURE__ */ jsxs2("div", { className: "sa-approval", role: "alertdialog", "aria-label": "Approval needed", children: [
442
- /* @__PURE__ */ jsxs2("strong", { className: "sa-approval-title", children: [
443
- /* @__PURE__ */ jsx2(ExclamationTriangleIcon, {}),
444
- " Confirm before I continue"
445
- ] }),
446
- /* @__PURE__ */ jsxs2("p", { className: "sa-approval-text", children: [
447
- "I'd like to ",
448
- /* @__PURE__ */ jsx2("strong", { children: action }),
449
- ". Nothing changes until you approve."
450
- ] }),
451
- fields.length > 0 && /* @__PURE__ */ jsx2("dl", { className: "sa-fields", children: fields.map(([k, v]) => /* @__PURE__ */ jsxs2("div", { className: "sa-field", children: [
452
- /* @__PURE__ */ jsx2("dt", { children: k }),
453
- /* @__PURE__ */ jsx2("dd", { children: v })
454
- ] }, k)) }),
455
- /* @__PURE__ */ jsxs2("details", { className: "sa-details", children: [
456
- /* @__PURE__ */ jsxs2("summary", { children: [
457
- /* @__PURE__ */ jsx2(ChevronRightIcon, {}),
458
- " Details"
459
- ] }),
460
- /* @__PURE__ */ jsxs2("pre", { children: [
461
- event.method,
462
- " ",
463
- event.path,
464
- "\n",
465
- pretty(event.args)
466
- ] })
467
- ] }),
468
- /* @__PURE__ */ jsxs2("div", { className: "sa-row", children: [
469
- /* @__PURE__ */ jsx2("button", { className: "sa-btn sa-btn-primary", onClick: () => onDecide("approve"), children: "Approve" }),
470
- /* @__PURE__ */ jsx2("button", { className: "sa-btn", onClick: () => onDecide("reject"), children: "Reject" })
471
- ] })
472
- ] });
379
+ async function callJson(body, opts) {
380
+ const res = await fetch(opts.endpoint, {
381
+ method: "POST",
382
+ credentials: opts.credentials ?? "same-origin",
383
+ headers: { "content-type": "application/json", ...opts.headers },
384
+ body: JSON.stringify(body)
385
+ });
386
+ let data = null;
387
+ try {
388
+ data = await res.json();
389
+ } catch {
390
+ }
391
+ return { status: res.status, data };
473
392
  }
393
+
394
+ // src/client/Chat.tsx
395
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
474
396
  function Chat(props) {
475
397
  const { endpoint, hooks, credentials, headers } = props;
476
398
  const latest = useRef(props);
@@ -485,53 +407,30 @@ function Chat(props) {
485
407
  const last = [...messages].reverse().find((m) => m.role === "user");
486
408
  const text = (last?.content ?? []).map((c) => c.type === "text" ? c.text : "").join("\n").trim();
487
409
  const ctx = typeof p.context === "function" ? p.context() : p.context;
488
- const parts = [];
489
- const upsertTool = (callId, patch) => {
490
- let part = parts.find((x) => x.type === "tool-call" && x.toolCallId === callId);
491
- if (!part) {
492
- part = { type: "tool-call", toolCallId: callId, toolName: "", args: {}, argsText: "{}" };
493
- parts.push(part);
494
- }
495
- Object.assign(part, patch);
496
- };
410
+ let parts = [];
497
411
  safely(p.hooks?.onTurnStart);
498
412
  let body = { threadId: threadId.current, message: text, context: ctx };
499
413
  try {
500
414
  for (; ; ) {
501
415
  let pending;
502
- for await (const ev of streamChat(body, { endpoint: p.endpoint, credentials: p.credentials, headers: p.headers, signal: abortSignal })) {
503
- switch (ev.type) {
504
- case "turn.start":
505
- threadId.current = ev.threadId;
506
- p.onThread(ev.threadId);
507
- break;
508
- case "text.delta": {
509
- const tail = parts[parts.length - 1];
510
- if (tail?.type === "text") tail.text += ev.text;
511
- else parts.push({ type: "text", text: ev.text });
512
- break;
513
- }
514
- case "approval.request":
515
- pending = ev;
516
- upsertTool(ev.callId, { toolName: ev.operationId, args: ev.args, argsText: pretty(ev.args) });
517
- break;
518
- case "tool.start":
519
- upsertTool(ev.callId, { toolName: ev.operationId });
520
- break;
521
- case "tool.rejected":
522
- upsertTool(ev.callId, { toolName: ev.operationId, result: { rejected: true } });
523
- break;
524
- case "tool.complete": {
525
- upsertTool(ev.callId, { toolName: ev.operationId, args: ev.args ?? {}, argsText: pretty(ev.args), result: ev.result ?? null });
526
- fireComplete(p.hooks, ev);
527
- break;
528
- }
529
- case "error":
530
- parts.push({ type: "text", text: `\u26A0 ${ev.message}` });
531
- safely(() => p.hooks?.onError?.({ code: ev.code, message: ev.message }));
532
- break;
416
+ for await (const ev of streamChat(body, {
417
+ endpoint: p.endpoint,
418
+ credentials: p.credentials,
419
+ headers: p.headers,
420
+ signal: abortSignal
421
+ })) {
422
+ parts = applyEvent(parts, ev);
423
+ if (ev.type === "turn.start") {
424
+ threadId.current = ev.threadId;
425
+ p.onThread(ev.threadId);
426
+ } else if (ev.type === "approval.request") {
427
+ pending = ev;
428
+ } else if (ev.type === "tool.complete") {
429
+ fireComplete(p.hooks, ev);
430
+ } else if (ev.type === "error") {
431
+ safely(() => p.hooks?.onError?.({ code: ev.code, message: ev.message }));
533
432
  }
534
- yield { content: parts.map((x) => ({ ...x })) };
433
+ yield { content: parts };
535
434
  }
536
435
  if (!pending) break;
537
436
  const req = pending;
@@ -547,7 +446,7 @@ function Chat(props) {
547
446
  setApproval(null);
548
447
  safely(p.hooks?.onTurnEnd);
549
448
  }
550
- yield { content: parts.map((x) => ({ ...x })) };
449
+ yield { content: parts };
551
450
  }
552
451
  }),
553
452
  // endpoint/hooks/etc. are read through `latest`, so the adapter stays stable.
@@ -558,7 +457,10 @@ function Chat(props) {
558
457
  const req = loadedPending;
559
458
  if (!req || !threadId.current) return;
560
459
  setLoadedPending(null);
561
- for await (const ev of streamChat({ threadId: threadId.current, approval: { token: req.token, decision } }, { endpoint, credentials, headers })) {
460
+ for await (const ev of streamChat(
461
+ { threadId: threadId.current, approval: { token: req.token, decision } },
462
+ { endpoint, credentials, headers }
463
+ )) {
562
464
  if (ev.type === "tool.complete") fireComplete(hooks, ev);
563
465
  if (ev.type === "error") safely(() => hooks?.onError?.({ code: ev.code, message: ev.message }));
564
466
  }
@@ -566,24 +468,37 @@ function Chat(props) {
566
468
  };
567
469
  const shownApproval = approval?.event ?? loadedPending;
568
470
  const decide = (d) => approval ? approval.decide(d) : void decideLoaded(d);
569
- return /* @__PURE__ */ jsx2(RenderersContext.Provider, { value: props.resultRenderers, children: /* @__PURE__ */ jsx2(AssistantRuntimeProvider, { runtime, children: /* @__PURE__ */ jsxs2(ThreadPrimitive.Root, { style: { display: "contents" }, children: [
570
- /* @__PURE__ */ jsxs2(ThreadPrimitive.Viewport, { className: "sa-viewport", children: [
571
- /* @__PURE__ */ jsx2(ThreadPrimitive.Empty, { children: /* @__PURE__ */ jsx2("div", { className: "sa-empty", children: "Ask me to look things up or make changes. I will ask before changing anything." }) }),
572
- /* @__PURE__ */ jsx2(ThreadPrimitive.Messages, { components: { UserMessage, AssistantMessage } })
471
+ return /* @__PURE__ */ jsx5(RenderersContext.Provider, { value: props.resultRenderers, children: /* @__PURE__ */ jsx5(AssistantRuntimeProvider, { runtime, children: /* @__PURE__ */ jsxs4(ThreadPrimitive.Root, { style: { display: "contents" }, children: [
472
+ /* @__PURE__ */ jsxs4(ThreadPrimitive.Viewport, { className: "sa-viewport", children: [
473
+ /* @__PURE__ */ jsx5(ThreadPrimitive.Empty, { children: /* @__PURE__ */ jsx5("div", { className: "sa-empty", children: "Ask me to look things up or make changes. I will ask before changing anything." }) }),
474
+ /* @__PURE__ */ jsx5(ThreadPrimitive.Messages, { components: { UserMessage, AssistantMessage } })
573
475
  ] }),
574
- shownApproval && /* @__PURE__ */ jsx2(ApprovalCard, { event: shownApproval, onDecide: decide }),
575
- /* @__PURE__ */ jsxs2(ComposerPrimitive.Root, { className: "sa-composer", children: [
576
- /* @__PURE__ */ jsx2(ComposerPrimitive.Input, { className: "sa-input", rows: 2, placeholder: props.placeholder ?? "Message\u2026", disabled: !!shownApproval }),
577
- /* @__PURE__ */ jsxs2("div", { className: "sa-composer-bar", children: [
578
- /* @__PURE__ */ jsx2("span", { className: "sa-hint", children: "Enter to send \xB7 Shift+Enter for a new line" }),
579
- /* @__PURE__ */ jsx2(ComposerPrimitive.Send, { className: "sa-send", "aria-label": "Send", title: "Send", children: /* @__PURE__ */ jsx2(ArrowUpIcon, {}) })
476
+ shownApproval && /* @__PURE__ */ jsx5(ApprovalCard, { event: shownApproval, onDecide: decide }),
477
+ /* @__PURE__ */ jsxs4(ComposerPrimitive.Root, { className: "sa-composer", children: [
478
+ /* @__PURE__ */ jsx5(
479
+ ComposerPrimitive.Input,
480
+ {
481
+ className: "sa-input",
482
+ rows: 2,
483
+ placeholder: props.placeholder ?? "Message\u2026",
484
+ disabled: !!shownApproval
485
+ }
486
+ ),
487
+ /* @__PURE__ */ jsxs4("div", { className: "sa-composer-bar", children: [
488
+ /* @__PURE__ */ jsx5("span", { className: "sa-hint", children: "Enter to send \xB7 Shift+Enter for a new line" }),
489
+ /* @__PURE__ */ jsx5(ComposerPrimitive.Send, { className: "sa-send", "aria-label": "Send", title: "Send", children: /* @__PURE__ */ jsx5(ArrowUpIcon, {}) })
580
490
  ] })
581
491
  ] })
582
492
  ] }) }) });
583
493
  }
494
+
495
+ // src/client/HistoryList.tsx
496
+ import { ArrowLeftIcon, TrashIcon } from "@radix-ui/react-icons";
497
+ import { useEffect, useState as useState2 } from "react";
498
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
584
499
  function HistoryList(props) {
585
- const [threads, setThreads] = useState(null);
586
- const [error, setError] = useState(false);
500
+ const [threads, setThreads] = useState2(null);
501
+ const [error, setError] = useState2(false);
587
502
  const opts = { endpoint: props.endpoint, credentials: props.credentials, headers: props.headers };
588
503
  useEffect(() => {
589
504
  callJson({ action: "threads.list" }, opts).then((r) => r.data?.threads ? setThreads(r.data.threads) : setError(true)).catch(() => setError(true));
@@ -593,38 +508,239 @@ function HistoryList(props) {
593
508
  setThreads((t) => t?.filter((x) => x.id !== id) ?? null);
594
509
  props.onDeleted(id);
595
510
  };
596
- return /* @__PURE__ */ jsxs2("div", { className: "sa-viewport sa-history", children: [
597
- /* @__PURE__ */ jsxs2("button", { className: "sa-btn sa-back", onClick: props.onClose, children: [
598
- /* @__PURE__ */ jsx2(ArrowLeftIcon, {}),
511
+ return /* @__PURE__ */ jsxs5("div", { className: "sa-viewport sa-history", children: [
512
+ /* @__PURE__ */ jsxs5("button", { className: "sa-btn sa-back", onClick: props.onClose, children: [
513
+ /* @__PURE__ */ jsx6(ArrowLeftIcon, {}),
599
514
  " Back to chat"
600
515
  ] }),
601
- error && /* @__PURE__ */ jsx2("div", { className: "sa-error", children: "Could not load history." }),
602
- threads === null && !error && /* @__PURE__ */ jsx2("div", { className: "sa-more", children: "Loading\u2026" }),
603
- threads?.length === 0 && /* @__PURE__ */ jsx2("div", { className: "sa-empty", children: "No conversations yet." }),
604
- threads?.map((t) => /* @__PURE__ */ jsxs2("div", { className: "sa-thread", children: [
605
- /* @__PURE__ */ jsxs2("button", { className: "sa-thread-open", onClick: () => props.onOpen(t.id), children: [
606
- /* @__PURE__ */ jsx2("span", { className: "sa-thread-title", children: t.title }),
607
- /* @__PURE__ */ jsx2("span", { className: "sa-more", children: ago(t.updatedAt) })
516
+ error && /* @__PURE__ */ jsx6("div", { className: "sa-error", children: "Could not load history." }),
517
+ threads === null && !error && /* @__PURE__ */ jsx6("div", { className: "sa-more", children: "Loading\u2026" }),
518
+ threads?.length === 0 && /* @__PURE__ */ jsx6("div", { className: "sa-empty", children: "No conversations yet." }),
519
+ threads?.map((t) => /* @__PURE__ */ jsxs5("div", { className: "sa-thread", children: [
520
+ /* @__PURE__ */ jsxs5("button", { className: "sa-thread-open", onClick: () => props.onOpen(t.id), children: [
521
+ /* @__PURE__ */ jsx6("span", { className: "sa-thread-title", children: t.title }),
522
+ /* @__PURE__ */ jsx6("span", { className: "sa-more", children: ago(t.updatedAt) })
608
523
  ] }),
609
- /* @__PURE__ */ jsx2("button", { className: "sa-btn sa-icon-btn", "aria-label": "Delete conversation", title: "Delete", onClick: () => void remove(t.id), children: /* @__PURE__ */ jsx2(TrashIcon, {}) })
524
+ /* @__PURE__ */ jsx6(
525
+ "button",
526
+ {
527
+ className: "sa-btn sa-icon-btn",
528
+ "aria-label": "Delete conversation",
529
+ title: "Delete",
530
+ onClick: () => void remove(t.id),
531
+ children: /* @__PURE__ */ jsx6(TrashIcon, {})
532
+ }
533
+ )
610
534
  ] }, t.id))
611
535
  ] });
612
536
  }
537
+
538
+ // src/client/storage.ts
539
+ var MODE_KEY = "sideagent:mode";
540
+ var WIDTH_KEY = "sideagent:width";
541
+ var threadKey = (endpoint) => `sideagent:thread:${endpoint}`;
542
+ var MODES = ["floating", "sidebar"];
543
+ function storeGet(k) {
544
+ try {
545
+ return localStorage.getItem(k);
546
+ } catch {
547
+ return null;
548
+ }
549
+ }
550
+ function storeSet(k, v) {
551
+ try {
552
+ if (v === null) localStorage.removeItem(k);
553
+ else localStorage.setItem(k, v);
554
+ } catch {
555
+ }
556
+ }
557
+ function readMode(fallback) {
558
+ const v = storeGet(MODE_KEY);
559
+ return v && MODES.includes(v) ? v : fallback;
560
+ }
561
+
562
+ // src/client/styles.ts
563
+ var CSS = `
564
+ .sa-root{
565
+ --sa-bg:var(--sideagent-bg,#ffffff);
566
+ --sa-fg:var(--sideagent-fg,#171717);
567
+ --sa-muted:var(--sideagent-muted,#6b6b6b);
568
+ --sa-border:var(--sideagent-border,#e5e5e5);
569
+ --sa-surface:var(--sideagent-surface,#f5f5f5);
570
+ --sa-accent:var(--sideagent-accent,#171717);
571
+ --sa-accent-fg:var(--sideagent-accent-fg,#ffffff);
572
+ --sa-danger:var(--sideagent-danger,#b42318);
573
+ --sa-radius:var(--sideagent-radius,10px);
574
+ --sa-font:var(--sideagent-font,inherit);
575
+ color-scheme:light;
576
+ }
577
+ @media (prefers-color-scheme:dark){
578
+ .sa-root[data-theme=auto]{
579
+ --sa-bg:var(--sideagent-bg,#171717);--sa-fg:var(--sideagent-fg,#f5f5f5);--sa-muted:var(--sideagent-muted,#a3a3a3);
580
+ --sa-border:var(--sideagent-border,#2e2e2e);--sa-surface:var(--sideagent-surface,#262626);
581
+ --sa-accent:var(--sideagent-accent,#f5f5f5);--sa-accent-fg:var(--sideagent-accent-fg,#171717);color-scheme:dark}
582
+ }
583
+ :is(.dark,[data-theme=dark],[data-bs-theme=dark]) .sa-root[data-theme=auto],
584
+ .sa-root[data-theme=dark]{
585
+ --sa-bg:var(--sideagent-bg,#171717);--sa-fg:var(--sideagent-fg,#f5f5f5);--sa-muted:var(--sideagent-muted,#a3a3a3);
586
+ --sa-border:var(--sideagent-border,#2e2e2e);--sa-surface:var(--sideagent-surface,#262626);
587
+ --sa-accent:var(--sideagent-accent,#f5f5f5);--sa-accent-fg:var(--sideagent-accent-fg,#171717);color-scheme:dark}
588
+ .sa-root{font-family:var(--sa-font);font-size:14px;line-height:1.5;color:var(--sa-fg);box-sizing:border-box;-webkit-font-smoothing:antialiased}
589
+ .sa-root *{box-sizing:border-box}
590
+ .sa-panel{display:flex;flex-direction:column;background:var(--sa-bg);border:1px solid var(--sa-border);overflow:hidden}
591
+ .sa-floating .sa-panel{position:fixed;right:16px;bottom:80px;width:min(400px,calc(100vw - 32px));height:min(600px,calc(100vh - 104px));border-radius:calc(var(--sa-radius) + 4px);box-shadow:0 8px 30px rgba(0,0,0,.12);z-index:2147483000}
592
+ .sa-sidebar .sa-panel{position:fixed;top:0;right:0;bottom:0;width:min(400px,100vw);border-width:0 0 0 1px;z-index:2147483000}
593
+ .sa-shell{display:flex;height:100dvh;width:100%;min-width:0}
594
+ .sa-shell-main{flex:1;min-width:0;overflow:auto}
595
+ .sa-docked{display:contents}
596
+ .sa-docked .sa-panel{position:relative;flex:none;max-width:70vw;height:100%;border-width:0 0 0 1px;z-index:auto}
597
+ .sa-resizer{position:absolute;top:0;bottom:0;left:-3px;width:7px;cursor:col-resize;touch-action:none;z-index:1}
598
+ .sa-resizer::after{content:"";position:absolute;top:0;bottom:0;left:3px;width:2px;background:transparent;transition:background .15s}
599
+ .sa-resizer:hover::after,.sa-resizer:focus-visible::after,.sa-resizing .sa-resizer::after{background:var(--sa-muted)}
600
+ .sa-resizer:focus-visible{outline:none}
601
+ .sa-resizing{user-select:none;cursor:col-resize}
602
+ .sa-fab{position:fixed;right:calc(16px + env(safe-area-inset-right,0px));bottom:calc(16px + env(safe-area-inset-bottom,0px));width:48px;height:48px;border-radius:50%;border:0;background:var(--sa-accent);color:var(--sa-accent-fg);cursor:pointer;display:grid;place-items:center;box-shadow:0 4px 14px rgba(0,0,0,.2);z-index:2147483000}
603
+ .sa-header{display:flex;align-items:center;gap:6px;padding:10px 12px;border-bottom:1px solid var(--sa-border)}
604
+ .sa-title{font-weight:600;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
605
+ .sa-btn{border:1px solid var(--sa-border);background:transparent;color:var(--sa-fg);border-radius:calc(var(--sa-radius) - 2px);padding:4px 9px;min-height:28px;font:inherit;font-size:12px;cursor:pointer}
606
+ .sa-btn:hover{background:var(--sa-surface)}
607
+ .sa-btn[aria-pressed=true]{background:var(--sa-surface);border-color:var(--sa-muted)}
608
+ .sa-btn-primary{background:var(--sa-accent);color:var(--sa-accent-fg);border-color:var(--sa-accent)}
609
+ .sa-btn-primary:hover{background:var(--sa-accent);opacity:.88}
610
+ .sa-btn:disabled{opacity:.5;cursor:not-allowed}
611
+ .sa-root :is(button,textarea):focus-visible{outline:2px solid var(--sa-fg);outline-offset:2px}
612
+ .sa-viewport{flex:1;overflow-y:auto;overscroll-behavior:contain;padding:14px 12px;display:flex;flex-direction:column;gap:10px}
613
+ .sa-empty{color:var(--sa-muted);text-align:center;margin:auto;padding:24px}
614
+ .sa-msg{max-width:90%;white-space:pre-wrap;word-break:break-word}
615
+ .sa-user{align-self:flex-end;background:var(--sa-surface);padding:7px 12px;border-radius:calc(var(--sa-radius) + 4px)}
616
+ .sa-assistant{align-self:flex-start}
617
+ .sa-md{white-space:normal;min-width:0}
618
+ .sa-md>:first-child{margin-top:0}.sa-md>:last-child{margin-bottom:0}
619
+ .sa-md :is(p,ul,ol,pre,table,blockquote){margin:0 0 .6em}
620
+ .sa-md :is(ul,ol){padding-left:1.4em}
621
+ .sa-md :is(h1,h2,h3,h4){font-size:1em;font-weight:600;margin:.8em 0 .3em}
622
+ .sa-md a{color:inherit;text-decoration:underline}
623
+ .sa-md code{font-family:ui-monospace,monospace;font-size:.9em;background:var(--sa-surface);padding:1px 4px;border-radius:4px}
624
+ .sa-md pre{background:var(--sa-surface);padding:8px 10px;border-radius:calc(var(--sa-radius) - 2px);overflow-x:auto}
625
+ .sa-md pre code{background:none;padding:0}
626
+ .sa-md blockquote{border-left:3px solid var(--sa-border);padding-left:10px;color:var(--sa-muted);margin-left:0}
627
+ .sa-md table{border-collapse:collapse;display:block;overflow-x:auto}
628
+ .sa-md :is(th,td){border:1px solid var(--sa-border);padding:3px 8px;text-align:left}
629
+ .sa-icon-btn{display:inline-grid;place-items:center;min-width:28px;padding:4px}
630
+ .sa-icon-btn svg{width:15px;height:15px}
631
+ .sa-tool{align-self:stretch;border:1px solid var(--sa-border);border-radius:var(--sa-radius);padding:8px 10px;font-size:13px;min-width:0}
632
+ .sa-tool-head{display:flex;align-items:center;gap:6px;font-weight:600;font-size:12.5px;margin-bottom:6px}
633
+ .sa-tool[data-state=done] .sa-tool-head svg{color:var(--sa-muted)}
634
+ .sa-tool[data-state=error] .sa-tool-head,.sa-tool[data-state=rejected] .sa-tool-head{color:var(--sa-danger)}
635
+ .sa-spin{animation:sa-rot 1s linear infinite}
636
+ @keyframes sa-rot{to{transform:rotate(360deg)}}
637
+ @media (prefers-reduced-motion:reduce){.sa-spin{animation:none}}
638
+ .sa-table-wrap{overflow:auto;max-height:240px;border:1px solid var(--sa-border);border-radius:calc(var(--sa-radius) - 2px)}
639
+ .sa-table{border-collapse:collapse;width:100%;font-size:12.5px}
640
+ .sa-table th,.sa-table td{padding:5px 9px;text-align:left;white-space:nowrap;border-bottom:1px solid var(--sa-border)}
641
+ .sa-table th{position:sticky;top:0;background:var(--sa-surface);font-weight:600;color:var(--sa-muted)}
642
+ .sa-table tr:last-child td{border-bottom:0}
643
+ .sa-table .sa-num{text-align:right;font-variant-numeric:tabular-nums}
644
+ .sa-more{color:var(--sa-muted);font-size:12px;margin-top:4px}
645
+ .sa-fields{margin:0;display:grid;gap:4px}
646
+ .sa-field{display:grid;grid-template-columns:minmax(80px,35%) 1fr;gap:8px}
647
+ .sa-field dt{color:var(--sa-muted)}
648
+ .sa-field dd{margin:0;word-break:break-word}
649
+ .sa-list{margin:0;padding-left:18px}
650
+ .sa-sources{margin:0;padding:0;list-style:none;display:grid;gap:4px}
651
+ .sa-sources li{display:flex;align-items:center;gap:6px}
652
+ .sa-sources svg{flex:none;color:var(--sa-muted)}
653
+ .sa-sources a{color:inherit;text-decoration:underline;text-underline-offset:2px;display:inline-flex;align-items:center;gap:4px}
654
+ .sa-details{margin-top:6px}
655
+ .sa-details summary{cursor:pointer;color:var(--sa-muted);font-size:12px;list-style:none;display:flex;align-items:center;gap:2px}
656
+ .sa-details summary::-webkit-details-marker{display:none}
657
+ .sa-details[open] summary svg{transform:rotate(90deg)}
658
+ .sa-details pre{margin:6px 0 0;white-space:pre-wrap;word-break:break-word;max-height:160px;overflow:auto;font-size:11.5px}
659
+ .sa-approval-title{display:flex;align-items:center;gap:6px}
660
+ .sa-approval{margin:0 12px 8px;border:1px solid var(--sa-fg);border-radius:var(--sa-radius);padding:10px;background:var(--sa-surface)}
661
+ .sa-approval pre{margin:6px 0;white-space:pre-wrap;word-break:break-word;max-height:120px;overflow:auto;font-size:12px}
662
+ .sa-approval-text{margin:6px 0}
663
+ .sa-approval .sa-fields{margin:0 0 6px}
664
+ .sa-approval .sa-row{margin-top:8px}
665
+ .sa-row{display:flex;gap:8px}
666
+ .sa-composer{display:flex;flex-direction:column;gap:4px;margin:0 12px 12px;padding:10px 10px 8px 14px;border:1px solid var(--sa-border);border-radius:18px;background:var(--sa-surface)}
667
+ .sa-input{width:100%;resize:none;border:0;background:transparent;padding:2px 0;font:inherit;color:inherit;max-height:160px}
668
+ .sa-root .sa-input:focus-visible{outline:none}
669
+ .sa-input::placeholder{color:var(--sa-muted)}
670
+ .sa-composer-bar{display:flex;align-items:center;justify-content:space-between;gap:8px}
671
+ .sa-hint{color:var(--sa-muted);font-size:12px;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
672
+ .sa-send{flex:none;margin-left:auto;display:grid;place-items:center;width:32px;height:32px;border-radius:50%;border:0;background:var(--sa-accent);color:var(--sa-accent-fg);cursor:pointer}
673
+ .sa-send:hover{opacity:.88}
674
+ .sa-send:disabled{opacity:.35;cursor:not-allowed}
675
+ .sa-send svg{width:16px;height:16px}
676
+ .sa-chatwrap{display:flex;flex-direction:column;flex:1;min-height:0}
677
+ .sa-chatwrap[hidden]{display:none}
678
+ .sa-history{gap:6px}
679
+ .sa-back{align-self:flex-start;display:inline-flex;align-items:center;gap:4px;margin-bottom:4px}
680
+ .sa-thread{display:flex;gap:6px;align-items:stretch}
681
+ .sa-thread-open{flex:1;min-width:0;text-align:left;border:1px solid var(--sa-border);background:transparent;color:inherit;border-radius:var(--sa-radius);padding:8px 10px;font:inherit;cursor:pointer;display:flex;flex-direction:column;gap:2px}
682
+ .sa-thread-open:hover{background:var(--sa-surface)}
683
+ .sa-thread-title{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
684
+ .sa-error{color:var(--sa-danger)}
685
+ @media (max-width:640px){
686
+ .sa-root .sa-panel{position:fixed;inset:0;width:auto!important;max-width:none;height:auto;border:0;border-radius:0;box-shadow:none;z-index:2147483000;
687
+ padding:env(safe-area-inset-top,0px) env(safe-area-inset-right,0px) env(safe-area-inset-bottom,0px) env(safe-area-inset-left,0px)}
688
+ .sa-resizer,.sa-mode-btn{display:none}
689
+ .sa-input{font-size:16px}
690
+ .sa-msg{max-width:100%}
691
+ .sa-user{max-width:85%}
692
+ .sa-approval{max-height:55dvh;overflow-y:auto}
693
+ }
694
+ @media (pointer:coarse){
695
+ .sa-btn{min-height:36px}
696
+ .sa-icon-btn{min-width:36px}
697
+ .sa-icon-btn svg{width:17px;height:17px}
698
+ .sa-send{width:36px;height:36px}
699
+ .sa-hint{display:none}
700
+ }
701
+ `;
702
+
703
+ // src/client/target.ts
704
+ function resolveTarget(props) {
705
+ if (props.publishableKey === void 0) {
706
+ if (!props.endpoint) throw new Error("sideagent: pass endpoint or publishableKey");
707
+ return { endpoint: props.endpoint, headers: props.headers };
708
+ }
709
+ const parts = parsePublishableKey(props.publishableKey);
710
+ if (!parts) throw new Error("sideagent: invalid publishableKey");
711
+ return {
712
+ // An explicit endpoint wins, e.g. a proxy in front of sideagent Cloud.
713
+ endpoint: props.endpoint || publishableEndpoint(parts),
714
+ headers: { ...props.headers, [PUBLISHABLE_KEY_HEADER]: props.publishableKey }
715
+ };
716
+ }
717
+
718
+ // src/client/AgentChat.tsx
719
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
720
+ var MIN_WIDTH = 300;
721
+ var DEFAULT_WIDTH = 400;
722
+ var maxWidth = () => Math.max(MIN_WIDTH, Math.min(760, Math.round(window.innerWidth * 0.7)));
723
+ var clampWidth = (w) => Math.min(maxWidth(), Math.max(MIN_WIDTH, Math.round(w)));
613
724
  function AgentChat(props) {
614
- const [mode, setModeState] = useState(() => readMode(props.mode ?? "floating"));
615
- const [localOpen, setLocalOpen] = useState(props.defaultOpen ?? false);
616
- const shared = useContext(AgentContext);
725
+ const target = useMemo2(
726
+ () => resolveTarget(props),
727
+ // eslint-disable-next-line react-hooks/exhaustive-deps
728
+ [props.endpoint, props.publishableKey, props.headers]
729
+ );
730
+ const [mode, setModeState] = useState3(() => readMode(props.mode ?? "floating"));
731
+ const [localOpen, setLocalOpen] = useState3(props.defaultOpen ?? false);
732
+ const shared = useContext3(AgentContext);
617
733
  const open = shared ? shared.open : localOpen;
618
734
  const setOpen = shared ? shared.setOpen : setLocalOpen;
619
- const [view, setView] = useState("chat");
620
- const [session, setSession] = useState({ key: 0 });
621
- const [loading, setLoading] = useState(false);
622
- const docked = useContext(DockContext) && mode === "sidebar";
623
- const [width, setWidth] = useState(() => clampWidth(Number(storeGet(WIDTH_KEY)) || DEFAULT_WIDTH));
624
- const [resizing, setResizing] = useState(false);
625
- const booted = useRef(false);
626
- const key = threadKey(props.endpoint);
627
- const opts = { endpoint: props.endpoint, credentials: props.credentials, headers: props.headers };
735
+ const [view, setView] = useState3("chat");
736
+ const [session, setSession] = useState3({ key: 0 });
737
+ const [loading, setLoading] = useState3(false);
738
+ const docked = useContext3(DockContext) && mode === "sidebar";
739
+ const [width, setWidth] = useState3(() => clampWidth(Number(storeGet(WIDTH_KEY)) || DEFAULT_WIDTH));
740
+ const [resizing, setResizing] = useState3(false);
741
+ const booted = useRef2(false);
742
+ const key = threadKey(target.endpoint);
743
+ const opts = { endpoint: target.endpoint, credentials: props.credentials, headers: target.headers };
628
744
  const setMode = useCallback((m) => {
629
745
  setModeState(m);
630
746
  storeSet(MODE_KEY, m);
@@ -649,7 +765,7 @@ function AgentChat(props) {
649
765
  }
650
766
  },
651
767
  // eslint-disable-next-line react-hooks/exhaustive-deps
652
- [props.endpoint, props.credentials, props.headers]
768
+ [target, props.credentials]
653
769
  );
654
770
  const resizeTo = useCallback((w) => {
655
771
  const next = clampWidth(w);
@@ -680,73 +796,108 @@ function AgentChat(props) {
680
796
  setView("chat");
681
797
  };
682
798
  const visible = open;
683
- useEffect(() => {
799
+ useEffect2(() => {
684
800
  if (!visible || booted.current) return;
685
801
  booted.current = true;
686
802
  const last = storeGet(key);
687
803
  if (last) void openThread(last);
688
804
  }, [visible, key, openThread]);
689
- return /* @__PURE__ */ jsxs2("div", { className: `sa-root sa-${mode}${docked ? " sa-docked" : ""}${resizing ? " sa-resizing" : ""}`, "data-theme": props.theme ?? "auto", children: [
690
- /* @__PURE__ */ jsx2("style", { children: CSS }),
691
- !open && /* @__PURE__ */ jsx2("button", { className: "sa-fab", "aria-label": "Open assistant", onClick: () => setOpen(true), children: /* @__PURE__ */ jsx2(ChatBubbleIcon, { width: 22, height: 22 }) }),
692
- visible && /* @__PURE__ */ jsxs2("section", { className: "sa-panel", "aria-label": props.title ?? "Assistant", style: docked ? { width } : void 0, children: [
693
- docked && /* @__PURE__ */ jsx2(
694
- "div",
695
- {
696
- className: "sa-resizer",
697
- role: "separator",
698
- "aria-orientation": "vertical",
699
- "aria-label": "Resize assistant",
700
- "aria-valuemin": MIN_WIDTH,
701
- "aria-valuemax": maxWidth(),
702
- "aria-valuenow": width,
703
- tabIndex: 0,
704
- onPointerDown: onHandleDown,
705
- onPointerMove: onHandleMove,
706
- onPointerUp: onHandleUp,
707
- onPointerCancel: onHandleUp,
708
- onKeyDown: onHandleKey
709
- }
710
- ),
711
- /* @__PURE__ */ jsxs2("header", { className: "sa-header", children: [
712
- /* @__PURE__ */ jsx2("span", { className: "sa-title", children: props.title ?? "Assistant" }),
713
- props.allowModeSwitch !== false && /* @__PURE__ */ jsx2(
714
- "button",
715
- {
716
- className: "sa-btn sa-icon-btn",
717
- onClick: () => setMode(mode === "floating" ? "sidebar" : "floating"),
718
- title: mode === "floating" ? "Dock to sidebar" : "Float",
719
- "aria-label": mode === "floating" ? "Dock to sidebar" : "Float",
720
- children: mode === "floating" ? /* @__PURE__ */ jsx2(ViewVerticalIcon, {}) : /* @__PURE__ */ jsx2(OpenInNewWindowIcon, {})
721
- }
722
- ),
723
- /* @__PURE__ */ jsx2("button", { className: "sa-btn sa-icon-btn", "aria-pressed": view === "history", onClick: () => setView((v) => v === "history" ? "chat" : "history"), title: "History", "aria-label": "History", children: /* @__PURE__ */ jsx2(CounterClockwiseClockIcon, {}) }),
724
- /* @__PURE__ */ jsx2("button", { className: "sa-btn sa-icon-btn", onClick: newChat, title: "New chat", "aria-label": "New chat", children: /* @__PURE__ */ jsx2(PlusIcon, {}) }),
725
- /* @__PURE__ */ jsx2("button", { className: "sa-btn sa-icon-btn", onClick: () => setOpen(false), "aria-label": "Close", title: "Close", children: /* @__PURE__ */ jsx2(Cross2Icon, {}) })
726
- ] }),
727
- view === "history" && /* @__PURE__ */ jsx2(
728
- HistoryList,
729
- {
730
- ...opts,
731
- onOpen: (id) => void openThread(id),
732
- onClose: () => setView("chat"),
733
- onDeleted: (id) => {
734
- if (id === session.threadId) newChat();
735
- }
736
- }
737
- ),
738
- /* @__PURE__ */ jsx2("div", { className: "sa-chatwrap", hidden: view === "history", children: loading ? /* @__PURE__ */ jsx2("div", { className: "sa-viewport", children: /* @__PURE__ */ jsx2("div", { className: "sa-more", children: "Loading\u2026" }) }) : /* @__PURE__ */ jsx2(
739
- Chat,
740
- {
741
- ...props,
742
- session,
743
- onThread: (id) => storeSet(key, id),
744
- onReload: () => session.threadId && void openThread(session.threadId)
745
- },
746
- session.key
747
- ) })
748
- ] })
749
- ] });
805
+ return /* @__PURE__ */ jsxs6(
806
+ "div",
807
+ {
808
+ className: `sa-root sa-${mode}${docked ? " sa-docked" : ""}${resizing ? " sa-resizing" : ""}`,
809
+ "data-theme": props.theme ?? "auto",
810
+ children: [
811
+ /* @__PURE__ */ jsx7("style", { children: CSS }),
812
+ !open && /* @__PURE__ */ jsx7("button", { className: "sa-fab", "aria-label": "Open assistant", onClick: () => setOpen(true), children: /* @__PURE__ */ jsx7(ChatBubbleIcon, { width: 22, height: 22 }) }),
813
+ visible && /* @__PURE__ */ jsxs6("section", { className: "sa-panel", "aria-label": props.title ?? "Assistant", style: docked ? { width } : void 0, children: [
814
+ docked && /* @__PURE__ */ jsx7(
815
+ "div",
816
+ {
817
+ className: "sa-resizer",
818
+ role: "separator",
819
+ "aria-orientation": "vertical",
820
+ "aria-label": "Resize assistant",
821
+ "aria-valuemin": MIN_WIDTH,
822
+ "aria-valuemax": maxWidth(),
823
+ "aria-valuenow": width,
824
+ tabIndex: 0,
825
+ onPointerDown: onHandleDown,
826
+ onPointerMove: onHandleMove,
827
+ onPointerUp: onHandleUp,
828
+ onPointerCancel: onHandleUp,
829
+ onKeyDown: onHandleKey
830
+ }
831
+ ),
832
+ /* @__PURE__ */ jsxs6("header", { className: "sa-header", children: [
833
+ /* @__PURE__ */ jsx7("span", { className: "sa-title", children: props.title ?? "Assistant" }),
834
+ props.allowModeSwitch !== false && /* @__PURE__ */ jsx7(
835
+ "button",
836
+ {
837
+ className: "sa-btn sa-icon-btn sa-mode-btn",
838
+ onClick: () => setMode(mode === "floating" ? "sidebar" : "floating"),
839
+ title: mode === "floating" ? "Dock to sidebar" : "Float",
840
+ "aria-label": mode === "floating" ? "Dock to sidebar" : "Float",
841
+ children: mode === "floating" ? /* @__PURE__ */ jsx7(ViewVerticalIcon, {}) : /* @__PURE__ */ jsx7(OpenInNewWindowIcon, {})
842
+ }
843
+ ),
844
+ /* @__PURE__ */ jsx7(
845
+ "button",
846
+ {
847
+ className: "sa-btn sa-icon-btn",
848
+ "aria-pressed": view === "history",
849
+ onClick: () => setView((v) => v === "history" ? "chat" : "history"),
850
+ title: "History",
851
+ "aria-label": "History",
852
+ children: /* @__PURE__ */ jsx7(CounterClockwiseClockIcon, {})
853
+ }
854
+ ),
855
+ /* @__PURE__ */ jsx7("button", { className: "sa-btn sa-icon-btn", onClick: newChat, title: "New chat", "aria-label": "New chat", children: /* @__PURE__ */ jsx7(PlusIcon, {}) }),
856
+ /* @__PURE__ */ jsx7("button", { className: "sa-btn sa-icon-btn", onClick: () => setOpen(false), "aria-label": "Close", title: "Close", children: /* @__PURE__ */ jsx7(Cross2Icon, {}) })
857
+ ] }),
858
+ view === "history" && /* @__PURE__ */ jsx7(
859
+ HistoryList,
860
+ {
861
+ ...opts,
862
+ onOpen: (id) => void openThread(id),
863
+ onClose: () => setView("chat"),
864
+ onDeleted: (id) => {
865
+ if (id === session.threadId) newChat();
866
+ }
867
+ }
868
+ ),
869
+ /* @__PURE__ */ jsx7("div", { className: "sa-chatwrap", hidden: view === "history", children: loading ? /* @__PURE__ */ jsx7("div", { className: "sa-viewport", children: /* @__PURE__ */ jsx7("div", { className: "sa-more", children: "Loading\u2026" }) }) : /* @__PURE__ */ jsx7(
870
+ Chat,
871
+ {
872
+ ...props,
873
+ ...target,
874
+ session,
875
+ onThread: (id) => storeSet(key, id),
876
+ onReload: () => session.threadId && void openThread(session.threadId)
877
+ },
878
+ session.key
879
+ ) })
880
+ ] })
881
+ ]
882
+ }
883
+ );
884
+ }
885
+
886
+ // src/client/AgentProvider.tsx
887
+ import { useMemo as useMemo3, useState as useState4 } from "react";
888
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
889
+ function AgentProvider({
890
+ children,
891
+ className,
892
+ style,
893
+ ...chat
894
+ }) {
895
+ const [open, setOpen] = useState4(chat.defaultOpen ?? false);
896
+ const controls = useMemo3(() => ({ open, setOpen }), [open]);
897
+ return /* @__PURE__ */ jsx8(AgentContext.Provider, { value: controls, children: /* @__PURE__ */ jsx8(DockContext.Provider, { value: true, children: /* @__PURE__ */ jsxs7("div", { className: `sa-shell${className ? ` ${className}` : ""}`, style, children: [
898
+ /* @__PURE__ */ jsx8("div", { className: "sa-shell-main", children }),
899
+ /* @__PURE__ */ jsx8(AgentChat, { ...chat })
900
+ ] }) }) });
750
901
  }
751
902
  export {
752
903
  AgentChat,