feat: upgrade @ai-sdk/react to 2.0.107 and migrate to new API (#126)

- Upgrade @ai-sdk/react from 2.0.22 to 2.0.107
- Migrate from addToolResult to addToolOutput (new API)
- Add output-error state for proper error signaling to model
- Add sendAutomaticallyWhen for auto-retry on tool errors
- Add stop function ref for potential future use

Co-authored-by: dayuan.jiang <jiangdy@amazon.co.jp>
This commit is contained in:
Dayuan Jiang
2025-12-07 00:40:11 +09:00
committed by GitHub
parent 46567cb0b8
commit 6965a54f48
3 changed files with 153 additions and 264 deletions

View File

@@ -1,7 +1,10 @@
"use client"
import { useChat } from "@ai-sdk/react"
import { DefaultChatTransport } from "ai"
import {
DefaultChatTransport,
lastAssistantMessageIsCompleteWithToolCalls,
} from "ai"
import {
CheckCircle,
PanelRightClose,
@@ -105,83 +108,109 @@ export default function ChatPanel({
chartXMLRef.current = chartXML
}, [chartXML])
const { messages, sendMessage, addToolResult, status, error, setMessages } =
useChat({
transport: new DefaultChatTransport({
api: "/api/chat",
}),
async onToolCall({ toolCall }) {
if (toolCall.toolName === "display_diagram") {
const { xml } = toolCall.input as { xml: string }
// Ref to hold stop function for use in onToolCall (avoids stale closure)
const stopRef = useRef<(() => void) | null>(null)
const validationError = validateMxCellStructure(xml)
const {
messages,
sendMessage,
addToolOutput,
stop,
status,
error,
setMessages,
} = useChat({
transport: new DefaultChatTransport({
api: "/api/chat",
}),
async onToolCall({ toolCall }) {
if (toolCall.toolName === "display_diagram") {
const { xml } = toolCall.input as { xml: string }
if (validationError) {
addToolResult({
tool: "display_diagram",
toolCallId: toolCall.toolCallId,
output: validationError,
})
// Validate the final XML result
const validationError = validateMxCellStructure(xml)
if (validationError) {
console.warn(
"[display_diagram] Validation error:",
validationError,
)
// Return error to model - sendAutomaticallyWhen will trigger retry
const errorMessage = `${validationError}
Please fix the XML issues and call display_diagram again with corrected XML.
Your failed XML:
\`\`\`xml
${xml}
\`\`\``
addToolOutput({
tool: "display_diagram",
toolCallId: toolCall.toolCallId,
state: "output-error",
errorText: errorMessage,
})
} else {
// Success - diagram will be rendered by chat-message-display
addToolOutput({
tool: "display_diagram",
toolCallId: toolCall.toolCallId,
output: "Successfully displayed the diagram.",
})
}
} else if (toolCall.toolName === "edit_diagram") {
const { edits } = toolCall.input as {
edits: Array<{ search: string; replace: string }>
}
let currentXml = ""
try {
console.log("[edit_diagram] Starting...")
// Use chartXML from ref directly - more reliable than export
// especially on Vercel where DrawIO iframe may have latency issues
// Using ref to avoid stale closure in callback
const cachedXML = chartXMLRef.current
if (cachedXML) {
currentXml = cachedXML
console.log(
"[edit_diagram] Using cached chartXML, length:",
currentXml.length,
)
} else {
addToolResult({
tool: "display_diagram",
toolCallId: toolCall.toolCallId,
output: "Successfully displayed the diagram.",
})
}
} else if (toolCall.toolName === "edit_diagram") {
const { edits } = toolCall.input as {
edits: Array<{ search: string; replace: string }>
// Fallback to export only if no cached XML
console.log(
"[edit_diagram] No cached XML, fetching from DrawIO...",
)
currentXml = await onFetchChart(false)
console.log(
"[edit_diagram] Got XML from export, length:",
currentXml.length,
)
}
let currentXml = ""
try {
console.log("[edit_diagram] Starting...")
// Use chartXML from ref directly - more reliable than export
// especially on Vercel where DrawIO iframe may have latency issues
// Using ref to avoid stale closure in callback
const cachedXML = chartXMLRef.current
if (cachedXML) {
currentXml = cachedXML
console.log(
"[edit_diagram] Using cached chartXML, length:",
currentXml.length,
)
} else {
// Fallback to export only if no cached XML
console.log(
"[edit_diagram] No cached XML, fetching from DrawIO...",
)
currentXml = await onFetchChart(false)
console.log(
"[edit_diagram] Got XML from export, length:",
currentXml.length,
)
}
const { replaceXMLParts } = await import("@/lib/utils")
const editedXml = replaceXMLParts(currentXml, edits)
const { replaceXMLParts } = await import("@/lib/utils")
const editedXml = replaceXMLParts(currentXml, edits)
onDisplayChart(editedXml)
onDisplayChart(editedXml)
addToolOutput({
tool: "edit_diagram",
toolCallId: toolCall.toolCallId,
output: `Successfully applied ${edits.length} edit(s) to the diagram.`,
})
console.log("[edit_diagram] Success")
} catch (error) {
console.error("[edit_diagram] Failed:", error)
addToolResult({
tool: "edit_diagram",
toolCallId: toolCall.toolCallId,
output: `Successfully applied ${edits.length} edit(s) to the diagram.`,
})
console.log("[edit_diagram] Success")
} catch (error) {
console.error("[edit_diagram] Failed:", error)
const errorMessage =
error instanceof Error ? error.message : String(error)
const errorMessage =
error instanceof Error
? error.message
: String(error)
addToolResult({
tool: "edit_diagram",
toolCallId: toolCall.toolCallId,
output: `Edit failed: ${errorMessage}
// Use addToolOutput with state: 'output-error' for proper error signaling
addToolOutput({
tool: "edit_diagram",
toolCallId: toolCall.toolCallId,
state: "output-error",
errorText: `Edit failed: ${errorMessage}
Current diagram XML:
\`\`\`xml
@@ -189,34 +218,40 @@ ${currentXml || "No XML available"}
\`\`\`
Please retry with an adjusted search pattern or use display_diagram if retries are exhausted.`,
})
}
}
},
onError: (error) => {
// Silence access code error in console since it's handled by UI
if (!error.message.includes("Invalid or missing access code")) {
console.error("Chat error:", error)
})
}
}
},
onError: (error) => {
// Silence access code error in console since it's handled by UI
if (!error.message.includes("Invalid or missing access code")) {
console.error("Chat error:", error)
}
// Add system message for error so it can be cleared
setMessages((currentMessages) => {
const errorMessage = {
id: `error-${Date.now()}`,
role: "system" as const,
content: error.message,
parts: [{ type: "text" as const, text: error.message }],
}
return [...currentMessages, errorMessage]
})
if (error.message.includes("Invalid or missing access code")) {
// Show settings button and open dialog to help user fix it
setAccessCodeRequired(true)
setShowSettingsDialog(true)
// Add system message for error so it can be cleared
setMessages((currentMessages) => {
const errorMessage = {
id: `error-${Date.now()}`,
role: "system" as const,
content: error.message,
parts: [{ type: "text" as const, text: error.message }],
}
},
})
return [...currentMessages, errorMessage]
})
if (error.message.includes("Invalid or missing access code")) {
// Show settings button and open dialog to help user fix it
setAccessCodeRequired(true)
setShowSettingsDialog(true)
}
},
// Auto-resubmit when all tool results are available (including errors)
// This enables the model to retry when a tool returns an error
sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls,
})
// Update stopRef so onToolCall can access it
stopRef.current = stop
const messagesEndRef = useRef<HTMLDivElement>(null)

192
package-lock.json generated
View File

@@ -15,7 +15,7 @@
"@ai-sdk/deepseek": "^1.0.30",
"@ai-sdk/google": "^2.0.0",
"@ai-sdk/openai": "^2.0.19",
"@ai-sdk/react": "^2.0.22",
"@ai-sdk/react": "^2.0.107",
"@aws-sdk/credential-providers": "^3.943.0",
"@langfuse/client": "^4.4.9",
"@langfuse/otel": "^4.4.4",
@@ -90,23 +90,6 @@
"zod": "^3.25.76 || ^4.1.8"
}
},
"node_modules/@ai-sdk/amazon-bedrock/node_modules/@ai-sdk/provider-utils": {
"version": "3.0.18",
"resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.18.tgz",
"integrity": "sha512-ypv1xXMsgGcNKUP+hglKqtdDuMg68nWHucPPAhIENrbFAI+xCHiqPVN8Zllxyv1TNZwGWUghPxJXU+Mqps0YRQ==",
"license": "Apache-2.0",
"dependencies": {
"@ai-sdk/provider": "2.0.0",
"@standard-schema/spec": "^1.0.0",
"eventsource-parser": "^3.0.6"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"zod": "^3.25.76 || ^4.1.8"
}
},
"node_modules/@ai-sdk/anthropic": {
"version": "2.0.50",
"resolved": "https://registry.npmjs.org/@ai-sdk/anthropic/-/anthropic-2.0.50.tgz",
@@ -123,23 +106,6 @@
"zod": "^3.25.76 || ^4.1.8"
}
},
"node_modules/@ai-sdk/anthropic/node_modules/@ai-sdk/provider-utils": {
"version": "3.0.18",
"resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.18.tgz",
"integrity": "sha512-ypv1xXMsgGcNKUP+hglKqtdDuMg68nWHucPPAhIENrbFAI+xCHiqPVN8Zllxyv1TNZwGWUghPxJXU+Mqps0YRQ==",
"license": "Apache-2.0",
"dependencies": {
"@ai-sdk/provider": "2.0.0",
"@standard-schema/spec": "^1.0.0",
"eventsource-parser": "^3.0.6"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"zod": "^3.25.76 || ^4.1.8"
}
},
"node_modules/@ai-sdk/azure": {
"version": "2.0.69",
"resolved": "https://registry.npmjs.org/@ai-sdk/azure/-/azure-2.0.69.tgz",
@@ -191,32 +157,15 @@
"zod": "^3.25.76 || ^4.1.8"
}
},
"node_modules/@ai-sdk/deepseek/node_modules/@ai-sdk/provider-utils": {
"version": "3.0.18",
"resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.18.tgz",
"integrity": "sha512-ypv1xXMsgGcNKUP+hglKqtdDuMg68nWHucPPAhIENrbFAI+xCHiqPVN8Zllxyv1TNZwGWUghPxJXU+Mqps0YRQ==",
"license": "Apache-2.0",
"dependencies": {
"@ai-sdk/provider": "2.0.0",
"@standard-schema/spec": "^1.0.0",
"eventsource-parser": "^3.0.6"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"zod": "^3.25.76 || ^4.1.8"
}
},
"node_modules/@ai-sdk/gateway": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-2.0.7.tgz",
"integrity": "sha512-/AI5AKi4vOK9SEb8Z1dfXkhsJ5NAfWsoJQc96B/mzn2KIrjw5occOjIwD06scuhV9xWlghCoXJT1sQD9QH/tyg==",
"version": "2.0.18",
"resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-2.0.18.tgz",
"integrity": "sha512-sDQcW+6ck2m0pTIHW6BPHD7S125WD3qNkx/B8sEzJp/hurocmJ5Cni0ybExg6sQMGo+fr/GWOwpHF1cmCdg5rQ==",
"license": "Apache-2.0",
"dependencies": {
"@ai-sdk/provider": "2.0.0",
"@ai-sdk/provider-utils": "3.0.16",
"@vercel/oidc": "3.0.3"
"@ai-sdk/provider-utils": "3.0.18",
"@vercel/oidc": "3.0.5"
},
"engines": {
"node": ">=18"
@@ -300,23 +249,6 @@
"zod": "^3.25.76 || ^4.1.8"
}
},
"node_modules/@ai-sdk/openai-compatible/node_modules/@ai-sdk/provider-utils": {
"version": "3.0.18",
"resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.18.tgz",
"integrity": "sha512-ypv1xXMsgGcNKUP+hglKqtdDuMg68nWHucPPAhIENrbFAI+xCHiqPVN8Zllxyv1TNZwGWUghPxJXU+Mqps0YRQ==",
"license": "Apache-2.0",
"dependencies": {
"@ai-sdk/provider": "2.0.0",
"@standard-schema/spec": "^1.0.0",
"eventsource-parser": "^3.0.6"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"zod": "^3.25.76 || ^4.1.8"
}
},
"node_modules/@ai-sdk/openai/node_modules/@ai-sdk/provider-utils": {
"version": "3.0.17",
"resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.17.tgz",
@@ -347,9 +279,9 @@
}
},
"node_modules/@ai-sdk/provider-utils": {
"version": "3.0.16",
"resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.16.tgz",
"integrity": "sha512-lsWQY9aDXHitw7C1QRYIbVGmgwyT98TF3MfM8alNIXKpdJdi+W782Rzd9f1RyOfgRmZ08gJ2EYNDhWNK7RqpEA==",
"version": "3.0.18",
"resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.18.tgz",
"integrity": "sha512-ypv1xXMsgGcNKUP+hglKqtdDuMg68nWHucPPAhIENrbFAI+xCHiqPVN8Zllxyv1TNZwGWUghPxJXU+Mqps0YRQ==",
"license": "Apache-2.0",
"dependencies": {
"@ai-sdk/provider": "2.0.0",
@@ -364,13 +296,13 @@
}
},
"node_modules/@ai-sdk/react": {
"version": "2.0.22",
"resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-2.0.22.tgz",
"integrity": "sha512-nJt2U0ZDjpdPEIHCEWlxOixUhQyA/teQ0y9gz66mYW40OhBjSsZjcEAYhbS05mvy+NMVqzlE3sVu54DqzjR68w==",
"version": "2.0.107",
"resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-2.0.107.tgz",
"integrity": "sha512-rv0u+tAi2r2zJu2uSLXcC3TBgGrkQIWXRM+i6us6qcGmYQ2kOu2VYg+lxviOSGPhL9PVebvTlN5x8mf3rDqX+w==",
"license": "Apache-2.0",
"dependencies": {
"@ai-sdk/provider-utils": "3.0.5",
"ai": "5.0.22",
"@ai-sdk/provider-utils": "3.0.18",
"ai": "5.0.107",
"swr": "^2.2.5",
"throttleit": "2.1.0"
},
@@ -379,7 +311,7 @@
},
"peerDependencies": {
"react": "^18 || ^19 || ^19.0.0-rc",
"zod": "^3.25.76 || ^4"
"zod": "^3.25.76 || ^4.1.8"
},
"peerDependenciesMeta": {
"zod": {
@@ -387,67 +319,6 @@
}
}
},
"node_modules/@ai-sdk/react/node_modules/@ai-sdk/gateway": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-1.0.11.tgz",
"integrity": "sha512-ErwWS3sPOuWy42eE3AVxlKkTa1XjjKBEtNCOylVKMO5KNyz5qie8QVlLYbULOG56dtxX4zTKX3rQNJudplhcmQ==",
"license": "Apache-2.0",
"dependencies": {
"@ai-sdk/provider": "2.0.0",
"@ai-sdk/provider-utils": "3.0.5"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"zod": "^3.25.76 || ^4"
}
},
"node_modules/@ai-sdk/react/node_modules/@ai-sdk/provider-utils": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.5.tgz",
"integrity": "sha512-HliwB/yzufw3iwczbFVE2Fiwf1XqROB/I6ng8EKUsPM5+2wnIa8f4VbljZcDx+grhFrPV+PnRZH7zBqi8WZM7Q==",
"license": "Apache-2.0",
"dependencies": {
"@ai-sdk/provider": "2.0.0",
"@standard-schema/spec": "^1.0.0",
"eventsource-parser": "^3.0.3",
"zod-to-json-schema": "^3.24.1"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"zod": "^3.25.76 || ^4"
}
},
"node_modules/@ai-sdk/react/node_modules/@ai-sdk/provider-utils/node_modules/zod-to-json-schema": {
"version": "3.24.6",
"resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz",
"integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==",
"license": "ISC",
"peerDependencies": {
"zod": "^3.24.1"
}
},
"node_modules/@ai-sdk/react/node_modules/ai": {
"version": "5.0.22",
"resolved": "https://registry.npmjs.org/ai/-/ai-5.0.22.tgz",
"integrity": "sha512-RZiYhj7Ux7hrLtXkHPcxzdiSZt4NOiC69O5AkNfMCsz3twwz/KRkl9ASptosoOsg833s5yRcTSdIu5z53Sl6Pw==",
"license": "Apache-2.0",
"dependencies": {
"@ai-sdk/gateway": "1.0.11",
"@ai-sdk/provider": "2.0.0",
"@ai-sdk/provider-utils": "3.0.5",
"@opentelemetry/api": "1.9.0"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"zod": "^3.25.76 || ^4"
}
},
"node_modules/@alloc/quick-lru": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
@@ -5999,9 +5870,9 @@
}
},
"node_modules/@vercel/oidc": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@vercel/oidc/-/oidc-3.0.3.tgz",
"integrity": "sha512-yNEQvPcVrK9sIe637+I0jD6leluPxzwJKx/Haw6F4H77CdDsszUn5V3o96LPziXkSNE2B83+Z3mjqGKBK/R6Gg==",
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@vercel/oidc/-/oidc-3.0.5.tgz",
"integrity": "sha512-fnYhv671l+eTTp48gB4zEsTW/YtRgRPnkI2nT7x6qw5rkI1Lq2hTmQIpHPgyThI0znLK+vX2n9XxKdXZ7BUbbw==",
"license": "Apache-2.0",
"engines": {
"node": ">= 20"
@@ -6049,14 +5920,14 @@
}
},
"node_modules/ai": {
"version": "5.0.90",
"resolved": "https://registry.npmjs.org/ai/-/ai-5.0.90.tgz",
"integrity": "sha512-bawNN10N2cXzFedbDdNUZo8KkcGp12VX1b+mCL5dfllh6WmLsIYYME7GVxsRJvHvPP7xRhuds5fn0jtLyxGnZw==",
"version": "5.0.107",
"resolved": "https://registry.npmjs.org/ai/-/ai-5.0.107.tgz",
"integrity": "sha512-laZlS9ZC/DZfSaxPgrBqI4mM+kxRvTPBBQfa74ceBFskkunZKEsaGVFNEs4cfyGa3nCCCl1WO/fjxixp4V8Zag==",
"license": "Apache-2.0",
"dependencies": {
"@ai-sdk/gateway": "2.0.7",
"@ai-sdk/gateway": "2.0.18",
"@ai-sdk/provider": "2.0.0",
"@ai-sdk/provider-utils": "3.0.16",
"@ai-sdk/provider-utils": "3.0.18",
"@opentelemetry/api": "1.9.0"
},
"engines": {
@@ -10743,23 +10614,6 @@
"zod": "^4.0.16"
}
},
"node_modules/ollama-ai-provider-v2/node_modules/@ai-sdk/provider-utils": {
"version": "3.0.17",
"resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.17.tgz",
"integrity": "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw==",
"license": "Apache-2.0",
"dependencies": {
"@ai-sdk/provider": "2.0.0",
"@standard-schema/spec": "^1.0.0",
"eventsource-parser": "^3.0.6"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"zod": "^3.25.76 || ^4.1.8"
}
},
"node_modules/onetime": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz",

View File

@@ -19,7 +19,7 @@
"@ai-sdk/deepseek": "^1.0.30",
"@ai-sdk/google": "^2.0.0",
"@ai-sdk/openai": "^2.0.19",
"@ai-sdk/react": "^2.0.22",
"@ai-sdk/react": "^2.0.107",
"@aws-sdk/credential-providers": "^3.943.0",
"@langfuse/client": "^4.4.9",
"@langfuse/otel": "^4.4.4",