ai-protocol-adapters 1.0.0-alpha.17 → 1.0.0-alpha.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -5606,6 +5606,11 @@ interface A2ORequestAdapterConfig {
5606
5606
  logLevel: 'none' | 'error' | 'warn' | 'info' | 'debug';
5607
5607
  enableMetrics: boolean;
5608
5608
  };
5609
+ imageProxy: {
5610
+ enabled: boolean;
5611
+ timeout: number;
5612
+ maxSize: number;
5613
+ };
5609
5614
  }
5610
5615
 
5611
5616
  /**
@@ -5626,9 +5631,9 @@ declare class A2ORequestAdapter {
5626
5631
  */
5627
5632
  convertAnthropicRequestToOpenAIEnhanced(anthropicRequest: unknown): Promise<ConversionResult$2<OpenAIRequest>>;
5628
5633
  /**
5629
- * 执行核心转换逻辑(原有逻辑保持不变)
5634
+ * 执行核心转换逻辑(支持图片代理)
5630
5635
  */
5631
- private performCoreConversion;
5636
+ performCoreConversion(anthropicRequest: ClaudeRequest): Promise<any>;
5632
5637
  /**
5633
5638
  * 转换Anthropic请求格式为OpenAI兼容格式 - 原有方法保持兼容
5634
5639
  */
@@ -5723,8 +5728,35 @@ declare const A2ORequestAdapterStatic: {
5723
5728
  * 获取工具映射(静态方法,已弃用)
5724
5729
  */
5725
5730
  getToolMapping: (claudeToolName: string) => string | undefined;
5731
+ /**
5732
+ * 转换Anthropic请求格式为OpenAI兼容格式(异步版本,支持图片URL自动下载)
5733
+ * 解决GitHub Copilot等API不支持外部图片URL的问题
5734
+ * @param anthropicRequest Claude格式的请求
5735
+ * @param downloadImageUrls 是否下载图片URL并转换为base64(默认true)
5736
+ */
5737
+ convertAnthropicRequestToOpenAIAsync: (anthropicRequest: ClaudeRequest, downloadImageUrls?: boolean) => Promise<OpenAIRequest>;
5726
5738
  };
5727
5739
 
