coreason-manifest 0.7.0__py3-none-any.whl → 0.10.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.
@@ -0,0 +1,47 @@
1
+ # Copyright (c) 2025 CoReason, Inc.
2
+ #
3
+ # This software is proprietary and dual-licensed.
4
+ # Licensed under the Prosperity Public License 3.0 (the "License").
5
+ # A copy of the license is available at https://prosperitylicense.com/versions/3.0.0
6
+ # For details, see the LICENSE file.
7
+ # Commercial use beyond a 30-day trial requires a separate license.
8
+ #
9
+ # Source Code: https://github.com/CoReason-AI/coreason-manifest
10
+
11
+ from typing import Any, Dict
12
+
13
+ from pydantic import BaseModel, ConfigDict
14
+
15
+
16
+ class CoReasonBaseModel(BaseModel):
17
+ """Base model for all CoReason Pydantic models with enhanced serialization.
18
+
19
+ This base class addresses JSON serialization challenges in Pydantic v2 (e.g., UUID, datetime)
20
+ by providing standardized methods (`dump`, `to_json`) with optimal configuration.
21
+
22
+ For a detailed rationale, see `docs/coreason_base_model_rationale.md`.
23
+ """
24
+
25
+ model_config = ConfigDict(populate_by_name=True)
26
+
27
+ def dump(self, **kwargs: Any) -> Dict[str, Any]:
28
+ """Serialize the model to a JSON-compatible dictionary.
29
+
30
+ Uses mode='json' to ensure types like UUID and datetime are serialized to strings.
31
+ Defaults to by_alias=True and exclude_none=True.
32
+ """
33
+ # Set defaults but allow overrides
34
+ kwargs.setdefault("mode", "json")
35
+ kwargs.setdefault("by_alias", True)
36
+ kwargs.setdefault("exclude_none", True)
37
+ return self.model_dump(**kwargs)
38
+
39
+ def to_json(self, **kwargs: Any) -> str:
40
+ """Serialize the model to a JSON string.
41
+
42
+ Defaults to by_alias=True and exclude_none=True.
43
+ """
44
+ # Set defaults but allow overrides
45
+ kwargs.setdefault("by_alias", True)
46
+ kwargs.setdefault("exclude_none", True)
47
+ return self.model_dump_json(**kwargs)
@@ -0,0 +1,423 @@
1
+ # Copyright (c) 2025 CoReason, Inc.
2
+ #
3
+ # This software is proprietary and dual-licensed.
4
+ # Licensed under the Prosperity Public License 3.0 (the "License").
5
+ # A copy of the license is available at https://prosperitylicense.com/versions/3.0.0
6
+ # For details, see the LICENSE file.
7
+ # Commercial use beyond a 30-day trial requires a separate license.
8
+ #
9
+ # Source Code: https://github.com/CoReason-AI/coreason-manifest
10
+
11
+ from datetime import datetime, timezone
12
+ from typing import Annotated, Any, Dict, Generic, Literal, Optional, Protocol, TypeVar, Union, runtime_checkable
13
+ from uuid import uuid4
14
+
15
+ from pydantic import ConfigDict, Field
16
+
17
+ from coreason_manifest.definitions.base import CoReasonBaseModel
18
+ from coreason_manifest.definitions.topology import RuntimeVisualMetadata
19
+
20
+ # --- CloudEvents v1.0 Implementation ---
21
+
22
+ T = TypeVar("T")
23
+
24
+
25
+ @runtime_checkable
26
+ class CloudEventSource(Protocol):
27
+ def as_cloud_event_payload(self) -> Any: ...
28
+
29
+
30
+ class CloudEvent(CoReasonBaseModel, Generic[T]):
31
+ """Standard CloudEvent v1.0 Envelope."""
32
+
33
+ model_config = ConfigDict(extra="allow", populate_by_name=True)
34
+
35
+ specversion: Literal["1.0"] = "1.0"
36
+ type: str = Field(..., description="Type of occurrence (e.g. ai.coreason.node.started)")
37
+ source: str = Field(..., description="URI identifying the event producer")
38
+ id: str = Field(default_factory=lambda: str(uuid4()), description="Unique event identifier")
39
+ time: datetime = Field(
40
+ default_factory=lambda: datetime.now(timezone.utc), description="Timestamp of when the occurrence happened"
41
+ )
42
+ datacontenttype: Literal["application/json"] = "application/json"
43
+
44
+ data: Optional[T] = Field(None, description="The event payload")
45
+
46
+ # Distributed Tracing Extensions (W3C)
47
+ traceparent: Optional[str] = Field(None, description="W3C Trace Context traceparent")
48
+ tracestate: Optional[str] = Field(None, description="W3C Trace Context tracestate")
49
+
50
+
51
+ # --- OTel Semantic Conventions ---
52
+
53
+
54
+ class GenAIUsage(CoReasonBaseModel):
55
+ """GenAI Usage metrics."""
56
+
57
+ input_tokens: Optional[int] = Field(None, alias="input_tokens")
58
+ output_tokens: Optional[int] = Field(None, alias="output_tokens")
59
+
60
+ model_config = ConfigDict(populate_by_name=True)
61
+
62
+
63
+ class GenAIRequest(CoReasonBaseModel):
64
+ """GenAI Request details."""
65
+
66
+ model: Optional[str] = None
67
+ temperature: Optional[float] = None
68
+ top_p: Optional[float] = None
69
+
70
+
71
+ class GenAICompletion(CoReasonBaseModel):
72
+ """GenAI Completion details."""
73
+
74
+ chunk: Optional[str] = None
75
+ finish_reason: Optional[str] = None
76
+
77
+
78
+ class GenAISemantics(CoReasonBaseModel):
79
+ """OpenTelemetry GenAI Semantic Conventions."""
80
+
81
+ system: Optional[str] = None
82
+ usage: Optional[GenAIUsage] = None
83
+ request: Optional[GenAIRequest] = None
84
+ completion: Optional[GenAICompletion] = None
85
+
86
+
87
+ # --- Base Models ---
88
+
89
+
90
+ class BaseNodePayload(CoReasonBaseModel):
91
+ """Base model for node-related events."""
92
+
93
+ model_config = ConfigDict(extra="ignore")
94
+ node_id: str
95
+
96
+ # Common OTel attributes that might appear in payload
97
+ model: Optional[str] = None
98
+ system: Optional[str] = None
99
+
100
+ def as_cloud_event_payload(self) -> Any:
101
+ return self
102
+
103
+
104
+ # --- Payload Models ---
105
+
106
+
107
+ class NodeInit(BaseNodePayload):
108
+ """Payload for NODE_INIT event."""
109
+
110
+ type: str = "DEFAULT"
111
+ visual_cue: str = "IDLE"
112
+
113
+
114
+ class NodeStarted(BaseNodePayload):
115
+ """Payload for NODE_START event."""
116
+
117
+ timestamp: float
118
+ status: Literal["RUNNING"] = "RUNNING"
119
+ visual_cue: str = "PULSE"
120
+ input_tokens: Optional[int] = None
121
+
122
+ def as_cloud_event_payload(self) -> Any:
123
+ semantics = GenAISemantics()
124
+ has_semantics = False
125
+
126
+ if self.input_tokens is not None:
127
+ semantics.usage = GenAIUsage(input_tokens=self.input_tokens)
128
+ has_semantics = True
129
+
130
+ if self.model:
131
+ semantics.request = GenAIRequest(model=self.model)
132
+ has_semantics = True
133
+
134
+ if self.system:
135
+ semantics.system = self.system
136
+ has_semantics = True
137
+
138
+ return StandardizedNodeStarted(
139
+ node_id=self.node_id,
140
+ status=self.status,
141
+ gen_ai=semantics if has_semantics else None,
142
+ )
143
+
144
+
145
+ class NodeCompleted(BaseNodePayload):
146
+ """Payload for NODE_DONE event."""
147
+
148
+ output_summary: str
149
+ status: Literal["SUCCESS"] = "SUCCESS"
150
+ visual_cue: str = "GREEN_GLOW"
151
+ cost: Optional[float] = None
152
+
153
+ def as_cloud_event_payload(self) -> Any:
154
+ semantics = GenAISemantics()
155
+ has_semantics = False
156
+
157
+ if self.model:
158
+ semantics.request = GenAIRequest(model=self.model)
159
+ has_semantics = True
160
+
161
+ return StandardizedNodeCompleted(
162
+ node_id=self.node_id,
163
+ output_summary=self.output_summary,
164
+ status=self.status,
165
+ gen_ai=semantics if has_semantics else None,
166
+ )
167
+
168
+
169
+ class NodeRestored(BaseNodePayload):
170
+ """Payload for NODE_RESTORED event."""
171
+
172
+ output_summary: str
173
+ status: Literal["RESTORED"] = "RESTORED"
174
+ visual_cue: str = "INSTANT_GREEN"
175
+
176
+
177
+ class NodeSkipped(BaseNodePayload):
178
+ """Payload for NODE_SKIPPED event."""
179
+
180
+ status: Literal["SKIPPED"] = "SKIPPED"
181
+ visual_cue: str = "GREY_OUT"
182
+
183
+
184
+ class NodeStream(BaseNodePayload):
185
+ """Payload for NODE_STREAM event."""
186
+
187
+ chunk: str
188
+ visual_cue: str = "TEXT_BUBBLE"
189
+
190
+ def as_cloud_event_payload(self) -> Any:
191
+ semantics = GenAISemantics(completion=GenAICompletion(chunk=self.chunk))
192
+ if self.model:
193
+ if not semantics.request:
194
+ semantics.request = GenAIRequest()
195
+ semantics.request.model = self.model
196
+
197
+ return StandardizedNodeStream(node_id=self.node_id, gen_ai=semantics)
198
+
199
+
200
+ class ArtifactGenerated(BaseNodePayload):
201
+ """Payload for ARTIFACT_GENERATED event."""
202
+
203
+ artifact_type: str = "PDF"
204
+ url: str
205
+
206
+
207
+ class EdgeTraversed(CoReasonBaseModel):
208
+ """Payload for EDGE_ACTIVE event."""
209
+
210
+ model_config = ConfigDict(extra="ignore")
211
+ source: str
212
+ target: str
213
+ animation_speed: str = "FAST"
214
+
215
+ def as_cloud_event_payload(self) -> Any:
216
+ return self
217
+
218
+
219
+ class CouncilVote(BaseNodePayload):
220
+ """Payload for COUNCIL_VOTE event."""
221
+
222
+ votes: Dict[str, str]
223
+
224
+
225
+ class WorkflowError(BaseNodePayload):
226
+ """Payload for ERROR event."""
227
+
228
+ error_message: str
229
+ stack_trace: str
230
+ input_snapshot: Dict[str, Any]
231
+ status: Literal["ERROR"] = "ERROR"
232
+ visual_cue: str = "RED_FLASH"
233
+
234
+
235
+ # --- Standardized Payloads ---
236
+
237
+
238
+ class StandardizedNodeStarted(BaseNodePayload):
239
+ """Standardized payload for node start with OTel support."""
240
+
241
+ gen_ai: Optional[GenAISemantics] = None
242
+ status: Literal["RUNNING"] = "RUNNING"
243
+
244
+
245
+ class StandardizedNodeCompleted(BaseNodePayload):
246
+ """Standardized payload for node completion with OTel support."""
247
+
248
+ gen_ai: Optional[GenAISemantics] = None
249
+ output_summary: str
250
+ status: Literal["SUCCESS"] = "SUCCESS"
251
+
252
+
253
+ class StandardizedNodeStream(BaseNodePayload):
254
+ """Standardized payload for node stream with OTel support."""
255
+
256
+ gen_ai: Optional[GenAISemantics] = None
257
+
258
+
259
+ # --- Aliases for Backward Compatibility ---
260
+ # These ensure that code importing `NodeInitPayload` still works.
261
+ NodeInitPayload = NodeInit
262
+ NodeStartedPayload = NodeStarted
263
+ NodeCompletedPayload = NodeCompleted
264
+ NodeSkippedPayload = NodeSkipped
265
+ NodeStreamPayload = NodeStream
266
+ EdgeTraversedPayload = EdgeTraversed
267
+ ArtifactGeneratedPayload = ArtifactGenerated
268
+ CouncilVotePayload = CouncilVote
269
+ WorkflowErrorPayload = WorkflowError
270
+
271
+
272
+ # --- Graph Event Wrapper ---
273
+
274
+
275
+ class BaseGraphEvent(CoReasonBaseModel):
276
+ """Base class for GraphEvents.
277
+
278
+ Standardized IDs:
279
+ - run_id: Workflow execution ID.
280
+ - trace_id: OpenTelemetry Distributed Trace ID.
281
+ """
282
+
283
+ model_config = ConfigDict(extra="forbid")
284
+
285
+ run_id: str
286
+ trace_id: str = Field(
287
+ default_factory=lambda: "unknown"
288
+ ) # Default for compatibility if missing in some legacy calls
289
+ node_id: str # Required per BRD and existing tests
290
+ timestamp: float
291
+ sequence_id: Optional[int] = None # Optional for internal use
292
+
293
+ # Visual Metadata drives the Flutter animation engine
294
+ visual_metadata: RuntimeVisualMetadata = Field(
295
+ ..., description="Hints for UI: color='#00FF00', animation='pulse', progress='0.5'"
296
+ )
297
+
298
+
299
+ class GraphEventNodeInit(BaseGraphEvent):
300
+ event_type: Literal["NODE_INIT"] = "NODE_INIT"
301
+ payload: NodeInit = Field(..., description="The logic output")
302
+
303
+
304
+ class GraphEventNodeStart(BaseGraphEvent):
305
+ event_type: Literal["NODE_START"] = "NODE_START"
306
+ payload: NodeStarted = Field(..., description="The logic output")
307
+
308
+
309
+ class GraphEventNodeStream(BaseGraphEvent):
310
+ event_type: Literal["NODE_STREAM"] = "NODE_STREAM"
311
+ payload: NodeStream = Field(..., description="The logic output")
312
+
313
+
314
+ class GraphEventNodeDone(BaseGraphEvent):
315
+ event_type: Literal["NODE_DONE"] = "NODE_DONE"
316
+ payload: NodeCompleted = Field(..., description="The logic output")
317
+
318
+
319
+ class GraphEventNodeSkipped(BaseGraphEvent):
320
+ event_type: Literal["NODE_SKIPPED"] = "NODE_SKIPPED"
321
+ payload: NodeSkipped = Field(..., description="The logic output")
322
+
323
+
324
+ class GraphEventEdgeActive(BaseGraphEvent):
325
+ event_type: Literal["EDGE_ACTIVE"] = "EDGE_ACTIVE"
326
+ payload: EdgeTraversed = Field(..., description="The logic output")
327
+
328
+
329
+ class GraphEventCouncilVote(BaseGraphEvent):
330
+ event_type: Literal["COUNCIL_VOTE"] = "COUNCIL_VOTE"
331
+ payload: CouncilVote = Field(..., description="The logic output")
332
+
333
+
334
+ class GraphEventError(BaseGraphEvent):
335
+ event_type: Literal["ERROR"] = "ERROR"
336
+ payload: WorkflowError = Field(..., description="The logic output")
337
+
338
+
339
+ class GraphEventNodeRestored(BaseGraphEvent):
340
+ event_type: Literal["NODE_RESTORED"] = "NODE_RESTORED"
341
+ payload: NodeRestored = Field(..., description="The logic output")
342
+
343
+
344
+ class GraphEventArtifactGenerated(BaseGraphEvent):
345
+ event_type: Literal["ARTIFACT_GENERATED"] = "ARTIFACT_GENERATED"
346
+ payload: ArtifactGenerated = Field(..., description="The logic output")
347
+
348
+
349
+ GraphEvent = Annotated[
350
+ Union[
351
+ GraphEventNodeInit,
352
+ GraphEventNodeStart,
353
+ GraphEventNodeStream,
354
+ GraphEventNodeDone,
355
+ GraphEventNodeSkipped,
356
+ GraphEventEdgeActive,
357
+ GraphEventCouncilVote,
358
+ GraphEventError,
359
+ GraphEventNodeRestored,
360
+ GraphEventArtifactGenerated,
361
+ ],
362
+ Field(discriminator="event_type", description="Polymorphic graph event definition."),
363
+ ]
364
+
365
+
366
+ # --- Migration Logic ---
367
+
368
+
369
+ def migrate_graph_event_to_cloud_event(event: GraphEvent) -> CloudEvent[Any]:
370
+ """Migrates a legacy GraphEvent to a CloudEvent v1.0."""
371
+
372
+ ce_type_map = {
373
+ "NODE_START": "ai.coreason.node.started",
374
+ "NODE_STREAM": "ai.coreason.node.stream",
375
+ "NODE_DONE": "ai.coreason.node.completed",
376
+ }
377
+ ce_type = ce_type_map.get(event.event_type, f"ai.coreason.legacy.{event.event_type.lower()}")
378
+ ce_source = f"urn:node:{event.node_id}"
379
+
380
+ data: Any = None
381
+
382
+ # event.payload is already a strictly typed Pydantic model (from GraphEvent union).
383
+ # All supported payload models implement CloudEventSource protocol (duck-typed or via BaseNodePayload).
384
+ if isinstance(event.payload, CloudEventSource):
385
+ data = event.payload.as_cloud_event_payload()
386
+ else:
387
+ # Fallback for models that might not implement the protocol (e.g. unknown future extensions)
388
+ data = event.payload
389
+
390
+ # UI Metadata as extension
391
+ payload_visual_cue = getattr(event.payload, "visual_cue", None)
392
+
393
+ visual_dict = event.visual_metadata.model_dump(exclude_none=True)
394
+ extensions = {
395
+ "com_coreason_ui_cue": event.visual_metadata.animation or payload_visual_cue,
396
+ "com_coreason_ui_metadata": visual_dict,
397
+ }
398
+
399
+ # Filter out None values in extensions
400
+ # For dictionaries, we want to filter out empty dicts too.
401
+ filtered_extensions = {}
402
+ for k, v in extensions.items():
403
+ if v is None:
404
+ continue
405
+ if isinstance(v, dict) and not v:
406
+ continue
407
+ if isinstance(v, str) and not v:
408
+ continue
409
+ # Also check if it's a dict containing only empty strings (recursive check not needed for now, just 1 level)
410
+ if isinstance(v, dict) and all(isinstance(val, str) and not val for val in v.values()):
411
+ continue
412
+
413
+ filtered_extensions[k] = v
414
+
415
+ extensions = filtered_extensions
416
+
417
+ return CloudEvent(
418
+ type=ce_type,
419
+ source=ce_source,
420
+ time=datetime.fromtimestamp(event.timestamp, timezone.utc),
421
+ data=data,
422
+ **extensions,
423
+ )
@@ -0,0 +1,188 @@
1
+ # Copyright (c) 2025 CoReason, Inc.
2
+ #
3
+ # This software is proprietary and dual-licensed.
4
+ # Licensed under the Prosperity Public License 3.0 (the "License").
5
+ # A copy of the license is available at https://prosperitylicense.com/versions/3.0.0
6
+ # For details, see the LICENSE file.
7
+ # Commercial use beyond a 30-day trial requires a separate license.
8
+ #
9
+ # Source Code: https://github.com/CoReason-AI/coreason-manifest
10
+
11
+ import functools
12
+ import json
13
+ import warnings
14
+ from enum import Enum
15
+ from typing import Annotated, Any, Dict, List, Literal, Optional, Union, cast
16
+
17
+ from pydantic import ConfigDict, Field, model_validator
18
+
19
+ from coreason_manifest.definitions.base import CoReasonBaseModel
20
+
21
+ # --- Enums ---
22
+
23
+
24
+ class Role(str, Enum):
25
+ SYSTEM = "system"
26
+ USER = "user"
27
+ ASSISTANT = "assistant"
28
+ TOOL = "tool"
29
+
30
+
31
+ class Modality(str, Enum):
32
+ TEXT = "text"
33
+ IMAGE = "image"
34
+ AUDIO = "audio"
35
+ VIDEO = "video"
36
+
37
+
38
+ # --- Message Parts ---
39
+
40
+
41
+ class TextPart(CoReasonBaseModel):
42
+ """Represents text content sent to or received from the model."""
43
+
44
+ model_config = ConfigDict(extra="ignore")
45
+ type: Literal["text"] = "text"
46
+ content: str
47
+
48
+
49
+ class BlobPart(CoReasonBaseModel):
50
+ """Represents blob binary data sent inline to the model."""
51
+
52
+ model_config = ConfigDict(extra="ignore")
53
+ type: Literal["blob"] = "blob"
54
+ content: str # Base64 encoded string
55
+ modality: Modality
56
+ mime_type: Optional[str] = None
57
+
58
+
59
+ class FilePart(CoReasonBaseModel):
60
+ """Represents an external referenced file sent to the model by file id."""
61
+
62
+ model_config = ConfigDict(extra="ignore")
63
+ type: Literal["file"] = "file"
64
+ file_id: str
65
+ modality: Modality
66
+ mime_type: Optional[str] = None
67
+
68
+
69
+ class UriPart(CoReasonBaseModel):
70
+ """Represents an external referenced file sent to the model by URI."""
71
+
72
+ model_config = ConfigDict(extra="ignore")
73
+ type: Literal["uri"] = "uri"
74
+ uri: str
75
+ modality: Modality
76
+ mime_type: Optional[str] = None
77
+
78
+
79
+ class ToolCallRequestPart(CoReasonBaseModel):
80
+ """Represents a tool call requested by the model."""
81
+
82
+ model_config = ConfigDict(extra="ignore")
83
+ type: Literal["tool_call"] = "tool_call"
84
+ name: str
85
+ arguments: Union[Dict[str, Any], str] # Structured arguments or JSON string
86
+ id: Optional[str] = None
87
+
88
+ @functools.cached_property
89
+ def parsed_arguments(self) -> Dict[str, Any]:
90
+ """Return arguments as a dictionary, parsing JSON if necessary."""
91
+ if isinstance(self.arguments, dict):
92
+ return self.arguments
93
+ try:
94
+ result = json.loads(self.arguments)
95
+ return cast(Dict[str, Any], result) if isinstance(result, dict) else {}
96
+ except (json.JSONDecodeError, TypeError):
97
+ return {}
98
+
99
+
100
+ class ToolCallResponsePart(CoReasonBaseModel):
101
+ """Represents a tool call result sent to the model."""
102
+
103
+ model_config = ConfigDict(extra="ignore")
104
+ type: Literal["tool_call_response"] = "tool_call_response"
105
+ response: Any # The result of the tool call
106
+ id: Optional[str] = None
107
+
108
+
109
+ class ReasoningPart(CoReasonBaseModel):
110
+ """Represents reasoning/thinking content received from the model."""
111
+
112
+ model_config = ConfigDict(extra="ignore")
113
+ type: Literal["reasoning"] = "reasoning"
114
+ content: str
115
+
116
+
117
+ # --- Union of All Parts ---
118
+
119
+ Part = Annotated[
120
+ Union[TextPart, BlobPart, FilePart, UriPart, ToolCallRequestPart, ToolCallResponsePart, ReasoningPart],
121
+ Field(discriminator="type"),
122
+ ]
123
+
124
+ # --- Main Message Model ---
125
+
126
+
127
+ class ChatMessage(CoReasonBaseModel):
128
+ """Represents a message in a conversation with an LLM."""
129
+
130
+ model_config = ConfigDict(extra="ignore")
131
+
132
+ role: Role
133
+ parts: List[Part] = Field(..., description="List of message parts that make up the message content.")
134
+ name: Optional[str] = None
135
+
136
+ @classmethod
137
+ def user(cls, content: str, name: Optional[str] = None) -> "ChatMessage":
138
+ """Factory method to create a user message with text content."""
139
+ return cls(role=Role.USER, parts=[TextPart(content=content)], name=name)
140
+
141
+ @classmethod
142
+ def assistant(cls, content: str, name: Optional[str] = None) -> "ChatMessage":
143
+ """Factory method to create an assistant message with text content."""
144
+ return cls(role=Role.ASSISTANT, parts=[TextPart(content=content)], name=name)
145
+
146
+ @classmethod
147
+ def tool(cls, tool_call_id: str, content: Any) -> "ChatMessage":
148
+ """Factory method to create a tool message with the result."""
149
+ return cls(role=Role.TOOL, parts=[ToolCallResponsePart(id=tool_call_id, response=content)])
150
+
151
+
152
+ # --- Backward Compatibility ---
153
+
154
+
155
+ class FunctionCall(CoReasonBaseModel):
156
+ """Deprecated: Use ToolCallRequestPart instead."""
157
+
158
+ name: str
159
+ arguments: str
160
+
161
+ @model_validator(mode="after")
162
+ def warn_deprecated(self) -> "FunctionCall":
163
+ warnings.warn(
164
+ "FunctionCall is deprecated. Use ToolCallRequestPart instead.",
165
+ DeprecationWarning,
166
+ stacklevel=2,
167
+ )
168
+ return self
169
+
170
+
171
+ class ToolCall(CoReasonBaseModel):
172
+ """Deprecated: Use ToolCallRequestPart instead."""
173
+
174
+ id: str
175
+ type: str = "function"
176
+ function: FunctionCall
177
+
178
+ @model_validator(mode="after")
179
+ def warn_deprecated(self) -> "ToolCall":
180
+ warnings.warn(
181
+ "ToolCall is deprecated. Use ToolCallRequestPart instead.",
182
+ DeprecationWarning,
183
+ stacklevel=2,
184
+ )
185
+ return self
186
+
187
+
188
+ Message = ChatMessage