fix: remove CORS checking for downloading remote model file

This commit is contained in:
Xiaohan-Tian
2026-05-15 12:53:36 -07:00
parent 1fd2c283f2
commit b2bbc08761
7 changed files with 212 additions and 46 deletions
+37
View File
@@ -1,5 +1,6 @@
import { describe, expect, it } from 'vitest';
import {
detectLocalLLMRuntimeSupport,
formatLocalLLMContextLength,
LOCAL_LLM_DEFAULT_CONTEXT_LENGTH,
normalizeLocalLLMContextLength,
@@ -16,4 +17,40 @@ describe('localLLMConfig', () => {
expect(formatLocalLLMContextLength(65536)).toBe('64k');
expect(formatLocalLLMContextLength(131072)).toBe('128k');
});
it('marks insecure contexts as unsupported', () => {
Object.defineProperty(window, 'isSecureContext', { value: false, configurable: true });
Object.defineProperty(window, 'crossOriginIsolated', { value: true, configurable: true });
Object.defineProperty(globalThis, 'SharedArrayBuffer', { value: class SharedArrayBuffer {}, configurable: true });
Object.defineProperty(globalThis, 'navigator', { value: { gpu: {} }, configurable: true });
const support = detectLocalLLMRuntimeSupport();
expect(support.supported).toBe(false);
expect(support.reason).toContain('secure context');
});
it('marks missing WebGPU as unsupported', () => {
Object.defineProperty(window, 'isSecureContext', { value: true, configurable: true });
Object.defineProperty(window, 'crossOriginIsolated', { value: true, configurable: true });
Object.defineProperty(globalThis, 'SharedArrayBuffer', { value: class SharedArrayBuffer {}, configurable: true });
Object.defineProperty(globalThis, 'navigator', { value: {}, configurable: true });
const support = detectLocalLLMRuntimeSupport();
expect(support.supported).toBe(false);
expect(support.reason).toContain('WebGPU');
});
it('allows runtime attempts when only SharedArrayBuffer isolation support is missing', () => {
Object.defineProperty(window, 'isSecureContext', { value: true, configurable: true });
Object.defineProperty(window, 'crossOriginIsolated', { value: false, configurable: true });
Object.defineProperty(globalThis, 'SharedArrayBuffer', { value: undefined, configurable: true });
Object.defineProperty(globalThis, 'navigator', { value: { gpu: {} }, configurable: true });
const support = detectLocalLLMRuntimeSupport();
expect(support.supported).toBe(true);
expect(support.reason).toContain('cross-origin isolation');
});
});
+3 -3
View File
@@ -29,14 +29,14 @@ export function detectLocalLLMRuntimeSupport(): LocalLLMRuntimeSupport {
let reason: string | null = null;
if (!secureContext) {
reason = 'Local browser LLM requires a secure context (HTTPS or localhost).';
} else if (!crossOriginIsolated || !sharedArrayBufferAvailable) {
reason = 'Local browser LLM requires SharedArrayBuffer support. Ensure COOP/COEP headers are enabled.';
} else if (!webgpuExposed) {
reason = 'Local browser LLM currently requires a browser with WebGPU support.';
} else if (!crossOriginIsolated || !sharedArrayBufferAvailable) {
reason = 'This host may not support the local browser runtime reliably because cross-origin isolation or SharedArrayBuffer is unavailable. COOP/COEP headers may be missing.';
}
return {
supported: reason === null,
supported: secureContext && webgpuExposed,
webgpuExposed,
crossOriginIsolated,
sharedArrayBufferAvailable,
@@ -2,7 +2,16 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
import { processUserMessage } from './UserMessageFilter';
import { addWelcomeMessage } from '../../utils/chatMessageUtils';
const { detectLocalLLMRuntimeSupportMock } = vi.hoisted(() => ({
detectLocalLLMRuntimeSupportMock: vi.fn(),
}));
const configState = new Map<string, unknown>();
const storeState = {
setStatus: vi.fn(),
activeRegionId: null as string | null,
selectedRegionIds: [] as string[],
};
const configManagerMock = {
getIsInitialized: vi.fn(() => true),
@@ -22,11 +31,7 @@ vi.mock('../chatUtil', () => ({
vi.mock('../../stores/projectStore', () => ({
useProjectStore: {
getState: () => ({
setStatus: vi.fn(),
activeRegionId: null,
selectedRegionIds: [],
}),
getState: () => storeState,
},
}));
@@ -36,6 +41,14 @@ vi.mock('../../agent/core/SystemPrompts', () => ({
},
}));
vi.mock('../localLLMConfig', async () => {
const actual = await vi.importActual<typeof import('../localLLMConfig')>('../localLLMConfig');
return {
...actual,
detectLocalLLMRuntimeSupport: detectLocalLLMRuntimeSupportMock,
};
});
describe('processUserMessage /welcome', () => {
beforeEach(() => {
configState.clear();
@@ -50,6 +63,18 @@ describe('processUserMessage /welcome', () => {
configManagerMock.getIsInitialized.mockReturnValue(true);
configManagerMock.initialize.mockClear();
configManagerMock.get.mockClear();
storeState.setStatus.mockClear();
storeState.activeRegionId = null;
storeState.selectedRegionIds = [];
detectLocalLLMRuntimeSupportMock.mockReset();
detectLocalLLMRuntimeSupportMock.mockReturnValue({
supported: true,
webgpuExposed: true,
crossOriginIsolated: true,
sharedArrayBufferAvailable: true,
secureContext: true,
reason: null,
});
vi.stubGlobal('fetch', vi.fn(async (input: string | URL | Request) => {
const url = String(input);
@@ -106,4 +131,39 @@ describe('processUserMessage /welcome', () => {
expect(message?.role).toBe('assistant');
expect(message?.content).toContain('welcome_local_llm.md');
});
it('blocks local-browser messages when the runtime is hard unsupported', async () => {
detectLocalLLMRuntimeSupportMock.mockReturnValue({
supported: false,
webgpuExposed: false,
crossOriginIsolated: false,
sharedArrayBufferAvailable: false,
secureContext: true,
reason: 'Local browser LLM currently requires a browser with WebGPU support.',
});
const result = await processUserMessage('hello');
expect(result.sendToLLM).toBe(false);
expect(result.pseudoAssistantResponse).toContain('WebGPU');
expect(result.metadata).toMatchObject({ error: 'local_browser_unsupported' });
});
it('allows local-browser messages when only SharedArrayBuffer isolation support is missing', async () => {
detectLocalLLMRuntimeSupportMock.mockReturnValue({
supported: true,
webgpuExposed: true,
crossOriginIsolated: false,
sharedArrayBufferAvailable: false,
secureContext: true,
reason: 'This host may not support the local browser runtime reliably because cross-origin isolation or SharedArrayBuffer is unavailable. COOP/COEP headers may be missing.',
});
storeState.activeRegionId = 'region-1';
const result = await processUserMessage('hello');
expect(result.sendToLLM).toBe(true);
expect(result.finalMessageForLLM).toContain('hello');
expect(result.pseudoAssistantResponse).toBeNull();
});
});