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