@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/browser/index.js
CHANGED
|
@@ -483,7 +483,15 @@ import * as React3 from "react";
|
|
|
483
483
|
import { cn as cn3 } from "@contractspec/lib.ui-kit-web/ui/utils";
|
|
484
484
|
import { Avatar, AvatarFallback } from "@contractspec/lib.ui-kit-web/ui/avatar";
|
|
485
485
|
import { Skeleton } from "@contractspec/lib.ui-kit-web/ui/skeleton";
|
|
486
|
-
import {
|
|
486
|
+
import {
|
|
487
|
+
Bot,
|
|
488
|
+
User,
|
|
489
|
+
AlertCircle,
|
|
490
|
+
Copy as Copy2,
|
|
491
|
+
Check as Check2,
|
|
492
|
+
ExternalLink,
|
|
493
|
+
Wrench
|
|
494
|
+
} from "lucide-react";
|
|
487
495
|
import { Button as Button2 } from "@contractspec/lib.design-system";
|
|
488
496
|
|
|
489
497
|
// src/presentation/components/CodePreview.tsx
|
|
@@ -645,12 +653,40 @@ function extractCodeBlocks(content) {
|
|
|
645
653
|
}
|
|
646
654
|
return blocks;
|
|
647
655
|
}
|
|
656
|
+
function renderInlineMarkdown(text) {
|
|
657
|
+
const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
|
|
658
|
+
const parts = [];
|
|
659
|
+
let lastIndex = 0;
|
|
660
|
+
let match;
|
|
661
|
+
let key = 0;
|
|
662
|
+
while ((match = linkRegex.exec(text)) !== null) {
|
|
663
|
+
if (match.index > lastIndex) {
|
|
664
|
+
parts.push(/* @__PURE__ */ jsxDEV3("span", {
|
|
665
|
+
children: text.slice(lastIndex, match.index)
|
|
666
|
+
}, key++, false, undefined, this));
|
|
667
|
+
}
|
|
668
|
+
parts.push(/* @__PURE__ */ jsxDEV3("a", {
|
|
669
|
+
href: match[2],
|
|
670
|
+
target: "_blank",
|
|
671
|
+
rel: "noopener noreferrer",
|
|
672
|
+
className: "text-primary underline hover:no-underline",
|
|
673
|
+
children: match[1]
|
|
674
|
+
}, key++, false, undefined, this));
|
|
675
|
+
lastIndex = match.index + match[0].length;
|
|
676
|
+
}
|
|
677
|
+
if (lastIndex < text.length) {
|
|
678
|
+
parts.push(/* @__PURE__ */ jsxDEV3("span", {
|
|
679
|
+
children: text.slice(lastIndex)
|
|
680
|
+
}, key++, false, undefined, this));
|
|
681
|
+
}
|
|
682
|
+
return parts.length > 0 ? parts : [text];
|
|
683
|
+
}
|
|
648
684
|
function MessageContent({ content }) {
|
|
649
685
|
const codeBlocks = extractCodeBlocks(content);
|
|
650
686
|
if (codeBlocks.length === 0) {
|
|
651
687
|
return /* @__PURE__ */ jsxDEV3("p", {
|
|
652
688
|
className: "whitespace-pre-wrap",
|
|
653
|
-
children: content
|
|
689
|
+
children: renderInlineMarkdown(content)
|
|
654
690
|
}, undefined, false, undefined, this);
|
|
655
691
|
}
|
|
656
692
|
let remaining = content;
|
|
@@ -661,7 +697,7 @@ function MessageContent({ content }) {
|
|
|
661
697
|
if (before) {
|
|
662
698
|
parts.push(/* @__PURE__ */ jsxDEV3("p", {
|
|
663
699
|
className: "whitespace-pre-wrap",
|
|
664
|
-
children: before.trim()
|
|
700
|
+
children: renderInlineMarkdown(before.trim())
|
|
665
701
|
}, key++, false, undefined, this));
|
|
666
702
|
}
|
|
667
703
|
parts.push(/* @__PURE__ */ jsxDEV3(CodePreview, {
|
|
@@ -674,7 +710,7 @@ function MessageContent({ content }) {
|
|
|
674
710
|
if (remaining.trim()) {
|
|
675
711
|
parts.push(/* @__PURE__ */ jsxDEV3("p", {
|
|
676
712
|
className: "whitespace-pre-wrap",
|
|
677
|
-
children: remaining.trim()
|
|
713
|
+
children: renderInlineMarkdown(remaining.trim())
|
|
678
714
|
}, key++, false, undefined, this));
|
|
679
715
|
}
|
|
680
716
|
return /* @__PURE__ */ jsxDEV3(Fragment, {
|
|
@@ -792,7 +828,77 @@ function ChatMessage({
|
|
|
792
828
|
}, undefined, false, undefined, this)
|
|
793
829
|
}, undefined, false, undefined, this)
|
|
794
830
|
]
|
|
795
|
-
}, undefined, true, undefined, this)
|
|
831
|
+
}, undefined, true, undefined, this),
|
|
832
|
+
message.sources && message.sources.length > 0 && /* @__PURE__ */ jsxDEV3("div", {
|
|
833
|
+
className: "mt-2 flex flex-wrap gap-2",
|
|
834
|
+
children: message.sources.map((source) => /* @__PURE__ */ jsxDEV3("a", {
|
|
835
|
+
href: source.url ?? "#",
|
|
836
|
+
target: "_blank",
|
|
837
|
+
rel: "noopener noreferrer",
|
|
838
|
+
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",
|
|
839
|
+
children: [
|
|
840
|
+
/* @__PURE__ */ jsxDEV3(ExternalLink, {
|
|
841
|
+
className: "h-3 w-3"
|
|
842
|
+
}, undefined, false, undefined, this),
|
|
843
|
+
source.title || source.url || source.id
|
|
844
|
+
]
|
|
845
|
+
}, source.id, true, undefined, this))
|
|
846
|
+
}, undefined, false, undefined, this),
|
|
847
|
+
message.toolCalls && message.toolCalls.length > 0 && /* @__PURE__ */ jsxDEV3("div", {
|
|
848
|
+
className: "mt-2 space-y-2",
|
|
849
|
+
children: message.toolCalls.map((tc) => /* @__PURE__ */ jsxDEV3("details", {
|
|
850
|
+
className: "bg-muted border-border rounded-md border",
|
|
851
|
+
children: [
|
|
852
|
+
/* @__PURE__ */ jsxDEV3("summary", {
|
|
853
|
+
className: "flex cursor-pointer items-center gap-2 px-3 py-2 text-sm font-medium",
|
|
854
|
+
children: [
|
|
855
|
+
/* @__PURE__ */ jsxDEV3(Wrench, {
|
|
856
|
+
className: "text-muted-foreground h-4 w-4"
|
|
857
|
+
}, undefined, false, undefined, this),
|
|
858
|
+
tc.name,
|
|
859
|
+
/* @__PURE__ */ jsxDEV3("span", {
|
|
860
|
+
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"),
|
|
861
|
+
children: tc.status
|
|
862
|
+
}, undefined, false, undefined, this)
|
|
863
|
+
]
|
|
864
|
+
}, undefined, true, undefined, this),
|
|
865
|
+
/* @__PURE__ */ jsxDEV3("div", {
|
|
866
|
+
className: "border-border border-t px-3 py-2 text-xs",
|
|
867
|
+
children: [
|
|
868
|
+
Object.keys(tc.args).length > 0 && /* @__PURE__ */ jsxDEV3("div", {
|
|
869
|
+
className: "mb-2",
|
|
870
|
+
children: [
|
|
871
|
+
/* @__PURE__ */ jsxDEV3("span", {
|
|
872
|
+
className: "text-muted-foreground font-medium",
|
|
873
|
+
children: "Input:"
|
|
874
|
+
}, undefined, false, undefined, this),
|
|
875
|
+
/* @__PURE__ */ jsxDEV3("pre", {
|
|
876
|
+
className: "bg-background mt-1 overflow-x-auto rounded p-2",
|
|
877
|
+
children: JSON.stringify(tc.args, null, 2)
|
|
878
|
+
}, undefined, false, undefined, this)
|
|
879
|
+
]
|
|
880
|
+
}, undefined, true, undefined, this),
|
|
881
|
+
tc.result !== undefined && /* @__PURE__ */ jsxDEV3("div", {
|
|
882
|
+
children: [
|
|
883
|
+
/* @__PURE__ */ jsxDEV3("span", {
|
|
884
|
+
className: "text-muted-foreground font-medium",
|
|
885
|
+
children: "Output:"
|
|
886
|
+
}, undefined, false, undefined, this),
|
|
887
|
+
/* @__PURE__ */ jsxDEV3("pre", {
|
|
888
|
+
className: "bg-background mt-1 overflow-x-auto rounded p-2",
|
|
889
|
+
children: typeof tc.result === "object" ? JSON.stringify(tc.result, null, 2) : String(tc.result)
|
|
890
|
+
}, undefined, false, undefined, this)
|
|
891
|
+
]
|
|
892
|
+
}, undefined, true, undefined, this),
|
|
893
|
+
tc.error && /* @__PURE__ */ jsxDEV3("p", {
|
|
894
|
+
className: "text-destructive mt-1",
|
|
895
|
+
children: tc.error
|
|
896
|
+
}, undefined, false, undefined, this)
|
|
897
|
+
]
|
|
898
|
+
}, undefined, true, undefined, this)
|
|
899
|
+
]
|
|
900
|
+
}, tc.id, true, undefined, this))
|
|
901
|
+
}, undefined, false, undefined, this)
|
|
796
902
|
]
|
|
797
903
|
}, undefined, true, undefined, this)
|
|
798
904
|
]
|
|
@@ -1365,6 +1471,8 @@ function ContextIndicator({
|
|
|
1365
1471
|
}
|
|
1366
1472
|
// src/presentation/hooks/useChat.tsx
|
|
1367
1473
|
import * as React6 from "react";
|
|
1474
|
+
import { tool } from "ai";
|
|
1475
|
+
import { z } from "zod";
|
|
1368
1476
|
|
|
1369
1477
|
// src/core/chat-service.ts
|
|
1370
1478
|
import { generateText, streamText } from "ai";
|
|
@@ -1499,6 +1607,9 @@ class ChatService {
|
|
|
1499
1607
|
systemPrompt;
|
|
1500
1608
|
maxHistoryMessages;
|
|
1501
1609
|
onUsage;
|
|
1610
|
+
tools;
|
|
1611
|
+
sendReasoning;
|
|
1612
|
+
sendSources;
|
|
1502
1613
|
constructor(config) {
|
|
1503
1614
|
this.provider = config.provider;
|
|
1504
1615
|
this.context = config.context;
|
|
@@ -1506,6 +1617,9 @@ class ChatService {
|
|
|
1506
1617
|
this.systemPrompt = config.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
|
|
1507
1618
|
this.maxHistoryMessages = config.maxHistoryMessages ?? 20;
|
|
1508
1619
|
this.onUsage = config.onUsage;
|
|
1620
|
+
this.tools = config.tools;
|
|
1621
|
+
this.sendReasoning = config.sendReasoning ?? false;
|
|
1622
|
+
this.sendSources = config.sendSources ?? false;
|
|
1509
1623
|
}
|
|
1510
1624
|
async send(options) {
|
|
1511
1625
|
let conversation;
|
|
@@ -1530,13 +1644,14 @@ class ChatService {
|
|
|
1530
1644
|
status: "completed",
|
|
1531
1645
|
attachments: options.attachments
|
|
1532
1646
|
});
|
|
1533
|
-
const
|
|
1647
|
+
const messages = this.buildMessages(conversation, options);
|
|
1534
1648
|
const model = this.provider.getModel();
|
|
1535
1649
|
try {
|
|
1536
1650
|
const result = await generateText({
|
|
1537
1651
|
model,
|
|
1538
|
-
|
|
1539
|
-
system: this.systemPrompt
|
|
1652
|
+
messages,
|
|
1653
|
+
system: this.systemPrompt,
|
|
1654
|
+
tools: this.tools
|
|
1540
1655
|
});
|
|
1541
1656
|
const assistantMessage = await this.store.appendMessage(conversation.id, {
|
|
1542
1657
|
role: "assistant",
|
|
@@ -1592,33 +1707,106 @@ class ChatService {
|
|
|
1592
1707
|
content: "",
|
|
1593
1708
|
status: "streaming"
|
|
1594
1709
|
});
|
|
1595
|
-
const
|
|
1710
|
+
const messages = this.buildMessages(conversation, options);
|
|
1596
1711
|
const model = this.provider.getModel();
|
|
1597
|
-
const
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1712
|
+
const systemPrompt = this.systemPrompt;
|
|
1713
|
+
const tools = this.tools;
|
|
1714
|
+
const store = this.store;
|
|
1715
|
+
const onUsage = this.onUsage;
|
|
1601
1716
|
async function* streamGenerator() {
|
|
1602
1717
|
let fullContent = "";
|
|
1718
|
+
let fullReasoning = "";
|
|
1719
|
+
const toolCallsMap = new Map;
|
|
1720
|
+
const sources = [];
|
|
1603
1721
|
try {
|
|
1604
1722
|
const result = streamText({
|
|
1605
1723
|
model,
|
|
1606
|
-
|
|
1607
|
-
system:
|
|
1724
|
+
messages,
|
|
1725
|
+
system: systemPrompt,
|
|
1726
|
+
tools
|
|
1608
1727
|
});
|
|
1609
|
-
for await (const
|
|
1610
|
-
|
|
1611
|
-
|
|
1728
|
+
for await (const part of result.fullStream) {
|
|
1729
|
+
if (part.type === "text-delta") {
|
|
1730
|
+
const text = part.text ?? "";
|
|
1731
|
+
if (text) {
|
|
1732
|
+
fullContent += text;
|
|
1733
|
+
yield { type: "text", content: text };
|
|
1734
|
+
}
|
|
1735
|
+
} else if (part.type === "reasoning-delta") {
|
|
1736
|
+
const text = part.text ?? "";
|
|
1737
|
+
if (text) {
|
|
1738
|
+
fullReasoning += text;
|
|
1739
|
+
yield { type: "reasoning", content: text };
|
|
1740
|
+
}
|
|
1741
|
+
} else if (part.type === "source") {
|
|
1742
|
+
const src = part;
|
|
1743
|
+
const source = {
|
|
1744
|
+
id: src.id,
|
|
1745
|
+
title: src.title ?? "",
|
|
1746
|
+
url: src.url,
|
|
1747
|
+
type: "web"
|
|
1748
|
+
};
|
|
1749
|
+
sources.push(source);
|
|
1750
|
+
yield { type: "source", source };
|
|
1751
|
+
} else if (part.type === "tool-call") {
|
|
1752
|
+
const toolCall = {
|
|
1753
|
+
id: part.toolCallId,
|
|
1754
|
+
name: part.toolName,
|
|
1755
|
+
args: part.input ?? {},
|
|
1756
|
+
status: "running"
|
|
1757
|
+
};
|
|
1758
|
+
toolCallsMap.set(part.toolCallId, toolCall);
|
|
1759
|
+
yield { type: "tool_call", toolCall };
|
|
1760
|
+
} else if (part.type === "tool-result") {
|
|
1761
|
+
const tc = toolCallsMap.get(part.toolCallId);
|
|
1762
|
+
if (tc) {
|
|
1763
|
+
tc.result = part.output;
|
|
1764
|
+
tc.status = "completed";
|
|
1765
|
+
}
|
|
1766
|
+
yield {
|
|
1767
|
+
type: "tool_result",
|
|
1768
|
+
toolResult: {
|
|
1769
|
+
toolCallId: part.toolCallId,
|
|
1770
|
+
toolName: part.toolName,
|
|
1771
|
+
result: part.output
|
|
1772
|
+
}
|
|
1773
|
+
};
|
|
1774
|
+
} else if (part.type === "tool-error") {
|
|
1775
|
+
const tc = toolCallsMap.get(part.toolCallId);
|
|
1776
|
+
if (tc) {
|
|
1777
|
+
tc.status = "error";
|
|
1778
|
+
tc.error = part.error ?? "Tool execution failed";
|
|
1779
|
+
}
|
|
1780
|
+
} else if (part.type === "finish") {
|
|
1781
|
+
const usage = part.usage;
|
|
1782
|
+
const inputTokens = usage?.inputTokens ?? 0;
|
|
1783
|
+
const outputTokens = usage?.completionTokens ?? 0;
|
|
1784
|
+
await store.updateMessage(conversation.id, assistantMessage.id, {
|
|
1785
|
+
content: fullContent,
|
|
1786
|
+
status: "completed",
|
|
1787
|
+
reasoning: fullReasoning || undefined,
|
|
1788
|
+
sources: sources.length > 0 ? sources : undefined,
|
|
1789
|
+
toolCalls: toolCallsMap.size > 0 ? Array.from(toolCallsMap.values()) : undefined,
|
|
1790
|
+
usage: usage ? { inputTokens, outputTokens } : undefined
|
|
1791
|
+
});
|
|
1792
|
+
onUsage?.({ inputTokens, outputTokens });
|
|
1793
|
+
yield {
|
|
1794
|
+
type: "done",
|
|
1795
|
+
usage: usage ? { inputTokens, outputTokens } : undefined
|
|
1796
|
+
};
|
|
1797
|
+
return;
|
|
1798
|
+
}
|
|
1612
1799
|
}
|
|
1613
|
-
await
|
|
1800
|
+
await store.updateMessage(conversation.id, assistantMessage.id, {
|
|
1614
1801
|
content: fullContent,
|
|
1615
|
-
status: "completed"
|
|
1802
|
+
status: "completed",
|
|
1803
|
+
reasoning: fullReasoning || undefined,
|
|
1804
|
+
sources: sources.length > 0 ? sources : undefined,
|
|
1805
|
+
toolCalls: toolCallsMap.size > 0 ? Array.from(toolCallsMap.values()) : undefined
|
|
1616
1806
|
});
|
|
1617
|
-
yield {
|
|
1618
|
-
type: "done"
|
|
1619
|
-
};
|
|
1807
|
+
yield { type: "done" };
|
|
1620
1808
|
} catch (error) {
|
|
1621
|
-
await
|
|
1809
|
+
await store.updateMessage(conversation.id, assistantMessage.id, {
|
|
1622
1810
|
content: fullContent,
|
|
1623
1811
|
status: "error",
|
|
1624
1812
|
error: {
|
|
@@ -1653,40 +1841,59 @@ class ChatService {
|
|
|
1653
1841
|
async deleteConversation(conversationId) {
|
|
1654
1842
|
return this.store.delete(conversationId);
|
|
1655
1843
|
}
|
|
1656
|
-
|
|
1657
|
-
let prompt = "";
|
|
1844
|
+
buildMessages(conversation, _options) {
|
|
1658
1845
|
const historyStart = Math.max(0, conversation.messages.length - this.maxHistoryMessages);
|
|
1846
|
+
const messages = [];
|
|
1659
1847
|
for (let i = historyStart;i < conversation.messages.length; i++) {
|
|
1660
1848
|
const msg = conversation.messages[i];
|
|
1661
1849
|
if (!msg)
|
|
1662
1850
|
continue;
|
|
1663
|
-
if (msg.role === "user"
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
let content = options.content;
|
|
1670
|
-
if (options.attachments?.length) {
|
|
1671
|
-
const attachmentInfo = options.attachments.map((a) => {
|
|
1672
|
-
if (a.type === "file" || a.type === "code") {
|
|
1673
|
-
return `
|
|
1851
|
+
if (msg.role === "user") {
|
|
1852
|
+
let content = msg.content;
|
|
1853
|
+
if (msg.attachments?.length) {
|
|
1854
|
+
const attachmentInfo = msg.attachments.map((a) => {
|
|
1855
|
+
if (a.type === "file" || a.type === "code") {
|
|
1856
|
+
return `
|
|
1674
1857
|
|
|
1675
1858
|
### ${a.name}
|
|
1676
1859
|
\`\`\`
|
|
1677
|
-
${a.content}
|
|
1860
|
+
${a.content ?? ""}
|
|
1678
1861
|
\`\`\``;
|
|
1679
|
-
|
|
1680
|
-
|
|
1862
|
+
}
|
|
1863
|
+
return `
|
|
1681
1864
|
|
|
1682
1865
|
[Attachment: ${a.name}]`;
|
|
1683
|
-
|
|
1684
|
-
|
|
1866
|
+
}).join("");
|
|
1867
|
+
content += attachmentInfo;
|
|
1868
|
+
}
|
|
1869
|
+
messages.push({ role: "user", content });
|
|
1870
|
+
} else if (msg.role === "assistant") {
|
|
1871
|
+
if (msg.toolCalls?.length) {
|
|
1872
|
+
messages.push({
|
|
1873
|
+
role: "assistant",
|
|
1874
|
+
content: msg.content || "",
|
|
1875
|
+
toolCalls: msg.toolCalls.map((tc) => ({
|
|
1876
|
+
type: "tool-call",
|
|
1877
|
+
toolCallId: tc.id,
|
|
1878
|
+
toolName: tc.name,
|
|
1879
|
+
args: tc.args
|
|
1880
|
+
}))
|
|
1881
|
+
});
|
|
1882
|
+
messages.push({
|
|
1883
|
+
role: "tool",
|
|
1884
|
+
content: msg.toolCalls.map((tc) => ({
|
|
1885
|
+
type: "tool-result",
|
|
1886
|
+
toolCallId: tc.id,
|
|
1887
|
+
toolName: tc.name,
|
|
1888
|
+
output: tc.result
|
|
1889
|
+
}))
|
|
1890
|
+
});
|
|
1891
|
+
} else {
|
|
1892
|
+
messages.push({ role: "assistant", content: msg.content });
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1685
1895
|
}
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
Assistant:`;
|
|
1689
|
-
return prompt;
|
|
1896
|
+
return messages;
|
|
1690
1897
|
}
|
|
1691
1898
|
}
|
|
1692
1899
|
function createChatService(config) {
|
|
@@ -1698,6 +1905,17 @@ import {
|
|
|
1698
1905
|
createProvider
|
|
1699
1906
|
} from "@contractspec/lib.ai-providers";
|
|
1700
1907
|
"use client";
|
|
1908
|
+
function toolsToToolSet(defs) {
|
|
1909
|
+
const result = {};
|
|
1910
|
+
for (const def of defs) {
|
|
1911
|
+
result[def.name] = tool({
|
|
1912
|
+
description: def.description ?? def.name,
|
|
1913
|
+
inputSchema: z.object({}).passthrough(),
|
|
1914
|
+
execute: async () => ({})
|
|
1915
|
+
});
|
|
1916
|
+
}
|
|
1917
|
+
return result;
|
|
1918
|
+
}
|
|
1701
1919
|
function useChat(options = {}) {
|
|
1702
1920
|
const {
|
|
1703
1921
|
provider = "openai",
|
|
@@ -1711,7 +1929,8 @@ function useChat(options = {}) {
|
|
|
1711
1929
|
onSend,
|
|
1712
1930
|
onResponse,
|
|
1713
1931
|
onError,
|
|
1714
|
-
onUsage
|
|
1932
|
+
onUsage,
|
|
1933
|
+
tools: toolsDefs
|
|
1715
1934
|
} = options;
|
|
1716
1935
|
const [messages, setMessages] = React6.useState([]);
|
|
1717
1936
|
const [conversation, setConversation] = React6.useState(null);
|
|
@@ -1730,9 +1949,19 @@ function useChat(options = {}) {
|
|
|
1730
1949
|
chatServiceRef.current = new ChatService({
|
|
1731
1950
|
provider: chatProvider,
|
|
1732
1951
|
systemPrompt,
|
|
1733
|
-
onUsage
|
|
1952
|
+
onUsage,
|
|
1953
|
+
tools: toolsDefs?.length ? toolsToToolSet(toolsDefs) : undefined
|
|
1734
1954
|
});
|
|
1735
|
-
}, [
|
|
1955
|
+
}, [
|
|
1956
|
+
provider,
|
|
1957
|
+
mode,
|
|
1958
|
+
model,
|
|
1959
|
+
apiKey,
|
|
1960
|
+
proxyUrl,
|
|
1961
|
+
systemPrompt,
|
|
1962
|
+
onUsage,
|
|
1963
|
+
toolsDefs
|
|
1964
|
+
]);
|
|
1736
1965
|
React6.useEffect(() => {
|
|
1737
1966
|
if (!conversationId || !chatServiceRef.current)
|
|
1738
1967
|
return;
|
|
@@ -1787,13 +2016,50 @@ function useChat(options = {}) {
|
|
|
1787
2016
|
};
|
|
1788
2017
|
setMessages((prev) => [...prev, assistantMessage]);
|
|
1789
2018
|
let fullContent = "";
|
|
2019
|
+
let fullReasoning = "";
|
|
2020
|
+
const toolCallsMap = new Map;
|
|
2021
|
+
const sources = [];
|
|
1790
2022
|
for await (const chunk of result.stream) {
|
|
1791
2023
|
if (chunk.type === "text" && chunk.content) {
|
|
1792
2024
|
fullContent += chunk.content;
|
|
1793
|
-
setMessages((prev) => prev.map((m) => m.id === result.messageId ? {
|
|
2025
|
+
setMessages((prev) => prev.map((m) => m.id === result.messageId ? {
|
|
2026
|
+
...m,
|
|
2027
|
+
content: fullContent,
|
|
2028
|
+
reasoning: fullReasoning || undefined,
|
|
2029
|
+
sources: sources.length ? sources : undefined,
|
|
2030
|
+
toolCalls: toolCallsMap.size ? Array.from(toolCallsMap.values()) : undefined
|
|
2031
|
+
} : m));
|
|
2032
|
+
} else if (chunk.type === "reasoning" && chunk.content) {
|
|
2033
|
+
fullReasoning += chunk.content;
|
|
2034
|
+
setMessages((prev) => prev.map((m) => m.id === result.messageId ? { ...m, reasoning: fullReasoning } : m));
|
|
2035
|
+
} else if (chunk.type === "source" && chunk.source) {
|
|
2036
|
+
sources.push(chunk.source);
|
|
2037
|
+
setMessages((prev) => prev.map((m) => m.id === result.messageId ? { ...m, sources: [...sources] } : m));
|
|
2038
|
+
} else if (chunk.type === "tool_call" && chunk.toolCall) {
|
|
2039
|
+
const tc = chunk.toolCall;
|
|
2040
|
+
const chatTc = {
|
|
2041
|
+
id: tc.id,
|
|
2042
|
+
name: tc.name,
|
|
2043
|
+
args: tc.args,
|
|
2044
|
+
status: "running"
|
|
2045
|
+
};
|
|
2046
|
+
toolCallsMap.set(tc.id, chatTc);
|
|
2047
|
+
setMessages((prev) => prev.map((m) => m.id === result.messageId ? { ...m, toolCalls: Array.from(toolCallsMap.values()) } : m));
|
|
2048
|
+
} else if (chunk.type === "tool_result" && chunk.toolResult) {
|
|
2049
|
+
const tr = chunk.toolResult;
|
|
2050
|
+
const tc = toolCallsMap.get(tr.toolCallId);
|
|
2051
|
+
if (tc) {
|
|
2052
|
+
tc.result = tr.result;
|
|
2053
|
+
tc.status = "completed";
|
|
2054
|
+
}
|
|
2055
|
+
setMessages((prev) => prev.map((m) => m.id === result.messageId ? { ...m, toolCalls: Array.from(toolCallsMap.values()) } : m));
|
|
1794
2056
|
} else if (chunk.type === "done") {
|
|
1795
2057
|
setMessages((prev) => prev.map((m) => m.id === result.messageId ? {
|
|
1796
2058
|
...m,
|
|
2059
|
+
content: fullContent,
|
|
2060
|
+
reasoning: fullReasoning || undefined,
|
|
2061
|
+
sources: sources.length ? sources : undefined,
|
|
2062
|
+
toolCalls: toolCallsMap.size ? Array.from(toolCallsMap.values()) : undefined,
|
|
1797
2063
|
status: "completed",
|
|
1798
2064
|
usage: chunk.usage,
|
|
1799
2065
|
updatedAt: new Date
|
|
@@ -1855,6 +2121,10 @@ function useChat(options = {}) {
|
|
|
1855
2121
|
abortControllerRef.current?.abort();
|
|
1856
2122
|
setIsLoading(false);
|
|
1857
2123
|
}, []);
|
|
2124
|
+
const addToolApprovalResponse = React6.useCallback((_toolCallId, _result) => {
|
|
2125
|
+
throw new Error(`addToolApprovalResponse: Tool approval requires server route with toUIMessageStreamResponse. ` + `Use createChatRoute and @ai-sdk/react useChat for tools with requireApproval.`);
|
|
2126
|
+
}, []);
|
|
2127
|
+
const hasApprovalTools = toolsDefs?.some((t) => t.requireApproval) ?? false;
|
|
1858
2128
|
return {
|
|
1859
2129
|
messages,
|
|
1860
2130
|
conversation,
|
|
@@ -1864,7 +2134,8 @@ function useChat(options = {}) {
|
|
|
1864
2134
|
clearConversation,
|
|
1865
2135
|
setConversationId,
|
|
1866
2136
|
regenerate,
|
|
1867
|
-
stop
|
|
2137
|
+
stop,
|
|
2138
|
+
...hasApprovalTools && { addToolApprovalResponse }
|
|
1868
2139
|
};
|
|
1869
2140
|
}
|
|
1870
2141
|
// src/presentation/hooks/useProviders.tsx
|
|
@@ -1907,6 +2178,9 @@ function useProviders() {
|
|
|
1907
2178
|
refresh: loadProviders
|
|
1908
2179
|
};
|
|
1909
2180
|
}
|
|
2181
|
+
|
|
2182
|
+
// src/presentation/hooks/index.ts
|
|
2183
|
+
import { useCompletion } from "@ai-sdk/react";
|
|
1910
2184
|
// src/providers/index.ts
|
|
1911
2185
|
import {
|
|
1912
2186
|
createProvider as createProvider2,
|
|
@@ -2241,6 +2515,7 @@ var ChatErrorEvent = defineEvent({
|
|
|
2241
2515
|
export {
|
|
2242
2516
|
validateProvider,
|
|
2243
2517
|
useProviders,
|
|
2518
|
+
useCompletion,
|
|
2244
2519
|
useChat,
|
|
2245
2520
|
supportsLocalMode,
|
|
2246
2521
|
listOllamaModels,
|