archastro.platform.channels.api_chat_channel

  1# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved.
  2# This file is auto-generated by @archastro/sdk-generator. Do not edit.
  3# Content hash: 25ed606eeb04
  4
  5from collections.abc import Callable
  6from datetime import datetime
  7from typing import TYPE_CHECKING, Any, Literal, Required, TypedDict
  8
  9if TYPE_CHECKING:
 10    from archastro.phx_channel.socket import Socket
 11
 12
 13class ApiChatForkThreadInput(TypedDict, total=False):
 14    "Fork a sub-thread from an existing message"
 15
 16    message_id: Required[str]
 17    title: str
 18
 19
 20class ApiChatMarkThreadReadInput(TypedDict):
 21    "Mark a thread as read up to a given message"
 22
 23    message_id: str
 24
 25
 26class ApiChatLoadMoreMessagesInput(TypedDict, total=False):
 27    "Load additional messages with cursor-based pagination"
 28
 29    after_cursor: str
 30    before_cursor: str
 31    include_metadata: bool
 32    limit: int
 33
 34
 35class ApiChatPostMessageInput(TypedDict, total=False):
 36    "Post a new message with optional uploads and reply-to"
 37
 38    content: Required[str]
 39    idempotency_key: str
 40    reply_to: str
 41    uploads: list[dict[str, Any]]
 42
 43
 44class ApiChatPostSimpleMessageInput(TypedDict, total=False):
 45    "Post a simple text message"
 46
 47    content: str
 48    idempotency_key: str
 49    reply_to: str
 50
 51
 52class ApiChatEditMessageInput(TypedDict):
 53    "Edit an existing message's content"
 54
 55    content: str
 56    message_id: str
 57
 58
 59class ApiChatDeleteMessageInput(TypedDict):
 60    "Delete a message"
 61
 62    message_id: str
 63
 64
 65class ApiChatAddReactionInput(TypedDict):
 66    "Add an emoji reaction to a message"
 67
 68    emoji: str
 69    message_id: str
 70
 71
 72class ApiChatRemoveReactionInput(TypedDict):
 73    "Remove an emoji reaction from a message"
 74
 75    emoji: str
 76    message_id: str
 77
 78
 79class ApiChatTypingInput(TypedDict):
 80    "Signal that the current user has started or stopped typing in the thread"
 81
 82    is_typing: bool
 83
 84
 85class MessageAddedPayloadMessageActorsItemProfilePicture(TypedDict, total=False):
 86    file: str | None
 87    "ID of the underlying storage file (`fil_...`). `null` when the image is not backed by a platform storage file."
 88    height: int | None
 89    "Height of the image in pixels. `null` if not known."
 90    media: str | None
 91    "ID of the associated media record (`med_...`). `null` when the image is not linked to a media entity."
 92    mime_type: str | None
 93    'MIME type of the image, e.g. `"image/png"` or `"image/jpeg"`. `null` if not known.'
 94    refresh_url: str | None
 95    "Endpoint URL you can call to obtain a fresh signed `url` when the current one has expired. `null` if the URL does not require refreshing."
 96    url: str | None
 97    "Signed or public URL for downloading the image. May be time-limited; use `refresh_url` to obtain a new URL when this one expires."
 98    width: int | None
 99    "Width of the image in pixels. `null` if not known."
