@contractspec/module.ai-chat 4.0.3 → 4.1.2

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