mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-26 20:35:39 -06:00
fix(ollama): bound stream error bodies
This commit is contained in:
@@ -1534,6 +1534,28 @@ function getGuardedFetchCall(fetchMock: typeof fetchWithSsrFGuardMock): GuardedF
|
||||
return (fetchMock.mock.calls.at(0)?.[0] as GuardedFetchCall | undefined) ?? { url: "" };
|
||||
}
|
||||
|
||||
function cancelTrackedResponse(
|
||||
text: string,
|
||||
init: ResponseInit,
|
||||
): {
|
||||
response: Response;
|
||||
wasCanceled: () => boolean;
|
||||
} {
|
||||
let canceled = false;
|
||||
const stream = new ReadableStream<Uint8Array>({
|
||||
start(controller) {
|
||||
controller.enqueue(new TextEncoder().encode(text));
|
||||
},
|
||||
cancel() {
|
||||
canceled = true;
|
||||
},
|
||||
});
|
||||
return {
|
||||
response: new Response(stream, init),
|
||||
wasCanceled: () => canceled,
|
||||
};
|
||||
}
|
||||
|
||||
async function createOllamaTestStream(params: {
|
||||
baseUrl: string;
|
||||
defaultHeaders?: Record<string, string>;
|
||||
@@ -2684,12 +2706,14 @@ describe("createOllamaStreamFn", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("surfaces non-2xx HTTP response as status-prefixed error", async () => {
|
||||
it("surfaces bounded non-2xx HTTP response text as a status-prefixed error", async () => {
|
||||
const tracked = cancelTrackedResponse(`${"Service Unavailable ".repeat(1024)}tail`, {
|
||||
status: 503,
|
||||
statusText: "Service Unavailable",
|
||||
});
|
||||
const textSpy = vi.spyOn(tracked.response, "text").mockRejectedValue(new Error("unbounded"));
|
||||
fetchWithSsrFGuardMock.mockResolvedValue({
|
||||
response: new Response("Service Unavailable", {
|
||||
status: 503,
|
||||
statusText: "Service Unavailable",
|
||||
}),
|
||||
response: tracked.response,
|
||||
release: vi.fn(async () => undefined),
|
||||
});
|
||||
try {
|
||||
@@ -2705,6 +2729,10 @@ describe("createOllamaStreamFn", () => {
|
||||
// The error message must start with the HTTP status code so that
|
||||
// extractLeadingHttpStatus can parse it for failover/retry logic.
|
||||
expect(errorEvent.error.errorMessage).toMatch(/^503\b/);
|
||||
expect(errorEvent.error.errorMessage).toContain("Service Unavailable");
|
||||
expect(errorEvent.error.errorMessage).not.toContain("tail");
|
||||
expect(tracked.wasCanceled()).toBe(true);
|
||||
expect(textSpy).not.toHaveBeenCalled();
|
||||
} finally {
|
||||
fetchWithSsrFGuardMock.mockReset();
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import type {
|
||||
ProviderWrapStreamFnContext,
|
||||
} from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { isNonSecretApiKeyMarker } from "openclaw/plugin-sdk/provider-auth";
|
||||
import { readResponseTextLimited } from "openclaw/plugin-sdk/provider-http";
|
||||
import {
|
||||
DEFAULT_CONTEXT_TOKENS,
|
||||
normalizeProviderId,
|
||||
@@ -54,6 +55,7 @@ export const OLLAMA_NATIVE_BASE_URL = OLLAMA_DEFAULT_BASE_URL;
|
||||
|
||||
const OLLAMA_STREAM_COOPERATIVE_YIELD_INTERVAL_MS = 12;
|
||||
const OLLAMA_STREAM_COOPERATIVE_YIELD_MAX_EVENTS = 64;
|
||||
const OLLAMA_STREAM_ERROR_BODY_LIMIT_BYTES = 8 * 1024;
|
||||
const GARBLED_VISIBLE_TEXT_MODEL_RE = /\b(?:glm|kimi)\b/i;
|
||||
const GARBLED_VISIBLE_TEXT_MIN_CHARS = 80;
|
||||
const GARBLED_VISIBLE_TEXT_SYMBOL_RE = /[$#%&="'_~`^|\\/*+\-[\]{}()<>:;,.!?]/gu;
|
||||
@@ -1211,7 +1213,10 @@ function createRawOllamaStreamFn(
|
||||
|
||||
try {
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text().catch(() => "unknown error");
|
||||
const errorText = await readResponseTextLimited(
|
||||
response,
|
||||
OLLAMA_STREAM_ERROR_BODY_LIMIT_BYTES,
|
||||
).catch(() => "unknown error");
|
||||
throw new Error(`${response.status} ${errorText}`);
|
||||
}
|
||||
if (!response.body) {
|
||||
|
||||
Reference in New Issue
Block a user