openai/chatkit-python

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
4e1156dd81f931ed2f455197737892dd59437ab2

Branches

Tags

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

Clone

HTTPS

Download ZIP

chatkit/types.py

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