File size: 6,051 Bytes
1dbc34b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import {
  resolveModelString,
  getEffectiveModel,
  CLAUDE_MODEL_MAP,
  CURSOR_MODEL_MAP,
  DEFAULT_MODELS,
} from '@automaker/model-resolver';

describe('model-resolver.ts', () => {
  let consoleSpy: any;

  beforeEach(() => {
    consoleSpy = {
      log: vi.spyOn(console, 'log').mockImplementation(() => {}),
      warn: vi.spyOn(console, 'warn').mockImplementation(() => {}),
    };
  });

  afterEach(() => {
    consoleSpy.log.mockRestore();
    consoleSpy.warn.mockRestore();
  });

  describe('resolveModelString', () => {
    it("should resolve 'haiku' alias to full model string", () => {
      const result = resolveModelString('haiku');
      expect(result).toBe(CLAUDE_MODEL_MAP.haiku);
    });

    it("should resolve 'sonnet' alias to full model string", () => {
      const result = resolveModelString('sonnet');
      expect(result).toBe(CLAUDE_MODEL_MAP.sonnet);
    });

    it("should resolve 'opus' alias to full model string", () => {
      const result = resolveModelString('opus');
      expect(result).toBe('claude-opus-4-6');
      expect(consoleSpy.log).toHaveBeenCalledWith(
        expect.stringContaining('Migrated legacy ID: "opus" -> "claude-opus"')
      );
    });

    it('should pass through unknown models unchanged (may be provider models)', () => {
      // Unknown models now pass through unchanged to support ClaudeCompatibleProvider models
      // like GLM-4.7, MiniMax-M2.1, o1, etc.
      const models = ['o1', 'o1-mini', 'o3', 'unknown-model', 'fake-model-123', 'GLM-4.7'];
      models.forEach((model) => {
        const result = resolveModelString(model);
        // Should pass through unchanged (could be provider models)
        expect(result).toBe(model);
      });
    });

    it('should pass through full Claude model strings', () => {
      const models = [CLAUDE_MODEL_MAP.opus, CLAUDE_MODEL_MAP.sonnet, CLAUDE_MODEL_MAP.haiku];
      models.forEach((model) => {
        const result = resolveModelString(model);
        expect(result).toBe(model);
      });
      expect(consoleSpy.log).toHaveBeenCalledWith(
        expect.stringContaining('Using full Claude model string')
      );
    });

    it('should return default model when modelKey is undefined', () => {
      const result = resolveModelString(undefined);
      expect(result).toBe(DEFAULT_MODELS.claude);
    });

    it('should return custom default model when provided', () => {
      const customDefault = 'custom-model';
      const result = resolveModelString(undefined, customDefault);
      expect(result).toBe(customDefault);
    });

    it('should pass through unknown model key unchanged (no warning)', () => {
      const result = resolveModelString('unknown-model');
      // Unknown models pass through unchanged (could be provider models)
      expect(result).toBe('unknown-model');
      // No warning - unknown models are valid for providers
      expect(consoleSpy.warn).not.toHaveBeenCalled();
    });

    it('should handle empty string', () => {
      const result = resolveModelString('');
      expect(result).toBe(DEFAULT_MODELS.claude);
    });

    describe('Cursor models', () => {
      it('should pass through cursor-prefixed models unchanged', () => {
        const result = resolveModelString('cursor-composer-1');
        expect(result).toBe('cursor-composer-1');
        expect(consoleSpy.log).toHaveBeenCalledWith(expect.stringContaining('Using Cursor model'));
      });

      it('should add cursor- prefix to bare Cursor model IDs', () => {
        const result = resolveModelString('composer-1');
        expect(result).toBe('cursor-composer-1');
      });

      it('should handle cursor-auto model', () => {
        const result = resolveModelString('cursor-auto');
        expect(result).toBe('cursor-auto');
      });

      it('should handle all known Cursor model IDs with prefix', () => {
        const cursorModelIds = Object.keys(CURSOR_MODEL_MAP);
        cursorModelIds.forEach((modelId) => {
          const result = resolveModelString(`cursor-${modelId}`);
          expect(result).toBe(`cursor-${modelId}`);
        });
      });
    });
  });

  describe('getEffectiveModel', () => {
    it('should prioritize explicit model over session and default', () => {
      const result = getEffectiveModel('opus', 'haiku', 'gpt-5.2');
      expect(result).toBe('claude-opus-4-6');
    });

    it('should use session model when explicit is not provided', () => {
      const result = getEffectiveModel(undefined, 'sonnet', 'gpt-5.2');
      expect(result).toBe(CLAUDE_MODEL_MAP.sonnet);
    });

    it('should use default when neither explicit nor session is provided', () => {
      const customDefault = CLAUDE_MODEL_MAP.haiku;
      const result = getEffectiveModel(undefined, undefined, customDefault);
      expect(result).toBe(customDefault);
    });

    it('should use Claude default when no arguments provided', () => {
      const result = getEffectiveModel();
      expect(result).toBe(DEFAULT_MODELS.claude);
    });

    it('should handle explicit empty strings as undefined', () => {
      const result = getEffectiveModel('', 'haiku');
      expect(result).toBe(CLAUDE_MODEL_MAP.haiku);
    });
  });

  describe('CLAUDE_MODEL_MAP', () => {
    it('should have haiku, sonnet, opus mappings', () => {
      expect(CLAUDE_MODEL_MAP).toHaveProperty('haiku');
      expect(CLAUDE_MODEL_MAP).toHaveProperty('sonnet');
      expect(CLAUDE_MODEL_MAP).toHaveProperty('opus');
    });

    it('should have valid Claude model strings', () => {
      expect(CLAUDE_MODEL_MAP.haiku).toContain('haiku');
      expect(CLAUDE_MODEL_MAP.sonnet).toContain('sonnet');
      expect(CLAUDE_MODEL_MAP.opus).toContain('opus');
    });
  });

  describe('DEFAULT_MODELS', () => {
    it('should have claude default', () => {
      expect(DEFAULT_MODELS).toHaveProperty('claude');
    });

    it('should have valid default model', () => {
      expect(DEFAULT_MODELS.claude).toContain('claude');
    });
  });
});