anthropic 0.74.1__py3-none-any.whl → 0.76.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 (84) hide show
  1. anthropic/_base_client.py +140 -11
  2. anthropic/_client.py +4 -12
  3. anthropic/_models.py +16 -1
  4. anthropic/_streaming.py +78 -76
  5. anthropic/_types.py +12 -2
  6. anthropic/_version.py +1 -1
  7. anthropic/lib/streaming/_beta_messages.py +1 -1
  8. anthropic/lib/tools/_beta_compaction_control.py +48 -0
  9. anthropic/lib/tools/_beta_functions.py +30 -8
  10. anthropic/lib/tools/_beta_runner.py +233 -34
  11. anthropic/resources/beta/messages/messages.py +134 -27
  12. anthropic/resources/messages/messages.py +4 -0
  13. anthropic/types/anthropic_beta_param.py +1 -0
  14. anthropic/types/beta/__init__.py +31 -0
  15. anthropic/types/beta/beta_code_execution_tool_20250522_param.py +9 -1
  16. anthropic/types/beta/beta_code_execution_tool_20250825_param.py +9 -1
  17. anthropic/types/beta/beta_container.py +4 -0
  18. anthropic/types/beta/beta_container_params.py +2 -0
  19. anthropic/types/beta/beta_container_upload_block.py +2 -0
  20. anthropic/types/beta/beta_container_upload_block_param.py +5 -0
  21. anthropic/types/beta/beta_content_block.py +2 -0
  22. anthropic/types/beta/beta_content_block_param.py +2 -0
  23. anthropic/types/beta/beta_direct_caller.py +13 -0
  24. anthropic/types/beta/beta_direct_caller_param.py +13 -0
  25. anthropic/types/beta/beta_mcp_tool_config_param.py +15 -0
  26. anthropic/types/beta/beta_mcp_tool_default_config_param.py +15 -0
  27. anthropic/types/beta/beta_mcp_toolset_param.py +34 -0
  28. anthropic/types/beta/beta_memory_tool_20250818_param.py +11 -1
  29. anthropic/types/beta/beta_output_config_param.py +13 -0
  30. anthropic/types/beta/beta_raw_content_block_start_event.py +2 -0
  31. anthropic/types/beta/beta_server_tool_caller.py +15 -0
  32. anthropic/types/beta/beta_server_tool_caller_param.py +15 -0
  33. anthropic/types/beta/beta_server_tool_use_block.py +20 -4
  34. anthropic/types/beta/beta_server_tool_use_block_param.py +19 -4
  35. anthropic/types/beta/beta_skill.py +2 -0
  36. anthropic/types/beta/beta_skill_params.py +2 -0
  37. anthropic/types/beta/beta_tool_bash_20241022_param.py +11 -1
  38. anthropic/types/beta/beta_tool_bash_20250124_param.py +11 -1
  39. anthropic/types/beta/beta_tool_choice_any_param.py +2 -0
  40. anthropic/types/beta/beta_tool_choice_auto_param.py +2 -0
  41. anthropic/types/beta/beta_tool_choice_none_param.py +2 -0
  42. anthropic/types/beta/beta_tool_choice_tool_param.py +2 -0
  43. anthropic/types/beta/beta_tool_computer_use_20241022_param.py +11 -1
  44. anthropic/types/beta/beta_tool_computer_use_20250124_param.py +11 -1
  45. anthropic/types/beta/beta_tool_computer_use_20251124_param.py +47 -0
  46. anthropic/types/beta/beta_tool_param.py +16 -1
  47. anthropic/types/beta/beta_tool_reference_block.py +13 -0
  48. anthropic/types/beta/beta_tool_reference_block_param.py +21 -0
  49. anthropic/types/beta/beta_tool_result_block_param.py +6 -1
  50. anthropic/types/beta/beta_tool_search_tool_bm25_20251119_param.py +33 -0
  51. anthropic/types/beta/beta_tool_search_tool_regex_20251119_param.py +33 -0
  52. anthropic/types/beta/beta_tool_search_tool_result_block.py +20 -0
  53. anthropic/types/beta/beta_tool_search_tool_result_block_param.py +25 -0
  54. anthropic/types/beta/beta_tool_search_tool_result_error.py +16 -0
  55. anthropic/types/beta/beta_tool_search_tool_result_error_param.py +13 -0
  56. anthropic/types/beta/beta_tool_search_tool_search_result_block.py +15 -0
  57. anthropic/types/beta/beta_tool_search_tool_search_result_block_param.py +16 -0
  58. anthropic/types/beta/beta_tool_text_editor_20241022_param.py +11 -1
  59. anthropic/types/beta/beta_tool_text_editor_20250124_param.py +11 -1
  60. anthropic/types/beta/beta_tool_text_editor_20250429_param.py +11 -1
  61. anthropic/types/beta/beta_tool_text_editor_20250728_param.py +11 -1
  62. anthropic/types/beta/beta_tool_union_param.py +8 -0
  63. anthropic/types/beta/beta_tool_use_block.py +11 -3
  64. anthropic/types/beta/beta_tool_use_block_param.py +10 -3
  65. anthropic/types/beta/beta_web_fetch_tool_20250910_param.py +9 -1
  66. anthropic/types/beta/beta_web_search_tool_20250305_param.py +14 -1
  67. anthropic/types/beta/message_count_tokens_params.py +15 -0
  68. anthropic/types/beta/message_create_params.py +7 -0
  69. anthropic/types/beta/messages/batch_create_params.py +4 -4
  70. anthropic/types/beta/messages/beta_message_batch_individual_response.py +4 -0
  71. anthropic/types/messages/batch_create_params.py +2 -2
  72. anthropic/types/messages/message_batch_individual_response.py +4 -0
  73. anthropic/types/model.py +2 -0
  74. anthropic/types/model_param.py +2 -0
  75. anthropic/types/tool_choice_any_param.py +2 -0
  76. anthropic/types/tool_choice_auto_param.py +2 -0
  77. anthropic/types/tool_choice_none_param.py +2 -0
  78. anthropic/types/tool_choice_tool_param.py +2 -0
  79. anthropic/types/tool_param.py +5 -0
  80. anthropic/types/web_search_tool_20250305_param.py +5 -0
  81. {anthropic-0.74.1.dist-info → anthropic-0.76.0.dist-info}/METADATA +4 -2
  82. {anthropic-0.74.1.dist-info → anthropic-0.76.0.dist-info}/RECORD +84 -64
  83. {anthropic-0.74.1.dist-info → anthropic-0.76.0.dist-info}/WHEEL +0 -0
  84. {anthropic-0.74.1.dist-info → anthropic-0.76.0.dist-info}/licenses/LICENSE +0 -0
