Matt Rubens 1 year ago
parent
commit
16495560d7
1 changed files with 129 additions and 1 deletions
  1. 129 1
      src/core/__tests__/Cline.test.ts

+ 129 - 1
src/core/__tests__/Cline.test.ts

@@ -1,7 +1,8 @@
 import { Cline } from '../Cline';
 import { ClineProvider } from '../webview/ClineProvider';
-import { ApiConfiguration } from '../../shared/api';
+import { ApiConfiguration, ModelInfo } from '../../shared/api';
 import { ApiStreamChunk } from '../../api/transform/stream';
+import { Anthropic } from '@anthropic-ai/sdk';
 import * as vscode from 'vscode';
 
 // Mock all MCP-related modules
@@ -498,6 +499,133 @@ describe('Cline', () => {
                 expect(passedMessage).not.toHaveProperty('ts');
                 expect(passedMessage).not.toHaveProperty('extraProp');
             });
+
+            it('should handle image blocks based on model capabilities', async () => {
+                // Create two configurations - one with image support, one without
+                const configWithImages = {
+                    ...mockApiConfig,
+                    apiModelId: 'claude-3-sonnet'
+                };
+                const configWithoutImages = {
+                    ...mockApiConfig,
+                    apiModelId: 'gpt-3.5-turbo'
+                };
+
+                // Create test conversation history with mixed content
+                const conversationHistory: (Anthropic.MessageParam & { ts?: number })[] = [
+                    {
+                        role: 'user' as const,
+                        content: [
+                            {
+                                type: 'text' as const,
+                                text: 'Here is an image'
+                            } satisfies Anthropic.TextBlockParam,
+                            {
+                                type: 'image' as const,
+                                source: {
+                                    type: 'base64' as const,
+                                    media_type: 'image/jpeg',
+                                    data: 'base64data'
+                                }
+                            } satisfies Anthropic.ImageBlockParam
+                        ]
+                    },
+                    {
+                        role: 'assistant' as const,
+                        content: [{
+                            type: 'text' as const,
+                            text: 'I see the image'
+                        } satisfies Anthropic.TextBlockParam]
+                    }
+                ];
+
+                // Test with model that supports images
+                const clineWithImages = new Cline(
+                    mockProvider,
+                    configWithImages,
+                    undefined,
+                    false,
+                    undefined,
+                    'test task'
+                );
+                // Mock the model info to indicate image support
+                jest.spyOn(clineWithImages.api, 'getModel').mockReturnValue({
+                    id: 'claude-3-sonnet',
+                    info: {
+                        supportsImages: true,
+                        supportsPromptCache: true,
+                        supportsComputerUse: true,
+                        contextWindow: 200000,
+                        maxTokens: 4096,
+                        inputPrice: 0.25,
+                        outputPrice: 0.75
+                    } as ModelInfo
+                });
+                clineWithImages.apiConversationHistory = conversationHistory;
+
+                // Test with model that doesn't support images
+                const clineWithoutImages = new Cline(
+                    mockProvider,
+                    configWithoutImages,
+                    undefined,
+                    false,
+                    undefined,
+                    'test task'
+                );
+                // Mock the model info to indicate no image support
+                jest.spyOn(clineWithoutImages.api, 'getModel').mockReturnValue({
+                    id: 'gpt-3.5-turbo',
+                    info: {
+                        supportsImages: false,
+                        supportsPromptCache: false,
+                        supportsComputerUse: false,
+                        contextWindow: 16000,
+                        maxTokens: 2048,
+                        inputPrice: 0.1,
+                        outputPrice: 0.2
+                    } as ModelInfo
+                });
+                clineWithoutImages.apiConversationHistory = conversationHistory;
+
+                // Create message spy for both instances
+                const createMessageSpyWithImages = jest.fn();
+                const createMessageSpyWithoutImages = jest.fn();
+                const mockStream = {
+                    async *[Symbol.asyncIterator]() {
+                        yield { type: 'text', text: '' };
+                    }
+                } as AsyncGenerator<ApiStreamChunk>;
+
+                jest.spyOn(clineWithImages.api, 'createMessage').mockImplementation((...args) => {
+                    createMessageSpyWithImages(...args);
+                    return mockStream;
+                });
+                jest.spyOn(clineWithoutImages.api, 'createMessage').mockImplementation((...args) => {
+                    createMessageSpyWithoutImages(...args);
+                    return mockStream;
+                });
+
+                // Trigger API requests for both instances
+                await clineWithImages.recursivelyMakeClineRequests([{ type: 'text', text: 'test' }]);
+                await clineWithoutImages.recursivelyMakeClineRequests([{ type: 'text', text: 'test' }]);
+
+                // Verify model with image support preserves image blocks
+                const callsWithImages = createMessageSpyWithImages.mock.calls;
+                const historyWithImages = callsWithImages[0][1][0];
+                expect(historyWithImages.content).toHaveLength(2);
+                expect(historyWithImages.content[0]).toEqual({ type: 'text', text: 'Here is an image' });
+                expect(historyWithImages.content[1]).toHaveProperty('type', 'image');
+
+                // Verify model without image support converts image blocks to text
+                const callsWithoutImages = createMessageSpyWithoutImages.mock.calls;
+                const historyWithoutImages = callsWithoutImages[0][1][0];
+                expect(historyWithoutImages.content).toHaveLength(2);
+                expect(historyWithoutImages.content[0]).toEqual({ type: 'text', text: 'Here is an image' });
+                expect(historyWithoutImages.content[1]).toEqual({
+                    type: 'text',
+                    text: '[Referenced image in conversation]'
+                });
+            });
         });
     });
 });