





















@@ -394,14 +394,27 @@ describe("capability cli", () => {
394394});
395395}
396396397-type GatewayCall = { method?: unknown; params?: Record<string, unknown> };
397+type GatewayCall = {
398+clientName?: unknown;
399+method?: unknown;
400+mode?: unknown;
401+params?: Record<string, unknown>;
402+scopes?: unknown;
403+};
398404type CompletionCall = {
399405context?: {
400406messages?: Array<{ content?: unknown; role?: unknown }>;
401407systemPrompt?: unknown;
402408};
403409options?: { reasoning?: unknown };
404410};
411+type ImageDescribeParams = {
412+filePath?: string;
413+model?: unknown;
414+prompt?: unknown;
415+provider?: unknown;
416+timeoutMs?: unknown;
417+};
405418406419function firstGatewayCall() {
407420return mocks.callGateway.mock.calls[0]?.[0] as GatewayCall | undefined;
@@ -425,6 +438,18 @@ describe("capability cli", () => {
425438return mocks.runtime.writeJson.mock.calls[0]?.[0] as Record<string, unknown> | undefined;
426439}
427440441+function imageDescribeCall(index = 0) {
442+const calls = mocks.describeImageFile.mock.calls as unknown as Array<[ImageDescribeParams]>;
443+return calls[index]?.[0];
444+}
445+446+function firstImageDescribeWithModelCall() {
447+const calls = mocks.describeImageFileWithModel.mock.calls as unknown as Array<
448+[ImageDescribeParams]
449+>;
450+return calls[0]?.[0];
451+}
452+428453function expectModelRunDispatch(transport: "local" | "gateway", modelRef: string) {
429454if (transport === "gateway") {
430455const slash = modelRef.indexOf("/");
@@ -785,16 +810,11 @@ describe("capability cli", () => {
785810argv: ["capability", "model", "run", "--prompt", "hello", "--gateway", "--json"],
786811});
787812788-expect(mocks.callGateway).toHaveBeenCalledWith(
789-expect.objectContaining({
790-method: "agent",
791-params: expect.objectContaining({
792-cleanupBundleMcpOnRunEnd: true,
793-modelRun: true,
794-promptMode: "none",
795-}),
796-}),
797-);
813+const gatewayCall = firstGatewayCall();
814+expect(gatewayCall?.method).toBe("agent");
815+expect(gatewayCall?.params?.cleanupBundleMcpOnRunEnd).toBe(true);
816+expect(gatewayCall?.params?.modelRun).toBe(true);
817+expect(gatewayCall?.params?.promptMode).toBe("none");
798818});
799819800820it("surfaces gateway model fallback attempts in model probe JSON", async () => {
@@ -823,19 +843,14 @@ describe("capability cli", () => {
823843argv: ["capability", "model", "run", "--prompt", "hello", "--gateway", "--json"],
824844});
825845826-expect(mocks.runtime.writeJson).toHaveBeenCalledWith(
827-expect.objectContaining({
828-provider: "openai",
829-model: "gpt-4.1-mini",
830-attempts: [
831-expect.objectContaining({
832-provider: "openrouter",
833-model: "openrouter/auto",
834-reason: "model_not_found",
835-}),
836-],
837-}),
838-);
846+const payload = firstJsonOutput();
847+const attempts = payload?.attempts as Array<Record<string, unknown>>;
848+expect(payload?.provider).toBe("openai");
849+expect(payload?.model).toBe("gpt-4.1-mini");
850+expect(attempts).toHaveLength(1);
851+expect(attempts[0]?.provider).toBe("openrouter");
852+expect(attempts[0]?.model).toBe("openrouter/auto");
853+expect(attempts[0]?.reason).toBe("model_not_found");
839854});
840855841856it("requests admin scope for gateway model probes with provider/model overrides", async () => {
@@ -854,20 +869,15 @@ describe("capability cli", () => {
854869],
855870});
856871857-expect(mocks.callGateway).toHaveBeenCalledWith(
858-expect.objectContaining({
859-clientName: "gateway-client",
860-method: "agent",
861-mode: "backend",
862-scopes: ["operator.admin"],
863-params: expect.objectContaining({
864-provider: "anthropic",
865-model: "claude-haiku-4-5",
866-modelRun: true,
867-promptMode: "none",
868-}),
869-}),
870-);
872+const gatewayCall = firstGatewayCall();
873+expect(gatewayCall?.clientName).toBe("gateway-client");
874+expect(gatewayCall?.method).toBe("agent");
875+expect(gatewayCall?.mode).toBe("backend");
876+expect(gatewayCall?.scopes).toEqual(["operator.admin"]);
877+expect(gatewayCall?.params?.provider).toBe("anthropic");
878+expect(gatewayCall?.params?.model).toBe("claude-haiku-4-5");
879+expect(gatewayCall?.params?.modelRun).toBe(true);
880+expect(gatewayCall?.params?.promptMode).toBe("none");
871881});
872882873883it.each(["local", "gateway"] as const)(
@@ -879,9 +889,11 @@ describe("capability cli", () => {
879889880890await runModelRunWithModel("Anthropic/CLAUDE-OPUS-4-7", transport);
881891882-expect(mocks.loadModelCatalog).toHaveBeenCalledWith(
883-expect.objectContaining({ readOnly: true }),
884-);
892+const catalogCalls = mocks.loadModelCatalog.mock.calls as unknown as Array<
893+[{ readOnly?: unknown }]
894+>;
895+const catalogParams = catalogCalls[0]?.[0];
896+expect(catalogParams?.readOnly).toBe(true);
885897expectModelRunDispatch(transport, "anthropic/claude-opus-4-7");
886898},
887899);
@@ -930,16 +942,11 @@ describe("capability cli", () => {
930942],
931943});
932944933-expect(mocks.callGateway).toHaveBeenCalledWith(
934-expect.objectContaining({
935-method: "agent",
936-params: expect.objectContaining({
937-thinking: "high",
938-modelRun: true,
939-promptMode: "none",
940-}),
941-}),
942-);
945+const gatewayCall = firstGatewayCall();
946+expect(gatewayCall?.method).toBe("agent");
947+expect(gatewayCall?.params?.thinking).toBe("high");
948+expect(gatewayCall?.params?.modelRun).toBe(true);
949+expect(gatewayCall?.params?.promptMode).toBe("none");
943950});
944951945952it("rejects invalid model run thinking overrides before dispatch", async () => {
@@ -989,12 +996,8 @@ describe("capability cli", () => {
989996argv: ["capability", "tts", "status", "--json"],
990997});
991998992-expect(mocks.callGateway).toHaveBeenCalledWith(
993-expect.objectContaining({ method: "tts.status" }),
994-);
995-expect(mocks.runtime.writeJson).toHaveBeenCalledWith(
996-expect.objectContaining({ transport: "gateway" }),
997-);
999+expect(firstGatewayCall()?.method).toBe("tts.status");
1000+expect(firstJsonOutput()?.transport).toBe("gateway");
9981001});
999100210001003it("routes image describe through media understanding, not generation", async () => {
@@ -1003,15 +1006,13 @@ describe("capability cli", () => {
10031006argv: ["capability", "image", "describe", "--file", "photo.jpg", "--json"],
10041007});
100510081006-expect(mocks.describeImageFile).toHaveBeenCalledWith(
1007-expect.objectContaining({ filePath: expect.stringMatching(/photo\.jpg$/) }),
1008-);
1009-expect(mocks.runtime.writeJson).toHaveBeenCalledWith(
1010-expect.objectContaining({
1011-capability: "image.describe",
1012-outputs: [expect.objectContaining({ kind: "image.description" })],
1013-}),
1014-);
1009+const describeCall = imageDescribeCall();
1010+expect(path.basename(describeCall?.filePath ?? "")).toBe("photo.jpg");
1011+const output = firstJsonOutput();
1012+const outputs = output?.outputs as Array<Record<string, unknown>>;
1013+expect(output?.capability).toBe("image.describe");
1014+expect(outputs).toHaveLength(1);
1015+expect(outputs[0]?.kind).toBe("image.description");
10151016});
1016101710171018it("passes image describe prompts through media understanding", async () => {
@@ -1031,13 +1032,10 @@ describe("capability cli", () => {
10311032],
10321033});
103310341034-expect(mocks.describeImageFile).toHaveBeenCalledWith(
1035-expect.objectContaining({
1036-filePath: expect.stringMatching(/photo\.jpg$/),
1037-prompt: "Read the menu text",
1038-timeoutMs: 90000,
1039-}),
1040-);
1035+const describeCall = imageDescribeCall();
1036+expect(path.basename(describeCall?.filePath ?? "")).toBe("photo.jpg");
1037+expect(describeCall?.prompt).toBe("Read the menu text");
1038+expect(describeCall?.timeoutMs).toBe(90000);
10411039});
1042104010431041it("uses the explicit media-understanding provider for image describe model overrides", async () => {
@@ -1059,22 +1057,15 @@ describe("capability cli", () => {
10591057],
10601058});
106110591062-expect(mocks.describeImageFileWithModel).toHaveBeenCalledWith(
1063-expect.objectContaining({
1064-filePath: expect.stringMatching(/photo\.jpg$/),
1065-provider: "ollama",
1066-model: "qwen2.5vl:7b",
1067-prompt: "Count visible buttons",
1068-timeoutMs: 120000,
1069-}),
1070-);
1060+const describeCall = firstImageDescribeWithModelCall();
1061+expect(path.basename(describeCall?.filePath ?? "")).toBe("photo.jpg");
1062+expect(describeCall?.provider).toBe("ollama");
1063+expect(describeCall?.model).toBe("qwen2.5vl:7b");
1064+expect(describeCall?.prompt).toBe("Count visible buttons");
1065+expect(describeCall?.timeoutMs).toBe(120000);
10711066expect(mocks.describeImageFile).not.toHaveBeenCalled();
1072-expect(mocks.runtime.writeJson).toHaveBeenCalledWith(
1073-expect.objectContaining({
1074-provider: "ollama",
1075-model: "gpt-4.1-mini",
1076-}),
1077-);
1067+expect(firstJsonOutput()?.provider).toBe("ollama");
1068+expect(firstJsonOutput()?.model).toBe("gpt-4.1-mini");
10781069});
1079107010801071it("passes describe-many prompts to each image", async () => {
@@ -1097,22 +1088,14 @@ describe("capability cli", () => {
10971088});
1098108910991090expect(mocks.describeImageFile).toHaveBeenCalledTimes(2);
1100-expect(mocks.describeImageFile).toHaveBeenNthCalledWith(
1101-1,
1102-expect.objectContaining({
1103-filePath: expect.stringMatching(/a\.jpg$/),
1104-prompt: "Extract all visible labels",
1105-timeoutMs: 45000,
1106-}),
1107-);
1108-expect(mocks.describeImageFile).toHaveBeenNthCalledWith(
1109-2,
1110-expect.objectContaining({
1111-filePath: expect.stringMatching(/b\.jpg$/),
1112-prompt: "Extract all visible labels",
1113-timeoutMs: 45000,
1114-}),
1115-);
1091+const firstDescribe = imageDescribeCall(0);
1092+const secondDescribe = imageDescribeCall(1);
1093+expect(path.basename(firstDescribe?.filePath ?? "")).toBe("a.jpg");
1094+expect(firstDescribe?.prompt).toBe("Extract all visible labels");
1095+expect(firstDescribe?.timeoutMs).toBe(45000);
1096+expect(path.basename(secondDescribe?.filePath ?? "")).toBe("b.jpg");
1097+expect(secondDescribe?.prompt).toBe("Extract all visible labels");
1098+expect(secondDescribe?.timeoutMs).toBe(45000);
11161099});
1117110011181101it("fails image describe when no description text is returned", async () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。