Skip to content

Commit 565097f

Browse files
fix(media-understanding): video auto-selection runs without a model (#99791)
* fix: require video media provider models * fix video media test typing and skip coverage * fix(media-understanding): preserve self-defaulting video providers --------- Co-authored-by: Vincent Koc <[email protected]>
1 parent e752232 commit 565097f

2 files changed

Lines changed: 137 additions & 6 deletions

File tree

src/media-understanding/runner.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -632,7 +632,14 @@ async function resolveKeyEntry(params: {
632632
explicitModel: model,
633633
workspaceDir,
634634
})
635-
: model;
635+
: capability === "video"
636+
? (model ??
637+
resolveDefaultMediaModelFromRegistry({
638+
providerId,
639+
capability: "video",
640+
providerRegistry,
641+
}))
642+
: model;
636643
if (capability === "image" && !resolvedModel) {
637644
return null;
638645
}
@@ -907,7 +914,13 @@ async function resolveActiveModelEntry(params: {
907914
providerRegistry: params.providerRegistry,
908915
});
909916
} else {
910-
model = params.activeModel?.model;
917+
model =
918+
params.activeModel?.model ??
919+
resolveDefaultMediaModelFromRegistry({
920+
providerId,
921+
capability: "video",
922+
providerRegistry: params.providerRegistry,
923+
});
911924
}
912925
if ((params.capability === "image" || params.capability === "audio") && !model) {
913926
return null;

src/media-understanding/runner.video.test.ts

Lines changed: 122 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { withTempDir } from "../test-helpers/temp-dir.js";
66
import { withEnvAsync } from "../test-utils/env.js";
77
import { runCapability } from "./runner.js";
88
import { withVideoFixture } from "./runner.test-utils.js";
9+
import type { MediaUnderstandingProvider } from "./types.js";
910

1011
vi.mock("../media/channel-inbound-roots.js", () => ({
1112
resolveChannelInboundAttachmentRoots: () => undefined,
@@ -84,7 +85,7 @@ describe("runCapability video provider wiring", () => {
8485
agentDir: isolatedAgentDir,
8586
attachments: cache,
8687
media,
87-
providerRegistry: new Map([
88+
providerRegistry: new Map<string, MediaUnderstandingProvider>([
8889
[
8990
"moonshot",
9091
{
@@ -150,7 +151,7 @@ describe("runCapability video provider wiring", () => {
150151
agentDir: isolatedAgentDir,
151152
attachments: cache,
152153
media,
153-
providerRegistry: new Map([
154+
providerRegistry: new Map<string, MediaUnderstandingProvider>([
154155
[
155156
"google",
156157
{
@@ -164,7 +165,8 @@ describe("runCapability video provider wiring", () => {
164165
{
165166
id: "moonshot",
166167
capabilities: ["video"],
167-
describeVideo: async () => ({ text: "moonshot", model: "kimi-k2.5" }),
168+
defaultModels: { video: "kimi-k2.5" },
169+
describeVideo: async (req) => ({ text: "moonshot", model: req.model }),
168170
},
169171
],
170172
]),
@@ -180,6 +182,122 @@ describe("runCapability video provider wiring", () => {
180182
});
181183
});
182184

185+
it("uses the provider video default when the active provider has no model", async () => {
186+
let seenModel: string | undefined;
187+
188+
await withTempDir({ prefix: "openclaw-video-active-provider-" }, async (isolatedAgentDir) => {
189+
await withVideoFixture("openclaw-video-active-default", async ({ ctx, media, cache }) => {
190+
const cfg = {
191+
models: {
192+
providers: {
193+
moonshot: {
194+
auth: "api-key",
195+
apiKey: "moonshot-key", // pragma: allowlist secret
196+
models: [],
197+
},
198+
},
199+
},
200+
tools: {
201+
media: {
202+
video: {
203+
enabled: true,
204+
},
205+
},
206+
},
207+
} as unknown as OpenClawConfig;
208+
209+
const result = await runCapability({
210+
capability: "video",
211+
cfg,
212+
ctx,
213+
agentDir: isolatedAgentDir,
214+
attachments: cache,
215+
media,
216+
providerRegistry: new Map<string, MediaUnderstandingProvider>([
217+
[
218+
"moonshot",
219+
{
220+
id: "moonshot",
221+
capabilities: ["video"],
222+
defaultModels: { video: "kimi-k2.5" },
223+
describeVideo: async (req) => {
224+
seenModel = req.model;
225+
return { text: "moonshot", model: req.model };
226+
},
227+
},
228+
],
229+
]),
230+
activeModel: { provider: "moonshot" },
231+
});
232+
233+
expect(result.decision.outcome).toBe("success");
234+
const output = requireCapabilityOutput(result, 0);
235+
expect(output.provider).toBe("moonshot");
236+
expect(output.model).toBe("kimi-k2.5");
237+
expect(seenModel).toBe("kimi-k2.5");
238+
});
239+
});
240+
});
241+
242+
it("preserves self-defaulting video providers without registry model metadata", async () => {
243+
let seenModel: string | undefined;
244+
245+
await withTempDir(
246+
{ prefix: "openclaw-video-no-default-provider-" },
247+
async (isolatedAgentDir) => {
248+
await withVideoFixture("openclaw-video-no-default", async ({ ctx, media, cache }) => {
249+
const cfg = {
250+
models: {
251+
providers: {
252+
moonshot: {
253+
auth: "api-key",
254+
apiKey: "moonshot-key", // pragma: allowlist secret
255+
models: [],
256+
},
257+
},
258+
},
259+
tools: {
260+
media: {
261+
video: {
262+
enabled: true,
263+
},
264+
},
265+
},
266+
} as unknown as OpenClawConfig;
267+
268+
const result = await runCapability({
269+
capability: "video",
270+
cfg,
271+
ctx,
272+
agentDir: isolatedAgentDir,
273+
attachments: cache,
274+
media,
275+
providerRegistry: new Map<string, MediaUnderstandingProvider>([
276+
[
277+
"moonshot",
278+
{
279+
id: "moonshot",
280+
capabilities: ["video"],
281+
describeVideo: async (req) => {
282+
seenModel = req.model;
283+
return { text: "moonshot", model: "provider-default" };
284+
},
285+
},
286+
],
287+
]),
288+
activeModel: { provider: "moonshot" },
289+
});
290+
291+
expect(result.decision.outcome).toBe("success");
292+
const output = requireCapabilityOutput(result, 0);
293+
expect(output.provider).toBe("moonshot");
294+
expect(output.model).toBe("provider-default");
295+
expect(seenModel).toBeUndefined();
296+
});
297+
},
298+
);
299+
});
300+
183301
it("does not use provider api config as video auth modelApi", async () => {
184302
const modelAuth = await import("../agents/model-auth.js");
185303
const resolveApiKeyForProvider = vi.mocked(modelAuth.resolveApiKeyForProvider);
@@ -214,7 +332,7 @@ describe("runCapability video provider wiring", () => {
214332
agentDir: isolatedAgentDir,
215333
attachments: cache,
216334
media,
217-
providerRegistry: new Map([
335+
providerRegistry: new Map<string, MediaUnderstandingProvider>([
218336
[
219337
"openai",
220338
{

0 commit comments

Comments
 (0)