@@ -113,6 +113,13 @@ type ReplayableResponseReasoningItem = Omit<ResponseReasoningItem, "id"> & {
113113 [ OPENAI_RESPONSES_REASONING_REPLAY_META_KEY ] ?: OpenAIResponsesReasoningReplayMetadata ;
114114} ;
115115type ResponsesClientLike = ReturnType < typeof createOpenAIResponsesClient > ;
116+ type OpenAITransportTool = NonNullable < Context [ "tools" ] > [ number ] ;
117+ type MaterializedOpenAITransportTool = {
118+ name : string ;
119+ description : string ;
120+ parameters : unknown ;
121+ sourceIndex : number ;
122+ } ;
116123
117124type BaseStreamOptions = {
118125 temperature ?: number ;
@@ -1256,11 +1263,12 @@ function convertResponsesTools(
12561263 model : OpenAIModeModel ,
12571264 options ?: { strict ?: boolean | null } ,
12581265) : FunctionTool [ ] {
1259- const strict = resolveOpenAIStrictToolFlagWithDiagnostics ( tools , options ?. strict , {
1266+ const materializedTools = materializeOpenAITransportTools ( tools ) ;
1267+ const strict = resolveOpenAIStrictToolFlagWithDiagnostics ( materializedTools , options ?. strict , {
12601268 transport : "responses" ,
12611269 model,
12621270 } ) ;
1263- return sortTransportToolsByName ( tools ) . map ( ( tool ) : FunctionTool => {
1271+ return sortTransportToolsByName ( materializedTools ) . map ( ( tool ) : FunctionTool => {
12641272 const result = {
12651273 type : "function" as const ,
12661274 name : tool . name ,
@@ -1279,7 +1287,7 @@ function convertResponsesTools(
12791287}
12801288
12811289function resolveOpenAIStrictToolFlagWithDiagnostics (
1282- tools : NonNullable < Context [ "tools" ] > ,
1290+ tools : readonly { name ?: string ; parameters : unknown } [ ] ,
12831291 strictSetting : boolean | null | undefined ,
12841292 context : { transport : "responses" | "completions" ; model : OpenAIModeModel } ,
12851293) : boolean | undefined {
@@ -3263,8 +3271,9 @@ function convertTools(
32633271 compat : ReturnType < typeof getCompat > ,
32643272 model : OpenAIModeModel ,
32653273) {
3274+ const materializedTools = materializeOpenAITransportTools ( tools ) ;
32663275 const strict = resolveOpenAIStrictToolFlagWithDiagnostics (
3267- tools ,
3276+ materializedTools ,
32683277 resolveOpenAIStrictToolSetting ( model , {
32693278 transport : "stream" ,
32703279 supportsStrictMode : compat ?. supportsStrictMode ,
@@ -3274,7 +3283,7 @@ function convertTools(
32743283 model,
32753284 } ,
32763285 ) ;
3277- return sortTransportToolsByName ( tools ) . map ( ( tool ) => {
3286+ return sortTransportToolsByName ( materializedTools ) . map ( ( tool ) => {
32783287 const functionTool : {
32793288 name : string ;
32803289 description : string | undefined ;
@@ -3299,6 +3308,58 @@ function convertTools(
32993308 } ) ;
33003309}
33013310
3311+ function readOpenAITransportToolField < TField extends keyof OpenAITransportTool > (
3312+ tool : OpenAITransportTool ,
3313+ field : TField ,
3314+ ) : { ok : true ; value : OpenAITransportTool [ TField ] } | { ok : false } {
3315+ try {
3316+ return { ok : true , value : tool [ field ] } ;
3317+ } catch {
3318+ return { ok : false } ;
3319+ }
3320+ }
3321+
3322+ function materializeOpenAITransportTools (
3323+ tools : NonNullable < Context [ "tools" ] > ,
3324+ ) : MaterializedOpenAITransportTool [ ] {
3325+ const materializedTools : MaterializedOpenAITransportTool [ ] = [ ] ;
3326+ const skippedTools : string [ ] = [ ] ;
3327+ tools . forEach ( ( tool , sourceIndex ) => {
3328+ const nameRead = readOpenAITransportToolField ( tool , "name" ) ;
3329+ const name = nameRead . ok && typeof nameRead . value === "string" ? nameRead . value : "" ;
3330+ const toolLabel = name || `tool[${ sourceIndex } ]` ;
3331+ if ( ! name ) {
3332+ skippedTools . push ( toolLabel ) ;
3333+ return ;
3334+ }
3335+ const parametersRead = readOpenAITransportToolField ( tool , "parameters" ) ;
3336+ if ( ! parametersRead . ok ) {
3337+ skippedTools . push ( toolLabel ) ;
3338+ return ;
3339+ }
3340+ const descriptionRead = readOpenAITransportToolField ( tool , "description" ) ;
3341+ materializedTools . push ( {
3342+ name,
3343+ description :
3344+ descriptionRead . ok && typeof descriptionRead . value === "string"
3345+ ? descriptionRead . value
3346+ : "" ,
3347+ parameters : parametersRead . value ,
3348+ sourceIndex,
3349+ } ) ;
3350+ } ) ;
3351+ if ( skippedTools . length > 0 ) {
3352+ log . warn (
3353+ `OpenAI transport skipped ${ skippedTools . length } unreadable tool descriptor${ skippedTools . length === 1 ? "" : "s" } before request conversion` ,
3354+ {
3355+ tools : skippedTools . slice ( 0 , 10 ) ,
3356+ omitted : Math . max ( 0 , skippedTools . length - 10 ) ,
3357+ } ,
3358+ ) ;
3359+ }
3360+ return materializedTools ;
3361+ }
3362+
33023363function compareTransportToolText ( left : string | undefined , right : string | undefined ) : number {
33033364 const leftText = left ?? "" ;
33043365 const rightText = right ?? "" ;
0 commit comments