ai-protocol-adapters 1.0.0-alpha.7 → 1.0.0-alpha.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +699 -657
- package/dist/index.d.ts +699 -657
- package/dist/index.js +2363 -2094
- package/dist/index.mjs +2363 -2094
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -397,1360 +397,925 @@ function getGlobalLogger() {
|
|
|
397
397
|
return globalLogger;
|
|
398
398
|
}
|
|
399
399
|
|
|
400
|
-
// src/core/
|
|
401
|
-
var
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
}
|
|
400
|
+
// src/core/a2o-request-adapter/config.ts
|
|
401
|
+
var DEFAULT_CONFIG = {
|
|
402
|
+
// 原有配置
|
|
403
|
+
debugMode: false,
|
|
404
|
+
maxDescriptionLength: 100,
|
|
405
|
+
enableToolNameValidation: true,
|
|
406
|
+
enableFormatValidation: true,
|
|
407
|
+
// 新增默认配置
|
|
408
|
+
validation: {
|
|
409
|
+
enabled: true,
|
|
410
|
+
strict: false,
|
|
411
|
+
// 默认开启自动修复
|
|
412
|
+
customSchemas: {}
|
|
413
|
+
},
|
|
414
|
+
healing: {
|
|
415
|
+
enabled: true,
|
|
416
|
+
maxAttempts: 3,
|
|
417
|
+
enableCustomRules: true
|
|
418
|
+
},
|
|
419
|
+
recovery: {
|
|
420
|
+
enabled: true,
|
|
421
|
+
maxRetries: 2,
|
|
422
|
+
backoffMs: 1e3
|
|
423
|
+
},
|
|
424
|
+
monitoring: {
|
|
425
|
+
enabled: false,
|
|
426
|
+
logLevel: "warn",
|
|
427
|
+
enableMetrics: false
|
|
413
428
|
}
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
429
|
+
};
|
|
430
|
+
var SUPPORTED_IMAGE_TYPES = [
|
|
431
|
+
"image/jpeg",
|
|
432
|
+
"image/png",
|
|
433
|
+
"image/gif",
|
|
434
|
+
"image/webp"
|
|
435
|
+
];
|
|
436
|
+
var TOOL_CONVERSION = {
|
|
437
|
+
/**
|
|
438
|
+
* 终极泛化:完全移除工具名称映射
|
|
439
|
+
* 基于GitHub Copilot API测试结果,100%保持原始格式
|
|
440
|
+
*/
|
|
441
|
+
PRESERVE_ORIGINAL_NAMES: true,
|
|
442
|
+
/**
|
|
443
|
+
* 默认工具描述
|
|
444
|
+
*/
|
|
445
|
+
DEFAULT_DESCRIPTION: "Tool description",
|
|
446
|
+
/**
|
|
447
|
+
* 未知工具回退名称
|
|
448
|
+
*/
|
|
449
|
+
UNKNOWN_TOOL_FALLBACK: "unknown_tool"
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
// src/core/a2o-request-adapter/message-converter.ts
|
|
453
|
+
var MessageConverter = class {
|
|
454
|
+
/**
|
|
455
|
+
* 转换消息格式,正确处理工具调用和工具结果
|
|
456
|
+
* 修复关键问题:将tool_use转换为tool_calls,tool_result转换为role:"tool"消息
|
|
457
|
+
* 使用tool_use_id溯回工具名称解决unknown_tool问题
|
|
458
|
+
*/
|
|
459
|
+
static convertMessages(messages, system) {
|
|
460
|
+
const debugEnabled = process.env.AI_PROTOCOL_DEBUG === "true";
|
|
461
|
+
if (debugEnabled) {
|
|
462
|
+
if (system !== void 0) {
|
|
463
|
+
console.debug("[MessageConverter] convertMessages called with system:", JSON.stringify(system, null, 2));
|
|
464
|
+
} else {
|
|
465
|
+
console.debug("[MessageConverter] convertMessages called WITHOUT system parameter");
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
const context = this.createConversionContext(messages);
|
|
469
|
+
const convertedMessages = [];
|
|
470
|
+
for (const msg of messages) {
|
|
471
|
+
if (Array.isArray(msg.content)) {
|
|
472
|
+
const processedMessages = this.processComplexMessage(msg, context);
|
|
473
|
+
convertedMessages.push(...processedMessages);
|
|
474
|
+
} else {
|
|
475
|
+
const safeMsg = { ...msg };
|
|
476
|
+
if (safeMsg.content === null || safeMsg.content === void 0) {
|
|
477
|
+
safeMsg.content = "";
|
|
478
|
+
}
|
|
479
|
+
convertedMessages.push(safeMsg);
|
|
480
|
+
}
|
|
417
481
|
}
|
|
482
|
+
if (system) {
|
|
483
|
+
const systemMessage = this.processSystemMessage(system);
|
|
484
|
+
if (systemMessage) {
|
|
485
|
+
convertedMessages.unshift(systemMessage);
|
|
486
|
+
if (debugEnabled) {
|
|
487
|
+
console.debug("[MessageConverter] System message added to messages array at index 0");
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
if (debugEnabled) {
|
|
492
|
+
console.debug("[MessageConverter] Final converted messages count:", convertedMessages.length);
|
|
493
|
+
console.debug("[MessageConverter] First message:", JSON.stringify(convertedMessages[0], null, 2));
|
|
494
|
+
}
|
|
495
|
+
return convertedMessages.map((msg) => {
|
|
496
|
+
if (Array.isArray(msg.tools)) {
|
|
497
|
+
msg.tools = msg.tools.map((tool) => {
|
|
498
|
+
if (tool?.type === "function" && tool.function) {
|
|
499
|
+
const description = tool.function.description?.trim() || "Converted tool with no description provided.";
|
|
500
|
+
return {
|
|
501
|
+
...tool,
|
|
502
|
+
function: {
|
|
503
|
+
...tool.function,
|
|
504
|
+
description
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
return tool;
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
return msg;
|
|
512
|
+
});
|
|
418
513
|
}
|
|
419
514
|
/**
|
|
420
|
-
*
|
|
515
|
+
* 创建消息转换上下文
|
|
421
516
|
*/
|
|
422
|
-
|
|
423
|
-
const
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
stream: anthropicRequest.stream ?? true,
|
|
431
|
-
temperature: anthropicRequest.temperature,
|
|
432
|
-
max_tokens: anthropicRequest.max_tokens
|
|
433
|
-
};
|
|
434
|
-
if (anthropicRequest.tools) {
|
|
435
|
-
openaiRequest.tools = anthropicRequest.tools.map((tool) => ({
|
|
436
|
-
type: "function",
|
|
437
|
-
function: {
|
|
438
|
-
name: tool.name,
|
|
439
|
-
description: tool.description,
|
|
440
|
-
parameters: tool.input_schema
|
|
517
|
+
static createConversionContext(messages) {
|
|
518
|
+
const toolIdToNameMap = /* @__PURE__ */ new Map();
|
|
519
|
+
for (const msg of messages) {
|
|
520
|
+
if (Array.isArray(msg.content)) {
|
|
521
|
+
for (const item of msg.content) {
|
|
522
|
+
if (typeof item === "object" && item !== null && item.type === "tool_use") {
|
|
523
|
+
toolIdToNameMap.set(item.id, item.name);
|
|
524
|
+
}
|
|
441
525
|
}
|
|
442
|
-
}
|
|
526
|
+
}
|
|
443
527
|
}
|
|
444
|
-
const hasImages = this.hasImageContent(anthropicRequest);
|
|
445
528
|
return {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
hasImages,
|
|
449
|
-
requiresVisionHeaders: hasImages
|
|
450
|
-
}
|
|
529
|
+
toolIdToNameMap,
|
|
530
|
+
hasSystemMessage: false
|
|
451
531
|
};
|
|
452
532
|
}
|
|
453
533
|
/**
|
|
454
|
-
*
|
|
534
|
+
* 处理复杂消息(包含多种内容类型)
|
|
455
535
|
*/
|
|
456
|
-
|
|
457
|
-
const
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
536
|
+
static processComplexMessage(msg, context) {
|
|
537
|
+
const { textContent, toolUses, toolResults } = this.categorizeContent(msg.content);
|
|
538
|
+
const resultMessages = [];
|
|
539
|
+
if (msg.role === "assistant" && toolUses.length > 0) {
|
|
540
|
+
const assistantMessage = this.createAssistantMessageWithToolCalls(textContent, toolUses);
|
|
541
|
+
resultMessages.push(assistantMessage);
|
|
542
|
+
} else if (toolResults.length > 0) {
|
|
543
|
+
const toolMessages = this.createToolResultMessages(toolResults, context.toolIdToNameMap);
|
|
544
|
+
resultMessages.push(...toolMessages);
|
|
545
|
+
const textMessage = this.createTextMessage(msg.role, textContent);
|
|
546
|
+
if (textMessage) {
|
|
547
|
+
resultMessages.push(textMessage);
|
|
464
548
|
}
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
anthropicSSE: "",
|
|
470
|
-
anthropicStandardResponse: null
|
|
471
|
-
};
|
|
549
|
+
} else if (textContent.length > 0) {
|
|
550
|
+
const textMessage = this.createTextMessage(msg.role, textContent);
|
|
551
|
+
if (textMessage) {
|
|
552
|
+
resultMessages.push(textMessage);
|
|
472
553
|
}
|
|
473
|
-
const anthropicSSE = this.convertToAnthropicSSE(openaiStream, originalRequest.model);
|
|
474
|
-
const anthropicStandardResponse = this.buildStandardResponse(openaiStream);
|
|
475
|
-
return {
|
|
476
|
-
success: true,
|
|
477
|
-
anthropicSSE,
|
|
478
|
-
anthropicStandardResponse
|
|
479
|
-
};
|
|
480
|
-
} catch (error) {
|
|
481
|
-
const errorMessage = error instanceof Error ? error.message : "Unknown conversion error";
|
|
482
|
-
logger.error("Stream conversion failed", { error: errorMessage });
|
|
483
|
-
return {
|
|
484
|
-
success: false,
|
|
485
|
-
error: errorMessage,
|
|
486
|
-
anthropicSSE: "",
|
|
487
|
-
anthropicStandardResponse: null
|
|
488
|
-
};
|
|
489
554
|
}
|
|
555
|
+
return resultMessages;
|
|
490
556
|
}
|
|
491
557
|
/**
|
|
492
|
-
*
|
|
558
|
+
* 分类内容块
|
|
493
559
|
*/
|
|
494
|
-
|
|
495
|
-
const
|
|
496
|
-
const
|
|
497
|
-
const
|
|
498
|
-
|
|
499
|
-
"
|
|
500
|
-
|
|
501
|
-
""
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
560
|
+
static categorizeContent(content) {
|
|
561
|
+
const textContent = [];
|
|
562
|
+
const toolUses = [];
|
|
563
|
+
const toolResults = [];
|
|
564
|
+
for (const item of content) {
|
|
565
|
+
if (typeof item === "string") {
|
|
566
|
+
textContent.push({ type: "text", text: item });
|
|
567
|
+
} else if (typeof item === "object" && item !== null) {
|
|
568
|
+
switch (item.type) {
|
|
569
|
+
case "text":
|
|
570
|
+
textContent.push(item);
|
|
571
|
+
break;
|
|
572
|
+
case "tool_use":
|
|
573
|
+
toolUses.push(item);
|
|
574
|
+
break;
|
|
575
|
+
case "tool_result":
|
|
576
|
+
toolResults.push(item);
|
|
577
|
+
break;
|
|
578
|
+
case "image":
|
|
579
|
+
const imageContent = this.convertImageContent(item);
|
|
580
|
+
if (imageContent) {
|
|
581
|
+
textContent.push(imageContent);
|
|
582
|
+
}
|
|
583
|
+
break;
|
|
517
584
|
}
|
|
518
585
|
}
|
|
519
586
|
}
|
|
520
|
-
return
|
|
587
|
+
return { textContent, toolUses, toolResults };
|
|
521
588
|
}
|
|
522
589
|
/**
|
|
523
|
-
*
|
|
590
|
+
* 转换图片内容格式
|
|
524
591
|
*/
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
const isNonText = !choice.delta?.content;
|
|
531
|
-
if (this.config.debugMode && (hasToolCalls || hasFinishReason || isNonText && choice.delta)) {
|
|
532
|
-
this.logDebug("Streaming chunk processed", { chunk });
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
if (!choice) return;
|
|
536
|
-
const delta = choice.delta;
|
|
537
|
-
if (delta.reasoning_content) {
|
|
538
|
-
state.reasoningContent += delta.reasoning_content;
|
|
539
|
-
if (!state.thinkingBlockStarted) {
|
|
540
|
-
sseLines.push(
|
|
541
|
-
"event: content_block_start",
|
|
542
|
-
'data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":"<thinking>"}}',
|
|
543
|
-
""
|
|
544
|
-
);
|
|
545
|
-
state.thinkingBlockStarted = true;
|
|
546
|
-
}
|
|
547
|
-
sseLines.push(
|
|
548
|
-
"event: content_block_delta",
|
|
549
|
-
`data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"${this.escapeJsonString(delta.reasoning_content)}"}}`,
|
|
550
|
-
""
|
|
551
|
-
);
|
|
552
|
-
}
|
|
553
|
-
if (delta.content && delta.content !== "") {
|
|
554
|
-
if (state.thinkingBlockStarted && !state.contentBlockStarted) {
|
|
555
|
-
sseLines.push(
|
|
556
|
-
"event: content_block_delta",
|
|
557
|
-
'data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"</thinking>\\n\\n"}}',
|
|
558
|
-
"",
|
|
559
|
-
"event: content_block_stop",
|
|
560
|
-
'data: {"type":"content_block_stop","index":0}',
|
|
561
|
-
"",
|
|
562
|
-
"event: content_block_start",
|
|
563
|
-
'data: {"type":"content_block_start","index":1,"content_block":{"type":"text","text":""}}',
|
|
564
|
-
""
|
|
565
|
-
);
|
|
566
|
-
state.contentBlockStarted = true;
|
|
567
|
-
} else if (!state.contentBlockStarted && !state.thinkingBlockStarted) {
|
|
568
|
-
sseLines.push(
|
|
569
|
-
"event: content_block_start",
|
|
570
|
-
'data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}',
|
|
571
|
-
""
|
|
572
|
-
);
|
|
573
|
-
state.contentBlockStarted = true;
|
|
592
|
+
static convertImageContent(item) {
|
|
593
|
+
if (item.source && item.source.type === "base64" && item.source.data && item.source.media_type) {
|
|
594
|
+
if (!SUPPORTED_IMAGE_TYPES.includes(item.source.media_type)) {
|
|
595
|
+
console.warn(`\u4E0D\u652F\u6301\u7684\u56FE\u7247\u683C\u5F0F: ${item.source.media_type}`);
|
|
596
|
+
return null;
|
|
574
597
|
}
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
}
|
|
583
|
-
if (delta.tool_calls) {
|
|
584
|
-
this.processToolCalls(delta.tool_calls, state, sseLines);
|
|
585
|
-
}
|
|
586
|
-
if (chunk.usage) {
|
|
587
|
-
state.usage.input_tokens = chunk.usage.prompt_tokens;
|
|
588
|
-
state.usage.output_tokens = chunk.usage.completion_tokens;
|
|
598
|
+
const dataUri = `data:${item.source.media_type};base64,${item.source.data}`;
|
|
599
|
+
return {
|
|
600
|
+
type: "image_url",
|
|
601
|
+
image_url: {
|
|
602
|
+
url: dataUri
|
|
603
|
+
}
|
|
604
|
+
};
|
|
589
605
|
}
|
|
606
|
+
return null;
|
|
590
607
|
}
|
|
591
608
|
/**
|
|
592
|
-
*
|
|
593
|
-
* OpenAI流式API会将tool_calls分多个chunk发送:
|
|
594
|
-
* - Chunk 1: {index:0, id:"call_xxx", type:"function", function:{name:"web_search"}}
|
|
595
|
-
* - Chunk 2: {index:0, function:{arguments:"{\"query\":\"xxx\"}"}}
|
|
596
|
-
* - Chunk N: 继续累积arguments
|
|
609
|
+
* 创建包含工具调用的助手消息
|
|
597
610
|
*/
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
hasArgs: !!toolArgs,
|
|
609
|
-
argsLength: toolArgs?.length
|
|
610
|
-
});
|
|
611
|
-
const stateKey = `tool_${index}`;
|
|
612
|
-
let toolData = state.toolCallsMap.get(stateKey);
|
|
613
|
-
if (!toolData) {
|
|
614
|
-
toolData = {
|
|
615
|
-
id: toolId || "",
|
|
616
|
-
name: toolName || "",
|
|
617
|
-
input: "",
|
|
618
|
-
blockStartSent: false,
|
|
619
|
-
blockStopSent: false
|
|
620
|
-
};
|
|
621
|
-
state.toolCallsMap.set(stateKey, toolData);
|
|
622
|
-
} else {
|
|
623
|
-
if (toolId) toolData.id = toolId;
|
|
624
|
-
if (toolName) toolData.name = toolName;
|
|
625
|
-
}
|
|
626
|
-
if (toolArgs) {
|
|
627
|
-
toolData.input += toolArgs;
|
|
628
|
-
this.logDebug(`Accumulated tool arguments for index ${index}`, {
|
|
629
|
-
currentLength: toolData.input.length
|
|
630
|
-
});
|
|
631
|
-
}
|
|
632
|
-
if (toolData.id && toolData.name && !toolData.blockStartSent) {
|
|
633
|
-
const blockIndex = state.completedToolCalls.length + 1;
|
|
634
|
-
toolData.blockIndex = blockIndex;
|
|
635
|
-
sseLines.push(
|
|
636
|
-
"event: content_block_start",
|
|
637
|
-
`data: {"type":"content_block_start","index":${blockIndex},"content_block":{"type":"tool_use","id":"${toolData.id}","name":"${toolData.name}","input":{}}}`,
|
|
638
|
-
""
|
|
639
|
-
);
|
|
640
|
-
toolData.blockStartSent = true;
|
|
641
|
-
this.logDebug("Sent content_block_start", { toolName: toolData.name, blockIndex });
|
|
642
|
-
}
|
|
643
|
-
if (toolArgs && toolData.blockStartSent && toolData.blockIndex !== void 0) {
|
|
644
|
-
sseLines.push(
|
|
645
|
-
"event: content_block_delta",
|
|
646
|
-
`data: {"type":"content_block_delta","index":${toolData.blockIndex},"delta":{"type":"input_json_delta","partial_json":${JSON.stringify(toolArgs)}}}`,
|
|
647
|
-
""
|
|
648
|
-
);
|
|
649
|
-
this.logDebug("Sent input_json_delta", { blockIndex: toolData.blockIndex });
|
|
611
|
+
static createAssistantMessageWithToolCalls(textContent, toolUses) {
|
|
612
|
+
const assistantMessage = {
|
|
613
|
+
role: "assistant",
|
|
614
|
+
content: ""
|
|
615
|
+
// 默认为空字符串,避免null值
|
|
616
|
+
};
|
|
617
|
+
if (textContent.length > 0) {
|
|
618
|
+
const textOnly = textContent.map((item) => item.text || "").join("");
|
|
619
|
+
if (textOnly.trim()) {
|
|
620
|
+
assistantMessage.content = textOnly.trim();
|
|
650
621
|
}
|
|
651
622
|
}
|
|
623
|
+
assistantMessage.tool_calls = toolUses.map((toolUse) => ({
|
|
624
|
+
id: toolUse.id,
|
|
625
|
+
type: "function",
|
|
626
|
+
function: {
|
|
627
|
+
name: toolUse.name,
|
|
628
|
+
arguments: JSON.stringify(toolUse.input || {})
|
|
629
|
+
}
|
|
630
|
+
}));
|
|
631
|
+
return assistantMessage;
|
|
652
632
|
}
|
|
653
633
|
/**
|
|
654
|
-
*
|
|
634
|
+
* 创建工具结果消息
|
|
655
635
|
*/
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
state.completedToolCalls.push(toolData.id);
|
|
666
|
-
this.logDebug("Sent content_block_stop", { toolName: toolData.name, blockIndex: toolData.blockIndex });
|
|
636
|
+
static createToolResultMessages(toolResults, toolIdToNameMap) {
|
|
637
|
+
return toolResults.map((toolResult) => {
|
|
638
|
+
let resultContent = "No content";
|
|
639
|
+
if (toolResult.content) {
|
|
640
|
+
if (typeof toolResult.content === "string") {
|
|
641
|
+
resultContent = toolResult.content;
|
|
642
|
+
} else {
|
|
643
|
+
resultContent = JSON.stringify(toolResult.content, null, 2);
|
|
644
|
+
}
|
|
667
645
|
}
|
|
668
|
-
|
|
646
|
+
const toolName = toolIdToNameMap.get(toolResult.tool_use_id) || TOOL_CONVERSION.UNKNOWN_TOOL_FALLBACK;
|
|
647
|
+
return {
|
|
648
|
+
role: "tool",
|
|
649
|
+
tool_call_id: toolResult.tool_use_id,
|
|
650
|
+
name: toolName,
|
|
651
|
+
content: resultContent
|
|
652
|
+
};
|
|
653
|
+
});
|
|
669
654
|
}
|
|
670
655
|
/**
|
|
671
|
-
*
|
|
656
|
+
* 创建文本消息
|
|
672
657
|
*/
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
'data: {"type":"content_block_stop","index":0}',
|
|
689
|
-
""
|
|
690
|
-
);
|
|
658
|
+
static createTextMessage(role, textContent) {
|
|
659
|
+
if (textContent.length === 0) return null;
|
|
660
|
+
const hasNonTextContent = textContent.some((item) => item.type !== "text");
|
|
661
|
+
if (hasNonTextContent) {
|
|
662
|
+
return {
|
|
663
|
+
role,
|
|
664
|
+
content: textContent
|
|
665
|
+
};
|
|
666
|
+
} else {
|
|
667
|
+
const textOnly = textContent.map((item) => item.text || "").join("");
|
|
668
|
+
return {
|
|
669
|
+
role,
|
|
670
|
+
content: textOnly.trim() || ""
|
|
671
|
+
// 确保content为字符串,避免null
|
|
672
|
+
};
|
|
691
673
|
}
|
|
692
|
-
const stopReason = state.completedToolCalls.length > 0 ? "tool_use" : "end_turn";
|
|
693
|
-
sseLines.push(
|
|
694
|
-
"event: message_delta",
|
|
695
|
-
`data: {"type":"message_delta","delta":{"stop_reason":"${stopReason}","stop_sequence":null},"usage":{"output_tokens":${state.usage.output_tokens}}}`,
|
|
696
|
-
"",
|
|
697
|
-
"event: message_stop",
|
|
698
|
-
'data: {"type":"message_stop"}',
|
|
699
|
-
""
|
|
700
|
-
);
|
|
701
674
|
}
|
|
702
675
|
/**
|
|
703
|
-
*
|
|
676
|
+
* 处理系统消息
|
|
704
677
|
*/
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
if (dataLine.trim() === "[DONE]") break;
|
|
712
|
-
try {
|
|
713
|
-
const chunk = JSON.parse(dataLine);
|
|
714
|
-
const choice = chunk.choices?.[0];
|
|
715
|
-
if (!choice) continue;
|
|
716
|
-
const delta = choice.delta;
|
|
717
|
-
if (delta.content) {
|
|
718
|
-
state.textContent += delta.content;
|
|
719
|
-
}
|
|
720
|
-
if (chunk.usage) {
|
|
721
|
-
state.usage.input_tokens = chunk.usage.prompt_tokens;
|
|
722
|
-
state.usage.output_tokens = chunk.usage.completion_tokens;
|
|
723
|
-
}
|
|
724
|
-
} catch (error) {
|
|
678
|
+
static processSystemMessage(system) {
|
|
679
|
+
let systemContent;
|
|
680
|
+
if (Array.isArray(system)) {
|
|
681
|
+
systemContent = system.map((s) => {
|
|
682
|
+
if (typeof s === "string") {
|
|
683
|
+
return s;
|
|
725
684
|
}
|
|
726
|
-
|
|
685
|
+
return s.text || "";
|
|
686
|
+
}).filter((text) => text.length > 0).join("\n").trim();
|
|
687
|
+
} else {
|
|
688
|
+
systemContent = system;
|
|
727
689
|
}
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
text: state.textContent
|
|
736
|
-
}
|
|
737
|
-
],
|
|
738
|
-
model: "claude-3-sonnet-20240229",
|
|
739
|
-
stop_reason: "end_turn",
|
|
740
|
-
stop_sequence: null,
|
|
741
|
-
usage: state.usage
|
|
742
|
-
};
|
|
690
|
+
if (systemContent) {
|
|
691
|
+
return {
|
|
692
|
+
role: "system",
|
|
693
|
+
content: systemContent
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
return null;
|
|
743
697
|
}
|
|
698
|
+
};
|
|
699
|
+
|
|
700
|
+
// src/core/a2o-request-adapter/tool-converter.ts
|
|
701
|
+
var ToolConverter = class {
|
|
744
702
|
/**
|
|
745
|
-
*
|
|
703
|
+
* 将Anthropic工具定义转换为OpenAI格式
|
|
746
704
|
*/
|
|
747
|
-
|
|
705
|
+
static convertAnthropicToolToOpenAI(anthropicTool) {
|
|
706
|
+
if (!anthropicTool || !anthropicTool.name) {
|
|
707
|
+
throw new Error("Invalid tool definition: missing name");
|
|
708
|
+
}
|
|
709
|
+
const openaiName = anthropicTool.name;
|
|
710
|
+
const description = this.simplifyDescription(anthropicTool.description || TOOL_CONVERSION.DEFAULT_DESCRIPTION);
|
|
711
|
+
if (!anthropicTool.input_schema) {
|
|
712
|
+
throw new Error(`Invalid tool definition for ${anthropicTool.name}: missing input_schema`);
|
|
713
|
+
}
|
|
714
|
+
const parameters = {
|
|
715
|
+
type: anthropicTool.input_schema.type || "object",
|
|
716
|
+
properties: anthropicTool.input_schema.properties || {},
|
|
717
|
+
...anthropicTool.input_schema.required && { required: anthropicTool.input_schema.required }
|
|
718
|
+
};
|
|
748
719
|
return {
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
errors: [],
|
|
756
|
-
usage: {
|
|
757
|
-
input_tokens: 0,
|
|
758
|
-
output_tokens: 0
|
|
759
|
-
},
|
|
760
|
-
thinkingBlockStarted: false,
|
|
761
|
-
contentBlockStarted: false
|
|
720
|
+
type: "function",
|
|
721
|
+
function: {
|
|
722
|
+
name: openaiName,
|
|
723
|
+
description,
|
|
724
|
+
parameters
|
|
725
|
+
}
|
|
762
726
|
};
|
|
763
727
|
}
|
|
764
728
|
/**
|
|
765
|
-
*
|
|
729
|
+
* 将OpenAI工具调用转换为Claude格式
|
|
766
730
|
*/
|
|
767
|
-
|
|
768
|
-
return
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
731
|
+
static convertOpenAIToolCallsToClaude(toolCalls) {
|
|
732
|
+
return toolCalls.map((toolCall) => {
|
|
733
|
+
const claudeToolName = toolCall.function.name;
|
|
734
|
+
let parsedInput = {};
|
|
735
|
+
try {
|
|
736
|
+
parsedInput = JSON.parse(toolCall.function.arguments);
|
|
737
|
+
} catch (error) {
|
|
738
|
+
parsedInput = { raw_arguments: toolCall.function.arguments };
|
|
739
|
+
}
|
|
740
|
+
return {
|
|
741
|
+
type: "tool_use",
|
|
742
|
+
id: toolCall.id,
|
|
743
|
+
name: claudeToolName,
|
|
744
|
+
input: parsedInput
|
|
745
|
+
};
|
|
746
|
+
});
|
|
772
747
|
}
|
|
773
748
|
/**
|
|
774
|
-
*
|
|
749
|
+
* 检查是否为OpenAI工具格式
|
|
775
750
|
*/
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
"glm-4.5",
|
|
779
|
-
"kimi-k2",
|
|
780
|
-
"deepseek-v3.1",
|
|
781
|
-
"deepseek-r1",
|
|
782
|
-
"deepseek-v3",
|
|
783
|
-
"qwen3-32b",
|
|
784
|
-
"qwen3-coder",
|
|
785
|
-
"qwen3-235b",
|
|
786
|
-
"tstars2.0"
|
|
787
|
-
];
|
|
788
|
-
if (supportedModels.includes(model)) {
|
|
789
|
-
return model;
|
|
790
|
-
}
|
|
791
|
-
const mapping = {
|
|
792
|
-
"claude-3-sonnet-20240229": "glm-4.5",
|
|
793
|
-
"claude-3-haiku-20240307": "kimi-k2",
|
|
794
|
-
"claude-3-opus-20240229": "deepseek-v3.1"
|
|
795
|
-
};
|
|
796
|
-
return mapping[model] || "glm-4.5";
|
|
751
|
+
static isOpenAIToolFormat(tool) {
|
|
752
|
+
return tool && tool.type === "function" && tool.function && tool.function.name;
|
|
797
753
|
}
|
|
798
754
|
/**
|
|
799
|
-
*
|
|
755
|
+
* 简化Claude的详细描述为OpenAI兼容的简短描述
|
|
800
756
|
*/
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
)
|
|
757
|
+
static simplifyDescription(claudeDescription) {
|
|
758
|
+
const firstLine = claudeDescription.split("\n")[0].trim();
|
|
759
|
+
const maxLength = 100;
|
|
760
|
+
if (firstLine.length > maxLength) {
|
|
761
|
+
return firstLine.substring(0, maxLength - 3) + "...";
|
|
762
|
+
}
|
|
763
|
+
return firstLine;
|
|
805
764
|
}
|
|
806
765
|
/**
|
|
807
|
-
*
|
|
766
|
+
* 验证工具定义的有效性
|
|
808
767
|
*/
|
|
809
|
-
|
|
810
|
-
|
|
768
|
+
static validateToolDefinition(tool) {
|
|
769
|
+
if (!tool) return false;
|
|
770
|
+
if ("input_schema" in tool) {
|
|
771
|
+
return !!(tool.name && tool.input_schema && tool.input_schema.type);
|
|
772
|
+
}
|
|
773
|
+
if ("function" in tool) {
|
|
774
|
+
return !!(tool.type === "function" && tool.function?.name && tool.function?.parameters);
|
|
775
|
+
}
|
|
776
|
+
return false;
|
|
811
777
|
}
|
|
812
778
|
/**
|
|
813
|
-
*
|
|
779
|
+
* 获取工具名称(通用方法)
|
|
814
780
|
*/
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
781
|
+
static getToolName(tool) {
|
|
782
|
+
if ("name" in tool) {
|
|
783
|
+
return tool.name;
|
|
784
|
+
}
|
|
785
|
+
if ("function" in tool) {
|
|
786
|
+
return tool.function.name;
|
|
787
|
+
}
|
|
788
|
+
return TOOL_CONVERSION.UNKNOWN_TOOL_FALLBACK;
|
|
789
|
+
}
|
|
790
|
+
};
|
|
791
|
+
|
|
792
|
+
// src/utils/validation-logger.ts
|
|
793
|
+
import { writeFileSync, mkdirSync } from "fs";
|
|
794
|
+
import { join } from "path";
|
|
795
|
+
var ValidationLogger = class {
|
|
796
|
+
constructor(logsDir = "/app/logs/request-validation-errors") {
|
|
797
|
+
this.logsDir = logsDir;
|
|
798
|
+
this.ensureLogsDir();
|
|
824
799
|
}
|
|
825
800
|
/**
|
|
826
|
-
*
|
|
827
|
-
* 用于逐个处理流式数据片段
|
|
801
|
+
* 确保日志目录存在
|
|
828
802
|
*/
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
this.addFinalEvents(state, sseEvents);
|
|
834
|
-
return sseEvents;
|
|
803
|
+
ensureLogsDir() {
|
|
804
|
+
try {
|
|
805
|
+
mkdirSync(this.logsDir, { recursive: true });
|
|
806
|
+
} catch (error) {
|
|
835
807
|
}
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* 记录验证错误
|
|
811
|
+
*/
|
|
812
|
+
logValidationError(type, validationData, context = {}) {
|
|
813
|
+
const timestamp = context.timestamp || (/* @__PURE__ */ new Date()).toISOString();
|
|
814
|
+
const requestId = context.requestId || `req-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
815
|
+
const logEntry = {
|
|
816
|
+
timestamp,
|
|
817
|
+
requestId,
|
|
818
|
+
type,
|
|
819
|
+
model: context.model,
|
|
820
|
+
validationData,
|
|
821
|
+
errorDetails: this.extractErrorDetails(validationData, type)
|
|
822
|
+
};
|
|
823
|
+
const filename = `${type}-${requestId}.json`;
|
|
824
|
+
const filepath = join(this.logsDir, filename);
|
|
836
825
|
try {
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
return sseEvents;
|
|
826
|
+
writeFileSync(filepath, JSON.stringify(logEntry, null, 2));
|
|
827
|
+
return filepath;
|
|
840
828
|
} catch (error) {
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
line: openaiDataLine.substring(0, 200),
|
|
844
|
-
error: error instanceof Error ? error.message : String(error)
|
|
845
|
-
});
|
|
846
|
-
}
|
|
847
|
-
return [];
|
|
829
|
+
console.warn(`\u26A0\uFE0F [ValidationLogger] Failed to write log file: ${filepath} (${error.code})`);
|
|
830
|
+
return "";
|
|
848
831
|
}
|
|
849
832
|
}
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
833
|
+
/**
|
|
834
|
+
* 提取错误详情
|
|
835
|
+
*/
|
|
836
|
+
extractErrorDetails(data, type) {
|
|
837
|
+
switch (type) {
|
|
838
|
+
case "claude-request":
|
|
839
|
+
return {
|
|
840
|
+
model: data.model,
|
|
841
|
+
messagesCount: data.messages?.length,
|
|
842
|
+
hasSystem: !!data.system,
|
|
843
|
+
hasTools: !!data.tools,
|
|
844
|
+
missingFields: this.findMissingFields(data, ["model", "messages"]),
|
|
845
|
+
invalidTypes: this.findInvalidTypes(data)
|
|
846
|
+
};
|
|
847
|
+
case "claude-message":
|
|
848
|
+
return {
|
|
849
|
+
role: data.role,
|
|
850
|
+
contentType: typeof data.content,
|
|
851
|
+
missingFields: this.findMissingFields(data, ["role", "content"]),
|
|
852
|
+
invalidRoles: ["user", "assistant", "system"].includes(data.role) ? null : data.role
|
|
853
|
+
};
|
|
854
|
+
case "claude-tool":
|
|
855
|
+
return {
|
|
856
|
+
name: data.name,
|
|
857
|
+
description: data.description?.length,
|
|
858
|
+
missingFields: this.findMissingFields(data, ["name", "description", "input_schema"])
|
|
859
|
+
};
|
|
860
|
+
default:
|
|
861
|
+
return data;
|
|
862
|
+
}
|
|
880
863
|
}
|
|
881
|
-
};
|
|
882
|
-
var SUPPORTED_IMAGE_TYPES = [
|
|
883
|
-
"image/jpeg",
|
|
884
|
-
"image/png",
|
|
885
|
-
"image/gif",
|
|
886
|
-
"image/webp"
|
|
887
|
-
];
|
|
888
|
-
var TOOL_CONVERSION = {
|
|
889
864
|
/**
|
|
890
|
-
*
|
|
891
|
-
* 基于GitHub Copilot API测试结果,100%保持原始格式
|
|
865
|
+
* 查找缺失字段
|
|
892
866
|
*/
|
|
893
|
-
|
|
867
|
+
findMissingFields(obj, requiredFields) {
|
|
868
|
+
return requiredFields.filter((field) => obj[field] === void 0);
|
|
869
|
+
}
|
|
894
870
|
/**
|
|
895
|
-
*
|
|
871
|
+
* 查找无效类型
|
|
896
872
|
*/
|
|
897
|
-
|
|
873
|
+
findInvalidTypes(obj) {
|
|
874
|
+
const invalidTypes = {};
|
|
875
|
+
if (typeof obj.model !== "string") invalidTypes.model = typeof obj.model;
|
|
876
|
+
if (!Array.isArray(obj.messages)) invalidTypes.messages = typeof obj.messages;
|
|
877
|
+
if (obj.tools && !Array.isArray(obj.tools)) invalidTypes.tools = typeof obj.tools;
|
|
878
|
+
return invalidTypes;
|
|
879
|
+
}
|
|
898
880
|
/**
|
|
899
|
-
*
|
|
881
|
+
* 获取最近的验证错误日志路径
|
|
900
882
|
*/
|
|
901
|
-
|
|
883
|
+
getRecentLogs(limit = 10) {
|
|
884
|
+
try {
|
|
885
|
+
const fs = __require("fs");
|
|
886
|
+
const files = fs.readdirSync(this.logsDir).filter((f) => f.endsWith(".json")).sort((a, b) => {
|
|
887
|
+
const aTime = fs.statSync(join(this.logsDir, a)).mtime;
|
|
888
|
+
const bTime = fs.statSync(join(this.logsDir, b)).mtime;
|
|
889
|
+
return bTime.getTime() - aTime.getTime();
|
|
890
|
+
}).slice(0, limit).map((f) => join(this.logsDir, f));
|
|
891
|
+
return files;
|
|
892
|
+
} catch (error) {
|
|
893
|
+
return [];
|
|
894
|
+
}
|
|
895
|
+
}
|
|
902
896
|
};
|
|
897
|
+
var validationLogger = new ValidationLogger();
|
|
903
898
|
|
|
904
|
-
// src/core/a2o-request-adapter/
|
|
905
|
-
var
|
|
899
|
+
// src/core/a2o-request-adapter/format-validator.ts
|
|
900
|
+
var FormatValidator = class {
|
|
906
901
|
/**
|
|
907
|
-
*
|
|
908
|
-
* 修复关键问题:将tool_use转换为tool_calls,tool_result转换为role:"tool"消息
|
|
909
|
-
* 使用tool_use_id溯回工具名称解决unknown_tool问题
|
|
902
|
+
* 验证Claude请求格式
|
|
910
903
|
*/
|
|
911
|
-
static
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
}
|
|
904
|
+
static validateClaudeRequest(request) {
|
|
905
|
+
if (!request) {
|
|
906
|
+
const errorInfo = {
|
|
907
|
+
error: "request_is_null",
|
|
908
|
+
details: "Request\u5BF9\u8C61\u4E3A\u7A7A\u6216\u672A\u5B9A\u4E49"
|
|
909
|
+
};
|
|
910
|
+
const logPath = validationLogger.logValidationError("claude-request", errorInfo, { model: "unknown" });
|
|
911
|
+
console.warn(`\u26A0\uFE0F [Validation] Request validation failed. Log: ${logPath}`);
|
|
912
|
+
return false;
|
|
919
913
|
}
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
914
|
+
if (typeof request.model !== "string") {
|
|
915
|
+
const errorInfo = {
|
|
916
|
+
error: "invalid_model_type",
|
|
917
|
+
expected: "string",
|
|
918
|
+
actual: typeof request.model,
|
|
919
|
+
value: request.model
|
|
920
|
+
};
|
|
921
|
+
const logPath = validationLogger.logValidationError("claude-request", errorInfo, { model: String(request.model) });
|
|
922
|
+
console.warn(`\u26A0\uFE0F [Validation] Request validation failed. Log: ${logPath}`);
|
|
923
|
+
return false;
|
|
924
|
+
}
|
|
925
|
+
if (!Array.isArray(request.messages) || request.messages.length === 0) {
|
|
926
|
+
const errorInfo = {
|
|
927
|
+
error: "invalid_messages",
|
|
928
|
+
isArray: Array.isArray(request.messages),
|
|
929
|
+
length: request.messages?.length,
|
|
930
|
+
actualType: typeof request.messages,
|
|
931
|
+
messages: request.messages
|
|
932
|
+
};
|
|
933
|
+
const logPath = validationLogger.logValidationError("claude-request", errorInfo, { model: request.model });
|
|
934
|
+
console.warn(`\u26A0\uFE0F [Validation] Request validation failed. Log: ${logPath}`);
|
|
935
|
+
return false;
|
|
936
|
+
}
|
|
937
|
+
for (let i = 0; i < request.messages.length; i++) {
|
|
938
|
+
const message = request.messages[i];
|
|
939
|
+
if (!this.validateClaudeMessage(message)) {
|
|
940
|
+
const errorInfo = {
|
|
941
|
+
error: "invalid_message_at_index",
|
|
942
|
+
index: i,
|
|
943
|
+
message,
|
|
944
|
+
role: message?.role,
|
|
945
|
+
contentType: typeof message?.content,
|
|
946
|
+
hasContent: message?.content !== void 0
|
|
947
|
+
};
|
|
948
|
+
const logPath = validationLogger.logValidationError("claude-message", errorInfo, { model: request.model });
|
|
949
|
+
console.warn(`\u26A0\uFE0F [Validation] Request validation failed. Log: ${logPath}`);
|
|
950
|
+
return false;
|
|
932
951
|
}
|
|
933
952
|
}
|
|
934
|
-
if (
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
953
|
+
if (request.tools) {
|
|
954
|
+
if (!Array.isArray(request.tools)) {
|
|
955
|
+
const errorInfo = {
|
|
956
|
+
error: "invalid_tools_type",
|
|
957
|
+
expected: "array",
|
|
958
|
+
actual: typeof request.tools,
|
|
959
|
+
tools: request.tools
|
|
960
|
+
};
|
|
961
|
+
const logPath = validationLogger.logValidationError("claude-tool", errorInfo, { model: request.model });
|
|
962
|
+
console.warn(`\u26A0\uFE0F [Validation] Request validation failed. Log: ${logPath}`);
|
|
963
|
+
return false;
|
|
964
|
+
}
|
|
965
|
+
for (let i = 0; i < request.tools.length; i++) {
|
|
966
|
+
const tool = request.tools[i];
|
|
967
|
+
if (!this.validateClaudeToolDefinition(tool)) {
|
|
968
|
+
const errorInfo = {
|
|
969
|
+
error: "invalid_tool_definition",
|
|
970
|
+
index: i,
|
|
971
|
+
tool,
|
|
972
|
+
hasName: !!tool?.name,
|
|
973
|
+
hasDescription: !!tool?.description,
|
|
974
|
+
hasInputSchema: !!tool?.input_schema,
|
|
975
|
+
missingFields: this.getMissingToolFields(tool)
|
|
976
|
+
};
|
|
977
|
+
const logPath = validationLogger.logValidationError("claude-tool", errorInfo, { model: request.model });
|
|
978
|
+
console.warn(`\u26A0\uFE0F [Validation] Request validation failed. Log: ${logPath}`);
|
|
979
|
+
return false;
|
|
940
980
|
}
|
|
941
981
|
}
|
|
942
982
|
}
|
|
943
|
-
if (
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
if (Array.isArray(msg.content)) {
|
|
956
|
-
for (const item of msg.content) {
|
|
957
|
-
if (typeof item === "object" && item !== null && item.type === "tool_use") {
|
|
958
|
-
toolIdToNameMap.set(item.id, item.name);
|
|
959
|
-
}
|
|
960
|
-
}
|
|
983
|
+
if (request.system !== void 0) {
|
|
984
|
+
if (!this.validateSystemMessage(request.system)) {
|
|
985
|
+
const errorInfo = {
|
|
986
|
+
error: "invalid_system_message",
|
|
987
|
+
system: request.system,
|
|
988
|
+
type: typeof request.system,
|
|
989
|
+
isString: typeof request.system === "string",
|
|
990
|
+
isArray: Array.isArray(request.system)
|
|
991
|
+
};
|
|
992
|
+
const logPath = validationLogger.logValidationError("claude-request", errorInfo, { model: request.model });
|
|
993
|
+
console.warn(`\u26A0\uFE0F [Validation] Request validation failed. Log: ${logPath}`);
|
|
994
|
+
return false;
|
|
961
995
|
}
|
|
962
996
|
}
|
|
963
|
-
return
|
|
964
|
-
toolIdToNameMap,
|
|
965
|
-
hasSystemMessage: false
|
|
966
|
-
};
|
|
997
|
+
return true;
|
|
967
998
|
}
|
|
968
999
|
/**
|
|
969
|
-
*
|
|
1000
|
+
* 验证OpenAI请求格式
|
|
970
1001
|
*/
|
|
971
|
-
static
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
}
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
} else if (textContent.length > 0) {
|
|
985
|
-
const textMessage = this.createTextMessage(msg.role, textContent);
|
|
986
|
-
if (textMessage) {
|
|
987
|
-
resultMessages.push(textMessage);
|
|
1002
|
+
static validateOpenAIRequest(request) {
|
|
1003
|
+
if (!request) {
|
|
1004
|
+
return false;
|
|
1005
|
+
}
|
|
1006
|
+
if (typeof request.model !== "string") {
|
|
1007
|
+
return false;
|
|
1008
|
+
}
|
|
1009
|
+
if (!Array.isArray(request.messages) || request.messages.length === 0) {
|
|
1010
|
+
return false;
|
|
1011
|
+
}
|
|
1012
|
+
for (const message of request.messages) {
|
|
1013
|
+
if (!this.validateOpenAIMessage(message)) {
|
|
1014
|
+
return false;
|
|
988
1015
|
}
|
|
989
1016
|
}
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
const toolUses = [];
|
|
998
|
-
const toolResults = [];
|
|
999
|
-
for (const item of content) {
|
|
1000
|
-
if (typeof item === "string") {
|
|
1001
|
-
textContent.push({ type: "text", text: item });
|
|
1002
|
-
} else if (typeof item === "object" && item !== null) {
|
|
1003
|
-
switch (item.type) {
|
|
1004
|
-
case "text":
|
|
1005
|
-
textContent.push(item);
|
|
1006
|
-
break;
|
|
1007
|
-
case "tool_use":
|
|
1008
|
-
toolUses.push(item);
|
|
1009
|
-
break;
|
|
1010
|
-
case "tool_result":
|
|
1011
|
-
toolResults.push(item);
|
|
1012
|
-
break;
|
|
1013
|
-
case "image":
|
|
1014
|
-
const imageContent = this.convertImageContent(item);
|
|
1015
|
-
if (imageContent) {
|
|
1016
|
-
textContent.push(imageContent);
|
|
1017
|
-
}
|
|
1018
|
-
break;
|
|
1017
|
+
if (request.tools) {
|
|
1018
|
+
if (!Array.isArray(request.tools)) {
|
|
1019
|
+
return false;
|
|
1020
|
+
}
|
|
1021
|
+
for (const tool of request.tools) {
|
|
1022
|
+
if (!this.validateOpenAIToolDefinition(tool)) {
|
|
1023
|
+
return false;
|
|
1019
1024
|
}
|
|
1020
1025
|
}
|
|
1021
1026
|
}
|
|
1022
|
-
return
|
|
1027
|
+
return true;
|
|
1023
1028
|
}
|
|
1024
1029
|
/**
|
|
1025
|
-
*
|
|
1030
|
+
* 验证Claude消息格式
|
|
1026
1031
|
*/
|
|
1027
|
-
static
|
|
1028
|
-
if (
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
return {
|
|
1035
|
-
type: "image_url",
|
|
1036
|
-
image_url: {
|
|
1037
|
-
url: dataUri
|
|
1038
|
-
}
|
|
1032
|
+
static validateClaudeMessage(message) {
|
|
1033
|
+
if (!message || !message.role) {
|
|
1034
|
+
const errorInfo2 = {
|
|
1035
|
+
error: "missing_message_or_role",
|
|
1036
|
+
hasMessage: !!message,
|
|
1037
|
+
hasRole: !!message?.role,
|
|
1038
|
+
message
|
|
1039
1039
|
};
|
|
1040
|
+
const logPath2 = validationLogger.logValidationError("claude-message", errorInfo2);
|
|
1041
|
+
console.warn(`\u26A0\uFE0F [Validation] Message validation failed. Log: ${logPath2}`);
|
|
1042
|
+
return false;
|
|
1040
1043
|
}
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
};
|
|
1052
|
-
if (textContent.length > 0) {
|
|
1053
|
-
const textOnly = textContent.map((item) => item.text || "").join("");
|
|
1054
|
-
if (textOnly.trim()) {
|
|
1055
|
-
assistantMessage.content = textOnly.trim();
|
|
1056
|
-
}
|
|
1044
|
+
const validRoles = ["user", "assistant", "system"];
|
|
1045
|
+
if (!validRoles.includes(message.role)) {
|
|
1046
|
+
const errorInfo2 = {
|
|
1047
|
+
error: "invalid_role",
|
|
1048
|
+
role: message.role,
|
|
1049
|
+
validRoles
|
|
1050
|
+
};
|
|
1051
|
+
const logPath2 = validationLogger.logValidationError("claude-message", errorInfo2);
|
|
1052
|
+
console.warn(`\u26A0\uFE0F [Validation] Message role validation failed. Log: ${logPath2}`);
|
|
1053
|
+
return false;
|
|
1057
1054
|
}
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1055
|
+
if (message.content === void 0) {
|
|
1056
|
+
const errorInfo2 = {
|
|
1057
|
+
error: "content_is_undefined",
|
|
1058
|
+
message,
|
|
1059
|
+
role: message.role
|
|
1060
|
+
};
|
|
1061
|
+
const logPath2 = validationLogger.logValidationError("claude-message", errorInfo2);
|
|
1062
|
+
console.warn(`\u26A0\uFE0F [Validation] Message content validation failed. Log: ${logPath2}`);
|
|
1063
|
+
return false;
|
|
1064
|
+
}
|
|
1065
|
+
if (typeof message.content === "string") {
|
|
1066
|
+
return true;
|
|
1067
|
+
}
|
|
1068
|
+
if (Array.isArray(message.content)) {
|
|
1069
|
+
return this.validateClaudeContentBlocks(message.content);
|
|
1070
|
+
}
|
|
1071
|
+
const errorInfo = {
|
|
1072
|
+
error: "invalid_content_type",
|
|
1073
|
+
contentType: typeof message.content,
|
|
1074
|
+
content: message.content,
|
|
1075
|
+
role: message.role
|
|
1076
|
+
};
|
|
1077
|
+
const logPath = validationLogger.logValidationError("claude-content", errorInfo);
|
|
1078
|
+
console.warn(`\u26A0\uFE0F [Validation] Message content type validation failed. Log: ${logPath}`);
|
|
1079
|
+
return false;
|
|
1067
1080
|
}
|
|
1068
1081
|
/**
|
|
1069
|
-
*
|
|
1082
|
+
* 验证Claude内容块
|
|
1070
1083
|
*/
|
|
1071
|
-
static
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
if (
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1084
|
+
static validateClaudeContentBlocks(content) {
|
|
1085
|
+
for (let i = 0; i < content.length; i++) {
|
|
1086
|
+
const block = content[i];
|
|
1087
|
+
if (!this.validateClaudeContentBlock(block)) {
|
|
1088
|
+
const errorInfo = {
|
|
1089
|
+
error: "invalid_content_block",
|
|
1090
|
+
index: i,
|
|
1091
|
+
block,
|
|
1092
|
+
type: block?.type,
|
|
1093
|
+
hasType: !!block?.type
|
|
1094
|
+
};
|
|
1095
|
+
validationLogger.logValidationError("claude-content", errorInfo);
|
|
1096
|
+
return false;
|
|
1080
1097
|
}
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
role: "tool",
|
|
1084
|
-
tool_call_id: toolResult.tool_use_id,
|
|
1085
|
-
name: toolName,
|
|
1086
|
-
content: resultContent
|
|
1087
|
-
};
|
|
1088
|
-
});
|
|
1098
|
+
}
|
|
1099
|
+
return true;
|
|
1089
1100
|
}
|
|
1090
1101
|
/**
|
|
1091
|
-
*
|
|
1102
|
+
* 验证单个Claude内容块
|
|
1092
1103
|
*/
|
|
1093
|
-
static
|
|
1094
|
-
if (
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
};
|
|
1101
|
-
} else {
|
|
1102
|
-
const textOnly = textContent.map((item) => item.text || "").join("");
|
|
1103
|
-
return {
|
|
1104
|
-
role,
|
|
1105
|
-
content: textOnly.trim() || ""
|
|
1106
|
-
// 确保content为字符串,避免null
|
|
1104
|
+
static validateClaudeContentBlock(block) {
|
|
1105
|
+
if (!block || !block.type) {
|
|
1106
|
+
const errorInfo = {
|
|
1107
|
+
error: "missing_block_or_type",
|
|
1108
|
+
hasBlock: !!block,
|
|
1109
|
+
hasType: !!block?.type,
|
|
1110
|
+
block
|
|
1107
1111
|
};
|
|
1112
|
+
validationLogger.logValidationError("claude-content", errorInfo);
|
|
1113
|
+
return false;
|
|
1114
|
+
}
|
|
1115
|
+
switch (block.type) {
|
|
1116
|
+
case "text":
|
|
1117
|
+
if (typeof block.text !== "string") {
|
|
1118
|
+
const errorInfo2 = {
|
|
1119
|
+
error: "invalid_text_content",
|
|
1120
|
+
type: block.type,
|
|
1121
|
+
textType: typeof block.text,
|
|
1122
|
+
text: block.text
|
|
1123
|
+
};
|
|
1124
|
+
validationLogger.logValidationError("claude-content", errorInfo2);
|
|
1125
|
+
return false;
|
|
1126
|
+
}
|
|
1127
|
+
return true;
|
|
1128
|
+
case "thinking":
|
|
1129
|
+
return typeof block.thinking === "string";
|
|
1130
|
+
case "tool_use":
|
|
1131
|
+
return !!(block.id && block.name && block.input !== void 0);
|
|
1132
|
+
case "tool_result":
|
|
1133
|
+
return !!(block.tool_use_id && block.content !== void 0);
|
|
1134
|
+
case "image":
|
|
1135
|
+
return this.validateImageBlock(block);
|
|
1136
|
+
default:
|
|
1137
|
+
const errorInfo = {
|
|
1138
|
+
error: "unknown_block_type",
|
|
1139
|
+
type: block.type,
|
|
1140
|
+
validTypes: ["text", "thinking", "tool_use", "tool_result", "image"],
|
|
1141
|
+
block
|
|
1142
|
+
};
|
|
1143
|
+
validationLogger.logValidationError("claude-content", errorInfo);
|
|
1144
|
+
return false;
|
|
1108
1145
|
}
|
|
1109
1146
|
}
|
|
1110
1147
|
/**
|
|
1111
|
-
*
|
|
1148
|
+
* 验证图片块
|
|
1112
1149
|
*/
|
|
1113
|
-
static
|
|
1114
|
-
|
|
1115
|
-
if (
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
systemContent = system;
|
|
1124
|
-
}
|
|
1125
|
-
if (systemContent) {
|
|
1126
|
-
return {
|
|
1127
|
-
role: "system",
|
|
1128
|
-
content: systemContent
|
|
1150
|
+
static validateImageBlock(block) {
|
|
1151
|
+
const isValid = !!(block.source && block.source.type === "base64" && block.source.data && block.source.media_type);
|
|
1152
|
+
if (!isValid) {
|
|
1153
|
+
const errorInfo = {
|
|
1154
|
+
error: "invalid_image_block",
|
|
1155
|
+
hasSource: !!block.source,
|
|
1156
|
+
sourceType: block.source?.type,
|
|
1157
|
+
hasData: !!block.source?.data,
|
|
1158
|
+
hasMediaType: !!block.source?.media_type,
|
|
1159
|
+
block
|
|
1129
1160
|
};
|
|
1161
|
+
validationLogger.logValidationError("claude-content", errorInfo);
|
|
1130
1162
|
}
|
|
1131
|
-
return
|
|
1163
|
+
return isValid;
|
|
1132
1164
|
}
|
|
1133
|
-
};
|
|
1134
|
-
|
|
1135
|
-
// src/core/a2o-request-adapter/tool-converter.ts
|
|
1136
|
-
var ToolConverter = class {
|
|
1137
1165
|
/**
|
|
1138
|
-
*
|
|
1166
|
+
* 验证OpenAI消息格式
|
|
1139
1167
|
*/
|
|
1140
|
-
static
|
|
1141
|
-
if (!
|
|
1142
|
-
|
|
1168
|
+
static validateOpenAIMessage(message) {
|
|
1169
|
+
if (!message || !message.role) {
|
|
1170
|
+
return false;
|
|
1143
1171
|
}
|
|
1144
|
-
const
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
throw new Error(`Invalid tool definition for ${anthropicTool.name}: missing input_schema`);
|
|
1172
|
+
const validRoles = ["user", "assistant", "system", "tool"];
|
|
1173
|
+
if (!validRoles.includes(message.role)) {
|
|
1174
|
+
return false;
|
|
1148
1175
|
}
|
|
1149
|
-
const
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
parameters
|
|
1160
|
-
}
|
|
1161
|
-
};
|
|
1176
|
+
const hasContent = message.content !== void 0;
|
|
1177
|
+
const hasToolCalls = Array.isArray(message.tool_calls) && message.tool_calls.length > 0;
|
|
1178
|
+
const hasToolCallId = message.role === "tool" && message.tool_call_id;
|
|
1179
|
+
if (message.role === "assistant") {
|
|
1180
|
+
return hasContent || hasToolCalls;
|
|
1181
|
+
}
|
|
1182
|
+
if (message.role === "tool") {
|
|
1183
|
+
return hasToolCallId && hasContent;
|
|
1184
|
+
}
|
|
1185
|
+
return hasContent;
|
|
1162
1186
|
}
|
|
1163
1187
|
/**
|
|
1164
|
-
*
|
|
1188
|
+
* 获取工具定义缺失字段
|
|
1165
1189
|
*/
|
|
1166
|
-
static
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
} catch (error) {
|
|
1173
|
-
parsedInput = { raw_arguments: toolCall.function.arguments };
|
|
1174
|
-
}
|
|
1175
|
-
return {
|
|
1176
|
-
type: "tool_use",
|
|
1177
|
-
id: toolCall.id,
|
|
1178
|
-
name: claudeToolName,
|
|
1179
|
-
input: parsedInput
|
|
1180
|
-
};
|
|
1181
|
-
});
|
|
1190
|
+
static getMissingToolFields(tool) {
|
|
1191
|
+
const missing = [];
|
|
1192
|
+
if (!tool?.name) missing.push("name");
|
|
1193
|
+
if (!tool?.description) missing.push("description");
|
|
1194
|
+
if (!tool?.input_schema) missing.push("input_schema");
|
|
1195
|
+
return missing;
|
|
1182
1196
|
}
|
|
1183
1197
|
/**
|
|
1184
|
-
*
|
|
1198
|
+
* 验证Claude工具定义
|
|
1185
1199
|
*/
|
|
1186
|
-
static
|
|
1187
|
-
|
|
1200
|
+
static validateClaudeToolDefinition(tool) {
|
|
1201
|
+
if (!tool || typeof tool.name !== "string") {
|
|
1202
|
+
const errorInfo = {
|
|
1203
|
+
error: "invalid_tool_name",
|
|
1204
|
+
hasTool: !!tool,
|
|
1205
|
+
nameType: typeof tool?.name,
|
|
1206
|
+
name: tool?.name
|
|
1207
|
+
};
|
|
1208
|
+
validationLogger.logValidationError("claude-tool", errorInfo);
|
|
1209
|
+
return false;
|
|
1210
|
+
}
|
|
1211
|
+
if (!tool.input_schema || typeof tool.input_schema !== "object") {
|
|
1212
|
+
const errorInfo = {
|
|
1213
|
+
error: "invalid_input_schema",
|
|
1214
|
+
hasInputSchema: !!tool.input_schema,
|
|
1215
|
+
schemaType: typeof tool.input_schema,
|
|
1216
|
+
tool
|
|
1217
|
+
};
|
|
1218
|
+
validationLogger.logValidationError("claude-tool", errorInfo);
|
|
1219
|
+
return false;
|
|
1220
|
+
}
|
|
1221
|
+
const schema = tool.input_schema;
|
|
1222
|
+
if (!schema.type || !schema.properties) {
|
|
1223
|
+
const errorInfo = {
|
|
1224
|
+
error: "invalid_schema_structure",
|
|
1225
|
+
hasType: !!schema.type,
|
|
1226
|
+
hasProperties: !!schema.properties,
|
|
1227
|
+
schema
|
|
1228
|
+
};
|
|
1229
|
+
validationLogger.logValidationError("claude-tool", errorInfo);
|
|
1230
|
+
return false;
|
|
1231
|
+
}
|
|
1232
|
+
return ToolConverter.validateToolDefinition(tool);
|
|
1188
1233
|
}
|
|
1189
1234
|
/**
|
|
1190
|
-
*
|
|
1235
|
+
* 验证OpenAI工具定义
|
|
1191
1236
|
*/
|
|
1192
|
-
static
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
if (firstLine.length > maxLength) {
|
|
1196
|
-
return firstLine.substring(0, maxLength - 3) + "...";
|
|
1237
|
+
static validateOpenAIToolDefinition(tool) {
|
|
1238
|
+
if (!tool || tool.type !== "function") {
|
|
1239
|
+
return false;
|
|
1197
1240
|
}
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
/**
|
|
1201
|
-
* 验证工具定义的有效性
|
|
1202
|
-
*/
|
|
1203
|
-
static validateToolDefinition(tool) {
|
|
1204
|
-
if (!tool) return false;
|
|
1205
|
-
if ("input_schema" in tool) {
|
|
1206
|
-
return !!(tool.name && tool.input_schema && tool.input_schema.type);
|
|
1241
|
+
if (!tool.function || typeof tool.function.name !== "string") {
|
|
1242
|
+
return false;
|
|
1207
1243
|
}
|
|
1208
|
-
if (
|
|
1209
|
-
return
|
|
1244
|
+
if (!tool.function.parameters || typeof tool.function.parameters !== "object") {
|
|
1245
|
+
return false;
|
|
1210
1246
|
}
|
|
1211
|
-
return
|
|
1247
|
+
return ToolConverter.validateToolDefinition(tool);
|
|
1212
1248
|
}
|
|
1213
1249
|
/**
|
|
1214
|
-
*
|
|
1250
|
+
* 验证系统消息
|
|
1215
1251
|
*/
|
|
1216
|
-
static
|
|
1217
|
-
if ("
|
|
1218
|
-
return
|
|
1252
|
+
static validateSystemMessage(system) {
|
|
1253
|
+
if (typeof system === "string") {
|
|
1254
|
+
return true;
|
|
1219
1255
|
}
|
|
1220
|
-
if (
|
|
1221
|
-
return
|
|
1256
|
+
if (Array.isArray(system)) {
|
|
1257
|
+
return system.every(
|
|
1258
|
+
(item) => typeof item === "string" || typeof item === "object" && typeof item.text === "string"
|
|
1259
|
+
);
|
|
1222
1260
|
}
|
|
1223
|
-
return
|
|
1224
|
-
}
|
|
1225
|
-
};
|
|
1226
|
-
|
|
1227
|
-
// src/utils/validation-logger.ts
|
|
1228
|
-
import { writeFileSync, mkdirSync } from "fs";
|
|
1229
|
-
import { join } from "path";
|
|
1230
|
-
var ValidationLogger = class {
|
|
1231
|
-
constructor(logsDir = "/app/logs/request-validation-errors") {
|
|
1232
|
-
this.logsDir = logsDir;
|
|
1233
|
-
this.ensureLogsDir();
|
|
1261
|
+
return false;
|
|
1234
1262
|
}
|
|
1235
1263
|
/**
|
|
1236
|
-
*
|
|
1264
|
+
* 验证转换结果
|
|
1237
1265
|
*/
|
|
1238
|
-
|
|
1266
|
+
static validateConversionResult(original, converted, direction) {
|
|
1239
1267
|
try {
|
|
1240
|
-
|
|
1268
|
+
if (direction === "claude-to-openai") {
|
|
1269
|
+
return this.validateOpenAIRequest(converted);
|
|
1270
|
+
} else {
|
|
1271
|
+
return this.validateClaudeRequest(converted);
|
|
1272
|
+
}
|
|
1241
1273
|
} catch (error) {
|
|
1274
|
+
console.warn(`\u26A0\uFE0F [Validation] Conversion result validation failed: ${error?.message || error}`);
|
|
1275
|
+
return false;
|
|
1242
1276
|
}
|
|
1243
1277
|
}
|
|
1244
1278
|
/**
|
|
1245
|
-
*
|
|
1279
|
+
* 获取验证错误详情
|
|
1246
1280
|
*/
|
|
1247
|
-
|
|
1248
|
-
const
|
|
1249
|
-
const requestId = context.requestId || `req-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
1250
|
-
const logEntry = {
|
|
1251
|
-
timestamp,
|
|
1252
|
-
requestId,
|
|
1253
|
-
type,
|
|
1254
|
-
model: context.model,
|
|
1255
|
-
validationData,
|
|
1256
|
-
errorDetails: this.extractErrorDetails(validationData, type)
|
|
1257
|
-
};
|
|
1258
|
-
const filename = `${type}-${requestId}.json`;
|
|
1259
|
-
const filepath = join(this.logsDir, filename);
|
|
1281
|
+
static getValidationErrors(request, type) {
|
|
1282
|
+
const errors = [];
|
|
1260
1283
|
try {
|
|
1261
|
-
|
|
1262
|
-
|
|
1284
|
+
if (type === "claude") {
|
|
1285
|
+
if (!this.validateClaudeRequest(request)) {
|
|
1286
|
+
errors.push("Claude\u8BF7\u6C42\u683C\u5F0F\u9A8C\u8BC1\u5931\u8D25");
|
|
1287
|
+
}
|
|
1288
|
+
} else {
|
|
1289
|
+
if (!this.validateOpenAIRequest(request)) {
|
|
1290
|
+
errors.push("OpenAI\u8BF7\u6C42\u683C\u5F0F\u9A8C\u8BC1\u5931\u8D25");
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1263
1293
|
} catch (error) {
|
|
1264
|
-
|
|
1265
|
-
return "";
|
|
1266
|
-
}
|
|
1267
|
-
}
|
|
1268
|
-
/**
|
|
1269
|
-
* 提取错误详情
|
|
1270
|
-
*/
|
|
1271
|
-
extractErrorDetails(data, type) {
|
|
1272
|
-
switch (type) {
|
|
1273
|
-
case "claude-request":
|
|
1274
|
-
return {
|
|
1275
|
-
model: data.model,
|
|
1276
|
-
messagesCount: data.messages?.length,
|
|
1277
|
-
hasSystem: !!data.system,
|
|
1278
|
-
hasTools: !!data.tools,
|
|
1279
|
-
missingFields: this.findMissingFields(data, ["model", "messages"]),
|
|
1280
|
-
invalidTypes: this.findInvalidTypes(data)
|
|
1281
|
-
};
|
|
1282
|
-
case "claude-message":
|
|
1283
|
-
return {
|
|
1284
|
-
role: data.role,
|
|
1285
|
-
contentType: typeof data.content,
|
|
1286
|
-
missingFields: this.findMissingFields(data, ["role", "content"]),
|
|
1287
|
-
invalidRoles: ["user", "assistant", "system"].includes(data.role) ? null : data.role
|
|
1288
|
-
};
|
|
1289
|
-
case "claude-tool":
|
|
1290
|
-
return {
|
|
1291
|
-
name: data.name,
|
|
1292
|
-
description: data.description?.length,
|
|
1293
|
-
missingFields: this.findMissingFields(data, ["name", "description", "input_schema"])
|
|
1294
|
-
};
|
|
1295
|
-
default:
|
|
1296
|
-
return data;
|
|
1294
|
+
errors.push(`\u9A8C\u8BC1\u8FC7\u7A0B\u51FA\u9519: ${error.message}`);
|
|
1297
1295
|
}
|
|
1298
|
-
|
|
1299
|
-
/**
|
|
1300
|
-
* 查找缺失字段
|
|
1301
|
-
*/
|
|
1302
|
-
findMissingFields(obj, requiredFields) {
|
|
1303
|
-
return requiredFields.filter((field) => obj[field] === void 0);
|
|
1304
|
-
}
|
|
1305
|
-
/**
|
|
1306
|
-
* 查找无效类型
|
|
1307
|
-
*/
|
|
1308
|
-
findInvalidTypes(obj) {
|
|
1309
|
-
const invalidTypes = {};
|
|
1310
|
-
if (typeof obj.model !== "string") invalidTypes.model = typeof obj.model;
|
|
1311
|
-
if (!Array.isArray(obj.messages)) invalidTypes.messages = typeof obj.messages;
|
|
1312
|
-
if (obj.tools && !Array.isArray(obj.tools)) invalidTypes.tools = typeof obj.tools;
|
|
1313
|
-
return invalidTypes;
|
|
1296
|
+
return errors;
|
|
1314
1297
|
}
|
|
1315
1298
|
/**
|
|
1316
1299
|
* 获取最近的验证错误日志路径
|
|
1317
1300
|
*/
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
const fs = __require("fs");
|
|
1321
|
-
const files = fs.readdirSync(this.logsDir).filter((f) => f.endsWith(".json")).sort((a, b) => {
|
|
1322
|
-
const aTime = fs.statSync(join(this.logsDir, a)).mtime;
|
|
1323
|
-
const bTime = fs.statSync(join(this.logsDir, b)).mtime;
|
|
1324
|
-
return bTime.getTime() - aTime.getTime();
|
|
1325
|
-
}).slice(0, limit).map((f) => join(this.logsDir, f));
|
|
1326
|
-
return files;
|
|
1327
|
-
} catch (error) {
|
|
1328
|
-
return [];
|
|
1329
|
-
}
|
|
1301
|
+
static getRecentValidationLogs(limit = 5) {
|
|
1302
|
+
return validationLogger.getRecentLogs(limit);
|
|
1330
1303
|
}
|
|
1331
|
-
};
|
|
1332
|
-
var validationLogger = new ValidationLogger();
|
|
1333
|
-
|
|
1334
|
-
// src/core/a2o-request-adapter/format-validator.ts
|
|
1335
|
-
var FormatValidator = class {
|
|
1336
1304
|
/**
|
|
1337
|
-
*
|
|
1305
|
+
* 验证并返回日志路径
|
|
1338
1306
|
*/
|
|
1339
|
-
static
|
|
1340
|
-
if (
|
|
1341
|
-
const
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
const logPath = validationLogger.logValidationError("claude-request", errorInfo, { model: "unknown" });
|
|
1346
|
-
console.warn(`\u26A0\uFE0F [Validation] Request validation failed. Log: ${logPath}`);
|
|
1347
|
-
return false;
|
|
1348
|
-
}
|
|
1349
|
-
if (typeof request.model !== "string") {
|
|
1350
|
-
const errorInfo = {
|
|
1351
|
-
error: "invalid_model_type",
|
|
1352
|
-
expected: "string",
|
|
1353
|
-
actual: typeof request.model,
|
|
1354
|
-
value: request.model
|
|
1355
|
-
};
|
|
1356
|
-
const logPath = validationLogger.logValidationError("claude-request", errorInfo, { model: String(request.model) });
|
|
1357
|
-
console.warn(`\u26A0\uFE0F [Validation] Request validation failed. Log: ${logPath}`);
|
|
1358
|
-
return false;
|
|
1359
|
-
}
|
|
1360
|
-
if (!Array.isArray(request.messages) || request.messages.length === 0) {
|
|
1361
|
-
const errorInfo = {
|
|
1362
|
-
error: "invalid_messages",
|
|
1363
|
-
isArray: Array.isArray(request.messages),
|
|
1364
|
-
length: request.messages?.length,
|
|
1365
|
-
actualType: typeof request.messages,
|
|
1366
|
-
messages: request.messages
|
|
1367
|
-
};
|
|
1368
|
-
const logPath = validationLogger.logValidationError("claude-request", errorInfo, { model: request.model });
|
|
1369
|
-
console.warn(`\u26A0\uFE0F [Validation] Request validation failed. Log: ${logPath}`);
|
|
1370
|
-
return false;
|
|
1371
|
-
}
|
|
1372
|
-
for (let i = 0; i < request.messages.length; i++) {
|
|
1373
|
-
const message = request.messages[i];
|
|
1374
|
-
if (!this.validateClaudeMessage(message)) {
|
|
1375
|
-
const errorInfo = {
|
|
1376
|
-
error: "invalid_message_at_index",
|
|
1377
|
-
index: i,
|
|
1378
|
-
message,
|
|
1379
|
-
role: message?.role,
|
|
1380
|
-
contentType: typeof message?.content,
|
|
1381
|
-
hasContent: message?.content !== void 0
|
|
1382
|
-
};
|
|
1383
|
-
const logPath = validationLogger.logValidationError("claude-message", errorInfo, { model: request.model });
|
|
1384
|
-
console.warn(`\u26A0\uFE0F [Validation] Request validation failed. Log: ${logPath}`);
|
|
1385
|
-
return false;
|
|
1386
|
-
}
|
|
1387
|
-
}
|
|
1388
|
-
if (request.tools) {
|
|
1389
|
-
if (!Array.isArray(request.tools)) {
|
|
1390
|
-
const errorInfo = {
|
|
1391
|
-
error: "invalid_tools_type",
|
|
1392
|
-
expected: "array",
|
|
1393
|
-
actual: typeof request.tools,
|
|
1394
|
-
tools: request.tools
|
|
1395
|
-
};
|
|
1396
|
-
const logPath = validationLogger.logValidationError("claude-tool", errorInfo, { model: request.model });
|
|
1397
|
-
console.warn(`\u26A0\uFE0F [Validation] Request validation failed. Log: ${logPath}`);
|
|
1398
|
-
return false;
|
|
1307
|
+
static validateWithLogPath(request, type) {
|
|
1308
|
+
if (type === "claude") {
|
|
1309
|
+
const isValid = this.validateClaudeRequest(request);
|
|
1310
|
+
if (!isValid) {
|
|
1311
|
+
const logPath = validationLogger.getRecentLogs(1)[0];
|
|
1312
|
+
return { valid: false, logPath };
|
|
1399
1313
|
}
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
index: i,
|
|
1406
|
-
tool,
|
|
1407
|
-
hasName: !!tool?.name,
|
|
1408
|
-
hasDescription: !!tool?.description,
|
|
1409
|
-
hasInputSchema: !!tool?.input_schema,
|
|
1410
|
-
missingFields: this.getMissingToolFields(tool)
|
|
1411
|
-
};
|
|
1412
|
-
const logPath = validationLogger.logValidationError("claude-tool", errorInfo, { model: request.model });
|
|
1413
|
-
console.warn(`\u26A0\uFE0F [Validation] Request validation failed. Log: ${logPath}`);
|
|
1414
|
-
return false;
|
|
1415
|
-
}
|
|
1416
|
-
}
|
|
1417
|
-
}
|
|
1418
|
-
if (request.system !== void 0) {
|
|
1419
|
-
if (!this.validateSystemMessage(request.system)) {
|
|
1420
|
-
const errorInfo = {
|
|
1421
|
-
error: "invalid_system_message",
|
|
1422
|
-
system: request.system,
|
|
1423
|
-
type: typeof request.system,
|
|
1424
|
-
isString: typeof request.system === "string",
|
|
1425
|
-
isArray: Array.isArray(request.system)
|
|
1426
|
-
};
|
|
1427
|
-
const logPath = validationLogger.logValidationError("claude-request", errorInfo, { model: request.model });
|
|
1428
|
-
console.warn(`\u26A0\uFE0F [Validation] Request validation failed. Log: ${logPath}`);
|
|
1429
|
-
return false;
|
|
1430
|
-
}
|
|
1431
|
-
}
|
|
1432
|
-
return true;
|
|
1433
|
-
}
|
|
1434
|
-
/**
|
|
1435
|
-
* 验证OpenAI请求格式
|
|
1436
|
-
*/
|
|
1437
|
-
static validateOpenAIRequest(request) {
|
|
1438
|
-
if (!request) {
|
|
1439
|
-
return false;
|
|
1440
|
-
}
|
|
1441
|
-
if (typeof request.model !== "string") {
|
|
1442
|
-
return false;
|
|
1443
|
-
}
|
|
1444
|
-
if (!Array.isArray(request.messages) || request.messages.length === 0) {
|
|
1445
|
-
return false;
|
|
1446
|
-
}
|
|
1447
|
-
for (const message of request.messages) {
|
|
1448
|
-
if (!this.validateOpenAIMessage(message)) {
|
|
1449
|
-
return false;
|
|
1450
|
-
}
|
|
1451
|
-
}
|
|
1452
|
-
if (request.tools) {
|
|
1453
|
-
if (!Array.isArray(request.tools)) {
|
|
1454
|
-
return false;
|
|
1455
|
-
}
|
|
1456
|
-
for (const tool of request.tools) {
|
|
1457
|
-
if (!this.validateOpenAIToolDefinition(tool)) {
|
|
1458
|
-
return false;
|
|
1459
|
-
}
|
|
1460
|
-
}
|
|
1461
|
-
}
|
|
1462
|
-
return true;
|
|
1463
|
-
}
|
|
1464
|
-
/**
|
|
1465
|
-
* 验证Claude消息格式
|
|
1466
|
-
*/
|
|
1467
|
-
static validateClaudeMessage(message) {
|
|
1468
|
-
if (!message || !message.role) {
|
|
1469
|
-
const errorInfo2 = {
|
|
1470
|
-
error: "missing_message_or_role",
|
|
1471
|
-
hasMessage: !!message,
|
|
1472
|
-
hasRole: !!message?.role,
|
|
1473
|
-
message
|
|
1474
|
-
};
|
|
1475
|
-
const logPath2 = validationLogger.logValidationError("claude-message", errorInfo2);
|
|
1476
|
-
console.warn(`\u26A0\uFE0F [Validation] Message validation failed. Log: ${logPath2}`);
|
|
1477
|
-
return false;
|
|
1478
|
-
}
|
|
1479
|
-
const validRoles = ["user", "assistant", "system"];
|
|
1480
|
-
if (!validRoles.includes(message.role)) {
|
|
1481
|
-
const errorInfo2 = {
|
|
1482
|
-
error: "invalid_role",
|
|
1483
|
-
role: message.role,
|
|
1484
|
-
validRoles
|
|
1485
|
-
};
|
|
1486
|
-
const logPath2 = validationLogger.logValidationError("claude-message", errorInfo2);
|
|
1487
|
-
console.warn(`\u26A0\uFE0F [Validation] Message role validation failed. Log: ${logPath2}`);
|
|
1488
|
-
return false;
|
|
1489
|
-
}
|
|
1490
|
-
if (message.content === void 0) {
|
|
1491
|
-
const errorInfo2 = {
|
|
1492
|
-
error: "content_is_undefined",
|
|
1493
|
-
message,
|
|
1494
|
-
role: message.role
|
|
1495
|
-
};
|
|
1496
|
-
const logPath2 = validationLogger.logValidationError("claude-message", errorInfo2);
|
|
1497
|
-
console.warn(`\u26A0\uFE0F [Validation] Message content validation failed. Log: ${logPath2}`);
|
|
1498
|
-
return false;
|
|
1499
|
-
}
|
|
1500
|
-
if (typeof message.content === "string") {
|
|
1501
|
-
return true;
|
|
1502
|
-
}
|
|
1503
|
-
if (Array.isArray(message.content)) {
|
|
1504
|
-
return this.validateClaudeContentBlocks(message.content);
|
|
1505
|
-
}
|
|
1506
|
-
const errorInfo = {
|
|
1507
|
-
error: "invalid_content_type",
|
|
1508
|
-
contentType: typeof message.content,
|
|
1509
|
-
content: message.content,
|
|
1510
|
-
role: message.role
|
|
1511
|
-
};
|
|
1512
|
-
const logPath = validationLogger.logValidationError("claude-content", errorInfo);
|
|
1513
|
-
console.warn(`\u26A0\uFE0F [Validation] Message content type validation failed. Log: ${logPath}`);
|
|
1514
|
-
return false;
|
|
1515
|
-
}
|
|
1516
|
-
/**
|
|
1517
|
-
* 验证Claude内容块
|
|
1518
|
-
*/
|
|
1519
|
-
static validateClaudeContentBlocks(content) {
|
|
1520
|
-
for (let i = 0; i < content.length; i++) {
|
|
1521
|
-
const block = content[i];
|
|
1522
|
-
if (!this.validateClaudeContentBlock(block)) {
|
|
1523
|
-
const errorInfo = {
|
|
1524
|
-
error: "invalid_content_block",
|
|
1525
|
-
index: i,
|
|
1526
|
-
block,
|
|
1527
|
-
type: block?.type,
|
|
1528
|
-
hasType: !!block?.type
|
|
1529
|
-
};
|
|
1530
|
-
validationLogger.logValidationError("claude-content", errorInfo);
|
|
1531
|
-
return false;
|
|
1532
|
-
}
|
|
1533
|
-
}
|
|
1534
|
-
return true;
|
|
1535
|
-
}
|
|
1536
|
-
/**
|
|
1537
|
-
* 验证单个Claude内容块
|
|
1538
|
-
*/
|
|
1539
|
-
static validateClaudeContentBlock(block) {
|
|
1540
|
-
if (!block || !block.type) {
|
|
1541
|
-
const errorInfo = {
|
|
1542
|
-
error: "missing_block_or_type",
|
|
1543
|
-
hasBlock: !!block,
|
|
1544
|
-
hasType: !!block?.type,
|
|
1545
|
-
block
|
|
1546
|
-
};
|
|
1547
|
-
validationLogger.logValidationError("claude-content", errorInfo);
|
|
1548
|
-
return false;
|
|
1549
|
-
}
|
|
1550
|
-
switch (block.type) {
|
|
1551
|
-
case "text":
|
|
1552
|
-
if (typeof block.text !== "string") {
|
|
1553
|
-
const errorInfo2 = {
|
|
1554
|
-
error: "invalid_text_content",
|
|
1555
|
-
type: block.type,
|
|
1556
|
-
textType: typeof block.text,
|
|
1557
|
-
text: block.text
|
|
1558
|
-
};
|
|
1559
|
-
validationLogger.logValidationError("claude-content", errorInfo2);
|
|
1560
|
-
return false;
|
|
1561
|
-
}
|
|
1562
|
-
return true;
|
|
1563
|
-
case "thinking":
|
|
1564
|
-
return typeof block.thinking === "string";
|
|
1565
|
-
case "tool_use":
|
|
1566
|
-
return !!(block.id && block.name && block.input !== void 0);
|
|
1567
|
-
case "tool_result":
|
|
1568
|
-
return !!(block.tool_use_id && block.content !== void 0);
|
|
1569
|
-
case "image":
|
|
1570
|
-
return this.validateImageBlock(block);
|
|
1571
|
-
default:
|
|
1572
|
-
const errorInfo = {
|
|
1573
|
-
error: "unknown_block_type",
|
|
1574
|
-
type: block.type,
|
|
1575
|
-
validTypes: ["text", "thinking", "tool_use", "tool_result", "image"],
|
|
1576
|
-
block
|
|
1577
|
-
};
|
|
1578
|
-
validationLogger.logValidationError("claude-content", errorInfo);
|
|
1579
|
-
return false;
|
|
1580
|
-
}
|
|
1581
|
-
}
|
|
1582
|
-
/**
|
|
1583
|
-
* 验证图片块
|
|
1584
|
-
*/
|
|
1585
|
-
static validateImageBlock(block) {
|
|
1586
|
-
const isValid = !!(block.source && block.source.type === "base64" && block.source.data && block.source.media_type);
|
|
1587
|
-
if (!isValid) {
|
|
1588
|
-
const errorInfo = {
|
|
1589
|
-
error: "invalid_image_block",
|
|
1590
|
-
hasSource: !!block.source,
|
|
1591
|
-
sourceType: block.source?.type,
|
|
1592
|
-
hasData: !!block.source?.data,
|
|
1593
|
-
hasMediaType: !!block.source?.media_type,
|
|
1594
|
-
block
|
|
1595
|
-
};
|
|
1596
|
-
validationLogger.logValidationError("claude-content", errorInfo);
|
|
1597
|
-
}
|
|
1598
|
-
return isValid;
|
|
1599
|
-
}
|
|
1600
|
-
/**
|
|
1601
|
-
* 验证OpenAI消息格式
|
|
1602
|
-
*/
|
|
1603
|
-
static validateOpenAIMessage(message) {
|
|
1604
|
-
if (!message || !message.role) {
|
|
1605
|
-
return false;
|
|
1606
|
-
}
|
|
1607
|
-
const validRoles = ["user", "assistant", "system", "tool"];
|
|
1608
|
-
if (!validRoles.includes(message.role)) {
|
|
1609
|
-
return false;
|
|
1610
|
-
}
|
|
1611
|
-
const hasContent = message.content !== void 0;
|
|
1612
|
-
const hasToolCalls = Array.isArray(message.tool_calls) && message.tool_calls.length > 0;
|
|
1613
|
-
const hasToolCallId = message.role === "tool" && message.tool_call_id;
|
|
1614
|
-
if (message.role === "assistant") {
|
|
1615
|
-
return hasContent || hasToolCalls;
|
|
1616
|
-
}
|
|
1617
|
-
if (message.role === "tool") {
|
|
1618
|
-
return hasToolCallId && hasContent;
|
|
1619
|
-
}
|
|
1620
|
-
return hasContent;
|
|
1621
|
-
}
|
|
1622
|
-
/**
|
|
1623
|
-
* 获取工具定义缺失字段
|
|
1624
|
-
*/
|
|
1625
|
-
static getMissingToolFields(tool) {
|
|
1626
|
-
const missing = [];
|
|
1627
|
-
if (!tool?.name) missing.push("name");
|
|
1628
|
-
if (!tool?.description) missing.push("description");
|
|
1629
|
-
if (!tool?.input_schema) missing.push("input_schema");
|
|
1630
|
-
return missing;
|
|
1631
|
-
}
|
|
1632
|
-
/**
|
|
1633
|
-
* 验证Claude工具定义
|
|
1634
|
-
*/
|
|
1635
|
-
static validateClaudeToolDefinition(tool) {
|
|
1636
|
-
if (!tool || typeof tool.name !== "string") {
|
|
1637
|
-
const errorInfo = {
|
|
1638
|
-
error: "invalid_tool_name",
|
|
1639
|
-
hasTool: !!tool,
|
|
1640
|
-
nameType: typeof tool?.name,
|
|
1641
|
-
name: tool?.name
|
|
1642
|
-
};
|
|
1643
|
-
validationLogger.logValidationError("claude-tool", errorInfo);
|
|
1644
|
-
return false;
|
|
1645
|
-
}
|
|
1646
|
-
if (!tool.input_schema || typeof tool.input_schema !== "object") {
|
|
1647
|
-
const errorInfo = {
|
|
1648
|
-
error: "invalid_input_schema",
|
|
1649
|
-
hasInputSchema: !!tool.input_schema,
|
|
1650
|
-
schemaType: typeof tool.input_schema,
|
|
1651
|
-
tool
|
|
1652
|
-
};
|
|
1653
|
-
validationLogger.logValidationError("claude-tool", errorInfo);
|
|
1654
|
-
return false;
|
|
1655
|
-
}
|
|
1656
|
-
const schema = tool.input_schema;
|
|
1657
|
-
if (!schema.type || !schema.properties) {
|
|
1658
|
-
const errorInfo = {
|
|
1659
|
-
error: "invalid_schema_structure",
|
|
1660
|
-
hasType: !!schema.type,
|
|
1661
|
-
hasProperties: !!schema.properties,
|
|
1662
|
-
schema
|
|
1663
|
-
};
|
|
1664
|
-
validationLogger.logValidationError("claude-tool", errorInfo);
|
|
1665
|
-
return false;
|
|
1666
|
-
}
|
|
1667
|
-
return ToolConverter.validateToolDefinition(tool);
|
|
1668
|
-
}
|
|
1669
|
-
/**
|
|
1670
|
-
* 验证OpenAI工具定义
|
|
1671
|
-
*/
|
|
1672
|
-
static validateOpenAIToolDefinition(tool) {
|
|
1673
|
-
if (!tool || tool.type !== "function") {
|
|
1674
|
-
return false;
|
|
1675
|
-
}
|
|
1676
|
-
if (!tool.function || typeof tool.function.name !== "string") {
|
|
1677
|
-
return false;
|
|
1678
|
-
}
|
|
1679
|
-
if (!tool.function.parameters || typeof tool.function.parameters !== "object") {
|
|
1680
|
-
return false;
|
|
1681
|
-
}
|
|
1682
|
-
return ToolConverter.validateToolDefinition(tool);
|
|
1683
|
-
}
|
|
1684
|
-
/**
|
|
1685
|
-
* 验证系统消息
|
|
1686
|
-
*/
|
|
1687
|
-
static validateSystemMessage(system) {
|
|
1688
|
-
if (typeof system === "string") {
|
|
1689
|
-
return true;
|
|
1690
|
-
}
|
|
1691
|
-
if (Array.isArray(system)) {
|
|
1692
|
-
return system.every(
|
|
1693
|
-
(item) => typeof item === "string" || typeof item === "object" && typeof item.text === "string"
|
|
1694
|
-
);
|
|
1695
|
-
}
|
|
1696
|
-
return false;
|
|
1697
|
-
}
|
|
1698
|
-
/**
|
|
1699
|
-
* 验证转换结果
|
|
1700
|
-
*/
|
|
1701
|
-
static validateConversionResult(original, converted, direction) {
|
|
1702
|
-
try {
|
|
1703
|
-
if (direction === "claude-to-openai") {
|
|
1704
|
-
return this.validateOpenAIRequest(converted);
|
|
1705
|
-
} else {
|
|
1706
|
-
return this.validateClaudeRequest(converted);
|
|
1707
|
-
}
|
|
1708
|
-
} catch (error) {
|
|
1709
|
-
console.warn(`\u26A0\uFE0F [Validation] Conversion result validation failed: ${error?.message || error}`);
|
|
1710
|
-
return false;
|
|
1711
|
-
}
|
|
1712
|
-
}
|
|
1713
|
-
/**
|
|
1714
|
-
* 获取验证错误详情
|
|
1715
|
-
*/
|
|
1716
|
-
static getValidationErrors(request, type) {
|
|
1717
|
-
const errors = [];
|
|
1718
|
-
try {
|
|
1719
|
-
if (type === "claude") {
|
|
1720
|
-
if (!this.validateClaudeRequest(request)) {
|
|
1721
|
-
errors.push("Claude\u8BF7\u6C42\u683C\u5F0F\u9A8C\u8BC1\u5931\u8D25");
|
|
1722
|
-
}
|
|
1723
|
-
} else {
|
|
1724
|
-
if (!this.validateOpenAIRequest(request)) {
|
|
1725
|
-
errors.push("OpenAI\u8BF7\u6C42\u683C\u5F0F\u9A8C\u8BC1\u5931\u8D25");
|
|
1726
|
-
}
|
|
1727
|
-
}
|
|
1728
|
-
} catch (error) {
|
|
1729
|
-
errors.push(`\u9A8C\u8BC1\u8FC7\u7A0B\u51FA\u9519: ${error.message}`);
|
|
1730
|
-
}
|
|
1731
|
-
return errors;
|
|
1732
|
-
}
|
|
1733
|
-
/**
|
|
1734
|
-
* 获取最近的验证错误日志路径
|
|
1735
|
-
*/
|
|
1736
|
-
static getRecentValidationLogs(limit = 5) {
|
|
1737
|
-
return validationLogger.getRecentLogs(limit);
|
|
1738
|
-
}
|
|
1739
|
-
/**
|
|
1740
|
-
* 验证并返回日志路径
|
|
1741
|
-
*/
|
|
1742
|
-
static validateWithLogPath(request, type) {
|
|
1743
|
-
if (type === "claude") {
|
|
1744
|
-
const isValid = this.validateClaudeRequest(request);
|
|
1745
|
-
if (!isValid) {
|
|
1746
|
-
const logPath = validationLogger.getRecentLogs(1)[0];
|
|
1747
|
-
return { valid: false, logPath };
|
|
1748
|
-
}
|
|
1749
|
-
} else {
|
|
1750
|
-
const isValid = this.validateOpenAIRequest(request);
|
|
1751
|
-
if (!isValid) {
|
|
1752
|
-
const logPath = validationLogger.getRecentLogs(1)[0];
|
|
1753
|
-
return { valid: false, logPath };
|
|
1314
|
+
} else {
|
|
1315
|
+
const isValid = this.validateOpenAIRequest(request);
|
|
1316
|
+
if (!isValid) {
|
|
1317
|
+
const logPath = validationLogger.getRecentLogs(1)[0];
|
|
1318
|
+
return { valid: false, logPath };
|
|
1754
1319
|
}
|
|
1755
1320
|
}
|
|
1756
1321
|
return { valid: true };
|
|
@@ -3043,1063 +2608,1763 @@ var REQUEST_HEALING_STRATEGIES = [
|
|
|
3043
2608
|
}),
|
|
3044
2609
|
priority: 5
|
|
3045
2610
|
},
|
|
3046
|
-
// 停止序列修复
|
|
2611
|
+
// 停止序列修复
|
|
2612
|
+
{
|
|
2613
|
+
id: "fix-stop-sequences-format",
|
|
2614
|
+
name: "Fix Stop Sequences Format",
|
|
2615
|
+
description: "Convert between stop_sequences and stop formats",
|
|
2616
|
+
condition: (data, ctx) => {
|
|
2617
|
+
return ctx.direction === "a2o" && data?.stop_sequences || ctx.direction === "o2a" && data?.stop;
|
|
2618
|
+
},
|
|
2619
|
+
fix: (data, ctx) => {
|
|
2620
|
+
if (ctx.direction === "a2o") {
|
|
2621
|
+
const { stop_sequences, ...rest } = data;
|
|
2622
|
+
return {
|
|
2623
|
+
...rest,
|
|
2624
|
+
stop: Array.isArray(stop_sequences) ? stop_sequences.slice(0, 4) : [stop_sequences]
|
|
2625
|
+
};
|
|
2626
|
+
} else {
|
|
2627
|
+
const { stop, ...rest } = data;
|
|
2628
|
+
return {
|
|
2629
|
+
...rest,
|
|
2630
|
+
stop_sequences: Array.isArray(stop) ? stop : [stop]
|
|
2631
|
+
};
|
|
2632
|
+
}
|
|
2633
|
+
},
|
|
2634
|
+
priority: 4
|
|
2635
|
+
}
|
|
2636
|
+
];
|
|
2637
|
+
var RESPONSE_HEALING_STRATEGIES = [
|
|
2638
|
+
// Usage 字段修复
|
|
2639
|
+
{
|
|
2640
|
+
id: "fix-usage-mapping-o2a",
|
|
2641
|
+
name: "Fix OpenAI to Anthropic Usage Mapping",
|
|
2642
|
+
description: "Map OpenAI usage format to Anthropic format",
|
|
2643
|
+
condition: (data, ctx) => {
|
|
2644
|
+
return ctx.direction === "o2a" && ctx.stage === "response" && data?.usage?.prompt_tokens != null;
|
|
2645
|
+
},
|
|
2646
|
+
fix: (data) => {
|
|
2647
|
+
const usage = data.usage;
|
|
2648
|
+
return {
|
|
2649
|
+
...data,
|
|
2650
|
+
usage: {
|
|
2651
|
+
input_tokens: usage.prompt_tokens || 0,
|
|
2652
|
+
output_tokens: usage.completion_tokens || 0,
|
|
2653
|
+
cache_read_input_tokens: usage.prompt_tokens_details?.cached_tokens || void 0
|
|
2654
|
+
}
|
|
2655
|
+
};
|
|
2656
|
+
},
|
|
2657
|
+
priority: 8
|
|
2658
|
+
},
|
|
2659
|
+
// Finish reason 修复
|
|
2660
|
+
{
|
|
2661
|
+
id: "fix-finish-reason-mapping",
|
|
2662
|
+
name: "Fix Finish Reason Mapping",
|
|
2663
|
+
description: "Map OpenAI finish_reason to Anthropic stop_reason",
|
|
2664
|
+
condition: (data, ctx) => {
|
|
2665
|
+
return ctx.direction === "o2a" && ctx.stage === "response" && data?.choices?.[0]?.finish_reason;
|
|
2666
|
+
},
|
|
2667
|
+
fix: (data) => {
|
|
2668
|
+
const choice = data.choices[0];
|
|
2669
|
+
let stop_reason = "end_turn";
|
|
2670
|
+
switch (choice.finish_reason) {
|
|
2671
|
+
case "length":
|
|
2672
|
+
stop_reason = "max_tokens";
|
|
2673
|
+
break;
|
|
2674
|
+
case "tool_calls":
|
|
2675
|
+
stop_reason = "tool_use";
|
|
2676
|
+
break;
|
|
2677
|
+
case "stop":
|
|
2678
|
+
default:
|
|
2679
|
+
stop_reason = "end_turn";
|
|
2680
|
+
break;
|
|
2681
|
+
}
|
|
2682
|
+
return {
|
|
2683
|
+
...data,
|
|
2684
|
+
stop_reason,
|
|
2685
|
+
stop_sequence: null
|
|
2686
|
+
};
|
|
2687
|
+
},
|
|
2688
|
+
priority: 7
|
|
2689
|
+
},
|
|
2690
|
+
// 内容格式修复
|
|
2691
|
+
{
|
|
2692
|
+
id: "fix-content-format-o2a",
|
|
2693
|
+
name: "Fix Content Format for Anthropic",
|
|
2694
|
+
description: "Convert OpenAI message content to Anthropic content blocks",
|
|
2695
|
+
condition: (data, ctx) => {
|
|
2696
|
+
return ctx.direction === "o2a" && ctx.stage === "response" && data?.choices?.[0]?.message;
|
|
2697
|
+
},
|
|
2698
|
+
fix: (data) => {
|
|
2699
|
+
const message = data.choices[0].message;
|
|
2700
|
+
const content = [];
|
|
2701
|
+
if (message.content) {
|
|
2702
|
+
content.push({
|
|
2703
|
+
type: "text",
|
|
2704
|
+
text: message.content
|
|
2705
|
+
});
|
|
2706
|
+
}
|
|
2707
|
+
if (message.tool_calls) {
|
|
2708
|
+
message.tool_calls.forEach((toolCall) => {
|
|
2709
|
+
content.push({
|
|
2710
|
+
type: "tool_use",
|
|
2711
|
+
id: toolCall.id,
|
|
2712
|
+
name: toolCall.function.name,
|
|
2713
|
+
input: JSON.parse(toolCall.function.arguments || "{}")
|
|
2714
|
+
});
|
|
2715
|
+
});
|
|
2716
|
+
}
|
|
2717
|
+
return {
|
|
2718
|
+
id: data.id || `msg_${Date.now()}`,
|
|
2719
|
+
type: "message",
|
|
2720
|
+
role: "assistant",
|
|
2721
|
+
model: data.model,
|
|
2722
|
+
content,
|
|
2723
|
+
stop_reason: data.stop_reason || "end_turn",
|
|
2724
|
+
stop_sequence: null,
|
|
2725
|
+
usage: data.usage
|
|
2726
|
+
};
|
|
2727
|
+
},
|
|
2728
|
+
priority: 9
|
|
2729
|
+
}
|
|
2730
|
+
];
|
|
2731
|
+
var STREAM_HEALING_STRATEGIES = [
|
|
2732
|
+
// SSE 格式修复
|
|
2733
|
+
{
|
|
2734
|
+
id: "fix-sse-format",
|
|
2735
|
+
name: "Fix SSE Format",
|
|
2736
|
+
description: "Ensure proper SSE event format",
|
|
2737
|
+
condition: (data, ctx) => {
|
|
2738
|
+
return ctx.stage === "stream" && typeof data === "string" && !data.startsWith("data: ");
|
|
2739
|
+
},
|
|
2740
|
+
fix: (data) => {
|
|
2741
|
+
if (data.trim() === "[DONE]") {
|
|
2742
|
+
return "data: [DONE]\n\n";
|
|
2743
|
+
}
|
|
2744
|
+
try {
|
|
2745
|
+
JSON.parse(data);
|
|
2746
|
+
return `data: ${data}
|
|
2747
|
+
|
|
2748
|
+
`;
|
|
2749
|
+
} catch {
|
|
2750
|
+
return data;
|
|
2751
|
+
}
|
|
2752
|
+
},
|
|
2753
|
+
priority: 8
|
|
2754
|
+
},
|
|
2755
|
+
// 事件类型修复
|
|
2756
|
+
{
|
|
2757
|
+
id: "fix-stream-event-type",
|
|
2758
|
+
name: "Fix Stream Event Type",
|
|
2759
|
+
description: "Add missing event type to stream events",
|
|
2760
|
+
condition: (data, ctx) => {
|
|
2761
|
+
return ctx.stage === "stream" && typeof data === "object" && !data?.type;
|
|
2762
|
+
},
|
|
2763
|
+
fix: (data, ctx) => {
|
|
2764
|
+
if (data.choices) {
|
|
2765
|
+
return { ...data, object: "chat.completion.chunk" };
|
|
2766
|
+
} else if (data.delta) {
|
|
2767
|
+
return { ...data, type: "content_block_delta" };
|
|
2768
|
+
} else {
|
|
2769
|
+
return { ...data, type: "message_start" };
|
|
2770
|
+
}
|
|
2771
|
+
},
|
|
2772
|
+
priority: 6
|
|
2773
|
+
}
|
|
2774
|
+
];
|
|
2775
|
+
var EMERGENCY_HEALING_STRATEGIES = [
|
|
2776
|
+
// 空数据修复
|
|
2777
|
+
{
|
|
2778
|
+
id: "emergency-null-data",
|
|
2779
|
+
name: "Emergency Null Data Fix",
|
|
2780
|
+
description: "Handle null or undefined data with safe defaults",
|
|
2781
|
+
condition: (data) => data == null,
|
|
2782
|
+
fix: (data, ctx) => {
|
|
2783
|
+
if (ctx.stage === "request") {
|
|
2784
|
+
return {
|
|
2785
|
+
model: ctx.direction === "a2o" ? "gpt-3.5-turbo" : "claude-3-haiku-20240307",
|
|
2786
|
+
messages: [{ role: "user", content: "Hello" }],
|
|
2787
|
+
max_tokens: ctx.direction === "o2a" ? 100 : void 0
|
|
2788
|
+
};
|
|
2789
|
+
}
|
|
2790
|
+
return {};
|
|
2791
|
+
},
|
|
2792
|
+
priority: 10
|
|
2793
|
+
},
|
|
2794
|
+
// 循环引用修复
|
|
2795
|
+
{
|
|
2796
|
+
id: "emergency-circular-reference",
|
|
2797
|
+
name: "Emergency Circular Reference Fix",
|
|
2798
|
+
description: "Remove circular references in data",
|
|
2799
|
+
condition: (data) => {
|
|
2800
|
+
try {
|
|
2801
|
+
JSON.stringify(data);
|
|
2802
|
+
return false;
|
|
2803
|
+
} catch (error) {
|
|
2804
|
+
return error.message.includes("circular");
|
|
2805
|
+
}
|
|
2806
|
+
},
|
|
2807
|
+
fix: (data) => {
|
|
2808
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
2809
|
+
const cleanData = JSON.parse(JSON.stringify(data, (key, value) => {
|
|
2810
|
+
if (typeof value === "object" && value !== null) {
|
|
2811
|
+
if (seen.has(value)) {
|
|
2812
|
+
return "[Circular Reference Removed]";
|
|
2813
|
+
}
|
|
2814
|
+
seen.add(value);
|
|
2815
|
+
}
|
|
2816
|
+
return value;
|
|
2817
|
+
}));
|
|
2818
|
+
return cleanData;
|
|
2819
|
+
},
|
|
2820
|
+
priority: 10
|
|
2821
|
+
},
|
|
2822
|
+
// 数据类型修复
|
|
3047
2823
|
{
|
|
3048
|
-
id: "
|
|
3049
|
-
name: "
|
|
3050
|
-
description: "
|
|
3051
|
-
condition: (data
|
|
3052
|
-
return
|
|
2824
|
+
id: "emergency-wrong-type",
|
|
2825
|
+
name: "Emergency Wrong Type Fix",
|
|
2826
|
+
description: "Fix fundamental type errors",
|
|
2827
|
+
condition: (data) => {
|
|
2828
|
+
return typeof data === "string" && (data.startsWith("{") || data.startsWith("["));
|
|
3053
2829
|
},
|
|
3054
|
-
fix: (data
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
stop: Array.isArray(stop_sequences) ? stop_sequences.slice(0, 4) : [stop_sequences]
|
|
3060
|
-
};
|
|
3061
|
-
} else {
|
|
3062
|
-
const { stop, ...rest } = data;
|
|
3063
|
-
return {
|
|
3064
|
-
...rest,
|
|
3065
|
-
stop_sequences: Array.isArray(stop) ? stop : [stop]
|
|
3066
|
-
};
|
|
2830
|
+
fix: (data) => {
|
|
2831
|
+
try {
|
|
2832
|
+
return JSON.parse(data);
|
|
2833
|
+
} catch {
|
|
2834
|
+
return { error: "Failed to parse JSON string", original: data };
|
|
3067
2835
|
}
|
|
3068
2836
|
},
|
|
3069
|
-
priority:
|
|
2837
|
+
priority: 9
|
|
3070
2838
|
}
|
|
3071
2839
|
];
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
2840
|
+
function getAllHealingStrategies() {
|
|
2841
|
+
return [
|
|
2842
|
+
...EMERGENCY_HEALING_STRATEGIES,
|
|
2843
|
+
...REQUEST_HEALING_STRATEGIES,
|
|
2844
|
+
...RESPONSE_HEALING_STRATEGIES,
|
|
2845
|
+
...STREAM_HEALING_STRATEGIES
|
|
2846
|
+
].sort((a, b) => b.priority - a.priority);
|
|
2847
|
+
}
|
|
2848
|
+
function getStrategiesForContext(context) {
|
|
2849
|
+
const allStrategies = getAllHealingStrategies();
|
|
2850
|
+
return allStrategies.filter((strategy) => {
|
|
2851
|
+
try {
|
|
2852
|
+
const testData = {};
|
|
2853
|
+
const testContext = {
|
|
2854
|
+
direction: context.direction || "a2o",
|
|
2855
|
+
stage: context.stage || "request",
|
|
2856
|
+
originalData: testData,
|
|
2857
|
+
attemptCount: 0,
|
|
2858
|
+
maxAttempts: 3
|
|
2859
|
+
};
|
|
2860
|
+
return true;
|
|
2861
|
+
} catch {
|
|
2862
|
+
return false;
|
|
2863
|
+
}
|
|
2864
|
+
});
|
|
2865
|
+
}
|
|
2866
|
+
|
|
2867
|
+
// src/core/healing/error-recovery.ts
|
|
2868
|
+
var ErrorDetector = class {
|
|
2869
|
+
/**
|
|
2870
|
+
* 检测并分析错误
|
|
2871
|
+
*/
|
|
2872
|
+
static analyzeError(error, context) {
|
|
2873
|
+
const timestamp = Date.now();
|
|
2874
|
+
if (error instanceof Error) {
|
|
3083
2875
|
return {
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
2876
|
+
type: this.classifyError(error),
|
|
2877
|
+
severity: this.assessSeverity(error),
|
|
2878
|
+
message: error.message,
|
|
2879
|
+
context,
|
|
2880
|
+
timestamp,
|
|
2881
|
+
stackTrace: error.stack
|
|
2882
|
+
};
|
|
2883
|
+
}
|
|
2884
|
+
if (typeof error === "object" && error && "status" in error) {
|
|
2885
|
+
const httpError = error;
|
|
2886
|
+
return {
|
|
2887
|
+
type: this.classifyHttpError(httpError.status),
|
|
2888
|
+
severity: this.assessHttpSeverity(httpError.status),
|
|
2889
|
+
message: httpError.message || `HTTP ${httpError.status}`,
|
|
2890
|
+
code: httpError.status,
|
|
2891
|
+
context,
|
|
2892
|
+
timestamp
|
|
2893
|
+
};
|
|
2894
|
+
}
|
|
2895
|
+
if (typeof error === "string") {
|
|
2896
|
+
return {
|
|
2897
|
+
type: this.classifyErrorMessage(error),
|
|
2898
|
+
severity: this.assessMessageSeverity(error),
|
|
2899
|
+
message: error,
|
|
2900
|
+
context,
|
|
2901
|
+
timestamp
|
|
2902
|
+
};
|
|
2903
|
+
}
|
|
2904
|
+
return {
|
|
2905
|
+
type: "unknown_error",
|
|
2906
|
+
severity: "medium",
|
|
2907
|
+
message: JSON.stringify(error),
|
|
2908
|
+
context,
|
|
2909
|
+
timestamp
|
|
2910
|
+
};
|
|
2911
|
+
}
|
|
2912
|
+
/**
|
|
2913
|
+
* 根据 Error 对象分类错误类型
|
|
2914
|
+
*/
|
|
2915
|
+
static classifyError(error) {
|
|
2916
|
+
const message = error.message.toLowerCase();
|
|
2917
|
+
if (message.includes("validation") || message.includes("schema")) {
|
|
2918
|
+
return "validation_error";
|
|
2919
|
+
}
|
|
2920
|
+
if (message.includes("timeout")) {
|
|
2921
|
+
return "timeout_error";
|
|
2922
|
+
}
|
|
2923
|
+
if (message.includes("network") || message.includes("fetch") || message.includes("connection")) {
|
|
2924
|
+
return "network_error";
|
|
2925
|
+
}
|
|
2926
|
+
if (message.includes("auth") || message.includes("unauthorized")) {
|
|
2927
|
+
return "auth_error";
|
|
2928
|
+
}
|
|
2929
|
+
if (message.includes("rate limit") || message.includes("too many requests")) {
|
|
2930
|
+
return "rate_limit_error";
|
|
2931
|
+
}
|
|
2932
|
+
if (message.includes("quota") || message.includes("limit exceeded")) {
|
|
2933
|
+
return "quota_error";
|
|
2934
|
+
}
|
|
2935
|
+
if (message.includes("convert") || message.includes("transform")) {
|
|
2936
|
+
return "conversion_error";
|
|
2937
|
+
}
|
|
2938
|
+
return "client_error";
|
|
2939
|
+
}
|
|
2940
|
+
/**
|
|
2941
|
+
* 根据 HTTP 状态码分类错误
|
|
2942
|
+
*/
|
|
2943
|
+
static classifyHttpError(status) {
|
|
2944
|
+
if (status === 401 || status === 403) return "auth_error";
|
|
2945
|
+
if (status === 429) return "rate_limit_error";
|
|
2946
|
+
if (status === 408 || status === 504) return "timeout_error";
|
|
2947
|
+
if (status >= 500) return "server_error";
|
|
2948
|
+
if (status >= 400) return "client_error";
|
|
2949
|
+
return "unknown_error";
|
|
2950
|
+
}
|
|
2951
|
+
/**
|
|
2952
|
+
* 评估错误严重程度
|
|
2953
|
+
*/
|
|
2954
|
+
static assessSeverity(error) {
|
|
2955
|
+
const message = error.message.toLowerCase();
|
|
2956
|
+
if (message.includes("critical") || message.includes("fatal")) return "critical";
|
|
2957
|
+
if (message.includes("timeout") || message.includes("network")) return "high";
|
|
2958
|
+
if (message.includes("validation") || message.includes("auth")) return "medium";
|
|
2959
|
+
return "low";
|
|
2960
|
+
}
|
|
2961
|
+
/**
|
|
2962
|
+
* 评估 HTTP 错误严重程度
|
|
2963
|
+
*/
|
|
2964
|
+
static assessHttpSeverity(status) {
|
|
2965
|
+
if (status >= 500) return "critical";
|
|
2966
|
+
if (status === 429 || status === 408 || status === 504) return "high";
|
|
2967
|
+
if (status === 401 || status === 403) return "medium";
|
|
2968
|
+
return "low";
|
|
2969
|
+
}
|
|
2970
|
+
/**
|
|
2971
|
+
* 根据消息内容分类错误
|
|
2972
|
+
*/
|
|
2973
|
+
static classifyErrorMessage(message) {
|
|
2974
|
+
const lowerMessage = message.toLowerCase();
|
|
2975
|
+
if (lowerMessage.includes("validation")) return "validation_error";
|
|
2976
|
+
if (lowerMessage.includes("timeout")) return "timeout_error";
|
|
2977
|
+
if (lowerMessage.includes("network")) return "network_error";
|
|
2978
|
+
if (lowerMessage.includes("auth")) return "auth_error";
|
|
2979
|
+
if (lowerMessage.includes("rate")) return "rate_limit_error";
|
|
2980
|
+
if (lowerMessage.includes("quota")) return "quota_error";
|
|
2981
|
+
return "unknown_error";
|
|
2982
|
+
}
|
|
2983
|
+
/**
|
|
2984
|
+
* 评估消息严重程度
|
|
2985
|
+
*/
|
|
2986
|
+
static assessMessageSeverity(message) {
|
|
2987
|
+
const lowerMessage = message.toLowerCase();
|
|
2988
|
+
if (lowerMessage.includes("critical") || lowerMessage.includes("fatal")) return "critical";
|
|
2989
|
+
if (lowerMessage.includes("error")) return "high";
|
|
2990
|
+
if (lowerMessage.includes("warning")) return "medium";
|
|
2991
|
+
return "low";
|
|
2992
|
+
}
|
|
2993
|
+
};
|
|
2994
|
+
var RECOVERY_STRATEGIES = [
|
|
2995
|
+
// 网络错误恢复
|
|
2996
|
+
{
|
|
2997
|
+
id: "network-retry",
|
|
2998
|
+
name: "Network Error Retry",
|
|
2999
|
+
description: "Retry request after network errors",
|
|
3000
|
+
supportedErrors: ["network_error", "timeout_error"],
|
|
3001
|
+
maxRetries: 3,
|
|
3002
|
+
backoffMs: 1e3,
|
|
3003
|
+
condition: (error) => ["network_error", "timeout_error"].includes(error.type),
|
|
3004
|
+
recover: async (error, context) => {
|
|
3005
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
3006
|
+
return { retryRecommended: true, backoffMs: 2e3 };
|
|
3007
|
+
}
|
|
3008
|
+
},
|
|
3009
|
+
// 限流错误恢复
|
|
3010
|
+
{
|
|
3011
|
+
id: "rate-limit-backoff",
|
|
3012
|
+
name: "Rate Limit Backoff",
|
|
3013
|
+
description: "Exponential backoff for rate limit errors",
|
|
3014
|
+
supportedErrors: ["rate_limit_error"],
|
|
3015
|
+
maxRetries: 5,
|
|
3016
|
+
backoffMs: 5e3,
|
|
3017
|
+
condition: (error) => error.type === "rate_limit_error",
|
|
3018
|
+
recover: async (error, context) => {
|
|
3019
|
+
const backoffTime = Math.min(3e4, 5e3 * Math.pow(2, context.attemptCount));
|
|
3020
|
+
await new Promise((resolve) => setTimeout(resolve, backoffTime));
|
|
3021
|
+
return { retryRecommended: true, backoffMs: backoffTime };
|
|
3022
|
+
}
|
|
3023
|
+
},
|
|
3024
|
+
// 验证错误恢复
|
|
3025
|
+
{
|
|
3026
|
+
id: "validation-healing",
|
|
3027
|
+
name: "Validation Error Healing",
|
|
3028
|
+
description: "Attempt to fix validation errors automatically",
|
|
3029
|
+
supportedErrors: ["validation_error"],
|
|
3030
|
+
maxRetries: 2,
|
|
3031
|
+
backoffMs: 0,
|
|
3032
|
+
condition: (error) => error.type === "validation_error",
|
|
3033
|
+
recover: async (error, context) => {
|
|
3034
|
+
const { protocolHealer: protocolHealer3 } = await Promise.resolve().then(() => (init_protocol_healer(), protocol_healer_exports));
|
|
3035
|
+
if (context.direction === "a2o") {
|
|
3036
|
+
const healResult = await protocolHealer3.healA2ORequest(context.originalData);
|
|
3037
|
+
if (healResult.success) {
|
|
3038
|
+
return {
|
|
3039
|
+
retryRecommended: true,
|
|
3040
|
+
healedData: healResult.data,
|
|
3041
|
+
appliedFixes: healResult.appliedFixes
|
|
3042
|
+
};
|
|
3043
|
+
}
|
|
3044
|
+
} else if (context.direction === "o2a") {
|
|
3045
|
+
const healResult = await protocolHealer3.healO2ARequest(context.originalData);
|
|
3046
|
+
if (healResult.success) {
|
|
3047
|
+
return {
|
|
3048
|
+
retryRecommended: true,
|
|
3049
|
+
healedData: healResult.data,
|
|
3050
|
+
appliedFixes: healResult.appliedFixes
|
|
3051
|
+
};
|
|
3089
3052
|
}
|
|
3090
|
-
}
|
|
3091
|
-
|
|
3092
|
-
|
|
3053
|
+
}
|
|
3054
|
+
return { retryRecommended: false, reason: "Healing failed" };
|
|
3055
|
+
}
|
|
3093
3056
|
},
|
|
3094
|
-
//
|
|
3057
|
+
// 模型不可用恢复
|
|
3095
3058
|
{
|
|
3096
|
-
id: "
|
|
3097
|
-
name: "
|
|
3098
|
-
description: "
|
|
3099
|
-
|
|
3100
|
-
|
|
3059
|
+
id: "model-fallback",
|
|
3060
|
+
name: "Model Fallback",
|
|
3061
|
+
description: "Fallback to alternative models when primary model is unavailable",
|
|
3062
|
+
supportedErrors: ["server_error", "client_error"],
|
|
3063
|
+
maxRetries: 2,
|
|
3064
|
+
backoffMs: 0,
|
|
3065
|
+
condition: (error) => {
|
|
3066
|
+
return error.message.toLowerCase().includes("model") && ["server_error", "client_error"].includes(error.type);
|
|
3101
3067
|
},
|
|
3102
|
-
|
|
3103
|
-
const
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3068
|
+
recover: async (error, context) => {
|
|
3069
|
+
const data = context.originalData;
|
|
3070
|
+
if (!data?.model) {
|
|
3071
|
+
return { retryRecommended: false, reason: "No model specified" };
|
|
3072
|
+
}
|
|
3073
|
+
const fallbackMappings = {
|
|
3074
|
+
"gpt-4": ["gpt-4-turbo", "gpt-3.5-turbo"],
|
|
3075
|
+
"gpt-4-turbo": ["gpt-3.5-turbo", "gpt-3.5-turbo-16k"],
|
|
3076
|
+
"claude-3-opus-20240229": ["claude-3-sonnet-20240229", "claude-3-haiku-20240307"],
|
|
3077
|
+
"claude-3-5-sonnet-20241022": ["claude-3-sonnet-20240229", "claude-3-haiku-20240307"]
|
|
3078
|
+
};
|
|
3079
|
+
const fallbacks = fallbackMappings[data.model];
|
|
3080
|
+
if (!fallbacks || context.attemptCount >= fallbacks.length) {
|
|
3081
|
+
return { retryRecommended: false, reason: "No more fallback models" };
|
|
3116
3082
|
}
|
|
3083
|
+
const fallbackModel = fallbacks[context.attemptCount];
|
|
3117
3084
|
return {
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3085
|
+
retryRecommended: true,
|
|
3086
|
+
healedData: { ...data, model: fallbackModel },
|
|
3087
|
+
appliedFixes: [`Model fallback: ${data.model} \u2192 ${fallbackModel}`]
|
|
3121
3088
|
};
|
|
3122
|
-
}
|
|
3123
|
-
priority: 7
|
|
3089
|
+
}
|
|
3124
3090
|
},
|
|
3125
|
-
//
|
|
3091
|
+
// 认证错误恢复
|
|
3126
3092
|
{
|
|
3127
|
-
id: "
|
|
3128
|
-
name: "
|
|
3129
|
-
description: "
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
const content = [];
|
|
3136
|
-
if (message.content) {
|
|
3137
|
-
content.push({
|
|
3138
|
-
type: "text",
|
|
3139
|
-
text: message.content
|
|
3140
|
-
});
|
|
3141
|
-
}
|
|
3142
|
-
if (message.tool_calls) {
|
|
3143
|
-
message.tool_calls.forEach((toolCall) => {
|
|
3144
|
-
content.push({
|
|
3145
|
-
type: "tool_use",
|
|
3146
|
-
id: toolCall.id,
|
|
3147
|
-
name: toolCall.function.name,
|
|
3148
|
-
input: JSON.parse(toolCall.function.arguments || "{}")
|
|
3149
|
-
});
|
|
3150
|
-
});
|
|
3151
|
-
}
|
|
3093
|
+
id: "auth-refresh",
|
|
3094
|
+
name: "Authentication Refresh",
|
|
3095
|
+
description: "Attempt to refresh authentication",
|
|
3096
|
+
supportedErrors: ["auth_error"],
|
|
3097
|
+
maxRetries: 1,
|
|
3098
|
+
backoffMs: 0,
|
|
3099
|
+
condition: (error) => error.type === "auth_error",
|
|
3100
|
+
recover: async (error, context) => {
|
|
3152
3101
|
return {
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
role: "assistant",
|
|
3156
|
-
model: data.model,
|
|
3157
|
-
content,
|
|
3158
|
-
stop_reason: data.stop_reason || "end_turn",
|
|
3159
|
-
stop_sequence: null,
|
|
3160
|
-
usage: data.usage
|
|
3102
|
+
retryRecommended: false,
|
|
3103
|
+
reason: "Authentication refresh not implemented - check API keys"
|
|
3161
3104
|
};
|
|
3162
|
-
}
|
|
3163
|
-
priority: 9
|
|
3105
|
+
}
|
|
3164
3106
|
}
|
|
3165
3107
|
];
|
|
3166
|
-
var
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3108
|
+
var ErrorRecovery = class {
|
|
3109
|
+
constructor(customStrategies = [], maxGlobalRetries = 5) {
|
|
3110
|
+
this.strategies = [...RECOVERY_STRATEGIES, ...customStrategies];
|
|
3111
|
+
this.maxGlobalRetries = maxGlobalRetries;
|
|
3112
|
+
}
|
|
3113
|
+
/**
|
|
3114
|
+
* 尝试从错误中恢复
|
|
3115
|
+
*/
|
|
3116
|
+
async attemptRecovery(error, context) {
|
|
3117
|
+
const startTime = Date.now();
|
|
3118
|
+
const errorInfo = ErrorDetector.analyzeError(error, context);
|
|
3119
|
+
const messages = [];
|
|
3120
|
+
messages.push(`Analyzing error: ${errorInfo.type} - ${errorInfo.message}`);
|
|
3121
|
+
const applicableStrategies = this.strategies.filter(
|
|
3122
|
+
(strategy) => strategy.condition(errorInfo, context)
|
|
3123
|
+
);
|
|
3124
|
+
if (applicableStrategies.length === 0) {
|
|
3125
|
+
return {
|
|
3126
|
+
success: false,
|
|
3127
|
+
error: errorInfo,
|
|
3128
|
+
attemptCount: context.attemptCount,
|
|
3129
|
+
totalTime: Date.now() - startTime,
|
|
3130
|
+
messages: [...messages, "No applicable recovery strategies found"]
|
|
3131
|
+
};
|
|
3132
|
+
}
|
|
3133
|
+
for (const strategy of applicableStrategies) {
|
|
3134
|
+
if (context.attemptCount >= strategy.maxRetries) {
|
|
3135
|
+
messages.push(`Strategy ${strategy.name} exceeded max retries (${strategy.maxRetries})`);
|
|
3136
|
+
continue;
|
|
3137
|
+
}
|
|
3138
|
+
if (context.attemptCount >= this.maxGlobalRetries) {
|
|
3139
|
+
messages.push(`Global retry limit exceeded (${this.maxGlobalRetries})`);
|
|
3140
|
+
break;
|
|
3178
3141
|
}
|
|
3179
3142
|
try {
|
|
3180
|
-
|
|
3181
|
-
|
|
3143
|
+
messages.push(`Attempting recovery with strategy: ${strategy.name}`);
|
|
3144
|
+
const recoveryResult = await strategy.recover(errorInfo, context);
|
|
3145
|
+
if (recoveryResult.retryRecommended) {
|
|
3146
|
+
return {
|
|
3147
|
+
success: true,
|
|
3148
|
+
data: recoveryResult.healedData || context.originalData,
|
|
3149
|
+
strategy: strategy.name,
|
|
3150
|
+
attemptCount: context.attemptCount + 1,
|
|
3151
|
+
totalTime: Date.now() - startTime,
|
|
3152
|
+
messages: [
|
|
3153
|
+
...messages,
|
|
3154
|
+
`Recovery successful with ${strategy.name}`,
|
|
3155
|
+
...recoveryResult.appliedFixes || []
|
|
3156
|
+
]
|
|
3157
|
+
};
|
|
3158
|
+
} else {
|
|
3159
|
+
messages.push(`Strategy ${strategy.name} declined to retry: ${recoveryResult.reason || "Unknown reason"}`);
|
|
3160
|
+
}
|
|
3161
|
+
} catch (strategyError) {
|
|
3162
|
+
messages.push(`Strategy ${strategy.name} failed: ${strategyError.message}`);
|
|
3163
|
+
}
|
|
3164
|
+
}
|
|
3165
|
+
return {
|
|
3166
|
+
success: false,
|
|
3167
|
+
error: errorInfo,
|
|
3168
|
+
attemptCount: context.attemptCount,
|
|
3169
|
+
totalTime: Date.now() - startTime,
|
|
3170
|
+
messages: [...messages, "All recovery strategies failed"]
|
|
3171
|
+
};
|
|
3172
|
+
}
|
|
3173
|
+
/**
|
|
3174
|
+
* 检查错误是否可恢复
|
|
3175
|
+
*/
|
|
3176
|
+
isRecoverable(error, context) {
|
|
3177
|
+
const errorInfo = ErrorDetector.analyzeError(error, context);
|
|
3178
|
+
const hasApplicableStrategy = this.strategies.some(
|
|
3179
|
+
(strategy) => strategy.condition(errorInfo, context) && context.attemptCount < strategy.maxRetries
|
|
3180
|
+
);
|
|
3181
|
+
const withinGlobalLimit = context.attemptCount < this.maxGlobalRetries;
|
|
3182
|
+
return hasApplicableStrategy && withinGlobalLimit;
|
|
3183
|
+
}
|
|
3184
|
+
/**
|
|
3185
|
+
* 获取错误的恢复建议
|
|
3186
|
+
*/
|
|
3187
|
+
getRecoveryRecommendations(error, context) {
|
|
3188
|
+
const errorInfo = ErrorDetector.analyzeError(error, context);
|
|
3189
|
+
const applicableStrategies = this.strategies.filter(
|
|
3190
|
+
(strategy) => strategy.condition(errorInfo, context)
|
|
3191
|
+
);
|
|
3192
|
+
const estimatedTime = applicableStrategies.reduce(
|
|
3193
|
+
(total, strategy) => total + strategy.backoffMs,
|
|
3194
|
+
0
|
|
3195
|
+
);
|
|
3196
|
+
let confidence = 0;
|
|
3197
|
+
if (applicableStrategies.length > 0) {
|
|
3198
|
+
const severityWeight = {
|
|
3199
|
+
low: 0.9,
|
|
3200
|
+
medium: 0.7,
|
|
3201
|
+
high: 0.5,
|
|
3202
|
+
critical: 0.2
|
|
3203
|
+
}[errorInfo.severity];
|
|
3204
|
+
const strategyWeight = Math.min(1, applicableStrategies.length / 3);
|
|
3205
|
+
confidence = severityWeight * strategyWeight;
|
|
3206
|
+
}
|
|
3207
|
+
return {
|
|
3208
|
+
isRecoverable: this.isRecoverable(error, context),
|
|
3209
|
+
strategies: applicableStrategies.map((s) => s.name),
|
|
3210
|
+
estimatedTime,
|
|
3211
|
+
confidence
|
|
3212
|
+
};
|
|
3213
|
+
}
|
|
3214
|
+
/**
|
|
3215
|
+
* 添加自定义恢复策略
|
|
3216
|
+
*/
|
|
3217
|
+
addStrategy(strategy) {
|
|
3218
|
+
this.strategies.push(strategy);
|
|
3219
|
+
}
|
|
3220
|
+
/**
|
|
3221
|
+
* 移除恢复策略
|
|
3222
|
+
*/
|
|
3223
|
+
removeStrategy(strategyId) {
|
|
3224
|
+
const index = this.strategies.findIndex((s) => s.id === strategyId);
|
|
3225
|
+
if (index >= 0) {
|
|
3226
|
+
this.strategies.splice(index, 1);
|
|
3227
|
+
return true;
|
|
3228
|
+
}
|
|
3229
|
+
return false;
|
|
3230
|
+
}
|
|
3231
|
+
/**
|
|
3232
|
+
* 获取所有策略信息
|
|
3233
|
+
*/
|
|
3234
|
+
getStrategies() {
|
|
3235
|
+
return this.strategies.map((strategy) => ({
|
|
3236
|
+
id: strategy.id,
|
|
3237
|
+
name: strategy.name,
|
|
3238
|
+
description: strategy.description,
|
|
3239
|
+
supportedErrors: strategy.supportedErrors,
|
|
3240
|
+
maxRetries: strategy.maxRetries,
|
|
3241
|
+
backoffMs: strategy.backoffMs
|
|
3242
|
+
}));
|
|
3243
|
+
}
|
|
3244
|
+
};
|
|
3245
|
+
var errorRecovery = new ErrorRecovery();
|
|
3246
|
+
var attemptRecovery = (error, context) => errorRecovery.attemptRecovery(error, context);
|
|
3247
|
+
var isRecoverable = (error, context) => errorRecovery.isRecoverable(error, context);
|
|
3248
|
+
var getRecoveryRecommendations = (error, context) => errorRecovery.getRecoveryRecommendations(error, context);
|
|
3182
3249
|
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
},
|
|
3188
|
-
priority: 8
|
|
3189
|
-
},
|
|
3190
|
-
// 事件类型修复
|
|
3191
|
-
{
|
|
3192
|
-
id: "fix-stream-event-type",
|
|
3193
|
-
name: "Fix Stream Event Type",
|
|
3194
|
-
description: "Add missing event type to stream events",
|
|
3195
|
-
condition: (data, ctx) => {
|
|
3196
|
-
return ctx.stage === "stream" && typeof data === "object" && !data?.type;
|
|
3197
|
-
},
|
|
3198
|
-
fix: (data, ctx) => {
|
|
3199
|
-
if (data.choices) {
|
|
3200
|
-
return { ...data, object: "chat.completion.chunk" };
|
|
3201
|
-
} else if (data.delta) {
|
|
3202
|
-
return { ...data, type: "content_block_delta" };
|
|
3203
|
-
} else {
|
|
3204
|
-
return { ...data, type: "message_start" };
|
|
3205
|
-
}
|
|
3206
|
-
},
|
|
3207
|
-
priority: 6
|
|
3250
|
+
// src/core/a2o-request-adapter/adapter.ts
|
|
3251
|
+
var A2ORequestAdapter = class {
|
|
3252
|
+
constructor(config = {}) {
|
|
3253
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
3208
3254
|
}
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3255
|
+
/**
|
|
3256
|
+
* 转换Anthropic请求格式为OpenAI兼容格式 - 增强版
|
|
3257
|
+
* 集成校验、修复和错误恢复功能
|
|
3258
|
+
*/
|
|
3259
|
+
async convertAnthropicRequestToOpenAIEnhanced(anthropicRequest) {
|
|
3260
|
+
const startTime = Date.now();
|
|
3261
|
+
const result = {
|
|
3262
|
+
success: false,
|
|
3263
|
+
originalData: anthropicRequest,
|
|
3264
|
+
warnings: [],
|
|
3265
|
+
errors: [],
|
|
3266
|
+
healingApplied: false,
|
|
3267
|
+
appliedFixes: []
|
|
3268
|
+
};
|
|
3269
|
+
try {
|
|
3270
|
+
let validatedInput;
|
|
3271
|
+
if (this.config.validation.enabled) {
|
|
3272
|
+
if (this.config.validation.strict) {
|
|
3273
|
+
try {
|
|
3274
|
+
validatedInput = validateAnthropicRequest(anthropicRequest);
|
|
3275
|
+
result.validationResult = {
|
|
3276
|
+
inputValid: true,
|
|
3277
|
+
outputValid: false,
|
|
3278
|
+
issues: []
|
|
3279
|
+
};
|
|
3280
|
+
} catch (error) {
|
|
3281
|
+
result.errors.push(`Input validation failed: ${error.message}`);
|
|
3282
|
+
return result;
|
|
3283
|
+
}
|
|
3284
|
+
} else {
|
|
3285
|
+
if (this.config.healing.enabled) {
|
|
3286
|
+
const healingResult = await healA2ORequest(anthropicRequest, this.config.healing.maxAttempts);
|
|
3287
|
+
if (healingResult.success) {
|
|
3288
|
+
validatedInput = healingResult.data;
|
|
3289
|
+
result.healingApplied = true;
|
|
3290
|
+
result.appliedFixes = healingResult.appliedFixes;
|
|
3291
|
+
result.warnings.push(...healingResult.warnings);
|
|
3292
|
+
} else {
|
|
3293
|
+
result.errors.push(...healingResult.errors);
|
|
3294
|
+
return result;
|
|
3295
|
+
}
|
|
3296
|
+
} else {
|
|
3297
|
+
try {
|
|
3298
|
+
validatedInput = validateAnthropicRequest(anthropicRequest);
|
|
3299
|
+
} catch {
|
|
3300
|
+
validatedInput = anthropicRequest;
|
|
3301
|
+
result.warnings.push("Input validation skipped due to errors");
|
|
3302
|
+
}
|
|
3303
|
+
}
|
|
3304
|
+
}
|
|
3305
|
+
} else {
|
|
3306
|
+
validatedInput = anthropicRequest;
|
|
3240
3307
|
}
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3308
|
+
const openaiRequest = await this.performCoreConversion(validatedInput);
|
|
3309
|
+
if (this.config.validation.enabled) {
|
|
3310
|
+
try {
|
|
3311
|
+
const validatedOutput = validateOpenAIRequest(openaiRequest);
|
|
3312
|
+
result.data = validatedOutput;
|
|
3313
|
+
result.validationResult = {
|
|
3314
|
+
inputValid: true,
|
|
3315
|
+
outputValid: true,
|
|
3316
|
+
issues: []
|
|
3317
|
+
};
|
|
3318
|
+
} catch (error) {
|
|
3319
|
+
if (this.config.validation.strict) {
|
|
3320
|
+
result.errors.push(`Output validation failed: ${error.message}`);
|
|
3321
|
+
return result;
|
|
3322
|
+
} else {
|
|
3323
|
+
result.warnings.push(`Output validation warning: ${error.message}`);
|
|
3324
|
+
result.data = openaiRequest;
|
|
3248
3325
|
}
|
|
3249
|
-
seen.add(value);
|
|
3250
3326
|
}
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
return cleanData;
|
|
3254
|
-
},
|
|
3255
|
-
priority: 10
|
|
3256
|
-
},
|
|
3257
|
-
// 数据类型修复
|
|
3258
|
-
{
|
|
3259
|
-
id: "emergency-wrong-type",
|
|
3260
|
-
name: "Emergency Wrong Type Fix",
|
|
3261
|
-
description: "Fix fundamental type errors",
|
|
3262
|
-
condition: (data) => {
|
|
3263
|
-
return typeof data === "string" && (data.startsWith("{") || data.startsWith("["));
|
|
3264
|
-
},
|
|
3265
|
-
fix: (data) => {
|
|
3266
|
-
try {
|
|
3267
|
-
return JSON.parse(data);
|
|
3268
|
-
} catch {
|
|
3269
|
-
return { error: "Failed to parse JSON string", original: data };
|
|
3327
|
+
} else {
|
|
3328
|
+
result.data = openaiRequest;
|
|
3270
3329
|
}
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
]
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
}
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
const testContext = {
|
|
3289
|
-
direction: context.direction || "a2o",
|
|
3290
|
-
stage: context.stage || "request",
|
|
3291
|
-
originalData: testData,
|
|
3292
|
-
attemptCount: 0,
|
|
3293
|
-
maxAttempts: 3
|
|
3294
|
-
};
|
|
3295
|
-
return true;
|
|
3296
|
-
} catch {
|
|
3297
|
-
return false;
|
|
3330
|
+
if (this.config.monitoring.enabled) {
|
|
3331
|
+
const processingTime = Date.now() - startTime;
|
|
3332
|
+
if (this.config.monitoring.logLevel !== "none") {
|
|
3333
|
+
console.log(`[A2O Adapter] Conversion completed in ${processingTime}ms`, {
|
|
3334
|
+
healingApplied: result.healingApplied,
|
|
3335
|
+
fixesCount: result.appliedFixes?.length || 0
|
|
3336
|
+
});
|
|
3337
|
+
}
|
|
3338
|
+
}
|
|
3339
|
+
result.success = true;
|
|
3340
|
+
return result;
|
|
3341
|
+
} catch (error) {
|
|
3342
|
+
result.errors.push(`Conversion failed: ${error.message}`);
|
|
3343
|
+
if (this.config.recovery.enabled) {
|
|
3344
|
+
result.warnings.push("Error recovery attempted but not implemented yet");
|
|
3345
|
+
}
|
|
3346
|
+
return result;
|
|
3298
3347
|
}
|
|
3299
|
-
}
|
|
3300
|
-
}
|
|
3301
|
-
|
|
3302
|
-
// src/core/healing/error-recovery.ts
|
|
3303
|
-
var ErrorDetector = class {
|
|
3348
|
+
}
|
|
3304
3349
|
/**
|
|
3305
|
-
*
|
|
3350
|
+
* 执行核心转换逻辑(原有逻辑保持不变)
|
|
3306
3351
|
*/
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
};
|
|
3318
|
-
}
|
|
3319
|
-
if (typeof error === "object" && error && "status" in error) {
|
|
3320
|
-
const httpError = error;
|
|
3321
|
-
return {
|
|
3322
|
-
type: this.classifyHttpError(httpError.status),
|
|
3323
|
-
severity: this.assessHttpSeverity(httpError.status),
|
|
3324
|
-
message: httpError.message || `HTTP ${httpError.status}`,
|
|
3325
|
-
code: httpError.status,
|
|
3326
|
-
context,
|
|
3327
|
-
timestamp
|
|
3328
|
-
};
|
|
3329
|
-
}
|
|
3330
|
-
if (typeof error === "string") {
|
|
3331
|
-
return {
|
|
3332
|
-
type: this.classifyErrorMessage(error),
|
|
3333
|
-
severity: this.assessMessageSeverity(error),
|
|
3334
|
-
message: error,
|
|
3335
|
-
context,
|
|
3336
|
-
timestamp
|
|
3337
|
-
};
|
|
3338
|
-
}
|
|
3339
|
-
return {
|
|
3340
|
-
type: "unknown_error",
|
|
3341
|
-
severity: "medium",
|
|
3342
|
-
message: JSON.stringify(error),
|
|
3343
|
-
context,
|
|
3344
|
-
timestamp
|
|
3352
|
+
async performCoreConversion(anthropicRequest) {
|
|
3353
|
+
if (this.config.enableFormatValidation) {
|
|
3354
|
+
FormatValidator.validateClaudeRequest(anthropicRequest);
|
|
3355
|
+
}
|
|
3356
|
+
const openaiRequest = {
|
|
3357
|
+
model: anthropicRequest.model,
|
|
3358
|
+
messages: MessageConverter.convertMessages(anthropicRequest.messages, anthropicRequest.system),
|
|
3359
|
+
max_tokens: anthropicRequest.max_tokens,
|
|
3360
|
+
temperature: anthropicRequest.temperature,
|
|
3361
|
+
stream: anthropicRequest.stream
|
|
3345
3362
|
};
|
|
3363
|
+
if (anthropicRequest.tools && anthropicRequest.tools.length > 0) {
|
|
3364
|
+
openaiRequest.tools = this.convertToolDefinitions(anthropicRequest.tools);
|
|
3365
|
+
}
|
|
3366
|
+
const specialFields = ["_anthropic_protocol", "_rovo_tool_injected", "_routeResult"];
|
|
3367
|
+
for (const field of specialFields) {
|
|
3368
|
+
if (anthropicRequest[field] !== void 0) {
|
|
3369
|
+
openaiRequest[field] = anthropicRequest[field];
|
|
3370
|
+
}
|
|
3371
|
+
}
|
|
3372
|
+
return openaiRequest;
|
|
3346
3373
|
}
|
|
3347
3374
|
/**
|
|
3348
|
-
*
|
|
3375
|
+
* 转换Anthropic请求格式为OpenAI兼容格式 - 原有方法保持兼容
|
|
3349
3376
|
*/
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
return "validation_error";
|
|
3354
|
-
}
|
|
3355
|
-
if (message.includes("timeout")) {
|
|
3356
|
-
return "timeout_error";
|
|
3377
|
+
convertAnthropicRequestToOpenAI(anthropicRequest) {
|
|
3378
|
+
if (this.config.enableFormatValidation) {
|
|
3379
|
+
FormatValidator.validateClaudeRequest(anthropicRequest);
|
|
3357
3380
|
}
|
|
3358
|
-
|
|
3359
|
-
|
|
3381
|
+
const openaiRequest = {
|
|
3382
|
+
model: anthropicRequest.model,
|
|
3383
|
+
messages: MessageConverter.convertMessages(anthropicRequest.messages, anthropicRequest.system),
|
|
3384
|
+
max_tokens: anthropicRequest.max_tokens,
|
|
3385
|
+
temperature: anthropicRequest.temperature,
|
|
3386
|
+
stream: anthropicRequest.stream,
|
|
3387
|
+
n: 1
|
|
3388
|
+
};
|
|
3389
|
+
if (anthropicRequest.tools && anthropicRequest.tools.length > 0) {
|
|
3390
|
+
openaiRequest.tools = this.convertToolDefinitions(anthropicRequest.tools);
|
|
3360
3391
|
}
|
|
3361
|
-
|
|
3362
|
-
|
|
3392
|
+
const specialFields = ["_anthropic_protocol", "_rovo_tool_injected", "_routeResult"];
|
|
3393
|
+
for (const field of specialFields) {
|
|
3394
|
+
if (anthropicRequest[field] !== void 0) {
|
|
3395
|
+
openaiRequest[field] = anthropicRequest[field];
|
|
3396
|
+
}
|
|
3363
3397
|
}
|
|
3364
|
-
if (
|
|
3365
|
-
|
|
3398
|
+
if (this.config.enableFormatValidation && !FormatValidator.validateOpenAIRequest(openaiRequest)) {
|
|
3399
|
+
throw new Error("Generated OpenAI request format is invalid");
|
|
3366
3400
|
}
|
|
3367
|
-
|
|
3368
|
-
|
|
3401
|
+
return openaiRequest;
|
|
3402
|
+
}
|
|
3403
|
+
/**
|
|
3404
|
+
* 转换OpenAI响应格式为Claude兼容格式
|
|
3405
|
+
*/
|
|
3406
|
+
convertOpenAIResponseToClaude(openaiResponse) {
|
|
3407
|
+
const claudeContent = [];
|
|
3408
|
+
const message = openaiResponse.choices?.[0]?.message;
|
|
3409
|
+
if (message?.content) {
|
|
3410
|
+
claudeContent.push({
|
|
3411
|
+
type: "text",
|
|
3412
|
+
text: message.content
|
|
3413
|
+
});
|
|
3369
3414
|
}
|
|
3370
|
-
if (message
|
|
3371
|
-
|
|
3415
|
+
if (message?.tool_calls) {
|
|
3416
|
+
const toolUseContents = ToolConverter.convertOpenAIToolCallsToClaude(message.tool_calls);
|
|
3417
|
+
claudeContent.push(...toolUseContents);
|
|
3372
3418
|
}
|
|
3373
|
-
|
|
3419
|
+
const claudeResponse = {
|
|
3420
|
+
role: "assistant",
|
|
3421
|
+
content: claudeContent
|
|
3422
|
+
};
|
|
3423
|
+
return claudeResponse;
|
|
3374
3424
|
}
|
|
3375
3425
|
/**
|
|
3376
|
-
*
|
|
3426
|
+
* 转换工具定义列表
|
|
3377
3427
|
*/
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3428
|
+
convertToolDefinitions(tools) {
|
|
3429
|
+
return tools.map((tool) => {
|
|
3430
|
+
if (ToolConverter.isOpenAIToolFormat(tool)) {
|
|
3431
|
+
return tool;
|
|
3432
|
+
} else {
|
|
3433
|
+
return ToolConverter.convertAnthropicToolToOpenAI(tool);
|
|
3434
|
+
}
|
|
3435
|
+
});
|
|
3385
3436
|
}
|
|
3386
3437
|
/**
|
|
3387
|
-
*
|
|
3438
|
+
* 验证Claude请求格式
|
|
3388
3439
|
*/
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
if (message.includes("critical") || message.includes("fatal")) return "critical";
|
|
3392
|
-
if (message.includes("timeout") || message.includes("network")) return "high";
|
|
3393
|
-
if (message.includes("validation") || message.includes("auth")) return "medium";
|
|
3394
|
-
return "low";
|
|
3440
|
+
validateClaudeRequest(request) {
|
|
3441
|
+
return FormatValidator.validateClaudeRequest(request);
|
|
3395
3442
|
}
|
|
3396
3443
|
/**
|
|
3397
|
-
*
|
|
3444
|
+
* 验证OpenAI请求格式
|
|
3398
3445
|
*/
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
if (status === 429 || status === 408 || status === 504) return "high";
|
|
3402
|
-
if (status === 401 || status === 403) return "medium";
|
|
3403
|
-
return "low";
|
|
3446
|
+
validateOpenAIRequest(request) {
|
|
3447
|
+
return FormatValidator.validateOpenAIRequest(request);
|
|
3404
3448
|
}
|
|
3405
3449
|
/**
|
|
3406
|
-
*
|
|
3450
|
+
* 获取支持的工具列表
|
|
3407
3451
|
*/
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
if (lowerMessage.includes("validation")) return "validation_error";
|
|
3411
|
-
if (lowerMessage.includes("timeout")) return "timeout_error";
|
|
3412
|
-
if (lowerMessage.includes("network")) return "network_error";
|
|
3413
|
-
if (lowerMessage.includes("auth")) return "auth_error";
|
|
3414
|
-
if (lowerMessage.includes("rate")) return "rate_limit_error";
|
|
3415
|
-
if (lowerMessage.includes("quota")) return "quota_error";
|
|
3416
|
-
return "unknown_error";
|
|
3452
|
+
getSupportedTools() {
|
|
3453
|
+
return [];
|
|
3417
3454
|
}
|
|
3418
3455
|
/**
|
|
3419
|
-
*
|
|
3456
|
+
* 检查工具是否支持
|
|
3420
3457
|
*/
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
if (lowerMessage.includes("critical") || lowerMessage.includes("fatal")) return "critical";
|
|
3424
|
-
if (lowerMessage.includes("error")) return "high";
|
|
3425
|
-
if (lowerMessage.includes("warning")) return "medium";
|
|
3426
|
-
return "low";
|
|
3458
|
+
isToolSupported(_toolName) {
|
|
3459
|
+
return true;
|
|
3427
3460
|
}
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
{
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
id: "validation-healing",
|
|
3462
|
-
name: "Validation Error Healing",
|
|
3463
|
-
description: "Attempt to fix validation errors automatically",
|
|
3464
|
-
supportedErrors: ["validation_error"],
|
|
3465
|
-
maxRetries: 2,
|
|
3466
|
-
backoffMs: 0,
|
|
3467
|
-
condition: (error) => error.type === "validation_error",
|
|
3468
|
-
recover: async (error, context) => {
|
|
3469
|
-
const { protocolHealer: protocolHealer3 } = await Promise.resolve().then(() => (init_protocol_healer(), protocol_healer_exports));
|
|
3470
|
-
if (context.direction === "a2o") {
|
|
3471
|
-
const healResult = await protocolHealer3.healA2ORequest(context.originalData);
|
|
3472
|
-
if (healResult.success) {
|
|
3473
|
-
return {
|
|
3474
|
-
retryRecommended: true,
|
|
3475
|
-
healedData: healResult.data,
|
|
3476
|
-
appliedFixes: healResult.appliedFixes
|
|
3477
|
-
};
|
|
3478
|
-
}
|
|
3479
|
-
} else if (context.direction === "o2a") {
|
|
3480
|
-
const healResult = await protocolHealer3.healO2ARequest(context.originalData);
|
|
3481
|
-
if (healResult.success) {
|
|
3482
|
-
return {
|
|
3483
|
-
retryRecommended: true,
|
|
3484
|
-
healedData: healResult.data,
|
|
3485
|
-
appliedFixes: healResult.appliedFixes
|
|
3486
|
-
};
|
|
3487
|
-
}
|
|
3488
|
-
}
|
|
3489
|
-
return { retryRecommended: false, reason: "Healing failed" };
|
|
3490
|
-
}
|
|
3491
|
-
},
|
|
3492
|
-
// 模型不可用恢复
|
|
3493
|
-
{
|
|
3494
|
-
id: "model-fallback",
|
|
3495
|
-
name: "Model Fallback",
|
|
3496
|
-
description: "Fallback to alternative models when primary model is unavailable",
|
|
3497
|
-
supportedErrors: ["server_error", "client_error"],
|
|
3498
|
-
maxRetries: 2,
|
|
3499
|
-
backoffMs: 0,
|
|
3500
|
-
condition: (error) => {
|
|
3501
|
-
return error.message.toLowerCase().includes("model") && ["server_error", "client_error"].includes(error.type);
|
|
3502
|
-
},
|
|
3503
|
-
recover: async (error, context) => {
|
|
3504
|
-
const data = context.originalData;
|
|
3505
|
-
if (!data?.model) {
|
|
3506
|
-
return { retryRecommended: false, reason: "No model specified" };
|
|
3507
|
-
}
|
|
3508
|
-
const fallbackMappings = {
|
|
3509
|
-
"gpt-4": ["gpt-4-turbo", "gpt-3.5-turbo"],
|
|
3510
|
-
"gpt-4-turbo": ["gpt-3.5-turbo", "gpt-3.5-turbo-16k"],
|
|
3511
|
-
"claude-3-opus-20240229": ["claude-3-sonnet-20240229", "claude-3-haiku-20240307"],
|
|
3512
|
-
"claude-3-5-sonnet-20241022": ["claude-3-sonnet-20240229", "claude-3-haiku-20240307"]
|
|
3513
|
-
};
|
|
3514
|
-
const fallbacks = fallbackMappings[data.model];
|
|
3515
|
-
if (!fallbacks || context.attemptCount >= fallbacks.length) {
|
|
3516
|
-
return { retryRecommended: false, reason: "No more fallback models" };
|
|
3461
|
+
/**
|
|
3462
|
+
* 获取工具映射(已弃用,保持兼容性)
|
|
3463
|
+
*/
|
|
3464
|
+
getToolMapping(claudeToolName) {
|
|
3465
|
+
return claudeToolName;
|
|
3466
|
+
}
|
|
3467
|
+
/**
|
|
3468
|
+
* 更新配置
|
|
3469
|
+
*/
|
|
3470
|
+
updateConfig(newConfig) {
|
|
3471
|
+
this.config = { ...this.config, ...newConfig };
|
|
3472
|
+
}
|
|
3473
|
+
/**
|
|
3474
|
+
* 获取当前配置
|
|
3475
|
+
*/
|
|
3476
|
+
getConfig() {
|
|
3477
|
+
return { ...this.config };
|
|
3478
|
+
}
|
|
3479
|
+
/**
|
|
3480
|
+
* 执行带验证的核心转换(同步版本)
|
|
3481
|
+
* 为静态方法提供增强功能,但保持同步特性
|
|
3482
|
+
*/
|
|
3483
|
+
performCoreConversionWithValidation(anthropicRequest) {
|
|
3484
|
+
if (this.config.validation.enabled) {
|
|
3485
|
+
try {
|
|
3486
|
+
validateAnthropicRequest(anthropicRequest);
|
|
3487
|
+
} catch (error) {
|
|
3488
|
+
if (this.config.validation.strict) {
|
|
3489
|
+
throw error;
|
|
3490
|
+
} else {
|
|
3491
|
+
const errorSummary = this.getValidationErrorSummary(error);
|
|
3492
|
+
console.warn(`[A2ORequestAdapter] Input validation warning: ${errorSummary}. Details saved to logs.`);
|
|
3493
|
+
}
|
|
3517
3494
|
}
|
|
3518
|
-
const fallbackModel = fallbacks[context.attemptCount];
|
|
3519
|
-
return {
|
|
3520
|
-
retryRecommended: true,
|
|
3521
|
-
healedData: { ...data, model: fallbackModel },
|
|
3522
|
-
appliedFixes: [`Model fallback: ${data.model} \u2192 ${fallbackModel}`]
|
|
3523
|
-
};
|
|
3524
3495
|
}
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3496
|
+
let processedRequest = anthropicRequest;
|
|
3497
|
+
if (this.config.healing.enabled) {
|
|
3498
|
+
try {
|
|
3499
|
+
processedRequest = this.applySyncHealing(anthropicRequest);
|
|
3500
|
+
} catch (healingError) {
|
|
3501
|
+
console.warn("[A2ORequestAdapter] Healing failed:", healingError);
|
|
3502
|
+
}
|
|
3503
|
+
}
|
|
3504
|
+
const result = this.performBasicConversion(processedRequest, true);
|
|
3505
|
+
if (this.config.validation.enabled) {
|
|
3506
|
+
try {
|
|
3507
|
+
validateOpenAIRequest(result);
|
|
3508
|
+
} catch (error) {
|
|
3509
|
+
if (this.config.validation.strict) {
|
|
3510
|
+
throw error;
|
|
3511
|
+
} else {
|
|
3512
|
+
console.warn("[A2ORequestAdapter] Output validation warning:", error);
|
|
3513
|
+
}
|
|
3514
|
+
}
|
|
3540
3515
|
}
|
|
3516
|
+
return result;
|
|
3541
3517
|
}
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
this.
|
|
3518
|
+
/**
|
|
3519
|
+
* 执行基础转换逻辑(原有逻辑的提取)
|
|
3520
|
+
*/
|
|
3521
|
+
performBasicConversion(anthropicRequest, skipValidation = false) {
|
|
3522
|
+
if (!skipValidation && this.config.enableFormatValidation) {
|
|
3523
|
+
FormatValidator.validateClaudeRequest(anthropicRequest);
|
|
3524
|
+
}
|
|
3525
|
+
const openaiRequest = {
|
|
3526
|
+
model: anthropicRequest.model,
|
|
3527
|
+
messages: MessageConverter.convertMessages(anthropicRequest.messages, anthropicRequest.system),
|
|
3528
|
+
max_tokens: anthropicRequest.max_tokens,
|
|
3529
|
+
temperature: anthropicRequest.temperature,
|
|
3530
|
+
stream: anthropicRequest.stream,
|
|
3531
|
+
n: 1
|
|
3532
|
+
};
|
|
3533
|
+
if (anthropicRequest.tools && anthropicRequest.tools.length > 0) {
|
|
3534
|
+
openaiRequest.tools = this.convertToolDefinitions(anthropicRequest.tools);
|
|
3535
|
+
}
|
|
3536
|
+
const specialFields = ["_anthropic_protocol", "_rovo_tool_injected", "_routeResult"];
|
|
3537
|
+
for (const field of specialFields) {
|
|
3538
|
+
if (anthropicRequest[field] !== void 0) {
|
|
3539
|
+
openaiRequest[field] = anthropicRequest[field];
|
|
3540
|
+
}
|
|
3541
|
+
}
|
|
3542
|
+
if (this.config.enableFormatValidation && !FormatValidator.validateOpenAIRequest(openaiRequest)) {
|
|
3543
|
+
throw new Error("Generated OpenAI request format is invalid");
|
|
3544
|
+
}
|
|
3545
|
+
return openaiRequest;
|
|
3547
3546
|
}
|
|
3548
3547
|
/**
|
|
3549
|
-
*
|
|
3548
|
+
* 应用同步修复逻辑
|
|
3549
|
+
* 简化版的修复,不依赖异步操作
|
|
3550
3550
|
*/
|
|
3551
|
-
|
|
3552
|
-
const
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
messages.push(`Analyzing error: ${errorInfo.type} - ${errorInfo.message}`);
|
|
3556
|
-
const applicableStrategies = this.strategies.filter(
|
|
3557
|
-
(strategy) => strategy.condition(errorInfo, context)
|
|
3558
|
-
);
|
|
3559
|
-
if (applicableStrategies.length === 0) {
|
|
3560
|
-
return {
|
|
3561
|
-
success: false,
|
|
3562
|
-
error: errorInfo,
|
|
3563
|
-
attemptCount: context.attemptCount,
|
|
3564
|
-
totalTime: Date.now() - startTime,
|
|
3565
|
-
messages: [...messages, "No applicable recovery strategies found"]
|
|
3566
|
-
};
|
|
3551
|
+
applySyncHealing(request) {
|
|
3552
|
+
const healedRequest = { ...request };
|
|
3553
|
+
if (!healedRequest.max_tokens || healedRequest.max_tokens <= 0) {
|
|
3554
|
+
healedRequest.max_tokens = 4096;
|
|
3567
3555
|
}
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3556
|
+
if (!healedRequest.messages || !Array.isArray(healedRequest.messages)) {
|
|
3557
|
+
throw new Error("Invalid messages array");
|
|
3558
|
+
}
|
|
3559
|
+
if (!healedRequest.model) {
|
|
3560
|
+
healedRequest.model = "claude-sonnet-4";
|
|
3561
|
+
}
|
|
3562
|
+
for (const message of healedRequest.messages) {
|
|
3563
|
+
if (!message.role) {
|
|
3564
|
+
message.role = "user";
|
|
3572
3565
|
}
|
|
3573
|
-
if (
|
|
3574
|
-
|
|
3575
|
-
break;
|
|
3566
|
+
if (!message.content) {
|
|
3567
|
+
message.content = "";
|
|
3576
3568
|
}
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3569
|
+
}
|
|
3570
|
+
return healedRequest;
|
|
3571
|
+
}
|
|
3572
|
+
/**
|
|
3573
|
+
* 获取验证错误详情
|
|
3574
|
+
*/
|
|
3575
|
+
getValidationErrors(request, type) {
|
|
3576
|
+
return FormatValidator.getValidationErrors(request, type);
|
|
3577
|
+
}
|
|
3578
|
+
/**
|
|
3579
|
+
* 生成简洁的验证错误摘要
|
|
3580
|
+
*/
|
|
3581
|
+
getValidationErrorSummary(error) {
|
|
3582
|
+
if (error?.issues?.length > 0) {
|
|
3583
|
+
const invalidEnums = error.issues.filter((i) => i.code === "invalid_enum_value");
|
|
3584
|
+
const missingFields = error.issues.filter((i) => i.code === "invalid_type");
|
|
3585
|
+
const summary = [];
|
|
3586
|
+
if (invalidEnums.length > 0) {
|
|
3587
|
+
const first = invalidEnums[0];
|
|
3588
|
+
summary.push(`invalid_${first.path?.join(".")}: '${first.received}'`);
|
|
3589
|
+
}
|
|
3590
|
+
if (missingFields.length > 0) {
|
|
3591
|
+
summary.push(`${missingFields.length} missing fields`);
|
|
3598
3592
|
}
|
|
3593
|
+
return summary.slice(0, 2).join(", ") + (error.issues.length > 5 ? ` (+${error.issues.length - 5} more)` : "");
|
|
3599
3594
|
}
|
|
3600
|
-
return
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3595
|
+
return error.message || "Validation failed";
|
|
3596
|
+
}
|
|
3597
|
+
};
|
|
3598
|
+
var A2ORequestAdapterStatic = {
|
|
3599
|
+
/**
|
|
3600
|
+
* 转换Anthropic请求格式为OpenAI兼容格式(静态方法)
|
|
3601
|
+
* 内部使用增强转换器,所有调用点自动获得增强功能
|
|
3602
|
+
*/
|
|
3603
|
+
convertAnthropicRequestToOpenAI: (anthropicRequest) => {
|
|
3604
|
+
const adapter = new A2ORequestAdapter({
|
|
3605
|
+
debugMode: false,
|
|
3606
|
+
maxDescriptionLength: 100,
|
|
3607
|
+
enableToolNameValidation: true,
|
|
3608
|
+
enableFormatValidation: true,
|
|
3609
|
+
validation: { enabled: true, strict: false },
|
|
3610
|
+
healing: { enabled: true, maxAttempts: 2, enableCustomRules: true },
|
|
3611
|
+
recovery: { enabled: false, maxRetries: 0, backoffMs: 1e3 },
|
|
3612
|
+
monitoring: { enabled: false, logLevel: "none", enableMetrics: false }
|
|
3613
|
+
});
|
|
3614
|
+
try {
|
|
3615
|
+
const result = adapter.performCoreConversionWithValidation(anthropicRequest);
|
|
3616
|
+
return result;
|
|
3617
|
+
} catch (error) {
|
|
3618
|
+
console.warn(`[A2ORequestAdapterStatic] Enhanced conversion failed, using basic conversion: ${error?.message || error}`);
|
|
3619
|
+
return adapter.performBasicConversion(anthropicRequest, true);
|
|
3620
|
+
}
|
|
3621
|
+
},
|
|
3622
|
+
/**
|
|
3623
|
+
* 转换OpenAI响应格式为Claude兼容格式(静态方法)
|
|
3624
|
+
* 内部使用增强转换器
|
|
3625
|
+
*/
|
|
3626
|
+
convertOpenAIResponseToClaude: (openaiResponse) => {
|
|
3627
|
+
const adapter = new A2ORequestAdapter({
|
|
3628
|
+
debugMode: false,
|
|
3629
|
+
maxDescriptionLength: 100,
|
|
3630
|
+
enableToolNameValidation: true,
|
|
3631
|
+
enableFormatValidation: true,
|
|
3632
|
+
validation: { enabled: true, strict: false },
|
|
3633
|
+
healing: { enabled: true, maxAttempts: 2, enableCustomRules: true },
|
|
3634
|
+
recovery: { enabled: false, maxRetries: 0, backoffMs: 1e3 },
|
|
3635
|
+
monitoring: { enabled: false, logLevel: "none", enableMetrics: false }
|
|
3636
|
+
});
|
|
3637
|
+
return adapter.convertOpenAIResponseToClaude(openaiResponse);
|
|
3638
|
+
},
|
|
3639
|
+
/**
|
|
3640
|
+
* 验证Claude请求格式(静态方法)
|
|
3641
|
+
*/
|
|
3642
|
+
validateClaudeRequest: (request) => {
|
|
3643
|
+
return FormatValidator.validateClaudeRequest(request);
|
|
3644
|
+
},
|
|
3645
|
+
/**
|
|
3646
|
+
* 验证OpenAI请求格式(静态方法)
|
|
3647
|
+
*/
|
|
3648
|
+
validateOpenAIRequest: (request) => {
|
|
3649
|
+
return FormatValidator.validateOpenAIRequest(request);
|
|
3650
|
+
},
|
|
3651
|
+
/**
|
|
3652
|
+
* 获取支持的工具列表(静态方法)
|
|
3653
|
+
*/
|
|
3654
|
+
getSupportedTools: () => {
|
|
3655
|
+
return [];
|
|
3656
|
+
},
|
|
3657
|
+
/**
|
|
3658
|
+
* 检查工具是否支持(静态方法)
|
|
3659
|
+
*/
|
|
3660
|
+
isToolSupported: (_toolName) => {
|
|
3661
|
+
return true;
|
|
3662
|
+
},
|
|
3663
|
+
/**
|
|
3664
|
+
* 获取工具映射(静态方法,已弃用)
|
|
3665
|
+
*/
|
|
3666
|
+
getToolMapping: (claudeToolName) => {
|
|
3667
|
+
return claudeToolName;
|
|
3668
|
+
}
|
|
3669
|
+
};
|
|
3670
|
+
|
|
3671
|
+
// src/core/streaming/streaming-protocol-adapter.ts
|
|
3672
|
+
var StreamingProtocolAdapter = class {
|
|
3673
|
+
constructor(options = {}) {
|
|
3674
|
+
this.config = {
|
|
3675
|
+
debugMode: options.debugMode ?? false,
|
|
3676
|
+
validateInput: options.validateInput ?? false,
|
|
3677
|
+
validateOutput: options.validateOutput ?? false,
|
|
3678
|
+
autoHeal: options.autoHeal ?? false,
|
|
3679
|
+
timeout: options.timeout ?? 3e4,
|
|
3680
|
+
retries: options.retries ?? 3,
|
|
3681
|
+
bufferSize: options.bufferSize ?? 1024,
|
|
3682
|
+
logger: options.logger ?? getGlobalLogger()
|
|
3606
3683
|
};
|
|
3607
3684
|
}
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
const errorInfo = ErrorDetector.analyzeError(error, context);
|
|
3613
|
-
const hasApplicableStrategy = this.strategies.some(
|
|
3614
|
-
(strategy) => strategy.condition(errorInfo, context) && context.attemptCount < strategy.maxRetries
|
|
3615
|
-
);
|
|
3616
|
-
const withinGlobalLimit = context.attemptCount < this.maxGlobalRetries;
|
|
3617
|
-
return hasApplicableStrategy && withinGlobalLimit;
|
|
3685
|
+
logDebug(message, meta) {
|
|
3686
|
+
if (this.config.debugMode) {
|
|
3687
|
+
this.config.logger.debug(message, meta);
|
|
3688
|
+
}
|
|
3618
3689
|
}
|
|
3619
3690
|
/**
|
|
3620
|
-
*
|
|
3691
|
+
* 转换Anthropic请求为OpenAI格式
|
|
3621
3692
|
*/
|
|
3622
|
-
|
|
3623
|
-
const
|
|
3624
|
-
|
|
3625
|
-
(
|
|
3626
|
-
);
|
|
3627
|
-
const estimatedTime = applicableStrategies.reduce(
|
|
3628
|
-
(total, strategy) => total + strategy.backoffMs,
|
|
3629
|
-
0
|
|
3630
|
-
);
|
|
3631
|
-
let confidence = 0;
|
|
3632
|
-
if (applicableStrategies.length > 0) {
|
|
3633
|
-
const severityWeight = {
|
|
3634
|
-
low: 0.9,
|
|
3635
|
-
medium: 0.7,
|
|
3636
|
-
high: 0.5,
|
|
3637
|
-
critical: 0.2
|
|
3638
|
-
}[errorInfo.severity];
|
|
3639
|
-
const strategyWeight = Math.min(1, applicableStrategies.length / 3);
|
|
3640
|
-
confidence = severityWeight * strategyWeight;
|
|
3693
|
+
convertAnthropicToOpenAI(anthropicRequest) {
|
|
3694
|
+
const logger = this.config.logger;
|
|
3695
|
+
if (this.config.debugMode) {
|
|
3696
|
+
logger.debug("Converting Anthropic request to OpenAI format", { model: anthropicRequest.model });
|
|
3641
3697
|
}
|
|
3698
|
+
const openaiRequest = A2ORequestAdapterStatic.convertAnthropicRequestToOpenAI(anthropicRequest);
|
|
3699
|
+
openaiRequest.stream = true;
|
|
3700
|
+
const hasImages = this.hasImageContent(anthropicRequest);
|
|
3642
3701
|
return {
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3702
|
+
openaiRequest,
|
|
3703
|
+
metadata: {
|
|
3704
|
+
hasImages,
|
|
3705
|
+
requiresVisionHeaders: hasImages
|
|
3706
|
+
}
|
|
3647
3707
|
};
|
|
3648
3708
|
}
|
|
3649
3709
|
/**
|
|
3650
|
-
*
|
|
3710
|
+
* 与StandardProtocolAdapter保持一致的API,用于集成测试和向后兼容。
|
|
3651
3711
|
*/
|
|
3652
|
-
|
|
3653
|
-
this.
|
|
3712
|
+
convertRequest(anthropicRequest) {
|
|
3713
|
+
return this.convertAnthropicToOpenAI(anthropicRequest);
|
|
3654
3714
|
}
|
|
3655
3715
|
/**
|
|
3656
|
-
*
|
|
3716
|
+
* 转换OpenAI流式响应为Anthropic SSE格式
|
|
3657
3717
|
*/
|
|
3658
|
-
|
|
3659
|
-
const
|
|
3660
|
-
|
|
3661
|
-
this.
|
|
3662
|
-
|
|
3718
|
+
convertOpenAIStreamToAnthropic(openaiStream, originalRequest) {
|
|
3719
|
+
const logger = this.config.logger;
|
|
3720
|
+
try {
|
|
3721
|
+
if (this.config.debugMode) {
|
|
3722
|
+
logger.debug("Converting OpenAI stream to Anthropic SSE", {
|
|
3723
|
+
streamLength: openaiStream.length,
|
|
3724
|
+
model: originalRequest.model
|
|
3725
|
+
});
|
|
3726
|
+
}
|
|
3727
|
+
if (!openaiStream || openaiStream.trim() === "") {
|
|
3728
|
+
return {
|
|
3729
|
+
success: false,
|
|
3730
|
+
error: "Empty stream response",
|
|
3731
|
+
anthropicSSE: "",
|
|
3732
|
+
anthropicStandardResponse: null
|
|
3733
|
+
};
|
|
3734
|
+
}
|
|
3735
|
+
const anthropicSSE = this.convertToAnthropicSSE(openaiStream, originalRequest.model);
|
|
3736
|
+
const anthropicStandardResponse = this.buildStandardResponse(openaiStream);
|
|
3737
|
+
return {
|
|
3738
|
+
success: true,
|
|
3739
|
+
anthropicSSE,
|
|
3740
|
+
anthropicStandardResponse
|
|
3741
|
+
};
|
|
3742
|
+
} catch (error) {
|
|
3743
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown conversion error";
|
|
3744
|
+
logger.error("Stream conversion failed", { error: errorMessage });
|
|
3745
|
+
return {
|
|
3746
|
+
success: false,
|
|
3747
|
+
error: errorMessage,
|
|
3748
|
+
anthropicSSE: "",
|
|
3749
|
+
anthropicStandardResponse: null
|
|
3750
|
+
};
|
|
3663
3751
|
}
|
|
3664
|
-
return false;
|
|
3665
|
-
}
|
|
3666
|
-
/**
|
|
3667
|
-
* 获取所有策略信息
|
|
3668
|
-
*/
|
|
3669
|
-
getStrategies() {
|
|
3670
|
-
return this.strategies.map((strategy) => ({
|
|
3671
|
-
id: strategy.id,
|
|
3672
|
-
name: strategy.name,
|
|
3673
|
-
description: strategy.description,
|
|
3674
|
-
supportedErrors: strategy.supportedErrors,
|
|
3675
|
-
maxRetries: strategy.maxRetries,
|
|
3676
|
-
backoffMs: strategy.backoffMs
|
|
3677
|
-
}));
|
|
3678
|
-
}
|
|
3679
|
-
};
|
|
3680
|
-
var errorRecovery = new ErrorRecovery();
|
|
3681
|
-
var attemptRecovery = (error, context) => errorRecovery.attemptRecovery(error, context);
|
|
3682
|
-
var isRecoverable = (error, context) => errorRecovery.isRecoverable(error, context);
|
|
3683
|
-
var getRecoveryRecommendations = (error, context) => errorRecovery.getRecoveryRecommendations(error, context);
|
|
3684
|
-
|
|
3685
|
-
// src/core/a2o-request-adapter/adapter.ts
|
|
3686
|
-
var A2ORequestAdapter = class {
|
|
3687
|
-
constructor(config = {}) {
|
|
3688
|
-
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
3689
3752
|
}
|
|
3690
3753
|
/**
|
|
3691
|
-
*
|
|
3692
|
-
* 集成校验、修复和错误恢复功能
|
|
3754
|
+
* 将OpenAI流转换为Anthropic SSE格式
|
|
3693
3755
|
*/
|
|
3694
|
-
|
|
3695
|
-
const
|
|
3696
|
-
const
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
validatedInput = validateAnthropicRequest(anthropicRequest);
|
|
3710
|
-
result.validationResult = {
|
|
3711
|
-
inputValid: true,
|
|
3712
|
-
outputValid: false,
|
|
3713
|
-
issues: []
|
|
3714
|
-
};
|
|
3715
|
-
} catch (error) {
|
|
3716
|
-
result.errors.push(`Input validation failed: ${error.message}`);
|
|
3717
|
-
return result;
|
|
3718
|
-
}
|
|
3719
|
-
} else {
|
|
3720
|
-
if (this.config.healing.enabled) {
|
|
3721
|
-
const healingResult = await healA2ORequest(anthropicRequest, this.config.healing.maxAttempts);
|
|
3722
|
-
if (healingResult.success) {
|
|
3723
|
-
validatedInput = healingResult.data;
|
|
3724
|
-
result.healingApplied = true;
|
|
3725
|
-
result.appliedFixes = healingResult.appliedFixes;
|
|
3726
|
-
result.warnings.push(...healingResult.warnings);
|
|
3727
|
-
} else {
|
|
3728
|
-
result.errors.push(...healingResult.errors);
|
|
3729
|
-
return result;
|
|
3730
|
-
}
|
|
3731
|
-
} else {
|
|
3732
|
-
try {
|
|
3733
|
-
validatedInput = validateAnthropicRequest(anthropicRequest);
|
|
3734
|
-
} catch {
|
|
3735
|
-
validatedInput = anthropicRequest;
|
|
3736
|
-
result.warnings.push("Input validation skipped due to errors");
|
|
3737
|
-
}
|
|
3738
|
-
}
|
|
3756
|
+
convertToAnthropicSSE(openaiStream, modelName) {
|
|
3757
|
+
const lines = openaiStream.split("\n");
|
|
3758
|
+
const sseLines = [];
|
|
3759
|
+
const state = this.createConversionState();
|
|
3760
|
+
sseLines.push(
|
|
3761
|
+
"event: message_start",
|
|
3762
|
+
`data: {"type":"message_start","message":{"id":"msg_${Date.now()}","type":"message","role":"assistant","model":"${modelName}","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0}}}`,
|
|
3763
|
+
""
|
|
3764
|
+
);
|
|
3765
|
+
for (const line of lines) {
|
|
3766
|
+
if (line.startsWith("data:")) {
|
|
3767
|
+
const dataLine = line.substring(5);
|
|
3768
|
+
if (dataLine.trim() === "[DONE]") {
|
|
3769
|
+
this.addFinalEvents(state, sseLines);
|
|
3770
|
+
break;
|
|
3739
3771
|
}
|
|
3740
|
-
} else {
|
|
3741
|
-
validatedInput = anthropicRequest;
|
|
3742
|
-
}
|
|
3743
|
-
const openaiRequest = await this.performCoreConversion(validatedInput);
|
|
3744
|
-
if (this.config.validation.enabled) {
|
|
3745
3772
|
try {
|
|
3746
|
-
const
|
|
3747
|
-
|
|
3748
|
-
result.validationResult = {
|
|
3749
|
-
inputValid: true,
|
|
3750
|
-
outputValid: true,
|
|
3751
|
-
issues: []
|
|
3752
|
-
};
|
|
3773
|
+
const chunk = JSON.parse(dataLine);
|
|
3774
|
+
this.processStreamChunk(chunk, state, sseLines);
|
|
3753
3775
|
} catch (error) {
|
|
3754
|
-
if (this.config.
|
|
3755
|
-
|
|
3756
|
-
return result;
|
|
3757
|
-
} else {
|
|
3758
|
-
result.warnings.push(`Output validation warning: ${error.message}`);
|
|
3759
|
-
result.data = openaiRequest;
|
|
3776
|
+
if (this.config.debugMode) {
|
|
3777
|
+
this.config.logger.warn("Failed to parse stream chunk", { line: dataLine.substring(0, 200) });
|
|
3760
3778
|
}
|
|
3761
3779
|
}
|
|
3762
|
-
} else {
|
|
3763
|
-
result.data = openaiRequest;
|
|
3764
|
-
}
|
|
3765
|
-
if (this.config.monitoring.enabled) {
|
|
3766
|
-
const processingTime = Date.now() - startTime;
|
|
3767
|
-
if (this.config.monitoring.logLevel !== "none") {
|
|
3768
|
-
console.log(`[A2O Adapter] Conversion completed in ${processingTime}ms`, {
|
|
3769
|
-
healingApplied: result.healingApplied,
|
|
3770
|
-
fixesCount: result.appliedFixes?.length || 0
|
|
3771
|
-
});
|
|
3772
|
-
}
|
|
3773
|
-
}
|
|
3774
|
-
result.success = true;
|
|
3775
|
-
return result;
|
|
3776
|
-
} catch (error) {
|
|
3777
|
-
result.errors.push(`Conversion failed: ${error.message}`);
|
|
3778
|
-
if (this.config.recovery.enabled) {
|
|
3779
|
-
result.warnings.push("Error recovery attempted but not implemented yet");
|
|
3780
|
-
}
|
|
3781
|
-
return result;
|
|
3782
|
-
}
|
|
3783
|
-
}
|
|
3784
|
-
/**
|
|
3785
|
-
* 执行核心转换逻辑(原有逻辑保持不变)
|
|
3786
|
-
*/
|
|
3787
|
-
async performCoreConversion(anthropicRequest) {
|
|
3788
|
-
if (this.config.enableFormatValidation) {
|
|
3789
|
-
FormatValidator.validateClaudeRequest(anthropicRequest);
|
|
3790
|
-
}
|
|
3791
|
-
const openaiRequest = {
|
|
3792
|
-
model: anthropicRequest.model,
|
|
3793
|
-
messages: MessageConverter.convertMessages(anthropicRequest.messages, anthropicRequest.system),
|
|
3794
|
-
max_tokens: anthropicRequest.max_tokens,
|
|
3795
|
-
temperature: anthropicRequest.temperature,
|
|
3796
|
-
stream: anthropicRequest.stream
|
|
3797
|
-
};
|
|
3798
|
-
if (anthropicRequest.tools && anthropicRequest.tools.length > 0) {
|
|
3799
|
-
openaiRequest.tools = this.convertToolDefinitions(anthropicRequest.tools);
|
|
3800
|
-
}
|
|
3801
|
-
const specialFields = ["_anthropic_protocol", "_rovo_tool_injected", "_routeResult"];
|
|
3802
|
-
for (const field of specialFields) {
|
|
3803
|
-
if (anthropicRequest[field] !== void 0) {
|
|
3804
|
-
openaiRequest[field] = anthropicRequest[field];
|
|
3805
3780
|
}
|
|
3806
3781
|
}
|
|
3807
|
-
return
|
|
3782
|
+
return sseLines.join("\n");
|
|
3808
3783
|
}
|
|
3809
3784
|
/**
|
|
3810
|
-
*
|
|
3785
|
+
* 处理单个流式数据块 - 支持thinking和content双模式
|
|
3811
3786
|
*/
|
|
3812
|
-
|
|
3813
|
-
if (this.
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
const openaiRequest = {
|
|
3817
|
-
model: anthropicRequest.model,
|
|
3818
|
-
messages: MessageConverter.convertMessages(anthropicRequest.messages, anthropicRequest.system),
|
|
3819
|
-
max_tokens: anthropicRequest.max_tokens,
|
|
3820
|
-
temperature: anthropicRequest.temperature,
|
|
3821
|
-
stream: anthropicRequest.stream,
|
|
3822
|
-
n: 1
|
|
3823
|
-
};
|
|
3824
|
-
if (anthropicRequest.tools && anthropicRequest.tools.length > 0) {
|
|
3825
|
-
openaiRequest.tools = this.convertToolDefinitions(anthropicRequest.tools);
|
|
3787
|
+
processStreamChunk(chunk, state, sseLines) {
|
|
3788
|
+
if (this.isResponsesEvent(chunk)) {
|
|
3789
|
+
this.processResponsesEvent(chunk, state, sseLines);
|
|
3790
|
+
return;
|
|
3826
3791
|
}
|
|
3827
|
-
const
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3792
|
+
const choice = chunk.choices?.[0];
|
|
3793
|
+
if (choice) {
|
|
3794
|
+
const hasToolCalls = choice.delta?.tool_calls;
|
|
3795
|
+
const hasFinishReason = choice.finish_reason;
|
|
3796
|
+
const isNonText = !choice.delta?.content;
|
|
3797
|
+
if (this.config.debugMode && (hasToolCalls || hasFinishReason || isNonText && choice.delta)) {
|
|
3798
|
+
this.logDebug("Streaming chunk processed", { chunk });
|
|
3831
3799
|
}
|
|
3832
3800
|
}
|
|
3833
|
-
if (
|
|
3834
|
-
|
|
3801
|
+
if (!choice) {
|
|
3802
|
+
this.updateUsageFromChunk(chunk, state);
|
|
3803
|
+
return;
|
|
3835
3804
|
}
|
|
3836
|
-
|
|
3805
|
+
const delta = choice.delta ?? {};
|
|
3806
|
+
this.appendThinkingContent(this.coalesceContent(delta.reasoning_content), state, sseLines);
|
|
3807
|
+
this.appendTextContent(this.coalesceContent(delta.content), state, sseLines);
|
|
3808
|
+
if (delta.tool_calls) {
|
|
3809
|
+
this.processToolCalls(delta.tool_calls, state, sseLines);
|
|
3810
|
+
}
|
|
3811
|
+
this.updateUsageFromChunk(chunk, state);
|
|
3837
3812
|
}
|
|
3838
3813
|
/**
|
|
3839
|
-
*
|
|
3814
|
+
* 处理工具调用 - 支持OpenAI流式分块累积
|
|
3815
|
+
* OpenAI流式API会将tool_calls分多个chunk发送:
|
|
3816
|
+
* - Chunk 1: {index:0, id:"call_xxx", type:"function", function:{name:"web_search"}}
|
|
3817
|
+
* - Chunk 2: {index:0, function:{arguments:"{\"query\":\"xxx\"}"}}
|
|
3818
|
+
* - Chunk N: 继续累积arguments
|
|
3840
3819
|
*/
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
const
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3820
|
+
processToolCalls(toolCalls, state, sseLines) {
|
|
3821
|
+
this.logDebug("processToolCalls called", { toolCalls });
|
|
3822
|
+
for (const toolCall of toolCalls) {
|
|
3823
|
+
const index = toolCall.index ?? 0;
|
|
3824
|
+
const toolId = toolCall.id;
|
|
3825
|
+
const toolName = toolCall.function?.name;
|
|
3826
|
+
const toolArgs = toolCall.function?.arguments;
|
|
3827
|
+
this.logDebug(`Processing tool chunk for index ${index}`, {
|
|
3828
|
+
hasId: !!toolId,
|
|
3829
|
+
hasName: !!toolName,
|
|
3830
|
+
hasArgs: !!toolArgs,
|
|
3831
|
+
argsLength: toolArgs?.length
|
|
3848
3832
|
});
|
|
3833
|
+
const stateKey = `openai_tool_${index}`;
|
|
3834
|
+
const toolData = this.getOrCreateToolCallState(state, stateKey);
|
|
3835
|
+
if (toolId && !toolData.id) {
|
|
3836
|
+
toolData.id = toolId;
|
|
3837
|
+
}
|
|
3838
|
+
if (toolName) {
|
|
3839
|
+
toolData.name = toolName;
|
|
3840
|
+
}
|
|
3841
|
+
this.registerToolCallAlias(state, toolId ? `openai_tool_id_${toolId}` : void 0, toolData);
|
|
3842
|
+
this.registerToolCallAlias(state, `openai_tool_index_${index}`, toolData);
|
|
3843
|
+
if (toolArgs) {
|
|
3844
|
+
toolData.pendingChunks.push(toolArgs);
|
|
3845
|
+
this.logDebug(`Accumulated tool arguments for index ${index}`, {
|
|
3846
|
+
currentLength: toolData.pendingChunks.reduce((acc, chunk) => acc + chunk.length, 0)
|
|
3847
|
+
});
|
|
3848
|
+
}
|
|
3849
|
+
const started = this.maybeStartToolBlock(toolData, state, sseLines);
|
|
3850
|
+
if (started || toolData.blockStartSent) {
|
|
3851
|
+
this.flushPendingToolChunks(toolData, sseLines);
|
|
3852
|
+
}
|
|
3849
3853
|
}
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3854
|
+
}
|
|
3855
|
+
getOrCreateToolCallState(state, key) {
|
|
3856
|
+
let existing = state.toolCallsMap.get(key);
|
|
3857
|
+
if (!existing) {
|
|
3858
|
+
existing = {
|
|
3859
|
+
id: "",
|
|
3860
|
+
name: "",
|
|
3861
|
+
input: "",
|
|
3862
|
+
blockStartSent: false,
|
|
3863
|
+
blockStopSent: false,
|
|
3864
|
+
pendingChunks: []
|
|
3865
|
+
};
|
|
3866
|
+
state.toolCallsMap.set(key, existing);
|
|
3853
3867
|
}
|
|
3854
|
-
|
|
3855
|
-
role: "assistant",
|
|
3856
|
-
content: claudeContent
|
|
3857
|
-
};
|
|
3858
|
-
return claudeResponse;
|
|
3868
|
+
return existing;
|
|
3859
3869
|
}
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
return tool;
|
|
3867
|
-
} else {
|
|
3868
|
-
return ToolConverter.convertAnthropicToolToOpenAI(tool);
|
|
3869
|
-
}
|
|
3870
|
-
});
|
|
3870
|
+
registerToolCallAlias(state, alias, toolData) {
|
|
3871
|
+
if (!alias) return;
|
|
3872
|
+
const current = state.toolCallsMap.get(alias);
|
|
3873
|
+
if (!current || current !== toolData) {
|
|
3874
|
+
state.toolCallsMap.set(alias, toolData);
|
|
3875
|
+
}
|
|
3871
3876
|
}
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
+
maybeStartToolBlock(toolData, state, sseLines) {
|
|
3878
|
+
if (toolData.blockStartSent) return false;
|
|
3879
|
+
if (!toolData.name) {
|
|
3880
|
+
return false;
|
|
3881
|
+
}
|
|
3882
|
+
if (!toolData.id) {
|
|
3883
|
+
toolData.id = `call_${++state.toolCallCounter}`;
|
|
3884
|
+
}
|
|
3885
|
+
const blockIndex = toolData.blockIndex ?? state.nextToolBlockIndex++;
|
|
3886
|
+
toolData.blockIndex = blockIndex;
|
|
3887
|
+
sseLines.push(
|
|
3888
|
+
"event: content_block_start",
|
|
3889
|
+
`data: {"type":"content_block_start","index":${blockIndex},"content_block":{"type":"tool_use","id":"${this.escapeJsonString(toolData.id)}","name":"${this.escapeJsonString(toolData.name)}","input":{}}}`,
|
|
3890
|
+
""
|
|
3891
|
+
);
|
|
3892
|
+
toolData.blockStartSent = true;
|
|
3893
|
+
this.logDebug("Sent content_block_start", { toolName: toolData.name, blockIndex });
|
|
3894
|
+
return true;
|
|
3877
3895
|
}
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3896
|
+
flushPendingToolChunks(toolData, sseLines) {
|
|
3897
|
+
if (!toolData.blockStartSent || toolData.blockIndex === void 0) {
|
|
3898
|
+
return;
|
|
3899
|
+
}
|
|
3900
|
+
while (toolData.pendingChunks.length > 0) {
|
|
3901
|
+
const chunk = toolData.pendingChunks.shift();
|
|
3902
|
+
if (chunk === void 0) continue;
|
|
3903
|
+
toolData.input += chunk;
|
|
3904
|
+
sseLines.push(
|
|
3905
|
+
"event: content_block_delta",
|
|
3906
|
+
`data: {"type":"content_block_delta","index":${toolData.blockIndex},"delta":{"type":"input_json_delta","partial_json":${JSON.stringify(chunk)}}}`,
|
|
3907
|
+
""
|
|
3908
|
+
);
|
|
3909
|
+
this.logDebug("Sent input_json_delta", { blockIndex: toolData.blockIndex });
|
|
3910
|
+
}
|
|
3883
3911
|
}
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3912
|
+
coalesceContent(content) {
|
|
3913
|
+
if (!content) return void 0;
|
|
3914
|
+
if (typeof content === "string") return content;
|
|
3915
|
+
if (Array.isArray(content)) {
|
|
3916
|
+
return content.map((item) => {
|
|
3917
|
+
if (typeof item === "string") return item;
|
|
3918
|
+
if (typeof item?.text === "string") return item.text;
|
|
3919
|
+
if (typeof item?.content === "string") return item.content;
|
|
3920
|
+
return "";
|
|
3921
|
+
}).join("");
|
|
3922
|
+
}
|
|
3923
|
+
if (typeof content === "object" && typeof content.text === "string") {
|
|
3924
|
+
return content.text;
|
|
3925
|
+
}
|
|
3926
|
+
return void 0;
|
|
3889
3927
|
}
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3928
|
+
appendThinkingContent(content, state, sseLines) {
|
|
3929
|
+
if (!content) return;
|
|
3930
|
+
state.reasoningContent += content;
|
|
3931
|
+
if (!state.thinkingBlockStarted) {
|
|
3932
|
+
sseLines.push(
|
|
3933
|
+
"event: content_block_start",
|
|
3934
|
+
'data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":"<thinking>"}}',
|
|
3935
|
+
""
|
|
3936
|
+
);
|
|
3937
|
+
state.thinkingBlockStarted = true;
|
|
3938
|
+
}
|
|
3939
|
+
sseLines.push(
|
|
3940
|
+
"event: content_block_delta",
|
|
3941
|
+
`data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"${this.escapeJsonString(content)}"}}`,
|
|
3942
|
+
""
|
|
3943
|
+
);
|
|
3895
3944
|
}
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3945
|
+
appendTextContent(content, state, sseLines) {
|
|
3946
|
+
if (!content || content === "") return;
|
|
3947
|
+
if (state.thinkingBlockStarted && !state.contentBlockStarted) {
|
|
3948
|
+
sseLines.push(
|
|
3949
|
+
"event: content_block_delta",
|
|
3950
|
+
'data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"</thinking>\\n\\n"}}',
|
|
3951
|
+
"",
|
|
3952
|
+
"event: content_block_stop",
|
|
3953
|
+
'data: {"type":"content_block_stop","index":0}',
|
|
3954
|
+
"",
|
|
3955
|
+
"event: content_block_start",
|
|
3956
|
+
'data: {"type":"content_block_start","index":1,"content_block":{"type":"text","text":""}}',
|
|
3957
|
+
""
|
|
3958
|
+
);
|
|
3959
|
+
state.contentBlockStarted = true;
|
|
3960
|
+
} else if (!state.contentBlockStarted && !state.thinkingBlockStarted) {
|
|
3961
|
+
sseLines.push(
|
|
3962
|
+
"event: content_block_start",
|
|
3963
|
+
'data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}',
|
|
3964
|
+
""
|
|
3965
|
+
);
|
|
3966
|
+
state.contentBlockStarted = true;
|
|
3967
|
+
}
|
|
3968
|
+
state.textContent += content;
|
|
3969
|
+
const blockIndex = state.thinkingBlockStarted ? 1 : 0;
|
|
3970
|
+
sseLines.push(
|
|
3971
|
+
"event: content_block_delta",
|
|
3972
|
+
`data: {"type":"content_block_delta","index":${blockIndex},"delta":{"type":"text_delta","text":"${this.escapeJsonString(content)}"}}`,
|
|
3973
|
+
""
|
|
3974
|
+
);
|
|
3901
3975
|
}
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3976
|
+
updateUsageFromChunk(chunk, state) {
|
|
3977
|
+
const usage = chunk?.usage || chunk?.response?.usage;
|
|
3978
|
+
if (!usage) return;
|
|
3979
|
+
if (typeof usage.prompt_tokens === "number") {
|
|
3980
|
+
state.usage.input_tokens = usage.prompt_tokens;
|
|
3981
|
+
}
|
|
3982
|
+
if (typeof usage.completion_tokens === "number") {
|
|
3983
|
+
state.usage.output_tokens = usage.completion_tokens;
|
|
3984
|
+
}
|
|
3985
|
+
if (typeof usage.input_tokens === "number") {
|
|
3986
|
+
state.usage.input_tokens = usage.input_tokens;
|
|
3987
|
+
}
|
|
3988
|
+
if (typeof usage.output_tokens === "number") {
|
|
3989
|
+
state.usage.output_tokens = usage.output_tokens;
|
|
3990
|
+
}
|
|
3907
3991
|
}
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
*/
|
|
3911
|
-
getConfig() {
|
|
3912
|
-
return { ...this.config };
|
|
3992
|
+
isResponsesEvent(chunk) {
|
|
3993
|
+
return typeof chunk?.type === "string" && chunk.type.startsWith("response.");
|
|
3913
3994
|
}
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3995
|
+
processResponsesEvent(event, state, sseLines) {
|
|
3996
|
+
this.updateUsageFromChunk(event, state);
|
|
3997
|
+
switch (event.type) {
|
|
3998
|
+
case "response.output_item.added":
|
|
3999
|
+
this.handleResponsesOutputItemAdded(event, state, sseLines);
|
|
4000
|
+
break;
|
|
4001
|
+
case "response.function_call_arguments.delta":
|
|
4002
|
+
this.handleResponsesFunctionArgumentsDelta(event, state, sseLines);
|
|
4003
|
+
break;
|
|
4004
|
+
case "response.function_call_arguments.done":
|
|
4005
|
+
case "response.output_item.done":
|
|
4006
|
+
this.handleResponsesFunctionArgumentsDone(event, state, sseLines);
|
|
4007
|
+
break;
|
|
4008
|
+
case "response.output_text.delta":
|
|
4009
|
+
case "response.text.delta":
|
|
4010
|
+
this.appendTextContent(this.extractResponsesTextDelta(event), state, sseLines);
|
|
4011
|
+
break;
|
|
4012
|
+
case "response.output_text.done":
|
|
4013
|
+
case "response.text.done":
|
|
4014
|
+
break;
|
|
4015
|
+
case "response.thinking.delta":
|
|
4016
|
+
this.appendThinkingContent(this.extractResponsesThinkingDelta(event), state, sseLines);
|
|
4017
|
+
break;
|
|
4018
|
+
default:
|
|
4019
|
+
break;
|
|
3930
4020
|
}
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
4021
|
+
}
|
|
4022
|
+
resolveResponsesToolData(identifiers, state) {
|
|
4023
|
+
const aliases = [];
|
|
4024
|
+
if (identifiers.call_id) aliases.push(`responses_call_${identifiers.call_id}`);
|
|
4025
|
+
if (identifiers.item_id) aliases.push(`responses_item_${identifiers.item_id}`);
|
|
4026
|
+
if (typeof identifiers.output_index === "number") aliases.push(`responses_index_${identifiers.output_index}`);
|
|
4027
|
+
let toolData;
|
|
4028
|
+
for (const alias of aliases) {
|
|
4029
|
+
const existing = state.toolCallsMap.get(alias);
|
|
4030
|
+
if (existing) {
|
|
4031
|
+
toolData = existing;
|
|
4032
|
+
break;
|
|
3937
4033
|
}
|
|
3938
4034
|
}
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
if (this.config.validation.strict) {
|
|
3945
|
-
throw error;
|
|
3946
|
-
} else {
|
|
3947
|
-
console.warn("[A2ORequestAdapter] Output validation warning:", error);
|
|
3948
|
-
}
|
|
4035
|
+
if (!toolData) {
|
|
4036
|
+
const baseAlias = aliases[0] ?? `responses_auto_${++state.toolCallCounter}`;
|
|
4037
|
+
toolData = this.getOrCreateToolCallState(state, baseAlias);
|
|
4038
|
+
if (!aliases.length) {
|
|
4039
|
+
aliases.push(baseAlias);
|
|
3949
4040
|
}
|
|
3950
4041
|
}
|
|
3951
|
-
|
|
4042
|
+
for (const alias of aliases) {
|
|
4043
|
+
this.registerToolCallAlias(state, alias, toolData);
|
|
4044
|
+
}
|
|
4045
|
+
return toolData;
|
|
3952
4046
|
}
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
if (
|
|
3958
|
-
|
|
4047
|
+
handleResponsesOutputItemAdded(event, state, sseLines) {
|
|
4048
|
+
const item = event?.item;
|
|
4049
|
+
if (!item) return;
|
|
4050
|
+
const itemType = item.type;
|
|
4051
|
+
if (itemType !== "function_call" && itemType !== "tool_call") {
|
|
4052
|
+
return;
|
|
3959
4053
|
}
|
|
3960
|
-
const
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
n: 1
|
|
3967
|
-
};
|
|
3968
|
-
if (anthropicRequest.tools && anthropicRequest.tools.length > 0) {
|
|
3969
|
-
openaiRequest.tools = this.convertToolDefinitions(anthropicRequest.tools);
|
|
4054
|
+
const toolData = this.resolveResponsesToolData(
|
|
4055
|
+
{ call_id: item.call_id ?? item.id, item_id: item.id, output_index: event.output_index },
|
|
4056
|
+
state
|
|
4057
|
+
);
|
|
4058
|
+
if (!toolData.id) {
|
|
4059
|
+
toolData.id = item.call_id || item.id || `call_${++state.toolCallCounter}`;
|
|
3970
4060
|
}
|
|
3971
|
-
const
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
4061
|
+
const name = item.name ?? item.function?.name ?? item.function_call?.name;
|
|
4062
|
+
if (name) {
|
|
4063
|
+
toolData.name = name;
|
|
4064
|
+
}
|
|
4065
|
+
if (typeof item.arguments === "string" && item.arguments.length > 0) {
|
|
4066
|
+
toolData.pendingChunks.push(item.arguments);
|
|
4067
|
+
}
|
|
4068
|
+
const started = this.maybeStartToolBlock(toolData, state, sseLines);
|
|
4069
|
+
if (started || toolData.blockStartSent) {
|
|
4070
|
+
this.flushPendingToolChunks(toolData, sseLines);
|
|
4071
|
+
}
|
|
4072
|
+
}
|
|
4073
|
+
handleResponsesFunctionArgumentsDelta(event, state, sseLines) {
|
|
4074
|
+
const toolData = this.resolveResponsesToolData(
|
|
4075
|
+
{ call_id: event.call_id, item_id: event.item_id, output_index: event.output_index },
|
|
4076
|
+
state
|
|
4077
|
+
);
|
|
4078
|
+
if (!toolData.id && event.call_id) {
|
|
4079
|
+
toolData.id = event.call_id;
|
|
4080
|
+
}
|
|
4081
|
+
const name = event.name ?? event.function_name ?? event.function?.name;
|
|
4082
|
+
if (name) {
|
|
4083
|
+
toolData.name = name;
|
|
4084
|
+
}
|
|
4085
|
+
const argsChunk = this.extractArgumentsDelta(event);
|
|
4086
|
+
if (argsChunk) {
|
|
4087
|
+
toolData.pendingChunks.push(argsChunk);
|
|
4088
|
+
}
|
|
4089
|
+
const started = this.maybeStartToolBlock(toolData, state, sseLines);
|
|
4090
|
+
if (started || toolData.blockStartSent) {
|
|
4091
|
+
this.flushPendingToolChunks(toolData, sseLines);
|
|
4092
|
+
}
|
|
4093
|
+
}
|
|
4094
|
+
handleResponsesFunctionArgumentsDone(event, state, sseLines) {
|
|
4095
|
+
const toolData = this.resolveResponsesToolData(
|
|
4096
|
+
{ call_id: event.call_id, item_id: event.item_id, output_index: event.output_index },
|
|
4097
|
+
state
|
|
4098
|
+
);
|
|
4099
|
+
if (typeof event.arguments === "string" && event.arguments.length > 0) {
|
|
4100
|
+
toolData.pendingChunks.push(event.arguments);
|
|
4101
|
+
}
|
|
4102
|
+
const started = this.maybeStartToolBlock(toolData, state, sseLines);
|
|
4103
|
+
if (started || toolData.blockStartSent) {
|
|
4104
|
+
this.flushPendingToolChunks(toolData, sseLines);
|
|
4105
|
+
}
|
|
4106
|
+
if (toolData.blockStartSent && !toolData.blockStopSent && toolData.blockIndex !== void 0) {
|
|
4107
|
+
sseLines.push(
|
|
4108
|
+
"event: content_block_stop",
|
|
4109
|
+
`data: {"type":"content_block_stop","index":${toolData.blockIndex}}`,
|
|
4110
|
+
""
|
|
4111
|
+
);
|
|
4112
|
+
toolData.blockStopSent = true;
|
|
4113
|
+
if (toolData.id && !state.completedToolCalls.includes(toolData.id)) {
|
|
4114
|
+
state.completedToolCalls.push(toolData.id);
|
|
3975
4115
|
}
|
|
4116
|
+
this.logDebug("Sent content_block_stop", { toolName: toolData.name, blockIndex: toolData.blockIndex });
|
|
3976
4117
|
}
|
|
3977
|
-
|
|
3978
|
-
|
|
4118
|
+
}
|
|
4119
|
+
extractResponsesTextDelta(event) {
|
|
4120
|
+
if (!event) return void 0;
|
|
4121
|
+
if (typeof event.delta === "string") return event.delta;
|
|
4122
|
+
if (event.delta && typeof event.delta.text === "string") return event.delta.text;
|
|
4123
|
+
if (typeof event.text === "string") return event.text;
|
|
4124
|
+
if (Array.isArray(event.output_text)) {
|
|
4125
|
+
return event.output_text.map((item) => item?.text ?? "").join("");
|
|
3979
4126
|
}
|
|
3980
|
-
return
|
|
4127
|
+
return void 0;
|
|
4128
|
+
}
|
|
4129
|
+
extractResponsesThinkingDelta(event) {
|
|
4130
|
+
if (!event) return void 0;
|
|
4131
|
+
if (typeof event.delta === "string") return event.delta;
|
|
4132
|
+
if (event.delta && typeof event.delta.thinking === "string") return event.delta.thinking;
|
|
4133
|
+
if (typeof event.text === "string") return event.text;
|
|
4134
|
+
return void 0;
|
|
4135
|
+
}
|
|
4136
|
+
extractArgumentsDelta(event) {
|
|
4137
|
+
if (!event) return void 0;
|
|
4138
|
+
if (typeof event.delta === "string") return event.delta;
|
|
4139
|
+
if (event.delta && typeof event.delta.arguments === "string") return event.delta.arguments;
|
|
4140
|
+
if (typeof event.arguments_delta === "string") return event.arguments_delta;
|
|
4141
|
+
if (typeof event.arguments === "string") return event.arguments;
|
|
4142
|
+
if (typeof event.partial_json === "string") return event.partial_json;
|
|
4143
|
+
return void 0;
|
|
3981
4144
|
}
|
|
3982
4145
|
/**
|
|
3983
|
-
*
|
|
3984
|
-
* 简化版的修复,不依赖异步操作
|
|
4146
|
+
* 在流结束时关闭所有未关闭的工具调用块
|
|
3985
4147
|
*/
|
|
3986
|
-
|
|
3987
|
-
const
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
message.role = "user";
|
|
4148
|
+
closeAllToolCallBlocks(state, sseLines) {
|
|
4149
|
+
const processed = /* @__PURE__ */ new Set();
|
|
4150
|
+
for (const toolData of state.toolCallsMap.values()) {
|
|
4151
|
+
if (processed.has(toolData)) continue;
|
|
4152
|
+
processed.add(toolData);
|
|
4153
|
+
if (!toolData.blockStartSent && toolData.pendingChunks.length > 0) {
|
|
4154
|
+
if (!toolData.name) {
|
|
4155
|
+
toolData.name = "unknown_tool";
|
|
4156
|
+
}
|
|
4157
|
+
const started = this.maybeStartToolBlock(toolData, state, sseLines);
|
|
4158
|
+
if (started) {
|
|
4159
|
+
this.flushPendingToolChunks(toolData, sseLines);
|
|
4160
|
+
}
|
|
4000
4161
|
}
|
|
4001
|
-
if (!
|
|
4002
|
-
|
|
4162
|
+
if (toolData.blockStartSent && !toolData.blockStopSent && toolData.blockIndex !== void 0) {
|
|
4163
|
+
this.flushPendingToolChunks(toolData, sseLines);
|
|
4164
|
+
sseLines.push(
|
|
4165
|
+
"event: content_block_stop",
|
|
4166
|
+
`data: {"type":"content_block_stop","index":${toolData.blockIndex}}`,
|
|
4167
|
+
""
|
|
4168
|
+
);
|
|
4169
|
+
toolData.blockStopSent = true;
|
|
4170
|
+
if (toolData.id && !state.completedToolCalls.includes(toolData.id)) {
|
|
4171
|
+
state.completedToolCalls.push(toolData.id);
|
|
4172
|
+
}
|
|
4173
|
+
this.logDebug("Sent content_block_stop", { toolName: toolData.name, blockIndex: toolData.blockIndex });
|
|
4003
4174
|
}
|
|
4004
4175
|
}
|
|
4005
|
-
return healedRequest;
|
|
4006
4176
|
}
|
|
4007
4177
|
/**
|
|
4008
|
-
*
|
|
4178
|
+
* 添加最终事件 - 支持thinking+content双模式
|
|
4009
4179
|
*/
|
|
4010
|
-
|
|
4011
|
-
|
|
4180
|
+
addFinalEvents(state, sseLines) {
|
|
4181
|
+
this.closeAllToolCallBlocks(state, sseLines);
|
|
4182
|
+
if (state.contentBlockStarted) {
|
|
4183
|
+
const blockIndex = state.thinkingBlockStarted ? 1 : 0;
|
|
4184
|
+
sseLines.push(
|
|
4185
|
+
"event: content_block_stop",
|
|
4186
|
+
`data: {"type":"content_block_stop","index":${blockIndex}}`,
|
|
4187
|
+
""
|
|
4188
|
+
);
|
|
4189
|
+
} else if (state.thinkingBlockStarted) {
|
|
4190
|
+
sseLines.push(
|
|
4191
|
+
"event: content_block_delta",
|
|
4192
|
+
'data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"</thinking>"}}',
|
|
4193
|
+
"",
|
|
4194
|
+
"event: content_block_stop",
|
|
4195
|
+
'data: {"type":"content_block_stop","index":0}',
|
|
4196
|
+
""
|
|
4197
|
+
);
|
|
4198
|
+
}
|
|
4199
|
+
const stopReason = state.completedToolCalls.length > 0 ? "tool_use" : "end_turn";
|
|
4200
|
+
const usagePayload = state.usage.input_tokens > 0 ? `{"input_tokens":${state.usage.input_tokens},"output_tokens":${state.usage.output_tokens}}` : `{"output_tokens":${state.usage.output_tokens}}`;
|
|
4201
|
+
sseLines.push(
|
|
4202
|
+
"event: message_delta",
|
|
4203
|
+
`data: {"type":"message_delta","delta":{"stop_reason":"${stopReason}","stop_sequence":null},"usage":${usagePayload}}`,
|
|
4204
|
+
"",
|
|
4205
|
+
"event: message_stop",
|
|
4206
|
+
'data: {"type":"message_stop"}',
|
|
4207
|
+
""
|
|
4208
|
+
);
|
|
4012
4209
|
}
|
|
4013
4210
|
/**
|
|
4014
|
-
*
|
|
4211
|
+
* 构建标准响应格式
|
|
4015
4212
|
*/
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
if (
|
|
4022
|
-
const
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4213
|
+
buildStandardResponse(openaiStream) {
|
|
4214
|
+
const state = this.createConversionState();
|
|
4215
|
+
const lines = openaiStream.split("\n");
|
|
4216
|
+
const noopSseLines = [];
|
|
4217
|
+
for (const line of lines) {
|
|
4218
|
+
if (line.startsWith("data:")) {
|
|
4219
|
+
const dataLine = line.startsWith("data: ") ? line.substring(6) : line.substring(5);
|
|
4220
|
+
if (dataLine.trim() === "[DONE]") break;
|
|
4221
|
+
try {
|
|
4222
|
+
const chunk = JSON.parse(dataLine);
|
|
4223
|
+
noopSseLines.length = 0;
|
|
4224
|
+
this.processStreamChunk(chunk, state, noopSseLines);
|
|
4225
|
+
} catch (error) {
|
|
4226
|
+
}
|
|
4027
4227
|
}
|
|
4028
|
-
return summary.slice(0, 2).join(", ") + (error.issues.length > 5 ? ` (+${error.issues.length - 5} more)` : "");
|
|
4029
4228
|
}
|
|
4030
|
-
|
|
4229
|
+
const stopReason = state.completedToolCalls.length > 0 ? "tool_use" : "end_turn";
|
|
4230
|
+
return {
|
|
4231
|
+
id: `msg_${Date.now()}`,
|
|
4232
|
+
type: "message",
|
|
4233
|
+
role: "assistant",
|
|
4234
|
+
content: state.textContent ? [
|
|
4235
|
+
{
|
|
4236
|
+
type: "text",
|
|
4237
|
+
text: state.textContent
|
|
4238
|
+
}
|
|
4239
|
+
] : [],
|
|
4240
|
+
model: "claude-3-sonnet-20240229",
|
|
4241
|
+
stop_reason: stopReason,
|
|
4242
|
+
stop_sequence: null,
|
|
4243
|
+
usage: state.usage
|
|
4244
|
+
};
|
|
4031
4245
|
}
|
|
4032
|
-
};
|
|
4033
|
-
var A2ORequestAdapterStatic = {
|
|
4034
4246
|
/**
|
|
4035
|
-
*
|
|
4036
|
-
* 内部使用增强转换器,所有调用点自动获得增强功能
|
|
4247
|
+
* 创建转换状态对象
|
|
4037
4248
|
*/
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
}
|
|
4056
|
-
}
|
|
4249
|
+
createConversionState() {
|
|
4250
|
+
return {
|
|
4251
|
+
processedLines: 0,
|
|
4252
|
+
textContent: "",
|
|
4253
|
+
reasoningContent: "",
|
|
4254
|
+
toolCallsMap: /* @__PURE__ */ new Map(),
|
|
4255
|
+
completedToolCalls: [],
|
|
4256
|
+
allSSELines: [],
|
|
4257
|
+
errors: [],
|
|
4258
|
+
usage: {
|
|
4259
|
+
input_tokens: 0,
|
|
4260
|
+
output_tokens: 0
|
|
4261
|
+
},
|
|
4262
|
+
thinkingBlockStarted: false,
|
|
4263
|
+
contentBlockStarted: false,
|
|
4264
|
+
toolCallCounter: 0,
|
|
4265
|
+
nextToolBlockIndex: 1
|
|
4266
|
+
};
|
|
4267
|
+
}
|
|
4057
4268
|
/**
|
|
4058
|
-
*
|
|
4059
|
-
* 内部使用增强转换器
|
|
4269
|
+
* 转换消息格式
|
|
4060
4270
|
*/
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
validation: { enabled: true, strict: false },
|
|
4068
|
-
healing: { enabled: true, maxAttempts: 2, enableCustomRules: true },
|
|
4069
|
-
recovery: { enabled: false, maxRetries: 0, backoffMs: 1e3 },
|
|
4070
|
-
monitoring: { enabled: false, logLevel: "none", enableMetrics: false }
|
|
4071
|
-
});
|
|
4072
|
-
return adapter.convertOpenAIResponseToClaude(openaiResponse);
|
|
4073
|
-
},
|
|
4271
|
+
convertMessages(messages) {
|
|
4272
|
+
return messages.map((msg) => ({
|
|
4273
|
+
role: msg.role,
|
|
4274
|
+
content: msg.content
|
|
4275
|
+
}));
|
|
4276
|
+
}
|
|
4074
4277
|
/**
|
|
4075
|
-
*
|
|
4278
|
+
* 映射Anthropic模型到OpenAI模型
|
|
4076
4279
|
*/
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4280
|
+
mapAnthropicModelToOpenAI(model) {
|
|
4281
|
+
const supportedModels = [
|
|
4282
|
+
"glm-4.5",
|
|
4283
|
+
"kimi-k2",
|
|
4284
|
+
"deepseek-v3.1",
|
|
4285
|
+
"deepseek-r1",
|
|
4286
|
+
"deepseek-v3",
|
|
4287
|
+
"qwen3-32b",
|
|
4288
|
+
"qwen3-coder",
|
|
4289
|
+
"qwen3-235b",
|
|
4290
|
+
"tstars2.0"
|
|
4291
|
+
];
|
|
4292
|
+
if (supportedModels.includes(model)) {
|
|
4293
|
+
return model;
|
|
4294
|
+
}
|
|
4295
|
+
const mapping = {
|
|
4296
|
+
"claude-3-sonnet-20240229": "glm-4.5",
|
|
4297
|
+
"claude-3-haiku-20240307": "kimi-k2",
|
|
4298
|
+
"claude-3-opus-20240229": "deepseek-v3.1"
|
|
4299
|
+
};
|
|
4300
|
+
return mapping[model] || "glm-4.5";
|
|
4301
|
+
}
|
|
4080
4302
|
/**
|
|
4081
|
-
*
|
|
4303
|
+
* 检查请求是否包含图片内容
|
|
4082
4304
|
*/
|
|
4083
|
-
|
|
4084
|
-
return
|
|
4085
|
-
|
|
4305
|
+
hasImageContent(request) {
|
|
4306
|
+
return request.messages.some(
|
|
4307
|
+
(msg) => Array.isArray(msg.content) && msg.content.some((content) => content?.type === "image")
|
|
4308
|
+
);
|
|
4309
|
+
}
|
|
4086
4310
|
/**
|
|
4087
|
-
*
|
|
4311
|
+
* 转义JSON字符串
|
|
4088
4312
|
*/
|
|
4089
|
-
|
|
4090
|
-
return
|
|
4091
|
-
}
|
|
4313
|
+
escapeJsonString(str) {
|
|
4314
|
+
return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
|
|
4315
|
+
}
|
|
4092
4316
|
/**
|
|
4093
|
-
*
|
|
4317
|
+
* 获取初始SSE事件(message_start + ping)
|
|
4094
4318
|
*/
|
|
4095
|
-
|
|
4096
|
-
return
|
|
4097
|
-
|
|
4319
|
+
getInitialSSEEvents(modelName = "claude-sonnet-4", messageId = `msg_${Date.now()}_${Math.random().toString(36).substring(2, 8)}`) {
|
|
4320
|
+
return [
|
|
4321
|
+
"event: message_start",
|
|
4322
|
+
`data: {"type":"message_start","message":{"id":"${messageId}","type":"message","role":"assistant","model":"${modelName}","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0}}}`,
|
|
4323
|
+
"",
|
|
4324
|
+
"event: ping",
|
|
4325
|
+
'data: {"type":"ping"}',
|
|
4326
|
+
""
|
|
4327
|
+
];
|
|
4328
|
+
}
|
|
4098
4329
|
/**
|
|
4099
|
-
*
|
|
4330
|
+
* 增量转换单个OpenAI数据块为Anthropic SSE事件
|
|
4331
|
+
* 用于逐个处理流式数据片段
|
|
4100
4332
|
*/
|
|
4101
|
-
|
|
4102
|
-
|
|
4333
|
+
convertIncrementalChunk(openaiDataLine, state) {
|
|
4334
|
+
const logger = this.config.logger;
|
|
4335
|
+
const sseEvents = [];
|
|
4336
|
+
state.processedLines += 1;
|
|
4337
|
+
if (openaiDataLine.trim() === "[DONE]") {
|
|
4338
|
+
this.addFinalEvents(state, sseEvents);
|
|
4339
|
+
state.allSSELines.push(...sseEvents);
|
|
4340
|
+
return sseEvents;
|
|
4341
|
+
}
|
|
4342
|
+
try {
|
|
4343
|
+
const chunk = JSON.parse(openaiDataLine);
|
|
4344
|
+
this.processStreamChunk(chunk, state, sseEvents);
|
|
4345
|
+
if (sseEvents.length > 0) {
|
|
4346
|
+
state.allSSELines.push(...sseEvents);
|
|
4347
|
+
}
|
|
4348
|
+
return sseEvents;
|
|
4349
|
+
} catch (error) {
|
|
4350
|
+
if (this.config.debugMode) {
|
|
4351
|
+
logger.warn("Failed to parse OpenAI stream chunk in convertIncrementalChunk", {
|
|
4352
|
+
line: openaiDataLine.substring(0, 200),
|
|
4353
|
+
error: error instanceof Error ? error.message : String(error)
|
|
4354
|
+
});
|
|
4355
|
+
}
|
|
4356
|
+
state.errors.push({
|
|
4357
|
+
error: error instanceof Error ? error.message : String(error),
|
|
4358
|
+
raw: openaiDataLine
|
|
4359
|
+
});
|
|
4360
|
+
return [];
|
|
4361
|
+
}
|
|
4362
|
+
}
|
|
4363
|
+
/**
|
|
4364
|
+
* 暴露内部状态创建方法,供外部增量处理流程使用。
|
|
4365
|
+
*/
|
|
4366
|
+
createIncrementalState() {
|
|
4367
|
+
return this.createConversionState();
|
|
4103
4368
|
}
|
|
4104
4369
|
};
|
|
4105
4370
|
|
|
@@ -4266,6 +4531,8 @@ var ToolCallProcessor = class _ToolCallProcessor {
|
|
|
4266
4531
|
console.debug("[ToolProcessor] Processing tool args, calling processToolArgs");
|
|
4267
4532
|
}
|
|
4268
4533
|
_ToolCallProcessor.processToolArgs(toolId, toolArgs, state, sseLines);
|
|
4534
|
+
} else if (toolName && toolId) {
|
|
4535
|
+
_ToolCallProcessor.processToolArgs(toolId, "", state, sseLines);
|
|
4269
4536
|
} else {
|
|
4270
4537
|
console.warn("\u26A0\uFE0F\u26A0\uFE0F\u26A0\uFE0F [ToolProcessor] No tool args to process! This will result in empty input!");
|
|
4271
4538
|
}
|
|
@@ -4301,6 +4568,8 @@ var ToolCallProcessor = class _ToolCallProcessor {
|
|
|
4301
4568
|
console.debug("[ToolProcessor] Processing batch tool args, calling processToolArgs");
|
|
4302
4569
|
}
|
|
4303
4570
|
_ToolCallProcessor.processToolArgs(toolId, toolArgs, state, sseLines);
|
|
4571
|
+
} else if (toolName && toolId) {
|
|
4572
|
+
_ToolCallProcessor.processToolArgs(toolId, "", state, sseLines);
|
|
4304
4573
|
} else {
|
|
4305
4574
|
console.warn("\u26A0\uFE0F\u26A0\uFE0F\u26A0\uFE0F [ToolProcessor] No batch tool args to process! This will result in empty input!");
|
|
4306
4575
|
}
|