100
101
102class MessageAddedPayloadMessageActorsItem(TypedDict, total=False):
103    alias: str | None
104    "Short handle or alias for the actor, used as an alternate display identifier. `null` if not configured."
105    id: str | None
106    'Composite actor identifier. Format is `"user-<usr_...>"` for human users or `"agent-<agi_...>"` for agents.'
107    name: str | None
108    "Display name of the actor shown in the UI. `null` if no name is set."
109    profile_picture: MessageAddedPayloadMessageActorsItemProfilePicture | None
110    "Profile picture for the actor. `null` if the actor has no profile picture."
111
112
113class MessageAddedPayloadMessageAttachmentsItemImageSource(TypedDict, total=False):
114    file: str | None
115    "ID of the underlying storage file (`fil_...`). `null` when the image is not backed by a platform storage file."
116    height: int | None
117    "Height of the image in pixels. `null` if not known."
118    media: str | None
119    "ID of the associated media record (`med_...`). `null` when the image is not linked to a media entity."
120    mime_type: str | None
121    'MIME type of the image, e.g. `"image/png"` or `"image/jpeg"`. `null` if not known.'
122    refresh_url: str | None
123    "Endpoint URL you can call to obtain a fresh signed `url` when the current one has expired. `null` if the URL does not require refreshing."
124    url: str | None
125    "Signed or public URL for downloading the image. May be time-limited; use `refresh_url` to obtain a new URL when this one expires."
126    width: int | None
127    "Width of the image in pixels. `null` if not known."
128
129
130class MessageAddedPayloadMessageAttachmentsItemVariantsItemImageSource(TypedDict, total=False):
131    file: str | None
132    "ID of the underlying storage file (`fil_...`). `null` when the image is not backed by a platform storage file."
133    height: int | None
134    "Height of the image in pixels. `null` if not known."
135    media: str | None
136    "ID of the associated media record (`med_...`). `null` when the image is not linked to a media entity."
137    mime_type: str | None
138    'MIME type of the image, e.g. `"image/png"` or `"image/jpeg"`. `null` if not known.'
139    refresh_url: str | None
140    "Endpoint URL you can call to obtain a fresh signed `url` when the current one has expired. `null` if the URL does not require refreshing."
141    url: str | None
142    "Signed or public URL for downloading the image. May be time-limited; use `refresh_url` to obtain a new URL when this one expires."
143    width: int | None
144    "Width of the image in pixels. `null` if not known."
145
146
147class MessageAddedPayloadMessageAttachmentsItemVariantsItem(TypedDict, total=False):
148    content_type: str | None
149    'MIME type of this variant\'s file (e.g., `"image/jpeg"`, `"video/mp4"`). `null` if the file is not loaded.'
150    created_at: datetime | None
151    "When this variant was created (ISO 8601)."
152    file: str | None
153    "ID of the underlying storage file that backs this variant (`fil_...`)."
154    filename: str | None
155    "Original filename of the uploaded file for this variant. `null` if the file is not loaded."
156    height: int | None
157    "Height of this variant in pixels. `null` if not recorded."
158    id: Required[str]
159    "Media variant ID (`mvr_...`)."
160    image_source: MessageAddedPayloadMessageAttachmentsItemVariantsItemImageSource | None
161    "Resolved image delivery metadata for this variant, including dimensions and CDN URL. `null` for non-image content types."
162    updated_at: datetime | None
163    "When this variant was last updated (ISO 8601)."
164    url: str | None
165    "Signed download URL for this variant, resolved at request time. `null` if the file is unavailable."
166    variant_key: str | None
167    'Identifier for this variant\'s processing tier. Common values include `"original"` (the unmodified upload) and `"thumbnail"` (a resized preview).'
168    width: int | None
169    "Width of this variant in pixels. `null` if not recorded."
170
171
172class MessageAddedPayloadMessageAttachmentsItem(TypedDict, total=False):
173    content_type: str | None
174    'MIME type of the attached file, e.g. `"image/png"` or `"application/pdf"`. Present on `file`, `artifact`, and `media` types. `null` otherwise.'
175    description: str | None
176    "Short description. The page meta-description for `scraped_link`, the artifact description for `artifact`, and the task description for `task` types. `null` on other types."
177    filename: str | None
178    'Original filename of the attached file, e.g. `"report.pdf"`. Present on `file`, `artifact`, and `media` types. `null` otherwise.'
179    height: int | None
180    "Height in pixels of the media item. Present on `media` type only. `null` otherwise."
181    id: Required[str]
182    "Unique identifier for this attachment within the message."
183    image_height: int | None
184    "Height in pixels of the scraped preview image. Present on `scraped_link` type only. `null` otherwise."
185    image_source: MessageAddedPayloadMessageAttachmentsItemImageSource | None
186    "Image source metadata for inline rendering. Present on `file`, `scraped_link`, `artifact`, and `media` types when the content is an image. `null` otherwise."
187    image_url: str | None
188    "URL of the preview image extracted from the scraped page. Present on `scraped_link` type only. `null` otherwise."
189    image_width: int | None
190    "Width in pixels of the scraped preview image. Present on `scraped_link` type only. `null` otherwise."
191    media_type: str | None
192    'The media category, e.g. `"video"` or `"audio"`. Present on `media` type only. `null` otherwise.'
193    name: str | None
194    "Display name of the media item. Present on `media` type only. `null` otherwise."
195    object: dict[str, Any] | None
196    "The full embedded object payload. For `task` type, contains the task record. For `action` type, contains the action definition. `null` on other types."
197    title: str | None
198    "Display title. The page title for `scraped_link`, the artifact name for `artifact`, and the task title for `task` types. `null` on other types."
199    type: Required[str]
200    'The attachment type. One of `"file"`, `"scraped_link"`, `"artifact"`, `"task"`, `"media"`, or `"action"`. Determines which additional fields are present.'
201    url: str | None
202    "URL to access the resource. A signed download URL for `file` and `artifact` types; the original URL for `scraped_link`; a media playback URL for `media`. `null` on `task` and `action` types."
203    variants: list[MessageAddedPayloadMessageAttachmentsItemVariantsItem] | None
204    "Array of available encoding variants for the media item (e.g. different resolutions). Present on `media` type only. `null` otherwise."
205    version: int | None
206    "Version number of the attached artifact at the time of attachment. Present on `artifact` type only. `null` otherwise."
207    width: int | None
208    "Width in pixels of the media item. Present on `media` type only. `null` otherwise."
209
210
211class MessageAddedPayloadMessageReactionsItem(TypedDict, total=False):
212    payload: dict[str, Any] | None
213    'Type-specific reaction data. For `"emoji_reaction"` reactions, contains an `emoji` key with the Unicode emoji string (e.g., `" "`).'
214    type: Required[str]
215    'Reaction type identifier. Currently always `"emoji_reaction"` for emoji-based reactions.'
216    user: str | None
217    "Public ID of the user who added the reaction (`usr_...`)."
218
219
220class MessageAddedPayloadMessage(TypedDict, total=False):
221    actors: list[MessageAddedPayloadMessageActorsItem] | None
222    "Resolved actor descriptors for the message sender, combining identity and display metadata. Always contains exactly one entry."
223    agent: str | None
224    "ID of the agent user that sent this message (`agi_...`). `null` for messages sent by human users."
225    agent_mode: Literal["cli", "embedded"] | None
226    "Local agent execution mode for this message. One of `cli`, `embedded`, or `null` when the message was not created by a local agent execution path."
227    attachments: list[MessageAddedPayloadMessageAttachmentsItem] | None
228    "Files, links, tasks, media, artifacts, and actions attached to this message. Empty array if there are no attachments."
229    branched_thread: str | None
230    "ID of the thread that was branched from this message (`thr_...`). `null` if this message has not spawned a branch thread."
231    content: str | None
232    "Text content of the message. `null` for messages that contain only attachments."
233    created_at: datetime | None
234    "When the message was posted (ISO 8601)."
235    has_replies: bool | None
236    "Whether this message has at least one reply. Only present when explicitly requested or computed by the server."
237    id: Required[str]
238    "Message ID (`msg_...`)."
239    idempotency_key: str | None
240    "Client-supplied idempotency key used to deduplicate message sends. `null` if the sender did not provide one."
241    legacy_agent: str | None
242    "Identifier of the legacy chat agent that sent this message, if applicable. `null` for messages sent by users or modern agent users."
243    metadata: dict[str, Any] | None
244    "Arbitrary key-value metadata attached to the message. Always present; defaults to an empty object when no metadata has been set."
245    org: str | None
246    "ID of the organization that owns this message (`org_...`)."
247    reactions: list[MessageAddedPayloadMessageReactionsItem] | None
248    "Emoji and other reactions added to this message by users. Empty array if no reactions have been added or the association is not preloaded."
249    rendering_mode: str | None
250    'Display hint for how the message should be rendered. One of `"reply"`, `"direct"`, or `"inline"`. `null` for user-authored messages, which are always rendered as standard replies.'
251    replies: list[dict[str, Any]] | None
252    "Inline array of reply messages, each serialized as a full message object. Only present when the server has preloaded replies for this message."
253    replies_after_cursor: str | None
254    "Opaque pagination cursor to fetch replies posted after the current page. Only present when inline replies are included in the response."
255    replies_before_cursor: str | None
256    "Opaque pagination cursor to fetch replies posted before the current page. Only present when inline replies are included in the response."
257    reply_count: int | None
258    "Total number of direct replies to this message. Only present when explicitly requested or computed by the server."
259    reply_to: dict[str, Any] | None
260    "The parent message this message is a reply to, expanded as a full message object when loaded. `null` if this is a top-level message or the association is not preloaded."
261    sandbox: str | None
262    "ID of the developer sandbox this message belongs to (`dsb_...`). `null` for non-sandbox messages."
263    team: str | None
264    "ID of the team this message is scoped to (`tem_...`). `null` if the message is not team-scoped."
265    thread: str | None
266    "ID of the thread this message belongs to (`thr_...`). `null` for messages not yet associated with a thread."
267    user: str | None
268    "The human user who sent this message. Returns a public ID string (`usr_...`) when the association is not preloaded, or an expanded user object when it is. `null` for messages sent by agents."
269
270
271class MessageAddedPayload(TypedDict, total=False):
272    "Broadcast when a new message is added to a thread"
273
274    after_cursor: str | None
275    before_cursor: str | None
276    message: MessageAddedPayloadMessage | None
277    "A chat message posted in a thread, including its content, author, attachments, reactions, and optional reply metadata."
278    thread_id: str | None
279
280
281class MessageUpdatedPayloadMessageActorsItemProfilePicture(TypedDict, total=False):
282    file: str | None
283    "ID of the underlying storage file (`fil_...`). `null` when the image is not backed by a platform storage file."
284    height: int | None
285    "Height of the image in pixels. `null` if not known."
286    media: str | None
287    "ID of the associated media record (`med_...`). `null` when the image is not linked to a media entity."
288    mime_type: str | None
289    'MIME type of the image, e.g. `"image/png"` or `"image/jpeg"`. `null` if not known.'
290    refresh_url: str | None
291    "Endpoint URL you can call to obtain a fresh signed `url` when the current one has expired. `null` if the URL does not require refreshing."
292    url: str | None
293    "Signed or public URL for downloading the image. May be time-limited; use `refresh_url` to obtain a new URL when this one expires."
294    width: int | None
295    "Width of the image in pixels. `null` if not known."
296
297
298class MessageUpdatedPayloadMessageActorsItem(TypedDict, total=False):
299    alias: str | None
300    "Short handle or alias for the actor, used as an alternate display identifier. `null` if not configured."
301    id: str | None
302    'Composite actor identifier. Format is `"user-<usr_...>"` for human users or `"agent-<agi_...>"` for agents.'
303    name: str | None
304    "Display name of the actor shown in the UI. `null` if no name is set."
305    profile_picture: MessageUpdatedPayloadMessageActorsItemProfilePicture | None
306    "Profile picture for the actor. `null` if the actor has no profile picture."
307
308
309class MessageUpdatedPayloadMessageAttachmentsItemImageSource(TypedDict, total=False):
310    file: str | None
311    "ID of the underlying storage file (`fil_...`). `null` when the image is not backed by a platform storage file."
312    height: int | None
313    "Height of the image in pixels. `null` if not known."
314    media: str | None
315    "ID of the associated media record (`med_...`). `null` when the image is not linked to a media entity."
316    mime_type: str | None
317    'MIME type of the image, e.g. `"image/png"` or `"image/jpeg"`. `null` if not known.'
318    refresh_url: str | None
319    "Endpoint URL you can call to obtain a fresh signed `url` when the current one has expired. `null` if the URL does not require refreshing."
320    url: str | None
321    "Signed or public URL for downloading the image. May be time-limited; use `refresh_url` to obtain a new URL when this one expires."
322    width: int | None
323    "Width of the image in pixels. `null` if not known."
324
325
326class MessageUpdatedPayloadMessageAttachmentsItemVariantsItemImageSource(TypedDict, total=False):
327    file: str | None
328    "ID of the underlying storage file (`fil_...`). `null` when the image is not backed by a platform storage file."
329    height: int | None
330    "Height of the image in pixels. `null` if not known."
331    media: str | None
332    "ID of the associated media record (`med_...`). `null` when the image is not linked to a media entity."
333    mime_type: str | None
334    'MIME type of the image, e.g. `"image/png"` or `"image/jpeg"`. `null` if not known.'
335    refresh_url: str | None
336    "Endpoint URL you can call to obtain a fresh signed `url` when the current one has expired. `null` if the URL does not require refreshing."
337    url: str | None
338    "Signed or public URL for downloading the image. May be time-limited; use `refresh_url` to obtain a new URL when this one expires."
339    width: int | None
340    "Width of the image in pixels. `null` if not known."
341
342
343class MessageUpdatedPayloadMessageAttachmentsItemVariantsItem(TypedDict, total=False):
344    content_type: str | None
345    'MIME type of this variant\'s file (e.g., `"image/jpeg"`, `"video/mp4"`). `null` if the file is not loaded.'
346    created_at: datetime | None
347    "When this variant was created (ISO 8601)."
348    file: str | None
349    "ID of the underlying storage file that backs this variant (`fil_...`)."
350    filename: str | None
351    "Original filename of the uploaded file for this variant. `null` if the file is not loaded."
352    height: int | None
353    "Height of this variant in pixels. `null` if not recorded."
354    id: Required[str]
355    "Media variant ID (`mvr_...`)."
356    image_source: MessageUpdatedPayloadMessageAttachmentsItemVariantsItemImageSource | None
357    "Resolved image delivery metadata for this variant, including dimensions and CDN URL. `null` for non-image content types."
358    updated_at: datetime | None
359    "When this variant was last updated (ISO 8601)."
360    url: str | None
361    "Signed download URL for this variant, resolved at request time. `null` if the file is unavailable."
362    variant_key: str | None
363    'Identifier for this variant\'s processing tier. Common values include `"original"` (the unmodified upload) and `"thumbnail"` (a resized preview).'
364    width: int | None
365    "Width of this variant in pixels. `null` if not recorded."
366
367
368class MessageUpdatedPayloadMessageAttachmentsItem(TypedDict, total=False):
369    content_type: str | None
370    'MIME type of the attached file, e.g. `"image/png"` or `"application/pdf"`. Present on `file`, `artifact`, and `media` types. `null` otherwise.'
371    description: str | None
372    "Short description. The page meta-description for `scraped_link`, the artifact description for `artifact`, and the task description for `task` types. `null` on other types."
373    filename: str | None
374    'Original filename of the attached file, e.g. `"report.pdf"`. Present on `file`, `artifact`, and `media` types. `null` otherwise.'
375    height: int | None
376    "Height in pixels of the media item. Present on `media` type only. `null` otherwise."
377    id: Required[str]
378    "Unique identifier for this attachment within the message."
379    image_height: int | None
380    "Height in pixels of the scraped preview image. Present on `scraped_link` type only. `null` otherwise."
381    image_source: MessageUpdatedPayloadMessageAttachmentsItemImageSource | None
382    "Image source metadata for inline rendering. Present on `file`, `scraped_link`, `artifact`, and `media` types when the content is an image. `null` otherwise."
383    image_url: str | None
384    "URL of the preview image extracted from the scraped page. Present on `scraped_link` type only. `null` otherwise."
385    image_width: int | None
386    "Width in pixels of the scraped preview image. Present on `scraped_link` type only. `null` otherwise."
387    media_type: str | None
388    'The media category, e.g. `"video"` or `"audio"`. Present on `media` type only. `null` otherwise.'
389    name: str | None
390    "Display name of the media item. Present on `media` type only. `null` otherwise."
391    object: dict[str, Any] | None
392    "The full embedded object payload. For `task` type, contains the task record. For `action` type, contains the action definition. `null` on other types."
393    title: str | None
394    "Display title. The page title for `scraped_link`, the artifact name for `artifact`, and the task title for `task` types. `null` on other types."
395    type: Required[str]
396    'The attachment type. One of `"file"`, `"scraped_link"`, `"artifact"`, `"task"`, `"media"`, or `"action"`. Determines which additional fields are present.'
397    url: str | None
398    "URL to access the resource. A signed download URL for `file` and `artifact` types; the original URL for `scraped_link`; a media playback URL for `media`. `null` on `task` and `action` types."
399    variants: list[MessageUpdatedPayloadMessageAttachmentsItemVariantsItem] | None
400    "Array of available encoding variants for the media item (e.g. different resolutions). Present on `media` type only. `null` otherwise."
401    version: int | None
402    "Version number of the attached artifact at the time of attachment. Present on `artifact` type only. `null` otherwise."
403    width: int | None
404    "Width in pixels of the media item. Present on `media` type only. `null` otherwise."
405
406
407class MessageUpdatedPayloadMessageReactionsItem(TypedDict, total=False):
408    payload: dict[str, Any] | None
409    'Type-specific reaction data. For `"emoji_reaction"` reactions, contains an `emoji` key with the Unicode emoji string (e.g., `" "`).'
410    type: Required[str]
411    'Reaction type identifier. Currently always `"emoji_reaction"` for emoji-based reactions.'
412    user: str | None
413    "Public ID of the user who added the reaction (`usr_...`)."
414
415
416class MessageUpdatedPayloadMessage(TypedDict, total=False):
417    actors: list[MessageUpdatedPayloadMessageActorsItem] | None
418    "Resolved actor descriptors for the message sender, combining identity and display metadata. Always contains exactly one entry."
419    agent: str | None
420    "ID of the agent user that sent this message (`agi_...`). `null` for messages sent by human users."
421    agent_mode: Literal["cli", "embedded"] | None
422    "Local agent execution mode for this message. One of `cli`, `embedded`, or `null` when the message was not created by a local agent execution path."
423    attachments: list[MessageUpdatedPayloadMessageAttachmentsItem] | None
424    "Files, links, tasks, media, artifacts, and actions attached to this message. Empty array if there are no attachments."
425    branched_thread: str | None
426    "ID of the thread that was branched from this message (`thr_...`). `null` if this message has not spawned a branch thread."
427    content: str | None
428    "Text content of the message. `null` for messages that contain only attachments."
429    created_at: datetime | None
430    "When the message was posted (ISO 8601)."
431    has_replies: bool | None
432    "Whether this message has at least one reply. Only present when explicitly requested or computed by the server."
433    id: Required[str]
434    "Message ID (`msg_...`)."
435    idempotency_key: str | None
436    "Client-supplied idempotency key used to deduplicate message sends. `null` if the sender did not provide one."
437    legacy_agent: str | None
438    "Identifier of the legacy chat agent that sent this message, if applicable. `null` for messages sent by users or modern agent users."
439    metadata: dict[str, Any] | None
440    "Arbitrary key-value metadata attached to the message. Always present; defaults to an empty object when no metadata has been set."
441    org: str | None
442    "ID of the organization that owns this message (`org_...`)."
443    reactions: list[MessageUpdatedPayloadMessageReactionsItem] | None
444    "Emoji and other reactions added to this message by users. Empty array if no reactions have been added or the association is not preloaded."
445    rendering_mode: str | None
446    'Display hint for how the message should be rendered. One of `"reply"`, `"direct"`, or `"inline"`. `null` for user-authored messages, which are always rendered as standard replies.'
447    replies: list[dict[str, Any]] | None
448    "Inline array of reply messages, each serialized as a full message object. Only present when the server has preloaded replies for this message."
449    replies_after_cursor: str | None
450    "Opaque pagination cursor to fetch replies posted after the current page. Only present when inline replies are included in the response."
451    replies_before_cursor: str | None
452    "Opaque pagination cursor to fetch replies posted before the current page. Only present when inline replies are included in the response."
453    reply_count: int | None
454    "Total number of direct replies to this message. Only present when explicitly requested or computed by the server."
455    reply_to: dict[str, Any] | None
456    "The parent message this message is a reply to, expanded as a full message object when loaded. `null` if this is a top-level message or the association is not preloaded."
457    sandbox: str | None
458    "ID of the developer sandbox this message belongs to (`dsb_...`). `null` for non-sandbox messages."
459    team: str | None
460    "ID of the team this message is scoped to (`tem_...`). `null` if the message is not team-scoped."
461    thread: str | None
462    "ID of the thread this message belongs to (`thr_...`). `null` for messages not yet associated with a thread."
463    user: str | None
464    "The human user who sent this message. Returns a public ID string (`usr_...`) when the association is not preloaded, or an expanded user object when it is. `null` for messages sent by agents."
465
466
467class MessageUpdatedPayload(TypedDict, total=False):
468    "Broadcast when a message is updated or removed"
469
470    message: MessageUpdatedPayloadMessage | None
471    "A chat message posted in a thread, including its content, author, attachments, reactions, and optional reply metadata."
472    thread_id: str | None
473
474
475class ThreadEventPayload(TypedDict, total=False):
476    "Broadcast thread-level events (agent updates, read state, unread counts)"
477
478    payload: dict[str, Any] | None
479    thread_id: str | None
480    type: str | None
481
482
483class SystemEventPayload(TypedDict, total=False):
484    "Broadcast system-wide events"
485
486    event: dict[str, Any] | None
487
488
489class TypingPayloadActorProfilePicture(TypedDict, total=False):
490    file: str | None
491    "ID of the underlying storage file (`fil_...`). `null` when the image is not backed by a platform storage file."
492    height: int | None
493    "Height of the image in pixels. `null` if not known."
494    media: str | None
495    "ID of the associated media record (`med_...`). `null` when the image is not linked to a media entity."
496    mime_type: str | None
497    'MIME type of the image, e.g. `"image/png"` or `"image/jpeg"`. `null` if not known.'
498    refresh_url: str | None
499    "Endpoint URL you can call to obtain a fresh signed `url` when the current one has expired. `null` if the URL does not require refreshing."
500    url: str | None
501    "Signed or public URL for downloading the image. May be time-limited; use `refresh_url` to obtain a new URL when this one expires."
502    width: int | None
503    "Width of the image in pixels. `null` if not known."
504
505
506class TypingPayloadActor(TypedDict, total=False):
507    alias: str | None
508    "Short handle or alias for the actor, used as an alternate display identifier. `null` if not configured."
509    id: str | None
510    'Composite actor identifier. Format is `"user-<usr_...>"` for human users or `"agent-<agi_...>"` for agents.'
511    name: str | None
512    "Display name of the actor shown in the UI. `null` if no name is set."
513    profile_picture: TypingPayloadActorProfilePicture | None
514    "Profile picture for the actor. `null` if the actor has no profile picture."
515
516
517class TypingPayload(TypedDict, total=False):
518    "Broadcast when a participant (human or agent) starts or stops typing. Ephemeral; never persisted."
519
520    actor: TypingPayloadActor | None
521    "The entity that authored a message, either a human user or an agent."
522    is_typing: bool | None
523    thread_id: str | None
524
525
526# Channel for real-time chat messaging.
527# Supports team-scoped and user-scoped threads with keyed, transient, and direct
528# thread access patterns.
529class ApiChatChannel:
530    def __init__(self, channel, join_response=None):
531        self._channel = channel
532        self.join_response = join_response
533
534    # Join a team-scoped thread by ID
535    @staticmethod
536    def topic_team_thread(team_id: str, thread_id: str) -> str:
537        return f"api:chat:team:{team_id}:thread:{thread_id}"
538
539    # Join a team-scoped thread by ID
540    @classmethod
541    async def join_team_thread(
542        cls,
543        socket: "Socket",
544        team_id: str,
545        thread_id: str,
546        *,
547        after_cursor: str | None = None,
548        before_cursor: str | None = None,
549        include_metadata: bool | None = None,
550        limit: int | None = None,
551    ) -> "ApiChatChannel":
552        topic = cls.topic_team_thread(team_id, thread_id)
553        channel = socket.channel(topic)
554        payload: dict[str, object] = {}
555        if after_cursor is not None:
556            payload["after_cursor"] = after_cursor
557        if before_cursor is not None:
558            payload["before_cursor"] = before_cursor
559        if include_metadata is not None:
560            payload["include_metadata"] = include_metadata
561        if limit is not None:
562            payload["limit"] = limit
563        join_response = await channel.join(payload)
564        return cls(channel, join_response)
565
566    # Join or create a team-scoped keyed thread
567    @staticmethod
568    def topic_team_keyed(team_id: str, key: str) -> str:
569        return f"api:chat:team:{team_id}:key:{key}"
570
571    # Join or create a team-scoped keyed thread
572    @classmethod
573    async def join_team_keyed(
574        cls,
575        socket: "Socket",
576        team_id: str,
577        key: str,
578        *,
579        after_cursor: str | None = None,
580        before_cursor: str | None = None,
581        include_metadata: bool | None = None,
582        limit: int | None = None,
583    ) -> "ApiChatChannel":
584        topic = cls.topic_team_keyed(team_id, key)
585        channel = socket.channel(topic)
586        payload: dict[str, object] = {}
587        if after_cursor is not None:
588            payload["after_cursor"] = after_cursor
589        if before_cursor is not None:
590            payload["before_cursor"] = before_cursor
591        if include_metadata is not None:
592            payload["include_metadata"] = include_metadata
593        if limit is not None:
594            payload["limit"] = limit
595        join_response = await channel.join(payload)
596        return cls(channel, join_response)
597
598    # Join a team-scoped transient (ephemeral) thread
599    @staticmethod
600    def topic_team_transient(team_id: str, key: str) -> str:
601        return f"api:chat:team:{team_id}:transient:{key}"
602
603    # Join a team-scoped transient (ephemeral) thread
604    @classmethod
605    async def join_team_transient(
606        cls,
607        socket: "Socket",
608        team_id: str,
609        key: str,
610        *,
611        after_cursor: str | None = None,
612        before_cursor: str | None = None,
613        include_metadata: bool | None = None,
614        limit: int | None = None,
615    ) -> "ApiChatChannel":
616        topic = cls.topic_team_transient(team_id, key)
617        channel = socket.channel(topic)
618        payload: dict[str, object] = {}
619        if after_cursor is not None:
620            payload["after_cursor"] = after_cursor
621        if before_cursor is not None:
622            payload["before_cursor"] = before_cursor
623        if include_metadata is not None:
624            payload["include_metadata"] = include_metadata
625        if limit is not None:
626            payload["limit"] = limit
627        join_response = await channel.join(payload)
628        return cls(channel, join_response)
629
630    # Join a user-scoped thread by ID
631    @staticmethod
632    def topic_user_thread(thread_id: str) -> str:
633        return f"api:chat:user:thread:{thread_id}"
634
635    # Join a user-scoped thread by ID
636    @classmethod
637    async def join_user_thread(
638        cls,
639        socket: "Socket",
640        thread_id: str,
641        *,
642        after_cursor: str | None = None,
643        before_cursor: str | None = None,
644        include_metadata: bool | None = None,
645        limit: int | None = None,
646    ) -> "ApiChatChannel":
647        topic = cls.topic_user_thread(thread_id)
648        channel = socket.channel(topic)
649        payload: dict[str, object] = {}
650        if after_cursor is not None:
651            payload["after_cursor"] = after_cursor
652        if before_cursor is not None:
653            payload["before_cursor"] = before_cursor
654        if include_metadata is not None:
655            payload["include_metadata"] = include_metadata
656        if limit is not None:
657            payload["limit"] = limit
658        join_response = await channel.join(payload)
659        return cls(channel, join_response)
660
661    # Join or create a user-scoped keyed thread
662    @staticmethod
663    def topic_user_keyed(key: str) -> str:
664        return f"api:chat:user:key:{key}"
665
666    # Join or create a user-scoped keyed thread
667    @classmethod
668    async def join_user_keyed(
669        cls,
670        socket: "Socket",
671        key: str,
672        *,
673        after_cursor: str | None = None,
674        before_cursor: str | None = None,
675        include_metadata: bool | None = None,
676        limit: int | None = None,
677    ) -> "ApiChatChannel":
678        topic = cls.topic_user_keyed(key)
679        channel = socket.channel(topic)
680        payload: dict[str, object] = {}
681        if after_cursor is not None:
682            payload["after_cursor"] = after_cursor
683        if before_cursor is not None:
684            payload["before_cursor"] = before_cursor
685        if include_metadata is not None:
686            payload["include_metadata"] = include_metadata
687        if limit is not None:
688            payload["limit"] = limit
689        join_response = await channel.join(payload)
690        return cls(channel, join_response)
691
692    # Join a user-scoped transient (ephemeral) thread
693    @staticmethod
694    def topic_user_transient(key: str) -> str:
695        return f"api:chat:user:transient:{key}"
696
697    # Join a user-scoped transient (ephemeral) thread
698    @classmethod
699    async def join_user_transient(
700        cls,
701        socket: "Socket",
702        key: str,
703        *,
704        after_cursor: str | None = None,
705        before_cursor: str | None = None,
706        include_metadata: bool | None = None,
707        limit: int | None = None,
708    ) -> "ApiChatChannel":
709        topic = cls.topic_user_transient(key)
710        channel = socket.channel(topic)
711        payload: dict[str, object] = {}
712        if after_cursor is not None:
713            payload["after_cursor"] = after_cursor
714        if before_cursor is not None:
715            payload["before_cursor"] = before_cursor
716        if include_metadata is not None:
717            payload["include_metadata"] = include_metadata
718        if limit is not None:
719            payload["limit"] = limit
720        join_response = await channel.join(payload)
721        return cls(channel, join_response)
722
723    # Leave the underlying channel.
724    async def leave(self):
725        await self._channel.leave()
726
727    # Fork a sub-thread from an existing message
728    async def api_chat_fork_thread(self, payload: ApiChatForkThreadInput) -> dict[str, Any]:
729        return await self._channel.push("api:chat:fork_thread", payload)
730
731    # Mark a thread as read up to a given message
732    async def api_chat_mark_thread_read(
733        self, payload: ApiChatMarkThreadReadInput
734    ) -> dict[str, Any]:
735        return await self._channel.push("api:chat:mark_thread_read", payload)
736
737    # List all messages in the current thread
738    async def api_chat_list_messages(self, payload: dict) -> dict[str, Any]:
739        return await self._channel.push("api:chat:list_messages", payload)
740
741    # Load additional messages with cursor-based pagination
742    async def api_chat_load_more_messages(
743        self, payload: ApiChatLoadMoreMessagesInput
744    ) -> dict[str, Any]:
745        return await self._channel.push("api:chat:load_more_messages", payload)
746
747    # Post a new message with optional uploads and reply-to
748    async def api_chat_post_message(self, payload: ApiChatPostMessageInput) -> dict[str, Any]:
749        return await self._channel.push("api:chat:post_message", payload)
750
751    # Post a simple text message
752    async def api_chat_post_simple_message(
753        self, payload: ApiChatPostSimpleMessageInput
754    ) -> dict[str, Any]:
755        return await self._channel.push("api:chat:post_simple_message", payload)
756
757    # Edit an existing message's content
758    async def api_chat_edit_message(self, payload: ApiChatEditMessageInput) -> dict[str, Any]:
759        return await self._channel.push("api:chat:edit_message", payload)
760
761    # Delete a message
762    async def api_chat_delete_message(self, payload: ApiChatDeleteMessageInput) -> dict[str, Any]:
763        return await self._channel.push("api:chat:delete_message", payload)
764
765    # Add an emoji reaction to a message
766    async def api_chat_add_reaction(self, payload: ApiChatAddReactionInput) -> dict[str, Any]:
767        return await self._channel.push("api:chat:add_reaction", payload)
768
769    # Remove an emoji reaction from a message
770    async def api_chat_remove_reaction(self, payload: ApiChatRemoveReactionInput) -> dict[str, Any]:
771        return await self._channel.push("api:chat:remove_reaction", payload)
772
773    # Signal that the current user has started or stopped typing in the thread
774    async def api_chat_typing(self, payload: ApiChatTypingInput) -> dict[str, Any]:
775        return await self._channel.push("api:chat:typing", payload)
776
777    # Broadcast when a new message is added to a thread
778    def on_message_added(
779        self, callback: Callable[[MessageAddedPayload], None]
780    ) -> Callable[[], None]:
781        return self._channel.on("message_added", callback)
782
783    # Broadcast when a message is updated or removed
784    def on_message_updated(
785        self, callback: Callable[[MessageUpdatedPayload], None]
786    ) -> Callable[[], None]:
787        return self._channel.on("message_updated", callback)
788
789    # Broadcast thread-level events (agent updates, read state, unread counts)
790    def on_thread_event(self, callback: Callable[[ThreadEventPayload], None]) -> Callable[[], None]:
791        return self._channel.on("thread_event", callback)
792
793    # Broadcast system-wide events
794    def on_system_event(self, callback: Callable[[SystemEventPayload], None]) -> Callable[[], None]:
795        return self._channel.on("system_event", callback)
796
797    # Broadcast when a participant (human or agent) starts or stops typing. Ephemeral; never persisted.
798    def on_typing(self, callback: Callable[[TypingPayload], None]) -> Callable[[], None]:
799        return self._channel.on("typing", callback)
class ApiChatForkThreadInput(typing.TypedDict):
14class ApiChatForkThreadInput(TypedDict, total=False):
15    "Fork a sub-thread from an existing message"
16
17    message_id: Required[str]
18    title: str

