Skip to content

Commit 99cb2fc

Browse files
author
MouseWW
committed
fix(llm): surface openai stream errors
1 parent ed60366 commit 99cb2fc

4 files changed

Lines changed: 42 additions & 20 deletions

File tree

RELEASE_NOTES.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
# Anything Analyzer v3.6.16
1+
# Anything Analyzer v3.6.17
22

33
## 修复
44

5-
- **Anthropic/MiniMax 流式末尾事件不再丢失** — 修复 Anthropic SSE 响应没有尾随换行时最后一行 `data:` 未被解析,导致末尾文本块被忽略的问题
6-
- 在流结束后补处理剩余 buffer,与 OpenAI/Responses 流式解析保持一致
7-
- 增加无尾随换行 SSE 回归测试,确保最后文本块会进入增量回调和最终内容
5+
- **OpenAI Chat 流式错误不再被吞掉** — 修复 Chat Completions SSE 返回 `error` payload 时被当成空成功响应的问题
6+
- 将流式 JSON 容错解析与 API 错误判断拆开,保留 malformed chunk 跳过逻辑
7+
- 增加错误 payload 回归测试,确保限流、鉴权等流式错误会抛给调用方
88

99
## 下载
1010

1111
| 平台 | 文件 |
1212
|------|------|
13-
| Windows | Anything-Analyzer-Setup-3.6.16.exe |
14-
| macOS (Apple Silicon) | Anything-Analyzer-3.6.16-arm64.dmg |
15-
| macOS (Intel) | Anything-Analyzer-3.6.16-x64.dmg |
16-
| Linux | Anything-Analyzer-3.6.16.AppImage |
13+
| Windows | Anything-Analyzer-Setup-3.6.17.exe |
14+
| macOS (Apple Silicon) | Anything-Analyzer-3.6.17-arm64.dmg |
15+
| macOS (Intel) | Anything-Analyzer-3.6.17-x64.dmg |
16+
| Linux | Anything-Analyzer-3.6.17.AppImage |

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "anything-analyzer",
3-
"version": "3.6.16",
3+
"version": "3.6.17",
44
"description": "Universal web protocol analyzer with embedded browser and AI-powered analysis",
55
"packageManager": "pnpm@10.24.0",
66
"main": "./out/main/index.js",

src/main/ai/llm-router.ts

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -635,19 +635,25 @@ export class LLMRouter {
635635
if (!trimmed || !trimmed.startsWith("data: ")) return;
636636
const data = trimmed.slice(6);
637637
if (data === "[DONE]") return;
638+
let parsed: any;
638639
try {
639-
const parsed = JSON.parse(data) as any;
640-
const chunk = parsed.choices?.[0]?.delta?.content || "";
641-
if (chunk) {
642-
fullContent += chunk;
643-
onChunk(chunk);
644-
}
645-
if (parsed.usage) {
646-
promptTokens = parsed.usage.prompt_tokens;
647-
completionTokens = parsed.usage.completion_tokens;
648-
}
640+
parsed = JSON.parse(data) as any;
649641
} catch {
650-
/* skip */
642+
/* skip malformed JSON */
643+
return;
644+
}
645+
if (parsed.error) {
646+
const errorMsg = parsed.error.message || "Unknown stream error";
647+
throw new Error(`OpenAI stream error: ${errorMsg}`);
648+
}
649+
const chunk = parsed.choices?.[0]?.delta?.content || "";
650+
if (chunk) {
651+
fullContent += chunk;
652+
onChunk(chunk);
653+
}
654+
if (parsed.usage) {
655+
promptTokens = parsed.usage.prompt_tokens;
656+
completionTokens = parsed.usage.completion_tokens;
651657
}
652658
};
653659

tests/main/ai/llm-router.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,22 @@ describe("LLMRouter", () => {
365365
expect(chunks).toEqual(["final chat chunk"]);
366366
expect(result.content).toBe("final chat chunk");
367367
});
368+
369+
it("should reject when OpenAI chat stream emits an error payload", async () => {
370+
fetchSpy.mockResolvedValueOnce(
371+
createSSEResponse([
372+
{
373+
data: '{"error":{"message":"quota exceeded"}}',
374+
},
375+
]),
376+
);
377+
378+
const router = new LLMRouter(baseConfig);
379+
380+
await expect(
381+
router.complete([{ role: "user", content: "test" }], () => {}),
382+
).rejects.toThrow("OpenAI stream error: quota exceeded");
383+
});
368384
});
369385

370386
describe("completeAnthropic - streaming", () => {

0 commit comments

Comments
 (0)