@@ -6,11 +6,13 @@ import {
66 extractToolResultMediaArtifact ,
77 filterToolResultMediaUrls ,
88 HEARTBEAT_RESPONSE_TOOL_NAME ,
9+ embeddedAgentLog ,
910 type EmbeddedRunAttemptParams ,
1011 isToolWrappedWithBeforeToolCallHook ,
1112 isMessagingTool ,
1213 isMessagingToolSendAction ,
1314 normalizeHeartbeatToolResponse ,
15+ projectRuntimeToolInputSchema ,
1416 runAgentHarnessAfterToolCallHook ,
1517 setBeforeToolCallDiagnosticsEnabled ,
1618 type AnyAgentTool ,
@@ -46,6 +48,16 @@ type CodexDynamicToolHookContext = {
4648
4749type CodexToolResultHookContext = Omit < CodexDynamicToolHookContext , "config" > ;
4850
51+ type ProjectedCodexDynamicTool = {
52+ tool : AnyAgentTool ;
53+ inputSchema : JsonValue ;
54+ } ;
55+
56+ type CodexDynamicToolSchemaQuarantine = {
57+ tool : string ;
58+ violations : readonly string [ ] ;
59+ } ;
60+
4961export type CodexDynamicToolBridge = {
5062 availableSpecs : CodexDynamicToolSpec [ ] ;
5163 specs : CodexDynamicToolSpec [ ] ;
@@ -63,6 +75,7 @@ export type CodexDynamicToolBridge = {
6375 toolMediaUrls : string [ ] ;
6476 toolAudioAsVoice : boolean ;
6577 successfulCronAdds ?: number ;
78+ quarantinedTools : CodexDynamicToolSchemaQuarantine [ ] ;
6679 } ;
6780} ;
6881
@@ -83,16 +96,28 @@ export function createCodexDynamicToolBridge(params: {
8396} ) : CodexDynamicToolBridge {
8497 const toolResultHookContext = toToolResultHookContext ( params . hookContext ) ;
8598 const toolResultMaxChars = resolveCodexDynamicToolResultMaxChars ( params . hookContext ) ;
86- const tools = params . tools . map ( ( tool ) => {
99+ const availableProjection = projectCodexDynamicTools ( params . tools ) ;
100+ const registeredProjection = params . registeredTools
101+ ? projectCodexDynamicTools ( params . registeredTools )
102+ : availableProjection ;
103+ const availableTools = availableProjection . tools . map ( ( { tool, inputSchema } ) => {
87104 if ( isToolWrappedWithBeforeToolCallHook ( tool ) ) {
88105 setBeforeToolCallDiagnosticsEnabled ( tool , false ) ;
89- return tool ;
106+ return { tool, inputSchema } ;
90107 }
91- return wrapToolWithBeforeToolCallHook ( tool , params . hookContext , { emitDiagnostics : false } ) ;
108+ return {
109+ tool : wrapToolWithBeforeToolCallHook ( tool , params . hookContext , { emitDiagnostics : false } ) ,
110+ inputSchema,
111+ } ;
92112 } ) ;
93- const toolMap = new Map ( tools . map ( ( tool ) => [ tool . name , tool ] ) ) ;
94- const registeredTools = params . registeredTools ?? tools ;
113+ const toolMap = new Map ( availableTools . map ( ( { tool } ) => [ tool . name , tool ] ) ) ;
114+ const registeredTools = registeredProjection . tools . map ( ( { tool } ) => tool ) ;
95115 const registeredToolNames = new Set ( registeredTools . map ( ( tool ) => tool . name ) ) ;
116+ const quarantinedTools = dedupeQuarantinedDynamicTools ( [
117+ ...availableProjection . quarantinedTools ,
118+ ...registeredProjection . quarantinedTools ,
119+ ] ) ;
120+ warnQuarantinedDynamicTools ( quarantinedTools ) ;
96121 const telemetry : CodexDynamicToolBridge [ "telemetry" ] = {
97122 didSendViaMessagingTool : false ,
98123 messagingToolSentTexts : [ ] ,
@@ -101,6 +126,7 @@ export function createCodexDynamicToolBridge(params: {
101126 messagingToolSourceReplyPayloads : [ ] ,
102127 toolMediaUrls : [ ] ,
103128 toolAudioAsVoice : false ,
129+ quarantinedTools,
104130 } ;
105131 const middlewareRunner = createAgentToolResultMiddlewareRunner ( {
106132 runtime : "codex" ,
@@ -114,16 +140,18 @@ export function createCodexDynamicToolBridge(params: {
114140 ] ) ;
115141
116142 return {
117- availableSpecs : tools . map ( ( tool ) =>
143+ availableSpecs : availableTools . map ( ( { tool, inputSchema } ) =>
118144 createCodexDynamicToolSpec ( {
119145 tool,
146+ inputSchema,
120147 loading : params . loading ?? "searchable" ,
121148 directToolNames,
122149 } ) ,
123150 ) ,
124- specs : registeredTools . map ( ( tool ) =>
151+ specs : registeredProjection . tools . map ( ( { tool, inputSchema } ) =>
125152 createCodexDynamicToolSpec ( {
126153 tool,
154+ inputSchema,
127155 loading : params . loading ?? "searchable" ,
128156 directToolNames,
129157 } ) ,
@@ -257,13 +285,14 @@ export function createCodexDynamicToolBridge(params: {
257285
258286function createCodexDynamicToolSpec ( params : {
259287 tool : AnyAgentTool ;
288+ inputSchema : JsonValue ;
260289 loading : CodexDynamicToolsLoading ;
261290 directToolNames : ReadonlySet < string > ;
262291} ) : CodexDynamicToolSpec {
263292 const base = {
264293 name : params . tool . name ,
265294 description : params . tool . description ,
266- inputSchema : toJsonValue ( params . tool . parameters ) ,
295+ inputSchema : params . inputSchema ,
267296 } ;
268297 if ( params . loading === "direct" || params . directToolNames . has ( params . tool . name ) ) {
269298 return base ;
@@ -274,6 +303,55 @@ function createCodexDynamicToolSpec(params: {
274303 deferLoading : true ,
275304 } ;
276305}
306+
307+ function projectCodexDynamicTools ( tools : readonly AnyAgentTool [ ] ) : {
308+ tools : ProjectedCodexDynamicTool [ ] ;
309+ quarantinedTools : CodexDynamicToolSchemaQuarantine [ ] ;
310+ } {
311+ const projectedTools : ProjectedCodexDynamicTool [ ] = [ ] ;
312+ const quarantinedTools : CodexDynamicToolSchemaQuarantine [ ] = [ ] ;
313+ for ( const tool of tools ) {
314+ const projection = projectRuntimeToolInputSchema ( tool . parameters , `${ tool . name } .inputSchema` ) ;
315+ if ( projection . violations . length > 0 ) {
316+ quarantinedTools . push ( { tool : tool . name , violations : projection . violations } ) ;
317+ continue ;
318+ }
319+ projectedTools . push ( { tool, inputSchema : projection . schema as JsonValue } ) ;
320+ }
321+ return { tools : projectedTools , quarantinedTools } ;
322+ }
323+
324+ function warnQuarantinedDynamicTools ( tools : readonly CodexDynamicToolSchemaQuarantine [ ] ) : void {
325+ if ( tools . length === 0 ) {
326+ return ;
327+ }
328+ const unique = new Map < string , readonly string [ ] > ( ) ;
329+ for ( const tool of tools ) {
330+ unique . set ( tool . tool , tool . violations ) ;
331+ }
332+ embeddedAgentLog . warn (
333+ `codex app-server quarantined ${ unique . size } dynamic ${ unique . size === 1 ? "tool" : "tools" } with unsupported input schemas: ${ [ ...unique . keys ( ) ] . join ( ", " ) } ` ,
334+ {
335+ tools : [ ...unique . entries ( ) ] . map ( ( [ tool , violations ] ) => ( { tool, violations } ) ) ,
336+ } ,
337+ ) ;
338+ }
339+
340+ function dedupeQuarantinedDynamicTools (
341+ tools : readonly CodexDynamicToolSchemaQuarantine [ ] ,
342+ ) : CodexDynamicToolSchemaQuarantine [ ] {
343+ return [
344+ ...new Map (
345+ tools . map ( ( tool ) => [
346+ tool . tool ,
347+ {
348+ tool : tool . tool ,
349+ violations : tool . violations ,
350+ } ,
351+ ] ) ,
352+ ) . values ( ) ,
353+ ] ;
354+ }
277355function toToolResultHookContext (
278356 ctx : CodexDynamicToolHookContext | undefined ,
279357) : CodexToolResultHookContext {
@@ -634,18 +712,6 @@ function convertToolContent(
634712 ] ;
635713}
636714
637- function toJsonValue ( value : unknown ) : JsonValue {
638- try {
639- const text = JSON . stringify ( value ) ;
640- if ( ! text ) {
641- return { } ;
642- }
643- return JSON . parse ( text ) as JsonValue ;
644- } catch {
645- return { } ;
646- }
647- }
648-
649715function jsonObjectToRecord ( value : JsonValue | undefined ) : Record < string , unknown > {
650716 if ( ! value || typeof value !== "object" || Array . isArray ( value ) ) {
651717 return { } ;
0 commit comments