@@ -14,9 +14,9 @@ from ... import _compat
14
14
  from ..._utils import is_dict
15
15
  from ..._compat import cached_property
16
16
  from ..._models import TypeAdapter
17
- from ...types.beta import BetaToolUnionParam
17
+ from ...types.beta import BetaToolParam, BetaToolUnionParam
18
18
  from ..._utils._utils import CallableT
19
- from ...types.tool_param import ToolParam, InputSchema
19
+ from ...types.tool_param import InputSchema
20
20
  from ...types.beta.beta_tool_result_block_param import Content as BetaContent
21
21
 
22
22
  log = logging.getLogger(__name__)
@@ -39,7 +39,10 @@ class BetaBuiltinFunctionTool(ABC):
39
39
 
40
40
  @property
41
41
  def name(self) -> str:
42
- return self.to_dict()["name"]
42
+ raw = self.to_dict()
43
+ if "mcp_server_name" in raw:
44
+ return raw["mcp_server_name"]
45
+ return raw["name"]
43
46
 
44
47
 
45
48
  class BetaAsyncBuiltinFunctionTool(ABC):
@@ -51,7 +54,10 @@ class BetaAsyncBuiltinFunctionTool(ABC):
51
54
 
52
55
  @property
53
56
  def name(self) -> str:
