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