feat(qoder): refresh model catalog, add capability mapping and image pass-through

- Registry/constants: drop qmodel_preview/gm51model, add lite,
  qmodel_38max (Qwen3.8-Max), qfmodel (Qwen3.8-Flash), gmodel (GLM-5.3),
  gfmodel (GLM-5.3-Flash)
- capabilities: add PROVIDER_CAPABILITIES['qoder'] so opaque internal
  ids resolve to their real models' context windows and limits
- executor: preserve image blocks instead of flattening away, convert
  Claude-style image blocks, and hash images into chat_record_id
- tests: cover image preservation, data-URI and Claude-block conversion
- build(docker): use CN mirrors for apk and npm
This commit is contained in:
hangyu
2026-09-03 23:01:53 +07:00
parent c08efdbe2b
commit 2ab6a4c949
6 changed files with 176 additions and 11 deletions

View File

@@ -367,6 +367,70 @@ describe("normalizeMessages", () => {
expect(result.messages).toEqual([]);
expect(result.systemText).toBe("");
});
it("preserves image_url blocks (http URL) instead of dropping them", () => {
const result = normalizeMessages([
{
role: "user",
content: [
{ type: "text", text: "describe" },
{ type: "image_url", image_url: { url: "https://example.com/a.png" } },
],
},
]);
const content = result.messages[0].content;
expect(Array.isArray(content)).toBe(true);
expect(content).toContainEqual({ type: "text", text: "describe" });
expect(content).toContainEqual({ type: "image_url", image_url: { url: "https://example.com/a.png" } });
});
it("preserves base64 data: URI images (no OSS upload needed)", () => {
const dataUri = "data:image/png;base64,iVBORw0KGgo=";
const result = normalizeMessages([
{
role: "user",
content: [
{ type: "image_url", image_url: { url: dataUri } },
{ type: "text", text: "what color?" },
],
},
]);
const content = result.messages[0].content;
expect(Array.isArray(content)).toBe(true);
expect(content[0]).toEqual({ type: "image_url", image_url: { url: dataUri } });
expect(content.some((b) => b.type === "text" && b.text === "what color?")).toBe(true);
});
it("converts claude-style base64 image blocks to image_url data URIs", () => {
const result = normalizeMessages([
{
role: "user",
content: [
{ type: "text", text: "see this" },
{ type: "image", source: { type: "base64", media_type: "image/jpeg", data: "AAAA" } },
],
},
]);
const content = result.messages[0].content;
expect(content).toContainEqual({
type: "image_url",
image_url: { url: "data:image/jpeg;base64,AAAA" },
});
});
it("drops image blocks with no usable url but keeps the text", () => {
const result = normalizeMessages([
{
role: "user",
content: [
{ type: "text", text: "hi" },
{ type: "image_url", image_url: {} },
{ type: "image", source: { type: "base64" } },
],
},
]);
expect(result.messages[0].content).toBe("hi");
});
});
describe("wrapQoderSSE", () => {