



















@@ -394,23 +394,47 @@ describe("capability cli", () => {
394394});
395395}
396396397+type GatewayCall = { method?: unknown; params?: Record<string, unknown> };
398+type CompletionCall = {
399+context?: {
400+messages?: Array<{ content?: unknown; role?: unknown }>;
401+systemPrompt?: unknown;
402+};
403+options?: { reasoning?: unknown };
404+};
405+406+function firstGatewayCall() {
407+return mocks.callGateway.mock.calls[0]?.[0] as GatewayCall | undefined;
408+}
409+410+function firstCompletionCall() {
411+const calls = mocks.completeWithPreparedSimpleCompletionModel.mock.calls as unknown as Array<
412+[CompletionCall]
413+>;
414+return calls[0]?.[0];
415+}
416+417+function firstPreparedModelParams() {
418+const calls = mocks.prepareSimpleCompletionModelForAgent.mock.calls as unknown as Array<
419+[Record<string, unknown>]
420+>;
421+return calls[0]?.[0];
422+}
423+424+function firstJsonOutput() {
425+return mocks.runtime.writeJson.mock.calls[0]?.[0] as Record<string, unknown> | undefined;
426+}
427+397428function expectModelRunDispatch(transport: "local" | "gateway", modelRef: string) {
398429if (transport === "gateway") {
399430const slash = modelRef.indexOf("/");
400-expect(mocks.callGateway).toHaveBeenCalledWith(
401-expect.objectContaining({
402-method: "agent",
403-params: expect.objectContaining({
404-provider: modelRef.slice(0, slash),
405-model: modelRef.slice(slash + 1),
406-}),
407-}),
408-);
431+const gatewayCall = firstGatewayCall();
432+expect(gatewayCall?.method).toBe("agent");
433+expect(gatewayCall?.params?.provider).toBe(modelRef.slice(0, slash));
434+expect(gatewayCall?.params?.model).toBe(modelRef.slice(slash + 1));
409435return;
410436}
411-expect(mocks.prepareSimpleCompletionModelForAgent).toHaveBeenCalledWith(
412-expect.objectContaining({ modelRef }),
413-);
437+expect(firstPreparedModelParams()?.modelRef).toBe(modelRef);
414438}
415439416440it("lists canonical capabilities", async () => {
@@ -420,9 +444,9 @@ describe("capability cli", () => {
420444});
421445422446const payload = mocks.runtime.writeJson.mock.calls[0]?.[0] as Array<{ id: string }>;
423-expect(payload.map((entry) => entry.id)).toEqual(
424- expect.arrayContaining(["model.run", "image.describe"]),
425-);
447+const ids = payload.map((entry) => entry.id);
448+expect(ids).toContain("model.run");
449+expect(ids).toContain("image.describe");
426450});
427451428452it("defaults model run to local transport", async () => {
@@ -434,12 +458,8 @@ describe("capability cli", () => {
434458expect(mocks.prepareSimpleCompletionModelForAgent).toHaveBeenCalledTimes(1);
435459expect(mocks.completeWithPreparedSimpleCompletionModel).toHaveBeenCalledTimes(1);
436460expect(mocks.callGateway).not.toHaveBeenCalled();
437-expect(mocks.runtime.writeJson).toHaveBeenCalledWith(
438-expect.objectContaining({
439-capability: "model.run",
440-transport: "local",
441-}),
442-);
461+expect(firstJsonOutput()?.capability).toBe("model.run");
462+expect(firstJsonOutput()?.transport).toBe("local");
443463});
444464445465it("runs local model probes through the lean completion path", async () => {
@@ -448,42 +468,23 @@ describe("capability cli", () => {
448468argv: ["capability", "model", "run", "--prompt", "hello", "--json"],
449469});
450470451-expect(mocks.prepareSimpleCompletionModelForAgent).toHaveBeenCalledWith(
452-expect.objectContaining({
453-agentId: "main",
454-allowMissingApiKeyModes: ["aws-sdk"],
455-skipPiDiscovery: true,
456-}),
457-);
458-expect(mocks.completeWithPreparedSimpleCompletionModel).toHaveBeenCalledWith(
459-expect.objectContaining({
460-context: expect.objectContaining({
461-messages: [
462-expect.objectContaining({
463-role: "user",
464-content: "hello",
465-}),
466-],
467-}),
468-}),
469-);
470-const calls = mocks.completeWithPreparedSimpleCompletionModel.mock.calls as unknown as Array<
471-[{ context?: { systemPrompt?: string } }]
472->;
473-const call = calls[0]?.[0];
474-expect(call.context).not.toHaveProperty("systemPrompt");
471+const preparedParams = firstPreparedModelParams();
472+expect(preparedParams?.agentId).toBe("main");
473+expect(preparedParams?.allowMissingApiKeyModes).toEqual(["aws-sdk"]);
474+expect(preparedParams?.skipPiDiscovery).toBe(true);
475+const call = firstCompletionCall();
476+expect(call?.context?.messages?.[0]?.role).toBe("user");
477+expect(call?.context?.messages?.[0]?.content).toBe("hello");
478+expect(call?.context).not.toHaveProperty("systemPrompt");
475479});
476480477481it("opts explicit local provider/model probes into bundled static catalog fallback", async () => {
478482await runModelRunWithModel("mistral/mistral-medium-3-5", "local");
479483480-expect(mocks.prepareSimpleCompletionModelForAgent).toHaveBeenCalledWith(
481-expect.objectContaining({
482-modelRef: "mistral/mistral-medium-3-5",
483-allowBundledStaticCatalogFallback: true,
484-skipPiDiscovery: true,
485-}),
486-);
484+const params = firstPreparedModelParams();
485+expect(params?.modelRef).toBe("mistral/mistral-medium-3-5");
486+expect(params?.allowBundledStaticCatalogFallback).toBe(true);
487+expect(params?.skipPiDiscovery).toBe(true);
487488});
488489489490it("does not enable bundled static catalog fallback without an explicit provider/model override", async () => {
@@ -518,36 +519,17 @@ describe("capability cli", () => {
518519],
519520});
520521521-expect(mocks.completeWithPreparedSimpleCompletionModel).toHaveBeenCalledWith(
522-expect.objectContaining({
523-context: expect.objectContaining({
524-messages: [
525-expect.objectContaining({
526-role: "user",
527-content: [
528-{ type: "text", text: "describe this" },
529-{ type: "image", data: PNG_1X1_BASE64, mimeType: "image/png" },
530-],
531-}),
532-],
533-}),
534-}),
535-);
536-const calls = mocks.completeWithPreparedSimpleCompletionModel.mock.calls as unknown as Array<
537-[{ context?: { systemPrompt?: string } }]
538->;
539-const call = calls[0]?.[0];
540-expect(call.context).not.toHaveProperty("systemPrompt");
541-expect(mocks.runtime.writeJson).toHaveBeenCalledWith(
542-expect.objectContaining({
543-inputs: [
544-expect.objectContaining({
545-path: tempInput,
546-mimeType: "image/png",
547-}),
548-],
549-}),
550-);
522+const call = firstCompletionCall();
523+expect(call?.context?.messages?.[0]?.role).toBe("user");
524+expect(call?.context?.messages?.[0]?.content).toEqual([
525+{ type: "text", text: "describe this" },
526+{ type: "image", data: PNG_1X1_BASE64, mimeType: "image/png" },
527+]);
528+expect(call?.context).not.toHaveProperty("systemPrompt");
529+const inputs = firstJsonOutput()?.inputs as Array<{ mimeType?: unknown; path?: unknown }>;
530+expect(inputs).toHaveLength(1);
531+expect(inputs[0]?.path).toBe(tempInput);
532+expect(inputs[0]?.mimeType).toBe("image/png");
551533});
552534553535it("adds minimal instructions only for openai-codex local model probes", async () => {
@@ -584,19 +566,12 @@ describe("capability cli", () => {
584566],
585567});
586568587-expect(mocks.completeWithPreparedSimpleCompletionModel).toHaveBeenCalledWith(
588-expect.objectContaining({
589-context: expect.objectContaining({
590-systemPrompt: "You are a personal assistant running inside OpenClaw.",
591-messages: [
592-expect.objectContaining({
593-role: "user",
594-content: "hello",
595-}),
596-],
597-}),
598-}),
569+const call = firstCompletionCall();
570+expect(call?.context?.systemPrompt).toBe(
571+"You are a personal assistant running inside OpenClaw.",
599572);
573+expect(call?.context?.messages?.[0]?.role).toBe("user");
574+expect(call?.context?.messages?.[0]?.content).toBe("hello");
600575});
601576602577it("passes thinking overrides to local model probes", async () => {
@@ -605,13 +580,7 @@ describe("capability cli", () => {
605580argv: ["capability", "model", "run", "--prompt", "hello", "--thinking", "high", "--json"],
606581});
607582608-expect(mocks.completeWithPreparedSimpleCompletionModel).toHaveBeenCalledWith(
609-expect.objectContaining({
610-options: expect.objectContaining({
611-reasoning: "high",
612-}),
613-}),
614-);
583+expect(firstCompletionCall()?.options?.reasoning).toBe("high");
615584});
616585617586it("passes image files to gateway model probes as attachments", async () => {
@@ -633,24 +602,19 @@ describe("capability cli", () => {
633602],
634603});
635604636-expect(mocks.callGateway).toHaveBeenCalledWith(
637-expect.objectContaining({
638-method: "agent",
639-params: expect.objectContaining({
640-message: "describe this",
641-attachments: [
642-{
643-type: "image",
644-fileName: path.basename(tempInput),
645-mimeType: "image/png",
646-content: PNG_1X1_BASE64,
647-},
648-],
649-modelRun: true,
650-promptMode: "none",
651-}),
652-}),
653-);
605+const gatewayCall = firstGatewayCall();
606+expect(gatewayCall?.method).toBe("agent");
607+expect(gatewayCall?.params?.message).toBe("describe this");
608+expect(gatewayCall?.params?.attachments).toEqual([
609+{
610+type: "image",
611+fileName: path.basename(tempInput),
612+mimeType: "image/png",
613+content: PNG_1X1_BASE64,
614+},
615+]);
616+expect(gatewayCall?.params?.modelRun).toBe(true);
617+expect(gatewayCall?.params?.promptMode).toBe("none");
654618});
655619656620it("normalizes HEIC files to JPEG before local model probes", async () => {
@@ -672,35 +636,20 @@ describe("capability cli", () => {
672636});
673637674638expect(mocks.convertHeicToJpeg).toHaveBeenCalledWith(Buffer.from("heic-like"));
675-expect(mocks.completeWithPreparedSimpleCompletionModel).toHaveBeenCalledWith(
676-expect.objectContaining({
677-context: expect.objectContaining({
678-messages: [
679-expect.objectContaining({
680-role: "user",
681-content: [
682-{ type: "text", text: "describe this" },
683-{
684-type: "image",
685-data: Buffer.from("jpeg-normalized").toString("base64"),
686-mimeType: "image/jpeg",
687-},
688-],
689-}),
690-],
691-}),
692-}),
693-);
694-expect(mocks.runtime.writeJson).toHaveBeenCalledWith(
695-expect.objectContaining({
696-inputs: [
697-expect.objectContaining({
698-path: tempInput,
699-mimeType: "image/jpeg",
700-}),
701-],
702-}),
703-);
639+const call = firstCompletionCall();
640+expect(call?.context?.messages?.[0]?.role).toBe("user");
641+expect(call?.context?.messages?.[0]?.content).toEqual([
642+{ type: "text", text: "describe this" },
643+{
644+type: "image",
645+data: Buffer.from("jpeg-normalized").toString("base64"),
646+mimeType: "image/jpeg",
647+},
648+]);
649+const inputs = firstJsonOutput()?.inputs as Array<{ mimeType?: unknown; path?: unknown }>;
650+expect(inputs).toHaveLength(1);
651+expect(inputs[0]?.path).toBe(tempInput);
652+expect(inputs[0]?.mimeType).toBe("image/jpeg");
704653});
705654706655it("rejects non-image files for model probes", async () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。