openai/chatkit-python

Public

mirrored fromhttps://github.com/openai/chatkit-pythonAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v1.6.5

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

chatkit/types.py

1095lines · modecode

1from __future__ import annotations
2
3from datetime import datetime
4from typing import Any, Generic, Literal
5
6from pydantic import (
7 AnyUrl,
8 BaseModel,
9 Field,
10 SerializationInfo,
11 model_serializer,
12)
13from typing_extensions import Annotated, TypeIs, TypeVar
14
15from chatkit.errors import ErrorCode
16
17from .actions import Action
18from .icons import IconName
19from .widgets import WidgetComponent, WidgetRoot
20
21T = TypeVar("T")
22
23
24class Page(BaseModel, Generic[T]):
25 """Paginated collection of records returned from the API."""
26
27 data: list[T] = []
28 has_more: bool = False
29 after: str | None = None
30
31
32### REQUEST TYPES
33
34
35class BaseReq(BaseModel):
36 """Base class for all request payloads."""
37
38 metadata: dict[str, Any] = Field(default_factory=dict)
39 """Arbitrary integration-specific metadata."""
40
41
42class ThreadsGetByIdReq(BaseReq):
43 """Request to fetch a single thread by its identifier."""
44
45 type: Literal["threads.get_by_id"] = "threads.get_by_id"
46 params: ThreadGetByIdParams
47
48
49class ThreadGetByIdParams(BaseModel):
50 """Parameters for retrieving a thread by id."""
51
52 thread_id: str
53
54
55class ThreadsCreateReq(BaseReq):
56 """Request to create a new thread from a user message."""
57
58 type: Literal["threads.create"] = "threads.create"
59 params: ThreadCreateParams
60
61
62class ThreadCreateParams(BaseModel):
63 """User input required to create a thread."""
64
65 input: UserMessageInput
66
67
68class ThreadListParams(BaseModel):
69 """Pagination parameters for listing threads."""
70
71 limit: int | None = None
72 order: Literal["asc", "desc"] = "desc"
73 after: str | None = None
74
75
76class ThreadsListReq(BaseReq):
77 """Request to list threads."""
78
79 type: Literal["threads.list"] = "threads.list"
80 params: ThreadListParams
81
82
83class ThreadsAddUserMessageReq(BaseReq):
84 """Request to append a user message to a thread."""
85
86 type: Literal["threads.add_user_message"] = "threads.add_user_message"
87 params: ThreadAddUserMessageParams
88
89
90class ThreadAddUserMessageParams(BaseModel):
91 """Parameters for adding a user message to a thread."""
92
93 input: UserMessageInput
94 thread_id: str
95
96
97class ThreadsAddClientToolOutputReq(BaseReq):
98 """Request to add a client tool's output to a thread."""
99
100 type: Literal["threads.add_client_tool_output"] = "threads.add_client_tool_output"
101 params: ThreadAddClientToolOutputParams
102
103
104class ThreadAddClientToolOutputParams(BaseModel):
105 """Parameters for recording tool output in a thread."""
106
107 thread_id: str
108 result: Any
109
110
111class StructuredInputAnswerSubmission(BaseModel):
112 """Client-submitted answer for a structured input."""
113
114 values: list[str] = Field(default_factory=list)
115 """Text answer values submitted for the structured input."""
116 skipped: bool = False
117 """Whether this structured input answer was skipped."""
118
119
120class StructuredInputSubmission(BaseModel):
121 """Client-submitted answers for a pending structured input item."""
122
123 status: Literal["answered", "skipped"] = "answered"
124 """Overall status for the structured input submission."""
125 answers: dict[str, StructuredInputAnswerSubmission] = Field(default_factory=dict)
126 """Answers keyed by structured input id."""
127
128
129class ThreadsAddStructuredInputReq(BaseReq):
130 """Request to submit answers for a pending structured input item."""
131
132 type: Literal["threads.add_structured_input"] = "threads.add_structured_input"
133 params: ThreadAddStructuredInputParams
134
135
136class ThreadAddStructuredInputParams(BaseModel):
137 """Parameters for adding structured input to a thread."""
138
139 thread_id: str
140 item_id: str
141 input: StructuredInputSubmission
142
143
144class ThreadsCustomActionReq(BaseReq):
145 """Request to execute a custom action within a thread."""
146
147 type: Literal["threads.custom_action"] = "threads.custom_action"
148 params: ThreadCustomActionParams
149
150
151class ThreadsSyncCustomActionReq(BaseReq):
152 """Request to execute a custom action and return a single item update."""
153
154 type: Literal["threads.sync_custom_action"] = "threads.sync_custom_action"
155 params: ThreadCustomActionParams
156
157
158class ThreadCustomActionParams(BaseModel):
159 """Parameters describing the custom action to execute."""
160
161 thread_id: str
162 item_id: str | None = None
163 action: Action[str, Any]
164
165
166class ThreadsRetryAfterItemReq(BaseReq):
167 """Request to retry processing after a specific thread item."""
168
169 type: Literal["threads.retry_after_item"] = "threads.retry_after_item"
170 params: ThreadRetryAfterItemParams
171
172
173class ThreadRetryAfterItemParams(BaseModel):
174 """Parameters specifying which item to retry."""
175
176 thread_id: str
177 item_id: str
178
179
180class ItemsFeedbackReq(BaseReq):
181 """Request to submit feedback on specific items."""
182
183 type: Literal["items.feedback"] = "items.feedback"
184 params: ItemFeedbackParams
185
186
187class ItemFeedbackParams(BaseModel):
188 """Parameters describing feedback targets and sentiment."""
189
190 thread_id: str
191 item_ids: list[str]
192 kind: FeedbackKind
193
194
195class AttachmentsDeleteReq(BaseReq):
196 """Request to remove an attachment."""
197
198 type: Literal["attachments.delete"] = "attachments.delete"
199 params: AttachmentDeleteParams
200
201
202class AttachmentDeleteParams(BaseModel):
203 """Parameters identifying an attachment to delete."""
204
205 attachment_id: str
206
207
208class AttachmentsCreateReq(BaseReq):
209 """Request to register a new attachment."""
210
211 type: Literal["attachments.create"] = "attachments.create"
212 params: AttachmentCreateParams
213
214
215class AttachmentCreateParams(BaseModel):
216 """Metadata needed to initialize an attachment."""
217
218 name: str
219 size: int
220 mime_type: str
221
222
223class InputTranscribeReq(BaseReq):
224 """Request to transcribe an audio payload into text."""
225
226 type: Literal["input.transcribe"] = "input.transcribe"
227 params: InputTranscribeParams
228
229
230class InputTranscribeParams(BaseModel):
231 """Parameters for speech transcription."""
232
233 audio_base64: str
234 """Base64-encoded audio bytes."""
235
236 mime_type: str
237 """Raw MIME type for the audio payload, e.g. "audio/webm;codecs=opus"."""
238
239
240class AudioInput(BaseModel):
241 """Audio input data for transcription."""
242
243 data: bytes
244 """Audio data bytes."""
245
246 mime_type: str
247 """Raw MIME type for the audio payload, e.g. "audio/webm;codecs=opus"."""
248
249 @property
250 def media_type(self) -> str:
251 """Media type for the audio payload, e.g. "audio/webm"."""
252 return self.mime_type.split(";", 1)[0]
253
254
255class TranscriptionResult(BaseModel):
256 """Input speech transcription result."""
257
258 text: str
259
260
261class ItemsListReq(BaseReq):
262 """Request to list items inside a thread."""
263
264 type: Literal["items.list"] = "items.list"
265 params: ItemsListParams
266
267
268class ItemsListParams(BaseModel):
269 """Pagination parameters for listing thread items."""
270
271 thread_id: str
272 limit: int | None = None
273 order: Literal["asc", "desc"] = "desc"
274 after: str | None = None
275
276
277class ThreadsUpdateReq(BaseReq):
278 """Request to update thread metadata."""
279
280 type: Literal["threads.update"] = "threads.update"
281 params: ThreadUpdateParams
282
283
284class ThreadUpdateParams(BaseModel):
285 """Parameters for updating a thread's properties."""
286
287 thread_id: str
288 title: str
289
290
291class ThreadsDeleteReq(BaseReq):
292 """Request to delete a thread."""
293
294 type: Literal["threads.delete"] = "threads.delete"
295 params: ThreadDeleteParams
296
297
298class ThreadDeleteParams(BaseModel):
299 """Parameters identifying a thread to delete."""
300
301 thread_id: str
302
303
304StreamingReq = (
305 ThreadsCreateReq
306 | ThreadsAddUserMessageReq
307 | ThreadsAddClientToolOutputReq
308 | ThreadsAddStructuredInputReq
309 | ThreadsRetryAfterItemReq
310 | ThreadsCustomActionReq
311)
312"""Union of request types that produce streaming responses."""
313
314
315NonStreamingReq = (
316 ThreadsGetByIdReq
317 | ThreadsListReq
318 | ItemsListReq
319 | ItemsFeedbackReq
320 | AttachmentsCreateReq
321 | AttachmentsDeleteReq
322 | ThreadsUpdateReq
323 | ThreadsDeleteReq
324 | InputTranscribeReq
325 | ThreadsSyncCustomActionReq
326)
327"""Union of request types that yield immediate responses."""
328
329
330ChatKitReq = Annotated[
331 StreamingReq | NonStreamingReq,
332 Field(discriminator="type"),
333]
334
335
336def is_streaming_req(request: ChatKitReq) -> TypeIs[StreamingReq]:
337 """Return True if the given request should be processed as streaming."""
338 return isinstance(
339 request,
340 (
341 ThreadsCreateReq,
342 ThreadsAddUserMessageReq,
343 ThreadsRetryAfterItemReq,
344 ThreadsAddClientToolOutputReq,
345 ThreadsAddStructuredInputReq,
346 ThreadsCustomActionReq,
347 ),
348 )
349
350
351### THREAD STREAM EVENT TYPES
352
353
354class ThreadCreatedEvent(BaseModel):
355 """Event emitted when a thread is created."""
356
357 type: Literal["thread.created"] = "thread.created"
358 thread: Thread
359
360
361class ThreadUpdatedEvent(BaseModel):
362 """Event emitted when a thread is updated."""
363
364 type: Literal["thread.updated"] = "thread.updated"
365 thread: Thread
366
367
368class ThreadItemAddedEvent(BaseModel):
369 """Event emitted when a new item is added to a thread."""
370
371 type: Literal["thread.item.added"] = "thread.item.added"
372 item: ThreadItem
373
374
375class ThreadItemUpdatedEvent(BaseModel):
376 """Event describing an update to an existing thread item."""
377
378 type: Literal["thread.item.updated"] = "thread.item.updated"
379 item_id: str
380 update: ThreadItemUpdate
381
382
383# Type alias for backwards compatibility
384ThreadItemUpdated = ThreadItemUpdatedEvent
385
386
387class ThreadItemDoneEvent(BaseModel):
388 """Event emitted when a thread item is marked complete."""
389
390 type: Literal["thread.item.done"] = "thread.item.done"
391 item: ThreadItem
392
393
394class ThreadItemRemovedEvent(BaseModel):
395 """Event emitted when a thread item is removed."""
396
397 type: Literal["thread.item.removed"] = "thread.item.removed"
398 item_id: str
399
400
401class ThreadItemReplacedEvent(BaseModel):
402 """Event emitted when a thread item is replaced."""
403
404 type: Literal["thread.item.replaced"] = "thread.item.replaced"
405 item: ThreadItem
406
407
408class StreamOptions(BaseModel):
409 """Settings that control runtime stream behavior."""
410
411 allow_cancel: bool
412 """Allow the client to request cancellation mid-stream."""
413
414
415class StreamOptionsEvent(BaseModel):
416 """Event emitted to set stream options at runtime."""
417
418 type: Literal["stream_options"] = "stream_options"
419 stream_options: StreamOptions
420
421
422class ProgressUpdateEvent(BaseModel):
423 """Event providing incremental progress from the assistant."""
424
425 type: Literal["progress_update"] = "progress_update"
426 icon: IconName | None = None
427 text: str
428
429
430class ClientEffectEvent(BaseModel):
431 """Event emitted to trigger a client side-effect."""
432
433 type: Literal["client_effect"] = "client_effect"
434 name: str
435 data: dict[str, Any] = Field(default_factory=dict)
436
437
438class ErrorEvent(BaseModel):
439 """Event indicating an error occurred while processing a thread."""
440
441 type: Literal["error"] = "error"
442 code: ErrorCode | Literal["custom"] = Field(default="custom")
443 message: str | None = None
444 allow_retry: bool = Field(default=False)
445
446
447class NoticeEvent(BaseModel):
448 """Event conveying a user-facing notice."""
449
450 type: Literal["notice"] = "notice"
451 level: Literal["info", "warning", "danger"]
452 message: str
453 """
454 Supports markdown e.g. "You've reached your limit of 100 messages. [Upgrade](https://...) to a paid plan."
455 """
456 title: str | None = None
457
458
459ThreadStreamEvent = Annotated[
460 ThreadCreatedEvent
461 | ThreadUpdatedEvent
462 | ThreadItemDoneEvent
463 | ThreadItemAddedEvent
464 | ThreadItemUpdated
465 | ThreadItemRemovedEvent
466 | ThreadItemReplacedEvent
467 | StreamOptionsEvent
468 | ProgressUpdateEvent
469 | ClientEffectEvent
470 | ErrorEvent
471 | NoticeEvent,
472 Field(discriminator="type"),
473]
474"""Union of all streaming events emitted to clients."""
475
476### THREAD ITEM UPDATE TYPES
477
478
479class AssistantMessageContentPartAdded(BaseModel):
480 """Event emitted when new assistant content is appended."""
481
482 type: Literal["assistant_message.content_part.added"] = (
483 "assistant_message.content_part.added"
484 )
485 content_index: int
486 content: AssistantMessageContent
487
488
489class AssistantMessageContentPartTextDelta(BaseModel):
490 """Event carrying incremental assistant text output."""
491
492 type: Literal["assistant_message.content_part.text_delta"] = (
493 "assistant_message.content_part.text_delta"
494 )
495 content_index: int
496 delta: str
497
498
499class AssistantMessageContentPartAnnotationAdded(BaseModel):
500 """Event announcing a new annotation on assistant content."""
501
502 type: Literal["assistant_message.content_part.annotation_added"] = (
503 "assistant_message.content_part.annotation_added"
504 )
505 content_index: int
506 annotation_index: int
507 annotation: Annotation
508
509
510class AssistantMessageContentPartDone(BaseModel):
511 """Event indicating an assistant content part is finalized."""
512
513 type: Literal["assistant_message.content_part.done"] = (
514 "assistant_message.content_part.done"
515 )
516 content_index: int
517 content: AssistantMessageContent
518
519
520class WidgetStreamingTextValueDelta(BaseModel):
521 """Event streaming widget text deltas."""
522
523 type: Literal["widget.streaming_text.value_delta"] = (
524 "widget.streaming_text.value_delta"
525 )
526 component_id: str
527 delta: str
528 done: bool
529
530
531class WidgetRootUpdated(BaseModel):
532 """Event published when the widget root changes."""
533
534 type: Literal["widget.root.updated"] = "widget.root.updated"
535 widget: WidgetRoot
536
537
538class WidgetComponentUpdated(BaseModel):
539 """Event emitted when a widget component updates."""
540
541 type: Literal["widget.component.updated"] = "widget.component.updated"
542 component_id: str
543 component: WidgetComponent
544
545
546class WorkflowTaskAdded(BaseModel):
547 """Event emitted when a workflow task is added."""
548
549 type: Literal["workflow.task.added"] = "workflow.task.added"
550 task_index: int
551 task: Task
552
553
554class WorkflowTaskUpdated(BaseModel):
555 """Event emitted when a workflow task is updated."""
556
557 type: Literal["workflow.task.updated"] = "workflow.task.updated"
558 task_index: int
559 task: Task
560
561
562class GeneratedImageUpdated(BaseModel):
563 """Event emitted when a generated image is updated."""
564
565 type: Literal["generated_image.updated"] = "generated_image.updated"
566 image: GeneratedImage
567 progress: float | None = None
568
569
570ThreadItemUpdate = (
571 AssistantMessageContentPartAdded
572 | AssistantMessageContentPartTextDelta
573 | AssistantMessageContentPartAnnotationAdded
574 | AssistantMessageContentPartDone
575 | WidgetStreamingTextValueDelta
576 | WidgetComponentUpdated
577 | WidgetRootUpdated
578 | WorkflowTaskAdded
579 | WorkflowTaskUpdated
580 | GeneratedImageUpdated
581)
582"""Union of possible updates applied to thread items."""
583
584
585class SyncCustomActionResponse(BaseModel):
586 """Single thread item update returned by a sync custom action."""
587 updated_item: ThreadItem | None = None
588
589
590### THREAD TYPES
591
592
593class ThreadMetadata(BaseModel):
594 """Metadata describing a thread without its items."""
595
596 title: str | None = None
597 id: str
598 created_at: datetime
599 status: ThreadStatus = Field(default_factory=lambda: ActiveStatus())
600 allowed_image_domains: list[str] | None = None
601 metadata: dict[str, Any] = Field(default_factory=dict)
602
603
604class ActiveStatus(BaseModel):
605 """Status indicating the thread is active."""
606
607 type: Literal["active"] = Field(default="active", frozen=True)
608
609
610class LockedStatus(BaseModel):
611 """Status indicating the thread is locked."""
612
613 type: Literal["locked"] = Field(default="locked", frozen=True)
614 reason: str | None = None
615
616
617class ClosedStatus(BaseModel):
618 """Status indicating the thread is closed."""
619
620 type: Literal["closed"] = Field(default="closed", frozen=True)
621 reason: str | None = None
622
623
624ThreadStatus = Annotated[
625 ActiveStatus | LockedStatus | ClosedStatus,
626 Field(discriminator="type"),
627]
628"""Union of lifecycle states for a thread."""
629
630
631class Thread(ThreadMetadata):
632 """Thread with its paginated items."""
633
634 items: Page[ThreadItem]
635
636
637### THREAD ITEM TYPES
638
639
640class ThreadItemBase(BaseModel):
641 """Base fields shared by all thread items."""
642
643 id: str
644 thread_id: str
645 created_at: datetime
646
647
648class UserMessageItem(ThreadItemBase):
649 """Thread item representing a user message."""
650
651 type: Literal["user_message"] = "user_message"
652 content: list[UserMessageContent]
653 attachments: list[Attachment] = Field(default_factory=list)
654 quoted_text: str | None = None
655 inference_options: InferenceOptions
656
657
658class AssistantMessageItem(ThreadItemBase):
659 """Thread item representing an assistant message."""
660
661 type: Literal["assistant_message"] = "assistant_message"
662 content: list[AssistantMessageContent]
663
664
665class ClientToolCallItem(ThreadItemBase):
666 """Thread item capturing a client tool call."""
667
668 type: Literal["client_tool_call"] = "client_tool_call"
669 status: Literal["pending", "completed"] = "pending"
670 call_id: str
671 name: str
672 arguments: dict[str, Any]
673 output: Any | None = None
674
675
676class WidgetItem(ThreadItemBase):
677 """Thread item containing widget content."""
678
679 type: Literal["widget"] = "widget"
680 widget: WidgetRoot
681 copy_text: str | None = None
682
683
684class GeneratedImage(BaseModel):
685 """Generated image."""
686
687 id: str
688 url: str
689
690
691class GeneratedImageItem(ThreadItemBase):
692 """Thread item containing a generated image."""
693
694 type: Literal["generated_image"] = "generated_image"
695 image: GeneratedImage | None = None
696
697
698class StructuredInputAnswer(BaseModel):
699 """Answer recorded for a structured input."""
700
701 values: list[str] = Field(default_factory=list)
702 """Text answer values recorded for the structured input."""
703 skipped: bool = False
704 """Whether this structured input answer was skipped."""
705
706
707class StructuredInputBase(BaseModel):
708 """Base fields shared by structured inputs."""
709
710 id: str
711 """Stable id for this structured input."""
712 question: str
713 """Question shown to the user."""
714 answer: StructuredInputAnswer | None = None
715 """Answer recorded for this structured input, if available."""
716
717
718class StructuredInputMultipleChoiceOption(BaseModel):
719 """Option shown for a multiple-choice structured input."""
720
721 value: str
722 """Text value submitted when this option is selected."""
723
724
725class StructuredInputMultipleChoice(StructuredInputBase):
726 """Structured input answered by choosing one or more options."""
727
728 type: Literal["multiple_choice"] = "multiple_choice"
729 options: list[StructuredInputMultipleChoiceOption]
730 """Suggested choices to display before the freeform custom answer affordance."""
731 multiple: bool = False
732 """Whether the user may submit more than one text value."""
733
734
735class StructuredInputFreeform(StructuredInputBase):
736 """Structured input answered with freeform text."""
737
738 type: Literal["freeform"] = "freeform"
739 description: str | None = None
740 """Supporting text shown with this input."""
741
742
743StructuredInput = Annotated[
744 StructuredInputMultipleChoice | StructuredInputFreeform,
745 Field(discriminator="type"),
746]
747"""Structured input variants supported by a structured input item."""
748
749
750class StructuredInputItem(ThreadItemBase):
751 """Thread item requesting structured input from the user."""
752
753 type: Literal["structured_input"] = "structured_input"
754 status: Literal["pending", "answered", "skipped"] = "pending"
755 inputs: list[StructuredInput]
756
757
758class TaskItem(ThreadItemBase):
759 """Thread item containing a task."""
760
761 type: Literal["task"] = "task"
762 task: Task
763
764
765class WorkflowItem(ThreadItemBase):
766 """Thread item representing a workflow."""
767
768 type: Literal["workflow"] = "workflow"
769 workflow: Workflow
770
771
772class EndOfTurnItem(ThreadItemBase):
773 """Marker item indicating the assistant ends its turn."""
774
775 type: Literal["end_of_turn"] = "end_of_turn"
776
777
778class HiddenContextItem(ThreadItemBase):
779 """
780 HiddenContext is never sent to the client. It's not officially part of ChatKit.js.
781 It is only used internally to store additional context in a specific place in the thread.
782 """
783
784 type: Literal["hidden_context_item"] = "hidden_context_item"
785 content: Any
786
787
788class SDKHiddenContextItem(ThreadItemBase):
789 """
790 Hidden context that is used by the ChatKit Python SDK for storing additional context
791 for internal operations.
792 """
793
794 type: Literal["sdk_hidden_context"] = "sdk_hidden_context"
795 content: str
796
797
798ThreadItem = Annotated[
799 UserMessageItem
800 | AssistantMessageItem
801 | ClientToolCallItem
802 | WidgetItem
803 | GeneratedImageItem
804 | StructuredInputItem
805 | WorkflowItem
806 | TaskItem
807 | HiddenContextItem
808 | SDKHiddenContextItem
809 | EndOfTurnItem,
810 Field(discriminator="type"),
811]
812"""Union of all thread item variants."""
813
814
815### ASSISTANT MESSAGE TYPES
816
817
818class AssistantMessageContent(BaseModel):
819 """Assistant message content consisting of text and annotations."""
820
821 annotations: list[Annotation] = Field(default_factory=list)
822 text: str
823 type: Literal["output_text"] = "output_text"
824
825
826class Annotation(BaseModel):
827 """Reference to supporting context attached to assistant output."""
828
829 type: Literal["annotation"] = "annotation"
830 source: URLSource | FileSource | EntitySource
831 index: int | None = None
832
833
834### USER MESSAGE TYPES
835
836
837class UserMessageInput(BaseModel):
838 """Payload describing a user message submission."""
839
840 content: list[UserMessageContent]
841 attachments: list[str]
842 quoted_text: str | None = None
843 inference_options: InferenceOptions
844
845
846class UserMessageTextContent(BaseModel):
847 """User message content containing plaintext."""
848
849 type: Literal["input_text"] = "input_text"
850 text: str
851
852
853class UserMessageTagContent(BaseModel):
854 """User message content representing an interactive tag."""
855
856 type: Literal["input_tag"] = "input_tag"
857 id: str
858 text: str
859 data: dict[str, Any]
860 group: str | None = None
861 interactive: bool = False
862
863
864UserMessageContent = Annotated[
865 UserMessageTextContent | UserMessageTagContent, Field(discriminator="type")
866]
867"""Union of allowed user message content payloads."""
868
869
870class InferenceOptions(BaseModel):
871 """Model and tool configuration for message processing."""
872
873 tool_choice: ToolChoice | None = None
874 model: str | None = None
875
876
877class ToolChoice(BaseModel):
878 """Explicit tool selection for the assistant to invoke."""
879
880 id: str
881
882
883class AttachmentUploadDescriptor(BaseModel):
884 """Two-phase upload instructions."""
885
886 url: AnyUrl
887 method: Literal["POST", "PUT"]
888 """The HTTP method to use when uploading the file for two-phase upload."""
889 headers: dict[str, str] = Field(default_factory=dict)
890 """Optional headers to include in the upload request."""
891
892
893class AttachmentBase(BaseModel):
894 """Base metadata shared by all attachments."""
895
896 id: str
897 name: str
898 mime_type: str
899 upload_descriptor: AttachmentUploadDescriptor | None = None
900 """
901 Two-phase upload instructions.
902 Should be set to None after upload is complete or when using direct upload
903 where uploading happens when creating the attachment object.
904 """
905 thread_id: str | None = None
906 """
907 The thread the attachment belongs to, if any.
908 Added when the user message that contains the attachment is saved to store.
909 """
910 metadata: dict[str, Any] | None = None
911 """
912 Integration-only metadata stored with the attachment. Ignored by ChatKit and not
913 returned in ChatKitServer responses. If you serialize attachments from a custom
914 direct-upload endpoint and want to omit this field, pass context={"exclude_metadata": True}.
915 """
916
917 @model_serializer(mode="wrap")
918 def _serialize(self, serializer, info: SerializationInfo):
919 data = serializer(self)
920 if isinstance(data, dict) and (info.context or {}).get("exclude_metadata"):
921 data.pop("metadata", None)
922 return data
923
924
925class FileAttachment(AttachmentBase):
926 """Attachment representing a generic file."""
927
928 type: Literal["file"] = "file"
929
930
931class ImageAttachment(AttachmentBase):
932 """Attachment representing an image resource."""
933
934 type: Literal["image"] = "image"
935 preview_url: AnyUrl
936
937
938Attachment = Annotated[
939 FileAttachment | ImageAttachment,
940 Field(discriminator="type"),
941]
942"""Union of supported attachment types."""
943
944
945### WORKFLOW TYPES
946
947
948class Workflow(BaseModel):
949 """Workflow attached to a thread with optional summary."""
950
951 type: Literal["custom", "reasoning"]
952 tasks: list[Task]
953 summary: WorkflowSummary | None = None
954 expanded: bool = False
955
956
957class CustomSummary(BaseModel):
958 """Custom summary for a workflow."""
959
960 title: str
961 icon: IconName | None = None
962
963
964class DurationSummary(BaseModel):
965 """Summary providing total workflow duration."""
966
967 duration: int
968 """The duration of the workflow in seconds"""
969
970
971WorkflowSummary = CustomSummary | DurationSummary
972"""Summary variants available for workflows."""
973
974### TASK TYPES
975
976
977class BaseTask(BaseModel):
978 """Base fields common to all workflow tasks."""
979
980 status_indicator: Literal["none", "loading", "complete"] = "none"
981 """Only used when rendering the task as part of a workflow. Indicates the status of the task."""
982
983
984class CustomTask(BaseTask):
985 """Workflow task displaying custom content."""
986
987 type: Literal["custom"] = "custom"
988 title: str | None = None
989 icon: IconName | None = None
990 content: str | None = None
991
992
993class SearchTask(BaseTask):
994 """Workflow task representing a web search."""
995
996 type: Literal["web_search"] = "web_search"
997 title: str | None = None
998 title_query: str | None = None
999 queries: list[str] = Field(default_factory=list)
1000 sources: list[URLSource] = Field(default_factory=list)
1001
1002
1003class ThoughtTask(BaseTask):
1004 """Workflow task capturing assistant reasoning."""
1005
1006 type: Literal["thought"] = "thought"
1007 title: str | None = None
1008 content: str
1009
1010
1011class FileTask(BaseTask):
1012 """Workflow task referencing file sources."""
1013
1014 type: Literal["file"] = "file"
1015 title: str | None = None
1016 sources: list[FileSource] = Field(default_factory=list)
1017
1018
1019class ImageTask(BaseTask):
1020 """Workflow task rendering image content."""
1021
1022 type: Literal["image"] = "image"
1023 title: str | None = None
1024
1025
1026Task = Annotated[
1027 CustomTask | SearchTask | ThoughtTask | FileTask | ImageTask,
1028 Field(discriminator="type"),
1029]
1030"""Union of workflow task variants."""
1031
1032
1033### SOURCE TYPES
1034
1035
1036class SourceBase(BaseModel):
1037 """Base class for sources displayed to users."""
1038
1039 title: str
1040 description: str | None = None
1041 timestamp: str | None = None
1042 group: str | None = None
1043
1044
1045class FileSource(SourceBase):
1046 """Source metadata for file-based references."""
1047
1048 type: Literal["file"] = "file"
1049 filename: str
1050
1051
1052class URLSource(SourceBase):
1053 """Source metadata for external URLs."""
1054
1055 type: Literal["url"] = "url"
1056 url: str
1057 attribution: str | None = None
1058
1059
1060class EntitySource(SourceBase):
1061 """Source metadata for entity references."""
1062
1063 type: Literal["entity"] = "entity"
1064 id: str
1065 icon: IconName | None = None
1066 label: str | None = None
1067 """Optional label shown with the icon in the default entity hover header
1068 when no preview callback is provided.
1069 """
1070 inline_label: str | None = None
1071 """Optional label for the inline annotation view. When not provided, the icon is used instead."""
1072 interactive: bool = False
1073 """Per-entity toggle to wire client callbacks and render this entity as interactive."""
1074 data: dict[str, Any] = Field(default_factory=dict)
1075 """Additional data for the entity source that is passed to client entity callbacks."""
1076
1077 preview: Literal["lazy"] | None = Field(
1078 default=None,
1079 deprecated=True,
1080 description="This field is ignored. Please use the entities.onRequestPreview ChatKit.js option instead.",
1081 )
1082
1083
1084Source = Annotated[
1085 URLSource | FileSource | EntitySource,
1086 Field(discriminator="type"),
1087]
1088"""Union of supported source types."""
1089
1090
1091### MISC TYPES
1092
1093
1094FeedbackKind = Literal["positive", "negative"]
1095"""Literal type for feedback sentiment."""
1096