diff --git a/kittycad.py.patch.json b/kittycad.py.patch.json index dbc8f7e4f..6097407da 100644 --- a/kittycad.py.patch.json +++ b/kittycad.py.patch.json @@ -147,7 +147,7 @@ "op": "add", "path": "/paths/~1file~1conversion/post/x-python", "value": { - "example": "from kittycad.models.conversion_params import ConversionParams\nfrom kittycad.models.output_format3d import OptionStep\nfrom kittycad.models.system import System\nfrom kittycad.models.axis_direction_pair import AxisDirectionPair\nfrom kittycad.models.axis import Axis\nfrom kittycad.models.direction import Direction\nfrom kittycad.models.axis_direction_pair import AxisDirectionPair\nfrom kittycad.models.axis import Axis\nfrom kittycad.models.direction import Direction\nfrom kittycad.models.step_presentation import StepPresentation\nfrom kittycad.models.unit_length import UnitLength\nfrom kittycad.models.output_format3d import OutputFormat3d\nfrom kittycad.models.input_format3d import OptionStep\nfrom kittycad.models.system import System\nfrom kittycad.models.axis_direction_pair import AxisDirectionPair\nfrom kittycad.models.axis import Axis\nfrom kittycad.models.direction import Direction\nfrom kittycad.models.axis_direction_pair import AxisDirectionPair\nfrom kittycad.models.axis import Axis\nfrom kittycad.models.direction import Direction\nfrom kittycad.models.input_format3d import InputFormat3d\nfrom pathlib import Path\nfrom typing import Dict\nfrom kittycad._io_types import SyncUpload\nfrom kittycad.models import FileConversion\nfrom typing import Union, Any, Optional, List, Tuple\nfrom kittycad.types import Response\ndef example_create_file_conversion_options():\n client = KittyCAD() # Uses KITTYCAD_API_TOKEN environment variable\n\n result: FileConversion = client.file.create_file_conversion_options(body=ConversionParams(\n output_format=OutputFormat3d(OptionStep(\n coords=System(\n forward=AxisDirectionPair(\n axis=Axis.Y,\n direction=Direction.POSITIVE,\n ),\n up=AxisDirectionPair(\n axis=Axis.Y,\n direction=Direction.POSITIVE,\n ),\n ),\n presentation=StepPresentation.COMPACT,\n units=UnitLength.CM,\n )),\n src_format=InputFormat3d(OptionStep(\n coords=System(\n forward=AxisDirectionPair(\n axis=Axis.Y,\n direction=Direction.POSITIVE,\n ),\n up=AxisDirectionPair(\n axis=Axis.Y,\n direction=Direction.POSITIVE,\n ),\n ),\n split_closed_faces=False,\n )),\n ),\n file_attachments={\n \"main.kcl\": Path(\"path/to/main.kcl\"),\n \"helper.kcl\": Path(\"path/to/helper.kcl\"),\n })\n\n\n body: FileConversion = result\n print(body)\n\n", + "example": "from kittycad.models.conversion_params import ConversionParams\nfrom kittycad.models.output_format3d import OptionStep\nfrom kittycad.models.system import System\nfrom kittycad.models.axis_direction_pair import AxisDirectionPair\nfrom kittycad.models.axis import Axis\nfrom kittycad.models.direction import Direction\nfrom kittycad.models.axis_direction_pair import AxisDirectionPair\nfrom kittycad.models.axis import Axis\nfrom kittycad.models.direction import Direction\nfrom kittycad.models.step_presentation import StepPresentation\nfrom kittycad.models.unit_length import UnitLength\nfrom kittycad.models.output_format3d import OutputFormat3d\nfrom kittycad.models.input_format3d import OptionGltf\nfrom kittycad.models.input_format3d import InputFormat3d\nfrom pathlib import Path\nfrom typing import Dict\nfrom kittycad._io_types import SyncUpload\nfrom kittycad.models import FileConversion\nfrom typing import Union, Any, Optional, List, Tuple\nfrom kittycad.types import Response\ndef example_create_file_conversion_options():\n client = KittyCAD() # Uses KITTYCAD_API_TOKEN environment variable\n\n result: FileConversion = client.file.create_file_conversion_options(body=ConversionParams(\n output_format=OutputFormat3d(OptionStep(\n coords=System(\n forward=AxisDirectionPair(\n axis=Axis.Y,\n direction=Direction.POSITIVE,\n ),\n up=AxisDirectionPair(\n axis=Axis.Y,\n direction=Direction.POSITIVE,\n ),\n ),\n presentation=StepPresentation.COMPACT,\n units=UnitLength.CM,\n )),\n src_format=InputFormat3d(OptionGltf()),\n ),\n file_attachments={\n \"main.kcl\": Path(\"path/to/main.kcl\"),\n \"helper.kcl\": Path(\"path/to/helper.kcl\"),\n })\n\n\n body: FileConversion = result\n print(body)\n\n", "libDocsLink": "https://python.api.docs.zoo.dev/_autosummary/kittycad.KittyCAD.html#kittycad.KittyCAD.file" } }, @@ -747,7 +747,7 @@ "op": "add", "path": "/paths/~1org~1saml~1idp/post/x-python", "value": { - "example": "from kittycad.models.saml_identity_provider_create import SamlIdentityProviderCreate\nfrom kittycad.models.idp_metadata_source import OptionBase64EncodedXml\nfrom kittycad.models.base64data import Base64Data\nfrom kittycad.models.idp_metadata_source import IdpMetadataSource\nfrom kittycad.models import SamlIdentityProvider\nfrom typing import Union, Any, Optional, List, Tuple\nfrom kittycad.types import Response\ndef example_create_org_saml_idp():\n client = KittyCAD() # Uses KITTYCAD_API_TOKEN environment variable\n\n result: SamlIdentityProvider = client.orgs.create_org_saml_idp(body=SamlIdentityProviderCreate(\n idp_entity_id=\"\",\n idp_metadata_source=IdpMetadataSource(OptionBase64EncodedXml(\n data=Base64Data(b\"\"),\n )),\n technical_contact_email=\"\",\n ))\n\n\n body: SamlIdentityProvider = result\n print(body)\n\n", + "example": "from kittycad.models.saml_identity_provider_create import SamlIdentityProviderCreate\nfrom kittycad.models.idp_metadata_source import OptionUrl\nfrom kittycad.models.idp_metadata_source import IdpMetadataSource\nfrom kittycad.models import SamlIdentityProvider\nfrom typing import Union, Any, Optional, List, Tuple\nfrom kittycad.types import Response\ndef example_create_org_saml_idp():\n client = KittyCAD() # Uses KITTYCAD_API_TOKEN environment variable\n\n result: SamlIdentityProvider = client.orgs.create_org_saml_idp(body=SamlIdentityProviderCreate(\n idp_entity_id=\"\",\n idp_metadata_source=IdpMetadataSource(OptionUrl(\n url=\"\",\n )),\n technical_contact_email=\"\",\n ))\n\n\n body: SamlIdentityProvider = result\n print(body)\n\n", "libDocsLink": "https://python.api.docs.zoo.dev/_autosummary/kittycad.KittyCAD.html#kittycad.KittyCAD.orgs" } }, @@ -1611,7 +1611,7 @@ "op": "add", "path": "/paths/~1ws~1ml~1copilot/get/x-python", "value": { - "example": "from kittycad.models import MlCopilotClientMessage\nfrom kittycad.models.ml_copilot_client_message import OptionHeaders\nfrom kittycad.models import MlCopilotServerMessage\nfrom typing import Union, Any, Optional, List, Tuple\nfrom kittycad.types import Response\ndef example_ml_copilot_ws():\n client = KittyCAD() # Uses KITTYCAD_API_TOKEN environment variable\n\n # Connect to the websocket.\n with client.ml.ml_copilot_ws(replay=None,\n conversation_id=None,\n pr=None) as websocket:\n\n # Send a message.\n websocket.send(MlCopilotClientMessage(OptionHeaders(\nheaders={\"\": \"\"},\n)))\n\n # Get a message.\n message = websocket.recv()\n print(message)\n\n ", + "example": "from kittycad.models import MlCopilotClientMessage\nfrom kittycad.models.ml_copilot_client_message import OptionSystem\nfrom kittycad.models.ml_copilot_system_command import MlCopilotSystemCommand\nfrom kittycad.models import MlCopilotServerMessage\nfrom typing import Union, Any, Optional, List, Tuple\nfrom kittycad.types import Response\ndef example_ml_copilot_ws():\n client = KittyCAD() # Uses KITTYCAD_API_TOKEN environment variable\n\n # Connect to the websocket.\n with client.ml.ml_copilot_ws(replay=None,\n conversation_id=None,\n pr=None) as websocket:\n\n # Send a message.\n websocket.send(MlCopilotClientMessage(OptionSystem(\ncommand=MlCopilotSystemCommand.NEW,\n)))\n\n # Get a message.\n message = websocket.recv()\n print(message)\n\n ", "libDocsLink": "https://python.api.docs.zoo.dev/_autosummary/kittycad.KittyCAD.html#kittycad.KittyCAD.ml" } }, @@ -1619,7 +1619,7 @@ "op": "add", "path": "/paths/~1ws~1ml~1reasoning~1{id}/get/x-python", "value": { - "example": "from kittycad.models import MlCopilotClientMessage\nfrom kittycad.models.ml_copilot_client_message import OptionPing\nfrom kittycad.models import MlCopilotServerMessage\nfrom typing import Union, Any, Optional, List, Tuple\nfrom kittycad.types import Response\ndef example_ml_reasoning_ws():\n client = KittyCAD() # Uses KITTYCAD_API_TOKEN environment variable\n\n # Connect to the websocket.\n with client.ml.ml_reasoning_ws(id=\"\") as websocket:\n\n # Send a message.\n websocket.send(MlCopilotClientMessage(OptionPing()))\n\n # Get a message.\n message = websocket.recv()\n print(message)\n\n ", + "example": "from kittycad.models import MlCopilotClientMessage\nfrom kittycad.models.ml_copilot_client_message import OptionSystem\nfrom kittycad.models.ml_copilot_system_command import MlCopilotSystemCommand\nfrom kittycad.models import MlCopilotServerMessage\nfrom typing import Union, Any, Optional, List, Tuple\nfrom kittycad.types import Response\ndef example_ml_reasoning_ws():\n client = KittyCAD() # Uses KITTYCAD_API_TOKEN environment variable\n\n # Connect to the websocket.\n with client.ml.ml_reasoning_ws(id=\"\") as websocket:\n\n # Send a message.\n websocket.send(MlCopilotClientMessage(OptionSystem(\ncommand=MlCopilotSystemCommand.NEW,\n)))\n\n # Get a message.\n message = websocket.recv()\n print(message)\n\n ", "libDocsLink": "https://python.api.docs.zoo.dev/_autosummary/kittycad.KittyCAD.html#kittycad.KittyCAD.ml" } }, @@ -1627,7 +1627,7 @@ "op": "add", "path": "/paths/~1ws~1modeling~1commands/get/x-python", "value": { - "example": "from kittycad.models.post_effect_type import PostEffectType\nfrom kittycad.models import WebSocketRequest\nfrom kittycad.models.web_socket_request import OptionHeaders\nfrom kittycad.models import WebSocketResponse\nfrom typing import Union, Any, Optional, List, Tuple\nfrom kittycad.types import Response\ndef example_modeling_commands_ws():\n client = KittyCAD() # Uses KITTYCAD_API_TOKEN environment variable\n\n # Connect to the websocket.\n with client.modeling.modeling_commands_ws(video_res_width=10,\n video_res_height=10,\n fps=10,\n unlocked_framerate=False,\n post_effect=PostEffectType.PHOSPHOR,\n webrtc=False,\n show_grid=False,\n order_independent_transparency=False,\n pool=None,\n replay=None,\n api_call_id=None,\n pr=None) as websocket:\n\n # Send a message.\n websocket.send(WebSocketRequest(OptionHeaders(\nheaders={\"\": \"\"},\n)))\n\n # Get a message.\n message = websocket.recv()\n print(message)\n\n ", + "example": "from kittycad.models.post_effect_type import PostEffectType\nfrom kittycad.models import WebSocketRequest\nfrom kittycad.models.web_socket_request import OptionModelingCmdReq\nfrom kittycad.models.modeling_cmd import OptionObjectBringToFront\nfrom kittycad.models.modeling_cmd import ModelingCmd\nfrom kittycad.models.modeling_cmd_id import ModelingCmdId\nfrom kittycad.models.modeling_cmd_id import ModelingCmdId\nfrom kittycad.models import WebSocketResponse\nfrom typing import Union, Any, Optional, List, Tuple\nfrom kittycad.types import Response\ndef example_modeling_commands_ws():\n client = KittyCAD() # Uses KITTYCAD_API_TOKEN environment variable\n\n # Connect to the websocket.\n with client.modeling.modeling_commands_ws(video_res_width=10,\n video_res_height=10,\n fps=10,\n unlocked_framerate=False,\n post_effect=PostEffectType.PHOSPHOR,\n webrtc=False,\n show_grid=False,\n order_independent_transparency=False,\n pool=None,\n replay=None,\n api_call_id=None,\n pr=None) as websocket:\n\n # Send a message.\n websocket.send(WebSocketRequest(OptionModelingCmdReq(\ncmd=ModelingCmd(OptionObjectBringToFront(\nobject_id=\"\",\n)),\n\ncmd_id=ModelingCmdId(\"\"),\n)))\n\n # Get a message.\n message = websocket.recv()\n print(message)\n\n ", "libDocsLink": "https://python.api.docs.zoo.dev/_autosummary/kittycad.KittyCAD.html#kittycad.KittyCAD.modeling" } } diff --git a/kittycad/models/ml_copilot_client_message.py b/kittycad/models/ml_copilot_client_message.py index 726441784..633e5644e 100644 --- a/kittycad/models/ml_copilot_client_message.py +++ b/kittycad/models/ml_copilot_client_message.py @@ -10,6 +10,7 @@ from ..models.ml_copilot_tool import MlCopilotTool from ..models.ml_reasoning_effort import MlReasoningEffort from ..models.source_range_prompt import SourceRangePrompt +from ..models.uuid import Uuid from .base import KittyCadBaseModel @@ -75,6 +76,22 @@ class OptionSystem(KittyCadBaseModel): type: Literal["system"] = "system" +class OptionAttachmentResponse(KittyCadBaseModel): + """Attachments returned by API in response to a backend `RequestAttachments` message.""" + + error: Optional[str] = None + + files: Optional[List[MlCopilotFile]] = None + + prompt_id: Optional[Uuid] = None + + request_id: Optional[str] = None + + seq: Optional[int] = None + + type: Literal["attachment_response"] = "attachment_response" + + MlCopilotClientMessage = RootModel[ Annotated[ Union[ @@ -84,6 +101,7 @@ class OptionSystem(KittyCadBaseModel): OptionProjectContext, OptionUser, OptionSystem, + OptionAttachmentResponse, ], Field(discriminator="type"), ] diff --git a/kittycad/models/ml_copilot_server_message.py b/kittycad/models/ml_copilot_server_message.py index ed44a8ee0..7f1c30369 100644 --- a/kittycad/models/ml_copilot_server_message.py +++ b/kittycad/models/ml_copilot_server_message.py @@ -239,6 +239,66 @@ class Reasoning(KittyCadBaseModel): reasoning: ReasoningMessage +class RequestAttachments(KittyCadBaseModel): + """Backend-only request for API to reload client attachments from storage. + + API handles this message internally and responds upstream with `MlCopilotClientMessage::AttachmentResponse`; it is not forwarded to the browser.""" + + conversation_id: Optional[Uuid] = None + + names: Optional[List[str]] = None + + only_metadata: Optional[bool] = False + + prompt_id: Optional[Uuid] = None + + request_id: Optional[str] = None + + seq: Optional[int] = None + + @model_validator(mode="before") + @classmethod + def _unwrap(cls, data): + if ( + isinstance(data, dict) + and "request_attachments" in data + and isinstance(data["request_attachments"], dict) + ): + return data["request_attachments"] + + return data + + @model_serializer(mode="wrap") + def _wrap(self, handler, info): + payload = handler(self, info) + + return {"request_attachments": payload} + + +class AttachmentsLoaded(KittyCadBaseModel): + """Notification that API finished loading all attachments for the conversation.""" + + request_id: Optional[str] = None + + @model_validator(mode="before") + @classmethod + def _unwrap(cls, data): + if ( + isinstance(data, dict) + and "attachments_loaded" in data + and isinstance(data["attachments_loaded"], dict) + ): + return data["attachments_loaded"] + + return data + + @model_serializer(mode="wrap") + def _wrap(self, handler, info): + payload = handler(self, info) + + return {"attachments_loaded": payload} + + class Replay(KittyCadBaseModel): """Replay containing raw bytes for previously-saved messages for a conversation. Includes server messages and client `User` messages. @@ -336,6 +396,8 @@ def _wrap(self, handler, info): BackendShutdown, ProjectUpdated, Reasoning, + RequestAttachments, + AttachmentsLoaded, Replay, EndOfStream, Files, diff --git a/kittycad/tests/test_examples.py b/kittycad/tests/test_examples.py index d692c2c23..1fc48331b 100644 --- a/kittycad/tests/test_examples.py +++ b/kittycad/tests/test_examples.py @@ -129,23 +129,24 @@ from kittycad.models.idp_metadata_source import ( IdpMetadataSource, OptionBase64EncodedXml, + OptionUrl, ) -from kittycad.models.input_format3d import InputFormat3d, OptionStep +from kittycad.models.input_format3d import InputFormat3d, OptionGltf from kittycad.models.kcl_code_completion_params import KclCodeCompletionParams from kittycad.models.kcl_code_completion_request import KclCodeCompletionRequest from kittycad.models.kcl_project_share_link_access_mode import ( KclProjectShareLinkAccessMode, ) from kittycad.models.lenient_url import LenientUrl -from kittycad.models.ml_copilot_client_message import OptionHeaders, OptionPing +from kittycad.models.ml_copilot_client_message import OptionSystem +from kittycad.models.ml_copilot_system_command import MlCopilotSystemCommand from kittycad.models.ml_feedback import MlFeedback +from kittycad.models.modeling_cmd import ModelingCmd, OptionObjectBringToFront +from kittycad.models.modeling_cmd_id import ModelingCmdId from kittycad.models.o_auth2_app_grant_type import OAuth2AppGrantType from kittycad.models.org_dataset_source import OrgDatasetSource from kittycad.models.org_details import OrgDetails -from kittycad.models.output_format3d import ( - OptionStep as OutputFormat3dOptionStep, - OutputFormat3d, -) +from kittycad.models.output_format3d import OptionStep, OutputFormat3d from kittycad.models.plan_interval import PlanInterval from kittycad.models.post_effect_type import PostEffectType from kittycad.models.price_upsert_request import PriceUpsertRequest @@ -199,9 +200,7 @@ from kittycad.models.user_identifier import UserIdentifier from kittycad.models.user_org_role import UserOrgRole from kittycad.models.uuid import Uuid -from kittycad.models.web_socket_request import ( - OptionHeaders as WebSocketRequestOptionHeaders, -) +from kittycad.models.web_socket_request import OptionModelingCmdReq from kittycad.models.website_sales_form import WebsiteSalesForm from kittycad.models.website_support_form import WebsiteSupportForm from kittycad.models.zoo_product_subscriptions_org_request import ( @@ -571,7 +570,7 @@ def test_create_file_conversion_options(): result: FileConversion = client.file.create_file_conversion_options( body=ConversionParams( output_format=OutputFormat3d( - OutputFormat3dOptionStep( + OptionStep( coords=System( forward=AxisDirectionPair( axis=Axis.Y, @@ -586,21 +585,7 @@ def test_create_file_conversion_options(): units=UnitLength.CM, ) ), - src_format=InputFormat3d( - OptionStep( - coords=System( - forward=AxisDirectionPair( - axis=Axis.Y, - direction=Direction.POSITIVE, - ), - up=AxisDirectionPair( - axis=Axis.Y, - direction=Direction.POSITIVE, - ), - ), - split_closed_faces=False, - ) - ), + src_format=InputFormat3d(OptionGltf()), ), file_attachments={ "main.kcl": Path("path/to/main.kcl"), @@ -621,7 +606,7 @@ async def test_create_file_conversion_options_async(): result: FileConversion = await client.file.create_file_conversion_options( body=ConversionParams( output_format=OutputFormat3d( - OutputFormat3dOptionStep( + OptionStep( coords=System( forward=AxisDirectionPair( axis=Axis.Y, @@ -636,21 +621,7 @@ async def test_create_file_conversion_options_async(): units=UnitLength.CM, ) ), - src_format=InputFormat3d( - OptionStep( - coords=System( - forward=AxisDirectionPair( - axis=Axis.Y, - direction=Direction.POSITIVE, - ), - up=AxisDirectionPair( - axis=Axis.Y, - direction=Direction.POSITIVE, - ), - ), - split_closed_faces=False, - ) - ), + src_format=InputFormat3d(OptionGltf()), ), file_attachments={ "main.kcl": Path("path/to/main.kcl"), @@ -2330,8 +2301,8 @@ def test_create_org_saml_idp(): body=SamlIdentityProviderCreate( idp_entity_id="", idp_metadata_source=IdpMetadataSource( - OptionBase64EncodedXml( - data=Base64Data(b""), + OptionUrl( + url="", ) ), technical_contact_email="", @@ -2352,8 +2323,8 @@ async def test_create_org_saml_idp_async(): body=SamlIdentityProviderCreate( idp_entity_id="", idp_metadata_source=IdpMetadataSource( - OptionBase64EncodedXml( - data=Base64Data(b""), + OptionUrl( + url="", ) ), technical_contact_email="", @@ -5077,8 +5048,8 @@ def test_ml_copilot_ws(): # Send a message. websocket.send( MlCopilotClientMessage( - OptionHeaders( - headers={"": ""}, + OptionSystem( + command=MlCopilotSystemCommand.NEW, ) ) ) @@ -5114,7 +5085,13 @@ def test_ml_reasoning_ws(): # Connect to the websocket. with client.ml.ml_reasoning_ws(id="") as websocket: # Send a message. - websocket.send(MlCopilotClientMessage(OptionPing())) + websocket.send( + MlCopilotClientMessage( + OptionSystem( + command=MlCopilotSystemCommand.NEW, + ) + ) + ) # Get a message. message = websocket.recv() @@ -5160,8 +5137,13 @@ def test_modeling_commands_ws(): # Send a message. websocket.send( WebSocketRequest( - WebSocketRequestOptionHeaders( - headers={"": ""}, + OptionModelingCmdReq( + cmd=ModelingCmd( + OptionObjectBringToFront( + object_id="", + ) + ), + cmd_id=ModelingCmdId(""), ) ) ) diff --git a/spec.json b/spec.json index bb5195167..b7d4fb62c 100644 --- a/spec.json +++ b/spec.json @@ -40591,6 +40591,53 @@ "command", "type" ] + }, + { + "description": "Attachments returned by API in response to a backend `RequestAttachments` message.", + "type": "object", + "properties": { + "error": { + "nullable": true, + "description": "Error encountered while loading attachments, if any.", + "type": "string" + }, + "files": { + "description": "Loaded attachment files. Empty when no matching attachments were found.", + "type": "array", + "items": { + "$ref": "#/components/schemas/MlCopilotFile" + } + }, + "prompt_id": { + "nullable": true, + "description": "Prompt the attachments were loaded from.", + "allOf": [ + { + "$ref": "#/components/schemas/Uuid" + } + ] + }, + "request_id": { + "nullable": true, + "description": "Optional backend-provided identifier to correlate request/response pairs.", + "type": "string" + }, + "seq": { + "nullable": true, + "description": "Specific client message sequence, when requested.", + "type": "integer", + "format": "int32" + }, + "type": { + "type": "string", + "enum": [ + "attachment_response" + ] + } + }, + "required": [ + "type" + ] } ] }, @@ -40922,6 +40969,82 @@ ], "additionalProperties": false }, + { + "description": "Backend-only request for API to reload client attachments from storage.\n\nAPI handles this message internally and responds upstream with `MlCopilotClientMessage::AttachmentResponse`; it is not forwarded to the browser.", + "type": "object", + "properties": { + "request_attachments": { + "type": "object", + "properties": { + "conversation_id": { + "nullable": true, + "description": "Conversation to search for attachments. Used when prompt_id is not known.", + "allOf": [ + { + "$ref": "#/components/schemas/Uuid" + } + ] + }, + "names": { + "description": "Attachment names to load. Empty means load all attachments from the selected message(s).", + "type": "array", + "items": { + "type": "string" + } + }, + "only_metadata": { + "description": "If true, return attachment metadata without loading file contents.", + "default": false, + "type": "boolean" + }, + "prompt_id": { + "nullable": true, + "description": "Prompt to load attachments from. Defaults to the active/resumed prompt.", + "allOf": [ + { + "$ref": "#/components/schemas/Uuid" + } + ] + }, + "request_id": { + "nullable": true, + "description": "Optional backend-provided identifier to correlate request/response pairs.", + "type": "string" + }, + "seq": { + "nullable": true, + "description": "Specific client message sequence to inspect. Defaults to recent client messages.", + "type": "integer", + "format": "int32" + } + } + } + }, + "required": [ + "request_attachments" + ], + "additionalProperties": false + }, + { + "description": "Notification that API finished loading all attachments for the conversation.", + "type": "object", + "properties": { + "attachments_loaded": { + "type": "object", + "properties": { + "request_id": { + "nullable": true, + "description": "Optional backend-provided identifier to correlate request/response pairs.", + "type": "string" + } + } + } + }, + "required": [ + "attachments_loaded" + ], + "additionalProperties": false + }, { "description": "Replay containing raw bytes for previously-saved messages for a conversation. Includes server messages and client `User` messages.\n\nInvariants: - Includes server messages: `Info`, `Error`, `Reasoning(..)`, `ToolOutput { .. }`, `Files { .. }`, `ProjectUpdated { .. }`, and `EndOfStream { .. }`. - Also includes client `User` messages. - The following are NEVER included: `SessionData`, `ConversationId`, `Delta`, or `BackendShutdown`. - Ordering is stable: messages are ordered by prompt creation time within the conversation, then by the per-prompt `seq` value (monotonically increasing as seen in the original stream).\n\nWire format: - Each element is canonical serialized bytes (typically JSON) for either a `MlCopilotServerMessage` or a `MlCopilotClientMessage::User`. - When delivered as an initial replay over the websocket (upon `?replay=true&conversation_id=`), the server sends a single WebSocket Binary frame containing a MsgPack-encoded document of this enum: `Replay { messages }`.", "type": "object",