54
- return self.to_dict()["name"]
57
+ raw = self.to_dict()
58
+ if "mcp_server_name" in raw:
59
+ return raw["mcp_server_name"]
60
+ return raw["name"]
55
61
 
56
62
 
57
63
  class BaseFunctionTool(Generic[CallableT]):
@@ -72,6 +78,7 @@ class BaseFunctionTool(Generic[CallableT]):
72
78
  name: str | None = None,
73
79
  description: str | None = None,
74
80
  input_schema: InputSchema | type[BaseModel] | None = None,
81
+ defer_loading: bool | None = None,
75
82
  ) -> None:
76
83
  if _compat.PYDANTIC_V1:
77
84
  raise RuntimeError("Tool functions are only supported with Pydantic v2")
@@ -79,6 +86,7 @@ class BaseFunctionTool(Generic[CallableT]):
79
86
  self.func = func
80
87
  self._func_with_validate = pydantic.validate_call(func)
81
88
  self.name = name or func.__name__
89
+ self._defer_loading = defer_loading
82
90
 
83
91
  self.description = description or self._get_description_from_docstring()
84
92
 
@@ -94,12 +102,15 @@ class BaseFunctionTool(Generic[CallableT]):
94
102
  def __call__(self) -> CallableT:
95
103
  return self.func
96
104
 
