@contractspec/module.ai-chat 4.0.3 → 4.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.
Files changed (51) hide show
  1. package/README.md +130 -10
  2. package/dist/adapters/ai-sdk-bundle-adapter.d.ts +18 -0
  3. package/dist/adapters/index.d.ts +4 -0
  4. package/dist/browser/core/index.js +1138 -21
  5. package/dist/browser/index.js +2816 -651
  6. package/dist/browser/presentation/components/index.js +3143 -358
  7. package/dist/browser/presentation/hooks/index.js +961 -43
  8. package/dist/browser/presentation/index.js +2784 -666
  9. package/dist/core/agent-adapter.d.ts +53 -0
  10. package/dist/core/agent-tools-adapter.d.ts +12 -0
  11. package/dist/core/chat-service.d.ts +49 -1
  12. package/dist/core/contracts-context.d.ts +46 -0
  13. package/dist/core/contracts-context.test.d.ts +1 -0
  14. package/dist/core/conversation-store.d.ts +16 -2
  15. package/dist/core/create-chat-route.d.ts +3 -0
  16. package/dist/core/export-formatters.d.ts +29 -0
  17. package/dist/core/export-formatters.test.d.ts +1 -0
  18. package/dist/core/index.d.ts +8 -0
  19. package/dist/core/index.js +1138 -21
  20. package/dist/core/local-storage-conversation-store.d.ts +33 -0
  21. package/dist/core/message-types.d.ts +6 -0
  22. package/dist/core/surface-planner-tools.d.ts +23 -0
  23. package/dist/core/surface-planner-tools.test.d.ts +1 -0
  24. package/dist/core/thinking-levels.d.ts +38 -0
  25. package/dist/core/thinking-levels.test.d.ts +1 -0
  26. package/dist/core/workflow-tools.d.ts +18 -0
  27. package/dist/core/workflow-tools.test.d.ts +1 -0
  28. package/dist/index.d.ts +4 -2
  29. package/dist/index.js +2816 -651
  30. package/dist/node/core/index.js +1138 -21
  31. package/dist/node/index.js +2816 -651
  32. package/dist/node/presentation/components/index.js +3143 -358
  33. package/dist/node/presentation/hooks/index.js +961 -43
  34. package/dist/node/presentation/index.js +2787 -669
  35. package/dist/presentation/components/ChatContainer.d.ts +3 -1
  36. package/dist/presentation/components/ChatExportToolbar.d.ts +25 -0
  37. package/dist/presentation/components/ChatMessage.d.ts +16 -1
  38. package/dist/presentation/components/ChatSidebar.d.ts +26 -0
  39. package/dist/presentation/components/ChatWithExport.d.ts +34 -0
  40. package/dist/presentation/components/ChatWithSidebar.d.ts +19 -0
  41. package/dist/presentation/components/ThinkingLevelPicker.d.ts +16 -0
  42. package/dist/presentation/components/ToolResultRenderer.d.ts +33 -0
  43. package/dist/presentation/components/index.d.ts +6 -0
  44. package/dist/presentation/components/index.js +3143 -358
  45. package/dist/presentation/hooks/index.d.ts +2 -0
  46. package/dist/presentation/hooks/index.js +961 -43
  47. package/dist/presentation/hooks/useChat.d.ts +44 -2
  48. package/dist/presentation/hooks/useConversations.d.ts +18 -0
  49. package/dist/presentation/hooks/useMessageSelection.d.ts +13 -0
  50. package/dist/presentation/index.js +2787 -669
  51. package/package.json +14 -18
@@ -5,12 +5,13 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
5
5
  import * as React from "react";
6
6
  import { ScrollArea } from "@contractspec/lib.ui-kit-web/ui/scroll-area";
7
7
  import { cn } from "@contractspec/lib.ui-kit-web/ui/utils";
8
- import { jsxDEV } from "react/jsx-dev-runtime";
8
+ import { jsx, jsxs } from "react/jsx-runtime";
9
9
  "use client";
10
10
  function ChatContainer({
11
11
  children,
12
12
  className,
13
- showScrollButton = true
13
+ showScrollButton = true,
14
+ headerContent
14
15
  }) {
15
16
  const scrollRef = React.useRef(null);
16
17
  const [showScrollDown, setShowScrollDown] = React.useState(false);
@@ -37,24 +38,28 @@ function ChatContainer({
37
38
  });
38
39
  }
39
40
  }, []);
