diff --git a/src/api/providers/qwen-code.ts b/src/api/providers/qwen-code.ts index d930d9dfc7..bef8e1b599 100644 --- a/src/api/providers/qwen-code.ts +++ b/src/api/providers/qwen-code.ts @@ -10,6 +10,7 @@ import type { ApiHandlerOptions } from "../../shared/api" import { convertToOpenAiMessages } from "../transform/openai-format" import { ApiStream } from "../transform/stream" +import { handleOpenAIError } from "./utils/openai-error-handler" import { BaseProvider } from "./base-provider" import type { SingleCompletionHandler } from "../index" @@ -54,6 +55,7 @@ export class QwenCodeHandler extends BaseProvider implements SingleCompletionHan private credentials: QwenOAuthCredentials | null = null private client: OpenAI | undefined private refreshPromise: Promise | null = null + private readonly providerName = "QwenCode" constructor(options: QwenCodeHandlerOptions) { super() @@ -213,16 +215,27 @@ export class QwenCodeHandler extends BaseProvider implements SingleCompletionHan const convertedMessages = [systemMessage, ...convertToOpenAiMessages(messages)] + // Extract tools from the system prompt if present + const tools = this.extractToolsFromSystemPrompt(systemPrompt) + const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = { model: model.id, - temperature: 0, + temperature: this.options.modelTemperature ?? 0.2, // Use a slightly higher default for better tool usage messages: convertedMessages, stream: true, stream_options: { include_usage: true }, - max_completion_tokens: model.info.maxTokens, + // Use max_completion_tokens instead of deprecated max_tokens + max_completion_tokens: this.options.modelMaxTokens || model.info.maxTokens, + // Add tools if they were found in the system prompt + ...(tools.length > 0 && { tools }), } - const stream = await this.callApiWithRetry(() => client.chat.completions.create(requestOptions)) + let stream + try { + stream = await this.callApiWithRetry(() => client.chat.completions.create(requestOptions)) + } catch (error) { + throw handleOpenAIError(error, this.providerName) + } let fullContent = "" @@ -290,6 +303,78 @@ export class QwenCodeHandler extends BaseProvider implements SingleCompletionHan return { id, info } } + /** + * Extract tools from the system prompt if they are defined + * This helps the model understand what tools are available + */ + private extractToolsFromSystemPrompt(systemPrompt: string): OpenAI.Chat.Completions.ChatCompletionTool[] { + const tools: OpenAI.Chat.Completions.ChatCompletionTool[] = [] + + // Look for tool definitions in the system prompt + // This is a simple pattern matching approach - could be enhanced + const toolPattern = /(.*?)<\/tool_name>.*?(.*?)<\/parameters>/gs + const matches = systemPrompt.matchAll(toolPattern) + + for (const match of matches) { + const toolName = match[1].trim() + const parametersText = match[2].trim() + + // Try to parse parameters into a schema + try { + // Create properties and required arrays with proper types + const properties: Record = {} + const required: string[] = [] + + // Create a basic function schema for the tool + const tool: OpenAI.Chat.Completions.ChatCompletionTool = { + type: "function", + function: { + name: toolName, + description: `Tool: ${toolName}`, + parameters: { + type: "object", + properties: properties, + required: required, + }, + }, + } + + // Parse parameter definitions if they follow a pattern + const paramPattern = /- (\w+):\s*\((\w+)\)\s*(.*)/g + const paramMatches = parametersText.matchAll(paramPattern) + + for (const paramMatch of paramMatches) { + const paramName = paramMatch[1] + const paramType = paramMatch[2].toLowerCase() + const isRequired = paramMatch[3].includes("required") + + // Map to JSON schema types + let schemaType = "string" + if (paramType === "boolean") schemaType = "boolean" + else if (paramType === "number" || paramType === "integer") schemaType = "number" + else if (paramType === "array") schemaType = "array" + else if (paramType === "object") schemaType = "object" + + properties[paramName] = { + type: schemaType, + description: paramMatch[3], + } + + if (isRequired) { + required.push(paramName) + } + } + + tools.push(tool) + } catch (error) { + // If we can't parse the tool definition, skip it + console.debug(`Could not parse tool definition for ${toolName}:`, error) + } + } + + return tools + } + async completePrompt(prompt: string): Promise { await this.ensureAuthenticated() const client = this.ensureClient() @@ -298,10 +383,17 @@ export class QwenCodeHandler extends BaseProvider implements SingleCompletionHan const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming = { model: model.id, messages: [{ role: "user", content: prompt }], - max_completion_tokens: model.info.maxTokens, + // Use max_completion_tokens instead of deprecated max_tokens + max_completion_tokens: this.options.modelMaxTokens || model.info.maxTokens, + temperature: this.options.modelTemperature ?? 0.2, } - const response = await this.callApiWithRetry(() => client.chat.completions.create(requestOptions)) + let response + try { + response = await this.callApiWithRetry(() => client.chat.completions.create(requestOptions)) + } catch (error) { + throw handleOpenAIError(error, this.providerName) + } return response.choices[0]?.message.content || "" }