mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:50:43 +00:00
fix: suppress streaming JSON parse errors without hiding provider diagnostics
This commit is contained in:
@@ -20,6 +20,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Agents/errors: suppress malformed streaming tool-call JSON fragments before they reach chat surfaces while preserving provider request-validation diagnostics. Fixes #59076; keeps #59080 as duplicate coverage. (#59118) Thanks @singleGanghood.
|
||||
- CLI/models: restore provider-filtered `models list --all --provider <id>` rows for providers without manifest/static catalog coverage, including Anthropic and Amazon Bedrock, while keeping the compatibility fallback off expensive availability and resolver paths. Thanks @shakkernerd.
|
||||
- CLI/tools: keep the Gateway `tools.*` RPC namespace out of plugin command discovery and managed proxy startup, so stray commands like `openclaw tools effective` fail quickly instead of cold-loading plugin metadata. Refs #73477. Thanks @oromeis.
|
||||
- CLI/status: keep default text `openclaw status --usage` on metadata-only channel scans unless `--deep` or `--all` is set, and send stray `openclaw tools --help` through the precomputed root-help fast path so latency-triage commands avoid plugin/runtime cold loads before printing. Refs #73477 and #74220. Thanks @oromeis and @NianJiuZst.
|
||||
|
||||
@@ -369,13 +369,27 @@ describe("formatAssistantErrorText", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("sanitizes 'Unexpected token' JSON parse errors (#59076)", () => {
|
||||
const msg = makeAssistantError("Unexpected token < in JSON at position 0");
|
||||
it("sanitizes context-proven streaming 'Unexpected token' JSON parse errors (#59076)", () => {
|
||||
const msg = makeAssistantError(
|
||||
'Could not parse Anthropic SSE event content_block_delta: Unexpected token } in JSON at position 14; data={"type":"content_block_delta","delta":{"type":"input_json_delta","partial_json":"}"},"index":1}',
|
||||
);
|
||||
expect(formatAssistantErrorText(msg)).toBe(
|
||||
"LLM streaming response contained a malformed fragment. Please try again.",
|
||||
);
|
||||
});
|
||||
|
||||
it("does not broadly rewrite non-streaming 'Unexpected token' JSON parse errors", () => {
|
||||
const msg = makeAssistantError("Unexpected token < in JSON at position 0");
|
||||
expect(formatAssistantErrorText(msg)).toBe("Unexpected token < in JSON at position 0");
|
||||
});
|
||||
|
||||
it("does not rewrite non-streaming provider JSON request-validation diagnostics", () => {
|
||||
const msg = makeAssistantError("Expected value in JSON at position 12 for messages.0.content");
|
||||
expect(formatAssistantErrorText(msg)).toBe(
|
||||
"Expected value in JSON at position 12 for messages.0.content",
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps provider request-validation JSON diagnostics actionable", () => {
|
||||
const msg = makeAssistantError(
|
||||
'{"type":"error","error":{"type":"invalid_request_error","message":"Expected value in JSON at position 12 for messages.0.content"}}',
|
||||
|
||||
39
src/agents/pi-embedded-helpers/errors.test.ts
Normal file
39
src/agents/pi-embedded-helpers/errors.test.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import type { AssistantMessage } from "@mariozechner/pi-ai";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { makeAssistantMessageFixture } from "../test-helpers/assistant-message-fixtures.js";
|
||||
import { formatAssistantErrorText } from "./errors.js";
|
||||
|
||||
describe("formatAssistantErrorText streaming JSON parse classification", () => {
|
||||
const makeAssistantError = (errorMessage: string): AssistantMessage =>
|
||||
makeAssistantMessageFixture({
|
||||
errorMessage,
|
||||
content: [{ type: "text", text: errorMessage }],
|
||||
});
|
||||
|
||||
it("suppresses raw streaming tool-call fragment parse failures", () => {
|
||||
const msg = makeAssistantError(
|
||||
"Expected ',' or '}' after property value in JSON at position 334 (line 1 column 335)",
|
||||
);
|
||||
expect(formatAssistantErrorText(msg)).toBe(
|
||||
"LLM streaming response contained a malformed fragment. Please try again.",
|
||||
);
|
||||
});
|
||||
|
||||
it("suppresses structured Anthropic tool-call delta parse failures", () => {
|
||||
const msg = makeAssistantError(
|
||||
'Could not parse Anthropic SSE event content_block_delta: Unexpected end of JSON input; data={"type":"content_block_delta","delta":{"type":"input_json_delta","partial_json":"{\\"path\\":"},"index":0}',
|
||||
);
|
||||
expect(formatAssistantErrorText(msg)).toBe(
|
||||
"LLM streaming response contained a malformed fragment. Please try again.",
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps non-streaming provider request-validation syntax diagnostics", () => {
|
||||
const msg = makeAssistantError(
|
||||
'{"type":"error","error":{"type":"invalid_request_error","message":"Expected value in JSON at position 12 for messages.0.content"}}',
|
||||
);
|
||||
expect(formatAssistantErrorText(msg)).toBe(
|
||||
"LLM request rejected: Expected value in JSON at position 12 for messages.0.content",
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -213,7 +213,19 @@ export function isStreamingJsonParseError(raw: string): boolean {
|
||||
if (!raw) {
|
||||
return false;
|
||||
}
|
||||
return /\b(?:expected|unexpected)\b.+\bin json\b.+\bposition\b/i.test(raw);
|
||||
const trimmed = raw.trim();
|
||||
if (
|
||||
/\bcould not parse anthropic sse event\b/i.test(trimmed) &&
|
||||
/\b(?:content_block_delta|input_json_delta|partial_json|tool_use)\b/i.test(trimmed) &&
|
||||
(/\b(?:expected|unexpected|unterminated)\b.+\bin json\b.+\bposition\b/i.test(trimmed) ||
|
||||
/\bunexpected end of json input\b/i.test(trimmed))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return /^(?:Expected (?:',' or '\}' after property value|double-quoted property name|':' after property name|',' or '\]' after array element)|Unterminated string) in JSON at position \d+(?: \(line \d+ column \d+\))?$/i.test(
|
||||
trimmed,
|
||||
);
|
||||
}
|
||||
|
||||
function hasRateLimitTpmHint(raw: string): boolean {
|
||||
|
||||
Reference in New Issue
Block a user