@@ -27,7 +27,11 @@ import {
2727 applyOpenAIResponsesPayloadPolicy ,
2828 resolveOpenAIResponsesPayloadPolicy ,
2929} from "./openai-responses-payload-policy.js" ;
30- import { resolveProviderRequestCapabilities } from "./provider-attribution.js" ;
30+ import {
31+ normalizeOpenAIStrictToolParameters ,
32+ resolveOpenAIStrictToolFlagForInventory ,
33+ resolveOpenAIStrictToolSetting ,
34+ } from "./openai-tool-schema.js" ;
3135import { buildGuardedModelFetch } from "./provider-transport-fetch.js" ;
3236import { stripSystemPromptCacheBoundary } from "./system-prompt-cache-boundary.js" ;
3337import { transformTransportMessages } from "./transport-message-transform.js" ;
@@ -332,7 +336,7 @@ function convertResponsesTools(
332336 tools : NonNullable < Context [ "tools" ] > ,
333337 options ?: { strict ?: boolean | null } ,
334338) : FunctionTool [ ] {
335- const strict = resolveStrictToolFlagForInventory ( tools , options ?. strict ) ;
339+ const strict = resolveOpenAIStrictToolFlagForInventory ( tools , options ?. strict ) ;
336340 if ( strict === undefined ) {
337341 return tools . map ( ( tool ) => ( {
338342 type : "function" ,
@@ -350,104 +354,6 @@ function convertResponsesTools(
350354 } ) ) ;
351355}
352356
353- function normalizeOpenAIStrictToolParameters < T > ( schema : T , strict : boolean ) : T {
354- if ( ! strict ) {
355- return schema ;
356- }
357- return normalizeStrictOpenAIJsonSchema ( schema ) as T ;
358- }
359-
360- function normalizeStrictOpenAIJsonSchema ( schema : unknown ) : unknown {
361- if ( Array . isArray ( schema ) ) {
362- let changed = false ;
363- const normalized = schema . map ( ( entry ) => {
364- const next = normalizeStrictOpenAIJsonSchema ( entry ) ;
365- changed ||= next !== entry ;
366- return next ;
367- } ) ;
368- return changed ? normalized : schema ;
369- }
370- if ( ! schema || typeof schema !== "object" ) {
371- return schema ;
372- }
373-
374- const record = schema as Record < string , unknown > ;
375- let changed = false ;
376- const normalized : Record < string , unknown > = { } ;
377- for ( const [ key , value ] of Object . entries ( record ) ) {
378- const next = normalizeStrictOpenAIJsonSchema ( value ) ;
379- normalized [ key ] = next ;
380- changed ||= next !== value ;
381- }
382-
383- if ( normalized . type === "object" ) {
384- const properties =
385- normalized . properties &&
386- typeof normalized . properties === "object" &&
387- ! Array . isArray ( normalized . properties )
388- ? ( normalized . properties as Record < string , unknown > )
389- : undefined ;
390- if ( properties && Object . keys ( properties ) . length === 0 && ! Array . isArray ( normalized . required ) ) {
391- normalized . required = [ ] ;
392- changed = true ;
393- }
394- }
395-
396- return changed ? normalized : schema ;
397- }
398-
399- function isStrictOpenAIJsonSchemaCompatible ( schema : unknown ) : boolean {
400- if ( Array . isArray ( schema ) ) {
401- return schema . every ( ( entry ) => isStrictOpenAIJsonSchemaCompatible ( entry ) ) ;
402- }
403- if ( ! schema || typeof schema !== "object" ) {
404- return true ;
405- }
406-
407- const record = schema as Record < string , unknown > ;
408- if ( "anyOf" in record || "oneOf" in record || "allOf" in record ) {
409- return false ;
410- }
411- if ( Array . isArray ( record . type ) ) {
412- return false ;
413- }
414- if ( record . type === "object" && record . additionalProperties !== false ) {
415- return false ;
416- }
417- if ( record . type === "object" ) {
418- const properties =
419- record . properties &&
420- typeof record . properties === "object" &&
421- ! Array . isArray ( record . properties )
422- ? ( record . properties as Record < string , unknown > )
423- : { } ;
424- const required = Array . isArray ( record . required )
425- ? record . required . filter ( ( entry ) : entry is string => typeof entry === "string" )
426- : undefined ;
427- if ( ! required ) {
428- return false ;
429- }
430- const requiredSet = new Set ( required ) ;
431- if ( Object . keys ( properties ) . some ( ( key ) => ! requiredSet . has ( key ) ) ) {
432- return false ;
433- }
434- }
435-
436- return Object . values ( record ) . every ( ( entry ) => isStrictOpenAIJsonSchemaCompatible ( entry ) ) ;
437- }
438-
439- function resolveStrictToolFlagForInventory (
440- tools : NonNullable < Context [ "tools" ] > ,
441- strict : boolean | null | undefined ,
442- ) : boolean | undefined {
443- if ( strict !== true ) {
444- return strict === false ? false : undefined ;
445- }
446- return tools . every ( ( tool ) =>
447- isStrictOpenAIJsonSchemaCompatible ( normalizeStrictOpenAIJsonSchema ( tool . parameters ) ) ,
448- ) ;
449- }
450-
451357async function processResponsesStream (
452358 openaiStream : AsyncIterable < unknown > ,
453359 output : MutableAssistantOutput ,
@@ -857,7 +763,9 @@ export function buildOpenAIResponsesParams(
857763 }
858764 if ( context . tools ) {
859765 params . tools = convertResponsesTools ( context . tools , {
860- strict : resolveOpenAIStrictToolSetting ( model as OpenAIModeModel ) ,
766+ strict : resolveOpenAIStrictToolSetting ( model as OpenAIModeModel , {
767+ transport : "stream" ,
768+ } ) ,
861769 } ) ;
862770 }
863771 if ( model . reasoning ) {
@@ -1318,51 +1226,17 @@ function mapReasoningEffort(effort: string, reasoningEffortMap: Record<string, s
13181226 return reasoningEffortMap [ effort ] ?? effort ;
13191227}
13201228
1321- function resolvesToNativeOpenAIStrictTools ( model : OpenAIModeModel ) : boolean {
1322- const capabilities = resolveProviderRequestCapabilities ( {
1323- provider : model . provider ,
1324- api : model . api ,
1325- baseUrl : model . baseUrl ,
1326- capability : "llm" ,
1327- transport : "stream" ,
1328- modelId : model . id ,
1329- compat :
1330- model . compat && typeof model . compat === "object"
1331- ? ( model . compat as { supportsStore ?: boolean } )
1332- : undefined ,
1333- } ) ;
1334- if ( ! capabilities . usesKnownNativeOpenAIRoute ) {
1335- return false ;
1336- }
1337- return (
1338- capabilities . provider === "openai" ||
1339- capabilities . provider === "openai-codex" ||
1340- capabilities . provider === "azure-openai" ||
1341- capabilities . provider === "azure-openai-responses"
1342- ) ;
1343- }
1344-
1345- function resolveOpenAIStrictToolSetting (
1346- model : OpenAIModeModel ,
1347- compat ?: ReturnType < typeof getCompat > ,
1348- ) : boolean | undefined {
1349- if ( resolvesToNativeOpenAIStrictTools ( model ) ) {
1350- return true ;
1351- }
1352- if ( compat ?. supportsStrictMode ) {
1353- return false ;
1354- }
1355- return undefined ;
1356- }
1357-
13581229function convertTools (
13591230 tools : NonNullable < Context [ "tools" ] > ,
13601231 compat : ReturnType < typeof getCompat > ,
13611232 model : OpenAIModeModel ,
13621233) {
1363- const strict = resolveStrictToolFlagForInventory (
1234+ const strict = resolveOpenAIStrictToolFlagForInventory (
13641235 tools ,
1365- resolveOpenAIStrictToolSetting ( model , compat ) ,
1236+ resolveOpenAIStrictToolSetting ( model , {
1237+ transport : "stream" ,
1238+ supportsStrictMode : compat ?. supportsStrictMode ,
1239+ } ) ,
13661240 ) ;
13671241 return tools . map ( ( tool ) => ( {
13681242 type : "function" ,
0 commit comments