From fe52a62cdaf5178a9aa5769c4edf98a190cdc30e Mon Sep 17 00:00:00 2001 From: Lukin Date: Thu, 12 Feb 2026 23:32:26 +0800 Subject: [PATCH] fix(console): transform system role messages for Console API compatibility Claude Console API (e.g., GLM accounts) does not accept messages with role='system' in the messages array, returning 422 error: "Input should be 'user' or 'assistant'" This fix automatically transforms system messages by merging them into the first user message's content, maintaining compatibility with clients like opencode that send system role messages. Changes: - Add _transformSystemMessages() method to merge system content into user messages - Apply transformation in both relayRequest() and relayStreamRequestWithUsageCapture() - Only affects claude-console account type, no impact on official API Fixes issues where opencode users get 422 errors when using Console API accounts. --- .../relay/claudeConsoleRelayService.js | 72 ++++++++++++++++++- 1 file changed, 70 insertions(+), 2 deletions(-) diff --git a/src/services/relay/claudeConsoleRelayService.js b/src/services/relay/claudeConsoleRelayService.js index d7dcabc1..1cd55329 100644 --- a/src/services/relay/claudeConsoleRelayService.js +++ b/src/services/relay/claudeConsoleRelayService.js @@ -19,6 +19,68 @@ class ClaudeConsoleRelayService { this.defaultUserAgent = 'claude-cli/2.0.52 (external, cli)' } + /** + * 🔄 转换 messages 数组中的 system role 消息 + * Console API 不支持 role="system",需要将 system 内容合并到第一条 user 消息 + * @param {Object} requestBody - 原始请求体 + * @returns {Object} 转换后的请求体 + */ + _transformSystemMessages(requestBody) { + if (!requestBody || !Array.isArray(requestBody.messages)) { + return requestBody + } + + // 收集所有 system messages 的内容 + const systemContents = [] + const nonSystemMessages = [] + + for (const msg of requestBody.messages) { + if (msg && msg.role === 'system') { + systemContents.push(msg.content) + } else { + nonSystemMessages.push(msg) + } + } + + // 如果没有 system messages,直接返回原请求 + if (systemContents.length === 0) { + return requestBody + } + + // 合并 system 内容到第一条 user 消息 + const systemText = systemContents.join('\n\n') + let transformedMessages = nonSystemMessages + + // 查找第一条 user 消息 + const firstUserIndex = nonSystemMessages.findIndex((m) => m && m.role === 'user') + + if (firstUserIndex !== -1) { + // 将 system 内容前置到第一条 user 消息 + const firstUserMsg = nonSystemMessages[firstUserIndex] + const mergedContent = `${systemText}\n\n${firstUserMsg.content}` + transformedMessages = [...nonSystemMessages] + transformedMessages[firstUserIndex] = { + ...firstUserMsg, + content: mergedContent + } + } else { + // 如果没有 user 消息,创建一个包含 system 内容的 user 消息 + logger.warn( + `⚠️ Console API: No user message found to merge system prompt, creating new user message with system content` + ) + transformedMessages = [{ role: 'user', content: systemText }, ...nonSystemMessages] + } + + logger.debug( + `🔄 Console API: Transformed ${systemContents.length} system message(s) into user message context` + ) + + return { + ...requestBody, + messages: transformedMessages + } + } + // 🚀 转发请求到Claude Console API async relayRequest( requestBody, @@ -163,6 +225,9 @@ class ClaudeConsoleRelayService { model: mappedModel } + // 🔄 转换 system messages (Console API 不支持 role="system") + const transformedRequestBody = this._transformSystemMessages(modifiedRequestBody) + // 模型兼容性检查已经在调度器中完成,这里不需要再检查 // 创建代理agent @@ -219,7 +284,7 @@ class ClaudeConsoleRelayService { const requestConfig = { method: 'POST', url: apiEndpoint, - data: modifiedRequestBody, + data: transformedRequestBody, headers: { 'Content-Type': 'application/json', 'anthropic-version': '2023-06-01', @@ -649,6 +714,9 @@ class ClaudeConsoleRelayService { model: mappedModel } + // 🔄 转换 system messages (Console API 不支持 role="system") + const transformedRequestBody = this._transformSystemMessages(modifiedRequestBody) + // 模型兼容性检查已经在调度器中完成,这里不需要再检查 // 创建代理agent @@ -656,7 +724,7 @@ class ClaudeConsoleRelayService { // 发送流式请求 await this._makeClaudeConsoleStreamRequest( - modifiedRequestBody, + transformedRequestBody, account, proxyAgent, clientHeaders,