5740
+ /**
5741
+ * 从URL下载图片并转换为base64 data URI
5742
+ * @param url 图片URL
5743
+ * @param options 下载选项
5744
+ * @returns base64 data URI (例如: "data:image/jpeg;base64,/9j/4AAQ...")
5745
+ */
5746
+ declare function downloadImageAsBase64(url: string, options?: {
5747
+ timeout?: number;
5748
+ maxSize?: number;
5749
+ userAgent?: string;
5750
+ }): Promise<string>;
5751
+ /**
5752
+ * 判断URL是否是外部URL(http/https开头)
5753
+ */
5754
+ declare function isExternalUrl(url: string): boolean;
5755
+ /**
5756
+ * 判断URL是否已经是base64 data URI
5757
+ */
5758
+ declare function isBase64DataUri(url: string): boolean;
5759
+
5728
5760
  /**
5729
5761
  * A2O格式验证器
5730
5762
  * 专门处理Claude和OpenAI请求格式的验证
@@ -5799,10 +5831,6 @@ declare class FormatValidator {
5799
5831
  };
5800
5832
  }
5801
5833
 
5802
- /**
5803
- * 流式协议适配器 - OpenAI到Anthropic的SSE转换
5804
- * 基于编译后的JavaScript文件重新生成的TypeScript源码
5805
- */
5806
5834
  interface ToolCallState {
5807
5835
  id: string;
5808
5836
  name: string;
@@ -5845,6 +5873,13 @@ interface ConversionResult$1 {
5845
5873
  anthropicSSE: string;
5846
5874
  anthropicStandardResponse: any;
5847
5875
  }
5876
+ interface AnthropicToOpenAIChunkResult {
5877
+ buffer: string;
5878
+ chunks: OpenAIStreamChunk[];
5879
+ finishReason?: OpenAIFinishReason;
5880
+ streamStopped: boolean;
5881
+ }
5882
+ type OpenAIFinishReason = NonNullable<OpenAIStreamChunk['choices'][number]['finish_reason']>;
5848
5883
  declare class StreamingProtocolAdapter {
5849
5884
  private config;
5850
5885
  constructor(options?: StreamingProtocolAdapterOptions);
@@ -5873,6 +5908,16 @@ declare class StreamingProtocolAdapter {
5873
5908
  * 转换OpenAI流式响应为Anthropic SSE格式
5874
5909
  */
5875
5910
  convertOpenAIStreamToAnthropic(openaiStream: string, originalRequest: any): ConversionResult$1;
5911
+ /**
5912
+ * 增量解析Anthropic SSE,转换为OpenAI流式chunk
5913
+ * 供 OpenAI Chat Completions 端点直接复用
5914
+ */
5915
+ convertAnthropicSSEChunkToOpenAI(params: {
5916
+ buffer: string;
5917
+ chunk: string;
5918
+ model: string;
5919
+ flush?: boolean;
5920
+ }): AnthropicToOpenAIChunkResult;
5876
5921
  /**
5877
5922
  * 将OpenAI流转换为Anthropic SSE格式
5878
5923
  */
@@ -5922,6 +5967,10 @@ declare class StreamingProtocolAdapter {
5922
5967
  * 创建转换状态对象
5923
5968
  */
5924
5969
  private createConversionState;
5970
+ private parseAnthropicSSEEvent;
5971
+ private extractTextFromAnthropicDelta;
5972
+ private mapAnthropicStopReasonToOpenAI;
5973
+ private buildOpenAIStreamChunk;
5925
5974
  /**
5926
5975
  * 转换消息格式
5927
5976
  */
@@ -7333,4 +7382,4 @@ declare const FEATURES: {
7333
7382
  readonly externalPackageVerified: true;
7334
7383
  };
7335
7384
 
7336
- export { A2ORequestAdapter, A2ORequestAdapterStatic, ADAPTERS_VERSION, type AnthropicContentBlock, AnthropicContentBlockBaseSchema, AnthropicContentBlockDeltaEventSchema, AnthropicContentBlockSchema, AnthropicContentBlockStartEventSchema, AnthropicContentBlockStopEventSchema, AnthropicErrorEventSchema, type AnthropicImageContentBlock, AnthropicImageContentBlockSchema, AnthropicImageSourceSchema, type AnthropicMessage, AnthropicMessageDeltaEventSchema, AnthropicMessageSchema, AnthropicMessageStartEventSchema, AnthropicMessageStopEventSchema, AnthropicMetadataSchema, AnthropicPingEventSchema, type AnthropicRequest, AnthropicRequestSchema, type AnthropicResponse, AnthropicResponseSchema, type AnthropicSDKConfig, AnthropicSDKWrapper, type AnthropicStreamEvent, AnthropicStreamEventBaseSchema, AnthropicStreamEventSchema, AnthropicSystemMessageSchema, type AnthropicTextContentBlock, AnthropicTextContentBlockSchema, type AnthropicTool, AnthropicToolInputSchemaSchema, type AnthropicToolResultContentBlock, AnthropicToolResultContentBlockSchema, AnthropicToolSchema, type AnthropicToolUseContentBlock, AnthropicToolUseContentBlockSchema, type AnthropicUsage, AnthropicUsageSchema, type BaseSDKClient, A2ORequestAdapter as ClaudeToOpenAIConverter, type ConversionFixResult, ConversionFixer, type ConversionResult$1 as ConversionResult, type ConversionState, ConversionValidator, type ConvertedRequest, type ConvertedResponse, EMERGENCY_HEALING_STRATEGIES, type EnhancedAdapterOptions, type EnhancedConversionResult, ErrorDetector, type ErrorInfo, ErrorRecovery, type ErrorSeverity, type ErrorType, FEATURES, FormatValidator, type HealingContext, type HealingResult, type HealingRule, StandardProtocolAdapter as NonStreamingIOConverter, O2ASSEAdapter, O2ASSEAdapterStatic, type OpenAIChoice, OpenAIChoiceSchema, OpenAIFunctionParametersSchema, OpenAIFunctionSchema, type OpenAIMessage, OpenAIMessageContentSchema, OpenAIMessageSchema, type OpenAIRequest$1 as OpenAIRequest, OpenAIRequestSchema, type OpenAIResponse$1 as OpenAIResponse, OpenAIResponseSchema, type OpenAISDKConfig, OpenAISDKWrapper, type OpenAIStreamChunk, OpenAIStreamChunkSchema, O2ASSEAdapter as OpenAIToClaudeSSEConverter, type OpenAITool, type OpenAIToolCall, OpenAIToolCallSchema, OpenAIToolSchema, type OpenAIUsage, OpenAIUsageSchema, ProtocolHealer, RECOVERY_STRATEGIES, REQUEST_HEALING_STRATEGIES, RESPONSE_HEALING_STRATEGIES, type RecoveryResult, type RecoveryStrategy, type RequestOptions, type SDKConfig, SDKFactory, type SDKProvider, type SDKResponse, type SDKStreamEvent, SSEEventGenerator, STREAM_HEALING_STRATEGIES, type ConversionResult as SchemaConversionResult, StandardProtocolAdapter as StandardGateway, StandardProtocolAdapter, type StandardProtocolAdapterOptions, StreamingProtocolAdapter as StreamingGateway, StreamingProtocolAdapter as StreamingIOConverter, StreamingProtocolAdapter, type StreamingProtocolAdapterOptions, type UnifiedSDKConfig, type ValidationResult, attemptRecovery, conversionValidator, createAnthropicSDK, createOpenAISDK, createValidator, errorRecovery, getAllHealingStrategies, getGlobalLogger, getRecoveryRecommendations, getStrategiesForContext, healA2ORequest, healO2ARequest, healO2AResponse, healingValidate, isRecoverable, protocolHealer, safeValidate, sdkFactory, selectSDKByModel, strictValidate, validateA2OConversion, validateAnthropicRequest, validateAnthropicResponse, validateAnthropicStreamEvent, validateBatch, validateO2AConversion, validateOpenAIRequest, validateOpenAIResponse, validateOpenAIStreamChunk, validateSDKConfig, validateStreamConversion };
7385
+ export { A2ORequestAdapter, A2ORequestAdapterStatic, ADAPTERS_VERSION, type AnthropicContentBlock, AnthropicContentBlockBaseSchema, AnthropicContentBlockDeltaEventSchema, AnthropicContentBlockSchema, AnthropicContentBlockStartEventSchema, AnthropicContentBlockStopEventSchema, AnthropicErrorEventSchema, type AnthropicImageContentBlock, AnthropicImageContentBlockSchema, AnthropicImageSourceSchema, type AnthropicMessage, AnthropicMessageDeltaEventSchema, AnthropicMessageSchema, AnthropicMessageStartEventSchema, AnthropicMessageStopEventSchema, AnthropicMetadataSchema, AnthropicPingEventSchema, type AnthropicRequest, AnthropicRequestSchema, type AnthropicResponse, AnthropicResponseSchema, type AnthropicSDKConfig, AnthropicSDKWrapper, type AnthropicStreamEvent, AnthropicStreamEventBaseSchema, AnthropicStreamEventSchema, AnthropicSystemMessageSchema, type AnthropicTextContentBlock, AnthropicTextContentBlockSchema, type AnthropicToOpenAIChunkResult, type AnthropicTool, AnthropicToolInputSchemaSchema, type AnthropicToolResultContentBlock, AnthropicToolResultContentBlockSchema, AnthropicToolSchema, type AnthropicToolUseContentBlock, AnthropicToolUseContentBlockSchema, type AnthropicUsage, AnthropicUsageSchema, type BaseSDKClient, A2ORequestAdapter as ClaudeToOpenAIConverter, type ConversionFixResult, ConversionFixer, type ConversionResult$1 as ConversionResult, type ConversionState, ConversionValidator, type ConvertedRequest, type ConvertedResponse, EMERGENCY_HEALING_STRATEGIES, type EnhancedAdapterOptions, type EnhancedConversionResult, ErrorDetector, type ErrorInfo, ErrorRecovery, type ErrorSeverity, type ErrorType, FEATURES, FormatValidator, type HealingContext, type HealingResult, type HealingRule, StandardProtocolAdapter as NonStreamingIOConverter, O2ASSEAdapter, O2ASSEAdapterStatic, type OpenAIChoice, OpenAIChoiceSchema, OpenAIFunctionParametersSchema, OpenAIFunctionSchema, type OpenAIMessage, OpenAIMessageContentSchema, OpenAIMessageSchema, type OpenAIRequest$1 as OpenAIRequest, OpenAIRequestSchema, type OpenAIResponse$1 as OpenAIResponse, OpenAIResponseSchema, type OpenAISDKConfig, OpenAISDKWrapper, type OpenAIStreamChunk, OpenAIStreamChunkSchema, O2ASSEAdapter as OpenAIToClaudeSSEConverter, type OpenAITool, type OpenAIToolCall, OpenAIToolCallSchema, OpenAIToolSchema, type OpenAIUsage, OpenAIUsageSchema, ProtocolHealer, RECOVERY_STRATEGIES, REQUEST_HEALING_STRATEGIES, RESPONSE_HEALING_STRATEGIES, type RecoveryResult, type RecoveryStrategy, type RequestOptions, type SDKConfig, SDKFactory, type SDKProvider, type SDKResponse, type SDKStreamEvent, SSEEventGenerator, STREAM_HEALING_STRATEGIES, type ConversionResult as SchemaConversionResult, StandardProtocolAdapter as StandardGateway, StandardProtocolAdapter, type StandardProtocolAdapterOptions, StreamingProtocolAdapter as StreamingGateway, StreamingProtocolAdapter as StreamingIOConverter, StreamingProtocolAdapter, type StreamingProtocolAdapterOptions, type UnifiedSDKConfig, type ValidationResult, attemptRecovery, conversionValidator, createAnthropicSDK, createOpenAISDK, createValidator, downloadImageAsBase64, errorRecovery, getAllHealingStrategies, getGlobalLogger, getRecoveryRecommendations, getStrategiesForContext, healA2ORequest, healO2ARequest, healO2AResponse, healingValidate, isBase64DataUri, isExternalUrl, isRecoverable, protocolHealer, safeValidate, sdkFactory, selectSDKByModel, strictValidate, validateA2OConversion, validateAnthropicRequest, validateAnthropicResponse, validateAnthropicStreamEvent, validateBatch, validateO2AConversion, validateOpenAIRequest, validateOpenAIResponse, validateOpenAIStreamChunk, validateSDKConfig, validateStreamConversion };
package/dist/index.d.ts CHANGED
@@ -5606,6 +5606,11 @@ interface A2ORequestAdapterConfig {
5606
5606
  logLevel: 'none' | 'error' | 'warn' | 'info' | 'debug';
5607
5607
  enableMetrics: boolean;
5608
5608
  };
5609
+ imageProxy: {
5610
+ enabled: boolean;
5611
+ timeout: number;
5612
+ maxSize: number;
5613
+ };
5609
5614
  }
5610
5615
 
5611
5616
  /**
@@ -5626,9 +5631,9 @@ declare class A2ORequestAdapter {
5626
5631
  */
5627
5632
  convertAnthropicRequestToOpenAIEnhanced(anthropicRequest: unknown): Promise<ConversionResult$2<OpenAIRequest>>;
5628
5633
  /**
5629
- * 执行核心转换逻辑(原有逻辑保持不变)
5634
+ * 执行核心转换逻辑(支持图片代理)
5630
5635
  */
5631
- private performCoreConversion;
5636
+ performCoreConversion(anthropicRequest: ClaudeRequest): Promise<any>;
5632
5637
  /**
5633
5638
  * 转换Anthropic请求格式为OpenAI兼容格式 - 原有方法保持兼容
5634
5639
  */
@@ -5723,8 +5728,35 @@ declare const A2ORequestAdapterStatic: {
5723
5728
  * 获取工具映射(静态方法,已弃用)
5724
5729
  */
5725
5730
  getToolMapping: (claudeToolName: string) => string | undefined;
5731
+ /**
5732
+ * 转换Anthropic请求格式为OpenAI兼容格式(异步版本,支持图片URL自动下载)
5733
+ * 解决GitHub Copilot等API不支持外部图片URL的问题
5734
+ * @param anthropicRequest Claude格式的请求
5735
+ * @param downloadImageUrls 是否下载图片URL并转换为base64(默认true)
5736
+ */
5737
+ convertAnthropicRequestToOpenAIAsync: (anthropicRequest: ClaudeRequest, downloadImageUrls?: boolean) => Promise<OpenAIRequest>;
5726
5738
  };
5727
5739
 
5740
+ /**
5741
+ * 从URL下载图片并转换为base64 data URI
5742
+ * @param url 图片URL
5743
+ * @param options 下载选项
5744
+ * @returns base64 data URI (例如: "data:image/jpeg;base64,/9j/4AAQ...")
5745
+ */
5746
+ declare function downloadImageAsBase64(url: string, options?: {
5747
+ timeout?: number;
5748
+ maxSize?: number;
5749
+ userAgent?: string;
5750
+ }): Promise<string>;
5751
+ /**
5752
+ * 判断URL是否是外部URL(http/https开头)
5753
+ */
5754
+ declare function isExternalUrl(url: string): boolean;
5755
+ /**
5756
+ * 判断URL是否已经是base64 data URI
5757
+ */
5758
+ declare function isBase64DataUri(url: string): boolean;
5759
+
5728
5760
  /**
5729
5761
  * A2O格式验证器
5730
5762
  * 专门处理Claude和OpenAI请求格式的验证
@@ -5799,10 +5831,6 @@ declare class FormatValidator {
5799
5831
  };
5800
5832
  }
5801
5833
 
5802
- /**
5803
- * 流式协议适配器 - OpenAI到Anthropic的SSE转换
5804
- * 基于编译后的JavaScript文件重新生成的TypeScript源码
5805
- */
5806
5834
  interface ToolCallState {
5807
5835
  id: string;
5808
5836
  name: string;
@@ -5845,6 +5873,13 @@ interface ConversionResult$1 {
5845
5873
  anthropicSSE: string;
5846
5874
  anthropicStandardResponse: any;
5847
5875
  }
5876
+ interface AnthropicToOpenAIChunkResult {
5877
+ buffer: string;
5878
+ chunks: OpenAIStreamChunk[];
5879
+ finishReason?: OpenAIFinishReason;
5880
+ streamStopped: boolean;
5881
+ }
5882
+ type OpenAIFinishReason = NonNullable<OpenAIStreamChunk['choices'][number]['finish_reason']>;
5848
5883
  declare class StreamingProtocolAdapter {
5849
5884
  private config;
5850
5885
  constructor(options?: StreamingProtocolAdapterOptions);
@@ -5873,6 +5908,16 @@ declare class StreamingProtocolAdapter {
5873
5908
  * 转换OpenAI流式响应为Anthropic SSE格式
5874
5909
  */
5875
5910
  convertOpenAIStreamToAnthropic(openaiStream: string, originalRequest: any): ConversionResult$1;
5911
+ /**
5912
+ * 增量解析Anthropic SSE,转换为OpenAI流式chunk
5913
+ * 供 OpenAI Chat Completions 端点直接复用
5914
+ */
5915
+ convertAnthropicSSEChunkToOpenAI(params: {
5916
+ buffer: string;
5917
+ chunk: string;
5918
+ model: string;
5919
+ flush?: boolean;
5920
+ }): AnthropicToOpenAIChunkResult;
5876
5921
  /**
5877
5922
  * 将OpenAI流转换为Anthropic SSE格式
5878
5923
  */
@@ -5922,6 +5967,10 @@ declare class StreamingProtocolAdapter {
5922
5967
  * 创建转换状态对象
5923
5968
  */
5924
5969
  private createConversionState;
5970
+ private parseAnthropicSSEEvent;
5971
+ private extractTextFromAnthropicDelta;
5972
+ private mapAnthropicStopReasonToOpenAI;
5973
+ private buildOpenAIStreamChunk;
5925
5974
  /**
5926
5975
  * 转换消息格式
5927
5976
  */
@@ -7333,4 +7382,4 @@ declare const FEATURES: {
7333
7382
  readonly externalPackageVerified: true;
7334
7383
  };
7335
7384
 
7336
- export { A2ORequestAdapter, A2ORequestAdapterStatic, ADAPTERS_VERSION, type AnthropicContentBlock, AnthropicContentBlockBaseSchema, AnthropicContentBlockDeltaEventSchema, AnthropicContentBlockSchema, AnthropicContentBlockStartEventSchema, AnthropicContentBlockStopEventSchema, AnthropicErrorEventSchema, type AnthropicImageContentBlock, AnthropicImageContentBlockSchema, AnthropicImageSourceSchema, type AnthropicMessage, AnthropicMessageDeltaEventSchema, AnthropicMessageSchema, AnthropicMessageStartEventSchema, AnthropicMessageStopEventSchema, AnthropicMetadataSchema, AnthropicPingEventSchema, type AnthropicRequest, AnthropicRequestSchema, type AnthropicResponse, AnthropicResponseSchema, type AnthropicSDKConfig, AnthropicSDKWrapper, type AnthropicStreamEvent, AnthropicStreamEventBaseSchema, AnthropicStreamEventSchema, AnthropicSystemMessageSchema, type AnthropicTextContentBlock, AnthropicTextContentBlockSchema, type AnthropicTool, AnthropicToolInputSchemaSchema, type AnthropicToolResultContentBlock, AnthropicToolResultContentBlockSchema, AnthropicToolSchema, type AnthropicToolUseContentBlock, AnthropicToolUseContentBlockSchema, type AnthropicUsage, AnthropicUsageSchema, type BaseSDKClient, A2ORequestAdapter as ClaudeToOpenAIConverter, type ConversionFixResult, ConversionFixer, type ConversionResult$1 as ConversionResult, type ConversionState, ConversionValidator, type ConvertedRequest, type ConvertedResponse, EMERGENCY_HEALING_STRATEGIES, type EnhancedAdapterOptions, type EnhancedConversionResult, ErrorDetector, type ErrorInfo, ErrorRecovery, type ErrorSeverity, type ErrorType, FEATURES, FormatValidator, type HealingContext, type HealingResult, type HealingRule, StandardProtocolAdapter as NonStreamingIOConverter, O2ASSEAdapter, O2ASSEAdapterStatic, type OpenAIChoice, OpenAIChoiceSchema, OpenAIFunctionParametersSchema, OpenAIFunctionSchema, type OpenAIMessage, OpenAIMessageContentSchema, OpenAIMessageSchema, type OpenAIRequest$1 as OpenAIRequest, OpenAIRequestSchema, type OpenAIResponse$1 as OpenAIResponse, OpenAIResponseSchema, type OpenAISDKConfig, OpenAISDKWrapper, type OpenAIStreamChunk, OpenAIStreamChunkSchema, O2ASSEAdapter as OpenAIToClaudeSSEConverter, type OpenAITool, type OpenAIToolCall, OpenAIToolCallSchema, OpenAIToolSchema, type OpenAIUsage, OpenAIUsageSchema, ProtocolHealer, RECOVERY_STRATEGIES, REQUEST_HEALING_STRATEGIES, RESPONSE_HEALING_STRATEGIES, type RecoveryResult, type RecoveryStrategy, type RequestOptions, type SDKConfig, SDKFactory, type SDKProvider, type SDKResponse, type SDKStreamEvent, SSEEventGenerator, STREAM_HEALING_STRATEGIES, type ConversionResult as SchemaConversionResult, StandardProtocolAdapter as StandardGateway, StandardProtocolAdapter, type StandardProtocolAdapterOptions, StreamingProtocolAdapter as StreamingGateway, StreamingProtocolAdapter as StreamingIOConverter, StreamingProtocolAdapter, type StreamingProtocolAdapterOptions, type UnifiedSDKConfig, type ValidationResult, attemptRecovery, conversionValidator, createAnthropicSDK, createOpenAISDK, createValidator, errorRecovery, getAllHealingStrategies, getGlobalLogger, getRecoveryRecommendations, getStrategiesForContext, healA2ORequest, healO2ARequest, healO2AResponse, healingValidate, isRecoverable, protocolHealer, safeValidate, sdkFactory, selectSDKByModel, strictValidate, validateA2OConversion, validateAnthropicRequest, validateAnthropicResponse, validateAnthropicStreamEvent, validateBatch, validateO2AConversion, validateOpenAIRequest, validateOpenAIResponse, validateOpenAIStreamChunk, validateSDKConfig, validateStreamConversion };
7385
+ export { A2ORequestAdapter, A2ORequestAdapterStatic, ADAPTERS_VERSION, type AnthropicContentBlock, AnthropicContentBlockBaseSchema, AnthropicContentBlockDeltaEventSchema, AnthropicContentBlockSchema, AnthropicContentBlockStartEventSchema, AnthropicContentBlockStopEventSchema, AnthropicErrorEventSchema, type AnthropicImageContentBlock, AnthropicImageContentBlockSchema, AnthropicImageSourceSchema, type AnthropicMessage, AnthropicMessageDeltaEventSchema, AnthropicMessageSchema, AnthropicMessageStartEventSchema, AnthropicMessageStopEventSchema, AnthropicMetadataSchema, AnthropicPingEventSchema, type AnthropicRequest, AnthropicRequestSchema, type AnthropicResponse, AnthropicResponseSchema, type AnthropicSDKConfig, AnthropicSDKWrapper, type AnthropicStreamEvent, AnthropicStreamEventBaseSchema, AnthropicStreamEventSchema, AnthropicSystemMessageSchema, type AnthropicTextContentBlock, AnthropicTextContentBlockSchema, type AnthropicToOpenAIChunkResult, type AnthropicTool, AnthropicToolInputSchemaSchema, type AnthropicToolResultContentBlock, AnthropicToolResultContentBlockSchema, AnthropicToolSchema, type AnthropicToolUseContentBlock, AnthropicToolUseContentBlockSchema, type AnthropicUsage, AnthropicUsageSchema, type BaseSDKClient, A2ORequestAdapter as ClaudeToOpenAIConverter, type ConversionFixResult, ConversionFixer, type ConversionResult$1 as ConversionResult, type ConversionState, ConversionValidator, type ConvertedRequest, type ConvertedResponse, EMERGENCY_HEALING_STRATEGIES, type EnhancedAdapterOptions, type EnhancedConversionResult, ErrorDetector, type ErrorInfo, ErrorRecovery, type ErrorSeverity, type ErrorType, FEATURES, FormatValidator, type HealingContext, type HealingResult, type HealingRule, StandardProtocolAdapter as NonStreamingIOConverter, O2ASSEAdapter, O2ASSEAdapterStatic, type OpenAIChoice, OpenAIChoiceSchema, OpenAIFunctionParametersSchema, OpenAIFunctionSchema, type OpenAIMessage, OpenAIMessageContentSchema, OpenAIMessageSchema, type OpenAIRequest$1 as OpenAIRequest, OpenAIRequestSchema, type OpenAIResponse$1 as OpenAIResponse, OpenAIResponseSchema, type OpenAISDKConfig, OpenAISDKWrapper, type OpenAIStreamChunk, OpenAIStreamChunkSchema, O2ASSEAdapter as OpenAIToClaudeSSEConverter, type OpenAITool, type OpenAIToolCall, OpenAIToolCallSchema, OpenAIToolSchema, type OpenAIUsage, OpenAIUsageSchema, ProtocolHealer, RECOVERY_STRATEGIES, REQUEST_HEALING_STRATEGIES, RESPONSE_HEALING_STRATEGIES, type RecoveryResult, type RecoveryStrategy, type RequestOptions, type SDKConfig, SDKFactory, type SDKProvider, type SDKResponse, type SDKStreamEvent, SSEEventGenerator, STREAM_HEALING_STRATEGIES, type ConversionResult as SchemaConversionResult, StandardProtocolAdapter as StandardGateway, StandardProtocolAdapter, type StandardProtocolAdapterOptions, StreamingProtocolAdapter as StreamingGateway, StreamingProtocolAdapter as StreamingIOConverter, StreamingProtocolAdapter, type StreamingProtocolAdapterOptions, type UnifiedSDKConfig, type ValidationResult, attemptRecovery, conversionValidator, createAnthropicSDK, createOpenAISDK, createValidator, downloadImageAsBase64, errorRecovery, getAllHealingStrategies, getGlobalLogger, getRecoveryRecommendations, getStrategiesForContext, healA2ORequest, healO2ARequest, healO2AResponse, healingValidate, isBase64DataUri, isExternalUrl, isRecoverable, protocolHealer, safeValidate, sdkFactory, selectSDKByModel, strictValidate, validateA2OConversion, validateAnthropicRequest, validateAnthropicResponse, validateAnthropicStreamEvent, validateBatch, validateO2AConversion, validateOpenAIRequest, validateOpenAIResponse, validateOpenAIStreamChunk, validateSDKConfig, validateStreamConversion };
package/dist/index.js CHANGED
@@ -453,6 +453,7 @@ __export(index_exports, {
453
453
  createAnthropicSDK: () => createAnthropicSDK,
454
454
  createOpenAISDK: () => createOpenAISDK,
455
455
  createValidator: () => createValidator,
456
+ downloadImageAsBase64: () => downloadImageAsBase64,
456
457
  errorRecovery: () => errorRecovery,
457
458
  getAllHealingStrategies: () => getAllHealingStrategies,
458
459
  getGlobalLogger: () => getGlobalLogger,
@@ -462,6 +463,8 @@ __export(index_exports, {
462
463
  healO2ARequest: () => healO2ARequest,
463
464
  healO2AResponse: () => healO2AResponse,
464
465
  healingValidate: () => healingValidate,
466
+ isBase64DataUri: () => isBase64DataUri,
467
+ isExternalUrl: () => isExternalUrl,
465
468
  isRecoverable: () => isRecoverable,
466
469
  protocolHealer: () => protocolHealer,
467
470
  safeValidate: () => safeValidate,
@@ -532,6 +535,14 @@ var DEFAULT_CONFIG = {
532
535
  enabled: false,
533
536
  logLevel: "warn",
534
537
  enableMetrics: false
538
+ },
539
+ imageProxy: {
540
+ enabled: true,
541
+ // 默认启用图片代理(解决GitHub Copilot等不支持外部URL的问题)
542
+ timeout: 1e4,
543
+ // 10秒超时
544
+ maxSize: 10 * 1024 * 1024
545
+ // 10MB最大文件大小
535
546
  }
536
547
  };
537
548
  var SUPPORTED_IMAGE_TYPES = [
@@ -556,6 +567,61 @@ var TOOL_CONVERSION = {
556
567
  UNKNOWN_TOOL_FALLBACK: "unknown_tool"
557
568
  };
558
569
 
570
+ // src/core/a2o-request-adapter/image-proxy.ts
571
+ var SUPPORTED_IMAGE_MIME_TYPES = [
572
+ "image/jpeg",
573
+ "image/png",
574
+ "image/gif",
575
+ "image/webp"
576
+ ];
577
+ async function downloadImageAsBase64(url, options = {}) {
578
+ const {
579
+ timeout = 1e4,
580
+ maxSize = 10 * 1024 * 1024,
581
+ // 10MB
582
+ userAgent = "ai-protocol-adapters/1.0"
583
+ } = options;
584
+ try {
585
+ const controller = new AbortController();
586
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
587
+ const response = await fetch(url, {
588
+ signal: controller.signal,
589
+ headers: {
590
+ "User-Agent": userAgent
591
+ }
592
+ });
593
+ clearTimeout(timeoutId);
594
+ if (!response.ok) {
595
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
596
+ }
597
+ const contentType = response.headers.get("content-type");
598
+ if (!contentType || !SUPPORTED_IMAGE_MIME_TYPES.some((type) => contentType.includes(type))) {
599
+ throw new Error(`Unsupported content type: ${contentType}`);
600
+ }
601
+ const contentLength = response.headers.get("content-length");
602
+ if (contentLength && parseInt(contentLength) > maxSize) {
603
+ throw new Error(`Image too large: ${contentLength} bytes (max: ${maxSize} bytes)`);
604
+ }
605
+ const arrayBuffer = await response.arrayBuffer();
606
+ if (arrayBuffer.byteLength > maxSize) {
607
+ throw new Error(`Image too large: ${arrayBuffer.byteLength} bytes (max: ${maxSize} bytes)`);
608
+ }
609
+ const base64 = Buffer.from(arrayBuffer).toString("base64");
610
+ return `data:${contentType};base64,${base64}`;
611
+ } catch (error) {
612
+ if (error.name === "AbortError") {
613
+ throw new Error(`Image download timeout after ${timeout}ms`);
614
+ }
615
+ throw new Error(`Failed to download image from ${url}: ${error.message}`);
616
+ }
617
+ }
618
+ function isExternalUrl(url) {
619
+ return url.startsWith("http://") || url.startsWith("https://");
620
+ }
621
+ function isBase64DataUri(url) {
622
+ return url.startsWith("data:");
623
+ }
624
+
559
625
  // src/core/a2o-request-adapter/message-converter.ts
560
626
  var MessageConverter = class {
561
627
  /**
@@ -817,6 +883,170 @@ var MessageConverter = class {
817
883
  }
818
884
  return null;
819
885
  }
886
+ /**
887
+ * 异步转换图片内容格式(支持URL自动下载转base64)
888
+ * @param item 图片内容项
889
+ * @param downloadUrls 是否下载URL并转换为base64(默认true)
890
+ */
891
+ static async convertImageContentAsync(item, downloadUrls = true) {
892
+ if (!item.source) {
893
+ return null;
894
+ }
895
+ if (item.source.type === "url" && item.source.url) {
896
+ const url = item.source.url;
897
+ if (isBase64DataUri(url)) {
898
+ return {
899
+ type: "image_url",
900
+ image_url: {
901
+ url,
902
+ detail: "auto"
903
+ }
904
+ };
905
+ }
906
+ if (downloadUrls && isExternalUrl(url)) {
907
+ try {
908
+ console.log(`[MessageConverter] Downloading image from URL: ${url}`);
909
+ const base64DataUri = await downloadImageAsBase64(url);
910
+ console.log(`[MessageConverter] Successfully converted image to base64`);
911
+ return {
912
+ type: "image_url",
913
+ image_url: {
914
+ url: base64DataUri,
915
+ detail: "auto"
916
+ }
917
+ };
918
+ } catch (error) {
919
+ console.error(`[MessageConverter] Failed to download image: ${error.message}`);
920
+ return {
921
+ type: "image_url",
922
+ image_url: {
923
+ url,
924
+ detail: "auto"
925
+ }
926
+ };
927
+ }
928
+ }
929
+ return {
930
+ type: "image_url",
931
+ image_url: {
932
+ url,
933
+ detail: "auto"
934
+ }
935
+ };
936
+ }
937
+ if (item.source.type === "base64" && item.source.data && item.source.media_type) {
938
+ if (!SUPPORTED_IMAGE_TYPES.includes(item.source.media_type)) {
939
+ console.warn(`\u4E0D\u652F\u6301\u7684\u56FE\u7247\u683C\u5F0F: ${item.source.media_type}`);
940
+ return null;
941
+ }
942
+ const dataUri = `data:${item.source.media_type};base64,${item.source.data}`;
943
+ return {
944
+ type: "image_url",
945
+ image_url: {
946
+ url: dataUri,
947
+ detail: "auto"
948
+ }
949
+ };
950
+ }
951
+ return null;
952
+ }
953
+ /**
954
+ * 异步处理消息内容(支持图片URL下载)
955
+ */
956
+ static async processMessageContentAsync(content, downloadUrls = true) {
957
+ const textContent = [];
958
+ const toolUses = [];
959
+ const toolResults = [];
960
+ for (const item of content) {
961
+ if (item.type) {
962
+ switch (item.type) {
963
+ case "text":
964
+ if (item.text) {
965
+ textContent.push({ type: "text", text: item.text });
966
+ }
967
+ break;
968
+ case "tool_use":
969
+ toolUses.push(item);
970
+ break;
971
+ case "tool_result":
972
+ toolResults.push(item);
973
+ break;
974
+ case "image":
975
+ const imageContent = await this.convertImageContentAsync(item, downloadUrls);
976
+ if (imageContent) {
977
+ textContent.push(imageContent);
978
+ }
979
+ break;
980
+ }
981
+ }
982
+ }
983
+ return { textContent, toolUses, toolResults };
984
+ }
985
+ /**
986
+ * 异步转换消息格式(支持图片URL自动下载)
987
+ * @param messages Claude格式的消息数组
988
+ * @param system 系统消息
989
+ * @param downloadImageUrls 是否下载图片URL并转换为base64(默认true,解决GitHub Copilot等API不支持外部URL的问题)
990
+ */
991
+ static async convertMessagesAsync(messages, system, downloadImageUrls = true) {
992
+ const debugEnabled = process.env.AI_PROTOCOL_DEBUG === "true";
993
+ if (debugEnabled) {
994
+ console.debug(
995
+ `[MessageConverter] convertMessagesAsync called (downloadImageUrls: ${downloadImageUrls})`
996
+ );
997
+ }
998
+ const context = this.createConversionContext(messages);
999
+ const convertedMessages = [];
1000
+ for (const msg of messages) {
1001
+ if (Array.isArray(msg.content)) {
1002
+ const processedMessages = await this.processComplexMessageAsync(msg, context, downloadImageUrls);
1003
+ convertedMessages.push(...processedMessages);
1004
+ } else {
1005
+ const safeMsg = { ...msg };
1006
+ if (safeMsg.content === null || safeMsg.content === void 0) {
1007
+ safeMsg.content = "";
1008
+ }
1009
+ convertedMessages.push(safeMsg);
1010
+ }
1011
+ }
1012
+ const systemMessage = this.processSystemMessage(system);
1013
+ if (systemMessage) {
1014
+ return [systemMessage, ...convertedMessages];
1015
+ }
1016
+ return convertedMessages;
1017
+ }
1018
+ /**
1019
+ * 异步处理复杂消息(支持图片URL下载)
1020
+ */
1021
+ static async processComplexMessageAsync(msg, context, downloadUrls) {
1022
+ const { textContent, toolUses, toolResults } = await this.processMessageContentAsync(
1023
+ msg.content,
1024
+ downloadUrls
1025
+ );
1026
+ const result = [];
1027
+ if (msg.role === "user") {
1028
+ const toolMessages = this.createToolResultMessages(toolResults, context.toolIdToNameMap);
1029
+ result.push(...toolMessages);
1030
+ const textMessage = this.createTextMessage("user", textContent);
1031
+ if (textMessage) {
1032
+ result.push(textMessage);
1033
+ }
1034
+ } else if (msg.role === "assistant") {
1035
+ if (toolUses.length > 0) {
1036
+ const assistantMessage = this.createAssistantMessageWithToolCalls(textContent, toolUses);
1037
+ result.push(assistantMessage);
1038
+ toolUses.forEach((toolUse) => {
1039
+ context.toolIdToNameMap.set(toolUse.id, toolUse.name);
1040
+ });
1041
+ } else {
1042
+ const textMessage = this.createTextMessage("assistant", textContent);
1043
+ if (textMessage) {
1044
+ result.push(textMessage);
1045
+ }
1046
+ }
1047
+ }
1048
+ return result;
1049
+ }
820
1050
  };
821
1051
 
822
1052
  // src/core/a2o-request-adapter/tool-converter.ts
@@ -3469,15 +3699,21 @@ var A2ORequestAdapter = class {
3469
3699
  }
3470
3700
  }
3471
3701
  /**
3472
- * 执行核心转换逻辑(原有逻辑保持不变)
3702
+ * 执行核心转换逻辑(支持图片代理)
3473
3703
  */
3474
3704
  async performCoreConversion(anthropicRequest) {
3475
3705
  if (this.config.enableFormatValidation) {
3476
3706
  FormatValidator.validateClaudeRequest(anthropicRequest);
3477
3707
  }
3708
+ const messages = this.config.imageProxy.enabled ? await MessageConverter.convertMessagesAsync(
3709
+ anthropicRequest.messages,
3710
+ anthropicRequest.system,
3711
+ true
3712
+ // 启用图片下载
3713
+ ) : MessageConverter.convertMessages(anthropicRequest.messages, anthropicRequest.system);
3478
3714
  const openaiRequest = {
3479
3715
  model: anthropicRequest.model,
3480
- messages: MessageConverter.convertMessages(anthropicRequest.messages, anthropicRequest.system),
3716
+ messages,
3481
3717
  max_tokens: anthropicRequest.max_tokens,
3482
3718
  temperature: anthropicRequest.temperature,
3483
3719
  stream: anthropicRequest.stream
@@ -3787,6 +4023,36 @@ var A2ORequestAdapterStatic = {
3787
4023
  */
3788
4024
  getToolMapping: (claudeToolName) => {
3789
4025
  return claudeToolName;
4026
+ },
4027
+ /**
4028
+ * 转换Anthropic请求格式为OpenAI兼容格式(异步版本,支持图片URL自动下载)
4029
+ * 解决GitHub Copilot等API不支持外部图片URL的问题
4030
+ * @param anthropicRequest Claude格式的请求
4031
+ * @param downloadImageUrls 是否下载图片URL并转换为base64(默认true)
4032
+ */
4033
+ convertAnthropicRequestToOpenAIAsync: async (anthropicRequest, downloadImageUrls = true) => {
4034
+ const adapter = new A2ORequestAdapter({
4035
+ debugMode: false,
4036
+ maxDescriptionLength: 100,
4037
+ enableToolNameValidation: true,
4038
+ enableFormatValidation: true,
4039
+ validation: { enabled: true, strict: false },
4040
+ healing: { enabled: true, maxAttempts: 2, enableCustomRules: true },
4041
+ recovery: { enabled: false, maxRetries: 0, backoffMs: 1e3 },
4042
+ monitoring: { enabled: false, logLevel: "none", enableMetrics: false },
4043
+ imageProxy: {
4044
+ enabled: downloadImageUrls,
4045
+ timeout: 1e4,
4046
+ maxSize: 10 * 1024 * 1024
4047
+ }
4048
+ });
4049
+ try {
4050
+ const result = await adapter.performCoreConversion(anthropicRequest);
4051
+ return result;
4052
+ } catch (error) {
4053
+ console.warn(`[A2ORequestAdapterStatic] Async conversion failed: ${error?.message || error}`);
4054
+ return adapter.performBasicConversion(anthropicRequest, true);
4055
+ }
3790
4056
  }
3791
4057
  };
3792
4058
 
@@ -3872,6 +4138,57 @@ var StreamingProtocolAdapter = class {
3872
4138
  };
3873
4139
  }
3874
4140
  }
4141
+ /**
4142
+ * 增量解析Anthropic SSE,转换为OpenAI流式chunk
4143
+ * 供 OpenAI Chat Completions 端点直接复用
4144
+ */
4145
+ convertAnthropicSSEChunkToOpenAI(params) {
4146
+ const { buffer, chunk, model, flush = false } = params;
4147
+ let localBuffer = buffer + (chunk || "");
4148
+ const emittedChunks = [];
4149
+ let finishReason;
4150
+ let streamStopped = false;
4151
+ const processEvent = (eventText) => {
4152
+ const { eventType, data } = this.parseAnthropicSSEEvent(eventText);
4153
+ if (!eventType || !data) {
4154
+ return;
4155
+ }
4156
+ if (eventType === "content_block_delta") {
4157
+ const text = this.extractTextFromAnthropicDelta(data);
4158
+ if (text) {
4159
+ emittedChunks.push(this.buildOpenAIStreamChunk(model, text));
4160
+ }
4161
+ } else if (eventType === "message_stop") {
4162
+ finishReason = this.mapAnthropicStopReasonToOpenAI(data?.stop_reason);
4163
+ streamStopped = true;
4164
+ }
4165
+ };
4166
+ while (true) {
4167
+ const separatorIndex = localBuffer.indexOf("\n\n");
4168
+ if (separatorIndex === -1) {
4169
+ break;
4170
+ }
4171
+ const rawEvent = localBuffer.slice(0, separatorIndex);
4172
+ localBuffer = localBuffer.slice(separatorIndex + 2);
4173
+ if (!rawEvent.trim()) {
4174
+ continue;
4175
+ }
4176
+ processEvent(rawEvent);
4177
+ if (streamStopped) {
4178
+ break;
4179
+ }
4180
+ }
4181
+ if (flush && localBuffer.trim()) {
4182
+ processEvent(localBuffer);
4183
+ localBuffer = "";
4184
+ }
4185
+ return {
4186
+ buffer: localBuffer,
4187
+ chunks: emittedChunks,
4188
+ finishReason,
4189
+ streamStopped
4190
+ };
4191
+ }
3875
4192
  /**
3876
4193
  * 将OpenAI流转换为Anthropic SSE格式
3877
4194
  */
@@ -4389,6 +4706,64 @@ var StreamingProtocolAdapter = class {
4389
4706
  nextToolBlockIndex: 1
4390
4707
  };
4391
4708
  }
4709
+ parseAnthropicSSEEvent(rawEvent) {
4710
+ const lines = rawEvent.split("\n");
4711
+ let eventType = null;
4712
+ const dataLines = [];
4713
+ for (const line of lines) {
4714
+ if (line.startsWith("event:")) {
4715
+ eventType = line.slice(6).trim();
4716
+ } else if (line.startsWith("data:")) {
4717
+ dataLines.push(line.slice(5).trim());
4718
+ }
4719
+ }
4720
+ const dataString = dataLines.join("\n");
4721
+ let data = null;
4722
+ if (dataString) {
4723
+ try {
4724
+ data = JSON.parse(dataString);
4725
+ } catch (error) {
4726
+ this.logDebug("Failed to parse Anthropic SSE JSON", { error });
4727
+ }
4728
+ }
4729
+ return { eventType, data };
4730
+ }
4731
+ extractTextFromAnthropicDelta(data) {
4732
+ const delta = data?.delta;
4733
+ if (!delta) return null;
4734
+ if (typeof delta.text === "string") {
4735
+ return delta.text;
4736
+ }
4737
+ if (delta.type === "text_delta" && typeof delta.text === "string") {
4738
+ return delta.text;
4739
+ }
4740
+ return null;
4741
+ }
4742
+ mapAnthropicStopReasonToOpenAI(reason) {
4743
+ switch (reason) {
4744
+ case "max_tokens":
4745
+ return "length";
4746
+ case "tool_use":
4747
+ return "tool_calls";
4748
+ case "stop_sequence":
4749
+ case "end_turn":
4750
+ default:
4751
+ return "stop";
4752
+ }
4753
+ }
4754
+ buildOpenAIStreamChunk(model, content, finishReason = null) {
4755
+ return {
4756
+ id: `chatcmpl-${Date.now()}`,
4757
+ object: "chat.completion.chunk",
4758
+ created: Math.floor(Date.now() / 1e3),
4759
+ model,
4760
+ choices: [{
4761
+ index: 0,
4762
+ delta: content ? { content } : {},
4763
+ finish_reason: finishReason
4764
+ }]
4765
+ };
4766
+ }
4392
4767
  /**
4393
4768
  * 转换消息格式
4394
4769
  */
@@ -6673,6 +7048,7 @@ var FEATURES = {
6673
7048
  createAnthropicSDK,
6674
7049
  createOpenAISDK,
6675
7050
  createValidator,
7051
+ downloadImageAsBase64,
6676
7052
  errorRecovery,
6677
7053
  getAllHealingStrategies,
6678
7054
  getGlobalLogger,
@@ -6682,6 +7058,8 @@ var FEATURES = {
6682
7058
  healO2ARequest,
6683
7059
  healO2AResponse,
6684
7060
  healingValidate,
7061
+ isBase64DataUri,
7062
+ isExternalUrl,
6685
7063
  isRecoverable,
6686
7064
  protocolHealer,
6687
7065
  safeValidate,
package/dist/index.mjs CHANGED
@@ -425,6 +425,14 @@ var DEFAULT_CONFIG = {
425
425
  enabled: false,
426
426
  logLevel: "warn",
427
427
  enableMetrics: false
428
+ },
429
+ imageProxy: {
430
+ enabled: true,
431
+ // 默认启用图片代理(解决GitHub Copilot等不支持外部URL的问题)
432
+ timeout: 1e4,
433
+ // 10秒超时
434
+ maxSize: 10 * 1024 * 1024
435
+ // 10MB最大文件大小
428
436
  }
429
437
  };
430
438
  var SUPPORTED_IMAGE_TYPES = [
@@ -449,6 +457,61 @@ var TOOL_CONVERSION = {
449
457
  UNKNOWN_TOOL_FALLBACK: "unknown_tool"
450
458
  };
451
459
 
460
+ // src/core/a2o-request-adapter/image-proxy.ts
461
+ var SUPPORTED_IMAGE_MIME_TYPES = [
462
+ "image/jpeg",
463
+ "image/png",
464
+ "image/gif",
465
+ "image/webp"
466
+ ];
467
+ async function downloadImageAsBase64(url, options = {}) {
468
+ const {
469
+ timeout = 1e4,
470
+ maxSize = 10 * 1024 * 1024,
471
+ // 10MB
472
+ userAgent = "ai-protocol-adapters/1.0"
473
+ } = options;
474
+ try {
475
+ const controller = new AbortController();
476
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
477
+ const response = await fetch(url, {
478
+ signal: controller.signal,
479
+ headers: {
480
+ "User-Agent": userAgent
481
+ }
482
+ });
483
+ clearTimeout(timeoutId);
484
+ if (!response.ok) {
485
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
486
+ }
487
+ const contentType = response.headers.get("content-type");
488
+ if (!contentType || !SUPPORTED_IMAGE_MIME_TYPES.some((type) => contentType.includes(type))) {
489
+ throw new Error(`Unsupported content type: ${contentType}`);
490
+ }
491
+ const contentLength = response.headers.get("content-length");
492
+ if (contentLength && parseInt(contentLength) > maxSize) {
493
+ throw new Error(`Image too large: ${contentLength} bytes (max: ${maxSize} bytes)`);
494
+ }
495
+ const arrayBuffer = await response.arrayBuffer();
496
+ if (arrayBuffer.byteLength > maxSize) {
497
+ throw new Error(`Image too large: ${arrayBuffer.byteLength} bytes (max: ${maxSize} bytes)`);
498
+ }
499
+ const base64 = Buffer.from(arrayBuffer).toString("base64");
500
+ return `data:${contentType};base64,${base64}`;
501
+ } catch (error) {
502
+ if (error.name === "AbortError") {
503
+ throw new Error(`Image download timeout after ${timeout}ms`);
504
+ }
505
+ throw new Error(`Failed to download image from ${url}: ${error.message}`);
506
+ }
507
+ }
508
+ function isExternalUrl(url) {
509
+ return url.startsWith("http://") || url.startsWith("https://");
510
+ }
511
+ function isBase64DataUri(url) {
512
+ return url.startsWith("data:");
513
+ }
514
+
452
515
  // src/core/a2o-request-adapter/message-converter.ts
453
516
  var MessageConverter = class {
454
517
  /**
@@ -710,6 +773,170 @@ var MessageConverter = class {
710
773
  }
711
774
  return null;
712
775
  }
776
+ /**
777
+ * 异步转换图片内容格式(支持URL自动下载转base64)
778
+ * @param item 图片内容项
779
+ * @param downloadUrls 是否下载URL并转换为base64(默认true)
780
+ */
781
+ static async convertImageContentAsync(item, downloadUrls = true) {
782
+ if (!item.source) {
783
+ return null;
784
+ }
785
+ if (item.source.type === "url" && item.source.url) {
786
+ const url = item.source.url;
787
+ if (isBase64DataUri(url)) {
788
+ return {
789
+ type: "image_url",
790
+ image_url: {
791
+ url,
792
+ detail: "auto"
793
+ }
794
+ };
795
+ }
796
+ if (downloadUrls && isExternalUrl(url)) {
797
+ try {
798
+ console.log(`[MessageConverter] Downloading image from URL: ${url}`);
799
+ const base64DataUri = await downloadImageAsBase64(url);
800
+ console.log(`[MessageConverter] Successfully converted image to base64`);
801
+ return {
802
+ type: "image_url",
803
+ image_url: {
804
+ url: base64DataUri,
805
+ detail: "auto"
806
+ }
807
+ };
808
+ } catch (error) {
809
+ console.error(`[MessageConverter] Failed to download image: ${error.message}`);
810
+ return {
811
+ type: "image_url",
812
+ image_url: {
813
+ url,
814
+ detail: "auto"
815
+ }
816
+ };
817
+ }
818
+ }
819
+ return {
820
+ type: "image_url",
821
+ image_url: {
822
+ url,
823
+ detail: "auto"
824
+ }
825
+ };
826
+ }
827
+ if (item.source.type === "base64" && item.source.data && item.source.media_type) {
828
+ if (!SUPPORTED_IMAGE_TYPES.includes(item.source.media_type)) {
829
+ console.warn(`\u4E0D\u652F\u6301\u7684\u56FE\u7247\u683C\u5F0F: ${item.source.media_type}`);
830
+ return null;
831
+ }
832
+ const dataUri = `data:${item.source.media_type};base64,${item.source.data}`;
833
+ return {
834
+ type: "image_url",
835
+ image_url: {
836
+ url: dataUri,
837
+ detail: "auto"
838
+ }
839
+ };
840
+ }
841
+ return null;
842
+ }
843
+ /**
844
+ * 异步处理消息内容(支持图片URL下载)
845
+ */
846
+ static async processMessageContentAsync(content, downloadUrls = true) {
847
+ const textContent = [];
848
+ const toolUses = [];
849
+ const toolResults = [];
850
+ for (const item of content) {
851
+ if (item.type) {
852
+ switch (item.type) {
853
+ case "text":
854
+ if (item.text) {
855
+ textContent.push({ type: "text", text: item.text });
856
+ }
857
+ break;
858
+ case "tool_use":
859
+ toolUses.push(item);
860
+ break;
861
+ case "tool_result":
862
+ toolResults.push(item);
863
+ break;
864
+ case "image":
865
+ const imageContent = await this.convertImageContentAsync(item, downloadUrls);
866
+ if (imageContent) {
867
+ textContent.push(imageContent);
868
+ }
869
+ break;
870
+ }
871
+ }
872
+ }
873
+ return { textContent, toolUses, toolResults };
874
+ }
875
+ /**
876
+ * 异步转换消息格式(支持图片URL自动下载)
877
+ * @param messages Claude格式的消息数组
878
+ * @param system 系统消息
879
+ * @param downloadImageUrls 是否下载图片URL并转换为base64(默认true,解决GitHub Copilot等API不支持外部URL的问题)
880
+ */
881
+ static async convertMessagesAsync(messages, system, downloadImageUrls = true) {
882
+ const debugEnabled = process.env.AI_PROTOCOL_DEBUG === "true";
883
+ if (debugEnabled) {
884
+ console.debug(
885
+ `[MessageConverter] convertMessagesAsync called (downloadImageUrls: ${downloadImageUrls})`
886
+ );
887
+ }
888
+ const context = this.createConversionContext(messages);
889
+ const convertedMessages = [];
890
+ for (const msg of messages) {
891
+ if (Array.isArray(msg.content)) {
892
+ const processedMessages = await this.processComplexMessageAsync(msg, context, downloadImageUrls);
893
+ convertedMessages.push(...processedMessages);
894
+ } else {
895
+ const safeMsg = { ...msg };
896
+ if (safeMsg.content === null || safeMsg.content === void 0) {
897
+ safeMsg.content = "";
898
+ }
899
+ convertedMessages.push(safeMsg);
900
+ }
901
+ }
902
+ const systemMessage = this.processSystemMessage(system);
903
+ if (systemMessage) {
904
+ return [systemMessage, ...convertedMessages];
905
+ }
906
+ return convertedMessages;
907
+ }
908
+ /**
909
+ * 异步处理复杂消息(支持图片URL下载)
910
+ */
911
+ static async processComplexMessageAsync(msg, context, downloadUrls) {
912
+ const { textContent, toolUses, toolResults } = await this.processMessageContentAsync(
913
+ msg.content,
914
+ downloadUrls
915
+ );
916
+ const result = [];
917
+ if (msg.role === "user") {
918
+ const toolMessages = this.createToolResultMessages(toolResults, context.toolIdToNameMap);
919
+ result.push(...toolMessages);
920
+ const textMessage = this.createTextMessage("user", textContent);
921
+ if (textMessage) {
922
+ result.push(textMessage);
923
+ }
924
+ } else if (msg.role === "assistant") {
925
+ if (toolUses.length > 0) {
926
+ const assistantMessage = this.createAssistantMessageWithToolCalls(textContent, toolUses);
927
+ result.push(assistantMessage);
928
+ toolUses.forEach((toolUse) => {
929
+ context.toolIdToNameMap.set(toolUse.id, toolUse.name);
930
+ });
931
+ } else {
932
+ const textMessage = this.createTextMessage("assistant", textContent);
933
+ if (textMessage) {
934
+ result.push(textMessage);
935
+ }
936
+ }
937
+ }
938
+ return result;
939
+ }
713
940
  };
714
941
 
715
942
  // src/core/a2o-request-adapter/tool-converter.ts
@@ -3362,15 +3589,21 @@ var A2ORequestAdapter = class {
3362
3589
  }
3363
3590
  }
3364
3591
  /**
3365
- * 执行核心转换逻辑(原有逻辑保持不变)
3592
+ * 执行核心转换逻辑(支持图片代理)
3366
3593
  */
3367
3594
  async performCoreConversion(anthropicRequest) {
3368
3595
  if (this.config.enableFormatValidation) {
3369
3596
  FormatValidator.validateClaudeRequest(anthropicRequest);
3370
3597
  }
3598
+ const messages = this.config.imageProxy.enabled ? await MessageConverter.convertMessagesAsync(
3599
+ anthropicRequest.messages,
3600
+ anthropicRequest.system,
3601
+ true
3602
+ // 启用图片下载
3603
+ ) : MessageConverter.convertMessages(anthropicRequest.messages, anthropicRequest.system);
3371
3604
  const openaiRequest = {
3372
3605
  model: anthropicRequest.model,
3373
- messages: MessageConverter.convertMessages(anthropicRequest.messages, anthropicRequest.system),
3606
+ messages,
3374
3607
  max_tokens: anthropicRequest.max_tokens,
3375
3608
  temperature: anthropicRequest.temperature,
3376
3609
  stream: anthropicRequest.stream
@@ -3680,6 +3913,36 @@ var A2ORequestAdapterStatic = {
3680
3913
  */
3681
3914
  getToolMapping: (claudeToolName) => {
3682
3915
  return claudeToolName;
3916
+ },
3917
+ /**
3918
+ * 转换Anthropic请求格式为OpenAI兼容格式(异步版本,支持图片URL自动下载)
3919
+ * 解决GitHub Copilot等API不支持外部图片URL的问题
3920
+ * @param anthropicRequest Claude格式的请求
3921
+ * @param downloadImageUrls 是否下载图片URL并转换为base64(默认true)
3922
+ */
3923
+ convertAnthropicRequestToOpenAIAsync: async (anthropicRequest, downloadImageUrls = true) => {
3924
+ const adapter = new A2ORequestAdapter({
3925
+ debugMode: false,
3926
+ maxDescriptionLength: 100,
3927
+ enableToolNameValidation: true,
3928
+ enableFormatValidation: true,
3929
+ validation: { enabled: true, strict: false },
3930
+ healing: { enabled: true, maxAttempts: 2, enableCustomRules: true },
3931
+ recovery: { enabled: false, maxRetries: 0, backoffMs: 1e3 },
3932
+ monitoring: { enabled: false, logLevel: "none", enableMetrics: false },
3933
+ imageProxy: {
3934
+ enabled: downloadImageUrls,
3935
+ timeout: 1e4,
3936
+ maxSize: 10 * 1024 * 1024
3937
+ }
3938
+ });
3939
+ try {
3940
+ const result = await adapter.performCoreConversion(anthropicRequest);
3941
+ return result;
3942
+ } catch (error) {
3943
+ console.warn(`[A2ORequestAdapterStatic] Async conversion failed: ${error?.message || error}`);
3944
+ return adapter.performBasicConversion(anthropicRequest, true);
3945
+ }
3683
3946
  }
3684
3947
  };
3685
3948
 
@@ -3765,6 +4028,57 @@ var StreamingProtocolAdapter = class {
3765
4028
  };
3766
4029
  }
3767
4030
  }
4031
+ /**
4032
+ * 增量解析Anthropic SSE,转换为OpenAI流式chunk
4033
+ * 供 OpenAI Chat Completions 端点直接复用
4034
+ */
4035
+ convertAnthropicSSEChunkToOpenAI(params) {
4036
+ const { buffer, chunk, model, flush = false } = params;
4037
+ let localBuffer = buffer + (chunk || "");
4038
+ const emittedChunks = [];
4039
+ let finishReason;
4040
+ let streamStopped = false;
4041
+ const processEvent = (eventText) => {
4042
+ const { eventType, data } = this.parseAnthropicSSEEvent(eventText);
4043
+ if (!eventType || !data) {
4044
+ return;
4045
+ }
4046
+ if (eventType === "content_block_delta") {
4047
+ const text = this.extractTextFromAnthropicDelta(data);
4048
+ if (text) {
4049
+ emittedChunks.push(this.buildOpenAIStreamChunk(model, text));
4050
+ }
4051
+ } else if (eventType === "message_stop") {
4052
+ finishReason = this.mapAnthropicStopReasonToOpenAI(data?.stop_reason);
4053
+ streamStopped = true;
4054
+ }
4055
+ };
4056
+ while (true) {
4057
+ const separatorIndex = localBuffer.indexOf("\n\n");
4058
+ if (separatorIndex === -1) {
4059
+ break;
4060
+ }
4061
+ const rawEvent = localBuffer.slice(0, separatorIndex);
4062
+ localBuffer = localBuffer.slice(separatorIndex + 2);
4063
+ if (!rawEvent.trim()) {
4064
+ continue;
4065
+ }
4066
+ processEvent(rawEvent);
4067
+ if (streamStopped) {
4068
+ break;
4069
+ }
4070
+ }
4071
+ if (flush && localBuffer.trim()) {
4072
+ processEvent(localBuffer);
4073
+ localBuffer = "";
4074
+ }
4075
+ return {
4076
+ buffer: localBuffer,
4077
+ chunks: emittedChunks,
4078
+ finishReason,
4079
+ streamStopped
4080
+ };
4081
+ }
3768
4082
  /**
3769
4083
  * 将OpenAI流转换为Anthropic SSE格式
3770
4084
  */
@@ -4282,6 +4596,64 @@ var StreamingProtocolAdapter = class {
4282
4596
  nextToolBlockIndex: 1
4283
4597
  };
4284
4598
  }
4599
+ parseAnthropicSSEEvent(rawEvent) {
4600
+ const lines = rawEvent.split("\n");
4601
+ let eventType = null;
4602
+ const dataLines = [];
4603
+ for (const line of lines) {
4604
+ if (line.startsWith("event:")) {
4605
+ eventType = line.slice(6).trim();
4606
+ } else if (line.startsWith("data:")) {
4607
+ dataLines.push(line.slice(5).trim());
4608
+ }
4609
+ }
4610
+ const dataString = dataLines.join("\n");
4611
+ let data = null;
4612
+ if (dataString) {
4613
+ try {
4614
+ data = JSON.parse(dataString);
4615
+ } catch (error) {
4616
+ this.logDebug("Failed to parse Anthropic SSE JSON", { error });
4617
+ }
4618
+ }
4619
+ return { eventType, data };
4620
+ }
4621
+ extractTextFromAnthropicDelta(data) {
4622
+ const delta = data?.delta;
4623
+ if (!delta) return null;
4624
+ if (typeof delta.text === "string") {
4625
+ return delta.text;
4626
+ }
4627
+ if (delta.type === "text_delta" && typeof delta.text === "string") {
4628
+ return delta.text;
4629
+ }
4630
+ return null;
4631
+ }
4632
+ mapAnthropicStopReasonToOpenAI(reason) {
4633
+ switch (reason) {
4634
+ case "max_tokens":
4635
+ return "length";
4636
+ case "tool_use":
4637
+ return "tool_calls";
4638
+ case "stop_sequence":
4639
+ case "end_turn":
4640
+ default:
4641
+ return "stop";
4642
+ }
4643
+ }
4644
+ buildOpenAIStreamChunk(model, content, finishReason = null) {
4645
+ return {
4646
+ id: `chatcmpl-${Date.now()}`,
4647
+ object: "chat.completion.chunk",
4648
+ created: Math.floor(Date.now() / 1e3),
4649
+ model,
4650
+ choices: [{
4651
+ index: 0,
4652
+ delta: content ? { content } : {},
4653
+ finish_reason: finishReason
4654
+ }]
4655
+ };
4656
+ }
4285
4657
  /**
4286
4658
  * 转换消息格式
4287
4659
  */
@@ -6565,6 +6937,7 @@ export {
6565
6937
  createAnthropicSDK,
6566
6938
  createOpenAISDK,
6567
6939
  createValidator,
6940
+ downloadImageAsBase64,
6568
6941
  errorRecovery,
6569
6942
  getAllHealingStrategies,
6570
6943
  getGlobalLogger,
@@ -6574,6 +6947,8 @@ export {
6574
6947
  healO2ARequest,
6575
6948
  healO2AResponse,
6576
6949
  healingValidate,
6950
+ isBase64DataUri,
6951
+ isExternalUrl,
6577
6952
  isRecoverable,
6578
6953
  protocolHealer,
6579
6954
  safeValidate,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-protocol-adapters",
3
- "version": "1.0.0-alpha.17",
3
+ "version": "1.0.0-alpha.19",
4
4
  "description": "Universal AI Protocol Converter - OpenAI ⇄ Anthropic with full TypeScript support",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",