mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-19 22:51:37 +00:00
* fix(minimax): bound image/video success response reads MiniMax image generation and video generation (task submit + status poll) read their success responses through unbounded `await response.json()`, so a misbehaving or hostile endpoint could stream an arbitrarily large body into memory before parsing and exhaust the process. Read those success bodies through the shared bounded reader (16 MiB cap, the same limit other bundled providers and the sibling MiniMax web-search provider already use) and cancel the stream on overflow. The error-body path is already bounded via assertOkOrThrowHttpError; this closes the matching success-JSON gap. MiniMax TTS is already bounded and is left unchanged. AI-assisted. * fix(minimax): bound video metadata response reads * fix(minimax): leave image response sizing to image hardening * fix(minimax): bound image/video success response reads MiniMax image generation and video generation (task submit + status poll) read their success responses through unbounded `await response.json()`, so a misbehaving or hostile endpoint could stream an arbitrarily large body into memory before parsing and exhaust the process. Read those success bodies through the shared bounded reader (16 MiB cap, the same limit other bundled providers and the sibling MiniMax web-search provider already use) and cancel the stream on overflow. The error-body path is already bounded via assertOkOrThrowHttpError; this closes the matching success-JSON gap. MiniMax TTS is already bounded and is left unchanged. AI-assisted. * fix(minimax): bound video metadata response reads
148 lines
5.4 KiB
TypeScript
148 lines
5.4 KiB
TypeScript
// Minimax provider module implements model/runtime integration.
|
|
import type {
|
|
fetchProviderDownloadResponse,
|
|
fetchProviderOperationResponse,
|
|
resolveProviderHttpRequestConfig,
|
|
} from "openclaw/plugin-sdk/provider-http";
|
|
import { afterEach, vi, type Mock } from "vitest";
|
|
|
|
type ResolveProviderHttpRequestConfigParams = Parameters<
|
|
typeof resolveProviderHttpRequestConfig
|
|
>[0];
|
|
type FetchProviderOperationResponseParams = Parameters<typeof fetchProviderOperationResponse>[0];
|
|
type FetchProviderDownloadResponseParams = Parameters<typeof fetchProviderDownloadResponse>[0];
|
|
|
|
type ResolveProviderHttpRequestConfigResult = {
|
|
baseUrl: string;
|
|
allowPrivateNetwork: boolean;
|
|
headers: Headers;
|
|
dispatcherPolicy: undefined;
|
|
};
|
|
|
|
type AnyMock = Mock<(...args: any[]) => any>;
|
|
|
|
interface MinimaxProviderHttpMocks {
|
|
resolveApiKeyForProviderMock: Mock<() => Promise<{ apiKey: string }>>;
|
|
postJsonRequestMock: AnyMock;
|
|
fetchWithTimeoutMock: AnyMock;
|
|
fetchProviderOperationResponseMock: AnyMock;
|
|
fetchProviderDownloadResponseMock: AnyMock;
|
|
assertOkOrThrowHttpErrorMock: Mock<(response: Response, label: string) => Promise<void>>;
|
|
resolveProviderHttpRequestConfigMock: Mock<
|
|
(params: ResolveProviderHttpRequestConfigParams) => ResolveProviderHttpRequestConfigResult
|
|
>;
|
|
}
|
|
|
|
const minimaxProviderHttpMocks = vi.hoisted(() => ({
|
|
resolveApiKeyForProviderMock: vi.fn(async () => ({ apiKey: "provider-key" })),
|
|
postJsonRequestMock: vi.fn(),
|
|
fetchWithTimeoutMock: vi.fn(),
|
|
fetchProviderOperationResponseMock: vi.fn(),
|
|
fetchProviderDownloadResponseMock: vi.fn(),
|
|
assertOkOrThrowHttpErrorMock: vi.fn(async (_response: Response, _label: string) => {}),
|
|
resolveProviderHttpRequestConfigMock: vi.fn((params: ResolveProviderHttpRequestConfigParams) => ({
|
|
baseUrl: params.baseUrl ?? params.defaultBaseUrl,
|
|
allowPrivateNetwork: false,
|
|
headers: new Headers(params.defaultHeaders),
|
|
dispatcherPolicy: undefined,
|
|
})),
|
|
}));
|
|
|
|
function resolveMockProviderTimeoutMs(
|
|
timeoutMs: FetchProviderOperationResponseParams["timeoutMs"],
|
|
) {
|
|
return typeof timeoutMs === "function" ? timeoutMs() : (timeoutMs ?? 60_000);
|
|
}
|
|
|
|
minimaxProviderHttpMocks.fetchProviderOperationResponseMock.mockImplementation(
|
|
async (params: FetchProviderOperationResponseParams) => {
|
|
const response = await minimaxProviderHttpMocks.fetchWithTimeoutMock(
|
|
params.url,
|
|
params.init ?? {},
|
|
resolveMockProviderTimeoutMs(params.timeoutMs),
|
|
params.fetchFn,
|
|
);
|
|
if (params.requestFailedMessage) {
|
|
await minimaxProviderHttpMocks.assertOkOrThrowHttpErrorMock(
|
|
response,
|
|
params.requestFailedMessage,
|
|
);
|
|
}
|
|
return response;
|
|
},
|
|
);
|
|
|
|
minimaxProviderHttpMocks.fetchProviderDownloadResponseMock.mockImplementation(
|
|
async (params: FetchProviderDownloadResponseParams) => {
|
|
const response = await minimaxProviderHttpMocks.fetchWithTimeoutMock(
|
|
params.url,
|
|
params.init ?? {},
|
|
resolveMockProviderTimeoutMs(params.timeoutMs),
|
|
params.fetchFn,
|
|
);
|
|
await minimaxProviderHttpMocks.assertOkOrThrowHttpErrorMock(
|
|
response,
|
|
params.requestFailedMessage,
|
|
);
|
|
return response;
|
|
},
|
|
);
|
|
|
|
vi.mock("openclaw/plugin-sdk/provider-auth-runtime", () => ({
|
|
resolveApiKeyForProvider: minimaxProviderHttpMocks.resolveApiKeyForProviderMock,
|
|
}));
|
|
|
|
vi.mock("openclaw/plugin-sdk/provider-http", async (importActual) => {
|
|
const actual = await importActual<typeof import("openclaw/plugin-sdk/provider-http")>();
|
|
return {
|
|
assertOkOrThrowHttpError: minimaxProviderHttpMocks.assertOkOrThrowHttpErrorMock,
|
|
createProviderOperationDeadline: ({
|
|
label,
|
|
timeoutMs,
|
|
}: {
|
|
label: string;
|
|
timeoutMs?: number;
|
|
}) => ({
|
|
label,
|
|
timeoutMs,
|
|
}),
|
|
createProviderOperationTimeoutResolver:
|
|
({ defaultTimeoutMs }: { defaultTimeoutMs: number }) =>
|
|
() =>
|
|
defaultTimeoutMs,
|
|
fetchProviderDownloadResponse: minimaxProviderHttpMocks.fetchProviderDownloadResponseMock,
|
|
fetchProviderOperationResponse: minimaxProviderHttpMocks.fetchProviderOperationResponseMock,
|
|
fetchWithTimeout: minimaxProviderHttpMocks.fetchWithTimeoutMock,
|
|
postJsonRequest: minimaxProviderHttpMocks.postJsonRequestMock,
|
|
readProviderJsonResponse: actual.readProviderJsonResponse,
|
|
resolveProviderOperationTimeoutMs: ({ defaultTimeoutMs }: { defaultTimeoutMs: number }) =>
|
|
defaultTimeoutMs,
|
|
resolveProviderHttpRequestConfig: minimaxProviderHttpMocks.resolveProviderHttpRequestConfigMock,
|
|
waitProviderOperationPollInterval: async () => {},
|
|
};
|
|
});
|
|
|
|
export function getMinimaxProviderHttpMocks(): MinimaxProviderHttpMocks {
|
|
return minimaxProviderHttpMocks;
|
|
}
|
|
|
|
export function installMinimaxProviderHttpMockCleanup(): void {
|
|
afterEach(() => {
|
|
minimaxProviderHttpMocks.resolveApiKeyForProviderMock.mockClear();
|
|
minimaxProviderHttpMocks.postJsonRequestMock.mockReset();
|
|
minimaxProviderHttpMocks.fetchWithTimeoutMock.mockReset();
|
|
minimaxProviderHttpMocks.fetchProviderOperationResponseMock.mockClear();
|
|
minimaxProviderHttpMocks.fetchProviderDownloadResponseMock.mockClear();
|
|
minimaxProviderHttpMocks.assertOkOrThrowHttpErrorMock.mockClear();
|
|
minimaxProviderHttpMocks.resolveProviderHttpRequestConfigMock.mockClear();
|
|
});
|
|
}
|
|
|
|
export function loadMinimaxMusicGenerationProviderModule() {
|
|
return import("./music-generation-provider.js");
|
|
}
|
|
|
|
export function loadMinimaxVideoGenerationProviderModule() {
|
|
return import("./video-generation-provider.js");
|
|
}
|