@contractspec/module.ai-chat 3.2.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +70 -0
- package/dist/browser/core/index.js +201 -41
- package/dist/browser/index.js +326 -51
- package/dist/browser/presentation/components/index.js +111 -5
- package/dist/browser/presentation/hooks/index.js +215 -46
- package/dist/browser/presentation/index.js +326 -51
- package/dist/core/chat-service.d.ts +15 -2
- package/dist/core/create-chat-route.d.ts +35 -0
- package/dist/core/create-completion-route.d.ts +16 -0
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +201 -41
- package/dist/core/message-types.d.ts +11 -2
- package/dist/index.js +326 -51
- package/dist/node/core/index.js +201 -41
- package/dist/node/index.js +326 -51
- package/dist/node/presentation/components/index.js +111 -5
- package/dist/node/presentation/hooks/index.js +215 -46
- package/dist/node/presentation/index.js +326 -51
- package/dist/presentation/components/index.js +111 -5
- package/dist/presentation/hooks/index.d.ts +3 -1
- package/dist/presentation/hooks/index.js +215 -46
- package/dist/presentation/hooks/useChat.d.ts +18 -0
- package/dist/presentation/index.js +326 -51
- package/package.json +18 -12
package/dist/node/index.js
CHANGED
|
@@ -478,7 +478,15 @@ import * as React3 from "react";
|
|
|
478
478
|
import { cn as cn3 } from "@contractspec/lib.ui-kit-web/ui/utils";
|
|
479
479
|
import { Avatar, AvatarFallback } from "@contractspec/lib.ui-kit-web/ui/avatar";
|
|
480
480
|
import { Skeleton } from "@contractspec/lib.ui-kit-web/ui/skeleton";
|
|
481
|
-
import {
|
|
481
|
+
import {
|
|
482
|
+
Bot,
|
|
483
|
+
User,
|
|
484
|
+
AlertCircle,
|
|
485
|
+
Copy as Copy2,
|
|
486
|
+
Check as Check2,
|
|
487
|
+
ExternalLink,
|
|
488
|
+
Wrench
|
|
489
|
+
} from "lucide-react";
|
|
482
490
|
import { Button as Button2 } from "@contractspec/lib.design-system";
|
|
483
491
|
|
|
484
492
|
// src/presentation/components/CodePreview.tsx
|
|
@@ -640,12 +648,40 @@ function extractCodeBlocks(content) {
|
|
|
640
648
|
}
|
|
641
649
|
return blocks;
|
|
642
650
|
}
|
|
651
|
+
function renderInlineMarkdown(text) {
|
|
652
|
+
const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
|
|
653
|
+
const parts = [];
|
|
654
|
+
let lastIndex = 0;
|
|
655
|
+
let match;
|
|
656
|
+
let key = 0;
|
|
657
|
+
while ((match = linkRegex.exec(text)) !== null) {
|
|
658
|
+
if (match.index > lastIndex) {
|
|
659
|
+
parts.push(/* @__PURE__ */ jsxDEV3("span", {
|
|
660
|
+
children: text.slice(lastIndex, match.index)
|
|
661
|
+
}, key++, false, undefined, this));
|
|
662
|
+
}
|
|
663
|
+
parts.push(/* @__PURE__ */ jsxDEV3("a", {
|
|
664
|
+
href: match[2],
|
|
665
|
+
target: "_blank",
|
|
666
|
+
rel: "noopener noreferrer",
|
|
667
|
+
className: "text-primary underline hover:no-underline",
|
|
668
|
+
children: match[1]
|
|
669
|
+
}, key++, false, undefined, this));
|
|
670
|
+
lastIndex = match.index + match[0].length;
|
|
671
|
+
}
|
|
672
|
+
if (lastIndex < text.length) {
|
|
673
|
+
parts.push(/* @__PURE__ */ jsxDEV3("span", {
|
|
674
|
+
children: text.slice(lastIndex)
|
|
675
|
+
}, key++, false, undefined, this));
|
|
676
|
+
}
|
|
677
|
+
return parts.length > 0 ? parts : [text];
|
|
678
|
+
}
|
|
643
679
|
function MessageContent({ content }) {
|
|
644
680
|
const codeBlocks = extractCodeBlocks(content);
|
|
645
681
|
if (codeBlocks.length === 0) {
|
|
646
682
|
return /* @__PURE__ */ jsxDEV3("p", {
|
|
647
683
|
className: "whitespace-pre-wrap",
|
|
648
|
-
children: content
|
|
684
|
+
children: renderInlineMarkdown(content)
|
|
649
685
|
}, undefined, false, undefined, this);
|
|
650
686
|
}
|
|
651
687
|
let remaining = content;
|
|
@@ -656,7 +692,7 @@ function MessageContent({ content }) {
|
|
|
656
692
|
if (before) {
|
|
657
693
|
parts.push(/* @__PURE__ */ jsxDEV3("p", {
|
|
658
694
|
className: "whitespace-pre-wrap",
|
|
659
|
-
children: before.trim()
|
|
695
|
+
children: renderInlineMarkdown(before.trim())
|
|
660
696
|
}, key++, false, undefined, this));
|
|
661
697
|
}
|
|
662
698
|
parts.push(/* @__PURE__ */ jsxDEV3(CodePreview, {
|
|
@@ -669,7 +705,7 @@ function MessageContent({ content }) {
|
|
|
669
705
|
if (remaining.trim()) {
|
|
670
706
|
parts.push(/* @__PURE__ */ jsxDEV3("p", {
|
|
671
707
|
className: "whitespace-pre-wrap",
|
|
672
|
-
children: remaining.trim()
|
|
708
|
+
children: renderInlineMarkdown(remaining.trim())
|
|
673
709
|
}, key++, false, undefined, this));
|
|
674
710
|
}
|
|
675
711
|
return /* @__PURE__ */ jsxDEV3(Fragment, {
|
|
@@ -787,7 +823,77 @@ function ChatMessage({
|
|
|
787
823
|
}, undefined, false, undefined, this)
|
|
788
824
|
}, undefined, false, undefined, this)
|
|
789
825
|
]
|
|
790
|
-
}, undefined, true, undefined, this)
|
|
826
|
+
}, undefined, true, undefined, this),
|
|
827
|
+
message.sources && message.sources.length > 0 && /* @__PURE__ */ jsxDEV3("div", {
|
|
828
|
+
className: "mt-2 flex flex-wrap gap-2",
|
|
829
|
+
children: message.sources.map((source) => /* @__PURE__ */ jsxDEV3("a", {
|
|
830
|
+
href: source.url ?? "#",
|
|
831
|
+
target: "_blank",
|
|
832
|
+
rel: "noopener noreferrer",
|
|
833
|
+
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",
|
|
834
|
+
children: [
|
|
835
|
+
/* @__PURE__ */ jsxDEV3(ExternalLink, {
|
|
836
|
+
className: "h-3 w-3"
|
|
837
|
+
}, undefined, false, undefined, this),
|
|
838
|
+
source.title || source.url || source.id
|
|
839
|
+
]
|
|
840
|
+
}, source.id, true, undefined, this))
|
|
841
|
+
}, undefined, false, undefined, this),
|
|
842
|
+
message.toolCalls && message.toolCalls.length > 0 && /* @__PURE__ */ jsxDEV3("div", {
|
|
843
|
+
className: "mt-2 space-y-2",
|
|
844
|
+
children: message.toolCalls.map((tc) => /* @__PURE__ */ jsxDEV3("details", {
|
|
845
|
+
className: "bg-muted border-border rounded-md border",
|
|
846
|
+
children: [
|
|
847
|
+
/* @__PURE__ */ jsxDEV3("summary", {
|
|
848
|
+
className: "flex cursor-pointer items-center gap-2 px-3 py-2 text-sm font-medium",
|
|
849
|
+
children: [
|
|
850
|
+
/* @__PURE__ */ jsxDEV3(Wrench, {
|
|
851
|
+
className: "text-muted-foreground h-4 w-4"
|
|
852
|
+
}, undefined, false, undefined, this),
|
|
853
|
+
tc.name,
|
|
854
|
+
/* @__PURE__ */ jsxDEV3("span", {
|
|
855
|
+
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"),
|
|
856
|
+
children: tc.status
|
|
857
|
+
}, undefined, false, undefined, this)
|
|
858
|
+
]
|
|
859
|
+
}, undefined, true, undefined, this),
|
|
860
|
+
/* @__PURE__ */ jsxDEV3("div", {
|
|
861
|
+
className: "border-border border-t px-3 py-2 text-xs",
|
|
862
|
+
children: [
|
|
863
|
+
Object.keys(tc.args).length > 0 && /* @__PURE__ */ jsxDEV3("div", {
|
|
864
|
+
className: "mb-2",
|
|
865
|
+
children: [
|
|
866
|
+
/* @__PURE__ */ jsxDEV3("span", {
|
|
867
|
+
className: "text-muted-foreground font-medium",
|
|
868
|
+
children: "Input:"
|
|
869
|
+
}, undefined, false, undefined, this),
|
|
870
|
+
/* @__PURE__ */ jsxDEV3("pre", {
|
|
871
|
+
className: "bg-background mt-1 overflow-x-auto rounded p-2",
|
|
872
|
+
children: JSON.stringify(tc.args, null, 2)
|
|
873
|
+
}, undefined, false, undefined, this)
|
|
874
|
+
]
|
|
875
|
+
}, undefined, true, undefined, this),
|
|
876
|
+
tc.result !== undefined && /* @__PURE__ */ jsxDEV3("div", {
|
|
877
|
+
children: [
|
|
878
|
+
/* @__PURE__ */ jsxDEV3("span", {
|
|
879
|
+
className: "text-muted-foreground font-medium",
|
|
880
|
+
children: "Output:"
|
|
881
|
+
}, undefined, false, undefined, this),
|
|
882
|
+
/* @__PURE__ */ jsxDEV3("pre", {
|
|
883
|
+
className: "bg-background mt-1 overflow-x-auto rounded p-2",
|
|
884
|
+
children: typeof tc.result === "object" ? JSON.stringify(tc.result, null, 2) : String(tc.result)
|
|
885
|
+
}, undefined, false, undefined, this)
|
|
886
|
+
]
|
|
887
|
+
}, undefined, true, undefined, this),
|
|
888
|
+
tc.error && /* @__PURE__ */ jsxDEV3("p", {
|
|
889
|
+
className: "text-destructive mt-1",
|
|
890
|
+
children: tc.error
|
|
891
|
+
}, undefined, false, undefined, this)
|
|
892
|
+
]
|
|
893
|
+
}, undefined, true, undefined, this)
|
|
894
|
+
]
|
|
895
|
+
}, tc.id, true, undefined, this))
|
|
896
|
+
}, undefined, false, undefined, this)
|
|
791
897
|
]
|
|
792
898
|
}, undefined, true, undefined, this)
|
|
793
899
|
]
|
|
@@ -1360,6 +1466,8 @@ function ContextIndicator({
|
|
|
1360
1466
|
}
|
|
1361
1467
|
// src/presentation/hooks/useChat.tsx
|
|
1362
1468
|
import * as React6 from "react";
|
|
1469
|
+
import { tool } from "ai";
|
|
1470
|
+
import { z } from "zod";
|
|
1363
1471
|
|
|
1364
1472
|
// src/core/chat-service.ts
|
|
1365
1473
|
import { generateText, streamText } from "ai";
|
|
@@ -1494,6 +1602,9 @@ class ChatService {
|
|
|
1494
1602
|
systemPrompt;
|
|
1495
1603
|
maxHistoryMessages;
|
|
1496
1604
|
onUsage;
|
|
1605
|
+
tools;
|
|
1606
|
+
sendReasoning;
|
|
1607
|
+
sendSources;
|
|
1497
1608
|
constructor(config) {
|
|
1498
1609
|
this.provider = config.provider;
|
|
1499
1610
|
this.context = config.context;
|
|
@@ -1501,6 +1612,9 @@ class ChatService {
|
|
|
1501
1612
|
this.systemPrompt = config.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
|
|
1502
1613
|
this.maxHistoryMessages = config.maxHistoryMessages ?? 20;
|
|
1503
1614
|
this.onUsage = config.onUsage;
|
|
1615
|
+
this.tools = config.tools;
|
|
1616
|
+
this.sendReasoning = config.sendReasoning ?? false;
|
|
1617
|
+
this.sendSources = config.sendSources ?? false;
|
|
1504
1618
|
}
|
|
1505
1619
|
async send(options) {
|
|
1506
1620
|
let conversation;
|
|
@@ -1525,13 +1639,14 @@ class ChatService {
|
|
|
1525
1639
|
status: "completed",
|
|
1526
1640
|
attachments: options.attachments
|
|
1527
1641
|
});
|
|
1528
|
-
const
|
|
1642
|
+
const messages = this.buildMessages(conversation, options);
|
|
1529
1643
|
const model = this.provider.getModel();
|
|
1530
1644
|
try {
|
|
1531
1645
|
const result = await generateText({
|
|
1532
1646
|
model,
|
|
1533
|
-
|
|
1534
|
-
system: this.systemPrompt
|
|
1647
|
+
messages,
|
|
1648
|
+
system: this.systemPrompt,
|
|
1649
|
+
tools: this.tools
|
|
1535
1650
|
});
|
|
1536
1651
|
const assistantMessage = await this.store.appendMessage(conversation.id, {
|
|
1537
1652
|
role: "assistant",
|
|
@@ -1587,33 +1702,106 @@ class ChatService {
|
|
|
1587
1702
|
content: "",
|
|
1588
1703
|
status: "streaming"
|
|
1589
1704
|
});
|
|
1590
|
-
const
|
|
1705
|
+
const messages = this.buildMessages(conversation, options);
|
|
1591
1706
|
const model = this.provider.getModel();
|
|
1592
|
-
const
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1707
|
+
const systemPrompt = this.systemPrompt;
|
|
1708
|
+
const tools = this.tools;
|
|
1709
|
+
const store = this.store;
|
|
1710
|
+
const onUsage = this.onUsage;
|
|
1596
1711
|
async function* streamGenerator() {
|
|
1597
1712
|
let fullContent = "";
|
|
1713
|
+
let fullReasoning = "";
|
|
1714
|
+
const toolCallsMap = new Map;
|
|
1715
|
+
const sources = [];
|
|
1598
1716
|
try {
|
|
1599
1717
|
const result = streamText({
|
|
1600
1718
|
model,
|
|
1601
|
-
|
|
1602
|
-
system:
|
|
1719
|
+
messages,
|
|
1720
|
+
system: systemPrompt,
|
|
1721
|
+
tools
|
|
1603
1722
|
});
|
|
1604
|
-
for await (const
|
|
1605
|
-
|
|
1606
|
-
|
|
1723
|
+
for await (const part of result.fullStream) {
|
|
1724
|
+
if (part.type === "text-delta") {
|
|
1725
|
+
const text = part.text ?? "";
|
|
1726
|
+
if (text) {
|
|
1727
|
+
fullContent += text;
|
|
1728
|
+
yield { type: "text", content: text };
|
|
1729
|
+
}
|
|
1730
|
+
} else if (part.type === "reasoning-delta") {
|
|
1731
|
+
const text = part.text ?? "";
|
|
1732
|
+
if (text) {
|
|
1733
|
+
fullReasoning += text;
|
|
1734
|
+
yield { type: "reasoning", content: text };
|
|
1735
|
+
}
|
|
1736
|
+
} else if (part.type === "source") {
|
|
1737
|
+
const src = part;
|
|
1738
|
+
const source = {
|
|
1739
|
+
id: src.id,
|
|
1740
|
+
title: src.title ?? "",
|
|
1741
|
+
url: src.url,
|
|
1742
|
+
type: "web"
|
|
1743
|
+
};
|
|
1744
|
+
sources.push(source);
|
|
1745
|
+
yield { type: "source", source };
|
|
1746
|
+
} else if (part.type === "tool-call") {
|
|
1747
|
+
const toolCall = {
|
|
1748
|
+
id: part.toolCallId,
|
|
1749
|
+
name: part.toolName,
|
|
1750
|
+
args: part.input ?? {},
|
|
1751
|
+
status: "running"
|
|
1752
|
+
};
|
|
1753
|
+
toolCallsMap.set(part.toolCallId, toolCall);
|
|
1754
|
+
yield { type: "tool_call", toolCall };
|
|
1755
|
+
} else if (part.type === "tool-result") {
|
|
1756
|
+
const tc = toolCallsMap.get(part.toolCallId);
|
|
1757
|
+
if (tc) {
|
|
1758
|
+
tc.result = part.output;
|
|
1759
|
+
tc.status = "completed";
|
|
1760
|
+
}
|
|
1761
|
+
yield {
|
|
1762
|
+
type: "tool_result",
|
|
1763
|
+
toolResult: {
|
|
1764
|
+
toolCallId: part.toolCallId,
|
|
1765
|
+
toolName: part.toolName,
|
|
1766
|
+
result: part.output
|
|
1767
|
+
}
|
|
1768
|
+
};
|
|
1769
|
+
} else if (part.type === "tool-error") {
|
|
1770
|
+
const tc = toolCallsMap.get(part.toolCallId);
|
|
1771
|
+
if (tc) {
|
|
1772
|
+
tc.status = "error";
|
|
1773
|
+
tc.error = part.error ?? "Tool execution failed";
|
|
1774
|
+
}
|
|
1775
|
+
} else if (part.type === "finish") {
|
|
1776
|
+
const usage = part.usage;
|
|
1777
|
+
const inputTokens = usage?.inputTokens ?? 0;
|
|
1778
|
+
const outputTokens = usage?.completionTokens ?? 0;
|
|
1779
|
+
await store.updateMessage(conversation.id, assistantMessage.id, {
|
|
1780
|
+
content: fullContent,
|
|
1781
|
+
status: "completed",
|
|
1782
|
+
reasoning: fullReasoning || undefined,
|
|
1783
|
+
sources: sources.length > 0 ? sources : undefined,
|
|
1784
|
+
toolCalls: toolCallsMap.size > 0 ? Array.from(toolCallsMap.values()) : undefined,
|
|
1785
|
+
usage: usage ? { inputTokens, outputTokens } : undefined
|
|
1786
|
+
});
|
|
1787
|
+
onUsage?.({ inputTokens, outputTokens });
|
|
1788
|
+
yield {
|
|
1789
|
+
type: "done",
|
|
1790
|
+
usage: usage ? { inputTokens, outputTokens } : undefined
|
|
1791
|
+
};
|
|
1792
|
+
return;
|
|
1793
|
+
}
|
|
1607
1794
|
}
|
|
1608
|
-
await
|
|
1795
|
+
await store.updateMessage(conversation.id, assistantMessage.id, {
|
|
1609
1796
|
content: fullContent,
|
|
1610
|
-
status: "completed"
|
|
1797
|
+
status: "completed",
|
|
1798
|
+
reasoning: fullReasoning || undefined,
|
|
1799
|
+
sources: sources.length > 0 ? sources : undefined,
|
|
1800
|
+
toolCalls: toolCallsMap.size > 0 ? Array.from(toolCallsMap.values()) : undefined
|
|
1611
1801
|
});
|
|
1612
|
-
yield {
|
|
1613
|
-
type: "done"
|
|
1614
|
-
};
|
|
1802
|
+
yield { type: "done" };
|
|
1615
1803
|
} catch (error) {
|
|
1616
|
-
await
|
|
1804
|
+
await store.updateMessage(conversation.id, assistantMessage.id, {
|
|
1617
1805
|
content: fullContent,
|
|
1618
1806
|
status: "error",
|
|
1619
1807
|
error: {
|
|
@@ -1648,40 +1836,59 @@ class ChatService {
|
|
|
1648
1836
|
async deleteConversation(conversationId) {
|
|
1649
1837
|
return this.store.delete(conversationId);
|
|
1650
1838
|
}
|
|
1651
|
-
|
|
1652
|
-
let prompt = "";
|
|
1839
|
+
buildMessages(conversation, _options) {
|
|
1653
1840
|
const historyStart = Math.max(0, conversation.messages.length - this.maxHistoryMessages);
|
|
1841
|
+
const messages = [];
|
|
1654
1842
|
for (let i = historyStart;i < conversation.messages.length; i++) {
|
|
1655
1843
|
const msg = conversation.messages[i];
|
|
1656
1844
|
if (!msg)
|
|
1657
1845
|
continue;
|
|
1658
|
-
if (msg.role === "user"
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
let content = options.content;
|
|
1665
|
-
if (options.attachments?.length) {
|
|
1666
|
-
const attachmentInfo = options.attachments.map((a) => {
|
|
1667
|
-
if (a.type === "file" || a.type === "code") {
|
|
1668
|
-
return `
|
|
1846
|
+
if (msg.role === "user") {
|
|
1847
|
+
let content = msg.content;
|
|
1848
|
+
if (msg.attachments?.length) {
|
|
1849
|
+
const attachmentInfo = msg.attachments.map((a) => {
|
|
1850
|
+
if (a.type === "file" || a.type === "code") {
|
|
1851
|
+
return `
|
|
1669
1852
|
|
|
1670
1853
|
### ${a.name}
|
|
1671
1854
|
\`\`\`
|
|
1672
|
-
${a.content}
|
|
1855
|
+
${a.content ?? ""}
|
|
1673
1856
|
\`\`\``;
|
|
1674
|
-
|
|
1675
|
-
|
|
1857
|
+
}
|
|
1858
|
+
return `
|
|
1676
1859
|
|
|
1677
1860
|
[Attachment: ${a.name}]`;
|
|
1678
|
-
|
|
1679
|
-
|
|
1861
|
+
}).join("");
|
|
1862
|
+
content += attachmentInfo;
|
|
1863
|
+
}
|
|
1864
|
+
messages.push({ role: "user", content });
|
|
1865
|
+
} else if (msg.role === "assistant") {
|
|
1866
|
+
if (msg.toolCalls?.length) {
|
|
1867
|
+
messages.push({
|
|
1868
|
+
role: "assistant",
|
|
1869
|
+
content: msg.content || "",
|
|
1870
|
+
toolCalls: msg.toolCalls.map((tc) => ({
|
|
1871
|
+
type: "tool-call",
|
|
1872
|
+
toolCallId: tc.id,
|
|
1873
|
+
toolName: tc.name,
|
|
1874
|
+
args: tc.args
|
|
1875
|
+
}))
|
|
1876
|
+
});
|
|
1877
|
+
messages.push({
|
|
1878
|
+
role: "tool",
|
|
1879
|
+
content: msg.toolCalls.map((tc) => ({
|
|
1880
|
+
type: "tool-result",
|
|
1881
|
+
toolCallId: tc.id,
|
|
1882
|
+
toolName: tc.name,
|
|
1883
|
+
output: tc.result
|
|
1884
|
+
}))
|
|
1885
|
+
});
|
|
1886
|
+
} else {
|
|
1887
|
+
messages.push({ role: "assistant", content: msg.content });
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1680
1890
|
}
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
Assistant:`;
|
|
1684
|
-
return prompt;
|
|
1891
|
+
return messages;
|
|
1685
1892
|
}
|
|
1686
1893
|
}
|
|
1687
1894
|
function createChatService(config) {
|
|
@@ -1693,6 +1900,17 @@ import {
|
|
|
1693
1900
|
createProvider
|
|
1694
1901
|
} from "@contractspec/lib.ai-providers";
|
|
1695
1902
|
"use client";
|
|
1903
|
+
function toolsToToolSet(defs) {
|
|
1904
|
+
const result = {};
|
|
1905
|
+
for (const def of defs) {
|
|
1906
|
+
result[def.name] = tool({
|
|
1907
|
+
description: def.description ?? def.name,
|
|
1908
|
+
inputSchema: z.object({}).passthrough(),
|
|
1909
|
+
execute: async () => ({})
|
|
1910
|
+
});
|
|
1911
|
+
}
|
|
1912
|
+
return result;
|
|
1913
|
+
}
|
|
1696
1914
|
function useChat(options = {}) {
|
|
1697
1915
|
const {
|
|
1698
1916
|
provider = "openai",
|
|
@@ -1706,7 +1924,8 @@ function useChat(options = {}) {
|
|
|
1706
1924
|
onSend,
|
|
1707
1925
|
onResponse,
|
|
1708
1926
|
onError,
|
|
1709
|
-
onUsage
|
|
1927
|
+
onUsage,
|
|
1928
|
+
tools: toolsDefs
|
|
1710
1929
|
} = options;
|
|
1711
1930
|
const [messages, setMessages] = React6.useState([]);
|
|
1712
1931
|
const [conversation, setConversation] = React6.useState(null);
|
|
@@ -1725,9 +1944,19 @@ function useChat(options = {}) {
|
|
|
1725
1944
|
chatServiceRef.current = new ChatService({
|
|
1726
1945
|
provider: chatProvider,
|
|
1727
1946
|
systemPrompt,
|
|
1728
|
-
onUsage
|
|
1947
|
+
onUsage,
|
|
1948
|
+
tools: toolsDefs?.length ? toolsToToolSet(toolsDefs) : undefined
|
|
1729
1949
|
});
|
|
1730
|
-
}, [
|
|
1950
|
+
}, [
|
|
1951
|
+
provider,
|
|
1952
|
+
mode,
|
|
1953
|
+
model,
|
|
1954
|
+
apiKey,
|
|
1955
|
+
proxyUrl,
|
|
1956
|
+
systemPrompt,
|
|
1957
|
+
onUsage,
|
|
1958
|
+
toolsDefs
|
|
1959
|
+
]);
|
|
1731
1960
|
React6.useEffect(() => {
|
|
1732
1961
|
if (!conversationId || !chatServiceRef.current)
|
|
1733
1962
|
return;
|
|
@@ -1782,13 +2011,50 @@ function useChat(options = {}) {
|
|
|
1782
2011
|
};
|
|
1783
2012
|
setMessages((prev) => [...prev, assistantMessage]);
|
|
1784
2013
|
let fullContent = "";
|
|
2014
|
+
let fullReasoning = "";
|
|
2015
|
+
const toolCallsMap = new Map;
|
|
2016
|
+
const sources = [];
|
|
1785
2017
|
for await (const chunk of result.stream) {
|
|
1786
2018
|
if (chunk.type === "text" && chunk.content) {
|
|
1787
2019
|
fullContent += chunk.content;
|
|
1788
|
-
setMessages((prev) => prev.map((m) => m.id === result.messageId ? {
|
|
2020
|
+
setMessages((prev) => prev.map((m) => m.id === result.messageId ? {
|
|
2021
|
+
...m,
|
|
2022
|
+
content: fullContent,
|
|
2023
|
+
reasoning: fullReasoning || undefined,
|
|
2024
|
+
sources: sources.length ? sources : undefined,
|
|
2025
|
+
toolCalls: toolCallsMap.size ? Array.from(toolCallsMap.values()) : undefined
|
|
2026
|
+
} : m));
|
|
2027
|
+
} else if (chunk.type === "reasoning" && chunk.content) {
|
|
2028
|
+
fullReasoning += chunk.content;
|
|
2029
|
+
setMessages((prev) => prev.map((m) => m.id === result.messageId ? { ...m, reasoning: fullReasoning } : m));
|
|
2030
|
+
} else if (chunk.type === "source" && chunk.source) {
|
|
2031
|
+
sources.push(chunk.source);
|
|
2032
|
+
setMessages((prev) => prev.map((m) => m.id === result.messageId ? { ...m, sources: [...sources] } : m));
|
|
2033
|
+
} else if (chunk.type === "tool_call" && chunk.toolCall) {
|
|
2034
|
+
const tc = chunk.toolCall;
|
|
2035
|
+
const chatTc = {
|
|
2036
|
+
id: tc.id,
|
|
2037
|
+
name: tc.name,
|
|
2038
|
+
args: tc.args,
|
|
2039
|
+
status: "running"
|
|
2040
|
+
};
|
|
2041
|
+
toolCallsMap.set(tc.id, chatTc);
|
|
2042
|
+
setMessages((prev) => prev.map((m) => m.id === result.messageId ? { ...m, toolCalls: Array.from(toolCallsMap.values()) } : m));
|
|
2043
|
+
} else if (chunk.type === "tool_result" && chunk.toolResult) {
|
|
2044
|
+
const tr = chunk.toolResult;
|
|
2045
|
+
const tc = toolCallsMap.get(tr.toolCallId);
|
|
2046
|
+
if (tc) {
|
|
2047
|
+
tc.result = tr.result;
|
|
2048
|
+
tc.status = "completed";
|
|
2049
|
+
}
|
|
2050
|
+
setMessages((prev) => prev.map((m) => m.id === result.messageId ? { ...m, toolCalls: Array.from(toolCallsMap.values()) } : m));
|
|
1789
2051
|
} else if (chunk.type === "done") {
|
|
1790
2052
|
setMessages((prev) => prev.map((m) => m.id === result.messageId ? {
|
|
1791
2053
|
...m,
|
|
2054
|
+
content: fullContent,
|
|
2055
|
+
reasoning: fullReasoning || undefined,
|
|
2056
|
+
sources: sources.length ? sources : undefined,
|
|
2057
|
+
toolCalls: toolCallsMap.size ? Array.from(toolCallsMap.values()) : undefined,
|
|
1792
2058
|
status: "completed",
|
|
1793
2059
|
usage: chunk.usage,
|
|
1794
2060
|
updatedAt: new Date
|
|
@@ -1850,6 +2116,10 @@ function useChat(options = {}) {
|
|
|
1850
2116
|
abortControllerRef.current?.abort();
|
|
1851
2117
|
setIsLoading(false);
|
|
1852
2118
|
}, []);
|
|
2119
|
+
const addToolApprovalResponse = React6.useCallback((_toolCallId, _result) => {
|
|
2120
|
+
throw new Error(`addToolApprovalResponse: Tool approval requires server route with toUIMessageStreamResponse. ` + `Use createChatRoute and @ai-sdk/react useChat for tools with requireApproval.`);
|
|
2121
|
+
}, []);
|
|
2122
|
+
const hasApprovalTools = toolsDefs?.some((t) => t.requireApproval) ?? false;
|
|
1853
2123
|
return {
|
|
1854
2124
|
messages,
|
|
1855
2125
|
conversation,
|
|
@@ -1859,7 +2129,8 @@ function useChat(options = {}) {
|
|
|
1859
2129
|
clearConversation,
|
|
1860
2130
|
setConversationId,
|
|
1861
2131
|
regenerate,
|
|
1862
|
-
stop
|
|
2132
|
+
stop,
|
|
2133
|
+
...hasApprovalTools && { addToolApprovalResponse }
|
|
1863
2134
|
};
|
|
1864
2135
|
}
|
|
1865
2136
|
// src/presentation/hooks/useProviders.tsx
|
|
@@ -1902,6 +2173,9 @@ function useProviders() {
|
|
|
1902
2173
|
refresh: loadProviders
|
|
1903
2174
|
};
|
|
1904
2175
|
}
|
|
2176
|
+
|
|
2177
|
+
// src/presentation/hooks/index.ts
|
|
2178
|
+
import { useCompletion } from "@ai-sdk/react";
|
|
1905
2179
|
// src/providers/index.ts
|
|
1906
2180
|
import {
|
|
1907
2181
|
createProvider as createProvider2,
|
|
@@ -2236,6 +2510,7 @@ var ChatErrorEvent = defineEvent({
|
|
|
2236
2510
|
export {
|
|
2237
2511
|
validateProvider,
|
|
2238
2512
|
useProviders,
|
|
2513
|
+
useCompletion,
|
|
2239
2514
|
useChat,
|
|
2240
2515
|
supportsLocalMode,
|
|
2241
2516
|
listOllamaModels,
|