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.js CHANGED
@@ -504,1360 +504,925 @@ function getGlobalLogger() {
504
504
  return globalLogger;
505
505
  }
506
506
 
507
- // src/core/streaming/streaming-protocol-adapter.ts
508
- var StreamingProtocolAdapter = class {
509
- constructor(options = {}) {
510
- this.config = {
511
- debugMode: options.debugMode ?? false,
512
- validateInput: options.validateInput ?? false,
513
- validateOutput: options.validateOutput ?? false,
514
- autoHeal: options.autoHeal ?? false,
515
- timeout: options.timeout ?? 3e4,
516
- retries: options.retries ?? 3,
517
- bufferSize: options.bufferSize ?? 1024,
518
- logger: options.logger ?? getGlobalLogger()
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
- logDebug(message, meta) {
522
- if (this.config.debugMode) {
523
- this.config.logger.debug(message, meta);
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
- * 转换Anthropic请求为OpenAI格式
622
+ * 创建消息转换上下文
528
623
  */
529
- convertAnthropicToOpenAI(anthropicRequest) {
530
- const logger = this.config.logger;
531
- if (this.config.debugMode) {
532
- logger.debug("Converting Anthropic request to OpenAI format", { model: anthropicRequest.model });
533
- }
534
- const openaiRequest = {
535
- model: this.mapAnthropicModelToOpenAI(anthropicRequest.model),
536
- messages: this.convertMessages(anthropicRequest.messages),
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
- openaiRequest,
554
- metadata: {
555
- hasImages,
556
- requiresVisionHeaders: hasImages
557
- }
636
+ toolIdToNameMap,
637
+ hasSystemMessage: false
558
638
  };
559
639
  }
560
640
  /**
561
- * 转换OpenAI流式响应为Anthropic SSE格式
641
+ * 处理复杂消息(包含多种内容类型)
562
642
  */
563
- convertOpenAIStreamToAnthropic(openaiStream, originalRequest) {
564
- const logger = this.config.logger;
565
- try {
566
- if (this.config.debugMode) {
567
- logger.debug("Converting OpenAI stream to Anthropic SSE", {
568
- streamLength: openaiStream.length,
569
- model: originalRequest.model
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
- if (!openaiStream || openaiStream.trim() === "") {
573
- return {
574
- success: false,
575
- error: "Empty stream response",
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
- * 将OpenAI流转换为Anthropic SSE格式
665
+ * 分类内容块
600
666
  */
601
- convertToAnthropicSSE(openaiStream, modelName) {
602
- const lines = openaiStream.split("\n");
603
- const sseLines = [];
604
- const state = this.createConversionState();
605
- sseLines.push(
606
- "event: message_start",
607
- `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}}}`,
608
- ""
609
- );
610
- for (const line of lines) {
611
- if (line.startsWith("data:")) {
612
- const dataLine = line.substring(5);
613
- if (dataLine.trim() === "[DONE]") {
614
- this.addFinalEvents(state, sseLines);
615
- break;
616
- }
617
- try {
618
- const chunk = JSON.parse(dataLine);
619
- this.processStreamChunk(chunk, state, sseLines);
620
- } catch (error) {
621
- if (this.config.debugMode) {
622
- this.config.logger.warn("Failed to parse stream chunk", { line: dataLine.substring(0, 200) });
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 sseLines.join("\n");
694
+ return { textContent, toolUses, toolResults };
628
695
  }
629
696
  /**
630
- * 处理单个流式数据块 - 支持thinking和content双模式
697
+ * 转换图片内容格式
631
698
  */
632
- processStreamChunk(chunk, state, sseLines) {
633
- const choice = chunk.choices?.[0];
634
- if (choice) {
635
- const hasToolCalls = choice.delta?.tool_calls;
636
- const hasFinishReason = choice.finish_reason;
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
- state.textContent += delta.content;
683
- const blockIndex = state.thinkingBlockStarted ? 1 : 0;
684
- sseLines.push(
685
- "event: content_block_delta",
686
- `data: {"type":"content_block_delta","index":${blockIndex},"delta":{"type":"text_delta","text":"${this.escapeJsonString(delta.content)}"}}`,
687
- ""
688
- );
689
- }
690
- if (delta.tool_calls) {
691
- this.processToolCalls(delta.tool_calls, state, sseLines);
692
- }
693
- if (chunk.usage) {
694
- state.usage.input_tokens = chunk.usage.prompt_tokens;
695
- state.usage.output_tokens = chunk.usage.completion_tokens;
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
- * 处理工具调用 - 支持OpenAI流式分块累积
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
- processToolCalls(toolCalls, state, sseLines) {
706
- this.logDebug("processToolCalls called", { toolCalls });
707
- for (const toolCall of toolCalls) {
708
- const index = toolCall.index ?? 0;
709
- const toolId = toolCall.id;
710
- const toolName = toolCall.function?.name;
711
- const toolArgs = toolCall.function?.arguments;
712
- this.logDebug(`Processing tool chunk for index ${index}`, {
713
- hasId: !!toolId,
714
- hasName: !!toolName,
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
- closeAllToolCallBlocks(state, sseLines) {
764
- for (const [key, toolData] of state.toolCallsMap.entries()) {
765
- if (toolData.blockStartSent && !toolData.blockStopSent && toolData.blockIndex !== void 0) {
766
- sseLines.push(
767
- "event: content_block_stop",
768
- `data: {"type":"content_block_stop","index":${toolData.blockIndex}}`,
769
- ""
770
- );
771
- toolData.blockStopSent = true;
772
- state.completedToolCalls.push(toolData.id);
773
- this.logDebug("Sent content_block_stop", { toolName: toolData.name, blockIndex: toolData.blockIndex });
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
- * 添加最终事件 - 支持thinking+content双模式
763
+ * 创建文本消息
779
764
  */
780
- addFinalEvents(state, sseLines) {
781
- this.closeAllToolCallBlocks(state, sseLines);
782
- if (state.contentBlockStarted) {
783
- const blockIndex = state.thinkingBlockStarted ? 1 : 0;
784
- sseLines.push(
785
- "event: content_block_stop",
786
- `data: {"type":"content_block_stop","index":${blockIndex}}`,
787
- ""
788
- );
789
- } else if (state.thinkingBlockStarted) {
790
- sseLines.push(
791
- "event: content_block_delta",
792
- 'data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"</thinking>"}}',
793
- "",
794
- "event: content_block_stop",
795
- 'data: {"type":"content_block_stop","index":0}',
796
- ""
797
- );
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
- buildStandardResponse(openaiStream) {
813
- const state = this.createConversionState();
814
- const lines = openaiStream.split("\n");
815
- for (const line of lines) {
816
- if (line.startsWith("data: ")) {
817
- const dataLine = line.substring(6);
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
- return {
836
- id: `msg_${Date.now()}`,
837
- type: "message",
838
- role: "assistant",
839
- content: [
840
- {
841
- type: "text",
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
- createConversionState() {
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
- processedLines: 0,
857
- textContent: "",
858
- reasoningContent: "",
859
- toolCallsMap: /* @__PURE__ */ new Map(),
860
- completedToolCalls: [],
861
- allSSELines: [],
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
- convertMessages(messages) {
875
- return messages.map((msg) => ({
876
- role: msg.role,
877
- content: msg.content
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
- * 映射Anthropic模型到OpenAI模型
856
+ * 检查是否为OpenAI工具格式
882
857
  */
883
- mapAnthropicModelToOpenAI(model) {
884
- const supportedModels = [
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
- hasImageContent(request) {
909
- return request.messages.some(
910
- (msg) => Array.isArray(msg.content) && msg.content.some((content) => content?.type === "image")
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
- * 转义JSON字符串
873
+ * 验证工具定义的有效性
915
874
  */
916
- escapeJsonString(str) {
917
- return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
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
- * 获取初始SSE事件(message_start + ping)
886
+ * 获取工具名称(通用方法)
921
887
  */
922
- getInitialSSEEvents(modelName = "claude-sonnet-4", messageId = `msg_${Date.now()}_${Math.random().toString(36).substring(2, 8)}`) {
923
- return [
924
- "event: message_start",
925
- `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}}}`,
926
- "",
927
- "event: ping",
928
- 'data: {"type":"ping"}',
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
- * 增量转换单个OpenAI数据块为Anthropic SSE事件
934
- * 用于逐个处理流式数据片段
908
+ * 确保日志目录存在
935
909
  */
936
- convertIncrementalChunk(openaiDataLine, state) {
937
- const logger = this.config.logger;
938
- const sseEvents = [];
939
- if (openaiDataLine.trim() === "[DONE]") {
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
- const chunk = JSON.parse(openaiDataLine);
945
- this.processStreamChunk(chunk, state, sseEvents);
946
- return sseEvents;
933
+ (0, import_fs.writeFileSync)(filepath, JSON.stringify(logEntry, null, 2));
934
+ return filepath;
947
935
  } catch (error) {
948
- if (this.config.debugMode) {
949
- logger.warn("Failed to parse OpenAI stream chunk in convertIncrementalChunk", {
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
- // src/core/a2o-request-adapter/config.ts
960
- var DEFAULT_CONFIG = {
961
- // 原有配置
962
- debugMode: false,
963
- maxDescriptionLength: 100,
964
- enableToolNameValidation: true,
965
- enableFormatValidation: true,
966
- // 新增默认配置
967
- validation: {
968
- enabled: true,
969
- strict: false,
970
- // 默认开启自动修复
971
- customSchemas: {}
972
- },
973
- healing: {
974
- enabled: true,
975
- maxAttempts: 3,
976
- enableCustomRules: true
977
- },
978
- recovery: {
979
- enabled: true,
980
- maxRetries: 2,
981
- backoffMs: 1e3
982
- },
983
- monitoring: {
984
- enabled: false,
985
- logLevel: "warn",
986
- enableMetrics: false
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
- PRESERVE_ORIGINAL_NAMES: true,
974
+ findMissingFields(obj, requiredFields) {
975
+ return requiredFields.filter((field) => obj[field] === void 0);
976
+ }
1001
977
  /**
1002
- * 默认工具描述
978
+ * 查找无效类型
1003
979
  */
1004
- DEFAULT_DESCRIPTION: "Tool description",
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
- UNKNOWN_TOOL_FALLBACK: "unknown_tool"
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/message-converter.ts
1012
- var MessageConverter = class {
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 convertMessages(messages, system) {
1019
- const debugEnabled = process.env.AI_PROTOCOL_DEBUG === "true";
1020
- if (debugEnabled) {
1021
- if (system !== void 0) {
1022
- console.debug("[MessageConverter] convertMessages called with system:", JSON.stringify(system, null, 2));
1023
- } else {
1024
- console.debug("[MessageConverter] convertMessages called WITHOUT system parameter");
1025
- }
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
- const context = this.createConversionContext(messages);
1028
- const convertedMessages = [];
1029
- for (const msg of messages) {
1030
- if (Array.isArray(msg.content)) {
1031
- const processedMessages = this.processComplexMessage(msg, context);
1032
- convertedMessages.push(...processedMessages);
1033
- } else {
1034
- const safeMsg = { ...msg };
1035
- if (safeMsg.content === null || safeMsg.content === void 0) {
1036
- safeMsg.content = "";
1037
- }
1038
- convertedMessages.push(safeMsg);
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 (system) {
1042
- const systemMessage = this.processSystemMessage(system);
1043
- if (systemMessage) {
1044
- convertedMessages.unshift(systemMessage);
1045
- if (debugEnabled) {
1046
- console.debug("[MessageConverter] System message added to messages array at index 0");
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 (debugEnabled) {
1051
- console.debug("[MessageConverter] Final converted messages count:", convertedMessages.length);
1052
- console.debug("[MessageConverter] First message:", JSON.stringify(convertedMessages[0], null, 2));
1053
- }
1054
- return convertedMessages;
1055
- }
1056
- /**
1057
- * 创建消息转换上下文
1058
- */
1059
- static createConversionContext(messages) {
1060
- const toolIdToNameMap = /* @__PURE__ */ new Map();
1061
- for (const msg of messages) {
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 processComplexMessage(msg, context) {
1079
- const { textContent, toolUses, toolResults } = this.categorizeContent(msg.content);
1080
- const resultMessages = [];
1081
- if (msg.role === "assistant" && toolUses.length > 0) {
1082
- const assistantMessage = this.createAssistantMessageWithToolCalls(textContent, toolUses);
1083
- resultMessages.push(assistantMessage);
1084
- } else if (toolResults.length > 0) {
1085
- const toolMessages = this.createToolResultMessages(toolResults, context.toolIdToNameMap);
1086
- resultMessages.push(...toolMessages);
1087
- const textMessage = this.createTextMessage(msg.role, textContent);
1088
- if (textMessage) {
1089
- resultMessages.push(textMessage);
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
- return resultMessages;
1098
- }
1099
- /**
1100
- * 分类内容块
1101
- */
1102
- static categorizeContent(content) {
1103
- const textContent = [];
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 { textContent, toolUses, toolResults };
1134
+ return true;
1130
1135
  }
1131
1136
  /**
1132
- * 转换图片内容格式
1137
+ * 验证Claude消息格式
1133
1138
  */
1134
- static convertImageContent(item) {
1135
- if (item.source && item.source.type === "base64" && item.source.data && item.source.media_type) {
1136
- if (!SUPPORTED_IMAGE_TYPES.includes(item.source.media_type)) {
1137
- console.warn(`\u4E0D\u652F\u6301\u7684\u56FE\u7247\u683C\u5F0F: ${item.source.media_type}`);
1138
- return null;
1139
- }
1140
- const dataUri = `data:${item.source.media_type};base64,${item.source.data}`;
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
- return null;
1149
- }
1150
- /**
1151
- * 创建包含工具调用的助手消息
1152
- */
1153
- static createAssistantMessageWithToolCalls(textContent, toolUses) {
1154
- const assistantMessage = {
1155
- role: "assistant",
1156
- content: ""
1157
- // 默认为空字符串,避免null值
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
- assistantMessage.tool_calls = toolUses.map((toolUse) => ({
1166
- id: toolUse.id,
1167
- type: "function",
1168
- function: {
1169
- name: toolUse.name,
1170
- arguments: JSON.stringify(toolUse.input || {})
1171
- }
1172
- }));
1173
- return assistantMessage;
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 createToolResultMessages(toolResults, toolIdToNameMap) {
1179
- return toolResults.map((toolResult) => {
1180
- let resultContent = "No content";
1181
- if (toolResult.content) {
1182
- if (typeof toolResult.content === "string") {
1183
- resultContent = toolResult.content;
1184
- } else {
1185
- resultContent = JSON.stringify(toolResult.content, null, 2);
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
- const toolName = toolIdToNameMap.get(toolResult.tool_use_id) || TOOL_CONVERSION.UNKNOWN_TOOL_FALLBACK;
1189
- return {
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 createTextMessage(role, textContent) {
1201
- if (textContent.length === 0) return null;
1202
- const hasNonTextContent = textContent.some((item) => item.type !== "text");
1203
- if (hasNonTextContent) {
1204
- return {
1205
- role,
1206
- content: textContent
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 processSystemMessage(system) {
1221
- let systemContent;
1222
- if (Array.isArray(system)) {
1223
- systemContent = system.map((s) => {
1224
- if (typeof s === "string") {
1225
- return s;
1226
- }
1227
- return s.text || "";
1228
- }).filter((text) => text.length > 0).join("\n").trim();
1229
- } else {
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 null;
1270
+ return isValid;
1239
1271
  }
1240
- };
1241
-
1242
- // src/core/a2o-request-adapter/tool-converter.ts
1243
- var ToolConverter = class {
1244
1272
  /**
1245
- * 将Anthropic工具定义转换为OpenAI格式
1273
+ * 验证OpenAI消息格式
1246
1274
  */
1247
- static convertAnthropicToolToOpenAI(anthropicTool) {
1248
- if (!anthropicTool || !anthropicTool.name) {
1249
- throw new Error("Invalid tool definition: missing name");
1275
+ static validateOpenAIMessage(message) {
1276
+ if (!message || !message.role) {
1277
+ return false;
1250
1278
  }
1251
- const openaiName = anthropicTool.name;
1252
- const description = this.simplifyDescription(anthropicTool.description || TOOL_CONVERSION.DEFAULT_DESCRIPTION);
1253
- if (!anthropicTool.input_schema) {
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 parameters = {
1257
- type: anthropicTool.input_schema.type || "object",
1258
- properties: anthropicTool.input_schema.properties || {},
1259
- ...anthropicTool.input_schema.required && { required: anthropicTool.input_schema.required }
1260
- };
1261
- return {
1262
- type: "function",
1263
- function: {
1264
- name: openaiName,
1265
- description,
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
- * 将OpenAI工具调用转换为Claude格式
1295
+ * 获取工具定义缺失字段
1272
1296
  */
1273
- static convertOpenAIToolCallsToClaude(toolCalls) {
1274
- return toolCalls.map((toolCall) => {
1275
- const claudeToolName = toolCall.function.name;
1276
- let parsedInput = {};
1277
- try {
1278
- parsedInput = JSON.parse(toolCall.function.arguments);
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
- * 检查是否为OpenAI工具格式
1305
+ * 验证Claude工具定义
1292
1306
  */
1293
- static isOpenAIToolFormat(tool) {
1294
- return tool && tool.type === "function" && tool.function && tool.function.name;
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
- * 简化Claude的详细描述为OpenAI兼容的简短描述
1342
+ * 验证OpenAI工具定义
1298
1343
  */
1299
- static simplifyDescription(claudeDescription) {
1300
- const firstLine = claudeDescription.split("\n")[0].trim();
1301
- const maxLength = 100;
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
- return firstLine;
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 ("function" in tool) {
1316
- return !!(tool.type === "function" && tool.function?.name && tool.function?.parameters);
1351
+ if (!tool.function.parameters || typeof tool.function.parameters !== "object") {
1352
+ return false;
1317
1353
  }
1318
- return false;
1354
+ return ToolConverter.validateToolDefinition(tool);
1319
1355
  }
1320
1356
  /**
1321
- * 获取工具名称(通用方法)
1357
+ * 验证系统消息
1322
1358
  */
1323
- static getToolName(tool) {
1324
- if ("name" in tool) {
1325
- return tool.name;
1359
+ static validateSystemMessage(system) {
1360
+ if (typeof system === "string") {
1361
+ return true;
1326
1362
  }
1327
- if ("function" in tool) {
1328
- return tool.function.name;
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 TOOL_CONVERSION.UNKNOWN_TOOL_FALLBACK;
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
- ensureLogsDir() {
1373
+ static validateConversionResult(original, converted, direction) {
1346
1374
  try {
1347
- (0, import_fs.mkdirSync)(this.logsDir, { recursive: true });
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
- logValidationError(type, validationData, context = {}) {
1355
- const timestamp = context.timestamp || (/* @__PURE__ */ new Date()).toISOString();
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
- (0, import_fs.writeFileSync)(filepath, JSON.stringify(logEntry, null, 2));
1369
- return filepath;
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
- console.warn(`\u26A0\uFE0F [ValidationLogger] Failed to write log file: ${filepath} (${error.code})`);
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
- getRecentLogs(limit = 10) {
1426
- try {
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
- * 验证Claude请求格式
1412
+ * 验证并返回日志路径
1445
1413
  */
1446
- static validateClaudeRequest(request) {
1447
- if (!request) {
1448
- const errorInfo = {
1449
- error: "request_is_null",
1450
- details: "Request\u5BF9\u8C61\u4E3A\u7A7A\u6216\u672A\u5B9A\u4E49"
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
- for (let i = 0; i < request.tools.length; i++) {
1508
- const tool = request.tools[i];
1509
- if (!this.validateClaudeToolDefinition(tool)) {
1510
- const errorInfo = {
1511
- error: "invalid_tool_definition",
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: "fix-stop-sequences-format",
3156
- name: "Fix Stop Sequences Format",
3157
- description: "Convert between stop_sequences and stop formats",
3158
- condition: (data, ctx) => {
3159
- return ctx.direction === "a2o" && data?.stop_sequences || ctx.direction === "o2a" && data?.stop;
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, ctx) => {
3162
- if (ctx.direction === "a2o") {
3163
- const { stop_sequences, ...rest } = data;
3164
- return {
3165
- ...rest,
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: 4
2944
+ priority: 9
3177
2945
  }
3178
2946
  ];
3179
- var RESPONSE_HEALING_STRATEGIES = [
3180
- // Usage 字段修复
3181
- {
3182
- id: "fix-usage-mapping-o2a",
3183
- name: "Fix OpenAI to Anthropic Usage Mapping",
3184
- description: "Map OpenAI usage format to Anthropic format",
3185
- condition: (data, ctx) => {
3186
- return ctx.direction === "o2a" && ctx.stage === "response" && data?.usage?.prompt_tokens != null;
3187
- },
3188
- fix: (data) => {
3189
- const usage = data.usage;
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
- ...data,
3192
- usage: {
3193
- input_tokens: usage.prompt_tokens || 0,
3194
- output_tokens: usage.completion_tokens || 0,
3195
- cache_read_input_tokens: usage.prompt_tokens_details?.cached_tokens || void 0
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
- priority: 8
3160
+ }
3161
+ return { retryRecommended: false, reason: "Healing failed" };
3162
+ }
3200
3163
  },
3201
- // Finish reason 修复
3164
+ // 模型不可用恢复
3202
3165
  {
3203
- id: "fix-finish-reason-mapping",
3204
- name: "Fix Finish Reason Mapping",
3205
- description: "Map OpenAI finish_reason to Anthropic stop_reason",
3206
- condition: (data, ctx) => {
3207
- return ctx.direction === "o2a" && ctx.stage === "response" && data?.choices?.[0]?.finish_reason;
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
- fix: (data) => {
3210
- const choice = data.choices[0];
3211
- let stop_reason = "end_turn";
3212
- switch (choice.finish_reason) {
3213
- case "length":
3214
- stop_reason = "max_tokens";
3215
- break;
3216
- case "tool_calls":
3217
- stop_reason = "tool_use";
3218
- break;
3219
- case "stop":
3220
- default:
3221
- stop_reason = "end_turn";
3222
- break;
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
- ...data,
3226
- stop_reason,
3227
- stop_sequence: null
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: "fix-content-format-o2a",
3235
- name: "Fix Content Format for Anthropic",
3236
- description: "Convert OpenAI message content to Anthropic content blocks",
3237
- condition: (data, ctx) => {
3238
- return ctx.direction === "o2a" && ctx.stage === "response" && data?.choices?.[0]?.message;
3239
- },
3240
- fix: (data) => {
3241
- const message = data.choices[0].message;
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
- id: data.id || `msg_${Date.now()}`,
3261
- type: "message",
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 STREAM_HEALING_STRATEGIES = [
3274
- // SSE 格式修复
3275
- {
3276
- id: "fix-sse-format",
3277
- name: "Fix SSE Format",
3278
- description: "Ensure proper SSE event format",
3279
- condition: (data, ctx) => {
3280
- return ctx.stage === "stream" && typeof data === "string" && !data.startsWith("data: ");
3281
- },
3282
- fix: (data) => {
3283
- if (data.trim() === "[DONE]") {
3284
- return "data: [DONE]\n\n";
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
- JSON.parse(data);
3288
- return `data: ${data}
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
- } catch {
3292
- return data;
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
- var EMERGENCY_HEALING_STRATEGIES = [
3318
- // 空数据修复
3319
- {
3320
- id: "emergency-null-data",
3321
- name: "Emergency Null Data Fix",
3322
- description: "Handle null or undefined data with safe defaults",
3323
- condition: (data) => data == null,
3324
- fix: (data, ctx) => {
3325
- if (ctx.stage === "request") {
3326
- return {
3327
- model: ctx.direction === "a2o" ? "gpt-3.5-turbo" : "claude-3-haiku-20240307",
3328
- messages: [{ role: "user", content: "Hello" }],
3329
- max_tokens: ctx.direction === "o2a" ? 100 : void 0
3330
- };
3331
- }
3332
- return {};
3333
- },
3334
- priority: 10
3335
- },
3336
- // 循环引用修复
3337
- {
3338
- id: "emergency-circular-reference",
3339
- name: "Emergency Circular Reference Fix",
3340
- description: "Remove circular references in data",
3341
- condition: (data) => {
3342
- try {
3343
- JSON.stringify(data);
3344
- return false;
3345
- } catch (error) {
3346
- return error.message.includes("circular");
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
- fix: (data) => {
3350
- const seen = /* @__PURE__ */ new WeakSet();
3351
- const cleanData = JSON.parse(JSON.stringify(data, (key, value) => {
3352
- if (typeof value === "object" && value !== null) {
3353
- if (seen.has(value)) {
3354
- return "[Circular Reference Removed]";
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
- return value;
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
- priority: 9
3380
- }
3381
- ];
3382
- function getAllHealingStrategies() {
3383
- return [
3384
- ...EMERGENCY_HEALING_STRATEGIES,
3385
- ...REQUEST_HEALING_STRATEGIES,
3386
- ...RESPONSE_HEALING_STRATEGIES,
3387
- ...STREAM_HEALING_STRATEGIES
3388
- ].sort((a, b) => b.priority - a.priority);
3389
- }
3390
- function getStrategiesForContext(context) {
3391
- const allStrategies = getAllHealingStrategies();
3392
- return allStrategies.filter((strategy) => {
3393
- try {
3394
- const testData = {};
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
- static analyzeError(error, context) {
3415
- const timestamp = Date.now();
3416
- if (error instanceof Error) {
3417
- return {
3418
- type: this.classifyError(error),
3419
- severity: this.assessSeverity(error),
3420
- message: error.message,
3421
- context,
3422
- timestamp,
3423
- stackTrace: error.stack
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
- * 根据 Error 对象分类错误类型
3482
+ * 转换Anthropic请求格式为OpenAI兼容格式 - 原有方法保持兼容
3456
3483
  */
3457
- static classifyError(error) {
3458
- const message = error.message.toLowerCase();
3459
- if (message.includes("validation") || message.includes("schema")) {
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
- if (message.includes("network") || message.includes("fetch") || message.includes("connection")) {
3466
- return "network_error";
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
- if (message.includes("auth") || message.includes("unauthorized")) {
3469
- return "auth_error";
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 (message.includes("rate limit") || message.includes("too many requests")) {
3472
- return "rate_limit_error";
3505
+ if (this.config.enableFormatValidation && !FormatValidator.validateOpenAIRequest(openaiRequest)) {
3506
+ throw new Error("Generated OpenAI request format is invalid");
3473
3507
  }
3474
- if (message.includes("quota") || message.includes("limit exceeded")) {
3475
- return "quota_error";
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.includes("convert") || message.includes("transform")) {
3478
- return "conversion_error";
3522
+ if (message?.tool_calls) {
3523
+ const toolUseContents = ToolConverter.convertOpenAIToolCallsToClaude(message.tool_calls);
3524
+ claudeContent.push(...toolUseContents);
3479
3525
  }
3480
- return "client_error";
3526
+ const claudeResponse = {
3527
+ role: "assistant",
3528
+ content: claudeContent
3529
+ };
3530
+ return claudeResponse;
3481
3531
  }
3482
3532
  /**
3483
- * 根据 HTTP 状态码分类错误
3533
+ * 转换工具定义列表
3484
3534
  */
3485
- static classifyHttpError(status) {
3486
- if (status === 401 || status === 403) return "auth_error";
3487
- if (status === 429) return "rate_limit_error";
3488
- if (status === 408 || status === 504) return "timeout_error";
3489
- if (status >= 500) return "server_error";
3490
- if (status >= 400) return "client_error";
3491
- return "unknown_error";
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
- static assessSeverity(error) {
3497
- const message = error.message.toLowerCase();
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
- * 评估 HTTP 错误严重程度
3551
+ * 验证OpenAI请求格式
3505
3552
  */
3506
- static assessHttpSeverity(status) {
3507
- if (status >= 500) return "critical";
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
- static classifyErrorMessage(message) {
3516
- const lowerMessage = message.toLowerCase();
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
- static assessMessageSeverity(message) {
3529
- const lowerMessage = message.toLowerCase();
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
- var RECOVERY_STRATEGIES = [
3537
- // 网络错误恢复
3538
- {
3539
- id: "network-retry",
3540
- name: "Network Error Retry",
3541
- description: "Retry request after network errors",
3542
- supportedErrors: ["network_error", "timeout_error"],
3543
- maxRetries: 3,
3544
- backoffMs: 1e3,
3545
- condition: (error) => ["network_error", "timeout_error"].includes(error.type),
3546
- recover: async (error, context) => {
3547
- await new Promise((resolve) => setTimeout(resolve, 1e3));
3548
- return { retryRecommended: true, backoffMs: 2e3 };
3549
- }
3550
- },
3551
- // 限流错误恢复
3552
- {
3553
- id: "rate-limit-backoff",
3554
- name: "Rate Limit Backoff",
3555
- description: "Exponential backoff for rate limit errors",
3556
- supportedErrors: ["rate_limit_error"],
3557
- maxRetries: 5,
3558
- backoffMs: 5e3,
3559
- condition: (error) => error.type === "rate_limit_error",
3560
- recover: async (error, context) => {
3561
- const backoffTime = Math.min(3e4, 5e3 * Math.pow(2, context.attemptCount));
3562
- await new Promise((resolve) => setTimeout(resolve, backoffTime));
3563
- return { retryRecommended: true, backoffMs: backoffTime };
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
- id: "auth-refresh",
3636
- name: "Authentication Refresh",
3637
- description: "Attempt to refresh authentication",
3638
- supportedErrors: ["auth_error"],
3639
- maxRetries: 1,
3640
- backoffMs: 0,
3641
- condition: (error) => error.type === "auth_error",
3642
- recover: async (error, context) => {
3643
- return {
3644
- retryRecommended: false,
3645
- reason: "Authentication refresh not implemented - check API keys"
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
- var ErrorRecovery = class {
3651
- constructor(customStrategies = [], maxGlobalRetries = 5) {
3652
- this.strategies = [...RECOVERY_STRATEGIES, ...customStrategies];
3653
- this.maxGlobalRetries = maxGlobalRetries;
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
- async attemptRecovery(error, context) {
3659
- const startTime = Date.now();
3660
- const errorInfo = ErrorDetector.analyzeError(error, context);
3661
- const messages = [];
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
- for (const strategy of applicableStrategies) {
3676
- if (context.attemptCount >= strategy.maxRetries) {
3677
- messages.push(`Strategy ${strategy.name} exceeded max retries (${strategy.maxRetries})`);
3678
- continue;
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 (context.attemptCount >= this.maxGlobalRetries) {
3681
- messages.push(`Global retry limit exceeded (${this.maxGlobalRetries})`);
3682
- break;
3673
+ if (!message.content) {
3674
+ message.content = "";
3683
3675
  }
3684
- try {
3685
- messages.push(`Attempting recovery with strategy: ${strategy.name}`);
3686
- const recoveryResult = await strategy.recover(errorInfo, context);
3687
- if (recoveryResult.retryRecommended) {
3688
- return {
3689
- success: true,
3690
- data: recoveryResult.healedData || context.originalData,
3691
- strategy: strategy.name,
3692
- attemptCount: context.attemptCount + 1,
3693
- totalTime: Date.now() - startTime,
3694
- messages: [
3695
- ...messages,
3696
- `Recovery successful with ${strategy.name}`,
3697
- ...recoveryResult.appliedFixes || []
3698
- ]
3699
- };
3700
- } else {
3701
- messages.push(`Strategy ${strategy.name} declined to retry: ${recoveryResult.reason || "Unknown reason"}`);
3702
- }
3703
- } catch (strategyError) {
3704
- messages.push(`Strategy ${strategy.name} failed: ${strategyError.message}`);
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
- success: false,
3709
- error: errorInfo,
3710
- attemptCount: context.attemptCount,
3711
- totalTime: Date.now() - startTime,
3712
- messages: [...messages, "All recovery strategies failed"]
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
- isRecoverable(error, context) {
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
- getRecoveryRecommendations(error, context) {
3730
- const errorInfo = ErrorDetector.analyzeError(error, context);
3731
- const applicableStrategies = this.strategies.filter(
3732
- (strategy) => strategy.condition(errorInfo, context)
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
- isRecoverable: this.isRecoverable(error, context),
3751
- strategies: applicableStrategies.map((s) => s.name),
3752
- estimatedTime,
3753
- confidence
3809
+ openaiRequest,
3810
+ metadata: {
3811
+ hasImages,
3812
+ requiresVisionHeaders: hasImages
3813
+ }
3754
3814
  };
3755
3815
  }
3756
3816
  /**
3757
- * 添加自定义恢复策略
3817
+ * 与StandardProtocolAdapter保持一致的API,用于集成测试和向后兼容。
3758
3818
  */
3759
- addStrategy(strategy) {
3760
- this.strategies.push(strategy);
3819
+ convertRequest(anthropicRequest) {
3820
+ return this.convertAnthropicToOpenAI(anthropicRequest);
3761
3821
  }
3762
3822
  /**
3763
- * 移除恢复策略
3823
+ * 转换OpenAI流式响应为Anthropic SSE格式
3764
3824
  */
3765
- removeStrategy(strategyId) {
3766
- const index = this.strategies.findIndex((s) => s.id === strategyId);
3767
- if (index >= 0) {
3768
- this.strategies.splice(index, 1);
3769
- return true;
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
- * 转换Anthropic请求格式为OpenAI兼容格式 - 增强版
3799
- * 集成校验、修复和错误恢复功能
3861
+ * 将OpenAI流转换为Anthropic SSE格式
3800
3862
  */
3801
- async convertAnthropicRequestToOpenAIEnhanced(anthropicRequest) {
3802
- const startTime = Date.now();
3803
- const result = {
3804
- success: false,
3805
- originalData: anthropicRequest,
3806
- warnings: [],
3807
- errors: [],
3808
- healingApplied: false,
3809
- appliedFixes: []
3810
- };
3811
- try {
3812
- let validatedInput;
3813
- if (this.config.validation.enabled) {
3814
- if (this.config.validation.strict) {
3815
- try {
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 validatedOutput = validateOpenAIRequest(openaiRequest);
3854
- result.data = validatedOutput;
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.validation.strict) {
3862
- result.errors.push(`Output validation failed: ${error.message}`);
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 openaiRequest;
3889
+ return sseLines.join("\n");
3915
3890
  }
3916
3891
  /**
3917
- * 转换Anthropic请求格式为OpenAI兼容格式 - 原有方法保持兼容
3892
+ * 处理单个流式数据块 - 支持thinking和content双模式
3918
3893
  */
3919
- convertAnthropicRequestToOpenAI(anthropicRequest) {
3920
- if (this.config.enableFormatValidation) {
3921
- FormatValidator.validateClaudeRequest(anthropicRequest);
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 specialFields = ["_anthropic_protocol", "_rovo_tool_injected", "_routeResult"];
3935
- for (const field of specialFields) {
3936
- if (anthropicRequest[field] !== void 0) {
3937
- openaiRequest[field] = anthropicRequest[field];
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 (this.config.enableFormatValidation && !FormatValidator.validateOpenAIRequest(openaiRequest)) {
3941
- throw new Error("Generated OpenAI request format is invalid");
3908
+ if (!choice) {
3909
+ this.updateUsageFromChunk(chunk, state);
3910
+ return;
3942
3911
  }
3943
- return openaiRequest;
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
- * 转换OpenAI响应格式为Claude兼容格式
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
- convertOpenAIResponseToClaude(openaiResponse) {
3949
- const claudeContent = [];
3950
- const message = openaiResponse.choices?.[0]?.message;
3951
- if (message?.content) {
3952
- claudeContent.push({
3953
- type: "text",
3954
- text: message.content
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
- if (message?.tool_calls) {
3958
- const toolUseContents = ToolConverter.convertOpenAIToolCallsToClaude(message.tool_calls);
3959
- claudeContent.push(...toolUseContents);
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
- const claudeResponse = {
3962
- role: "assistant",
3963
- content: claudeContent
3964
- };
3965
- return claudeResponse;
3975
+ return existing;
3966
3976
  }
3967
- /**
3968
- * 转换工具定义列表
3969
- */
3970
- convertToolDefinitions(tools) {
3971
- return tools.map((tool) => {
3972
- if (ToolConverter.isOpenAIToolFormat(tool)) {
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
- * 验证Claude请求格式
3981
- */
3982
- validateClaudeRequest(request) {
3983
- return FormatValidator.validateClaudeRequest(request);
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
- * 验证OpenAI请求格式
3987
- */
3988
- validateOpenAIRequest(request) {
3989
- return FormatValidator.validateOpenAIRequest(request);
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
- getSupportedTools() {
3995
- return [];
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
- isToolSupported(_toolName) {
4001
- return true;
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
- getToolMapping(claudeToolName) {
4007
- return claudeToolName;
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
- updateConfig(newConfig) {
4013
- this.config = { ...this.config, ...newConfig };
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
- performCoreConversionWithValidation(anthropicRequest) {
4026
- if (this.config.validation.enabled) {
4027
- try {
4028
- validateAnthropicRequest(anthropicRequest);
4029
- } catch (error) {
4030
- if (this.config.validation.strict) {
4031
- throw error;
4032
- } else {
4033
- const errorSummary = this.getValidationErrorSummary(error);
4034
- console.warn(`[A2ORequestAdapter] Input validation warning: ${errorSummary}. Details saved to logs.`);
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
- let processedRequest = anthropicRequest;
4039
- if (this.config.healing.enabled) {
4040
- try {
4041
- processedRequest = this.applySyncHealing(anthropicRequest);
4042
- } catch (healingError) {
4043
- console.warn("[A2ORequestAdapter] Healing failed:", healingError);
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
- const result = this.performBasicConversion(processedRequest, true);
4047
- if (this.config.validation.enabled) {
4048
- try {
4049
- validateOpenAIRequest(result);
4050
- } catch (error) {
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
- return result;
4149
+ for (const alias of aliases) {
4150
+ this.registerToolCallAlias(state, alias, toolData);
4151
+ }
4152
+ return toolData;
4059
4153
  }
4060
- /**
4061
- * 执行基础转换逻辑(原有逻辑的提取)
4062
- */
4063
- performBasicConversion(anthropicRequest, skipValidation = false) {
4064
- if (!skipValidation && this.config.enableFormatValidation) {
4065
- FormatValidator.validateClaudeRequest(anthropicRequest);
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 openaiRequest = {
4068
- model: anthropicRequest.model,
4069
- messages: MessageConverter.convertMessages(anthropicRequest.messages, anthropicRequest.system),
4070
- max_tokens: anthropicRequest.max_tokens,
4071
- temperature: anthropicRequest.temperature,
4072
- stream: anthropicRequest.stream,
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 specialFields = ["_anthropic_protocol", "_rovo_tool_injected", "_routeResult"];
4079
- for (const field of specialFields) {
4080
- if (anthropicRequest[field] !== void 0) {
4081
- openaiRequest[field] = anthropicRequest[field];
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
- if (this.config.enableFormatValidation && !FormatValidator.validateOpenAIRequest(openaiRequest)) {
4085
- throw new Error("Generated OpenAI request format is invalid");
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 openaiRequest;
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
- applySyncHealing(request) {
4094
- const healedRequest = { ...request };
4095
- if (!healedRequest.max_tokens || healedRequest.max_tokens <= 0) {
4096
- healedRequest.max_tokens = 4096;
4097
- }
4098
- if (!healedRequest.messages || !Array.isArray(healedRequest.messages)) {
4099
- throw new Error("Invalid messages array");
4100
- }
4101
- if (!healedRequest.model) {
4102
- healedRequest.model = "claude-sonnet-4";
4103
- }
4104
- for (const message of healedRequest.messages) {
4105
- if (!message.role) {
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 (!message.content) {
4109
- message.content = "";
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
- getValidationErrors(request, type) {
4118
- return FormatValidator.getValidationErrors(request, type);
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
- getValidationErrorSummary(error) {
4124
- if (error?.issues?.length > 0) {
4125
- const invalidEnums = error.issues.filter((i) => i.code === "invalid_enum_value");
4126
- const missingFields = error.issues.filter((i) => i.code === "invalid_type");
4127
- const summary = [];
4128
- if (invalidEnums.length > 0) {
4129
- const first = invalidEnums[0];
4130
- summary.push(`invalid_${first.path?.join(".")}: '${first.received}'`);
4131
- }
4132
- if (missingFields.length > 0) {
4133
- summary.push(`${missingFields.length} missing fields`);
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
- return error.message || "Validation failed";
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
- * 转换Anthropic请求格式为OpenAI兼容格式(静态方法)
4143
- * 内部使用增强转换器,所有调用点自动获得增强功能
4354
+ * 创建转换状态对象
4144
4355
  */
4145
- convertAnthropicRequestToOpenAI: (anthropicRequest) => {
4146
- const adapter = new A2ORequestAdapter({
4147
- debugMode: false,
4148
- maxDescriptionLength: 100,
4149
- enableToolNameValidation: true,
4150
- enableFormatValidation: true,
4151
- validation: { enabled: true, strict: false },
4152
- healing: { enabled: true, maxAttempts: 2, enableCustomRules: true },
4153
- recovery: { enabled: false, maxRetries: 0, backoffMs: 1e3 },
4154
- monitoring: { enabled: false, logLevel: "none", enableMetrics: false }
4155
- });
4156
- try {
4157
- const result = adapter.performCoreConversionWithValidation(anthropicRequest);
4158
- return result;
4159
- } catch (error) {
4160
- console.warn(`[A2ORequestAdapterStatic] Enhanced conversion failed, using basic conversion: ${error?.message || error}`);
4161
- return adapter.performBasicConversion(anthropicRequest, true);
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
- * 转换OpenAI响应格式为Claude兼容格式(静态方法)
4166
- * 内部使用增强转换器
4376
+ * 转换消息格式
4167
4377
  */
4168
- convertOpenAIResponseToClaude: (openaiResponse) => {
4169
- const adapter = new A2ORequestAdapter({
4170
- debugMode: false,
4171
- maxDescriptionLength: 100,
4172
- enableToolNameValidation: true,
4173
- enableFormatValidation: true,
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
- * 验证Claude请求格式(静态方法)
4385
+ * 映射Anthropic模型到OpenAI模型
4183
4386
  */
4184
- validateClaudeRequest: (request) => {
4185
- return FormatValidator.validateClaudeRequest(request);
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
- * 验证OpenAI请求格式(静态方法)
4410
+ * 检查请求是否包含图片内容
4189
4411
  */
4190
- validateOpenAIRequest: (request) => {
4191
- return FormatValidator.validateOpenAIRequest(request);
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
- getSupportedTools: () => {
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
- isToolSupported: (_toolName) => {
4203
- return true;
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
- getToolMapping: (claudeToolName) => {
4209
- return claudeToolName;
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
  }