Continuing work on support for OpenRouter compression (#43)

This commit is contained in:
John Stearns
2024-12-07 09:38:13 -08:00
committed by GitHub
parent 0ac3dd5753
commit 423e2af520
12 changed files with 3026 additions and 7352 deletions

View File

@@ -61,6 +61,7 @@ type GlobalStateKey =
| "azureApiVersion"
| "openRouterModelId"
| "openRouterModelInfo"
| "openRouterUseMiddleOutTransform"
| "allowedCommands"
| "soundEnabled"
@@ -391,6 +392,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
azureApiVersion,
openRouterModelId,
openRouterModelInfo,
openRouterUseMiddleOutTransform,
} = message.apiConfiguration
await this.updateGlobalState("apiProvider", apiProvider)
await this.updateGlobalState("apiModelId", apiModelId)
@@ -416,6 +418,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
await this.updateGlobalState("azureApiVersion", azureApiVersion)
await this.updateGlobalState("openRouterModelId", openRouterModelId)
await this.updateGlobalState("openRouterModelInfo", openRouterModelInfo)
await this.updateGlobalState("openRouterUseMiddleOutTransform", openRouterUseMiddleOutTransform)
if (this.cline) {
this.cline.api = buildApiHandler(message.apiConfiguration)
}
@@ -943,6 +946,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
azureApiVersion,
openRouterModelId,
openRouterModelInfo,
openRouterUseMiddleOutTransform,
lastShownAnnouncementId,
customInstructions,
alwaysAllowReadOnly,
@@ -977,6 +981,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
this.getGlobalState("azureApiVersion") as Promise<string | undefined>,
this.getGlobalState("openRouterModelId") as Promise<string | undefined>,
this.getGlobalState("openRouterModelInfo") as Promise<ModelInfo | undefined>,
this.getGlobalState("openRouterUseMiddleOutTransform") as Promise<boolean | undefined>,
this.getGlobalState("lastShownAnnouncementId") as Promise<string | undefined>,
this.getGlobalState("customInstructions") as Promise<string | undefined>,
this.getGlobalState("alwaysAllowReadOnly") as Promise<boolean | undefined>,
@@ -1028,6 +1033,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
azureApiVersion,
openRouterModelId,
openRouterModelInfo,
openRouterUseMiddleOutTransform,
},
lastShownAnnouncementId,
customInstructions,

View File

