diff --git a/frontend/src/lib/components/copilot/chat/AIChatManager.svelte.ts b/frontend/src/lib/components/copilot/chat/AIChatManager.svelte.ts index faf5da8c2b..966f5ce1c2 100644 --- a/frontend/src/lib/components/copilot/chat/AIChatManager.svelte.ts +++ b/frontend/src/lib/components/copilot/chat/AIChatManager.svelte.ts @@ -416,6 +416,10 @@ class AIChatManager { // on each iteration. This is critical for changeModeTool (Navigator → Script/Flow) // which reassigns this.tools, this.helpers, this.systemMessage mid-loop. const self = this + const [openai, anthropic] = await Promise.all([ + workspaceAIClients.getOpenaiClient(), + workspaceAIClients.getAnthropicClient() + ]) const result = await runChatLoop({ messages, get systemMessage() { @@ -433,8 +437,8 @@ class AIChatManager { return getCurrentModel() }, clients: { - openai: workspaceAIClients.getOpenaiClient(), - anthropic: workspaceAIClients.getAnthropicClient() + openai, + anthropic }, workspace: get(workspaceStore) ?? '', skipResponsesApi: this.skipResponsesApi, diff --git a/frontend/src/lib/components/copilot/chat/anthropic.ts b/frontend/src/lib/components/copilot/chat/anthropic.ts index 4bc581db88..6fd7726b88 100644 --- a/frontend/src/lib/components/copilot/chat/anthropic.ts +++ b/frontend/src/lib/components/copilot/chat/anthropic.ts @@ -1,5 +1,5 @@ -import { OpenAI } from 'openai' -import Anthropic from '@anthropic-ai/sdk' +import type { OpenAI } from 'openai' +import type Anthropic from '@anthropic-ai/sdk' import type { ChatCompletionMessageParam, ChatCompletionMessageFunctionToolCall @@ -41,7 +41,7 @@ export async function getAnthropicCompletion( const { system, messages: anthropicMessages } = convertOpenAIToAnthropicMessages(messages) const anthropicTools = convertOpenAIToolsToAnthropic(tools) - const client = options?.anthropicClient ?? workspaceAIClients.getAnthropicClient() + const client = await (options?.anthropicClient ?? workspaceAIClients.getAnthropicClient()) const anthropicParams = { model: config.model, diff --git a/frontend/src/lib/components/copilot/chat/chatLoop.ts b/frontend/src/lib/components/copilot/chat/chatLoop.ts index a624698cc9..08c75a3f48 100644 --- a/frontend/src/lib/components/copilot/chat/chatLoop.ts +++ b/frontend/src/lib/components/copilot/chat/chatLoop.ts @@ -1,5 +1,5 @@ -import OpenAI from 'openai' -import Anthropic from '@anthropic-ai/sdk' +import type OpenAI from 'openai' +import type Anthropic from '@anthropic-ai/sdk' import type { ChatCompletionMessageParam, ChatCompletionSystemMessageParam, @@ -8,16 +8,9 @@ import type { import type { AIProviderModel } from '$lib/gen' import { getCompletion, parseOpenAICompletion } from '../lib' import { getAnthropicCompletion, parseAnthropicCompletion } from './anthropic' -import { - getOpenAIResponsesCompletion, - parseOpenAIResponsesCompletion -} from './openai-responses' +import { getOpenAIResponsesCompletion, parseOpenAIResponsesCompletion } from './openai-responses' import type { Tool, ToolCallbacks } from './shared' -import { - addChatTokenUsage, - emptyChatTokenUsage, - type ChatTokenUsage -} from './tokenUsage' +import { addChatTokenUsage, emptyChatTokenUsage, type ChatTokenUsage } from './tokenUsage' export interface ChatClients { openai: OpenAI @@ -137,10 +130,7 @@ export async function runChatLoop(config: ChatLoopConfig): Promise { + const { default: OpenAIClass } = await import('openai') + return new OpenAIClass({ baseURL, apiKey: 'fake-key', defaultHeaders: { @@ -400,8 +401,9 @@ export function createOpenAIProxyClient(baseURL: string): OpenAI { }) } -export function createAnthropicProxyClient(baseURL: string): Anthropic { - return new Anthropic({ +export async function createAnthropicProxyClient(baseURL: string): Promise { + const { default: AnthropicClass } = await import('@anthropic-ai/sdk') + return new AnthropicClass({ baseURL, apiKey: 'fake-key', dangerouslyAllowBrowser: true, @@ -410,30 +412,30 @@ export function createAnthropicProxyClient(baseURL: string): Anthropic { } class WorkspacedAIClients { - private openaiClient: OpenAI | undefined - private anthropicClient: Anthropic | undefined + private openaiClient: Promise | undefined + private anthropicClient: Promise | undefined init(workspace: string) { this.openaiClient = this.createOpenaiClient(workspace) this.anthropicClient = this.createAnthropicClient(workspace) } - createOpenaiClient(workspace: string): OpenAI { + createOpenaiClient(workspace: string): Promise { return createOpenAIProxyClient(getAiProxyBaseURL(workspace)) } - createAnthropicClient(workspace: string): Anthropic { + createAnthropicClient(workspace: string): Promise { return createAnthropicProxyClient(getAiProxyBaseURL(workspace)) } - getOpenaiClient() { + getOpenaiClient(): Promise { if (!this.openaiClient) { throw new Error('OpenAI not initialized') } return this.openaiClient } - getAnthropicClient() { + getAnthropicClient(): Promise { if (!this.anthropicClient) { throw new Error('Anthropic not initialized') } @@ -522,11 +524,11 @@ async function testAnthropicKey({ headers['X-API-Key'] = apiKey } - const anthropicClient = apiKey + const anthropicClient = await (apiKey ? createAnthropicProxyClient(getAiProxyBaseURL()) : workspace ? workspaceAIClients.createAnthropicClient(workspace) - : workspaceAIClients.getAnthropicClient() + : workspaceAIClients.getAnthropicClient()) await anthropicClient.messages.create( { @@ -800,11 +802,11 @@ export async function getNonStreamingCompletion( 'X-API-Key': testOptions.apiKey } } - const openaiClient = testOptions?.apiKey + const openaiClient = await (testOptions?.apiKey ? createOpenAIProxyClient(getAiProxyBaseURL()) : testOptions?.workspace ? workspaceAIClients.createOpenaiClient(testOptions.workspace) - : workspaceAIClients.getOpenaiClient() + : workspaceAIClients.getOpenaiClient()) const completion = await openaiClient.chat.completions.create(config, fetchOptions) response = completion.choices?.[0]?.message.content || '' @@ -911,7 +913,7 @@ export async function getCompletion( } // Use Completions API for other providers - const client = options?.openaiClient ?? workspaceAIClients.getOpenaiClient() + const client = await (options?.openaiClient ?? workspaceAIClients.getOpenaiClient()) const completionConfig = (provider === 'openai' || provider === 'azure_openai' || provider === 'googleai') && config.stream diff --git a/frontend/vite.config.js b/frontend/vite.config.js index 8f49dc1ba4..8d0759fe0b 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -111,6 +111,53 @@ const config = { dedupe: ['vscode', 'monaco-editor'] }, assetsInclude: ['**/*.wasm'], + build: { + rollupOptions: { + output: { + manualChunks(id) { + if (id.includes('node_modules')) { + if (id.includes('monaco-editor') || id.includes('@codingame/monaco-vscode')) { + return 'vendor-monaco' + } + if (id.includes('/openai/') || id.includes('/@anthropic-ai/')) { + return 'vendor-ai' + } + if (id.includes('@xyflow/svelte') || id.includes('d3-dag')) { + return 'vendor-flow' + } + if (id.includes('/yjs/') || id.includes('y-websocket') || id.includes('y-monaco')) { + return 'vendor-collab' + } + if (id.includes('ag-grid') || id.includes('ag-charts')) { + return 'vendor-ag' + } + if (id.includes('pdfjs-dist')) { + return 'vendor-pdf' + } + if ( + id.includes('svelte-exmarkdown') || + id.includes('/parse5/') || + id.includes('/rehype-') || + id.includes('/remark-') || + id.includes('/mdast-') || + id.includes('/micromark') + ) { + return 'vendor-markdown' + } + if (id.includes('quicktype-core')) { + return 'vendor-quicktype' + } + if (id.includes('/quill/')) { + return 'vendor-quill' + } + if (id.includes('/chart.js/') || id.includes('chartjs-')) { + return 'vendor-chart' + } + } + } + } + } + }, test: { expect: { requireAssertions: true }, projects: [