diff --git a/codex-rs/app-server-protocol/schema/json/ClientRequest.json b/codex-rs/app-server-protocol/schema/json/ClientRequest.json index f90aee1853..a605ae5069 100644 --- a/codex-rs/app-server-protocol/schema/json/ClientRequest.json +++ b/codex-rs/app-server-protocol/schema/json/ClientRequest.json @@ -3322,6 +3322,34 @@ ], "type": "object" }, + "ThreadArtifactCreateParams": { + "properties": { + "artifact": { + "$ref": "#/definitions/ThreadArtifactWriteParams" + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "artifact", + "threadId" + ], + "type": "object" + }, + "ThreadArtifactWriteParams": { + "properties": { + "artifactType": { + "type": "string" + }, + "payload": true + }, + "required": [ + "artifactType", + "payload" + ], + "type": "object" + }, "ThreadCompactStartParams": { "properties": { "threadId": { @@ -4397,79 +4425,6 @@ "mode" ], "type": "object" - }, - "ThreadArtifactCreateParams": { - "properties": { - "artifact": { - "$ref": "#/definitions/ThreadArtifactWriteParams" - }, - "threadId": { - "type": "string" - } - }, - "required": [ - "artifact", - "threadId" - ], - "type": "object" - }, - "ThreadArtifactDeleteParams": { - "properties": { - "artifactId": { - "type": "string" - }, - "threadId": { - "type": "string" - } - }, - "required": [ - "artifactId", - "threadId" - ], - "type": "object" - }, - "ThreadArtifactListParams": { - "properties": { - "threadId": { - "type": "string" - } - }, - "required": [ - "threadId" - ], - "type": "object" - }, - "ThreadArtifactUpdateParams": { - "properties": { - "artifact": { - "$ref": "#/definitions/ThreadArtifactWriteParams" - }, - "artifactId": { - "type": "string" - }, - "threadId": { - "type": "string" - } - }, - "required": [ - "artifact", - "artifactId", - "threadId" - ], - "type": "object" - }, - "ThreadArtifactWriteParams": { - "properties": { - "artifactType": { - "type": "string" - }, - "payload": true - }, - "required": [ - "artifactType", - "payload" - ], - "type": "object" } }, "description": "Request from the client to the server.", @@ -4667,6 +4622,30 @@ "title": "Thread/metadata/updateRequest", "type": "object" }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "thread/artifact/create" + ], + "title": "Thread/artifact/createRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadArtifactCreateParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Thread/artifact/createRequest", + "type": "object" + }, { "properties": { "id": { @@ -6274,103 +6253,7 @@ ], "title": "FuzzyFileSearchRequest", "type": "object" - }, - { - "properties": { - "id": { - "$ref": "#/definitions/RequestId" - }, - "method": { - "enum": [ - "thread/artifact/list" - ], - "title": "Thread/artifact/listRequestMethod", - "type": "string" - }, - "params": { - "$ref": "#/definitions/ThreadArtifactListParams" - } - }, - "required": [ - "id", - "method", - "params" - ], - "title": "Thread/artifact/listRequest", - "type": "object" - }, - { - "properties": { - "id": { - "$ref": "#/definitions/RequestId" - }, - "method": { - "enum": [ - "thread/artifact/create" - ], - "title": "Thread/artifact/createRequestMethod", - "type": "string" - }, - "params": { - "$ref": "#/definitions/ThreadArtifactCreateParams" - } - }, - "required": [ - "id", - "method", - "params" - ], - "title": "Thread/artifact/createRequest", - "type": "object" - }, - { - "properties": { - "id": { - "$ref": "#/definitions/RequestId" - }, - "method": { - "enum": [ - "thread/artifact/update" - ], - "title": "Thread/artifact/updateRequestMethod", - "type": "string" - }, - "params": { - "$ref": "#/definitions/ThreadArtifactUpdateParams" - } - }, - "required": [ - "id", - "method", - "params" - ], - "title": "Thread/artifact/updateRequest", - "type": "object" - }, - { - "properties": { - "id": { - "$ref": "#/definitions/RequestId" - }, - "method": { - "enum": [ - "thread/artifact/delete" - ], - "title": "Thread/artifact/deleteRequestMethod", - "type": "string" - }, - "params": { - "$ref": "#/definitions/ThreadArtifactDeleteParams" - } - }, - "required": [ - "id", - "method", - "params" - ], - "title": "Thread/artifact/deleteRequest", - "type": "object" } ], "title": "ClientRequest" -} +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/ServerNotification.json b/codex-rs/app-server-protocol/schema/json/ServerNotification.json index 43add3bb30..d4456b3a07 100644 --- a/codex-rs/app-server-protocol/schema/json/ServerNotification.json +++ b/codex-rs/app-server-protocol/schema/json/ServerNotification.json @@ -3032,6 +3032,14 @@ "null" ] }, + "artifacts": { + "default": [], + "description": "Model-managed artifacts associated with the thread.", + "items": { + "$ref": "#/definitions/ThreadArtifact" + }, + "type": "array" + }, "cliVersion": { "description": "Version of the CLI that created the thread.", "type": "string" @@ -3138,16 +3146,6 @@ "description": "Unix timestamp (in seconds) when the thread was last updated.", "format": "int64", "type": "integer" - }, - "artifacts": { - "description": "Model-managed artifacts associated with the thread.", - "items": { - "$ref": "#/definitions/ThreadArtifact" - }, - "type": [ - "array", - "null" - ] } }, "required": [ @@ -3184,6 +3182,46 @@ ], "type": "object" }, + "ThreadArtifact": { + "properties": { + "artifactType": { + "type": "string" + }, + "createdAt": { + "format": "int64", + "type": "integer" + }, + "id": { + "type": "string" + }, + "payload": true + }, + "required": [ + "artifactType", + "createdAt", + "id", + "payload" + ], + "type": "object" + }, + "ThreadArtifactsUpdatedNotification": { + "properties": { + "artifacts": { + "items": { + "$ref": "#/definitions/ThreadArtifact" + }, + "type": "array" + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "artifacts", + "threadId" + ], + "type": "object" + }, "ThreadClosedNotification": { "properties": { "threadId": { @@ -4858,52 +4896,6 @@ "samplePaths" ], "type": "object" - }, - "ThreadArtifact": { - "properties": { - "artifactType": { - "type": "string" - }, - "createdAt": { - "format": "int64", - "type": "integer" - }, - "id": { - "type": "string" - }, - "payload": true - }, - "required": [ - "artifactType", - "createdAt", - "id", - "payload" - ], - "type": "object" - }, - "ThreadArtifactsUpdatedNotification": { - "properties": { - "artifacts": { - "items": { - "$ref": "#/definitions/ThreadArtifact" - }, - "type": "array" - }, - "threadId": { - "type": "string" - }, - "turnId": { - "type": [ - "string", - "null" - ] - } - }, - "required": [ - "artifacts", - "threadId" - ], - "type": "object" } }, "description": "Notification sent from the server to the client.", @@ -5089,6 +5081,26 @@ "title": "Thread/goal/updatedNotification", "type": "object" }, + { + "properties": { + "method": { + "enum": [ + "thread/artifacts/updated" + ], + "title": "Thread/artifacts/updatedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadArtifactsUpdatedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Thread/artifacts/updatedNotification", + "type": "object" + }, { "properties": { "method": { @@ -6175,27 +6187,7 @@ ], "title": "Account/login/completedNotification", "type": "object" - }, - { - "properties": { - "method": { - "enum": [ - "thread/artifacts/updated" - ], - "title": "Thread/artifacts/updatedNotificationMethod", - "type": "string" - }, - "params": { - "$ref": "#/definitions/ThreadArtifactsUpdatedNotification" - } - }, - "required": [ - "method", - "params" - ], - "title": "Thread/artifacts/updatedNotification", - "type": "object" } ], "title": "ServerNotification" -} +} \ No newline at end of file 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 c069ca7ac3..87ee290ba4 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 @@ -396,6 +396,30 @@ "title": "Thread/metadata/updateRequest", "type": "object" }, + { + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "enum": [ + "thread/artifact/create" + ], + "title": "Thread/artifact/createRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/ThreadArtifactCreateParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Thread/artifact/createRequest", + "type": "object" + }, { "properties": { "id": { @@ -2003,102 +2027,6 @@ ], "title": "FuzzyFileSearchRequest", "type": "object" - }, - { - "properties": { - "id": { - "$ref": "#/definitions/v2/RequestId" - }, - "method": { - "enum": [ - "thread/artifact/list" - ], - "title": "Thread/artifact/listRequestMethod", - "type": "string" - }, - "params": { - "$ref": "#/definitions/v2/ThreadArtifactListParams" - } - }, - "required": [ - "id", - "method", - "params" - ], - "title": "Thread/artifact/listRequest", - "type": "object" - }, - { - "properties": { - "id": { - "$ref": "#/definitions/v2/RequestId" - }, - "method": { - "enum": [ - "thread/artifact/create" - ], - "title": "Thread/artifact/createRequestMethod", - "type": "string" - }, - "params": { - "$ref": "#/definitions/v2/ThreadArtifactCreateParams" - } - }, - "required": [ - "id", - "method", - "params" - ], - "title": "Thread/artifact/createRequest", - "type": "object" - }, - { - "properties": { - "id": { - "$ref": "#/definitions/v2/RequestId" - }, - "method": { - "enum": [ - "thread/artifact/update" - ], - "title": "Thread/artifact/updateRequestMethod", - "type": "string" - }, - "params": { - "$ref": "#/definitions/v2/ThreadArtifactUpdateParams" - } - }, - "required": [ - "id", - "method", - "params" - ], - "title": "Thread/artifact/updateRequest", - "type": "object" - }, - { - "properties": { - "id": { - "$ref": "#/definitions/v2/RequestId" - }, - "method": { - "enum": [ - "thread/artifact/delete" - ], - "title": "Thread/artifact/deleteRequestMethod", - "type": "string" - }, - "params": { - "$ref": "#/definitions/v2/ThreadArtifactDeleteParams" - } - }, - "required": [ - "id", - "method", - "params" - ], - "title": "Thread/artifact/deleteRequest", - "type": "object" } ], "title": "ClientRequest" @@ -4059,6 +3987,26 @@ "title": "Thread/goal/updatedNotification", "type": "object" }, + { + "properties": { + "method": { + "enum": [ + "thread/artifacts/updated" + ], + "title": "Thread/artifacts/updatedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/ThreadArtifactsUpdatedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Thread/artifacts/updatedNotification", + "type": "object" + }, { "properties": { "method": { @@ -5145,26 +5093,6 @@ ], "title": "Account/login/completedNotification", "type": "object" - }, - { - "properties": { - "method": { - "enum": [ - "thread/artifacts/updated" - ], - "title": "Thread/artifacts/updatedNotificationMethod", - "type": "string" - }, - "params": { - "$ref": "#/definitions/v2/ThreadArtifactsUpdatedNotification" - } - }, - "required": [ - "method", - "params" - ], - "title": "Thread/artifacts/updatedNotification", - "type": "object" } ], "title": "ServerNotification" @@ -15211,6 +15139,14 @@ "null" ] }, + "artifacts": { + "default": [], + "description": "Model-managed artifacts associated with the thread.", + "items": { + "$ref": "#/definitions/v2/ThreadArtifact" + }, + "type": "array" + }, "cliVersion": { "description": "Version of the CLI that created the thread.", "type": "string" @@ -15317,16 +15253,6 @@ "description": "Unix timestamp (in seconds) when the thread was last updated.", "format": "int64", "type": "integer" - }, - "artifacts": { - "description": "Model-managed artifacts associated with the thread.", - "items": { - "$ref": "#/definitions/v2/ThreadArtifact" - }, - "type": [ - "array", - "null" - ] } }, "required": [ @@ -15405,6 +15331,91 @@ "title": "ThreadArchivedNotification", "type": "object" }, + "ThreadArtifact": { + "properties": { + "artifactType": { + "type": "string" + }, + "createdAt": { + "format": "int64", + "type": "integer" + }, + "id": { + "type": "string" + }, + "payload": true + }, + "required": [ + "artifactType", + "createdAt", + "id", + "payload" + ], + "type": "object" + }, + "ThreadArtifactCreateParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "artifact": { + "$ref": "#/definitions/v2/ThreadArtifactWriteParams" + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "artifact", + "threadId" + ], + "title": "ThreadArtifactCreateParams", + "type": "object" + }, + "ThreadArtifactCreateResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "artifact": { + "$ref": "#/definitions/v2/ThreadArtifact" + } + }, + "required": [ + "artifact" + ], + "title": "ThreadArtifactCreateResponse", + "type": "object" + }, + "ThreadArtifactWriteParams": { + "properties": { + "artifactType": { + "type": "string" + }, + "payload": true + }, + "required": [ + "artifactType", + "payload" + ], + "type": "object" + }, + "ThreadArtifactsUpdatedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "artifacts": { + "items": { + "$ref": "#/definitions/v2/ThreadArtifact" + }, + "type": "array" + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "artifacts", + "threadId" + ], + "title": "ThreadArtifactsUpdatedNotification", + "type": "object" + }, "ThreadClosedNotification": { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { @@ -18614,185 +18625,9 @@ "okOverridden" ], "type": "string" - }, - "ThreadArtifact": { - "properties": { - "artifactType": { - "type": "string" - }, - "createdAt": { - "format": "int64", - "type": "integer" - }, - "id": { - "type": "string" - }, - "payload": true - }, - "required": [ - "artifactType", - "createdAt", - "id", - "payload" - ], - "type": "object" - }, - "ThreadArtifactCreateParams": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "artifact": { - "$ref": "#/definitions/v2/ThreadArtifactWriteParams" - }, - "threadId": { - "type": "string" - } - }, - "required": [ - "artifact", - "threadId" - ], - "title": "ThreadArtifactCreateParams", - "type": "object" - }, - "ThreadArtifactCreateResponse": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "artifact": { - "$ref": "#/definitions/v2/ThreadArtifact" - } - }, - "required": [ - "artifact" - ], - "title": "ThreadArtifactCreateResponse", - "type": "object" - }, - "ThreadArtifactDeleteParams": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "artifactId": { - "type": "string" - }, - "threadId": { - "type": "string" - } - }, - "required": [ - "artifactId", - "threadId" - ], - "title": "ThreadArtifactDeleteParams", - "type": "object" - }, - "ThreadArtifactDeleteResponse": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ThreadArtifactDeleteResponse", - "type": "object" - }, - "ThreadArtifactListParams": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "threadId": { - "type": "string" - } - }, - "required": [ - "threadId" - ], - "title": "ThreadArtifactListParams", - "type": "object" - }, - "ThreadArtifactListResponse": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "artifacts": { - "items": { - "$ref": "#/definitions/v2/ThreadArtifact" - }, - "type": "array" - } - }, - "required": [ - "artifacts" - ], - "title": "ThreadArtifactListResponse", - "type": "object" - }, - "ThreadArtifactUpdateParams": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "artifact": { - "$ref": "#/definitions/v2/ThreadArtifactWriteParams" - }, - "artifactId": { - "type": "string" - }, - "threadId": { - "type": "string" - } - }, - "required": [ - "artifact", - "artifactId", - "threadId" - ], - "title": "ThreadArtifactUpdateParams", - "type": "object" - }, - "ThreadArtifactUpdateResponse": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "artifact": { - "$ref": "#/definitions/v2/ThreadArtifact" - } - }, - "required": [ - "artifact" - ], - "title": "ThreadArtifactUpdateResponse", - "type": "object" - }, - "ThreadArtifactWriteParams": { - "properties": { - "artifactType": { - "type": "string" - }, - "payload": true - }, - "required": [ - "artifactType", - "payload" - ], - "type": "object" - }, - "ThreadArtifactsUpdatedNotification": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "artifacts": { - "items": { - "$ref": "#/definitions/v2/ThreadArtifact" - }, - "type": "array" - }, - "threadId": { - "type": "string" - }, - "turnId": { - "type": [ - "string", - "null" - ] - } - }, - "required": [ - "artifacts", - "threadId" - ], - "title": "ThreadArtifactsUpdatedNotification", - "type": "object" } } }, "title": "CodexAppServerProtocol", "type": "object" -} +} \ No newline at end of file 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 a45d74d291..29ae667714 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 @@ -1136,6 +1136,30 @@ "title": "Thread/metadata/updateRequest", "type": "object" }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "thread/artifact/create" + ], + "title": "Thread/artifact/createRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadArtifactCreateParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Thread/artifact/createRequest", + "type": "object" + }, { "properties": { "id": { @@ -2743,102 +2767,6 @@ ], "title": "FuzzyFileSearchRequest", "type": "object" - }, - { - "properties": { - "id": { - "$ref": "#/definitions/RequestId" - }, - "method": { - "enum": [ - "thread/artifact/list" - ], - "title": "Thread/artifact/listRequestMethod", - "type": "string" - }, - "params": { - "$ref": "#/definitions/ThreadArtifactListParams" - } - }, - "required": [ - "id", - "method", - "params" - ], - "title": "Thread/artifact/listRequest", - "type": "object" - }, - { - "properties": { - "id": { - "$ref": "#/definitions/RequestId" - }, - "method": { - "enum": [ - "thread/artifact/create" - ], - "title": "Thread/artifact/createRequestMethod", - "type": "string" - }, - "params": { - "$ref": "#/definitions/ThreadArtifactCreateParams" - } - }, - "required": [ - "id", - "method", - "params" - ], - "title": "Thread/artifact/createRequest", - "type": "object" - }, - { - "properties": { - "id": { - "$ref": "#/definitions/RequestId" - }, - "method": { - "enum": [ - "thread/artifact/update" - ], - "title": "Thread/artifact/updateRequestMethod", - "type": "string" - }, - "params": { - "$ref": "#/definitions/ThreadArtifactUpdateParams" - } - }, - "required": [ - "id", - "method", - "params" - ], - "title": "Thread/artifact/updateRequest", - "type": "object" - }, - { - "properties": { - "id": { - "$ref": "#/definitions/RequestId" - }, - "method": { - "enum": [ - "thread/artifact/delete" - ], - "title": "Thread/artifact/deleteRequestMethod", - "type": "string" - }, - "params": { - "$ref": "#/definitions/ThreadArtifactDeleteParams" - } - }, - "required": [ - "id", - "method", - "params" - ], - "title": "Thread/artifact/deleteRequest", - "type": "object" } ], "title": "ClientRequest" @@ -11287,6 +11215,26 @@ "title": "Thread/goal/updatedNotification", "type": "object" }, + { + "properties": { + "method": { + "enum": [ + "thread/artifacts/updated" + ], + "title": "Thread/artifacts/updatedNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadArtifactsUpdatedNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "Thread/artifacts/updatedNotification", + "type": "object" + }, { "properties": { "method": { @@ -12373,26 +12321,6 @@ ], "title": "Account/login/completedNotification", "type": "object" - }, - { - "properties": { - "method": { - "enum": [ - "thread/artifacts/updated" - ], - "title": "Thread/artifacts/updatedNotificationMethod", - "type": "string" - }, - "params": { - "$ref": "#/definitions/ThreadArtifactsUpdatedNotification" - } - }, - "required": [ - "method", - "params" - ], - "title": "Thread/artifacts/updatedNotification", - "type": "object" } ], "title": "ServerNotification" @@ -13035,6 +12963,14 @@ "null" ] }, + "artifacts": { + "default": [], + "description": "Model-managed artifacts associated with the thread.", + "items": { + "$ref": "#/definitions/ThreadArtifact" + }, + "type": "array" + }, "cliVersion": { "description": "Version of the CLI that created the thread.", "type": "string" @@ -13141,16 +13077,6 @@ "description": "Unix timestamp (in seconds) when the thread was last updated.", "format": "int64", "type": "integer" - }, - "artifacts": { - "description": "Model-managed artifacts associated with the thread.", - "items": { - "$ref": "#/definitions/ThreadArtifact" - }, - "type": [ - "array", - "null" - ] } }, "required": [ @@ -13229,6 +13155,91 @@ "title": "ThreadArchivedNotification", "type": "object" }, + "ThreadArtifact": { + "properties": { + "artifactType": { + "type": "string" + }, + "createdAt": { + "format": "int64", + "type": "integer" + }, + "id": { + "type": "string" + }, + "payload": true + }, + "required": [ + "artifactType", + "createdAt", + "id", + "payload" + ], + "type": "object" + }, + "ThreadArtifactCreateParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "artifact": { + "$ref": "#/definitions/ThreadArtifactWriteParams" + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "artifact", + "threadId" + ], + "title": "ThreadArtifactCreateParams", + "type": "object" + }, + "ThreadArtifactCreateResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "artifact": { + "$ref": "#/definitions/ThreadArtifact" + } + }, + "required": [ + "artifact" + ], + "title": "ThreadArtifactCreateResponse", + "type": "object" + }, + "ThreadArtifactWriteParams": { + "properties": { + "artifactType": { + "type": "string" + }, + "payload": true + }, + "required": [ + "artifactType", + "payload" + ], + "type": "object" + }, + "ThreadArtifactsUpdatedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "artifacts": { + "items": { + "$ref": "#/definitions/ThreadArtifact" + }, + "type": "array" + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "artifacts", + "threadId" + ], + "title": "ThreadArtifactsUpdatedNotification", + "type": "object" + }, "ThreadClosedNotification": { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { @@ -16438,184 +16449,8 @@ "okOverridden" ], "type": "string" - }, - "ThreadArtifact": { - "properties": { - "artifactType": { - "type": "string" - }, - "createdAt": { - "format": "int64", - "type": "integer" - }, - "id": { - "type": "string" - }, - "payload": true - }, - "required": [ - "artifactType", - "createdAt", - "id", - "payload" - ], - "type": "object" - }, - "ThreadArtifactCreateParams": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "artifact": { - "$ref": "#/definitions/ThreadArtifactWriteParams" - }, - "threadId": { - "type": "string" - } - }, - "required": [ - "artifact", - "threadId" - ], - "title": "ThreadArtifactCreateParams", - "type": "object" - }, - "ThreadArtifactCreateResponse": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "artifact": { - "$ref": "#/definitions/ThreadArtifact" - } - }, - "required": [ - "artifact" - ], - "title": "ThreadArtifactCreateResponse", - "type": "object" - }, - "ThreadArtifactDeleteParams": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "artifactId": { - "type": "string" - }, - "threadId": { - "type": "string" - } - }, - "required": [ - "artifactId", - "threadId" - ], - "title": "ThreadArtifactDeleteParams", - "type": "object" - }, - "ThreadArtifactDeleteResponse": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ThreadArtifactDeleteResponse", - "type": "object" - }, - "ThreadArtifactListParams": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "threadId": { - "type": "string" - } - }, - "required": [ - "threadId" - ], - "title": "ThreadArtifactListParams", - "type": "object" - }, - "ThreadArtifactListResponse": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "artifacts": { - "items": { - "$ref": "#/definitions/ThreadArtifact" - }, - "type": "array" - } - }, - "required": [ - "artifacts" - ], - "title": "ThreadArtifactListResponse", - "type": "object" - }, - "ThreadArtifactUpdateParams": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "artifact": { - "$ref": "#/definitions/ThreadArtifactWriteParams" - }, - "artifactId": { - "type": "string" - }, - "threadId": { - "type": "string" - } - }, - "required": [ - "artifact", - "artifactId", - "threadId" - ], - "title": "ThreadArtifactUpdateParams", - "type": "object" - }, - "ThreadArtifactUpdateResponse": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "artifact": { - "$ref": "#/definitions/ThreadArtifact" - } - }, - "required": [ - "artifact" - ], - "title": "ThreadArtifactUpdateResponse", - "type": "object" - }, - "ThreadArtifactWriteParams": { - "properties": { - "artifactType": { - "type": "string" - }, - "payload": true - }, - "required": [ - "artifactType", - "payload" - ], - "type": "object" - }, - "ThreadArtifactsUpdatedNotification": { - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "artifacts": { - "items": { - "$ref": "#/definitions/ThreadArtifact" - }, - "type": "array" - }, - "threadId": { - "type": "string" - }, - "turnId": { - "type": [ - "string", - "null" - ] - } - }, - "required": [ - "artifacts", - "threadId" - ], - "title": "ThreadArtifactsUpdatedNotification", - "type": "object" } }, "title": "CodexAppServerProtocolV2", "type": "object" -} +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadArtifactDeleteParams.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadArtifactDeleteParams.json deleted file mode 100644 index cdc698598e..0000000000 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadArtifactDeleteParams.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "artifactId": { - "type": "string" - }, - "threadId": { - "type": "string" - } - }, - "required": [ - "artifactId", - "threadId" - ], - "title": "ThreadArtifactDeleteParams", - "type": "object" -} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadArtifactDeleteResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadArtifactDeleteResponse.json deleted file mode 100644 index 652a8fc215..0000000000 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadArtifactDeleteResponse.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ThreadArtifactDeleteResponse", - "type": "object" -} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadArtifactListParams.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadArtifactListParams.json deleted file mode 100644 index 3e84c2d4fd..0000000000 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadArtifactListParams.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "threadId": { - "type": "string" - } - }, - "required": [ - "threadId" - ], - "title": "ThreadArtifactListParams", - "type": "object" -} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadArtifactListResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadArtifactListResponse.json deleted file mode 100644 index 763a001359..0000000000 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadArtifactListResponse.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "definitions": { - "ThreadArtifact": { - "properties": { - "artifactType": { - "type": "string" - }, - "createdAt": { - "format": "int64", - "type": "integer" - }, - "id": { - "type": "string" - }, - "payload": true - }, - "required": [ - "artifactType", - "createdAt", - "id", - "payload" - ], - "type": "object" - } - }, - "properties": { - "artifacts": { - "items": { - "$ref": "#/definitions/ThreadArtifact" - }, - "type": "array" - } - }, - "required": [ - "artifacts" - ], - "title": "ThreadArtifactListResponse", - "type": "object" -} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadArtifactUpdateParams.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadArtifactUpdateParams.json deleted file mode 100644 index f5f1d89f7e..0000000000 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadArtifactUpdateParams.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "definitions": { - "ThreadArtifactWriteParams": { - "properties": { - "artifactType": { - "type": "string" - }, - "payload": true - }, - "required": [ - "artifactType", - "payload" - ], - "type": "object" - } - }, - "properties": { - "artifact": { - "$ref": "#/definitions/ThreadArtifactWriteParams" - }, - "artifactId": { - "type": "string" - }, - "threadId": { - "type": "string" - } - }, - "required": [ - "artifact", - "artifactId", - "threadId" - ], - "title": "ThreadArtifactUpdateParams", - "type": "object" -} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadArtifactUpdateResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadArtifactUpdateResponse.json deleted file mode 100644 index 0c477ce43c..0000000000 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadArtifactUpdateResponse.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "definitions": { - "ThreadArtifact": { - "properties": { - "artifactType": { - "type": "string" - }, - "createdAt": { - "format": "int64", - "type": "integer" - }, - "id": { - "type": "string" - }, - "payload": true - }, - "required": [ - "artifactType", - "createdAt", - "id", - "payload" - ], - "type": "object" - } - }, - "properties": { - "artifact": { - "$ref": "#/definitions/ThreadArtifact" - } - }, - "required": [ - "artifact" - ], - "title": "ThreadArtifactUpdateResponse", - "type": "object" -} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadArtifactsUpdatedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadArtifactsUpdatedNotification.json index 3eda357616..277b1909de 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadArtifactsUpdatedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadArtifactsUpdatedNotification.json @@ -33,12 +33,6 @@ }, "threadId": { "type": "string" - }, - "turnId": { - "type": [ - "string", - "null" - ] } }, "required": [ 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 269c70b0d3..ce2bbac78b 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json @@ -1332,6 +1332,14 @@ "null" ] }, + "artifacts": { + "default": [], + "description": "Model-managed artifacts associated with the thread.", + "items": { + "$ref": "#/definitions/ThreadArtifact" + }, + "type": "array" + }, "cliVersion": { "description": "Version of the CLI that created the thread.", "type": "string" @@ -1438,16 +1446,6 @@ "description": "Unix timestamp (in seconds) when the thread was last updated.", "format": "int64", "type": "integer" - }, - "artifacts": { - "description": "Model-managed artifacts associated with the thread.", - "items": { - "$ref": "#/definitions/ThreadArtifact" - }, - "type": [ - "array", - "null" - ] } }, "required": [ @@ -1473,6 +1471,28 @@ ], "type": "string" }, + "ThreadArtifact": { + "properties": { + "artifactType": { + "type": "string" + }, + "createdAt": { + "format": "int64", + "type": "integer" + }, + "id": { + "type": "string" + }, + "payload": true + }, + "required": [ + "artifactType", + "createdAt", + "id", + "payload" + ], + "type": "object" + }, "ThreadId": { "type": "string" }, @@ -2568,28 +2588,6 @@ "type": "object" } ] - }, - "ThreadArtifact": { - "properties": { - "artifactType": { - "type": "string" - }, - "createdAt": { - "format": "int64", - "type": "integer" - }, - "id": { - "type": "string" - }, - "payload": true - }, - "required": [ - "artifactType", - "createdAt", - "id", - "payload" - ], - "type": "object" } }, "properties": { @@ -2660,4 +2658,4 @@ ], "title": "ThreadForkResponse", "type": "object" -} +} \ No newline at end of file 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 e08256d0a6..ba9f31126b 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json @@ -789,6 +789,14 @@ "null" ] }, + "artifacts": { + "default": [], + "description": "Model-managed artifacts associated with the thread.", + "items": { + "$ref": "#/definitions/ThreadArtifact" + }, + "type": "array" + }, "cliVersion": { "description": "Version of the CLI that created the thread.", "type": "string" @@ -895,16 +903,6 @@ "description": "Unix timestamp (in seconds) when the thread was last updated.", "format": "int64", "type": "integer" - }, - "artifacts": { - "description": "Model-managed artifacts associated with the thread.", - "items": { - "$ref": "#/definitions/ThreadArtifact" - }, - "type": [ - "array", - "null" - ] } }, "required": [ @@ -930,6 +928,28 @@ ], "type": "string" }, + "ThreadArtifact": { + "properties": { + "artifactType": { + "type": "string" + }, + "createdAt": { + "format": "int64", + "type": "integer" + }, + "id": { + "type": "string" + }, + "payload": true + }, + "required": [ + "artifactType", + "createdAt", + "id", + "payload" + ], + "type": "object" + }, "ThreadId": { "type": "string" }, @@ -2025,28 +2045,6 @@ "type": "object" } ] - }, - "ThreadArtifact": { - "properties": { - "artifactType": { - "type": "string" - }, - "createdAt": { - "format": "int64", - "type": "integer" - }, - "id": { - "type": "string" - }, - "payload": true - }, - "required": [ - "artifactType", - "createdAt", - "id", - "payload" - ], - "type": "object" } }, "properties": { @@ -2076,4 +2074,4 @@ ], "title": "ThreadListResponse", "type": "object" -} +} \ No newline at end of file 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 69a38baf78..4a1f3cb797 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json @@ -789,6 +789,14 @@ "null" ] }, + "artifacts": { + "default": [], + "description": "Model-managed artifacts associated with the thread.", + "items": { + "$ref": "#/definitions/ThreadArtifact" + }, + "type": "array" + }, "cliVersion": { "description": "Version of the CLI that created the thread.", "type": "string" @@ -895,16 +903,6 @@ "description": "Unix timestamp (in seconds) when the thread was last updated.", "format": "int64", "type": "integer" - }, - "artifacts": { - "description": "Model-managed artifacts associated with the thread.", - "items": { - "$ref": "#/definitions/ThreadArtifact" - }, - "type": [ - "array", - "null" - ] } }, "required": [ @@ -930,6 +928,28 @@ ], "type": "string" }, + "ThreadArtifact": { + "properties": { + "artifactType": { + "type": "string" + }, + "createdAt": { + "format": "int64", + "type": "integer" + }, + "id": { + "type": "string" + }, + "payload": true + }, + "required": [ + "artifactType", + "createdAt", + "id", + "payload" + ], + "type": "object" + }, "ThreadId": { "type": "string" }, @@ -2025,28 +2045,6 @@ "type": "object" } ] - }, - "ThreadArtifact": { - "properties": { - "artifactType": { - "type": "string" - }, - "createdAt": { - "format": "int64", - "type": "integer" - }, - "id": { - "type": "string" - }, - "payload": true - }, - "required": [ - "artifactType", - "createdAt", - "id", - "payload" - ], - "type": "object" } }, "properties": { @@ -2059,4 +2057,4 @@ ], "title": "ThreadMetadataUpdateResponse", "type": "object" -} +} \ No newline at end of file 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 3f6cd7754f..bbdbc88429 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json @@ -789,6 +789,14 @@ "null" ] }, + "artifacts": { + "default": [], + "description": "Model-managed artifacts associated with the thread.", + "items": { + "$ref": "#/definitions/ThreadArtifact" + }, + "type": "array" + }, "cliVersion": { "description": "Version of the CLI that created the thread.", "type": "string" @@ -895,16 +903,6 @@ "description": "Unix timestamp (in seconds) when the thread was last updated.", "format": "int64", "type": "integer" - }, - "artifacts": { - "description": "Model-managed artifacts associated with the thread.", - "items": { - "$ref": "#/definitions/ThreadArtifact" - }, - "type": [ - "array", - "null" - ] } }, "required": [ @@ -930,6 +928,28 @@ ], "type": "string" }, + "ThreadArtifact": { + "properties": { + "artifactType": { + "type": "string" + }, + "createdAt": { + "format": "int64", + "type": "integer" + }, + "id": { + "type": "string" + }, + "payload": true + }, + "required": [ + "artifactType", + "createdAt", + "id", + "payload" + ], + "type": "object" + }, "ThreadId": { "type": "string" }, @@ -2025,28 +2045,6 @@ "type": "object" } ] - }, - "ThreadArtifact": { - "properties": { - "artifactType": { - "type": "string" - }, - "createdAt": { - "format": "int64", - "type": "integer" - }, - "id": { - "type": "string" - }, - "payload": true - }, - "required": [ - "artifactType", - "createdAt", - "id", - "payload" - ], - "type": "object" } }, "properties": { @@ -2059,4 +2057,4 @@ ], "title": "ThreadReadResponse", "type": "object" -} +} \ No newline at end of file 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 e80139139d..e977c2a9a6 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json @@ -1332,6 +1332,14 @@ "null" ] }, + "artifacts": { + "default": [], + "description": "Model-managed artifacts associated with the thread.", + "items": { + "$ref": "#/definitions/ThreadArtifact" + }, + "type": "array" + }, "cliVersion": { "description": "Version of the CLI that created the thread.", "type": "string" @@ -1438,16 +1446,6 @@ "description": "Unix timestamp (in seconds) when the thread was last updated.", "format": "int64", "type": "integer" - }, - "artifacts": { - "description": "Model-managed artifacts associated with the thread.", - "items": { - "$ref": "#/definitions/ThreadArtifact" - }, - "type": [ - "array", - "null" - ] } }, "required": [ @@ -1473,6 +1471,28 @@ ], "type": "string" }, + "ThreadArtifact": { + "properties": { + "artifactType": { + "type": "string" + }, + "createdAt": { + "format": "int64", + "type": "integer" + }, + "id": { + "type": "string" + }, + "payload": true + }, + "required": [ + "artifactType", + "createdAt", + "id", + "payload" + ], + "type": "object" + }, "ThreadId": { "type": "string" }, @@ -2568,28 +2588,6 @@ "type": "object" } ] - }, - "ThreadArtifact": { - "properties": { - "artifactType": { - "type": "string" - }, - "createdAt": { - "format": "int64", - "type": "integer" - }, - "id": { - "type": "string" - }, - "payload": true - }, - "required": [ - "artifactType", - "createdAt", - "id", - "payload" - ], - "type": "object" } }, "properties": { @@ -2660,4 +2658,4 @@ ], "title": "ThreadResumeResponse", "type": "object" -} +} \ No newline at end of file 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 e77e4b1513..f172287b46 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json @@ -789,6 +789,14 @@ "null" ] }, + "artifacts": { + "default": [], + "description": "Model-managed artifacts associated with the thread.", + "items": { + "$ref": "#/definitions/ThreadArtifact" + }, + "type": "array" + }, "cliVersion": { "description": "Version of the CLI that created the thread.", "type": "string" @@ -895,16 +903,6 @@ "description": "Unix timestamp (in seconds) when the thread was last updated.", "format": "int64", "type": "integer" - }, - "artifacts": { - "description": "Model-managed artifacts associated with the thread.", - "items": { - "$ref": "#/definitions/ThreadArtifact" - }, - "type": [ - "array", - "null" - ] } }, "required": [ @@ -930,6 +928,28 @@ ], "type": "string" }, + "ThreadArtifact": { + "properties": { + "artifactType": { + "type": "string" + }, + "createdAt": { + "format": "int64", + "type": "integer" + }, + "id": { + "type": "string" + }, + "payload": true + }, + "required": [ + "artifactType", + "createdAt", + "id", + "payload" + ], + "type": "object" + }, "ThreadId": { "type": "string" }, @@ -2025,28 +2045,6 @@ "type": "object" } ] - }, - "ThreadArtifact": { - "properties": { - "artifactType": { - "type": "string" - }, - "createdAt": { - "format": "int64", - "type": "integer" - }, - "id": { - "type": "string" - }, - "payload": true - }, - "required": [ - "artifactType", - "createdAt", - "id", - "payload" - ], - "type": "object" } }, "properties": { @@ -2064,4 +2062,4 @@ ], "title": "ThreadRollbackResponse", "type": "object" -} +} \ No newline at end of file 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 e142d45f3e..b79074cf98 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json @@ -1332,6 +1332,14 @@ "null" ] }, + "artifacts": { + "default": [], + "description": "Model-managed artifacts associated with the thread.", + "items": { + "$ref": "#/definitions/ThreadArtifact" + }, + "type": "array" + }, "cliVersion": { "description": "Version of the CLI that created the thread.", "type": "string" @@ -1438,16 +1446,6 @@ "description": "Unix timestamp (in seconds) when the thread was last updated.", "format": "int64", "type": "integer" - }, - "artifacts": { - "description": "Model-managed artifacts associated with the thread.", - "items": { - "$ref": "#/definitions/ThreadArtifact" - }, - "type": [ - "array", - "null" - ] } }, "required": [ @@ -1473,6 +1471,28 @@ ], "type": "string" }, + "ThreadArtifact": { + "properties": { + "artifactType": { + "type": "string" + }, + "createdAt": { + "format": "int64", + "type": "integer" + }, + "id": { + "type": "string" + }, + "payload": true + }, + "required": [ + "artifactType", + "createdAt", + "id", + "payload" + ], + "type": "object" + }, "ThreadId": { "type": "string" }, @@ -2568,28 +2588,6 @@ "type": "object" } ] - }, - "ThreadArtifact": { - "properties": { - "artifactType": { - "type": "string" - }, - "createdAt": { - "format": "int64", - "type": "integer" - }, - "id": { - "type": "string" - }, - "payload": true - }, - "required": [ - "artifactType", - "createdAt", - "id", - "payload" - ], - "type": "object" } }, "properties": { @@ -2660,4 +2658,4 @@ ], "title": "ThreadStartResponse", "type": "object" -} +} \ No newline at end of file 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 82eb3aa502..30ffa79da2 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json @@ -789,6 +789,14 @@ "null" ] }, + "artifacts": { + "default": [], + "description": "Model-managed artifacts associated with the thread.", + "items": { + "$ref": "#/definitions/ThreadArtifact" + }, + "type": "array" + }, "cliVersion": { "description": "Version of the CLI that created the thread.", "type": "string" @@ -895,16 +903,6 @@ "description": "Unix timestamp (in seconds) when the thread was last updated.", "format": "int64", "type": "integer" - }, - "artifacts": { - "description": "Model-managed artifacts associated with the thread.", - "items": { - "$ref": "#/definitions/ThreadArtifact" - }, - "type": [ - "array", - "null" - ] } }, "required": [ @@ -930,6 +928,28 @@ ], "type": "string" }, + "ThreadArtifact": { + "properties": { + "artifactType": { + "type": "string" + }, + "createdAt": { + "format": "int64", + "type": "integer" + }, + "id": { + "type": "string" + }, + "payload": true + }, + "required": [ + "artifactType", + "createdAt", + "id", + "payload" + ], + "type": "object" + }, "ThreadId": { "type": "string" }, @@ -2025,28 +2045,6 @@ "type": "object" } ] - }, - "ThreadArtifact": { - "properties": { - "artifactType": { - "type": "string" - }, - "createdAt": { - "format": "int64", - "type": "integer" - }, - "id": { - "type": "string" - }, - "payload": true - }, - "required": [ - "artifactType", - "createdAt", - "id", - "payload" - ], - "type": "object" } }, "properties": { @@ -2059,4 +2057,4 @@ ], "title": "ThreadStartedNotification", "type": "object" -} +} \ No newline at end of file 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 66d634e2f5..f446118d93 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json @@ -789,6 +789,14 @@ "null" ] }, + "artifacts": { + "default": [], + "description": "Model-managed artifacts associated with the thread.", + "items": { + "$ref": "#/definitions/ThreadArtifact" + }, + "type": "array" + }, "cliVersion": { "description": "Version of the CLI that created the thread.", "type": "string" @@ -895,16 +903,6 @@ "description": "Unix timestamp (in seconds) when the thread was last updated.", "format": "int64", "type": "integer" - }, - "artifacts": { - "description": "Model-managed artifacts associated with the thread.", - "items": { - "$ref": "#/definitions/ThreadArtifact" - }, - "type": [ - "array", - "null" - ] } }, "required": [ @@ -930,6 +928,28 @@ ], "type": "string" }, + "ThreadArtifact": { + "properties": { + "artifactType": { + "type": "string" + }, + "createdAt": { + "format": "int64", + "type": "integer" + }, + "id": { + "type": "string" + }, + "payload": true + }, + "required": [ + "artifactType", + "createdAt", + "id", + "payload" + ], + "type": "object" + }, "ThreadId": { "type": "string" }, @@ -2025,28 +2045,6 @@ "type": "object" } ] - }, - "ThreadArtifact": { - "properties": { - "artifactType": { - "type": "string" - }, - "createdAt": { - "format": "int64", - "type": "integer" - }, - "id": { - "type": "string" - }, - "payload": true - }, - "required": [ - "artifactType", - "createdAt", - "id", - "payload" - ], - "type": "object" } }, "properties": { @@ -2059,4 +2057,4 @@ ], "title": "ThreadUnarchiveResponse", "type": "object" -} +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts b/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts index c3a679f44d..d318f5902f 100644 --- a/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts +++ b/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts @@ -58,9 +58,6 @@ import type { SkillsListParams } from "./v2/SkillsListParams"; import type { ThreadApproveGuardianDeniedActionParams } from "./v2/ThreadApproveGuardianDeniedActionParams"; import type { ThreadArchiveParams } from "./v2/ThreadArchiveParams"; import type { ThreadArtifactCreateParams } from "./v2/ThreadArtifactCreateParams"; -import type { ThreadArtifactDeleteParams } from "./v2/ThreadArtifactDeleteParams"; -import type { ThreadArtifactListParams } from "./v2/ThreadArtifactListParams"; -import type { ThreadArtifactUpdateParams } from "./v2/ThreadArtifactUpdateParams"; import type { ThreadCompactStartParams } from "./v2/ThreadCompactStartParams"; import type { ThreadForkParams } from "./v2/ThreadForkParams"; import type { ThreadInjectItemsParams } from "./v2/ThreadInjectItemsParams"; @@ -83,4 +80,4 @@ import type { WindowsSandboxSetupStartParams } from "./v2/WindowsSandboxSetupSta /** * Request from the client to the server. */ -export type ClientRequest ={ "method": "initialize", id: RequestId, params: InitializeParams, } | { "method": "thread/start", id: RequestId, params: ThreadStartParams, } | { "method": "thread/resume", id: RequestId, params: ThreadResumeParams, } | { "method": "thread/fork", id: RequestId, params: ThreadForkParams, } | { "method": "thread/archive", id: RequestId, params: ThreadArchiveParams, } | { "method": "thread/unsubscribe", id: RequestId, params: ThreadUnsubscribeParams, } | { "method": "thread/name/set", id: RequestId, params: ThreadSetNameParams, } | { "method": "thread/metadata/update", id: RequestId, params: ThreadMetadataUpdateParams, } | { "method": "thread/artifact/list", id: RequestId, params: ThreadArtifactListParams, } | { "method": "thread/artifact/create", id: RequestId, params: ThreadArtifactCreateParams, } | { "method": "thread/artifact/update", id: RequestId, params: ThreadArtifactUpdateParams, } | { "method": "thread/artifact/delete", id: RequestId, params: ThreadArtifactDeleteParams, } | { "method": "thread/unarchive", id: RequestId, params: ThreadUnarchiveParams, } | { "method": "thread/compact/start", id: RequestId, params: ThreadCompactStartParams, } | { "method": "thread/shellCommand", id: RequestId, params: ThreadShellCommandParams, } | { "method": "thread/approveGuardianDeniedAction", id: RequestId, params: ThreadApproveGuardianDeniedActionParams, } | { "method": "thread/rollback", id: RequestId, params: ThreadRollbackParams, } | { "method": "thread/list", id: RequestId, params: ThreadListParams, } | { "method": "thread/loaded/list", id: RequestId, params: ThreadLoadedListParams, } | { "method": "thread/read", id: RequestId, params: ThreadReadParams, } | { "method": "thread/inject_items", id: RequestId, params: ThreadInjectItemsParams, } | { "method": "skills/list", id: RequestId, params: SkillsListParams, } | { "method": "hooks/list", id: RequestId, params: HooksListParams, } | { "method": "marketplace/add", id: RequestId, params: MarketplaceAddParams, } | { "method": "marketplace/remove", id: RequestId, params: MarketplaceRemoveParams, } | { "method": "marketplace/upgrade", id: RequestId, params: MarketplaceUpgradeParams, } | { "method": "plugin/list", id: RequestId, params: PluginListParams, } | { "method": "plugin/read", id: RequestId, params: PluginReadParams, } | { "method": "plugin/skill/read", id: RequestId, params: PluginSkillReadParams, } | { "method": "plugin/share/save", id: RequestId, params: PluginShareSaveParams, } | { "method": "plugin/share/updateTargets", id: RequestId, params: PluginShareUpdateTargetsParams, } | { "method": "plugin/share/list", id: RequestId, params: PluginShareListParams, } | { "method": "plugin/share/delete", id: RequestId, params: PluginShareDeleteParams, } | { "method": "app/list", id: RequestId, params: AppsListParams, } | { "method": "fs/readFile", id: RequestId, params: FsReadFileParams, } | { "method": "fs/writeFile", id: RequestId, params: FsWriteFileParams, } | { "method": "fs/createDirectory", id: RequestId, params: FsCreateDirectoryParams, } | { "method": "fs/getMetadata", id: RequestId, params: FsGetMetadataParams, } | { "method": "fs/readDirectory", id: RequestId, params: FsReadDirectoryParams, } | { "method": "fs/remove", id: RequestId, params: FsRemoveParams, } | { "method": "fs/copy", id: RequestId, params: FsCopyParams, } | { "method": "fs/watch", id: RequestId, params: FsWatchParams, } | { "method": "fs/unwatch", id: RequestId, params: FsUnwatchParams, } | { "method": "skills/config/write", id: RequestId, params: SkillsConfigWriteParams, } | { "method": "plugin/install", id: RequestId, params: PluginInstallParams, } | { "method": "plugin/uninstall", id: RequestId, params: PluginUninstallParams, } | { "method": "turn/start", id: RequestId, params: TurnStartParams, } | { "method": "turn/steer", id: RequestId, params: TurnSteerParams, } | { "method": "turn/interrupt", id: RequestId, params: TurnInterruptParams, } | { "method": "review/start", id: RequestId, params: ReviewStartParams, } | { "method": "model/list", id: RequestId, params: ModelListParams, } | { "method": "modelProvider/capabilities/read", id: RequestId, params: ModelProviderCapabilitiesReadParams, } | { "method": "experimentalFeature/list", id: RequestId, params: ExperimentalFeatureListParams, } | { "method": "experimentalFeature/enablement/set", id: RequestId, params: ExperimentalFeatureEnablementSetParams, } | { "method": "mcpServer/oauth/login", id: RequestId, params: McpServerOauthLoginParams, } | { "method": "config/mcpServer/reload", id: RequestId, params: undefined, } | { "method": "mcpServerStatus/list", id: RequestId, params: ListMcpServerStatusParams, } | { "method": "mcpServer/resource/read", id: RequestId, params: McpResourceReadParams, } | { "method": "mcpServer/tool/call", id: RequestId, params: McpServerToolCallParams, } | { "method": "windowsSandbox/setupStart", id: RequestId, params: WindowsSandboxSetupStartParams, } | { "method": "windowsSandbox/readiness", id: RequestId, params: undefined, } | { "method": "account/login/start", id: RequestId, params: LoginAccountParams, } | { "method": "account/login/cancel", id: RequestId, params: CancelLoginAccountParams, } | { "method": "account/logout", id: RequestId, params: undefined, } | { "method": "account/rateLimits/read", id: RequestId, params: undefined, } | { "method": "account/sendAddCreditsNudgeEmail", id: RequestId, params: SendAddCreditsNudgeEmailParams, } | { "method": "feedback/upload", id: RequestId, params: FeedbackUploadParams, } | { "method": "command/exec", id: RequestId, params: CommandExecParams, } | { "method": "command/exec/write", id: RequestId, params: CommandExecWriteParams, } | { "method": "command/exec/terminate", id: RequestId, params: CommandExecTerminateParams, } | { "method": "command/exec/resize", id: RequestId, params: CommandExecResizeParams, } | { "method": "config/read", id: RequestId, params: ConfigReadParams, } | { "method": "externalAgentConfig/detect", id: RequestId, params: ExternalAgentConfigDetectParams, } | { "method": "externalAgentConfig/import", id: RequestId, params: ExternalAgentConfigImportParams, } | { "method": "config/value/write", id: RequestId, params: ConfigValueWriteParams, } | { "method": "config/batchWrite", id: RequestId, params: ConfigBatchWriteParams, } | { "method": "configRequirements/read", id: RequestId, params: undefined, } | { "method": "account/read", id: RequestId, params: GetAccountParams, } | { "method": "getConversationSummary", id: RequestId, params: GetConversationSummaryParams, } | { "method": "gitDiffToRemote", id: RequestId, params: GitDiffToRemoteParams, } | { "method": "getAuthStatus", id: RequestId, params: GetAuthStatusParams, } | { "method": "fuzzyFileSearch", id: RequestId, params: FuzzyFileSearchParams, }; +export type ClientRequest ={ "method": "initialize", id: RequestId, params: InitializeParams, } | { "method": "thread/start", id: RequestId, params: ThreadStartParams, } | { "method": "thread/resume", id: RequestId, params: ThreadResumeParams, } | { "method": "thread/fork", id: RequestId, params: ThreadForkParams, } | { "method": "thread/archive", id: RequestId, params: ThreadArchiveParams, } | { "method": "thread/unsubscribe", id: RequestId, params: ThreadUnsubscribeParams, } | { "method": "thread/name/set", id: RequestId, params: ThreadSetNameParams, } | { "method": "thread/metadata/update", id: RequestId, params: ThreadMetadataUpdateParams, } | { "method": "thread/artifact/create", id: RequestId, params: ThreadArtifactCreateParams, } | { "method": "thread/unarchive", id: RequestId, params: ThreadUnarchiveParams, } | { "method": "thread/compact/start", id: RequestId, params: ThreadCompactStartParams, } | { "method": "thread/shellCommand", id: RequestId, params: ThreadShellCommandParams, } | { "method": "thread/approveGuardianDeniedAction", id: RequestId, params: ThreadApproveGuardianDeniedActionParams, } | { "method": "thread/rollback", id: RequestId, params: ThreadRollbackParams, } | { "method": "thread/list", id: RequestId, params: ThreadListParams, } | { "method": "thread/loaded/list", id: RequestId, params: ThreadLoadedListParams, } | { "method": "thread/read", id: RequestId, params: ThreadReadParams, } | { "method": "thread/inject_items", id: RequestId, params: ThreadInjectItemsParams, } | { "method": "skills/list", id: RequestId, params: SkillsListParams, } | { "method": "hooks/list", id: RequestId, params: HooksListParams, } | { "method": "marketplace/add", id: RequestId, params: MarketplaceAddParams, } | { "method": "marketplace/remove", id: RequestId, params: MarketplaceRemoveParams, } | { "method": "marketplace/upgrade", id: RequestId, params: MarketplaceUpgradeParams, } | { "method": "plugin/list", id: RequestId, params: PluginListParams, } | { "method": "plugin/read", id: RequestId, params: PluginReadParams, } | { "method": "plugin/skill/read", id: RequestId, params: PluginSkillReadParams, } | { "method": "plugin/share/save", id: RequestId, params: PluginShareSaveParams, } | { "method": "plugin/share/updateTargets", id: RequestId, params: PluginShareUpdateTargetsParams, } | { "method": "plugin/share/list", id: RequestId, params: PluginShareListParams, } | { "method": "plugin/share/delete", id: RequestId, params: PluginShareDeleteParams, } | { "method": "app/list", id: RequestId, params: AppsListParams, } | { "method": "fs/readFile", id: RequestId, params: FsReadFileParams, } | { "method": "fs/writeFile", id: RequestId, params: FsWriteFileParams, } | { "method": "fs/createDirectory", id: RequestId, params: FsCreateDirectoryParams, } | { "method": "fs/getMetadata", id: RequestId, params: FsGetMetadataParams, } | { "method": "fs/readDirectory", id: RequestId, params: FsReadDirectoryParams, } | { "method": "fs/remove", id: RequestId, params: FsRemoveParams, } | { "method": "fs/copy", id: RequestId, params: FsCopyParams, } | { "method": "fs/watch", id: RequestId, params: FsWatchParams, } | { "method": "fs/unwatch", id: RequestId, params: FsUnwatchParams, } | { "method": "skills/config/write", id: RequestId, params: SkillsConfigWriteParams, } | { "method": "plugin/install", id: RequestId, params: PluginInstallParams, } | { "method": "plugin/uninstall", id: RequestId, params: PluginUninstallParams, } | { "method": "turn/start", id: RequestId, params: TurnStartParams, } | { "method": "turn/steer", id: RequestId, params: TurnSteerParams, } | { "method": "turn/interrupt", id: RequestId, params: TurnInterruptParams, } | { "method": "review/start", id: RequestId, params: ReviewStartParams, } | { "method": "model/list", id: RequestId, params: ModelListParams, } | { "method": "modelProvider/capabilities/read", id: RequestId, params: ModelProviderCapabilitiesReadParams, } | { "method": "experimentalFeature/list", id: RequestId, params: ExperimentalFeatureListParams, } | { "method": "experimentalFeature/enablement/set", id: RequestId, params: ExperimentalFeatureEnablementSetParams, } | { "method": "mcpServer/oauth/login", id: RequestId, params: McpServerOauthLoginParams, } | { "method": "config/mcpServer/reload", id: RequestId, params: undefined, } | { "method": "mcpServerStatus/list", id: RequestId, params: ListMcpServerStatusParams, } | { "method": "mcpServer/resource/read", id: RequestId, params: McpResourceReadParams, } | { "method": "mcpServer/tool/call", id: RequestId, params: McpServerToolCallParams, } | { "method": "windowsSandbox/setupStart", id: RequestId, params: WindowsSandboxSetupStartParams, } | { "method": "windowsSandbox/readiness", id: RequestId, params: undefined, } | { "method": "account/login/start", id: RequestId, params: LoginAccountParams, } | { "method": "account/login/cancel", id: RequestId, params: CancelLoginAccountParams, } | { "method": "account/logout", id: RequestId, params: undefined, } | { "method": "account/rateLimits/read", id: RequestId, params: undefined, } | { "method": "account/sendAddCreditsNudgeEmail", id: RequestId, params: SendAddCreditsNudgeEmailParams, } | { "method": "feedback/upload", id: RequestId, params: FeedbackUploadParams, } | { "method": "command/exec", id: RequestId, params: CommandExecParams, } | { "method": "command/exec/write", id: RequestId, params: CommandExecWriteParams, } | { "method": "command/exec/terminate", id: RequestId, params: CommandExecTerminateParams, } | { "method": "command/exec/resize", id: RequestId, params: CommandExecResizeParams, } | { "method": "config/read", id: RequestId, params: ConfigReadParams, } | { "method": "externalAgentConfig/detect", id: RequestId, params: ExternalAgentConfigDetectParams, } | { "method": "externalAgentConfig/import", id: RequestId, params: ExternalAgentConfigImportParams, } | { "method": "config/value/write", id: RequestId, params: ConfigValueWriteParams, } | { "method": "config/batchWrite", id: RequestId, params: ConfigBatchWriteParams, } | { "method": "configRequirements/read", id: RequestId, params: undefined, } | { "method": "account/read", id: RequestId, params: GetAccountParams, } | { "method": "getConversationSummary", id: RequestId, params: GetConversationSummaryParams, } | { "method": "gitDiffToRemote", id: RequestId, params: GitDiffToRemoteParams, } | { "method": "getAuthStatus", id: RequestId, params: GetAuthStatusParams, } | { "method": "fuzzyFileSearch", id: RequestId, params: FuzzyFileSearchParams, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/Thread.ts b/codex-rs/app-server-protocol/schema/typescript/v2/Thread.ts index 6410544a8d..517bf1ddf9 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/Thread.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/Thread.ts @@ -77,7 +77,7 @@ gitInfo: GitInfo | null, /** * Model-managed artifacts associated with the thread. */ -artifacts?: Array, +artifacts: Array, /** * Optional user-facing thread title. */ diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadArtifactDeleteParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadArtifactDeleteParams.ts deleted file mode 100644 index 95b293fd07..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadArtifactDeleteParams.ts +++ /dev/null @@ -1,5 +0,0 @@ -// 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 ThreadArtifactDeleteParams = { threadId: string, artifactId: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadArtifactDeleteResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadArtifactDeleteResponse.ts deleted file mode 100644 index a91d62890d..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadArtifactDeleteResponse.ts +++ /dev/null @@ -1,5 +0,0 @@ -// 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 ThreadArtifactDeleteResponse = Record; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadArtifactListParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadArtifactListParams.ts deleted file mode 100644 index 0afad81542..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadArtifactListParams.ts +++ /dev/null @@ -1,5 +0,0 @@ -// 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 ThreadArtifactListParams = { threadId: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadArtifactListResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadArtifactListResponse.ts deleted file mode 100644 index 29091ea8a3..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadArtifactListResponse.ts +++ /dev/null @@ -1,6 +0,0 @@ -// 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 { ThreadArtifact } from "./ThreadArtifact"; - -export type ThreadArtifactListResponse = { artifacts: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadArtifactUpdateParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadArtifactUpdateParams.ts deleted file mode 100644 index 642877ef32..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadArtifactUpdateParams.ts +++ /dev/null @@ -1,6 +0,0 @@ -// 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 { ThreadArtifactWriteParams } from "./ThreadArtifactWriteParams"; - -export type ThreadArtifactUpdateParams = { threadId: string, artifactId: string, artifact: ThreadArtifactWriteParams, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadArtifactUpdateResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadArtifactUpdateResponse.ts deleted file mode 100644 index 178ac281ff..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadArtifactUpdateResponse.ts +++ /dev/null @@ -1,6 +0,0 @@ -// 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 { ThreadArtifact } from "./ThreadArtifact"; - -export type ThreadArtifactUpdateResponse = { artifact: ThreadArtifact, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadArtifactsUpdatedNotification.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadArtifactsUpdatedNotification.ts index e711687f98..741b9337e7 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadArtifactsUpdatedNotification.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadArtifactsUpdatedNotification.ts @@ -3,4 +3,4 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { ThreadArtifact } from "./ThreadArtifact"; -export type ThreadArtifactsUpdatedNotification = { threadId: string, turnId: string | null, artifacts: Array, }; +export type ThreadArtifactsUpdatedNotification = { threadId: string, artifacts: Array, }; 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 0f91fdd5e3..ee020c67ec 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts @@ -359,12 +359,6 @@ export type { ThreadArchivedNotification } from "./ThreadArchivedNotification"; export type { ThreadArtifact } from "./ThreadArtifact"; export type { ThreadArtifactCreateParams } from "./ThreadArtifactCreateParams"; export type { ThreadArtifactCreateResponse } from "./ThreadArtifactCreateResponse"; -export type { ThreadArtifactDeleteParams } from "./ThreadArtifactDeleteParams"; -export type { ThreadArtifactDeleteResponse } from "./ThreadArtifactDeleteResponse"; -export type { ThreadArtifactListParams } from "./ThreadArtifactListParams"; -export type { ThreadArtifactListResponse } from "./ThreadArtifactListResponse"; -export type { ThreadArtifactUpdateParams } from "./ThreadArtifactUpdateParams"; -export type { ThreadArtifactUpdateResponse } from "./ThreadArtifactUpdateResponse"; export type { ThreadArtifactWriteParams } from "./ThreadArtifactWriteParams"; export type { ThreadArtifactsUpdatedNotification } from "./ThreadArtifactsUpdatedNotification"; export type { ThreadClosedNotification } from "./ThreadClosedNotification"; diff --git a/codex-rs/app-server-protocol/src/protocol/common.rs b/codex-rs/app-server-protocol/src/protocol/common.rs index 43c8e38350..45e12f698e 100644 --- a/codex-rs/app-server-protocol/src/protocol/common.rs +++ b/codex-rs/app-server-protocol/src/protocol/common.rs @@ -516,26 +516,11 @@ client_request_definitions! { serialization: thread_id(params.thread_id), response: v2::ThreadMetadataUpdateResponse, }, - ThreadArtifactList => "thread/artifact/list" { - params: v2::ThreadArtifactListParams, - serialization: thread_id(params.thread_id), - response: v2::ThreadArtifactListResponse, - }, ThreadArtifactCreate => "thread/artifact/create" { params: v2::ThreadArtifactCreateParams, serialization: thread_id(params.thread_id), response: v2::ThreadArtifactCreateResponse, }, - ThreadArtifactUpdate => "thread/artifact/update" { - params: v2::ThreadArtifactUpdateParams, - serialization: thread_id(params.thread_id), - response: v2::ThreadArtifactUpdateResponse, - }, - ThreadArtifactDelete => "thread/artifact/delete" { - params: v2::ThreadArtifactDeleteParams, - serialization: thread_id(params.thread_id), - response: v2::ThreadArtifactDeleteResponse, - }, #[experimental("thread/memoryMode/set")] ThreadMemoryModeSet => "thread/memoryMode/set" { params: v2::ThreadMemoryModeSetParams, @@ -2286,7 +2271,7 @@ mod tests { agent_nickname: None, agent_role: None, git_info: None, - artifacts: None, + artifacts: Vec::new(), name: None, turns: Vec::new(), }, diff --git a/codex-rs/app-server-protocol/src/protocol/v2/thread.rs b/codex-rs/app-server-protocol/src/protocol/v2/thread.rs index 688227cfab..6298722cd8 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/thread.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/thread.rs @@ -691,20 +691,6 @@ pub struct ThreadMetadataUpdateResponse { pub thread: Thread, } -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] -#[serde(rename_all = "camelCase")] -#[ts(export_to = "v2/")] -pub struct ThreadArtifactListParams { - pub thread_id: String, -} - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] -#[serde(rename_all = "camelCase")] -#[ts(export_to = "v2/")] -pub struct ThreadArtifactListResponse { - pub artifacts: Vec, -} - #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] @@ -728,35 +714,6 @@ pub struct ThreadArtifactCreateResponse { pub artifact: ThreadArtifact, } -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] -#[serde(rename_all = "camelCase")] -#[ts(export_to = "v2/")] -pub struct ThreadArtifactUpdateParams { - pub thread_id: String, - pub artifact_id: String, - pub artifact: ThreadArtifactWriteParams, -} - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] -#[serde(rename_all = "camelCase")] -#[ts(export_to = "v2/")] -pub struct ThreadArtifactUpdateResponse { - pub artifact: ThreadArtifact, -} - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] -#[serde(rename_all = "camelCase")] -#[ts(export_to = "v2/")] -pub struct ThreadArtifactDeleteParams { - pub thread_id: String, - pub artifact_id: String, -} - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] -#[serde(rename_all = "camelCase")] -#[ts(export_to = "v2/")] -pub struct ThreadArtifactDeleteResponse {} - #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "lowercase")] #[ts(rename_all = "lowercase")] @@ -1242,7 +1199,6 @@ pub struct ThreadGoalUpdatedNotification { #[ts(export_to = "v2/")] pub struct ThreadArtifactsUpdatedNotification { pub thread_id: String, - pub turn_id: Option, pub artifacts: Vec, } 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 97df8716bc..465f2248ce 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 @@ -150,9 +150,8 @@ pub struct Thread { /// Optional Git metadata captured when the thread was created. pub git_info: Option, /// Model-managed artifacts associated with the thread. - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub artifacts: Option>, + #[serde(default)] + pub artifacts: Vec, /// Optional user-facing thread title. pub name: Option, /// Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` diff --git a/codex-rs/app-server/Cargo.toml b/codex-rs/app-server/Cargo.toml index 734475f946..03c5830d94 100644 --- a/codex-rs/app-server/Cargo.toml +++ b/codex-rs/app-server/Cargo.toml @@ -90,7 +90,6 @@ tokio-util = { workspace = true } tracing = { workspace = true, features = ["log"] } tracing-subscriber = { workspace = true, features = ["env-filter", "fmt", "json"] } uuid = { workspace = true, features = ["serde", "v7"] } - [dev-dependencies] app_test_support = { workspace = true } axum = { workspace = true, default-features = false, features = [ diff --git a/codex-rs/app-server/src/bespoke_event_handling.rs b/codex-rs/app-server/src/bespoke_event_handling.rs index 3fb2bc0996..1d31fbd709 100644 --- a/codex-rs/app-server/src/bespoke_event_handling.rs +++ b/codex-rs/app-server/src/bespoke_event_handling.rs @@ -2160,6 +2160,7 @@ mod tests { agent_role: None, agent_path: None, git_info: None, + artifacts: Vec::new(), approval_mode: AskForApproval::OnRequest, sandbox_policy: SandboxPolicy::new_read_only_policy(), token_usage: None, diff --git a/codex-rs/app-server/src/message_processor.rs b/codex-rs/app-server/src/message_processor.rs index 4897684564..a579f78747 100644 --- a/codex-rs/app-server/src/message_processor.rs +++ b/codex-rs/app-server/src/message_processor.rs @@ -1013,6 +1013,9 @@ impl MessageProcessor { ClientRequest::ThreadMetadataUpdate { params, .. } => { self.thread_processor.thread_metadata_update(params).await } + ClientRequest::ThreadArtifactCreate { params, .. } => { + self.thread_processor.thread_artifact_create(params).await + } ClientRequest::ThreadMemoryModeSet { params, .. } => { self.thread_processor.thread_memory_mode_set(params).await } diff --git a/codex-rs/app-server/src/request_processors.rs b/codex-rs/app-server/src/request_processors.rs index f24dcaa34f..1badf9b7ba 100644 --- a/codex-rs/app-server/src/request_processors.rs +++ b/codex-rs/app-server/src/request_processors.rs @@ -158,6 +158,11 @@ use codex_app_server_protocol::ThreadApproveGuardianDeniedActionResponse; use codex_app_server_protocol::ThreadArchiveParams; use codex_app_server_protocol::ThreadArchiveResponse; use codex_app_server_protocol::ThreadArchivedNotification; +use codex_app_server_protocol::ThreadArtifact; +use codex_app_server_protocol::ThreadArtifactCreateParams; +use codex_app_server_protocol::ThreadArtifactCreateResponse; +use codex_app_server_protocol::ThreadArtifactWriteParams; +use codex_app_server_protocol::ThreadArtifactsUpdatedNotification; use codex_app_server_protocol::ThreadBackgroundTerminalsCleanParams; use codex_app_server_protocol::ThreadBackgroundTerminalsCleanResponse; use codex_app_server_protocol::ThreadClosedNotification; @@ -390,13 +395,17 @@ use codex_rollout::state_db::reconcile_rollout; use codex_state::ThreadMetadata; use codex_state::log_db::LogDbLayer; use codex_thread_store::ArchiveThreadParams as StoreArchiveThreadParams; +use codex_thread_store::CreateThreadArtifactParams as StoreCreateThreadArtifactParams; use codex_thread_store::GitInfoPatch as StoreGitInfoPatch; +use codex_thread_store::ListThreadArtifactsParams as StoreListThreadArtifactsParams; use codex_thread_store::ListThreadsParams as StoreListThreadsParams; use codex_thread_store::LocalThreadStore; +use codex_thread_store::NewThreadArtifact as StoreNewThreadArtifact; use codex_thread_store::ReadThreadByRolloutPathParams as StoreReadThreadByRolloutPathParams; use codex_thread_store::ReadThreadParams as StoreReadThreadParams; use codex_thread_store::SortDirection as StoreSortDirection; use codex_thread_store::StoredThread; +use codex_thread_store::StoredThreadArtifact; use codex_thread_store::ThreadMetadataPatch as StoreThreadMetadataPatch; use codex_thread_store::ThreadSortKey as StoreThreadSortKey; use codex_thread_store::ThreadStore; 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 4c14229729..7dd9276f7a 100644 --- a/codex-rs/app-server/src/request_processors/thread_processor.rs +++ b/codex-rs/app-server/src/request_processors/thread_processor.rs @@ -493,6 +493,15 @@ impl ThreadRequestProcessor { .map(|response| Some(response.into())) } + pub(crate) async fn thread_artifact_create( + &self, + params: ThreadArtifactCreateParams, + ) -> Result, JSONRPCErrorError> { + self.thread_artifact_create_response_inner(params) + .await + .map(|response| Some(response.into())) + } + pub(crate) async fn thread_memory_mode_set( &self, params: ThreadMemoryModeSetParams, @@ -1580,6 +1589,64 @@ impl ThreadRequestProcessor { Ok(ThreadMetadataUpdateResponse { thread }) } + async fn thread_artifact_create_response_inner( + &self, + params: ThreadArtifactCreateParams, + ) -> Result { + let thread_id = parse_artifact_thread_id(params.thread_id.as_str())?; + self.reject_ephemeral_artifact_write(thread_id).await?; + let artifact = parse_thread_artifact_write_params(params.artifact)?; + let artifact = self + .thread_store + .create_thread_artifact(StoreCreateThreadArtifactParams { + thread_id, + artifact, + }) + .await + .map_err(|err| thread_store_write_error("create thread artifact", err))?; + self.notify_thread_artifacts_updated(thread_id).await?; + Ok(ThreadArtifactCreateResponse { + artifact: thread_artifact_from_stored_artifact(artifact), + }) + } + + async fn notify_thread_artifacts_updated( + &self, + thread_id: ThreadId, + ) -> Result<(), JSONRPCErrorError> { + let artifacts = self + .thread_store + .list_thread_artifacts(StoreListThreadArtifactsParams { thread_id }) + .await + .map_err(|err| thread_store_write_error("list thread artifacts", err))? + .into_iter() + .map(thread_artifact_from_stored_artifact) + .collect(); + self.outgoing + .send_server_notification(ServerNotification::ThreadArtifactsUpdated( + ThreadArtifactsUpdatedNotification { + thread_id: thread_id.to_string(), + artifacts, + }, + )) + .await; + Ok(()) + } + + async fn reject_ephemeral_artifact_write( + &self, + thread_id: ThreadId, + ) -> Result<(), JSONRPCErrorError> { + if let Ok(thread) = self.thread_manager.get_thread(thread_id).await + && thread.config_snapshot().await.ephemeral + { + return Err(invalid_request(format!( + "ephemeral thread does not support artifact updates: {thread_id}" + ))); + } + Ok(()) + } + fn normalize_thread_metadata_git_field( value: Option>, name: &str, @@ -3735,6 +3802,11 @@ pub(crate) fn thread_from_stored_thread( branch: info.branch, origin_url: info.repository_url, }); + let artifacts = thread + .artifacts + .into_iter() + .map(thread_artifact_from_stored_artifact) + .collect::>(); let cwd = AbsolutePathBuf::relative_to_current_dir(path_utils::normalize_for_native_workdir( thread.cwd, )) @@ -3771,12 +3843,40 @@ pub(crate) fn thread_from_stored_thread( source: source.into(), thread_source: thread.thread_source.map(Into::into), git_info, + artifacts, name: thread.name, turns: Vec::new(), }; (thread, history) } +fn parse_artifact_thread_id(thread_id: &str) -> Result { + ThreadId::from_string(thread_id) + .map_err(|err| invalid_request(format!("invalid thread id: {err}"))) +} + +fn parse_thread_artifact_write_params( + artifact: ThreadArtifactWriteParams, +) -> Result { + let artifact_type = artifact.artifact_type.trim(); + if artifact_type.is_empty() { + return Err(invalid_request("thread artifact type must not be empty")); + } + Ok(StoreNewThreadArtifact { + artifact_type: artifact_type.to_string(), + payload: artifact.payload, + }) +} + +fn thread_artifact_from_stored_artifact(artifact: StoredThreadArtifact) -> ThreadArtifact { + ThreadArtifact { + id: artifact.id, + created_at: artifact.created_at, + artifact_type: artifact.artifact_type, + payload: artifact.payload, + } +} + fn summary_from_stored_thread( thread: StoredThread, fallback_provider: &str, @@ -3973,6 +4073,7 @@ fn build_thread_from_snapshot( source: config_snapshot.session_source.clone().into(), thread_source: config_snapshot.thread_source.map(Into::into), git_info: None, + artifacts: Vec::new(), name: None, turns: Vec::new(), } diff --git a/codex-rs/app-server/src/request_processors/thread_processor_tests.rs b/codex-rs/app-server/src/request_processors/thread_processor_tests.rs index 61a31f9c4d..575442d594 100644 --- a/codex-rs/app-server/src/request_processors/thread_processor_tests.rs +++ b/codex-rs/app-server/src/request_processors/thread_processor_tests.rs @@ -374,6 +374,37 @@ mod thread_processor_behavior_tests { assert!(err.contains("Responses API"), "unexpected error: {err}"); } + #[test] + fn thread_artifacts_preserve_client_defined_payloads() { + let artifact = parse_thread_artifact_write_params(ThreadArtifactWriteParams { + artifact_type: "github/pull_request".to_string(), + payload: json!({ + "repoOwner": "openai", + "repoName": "codex", + "number": 123, + }), + }) + .expect("generic artifact payload should parse"); + + assert_eq!(artifact.artifact_type, "github/pull_request"); + assert_eq!(artifact.payload["number"], 123); + } + + #[test] + fn thread_artifacts_reject_empty_types() { + let err = parse_thread_artifact_write_params(ThreadArtifactWriteParams { + artifact_type: String::new(), + payload: json!({}), + }) + .expect_err("empty artifact type should be rejected"); + + assert!( + err.message.contains("type"), + "unexpected error: {}", + err.message + ); + } + #[test] fn summary_from_stored_thread_preserves_millisecond_precision() { let created_at = @@ -402,6 +433,7 @@ mod thread_processor_behavior_tests { agent_role: None, agent_path: None, git_info: None, + artifacts: Vec::new(), approval_mode: AskForApproval::OnRequest, sandbox_policy: SandboxPolicy::new_read_only_policy(), token_usage: None, @@ -889,6 +921,7 @@ mod thread_processor_behavior_tests { item: RolloutItem::SessionMeta(SessionMetaLine { meta: session_meta.clone(), git: None, + artifacts: std::collections::HashMap::new(), }), }; @@ -955,6 +988,7 @@ mod thread_processor_behavior_tests { item: RolloutItem::SessionMeta(SessionMetaLine { meta: session_meta, git: None, + artifacts: std::collections::HashMap::new(), }), }; fs::write(&path, format!("{}\n", serde_json::to_string(&line)?))?; @@ -996,6 +1030,7 @@ mod thread_processor_behavior_tests { item: RolloutItem::SessionMeta(SessionMetaLine { meta: session_meta, git: None, + artifacts: std::collections::HashMap::new(), }), }; fs::write(&path, format!("{}\n", serde_json::to_string(&line)?))?; diff --git a/codex-rs/app-server/src/request_processors/thread_resume_redaction.rs b/codex-rs/app-server/src/request_processors/thread_resume_redaction.rs index d547aef3f3..5ac1e9aef3 100644 --- a/codex-rs/app-server/src/request_processors/thread_resume_redaction.rs +++ b/codex-rs/app-server/src/request_processors/thread_resume_redaction.rs @@ -182,6 +182,7 @@ mod tests { agent_nickname: None, agent_role: None, git_info: None, + artifacts: Vec::new(), name: None, turns: vec![Turn { id: "turn-1".to_string(), diff --git a/codex-rs/app-server/src/request_processors/thread_summary.rs b/codex-rs/app-server/src/request_processors/thread_summary.rs index 875bd3deaf..293343445d 100644 --- a/codex-rs/app-server/src/request_processors/thread_summary.rs +++ b/codex-rs/app-server/src/request_processors/thread_summary.rs @@ -29,6 +29,7 @@ pub(crate) async fn read_summary_from_rollout( let SessionMetaLine { meta: session_meta, git, + .. } = session_meta_line; let mut session_meta = session_meta; session_meta.source = with_thread_spawn_agent_metadata( @@ -290,6 +291,7 @@ pub(crate) fn summary_to_thread( source: source.into(), thread_source: None, git_info, + artifacts: Vec::new(), name: None, turns: Vec::new(), } diff --git a/codex-rs/app-server/src/thread_status.rs b/codex-rs/app-server/src/thread_status.rs index 7315a13c02..a670411db5 100644 --- a/codex-rs/app-server/src/thread_status.rs +++ b/codex-rs/app-server/src/thread_status.rs @@ -905,6 +905,7 @@ mod tests { source, thread_source: None, git_info: None, + artifacts: Vec::new(), name: None, turns: Vec::new(), } diff --git a/codex-rs/app-server/tests/common/rollout.rs b/codex-rs/app-server/tests/common/rollout.rs index 6b2a9a0abe..d570ed245c 100644 --- a/codex-rs/app-server/tests/common/rollout.rs +++ b/codex-rs/app-server/tests/common/rollout.rs @@ -150,6 +150,7 @@ pub fn create_fake_rollout_with_source( let payload = serde_json::to_value(SessionMetaLine { meta, git: git_info, + artifacts: std::collections::HashMap::new(), })?; let lines = [ @@ -234,6 +235,7 @@ pub fn create_fake_rollout_with_text_elements( let payload = serde_json::to_value(SessionMetaLine { meta, git: git_info, + artifacts: std::collections::HashMap::new(), })?; let lines = [ diff --git a/codex-rs/app-server/tests/suite/v2/thread_resume.rs b/codex-rs/app-server/tests/suite/v2/thread_resume.rs index 64dfe0beb7..01e0cfd58d 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_resume.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_resume.rs @@ -1495,6 +1495,7 @@ stream_max_retries = 0 "payload": serde_json::to_value(SessionMetaLine { meta: session_meta, git: None, + artifacts: std::collections::HashMap::new(), })?, }) .to_string(), diff --git a/codex-rs/core/src/personality_migration_tests.rs b/codex-rs/core/src/personality_migration_tests.rs index 699e06fe67..66b92888a9 100644 --- a/codex-rs/core/src/personality_migration_tests.rs +++ b/codex-rs/core/src/personality_migration_tests.rs @@ -60,6 +60,7 @@ async fn write_rollout_with_user_event(dir: &Path, thread_id: ThreadId) -> io::R memory_mode: None, }, git: None, + artifacts: std::collections::HashMap::new(), }; let meta_line = RolloutLine { timestamp: TEST_TIMESTAMP.to_string(), diff --git a/codex-rs/core/src/realtime_context_tests.rs b/codex-rs/core/src/realtime_context_tests.rs index f36239f2ae..6830409b41 100644 --- a/codex-rs/core/src/realtime_context_tests.rs +++ b/codex-rs/core/src/realtime_context_tests.rs @@ -58,6 +58,7 @@ fn stored_thread(cwd: &str, title: &str, first_user_message: &str) -> StoredThre branch: Some("main".to_string()), repository_url: None, }), + artifacts: Vec::new(), approval_mode: AskForApproval::Never, sandbox_policy: SandboxPolicy::new_read_only_policy(), token_usage: None, diff --git a/codex-rs/core/src/tools/spec_plan.rs b/codex-rs/core/src/tools/spec_plan.rs index 13a79e5943..569cdf3f23 100644 --- a/codex-rs/core/src/tools/spec_plan.rs +++ b/codex-rs/core/src/tools/spec_plan.rs @@ -200,7 +200,6 @@ pub fn build_tool_registry_builder( builder.register_handler(Arc::new(CreateGoalHandler)); builder.register_handler(Arc::new(UpdateGoalHandler)); } - builder.register_handler(Arc::new(RequestUserInputHandler { available_modes: config.request_user_input_available_modes.clone(), })); diff --git a/codex-rs/core/tests/suite/client.rs b/codex-rs/core/tests/suite/client.rs index 510de6f1bf..88e47ee208 100644 --- a/codex-rs/core/tests/suite/client.rs +++ b/codex-rs/core/tests/suite/client.rs @@ -493,6 +493,7 @@ async fn resume_replays_legacy_js_repl_image_rollout_shapes() { ..Default::default() }, git: None, + artifacts: std::collections::HashMap::new(), }), }, RolloutLine { @@ -623,6 +624,7 @@ async fn resume_replays_image_tool_outputs_with_detail() { ..Default::default() }, git: None, + artifacts: std::collections::HashMap::new(), }), }, RolloutLine { diff --git a/codex-rs/core/tests/suite/personality_migration.rs b/codex-rs/core/tests/suite/personality_migration.rs index 0b89a9cfba..e6a67f7e85 100644 --- a/codex-rs/core/tests/suite/personality_migration.rs +++ b/codex-rs/core/tests/suite/personality_migration.rs @@ -76,6 +76,7 @@ async fn write_rollout_with_user_event(dir: &Path, thread_id: ThreadId) -> io::R memory_mode: None, }, git: None, + artifacts: std::collections::HashMap::new(), }; let meta_line = RolloutLine { timestamp: TEST_TIMESTAMP.to_string(), @@ -122,6 +123,7 @@ async fn write_rollout_with_meta_only(dir: &Path, thread_id: ThreadId) -> io::Re memory_mode: None, }, git: None, + artifacts: std::collections::HashMap::new(), }; let meta_line = RolloutLine { timestamp: TEST_TIMESTAMP.to_string(), diff --git a/codex-rs/core/tests/suite/sqlite_state.rs b/codex-rs/core/tests/suite/sqlite_state.rs index b2a2778047..fd52eb8e68 100644 --- a/codex-rs/core/tests/suite/sqlite_state.rs +++ b/codex-rs/core/tests/suite/sqlite_state.rs @@ -154,6 +154,7 @@ async fn backfill_scans_existing_rollouts() -> Result<()> { memory_mode: None, }, git: None, + artifacts: std::collections::HashMap::new(), }; let lines = [ diff --git a/codex-rs/mcp-server/src/codex_tool_runner.rs b/codex-rs/mcp-server/src/codex_tool_runner.rs index 9e3ac1bae9..7db916853c 100644 --- a/codex-rs/mcp-server/src/codex_tool_runner.rs +++ b/codex-rs/mcp-server/src/codex_tool_runner.rs @@ -321,7 +321,7 @@ async fn run_codex_tool_session_inner( tracing::error!("unexpected SessionConfigured event"); } EventMsg::ThreadGoalUpdated(_) => { - // Ignore thread goal metadata updates in MCP tool runner. + // Ignore thread metadata updates in MCP tool runner. } EventMsg::McpStartupUpdate(_) | EventMsg::McpStartupComplete(_) => { // Ignored in MCP tool runner. diff --git a/codex-rs/protocol/src/protocol.rs b/codex-rs/protocol/src/protocol.rs index 91fd02d858..a88c885064 100644 --- a/codex-rs/protocol/src/protocol.rs +++ b/codex-rs/protocol/src/protocol.rs @@ -2759,6 +2759,15 @@ pub struct SessionMetaLine { pub meta: SessionMeta, #[serde(skip_serializing_if = "Option::is_none")] pub git: Option, + #[serde(default, skip_serializing_if = "HashMap::is_empty")] + pub artifacts: HashMap>, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] +pub struct ThreadArtifact { + pub created_at: i64, + pub artifact_type: String, + pub payload: serde_json::Value, } #[derive(Serialize, Deserialize, Debug, Clone, JsonSchema, TS)] diff --git a/codex-rs/rollout/src/metadata_tests.rs b/codex-rs/rollout/src/metadata_tests.rs index 45db758c65..36e263d735 100644 --- a/codex-rs/rollout/src/metadata_tests.rs +++ b/codex-rs/rollout/src/metadata_tests.rs @@ -52,6 +52,7 @@ async fn extract_metadata_from_rollout_uses_session_meta() { let session_meta_line = SessionMetaLine { meta: session_meta, git: None, + artifacts: std::collections::HashMap::new(), }; let rollout_line = RolloutLine { timestamp: "2026-01-27T12:34:56Z".to_string(), @@ -111,6 +112,7 @@ async fn extract_metadata_from_rollout_returns_latest_memory_mode() { item: RolloutItem::SessionMeta(SessionMetaLine { meta: session_meta, git: None, + artifacts: std::collections::HashMap::new(), }), }, RolloutLine { @@ -118,6 +120,7 @@ async fn extract_metadata_from_rollout_returns_latest_memory_mode() { item: RolloutItem::SessionMeta(SessionMetaLine { meta: polluted_meta, git: None, + artifacts: std::collections::HashMap::new(), }), }, ]; @@ -364,6 +367,7 @@ fn write_rollout_in_sessions_with_cwd( let session_meta_line = SessionMetaLine { meta: session_meta, git, + artifacts: std::collections::HashMap::new(), }; let rollout_line = RolloutLine { timestamp: event_ts.to_string(), diff --git a/codex-rs/rollout/src/recorder.rs b/codex-rs/rollout/src/recorder.rs index b4e0ab1e09..01a6a937f3 100644 --- a/codex-rs/rollout/src/recorder.rs +++ b/codex-rs/rollout/src/recorder.rs @@ -1,5 +1,6 @@ //! Persist Codex session rollouts (.jsonl) so sessions can be replayed or inspected later. +use std::collections::HashMap; use std::collections::HashSet; use std::fs; use std::fs::File; @@ -1760,6 +1761,7 @@ async fn write_session_meta( let session_meta_line = SessionMetaLine { meta: session_meta, git: git_info, + artifacts: HashMap::new(), }; if state_db_ctx.is_some() { *state_builder = metadata::builder_from_session_meta(&session_meta_line, rollout_path); diff --git a/codex-rs/rollout/src/recorder_tests.rs b/codex-rs/rollout/src/recorder_tests.rs index 2063020be2..d7b0f88a49 100644 --- a/codex-rs/rollout/src/recorder_tests.rs +++ b/codex-rs/rollout/src/recorder_tests.rs @@ -101,6 +101,7 @@ async fn state_db_init_backfills_before_returning() -> anyhow::Result<()> { memory_mode: None, }, git: None, + artifacts: std::collections::HashMap::new(), }; let lines = [ RolloutLine { diff --git a/codex-rs/rollout/src/session_index_tests.rs b/codex-rs/rollout/src/session_index_tests.rs index 757b08b4d4..2dc0139abf 100644 --- a/codex-rs/rollout/src/session_index_tests.rs +++ b/codex-rs/rollout/src/session_index_tests.rs @@ -42,6 +42,7 @@ fn write_rollout_with_metadata(path: &Path, thread_id: ThreadId) -> std::io::Res memory_mode: None, }, git: None, + artifacts: std::collections::HashMap::new(), }), }; let body = serde_json::to_string(&line).map_err(std::io::Error::other)?; diff --git a/codex-rs/rollout/src/tests.rs b/codex-rs/rollout/src/tests.rs index 22f9a73a4e..c36b9728eb 100644 --- a/codex-rs/rollout/src/tests.rs +++ b/codex-rs/rollout/src/tests.rs @@ -1393,6 +1393,7 @@ async fn test_updated_at_uses_file_mtime() -> Result<()> { memory_mode: None, }, git: None, + artifacts: std::collections::HashMap::new(), }), }; writeln!(file, "{}", serde_json::to_string(&meta_line)?)?; diff --git a/codex-rs/state/migrations/0033_threads_artifacts_json.sql b/codex-rs/state/migrations/0033_threads_artifacts_json.sql new file mode 100644 index 0000000000..6e8d19022e --- /dev/null +++ b/codex-rs/state/migrations/0033_threads_artifacts_json.sql @@ -0,0 +1 @@ +ALTER TABLE threads ADD COLUMN artifacts_json TEXT NOT NULL DEFAULT '[]'; diff --git a/codex-rs/state/src/extract.rs b/codex-rs/state/src/extract.rs index d815d444ce..11a3818b81 100644 --- a/codex-rs/state/src/extract.rs +++ b/codex-rs/state/src/extract.rs @@ -68,6 +68,34 @@ fn apply_session_meta_from_item(metadata: &mut ThreadMetadata, meta_line: &Sessi metadata.git_branch = git.branch.clone(); metadata.git_origin_url = git.repository_url.clone(); } + for (id, artifact) in &meta_line.artifacts { + match artifact { + Some(artifact) => { + let Some(created_at) = + chrono::DateTime::::from_timestamp(artifact.created_at, 0) + else { + continue; + }; + let updated_artifact = crate::ThreadArtifact { + thread_id: metadata.id, + id: id.clone(), + created_at, + artifact_type: artifact.artifact_type.clone(), + payload: artifact.payload.clone(), + }; + if let Some(existing) = metadata + .artifacts + .iter_mut() + .find(|existing| existing.id == *id) + { + *existing = updated_artifact; + } else { + metadata.artifacts.push(updated_artifact); + } + } + None => metadata.artifacts.retain(|artifact| artifact.id != *id), + } + } } fn apply_turn_context(metadata: &mut ThreadMetadata, turn_ctx: &TurnContextItem) { @@ -328,6 +356,7 @@ mod tests { memory_mode: None, }, git: None, + artifacts: std::collections::HashMap::new(), }), "test-provider", ); @@ -462,6 +491,7 @@ mod tests { memory_mode: None, }, git: None, + artifacts: std::collections::HashMap::new(), }), "test-provider", ); @@ -498,6 +528,7 @@ mod tests { git_sha: None, git_branch: None, git_origin_url: None, + artifacts: Vec::new(), } } diff --git a/codex-rs/state/src/lib.rs b/codex-rs/state/src/lib.rs index 3c98a77978..8ea14e4406 100644 --- a/codex-rs/state/src/lib.rs +++ b/codex-rs/state/src/lib.rs @@ -43,6 +43,7 @@ pub use model::Stage1JobClaim; pub use model::Stage1JobClaimOutcome; pub use model::Stage1Output; pub use model::Stage1StartupClaimParams; +pub use model::ThreadArtifact; pub use model::ThreadGoal; pub use model::ThreadGoalStatus; pub use model::ThreadMetadata; diff --git a/codex-rs/state/src/model/mod.rs b/codex-rs/state/src/model/mod.rs index a431bc64c0..6dd3dc5470 100644 --- a/codex-rs/state/src/model/mod.rs +++ b/codex-rs/state/src/model/mod.rs @@ -3,6 +3,7 @@ mod backfill_state; mod graph; mod log; mod memories; +mod thread_artifact; mod thread_goal; mod thread_metadata; @@ -24,6 +25,7 @@ pub use memories::Stage1JobClaim; pub use memories::Stage1JobClaimOutcome; pub use memories::Stage1Output; pub use memories::Stage1StartupClaimParams; +pub use thread_artifact::ThreadArtifact; pub use thread_goal::ThreadGoal; pub use thread_goal::ThreadGoalStatus; pub use thread_metadata::Anchor; @@ -41,6 +43,7 @@ pub(crate) use memories::Stage1OutputRow; pub(crate) use thread_goal::ThreadGoalRow; pub(crate) use thread_metadata::ThreadRow; pub(crate) use thread_metadata::anchor_from_item; +pub(crate) use thread_metadata::artifacts_to_json; pub(crate) use thread_metadata::datetime_to_epoch_millis; pub(crate) use thread_metadata::datetime_to_epoch_seconds; pub(crate) use thread_metadata::epoch_millis_to_datetime; diff --git a/codex-rs/state/src/model/thread_artifact.rs b/codex-rs/state/src/model/thread_artifact.rs new file mode 100644 index 0000000000..faa28800d1 --- /dev/null +++ b/codex-rs/state/src/model/thread_artifact.rs @@ -0,0 +1,13 @@ +use chrono::DateTime; +use chrono::Utc; +use codex_protocol::ThreadId; +use serde_json::Value as JsonValue; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ThreadArtifact { + pub thread_id: ThreadId, + pub id: String, + pub created_at: DateTime, + pub artifact_type: String, + pub payload: JsonValue, +} diff --git a/codex-rs/state/src/model/thread_metadata.rs b/codex-rs/state/src/model/thread_metadata.rs index f0b8d315e5..3042a5ec52 100644 --- a/codex-rs/state/src/model/thread_metadata.rs +++ b/codex-rs/state/src/model/thread_metadata.rs @@ -7,6 +7,8 @@ use codex_protocol::protocol::AskForApproval; use codex_protocol::protocol::SandboxPolicy; use codex_protocol::protocol::SessionSource; use codex_protocol::protocol::ThreadSource; +use serde::Deserialize; +use serde::Serialize; use sqlx::Row; use sqlx::sqlite::SqliteRow; use std::path::PathBuf; @@ -107,6 +109,8 @@ pub struct ThreadMetadata { pub git_branch: Option, /// The git origin URL, if known. pub git_origin_url: Option, + /// Model-managed artifacts attached to the thread. + pub artifacts: Vec, } /// Builder data required to construct [`ThreadMetadata`] without parsing filenames. @@ -148,6 +152,8 @@ pub struct ThreadMetadataBuilder { pub git_branch: Option, /// The git origin URL, if known. pub git_origin_url: Option, + /// Model-managed artifacts attached to the thread. + pub artifacts: Vec, } impl ThreadMetadataBuilder { @@ -177,6 +183,7 @@ impl ThreadMetadataBuilder { git_sha: None, git_branch: None, git_origin_url: None, + artifacts: Vec::new(), } } @@ -221,6 +228,7 @@ impl ThreadMetadataBuilder { git_sha: self.git_sha.clone(), git_branch: self.git_branch.clone(), git_origin_url: self.git_origin_url.clone(), + artifacts: self.artifacts.clone(), } } } @@ -311,6 +319,9 @@ impl ThreadMetadata { if self.git_origin_url != other.git_origin_url { diffs.push("git_origin_url"); } + if self.artifacts != other.artifacts { + diffs.push("artifacts"); + } diffs } } @@ -345,6 +356,7 @@ pub(crate) struct ThreadRow { git_sha: Option, git_branch: Option, git_origin_url: Option, + artifacts_json: String, } impl ThreadRow { @@ -374,6 +386,7 @@ impl ThreadRow { git_sha: row.try_get("git_sha")?, git_branch: row.try_get("git_branch")?, git_origin_url: row.try_get("git_origin_url")?, + artifacts_json: row.try_get("artifacts_json")?, }) } } @@ -407,13 +420,15 @@ impl TryFrom for ThreadMetadata { git_sha, git_branch, git_origin_url, + artifacts_json, } = row; let thread_source = thread_source .map(|thread_source| thread_source.parse()) .transpose() .map_err(anyhow::Error::msg)?; + let thread_id = ThreadId::try_from(id)?; Ok(Self { - id: ThreadId::try_from(id)?, + id: thread_id, rollout_path: PathBuf::from(rollout_path), created_at: epoch_millis_to_datetime(created_at)?, updated_at: epoch_millis_to_datetime(updated_at)?, @@ -438,10 +453,49 @@ impl TryFrom for ThreadMetadata { git_sha, git_branch, git_origin_url, + artifacts: artifacts_from_json(thread_id, artifacts_json.as_str())?, }) } } +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +struct StoredThreadArtifactMetadata { + id: String, + created_at: i64, + artifact_type: String, + payload: serde_json::Value, +} + +pub(crate) fn artifacts_to_json(artifacts: &[crate::ThreadArtifact]) -> Result { + serde_json::to_string( + &artifacts + .iter() + .map(|artifact| StoredThreadArtifactMetadata { + id: artifact.id.clone(), + created_at: artifact.created_at.timestamp(), + artifact_type: artifact.artifact_type.clone(), + payload: artifact.payload.clone(), + }) + .collect::>(), + ) + .map_err(Into::into) +} + +fn artifacts_from_json(thread_id: ThreadId, json: &str) -> Result> { + serde_json::from_str::>(json)? + .into_iter() + .map(|artifact| { + Ok(crate::ThreadArtifact { + thread_id, + id: artifact.id, + created_at: epoch_seconds_to_datetime(artifact.created_at)?, + artifact_type: artifact.artifact_type, + payload: artifact.payload, + }) + }) + .collect() +} + pub(crate) fn anchor_from_item(item: &ThreadMetadata, sort_key: SortKey) -> Option { let ts = match sort_key { SortKey::CreatedAt => item.created_at, @@ -524,6 +578,7 @@ mod tests { git_sha: None, git_branch: None, git_origin_url: None, + artifacts_json: "[]".to_string(), } } @@ -554,6 +609,7 @@ mod tests { git_sha: None, git_branch: None, git_origin_url: None, + artifacts: Vec::new(), } } diff --git a/codex-rs/state/src/runtime/memories.rs b/codex-rs/state/src/runtime/memories.rs index aeafe62a77..039e53d18d 100644 --- a/codex-rs/state/src/runtime/memories.rs +++ b/codex-rs/state/src/runtime/memories.rs @@ -155,7 +155,8 @@ SELECT threads.archived_at, threads.git_sha, threads.git_branch, - threads.git_origin_url + threads.git_origin_url, + threads.artifacts_json FROM threads LEFT JOIN stage1_outputs ON stage1_outputs.thread_id = threads.id diff --git a/codex-rs/state/src/runtime/test_support.rs b/codex-rs/state/src/runtime/test_support.rs index 848a193335..d7ca9dde2e 100644 --- a/codex-rs/state/src/runtime/test_support.rs +++ b/codex-rs/state/src/runtime/test_support.rs @@ -67,5 +67,6 @@ pub(super) fn test_thread_metadata( git_sha: None, git_branch: None, git_origin_url: None, + artifacts: Vec::new(), } } diff --git a/codex-rs/state/src/runtime/threads.rs b/codex-rs/state/src/runtime/threads.rs index 925fb45288..18008fcf11 100644 --- a/codex-rs/state/src/runtime/threads.rs +++ b/codex-rs/state/src/runtime/threads.rs @@ -31,7 +31,8 @@ SELECT threads.archived_at, threads.git_sha, threads.git_branch, - threads.git_origin_url + threads.git_origin_url, + threads.artifacts_json FROM threads WHERE threads.id = ? "#, @@ -479,6 +480,7 @@ ON CONFLICT(child_thread_id) DO NOTHING ) -> anyhow::Result { let updated_at = self.allocate_thread_updated_at(metadata.updated_at)?; let preview = metadata_preview(metadata); + let artifacts_json = crate::model::artifacts_to_json(metadata.artifacts.as_slice())?; let result = sqlx::query( r#" INSERT INTO threads ( @@ -509,8 +511,9 @@ INSERT INTO threads ( git_sha, git_branch, git_origin_url, + artifacts_json, memory_mode -) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) +) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(id) DO NOTHING "#, ) @@ -550,6 +553,7 @@ ON CONFLICT(id) DO NOTHING .bind(metadata.git_sha.as_deref()) .bind(metadata.git_branch.as_deref()) .bind(metadata.git_origin_url.as_deref()) + .bind(artifacts_json) .bind("enabled") .execute(self.pool.as_ref()) .await?; @@ -571,6 +575,23 @@ ON CONFLICT(id) DO NOTHING Ok(result.rows_affected() > 0) } + pub async fn append_thread_artifact( + &self, + artifact: crate::ThreadArtifact, + ) -> anyhow::Result { + let Some(mut metadata) = self.get_thread(artifact.thread_id).await? else { + return Ok(false); + }; + metadata.artifacts.push(artifact); + let artifacts_json = crate::model::artifacts_to_json(metadata.artifacts.as_slice())?; + let result = sqlx::query("UPDATE threads SET artifacts_json = ? WHERE id = ?") + .bind(artifacts_json) + .bind(metadata.id.to_string()) + .execute(self.pool.as_ref()) + .await?; + Ok(result.rows_affected() > 0) + } + pub async fn update_thread_title( &self, thread_id: ThreadId, @@ -685,6 +706,7 @@ WHERE id = ? // Backfill/reconcile callers merge existing git info before upserting, but that // read/modify/write is not atomic. Preserve non-null SQLite git fields here so // an explicit metadata update cannot be lost if a stale rollout upsert lands later. + let artifacts_json = crate::model::artifacts_to_json(metadata.artifacts.as_slice())?; sqlx::query( r#" INSERT INTO threads ( @@ -715,8 +737,9 @@ INSERT INTO threads ( git_sha, git_branch, git_origin_url, + artifacts_json, memory_mode -) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) +) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(id) DO UPDATE SET rollout_path = excluded.rollout_path, created_at = excluded.created_at, @@ -743,7 +766,8 @@ ON CONFLICT(id) DO UPDATE SET archived_at = excluded.archived_at, git_sha = COALESCE(threads.git_sha, excluded.git_sha), git_branch = COALESCE(threads.git_branch, excluded.git_branch), - git_origin_url = COALESCE(threads.git_origin_url, excluded.git_origin_url) + git_origin_url = COALESCE(threads.git_origin_url, excluded.git_origin_url), + artifacts_json = excluded.artifacts_json "#, ) .bind(metadata.id.to_string()) @@ -782,6 +806,7 @@ ON CONFLICT(id) DO UPDATE SET .bind(metadata.git_sha.as_deref()) .bind(metadata.git_branch.as_deref()) .bind(metadata.git_origin_url.as_deref()) + .bind(artifacts_json) .bind(creation_memory_mode.unwrap_or("enabled")) .execute(self.pool.as_ref()) .await?; @@ -998,7 +1023,8 @@ SELECT threads.archived_at, threads.git_sha, threads.git_branch, - threads.git_origin_url + threads.git_origin_url, + threads.artifacts_json "#, ); } @@ -1169,6 +1195,7 @@ mod tests { use codex_protocol::protocol::SessionMeta; use codex_protocol::protocol::SessionMetaLine; use codex_protocol::protocol::SessionSource; + use codex_protocol::protocol::ThreadArtifact as RolloutThreadArtifact; use pretty_assertions::assert_eq; use std::path::PathBuf; @@ -1210,6 +1237,124 @@ mod tests { assert_eq!(memory_mode, "disabled"); } + #[tokio::test] + async fn thread_artifacts_restore_from_session_meta_in_creation_order() { + let codex_home = unique_temp_dir(); + let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) + .await + .expect("state db should initialize"); + let thread_id = + ThreadId::from_string("00000000-0000-0000-0000-000000000321").expect("valid thread id"); + let metadata = test_thread_metadata(&codex_home, thread_id, codex_home.clone()); + runtime + .upsert_thread(&metadata) + .await + .expect("thread insert should succeed"); + + let builder = ThreadMetadataBuilder::new( + thread_id, + metadata.rollout_path.clone(), + metadata.created_at, + SessionSource::Cli, + ); + runtime + .apply_rollout_items( + &builder, + &[ + RolloutItem::SessionMeta(SessionMetaLine { + meta: SessionMeta { + id: thread_id, + timestamp: metadata.created_at.to_rfc3339(), + source: SessionSource::Cli, + ..Default::default() + }, + git: None, + artifacts: std::collections::HashMap::from([( + "artifact-1".to_string(), + Some(RolloutThreadArtifact { + created_at: 1_700_000_001, + artifact_type: "github/pull_request".to_string(), + payload: serde_json::json!({ "number": 12 }), + }), + )]), + }), + RolloutItem::SessionMeta(SessionMetaLine { + meta: SessionMeta { + id: thread_id, + timestamp: metadata.created_at.to_rfc3339(), + source: SessionSource::Cli, + ..Default::default() + }, + git: None, + artifacts: std::collections::HashMap::from([( + "artifact-2".to_string(), + Some(RolloutThreadArtifact { + created_at: 1_700_000_002, + artifact_type: "github/pull_request".to_string(), + payload: serde_json::json!({ "number": 34 }), + }), + )]), + }), + ], + /*new_thread_memory_mode*/ None, + /*updated_at_override*/ None, + ) + .await + .expect("artifacts should restore from rollout metadata"); + + assert_eq!( + runtime + .get_thread(thread_id) + .await + .expect("thread should load") + .expect("thread should exist") + .artifacts + .iter() + .map(|artifact| artifact.id.as_str()) + .collect::>(), + vec!["artifact-1", "artifact-2"] + ); + + runtime + .apply_rollout_items( + &builder, + &[RolloutItem::SessionMeta(SessionMetaLine { + meta: SessionMeta { + id: thread_id, + timestamp: metadata.created_at.to_rfc3339(), + source: SessionSource::Cli, + ..Default::default() + }, + git: None, + artifacts: std::collections::HashMap::from([ + ( + "artifact-1".to_string(), + Some(RolloutThreadArtifact { + created_at: 1_700_000_001, + artifact_type: "github/pull_request".to_string(), + payload: serde_json::json!({ "number": 56 }), + }), + ), + ("artifact-2".to_string(), None), + ]), + })], + /*new_thread_memory_mode*/ None, + /*updated_at_override*/ None, + ) + .await + .expect("artifact patches should apply from rollout metadata"); + + let artifacts = runtime + .get_thread(thread_id) + .await + .expect("thread should load") + .expect("thread should exist") + .artifacts; + assert_eq!(artifacts.len(), 1); + assert_eq!(artifacts[0].id, "artifact-1"); + assert_eq!(artifacts[0].payload, serde_json::json!({ "number": 56 })); + } + #[tokio::test] async fn list_threads_updated_after_returns_oldest_changes_first() { let codex_home = unique_temp_dir(); @@ -1405,6 +1550,7 @@ mod tests { memory_mode: Some("polluted".to_string()), }, git: None, + artifacts: std::collections::HashMap::new(), })]; runtime @@ -1468,6 +1614,7 @@ mod tests { branch: Some("rollout-branch".to_string()), repository_url: Some("git@example.com:openai/codex.git".to_string()), }), + artifacts: std::collections::HashMap::new(), })]; runtime diff --git a/codex-rs/thread-store/Cargo.toml b/codex-rs/thread-store/Cargo.toml index 0f8e83fe60..052bac53a9 100644 --- a/codex-rs/thread-store/Cargo.toml +++ b/codex-rs/thread-store/Cargo.toml @@ -24,6 +24,7 @@ serde_json = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } +uuid = { workspace = true, features = ["v7"] } [dev-dependencies] pretty_assertions = { workspace = true } diff --git a/codex-rs/thread-store/src/in_memory.rs b/codex-rs/thread-store/src/in_memory.rs index 1483906267..1c5539f632 100644 --- a/codex-rs/thread-store/src/in_memory.rs +++ b/codex-rs/thread-store/src/in_memory.rs @@ -14,13 +14,16 @@ use codex_protocol::protocol::SandboxPolicy; use crate::AppendThreadItemsParams; use crate::ArchiveThreadParams; +use crate::CreateThreadArtifactParams; use crate::CreateThreadParams; +use crate::ListThreadArtifactsParams; use crate::ListThreadsParams; use crate::LoadThreadHistoryParams; use crate::ReadThreadByRolloutPathParams; use crate::ReadThreadParams; use crate::ResumeThreadParams; use crate::StoredThread; +use crate::StoredThreadArtifact; use crate::StoredThreadHistory; use crate::ThreadPage; use crate::ThreadStore; @@ -107,6 +110,8 @@ pub struct InMemoryThreadStoreCalls { pub read_thread: usize, pub read_thread_by_rollout_path: usize, pub list_threads: usize, + pub list_thread_artifacts: usize, + pub create_thread_artifact: usize, pub update_thread_metadata: usize, pub archive_thread: usize, pub unarchive_thread: usize, @@ -128,6 +133,7 @@ struct InMemoryThreadStoreState { created_threads: HashMap, histories: HashMap>, names: HashMap>, + artifacts: HashMap>, rollout_paths: HashMap, } @@ -265,6 +271,36 @@ impl ThreadStore for InMemoryThreadStore { }) } + async fn list_thread_artifacts( + &self, + params: ListThreadArtifactsParams, + ) -> ThreadStoreResult> { + let mut state = self.state.lock().await; + state.calls.list_thread_artifacts += 1; + Ok(state + .artifacts + .get(¶ms.thread_id) + .cloned() + .unwrap_or_default()) + } + + async fn create_thread_artifact( + &self, + params: CreateThreadArtifactParams, + ) -> ThreadStoreResult { + let mut state = self.state.lock().await; + state.calls.create_thread_artifact += 1; + if !state.created_threads.contains_key(¶ms.thread_id) { + return Err(ThreadStoreError::ThreadNotFound { + thread_id: params.thread_id, + }); + } + let artifacts = state.artifacts.entry(params.thread_id).or_default(); + let artifact = stored_artifact_from_new(params.artifact, uuid::Uuid::now_v7().to_string()); + artifacts.push(artifact.clone()); + Ok(artifact) + } + async fn update_thread_metadata( &self, params: UpdateThreadMetadataParams, @@ -334,6 +370,7 @@ fn stored_thread_from_state( agent_role: None, agent_path: None, git_info: None, + artifacts: state.artifacts.get(&thread_id).cloned().unwrap_or_default(), approval_mode: AskForApproval::Never, sandbox_policy: SandboxPolicy::new_read_only_policy(), token_usage: None, @@ -341,3 +378,15 @@ fn stored_thread_from_state( history, }) } + +fn stored_artifact_from_new( + artifact: crate::NewThreadArtifact, + id: String, +) -> StoredThreadArtifact { + StoredThreadArtifact { + id, + created_at: Utc::now().timestamp(), + artifact_type: artifact.artifact_type, + payload: artifact.payload, + } +} diff --git a/codex-rs/thread-store/src/lib.rs b/codex-rs/thread-store/src/lib.rs index c7f6b4eaed..6a9d86ffdc 100644 --- a/codex-rs/thread-store/src/lib.rs +++ b/codex-rs/thread-store/src/lib.rs @@ -22,19 +22,23 @@ pub use local::LocalThreadStoreConfig; pub use store::ThreadStore; pub use types::AppendThreadItemsParams; pub use types::ArchiveThreadParams; +pub use types::CreateThreadArtifactParams; pub use types::CreateThreadParams; pub use types::GitInfoPatch; pub use types::ItemPage; pub use types::ListItemsParams; +pub use types::ListThreadArtifactsParams; pub use types::ListThreadsParams; pub use types::ListTurnsParams; pub use types::LoadThreadHistoryParams; +pub use types::NewThreadArtifact; pub use types::OptionalStringPatch; pub use types::ReadThreadByRolloutPathParams; pub use types::ReadThreadParams; pub use types::ResumeThreadParams; pub use types::SortDirection; pub use types::StoredThread; +pub use types::StoredThreadArtifact; pub use types::StoredThreadHistory; pub use types::StoredTurn; pub use types::StoredTurnError; diff --git a/codex-rs/thread-store/src/local/helpers.rs b/codex-rs/thread-store/src/local/helpers.rs index bfdc94fbf0..73a3f6de17 100644 --- a/codex-rs/thread-store/src/local/helpers.rs +++ b/codex-rs/thread-store/src/local/helpers.rs @@ -139,6 +139,7 @@ pub(super) fn stored_thread_from_rollout_item( agent_role: item.agent_role, agent_path: None, git_info, + artifacts: Vec::new(), approval_mode: AskForApproval::OnRequest, sandbox_policy: SandboxPolicy::new_read_only_policy(), token_usage: None, diff --git a/codex-rs/thread-store/src/local/list_threads.rs b/codex-rs/thread-store/src/local/list_threads.rs index ede9e9e9c8..7dcabe2f7f 100644 --- a/codex-rs/thread-store/src/local/list_threads.rs +++ b/codex-rs/thread-store/src/local/list_threads.rs @@ -80,6 +80,8 @@ pub(super) async fn list_threads( .map(|thread| thread.thread_id) .collect::>(); let mut names = HashMap::::with_capacity(thread_ids.len()); + let mut artifacts = + HashMap::>::with_capacity(thread_ids.len()); if let Some(state_db_ctx) = store.state_db().await { for &thread_id in &thread_ids { let Ok(Some(metadata)) = state_db_ctx.get_thread(thread_id).await else { @@ -88,6 +90,14 @@ pub(super) async fn list_threads( if let Some(title) = distinct_thread_metadata_title(&metadata) { names.insert(thread_id, title); } + artifacts.insert( + thread_id, + metadata + .artifacts + .into_iter() + .map(super::thread_artifacts::stored_artifact_from_state) + .collect(), + ); } } if names.len() < thread_ids.len() @@ -102,6 +112,9 @@ pub(super) async fn list_threads( if let Some(title) = names.get(&thread.thread_id).cloned() { set_thread_name_from_title(thread, title); } + if let Some(thread_artifacts) = artifacts.remove(&thread.thread_id) { + thread.artifacts = thread_artifacts; + } } Ok(ThreadPage { items, next_cursor }) @@ -193,6 +206,8 @@ mod tests { use uuid::Uuid; use super::*; + use crate::CreateThreadArtifactParams; + use crate::NewThreadArtifact; use crate::ThreadStore; use crate::local::LocalThreadStore; use crate::local::test_support::test_config; @@ -225,7 +240,7 @@ mod tests { cwd_filters: None, archived: false, search_term: None, - use_state_db_only: false, + use_state_db_only: true, }) .await .expect("thread listing"); @@ -301,6 +316,78 @@ mod tests { ); } + #[tokio::test] + async fn list_threads_preserves_sqlite_artifacts_for_rollout_rows() { + let home = TempDir::new().expect("temp dir"); + let config = test_config(home.path()); + let uuid = Uuid::from_u128(107); + let thread_id = ThreadId::from_string(&uuid.to_string()).expect("valid thread id"); + let rollout_path = + write_session_file(home.path(), "2025-01-03T12-00-00", uuid).expect("session file"); + let runtime = codex_state::StateRuntime::init( + config.sqlite_home.clone(), + config.default_model_provider_id.clone(), + ) + .await + .expect("state db should initialize"); + let store = LocalThreadStore::new(config.clone(), Some(runtime.clone())); + runtime + .mark_backfill_complete(/*last_watermark*/ None) + .await + .expect("backfill should be complete"); + let mut builder = codex_state::ThreadMetadataBuilder::new( + thread_id, + rollout_path, + Utc::now(), + SessionSource::Cli, + ); + builder.model_provider = Some(config.default_model_provider_id.clone()); + let mut metadata = builder.build(config.default_model_provider_id.as_str()); + metadata.title = "artifact thread".to_string(); + metadata.first_user_message = Some("plain preview".to_string()); + metadata.preview = metadata.first_user_message.clone(); + runtime + .upsert_thread(&metadata) + .await + .expect("state db upsert should succeed"); + let artifact = store + .create_thread_artifact(CreateThreadArtifactParams { + thread_id, + artifact: NewThreadArtifact { + artifact_type: "github/pull_request".to_string(), + payload: serde_json::json!({ + "repoOwner": "openai", + "repoName": "codex", + "number": 910887, + "canonicalUrl": "https://github.com/openai/codex/pull/910887", + }), + }, + }) + .await + .expect("artifact should create"); + + let page = store + .list_threads(ListThreadsParams { + page_size: 10, + cursor: None, + sort_key: ThreadSortKey::CreatedAt, + sort_direction: SortDirection::Desc, + allowed_sources: Vec::new(), + model_providers: None, + cwd_filters: None, + archived: false, + search_term: None, + use_state_db_only: true, + }) + .await + .expect("thread listing"); + + assert_eq!(page.items.len(), 1); + assert_eq!(page.items[0].thread_id, thread_id); + assert_eq!(page.items[0].artifacts.len(), 1); + assert_eq!(page.items[0].artifacts[0].id, artifact.id); + } + #[tokio::test] async fn list_threads_selects_active_or_archived_collection() { let home = TempDir::new().expect("temp dir"); diff --git a/codex-rs/thread-store/src/local/mod.rs b/codex-rs/thread-store/src/local/mod.rs index 07aa5e925f..77b8e8ab77 100644 --- a/codex-rs/thread-store/src/local/mod.rs +++ b/codex-rs/thread-store/src/local/mod.rs @@ -4,6 +4,7 @@ mod helpers; mod list_threads; mod live_writer; mod read_thread; +mod thread_artifacts; mod unarchive_thread; mod update_thread_metadata; @@ -22,13 +23,16 @@ use tokio::sync::Mutex; use crate::AppendThreadItemsParams; use crate::ArchiveThreadParams; +use crate::CreateThreadArtifactParams; use crate::CreateThreadParams; +use crate::ListThreadArtifactsParams; use crate::ListThreadsParams; use crate::LoadThreadHistoryParams; use crate::ReadThreadByRolloutPathParams; use crate::ReadThreadParams; use crate::ResumeThreadParams; use crate::StoredThread; +use crate::StoredThreadArtifact; use crate::StoredThreadHistory; use crate::ThreadPage; use crate::ThreadStore; @@ -249,6 +253,20 @@ impl ThreadStore for LocalThreadStore { list_threads::list_threads(self, params).await } + async fn list_thread_artifacts( + &self, + params: ListThreadArtifactsParams, + ) -> ThreadStoreResult> { + thread_artifacts::list_thread_artifacts(self, params).await + } + + async fn create_thread_artifact( + &self, + params: CreateThreadArtifactParams, + ) -> ThreadStoreResult { + thread_artifacts::create_thread_artifact(self, params).await + } + async fn update_thread_metadata( &self, params: UpdateThreadMetadataParams, diff --git a/codex-rs/thread-store/src/local/read_thread.rs b/codex-rs/thread-store/src/local/read_thread.rs index 8e93d25ae1..42e9e20c6a 100644 --- a/codex-rs/thread-store/src/local/read_thread.rs +++ b/codex-rs/thread-store/src/local/read_thread.rs @@ -30,7 +30,8 @@ pub(super) async fn read_thread( params: ReadThreadParams, ) -> ThreadStoreResult { let thread_id = params.thread_id; - if let Some(metadata) = read_sqlite_metadata(store, thread_id).await + let sqlite_metadata = read_sqlite_metadata(store, thread_id).await; + if let Some(metadata) = sqlite_metadata.clone() && (params.include_archived || (metadata.archived_at.is_none() && !rollout_path_is_archived( @@ -75,6 +76,9 @@ pub(super) async fn read_thread( message: format!("thread {} is archived", thread.thread_id), }); } + if let Some(metadata) = sqlite_metadata { + apply_sqlite_git_info(&mut thread, metadata); + } attach_history_if_requested(&mut thread, params.include_history).await?; Ok(thread) } @@ -109,25 +113,29 @@ pub(super) async fn read_thread_by_rollout_path( }); } if let Some(metadata) = read_sqlite_metadata(store, thread.thread_id).await { - let existing_git_info = thread.git_info.take(); - let (fallback_sha, fallback_branch, fallback_origin_url) = match existing_git_info { - Some(info) => ( - info.commit_hash.map(|sha| sha.0), - info.branch, - info.repository_url, - ), - None => (None, None, None), - }; - thread.git_info = git_info_from_parts( - metadata.git_sha.or(fallback_sha), - metadata.git_branch.or(fallback_branch), - metadata.git_origin_url.or(fallback_origin_url), - ); + apply_sqlite_git_info(&mut thread, metadata); } attach_history_if_requested(&mut thread, include_history).await?; Ok(thread) } +fn apply_sqlite_git_info(thread: &mut StoredThread, metadata: ThreadMetadata) { + let existing_git_info = thread.git_info.take(); + let (fallback_sha, fallback_branch, fallback_origin_url) = match existing_git_info { + Some(info) => ( + info.commit_hash.map(|sha| sha.0), + info.branch, + info.repository_url, + ), + None => (None, None, None), + }; + thread.git_info = git_info_from_parts( + metadata.git_sha.or(fallback_sha), + metadata.git_branch.or(fallback_branch), + metadata.git_origin_url.or(fallback_origin_url), + ); +} + fn resolve_requested_rollout_path( store: &LocalThreadStore, rollout_path: std::path::PathBuf, @@ -242,6 +250,8 @@ async fn read_thread_from_rollout_path( { set_thread_name_from_title(&mut thread, title); } + thread.artifacts = + super::thread_artifacts::rollout_thread_artifacts(path.as_path(), thread.thread_id).await?; Ok(thread) } @@ -313,6 +323,11 @@ async fn stored_thread_from_sqlite_metadata( metadata.git_branch, metadata.git_origin_url, ), + artifacts: metadata + .artifacts + .into_iter() + .map(super::thread_artifacts::stored_artifact_from_state) + .collect(), approval_mode: parse_or_default(&metadata.approval_mode, AskForApproval::OnRequest), sandbox_policy: parse_or_default( &metadata.sandbox_policy, @@ -334,9 +349,16 @@ async fn stored_thread_from_session_meta( message: format!("failed to read thread {}: {err}", path.display()), })?; let archived = rollout_path_is_archived(store.config.codex_home.as_path(), path.as_path()); - Ok(stored_thread_from_meta_line( - store, meta_line, path, archived, - )) + let mut thread = stored_thread_from_meta_line(store, meta_line, path, archived); + thread.artifacts = super::thread_artifacts::rollout_thread_artifacts( + thread + .rollout_path + .as_deref() + .expect("session-meta thread should retain rollout path"), + thread.thread_id, + ) + .await?; + Ok(thread) } fn stored_thread_from_meta_line( @@ -375,6 +397,7 @@ fn stored_thread_from_meta_line( agent_role: meta_line.meta.agent_role, agent_path: meta_line.meta.agent_path, git_info: meta_line.git, + artifacts: Vec::new(), approval_mode: AskForApproval::OnRequest, sandbox_policy: SandboxPolicy::new_read_only_policy(), token_usage: None, @@ -418,6 +441,8 @@ mod tests { use uuid::Uuid; use super::*; + use crate::CreateThreadArtifactParams; + use crate::NewThreadArtifact; use crate::ThreadStore; use crate::local::LocalThreadStore; use crate::local::test_support::test_config; @@ -657,7 +682,6 @@ mod tests { .upsert_thread(&metadata) .await .expect("state db upsert should succeed"); - let thread = store .read_thread(ReadThreadParams { thread_id, @@ -670,6 +694,58 @@ mod tests { assert_eq!(thread.name, Some("Saved title".to_string())); } + #[tokio::test] + async fn read_thread_preserves_sqlite_artifacts_with_rollout_preview() { + let home = TempDir::new().expect("temp dir"); + let config = test_config(home.path()); + let uuid = Uuid::from_u128(225); + let thread_id = ThreadId::from_string(&uuid.to_string()).expect("valid thread id"); + let rollout_path = + write_session_file(home.path(), "2025-01-03T12-00-00", uuid).expect("session file"); + let runtime = codex_state::StateRuntime::init( + config.sqlite_home.clone(), + config.default_model_provider_id.clone(), + ) + .await + .expect("state db should initialize"); + let store = LocalThreadStore::new(config.clone(), Some(runtime.clone())); + let mut builder = + ThreadMetadataBuilder::new(thread_id, rollout_path, Utc::now(), SessionSource::Cli); + builder.model_provider = Some(config.default_model_provider_id.clone()); + runtime + .upsert_thread(&builder.build(config.default_model_provider_id.as_str())) + .await + .expect("state db upsert should succeed"); + let artifact = store + .create_thread_artifact(CreateThreadArtifactParams { + thread_id, + artifact: NewThreadArtifact { + artifact_type: "github/pull_request".to_string(), + payload: serde_json::json!({ + "repoOwner": "openai", + "repoName": "codex", + "number": 910887, + "canonicalUrl": "https://github.com/openai/codex/pull/910887", + }), + }, + }) + .await + .expect("artifact should create"); + + let thread = store + .read_thread(ReadThreadParams { + thread_id, + include_archived: false, + include_history: false, + }) + .await + .expect("read thread"); + + assert_eq!(thread.preview, "Hello from user"); + assert_eq!(thread.artifacts.len(), 1); + assert_eq!(thread.artifacts[0].id, artifact.id); + } + #[tokio::test] async fn read_thread_preserves_rollout_cwd_when_sqlite_metadata_exists() { let home = TempDir::new().expect("temp dir"); @@ -868,6 +944,20 @@ mod tests { .upsert_thread(&metadata) .await .expect("state db upsert should succeed"); + let artifact = store + .create_thread_artifact(CreateThreadArtifactParams { + thread_id, + artifact: NewThreadArtifact { + artifact_type: "github/pull_request".to_string(), + payload: serde_json::json!({ + "repoOwner": "openai", + "repoName": "codex", + "number": 910887, + }), + }, + }) + .await + .expect("artifact should create"); let thread = store .read_thread(ReadThreadParams { @@ -882,9 +972,11 @@ mod tests { assert_eq!(thread.rollout_path, Some(rollout_path)); assert_eq!(thread.preview, "Hello from user"); assert_eq!(thread.model_provider, config.default_model_provider_id); + assert_eq!(thread.artifacts.len(), 1); + assert_eq!(thread.artifacts[0].id, artifact.id); let history = thread.history.expect("history should load"); assert_eq!(history.thread_id, thread_id); - assert_eq!(history.items.len(), 2); + assert_eq!(history.items.len(), 3); } #[tokio::test] diff --git a/codex-rs/thread-store/src/local/thread_artifacts.rs b/codex-rs/thread-store/src/local/thread_artifacts.rs new file mode 100644 index 0000000000..fe61e7041e --- /dev/null +++ b/codex-rs/thread-store/src/local/thread_artifacts.rs @@ -0,0 +1,265 @@ +use std::collections::HashMap; +use std::path::Path; + +use super::LocalThreadStore; +use super::live_writer; +use super::update_thread_metadata::ResolvedRolloutPath; +use super::update_thread_metadata::resolve_rollout_path; +use crate::CreateThreadArtifactParams; +use crate::ListThreadArtifactsParams; +use crate::NewThreadArtifact; +use crate::StoredThreadArtifact; +use crate::ThreadStoreError; +use crate::ThreadStoreResult; +use chrono::Utc; +use codex_protocol::ThreadId; +use codex_protocol::protocol::RolloutItem; +use codex_protocol::protocol::ThreadArtifact as RolloutThreadArtifact; +use codex_rollout::RolloutRecorder; +use codex_rollout::append_rollout_item_to_path; +use codex_rollout::read_session_meta_line; + +pub(super) async fn list_thread_artifacts( + store: &LocalThreadStore, + params: ListThreadArtifactsParams, +) -> ThreadStoreResult> { + let state_db = state_db(store, params.thread_id).await?; + state_db + .get_thread(params.thread_id) + .await + .map(|thread| { + thread + .map(|metadata| metadata.artifacts) + .unwrap_or_default() + .into_iter() + .map(stored_artifact_from_state) + .collect() + }) + .map_err(|err| ThreadStoreError::Internal { + message: format!( + "failed to list thread artifacts for {}: {err}", + params.thread_id + ), + }) +} + +pub(super) async fn create_thread_artifact( + store: &LocalThreadStore, + params: CreateThreadArtifactParams, +) -> ThreadStoreResult { + let resolved_rollout_path = writable_rollout_path(store, params.thread_id).await?; + let (artifact_type, payload) = state_artifact_kind(params.artifact); + let artifact_id = uuid::Uuid::now_v7().to_string(); + let artifact = RolloutThreadArtifact { + created_at: Utc::now().timestamp(), + artifact_type, + payload, + }; + write_rollout_thread_artifacts( + &resolved_rollout_path, + params.thread_id, + HashMap::from([(artifact_id.clone(), Some(artifact.clone()))]), + ) + .await?; + let stored_artifact = stored_artifact_from_rollout(artifact_id, artifact); + append_state_thread_artifact(store, params.thread_id, &stored_artifact).await?; + Ok(stored_artifact) +} + +async fn state_db( + store: &LocalThreadStore, + thread_id: ThreadId, +) -> ThreadStoreResult { + store + .state_db() + .await + .ok_or_else(|| ThreadStoreError::Internal { + message: format!("sqlite state db unavailable for thread {thread_id}"), + }) +} + +async fn append_state_thread_artifact( + store: &LocalThreadStore, + thread_id: ThreadId, + artifact: &StoredThreadArtifact, +) -> ThreadStoreResult<()> { + let state_db = state_db(store, thread_id).await?; + let created_at = + chrono::DateTime::::from_timestamp(artifact.created_at, 0).ok_or_else(|| { + ThreadStoreError::Internal { + message: format!( + "failed to cache thread artifact {} for {thread_id}: invalid timestamp {}", + artifact.id, artifact.created_at + ), + } + })?; + let updated = state_db + .append_thread_artifact(codex_state::ThreadArtifact { + thread_id, + id: artifact.id.clone(), + created_at, + artifact_type: artifact.artifact_type.clone(), + payload: artifact.payload.clone(), + }) + .await + .map_err(|err| ThreadStoreError::Internal { + message: format!("failed to cache thread artifact for {thread_id}: {err}"), + })?; + if !updated { + return Err(ThreadStoreError::ThreadNotFound { thread_id }); + } + Ok(()) +} + +pub(super) fn stored_artifact_from_state( + artifact: codex_state::ThreadArtifact, +) -> StoredThreadArtifact { + StoredThreadArtifact { + id: artifact.id, + created_at: artifact.created_at.timestamp(), + artifact_type: artifact.artifact_type, + payload: artifact.payload, + } +} + +pub(super) fn stored_artifact_from_rollout( + id: String, + artifact: RolloutThreadArtifact, +) -> StoredThreadArtifact { + StoredThreadArtifact { + id, + created_at: artifact.created_at, + artifact_type: artifact.artifact_type, + payload: artifact.payload, + } +} + +fn state_artifact_kind(artifact: NewThreadArtifact) -> (String, serde_json::Value) { + (artifact.artifact_type, artifact.payload) +} + +async fn writable_rollout_path( + store: &LocalThreadStore, + thread_id: ThreadId, +) -> ThreadStoreResult { + if live_writer::rollout_path(store, thread_id).await.is_ok() { + live_writer::persist_thread(store, thread_id).await?; + } + resolve_rollout_path(store, thread_id, /*include_archived*/ false) + .await + .map_err(|err| match err { + ThreadStoreError::InvalidRequest { .. } => { + ThreadStoreError::ThreadNotFound { thread_id } + } + other => other, + }) +} + +pub(super) async fn rollout_thread_artifacts( + rollout_path: &Path, + thread_id: ThreadId, +) -> ThreadStoreResult> { + let (items, _, _) = RolloutRecorder::load_rollout_items(rollout_path) + .await + .map_err(|err| ThreadStoreError::Internal { + message: format!( + "failed to read thread artifacts for {thread_id} from {}: {err}", + rollout_path.display() + ), + })?; + let mut artifacts = Vec::::new(); + for item in items { + let RolloutItem::SessionMeta(meta_line) = item else { + continue; + }; + if meta_line.meta.id != thread_id { + continue; + } + for (id, artifact) in meta_line.artifacts { + match artifact { + Some(artifact) => { + let updated_artifact = stored_artifact_from_rollout(id.clone(), artifact); + if let Some(existing) = artifacts.iter_mut().find(|existing| existing.id == id) + { + *existing = updated_artifact; + } else { + artifacts.push(updated_artifact); + } + } + None => artifacts.retain(|artifact| artifact.id != id), + } + } + } + Ok(artifacts) +} + +async fn write_rollout_thread_artifacts( + resolved_rollout_path: &ResolvedRolloutPath, + thread_id: ThreadId, + artifacts: HashMap>, +) -> ThreadStoreResult<()> { + let mut session_meta = read_session_meta_line(resolved_rollout_path.path.as_path()) + .await + .map_err(|err| ThreadStoreError::Internal { + message: format!("failed to set thread artifacts for {thread_id}: {err}"), + })?; + if session_meta.meta.id != thread_id { + return Err(ThreadStoreError::Internal { + message: format!( + "failed to set thread artifacts: rollout session metadata id mismatch: expected {thread_id}, found {}", + session_meta.meta.id + ), + }); + } + session_meta.git = None; + session_meta.artifacts = artifacts; + append_rollout_item_to_path( + resolved_rollout_path.path.as_path(), + &RolloutItem::SessionMeta(session_meta), + ) + .await + .map_err(|err| ThreadStoreError::Internal { + message: format!("failed to set thread artifacts for {thread_id}: {err}"), + })?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ThreadStore; + use crate::local::test_support::test_config; + use tempfile::TempDir; + + #[tokio::test] + async fn create_thread_artifact_returns_thread_not_found_for_missing_thread() { + let home = TempDir::new().expect("temp dir"); + let config = test_config(home.path()); + let runtime = codex_state::StateRuntime::init( + config.sqlite_home.clone(), + config.default_model_provider_id.clone(), + ) + .await + .expect("state db should initialize"); + let store = LocalThreadStore::new(config, Some(runtime)); + let thread_id = + ThreadId::from_string("00000000-0000-0000-0000-000000000123").expect("valid thread id"); + + let err = store + .create_thread_artifact(CreateThreadArtifactParams { + thread_id, + artifact: NewThreadArtifact { + artifact_type: "github/pull_request".to_string(), + payload: serde_json::json!({ "number": 123 }), + }, + }) + .await + .expect_err("missing thread should fail"); + + assert!(matches!( + err, + ThreadStoreError::ThreadNotFound { thread_id: missing_thread_id } + if missing_thread_id == thread_id + )); + } +} diff --git a/codex-rs/thread-store/src/local/update_thread_metadata.rs b/codex-rs/thread-store/src/local/update_thread_metadata.rs index ef69cfa8b8..3d4bfa7d8c 100644 --- a/codex-rs/thread-store/src/local/update_thread_metadata.rs +++ b/codex-rs/thread-store/src/local/update_thread_metadata.rs @@ -23,9 +23,9 @@ use crate::ThreadStoreResult; use crate::UpdateThreadMetadataParams; use crate::local::read_thread; -struct ResolvedRolloutPath { - path: PathBuf, - archived: bool, +pub(super) struct ResolvedRolloutPath { + pub(super) path: PathBuf, + pub(super) archived: bool, } pub(super) async fn update_thread_metadata( @@ -114,13 +114,13 @@ pub(super) async fn update_thread_metadata( apply_thread_git_info_to_rollout( resolved_rollout_path.path.as_path(), thread_id, - sha, - branch, - origin_url, + &sha, + &branch, + &origin_url, memory_mode.as_deref(), ) .await?; - apply_thread_git_info(store, thread_id, sha, branch, origin_url).await?; + apply_thread_git_info(store, thread_id, &sha, &branch, &origin_url).await?; } let mut thread = match read_thread::read_thread( @@ -228,6 +228,7 @@ async fn apply_thread_git_info_to_rollout( branch: branch.clone(), repository_url: origin_url.clone(), }); + session_meta.artifacts.clear(); session_meta.meta.memory_mode = memory_mode.map(str::to_string); append_rollout_item_to_path(rollout_path, &RolloutItem::SessionMeta(session_meta)) .await @@ -285,6 +286,7 @@ async fn apply_thread_memory_mode( // Memory-mode updates should not modify git metadata. The rollout replay // code will preserve the latest prior git marker when this field is absent. session_meta.git = None; + session_meta.artifacts.clear(); session_meta.meta.memory_mode = Some(memory_mode_as_str(memory_mode).to_string()); append_rollout_item_to_path(rollout_path, &RolloutItem::SessionMeta(session_meta)) .await @@ -300,7 +302,7 @@ fn memory_mode_as_str(mode: ThreadMemoryMode) -> &'static str { } } -async fn resolve_rollout_path( +pub(super) async fn resolve_rollout_path( store: &LocalThreadStore, thread_id: ThreadId, include_archived: bool, @@ -362,7 +364,10 @@ mod tests { use uuid::Uuid; use super::*; + use crate::CreateThreadArtifactParams; use crate::GitInfoPatch; + use crate::ListThreadArtifactsParams; + use crate::NewThreadArtifact; use crate::ResumeThreadParams; use crate::ThreadEventPersistenceMode; use crate::ThreadMetadataPatch; @@ -456,6 +461,17 @@ mod tests { .expect("state db should initialize"); let store = LocalThreadStore::new(config.clone(), Some(runtime.clone())); + let artifact = store + .create_thread_artifact(CreateThreadArtifactParams { + thread_id, + artifact: NewThreadArtifact { + artifact_type: "github/pull_request".to_string(), + payload: json!({ "number": 910887 }), + }, + }) + .await + .expect("create artifact"); + store .update_thread_metadata(UpdateThreadMetadataParams { thread_id, @@ -491,6 +507,7 @@ mod tests { assert_eq!(appended["type"], "session_meta"); assert_eq!(appended["payload"]["memory_mode"], "disabled"); assert_eq!(appended["payload"]["git"]["branch"], "feature"); + assert!(appended["payload"].get("artifacts").is_none()); codex_rollout::state_db::reconcile_rollout( Some(runtime.as_ref()), @@ -507,6 +524,13 @@ mod tests { .await .expect("thread memory mode should be readable"); assert_eq!(memory_mode.as_deref(), Some("disabled")); + assert_eq!( + store + .list_thread_artifacts(ListThreadArtifactsParams { thread_id }) + .await + .expect("thread artifacts should remain readable"), + vec![artifact] + ); } #[tokio::test] diff --git a/codex-rs/thread-store/src/store.rs b/codex-rs/thread-store/src/store.rs index bd5e3e7d3d..423b94f792 100644 --- a/codex-rs/thread-store/src/store.rs +++ b/codex-rs/thread-store/src/store.rs @@ -4,9 +4,11 @@ use std::any::Any; use crate::AppendThreadItemsParams; use crate::ArchiveThreadParams; +use crate::CreateThreadArtifactParams; use crate::CreateThreadParams; use crate::ItemPage; use crate::ListItemsParams; +use crate::ListThreadArtifactsParams; use crate::ListThreadsParams; use crate::ListTurnsParams; use crate::LoadThreadHistoryParams; @@ -14,6 +16,7 @@ use crate::ReadThreadByRolloutPathParams; use crate::ReadThreadParams; use crate::ResumeThreadParams; use crate::StoredThread; +use crate::StoredThreadArtifact; use crate::StoredThreadHistory; use crate::ThreadPage; use crate::ThreadStoreError; @@ -72,6 +75,18 @@ pub trait ThreadStore: Any + Send + Sync { /// Lists stored threads matching the supplied filters. async fn list_threads(&self, params: ListThreadsParams) -> ThreadStoreResult; + /// Lists artifacts associated with a thread in insertion order. + async fn list_thread_artifacts( + &self, + params: ListThreadArtifactsParams, + ) -> ThreadStoreResult>; + + /// Creates an artifact and returns the persisted record. + async fn create_thread_artifact( + &self, + params: CreateThreadArtifactParams, + ) -> ThreadStoreResult; + /// Lists turns within a stored thread. async fn list_turns(&self, _params: ListTurnsParams) -> ThreadStoreResult { Err(ThreadStoreError::Unsupported { diff --git a/codex-rs/thread-store/src/types.rs b/codex-rs/thread-store/src/types.rs index 1fed7bc829..9c2dd84a8c 100644 --- a/codex-rs/thread-store/src/types.rs +++ b/codex-rs/thread-store/src/types.rs @@ -16,6 +16,7 @@ use codex_protocol::protocol::ThreadSource; use codex_protocol::protocol::TokenUsage; use serde::Deserialize; use serde::Serialize; +use serde_json::Value as JsonValue; /// Controls how many event variants should be persisted for future replay. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] @@ -335,6 +336,8 @@ pub struct StoredThread { pub agent_path: Option, /// Optional Git metadata captured for the thread. pub git_info: Option, + /// Model-managed artifacts attached to the thread. + pub artifacts: Vec, /// Approval mode captured for the thread. pub approval_mode: AskForApproval, /// Sandbox policy captured for the thread. @@ -347,6 +350,39 @@ pub struct StoredThread { pub history: Option, } +/// Store-owned model-managed thread artifact. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct StoredThreadArtifact { + /// Stable artifact id. + pub id: String, + /// Unix timestamp in seconds when the artifact was created. + pub created_at: i64, + /// Namespaced artifact kind chosen by the client. + pub artifact_type: String, + /// Opaque client-defined artifact payload. + pub payload: JsonValue, +} + +/// Parameters for listing thread artifacts. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct ListThreadArtifactsParams { + pub thread_id: ThreadId, +} + +/// Parameters for creating a thread artifact. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct CreateThreadArtifactParams { + pub thread_id: ThreadId, + pub artifact: NewThreadArtifact, +} + +/// New artifact payload accepted by the store. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct NewThreadArtifact { + pub artifact_type: String, + pub payload: JsonValue, +} + /// Optional field patch where omission leaves a value unchanged and `Some(None)` clears it. pub type OptionalStringPatch = Option>; diff --git a/codex-rs/tui/src/app/app_server_event_targets.rs b/codex-rs/tui/src/app/app_server_event_targets.rs index d535bf8e3d..7fc94375bc 100644 --- a/codex-rs/tui/src/app/app_server_event_targets.rs +++ b/codex-rs/tui/src/app/app_server_event_targets.rs @@ -62,6 +62,9 @@ pub(super) fn server_notification_thread_target( ServerNotification::ThreadGoalCleared(notification) => { Some(notification.thread_id.as_str()) } + ServerNotification::ThreadArtifactsUpdated(notification) => { + Some(notification.thread_id.as_str()) + } ServerNotification::TurnStarted(notification) => Some(notification.thread_id.as_str()), ServerNotification::HookStarted(notification) => Some(notification.thread_id.as_str()), ServerNotification::TurnCompleted(notification) => Some(notification.thread_id.as_str()), diff --git a/codex-rs/tui/src/app/loaded_threads.rs b/codex-rs/tui/src/app/loaded_threads.rs index 0ab8e14ee3..e42094b392 100644 --- a/codex-rs/tui/src/app/loaded_threads.rs +++ b/codex-rs/tui/src/app/loaded_threads.rs @@ -134,6 +134,7 @@ mod tests { agent_nickname: None, agent_role: None, git_info: None, + artifacts: Vec::new(), name: None, turns: Vec::new(), } diff --git a/codex-rs/tui/src/app/tests.rs b/codex-rs/tui/src/app/tests.rs index d2a942bb42..82f3d9902f 100644 --- a/codex-rs/tui/src/app/tests.rs +++ b/codex-rs/tui/src/app/tests.rs @@ -3009,6 +3009,7 @@ async fn inactive_thread_started_notification_initializes_replay_session() -> Re agent_nickname: Some("Robie".to_string()), agent_role: Some("explorer".to_string()), git_info: None, + artifacts: Vec::new(), name: Some("agent thread".to_string()), turns: Vec::new(), }, @@ -3092,6 +3093,7 @@ async fn inactive_thread_started_notification_preserves_primary_model_when_path_ agent_nickname: Some("Robie".to_string()), agent_role: Some("explorer".to_string()), git_info: None, + artifacts: Vec::new(), name: Some("agent thread".to_string()), turns: Vec::new(), }, @@ -3148,6 +3150,7 @@ async fn thread_read_session_state_does_not_reuse_primary_permission_profile() { agent_nickname: None, agent_role: None, git_info: None, + artifacts: Vec::new(), name: Some("read thread".to_string()), turns: Vec::new(), }; @@ -5161,6 +5164,7 @@ async fn thread_rollback_response_discards_queued_active_thread_events() { agent_nickname: None, agent_role: None, git_info: None, + artifacts: Vec::new(), name: None, turns: Vec::new(), }, diff --git a/codex-rs/tui/src/app/thread_session_state.rs b/codex-rs/tui/src/app/thread_session_state.rs index 4f5f48cfc8..582de16b41 100644 --- a/codex-rs/tui/src/app/thread_session_state.rs +++ b/codex-rs/tui/src/app/thread_session_state.rs @@ -335,6 +335,7 @@ mod tests { agent_nickname: None, agent_role: None, git_info: None, + artifacts: Vec::new(), name: Some("read thread".to_string()), turns: Vec::new(), }; diff --git a/codex-rs/tui/src/app_server_session.rs b/codex-rs/tui/src/app_server_session.rs index 73c4c807ef..9fd1de4fd1 100644 --- a/codex-rs/tui/src/app_server_session.rs +++ b/codex-rs/tui/src/app_server_session.rs @@ -1959,6 +1959,7 @@ mod tests { agent_nickname: None, agent_role: None, git_info: None, + artifacts: Vec::new(), name: None, turns: vec![Turn { id: "turn-1".to_string(), diff --git a/codex-rs/tui/src/chatwidget/protocol.rs b/codex-rs/tui/src/chatwidget/protocol.rs index f0e3efea0e..7c7ff27ee8 100644 --- a/codex-rs/tui/src/chatwidget/protocol.rs +++ b/codex-rs/tui/src/chatwidget/protocol.rs @@ -219,6 +219,7 @@ impl ChatWidget { | ServerNotification::ThreadStatusChanged(_) | ServerNotification::ThreadArchived(_) | ServerNotification::ThreadUnarchived(_) + | ServerNotification::ThreadArtifactsUpdated(_) | ServerNotification::RawResponseItemCompleted(_) | ServerNotification::CommandExecOutputDelta(_) | ServerNotification::ProcessOutputDelta(_) diff --git a/codex-rs/tui/src/lib.rs b/codex-rs/tui/src/lib.rs index df84dfc45a..0039175fa3 100644 --- a/codex-rs/tui/src/lib.rs +++ b/codex-rs/tui/src/lib.rs @@ -1990,6 +1990,7 @@ mod tests { let session_meta = serde_json::to_value(codex_protocol::protocol::SessionMetaLine { meta: session_meta, git: None, + artifacts: std::collections::HashMap::new(), })?; let lines = [ serde_json::json!({ diff --git a/codex-rs/tui/src/resume_picker.rs b/codex-rs/tui/src/resume_picker.rs index 66e26d9778..da0cf8ac72 100644 --- a/codex-rs/tui/src/resume_picker.rs +++ b/codex-rs/tui/src/resume_picker.rs @@ -5700,6 +5700,7 @@ session_picker_view = "dense" agent_nickname: None, agent_role: None, git_info: None, + artifacts: Vec::new(), name: Some(String::from("Named thread")), turns: Vec::new(), }; @@ -5734,6 +5735,7 @@ session_picker_view = "dense" agent_nickname: None, agent_role: None, git_info: None, + artifacts: Vec::new(), name: None, turns: vec![codex_app_server_protocol::Turn { id: String::from("turn-1"), @@ -5801,6 +5803,7 @@ session_picker_view = "dense" agent_nickname: None, agent_role: None, git_info: None, + artifacts: Vec::new(), name: None, turns: vec![codex_app_server_protocol::Turn { id: String::from("turn-1"), @@ -5858,6 +5861,7 @@ session_picker_view = "dense" agent_nickname: None, agent_role: None, git_info: None, + artifacts: Vec::new(), name: None, turns: vec![codex_app_server_protocol::Turn { id: String::from("turn-1"),