





















@@ -41,6 +41,11 @@ type ResolveModelWithRegistryTestParams = {
4141modelId: string;
4242};
434344+type AuthRequestCall = {
45+profileId?: string;
46+store?: unknown;
47+};
48+4449vi.mock("@mariozechner/pi-ai", async () => {
4550const actual = await vi.importActual<typeof import("@mariozechner/pi-ai")>("@mariozechner/pi-ai");
4651return {
@@ -123,6 +128,14 @@ describe("describeImageWithModel", () => {
123128);
124129});
125130131+function getApiKeyForModelCall(index = 0): AuthRequestCall {
132+const call = (getApiKeyForModelMock.mock.calls as unknown[][])[index];
133+if (!call) {
134+throw new Error(`Expected getApiKeyForModel call ${index}`);
135+}
136+return call[0] as AuthRequestCall;
137+}
138+126139it("routes minimax-portal image models through the MiniMax VLM endpoint", async () => {
127140const timeoutSpy = vi.spyOn(AbortSignal, "timeout");
128141const authStore = { version: 1, profiles: {} };
@@ -144,27 +157,25 @@ describe("describeImageWithModel", () => {
144157model: "MiniMax-VL-01",
145158});
146159expect(ensureOpenClawModelsJsonMock).toHaveBeenCalled();
147-expect(getApiKeyForModelMock).toHaveBeenCalledWith(
148-expect.objectContaining({ store: authStore }),
149-);
160+const authRequest = getApiKeyForModelCall();
161+expect(authRequest?.store).toBe(authStore);
150162expect(requireApiKeyMock).toHaveBeenCalled();
151163expect(setRuntimeApiKeyMock).toHaveBeenCalledWith("minimax-portal", "oauth-test");
152-expect(fetchMock).toHaveBeenCalledWith(
153-"https://api.minimax.io/v1/coding_plan/vlm",
154-expect.objectContaining({
155-method: "POST",
156-headers: {
157-Authorization: "Bearer oauth-test",
158-"Content-Type": "application/json",
159-"MM-API-Source": "OpenClaw",
160-},
161-body: JSON.stringify({
162-prompt: "Describe the image.",
163-image_url: `data:image/png;base64,${Buffer.from("png-bytes").toString("base64")}`,
164-}),
164+const [fetchUrl, fetchOptions] = fetchMock.mock.calls[0] ?? [];
165+expect(fetchUrl).toBe("https://api.minimax.io/v1/coding_plan/vlm");
166+expect(fetchOptions).toEqual({
167+method: "POST",
168+headers: {
169+Authorization: "Bearer oauth-test",
170+"Content-Type": "application/json",
171+"MM-API-Source": "OpenClaw",
172+},
173+body: JSON.stringify({
174+prompt: "Describe the image.",
175+image_url: `data:image/png;base64,${Buffer.from("png-bytes").toString("base64")}`,
165176}),
166-);
167-const [, fetchOptions] = fetchMock.mock.calls[0] ?? [];
177+ signal: fetchOptions?.signal,
178+});
168179expect(fetchOptions?.signal).toBeInstanceOf(AbortSignal);
169180expect(timeoutSpy).toHaveBeenCalledWith(1000);
170181expect(completeMock).not.toHaveBeenCalled();
@@ -205,16 +216,17 @@ describe("describeImageWithModel", () => {
205216text: "generic ok",
206217model: "custom-vision",
207218});
208-expect(registerProviderStreamForModelMock).toHaveBeenCalledWith(
209-expect.objectContaining({
210-model: expect.objectContaining({
211-provider: "minimax-portal",
212-id: "custom-vision",
213-}),
214-cfg: {},
215-agentDir: "/tmp/openclaw-agent",
216-}),
217-);
219+const [streamRequest] = registerProviderStreamForModelMock.mock.calls[0] ?? [];
220+expect(streamRequest).toEqual({
221+model: {
222+provider: "minimax-portal",
223+id: "custom-vision",
224+input: ["text", "image"],
225+baseUrl: "https://api.minimax.io/anthropic",
226+},
227+cfg: {},
228+agentDir: "/tmp/openclaw-agent",
229+});
218230expect(completeMock).toHaveBeenCalledOnce();
219231expect(fetchMock).not.toHaveBeenCalled();
220232});
@@ -278,21 +290,11 @@ describe("describeImageWithModel", () => {
278290model: "google/gemma-4-e2b",
279291});
280292expect(registryFind).not.toHaveBeenCalled();
281-expect(resolveModelWithRegistryMock).toHaveBeenCalledWith(
282-expect.objectContaining({
283-provider: "lmstudio",
284-modelId: "google/gemma-4-e2b",
285-cfg: expect.objectContaining({
286-models: expect.objectContaining({
287-providers: expect.objectContaining({
288-lmstudio: expect.objectContaining({
289-baseUrl: "http://127.0.0.1:1234",
290-}),
291-}),
292-}),
293-}),
294-}),
295-);
293+const [resolveRequest] = resolveModelWithRegistryMock.mock.calls[0] ?? [];
294+expect(resolveRequest?.provider).toBe("lmstudio");
295+expect(resolveRequest?.modelId).toBe("google/gemma-4-e2b");
296+expect(resolveRequest?.agentDir).toBe("/tmp/openclaw-agent");
297+expect(resolveRequest?.cfg.models?.providers?.lmstudio?.baseUrl).toBe("http://127.0.0.1:1234");
296298expect(prepareProviderDynamicModelMock).not.toHaveBeenCalled();
297299expect(completeMock).toHaveBeenCalledOnce();
298300});
@@ -362,44 +364,36 @@ describe("describeImageWithModel", () => {
362364model: "gpt-5.4",
363365});
364366expect(completeMock).toHaveBeenCalledOnce();
365-expect(completeMock).toHaveBeenCalledWith(
366-expect.objectContaining({
367-provider: "openai-codex",
368-id: "gpt-5.4",
369-}),
370-expect.objectContaining({
371-systemPrompt: "Describe the image.",
372-messages: [
373-expect.objectContaining({
374-role: "user",
375-content: [
376-expect.objectContaining({
377-type: "image",
378-mimeType: "image/png",
379-}),
380-],
381-}),
382-],
383-}),
384-expect.objectContaining({
385-apiKey: "oauth-test",
386-maxTokens: 512,
387-}),
388-);
389367const firstCall = completeMock.mock.calls[0];
390368if (!firstCall) {
391369throw new Error("Expected image completion call");
392370}
393-const [, context, options] = firstCall;
371+const [completionModel, context, options] = firstCall;
372+expect(completionModel).toEqual({
373+provider: "openai-codex",
374+id: "gpt-5.4",
375+input: ["text", "image"],
376+baseUrl: "https://chatgpt.com/backend-api",
377+});
378+expect(context.systemPrompt).toBe("Describe the image.");
379+expect(context.messages).toHaveLength(1);
394380expect(Object.keys(options).toSorted()).toEqual(["apiKey", "maxTokens", "signal", "timeoutMs"]);
381+expect(options.apiKey).toBe("oauth-test");
382+expect(options.maxTokens).toBe(512);
395383expect(options.signal).toBeInstanceOf(AbortSignal);
396384expect(options.timeoutMs).toBeGreaterThan(0);
397385expect(options.timeoutMs).toBeLessThanOrEqual(1000);
398386const userMessage = context.messages[0];
399387if (!userMessage) {
400388throw new Error("expected image completion user message");
401389}
390+expect(userMessage.role).toBe("user");
402391expect(userMessage.content).toHaveLength(1);
392+expect(userMessage.content[0]).toEqual({
393+type: "image",
394+data: Buffer.from("png-bytes").toString("base64"),
395+mimeType: "image/png",
396+});
403397});
404398405399it("places OpenRouter image prompts in user content before images", async () => {
@@ -450,10 +444,11 @@ describe("describeImageWithModel", () => {
450444}
451445expect(userMessage.content).toEqual([
452446{ type: "text", text: "Describe the image." },
453-expect.objectContaining({
447+{
454448type: "image",
449+data: Buffer.from("png-bytes").toString("base64"),
455450mimeType: "image/png",
456-}),
451+},
457452]);
458453});
459454@@ -682,11 +677,8 @@ describe("describeImageWithModel", () => {
682677model: "gemini-3-flash-preview",
683678});
684679expect(findMock).toHaveBeenCalledOnce();
685-expect(getApiKeyForModelMock).toHaveBeenCalledWith(
686-expect.objectContaining({
687-profileId: "google:default",
688-}),
689-);
680+const authRequest = getApiKeyForModelCall();
681+expect(authRequest?.profileId).toBe("google:default");
690682expect(setRuntimeApiKeyMock).toHaveBeenCalledWith("google", "oauth-test");
691683});
692684@@ -730,11 +722,8 @@ describe("describeImageWithModel", () => {
730722model: "gemini-3.1-flash-lite-preview",
731723});
732724expect(findMock).toHaveBeenCalledOnce();
733-expect(getApiKeyForModelMock).toHaveBeenCalledWith(
734-expect.objectContaining({
735-profileId: "google:default",
736-}),
737-);
725+const authRequest = getApiKeyForModelCall();
726+expect(authRequest?.profileId).toBe("google:default");
738727expect(setRuntimeApiKeyMock).toHaveBeenCalledWith("google", "oauth-test");
739728});
740729});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。