diff --git a/codex-rs/app-server-protocol/schema/json/ServerNotification.json b/codex-rs/app-server-protocol/schema/json/ServerNotification.json index 489d2cfaaf..cf63aaf2e2 100644 --- a/codex-rs/app-server-protocol/schema/json/ServerNotification.json +++ b/codex-rs/app-server-protocol/schema/json/ServerNotification.json @@ -1100,6 +1100,33 @@ ], "type": "string" }, + "DynamicToolSchemaErrorTool": { + "properties": { + "index": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "name": { + "type": "string" + }, + "namespace": { + "type": [ + "string", + "null" + ] + }, + "qualifiedName": { + "type": "string" + } + }, + "required": [ + "index", + "name", + "qualifiedName" + ], + "type": "object" + }, "ErrorNotification": { "properties": { "error": { @@ -4831,6 +4858,14 @@ ] }, "data": { + "anyOf": [ + { + "$ref": "#/definitions/TurnErrorData" + }, + { + "type": "null" + } + ], "default": null }, "message": { @@ -4842,6 +4877,59 @@ ], "type": "object" }, + "TurnErrorData": { + "oneOf": [ + { + "properties": { + "backend": { + "type": "string" + }, + "reason": { + "enum": [ + "dynamicToolInputSchema" + ], + "type": "string" + }, + "remediation": { + "type": "string" + }, + "schemaPath": { + "type": [ + "string", + "null" + ] + }, + "tool": { + "anyOf": [ + { + "$ref": "#/definitions/DynamicToolSchemaErrorTool" + }, + { + "type": "null" + } + ] + }, + "toolCandidates": { + "items": { + "$ref": "#/definitions/DynamicToolSchemaErrorTool" + }, + "type": "array" + }, + "underlyingError": { + "type": "string" + } + }, + "required": [ + "backend", + "reason", + "remediation", + "toolCandidates", + "underlyingError" + ], + "type": "object" + } + ] + }, "TurnItemsView": { "oneOf": [ { diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index c532665190..b15474f008 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -8214,6 +8214,33 @@ ], "type": "string" }, + "DynamicToolSchemaErrorTool": { + "properties": { + "index": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "name": { + "type": "string" + }, + "namespace": { + "type": [ + "string", + "null" + ] + }, + "qualifiedName": { + "type": "string" + } + }, + "required": [ + "index", + "name", + "qualifiedName" + ], + "type": "object" + }, "DynamicToolSpec": { "properties": { "deferLoading": { @@ -18260,6 +18287,14 @@ ] }, "data": { + "anyOf": [ + { + "$ref": "#/definitions/v2/TurnErrorData" + }, + { + "type": "null" + } + ], "default": null }, "message": { @@ -18271,6 +18306,59 @@ ], "type": "object" }, + "TurnErrorData": { + "oneOf": [ + { + "properties": { + "backend": { + "type": "string" + }, + "reason": { + "enum": [ + "dynamicToolInputSchema" + ], + "type": "string" + }, + "remediation": { + "type": "string" + }, + "schemaPath": { + "type": [ + "string", + "null" + ] + }, + "tool": { + "anyOf": [ + { + "$ref": "#/definitions/v2/DynamicToolSchemaErrorTool" + }, + { + "type": "null" + } + ] + }, + "toolCandidates": { + "items": { + "$ref": "#/definitions/v2/DynamicToolSchemaErrorTool" + }, + "type": "array" + }, + "underlyingError": { + "type": "string" + } + }, + "required": [ + "backend", + "reason", + "remediation", + "toolCandidates", + "underlyingError" + ], + "type": "object" + } + ] + }, "TurnInterruptParams": { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json index a80599d356..5c33370896 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json @@ -4583,6 +4583,33 @@ ], "type": "string" }, + "DynamicToolSchemaErrorTool": { + "properties": { + "index": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "name": { + "type": "string" + }, + "namespace": { + "type": [ + "string", + "null" + ] + }, + "qualifiedName": { + "type": "string" + } + }, + "required": [ + "index", + "name", + "qualifiedName" + ], + "type": "object" + }, "DynamicToolSpec": { "properties": { "deferLoading": { @@ -16084,6 +16111,14 @@ ] }, "data": { + "anyOf": [ + { + "$ref": "#/definitions/TurnErrorData" + }, + { + "type": "null" + } + ], "default": null }, "message": { @@ -16095,6 +16130,59 @@ ], "type": "object" }, + "TurnErrorData": { + "oneOf": [ + { + "properties": { + "backend": { + "type": "string" + }, + "reason": { + "enum": [ + "dynamicToolInputSchema" + ], + "type": "string" + }, + "remediation": { + "type": "string" + }, + "schemaPath": { + "type": [ + "string", + "null" + ] + }, + "tool": { + "anyOf": [ + { + "$ref": "#/definitions/DynamicToolSchemaErrorTool" + }, + { + "type": "null" + } + ] + }, + "toolCandidates": { + "items": { + "$ref": "#/definitions/DynamicToolSchemaErrorTool" + }, + "type": "array" + }, + "underlyingError": { + "type": "string" + } + }, + "required": [ + "backend", + "reason", + "remediation", + "toolCandidates", + "underlyingError" + ], + "type": "object" + } + ] + }, "TurnInterruptParams": { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ErrorNotification.json b/codex-rs/app-server-protocol/schema/json/v2/ErrorNotification.json index 6f31beb813..e6694582f7 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ErrorNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ErrorNotification.json @@ -138,6 +138,33 @@ } ] }, + "DynamicToolSchemaErrorTool": { + "properties": { + "index": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "name": { + "type": "string" + }, + "namespace": { + "type": [ + "string", + "null" + ] + }, + "qualifiedName": { + "type": "string" + } + }, + "required": [ + "index", + "name", + "qualifiedName" + ], + "type": "object" + }, "NonSteerableTurnKind": { "enum": [ "review", @@ -165,6 +192,14 @@ ] }, "data": { + "anyOf": [ + { + "$ref": "#/definitions/TurnErrorData" + }, + { + "type": "null" + } + ], "default": null }, "message": { @@ -175,6 +210,59 @@ "message" ], "type": "object" + }, + "TurnErrorData": { + "oneOf": [ + { + "properties": { + "backend": { + "type": "string" + }, + "reason": { + "enum": [ + "dynamicToolInputSchema" + ], + "type": "string" + }, + "remediation": { + "type": "string" + }, + "schemaPath": { + "type": [ + "string", + "null" + ] + }, + "tool": { + "anyOf": [ + { + "$ref": "#/definitions/DynamicToolSchemaErrorTool" + }, + { + "type": "null" + } + ] + }, + "toolCandidates": { + "items": { + "$ref": "#/definitions/DynamicToolSchemaErrorTool" + }, + "type": "array" + }, + "underlyingError": { + "type": "string" + } + }, + "required": [ + "backend", + "reason", + "remediation", + "toolCandidates", + "underlyingError" + ], + "type": "object" + } + ] } }, "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json index 4d587543e2..39aca065c2 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json @@ -388,6 +388,33 @@ ], "type": "string" }, + "DynamicToolSchemaErrorTool": { + "properties": { + "index": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "name": { + "type": "string" + }, + "namespace": { + "type": [ + "string", + "null" + ] + }, + "qualifiedName": { + "type": "string" + } + }, + "required": [ + "index", + "name", + "qualifiedName" + ], + "type": "object" + }, "FileUpdateChange": { "properties": { "diff": { @@ -1391,6 +1418,14 @@ ] }, "data": { + "anyOf": [ + { + "$ref": "#/definitions/TurnErrorData" + }, + { + "type": "null" + } + ], "default": null }, "message": { @@ -1402,6 +1437,59 @@ ], "type": "object" }, + "TurnErrorData": { + "oneOf": [ + { + "properties": { + "backend": { + "type": "string" + }, + "reason": { + "enum": [ + "dynamicToolInputSchema" + ], + "type": "string" + }, + "remediation": { + "type": "string" + }, + "schemaPath": { + "type": [ + "string", + "null" + ] + }, + "tool": { + "anyOf": [ + { + "$ref": "#/definitions/DynamicToolSchemaErrorTool" + }, + { + "type": "null" + } + ] + }, + "toolCandidates": { + "items": { + "$ref": "#/definitions/DynamicToolSchemaErrorTool" + }, + "type": "array" + }, + "underlyingError": { + "type": "string" + } + }, + "required": [ + "backend", + "reason", + "remediation", + "toolCandidates", + "underlyingError" + ], + "type": "object" + } + ] + }, "TurnItemsView": { "oneOf": [ { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json index 0deadad717..63bb959c33 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json @@ -470,6 +470,33 @@ ], "type": "string" }, + "DynamicToolSchemaErrorTool": { + "properties": { + "index": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "name": { + "type": "string" + }, + "namespace": { + "type": [ + "string", + "null" + ] + }, + "qualifiedName": { + "type": "string" + } + }, + "required": [ + "index", + "name", + "qualifiedName" + ], + "type": "object" + }, "FileUpdateChange": { "properties": { "diff": { @@ -1951,6 +1978,14 @@ ] }, "data": { + "anyOf": [ + { + "$ref": "#/definitions/TurnErrorData" + }, + { + "type": "null" + } + ], "default": null }, "message": { @@ -1962,6 +1997,59 @@ ], "type": "object" }, + "TurnErrorData": { + "oneOf": [ + { + "properties": { + "backend": { + "type": "string" + }, + "reason": { + "enum": [ + "dynamicToolInputSchema" + ], + "type": "string" + }, + "remediation": { + "type": "string" + }, + "schemaPath": { + "type": [ + "string", + "null" + ] + }, + "tool": { + "anyOf": [ + { + "$ref": "#/definitions/DynamicToolSchemaErrorTool" + }, + { + "type": "null" + } + ] + }, + "toolCandidates": { + "items": { + "$ref": "#/definitions/DynamicToolSchemaErrorTool" + }, + "type": "array" + }, + "underlyingError": { + "type": "string" + } + }, + "required": [ + "backend", + "reason", + "remediation", + "toolCandidates", + "underlyingError" + ], + "type": "object" + } + ] + }, "TurnItemsView": { "oneOf": [ { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json index 2a5fdda1dd..6d7a7527f7 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json @@ -391,6 +391,33 @@ ], "type": "string" }, + "DynamicToolSchemaErrorTool": { + "properties": { + "index": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "name": { + "type": "string" + }, + "namespace": { + "type": [ + "string", + "null" + ] + }, + "qualifiedName": { + "type": "string" + } + }, + "required": [ + "index", + "name", + "qualifiedName" + ], + "type": "object" + }, "FileUpdateChange": { "properties": { "diff": { @@ -1766,6 +1793,14 @@ ] }, "data": { + "anyOf": [ + { + "$ref": "#/definitions/TurnErrorData" + }, + { + "type": "null" + } + ], "default": null }, "message": { @@ -1777,6 +1812,59 @@ ], "type": "object" }, + "TurnErrorData": { + "oneOf": [ + { + "properties": { + "backend": { + "type": "string" + }, + "reason": { + "enum": [ + "dynamicToolInputSchema" + ], + "type": "string" + }, + "remediation": { + "type": "string" + }, + "schemaPath": { + "type": [ + "string", + "null" + ] + }, + "tool": { + "anyOf": [ + { + "$ref": "#/definitions/DynamicToolSchemaErrorTool" + }, + { + "type": "null" + } + ] + }, + "toolCandidates": { + "items": { + "$ref": "#/definitions/DynamicToolSchemaErrorTool" + }, + "type": "array" + }, + "underlyingError": { + "type": "string" + } + }, + "required": [ + "backend", + "reason", + "remediation", + "toolCandidates", + "underlyingError" + ], + "type": "object" + } + ] + }, "TurnItemsView": { "oneOf": [ { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json index 8bebcdf368..d29d279c88 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json @@ -391,6 +391,33 @@ ], "type": "string" }, + "DynamicToolSchemaErrorTool": { + "properties": { + "index": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "name": { + "type": "string" + }, + "namespace": { + "type": [ + "string", + "null" + ] + }, + "qualifiedName": { + "type": "string" + } + }, + "required": [ + "index", + "name", + "qualifiedName" + ], + "type": "object" + }, "FileUpdateChange": { "properties": { "diff": { @@ -1766,6 +1793,14 @@ ] }, "data": { + "anyOf": [ + { + "$ref": "#/definitions/TurnErrorData" + }, + { + "type": "null" + } + ], "default": null }, "message": { @@ -1777,6 +1812,59 @@ ], "type": "object" }, + "TurnErrorData": { + "oneOf": [ + { + "properties": { + "backend": { + "type": "string" + }, + "reason": { + "enum": [ + "dynamicToolInputSchema" + ], + "type": "string" + }, + "remediation": { + "type": "string" + }, + "schemaPath": { + "type": [ + "string", + "null" + ] + }, + "tool": { + "anyOf": [ + { + "$ref": "#/definitions/DynamicToolSchemaErrorTool" + }, + { + "type": "null" + } + ] + }, + "toolCandidates": { + "items": { + "$ref": "#/definitions/DynamicToolSchemaErrorTool" + }, + "type": "array" + }, + "underlyingError": { + "type": "string" + } + }, + "required": [ + "backend", + "reason", + "remediation", + "toolCandidates", + "underlyingError" + ], + "type": "object" + } + ] + }, "TurnItemsView": { "oneOf": [ { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json index c2ca377790..91afe16373 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json @@ -391,6 +391,33 @@ ], "type": "string" }, + "DynamicToolSchemaErrorTool": { + "properties": { + "index": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "name": { + "type": "string" + }, + "namespace": { + "type": [ + "string", + "null" + ] + }, + "qualifiedName": { + "type": "string" + } + }, + "required": [ + "index", + "name", + "qualifiedName" + ], + "type": "object" + }, "FileUpdateChange": { "properties": { "diff": { @@ -1766,6 +1793,14 @@ ] }, "data": { + "anyOf": [ + { + "$ref": "#/definitions/TurnErrorData" + }, + { + "type": "null" + } + ], "default": null }, "message": { @@ -1777,6 +1812,59 @@ ], "type": "object" }, + "TurnErrorData": { + "oneOf": [ + { + "properties": { + "backend": { + "type": "string" + }, + "reason": { + "enum": [ + "dynamicToolInputSchema" + ], + "type": "string" + }, + "remediation": { + "type": "string" + }, + "schemaPath": { + "type": [ + "string", + "null" + ] + }, + "tool": { + "anyOf": [ + { + "$ref": "#/definitions/DynamicToolSchemaErrorTool" + }, + { + "type": "null" + } + ] + }, + "toolCandidates": { + "items": { + "$ref": "#/definitions/DynamicToolSchemaErrorTool" + }, + "type": "array" + }, + "underlyingError": { + "type": "string" + } + }, + "required": [ + "backend", + "reason", + "remediation", + "toolCandidates", + "underlyingError" + ], + "type": "object" + } + ] + }, "TurnItemsView": { "oneOf": [ { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json index d9e542f34d..09fbd46e9d 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json @@ -470,6 +470,33 @@ ], "type": "string" }, + "DynamicToolSchemaErrorTool": { + "properties": { + "index": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "name": { + "type": "string" + }, + "namespace": { + "type": [ + "string", + "null" + ] + }, + "qualifiedName": { + "type": "string" + } + }, + "required": [ + "index", + "name", + "qualifiedName" + ], + "type": "object" + }, "FileUpdateChange": { "properties": { "diff": { @@ -1951,6 +1978,14 @@ ] }, "data": { + "anyOf": [ + { + "$ref": "#/definitions/TurnErrorData" + }, + { + "type": "null" + } + ], "default": null }, "message": { @@ -1962,6 +1997,59 @@ ], "type": "object" }, + "TurnErrorData": { + "oneOf": [ + { + "properties": { + "backend": { + "type": "string" + }, + "reason": { + "enum": [ + "dynamicToolInputSchema" + ], + "type": "string" + }, + "remediation": { + "type": "string" + }, + "schemaPath": { + "type": [ + "string", + "null" + ] + }, + "tool": { + "anyOf": [ + { + "$ref": "#/definitions/DynamicToolSchemaErrorTool" + }, + { + "type": "null" + } + ] + }, + "toolCandidates": { + "items": { + "$ref": "#/definitions/DynamicToolSchemaErrorTool" + }, + "type": "array" + }, + "underlyingError": { + "type": "string" + } + }, + "required": [ + "backend", + "reason", + "remediation", + "toolCandidates", + "underlyingError" + ], + "type": "object" + } + ] + }, "TurnItemsView": { "oneOf": [ { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json index 7af419c3bb..82b08c6360 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json @@ -391,6 +391,33 @@ ], "type": "string" }, + "DynamicToolSchemaErrorTool": { + "properties": { + "index": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "name": { + "type": "string" + }, + "namespace": { + "type": [ + "string", + "null" + ] + }, + "qualifiedName": { + "type": "string" + } + }, + "required": [ + "index", + "name", + "qualifiedName" + ], + "type": "object" + }, "FileUpdateChange": { "properties": { "diff": { @@ -1766,6 +1793,14 @@ ] }, "data": { + "anyOf": [ + { + "$ref": "#/definitions/TurnErrorData" + }, + { + "type": "null" + } + ], "default": null }, "message": { @@ -1777,6 +1812,59 @@ ], "type": "object" }, + "TurnErrorData": { + "oneOf": [ + { + "properties": { + "backend": { + "type": "string" + }, + "reason": { + "enum": [ + "dynamicToolInputSchema" + ], + "type": "string" + }, + "remediation": { + "type": "string" + }, + "schemaPath": { + "type": [ + "string", + "null" + ] + }, + "tool": { + "anyOf": [ + { + "$ref": "#/definitions/DynamicToolSchemaErrorTool" + }, + { + "type": "null" + } + ] + }, + "toolCandidates": { + "items": { + "$ref": "#/definitions/DynamicToolSchemaErrorTool" + }, + "type": "array" + }, + "underlyingError": { + "type": "string" + } + }, + "required": [ + "backend", + "reason", + "remediation", + "toolCandidates", + "underlyingError" + ], + "type": "object" + } + ] + }, "TurnItemsView": { "oneOf": [ { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json index 4dd55d350c..e797773029 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json @@ -470,6 +470,33 @@ ], "type": "string" }, + "DynamicToolSchemaErrorTool": { + "properties": { + "index": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "name": { + "type": "string" + }, + "namespace": { + "type": [ + "string", + "null" + ] + }, + "qualifiedName": { + "type": "string" + } + }, + "required": [ + "index", + "name", + "qualifiedName" + ], + "type": "object" + }, "FileUpdateChange": { "properties": { "diff": { @@ -1951,6 +1978,14 @@ ] }, "data": { + "anyOf": [ + { + "$ref": "#/definitions/TurnErrorData" + }, + { + "type": "null" + } + ], "default": null }, "message": { @@ -1962,6 +1997,59 @@ ], "type": "object" }, + "TurnErrorData": { + "oneOf": [ + { + "properties": { + "backend": { + "type": "string" + }, + "reason": { + "enum": [ + "dynamicToolInputSchema" + ], + "type": "string" + }, + "remediation": { + "type": "string" + }, + "schemaPath": { + "type": [ + "string", + "null" + ] + }, + "tool": { + "anyOf": [ + { + "$ref": "#/definitions/DynamicToolSchemaErrorTool" + }, + { + "type": "null" + } + ] + }, + "toolCandidates": { + "items": { + "$ref": "#/definitions/DynamicToolSchemaErrorTool" + }, + "type": "array" + }, + "underlyingError": { + "type": "string" + } + }, + "required": [ + "backend", + "reason", + "remediation", + "toolCandidates", + "underlyingError" + ], + "type": "object" + } + ] + }, "TurnItemsView": { "oneOf": [ { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json index 8b8c9ecfb0..8ddfeb33ad 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json @@ -391,6 +391,33 @@ ], "type": "string" }, + "DynamicToolSchemaErrorTool": { + "properties": { + "index": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "name": { + "type": "string" + }, + "namespace": { + "type": [ + "string", + "null" + ] + }, + "qualifiedName": { + "type": "string" + } + }, + "required": [ + "index", + "name", + "qualifiedName" + ], + "type": "object" + }, "FileUpdateChange": { "properties": { "diff": { @@ -1766,6 +1793,14 @@ ] }, "data": { + "anyOf": [ + { + "$ref": "#/definitions/TurnErrorData" + }, + { + "type": "null" + } + ], "default": null }, "message": { @@ -1777,6 +1812,59 @@ ], "type": "object" }, + "TurnErrorData": { + "oneOf": [ + { + "properties": { + "backend": { + "type": "string" + }, + "reason": { + "enum": [ + "dynamicToolInputSchema" + ], + "type": "string" + }, + "remediation": { + "type": "string" + }, + "schemaPath": { + "type": [ + "string", + "null" + ] + }, + "tool": { + "anyOf": [ + { + "$ref": "#/definitions/DynamicToolSchemaErrorTool" + }, + { + "type": "null" + } + ] + }, + "toolCandidates": { + "items": { + "$ref": "#/definitions/DynamicToolSchemaErrorTool" + }, + "type": "array" + }, + "underlyingError": { + "type": "string" + } + }, + "required": [ + "backend", + "reason", + "remediation", + "toolCandidates", + "underlyingError" + ], + "type": "object" + } + ] + }, "TurnItemsView": { "oneOf": [ { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json index c07d2f49d6..219404fda9 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json @@ -391,6 +391,33 @@ ], "type": "string" }, + "DynamicToolSchemaErrorTool": { + "properties": { + "index": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "name": { + "type": "string" + }, + "namespace": { + "type": [ + "string", + "null" + ] + }, + "qualifiedName": { + "type": "string" + } + }, + "required": [ + "index", + "name", + "qualifiedName" + ], + "type": "object" + }, "FileUpdateChange": { "properties": { "diff": { @@ -1766,6 +1793,14 @@ ] }, "data": { + "anyOf": [ + { + "$ref": "#/definitions/TurnErrorData" + }, + { + "type": "null" + } + ], "default": null }, "message": { @@ -1777,6 +1812,59 @@ ], "type": "object" }, + "TurnErrorData": { + "oneOf": [ + { + "properties": { + "backend": { + "type": "string" + }, + "reason": { + "enum": [ + "dynamicToolInputSchema" + ], + "type": "string" + }, + "remediation": { + "type": "string" + }, + "schemaPath": { + "type": [ + "string", + "null" + ] + }, + "tool": { + "anyOf": [ + { + "$ref": "#/definitions/DynamicToolSchemaErrorTool" + }, + { + "type": "null" + } + ] + }, + "toolCandidates": { + "items": { + "$ref": "#/definitions/DynamicToolSchemaErrorTool" + }, + "type": "array" + }, + "underlyingError": { + "type": "string" + } + }, + "required": [ + "backend", + "reason", + "remediation", + "toolCandidates", + "underlyingError" + ], + "type": "object" + } + ] + }, "TurnItemsView": { "oneOf": [ { diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json index 1f0800afa6..664fcfbacf 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json @@ -388,6 +388,33 @@ ], "type": "string" }, + "DynamicToolSchemaErrorTool": { + "properties": { + "index": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "name": { + "type": "string" + }, + "namespace": { + "type": [ + "string", + "null" + ] + }, + "qualifiedName": { + "type": "string" + } + }, + "required": [ + "index", + "name", + "qualifiedName" + ], + "type": "object" + }, "FileUpdateChange": { "properties": { "diff": { @@ -1391,6 +1418,14 @@ ] }, "data": { + "anyOf": [ + { + "$ref": "#/definitions/TurnErrorData" + }, + { + "type": "null" + } + ], "default": null }, "message": { @@ -1402,6 +1437,59 @@ ], "type": "object" }, + "TurnErrorData": { + "oneOf": [ + { + "properties": { + "backend": { + "type": "string" + }, + "reason": { + "enum": [ + "dynamicToolInputSchema" + ], + "type": "string" + }, + "remediation": { + "type": "string" + }, + "schemaPath": { + "type": [ + "string", + "null" + ] + }, + "tool": { + "anyOf": [ + { + "$ref": "#/definitions/DynamicToolSchemaErrorTool" + }, + { + "type": "null" + } + ] + }, + "toolCandidates": { + "items": { + "$ref": "#/definitions/DynamicToolSchemaErrorTool" + }, + "type": "array" + }, + "underlyingError": { + "type": "string" + } + }, + "required": [ + "backend", + "reason", + "remediation", + "toolCandidates", + "underlyingError" + ], + "type": "object" + } + ] + }, "TurnItemsView": { "oneOf": [ { diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json b/codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json index 30af2dd4af..095b4b23d1 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json @@ -388,6 +388,33 @@ ], "type": "string" }, + "DynamicToolSchemaErrorTool": { + "properties": { + "index": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "name": { + "type": "string" + }, + "namespace": { + "type": [ + "string", + "null" + ] + }, + "qualifiedName": { + "type": "string" + } + }, + "required": [ + "index", + "name", + "qualifiedName" + ], + "type": "object" + }, "FileUpdateChange": { "properties": { "diff": { @@ -1391,6 +1418,14 @@ ] }, "data": { + "anyOf": [ + { + "$ref": "#/definitions/TurnErrorData" + }, + { + "type": "null" + } + ], "default": null }, "message": { @@ -1402,6 +1437,59 @@ ], "type": "object" }, + "TurnErrorData": { + "oneOf": [ + { + "properties": { + "backend": { + "type": "string" + }, + "reason": { + "enum": [ + "dynamicToolInputSchema" + ], + "type": "string" + }, + "remediation": { + "type": "string" + }, + "schemaPath": { + "type": [ + "string", + "null" + ] + }, + "tool": { + "anyOf": [ + { + "$ref": "#/definitions/DynamicToolSchemaErrorTool" + }, + { + "type": "null" + } + ] + }, + "toolCandidates": { + "items": { + "$ref": "#/definitions/DynamicToolSchemaErrorTool" + }, + "type": "array" + }, + "underlyingError": { + "type": "string" + } + }, + "required": [ + "backend", + "reason", + "remediation", + "toolCandidates", + "underlyingError" + ], + "type": "object" + } + ] + }, "TurnItemsView": { "oneOf": [ { diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json index f6ee8cf652..c2e224ce49 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json @@ -388,6 +388,33 @@ ], "type": "string" }, + "DynamicToolSchemaErrorTool": { + "properties": { + "index": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "name": { + "type": "string" + }, + "namespace": { + "type": [ + "string", + "null" + ] + }, + "qualifiedName": { + "type": "string" + } + }, + "required": [ + "index", + "name", + "qualifiedName" + ], + "type": "object" + }, "FileUpdateChange": { "properties": { "diff": { @@ -1391,6 +1418,14 @@ ] }, "data": { + "anyOf": [ + { + "$ref": "#/definitions/TurnErrorData" + }, + { + "type": "null" + } + ], "default": null }, "message": { @@ -1402,6 +1437,59 @@ ], "type": "object" }, + "TurnErrorData": { + "oneOf": [ + { + "properties": { + "backend": { + "type": "string" + }, + "reason": { + "enum": [ + "dynamicToolInputSchema" + ], + "type": "string" + }, + "remediation": { + "type": "string" + }, + "schemaPath": { + "type": [ + "string", + "null" + ] + }, + "tool": { + "anyOf": [ + { + "$ref": "#/definitions/DynamicToolSchemaErrorTool" + }, + { + "type": "null" + } + ] + }, + "toolCandidates": { + "items": { + "$ref": "#/definitions/DynamicToolSchemaErrorTool" + }, + "type": "array" + }, + "underlyingError": { + "type": "string" + } + }, + "required": [ + "backend", + "reason", + "remediation", + "toolCandidates", + "underlyingError" + ], + "type": "object" + } + ] + }, "TurnItemsView": { "oneOf": [ { diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/DynamicToolSchemaErrorTool.ts b/codex-rs/app-server-protocol/schema/typescript/v2/DynamicToolSchemaErrorTool.ts new file mode 100644 index 0000000000..d7547782a3 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/DynamicToolSchemaErrorTool.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type DynamicToolSchemaErrorTool = { index: number, namespace: string | null, name: string, qualifiedName: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/TurnError.ts b/codex-rs/app-server-protocol/schema/typescript/v2/TurnError.ts index e067369922..0a21ead95a 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/TurnError.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/TurnError.ts @@ -1,7 +1,7 @@ // GENERATED CODE! DO NOT MODIFY BY HAND! // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { JsonValue } from "../serde_json/JsonValue"; import type { CodexErrorInfo } from "./CodexErrorInfo"; +import type { TurnErrorData } from "./TurnErrorData"; -export type TurnError = { message: string, codexErrorInfo: CodexErrorInfo | null, additionalDetails: string | null, data: JsonValue | null, }; +export type TurnError = { message: string, codexErrorInfo: CodexErrorInfo | null, additionalDetails: string | null, data: TurnErrorData | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/TurnErrorData.ts b/codex-rs/app-server-protocol/schema/typescript/v2/TurnErrorData.ts new file mode 100644 index 0000000000..6cb1a9d1b4 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/TurnErrorData.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { DynamicToolSchemaErrorTool } from "./DynamicToolSchemaErrorTool"; + +export type TurnErrorData = { "reason": "dynamicToolInputSchema", backend: string, tool: DynamicToolSchemaErrorTool | null, toolCandidates: Array, schemaPath: string | null, underlyingError: string, remediation: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts index e9b8993c26..bbb9fc9c1b 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts @@ -85,6 +85,7 @@ export type { DynamicToolCallOutputContentItem } from "./DynamicToolCallOutputCo export type { DynamicToolCallParams } from "./DynamicToolCallParams"; export type { DynamicToolCallResponse } from "./DynamicToolCallResponse"; export type { DynamicToolCallStatus } from "./DynamicToolCallStatus"; +export type { DynamicToolSchemaErrorTool } from "./DynamicToolSchemaErrorTool"; export type { DynamicToolSpec } from "./DynamicToolSpec"; export type { ErrorNotification } from "./ErrorNotification"; export type { ExecPolicyAmendment } from "./ExecPolicyAmendment"; @@ -436,6 +437,7 @@ export type { TurnCompletedNotification } from "./TurnCompletedNotification"; export type { TurnDiffUpdatedNotification } from "./TurnDiffUpdatedNotification"; export type { TurnEnvironmentParams } from "./TurnEnvironmentParams"; export type { TurnError } from "./TurnError"; +export type { TurnErrorData } from "./TurnErrorData"; export type { TurnInterruptParams } from "./TurnInterruptParams"; export type { TurnInterruptResponse } from "./TurnInterruptResponse"; export type { TurnItemsView } from "./TurnItemsView"; diff --git a/codex-rs/app-server-protocol/src/protocol/thread_history.rs b/codex-rs/app-server-protocol/src/protocol/thread_history.rs index d9e07c48b4..ea6ae4c358 100644 --- a/codex-rs/app-server-protocol/src/protocol/thread_history.rs +++ b/codex-rs/app-server-protocol/src/protocol/thread_history.rs @@ -3181,19 +3181,19 @@ mod tests { message: error_message.to_string(), codex_error_info: Some(crate::protocol::v2::CodexErrorInfo::BadRequest), additional_details: None, - data: Some(serde_json::json!({ - "reason": "dynamicToolInputSchema", - "backend": "Responses API", - "tool": { - "index": 0, - "namespace": "repro", - "name": "create", - "qualifiedName": "repro.create" - }, - "schemaPath": "dynamicTools[0].inputSchema.anyOf", - "underlyingError": error_message, - "remediation": "Adjust the dynamic tool inputSchema to the JSON Schema subset accepted by the model backend. For discriminated unions, wrap the union in a top-level object property or flatten it into one object schema." - })), + data: Some(crate::protocol::v2::TurnErrorData::DynamicToolInputSchema { + backend: "Responses API".to_string(), + tool: Some(crate::protocol::v2::DynamicToolSchemaErrorTool { + index: 0, + namespace: Some("repro".to_string()), + name: "create".to_string(), + qualified_name: "repro.create".to_string(), + }), + tool_candidates: Vec::new(), + schema_path: Some("dynamicTools[0].inputSchema.anyOf".to_string()), + underlying_error: error_message.to_string(), + remediation: "Adjust the dynamic tool inputSchema to the JSON Schema subset accepted by the model backend. For discriminated unions, wrap the union in a top-level object property or flatten it into one object schema.".to_string(), + }), }), started_at: None, completed_at: None, diff --git a/codex-rs/app-server-protocol/src/protocol/v2/thread_data.rs b/codex-rs/app-server-protocol/src/protocol/v2/thread_data.rs index a098482ec6..09267194cc 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/thread_data.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/thread_data.rs @@ -12,7 +12,6 @@ use schemars::JsonSchema; use serde::Deserialize; use serde::Serialize; use serde_json::Value as JsonValue; -use serde_json::json; use std::path::PathBuf; use thiserror::Error; use ts_rs::TS; @@ -198,7 +197,34 @@ pub struct TurnError { #[serde(default)] pub additional_details: Option, #[serde(default)] - pub data: Option, + pub data: Option, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] +#[serde(tag = "reason", rename_all = "camelCase")] +#[ts(tag = "reason")] +#[ts(export_to = "v2/")] +pub enum TurnErrorData { + #[serde(rename = "dynamicToolInputSchema", rename_all = "camelCase")] + #[ts(rename = "dynamicToolInputSchema", rename_all = "camelCase")] + DynamicToolInputSchema { + backend: String, + tool: Option, + tool_candidates: Vec, + schema_path: Option, + underlying_error: String, + remediation: String, + }, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct DynamicToolSchemaErrorTool { + pub index: usize, + pub namespace: Option, + pub name: String, + pub qualified_name: String, } impl TurnError { @@ -221,39 +247,46 @@ impl TurnError { fn dynamic_tool_input_schema_error_data( message: &str, dynamic_tools: &[CoreDynamicToolSpec], -) -> Option { +) -> Option { let underlying_error = underlying_backend_error_message(message); let backend_function_name = backend_function_name_from_schema_error(&underlying_error)?; let matching_tools = dynamic_tools .iter() .enumerate() - .filter(|(_, tool)| { - backend_function_name == tool.name - || backend_function_name == qualified_dynamic_tool_name(tool) - }) + .filter(|(_, tool)| dynamic_tool_matches_backend_name(tool, backend_function_name)) .collect::>(); - let [(index, tool)] = matching_tools.as_slice() else { + if matching_tools.is_empty() { return None; + } + let (tool, tool_candidates, schema_path) = match matching_tools.as_slice() { + [(index, tool)] => ( + Some(dynamic_tool_error_tool(*index, tool)), + Vec::new(), + Some(dynamic_tool_schema_path(*index, &tool.input_schema)), + ), + _ => ( + None, + matching_tools + .iter() + .map(|(index, tool)| dynamic_tool_error_tool(*index, tool)) + .collect(), + None, + ), }; - let qualified_name = qualified_dynamic_tool_name(tool); - let schema_path = dynamic_tool_schema_path(*index, &tool.input_schema); - Some(json!({ - "reason": "dynamicToolInputSchema", - "backend": "Responses API", - "tool": { - "index": index, - "namespace": tool.namespace, - "name": tool.name, - "qualifiedName": qualified_name, - }, - "schemaPath": schema_path, - "underlyingError": underlying_error, - "remediation": "Adjust the dynamic tool inputSchema to the JSON Schema subset accepted by the model backend. For discriminated unions, wrap the union in a top-level object property or flatten it into one object schema.", - })) + Some(TurnErrorData::DynamicToolInputSchema { + backend: "Responses API".to_string(), + tool, + tool_candidates, + schema_path, + underlying_error, + remediation: DYNAMIC_TOOL_INPUT_SCHEMA_REMEDIATION.to_string(), + }) } +const DYNAMIC_TOOL_INPUT_SCHEMA_REMEDIATION: &str = "Adjust the dynamic tool inputSchema to the JSON Schema subset accepted by the model backend. For discriminated unions, wrap the union in a top-level object property or flatten it into one object schema."; + fn underlying_backend_error_message(message: &str) -> String { let Ok(value) = serde_json::from_str::(message) else { return message.to_string(); @@ -305,9 +338,170 @@ fn dynamic_tool_schema_path(index: usize, input_schema: &JsonValue) -> String { format!("dynamicTools[{index}].inputSchema{suffix}") } +fn dynamic_tool_matches_backend_name( + tool: &CoreDynamicToolSpec, + backend_function_name: &str, +) -> bool { + backend_function_name == tool.name || backend_function_name == qualified_dynamic_tool_name(tool) +} + +fn dynamic_tool_error_tool(index: usize, tool: &CoreDynamicToolSpec) -> DynamicToolSchemaErrorTool { + DynamicToolSchemaErrorTool { + index, + namespace: tool.namespace.clone(), + name: tool.name.clone(), + qualified_name: qualified_dynamic_tool_name(tool), + } +} + fn qualified_dynamic_tool_name(tool: &CoreDynamicToolSpec) -> String { match tool.namespace.as_deref() { Some(namespace) if !namespace.is_empty() => format!("{namespace}.{}", tool.name), Some(_) | None => tool.name.clone(), } } + +#[cfg(test)] +mod tests { + use super::DynamicToolSchemaErrorTool; + use super::TurnError; + use super::TurnErrorData; + use codex_protocol::dynamic_tools::DynamicToolSpec; + use codex_protocol::protocol::CodexErrorInfo; + use codex_protocol::protocol::ErrorEvent; + use pretty_assertions::assert_eq; + use serde_json::json; + + #[test] + fn dynamic_tool_schema_error_extracts_json_backend_message() { + let backend_error = "invalid_function_parameters ... Invalid schema for function 'create': schema must be a JSON Schema of 'type: \"object\"', got 'type: \"None\""; + let error = TurnError::from_core_error_event( + &ErrorEvent { + message: json!({ + "error": { + "message": backend_error, + "type": "invalid_request_error", + "code": "invalid_function_parameters" + } + }) + .to_string(), + codex_error_info: Some(CodexErrorInfo::BadRequest), + }, + Some(&[dynamic_tool(Some("repro"), "create", json!({"anyOf": []}))]), + ); + + assert_eq!( + error.data, + Some(TurnErrorData::DynamicToolInputSchema { + backend: "Responses API".to_string(), + tool: Some(DynamicToolSchemaErrorTool { + index: 0, + namespace: Some("repro".to_string()), + name: "create".to_string(), + qualified_name: "repro.create".to_string(), + }), + tool_candidates: Vec::new(), + schema_path: Some("dynamicTools[0].inputSchema.anyOf".to_string()), + underlying_error: backend_error.to_string(), + remediation: super::DYNAMIC_TOOL_INPUT_SCHEMA_REMEDIATION.to_string(), + }) + ); + } + + #[test] + fn dynamic_tool_schema_error_matches_namespaced_backend_name() { + let error = TurnError::from_core_error_event( + &ErrorEvent { + message: "Invalid schema for function 'repro.create'".to_string(), + codex_error_info: Some(CodexErrorInfo::BadRequest), + }, + Some(&[ + dynamic_tool(Some("other"), "create", json!({"anyOf": []})), + dynamic_tool(Some("repro"), "create", json!({"oneOf": []})), + ]), + ); + + assert_eq!( + error.data, + Some(TurnErrorData::DynamicToolInputSchema { + backend: "Responses API".to_string(), + tool: Some(DynamicToolSchemaErrorTool { + index: 1, + namespace: Some("repro".to_string()), + name: "create".to_string(), + qualified_name: "repro.create".to_string(), + }), + tool_candidates: Vec::new(), + schema_path: Some("dynamicTools[1].inputSchema.oneOf".to_string()), + underlying_error: "Invalid schema for function 'repro.create'".to_string(), + remediation: super::DYNAMIC_TOOL_INPUT_SCHEMA_REMEDIATION.to_string(), + }) + ); + } + + #[test] + fn dynamic_tool_schema_error_reports_ambiguous_bare_name_candidates() { + let error = TurnError::from_core_error_event( + &ErrorEvent { + message: "Invalid schema for function 'create'".to_string(), + codex_error_info: Some(CodexErrorInfo::BadRequest), + }, + Some(&[ + dynamic_tool(Some("alpha"), "create", json!({"anyOf": []})), + dynamic_tool(Some("beta"), "create", json!({"oneOf": []})), + ]), + ); + + assert_eq!( + error.data, + Some(TurnErrorData::DynamicToolInputSchema { + backend: "Responses API".to_string(), + tool: None, + tool_candidates: vec![ + DynamicToolSchemaErrorTool { + index: 0, + namespace: Some("alpha".to_string()), + name: "create".to_string(), + qualified_name: "alpha.create".to_string(), + }, + DynamicToolSchemaErrorTool { + index: 1, + namespace: Some("beta".to_string()), + name: "create".to_string(), + qualified_name: "beta.create".to_string(), + }, + ], + schema_path: None, + underlying_error: "Invalid schema for function 'create'".to_string(), + remediation: super::DYNAMIC_TOOL_INPUT_SCHEMA_REMEDIATION.to_string(), + }) + ); + } + + #[test] + fn dynamic_tool_schema_error_ignores_unknown_function_name() { + let error = TurnError::from_core_error_event( + &ErrorEvent { + message: "Invalid schema for function 'shell'".to_string(), + codex_error_info: Some(CodexErrorInfo::BadRequest), + }, + Some(&[dynamic_tool(Some("repro"), "create", json!({"anyOf": []}))]), + ); + + assert_eq!(error.data, None); + } + + fn dynamic_tool( + namespace: Option<&str>, + name: &str, + input_schema: serde_json::Value, + ) -> DynamicToolSpec { + DynamicToolSpec { + namespace: namespace.map(str::to_string), + name: name.to_string(), + description: "test".to_string(), + input_schema, + defer_loading: false, + } + } +} diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index 17bbf365bd..2a65f7576b 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -1410,7 +1410,7 @@ Dynamic tool identifiers follow the same constraints as Responses function tools Each dynamic tool may set `deferLoading`. When omitted, it defaults to `false`. Set it to `true` to keep the tool registered and callable by runtime features such as `code_mode`, while excluding it from the model-facing tool list sent on ordinary turns. When `tool_search` is available, deferred dynamic tools are searchable and can be exposed by a matching search result. -If the model backend rejects a dynamic tool schema during turn startup, app-server emits the normal `error` notification and stores the failure on the turn returned by `thread/read`. When the backend error names a dynamic tool, `TurnError.data.reason` is `dynamicToolInputSchema` and includes the tool namespace/name, `schemaPath`, backend, underlying backend message, and remediation text. +If the model backend rejects a dynamic tool schema during turn startup, app-server emits the normal `error` notification and stores the failure on the turn returned by `thread/read` and `thread/turns/list`. When the backend error names a dynamic tool, `TurnError.data.reason` is `dynamicToolInputSchema` and includes the tool namespace/name, `schemaPath`, backend, underlying backend message, and remediation text. If the backend reports an ambiguous bare function name, `tool` and `schemaPath` are `null` and `toolCandidates` lists the matching dynamic tools. When a dynamic tool is invoked during a turn, the server sends an `item/tool/call` JSON-RPC request to the client: diff --git a/codex-rs/app-server/src/request_processors/thread_processor.rs b/codex-rs/app-server/src/request_processors/thread_processor.rs index acaf4ddb6f..e9d80089e9 100644 --- a/codex-rs/app-server/src/request_processors/thread_processor.rs +++ b/codex-rs/app-server/src/request_processors/thread_processor.rs @@ -2131,11 +2131,15 @@ impl ThreadRequestProcessor { }; let active_turn = if loaded_thread.is_some() { // Persisted history may not yet include the currently running turn. The - // app-server listener has already projected live turn events into ThreadState, - // so merge that in-memory snapshot before paginating. + // app-server listener has already projected live turn events into + // ThreadState, so merge that in-memory snapshot before paginating. A + // failed turn may also complete before its error event is persisted, so + // bridge the latest terminal failed snapshot too. let thread_state = self.thread_state_manager.thread_state(thread_uuid).await; let state = thread_state.lock().await; - state.active_turn_snapshot() + state + .active_turn_snapshot() + .or_else(|| state.last_terminal_turn_snapshot()) } else { None }; diff --git a/codex-rs/app-server/src/thread_state.rs b/codex-rs/app-server/src/thread_state.rs index ceb0876dc3..c413fe4291 100644 --- a/codex-rs/app-server/src/thread_state.rs +++ b/codex-rs/app-server/src/thread_state.rs @@ -136,7 +136,12 @@ impl ThreadState { } pub(crate) fn active_turn_snapshot(&self) -> Option { - self.current_turn_history.active_turn_snapshot() + self.current_turn_history + .active_turn_snapshot() + .map(|mut turn| { + Self::overlay_last_error(&mut turn, self.turn_summary.last_error.clone()); + turn + }) } pub(crate) fn last_terminal_turn_snapshot(&self) -> Option { @@ -146,6 +151,7 @@ impl ThreadState { pub(crate) fn track_current_turn_event(&mut self, event_turn_id: &str, event: &EventMsg) { if let EventMsg::TurnStarted(payload) = event { self.turn_summary.started_at = payload.started_at; + self.turn_summary.last_error = None; self.last_terminal_turn_id = None; self.last_terminal_turn = None; } @@ -155,11 +161,8 @@ impl ThreadState { { self.last_terminal_turn_id = Some(event_turn_id.to_string()); let mut terminal_turn = self.current_turn_history.active_turn_snapshot(); - if let Some(turn) = terminal_turn.as_mut() - && let Some(error) = self.turn_summary.last_error.clone() - { - turn.status = TurnStatus::Failed; - turn.error = Some(error); + if let Some(turn) = terminal_turn.as_mut() { + Self::overlay_last_error(turn, self.turn_summary.last_error.clone()); } self.last_terminal_turn = terminal_turn .filter(|turn| turn.error.is_some() || matches!(turn.status, TurnStatus::Failed)); @@ -167,6 +170,13 @@ impl ThreadState { } } + fn overlay_last_error(turn: &mut Turn, error: Option) { + if let Some(error) = error { + turn.status = TurnStatus::Failed; + turn.error = Some(error); + } + } + pub(crate) fn note_thread_settings(&mut self, thread_settings: ThreadSettings) -> bool { let changed = self.last_thread_settings.as_ref() != Some(&thread_settings); self.last_thread_settings = Some(thread_settings); @@ -211,12 +221,51 @@ mod tests { use super::*; use codex_app_server_protocol::ApprovalsReviewer; use codex_app_server_protocol::AskForApproval; + use codex_app_server_protocol::CodexErrorInfo; use codex_app_server_protocol::SandboxPolicy; + use codex_app_server_protocol::TurnStatus; use codex_protocol::config_types::CollaborationMode; use codex_protocol::config_types::ModeKind; use codex_protocol::config_types::Settings; + use codex_protocol::protocol::CodexErrorInfo as CoreCodexErrorInfo; + use codex_protocol::protocol::ErrorEvent; + use codex_protocol::protocol::TurnStartedEvent; use pretty_assertions::assert_eq; + #[test] + fn active_turn_snapshot_overlays_enriched_last_error() { + let mut state = ThreadState::default(); + state.track_current_turn_event( + "turn-1", + &EventMsg::TurnStarted(TurnStartedEvent { + turn_id: "turn-1".to_string(), + started_at: None, + model_context_window: None, + collaboration_mode_kind: Default::default(), + }), + ); + state.track_current_turn_event( + "turn-1", + &EventMsg::Error(ErrorEvent { + message: "raw error".to_string(), + codex_error_info: Some(CoreCodexErrorInfo::BadRequest), + }), + ); + state.turn_summary.last_error = Some(TurnError { + message: "enriched error".to_string(), + codex_error_info: Some(CodexErrorInfo::BadRequest), + additional_details: None, + data: None, + }); + + let turn = state + .active_turn_snapshot() + .expect("active turn snapshot should exist"); + + assert_eq!(turn.status, TurnStatus::Failed); + assert_eq!(turn.error, state.turn_summary.last_error); + } + #[test] fn note_thread_settings_reports_only_effective_changes() { let mut state = ThreadState::default(); diff --git a/codex-rs/app-server/tests/suite/v2/dynamic_tools.rs b/codex-rs/app-server/tests/suite/v2/dynamic_tools.rs index 51a706fcfc..8fcf3dba25 100644 --- a/codex-rs/app-server/tests/suite/v2/dynamic_tools.rs +++ b/codex-rs/app-server/tests/suite/v2/dynamic_tools.rs @@ -8,6 +8,7 @@ use codex_app_server_protocol::DynamicToolCallOutputContentItem; use codex_app_server_protocol::DynamicToolCallParams; use codex_app_server_protocol::DynamicToolCallResponse; use codex_app_server_protocol::DynamicToolCallStatus; +use codex_app_server_protocol::DynamicToolSchemaErrorTool; use codex_app_server_protocol::DynamicToolSpec; use codex_app_server_protocol::ErrorNotification; use codex_app_server_protocol::ItemCompletedNotification; @@ -21,6 +22,7 @@ use codex_app_server_protocol::ThreadReadParams; use codex_app_server_protocol::ThreadReadResponse; use codex_app_server_protocol::ThreadStartParams; use codex_app_server_protocol::ThreadStartResponse; +use codex_app_server_protocol::TurnErrorData; use codex_app_server_protocol::TurnStartParams; use codex_app_server_protocol::TurnStartResponse; use codex_app_server_protocol::TurnStatus; @@ -418,20 +420,20 @@ async fn backend_dynamic_tool_schema_error_is_reported_to_clients() -> Result<() Ok(()) } -fn expected_dynamic_tool_schema_error_data(underlying_error: &str) -> Value { - json!({ - "reason": "dynamicToolInputSchema", - "backend": "Responses API", - "tool": { - "index": 0, - "namespace": "repro", - "name": "create", - "qualifiedName": "repro.create" - }, - "schemaPath": "dynamicTools[0].inputSchema.anyOf", - "underlyingError": underlying_error, - "remediation": "Adjust the dynamic tool inputSchema to the JSON Schema subset accepted by the model backend. For discriminated unions, wrap the union in a top-level object property or flatten it into one object schema." - }) +fn expected_dynamic_tool_schema_error_data(underlying_error: &str) -> TurnErrorData { + TurnErrorData::DynamicToolInputSchema { + backend: "Responses API".to_string(), + tool: Some(DynamicToolSchemaErrorTool { + index: 0, + namespace: Some("repro".to_string()), + name: "create".to_string(), + qualified_name: "repro.create".to_string(), + }), + tool_candidates: Vec::new(), + schema_path: Some("dynamicTools[0].inputSchema.anyOf".to_string()), + underlying_error: underlying_error.to_string(), + remediation: "Adjust the dynamic tool inputSchema to the JSON Schema subset accepted by the model backend. For discriminated unions, wrap the union in a top-level object property or flatten it into one object schema.".to_string(), + } } /// Exercises the full dynamic tool call path (server request, client response, model output). diff --git a/codex-rs/exec/src/event_processor_with_jsonl_output_tests.rs b/codex-rs/exec/src/event_processor_with_jsonl_output_tests.rs index b8b0267fc4..adc6221547 100644 --- a/codex-rs/exec/src/event_processor_with_jsonl_output_tests.rs +++ b/codex-rs/exec/src/event_processor_with_jsonl_output_tests.rs @@ -40,6 +40,7 @@ fn failed_turn_does_not_overwrite_output_last_message_file() { message: "turn failed".to_string(), additional_details: None, codex_error_info: None, + data: None, }), started_at: None, completed_at: Some(0), diff --git a/codex-rs/exec/tests/event_processor_with_json_output.rs b/codex-rs/exec/tests/event_processor_with_json_output.rs index ff97b6832c..3aa1692672 100644 --- a/codex-rs/exec/tests/event_processor_with_json_output.rs +++ b/codex-rs/exec/tests/event_processor_with_json_output.rs @@ -1530,6 +1530,7 @@ fn failed_turn_clears_stale_final_message() { message: "turn failed".to_string(), additional_details: None, codex_error_info: None, + data: None, }), started_at: None, completed_at: None, @@ -1586,6 +1587,7 @@ fn turn_failure_prefers_structured_error_message() { message: "backend failed".to_string(), codex_error_info: None, additional_details: Some("request id abc".to_string()), + data: None, }, will_retry: false, thread_id: "thread-1".to_string(), diff --git a/codex-rs/tui/src/app/tests.rs b/codex-rs/tui/src/app/tests.rs index c0050db285..2da76f6ae5 100644 --- a/codex-rs/tui/src/app/tests.rs +++ b/codex-rs/tui/src/app/tests.rs @@ -4557,6 +4557,7 @@ fn active_turn_not_steerable_turn_error_extracts_structured_server_error() { turn_kind: AppServerNonSteerableTurnKind::Review, }), additional_details: None, + data: None, }; let error = TypedRequestError::Server { method: "turn/steer".to_string(), diff --git a/codex-rs/tui/src/chatwidget/tests/app_server.rs b/codex-rs/tui/src/chatwidget/tests/app_server.rs index c8d45d2a59..138e549de8 100644 --- a/codex-rs/tui/src/chatwidget/tests/app_server.rs +++ b/codex-rs/tui/src/chatwidget/tests/app_server.rs @@ -751,6 +751,7 @@ async fn live_app_server_failed_turn_does_not_duplicate_error_history() { message: "permission denied".to_string(), codex_error_info: None, additional_details: None, + data: None, }, will_retry: false, thread_id: "thread-1".to_string(), @@ -775,6 +776,7 @@ async fn live_app_server_failed_turn_does_not_duplicate_error_history() { message: "permission denied".to_string(), codex_error_info: None, additional_details: None, + data: None, }), started_at: None, completed_at: Some(0), @@ -850,6 +852,7 @@ async fn live_app_server_stream_recovery_restores_previous_status_header() { message: "Reconnecting... 1/5".to_string(), codex_error_info: Some(CodexErrorInfo::Other), additional_details: None, + data: None, }, will_retry: true, thread_id: "thread-1".to_string(), @@ -908,6 +911,7 @@ async fn live_app_server_server_overloaded_error_renders_warning() { message: "server overloaded".to_string(), codex_error_info: Some(CodexErrorInfo::ServerOverloaded), additional_details: None, + data: None, }, will_retry: false, thread_id: "thread-1".to_string(), @@ -950,6 +954,7 @@ async fn live_app_server_cyber_policy_error_renders_dedicated_notice() { message: "server fallback message".to_string(), codex_error_info: Some(CodexErrorInfo::CyberPolicy), additional_details: None, + data: None, }, will_retry: false, thread_id: "thread-1".to_string(), diff --git a/codex-rs/tui/src/chatwidget/tests/helpers.rs b/codex-rs/tui/src/chatwidget/tests/helpers.rs index 63487f18b9..d592537667 100644 --- a/codex-rs/tui/src/chatwidget/tests/helpers.rs +++ b/codex-rs/tui/src/chatwidget/tests/helpers.rs @@ -413,6 +413,7 @@ pub(super) fn handle_error( message: message.into(), codex_error_info, additional_details: None, + data: None, }, will_retry: false, thread_id: thread_id(chat), @@ -446,6 +447,7 @@ pub(super) fn handle_stream_error_with_replay( message: message.into(), codex_error_info: None, additional_details, + data: None, }, will_retry: true, thread_id: thread_id(chat), diff --git a/codex-rs/tui/src/chatwidget/tests/history_replay.rs b/codex-rs/tui/src/chatwidget/tests/history_replay.rs index 58f99231f3..fde8d8b8fa 100644 --- a/codex-rs/tui/src/chatwidget/tests/history_replay.rs +++ b/codex-rs/tui/src/chatwidget/tests/history_replay.rs @@ -719,6 +719,7 @@ async fn replayed_retryable_app_server_error_keeps_turn_running() { message: "Reconnecting... 1/5".to_string(), codex_error_info: None, additional_details: Some("Idle timeout waiting for SSE".to_string()), + data: None, }, will_retry: true, thread_id: "thread-1".to_string(),