mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-10-11 03:59:58 +08:00
fix(chat): an edit after a broken edit call no longer fails, found with Opus 5.5
- Claude Opus 5.5 sent an edit with invalid JSON, then the same edit
again. The first call's streamed preview was never undone: its input
has no operations, and the undo sat behind that check. The second edit
then started from the preview, failed on a duplicate id, and the model
had to try a third time. The undo now runs first, and an edit that
starts in the same render uses the undone diagram.
- The SDK passes an invalid tool call's error as a string, which was
wrapped as a provider error. streamErrorText keeps it as the text the
model reads.
- Bedrock's "on-demand throughput isn't supported" gets the model id hint.
- The thinking header uses the page language ("Thought for 1 second" in
English), from the dictionary entries that were already there.
This commit is contained in:
@@ -1,7 +1,20 @@
|
||||
// @vitest-environment node
|
||||
import { APICallError, InvalidToolInputError, RetryError } from "ai"
|
||||
import {
|
||||
APICallError,
|
||||
InvalidToolInputError,
|
||||
RetryError,
|
||||
simulateReadableStream,
|
||||
streamText,
|
||||
tool,
|
||||
} from "ai"
|
||||
import { MockLanguageModelV3 } from "ai/test"
|
||||
import { describe, expect, it } from "vitest"
|
||||
import { classifyLLMError, isToolCallError } from "@/lib/llm-errors"
|
||||
import { z } from "zod"
|
||||
import {
|
||||
classifyLLMError,
|
||||
isToolCallError,
|
||||
streamErrorText,
|
||||
} from "@/lib/llm-errors"
|
||||
|
||||
const apiError = (statusCode: number, message: string, responseBody = "") =>
|
||||
new APICallError({
|
||||
@@ -89,6 +102,14 @@ describe("classifyLLMError", () => {
|
||||
expect(classifyLLMError(timeout).code).toBe("timeout")
|
||||
})
|
||||
|
||||
it("points to the model id when Bedrock wants an inference profile", () => {
|
||||
const error = apiError(
|
||||
400,
|
||||
"Invocation of model ID anthropic.claude-sonnet-5-5 with on-demand throughput isn’t supported. Retry your request with the ID or ARN of an inference profile that contains this model.",
|
||||
)
|
||||
expect(classifyLLMError(error).code).toBe("model_not_found")
|
||||
})
|
||||
|
||||
it("names a network error the SDK wrapped", () => {
|
||||
const error = new APICallError({
|
||||
message:
|
||||
@@ -131,6 +152,64 @@ describe("classifyLLMError", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("streamErrorText", () => {
|
||||
it("keeps the text of a tool call the model got wrong", async () => {
|
||||
// Seen with Claude Opus 5.5: a quote left unescaped in the input
|
||||
const model = new MockLanguageModelV3({
|
||||
doStream: (async () => ({
|
||||
stream: simulateReadableStream({
|
||||
chunks: [
|
||||
{
|
||||
type: "tool-call",
|
||||
toolCallId: "c1",
|
||||
toolName: "edit_diagram",
|
||||
input: '{"operations": [{"new_xml": "as="x""}]}',
|
||||
},
|
||||
{
|
||||
type: "finish",
|
||||
finishReason: {
|
||||
unified: "tool-calls",
|
||||
raw: "tool_use",
|
||||
},
|
||||
usage: {
|
||||
inputTokens: { total: 1 },
|
||||
outputTokens: { total: 1 },
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
})) as any,
|
||||
})
|
||||
const result = streamText({
|
||||
model: model as any,
|
||||
prompt: "edit",
|
||||
tools: {
|
||||
edit_diagram: tool({
|
||||
inputSchema: z.object({ operations: z.array(z.any()) }),
|
||||
}),
|
||||
},
|
||||
})
|
||||
const errors: string[] = []
|
||||
for await (const chunk of result.toUIMessageStream({
|
||||
onError: streamErrorText,
|
||||
})) {
|
||||
if ("errorText" in chunk) errors.push(chunk.errorText)
|
||||
}
|
||||
expect(errors.length).toBeGreaterThan(0)
|
||||
for (const text of errors) {
|
||||
expect(text).toMatch(/^Invalid input for tool edit_diagram/)
|
||||
}
|
||||
})
|
||||
|
||||
it("classifies a provider error", () => {
|
||||
expect(JSON.parse(streamErrorText(apiError(401, "bad key")))).toEqual({
|
||||
type: "provider",
|
||||
code: "invalid_api_key",
|
||||
message: "bad key",
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("isToolCallError", () => {
|
||||
it("spots errors the model must see unchanged", () => {
|
||||
const invalid = new InvalidToolInputError({
|
||||
|
||||
Reference in New Issue
Block a user