Fork a sub-thread from an existing message

message_id: Required[str]
title: str
class ApiChatMarkThreadReadInput(typing.TypedDict):
21class ApiChatMarkThreadReadInput(TypedDict):
22    "Mark a thread as read up to a given message"
23
24    message_id: str

Mark a thread as read up to a given message

message_id: str
class ApiChatLoadMoreMessagesInput(typing.TypedDict):
27class ApiChatLoadMoreMessagesInput(TypedDict, total=False):
28    "Load additional messages with cursor-based pagination"
29
30    after_cursor: str
31    before_cursor: str
32    include_metadata: bool
33    limit: int

Load additional messages with cursor-based pagination

after_cursor: str
before_cursor: str
include_metadata: bool
limit: int
class ApiChatPostMessageInput(typing.TypedDict):
36class ApiChatPostMessageInput(TypedDict, total=False):
37    "Post a new message with optional uploads and reply-to"
38
39    content: Required[str]
40    idempotency_key: str
41    reply_to: str
42    uploads: list[dict[str, Any]]

Post a new message with optional uploads and reply-to

content: Required[str]
idempotency_key: str
reply_to: str
uploads: list[dict[str, typing.Any]]
class ApiChatPostSimpleMessageInput(typing.TypedDict):
45class ApiChatPostSimpleMessageInput(TypedDict, total=False):
46    "Post a simple text message"
47
48    content: str
49    idempotency_key: str
50    reply_to: str

Post a simple text message

content: str
idempotency_key: str
reply_to: str
class ApiChatEditMessageInput(typing.TypedDict):
53class ApiChatEditMessageInput(TypedDict):
54    "Edit an existing message's content"
55
56    content: str
57    message_id: str

Edit an existing message's content

content: str
message_id: str
class ApiChatDeleteMessageInput(typing.TypedDict):
60class ApiChatDeleteMessageInput(TypedDict):
61    "Delete a message"
62
63    message_id: str

Delete a message

message_id: str
class ApiChatAddReactionInput(typing.TypedDict):
66class ApiChatAddReactionInput(TypedDict):
67    "Add an emoji reaction to a message"
68
69    emoji: str
70    message_id: str

Add an emoji reaction to a message

emoji: str
message_id: str
class ApiChatRemoveReactionInput(typing.TypedDict):
73class ApiChatRemoveReactionInput(TypedDict):
74    "Remove an emoji reaction from a message"
75
76    emoji: str
77    message_id: str

Remove an emoji reaction from a message

emoji: str
message_id: str
class ApiChatTypingInput(typing.TypedDict):
80class ApiChatTypingInput(TypedDict):
81    "Signal that the current user has started or stopped typing in the thread"
82
83    is_typing: bool

