


























@@ -18,6 +18,8 @@ const hoisted = vi.hoisted(() => ({
1818discoverModelsMock: vi.fn(),
1919fetchMock: vi.fn(),
2020registerProviderStreamForModelMock: vi.fn(),
21+prepareProviderDynamicModelMock: vi.fn(async () => {}),
22+resolveModelWithRegistryMock: vi.fn(),
2123}));
2224const {
2325 completeMock,
@@ -29,8 +31,16 @@ const {
2931 discoverModelsMock,
3032 fetchMock,
3133 registerProviderStreamForModelMock,
34+ prepareProviderDynamicModelMock,
35+ resolveModelWithRegistryMock,
3236} = hoisted;
333738+type ResolveModelWithRegistryTestParams = {
39+modelRegistry: { find: (provider: string, modelId: string) => unknown };
40+provider: string;
41+modelId: string;
42+};
43+3444vi.mock("@mariozechner/pi-ai", async () => {
3545const actual = await vi.importActual<typeof import("@mariozechner/pi-ai")>("@mariozechner/pi-ai");
3646return {
@@ -63,6 +73,17 @@ vi.mock("../agents/pi-model-discovery-runtime.js", () => ({
6373discoverModels: discoverModelsMock,
6474}));
657576+vi.mock("../plugins/provider-runtime.js", async () => ({
77+ ...(await vi.importActual<typeof import("../plugins/provider-runtime.js")>(
78+"../plugins/provider-runtime.js",
79+)),
80+prepareProviderDynamicModel: prepareProviderDynamicModelMock,
81+}));
82+83+vi.mock("../agents/pi-embedded-runner/model.js", () => ({
84+resolveModelWithRegistry: resolveModelWithRegistryMock,
85+}));
86+6687const { describeImageWithModel } = await import("./image.js");
67886889describe("describeImageWithModel", () => {
@@ -93,6 +114,12 @@ describe("describeImageWithModel", () => {
93114baseUrl: "https://api.minimax.io/anthropic",
94115})),
95116});
117+resolveModelWithRegistryMock.mockImplementation(
118+// Delegate to modelRegistry.find so tests that override discoverModelsMock
119+// automatically get the right model through resolveModelWithRegistry.
120+({ modelRegistry, provider, modelId }: ResolveModelWithRegistryTestParams) =>
121+modelRegistry.find(provider, modelId),
122+);
96123});
9712498125it("routes minimax-portal image models through the MiniMax VLM endpoint", async () => {
@@ -188,6 +215,84 @@ describe("describeImageWithModel", () => {
188215expect(fetchMock).not.toHaveBeenCalled();
189216});
190217218+it("resolves configured image models when discovery has not registered the provider", async () => {
219+const registryFind = vi.fn(() => null);
220+discoverModelsMock.mockReturnValue({ find: registryFind });
221+resolveModelWithRegistryMock.mockImplementationOnce(
222+({ provider, modelId }: ResolveModelWithRegistryTestParams) => ({
223+ provider,
224+id: modelId,
225+api: "anthropic-messages",
226+input: ["text", "image"],
227+baseUrl: "http://127.0.0.1:1234",
228+}),
229+);
230+completeMock.mockResolvedValue({
231+role: "assistant",
232+api: "anthropic-messages",
233+provider: "lmstudio",
234+model: "google/gemma-4-e2b",
235+stopReason: "stop",
236+timestamp: Date.now(),
237+content: [{ type: "text", text: "local vision ok" }],
238+});
239+240+const result = await describeImageWithModel({
241+cfg: {
242+models: {
243+providers: {
244+lmstudio: {
245+api: "anthropic-messages",
246+baseUrl: "http://127.0.0.1:1234",
247+models: [
248+{
249+id: "google/gemma-4-e2b",
250+name: "google/gemma-4-e2b",
251+input: ["text", "image"],
252+reasoning: false,
253+cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
254+contextWindow: 131_072,
255+maxTokens: 4096,
256+},
257+],
258+},
259+},
260+},
261+},
262+agentDir: "/tmp/openclaw-agent",
263+provider: "lmstudio",
264+model: "google/gemma-4-e2b",
265+buffer: Buffer.from("png-bytes"),
266+fileName: "image.png",
267+mime: "image/png",
268+prompt: "Describe the image.",
269+timeoutMs: 1000,
270+});
271+272+expect(result).toEqual({
273+text: "local vision ok",
274+model: "google/gemma-4-e2b",
275+});
276+expect(registryFind).not.toHaveBeenCalled();
277+expect(resolveModelWithRegistryMock).toHaveBeenCalledWith(
278+expect.objectContaining({
279+provider: "lmstudio",
280+modelId: "google/gemma-4-e2b",
281+cfg: expect.objectContaining({
282+models: expect.objectContaining({
283+providers: expect.objectContaining({
284+lmstudio: expect.objectContaining({
285+baseUrl: "http://127.0.0.1:1234",
286+}),
287+}),
288+}),
289+}),
290+}),
291+);
292+expect(prepareProviderDynamicModelMock).not.toHaveBeenCalled();
293+expect(completeMock).toHaveBeenCalledOnce();
294+});
295+191296it("passes image prompt as system instructions for codex image requests", async () => {
192297discoverModelsMock.mockReturnValue({
193298find: vi.fn(() => ({
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。