97
- def to_dict(self) -> ToolParam:
98
- return {
105
+ def to_dict(self) -> BetaToolParam:
106
+ defn: BetaToolParam = {
99
107
  "name": self.name,
100
108
  "description": self.description,
101
109
  "input_schema": self.input_schema,
102
110
  }
111
+ if self._defer_loading is not None:
112
+ defn["defer_loading"] = self._defer_loading
113
+ return defn
103
114
 
104
115
  @cached_property
105
116
  def _parsed_docstring(self) -> docstring_parser.Docstring:
@@ -211,6 +222,7 @@ def beta_tool(
211
222
  name: str | None = None,
212
223
  description: str | None = None,
213
224
  input_schema: InputSchema | type[BaseModel] | None = None,
225
+ defer_loading: bool | None = None,
214
226
  ) -> Callable[[FunctionT], BetaFunctionTool[FunctionT]]: ...
215
227
 
216
228
 
@@ -220,6 +232,7 @@ def beta_tool(
220
232
  name: str | None = None,
221
233
  description: str | None = None,
222
234
  input_schema: InputSchema | type[BaseModel] | None = None,
235
+ defer_loading: bool | None = None,
223
236
  ) -> BetaFunctionTool[FunctionT] | Callable[[FunctionT], BetaFunctionTool[FunctionT]]:
224
237
  """Create a FunctionTool from a function with automatic schema inference.
225
238
 
@@ -239,11 +252,15 @@ def beta_tool(
239
252
 
240
253
  if func is not None:
241
254
  # @beta_tool called without parentheses
242
- return BetaFunctionTool(func=func, name=name, description=description, input_schema=input_schema)
255
+ return BetaFunctionTool(
256
+ func=func, name=name, description=description, input_schema=input_schema, defer_loading=defer_loading
257
+ )
243
258
 
244
259
  # @beta_tool()
245
260
  def decorator(func: FunctionT) -> BetaFunctionTool[FunctionT]:
246
- return BetaFunctionTool(func=func, name=name, description=description, input_schema=input_schema)
261
+ return BetaFunctionTool(
262
+ func=func, name=name, description=description, input_schema=input_schema, defer_loading=defer_loading
263
+ )
247
264
 
248
265
  return decorator
249
266
 
@@ -259,6 +276,7 @@ def beta_async_tool(
259
276
  name: str | None = None,
260
277
  description: str | None = None,
261
278
  input_schema: InputSchema | type[BaseModel] | None = None,
279
+ defer_loading: bool | None = None,
262
280
  ) -> BetaAsyncFunctionTool[AsyncFunctionT]: ...
263
281
 
264
282
 
@@ -268,6 +286,7 @@ def beta_async_tool(
268
286
  name: str | None = None,
269
287
  description: str | None = None,
270
288
  input_schema: InputSchema | type[BaseModel] | None = None,
289
+ defer_loading: bool | None = None,
271
290
  ) -> Callable[[AsyncFunctionT], BetaAsyncFunctionTool[AsyncFunctionT]]: ...
272
291
 
273
292
 
@@ -277,6 +296,7 @@ def beta_async_tool(
277
296
  name: str | None = None,
278
297
  description: str | None = None,
279
298
  input_schema: InputSchema | type[BaseModel] | None = None,
299
+ defer_loading: bool | None = None,
280
300
  ) -> BetaAsyncFunctionTool[AsyncFunctionT] | Callable[[AsyncFunctionT], BetaAsyncFunctionTool[AsyncFunctionT]]:
281
301
  """Create an AsyncFunctionTool from a function with automatic schema inference.
282
302
 
@@ -301,6 +321,7 @@ def beta_async_tool(
301
321
  name=name,
302
322
  description=description,
303
323
  input_schema=input_schema,
324
+ defer_loading=defer_loading,
304
325
  )
305
326
 
306
327
  # @beta_async_tool()
@@ -310,6 +331,7 @@ def beta_async_tool(
310
331
  name=name,
311
332
  description=description,
312
333
  input_schema=input_schema,
334
+ defer_loading=defer_loading,
313
335
  )
314
336
 
315
337
  return decorator
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import logging
4
+ import warnings
4
5
  from abc import ABC, abstractmethod
5
6
  from typing import (
6
7
  TYPE_CHECKING,
@@ -31,6 +32,7 @@ from ._beta_functions import (
31
32
  BetaBuiltinFunctionTool,
32
33
  BetaAsyncBuiltinFunctionTool,
33
34
  )
35
+ from ._beta_compaction_control import DEFAULT_THRESHOLD, DEFAULT_SUMMARY_PROMPT, CompactionControl
34
36
  from ..streaming._beta_messages import BetaMessageStream, BetaAsyncMessageStream
35
37
  from ...types.beta.parsed_beta_message import ResponseFormatT, ParsedBetaMessage, ParsedBetaContentBlock
36
38
  from ...types.beta.message_create_params import ParseMessageCreateParamsBase
@@ -66,6 +68,7 @@ class BaseToolRunner(Generic[AnyFunctionToolT, ResponseFormatT]):
66
68
  options: RequestOptions,
67
69
  tools: Iterable[AnyFunctionToolT],
68
70
  max_iterations: int | None = None,
71
+ compaction_control: CompactionControl | None = None,
69
72
  ) -> None:
70
73
  self._tools_by_name = {tool.name: tool for tool in tools}
71
74
  self._params: ParseMessageCreateParamsBase[ResponseFormatT] = {
@@ -77,6 +80,7 @@ class BaseToolRunner(Generic[AnyFunctionToolT, ResponseFormatT]):
77
80
  self._cached_tool_call_response: BetaMessageParam | None = None
78
81
  self._max_iterations = max_iterations
79
82
  self._iteration_count = 0
83
+ self._compaction_control = compaction_control
80
84
 
81
85
  def set_messages_params(
82
86
  self,
@@ -122,9 +126,17 @@ class BaseSyncToolRunner(BaseToolRunner[BetaRunnableTool, ResponseFormatT], Gene
122
126
  tools: Iterable[BetaRunnableTool],
123
127
  client: Anthropic,
124
128
  max_iterations: int | None = None,
129
+ compaction_control: CompactionControl | None = None,
125
130
  ) -> None:
126
- super().__init__(params=params, options=options, tools=tools, max_iterations=max_iterations)
131
+ super().__init__(
132
+ params=params,
133
+ options=options,
134
+ tools=tools,
135
+ max_iterations=max_iterations,
136
+ compaction_control=compaction_control,
137
+ )
127
138
  self._client = client
139
+
128
140
  self._iterator = self.__run__()
129
141
  self._last_message: (
130
142
  Callable[[], ParsedBetaMessage[ResponseFormatT]] | ParsedBetaMessage[ResponseFormatT] | None
@@ -143,31 +155,112 @@ class BaseSyncToolRunner(BaseToolRunner[BetaRunnableTool, ResponseFormatT], Gene
143
155
  raise NotImplementedError()
144
156
  yield # type: ignore[unreachable]
145
157
 
146
- def __run__(self) -> Iterator[RunnerItemT]:
147
- with self._handle_request() as item:
148
- yield item
149
- message = self._get_last_message()
150
- assert message is not None
151
- self._iteration_count += 1
158
+ def _check_and_compact(self) -> bool:
159
+ """
160
+ Check token usage and compact messages if threshold exceeded.
161
+ Returns True if compaction was performed, False otherwise.
162
+ """
163
+ if self._compaction_control is None or not self._compaction_control["enabled"]:
164
+ return False
165
+
166
+ message = self._get_last_message()
167
+ tokens_used = 0
168
+ if message is not None:
169
+ total_input_tokens = (
170
+ message.usage.input_tokens
171
+ + (message.usage.cache_creation_input_tokens or 0)
172
+ + (message.usage.cache_read_input_tokens or 0)
173
+ )
174
+ tokens_used = total_input_tokens + message.usage.output_tokens
175
+
176
+ threshold = self._compaction_control.get("context_token_threshold", DEFAULT_THRESHOLD)
177
+
178
+ if tokens_used < threshold:
179
+ return False
180
+
181
+ # Perform compaction
182
+ log.info(f"Token usage {tokens_used} has exceeded the threshold of {threshold}. Performing compaction.")
183
+
184
+ model = self._compaction_control.get("model", self._params["model"])
185
+
186
+ messages = list(self._params["messages"])
187
+
188
+ if messages[-1]["role"] == "assistant":
189
+ # Remove tool_use blocks from the last message to avoid 400 error
190
+ # (tool_use requires tool_result, which we don't have yet)
191
+ non_tool_blocks = [
192
+ block
193
+ for block in messages[-1]["content"]
194
+ if isinstance(block, dict) and block.get("type") != "tool_use"
195
+ ]
196
+
197
+ if non_tool_blocks:
198
+ messages[-1]["content"] = non_tool_blocks
199
+ else:
200
+ messages.pop()
201
+
202
+ messages = [
203
+ *messages,
204
+ BetaMessageParam(
205
+ role="user",
206
+ content=self._compaction_control.get("summary_prompt", DEFAULT_SUMMARY_PROMPT),
207
+ ),
208
+ ]
152
209
 
153
- while not self._should_stop():
154
- response = self.generate_tool_call_response()
155
- if response is None:
156
- log.debug("Tool call was not requested, exiting from tool runner loop.")
157
- return
210
+ response = self._client.beta.messages.create(
211
+ model=model,
212
+ messages=messages,
213
+ max_tokens=self._params["max_tokens"],
214
+ extra_headers={"X-Stainless-Helper": "compaction"},
215
+ )
158
216
 
159
- if not self._messages_modified:
160
- self.append_messages(message, response)
217
+ log.info(f"Compaction complete. New token usage: {response.usage.output_tokens}")
161
218
 
162
- self._iteration_count += 1
163
- self._messages_modified = False
164
- self._cached_tool_call_response = None
219
+ first_content = list(response.content)[0]
165
220
 
221
+ if first_content.type != "text":
222
+ raise ValueError("Compaction response content is not of type 'text'")
223
+
224
+ self.set_messages_params(
225
+ lambda params: {
226
+ **params,
227
+ "messages": [
228
+ {
229
+ "role": "user",
230
+ "content": [
231
+ {
232
+ "type": "text",
233
+ "text": first_content.text,
234
+ }
235
+ ],
236
+ }
237
+ ],
238
+ }
239
+ )
240
+ return True
241
+
242
+ def __run__(self) -> Iterator[RunnerItemT]:
243
+ while not self._should_stop():
166
244
  with self._handle_request() as item:
167
245
  yield item
168
246
  message = self._get_last_message()
169
247
  assert message is not None
170
248
 
249
+ self._iteration_count += 1
250
+
251
+ # If the compaction was performed, skip tool call generation this iteration
252
+ if not self._check_and_compact():
253
+ response = self.generate_tool_call_response()
254
+ if response is None:
255
+ log.debug("Tool call was not requested, exiting from tool runner loop.")
256
+ return
257
+
258
+ if not self._messages_modified:
259
+ self.append_messages(message, response)
260
+
261
+ self._messages_modified = False
262
+ self._cached_tool_call_response = None
263
+
171
264
  def until_done(self) -> ParsedBetaMessage[ResponseFormatT]:
172
265
  """
173
266
  Consumes the tool runner stream and returns the last message if it has not been consumed yet.
@@ -206,6 +299,14 @@ class BaseSyncToolRunner(BaseToolRunner[BetaRunnableTool, ResponseFormatT], Gene
206
299
  for tool_use in tool_use_blocks:
207
300
  tool = self._tools_by_name.get(tool_use.name)
208
301
  if tool is None:
302
+ warnings.warn(
303
+ f"Tool '{tool_use.name}' not found in tool runner. "
304
+ f"Available tools: {list(self._tools_by_name.keys())}. "
305
+ f"If using a raw tool definition, handle the tool call manually and use `append_messages()` to add the result. "
306
+ f"Otherwise, pass the tool using `beta_tool(func)` or a `@beta_tool` decorated function.",
307
+ UserWarning,
308
+ stacklevel=3,
309
+ )
209
310
  results.append(
210
311
  {
211
312
  "type": "tool_result",
@@ -274,9 +375,17 @@ class BaseAsyncToolRunner(
274
375
  tools: Iterable[BetaAsyncRunnableTool],
275
376
  client: AsyncAnthropic,
276
377
  max_iterations: int | None = None,
378
+ compaction_control: CompactionControl | None = None,
277
379
  ) -> None:
278
- super().__init__(params=params, options=options, tools=tools, max_iterations=max_iterations)
380
+ super().__init__(
381
+ params=params,
382
+ options=options,
383
+ tools=tools,
384
+ max_iterations=max_iterations,
385
+ compaction_control=compaction_control,
386
+ )
279
387
  self._client = client
388
+
280
389
  self._iterator = self.__run__()
281
390
  self._last_message: (
282
391
  Callable[[], Coroutine[None, None, ParsedBetaMessage[ResponseFormatT]]]
@@ -297,30 +406,112 @@ class BaseAsyncToolRunner(
297
406
  raise NotImplementedError()
298
407
  yield # type: ignore[unreachable]
299
408
 
300
- async def __run__(self) -> AsyncIterator[RunnerItemT]:
301
- async with self._handle_request() as item:
302
- yield item
303
- message = await self._get_last_message()
304
- assert message is not None
305
- self._iteration_count += 1
409
+ async def _check_and_compact(self) -> bool:
410
+ """
411
+ Check token usage and compact messages if threshold exceeded.
412
+ Returns True if compaction was performed, False otherwise.
413
+ """
414
+ if self._compaction_control is None or not self._compaction_control["enabled"]:
415
+ return False
416
+
417
+ message = await self._get_last_message()
418
+ tokens_used = 0
419
+ if message is not None:
420
+ total_input_tokens = (
421
+ message.usage.input_tokens
422
+ + (message.usage.cache_creation_input_tokens or 0)
423
+ + (message.usage.cache_read_input_tokens or 0)
424
+ )
425
+ tokens_used = total_input_tokens + message.usage.output_tokens
426
+
427
+ threshold = self._compaction_control.get("context_token_threshold", DEFAULT_THRESHOLD)
428
+
429
+ if tokens_used < threshold:
430
+ return False
431
+
432
+ # Perform compaction
433
+ log.info(f"Token usage {tokens_used} has exceeded the threshold of {threshold}. Performing compaction.")
434
+
435
+ model = self._compaction_control.get("model", self._params["model"])
436
+
437
+ messages = list(self._params["messages"])
438
+
439
+ if messages[-1]["role"] == "assistant":
440
+ # Remove tool_use blocks from the last message to avoid 400 error
441
+ # (tool_use requires tool_result, which we don't have yet)
442
+ non_tool_blocks = [
443
+ block
444
+ for block in messages[-1]["content"]
445
+ if isinstance(block, dict) and block.get("type") != "tool_use"
446
+ ]
447
+
448
+ if non_tool_blocks:
449
+ messages[-1]["content"] = non_tool_blocks
450
+ else:
451
+ messages.pop()
452
+
453
+ messages = [
454
+ *self._params["messages"],
455
+ BetaMessageParam(
456
+ role="user",
457
+ content=self._compaction_control.get("summary_prompt", DEFAULT_SUMMARY_PROMPT),
458
+ ),
459
+ ]
306
460
 
307
- while not self._should_stop():
308
- response = await self.generate_tool_call_response()
309
- if response is None:
310
- log.debug("Tool call was not requested, exiting from tool runner loop.")
311
- return
461
+ response = await self._client.beta.messages.create(
462
+ model=model,
463
+ messages=messages,
464
+ max_tokens=self._params["max_tokens"],
465
+ extra_headers={"X-Stainless-Helper": "compaction"},
466
+ )
312
467
 
313
- if not self._messages_modified:
314
- self.append_messages(message, response)
315
- self._iteration_count += 1
316
- self._messages_modified = False
317
- self._cached_tool_call_response = None
468
+ log.info(f"Compaction complete. New token usage: {response.usage.output_tokens}")
469
+
470
+ first_content = list(response.content)[0]
471
+
472
+ if first_content.type != "text":
473
+ raise ValueError("Compaction response content is not of type 'text'")
474
+
475
+ self.set_messages_params(
476
+ lambda params: {
477
+ **params,
478
+ "messages": [
479
+ {
480
+ "role": "user",
481
+ "content": [
482
+ {
483
+ "type": "text",
484
+ "text": first_content.text,
485
+ }
486
+ ],
487
+ }
488
+ ],
489
+ }
490
+ )
491
+ return True
318
492
 
493
+ async def __run__(self) -> AsyncIterator[RunnerItemT]:
494
+ while not self._should_stop():
319
495
  async with self._handle_request() as item:
320
496
  yield item
321
497
  message = await self._get_last_message()
322
498
  assert message is not None
323
499
 
500
+ self._iteration_count += 1
501
+
502
+ # If the compaction was performed, skip tool call generation this iteration
503
+ if not await self._check_and_compact():
504
+ response = await self.generate_tool_call_response()
505
+ if response is None:
506
+ log.debug("Tool call was not requested, exiting from tool runner loop.")
507
+ return
508
+
509
+ if not self._messages_modified:
510
+ self.append_messages(message, response)
511
+
512
+ self._messages_modified = False
513
+ self._cached_tool_call_response = None
514
+
324
515
  async def until_done(self) -> ParsedBetaMessage[ResponseFormatT]:
325
516
  """
326
517
  Consumes the tool runner stream and returns the last message if it has not been consumed yet.
@@ -372,6 +563,14 @@ class BaseAsyncToolRunner(
372
563
  for tool_use in tool_use_blocks:
373
564
  tool = self._tools_by_name.get(tool_use.name)
374
565
  if tool is None:
566
+ warnings.warn(
567
+ f"Tool '{tool_use.name}' not found in tool runner. "
568
+ f"Available tools: {list(self._tools_by_name.keys())}. "
569
+ f"If using a raw tool definition, handle the tool call manually and use `append_messages()` to add the result. "
570
+ f"Otherwise, pass the tool using `beta_async_tool(func)` or a `@beta_async_tool` decorated function.",
571
+ UserWarning,
572
+ stacklevel=3,
573
+ )
375
574
  results.append(
376
575
  {
377
576
  "type": "tool_result",