feat(cloud): add targeted task governance foundation
Tests / Test passed: 659

This commit is contained in:
2026-07-13 22:21:12 +08:00
parent a3ba94be04
commit 2cd314b183
41 changed files with 2099 additions and 15 deletions
+52 -2
View File
@@ -13,10 +13,18 @@ class ToolCallUnavailable(Exception):
"""Internal signal for expected tool-calling transport/response failures."""
@dataclass(frozen=True)
class ToolCallUsage:
input_tokens: int | None = None
output_tokens: int | None = None
total_tokens: int | None = None
@dataclass(frozen=True)
class ToolCallDecision:
tool_name: str
arguments: dict[str, Any]
usage: ToolCallUsage | None = None
class ToolCallingClient(Protocol):
@@ -234,7 +242,11 @@ def _decision_from_anthropic_response(response: Any) -> ToolCallDecision:
name = _value(block, "name")
arguments = _value(block, "input")
if isinstance(name, str) and isinstance(arguments, dict):
return ToolCallDecision(tool_name=name, arguments=arguments)
return ToolCallDecision(
tool_name=name,
arguments=arguments,
usage=_anthropic_usage(response),
)
raise ValueError("anthropic response did not include a tool_use block")
@@ -278,7 +290,40 @@ def _decision_from_openai_response(response: Any) -> ToolCallDecision:
if not isinstance(name, str):
raise ValueError("openai tool call missing a function name")
arguments = _decode_openai_arguments(_value(function, "arguments"))
return ToolCallDecision(tool_name=name, arguments=arguments)
return ToolCallDecision(
tool_name=name,
arguments=arguments,
usage=_openai_usage(response),
)
def _anthropic_usage(response: Any) -> ToolCallUsage | None:
usage = _value(response, "usage")
input_tokens = _integer_value(usage, "input_tokens")
output_tokens = _integer_value(usage, "output_tokens")
if input_tokens is None and output_tokens is None:
return None
return ToolCallUsage(
input_tokens=input_tokens,
output_tokens=output_tokens,
total_tokens=(input_tokens or 0) + (output_tokens or 0),
)
def _openai_usage(response: Any) -> ToolCallUsage | None:
usage = _value(response, "usage")
input_tokens = _integer_value(usage, "prompt_tokens")
output_tokens = _integer_value(usage, "completion_tokens")
total_tokens = _integer_value(usage, "total_tokens")
if input_tokens is None and output_tokens is None and total_tokens is None:
return None
return ToolCallUsage(
input_tokens=input_tokens,
output_tokens=output_tokens,
total_tokens=total_tokens
if total_tokens is not None
else (input_tokens or 0) + (output_tokens or 0),
)
def _decode_openai_arguments(raw_arguments: Any) -> dict[str, Any]:
@@ -296,3 +341,8 @@ def _value(source: Any, key: str) -> Any:
return source.get(key)
value = getattr(source, key, None)
return None if callable(value) else value
def _integer_value(source: Any, key: str) -> int | None:
value = _value(source, key)
return value if isinstance(value, int) and value >= 0 else None