diff --git a/frontend/src/lib/components/copilot/chat/AIChatManager.svelte.ts b/frontend/src/lib/components/copilot/chat/AIChatManager.svelte.ts index dcd91e1d1f..0fa96862de 100644 --- a/frontend/src/lib/components/copilot/chat/AIChatManager.svelte.ts +++ b/frontend/src/lib/components/copilot/chat/AIChatManager.svelte.ts @@ -437,6 +437,7 @@ class AIChatManager { } return addedMessages } catch (err) { + console.error('Error in chatRequest', err) callbacks.onMessageEnd() if (!abortController.signal.aborted) { throw err diff --git a/frontend/src/lib/components/copilot/chat/anthropic.ts b/frontend/src/lib/components/copilot/chat/anthropic.ts index 58417bbc1f..6ce3b9f97d 100644 --- a/frontend/src/lib/components/copilot/chat/anthropic.ts +++ b/frontend/src/lib/components/copilot/chat/anthropic.ts @@ -59,6 +59,7 @@ export async function parseAnthropicCompletion( helpers: any ): Promise { let toolCallsToProcess: ChatCompletionMessageFunctionToolCall[] = [] + let error = null // Handle text streaming completion.on('text', (textDelta: string, _textSnapshot: string) => { @@ -95,7 +96,7 @@ export async function parseAnthropicCompletion( // Handle errors completion.on('error', (error: any) => { console.error('Anthropic stream error:', error) - throw error + error = error }) // Wait for completion @@ -103,6 +104,10 @@ export async function parseAnthropicCompletion( callbacks.onMessageEnd() + if (error) { + throw error + } + // Process tool calls if any if (toolCallsToProcess.length > 0) { const assistantWithTools = { diff --git a/frontend/src/lib/components/copilot/chat/api/apiTools.ts b/frontend/src/lib/components/copilot/chat/api/apiTools.ts index 4964201443..1fa644603c 100644 --- a/frontend/src/lib/components/copilot/chat/api/apiTools.ts +++ b/frontend/src/lib/components/copilot/chat/api/apiTools.ts @@ -1,11 +1,11 @@ -import type { ChatCompletionTool } from 'openai/resources/index.mjs' +import type { ChatCompletionFunctionTool } from 'openai/resources/index.mjs' import type { Tool } from '../shared' import { get } from 'svelte/store' import { workspaceStore } from '$lib/stores' import type { EndpointTool } from '$lib/gen/types.gen' import { McpService } from '$lib/gen/services.gen' -function buildApiCallTool(endpointTool: EndpointTool): ChatCompletionTool { +function buildApiCallTool(endpointTool: EndpointTool): ChatCompletionFunctionTool { // Build the parameters schema for OpenAI function calling const parameters: Record = { type: 'object', @@ -18,10 +18,13 @@ function buildApiCallTool(endpointTool: EndpointTool): ChatCompletionTool { for (const [key, schema] of Object.entries(endpointTool.path_params_schema.properties)) { // Skip workspace parameter as it's auto-filled if (key === 'workspace') continue - + parameters.properties[key] = schema - - if (Array.isArray(endpointTool.path_params_schema.required) && endpointTool.path_params_schema.required.includes(key)) { + + if ( + Array.isArray(endpointTool.path_params_schema.required) && + endpointTool.path_params_schema.required.includes(key) + ) { parameters.required.push(key) } } @@ -31,8 +34,11 @@ function buildApiCallTool(endpointTool: EndpointTool): ChatCompletionTool { if (endpointTool.query_params_schema?.properties) { for (const [key, schema] of Object.entries(endpointTool.query_params_schema.properties)) { parameters.properties[key] = schema - - if (Array.isArray(endpointTool.query_params_schema.required) && endpointTool.query_params_schema.required.includes(key)) { + + if ( + Array.isArray(endpointTool.query_params_schema.required) && + endpointTool.query_params_schema.required.includes(key) + ) { parameters.required.push(key) } } @@ -47,8 +53,11 @@ function buildApiCallTool(endpointTool: EndpointTool): ChatCompletionTool { properties: endpointTool.body_schema.properties, required: endpointTool.body_schema.required || [] } - - if (Array.isArray(endpointTool.body_schema.required) && endpointTool.body_schema.required.length > 0) { + + if ( + Array.isArray(endpointTool.body_schema.required) && + endpointTool.body_schema.required.length > 0 + ) { parameters.required.push('body') } } @@ -63,16 +72,17 @@ function buildApiCallTool(endpointTool: EndpointTool): ChatCompletionTool { } } -function buildToolsFromEndpoints( - endpointTools: EndpointTool[] -): { tools: ChatCompletionTool[]; endpointMap: Record } { - const tools: ChatCompletionTool[] = [] +function buildToolsFromEndpoints(endpointTools: EndpointTool[]): { + tools: ChatCompletionFunctionTool[] + endpointMap: Record +} { + const tools: ChatCompletionFunctionTool[] = [] const endpointMap: Record = {} for (const endpointTool of endpointTools) { const tool = buildApiCallTool(endpointTool) tools.push(tool) - + // Store the endpoint info in the map endpointMap[endpointTool.name] = { method: endpointTool.method, @@ -84,17 +94,17 @@ function buildToolsFromEndpoints( } export function createApiTools( - chatTools: ChatCompletionTool[], + chatTools: ChatCompletionFunctionTool[], endpointMap: Record = {} ): Tool<{}>[] { return chatTools.map((chatTool) => { const toolName = chatTool.function.name const endpoint = endpointMap[toolName] const method = endpoint?.method?.toUpperCase() || 'GET' - + // Determine if tool needs confirmation based on method const needsConfirmation = ['DELETE', 'POST', 'PUT', 'PATCH'].includes(method) - + return { def: chatTool, requiresConfirmation: needsConfirmation, @@ -103,7 +113,7 @@ export function createApiTools( fn: async ({ args, toolId, toolCallbacks }) => { const toolName = chatTool.function.name const endpoint = endpointMap[toolName] - + if (!endpoint) { throw new Error(`No endpoint mapping found for tool ${toolName}`) } @@ -111,7 +121,7 @@ export function createApiTools( try { const workspace = get(workspaceStore) as string let path = endpoint.path.replace('{workspace}', workspace) - + // Build URL with path parameters let url = `/api${path}` const queryParams: Record = {} @@ -143,7 +153,7 @@ export function createApiTools( } toolCallbacks.setToolStatus(toolId, { - content: `Calling ${toolName}...`, + content: `Calling ${toolName}...` }) const fetchOptions: RequestInit = { @@ -173,7 +183,7 @@ export function createApiTools( }) toolCallbacks.setToolStatus(toolId, { content: `Call to ${toolName} completed`, - result: jsonResult, + result: jsonResult }) return jsonResult } else { @@ -186,7 +196,7 @@ export function createApiTools( toolCallbacks.setToolStatus(toolId, { content: `Call to ${toolName} failed`, result: jsonResult, - error: `HTTP ${response.status}: ${text}`, + error: `HTTP ${response.status}: ${text}` }) return jsonResult } @@ -194,7 +204,7 @@ export function createApiTools( const errorMessage = `Error calling API: ${error instanceof Error ? error.message : String(error)}` toolCallbacks.setToolStatus(toolId, { content: `Call to ${toolName} failed`, - error: errorMessage, + error: errorMessage }) console.error(`Error calling API:`, error) return errorMessage @@ -210,10 +220,10 @@ export async function loadApiTools(): Promise[]> { const endpointTools = await McpService.listMcpTools({ workspace: get(workspaceStore) as string }) - + // Build tools from the endpoint definitions const { tools: apiTools, endpointMap } = buildToolsFromEndpoints(endpointTools) - + // Create executable tools const executableApiTools = createApiTools(apiTools, endpointMap) return executableApiTools @@ -221,4 +231,4 @@ export async function loadApiTools(): Promise[]> { console.error('Failed to load API tools:', error) return [] } -} \ No newline at end of file +} diff --git a/frontend/src/lib/components/copilot/chat/script/core.ts b/frontend/src/lib/components/copilot/chat/script/core.ts index 1154623f3a..9deb4f0412 100644 --- a/frontend/src/lib/components/copilot/chat/script/core.ts +++ b/frontend/src/lib/components/copilot/chat/script/core.ts @@ -5,7 +5,7 @@ import { get } from 'svelte/store' import { compile, phpCompile, pythonCompile } from '../../utils' import type { ChatCompletionSystemMessageParam, - ChatCompletionTool, + ChatCompletionFunctionTool, ChatCompletionUserMessageParam } from 'openai/resources/index.mjs' import { type DBSchema, dbSchemas, getCurrentModel } from '$lib/stores' @@ -498,7 +498,7 @@ export function prepareScriptUserMessage( } } -const RESOURCE_TYPE_FUNCTION_DEF: ChatCompletionTool = { +const RESOURCE_TYPE_FUNCTION_DEF: ChatCompletionFunctionTool = { type: 'function', function: { name: 'search_resource_types', @@ -519,7 +519,7 @@ const RESOURCE_TYPE_FUNCTION_DEF: ChatCompletionTool = { } } -const DB_SCHEMA_FUNCTION_DEF: ChatCompletionTool = { +const DB_SCHEMA_FUNCTION_DEF: ChatCompletionFunctionTool = { type: 'function', function: { name: 'get_db_schema', @@ -693,7 +693,7 @@ export async function searchExternalIntegrationResources(args: { query: string } } } -const SEARCH_NPM_PACKAGES_TOOL: ChatCompletionTool = { +const SEARCH_NPM_PACKAGES_TOOL: ChatCompletionFunctionTool = { type: 'function', function: { name: 'search_npm_packages', @@ -778,7 +778,7 @@ export async function fetchNpmPackageTypes( } } -const TEST_RUN_SCRIPT_TOOL: ChatCompletionTool = { +const TEST_RUN_SCRIPT_TOOL: ChatCompletionFunctionTool = { type: 'function', function: { name: 'test_run_script', diff --git a/frontend/src/lib/components/copilot/lib.ts b/frontend/src/lib/components/copilot/lib.ts index fa2ccd545a..fa83f8a3bd 100644 --- a/frontend/src/lib/components/copilot/lib.ts +++ b/frontend/src/lib/components/copilot/lib.ts @@ -669,6 +669,7 @@ export async function parseOpenAICompletion( helpers: any ): Promise { let toolCallsToProcess: ChatCompletionMessageToolCall[] = [] + let error = null // Handle content streaming stream.on('content.delta', ({ delta }: { delta: string }) => { @@ -677,6 +678,7 @@ export async function parseOpenAICompletion( // Handle complete messages stream.on('message', (message: ChatCompletionMessageParam) => { + console.log('message', message) if (message.role === 'assistant') { // Add the complete assistant message (whether it has content, tool calls, or both) messages.push(message) @@ -690,6 +692,7 @@ export async function parseOpenAICompletion( toolCallsToProcess = message.tool_calls // Preprocess tools for (const toolCall of message.tool_calls) { + console.log('toolCall', toolCall) if (toolCall.type !== 'function') continue const tool = tools.find((t) => t.def.function.name === toolCall.function.name) if (tool?.preAction) { @@ -703,12 +706,16 @@ export async function parseOpenAICompletion( // Handle errors stream.on('error', (error: any) => { console.error('OpenAI stream error:', error) - throw error + error = error }) // Wait for completion await stream.done() + if (error) { + throw error + } + callbacks.onMessageEnd() // Process tool calls if any