|
1 | 1 | import type { Mock } from "vitest" |
2 | | -import { checkModelSupportsImages, IMAGE_CAPABLE_MODEL_PREFIXES } from "../vscode-lm" |
| 2 | +import { checkModelSupportsImages, IMAGE_CAPABLE_MODEL_PATTERNS, IMAGE_INCAPABLE_MODEL_PATTERNS } from "../vscode-lm" |
3 | 3 |
|
4 | 4 | // Mocks must come first, before imports |
5 | 5 | vi.mock("vscode", () => { |
@@ -540,90 +540,132 @@ describe("VsCodeLmHandler", () => { |
540 | 540 | }) |
541 | 541 |
|
542 | 542 | describe("checkModelSupportsImages", () => { |
543 | | - describe("OpenAI GPT models", () => { |
544 | | - it("should return true for all gpt-* models (GitHub Copilot)", () => { |
545 | | - // All GPT models in GitHub Copilot support images |
546 | | - expect(checkModelSupportsImages("gpt", "gpt-4o")).toBe(true) |
547 | | - expect(checkModelSupportsImages("gpt", "gpt-4.1")).toBe(true) |
548 | | - expect(checkModelSupportsImages("gpt", "gpt-5")).toBe(true) |
549 | | - expect(checkModelSupportsImages("gpt", "gpt-5.1")).toBe(true) |
550 | | - expect(checkModelSupportsImages("gpt", "gpt-5.2")).toBe(true) |
551 | | - expect(checkModelSupportsImages("gpt-mini", "gpt-5-mini")).toBe(true) |
552 | | - expect(checkModelSupportsImages("gpt-codex", "gpt-5.1-codex")).toBe(true) |
553 | | - expect(checkModelSupportsImages("gpt-codex", "gpt-5.2-codex")).toBe(true) |
554 | | - expect(checkModelSupportsImages("gpt-codex", "gpt-5.1-codex-max")).toBe(true) |
555 | | - expect(checkModelSupportsImages("gpt-codex", "gpt-5.1-codex-mini")).toBe(true) |
556 | | - }) |
557 | | - |
558 | | - it("should return true for o1 and o3 reasoning models", () => { |
559 | | - expect(checkModelSupportsImages("o1", "o1-preview")).toBe(true) |
560 | | - expect(checkModelSupportsImages("o1", "o1-mini")).toBe(true) |
561 | | - expect(checkModelSupportsImages("o3", "o3")).toBe(true) |
| 543 | + describe("static vscodeLlmModels lookup", () => { |
| 544 | + it("should return supportsImages from static definitions when model family matches", () => { |
| 545 | + // Models in vscodeLlmModels should return their static supportsImages value |
| 546 | + expect(checkModelSupportsImages("gpt-3.5-turbo", "gpt-3.5-turbo")).toBe(false) |
| 547 | + expect(checkModelSupportsImages("gpt-4", "gpt-4")).toBe(false) |
| 548 | + expect(checkModelSupportsImages("gpt-4o-mini", "gpt-4o-mini")).toBe(false) |
| 549 | + expect(checkModelSupportsImages("gpt-4o", "gpt-4o")).toBe(true) |
| 550 | + expect(checkModelSupportsImages("gpt-4.1", "gpt-4.1")).toBe(true) |
| 551 | + expect(checkModelSupportsImages("gpt-5", "gpt-5")).toBe(true) |
| 552 | + expect(checkModelSupportsImages("gpt-5-mini", "gpt-5-mini")).toBe(true) |
| 553 | + expect(checkModelSupportsImages("o1", "o1")).toBe(false) |
| 554 | + expect(checkModelSupportsImages("o3-mini", "o3-mini")).toBe(false) |
| 555 | + expect(checkModelSupportsImages("o4-mini", "o4-mini")).toBe(false) |
| 556 | + }) |
| 557 | + |
| 558 | + it("should return supportsImages from static definitions for claude models", () => { |
| 559 | + expect(checkModelSupportsImages("claude-3.5-sonnet", "claude-3.5-sonnet")).toBe(true) |
| 560 | + expect(checkModelSupportsImages("claude-4-sonnet", "claude-4-sonnet")).toBe(true) |
562 | 561 | }) |
563 | | - }) |
564 | 562 |
|
565 | | - describe("Anthropic Claude models", () => { |
566 | | - it("should return true for all claude-* models (GitHub Copilot)", () => { |
567 | | - // All Claude models in GitHub Copilot support images |
568 | | - expect(checkModelSupportsImages("claude-haiku", "claude-haiku-4.5")).toBe(true) |
569 | | - expect(checkModelSupportsImages("claude-opus", "claude-opus-4.5")).toBe(true) |
570 | | - expect(checkModelSupportsImages("claude-sonnet", "claude-sonnet-4")).toBe(true) |
571 | | - expect(checkModelSupportsImages("claude-sonnet", "claude-sonnet-4.5")).toBe(true) |
| 563 | + it("should return supportsImages from static definitions for gemini models", () => { |
| 564 | + expect(checkModelSupportsImages("gemini-2.0-flash-001", "gemini-2.0-flash-001")).toBe(true) |
| 565 | + expect(checkModelSupportsImages("gemini-2.5-pro", "gemini-2.5-pro")).toBe(true) |
572 | 566 | }) |
573 | 567 | }) |
574 | 568 |
|
575 | | - describe("Google Gemini models", () => { |
576 | | - it("should return true for all gemini-* models (GitHub Copilot)", () => { |
577 | | - // All Gemini models in GitHub Copilot support images |
578 | | - expect(checkModelSupportsImages("gemini-pro", "gemini-2.5-pro")).toBe(true) |
579 | | - expect(checkModelSupportsImages("gemini-flash", "gemini-3-flash-preview")).toBe(true) |
580 | | - expect(checkModelSupportsImages("gemini-pro", "gemini-3-pro-preview")).toBe(true) |
| 569 | + describe("pattern matching for unknown models", () => { |
| 570 | + it("should return true for gpt-4o (but not gpt-4o-mini)", () => { |
| 571 | + expect(checkModelSupportsImages("custom", "gpt-4o")).toBe(true) |
| 572 | + expect(checkModelSupportsImages("custom", "gpt-4o-mini")).toBe(false) |
| 573 | + }) |
| 574 | + |
| 575 | + it("should return true for gpt-4.x and higher versions", () => { |
| 576 | + expect(checkModelSupportsImages("custom", "gpt-4.1-preview")).toBe(true) |
| 577 | + expect(checkModelSupportsImages("custom", "gpt-4.2")).toBe(true) |
| 578 | + }) |
| 579 | + |
| 580 | + it("should return true for gpt-5 and higher (unknown variants)", () => { |
| 581 | + expect(checkModelSupportsImages("custom", "gpt-5-turbo")).toBe(true) |
| 582 | + expect(checkModelSupportsImages("custom", "gpt-6")).toBe(true) |
| 583 | + }) |
| 584 | + |
| 585 | + it("should return true for all claude-* models", () => { |
| 586 | + expect(checkModelSupportsImages("custom", "claude-haiku-4.5")).toBe(true) |
| 587 | + expect(checkModelSupportsImages("custom", "claude-opus-4.5")).toBe(true) |
| 588 | + expect(checkModelSupportsImages("custom", "claude-sonnet-4")).toBe(true) |
| 589 | + }) |
| 590 | + |
| 591 | + it("should return true for all gemini-* models", () => { |
| 592 | + expect(checkModelSupportsImages("custom", "gemini-2.5-pro")).toBe(true) |
| 593 | + expect(checkModelSupportsImages("custom", "gemini-3-flash-preview")).toBe(true) |
581 | 594 | }) |
582 | 595 | }) |
583 | 596 |
|
584 | 597 | describe("non-vision models", () => { |
585 | | - it("should return false for grok models (text-only in GitHub Copilot)", () => { |
586 | | - // Grok is the only model family in GitHub Copilot that doesn't support images |
587 | | - expect(checkModelSupportsImages("grok", "grok-code-fast-1")).toBe(false) |
| 598 | + it("should return false for gpt-3.5 models", () => { |
| 599 | + expect(checkModelSupportsImages("custom", "gpt-3.5-turbo")).toBe(false) |
| 600 | + expect(checkModelSupportsImages("custom", "gpt-3.5-turbo-16k")).toBe(false) |
| 601 | + }) |
| 602 | + |
| 603 | + it("should return false for base gpt-4 and gpt-4-* variants", () => { |
| 604 | + expect(checkModelSupportsImages("custom", "gpt-4")).toBe(false) |
| 605 | + expect(checkModelSupportsImages("custom", "gpt-4-0125-preview")).toBe(false) |
| 606 | + expect(checkModelSupportsImages("custom", "gpt-4-turbo")).toBe(false) |
| 607 | + }) |
| 608 | + |
| 609 | + it("should return false for reasoning models (o1, o3-mini, o4-mini)", () => { |
| 610 | + expect(checkModelSupportsImages("custom", "o1")).toBe(false) |
| 611 | + expect(checkModelSupportsImages("custom", "o1-preview")).toBe(false) |
| 612 | + expect(checkModelSupportsImages("custom", "o1-mini")).toBe(false) |
| 613 | + expect(checkModelSupportsImages("custom", "o3-mini")).toBe(false) |
| 614 | + expect(checkModelSupportsImages("custom", "o4-mini")).toBe(false) |
588 | 615 | }) |
589 | 616 |
|
590 | | - it("should return false for models with non-matching prefixes", () => { |
591 | | - // Models that don't start with gpt, claude, gemini, o1, or o3 |
| 617 | + it("should return false for grok models", () => { |
| 618 | + expect(checkModelSupportsImages("custom", "grok-code-fast-1")).toBe(false) |
| 619 | + expect(checkModelSupportsImages("custom", "grok-2")).toBe(false) |
| 620 | + }) |
| 621 | + |
| 622 | + it("should return false for unknown model families", () => { |
592 | 623 | expect(checkModelSupportsImages("mistral", "mistral-large")).toBe(false) |
593 | 624 | expect(checkModelSupportsImages("llama", "llama-3-70b")).toBe(false) |
594 | 625 | expect(checkModelSupportsImages("unknown", "some-random-model")).toBe(false) |
595 | 626 | }) |
596 | 627 | }) |
597 | 628 |
|
598 | 629 | describe("case insensitivity", () => { |
599 | | - it("should match regardless of case", () => { |
600 | | - expect(checkModelSupportsImages("GPT", "GPT-4O")).toBe(true) |
601 | | - expect(checkModelSupportsImages("CLAUDE", "CLAUDE-SONNET-4")).toBe(true) |
602 | | - expect(checkModelSupportsImages("GEMINI", "GEMINI-2.5-PRO")).toBe(true) |
| 630 | + it("should match regardless of case for pattern matching", () => { |
| 631 | + expect(checkModelSupportsImages("custom", "GPT-4O")).toBe(true) |
| 632 | + expect(checkModelSupportsImages("custom", "CLAUDE-SONNET-4")).toBe(true) |
| 633 | + expect(checkModelSupportsImages("custom", "GEMINI-2.5-PRO")).toBe(true) |
603 | 634 | }) |
604 | 635 | }) |
605 | 636 |
|
606 | | - describe("prefix matching", () => { |
607 | | - it("should only match IDs that start with known prefixes", () => { |
608 | | - // ID must START with the prefix, not just contain it |
609 | | - expect(checkModelSupportsImages("custom", "gpt-4o")).toBe(true) // ID starts with gpt |
610 | | - expect(checkModelSupportsImages("custom", "my-gpt-model")).toBe(false) // gpt not at start |
| 637 | + describe("pattern matching edge cases", () => { |
| 638 | + it("should only match IDs that start with known patterns", () => { |
| 639 | + expect(checkModelSupportsImages("custom", "my-gpt-4o-model")).toBe(false) // gpt not at start |
611 | 640 | expect(checkModelSupportsImages("custom", "not-claude-model")).toBe(false) // claude not at start |
612 | 641 | }) |
613 | 642 | }) |
614 | 643 | }) |
615 | 644 |
|
616 | | -describe("IMAGE_CAPABLE_MODEL_PREFIXES", () => { |
617 | | - it("should export the model prefixes array", () => { |
618 | | - expect(Array.isArray(IMAGE_CAPABLE_MODEL_PREFIXES)).toBe(true) |
619 | | - expect(IMAGE_CAPABLE_MODEL_PREFIXES.length).toBeGreaterThan(0) |
| 645 | +describe("IMAGE_CAPABLE_MODEL_PATTERNS", () => { |
| 646 | + it("should export the model patterns array", () => { |
| 647 | + expect(Array.isArray(IMAGE_CAPABLE_MODEL_PATTERNS)).toBe(true) |
| 648 | + expect(IMAGE_CAPABLE_MODEL_PATTERNS.length).toBeGreaterThan(0) |
| 649 | + }) |
| 650 | + |
| 651 | + it("should contain RegExp patterns for vision-capable models", () => { |
| 652 | + // All patterns should be RegExp instances |
| 653 | + IMAGE_CAPABLE_MODEL_PATTERNS.forEach((pattern) => { |
| 654 | + expect(pattern).toBeInstanceOf(RegExp) |
| 655 | + }) |
| 656 | + }) |
| 657 | +}) |
| 658 | + |
| 659 | +describe("IMAGE_INCAPABLE_MODEL_PATTERNS", () => { |
| 660 | + it("should export the incapable model patterns array", () => { |
| 661 | + expect(Array.isArray(IMAGE_INCAPABLE_MODEL_PATTERNS)).toBe(true) |
| 662 | + expect(IMAGE_INCAPABLE_MODEL_PATTERNS.length).toBeGreaterThan(0) |
620 | 663 | }) |
621 | 664 |
|
622 | | - it("should include key model prefixes", () => { |
623 | | - expect(IMAGE_CAPABLE_MODEL_PREFIXES).toContain("gpt") |
624 | | - expect(IMAGE_CAPABLE_MODEL_PREFIXES).toContain("claude") |
625 | | - expect(IMAGE_CAPABLE_MODEL_PREFIXES).toContain("gemini") |
626 | | - expect(IMAGE_CAPABLE_MODEL_PREFIXES).toContain("o1") |
627 | | - expect(IMAGE_CAPABLE_MODEL_PREFIXES).toContain("o3") |
| 665 | + it("should contain RegExp patterns for non-vision models", () => { |
| 666 | + // All patterns should be RegExp instances |
| 667 | + IMAGE_INCAPABLE_MODEL_PATTERNS.forEach((pattern) => { |
| 668 | + expect(pattern).toBeInstanceOf(RegExp) |
| 669 | + }) |
628 | 670 | }) |
629 | 671 | }) |
0 commit comments