mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
feat(app-server): move v2 sessionId onto Thread (#21336)
## Why `session_id` and `thread_id` are separate identities after #20437, but app-server only surfaced `sessionId` on the `thread/start`, `thread/resume`, and `thread/fork` response envelopes. Other thread-bearing surfaces such as `thread/list`, `thread/read`, `thread/started`, `thread/rollback`, `thread/metadata/update`, and `thread/unarchive` either lacked the grouping key or forced clients to special-case those three responses. Making `sessionId` part of the reusable `Thread` payload gives every v2 API surface one place to expose session-tree identity. ## Mental model 1. thread.sessionId lives on `Thread` 2. It is a view/runtime identity for the current live session tree, not durable stored lineage metadata 3. When app-server has a live loaded thread, it copies the real value from core’s session_configured.session_id 4. When it only has stored/unloaded data, it falls back to thread.sessionId = thread.id ## What changed - Added `sessionId` to the v2 [`Thread`](https://github.com/openai/codex/blob/8fc9e9b4cf81b6f61d432e71f1eb266f6f104b63/codex-rs/app-server-protocol/src/protocol/v2/thread_data.rs#L105-L109). - Removed the duplicate top-level `sessionId` fields from `thread/start`, `thread/resume`, and `thread/fork`; clients should now read `response.thread.sessionId`. - Populated `thread.sessionId` when building live thread responses, replaying loaded threads, and returning stored-thread summaries so the field is present across start, resume, fork, list, read, rollback, metadata-update, unarchive, and `thread/started` paths. See [`load_thread_from_resume_source_or_send_internal`](https://github.com/openai/codex/blob/8fc9e9b4cf81b6f61d432e71f1eb266f6f104b63/codex-rs/app-server/src/request_processors/thread_processor.rs#L2824-L2918) and [`thread_from_stored_thread`](https://github.com/openai/codex/blob/8fc9e9b4cf81b6f61d432e71f1eb266f6f104b63/codex-rs/app-server/src/request_processors/thread_processor.rs#L3671-L3719). - Preserved the stored-thread fallback: if a thread has not been loaded into a live session tree yet, `thread.sessionId` falls back to `thread.id`; once the thread is live again, the field reports the active session tree root. - Regenerated the JSON/TypeScript schemas and updated the app-server README examples to show [`thread.sessionId`](https://github.com/openai/codex/blob/8fc9e9b4cf81b6f61d432e71f1eb266f6f104b63/codex-rs/app-server/README.md#L306-L310) on the thread object.
This commit is contained in:
committed by
GitHub
Unverified
parent
ca257b6ce5
commit
5ecff05196
@@ -128,6 +128,7 @@ fn sample_thread_with_metadata(
|
||||
) -> Thread {
|
||||
Thread {
|
||||
id: thread_id.to_string(),
|
||||
session_id: format!("session-{thread_id}"),
|
||||
forked_from_id: None,
|
||||
preview: "first prompt".to_string(),
|
||||
ephemeral,
|
||||
@@ -154,7 +155,6 @@ fn sample_thread_start_response(
|
||||
model: &str,
|
||||
) -> ClientResponsePayload {
|
||||
ClientResponsePayload::ThreadStart(ThreadStartResponse {
|
||||
session_id: format!("session-{thread_id}"),
|
||||
thread: sample_thread_with_metadata(
|
||||
thread_id,
|
||||
ephemeral,
|
||||
@@ -216,7 +216,6 @@ fn sample_thread_resume_response_with_source(
|
||||
thread_source: Option<AppServerThreadSource>,
|
||||
) -> ClientResponsePayload {
|
||||
ClientResponsePayload::ThreadResume(ThreadResumeResponse {
|
||||
session_id: format!("session-{thread_id}"),
|
||||
thread: sample_thread_with_metadata(thread_id, ephemeral, source, thread_source),
|
||||
model: model.to_string(),
|
||||
model_provider: "openai".to_string(),
|
||||
|
||||
@@ -76,6 +76,7 @@ fn sample_thread_archive_request() -> ClientRequest {
|
||||
fn sample_thread(thread_id: &str) -> Thread {
|
||||
Thread {
|
||||
id: thread_id.to_string(),
|
||||
session_id: format!("session-{thread_id}"),
|
||||
forked_from_id: None,
|
||||
preview: "first prompt".to_string(),
|
||||
ephemeral: false,
|
||||
@@ -102,7 +103,6 @@ fn sample_permission_profile() -> AppServerPermissionProfile {
|
||||
|
||||
fn sample_thread_start_response() -> ClientResponsePayload {
|
||||
ClientResponsePayload::ThreadStart(ThreadStartResponse {
|
||||
session_id: "session-1".to_string(),
|
||||
thread: sample_thread("thread-1"),
|
||||
model: "gpt-5".to_string(),
|
||||
model_provider: "openai".to_string(),
|
||||
@@ -120,7 +120,6 @@ fn sample_thread_start_response() -> ClientResponsePayload {
|
||||
|
||||
fn sample_thread_resume_response() -> ClientResponsePayload {
|
||||
ClientResponsePayload::ThreadResume(ThreadResumeResponse {
|
||||
session_id: "session-2".to_string(),
|
||||
thread: sample_thread("thread-2"),
|
||||
model: "gpt-5".to_string(),
|
||||
model_provider: "openai".to_string(),
|
||||
@@ -138,7 +137,6 @@ fn sample_thread_resume_response() -> ClientResponsePayload {
|
||||
|
||||
fn sample_thread_fork_response() -> ClientResponsePayload {
|
||||
ClientResponsePayload::ThreadFork(ThreadForkResponse {
|
||||
session_id: "session-3".to_string(),
|
||||
thread: sample_thread("thread-3"),
|
||||
model: "gpt-5".to_string(),
|
||||
model_provider: "openai".to_string(),
|
||||
|
||||
@@ -3072,6 +3072,10 @@
|
||||
"description": "Usually the first user message in the thread, if available.",
|
||||
"type": "string"
|
||||
},
|
||||
"sessionId": {
|
||||
"description": "Session id shared by threads that belong to the same session tree.",
|
||||
"type": "string"
|
||||
},
|
||||
"source": {
|
||||
"allOf": [
|
||||
{
|
||||
@@ -3120,6 +3124,7 @@
|
||||
"id",
|
||||
"modelProvider",
|
||||
"preview",
|
||||
"sessionId",
|
||||
"source",
|
||||
"status",
|
||||
"turns",
|
||||
|
||||
+5
-15
@@ -15345,6 +15345,10 @@
|
||||
"description": "Usually the first user message in the thread, if available.",
|
||||
"type": "string"
|
||||
},
|
||||
"sessionId": {
|
||||
"description": "Session id shared by threads that belong to the same session tree.",
|
||||
"type": "string"
|
||||
},
|
||||
"source": {
|
||||
"allOf": [
|
||||
{
|
||||
@@ -15393,6 +15397,7 @@
|
||||
"id",
|
||||
"modelProvider",
|
||||
"preview",
|
||||
"sessionId",
|
||||
"source",
|
||||
"status",
|
||||
"turns",
|
||||
@@ -15664,11 +15669,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"sessionId": {
|
||||
"default": "",
|
||||
"description": "Session id shared by threads that belong to the same session tree.",
|
||||
"type": "string"
|
||||
},
|
||||
"thread": {
|
||||
"$ref": "#/definitions/v2/Thread"
|
||||
}
|
||||
@@ -17176,11 +17176,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"sessionId": {
|
||||
"default": "",
|
||||
"description": "Session id shared by threads that belong to the same session tree.",
|
||||
"type": "string"
|
||||
},
|
||||
"thread": {
|
||||
"$ref": "#/definitions/v2/Thread"
|
||||
}
|
||||
@@ -17504,11 +17499,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"sessionId": {
|
||||
"default": "",
|
||||
"description": "Session id shared by threads that belong to the same session tree.",
|
||||
"type": "string"
|
||||
},
|
||||
"thread": {
|
||||
"$ref": "#/definitions/v2/Thread"
|
||||
}
|
||||
|
||||
+5
-15
@@ -13231,6 +13231,10 @@
|
||||
"description": "Usually the first user message in the thread, if available.",
|
||||
"type": "string"
|
||||
},
|
||||
"sessionId": {
|
||||
"description": "Session id shared by threads that belong to the same session tree.",
|
||||
"type": "string"
|
||||
},
|
||||
"source": {
|
||||
"allOf": [
|
||||
{
|
||||
@@ -13279,6 +13283,7 @@
|
||||
"id",
|
||||
"modelProvider",
|
||||
"preview",
|
||||
"sessionId",
|
||||
"source",
|
||||
"status",
|
||||
"turns",
|
||||
@@ -13550,11 +13555,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"sessionId": {
|
||||
"default": "",
|
||||
"description": "Session id shared by threads that belong to the same session tree.",
|
||||
"type": "string"
|
||||
},
|
||||
"thread": {
|
||||
"$ref": "#/definitions/Thread"
|
||||
}
|
||||
@@ -15062,11 +15062,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"sessionId": {
|
||||
"default": "",
|
||||
"description": "Session id shared by threads that belong to the same session tree.",
|
||||
"type": "string"
|
||||
},
|
||||
"thread": {
|
||||
"$ref": "#/definitions/Thread"
|
||||
}
|
||||
@@ -15390,11 +15385,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"sessionId": {
|
||||
"default": "",
|
||||
"description": "Session id shared by threads that belong to the same session tree.",
|
||||
"type": "string"
|
||||
},
|
||||
"thread": {
|
||||
"$ref": "#/definitions/Thread"
|
||||
}
|
||||
|
||||
@@ -1403,6 +1403,10 @@
|
||||
"description": "Usually the first user message in the thread, if available.",
|
||||
"type": "string"
|
||||
},
|
||||
"sessionId": {
|
||||
"description": "Session id shared by threads that belong to the same session tree.",
|
||||
"type": "string"
|
||||
},
|
||||
"source": {
|
||||
"allOf": [
|
||||
{
|
||||
@@ -1451,6 +1455,7 @@
|
||||
"id",
|
||||
"modelProvider",
|
||||
"preview",
|
||||
"sessionId",
|
||||
"source",
|
||||
"status",
|
||||
"turns",
|
||||
@@ -2619,11 +2624,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"sessionId": {
|
||||
"default": "",
|
||||
"description": "Session id shared by threads that belong to the same session tree.",
|
||||
"type": "string"
|
||||
},
|
||||
"thread": {
|
||||
"$ref": "#/definitions/Thread"
|
||||
}
|
||||
|
||||
@@ -853,6 +853,10 @@
|
||||
"description": "Usually the first user message in the thread, if available.",
|
||||
"type": "string"
|
||||
},
|
||||
"sessionId": {
|
||||
"description": "Session id shared by threads that belong to the same session tree.",
|
||||
"type": "string"
|
||||
},
|
||||
"source": {
|
||||
"allOf": [
|
||||
{
|
||||
@@ -901,6 +905,7 @@
|
||||
"id",
|
||||
"modelProvider",
|
||||
"preview",
|
||||
"sessionId",
|
||||
"source",
|
||||
"status",
|
||||
"turns",
|
||||
|
||||
+5
@@ -853,6 +853,10 @@
|
||||
"description": "Usually the first user message in the thread, if available.",
|
||||
"type": "string"
|
||||
},
|
||||
"sessionId": {
|
||||
"description": "Session id shared by threads that belong to the same session tree.",
|
||||
"type": "string"
|
||||
},
|
||||
"source": {
|
||||
"allOf": [
|
||||
{
|
||||
@@ -901,6 +905,7 @@
|
||||
"id",
|
||||
"modelProvider",
|
||||
"preview",
|
||||
"sessionId",
|
||||
"source",
|
||||
"status",
|
||||
"turns",
|
||||
|
||||
@@ -853,6 +853,10 @@
|
||||
"description": "Usually the first user message in the thread, if available.",
|
||||
"type": "string"
|
||||
},
|
||||
"sessionId": {
|
||||
"description": "Session id shared by threads that belong to the same session tree.",
|
||||
"type": "string"
|
||||
},
|
||||
"source": {
|
||||
"allOf": [
|
||||
{
|
||||
@@ -901,6 +905,7 @@
|
||||
"id",
|
||||
"modelProvider",
|
||||
"preview",
|
||||
"sessionId",
|
||||
"source",
|
||||
"status",
|
||||
"turns",
|
||||
|
||||
@@ -1403,6 +1403,10 @@
|
||||
"description": "Usually the first user message in the thread, if available.",
|
||||
"type": "string"
|
||||
},
|
||||
"sessionId": {
|
||||
"description": "Session id shared by threads that belong to the same session tree.",
|
||||
"type": "string"
|
||||
},
|
||||
"source": {
|
||||
"allOf": [
|
||||
{
|
||||
@@ -1451,6 +1455,7 @@
|
||||
"id",
|
||||
"modelProvider",
|
||||
"preview",
|
||||
"sessionId",
|
||||
"source",
|
||||
"status",
|
||||
"turns",
|
||||
@@ -2619,11 +2624,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"sessionId": {
|
||||
"default": "",
|
||||
"description": "Session id shared by threads that belong to the same session tree.",
|
||||
"type": "string"
|
||||
},
|
||||
"thread": {
|
||||
"$ref": "#/definitions/Thread"
|
||||
}
|
||||
|
||||
@@ -853,6 +853,10 @@
|
||||
"description": "Usually the first user message in the thread, if available.",
|
||||
"type": "string"
|
||||
},
|
||||
"sessionId": {
|
||||
"description": "Session id shared by threads that belong to the same session tree.",
|
||||
"type": "string"
|
||||
},
|
||||
"source": {
|
||||
"allOf": [
|
||||
{
|
||||
@@ -901,6 +905,7 @@
|
||||
"id",
|
||||
"modelProvider",
|
||||
"preview",
|
||||
"sessionId",
|
||||
"source",
|
||||
"status",
|
||||
"turns",
|
||||
|
||||
@@ -1403,6 +1403,10 @@
|
||||
"description": "Usually the first user message in the thread, if available.",
|
||||
"type": "string"
|
||||
},
|
||||
"sessionId": {
|
||||
"description": "Session id shared by threads that belong to the same session tree.",
|
||||
"type": "string"
|
||||
},
|
||||
"source": {
|
||||
"allOf": [
|
||||
{
|
||||
@@ -1451,6 +1455,7 @@
|
||||
"id",
|
||||
"modelProvider",
|
||||
"preview",
|
||||
"sessionId",
|
||||
"source",
|
||||
"status",
|
||||
"turns",
|
||||
@@ -2619,11 +2624,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"sessionId": {
|
||||
"default": "",
|
||||
"description": "Session id shared by threads that belong to the same session tree.",
|
||||
"type": "string"
|
||||
},
|
||||
"thread": {
|
||||
"$ref": "#/definitions/Thread"
|
||||
}
|
||||
|
||||
@@ -853,6 +853,10 @@
|
||||
"description": "Usually the first user message in the thread, if available.",
|
||||
"type": "string"
|
||||
},
|
||||
"sessionId": {
|
||||
"description": "Session id shared by threads that belong to the same session tree.",
|
||||
"type": "string"
|
||||
},
|
||||
"source": {
|
||||
"allOf": [
|
||||
{
|
||||
@@ -901,6 +905,7 @@
|
||||
"id",
|
||||
"modelProvider",
|
||||
"preview",
|
||||
"sessionId",
|
||||
"source",
|
||||
"status",
|
||||
"turns",
|
||||
|
||||
@@ -853,6 +853,10 @@
|
||||
"description": "Usually the first user message in the thread, if available.",
|
||||
"type": "string"
|
||||
},
|
||||
"sessionId": {
|
||||
"description": "Session id shared by threads that belong to the same session tree.",
|
||||
"type": "string"
|
||||
},
|
||||
"source": {
|
||||
"allOf": [
|
||||
{
|
||||
@@ -901,6 +905,7 @@
|
||||
"id",
|
||||
"modelProvider",
|
||||
"preview",
|
||||
"sessionId",
|
||||
"source",
|
||||
"status",
|
||||
"turns",
|
||||
|
||||
@@ -9,6 +9,10 @@ import type { ThreadStatus } from "./ThreadStatus";
|
||||
import type { Turn } from "./Turn";
|
||||
|
||||
export type Thread = { id: string,
|
||||
/**
|
||||
* Session id shared by threads that belong to the same session tree.
|
||||
*/
|
||||
sessionId: string,
|
||||
/**
|
||||
* Source thread id when this thread was created by forking another thread.
|
||||
*/
|
||||
|
||||
@@ -9,10 +9,7 @@ import type { AskForApproval } from "./AskForApproval";
|
||||
import type { SandboxPolicy } from "./SandboxPolicy";
|
||||
import type { Thread } from "./Thread";
|
||||
|
||||
export type ThreadForkResponse = {/**
|
||||
* Session id shared by threads that belong to the same session tree.
|
||||
*/
|
||||
sessionId: string, thread: Thread, model: string, modelProvider: string, serviceTier: ServiceTier | null, cwd: AbsolutePathBuf, /**
|
||||
export type ThreadForkResponse = {thread: Thread, model: string, modelProvider: string, serviceTier: ServiceTier | null, cwd: AbsolutePathBuf, /**
|
||||
* Instruction source files currently loaded for this thread.
|
||||
*/
|
||||
instructionSources: Array<AbsolutePathBuf>, approvalPolicy: AskForApproval, /**
|
||||
|
||||
@@ -9,10 +9,7 @@ import type { AskForApproval } from "./AskForApproval";
|
||||
import type { SandboxPolicy } from "./SandboxPolicy";
|
||||
import type { Thread } from "./Thread";
|
||||
|
||||
export type ThreadResumeResponse = {/**
|
||||
* Session id shared by threads that belong to the same session tree.
|
||||
*/
|
||||
sessionId: string, thread: Thread, model: string, modelProvider: string, serviceTier: ServiceTier | null, cwd: AbsolutePathBuf, /**
|
||||
export type ThreadResumeResponse = {thread: Thread, model: string, modelProvider: string, serviceTier: ServiceTier | null, cwd: AbsolutePathBuf, /**
|
||||
* Instruction source files currently loaded for this thread.
|
||||
*/
|
||||
instructionSources: Array<AbsolutePathBuf>, approvalPolicy: AskForApproval, /**
|
||||
|
||||
@@ -9,10 +9,7 @@ import type { AskForApproval } from "./AskForApproval";
|
||||
import type { SandboxPolicy } from "./SandboxPolicy";
|
||||
import type { Thread } from "./Thread";
|
||||
|
||||
export type ThreadStartResponse = {/**
|
||||
* Session id shared by threads that belong to the same session tree.
|
||||
*/
|
||||
sessionId: string, thread: Thread, model: string, modelProvider: string, serviceTier: ServiceTier | null, cwd: AbsolutePathBuf, /**
|
||||
export type ThreadStartResponse = {thread: Thread, model: string, modelProvider: string, serviceTier: ServiceTier | null, cwd: AbsolutePathBuf, /**
|
||||
* Instruction source files currently loaded for this thread.
|
||||
*/
|
||||
instructionSources: Array<AbsolutePathBuf>, approvalPolicy: AskForApproval, /**
|
||||
|
||||
@@ -2176,9 +2176,9 @@ mod tests {
|
||||
let response = ClientResponse::ThreadStart {
|
||||
request_id: RequestId::Integer(7),
|
||||
response: v2::ThreadStartResponse {
|
||||
session_id: "67e55044-10b1-426f-9247-bb680e5fe0c7".to_string(),
|
||||
thread: v2::Thread {
|
||||
id: "67e55044-10b1-426f-9247-bb680e5fe0c8".to_string(),
|
||||
session_id: "67e55044-10b1-426f-9247-bb680e5fe0c7".to_string(),
|
||||
forked_from_id: None,
|
||||
preview: "first prompt".to_string(),
|
||||
ephemeral: true,
|
||||
@@ -2218,9 +2218,9 @@ mod tests {
|
||||
"method": "thread/start",
|
||||
"id": 7,
|
||||
"response": {
|
||||
"sessionId": "67e55044-10b1-426f-9247-bb680e5fe0c7",
|
||||
"thread": {
|
||||
"id": "67e55044-10b1-426f-9247-bb680e5fe0c8",
|
||||
"sessionId": "67e55044-10b1-426f-9247-bb680e5fe0c7",
|
||||
"forkedFromId": null,
|
||||
"preview": "first prompt",
|
||||
"ephemeral": true,
|
||||
|
||||
@@ -3437,10 +3437,11 @@ fn thread_start_params_preserve_explicit_null_service_tier() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn thread_lifecycle_responses_default_missing_compat_fields() {
|
||||
fn thread_lifecycle_responses_default_missing_optional_fields() {
|
||||
let response = json!({
|
||||
"thread": {
|
||||
"id": "thread-id",
|
||||
"sessionId": "thread-id",
|
||||
"forkedFromId": null,
|
||||
"preview": "",
|
||||
"ephemeral": false,
|
||||
|
||||
@@ -189,9 +189,6 @@ pub struct MockExperimentalMethodResponse {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ThreadStartResponse {
|
||||
/// Session id shared by threads that belong to the same session tree.
|
||||
#[serde(default)]
|
||||
pub session_id: String,
|
||||
pub thread: Thread,
|
||||
pub model: String,
|
||||
pub model_provider: String,
|
||||
@@ -307,9 +304,6 @@ pub struct ThreadResumeParams {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ThreadResumeResponse {
|
||||
/// Session id shared by threads that belong to the same session tree.
|
||||
#[serde(default)]
|
||||
pub session_id: String,
|
||||
pub thread: Thread,
|
||||
pub model: String,
|
||||
pub model_provider: String,
|
||||
@@ -419,9 +413,6 @@ pub struct ThreadForkParams {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ThreadForkResponse {
|
||||
/// Session id shared by threads that belong to the same session tree.
|
||||
#[serde(default)]
|
||||
pub session_id: String,
|
||||
pub thread: Thread,
|
||||
pub model: String,
|
||||
pub model_provider: String,
|
||||
|
||||
@@ -104,6 +104,8 @@ pub struct GitInfo {
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct Thread {
|
||||
pub id: String,
|
||||
/// Session id shared by threads that belong to the same session tree.
|
||||
pub session_id: String,
|
||||
/// Source thread id when this thread was created by forking another thread.
|
||||
pub forked_from_id: Option<String>,
|
||||
/// Usually the first user message in the thread, if available.
|
||||
|
||||
@@ -303,11 +303,11 @@ Example:
|
||||
{ "id": 12, "result": { "thread": { "id": "thr_123", "turns": [], … } } }
|
||||
```
|
||||
|
||||
To branch from a stored session, call `thread/fork` with the `thread.id`. This creates a new thread id and emits a `thread/started` notification for it. The response includes the forked thread's `sessionId`, so clients do not need to infer it from the new thread id. When the source history includes persisted token usage, the server also emits `thread/tokenUsage/updated` for the new thread immediately after the response. If the source thread is actively running, the fork snapshots it as if the current turn had been interrupted first. Pass `ephemeral: true` when the fork should stay in-memory only:
|
||||
To branch from a stored session, call `thread/fork` with the `thread.id`. This creates a new thread id and emits a `thread/started` notification for it. The returned `thread.sessionId` identifies the current live session tree root. Root threads use their own `thread.id` as `thread.sessionId`; stored threads that are not loaded also report their own `thread.id`, because resuming one makes it the root of a new live session tree. When the source history includes persisted token usage, the server also emits `thread/tokenUsage/updated` for the new thread immediately after the response. If the source thread is actively running, the fork snapshots it as if the current turn had been interrupted first. Pass `ephemeral: true` when the fork should stay in-memory only:
|
||||
|
||||
```json
|
||||
{ "method": "thread/fork", "id": 12, "params": { "threadId": "thr_123", "ephemeral": true } }
|
||||
{ "id": 12, "result": { "sessionId": "thr_456", "thread": { "id": "thr_456", … } } }
|
||||
{ "id": 12, "result": { "thread": { "id": "thr_456", "sessionId": "thr_456", … } } }
|
||||
{ "method": "thread/started", "params": { "thread": { … } } }
|
||||
```
|
||||
|
||||
|
||||
@@ -1190,6 +1190,7 @@ pub(crate) async fn apply_bespoke_event_handling(
|
||||
.await;
|
||||
let response = match thread_rollback_response_from_stored_thread(
|
||||
stored_thread,
|
||||
conversation.session_configured().session_id.to_string(),
|
||||
fallback_model_provider.as_str(),
|
||||
&fallback_cwd,
|
||||
loaded_status,
|
||||
@@ -1543,6 +1544,7 @@ async fn handle_thread_rollback_failed(
|
||||
|
||||
fn thread_rollback_response_from_stored_thread(
|
||||
stored_thread: codex_thread_store::StoredThread,
|
||||
session_id: String,
|
||||
fallback_model_provider: &str,
|
||||
fallback_cwd: &AbsolutePathBuf,
|
||||
loaded_status: ThreadStatus,
|
||||
@@ -1550,6 +1552,7 @@ fn thread_rollback_response_from_stored_thread(
|
||||
let thread_id = stored_thread.thread_id;
|
||||
let (mut thread, history) =
|
||||
thread_from_stored_thread(stored_thread, fallback_model_provider, fallback_cwd);
|
||||
thread.session_id = session_id;
|
||||
let Some(history) = history else {
|
||||
return Err(format!(
|
||||
"thread {thread_id} did not include persisted history after rollback"
|
||||
@@ -2200,6 +2203,7 @@ mod tests {
|
||||
|
||||
let response = thread_rollback_response_from_stored_thread(
|
||||
stored_thread,
|
||||
thread_id.to_string(),
|
||||
"fallback-provider",
|
||||
&fallback_cwd,
|
||||
ThreadStatus::NotLoaded,
|
||||
|
||||
@@ -599,9 +599,9 @@ pub(super) async fn handle_pending_thread_resume_request(
|
||||
let active_permission_profile =
|
||||
thread_response_active_permission_profile(active_permission_profile);
|
||||
let session_id = conversation.session_configured().session_id.to_string();
|
||||
thread.session_id = session_id;
|
||||
|
||||
let response = ThreadResumeResponse {
|
||||
session_id,
|
||||
thread,
|
||||
model,
|
||||
model_provider: model_provider_id,
|
||||
|
||||
@@ -1096,6 +1096,7 @@ impl ThreadRequestProcessor {
|
||||
.await;
|
||||
let mut thread = build_thread_from_snapshot(
|
||||
thread_id,
|
||||
session_configured.session_id.to_string(),
|
||||
&config_snapshot,
|
||||
session_configured.rollout_path.clone(),
|
||||
);
|
||||
@@ -1148,7 +1149,6 @@ impl ThreadRequestProcessor {
|
||||
thread_response_active_permission_profile(config_snapshot.active_permission_profile);
|
||||
|
||||
let response = ThreadStartResponse {
|
||||
session_id: session_configured.session_id.to_string(),
|
||||
thread: thread.clone(),
|
||||
model: config_snapshot.model,
|
||||
model_provider: config_snapshot.model_provider_id,
|
||||
@@ -1529,9 +1529,9 @@ impl ThreadRequestProcessor {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let loaded_thread = self.thread_manager.get_thread(thread_uuid).await.ok();
|
||||
let updated_thread = {
|
||||
let _thread_list_state_permit = self.acquire_thread_list_state_permit().await?;
|
||||
let loaded_thread = self.thread_manager.get_thread(thread_uuid).await.ok();
|
||||
if let Some(loaded_thread) = loaded_thread.as_ref() {
|
||||
if loaded_thread.config_snapshot().await.ephemeral {
|
||||
return Err(invalid_request(format!(
|
||||
@@ -1552,12 +1552,14 @@ impl ThreadRequestProcessor {
|
||||
}
|
||||
.map_err(|err| thread_store_write_error("update thread metadata", err))?
|
||||
};
|
||||
|
||||
let (mut thread, _) = thread_from_stored_thread(
|
||||
updated_thread,
|
||||
self.config.model_provider_id.as_str(),
|
||||
&self.config.cwd,
|
||||
);
|
||||
if let Some(loaded_thread) = loaded_thread.as_ref() {
|
||||
thread.session_id = loaded_thread.session_configured().session_id.to_string();
|
||||
}
|
||||
self.attach_thread_name(thread_uuid, &mut thread).await;
|
||||
thread.status = resolve_thread_status(
|
||||
self.thread_watch_manager
|
||||
@@ -1603,18 +1605,16 @@ impl ThreadRequestProcessor {
|
||||
.map_err(|err| invalid_request(format!("invalid thread id: {err}")))?;
|
||||
|
||||
let fallback_provider = self.config.model_provider_id.clone();
|
||||
let mut thread = self
|
||||
let stored_thread = self
|
||||
.thread_store
|
||||
.unarchive_thread(StoreArchiveThreadParams { thread_id })
|
||||
.await
|
||||
.map_err(|err| thread_store_archive_error("unarchive", err))
|
||||
.and_then(|stored_thread| {
|
||||
summary_from_stored_thread(stored_thread, fallback_provider.as_str())
|
||||
.map(|summary| summary_to_thread(summary, &self.config.cwd))
|
||||
.ok_or_else(|| {
|
||||
internal_error(format!("failed to read unarchived thread {thread_id}"))
|
||||
})
|
||||
.map_err(|err| thread_store_archive_error("unarchive", err))?;
|
||||
let summary = summary_from_stored_thread(stored_thread, fallback_provider.as_str())
|
||||
.ok_or_else(|| {
|
||||
internal_error(format!("failed to read unarchived thread {thread_id}"))
|
||||
})?;
|
||||
let mut thread = summary_to_thread(summary, &self.config.cwd);
|
||||
|
||||
thread.status = resolve_thread_status(
|
||||
self.thread_watch_manager
|
||||
@@ -2040,6 +2040,7 @@ impl ThreadRequestProcessor {
|
||||
if thread.path.is_none() {
|
||||
thread.path = fallback_thread.path.clone();
|
||||
}
|
||||
thread.session_id.clone_from(&fallback_thread.session_id);
|
||||
thread.ephemeral = fallback_thread.ephemeral;
|
||||
thread
|
||||
} else {
|
||||
@@ -2223,8 +2224,12 @@ impl ThreadRequestProcessor {
|
||||
) {
|
||||
if let Ok(thread) = self.thread_manager.get_thread(thread_id).await {
|
||||
let config_snapshot = thread.config_snapshot().await;
|
||||
let loaded_thread =
|
||||
build_thread_from_snapshot(thread_id, &config_snapshot, thread.rollout_path());
|
||||
let loaded_thread = build_thread_from_snapshot(
|
||||
thread_id,
|
||||
thread.session_configured().session_id.to_string(),
|
||||
&config_snapshot,
|
||||
thread.rollout_path(),
|
||||
);
|
||||
self.thread_watch_manager.upsert_thread(loaded_thread).await;
|
||||
}
|
||||
|
||||
@@ -2477,7 +2482,6 @@ impl ThreadRequestProcessor {
|
||||
);
|
||||
|
||||
let response = ThreadResumeResponse {
|
||||
session_id: session_configured.session_id.to_string(),
|
||||
thread,
|
||||
model: session_configured.model,
|
||||
model_provider: session_configured.model_provider_id,
|
||||
@@ -2651,11 +2655,12 @@ impl ThreadRequestProcessor {
|
||||
}
|
||||
let mut summary_source_thread = source_thread;
|
||||
summary_source_thread.history = None;
|
||||
let thread_summary = self.stored_thread_to_api_thread(
|
||||
let mut thread_summary = self.stored_thread_to_api_thread(
|
||||
summary_source_thread,
|
||||
config_snapshot.model_provider_id.as_str(),
|
||||
/*include_turns*/ false,
|
||||
);
|
||||
thread_summary.session_id = existing_thread.session_configured().session_id.to_string();
|
||||
let mut config_for_instruction_sources = self.config.as_ref().clone();
|
||||
config_for_instruction_sources.cwd = config_snapshot.cwd.clone();
|
||||
let instruction_sources =
|
||||
@@ -2824,6 +2829,7 @@ impl ThreadRequestProcessor {
|
||||
include_turns: bool,
|
||||
) -> std::result::Result<Thread, String> {
|
||||
let config_snapshot = thread.config_snapshot().await;
|
||||
let session_id = thread.session_configured().session_id.to_string();
|
||||
let thread = match thread_history {
|
||||
InitialHistory::Resumed(resumed) => {
|
||||
let fallback_provider = config_snapshot.model_provider_id.as_str();
|
||||
@@ -2885,6 +2891,7 @@ impl ThreadRequestProcessor {
|
||||
InitialHistory::Forked(items) => {
|
||||
let mut thread = build_thread_from_snapshot(
|
||||
thread_id,
|
||||
session_id.clone(),
|
||||
&config_snapshot,
|
||||
Some(rollout_path.into()),
|
||||
);
|
||||
@@ -2897,6 +2904,7 @@ impl ThreadRequestProcessor {
|
||||
};
|
||||
let mut thread = thread?;
|
||||
thread.id = thread_id.to_string();
|
||||
thread.session_id = session_id;
|
||||
thread.path = Some(rollout_path.to_path_buf());
|
||||
if include_turns {
|
||||
let history_items = thread_history.get_rollout_items();
|
||||
@@ -3078,8 +3086,12 @@ impl ThreadRequestProcessor {
|
||||
} else {
|
||||
let config_snapshot = forked_thread.config_snapshot().await;
|
||||
// forked thread names do not inherit the source thread name
|
||||
let mut thread =
|
||||
build_thread_from_snapshot(thread_id, &config_snapshot, /*path*/ None);
|
||||
let mut thread = build_thread_from_snapshot(
|
||||
thread_id,
|
||||
session_configured.session_id.to_string(),
|
||||
&config_snapshot,
|
||||
/*path*/ None,
|
||||
);
|
||||
thread.preview = preview_from_rollout_items(&history_items);
|
||||
thread.forked_from_id = Some(source_thread_id.to_string());
|
||||
if include_turns {
|
||||
@@ -3091,6 +3103,7 @@ impl ThreadRequestProcessor {
|
||||
}
|
||||
thread
|
||||
};
|
||||
thread.session_id = session_configured.session_id.to_string();
|
||||
thread.thread_source = forked_thread
|
||||
.config_snapshot()
|
||||
.await
|
||||
@@ -3116,7 +3129,6 @@ impl ThreadRequestProcessor {
|
||||
thread_response_active_permission_profile(config_snapshot.active_permission_profile);
|
||||
|
||||
let response = ThreadForkResponse {
|
||||
session_id: session_configured.session_id.to_string(),
|
||||
thread: thread.clone(),
|
||||
model: session_configured.model,
|
||||
model_provider: session_configured.model_provider_id,
|
||||
@@ -3680,8 +3692,10 @@ pub(crate) fn thread_from_stored_thread(
|
||||
thread.agent_role.clone(),
|
||||
);
|
||||
let history = thread.history;
|
||||
let thread_id = thread.thread_id.to_string();
|
||||
let thread = Thread {
|
||||
id: thread.thread_id.to_string(),
|
||||
id: thread_id.clone(),
|
||||
session_id: thread_id,
|
||||
forked_from_id: thread.forked_from_id.map(|id| id.to_string()),
|
||||
preview: thread.first_user_message.unwrap_or(thread.preview),
|
||||
ephemeral: false,
|
||||
@@ -3878,12 +3892,14 @@ fn permission_profile_trusts_project(
|
||||
|
||||
fn build_thread_from_snapshot(
|
||||
thread_id: ThreadId,
|
||||
session_id: String,
|
||||
config_snapshot: &ThreadConfigSnapshot,
|
||||
path: Option<PathBuf>,
|
||||
) -> Thread {
|
||||
let now = time::OffsetDateTime::now_utc().unix_timestamp();
|
||||
Thread {
|
||||
id: thread_id.to_string(),
|
||||
session_id,
|
||||
forked_from_id: None,
|
||||
preview: String::new(),
|
||||
ephemeral: config_snapshot.ephemeral,
|
||||
@@ -3909,7 +3925,12 @@ fn build_thread_from_loaded_snapshot(
|
||||
config_snapshot: &ThreadConfigSnapshot,
|
||||
loaded_thread: &CodexThread,
|
||||
) -> Thread {
|
||||
build_thread_from_snapshot(thread_id, config_snapshot, loaded_thread.rollout_path())
|
||||
build_thread_from_snapshot(
|
||||
thread_id,
|
||||
loaded_thread.session_configured().session_id.to_string(),
|
||||
config_snapshot,
|
||||
loaded_thread.rollout_path(),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -263,8 +263,10 @@ pub(crate) fn summary_to_thread(
|
||||
fallback_cwd.clone()
|
||||
});
|
||||
|
||||
let thread_id = conversation_id.to_string();
|
||||
Thread {
|
||||
id: conversation_id.to_string(),
|
||||
id: thread_id.clone(),
|
||||
session_id: thread_id,
|
||||
forked_from_id: None,
|
||||
preview,
|
||||
ephemeral: false,
|
||||
|
||||
@@ -935,6 +935,7 @@ impl TurnRequestProcessor {
|
||||
Ok(stored_thread) => {
|
||||
let (mut thread, _) =
|
||||
thread_from_stored_thread(stored_thread, fallback_provider, &self.config.cwd);
|
||||
thread.session_id = review_thread.session_configured().session_id.to_string();
|
||||
self.thread_watch_manager
|
||||
.upsert_thread_silently(thread.clone())
|
||||
.await;
|
||||
|
||||
@@ -889,6 +889,7 @@ mod tests {
|
||||
fn test_thread(thread_id: &str, source: codex_app_server_protocol::SessionSource) -> Thread {
|
||||
Thread {
|
||||
id: thread_id.to_string(),
|
||||
session_id: thread_id.to_string(),
|
||||
forked_from_id: None,
|
||||
preview: String::new(),
|
||||
ephemeral: false,
|
||||
|
||||
@@ -363,6 +363,7 @@ async fn review_start_with_detached_delivery_returns_new_thread_id() -> Result<(
|
||||
let started: ThreadStartedNotification =
|
||||
serde_json::from_value(notification.params.expect("params must be present"))?;
|
||||
assert_eq!(started.thread.id, review_thread_id);
|
||||
assert_eq!(started.thread.session_id, review_thread_id);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -101,20 +101,28 @@ async fn thread_fork_creates_new_thread_and_emits_started() -> Result<()> {
|
||||
)
|
||||
.await??;
|
||||
let fork_result = fork_resp.result.clone();
|
||||
let ThreadForkResponse {
|
||||
session_id, thread, ..
|
||||
} = to_response::<ThreadForkResponse>(fork_resp)?;
|
||||
let ThreadForkResponse { thread, .. } = to_response::<ThreadForkResponse>(fork_resp)?;
|
||||
|
||||
// Wire contract: thread title field is `name`, serialized as null when unset.
|
||||
let thread_json = fork_result
|
||||
.get("thread")
|
||||
.and_then(Value::as_object)
|
||||
.expect("thread/fork result.thread must be an object");
|
||||
assert_eq!(
|
||||
thread_json.get("sessionId").and_then(Value::as_str),
|
||||
Some(thread.session_id.as_str()),
|
||||
"forked threads should serialize `sessionId` on the thread object"
|
||||
);
|
||||
assert_eq!(
|
||||
thread_json.get("name"),
|
||||
Some(&Value::Null),
|
||||
"forked threads do not inherit a name; expected `name: null`"
|
||||
);
|
||||
assert_eq!(
|
||||
fork_result.get("sessionId"),
|
||||
None,
|
||||
"thread/fork should not serialize a top-level `sessionId`"
|
||||
);
|
||||
|
||||
let after_contents = std::fs::read_to_string(&original_path)?;
|
||||
assert_eq!(
|
||||
@@ -123,7 +131,7 @@ async fn thread_fork_creates_new_thread_and_emits_started() -> Result<()> {
|
||||
);
|
||||
|
||||
assert_ne!(thread.id, conversation_id);
|
||||
assert_eq!(session_id, thread.id);
|
||||
assert_eq!(thread.session_id, thread.id);
|
||||
assert_eq!(thread.forked_from_id, Some(conversation_id.clone()));
|
||||
assert_eq!(thread.preview, preview);
|
||||
assert_eq!(thread.model_provider, "mock_provider");
|
||||
|
||||
@@ -879,6 +879,7 @@ async fn thread_list_filters_by_source_kind_subagent_thread_spawn() -> Result<()
|
||||
assert_eq!(ids, vec![subagent_id.as_str()]);
|
||||
assert_ne!(cli_id, subagent_id);
|
||||
assert!(matches!(data[0].source, SessionSource::SubAgent(_)));
|
||||
assert_eq!(data[0].session_id, subagent_id);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -77,6 +77,7 @@ async fn thread_metadata_update_patches_git_branch_and_returns_updated_thread()
|
||||
to_response::<ThreadMetadataUpdateResponse>(update_resp)?;
|
||||
|
||||
assert_eq!(updated.id, thread.id);
|
||||
assert_eq!(updated.session_id, thread.session_id);
|
||||
assert_eq!(
|
||||
updated.git_info,
|
||||
Some(GitInfo {
|
||||
@@ -90,6 +91,10 @@ async fn thread_metadata_update_patches_git_branch_and_returns_updated_thread()
|
||||
.get("thread")
|
||||
.and_then(Value::as_object)
|
||||
.expect("thread/metadata/update result.thread must be an object");
|
||||
assert_eq!(
|
||||
updated_thread_json.get("sessionId").and_then(Value::as_str),
|
||||
Some(thread.session_id.as_str())
|
||||
);
|
||||
let updated_git_info_json = updated_thread_json
|
||||
.get("gitInfo")
|
||||
.and_then(Value::as_object)
|
||||
|
||||
@@ -270,10 +270,11 @@ async fn thread_resume_tracks_thread_initialized_analytics() -> Result<()> {
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(resume_id)),
|
||||
)
|
||||
.await??;
|
||||
let ThreadResumeResponse {
|
||||
session_id, thread, ..
|
||||
} = to_response::<ThreadResumeResponse>(resume_resp)?;
|
||||
assert!(!session_id.is_empty(), "session id should not be empty");
|
||||
let ThreadResumeResponse { thread, .. } = to_response::<ThreadResumeResponse>(resume_resp)?;
|
||||
assert!(
|
||||
!thread.session_id.is_empty(),
|
||||
"session id should not be empty"
|
||||
);
|
||||
assert_eq!(thread.thread_source, Some(ThreadSource::User));
|
||||
|
||||
let payload = wait_for_analytics_payload(&server, DEFAULT_READ_TIMEOUT).await?;
|
||||
|
||||
@@ -119,11 +119,16 @@ async fn thread_rollback_drops_last_turns_and_persists_to_rollout() -> Result<()
|
||||
.and_then(Value::as_object)
|
||||
.expect("thread/rollback result.thread must be an object");
|
||||
assert_eq!(rolled_back_thread.name, None);
|
||||
assert_eq!(rolled_back_thread.session_id, thread.session_id);
|
||||
assert_eq!(
|
||||
thread_json.get("name"),
|
||||
Some(&Value::Null),
|
||||
"thread/rollback must serialize `name: null` when unset"
|
||||
);
|
||||
assert_eq!(
|
||||
thread_json.get("sessionId").and_then(Value::as_str),
|
||||
Some(thread.session_id.as_str())
|
||||
);
|
||||
|
||||
assert_eq!(rolled_back_thread.turns.len(), 1);
|
||||
assert_eq!(rolled_back_thread.status, ThreadStatus::Idle);
|
||||
|
||||
@@ -121,12 +121,14 @@ async fn thread_start_creates_thread_and_emits_started() -> Result<()> {
|
||||
.await??;
|
||||
let resp_result = resp.result.clone();
|
||||
let ThreadStartResponse {
|
||||
session_id,
|
||||
thread,
|
||||
model_provider,
|
||||
..
|
||||
} = to_response::<ThreadStartResponse>(resp)?;
|
||||
assert!(!session_id.is_empty(), "session id should not be empty");
|
||||
assert!(
|
||||
!thread.session_id.is_empty(),
|
||||
"session id should not be empty"
|
||||
);
|
||||
assert!(!thread.id.is_empty(), "thread id should not be empty");
|
||||
assert!(
|
||||
thread.preview.is_empty(),
|
||||
@@ -155,11 +157,21 @@ async fn thread_start_creates_thread_and_emits_started() -> Result<()> {
|
||||
.get("thread")
|
||||
.and_then(Value::as_object)
|
||||
.expect("thread/start result.thread must be an object");
|
||||
assert_eq!(
|
||||
thread_json.get("sessionId").and_then(Value::as_str),
|
||||
Some(thread.session_id.as_str()),
|
||||
"new threads should serialize `sessionId` on the thread object"
|
||||
);
|
||||
assert_eq!(
|
||||
thread_json.get("name"),
|
||||
Some(&Value::Null),
|
||||
"new threads should serialize `name: null`"
|
||||
);
|
||||
assert_eq!(
|
||||
resp_result.get("sessionId"),
|
||||
None,
|
||||
"thread/start should not serialize a top-level `sessionId`"
|
||||
);
|
||||
assert_eq!(
|
||||
thread_json.get("ephemeral").and_then(Value::as_bool),
|
||||
Some(false),
|
||||
|
||||
@@ -1060,7 +1060,7 @@ fn session_configured_from_thread_start_response(
|
||||
config: &Config,
|
||||
) -> Result<SessionConfiguredEvent, String> {
|
||||
session_configured_from_thread_response(
|
||||
&response.session_id,
|
||||
&response.thread.session_id,
|
||||
&response.thread.id,
|
||||
response.thread.name.clone(),
|
||||
response.thread.path.clone(),
|
||||
@@ -1085,7 +1085,7 @@ fn session_configured_from_thread_resume_response(
|
||||
config: &Config,
|
||||
) -> Result<SessionConfiguredEvent, String> {
|
||||
session_configured_from_thread_response(
|
||||
&response.session_id,
|
||||
&response.thread.session_id,
|
||||
&response.thread.id,
|
||||
response.thread.name.clone(),
|
||||
response.thread.path.clone(),
|
||||
|
||||
@@ -244,6 +244,7 @@ async fn resume_lookup_model_providers_filters_only_last_lookup() {
|
||||
fn turn_items_for_thread_returns_matching_turn_items() {
|
||||
let thread = AppServerThread {
|
||||
id: "thread-1".to_string(),
|
||||
session_id: "thread-1".to_string(),
|
||||
forked_from_id: None,
|
||||
preview: String::new(),
|
||||
ephemeral: false,
|
||||
@@ -473,9 +474,9 @@ async fn session_configured_from_thread_response_uses_permission_profile_from_re
|
||||
|
||||
fn sample_thread_start_response() -> ThreadStartResponse {
|
||||
ThreadStartResponse {
|
||||
session_id: "67e55044-10b1-426f-9247-bb680e5fe0c7".to_string(),
|
||||
thread: codex_app_server_protocol::Thread {
|
||||
id: "67e55044-10b1-426f-9247-bb680e5fe0c8".to_string(),
|
||||
session_id: "67e55044-10b1-426f-9247-bb680e5fe0c7".to_string(),
|
||||
forked_from_id: None,
|
||||
preview: String::new(),
|
||||
ephemeral: false,
|
||||
|
||||
@@ -118,6 +118,7 @@ mod tests {
|
||||
fn test_thread(thread_id: ThreadId, source: SessionSource) -> Thread {
|
||||
Thread {
|
||||
id: thread_id.to_string(),
|
||||
session_id: thread_id.to_string(),
|
||||
forked_from_id: None,
|
||||
preview: String::new(),
|
||||
ephemeral: false,
|
||||
|
||||
@@ -2940,6 +2940,7 @@ async fn inactive_thread_started_notification_initializes_replay_session() -> Re
|
||||
ServerNotification::ThreadStarted(ThreadStartedNotification {
|
||||
thread: Thread {
|
||||
id: agent_thread_id.to_string(),
|
||||
session_id: agent_thread_id.to_string(),
|
||||
forked_from_id: None,
|
||||
preview: "agent thread".to_string(),
|
||||
ephemeral: false,
|
||||
@@ -3022,6 +3023,7 @@ async fn inactive_thread_started_notification_preserves_primary_model_when_path_
|
||||
ServerNotification::ThreadStarted(ThreadStartedNotification {
|
||||
thread: Thread {
|
||||
id: agent_thread_id.to_string(),
|
||||
session_id: agent_thread_id.to_string(),
|
||||
forked_from_id: None,
|
||||
preview: "agent thread".to_string(),
|
||||
ephemeral: false,
|
||||
@@ -3077,6 +3079,7 @@ async fn thread_read_session_state_does_not_reuse_primary_permission_profile() {
|
||||
|
||||
let thread = Thread {
|
||||
id: read_thread_id.to_string(),
|
||||
session_id: read_thread_id.to_string(),
|
||||
forked_from_id: None,
|
||||
preview: "read thread".to_string(),
|
||||
ephemeral: false,
|
||||
@@ -5041,6 +5044,7 @@ async fn thread_rollback_response_discards_queued_active_thread_events() {
|
||||
&ThreadRollbackResponse {
|
||||
thread: Thread {
|
||||
id: thread_id.to_string(),
|
||||
session_id: thread_id.to_string(),
|
||||
forked_from_id: None,
|
||||
preview: String::new(),
|
||||
ephemeral: false,
|
||||
|
||||
@@ -322,6 +322,7 @@ mod tests {
|
||||
};
|
||||
let read_thread = Thread {
|
||||
id: read_thread_id.to_string(),
|
||||
session_id: read_thread_id.to_string(),
|
||||
forked_from_id: None,
|
||||
preview: "read thread".to_string(),
|
||||
ephemeral: false,
|
||||
|
||||
@@ -1813,9 +1813,9 @@ mod tests {
|
||||
let forked_from_id = ThreadId::new();
|
||||
let read_only_profile = PermissionProfile::read_only();
|
||||
let response = ThreadResumeResponse {
|
||||
session_id: ThreadId::new().to_string(),
|
||||
thread: codex_app_server_protocol::Thread {
|
||||
id: thread_id.to_string(),
|
||||
session_id: ThreadId::new().to_string(),
|
||||
forked_from_id: Some(forked_from_id.to_string()),
|
||||
preview: "hello".to_string(),
|
||||
ephemeral: false,
|
||||
|
||||
@@ -5684,6 +5684,7 @@ session_picker_view = "dense"
|
||||
let thread_id = ThreadId::new();
|
||||
let thread = Thread {
|
||||
id: thread_id.to_string(),
|
||||
session_id: thread_id.to_string(),
|
||||
forked_from_id: None,
|
||||
preview: String::from("remote thread"),
|
||||
ephemeral: false,
|
||||
@@ -5717,6 +5718,7 @@ session_picker_view = "dense"
|
||||
let thread_id = ThreadId::new();
|
||||
let thread = Thread {
|
||||
id: thread_id.to_string(),
|
||||
session_id: thread_id.to_string(),
|
||||
forked_from_id: None,
|
||||
preview: String::from("preview"),
|
||||
ephemeral: false,
|
||||
@@ -5783,6 +5785,7 @@ session_picker_view = "dense"
|
||||
let thread_id = ThreadId::new();
|
||||
let thread = Thread {
|
||||
id: thread_id.to_string(),
|
||||
session_id: thread_id.to_string(),
|
||||
forked_from_id: None,
|
||||
preview: String::from("preview"),
|
||||
ephemeral: false,
|
||||
@@ -5839,6 +5842,7 @@ session_picker_view = "dense"
|
||||
let thread_id = ThreadId::new();
|
||||
let thread = Thread {
|
||||
id: thread_id.to_string(),
|
||||
session_id: thread_id.to_string(),
|
||||
forked_from_id: None,
|
||||
preview: String::from("preview"),
|
||||
ephemeral: false,
|
||||
|
||||
Reference in New Issue
Block a user