anthropic 0.76.0__py3-none-any.whl → 0.77.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.
Files changed (51) hide show
  1. anthropic/_base_client.py +5 -2
  2. anthropic/_compat.py +3 -3
  3. anthropic/_utils/_json.py +35 -0
  4. anthropic/_version.py +1 -1
  5. anthropic/lib/_parse/_response.py +29 -1
  6. anthropic/lib/streaming/__init__.py +3 -0
  7. anthropic/lib/streaming/_messages.py +74 -40
  8. anthropic/lib/streaming/_types.py +42 -2
  9. anthropic/resources/beta/messages/messages.py +170 -59
  10. anthropic/resources/messages/messages.py +407 -5
  11. anthropic/types/__init__.py +7 -0
  12. anthropic/types/beta/beta_code_execution_tool_20250522_param.py +1 -0
  13. anthropic/types/beta/beta_code_execution_tool_20250825_param.py +1 -0
  14. anthropic/types/beta/beta_memory_tool_20250818_param.py +1 -0
  15. anthropic/types/beta/beta_output_config_param.py +15 -1
  16. anthropic/types/beta/beta_server_tool_use_block.py +4 -4
  17. anthropic/types/beta/beta_tool_bash_20241022_param.py +1 -0
  18. anthropic/types/beta/beta_tool_bash_20250124_param.py +1 -0
  19. anthropic/types/beta/beta_tool_computer_use_20241022_param.py +1 -0
  20. anthropic/types/beta/beta_tool_computer_use_20250124_param.py +1 -0
  21. anthropic/types/beta/beta_tool_computer_use_20251124_param.py +1 -0
  22. anthropic/types/beta/beta_tool_param.py +1 -0
  23. anthropic/types/beta/beta_tool_search_tool_bm25_20251119_param.py +1 -0
  24. anthropic/types/beta/beta_tool_search_tool_regex_20251119_param.py +1 -0
  25. anthropic/types/beta/beta_tool_text_editor_20241022_param.py +1 -0
  26. anthropic/types/beta/beta_tool_text_editor_20250124_param.py +1 -0
  27. anthropic/types/beta/beta_tool_text_editor_20250429_param.py +1 -0
  28. anthropic/types/beta/beta_tool_text_editor_20250728_param.py +1 -0
  29. anthropic/types/beta/beta_web_fetch_tool_20250910_param.py +1 -0
  30. anthropic/types/beta/beta_web_search_tool_20250305_param.py +1 -0
  31. anthropic/types/beta/beta_web_search_tool_result_error_code.py +1 -1
  32. anthropic/types/beta/message_count_tokens_params.py +9 -5
  33. anthropic/types/beta/message_create_params.py +9 -5
  34. anthropic/types/beta/messages/batch_create_params.py +2 -9
  35. anthropic/types/json_output_format_param.py +15 -0
  36. anthropic/types/message_count_tokens_params.py +4 -0
  37. anthropic/types/message_create_params.py +4 -0
  38. anthropic/types/output_config_param.py +19 -0
  39. anthropic/types/parsed_message.py +56 -0
  40. anthropic/types/tool_bash_20250124_param.py +3 -0
  41. anthropic/types/tool_param.py +3 -0
  42. anthropic/types/tool_text_editor_20250124_param.py +3 -0
  43. anthropic/types/tool_text_editor_20250429_param.py +3 -0
  44. anthropic/types/tool_text_editor_20250728_param.py +3 -0
  45. anthropic/types/web_search_tool_20250305_param.py +3 -0
  46. anthropic/types/web_search_tool_request_error_param.py +8 -1
  47. anthropic/types/web_search_tool_result_error.py +8 -1
  48. {anthropic-0.76.0.dist-info → anthropic-0.77.0.dist-info}/METADATA +1 -1
  49. {anthropic-0.76.0.dist-info → anthropic-0.77.0.dist-info}/RECORD +51 -47
  50. {anthropic-0.76.0.dist-info → anthropic-0.77.0.dist-info}/WHEEL +0 -0
  51. {anthropic-0.76.0.dist-info → anthropic-0.77.0.dist-info}/licenses/LICENSE +0 -0