Signal that the current user has started or stopped typing in the thread

is_typing: bool
class MessageAddedPayloadMessageActorsItemProfilePicture(typing.TypedDict):
 86class MessageAddedPayloadMessageActorsItemProfilePicture(TypedDict, total=False):
 87    file: str | None
 88    "ID of the underlying storage file (`fil_...`). `null` when the image is not backed by a platform storage file."
 89    height: int | None
 90    "Height of the image in pixels. `null` if not known."
 91    media: str | None
 92    "ID of the associated media record (`med_...`). `null` when the image is not linked to a media entity."
 93    mime_type: str | None
 94    'MIME type of the image, e.g. `"image/png"` or `"image/jpeg"`. `null` if not known.'
 95    refresh_url: str | None
 96    "Endpoint URL you can call to obtain a fresh signed `url` when the current one has expired. `null` if the URL does not require refreshing."
 97    url: str | None
 98    "Signed or public URL for downloading the image. May be time-limited; use `refresh_url` to obtain a new URL when this one expires."
 99    width: int | None
100    "Width of the image in pixels. `null` if not known."
file: str | None

ID of the underlying storage file (fil_...). null when the image is not backed by a platform storage file.

height: int | None

Height of the image in pixels. null if not known.

media: str | None

ID of the associated media record (med_...). null when the image is not linked to a media entity.

mime_type: str | None

MIME type of the image, e.g. "image/png" or "image/jpeg". null if not known.

refresh_url: str | None

Endpoint URL you can call to obtain a fresh signed url when the current one has expired. null if the URL does not require refreshing.

url: str | None

Signed or public URL for downloading the image. May be time-limited; use refresh_url to obtain a new URL when this one expires.

width: int | None

Width of the image in pixels. null if not known.

class MessageAddedPayloadMessageActorsItem(typing.TypedDict):
103class MessageAddedPayloadMessageActorsItem(TypedDict, total=False):
104    alias: str | None
105    "Short handle or alias for the actor, used as an alternate display identifier. `null` if not configured."
106    id: str | None
107    'Composite actor identifier. Format is `"user-<usr_...>"` for human users or `"agent-<agi_...>"` for agents.'
108    name: str | None
109    "Display name of the actor shown in the UI. `null` if no name is set."
110    profile_picture: MessageAddedPayloadMessageActorsItemProfilePicture | None
111    "Profile picture for the actor. `null` if the actor has no profile picture."
alias: str | None

Short handle or alias for the actor, used as an alternate display identifier. null if not configured.

id: str | None

Composite actor identifier. Format is "user-<usr_...>" for human users or "agent-<agi_...>" for agents.

name: str | None

Display name of the actor shown in the UI. null if no name is set.

Profile picture for the actor. null if the actor has no profile picture.

class MessageAddedPayloadMessageAttachmentsItemImageSource(typing.TypedDict):
114class MessageAddedPayloadMessageAttachmentsItemImageSource(TypedDict, total=False):
115    file: str | None
116    "ID of the underlying storage file (`fil_...`). `null` when the image is not backed by a platform storage file."
117    height: int | None
118    "Height of the image in pixels. `null` if not known."
119    media: str | None
120    "ID of the associated media record (`med_...`). `null` when the image is not linked to a media entity."
121    mime_type: str | None
122    'MIME type of the image, e.g. `"image/png"` or `"image/jpeg"`. `null` if not known.'
123    refresh_url: str | None
124    "Endpoint URL you can call to obtain a fresh signed `url` when the current one has expired. `null` if the URL does not require refreshing."
125    url: str | None
126    "Signed or public URL for downloading the image. May be time-limited; use `refresh_url` to obtain a new URL when this one expires."
127    width: int | None
128    "Width of the image in pixels. `null` if not known."
file: str | None

ID of the underlying storage file (fil_...). null when the image is not backed by a platform storage file.

height: int | None

Height of the image in pixels. null if not known.

media: str | None

ID of the associated media record (med_...). null when the image is not linked to a media entity.

mime_type: str | None

MIME type of the image, e.g. "image/png" or "image/jpeg". null if not known.

refresh_url: str | None

Endpoint URL you can call to obtain a fresh signed url when the current one has expired. null if the URL does not require refreshing.

url: str | None

Signed or public URL for downloading the image. May be time-limited; use refresh_url to obtain a new URL when this one expires.

width: int | None

Width of the image in pixels. null if not known.

class MessageAddedPayloadMessageAttachmentsItemVariantsItemImageSource(typing.TypedDict):
131class MessageAddedPayloadMessageAttachmentsItemVariantsItemImageSource(TypedDict, total=False):
132    file: str | None
133    "ID of the underlying storage file (`fil_...`). `null` when the image is not backed by a platform storage file."
134    height: int | None
135    "Height of the image in pixels. `null` if not known."
136    media: str | None
137    "ID of the associated media record (`med_...`). `null` when the image is not linked to a media entity."
138    mime_type: str | None
139    'MIME type of the image, e.g. `"image/png"` or `"image/jpeg"`. `null` if not known.'
140    refresh_url: str | None
141    "Endpoint URL you can call to obtain a fresh signed `url` when the current one has expired. `null` if the URL does not require refreshing."
142    url: str | None
143    "Signed or public URL for downloading the image. May be time-limited; use `refresh_url` to obtain a new URL when this one expires."
144    width: int | None
145    "Width of the image in pixels. `null` if not known."
file: str | None

ID of the underlying storage file (fil_...). null when the image is not backed by a platform storage file.

height: int | None

Height of the image in pixels. null if not known.

media: str | None

ID of the associated media record (med_...). null when the image is not linked to a media entity.

mime_type: str | None

MIME type of the image, e.g. "image/png" or "image/jpeg". null if not known.

refresh_url: str | None

Endpoint URL you can call to obtain a fresh signed url when the current one has expired. null if the URL does not require refreshing.

url: str | None

Signed or public URL for downloading the image. May be time-limited; use refresh_url to obtain a new URL when this one expires.

width: int | None

Width of the image in pixels. null if not known.

class MessageAddedPayloadMessageAttachmentsItemVariantsItem(typing.TypedDict):
148class MessageAddedPayloadMessageAttachmentsItemVariantsItem(TypedDict, total=False):
149    content_type: str | None
150    'MIME type of this variant\'s file (e.g., `"image/jpeg"`, `"video/mp4"`). `null` if the file is not loaded.'
151    created_at: datetime | None
152    "When this variant was created (ISO 8601)."
153    file: str | None
154    "ID of the underlying storage file that backs this variant (`fil_...`)."
155    filename: str | None
156    "Original filename of the uploaded file for this variant. `null` if the file is not loaded."
157    height: int | None
158    "Height of this variant in pixels. `null` if not recorded."
159    id: Required[str]
160    "Media variant ID (`mvr_...`)."
161    image_source: MessageAddedPayloadMessageAttachmentsItemVariantsItemImageSource | None
162    "Resolved image delivery metadata for this variant, including dimensions and CDN URL. `null` for non-image content types."
163    updated_at: datetime | None
164    "When this variant was last updated (ISO 8601)."
165    url: str | None
166    "Signed download URL for this variant, resolved at request time. `null` if the file is unavailable."
167    variant_key: str | None
168    'Identifier for this variant\'s processing tier. Common values include `"original"` (the unmodified upload) and `"thumbnail"` (a resized preview).'
169    width: int | None
170    "Width of this variant in pixels. `null` if not recorded."
content_type: str | None

MIME type of this variant's file (e.g., "image/jpeg", "video/mp4"). null if the file is not loaded.

created_at: datetime.datetime | None

When this variant was created (ISO 8601).

file: str | None

ID of the underlying storage file that backs this variant (fil_...).

filename: str | None

Original filename of the uploaded file for this variant. null if the file is not loaded.

height: int | None

Height of this variant in pixels. null if not recorded.

id: Required[str]

Media variant ID (mvr_...).

Resolved image delivery metadata for this variant, including dimensions and CDN URL. null for non-image content types.

updated_at: datetime.datetime | None

When this variant was last updated (ISO 8601).

url: str | None

Signed download URL for this variant, resolved at request time. null if the file is unavailable.

variant_key: str | None

Identifier for this variant's processing tier. Common values include "original" (the unmodified upload) and "thumbnail" (a resized preview).

width: int | None

Width of this variant in pixels. null if not recorded.

class MessageAddedPayloadMessageAttachmentsItem(typing.TypedDict):
173class MessageAddedPayloadMessageAttachmentsItem(TypedDict, total=False):
174    content_type: str | None
175    'MIME type of the attached file, e.g. `"image/png"` or `"application/pdf"`. Present on `file`, `artifact`, and `media` types. `null` otherwise.'
176    description: str | None
177    "Short description. The page meta-description for `scraped_link`, the artifact description for `artifact`, and the task description for `task` types. `null` on other types."
178    filename: str | None
179    'Original filename of the attached file, e.g. `"report.pdf"`. Present on `file`, `artifact`, and `media` types. `null` otherwise.'
180    height: int | None
181    "Height in pixels of the media item. Present on `media` type only. `null` otherwise."
182    id: Required[str]
183    "Unique identifier for this attachment within the message."
184    image_height: int | None
185    "Height in pixels of the scraped preview image. Present on `scraped_link` type only. `null` otherwise."
186    image_source: MessageAddedPayloadMessageAttachmentsItemImageSource | None
187    "Image source metadata for inline rendering. Present on `file`, `scraped_link`, `artifact`, and `media` types when the content is an image. `null` otherwise."
188    image_url: str | None
189    "URL of the preview image extracted from the scraped page. Present on `scraped_link` type only. `null` otherwise."
190    image_width: int | None
191    "Width in pixels of the scraped preview image. Present on `scraped_link` type only. `null` otherwise."
192    media_type: str | None
193    'The media category, e.g. `"video"` or `"audio"`. Present on `media` type only. `null` otherwise.'
194    name: str | None
195    "Display name of the media item. Present on `media` type only. `null` otherwise."
196    object: dict[str, Any] | None
197    "The full embedded object payload. For `task` type, contains the task record. For `action` type, contains the action definition. `null` on other types."
198    title: str | None
199    "Display title. The page title for `scraped_link`, the artifact name for `artifact`, and the task title for `task` types. `null` on other types."
200    type: Required[str]
201    'The attachment type. One of `"file"`, `"scraped_link"`, `"artifact"`, `"task"`, `"media"`, or `"action"`. Determines which additional fields are present.'
202    url: str | None
203    "URL to access the resource. A signed download URL for `file` and `artifact` types; the original URL for `scraped_link`; a media playback URL for `media`. `null` on `task` and `action` types."
204    variants: list[MessageAddedPayloadMessageAttachmentsItemVariantsItem] | None
205    "Array of available encoding variants for the media item (e.g. different resolutions). Present on `media` type only. `null` otherwise."
206    version: int | None
207    "Version number of the attached artifact at the time of attachment. Present on `artifact` type only. `null` otherwise."
208    width: int | None
209    "Width in pixels of the media item. Present on `media` type only. `null` otherwise."
content_type: str | None

MIME type of the attached file, e.g. "image/png" or "application/pdf". Present on file, artifact, and media types. null otherwise.

description: str | None

Short description. The page meta-description for scraped_link, the artifact description for artifact, and the task description for task types. null on other types.

filename: str | None

Original filename of the attached file, e.g. "report.pdf". Present on file, artifact, and media types. null otherwise.

height: int | None

Height in pixels of the media item. Present on media type only. null otherwise.

id: Required[str]

Unique identifier for this attachment within the message.

image_height: int | None

Height in pixels of the scraped preview image. Present on scraped_link type only. null otherwise.

Image source metadata for inline rendering. Present on file, scraped_link, artifact, and media types when the content is an image. null otherwise.

image_url: str | None

URL of the preview image extracted from the scraped page. Present on scraped_link type only. null otherwise.

image_width: int | None

Width in pixels of the scraped preview image. Present on scraped_link type only. null otherwise.

media_type: str | None

The media category, e.g. "video" or "audio". Present on media type only. null otherwise.

name: str | None

Display name of the media item. Present on media type only. null otherwise.

object: dict[str, typing.Any] | None

The full embedded object payload. For task type, contains the task record. For action type, contains the action definition. null on other types.

title: str | None

Display title. The page title for scraped_link, the artifact name for artifact, and the task title for task types. null on other types.

type: Required[str]

The attachment type. One of "file", "scraped_link", "artifact", "task", "media", or "action". Determines which additional fields are present.

url: str | None

URL to access the resource. A signed download URL for file and artifact types; the original URL for scraped_link; a media playback URL for media. null on task and action types.

Array of available encoding variants for the media item (e.g. different resolutions). Present on media type only. null otherwise.

version: int | None

Version number of the attached artifact at the time of attachment. Present on artifact type only. null otherwise.

width: int | None

Width in pixels of the media item. Present on media type only. null otherwise.

class MessageAddedPayloadMessageReactionsItem(typing.TypedDict):
212class MessageAddedPayloadMessageReactionsItem(TypedDict, total=False):
213    payload: dict[str, Any] | None
214    'Type-specific reaction data. For `"emoji_reaction"` reactions, contains an `emoji` key with the Unicode emoji string (e.g., `" "`).'
215    type: Required[str]
216    'Reaction type identifier. Currently always `"emoji_reaction"` for emoji-based reactions.'
217    user: str | None
218    "Public ID of the user who added the reaction (`usr_...`)."
payload: dict[str, typing.Any] | None

