"""Extend the tool-calling loop at runtime.
Allows the bot to grant itself additional tool-calling rounds when it
determines that the current limit is insufficient to complete a complex
multi-step task.
"""
from __future__ import annotations
import json
import logging
logger = logging.getLogger(__name__)
# Maximum additional rounds that can be requested in one call (still clamped
# by _ABSOLUTE_MAX_TOOL_ROUNDS minus current max).
_MAX_EXTENSION_PER_CALL = 20
# Hard ceiling for max_tool_rounds after extension (product policy).
_ABSOLUTE_MAX_TOOL_ROUNDS = 25
TOOL_NAME = "extend_tool_loop"
TOOL_DESCRIPTION = (
"Grant yourself additional tool-calling rounds when you need more "
"turns to complete a multi-step task. Use this when you realize "
"you are running low on remaining tool rounds and still have work "
"to do. Default extension is 5 rounds, up to 20 per call, and the "
"total limit never exceeds 25 (if already at the cap, no rounds are added)."
)
TOOL_PARAMETERS = {
"type": "object",
"properties": {
"rounds": {
"type": "integer",
"description": (
"Number of additional tool-calling rounds to grant "
"(default: 5, max: 20 per request, absolute max total rounds: 25)."
),
},
},
"required": [],
}
[docs]
async def run(rounds: int = 5, ctx=None) -> str:
"""Grant additional tool-calling rounds.
Parameters
----------
rounds : int
Number of extra rounds to add (capped per call and by absolute max).
ctx : ToolContext
Injected context with access to the OpenRouter client.
"""
openrouter = getattr(ctx, "openrouter", None)
if openrouter is None:
return json.dumps({
"success": False,
"error": "OpenRouter client not available in context.",
})
old_max = int(openrouter.max_tool_rounds)
headroom = max(0, _ABSOLUTE_MAX_TOOL_ROUNDS - old_max)
try:
if rounds is None:
requested = 5
else:
requested = int(rounds)
except (TypeError, ValueError):
requested = 5
requested = max(1, min(requested, _MAX_EXTENSION_PER_CALL))
extension = min(requested, headroom)
new_max = old_max + extension
openrouter.max_tool_rounds = new_max
if extension:
logger.info(
"Extended tool loop: %d -> %d rounds (+%d, cap %d)",
old_max, new_max, extension, _ABSOLUTE_MAX_TOOL_ROUNDS,
)
else:
logger.info(
"extend_tool_loop: no change (current=%d, cap=%d, headroom=%d)",
old_max, _ABSOLUTE_MAX_TOOL_ROUNDS, headroom,
)
return json.dumps({
"success": True,
"added_rounds": extension,
"previous_max_rounds": old_max,
"new_max_rounds": new_max,
"absolute_cap": _ABSOLUTE_MAX_TOOL_ROUNDS,
"headroom_before": headroom,
})