From 97dabd3f1d5f6306e8fcaac1cd2bdb6531b0ab3f Mon Sep 17 00:00:00 2001 From: centdix Date: Fri, 29 Aug 2025 11:32:58 +0000 Subject: [PATCH] draft parser --- .../copilot/chat/AIChatManager.svelte.ts | 55 ++++++++--- .../lib/components/copilot/chat/anthropic.ts | 98 +++++++++++++++++-- 2 files changed, 132 insertions(+), 21 deletions(-) diff --git a/frontend/src/lib/components/copilot/chat/AIChatManager.svelte.ts b/frontend/src/lib/components/copilot/chat/AIChatManager.svelte.ts index e64d85d222..555b5f14c4 100644 --- a/frontend/src/lib/components/copilot/chat/AIChatManager.svelte.ts +++ b/frontend/src/lib/components/copilot/chat/AIChatManager.svelte.ts @@ -44,6 +44,7 @@ import type { ContextElement } from './context' import type { Selection } from 'monaco-editor' import type AIChatInput from './AIChatInput.svelte' import { prepareApiSystemMessage, prepareApiUserMessage } from './api/core' +import { getAnthropicCompletion, parseAnthropicCompletion } from './anthropic' // If the estimated token usage is greater than the model context window - the threshold, we delete the oldest message const MAX_TOKENS_THRESHOLD_PERCENTAGE = 0.05 @@ -409,23 +410,47 @@ class AIChatManager { this.pendingPrompt = '' } - const completion = await getCompletion( - [systemMessage, ...messages, ...(pendingUserMessage ? [pendingUserMessage] : [])], - abortController, - tools.map((t) => t.def) - ) + const model = getCurrentModel() - if (completion) { - const continueCompletion = await parseOpenAICompletion( - completion, - callbacks, - messages, - addedMessages, - tools, - helpers + if (model.provider === 'anthropic') { + const completion = await getAnthropicCompletion( + [systemMessage, ...messages, ...(pendingUserMessage ? [pendingUserMessage] : [])], + abortController, + tools.map((t) => t.def) ) - if (!continueCompletion) { - break + + if (completion) { + const continueCompletion = await parseAnthropicCompletion( + completion, + callbacks, + messages, + addedMessages, + tools, + helpers + ) + if (!continueCompletion) { + break + } + } + } else { + const completion = await getCompletion( + [systemMessage, ...messages, ...(pendingUserMessage ? [pendingUserMessage] : [])], + abortController, + tools.map((t) => t.def) + ) + + if (completion) { + const continueCompletion = await parseOpenAICompletion( + completion, + callbacks, + messages, + addedMessages, + tools, + helpers + ) + if (!continueCompletion) { + break + } } } } diff --git a/frontend/src/lib/components/copilot/chat/anthropic.ts b/frontend/src/lib/components/copilot/chat/anthropic.ts index ef642bab53..291225b098 100644 --- a/frontend/src/lib/components/copilot/chat/anthropic.ts +++ b/frontend/src/lib/components/copilot/chat/anthropic.ts @@ -7,15 +7,15 @@ import type { ToolUseBlockParam, Tool as AnthropicTool } from '@anthropic-ai/sdk/resources' +import type { MessageStream } from '@anthropic-ai/sdk/lib/MessageStream' import { getProviderAndCompletionConfig, workspaceAIClients } from '../lib' -import type { Stream } from 'openai/streaming.mjs' -import type { Tool, ToolCallbacks } from './shared' +import { processToolCall, type Tool, type ToolCallbacks } from './shared' export async function getAnthropicCompletion( messages: ChatCompletionMessageParam[], abortController: AbortController, tools?: OpenAI.Chat.Completions.ChatCompletionTool[] -) { +): Promise { const { provider, config } = getProviderAndCompletionConfig({ messages, stream: true }) const { system, messages: anthropicMessages } = convertOpenAIToAnthropicMessages(messages) const anthropicTools = convertOpenAIToolsToAnthropic(tools) @@ -43,7 +43,7 @@ export async function getAnthropicCompletion( } export async function parseAnthropicCompletion( - completion: Stream, + completion: MessageStream, callbacks: ToolCallbacks & { onNewToken: (token: string) => void onMessageEnd: () => void @@ -53,8 +53,94 @@ export async function parseAnthropicCompletion( tools: Tool[], helpers: any ): Promise { - // TODO: Implement Anthropic completion parsing - return true + let hasToolCalls = false + let toolCallsToProcess: { id: string; name: string; input: any }[] = [] + let textContent = '' + + // Handle text streaming + completion.on('text', (textDelta: string, _textSnapshot: string) => { + textContent += textDelta + callbacks.onNewToken(textDelta) + }) + + // Handle completed content blocks (including tool use) + completion.on('contentBlock', (contentBlock: any) => { + if (contentBlock.type === 'tool_use') { + hasToolCalls = true + toolCallsToProcess.push({ + id: contentBlock.id, + name: contentBlock.name, + input: contentBlock.input + }) + // Preprocess tool if it has a preAction + const tool = tools.find((t) => t.def.function.name === contentBlock.name) + if (tool && tool.preAction) { + tool.preAction({ toolCallbacks: callbacks, toolId: contentBlock.id }) + } + } + }) + + // Handle errors + completion.on('error', (error: any) => { + console.error('Anthropic stream error:', error) + throw error + }) + + // Wait for completion + await completion.done() + + // Add text content to messages if any + if (textContent) { + const assistantMessage = { role: 'assistant' as const, content: textContent } + addedMessages.push(assistantMessage) + messages.push(assistantMessage) + } + + callbacks.onMessageEnd() + + // Process tool calls if any + if (hasToolCalls && toolCallsToProcess.length > 0) { + // Convert Anthropic tool calls to OpenAI format for compatibility + const toolCalls = toolCallsToProcess.map((toolCall) => ({ + id: toolCall.id, + type: 'function' as const, + function: { + name: toolCall.name, + arguments: JSON.stringify(toolCall.input) + } + })) + + const assistantWithTools = { + role: 'assistant' as const, + tool_calls: toolCalls + } + messages.push(assistantWithTools) + addedMessages.push(assistantWithTools) + + // Process each tool call + for (const toolCall of toolCallsToProcess) { + const openAIToolCall = { + id: toolCall.id, + type: 'function' as const, + function: { + name: toolCall.name, + arguments: JSON.stringify(toolCall.input) + } + } + + const messageToAdd = await processToolCall({ + tools, + toolCall: openAIToolCall, + helpers, + toolCallbacks: callbacks + }) + messages.push(messageToAdd) + addedMessages.push(messageToAdd) + } + return true // Continue the conversation loop + } + + return false // End the conversation } export function convertOpenAIToAnthropicMessages(messages: ChatCompletionMessageParam[]): {