Type-specific reaction data. For "emoji_reaction" reactions, contains an emoji key with the Unicode emoji string (e.g., " ").

type: Required[str]

Reaction type identifier. Currently always "emoji_reaction" for emoji-based reactions.

user: str | None

Public ID of the user who added the reaction (usr_...).

class MessageAddedPayloadMessage(typing.TypedDict):
221class MessageAddedPayloadMessage(TypedDict, total=False):
222    actors: list[MessageAddedPayloadMessageActorsItem] | None
223    "Resolved actor descriptors for the message sender, combining identity and display metadata. Always contains exactly one entry."
224    agent: str | None
225    "ID of the agent user that sent this message (`agi_...`). `null` for messages sent by human users."
226    agent_mode: Literal["cli", "embedded"] | None
227    "Local agent execution mode for this message. One of `cli`, `embedded`, or `null` when the message was not created by a local agent execution path."
228    attachments: list[MessageAddedPayloadMessageAttachmentsItem] | None
229    "Files, links, tasks, media, artifacts, and actions attached to this message. Empty array if there are no attachments."
230    branched_thread: str | None
231    "ID of the thread that was branched from this message (`thr_...`). `null` if this message has not spawned a branch thread."
232    content: str | None
233    "Text content of the message. `null` for messages that contain only attachments."
234    created_at: datetime | None
235    "When the message was posted (ISO 8601)."
236    has_replies: bool | None
237    "Whether this message has at least one reply. Only present when explicitly requested or computed by the server."
238    id: Required[str]
239    "Message ID (`msg_...`)."
240    idempotency_key: str | None
241    "Client-supplied idempotency key used to deduplicate message sends. `null` if the sender did not provide one."
242    legacy_agent: str | None
243    "Identifier of the legacy chat agent that sent this message, if applicable. `null` for messages sent by users or modern agent users."
244    metadata: dict[str, Any] | None
245    "Arbitrary key-value metadata attached to the message. Always present; defaults to an empty object when no metadata has been set."
246    org: str | None
247    "ID of the organization that owns this message (`org_...`)."
248    reactions: list[MessageAddedPayloadMessageReactionsItem] | None
249    "Emoji and other reactions added to this message by users. Empty array if no reactions have been added or the association is not preloaded."
250    rendering_mode: str | None
251    'Display hint for how the message should be rendered. One of `"reply"`, `"direct"`, or `"inline"`. `null` for user-authored messages, which are always rendered as standard replies.'
252    replies: list[dict[str, Any]] | None
253    "Inline array of reply messages, each serialized as a full message object. Only present when the server has preloaded replies for this message."
254    replies_after_cursor: str | None
255    "Opaque pagination cursor to fetch replies posted after the current page. Only present when inline replies are included in the response."
256    replies_before_cursor: str | None
257    "Opaque pagination cursor to fetch replies posted before the current page. Only present when inline replies are included in the response."
258    reply_count: int | None
259    "Total number of direct replies to this message. Only present when explicitly requested or computed by the server."
260    reply_to: dict[str, Any] | None
261    "The parent message this message is a reply to, expanded as a full message object when loaded. `null` if this is a top-level message or the association is not preloaded."
262    sandbox: str | None
263    "ID of the developer sandbox this message belongs to (`dsb_...`). `null` for non-sandbox messages."
264    team: str | None
265    "ID of the team this message is scoped to (`tem_...`). `null` if the message is not team-scoped."
266    thread: str | None
267    "ID of the thread this message belongs to (`thr_...`). `null` for messages not yet associated with a thread."
268    user: str | None
269    "The human user who sent this message. Returns a public ID string (`usr_...`) when the association is not preloaded, or an expanded user object when it is. `null` for messages sent by agents."

Resolved actor descriptors for the message sender, combining identity and display metadata. Always contains exactly one entry.

agent: str | None

ID of the agent user that sent this message (agi_...). null for messages sent by human users.

agent_mode: Optional[Literal['cli', 'embedded']]

Local agent execution mode for this message. One of cli, embedded, or null when the message was not created by a local agent execution path.

attachments: list[MessageAddedPayloadMessageAttachmentsItem] | None

Files, links, tasks, media, artifacts, and actions attached to this message. Empty array if there are no attachments.

branched_thread: str | None

ID of the thread that was branched from this message (thr_...). null if this message has not spawned a branch thread.

content: str | None

Text content of the message. null for messages that contain only attachments.

created_at: datetime.datetime | None

When the message was posted (ISO 8601).

has_replies: bool | None

Whether this message has at least one reply. Only present when explicitly requested or computed by the server.

id: Required[str]

Message ID (msg_...).

idempotency_key: str | None

Client-supplied idempotency key used to deduplicate message sends. null if the sender did not provide one.

legacy_agent: str | None

Identifier of the legacy chat agent that sent this message, if applicable. null for messages sent by users or modern agent users.

metadata: dict[str, typing.Any] | None

Arbitrary key-value metadata attached to the message. Always present; defaults to an empty object when no metadata has been set.

org: str | None

ID of the organization that owns this message (org_...).

reactions: list[MessageAddedPayloadMessageReactionsItem] | None

Emoji and other reactions added to this message by users. Empty array if no reactions have been added or the association is not preloaded.

rendering_mode: str | None

Display hint for how the message should be rendered. One of "reply", "direct", or "inline". null for user-authored messages, which are always rendered as standard replies.

replies: list[dict[str, typing.Any]] | None

Inline array of reply messages, each serialized as a full message object. Only present when the server has preloaded replies for this message.

replies_after_cursor: str | None

Opaque pagination cursor to fetch replies posted after the current page. Only present when inline replies are included in the response.

replies_before_cursor: str | None

Opaque pagination cursor to fetch replies posted before the current page. Only present when inline replies are included in the response.

reply_count: int | None

Total number of direct replies to this message. Only present when explicitly requested or computed by the server.

reply_to: dict[str, typing.Any] | None

The parent message this message is a reply to, expanded as a full message object when loaded. null if this is a top-level message or the association is not preloaded.

sandbox: str | None

ID of the developer sandbox this message belongs to (dsb_...). null for non-sandbox messages.

team: str | None

ID of the team this message is scoped to (tem_...). null if the message is not team-scoped.

thread: str | None

ID of the thread this message belongs to (thr_...). null for messages not yet associated with a thread.

user: str | None

The human user who sent this message. Returns a public ID string (usr_...) when the association is not preloaded, or an expanded user object when it is. null for messages sent by agents.

class MessageAddedPayload(typing.TypedDict):
272class MessageAddedPayload(TypedDict, total=False):
273    "Broadcast when a new message is added to a thread"
274
275    after_cursor: str | None
276    before_cursor: str | None
277    message: MessageAddedPayloadMessage | None
278    "A chat message posted in a thread, including its content, author, attachments, reactions, and optional reply metadata."
279    thread_id: str | None

Broadcast when a new message is added to a thread

after_cursor: str | None
before_cursor: str | None
message: MessageAddedPayloadMessage | None

A chat message posted in a thread, including its content, author, attachments, reactions, and optional reply metadata.

thread_id: str | None
class MessageUpdatedPayloadMessageActorsItemProfilePicture(typing.TypedDict):
282class MessageUpdatedPayloadMessageActorsItemProfilePicture(TypedDict, total=False):
283    file: str | None
284    "ID of the underlying storage file (`fil_...`). `null` when the image is not backed by a platform storage file."
285    height: int | None
286    "Height of the image in pixels. `null` if not known."
287    media: str | None
288    "ID of the associated media record (`med_...`). `null` when the image is not linked to a media entity."
289    mime_type: str | None
290    'MIME type of the image, e.g. `"image/png"` or `"image/jpeg"`. `null` if not known.'
291    refresh_url: str | None
292    "Endpoint URL you can call to obtain a fresh signed `url` when the current one has expired. `null` if the URL does not require refreshing."
293    url: str | None
294    "Signed or public URL for downloading the image. May be time-limited; use `refresh_url` to obtain a new URL when this one expires."
295    width: int | None
296    "Width of the image in pixels. `null` if not known."
file: str | None

ID of the underlying storage file (fil_...). null when the image is not backed by a platform storage file.

height: int | None

Height of the image in pixels. null if not known.

media: str | None

ID of the associated media record (med_...). null when the image is not linked to a media entity.

mime_type: str | None

MIME type of the image, e.g. "image/png" or "image/jpeg". null if not known.

refresh_url: str | None

Endpoint URL you can call to obtain a fresh signed url when the current one has expired. null if the URL does not require refreshing.

url: str | None

Signed or public URL for downloading the image. May be time-limited; use refresh_url to obtain a new URL when this one expires.

width: int | None

Width of the image in pixels. null if not known.

class MessageUpdatedPayloadMessageActorsItem(typing.TypedDict):
299class MessageUpdatedPayloadMessageActorsItem(TypedDict, total=False):
300    alias: str | None
301    "Short handle or alias for the actor, used as an alternate display identifier. `null` if not configured."
302    id: str | None
303    'Composite actor identifier. Format is `"user-<usr_...>"` for human users or `"agent-<agi_...>"` for agents.'
304    name: str | None
305    "Display name of the actor shown in the UI. `null` if no name is set."
306    profile_picture: MessageUpdatedPayloadMessageActorsItemProfilePicture | None
307    "Profile picture for the actor. `null` if the actor has no profile picture."
alias: str | None

Short handle or alias for the actor, used as an alternate display identifier. null if not configured.

id: str | None

Composite actor identifier. Format is "user-<usr_...>" for human users or "agent-<agi_...>" for agents.

name: str | None

Display name of the actor shown in the UI. null if no name is set.

Profile picture for the actor. null if the actor has no profile picture.

class MessageUpdatedPayloadMessageAttachmentsItemImageSource(typing.TypedDict):
310class MessageUpdatedPayloadMessageAttachmentsItemImageSource(TypedDict, total=False):
311    file: str | None
312    "ID of the underlying storage file (`fil_...`). `null` when the image is not backed by a platform storage file."
313    height: int | None
314    "Height of the image in pixels. `null` if not known."
315    media: str | None
316    "ID of the associated media record (`med_...`). `null` when the image is not linked to a media entity."
317    mime_type: str | None
318    'MIME type of the image, e.g. `"image/png"` or `"image/jpeg"`. `null` if not known.'
319    refresh_url: str | None
320    "Endpoint URL you can call to obtain a fresh signed `url` when the current one has expired. `null` if the URL does not require refreshing."
321    url: str | None
322    "Signed or public URL for downloading the image. May be time-limited; use `refresh_url` to obtain a new URL when this one expires."
323    width: int | None
324    "Width of the image in pixels. `null` if not known."
file: str | None

ID of the underlying storage file (fil_...). null when the image is not backed by a platform storage file.

height: int | None

Height of the image in pixels. null if not known.

media: str | None

ID of the associated media record (med_...). null when the image is not linked to a media entity.

mime_type: str | None

MIME type of the image, e.g. "image/png" or "image/jpeg". null if not known.

refresh_url: str | None

Endpoint URL you can call to obtain a fresh signed url when the current one has expired. null if the URL does not require refreshing.

url: str | None

Signed or public URL for downloading the image. May be time-limited; use refresh_url to obtain a new URL when this one expires.

width: int | None

Width of the image in pixels. null if not known.

class MessageUpdatedPayloadMessageAttachmentsItemVariantsItemImageSource(typing.TypedDict):
327class MessageUpdatedPayloadMessageAttachmentsItemVariantsItemImageSource(TypedDict, total=False):
328    file: str | None
329    "ID of the underlying storage file (`fil_...`). `null` when the image is not backed by a platform storage file."
330    height: int | None
331    "Height of the image in pixels. `null` if not known."
332    media: str | None
333    "ID of the associated media record (`med_...`). `null` when the image is not linked to a media entity."
334    mime_type: str | None
335    'MIME type of the image, e.g. `"image/png"` or `"image/jpeg"`. `null` if not known.'
336    refresh_url: str | None
337    "Endpoint URL you can call to obtain a fresh signed `url` when the current one has expired. `null` if the URL does not require refreshing."
338    url: str | None
339    "Signed or public URL for downloading the image. May be time-limited; use `refresh_url` to obtain a new URL when this one expires."
340    width: int | None
341    "Width of the image in pixels. `null` if not known."
file: str | None

ID of the underlying storage file (fil_...). null when the image is not backed by a platform storage file.

height: int | None

Height of the image in pixels. null if not known.

media: str | None

ID of the associated media record (med_...). null when the image is not linked to a media entity.

mime_type: str | None

MIME type of the image, e.g. "image/png" or "image/jpeg". null if not known.

refresh_url: str | None

Endpoint URL you can call to obtain a fresh signed url when the current one has expired. null if the URL does not require refreshing.

url: str | None

Signed or public URL for downloading the image. May be time-limited; use refresh_url to obtain a new URL when this one expires.

width: int | None

Width of the image in pixels. null if not known.

