asap-protocol 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
asap/models/parts.py ADDED
@@ -0,0 +1,207 @@
1
+ """Part models for ASAP protocol content types.
2
+
3
+ Parts are atomic content units used within messages and artifacts.
4
+ They support different content types including text, structured data,
5
+ files, MCP resources, and parameterized templates.
6
+ """
7
+
8
+ import base64
9
+ import re
10
+ from typing import Annotated, Any, Literal, Union
11
+
12
+ from pydantic import Discriminator, Field, TypeAdapter, field_validator
13
+
14
+ from asap.models.base import ASAPBaseModel
15
+ from asap.models.types import MIMEType
16
+
17
+
18
+ class TextPart(ASAPBaseModel):
19
+ """Plain text content part.
20
+
21
+ TextPart represents simple text content, such as user messages,
22
+ agent responses, or any textual information.
23
+
24
+ Attributes:
25
+ type: Discriminator field, always "text"
26
+ content: The text content
27
+
28
+ Example:
29
+ >>> part = TextPart(
30
+ ... type="text",
31
+ ... content="Research Q3 market trends for AI infrastructure"
32
+ ... )
33
+ """
34
+
35
+ type: Literal["text"] = Field(..., description="Part type discriminator")
36
+ content: str = Field(..., description="Text content")
37
+
38
+
39
+ class DataPart(ASAPBaseModel):
40
+ """Structured JSON data part.
41
+
42
+ DataPart represents structured data in JSON format, optionally
43
+ with a schema URI for validation.
44
+
45
+ Attributes:
46
+ type: Discriminator field, always "data"
47
+ data: Arbitrary JSON-serializable data
48
+ schema_uri: Optional URI to JSON Schema for validation
49
+
50
+ Example:
51
+ >>> part = DataPart(
52
+ ... type="data",
53
+ ... data={"query": "AI trends", "max_results": 10},
54
+ ... schema_uri="https://example.com/schemas/search.json"
55
+ ... )
56
+ """
57
+
58
+ type: Literal["data"] = Field(..., description="Part type discriminator")
59
+ data: dict[str, Any] = Field(..., description="Structured data (JSON-serializable)")
60
+ schema_uri: str | None = Field(
61
+ default=None, description="Optional JSON Schema URI for validation"
62
+ )
63
+
64
+
65
+ class FilePart(ASAPBaseModel):
66
+ """Binary or text file part.
67
+
68
+ FilePart represents file content, either by reference (URI) or
69
+ inline (base64-encoded data). Includes MIME type for proper handling.
70
+
71
+ Attributes:
72
+ type: Discriminator field, always "file"
73
+ uri: File URI (can be asap://, file://, https://, or data: URI)
74
+ mime_type: MIME type of the file (e.g., "application/pdf")
75
+ inline_data: Optional base64-encoded inline file data
76
+
77
+ Example:
78
+ >>> part = FilePart(
79
+ ... type="file",
80
+ ... uri="asap://artifacts/task_123/report.pdf",
81
+ ... mime_type="application/pdf"
82
+ ... )
83
+ >>>
84
+ >>> # With inline data
85
+ >>> part_inline = FilePart(
86
+ ... type="file",
87
+ ... uri="data:text/plain;base64,SGVsbG8gV29ybGQ=",
88
+ ... mime_type="text/plain",
89
+ ... inline_data="SGVsbG8gV29ybGQ="
90
+ ... )
91
+ """
92
+
93
+ type: Literal["file"] = Field(..., description="Part type discriminator")
94
+ uri: str = Field(..., description="File URI (asap://, file://, https://, data:)")
95
+ mime_type: MIMEType = Field(..., description="MIME type (e.g., application/pdf)")
96
+ inline_data: str | None = Field(
97
+ default=None, description="Optional base64-encoded inline file data"
98
+ )
99
+
100
+ @field_validator("mime_type")
101
+ @classmethod
102
+ def validate_mime_type(cls, v: str) -> str:
103
+ """Validate MIME type format (type/subtype)."""
104
+ # Pattern: type/subtype where both parts can contain alphanumeric, dots, plus, and hyphens
105
+ if not re.match(r"^[a-z0-9-]+/[a-z0-9.+\-]+$", v.lower()):
106
+ raise ValueError(f"Invalid MIME type format: {v}")
107
+ return v
108
+
109
+ @field_validator("inline_data")
110
+ @classmethod
111
+ def validate_base64(cls, v: str | None) -> str | None:
112
+ """Validate that inline_data is valid base64 when provided.
113
+
114
+ Args:
115
+ v: Base64 string or None
116
+
117
+ Returns:
118
+ Base64 string after validation
119
+
120
+ Raises:
121
+ ValueError: If inline_data is not valid base64
122
+ """
123
+ if v is not None:
124
+ try:
125
+ base64.b64decode(v, validate=True)
126
+ except Exception as e:
127
+ raise ValueError(f"inline_data must be valid base64: {e}") from e
128
+ return v
129
+
130
+
131
+ class ResourcePart(ASAPBaseModel):
132
+ """Reference to an MCP resource.
133
+
134
+ ResourcePart represents a reference to a resource provided by
135
+ an MCP (Model Context Protocol) server, enabling integration
136
+ with external tools and data sources.
137
+
138
+ Attributes:
139
+ type: Discriminator field, always "resource"
140
+ resource_uri: URI of the MCP resource
141
+
142
+ Example:
143
+ >>> part = ResourcePart(
144
+ ... type="resource",
145
+ ... resource_uri="mcp://tools.example.com/web/search_results"
146
+ ... )
147
+ """
148
+
149
+ type: Literal["resource"] = Field(..., description="Part type discriminator")
150
+ resource_uri: str = Field(..., description="MCP resource URI")
151
+
152
+
153
+ class TemplatePart(ASAPBaseModel):
154
+ """Parameterized prompt template.
155
+
156
+ TemplatePart represents a template string with variable placeholders
157
+ (using {{variable}} syntax) and their corresponding values.
158
+
159
+ Attributes:
160
+ type: Discriminator field, always "template"
161
+ template: Template string with {{variable}} placeholders
162
+ variables: Dictionary mapping variable names to their values
163
+
164
+ Example:
165
+ >>> part = TemplatePart(
166
+ ... type="template",
167
+ ... template="Research {{topic}} for {{timeframe}}",
168
+ ... variables={"topic": "AI trends", "timeframe": "Q3 2025"}
169
+ ... )
170
+ """
171
+
172
+ type: Literal["template"] = Field(..., description="Part type discriminator")
173
+ template: str = Field(..., description="Template string with {{variable}} syntax")
174
+ variables: dict[str, Any] = Field(..., description="Variable name to value mapping")
175
+
176
+
177
+ # Discriminated union type for type hints
178
+ PartType = Annotated[
179
+ Union[TextPart, DataPart, FilePart, ResourcePart, TemplatePart], Discriminator("type")
180
+ ]
181
+
182
+ # TypeAdapter for validation and deserialization
183
+ Part: TypeAdapter[PartType] = TypeAdapter(PartType)
184
+ """TypeAdapter for Part discriminated union.
185
+
186
+ Part is a TypeAdapter that can validate and deserialize any of the five
187
+ part types: TextPart, DataPart, FilePart, ResourcePart, or TemplatePart.
188
+
189
+ The 'type' field is used as a discriminator to automatically deserialize
190
+ JSON data into the correct Part subtype.
191
+
192
+ Example:
193
+ >>> from asap.models.parts import Part
194
+ >>>
195
+ >>> # Deserializes to TextPart
196
+ >>> text_part = Part.validate_python({"type": "text", "content": "Hello"})
197
+ >>>
198
+ >>> # Deserializes to DataPart
199
+ >>> data_part = Part.validate_python({"type": "data", "data": {"key": "value"}})
200
+ >>>
201
+ >>> # Deserializes to FilePart
202
+ >>> file_part = Part.validate_python({
203
+ ... "type": "file",
204
+ ... "uri": "file://test.pdf",
205
+ ... "mime_type": "application/pdf"
206
+ ... })
207
+ """
@@ -0,0 +1,423 @@
1
+ """Payload models for ASAP protocol messages.
2
+
3
+ Payloads define the content of protocol messages for different operations
4
+ like task requests, responses, updates, state management, and MCP integration.
5
+ """
6
+
7
+ from typing import Any, Union
8
+
9
+ from pydantic import Field, field_validator, model_validator
10
+
11
+ from asap.models.base import ASAPBaseModel
12
+ from asap.models.enums import MessageRole, TaskStatus, UpdateType
13
+ from asap.models.types import (
14
+ AgentURN,
15
+ ArtifactID,
16
+ ConversationID,
17
+ MessageID,
18
+ PartID,
19
+ SnapshotID,
20
+ TaskID,
21
+ )
22
+
23
+
24
+ class TaskRequest(ASAPBaseModel):
25
+ """Request to execute a task on an agent.
26
+
27
+ TaskRequest initiates task execution, specifying the skill to invoke,
28
+ input data, and optional configuration parameters.
29
+
30
+ Attributes:
31
+ conversation_id: ID of the conversation this task belongs to
32
+ parent_task_id: Optional ID of parent task (for subtasks)
33
+ skill_id: Identifier of the skill to execute
34
+ input: Input data for the skill (JSON-serializable)
35
+ config: Optional configuration (timeout, priority, streaming, etc.)
36
+
37
+ Example:
38
+ >>> request = TaskRequest(
39
+ ... conversation_id="conv_01HX5K3MQVN8",
40
+ ... skill_id="web_research",
41
+ ... input={"query": "AI infrastructure market trends Q3 2025"},
42
+ ... config={"timeout_seconds": 600, "streaming": True}
43
+ ... )
44
+ """
45
+
46
+ conversation_id: ConversationID = Field(..., description="Parent conversation ID")
47
+ parent_task_id: TaskID | None = Field(default=None, description="Parent task ID for subtasks")
48
+ skill_id: str = Field(..., description="Skill identifier to execute")
49
+ input: dict[str, Any] = Field(..., description="Input data for the skill")
50
+ config: dict[str, Any] | None = Field(
51
+ default=None, description="Optional configuration (timeout, priority, streaming, etc.)"
52
+ )
53
+
54
+
55
+ class TaskResponse(ASAPBaseModel):
56
+ """Response to a task execution request.
57
+
58
+ TaskResponse provides the final result of task execution, including
59
+ status, result data, final state snapshot, and execution metrics.
60
+
61
+ Attributes:
62
+ task_id: ID of the completed task
63
+ status: Final task status (completed, failed, cancelled, etc.)
64
+ result: Optional result data (summary, artifacts, etc.)
65
+ final_state: Optional final state snapshot
66
+ metrics: Optional execution metrics (duration, tokens used, etc.)
67
+
68
+ Example:
69
+ >>> response = TaskResponse(
70
+ ... task_id="task_01HX5K4N",
71
+ ... status="completed",
72
+ ... result={"summary": "Analysis complete", "artifacts": ["art_123"]},
73
+ ... metrics={"duration_ms": 45000, "tokens_used": 12500}
74
+ ... )
75
+ """
76
+
77
+ task_id: TaskID = Field(..., description="Task identifier")
78
+ status: TaskStatus = Field(..., description="Final task status")
79
+ result: dict[str, Any] | None = Field(
80
+ default=None, description="Result data (summary, artifacts, etc.)"
81
+ )
82
+ final_state: dict[str, Any] | None = Field(default=None, description="Final state snapshot")
83
+ metrics: dict[str, Any] | None = Field(
84
+ default=None, description="Execution metrics (duration, tokens, etc.)"
85
+ )
86
+
87
+
88
+ class TaskUpdate(ASAPBaseModel):
89
+ """Update on task execution progress or status.
90
+
91
+ TaskUpdate provides real-time updates during task execution, including
92
+ progress information or requests for additional input.
93
+
94
+ Attributes:
95
+ task_id: ID of the task being updated
96
+ update_type: Type of update (progress, input_required, etc.)
97
+ status: Current task status
98
+ progress: Optional progress information (percent, message, ETA)
99
+ input_request: Optional request for additional input from user
100
+
101
+ Example:
102
+ >>> # Progress update
103
+ >>> update = TaskUpdate(
104
+ ... task_id="task_123",
105
+ ... update_type="progress",
106
+ ... status="working",
107
+ ... progress={"percent": 65, "message": "Synthesizing findings..."}
108
+ ... )
109
+ >>>
110
+ >>> # Input required update
111
+ >>> update = TaskUpdate(
112
+ ... task_id="task_123",
113
+ ... update_type="input_required",
114
+ ... status="input_required",
115
+ ... input_request={"prompt": "Please clarify:", "options": [...]}
116
+ ... )
117
+ """
118
+
119
+ task_id: TaskID = Field(..., description="Task identifier")
120
+ update_type: UpdateType = Field(..., description="Update type (progress, input_required)")
121
+ status: TaskStatus = Field(..., description="Current task status")
122
+ progress: dict[str, Any] | None = Field(
123
+ default=None, description="Progress info (percent, message, ETA)"
124
+ )
125
+ input_request: dict[str, Any] | None = Field(
126
+ default=None, description="Request for additional input"
127
+ )
128
+
129
+ @field_validator("progress")
130
+ @classmethod
131
+ def validate_progress_percent(cls, v: dict[str, Any] | None) -> dict[str, Any] | None:
132
+ """Validate that progress percent is between 0 and 100 if provided.
133
+
134
+ Args:
135
+ v: Progress dictionary or None
136
+
137
+ Returns:
138
+ Progress dictionary after validation
139
+
140
+ Raises:
141
+ ValueError: If percent is not between 0 and 100
142
+ """
143
+ if v and "percent" in v:
144
+ percent = v["percent"]
145
+ if not isinstance(percent, (int, float)):
146
+ raise ValueError("progress.percent must be a number")
147
+ if not (0 <= percent <= 100):
148
+ raise ValueError("progress.percent must be between 0 and 100")
149
+ return v
150
+
151
+
152
+ class TaskCancel(ASAPBaseModel):
153
+ """Request to cancel a running task.
154
+
155
+ TaskCancel requests cancellation of a task that is currently executing.
156
+ The agent should attempt graceful cancellation and cleanup.
157
+
158
+ Attributes:
159
+ task_id: ID of the task to cancel
160
+ reason: Optional reason for cancellation
161
+
162
+ Example:
163
+ >>> cancel = TaskCancel(
164
+ ... task_id="task_123",
165
+ ... reason="User requested cancellation"
166
+ ... )
167
+ """
168
+
169
+ task_id: TaskID = Field(..., description="Task identifier to cancel")
170
+ reason: str | None = Field(default=None, description="Optional cancellation reason")
171
+
172
+
173
+ class MessageSend(ASAPBaseModel):
174
+ """Send a message within a task conversation.
175
+
176
+ MessageSend exchanges conversation turns between agents during
177
+ task execution, containing message content as parts.
178
+
179
+ Attributes:
180
+ task_id: ID of the task this message belongs to
181
+ message_id: Unique identifier for this message
182
+ sender: Agent URN of the message sender
183
+ role: Message role (user, assistant, system)
184
+ parts: List of part IDs that make up this message
185
+
186
+ Example:
187
+ >>> message = MessageSend(
188
+ ... task_id="task_123",
189
+ ... message_id="msg_456",
190
+ ... sender="urn:asap:agent:coordinator",
191
+ ... role="user",
192
+ ... parts=["part_789"]
193
+ ... )
194
+ """
195
+
196
+ task_id: TaskID = Field(..., description="Parent task ID")
197
+ message_id: MessageID = Field(..., description="Unique message identifier")
198
+ sender: AgentURN = Field(..., description="Sender agent URN")
199
+ role: MessageRole = Field(..., description="Message role (user, assistant, system)")
200
+ parts: list[PartID] = Field(..., description="Part IDs making up this message")
201
+
202
+
203
+ class StateQuery(ASAPBaseModel):
204
+ """Request a state snapshot for a task.
205
+
206
+ StateQuery requests the current or a specific version of a task's
207
+ state snapshot for inspection or restoration.
208
+
209
+ Attributes:
210
+ task_id: ID of the task to query state for
211
+ version: Optional specific version number to retrieve
212
+
213
+ Example:
214
+ >>> # Query latest state
215
+ >>> query = StateQuery(task_id="task_123")
216
+ >>>
217
+ >>> # Query specific version
218
+ >>> query = StateQuery(task_id="task_123", version=5)
219
+ """
220
+
221
+ task_id: TaskID = Field(..., description="Task identifier")
222
+ version: int | None = Field(default=None, description="Optional specific version to retrieve")
223
+
224
+
225
+ class StateRestore(ASAPBaseModel):
226
+ """Restore a task to a previous state snapshot.
227
+
228
+ StateRestore requests restoration of a task to a previously saved
229
+ state snapshot, enabling rollback and recovery scenarios.
230
+
231
+ Attributes:
232
+ task_id: ID of the task to restore
233
+ snapshot_id: ID of the snapshot to restore from
234
+
235
+ Example:
236
+ >>> restore = StateRestore(
237
+ ... task_id="task_123",
238
+ ... snapshot_id="snap_456"
239
+ ... )
240
+ """
241
+
242
+ task_id: TaskID = Field(..., description="Task identifier")
243
+ snapshot_id: SnapshotID = Field(..., description="Snapshot ID to restore from")
244
+
245
+
246
+ class ArtifactNotify(ASAPBaseModel):
247
+ """Notify about artifact creation or availability.
248
+
249
+ ArtifactNotify informs agents when a new artifact has been created
250
+ or is available for retrieval.
251
+
252
+ Attributes:
253
+ artifact_id: ID of the artifact
254
+ task_id: ID of the task that produced the artifact
255
+ name: Optional human-readable artifact name
256
+
257
+ Example:
258
+ >>> notify = ArtifactNotify(
259
+ ... artifact_id="art_123",
260
+ ... task_id="task_456",
261
+ ... name="Q3 Market Analysis Report"
262
+ ... )
263
+ """
264
+
265
+ artifact_id: ArtifactID = Field(..., description="Artifact identifier")
266
+ task_id: TaskID = Field(..., description="Parent task ID")
267
+ name: str | None = Field(default=None, description="Optional human-readable artifact name")
268
+
269
+
270
+ class McpToolCall(ASAPBaseModel):
271
+ """Call an MCP tool.
272
+
273
+ McpToolCall invokes a tool provided by an MCP server, enabling
274
+ agents to leverage external capabilities and integrations.
275
+
276
+ Attributes:
277
+ request_id: Unique identifier for this tool call request
278
+ tool_name: Name of the MCP tool to invoke
279
+ arguments: Arguments to pass to the tool (JSON-serializable)
280
+ mcp_context: Optional MCP-specific context (server, session, etc.)
281
+
282
+ Example:
283
+ >>> tool_call = McpToolCall(
284
+ ... request_id="req_123",
285
+ ... tool_name="web_search",
286
+ ... arguments={"query": "AI trends", "max_results": 10},
287
+ ... mcp_context={"server": "mcp://tools.example.com"}
288
+ ... )
289
+ """
290
+
291
+ request_id: str = Field(..., description="Unique request identifier")
292
+ tool_name: str = Field(..., description="MCP tool name to invoke")
293
+ arguments: dict[str, Any] = Field(..., description="Tool arguments (JSON-serializable)")
294
+ mcp_context: dict[str, Any] | None = Field(
295
+ default=None, description="Optional MCP context (server, session, etc.)"
296
+ )
297
+
298
+
299
+ class McpToolResult(ASAPBaseModel):
300
+ """Result of an MCP tool call.
301
+
302
+ McpToolResult provides the outcome of an MCP tool invocation,
303
+ including success status, result data, or error information.
304
+
305
+ Attributes:
306
+ request_id: ID of the original tool call request
307
+ success: Whether the tool call succeeded
308
+ result: Optional result data (if successful)
309
+ error: Optional error message (if failed)
310
+
311
+ Example:
312
+ >>> # Successful result
313
+ >>> result = McpToolResult(
314
+ ... request_id="req_123",
315
+ ... success=True,
316
+ ... result={"findings": ["finding1", "finding2"]}
317
+ ... )
318
+ >>>
319
+ >>> # Failed result
320
+ >>> result = McpToolResult(
321
+ ... request_id="req_123",
322
+ ... success=False,
323
+ ... error="Tool execution failed: timeout"
324
+ ... )
325
+ """
326
+
327
+ request_id: str = Field(..., description="Original request identifier")
328
+ success: bool = Field(..., description="Whether tool call succeeded")
329
+ result: dict[str, Any] | None = Field(default=None, description="Result data (if successful)")
330
+ error: str | None = Field(default=None, description="Error message (if failed)")
331
+
332
+ @model_validator(mode="after")
333
+ def validate_result_error_exclusivity(self) -> "McpToolResult":
334
+ """Validate that result and error are mutually exclusive based on success.
335
+
336
+ When success=True, result must be provided and error must be None.
337
+ When success=False, error must be provided and result must be None.
338
+
339
+ Returns:
340
+ Self after validation
341
+
342
+ Raises:
343
+ ValueError: If result/error are not mutually exclusive based on success
344
+ """
345
+ if self.success:
346
+ if self.result is None:
347
+ raise ValueError("result must be provided when success=True")
348
+ if self.error is not None:
349
+ raise ValueError("error must be None when success=True")
350
+ else:
351
+ if self.error is None:
352
+ raise ValueError("error must be provided when success=False")
353
+ if self.result is not None:
354
+ raise ValueError("result must be None when success=False")
355
+ return self
356
+
357
+
358
+ class McpResourceFetch(ASAPBaseModel):
359
+ """Request to fetch an MCP resource.
360
+
361
+ McpResourceFetch requests retrieval of a resource from an MCP server,
362
+ such as documentation, data, or other content.
363
+
364
+ Attributes:
365
+ resource_uri: URI of the MCP resource to fetch
366
+
367
+ Example:
368
+ >>> fetch = McpResourceFetch(
369
+ ... resource_uri="mcp://server/resources/data_123"
370
+ ... )
371
+ """
372
+
373
+ resource_uri: str = Field(..., description="MCP resource URI to fetch")
374
+
375
+
376
+ class McpResourceData(ASAPBaseModel):
377
+ """Data from an MCP resource.
378
+
379
+ McpResourceData provides the content of a fetched MCP resource.
380
+
381
+ Attributes:
382
+ resource_uri: URI of the resource
383
+ content: Resource content (JSON-serializable)
384
+
385
+ Example:
386
+ >>> data = McpResourceData(
387
+ ... resource_uri="mcp://server/resources/data_123",
388
+ ... content={"data": [1, 2, 3], "metadata": {"source": "api"}}
389
+ ... )
390
+ """
391
+
392
+ resource_uri: str = Field(..., description="MCP resource URI")
393
+ content: dict[str, Any] = Field(..., description="Resource content (JSON-serializable)")
394
+
395
+
396
+ # Union type for all payload types
397
+ # Note: The discriminator (payload_type) will be in the Envelope, not in individual payloads
398
+ PayloadType = Union[
399
+ TaskRequest,
400
+ TaskResponse,
401
+ TaskUpdate,
402
+ TaskCancel,
403
+ MessageSend,
404
+ StateQuery,
405
+ StateRestore,
406
+ ArtifactNotify,
407
+ McpToolCall,
408
+ McpToolResult,
409
+ McpResourceFetch,
410
+ McpResourceData,
411
+ ]
412
+ """Union type of all ASAP payload types.
413
+
414
+ PayloadType represents any of the 12 payload types used in ASAP protocol messages.
415
+ The actual payload type discrimination is done via the 'payload_type' field in the
416
+ Envelope that wraps these payloads.
417
+
418
+ Payload types:
419
+ - Task operations: TaskRequest, TaskResponse, TaskUpdate, TaskCancel
420
+ - Messaging: MessageSend
421
+ - State management: StateQuery, StateRestore, ArtifactNotify
422
+ - MCP integration: McpToolCall, McpToolResult, McpResourceFetch, McpResourceData
423
+ """
asap/models/types.py ADDED
@@ -0,0 +1,39 @@
1
+ """Type aliases for ASAP protocol.
2
+
3
+ This module defines type aliases to improve code readability and
4
+ document the semantic meaning of string types.
5
+ """
6
+
7
+ from typing import TypeAlias
8
+
9
+ # Entity identifiers
10
+ AgentURN: TypeAlias = str
11
+ """Agent identifier in URN format: urn:asap:agent:{name}"""
12
+
13
+ ConversationID: TypeAlias = str
14
+ """Unique conversation identifier (ULID format)"""
15
+
16
+ TaskID: TypeAlias = str
17
+ """Unique task identifier (ULID format)"""
18
+
19
+ MessageID: TypeAlias = str
20
+ """Unique message identifier (ULID format)"""
21
+
22
+ ArtifactID: TypeAlias = str
23
+ """Unique artifact identifier (ULID format)"""
24
+
25
+ SnapshotID: TypeAlias = str
26
+ """Unique state snapshot identifier (ULID format)"""
27
+
28
+ PartID: TypeAlias = str
29
+ """Unique part identifier (ULID format)"""
30
+
31
+ # Other semantic types
32
+ URI: TypeAlias = str
33
+ """Uniform Resource Identifier"""
34
+
35
+ MIMEType: TypeAlias = str
36
+ """MIME type string (e.g., 'application/json')"""
37
+
38
+ SemanticVersion: TypeAlias = str
39
+ """Semantic version string (e.g., '1.0.0')"""