11import type { Logger } from '@sim/logger'
22import type { StreamingExecution } from '@/executor/types'
33import { MAX_TOOL_ITERATIONS } from '@/providers'
4+ import type { Message , ProviderRequest , ProviderResponse , TimeSegment } from '@/providers/types'
5+ import {
6+ calculateCost ,
7+ prepareToolExecution ,
8+ prepareToolsWithUsageControl ,
9+ trackForcedToolUsage ,
10+ } from '@/providers/utils'
11+ import { executeTool } from '@/tools'
412import {
513 buildResponsesInputFromMessages ,
614 convertResponseOutputToInputItems ,
@@ -12,19 +20,59 @@ import {
1220 type ResponsesInputItem ,
1321 type ResponsesToolCall ,
1422 toResponsesToolChoice ,
15- } from '@/providers/responses-utils'
16- import type { Message , ProviderRequest , ProviderResponse , TimeSegment } from '@/providers/types'
17- import {
18- calculateCost ,
19- prepareToolExecution ,
20- prepareToolsWithUsageControl ,
21- trackForcedToolUsage ,
22- } from '@/providers/utils'
23- import { executeTool } from '@/tools'
23+ } from './utils'
2424
2525type PreparedTools = ReturnType < typeof prepareToolsWithUsageControl >
2626type ToolChoice = PreparedTools [ 'toolChoice' ]
2727
28+ /**
29+ * Recursively enforces OpenAI strict mode requirements on a JSON schema.
30+ * - Sets additionalProperties: false on all object types.
31+ * - Ensures required includes ALL property keys.
32+ */
33+ function enforceStrictSchema ( schema : any ) : any {
34+ if ( ! schema || typeof schema !== 'object' ) return schema
35+
36+ const result = { ...schema }
37+
38+ // If this is an object type, enforce strict requirements
39+ if ( result . type === 'object' ) {
40+ result . additionalProperties = false
41+
42+ // Recursively process properties and ensure required includes all keys
43+ if ( result . properties && typeof result . properties === 'object' ) {
44+ const propKeys = Object . keys ( result . properties )
45+ result . required = propKeys // Strict mode requires ALL properties
46+ result . properties = Object . fromEntries (
47+ Object . entries ( result . properties ) . map ( ( [ key , value ] ) => [ key , enforceStrictSchema ( value ) ] )
48+ )
49+ }
50+ }
51+
52+ // Handle array items
53+ if ( result . type === 'array' && result . items ) {
54+ result . items = enforceStrictSchema ( result . items )
55+ }
56+
57+ // Handle anyOf, oneOf, allOf
58+ for ( const keyword of [ 'anyOf' , 'oneOf' , 'allOf' ] ) {
59+ if ( Array . isArray ( result [ keyword ] ) ) {
60+ result [ keyword ] = result [ keyword ] . map ( enforceStrictSchema )
61+ }
62+ }
63+
64+ // Handle $defs / definitions
65+ for ( const defKey of [ '$defs' , 'definitions' ] ) {
66+ if ( result [ defKey ] && typeof result [ defKey ] === 'object' ) {
67+ result [ defKey ] = Object . fromEntries (
68+ Object . entries ( result [ defKey ] ) . map ( ( [ key , value ] ) => [ key , enforceStrictSchema ( value ) ] )
69+ )
70+ }
71+ }
72+
73+ return result
74+ }
75+
2876export interface ResponsesProviderConfig {
2977 providerId : string
3078 providerLabel : string
@@ -97,15 +145,18 @@ export async function executeResponsesProviderRequest(
97145 }
98146
99147 if ( request . responseFormat ) {
148+ const isStrict = request . responseFormat . strict !== false
149+ const rawSchema = request . responseFormat . schema || request . responseFormat
150+ // OpenAI strict mode requires additionalProperties: false on ALL nested objects
151+ const cleanedSchema = isStrict ? enforceStrictSchema ( rawSchema ) : rawSchema
152+
100153 basePayload . text = {
101154 ...( basePayload . text ?? { } ) ,
102155 format : {
103156 type : 'json_schema' ,
104- json_schema : {
105- name : request . responseFormat . name || 'response_schema' ,
106- schema : request . responseFormat . schema || request . responseFormat ,
107- strict : request . responseFormat . strict !== false ,
108- } ,
157+ name : request . responseFormat . name || 'response_schema' ,
158+ schema : cleanedSchema ,
159+ strict : isStrict ,
109160 } ,
110161 }
111162
0 commit comments