ai-protocol-adapters 1.0.0-alpha.8 → 1.0.0-alpha.9

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