openai/chatkit-python

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
45ae38b39b3b506ac24db075ec0faf8a6a930b5e

Branches

Tags

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

Clone

HTTPS

Download ZIP

chatkit/types.py

888lines · 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
474ThreadItemUpdate = (
475 AssistantMessageContentPartAdded
476 | AssistantMessageContentPartTextDelta
477 | AssistantMessageContentPartAnnotationAdded
478 | AssistantMessageContentPartDone
479 | WidgetStreamingTextValueDelta
480 | WidgetComponentUpdated
481 | WidgetRootUpdated
482 | WorkflowTaskAdded
483 | WorkflowTaskUpdated
484)
485"""Union of possible updates applied to thread items."""
486
487
488### THREAD TYPES
489
490
491class ThreadMetadata(BaseModel):
492 """Metadata describing a thread without its items."""
493
494 title: str | None = None
495 id: str
496 created_at: datetime
497 status: ThreadStatus = Field(default_factory=lambda: ActiveStatus())
498 # TODO - make not client rendered
499 metadata: dict[str, Any] = Field(default_factory=dict)
500
501
502class ActiveStatus(BaseModel):
503 """Status indicating the thread is active."""
504
505 type: Literal["active"] = Field(default="active", frozen=True)
506
507
508class LockedStatus(BaseModel):
509 """Status indicating the thread is locked."""
510
511 type: Literal["locked"] = Field(default="locked", frozen=True)
512 reason: str | None = None
513
514
515class ClosedStatus(BaseModel):
516 """Status indicating the thread is closed."""
517
518 type: Literal["closed"] = Field(default="closed", frozen=True)
519 reason: str | None = None
520
521
522ThreadStatus = Annotated[
523 ActiveStatus | LockedStatus | ClosedStatus,
524 Field(discriminator="type"),
525]
526"""Union of lifecycle states for a thread."""
527
528
529class Thread(ThreadMetadata):
530 """Thread with its paginated items."""
531
532 items: Page[ThreadItem]
533
534
535### THREAD ITEM TYPES
536
537
538class ThreadItemBase(BaseModel):
539 """Base fields shared by all thread items."""
540
541 id: str
542 thread_id: str
543 created_at: datetime
544
545
546class UserMessageItem(ThreadItemBase):
547 """Thread item representing a user message."""
548
549 type: Literal["user_message"] = "user_message"
550 content: list[UserMessageContent]
551 attachments: list[Attachment] = Field(default_factory=list)
552 quoted_text: str | None = None
553 inference_options: InferenceOptions
554
555
556class AssistantMessageItem(ThreadItemBase):
557 """Thread item representing an assistant message."""
558
559 type: Literal["assistant_message"] = "assistant_message"
560 content: list[AssistantMessageContent]
561
562
563class ClientToolCallItem(ThreadItemBase):
564 """Thread item capturing a client tool call."""
565
566 type: Literal["client_tool_call"] = "client_tool_call"
567 status: Literal["pending", "completed"] = "pending"
568 call_id: str
569 name: str
570 arguments: dict[str, Any]
571 output: Any | None = None
572
573
574class WidgetItem(ThreadItemBase):
575 """Thread item containing widget content."""
576
577 type: Literal["widget"] = "widget"
578 widget: WidgetRoot
579 copy_text: str | None = None
580
581
582class TaskItem(ThreadItemBase):
583 """Thread item containing a task."""
584
585 type: Literal["task"] = "task"
586 task: Task
587
588
589class WorkflowItem(ThreadItemBase):
590 """Thread item representing a workflow."""
591
592 type: Literal["workflow"] = "workflow"
593 workflow: Workflow
594
595
596class EndOfTurnItem(ThreadItemBase):
597 """Marker item indicating the assistant ends its turn."""
598
599 type: Literal["end_of_turn"] = "end_of_turn"
600
601
602class HiddenContextItem(ThreadItemBase):
603 """
604 HiddenContext is never sent to the client. It's not officially part of ChatKit.js.
605 It is only used internally to store additional context in a specific place in the thread.
606 """
607
608 type: Literal["hidden_context_item"] = "hidden_context_item"
609 content: Any
610
611
612class SDKHiddenContextItem(ThreadItemBase):
613 """
614 Hidden context that is used by the ChatKit Python SDK for storing additional context
615 for internal operations.
616 """
617
618 type: Literal["sdk_hidden_context"] = "sdk_hidden_context"
619 content: str
620
621
622ThreadItem = Annotated[
623 UserMessageItem
624 | AssistantMessageItem
625 | ClientToolCallItem
626 | WidgetItem
627 | WorkflowItem
628 | TaskItem
629 | HiddenContextItem
630 | SDKHiddenContextItem
631 | EndOfTurnItem,
632 Field(discriminator="type"),
633]
634"""Union of all thread item variants."""
635
636
637### ASSISTANT MESSAGE TYPES
638
639
640class AssistantMessageContent(BaseModel):
641 """Assistant message content consisting of text and annotations."""
642
643 annotations: list[Annotation] = Field(default_factory=list)
644 text: str
645 type: Literal["output_text"] = "output_text"
646
647
648class Annotation(BaseModel):
649 """Reference to supporting context attached to assistant output."""
650
651 type: Literal["annotation"] = "annotation"
652 source: URLSource | FileSource | EntitySource
653 index: int | None = None
654
655
656### USER MESSAGE TYPES
657
658
659class UserMessageInput(BaseModel):
660 """Payload describing a user message submission."""
661
662 content: list[UserMessageContent]
663 attachments: list[str]
664 quoted_text: str | None = None
665 inference_options: InferenceOptions
666
667
668class UserMessageTextContent(BaseModel):
669 """User message content containing plaintext."""
670
671 type: Literal["input_text"] = "input_text"
672 text: str
673
674
675class UserMessageTagContent(BaseModel):
676 """User message content representing an interactive tag."""
677
678 type: Literal["input_tag"] = "input_tag"
679 id: str
680 text: str
681 data: dict[str, Any]
682 group: str | None = None
683 interactive: bool = False
684
685
686UserMessageContent = Annotated[
687 UserMessageTextContent | UserMessageTagContent, Field(discriminator="type")
688]
689"""Union of allowed user message content payloads."""
690
691
692class InferenceOptions(BaseModel):
693 """Model and tool configuration for message processing."""
694
695 tool_choice: ToolChoice | None = None
696 model: str | None = None
697
698
699class ToolChoice(BaseModel):
700 """Explicit tool selection for the assistant to invoke."""
701
702 id: str
703
704
705class AttachmentBase(BaseModel):
706 """Base metadata shared by all attachments."""
707
708 id: str
709 name: str
710 mime_type: str
711 upload_url: AnyUrl | None = None
712 """
713 The URL to upload the file, used for two-phase upload.
714 Should be set to None after upload is complete or when using direct upload where uploading happens when creating the attachment object.
715 """
716
717
718class FileAttachment(AttachmentBase):
719 """Attachment representing a generic file."""
720
721 type: Literal["file"] = "file"
722
723
724class ImageAttachment(AttachmentBase):
725 """Attachment representing an image resource."""
726
727 type: Literal["image"] = "image"
728 preview_url: AnyUrl
729
730
731Attachment = Annotated[
732 FileAttachment | ImageAttachment,
733 Field(discriminator="type"),
734]
735"""Union of supported attachment types."""
736
737
738### WORKFLOW TYPES
739
740
741class Workflow(BaseModel):
742 """Workflow attached to a thread with optional summary."""
743
744 type: Literal["custom", "reasoning"]
745 tasks: list[Task]
746 summary: WorkflowSummary | None = None
747 expanded: bool = False
748
749
750class CustomSummary(BaseModel):
751 """Custom summary for a workflow."""
752
753 title: str
754 icon: IconName | None = None
755
756
757class DurationSummary(BaseModel):
758 """Summary providing total workflow duration."""
759
760 duration: int
761 """The duration of the workflow in seconds"""
762
763
764WorkflowSummary = CustomSummary | DurationSummary
765"""Summary variants available for workflows."""
766
767### TASK TYPES
768
769
770class BaseTask(BaseModel):
771 """Base fields common to all workflow tasks."""
772
773 status_indicator: Literal["none", "loading", "complete"] = "none"
774 """Only used when rendering the task as part of a workflow. Indicates the status of the task."""
775
776
777class CustomTask(BaseTask):
778 """Workflow task displaying custom content."""
779
780 type: Literal["custom"] = "custom"
781 title: str | None = None
782 icon: IconName | None = None
783 content: str | None = None
784
785
786class SearchTask(BaseTask):
787 """Workflow task representing a web search."""
788
789 type: Literal["web_search"] = "web_search"
790 title: str | None = None
791 title_query: str | None = None
792 queries: list[str] = Field(default_factory=list)
793 sources: list[URLSource] = Field(default_factory=list)
794
795
796class ThoughtTask(BaseTask):
797 """Workflow task capturing assistant reasoning."""
798
799 type: Literal["thought"] = "thought"
800 title: str | None = None
801 content: str
802
803
804class FileTask(BaseTask):
805 """Workflow task referencing file sources."""
806
807 type: Literal["file"] = "file"
808 title: str | None = None
809 sources: list[FileSource] = Field(default_factory=list)
810
811
812class ImageTask(BaseTask):
813 """Workflow task rendering image content."""
814
815 type: Literal["image"] = "image"
816 title: str | None = None
817
818
819Task = Annotated[
820 CustomTask | SearchTask | ThoughtTask | FileTask | ImageTask,
821 Field(discriminator="type"),
822]
823"""Union of workflow task variants."""
824
825
826### SOURCE TYPES
827
828
829class SourceBase(BaseModel):
830 """Base class for sources displayed to users."""
831
832 title: str
833 description: str | None = None
834 timestamp: str | None = None
835 group: str | None = None
836
837
838class FileSource(SourceBase):
839 """Source metadata for file-based references."""
840
841 type: Literal["file"] = "file"
842 filename: str
843
844
845class URLSource(SourceBase):
846 """Source metadata for external URLs."""
847
848 type: Literal["url"] = "url"
849 url: str
850 attribution: str | None = None
851
852
853class EntitySource(SourceBase):
854 """Source metadata for entity references."""
855
856 type: Literal["entity"] = "entity"
857 id: str
858 icon: IconName | None = None
859 label: str | None = None
860 """Optional label shown with the icon in the default entity hover header
861 when no preview callback is provided.
862 """
863 inline_label: str | None = None
864 """Optional label for the inline annotation view. When not provided, the icon is used instead."""
865 interactive: bool = False
866 """Per-entity toggle to wire client callbacks and render this entity as interactive."""
867 data: dict[str, Any] = Field(default_factory=dict)
868 """Additional data for the entity source that is passed to client entity callbacks."""
869
870 preview: Literal["lazy"] | None = Field(
871 default=None,
872 deprecated=True,
873 description="This field is ignored. Please use the entities.onRequestPreview ChatKit.js option instead.",
874 )
875
876
877Source = Annotated[
878 URLSource | FileSource | EntitySource,
879 Field(discriminator="type"),
880]
881"""Union of supported source types."""
882
883
884### MISC TYPES
885
886
887FeedbackKind = Literal["positive", "negative"]
888"""Literal type for feedback sentiment."""
889