anthropic/_base_client.py CHANGED
@@ -89,6 +89,7 @@ from ._exceptions import (
89
89
  APIConnectionError,
90
90
  APIResponseValidationError,
91
91
  )
92
+ from ._utils._json import openapi_dumps
92
93
  from ._utils._httpx import get_environment_proxies
93
94
  from ._legacy_response import LegacyAPIResponse
94
95
 
@@ -567,8 +568,10 @@ class BaseClient(Generic[_HttpxClientT, _DefaultStreamT]):
567
568
  kwargs["content"] = options.content
568
569
  elif isinstance(json_data, bytes):
569
570
  kwargs["content"] = json_data
570
- else:
571
- kwargs["json"] = json_data if is_given(json_data) else None
571
+ elif not files:
572
+ # Don't set content when JSON is sent as multipart/form-data,
573
+ # since httpx's content param overrides other body arguments
574
+ kwargs["content"] = openapi_dumps(json_data) if is_given(json_data) and json_data is not None else None
572
575
  kwargs["files"] = files
573
576
  else:
574
577
  headers.pop("Content-Type", None)
anthropic/_compat.py CHANGED
@@ -145,6 +145,7 @@ def model_dump(
145
145
  exclude_defaults: bool = False,
146
146
  warnings: bool = True,
147
147
  mode: Literal["json", "python"] = "python",
148
+ by_alias: bool | None = None,
148
149
  ) -> dict[str, Any]:
149
150
  if (not PYDANTIC_V1) or hasattr(model, "model_dump"):