@@ -0,0 +1,234 @@
import { ClineProvider } from '../ClineProvider'
import * as vscode from 'vscode'
import { ExtensionMessage, ExtensionState } from '../../../shared/ExtensionMessage'
// Mock dependencies
jest.mock('vscode', () => ({
ExtensionContext: jest.fn(),
OutputChannel: jest.fn(),
WebviewView: jest.fn(),
Uri: {
joinPath: jest.fn(),
file: jest.fn()
},
workspace: {
getConfiguration: jest.fn().mockReturnValue({
get: jest.fn().mockReturnValue([]),
update: jest.fn()
}),
onDidChangeConfiguration: jest.fn().mockImplementation((callback) => ({
dispose: jest.fn()
}))
},
env: {
uriScheme: 'vscode'
}
}))
// Mock ESM modules
jest.mock('p-wait-for', () => ({
__esModule: true,
default: jest.fn().mockResolvedValue(undefined)
}))
// Mock fs/promises
jest.mock('fs/promises', () => ({
mkdir: jest.fn(),
writeFile: jest.fn(),
readFile: jest.fn(),
unlink: jest.fn(),
rmdir: jest.fn()
}))
// Mock axios
jest.mock('axios', () => ({
get: jest.fn().mockResolvedValue({ data: { data: [] } }),
post: jest.fn()
}))
// Mock buildApiHandler
jest.mock('../../../api', () => ({
buildApiHandler: jest.fn()
}))
// Mock WorkspaceTracker
jest.mock('../../../integrations/workspace/WorkspaceTracker', () => {
return jest.fn().mockImplementation(() => ({
initializeFilePaths: jest.fn(),
dispose: jest.fn()
}))
})
// Mock Cline
jest.mock('../../Cline', () => {
return {
Cline: jest.fn().mockImplementation(() => ({
abortTask: jest.fn(),
handleWebviewAskResponse: jest.fn(),
clineMessages: []
}))
}
})
// Spy on console.error and console.log to suppress expected messages
beforeAll(() => {
jest.spyOn(console, 'error').mockImplementation(() => {})
jest.spyOn(console, 'log').mockImplementation(() => {})
})
afterAll(() => {
jest.restoreAllMocks()
})
describe('ClineProvider', () => {
let provider: ClineProvider
let mockContext: vscode.ExtensionContext
let mockOutputChannel: vscode.OutputChannel
let mockWebviewView: vscode.WebviewView
let mockPostMessage: jest.Mock
let visibilityChangeCallback: (e?: unknown) => void
beforeEach(() => {
// Reset mocks
jest.clearAllMocks()
// Mock context
mockContext = {
extensionPath: '/test/path',
extensionUri: {} as vscode.Uri,
globalState: {
get: jest.fn(),
update: jest.fn(),
keys: jest.fn().mockReturnValue([]),
},
secrets: {
get: jest.fn(),
store: jest.fn(),
delete: jest.fn()
},
subscriptions: [],
extension: {
packageJSON: { version: '1.0.0' }
},
globalStorageUri: {
fsPath: '/test/storage/path'
}
} as unknown as vscode.ExtensionContext
// Mock output channel
mockOutputChannel = {
appendLine: jest.fn(),
clear: jest.fn(),
dispose: jest.fn()
} as unknown as vscode.OutputChannel
// Mock webview
mockPostMessage = jest.fn()
mockWebviewView = {
webview: {
postMessage: mockPostMessage,
html: '',
options: {},
onDidReceiveMessage: jest.fn(),
asWebviewUri: jest.fn()
},
visible: true,
onDidDispose: jest.fn().mockImplementation((callback) => {
callback()
return { dispose: jest.fn() }
}),
onDidChangeVisibility: jest.fn().mockImplementation((callback) => {
visibilityChangeCallback = callback
return { dispose: jest.fn() }
})
} as unknown as vscode.WebviewView
provider = new ClineProvider(mockContext, mockOutputChannel)
})
test('constructor initializes correctly', () => {
expect(provider).toBeInstanceOf(ClineProvider)
// Since getVisibleInstance returns the last instance where view.visible is true
// @ts-ignore - accessing private property for testing
provider.view = mockWebviewView
expect(ClineProvider.getVisibleInstance()).toBe(provider)
})
test('resolveWebviewView sets up webview correctly', () => {
provider.resolveWebviewView(mockWebviewView)
expect(mockWebviewView.webview.options).toEqual({
enableScripts: true,
localResourceRoots: [mockContext.extensionUri]
})
expect(mockWebviewView.webview.html).toContain('<!DOCTYPE html>')
})
test('postMessageToWebview sends message to webview', async () => {
provider.resolveWebviewView(mockWebviewView)
const mockState: ExtensionState = {
version: '1.0.0',
clineMessages: [],
taskHistory: [],
shouldShowAnnouncement: false,
apiConfiguration: {
apiProvider: 'openrouter'
},
customInstructions: undefined,
alwaysAllowReadOnly: false,
alwaysAllowWrite: false,
alwaysAllowExecute: false,
alwaysAllowBrowser: false,
uriScheme: 'vscode',
soundEnabled: true
}
const message: ExtensionMessage = {
type: 'state',
state: mockState
}
await provider.postMessageToWebview(message)
expect(mockPostMessage).toHaveBeenCalledWith(message)
})
test('handles webviewDidLaunch message', async () => {
provider.resolveWebviewView(mockWebviewView)
// Get the message handler from onDidReceiveMessage
const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as jest.Mock).mock.calls[0][0]
// Simulate webviewDidLaunch message
await messageHandler({ type: 'webviewDidLaunch' })
// Should post state and theme to webview
expect(mockPostMessage).toHaveBeenCalled()
})
test('clearTask aborts current task', async () => {
const mockAbortTask = jest.fn()
// @ts-ignore - accessing private property for testing
provider.cline = { abortTask: mockAbortTask }
await provider.clearTask()
expect(mockAbortTask).toHaveBeenCalled()
// @ts-ignore - accessing private property for testing
expect(provider.cline).toBeUndefined()
})
test('getState returns correct initial state', async () => {
const state = await provider.getState()
expect(state).toHaveProperty('apiConfiguration')
expect(state.apiConfiguration).toHaveProperty('apiProvider')
expect(state).toHaveProperty('customInstructions')
expect(state).toHaveProperty('alwaysAllowReadOnly')
expect(state).toHaveProperty('alwaysAllowWrite')
expect(state).toHaveProperty('alwaysAllowExecute')
expect(state).toHaveProperty('alwaysAllowBrowser')
expect(state).toHaveProperty('taskHistory')
expect(state).toHaveProperty('soundEnabled')
})
})