openai/chatkit-python

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v1.5.2

Branches

Tags

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

Clone

HTTPS

Download ZIP

chatkit/types.py

923lines · modecode

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