refactor(converters): 重构协议转换器架构并迁移至策略模式

- 创建新的转换器基础架构,包括BaseConverter、ConverterFactory和策略模式实现
- 迁移OpenAI、Claude、Gemini和OpenAI Responses(仅对话,无工具)转换器到新的策略模式
- 移除旧的ensureRolesInContents函数,优化内容处理逻辑
- 添加注册转换器机制,支持动态协议转换
- 更新API服务器以使用新的转换器工厂
- 优化流式响应处理和协议映射逻辑
- 添加详细的转换器工具和实用函数
- 改进错误处理和调试日志记录
This commit is contained in:
hex2077 2025-11-09 22:22:22 +08:00
parent abf7b15781
commit a212a71385
31 changed files with 7517 additions and 2539 deletions

View file

@ -111,6 +111,7 @@ import { promises as pfs } from 'fs';
import 'dotenv/config'; // Import dotenv and configure it
import deepmerge from 'deepmerge';
import './converters/register-converters.js'; // 注册所有转换器
import { getServiceAdapter, serviceInstances } from './adapter.js';
import { ProviderPoolManager } from './provider-pool-manager.js';
import {
@ -449,8 +450,7 @@ async function initApiService(config) {
if (config.providerPools && Object.keys(config.providerPools).length > 0) {
providerPoolManager = new ProviderPoolManager(config.providerPools, { globalConfig: config });
console.log('[Initialization] ProviderPoolManager initialized with configured pools.');
// 可以选择在这里触发一次健康检查
providerPoolManager.performHealthChecks(true);
// 健康检查将在服务器完全启动后执行
} else {
console.log('[Initialization] No provider pools configured. Using single provider mode.');
}
@ -505,7 +505,7 @@ function logProviderSpecificDetails(provider, config) {
} else {
console.log(` [gemini-cli-oauth] OAuth Creds: Default discovery`);
}
console.log(` [gemini-cli-oauth] Project ID: ${config.PROJECT_ID || 'Auto-discovered'}`);
// console.log(` [gemini-cli-oauth] Project ID: ${config.PROJECT_ID || 'Auto-discovered'}`);
break;
case MODEL_PROVIDER.KIRO_API:
if (config.KIRO_OAUTH_CREDS_FILE_PATH) {
@ -743,6 +743,11 @@ async function startServer() {
// 每 CRON_NEAR_MINUTES 分钟执行一次心跳日志和令牌刷新
setInterval(heartbeatAndRefreshToken, CONFIG.CRON_NEAR_MINUTES * 60 * 1000);
}
// 服务器完全启动后,执行初始健康检查
if (providerPoolManager) {
console.log('[Initialization] Performing initial health checks for provider pools...');
providerPoolManager.performHealthChecks(true);
}
});
return server; // Return the server instance for testing purposes
}

View file

@ -68,33 +68,6 @@ export function formatExpiryTime(expiryTimestamp) {
return `${pad(hours)}h ${pad(minutes)}m ${pad(seconds)}s`;
}
/**
* Ensures that all content parts in a request body have a 'role' property.
* If 'systemInstruction' is present and lacks a role, it defaults to 'user'.
* If any 'contents' entry lacks a role, it defaults to 'user'.
* @param {Object} requestBody - The request body object.
* @returns {Object} The modified request body with roles ensured.
*/
export function ensureRolesInContents(requestBody) {
if (requestBody.system_instruction) {
requestBody.systemInstruction = requestBody.system_instruction;
delete requestBody.system_instruction;
}
if (requestBody.systemInstruction && !requestBody.systemInstruction.role) {
requestBody.systemInstruction.role = 'user';
}
if (requestBody.contents && Array.isArray(requestBody.contents)) {
requestBody.contents.forEach(content => {
if (!content.role) {
content.role = 'user';
}
});
}
return requestBody;
}
/**
* Reads the entire request body from an HTTP request.
* @param {http.IncomingMessage} req - The HTTP request object.
@ -213,29 +186,23 @@ export async function handleStreamRequest(res, service, model, requestBody, from
// fs.writeFile('request'+Date.now()+'.json', JSON.stringify(requestBody));
// The service returns a stream in its native format (toProvider).
const nativeStream = await service.generateContentStream(model, requestBody);
const needsConversion = getProtocolPrefix(fromProvider) !== getProtocolPrefix(toProvider);
requestBody.model = model;
const nativeStream = await service.generateContentStream(model, requestBody);
const addEvent = getProtocolPrefix(fromProvider) === MODEL_PROTOCOL_PREFIX.CLAUDE || getProtocolPrefix(fromProvider) === MODEL_PROTOCOL_PREFIX.OPENAI_RESPONSES;
const openStop = getProtocolPrefix(fromProvider) === MODEL_PROTOCOL_PREFIX.OPENAI ;
const openResponses = getProtocolPrefix(fromProvider) === MODEL_PROTOCOL_PREFIX.OPENAI_RESPONSES ;
try {
if (openResponses && needsConversion) {
const beginChunks = getOpenAIResponsesStreamChunkBegin(model);
for (const chunk of beginChunks) {
res.write(`event: ${chunk.type}\n`);
res.write(`data: ${JSON.stringify(chunk)}\n\n`);
}
}
for await (const nativeChunk of nativeStream) {
// Convert chunk to the client's format (fromProvider), if necessary.
// Extract text for logging purposes
const chunkText = extractResponseText(nativeChunk, toProvider);
if (chunkText && !Array.isArray(chunkText)) {
fullResponseText += chunkText;
}
// Convert the complete chunk object to the client's format (fromProvider), if necessary.
const chunkToSend = needsConversion
? convertData(chunkText, 'streamChunk', toProvider, fromProvider, model)
? convertData(nativeChunk, 'streamChunk', toProvider, fromProvider, model)
: nativeChunk;
if (!chunkToSend) {
@ -247,6 +214,7 @@ export async function handleStreamRequest(res, service, model, requestBody, from
for (const chunk of chunksToSend) {
if (addEvent) {
// fullOldResponseJson += chunk.type+"\n";
// fullResponseJson += chunk.type+"\n";
res.write(`event: ${chunk.type}\n`);
// console.log(`event: ${chunk.type}\n`);
@ -258,13 +226,6 @@ export async function handleStreamRequest(res, service, model, requestBody, from
// console.log(`data: ${JSON.stringify(chunk)}\n`);
}
}
if (openResponses && needsConversion) {
const endChunks = getOpenAIResponsesStreamChunkEnd(model);
for (const chunk of endChunks) {
res.write(`event: ${chunk.type}\n`);
res.write(`data: ${JSON.stringify(chunk)}\n\n`);
}
}
if (openStop && needsConversion) {
res.write(`data: ${JSON.stringify(getOpenAIStreamChunkStop(model))}\n\n`);
// console.log(`data: ${JSON.stringify(getOpenAIStreamChunkStop(model))}\n`);
@ -291,20 +252,23 @@ export async function handleStreamRequest(res, service, model, requestBody, from
res.end();
}
await logConversation('output', fullResponseText, PROMPT_LOG_MODE, PROMPT_LOG_FILENAME);
// fs.writeFile('oldResponse'+Date.now()+'.json', fullOldResponseJson);
// fs.writeFile('response'+Date.now()+'.json', fullResponseJson);
// fs.writeFile('oldResponseChunk'+Date.now()+'.json', fullOldResponseJson);
// fs.writeFile('responseChunk'+Date.now()+'.json', fullResponseJson);
}
}
export async function handleUnaryRequest(res, service, model, requestBody, fromProvider, toProvider, PROMPT_LOG_MODE, PROMPT_LOG_FILENAME, providerPoolManager, pooluuid) {
try{
// The service returns the response in its native format (toProvider).
const needsConversion = getProtocolPrefix(fromProvider) !== getProtocolPrefix(toProvider);
requestBody.model = model;
// fs.writeFile('oldRequest'+Date.now()+'.json', JSON.stringify(requestBody));
const nativeResponse = await service.generateContent(model, requestBody);
const responseText = extractResponseText(nativeResponse, toProvider);
// Convert the response back to the client's format (fromProvider), if necessary.
let clientResponse = nativeResponse;
if (getProtocolPrefix(fromProvider) !== getProtocolPrefix(toProvider)) {
if (needsConversion) {
console.log(`[Response Convert] Converting response from ${toProvider} to ${fromProvider}`);
clientResponse = convertData(nativeResponse, 'response', toProvider, fromProvider, model);
}
@ -312,6 +276,7 @@ export async function handleUnaryRequest(res, service, model, requestBody, fromP
//console.log(`[Response] Sending response to client: ${JSON.stringify(clientResponse)}`);
await handleUnifiedResponse(res, JSON.stringify(clientResponse), false);
await logConversation('output', responseText, PROMPT_LOG_MODE, PROMPT_LOG_FILENAME);
// fs.writeFile('oldResponse'+Date.now()+'.json', JSON.stringify(clientResponse));
} catch (error) {
console.error('\n[Server] Error during unary processing:', error.stack);
if (providerPoolManager) {

2571
src/convert-old.js Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,115 @@
/**
* 转换器基类
* 使用策略模式定义转换器的通用接口
*/
/**
* 抽象转换器基类
* 所有具体的协议转换器都应继承此类
*/
export class BaseConverter {
constructor(protocolName) {
if (new.target === BaseConverter) {
throw new Error('BaseConverter是抽象类不能直接实例化');
}
this.protocolName = protocolName;
}
/**
* 转换请求
* @param {Object} data - 请求数据
* @param {string} targetProtocol - 目标协议
* @returns {Object} 转换后的请求
*/
convertRequest(data, targetProtocol) {
throw new Error('convertRequest方法必须被子类实现');
}
/**
* 转换响应
* @param {Object} data - 响应数据
* @param {string} targetProtocol - 目标协议
* @param {string} model - 模型名称
* @returns {Object} 转换后的响应
*/
convertResponse(data, targetProtocol, model) {
throw new Error('convertResponse方法必须被子类实现');
}
/**
* 转换流式响应块
* @param {Object} chunk - 流式响应块
* @param {string} targetProtocol - 目标协议
* @param {string} model - 模型名称
* @returns {Object} 转换后的流式响应块
*/
convertStreamChunk(chunk, targetProtocol, model) {
throw new Error('convertStreamChunk方法必须被子类实现');
}
/**
* 转换模型列表
* @param {Object} data - 模型列表数据
* @param {string} targetProtocol - 目标协议
* @returns {Object} 转换后的模型列表
*/
convertModelList(data, targetProtocol) {
throw new Error('convertModelList方法必须被子类实现');
}
/**
* 获取协议名称
* @returns {string} 协议名称
*/
getProtocolName() {
return this.protocolName;
}
}
/**
* 内容处理器接口
* 用于处理不同类型的内容文本图片音频等
*/
export class ContentProcessor {
/**
* 处理内容
* @param {*} content - 内容数据
* @returns {*} 处理后的内容
*/
process(content) {
throw new Error('process方法必须被子类实现');
}
}
/**
* 工具处理器接口
* 用于处理工具调用相关的转换
*/
export class ToolProcessor {
/**
* 处理工具定义
* @param {Array} tools - 工具定义数组
* @returns {Array} 处理后的工具定义
*/
processToolDefinitions(tools) {
throw new Error('processToolDefinitions方法必须被子类实现');
}
/**
* 处理工具调用
* @param {Object} toolCall - 工具调用数据
* @returns {Object} 处理后的工具调用
*/
processToolCall(toolCall) {
throw new Error('processToolCall方法必须被子类实现');
}
/**
* 处理工具结果
* @param {Object} toolResult - 工具结果数据
* @returns {Object} 处理后的工具结果
*/
processToolResult(toolResult) {
throw new Error('processToolResult方法必须被子类实现');
}
}

View file

