mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-21 10:16:44 +00:00
fix(openrouter): preserve completed music when stream cleanup fails (#111056)
* fix(openrouter): preserve completed music when stream cleanup fails * test(openrouter): clarify stream cleanup authority * test(openrouter): propagate stream cancellation
This commit is contained in:
@@ -41,7 +41,7 @@ vi.mock("openclaw/plugin-sdk/provider-http", async (importOriginal) => {
|
||||
|
||||
function sseResponse(
|
||||
lines: Array<string | Uint8Array>,
|
||||
options?: { cancel?: () => void; releaseLock?: () => void },
|
||||
options?: { cancel?: () => void | Promise<void>; releaseLock?: () => void },
|
||||
): Response {
|
||||
const encoder = new TextEncoder();
|
||||
const encodeLine = (line: string | Uint8Array) =>
|
||||
@@ -59,7 +59,7 @@ function sseResponse(
|
||||
controller.enqueue(encodeLine(line));
|
||||
},
|
||||
cancel() {
|
||||
options?.cancel?.();
|
||||
return options?.cancel?.();
|
||||
},
|
||||
}),
|
||||
{ status: 200, headers: { "content-type": "text/event-stream" } },
|
||||
@@ -211,7 +211,10 @@ describe("openrouter music generation provider", () => {
|
||||
expect(release).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("releases OpenRouter audio stream readers after completion", async () => {
|
||||
it("preserves completed OpenRouter audio when reader cleanup fails", async () => {
|
||||
const cancel = vi.fn(async () => {
|
||||
throw new Error("cancel failed");
|
||||
});
|
||||
const releaseLock = vi.fn();
|
||||
postJsonRequestMock.mockResolvedValue({
|
||||
response: sseResponse(
|
||||
@@ -219,7 +222,7 @@ describe("openrouter music generation provider", () => {
|
||||
audio: Buffer.from("wav-bytes").toString("base64"),
|
||||
done: true,
|
||||
}),
|
||||
{ releaseLock },
|
||||
{ cancel, releaseLock },
|
||||
),
|
||||
release: vi.fn(async () => {}),
|
||||
});
|
||||
@@ -234,6 +237,7 @@ describe("openrouter music generation provider", () => {
|
||||
).resolves.toMatchObject({
|
||||
tracks: [{ mimeType: "audio/wav" }],
|
||||
});
|
||||
expect(cancel).toHaveBeenCalledTimes(1);
|
||||
expect(releaseLock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
@@ -403,8 +407,10 @@ describe("openrouter music generation provider", () => {
|
||||
).rejects.toThrow("OpenRouter music generation stream ended before completion");
|
||||
});
|
||||
|
||||
it("surfaces and cancels OpenRouter mid-stream errors", async () => {
|
||||
const cancel = vi.fn();
|
||||
it("preserves OpenRouter mid-stream errors when reader cleanup fails", async () => {
|
||||
const cancel = vi.fn(async () => {
|
||||
throw new Error("cancel failed");
|
||||
});
|
||||
postJsonRequestMock.mockResolvedValue({
|
||||
response: sseResponse(
|
||||
[
|
||||
|
||||
@@ -295,7 +295,9 @@ async function readOpenRouterAudioStream(
|
||||
for (const line of lines) {
|
||||
if (processOpenRouterSseLine(line.trim(), result)) {
|
||||
flushOpenRouterMusicAudio(result);
|
||||
await reader.cancel();
|
||||
// Once [DONE] is observed, the generated result is authoritative.
|
||||
// Cancellation is cleanup and must not replace it with a transport error.
|
||||
await reader.cancel().catch(() => {});
|
||||
return {
|
||||
audioBuffer: Buffer.concat(result.audioBuffers, result.audioBytes),
|
||||
transcript: result.transcriptChunks.join(""),
|
||||
|
||||
Reference in New Issue
Block a user