mirror of
https://github.com/openai/codex.git
synced 2026-11-07 08:34:29 +00:00
Add thread section management APIs (#36380)
## What changed - Add `threadSection/create`, `threadSection/update`, and `threadSection/delete` app-server methods, with generated protocol schemas and TypeScript bindings. - Persist custom sections in SQLite with stable UUIDv7 identities, trim and validate display names, and prevent renaming or deleting the built-in pinned section. - Delete sections transactionally and return their active and archived threads to the unsectioned list. - Serialize section mutations with section listing so concurrent requests observe ordered updates. ## Testing - Cover section persistence across restarts, ordered renames, member cleanup on deletion, invalid requests, unsupported stores, and thread-store/state-runtime behavior. GitOrigin-RevId: dc9a4be283c21dbc93ab1829eafc4825d8df6575
This commit is contained in:
1 parent
d62353a312
commit
c42ea41ee0
38 files changed
+1862
-62
No files matched your search
@@ -4590,6 +4590,32 @@
|
|||||||
],
|
],
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"ThreadSectionCreateParams": {
|
||||||
|
"description": "Parameters for creating an independently persisted thread section.",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"description": "The user-visible name of the section.",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"ThreadSectionDeleteParams": {
|
||||||
|
"description": "Parameters for deleting an independently persisted thread section.",
|
||||||
|
"properties": {
|
||||||
|
"sectionId": {
|
||||||
|
"description": "The stable, server-generated identity of the section to delete.",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"sectionId"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
"ThreadSectionListParams": {
|
"ThreadSectionListParams": {
|
||||||
"description": "Parameters for listing independently persisted thread sections.",
|
"description": "Parameters for listing independently persisted thread sections.",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -4640,6 +4666,24 @@
|
|||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
"ThreadSectionUpdateParams": {
|
||||||
|
"description": "Parameters for updating an independently persisted thread section.",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"description": "The updated user-visible name of the section.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"sectionId": {
|
||||||
|
"description": "The stable, server-generated identity of the section to update.",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"name",
|
||||||
|
"sectionId"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
"ThreadSetNameParams": {
|
"ThreadSetNameParams": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"name": {
|
"name": {
|
||||||
@@ -5751,6 +5795,78 @@
|
|||||||
"title": "ThreadSection/listRequest",
|
"title": "ThreadSection/listRequest",
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"$ref": "#/definitions/RequestId"
|
||||||
|
},
|
||||||
|
"method": {
|
||||||
|
"enum": [
|
||||||
|
"threadSection/create"
|
||||||
|
],
|
||||||
|
"title": "ThreadSection/createRequestMethod",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"$ref": "#/definitions/ThreadSectionCreateParams"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"method",
|
||||||
|
"params"
|
||||||
|
],
|
||||||
|
"title": "ThreadSection/createRequest",
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"$ref": "#/definitions/RequestId"
|
||||||
|
},
|
||||||
|
"method": {
|
||||||
|
"enum": [
|
||||||
|
"threadSection/update"
|
||||||
|
],
|
||||||
|
"title": "ThreadSection/updateRequestMethod",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"$ref": "#/definitions/ThreadSectionUpdateParams"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"method",
|
||||||
|
"params"
|
||||||
|
],
|
||||||
|
"title": "ThreadSection/updateRequest",
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"$ref": "#/definitions/RequestId"
|
||||||
|
},
|
||||||
|
"method": {
|
||||||
|
"enum": [
|
||||||
|
"threadSection/delete"
|
||||||
|
],
|
||||||
|
"title": "ThreadSection/deleteRequestMethod",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"$ref": "#/definitions/ThreadSectionDeleteParams"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"method",
|
||||||
|
"params"
|
||||||
|
],
|
||||||
|
"title": "ThreadSection/deleteRequest",
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"properties": {
|
"properties": {
|
||||||
"id": {
|
"id": {
|
||||||
|
|||||||
+156
@@ -684,6 +684,78 @@
|
|||||||
"title": "ThreadSection/listRequest",
|
"title": "ThreadSection/listRequest",
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"$ref": "#/definitions/v2/RequestId"
|
||||||
|
},
|
||||||
|
"method": {
|
||||||
|
"enum": [
|
||||||
|
"threadSection/create"
|
||||||
|
],
|
||||||
|
"title": "ThreadSection/createRequestMethod",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"$ref": "#/definitions/v2/ThreadSectionCreateParams"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"method",
|
||||||
|
"params"
|
||||||
|
],
|
||||||
|
"title": "ThreadSection/createRequest",
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"$ref": "#/definitions/v2/RequestId"
|
||||||
|
},
|
||||||
|
"method": {
|
||||||
|
"enum": [
|
||||||
|
"threadSection/update"
|
||||||
|
],
|
||||||
|
"title": "ThreadSection/updateRequestMethod",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"$ref": "#/definitions/v2/ThreadSectionUpdateParams"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"method",
|
||||||
|
"params"
|
||||||
|
],
|
||||||
|
"title": "ThreadSection/updateRequest",
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"$ref": "#/definitions/v2/RequestId"
|
||||||
|
},
|
||||||
|
"method": {
|
||||||
|
"enum": [
|
||||||
|
"threadSection/delete"
|
||||||
|
],
|
||||||
|
"title": "ThreadSection/deleteRequestMethod",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"$ref": "#/definitions/v2/ThreadSectionDeleteParams"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"method",
|
||||||
|
"params"
|
||||||
|
],
|
||||||
|
"title": "ThreadSection/deleteRequest",
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"properties": {
|
"properties": {
|
||||||
"id": {
|
"id": {
|
||||||
@@ -20593,6 +20665,56 @@
|
|||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
"ThreadSectionCreateParams": {
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"description": "Parameters for creating an independently persisted thread section.",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"description": "The user-visible name of the section.",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"title": "ThreadSectionCreateParams",
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"ThreadSectionCreateResponse": {
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"description": "The independently persisted section created by the server.",
|
||||||
|
"properties": {
|
||||||
|
"section": {
|
||||||
|
"$ref": "#/definitions/v2/ThreadSection"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"section"
|
||||||
|
],
|
||||||
|
"title": "ThreadSectionCreateResponse",
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"ThreadSectionDeleteParams": {
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"description": "Parameters for deleting an independently persisted thread section.",
|
||||||
|
"properties": {
|
||||||
|
"sectionId": {
|
||||||
|
"description": "The stable, server-generated identity of the section to delete.",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"sectionId"
|
||||||
|
],
|
||||||
|
"title": "ThreadSectionDeleteParams",
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"ThreadSectionDeleteResponse": {
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"description": "Successful deletion does not return additional section data.",
|
||||||
|
"title": "ThreadSectionDeleteResponse",
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
"ThreadSectionListParams": {
|
"ThreadSectionListParams": {
|
||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
"description": "Parameters for listing independently persisted thread sections.",
|
"description": "Parameters for listing independently persisted thread sections.",
|
||||||
@@ -20676,6 +20798,40 @@
|
|||||||
"title": "ThreadSectionMoveResponse",
|
"title": "ThreadSectionMoveResponse",
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
"ThreadSectionUpdateParams": {
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"description": "Parameters for updating an independently persisted thread section.",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"description": "The updated user-visible name of the section.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"sectionId": {
|
||||||
|
"description": "The stable, server-generated identity of the section to update.",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"name",
|
||||||
|
"sectionId"
|
||||||
|
],
|
||||||
|
"title": "ThreadSectionUpdateParams",
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"ThreadSectionUpdateResponse": {
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"description": "The independently persisted section after its name is updated.",
|
||||||
|
"properties": {
|
||||||
|
"section": {
|
||||||
|
"$ref": "#/definitions/v2/ThreadSection"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"section"
|
||||||
|
],
|
||||||
|
"title": "ThreadSectionUpdateResponse",
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
"ThreadSetNameParams": {
|
"ThreadSetNameParams": {
|
||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
+156
@@ -1834,6 +1834,78 @@
|
|||||||
"title": "ThreadSection/listRequest",
|
"title": "ThreadSection/listRequest",
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"$ref": "#/definitions/RequestId"
|
||||||
|
},
|
||||||
|
"method": {
|
||||||
|
"enum": [
|
||||||
|
"threadSection/create"
|
||||||
|
],
|
||||||
|
"title": "ThreadSection/createRequestMethod",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"$ref": "#/definitions/ThreadSectionCreateParams"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"method",
|
||||||
|
"params"
|
||||||
|
],
|
||||||
|
"title": "ThreadSection/createRequest",
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"$ref": "#/definitions/RequestId"
|
||||||
|
},
|
||||||
|
"method": {
|
||||||
|
"enum": [
|
||||||
|
"threadSection/update"
|
||||||
|
],
|
||||||
|
"title": "ThreadSection/updateRequestMethod",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"$ref": "#/definitions/ThreadSectionUpdateParams"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"method",
|
||||||
|
"params"
|
||||||
|
],
|
||||||
|
"title": "ThreadSection/updateRequest",
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"$ref": "#/definitions/RequestId"
|
||||||
|
},
|
||||||
|
"method": {
|
||||||
|
"enum": [
|
||||||
|
"threadSection/delete"
|
||||||
|
],
|
||||||
|
"title": "ThreadSection/deleteRequestMethod",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"$ref": "#/definitions/ThreadSectionDeleteParams"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"method",
|
||||||
|
"params"
|
||||||
|
],
|
||||||
|
"title": "ThreadSection/deleteRequest",
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"properties": {
|
"properties": {
|
||||||
"id": {
|
"id": {
|
||||||
@@ -18357,6 +18429,56 @@
|
|||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
"ThreadSectionCreateParams": {
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"description": "Parameters for creating an independently persisted thread section.",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"description": "The user-visible name of the section.",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"title": "ThreadSectionCreateParams",
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"ThreadSectionCreateResponse": {
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"description": "The independently persisted section created by the server.",
|
||||||
|
"properties": {
|
||||||
|
"section": {
|
||||||
|
"$ref": "#/definitions/ThreadSection"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"section"
|
||||||
|
],
|
||||||
|
"title": "ThreadSectionCreateResponse",
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"ThreadSectionDeleteParams": {
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"description": "Parameters for deleting an independently persisted thread section.",
|
||||||
|
"properties": {
|
||||||
|
"sectionId": {
|
||||||
|
"description": "The stable, server-generated identity of the section to delete.",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"sectionId"
|
||||||
|
],
|
||||||
|
"title": "ThreadSectionDeleteParams",
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"ThreadSectionDeleteResponse": {
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"description": "Successful deletion does not return additional section data.",
|
||||||
|
"title": "ThreadSectionDeleteResponse",
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
"ThreadSectionListParams": {
|
"ThreadSectionListParams": {
|
||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
"description": "Parameters for listing independently persisted thread sections.",
|
"description": "Parameters for listing independently persisted thread sections.",
|
||||||
@@ -18440,6 +18562,40 @@
|
|||||||
"title": "ThreadSectionMoveResponse",
|
"title": "ThreadSectionMoveResponse",
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
"ThreadSectionUpdateParams": {
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"description": "Parameters for updating an independently persisted thread section.",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"description": "The updated user-visible name of the section.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"sectionId": {
|
||||||
|
"description": "The stable, server-generated identity of the section to update.",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"name",
|
||||||
|
"sectionId"
|
||||||
|
],
|
||||||
|
"title": "ThreadSectionUpdateParams",
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"ThreadSectionUpdateResponse": {
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"description": "The independently persisted section after its name is updated.",
|
||||||
|
"properties": {
|
||||||
|
"section": {
|
||||||
|
"$ref": "#/definitions/ThreadSection"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"section"
|
||||||
|
],
|
||||||
|
"title": "ThreadSectionUpdateResponse",
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
"ThreadSetNameParams": {
|
"ThreadSetNameParams": {
|
||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"description": "Parameters for creating an independently persisted thread section.",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"description": "The user-visible name of the section.",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"title": "ThreadSectionCreateParams",
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"definitions": {
|
||||||
|
"ThreadSection": {
|
||||||
|
"description": "An independently persisted, user-visible thread section.",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"description": "Opaque UUIDv7 identity that remains stable when the section is renamed.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"description": "The current user-visible section name.",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "The independently persisted section created by the server.",
|
||||||
|
"properties": {
|
||||||
|
"section": {
|
||||||
|
"$ref": "#/definitions/ThreadSection"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"section"
|
||||||
|
],
|
||||||
|
"title": "ThreadSectionCreateResponse",
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"description": "Parameters for deleting an independently persisted thread section.",
|
||||||
|
"properties": {
|
||||||
|
"sectionId": {
|
||||||
|
"description": "The stable, server-generated identity of the section to delete.",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"sectionId"
|
||||||
|
],
|
||||||
|
"title": "ThreadSectionDeleteParams",
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"description": "Successful deletion does not return additional section data.",
|
||||||
|
"title": "ThreadSectionDeleteResponse",
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"description": "Parameters for updating an independently persisted thread section.",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"description": "The updated user-visible name of the section.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"sectionId": {
|
||||||
|
"description": "The stable, server-generated identity of the section to update.",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"name",
|
||||||
|
"sectionId"
|
||||||
|
],
|
||||||
|
"title": "ThreadSectionUpdateParams",
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"definitions": {
|
||||||
|
"ThreadSection": {
|
||||||
|
"description": "An independently persisted, user-visible thread section.",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"description": "Opaque UUIDv7 identity that remains stable when the section is renamed.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"description": "The current user-visible section name.",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "The independently persisted section after its name is updated.",
|
||||||
|
"properties": {
|
||||||
|
"section": {
|
||||||
|
"$ref": "#/definitions/ThreadSection"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"section"
|
||||||
|
],
|
||||||
|
"title": "ThreadSectionUpdateResponse",
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -78,8 +78,11 @@ import type { ThreadMetadataUpdateParams } from "./v2/ThreadMetadataUpdateParams
|
|||||||
import type { ThreadReadParams } from "./v2/ThreadReadParams";
|
import type { ThreadReadParams } from "./v2/ThreadReadParams";
|
||||||
import type { ThreadResumeParams } from "./v2/ThreadResumeParams";
|
import type { ThreadResumeParams } from "./v2/ThreadResumeParams";
|
||||||
import type { ThreadRollbackParams } from "./v2/ThreadRollbackParams";
|
import type { ThreadRollbackParams } from "./v2/ThreadRollbackParams";
|
||||||
|
import type { ThreadSectionCreateParams } from "./v2/ThreadSectionCreateParams";
|
||||||
|
import type { ThreadSectionDeleteParams } from "./v2/ThreadSectionDeleteParams";
|
||||||
import type { ThreadSectionListParams } from "./v2/ThreadSectionListParams";
|
import type { ThreadSectionListParams } from "./v2/ThreadSectionListParams";
|
||||||
import type { ThreadSectionMoveParams } from "./v2/ThreadSectionMoveParams";
|
import type { ThreadSectionMoveParams } from "./v2/ThreadSectionMoveParams";
|
||||||
|
import type { ThreadSectionUpdateParams } from "./v2/ThreadSectionUpdateParams";
|
||||||
import type { ThreadSetNameParams } from "./v2/ThreadSetNameParams";
|
import type { ThreadSetNameParams } from "./v2/ThreadSetNameParams";
|
||||||
import type { ThreadShellCommandParams } from "./v2/ThreadShellCommandParams";
|
import type { ThreadShellCommandParams } from "./v2/ThreadShellCommandParams";
|
||||||
import type { ThreadStartParams } from "./v2/ThreadStartParams";
|
import type { ThreadStartParams } from "./v2/ThreadStartParams";
|
||||||
@@ -93,4 +96,4 @@ import type { WindowsSandboxSetupStartParams } from "./v2/WindowsSandboxSetupSta
|
|||||||
/**
|
/**
|
||||||
* Request from the client to the server.
|
* 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/delete", id: RequestId, params: ThreadDeleteParams, } | { "method": "thread/unsubscribe", id: RequestId, params: ThreadUnsubscribeParams, } | { "method": "thread/name/set", id: RequestId, params: ThreadSetNameParams, } | { "method": "thread/goal/set", id: RequestId, params: ThreadGoalSetParams, } | { "method": "thread/goal/get", id: RequestId, params: ThreadGoalGetParams, } | { "method": "thread/goal/clear", id: RequestId, params: ThreadGoalClearParams, } | { "method": "thread/metadata/update", id: RequestId, params: ThreadMetadataUpdateParams, } | { "method": "thread/section/move", id: RequestId, params: ThreadSectionMoveParams, } | { "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": "threadSection/list", id: RequestId, params: ThreadSectionListParams, } | { "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": "skills/extraRoots/set", id: RequestId, params: SkillsExtraRootsSetParams, } | { "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/installed", id: RequestId, params: PluginInstalledParams, } | { "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/checkout", id: RequestId, params: PluginShareCheckoutParams, } | { "method": "plugin/share/delete", id: RequestId, params: PluginShareDeleteParams, } | { "method": "app/read", id: RequestId, params: AppsReadParams, } | { "method": "app/list", id: RequestId, params: AppsListParams, } | { "method": "app/installed", id: RequestId, params: AppsInstalledParams, } | { "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": "permissionProfile/list", id: RequestId, params: PermissionProfileListParams, } | { "method": "experimentalFeature/enablement/set", id: ReLine truncated
|
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/delete", id: RequestId, params: ThreadDeleteParams, } | { "method": "thread/unsubscribe", id: RequestId, params: ThreadUnsubscribeParams, } | { "method": "thread/name/set", id: RequestId, params: ThreadSetNameParams, } | { "method": "thread/goal/set", id: RequestId, params: ThreadGoalSetParams, } | { "method": "thread/goal/get", id: RequestId, params: ThreadGoalGetParams, } | { "method": "thread/goal/clear", id: RequestId, params: ThreadGoalClearParams, } | { "method": "thread/metadata/update", id: RequestId, params: ThreadMetadataUpdateParams, } | { "method": "thread/section/move", id: RequestId, params: ThreadSectionMoveParams, } | { "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": "threadSection/list", id: RequestId, params: ThreadSectionListParams, } | { "method": "threadSection/create", id: RequestId, params: ThreadSectionCreateParams, } | { "method": "threadSection/update", id: RequestId, params: ThreadSectionUpdateParams, } | { "method": "threadSection/delete", id: RequestId, params: ThreadSectionDeleteParams, } | { "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": "skills/extraRoots/set", id: RequestId, params: SkillsExtraRootsSetParams, } | { "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/installed", id: RequestId, params: PluginInstalledParams, } | { "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/checkout", id: RequestId, params: PluginShareCheckoutParams, } | { "method": "plugin/share/delete", id: RequestId, params: PluginShareDeleteParams, } | { "method": "app/read", id: RequestId, params: AppsReadParams, } | { "method": "app/list", id: RequestId, params: AppsListParams, } | { "method": "app/installed", id: RequestId, params: AppsInstalledParams, } | { "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: ModelProviderCapabiLine truncated
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parameters for creating an independently persisted thread section.
|
||||||
|
*/
|
||||||
|
export type ThreadSectionCreateParams = {
|
||||||
|
/**
|
||||||
|
* The user-visible name of the section.
|
||||||
|
*/
|
||||||
|
name: string, };
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
// 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 { ThreadSection } from "./ThreadSection";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The independently persisted section created by the server.
|
||||||
|
*/
|
||||||
|
export type ThreadSectionCreateResponse = { section: ThreadSection, };
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parameters for deleting an independently persisted thread section.
|
||||||
|
*/
|
||||||
|
export type ThreadSectionDeleteParams = {
|
||||||
|
/**
|
||||||
|
* The stable, server-generated identity of the section to delete.
|
||||||
|
*/
|
||||||
|
sectionId: string, };
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Successful deletion does not return additional section data.
|
||||||
|
*/
|
||||||
|
export type ThreadSectionDeleteResponse = Record<string, never>;
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parameters for updating an independently persisted thread section.
|
||||||
|
*/
|
||||||
|
export type ThreadSectionUpdateParams = {
|
||||||
|
/**
|
||||||
|
* The stable, server-generated identity of the section to update.
|
||||||
|
*/
|
||||||
|
sectionId: string,
|
||||||
|
/**
|
||||||
|
* The updated user-visible name of the section.
|
||||||
|
*/
|
||||||
|
name: string, };
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
// 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 { ThreadSection } from "./ThreadSection";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The independently persisted section after its name is updated.
|
||||||
|
*/
|
||||||
|
export type ThreadSectionUpdateResponse = { section: ThreadSection, };
|
||||||
@@ -474,10 +474,16 @@ export type { ThreadRollbackResponse } from "./ThreadRollbackResponse";
|
|||||||
export type { ThreadSearchResult } from "./ThreadSearchResult";
|
export type { ThreadSearchResult } from "./ThreadSearchResult";
|
||||||
export type { ThreadSearchSortKey } from "./ThreadSearchSortKey";
|
export type { ThreadSearchSortKey } from "./ThreadSearchSortKey";
|
||||||
export type { ThreadSection } from "./ThreadSection";
|
export type { ThreadSection } from "./ThreadSection";
|
||||||
|
export type { ThreadSectionCreateParams } from "./ThreadSectionCreateParams";
|
||||||
|
export type { ThreadSectionCreateResponse } from "./ThreadSectionCreateResponse";
|
||||||
|
export type { ThreadSectionDeleteParams } from "./ThreadSectionDeleteParams";
|
||||||
|
export type { ThreadSectionDeleteResponse } from "./ThreadSectionDeleteResponse";
|
||||||
export type { ThreadSectionListParams } from "./ThreadSectionListParams";
|
export type { ThreadSectionListParams } from "./ThreadSectionListParams";
|
||||||
export type { ThreadSectionListResponse } from "./ThreadSectionListResponse";
|
export type { ThreadSectionListResponse } from "./ThreadSectionListResponse";
|
||||||
export type { ThreadSectionMoveParams } from "./ThreadSectionMoveParams";
|
export type { ThreadSectionMoveParams } from "./ThreadSectionMoveParams";
|
||||||
export type { ThreadSectionMoveResponse } from "./ThreadSectionMoveResponse";
|
export type { ThreadSectionMoveResponse } from "./ThreadSectionMoveResponse";
|
||||||
|
export type { ThreadSectionUpdateParams } from "./ThreadSectionUpdateParams";
|
||||||
|
export type { ThreadSectionUpdateResponse } from "./ThreadSectionUpdateResponse";
|
||||||
export type { ThreadSetNameParams } from "./ThreadSetNameParams";
|
export type { ThreadSetNameParams } from "./ThreadSetNameParams";
|
||||||
export type { ThreadSetNameResponse } from "./ThreadSetNameResponse";
|
export type { ThreadSetNameResponse } from "./ThreadSetNameResponse";
|
||||||
export type { ThreadSettings } from "./ThreadSettings";
|
export type { ThreadSettings } from "./ThreadSettings";
|
||||||
|
|||||||
@@ -633,9 +633,24 @@ client_request_definitions! {
|
|||||||
},
|
},
|
||||||
ThreadSectionList => "threadSection/list" {
|
ThreadSectionList => "threadSection/list" {
|
||||||
params: v2::ThreadSectionListParams,
|
params: v2::ThreadSectionListParams,
|
||||||
serialization: None,
|
serialization: global_shared_read("thread-sections"),
|
||||||
response: v2::ThreadSectionListResponse,
|
response: v2::ThreadSectionListResponse,
|
||||||
},
|
},
|
||||||
|
ThreadSectionCreate => "threadSection/create" {
|
||||||
|
params: v2::ThreadSectionCreateParams,
|
||||||
|
serialization: global("thread-sections"),
|
||||||
|
response: v2::ThreadSectionCreateResponse,
|
||||||
|
},
|
||||||
|
ThreadSectionUpdate => "threadSection/update" {
|
||||||
|
params: v2::ThreadSectionUpdateParams,
|
||||||
|
serialization: global("thread-sections"),
|
||||||
|
response: v2::ThreadSectionUpdateResponse,
|
||||||
|
},
|
||||||
|
ThreadSectionDelete => "threadSection/delete" {
|
||||||
|
params: v2::ThreadSectionDeleteParams,
|
||||||
|
serialization: global("thread-sections"),
|
||||||
|
response: v2::ThreadSectionDeleteResponse,
|
||||||
|
},
|
||||||
#[experimental("thread/search")]
|
#[experimental("thread/search")]
|
||||||
ThreadSearch => "thread/search" {
|
ThreadSearch => "thread/search" {
|
||||||
params: v2::ThreadSearchParams,
|
params: v2::ThreadSearchParams,
|
||||||
|
|||||||
@@ -1139,6 +1139,57 @@ pub struct ThreadSectionListResponse {
|
|||||||
pub next_cursor: Option<String>,
|
pub next_cursor: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parameters for creating an independently persisted thread section.
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[ts(export_to = "v2/")]
|
||||||
|
pub struct ThreadSectionCreateParams {
|
||||||
|
/// The user-visible name of the section.
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The independently persisted section created by the server.
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[ts(export_to = "v2/")]
|
||||||
|
pub struct ThreadSectionCreateResponse {
|
||||||
|
pub section: ThreadSection,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parameters for updating an independently persisted thread section.
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[ts(export_to = "v2/")]
|
||||||
|
pub struct ThreadSectionUpdateParams {
|
||||||
|
/// The stable, server-generated identity of the section to update.
|
||||||
|
pub section_id: String,
|
||||||
|
/// The updated user-visible name of the section.
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The independently persisted section after its name is updated.
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[ts(export_to = "v2/")]
|
||||||
|
pub struct ThreadSectionUpdateResponse {
|
||||||
|
pub section: ThreadSection,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parameters for deleting an independently persisted thread section.
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[ts(export_to = "v2/")]
|
||||||
|
pub struct ThreadSectionDeleteParams {
|
||||||
|
/// The stable, server-generated identity of the section to delete.
|
||||||
|
pub section_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Successful deletion does not return additional section data.
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[ts(export_to = "v2/")]
|
||||||
|
pub struct ThreadSectionDeleteResponse {}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, ExperimentalApi)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, ExperimentalApi)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[ts(export_to = "v2/")]
|
#[ts(export_to = "v2/")]
|
||||||
|
|||||||
@@ -145,6 +145,9 @@ Example with notification opt-out:
|
|||||||
- `thread/start`, `thread/resume`, and `thread/fork` responses include the legacy `sandbox` compatibility projection. `instructionSources` lists loaded instruction files using each source environment's native absolute path syntax, including files loaded from remote environments. Experimental clients can read `runtimeWorkspaceRoots` for the thread-scoped runtime roots and `activePermissionProfile` for the named or implicit built-in profile identity/provenance when known. Their deprecated experimental `multiAgentMode` field, and the corresponding thread setting, always report `explicitRequestOnly`; Ultra reasoning effort is the source of proactive multi-agent behavior.
|
- `thread/start`, `thread/resume`, and `thread/fork` responses include the legacy `sandbox` compatibility projection. `instructionSources` lists loaded instruction files using each source environment's native absolute path syntax, including files loaded from remote environments. Experimental clients can read `runtimeWorkspaceRoots` for the thread-scoped runtime roots and `activePermissionProfile` for the named or implicit built-in profile identity/provenance when known. Their deprecated experimental `multiAgentMode` field, and the corresponding thread setting, always report `explicitRequestOnly`; Ultra reasoning effort is the source of proactive multi-agent behavior.
|
||||||
- `thread/list` — page through stored threads; supports cursor-based pagination and optional `modelProviders`, `sourceKinds`, `archived`, `sectionId`, `cwd`, and `searchTerm` filters. Set `sortKey` to `"section_position"` when listing a section in its persisted manual order. Experimental clients can use `parentThreadId` for direct spawned children or `ancestorThreadId` for spawned descendants at any depth; the two filters are mutually exclusive. Review and Guardian threads are not included because they do not participate in that spawn-edge lifecycle. Each returned `thread` includes `status` (`ThreadStatus`), defaulting to `notLoaded` when the thread is not currently loaded. Subagent threads also include `parentThreadId` when the immediate parent is known.
|
- `thread/list` — page through stored threads; supports cursor-based pagination and optional `modelProviders`, `sourceKinds`, `archived`, `sectionId`, `cwd`, and `searchTerm` filters. Set `sortKey` to `"section_position"` when listing a section in its persisted manual order. Experimental clients can use `parentThreadId` for direct spawned children or `ancestorThreadId` for spawned descendants at any depth; the two filters are mutually exclusive. Review and Guardian threads are not included because they do not participate in that spawn-edge lifecycle. Each returned `thread` includes `status` (`ThreadStatus`), defaulting to `notLoaded` when the thread is not currently loaded. Subagent threads also include `parentThreadId` when the immediate parent is known.
|
||||||
- `threadSection/list` — page through independently persisted thread sections and their display names, including sections that do not currently contain any threads.
|
- `threadSection/list` — page through independently persisted thread sections and their display names, including sections that do not currently contain any threads.
|
||||||
|
- `threadSection/create` — create a durable custom section with a server-generated UUID and a nonempty display name; returns its `section`.
|
||||||
|
- `threadSection/update` — rename an existing custom section while retaining its stable UUID; returns the updated `section`. The built-in pinned section cannot be renamed.
|
||||||
|
- `threadSection/delete` — delete an existing custom section and atomically return its member threads to the unsectioned list; returns `{}`. The built-in pinned section cannot be deleted.
|
||||||
- `thread/loaded/list` — list the thread ids currently loaded in memory.
|
- `thread/loaded/list` — list the thread ids currently loaded in memory.
|
||||||
- `thread/read` — read a stored thread by id without resuming it; optionally include turns via `includeTurns`. The returned `thread` includes `status` (`ThreadStatus`), defaulting to `notLoaded` when the thread is not currently loaded. For loaded threads, experimental clients can use `canAcceptDirectInput` to determine whether `turn/start` and `turn/steer` are accepted; unloaded stored threads report `null` when that capability is unavailable.
|
- `thread/read` — read a stored thread by id without resuming it; optionally include turns via `includeTurns`. The returned `thread` includes `status` (`ThreadStatus`), defaulting to `notLoaded` when the thread is not currently loaded. For loaded threads, experimental clients can use `canAcceptDirectInput` to determine whether `turn/start` and `turn/steer` are accepted; unloaded stored threads report `null` when that capability is unavailable.
|
||||||
- `thread/turns/list` — experimental; page through a stored thread’s turn history without resuming it; supports cursor-based pagination with `sortDirection`, `itemsView`, `nextCursor`, and `backwardsCursor`.
|
- `thread/turns/list` — experimental; page through a stored thread’s turn history without resuming it; supports cursor-based pagination with `sortDirection`, `itemsView`, `nextCursor`, and `backwardsCursor`.
|
||||||
|
|||||||
@@ -1120,6 +1120,15 @@ impl MessageProcessor {
|
|||||||
ClientRequest::ThreadSectionList { params, .. } => {
|
ClientRequest::ThreadSectionList { params, .. } => {
|
||||||
self.thread_processor.thread_section_list(params).await
|
self.thread_processor.thread_section_list(params).await
|
||||||
}
|
}
|
||||||
|
ClientRequest::ThreadSectionCreate { params, .. } => {
|
||||||
|
self.thread_processor.thread_section_create(params).await
|
||||||
|
}
|
||||||
|
ClientRequest::ThreadSectionUpdate { params, .. } => {
|
||||||
|
self.thread_processor.thread_section_update(params).await
|
||||||
|
}
|
||||||
|
ClientRequest::ThreadSectionDelete { params, .. } => {
|
||||||
|
self.thread_processor.thread_section_delete(params).await
|
||||||
|
}
|
||||||
ClientRequest::ThreadSettingsUpdate { params, .. } => {
|
ClientRequest::ThreadSettingsUpdate { params, .. } => {
|
||||||
self.turn_processor
|
self.turn_processor
|
||||||
.thread_settings_update(&request_id, params)
|
.thread_settings_update(&request_id, params)
|
||||||
|
|||||||
@@ -540,6 +540,7 @@ mod search;
|
|||||||
mod thread_enrichment;
|
mod thread_enrichment;
|
||||||
mod thread_fork_goal;
|
mod thread_fork_goal;
|
||||||
mod thread_processor;
|
mod thread_processor;
|
||||||
|
mod thread_sections;
|
||||||
mod token_usage_replay;
|
mod token_usage_replay;
|
||||||
mod turn_processor;
|
mod turn_processor;
|
||||||
mod windows_sandbox_processor;
|
mod windows_sandbox_processor;
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ use super::*;
|
|||||||
use crate::error_code::method_not_found;
|
use crate::error_code::method_not_found;
|
||||||
use codex_app_server_protocol::SelectedCapabilityRoot;
|
use codex_app_server_protocol::SelectedCapabilityRoot;
|
||||||
use codex_app_server_protocol::ThreadSection;
|
use codex_app_server_protocol::ThreadSection;
|
||||||
use codex_app_server_protocol::ThreadSectionListParams;
|
|
||||||
use codex_app_server_protocol::ThreadSectionListResponse;
|
|
||||||
use codex_app_server_protocol::ThreadSectionMoveParams;
|
use codex_app_server_protocol::ThreadSectionMoveParams;
|
||||||
use codex_app_server_protocol::ThreadSectionMoveResponse;
|
use codex_app_server_protocol::ThreadSectionMoveResponse;
|
||||||
use codex_extension_api::ExtensionDataInit;
|
use codex_extension_api::ExtensionDataInit;
|
||||||
@@ -16,8 +14,8 @@ use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_DANGER_FULL_ACCESS;
|
|||||||
use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_WORKSPACE;
|
use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_WORKSPACE;
|
||||||
use codex_protocol::protocol::ThreadHistoryMode;
|
use codex_protocol::protocol::ThreadHistoryMode;
|
||||||
|
|
||||||
const THREAD_LIST_DEFAULT_LIMIT: usize = 25;
|
pub(super) const THREAD_LIST_DEFAULT_LIMIT: usize = 25;
|
||||||
const THREAD_LIST_MAX_LIMIT: usize = 100;
|
pub(super) const THREAD_LIST_MAX_LIMIT: usize = 100;
|
||||||
const CODEX_TUI_CLIENT_NAME: &str = "codex-tui";
|
const CODEX_TUI_CLIENT_NAME: &str = "codex-tui";
|
||||||
const THREAD_ROLLBACK_DEPRECATION_SUMMARY: &str =
|
const THREAD_ROLLBACK_DEPRECATION_SUMMARY: &str =
|
||||||
"thread/rollback is deprecated and will be removed soon";
|
"thread/rollback is deprecated and will be removed soon";
|
||||||
@@ -743,39 +741,6 @@ impl ThreadRequestProcessor {
|
|||||||
.map(|response| Some(response.into()))
|
.map(|response| Some(response.into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn thread_section_list(
|
|
||||||
&self,
|
|
||||||
params: ThreadSectionListParams,
|
|
||||||
) -> Result<Option<ClientResponsePayload>, JSONRPCErrorError> {
|
|
||||||
let limit = params
|
|
||||||
.limit
|
|
||||||
.map(|value| value as usize)
|
|
||||||
.unwrap_or(THREAD_LIST_DEFAULT_LIMIT)
|
|
||||||
.clamp(1, THREAD_LIST_MAX_LIMIT);
|
|
||||||
let state_db = self.state_db.clone().ok_or_else(|| {
|
|
||||||
method_not_found("threadSection/list is unavailable without sqlite state")
|
|
||||||
})?;
|
|
||||||
let page = state_db
|
|
||||||
.list_thread_sections(params.cursor.as_deref(), limit)
|
|
||||||
.await
|
|
||||||
.map_err(|err| internal_error(format!("failed to list thread sections: {err}")))?;
|
|
||||||
|
|
||||||
Ok(Some(
|
|
||||||
ThreadSectionListResponse {
|
|
||||||
data: page
|
|
||||||
.sections
|
|
||||||
.into_iter()
|
|
||||||
.map(|section| ThreadSection {
|
|
||||||
id: section.id,
|
|
||||||
name: section.name,
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
next_cursor: page.next_cursor,
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn thread_search(
|
pub(crate) async fn thread_search(
|
||||||
&self,
|
&self,
|
||||||
params: ThreadSearchParams,
|
params: ThreadSearchParams,
|
||||||
|
|||||||
@@ -0,0 +1,195 @@
|
|||||||
|
use super::thread_processor::THREAD_LIST_DEFAULT_LIMIT;
|
||||||
|
use super::thread_processor::THREAD_LIST_MAX_LIMIT;
|
||||||
|
use super::thread_processor::ThreadRequestProcessor;
|
||||||
|
use crate::error_code::internal_error;
|
||||||
|
use crate::error_code::invalid_params;
|
||||||
|
use crate::error_code::method_not_found;
|
||||||
|
use codex_app_server_protocol::ClientResponsePayload;
|
||||||
|
use codex_app_server_protocol::JSONRPCErrorError;
|
||||||
|
use codex_app_server_protocol::ThreadSection;
|
||||||
|
use codex_app_server_protocol::ThreadSectionCreateParams;
|
||||||
|
use codex_app_server_protocol::ThreadSectionCreateResponse;
|
||||||
|
use codex_app_server_protocol::ThreadSectionDeleteParams;
|
||||||
|
use codex_app_server_protocol::ThreadSectionDeleteResponse;
|
||||||
|
use codex_app_server_protocol::ThreadSectionListParams;
|
||||||
|
use codex_app_server_protocol::ThreadSectionListResponse;
|
||||||
|
use codex_app_server_protocol::ThreadSectionUpdateParams;
|
||||||
|
use codex_app_server_protocol::ThreadSectionUpdateResponse;
|
||||||
|
use codex_state::PINNED_THREAD_SECTION_ID;
|
||||||
|
use codex_thread_store::CreateThreadSectionParams as StoreCreateThreadSectionParams;
|
||||||
|
use codex_thread_store::DeleteThreadSectionParams as StoreDeleteThreadSectionParams;
|
||||||
|
use codex_thread_store::ListThreadSectionsParams as StoreListThreadSectionsParams;
|
||||||
|
use codex_thread_store::RenameThreadSectionParams as StoreRenameThreadSectionParams;
|
||||||
|
use codex_thread_store::ThreadStoreError;
|
||||||
|
|
||||||
|
impl ThreadRequestProcessor {
|
||||||
|
pub(crate) async fn thread_section_list(
|
||||||
|
&self,
|
||||||
|
params: ThreadSectionListParams,
|
||||||
|
) -> Result<Option<ClientResponsePayload>, JSONRPCErrorError> {
|
||||||
|
const OPERATION: &str = "threadSection/list";
|
||||||
|
self.ensure_thread_sections_supported(OPERATION)?;
|
||||||
|
let limit = params
|
||||||
|
.limit
|
||||||
|
.map(|value| value as usize)
|
||||||
|
.unwrap_or(THREAD_LIST_DEFAULT_LIMIT)
|
||||||
|
.clamp(1, THREAD_LIST_MAX_LIMIT);
|
||||||
|
let page = self
|
||||||
|
.thread_store
|
||||||
|
.list_thread_sections(StoreListThreadSectionsParams {
|
||||||
|
cursor: params.cursor,
|
||||||
|
limit,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|err| thread_section_store_error(OPERATION, err))?;
|
||||||
|
|
||||||
|
Ok(Some(
|
||||||
|
ThreadSectionListResponse {
|
||||||
|
data: page
|
||||||
|
.sections
|
||||||
|
.into_iter()
|
||||||
|
.map(|section| ThreadSection {
|
||||||
|
id: section.id,
|
||||||
|
name: section.name,
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
next_cursor: page.next_cursor,
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn thread_section_create(
|
||||||
|
&self,
|
||||||
|
params: ThreadSectionCreateParams,
|
||||||
|
) -> Result<Option<ClientResponsePayload>, JSONRPCErrorError> {
|
||||||
|
const OPERATION: &str = "threadSection/create";
|
||||||
|
self.ensure_thread_sections_supported(OPERATION)?;
|
||||||
|
let name = params.name.trim();
|
||||||
|
if name.is_empty() {
|
||||||
|
return Err(invalid_params("section name must not be empty"));
|
||||||
|
}
|
||||||
|
let section = self
|
||||||
|
.thread_store
|
||||||
|
.create_thread_section(StoreCreateThreadSectionParams {
|
||||||
|
name: name.to_string(),
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|err| thread_section_store_error(OPERATION, err))?;
|
||||||
|
|
||||||
|
Ok(Some(
|
||||||
|
ThreadSectionCreateResponse {
|
||||||
|
section: ThreadSection {
|
||||||
|
id: section.id,
|
||||||
|
name: section.name,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn thread_section_update(
|
||||||
|
&self,
|
||||||
|
params: ThreadSectionUpdateParams,
|
||||||
|
) -> Result<Option<ClientResponsePayload>, JSONRPCErrorError> {
|
||||||
|
const OPERATION: &str = "threadSection/update";
|
||||||
|
self.ensure_thread_sections_supported(OPERATION)?;
|
||||||
|
let name = params.name.trim();
|
||||||
|
if name.is_empty() {
|
||||||
|
return Err(invalid_params("section name must not be empty"));
|
||||||
|
}
|
||||||
|
if params.section_id.trim().is_empty() {
|
||||||
|
return Err(invalid_params("sectionId must not be empty"));
|
||||||
|
}
|
||||||
|
if params.section_id == PINNED_THREAD_SECTION_ID {
|
||||||
|
return Err(invalid_params(
|
||||||
|
"the built-in pinned section cannot be renamed",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let section = self
|
||||||
|
.thread_store
|
||||||
|
.rename_thread_section(StoreRenameThreadSectionParams {
|
||||||
|
section_id: params.section_id.clone(),
|
||||||
|
name: name.to_string(),
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|err| thread_section_store_error(OPERATION, err))?
|
||||||
|
.ok_or_else(|| {
|
||||||
|
invalid_params(format!("thread section not found: {}", params.section_id))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(Some(
|
||||||
|
ThreadSectionUpdateResponse {
|
||||||
|
section: ThreadSection {
|
||||||
|
id: section.id,
|
||||||
|
name: section.name,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn thread_section_delete(
|
||||||
|
&self,
|
||||||
|
params: ThreadSectionDeleteParams,
|
||||||
|
) -> Result<Option<ClientResponsePayload>, JSONRPCErrorError> {
|
||||||
|
const OPERATION: &str = "threadSection/delete";
|
||||||
|
self.ensure_thread_sections_supported(OPERATION)?;
|
||||||
|
if params.section_id.trim().is_empty() {
|
||||||
|
return Err(invalid_params("sectionId must not be empty"));
|
||||||
|
}
|
||||||
|
if params.section_id == PINNED_THREAD_SECTION_ID {
|
||||||
|
return Err(invalid_params(
|
||||||
|
"the built-in pinned section cannot be deleted",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let deleted = self
|
||||||
|
.thread_store
|
||||||
|
.delete_thread_section(StoreDeleteThreadSectionParams {
|
||||||
|
section_id: params.section_id.clone(),
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|err| thread_section_store_error(OPERATION, err))?;
|
||||||
|
if !deleted {
|
||||||
|
return Err(invalid_params(format!(
|
||||||
|
"thread section not found: {}",
|
||||||
|
params.section_id
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Some(ThreadSectionDeleteResponse {}.into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ensure_thread_sections_supported(
|
||||||
|
&self,
|
||||||
|
operation: &'static str,
|
||||||
|
) -> Result<(), JSONRPCErrorError> {
|
||||||
|
if self.thread_store.supports_thread_sections() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(unsupported_thread_section_operation(operation))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unsupported_thread_section_operation(operation: &'static str) -> JSONRPCErrorError {
|
||||||
|
method_not_found(format!("{operation} is unavailable without sqlite state"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn thread_section_store_error(
|
||||||
|
operation: &'static str,
|
||||||
|
error: ThreadStoreError,
|
||||||
|
) -> JSONRPCErrorError {
|
||||||
|
match error {
|
||||||
|
ThreadStoreError::Unsupported { .. } => unsupported_thread_section_operation(operation),
|
||||||
|
ThreadStoreError::InvalidRequest { message } => invalid_params(message),
|
||||||
|
error @ (ThreadStoreError::ThreadNotFound { .. }
|
||||||
|
| ThreadStoreError::Conflict { .. }
|
||||||
|
| ThreadStoreError::Internal { .. }) => {
|
||||||
|
let action = operation
|
||||||
|
.strip_prefix("threadSection/")
|
||||||
|
.unwrap_or(operation);
|
||||||
|
internal_error(format!("failed to {action} thread section: {error}"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -87,6 +87,7 @@ mod thread_name_websocket;
|
|||||||
mod thread_read;
|
mod thread_read;
|
||||||
mod thread_resume;
|
mod thread_resume;
|
||||||
mod thread_rollback;
|
mod thread_rollback;
|
||||||
|
mod thread_sections;
|
||||||
mod thread_settings_update;
|
mod thread_settings_update;
|
||||||
mod thread_shell_command;
|
mod thread_shell_command;
|
||||||
mod thread_start;
|
mod thread_start;
|
||||||
|
|||||||
@@ -37,7 +37,10 @@ use codex_app_server_protocol::ThreadHistoryMode;
|
|||||||
use codex_app_server_protocol::ThreadListParams;
|
use codex_app_server_protocol::ThreadListParams;
|
||||||
use codex_app_server_protocol::ThreadListResponse;
|
use codex_app_server_protocol::ThreadListResponse;
|
||||||
use codex_app_server_protocol::ThreadResumeParams;
|
use codex_app_server_protocol::ThreadResumeParams;
|
||||||
|
use codex_app_server_protocol::ThreadSectionCreateParams;
|
||||||
|
use codex_app_server_protocol::ThreadSectionDeleteParams;
|
||||||
use codex_app_server_protocol::ThreadSectionListParams;
|
use codex_app_server_protocol::ThreadSectionListParams;
|
||||||
|
use codex_app_server_protocol::ThreadSectionUpdateParams;
|
||||||
use codex_app_server_protocol::ThreadStartParams;
|
use codex_app_server_protocol::ThreadStartParams;
|
||||||
use codex_app_server_protocol::ThreadStartResponse;
|
use codex_app_server_protocol::ThreadStartResponse;
|
||||||
use codex_app_server_protocol::TurnStartParams;
|
use codex_app_server_protocol::TurnStartParams;
|
||||||
@@ -55,6 +58,7 @@ use codex_protocol::ThreadId;
|
|||||||
use codex_protocol::models::BaseInstructions;
|
use codex_protocol::models::BaseInstructions;
|
||||||
use codex_protocol::protocol::SessionSource;
|
use codex_protocol::protocol::SessionSource;
|
||||||
use codex_protocol::protocol::ThreadMemoryMode;
|
use codex_protocol::protocol::ThreadMemoryMode;
|
||||||
|
use codex_state::PINNED_THREAD_SECTION_ID;
|
||||||
use codex_thread_store::CreateThreadParams as StoreCreateThreadParams;
|
use codex_thread_store::CreateThreadParams as StoreCreateThreadParams;
|
||||||
use codex_thread_store::InMemoryThreadStore;
|
use codex_thread_store::InMemoryThreadStore;
|
||||||
use codex_thread_store::ThreadPersistenceMetadata;
|
use codex_thread_store::ThreadPersistenceMetadata;
|
||||||
@@ -68,36 +72,92 @@ use uuid::Uuid;
|
|||||||
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
|
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn thread_section_list_without_sqlite_returns_method_not_found() -> Result<()> {
|
async fn thread_section_operations_without_sqlite_return_method_not_found() -> Result<()> {
|
||||||
let codex_home = TempDir::new()?;
|
let codex_home = TempDir::new()?;
|
||||||
let store_id = Uuid::new_v4().to_string();
|
let store_id = Uuid::new_v4().to_string();
|
||||||
create_config_toml_with_thread_store(codex_home.path(), "http://127.0.0.1:1", &store_id)?;
|
create_config_toml_with_thread_store(codex_home.path(), "http://127.0.0.1:1", &store_id)?;
|
||||||
let _in_memory_store = InMemoryThreadStoreId { store_id };
|
let _in_memory_store = InMemoryThreadStoreId { store_id };
|
||||||
let client = start_in_process_server(codex_home.path()).await?;
|
let client = start_in_process_server(codex_home.path()).await?;
|
||||||
|
|
||||||
let error = client
|
let section_id = Uuid::now_v7().to_string();
|
||||||
.request(ClientRequest::ThreadSectionList {
|
|
||||||
|
for request in [
|
||||||
|
ClientRequest::ThreadSectionList {
|
||||||
request_id: RequestId::Integer(1),
|
request_id: RequestId::Integer(1),
|
||||||
params: ThreadSectionListParams::default(),
|
params: ThreadSectionListParams::default(),
|
||||||
})
|
},
|
||||||
.await?
|
ClientRequest::ThreadSectionCreate {
|
||||||
.expect_err("section discovery requires sqlite state");
|
request_id: RequestId::Integer(2),
|
||||||
|
params: ThreadSectionCreateParams {
|
||||||
|
name: "Work".to_string(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ClientRequest::ThreadSectionUpdate {
|
||||||
|
request_id: RequestId::Integer(3),
|
||||||
|
params: ThreadSectionUpdateParams {
|
||||||
|
section_id: section_id.clone(),
|
||||||
|
name: "Projects".to_string(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ClientRequest::ThreadSectionDelete {
|
||||||
|
request_id: RequestId::Integer(4),
|
||||||
|
params: ThreadSectionDeleteParams { section_id },
|
||||||
|
},
|
||||||
|
ClientRequest::ThreadSectionCreate {
|
||||||
|
request_id: RequestId::Integer(5),
|
||||||
|
params: ThreadSectionCreateParams {
|
||||||
|
name: " ".to_string(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ClientRequest::ThreadSectionUpdate {
|
||||||
|
request_id: RequestId::Integer(6),
|
||||||
|
params: ThreadSectionUpdateParams {
|
||||||
|
section_id: " ".to_string(),
|
||||||
|
name: "Work".to_string(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ClientRequest::ThreadSectionUpdate {
|
||||||
|
request_id: RequestId::Integer(7),
|
||||||
|
params: ThreadSectionUpdateParams {
|
||||||
|
section_id: PINNED_THREAD_SECTION_ID.to_string(),
|
||||||
|
name: "Pinned again".to_string(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ClientRequest::ThreadSectionDelete {
|
||||||
|
request_id: RequestId::Integer(8),
|
||||||
|
params: ThreadSectionDeleteParams {
|
||||||
|
section_id: " ".to_string(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ClientRequest::ThreadSectionDelete {
|
||||||
|
request_id: RequestId::Integer(9),
|
||||||
|
params: ThreadSectionDeleteParams {
|
||||||
|
section_id: PINNED_THREAD_SECTION_ID.to_string(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ClientRequest::ThreadSectionUpdate {
|
||||||
|
request_id: RequestId::Integer(10),
|
||||||
|
params: ThreadSectionUpdateParams {
|
||||||
|
section_id: PINNED_THREAD_SECTION_ID.to_string(),
|
||||||
|
name: " ".to_string(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
] {
|
||||||
|
let method = request.method_name();
|
||||||
|
let error = client
|
||||||
|
.request(request)
|
||||||
|
.await?
|
||||||
|
.expect_err("section management requires sqlite state");
|
||||||
|
|
||||||
assert_eq!(error.code, -32601);
|
assert_eq!(error.code, -32601);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
error.message,
|
error.message,
|
||||||
"threadSection/list is unavailable without sqlite state"
|
format!("{method} is unavailable without sqlite state")
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
client.shutdown().await?;
|
client.shutdown().await?;
|
||||||
assert!(
|
assert_no_local_persistence_artifacts(codex_home.path())?;
|
||||||
!codex_home.path().join("state_5.sqlite").exists(),
|
|
||||||
"section discovery must not create local SQLite state"
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
!codex_home.path().join("sessions").exists(),
|
|
||||||
"section discovery must not create local thread persistence"
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -466,14 +526,13 @@ fn assert_no_local_persistence_artifacts(codex_home: &Path) -> Result<()> {
|
|||||||
"non-local thread persistence should not create sqlite artifacts: {sqlite_artifacts:?}"
|
"non-local thread persistence should not create sqlite artifacts: {sqlite_artifacts:?}"
|
||||||
);
|
);
|
||||||
let mut entries = codex_home_entries(codex_home)?;
|
let mut entries = codex_home_entries(codex_home)?;
|
||||||
// Bazel test runs may initialize shell snapshot storage under codex_home.
|
// Host startup may leave sandbox migration markers, and Bazel test runs may
|
||||||
// That is not thread persistence; keep the assertion focused on rollout,
|
// initialize shell snapshot storage. Neither is thread persistence.
|
||||||
// session, sqlite, and other unexpected thread-store artifacts.
|
entries.remove(".sandbox_migration");
|
||||||
entries.remove("shell_snapshots");
|
entries.remove("shell_snapshots");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
entries,
|
entries,
|
||||||
BTreeSet::from([
|
BTreeSet::from([
|
||||||
".sandbox_migration".to_string(),
|
|
||||||
"config.toml".to_string(),
|
"config.toml".to_string(),
|
||||||
"installation_id".to_string(),
|
"installation_id".to_string(),
|
||||||
"skills".to_string(),
|
"skills".to_string(),
|
||||||
|
|||||||
@@ -0,0 +1,360 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
use app_test_support::MockResponsesConfig;
|
||||||
|
use app_test_support::TestAppServer;
|
||||||
|
use app_test_support::create_fake_rollout;
|
||||||
|
use app_test_support::create_mock_responses_server_repeating_assistant;
|
||||||
|
use codex_app_server::INVALID_PARAMS_ERROR_CODE;
|
||||||
|
use codex_app_server_protocol::ClientRequest;
|
||||||
|
use codex_app_server_protocol::JSONRPCError;
|
||||||
|
use codex_app_server_protocol::RequestId;
|
||||||
|
use codex_app_server_protocol::ThreadArchiveParams;
|
||||||
|
use codex_app_server_protocol::ThreadArchiveResponse;
|
||||||
|
use codex_app_server_protocol::ThreadListResponse;
|
||||||
|
use codex_app_server_protocol::ThreadReadParams;
|
||||||
|
use codex_app_server_protocol::ThreadReadResponse;
|
||||||
|
use codex_app_server_protocol::ThreadSection;
|
||||||
|
use codex_app_server_protocol::ThreadSectionCreateParams;
|
||||||
|
use codex_app_server_protocol::ThreadSectionCreateResponse;
|
||||||
|
use codex_app_server_protocol::ThreadSectionDeleteParams;
|
||||||
|
use codex_app_server_protocol::ThreadSectionDeleteResponse;
|
||||||
|
use codex_app_server_protocol::ThreadSectionListParams;
|
||||||
|
use codex_app_server_protocol::ThreadSectionListResponse;
|
||||||
|
use codex_app_server_protocol::ThreadSectionMoveParams;
|
||||||
|
use codex_app_server_protocol::ThreadSectionMoveResponse;
|
||||||
|
use codex_app_server_protocol::ThreadSectionUpdateParams;
|
||||||
|
use codex_app_server_protocol::ThreadSectionUpdateResponse;
|
||||||
|
use codex_app_server_protocol::ThreadUnarchiveParams;
|
||||||
|
use codex_app_server_protocol::ThreadUnarchiveResponse;
|
||||||
|
use codex_features::Feature;
|
||||||
|
use codex_state::PINNED_THREAD_SECTION_ID;
|
||||||
|
use codex_state::PINNED_THREAD_SECTION_NAME;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
use serde_json::Value;
|
||||||
|
use serde_json::json;
|
||||||
|
use tempfile::TempDir;
|
||||||
|
use tokio::time::timeout;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
|
||||||
|
|
||||||
|
async fn section_request_error(
|
||||||
|
server: &mut TestAppServer,
|
||||||
|
method: &str,
|
||||||
|
params: Value,
|
||||||
|
) -> Result<JSONRPCError> {
|
||||||
|
let request_id = server.send_raw_request(method, Some(params)).await?;
|
||||||
|
timeout(
|
||||||
|
DEFAULT_READ_TIMEOUT,
|
||||||
|
server.read_stream_until_error_message(RequestId::Integer(request_id)),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn custom_sections_remain_discoverable_across_ordered_updates_and_restart() -> Result<()> {
|
||||||
|
let responses = create_mock_responses_server_repeating_assistant("Done").await;
|
||||||
|
let codex_home = TempDir::new()?;
|
||||||
|
MockResponsesConfig::new(&responses.uri())
|
||||||
|
.enable_feature(Feature::Sqlite)
|
||||||
|
.write(codex_home.path())?;
|
||||||
|
let mut server = TestAppServer::builder()
|
||||||
|
.with_codex_home(codex_home.path())
|
||||||
|
.build_initialized()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let created: ThreadSectionCreateResponse = server
|
||||||
|
.request(|request_id| ClientRequest::ThreadSectionCreate {
|
||||||
|
request_id,
|
||||||
|
params: ThreadSectionCreateParams {
|
||||||
|
name: " Work ".to_string(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
assert_eq!(created.section.name, "Work");
|
||||||
|
assert_eq!(Uuid::parse_str(&created.section.id)?.get_version_num(), 7);
|
||||||
|
|
||||||
|
let retained: ThreadSectionCreateResponse = server
|
||||||
|
.request(|request_id| ClientRequest::ThreadSectionCreate {
|
||||||
|
request_id,
|
||||||
|
params: ThreadSectionCreateParams {
|
||||||
|
name: "Personal".to_string(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
let discovered: ThreadSectionListResponse = server
|
||||||
|
.request(|request_id| ClientRequest::ThreadSectionList {
|
||||||
|
request_id,
|
||||||
|
params: ThreadSectionListParams {
|
||||||
|
cursor: None,
|
||||||
|
limit: Some(20),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
assert_eq!(discovered.data.len(), 3);
|
||||||
|
assert!(discovered.data.contains(&created.section));
|
||||||
|
assert!(discovered.data.contains(&retained.section));
|
||||||
|
|
||||||
|
let first_rename = server
|
||||||
|
.send_raw_request(
|
||||||
|
"threadSection/update",
|
||||||
|
Some(json!({ "sectionId": created.section.id, "name": "Queued work" })),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
let second_rename = server
|
||||||
|
.send_raw_request(
|
||||||
|
"threadSection/update",
|
||||||
|
Some(json!({ "sectionId": created.section.id, "name": " Projects " })),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
let listed_after_renames = server
|
||||||
|
.send_raw_request("threadSection/list", Some(json!({ "limit": 20 })))
|
||||||
|
.await?;
|
||||||
|
let _: ThreadSectionUpdateResponse =
|
||||||
|
timeout(DEFAULT_READ_TIMEOUT, server.read_response(first_rename)).await??;
|
||||||
|
let renamed: ThreadSectionUpdateResponse =
|
||||||
|
timeout(DEFAULT_READ_TIMEOUT, server.read_response(second_rename)).await??;
|
||||||
|
let observed: ThreadSectionListResponse = timeout(
|
||||||
|
DEFAULT_READ_TIMEOUT,
|
||||||
|
server.read_response(listed_after_renames),
|
||||||
|
)
|
||||||
|
.await??;
|
||||||
|
assert_eq!(
|
||||||
|
renamed.section,
|
||||||
|
ThreadSection {
|
||||||
|
id: created.section.id,
|
||||||
|
name: "Projects".to_string(),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert!(observed.data.contains(&renamed.section));
|
||||||
|
|
||||||
|
drop(server);
|
||||||
|
let mut restarted = TestAppServer::builder()
|
||||||
|
.with_codex_home(codex_home.path())
|
||||||
|
.build_initialized()
|
||||||
|
.await?;
|
||||||
|
let persisted: ThreadSectionListResponse = restarted
|
||||||
|
.request(|request_id| ClientRequest::ThreadSectionList {
|
||||||
|
request_id,
|
||||||
|
params: ThreadSectionListParams {
|
||||||
|
cursor: None,
|
||||||
|
limit: Some(20),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
assert_eq!(
|
||||||
|
persisted.data,
|
||||||
|
vec![
|
||||||
|
ThreadSection {
|
||||||
|
id: PINNED_THREAD_SECTION_ID.to_string(),
|
||||||
|
name: PINNED_THREAD_SECTION_NAME.to_string(),
|
||||||
|
},
|
||||||
|
renamed.section,
|
||||||
|
retained.section,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn deleting_custom_sections_unassigns_active_and_archived_members() -> Result<()> {
|
||||||
|
let responses = create_mock_responses_server_repeating_assistant("Done").await;
|
||||||
|
let codex_home = TempDir::new()?;
|
||||||
|
MockResponsesConfig::new(&responses.uri())
|
||||||
|
.enable_feature(Feature::Sqlite)
|
||||||
|
.write(codex_home.path())?;
|
||||||
|
let first_thread = create_fake_rollout(
|
||||||
|
codex_home.path(),
|
||||||
|
"2025-01-06T08-00-00",
|
||||||
|
"2025-01-06T08:00:00Z",
|
||||||
|
"First thread",
|
||||||
|
Some("mock_provider"),
|
||||||
|
/*git_info*/ None,
|
||||||
|
)?;
|
||||||
|
let archived_thread = create_fake_rollout(
|
||||||
|
codex_home.path(),
|
||||||
|
"2025-01-06T09-00-00",
|
||||||
|
"2025-01-06T09:00:00Z",
|
||||||
|
"Archived thread",
|
||||||
|
Some("mock_provider"),
|
||||||
|
/*git_info*/ None,
|
||||||
|
)?;
|
||||||
|
let mut server = TestAppServer::builder()
|
||||||
|
.with_codex_home(codex_home.path())
|
||||||
|
.build_initialized()
|
||||||
|
.await?;
|
||||||
|
let request_id = server
|
||||||
|
.send_raw_request("thread/list", Some(json!({ "limit": 20 })))
|
||||||
|
.await?;
|
||||||
|
let _: ThreadListResponse =
|
||||||
|
timeout(DEFAULT_READ_TIMEOUT, server.read_response(request_id)).await??;
|
||||||
|
|
||||||
|
let created: ThreadSectionCreateResponse = server
|
||||||
|
.request(|request_id| ClientRequest::ThreadSectionCreate {
|
||||||
|
request_id,
|
||||||
|
params: ThreadSectionCreateParams {
|
||||||
|
name: "Work".to_string(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
for thread_id in [&first_thread, &archived_thread] {
|
||||||
|
let _: ThreadSectionMoveResponse = server
|
||||||
|
.request(|request_id| ClientRequest::ThreadSectionMove {
|
||||||
|
request_id,
|
||||||
|
params: ThreadSectionMoveParams {
|
||||||
|
thread_id: thread_id.clone(),
|
||||||
|
section_id: Some(created.section.id.clone()),
|
||||||
|
before_thread_id: None,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
let _: ThreadArchiveResponse = server
|
||||||
|
.request(|request_id| ClientRequest::ThreadArchive {
|
||||||
|
request_id,
|
||||||
|
params: ThreadArchiveParams {
|
||||||
|
thread_id: archived_thread.clone(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let renamed: ThreadSectionUpdateResponse = server
|
||||||
|
.request(|request_id| ClientRequest::ThreadSectionUpdate {
|
||||||
|
request_id,
|
||||||
|
params: ThreadSectionUpdateParams {
|
||||||
|
section_id: created.section.id.clone(),
|
||||||
|
name: "Projects".to_string(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
let renamed_member: ThreadReadResponse = server
|
||||||
|
.request(|request_id| ClientRequest::ThreadRead {
|
||||||
|
request_id,
|
||||||
|
params: ThreadReadParams {
|
||||||
|
thread_id: first_thread.clone(),
|
||||||
|
include_turns: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
assert_eq!(renamed_member.thread.section, Some(renamed.section));
|
||||||
|
|
||||||
|
let _: ThreadSectionDeleteResponse = server
|
||||||
|
.request(|request_id| ClientRequest::ThreadSectionDelete {
|
||||||
|
request_id,
|
||||||
|
params: ThreadSectionDeleteParams {
|
||||||
|
section_id: created.section.id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
let unsectioned: ThreadReadResponse = server
|
||||||
|
.request(|request_id| ClientRequest::ThreadRead {
|
||||||
|
request_id,
|
||||||
|
params: ThreadReadParams {
|
||||||
|
thread_id: first_thread,
|
||||||
|
include_turns: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
assert_eq!(
|
||||||
|
(
|
||||||
|
unsectioned.thread.section,
|
||||||
|
unsectioned.thread.section_entered_at
|
||||||
|
),
|
||||||
|
(None, None)
|
||||||
|
);
|
||||||
|
let restored: ThreadUnarchiveResponse = server
|
||||||
|
.request(|request_id| ClientRequest::ThreadUnarchive {
|
||||||
|
request_id,
|
||||||
|
params: ThreadUnarchiveParams {
|
||||||
|
thread_id: archived_thread.clone(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
assert_eq!(
|
||||||
|
(restored.thread.section, restored.thread.section_entered_at),
|
||||||
|
(None, None)
|
||||||
|
);
|
||||||
|
|
||||||
|
drop(server);
|
||||||
|
let mut restarted = TestAppServer::builder()
|
||||||
|
.with_codex_home(codex_home.path())
|
||||||
|
.build_initialized()
|
||||||
|
.await?;
|
||||||
|
let restored_after_restart: ThreadReadResponse = restarted
|
||||||
|
.request(|request_id| ClientRequest::ThreadRead {
|
||||||
|
request_id,
|
||||||
|
params: ThreadReadParams {
|
||||||
|
thread_id: archived_thread,
|
||||||
|
include_turns: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
assert_eq!(
|
||||||
|
(
|
||||||
|
restored_after_restart.thread.section,
|
||||||
|
restored_after_restart.thread.section_entered_at
|
||||||
|
),
|
||||||
|
(None, None)
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn custom_section_management_rejects_empty_names_missing_ids_and_pinned_mutations()
|
||||||
|
-> Result<()> {
|
||||||
|
let responses = create_mock_responses_server_repeating_assistant("Done").await;
|
||||||
|
let codex_home = TempDir::new()?;
|
||||||
|
MockResponsesConfig::new(&responses.uri())
|
||||||
|
.enable_feature(Feature::Sqlite)
|
||||||
|
.write(codex_home.path())?;
|
||||||
|
let mut server = TestAppServer::builder()
|
||||||
|
.with_codex_home(codex_home.path())
|
||||||
|
.build_initialized()
|
||||||
|
.await?;
|
||||||
|
let missing_id = Uuid::now_v7().to_string();
|
||||||
|
|
||||||
|
for (method, params, message) in [
|
||||||
|
(
|
||||||
|
"threadSection/create",
|
||||||
|
json!({ "name": " " }),
|
||||||
|
"section name must not be empty",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"threadSection/update",
|
||||||
|
json!({ "sectionId": " ", "name": "Work" }),
|
||||||
|
"sectionId must not be empty",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"threadSection/update",
|
||||||
|
json!({ "sectionId": PINNED_THREAD_SECTION_ID, "name": "Pinned again" }),
|
||||||
|
"the built-in pinned section cannot be renamed",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"threadSection/delete",
|
||||||
|
json!({ "sectionId": PINNED_THREAD_SECTION_ID }),
|
||||||
|
"the built-in pinned section cannot be deleted",
|
||||||
|
),
|
||||||
|
] {
|
||||||
|
let error = section_request_error(&mut server, method, params).await?;
|
||||||
|
assert_eq!(error.error.code, INVALID_PARAMS_ERROR_CODE);
|
||||||
|
assert_eq!(error.error.message, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (method, params) in [
|
||||||
|
(
|
||||||
|
"threadSection/update",
|
||||||
|
json!({ "sectionId": missing_id, "name": "Work" }),
|
||||||
|
),
|
||||||
|
("threadSection/delete", json!({ "sectionId": missing_id })),
|
||||||
|
] {
|
||||||
|
let error = section_request_error(&mut server, method, params).await?;
|
||||||
|
assert_eq!(error.error.code, INVALID_PARAMS_ERROR_CODE);
|
||||||
|
assert_eq!(
|
||||||
|
error.error.message,
|
||||||
|
format!("thread section not found: {missing_id}")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -48,6 +48,7 @@ mod remote_control;
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) mod test_support;
|
pub(crate) mod test_support;
|
||||||
mod thread_section_order;
|
mod thread_section_order;
|
||||||
|
mod thread_sections;
|
||||||
mod threads;
|
mod threads;
|
||||||
|
|
||||||
pub use external_agent_config_imports::ExternalAgentConfigImportDetailsRecord;
|
pub use external_agent_config_imports::ExternalAgentConfigImportDetailsRecord;
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
use super::StateRuntime;
|
||||||
|
use crate::PINNED_THREAD_SECTION_ID;
|
||||||
|
use crate::ThreadSection;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
impl StateRuntime {
|
||||||
|
/// Create a custom thread section with a stable, server-assigned UUIDv7.
|
||||||
|
pub async fn create_thread_section(&self, name: &str) -> anyhow::Result<ThreadSection> {
|
||||||
|
let section = ThreadSection {
|
||||||
|
id: Uuid::now_v7().to_string(),
|
||||||
|
name: name.to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
sqlx::query("INSERT INTO thread_sections (id, name) VALUES (?, ?)")
|
||||||
|
.bind(§ion.id)
|
||||||
|
.bind(§ion.name)
|
||||||
|
.execute(self.pool.as_ref())
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(section)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Rename a custom thread section without changing its stable identity.
|
||||||
|
pub async fn rename_thread_section(
|
||||||
|
&self,
|
||||||
|
id: &str,
|
||||||
|
name: &str,
|
||||||
|
) -> anyhow::Result<Option<ThreadSection>> {
|
||||||
|
if id == PINNED_THREAD_SECTION_ID {
|
||||||
|
anyhow::bail!("built-in pinned thread section cannot be renamed");
|
||||||
|
}
|
||||||
|
|
||||||
|
let section = sqlx::query_as::<_, (String, String)>(
|
||||||
|
"UPDATE thread_sections SET name = ? WHERE id = ? RETURNING id, name",
|
||||||
|
)
|
||||||
|
.bind(name)
|
||||||
|
.bind(id)
|
||||||
|
.fetch_optional(self.pool.as_ref())
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(section.map(|(id, name)| ThreadSection { id, name }))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete a custom section and return its threads to the unsectioned list.
|
||||||
|
pub async fn delete_thread_section(&self, id: &str) -> anyhow::Result<bool> {
|
||||||
|
if id == PINNED_THREAD_SECTION_ID {
|
||||||
|
anyhow::bail!("built-in pinned thread section cannot be deleted");
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut tx = self.pool.begin_with("BEGIN IMMEDIATE").await?;
|
||||||
|
sqlx::query(
|
||||||
|
"UPDATE threads SET section_position = NULL, section_entered_at_ms = NULL WHERE thread_section_id = ?",
|
||||||
|
)
|
||||||
|
.bind(id)
|
||||||
|
.execute(&mut *tx)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let deleted = sqlx::query("DELETE FROM thread_sections WHERE id = ?")
|
||||||
|
.bind(id)
|
||||||
|
.execute(&mut *tx)
|
||||||
|
.await?
|
||||||
|
.rows_affected()
|
||||||
|
> 0;
|
||||||
|
tx.commit().await?;
|
||||||
|
|
||||||
|
Ok(deleted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "thread_sections_tests.rs"]
|
||||||
|
mod tests;
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
use super::StateRuntime;
|
||||||
|
use crate::runtime::test_support::test_thread_metadata;
|
||||||
|
use crate::runtime::test_support::unique_temp_dir;
|
||||||
|
use anyhow::Result;
|
||||||
|
use codex_protocol::ThreadId;
|
||||||
|
use codex_utils_absolute_path::test_support::PathExt;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn deleting_custom_section_preserves_threads_and_clears_section_ordering() -> Result<()> {
|
||||||
|
let codex_home = unique_temp_dir();
|
||||||
|
let runtime = StateRuntime::init(
|
||||||
|
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||||
|
"test-provider".to_string(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
let deleted_section = runtime.create_thread_section("Research").await?;
|
||||||
|
let retained_section = runtime.create_thread_section("Planning").await?;
|
||||||
|
let first = ThreadId::new();
|
||||||
|
let second = ThreadId::new();
|
||||||
|
let retained = ThreadId::new();
|
||||||
|
|
||||||
|
for (thread_id, section_id) in [
|
||||||
|
(first, &deleted_section.id),
|
||||||
|
(second, &deleted_section.id),
|
||||||
|
(retained, &retained_section.id),
|
||||||
|
] {
|
||||||
|
runtime
|
||||||
|
.upsert_thread(&test_thread_metadata(
|
||||||
|
&codex_home,
|
||||||
|
thread_id,
|
||||||
|
codex_home.clone(),
|
||||||
|
))
|
||||||
|
.await?;
|
||||||
|
runtime
|
||||||
|
.move_thread_to_section(thread_id, Some(section_id), /*before_thread_id*/ None)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(runtime.delete_thread_section(&deleted_section.id).await?);
|
||||||
|
assert_eq!(runtime.get_thread_section(&deleted_section.id).await?, None);
|
||||||
|
|
||||||
|
for thread_id in [first, second] {
|
||||||
|
let thread = runtime
|
||||||
|
.get_thread(thread_id)
|
||||||
|
.await?
|
||||||
|
.expect("section deletion must preserve its member threads");
|
||||||
|
assert_eq!(
|
||||||
|
(
|
||||||
|
thread.section,
|
||||||
|
thread.section_position,
|
||||||
|
thread.section_entered_at
|
||||||
|
),
|
||||||
|
(None, None, None)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let retained_thread = runtime
|
||||||
|
.get_thread(retained)
|
||||||
|
.await?
|
||||||
|
.expect("unrelated section member should remain");
|
||||||
|
assert_eq!(retained_thread.section, Some(retained_section));
|
||||||
|
assert!(retained_thread.section_position.is_some());
|
||||||
|
assert!(retained_thread.section_entered_at.is_some());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn concurrent_section_deletion_and_membership_moves_preserve_thread_invariants() -> Result<()>
|
||||||
|
{
|
||||||
|
let codex_home = unique_temp_dir();
|
||||||
|
let runtime = StateRuntime::init(
|
||||||
|
crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()),
|
||||||
|
"test-provider".to_string(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
let section = runtime.create_thread_section("Research").await?;
|
||||||
|
let thread_id = ThreadId::new();
|
||||||
|
runtime
|
||||||
|
.upsert_thread(&test_thread_metadata(
|
||||||
|
&codex_home,
|
||||||
|
thread_id,
|
||||||
|
codex_home.clone(),
|
||||||
|
))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let (deleted, moved) = tokio::join!(
|
||||||
|
runtime.delete_thread_section(§ion.id),
|
||||||
|
runtime.move_thread_to_section(
|
||||||
|
thread_id,
|
||||||
|
Some(§ion.id),
|
||||||
|
/*before_thread_id*/ None
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(deleted?);
|
||||||
|
if let Err(error) = moved {
|
||||||
|
assert!(error.to_string().contains("does not exist"));
|
||||||
|
}
|
||||||
|
let thread = runtime
|
||||||
|
.get_thread(thread_id)
|
||||||
|
.await?
|
||||||
|
.expect("concurrent section deletion must preserve the thread");
|
||||||
|
assert_eq!(
|
||||||
|
(
|
||||||
|
thread.section,
|
||||||
|
thread.section_position,
|
||||||
|
thread.section_entered_at
|
||||||
|
),
|
||||||
|
(None, None, None)
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ mod live_thread;
|
|||||||
mod local;
|
mod local;
|
||||||
mod store;
|
mod store;
|
||||||
mod thread_metadata_sync;
|
mod thread_metadata_sync;
|
||||||
|
mod thread_sections;
|
||||||
mod types;
|
mod types;
|
||||||
|
|
||||||
pub use error::ThreadStoreError;
|
pub use error::ThreadStoreError;
|
||||||
@@ -22,6 +23,12 @@ pub use local::LocalThreadStore;
|
|||||||
pub use local::LocalThreadStoreConfig;
|
pub use local::LocalThreadStoreConfig;
|
||||||
pub use store::ThreadStore;
|
pub use store::ThreadStore;
|
||||||
pub use store::ThreadStoreFuture;
|
pub use store::ThreadStoreFuture;
|
||||||
|
pub use thread_sections::CreateThreadSectionParams;
|
||||||
|
pub use thread_sections::DeleteThreadSectionParams;
|
||||||
|
pub use thread_sections::ListThreadSectionsParams;
|
||||||
|
pub use thread_sections::RenameThreadSectionParams;
|
||||||
|
pub use thread_sections::StoredThreadSection;
|
||||||
|
pub use thread_sections::StoredThreadSectionsPage;
|
||||||
pub use types::AppendThreadItemsParams;
|
pub use types::AppendThreadItemsParams;
|
||||||
pub use types::ArchiveThreadParams;
|
pub use types::ArchiveThreadParams;
|
||||||
pub use types::ArchiveThreadsParams;
|
pub use types::ArchiveThreadsParams;
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ mod rollout_lineage;
|
|||||||
mod search_threads;
|
mod search_threads;
|
||||||
mod thread_history;
|
mod thread_history;
|
||||||
mod thread_history_materialization;
|
mod thread_history_materialization;
|
||||||
|
mod thread_sections;
|
||||||
mod unarchive_thread;
|
mod unarchive_thread;
|
||||||
mod update_thread_metadata;
|
mod update_thread_metadata;
|
||||||
mod writer_lock;
|
mod writer_lock;
|
||||||
@@ -41,10 +42,13 @@ use crate::AppendThreadItemsParams;
|
|||||||
use crate::ArchiveThreadParams;
|
use crate::ArchiveThreadParams;
|
||||||
use crate::ArchiveThreadsParams;
|
use crate::ArchiveThreadsParams;
|
||||||
use crate::CreateThreadParams;
|
use crate::CreateThreadParams;
|
||||||
|
use crate::CreateThreadSectionParams;
|
||||||
use crate::DeleteThreadParams;
|
use crate::DeleteThreadParams;
|
||||||
|
use crate::DeleteThreadSectionParams;
|
||||||
use crate::DeleteThreadsParams;
|
use crate::DeleteThreadsParams;
|
||||||
use crate::ItemPage;
|
use crate::ItemPage;
|
||||||
use crate::ListItemsParams;
|
use crate::ListItemsParams;
|
||||||
|
use crate::ListThreadSectionsParams;
|
||||||
use crate::ListThreadsParams;
|
use crate::ListThreadsParams;
|
||||||
use crate::ListTurnsParams;
|
use crate::ListTurnsParams;
|
||||||
use crate::LoadThreadHistoryParams;
|
use crate::LoadThreadHistoryParams;
|
||||||
@@ -53,12 +57,15 @@ use crate::PrepareForkParams;
|
|||||||
use crate::PreparedFork;
|
use crate::PreparedFork;
|
||||||
use crate::ReadThreadByRolloutPathParams;
|
use crate::ReadThreadByRolloutPathParams;
|
||||||
use crate::ReadThreadParams;
|
use crate::ReadThreadParams;
|
||||||
|
use crate::RenameThreadSectionParams;
|
||||||
use crate::ResumeThreadParams;
|
use crate::ResumeThreadParams;
|
||||||
use crate::SearchThreadOccurrencesParams;
|
use crate::SearchThreadOccurrencesParams;
|
||||||
use crate::SearchThreadsParams;
|
use crate::SearchThreadsParams;
|
||||||
use crate::StoredModelContext;
|
use crate::StoredModelContext;
|
||||||
use crate::StoredThread;
|
use crate::StoredThread;
|
||||||
use crate::StoredThreadHistory;
|
use crate::StoredThreadHistory;
|
||||||
|
use crate::StoredThreadSection;
|
||||||
|
use crate::StoredThreadSectionsPage;
|
||||||
use crate::ThreadOccurrenceSearchPage;
|
use crate::ThreadOccurrenceSearchPage;
|
||||||
use crate::ThreadPage;
|
use crate::ThreadPage;
|
||||||
use crate::ThreadSearchPage;
|
use crate::ThreadSearchPage;
|
||||||
@@ -458,6 +465,38 @@ impl ThreadStore for LocalThreadStore {
|
|||||||
Box::pin(async move { list_threads::list_threads(self, params).await })
|
Box::pin(async move { list_threads::list_threads(self, params).await })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn supports_thread_sections(&self) -> bool {
|
||||||
|
self.state_db.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list_thread_sections(
|
||||||
|
&self,
|
||||||
|
params: ListThreadSectionsParams,
|
||||||
|
) -> ThreadStoreFuture<'_, StoredThreadSectionsPage> {
|
||||||
|
Box::pin(async move { thread_sections::list_thread_sections(self, params).await })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_thread_section(
|
||||||
|
&self,
|
||||||
|
params: CreateThreadSectionParams,
|
||||||
|
) -> ThreadStoreFuture<'_, StoredThreadSection> {
|
||||||
|
Box::pin(async move { thread_sections::create_thread_section(self, params).await })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rename_thread_section(
|
||||||
|
&self,
|
||||||
|
params: RenameThreadSectionParams,
|
||||||
|
) -> ThreadStoreFuture<'_, Option<StoredThreadSection>> {
|
||||||
|
Box::pin(async move { thread_sections::rename_thread_section(self, params).await })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete_thread_section(
|
||||||
|
&self,
|
||||||
|
params: DeleteThreadSectionParams,
|
||||||
|
) -> ThreadStoreFuture<'_, bool> {
|
||||||
|
Box::pin(async move { thread_sections::delete_thread_section(self, params).await })
|
||||||
|
}
|
||||||
|
|
||||||
fn supports_paginated_history_lists(&self) -> bool {
|
fn supports_paginated_history_lists(&self) -> bool {
|
||||||
self.state_db.is_some()
|
self.state_db.is_some()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
use super::LocalThreadStore;
|
||||||
|
use crate::CreateThreadSectionParams;
|
||||||
|
use crate::DeleteThreadSectionParams;
|
||||||
|
use crate::ListThreadSectionsParams;
|
||||||
|
use crate::RenameThreadSectionParams;
|
||||||
|
use crate::StoredThreadSection;
|
||||||
|
use crate::StoredThreadSectionsPage;
|
||||||
|
use crate::ThreadStoreError;
|
||||||
|
use crate::ThreadStoreResult;
|
||||||
|
use codex_rollout::StateDbHandle;
|
||||||
|
|
||||||
|
pub(super) async fn list_thread_sections(
|
||||||
|
store: &LocalThreadStore,
|
||||||
|
params: ListThreadSectionsParams,
|
||||||
|
) -> ThreadStoreResult<StoredThreadSectionsPage> {
|
||||||
|
let state_db = state_db(store, "threadSection/list")?;
|
||||||
|
let page = state_db
|
||||||
|
.list_thread_sections(params.cursor.as_deref(), params.limit)
|
||||||
|
.await
|
||||||
|
.map_err(|err| section_error("list", err))?;
|
||||||
|
|
||||||
|
Ok(StoredThreadSectionsPage {
|
||||||
|
sections: page
|
||||||
|
.sections
|
||||||
|
.into_iter()
|
||||||
|
.map(|section| StoredThreadSection {
|
||||||
|
id: section.id,
|
||||||
|
name: section.name,
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
next_cursor: page.next_cursor,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) async fn create_thread_section(
|
||||||
|
store: &LocalThreadStore,
|
||||||
|
params: CreateThreadSectionParams,
|
||||||
|
) -> ThreadStoreResult<StoredThreadSection> {
|
||||||
|
let state_db = state_db(store, "threadSection/create")?;
|
||||||
|
let section = state_db
|
||||||
|
.create_thread_section(¶ms.name)
|
||||||
|
.await
|
||||||
|
.map_err(|err| section_error("create", err))?;
|
||||||
|
|
||||||
|
Ok(StoredThreadSection {
|
||||||
|
id: section.id,
|
||||||
|
name: section.name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) async fn rename_thread_section(
|
||||||
|
store: &LocalThreadStore,
|
||||||
|
params: RenameThreadSectionParams,
|
||||||
|
) -> ThreadStoreResult<Option<StoredThreadSection>> {
|
||||||
|
let state_db = state_db(store, "threadSection/update")?;
|
||||||
|
let section = state_db
|
||||||
|
.rename_thread_section(¶ms.section_id, ¶ms.name)
|
||||||
|
.await
|
||||||
|
.map_err(|err| section_error("update", err))?;
|
||||||
|
|
||||||
|
Ok(section.map(|section| StoredThreadSection {
|
||||||
|
id: section.id,
|
||||||
|
name: section.name,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) async fn delete_thread_section(
|
||||||
|
store: &LocalThreadStore,
|
||||||
|
params: DeleteThreadSectionParams,
|
||||||
|
) -> ThreadStoreResult<bool> {
|
||||||
|
let state_db = state_db(store, "threadSection/delete")?;
|
||||||
|
state_db
|
||||||
|
.delete_thread_section(¶ms.section_id)
|
||||||
|
.await
|
||||||
|
.map_err(|err| section_error("delete", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn state_db<'store>(
|
||||||
|
store: &'store LocalThreadStore,
|
||||||
|
operation: &'static str,
|
||||||
|
) -> ThreadStoreResult<&'store StateDbHandle> {
|
||||||
|
store
|
||||||
|
.state_db
|
||||||
|
.as_ref()
|
||||||
|
.ok_or(ThreadStoreError::Unsupported { operation })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn section_error(operation: &str, error: impl std::fmt::Display) -> ThreadStoreError {
|
||||||
|
ThreadStoreError::Internal {
|
||||||
|
message: format!("failed to {operation} thread section: {error}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "thread_sections_tests.rs"]
|
||||||
|
mod tests;
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
use super::LocalThreadStore;
|
||||||
|
use crate::CreateThreadSectionParams;
|
||||||
|
use crate::DeleteThreadSectionParams;
|
||||||
|
use crate::ListThreadSectionsParams;
|
||||||
|
use crate::RenameThreadSectionParams;
|
||||||
|
use crate::StoredThreadSection;
|
||||||
|
use crate::ThreadStore;
|
||||||
|
use crate::ThreadStoreError;
|
||||||
|
use crate::local::test_support::test_config;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn local_thread_sections_require_sqlite_and_preserve_section_identity() {
|
||||||
|
let home = tempfile::TempDir::new().expect("temp dir");
|
||||||
|
let config = test_config(home.path());
|
||||||
|
let unsupported_store = LocalThreadStore::new(config.clone(), /*state_db*/ None);
|
||||||
|
assert!(!unsupported_store.supports_thread_sections());
|
||||||
|
assert!(matches!(
|
||||||
|
unsupported_store
|
||||||
|
.create_thread_section(CreateThreadSectionParams {
|
||||||
|
name: "Research".to_string(),
|
||||||
|
})
|
||||||
|
.await,
|
||||||
|
Err(ThreadStoreError::Unsupported {
|
||||||
|
operation: "threadSection/create"
|
||||||
|
})
|
||||||
|
));
|
||||||
|
|
||||||
|
let runtime = codex_state::StateRuntime::init(
|
||||||
|
config.sqlite.clone(),
|
||||||
|
config.default_model_provider_id.clone(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("state db should initialize");
|
||||||
|
let store = LocalThreadStore::new(config, Some(runtime));
|
||||||
|
assert!(store.supports_thread_sections());
|
||||||
|
|
||||||
|
let section = store
|
||||||
|
.create_thread_section(CreateThreadSectionParams {
|
||||||
|
name: "Research".to_string(),
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.expect("create section");
|
||||||
|
let sections = store
|
||||||
|
.list_thread_sections(ListThreadSectionsParams {
|
||||||
|
cursor: None,
|
||||||
|
limit: 10,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.expect("list sections");
|
||||||
|
assert!(sections.sections.contains(§ion));
|
||||||
|
|
||||||
|
let renamed = store
|
||||||
|
.rename_thread_section(RenameThreadSectionParams {
|
||||||
|
section_id: section.id.clone(),
|
||||||
|
name: "Planning".to_string(),
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.expect("rename section");
|
||||||
|
assert_eq!(
|
||||||
|
renamed,
|
||||||
|
Some(StoredThreadSection {
|
||||||
|
id: section.id.clone(),
|
||||||
|
name: "Planning".to_string(),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
store
|
||||||
|
.delete_thread_section(DeleteThreadSectionParams {
|
||||||
|
section_id: section.id,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.expect("delete section")
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -8,10 +8,13 @@ use crate::AppendThreadItemsParams;
|
|||||||
use crate::ArchiveThreadParams;
|
use crate::ArchiveThreadParams;
|
||||||
use crate::ArchiveThreadsParams;
|
use crate::ArchiveThreadsParams;
|
||||||
use crate::CreateThreadParams;
|
use crate::CreateThreadParams;
|
||||||
|
use crate::CreateThreadSectionParams;
|
||||||
use crate::DeleteThreadParams;
|
use crate::DeleteThreadParams;
|
||||||
|
use crate::DeleteThreadSectionParams;
|
||||||
use crate::DeleteThreadsParams;
|
use crate::DeleteThreadsParams;
|
||||||
use crate::ItemPage;
|
use crate::ItemPage;
|
||||||
use crate::ListItemsParams;
|
use crate::ListItemsParams;
|
||||||
|
use crate::ListThreadSectionsParams;
|
||||||
use crate::ListThreadsParams;
|
use crate::ListThreadsParams;
|
||||||
use crate::ListTurnsParams;
|
use crate::ListTurnsParams;
|
||||||
use crate::LoadThreadHistoryParams;
|
use crate::LoadThreadHistoryParams;
|
||||||
@@ -20,12 +23,15 @@ use crate::PrepareForkParams;
|
|||||||
use crate::PreparedFork;
|
use crate::PreparedFork;
|
||||||
use crate::ReadThreadByRolloutPathParams;
|
use crate::ReadThreadByRolloutPathParams;
|
||||||
use crate::ReadThreadParams;
|
use crate::ReadThreadParams;
|
||||||
|
use crate::RenameThreadSectionParams;
|
||||||
use crate::ResumeThreadParams;
|
use crate::ResumeThreadParams;
|
||||||
use crate::SearchThreadOccurrencesParams;
|
use crate::SearchThreadOccurrencesParams;
|
||||||
use crate::SearchThreadsParams;
|
use crate::SearchThreadsParams;
|
||||||
use crate::StoredModelContext;
|
use crate::StoredModelContext;
|
||||||
use crate::StoredThread;
|
use crate::StoredThread;
|
||||||
use crate::StoredThreadHistory;
|
use crate::StoredThreadHistory;
|
||||||
|
use crate::StoredThreadSection;
|
||||||
|
use crate::StoredThreadSectionsPage;
|
||||||
use crate::ThreadOccurrenceSearchPage;
|
use crate::ThreadOccurrenceSearchPage;
|
||||||
use crate::ThreadPage;
|
use crate::ThreadPage;
|
||||||
use crate::ThreadSearchPage;
|
use crate::ThreadSearchPage;
|
||||||
@@ -123,6 +129,59 @@ pub trait ThreadStore: Any + Send + Sync {
|
|||||||
/// Lists stored threads matching the supplied filters.
|
/// Lists stored threads matching the supplied filters.
|
||||||
fn list_threads(&self, params: ListThreadsParams) -> ThreadStoreFuture<'_, ThreadPage>;
|
fn list_threads(&self, params: ListThreadsParams) -> ThreadStoreFuture<'_, ThreadPage>;
|
||||||
|
|
||||||
|
/// Whether this store can discover and manage independently persisted thread sections.
|
||||||
|
fn supports_thread_sections(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Lists independently persisted thread sections.
|
||||||
|
fn list_thread_sections(
|
||||||
|
&self,
|
||||||
|
_params: ListThreadSectionsParams,
|
||||||
|
) -> ThreadStoreFuture<'_, StoredThreadSectionsPage> {
|
||||||
|
Box::pin(async {
|
||||||
|
Err(ThreadStoreError::Unsupported {
|
||||||
|
operation: "threadSection/list",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a custom thread section with a stable, server-assigned identity.
|
||||||
|
fn create_thread_section(
|
||||||
|
&self,
|
||||||
|
_params: CreateThreadSectionParams,
|
||||||
|
) -> ThreadStoreFuture<'_, StoredThreadSection> {
|
||||||
|
Box::pin(async {
|
||||||
|
Err(ThreadStoreError::Unsupported {
|
||||||
|
operation: "threadSection/create",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Renames a custom thread section, returning `None` when it does not exist.
|
||||||
|
fn rename_thread_section(
|
||||||
|
&self,
|
||||||
|
_params: RenameThreadSectionParams,
|
||||||
|
) -> ThreadStoreFuture<'_, Option<StoredThreadSection>> {
|
||||||
|
Box::pin(async {
|
||||||
|
Err(ThreadStoreError::Unsupported {
|
||||||
|
operation: "threadSection/update",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deletes a custom thread section and reports whether it existed.
|
||||||
|
fn delete_thread_section(
|
||||||
|
&self,
|
||||||
|
_params: DeleteThreadSectionParams,
|
||||||
|
) -> ThreadStoreFuture<'_, bool> {
|
||||||
|
Box::pin(async {
|
||||||
|
Err(ThreadStoreError::Unsupported {
|
||||||
|
operation: "threadSection/delete",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Whether paginated threads can hydrate durable history through turn and item lists.
|
/// Whether paginated threads can hydrate durable history through turn and item lists.
|
||||||
fn supports_paginated_history_lists(&self) -> bool {
|
fn supports_paginated_history_lists(&self) -> bool {
|
||||||
false
|
false
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/// Parameters for listing independently persisted thread sections.
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct ListThreadSectionsParams {
|
||||||
|
/// Opaque cursor returned by a previous section listing.
|
||||||
|
pub cursor: Option<String>,
|
||||||
|
/// Maximum number of sections to return.
|
||||||
|
pub limit: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parameters for creating a thread section.
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct CreateThreadSectionParams {
|
||||||
|
/// User-facing section name.
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parameters for renaming a thread section.
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct RenameThreadSectionParams {
|
||||||
|
/// Stable identifier of the section to rename.
|
||||||
|
pub section_id: String,
|
||||||
|
/// Replacement user-facing section name.
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parameters for deleting a thread section.
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct DeleteThreadSectionParams {
|
||||||
|
/// Stable identifier of the section to delete.
|
||||||
|
pub section_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An independently persisted thread section and its user-facing name.
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct StoredThreadSection {
|
||||||
|
/// Stable, server-owned section identifier.
|
||||||
|
pub id: String,
|
||||||
|
/// User-facing section name.
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A cursor-paginated page of independently persisted thread sections.
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct StoredThreadSectionsPage {
|
||||||
|
/// Sections returned for this page.
|
||||||
|
pub sections: Vec<StoredThreadSection>,
|
||||||
|
/// Opaque cursor to continue listing sections.
|
||||||
|
pub next_cursor: Option<String>,
|
||||||
|
}
|
||||||
Reference in new issue
Block a user