summaryrefslogtreecommitdiffhomepage
path: root/packages/vision-handoff/src
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-27 18:36:36 +0900
committerAdam Malczewski <[email protected]>2026-06-27 18:36:36 +0900
commitfe338f88a52df1360347408fb3a8f9ea83172f62 (patch)
tree00a0994cf7e19f22540fe74582fb670fab684075 /packages/vision-handoff/src
parente76790fef11de3cc33f60689f9301030ed740cd6 (diff)
downloaddispatch-fe338f88a52df1360347408fb3a8f9ea83172f62.tar.gz
dispatch-fe338f88a52df1360347408fb3a8f9ea83172f62.zip
fix(vision): detect umans kimi + qwen models as vision-capable (not just kimi)
Diffstat (limited to 'packages/vision-handoff/src')
-rw-r--r--packages/vision-handoff/src/pure.test.ts42
-rw-r--r--packages/vision-handoff/src/service.test.ts38
2 files changed, 42 insertions, 38 deletions
diff --git a/packages/vision-handoff/src/pure.test.ts b/packages/vision-handoff/src/pure.test.ts
index 4198d00..ea28288 100644
--- a/packages/vision-handoff/src/pure.test.ts
+++ b/packages/vision-handoff/src/pure.test.ts
@@ -11,21 +11,21 @@ import {
describe("isVisionCapable", () => {
it("returns true when ModelInfo.vision is true", () => {
- expect(isVisionCapable("umans/kimi-k2.7", { id: "kimi-k2.7", vision: true })).toBe(true);
+ expect(isVisionCapable("umans/umans-kimi-k2.7", { id: "umans-kimi-k2.7", vision: true })).toBe(true);
});
it("returns false when ModelInfo.vision is false (overrides name heuristic)", () => {
- expect(isVisionCapable("umans/kimi-k2.7", { id: "kimi-k2.7", vision: false })).toBe(false);
+ expect(isVisionCapable("umans/umans-kimi-k2.7", { id: "umans-kimi-k2.7", vision: false })).toBe(false);
});
- it("falls back to name heuristic when vision is absent (kimi)", () => {
- expect(isVisionCapable("umans/kimi-k2.7", undefined)).toBe(true);
- expect(isVisionCapable("umans/Kimi-K2.7", undefined)).toBe(true); // case-insensitive
+ it("falls back to name heuristic when vision is absent (umans kimi + qwen)", () => {
+ expect(isVisionCapable("umans/umans-kimi-k2.7", undefined)).toBe(true);
+ expect(isVisionCapable("umans/umans-qwen3.6-35b-a3b", undefined)).toBe(true);
});
- it("falls back to name heuristic when vision is absent (non-kimi)", () => {
- expect(isVisionCapable("umans/glm-5.2", undefined)).toBe(false);
- expect(isVisionCapable("umans/deepseek-v4-flash", { id: "deepseek-v4-flash" })).toBe(false);
+ it("falls back to name heuristic when vision is absent (non-vision)", () => {
+ expect(isVisionCapable("umans/umans-glm-5.2", undefined)).toBe(false);
+ expect(isVisionCapable("umans/umans-coder", { id: "umans-coder" })).toBe(false);
});
it("returns false for undefined model name", () => {
@@ -36,37 +36,41 @@ describe("isVisionCapable", () => {
describe("findVisionModelName", () => {
const getInfo = async (name: string): Promise<ModelInfo | undefined> => {
const map: Record<string, ModelInfo> = {
- "umans/kimi-k2.7": { id: "kimi-k2.7", vision: true },
- "umans/glm-5.2": { id: "glm-5.2" },
+ "umans/umans-kimi-k2.7": { id: "umans-kimi-k2.7", vision: true },
+ "umans/umans-qwen3.6-35b-a3b": { id: "umans-qwen3.6-35b-a3b", vision: true },
+ "umans/umans-glm-5.2": { id: "umans-glm-5.2" },
"umans/llama-vision": { id: "llama-vision", vision: true },
};
return map[name];
};
- it("finds the first kimi-family model via name heuristic", async () => {
+ it("finds the first umans kimi model via name heuristic", async () => {
const name = await findVisionModelName(
- ["umans/glm-5.2", "umans/kimi-k2.7", "umans/llama-vision"],
+ ["umans/umans-glm-5.2", "umans/umans-kimi-k2.7", "umans/llama-vision"],
getInfo,
);
- expect(name).toBe("umans/kimi-k2.7");
+ expect(name).toBe("umans/umans-kimi-k2.7");
});
it("finds a vision model via ModelInfo.vision when name heuristic misses", async () => {
- const name = await findVisionModelName(["umans/glm-5.2", "umans/llama-vision"], getInfo);
+ const name = await findVisionModelName(
+ ["umans/umans-glm-5.2", "umans/llama-vision"],
+ getInfo,
+ );
expect(name).toBe("umans/llama-vision");
});
- it("skips the excluded model", async () => {
+ it("skips the excluded model and finds the next vision model", async () => {
const name = await findVisionModelName(
- ["umans/kimi-k2.7", "umans/llama-vision"],
+ ["umans/umans-kimi-k2.7", "umans/umans-qwen3.6-35b-a3b"],
getInfo,
- "umans/kimi-k2.7",
+ "umans/umans-kimi-k2.7",
);
- expect(name).toBe("umans/llama-vision");
+ expect(name).toBe("umans/umans-qwen3.6-35b-a3b");
});
it("returns undefined when no vision model is available", async () => {
- const name = await findVisionModelName(["umans/glm-5.2"], getInfo);
+ const name = await findVisionModelName(["umans/umans-glm-5.2"], getInfo);
expect(name).toBeUndefined();
});
diff --git a/packages/vision-handoff/src/service.test.ts b/packages/vision-handoff/src/service.test.ts
index 73c647b..dc54902 100644
--- a/packages/vision-handoff/src/service.test.ts
+++ b/packages/vision-handoff/src/service.test.ts
@@ -37,23 +37,23 @@ function makeVisionProvider(
function makeDeps(overrides: Partial<VisionHandoffDeps> = {}): VisionHandoffDeps {
const visionProvider = makeVisionProvider((url) => `DESCRIPTION of ${url}`);
- const catalog = ["umans/kimi-k2.7", "umans/glm-5.2"];
+ const catalog = ["umans/umans-kimi-k2.7", "umans/umans-glm-5.2"];
const infoMap: Record<string, ModelInfo> = {
- "umans/kimi-k2.7": { id: "kimi-k2.7", vision: true },
- "umans/glm-5.2": { id: "glm-5.2" },
+ "umans/umans-kimi-k2.7": { id: "umans-kimi-k2.7", vision: true },
+ "umans/umans-glm-5.2": { id: "umans-glm-5.2" },
};
return {
credentialStore: {
listCatalog: vi.fn(async () => catalog),
getModelInfo: vi.fn(async (name: string) => infoMap[name]),
resolve: vi.fn((name: string) => {
- if (name === "umans/kimi-k2.7") return { providerId: "umans", model: "kimi-k2.7" };
- if (name === "umans/glm-5.2") return { providerId: "umans", model: "glm-5.2" };
+ if (name === "umans/umans-kimi-k2.7") return { providerId: "umans", model: "umans-kimi-k2.7" };
+ if (name === "umans/umans-glm-5.2") return { providerId: "umans", model: "umans-glm-5.2" };
return undefined;
}),
},
resolveModel: vi.fn((name: string) =>
- name === "umans/kimi-k2.7" || name === "umans/glm-5.2"
+ name === "umans/umans-kimi-k2.7" || name === "umans/umans-glm-5.2"
? { provider: visionProvider, model: name.split("/")[1] }
: undefined,
),
@@ -65,12 +65,12 @@ function makeDeps(overrides: Partial<VisionHandoffDeps> = {}): VisionHandoffDeps
describe("VisionHandoffService.isVisionCapable", () => {
it("returns true for kimi (via ModelInfo)", async () => {
const svc = createVisionHandoffService(makeDeps());
- expect(await svc.isVisionCapable("umans/kimi-k2.7")).toBe(true);
+ expect(await svc.isVisionCapable("umans/umans-kimi-k2.7")).toBe(true);
});
it("returns false for glm-5.2", async () => {
const svc = createVisionHandoffService(makeDeps());
- expect(await svc.isVisionCapable("umans/glm-5.2")).toBe(false);
+ expect(await svc.isVisionCapable("umans/umans-glm-5.2")).toBe(false);
});
it("returns false for undefined model name", async () => {
@@ -83,13 +83,13 @@ describe("VisionHandoffService.resolveVisionModel", () => {
it("resolves the kimi model from the catalog", async () => {
const svc = createVisionHandoffService(makeDeps());
const vision = await svc.resolveVisionModel();
- expect(vision?.modelName).toBe("umans/kimi-k2.7");
- expect(vision?.model).toBe("kimi-k2.7");
+ expect(vision?.modelName).toBe("umans/umans-kimi-k2.7");
+ expect(vision?.model).toBe("umans-kimi-k2.7");
});
it("excludes the given model", async () => {
const svc = createVisionHandoffService(makeDeps());
- const vision = await svc.resolveVisionModel("umans/kimi-k2.7");
+ const vision = await svc.resolveVisionModel("umans/umans-kimi-k2.7");
expect(vision).toBeUndefined();
});
});
@@ -107,7 +107,7 @@ describe("VisionHandoffService.prepareForProvider", () => {
],
},
];
- const result = await svc.prepareForProvider(messages, "umans/kimi-k2.7");
+ const result = await svc.prepareForProvider(messages, "umans/umans-kimi-k2.7");
expect(result).toBe(messages); // same reference — no copy, no change
});
@@ -115,7 +115,7 @@ describe("VisionHandoffService.prepareForProvider", () => {
const deps = makeDeps();
const svc = createVisionHandoffService(deps);
const messages: ChatMessage[] = [{ role: "user", chunks: [{ type: "text", text: "hi" }] }];
- const result = await svc.prepareForProvider(messages, "umans/glm-5.2");
+ const result = await svc.prepareForProvider(messages, "umans/umans-glm-5.2");
expect(result).toBe(messages);
});
@@ -131,7 +131,7 @@ describe("VisionHandoffService.prepareForProvider", () => {
],
},
];
- const result = await svc.prepareForProvider(messages, "umans/glm-5.2", {
+ const result = await svc.prepareForProvider(messages, "umans/umans-glm-5.2", {
conversationId: "conv-1",
});
expect(result).toHaveLength(1);
@@ -154,7 +154,7 @@ describe("VisionHandoffService.prepareForProvider", () => {
{ role: "assistant", chunks: [{ type: "text", text: "ok" }] },
{ role: "user", chunks: [{ type: "image", url: "data:image/png;base64,b" }] },
];
- const result = await svc.prepareForProvider(messages, "umans/glm-5.2", {
+ const result = await svc.prepareForProvider(messages, "umans/umans-glm-5.2", {
conversationId: "conv-1",
});
// First image → Image 1, second → Image 2.
@@ -173,7 +173,7 @@ describe("VisionHandoffService.prepareForProvider", () => {
chunks: [{ type: "image", url: "data:image/png;base64,registered" }],
},
];
- await svc.prepareForProvider(messages, "umans/glm-5.2", { conversationId: "conv-42" });
+ await svc.prepareForProvider(messages, "umans/umans-glm-5.2", { conversationId: "conv-42" });
const img = svc.getRegisteredImage("conv-42", 1);
expect(img?.url).toBe("data:image/png;base64,registered");
});
@@ -185,7 +185,7 @@ describe("VisionHandoffService.prepareForProvider", () => {
const messages: ChatMessage[] = [
{ role: "user", chunks: [{ type: "image", url: "data:image/png;base64,abc" }] },
];
- const result = await svc.prepareForProvider(messages, "umans/glm-5.2", {
+ const result = await svc.prepareForProvider(messages, "umans/umans-glm-5.2", {
conversationId: "conv-1",
});
const text = (result[0]?.chunks[0] as { text: string }).text;
@@ -234,7 +234,7 @@ describe("VisionHandoffService.consultVision", () => {
const messages: ChatMessage[] = [
{ role: "user", chunks: [{ type: "image", url: "data:image/png;base64,img1" }] },
];
- await svc.prepareForProvider(messages, "umans/glm-5.2", { conversationId: "conv-1" });
+ await svc.prepareForProvider(messages, "umans/umans-glm-5.2", { conversationId: "conv-1" });
const result = await svc.consultVision("What error is shown?", {
conversationId: "conv-1",
@@ -251,7 +251,7 @@ describe("VisionHandoffService.consultVision", () => {
// The orchestrator was called with the vision model + the image.
expect(handleMessage).toHaveBeenCalledOnce();
const call = handleMessage.mock.calls[0]?.[0];
- expect(call.modelName).toBe("umans/kimi-k2.7");
+ expect(call.modelName).toBe("umans/umans-kimi-k2.7");
expect(call.images).toHaveLength(1);
expect(call.images?.[0]?.url).toBe("data:image/png;base64,img1");
});