@sandagent/sdk 0.2.3 → 0.2.5
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 +142 -0
- package/dist/react/index.js +14 -2
- package/dist/react/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# @sandagent/sdk
|
|
2
|
+
|
|
3
|
+
SandAgent SDK - AI Provider and React hooks for building AI agents with sandboxed execution.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm install @sandagent/sdk ai
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
For React applications:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install @sandagent/sdk ai react react-dom
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
### Backend
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { createSandAgent, LocalSandbox } from "@sandagent/sdk";
|
|
23
|
+
import { streamText, createUIMessageStream, createUIMessageStreamResponse } from "ai";
|
|
24
|
+
|
|
25
|
+
const sandbox = new LocalSandbox({
|
|
26
|
+
workdir: process.cwd(),
|
|
27
|
+
env: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY },
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const sandagent = createSandAgent({ sandbox });
|
|
31
|
+
|
|
32
|
+
// Use with AI SDK
|
|
33
|
+
const result = streamText({
|
|
34
|
+
model: sandagent("sonnet"),
|
|
35
|
+
messages: [{ role: "user", content: "Hello!" }],
|
|
36
|
+
});
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Frontend
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
import { useSandAgentChat } from "@sandagent/sdk/react";
|
|
43
|
+
|
|
44
|
+
export default function ChatPage() {
|
|
45
|
+
const { messages, sendMessage, isLoading } = useSandAgentChat({
|
|
46
|
+
apiEndpoint: "/api/ai",
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<div>
|
|
51
|
+
{messages.map((msg) => (
|
|
52
|
+
<div key={msg.id}>{/* render message */}</div>
|
|
53
|
+
))}
|
|
54
|
+
<button onClick={() => sendMessage("Hello!")}>Send</button>
|
|
55
|
+
</div>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Environment Variables
|
|
61
|
+
|
|
62
|
+
Set up your API key:
|
|
63
|
+
|
|
64
|
+
**Anthropic API (Recommended):**
|
|
65
|
+
```bash
|
|
66
|
+
ANTHROPIC_API_KEY=sk-ant-xxx
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**AWS Bedrock:**
|
|
70
|
+
```bash
|
|
71
|
+
AWS_BEARER_TOKEN_BEDROCK=xxx
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Features
|
|
75
|
+
|
|
76
|
+
- **AI Provider**: Use SandAgent as an AI SDK provider
|
|
77
|
+
- **React Hooks**: `useSandAgentChat` for chat interfaces
|
|
78
|
+
- **Artifacts**: Automatically extract and display AI-generated files
|
|
79
|
+
- **Custom Sandboxes**: Works with E2B, Sandock, Daytona, and Local sandboxes
|
|
80
|
+
|
|
81
|
+
## API Reference
|
|
82
|
+
|
|
83
|
+
### Backend Exports
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
import {
|
|
87
|
+
createSandAgent, // Create SandAgent provider
|
|
88
|
+
LocalSandbox, // Local sandbox adapter
|
|
89
|
+
SandAgentLanguageModel, // Language model class
|
|
90
|
+
resolveModelId, // Resolve model ID alias
|
|
91
|
+
} from "@sandagent/sdk";
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Frontend Exports
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
import {
|
|
98
|
+
useSandAgentChat, // Main chat hook
|
|
99
|
+
useArtifacts, // Artifacts management hook
|
|
100
|
+
useAskUserQuestion, // User question UI hook
|
|
101
|
+
useWriteTool, // Write tool UI hook
|
|
102
|
+
} from "@sandagent/sdk/react";
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Usage
|
|
106
|
+
|
|
107
|
+
### Artifacts
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
const { artifacts, selectedArtifact, setSelectedArtifact } = useSandAgentChat({
|
|
111
|
+
apiEndpoint: "/api/ai",
|
|
112
|
+
});
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Custom Agent Templates
|
|
116
|
+
|
|
117
|
+
Create a template directory with `CLAUDE.md` and skills:
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
my-agent-template/
|
|
121
|
+
├── CLAUDE.md
|
|
122
|
+
└── .claude/
|
|
123
|
+
└── skills/
|
|
124
|
+
└── my-skill/
|
|
125
|
+
└── SKILL.md
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Pass the template path to `LocalSandbox`:
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
const sandbox = new LocalSandbox({
|
|
132
|
+
workdir: process.cwd(),
|
|
133
|
+
templatesPath: "./my-agent-template",
|
|
134
|
+
env: { ANTHROPIC_API_KEY },
|
|
135
|
+
});
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
All files (including `CLAUDE.md` and `.claude/skills/`) will be automatically copied to the workspace.
|
|
139
|
+
|
|
140
|
+
## License
|
|
141
|
+
|
|
142
|
+
Apache-2.0
|
package/dist/react/index.js
CHANGED
|
@@ -17,6 +17,18 @@ function useSandAgentChat({
|
|
|
17
17
|
useEffect(() => {
|
|
18
18
|
bodyRef.current = body;
|
|
19
19
|
}, [body]);
|
|
20
|
+
const getSessionIdFromMessage = (message) => {
|
|
21
|
+
if (!message?.parts) return void 0;
|
|
22
|
+
for (const part of message.parts) {
|
|
23
|
+
if (part.type === "text") {
|
|
24
|
+
const providerMetadata = part.providerMetadata;
|
|
25
|
+
if (providerMetadata?.sandagent?.sessionId) {
|
|
26
|
+
return providerMetadata.sandagent.sessionId;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return void 0;
|
|
31
|
+
};
|
|
20
32
|
const {
|
|
21
33
|
messages,
|
|
22
34
|
sendMessage: sendMessageInternal,
|
|
@@ -28,9 +40,9 @@ function useSandAgentChat({
|
|
|
28
40
|
api: apiEndpoint,
|
|
29
41
|
body: () => {
|
|
30
42
|
const lastMessage = messagesRef.current[messagesRef.current.length - 1];
|
|
31
|
-
const
|
|
43
|
+
const sessionId = getSessionIdFromMessage(lastMessage);
|
|
32
44
|
return {
|
|
33
|
-
resume:
|
|
45
|
+
resume: sessionId,
|
|
34
46
|
...bodyRef.current
|
|
35
47
|
};
|
|
36
48
|
}
|
package/dist/react/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/react/useSandAgentChat.ts","../../src/react/useAskUserQuestion.ts","../../src/react/useWriteTool.ts","../../src/react/useArtifacts.ts"],"sourcesContent":["\"use client\";\n\nimport { useChat } from \"@ai-sdk/react\";\nimport { DefaultChatTransport, type UIMessage } from \"ai\";\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type {\n ArtifactData,\n UseSandAgentChatOptions,\n UseSandAgentChatReturn,\n} from \"./types\";\n\n/**\n * useSandAgentChat - Core hook for SandAgent chat functionality\n *\n * Provides all the logic needed for a chat interface:\n * - Message management\n * - Artifact extraction\n * - Session management\n *\n * @example\n * ```tsx\n * import { useSandAgentChat } from \"@sandagent/sdk/react\";\n *\n * const {\n * messages,\n * sendMessage,\n * status,\n * artifacts,\n * selectedArtifact,\n * setSelectedArtifact,\n * } = useSandAgentChat({\n * apiEndpoint: \"/api/ai\",\n * body: { template: \"default\" },\n * });\n * ```\n */\nexport function useSandAgentChat({\n apiEndpoint = \"/api/ai\",\n body = {},\n}: UseSandAgentChatOptions = {}): UseSandAgentChatReturn {\n // Artifact selection state\n const [selectedArtifact, setSelectedArtifact] = useState<ArtifactData | null>(\n null,\n );\n\n // Refs for accessing latest values in callbacks\n const bodyRef = useRef(body);\n const messagesRef = useRef<UIMessage[]>([]);\n\n useEffect(() => {\n bodyRef.current = body;\n }, [body]);\n\n // Core chat hook\n const {\n messages,\n sendMessage: sendMessageInternal,\n status,\n error,\n stop,\n } = useChat({\n transport: new DefaultChatTransport({\n api: apiEndpoint,\n body: () => {\n const lastMessage = messagesRef.current[messagesRef.current.length - 1];\n const metadata = lastMessage?.metadata as\n | { sessionId?: string }\n | undefined;\n return {\n resume: metadata?.sessionId,\n ...bodyRef.current,\n };\n },\n }),\n });\n\n // Keep messagesRef in sync\n useEffect(() => {\n messagesRef.current = messages;\n }, [messages]);\n\n // Extract artifacts from messages\n const prevArtifactsRef = useRef<ArtifactData[]>([]);\n const artifacts = useMemo(() => {\n const results: ArtifactData[] = [];\n for (const message of messages) {\n for (const part of message.parts) {\n if (part.type === \"data-artifact\") {\n const data = part.data as ArtifactData;\n if (!results.some((a) => a.artifactId === data.artifactId)) {\n results.push(data);\n }\n }\n }\n }\n\n // Memoization optimization\n const prev = prevArtifactsRef.current;\n if (\n prev.length === results.length &&\n prev.every((prevArt, idx) => {\n const currArt = results[idx];\n return (\n prevArt.artifactId === currArt.artifactId &&\n prevArt.content === currArt.content &&\n prevArt.mimeType === currArt.mimeType\n );\n })\n ) {\n return prev;\n }\n\n prevArtifactsRef.current = results;\n return results;\n }, [messages]);\n\n // Sync selectedArtifact when artifacts change\n useEffect(() => {\n if (artifacts.length > 0) {\n setSelectedArtifact((prev) => {\n if (!prev) return artifacts[0];\n\n const currentMatch = artifacts.find(\n (a) => a.artifactId === prev.artifactId,\n );\n\n if (!currentMatch) {\n return artifacts[0];\n }\n\n if (\n currentMatch.content === prev.content &&\n currentMatch.mimeType === prev.mimeType\n ) {\n return prev;\n }\n\n return currentMatch;\n });\n } else {\n setSelectedArtifact((prev) => (prev === null ? prev : null));\n }\n }, [artifacts]);\n\n const isLoading = status === \"streaming\" || status === \"submitted\";\n const hasError = status === \"error\" && !!error;\n\n // Send message helper\n const sendMessage = useCallback(\n (text: string) => {\n if (!isLoading && text.trim()) {\n sendMessageInternal({\n role: \"user\",\n parts: [{ type: \"text\", text: text.trim() }],\n });\n }\n },\n [isLoading, sendMessageInternal],\n );\n\n // Handle submit for PromptInput compatibility\n const handleSubmit = useCallback(\n (message: { text: string }) => {\n if (!isLoading) {\n if (message.text) {\n sendMessageInternal({\n role: \"user\",\n parts: [{ type: \"text\", text: message.text.trim() }],\n });\n } else {\n sendMessageInternal();\n }\n }\n },\n [isLoading, sendMessageInternal],\n );\n\n return {\n messages,\n status,\n error,\n isLoading,\n hasError,\n artifacts,\n selectedArtifact,\n setSelectedArtifact,\n sendMessage,\n stop,\n handleSubmit,\n };\n}\n","\"use client\";\n\nimport { useCallback, useMemo, useState } from \"react\";\nimport type {\n AskUserQuestionInput,\n AskUserQuestionOutput,\n Question,\n UseAskUserQuestionOptions,\n UseAskUserQuestionReturn,\n} from \"./types\";\n\n/**\n * useAskUserQuestion - Hook for handling AskUserQuestion tool interactions\n *\n * Manages the state for user question/answer interactions in chat.\n * Provides answer selection, completion detection, and formatted output.\n *\n * @example\n * ```tsx\n * import { useAskUserQuestion } from \"@sandagent/sdk/react\";\n *\n * function QuestionUI({ part, sessionId }) {\n * const {\n * questions,\n * answers,\n * isCompleted,\n * selectAnswer,\n * isSelected,\n * } = useAskUserQuestion({\n * part,\n * onAnswer: (data) => {\n * fetch(\"/api/approval/submit\", {\n * method: \"POST\",\n * body: JSON.stringify({ sessionId, ...data }),\n * });\n * },\n * });\n *\n * if (isCompleted) {\n * return <div>Completed</div>;\n * }\n *\n * return (\n * <div>\n * {questions.map((q) => (\n * <div key={q.question}>\n * <p>{q.question}</p>\n * {q.options?.map((opt) => (\n * <button\n * key={opt.label}\n * onClick={() => selectAnswer(q.question, opt.label, q.multiSelect)}\n * style={{ fontWeight: isSelected(q.question, opt.label, q.multiSelect) ? 'bold' : 'normal' }}\n * >\n * {opt.label}\n * </button>\n * ))}\n * </div>\n * ))}\n * </div>\n * );\n * }\n * ```\n */\nexport function useAskUserQuestion({\n part,\n onAnswer,\n}: UseAskUserQuestionOptions): UseAskUserQuestionReturn {\n const [answers, setAnswers] = useState<Record<string, string | string[]>>({});\n\n // Parse questions from part.input\n const questions = useMemo((): Question[] => {\n if (!part.input || typeof part.input !== \"object\") return [];\n const input = part.input as AskUserQuestionInput;\n if (!input.questions || !Array.isArray(input.questions)) return [];\n return input.questions;\n }, [part.input]);\n\n // Parse answers from part.output (for restored sessions)\n const outputAnswers = useMemo((): Record<\n string,\n string | string[]\n > | null => {\n if (part.output && typeof part.output === \"object\") {\n const output = part.output as AskUserQuestionOutput;\n if (output.answers) {\n const hasRealAnswers = Object.values(output.answers).some(\n (v) => v && v.trim() !== \"\",\n );\n if (hasRealAnswers) {\n const parsed: Record<string, string | string[]> = {};\n for (const q of questions) {\n const val = output.answers[q.question];\n if (q.multiSelect && val) {\n parsed[q.question] = val.split(\", \").filter(Boolean);\n } else {\n parsed[q.question] = val || \"\";\n }\n }\n return parsed;\n }\n }\n }\n return null;\n }, [part.output, questions]);\n\n // Determine display answers (user input or restored from output)\n const displayAnswers = useMemo(() => {\n return Object.keys(answers).length > 0 ? answers : outputAnswers || {};\n }, [answers, outputAnswers]);\n\n // Check if completed\n const isCompleted = useMemo(() => {\n return (\n part.state === \"output-available\" ||\n (Object.keys(answers).length === 0 && outputAnswers !== null)\n );\n }, [part.state, answers, outputAnswers]);\n\n // Check if waiting for input (for animation)\n const isWaitingForInput = part.state === \"input-available\";\n\n // Get formatted answers map\n const getAnswersMap = useCallback((): Record<string, string> => {\n const answersMap: Record<string, string> = {};\n for (const q of questions) {\n const answer = displayAnswers[q.question];\n if (q.multiSelect) {\n answersMap[q.question] = Array.isArray(answer) ? answer.join(\", \") : \"\";\n } else {\n answersMap[q.question] = (answer as string) || \"\";\n }\n }\n return answersMap;\n }, [questions, displayAnswers]);\n\n // Select answer handler\n const selectAnswer = useCallback(\n (question: string, value: string, multiSelect = false) => {\n const newAnswers = { ...answers };\n\n if (multiSelect) {\n const current = (newAnswers[question] as string[]) || [];\n newAnswers[question] = current.includes(value)\n ? current.filter((v) => v !== value)\n : [...current, value];\n } else {\n newAnswers[question] = value;\n }\n\n setAnswers(newAnswers);\n\n // Prepare answers map for callback\n const answersMap: Record<string, string> = {};\n for (const q of questions) {\n const answer = newAnswers[q.question];\n if (q.multiSelect) {\n answersMap[q.question] = Array.isArray(answer)\n ? answer.join(\", \")\n : \"\";\n } else {\n answersMap[q.question] = (answer as string) || \"\";\n }\n }\n\n // Trigger callback\n onAnswer?.({\n toolCallId: part.toolCallId,\n questions,\n answers: answersMap,\n });\n },\n [answers, questions, part.toolCallId, onAnswer],\n );\n\n // Check if option is selected\n const isSelected = useCallback(\n (question: string, optionLabel: string, multiSelect = false): boolean => {\n const selectedValue = displayAnswers[question];\n if (multiSelect) {\n return (\n (selectedValue as string[] | undefined)?.includes(optionLabel) ??\n false\n );\n }\n return selectedValue === optionLabel;\n },\n [displayAnswers],\n );\n\n return {\n questions,\n answers: displayAnswers,\n isCompleted,\n isWaitingForInput,\n selectAnswer,\n getAnswersMap,\n isSelected,\n };\n}\n","\"use client\";\n\nimport { useMemo } from \"react\";\nimport type { DynamicToolUIPart } from \"./types\";\n\n/**\n * Write tool input structure\n */\nexport interface WriteToolInput {\n file_path: string;\n content: string;\n}\n\n/**\n * Write tool output structure\n */\nexport interface WriteToolOutput {\n type: \"create\" | \"edit\";\n filePath: string;\n content: string;\n structuredPatch?: unknown[];\n originalFile?: string | null;\n}\n\n/**\n * Options for useWriteTool hook\n */\nexport interface UseWriteToolOptions {\n /** The dynamic tool UI part from the message */\n part: DynamicToolUIPart;\n}\n\n/**\n * Return type for useWriteTool hook\n */\nexport interface UseWriteToolReturn {\n /** File path being written to */\n filePath: string | null;\n /** File name (extracted from path) */\n fileName: string | null;\n /** File content */\n content: string | null;\n /** Operation type: 'create' or 'edit' */\n operationType: \"create\" | \"edit\" | null;\n /** Original file content (for edit operations) */\n originalContent: string | null;\n /** Structured patch data (for edit operations) */\n structuredPatch: unknown[] | null;\n /** Tool state */\n state: \"streaming\" | \"input-available\" | \"output-available\" | \"error\";\n /** Whether the tool is currently streaming input */\n isStreaming: boolean;\n /** Whether the write operation completed successfully */\n isCompleted: boolean;\n /** Whether there was an error */\n hasError: boolean;\n /** Error message if any */\n errorText: string | null;\n /** Whether this is a markdown file */\n isMarkdown: boolean;\n /** File extension */\n fileExtension: string | null;\n}\n\n/**\n * useWriteTool - Hook for handling Write tool interactions\n *\n * Parses the Write tool's input and output data, providing\n * easy access to file information and operation state.\n *\n * @example\n * ```tsx\n * import { useWriteTool } from \"@sandagent/sdk/react\";\n *\n * function WriteToolUI({ part }) {\n * const {\n * filePath,\n * fileName,\n * content,\n * operationType,\n * isStreaming,\n * isCompleted,\n * isMarkdown,\n * } = useWriteTool({ part });\n *\n * if (isStreaming) {\n * return <div>Writing {fileName}...</div>;\n * }\n *\n * return (\n * <div>\n * <h3>{fileName} ({operationType})</h3>\n * {isMarkdown ? (\n * <MarkdownRenderer content={content} />\n * ) : (\n * <pre>{content}</pre>\n * )}\n * </div>\n * );\n * }\n * ```\n */\nexport function useWriteTool({\n part,\n}: UseWriteToolOptions): UseWriteToolReturn {\n // Parse input data\n const inputData = useMemo((): WriteToolInput | null => {\n if (!part.input || typeof part.input !== \"object\") return null;\n const input = part.input as WriteToolInput;\n if (!input.file_path) return null;\n return input;\n }, [part.input]);\n\n // Parse output data\n const outputData = useMemo((): WriteToolOutput | null => {\n if (!part.output || typeof part.output !== \"object\") return null;\n const output = part.output as WriteToolOutput;\n if (!output.filePath) return null;\n return output;\n }, [part.output]);\n\n // Determine file path (prefer output, fallback to input)\n const filePath = outputData?.filePath ?? inputData?.file_path ?? null;\n\n // Extract file name\n const fileName = useMemo(() => {\n if (!filePath) return null;\n return filePath.split(\"/\").pop() || filePath;\n }, [filePath]);\n\n // Extract file extension\n const fileExtension = useMemo(() => {\n if (!fileName) return null;\n const parts = fileName.split(\".\");\n return parts.length > 1 ? parts.pop()?.toLowerCase() || null : null;\n }, [fileName]);\n\n // Determine content (prefer output, fallback to input)\n const content = outputData?.content ?? inputData?.content ?? null;\n\n // Operation type\n const operationType = outputData?.type ?? null;\n\n // Original content (for edits)\n const originalContent = outputData?.originalFile ?? null;\n\n // Structured patch\n const structuredPatch = outputData?.structuredPatch ?? null;\n\n // State parsing\n const state = useMemo((): UseWriteToolReturn[\"state\"] => {\n if (part.state === \"input-streaming\") return \"streaming\";\n if (part.state === \"output-error\") return \"error\";\n if (part.state === \"output-available\") return \"output-available\";\n return \"input-available\";\n }, [part.state]);\n\n const isStreaming = state === \"streaming\";\n const isCompleted = state === \"output-available\";\n const hasError = state === \"error\";\n const errorText = part.errorText ?? null;\n\n // Check if markdown\n const isMarkdown = useMemo(() => {\n if (!fileExtension) return false;\n return [\"md\", \"markdown\", \"mdx\"].includes(fileExtension);\n }, [fileExtension]);\n\n return {\n filePath,\n fileName,\n content,\n operationType,\n originalContent,\n structuredPatch,\n state,\n isStreaming,\n isCompleted,\n hasError,\n errorText,\n isMarkdown,\n fileExtension,\n };\n}\n","\"use client\";\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type { ArtifactData, UIMessage } from \"./types\";\n\n/**\n * Options for useArtifacts hook\n */\nexport interface UseArtifactsOptions {\n /** Messages to extract artifacts from */\n messages: UIMessage[];\n}\n\n/**\n * Return type for useArtifacts hook\n */\nexport interface UseArtifactsReturn {\n /** All extracted artifacts */\n artifacts: ArtifactData[];\n /** Currently selected artifact */\n selectedArtifact: ArtifactData | null;\n /** Set the selected artifact */\n setSelectedArtifact: (artifact: ArtifactData | null) => void;\n /** Select artifact by ID */\n selectArtifactById: (artifactId: string) => void;\n /** Whether there are any artifacts */\n hasArtifacts: boolean;\n /** Number of artifacts */\n count: number;\n /** Copy artifact content to clipboard */\n copyContent: (artifact: ArtifactData) => Promise<void>;\n /** Download artifact as file */\n downloadArtifact: (artifact: ArtifactData) => void;\n /** Get file extension from mime type */\n getFileExtension: (mimeType: string) => string;\n}\n\n/**\n * Get file extension from MIME type\n */\nfunction getFileExtensionFromMimeType(mimeType: string): string {\n const mimeToExtension: Record<string, string> = {\n \"text/plain\": \"txt\",\n \"text/html\": \"html\",\n \"text/css\": \"css\",\n \"text/javascript\": \"js\",\n \"text/markdown\": \"md\",\n \"application/json\": \"json\",\n \"application/xml\": \"xml\",\n \"application/pdf\": \"pdf\",\n \"image/png\": \"png\",\n \"image/jpeg\": \"jpg\",\n \"image/gif\": \"gif\",\n \"image/svg+xml\": \"svg\",\n };\n\n // Try exact match\n if (mimeToExtension[mimeType]) {\n return mimeToExtension[mimeType];\n }\n\n // Try partial match (e.g., \"text/x-markdown\" -> \"md\")\n if (mimeType.includes(\"markdown\")) return \"md\";\n if (mimeType.includes(\"json\")) return \"json\";\n if (mimeType.includes(\"javascript\")) return \"js\";\n if (mimeType.includes(\"typescript\")) return \"ts\";\n if (mimeType.includes(\"html\")) return \"html\";\n if (mimeType.includes(\"css\")) return \"css\";\n if (mimeType.includes(\"xml\")) return \"xml\";\n if (mimeType.includes(\"python\")) return \"py\";\n\n // Default\n return \"txt\";\n}\n\n/**\n * useArtifacts - Hook for managing artifacts from chat messages\n *\n * Extracts artifacts from messages and provides selection,\n * copy, and download functionality.\n *\n * @example\n * ```tsx\n * import { useArtifacts } from \"@sandagent/sdk/react\";\n *\n * function ArtifactPanel({ messages }) {\n * const {\n * artifacts,\n * selectedArtifact,\n * setSelectedArtifact,\n * hasArtifacts,\n * copyContent,\n * downloadArtifact,\n * } = useArtifacts({ messages });\n *\n * if (!hasArtifacts) return null;\n *\n * return (\n * <div>\n * {artifacts.map((artifact) => (\n * <button\n * key={artifact.artifactId}\n * onClick={() => setSelectedArtifact(artifact)}\n * >\n * {artifact.artifactId}\n * </button>\n * ))}\n *\n * {selectedArtifact && (\n * <div>\n * <pre>{selectedArtifact.content}</pre>\n * <button onClick={() => copyContent(selectedArtifact)}>Copy</button>\n * <button onClick={() => downloadArtifact(selectedArtifact)}>Download</button>\n * </div>\n * )}\n * </div>\n * );\n * }\n * ```\n */\nexport function useArtifacts({\n messages,\n}: UseArtifactsOptions): UseArtifactsReturn {\n const [selectedArtifact, setSelectedArtifact] = useState<ArtifactData | null>(\n null,\n );\n\n // Extract artifacts from messages with memoization optimization\n const prevArtifactsRef = useRef<ArtifactData[]>([]);\n const artifacts = useMemo(() => {\n const results: ArtifactData[] = [];\n for (const message of messages) {\n for (const part of message.parts) {\n if (part.type === \"data-artifact\") {\n const data = part.data as ArtifactData;\n if (!results.some((a) => a.artifactId === data.artifactId)) {\n results.push(data);\n }\n }\n }\n }\n\n // Memoization optimization - return previous reference if content unchanged\n const prev = prevArtifactsRef.current;\n if (\n prev.length === results.length &&\n prev.every((prevArt, idx) => {\n const currArt = results[idx];\n return (\n prevArt.artifactId === currArt.artifactId &&\n prevArt.content === currArt.content &&\n prevArt.mimeType === currArt.mimeType\n );\n })\n ) {\n return prev;\n }\n\n prevArtifactsRef.current = results;\n return results;\n }, [messages]);\n\n // Sync selectedArtifact when artifacts change\n useEffect(() => {\n if (artifacts.length > 0) {\n setSelectedArtifact((prev) => {\n if (!prev) return artifacts[0];\n\n const currentMatch = artifacts.find(\n (a) => a.artifactId === prev.artifactId,\n );\n\n if (!currentMatch) {\n return artifacts[0];\n }\n\n if (\n currentMatch.content === prev.content &&\n currentMatch.mimeType === prev.mimeType\n ) {\n return prev;\n }\n\n return currentMatch;\n });\n } else {\n setSelectedArtifact((prev) => (prev === null ? prev : null));\n }\n }, [artifacts]);\n\n // Select artifact by ID\n const selectArtifactById = useCallback(\n (artifactId: string) => {\n const artifact = artifacts.find((a) => a.artifactId === artifactId);\n if (artifact) {\n setSelectedArtifact(artifact);\n }\n },\n [artifacts],\n );\n\n // Copy content to clipboard\n const copyContent = useCallback(async (artifact: ArtifactData) => {\n await navigator.clipboard.writeText(artifact.content);\n }, []);\n\n // Download artifact as file\n const downloadArtifact = useCallback((artifact: ArtifactData) => {\n const blob = new Blob([artifact.content], { type: artifact.mimeType });\n const url = URL.createObjectURL(blob);\n const a = document.createElement(\"a\");\n a.href = url;\n const fileName =\n artifact.artifactId.split(\"/\").pop() || artifact.artifactId;\n const extension = getFileExtensionFromMimeType(artifact.mimeType);\n a.download = fileName.includes(\".\") ? fileName : `${fileName}.${extension}`;\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n URL.revokeObjectURL(url);\n }, []);\n\n const hasArtifacts = artifacts.length > 0;\n const count = artifacts.length;\n\n return {\n artifacts,\n selectedArtifact,\n setSelectedArtifact,\n selectArtifactById,\n hasArtifacts,\n count,\n copyContent,\n downloadArtifact,\n getFileExtension: getFileExtensionFromMimeType,\n };\n}\n"],"mappings":"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","names":["useCallback","useMemo","useState","useMemo","useCallback","useEffect","useMemo","useRef","useState"]}
|
|
1
|
+
{"version":3,"sources":["../../src/react/useSandAgentChat.ts","../../src/react/useAskUserQuestion.ts","../../src/react/useWriteTool.ts","../../src/react/useArtifacts.ts"],"sourcesContent":["\"use client\";\n\nimport { useChat } from \"@ai-sdk/react\";\nimport { DefaultChatTransport, type UIMessage } from \"ai\";\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type {\n ArtifactData,\n UseSandAgentChatOptions,\n UseSandAgentChatReturn,\n} from \"./types\";\n\n/**\n * useSandAgentChat - Core hook for SandAgent chat functionality\n *\n * Provides all the logic needed for a chat interface:\n * - Message management\n * - Artifact extraction\n * - Session management\n *\n * @example\n * ```tsx\n * import { useSandAgentChat } from \"@sandagent/sdk/react\";\n *\n * const {\n * messages,\n * sendMessage,\n * status,\n * artifacts,\n * selectedArtifact,\n * setSelectedArtifact,\n * } = useSandAgentChat({\n * apiEndpoint: \"/api/ai\",\n * body: { template: \"default\" },\n * });\n * ```\n */\nexport function useSandAgentChat({\n apiEndpoint = \"/api/ai\",\n body = {},\n}: UseSandAgentChatOptions = {}): UseSandAgentChatReturn {\n // Artifact selection state\n const [selectedArtifact, setSelectedArtifact] = useState<ArtifactData | null>(\n null,\n );\n\n // Refs for accessing latest values in callbacks\n const bodyRef = useRef(body);\n const messagesRef = useRef<UIMessage[]>([]);\n\n useEffect(() => {\n bodyRef.current = body;\n }, [body]);\n\n // Helper to extract sessionId from message parts' providerMetadata\n const getSessionIdFromMessage = (\n message: UIMessage | undefined,\n ): string | undefined => {\n if (!message?.parts) return undefined;\n // Find the first text part with providerMetadata.sandagent.sessionId\n for (const part of message.parts) {\n if (part.type === \"text\") {\n const providerMetadata = (\n part as { providerMetadata?: { sandagent?: { sessionId?: string } } }\n ).providerMetadata;\n if (providerMetadata?.sandagent?.sessionId) {\n return providerMetadata.sandagent.sessionId;\n }\n }\n }\n return undefined;\n };\n\n // Core chat hook\n const {\n messages,\n sendMessage: sendMessageInternal,\n status,\n error,\n stop,\n } = useChat({\n transport: new DefaultChatTransport({\n api: apiEndpoint,\n body: () => {\n const lastMessage = messagesRef.current[messagesRef.current.length - 1];\n const sessionId = getSessionIdFromMessage(lastMessage);\n return {\n resume: sessionId,\n ...bodyRef.current,\n };\n },\n }),\n });\n\n // Keep messagesRef in sync\n useEffect(() => {\n messagesRef.current = messages;\n }, [messages]);\n\n // Extract artifacts from messages\n const prevArtifactsRef = useRef<ArtifactData[]>([]);\n const artifacts = useMemo(() => {\n const results: ArtifactData[] = [];\n for (const message of messages) {\n for (const part of message.parts) {\n if (part.type === \"data-artifact\") {\n const data = part.data as ArtifactData;\n if (!results.some((a) => a.artifactId === data.artifactId)) {\n results.push(data);\n }\n }\n }\n }\n\n // Memoization optimization\n const prev = prevArtifactsRef.current;\n if (\n prev.length === results.length &&\n prev.every((prevArt, idx) => {\n const currArt = results[idx];\n return (\n prevArt.artifactId === currArt.artifactId &&\n prevArt.content === currArt.content &&\n prevArt.mimeType === currArt.mimeType\n );\n })\n ) {\n return prev;\n }\n\n prevArtifactsRef.current = results;\n return results;\n }, [messages]);\n\n // Sync selectedArtifact when artifacts change\n useEffect(() => {\n if (artifacts.length > 0) {\n setSelectedArtifact((prev) => {\n if (!prev) return artifacts[0];\n\n const currentMatch = artifacts.find(\n (a) => a.artifactId === prev.artifactId,\n );\n\n if (!currentMatch) {\n return artifacts[0];\n }\n\n if (\n currentMatch.content === prev.content &&\n currentMatch.mimeType === prev.mimeType\n ) {\n return prev;\n }\n\n return currentMatch;\n });\n } else {\n setSelectedArtifact((prev) => (prev === null ? prev : null));\n }\n }, [artifacts]);\n\n const isLoading = status === \"streaming\" || status === \"submitted\";\n const hasError = status === \"error\" && !!error;\n\n // Send message helper\n const sendMessage = useCallback(\n (text: string) => {\n if (!isLoading && text.trim()) {\n sendMessageInternal({\n role: \"user\",\n parts: [{ type: \"text\", text: text.trim() }],\n });\n }\n },\n [isLoading, sendMessageInternal],\n );\n\n // Handle submit for PromptInput compatibility\n const handleSubmit = useCallback(\n (message: { text: string }) => {\n if (!isLoading) {\n if (message.text) {\n sendMessageInternal({\n role: \"user\",\n parts: [{ type: \"text\", text: message.text.trim() }],\n });\n } else {\n sendMessageInternal();\n }\n }\n },\n [isLoading, sendMessageInternal],\n );\n\n return {\n messages,\n status,\n error,\n isLoading,\n hasError,\n artifacts,\n selectedArtifact,\n setSelectedArtifact,\n sendMessage,\n stop,\n handleSubmit,\n };\n}\n","\"use client\";\n\nimport { useCallback, useMemo, useState } from \"react\";\nimport type {\n AskUserQuestionInput,\n AskUserQuestionOutput,\n Question,\n UseAskUserQuestionOptions,\n UseAskUserQuestionReturn,\n} from \"./types\";\n\n/**\n * useAskUserQuestion - Hook for handling AskUserQuestion tool interactions\n *\n * Manages the state for user question/answer interactions in chat.\n * Provides answer selection, completion detection, and formatted output.\n *\n * @example\n * ```tsx\n * import { useAskUserQuestion } from \"@sandagent/sdk/react\";\n *\n * function QuestionUI({ part, sessionId }) {\n * const {\n * questions,\n * answers,\n * isCompleted,\n * selectAnswer,\n * isSelected,\n * } = useAskUserQuestion({\n * part,\n * onAnswer: (data) => {\n * fetch(\"/api/approval/submit\", {\n * method: \"POST\",\n * body: JSON.stringify({ sessionId, ...data }),\n * });\n * },\n * });\n *\n * if (isCompleted) {\n * return <div>Completed</div>;\n * }\n *\n * return (\n * <div>\n * {questions.map((q) => (\n * <div key={q.question}>\n * <p>{q.question}</p>\n * {q.options?.map((opt) => (\n * <button\n * key={opt.label}\n * onClick={() => selectAnswer(q.question, opt.label, q.multiSelect)}\n * style={{ fontWeight: isSelected(q.question, opt.label, q.multiSelect) ? 'bold' : 'normal' }}\n * >\n * {opt.label}\n * </button>\n * ))}\n * </div>\n * ))}\n * </div>\n * );\n * }\n * ```\n */\nexport function useAskUserQuestion({\n part,\n onAnswer,\n}: UseAskUserQuestionOptions): UseAskUserQuestionReturn {\n const [answers, setAnswers] = useState<Record<string, string | string[]>>({});\n\n // Parse questions from part.input\n const questions = useMemo((): Question[] => {\n if (!part.input || typeof part.input !== \"object\") return [];\n const input = part.input as AskUserQuestionInput;\n if (!input.questions || !Array.isArray(input.questions)) return [];\n return input.questions;\n }, [part.input]);\n\n // Parse answers from part.output (for restored sessions)\n const outputAnswers = useMemo((): Record<\n string,\n string | string[]\n > | null => {\n if (part.output && typeof part.output === \"object\") {\n const output = part.output as AskUserQuestionOutput;\n if (output.answers) {\n const hasRealAnswers = Object.values(output.answers).some(\n (v) => v && v.trim() !== \"\",\n );\n if (hasRealAnswers) {\n const parsed: Record<string, string | string[]> = {};\n for (const q of questions) {\n const val = output.answers[q.question];\n if (q.multiSelect && val) {\n parsed[q.question] = val.split(\", \").filter(Boolean);\n } else {\n parsed[q.question] = val || \"\";\n }\n }\n return parsed;\n }\n }\n }\n return null;\n }, [part.output, questions]);\n\n // Determine display answers (user input or restored from output)\n const displayAnswers = useMemo(() => {\n return Object.keys(answers).length > 0 ? answers : outputAnswers || {};\n }, [answers, outputAnswers]);\n\n // Check if completed\n const isCompleted = useMemo(() => {\n return (\n part.state === \"output-available\" ||\n (Object.keys(answers).length === 0 && outputAnswers !== null)\n );\n }, [part.state, answers, outputAnswers]);\n\n // Check if waiting for input (for animation)\n const isWaitingForInput = part.state === \"input-available\";\n\n // Get formatted answers map\n const getAnswersMap = useCallback((): Record<string, string> => {\n const answersMap: Record<string, string> = {};\n for (const q of questions) {\n const answer = displayAnswers[q.question];\n if (q.multiSelect) {\n answersMap[q.question] = Array.isArray(answer) ? answer.join(\", \") : \"\";\n } else {\n answersMap[q.question] = (answer as string) || \"\";\n }\n }\n return answersMap;\n }, [questions, displayAnswers]);\n\n // Select answer handler\n const selectAnswer = useCallback(\n (question: string, value: string, multiSelect = false) => {\n const newAnswers = { ...answers };\n\n if (multiSelect) {\n const current = (newAnswers[question] as string[]) || [];\n newAnswers[question] = current.includes(value)\n ? current.filter((v) => v !== value)\n : [...current, value];\n } else {\n newAnswers[question] = value;\n }\n\n setAnswers(newAnswers);\n\n // Prepare answers map for callback\n const answersMap: Record<string, string> = {};\n for (const q of questions) {\n const answer = newAnswers[q.question];\n if (q.multiSelect) {\n answersMap[q.question] = Array.isArray(answer)\n ? answer.join(\", \")\n : \"\";\n } else {\n answersMap[q.question] = (answer as string) || \"\";\n }\n }\n\n // Trigger callback\n onAnswer?.({\n toolCallId: part.toolCallId,\n questions,\n answers: answersMap,\n });\n },\n [answers, questions, part.toolCallId, onAnswer],\n );\n\n // Check if option is selected\n const isSelected = useCallback(\n (question: string, optionLabel: string, multiSelect = false): boolean => {\n const selectedValue = displayAnswers[question];\n if (multiSelect) {\n return (\n (selectedValue as string[] | undefined)?.includes(optionLabel) ??\n false\n );\n }\n return selectedValue === optionLabel;\n },\n [displayAnswers],\n );\n\n return {\n questions,\n answers: displayAnswers,\n isCompleted,\n isWaitingForInput,\n selectAnswer,\n getAnswersMap,\n isSelected,\n };\n}\n","\"use client\";\n\nimport { useMemo } from \"react\";\nimport type { DynamicToolUIPart } from \"./types\";\n\n/**\n * Write tool input structure\n */\nexport interface WriteToolInput {\n file_path: string;\n content: string;\n}\n\n/**\n * Write tool output structure\n */\nexport interface WriteToolOutput {\n type: \"create\" | \"edit\";\n filePath: string;\n content: string;\n structuredPatch?: unknown[];\n originalFile?: string | null;\n}\n\n/**\n * Options for useWriteTool hook\n */\nexport interface UseWriteToolOptions {\n /** The dynamic tool UI part from the message */\n part: DynamicToolUIPart;\n}\n\n/**\n * Return type for useWriteTool hook\n */\nexport interface UseWriteToolReturn {\n /** File path being written to */\n filePath: string | null;\n /** File name (extracted from path) */\n fileName: string | null;\n /** File content */\n content: string | null;\n /** Operation type: 'create' or 'edit' */\n operationType: \"create\" | \"edit\" | null;\n /** Original file content (for edit operations) */\n originalContent: string | null;\n /** Structured patch data (for edit operations) */\n structuredPatch: unknown[] | null;\n /** Tool state */\n state: \"streaming\" | \"input-available\" | \"output-available\" | \"error\";\n /** Whether the tool is currently streaming input */\n isStreaming: boolean;\n /** Whether the write operation completed successfully */\n isCompleted: boolean;\n /** Whether there was an error */\n hasError: boolean;\n /** Error message if any */\n errorText: string | null;\n /** Whether this is a markdown file */\n isMarkdown: boolean;\n /** File extension */\n fileExtension: string | null;\n}\n\n/**\n * useWriteTool - Hook for handling Write tool interactions\n *\n * Parses the Write tool's input and output data, providing\n * easy access to file information and operation state.\n *\n * @example\n * ```tsx\n * import { useWriteTool } from \"@sandagent/sdk/react\";\n *\n * function WriteToolUI({ part }) {\n * const {\n * filePath,\n * fileName,\n * content,\n * operationType,\n * isStreaming,\n * isCompleted,\n * isMarkdown,\n * } = useWriteTool({ part });\n *\n * if (isStreaming) {\n * return <div>Writing {fileName}...</div>;\n * }\n *\n * return (\n * <div>\n * <h3>{fileName} ({operationType})</h3>\n * {isMarkdown ? (\n * <MarkdownRenderer content={content} />\n * ) : (\n * <pre>{content}</pre>\n * )}\n * </div>\n * );\n * }\n * ```\n */\nexport function useWriteTool({\n part,\n}: UseWriteToolOptions): UseWriteToolReturn {\n // Parse input data\n const inputData = useMemo((): WriteToolInput | null => {\n if (!part.input || typeof part.input !== \"object\") return null;\n const input = part.input as WriteToolInput;\n if (!input.file_path) return null;\n return input;\n }, [part.input]);\n\n // Parse output data\n const outputData = useMemo((): WriteToolOutput | null => {\n if (!part.output || typeof part.output !== \"object\") return null;\n const output = part.output as WriteToolOutput;\n if (!output.filePath) return null;\n return output;\n }, [part.output]);\n\n // Determine file path (prefer output, fallback to input)\n const filePath = outputData?.filePath ?? inputData?.file_path ?? null;\n\n // Extract file name\n const fileName = useMemo(() => {\n if (!filePath) return null;\n return filePath.split(\"/\").pop() || filePath;\n }, [filePath]);\n\n // Extract file extension\n const fileExtension = useMemo(() => {\n if (!fileName) return null;\n const parts = fileName.split(\".\");\n return parts.length > 1 ? parts.pop()?.toLowerCase() || null : null;\n }, [fileName]);\n\n // Determine content (prefer output, fallback to input)\n const content = outputData?.content ?? inputData?.content ?? null;\n\n // Operation type\n const operationType = outputData?.type ?? null;\n\n // Original content (for edits)\n const originalContent = outputData?.originalFile ?? null;\n\n // Structured patch\n const structuredPatch = outputData?.structuredPatch ?? null;\n\n // State parsing\n const state = useMemo((): UseWriteToolReturn[\"state\"] => {\n if (part.state === \"input-streaming\") return \"streaming\";\n if (part.state === \"output-error\") return \"error\";\n if (part.state === \"output-available\") return \"output-available\";\n return \"input-available\";\n }, [part.state]);\n\n const isStreaming = state === \"streaming\";\n const isCompleted = state === \"output-available\";\n const hasError = state === \"error\";\n const errorText = part.errorText ?? null;\n\n // Check if markdown\n const isMarkdown = useMemo(() => {\n if (!fileExtension) return false;\n return [\"md\", \"markdown\", \"mdx\"].includes(fileExtension);\n }, [fileExtension]);\n\n return {\n filePath,\n fileName,\n content,\n operationType,\n originalContent,\n structuredPatch,\n state,\n isStreaming,\n isCompleted,\n hasError,\n errorText,\n isMarkdown,\n fileExtension,\n };\n}\n","\"use client\";\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type { ArtifactData, UIMessage } from \"./types\";\n\n/**\n * Options for useArtifacts hook\n */\nexport interface UseArtifactsOptions {\n /** Messages to extract artifacts from */\n messages: UIMessage[];\n}\n\n/**\n * Return type for useArtifacts hook\n */\nexport interface UseArtifactsReturn {\n /** All extracted artifacts */\n artifacts: ArtifactData[];\n /** Currently selected artifact */\n selectedArtifact: ArtifactData | null;\n /** Set the selected artifact */\n setSelectedArtifact: (artifact: ArtifactData | null) => void;\n /** Select artifact by ID */\n selectArtifactById: (artifactId: string) => void;\n /** Whether there are any artifacts */\n hasArtifacts: boolean;\n /** Number of artifacts */\n count: number;\n /** Copy artifact content to clipboard */\n copyContent: (artifact: ArtifactData) => Promise<void>;\n /** Download artifact as file */\n downloadArtifact: (artifact: ArtifactData) => void;\n /** Get file extension from mime type */\n getFileExtension: (mimeType: string) => string;\n}\n\n/**\n * Get file extension from MIME type\n */\nfunction getFileExtensionFromMimeType(mimeType: string): string {\n const mimeToExtension: Record<string, string> = {\n \"text/plain\": \"txt\",\n \"text/html\": \"html\",\n \"text/css\": \"css\",\n \"text/javascript\": \"js\",\n \"text/markdown\": \"md\",\n \"application/json\": \"json\",\n \"application/xml\": \"xml\",\n \"application/pdf\": \"pdf\",\n \"image/png\": \"png\",\n \"image/jpeg\": \"jpg\",\n \"image/gif\": \"gif\",\n \"image/svg+xml\": \"svg\",\n };\n\n // Try exact match\n if (mimeToExtension[mimeType]) {\n return mimeToExtension[mimeType];\n }\n\n // Try partial match (e.g., \"text/x-markdown\" -> \"md\")\n if (mimeType.includes(\"markdown\")) return \"md\";\n if (mimeType.includes(\"json\")) return \"json\";\n if (mimeType.includes(\"javascript\")) return \"js\";\n if (mimeType.includes(\"typescript\")) return \"ts\";\n if (mimeType.includes(\"html\")) return \"html\";\n if (mimeType.includes(\"css\")) return \"css\";\n if (mimeType.includes(\"xml\")) return \"xml\";\n if (mimeType.includes(\"python\")) return \"py\";\n\n // Default\n return \"txt\";\n}\n\n/**\n * useArtifacts - Hook for managing artifacts from chat messages\n *\n * Extracts artifacts from messages and provides selection,\n * copy, and download functionality.\n *\n * @example\n * ```tsx\n * import { useArtifacts } from \"@sandagent/sdk/react\";\n *\n * function ArtifactPanel({ messages }) {\n * const {\n * artifacts,\n * selectedArtifact,\n * setSelectedArtifact,\n * hasArtifacts,\n * copyContent,\n * downloadArtifact,\n * } = useArtifacts({ messages });\n *\n * if (!hasArtifacts) return null;\n *\n * return (\n * <div>\n * {artifacts.map((artifact) => (\n * <button\n * key={artifact.artifactId}\n * onClick={() => setSelectedArtifact(artifact)}\n * >\n * {artifact.artifactId}\n * </button>\n * ))}\n *\n * {selectedArtifact && (\n * <div>\n * <pre>{selectedArtifact.content}</pre>\n * <button onClick={() => copyContent(selectedArtifact)}>Copy</button>\n * <button onClick={() => downloadArtifact(selectedArtifact)}>Download</button>\n * </div>\n * )}\n * </div>\n * );\n * }\n * ```\n */\nexport function useArtifacts({\n messages,\n}: UseArtifactsOptions): UseArtifactsReturn {\n const [selectedArtifact, setSelectedArtifact] = useState<ArtifactData | null>(\n null,\n );\n\n // Extract artifacts from messages with memoization optimization\n const prevArtifactsRef = useRef<ArtifactData[]>([]);\n const artifacts = useMemo(() => {\n const results: ArtifactData[] = [];\n for (const message of messages) {\n for (const part of message.parts) {\n if (part.type === \"data-artifact\") {\n const data = part.data as ArtifactData;\n if (!results.some((a) => a.artifactId === data.artifactId)) {\n results.push(data);\n }\n }\n }\n }\n\n // Memoization optimization - return previous reference if content unchanged\n const prev = prevArtifactsRef.current;\n if (\n prev.length === results.length &&\n prev.every((prevArt, idx) => {\n const currArt = results[idx];\n return (\n prevArt.artifactId === currArt.artifactId &&\n prevArt.content === currArt.content &&\n prevArt.mimeType === currArt.mimeType\n );\n })\n ) {\n return prev;\n }\n\n prevArtifactsRef.current = results;\n return results;\n }, [messages]);\n\n // Sync selectedArtifact when artifacts change\n useEffect(() => {\n if (artifacts.length > 0) {\n setSelectedArtifact((prev) => {\n if (!prev) return artifacts[0];\n\n const currentMatch = artifacts.find(\n (a) => a.artifactId === prev.artifactId,\n );\n\n if (!currentMatch) {\n return artifacts[0];\n }\n\n if (\n currentMatch.content === prev.content &&\n currentMatch.mimeType === prev.mimeType\n ) {\n return prev;\n }\n\n return currentMatch;\n });\n } else {\n setSelectedArtifact((prev) => (prev === null ? prev : null));\n }\n }, [artifacts]);\n\n // Select artifact by ID\n const selectArtifactById = useCallback(\n (artifactId: string) => {\n const artifact = artifacts.find((a) => a.artifactId === artifactId);\n if (artifact) {\n setSelectedArtifact(artifact);\n }\n },\n [artifacts],\n );\n\n // Copy content to clipboard\n const copyContent = useCallback(async (artifact: ArtifactData) => {\n await navigator.clipboard.writeText(artifact.content);\n }, []);\n\n // Download artifact as file\n const downloadArtifact = useCallback((artifact: ArtifactData) => {\n const blob = new Blob([artifact.content], { type: artifact.mimeType });\n const url = URL.createObjectURL(blob);\n const a = document.createElement(\"a\");\n a.href = url;\n const fileName =\n artifact.artifactId.split(\"/\").pop() || artifact.artifactId;\n const extension = getFileExtensionFromMimeType(artifact.mimeType);\n a.download = fileName.includes(\".\") ? fileName : `${fileName}.${extension}`;\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n URL.revokeObjectURL(url);\n }, []);\n\n const hasArtifacts = artifacts.length > 0;\n const count = artifacts.length;\n\n return {\n artifacts,\n selectedArtifact,\n setSelectedArtifact,\n selectArtifactById,\n hasArtifacts,\n count,\n copyContent,\n downloadArtifact,\n getFileExtension: getFileExtensionFromMimeType,\n };\n}\n"],"mappings":"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","names":["useCallback","useMemo","useState","useMemo","useCallback","useEffect","useMemo","useRef","useState"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sandagent/sdk",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "SandAgent SDK - AI Provider and React hooks for building AI agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"dependencies": {
|
|
50
50
|
"@ai-sdk/provider": "^3.0.5",
|
|
51
51
|
"@ai-sdk/react": "^3.0.52",
|
|
52
|
-
"@sandagent/manager": "0.2.
|
|
52
|
+
"@sandagent/manager": "0.2.5"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@types/node": "^20.10.0",
|