mirror of
https://github.com/openai/codex.git
synced 2026-10-20 00:18:49 +00:00
Notify subscribers when thread read state changes (#52384)
## What changed Add the experimental `thread/readState/changed` notification with `threadId` and the committed `readState` receipt. Send it to existing thread subscribers when terminal activity updates read state or a revert clears a removed unread position. Emit terminal read-state updates before `turn/completed` so completion remains the last notification for that result. Expose the notification in generated JSON schemas, TypeScript types, and Python SDK models. Document experimental capability gating, subscription lifetime, notification opt-out, and refreshing receipts when concurrent responses disagree. ## Testing Extend the read-state integration test to assert that the notification receipt matches the receipt returned by `thread/read` after terminal activity. GitOrigin-RevId: 28935352a881b7a5e2e7f629173c3b2061bd7941
This commit is contained in:
@@ -5602,6 +5602,43 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ThreadReadState": {
|
||||
"description": "The first unread position in a durable local thread. A null position means read. The opaque revision guards edits against activity or marks the caller has not seen.",
|
||||
"properties": {
|
||||
"firstUnread": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ThreadUnreadPosition"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"revision": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"revision"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ThreadReadStateChangedNotification": {
|
||||
"properties": {
|
||||
"readState": {
|
||||
"$ref": "#/definitions/ThreadReadState"
|
||||
},
|
||||
"threadId": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"readState",
|
||||
"threadId"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ThreadRealtimeAudioChunk": {
|
||||
"description": "EXPERIMENTAL - thread realtime audio chunk.",
|
||||
"properties": {
|
||||
@@ -6335,6 +6372,48 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ThreadUnreadPosition": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Explicitly marked unread, including when the thread has no turns.",
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"threadStart"
|
||||
],
|
||||
"title": "ThreadStartThreadUnreadPositionType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"title": "ThreadStartThreadUnreadPosition",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"description": "The earliest completed result needing attention.",
|
||||
"properties": {
|
||||
"turnId": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"turn"
|
||||
],
|
||||
"title": "TurnThreadUnreadPositionType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"turnId",
|
||||
"type"
|
||||
],
|
||||
"title": "TurnThreadUnreadPosition",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"TokenUsageBreakdown": {
|
||||
"properties": {
|
||||
"cacheWriteInputTokens": {
|
||||
@@ -7079,6 +7158,26 @@
|
||||
"title": "Thread/status/changedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"method": {
|
||||
"enum": [
|
||||
"thread/readState/changed"
|
||||
],
|
||||
"title": "Thread/readState/changedNotificationMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/ThreadReadStateChangedNotification"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Thread/readState/changedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"method": {
|
||||
|
||||
+37
@@ -4683,6 +4683,26 @@
|
||||
"title": "Thread/status/changedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"method": {
|
||||
"enum": [
|
||||
"thread/readState/changed"
|
||||
],
|
||||
"title": "Thread/readState/changedNotificationMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/v2/ThreadReadStateChangedNotification"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Thread/readState/changedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"method": {
|
||||
@@ -23480,6 +23500,23 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ThreadReadStateChangedNotification": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"readState": {
|
||||
"$ref": "#/definitions/v2/ThreadReadState"
|
||||
},
|
||||
"threadId": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"readState",
|
||||
"threadId"
|
||||
],
|
||||
"title": "ThreadReadStateChangedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
"ThreadRealtimeAudioChunk": {
|
||||
"description": "EXPERIMENTAL - thread realtime audio chunk.",
|
||||
"properties": {
|
||||
|
||||
+37
@@ -16257,6 +16257,26 @@
|
||||
"title": "Thread/status/changedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"method": {
|
||||
"enum": [
|
||||
"thread/readState/changed"
|
||||
],
|
||||
"title": "Thread/readState/changedNotificationMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/ThreadReadStateChangedNotification"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Thread/readState/changedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"method": {
|
||||
@@ -21194,6 +21214,23 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ThreadReadStateChangedNotification": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"readState": {
|
||||
"$ref": "#/definitions/ThreadReadState"
|
||||
},
|
||||
"threadId": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"readState",
|
||||
"threadId"
|
||||
],
|
||||
"title": "ThreadReadStateChangedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
"ThreadRealtimeAudioChunk": {
|
||||
"description": "EXPERIMENTAL - thread realtime audio chunk.",
|
||||
"properties": {
|
||||
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"definitions": {
|
||||
"ThreadReadState": {
|
||||
"description": "The first unread position in a durable local thread. A null position means read. The opaque revision guards edits against activity or marks the caller has not seen.",
|
||||
"properties": {
|
||||
"firstUnread": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ThreadUnreadPosition"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"revision": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"revision"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ThreadUnreadPosition": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Explicitly marked unread, including when the thread has no turns.",
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"threadStart"
|
||||
],
|
||||
"title": "ThreadStartThreadUnreadPositionType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"title": "ThreadStartThreadUnreadPosition",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"description": "The earliest completed result needing attention.",
|
||||
"properties": {
|
||||
"turnId": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"turn"
|
||||
],
|
||||
"title": "TurnThreadUnreadPositionType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"turnId",
|
||||
"type"
|
||||
],
|
||||
"title": "TurnThreadUnreadPosition",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"readState": {
|
||||
"$ref": "#/definitions/ThreadReadState"
|
||||
},
|
||||
"threadId": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"readState",
|
||||
"threadId"
|
||||
],
|
||||
"title": "ThreadReadStateChangedNotification",
|
||||
"type": "object"
|
||||
}
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
File diff suppressed because one or more lines are too long
+2
-1
File diff suppressed because one or more lines are too long
+6
@@ -0,0 +1,6 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { ThreadReadState } from "./ThreadReadState";
|
||||
|
||||
export type ThreadReadStateChangedNotification = { threadId: string, readState: ThreadReadState, };
|
||||
@@ -540,6 +540,7 @@ export type { ThreadQueueChangedNotification } from "./ThreadQueueChangedNotific
|
||||
export type { ThreadReadParams } from "./ThreadReadParams";
|
||||
export type { ThreadReadResponse } from "./ThreadReadResponse";
|
||||
export type { ThreadReadState } from "./ThreadReadState";
|
||||
export type { ThreadReadStateChangedNotification } from "./ThreadReadStateChangedNotification";
|
||||
export type { ThreadRealtimeAudioChunk } from "./ThreadRealtimeAudioChunk";
|
||||
export type { ThreadRealtimeBemItemPresentation } from "./ThreadRealtimeBemItemPresentation";
|
||||
export type { ThreadRealtimeClosedNotification } from "./ThreadRealtimeClosedNotification";
|
||||
|
||||
@@ -1937,6 +1937,8 @@ server_notification_definitions! {
|
||||
Error => "error" (v2::ErrorNotification),
|
||||
ThreadStarted => "thread/started" (v2::ThreadStartedNotification),
|
||||
ThreadStatusChanged => "thread/status/changed" (v2::ThreadStatusChangedNotification),
|
||||
#[experimental("thread/readState/changed")]
|
||||
ThreadReadStateChanged => "thread/readState/changed" (v2::ThreadReadStateChangedNotification),
|
||||
ThreadArchived => "thread/archived" (v2::ThreadArchivedNotification),
|
||||
ThreadDeleted => "thread/deleted" (v2::ThreadDeletedNotification),
|
||||
ThreadUnarchived => "thread/unarchived" (v2::ThreadUnarchivedNotification),
|
||||
|
||||
@@ -2042,6 +2042,14 @@ pub enum ThreadUnreadPosition {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ThreadReadStateChangedNotification {
|
||||
pub thread_id: String,
|
||||
pub read_state: ThreadReadState,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
|
||||
@@ -27,7 +27,8 @@ thread IDs on the returned page to their receipts. Null (or absent on older serv
|
||||
means durable read state is unavailable; a missing map entry means that row is
|
||||
ineligible. Ephemeral, not-yet-persisted, service and product-owned threads are
|
||||
ineligible. Shared `Thread` objects, including search and lifecycle responses,
|
||||
carry no receipts. After start, resume or fork, refresh with `thread/read`.
|
||||
carry no receipts. After start, resume or fork, use `thread/read` to obtain the
|
||||
current receipt.
|
||||
Each local thread has one receipt, with the same storage ownership as the thread.
|
||||
|
||||
A read state is `{firstUnread, revision}`. `firstUnread` is null when read,
|
||||
@@ -42,6 +43,16 @@ clears a removed position; explicit thread-start marks survive. Loading snapshot
|
||||
never acknowledges activity; empty threads start read and forked history does
|
||||
not publish inherited turns as new results.
|
||||
|
||||
Enabling `initialize.capabilities.experimentalApi` permits receiving read-state
|
||||
notifications.
|
||||
|
||||
Started, resumed or forked threads send `thread/readState/changed` alongside
|
||||
ordinary thread events; thread/unsubscribe or disconnect stops delivery.
|
||||
Clients can filter this method with the existing notification opt-out setting.
|
||||
Revisions are equality tokens, not clocks: never compare or merge them across
|
||||
threads or hosts. When concurrent responses disagree, refresh with `thread/read`.
|
||||
Offscreen threads converge on page refresh.
|
||||
|
||||
# Guardian circuit-breaker errors
|
||||
|
||||
Set `auto_review.circuit_break_action = "strict"` to include `TooManyDenials` in
|
||||
|
||||
@@ -57,6 +57,7 @@ pub(crate) fn without_notification_media(notification: ServerNotification) -> Se
|
||||
ServerNotification::Error(_)
|
||||
| ServerNotification::ThreadStarted(_)
|
||||
| ServerNotification::ThreadStatusChanged(_)
|
||||
| ServerNotification::ThreadReadStateChanged(_)
|
||||
| ServerNotification::ThreadArchived(_)
|
||||
| ServerNotification::ThreadDeleted(_)
|
||||
| ServerNotification::ThreadUnarchived(_)
|
||||
|
||||
@@ -364,10 +364,16 @@ pub(super) async fn ensure_listener_task_running(
|
||||
conversation_id,
|
||||
);
|
||||
|
||||
// Make reads after turn/completed reflect the committed frontier.
|
||||
if let Some(summary) = terminal_summary {
|
||||
super::thread_read_state::publish(
|
||||
// Publish the committed receipt before turn/completed so completion
|
||||
// remains the last notification for this terminal result.
|
||||
if let Some(summary) = terminal_summary
|
||||
&& let Some(db) = super::thread_read_state::publish(
|
||||
&event, conversation_id, &conversation, &summary,
|
||||
).await
|
||||
{
|
||||
super::thread_read_state::notify(
|
||||
&db, &thread_state_manager,
|
||||
&outgoing_for_task, conversation_id,
|
||||
).await;
|
||||
}
|
||||
apply_bespoke_event_handling(
|
||||
|
||||
@@ -2303,12 +2303,24 @@ impl ThreadRequestProcessor {
|
||||
codex_state::ReadStateOperation::Read,
|
||||
)
|
||||
.await
|
||||
.map(|_| ()),
|
||||
Ok(None) => Ok(()),
|
||||
.map(|update| matches!(update, codex_state::ReadStateUpdate::Applied(_))),
|
||||
Ok(None) => Ok(false),
|
||||
Err(err) => Err(err),
|
||||
};
|
||||
if let Err(err) = retirement {
|
||||
tracing::warn!("reverted history but could not update unread position: {err}")
|
||||
match retirement {
|
||||
Ok(true) => {
|
||||
super::thread_read_state::notify(
|
||||
db,
|
||||
&self.thread_state_manager,
|
||||
&self.outgoing,
|
||||
thread_id,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
Ok(false) => {}
|
||||
Err(err) => {
|
||||
tracing::warn!("reverted history but could not update unread position: {err}")
|
||||
}
|
||||
}
|
||||
}
|
||||
let response = self
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
//! Read state for durable local threads returned by the thread store.
|
||||
//! Read state for durable local threads. Receipt changes use the thread's usual subscribers.
|
||||
|
||||
use super::*;
|
||||
use codex_app_server_protocol::ThreadReadState;
|
||||
use codex_app_server_protocol::ThreadReadStateChangedNotification;
|
||||
use codex_app_server_protocol::ThreadUnreadPosition;
|
||||
|
||||
// These scopes have product notification policy the local server cannot resolve.
|
||||
@@ -57,6 +58,30 @@ pub(super) async fn snapshots(
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn notify(
|
||||
db: &StateDbHandle,
|
||||
manager: &ThreadStateManager,
|
||||
outgoing: &Arc<OutgoingMessageSender>,
|
||||
id: ThreadId,
|
||||
) {
|
||||
match db.thread_read_states(&[id]).await {
|
||||
Ok(mut states) => {
|
||||
if let Some(state) = states.remove(&id) {
|
||||
let subscribers = manager.subscribed_connection_ids(id).await;
|
||||
ThreadScopedOutgoingMessageSender::new(Arc::clone(outgoing), subscribers, id)
|
||||
.send_server_notification(ServerNotification::ThreadReadStateChanged(
|
||||
ThreadReadStateChangedNotification {
|
||||
thread_id: id.to_string(),
|
||||
read_state: to_api(state),
|
||||
},
|
||||
))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
Err(err) => tracing::warn!("could not project committed read state: {err}"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs only on newly delivered Core events, never on history decoding or replay.
|
||||
pub(super) async fn publish(
|
||||
event: &codex_protocol::protocol::Event,
|
||||
|
||||
@@ -8,6 +8,7 @@ use codex_app_server_protocol::ThreadHistoryMode;
|
||||
use codex_app_server_protocol::ThreadListResponse;
|
||||
use codex_app_server_protocol::ThreadReadParams;
|
||||
use codex_app_server_protocol::ThreadReadResponse;
|
||||
use codex_app_server_protocol::ThreadReadStateChangedNotification;
|
||||
use codex_app_server_protocol::ThreadRevertParams;
|
||||
use codex_app_server_protocol::ThreadRevertResponse;
|
||||
use codex_app_server_protocol::ThreadStartParams;
|
||||
@@ -96,6 +97,13 @@ async fn read_state_tracks_terminal_activity_and_reverted_history() -> Result<()
|
||||
assert_eq!(listed.data[0].id, thread_id);
|
||||
assert_eq!(receipts, [(thread_id.clone(), first.clone())].into());
|
||||
|
||||
let changed: ThreadReadStateChangedNotification = timeout(
|
||||
TIMEOUT,
|
||||
server.read_notification("thread/readState/changed"),
|
||||
)
|
||||
.await??;
|
||||
assert_eq!(changed.read_state, first);
|
||||
|
||||
let _: ThreadRevertResponse = server
|
||||
.request(|request_id| ClientRequest::ThreadRevert {
|
||||
request_id,
|
||||
|
||||
@@ -202,6 +202,7 @@ pub(super) fn server_notification_thread_target(
|
||||
ServerNotification::ProjectChanged(_)
|
||||
| ServerNotification::SkillsChanged(_)
|
||||
| ServerNotification::AccountUpdated(_)
|
||||
| ServerNotification::ThreadReadStateChanged(_)
|
||||
| ServerNotification::GatewayOAuthChanged(_)
|
||||
| ServerNotification::AccountRateLimitsUpdated(_)
|
||||
| ServerNotification::AppListUpdated(_)
|
||||
|
||||
@@ -359,6 +359,7 @@ impl ChatWidget {
|
||||
| ServerNotification::AccountRateLimitsUpdated(_)
|
||||
| ServerNotification::ThreadStarted(_)
|
||||
| ServerNotification::ThreadStatusChanged(_)
|
||||
| ServerNotification::ThreadReadStateChanged(_)
|
||||
| ServerNotification::ThreadReverted(_)
|
||||
| ServerNotification::ThreadQueueChanged(_)
|
||||
| ServerNotification::ThreadPredictionUpdated(_)
|
||||
|
||||
@@ -64,6 +64,7 @@ from .v2_all import ThreadNameUpdatedNotification
|
||||
from .v2_all import ThreadPredictionUpdatedNotification
|
||||
from .v2_all import ThreadProjectUpdatedNotification
|
||||
from .v2_all import ThreadQueueChangedNotification
|
||||
from .v2_all import ThreadReadStateChangedNotification
|
||||
from .v2_all import ThreadRealtimeClosedNotification
|
||||
from .v2_all import ThreadRealtimeErrorNotification
|
||||
from .v2_all import ThreadRealtimeItemAddedNotification
|
||||
@@ -148,6 +149,7 @@ KnownNotificationPayload: TypeAlias = (
|
||||
| ThreadPredictionUpdatedNotification
|
||||
| ThreadProjectUpdatedNotification
|
||||
| ThreadQueueChangedNotification
|
||||
| ThreadReadStateChangedNotification
|
||||
| ThreadRealtimeClosedNotification
|
||||
| ThreadRealtimeErrorNotification
|
||||
| ThreadRealtimeItemAddedNotification
|
||||
@@ -235,6 +237,7 @@ NOTIFICATION_MODELS: dict[str, type[KnownNotificationPayload]] = {
|
||||
"thread/prediction/updated": ThreadPredictionUpdatedNotification,
|
||||
"thread/project/updated": ThreadProjectUpdatedNotification,
|
||||
"thread/queue/changed": ThreadQueueChangedNotification,
|
||||
"thread/readState/changed": ThreadReadStateChangedNotification,
|
||||
"thread/realtime/closed": ThreadRealtimeClosedNotification,
|
||||
"thread/realtime/error": ThreadRealtimeErrorNotification,
|
||||
"thread/realtime/item/completed": ThreadRealtimeItemCompletedNotification,
|
||||
|
||||
@@ -10249,6 +10249,14 @@ class ThreadReadState(BaseModel):
|
||||
revision: str
|
||||
|
||||
|
||||
class ThreadReadStateChangedNotification(BaseModel):
|
||||
model_config = ConfigDict(
|
||||
populate_by_name=True,
|
||||
)
|
||||
read_state: Annotated[ThreadReadState, Field(alias="readState")]
|
||||
thread_id: Annotated[str, Field(alias="threadId")]
|
||||
|
||||
|
||||
class TranscriptSegmentThreadRealtimeItem(BaseModel):
|
||||
model_config = ConfigDict(
|
||||
populate_by_name=True,
|
||||
@@ -11317,6 +11325,24 @@ class ErrorServerNotification(BaseModel):
|
||||
params: ErrorNotification
|
||||
|
||||
|
||||
class ThreadReadStateChangedServerNotification(BaseModel):
|
||||
model_config = ConfigDict(
|
||||
populate_by_name=True,
|
||||
)
|
||||
emitted_at_ms: Annotated[
|
||||
int | None,
|
||||
Field(
|
||||
alias="emittedAtMs",
|
||||
description="Unix timestamp (in milliseconds) when app-server emitted this notification.",
|
||||
),
|
||||
] = None
|
||||
method: Annotated[
|
||||
Literal["thread/readState/changed"],
|
||||
Field(title="Thread/readState/changedNotificationMethod"),
|
||||
]
|
||||
params: ThreadReadStateChangedNotification
|
||||
|
||||
|
||||
class ThreadGoalUpdatedServerNotification(BaseModel):
|
||||
model_config = ConfigDict(
|
||||
populate_by_name=True,
|
||||
@@ -13116,6 +13142,7 @@ class ServerNotification(
|
||||
ErrorServerNotification
|
||||
| ThreadStartedServerNotification
|
||||
| ThreadStatusChangedServerNotification
|
||||
| ThreadReadStateChangedServerNotification
|
||||
| ThreadArchivedServerNotification
|
||||
| ThreadDeletedServerNotification
|
||||
| ThreadUnarchivedServerNotification
|
||||
@@ -13206,6 +13233,7 @@ class ServerNotification(
|
||||
ErrorServerNotification
|
||||
| ThreadStartedServerNotification
|
||||
| ThreadStatusChangedServerNotification
|
||||
| ThreadReadStateChangedServerNotification
|
||||
| ThreadArchivedServerNotification
|
||||
| ThreadDeletedServerNotification
|
||||
| ThreadUnarchivedServerNotification
|
||||
|
||||
Reference in New Issue
Block a user