class MessageUpdatedPayloadMessageAttachmentsItemVariantsItem(typing.TypedDict):
344class MessageUpdatedPayloadMessageAttachmentsItemVariantsItem(TypedDict, total=False):
345    content_type: str | None
346    'MIME type of this variant\'s file (e.g., `"image/jpeg"`, `"video/mp4"`). `null` if the file is not loaded.'
347    created_at: datetime | None
348    "When this variant was created (ISO 8601)."
349    file: str | None
350    "ID of the underlying storage file that backs this variant (`fil_...`)."
351    filename: str | None
352    "Original filename of the uploaded file for this variant. `null` if the file is not loaded."
353    height: int | None
354    "Height of this variant in pixels. `null` if not recorded."
355    id: Required[str]
356    "Media variant ID (`mvr_...`)."
357    image_source: MessageUpdatedPayloadMessageAttachmentsItemVariantsItemImageSource | None
358    "Resolved image delivery metadata for this variant, including dimensions and CDN URL. `null` for non-image content types."
359    updated_at: datetime | None
360    "When this variant was last updated (ISO 8601)."
361    url: str | None
362    "Signed download URL for this variant, resolved at request time. `null` if the file is unavailable."
363    variant_key: str | None
364    'Identifier for this variant\'s processing tier. Common values include `"original"` (the unmodified upload) and `"thumbnail"` (a resized preview).'
365    width: int | None
366    "Width of this variant in pixels. `null` if not recorded."
content_type: str | None

MIME type of this variant's file (e.g., "image/jpeg", "video/mp4"). null if the file is not loaded.

created_at: datetime.datetime | None

When this variant was created (ISO 8601).

file: str | None

ID of the underlying storage file that backs this variant (fil_...).

filename: str | None

Original filename of the uploaded file for this variant. null if the file is not loaded.

height: int | None

Height of this variant in pixels. null if not recorded.

id: Required[str]

Media variant ID (mvr_...).

Resolved image delivery metadata for this variant, including dimensions and CDN URL. null for non-image content types.

updated_at: datetime.datetime | None

When this variant was last updated (ISO 8601).

url: str | None

Signed download URL for this variant, resolved at request time. null if the file is unavailable.

variant_key: str | None

Identifier for this variant's processing tier. Common values include "original" (the unmodified upload) and "thumbnail" (a resized preview).

width: int | None

Width of this variant in pixels. null if not recorded.

class MessageUpdatedPayloadMessageAttachmentsItem(typing.TypedDict):
369class MessageUpdatedPayloadMessageAttachmentsItem(TypedDict, total=False):
370    content_type: str | None
371    'MIME type of the attached file, e.g. `"image/png"` or `"application/pdf"`. Present on `file`, `artifact`, and `media` types. `null` otherwise.'
372    description: str | None
373    "Short description. The page meta-description for `scraped_link`, the artifact description for `artifact`, and the task description for `task` types. `null` on other types."
374    filename: str | None
375    'Original filename of the attached file, e.g. `"report.pdf"`. Present on `file`, `artifact`, and `media` types. `null` otherwise.'
376    height: int | None
377    "Height in pixels of the media item. Present on `media` type only. `null` otherwise."
378    id: Required[str]
379    "Unique identifier for this attachment within the message."
380    image_height: int | None
381    "Height in pixels of the scraped preview image. Present on `scraped_link` type only. `null` otherwise."
382    image_source: MessageUpdatedPayloadMessageAttachmentsItemImageSource | None
383    "Image source metadata for inline rendering. Present on `file`, `scraped_link`, `artifact`, and `media` types when the content is an image. `null` otherwise."
384    image_url: str | None
385    "URL of the preview image extracted from the scraped page. Present on `scraped_link` type only. `null` otherwise."
386    image_width: int | None
387    "Width in pixels of the scraped preview image. Present on `scraped_link` type only. `null` otherwise."
388    media_type: str | None
389    'The media category, e.g. `"video"` or `"audio"`. Present on `media` type only. `null` otherwise.'
390    name: str | None
391    "Display name of the media item. Present on `media` type only. `null` otherwise."
392    object: dict[str, Any] | None
393    "The full embedded object payload. For `task` type, contains the task record. For `action` type, contains the action definition. `null` on other types."
394    title: str | None
395    "Display title. The page title for `scraped_link`, the artifact name for `artifact`, and the task title for `task` types. `null` on other types."
396    type: Required[str]
397    'The attachment type. One of `"file"`, `"scraped_link"`, `"artifact"`, `"task"`, `"media"`, or `"action"`. Determines which additional fields are present.'
398    url: str | None
399    "URL to access the resource. A signed download URL for `file` and `artifact` types; the original URL for `scraped_link`; a media playback URL for `media`. `null` on `task` and `action` types."
400    variants: list[MessageUpdatedPayloadMessageAttachmentsItemVariantsItem] | None
401    "Array of available encoding variants for the media item (e.g. different resolutions). Present on `media` type only. `null` otherwise."
402    version: int | None
403    "Version number of the attached artifact at the time of attachment. Present on `artifact` type only. `null` otherwise."
404    width: int | None
405    "Width in pixels of the media item. Present on `media` type only. `null` otherwise."
content_type: str | None

MIME type of the attached file, e.g. "image/png" or "application/pdf". Present on file, artifact, and media types. null otherwise.

description: str | None

Short description. The page meta-description for scraped_link, the artifact description for artifact, and the task description for task types. null on other types.

filename: str | None

Original filename of the attached file, e.g. "report.pdf". Present on file, artifact, and media types. null otherwise.

height: int | None

Height in pixels of the media item. Present on media type only. null otherwise.

id: Required[str]

Unique identifier for this attachment within the message.

image_height: int | None

Height in pixels of the scraped preview image. Present on scraped_link type only. null otherwise.

Image source metadata for inline rendering. Present on file, scraped_link, artifact, and media types when the content is an image. null otherwise.

image_url: str | None

URL of the preview image extracted from the scraped page. Present on scraped_link type only. null otherwise.

image_width: int | None

Width in pixels of the scraped preview image. Present on scraped_link type only. null otherwise.

media_type: str | None

The media category, e.g. "video" or "audio". Present on media type only. null otherwise.

name: str | None

Display name of the media item. Present on media type only. null otherwise.

object: dict[str, typing.Any] | None

The full embedded object payload. For task type, contains the task record. For action type, contains the action definition. null on other types.

title: str | None

Display title. The page title for scraped_link, the artifact name for artifact, and the task title for task types. null on other types.

type: Required[str]

The attachment type. One of "file", "scraped_link", "artifact", "task", "media", or "action". Determines which additional fields are present.

url: str | None

URL to access the resource. A signed download URL for file and artifact types; the original URL for scraped_link; a media playback URL for media. null on task and action types.

Array of available encoding variants for the media item (e.g. different resolutions). Present on media type only. null otherwise.

version: int | None

Version number of the attached artifact at the time of attachment. Present on artifact type only. null otherwise.

width: int | None

Width in pixels of the media item. Present on media type only. null otherwise.

class MessageUpdatedPayloadMessageReactionsItem(typing.TypedDict):
408class MessageUpdatedPayloadMessageReactionsItem(TypedDict, total=False):
409    payload: dict[str, Any] | None
410    'Type-specific reaction data. For `"emoji_reaction"` reactions, contains an `emoji` key with the Unicode emoji string (e.g., `" "`).'
411    type: Required[str]
412    'Reaction type identifier. Currently always `"emoji_reaction"` for emoji-based reactions.'
413    user: str | None
414    "Public ID of the user who added the reaction (`usr_...`)."
payload: dict[str, typing.Any] | None

Type-specific reaction data. For "emoji_reaction" reactions, contains an emoji key with the Unicode emoji string (e.g., " ").

type: Required[str]

Reaction type identifier. Currently always "emoji_reaction" for emoji-based reactions.

user: str | None

Public ID of the user who added the reaction (usr_...).

class MessageUpdatedPayloadMessage(typing.TypedDict):
417class MessageUpdatedPayloadMessage(TypedDict, total=False):
418    actors: list[MessageUpdatedPayloadMessageActorsItem] | None
419    "Resolved actor descriptors for the message sender, combining identity and display metadata. Always contains exactly one entry."
420    agent: str | None
421    "ID of the agent user that sent this message (`agi_...`). `null` for messages sent by human users."
422    agent_mode: Literal["cli", "embedded"] | None
423    "Local agent execution mode for this message. One of `cli`, `embedded`, or `null` when the message was not created by a local agent execution path."
424    attachments: list[MessageUpdatedPayloadMessageAttachmentsItem] | None
425    "Files, links, tasks, media, artifacts, and actions attached to this message. Empty array if there are no attachments."
426    branched_thread: str | None
427    "ID of the thread that was branched from this message (`thr_...`). `null` if this message has not spawned a branch thread."
428    content: str | None
429    "Text content of the message. `null` for messages that contain only attachments."
430    created_at: datetime | None
431    "When the message was posted (ISO 8601)."
432    has_replies: bool | None
433    "Whether this message has at least one reply. Only present when explicitly requested or computed by the server."
434    id: Required[str]
435    "Message ID (`msg_...`)."
436    idempotency_key: str | None
437    "Client-supplied idempotency key used to deduplicate message sends. `null` if the sender did not provide one."
438    legacy_agent: str | None
439    "Identifier of the legacy chat agent that sent this message, if applicable. `null` for messages sent by users or modern agent users."
440    metadata: dict[str, Any] | None
441    "Arbitrary key-value metadata attached to the message. Always present; defaults to an empty object when no metadata has been set."
442    org: str | None
443    "ID of the organization that owns this message (`org_...`)."
444    reactions: list[MessageUpdatedPayloadMessageReactionsItem] | None
445    "Emoji and other reactions added to this message by users. Empty array if no reactions have been added or the association is not preloaded."
446    rendering_mode: str | None
447    'Display hint for how the message should be rendered. One of `"reply"`, `"direct"`, or `"inline"`. `null` for user-authored messages, which are always rendered as standard replies.'
448    replies: list[dict[str, Any]] | None
449    "Inline array of reply messages, each serialized as a full message object. Only present when the server has preloaded replies for this message."
450    replies_after_cursor: str | None
451    "Opaque pagination cursor to fetch replies posted after the current page. Only present when inline replies are included in the response."
452    replies_before_cursor: str | None
453    "Opaque pagination cursor to fetch replies posted before the current page. Only present when inline replies are included in the response."
454    reply_count: int | None
455    "Total number of direct replies to this message. Only present when explicitly requested or computed by the server."
456    reply_to: dict[str, Any] | None
457    "The parent message this message is a reply to, expanded as a full message object when loaded. `null` if this is a top-level message or the association is not preloaded."
458    sandbox: str | None
459    "ID of the developer sandbox this message belongs to (`dsb_...`). `null` for non-sandbox messages."
460    team: str | None
461    "ID of the team this message is scoped to (`tem_...`). `null` if the message is not team-scoped."
462    thread: str | None
463    "ID of the thread this message belongs to (`thr_...`). `null` for messages not yet associated with a thread."
464    user: str | None
465    "The human user who sent this message. Returns a public ID string (`usr_...`) when the association is not preloaded, or an expanded user object when it is. `null` for messages sent by agents."

Resolved actor descriptors for the message sender, combining identity and display metadata. Always contains exactly one entry.

agent: str | None

ID of the agent user that sent this message (agi_...). null for messages sent by human users.

agent_mode: Optional[Literal['cli', 'embedded']]

Local agent execution mode for this message. One of cli, embedded, or null when the message was not created by a local agent execution path.

Files, links, tasks, media, artifacts, and actions attached to this message. Empty array if there are no attachments.

branched_thread: str | None

ID of the thread that was branched from this message (thr_...). null if this message has not spawned a branch thread.

content: str | None

Text content of the message. null for messages that contain only attachments.

created_at: datetime.datetime | None

When the message was posted (ISO 8601).

has_replies: bool | None

Whether this message has at least one reply. Only present when explicitly requested or computed by the server.

id: Required[str]

Message ID (msg_...).

idempotency_key: str | None

Client-supplied idempotency key used to deduplicate message sends. null if the sender did not provide one.

legacy_agent: str | None

Identifier of the legacy chat agent that sent this message, if applicable. null for messages sent by users or modern agent users.

metadata: dict[str, typing.Any] | None

Arbitrary key-value metadata attached to the message. Always present; defaults to an empty object when no metadata has been set.

org: str | None

ID of the organization that owns this message (org_...).

Emoji and other reactions added to this message by users. Empty array if no reactions have been added or the association is not preloaded.

rendering_mode: str | None

Display hint for how the message should be rendered. One of "reply", "direct", or "inline". null for user-authored messages, which are always rendered as standard replies.

replies: list[dict[str, typing.Any]] | None

Inline array of reply messages, each serialized as a full message object. Only present when the server has preloaded replies for this message.

replies_after_cursor: str | None

Opaque pagination cursor to fetch replies posted after the current page. Only present when inline replies are included in the response.

replies_before_cursor: str | None

Opaque pagination cursor to fetch replies posted before the current page. Only present when inline replies are included in the response.

reply_count: int | None

Total number of direct replies to this message. Only present when explicitly requested or computed by the server.

reply_to: dict[str, typing.Any] | None

The parent message this message is a reply to, expanded as a full message object when loaded. null if this is a top-level message or the association is not preloaded.

sandbox: str | None

ID of the developer sandbox this message belongs to (dsb_...). null for non-sandbox messages.

team: str | None

ID of the team this message is scoped to (tem_...). null if the message is not team-scoped.

thread: str | None

ID of the thread this message belongs to (thr_...). null for messages not yet associated with a thread.

user: str | None

The human user who sent this message. Returns a public ID string (usr_...) when the association is not preloaded, or an expanded user object when it is. null for messages sent by agents.