150
151
  return model.model_dump(
@@ -154,13 +155,12 @@ def model_dump(
154
155
  exclude_defaults=exclude_defaults,
155
156
  # warnings are not supported in Pydantic v1
156
157
  warnings=True if PYDANTIC_V1 else warnings,
158
+ by_alias=by_alias,
157
159
  )
158
160
  return cast(
159
161
  "dict[str, Any]",
160
162
  model.dict( # pyright: ignore[reportDeprecated, reportUnnecessaryCast]
161
- exclude=exclude,
162
- exclude_unset=exclude_unset,
163
- exclude_defaults=exclude_defaults,
163
+ exclude=exclude, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, by_alias=bool(by_alias)
164
164
  ),
165
165
  )
166
166
 
@@ -0,0 +1,35 @@
1
+ import json
2
+ from typing import Any
3
+ from datetime import datetime
4
+ from typing_extensions import override
5
+
6
+ import pydantic
7
+
8
+ from .._compat import model_dump
9
+
10
+
11
+ def openapi_dumps(obj: Any) -> bytes:
12
+ """
13
+ Serialize an object to UTF-8 encoded JSON bytes.
14
+
15
+ Extends the standard json.dumps with support for additional types
16
+ commonly used in the SDK, such as `datetime`, `pydantic.BaseModel`, etc.
17
+ """
18
+ return json.dumps(
19
+ obj,
20
+ cls=_CustomEncoder,
21
+ # Uses the same defaults as httpx's JSON serialization
22
+ ensure_ascii=False,
23
+ separators=(",", ":"),
24
+ allow_nan=False,
25
+ ).encode()
26
+
27
+
28
+ class _CustomEncoder(json.JSONEncoder):
29
+ @override
30
+ def default(self, o: Any) -> Any:
31
+ if isinstance(o, datetime):
32
+ return o.isoformat()
33
+ if isinstance(o, pydantic.BaseModel):
34
+ return model_dump(o, exclude_unset=True, mode="json", by_alias=True)
35
+ return super().default(o)
anthropic/_version.py CHANGED
@@ -1,4 +1,4 @@
1
1
  # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
2
2
 
3
3
  __title__ = "anthropic"
4
- __version__ = "0.76.0" # x-release-please-version
4
+ __version__ = "0.77.0" # x-release-please-version
@@ -5,6 +5,8 @@ from typing_extensions import TypeVar
5
5
  from ..._types import NotGiven
6
6
  from ..._models import TypeAdapter, construct_type_unchecked
7
7
  from ..._utils._utils import is_given
8
+ from ...types.message import Message
9
+ from ...types.parsed_message import ParsedMessage, ParsedTextBlock, ParsedContentBlock
8
10
  from ...types.beta.beta_message import BetaMessage
9
11
  from ...types.beta.parsed_beta_message import ParsedBetaMessage, ParsedBetaTextBlock, ParsedBetaContentBlock
10
12
 
@@ -18,7 +20,7 @@ def parse_text(text: str, output_format: ResponseFormatT | NotGiven) -> Response
18
20
  return None
19
21
 
20
22
 
21
- def parse_response(
23
+ def parse_beta_response(
22
24
  *,
23
25
  output_format: ResponseFormatT | NotGiven,
24
26
  response: BetaMessage,
@@ -42,3 +44,29 @@ def parse_response(
42
44
  "content": content_list,
43
45
  },
44
46
  )
47
+
48
+
49
+ def parse_response(
50
+ *,
51
+ output_format: ResponseFormatT | NotGiven,
52
+ response: Message,
53
+ ) -> ParsedMessage[ResponseFormatT]:
54
+ content_list: list[ParsedContentBlock[ResponseFormatT]] = []
55
+ for content in response.content:
56
+ if content.type == "text":
57
+ content_list.append(
58
+ construct_type_unchecked(
59
+ type_=ParsedTextBlock[ResponseFormatT],
60
+ value={**content.to_dict(), "parsed_output": parse_text(content.text, output_format)},
61
+ )
62
+ )
63
+ else:
64
+ content_list.append(content) # type: ignore
65
+
66
+ return construct_type_unchecked(
67
+ type_=ParsedMessage[ResponseFormatT],
68
+ value={
69
+ **response.to_dict(),
70
+ "content": content_list,
71
+ },
72
+ )
@@ -6,6 +6,9 @@ from ._types import (
6
6
  MessageStopEvent as MessageStopEvent,
7
7
  MessageStreamEvent as MessageStreamEvent,
8
8
  ContentBlockStopEvent as ContentBlockStopEvent,
9
+ ParsedMessageStopEvent as ParsedMessageStopEvent,
10
+ ParsedMessageStreamEvent as ParsedMessageStreamEvent,
11
+ ParsedContentBlockStopEvent as ParsedContentBlockStopEvent,
9
12
  )
10
13
  from ._messages import (
11
14
  MessageStream as MessageStream,
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from types import TracebackType
4
- from typing import TYPE_CHECKING, Any, Type, Callable, cast
4
+ from typing import TYPE_CHECKING, Any, Type, Generic, Callable, cast
5
5
  from typing_extensions import Self, Iterator, Awaitable, AsyncIterator, assert_never
6
6
 
7
7
  import httpx
@@ -16,17 +16,21 @@ from ._types import (
16
16
  ThinkingEvent,
17
17
  InputJsonEvent,
18
18
  SignatureEvent,
19
- MessageStopEvent,
20
- MessageStreamEvent,
21
- ContentBlockStopEvent,
19
+ ParsedMessageStopEvent,
20
+ ParsedMessageStreamEvent,
21
+ ParsedContentBlockStopEvent,
22
22
  )
23
- from ...types import Message, ContentBlock, RawMessageStreamEvent
23
+ from ...types import RawMessageStreamEvent
24
+ from ..._types import NOT_GIVEN, NotGiven
24
25
  from ..._utils import consume_sync_iterator, consume_async_iterator
25
26
  from ..._models import build, construct_type, construct_type_unchecked
26
27
  from ..._streaming import Stream, AsyncStream
28
+ from ..._utils._utils import is_given
29
+ from .._parse._response import ResponseFormatT, parse_text
30
+ from ...types.parsed_message import ParsedMessage, ParsedContentBlock
27
31
 
28
32
 
29
- class MessageStream:
33
+ class MessageStream(Generic[ResponseFormatT]):
30
34
  text_stream: Iterator[str]
31
35
  """Iterator over just the text deltas in the stream.
32
36
 
@@ -37,11 +41,16 @@ class MessageStream:
37
41
  ```
38
42
  """
39
43
 
40
- def __init__(self, raw_stream: Stream[RawMessageStreamEvent]) -> None:
44
+ def __init__(
45
+ self,
46
+ raw_stream: Stream[RawMessageStreamEvent],
47
+ output_format: ResponseFormatT | NotGiven,
48
+ ) -> None:
41
49
  self._raw_stream = raw_stream
42
50
  self.text_stream = self.__stream_text__()
43
51
  self._iterator = self.__stream__()
44
- self.__final_message_snapshot: Message | None = None
52
+ self.__final_message_snapshot: ParsedMessage[ResponseFormatT] | None = None
53
+ self.__output_format = output_format
45
54
 
46
55
  @property
47
56
  def response(self) -> httpx.Response:
@@ -51,10 +60,10 @@ class MessageStream:
51
60
  def request_id(self) -> str | None:
52
61
  return self.response.headers.get("request-id") # type: ignore[no-any-return]
53
62
 
54
- def __next__(self) -> MessageStreamEvent:
63
+ def __next__(self) -> ParsedMessageStreamEvent[ResponseFormatT]:
55
64
  return self._iterator.__next__()
56
65
 
57
- def __iter__(self) -> Iterator[MessageStreamEvent]:
66
+ def __iter__(self) -> Iterator[ParsedMessageStreamEvent[ResponseFormatT]]:
58
67
  for item in self._iterator:
59
68
  yield item
60
69
 
@@ -77,7 +86,7 @@ class MessageStream:
77
86
  """
78
87
  self._raw_stream.close()
79
88
 
80
- def get_final_message(self) -> Message:
89
+ def get_final_message(self) -> ParsedMessage[ResponseFormatT]:
81
90
  """Waits until the stream has been read to completion and returns
82
91
  the accumulated `Message` object.
83
92
  """
@@ -112,15 +121,16 @@ class MessageStream:
112
121
 
113
122
  # properties
114
123
  @property
115
- def current_message_snapshot(self) -> Message:
124
+ def current_message_snapshot(self) -> ParsedMessage[ResponseFormatT]:
116
125
  assert self.__final_message_snapshot is not None
117
126
  return self.__final_message_snapshot
118
127
 
119
- def __stream__(self) -> Iterator[MessageStreamEvent]:
128
+ def __stream__(self) -> Iterator[ParsedMessageStreamEvent[ResponseFormatT]]:
120
129
  for sse_event in self._raw_stream:
121
130
  self.__final_message_snapshot = accumulate_event(
122
131
  event=sse_event,
123
132
  current_snapshot=self.__final_message_snapshot,
133
+ output_format=self.__output_format,
124
134
  )
125
135
 
126
136
  events_to_fire = build_events(event=sse_event, message_snapshot=self.current_message_snapshot)
@@ -133,7 +143,7 @@ class MessageStream:
133
143
  yield chunk.delta.text
134
144
 
135
145
 
136
- class MessageStreamManager:
146
+ class MessageStreamManager(Generic[ResponseFormatT]):
137
147
  """Wrapper over MessageStream that is returned by `.stream()`.
138
148
 
139
149
  ```py
@@ -146,13 +156,16 @@ class MessageStreamManager:
146
156
  def __init__(
147
157
  self,
148
158
  api_request: Callable[[], Stream[RawMessageStreamEvent]],
159
+ *,
160
+ output_format: ResponseFormatT | NotGiven,
149
161
  ) -> None:
150
- self.__stream: MessageStream | None = None
162
+ self.__stream: MessageStream[ResponseFormatT] | None = None
151
163
  self.__api_request = api_request
164
+ self.__output_format = output_format
152
165
 
153
- def __enter__(self) -> MessageStream:
166
+ def __enter__(self) -> MessageStream[ResponseFormatT]:
154
167
  raw_stream = self.__api_request()
155
- self.__stream = MessageStream(raw_stream)
168
+ self.__stream = MessageStream(raw_stream, output_format=self.__output_format)
156
169
  return self.__stream
157
170
 
158
171
  def __exit__(
@@ -165,7 +178,7 @@ class MessageStreamManager:
165
178
  self.__stream.close()
166
179
 
167
180
 
168
- class AsyncMessageStream:
181
+ class AsyncMessageStream(Generic[ResponseFormatT]):
169
182
  text_stream: AsyncIterator[str]
170
183
  """Async iterator over just the text deltas in the stream.
171
184
 
@@ -176,11 +189,16 @@ class AsyncMessageStream:
176
189
  ```
177
190
  """
178
191
 
179
- def __init__(self, raw_stream: AsyncStream[RawMessageStreamEvent]) -> None:
192
+ def __init__(
193
+ self,
194
+ raw_stream: AsyncStream[RawMessageStreamEvent],
195
+ output_format: ResponseFormatT | NotGiven,
196
+ ) -> None:
180
197
  self._raw_stream = raw_stream
181
198
  self.text_stream = self.__stream_text__()
182
199
  self._iterator = self.__stream__()
183
- self.__final_message_snapshot: Message | None = None
200
+ self.__final_message_snapshot: ParsedMessage[ResponseFormatT] | None = None
201
+ self.__output_format = output_format
184
202
 
185
203
  @property
186
204
  def response(self) -> httpx.Response:
@@ -190,10 +208,10 @@ class AsyncMessageStream:
190
208
  def request_id(self) -> str | None:
191
209
  return self.response.headers.get("request-id") # type: ignore[no-any-return]
192
210
 
193
- async def __anext__(self) -> MessageStreamEvent:
211
+ async def __anext__(self) -> ParsedMessageStreamEvent[ResponseFormatT]:
194
212
  return await self._iterator.__anext__()
195
213
 
196
- async def __aiter__(self) -> AsyncIterator[MessageStreamEvent]:
214
+ async def __aiter__(self) -> AsyncIterator[ParsedMessageStreamEvent[ResponseFormatT]]:
197
215
  async for item in self._iterator:
198
216
  yield item
199
217
 
@@ -216,7 +234,7 @@ class AsyncMessageStream:
216
234
  """
217
235
  await self._raw_stream.close()
218
236
 
219
- async def get_final_message(self) -> Message:
237
+ async def get_final_message(self) -> ParsedMessage[ResponseFormatT]:
220
238
  """Waits until the stream has been read to completion and returns
221
239
  the accumulated `Message` object.
222
240
  """
@@ -251,15 +269,16 @@ class AsyncMessageStream:
251
269
 
252
270
  # properties
253
271
  @property
254
- def current_message_snapshot(self) -> Message:
272
+ def current_message_snapshot(self) -> ParsedMessage[ResponseFormatT]:
255
273
  assert self.__final_message_snapshot is not None
256
274
  return self.__final_message_snapshot
257
275
 
258
- async def __stream__(self) -> AsyncIterator[MessageStreamEvent]:
276
+ async def __stream__(self) -> AsyncIterator[ParsedMessageStreamEvent[ResponseFormatT]]:
259
277
  async for sse_event in self._raw_stream:
260
278
  self.__final_message_snapshot = accumulate_event(
261
279
  event=sse_event,
262
280
  current_snapshot=self.__final_message_snapshot,
281
+ output_format=self.__output_format,
263
282
  )
264
283
 
265
284
  events_to_fire = build_events(event=sse_event, message_snapshot=self.current_message_snapshot)
@@ -272,7 +291,7 @@ class AsyncMessageStream:
272
291
  yield chunk.delta.text
273
292
 
274
293
 
275
- class AsyncMessageStreamManager:
294
+ class AsyncMessageStreamManager(Generic[ResponseFormatT]):
276
295
  """Wrapper over AsyncMessageStream that is returned by `.stream()`
277
296
  so that an async context manager can be used without `await`ing the
278
297
  original client call.
@@ -287,13 +306,16 @@ class AsyncMessageStreamManager:
287
306
  def __init__(
288
307
  self,
289
308
  api_request: Awaitable[AsyncStream[RawMessageStreamEvent]],
309
+ *,
310
+ output_format: ResponseFormatT | NotGiven = NOT_GIVEN,
290
311
  ) -> None:
291
- self.__stream: AsyncMessageStream | None = None
312
+ self.__stream: AsyncMessageStream[ResponseFormatT] | None = None
292
313
  self.__api_request = api_request
314
+ self.__output_format = output_format
293
315
 
294
- async def __aenter__(self) -> AsyncMessageStream:
316
+ async def __aenter__(self) -> AsyncMessageStream[ResponseFormatT]:
295
317
  raw_stream = await self.__api_request
296
- self.__stream = AsyncMessageStream(raw_stream)
318
+ self.__stream = AsyncMessageStream(raw_stream, output_format=self.__output_format)
297
319
  return self.__stream
298
320
 
299
321
  async def __aexit__(
@@ -309,16 +331,18 @@ class AsyncMessageStreamManager:
309
331
  def build_events(
310
332
  *,
311
333
  event: RawMessageStreamEvent,
312
- message_snapshot: Message,
313
- ) -> list[MessageStreamEvent]:
314
- events_to_fire: list[MessageStreamEvent] = []
334
+ message_snapshot: ParsedMessage[ResponseFormatT],
335
+ ) -> list[ParsedMessageStreamEvent[ResponseFormatT]]:
336
+ events_to_fire: list[ParsedMessageStreamEvent[ResponseFormatT]] = []
315
337
 
316
338
  if event.type == "message_start":
317
339
  events_to_fire.append(event)
318
340
  elif event.type == "message_delta":
319
341
  events_to_fire.append(event)
320
342
  elif event.type == "message_stop":
321
- events_to_fire.append(build(MessageStopEvent, type="message_stop", message=message_snapshot))
343
+ events_to_fire.append(
344
+ build(ParsedMessageStopEvent[ResponseFormatT], type="message_stop", message=message_snapshot)
345
+ )
322
346
  elif event.type == "content_block_start":
323
347
  events_to_fire.append(event)
324
348
  elif event.type == "content_block_delta":
@@ -382,9 +406,14 @@ def build_events(
382
406
  elif event.type == "content_block_stop":
383
407
  content_block = message_snapshot.content[event.index]
384
408
 
385
- events_to_fire.append(
386
- build(ContentBlockStopEvent, type="content_block_stop", index=event.index, content_block=content_block),
409
+ event_to_fire = build(
410
+ ParsedContentBlockStopEvent,
411
+ type="content_block_stop",
412
+ index=event.index,
413
+ content_block=content_block,
387
414
  )
415
+
416
+ events_to_fire.append(event_to_fire)
388
417
  else:
389
418
  # we only want exhaustive checking for linters, not at runtime
390
419
  if TYPE_CHECKING: # type: ignore[unreachable]
@@ -404,8 +433,9 @@ TRACKS_TOOL_INPUT = (
404
433
  def accumulate_event(
405
434
  *,
406
435
  event: RawMessageStreamEvent,
407
- current_snapshot: Message | None,
408
- ) -> Message:
436
+ current_snapshot: ParsedMessage[ResponseFormatT] | None,
437
+ output_format: ResponseFormatT | NotGiven = NOT_GIVEN,
438
+ ) -> ParsedMessage[ResponseFormatT]:
409
439
  if not isinstance(cast(Any, event), BaseModel):
410
440
  event = cast( # pyright: ignore[reportUnnecessaryCast]
411
441
  RawMessageStreamEvent,
@@ -419,7 +449,7 @@ def accumulate_event(
419
449
 
420
450
  if current_snapshot is None:
421
451
  if event.type == "message_start":
422
- return Message.construct(**cast(Any, event.message.to_dict()))
452
+ return cast(ParsedMessage[ResponseFormatT], ParsedMessage.construct(**cast(Any, event.message.to_dict())))
423
453
 
424
454
  raise RuntimeError(f'Unexpected event order, got {event.type} before "message_start"')
425
455
 
@@ -427,8 +457,8 @@ def accumulate_event(
427
457
  # TODO: check index
428
458
  current_snapshot.content.append(
429
459
  cast(
430
- ContentBlock,
431
- construct_type(type_=ContentBlock, value=event.content_block.model_dump()),
460
+ Any, # Pydantic does not support generic unions at runtime
461
+ construct_type(type_=ParsedContentBlock, value=event.content_block.model_dump()),
432
462
  ),
433
463
  )
434
464
  elif event.type == "content_block_delta":
@@ -466,6 +496,10 @@ def accumulate_event(
466
496
  # we only want exhaustive checking for linters, not at runtime
467
497
  if TYPE_CHECKING: # type: ignore[unreachable]
468
498
  assert_never(event.delta)
499
+ elif event.type == "content_block_stop":
500
+ content_block = current_snapshot.content[event.index]
501
+ if content_block.type == "text" and is_given(output_format):
502
+ content_block.parsed_output = parse_text(content_block.text, output_format)
469
503
  elif event.type == "message_delta":
470
504
  current_snapshot.stop_reason = event.delta.stop_reason
471
505
  current_snapshot.stop_sequence = event.delta.stop_sequence
@@ -1,6 +1,8 @@
1
- from typing import Union
1
+ from typing import TYPE_CHECKING, Any, Dict, Union, Generic, cast
2
2
  from typing_extensions import List, Literal, Annotated
3
3
 
4
+ import jiter
5
+
4
6
  from ...types import (
5
7
  Message,
6
8
  ContentBlock,
@@ -11,8 +13,10 @@ from ...types import (
11
13
  ContentBlockStartEvent as RawContentBlockStartEvent,
12
14
  RawContentBlockStopEvent,
13
15
  )
14
- from ..._models import BaseModel
16
+ from ..._models import BaseModel, GenericModel
17
+ from .._parse._response import ResponseFormatT
15
18
  from ..._utils._transform import PropertyInfo
19
+ from ...types.parsed_message import ParsedMessage, ParsedContentBlock
16
20
  from ...types.citations_delta import Citation
17
21
 
18
22
 
@@ -25,6 +29,9 @@ class TextEvent(BaseModel):
25
29
  snapshot: str
26
30
  """The entire accumulated text"""
27
31
 
32
+ def parsed_snapshot(self) -> Dict[str, Any]:
33
+ return cast(Dict[str, Any], jiter.from_json(self.snapshot.encode("utf-8"), partial_mode="trailing-strings"))
34
+
28
35
 
29
36
  class CitationEvent(BaseModel):
30
37
  type: Literal["citation"]
@@ -98,3 +105,36 @@ MessageStreamEvent = Annotated[
98
105
  ],
99
106
  PropertyInfo(discriminator="type"),
100
107
  ]
108
+
109
+
110
+ class ParsedMessageStopEvent(RawMessageStopEvent, GenericModel, Generic[ResponseFormatT]):
111
+ type: Literal["message_stop"]
112
+
113
+ message: ParsedMessage[ResponseFormatT]
114
+
115
+
116
+ class ParsedContentBlockStopEvent(RawContentBlockStopEvent, GenericModel, Generic[ResponseFormatT]):
117
+ type: Literal["content_block_stop"]
118
+
119
+ if TYPE_CHECKING:
120
+ content_block: ParsedContentBlock[ResponseFormatT]
121
+ else:
122
+ content_block: ParsedContentBlock
123
+
124
+
125
+ ParsedMessageStreamEvent = Annotated[
126
+ Union[
127
+ TextEvent,
128
+ CitationEvent,
129
+ ThinkingEvent,
130
+ SignatureEvent,
131
+ InputJsonEvent,
132
+ RawMessageStartEvent,
133
+ RawMessageDeltaEvent,
134
+ ParsedMessageStopEvent[ResponseFormatT],
135
+ RawContentBlockStartEvent,
136
+ RawContentBlockDeltaEvent,
137
+ ParsedContentBlockStopEvent[ResponseFormatT],
138
+ ],
139
+ PropertyInfo(discriminator="type"),
140
+ ]