@@ -6,7 +6,115 @@ import type * as models from "../models/index.js";
66import { ModelResult } from "../lib/model-result.js" ;
77import { convertToolsToAPIFormat } from "../lib/tool-executor.js" ;
88
9+ /**
10+ * Checks if a message looks like a Claude-style message
11+ */
12+ function isClaudeStyleMessage ( msg : any ) : msg is models . ClaudeMessageParam {
13+ if ( ! msg || typeof msg !== 'object' ) return false ;
14+
15+ // Check if it has a role field that's user or assistant
16+ const role = msg . role ;
17+ if ( role !== 'user' && role !== 'assistant' ) return false ;
18+
19+ // Check if content is an array with Claude-style content blocks
20+ if ( Array . isArray ( msg . content ) ) {
21+ return msg . content . some ( ( block : any ) =>
22+ block &&
23+ typeof block === 'object' &&
24+ block . type &&
25+ // Claude content block types (not OpenRouter types)
26+ ( block . type === 'text' || block . type === 'image' || block . type === 'tool_use' || block . type === 'tool_result' )
27+ ) ;
28+ }
29+
30+ return false ;
31+ }
32+
33+ /**
34+ * Converts Claude-style content blocks to OpenRouter format
35+ */
36+ function convertClaudeContentBlock (
37+ block : models . ClaudeContentBlockParam
38+ ) : models . ResponseInputText | models . ResponseInputImage | null {
39+ if ( ! block || typeof block !== 'object' || ! ( 'type' in block ) ) {
40+ return null ;
41+ }
42+
43+ switch ( block . type ) {
44+ case 'text' : {
45+ const textBlock = block as models . ClaudeTextBlockParam ;
46+ return {
47+ type : 'input_text' ,
48+ text : textBlock . text ,
49+ } ;
50+ }
51+ case 'image' : {
52+ const imageBlock = block as models . ClaudeImageBlockParam ;
53+ if ( imageBlock . source . type === 'url' ) {
54+ return {
55+ type : 'input_image' ,
56+ detail : 'auto' ,
57+ imageUrl : imageBlock . source . url ,
58+ } ;
59+ } else if ( imageBlock . source . type === 'base64' ) {
60+ const dataUri = `data:${ imageBlock . source . media_type } ;base64,${ imageBlock . source . data } ` ;
61+ return {
62+ type : 'input_image' ,
63+ detail : 'auto' ,
64+ imageUrl : dataUri ,
65+ } ;
66+ }
67+ return null ;
68+ }
69+ case 'tool_use' :
70+ case 'tool_result' :
71+ // tool_use and tool_result are not handled here as they map to different input types
72+ return null ;
73+ default :
74+ return null ;
75+ }
76+ }
77+
78+ /**
79+ * Converts a Claude-style message to OpenRouter EasyInputMessage format
80+ */
81+ function convertClaudeMessage ( msg : models . ClaudeMessageParam ) : models . OpenResponsesEasyInputMessage {
82+ const { role, content } = msg ;
83+
84+ if ( typeof content === 'string' ) {
85+ return {
86+ role : role === 'user' ? 'user' : 'assistant' ,
87+ content,
88+ } ;
89+ }
990
91+ // Convert array of content blocks
92+ const convertedBlocks : ( models . ResponseInputText | models . ResponseInputImage ) [ ] = [ ] ;
93+ for ( const block of content ) {
94+ const converted = convertClaudeContentBlock ( block ) ;
95+ if ( converted ) {
96+ convertedBlocks . push ( converted ) ;
97+ }
98+ }
99+
100+ // If all blocks were text, concatenate them into a string
101+ const allText = convertedBlocks . every ( b => b . type === 'input_text' ) ;
102+ if ( allText ) {
103+ const text = convertedBlocks
104+ . map ( b => ( b as models . ResponseInputText ) . text )
105+ . join ( '' ) ;
106+ return {
107+ role : role === 'user' ? 'user' : 'assistant' ,
108+ content : text ,
109+ } ;
110+ }
111+
112+ // Otherwise, return as array
113+ return {
114+ role : role === 'user' ? 'user' : 'assistant' ,
115+ content : convertedBlocks ,
116+ } ;
117+ }
10118
11119/**
12120 * Get a response with multiple consumption patterns
@@ -41,14 +149,27 @@ export function callModel(
41149) : ModelResult {
42150 const { tools, maxToolRounds, ...apiRequest } = request ;
43151
152+ // Auto-convert Claude-style messages if detected
153+ let processedInput = apiRequest . input ;
154+ if ( Array . isArray ( apiRequest . input ) ) {
155+ const hasClaudeMessages = apiRequest . input . some ( isClaudeStyleMessage ) ;
156+ if ( hasClaudeMessages ) {
157+ processedInput = apiRequest . input . map ( ( msg : any ) => {
158+ if ( isClaudeStyleMessage ( msg ) ) {
159+ return convertClaudeMessage ( msg ) ;
160+ }
161+ return msg ;
162+ } ) ;
163+ }
164+ }
44165
45166 // Convert tools to API format and extract enhanced tools if present
46167 const apiTools = tools ? convertToolsToAPIFormat ( tools ) : undefined ;
47168
48-
49- // Build the request with converted tools
169+ // Build the request with converted tools and input
50170 const finalRequest : models . OpenResponsesRequest = {
51171 ...apiRequest ,
172+ ...( processedInput !== undefined && { input : processedInput } ) ,
52173 ...( apiTools !== undefined && { tools : apiTools } ) ,
53174 } ;
54175
0 commit comments