class MessageUpdatedPayload(typing.TypedDict):
468class MessageUpdatedPayload(TypedDict, total=False):
469    "Broadcast when a message is updated or removed"
470
471    message: MessageUpdatedPayloadMessage | None
472    "A chat message posted in a thread, including its content, author, attachments, reactions, and optional reply metadata."
473    thread_id: str | None

Broadcast when a message is updated or removed

A chat message posted in a thread, including its content, author, attachments, reactions, and optional reply metadata.

thread_id: str | None
class ThreadEventPayload(typing.TypedDict):
476class ThreadEventPayload(TypedDict, total=False):
477    "Broadcast thread-level events (agent updates, read state, unread counts)"
478
479    payload: dict[str, Any] | None
480    thread_id: str | None
481    type: str | None

Broadcast thread-level events (agent updates, read state, unread counts)

payload: dict[str, typing.Any] | None
thread_id: str | None
type: str | None
class SystemEventPayload(typing.TypedDict):
484class SystemEventPayload(TypedDict, total=False):
485    "Broadcast system-wide events"
486
487    event: dict[str, Any] | None

Broadcast system-wide events

event: dict[str, typing.Any] | None
class TypingPayloadActorProfilePicture(typing.TypedDict):
490class TypingPayloadActorProfilePicture(TypedDict, total=False):
491    file: str | None
492    "ID of the underlying storage file (`fil_...`). `null` when the image is not backed by a platform storage file."
493    height: int | None
494    "Height of the image in pixels. `null` if not known."
495    media: str | None
496    "ID of the associated media record (`med_...`). `null` when the image is not linked to a media entity."
497    mime_type: str | None
498    'MIME type of the image, e.g. `"image/png"` or `"image/jpeg"`. `null` if not known.'
499    refresh_url: str | None
500    "Endpoint URL you can call to obtain a fresh signed `url` when the current one has expired. `null` if the URL does not require refreshing."
501    url: str | None
502    "Signed or public URL for downloading the image. May be time-limited; use `refresh_url` to obtain a new URL when this one expires."
503    width: int | None
504    "Width of the image in pixels. `null` if not known."
file: str | None

ID of the underlying storage file (fil_...). null when the image is not backed by a platform storage file.

height: int | None

Height of the image in pixels. null if not known.

media: str | None

ID of the associated media record (med_...). null when the image is not linked to a media entity.

mime_type: str | None

MIME type of the image, e.g. "image/png" or "image/jpeg". null if not known.

refresh_url: str | None

Endpoint URL you can call to obtain a fresh signed url when the current one has expired. null if the URL does not require refreshing.

url: str | None

Signed or public URL for downloading the image. May be time-limited; use refresh_url to obtain a new URL when this one expires.

width: int | None

Width of the image in pixels. null if not known.

class TypingPayloadActor(typing.TypedDict):
507class TypingPayloadActor(TypedDict, total=False):
508    alias: str | None
509    "Short handle or alias for the actor, used as an alternate display identifier. `null` if not configured."
510    id: str | None
511    'Composite actor identifier. Format is `"user-<usr_...>"` for human users or `"agent-<agi_...>"` for agents.'
512    name: str | None
513    "Display name of the actor shown in the UI. `null` if no name is set."
514    profile_picture: TypingPayloadActorProfilePicture | None
515    "Profile picture for the actor. `null` if the actor has no profile picture."
alias: str | None

Short handle or alias for the actor, used as an alternate display identifier. null if not configured.

id: str | None

Composite actor identifier. Format is "user-<usr_...>" for human users or "agent-<agi_...>" for agents.

name: str | None

Display name of the actor shown in the UI. null if no name is set.

profile_picture: TypingPayloadActorProfilePicture | None

Profile picture for the actor. null if the actor has no profile picture.

class TypingPayload(typing.TypedDict):
518class TypingPayload(TypedDict, total=False):
519    "Broadcast when a participant (human or agent) starts or stops typing. Ephemeral; never persisted."
520
521    actor: TypingPayloadActor | None
522    "The entity that authored a message, either a human user or an agent."
523    is_typing: bool | None
524    thread_id: str | None

Broadcast when a participant (human or agent) starts or stops typing. Ephemeral; never persisted.

actor: TypingPayloadActor | None

The entity that authored a message, either a human user or an agent.

