@@ -3,6 +3,7 @@ import type { RequestOptions } from "../lib/sdks.js";
33import type { Tool , MaxToolRounds } from "../lib/tool-types.js" ;
44import type * as models from "../models/index.js" ;
55
6+ import { fromClaudeMessages } from "../lib/anthropic-compat.js" ;
67import { ModelResult } from "../lib/model-result.js" ;
78import { convertToolsToAPIFormat } from "../lib/tool-executor.js" ;
89
@@ -14,6 +15,97 @@ export type CallModelTools =
1415 | models . ToolDefinitionJson [ ]
1516 | models . OpenResponsesRequest [ "tools" ] ;
1617
18+ /**
19+ * Input type that accepts OpenResponses input or Claude-style messages
20+ */
21+ export type CallModelInput =
22+ | models . OpenResponsesInput
23+ | models . ClaudeMessageParam [ ] ;
24+
25+ /**
26+ * Type guard for Claude-style messages (ClaudeMessageParam[])
27+ * Claude messages have role: "user" | "assistant" and content as string or content blocks
28+ */
29+ function isClaudeStyleInput (
30+ input : CallModelInput | undefined
31+ ) : input is models . ClaudeMessageParam [ ] {
32+ if ( ! input || ! Array . isArray ( input ) || input . length === 0 ) {
33+ return false ;
34+ }
35+
36+ const firstItem = input [ 0 ] ;
37+
38+ // Claude messages have role: "user" | "assistant"
39+ // and content as string or array of content blocks with type: "text" | "tool_use" | etc.
40+ if (
41+ typeof firstItem !== "object" ||
42+ firstItem === null ||
43+ ! ( "role" in firstItem ) ||
44+ ! ( "content" in firstItem )
45+ ) {
46+ return false ;
47+ }
48+
49+ const role = firstItem . role ;
50+ const content = firstItem . content ;
51+
52+ // Check if it's a Claude-style role (only "user" or "assistant")
53+ if ( role !== "user" && role !== "assistant" ) {
54+ return false ;
55+ }
56+
57+ // If content is an array, check if it has Claude-style content blocks
58+ if ( Array . isArray ( content ) ) {
59+ const firstBlock = content [ 0 ] ;
60+ if (
61+ firstBlock &&
62+ typeof firstBlock === "object" &&
63+ "type" in firstBlock &&
64+ ( firstBlock . type === "text" ||
65+ firstBlock . type === "tool_use" ||
66+ firstBlock . type === "tool_result" ||
67+ firstBlock . type === "image" )
68+ ) {
69+ return true ;
70+ }
71+ }
72+
73+ // If content is a string, we need to distinguish from OpenResponsesEasyInputMessage
74+ // OpenResponsesEasyInputMessage also has role and content as string
75+ // But Claude uses "user" | "assistant" while OpenResponses uses role enums
76+ // The key difference is that OpenResponsesEasyInputMessage role is an enum value like "user"
77+ // but that's the same...
78+ //
79+ // We need another heuristic: if the input doesn't have other OpenResponses fields
80+ // like "type", "id", etc., it's likely Claude-style
81+ if ( typeof content === "string" ) {
82+ // If item has no "type" field and role is strictly "user" or "assistant"
83+ // it's likely a Claude-style message
84+ // OpenResponses items typically have a "type" field (except for OpenResponsesEasyInputMessage)
85+ // This is ambiguous, so we'll be conservative and check if it matches OpenResponses format first
86+ return ! ( "type" in firstItem ) ;
87+ }
88+
89+ return false ;
90+ }
91+
92+ /**
93+ * Convert input to OpenResponsesInput format if needed
94+ */
95+ function normalizeInput (
96+ input : CallModelInput | undefined
97+ ) : models . OpenResponsesInput | undefined {
98+ if ( input === undefined ) {
99+ return undefined ;
100+ }
101+
102+ if ( isClaudeStyleInput ( input ) ) {
103+ return fromClaudeMessages ( input ) ;
104+ }
105+
106+ return input ;
107+ }
108+
17109/**
18110 * Discriminated tool type detection result
19111 */
@@ -219,17 +311,20 @@ function convertChatToResponsesTools(
219311export function callModel (
220312 client : OpenRouterCore ,
221313 request : Omit < models . OpenResponsesRequest , "stream" | "tools" | "input" > & {
222- input ?: models . OpenResponsesInput ;
314+ input ?: CallModelInput ;
223315 tools ?: CallModelTools ;
224316 maxToolRounds ?: MaxToolRounds ;
225317 } ,
226318 options ?: RequestOptions
227319) : ModelResult {
228320 const { tools, maxToolRounds, input, ...restRequest } = request ;
229321
322+ // Normalize input - convert Claude-style messages if needed
323+ const normalizedInput = normalizeInput ( input ) ;
324+
230325 const apiRequest = {
231326 ...restRequest ,
232- input,
327+ input : normalizedInput ,
233328 } ;
234329
235330 // Detect tool type using discriminated union
0 commit comments