40
- return /* @__PURE__ */ jsxDEV("div", {
41
+ return /* @__PURE__ */ jsxs("div", {
41
42
  className: cn("relative flex flex-1 flex-col", className),
42
43
  children: [
43
- /* @__PURE__ */ jsxDEV(ScrollArea, {
44
+ headerContent && /* @__PURE__ */ jsx("div", {
45
+ className: "border-border flex shrink-0 items-center justify-end gap-2 border-b px-4 py-2",
46
+ children: headerContent
47
+ }),
48
+ /* @__PURE__ */ jsx(ScrollArea, {
44
49
  ref: scrollRef,
45
50
  className: "flex-1",
46
51
  onScroll: handleScroll,
47
- children: /* @__PURE__ */ jsxDEV("div", {
52
+ children: /* @__PURE__ */ jsx("div", {
48
53
  className: "flex flex-col gap-4 p-4",
49
54
  children
50
- }, undefined, false, undefined, this)
51
- }, undefined, false, undefined, this),
52
- showScrollButton && showScrollDown && /* @__PURE__ */ jsxDEV("button", {
55
+ })
56
+ }),
57
+ showScrollButton && showScrollDown && /* @__PURE__ */ jsxs("button", {
53
58
  onClick: scrollToBottom,
54
59
  className: cn("absolute bottom-4 left-1/2 -translate-x-1/2", "bg-primary text-primary-foreground", "rounded-full px-3 py-1.5 text-sm font-medium shadow-lg", "hover:bg-primary/90 transition-colors", "flex items-center gap-1.5"),
55
60
  "aria-label": "Scroll to bottom",
56
61
  children: [
57
- /* @__PURE__ */ jsxDEV("svg", {
62
+ /* @__PURE__ */ jsx("svg", {
58
63
  xmlns: "http://www.w3.org/2000/svg",
59
64
  width: "16",
60
65
  height: "16",
@@ -64,15 +69,15 @@ function ChatContainer({
64
69
  strokeWidth: "2",
65
70
  strokeLinecap: "round",
66
71
  strokeLinejoin: "round",
67
- children: /* @__PURE__ */ jsxDEV("path", {
72
+ children: /* @__PURE__ */ jsx("path", {
68
73
  d: "m6 9 6 6 6-6"
69
- }, undefined, false, undefined, this)
70
- }, undefined, false, undefined, this),
74
+ })
75
+ }),
71
76
  "New messages"
72
77
  ]
73
- }, undefined, true, undefined, this)
78
+ })
74
79
  ]
75
- }, undefined, true, undefined, this);
80
+ });
76
81
  }
77
82
  // src/presentation/components/ChatMessage.tsx
78
83
  import * as React3 from "react";
@@ -86,16 +91,19 @@ import {
86
91
  Copy as Copy2,
87
92
  Check as Check2,
88
93
  ExternalLink,
89
- Wrench
94
+ Wrench,
95
+ Pencil,
96
+ X
90
97
  } from "lucide-react";
91
98
  import { Button as Button2 } from "@contractspec/lib.design-system";
99
+ import { Checkbox } from "@contractspec/lib.ui-kit-web/ui/checkbox";
92
100
 
93
101
  // src/presentation/components/CodePreview.tsx
94
102
  import * as React2 from "react";
95
103
  import { cn as cn2 } from "@contractspec/lib.ui-kit-web/ui/utils";
96
104
  import { Button } from "@contractspec/lib.design-system";
97
105
  import { Copy, Check, Play, Download } from "lucide-react";
98
- import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
106
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
99
107
  "use client";
100
108
  var LANGUAGE_NAMES = {
101
109
  ts: "TypeScript",
@@ -148,93 +156,147 @@ function CodePreview({
148
156
  document.body.removeChild(a);
149
157
  URL.revokeObjectURL(url);
150
158
  }, [code, filename, language]);
151
- return /* @__PURE__ */ jsxDEV2("div", {
159
+ return /* @__PURE__ */ jsxs2("div", {
152
160
  className: cn2("overflow-hidden rounded-lg border", "bg-muted/50", className),
153
161
  children: [
154
- /* @__PURE__ */ jsxDEV2("div", {
162
+ /* @__PURE__ */ jsxs2("div", {
155
163
  className: cn2("flex items-center justify-between px-3 py-1.5", "bg-muted/80 border-b"),
156
164
  children: [
157
- /* @__PURE__ */ jsxDEV2("div", {
165
+ /* @__PURE__ */ jsxs2("div", {
158
166
  className: "flex items-center gap-2 text-sm",
159
167
  children: [
160
- filename && /* @__PURE__ */ jsxDEV2("span", {
168
+ filename && /* @__PURE__ */ jsx2("span", {
161
169
  className: "text-foreground font-mono",
162
170
  children: filename
163
- }, undefined, false, undefined, this),
164
- /* @__PURE__ */ jsxDEV2("span", {
171
+ }),
172
+ /* @__PURE__ */ jsx2("span", {
165
173
  className: "text-muted-foreground",
166
174
  children: displayLanguage
167
- }, undefined, false, undefined, this)
175
+ })
168
176
  ]
169
- }, undefined, true, undefined, this),
170
- /* @__PURE__ */ jsxDEV2("div", {
177
+ }),
178
+ /* @__PURE__ */ jsxs2("div", {
171
179
  className: "flex items-center gap-1",
172
180
  children: [
173
- showExecute && onExecute && /* @__PURE__ */ jsxDEV2(Button, {
181
+ showExecute && onExecute && /* @__PURE__ */ jsx2(Button, {
174
182
  variant: "ghost",
175
183
  size: "sm",
176
184
  onPress: () => onExecute(code),
177
185
  className: "h-7 w-7 p-0",
178
186
  "aria-label": "Execute code",
179
- children: /* @__PURE__ */ jsxDEV2(Play, {
187
+ children: /* @__PURE__ */ jsx2(Play, {
180
188
  className: "h-3.5 w-3.5"
181
- }, undefined, false, undefined, this)
182
- }, undefined, false, undefined, this),
183
- showDownload && /* @__PURE__ */ jsxDEV2(Button, {
189
+ })
190
+ }),
191
+ showDownload && /* @__PURE__ */ jsx2(Button, {
184
192
  variant: "ghost",
185
193
  size: "sm",
186
194
  onPress: handleDownload,
187
195
  className: "h-7 w-7 p-0",
188
196
  "aria-label": "Download code",
189
- children: /* @__PURE__ */ jsxDEV2(Download, {
197
+ children: /* @__PURE__ */ jsx2(Download, {
190
198
  className: "h-3.5 w-3.5"
191
- }, undefined, false, undefined, this)
192
- }, undefined, false, undefined, this),
193
- showCopy && /* @__PURE__ */ jsxDEV2(Button, {
199
+ })
200
+ }),
201
+ showCopy && /* @__PURE__ */ jsx2(Button, {
194
202
  variant: "ghost",
195
203
  size: "sm",
196
204
  onPress: handleCopy,
197
205
  className: "h-7 w-7 p-0",
198
206
  "aria-label": copied ? "Copied" : "Copy code",
199
- children: copied ? /* @__PURE__ */ jsxDEV2(Check, {
207
+ children: copied ? /* @__PURE__ */ jsx2(Check, {
200
208
  className: "h-3.5 w-3.5 text-green-500"
201
- }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV2(Copy, {
209
+ }) : /* @__PURE__ */ jsx2(Copy, {
202
210
  className: "h-3.5 w-3.5"
203
- }, undefined, false, undefined, this)
204
- }, undefined, false, undefined, this)
211
+ })
212
+ })
205
213
  ]
206
- }, undefined, true, undefined, this)
214
+ })
207
215
  ]
208
- }, undefined, true, undefined, this),
209
- /* @__PURE__ */ jsxDEV2("div", {
216
+ }),
217
+ /* @__PURE__ */ jsx2("div", {
210
218
  className: "overflow-auto",
211
219
  style: { maxHeight },
212
- children: /* @__PURE__ */ jsxDEV2("pre", {
220
+ children: /* @__PURE__ */ jsx2("pre", {
213
221
  className: "p-3",
214
- children: /* @__PURE__ */ jsxDEV2("code", {
222
+ children: /* @__PURE__ */ jsx2("code", {
215
223
  className: "text-sm",
216
- children: lines.map((line, i) => /* @__PURE__ */ jsxDEV2("div", {
224
+ children: lines.map((line, i) => /* @__PURE__ */ jsxs2("div", {
217
225
  className: "flex",
218
226
  children: [
219
- /* @__PURE__ */ jsxDEV2("span", {
227
+ /* @__PURE__ */ jsx2("span", {
220
228
  className: "text-muted-foreground mr-4 w-8 text-right select-none",
221
229
  children: i + 1
222
- }, undefined, false, undefined, this),
223
- /* @__PURE__ */ jsxDEV2("span", {
230
+ }),
231
+ /* @__PURE__ */ jsx2("span", {
224
232
  className: "flex-1",
225
233
  children: line || " "
226
- }, undefined, false, undefined, this)
234
+ })
227
235
  ]
228
- }, i, true, undefined, this))
229
- }, undefined, false, undefined, this)
230
- }, undefined, false, undefined, this)
231
- }, undefined, false, undefined, this)
236
+ }, i))
237
+ })
238
+ })
239
+ })
232
240
  ]
233
- }, undefined, true, undefined, this);
241
+ });
242
+ }
243
+
244
+ // src/presentation/components/ToolResultRenderer.tsx
245
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
246
+ "use client";
247
+ function isPresentationToolResult(result) {
248
+ return typeof result === "object" && result !== null && "presentationKey" in result && typeof result.presentationKey === "string";
249
+ }
250
+ function isFormToolResult(result) {
251
+ return typeof result === "object" && result !== null && "formKey" in result && typeof result.formKey === "string";
252
+ }
253
+ function ToolResultRenderer({
254
+ toolName,
255
+ result,
256
+ presentationRenderer,
257
+ formRenderer,
258
+ showRawFallback = true
259
+ }) {
260
+ if (result === undefined || result === null) {
261
+ return null;
262
+ }
263
+ if (isPresentationToolResult(result) && presentationRenderer) {
264
+ const rendered = presentationRenderer(result.presentationKey, result.data);
265
+ if (rendered != null) {
266
+ return /* @__PURE__ */ jsx3("div", {
267
+ className: "mt-2 rounded-md border border-border bg-background/50 p-3",
268
+ children: rendered
269
+ });
270
+ }
271
+ }
272
+ if (isFormToolResult(result) && formRenderer) {
273
+ const rendered = formRenderer(result.formKey, result.defaultValues);
274
+ if (rendered != null) {
275
+ return /* @__PURE__ */ jsx3("div", {
276
+ className: "mt-2 rounded-md border border-border bg-background/50 p-3",
277
+ children: rendered
278
+ });
279
+ }
280
+ }
281
+ if (!showRawFallback) {
282
+ return null;
283
+ }
284
+ return /* @__PURE__ */ jsxs3("div", {
285
+ children: [
286
+ /* @__PURE__ */ jsx3("span", {
287
+ className: "text-muted-foreground font-medium",
288
+ children: "Output:"
289
+ }),
290
+ /* @__PURE__ */ jsx3("pre", {
291
+ className: "bg-background mt-1 overflow-x-auto rounded p-2 text-xs",
292
+ children: typeof result === "object" ? JSON.stringify(result, null, 2) : String(result)
293
+ })
294
+ ]
295
+ });
234
296
  }
235
297
 
236
298
  // src/presentation/components/ChatMessage.tsx
237
- import { jsxDEV as jsxDEV3, Fragment } from "react/jsx-dev-runtime";
299
+ import { jsx as jsx4, jsxs as jsxs4, Fragment } from "react/jsx-runtime";
238
300
  "use client";
239
301
  function extractCodeBlocks(content) {
240
302
  const codeBlockRegex = /```(\w+)?\n([\s\S]*?)```/g;
@@ -257,33 +319,33 @@ function renderInlineMarkdown(text) {
257
319
  let key = 0;
258
320
  while ((match = linkRegex.exec(text)) !== null) {
259
321
  if (match.index > lastIndex) {
260
- parts.push(/* @__PURE__ */ jsxDEV3("span", {
322
+ parts.push(/* @__PURE__ */ jsx4("span", {
261
323
  children: text.slice(lastIndex, match.index)
262
- }, key++, false, undefined, this));
324
+ }, key++));
263
325
  }
264
- parts.push(/* @__PURE__ */ jsxDEV3("a", {
326
+ parts.push(/* @__PURE__ */ jsx4("a", {
265
327
  href: match[2],
266
328
  target: "_blank",
267
329
  rel: "noopener noreferrer",
268
330
  className: "text-primary underline hover:no-underline",
269
331
  children: match[1]
270
- }, key++, false, undefined, this));
332
+ }, key++));
271
333
  lastIndex = match.index + match[0].length;
272
334
  }
273
335
  if (lastIndex < text.length) {
274
- parts.push(/* @__PURE__ */ jsxDEV3("span", {
336
+ parts.push(/* @__PURE__ */ jsx4("span", {
275
337
  children: text.slice(lastIndex)
276
- }, key++, false, undefined, this));
338
+ }, key++));
277
339
  }
278
340
  return parts.length > 0 ? parts : [text];
279
341
  }
280
342
  function MessageContent({ content }) {
281
343
  const codeBlocks = extractCodeBlocks(content);
282
344
  if (codeBlocks.length === 0) {
283
- return /* @__PURE__ */ jsxDEV3("p", {
345
+ return /* @__PURE__ */ jsx4("p", {
284
346
  className: "whitespace-pre-wrap",
285
347
  children: renderInlineMarkdown(content)
286
- }, undefined, false, undefined, this);
348
+ });
287
349
  }
288
350
  let remaining = content;
289
351
  const parts = [];
@@ -291,33 +353,40 @@ function MessageContent({ content }) {
291
353
  for (const block of codeBlocks) {
292
354
  const [before, after] = remaining.split(block.raw);
293
355
  if (before) {
294
- parts.push(/* @__PURE__ */ jsxDEV3("p", {
356
+ parts.push(/* @__PURE__ */ jsx4("p", {
295
357
  className: "whitespace-pre-wrap",
296
358
  children: renderInlineMarkdown(before.trim())
297
- }, key++, false, undefined, this));
359
+ }, key++));
298
360
  }
299
- parts.push(/* @__PURE__ */ jsxDEV3(CodePreview, {
361
+ parts.push(/* @__PURE__ */ jsx4(CodePreview, {
300
362
  code: block.code,
301
363
  language: block.language,
302
364
  className: "my-2"
303
- }, key++, false, undefined, this));
365
+ }, key++));
304
366
  remaining = after ?? "";
305
367
  }
306
368
  if (remaining.trim()) {
307
- parts.push(/* @__PURE__ */ jsxDEV3("p", {
369
+ parts.push(/* @__PURE__ */ jsx4("p", {
308
370
  className: "whitespace-pre-wrap",
309
371
  children: renderInlineMarkdown(remaining.trim())
310
- }, key++, false, undefined, this));
372
+ }, key++));
311
373
  }
312
- return /* @__PURE__ */ jsxDEV3(Fragment, {
374
+ return /* @__PURE__ */ jsx4(Fragment, {
313
375
  children: parts
314
- }, undefined, false, undefined, this);
376
+ });
315
377
  }
316
378
  function ChatMessage({
317
379
  message,
318
380
  className,
319
381
  showCopy = true,
320
- showAvatar = true
382
+ showAvatar = true,
383
+ selectable = false,
384
+ selected = false,
385
+ onSelect,
386
+ editable = false,
387
+ onEdit,
388
+ presentationRenderer,
389
+ formRenderer
321
390
  }) {
322
391
  const [copied, setCopied] = React3.useState(false);
323
392
  const isUser = message.role === "user";
@@ -328,185 +397,262 @@ function ChatMessage({
328
397
  setCopied(true);
329
398
  setTimeout(() => setCopied(false), 2000);
330
399
  }, [message.content]);
331
- return /* @__PURE__ */ jsxDEV3("div", {
400
+ const handleSelectChange = React3.useCallback((checked) => {
401
+ if (checked !== "indeterminate")
402
+ onSelect?.(message.id);
403
+ }, [message.id, onSelect]);
404
+ const [isEditing, setIsEditing] = React3.useState(false);
405
+ const [editContent, setEditContent] = React3.useState(message.content);
406
+ React3.useEffect(() => {
407
+ setEditContent(message.content);
408
+ }, [message.content]);
409
+ const handleStartEdit = React3.useCallback(() => {
410
+ setEditContent(message.content);
411
+ setIsEditing(true);
412
+ }, [message.content]);
413
+ const handleSaveEdit = React3.useCallback(async () => {
414
+ const trimmed = editContent.trim();
415
+ if (trimmed !== message.content) {
416
+ await onEdit?.(message.id, trimmed);
417
+ }
418
+ setIsEditing(false);
419
+ }, [editContent, message.id, message.content, onEdit]);
420
+ const handleCancelEdit = React3.useCallback(() => {
421
+ setEditContent(message.content);
422
+ setIsEditing(false);
423
+ }, [message.content]);
424
+ return /* @__PURE__ */ jsxs4("div", {
332
425
  className: cn3("group flex gap-3", isUser && "flex-row-reverse", className),
333
426
  children: [
334
- showAvatar && /* @__PURE__ */ jsxDEV3(Avatar, {
427
+ selectable && /* @__PURE__ */ jsx4("div", {
428
+ className: cn3("flex shrink-0 items-start pt-1", "opacity-0 transition-opacity group-hover:opacity-100"),
429
+ children: /* @__PURE__ */ jsx4(Checkbox, {
430
+ checked: selected,
431
+ onCheckedChange: handleSelectChange,
432
+ "aria-label": selected ? "Deselect message" : "Select message"
433
+ })
434
+ }),
435
+ showAvatar && /* @__PURE__ */ jsx4(Avatar, {
335
436
  className: "h-8 w-8 shrink-0",
336
- children: /* @__PURE__ */ jsxDEV3(AvatarFallback, {
437
+ children: /* @__PURE__ */ jsx4(AvatarFallback, {
337
438
  className: cn3(isUser ? "bg-primary text-primary-foreground" : "bg-muted"),
338
- children: isUser ? /* @__PURE__ */ jsxDEV3(User, {
439
+ children: isUser ? /* @__PURE__ */ jsx4(User, {
339
440
  className: "h-4 w-4"
340
- }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV3(Bot, {
441
+ }) : /* @__PURE__ */ jsx4(Bot, {
341
442
  className: "h-4 w-4"
342
- }, undefined, false, undefined, this)
343
- }, undefined, false, undefined, this)
344
- }, undefined, false, undefined, this),
345
- /* @__PURE__ */ jsxDEV3("div", {
443
+ })
444
+ })
445
+ }),
446
+ /* @__PURE__ */ jsxs4("div", {
346
447
  className: cn3("flex max-w-[80%] flex-col gap-1", isUser && "items-end"),
347
448
  children: [
348
- /* @__PURE__ */ jsxDEV3("div", {
449
+ /* @__PURE__ */ jsx4("div", {
349
450
  className: cn3("rounded-2xl px-4 py-2", isUser ? "bg-primary text-primary-foreground" : "bg-muted text-foreground", isError && "border-destructive bg-destructive/10 border"),
350
- children: isError && message.error ? /* @__PURE__ */ jsxDEV3("div", {
451
+ children: isError && message.error ? /* @__PURE__ */ jsxs4("div", {
351
452
  className: "flex items-start gap-2",
352
453
  children: [
353
- /* @__PURE__ */ jsxDEV3(AlertCircle, {
454
+ /* @__PURE__ */ jsx4(AlertCircle, {
354
455
  className: "text-destructive mt-0.5 h-4 w-4 shrink-0"
355
- }, undefined, false, undefined, this),
356
- /* @__PURE__ */ jsxDEV3("div", {
456
+ }),
457
+ /* @__PURE__ */ jsxs4("div", {
357
458
  children: [
358
- /* @__PURE__ */ jsxDEV3("p", {
459
+ /* @__PURE__ */ jsx4("p", {
359
460
  className: "text-destructive font-medium",
360
461
  children: message.error.code
361
- }, undefined, false, undefined, this),
362
- /* @__PURE__ */ jsxDEV3("p", {
462
+ }),
463
+ /* @__PURE__ */ jsx4("p", {
363
464
  className: "text-muted-foreground text-sm",
364
465
  children: message.error.message
365
- }, undefined, false, undefined, this)
466
+ })
467
+ ]
468
+ })
469
+ ]
470
+ }) : isEditing ? /* @__PURE__ */ jsxs4("div", {
471
+ className: "flex flex-col gap-2",
472
+ children: [
473
+ /* @__PURE__ */ jsx4("textarea", {
474
+ value: editContent,
475
+ onChange: (e) => setEditContent(e.target.value),
476
+ className: "bg-background/50 min-h-[80px] w-full resize-y rounded-md border px-3 py-2 text-sm",
477
+ rows: 4,
478
+ autoFocus: true
479
+ }),
480
+ /* @__PURE__ */ jsxs4("div", {
481
+ className: "flex gap-2",
482
+ children: [
483
+ /* @__PURE__ */ jsxs4(Button2, {
484
+ variant: "default",
485
+ size: "sm",
486
+ onPress: handleSaveEdit,
487
+ "aria-label": "Save edit",
488
+ children: [
489
+ /* @__PURE__ */ jsx4(Check2, {
490
+ className: "h-3 w-3"
491
+ }),
492
+ "Save"
493
+ ]
494
+ }),
495
+ /* @__PURE__ */ jsxs4(Button2, {
496
+ variant: "ghost",
497
+ size: "sm",
498
+ onPress: handleCancelEdit,
499
+ "aria-label": "Cancel edit",
500
+ children: [
501
+ /* @__PURE__ */ jsx4(X, {
502
+ className: "h-3 w-3"
503
+ }),
504
+ "Cancel"
505
+ ]
506
+ })
366
507
  ]
367
- }, undefined, true, undefined, this)
508
+ })
368
509
  ]
369
- }, undefined, true, undefined, this) : isStreaming && !message.content ? /* @__PURE__ */ jsxDEV3("div", {
510
+ }) : isStreaming && !message.content ? /* @__PURE__ */ jsxs4("div", {
370
511
  className: "flex flex-col gap-2",
371
512
  children: [
372
- /* @__PURE__ */ jsxDEV3(Skeleton, {
513
+ /* @__PURE__ */ jsx4(Skeleton, {
373
514
  className: "h-4 w-48"
374
- }, undefined, false, undefined, this),
375
- /* @__PURE__ */ jsxDEV3(Skeleton, {
515
+ }),
516
+ /* @__PURE__ */ jsx4(Skeleton, {
376
517
  className: "h-4 w-32"
377
- }, undefined, false, undefined, this)
518
+ })
378
519
  ]
379
- }, undefined, true, undefined, this) : /* @__PURE__ */ jsxDEV3(MessageContent, {
520
+ }) : /* @__PURE__ */ jsx4(MessageContent, {
380
521
  content: message.content
381
- }, undefined, false, undefined, this)
382
- }, undefined, false, undefined, this),
383
- /* @__PURE__ */ jsxDEV3("div", {
522
+ })
523
+ }),
524
+ /* @__PURE__ */ jsxs4("div", {
384
525
  className: cn3("flex items-center gap-2 text-xs", "text-muted-foreground opacity-0 transition-opacity", "group-hover:opacity-100"),
385
526
  children: [
386
- /* @__PURE__ */ jsxDEV3("span", {
527
+ /* @__PURE__ */ jsx4("span", {
387
528
  children: new Date(message.createdAt).toLocaleTimeString([], {
388
529
  hour: "2-digit",
389
530
  minute: "2-digit"
390
531
  })
391
- }, undefined, false, undefined, this),
392
- message.usage && /* @__PURE__ */ jsxDEV3("span", {
532
+ }),
533
+ message.usage && /* @__PURE__ */ jsxs4("span", {
393
534
  children: [
394
535
  message.usage.inputTokens + message.usage.outputTokens,
395
536
  " tokens"
396
537
  ]
397
- }, undefined, true, undefined, this),
398
- showCopy && !isUser && message.content && /* @__PURE__ */ jsxDEV3(Button2, {
538
+ }),
539
+ showCopy && !isUser && message.content && /* @__PURE__ */ jsx4(Button2, {
399
540
  variant: "ghost",
400
541
  size: "sm",
401
542
  className: "h-6 w-6 p-0",
402
543
  onPress: handleCopy,
403
544
  "aria-label": copied ? "Copied" : "Copy message",
404
- children: copied ? /* @__PURE__ */ jsxDEV3(Check2, {
545
+ children: copied ? /* @__PURE__ */ jsx4(Check2, {
405
546
  className: "h-3 w-3"
406
- }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV3(Copy2, {
547
+ }) : /* @__PURE__ */ jsx4(Copy2, {
407
548
  className: "h-3 w-3"
408
- }, undefined, false, undefined, this)
409
- }, undefined, false, undefined, this)
549
+ })
550
+ }),
551
+ editable && isUser && !isEditing && /* @__PURE__ */ jsx4(Button2, {
552
+ variant: "ghost",
553
+ size: "sm",
554
+ className: "h-6 w-6 p-0",
555
+ onPress: handleStartEdit,
556
+ "aria-label": "Edit message",
557
+ children: /* @__PURE__ */ jsx4(Pencil, {
558
+ className: "h-3 w-3"
559
+ })
560
+ })
410
561
  ]
411
- }, undefined, true, undefined, this),
412
- message.reasoning && /* @__PURE__ */ jsxDEV3("details", {
562
+ }),
563
+ message.reasoning && /* @__PURE__ */ jsxs4("details", {
413
564
  className: "text-muted-foreground mt-2 text-sm",
414
565
  children: [
415
- /* @__PURE__ */ jsxDEV3("summary", {
566
+ /* @__PURE__ */ jsx4("summary", {
416
567
  className: "cursor-pointer hover:underline",
417
568
  children: "View reasoning"
418
- }, undefined, false, undefined, this),
419
- /* @__PURE__ */ jsxDEV3("div", {
569
+ }),
570
+ /* @__PURE__ */ jsx4("div", {
420
571
  className: "bg-muted mt-1 rounded-md p-2",
421
- children: /* @__PURE__ */ jsxDEV3("p", {
572
+ children: /* @__PURE__ */ jsx4("p", {
422
573
  className: "whitespace-pre-wrap",
423
574
  children: message.reasoning
424
- }, undefined, false, undefined, this)
425
- }, undefined, false, undefined, this)
575
+ })
576
+ })
426
577
  ]
427
- }, undefined, true, undefined, this),
428
- message.sources && message.sources.length > 0 && /* @__PURE__ */ jsxDEV3("div", {
578
+ }),
579
+ message.sources && message.sources.length > 0 && /* @__PURE__ */ jsx4("div", {
429
580
  className: "mt-2 flex flex-wrap gap-2",
430
- children: message.sources.map((source) => /* @__PURE__ */ jsxDEV3("a", {
581
+ children: message.sources.map((source) => /* @__PURE__ */ jsxs4("a", {
431
582
  href: source.url ?? "#",
432
583
  target: "_blank",
433
584
  rel: "noopener noreferrer",
434
585
  className: "text-muted-foreground hover:text-foreground bg-muted inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs transition-colors",
435
586
  children: [
436
- /* @__PURE__ */ jsxDEV3(ExternalLink, {
587
+ /* @__PURE__ */ jsx4(ExternalLink, {
437
588
  className: "h-3 w-3"
438
- }, undefined, false, undefined, this),
589
+ }),
439
590
  source.title || source.url || source.id
440
591
  ]
441
- }, source.id, true, undefined, this))
442
- }, undefined, false, undefined, this),
443
- message.toolCalls && message.toolCalls.length > 0 && /* @__PURE__ */ jsxDEV3("div", {
592
+ }, source.id))
593
+ }),
594
+ message.toolCalls && message.toolCalls.length > 0 && /* @__PURE__ */ jsx4("div", {
444
595
  className: "mt-2 space-y-2",
445
- children: message.toolCalls.map((tc) => /* @__PURE__ */ jsxDEV3("details", {
596
+ children: message.toolCalls.map((tc) => /* @__PURE__ */ jsxs4("details", {
446
597
  className: "bg-muted border-border rounded-md border",
447
598
  children: [
448
- /* @__PURE__ */ jsxDEV3("summary", {
599
+ /* @__PURE__ */ jsxs4("summary", {
449
600
  className: "flex cursor-pointer items-center gap-2 px-3 py-2 text-sm font-medium",
450
601
  children: [
451
- /* @__PURE__ */ jsxDEV3(Wrench, {
602
+ /* @__PURE__ */ jsx4(Wrench, {
452
603
  className: "text-muted-foreground h-4 w-4"
453
- }, undefined, false, undefined, this),
604
+ }),
454
605
  tc.name,
455
- /* @__PURE__ */ jsxDEV3("span", {
606
+ /* @__PURE__ */ jsx4("span", {
456
607
  className: cn3("ml-auto rounded px-1.5 py-0.5 text-xs", tc.status === "completed" && "bg-green-500/20 text-green-700 dark:text-green-400", tc.status === "error" && "bg-destructive/20 text-destructive", tc.status === "running" && "bg-blue-500/20 text-blue-700 dark:text-blue-400"),
457
608
  children: tc.status
458
- }, undefined, false, undefined, this)
609
+ })
459
610
  ]
460
- }, undefined, true, undefined, this),
461
- /* @__PURE__ */ jsxDEV3("div", {
611
+ }),
612
+ /* @__PURE__ */ jsxs4("div", {
462
613
  className: "border-border border-t px-3 py-2 text-xs",
463
614
  children: [
464
- Object.keys(tc.args).length > 0 && /* @__PURE__ */ jsxDEV3("div", {
615
+ Object.keys(tc.args).length > 0 && /* @__PURE__ */ jsxs4("div", {
465
616
  className: "mb-2",
466
617
  children: [
467
- /* @__PURE__ */ jsxDEV3("span", {
618
+ /* @__PURE__ */ jsx4("span", {
468
619
  className: "text-muted-foreground font-medium",
469
620
  children: "Input:"
470
- }, undefined, false, undefined, this),
471
- /* @__PURE__ */ jsxDEV3("pre", {
621
+ }),
622
+ /* @__PURE__ */ jsx4("pre", {
472
623
  className: "bg-background mt-1 overflow-x-auto rounded p-2",
473
624
  children: JSON.stringify(tc.args, null, 2)
474
- }, undefined, false, undefined, this)
475
- ]
476
- }, undefined, true, undefined, this),
477
- tc.result !== undefined && /* @__PURE__ */ jsxDEV3("div", {
478
- children: [
479
- /* @__PURE__ */ jsxDEV3("span", {
480
- className: "text-muted-foreground font-medium",
481
- children: "Output:"
482
- }, undefined, false, undefined, this),
483
- /* @__PURE__ */ jsxDEV3("pre", {
484
- className: "bg-background mt-1 overflow-x-auto rounded p-2",
485
- children: typeof tc.result === "object" ? JSON.stringify(tc.result, null, 2) : String(tc.result)
486
- }, undefined, false, undefined, this)
625
+ })
487
626
  ]
488
- }, undefined, true, undefined, this),
489
- tc.error && /* @__PURE__ */ jsxDEV3("p", {
627
+ }),
628
+ tc.result !== undefined && /* @__PURE__ */ jsx4(ToolResultRenderer, {
629
+ toolName: tc.name,
630
+ result: tc.result,
631
+ presentationRenderer,
632
+ formRenderer,
633
+ showRawFallback: true
634
+ }),
635
+ tc.error && /* @__PURE__ */ jsx4("p", {
490
636
  className: "text-destructive mt-1",
491
637
  children: tc.error
492
- }, undefined, false, undefined, this)
638
+ })
493
639
  ]
494
- }, undefined, true, undefined, this)
640
+ })
495
641
  ]
496
- }, tc.id, true, undefined, this))
497
- }, undefined, false, undefined, this)
642
+ }, tc.id))
643
+ })
498
644
  ]
499
- }, undefined, true, undefined, this)
645
+ })
500
646
  ]
501
- }, undefined, true, undefined, this);
647
+ });
502
648
  }
503
649
  // src/presentation/components/ChatInput.tsx
504
650
  import * as React4 from "react";
505
651
  import { cn as cn4 } from "@contractspec/lib.ui-kit-web/ui/utils";
506
652
  import { Textarea } from "@contractspec/lib.design-system";
507
653
  import { Button as Button3 } from "@contractspec/lib.design-system";
508
- import { Send, Paperclip, X, Loader2, FileText, Code } from "lucide-react";
509
- import { jsxDEV as jsxDEV4, Fragment as Fragment2 } from "react/jsx-dev-runtime";
654
+ import { Send, Paperclip, X as X2, Loader2, FileText, Code } from "lucide-react";
655
+ import { jsx as jsx5, jsxs as jsxs5, Fragment as Fragment2 } from "react/jsx-runtime";
510
656
  "use client";
511
657
  function ChatInput({
512
658
  onSend,
@@ -572,42 +718,42 @@ function ChatInput({
572
718
  const removeAttachment = React4.useCallback((id) => {
573
719
  setAttachments((prev) => prev.filter((a) => a.id !== id));
574
720
  }, []);
575
- return /* @__PURE__ */ jsxDEV4("div", {
721
+ return /* @__PURE__ */ jsxs5("div", {
576
722
  className: cn4("flex flex-col gap-2", className),
577
723
  children: [
578
- attachments.length > 0 && /* @__PURE__ */ jsxDEV4("div", {
724
+ attachments.length > 0 && /* @__PURE__ */ jsx5("div", {
579
725
  className: "flex flex-wrap gap-2",
580
- children: attachments.map((attachment) => /* @__PURE__ */ jsxDEV4("div", {
726
+ children: attachments.map((attachment) => /* @__PURE__ */ jsxs5("div", {
581
727
  className: cn4("flex items-center gap-1.5 rounded-md px-2 py-1", "bg-muted text-muted-foreground text-sm"),
582
728
  children: [
583
- attachment.type === "code" ? /* @__PURE__ */ jsxDEV4(Code, {
729
+ attachment.type === "code" ? /* @__PURE__ */ jsx5(Code, {
584
730
  className: "h-3.5 w-3.5"
585
- }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV4(FileText, {
731
+ }) : /* @__PURE__ */ jsx5(FileText, {
586
732
  className: "h-3.5 w-3.5"
587
- }, undefined, false, undefined, this),
588
- /* @__PURE__ */ jsxDEV4("span", {
733
+ }),
734
+ /* @__PURE__ */ jsx5("span", {
589
735
  className: "max-w-[150px] truncate",
590
736
  children: attachment.name
591
- }, undefined, false, undefined, this),
592
- /* @__PURE__ */ jsxDEV4("button", {
737
+ }),
738
+ /* @__PURE__ */ jsx5("button", {
593
739
  type: "button",
594
740
  onClick: () => removeAttachment(attachment.id),
595
741
  className: "hover:text-foreground",
596
742
  "aria-label": `Remove ${attachment.name}`,
597
- children: /* @__PURE__ */ jsxDEV4(X, {
743
+ children: /* @__PURE__ */ jsx5(X2, {
598
744
  className: "h-3.5 w-3.5"
599
- }, undefined, false, undefined, this)
600
- }, undefined, false, undefined, this)
745
+ })
746
+ })
601
747
  ]
602
- }, attachment.id, true, undefined, this))
603
- }, undefined, false, undefined, this),
604
- /* @__PURE__ */ jsxDEV4("form", {
748
+ }, attachment.id))
749
+ }),
750
+ /* @__PURE__ */ jsxs5("form", {
605
751
  onSubmit: handleSubmit,
606
752
  className: "flex items-end gap-2",
607
753
  children: [
608
- showAttachments && /* @__PURE__ */ jsxDEV4(Fragment2, {
754
+ showAttachments && /* @__PURE__ */ jsxs5(Fragment2, {
609
755
  children: [
610
- /* @__PURE__ */ jsxDEV4("input", {
756
+ /* @__PURE__ */ jsx5("input", {
611
757
  ref: fileInputRef,
612
758
  type: "file",
613
759
  multiple: true,
@@ -615,23 +761,23 @@ function ChatInput({
615
761
  onChange: handleFileSelect,
616
762
  className: "hidden",
617
763
  "aria-label": "Attach files"
618
- }, undefined, false, undefined, this),
619
- /* @__PURE__ */ jsxDEV4(Button3, {
764
+ }),
765
+ /* @__PURE__ */ jsx5(Button3, {
620
766
  type: "button",
621
767
  variant: "ghost",
622
768
  size: "sm",
623
769
  onPress: () => fileInputRef.current?.click(),
624
770
  disabled: disabled || attachments.length >= maxAttachments,
625
771
  "aria-label": "Attach files",
626
- children: /* @__PURE__ */ jsxDEV4(Paperclip, {
772
+ children: /* @__PURE__ */ jsx5(Paperclip, {
627
773
  className: "h-4 w-4"
628
- }, undefined, false, undefined, this)
629
- }, undefined, false, undefined, this)
774
+ })
775
+ })
630
776
  ]
631
- }, undefined, true, undefined, this),
632
- /* @__PURE__ */ jsxDEV4("div", {
777
+ }),
778
+ /* @__PURE__ */ jsx5("div", {
633
779
  className: "relative flex-1",
634
- children: /* @__PURE__ */ jsxDEV4(Textarea, {
780
+ children: /* @__PURE__ */ jsx5(Textarea, {
635
781
  value: content,
636
782
  onChange: (e) => setContent(e.target.value),
637
783
  onKeyDown: handleKeyDown,
@@ -640,32 +786,418 @@ function ChatInput({
640
786
  className: cn4("max-h-[200px] min-h-[44px] resize-none pr-12", "focus-visible:ring-1"),
641
787
  rows: 1,
642
788
  "aria-label": "Chat message"
643
- }, undefined, false, undefined, this)
644
- }, undefined, false, undefined, this),
645
- /* @__PURE__ */ jsxDEV4(Button3, {
789
+ })
790
+ }),
791
+ /* @__PURE__ */ jsx5(Button3, {
646
792
  type: "submit",
647
793
  disabled: !canSend || disabled || isLoading,
648
794
  size: "sm",
649
795
  "aria-label": isLoading ? "Sending..." : "Send message",
650
- children: isLoading ? /* @__PURE__ */ jsxDEV4(Loader2, {
796
+ children: isLoading ? /* @__PURE__ */ jsx5(Loader2, {
651
797
  className: "h-4 w-4 animate-spin"
652
- }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV4(Send, {
798
+ }) : /* @__PURE__ */ jsx5(Send, {
653
799
  className: "h-4 w-4"
654
- }, undefined, false, undefined, this)
655
- }, undefined, false, undefined, this)
800
+ })
801
+ })
656
802
  ]
657
- }, undefined, true, undefined, this),
658
- /* @__PURE__ */ jsxDEV4("p", {
803
+ }),
804
+ /* @__PURE__ */ jsx5("p", {
659
805
  className: "text-muted-foreground text-xs",
660
806
  children: "Press Enter to send, Shift+Enter for new line"
661
- }, undefined, false, undefined, this)
807
+ })
662
808
  ]
663
- }, undefined, true, undefined, this);
809
+ });
664
810
  }
665
- // src/presentation/components/ModelPicker.tsx
811
+ // src/presentation/components/ChatExportToolbar.tsx
666
812
  import * as React5 from "react";
667
- import { cn as cn5 } from "@contractspec/lib.ui-kit-web/ui/utils";
813
+ import { Download as Download2, FileText as FileText2, Copy as Copy3, Check as Check3, Plus, GitFork } from "lucide-react";
668
814
  import { Button as Button4 } from "@contractspec/lib.design-system";
815
+ import {
816
+ DropdownMenu,
817
+ DropdownMenuContent,
818
+ DropdownMenuItem,
819
+ DropdownMenuSeparator,
820
+ DropdownMenuTrigger
821
+ } from "@contractspec/lib.ui-kit-web/ui/dropdown-menu";
822
+
823
+ // src/core/export-formatters.ts
824
+ function formatTimestamp(date) {
825
+ return date.toLocaleTimeString([], {
826
+ hour: "2-digit",
827
+ minute: "2-digit"
828
+ });
829
+ }
830
+ function toIsoString(date) {
831
+ return date.toISOString();
832
+ }
833
+ function messageToJsonSerializable(msg) {
834
+ return {
835
+ id: msg.id,
836
+ conversationId: msg.conversationId,
837
+ role: msg.role,
838
+ content: msg.content,
839
+ status: msg.status,
840
+ createdAt: toIsoString(msg.createdAt),
841
+ updatedAt: toIsoString(msg.updatedAt),
842
+ ...msg.attachments && { attachments: msg.attachments },
843
+ ...msg.codeBlocks && { codeBlocks: msg.codeBlocks },
844
+ ...msg.toolCalls && { toolCalls: msg.toolCalls },
845
+ ...msg.sources && { sources: msg.sources },
846
+ ...msg.reasoning && { reasoning: msg.reasoning },
847
+ ...msg.usage && { usage: msg.usage },
848
+ ...msg.error && { error: msg.error },
849
+ ...msg.metadata && { metadata: msg.metadata }
850
+ };
851
+ }
852
+ function formatSourcesMarkdown(sources) {
853
+ if (sources.length === 0)
854
+ return "";
855
+ return `
856
+
857
+ **Sources:**
858
+ ` + sources.map((s) => `- [${s.title}](${s.url ?? "#"})`).join(`
859
+ `);
860
+ }
861
+ function formatSourcesTxt(sources) {
862
+ if (sources.length === 0)
863
+ return "";
864
+ return `
865
+
866
+ Sources:
867
+ ` + sources.map((s) => `- ${s.title}${s.url ? ` - ${s.url}` : ""}`).join(`
868
+ `);
869
+ }
870
+ function formatToolCallsMarkdown(toolCalls) {
871
+ if (toolCalls.length === 0)
872
+ return "";
873
+ return `
874
+
875
+ **Tool calls:**
876
+ ` + toolCalls.map((tc) => `**${tc.name}** (${tc.status})
877
+ \`\`\`json
878
+ ${JSON.stringify(tc.args, null, 2)}
879
+ \`\`\`` + (tc.result !== undefined ? `
880
+ Output:
881
+ \`\`\`json
882
+ ${typeof tc.result === "object" ? JSON.stringify(tc.result, null, 2) : String(tc.result)}
883
+ \`\`\`` : "") + (tc.error ? `
884
+ Error: ${tc.error}` : "")).join(`
885
+
886
+ `);
887
+ }
888
+ function formatToolCallsTxt(toolCalls) {
889
+ if (toolCalls.length === 0)
890
+ return "";
891
+ return `
892
+
893
+ Tool calls:
894
+ ` + toolCalls.map((tc) => `- ${tc.name} (${tc.status}): ${JSON.stringify(tc.args)}` + (tc.result !== undefined ? ` -> ${typeof tc.result === "object" ? JSON.stringify(tc.result) : String(tc.result)}` : "") + (tc.error ? ` [Error: ${tc.error}]` : "")).join(`
895
+ `);
896
+ }
897
+ function formatUsage(usage) {
898
+ const total = usage.inputTokens + usage.outputTokens;
899
+ return ` (${total} tokens)`;
900
+ }
901
+ function formatMessagesAsMarkdown(messages) {
902
+ const parts = [];
903
+ for (const msg of messages) {
904
+ const roleLabel = msg.role === "user" ? "User" : msg.role === "assistant" ? "Assistant" : "System";
905
+ const header = `## ${roleLabel}`;
906
+ const timestamp = `*${formatTimestamp(msg.createdAt)}*`;
907
+ const usageSuffix = msg.usage ? formatUsage(msg.usage) : "";
908
+ const meta = `${timestamp}${usageSuffix}
909
+
910
+ `;
911
+ let body = msg.content;
912
+ if (msg.error) {
913
+ body += `
914
+
915
+ **Error:** ${msg.error.code} - ${msg.error.message}`;
916
+ }
917
+ if (msg.reasoning) {
918
+ body += `
919
+
920
+ > **Reasoning:**
921
+ > ${msg.reasoning.replace(/\n/g, `
922
+ > `)}`;
923
+ }
924
+ body += formatSourcesMarkdown(msg.sources ?? []);
925
+ body += formatToolCallsMarkdown(msg.toolCalls ?? []);
926
+ parts.push(`${header}
927
+
928
+ ${meta}${body}`);
929
+ }
930
+ return parts.join(`
931
+
932
+ ---
933
+
934
+ `);
935
+ }
936
+ function formatMessagesAsTxt(messages) {
937
+ const parts = [];
938
+ for (const msg of messages) {
939
+ const roleLabel = msg.role === "user" ? "User" : msg.role === "assistant" ? "Assistant" : "System";
940
+ const timestamp = `(${formatTimestamp(msg.createdAt)})`;
941
+ const usageSuffix = msg.usage ? formatUsage(msg.usage) : "";
942
+ const header = `[${roleLabel}] ${timestamp}${usageSuffix}
943
+
944
+ `;
945
+ let body = msg.content;
946
+ if (msg.error) {
947
+ body += `
948
+
949
+ Error: ${msg.error.code} - ${msg.error.message}`;
950
+ }
951
+ if (msg.reasoning) {
952
+ body += `
953
+
954
+ Reasoning: ${msg.reasoning}`;
955
+ }
956
+ body += formatSourcesTxt(msg.sources ?? []);
957
+ body += formatToolCallsTxt(msg.toolCalls ?? []);
958
+ parts.push(`${header}${body}`);
959
+ }
960
+ return parts.join(`
961
+
962
+ ---
963
+
964
+ `);
965
+ }
966
+ function formatMessagesAsJson(messages, conversation) {
967
+ const payload = {
968
+ messages: messages.map(messageToJsonSerializable)
969
+ };
970
+ if (conversation) {
971
+ payload.conversation = {
972
+ id: conversation.id,
973
+ title: conversation.title,
974
+ status: conversation.status,
975
+ createdAt: toIsoString(conversation.createdAt),
976
+ updatedAt: toIsoString(conversation.updatedAt),
977
+ provider: conversation.provider,
978
+ model: conversation.model,
979
+ workspacePath: conversation.workspacePath,
980
+ contextFiles: conversation.contextFiles,
981
+ summary: conversation.summary,
982
+ metadata: conversation.metadata
983
+ };
984
+ }
985
+ return JSON.stringify(payload, null, 2);
986
+ }
987
+ function getExportFilename(format, conversation) {
988
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
989
+ const base = conversation?.title ? conversation.title.replace(/[^a-zA-Z0-9-_]/g, "_").slice(0, 40) : "chat-export";
990
+ const ext = format === "markdown" ? "md" : format === "txt" ? "txt" : "json";
991
+ return `${base}-${timestamp}.${ext}`;
992
+ }
993
+ var MIME_TYPES = {
994
+ markdown: "text/markdown",
995
+ txt: "text/plain",
996
+ json: "application/json"
997
+ };
998
+ function downloadAsFile(content, filename, mimeType) {
999
+ const blob = new Blob([content], { type: mimeType });
1000
+ const url = URL.createObjectURL(blob);
1001
+ const a = document.createElement("a");
1002
+ a.href = url;
1003
+ a.download = filename;
1004
+ document.body.appendChild(a);
1005
+ a.click();
1006
+ document.body.removeChild(a);
1007
+ URL.revokeObjectURL(url);
1008
+ }
1009
+ function exportToFile(messages, format, conversation) {
1010
+ let content;
1011
+ if (format === "markdown") {
1012
+ content = formatMessagesAsMarkdown(messages);
1013
+ } else if (format === "txt") {
1014
+ content = formatMessagesAsTxt(messages);
1015
+ } else {
1016
+ content = formatMessagesAsJson(messages, conversation);
1017
+ }
1018
+ const filename = getExportFilename(format, conversation);
1019
+ const mimeType = MIME_TYPES[format];
1020
+ downloadAsFile(content, filename, mimeType);
1021
+ }
1022
+
1023
+ // src/presentation/components/ChatExportToolbar.tsx
1024
+ import { jsx as jsx6, jsxs as jsxs6, Fragment as Fragment3 } from "react/jsx-runtime";
1025
+ "use client";
1026
+ function ChatExportToolbar({
1027
+ messages,
1028
+ conversation,
1029
+ selectedIds,
1030
+ onExported,
1031
+ showSelectionSummary = true,
1032
+ onSelectAll,
1033
+ onClearSelection,
1034
+ selectedCount = selectedIds.size,
1035
+ totalCount = messages.length,
1036
+ onCreateNew,
1037
+ onFork
1038
+ }) {
1039
+ const [copied, setCopied] = React5.useState(false);
1040
+ const toExport = React5.useMemo(() => {
1041
+ if (selectedIds.size > 0) {
1042
+ const idSet = selectedIds;
1043
+ return messages.filter((m) => idSet.has(m.id));
1044
+ }
1045
+ return messages;
1046
+ }, [messages, selectedIds]);
1047
+ const handleExport = React5.useCallback((format) => {
1048
+ exportToFile(toExport, format, conversation);
1049
+ onExported?.(format, toExport.length);
1050
+ }, [toExport, conversation, onExported]);
1051
+ const handleCopy = React5.useCallback(async () => {
1052
+ const content = formatMessagesAsMarkdown(toExport);
1053
+ await navigator.clipboard.writeText(content);
1054
+ setCopied(true);
1055
+ setTimeout(() => setCopied(false), 2000);
1056
+ onExported?.("markdown", toExport.length);
1057
+ }, [toExport, onExported]);
1058
+ const disabled = messages.length === 0;
1059
+ const [forking, setForking] = React5.useState(false);
1060
+ const handleFork = React5.useCallback(async (upToMessageId) => {
1061
+ if (!onFork)
1062
+ return;
1063
+ setForking(true);
1064
+ try {
1065
+ await onFork(upToMessageId);
1066
+ } finally {
1067
+ setForking(false);
1068
+ }
1069
+ }, [onFork]);
1070
+ return /* @__PURE__ */ jsxs6("div", {
1071
+ className: "flex items-center gap-2",
1072
+ children: [
1073
+ onCreateNew && /* @__PURE__ */ jsxs6(Button4, {
1074
+ variant: "outline",
1075
+ size: "sm",
1076
+ onPress: onCreateNew,
1077
+ "aria-label": "New conversation",
1078
+ children: [
1079
+ /* @__PURE__ */ jsx6(Plus, {
1080
+ className: "h-4 w-4"
1081
+ }),
1082
+ "New"
1083
+ ]
1084
+ }),
1085
+ onFork && messages.length > 0 && /* @__PURE__ */ jsxs6(Button4, {
1086
+ variant: "outline",
1087
+ size: "sm",
1088
+ disabled: forking,
1089
+ onPress: () => handleFork(),
1090
+ "aria-label": "Fork conversation",
1091
+ children: [
1092
+ /* @__PURE__ */ jsx6(GitFork, {
1093
+ className: "h-4 w-4"
1094
+ }),
1095
+ "Fork"
1096
+ ]
1097
+ }),
1098
+ showSelectionSummary && selectedCount > 0 && /* @__PURE__ */ jsxs6("span", {
1099
+ className: "text-muted-foreground text-sm",
1100
+ children: [
1101
+ selectedCount,
1102
+ " message",
1103
+ selectedCount !== 1 ? "s" : "",
1104
+ " selected"
1105
+ ]
1106
+ }),
1107
+ onSelectAll && onClearSelection && totalCount > 0 && /* @__PURE__ */ jsxs6(Fragment3, {
1108
+ children: [
1109
+ /* @__PURE__ */ jsx6(Button4, {
1110
+ variant: "ghost",
1111
+ size: "sm",
1112
+ onPress: onSelectAll,
1113
+ className: "text-xs",
1114
+ children: "Select all"
1115
+ }),
1116
+ selectedCount > 0 && /* @__PURE__ */ jsx6(Button4, {
1117
+ variant: "ghost",
1118
+ size: "sm",
1119
+ onPress: onClearSelection,
1120
+ className: "text-xs",
1121
+ children: "Clear"
1122
+ })
1123
+ ]
1124
+ }),
1125
+ /* @__PURE__ */ jsxs6(DropdownMenu, {
1126
+ children: [
1127
+ /* @__PURE__ */ jsx6(DropdownMenuTrigger, {
1128
+ asChild: true,
1129
+ children: /* @__PURE__ */ jsxs6(Button4, {
1130
+ variant: "outline",
1131
+ size: "sm",
1132
+ disabled,
1133
+ "aria-label": selectedCount > 0 ? "Export selected messages" : "Export conversation",
1134
+ children: [
1135
+ /* @__PURE__ */ jsx6(Download2, {
1136
+ className: "h-4 w-4"
1137
+ }),
1138
+ "Export"
1139
+ ]
1140
+ })
1141
+ }),
1142
+ /* @__PURE__ */ jsxs6(DropdownMenuContent, {
1143
+ align: "end",
1144
+ children: [
1145
+ /* @__PURE__ */ jsxs6(DropdownMenuItem, {
1146
+ onSelect: () => handleExport("markdown"),
1147
+ disabled,
1148
+ children: [
1149
+ /* @__PURE__ */ jsx6(FileText2, {
1150
+ className: "h-4 w-4"
1151
+ }),
1152
+ "Export as Markdown (.md)"
1153
+ ]
1154
+ }),
1155
+ /* @__PURE__ */ jsxs6(DropdownMenuItem, {
1156
+ onSelect: () => handleExport("txt"),
1157
+ disabled,
1158
+ children: [
1159
+ /* @__PURE__ */ jsx6(FileText2, {
1160
+ className: "h-4 w-4"
1161
+ }),
1162
+ "Export as Plain Text (.txt)"
1163
+ ]
1164
+ }),
1165
+ /* @__PURE__ */ jsxs6(DropdownMenuItem, {
1166
+ onSelect: () => handleExport("json"),
1167
+ disabled,
1168
+ children: [
1169
+ /* @__PURE__ */ jsx6(FileText2, {
1170
+ className: "h-4 w-4"
1171
+ }),
1172
+ "Export as JSON (.json)"
1173
+ ]
1174
+ }),
1175
+ /* @__PURE__ */ jsx6(DropdownMenuSeparator, {}),
1176
+ /* @__PURE__ */ jsxs6(DropdownMenuItem, {
1177
+ onSelect: () => handleCopy(),
1178
+ disabled,
1179
+ children: [
1180
+ copied ? /* @__PURE__ */ jsx6(Check3, {
1181
+ className: "h-4 w-4 text-green-500"
1182
+ }) : /* @__PURE__ */ jsx6(Copy3, {
1183
+ className: "h-4 w-4"
1184
+ }),
1185
+ copied ? "Copied to clipboard" : "Copy to clipboard"
1186
+ ]
1187
+ })
1188
+ ]
1189
+ })
1190
+ ]
1191
+ })
1192
+ ]
1193
+ });
1194
+ }
1195
+ // src/presentation/components/ChatWithExport.tsx
1196
+ import * as React8 from "react";
1197
+
1198
+ // src/presentation/components/ThinkingLevelPicker.tsx
1199
+ import * as React6 from "react";
1200
+ import { cn as cn5 } from "@contractspec/lib.ui-kit-web/ui/utils";
669
1201
  import {
670
1202
  Select,
671
1203
  SelectContent,
@@ -673,30 +1205,2275 @@ import {
673
1205
  SelectTrigger,
674
1206
  SelectValue
675
1207
  } from "@contractspec/lib.ui-kit-web/ui/select";
676
- import { Badge } from "@contractspec/lib.ui-kit-web/ui/badge";
677
1208
  import { Label } from "@contractspec/lib.ui-kit-web/ui/label";
1209
+
1210
+ // src/core/thinking-levels.ts
1211
+ var THINKING_LEVEL_LABELS = {
1212
+ instant: "Instant",
1213
+ thinking: "Thinking",
1214
+ extra_thinking: "Extra Thinking",
1215
+ max: "Max"
1216
+ };
1217
+ var THINKING_LEVEL_DESCRIPTIONS = {
1218
+ instant: "Fast responses, minimal reasoning",
1219
+ thinking: "Standard reasoning depth",
1220
+ extra_thinking: "More thorough reasoning",
1221
+ max: "Maximum reasoning depth"
1222
+ };
1223
+ function getProviderOptions(level, providerName) {
1224
+ if (!level || level === "instant") {
1225
+ return {};
1226
+ }
1227
+ switch (providerName) {
1228
+ case "anthropic": {
1229
+ const budgetMap = {
1230
+ thinking: 8000,
1231
+ extra_thinking: 16000,
1232
+ max: 32000
1233
+ };
1234
+ return {
1235
+ anthropic: {
1236
+ thinking: { type: "enabled", budgetTokens: budgetMap[level] }
1237
+ }
1238
+ };
1239
+ }
1240
+ case "openai": {
1241
+ const effortMap = {
1242
+ thinking: "low",
1243
+ extra_thinking: "medium",
1244
+ max: "high"
1245
+ };
1246
+ return {
1247
+ openai: {
1248
+ reasoningEffort: effortMap[level]
1249
+ }
1250
+ };
1251
+ }
1252
+ case "ollama":
1253
+ case "mistral":
1254
+ case "gemini":
1255
+ return {};
1256
+ default:
1257
+ return {};
1258
+ }
1259
+ }
1260
+
1261
+ // src/presentation/components/ThinkingLevelPicker.tsx
1262
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1263
+ "use client";
1264
+ var THINKING_LEVELS = [
1265
+ "instant",
1266
+ "thinking",
1267
+ "extra_thinking",
1268
+ "max"
1269
+ ];
1270
+ function ThinkingLevelPicker({
1271
+ value,
1272
+ onChange,
1273
+ className,
1274
+ compact = false
1275
+ }) {
1276
+ const handleChange = React6.useCallback((v) => {
1277
+ onChange(v);
1278
+ }, [onChange]);
1279
+ if (compact) {
1280
+ return /* @__PURE__ */ jsxs7(Select, {
1281
+ value,
1282
+ onValueChange: handleChange,
1283
+ children: [
1284
+ /* @__PURE__ */ jsx7(SelectTrigger, {
1285
+ className: cn5("w-[140px]", className),
1286
+ children: /* @__PURE__ */ jsx7(SelectValue, {})
1287
+ }),
1288
+ /* @__PURE__ */ jsx7(SelectContent, {
1289
+ children: THINKING_LEVELS.map((level) => /* @__PURE__ */ jsx7(SelectItem, {
1290
+ value: level,
1291
+ children: THINKING_LEVEL_LABELS[level]
1292
+ }, level))
1293
+ })
1294
+ ]
1295
+ });
1296
+ }
1297
+ return /* @__PURE__ */ jsxs7("div", {
1298
+ className: cn5("flex flex-col gap-1.5", className),
1299
+ children: [
1300
+ /* @__PURE__ */ jsx7(Label, {
1301
+ htmlFor: "thinking-level-picker",
1302
+ className: "text-sm font-medium",
1303
+ children: "Thinking Level"
1304
+ }),
1305
+ /* @__PURE__ */ jsxs7(Select, {
1306
+ name: "thinking-level-picker",
1307
+ value,
1308
+ onValueChange: handleChange,
1309
+ children: [
1310
+ /* @__PURE__ */ jsx7(SelectTrigger, {
1311
+ children: /* @__PURE__ */ jsx7(SelectValue, {
1312
+ placeholder: "Select thinking level"
1313
+ })
1314
+ }),
1315
+ /* @__PURE__ */ jsx7(SelectContent, {
1316
+ children: THINKING_LEVELS.map((level) => /* @__PURE__ */ jsx7(SelectItem, {
1317
+ value: level,
1318
+ title: THINKING_LEVEL_DESCRIPTIONS[level],
1319
+ children: THINKING_LEVEL_LABELS[level]
1320
+ }, level))
1321
+ })
1322
+ ]
1323
+ })
1324
+ ]
1325
+ });
1326
+ }
1327
+
1328
+ // src/presentation/hooks/useMessageSelection.ts
1329
+ import * as React7 from "react";
1330
+ "use client";
1331
+ function useMessageSelection(messageIds) {
1332
+ const [selectedIds, setSelectedIds] = React7.useState(() => new Set);
1333
+ const idSet = React7.useMemo(() => new Set(messageIds), [messageIds.join(",")]);
1334
+ React7.useEffect(() => {
1335
+ setSelectedIds((prev) => {
1336
+ const next = new Set;
1337
+ for (const id of prev) {
1338
+ if (idSet.has(id))
1339
+ next.add(id);
1340
+ }
1341
+ return next.size === prev.size ? prev : next;
1342
+ });
1343
+ }, [idSet]);
1344
+ const toggle = React7.useCallback((id) => {
1345
+ setSelectedIds((prev) => {
1346
+ const next = new Set(prev);
1347
+ if (next.has(id))
1348
+ next.delete(id);
1349
+ else
1350
+ next.add(id);
1351
+ return next;
1352
+ });
1353
+ }, []);
1354
+ const selectAll = React7.useCallback(() => {
1355
+ setSelectedIds(new Set(messageIds));
1356
+ }, [messageIds.join(",")]);
1357
+ const clearSelection = React7.useCallback(() => {
1358
+ setSelectedIds(new Set);
1359
+ }, []);
1360
+ const isSelected = React7.useCallback((id) => selectedIds.has(id), [selectedIds]);
1361
+ const selectedCount = selectedIds.size;
1362
+ return {
1363
+ selectedIds,
1364
+ toggle,
1365
+ selectAll,
1366
+ clearSelection,
1367
+ isSelected,
1368
+ selectedCount
1369
+ };
1370
+ }
1371
+
1372
+ // src/presentation/components/ChatWithExport.tsx
1373
+ import { jsx as jsx8, jsxs as jsxs8, Fragment as Fragment4 } from "react/jsx-runtime";
1374
+ "use client";
1375
+ function ChatWithExport({
1376
+ messages,
1377
+ conversation,
1378
+ children,
1379
+ className,
1380
+ showExport = true,
1381
+ showMessageSelection = true,
1382
+ showScrollButton = true,
1383
+ onCreateNew,
1384
+ onFork,
1385
+ onEditMessage,
1386
+ thinkingLevel = "thinking",
1387
+ onThinkingLevelChange,
1388
+ presentationRenderer,
1389
+ formRenderer
1390
+ }) {
1391
+ const messageIds = React8.useMemo(() => messages.map((m) => m.id), [messages]);
1392
+ const selection = useMessageSelection(messageIds);
1393
+ const hasToolbar = showExport || showMessageSelection;
1394
+ const hasPicker = Boolean(onThinkingLevelChange);
1395
+ const headerContent = hasPicker || hasToolbar ? /* @__PURE__ */ jsxs8(Fragment4, {
1396
+ children: [
1397
+ hasPicker && /* @__PURE__ */ jsx8(ThinkingLevelPicker, {
1398
+ value: thinkingLevel,
1399
+ onChange: onThinkingLevelChange,
1400
+ compact: true
1401
+ }),
1402
+ hasToolbar && /* @__PURE__ */ jsx8(ChatExportToolbar, {
1403
+ messages,
1404
+ conversation,
1405
+ selectedIds: selection.selectedIds,
1406
+ showSelectionSummary: showMessageSelection,
1407
+ onSelectAll: showMessageSelection ? selection.selectAll : undefined,
1408
+ onClearSelection: showMessageSelection ? selection.clearSelection : undefined,
1409
+ selectedCount: selection.selectedCount,
1410
+ totalCount: messages.length,
1411
+ onCreateNew,
1412
+ onFork
1413
+ })
1414
+ ]
1415
+ }) : null;
1416
+ return /* @__PURE__ */ jsxs8(ChatContainer, {
1417
+ className,
1418
+ headerContent,
1419
+ showScrollButton,
1420
+ children: [
1421
+ messages.map((msg) => /* @__PURE__ */ jsx8(ChatMessage, {
1422
+ message: msg,
1423
+ selectable: showMessageSelection,
1424
+ selected: selection.isSelected(msg.id),
1425
+ onSelect: showMessageSelection ? selection.toggle : undefined,
1426
+ editable: msg.role === "user" && !!onEditMessage,
1427
+ onEdit: onEditMessage,
1428
+ presentationRenderer,
1429
+ formRenderer
1430
+ }, msg.id)),
1431
+ children
1432
+ ]
1433
+ });
1434
+ }
1435
+ // src/presentation/components/ChatSidebar.tsx
1436
+ import * as React10 from "react";
1437
+ import { Plus as Plus2, Trash2, MessageSquare } from "lucide-react";
1438
+ import { Button as Button5 } from "@contractspec/lib.design-system";
1439
+ import { cn as cn6 } from "@contractspec/lib.ui-kit-web/ui/utils";
1440
+
1441
+ // src/presentation/hooks/useConversations.ts
1442
+ import * as React9 from "react";
1443
+ "use client";
1444
+ function useConversations(options) {
1445
+ const { store, projectId, tags, limit = 50 } = options;
1446
+ const [conversations, setConversations] = React9.useState([]);
1447
+ const [isLoading, setIsLoading] = React9.useState(true);
1448
+ const refresh = React9.useCallback(async () => {
1449
+ setIsLoading(true);
1450
+ try {
1451
+ const list = await store.list({
1452
+ status: "active",
1453
+ projectId,
1454
+ tags,
1455
+ limit
1456
+ });
1457
+ setConversations(list);
1458
+ } finally {
1459
+ setIsLoading(false);
1460
+ }
1461
+ }, [store, projectId, tags, limit]);
1462
+ React9.useEffect(() => {
1463
+ refresh();
1464
+ }, [refresh]);
1465
+ const deleteConversation = React9.useCallback(async (id) => {
1466
+ const ok = await store.delete(id);
1467
+ if (ok) {
1468
+ setConversations((prev) => prev.filter((c) => c.id !== id));
1469
+ }
1470
+ return ok;
1471
+ }, [store]);
1472
+ return {
1473
+ conversations,
1474
+ isLoading,
1475
+ refresh,
1476
+ deleteConversation
1477
+ };
1478
+ }
1479
+
1480
+ // src/presentation/components/ChatSidebar.tsx
1481
+ import { jsx as jsx9, jsxs as jsxs9, Fragment as Fragment5 } from "react/jsx-runtime";
1482
+ "use client";
1483
+ function formatDate(date) {
1484
+ const d = new Date(date);
1485
+ const now = new Date;
1486
+ const diff = now.getTime() - d.getTime();
1487
+ if (diff < 86400000) {
1488
+ return d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
1489
+ }
1490
+ if (diff < 604800000) {
1491
+ return d.toLocaleDateString([], { weekday: "short" });
1492
+ }
1493
+ return d.toLocaleDateString([], { month: "short", day: "numeric" });
1494
+ }
1495
+ function ConversationItem({
1496
+ conversation,
1497
+ selected,
1498
+ onSelect,
1499
+ onDelete
1500
+ }) {
1501
+ const title = conversation.title ?? conversation.messages[0]?.content?.slice(0, 50) ?? "New chat";
1502
+ const displayTitle = title.length > 40 ? `${title.slice(0, 40)}…` : title;
1503
+ return /* @__PURE__ */ jsxs9("div", {
1504
+ role: "button",
1505
+ tabIndex: 0,
1506
+ onClick: onSelect,
1507
+ onKeyDown: (e) => {
1508
+ if (e.key === "Enter" || e.key === " ") {
1509
+ e.preventDefault();
1510
+ onSelect();
1511
+ }
1512
+ },
1513
+ className: cn6("group flex cursor-pointer items-center gap-2 rounded-md px-3 py-2 text-sm transition-colors", selected ? "bg-accent text-accent-foreground" : "hover:bg-accent/50"),
1514
+ children: [
1515
+ /* @__PURE__ */ jsx9(MessageSquare, {
1516
+ className: "text-muted-foreground h-4 w-4 shrink-0"
1517
+ }),
1518
+ /* @__PURE__ */ jsxs9("div", {
1519
+ className: "min-w-0 flex-1",
1520
+ children: [
1521
+ /* @__PURE__ */ jsx9("p", {
1522
+ className: "truncate",
1523
+ children: displayTitle
1524
+ }),
1525
+ /* @__PURE__ */ jsxs9("p", {
1526
+ className: "text-muted-foreground text-xs",
1527
+ children: [
1528
+ formatDate(conversation.updatedAt),
1529
+ conversation.projectName && ` · ${conversation.projectName}`,
1530
+ conversation.tags && conversation.tags.length > 0 && /* @__PURE__ */ jsxs9(Fragment5, {
1531
+ children: [
1532
+ " · ",
1533
+ conversation.tags.slice(0, 2).join(", ")
1534
+ ]
1535
+ })
1536
+ ]
1537
+ })
1538
+ ]
1539
+ }),
1540
+ /* @__PURE__ */ jsx9("span", {
1541
+ onClick: (e) => e.stopPropagation(),
1542
+ children: /* @__PURE__ */ jsx9(Button5, {
1543
+ variant: "ghost",
1544
+ size: "sm",
1545
+ className: "h-6 w-6 shrink-0 p-0 opacity-0 group-hover:opacity-100",
1546
+ onPress: onDelete,
1547
+ "aria-label": "Delete conversation",
1548
+ children: /* @__PURE__ */ jsx9(Trash2, {
1549
+ className: "h-3 w-3"
1550
+ })
1551
+ })
1552
+ })
1553
+ ]
1554
+ });
1555
+ }
1556
+ function ChatSidebar({
1557
+ store,
1558
+ selectedConversationId,
1559
+ onSelectConversation,
1560
+ onCreateNew,
1561
+ projectId,
1562
+ tags,
1563
+ limit = 50,
1564
+ className,
1565
+ collapsed = false,
1566
+ onUpdateConversation,
1567
+ selectedConversation
1568
+ }) {
1569
+ const { conversations, isLoading, refresh, deleteConversation } = useConversations({ store, projectId, tags, limit });
1570
+ const handleDelete = React10.useCallback(async (id) => {
1571
+ const ok = await deleteConversation(id);
1572
+ if (ok && selectedConversationId === id) {
1573
+ onSelectConversation(null);
1574
+ }
1575
+ }, [deleteConversation, selectedConversationId, onSelectConversation]);
1576
+ if (collapsed)
1577
+ return null;
1578
+ return /* @__PURE__ */ jsxs9("div", {
1579
+ className: cn6("border-border flex w-64 shrink-0 flex-col border-r", className),
1580
+ children: [
1581
+ /* @__PURE__ */ jsxs9("div", {
1582
+ className: "border-border flex shrink-0 items-center justify-between border-b p-2",
1583
+ children: [
1584
+ /* @__PURE__ */ jsx9("span", {
1585
+ className: "text-muted-foreground text-sm font-medium",
1586
+ children: "Conversations"
1587
+ }),
1588
+ /* @__PURE__ */ jsx9(Button5, {
1589
+ variant: "ghost",
1590
+ size: "sm",
1591
+ className: "h-8 w-8 p-0",
1592
+ onPress: onCreateNew,
1593
+ "aria-label": "New conversation",
1594
+ children: /* @__PURE__ */ jsx9(Plus2, {
1595
+ className: "h-4 w-4"
1596
+ })
1597
+ })
1598
+ ]
1599
+ }),
1600
+ /* @__PURE__ */ jsx9("div", {
1601
+ className: "flex-1 overflow-y-auto p-2",
1602
+ children: isLoading ? /* @__PURE__ */ jsx9("div", {
1603
+ className: "text-muted-foreground py-4 text-center text-sm",
1604
+ children: "Loading…"
1605
+ }) : conversations.length === 0 ? /* @__PURE__ */ jsx9("div", {
1606
+ className: "text-muted-foreground py-4 text-center text-sm",
1607
+ children: "No conversations yet"
1608
+ }) : /* @__PURE__ */ jsx9("div", {
1609
+ className: "flex flex-col gap-1",
1610
+ children: conversations.map((conv) => /* @__PURE__ */ jsx9(ConversationItem, {
1611
+ conversation: conv,
1612
+ selected: conv.id === selectedConversationId,
1613
+ onSelect: () => onSelectConversation(conv.id),
1614
+ onDelete: () => handleDelete(conv.id)
1615
+ }, conv.id))
1616
+ })
1617
+ }),
1618
+ selectedConversation && onUpdateConversation && /* @__PURE__ */ jsx9(ConversationMeta, {
1619
+ conversation: selectedConversation,
1620
+ onUpdate: onUpdateConversation
1621
+ })
1622
+ ]
1623
+ });
1624
+ }
1625
+ function ConversationMeta({
1626
+ conversation,
1627
+ onUpdate
1628
+ }) {
1629
+ const [projectName, setProjectName] = React10.useState(conversation.projectName ?? "");
1630
+ const [tagsStr, setTagsStr] = React10.useState(conversation.tags?.join(", ") ?? "");
1631
+ React10.useEffect(() => {
1632
+ setProjectName(conversation.projectName ?? "");
1633
+ setTagsStr(conversation.tags?.join(", ") ?? "");
1634
+ }, [conversation.id, conversation.projectName, conversation.tags]);
1635
+ const handleBlur = React10.useCallback(() => {
1636
+ const tags = tagsStr.split(",").map((t) => t.trim()).filter(Boolean);
1637
+ if (projectName !== (conversation.projectName ?? "") || JSON.stringify(tags) !== JSON.stringify(conversation.tags ?? [])) {
1638
+ onUpdate(conversation.id, {
1639
+ projectName: projectName || undefined,
1640
+ projectId: projectName ? projectName.replace(/\s+/g, "-") : undefined,
1641
+ tags: tags.length > 0 ? tags : undefined
1642
+ });
1643
+ }
1644
+ }, [
1645
+ conversation.id,
1646
+ conversation.projectName,
1647
+ conversation.tags,
1648
+ projectName,
1649
+ tagsStr,
1650
+ onUpdate
1651
+ ]);
1652
+ return /* @__PURE__ */ jsxs9("div", {
1653
+ className: "border-border shrink-0 border-t p-2",
1654
+ children: [
1655
+ /* @__PURE__ */ jsx9("p", {
1656
+ className: "text-muted-foreground mb-1 text-xs font-medium",
1657
+ children: "Project"
1658
+ }),
1659
+ /* @__PURE__ */ jsx9("input", {
1660
+ type: "text",
1661
+ value: projectName,
1662
+ onChange: (e) => setProjectName(e.target.value),
1663
+ onBlur: handleBlur,
1664
+ placeholder: "Project name",
1665
+ className: "border-input bg-background mb-2 w-full rounded px-2 py-1 text-xs"
1666
+ }),
1667
+ /* @__PURE__ */ jsx9("p", {
1668
+ className: "text-muted-foreground mb-1 text-xs font-medium",
1669
+ children: "Tags"
1670
+ }),
1671
+ /* @__PURE__ */ jsx9("input", {
1672
+ type: "text",
1673
+ value: tagsStr,
1674
+ onChange: (e) => setTagsStr(e.target.value),
1675
+ onBlur: handleBlur,
1676
+ placeholder: "tag1, tag2",
1677
+ className: "border-input bg-background w-full rounded px-2 py-1 text-xs"
1678
+ })
1679
+ ]
1680
+ });
1681
+ }
1682
+ // src/presentation/components/ChatWithSidebar.tsx
1683
+ import * as React12 from "react";
1684
+
1685
+ // src/presentation/hooks/useChat.tsx
1686
+ import * as React11 from "react";
1687
+ import { tool as tool4 } from "ai";
1688
+ import { z as z4 } from "zod";
1689
+
1690
+ // src/core/chat-service.ts
1691
+ import { generateText, streamText } from "ai";
1692
+
1693
+ // src/core/conversation-store.ts
1694
+ function generateId(prefix) {
1695
+ return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
1696
+ }
1697
+
1698
+ class InMemoryConversationStore {
1699
+ conversations = new Map;
1700
+ async get(conversationId) {
1701
+ return this.conversations.get(conversationId) ?? null;
1702
+ }
1703
+ async create(conversation) {
1704
+ const now = new Date;
1705
+ const fullConversation = {
1706
+ ...conversation,
1707
+ id: generateId("conv"),
1708
+ createdAt: now,
1709
+ updatedAt: now
1710
+ };
1711
+ this.conversations.set(fullConversation.id, fullConversation);
1712
+ return fullConversation;
1713
+ }
1714
+ async update(conversationId, updates) {
1715
+ const conversation = this.conversations.get(conversationId);
1716
+ if (!conversation)
1717
+ return null;
1718
+ const updated = {
1719
+ ...conversation,
1720
+ ...updates,
1721
+ updatedAt: new Date
1722
+ };
1723
+ this.conversations.set(conversationId, updated);
1724
+ return updated;
1725
+ }
1726
+ async appendMessage(conversationId, message) {
1727
+ const conversation = this.conversations.get(conversationId);
1728
+ if (!conversation) {
1729
+ throw new Error(`Conversation ${conversationId} not found`);
1730
+ }
1731
+ const now = new Date;
1732
+ const fullMessage = {
1733
+ ...message,
1734
+ id: generateId("msg"),
1735
+ conversationId,
1736
+ createdAt: now,
1737
+ updatedAt: now
1738
+ };
1739
+ conversation.messages.push(fullMessage);
1740
+ conversation.updatedAt = now;
1741
+ return fullMessage;
1742
+ }
1743
+ async updateMessage(conversationId, messageId, updates) {
1744
+ const conversation = this.conversations.get(conversationId);
1745
+ if (!conversation)
1746
+ return null;
1747
+ const messageIndex = conversation.messages.findIndex((m) => m.id === messageId);
1748
+ if (messageIndex === -1)
1749
+ return null;
1750
+ const message = conversation.messages[messageIndex];
1751
+ if (!message)
1752
+ return null;
1753
+ const updated = {
1754
+ ...message,
1755
+ ...updates,
1756
+ updatedAt: new Date
1757
+ };
1758
+ conversation.messages[messageIndex] = updated;
1759
+ conversation.updatedAt = new Date;
1760
+ return updated;
1761
+ }
1762
+ async delete(conversationId) {
1763
+ return this.conversations.delete(conversationId);
1764
+ }
1765
+ async list(options) {
1766
+ let results = Array.from(this.conversations.values());
1767
+ if (options?.status) {
1768
+ results = results.filter((c) => c.status === options.status);
1769
+ }
1770
+ if (options?.projectId) {
1771
+ results = results.filter((c) => c.projectId === options.projectId);
1772
+ }
1773
+ if (options?.tags && options.tags.length > 0) {
1774
+ const tagSet = new Set(options.tags);
1775
+ results = results.filter((c) => c.tags && c.tags.some((t) => tagSet.has(t)));
1776
+ }
1777
+ results.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());
1778
+ const offset = options?.offset ?? 0;
1779
+ const limit = options?.limit ?? 100;
1780
+ return results.slice(offset, offset + limit);
1781
+ }
1782
+ async fork(conversationId, upToMessageId) {
1783
+ const source = this.conversations.get(conversationId);
1784
+ if (!source) {
1785
+ throw new Error(`Conversation ${conversationId} not found`);
1786
+ }
1787
+ let messagesToCopy = source.messages;
1788
+ if (upToMessageId) {
1789
+ const idx = source.messages.findIndex((m) => m.id === upToMessageId);
1790
+ if (idx === -1) {
1791
+ throw new Error(`Message ${upToMessageId} not found`);
1792
+ }
1793
+ messagesToCopy = source.messages.slice(0, idx + 1);
1794
+ }
1795
+ const now = new Date;
1796
+ const forkedMessages = messagesToCopy.map((m) => ({
1797
+ ...m,
1798
+ id: generateId("msg"),
1799
+ conversationId: "",
1800
+ createdAt: new Date(m.createdAt),
1801
+ updatedAt: new Date(m.updatedAt)
1802
+ }));
1803
+ const forked = {
1804
+ ...source,
1805
+ id: generateId("conv"),
1806
+ title: source.title ? `${source.title} (fork)` : undefined,
1807
+ forkedFromId: source.id,
1808
+ createdAt: now,
1809
+ updatedAt: now,
1810
+ messages: forkedMessages
1811
+ };
1812
+ for (const m of forked.messages) {
1813
+ m.conversationId = forked.id;
1814
+ }
1815
+ this.conversations.set(forked.id, forked);
1816
+ return forked;
1817
+ }
1818
+ async truncateAfter(conversationId, messageId) {
1819
+ const conv = this.conversations.get(conversationId);
1820
+ if (!conv)
1821
+ return null;
1822
+ const idx = conv.messages.findIndex((m) => m.id === messageId);
1823
+ if (idx === -1)
1824
+ return null;
1825
+ conv.messages = conv.messages.slice(0, idx + 1);
1826
+ conv.updatedAt = new Date;
1827
+ return conv;
1828
+ }
1829
+ async search(query, limit = 20) {
1830
+ const lowerQuery = query.toLowerCase();
1831
+ const results = [];
1832
+ for (const conversation of this.conversations.values()) {
1833
+ if (conversation.title?.toLowerCase().includes(lowerQuery)) {
1834
+ results.push(conversation);
1835
+ continue;
1836
+ }
1837
+ const hasMatch = conversation.messages.some((m) => m.content.toLowerCase().includes(lowerQuery));
1838
+ if (hasMatch) {
1839
+ results.push(conversation);
1840
+ }
1841
+ if (results.length >= limit)
1842
+ break;
1843
+ }
1844
+ return results;
1845
+ }
1846
+ clear() {
1847
+ this.conversations.clear();
1848
+ }
1849
+ }
1850
+ function createInMemoryConversationStore() {
1851
+ return new InMemoryConversationStore;
1852
+ }
1853
+
1854
+ // src/core/workflow-tools.ts
1855
+ import { tool } from "ai";
1856
+ import { z } from "zod";
1857
+ import {
1858
+ WorkflowComposer,
1859
+ validateExtension
1860
+ } from "@contractspec/lib.workflow-composer";
1861
+ var StepTypeSchema = z.enum(["human", "automation", "decision"]);
1862
+ var StepActionSchema = z.object({
1863
+ operation: z.object({
1864
+ name: z.string(),
1865
+ version: z.number()
1866
+ }).optional(),
1867
+ form: z.object({
1868
+ key: z.string(),
1869
+ version: z.number()
1870
+ }).optional()
1871
+ }).optional();
1872
+ var StepSchema = z.object({
1873
+ id: z.string(),
1874
+ type: StepTypeSchema,
1875
+ label: z.string(),
1876
+ description: z.string().optional(),
1877
+ action: StepActionSchema
1878
+ });
1879
+ var StepInjectionSchema = z.object({
1880
+ after: z.string().optional(),
1881
+ before: z.string().optional(),
1882
+ inject: StepSchema,
1883
+ transitionTo: z.string().optional(),
1884
+ transitionFrom: z.string().optional(),
1885
+ when: z.string().optional()
1886
+ });
1887
+ var WorkflowExtensionInputSchema = z.object({
1888
+ workflow: z.string(),
1889
+ tenantId: z.string().optional(),
1890
+ role: z.string().optional(),
1891
+ priority: z.number().optional(),
1892
+ customSteps: z.array(StepInjectionSchema).optional(),
1893
+ hiddenSteps: z.array(z.string()).optional()
1894
+ });
1895
+ function createWorkflowTools(config) {
1896
+ const { baseWorkflows, composer } = config;
1897
+ const baseByKey = new Map(baseWorkflows.map((b) => [b.meta.key, b]));
1898
+ const createWorkflowExtensionTool = tool({
1899
+ description: "Create or validate a workflow extension. Use when the user asks to add steps, modify a workflow, or create a tenant-specific extension. The extension targets an existing base workflow.",
1900
+ inputSchema: WorkflowExtensionInputSchema,
1901
+ execute: async (input) => {
1902
+ const extension = {
1903
+ workflow: input.workflow,
1904
+ tenantId: input.tenantId,
1905
+ role: input.role,
1906
+ priority: input.priority,
1907
+ customSteps: input.customSteps,
1908
+ hiddenSteps: input.hiddenSteps
1909
+ };
1910
+ const base = baseByKey.get(input.workflow);
1911
+ if (!base) {
1912
+ return {
1913
+ success: false,
1914
+ error: `Base workflow "${input.workflow}" not found. Available: ${Array.from(baseByKey.keys()).join(", ")}`,
1915
+ extension
1916
+ };
1917
+ }
1918
+ try {
1919
+ validateExtension(extension, base);
1920
+ return {
1921
+ success: true,
1922
+ message: "Extension validated successfully",
1923
+ extension
1924
+ };
1925
+ } catch (err) {
1926
+ return {
1927
+ success: false,
1928
+ error: err instanceof Error ? err.message : String(err),
1929
+ extension
1930
+ };
1931
+ }
1932
+ }
1933
+ });
1934
+ const composeWorkflowInputSchema = z.object({
1935
+ workflowKey: z.string().describe("Base workflow meta.key"),
1936
+ tenantId: z.string().optional(),
1937
+ role: z.string().optional(),
1938
+ extensions: z.array(WorkflowExtensionInputSchema).optional().describe("Extensions to register before composing")
1939
+ });
1940
+ const composeWorkflowTool = tool({
1941
+ description: "Compose a workflow by applying registered extensions to a base workflow. Returns the composed WorkflowSpec.",
1942
+ inputSchema: composeWorkflowInputSchema,
1943
+ execute: async (input) => {
1944
+ const base = baseByKey.get(input.workflowKey);
1945
+ if (!base) {
1946
+ return {
1947
+ success: false,
1948
+ error: `Base workflow "${input.workflowKey}" not found. Available: ${Array.from(baseByKey.keys()).join(", ")}`
1949
+ };
1950
+ }
1951
+ const comp = composer ?? new WorkflowComposer;
1952
+ if (input.extensions?.length) {
1953
+ for (const ext of input.extensions) {
1954
+ comp.register({
1955
+ workflow: ext.workflow,
1956
+ tenantId: ext.tenantId,
1957
+ role: ext.role,
1958
+ priority: ext.priority,
1959
+ customSteps: ext.customSteps,
1960
+ hiddenSteps: ext.hiddenSteps
1961
+ });
1962
+ }
1963
+ }
1964
+ try {
1965
+ const composed = comp.compose({
1966
+ base,
1967
+ tenantId: input.tenantId,
1968
+ role: input.role
1969
+ });
1970
+ return {
1971
+ success: true,
1972
+ workflow: composed,
1973
+ meta: composed.meta,
1974
+ stepIds: composed.definition.steps.map((s) => s.id)
1975
+ };
1976
+ } catch (err) {
1977
+ return {
1978
+ success: false,
1979
+ error: err instanceof Error ? err.message : String(err)
1980
+ };
1981
+ }
1982
+ }
1983
+ });
1984
+ const generateWorkflowSpecCodeInputSchema = z.object({
1985
+ workflowKey: z.string().describe("Workflow meta.key"),
1986
+ composedSteps: z.array(z.object({
1987
+ id: z.string(),
1988
+ type: z.enum(["human", "automation", "decision"]),
1989
+ label: z.string(),
1990
+ description: z.string().optional()
1991
+ })).optional().describe("Steps to include; if omitted, uses the base workflow")
1992
+ });
1993
+ const generateWorkflowSpecCodeTool = tool({
1994
+ description: "Generate TypeScript code for a workflow spec. Use after composing a workflow to output the spec as code the user can save.",
1995
+ inputSchema: generateWorkflowSpecCodeInputSchema,
1996
+ execute: async (input) => {
1997
+ const base = baseByKey.get(input.workflowKey);
1998
+ if (!base) {
1999
+ return {
2000
+ success: false,
2001
+ error: `Base workflow "${input.workflowKey}" not found. Available: ${Array.from(baseByKey.keys()).join(", ")}`,
2002
+ code: null
2003
+ };
2004
+ }
2005
+ const steps = input.composedSteps ?? base.definition.steps;
2006
+ const specVarName = toPascalCase((base.meta.key.split(".").pop() ?? "Workflow") + "") + "Workflow";
2007
+ const stepsCode = steps.map((s) => ` {
2008
+ id: '${s.id}',
2009
+ type: '${s.type}',
2010
+ label: '${escapeString(s.label)}',${s.description ? `
2011
+ description: '${escapeString(s.description)}',` : ""}
2012
+ }`).join(`,
2013
+ `);
2014
+ const meta = base.meta;
2015
+ const transitionsJson = JSON.stringify(base.definition.transitions, null, 6);
2016
+ const code = `import type { WorkflowSpec } from '@contractspec/lib.contracts-spec/workflow';
2017
+
2018
+ /**
2019
+ * Workflow: ${base.meta.key}
2020
+ * Generated via AI chat workflow tools.
2021
+ */
2022
+ export const ${specVarName}: WorkflowSpec = {
2023
+ meta: {
2024
+ key: '${base.meta.key}',
2025
+ version: '${String(base.meta.version)}',
2026
+ title: '${escapeString(meta.title ?? base.meta.key)}',
2027
+ description: '${escapeString(meta.description ?? "")}',
2028
+ },
2029
+ definition: {
2030
+ entryStepId: '${base.definition.entryStepId ?? base.definition.steps[0]?.id ?? ""}',
2031
+ steps: [
2032
+ ${stepsCode}
2033
+ ],
2034
+ transitions: ${transitionsJson},
2035
+ },
2036
+ };
2037
+ `;
2038
+ return {
2039
+ success: true,
2040
+ code,
2041
+ workflowKey: input.workflowKey
2042
+ };
2043
+ }
2044
+ });
2045
+ return {
2046
+ create_workflow_extension: createWorkflowExtensionTool,
2047
+ compose_workflow: composeWorkflowTool,
2048
+ generate_workflow_spec_code: generateWorkflowSpecCodeTool
2049
+ };
2050
+ }
2051
+ function toPascalCase(value) {
2052
+ return value.split(/[-_.]/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
2053
+ }
2054
+ function escapeString(value) {
2055
+ return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
2056
+ }
2057
+
2058
+ // src/core/contracts-context.ts
2059
+ function buildContractsContextPrompt(config) {
2060
+ const parts = [];
2061
+ if (!config.agentSpecs?.length && !config.dataViewSpecs?.length && !config.formSpecs?.length && !config.presentationSpecs?.length && !config.operationRefs?.length) {
2062
+ return "";
2063
+ }
2064
+ parts.push(`
2065
+
2066
+ ## Available resources`);
2067
+ if (config.agentSpecs?.length) {
2068
+ parts.push(`
2069
+ ### Agent tools`);
2070
+ for (const agent of config.agentSpecs) {
2071
+ const toolNames = agent.tools?.map((t) => t.name).join(", ") ?? "none";
2072
+ parts.push(`- **${agent.key}**: tools: ${toolNames}`);
2073
+ }
2074
+ }
2075
+ if (config.dataViewSpecs?.length) {
2076
+ parts.push(`
2077
+ ### Data views`);
2078
+ for (const dv of config.dataViewSpecs) {
2079
+ parts.push(`- **${dv.key}**: ${dv.meta.title ?? dv.key}`);
2080
+ }
2081
+ }
2082
+ if (config.formSpecs?.length) {
2083
+ parts.push(`
2084
+ ### Forms`);
2085
+ for (const form of config.formSpecs) {
2086
+ parts.push(`- **${form.key}**: ${form.meta.title ?? form.key}`);
2087
+ }
2088
+ }
2089
+ if (config.presentationSpecs?.length) {
2090
+ parts.push(`
2091
+ ### Presentations`);
2092
+ for (const pres of config.presentationSpecs) {
2093
+ parts.push(`- **${pres.key}**: ${pres.meta.title ?? pres.key} (targets: ${pres.targets?.join(", ") ?? "react"})`);
2094
+ }
2095
+ }
2096
+ if (config.operationRefs?.length) {
2097
+ parts.push(`
2098
+ ### Operations`);
2099
+ for (const op of config.operationRefs) {
2100
+ parts.push(`- **${op.key}@${op.version}**`);
2101
+ }
2102
+ }
2103
+ parts.push(`
2104
+ Use the available tools to invoke operations, query data views, or propose surface changes when appropriate.`);
2105
+ return parts.join(`
2106
+ `);
2107
+ }
2108
+
2109
+ // src/core/agent-tools-adapter.ts
2110
+ import { tool as tool2 } from "ai";
2111
+ import { z as z2 } from "zod";
2112
+ function getInputSchema(_schema) {
2113
+ return z2.object({}).passthrough();
2114
+ }
2115
+ function agentToolConfigsToToolSet(configs, handlers) {
2116
+ const result = {};
2117
+ for (const config of configs) {
2118
+ const handler = handlers?.[config.name];
2119
+ const inputSchema = getInputSchema(config.schema);
2120
+ result[config.name] = tool2({
2121
+ description: config.description ?? config.name,
2122
+ inputSchema,
2123
+ execute: async (input) => {
2124
+ if (!handler) {
2125
+ return {
2126
+ status: "unimplemented",
2127
+ message: "Wire handler in host",
2128
+ toolName: config.name
2129
+ };
2130
+ }
2131
+ try {
2132
+ const output = await Promise.resolve(handler(input));
2133
+ return typeof output === "string" ? output : output;
2134
+ } catch (err) {
2135
+ return {
2136
+ status: "error",
2137
+ error: err instanceof Error ? err.message : String(err),
2138
+ toolName: config.name
2139
+ };
2140
+ }
2141
+ }
2142
+ });
2143
+ }
2144
+ return result;
2145
+ }
2146
+
2147
+ // src/core/surface-planner-tools.ts
2148
+ import { tool as tool3 } from "ai";
2149
+ import { z as z3 } from "zod";
2150
+ import { validatePatchProposal } from "@contractspec/lib.surface-runtime/spec/validate-surface-patch";
2151
+ import { buildSurfacePatchProposal } from "@contractspec/lib.surface-runtime/runtime/planner-tools";
2152
+ var VALID_OPS = [
2153
+ "insert-node",
2154
+ "replace-node",
2155
+ "remove-node",
2156
+ "move-node",
2157
+ "resize-panel",
2158
+ "set-layout",
2159
+ "reveal-field",
2160
+ "hide-field",
2161
+ "promote-action",
2162
+ "set-focus"
2163
+ ];
2164
+ var DEFAULT_NODE_KINDS = [
2165
+ "entity-section",
2166
+ "entity-card",
2167
+ "data-view",
2168
+ "assistant-panel",
2169
+ "chat-thread",
2170
+ "action-bar",
2171
+ "timeline",
2172
+ "table",
2173
+ "rich-doc",
2174
+ "form",
2175
+ "chart",
2176
+ "custom-widget"
2177
+ ];
2178
+ function collectSlotIdsFromRegion(node) {
2179
+ const ids = [];
2180
+ if (node.type === "slot") {
2181
+ ids.push(node.slotId);
2182
+ }
2183
+ if (node.type === "panel-group" || node.type === "stack") {
2184
+ for (const child of node.children) {
2185
+ ids.push(...collectSlotIdsFromRegion(child));
2186
+ }
2187
+ }
2188
+ if (node.type === "tabs") {
2189
+ for (const tab of node.tabs) {
2190
+ ids.push(...collectSlotIdsFromRegion(tab.child));
2191
+ }
2192
+ }
2193
+ if (node.type === "floating") {
2194
+ ids.push(node.anchorSlotId);
2195
+ ids.push(...collectSlotIdsFromRegion(node.child));
2196
+ }
2197
+ return ids;
2198
+ }
2199
+ function deriveConstraints(plan) {
2200
+ const slotIds = collectSlotIdsFromRegion(plan.layoutRoot);
2201
+ const uniqueSlots = [...new Set(slotIds)];
2202
+ return {
2203
+ allowedOps: VALID_OPS,
2204
+ allowedSlots: uniqueSlots.length > 0 ? uniqueSlots : ["assistant", "primary"],
2205
+ allowedNodeKinds: DEFAULT_NODE_KINDS
2206
+ };
2207
+ }
2208
+ var ProposePatchInputSchema = z3.object({
2209
+ proposalId: z3.string().describe("Unique proposal identifier"),
2210
+ ops: z3.array(z3.object({
2211
+ op: z3.enum([
2212
+ "insert-node",
2213
+ "replace-node",
2214
+ "remove-node",
2215
+ "move-node",
2216
+ "resize-panel",
2217
+ "set-layout",
2218
+ "reveal-field",
2219
+ "hide-field",
2220
+ "promote-action",
2221
+ "set-focus"
2222
+ ]),
2223
+ slotId: z3.string().optional(),
2224
+ nodeId: z3.string().optional(),
2225
+ toSlotId: z3.string().optional(),
2226
+ index: z3.number().optional(),
2227
+ node: z3.object({
2228
+ nodeId: z3.string(),
2229
+ kind: z3.string(),
2230
+ title: z3.string().optional(),
2231
+ props: z3.record(z3.string(), z3.unknown()).optional(),
2232
+ children: z3.array(z3.unknown()).optional()
2233
+ }).optional(),
2234
+ persistKey: z3.string().optional(),
2235
+ sizes: z3.array(z3.number()).optional(),
2236
+ layoutId: z3.string().optional(),
2237
+ fieldId: z3.string().optional(),
2238
+ actionId: z3.string().optional(),
2239
+ placement: z3.enum(["header", "inline", "context", "assistant"]).optional(),
2240
+ targetId: z3.string().optional()
2241
+ }))
2242
+ });
2243
+ function createSurfacePlannerTools(config) {
2244
+ const { plan, constraints, onPatchProposal } = config;
2245
+ const resolvedConstraints = constraints ?? deriveConstraints(plan);
2246
+ const proposePatchTool = tool3({
2247
+ description: "Propose surface patches (layout changes, node insertions, etc.) for user approval. " + "Only use allowed ops, slots, and node kinds from the planner context.",
2248
+ inputSchema: ProposePatchInputSchema,
2249
+ execute: async (input) => {
2250
+ const ops = input.ops;
2251
+ try {
2252
+ validatePatchProposal(ops, resolvedConstraints);
2253
+ const proposal = buildSurfacePatchProposal(input.proposalId, ops);
2254
+ onPatchProposal?.(proposal);
2255
+ return {
2256
+ success: true,
2257
+ proposalId: proposal.proposalId,
2258
+ opsCount: proposal.ops.length,
2259
+ message: "Patch proposal validated; awaiting user approval"
2260
+ };
2261
+ } catch (err) {
2262
+ return {
2263
+ success: false,
2264
+ error: err instanceof Error ? err.message : String(err),
2265
+ proposalId: input.proposalId
2266
+ };
2267
+ }
2268
+ }
2269
+ });
2270
+ return {
2271
+ "propose-patch": proposePatchTool
2272
+ };
2273
+ }
2274
+ function buildPlannerPromptInput(plan) {
2275
+ const constraints = deriveConstraints(plan);
2276
+ return {
2277
+ bundleMeta: {
2278
+ key: plan.bundleKey,
2279
+ version: "0.0.0",
2280
+ title: plan.bundleKey
2281
+ },
2282
+ surfaceId: plan.surfaceId,
2283
+ allowedPatchOps: constraints.allowedOps,
2284
+ allowedSlots: [...constraints.allowedSlots],
2285
+ allowedNodeKinds: [...constraints.allowedNodeKinds],
2286
+ actions: plan.actions.map((a) => ({ actionId: a.actionId, title: a.title })),
2287
+ preferences: {
2288
+ guidance: "hints",
2289
+ density: "standard",
2290
+ dataDepth: "detailed",
2291
+ control: "standard",
2292
+ media: "text",
2293
+ pace: "balanced",
2294
+ narrative: "top-down"
2295
+ }
2296
+ };
2297
+ }
2298
+
2299
+ // src/core/chat-service.ts
2300
+ import { compilePlannerPrompt } from "@contractspec/lib.surface-runtime/runtime/planner-prompt";
2301
+ var DEFAULT_SYSTEM_PROMPT = `You are ContractSpec AI, an expert coding assistant specialized in ContractSpec development.
2302
+
2303
+ Your capabilities:
2304
+ - Help users create, modify, and understand ContractSpec specifications
2305
+ - Generate code that follows ContractSpec patterns and best practices
2306
+ - Explain concepts from the ContractSpec documentation
2307
+ - Suggest improvements and identify issues in specs and implementations
2308
+
2309
+ Guidelines:
2310
+ - Be concise but thorough
2311
+ - Provide code examples when helpful
2312
+ - Reference relevant ContractSpec concepts and patterns
2313
+ - Ask clarifying questions when the user's intent is unclear
2314
+ - When suggesting code changes, explain the rationale`;
2315
+ var WORKFLOW_TOOLS_PROMPT = `
2316
+
2317
+ Workflow creation: You can create and modify workflows. Use create_workflow_extension when the user asks to add steps, change a workflow, or create a tenant-specific extension. Use compose_workflow to apply extensions to a base workflow. Use generate_workflow_spec_code to output TypeScript for the user to save.`;
2318
+
2319
+ class ChatService {
2320
+ provider;
2321
+ context;
2322
+ store;
2323
+ systemPrompt;
2324
+ maxHistoryMessages;
2325
+ onUsage;
2326
+ tools;
2327
+ thinkingLevel;
2328
+ sendReasoning;
2329
+ sendSources;
2330
+ modelSelector;
2331
+ constructor(config) {
2332
+ this.provider = config.provider;
2333
+ this.context = config.context;
2334
+ this.store = config.store ?? new InMemoryConversationStore;
2335
+ this.systemPrompt = this.buildSystemPrompt(config);
2336
+ this.maxHistoryMessages = config.maxHistoryMessages ?? 20;
2337
+ this.onUsage = config.onUsage;
2338
+ this.tools = this.mergeTools(config);
2339
+ this.thinkingLevel = config.thinkingLevel;
2340
+ this.modelSelector = config.modelSelector;
2341
+ this.sendReasoning = config.sendReasoning ?? (config.thinkingLevel != null && config.thinkingLevel !== "instant");
2342
+ this.sendSources = config.sendSources ?? false;
2343
+ }
2344
+ buildSystemPrompt(config) {
2345
+ let base = config.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
2346
+ if (config.workflowToolsConfig?.baseWorkflows?.length) {
2347
+ base += WORKFLOW_TOOLS_PROMPT;
2348
+ }
2349
+ const contractsPrompt = buildContractsContextPrompt(config.contractsContext ?? {});
2350
+ if (contractsPrompt) {
2351
+ base += contractsPrompt;
2352
+ }
2353
+ if (config.surfacePlanConfig?.plan) {
2354
+ const plannerInput = buildPlannerPromptInput(config.surfacePlanConfig.plan);
2355
+ base += `
2356
+
2357
+ ` + compilePlannerPrompt(plannerInput);
2358
+ }
2359
+ return base;
2360
+ }
2361
+ mergeTools(config) {
2362
+ let merged = config.tools ?? {};
2363
+ const wfConfig = config.workflowToolsConfig;
2364
+ if (wfConfig?.baseWorkflows?.length) {
2365
+ const workflowTools = createWorkflowTools({
2366
+ baseWorkflows: wfConfig.baseWorkflows,
2367
+ composer: wfConfig.composer
2368
+ });
2369
+ merged = { ...merged, ...workflowTools };
2370
+ }
2371
+ const contractsCtx = config.contractsContext;
2372
+ if (contractsCtx?.agentSpecs?.length) {
2373
+ const allTools = [];
2374
+ for (const agent of contractsCtx.agentSpecs) {
2375
+ if (agent.tools?.length)
2376
+ allTools.push(...agent.tools);
2377
+ }
2378
+ if (allTools.length > 0) {
2379
+ const agentTools = agentToolConfigsToToolSet(allTools);
2380
+ merged = { ...merged, ...agentTools };
2381
+ }
2382
+ }
2383
+ const surfaceConfig = config.surfacePlanConfig;
2384
+ if (surfaceConfig?.plan) {
2385
+ const plannerTools = createSurfacePlannerTools({
2386
+ plan: surfaceConfig.plan,
2387
+ onPatchProposal: surfaceConfig.onPatchProposal
2388
+ });
2389
+ merged = { ...merged, ...plannerTools };
2390
+ }
2391
+ if (config.mcpTools && Object.keys(config.mcpTools).length > 0) {
2392
+ merged = { ...merged, ...config.mcpTools };
2393
+ }
2394
+ return Object.keys(merged).length > 0 ? merged : undefined;
2395
+ }
2396
+ async resolveModel() {
2397
+ if (this.modelSelector) {
2398
+ const dimension = this.thinkingLevelToDimension(this.thinkingLevel);
2399
+ const { model, selection } = await this.modelSelector.selectAndCreate({
2400
+ taskDimension: dimension
2401
+ });
2402
+ return { model, providerName: selection.providerKey };
2403
+ }
2404
+ return {
2405
+ model: this.provider.getModel(),
2406
+ providerName: this.provider.name
2407
+ };
2408
+ }
2409
+ thinkingLevelToDimension(level) {
2410
+ if (!level || level === "instant")
2411
+ return "latency";
2412
+ return "reasoning";
2413
+ }
2414
+ async send(options) {
2415
+ let conversation;
2416
+ if (options.conversationId) {
2417
+ const existing = await this.store.get(options.conversationId);
2418
+ if (!existing) {
2419
+ throw new Error(`Conversation ${options.conversationId} not found`);
2420
+ }
2421
+ conversation = existing;
2422
+ } else {
2423
+ conversation = await this.store.create({
2424
+ status: "active",
2425
+ provider: this.provider.name,
2426
+ model: this.provider.model,
2427
+ messages: [],
2428
+ workspacePath: this.context?.workspacePath
2429
+ });
2430
+ }
2431
+ if (!options.skipUserAppend) {
2432
+ await this.store.appendMessage(conversation.id, {
2433
+ role: "user",
2434
+ content: options.content,
2435
+ status: "completed",
2436
+ attachments: options.attachments
2437
+ });
2438
+ }
2439
+ conversation = await this.store.get(conversation.id) ?? conversation;
2440
+ const messages = this.buildMessages(conversation, options);
2441
+ const { model, providerName } = await this.resolveModel();
2442
+ const providerOptions = getProviderOptions(this.thinkingLevel, providerName);
2443
+ try {
2444
+ const result = await generateText({
2445
+ model,
2446
+ messages,
2447
+ system: this.systemPrompt,
2448
+ tools: this.tools,
2449
+ providerOptions: Object.keys(providerOptions).length > 0 ? providerOptions : undefined
2450
+ });
2451
+ const assistantMessage = await this.store.appendMessage(conversation.id, {
2452
+ role: "assistant",
2453
+ content: result.text,
2454
+ status: "completed"
2455
+ });
2456
+ const updatedConversation = await this.store.get(conversation.id);
2457
+ if (!updatedConversation) {
2458
+ throw new Error("Conversation lost after update");
2459
+ }
2460
+ return {
2461
+ message: assistantMessage,
2462
+ conversation: updatedConversation
2463
+ };
2464
+ } catch (error) {
2465
+ await this.store.appendMessage(conversation.id, {
2466
+ role: "assistant",
2467
+ content: "",
2468
+ status: "error",
2469
+ error: {
2470
+ code: "generation_failed",
2471
+ message: error instanceof Error ? error.message : String(error)
2472
+ }
2473
+ });
2474
+ throw error;
2475
+ }
2476
+ }
2477
+ async stream(options) {
2478
+ let conversation;
2479
+ if (options.conversationId) {
2480
+ const existing = await this.store.get(options.conversationId);
2481
+ if (!existing) {
2482
+ throw new Error(`Conversation ${options.conversationId} not found`);
2483
+ }
2484
+ conversation = existing;
2485
+ } else {
2486
+ conversation = await this.store.create({
2487
+ status: "active",
2488
+ provider: this.provider.name,
2489
+ model: this.provider.model,
2490
+ messages: [],
2491
+ workspacePath: this.context?.workspacePath
2492
+ });
2493
+ }
2494
+ if (!options.skipUserAppend) {
2495
+ await this.store.appendMessage(conversation.id, {
2496
+ role: "user",
2497
+ content: options.content,
2498
+ status: "completed",
2499
+ attachments: options.attachments
2500
+ });
2501
+ }
2502
+ conversation = await this.store.get(conversation.id) ?? conversation;
2503
+ const assistantMessage = await this.store.appendMessage(conversation.id, {
2504
+ role: "assistant",
2505
+ content: "",
2506
+ status: "streaming"
2507
+ });
2508
+ const messages = this.buildMessages(conversation, options);
2509
+ const { model, providerName } = await this.resolveModel();
2510
+ const systemPrompt = this.systemPrompt;
2511
+ const tools = this.tools;
2512
+ const store = this.store;
2513
+ const onUsage = this.onUsage;
2514
+ const streamProviderOptions = getProviderOptions(this.thinkingLevel, providerName);
2515
+ async function* streamGenerator() {
2516
+ let fullContent = "";
2517
+ let fullReasoning = "";
2518
+ const toolCallsMap = new Map;
2519
+ const sources = [];
2520
+ try {
2521
+ const result = streamText({
2522
+ model,
2523
+ messages,
2524
+ system: systemPrompt,
2525
+ tools,
2526
+ providerOptions: Object.keys(streamProviderOptions).length > 0 ? streamProviderOptions : undefined
2527
+ });
2528
+ for await (const part of result.fullStream) {
2529
+ if (part.type === "text-delta") {
2530
+ const text = part.text ?? "";
2531
+ if (text) {
2532
+ fullContent += text;
2533
+ yield { type: "text", content: text };
2534
+ }
2535
+ } else if (part.type === "reasoning-delta") {
2536
+ const text = part.text ?? "";
2537
+ if (text) {
2538
+ fullReasoning += text;
2539
+ yield { type: "reasoning", content: text };
2540
+ }
2541
+ } else if (part.type === "source") {
2542
+ const src = part;
2543
+ const source = {
2544
+ id: src.id,
2545
+ title: src.title ?? "",
2546
+ url: src.url,
2547
+ type: "web"
2548
+ };
2549
+ sources.push(source);
2550
+ yield { type: "source", source };
2551
+ } else if (part.type === "tool-call") {
2552
+ const toolCall = {
2553
+ id: part.toolCallId,
2554
+ name: part.toolName,
2555
+ args: part.input ?? {},
2556
+ status: "running"
2557
+ };
2558
+ toolCallsMap.set(part.toolCallId, toolCall);
2559
+ yield { type: "tool_call", toolCall };
2560
+ } else if (part.type === "tool-result") {
2561
+ const tc = toolCallsMap.get(part.toolCallId);
2562
+ if (tc) {
2563
+ tc.result = part.output;
2564
+ tc.status = "completed";
2565
+ }
2566
+ yield {
2567
+ type: "tool_result",
2568
+ toolResult: {
2569
+ toolCallId: part.toolCallId,
2570
+ toolName: part.toolName,
2571
+ result: part.output
2572
+ }
2573
+ };
2574
+ } else if (part.type === "tool-error") {
2575
+ const tc = toolCallsMap.get(part.toolCallId);
2576
+ if (tc) {
2577
+ tc.status = "error";
2578
+ tc.error = part.error ?? "Tool execution failed";
2579
+ }
2580
+ } else if (part.type === "finish") {
2581
+ const usage = part.usage;
2582
+ const inputTokens = usage?.inputTokens ?? 0;
2583
+ const outputTokens = usage?.completionTokens ?? 0;
2584
+ await store.updateMessage(conversation.id, assistantMessage.id, {
2585
+ content: fullContent,
2586
+ status: "completed",
2587
+ reasoning: fullReasoning || undefined,
2588
+ sources: sources.length > 0 ? sources : undefined,
2589
+ toolCalls: toolCallsMap.size > 0 ? Array.from(toolCallsMap.values()) : undefined,
2590
+ usage: usage ? { inputTokens, outputTokens } : undefined
2591
+ });
2592
+ onUsage?.({ inputTokens, outputTokens });
2593
+ yield {
2594
+ type: "done",
2595
+ usage: usage ? { inputTokens, outputTokens } : undefined
2596
+ };
2597
+ return;
2598
+ }
2599
+ }
2600
+ await store.updateMessage(conversation.id, assistantMessage.id, {
2601
+ content: fullContent,
2602
+ status: "completed",
2603
+ reasoning: fullReasoning || undefined,
2604
+ sources: sources.length > 0 ? sources : undefined,
2605
+ toolCalls: toolCallsMap.size > 0 ? Array.from(toolCallsMap.values()) : undefined
2606
+ });
2607
+ yield { type: "done" };
2608
+ } catch (error) {
2609
+ await store.updateMessage(conversation.id, assistantMessage.id, {
2610
+ content: fullContent,
2611
+ status: "error",
2612
+ error: {
2613
+ code: "stream_failed",
2614
+ message: error instanceof Error ? error.message : String(error)
2615
+ }
2616
+ });
2617
+ yield {
2618
+ type: "error",
2619
+ error: {
2620
+ code: "stream_failed",
2621
+ message: error instanceof Error ? error.message : String(error)
2622
+ }
2623
+ };
2624
+ }
2625
+ }
2626
+ return {
2627
+ conversationId: conversation.id,
2628
+ messageId: assistantMessage.id,
2629
+ stream: streamGenerator()
2630
+ };
2631
+ }
2632
+ async getConversation(conversationId) {
2633
+ return this.store.get(conversationId);
2634
+ }
2635
+ async listConversations(options) {
2636
+ return this.store.list({
2637
+ status: "active",
2638
+ ...options
2639
+ });
2640
+ }
2641
+ async updateConversation(conversationId, updates) {
2642
+ return this.store.update(conversationId, updates);
2643
+ }
2644
+ async forkConversation(conversationId, upToMessageId) {
2645
+ return this.store.fork(conversationId, upToMessageId);
2646
+ }
2647
+ async updateMessage(conversationId, messageId, updates) {
2648
+ return this.store.updateMessage(conversationId, messageId, updates);
2649
+ }
2650
+ async truncateAfter(conversationId, messageId) {
2651
+ return this.store.truncateAfter(conversationId, messageId);
2652
+ }
2653
+ async deleteConversation(conversationId) {
2654
+ return this.store.delete(conversationId);
2655
+ }
2656
+ buildMessages(conversation, _options) {
2657
+ const historyStart = Math.max(0, conversation.messages.length - this.maxHistoryMessages);
2658
+ const messages = [];
2659
+ for (let i = historyStart;i < conversation.messages.length; i++) {
2660
+ const msg = conversation.messages[i];
2661
+ if (!msg)
2662
+ continue;
2663
+ if (msg.role === "user") {
2664
+ let content = msg.content;
2665
+ if (msg.attachments?.length) {
2666
+ const attachmentInfo = msg.attachments.map((a) => {
2667
+ if (a.type === "file" || a.type === "code") {
2668
+ return `
2669
+
2670
+ ### ${a.name}
2671
+ \`\`\`
2672
+ ${a.content ?? ""}
2673
+ \`\`\``;
2674
+ }
2675
+ return `
2676
+
2677
+ [Attachment: ${a.name}]`;
2678
+ }).join("");
2679
+ content += attachmentInfo;
2680
+ }
2681
+ messages.push({ role: "user", content });
2682
+ } else if (msg.role === "assistant") {
2683
+ if (msg.toolCalls?.length) {
2684
+ messages.push({
2685
+ role: "assistant",
2686
+ content: msg.content || "",
2687
+ toolCalls: msg.toolCalls.map((tc) => ({
2688
+ type: "tool-call",
2689
+ toolCallId: tc.id,
2690
+ toolName: tc.name,
2691
+ args: tc.args
2692
+ }))
2693
+ });
2694
+ messages.push({
2695
+ role: "tool",
2696
+ content: msg.toolCalls.map((tc) => ({
2697
+ type: "tool-result",
2698
+ toolCallId: tc.id,
2699
+ toolName: tc.name,
2700
+ output: tc.result
2701
+ }))
2702
+ });
2703
+ } else {
2704
+ messages.push({ role: "assistant", content: msg.content });
2705
+ }
2706
+ }
2707
+ }
2708
+ return messages;
2709
+ }
2710
+ }
2711
+ function createChatService(config) {
2712
+ return new ChatService(config);
2713
+ }
2714
+
2715
+ // src/presentation/hooks/useChat.tsx
2716
+ import {
2717
+ createProvider
2718
+ } from "@contractspec/lib.ai-providers";
2719
+ "use client";
2720
+ function toolsToToolSet(defs) {
2721
+ const result = {};
2722
+ for (const def of defs) {
2723
+ result[def.name] = tool4({
2724
+ description: def.description ?? def.name,
2725
+ inputSchema: z4.object({}).passthrough(),
2726
+ execute: async () => ({})
2727
+ });
2728
+ }
2729
+ return result;
2730
+ }
2731
+ function useChat(options = {}) {
2732
+ const {
2733
+ provider = "openai",
2734
+ mode = "byok",
2735
+ model,
2736
+ apiKey,
2737
+ proxyUrl,
2738
+ conversationId: initialConversationId,
2739
+ store,
2740
+ systemPrompt,
2741
+ streaming = true,
2742
+ onSend,
2743
+ onResponse,
2744
+ onError,
2745
+ onUsage,
2746
+ tools: toolsDefs,
2747
+ thinkingLevel,
2748
+ workflowToolsConfig,
2749
+ modelSelector,
2750
+ contractsContext,
2751
+ surfacePlanConfig,
2752
+ mcpServers,
2753
+ agentMode
2754
+ } = options;
2755
+ const [messages, setMessages] = React11.useState([]);
2756
+ const [mcpTools, setMcpTools] = React11.useState(null);
2757
+ const mcpCleanupRef = React11.useRef(null);
2758
+ const [conversation, setConversation] = React11.useState(null);
2759
+ const [isLoading, setIsLoading] = React11.useState(false);
2760
+ const [error, setError] = React11.useState(null);
2761
+ const [conversationId, setConversationId] = React11.useState(initialConversationId ?? null);
2762
+ const abortControllerRef = React11.useRef(null);
2763
+ const chatServiceRef = React11.useRef(null);
2764
+ React11.useEffect(() => {
2765
+ if (!mcpServers?.length) {
2766
+ setMcpTools(null);
2767
+ return;
2768
+ }
2769
+ let cancelled = false;
2770
+ import("@contractspec/lib.ai-agent/tools/mcp-client").then(({ createMcpToolsets }) => {
2771
+ createMcpToolsets(mcpServers).then(({ tools, cleanup }) => {
2772
+ if (!cancelled) {
2773
+ setMcpTools(tools);
2774
+ mcpCleanupRef.current = cleanup;
2775
+ } else {
2776
+ cleanup().catch(() => {
2777
+ return;
2778
+ });
2779
+ }
2780
+ }).catch(() => {
2781
+ if (!cancelled)
2782
+ setMcpTools(null);
2783
+ });
2784
+ });
2785
+ return () => {
2786
+ cancelled = true;
2787
+ const cleanup = mcpCleanupRef.current;
2788
+ mcpCleanupRef.current = null;
2789
+ if (cleanup)
2790
+ cleanup().catch(() => {
2791
+ return;
2792
+ });
2793
+ setMcpTools(null);
2794
+ };
2795
+ }, [mcpServers]);
2796
+ React11.useEffect(() => {
2797
+ const chatProvider = createProvider({
2798
+ provider,
2799
+ model,
2800
+ apiKey,
2801
+ proxyUrl
2802
+ });
2803
+ chatServiceRef.current = new ChatService({
2804
+ provider: chatProvider,
2805
+ store,
2806
+ systemPrompt,
2807
+ onUsage,
2808
+ tools: toolsDefs?.length ? toolsToToolSet(toolsDefs) : undefined,
2809
+ thinkingLevel,
2810
+ workflowToolsConfig,
2811
+ modelSelector,
2812
+ contractsContext,
2813
+ surfacePlanConfig,
2814
+ mcpTools
2815
+ });
2816
+ }, [
2817
+ provider,
2818
+ mode,
2819
+ model,
2820
+ apiKey,
2821
+ proxyUrl,
2822
+ store,
2823
+ systemPrompt,
2824
+ onUsage,
2825
+ toolsDefs,
2826
+ thinkingLevel,
2827
+ workflowToolsConfig,
2828
+ modelSelector,
2829
+ contractsContext,
2830
+ surfacePlanConfig,
2831
+ mcpTools
2832
+ ]);
2833
+ React11.useEffect(() => {
2834
+ if (!conversationId || !chatServiceRef.current)
2835
+ return;
2836
+ const loadConversation = async () => {
2837
+ if (!chatServiceRef.current)
2838
+ return;
2839
+ const conv = await chatServiceRef.current.getConversation(conversationId);
2840
+ if (conv) {
2841
+ setConversation(conv);
2842
+ setMessages(conv.messages);
2843
+ }
2844
+ };
2845
+ loadConversation().catch(console.error);
2846
+ }, [conversationId]);
2847
+ const sendMessage = React11.useCallback(async (content, attachments, opts) => {
2848
+ if (agentMode?.agent) {
2849
+ setIsLoading(true);
2850
+ setError(null);
2851
+ abortControllerRef.current = new AbortController;
2852
+ try {
2853
+ if (!opts?.skipUserAppend) {
2854
+ const userMessage = {
2855
+ id: `msg_${Date.now()}`,
2856
+ conversationId: conversationId ?? "",
2857
+ role: "user",
2858
+ content,
2859
+ status: "completed",
2860
+ createdAt: new Date,
2861
+ updatedAt: new Date,
2862
+ attachments
2863
+ };
2864
+ setMessages((prev) => [...prev, userMessage]);
2865
+ onSend?.(userMessage);
2866
+ }
2867
+ const result = await agentMode.agent.generate({
2868
+ prompt: content,
2869
+ signal: abortControllerRef.current.signal
2870
+ });
2871
+ const toolCallsMap = new Map;
2872
+ for (const tc of result.toolCalls ?? []) {
2873
+ const tr = result.toolResults?.find((r) => r.toolCallId === tc.toolCallId);
2874
+ toolCallsMap.set(tc.toolCallId, {
2875
+ id: tc.toolCallId,
2876
+ name: tc.toolName,
2877
+ args: tc.args ?? {},
2878
+ result: tr?.output,
2879
+ status: "completed"
2880
+ });
2881
+ }
2882
+ const assistantMessage = {
2883
+ id: `msg_${Date.now()}_a`,
2884
+ conversationId: conversationId ?? "",
2885
+ role: "assistant",
2886
+ content: result.text,
2887
+ status: "completed",
2888
+ createdAt: new Date,
2889
+ updatedAt: new Date,
2890
+ toolCalls: toolCallsMap.size ? Array.from(toolCallsMap.values()) : undefined,
2891
+ usage: result.usage
2892
+ };
2893
+ setMessages((prev) => [...prev, assistantMessage]);
2894
+ onResponse?.(assistantMessage);
2895
+ onUsage?.(result.usage ?? { inputTokens: 0, outputTokens: 0 });
2896
+ if (store && !conversationId) {
2897
+ const conv = await store.create({
2898
+ status: "active",
2899
+ provider: "agent",
2900
+ model: "agent",
2901
+ messages: []
2902
+ });
2903
+ if (!opts?.skipUserAppend) {
2904
+ await store.appendMessage(conv.id, {
2905
+ role: "user",
2906
+ content,
2907
+ status: "completed",
2908
+ attachments
2909
+ });
2910
+ }
2911
+ await store.appendMessage(conv.id, {
2912
+ role: "assistant",
2913
+ content: result.text,
2914
+ status: "completed",
2915
+ toolCalls: assistantMessage.toolCalls,
2916
+ usage: result.usage
2917
+ });
2918
+ const updated = await store.get(conv.id);
2919
+ if (updated)
2920
+ setConversation(updated);
2921
+ setConversationId(conv.id);
2922
+ }
2923
+ } catch (err) {
2924
+ setError(err instanceof Error ? err : new Error(String(err)));
2925
+ onError?.(err instanceof Error ? err : new Error(String(err)));
2926
+ } finally {
2927
+ setIsLoading(false);
2928
+ }
2929
+ return;
2930
+ }
2931
+ if (!chatServiceRef.current) {
2932
+ throw new Error("Chat service not initialized");
2933
+ }
2934
+ setIsLoading(true);
2935
+ setError(null);
2936
+ abortControllerRef.current = new AbortController;
2937
+ try {
2938
+ if (!opts?.skipUserAppend) {
2939
+ const userMessage = {
2940
+ id: `msg_${Date.now()}`,
2941
+ conversationId: conversationId ?? "",
2942
+ role: "user",
2943
+ content,
2944
+ status: "completed",
2945
+ createdAt: new Date,
2946
+ updatedAt: new Date,
2947
+ attachments
2948
+ };
2949
+ setMessages((prev) => [...prev, userMessage]);
2950
+ onSend?.(userMessage);
2951
+ }
2952
+ if (streaming) {
2953
+ const result = await chatServiceRef.current.stream({
2954
+ conversationId: conversationId ?? undefined,
2955
+ content,
2956
+ attachments,
2957
+ skipUserAppend: opts?.skipUserAppend
2958
+ });
2959
+ if (!conversationId && !opts?.skipUserAppend) {
2960
+ setConversationId(result.conversationId);
2961
+ }
2962
+ const assistantMessage = {
2963
+ id: result.messageId,
2964
+ conversationId: result.conversationId,
2965
+ role: "assistant",
2966
+ content: "",
2967
+ status: "streaming",
2968
+ createdAt: new Date,
2969
+ updatedAt: new Date
2970
+ };
2971
+ setMessages((prev) => [...prev, assistantMessage]);
2972
+ let fullContent = "";
2973
+ let fullReasoning = "";
2974
+ const toolCallsMap = new Map;
2975
+ const sources = [];
2976
+ for await (const chunk of result.stream) {
2977
+ if (chunk.type === "text" && chunk.content) {
2978
+ fullContent += chunk.content;
2979
+ setMessages((prev) => prev.map((m) => m.id === result.messageId ? {
2980
+ ...m,
2981
+ content: fullContent,
2982
+ reasoning: fullReasoning || undefined,
2983
+ sources: sources.length ? sources : undefined,
2984
+ toolCalls: toolCallsMap.size ? Array.from(toolCallsMap.values()) : undefined
2985
+ } : m));
2986
+ } else if (chunk.type === "reasoning" && chunk.content) {
2987
+ fullReasoning += chunk.content;
2988
+ setMessages((prev) => prev.map((m) => m.id === result.messageId ? { ...m, reasoning: fullReasoning } : m));
2989
+ } else if (chunk.type === "source" && chunk.source) {
2990
+ sources.push(chunk.source);
2991
+ setMessages((prev) => prev.map((m) => m.id === result.messageId ? { ...m, sources: [...sources] } : m));
2992
+ } else if (chunk.type === "tool_call" && chunk.toolCall) {
2993
+ const tc = chunk.toolCall;
2994
+ const chatTc = {
2995
+ id: tc.id,
2996
+ name: tc.name,
2997
+ args: tc.args,
2998
+ status: "running"
2999
+ };
3000
+ toolCallsMap.set(tc.id, chatTc);
3001
+ setMessages((prev) => prev.map((m) => m.id === result.messageId ? { ...m, toolCalls: Array.from(toolCallsMap.values()) } : m));
3002
+ } else if (chunk.type === "tool_result" && chunk.toolResult) {
3003
+ const tr = chunk.toolResult;
3004
+ const tc = toolCallsMap.get(tr.toolCallId);
3005
+ if (tc) {
3006
+ tc.result = tr.result;
3007
+ tc.status = "completed";
3008
+ }
3009
+ setMessages((prev) => prev.map((m) => m.id === result.messageId ? { ...m, toolCalls: Array.from(toolCallsMap.values()) } : m));
3010
+ } else if (chunk.type === "done") {
3011
+ setMessages((prev) => prev.map((m) => m.id === result.messageId ? {
3012
+ ...m,
3013
+ content: fullContent,
3014
+ reasoning: fullReasoning || undefined,
3015
+ sources: sources.length ? sources : undefined,
3016
+ toolCalls: toolCallsMap.size ? Array.from(toolCallsMap.values()) : undefined,
3017
+ status: "completed",
3018
+ usage: chunk.usage,
3019
+ updatedAt: new Date
3020
+ } : m));
3021
+ onResponse?.(messages.find((m) => m.id === result.messageId) ?? assistantMessage);
3022
+ } else if (chunk.type === "error") {
3023
+ setMessages((prev) => prev.map((m) => m.id === result.messageId ? {
3024
+ ...m,
3025
+ status: "error",
3026
+ error: chunk.error,
3027
+ updatedAt: new Date
3028
+ } : m));
3029
+ if (chunk.error) {
3030
+ const err = new Error(chunk.error.message);
3031
+ setError(err);
3032
+ onError?.(err);
3033
+ }
3034
+ }
3035
+ }
3036
+ } else {
3037
+ const result = await chatServiceRef.current.send({
3038
+ conversationId: conversationId ?? undefined,
3039
+ content,
3040
+ attachments,
3041
+ skipUserAppend: opts?.skipUserAppend
3042
+ });
3043
+ setConversation(result.conversation);
3044
+ setMessages(result.conversation.messages);
3045
+ if (!conversationId) {
3046
+ setConversationId(result.conversation.id);
3047
+ }
3048
+ onResponse?.(result.message);
3049
+ }
3050
+ } catch (err) {
3051
+ const error2 = err instanceof Error ? err : new Error(String(err));
3052
+ setError(error2);
3053
+ onError?.(error2);
3054
+ } finally {
3055
+ setIsLoading(false);
3056
+ abortControllerRef.current = null;
3057
+ }
3058
+ }, [conversationId, streaming, onSend, onResponse, onError, onUsage, messages, agentMode, store]);
3059
+ const clearConversation = React11.useCallback(() => {
3060
+ setMessages([]);
3061
+ setConversation(null);
3062
+ setConversationId(null);
3063
+ setError(null);
3064
+ }, []);
3065
+ const regenerate = React11.useCallback(async () => {
3066
+ const lastUserMessageIndex = messages.findLastIndex((m) => m.role === "user");
3067
+ if (lastUserMessageIndex === -1)
3068
+ return;
3069
+ const lastUserMessage = messages[lastUserMessageIndex];
3070
+ if (!lastUserMessage)
3071
+ return;
3072
+ setMessages((prev) => prev.slice(0, lastUserMessageIndex + 1));
3073
+ await sendMessage(lastUserMessage.content, lastUserMessage.attachments);
3074
+ }, [messages, sendMessage]);
3075
+ const stop = React11.useCallback(() => {
3076
+ abortControllerRef.current?.abort();
3077
+ setIsLoading(false);
3078
+ }, []);
3079
+ const createNewConversation = clearConversation;
3080
+ const editMessage = React11.useCallback(async (messageId, newContent) => {
3081
+ if (!chatServiceRef.current || !conversationId)
3082
+ return;
3083
+ const msg = messages.find((m) => m.id === messageId);
3084
+ if (!msg || msg.role !== "user")
3085
+ return;
3086
+ await chatServiceRef.current.updateMessage(conversationId, messageId, { content: newContent });
3087
+ const truncated = await chatServiceRef.current.truncateAfter(conversationId, messageId);
3088
+ if (truncated) {
3089
+ setMessages(truncated.messages);
3090
+ }
3091
+ await sendMessage(newContent, undefined, { skipUserAppend: true });
3092
+ }, [conversationId, messages, sendMessage]);
3093
+ const forkConversation = React11.useCallback(async (upToMessageId) => {
3094
+ if (!chatServiceRef.current)
3095
+ return null;
3096
+ const idToFork = conversationId ?? conversation?.id;
3097
+ if (!idToFork)
3098
+ return null;
3099
+ try {
3100
+ const forked = await chatServiceRef.current.forkConversation(idToFork, upToMessageId);
3101
+ setConversationId(forked.id);
3102
+ setConversation(forked);
3103
+ setMessages(forked.messages);
3104
+ return forked.id;
3105
+ } catch {
3106
+ return null;
3107
+ }
3108
+ }, [conversationId, conversation]);
3109
+ const updateConversationFn = React11.useCallback(async (updates) => {
3110
+ if (!chatServiceRef.current || !conversationId)
3111
+ return null;
3112
+ const updated = await chatServiceRef.current.updateConversation(conversationId, updates);
3113
+ if (updated)
3114
+ setConversation(updated);
3115
+ return updated;
3116
+ }, [conversationId]);
3117
+ const addToolApprovalResponse = React11.useCallback((_toolCallId, _result) => {
3118
+ throw new Error(`addToolApprovalResponse: Tool approval requires server route with toUIMessageStreamResponse. ` + `Use createChatRoute and @ai-sdk/react useChat for tools with requireApproval.`);
3119
+ }, []);
3120
+ const hasApprovalTools = toolsDefs?.some((t) => t.requireApproval) ?? false;
3121
+ return {
3122
+ messages,
3123
+ conversation,
3124
+ isLoading,
3125
+ error,
3126
+ sendMessage,
3127
+ clearConversation,
3128
+ setConversationId,
3129
+ regenerate,
3130
+ stop,
3131
+ createNewConversation,
3132
+ editMessage,
3133
+ forkConversation,
3134
+ updateConversation: updateConversationFn,
3135
+ ...hasApprovalTools && { addToolApprovalResponse }
3136
+ };
3137
+ }
3138
+
3139
+ // src/core/local-storage-conversation-store.ts
3140
+ var DEFAULT_KEY = "contractspec:ai-chat:conversations";
3141
+ function generateId2(prefix) {
3142
+ return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
3143
+ }
3144
+ function toSerializable(conv) {
3145
+ return {
3146
+ ...conv,
3147
+ createdAt: conv.createdAt.toISOString(),
3148
+ updatedAt: conv.updatedAt.toISOString(),
3149
+ messages: conv.messages.map((m) => ({
3150
+ ...m,
3151
+ createdAt: m.createdAt.toISOString(),
3152
+ updatedAt: m.updatedAt.toISOString()
3153
+ }))
3154
+ };
3155
+ }
3156
+ function fromSerializable(raw) {
3157
+ const messages = raw.messages?.map((m) => ({
3158
+ ...m,
3159
+ createdAt: new Date(m.createdAt),
3160
+ updatedAt: new Date(m.updatedAt)
3161
+ })) ?? [];
3162
+ return {
3163
+ ...raw,
3164
+ createdAt: new Date(raw.createdAt),
3165
+ updatedAt: new Date(raw.updatedAt),
3166
+ messages
3167
+ };
3168
+ }
3169
+ function loadAll(key) {
3170
+ if (typeof window === "undefined")
3171
+ return new Map;
3172
+ try {
3173
+ const raw = window.localStorage.getItem(key);
3174
+ if (!raw)
3175
+ return new Map;
3176
+ const arr = JSON.parse(raw);
3177
+ const map = new Map;
3178
+ for (const item of arr) {
3179
+ const conv = fromSerializable(item);
3180
+ map.set(conv.id, conv);
3181
+ }
3182
+ return map;
3183
+ } catch {
3184
+ return new Map;
3185
+ }
3186
+ }
3187
+ function saveAll(key, map) {
3188
+ if (typeof window === "undefined")
3189
+ return;
3190
+ try {
3191
+ const arr = Array.from(map.values()).map(toSerializable);
3192
+ window.localStorage.setItem(key, JSON.stringify(arr));
3193
+ } catch {}
3194
+ }
3195
+
3196
+ class LocalStorageConversationStore {
3197
+ key;
3198
+ cache = null;
3199
+ constructor(storageKey = DEFAULT_KEY) {
3200
+ this.key = storageKey;
3201
+ }
3202
+ getMap() {
3203
+ if (!this.cache) {
3204
+ this.cache = loadAll(this.key);
3205
+ }
3206
+ return this.cache;
3207
+ }
3208
+ persist() {
3209
+ saveAll(this.key, this.getMap());
3210
+ }
3211
+ async get(conversationId) {
3212
+ return this.getMap().get(conversationId) ?? null;
3213
+ }
3214
+ async create(conversation) {
3215
+ const now = new Date;
3216
+ const full = {
3217
+ ...conversation,
3218
+ id: generateId2("conv"),
3219
+ createdAt: now,
3220
+ updatedAt: now
3221
+ };
3222
+ this.getMap().set(full.id, full);
3223
+ this.persist();
3224
+ return full;
3225
+ }
3226
+ async update(conversationId, updates) {
3227
+ const conv = this.getMap().get(conversationId);
3228
+ if (!conv)
3229
+ return null;
3230
+ const updated = {
3231
+ ...conv,
3232
+ ...updates,
3233
+ updatedAt: new Date
3234
+ };
3235
+ this.getMap().set(conversationId, updated);
3236
+ this.persist();
3237
+ return updated;
3238
+ }
3239
+ async appendMessage(conversationId, message) {
3240
+ const conv = this.getMap().get(conversationId);
3241
+ if (!conv)
3242
+ throw new Error(`Conversation ${conversationId} not found`);
3243
+ const now = new Date;
3244
+ const fullMessage = {
3245
+ ...message,
3246
+ id: generateId2("msg"),
3247
+ conversationId,
3248
+ createdAt: now,
3249
+ updatedAt: now
3250
+ };
3251
+ conv.messages.push(fullMessage);
3252
+ conv.updatedAt = now;
3253
+ this.persist();
3254
+ return fullMessage;
3255
+ }
3256
+ async updateMessage(conversationId, messageId, updates) {
3257
+ const conv = this.getMap().get(conversationId);
3258
+ if (!conv)
3259
+ return null;
3260
+ const idx = conv.messages.findIndex((m) => m.id === messageId);
3261
+ if (idx === -1)
3262
+ return null;
3263
+ const msg = conv.messages[idx];
3264
+ if (!msg)
3265
+ return null;
3266
+ const updated = {
3267
+ ...msg,
3268
+ ...updates,
3269
+ updatedAt: new Date
3270
+ };
3271
+ conv.messages[idx] = updated;
3272
+ conv.updatedAt = new Date;
3273
+ this.persist();
3274
+ return updated;
3275
+ }
3276
+ async delete(conversationId) {
3277
+ const deleted = this.getMap().delete(conversationId);
3278
+ if (deleted)
3279
+ this.persist();
3280
+ return deleted;
3281
+ }
3282
+ async list(options) {
3283
+ let results = Array.from(this.getMap().values());
3284
+ if (options?.status) {
3285
+ results = results.filter((c) => c.status === options.status);
3286
+ }
3287
+ if (options?.projectId) {
3288
+ results = results.filter((c) => c.projectId === options.projectId);
3289
+ }
3290
+ if (options?.tags && options.tags.length > 0) {
3291
+ const tagSet = new Set(options.tags);
3292
+ results = results.filter((c) => c.tags && c.tags.some((t) => tagSet.has(t)));
3293
+ }
3294
+ results.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());
3295
+ const offset = options?.offset ?? 0;
3296
+ const limit = options?.limit ?? 100;
3297
+ return results.slice(offset, offset + limit);
3298
+ }
3299
+ async fork(conversationId, upToMessageId) {
3300
+ const source = this.getMap().get(conversationId);
3301
+ if (!source)
3302
+ throw new Error(`Conversation ${conversationId} not found`);
3303
+ let messagesToCopy = source.messages;
3304
+ if (upToMessageId) {
3305
+ const idx = source.messages.findIndex((m) => m.id === upToMessageId);
3306
+ if (idx === -1)
3307
+ throw new Error(`Message ${upToMessageId} not found`);
3308
+ messagesToCopy = source.messages.slice(0, idx + 1);
3309
+ }
3310
+ const now = new Date;
3311
+ const forkedMessages = messagesToCopy.map((m) => ({
3312
+ ...m,
3313
+ id: generateId2("msg"),
3314
+ conversationId: "",
3315
+ createdAt: new Date(m.createdAt),
3316
+ updatedAt: new Date(m.updatedAt)
3317
+ }));
3318
+ const forked = {
3319
+ ...source,
3320
+ id: generateId2("conv"),
3321
+ title: source.title ? `${source.title} (fork)` : undefined,
3322
+ forkedFromId: source.id,
3323
+ createdAt: now,
3324
+ updatedAt: now,
3325
+ messages: forkedMessages
3326
+ };
3327
+ for (const m of forked.messages) {
3328
+ m.conversationId = forked.id;
3329
+ }
3330
+ this.getMap().set(forked.id, forked);
3331
+ this.persist();
3332
+ return forked;
3333
+ }
3334
+ async truncateAfter(conversationId, messageId) {
3335
+ const conv = this.getMap().get(conversationId);
3336
+ if (!conv)
3337
+ return null;
3338
+ const idx = conv.messages.findIndex((m) => m.id === messageId);
3339
+ if (idx === -1)
3340
+ return null;
3341
+ conv.messages = conv.messages.slice(0, idx + 1);
3342
+ conv.updatedAt = new Date;
3343
+ this.persist();
3344
+ return conv;
3345
+ }
3346
+ async search(query, limit = 20) {
3347
+ const lowerQuery = query.toLowerCase();
3348
+ const results = [];
3349
+ for (const conv of this.getMap().values()) {
3350
+ if (conv.title?.toLowerCase().includes(lowerQuery)) {
3351
+ results.push(conv);
3352
+ continue;
3353
+ }
3354
+ if (conv.messages.some((m) => m.content.toLowerCase().includes(lowerQuery))) {
3355
+ results.push(conv);
3356
+ }
3357
+ if (results.length >= limit)
3358
+ break;
3359
+ }
3360
+ return results;
3361
+ }
3362
+ }
3363
+ function createLocalStorageConversationStore(storageKey) {
3364
+ return new LocalStorageConversationStore(storageKey);
3365
+ }
3366
+
3367
+ // src/presentation/components/ChatWithSidebar.tsx
3368
+ import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
3369
+ "use client";
3370
+ var defaultStore = createLocalStorageConversationStore();
3371
+ function ChatWithSidebar({
3372
+ store = defaultStore,
3373
+ projectId,
3374
+ tags,
3375
+ className,
3376
+ thinkingLevel: initialThinkingLevel = "thinking",
3377
+ presentationRenderer,
3378
+ formRenderer,
3379
+ ...useChatOptions
3380
+ }) {
3381
+ const effectiveStore = store;
3382
+ const [thinkingLevel, setThinkingLevel] = React12.useState(initialThinkingLevel);
3383
+ const chat = useChat({
3384
+ ...useChatOptions,
3385
+ store: effectiveStore,
3386
+ thinkingLevel
3387
+ });
3388
+ const {
3389
+ messages,
3390
+ conversation,
3391
+ sendMessage,
3392
+ isLoading,
3393
+ setConversationId,
3394
+ createNewConversation,
3395
+ editMessage,
3396
+ forkConversation,
3397
+ updateConversation
3398
+ } = chat;
3399
+ const selectedConversationId = conversation?.id ?? null;
3400
+ const handleSelectConversation = React12.useCallback((id) => {
3401
+ setConversationId(id);
3402
+ }, [setConversationId]);
3403
+ return /* @__PURE__ */ jsxs10("div", {
3404
+ className: className ?? "flex h-full w-full",
3405
+ children: [
3406
+ /* @__PURE__ */ jsx10(ChatSidebar, {
3407
+ store: effectiveStore,
3408
+ selectedConversationId,
3409
+ onSelectConversation: handleSelectConversation,
3410
+ onCreateNew: createNewConversation,
3411
+ projectId,
3412
+ tags,
3413
+ selectedConversation: conversation,
3414
+ onUpdateConversation: updateConversation ? async (id, updates) => {
3415
+ if (id === selectedConversationId) {
3416
+ await updateConversation(updates);
3417
+ }
3418
+ } : undefined
3419
+ }),
3420
+ /* @__PURE__ */ jsx10("div", {
3421
+ className: "flex min-w-0 flex-1 flex-col",
3422
+ children: /* @__PURE__ */ jsx10(ChatWithExport, {
3423
+ messages,
3424
+ conversation,
3425
+ onCreateNew: createNewConversation,
3426
+ onFork: forkConversation,
3427
+ onEditMessage: editMessage,
3428
+ thinkingLevel,
3429
+ onThinkingLevelChange: setThinkingLevel,
3430
+ presentationRenderer,
3431
+ formRenderer,
3432
+ children: /* @__PURE__ */ jsx10(ChatInput, {
3433
+ onSend: (content, att) => sendMessage(content, att),
3434
+ disabled: isLoading,
3435
+ isLoading
3436
+ })
3437
+ })
3438
+ })
3439
+ ]
3440
+ });
3441
+ }
3442
+ // src/presentation/components/ModelPicker.tsx
3443
+ import * as React13 from "react";
3444
+ import { cn as cn7 } from "@contractspec/lib.ui-kit-web/ui/utils";
3445
+ import { Button as Button6 } from "@contractspec/lib.design-system";
3446
+ import {
3447
+ Select as Select2,
3448
+ SelectContent as SelectContent2,
3449
+ SelectItem as SelectItem2,
3450
+ SelectTrigger as SelectTrigger2,
3451
+ SelectValue as SelectValue2
3452
+ } from "@contractspec/lib.ui-kit-web/ui/select";
3453
+ import { Badge } from "@contractspec/lib.ui-kit-web/ui/badge";
3454
+ import { Label as Label2 } from "@contractspec/lib.ui-kit-web/ui/label";
678
3455
  import { Bot as Bot2, Cloud, Cpu, Sparkles } from "lucide-react";
679
3456
  import {
680
3457
  getModelsForProvider
681
3458
  } from "@contractspec/lib.ai-providers";
682
- import { jsxDEV as jsxDEV5 } from "react/jsx-dev-runtime";
3459
+ import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
683
3460
  "use client";
684
3461
  var PROVIDER_ICONS = {
685
- ollama: /* @__PURE__ */ jsxDEV5(Cpu, {
3462
+ ollama: /* @__PURE__ */ jsx11(Cpu, {
686
3463
  className: "h-4 w-4"
687
- }, undefined, false, undefined, this),
688
- openai: /* @__PURE__ */ jsxDEV5(Bot2, {
3464
+ }),
3465
+ openai: /* @__PURE__ */ jsx11(Bot2, {
689
3466
  className: "h-4 w-4"
690
- }, undefined, false, undefined, this),
691
- anthropic: /* @__PURE__ */ jsxDEV5(Sparkles, {
3467
+ }),
3468
+ anthropic: /* @__PURE__ */ jsx11(Sparkles, {
692
3469
  className: "h-4 w-4"
693
- }, undefined, false, undefined, this),
694
- mistral: /* @__PURE__ */ jsxDEV5(Cloud, {
3470
+ }),
3471
+ mistral: /* @__PURE__ */ jsx11(Cloud, {
695
3472
  className: "h-4 w-4"
696
- }, undefined, false, undefined, this),
697
- gemini: /* @__PURE__ */ jsxDEV5(Sparkles, {
3473
+ }),
3474
+ gemini: /* @__PURE__ */ jsx11(Sparkles, {
698
3475
  className: "h-4 w-4"
699
- }, undefined, false, undefined, this)
3476
+ })
700
3477
  };
701
3478
  var PROVIDER_NAMES = {
702
3479
  ollama: "Ollama (Local)",
@@ -726,7 +3503,7 @@ function ModelPicker({
726
3503
  ];
727
3504
  const models = getModelsForProvider(value.provider);
728
3505
  const selectedModel = models.find((m) => m.id === value.model);
729
- const handleProviderChange = React5.useCallback((providerName) => {
3506
+ const handleProviderChange = React13.useCallback((providerName) => {
730
3507
  const provider = providerName;
731
3508
  const providerInfo = providers.find((p) => p.provider === provider);
732
3509
  const providerModels = getModelsForProvider(provider);
@@ -737,173 +3514,173 @@ function ModelPicker({
737
3514
  mode: providerInfo?.mode ?? "byok"
738
3515
  });
739
3516
  }, [onChange, providers]);
740
- const handleModelChange = React5.useCallback((modelId) => {
3517
+ const handleModelChange = React13.useCallback((modelId) => {
741
3518
  onChange({
742
3519
  ...value,
743
3520
  model: modelId
744
3521
  });
745
3522
  }, [onChange, value]);
746
3523
  if (compact) {
747
- return /* @__PURE__ */ jsxDEV5("div", {
748
- className: cn5("flex items-center gap-2", className),
3524
+ return /* @__PURE__ */ jsxs11("div", {
3525
+ className: cn7("flex items-center gap-2", className),
749
3526
  children: [
750
- /* @__PURE__ */ jsxDEV5(Select, {
3527
+ /* @__PURE__ */ jsxs11(Select2, {
751
3528
  value: value.provider,
752
3529
  onValueChange: handleProviderChange,
753
3530
  children: [
754
- /* @__PURE__ */ jsxDEV5(SelectTrigger, {
3531
+ /* @__PURE__ */ jsx11(SelectTrigger2, {
755
3532
  className: "w-[140px]",
756
- children: /* @__PURE__ */ jsxDEV5(SelectValue, {}, undefined, false, undefined, this)
757
- }, undefined, false, undefined, this),
758
- /* @__PURE__ */ jsxDEV5(SelectContent, {
759
- children: providers.map((p) => /* @__PURE__ */ jsxDEV5(SelectItem, {
3533
+ children: /* @__PURE__ */ jsx11(SelectValue2, {})
3534
+ }),
3535
+ /* @__PURE__ */ jsx11(SelectContent2, {
3536
+ children: providers.map((p) => /* @__PURE__ */ jsx11(SelectItem2, {
760
3537
  value: p.provider,
761
3538
  disabled: !p.available,
762
- children: /* @__PURE__ */ jsxDEV5("div", {
3539
+ children: /* @__PURE__ */ jsxs11("div", {
763
3540
  className: "flex items-center gap-2",
764
3541
  children: [
765
3542
  PROVIDER_ICONS[p.provider],
766
- /* @__PURE__ */ jsxDEV5("span", {
3543
+ /* @__PURE__ */ jsx11("span", {
767
3544
  children: PROVIDER_NAMES[p.provider]
768
- }, undefined, false, undefined, this)
3545
+ })
769
3546
  ]
770
- }, undefined, true, undefined, this)
771
- }, p.provider, false, undefined, this))
772
- }, undefined, false, undefined, this)
3547
+ })
3548
+ }, p.provider))
3549
+ })
773
3550
  ]
774
- }, undefined, true, undefined, this),
775
- /* @__PURE__ */ jsxDEV5(Select, {
3551
+ }),
3552
+ /* @__PURE__ */ jsxs11(Select2, {
776
3553
  value: value.model,
777
3554
  onValueChange: handleModelChange,
778
3555
  children: [
779
- /* @__PURE__ */ jsxDEV5(SelectTrigger, {
3556
+ /* @__PURE__ */ jsx11(SelectTrigger2, {
780
3557
  className: "w-[160px]",
781
- children: /* @__PURE__ */ jsxDEV5(SelectValue, {}, undefined, false, undefined, this)
782
- }, undefined, false, undefined, this),
783
- /* @__PURE__ */ jsxDEV5(SelectContent, {
784
- children: models.map((m) => /* @__PURE__ */ jsxDEV5(SelectItem, {
3558
+ children: /* @__PURE__ */ jsx11(SelectValue2, {})
3559
+ }),
3560
+ /* @__PURE__ */ jsx11(SelectContent2, {
3561
+ children: models.map((m) => /* @__PURE__ */ jsx11(SelectItem2, {
785
3562
  value: m.id,
786
3563
  children: m.name
787
- }, m.id, false, undefined, this))
788
- }, undefined, false, undefined, this)
3564
+ }, m.id))
3565
+ })
789
3566
  ]
790
- }, undefined, true, undefined, this)
3567
+ })
791
3568
  ]
792
- }, undefined, true, undefined, this);
3569
+ });
793
3570
  }
794
- return /* @__PURE__ */ jsxDEV5("div", {
795
- className: cn5("flex flex-col gap-3", className),
3571
+ return /* @__PURE__ */ jsxs11("div", {
3572
+ className: cn7("flex flex-col gap-3", className),
796
3573
  children: [
797
- /* @__PURE__ */ jsxDEV5("div", {
3574
+ /* @__PURE__ */ jsxs11("div", {
798
3575
  className: "flex flex-col gap-1.5",
799
3576
  children: [
800
- /* @__PURE__ */ jsxDEV5(Label, {
3577
+ /* @__PURE__ */ jsx11(Label2, {
801
3578
  htmlFor: "provider-selection",
802
3579
  className: "text-sm font-medium",
803
3580
  children: "Provider"
804
- }, undefined, false, undefined, this),
805
- /* @__PURE__ */ jsxDEV5("div", {
3581
+ }),
3582
+ /* @__PURE__ */ jsx11("div", {
806
3583
  className: "flex flex-wrap gap-2",
807
3584
  id: "provider-selection",
808
- children: providers.map((p) => /* @__PURE__ */ jsxDEV5(Button4, {
3585
+ children: providers.map((p) => /* @__PURE__ */ jsxs11(Button6, {
809
3586
  variant: value.provider === p.provider ? "default" : "outline",
810
3587
  size: "sm",
811
3588
  onPress: () => p.available && handleProviderChange(p.provider),
812
3589
  disabled: !p.available,
813
- className: cn5(!p.available && "opacity-50"),
3590
+ className: cn7(!p.available && "opacity-50"),
814
3591
  children: [
815
3592
  PROVIDER_ICONS[p.provider],
816
- /* @__PURE__ */ jsxDEV5("span", {
3593
+ /* @__PURE__ */ jsx11("span", {
817
3594
  children: PROVIDER_NAMES[p.provider]
818
- }, undefined, false, undefined, this),
819
- /* @__PURE__ */ jsxDEV5(Badge, {
3595
+ }),
3596
+ /* @__PURE__ */ jsx11(Badge, {
820
3597
  variant: MODE_BADGES[p.mode].variant,
821
3598
  className: "ml-1",
822
3599
  children: MODE_BADGES[p.mode].label
823
- }, undefined, false, undefined, this)
3600
+ })
824
3601
  ]
825
- }, p.provider, true, undefined, this))
826
- }, undefined, false, undefined, this)
3602
+ }, p.provider))
3603
+ })
827
3604
  ]
828
- }, undefined, true, undefined, this),
829
- /* @__PURE__ */ jsxDEV5("div", {
3605
+ }),
3606
+ /* @__PURE__ */ jsxs11("div", {
830
3607
  className: "flex flex-col gap-1.5",
831
3608
  children: [
832
- /* @__PURE__ */ jsxDEV5(Label, {
3609
+ /* @__PURE__ */ jsx11(Label2, {
833
3610
  htmlFor: "model-picker",
834
3611
  className: "text-sm font-medium",
835
3612
  children: "Model"
836
- }, undefined, false, undefined, this),
837
- /* @__PURE__ */ jsxDEV5(Select, {
3613
+ }),
3614
+ /* @__PURE__ */ jsxs11(Select2, {
838
3615
  name: "model-picker",
839
3616
  value: value.model,
840
3617
  onValueChange: handleModelChange,
841
3618
  children: [
842
- /* @__PURE__ */ jsxDEV5(SelectTrigger, {
843
- children: /* @__PURE__ */ jsxDEV5(SelectValue, {
3619
+ /* @__PURE__ */ jsx11(SelectTrigger2, {
3620
+ children: /* @__PURE__ */ jsx11(SelectValue2, {
844
3621
  placeholder: "Select a model"
845
- }, undefined, false, undefined, this)
846
- }, undefined, false, undefined, this),
847
- /* @__PURE__ */ jsxDEV5(SelectContent, {
848
- children: models.map((m) => /* @__PURE__ */ jsxDEV5(SelectItem, {
3622
+ })
3623
+ }),
3624
+ /* @__PURE__ */ jsx11(SelectContent2, {
3625
+ children: models.map((m) => /* @__PURE__ */ jsx11(SelectItem2, {
849
3626
  value: m.id,
850
- children: /* @__PURE__ */ jsxDEV5("div", {
3627
+ children: /* @__PURE__ */ jsxs11("div", {
851
3628
  className: "flex items-center gap-2",
852
3629
  children: [
853
- /* @__PURE__ */ jsxDEV5("span", {
3630
+ /* @__PURE__ */ jsx11("span", {
854
3631
  children: m.name
855
- }, undefined, false, undefined, this),
856
- /* @__PURE__ */ jsxDEV5("span", {
3632
+ }),
3633
+ /* @__PURE__ */ jsxs11("span", {
857
3634
  className: "text-muted-foreground text-xs",
858
3635
  children: [
859
3636
  Math.round(m.contextWindow / 1000),
860
3637
  "K"
861
3638
  ]
862
- }, undefined, true, undefined, this),
863
- m.capabilities.vision && /* @__PURE__ */ jsxDEV5(Badge, {
3639
+ }),
3640
+ m.capabilities.vision && /* @__PURE__ */ jsx11(Badge, {
864
3641
  variant: "outline",
865
3642
  className: "text-xs",
866
3643
  children: "Vision"
867
- }, undefined, false, undefined, this),
868
- m.capabilities.reasoning && /* @__PURE__ */ jsxDEV5(Badge, {
3644
+ }),
3645
+ m.capabilities.reasoning && /* @__PURE__ */ jsx11(Badge, {
869
3646
  variant: "outline",
870
3647
  className: "text-xs",
871
3648
  children: "Reasoning"
872
- }, undefined, false, undefined, this)
3649
+ })
873
3650
  ]
874
- }, undefined, true, undefined, this)
875
- }, m.id, false, undefined, this))
876
- }, undefined, false, undefined, this)
3651
+ })
3652
+ }, m.id))
3653
+ })
877
3654
  ]
878
- }, undefined, true, undefined, this)
3655
+ })
879
3656
  ]
880
- }, undefined, true, undefined, this),
881
- selectedModel && /* @__PURE__ */ jsxDEV5("div", {
3657
+ }),
3658
+ selectedModel && /* @__PURE__ */ jsxs11("div", {
882
3659
  className: "text-muted-foreground flex flex-wrap gap-2 text-xs",
883
3660
  children: [
884
- /* @__PURE__ */ jsxDEV5("span", {
3661
+ /* @__PURE__ */ jsxs11("span", {
885
3662
  children: [
886
3663
  "Context: ",
887
3664
  Math.round(selectedModel.contextWindow / 1000),
888
3665
  "K tokens"
889
3666
  ]
890
- }, undefined, true, undefined, this),
891
- selectedModel.capabilities.vision && /* @__PURE__ */ jsxDEV5("span", {
3667
+ }),
3668
+ selectedModel.capabilities.vision && /* @__PURE__ */ jsx11("span", {
892
3669
  children: "• Vision"
893
- }, undefined, false, undefined, this),
894
- selectedModel.capabilities.tools && /* @__PURE__ */ jsxDEV5("span", {
3670
+ }),
3671
+ selectedModel.capabilities.tools && /* @__PURE__ */ jsx11("span", {
895
3672
  children: "• Tools"
896
- }, undefined, false, undefined, this),
897
- selectedModel.capabilities.reasoning && /* @__PURE__ */ jsxDEV5("span", {
3673
+ }),
3674
+ selectedModel.capabilities.reasoning && /* @__PURE__ */ jsx11("span", {
898
3675
  children: "• Reasoning"
899
- }, undefined, false, undefined, this)
3676
+ })
900
3677
  ]
901
- }, undefined, true, undefined, this)
3678
+ })
902
3679
  ]
903
- }, undefined, true, undefined, this);
3680
+ });
904
3681
  }
905
3682
  // src/presentation/components/ContextIndicator.tsx
906
- import { cn as cn6 } from "@contractspec/lib.ui-kit-web/ui/utils";
3683
+ import { cn as cn8 } from "@contractspec/lib.ui-kit-web/ui/utils";
907
3684
  import { Badge as Badge2 } from "@contractspec/lib.ui-kit-web/ui/badge";
908
3685
  import {
909
3686
  Tooltip,
@@ -912,7 +3689,7 @@ import {
912
3689
  TooltipTrigger
913
3690
  } from "@contractspec/lib.ui-kit-web/ui/tooltip";
914
3691
  import { FolderOpen, FileCode, Zap, Info } from "lucide-react";
915
- import { jsxDEV as jsxDEV6, Fragment as Fragment3 } from "react/jsx-dev-runtime";
3692
+ import { jsx as jsx12, jsxs as jsxs12, Fragment as Fragment6 } from "react/jsx-runtime";
916
3693
  "use client";
917
3694
  function ContextIndicator({
918
3695
  summary,
@@ -921,155 +3698,163 @@ function ContextIndicator({
921
3698
  showDetails = true
922
3699
  }) {
923
3700
  if (!summary && !active) {
924
- return /* @__PURE__ */ jsxDEV6("div", {
925
- className: cn6("flex items-center gap-1.5 text-sm", "text-muted-foreground", className),
3701
+ return /* @__PURE__ */ jsxs12("div", {
3702
+ className: cn8("flex items-center gap-1.5 text-sm", "text-muted-foreground", className),
926
3703
  children: [
927
- /* @__PURE__ */ jsxDEV6(Info, {
3704
+ /* @__PURE__ */ jsx12(Info, {
928
3705
  className: "h-4 w-4"
929
- }, undefined, false, undefined, this),
930
- /* @__PURE__ */ jsxDEV6("span", {
3706
+ }),
3707
+ /* @__PURE__ */ jsx12("span", {
931
3708
  children: "No workspace context"
932
- }, undefined, false, undefined, this)
3709
+ })
933
3710
  ]
934
- }, undefined, true, undefined, this);
3711
+ });
935
3712
  }
936
- const content = /* @__PURE__ */ jsxDEV6("div", {
937
- className: cn6("flex items-center gap-2", active ? "text-foreground" : "text-muted-foreground", className),
3713
+ const content = /* @__PURE__ */ jsxs12("div", {
3714
+ className: cn8("flex items-center gap-2", active ? "text-foreground" : "text-muted-foreground", className),
938
3715
  children: [
939
- /* @__PURE__ */ jsxDEV6(Badge2, {
3716
+ /* @__PURE__ */ jsxs12(Badge2, {
940
3717
  variant: active ? "default" : "secondary",
941
3718
  className: "flex items-center gap-1",
942
3719
  children: [
943
- /* @__PURE__ */ jsxDEV6(Zap, {
3720
+ /* @__PURE__ */ jsx12(Zap, {
944
3721
  className: "h-3 w-3"
945
- }, undefined, false, undefined, this),
3722
+ }),
946
3723
  "Context"
947
3724
  ]
948
- }, undefined, true, undefined, this),
949
- summary && showDetails && /* @__PURE__ */ jsxDEV6(Fragment3, {
3725
+ }),
3726
+ summary && showDetails && /* @__PURE__ */ jsxs12(Fragment6, {
950
3727
  children: [
951
- /* @__PURE__ */ jsxDEV6("div", {
3728
+ /* @__PURE__ */ jsxs12("div", {
952
3729
  className: "flex items-center gap-1 text-xs",
953
3730
  children: [
954
- /* @__PURE__ */ jsxDEV6(FolderOpen, {
3731
+ /* @__PURE__ */ jsx12(FolderOpen, {
955
3732
  className: "h-3.5 w-3.5"
956
- }, undefined, false, undefined, this),
957
- /* @__PURE__ */ jsxDEV6("span", {
3733
+ }),
3734
+ /* @__PURE__ */ jsx12("span", {
958
3735
  children: summary.name
959
- }, undefined, false, undefined, this)
3736
+ })
960
3737
  ]
961
- }, undefined, true, undefined, this),
962
- /* @__PURE__ */ jsxDEV6("div", {
3738
+ }),
3739
+ /* @__PURE__ */ jsxs12("div", {
963
3740
  className: "flex items-center gap-1 text-xs",
964
3741
  children: [
965
- /* @__PURE__ */ jsxDEV6(FileCode, {
3742
+ /* @__PURE__ */ jsx12(FileCode, {
966
3743
  className: "h-3.5 w-3.5"
967
- }, undefined, false, undefined, this),
968
- /* @__PURE__ */ jsxDEV6("span", {
3744
+ }),
3745
+ /* @__PURE__ */ jsxs12("span", {
969
3746
  children: [
970
3747
  summary.specs.total,
971
3748
  " specs"
972
3749
  ]
973
- }, undefined, true, undefined, this)
3750
+ })
974
3751
  ]
975
- }, undefined, true, undefined, this)
3752
+ })
976
3753
  ]
977
- }, undefined, true, undefined, this)
3754
+ })
978
3755
  ]
979
- }, undefined, true, undefined, this);
3756
+ });
980
3757
  if (!summary) {
981
3758
  return content;
982
3759
  }
983
- return /* @__PURE__ */ jsxDEV6(TooltipProvider, {
984
- children: /* @__PURE__ */ jsxDEV6(Tooltip, {
3760
+ return /* @__PURE__ */ jsx12(TooltipProvider, {
3761
+ children: /* @__PURE__ */ jsxs12(Tooltip, {
985
3762
  children: [
986
- /* @__PURE__ */ jsxDEV6(TooltipTrigger, {
3763
+ /* @__PURE__ */ jsx12(TooltipTrigger, {
987
3764
  asChild: true,
988
3765
  children: content
989
- }, undefined, false, undefined, this),
990
- /* @__PURE__ */ jsxDEV6(TooltipContent, {
3766
+ }),
3767
+ /* @__PURE__ */ jsx12(TooltipContent, {
991
3768
  side: "bottom",
992
3769
  className: "max-w-[300px]",
993
- children: /* @__PURE__ */ jsxDEV6("div", {
3770
+ children: /* @__PURE__ */ jsxs12("div", {
994
3771
  className: "flex flex-col gap-2 text-sm",
995
3772
  children: [
996
- /* @__PURE__ */ jsxDEV6("div", {
3773
+ /* @__PURE__ */ jsx12("div", {
997
3774
  className: "font-medium",
998
3775
  children: summary.name
999
- }, undefined, false, undefined, this),
1000
- /* @__PURE__ */ jsxDEV6("div", {
3776
+ }),
3777
+ /* @__PURE__ */ jsx12("div", {
1001
3778
  className: "text-muted-foreground text-xs",
1002
3779
  children: summary.path
1003
- }, undefined, false, undefined, this),
1004
- /* @__PURE__ */ jsxDEV6("div", {
3780
+ }),
3781
+ /* @__PURE__ */ jsx12("div", {
1005
3782
  className: "border-t pt-2",
1006
- children: /* @__PURE__ */ jsxDEV6("div", {
3783
+ children: /* @__PURE__ */ jsxs12("div", {
1007
3784
  className: "grid grid-cols-2 gap-1 text-xs",
1008
3785
  children: [
1009
- /* @__PURE__ */ jsxDEV6("span", {
3786
+ /* @__PURE__ */ jsx12("span", {
1010
3787
  children: "Commands:"
1011
- }, undefined, false, undefined, this),
1012
- /* @__PURE__ */ jsxDEV6("span", {
3788
+ }),
3789
+ /* @__PURE__ */ jsx12("span", {
1013
3790
  className: "text-right",
1014
3791
  children: summary.specs.commands
1015
- }, undefined, false, undefined, this),
1016
- /* @__PURE__ */ jsxDEV6("span", {
3792
+ }),
3793
+ /* @__PURE__ */ jsx12("span", {
1017
3794
  children: "Queries:"
1018
- }, undefined, false, undefined, this),
1019
- /* @__PURE__ */ jsxDEV6("span", {
3795
+ }),
3796
+ /* @__PURE__ */ jsx12("span", {
1020
3797
  className: "text-right",
1021
3798
  children: summary.specs.queries
1022
- }, undefined, false, undefined, this),
1023
- /* @__PURE__ */ jsxDEV6("span", {
3799
+ }),
3800
+ /* @__PURE__ */ jsx12("span", {
1024
3801
  children: "Events:"
1025
- }, undefined, false, undefined, this),
1026
- /* @__PURE__ */ jsxDEV6("span", {
3802
+ }),
3803
+ /* @__PURE__ */ jsx12("span", {
1027
3804
  className: "text-right",
1028
3805
  children: summary.specs.events
1029
- }, undefined, false, undefined, this),
1030
- /* @__PURE__ */ jsxDEV6("span", {
3806
+ }),
3807
+ /* @__PURE__ */ jsx12("span", {
1031
3808
  children: "Presentations:"
1032
- }, undefined, false, undefined, this),
1033
- /* @__PURE__ */ jsxDEV6("span", {
3809
+ }),
3810
+ /* @__PURE__ */ jsx12("span", {
1034
3811
  className: "text-right",
1035
3812
  children: summary.specs.presentations
1036
- }, undefined, false, undefined, this)
3813
+ })
1037
3814
  ]
1038
- }, undefined, true, undefined, this)
1039
- }, undefined, false, undefined, this),
1040
- /* @__PURE__ */ jsxDEV6("div", {
3815
+ })
3816
+ }),
3817
+ /* @__PURE__ */ jsxs12("div", {
1041
3818
  className: "border-t pt-2 text-xs",
1042
3819
  children: [
1043
- /* @__PURE__ */ jsxDEV6("span", {
3820
+ /* @__PURE__ */ jsxs12("span", {
1044
3821
  children: [
1045
3822
  summary.files.total,
1046
3823
  " files"
1047
3824
  ]
1048
- }, undefined, true, undefined, this),
1049
- /* @__PURE__ */ jsxDEV6("span", {
3825
+ }),
3826
+ /* @__PURE__ */ jsx12("span", {
1050
3827
  className: "mx-1",
1051
3828
  children: "•"
1052
- }, undefined, false, undefined, this),
1053
- /* @__PURE__ */ jsxDEV6("span", {
3829
+ }),
3830
+ /* @__PURE__ */ jsxs12("span", {
1054
3831
  children: [
1055
3832
  summary.files.specFiles,
1056
3833
  " spec files"
1057
3834
  ]
1058
- }, undefined, true, undefined, this)
3835
+ })
1059
3836
  ]
1060
- }, undefined, true, undefined, this)
3837
+ })
1061
3838
  ]
1062
- }, undefined, true, undefined, this)
1063
- }, undefined, false, undefined, this)
3839
+ })
3840
+ })
1064
3841
  ]
1065
- }, undefined, true, undefined, this)
1066
- }, undefined, false, undefined, this);
3842
+ })
3843
+ });
1067
3844
  }
1068
3845
  export {
3846
+ isPresentationToolResult,
3847
+ isFormToolResult,
3848
+ ToolResultRenderer,
3849
+ ThinkingLevelPicker,
1069
3850
  ModelPicker,
1070
3851
  ContextIndicator,
1071
3852
  CodePreview,
3853
+ ChatWithSidebar,
3854
+ ChatWithExport,
3855
+ ChatSidebar,
1072
3856
  ChatMessage,
1073
3857
  ChatInput,
3858
+ ChatExportToolbar,
1074
3859
  ChatContainer
1075
3860
  };