mirror of
https://github.com/hanzoai/python-sdk.git
synced 2026-10-23 20:23:00 +00:00
Let HanzoCloud carry request headers, and a tool name its default action
Enso reads its routing bounds from X-Max-Cost and X-Max-Latency-Ms on /v1/chat/completions, and HanzoCloud could send no header but its own, so no tool could bound a routed request. post() and the shared request path now take extra headers that ride beside the bearer. BaseTool runs `help` for a call that names no action. A tool with one obvious act (llm's query, kai_decide's decide) now declares DEFAULT_ACTION, which the call, the advertised schema's default and its required list all read, so it answers the same no-action call the TypeScript and Rust runtimes do. Every other tool keeps `help`. hanzo-tools 0.3.8. Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
@@ -156,11 +156,13 @@ class HanzoCloud:
|
||||
)
|
||||
return self._client
|
||||
|
||||
async def _request(self, method: str, path: str, **kw: Any) -> Any:
|
||||
async def _request(
|
||||
self, method: str, path: str, headers: dict[str, str] | None = None, **kw: Any
|
||||
) -> Any:
|
||||
client = await self._get_client()
|
||||
try:
|
||||
resp = await client.request(
|
||||
method, path, headers=self._headers(), **kw
|
||||
method, path, headers={**self._headers(), **(headers or {})}, **kw
|
||||
)
|
||||
except Exception as e: # transport/DNS/timeout — no HTTP status
|
||||
raise CloudError(f"{method} {path} failed: {e}") from e
|
||||
@@ -180,9 +182,18 @@ class HanzoCloud:
|
||||
clean = {k: v for k, v in (params or {}).items() if v is not None}
|
||||
return await self._request("GET", path, params=clean)
|
||||
|
||||
async def post(self, path: str, json_body: dict[str, Any] | None = None) -> Any:
|
||||
"""POST json_body to path, returning parsed JSON. Raises on failure."""
|
||||
return await self._request("POST", path, json=json_body or {})
|
||||
async def post(
|
||||
self,
|
||||
path: str,
|
||||
json_body: dict[str, Any] | None = None,
|
||||
headers: dict[str, str] | None = None,
|
||||
) -> Any:
|
||||
"""POST json_body to path, returning parsed JSON. Raises on failure.
|
||||
|
||||
``headers`` ride beside the auth header, for a route that reads one (Enso's
|
||||
X-Max-Cost and X-Max-Latency-Ms on /v1/chat/completions).
|
||||
"""
|
||||
return await self._request("POST", path, headers=headers, json=json_body or {})
|
||||
|
||||
async def stream(
|
||||
self,
|
||||
|
||||
@@ -259,6 +259,11 @@ class BaseTool(_BaseToolABC):
|
||||
# Version for meta envelope
|
||||
VERSION: ClassVar[str] = "0.12.0"
|
||||
|
||||
# The action a call that names none runs. `help` unless the tool has one
|
||||
# obvious act, as `llm` (query) does — the same default the TypeScript and
|
||||
# Rust runtimes give that tool.
|
||||
DEFAULT_ACTION: ClassVar[str] = "help"
|
||||
|
||||
# Param aliases for cross-implementation parity (e.g., {"path": "uri"})
|
||||
PARAM_ALIASES: ClassVar[dict[str, str]] = {}
|
||||
|
||||
@@ -460,18 +465,20 @@ class BaseTool(_BaseToolABC):
|
||||
pass
|
||||
|
||||
async def call(
|
||||
self, ctx: MCPContext, action: str = "help", **kwargs: Any
|
||||
self, ctx: MCPContext, action: str | None = None, **kwargs: Any
|
||||
) -> dict[str, Any]:
|
||||
"""Execute tool with action routing.
|
||||
|
||||
Args:
|
||||
ctx: MCP context
|
||||
action: Action to execute (default: "help")
|
||||
action: Action to execute (default: DEFAULT_ACTION, normally "help")
|
||||
**kwargs: Action parameters (flat, matching TS wire format)
|
||||
|
||||
Returns:
|
||||
Unified response envelope
|
||||
"""
|
||||
action = action or self.DEFAULT_ACTION
|
||||
|
||||
# Unwrap kwargs wrapping from legacy clients: {"kwargs": {...}} → flat
|
||||
if "kwargs" in kwargs and isinstance(kwargs["kwargs"], dict) and len(kwargs) == 1:
|
||||
kwargs = kwargs["kwargs"]
|
||||
@@ -524,7 +531,7 @@ class BaseTool(_BaseToolABC):
|
||||
"action": {
|
||||
"type": "string",
|
||||
"description": "Action to perform",
|
||||
"default": "help",
|
||||
"default": self.DEFAULT_ACTION,
|
||||
}
|
||||
}
|
||||
for ah in self._handlers.values():
|
||||
@@ -536,14 +543,14 @@ class BaseTool(_BaseToolABC):
|
||||
parameters_schema = {
|
||||
"type": "object",
|
||||
"properties": all_properties,
|
||||
"required": ["action"],
|
||||
"required": ["action"] if self.DEFAULT_ACTION == "help" else [],
|
||||
"additionalProperties": True,
|
||||
}
|
||||
|
||||
# Permissive Pydantic model that accepts any extra fields
|
||||
class _FlexArgs(ArgModelBase):
|
||||
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
|
||||
action: str = "help"
|
||||
action: str | None = None
|
||||
|
||||
def model_dump_one_level(self) -> dict[str, Any]:
|
||||
result = super().model_dump_one_level()
|
||||
@@ -554,7 +561,7 @@ class BaseTool(_BaseToolABC):
|
||||
# Handler function — receives flat validated args
|
||||
tool_ref = self
|
||||
|
||||
async def _handler(ctx: MCPContext, action: str = "help", **kwargs: Any) -> Any:
|
||||
async def _handler(ctx: MCPContext, action: str | None = None, **kwargs: Any) -> Any:
|
||||
result = await tool_ref.call(ctx, action=action, **kwargs)
|
||||
return _result_to_mcp(result)
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "hanzo-tools"
|
||||
version = "0.3.7"
|
||||
version = "0.3.8"
|
||||
description = "Hanzo AI tools - core infrastructure and tool bundles"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
|
||||
Reference in New Issue
Block a user