@ -0,0 +1,182 @@
/**
* 转换器工厂类
* 使用工厂模式管理转换器实例的创建和缓存
*/
import { MODEL_PROTOCOL_PREFIX } from '../common.js';
/**
* 转换器工厂单例模式 + 工厂模式
*/
export class ConverterFactory {
// 私有静态属性:存储转换器实例
static #converters = new Map();
// 私有静态属性:存储转换器类
static #converterClasses = new Map();
/**
* 注册转换器类
* @param {string} protocolPrefix - 协议前缀
* @param {Class} ConverterClass - 转换器类
*/
static registerConverter(protocolPrefix, ConverterClass) {
this.#converterClasses.set(protocolPrefix, ConverterClass);
}
/**
* 获取转换器实例带缓存
* @param {string} protocolPrefix - 协议前缀
* @returns {BaseConverter} 转换器实例
*/
static getConverter(protocolPrefix) {
// 检查缓存
if (this.#converters.has(protocolPrefix)) {
return this.#converters.get(protocolPrefix);
}
// 创建新实例
const converter = this.createConverter(protocolPrefix);
// 缓存实例
if (converter) {
this.#converters.set(protocolPrefix, converter);
}
return converter;
}
/**
* 创建转换器实例
* @param {string} protocolPrefix - 协议前缀
* @returns {BaseConverter} 转换器实例
*/
static createConverter(protocolPrefix) {
const ConverterClass = this.#converterClasses.get(protocolPrefix);
if (!ConverterClass) {
throw new Error(`No converter registered for protocol: ${protocolPrefix}`);
}
return new ConverterClass();
}
/**
* 清除所有缓存的转换器
*/
static clearCache() {
this.#converters.clear();
}
/**
* 清除特定协议的转换器缓存
* @param {string} protocolPrefix - 协议前缀
*/
static clearConverterCache(protocolPrefix) {
this.#converters.delete(protocolPrefix);
}
/**
* 获取所有已注册的协议
* @returns {Array<string>} 协议前缀数组
*/
static getRegisteredProtocols() {
return Array.from(this.#converterClasses.keys());
}
/**
* 检查协议是否已注册
* @param {string} protocolPrefix - 协议前缀
* @returns {boolean} 是否已注册
*/
static isProtocolRegistered(protocolPrefix) {
return this.#converterClasses.has(protocolPrefix);
}
}
/**
* 内容处理器工厂
*/
export class ContentProcessorFactory {
static #processors = new Map();
/**
* 获取内容处理器
* @param {string} sourceFormat - 源格式
* @param {string} targetFormat - 目标格式
* @returns {ContentProcessor} 内容处理器实例
*/
static getProcessor(sourceFormat, targetFormat) {
const key = `${sourceFormat}_to_${targetFormat}`;
if (!this.#processors.has(key)) {
this.#processors.set(key, this.createProcessor(sourceFormat, targetFormat));
}
return this.#processors.get(key);
}
/**
* 创建内容处理器
* @param {string} sourceFormat - 源格式
* @param {string} targetFormat - 目标格式
* @returns {ContentProcessor} 内容处理器实例
*/
static createProcessor(sourceFormat, targetFormat) {
// 这里返回null实际使用时需要导入具体的处理器类
// 为了避免循环依赖,处理器类应该在使用时动态导入
console.warn(`Content processor for ${sourceFormat} to ${targetFormat} not yet implemented`);
return null;
}
/**
* 清除所有缓存的处理器
*/
static clearCache() {
this.#processors.clear();
}
}
/**
* 工具处理器工厂
*/
export class ToolProcessorFactory {
static #processors = new Map();
/**
* 获取工具处理器
* @param {string} sourceFormat - 源格式
* @param {string} targetFormat - 目标格式
* @returns {ToolProcessor} 工具处理器实例
*/
static getProcessor(sourceFormat, targetFormat) {
const key = `${sourceFormat}_to_${targetFormat}`;
if (!this.#processors.has(key)) {
this.#processors.set(key, this.createProcessor(sourceFormat, targetFormat));
}
return this.#processors.get(key);
}
/**
* 创建工具处理器
* @param {string} sourceFormat - 源格式
* @param {string} targetFormat - 目标格式
* @returns {ToolProcessor} 工具处理器实例
*/
static createProcessor(sourceFormat, targetFormat) {
console.warn(`Tool processor for ${sourceFormat} to ${targetFormat} not yet implemented`);
return null;
}
/**
* 清除所有缓存的处理器
*/
static clearCache() {
this.#processors.clear();
}
}
// 导出工厂类
export default ConverterFactory;

View file

@ -0,0 +1,25 @@
/**
* 转换器注册模块
* 用于注册所有转换器到工厂避免循环依赖问题
*/
import { MODEL_PROTOCOL_PREFIX } from '../common.js';
import { ConverterFactory } from './ConverterFactory.js';
import { OpenAIConverter } from './strategies/OpenAIConverter.js';
import { OpenAIResponsesConverter } from './strategies/OpenAIResponsesConverter.js';
import { ClaudeConverter } from './strategies/ClaudeConverter.js';
import { GeminiConverter } from './strategies/GeminiConverter.js';
/**
* 注册所有转换器到工厂
* 此函数应在应用启动时调用一次
*/
export function registerAllConverters() {
ConverterFactory.registerConverter(MODEL_PROTOCOL_PREFIX.OPENAI, OpenAIConverter);
ConverterFactory.registerConverter(MODEL_PROTOCOL_PREFIX.OPENAI_RESPONSES, OpenAIResponsesConverter);
ConverterFactory.registerConverter(MODEL_PROTOCOL_PREFIX.CLAUDE, ClaudeConverter);
ConverterFactory.registerConverter(MODEL_PROTOCOL_PREFIX.GEMINI, GeminiConverter);
}
// 自动注册所有转换器
registerAllConverters();

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,820 @@
/**
* Gemini转换器
* 处理GeminiGoogle协议与其他协议之间的转换
*/
import { v4 as uuidv4 } from 'uuid';
import { BaseConverter } from '../BaseConverter.js';
import {
checkAndAssignOrDefault
} from '../utils.js';
import { MODEL_PROTOCOL_PREFIX } from '../../common.js';
import {
generateResponseCreated,
generateResponseInProgress,
generateOutputItemAdded,
generateContentPartAdded,
generateOutputTextDone,
generateContentPartDone,
generateOutputItemDone,
generateResponseCompleted
} from '../../openai/openai-responses-core.mjs';
/**
* Gemini转换器类
* 实现Gemini协议到其他协议的转换
*/
export class GeminiConverter extends BaseConverter {
constructor() {
super('gemini');
}
/**
* 转换请求
*/
convertRequest(data, targetProtocol) {
switch (targetProtocol) {
case MODEL_PROTOCOL_PREFIX.OPENAI:
return this.toOpenAIRequest(data);
case MODEL_PROTOCOL_PREFIX.CLAUDE:
return this.toClaudeRequest(data);
case MODEL_PROTOCOL_PREFIX.OPENAI_RESPONSES:
return this.toOpenAIResponsesRequest(data);
default:
throw new Error(`Unsupported target protocol: ${targetProtocol}`);
}
}
/**
* 转换响应
*/
convertResponse(data, targetProtocol, model) {
switch (targetProtocol) {
case MODEL_PROTOCOL_PREFIX.OPENAI:
return this.toOpenAIResponse(data, model);
case MODEL_PROTOCOL_PREFIX.CLAUDE:
return this.toClaudeResponse(data, model);
case MODEL_PROTOCOL_PREFIX.OPENAI_RESPONSES:
return this.toOpenAIResponsesResponse(data, model);
default:
throw new Error(`Unsupported target protocol: ${targetProtocol}`);
}
}
/**
* 转换流式响应块
*/
convertStreamChunk(chunk, targetProtocol, model) {
switch (targetProtocol) {
case MODEL_PROTOCOL_PREFIX.OPENAI:
return this.toOpenAIStreamChunk(chunk, model);
case MODEL_PROTOCOL_PREFIX.CLAUDE:
return this.toClaudeStreamChunk(chunk, model);
case MODEL_PROTOCOL_PREFIX.OPENAI_RESPONSES:
return this.toOpenAIResponsesStreamChunk(chunk, model);
default:
throw new Error(`Unsupported target protocol: ${targetProtocol}`);
}
}
/**
* 转换模型列表
*/
convertModelList(data, targetProtocol) {
switch (targetProtocol) {
case MODEL_PROTOCOL_PREFIX.OPENAI:
return this.toOpenAIModelList(data);
case MODEL_PROTOCOL_PREFIX.CLAUDE:
return this.toClaudeModelList(data);
default:
return data;
}
}
// =========================================================================
// Gemini -> OpenAI 转换
// =========================================================================
/**
* Gemini请求 -> OpenAI请求
*/
toOpenAIRequest(geminiRequest) {
const openaiRequest = {
messages: [],
model: geminiRequest.model,
max_tokens: checkAndAssignOrDefault(geminiRequest.max_tokens, 8192),
temperature: checkAndAssignOrDefault(geminiRequest.temperature, 1),
top_p: checkAndAssignOrDefault(geminiRequest.top_p, 0.95),
};
// 处理系统指令
if (geminiRequest.systemInstruction && Array.isArray(geminiRequest.systemInstruction.parts)) {
const systemContent = this.processGeminiPartsToOpenAIContent(geminiRequest.systemInstruction.parts);
if (systemContent) {
openaiRequest.messages.push({
role: 'system',
content: systemContent
});
}
}
// 处理内容
if (geminiRequest.contents && Array.isArray(geminiRequest.contents)) {
geminiRequest.contents.forEach(content => {
if (content && Array.isArray(content.parts)) {
const openaiContent = this.processGeminiPartsToOpenAIContent(content.parts);
if (openaiContent && openaiContent.length > 0) {
const openaiRole = content.role === 'model' ? 'assistant' : content.role;
openaiRequest.messages.push({
role: openaiRole,
content: openaiContent
});
}
}
});
}
return openaiRequest;
}
/**
* Gemini响应 -> OpenAI响应
*/
toOpenAIResponse(geminiResponse, model) {
const content = this.processGeminiResponseContent(geminiResponse);
return {
id: `chatcmpl-${uuidv4()}`,
object: "chat.completion",
created: Math.floor(Date.now() / 1000),
model: model,
choices: [{
index: 0,
message: {
role: "assistant",
content: content
},
finish_reason: "stop",
}],
usage: geminiResponse.usageMetadata ? {
prompt_tokens: geminiResponse.usageMetadata.promptTokenCount || 0,
completion_tokens: geminiResponse.usageMetadata.candidatesTokenCount || 0,
total_tokens: geminiResponse.usageMetadata.totalTokenCount || 0,
} : {
prompt_tokens: 0,
completion_tokens: 0,
total_tokens: 0,
},
};
}
/**
* Gemini流式响应 -> OpenAI流式响应
*/
toOpenAIStreamChunk(geminiChunk, model) {
if (!geminiChunk) return null;
// 处理完整的Gemini chunk对象
if (typeof geminiChunk === 'object' && !Array.isArray(geminiChunk)) {
const candidate = geminiChunk.candidates?.[0];
// 提取文本内容
let content = '';
let finishReason = null;
if (candidate) {
// 从parts中提取文本
const parts = candidate.content?.parts;
if (parts && Array.isArray(parts)) {
content = parts
.filter(part => part && typeof part.text === 'string')
.map(part => part.text)
.join('');
}
// 处理finishReason
if (candidate.finishReason) {
finishReason = candidate.finishReason === 'STOP' ? 'stop' :
candidate.finishReason === 'MAX_TOKENS' ? 'length' :
candidate.finishReason.toLowerCase();
}
}
return {
id: `chatcmpl-${uuidv4()}`,
object: "chat.completion.chunk",
created: Math.floor(Date.now() / 1000),
model: model,
choices: [{
index: 0,
delta: content ? { content: content } : {},
finish_reason: finishReason,
}],
usage: geminiChunk.usageMetadata ? {
prompt_tokens: geminiChunk.usageMetadata.promptTokenCount || 0,
completion_tokens: geminiChunk.usageMetadata.candidatesTokenCount || 0,
total_tokens: geminiChunk.usageMetadata.totalTokenCount || 0,
} : {
prompt_tokens: 0,
completion_tokens: 0,
total_tokens: 0,
},
};
}
// 向后兼容:处理字符串格式
if (typeof geminiChunk === 'string') {
return {
id: `chatcmpl-${uuidv4()}`,
object: "chat.completion.chunk",
created: Math.floor(Date.now() / 1000),
model: model,
choices: [{
index: 0,
delta: { content: geminiChunk },
finish_reason: null,
}],
usage: {
prompt_tokens: 0,
completion_tokens: 0,
total_tokens: 0,
},
};
}
return null;
}
/**
* Gemini模型列表 -> OpenAI模型列表
*/
toOpenAIModelList(geminiModels) {
return {
object: "list",
data: geminiModels.models.map(m => ({
id: m.name.startsWith('models/') ? m.name.substring(7) : m.name,
object: "model",
created: Math.floor(Date.now() / 1000),
owned_by: "google",
})),
};
}
/**
* 处理Gemini parts到OpenAI内容
*/
processGeminiPartsToOpenAIContent(parts) {
if (!parts || !Array.isArray(parts)) return '';
const contentArray = [];
parts.forEach(part => {
if (!part) return;
if (typeof part.text === 'string') {
contentArray.push({
type: 'text',
text: part.text
});
}
if (part.inlineData) {
const { mimeType, data } = part.inlineData;
if (mimeType && data) {
contentArray.push({
type: 'image_url',
image_url: {
url: `data:${mimeType};base64,${data}`
}
});
}
}
if (part.fileData) {
const { mimeType, fileUri } = part.fileData;
if (mimeType && fileUri) {
if (mimeType.startsWith('image/')) {
contentArray.push({
type: 'image_url',
image_url: {
url: fileUri
}
});
} else if (mimeType.startsWith('audio/')) {
contentArray.push({
type: 'text',
text: `[Audio file: ${fileUri}]`
});
}
}
}
});
return contentArray.length === 1 && contentArray[0].type === 'text'
? contentArray[0].text
: contentArray;
}
/**
* 处理Gemini响应内容
*/
processGeminiResponseContent(geminiResponse) {
if (!geminiResponse || !geminiResponse.candidates) return '';
const contents = [];
geminiResponse.candidates.forEach(candidate => {
if (candidate.content && candidate.content.parts) {
candidate.content.parts.forEach(part => {
if (part.text) {
contents.push(part.text);
}
});
}
});
return contents.join('\n');
}
// =========================================================================
// Gemini -> Claude 转换
// =========================================================================
/**
* Gemini请求 -> Claude请求
*/
toClaudeRequest(geminiRequest) {
const claudeRequest = {
model: geminiRequest.model || 'claude-3-opus',
messages: [],
max_tokens: checkAndAssignOrDefault(geminiRequest.generationConfig?.maxOutputTokens, 8192),
temperature: checkAndAssignOrDefault(geminiRequest.generationConfig?.temperature, 1),
top_p: checkAndAssignOrDefault(geminiRequest.generationConfig?.topP, 0.95),
};
// 处理系统指令
if (geminiRequest.systemInstruction && geminiRequest.systemInstruction.parts) {
const systemText = geminiRequest.systemInstruction.parts
.filter(p => p.text)
.map(p => p.text)
.join('\n');
if (systemText) {
claudeRequest.system = systemText;
}
}
// 处理内容
if (geminiRequest.contents && Array.isArray(geminiRequest.contents)) {
geminiRequest.contents.forEach(content => {
if (!content || !content.parts) return;
const role = content.role === 'model' ? 'assistant' : 'user';
const claudeContent = this.processGeminiPartsToClaudeContent(content.parts);
if (claudeContent.length > 0) {
claudeRequest.messages.push({
role: role,
content: claudeContent
});
}
});
}
// 处理工具
if (geminiRequest.tools && geminiRequest.tools[0]?.functionDeclarations) {
claudeRequest.tools = geminiRequest.tools[0].functionDeclarations.map(func => ({
name: func.name,
description: func.description || '',
input_schema: func.parameters || { type: 'object', properties: {} }
}));
}
return claudeRequest;
}
/**
* Gemini响应 -> Claude响应
*/
toClaudeResponse(geminiResponse, model) {
if (!geminiResponse || !geminiResponse.candidates || geminiResponse.candidates.length === 0) {
return {
id: `msg_${uuidv4()}`,
type: "message",
role: "assistant",
content: [],
model: model,
stop_reason: "end_turn",
stop_sequence: null,
usage: {
input_tokens: geminiResponse?.usageMetadata?.promptTokenCount || 0,
output_tokens: geminiResponse?.usageMetadata?.candidatesTokenCount || 0
}
};
}
const candidate = geminiResponse.candidates[0];
const content = this.processGeminiResponseToClaudeContent(geminiResponse);
const finishReason = candidate.finishReason;
let stopReason = "end_turn";
if (finishReason) {
switch (finishReason) {
case 'STOP':
stopReason = 'end_turn';
break;
case 'MAX_TOKENS':
stopReason = 'max_tokens';
break;
case 'SAFETY':
stopReason = 'safety';
break;
case 'RECITATION':
stopReason = 'recitation';
break;
case 'OTHER':
stopReason = 'other';
break;
default:
stopReason = 'end_turn';
}
}
return {
id: `msg_${uuidv4()}`,
type: "message",
role: "assistant",
content: content,
model: model,
stop_reason: stopReason,
stop_sequence: null,
usage: {
input_tokens: geminiResponse.usageMetadata?.promptTokenCount || 0,
output_tokens: geminiResponse.usageMetadata?.candidatesTokenCount || 0
}
};
}
/**
* Gemini流式响应 -> Claude流式响应
*/
toClaudeStreamChunk(geminiChunk, model) {
if (!geminiChunk) return null;
// 处理完整的Gemini chunk对象
if (typeof geminiChunk === 'object' && !Array.isArray(geminiChunk)) {
const candidate = geminiChunk.candidates?.[0];
if (candidate) {
const parts = candidate.content?.parts;
// 提取文本内容
if (parts && Array.isArray(parts)) {
const textParts = parts.filter(part => part && typeof part.text === 'string');
if (textParts.length > 0) {
const text = textParts.map(part => part.text).join('');
return {
type: "content_block_delta",
index: 0,
delta: {
type: "text_delta",
text: text
}
};
}
}
// 处理finishReason
if (candidate.finishReason) {
return {
type: "message_delta",
delta: {
stop_reason: candidate.finishReason === 'STOP' ? 'end_turn' :
candidate.finishReason === 'MAX_TOKENS' ? 'max_tokens' :
candidate.finishReason.toLowerCase()
}
};
}
}
}
// 向后兼容:处理字符串格式
if (typeof geminiChunk === 'string') {
return {
type: "content_block_delta",
index: 0,
delta: {
type: "text_delta",
text: geminiChunk
}
};
}
return null;
}
/**
* Gemini模型列表 -> Claude模型列表
*/
toClaudeModelList(geminiModels) {
return {
models: geminiModels.models.map(m => ({
name: m.name.startsWith('models/') ? m.name.substring(7) : m.name,
description: "",
})),
};
}
/**
* 处理Gemini parts到Claude内容
*/
processGeminiPartsToClaudeContent(parts) {
if (!parts || !Array.isArray(parts)) return [];
const content = [];
parts.forEach(part => {
if (!part) return;
if (part.text) {
content.push({
type: 'text',
text: part.text
});
}
if (part.inlineData) {
content.push({
type: 'image',
source: {
type: 'base64',
media_type: part.inlineData.mimeType,
data: part.inlineData.data
}
});
}
if (part.functionCall) {
content.push({
type: 'tool_use',
id: uuidv4(),
name: part.functionCall.name,
input: part.functionCall.args || {}
});
}
if (part.functionResponse) {
content.push({
type: 'tool_result',
tool_use_id: part.functionResponse.name,
content: part.functionResponse.response
});
}
});
return content;
}
/**
* 处理Gemini响应到Claude内容
*/
processGeminiResponseToClaudeContent(geminiResponse) {
if (!geminiResponse || !geminiResponse.candidates || geminiResponse.candidates.length === 0) return [];
const content = [];
for (const candidate of geminiResponse.candidates) {
if (candidate.finishReason && candidate.finishReason !== 'STOP') {
if (candidate.finishMessage) {
content.push({
type: 'text',
text: `Error: ${candidate.finishMessage}`
});
}
continue;
}
if (candidate.content && candidate.content.parts) {
for (const part of candidate.content.parts) {
if (part.text) {
content.push({
type: 'text',
text: part.text
});
} else if (part.inlineData) {
content.push({
type: 'image',
source: {
type: 'base64',
media_type: part.inlineData.mimeType,
data: part.inlineData.data
}
});
} else if (part.functionCall) {
content.push({
type: 'tool_use',
id: uuidv4(),
name: part.functionCall.name,
input: part.functionCall.args || {}
});
}
}
}
}
return content;
}
// =========================================================================
// Gemini -> OpenAI Responses 转换
// =========================================================================
/**
* Gemini请求 -> OpenAI Responses请求
*/
toOpenAIResponsesRequest(geminiRequest) {
const responsesRequest = {
model: geminiRequest.model,
max_tokens: checkAndAssignOrDefault(geminiRequest.generationConfig?.maxOutputTokens, 8192),
temperature: checkAndAssignOrDefault(geminiRequest.generationConfig?.temperature, 1),
top_p: checkAndAssignOrDefault(geminiRequest.generationConfig?.topP, 0.95),
};
// 处理系统指令
if (geminiRequest.systemInstruction && geminiRequest.systemInstruction.parts) {
const instructionsText = geminiRequest.systemInstruction.parts
.filter(p => p.text)
.map(p => p.text)
.join('\n');
if (instructionsText) {
responsesRequest.instructions = instructionsText;
}
}
// 处理输入
if (geminiRequest.contents && Array.isArray(geminiRequest.contents)) {
const lastContent = geminiRequest.contents[geminiRequest.contents.length - 1];
if (lastContent && lastContent.parts) {
const inputText = lastContent.parts
.filter(p => p.text)
.map(p => p.text)
.join(' ');
if (inputText) {
responsesRequest.input = inputText;
}
}
}
return responsesRequest;
}
/**
* Gemini响应 -> OpenAI Responses响应
*/
toOpenAIResponsesResponse(geminiResponse, model) {
const content = this.processGeminiResponseContent(geminiResponse);
const textContent = typeof content === 'string' ? content : JSON.stringify(content);
let output = [];
output.push({
id: `msg_${uuidv4().replace(/-/g, '')}`,
summary: [],
type: "message",
role: "assistant",
status: "completed",
content: [{
annotations: [],
logprobs: [],
text: textContent,
type: "output_text"
}]
});
return {
background: false,
created_at: Math.floor(Date.now() / 1000),
error: null,
id: `resp_${uuidv4().replace(/-/g, '')}`,
incomplete_details: null,
max_output_tokens: null,
max_tool_calls: null,
metadata: {},
model: model,
object: "response",
output: output,
parallel_tool_calls: true,
previous_response_id: null,
prompt_cache_key: null,
reasoning: {},
safety_identifier: "user-" + uuidv4().replace(/-/g, ''),
service_tier: "default",
status: "completed",
store: false,
temperature: 1,
text: {
format: { type: "text" },
},
tool_choice: "auto",
tools: [],
top_logprobs: 0,
top_p: 1,
truncation: "disabled",
usage: {
input_tokens: geminiResponse.usageMetadata?.promptTokenCount || 0,
input_tokens_details: {
cached_tokens: geminiResponse.usageMetadata?.cachedTokens || 0,
},
output_tokens: geminiResponse.usageMetadata?.candidatesTokenCount || 0,
output_tokens_details: {
reasoning_tokens: 0
},
total_tokens: geminiResponse.usageMetadata?.totalTokenCount || 0,
},
user: null
};
}
/**
* Gemini流式响应 -> OpenAI Responses流式响应
*/
toOpenAIResponsesStreamChunk(geminiChunk, model, requestId = null) {
if (!geminiChunk) return [];
const responseId = requestId || `resp_${uuidv4().replace(/-/g, '')}`;
const events = [];
// 处理完整的Gemini chunk对象
if (typeof geminiChunk === 'object' && !Array.isArray(geminiChunk)) {
const candidate = geminiChunk.candidates?.[0];
if (candidate) {
const parts = candidate.content?.parts;
// 第一个chunk - 检测是否是开始有role
if (candidate.content?.role === 'model' && parts && parts.length > 0) {
// 只在第一次有内容时发送开始事件
const hasContent = parts.some(part => part && typeof part.text === 'string' && part.text.length > 0);
if (hasContent) {
events.push(
generateResponseCreated(responseId, model || 'unknown'),
generateResponseInProgress(responseId),
generateOutputItemAdded(responseId),
generateContentPartAdded(responseId)
);
}
}
// 提取文本内容
if (parts && Array.isArray(parts)) {
const textParts = parts.filter(part => part && typeof part.text === 'string');
if (textParts.length > 0) {
const text = textParts.map(part => part.text).join('');
events.push({
delta: text,
item_id: `msg_${uuidv4().replace(/-/g, '')}`,
output_index: 0,
sequence_number: 3,
type: "response.output_text.delta"
});
}
}
// 处理finishReason
if (candidate.finishReason) {
events.push(
generateOutputTextDone(responseId),
generateContentPartDone(responseId),
generateOutputItemDone(responseId),
generateResponseCompleted(responseId)
);
// 如果有 usage 信息,更新最后一个事件
if (geminiChunk.usageMetadata && events.length > 0) {
const lastEvent = events[events.length - 1];
if (lastEvent.response) {
lastEvent.response.usage = {
input_tokens: geminiChunk.usageMetadata.promptTokenCount || 0,
output_tokens: geminiChunk.usageMetadata.candidatesTokenCount || 0,
total_tokens: geminiChunk.usageMetadata.totalTokenCount || 0
};
}
}
}
}
}
// 向后兼容:处理字符串格式
if (typeof geminiChunk === 'string') {
events.push({
delta: geminiChunk,
item_id: `msg_${uuidv4().replace(/-/g, '')}`,
output_index: 0,
sequence_number: 3,
type: "response.output_text.delta"
});
}
return events;
}
}
export default GeminiConverter;

View file

@ -0,0 +1,997 @@
/**
* OpenAI转换器
* 处理OpenAI协议与其他协议之间的转换
*/
import { v4 as uuidv4 } from 'uuid';
import { BaseConverter } from '../BaseConverter.js';
import {
extractAndProcessSystemMessages as extractSystemMessages,
extractTextFromMessageContent as extractText,
safeParseJSON,
checkAndAssignOrDefault,
extractThinkingFromOpenAIText,
mapFinishReason,
cleanJsonSchemaProperties as cleanJsonSchema
} from '../utils.js';
import { MODEL_PROTOCOL_PREFIX } from '../../common.js';
import {
generateResponseCreated,
generateResponseInProgress,
generateOutputItemAdded,
generateContentPartAdded,
generateOutputTextDone,
generateContentPartDone,
generateOutputItemDone,
generateResponseCompleted
} from '../../openai/openai-responses-core.mjs';
/**
* OpenAI转换器类
* 实现OpenAI协议到其他协议的转换
*/
export class OpenAIConverter extends BaseConverter {
constructor() {
super('openai');
}
/**
* 转换请求
*/
convertRequest(data, targetProtocol) {
switch (targetProtocol) {
case MODEL_PROTOCOL_PREFIX.CLAUDE:
return this.toClaudeRequest(data);
case MODEL_PROTOCOL_PREFIX.GEMINI:
return this.toGeminiRequest(data);
case MODEL_PROTOCOL_PREFIX.OPENAI_RESPONSES:
return this.toOpenAIResponsesRequest(data);
default:
throw new Error(`Unsupported target protocol: ${targetProtocol}`);
}
}
/**
* 转换响应
*/
convertResponse(data, targetProtocol, model) {
// OpenAI作为源格式时通常不需要转换响应
// 因为其他协议会转换到OpenAI格式
switch (targetProtocol) {
case MODEL_PROTOCOL_PREFIX.CLAUDE:
return this.toClaudeResponse(data, model);
case MODEL_PROTOCOL_PREFIX.GEMINI:
return this.toGeminiResponse(data, model);
case MODEL_PROTOCOL_PREFIX.OPENAI_RESPONSES:
return this.toOpenAIResponsesResponse(data, model);
default:
throw new Error(`Unsupported target protocol: ${targetProtocol}`);
}
}
/**
* 转换流式响应块
*/
convertStreamChunk(chunk, targetProtocol, model) {
switch (targetProtocol) {
case MODEL_PROTOCOL_PREFIX.CLAUDE:
return this.toClaudeStreamChunk(chunk, model);
case MODEL_PROTOCOL_PREFIX.GEMINI:
return this.toGeminiStreamChunk(chunk, model);
case MODEL_PROTOCOL_PREFIX.OPENAI_RESPONSES:
return this.toOpenAIResponsesStreamChunk(chunk, model);
default:
throw new Error(`Unsupported target protocol: ${targetProtocol}`);
}
}
/**
* 转换模型列表
*/
convertModelList(data, targetProtocol) {
switch (targetProtocol) {
case MODEL_PROTOCOL_PREFIX.CLAUDE:
return this.toClaudeModelList(data);
case MODEL_PROTOCOL_PREFIX.GEMINI:
return this.toGeminiModelList(data);
default:
return data;
}
}
// =========================================================================
// OpenAI -> Claude 转换
// =========================================================================
/**
* OpenAI请求 -> Claude请求
*/
toClaudeRequest(openaiRequest) {
const messages = openaiRequest.messages || [];
const { systemInstruction, nonSystemMessages } = extractSystemMessages(messages);
const claudeMessages = [];
for (const message of nonSystemMessages) {
const role = message.role === 'assistant' ? 'assistant' : 'user';
let content = [];
if (message.role === 'tool') {
// 工具结果消息
content.push({
type: 'tool_result',
tool_use_id: message.tool_call_id,
content: safeParseJSON(message.content)
});
claudeMessages.push({ role: 'user', content: content });
} else if (message.role === 'assistant' && (message.tool_calls?.length || message.function_calls?.length)) {
// 助手工具调用消息 - 支持tool_calls和function_calls
const calls = message.tool_calls || message.function_calls || [];
const toolUseBlocks = calls.map(tc => ({
type: 'tool_use',
id: tc.id,
name: tc.function.name,
input: safeParseJSON(tc.function.arguments)
}));
claudeMessages.push({ role: 'assistant', content: toolUseBlocks });
} else {
// 普通消息
if (typeof message.content === 'string') {
if (message.content) {
content.push({ type: 'text', text: message.content });
}
} else if (Array.isArray(message.content)) {
message.content.forEach(item => {
if (!item) return;
switch (item.type) {
case 'text':
if (item.text) {
content.push({ type: 'text', text: item.text });
}
break;
case 'image_url':
if (item.image_url) {
const imageUrl = typeof item.image_url === 'string'
? item.image_url
: item.image_url.url;
if (imageUrl.startsWith('data:')) {
const [header, data] = imageUrl.split(',');
const mediaType = header.match(/data:([^;]+)/)?.[1] || 'image/jpeg';
content.push({
type: 'image',
source: {
type: 'base64',
media_type: mediaType,
data: data
}
});
} else {
content.push({ type: 'text', text: `[Image: ${imageUrl}]` });
}
}
break;
case 'audio':
if (item.audio_url) {
const audioUrl = typeof item.audio_url === 'string'
? item.audio_url
: item.audio_url.url;
content.push({ type: 'text', text: `[Audio: ${audioUrl}]` });
}
break;
}
});
}
if (content.length > 0) {
claudeMessages.push({ role: role, content: content });
}
}
}
// 合并相邻相同 role 的消息
const mergedClaudeMessages = [];
for (let i = 0; i < claudeMessages.length; i++) {
const currentMessage = claudeMessages[i];
if (mergedClaudeMessages.length === 0) {
mergedClaudeMessages.push(currentMessage);
} else {
const lastMessage = mergedClaudeMessages[mergedClaudeMessages.length - 1];
// 如果当前消息的 role 与上一条消息的 role 相同,则合并 content 数组
if (lastMessage.role === currentMessage.role) {
lastMessage.content = lastMessage.content.concat(currentMessage.content);
} else {
mergedClaudeMessages.push(currentMessage);
}
}
}
const claudeRequest = {
model: openaiRequest.model,
messages: mergedClaudeMessages,
max_tokens: checkAndAssignOrDefault(openaiRequest.max_tokens, 8192),
temperature: checkAndAssignOrDefault(openaiRequest.temperature, 1),
top_p: checkAndAssignOrDefault(openaiRequest.top_p, 0.95),
};
if (systemInstruction) {
claudeRequest.system = extractText(systemInstruction.parts[0].text);
}
if (openaiRequest.tools?.length) {
claudeRequest.tools = openaiRequest.tools.map(t => ({
name: t.function.name,
description: t.function.description || '',
input_schema: t.function.parameters || { type: 'object', properties: {} }
}));
claudeRequest.tool_choice = this.buildClaudeToolChoice(openaiRequest.tool_choice);
}
return claudeRequest;
}
/**
* OpenAI响应 -> Claude响应
*/
toClaudeResponse(openaiResponse, model) {
if (!openaiResponse || !openaiResponse.choices || openaiResponse.choices.length === 0) {
return {
id: `msg_${uuidv4()}`,
type: "message",
role: "assistant",
content: [],
model: model,
stop_reason: "end_turn",
stop_sequence: null,
usage: {
input_tokens: openaiResponse?.usage?.prompt_tokens || 0,
output_tokens: openaiResponse?.usage?.completion_tokens || 0
}
};
}
const choice = openaiResponse.choices[0];
const contentList = [];
// 处理工具调用 - 支持tool_calls和function_calls
const toolCalls = choice.message?.tool_calls || choice.message?.function_calls || [];
for (const toolCall of toolCalls.filter(tc => tc && typeof tc === 'object')) {
if (toolCall.function) {
const func = toolCall.function;
const argStr = func.arguments || "{}";
let argObj;
try {
argObj = typeof argStr === 'string' ? JSON.parse(argStr) : argStr;
} catch (e) {
argObj = {};
}
contentList.push({
type: "tool_use",
id: toolCall.id || "",
name: func.name || "",
input: argObj,
});
}
}
// 处理reasoning_content推理内容
const reasoningContent = choice.message?.reasoning_content || "";
if (reasoningContent) {
contentList.push({
type: "thinking",
thinking: reasoningContent
});
}
// 处理文本内容
const contentText = choice.message?.content || "";
if (contentText) {
const extractedContent = extractThinkingFromOpenAIText(contentText);
if (Array.isArray(extractedContent)) {
contentList.push(...extractedContent);
} else {
contentList.push({ type: "text", text: extractedContent });
}
}
// 映射结束原因
const stopReason = mapFinishReason(
choice.finish_reason || "stop",
"openai",
"anthropic"
);
return {
id: `msg_${uuidv4()}`,
type: "message",
role: "assistant",
content: contentList,
model: model,
stop_reason: stopReason,
stop_sequence: null,
usage: {
input_tokens: openaiResponse.usage?.prompt_tokens || 0,
output_tokens: openaiResponse.usage?.completion_tokens || 0
}
};
}
/**
* OpenAI流式响应 -> Claude流式响应
*
* 这个方法实现了与 ClaudeConverter.toOpenAIStreamChunk 相反的转换逻辑
* OpenAI 的流式 chunk 转换为 Claude 的流式事件
*/
toClaudeStreamChunk(openaiChunk, model) {
if (!openaiChunk) return null;
// 处理 OpenAI chunk 对象
if (typeof openaiChunk === 'object' && !Array.isArray(openaiChunk)) {
const choice = openaiChunk.choices?.[0];
if (!choice){
return null;
}
const delta = choice.delta;
const finishReason = choice.finish_reason;
const events = [];
// 注释部分是为了兼容claude code但是不兼容cherry studio
// 1. 处理 role (对应 message_start)
// if (delta?.role === "assistant") {
// events.push({
// type: "message_start",
// message: {
// id: openaiChunk.id || `msg_${uuidv4()}`,
// type: "message",
// role: "assistant",
// content: [],
// model: model || openaiChunk.model || "unknown",
// stop_reason: null,
// stop_sequence: null,
// usage: {
// input_tokens: openaiChunk.usage?.prompt_tokens || 0,
// output_tokens: 0
// }
// }
// });
// events.push({
// type: "content_block_start",
// index: 0,
// content_block: {
// type: "text",
// text: ""
// }
// });
// }
// 2. 处理 tool_calls (对应 content_block_start 和 content_block_delta)
// if (delta?.tool_calls) {
// const toolCalls = delta.tool_calls;
// for (const toolCall of toolCalls) {
// // 如果有 function.name说明是工具调用开始
// if (toolCall.function?.name) {
// events.push({
// type: "content_block_start",
// index: toolCall.index || 0,
// content_block: {
// type: "tool_use",
// id: toolCall.id || `tool_${uuidv4()}`,
// name: toolCall.function.name,
// input: {}
// }
// });
// }
// // 如果有 function.arguments说明是参数增量
// if (toolCall.function?.arguments) {
// events.push({
// type: "content_block_delta",
// index: toolCall.index || 0,
// delta: {
// type: "input_json_delta",
// partial_json: toolCall.function.arguments
// }
// });
// }
// }
// }
// 3. 处理 reasoning_content (对应 thinking 类型的 content_block)
if (delta?.reasoning_content) {
// 注意:这里可能需要先发送 content_block_start但由于状态管理复杂
// 我们假设调用方会处理这个逻辑
events.push({
type: "content_block_delta",
index: 0,
delta: {
type: "thinking_delta",
thinking: delta.reasoning_content
}
});
}
// 4. 处理普通文本 content (对应 text 类型的 content_block)
if (delta?.content) {
events.push({
type: "content_block_delta",
index: 0,
delta: {
type: "text_delta",
text: delta.content
}
});
}
// 5. 处理 finish_reason (对应 message_delta 和 message_stop)
if (finishReason) {
// 映射 finish_reason
const stopReason = finishReason === "stop" ? "end_turn" :
finishReason === "length" ? "max_tokens" :
"end_turn";
events.push({
type: "content_block_stop",
index: 0
});
// 发送 message_delta
events.push({
type: "message_delta",
delta: {
stop_reason: stopReason,
stop_sequence: null
},
usage: {
output_tokens: openaiChunk.usage?.completion_tokens || 0,
input_tokens: openaiChunk.usage?.prompt_tokens || 0,
}
});
// 发送 message_stop
events.push({
type: "message_stop"
});
}
return events.length > 0 ? events : null;
}
// 向后兼容:处理字符串格式
if (typeof openaiChunk === 'string') {
return {
type: "content_block_delta",
index: 0,
delta: {
type: "text_delta",
text: openaiChunk
}
};
}
return null;
}
/**
* OpenAI模型列表 -> Claude模型列表
*/
toClaudeModelList(openaiModels) {
return {
models: openaiModels.data.map(m => ({
name: m.id,
description: "",
})),
};
}
/**
* OpenAI 模型列表转换为 Gemini 模型列表
*/
toGeminiModelList(openaiModels) {
const models = openaiModels.data || [];
return {
models: models.map(m => ({
name: `models/${m.id}`,
version: m.version || "1.0.0",
displayName: m.displayName || m.id,
description: m.description || `A generative model for text and chat generation. ID: ${m.id}`,
inputTokenLimit: m.inputTokenLimit || 32768,
outputTokenLimit: m.outputTokenLimit || 8192,
supportedGenerationMethods: m.supportedGenerationMethods || ["generateContent", "streamGenerateContent"]
}))
};
}
/**
* 构建Claude工具选择
*/
buildClaudeToolChoice(toolChoice) {
if (typeof toolChoice === 'string') {
const mapping = { auto: 'auto', none: 'none', required: 'any' };
return { type: mapping[toolChoice] };
}
if (typeof toolChoice === 'object' && toolChoice.function) {
return { type: 'tool', name: toolChoice.function.name };
}
return undefined;
}
// =========================================================================
// OpenAI -> Gemini 转换
// =========================================================================
/**
* OpenAI请求 -> Gemini请求
*/
toGeminiRequest(openaiRequest) {
const messages = openaiRequest.messages || [];
const { systemInstruction, nonSystemMessages } = extractSystemMessages(messages);
const processedMessages = [];
let lastMessage = null;
for (const message of nonSystemMessages) {
const geminiRole = message.role === 'assistant' ? 'model' : message.role;
if (geminiRole === 'tool') {
if (lastMessage) processedMessages.push(lastMessage);
processedMessages.push({
role: 'function',
parts: [{
functionResponse: {
name: message.name,
response: { content: safeParseJSON(message.content) }
}
}]
});
lastMessage = null;
continue;
}
const processedContent = this.processOpenAIContentToGeminiParts(message.content);
if (lastMessage && lastMessage.role === geminiRole && !message.tool_calls &&
Array.isArray(processedContent) && processedContent.every(p => p.text) &&
Array.isArray(lastMessage.parts) && lastMessage.parts.every(p => p.text)) {
lastMessage.parts.push(...processedContent);
continue;
}
if (lastMessage) processedMessages.push(lastMessage);
lastMessage = { role: geminiRole, parts: processedContent };
}
if (lastMessage) processedMessages.push(lastMessage);
const geminiRequest = {
contents: processedMessages.filter(item => item.parts && item.parts.length > 0)
};
if (systemInstruction) geminiRequest.systemInstruction = systemInstruction;
if (openaiRequest.tools?.length) {
geminiRequest.tools = [{
functionDeclarations: openaiRequest.tools.map(t => {
if (!t || typeof t !== 'object' || !t.function) return null;
const func = t.function;
const parameters = cleanJsonSchema(func.parameters || {});
return {
name: String(func.name || ''),
description: String(func.description || ''),
parameters: parameters
};
}).filter(Boolean)
}];
if (geminiRequest.tools[0].functionDeclarations.length === 0) {
delete geminiRequest.tools;
}
}
if (openaiRequest.tool_choice) {
geminiRequest.toolConfig = this.buildGeminiToolConfig(openaiRequest.tool_choice);
}
const config = this.buildGeminiGenerationConfig(openaiRequest);
if (Object.keys(config).length) geminiRequest.generationConfig = config;
return geminiRequest;
}
/**
* 处理OpenAI内容到Gemini parts
*/
processOpenAIContentToGeminiParts(content) {
if (!content) return [];
if (typeof content === 'string') return [{ text: content }];
if (Array.isArray(content)) {
const parts = [];
for (const item of content) {
if (!item) continue;
if (item.type === 'text' && item.text) {
parts.push({ text: item.text });
} else if (item.type === 'image_url' && item.image_url) {
const imageUrl = typeof item.image_url === 'string'
? item.image_url
: item.image_url.url;
if (imageUrl.startsWith('data:')) {
const [header, data] = imageUrl.split(',');
const mimeType = header.match(/data:([^;]+)/)?.[1] || 'image/jpeg';
parts.push({ inlineData: { mimeType, data } });
} else {
parts.push({
fileData: { mimeType: 'image/jpeg', fileUri: imageUrl }
});
}
}
}
return parts;
}
return [];
}
/**
* 构建Gemini工具配置
*/
buildGeminiToolConfig(toolChoice) {
if (typeof toolChoice === 'string' && ['none', 'auto'].includes(toolChoice)) {
return { functionCallingConfig: { mode: toolChoice.toUpperCase() } };
}
if (typeof toolChoice === 'object' && toolChoice.function) {
return { functionCallingConfig: { mode: 'ANY', allowedFunctionNames: [toolChoice.function.name] } };
}
return null;
}
/**
* 构建Gemini生成配置
*/
buildGeminiGenerationConfig({ temperature, max_tokens, top_p, stop }) {
const config = {};
config.temperature = checkAndAssignOrDefault(temperature, 1);
config.maxOutputTokens = checkAndAssignOrDefault(max_tokens, 65535);
config.topP = checkAndAssignOrDefault(top_p, 0.95);
if (stop !== undefined) config.stopSequences = Array.isArray(stop) ? stop : [stop];
return config;
}
/**
* 将OpenAI响应转换为Gemini响应格式
*/
toGeminiResponse(openaiResponse, model) {
if (!openaiResponse || !openaiResponse.choices || !openaiResponse.choices[0]) {
return { candidates: [], usageMetadata: {} };
}
const choice = openaiResponse.choices[0];
const message = choice.message || {};
const parts = [];
// 处理文本内容
if (message.content) {
parts.push({ text: message.content });
}
// 处理工具调用
if (message.tool_calls && message.tool_calls.length > 0) {
for (const toolCall of message.tool_calls) {
if (toolCall.type === 'function') {
parts.push({
functionCall: {
name: toolCall.function.name,
args: typeof toolCall.function.arguments === 'string'
? JSON.parse(toolCall.function.arguments)
: toolCall.function.arguments
}
});
}
}
}
// 映射finish_reason
const finishReasonMap = {
'stop': 'STOP',
'length': 'MAX_TOKENS',
'tool_calls': 'STOP',
'content_filter': 'SAFETY'
};
return {
candidates: [{
content: {
role: 'model',
parts: parts
},
finishReason: finishReasonMap[choice.finish_reason] || 'STOP'
}],
usageMetadata: openaiResponse.usage ? {
promptTokenCount: openaiResponse.usage.prompt_tokens || 0,
candidatesTokenCount: openaiResponse.usage.completion_tokens || 0,
totalTokenCount: openaiResponse.usage.total_tokens || 0
} : {}
};
}
/**
* 将OpenAI流式响应块转换为Gemini流式响应格式
*/
toGeminiStreamChunk(openaiChunk, model) {
if (!openaiChunk || !openaiChunk.choices || !openaiChunk.choices[0]) {
return null;
}
const choice = openaiChunk.choices[0];
const delta = choice.delta || {};
const parts = [];
// 处理文本内容
if (delta.content) {
parts.push({ text: delta.content });
}
// 处理工具调用
if (delta.tool_calls && delta.tool_calls.length > 0) {
for (const toolCall of delta.tool_calls) {
if (toolCall.function) {
const functionCall = {
name: toolCall.function.name || '',
args: {}
};
if (toolCall.function.arguments) {
try {
functionCall.args = typeof toolCall.function.arguments === 'string'
? JSON.parse(toolCall.function.arguments)
: toolCall.function.arguments;
} catch (e) {
// 部分参数,保持为字符串
functionCall.args = { partial: toolCall.function.arguments };
}
}
parts.push({ functionCall });
}
}
}
const result = {
candidates: [{
content: {
role: 'model',
parts: parts
}
}]
};
// 添加finish_reason如果存在
if (choice.finish_reason) {
const finishReasonMap = {
'stop': 'STOP',
'length': 'MAX_TOKENS',
'tool_calls': 'STOP',
'content_filter': 'SAFETY'
};
result.candidates[0].finishReason = finishReasonMap[choice.finish_reason] || 'STOP';
}
// 添加usage信息如果存在
if (openaiChunk.usage) {
result.usageMetadata = {
promptTokenCount: openaiChunk.usage.prompt_tokens || 0,
candidatesTokenCount: openaiChunk.usage.completion_tokens || 0,
totalTokenCount: openaiChunk.usage.total_tokens || 0
};
}
return result;
}
/**
* 将OpenAI请求转换为OpenAI Responses格式
*/
toOpenAIResponsesRequest(openaiRequest) {
const responsesRequest = {
model: openaiRequest.model,
messages: []
};
// 转换messages
if (openaiRequest.messages && openaiRequest.messages.length > 0) {
responsesRequest.messages = openaiRequest.messages.map(msg => ({
role: msg.role,
content: typeof msg.content === 'string'
? [{ type: 'input_text', text: msg.content }]
: msg.content
}));
}
// 转换其他参数
if (openaiRequest.temperature !== undefined) {
responsesRequest.temperature = openaiRequest.temperature;
}
if (openaiRequest.max_tokens !== undefined) {
responsesRequest.max_output_tokens = openaiRequest.max_tokens;
}
if (openaiRequest.top_p !== undefined) {
responsesRequest.top_p = openaiRequest.top_p;
}
if (openaiRequest.tools) {
responsesRequest.tools = openaiRequest.tools;
}
if (openaiRequest.tool_choice) {
responsesRequest.tool_choice = openaiRequest.tool_choice;
}
return responsesRequest;
}
/**
* 将OpenAI响应转换为OpenAI Responses格式
*/
toOpenAIResponsesResponse(openaiResponse, model) {
if (!openaiResponse || !openaiResponse.choices || !openaiResponse.choices[0]) {
return {
id: `resp_${Date.now()}`,
object: 'response',
created_at: Math.floor(Date.now() / 1000),
status: 'completed',
model: model || 'unknown',
output: [],
usage: {
input_tokens: 0,
output_tokens: 0,
total_tokens: 0
}
};
}
const choice = openaiResponse.choices[0];
const message = choice.message || {};
const output = [];
// 构建message输出
const messageContent = [];
if (message.content) {
messageContent.push({
type: 'output_text',
text: message.content
});
}
output.push({
type: 'message',
id: `msg_${Date.now()}`,
status: 'completed',
role: 'assistant',
content: messageContent
});
return {
id: openaiResponse.id || `resp_${Date.now()}`,
object: 'response',
created_at: openaiResponse.created || Math.floor(Date.now() / 1000),
status: choice.finish_reason === 'stop' ? 'completed' : 'in_progress',
model: model || openaiResponse.model || 'unknown',
output: output,
usage: openaiResponse.usage ? {
input_tokens: openaiResponse.usage.prompt_tokens || 0,
output_tokens: openaiResponse.usage.completion_tokens || 0,
total_tokens: openaiResponse.usage.total_tokens || 0
} : {
input_tokens: 0,
output_tokens: 0,
total_tokens: 0
}
};
}
/**
* 将OpenAI流式响应转换为OpenAI Responses流式格式
* 参考 ClaudeConverter.toOpenAIResponsesStreamChunk 的实现逻辑
*/
toOpenAIResponsesStreamChunk(openaiChunk, model, requestId = null) {
if (!openaiChunk || !openaiChunk.choices || !openaiChunk.choices[0]) {
return [];
}
const responseId = requestId || `resp_${uuidv4().replace(/-/g, '')}`;
const choice = openaiChunk.choices[0];
const delta = choice.delta || {};
const events = [];
// 第一个chunk - role为assistant时调用 getOpenAIResponsesStreamChunkBegin
if (delta.role === 'assistant') {
events.push(
generateResponseCreated(responseId, model || openaiChunk.model || 'unknown'),
generateResponseInProgress(responseId),
generateOutputItemAdded(responseId),
generateContentPartAdded(responseId)
);
}
// 处理 reasoning_content推理内容
if (delta.reasoning_content) {
events.push({
delta: delta.reasoning_content,
item_id: `thinking_${uuidv4().replace(/-/g, '')}`,
output_index: 0,
sequence_number: 3,
type: "response.reasoning_summary_text.delta"
});
}
// 处理 tool_calls工具调用
if (delta.tool_calls && delta.tool_calls.length > 0) {
for (const toolCall of delta.tool_calls) {
const outputIndex = toolCall.index || 0;
// 如果有 function.name说明是工具调用开始
if (toolCall.function && toolCall.function.name) {
events.push({
item: {
id: toolCall.id || `call_${uuidv4().replace(/-/g, '')}`,
type: "function_call",
name: toolCall.function.name,
arguments: "",
status: "in_progress"
},
output_index: outputIndex,
sequence_number: 2,
type: "response.output_item.added"
});
}
// 如果有 function.arguments说明是参数增量
if (toolCall.function && toolCall.function.arguments) {
events.push({
delta: toolCall.function.arguments,
item_id: toolCall.id || `call_${uuidv4().replace(/-/g, '')}`,
output_index: outputIndex,
sequence_number: 3,
type: "response.custom_tool_call_input.delta"
});
}
}
}
// 处理普通文本内容
if (delta.content) {
events.push({
delta: delta.content,
item_id: `msg_${uuidv4().replace(/-/g, '')}`,
output_index: 0,
sequence_number: 3,
type: "response.output_text.delta"
});
}
// 处理完成状态 - 调用 getOpenAIResponsesStreamChunkEnd
if (choice.finish_reason) {
events.push(
generateOutputTextDone(responseId),
generateContentPartDone(responseId),
generateOutputItemDone(responseId),
generateResponseCompleted(responseId)
);
// 如果有 usage 信息,更新最后一个事件
if (openaiChunk.usage && events.length > 0) {
const lastEvent = events[events.length - 1];
if (lastEvent.response) {
lastEvent.response.usage = {
input_tokens: openaiChunk.usage.prompt_tokens || 0,
output_tokens: openaiChunk.usage.completion_tokens || 0,
total_tokens: openaiChunk.usage.total_tokens || 0
};
}
}
}
return events;
}
}
export default OpenAIConverter;

View file

@ -0,0 +1,532 @@
/**
* OpenAI Responses API 转换器
* 处理 OpenAI Responses API 格式与其他协议之间的转换
*/
import { BaseConverter } from '../BaseConverter.js';
import { MODEL_PROTOCOL_PREFIX } from '../../common.js';
import {
extractAndProcessSystemMessages as extractSystemMessages,
extractTextFromMessageContent as extractText
} from '../utils.js';
/**
* OpenAI Responses API 转换器类
* 支持 OpenAI Responses 格式与 OpenAIClaudeGemini 之间的转换
*/
export class OpenAIResponsesConverter extends BaseConverter {
constructor() {
super(MODEL_PROTOCOL_PREFIX.OPENAI_RESPONSES);
}
// =============================================================================
// 请求转换
// =============================================================================
/**
* 转换请求到目标协议
*/
convertRequest(data, toProtocol) {
switch (toProtocol) {
case MODEL_PROTOCOL_PREFIX.OPENAI:
return this.toOpenAIRequest(data);
case MODEL_PROTOCOL_PREFIX.CLAUDE:
return this.toClaudeRequest(data);
case MODEL_PROTOCOL_PREFIX.GEMINI:
return this.toGeminiRequest(data);
default:
throw new Error(`Unsupported target protocol: ${toProtocol}`);
}
}
/**
* 转换响应到目标协议
*/
convertResponse(data, toProtocol, model) {
switch (toProtocol) {
case MODEL_PROTOCOL_PREFIX.OPENAI:
return this.toOpenAIResponse(data, model);
case MODEL_PROTOCOL_PREFIX.CLAUDE:
return this.toClaudeResponse(data, model);
case MODEL_PROTOCOL_PREFIX.GEMINI:
return this.toGeminiResponse(data, model);
default:
throw new Error(`Unsupported target protocol: ${toProtocol}`);
}
}
/**
* 转换流式响应块到目标协议
*/
convertStreamChunk(chunk, toProtocol, model) {
switch (toProtocol) {
case MODEL_PROTOCOL_PREFIX.OPENAI:
return this.toOpenAIStreamChunk(chunk, model);
case MODEL_PROTOCOL_PREFIX.CLAUDE:
return this.toClaudeStreamChunk(chunk, model);
case MODEL_PROTOCOL_PREFIX.GEMINI:
return this.toGeminiStreamChunk(chunk, model);
default:
throw new Error(`Unsupported target protocol: ${toProtocol}`);
}
}
/**
* 转换模型列表到目标协议
*/
convertModelList(data, targetProtocol) {
switch (targetProtocol) {
case MODEL_PROTOCOL_PREFIX.OPENAI:
return this.toOpenAIModelList(data);
case MODEL_PROTOCOL_PREFIX.CLAUDE:
return this.toClaudeModelList(data);
case MODEL_PROTOCOL_PREFIX.GEMINI:
return this.toGeminiModelList(data);
default:
return data;
}
}
// =============================================================================
// 转换到 OpenAI 格式
// =============================================================================
/**
* OpenAI Responses 请求转换为标准 OpenAI 请求
*/
toOpenAIRequest(responsesRequest) {
const openaiRequest = {
model: responsesRequest.model,
messages: [],
stream: responsesRequest.stream || false
};
// OpenAI Responses API 使用 instructions 和 input 字段
// 需要转换为标准的 messages 格式
if (responsesRequest.instructions) {
// instructions 作为系统消息
openaiRequest.messages.push({
role: 'system',
content: responsesRequest.instructions
});
}
// input 包含用户消息和历史对话
if (responsesRequest.input && Array.isArray(responsesRequest.input)) {
responsesRequest.input.forEach(item => {
if (item.type === 'message') {
// 提取消息内容
const content = item.content
.filter(c => c.type === 'input_text')
.map(c => c.text)
.join('\n');
if (content) {
openaiRequest.messages.push({
role: item.role,
content: content
});
}
}
});
}
// 如果有标准的 messages 字段,也支持
if (responsesRequest.messages && Array.isArray(responsesRequest.messages)) {
responsesRequest.messages.forEach(msg => {
openaiRequest.messages.push({
role: msg.role,
content: msg.content
});
});
}
// 复制其他参数
if (responsesRequest.temperature !== undefined) {
openaiRequest.temperature = responsesRequest.temperature;
}
if (responsesRequest.max_tokens !== undefined) {
openaiRequest.max_tokens = responsesRequest.max_tokens;
}
if (responsesRequest.top_p !== undefined) {
openaiRequest.top_p = responsesRequest.top_p;
}
return openaiRequest;
}
/**
* OpenAI Responses 响应转换为标准 OpenAI 响应
*/
toOpenAIResponse(responsesResponse, model) {
// OpenAI Responses 格式已经很接近标准 OpenAI 格式
return {
id: responsesResponse.id || `chatcmpl-${Date.now()}`,
object: 'chat.completion',
created: responsesResponse.created || Math.floor(Date.now() / 1000),
model: model || responsesResponse.model,
choices: responsesResponse.choices || [{
index: 0,
message: {
role: 'assistant',
content: responsesResponse.content || ''
},
finish_reason: responsesResponse.finish_reason || 'stop'
}],
usage: responsesResponse.usage || {
prompt_tokens: 0,
completion_tokens: 0,
total_tokens: 0
}
};
}
/**
* OpenAI Responses 流式块转换为标准 OpenAI 流式块
*/
toOpenAIStreamChunk(responsesChunk, model) {
return {
id: responsesChunk.id || `chatcmpl-${Date.now()}`,
object: 'chat.completion.chunk',
created: responsesChunk.created || Math.floor(Date.now() / 1000),
model: model || responsesChunk.model,
choices: responsesChunk.choices || [{
index: 0,
delta: {
content: responsesChunk.delta?.content || ''
},
finish_reason: responsesChunk.finish_reason || null
}]
};
}
// =============================================================================
// 转换到 Claude 格式
// =============================================================================
/**
* OpenAI Responses 请求转换为 Claude 请求
*/
toClaudeRequest(responsesRequest) {
const claudeRequest = {
model: responsesRequest.model,
messages: [],
max_tokens: responsesRequest.max_tokens || 4096,
stream: responsesRequest.stream || false
};
// 处理 instructions 作为系统消息
if (responsesRequest.instructions) {
claudeRequest.system = responsesRequest.instructions;
}
// 处理 input 数组中的消息
if (responsesRequest.input && Array.isArray(responsesRequest.input)) {
responsesRequest.input.forEach(item => {
if (item.type === 'message') {
const content = item.content
.filter(c => c.type === 'input_text')
.map(c => c.text)
.join('\n');
if (content) {
claudeRequest.messages.push({
role: item.role === 'assistant' ? 'assistant' : 'user',
content: content
});
}
}
});
}
// 如果有标准的 messages 字段,也支持
if (responsesRequest.messages && Array.isArray(responsesRequest.messages)) {
if (!claudeRequest.system && systemMessages.length > 0) {
const systemTexts = systemMessages.map(msg => extractText(msg.content));
claudeRequest.system = systemTexts.join('\n');
}
otherMessages.forEach(msg => {
claudeRequest.messages.push({
role: msg.role === 'assistant' ? 'assistant' : 'user',
content: typeof msg.content === 'string' ? msg.content : extractText(msg.content)
});
});
}
// 复制其他参数
if (responsesRequest.temperature !== undefined) {
claudeRequest.temperature = responsesRequest.temperature;
}
if (responsesRequest.top_p !== undefined) {
claudeRequest.top_p = responsesRequest.top_p;
}
return claudeRequest;
}
/**
* OpenAI Responses 响应转换为 Claude 响应
*/
toClaudeResponse(responsesResponse, model) {
const content = responsesResponse.choices?.[0]?.message?.content ||
responsesResponse.content || '';
return {
id: responsesResponse.id || `msg_${Date.now()}`,
type: 'message',
role: 'assistant',
content: [{
type: 'text',
text: content
}],
model: model || responsesResponse.model,
stop_reason: responsesResponse.choices?.[0]?.finish_reason || 'end_turn',
usage: {
input_tokens: responsesResponse.usage?.prompt_tokens || 0,
output_tokens: responsesResponse.usage?.completion_tokens || 0
}
};
}
/**
* OpenAI Responses 流式块转换为 Claude 流式块
*/
toClaudeStreamChunk(responsesChunk, model) {
const delta = responsesChunk.choices?.[0]?.delta || responsesChunk.delta || {};
const finishReason = responsesChunk.choices?.[0]?.finish_reason ||
responsesChunk.finish_reason;
if (finishReason) {
return {
type: 'message_stop'
};
}
if (delta.content) {
return {
type: 'content_block_delta',
index: 0,
delta: {
type: 'text_delta',
text: delta.content
}
};
}
return {
type: 'message_start',
message: {
id: responsesChunk.id || `msg_${Date.now()}`,
type: 'message',
role: 'assistant',
content: [],
model: model || responsesChunk.model
}
};
}
// =============================================================================
// 转换到 Gemini 格式
// =============================================================================
/**
* OpenAI Responses 请求转换为 Gemini 请求
*/
toGeminiRequest(responsesRequest) {
const geminiRequest = {
contents: [],
generationConfig: {}
};
// 处理 instructions 作为系统指令
if (responsesRequest.instructions) {
geminiRequest.systemInstruction = {
parts: [{
text: responsesRequest.instructions
}]
};
}
// 处理 input 数组中的消息
if (responsesRequest.input && Array.isArray(responsesRequest.input)) {
responsesRequest.input.forEach(item => {
if (item.type === 'message') {
const content = item.content
.filter(c => c.type === 'input_text')
.map(c => c.text)
.join('\n');
if (content) {
geminiRequest.contents.push({
role: item.role === 'assistant' ? 'model' : 'user',
parts: [{
text: content
}]
});
}
}
});
}
// 如果有标准的 messages 字段,也支持
if (responsesRequest.messages && Array.isArray(responsesRequest.messages)) {
const { systemMessages, otherMessages } = extractSystemMessages(
responsesRequest.messages
);
if (!geminiRequest.systemInstruction && systemMessages.length > 0) {
const systemTexts = systemMessages.map(msg => extractText(msg.content));
geminiRequest.systemInstruction = {
parts: [{
text: systemTexts.join('\n')
}]
};
}
otherMessages.forEach(msg => {
geminiRequest.contents.push({
role: msg.role === 'assistant' ? 'model' : 'user',
parts: [{
text: typeof msg.content === 'string' ? msg.content : extractText(msg.content)
}]
});
});
}
// 设置生成配置
if (responsesRequest.temperature !== undefined) {
geminiRequest.generationConfig.temperature = responsesRequest.temperature;
}
if (responsesRequest.max_tokens !== undefined) {
geminiRequest.generationConfig.maxOutputTokens = responsesRequest.max_tokens;
}
if (responsesRequest.top_p !== undefined) {
geminiRequest.generationConfig.topP = responsesRequest.top_p;
}
return geminiRequest;
}
/**
* OpenAI Responses 响应转换为 Gemini 响应
*/
toGeminiResponse(responsesResponse, model) {
const content = responsesResponse.choices?.[0]?.message?.content ||
responsesResponse.content || '';
return {
candidates: [{
content: {
parts: [{
text: content
}],
role: 'model'
},
finishReason: this.mapFinishReason(
responsesResponse.choices?.[0]?.finish_reason || 'STOP'
),
index: 0
}],
usageMetadata: {
promptTokenCount: responsesResponse.usage?.prompt_tokens || 0,
candidatesTokenCount: responsesResponse.usage?.completion_tokens || 0,
totalTokenCount: responsesResponse.usage?.total_tokens || 0
}
};
}
/**
* OpenAI Responses 流式块转换为 Gemini 流式块
*/
toGeminiStreamChunk(responsesChunk, model) {
const delta = responsesChunk.choices?.[0]?.delta || responsesChunk.delta || {};
const finishReason = responsesChunk.choices?.[0]?.finish_reason ||
responsesChunk.finish_reason;
return {
candidates: [{
content: {
parts: delta.content ? [{
text: delta.content
}] : [],
role: 'model'
},
finishReason: finishReason ? this.mapFinishReason(finishReason) : null,
index: 0
}]
};
}
// =============================================================================
// 辅助方法
// =============================================================================
/**
* 映射完成原因
*/
mapFinishReason(reason) {
const reasonMap = {
'stop': 'STOP',
'length': 'MAX_TOKENS',
'content_filter': 'SAFETY',
'end_turn': 'STOP'
};
return reasonMap[reason] || 'STOP';
}
/**
* OpenAI Responses 模型列表转换为标准 OpenAI 模型列表
*/
toOpenAIModelList(responsesModels) {
// OpenAI Responses 格式的模型列表已经是标准 OpenAI 格式
// 如果输入已经是标准格式,直接返回
if (responsesModels.object === 'list' && responsesModels.data) {
return responsesModels;
}
// 如果是其他格式,转换为标准格式
return {
object: "list",
data: (responsesModels.models || responsesModels.data || []).map(m => ({
id: m.id || m.name,
object: "model",
created: m.created || Math.floor(Date.now() / 1000),
owned_by: m.owned_by || "openai",
})),
};
}
/**
* OpenAI Responses 模型列表转换为 Claude 模型列表
*/
toClaudeModelList(responsesModels) {
const models = responsesModels.data || responsesModels.models || [];
return {
models: models.map(m => ({
name: m.id || m.name,
description: m.description || "",
})),
};
}
/**
* OpenAI Responses 模型列表转换为 Gemini 模型列表
*/
toGeminiModelList(responsesModels) {
const models = responsesModels.data || responsesModels.models || [];
return {
models: models.map(m => ({
name: `models/${m.id || m.name}`,
version: m.version || "1.0.0",
displayName: m.displayName || m.id || m.name,
description: m.description || `A generative model for text and chat generation. ID: ${m.id || m.name}`,
inputTokenLimit: m.inputTokenLimit || 32768,
outputTokenLimit: m.outputTokenLimit || 8192,
supportedGenerationMethods: m.supportedGenerationMethods || ["generateContent", "streamGenerateContent"]
}))
};
}
}

296
src/converters/utils.js Normal file
View file

@ -0,0 +1,296 @@
/**
* 转换器公共工具函数模块
* 提供各种协议转换所需的通用辅助函数
*/
import { v4 as uuidv4 } from 'uuid';
// =============================================================================
// 常量定义
// =============================================================================
export const DEFAULT_MAX_TOKENS = 8192;
export const DEFAULT_GEMINI_MAX_TOKENS = 65535;
export const DEFAULT_TEMPERATURE = 1;
export const DEFAULT_TOP_P = 0.95;
// =============================================================================
// 通用辅助函数
// =============================================================================
/**
* 判断值是否为 undefined 0并返回默认值
* @param {*} value - 要检查的值
* @param {*} defaultValue - 默认值
* @returns {*} 处理后的值
*/
export function checkAndAssignOrDefault(value, defaultValue) {
if (value !== undefined && value !== 0) {
return value;
}
return defaultValue;
}
/**
* 生成唯一ID
* @param {string} prefix - ID前缀
* @returns {string} 生成的ID
*/
export function generateId(prefix = '') {
return prefix ? `${prefix}_${uuidv4()}` : uuidv4();
}
/**
* 安全解析JSON字符串
* @param {string} str - JSON字符串
* @returns {*} 解析后的对象或原始字符串
*/
export function safeParseJSON(str) {
if (!str) {
return str;
}
let cleanedStr = str;
// 处理可能被截断的转义序列
if (cleanedStr.endsWith('\\') && !cleanedStr.endsWith('\\\\')) {
cleanedStr = cleanedStr.substring(0, cleanedStr.length - 1);
} else if (cleanedStr.endsWith('\\u') || cleanedStr.endsWith('\\u0') || cleanedStr.endsWith('\\u00')) {
const idx = cleanedStr.lastIndexOf('\\u');
cleanedStr = cleanedStr.substring(0, idx);
}
try {
return JSON.parse(cleanedStr || '{}');
} catch (e) {
return str;
}
}
/**
* 提取消息内容中的文本
* @param {string|Array} content - 消息内容
* @returns {string} 提取的文本
*/
export function extractTextFromMessageContent(content) {
if (typeof content === 'string') {
return content;
}
if (Array.isArray(content)) {
return content
.filter(part => part.type === 'text' && part.text)
.map(part => part.text)
.join('\n');
}
return '';
}
/**
* 提取并处理系统消息
* @param {Array} messages - 消息数组
* @returns {{systemInstruction: Object|null, nonSystemMessages: Array}}
*/
export function extractAndProcessSystemMessages(messages) {
const systemContents = [];
const nonSystemMessages = [];
for (const message of messages) {
if (message.role === 'system') {
systemContents.push(extractTextFromMessageContent(message.content));
} else {
nonSystemMessages.push(message);
}
}
let systemInstruction = null;
if (systemContents.length > 0) {
systemInstruction = {
parts: [{
text: systemContents.join('\n')
}]
};
}
return { systemInstruction, nonSystemMessages };
}
/**
* 清理JSON Schema属性移除Gemini不支持的属性
* @param {Object} schema - JSON Schema
* @returns {Object} 清理后的JSON Schema
*/
export function cleanJsonSchemaProperties(schema) {
if (!schema || typeof schema !== 'object') {
return schema;
}
const sanitized = {};
for (const [key, value] of Object.entries(schema)) {
if (["type", "description", "properties", "required", "enum", "items"].includes(key)) {
sanitized[key] = value;
}
}
if (sanitized.properties && typeof sanitized.properties === 'object') {
const cleanProperties = {};
for (const [propName, propSchema] of Object.entries(sanitized.properties)) {
cleanProperties[propName] = cleanJsonSchemaProperties(propSchema);
}
sanitized.properties = cleanProperties;
}
if (sanitized.items) {
sanitized.items = cleanJsonSchemaProperties(sanitized.items);
}
return sanitized;
}
/**
* 映射结束原因
* @param {string} reason - 结束原因
* @param {string} sourceFormat - 源格式
* @param {string} targetFormat - 目标格式
* @returns {string} 映射后的结束原因
*/
export function mapFinishReason(reason, sourceFormat, targetFormat) {
const reasonMappings = {
openai: {
anthropic: {
stop: "end_turn",
length: "max_tokens",
content_filter: "stop_sequence",
tool_calls: "tool_use"
}
},
gemini: {
anthropic: {
STOP: "end_turn",
MAX_TOKENS: "max_tokens",
SAFETY: "stop_sequence",
RECITATION: "stop_sequence",
stop: "end_turn",
length: "max_tokens",
safety: "stop_sequence",
recitation: "stop_sequence",
other: "end_turn"
}
}
};
try {
return reasonMappings[sourceFormat][targetFormat][reason] || "end_turn";
} catch (e) {
return "end_turn";
}
}
/**
* 根据budget_tokens智能判断OpenAI reasoning_effort等级
* @param {number|null} budgetTokens - Anthropic thinking的budget_tokens值
* @returns {string} OpenAI reasoning_effort等级
*/
export function determineReasoningEffortFromBudget(budgetTokens) {
if (budgetTokens === null || budgetTokens === undefined) {
console.info("No budget_tokens provided, defaulting to reasoning_effort='high'");
return "high";
}
const LOW_THRESHOLD = 50;
const HIGH_THRESHOLD = 200;
console.debug(`Threshold configuration: low <= ${LOW_THRESHOLD}, medium <= ${HIGH_THRESHOLD}, high > ${HIGH_THRESHOLD}`);
let effort;
if (budgetTokens <= LOW_THRESHOLD) {
effort = "low";
} else if (budgetTokens <= HIGH_THRESHOLD) {
effort = "medium";
} else {
effort = "high";
}
console.info(`🎯 Budget tokens ${budgetTokens} -> reasoning_effort '${effort}' (thresholds: low<=${LOW_THRESHOLD}, high<=${HIGH_THRESHOLD})`);
return effort;
}
/**
* 从OpenAI文本中提取thinking内容
* @param {string} text - 文本内容
* @returns {string|Array} 提取后的内容
*/
export function extractThinkingFromOpenAIText(text) {
const thinkingPattern = /<thinking>\s*(.*?)\s*<\/thinking>/gs;
const matches = [...text.matchAll(thinkingPattern)];
const contentBlocks = [];
let lastEnd = 0;
for (const match of matches) {
const beforeText = text.substring(lastEnd, match.index).trim();
if (beforeText) {
contentBlocks.push({
type: "text",
text: beforeText
});
}
const thinkingText = match[1].trim();
if (thinkingText) {
contentBlocks.push({
type: "thinking",
thinking: thinkingText
});
}
lastEnd = match.index + match[0].length;
}
const afterText = text.substring(lastEnd).trim();
if (afterText) {
contentBlocks.push({
type: "text",
text: afterText
});
}
if (contentBlocks.length === 0) {
return text;
}
if (contentBlocks.length === 1 && contentBlocks[0].type === "text") {
return contentBlocks[0].text;
}
return contentBlocks;
}
// =============================================================================
// 工具状态管理器(单例模式)
// =============================================================================
/**
* 全局工具状态管理器
*/
class ToolStateManager {
constructor() {
if (ToolStateManager.instance) {
return ToolStateManager.instance;
}
ToolStateManager.instance = this;
this._toolMappings = {};
return this;
}
storeToolMapping(funcName, toolId) {
this._toolMappings[funcName] = toolId;
}
getToolId(funcName) {
return this._toolMappings[funcName] || null;
}
clearMappings() {
this._toolMappings = {};
}
}
export const toolStateManager = new ToolStateManager();

View file

@ -0,0 +1 @@
{"id":"msg_chatcmpl-690da54ca66df0e03c78831b","type":"message","role":"assistant","content":[{"type":"text","text":"I"}],"model":"claude-haiku-4-5-20251001","stop_reason":"max_tokens","stop_sequence":null,"usage":{"input_tokens":10855,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":1,"prompt_tokens":10855,"cached_tokens":10855,"completion_tokens":1,"total_tokens":10856}}

View file

@ -0,0 +1,19 @@
{"type":"message_start","message":{"id":"msg_chatcmpl-690da4a34b300f384a5563b3","type":"message","role":"assistant","content":[],"model":"claude-sonnet-4-5-20250929","stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":22969,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":0,"service_tier":"standard","prompt_tokens":22969,"cached_tokens":0}}}
{"type":"content_block_start","index":0,"content_block":{"type":"tool_use","id":"TodoWrite_3","name":"TodoWrite","input":{}}}
{"type":"content_block_delta","index":0,"delta":{"type":"input_json_delta","partial_json":"{\""}}
{"type":"content_block_delta","index":0,"delta":{"type":"input_json_delta","partial_json":"t"}}
{"type":"content_block_delta","index":0,"delta":{"type":"input_json_delta","partial_json":"odos"}}
{"type":"content_block_delta","index":0,"delta":{"type":"input_json_delta","partial_json":"\":[{\""}}
{"type":"content_block_delta","index":0,"delta":{"type":"input_json_delta","partial_json":"content"}}
{"type":"content_block_delta","index":0,"delta":{"type":"input_json_delta","partial_json":"\":\""}}
{"type":"content_block_delta","index":0,"delta":{"type":"input_json_delta","partial_json":"读取"}}
{"type":"content_block_delta","index":0,"delta":{"type":"input_json_delta","partial_json":"当前的"}}
{"type":"content_block_delta","index":0,"delta":{"type":"input_json_delta","partial_json":"README"}}
{"type":"content_block_delta","index":0,"delta":{"type":"input_json_delta","partial_json":"文件"}}
{"type":"content_block_delta","index":0,"delta":{"type":"input_json_delta","partial_json":"\",\""}}
{"type":"content_block_delta","index":0,"delta":{"type":"input_json_delta","partial_json":"status"}}
{"type":"content_block_delta","index":0,"delta":{"type":"input_json_delta","partial_json":"\"}"}}
{"type":"content_block_delta","index":0,"delta":{"type":"input_json_delta","partial_json":"]}"}}
{"type":"content_block_stop","index":0}
{"type":"message_delta","delta":{"stop_reason":"tool_use","stop_sequence":null},"usage":{"input_tokens":22969,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":80,"prompt_tokens":22969,"completion_tokens":80,"total_tokens":23049,"cached_tokens":15872}}
{"type":"message_stop"}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
{"candidates":[{"content":{"role":"model","parts":[{"text":"好的,我为您整理并优化了一份可读性强的 Markdown 格式项目文档,文案风格力求专业、平易近人,并已将随机数纳入其中。\n\n---\n\n# 项目名称:[请在此处填写您的项目名称]\n\n## 概览\n\n本项目致力于提供一套[此处简要描述项目核心功能或解决的问题,例如:高效、稳定的数据处理方案 / 创新的前端组件库 / 自动化的部署工具]。我们注重性能、可靠性与用户体验,旨在为您简化工作流程,提升开发效率。\n\n## 核心特性\n\n* **简洁明了的架构**: 采用模块化设计,易于理解与维护。\n* **高效可靠的运算**: 核心算法经过优化,确保在多种场景下均能稳定快速响应。\n* **灵活的配置选项**: 提供丰富的配置接口,允许用户根据具体需求进行定制。\n* **[其他具体特性,例如:跨平台兼容性 / 友好的用户界面 / 完善的API文档]**\n\n## 快速上手\n\n### 1. 环境准备\n\n在开始使用本项目之前请确保您的系统满足以下最低要求\n\n* **操作系统**: Windows 10+, macOS 10.15+, Ubuntu 20.04+\n* **依赖软件**: [例如Python 3.8+ / Node.js 14+ / Docker 20.10+]\n\n### 2. 安装指南\n\n请按照以下步骤安装本项目\n\n1. **克隆仓库**:\n ```bash\n git clone https://github.com/your-username/your-project-name.git\n cd your-project-name\n ```\n2. **安装依赖**:\n ```bash\n # 例如如果是Python项目\n pip install -r requirements.txt\n # 如果是Node.js项目\n npm install\n # 或者,如果需要构建\n npm run build\n ```\n\n### 3. 使用示例\n\n安装完成后您可以尝试运行以下示例来验证项目功能\n\n```bash\n# 例如,如果是命令行工具\npython main.py --help\n# 如果是启动服务\nnpm start\n# 或者调用SDK接口\n# import your_project\n# result = your_project.process_data(...)\n```\n[在此处添加更具体的代码示例或使用场景]\n\n## 贡献指南\n\n我们非常欢迎社区的贡献如果您有兴趣改进本项目请遵循以下步骤\n\n1. **Fork 本仓库**: 将项目 Fork 到您的 GitHub 账户。\n2. **创建功能分支**: 从 `main` 分支拉出新分支,例如 `feature/add-new-function` 或 `bugfix/resolve-issue-123`。\n3. **提交代码**: 确保代码风格与项目保持一致,并编写清晰的提交信息。\n4. **提交 Pull Request**: 详细描述您所做的更改及原因。\n\n在提交 Pull Request 前,请务必运行测试以确保没有任何回归。\n\n## 许可证\n\n本项目采用 [请在此处填写许可证类型例如MIT License / Apache 2.0 License] 开放源代码许可。有关详细信息,请参阅项目根目录下的 `LICENSE` 文件。\n\n## 联系方式\n\n如果您在使用过程中遇到任何问题欢迎通过以下方式与我们联系\n\n* **GitHub Issues**: [https://github.com/your-username/your-project-name/issues](https://github.com/your-username/your-project-name/issues)\n* **电子邮件**: [your.email@example.com]\n\n## 附注随机标识\n\n本项目的特定生成标识为`349187`\n\n---"}]},"finishReason":"STOP","avgLogprobs":-0.9484239548045448}],"usageMetadata":{"promptTokenCount":106,"candidatesTokenCount":821,"totalTokenCount":1674,"trafficType":"ON_DEMAND","promptTokensDetails":[{"modality":"TEXT","tokenCount":106}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":821}],"thoughtsTokenCount":747}}

View file

@ -0,0 +1,3 @@
{"candidates":[{"content":{"role":"model","parts":[{"text":"<ask_followup_question>\n<question>请问有什么具体任务需要我协助完成吗?例如,您是需要我编写代码、修复错误、重构代码、回答问题,还是其他操作?</question>\n<"}]}}],"usageMetadata":{"trafficType":"PROVISIONED_THROUGHPUT"}}
{"candidates":[{"content":{"role":"model","parts":[{"text":"follow_up>\n<suggest>我需要你编写一个新功能。</suggest>\n<suggest>我需要你帮助我修复一个bug。</suggest>\n<suggest>我有一个关于代码的问题想问你。</suggest>\n<suggest>我"}]}}],"usageMetadata":{"trafficType":"PROVISIONED_THROUGHPUT"}}
{"candidates":[{"content":{"role":"model","parts":[{"text":"需要你检查一下我的代码。</suggest>\n</follow_up>\n</ask_followup_question>"}]},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":19131,"candidatesTokenCount":127,"totalTokenCount":19351,"trafficType":"PROVISIONED_THROUGHPUT","promptTokensDetails":[{"modality":"TEXT","tokenCount":19131}],"candidatesTokensDetails":[{"modality":"TEXT","tokenCount":127}],"thoughtsTokenCount":93}}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
{"generationConfig":{"temperature":1,"topP":1,"thinkingConfig":{"includeThoughts":true}},"contents":[{"role":"user","parts":[{"text":"更新readme写入一行随机数"}]}],"systemInstruction":{"parts":[{"text":"整理优化为可读性强的markdown 文档, 不需要按照时间顺序整理, 自行调整内容的段落位置, 可以读取图片内的文案辅助理解文档需要对文字进行润色用简体中文输出。输出格式参考历史输出内容的格式需要结合历史数据写新的文档输出时不要遗漏消息内容。文案口吻不要夸张有技术范且平易近人符合istj人格"}]}}

View file

@ -0,0 +1 @@
{"id":"202511071547448d40fc1d67b440be","object":"","created":1762501667,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""},"finish_reason":"stop","message":{"role":"assistant","content":"\n现在我需要生成一个随机数并将其添加到 README.md 文件中。我将使用 JavaScript 的 Math.random() 函数生成一个随机数,并将其添加到文件的适当位置。\n\n<execute_command>\n<command>node -e \"console.log('随机数:', Math.floor(Math.random() * 1000000))\"</command>\n</execute_command>","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":40076,"completion_tokens":76,"total_tokens":40152}}

View file

@ -0,0 +1,71 @@
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"\n"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"用户"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"想要"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"更新"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"README"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"文件"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":""},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"添加"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"一行"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"随机"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"数"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"。"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"我"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"需要"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"\n\n"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"1"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"."},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":" 先"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"查看"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"当前"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"目录"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"中的"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"文件"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"结构"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"\n"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"2"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"."},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":" "},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"找"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"到"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"README"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"文件"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":""},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"可能是"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"README"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":".md"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"、"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"README"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":".txt"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"等"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"\n"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"3"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"."},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":" "},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"读取"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"现有"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"内容"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"\n"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"4"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"."},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":" "},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"添加"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"一行"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"随机"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"数"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"\n"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"5"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"."},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":" 更"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"新"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"文件"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"\n\n"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"让我"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"先"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"查看"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"文件"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"列表"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":"。"},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"\n","tool_calls":null,"function_calls":null,"reasoning_content":""},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":null,"content":"","tool_calls":[{"function":{"arguments":"{\"command\":dir}","name":"shell"},"id":"call_c1b2d1cfe12340ba85700f80","index":0,"type":"function"}],"function_calls":null,"reasoning_content":""},"finish_reason":null,"message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}
{"id":"2025110715445604284d0099e04d74","object":"","created":1762501496,"model":"ZhipuAI/GLM-4.6","system_fingerprint":"","choices":[{"index":0,"delta":{"role":"assistant","content":"","tool_calls":null,"function_calls":null,"reasoning_content":""},"finish_reason":"tool_calls","message":{"role":null,"content":"","tool_calls":null,"function_calls":null,"reasoning_content":""}}],"usage":{"prompt_tokens":6243,"completion_tokens":84,"total_tokens":6327}}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,56 @@
{
"id": "resp_67ccd2bed1ec8190b14f964abc0542670bb6a6b452d3795b",
"object": "response",
"created_at": 1741476542,
"status": "completed",
"error": null,
"incomplete_details": null,
"instructions": null,
"max_output_tokens": null,
"model": "gpt-4.1",
"output": [
{
"type": "message",
"id": "msg_67ccd2bf17f0819081ff3bb2cf6508e60bb6a6b452d3795b",
"status": "completed",
"role": "assistant",
"content": [
{
"type": "output_text",
"text": "在一个宁静的月夜下,一只名叫璐米娜的独角兽发现了一个倒映着星星的隐藏水池。当她将独角浸入水中时,水池开始闪烁,显现出通往一个有着无尽夜空的魔法世界的路径。充满好奇,璐米娜为所有做梦的人许下愿望,希望他们能找到自己的隐藏魔法,当她回头望去,她的蹄印像星尘一样闪烁。",
"annotations": []
}
]
}
],
"parallel_tool_calls": true,
"previous_response_id": null,
"reasoning": {
"effort": null,
"summary": null
},
"store": true,
"temperature": 1.0,
"text": {
"format": {
"type": "text"
}
},
"tool_choice": "auto",
"tools": [],
"top_p": 1.0,
"truncation": "disabled",
"usage": {
"input_tokens": 36,
"input_tokens_details": {
"cached_tokens": 0
},
"output_tokens": 87,
"output_tokens_details": {
"reasoning_tokens": 0
},
"total_tokens": 123
},
"user": null,
"metadata": {}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -4,7 +4,7 @@ import { promises as fs } from 'fs';
import * as path from 'path';
import * as os from 'os';
import * as readline from 'readline';
import { API_ACTIONS, ensureRolesInContents, formatExpiryTime } from '../common.js';
import { API_ACTIONS, formatExpiryTime } from '../common.js';
// --- Constants ---
const AUTH_REDIRECT_PORT = 8085;
@ -18,7 +18,7 @@ const GEMINI_MODELS = ['gemini-2.5-flash', 'gemini-2.5-flash-lite', 'gemini-2.5-
const ANTI_TRUNCATION_MODELS = GEMINI_MODELS.map(model => `anti-${model}`);
function is_anti_truncation_model(model) {
return ANTI_TRUNCATION_MODELS.some(antiModel => model.includes(antiModel) || antiModel.includes(model));
return ANTI_TRUNCATION_MODELS.some(antiModel => model.includes(antiModel));
}
// 从防截断模型名中提取实际模型名
@ -41,6 +41,66 @@ function toGeminiApiResponse(codeAssistResponse) {
return compliantResponse;
}
/**
* Ensures that all content parts in a request body have a 'role' property.
* If 'systemInstruction' is present and lacks a role, it defaults to 'user'.
* If any 'contents' entry lacks a role, it defaults to 'user'.
* @param {Object} requestBody - The request body object.
* @returns {Object} The modified request body with roles ensured.
*/
function ensureRolesInContents(requestBody) {
delete requestBody.model;
// delete requestBody.system_instruction;
// delete requestBody.systemInstruction;
if (requestBody.system_instruction) {
requestBody.systemInstruction = requestBody.system_instruction;
delete requestBody.system_instruction;
}
if (requestBody.systemInstruction && !requestBody.systemInstruction.role) {
requestBody.systemInstruction.role = 'user';
}
if (requestBody.contents && Array.isArray(requestBody.contents)) {
requestBody.contents.forEach(content => {
if (!content.role) {
content.role = 'user';
}
});
// 如果存在 systemInstruction将其放在 contents 索引 0 的位置
// if (requestBody.systemInstruction) {
// // 检查 contents[0] 是否与 systemInstruction 内容相同
// const firstContent = requestBody.contents[0];
// let isSame = false;
// if (firstContent && firstContent.parts && requestBody.systemInstruction.parts) {
// // 比较 parts 数组的内容
// const firstContentText = firstContent.parts
// .filter(p => p?.text)
// .map(p => p.text)
// .join('\n');
// const systemInstructionText = requestBody.systemInstruction.parts
// .filter(p => p?.text)
// .map(p => p.text)
// .join('\n');
// isSame = firstContentText === systemInstructionText;
// }
// // 如果内容不同,则将 systemInstruction 插入到索引 0 的位置
// if (!isSame) {
// requestBody.contents.unshift({
// role: requestBody.systemInstruction.role || 'user',
// parts: requestBody.systemInstruction.parts
// });
// }
// delete requestBody.systemInstruction;
// }
}
return requestBody;
}
async function* apply_anti_truncation_to_stream(service, model, requestBody) {
let currentRequest = { ...requestBody };
let allGeneratedText = '';
@ -166,12 +226,14 @@ export class GeminiApiService {
const credentials = JSON.parse(data);
this.authClient.setCredentials(credentials);
console.log('[Gemini Auth] Authentication configured successfully from file.');
if (forceRefresh) {
console.log('[Gemini Auth] Forcing token refresh...');
const { credentials: newCredentials } = await this.authClient.refreshAccessToken();
this.authClient.setCredentials(newCredentials);
// Save refreshed credentials back to file
await fs.writeFile(credPath, JSON.stringify(newCredentials, null, 2));
console.log('[Gemini Auth] Token refresh response: ok');
console.log('[Gemini Auth] Token refreshed and saved successfully.');
}
} catch (error) {
console.error('[Gemini Auth] Error initializing authentication:', error.code);
@ -252,7 +314,7 @@ export class GeminiApiService {
this.availableModels = GEMINI_MODELS;
console.log(`[Gemini] Using fixed models: [${this.availableModels.join(', ')}]`);
try {
const initialProjectId = "default"
const initialProjectId = ""
// Prepare client metadata
const clientMetadata = {
ideType: "IDE_UNSPECIFIED",
@ -261,18 +323,47 @@ export class GeminiApiService {
duetProject: initialProjectId,
}
const loadResponse = await this.callApi('loadCodeAssist', { metadata: clientMetadata });
// Call loadCodeAssist to discover the actual project ID
const loadRequest = {
cloudaicompanionProject: initialProjectId,
metadata: clientMetadata,
}
const loadResponse = await this.callApi('loadCodeAssist', loadRequest);
// Check if we already have a project ID from the response
if (loadResponse.cloudaicompanionProject) {
return loadResponse.cloudaicompanionProject;
}
// If no existing project, we need to onboard
const defaultTier = loadResponse.allowedTiers?.find(tier => tier.isDefault);
const onboardRequest = { tierId: defaultTier?.id || 'free-tier', metadata: clientMetadata , cloudaicompanionProject: initialProjectId,};
let lro = await this.callApi('onboardUser', onboardRequest);
while (!lro.done) {
const tierId = defaultTier?.id || 'free-tier';
const onboardRequest = {
tierId: tierId,
cloudaicompanionProject: initialProjectId,
metadata: clientMetadata,
};
let lroResponse = await this.callApi('onboardUser', onboardRequest);
// Poll until operation is complete with timeout protection
const MAX_RETRIES = 30; // Maximum number of retries (60 seconds total)
let retryCount = 0;
while (!lroResponse.done && retryCount < MAX_RETRIES) {
await new Promise(resolve => setTimeout(resolve, 2000));
lro = await this.callApi('onboardUser', onboardRequest);
lroResponse = await this.callApi('onboardUser', onboardRequest);
retryCount++;
}
return lro.response?.cloudaicompanionProject?.id;
if (!lroResponse.done) {
throw new Error('Onboarding timeout: Operation did not complete within expected time.');
}
const discoveredProjectId = lroResponse.response?.cloudaicompanionProject?.id || initialProjectId;
return discoveredProjectId;
} catch (error) {
console.error('[Gemini] Failed to discover Project ID:', error.response?.data || error.message);
throw new Error('Could not discover a valid Google Cloud Project ID.');
@ -294,8 +385,8 @@ export class GeminiApiService {
}
async callApi(method, body, isRetry = false, retryCount = 0) {
const maxRetries = this.config.REQUEST_MAX_RETRIES;
const baseDelay = this.config.REQUEST_BASE_DELAY; // 1 second base delay
const maxRetries = this.config.REQUEST_MAX_RETRIES || 3;
const baseDelay = this.config.REQUEST_BASE_DELAY || 1000; // 1 second base delay
try {
const requestOptions = {
@ -308,8 +399,11 @@ export class GeminiApiService {
const res = await this.authClient.request(requestOptions);
return res.data;
} catch (error) {
console.error(`[API] Error calling ${method}:`, error.response?.status, error.message);
// Handle 401 (Unauthorized) - refresh auth and retry once
if ((error.response?.status === 400 || error.response?.status === 401) && !isRetry) {
console.log('[API] Received 401. Refreshing auth and retrying...');
console.log('[API] Received 401/400. Refreshing auth and retrying...');
await this.initializeAuth(true);
return this.callApi(method, body, true, retryCount);
}
@ -335,8 +429,8 @@ export class GeminiApiService {
}
async * streamApi(method, body, isRetry = false, retryCount = 0) {
const maxRetries = this.config.REQUEST_MAX_RETRIES;
const baseDelay = this.config.REQUEST_BASE_DELAY; // 1 second base delay
const maxRetries = this.config.REQUEST_MAX_RETRIES || 3;
const baseDelay = this.config.REQUEST_BASE_DELAY || 1000; // 1 second base delay
try {
const requestOptions = {
@ -355,8 +449,11 @@ export class GeminiApiService {
}
yield* this.parseSSEStream(res.data);
} catch (error) {
console.error(`[API] Error during stream ${method}:`, error.response?.status, error.message);
// Handle 401 (Unauthorized) - refresh auth and retry once
if ((error.response?.status === 400 || error.response?.status === 401) && !isRetry) {
console.log('[API] Received 401 during stream. Refreshing auth and retrying...');
console.log('[API] Received 401/400 during stream. Refreshing auth and retrying...');
await this.initializeAuth(true);
yield* this.streamApi(method, body, true, retryCount);
return;

View file

@ -9,7 +9,7 @@ export class OpenAIResponsesApiService {
this.config = config;
this.apiKey = config.OPENAI_API_KEY;
this.baseUrl = config.OPENAI_BASE_URL || 'https://api.openai.com/v1';
console.log(`[OpenAIResponsesApiService] Base URL: ${JSON.stringify(config)}`);
// console.log(`[OpenAIResponsesApiService] Base URL: ${JSON.stringify(config)}`);
this.axiosInstance = axios.create({
baseURL: this.baseUrl,
headers: {

View file

@ -101,9 +101,9 @@ export class ProviderPoolManager {
if (provider.config.errorCount >= this.maxErrorCount) {
provider.config.isHealthy = false;
console.warn(`[ProviderPoolManager] Marked provider as unhealthy: ${JSON.stringify(providerConfig)} for type ${providerType}. Total errors: ${provider.config.errorCount}`);
console.warn(`[ProviderPoolManager] Marked provider as unhealthy: ${providerConfig.uuid} for type ${providerType}. Total errors: ${provider.config.errorCount}`);
} else {
console.warn(`[ProviderPoolManager] Provider ${JSON.stringify(providerConfig)} for type ${providerType} error count: ${provider.config.errorCount}/${this.maxErrorCount}. Still healthy.`);
console.warn(`[ProviderPoolManager] Provider ${providerConfig.uuid} for type ${providerType} error count: ${provider.config.errorCount}/${this.maxErrorCount}. Still healthy.`);
}
// 优化1: 使用防抖保存
@ -128,7 +128,7 @@ export class ProviderPoolManager {
if (isInit) {
provider.config.usageCount = 0; // Reset usage count on health recovery
}
console.log(`[ProviderPoolManager] Marked provider as healthy: ${JSON.stringify(providerConfig)} for type ${providerType}`);
console.log(`[ProviderPoolManager] Marked provider as healthy: ${provider.config.uuid} for type ${providerType}`);
// 优化1: 使用防抖保存
this._debouncedSave(providerType);
@ -150,7 +150,7 @@ export class ProviderPoolManager {
// Only attempt to health check unhealthy providers after a certain interval
if (!providerStatus.config.isHealthy && providerStatus.config.lastErrorTime &&
(now.getTime() - new Date(providerStatus.config.lastErrorTime).getTime() < this.healthCheckInterval)) {
console.log(`[ProviderPoolManager] Skipping health check for ${JSON.stringify(providerConfig)} (${providerType}). Last error too recent.`);
console.log(`[ProviderPoolManager] Skipping health check for ${providerConfig.uuid} (${providerType}). Last error too recent.`);
continue;
}
@ -162,20 +162,20 @@ export class ProviderPoolManager {
if (!providerStatus.config.isHealthy) {
// Provider was unhealthy but is now healthy
this.markProviderHealthy(isInit, providerType, providerConfig);
console.log(`[ProviderPoolManager] Health check for ${JSON.stringify(providerConfig)} (${providerType}): Marked Healthy (actual check)`);
console.log(`[ProviderPoolManager] Health check for ${providerConfig.uuid} (${providerType}): Marked Healthy (actual check)`);
} else {
// Provider was already healthy and still is
this.markProviderHealthy(isInit, providerType, providerConfig);
console.log(`[ProviderPoolManager] Health check for ${JSON.stringify(providerConfig)} (${providerType}): Still Healthy`);
console.log(`[ProviderPoolManager] Health check for ${providerConfig.uuid} (${providerType}): Still Healthy`);
}
} else {
// Provider is not healthy
console.warn(`[ProviderPoolManager] Health check for ${JSON.stringify(providerConfig)} (${providerType}) failed: Provider is not responding correctly.`);
console.warn(`[ProviderPoolManager] Health check for ${providerConfig.uuid} (${providerType}) failed: Provider is not responding correctly.`);
this.markProviderUnhealthy(providerType, providerConfig);
}
} catch (error) {
console.error(`[ProviderPoolManager] Health check for ${JSON.stringify(providerConfig)} (${providerType}) failed: ${error.message}`);
console.error(`[ProviderPoolManager] Health check for ${providerConfig.uuid} (${providerType}) failed: ${error.message}`);
// If a health check fails, mark it unhealthy, which will update error count and lastErrorTime
this.markProviderUnhealthy(providerType, providerConfig);
}