is_typing: bool | None
thread_id: str | None
class ApiChatChannel:
530class ApiChatChannel:
531    def __init__(self, channel, join_response=None):
532        self._channel = channel
533        self.join_response = join_response
534
535    # Join a team-scoped thread by ID
536    @staticmethod
537    def topic_team_thread(team_id: str, thread_id: str) -> str:
538        return f"api:chat:team:{team_id}:thread:{thread_id}"
539
540    # Join a team-scoped thread by ID
541    @classmethod
542    async def join_team_thread(
543        cls,
544        socket: "Socket",
545        team_id: str,
546        thread_id: str,
547        *,
548        after_cursor: str | None = None,
549        before_cursor: str | None = None,
550        include_metadata: bool | None = None,
551        limit: int | None = None,
552    ) -> "ApiChatChannel":
553        topic = cls.topic_team_thread(team_id, thread_id)
554        channel = socket.channel(topic)
555        payload: dict[str, object] = {}
556        if after_cursor is not None:
557            payload["after_cursor"] = after_cursor
558        if before_cursor is not None:
559            payload["before_cursor"] = before_cursor
560        if include_metadata is not None:
561            payload["include_metadata"] = include_metadata
562        if limit is not None:
563            payload["limit"] = limit
564        join_response = await channel.join(payload)
565        return cls(channel, join_response)
566
567    # Join or create a team-scoped keyed thread
568    @staticmethod
569    def topic_team_keyed(team_id: str, key: str) -> str:
570        return f"api:chat:team:{team_id}:key:{key}"
571
572    # Join or create a team-scoped keyed thread
573    @classmethod
574    async def join_team_keyed(
575        cls,
576        socket: "Socket",
577        team_id: str,
578        key: str,
579        *,
580        after_cursor: str | None = None,
581        before_cursor: str | None = None,
582        include_metadata: bool | None = None,
583        limit: int | None = None,
584    ) -> "ApiChatChannel":
585        topic = cls.topic_team_keyed(team_id, key)
586        channel = socket.channel(topic)
587        payload: dict[str, object] = {}
588        if after_cursor is not None:
589            payload["after_cursor"] = after_cursor
590        if before_cursor is not None:
591            payload["before_cursor"] = before_cursor
592        if include_metadata is not None:
593            payload["include_metadata"] = include_metadata
594        if limit is not None:
595            payload["limit"] = limit
596        join_response = await channel.join(payload)
597        return cls(channel, join_response)
598
599    # Join a team-scoped transient (ephemeral) thread
600    @staticmethod
601    def topic_team_transient(team_id: str, key: str) -> str:
602        return f"api:chat:team:{team_id}:transient:{key}"
603
604    # Join a team-scoped transient (ephemeral) thread
605    @classmethod
606    async def join_team_transient(
607        cls,
608        socket: "Socket",
609        team_id: str,
610        key: str,
611        *,
612        after_cursor: str | None = None,
613        before_cursor: str | None = None,
614        include_metadata: bool | None = None,
615        limit: int | None = None,
616    ) -> "ApiChatChannel":
617        topic = cls.topic_team_transient(team_id, key)
618        channel = socket.channel(topic)
619        payload: dict[str, object] = {}
620        if after_cursor is not None:
621            payload["after_cursor"] = after_cursor
622        if before_cursor is not None:
623            payload["before_cursor"] = before_cursor
624        if include_metadata is not None:
625            payload["include_metadata"] = include_metadata
626        if limit is not None:
627            payload["limit"] = limit
628        join_response = await channel.join(payload)
629        return cls(channel, join_response)
630
631    # Join a user-scoped thread by ID
632    @staticmethod
633    def topic_user_thread(thread_id: str) -> str:
634        return f"api:chat:user:thread:{thread_id}"
635
636    # Join a user-scoped thread by ID
637    @classmethod
638    async def join_user_thread(
639        cls,
640        socket: "Socket",
641        thread_id: str,
642        *,
643        after_cursor: str | None = None,
644        before_cursor: str | None = None,
645        include_metadata: bool | None = None,
646        limit: int | None = None,
647    ) -> "ApiChatChannel":
648        topic = cls.topic_user_thread(thread_id)
649        channel = socket.channel(topic)
650        payload: dict[str, object] = {}
651        if after_cursor is not None:
652            payload["after_cursor"] = after_cursor
653        if before_cursor is not None:
654            payload["before_cursor"] = before_cursor
655        if include_metadata is not None:
656            payload["include_metadata"] = include_metadata
657        if limit is not None:
658            payload["limit"] = limit
659        join_response = await channel.join(payload)
660        return cls(channel, join_response)
661
662    # Join or create a user-scoped keyed thread
663    @staticmethod
664    def topic_user_keyed(key: str) -> str:
665        return f"api:chat:user:key:{key}"
666
667    # Join or create a user-scoped keyed thread
668    @classmethod
669    async def join_user_keyed(
670        cls,
671        socket: "Socket",
672        key: str,
673        *,
674        after_cursor: str | None = None,
675        before_cursor: str | None = None,
676        include_metadata: bool | None = None,
677        limit: int | None = None,
678    ) -> "ApiChatChannel":
679        topic = cls.topic_user_keyed(key)
680        channel = socket.channel(topic)
681        payload: dict[str, object] = {}
682        if after_cursor is not None:
683            payload["after_cursor"] = after_cursor
684        if before_cursor is not None:
685            payload["before_cursor"] = before_cursor
686        if include_metadata is not None:
687            payload["include_metadata"] = include_metadata
688        if limit is not None:
689            payload["limit"] = limit
690        join_response = await channel.join(payload)
691        return cls(channel, join_response)
692
693    # Join a user-scoped transient (ephemeral) thread
694    @staticmethod
695    def topic_user_transient(key: str) -> str:
696        return f"api:chat:user:transient:{key}"
697
698    # Join a user-scoped transient (ephemeral) thread
699    @classmethod
700    async def join_user_transient(
701        cls,
702        socket: "Socket",
703        key: str,
704        *,
705        after_cursor: str | None = None,
706        before_cursor: str | None = None,
707        include_metadata: bool | None = None,
708        limit: int | None = None,
709    ) -> "ApiChatChannel":
710        topic = cls.topic_user_transient(key)
711        channel = socket.channel(topic)
712        payload: dict[str, object] = {}
713        if after_cursor is not None:
714            payload["after_cursor"] = after_cursor
715        if before_cursor is not None:
716            payload["before_cursor"] = before_cursor
717        if include_metadata is not None:
718            payload["include_metadata"] = include_metadata
719        if limit is not None:
720            payload["limit"] = limit
721        join_response = await channel.join(payload)
722        return cls(channel, join_response)
723
724    # Leave the underlying channel.
725    async def leave(self):
726        await self._channel.leave()
727
728    # Fork a sub-thread from an existing message
729    async def api_chat_fork_thread(self, payload: ApiChatForkThreadInput) -> dict[str, Any]:
730        return await self._channel.push("api:chat:fork_thread", payload)
731
732    # Mark a thread as read up to a given message
733    async def api_chat_mark_thread_read(
734        self, payload: ApiChatMarkThreadReadInput
735    ) -> dict[str, Any]:
736        return await self._channel.push("api:chat:mark_thread_read", payload)
737
738    # List all messages in the current thread
739    async def api_chat_list_messages(self, payload: dict) -> dict[str, Any]:
740        return await self._channel.push("api:chat:list_messages", payload)
741
742    # Load additional messages with cursor-based pagination
743    async def api_chat_load_more_messages(
744        self, payload: ApiChatLoadMoreMessagesInput
745    ) -> dict[str, Any]:
746        return await self._channel.push("api:chat:load_more_messages", payload)
747
748    # Post a new message with optional uploads and reply-to
749    async def api_chat_post_message(self, payload: ApiChatPostMessageInput) -> dict[str, Any]:
750        return await self._channel.push("api:chat:post_message", payload)
751
752    # Post a simple text message
753    async def api_chat_post_simple_message(
754        self, payload: ApiChatPostSimpleMessageInput
755    ) -> dict[str, Any]:
756        return await self._channel.push("api:chat:post_simple_message", payload)
757
758    # Edit an existing message's content
759    async def api_chat_edit_message(self, payload: ApiChatEditMessageInput) -> dict[str, Any]:
760        return await self._channel.push("api:chat:edit_message", payload)
761
762    # Delete a message
763    async def api_chat_delete_message(self, payload: ApiChatDeleteMessageInput) -> dict[str, Any]:
764        return await self._channel.push("api:chat:delete_message", payload)
765
766    # Add an emoji reaction to a message
767    async def api_chat_add_reaction(self, payload: ApiChatAddReactionInput) -> dict[str, Any]:
768        return await self._channel.push("api:chat:add_reaction", payload)
769
770    # Remove an emoji reaction from a message
771    async def api_chat_remove_reaction(self, payload: ApiChatRemoveReactionInput) -> dict[str, Any]:
772        return await self._channel.push("api:chat:remove_reaction", payload)
773
774    # Signal that the current user has started or stopped typing in the thread
775    async def api_chat_typing(self, payload: ApiChatTypingInput) -> dict[str, Any]:
776        return await self._channel.push("api:chat:typing", payload)
777
778    # Broadcast when a new message is added to a thread
779    def on_message_added(
780        self, callback: Callable[[MessageAddedPayload], None]
781    ) -> Callable[[], None]:
782        return self._channel.on("message_added", callback)
783
784    # Broadcast when a message is updated or removed
785    def on_message_updated(
786        self, callback: Callable[[MessageUpdatedPayload], None]
787    ) -> Callable[[], None]:
788        return self._channel.on("message_updated", callback)
789
790    # Broadcast thread-level events (agent updates, read state, unread counts)
791    def on_thread_event(self, callback: Callable[[ThreadEventPayload], None]) -> Callable[[], None]:
792        return self._channel.on("thread_event", callback)
793
794    # Broadcast system-wide events
795    def on_system_event(self, callback: Callable[[SystemEventPayload], None]) -> Callable[[], None]:
796        return self._channel.on("system_event", callback)
797
798    # Broadcast when a participant (human or agent) starts or stops typing. Ephemeral; never persisted.
799    def on_typing(self, callback: Callable[[TypingPayload], None]) -> Callable[[], None]:
800        return self._channel.on("typing", callback)
ApiChatChannel(channel, join_response=None)
531    def __init__(self, channel, join_response=None):
532        self._channel = channel
533        self.join_response = join_response
join_response
@staticmethod
def topic_team_thread(team_id: str, thread_id: str) -> str:
536    @staticmethod
537    def topic_team_thread(team_id: str, thread_id: str) -> str:
538        return f"api:chat:team:{team_id}:thread:{thread_id}"
@classmethod
async def join_team_thread( cls, socket: archastro.phx_channel.Socket, team_id: str, thread_id: str, *, after_cursor: str | None = None, before_cursor: str | None = None, include_metadata: bool | None = None, limit: int | None = None) -> ApiChatChannel:
541    @classmethod
542    async def join_team_thread(
543        cls,
544        socket: "Socket",
545        team_id: str,
546        thread_id: str,
547        *,
548        after_cursor: str | None = None,
549        before_cursor: str | None = None,
550        include_metadata: bool | None = None,
551        limit: int | None = None,
552    ) -> "ApiChatChannel":
553        topic = cls.topic_team_thread(team_id, thread_id)
554        channel = socket.channel(topic)
555        payload: dict[str, object] = {}
556        if after_cursor is not None:
557            payload["after_cursor"] = after_cursor
558        if before_cursor is not None:
559            payload["before_cursor"] = before_cursor
560        if include_metadata is not None:
561            payload["include_metadata"] = include_metadata
562        if limit is not None:
563            payload["limit"] = limit
564        join_response = await channel.join(payload)
565        return cls(channel, join_response)
@staticmethod
def topic_team_keyed(team_id: str, key: str) -> str:
568    @staticmethod
569    def topic_team_keyed(team_id: str, key: str) -> str:
570        return f"api:chat:team:{team_id}:key:{key}"
@classmethod
async def join_team_keyed( cls, socket: archastro.phx_channel.Socket, team_id: str, key: str, *, after_cursor: str | None = None, before_cursor: str | None = None, include_metadata: bool | None = None, limit: int | None = None) -> ApiChatChannel:
573    @classmethod
574    async def join_team_keyed(
575        cls,
576        socket: "Socket",
577        team_id: str,
578        key: str,
579        *,
580        after_cursor: str | None = None,
581        before_cursor: str | None = None,
582        include_metadata: bool | None = None,
583        limit: int | None = None,
584    ) -> "ApiChatChannel":
585        topic = cls.topic_team_keyed(team_id, key)
586        channel = socket.channel(topic)
587        payload: dict[str, object] = {}
588        if after_cursor is not None:
589            payload["after_cursor"] = after_cursor
590        if before_cursor is not None:
591            payload["before_cursor"] = before_cursor
592        if include_metadata is not None:
593            payload["include_metadata"] = include_metadata
594        if limit is not None:
595            payload["limit"] = limit
596        join_response = await channel.join(payload)
597        return cls(channel, join_response)
@staticmethod
def topic_team_transient(team_id: str, key: str) -> str:
600    @staticmethod
601    def topic_team_transient(team_id: str, key: str) -> str:
602        return f"api:chat:team:{team_id}:transient:{key}"
@classmethod
async def join_team_transient( cls, socket: archastro.phx_channel.Socket, team_id: str, key: str, *, after_cursor: str | None = None, before_cursor: str | None = None, include_metadata: bool | None = None, limit: int | None = None) -> ApiChatChannel:
605    @classmethod
606    async def join_team_transient(
607        cls,
608        socket: "Socket",
609        team_id: str,
610        key: str,
611        *,
612        after_cursor: str | None = None,
613        before_cursor: str | None = None,
614        include_metadata: bool | None = None,
615        limit: int | None = None,
616    ) -> "ApiChatChannel":
617        topic = cls.topic_team_transient(team_id, key)
618        channel = socket.channel(topic)
619        payload: dict[str, object] = {}
620        if after_cursor is not None:
621            payload["after_cursor"] = after_cursor
622        if before_cursor is not None:
623            payload["before_cursor"] = before_cursor
624        if include_metadata is not None:
625            payload["include_metadata"] = include_metadata
626        if limit is not None:
627            payload["limit"] = limit
628        join_response = await channel.join(payload)
629        return cls(channel, join_response)
@staticmethod
def topic_user_thread(thread_id: str) -> str:
632    @staticmethod
633    def topic_user_thread(thread_id: str) -> str:
634        return f"api:chat:user:thread:{thread_id}"
@classmethod
async def join_user_thread( cls, socket: archastro.phx_channel.Socket, thread_id: str, *, after_cursor: str | None = None, before_cursor: str | None = None, include_metadata: bool | None = None, limit: int | None = None) -> ApiChatChannel:
637    @classmethod
638    async def join_user_thread(
639        cls,
640        socket: "Socket",
641        thread_id: str,
642        *,
643        after_cursor: str | None = None,
644        before_cursor: str | None = None,
645        include_metadata: bool | None = None,
646        limit: int | None = None,
647    ) -> "ApiChatChannel":
648        topic = cls.topic_user_thread(thread_id)
649        channel = socket.channel(topic)
650        payload: dict[str, object] = {}
651        if after_cursor is not None:
652            payload["after_cursor"] = after_cursor
653        if before_cursor is not None:
654            payload["before_cursor"] = before_cursor
655        if include_metadata is not None:
656            payload["include_metadata"] = include_metadata
657        if limit is not None:
658            payload["limit"] = limit
659        join_response = await channel.join(payload)
660        return cls(channel, join_response)
@staticmethod
def topic_user_keyed(key: str) -> str:
663    @staticmethod
664    def topic_user_keyed(key: str) -> str:
665        return f"api:chat:user:key:{key}"
@classmethod
async def join_user_keyed( cls, socket: archastro.phx_channel.Socket, key: str, *, after_cursor: str | None = None, before_cursor: str | None = None, include_metadata: bool | None = None, limit: int | None = None) -> ApiChatChannel:
668    @classmethod
669    async def join_user_keyed(
670        cls,
671        socket: "Socket",
672        key: str,
673        *,
674        after_cursor: str | None = None,
675        before_cursor: str | None = None,
676        include_metadata: bool | None = None,
677        limit: int | None = None,
678    ) -> "ApiChatChannel":
679        topic = cls.topic_user_keyed(key)
680        channel = socket.channel(topic)
681        payload: dict[str, object] = {}
682        if after_cursor is not None:
683            payload["after_cursor"] = after_cursor
684        if before_cursor is not None:
685            payload["before_cursor"] = before_cursor
686        if include_metadata is not None:
687            payload["include_metadata"] = include_metadata
688        if limit is not None:
689            payload["limit"] = limit
690        join_response = await channel.join(payload)
691        return cls(channel, join_response)
@staticmethod
def topic_user_transient(key: str) -> str:
694    @staticmethod
695    def topic_user_transient(key: str) -> str:
696        return f"api:chat:user:transient:{key}"
@classmethod
async def join_user_transient( cls, socket: archastro.phx_channel.Socket, key: str, *, after_cursor: str | None = None, before_cursor: str | None = None, include_metadata: bool | None = None, limit: int | None = None) -> ApiChatChannel:
699    @classmethod
700    async def join_user_transient(
701        cls,
702        socket: "Socket",
703        key: str,
704        *,
705        after_cursor: str | None = None,
706        before_cursor: str | None = None,
707        include_metadata: bool | None = None,
708        limit: int | None = None,
709    ) -> "ApiChatChannel":
710        topic = cls.topic_user_transient(key)
711        channel = socket.channel(topic)
712        payload: dict[str, object] = {}
713        if after_cursor is not None:
714            payload["after_cursor"] = after_cursor
715        if before_cursor is not None:
716            payload["before_cursor"] = before_cursor
717        if include_metadata is not None:
718            payload["include_metadata"] = include_metadata
719        if limit is not None:
720            payload["limit"] = limit
721        join_response = await channel.join(payload)
722        return cls(channel, join_response)
async def leave(self):
725    async def leave(self):
726        await self._channel.leave()
async def api_chat_fork_thread( self, payload: ApiChatForkThreadInput) -> dict[str, typing.Any]:
729    async def api_chat_fork_thread(self, payload: ApiChatForkThreadInput) -> dict[str, Any]:
730        return await self._channel.push("api:chat:fork_thread", payload)
async def api_chat_mark_thread_read( self, payload: ApiChatMarkThreadReadInput) -> dict[str, typing.Any]:
733    async def api_chat_mark_thread_read(
734        self, payload: ApiChatMarkThreadReadInput
735    ) -> dict[str, Any]:
736        return await self._channel.push("api:chat:mark_thread_read", payload)
async def api_chat_list_messages(self, payload: dict) -> dict[str, typing.Any]:
739    async def api_chat_list_messages(self, payload: dict) -> dict[str, Any]:
740        return await self._channel.push("api:chat:list_messages", payload)
async def api_chat_load_more_messages( self, payload: ApiChatLoadMoreMessagesInput) -> dict[str, typing.Any]:
743    async def api_chat_load_more_messages(
744        self, payload: ApiChatLoadMoreMessagesInput
745    ) -> dict[str, Any]:
746        return await self._channel.push("api:chat:load_more_messages", payload)
async def api_chat_post_message( self, payload: ApiChatPostMessageInput) -> dict[str, typing.Any]:
749    async def api_chat_post_message(self, payload: ApiChatPostMessageInput) -> dict[str, Any]:
750        return await self._channel.push("api:chat:post_message", payload)
async def api_chat_post_simple_message( self, payload: ApiChatPostSimpleMessageInput) -> dict[str, typing.Any]:
753    async def api_chat_post_simple_message(
754        self, payload: ApiChatPostSimpleMessageInput
755    ) -> dict[str, Any]:
756        return await self._channel.push("api:chat:post_simple_message", payload)
async def api_chat_edit_message( self, payload: ApiChatEditMessageInput) -> dict[str, typing.Any]:
759    async def api_chat_edit_message(self, payload: ApiChatEditMessageInput) -> dict[str, Any]:
760        return await self._channel.push("api:chat:edit_message", payload)
async def api_chat_delete_message( self, payload: ApiChatDeleteMessageInput) -> dict[str, typing.Any]:
763    async def api_chat_delete_message(self, payload: ApiChatDeleteMessageInput) -> dict[str, Any]:
764        return await self._channel.push("api:chat:delete_message", payload)
async def api_chat_add_reaction( self, payload: ApiChatAddReactionInput) -> dict[str, typing.Any]:
767    async def api_chat_add_reaction(self, payload: ApiChatAddReactionInput) -> dict[str, Any]:
768        return await self._channel.push("api:chat:add_reaction", payload)
async def api_chat_remove_reaction( self, payload: ApiChatRemoveReactionInput) -> dict[str, typing.Any]:
771    async def api_chat_remove_reaction(self, payload: ApiChatRemoveReactionInput) -> dict[str, Any]:
772        return await self._channel.push("api:chat:remove_reaction", payload)
async def api_chat_typing( self, payload: ApiChatTypingInput) -> dict[str, typing.Any]:
775    async def api_chat_typing(self, payload: ApiChatTypingInput) -> dict[str, Any]:
776        return await self._channel.push("api:chat:typing", payload)
def on_message_added( self, callback: Callable[[MessageAddedPayload], None]) -> Callable[[], None]:
779    def on_message_added(
780        self, callback: Callable[[MessageAddedPayload], None]
781    ) -> Callable[[], None]:
782        return self._channel.on("message_added", callback)
def on_message_updated( self, callback: Callable[[MessageUpdatedPayload], None]) -> Callable[[], None]:
785    def on_message_updated(
786        self, callback: Callable[[MessageUpdatedPayload], None]
787    ) -> Callable[[], None]:
788        return self._channel.on("message_updated", callback)
def on_thread_event( self, callback: Callable[[ThreadEventPayload], None]) -> Callable[[], None]:
791    def on_thread_event(self, callback: Callable[[ThreadEventPayload], None]) -> Callable[[], None]:
792        return self._channel.on("thread_event", callback)
def on_system_event( self, callback: Callable[[SystemEventPayload], None]) -> Callable[[], None]:
795    def on_system_event(self, callback: Callable[[SystemEventPayload], None]) -> Callable[[], None]:
796        return self._channel.on("system_event", callback)
def on_typing( self, callback: Callable[[TypingPayload], None]) -> Callable[[], None]:
799    def on_typing(self, callback: Callable[[TypingPayload], None]) -> Callable[[], None]:
800        return self._channel.on("typing", callback)