ai-protocol-adapters 1.0.0-alpha.7 → 1.0.0-alpha.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +699 -657
- package/dist/index.d.ts +699 -657
- package/dist/index.js +2363 -2094
- package/dist/index.mjs +2363 -2094
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -504,1360 +504,925 @@ 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
|
-
}
|
|
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
|
|
520
535
|
}
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
536
|
+
};
|
|
537
|
+
var SUPPORTED_IMAGE_TYPES = [
|
|
538
|
+
"image/jpeg",
|
|
539
|
+
"image/png",
|
|
540
|
+
"image/gif",
|
|
541
|
+
"image/webp"
|
|
542
|
+
];
|
|
543
|
+
var TOOL_CONVERSION = {
|
|
544
|
+
/**
|
|
545
|
+
* 终极泛化:完全移除工具名称映射
|
|
546
|
+
* 基于GitHub Copilot API测试结果,100%保持原始格式
|
|
547
|
+
*/
|
|
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
|
+
}
|
|
574
|
+
}
|
|
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 = "";
|
|
585
|
+
}
|
|
586
|
+
convertedMessages.push(safeMsg);
|
|
587
|
+
}
|
|
524
588
|
}
|
|
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
|
+
}
|
|
596
|
+
}
|
|
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;
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
return msg;
|
|
619
|
+
});
|
|
525
620
|
}
|
|
526
621
|
/**
|
|
527
|
-
*
|
|
622
|
+
* 创建消息转换上下文
|
|
528
623
|
*/
|
|
529
|
-
|
|
530
|
-
const
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
stream: anthropicRequest.stream ?? true,
|
|
538
|
-
temperature: anthropicRequest.temperature,
|
|
539
|
-
max_tokens: anthropicRequest.max_tokens
|
|
540
|
-
};
|
|
541
|
-
if (anthropicRequest.tools) {
|
|
542
|
-
openaiRequest.tools = anthropicRequest.tools.map((tool) => ({
|
|
543
|
-
type: "function",
|
|
544
|
-
function: {
|
|
545
|
-
name: tool.name,
|
|
546
|
-
description: tool.description,
|
|
547
|
-
parameters: tool.input_schema
|
|
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);
|
|
631
|
+
}
|
|
548
632
|
}
|
|
549
|
-
}
|
|
633
|
+
}
|
|
550
634
|
}
|
|
551
|
-
const hasImages = this.hasImageContent(anthropicRequest);
|
|
552
635
|
return {
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
hasImages,
|
|
556
|
-
requiresVisionHeaders: hasImages
|
|
557
|
-
}
|
|
636
|
+
toolIdToNameMap,
|
|
637
|
+
hasSystemMessage: false
|
|
558
638
|
};
|
|
559
639
|
}
|
|
560
640
|
/**
|
|
561
|
-
*
|
|
641
|
+
* 处理复杂消息(包含多种内容类型)
|
|
562
642
|
*/
|
|
563
|
-
|
|
564
|
-
const
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
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);
|
|
571
655
|
}
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
anthropicSSE: "",
|
|
577
|
-
anthropicStandardResponse: null
|
|
578
|
-
};
|
|
656
|
+
} else if (textContent.length > 0) {
|
|
657
|
+
const textMessage = this.createTextMessage(msg.role, textContent);
|
|
658
|
+
if (textMessage) {
|
|
659
|
+
resultMessages.push(textMessage);
|
|
579
660
|
}
|
|
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
661
|
}
|
|
662
|
+
return resultMessages;
|
|
597
663
|
}
|
|
598
664
|
/**
|
|
599
|
-
*
|
|
665
|
+
* 分类内容块
|
|
600
666
|
*/
|
|
601
|
-
|
|
602
|
-
const
|
|
603
|
-
const
|
|
604
|
-
const
|
|
605
|
-
|
|
606
|
-
"
|
|
607
|
-
|
|
608
|
-
""
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
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;
|
|
624
691
|
}
|
|
625
692
|
}
|
|
626
693
|
}
|
|
627
|
-
return
|
|
694
|
+
return { textContent, toolUses, toolResults };
|
|
628
695
|
}
|
|
629
696
|
/**
|
|
630
|
-
*
|
|
697
|
+
* 转换图片内容格式
|
|
631
698
|
*/
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
const isNonText = !choice.delta?.content;
|
|
638
|
-
if (this.config.debugMode && (hasToolCalls || hasFinishReason || isNonText && choice.delta)) {
|
|
639
|
-
this.logDebug("Streaming chunk processed", { chunk });
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
if (!choice) return;
|
|
643
|
-
const delta = choice.delta;
|
|
644
|
-
if (delta.reasoning_content) {
|
|
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;
|
|
653
|
-
}
|
|
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;
|
|
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;
|
|
681
704
|
}
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
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;
|
|
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
|
+
};
|
|
696
712
|
}
|
|
713
|
+
return null;
|
|
697
714
|
}
|
|
698
715
|
/**
|
|
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
|
|
716
|
+
* 创建包含工具调用的助手消息
|
|
704
717
|
*/
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
hasArgs: !!toolArgs,
|
|
716
|
-
argsLength: toolArgs?.length
|
|
717
|
-
});
|
|
718
|
-
const stateKey = `tool_${index}`;
|
|
719
|
-
let toolData = state.toolCallsMap.get(stateKey);
|
|
720
|
-
if (!toolData) {
|
|
721
|
-
toolData = {
|
|
722
|
-
id: toolId || "",
|
|
723
|
-
name: toolName || "",
|
|
724
|
-
input: "",
|
|
725
|
-
blockStartSent: false,
|
|
726
|
-
blockStopSent: false
|
|
727
|
-
};
|
|
728
|
-
state.toolCallsMap.set(stateKey, toolData);
|
|
729
|
-
} else {
|
|
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 });
|
|
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();
|
|
757
728
|
}
|
|
758
729
|
}
|
|
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;
|
|
759
739
|
}
|
|
760
740
|
/**
|
|
761
|
-
*
|
|
741
|
+
* 创建工具结果消息
|
|
762
742
|
*/
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
state.completedToolCalls.push(toolData.id);
|
|
773
|
-
this.logDebug("Sent content_block_stop", { toolName: toolData.name, blockIndex: toolData.blockIndex });
|
|
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);
|
|
751
|
+
}
|
|
774
752
|
}
|
|
775
|
-
|
|
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
|
+
});
|
|
776
761
|
}
|
|
777
762
|
/**
|
|
778
|
-
*
|
|
763
|
+
* 创建文本消息
|
|
779
764
|
*/
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
'data: {"type":"content_block_stop","index":0}',
|
|
796
|
-
""
|
|
797
|
-
);
|
|
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
|
+
};
|
|
798
780
|
}
|
|
799
|
-
const stopReason = state.completedToolCalls.length > 0 ? "tool_use" : "end_turn";
|
|
800
|
-
sseLines.push(
|
|
801
|
-
"event: message_delta",
|
|
802
|
-
`data: {"type":"message_delta","delta":{"stop_reason":"${stopReason}","stop_sequence":null},"usage":{"output_tokens":${state.usage.output_tokens}}}`,
|
|
803
|
-
"",
|
|
804
|
-
"event: message_stop",
|
|
805
|
-
'data: {"type":"message_stop"}',
|
|
806
|
-
""
|
|
807
|
-
);
|
|
808
781
|
}
|
|
809
782
|
/**
|
|
810
|
-
*
|
|
783
|
+
* 处理系统消息
|
|
811
784
|
*/
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
if (dataLine.trim() === "[DONE]") break;
|
|
819
|
-
try {
|
|
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) {
|
|
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;
|
|
832
791
|
}
|
|
833
|
-
|
|
792
|
+
return s.text || "";
|
|
793
|
+
}).filter((text) => text.length > 0).join("\n").trim();
|
|
794
|
+
} else {
|
|
795
|
+
systemContent = system;
|
|
834
796
|
}
|
|
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
|
-
};
|
|
797
|
+
if (systemContent) {
|
|
798
|
+
return {
|
|
799
|
+
role: "system",
|
|
800
|
+
content: systemContent
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
return null;
|
|
850
804
|
}
|
|
805
|
+
};
|
|
806
|
+
|
|
807
|
+
// src/core/a2o-request-adapter/tool-converter.ts
|
|
808
|
+
var ToolConverter = class {
|
|
851
809
|
/**
|
|
852
|
-
*
|
|
810
|
+
* 将Anthropic工具定义转换为OpenAI格式
|
|
853
811
|
*/
|
|
854
|
-
|
|
812
|
+
static convertAnthropicToolToOpenAI(anthropicTool) {
|
|
813
|
+
if (!anthropicTool || !anthropicTool.name) {
|
|
814
|
+
throw new Error("Invalid tool definition: missing name");
|
|
815
|
+
}
|
|
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
|
+
};
|
|
855
826
|
return {
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
errors: [],
|
|
863
|
-
usage: {
|
|
864
|
-
input_tokens: 0,
|
|
865
|
-
output_tokens: 0
|
|
866
|
-
},
|
|
867
|
-
thinkingBlockStarted: false,
|
|
868
|
-
contentBlockStarted: false
|
|
827
|
+
type: "function",
|
|
828
|
+
function: {
|
|
829
|
+
name: openaiName,
|
|
830
|
+
description,
|
|
831
|
+
parameters
|
|
832
|
+
}
|
|
869
833
|
};
|
|
870
834
|
}
|
|
871
835
|
/**
|
|
872
|
-
*
|
|
836
|
+
* 将OpenAI工具调用转换为Claude格式
|
|
873
837
|
*/
|
|
874
|
-
|
|
875
|
-
return
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
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
|
+
});
|
|
879
854
|
}
|
|
880
855
|
/**
|
|
881
|
-
*
|
|
856
|
+
* 检查是否为OpenAI工具格式
|
|
882
857
|
*/
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
"glm-4.5",
|
|
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;
|
|
897
|
-
}
|
|
898
|
-
const mapping = {
|
|
899
|
-
"claude-3-sonnet-20240229": "glm-4.5",
|
|
900
|
-
"claude-3-haiku-20240307": "kimi-k2",
|
|
901
|
-
"claude-3-opus-20240229": "deepseek-v3.1"
|
|
902
|
-
};
|
|
903
|
-
return mapping[model] || "glm-4.5";
|
|
858
|
+
static isOpenAIToolFormat(tool) {
|
|
859
|
+
return tool && tool.type === "function" && tool.function && tool.function.name;
|
|
904
860
|
}
|
|
905
861
|
/**
|
|
906
|
-
*
|
|
862
|
+
* 简化Claude的详细描述为OpenAI兼容的简短描述
|
|
907
863
|
*/
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
)
|
|
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;
|
|
912
871
|
}
|
|
913
872
|
/**
|
|
914
|
-
*
|
|
873
|
+
* 验证工具定义的有效性
|
|
915
874
|
*/
|
|
916
|
-
|
|
917
|
-
|
|
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);
|
|
879
|
+
}
|
|
880
|
+
if ("function" in tool) {
|
|
881
|
+
return !!(tool.type === "function" && tool.function?.name && tool.function?.parameters);
|
|
882
|
+
}
|
|
883
|
+
return false;
|
|
918
884
|
}
|
|
919
885
|
/**
|
|
920
|
-
*
|
|
886
|
+
* 获取工具名称(通用方法)
|
|
921
887
|
*/
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
888
|
+
static getToolName(tool) {
|
|
889
|
+
if ("name" in tool) {
|
|
890
|
+
return tool.name;
|
|
891
|
+
}
|
|
892
|
+
if ("function" in tool) {
|
|
893
|
+
return tool.function.name;
|
|
894
|
+
}
|
|
895
|
+
return TOOL_CONVERSION.UNKNOWN_TOOL_FALLBACK;
|
|
896
|
+
}
|
|
897
|
+
};
|
|
898
|
+
|
|
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();
|
|
931
906
|
}
|
|
932
907
|
/**
|
|
933
|
-
*
|
|
934
|
-
* 用于逐个处理流式数据片段
|
|
908
|
+
* 确保日志目录存在
|
|
935
909
|
*/
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
this.addFinalEvents(state, sseEvents);
|
|
941
|
-
return sseEvents;
|
|
910
|
+
ensureLogsDir() {
|
|
911
|
+
try {
|
|
912
|
+
(0, import_fs.mkdirSync)(this.logsDir, { recursive: true });
|
|
913
|
+
} catch (error) {
|
|
942
914
|
}
|
|
915
|
+
}
|
|
916
|
+
/**
|
|
917
|
+
* 记录验证错误
|
|
918
|
+
*/
|
|
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);
|
|
943
932
|
try {
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
return sseEvents;
|
|
933
|
+
(0, import_fs.writeFileSync)(filepath, JSON.stringify(logEntry, null, 2));
|
|
934
|
+
return filepath;
|
|
947
935
|
} catch (error) {
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
line: openaiDataLine.substring(0, 200),
|
|
951
|
-
error: error instanceof Error ? error.message : String(error)
|
|
952
|
-
});
|
|
953
|
-
}
|
|
954
|
-
return [];
|
|
936
|
+
console.warn(`\u26A0\uFE0F [ValidationLogger] Failed to write log file: ${filepath} (${error.code})`);
|
|
937
|
+
return "";
|
|
955
938
|
}
|
|
956
939
|
}
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
940
|
+
/**
|
|
941
|
+
* 提取错误详情
|
|
942
|
+
*/
|
|
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
|
+
}
|
|
987
970
|
}
|
|
988
|
-
};
|
|
989
|
-
var SUPPORTED_IMAGE_TYPES = [
|
|
990
|
-
"image/jpeg",
|
|
991
|
-
"image/png",
|
|
992
|
-
"image/gif",
|
|
993
|
-
"image/webp"
|
|
994
|
-
];
|
|
995
|
-
var TOOL_CONVERSION = {
|
|
996
971
|
/**
|
|
997
|
-
*
|
|
998
|
-
* 基于GitHub Copilot API测试结果,100%保持原始格式
|
|
972
|
+
* 查找缺失字段
|
|
999
973
|
*/
|
|
1000
|
-
|
|
974
|
+
findMissingFields(obj, requiredFields) {
|
|
975
|
+
return requiredFields.filter((field) => obj[field] === void 0);
|
|
976
|
+
}
|
|
1001
977
|
/**
|
|
1002
|
-
*
|
|
978
|
+
* 查找无效类型
|
|
1003
979
|
*/
|
|
1004
|
-
|
|
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;
|
|
986
|
+
}
|
|
1005
987
|
/**
|
|
1006
|
-
*
|
|
988
|
+
* 获取最近的验证错误日志路径
|
|
1007
989
|
*/
|
|
1008
|
-
|
|
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 [];
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1009
1003
|
};
|
|
1004
|
+
var validationLogger = new ValidationLogger();
|
|
1010
1005
|
|
|
1011
|
-
// src/core/a2o-request-adapter/
|
|
1012
|
-
var
|
|
1006
|
+
// src/core/a2o-request-adapter/format-validator.ts
|
|
1007
|
+
var FormatValidator = class {
|
|
1013
1008
|
/**
|
|
1014
|
-
*
|
|
1015
|
-
* 修复关键问题:将tool_use转换为tool_calls,tool_result转换为role:"tool"消息
|
|
1016
|
-
* 使用tool_use_id溯回工具名称解决unknown_tool问题
|
|
1009
|
+
* 验证Claude请求格式
|
|
1017
1010
|
*/
|
|
1018
|
-
static
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
|
-
}
|
|
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;
|
|
1026
1020
|
}
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
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;
|
|
1039
1058
|
}
|
|
1040
1059
|
}
|
|
1041
|
-
if (
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
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;
|
|
1047
1087
|
}
|
|
1048
1088
|
}
|
|
1049
1089
|
}
|
|
1050
|
-
if (
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
if (Array.isArray(msg.content)) {
|
|
1063
|
-
for (const item of msg.content) {
|
|
1064
|
-
if (typeof item === "object" && item !== null && item.type === "tool_use") {
|
|
1065
|
-
toolIdToNameMap.set(item.id, item.name);
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
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;
|
|
1068
1102
|
}
|
|
1069
1103
|
}
|
|
1070
|
-
return
|
|
1071
|
-
toolIdToNameMap,
|
|
1072
|
-
hasSystemMessage: false
|
|
1073
|
-
};
|
|
1104
|
+
return true;
|
|
1074
1105
|
}
|
|
1075
1106
|
/**
|
|
1076
|
-
*
|
|
1107
|
+
* 验证OpenAI请求格式
|
|
1077
1108
|
*/
|
|
1078
|
-
static
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
}
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
} else if (textContent.length > 0) {
|
|
1092
|
-
const textMessage = this.createTextMessage(msg.role, textContent);
|
|
1093
|
-
if (textMessage) {
|
|
1094
|
-
resultMessages.push(textMessage);
|
|
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;
|
|
1095
1122
|
}
|
|
1096
1123
|
}
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
const toolUses = [];
|
|
1105
|
-
const toolResults = [];
|
|
1106
|
-
for (const item of content) {
|
|
1107
|
-
if (typeof item === "string") {
|
|
1108
|
-
textContent.push({ type: "text", text: item });
|
|
1109
|
-
} else if (typeof item === "object" && item !== null) {
|
|
1110
|
-
switch (item.type) {
|
|
1111
|
-
case "text":
|
|
1112
|
-
textContent.push(item);
|
|
1113
|
-
break;
|
|
1114
|
-
case "tool_use":
|
|
1115
|
-
toolUses.push(item);
|
|
1116
|
-
break;
|
|
1117
|
-
case "tool_result":
|
|
1118
|
-
toolResults.push(item);
|
|
1119
|
-
break;
|
|
1120
|
-
case "image":
|
|
1121
|
-
const imageContent = this.convertImageContent(item);
|
|
1122
|
-
if (imageContent) {
|
|
1123
|
-
textContent.push(imageContent);
|
|
1124
|
-
}
|
|
1125
|
-
break;
|
|
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;
|
|
1126
1131
|
}
|
|
1127
1132
|
}
|
|
1128
1133
|
}
|
|
1129
|
-
return
|
|
1134
|
+
return true;
|
|
1130
1135
|
}
|
|
1131
1136
|
/**
|
|
1132
|
-
*
|
|
1137
|
+
* 验证Claude消息格式
|
|
1133
1138
|
*/
|
|
1134
|
-
static
|
|
1135
|
-
if (
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
return {
|
|
1142
|
-
type: "image_url",
|
|
1143
|
-
image_url: {
|
|
1144
|
-
url: dataUri
|
|
1145
|
-
}
|
|
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
1146
|
};
|
|
1147
|
+
const logPath2 = validationLogger.logValidationError("claude-message", errorInfo2);
|
|
1148
|
+
console.warn(`\u26A0\uFE0F [Validation] Message validation failed. Log: ${logPath2}`);
|
|
1149
|
+
return false;
|
|
1147
1150
|
}
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
};
|
|
1159
|
-
if (textContent.length > 0) {
|
|
1160
|
-
const textOnly = textContent.map((item) => item.text || "").join("");
|
|
1161
|
-
if (textOnly.trim()) {
|
|
1162
|
-
assistantMessage.content = textOnly.trim();
|
|
1163
|
-
}
|
|
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;
|
|
1164
1161
|
}
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
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;
|
|
1174
1187
|
}
|
|
1175
1188
|
/**
|
|
1176
|
-
*
|
|
1189
|
+
* 验证Claude内容块
|
|
1177
1190
|
*/
|
|
1178
|
-
static
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
if (
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
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;
|
|
1187
1204
|
}
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
role: "tool",
|
|
1191
|
-
tool_call_id: toolResult.tool_use_id,
|
|
1192
|
-
name: toolName,
|
|
1193
|
-
content: resultContent
|
|
1194
|
-
};
|
|
1195
|
-
});
|
|
1205
|
+
}
|
|
1206
|
+
return true;
|
|
1196
1207
|
}
|
|
1197
1208
|
/**
|
|
1198
|
-
*
|
|
1209
|
+
* 验证单个Claude内容块
|
|
1199
1210
|
*/
|
|
1200
|
-
static
|
|
1201
|
-
if (
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
};
|
|
1208
|
-
} else {
|
|
1209
|
-
const textOnly = textContent.map((item) => item.text || "").join("");
|
|
1210
|
-
return {
|
|
1211
|
-
role,
|
|
1212
|
-
content: textOnly.trim() || ""
|
|
1213
|
-
// 确保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
|
|
1214
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;
|
|
1215
1252
|
}
|
|
1216
1253
|
}
|
|
1217
1254
|
/**
|
|
1218
|
-
*
|
|
1255
|
+
* 验证图片块
|
|
1219
1256
|
*/
|
|
1220
|
-
static
|
|
1221
|
-
|
|
1222
|
-
if (
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
systemContent = system;
|
|
1231
|
-
}
|
|
1232
|
-
if (systemContent) {
|
|
1233
|
-
return {
|
|
1234
|
-
role: "system",
|
|
1235
|
-
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
|
|
1236
1267
|
};
|
|
1268
|
+
validationLogger.logValidationError("claude-content", errorInfo);
|
|
1237
1269
|
}
|
|
1238
|
-
return
|
|
1270
|
+
return isValid;
|
|
1239
1271
|
}
|
|
1240
|
-
};
|
|
1241
|
-
|
|
1242
|
-
// src/core/a2o-request-adapter/tool-converter.ts
|
|
1243
|
-
var ToolConverter = class {
|
|
1244
1272
|
/**
|
|
1245
|
-
*
|
|
1273
|
+
* 验证OpenAI消息格式
|
|
1246
1274
|
*/
|
|
1247
|
-
static
|
|
1248
|
-
if (!
|
|
1249
|
-
|
|
1275
|
+
static validateOpenAIMessage(message) {
|
|
1276
|
+
if (!message || !message.role) {
|
|
1277
|
+
return false;
|
|
1250
1278
|
}
|
|
1251
|
-
const
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
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;
|
|
1255
1282
|
}
|
|
1256
|
-
const
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
parameters
|
|
1267
|
-
}
|
|
1268
|
-
};
|
|
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;
|
|
1269
1293
|
}
|
|
1270
1294
|
/**
|
|
1271
|
-
*
|
|
1295
|
+
* 获取工具定义缺失字段
|
|
1272
1296
|
*/
|
|
1273
|
-
static
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
} catch (error) {
|
|
1280
|
-
parsedInput = { raw_arguments: toolCall.function.arguments };
|
|
1281
|
-
}
|
|
1282
|
-
return {
|
|
1283
|
-
type: "tool_use",
|
|
1284
|
-
id: toolCall.id,
|
|
1285
|
-
name: claudeToolName,
|
|
1286
|
-
input: parsedInput
|
|
1287
|
-
};
|
|
1288
|
-
});
|
|
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;
|
|
1289
1303
|
}
|
|
1290
1304
|
/**
|
|
1291
|
-
*
|
|
1305
|
+
* 验证Claude工具定义
|
|
1292
1306
|
*/
|
|
1293
|
-
static
|
|
1294
|
-
|
|
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;
|
|
1317
|
+
}
|
|
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);
|
|
1295
1340
|
}
|
|
1296
1341
|
/**
|
|
1297
|
-
*
|
|
1342
|
+
* 验证OpenAI工具定义
|
|
1298
1343
|
*/
|
|
1299
|
-
static
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
if (firstLine.length > maxLength) {
|
|
1303
|
-
return firstLine.substring(0, maxLength - 3) + "...";
|
|
1344
|
+
static validateOpenAIToolDefinition(tool) {
|
|
1345
|
+
if (!tool || tool.type !== "function") {
|
|
1346
|
+
return false;
|
|
1304
1347
|
}
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
/**
|
|
1308
|
-
* 验证工具定义的有效性
|
|
1309
|
-
*/
|
|
1310
|
-
static validateToolDefinition(tool) {
|
|
1311
|
-
if (!tool) return false;
|
|
1312
|
-
if ("input_schema" in tool) {
|
|
1313
|
-
return !!(tool.name && tool.input_schema && tool.input_schema.type);
|
|
1348
|
+
if (!tool.function || typeof tool.function.name !== "string") {
|
|
1349
|
+
return false;
|
|
1314
1350
|
}
|
|
1315
|
-
if (
|
|
1316
|
-
return
|
|
1351
|
+
if (!tool.function.parameters || typeof tool.function.parameters !== "object") {
|
|
1352
|
+
return false;
|
|
1317
1353
|
}
|
|
1318
|
-
return
|
|
1354
|
+
return ToolConverter.validateToolDefinition(tool);
|
|
1319
1355
|
}
|
|
1320
1356
|
/**
|
|
1321
|
-
*
|
|
1357
|
+
* 验证系统消息
|
|
1322
1358
|
*/
|
|
1323
|
-
static
|
|
1324
|
-
if ("
|
|
1325
|
-
return
|
|
1359
|
+
static validateSystemMessage(system) {
|
|
1360
|
+
if (typeof system === "string") {
|
|
1361
|
+
return true;
|
|
1326
1362
|
}
|
|
1327
|
-
if (
|
|
1328
|
-
return
|
|
1363
|
+
if (Array.isArray(system)) {
|
|
1364
|
+
return system.every(
|
|
1365
|
+
(item) => typeof item === "string" || typeof item === "object" && typeof item.text === "string"
|
|
1366
|
+
);
|
|
1329
1367
|
}
|
|
1330
|
-
return
|
|
1331
|
-
}
|
|
1332
|
-
};
|
|
1333
|
-
|
|
1334
|
-
// src/utils/validation-logger.ts
|
|
1335
|
-
var import_fs = require("fs");
|
|
1336
|
-
var import_path = require("path");
|
|
1337
|
-
var ValidationLogger = class {
|
|
1338
|
-
constructor(logsDir = "/app/logs/request-validation-errors") {
|
|
1339
|
-
this.logsDir = logsDir;
|
|
1340
|
-
this.ensureLogsDir();
|
|
1368
|
+
return false;
|
|
1341
1369
|
}
|
|
1342
1370
|
/**
|
|
1343
|
-
*
|
|
1371
|
+
* 验证转换结果
|
|
1344
1372
|
*/
|
|
1345
|
-
|
|
1373
|
+
static validateConversionResult(original, converted, direction) {
|
|
1346
1374
|
try {
|
|
1347
|
-
(
|
|
1375
|
+
if (direction === "claude-to-openai") {
|
|
1376
|
+
return this.validateOpenAIRequest(converted);
|
|
1377
|
+
} else {
|
|
1378
|
+
return this.validateClaudeRequest(converted);
|
|
1379
|
+
}
|
|
1348
1380
|
} catch (error) {
|
|
1381
|
+
console.warn(`\u26A0\uFE0F [Validation] Conversion result validation failed: ${error?.message || error}`);
|
|
1382
|
+
return false;
|
|
1349
1383
|
}
|
|
1350
1384
|
}
|
|
1351
1385
|
/**
|
|
1352
|
-
*
|
|
1386
|
+
* 获取验证错误详情
|
|
1353
1387
|
*/
|
|
1354
|
-
|
|
1355
|
-
const
|
|
1356
|
-
const requestId = context.requestId || `req-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
1357
|
-
const logEntry = {
|
|
1358
|
-
timestamp,
|
|
1359
|
-
requestId,
|
|
1360
|
-
type,
|
|
1361
|
-
model: context.model,
|
|
1362
|
-
validationData,
|
|
1363
|
-
errorDetails: this.extractErrorDetails(validationData, type)
|
|
1364
|
-
};
|
|
1365
|
-
const filename = `${type}-${requestId}.json`;
|
|
1366
|
-
const filepath = (0, import_path.join)(this.logsDir, filename);
|
|
1388
|
+
static getValidationErrors(request, type) {
|
|
1389
|
+
const errors = [];
|
|
1367
1390
|
try {
|
|
1368
|
-
|
|
1369
|
-
|
|
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
|
+
}
|
|
1370
1400
|
} catch (error) {
|
|
1371
|
-
|
|
1372
|
-
return "";
|
|
1373
|
-
}
|
|
1374
|
-
}
|
|
1375
|
-
/**
|
|
1376
|
-
* 提取错误详情
|
|
1377
|
-
*/
|
|
1378
|
-
extractErrorDetails(data, type) {
|
|
1379
|
-
switch (type) {
|
|
1380
|
-
case "claude-request":
|
|
1381
|
-
return {
|
|
1382
|
-
model: data.model,
|
|
1383
|
-
messagesCount: data.messages?.length,
|
|
1384
|
-
hasSystem: !!data.system,
|
|
1385
|
-
hasTools: !!data.tools,
|
|
1386
|
-
missingFields: this.findMissingFields(data, ["model", "messages"]),
|
|
1387
|
-
invalidTypes: this.findInvalidTypes(data)
|
|
1388
|
-
};
|
|
1389
|
-
case "claude-message":
|
|
1390
|
-
return {
|
|
1391
|
-
role: data.role,
|
|
1392
|
-
contentType: typeof data.content,
|
|
1393
|
-
missingFields: this.findMissingFields(data, ["role", "content"]),
|
|
1394
|
-
invalidRoles: ["user", "assistant", "system"].includes(data.role) ? null : data.role
|
|
1395
|
-
};
|
|
1396
|
-
case "claude-tool":
|
|
1397
|
-
return {
|
|
1398
|
-
name: data.name,
|
|
1399
|
-
description: data.description?.length,
|
|
1400
|
-
missingFields: this.findMissingFields(data, ["name", "description", "input_schema"])
|
|
1401
|
-
};
|
|
1402
|
-
default:
|
|
1403
|
-
return data;
|
|
1401
|
+
errors.push(`\u9A8C\u8BC1\u8FC7\u7A0B\u51FA\u9519: ${error.message}`);
|
|
1404
1402
|
}
|
|
1405
|
-
|
|
1406
|
-
/**
|
|
1407
|
-
* 查找缺失字段
|
|
1408
|
-
*/
|
|
1409
|
-
findMissingFields(obj, requiredFields) {
|
|
1410
|
-
return requiredFields.filter((field) => obj[field] === void 0);
|
|
1411
|
-
}
|
|
1412
|
-
/**
|
|
1413
|
-
* 查找无效类型
|
|
1414
|
-
*/
|
|
1415
|
-
findInvalidTypes(obj) {
|
|
1416
|
-
const invalidTypes = {};
|
|
1417
|
-
if (typeof obj.model !== "string") invalidTypes.model = typeof obj.model;
|
|
1418
|
-
if (!Array.isArray(obj.messages)) invalidTypes.messages = typeof obj.messages;
|
|
1419
|
-
if (obj.tools && !Array.isArray(obj.tools)) invalidTypes.tools = typeof obj.tools;
|
|
1420
|
-
return invalidTypes;
|
|
1403
|
+
return errors;
|
|
1421
1404
|
}
|
|
1422
1405
|
/**
|
|
1423
1406
|
* 获取最近的验证错误日志路径
|
|
1424
1407
|
*/
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
const fs = require("fs");
|
|
1428
|
-
const files = fs.readdirSync(this.logsDir).filter((f) => f.endsWith(".json")).sort((a, b) => {
|
|
1429
|
-
const aTime = fs.statSync((0, import_path.join)(this.logsDir, a)).mtime;
|
|
1430
|
-
const bTime = fs.statSync((0, import_path.join)(this.logsDir, b)).mtime;
|
|
1431
|
-
return bTime.getTime() - aTime.getTime();
|
|
1432
|
-
}).slice(0, limit).map((f) => (0, import_path.join)(this.logsDir, f));
|
|
1433
|
-
return files;
|
|
1434
|
-
} catch (error) {
|
|
1435
|
-
return [];
|
|
1436
|
-
}
|
|
1408
|
+
static getRecentValidationLogs(limit = 5) {
|
|
1409
|
+
return validationLogger.getRecentLogs(limit);
|
|
1437
1410
|
}
|
|
1438
|
-
};
|
|
1439
|
-
var validationLogger = new ValidationLogger();
|
|
1440
|
-
|
|
1441
|
-
// src/core/a2o-request-adapter/format-validator.ts
|
|
1442
|
-
var FormatValidator = class {
|
|
1443
1411
|
/**
|
|
1444
|
-
*
|
|
1412
|
+
* 验证并返回日志路径
|
|
1445
1413
|
*/
|
|
1446
|
-
static
|
|
1447
|
-
if (
|
|
1448
|
-
const
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
const logPath = validationLogger.logValidationError("claude-request", errorInfo, { model: "unknown" });
|
|
1453
|
-
console.warn(`\u26A0\uFE0F [Validation] Request validation failed. Log: ${logPath}`);
|
|
1454
|
-
return false;
|
|
1455
|
-
}
|
|
1456
|
-
if (typeof request.model !== "string") {
|
|
1457
|
-
const errorInfo = {
|
|
1458
|
-
error: "invalid_model_type",
|
|
1459
|
-
expected: "string",
|
|
1460
|
-
actual: typeof request.model,
|
|
1461
|
-
value: request.model
|
|
1462
|
-
};
|
|
1463
|
-
const logPath = validationLogger.logValidationError("claude-request", errorInfo, { model: String(request.model) });
|
|
1464
|
-
console.warn(`\u26A0\uFE0F [Validation] Request validation failed. Log: ${logPath}`);
|
|
1465
|
-
return false;
|
|
1466
|
-
}
|
|
1467
|
-
if (!Array.isArray(request.messages) || request.messages.length === 0) {
|
|
1468
|
-
const errorInfo = {
|
|
1469
|
-
error: "invalid_messages",
|
|
1470
|
-
isArray: Array.isArray(request.messages),
|
|
1471
|
-
length: request.messages?.length,
|
|
1472
|
-
actualType: typeof request.messages,
|
|
1473
|
-
messages: request.messages
|
|
1474
|
-
};
|
|
1475
|
-
const logPath = validationLogger.logValidationError("claude-request", errorInfo, { model: request.model });
|
|
1476
|
-
console.warn(`\u26A0\uFE0F [Validation] Request validation failed. Log: ${logPath}`);
|
|
1477
|
-
return false;
|
|
1478
|
-
}
|
|
1479
|
-
for (let i = 0; i < request.messages.length; i++) {
|
|
1480
|
-
const message = request.messages[i];
|
|
1481
|
-
if (!this.validateClaudeMessage(message)) {
|
|
1482
|
-
const errorInfo = {
|
|
1483
|
-
error: "invalid_message_at_index",
|
|
1484
|
-
index: i,
|
|
1485
|
-
message,
|
|
1486
|
-
role: message?.role,
|
|
1487
|
-
contentType: typeof message?.content,
|
|
1488
|
-
hasContent: message?.content !== void 0
|
|
1489
|
-
};
|
|
1490
|
-
const logPath = validationLogger.logValidationError("claude-message", errorInfo, { model: request.model });
|
|
1491
|
-
console.warn(`\u26A0\uFE0F [Validation] Request validation failed. Log: ${logPath}`);
|
|
1492
|
-
return false;
|
|
1493
|
-
}
|
|
1494
|
-
}
|
|
1495
|
-
if (request.tools) {
|
|
1496
|
-
if (!Array.isArray(request.tools)) {
|
|
1497
|
-
const errorInfo = {
|
|
1498
|
-
error: "invalid_tools_type",
|
|
1499
|
-
expected: "array",
|
|
1500
|
-
actual: typeof request.tools,
|
|
1501
|
-
tools: request.tools
|
|
1502
|
-
};
|
|
1503
|
-
const logPath = validationLogger.logValidationError("claude-tool", errorInfo, { model: request.model });
|
|
1504
|
-
console.warn(`\u26A0\uFE0F [Validation] Request validation failed. Log: ${logPath}`);
|
|
1505
|
-
return false;
|
|
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 };
|
|
1506
1420
|
}
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
index: i,
|
|
1513
|
-
tool,
|
|
1514
|
-
hasName: !!tool?.name,
|
|
1515
|
-
hasDescription: !!tool?.description,
|
|
1516
|
-
hasInputSchema: !!tool?.input_schema,
|
|
1517
|
-
missingFields: this.getMissingToolFields(tool)
|
|
1518
|
-
};
|
|
1519
|
-
const logPath = validationLogger.logValidationError("claude-tool", errorInfo, { model: request.model });
|
|
1520
|
-
console.warn(`\u26A0\uFE0F [Validation] Request validation failed. Log: ${logPath}`);
|
|
1521
|
-
return false;
|
|
1522
|
-
}
|
|
1523
|
-
}
|
|
1524
|
-
}
|
|
1525
|
-
if (request.system !== void 0) {
|
|
1526
|
-
if (!this.validateSystemMessage(request.system)) {
|
|
1527
|
-
const errorInfo = {
|
|
1528
|
-
error: "invalid_system_message",
|
|
1529
|
-
system: request.system,
|
|
1530
|
-
type: typeof request.system,
|
|
1531
|
-
isString: typeof request.system === "string",
|
|
1532
|
-
isArray: Array.isArray(request.system)
|
|
1533
|
-
};
|
|
1534
|
-
const logPath = validationLogger.logValidationError("claude-request", errorInfo, { model: request.model });
|
|
1535
|
-
console.warn(`\u26A0\uFE0F [Validation] Request validation failed. Log: ${logPath}`);
|
|
1536
|
-
return false;
|
|
1537
|
-
}
|
|
1538
|
-
}
|
|
1539
|
-
return true;
|
|
1540
|
-
}
|
|
1541
|
-
/**
|
|
1542
|
-
* 验证OpenAI请求格式
|
|
1543
|
-
*/
|
|
1544
|
-
static validateOpenAIRequest(request) {
|
|
1545
|
-
if (!request) {
|
|
1546
|
-
return false;
|
|
1547
|
-
}
|
|
1548
|
-
if (typeof request.model !== "string") {
|
|
1549
|
-
return false;
|
|
1550
|
-
}
|
|
1551
|
-
if (!Array.isArray(request.messages) || request.messages.length === 0) {
|
|
1552
|
-
return false;
|
|
1553
|
-
}
|
|
1554
|
-
for (const message of request.messages) {
|
|
1555
|
-
if (!this.validateOpenAIMessage(message)) {
|
|
1556
|
-
return false;
|
|
1557
|
-
}
|
|
1558
|
-
}
|
|
1559
|
-
if (request.tools) {
|
|
1560
|
-
if (!Array.isArray(request.tools)) {
|
|
1561
|
-
return false;
|
|
1562
|
-
}
|
|
1563
|
-
for (const tool of request.tools) {
|
|
1564
|
-
if (!this.validateOpenAIToolDefinition(tool)) {
|
|
1565
|
-
return false;
|
|
1566
|
-
}
|
|
1567
|
-
}
|
|
1568
|
-
}
|
|
1569
|
-
return true;
|
|
1570
|
-
}
|
|
1571
|
-
/**
|
|
1572
|
-
* 验证Claude消息格式
|
|
1573
|
-
*/
|
|
1574
|
-
static validateClaudeMessage(message) {
|
|
1575
|
-
if (!message || !message.role) {
|
|
1576
|
-
const errorInfo2 = {
|
|
1577
|
-
error: "missing_message_or_role",
|
|
1578
|
-
hasMessage: !!message,
|
|
1579
|
-
hasRole: !!message?.role,
|
|
1580
|
-
message
|
|
1581
|
-
};
|
|
1582
|
-
const logPath2 = validationLogger.logValidationError("claude-message", errorInfo2);
|
|
1583
|
-
console.warn(`\u26A0\uFE0F [Validation] Message validation failed. Log: ${logPath2}`);
|
|
1584
|
-
return false;
|
|
1585
|
-
}
|
|
1586
|
-
const validRoles = ["user", "assistant", "system"];
|
|
1587
|
-
if (!validRoles.includes(message.role)) {
|
|
1588
|
-
const errorInfo2 = {
|
|
1589
|
-
error: "invalid_role",
|
|
1590
|
-
role: message.role,
|
|
1591
|
-
validRoles
|
|
1592
|
-
};
|
|
1593
|
-
const logPath2 = validationLogger.logValidationError("claude-message", errorInfo2);
|
|
1594
|
-
console.warn(`\u26A0\uFE0F [Validation] Message role validation failed. Log: ${logPath2}`);
|
|
1595
|
-
return false;
|
|
1596
|
-
}
|
|
1597
|
-
if (message.content === void 0) {
|
|
1598
|
-
const errorInfo2 = {
|
|
1599
|
-
error: "content_is_undefined",
|
|
1600
|
-
message,
|
|
1601
|
-
role: message.role
|
|
1602
|
-
};
|
|
1603
|
-
const logPath2 = validationLogger.logValidationError("claude-message", errorInfo2);
|
|
1604
|
-
console.warn(`\u26A0\uFE0F [Validation] Message content validation failed. Log: ${logPath2}`);
|
|
1605
|
-
return false;
|
|
1606
|
-
}
|
|
1607
|
-
if (typeof message.content === "string") {
|
|
1608
|
-
return true;
|
|
1609
|
-
}
|
|
1610
|
-
if (Array.isArray(message.content)) {
|
|
1611
|
-
return this.validateClaudeContentBlocks(message.content);
|
|
1612
|
-
}
|
|
1613
|
-
const errorInfo = {
|
|
1614
|
-
error: "invalid_content_type",
|
|
1615
|
-
contentType: typeof message.content,
|
|
1616
|
-
content: message.content,
|
|
1617
|
-
role: message.role
|
|
1618
|
-
};
|
|
1619
|
-
const logPath = validationLogger.logValidationError("claude-content", errorInfo);
|
|
1620
|
-
console.warn(`\u26A0\uFE0F [Validation] Message content type validation failed. Log: ${logPath}`);
|
|
1621
|
-
return false;
|
|
1622
|
-
}
|
|
1623
|
-
/**
|
|
1624
|
-
* 验证Claude内容块
|
|
1625
|
-
*/
|
|
1626
|
-
static validateClaudeContentBlocks(content) {
|
|
1627
|
-
for (let i = 0; i < content.length; i++) {
|
|
1628
|
-
const block = content[i];
|
|
1629
|
-
if (!this.validateClaudeContentBlock(block)) {
|
|
1630
|
-
const errorInfo = {
|
|
1631
|
-
error: "invalid_content_block",
|
|
1632
|
-
index: i,
|
|
1633
|
-
block,
|
|
1634
|
-
type: block?.type,
|
|
1635
|
-
hasType: !!block?.type
|
|
1636
|
-
};
|
|
1637
|
-
validationLogger.logValidationError("claude-content", errorInfo);
|
|
1638
|
-
return false;
|
|
1639
|
-
}
|
|
1640
|
-
}
|
|
1641
|
-
return true;
|
|
1642
|
-
}
|
|
1643
|
-
/**
|
|
1644
|
-
* 验证单个Claude内容块
|
|
1645
|
-
*/
|
|
1646
|
-
static validateClaudeContentBlock(block) {
|
|
1647
|
-
if (!block || !block.type) {
|
|
1648
|
-
const errorInfo = {
|
|
1649
|
-
error: "missing_block_or_type",
|
|
1650
|
-
hasBlock: !!block,
|
|
1651
|
-
hasType: !!block?.type,
|
|
1652
|
-
block
|
|
1653
|
-
};
|
|
1654
|
-
validationLogger.logValidationError("claude-content", errorInfo);
|
|
1655
|
-
return false;
|
|
1656
|
-
}
|
|
1657
|
-
switch (block.type) {
|
|
1658
|
-
case "text":
|
|
1659
|
-
if (typeof block.text !== "string") {
|
|
1660
|
-
const errorInfo2 = {
|
|
1661
|
-
error: "invalid_text_content",
|
|
1662
|
-
type: block.type,
|
|
1663
|
-
textType: typeof block.text,
|
|
1664
|
-
text: block.text
|
|
1665
|
-
};
|
|
1666
|
-
validationLogger.logValidationError("claude-content", errorInfo2);
|
|
1667
|
-
return false;
|
|
1668
|
-
}
|
|
1669
|
-
return true;
|
|
1670
|
-
case "thinking":
|
|
1671
|
-
return typeof block.thinking === "string";
|
|
1672
|
-
case "tool_use":
|
|
1673
|
-
return !!(block.id && block.name && block.input !== void 0);
|
|
1674
|
-
case "tool_result":
|
|
1675
|
-
return !!(block.tool_use_id && block.content !== void 0);
|
|
1676
|
-
case "image":
|
|
1677
|
-
return this.validateImageBlock(block);
|
|
1678
|
-
default:
|
|
1679
|
-
const errorInfo = {
|
|
1680
|
-
error: "unknown_block_type",
|
|
1681
|
-
type: block.type,
|
|
1682
|
-
validTypes: ["text", "thinking", "tool_use", "tool_result", "image"],
|
|
1683
|
-
block
|
|
1684
|
-
};
|
|
1685
|
-
validationLogger.logValidationError("claude-content", errorInfo);
|
|
1686
|
-
return false;
|
|
1687
|
-
}
|
|
1688
|
-
}
|
|
1689
|
-
/**
|
|
1690
|
-
* 验证图片块
|
|
1691
|
-
*/
|
|
1692
|
-
static validateImageBlock(block) {
|
|
1693
|
-
const isValid = !!(block.source && block.source.type === "base64" && block.source.data && block.source.media_type);
|
|
1694
|
-
if (!isValid) {
|
|
1695
|
-
const errorInfo = {
|
|
1696
|
-
error: "invalid_image_block",
|
|
1697
|
-
hasSource: !!block.source,
|
|
1698
|
-
sourceType: block.source?.type,
|
|
1699
|
-
hasData: !!block.source?.data,
|
|
1700
|
-
hasMediaType: !!block.source?.media_type,
|
|
1701
|
-
block
|
|
1702
|
-
};
|
|
1703
|
-
validationLogger.logValidationError("claude-content", errorInfo);
|
|
1704
|
-
}
|
|
1705
|
-
return isValid;
|
|
1706
|
-
}
|
|
1707
|
-
/**
|
|
1708
|
-
* 验证OpenAI消息格式
|
|
1709
|
-
*/
|
|
1710
|
-
static validateOpenAIMessage(message) {
|
|
1711
|
-
if (!message || !message.role) {
|
|
1712
|
-
return false;
|
|
1713
|
-
}
|
|
1714
|
-
const validRoles = ["user", "assistant", "system", "tool"];
|
|
1715
|
-
if (!validRoles.includes(message.role)) {
|
|
1716
|
-
return false;
|
|
1717
|
-
}
|
|
1718
|
-
const hasContent = message.content !== void 0;
|
|
1719
|
-
const hasToolCalls = Array.isArray(message.tool_calls) && message.tool_calls.length > 0;
|
|
1720
|
-
const hasToolCallId = message.role === "tool" && message.tool_call_id;
|
|
1721
|
-
if (message.role === "assistant") {
|
|
1722
|
-
return hasContent || hasToolCalls;
|
|
1723
|
-
}
|
|
1724
|
-
if (message.role === "tool") {
|
|
1725
|
-
return hasToolCallId && hasContent;
|
|
1726
|
-
}
|
|
1727
|
-
return hasContent;
|
|
1728
|
-
}
|
|
1729
|
-
/**
|
|
1730
|
-
* 获取工具定义缺失字段
|
|
1731
|
-
*/
|
|
1732
|
-
static getMissingToolFields(tool) {
|
|
1733
|
-
const missing = [];
|
|
1734
|
-
if (!tool?.name) missing.push("name");
|
|
1735
|
-
if (!tool?.description) missing.push("description");
|
|
1736
|
-
if (!tool?.input_schema) missing.push("input_schema");
|
|
1737
|
-
return missing;
|
|
1738
|
-
}
|
|
1739
|
-
/**
|
|
1740
|
-
* 验证Claude工具定义
|
|
1741
|
-
*/
|
|
1742
|
-
static validateClaudeToolDefinition(tool) {
|
|
1743
|
-
if (!tool || typeof tool.name !== "string") {
|
|
1744
|
-
const errorInfo = {
|
|
1745
|
-
error: "invalid_tool_name",
|
|
1746
|
-
hasTool: !!tool,
|
|
1747
|
-
nameType: typeof tool?.name,
|
|
1748
|
-
name: tool?.name
|
|
1749
|
-
};
|
|
1750
|
-
validationLogger.logValidationError("claude-tool", errorInfo);
|
|
1751
|
-
return false;
|
|
1752
|
-
}
|
|
1753
|
-
if (!tool.input_schema || typeof tool.input_schema !== "object") {
|
|
1754
|
-
const errorInfo = {
|
|
1755
|
-
error: "invalid_input_schema",
|
|
1756
|
-
hasInputSchema: !!tool.input_schema,
|
|
1757
|
-
schemaType: typeof tool.input_schema,
|
|
1758
|
-
tool
|
|
1759
|
-
};
|
|
1760
|
-
validationLogger.logValidationError("claude-tool", errorInfo);
|
|
1761
|
-
return false;
|
|
1762
|
-
}
|
|
1763
|
-
const schema = tool.input_schema;
|
|
1764
|
-
if (!schema.type || !schema.properties) {
|
|
1765
|
-
const errorInfo = {
|
|
1766
|
-
error: "invalid_schema_structure",
|
|
1767
|
-
hasType: !!schema.type,
|
|
1768
|
-
hasProperties: !!schema.properties,
|
|
1769
|
-
schema
|
|
1770
|
-
};
|
|
1771
|
-
validationLogger.logValidationError("claude-tool", errorInfo);
|
|
1772
|
-
return false;
|
|
1773
|
-
}
|
|
1774
|
-
return ToolConverter.validateToolDefinition(tool);
|
|
1775
|
-
}
|
|
1776
|
-
/**
|
|
1777
|
-
* 验证OpenAI工具定义
|
|
1778
|
-
*/
|
|
1779
|
-
static validateOpenAIToolDefinition(tool) {
|
|
1780
|
-
if (!tool || tool.type !== "function") {
|
|
1781
|
-
return false;
|
|
1782
|
-
}
|
|
1783
|
-
if (!tool.function || typeof tool.function.name !== "string") {
|
|
1784
|
-
return false;
|
|
1785
|
-
}
|
|
1786
|
-
if (!tool.function.parameters || typeof tool.function.parameters !== "object") {
|
|
1787
|
-
return false;
|
|
1788
|
-
}
|
|
1789
|
-
return ToolConverter.validateToolDefinition(tool);
|
|
1790
|
-
}
|
|
1791
|
-
/**
|
|
1792
|
-
* 验证系统消息
|
|
1793
|
-
*/
|
|
1794
|
-
static validateSystemMessage(system) {
|
|
1795
|
-
if (typeof system === "string") {
|
|
1796
|
-
return true;
|
|
1797
|
-
}
|
|
1798
|
-
if (Array.isArray(system)) {
|
|
1799
|
-
return system.every(
|
|
1800
|
-
(item) => typeof item === "string" || typeof item === "object" && typeof item.text === "string"
|
|
1801
|
-
);
|
|
1802
|
-
}
|
|
1803
|
-
return false;
|
|
1804
|
-
}
|
|
1805
|
-
/**
|
|
1806
|
-
* 验证转换结果
|
|
1807
|
-
*/
|
|
1808
|
-
static validateConversionResult(original, converted, direction) {
|
|
1809
|
-
try {
|
|
1810
|
-
if (direction === "claude-to-openai") {
|
|
1811
|
-
return this.validateOpenAIRequest(converted);
|
|
1812
|
-
} else {
|
|
1813
|
-
return this.validateClaudeRequest(converted);
|
|
1814
|
-
}
|
|
1815
|
-
} catch (error) {
|
|
1816
|
-
console.warn(`\u26A0\uFE0F [Validation] Conversion result validation failed: ${error?.message || error}`);
|
|
1817
|
-
return false;
|
|
1818
|
-
}
|
|
1819
|
-
}
|
|
1820
|
-
/**
|
|
1821
|
-
* 获取验证错误详情
|
|
1822
|
-
*/
|
|
1823
|
-
static getValidationErrors(request, type) {
|
|
1824
|
-
const errors = [];
|
|
1825
|
-
try {
|
|
1826
|
-
if (type === "claude") {
|
|
1827
|
-
if (!this.validateClaudeRequest(request)) {
|
|
1828
|
-
errors.push("Claude\u8BF7\u6C42\u683C\u5F0F\u9A8C\u8BC1\u5931\u8D25");
|
|
1829
|
-
}
|
|
1830
|
-
} else {
|
|
1831
|
-
if (!this.validateOpenAIRequest(request)) {
|
|
1832
|
-
errors.push("OpenAI\u8BF7\u6C42\u683C\u5F0F\u9A8C\u8BC1\u5931\u8D25");
|
|
1833
|
-
}
|
|
1834
|
-
}
|
|
1835
|
-
} catch (error) {
|
|
1836
|
-
errors.push(`\u9A8C\u8BC1\u8FC7\u7A0B\u51FA\u9519: ${error.message}`);
|
|
1837
|
-
}
|
|
1838
|
-
return errors;
|
|
1839
|
-
}
|
|
1840
|
-
/**
|
|
1841
|
-
* 获取最近的验证错误日志路径
|
|
1842
|
-
*/
|
|
1843
|
-
static getRecentValidationLogs(limit = 5) {
|
|
1844
|
-
return validationLogger.getRecentLogs(limit);
|
|
1845
|
-
}
|
|
1846
|
-
/**
|
|
1847
|
-
* 验证并返回日志路径
|
|
1848
|
-
*/
|
|
1849
|
-
static validateWithLogPath(request, type) {
|
|
1850
|
-
if (type === "claude") {
|
|
1851
|
-
const isValid = this.validateClaudeRequest(request);
|
|
1852
|
-
if (!isValid) {
|
|
1853
|
-
const logPath = validationLogger.getRecentLogs(1)[0];
|
|
1854
|
-
return { valid: false, logPath };
|
|
1855
|
-
}
|
|
1856
|
-
} else {
|
|
1857
|
-
const isValid = this.validateOpenAIRequest(request);
|
|
1858
|
-
if (!isValid) {
|
|
1859
|
-
const logPath = validationLogger.getRecentLogs(1)[0];
|
|
1860
|
-
return { valid: false, logPath };
|
|
1421
|
+
} else {
|
|
1422
|
+
const isValid = this.validateOpenAIRequest(request);
|
|
1423
|
+
if (!isValid) {
|
|
1424
|
+
const logPath = validationLogger.getRecentLogs(1)[0];
|
|
1425
|
+
return { valid: false, logPath };
|
|
1861
1426
|
}
|
|
1862
1427
|
}
|
|
1863
1428
|
return { valid: true };
|
|
@@ -3150,1063 +2715,1763 @@ var REQUEST_HEALING_STRATEGIES = [
|
|
|
3150
2715
|
}),
|
|
3151
2716
|
priority: 5
|
|
3152
2717
|
},
|
|
3153
|
-
// 停止序列修复
|
|
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
|
+
// 数据类型修复
|
|
3154
2930
|
{
|
|
3155
|
-
id: "
|
|
3156
|
-
name: "
|
|
3157
|
-
description: "
|
|
3158
|
-
condition: (data
|
|
3159
|
-
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("["));
|
|
3160
2936
|
},
|
|
3161
|
-
fix: (data
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
stop: Array.isArray(stop_sequences) ? stop_sequences.slice(0, 4) : [stop_sequences]
|
|
3167
|
-
};
|
|
3168
|
-
} else {
|
|
3169
|
-
const { stop, ...rest } = data;
|
|
3170
|
-
return {
|
|
3171
|
-
...rest,
|
|
3172
|
-
stop_sequences: Array.isArray(stop) ? stop : [stop]
|
|
3173
|
-
};
|
|
2937
|
+
fix: (data) => {
|
|
2938
|
+
try {
|
|
2939
|
+
return JSON.parse(data);
|
|
2940
|
+
} catch {
|
|
2941
|
+
return { error: "Failed to parse JSON string", original: data };
|
|
3174
2942
|
}
|
|
3175
2943
|
},
|
|
3176
|
-
priority:
|
|
2944
|
+
priority: 9
|
|
3177
2945
|
}
|
|
3178
2946
|
];
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
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) {
|
|
3190
2982
|
return {
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
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
|
+
};
|
|
3196
3159
|
}
|
|
3197
|
-
}
|
|
3198
|
-
|
|
3199
|
-
|
|
3160
|
+
}
|
|
3161
|
+
return { retryRecommended: false, reason: "Healing failed" };
|
|
3162
|
+
}
|
|
3200
3163
|
},
|
|
3201
|
-
//
|
|
3164
|
+
// 模型不可用恢复
|
|
3202
3165
|
{
|
|
3203
|
-
id: "
|
|
3204
|
-
name: "
|
|
3205
|
-
description: "
|
|
3206
|
-
|
|
3207
|
-
|
|
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);
|
|
3208
3174
|
},
|
|
3209
|
-
|
|
3210
|
-
const
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
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" };
|
|
3223
3189
|
}
|
|
3190
|
+
const fallbackModel = fallbacks[context.attemptCount];
|
|
3224
3191
|
return {
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3192
|
+
retryRecommended: true,
|
|
3193
|
+
healedData: { ...data, model: fallbackModel },
|
|
3194
|
+
appliedFixes: [`Model fallback: ${data.model} \u2192 ${fallbackModel}`]
|
|
3228
3195
|
};
|
|
3229
|
-
}
|
|
3230
|
-
priority: 7
|
|
3196
|
+
}
|
|
3231
3197
|
},
|
|
3232
|
-
//
|
|
3198
|
+
// 认证错误恢复
|
|
3233
3199
|
{
|
|
3234
|
-
id: "
|
|
3235
|
-
name: "
|
|
3236
|
-
description: "
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
const content = [];
|
|
3243
|
-
if (message.content) {
|
|
3244
|
-
content.push({
|
|
3245
|
-
type: "text",
|
|
3246
|
-
text: message.content
|
|
3247
|
-
});
|
|
3248
|
-
}
|
|
3249
|
-
if (message.tool_calls) {
|
|
3250
|
-
message.tool_calls.forEach((toolCall) => {
|
|
3251
|
-
content.push({
|
|
3252
|
-
type: "tool_use",
|
|
3253
|
-
id: toolCall.id,
|
|
3254
|
-
name: toolCall.function.name,
|
|
3255
|
-
input: JSON.parse(toolCall.function.arguments || "{}")
|
|
3256
|
-
});
|
|
3257
|
-
});
|
|
3258
|
-
}
|
|
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) => {
|
|
3259
3208
|
return {
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
role: "assistant",
|
|
3263
|
-
model: data.model,
|
|
3264
|
-
content,
|
|
3265
|
-
stop_reason: data.stop_reason || "end_turn",
|
|
3266
|
-
stop_sequence: null,
|
|
3267
|
-
usage: data.usage
|
|
3209
|
+
retryRecommended: false,
|
|
3210
|
+
reason: "Authentication refresh not implemented - check API keys"
|
|
3268
3211
|
};
|
|
3269
|
-
}
|
|
3270
|
-
priority: 9
|
|
3212
|
+
}
|
|
3271
3213
|
}
|
|
3272
3214
|
];
|
|
3273
|
-
var
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
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;
|
|
3285
3248
|
}
|
|
3286
3249
|
try {
|
|
3287
|
-
|
|
3288
|
-
|
|
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);
|
|
3289
3356
|
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
},
|
|
3295
|
-
priority: 8
|
|
3296
|
-
},
|
|
3297
|
-
// 事件类型修复
|
|
3298
|
-
{
|
|
3299
|
-
id: "fix-stream-event-type",
|
|
3300
|
-
name: "Fix Stream Event Type",
|
|
3301
|
-
description: "Add missing event type to stream events",
|
|
3302
|
-
condition: (data, ctx) => {
|
|
3303
|
-
return ctx.stage === "stream" && typeof data === "object" && !data?.type;
|
|
3304
|
-
},
|
|
3305
|
-
fix: (data, ctx) => {
|
|
3306
|
-
if (data.choices) {
|
|
3307
|
-
return { ...data, object: "chat.completion.chunk" };
|
|
3308
|
-
} else if (data.delta) {
|
|
3309
|
-
return { ...data, type: "content_block_delta" };
|
|
3310
|
-
} else {
|
|
3311
|
-
return { ...data, type: "message_start" };
|
|
3312
|
-
}
|
|
3313
|
-
},
|
|
3314
|
-
priority: 6
|
|
3357
|
+
// src/core/a2o-request-adapter/adapter.ts
|
|
3358
|
+
var A2ORequestAdapter = class {
|
|
3359
|
+
constructor(config = {}) {
|
|
3360
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
3315
3361
|
}
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
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;
|
|
3347
3414
|
}
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
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;
|
|
3355
3432
|
}
|
|
3356
|
-
seen.add(value);
|
|
3357
3433
|
}
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
return cleanData;
|
|
3361
|
-
},
|
|
3362
|
-
priority: 10
|
|
3363
|
-
},
|
|
3364
|
-
// 数据类型修复
|
|
3365
|
-
{
|
|
3366
|
-
id: "emergency-wrong-type",
|
|
3367
|
-
name: "Emergency Wrong Type Fix",
|
|
3368
|
-
description: "Fix fundamental type errors",
|
|
3369
|
-
condition: (data) => {
|
|
3370
|
-
return typeof data === "string" && (data.startsWith("{") || data.startsWith("["));
|
|
3371
|
-
},
|
|
3372
|
-
fix: (data) => {
|
|
3373
|
-
try {
|
|
3374
|
-
return JSON.parse(data);
|
|
3375
|
-
} catch {
|
|
3376
|
-
return { error: "Failed to parse JSON string", original: data };
|
|
3434
|
+
} else {
|
|
3435
|
+
result.data = openaiRequest;
|
|
3377
3436
|
}
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
]
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
}
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
const testContext = {
|
|
3396
|
-
direction: context.direction || "a2o",
|
|
3397
|
-
stage: context.stage || "request",
|
|
3398
|
-
originalData: testData,
|
|
3399
|
-
attemptCount: 0,
|
|
3400
|
-
maxAttempts: 3
|
|
3401
|
-
};
|
|
3402
|
-
return true;
|
|
3403
|
-
} catch {
|
|
3404
|
-
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;
|
|
3405
3454
|
}
|
|
3406
|
-
}
|
|
3407
|
-
}
|
|
3408
|
-
|
|
3409
|
-
// src/core/healing/error-recovery.ts
|
|
3410
|
-
var ErrorDetector = class {
|
|
3455
|
+
}
|
|
3411
3456
|
/**
|
|
3412
|
-
*
|
|
3457
|
+
* 执行核心转换逻辑(原有逻辑保持不变)
|
|
3413
3458
|
*/
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
};
|
|
3425
|
-
}
|
|
3426
|
-
if (typeof error === "object" && error && "status" in error) {
|
|
3427
|
-
const httpError = error;
|
|
3428
|
-
return {
|
|
3429
|
-
type: this.classifyHttpError(httpError.status),
|
|
3430
|
-
severity: this.assessHttpSeverity(httpError.status),
|
|
3431
|
-
message: httpError.message || `HTTP ${httpError.status}`,
|
|
3432
|
-
code: httpError.status,
|
|
3433
|
-
context,
|
|
3434
|
-
timestamp
|
|
3435
|
-
};
|
|
3436
|
-
}
|
|
3437
|
-
if (typeof error === "string") {
|
|
3438
|
-
return {
|
|
3439
|
-
type: this.classifyErrorMessage(error),
|
|
3440
|
-
severity: this.assessMessageSeverity(error),
|
|
3441
|
-
message: error,
|
|
3442
|
-
context,
|
|
3443
|
-
timestamp
|
|
3444
|
-
};
|
|
3445
|
-
}
|
|
3446
|
-
return {
|
|
3447
|
-
type: "unknown_error",
|
|
3448
|
-
severity: "medium",
|
|
3449
|
-
message: JSON.stringify(error),
|
|
3450
|
-
context,
|
|
3451
|
-
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
|
|
3452
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;
|
|
3453
3480
|
}
|
|
3454
3481
|
/**
|
|
3455
|
-
*
|
|
3482
|
+
* 转换Anthropic请求格式为OpenAI兼容格式 - 原有方法保持兼容
|
|
3456
3483
|
*/
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
return "validation_error";
|
|
3461
|
-
}
|
|
3462
|
-
if (message.includes("timeout")) {
|
|
3463
|
-
return "timeout_error";
|
|
3484
|
+
convertAnthropicRequestToOpenAI(anthropicRequest) {
|
|
3485
|
+
if (this.config.enableFormatValidation) {
|
|
3486
|
+
FormatValidator.validateClaudeRequest(anthropicRequest);
|
|
3464
3487
|
}
|
|
3465
|
-
|
|
3466
|
-
|
|
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);
|
|
3467
3498
|
}
|
|
3468
|
-
|
|
3469
|
-
|
|
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
|
+
}
|
|
3470
3504
|
}
|
|
3471
|
-
if (
|
|
3472
|
-
|
|
3505
|
+
if (this.config.enableFormatValidation && !FormatValidator.validateOpenAIRequest(openaiRequest)) {
|
|
3506
|
+
throw new Error("Generated OpenAI request format is invalid");
|
|
3473
3507
|
}
|
|
3474
|
-
|
|
3475
|
-
|
|
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
|
+
});
|
|
3476
3521
|
}
|
|
3477
|
-
if (message
|
|
3478
|
-
|
|
3522
|
+
if (message?.tool_calls) {
|
|
3523
|
+
const toolUseContents = ToolConverter.convertOpenAIToolCallsToClaude(message.tool_calls);
|
|
3524
|
+
claudeContent.push(...toolUseContents);
|
|
3479
3525
|
}
|
|
3480
|
-
|
|
3526
|
+
const claudeResponse = {
|
|
3527
|
+
role: "assistant",
|
|
3528
|
+
content: claudeContent
|
|
3529
|
+
};
|
|
3530
|
+
return claudeResponse;
|
|
3481
3531
|
}
|
|
3482
3532
|
/**
|
|
3483
|
-
*
|
|
3533
|
+
* 转换工具定义列表
|
|
3484
3534
|
*/
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
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
|
+
});
|
|
3492
3543
|
}
|
|
3493
3544
|
/**
|
|
3494
|
-
*
|
|
3545
|
+
* 验证Claude请求格式
|
|
3495
3546
|
*/
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
if (message.includes("critical") || message.includes("fatal")) return "critical";
|
|
3499
|
-
if (message.includes("timeout") || message.includes("network")) return "high";
|
|
3500
|
-
if (message.includes("validation") || message.includes("auth")) return "medium";
|
|
3501
|
-
return "low";
|
|
3547
|
+
validateClaudeRequest(request) {
|
|
3548
|
+
return FormatValidator.validateClaudeRequest(request);
|
|
3502
3549
|
}
|
|
3503
3550
|
/**
|
|
3504
|
-
*
|
|
3551
|
+
* 验证OpenAI请求格式
|
|
3505
3552
|
*/
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
if (status === 429 || status === 408 || status === 504) return "high";
|
|
3509
|
-
if (status === 401 || status === 403) return "medium";
|
|
3510
|
-
return "low";
|
|
3553
|
+
validateOpenAIRequest(request) {
|
|
3554
|
+
return FormatValidator.validateOpenAIRequest(request);
|
|
3511
3555
|
}
|
|
3512
3556
|
/**
|
|
3513
|
-
*
|
|
3557
|
+
* 获取支持的工具列表
|
|
3514
3558
|
*/
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
if (lowerMessage.includes("validation")) return "validation_error";
|
|
3518
|
-
if (lowerMessage.includes("timeout")) return "timeout_error";
|
|
3519
|
-
if (lowerMessage.includes("network")) return "network_error";
|
|
3520
|
-
if (lowerMessage.includes("auth")) return "auth_error";
|
|
3521
|
-
if (lowerMessage.includes("rate")) return "rate_limit_error";
|
|
3522
|
-
if (lowerMessage.includes("quota")) return "quota_error";
|
|
3523
|
-
return "unknown_error";
|
|
3559
|
+
getSupportedTools() {
|
|
3560
|
+
return [];
|
|
3524
3561
|
}
|
|
3525
3562
|
/**
|
|
3526
|
-
*
|
|
3563
|
+
* 检查工具是否支持
|
|
3527
3564
|
*/
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
if (lowerMessage.includes("critical") || lowerMessage.includes("fatal")) return "critical";
|
|
3531
|
-
if (lowerMessage.includes("error")) return "high";
|
|
3532
|
-
if (lowerMessage.includes("warning")) return "medium";
|
|
3533
|
-
return "low";
|
|
3565
|
+
isToolSupported(_toolName) {
|
|
3566
|
+
return true;
|
|
3534
3567
|
}
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
{
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
id: "validation-healing",
|
|
3569
|
-
name: "Validation Error Healing",
|
|
3570
|
-
description: "Attempt to fix validation errors automatically",
|
|
3571
|
-
supportedErrors: ["validation_error"],
|
|
3572
|
-
maxRetries: 2,
|
|
3573
|
-
backoffMs: 0,
|
|
3574
|
-
condition: (error) => error.type === "validation_error",
|
|
3575
|
-
recover: async (error, context) => {
|
|
3576
|
-
const { protocolHealer: protocolHealer3 } = await Promise.resolve().then(() => (init_protocol_healer(), protocol_healer_exports));
|
|
3577
|
-
if (context.direction === "a2o") {
|
|
3578
|
-
const healResult = await protocolHealer3.healA2ORequest(context.originalData);
|
|
3579
|
-
if (healResult.success) {
|
|
3580
|
-
return {
|
|
3581
|
-
retryRecommended: true,
|
|
3582
|
-
healedData: healResult.data,
|
|
3583
|
-
appliedFixes: healResult.appliedFixes
|
|
3584
|
-
};
|
|
3585
|
-
}
|
|
3586
|
-
} else if (context.direction === "o2a") {
|
|
3587
|
-
const healResult = await protocolHealer3.healO2ARequest(context.originalData);
|
|
3588
|
-
if (healResult.success) {
|
|
3589
|
-
return {
|
|
3590
|
-
retryRecommended: true,
|
|
3591
|
-
healedData: healResult.data,
|
|
3592
|
-
appliedFixes: healResult.appliedFixes
|
|
3593
|
-
};
|
|
3594
|
-
}
|
|
3595
|
-
}
|
|
3596
|
-
return { retryRecommended: false, reason: "Healing failed" };
|
|
3597
|
-
}
|
|
3598
|
-
},
|
|
3599
|
-
// 模型不可用恢复
|
|
3600
|
-
{
|
|
3601
|
-
id: "model-fallback",
|
|
3602
|
-
name: "Model Fallback",
|
|
3603
|
-
description: "Fallback to alternative models when primary model is unavailable",
|
|
3604
|
-
supportedErrors: ["server_error", "client_error"],
|
|
3605
|
-
maxRetries: 2,
|
|
3606
|
-
backoffMs: 0,
|
|
3607
|
-
condition: (error) => {
|
|
3608
|
-
return error.message.toLowerCase().includes("model") && ["server_error", "client_error"].includes(error.type);
|
|
3609
|
-
},
|
|
3610
|
-
recover: async (error, context) => {
|
|
3611
|
-
const data = context.originalData;
|
|
3612
|
-
if (!data?.model) {
|
|
3613
|
-
return { retryRecommended: false, reason: "No model specified" };
|
|
3614
|
-
}
|
|
3615
|
-
const fallbackMappings = {
|
|
3616
|
-
"gpt-4": ["gpt-4-turbo", "gpt-3.5-turbo"],
|
|
3617
|
-
"gpt-4-turbo": ["gpt-3.5-turbo", "gpt-3.5-turbo-16k"],
|
|
3618
|
-
"claude-3-opus-20240229": ["claude-3-sonnet-20240229", "claude-3-haiku-20240307"],
|
|
3619
|
-
"claude-3-5-sonnet-20241022": ["claude-3-sonnet-20240229", "claude-3-haiku-20240307"]
|
|
3620
|
-
};
|
|
3621
|
-
const fallbacks = fallbackMappings[data.model];
|
|
3622
|
-
if (!fallbacks || context.attemptCount >= fallbacks.length) {
|
|
3623
|
-
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
|
+
}
|
|
3624
3601
|
}
|
|
3625
|
-
const fallbackModel = fallbacks[context.attemptCount];
|
|
3626
|
-
return {
|
|
3627
|
-
retryRecommended: true,
|
|
3628
|
-
healedData: { ...data, model: fallbackModel },
|
|
3629
|
-
appliedFixes: [`Model fallback: ${data.model} \u2192 ${fallbackModel}`]
|
|
3630
|
-
};
|
|
3631
3602
|
}
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
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
|
+
}
|
|
3647
3622
|
}
|
|
3623
|
+
return result;
|
|
3648
3624
|
}
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
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;
|
|
3654
3653
|
}
|
|
3655
3654
|
/**
|
|
3656
|
-
*
|
|
3655
|
+
* 应用同步修复逻辑
|
|
3656
|
+
* 简化版的修复,不依赖异步操作
|
|
3657
3657
|
*/
|
|
3658
|
-
|
|
3659
|
-
const
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
messages.push(`Analyzing error: ${errorInfo.type} - ${errorInfo.message}`);
|
|
3663
|
-
const applicableStrategies = this.strategies.filter(
|
|
3664
|
-
(strategy) => strategy.condition(errorInfo, context)
|
|
3665
|
-
);
|
|
3666
|
-
if (applicableStrategies.length === 0) {
|
|
3667
|
-
return {
|
|
3668
|
-
success: false,
|
|
3669
|
-
error: errorInfo,
|
|
3670
|
-
attemptCount: context.attemptCount,
|
|
3671
|
-
totalTime: Date.now() - startTime,
|
|
3672
|
-
messages: [...messages, "No applicable recovery strategies found"]
|
|
3673
|
-
};
|
|
3658
|
+
applySyncHealing(request) {
|
|
3659
|
+
const healedRequest = { ...request };
|
|
3660
|
+
if (!healedRequest.max_tokens || healedRequest.max_tokens <= 0) {
|
|
3661
|
+
healedRequest.max_tokens = 4096;
|
|
3674
3662
|
}
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
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";
|
|
3679
3672
|
}
|
|
3680
|
-
if (
|
|
3681
|
-
|
|
3682
|
-
break;
|
|
3673
|
+
if (!message.content) {
|
|
3674
|
+
message.content = "";
|
|
3683
3675
|
}
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
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`);
|
|
3705
3699
|
}
|
|
3700
|
+
return summary.slice(0, 2).join(", ") + (error.issues.length > 5 ? ` (+${error.issues.length - 5} more)` : "");
|
|
3706
3701
|
}
|
|
3707
|
-
return
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
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()
|
|
3713
3790
|
};
|
|
3714
3791
|
}
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
const errorInfo = ErrorDetector.analyzeError(error, context);
|
|
3720
|
-
const hasApplicableStrategy = this.strategies.some(
|
|
3721
|
-
(strategy) => strategy.condition(errorInfo, context) && context.attemptCount < strategy.maxRetries
|
|
3722
|
-
);
|
|
3723
|
-
const withinGlobalLimit = context.attemptCount < this.maxGlobalRetries;
|
|
3724
|
-
return hasApplicableStrategy && withinGlobalLimit;
|
|
3792
|
+
logDebug(message, meta) {
|
|
3793
|
+
if (this.config.debugMode) {
|
|
3794
|
+
this.config.logger.debug(message, meta);
|
|
3795
|
+
}
|
|
3725
3796
|
}
|
|
3726
3797
|
/**
|
|
3727
|
-
*
|
|
3798
|
+
* 转换Anthropic请求为OpenAI格式
|
|
3728
3799
|
*/
|
|
3729
|
-
|
|
3730
|
-
const
|
|
3731
|
-
|
|
3732
|
-
(
|
|
3733
|
-
);
|
|
3734
|
-
const estimatedTime = applicableStrategies.reduce(
|
|
3735
|
-
(total, strategy) => total + strategy.backoffMs,
|
|
3736
|
-
0
|
|
3737
|
-
);
|
|
3738
|
-
let confidence = 0;
|
|
3739
|
-
if (applicableStrategies.length > 0) {
|
|
3740
|
-
const severityWeight = {
|
|
3741
|
-
low: 0.9,
|
|
3742
|
-
medium: 0.7,
|
|
3743
|
-
high: 0.5,
|
|
3744
|
-
critical: 0.2
|
|
3745
|
-
}[errorInfo.severity];
|
|
3746
|
-
const strategyWeight = Math.min(1, applicableStrategies.length / 3);
|
|
3747
|
-
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 });
|
|
3748
3804
|
}
|
|
3805
|
+
const openaiRequest = A2ORequestAdapterStatic.convertAnthropicRequestToOpenAI(anthropicRequest);
|
|
3806
|
+
openaiRequest.stream = true;
|
|
3807
|
+
const hasImages = this.hasImageContent(anthropicRequest);
|
|
3749
3808
|
return {
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3809
|
+
openaiRequest,
|
|
3810
|
+
metadata: {
|
|
3811
|
+
hasImages,
|
|
3812
|
+
requiresVisionHeaders: hasImages
|
|
3813
|
+
}
|
|
3754
3814
|
};
|
|
3755
3815
|
}
|
|
3756
3816
|
/**
|
|
3757
|
-
*
|
|
3817
|
+
* 与StandardProtocolAdapter保持一致的API,用于集成测试和向后兼容。
|
|
3758
3818
|
*/
|
|
3759
|
-
|
|
3760
|
-
this.
|
|
3819
|
+
convertRequest(anthropicRequest) {
|
|
3820
|
+
return this.convertAnthropicToOpenAI(anthropicRequest);
|
|
3761
3821
|
}
|
|
3762
3822
|
/**
|
|
3763
|
-
*
|
|
3823
|
+
* 转换OpenAI流式响应为Anthropic SSE格式
|
|
3764
3824
|
*/
|
|
3765
|
-
|
|
3766
|
-
const
|
|
3767
|
-
|
|
3768
|
-
this.
|
|
3769
|
-
|
|
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
|
+
};
|
|
3770
3858
|
}
|
|
3771
|
-
return false;
|
|
3772
|
-
}
|
|
3773
|
-
/**
|
|
3774
|
-
* 获取所有策略信息
|
|
3775
|
-
*/
|
|
3776
|
-
getStrategies() {
|
|
3777
|
-
return this.strategies.map((strategy) => ({
|
|
3778
|
-
id: strategy.id,
|
|
3779
|
-
name: strategy.name,
|
|
3780
|
-
description: strategy.description,
|
|
3781
|
-
supportedErrors: strategy.supportedErrors,
|
|
3782
|
-
maxRetries: strategy.maxRetries,
|
|
3783
|
-
backoffMs: strategy.backoffMs
|
|
3784
|
-
}));
|
|
3785
|
-
}
|
|
3786
|
-
};
|
|
3787
|
-
var errorRecovery = new ErrorRecovery();
|
|
3788
|
-
var attemptRecovery = (error, context) => errorRecovery.attemptRecovery(error, context);
|
|
3789
|
-
var isRecoverable = (error, context) => errorRecovery.isRecoverable(error, context);
|
|
3790
|
-
var getRecoveryRecommendations = (error, context) => errorRecovery.getRecoveryRecommendations(error, context);
|
|
3791
|
-
|
|
3792
|
-
// src/core/a2o-request-adapter/adapter.ts
|
|
3793
|
-
var A2ORequestAdapter = class {
|
|
3794
|
-
constructor(config = {}) {
|
|
3795
|
-
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
3796
3859
|
}
|
|
3797
3860
|
/**
|
|
3798
|
-
*
|
|
3799
|
-
* 集成校验、修复和错误恢复功能
|
|
3861
|
+
* 将OpenAI流转换为Anthropic SSE格式
|
|
3800
3862
|
*/
|
|
3801
|
-
|
|
3802
|
-
const
|
|
3803
|
-
const
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
validatedInput = validateAnthropicRequest(anthropicRequest);
|
|
3817
|
-
result.validationResult = {
|
|
3818
|
-
inputValid: true,
|
|
3819
|
-
outputValid: false,
|
|
3820
|
-
issues: []
|
|
3821
|
-
};
|
|
3822
|
-
} catch (error) {
|
|
3823
|
-
result.errors.push(`Input validation failed: ${error.message}`);
|
|
3824
|
-
return result;
|
|
3825
|
-
}
|
|
3826
|
-
} else {
|
|
3827
|
-
if (this.config.healing.enabled) {
|
|
3828
|
-
const healingResult = await healA2ORequest(anthropicRequest, this.config.healing.maxAttempts);
|
|
3829
|
-
if (healingResult.success) {
|
|
3830
|
-
validatedInput = healingResult.data;
|
|
3831
|
-
result.healingApplied = true;
|
|
3832
|
-
result.appliedFixes = healingResult.appliedFixes;
|
|
3833
|
-
result.warnings.push(...healingResult.warnings);
|
|
3834
|
-
} else {
|
|
3835
|
-
result.errors.push(...healingResult.errors);
|
|
3836
|
-
return result;
|
|
3837
|
-
}
|
|
3838
|
-
} else {
|
|
3839
|
-
try {
|
|
3840
|
-
validatedInput = validateAnthropicRequest(anthropicRequest);
|
|
3841
|
-
} catch {
|
|
3842
|
-
validatedInput = anthropicRequest;
|
|
3843
|
-
result.warnings.push("Input validation skipped due to errors");
|
|
3844
|
-
}
|
|
3845
|
-
}
|
|
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;
|
|
3846
3878
|
}
|
|
3847
|
-
} else {
|
|
3848
|
-
validatedInput = anthropicRequest;
|
|
3849
|
-
}
|
|
3850
|
-
const openaiRequest = await this.performCoreConversion(validatedInput);
|
|
3851
|
-
if (this.config.validation.enabled) {
|
|
3852
3879
|
try {
|
|
3853
|
-
const
|
|
3854
|
-
|
|
3855
|
-
result.validationResult = {
|
|
3856
|
-
inputValid: true,
|
|
3857
|
-
outputValid: true,
|
|
3858
|
-
issues: []
|
|
3859
|
-
};
|
|
3880
|
+
const chunk = JSON.parse(dataLine);
|
|
3881
|
+
this.processStreamChunk(chunk, state, sseLines);
|
|
3860
3882
|
} catch (error) {
|
|
3861
|
-
if (this.config.
|
|
3862
|
-
|
|
3863
|
-
return result;
|
|
3864
|
-
} else {
|
|
3865
|
-
result.warnings.push(`Output validation warning: ${error.message}`);
|
|
3866
|
-
result.data = openaiRequest;
|
|
3883
|
+
if (this.config.debugMode) {
|
|
3884
|
+
this.config.logger.warn("Failed to parse stream chunk", { line: dataLine.substring(0, 200) });
|
|
3867
3885
|
}
|
|
3868
3886
|
}
|
|
3869
|
-
} else {
|
|
3870
|
-
result.data = openaiRequest;
|
|
3871
|
-
}
|
|
3872
|
-
if (this.config.monitoring.enabled) {
|
|
3873
|
-
const processingTime = Date.now() - startTime;
|
|
3874
|
-
if (this.config.monitoring.logLevel !== "none") {
|
|
3875
|
-
console.log(`[A2O Adapter] Conversion completed in ${processingTime}ms`, {
|
|
3876
|
-
healingApplied: result.healingApplied,
|
|
3877
|
-
fixesCount: result.appliedFixes?.length || 0
|
|
3878
|
-
});
|
|
3879
|
-
}
|
|
3880
|
-
}
|
|
3881
|
-
result.success = true;
|
|
3882
|
-
return result;
|
|
3883
|
-
} catch (error) {
|
|
3884
|
-
result.errors.push(`Conversion failed: ${error.message}`);
|
|
3885
|
-
if (this.config.recovery.enabled) {
|
|
3886
|
-
result.warnings.push("Error recovery attempted but not implemented yet");
|
|
3887
|
-
}
|
|
3888
|
-
return result;
|
|
3889
|
-
}
|
|
3890
|
-
}
|
|
3891
|
-
/**
|
|
3892
|
-
* 执行核心转换逻辑(原有逻辑保持不变)
|
|
3893
|
-
*/
|
|
3894
|
-
async performCoreConversion(anthropicRequest) {
|
|
3895
|
-
if (this.config.enableFormatValidation) {
|
|
3896
|
-
FormatValidator.validateClaudeRequest(anthropicRequest);
|
|
3897
|
-
}
|
|
3898
|
-
const openaiRequest = {
|
|
3899
|
-
model: anthropicRequest.model,
|
|
3900
|
-
messages: MessageConverter.convertMessages(anthropicRequest.messages, anthropicRequest.system),
|
|
3901
|
-
max_tokens: anthropicRequest.max_tokens,
|
|
3902
|
-
temperature: anthropicRequest.temperature,
|
|
3903
|
-
stream: anthropicRequest.stream
|
|
3904
|
-
};
|
|
3905
|
-
if (anthropicRequest.tools && anthropicRequest.tools.length > 0) {
|
|
3906
|
-
openaiRequest.tools = this.convertToolDefinitions(anthropicRequest.tools);
|
|
3907
|
-
}
|
|
3908
|
-
const specialFields = ["_anthropic_protocol", "_rovo_tool_injected", "_routeResult"];
|
|
3909
|
-
for (const field of specialFields) {
|
|
3910
|
-
if (anthropicRequest[field] !== void 0) {
|
|
3911
|
-
openaiRequest[field] = anthropicRequest[field];
|
|
3912
3887
|
}
|
|
3913
3888
|
}
|
|
3914
|
-
return
|
|
3889
|
+
return sseLines.join("\n");
|
|
3915
3890
|
}
|
|
3916
3891
|
/**
|
|
3917
|
-
*
|
|
3892
|
+
* 处理单个流式数据块 - 支持thinking和content双模式
|
|
3918
3893
|
*/
|
|
3919
|
-
|
|
3920
|
-
if (this.
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
const openaiRequest = {
|
|
3924
|
-
model: anthropicRequest.model,
|
|
3925
|
-
messages: MessageConverter.convertMessages(anthropicRequest.messages, anthropicRequest.system),
|
|
3926
|
-
max_tokens: anthropicRequest.max_tokens,
|
|
3927
|
-
temperature: anthropicRequest.temperature,
|
|
3928
|
-
stream: anthropicRequest.stream,
|
|
3929
|
-
n: 1
|
|
3930
|
-
};
|
|
3931
|
-
if (anthropicRequest.tools && anthropicRequest.tools.length > 0) {
|
|
3932
|
-
openaiRequest.tools = this.convertToolDefinitions(anthropicRequest.tools);
|
|
3894
|
+
processStreamChunk(chunk, state, sseLines) {
|
|
3895
|
+
if (this.isResponsesEvent(chunk)) {
|
|
3896
|
+
this.processResponsesEvent(chunk, state, sseLines);
|
|
3897
|
+
return;
|
|
3933
3898
|
}
|
|
3934
|
-
const
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
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 });
|
|
3938
3906
|
}
|
|
3939
3907
|
}
|
|
3940
|
-
if (
|
|
3941
|
-
|
|
3908
|
+
if (!choice) {
|
|
3909
|
+
this.updateUsageFromChunk(chunk, state);
|
|
3910
|
+
return;
|
|
3942
3911
|
}
|
|
3943
|
-
|
|
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);
|
|
3944
3919
|
}
|
|
3945
3920
|
/**
|
|
3946
|
-
*
|
|
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
|
|
3947
3926
|
*/
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
const
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
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
|
|
3955
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
|
+
}
|
|
3956
3960
|
}
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
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);
|
|
3960
3974
|
}
|
|
3961
|
-
|
|
3962
|
-
role: "assistant",
|
|
3963
|
-
content: claudeContent
|
|
3964
|
-
};
|
|
3965
|
-
return claudeResponse;
|
|
3975
|
+
return existing;
|
|
3966
3976
|
}
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
return tool;
|
|
3974
|
-
} else {
|
|
3975
|
-
return ToolConverter.convertAnthropicToolToOpenAI(tool);
|
|
3976
|
-
}
|
|
3977
|
-
});
|
|
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
|
+
}
|
|
3978
3983
|
}
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
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;
|
|
3984
4002
|
}
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
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
|
+
}
|
|
3990
4018
|
}
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
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;
|
|
3996
4034
|
}
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
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
|
+
);
|
|
4002
4051
|
}
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
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
|
+
);
|
|
4008
4082
|
}
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
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
|
+
}
|
|
4014
4098
|
}
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
*/
|
|
4018
|
-
getConfig() {
|
|
4019
|
-
return { ...this.config };
|
|
4099
|
+
isResponsesEvent(chunk) {
|
|
4100
|
+
return typeof chunk?.type === "string" && chunk.type.startsWith("response.");
|
|
4020
4101
|
}
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
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;
|
|
4037
4127
|
}
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
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;
|
|
4044
4140
|
}
|
|
4045
4141
|
}
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
if (this.config.validation.strict) {
|
|
4052
|
-
throw error;
|
|
4053
|
-
} else {
|
|
4054
|
-
console.warn("[A2ORequestAdapter] Output validation warning:", error);
|
|
4055
|
-
}
|
|
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);
|
|
4056
4147
|
}
|
|
4057
4148
|
}
|
|
4058
|
-
|
|
4149
|
+
for (const alias of aliases) {
|
|
4150
|
+
this.registerToolCallAlias(state, alias, toolData);
|
|
4151
|
+
}
|
|
4152
|
+
return toolData;
|
|
4059
4153
|
}
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
if (
|
|
4065
|
-
|
|
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;
|
|
4066
4160
|
}
|
|
4067
|
-
const
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
n: 1
|
|
4074
|
-
};
|
|
4075
|
-
if (anthropicRequest.tools && anthropicRequest.tools.length > 0) {
|
|
4076
|
-
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}`;
|
|
4077
4167
|
}
|
|
4078
|
-
const
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
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);
|
|
4082
4222
|
}
|
|
4223
|
+
this.logDebug("Sent content_block_stop", { toolName: toolData.name, blockIndex: toolData.blockIndex });
|
|
4083
4224
|
}
|
|
4084
|
-
|
|
4085
|
-
|
|
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("");
|
|
4086
4233
|
}
|
|
4087
|
-
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;
|
|
4088
4251
|
}
|
|
4089
4252
|
/**
|
|
4090
|
-
*
|
|
4091
|
-
* 简化版的修复,不依赖异步操作
|
|
4253
|
+
* 在流结束时关闭所有未关闭的工具调用块
|
|
4092
4254
|
*/
|
|
4093
|
-
|
|
4094
|
-
const
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
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
|
+
}
|
|
4107
4268
|
}
|
|
4108
|
-
if (!
|
|
4109
|
-
|
|
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 });
|
|
4110
4281
|
}
|
|
4111
4282
|
}
|
|
4112
|
-
return healedRequest;
|
|
4113
4283
|
}
|
|
4114
4284
|
/**
|
|
4115
|
-
*
|
|
4285
|
+
* 添加最终事件 - 支持thinking+content双模式
|
|
4116
4286
|
*/
|
|
4117
|
-
|
|
4118
|
-
|
|
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
|
+
);
|
|
4119
4316
|
}
|
|
4120
4317
|
/**
|
|
4121
|
-
*
|
|
4318
|
+
* 构建标准响应格式
|
|
4122
4319
|
*/
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
if (
|
|
4129
|
-
const
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
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
|
+
}
|
|
4134
4334
|
}
|
|
4135
|
-
return summary.slice(0, 2).join(", ") + (error.issues.length > 5 ? ` (+${error.issues.length - 5} more)` : "");
|
|
4136
4335
|
}
|
|
4137
|
-
|
|
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
|
+
};
|
|
4138
4352
|
}
|
|
4139
|
-
};
|
|
4140
|
-
var A2ORequestAdapterStatic = {
|
|
4141
4353
|
/**
|
|
4142
|
-
*
|
|
4143
|
-
* 内部使用增强转换器,所有调用点自动获得增强功能
|
|
4354
|
+
* 创建转换状态对象
|
|
4144
4355
|
*/
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
}
|
|
4163
|
-
}
|
|
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
|
+
}
|
|
4164
4375
|
/**
|
|
4165
|
-
*
|
|
4166
|
-
* 内部使用增强转换器
|
|
4376
|
+
* 转换消息格式
|
|
4167
4377
|
*/
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
validation: { enabled: true, strict: false },
|
|
4175
|
-
healing: { enabled: true, maxAttempts: 2, enableCustomRules: true },
|
|
4176
|
-
recovery: { enabled: false, maxRetries: 0, backoffMs: 1e3 },
|
|
4177
|
-
monitoring: { enabled: false, logLevel: "none", enableMetrics: false }
|
|
4178
|
-
});
|
|
4179
|
-
return adapter.convertOpenAIResponseToClaude(openaiResponse);
|
|
4180
|
-
},
|
|
4378
|
+
convertMessages(messages) {
|
|
4379
|
+
return messages.map((msg) => ({
|
|
4380
|
+
role: msg.role,
|
|
4381
|
+
content: msg.content
|
|
4382
|
+
}));
|
|
4383
|
+
}
|
|
4181
4384
|
/**
|
|
4182
|
-
*
|
|
4385
|
+
* 映射Anthropic模型到OpenAI模型
|
|
4183
4386
|
*/
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
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
|
+
}
|
|
4187
4409
|
/**
|
|
4188
|
-
*
|
|
4410
|
+
* 检查请求是否包含图片内容
|
|
4189
4411
|
*/
|
|
4190
|
-
|
|
4191
|
-
return
|
|
4192
|
-
|
|
4412
|
+
hasImageContent(request) {
|
|
4413
|
+
return request.messages.some(
|
|
4414
|
+
(msg) => Array.isArray(msg.content) && msg.content.some((content) => content?.type === "image")
|
|
4415
|
+
);
|
|
4416
|
+
}
|
|
4193
4417
|
/**
|
|
4194
|
-
*
|
|
4418
|
+
* 转义JSON字符串
|
|
4195
4419
|
*/
|
|
4196
|
-
|
|
4197
|
-
return
|
|
4198
|
-
}
|
|
4420
|
+
escapeJsonString(str) {
|
|
4421
|
+
return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
|
|
4422
|
+
}
|
|
4199
4423
|
/**
|
|
4200
|
-
*
|
|
4424
|
+
* 获取初始SSE事件(message_start + ping)
|
|
4201
4425
|
*/
|
|
4202
|
-
|
|
4203
|
-
return
|
|
4204
|
-
|
|
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
|
+
}
|
|
4205
4436
|
/**
|
|
4206
|
-
*
|
|
4437
|
+
* 增量转换单个OpenAI数据块为Anthropic SSE事件
|
|
4438
|
+
* 用于逐个处理流式数据片段
|
|
4207
4439
|
*/
|
|
4208
|
-
|
|
4209
|
-
|
|
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();
|
|
4210
4475
|
}
|
|
4211
4476
|
};
|
|
4212
4477
|
|
|
@@ -4373,6 +4638,8 @@ var ToolCallProcessor = class _ToolCallProcessor {
|
|
|
4373
4638
|
console.debug("[ToolProcessor] Processing tool args, calling processToolArgs");
|
|
4374
4639
|
}
|
|
4375
4640
|
_ToolCallProcessor.processToolArgs(toolId, toolArgs, state, sseLines);
|
|
4641
|
+
} else if (toolName && toolId) {
|
|
4642
|
+
_ToolCallProcessor.processToolArgs(toolId, "", state, sseLines);
|
|
4376
4643
|
} else {
|
|
4377
4644
|
console.warn("\u26A0\uFE0F\u26A0\uFE0F\u26A0\uFE0F [ToolProcessor] No tool args to process! This will result in empty input!");
|
|
4378
4645
|
}
|
|
@@ -4408,6 +4675,8 @@ var ToolCallProcessor = class _ToolCallProcessor {
|
|
|
4408
4675
|
console.debug("[ToolProcessor] Processing batch tool args, calling processToolArgs");
|
|
4409
4676
|
}
|
|
4410
4677
|
_ToolCallProcessor.processToolArgs(toolId, toolArgs, state, sseLines);
|
|
4678
|
+
} else if (toolName && toolId) {
|
|
4679
|
+
_ToolCallProcessor.processToolArgs(toolId, "", state, sseLines);
|
|
4411
4680
|
} else {
|
|
4412
4681
|
console.warn("\u26A0\uFE0F\u26A0\uFE0F\u26A0\uFE0F [ToolProcessor] No batch tool args to process! This will result in empty input!");
|
|
4413
4682
|
}
|