refactor: nuke `strix_tool` shim + dead package re-exports
``@strix_tool`` was passing through every kwarg to ``@function_tool``
with the same defaults — zero Strix-specific value-add. The docstring
also still claimed terminal/browser/python tools opted into
``timeout_behavior="raise_exception"``, but those tools were all
deleted in the recent migrations.
- Replace 30 ``@strix_tool(...)`` callsites with ``@function_tool(...)``.
- Inline ``dump_tool_result(x)`` as ``json.dumps(x, ensure_ascii=False,
default=str)`` at all 64 callsites — no helper.
- Delete ``strix/tools/_decorator.py``.
Drive-by: gut dead package re-exports.
- ``strix/{agents,orchestration,tools}/__init__.py`` re-exported
symbols nobody imports via the package — every consumer uses deep
paths (``from strix.agents.factory import build_strix_agent``).
- The 8 ``strix/tools/<sub>/__init__.py`` re-exports only fed the
splat ``from .agents_graph import *`` etc. in the parent package
init, which is also gone now.
- Reduced to docstrings (or empty) so ``import strix.tools`` doesn't
drag every tool's transitive deps in eagerly.
Drive-by: drop dead helpers in ``runtime.session_manager``
(``cached_scan_ids``, ``_reset_cache_for_tests``) — zero callers since
``tests/`` was nuked in ``a6d578c``.
Verified all tool timeouts preserved (think=10, list_requests=120,
finish_scan=60, web_search=330) and ruff/mypy at baseline.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -214,9 +214,6 @@ ignore = [
|
||||
"strix/llm/multi_provider_setup.py" = [
|
||||
"TC002", # Model, ModelProvider imported for annotations
|
||||
]
|
||||
"strix/tools/_decorator.py" = [
|
||||
"TC002", # FunctionTool imported for annotation
|
||||
]
|
||||
# SDK function-tool wrappers: the SDK calls get_type_hints() at registration
|
||||
# time to derive the JSON schema, which evaluates annotations at runtime —
|
||||
# so RunContextWrapper / Tool / TResponseInputItem must be imported eagerly,
|
||||
|
||||
@@ -1,19 +1,12 @@
|
||||
"""Strix agent package.
|
||||
"""Strix agent assembly.
|
||||
|
||||
Public surface:
|
||||
- :func:`strix.agents.factory.build_strix_agent` — assemble a root or
|
||||
child ``SandboxAgent``.
|
||||
- :func:`strix.agents.factory.make_child_factory` — closure factory
|
||||
passed via context to the multi-agent ``create_agent`` graph tool.
|
||||
- :func:`strix.agents.prompt.render_system_prompt` — render the Jinja
|
||||
system prompt.
|
||||
|
||||
- :func:`build_strix_agent` — assemble a root or child ``agents.Agent``.
|
||||
- :func:`make_child_factory` — closure factory passed via context to
|
||||
the multi-agent ``create_agent`` graph tool.
|
||||
- :func:`render_system_prompt` — render the Jinja system prompt.
|
||||
Import deeply so ``import strix.agents`` doesn't pull every submodule's
|
||||
deps in eagerly.
|
||||
"""
|
||||
|
||||
from .factory import build_strix_agent, make_child_factory
|
||||
from .prompt import render_system_prompt
|
||||
|
||||
|
||||
__all__ = [
|
||||
"build_strix_agent",
|
||||
"make_child_factory",
|
||||
"render_system_prompt",
|
||||
]
|
||||
|
||||
@@ -1,18 +1,12 @@
|
||||
"""Strix multi-agent orchestration on top of OpenAI Agents SDK.
|
||||
|
||||
Provides:
|
||||
- AgentMessageBus: peer-to-peer agent inbox + status + stats aggregation
|
||||
- inject_messages_filter: SDK call_model_input_filter for inbox drain
|
||||
- StrixOrchestrationHooks: SDK RunHooks subclass for lifecycle wiring
|
||||
- :class:`AgentMessageBus` — peer-to-peer agent inbox + status + stats.
|
||||
- :func:`inject_messages_filter` — SDK ``call_model_input_filter`` for
|
||||
inbox drain at the top of each LLM turn.
|
||||
- :class:`StrixOrchestrationHooks` — SDK ``RunHooks`` subclass for
|
||||
lifecycle wiring.
|
||||
|
||||
Import deeply (``from strix.orchestration.bus import AgentMessageBus``)
|
||||
so ``import strix.orchestration`` doesn't drag every submodule's deps
|
||||
in eagerly.
|
||||
"""
|
||||
|
||||
from strix.orchestration.bus import AgentMessageBus
|
||||
from strix.orchestration.filter import inject_messages_filter
|
||||
from strix.orchestration.hooks import StrixOrchestrationHooks
|
||||
|
||||
|
||||
__all__ = [
|
||||
"AgentMessageBus",
|
||||
"StrixOrchestrationHooks",
|
||||
"inject_messages_filter",
|
||||
]
|
||||
|
||||
@@ -144,13 +144,3 @@ async def cleanup(scan_id: str) -> None:
|
||||
"cleanup(%s): client.delete raised; container may need manual reaping",
|
||||
scan_id,
|
||||
)
|
||||
|
||||
|
||||
def cached_scan_ids() -> list[str]:
|
||||
"""Snapshot of currently-cached scan ids. Used by the TUI / CLI."""
|
||||
return list(_SESSION_CACHE.keys())
|
||||
|
||||
|
||||
def _reset_cache_for_tests() -> None:
|
||||
"""Test helper — clears the module cache between unit tests."""
|
||||
_SESSION_CACHE.clear()
|
||||
|
||||
@@ -5,13 +5,7 @@ imported directly by :mod:`strix.agents.factory`. The sandbox-bound
|
||||
shell + filesystem tools are emitted by the SDK's ``Shell`` and
|
||||
``Filesystem`` capabilities and bound to the live sandbox session
|
||||
per-run.
|
||||
"""
|
||||
|
||||
from .agents_graph import * # noqa: F403
|
||||
from .finish import * # noqa: F403
|
||||
from .notes import * # noqa: F403
|
||||
from .proxy import * # noqa: F403
|
||||
from .reporting import * # noqa: F403
|
||||
from .thinking import * # noqa: F403
|
||||
from .todo import * # noqa: F403
|
||||
from .web_search import * # noqa: F403
|
||||
Import deeply so ``import strix.tools`` doesn't pull every submodule's
|
||||
deps in eagerly.
|
||||
"""
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
"""``strix_tool`` — ``function_tool`` factory with Strix defaults.
|
||||
|
||||
Every tool uses ``@strix_tool`` instead of bare ``@function_tool`` so
|
||||
defaults stay consistent across the suite. Override per call when
|
||||
needed.
|
||||
|
||||
Defaults:
|
||||
- ``timeout``: 120s.
|
||||
- ``timeout_behavior``: ``"error_as_result"`` for idempotent tools.
|
||||
Critical sandbox tools (terminal, browser, python) opt into
|
||||
``timeout_behavior="raise_exception"`` explicitly so the SDK
|
||||
fails the run rather than letting the model retry a hung call.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from collections.abc import Callable
|
||||
from typing import Any, Literal
|
||||
|
||||
from agents import function_tool
|
||||
from agents.tool import FunctionTool
|
||||
|
||||
|
||||
_ToolFn = Callable[..., Any]
|
||||
_ToolBehavior = Literal["error_as_result", "raise_exception"]
|
||||
|
||||
|
||||
def dump_tool_result(result: dict[str, Any]) -> str:
|
||||
"""Serialize a tool's dict result to JSON for the LLM.
|
||||
|
||||
Every Strix tool returns a dict; the SDK passes the tool's return
|
||||
straight into ``str(result)``, which produces ugly Python repr
|
||||
output. JSON is what the model expects.
|
||||
"""
|
||||
return json.dumps(result, ensure_ascii=False, default=str)
|
||||
|
||||
|
||||
def strix_tool(
|
||||
*,
|
||||
timeout: float = 120.0,
|
||||
timeout_behavior: _ToolBehavior = "error_as_result",
|
||||
name_override: str | None = None,
|
||||
description_override: str | None = None,
|
||||
strict_mode: bool = True,
|
||||
) -> Callable[[_ToolFn], FunctionTool]:
|
||||
"""Wrap ``agents.function_tool`` with Strix defaults.
|
||||
|
||||
Strict mode is on by default (forbids free-form ``dict[str, X]``
|
||||
parameters because the strict JSON schema needs
|
||||
``additionalProperties: false``). A few tools that take arbitrary
|
||||
header / modification dicts opt out via ``strict_mode=False``.
|
||||
|
||||
Usage::
|
||||
|
||||
@strix_tool()
|
||||
async def my_tool(ctx: RunContextWrapper, x: int) -> str: ...
|
||||
|
||||
@strix_tool(timeout=300, timeout_behavior="raise_exception")
|
||||
async def critical_tool(ctx: RunContextWrapper, ...) -> str: ...
|
||||
|
||||
@strix_tool(strict_mode=False)
|
||||
async def free_form_dict_tool(
|
||||
ctx: RunContextWrapper, headers: dict[str, str],
|
||||
) -> str: ...
|
||||
"""
|
||||
return function_tool(
|
||||
timeout=timeout,
|
||||
timeout_behavior=timeout_behavior,
|
||||
name_override=name_override,
|
||||
description_override=description_override,
|
||||
strict_mode=strict_mode,
|
||||
)
|
||||
@@ -1,18 +0,0 @@
|
||||
from .tools import (
|
||||
agent_finish,
|
||||
agent_status,
|
||||
create_agent,
|
||||
send_message_to_agent,
|
||||
view_agent_graph,
|
||||
wait_for_message,
|
||||
)
|
||||
|
||||
|
||||
__all__ = [
|
||||
"agent_finish",
|
||||
"agent_status",
|
||||
"create_agent",
|
||||
"send_message_to_agent",
|
||||
"view_agent_graph",
|
||||
"wait_for_message",
|
||||
]
|
||||
|
||||
@@ -21,12 +21,11 @@ import logging
|
||||
import uuid
|
||||
from typing import TYPE_CHECKING, Any, Literal
|
||||
|
||||
from agents import RunContextWrapper, Runner
|
||||
from agents import RunContextWrapper, Runner, function_tool
|
||||
from agents.items import TResponseInputItem
|
||||
|
||||
from strix.orchestration.hooks import StrixOrchestrationHooks
|
||||
from strix.run_config_factory import make_agent_context, make_run_config
|
||||
from strix.tools._decorator import dump_tool_result, strix_tool
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -42,7 +41,7 @@ def _ctx(ctx: RunContextWrapper) -> dict[str, Any]:
|
||||
return ctx.context if isinstance(ctx.context, dict) else {}
|
||||
|
||||
|
||||
@strix_tool(timeout=30)
|
||||
@function_tool(timeout=30)
|
||||
async def view_agent_graph(ctx: RunContextWrapper) -> str:
|
||||
"""Print the multi-agent tree — every agent, its parent, its status.
|
||||
|
||||
@@ -57,7 +56,11 @@ async def view_agent_graph(ctx: RunContextWrapper) -> str:
|
||||
bus = inner.get("bus")
|
||||
me = inner.get("agent_id")
|
||||
if bus is None:
|
||||
return dump_tool_result({"success": False, "error": "Bus not initialized in context."})
|
||||
return json.dumps(
|
||||
{"success": False, "error": "Bus not initialized in context."},
|
||||
ensure_ascii=False,
|
||||
default=str,
|
||||
)
|
||||
|
||||
async with bus._lock:
|
||||
parent_of = dict(bus.parent_of)
|
||||
@@ -86,16 +89,18 @@ async def view_agent_graph(ctx: RunContextWrapper) -> str:
|
||||
"crashed": sum(1 for s in statuses.values() if s == "crashed"),
|
||||
"stopped": sum(1 for s in statuses.values() if s == "stopped"),
|
||||
}
|
||||
return dump_tool_result(
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"graph_structure": "\n".join(lines) or "(no agents)",
|
||||
"summary": summary,
|
||||
}
|
||||
},
|
||||
ensure_ascii=False,
|
||||
default=str,
|
||||
)
|
||||
|
||||
|
||||
@strix_tool(timeout=30)
|
||||
@function_tool(timeout=30)
|
||||
async def agent_status(ctx: RunContextWrapper, agent_id: str) -> str:
|
||||
"""Look up one agent's lifecycle state + pending message count.
|
||||
|
||||
@@ -111,17 +116,23 @@ async def agent_status(ctx: RunContextWrapper, agent_id: str) -> str:
|
||||
inner = _ctx(ctx)
|
||||
bus = inner.get("bus")
|
||||
if bus is None:
|
||||
return dump_tool_result({"success": False, "error": "Bus not initialized in context."})
|
||||
return json.dumps(
|
||||
{"success": False, "error": "Bus not initialized in context."},
|
||||
ensure_ascii=False,
|
||||
default=str,
|
||||
)
|
||||
|
||||
async with bus._lock:
|
||||
if agent_id not in bus.statuses:
|
||||
return dump_tool_result(
|
||||
return json.dumps(
|
||||
{
|
||||
"success": False,
|
||||
"error": f"Unknown agent_id: {agent_id}",
|
||||
}
|
||||
},
|
||||
ensure_ascii=False,
|
||||
default=str,
|
||||
)
|
||||
return dump_tool_result(
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"agent_id": agent_id,
|
||||
@@ -129,11 +140,13 @@ async def agent_status(ctx: RunContextWrapper, agent_id: str) -> str:
|
||||
"status": bus.statuses.get(agent_id),
|
||||
"parent_id": bus.parent_of.get(agent_id),
|
||||
"pending_messages": len(bus.inboxes.get(agent_id, [])),
|
||||
}
|
||||
},
|
||||
ensure_ascii=False,
|
||||
default=str,
|
||||
)
|
||||
|
||||
|
||||
@strix_tool(timeout=30)
|
||||
@function_tool(timeout=30)
|
||||
async def send_message_to_agent(
|
||||
ctx: RunContextWrapper,
|
||||
target_agent_id: str,
|
||||
@@ -169,24 +182,32 @@ async def send_message_to_agent(
|
||||
bus = inner.get("bus")
|
||||
me = inner.get("agent_id")
|
||||
if bus is None or me is None:
|
||||
return dump_tool_result({"success": False, "error": "Bus or agent_id missing in context."})
|
||||
return json.dumps(
|
||||
{"success": False, "error": "Bus or agent_id missing in context."},
|
||||
ensure_ascii=False,
|
||||
default=str,
|
||||
)
|
||||
|
||||
async with bus._lock:
|
||||
if target_agent_id not in bus.statuses:
|
||||
return dump_tool_result(
|
||||
return json.dumps(
|
||||
{
|
||||
"success": False,
|
||||
"error": f"Target agent '{target_agent_id}' not found.",
|
||||
}
|
||||
},
|
||||
ensure_ascii=False,
|
||||
default=str,
|
||||
)
|
||||
target_status = bus.statuses.get(target_agent_id)
|
||||
|
||||
if target_status in ("completed", "crashed", "stopped"):
|
||||
return dump_tool_result(
|
||||
return json.dumps(
|
||||
{
|
||||
"success": False,
|
||||
"error": f"Target agent '{target_agent_id}' is {target_status}; message dropped.",
|
||||
}
|
||||
},
|
||||
ensure_ascii=False,
|
||||
default=str,
|
||||
)
|
||||
|
||||
msg_id = f"msg_{uuid.uuid4().hex[:8]}"
|
||||
@@ -200,13 +221,15 @@ async def send_message_to_agent(
|
||||
"priority": priority,
|
||||
},
|
||||
)
|
||||
return dump_tool_result(
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"message_id": msg_id,
|
||||
"target_agent_id": target_agent_id,
|
||||
"delivery_status": "queued",
|
||||
}
|
||||
},
|
||||
ensure_ascii=False,
|
||||
default=str,
|
||||
)
|
||||
|
||||
|
||||
@@ -215,7 +238,7 @@ async def send_message_to_agent(
|
||||
_WAIT_POLL_SECONDS = 1.0
|
||||
|
||||
|
||||
@strix_tool(timeout=601)
|
||||
@function_tool(timeout=601)
|
||||
async def wait_for_message(
|
||||
ctx: RunContextWrapper,
|
||||
reason: str = "Waiting for messages from other agents",
|
||||
@@ -251,7 +274,11 @@ async def wait_for_message(
|
||||
bus = inner.get("bus")
|
||||
me = inner.get("agent_id")
|
||||
if bus is None or me is None:
|
||||
return dump_tool_result({"success": False, "error": "Bus or agent_id missing in context."})
|
||||
return json.dumps(
|
||||
{"success": False, "error": "Bus or agent_id missing in context."},
|
||||
ensure_ascii=False,
|
||||
default=str,
|
||||
)
|
||||
|
||||
async with bus._lock:
|
||||
bus.statuses[me] = "waiting"
|
||||
@@ -264,13 +291,15 @@ async def wait_for_message(
|
||||
if pending > 0:
|
||||
async with bus._lock:
|
||||
bus.statuses[me] = "running"
|
||||
return dump_tool_result(
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"status": "message_arrived",
|
||||
"pending_messages": pending,
|
||||
"reason": reason,
|
||||
}
|
||||
},
|
||||
ensure_ascii=False,
|
||||
default=str,
|
||||
)
|
||||
await asyncio.sleep(_WAIT_POLL_SECONDS)
|
||||
finally:
|
||||
@@ -280,18 +309,20 @@ async def wait_for_message(
|
||||
if bus.statuses.get(me) == "waiting":
|
||||
bus.statuses[me] = "running"
|
||||
|
||||
return dump_tool_result(
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"status": "timeout",
|
||||
"timeout_seconds": timeout_seconds,
|
||||
"reason": reason,
|
||||
"note": "No messages within timeout — continue work or call agent_finish.",
|
||||
}
|
||||
},
|
||||
ensure_ascii=False,
|
||||
default=str,
|
||||
)
|
||||
|
||||
|
||||
@strix_tool(timeout=120)
|
||||
@function_tool(timeout=120)
|
||||
async def create_agent(
|
||||
ctx: RunContextWrapper,
|
||||
name: str,
|
||||
@@ -344,16 +375,22 @@ async def create_agent(
|
||||
factory: Callable[..., SDKAgent] | None = inner.get("agent_factory")
|
||||
|
||||
if bus is None or parent_id is None:
|
||||
return dump_tool_result({"success": False, "error": "Bus or agent_id missing in context."})
|
||||
return json.dumps(
|
||||
{"success": False, "error": "Bus or agent_id missing in context."},
|
||||
ensure_ascii=False,
|
||||
default=str,
|
||||
)
|
||||
if factory is None:
|
||||
return dump_tool_result(
|
||||
return json.dumps(
|
||||
{
|
||||
"success": False,
|
||||
"error": (
|
||||
"No agent_factory in context. "
|
||||
"The root assembly must inject one via make_agent_context."
|
||||
),
|
||||
}
|
||||
},
|
||||
ensure_ascii=False,
|
||||
default=str,
|
||||
)
|
||||
|
||||
child_id = uuid.uuid4().hex[:8]
|
||||
@@ -362,11 +399,13 @@ async def create_agent(
|
||||
child_agent = factory(name=name, skills=skills or [])
|
||||
except Exception as e:
|
||||
logger.exception("agent_factory raised while building child '%s'", name)
|
||||
return dump_tool_result(
|
||||
return json.dumps(
|
||||
{
|
||||
"success": False,
|
||||
"error": f"agent_factory failed: {e!s}",
|
||||
}
|
||||
},
|
||||
ensure_ascii=False,
|
||||
default=str,
|
||||
)
|
||||
|
||||
await bus.register(child_id, name, parent_id)
|
||||
@@ -437,18 +476,20 @@ async def create_agent(
|
||||
async with bus._lock:
|
||||
bus.tasks[child_id] = task_handle
|
||||
|
||||
return dump_tool_result(
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"agent_id": child_id,
|
||||
"name": name,
|
||||
"parent_id": parent_id,
|
||||
"message": f"Spawned '{name}' ({child_id}) running in parallel.",
|
||||
}
|
||||
},
|
||||
ensure_ascii=False,
|
||||
default=str,
|
||||
)
|
||||
|
||||
|
||||
@strix_tool(timeout=30)
|
||||
@function_tool(timeout=30)
|
||||
async def agent_finish(
|
||||
ctx: RunContextWrapper,
|
||||
result_summary: str,
|
||||
@@ -495,11 +536,15 @@ async def agent_finish(
|
||||
bus = inner.get("bus")
|
||||
me = inner.get("agent_id")
|
||||
if bus is None or me is None:
|
||||
return dump_tool_result({"success": False, "error": "Bus or agent_id missing in context."})
|
||||
return json.dumps(
|
||||
{"success": False, "error": "Bus or agent_id missing in context."},
|
||||
ensure_ascii=False,
|
||||
default=str,
|
||||
)
|
||||
|
||||
parent_id = inner.get("parent_id")
|
||||
if parent_id is None:
|
||||
return dump_tool_result(
|
||||
return json.dumps(
|
||||
{
|
||||
"success": False,
|
||||
"agent_completed": False,
|
||||
@@ -507,7 +552,9 @@ async def agent_finish(
|
||||
"agent_finish is for subagents. Root/main agents must call finish_scan instead."
|
||||
),
|
||||
"parent_notified": False,
|
||||
}
|
||||
},
|
||||
ensure_ascii=False,
|
||||
default=str,
|
||||
)
|
||||
|
||||
inner["agent_finish_called"] = True
|
||||
@@ -540,7 +587,7 @@ async def agent_finish(
|
||||
)
|
||||
parent_notified = True
|
||||
|
||||
return dump_tool_result(
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"agent_completed": True,
|
||||
@@ -549,5 +596,7 @@ async def agent_finish(
|
||||
"summary": result_summary,
|
||||
"findings_count": len(findings or []),
|
||||
"has_recommendations": bool(final_recommendations),
|
||||
}
|
||||
},
|
||||
ensure_ascii=False,
|
||||
default=str,
|
||||
)
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
from .tool import finish_scan
|
||||
|
||||
|
||||
__all__ = ["finish_scan"]
|
||||
|
||||
@@ -7,9 +7,7 @@ import json
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from agents import RunContextWrapper
|
||||
|
||||
from strix.tools._decorator import strix_tool
|
||||
from agents import RunContextWrapper, function_tool
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -71,7 +69,7 @@ def _do_finish(
|
||||
return {"success": False, "message": f"Failed to complete scan: {e!s}"}
|
||||
|
||||
|
||||
@strix_tool(timeout=60)
|
||||
@function_tool(timeout=60)
|
||||
async def finish_scan(
|
||||
ctx: RunContextWrapper,
|
||||
executive_summary: str,
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
from .tools import create_note, delete_note, get_note, list_notes, update_note
|
||||
|
||||
|
||||
__all__ = [
|
||||
"create_note",
|
||||
"delete_note",
|
||||
"get_note",
|
||||
"list_notes",
|
||||
"update_note",
|
||||
]
|
||||
|
||||
+21
-13
@@ -21,9 +21,7 @@ from typing import TYPE_CHECKING, Any
|
||||
if TYPE_CHECKING:
|
||||
from pathlib import Path
|
||||
|
||||
from agents import RunContextWrapper
|
||||
|
||||
from strix.tools._decorator import dump_tool_result, strix_tool
|
||||
from agents import RunContextWrapper, function_tool
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -397,7 +395,7 @@ def _delete_note_impl(note_id: str) -> dict[str, Any]:
|
||||
# --- public tools ---------------------------------------------------------
|
||||
|
||||
|
||||
@strix_tool(timeout=30)
|
||||
@function_tool(timeout=30)
|
||||
async def create_note(
|
||||
ctx: RunContextWrapper,
|
||||
title: str,
|
||||
@@ -437,12 +435,14 @@ async def create_note(
|
||||
category: One of the categories above. Default ``"general"``.
|
||||
tags: Optional free-form tags.
|
||||
"""
|
||||
return dump_tool_result(
|
||||
return json.dumps(
|
||||
await asyncio.to_thread(_create_note_impl, title, content, category, tags),
|
||||
ensure_ascii=False,
|
||||
default=str,
|
||||
)
|
||||
|
||||
|
||||
@strix_tool(timeout=30)
|
||||
@function_tool(timeout=30)
|
||||
async def list_notes(
|
||||
ctx: RunContextWrapper,
|
||||
category: str | None = None,
|
||||
@@ -468,7 +468,7 @@ async def list_notes(
|
||||
include_content: When False (default) entries have a preview;
|
||||
when True the full ``content`` is included.
|
||||
"""
|
||||
return dump_tool_result(
|
||||
return json.dumps(
|
||||
await asyncio.to_thread(
|
||||
_list_notes_impl,
|
||||
category=category,
|
||||
@@ -476,20 +476,24 @@ async def list_notes(
|
||||
search=search,
|
||||
include_content=include_content,
|
||||
),
|
||||
ensure_ascii=False,
|
||||
default=str,
|
||||
)
|
||||
|
||||
|
||||
@strix_tool(timeout=30)
|
||||
@function_tool(timeout=30)
|
||||
async def get_note(ctx: RunContextWrapper, note_id: str) -> str:
|
||||
"""Fetch one note by its 5-char ID. Returns the full content.
|
||||
|
||||
Args:
|
||||
note_id: Note id from ``create_note`` or a ``list_notes`` entry.
|
||||
"""
|
||||
return dump_tool_result(await asyncio.to_thread(_get_note_impl, note_id))
|
||||
return json.dumps(
|
||||
await asyncio.to_thread(_get_note_impl, note_id), ensure_ascii=False, default=str
|
||||
)
|
||||
|
||||
|
||||
@strix_tool(timeout=30)
|
||||
@function_tool(timeout=30)
|
||||
async def update_note(
|
||||
ctx: RunContextWrapper,
|
||||
note_id: str,
|
||||
@@ -509,7 +513,7 @@ async def update_note(
|
||||
content: New content, or ``None`` to keep.
|
||||
tags: New tags list, or ``None`` to keep.
|
||||
"""
|
||||
return dump_tool_result(
|
||||
return json.dumps(
|
||||
await asyncio.to_thread(
|
||||
_update_note_impl,
|
||||
note_id=note_id,
|
||||
@@ -517,14 +521,18 @@ async def update_note(
|
||||
content=content,
|
||||
tags=tags,
|
||||
),
|
||||
ensure_ascii=False,
|
||||
default=str,
|
||||
)
|
||||
|
||||
|
||||
@strix_tool(timeout=30)
|
||||
@function_tool(timeout=30)
|
||||
async def delete_note(ctx: RunContextWrapper, note_id: str) -> str:
|
||||
"""Delete a note. For wiki notes, also removes the rendered Markdown file.
|
||||
|
||||
Args:
|
||||
note_id: Note id to delete.
|
||||
"""
|
||||
return dump_tool_result(await asyncio.to_thread(_delete_note_impl, note_id))
|
||||
return json.dumps(
|
||||
await asyncio.to_thread(_delete_note_impl, note_id), ensure_ascii=False, default=str
|
||||
)
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
from .tools import list_requests, repeat_request, scope_rules, send_request, view_request
|
||||
|
||||
|
||||
__all__ = [
|
||||
"list_requests",
|
||||
"repeat_request",
|
||||
"scope_rules",
|
||||
"send_request",
|
||||
"view_request",
|
||||
]
|
||||
|
||||
+81
-30
@@ -12,6 +12,7 @@ Tools: ``list_requests``, ``view_request``, ``send_request``,
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
import json
|
||||
import re
|
||||
import time
|
||||
from dataclasses import is_dataclass
|
||||
@@ -19,7 +20,7 @@ from datetime import datetime
|
||||
from typing import TYPE_CHECKING, Any, Literal
|
||||
from urllib.parse import parse_qs, urlencode, urlparse, urlunparse
|
||||
|
||||
from agents import RunContextWrapper
|
||||
from agents import RunContextWrapper, function_tool
|
||||
from caido_sdk_client.types import (
|
||||
ConnectionInfoInput,
|
||||
CreateReplaySessionFromRaw,
|
||||
@@ -30,8 +31,6 @@ from caido_sdk_client.types import (
|
||||
UpdateScopeOptions,
|
||||
)
|
||||
|
||||
from strix.tools._decorator import dump_tool_result, strix_tool
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from caido_sdk_client import Client
|
||||
@@ -92,18 +91,20 @@ def _serialize(value: Any) -> Any:
|
||||
|
||||
|
||||
def _no_client() -> str:
|
||||
return dump_tool_result(
|
||||
return json.dumps(
|
||||
{
|
||||
"success": False,
|
||||
"error": "Caido client not initialized in context.",
|
||||
},
|
||||
ensure_ascii=False,
|
||||
default=str,
|
||||
)
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# list_requests
|
||||
# ----------------------------------------------------------------------
|
||||
@strix_tool(timeout=120)
|
||||
@function_tool(timeout=120)
|
||||
async def list_requests(
|
||||
ctx: RunContextWrapper,
|
||||
httpql_filter: str | None = None,
|
||||
@@ -201,7 +202,7 @@ async def list_requests(
|
||||
},
|
||||
)
|
||||
|
||||
return dump_tool_result(
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"entries": entries,
|
||||
@@ -212,15 +213,21 @@ async def list_requests(
|
||||
"end_cursor": connection.page_info.end_cursor,
|
||||
},
|
||||
},
|
||||
ensure_ascii=False,
|
||||
default=str,
|
||||
)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
return dump_tool_result({"success": False, "error": f"list_requests failed: {exc}"})
|
||||
return json.dumps(
|
||||
{"success": False, "error": f"list_requests failed: {exc}"},
|
||||
ensure_ascii=False,
|
||||
default=str,
|
||||
)
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# view_request
|
||||
# ----------------------------------------------------------------------
|
||||
@strix_tool(timeout=60)
|
||||
@function_tool(timeout=60)
|
||||
async def view_request(
|
||||
ctx: RunContextWrapper,
|
||||
request_id: str,
|
||||
@@ -265,8 +272,10 @@ async def view_request(
|
||||
)
|
||||
result = await client.request.get(request_id, opts)
|
||||
if result is None:
|
||||
return dump_tool_result(
|
||||
return json.dumps(
|
||||
{"success": False, "error": f"Request {request_id} not found"},
|
||||
ensure_ascii=False,
|
||||
default=str,
|
||||
)
|
||||
|
||||
raw_bytes = (
|
||||
@@ -275,20 +284,30 @@ async def view_request(
|
||||
else (result.response.raw if result.response is not None else None)
|
||||
)
|
||||
if raw_bytes is None:
|
||||
return dump_tool_result(
|
||||
return json.dumps(
|
||||
{
|
||||
"success": False,
|
||||
"error": f"No raw {part} for {request_id}",
|
||||
},
|
||||
ensure_ascii=False,
|
||||
default=str,
|
||||
)
|
||||
content = raw_bytes.decode("utf-8", errors="replace")
|
||||
|
||||
if search_pattern:
|
||||
return dump_tool_result(_regex_hits(content, search_pattern))
|
||||
return json.dumps(_regex_hits(content, search_pattern), ensure_ascii=False, default=str)
|
||||
|
||||
return dump_tool_result(_paginate_lines(content, page=page, page_size=page_size))
|
||||
return json.dumps(
|
||||
_paginate_lines(content, page=page, page_size=page_size),
|
||||
ensure_ascii=False,
|
||||
default=str,
|
||||
)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
return dump_tool_result({"success": False, "error": f"view_request failed: {exc}"})
|
||||
return json.dumps(
|
||||
{"success": False, "error": f"view_request failed: {exc}"},
|
||||
ensure_ascii=False,
|
||||
default=str,
|
||||
)
|
||||
|
||||
|
||||
def _regex_hits(content: str, pattern: str) -> dict[str, Any]:
|
||||
@@ -333,7 +352,7 @@ def _paginate_lines(content: str, *, page: int, page_size: int) -> dict[str, Any
|
||||
# ----------------------------------------------------------------------
|
||||
# send_request
|
||||
# ----------------------------------------------------------------------
|
||||
@strix_tool(timeout=120, strict_mode=False)
|
||||
@function_tool(timeout=120, strict_mode=False)
|
||||
async def send_request(
|
||||
ctx: RunContextWrapper,
|
||||
method: str,
|
||||
@@ -367,13 +386,17 @@ async def send_request(
|
||||
)
|
||||
return await _replay_send(client, raw=raw, connection=connection)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
return dump_tool_result({"success": False, "error": f"send_request failed: {exc}"})
|
||||
return json.dumps(
|
||||
{"success": False, "error": f"send_request failed: {exc}"},
|
||||
ensure_ascii=False,
|
||||
default=str,
|
||||
)
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# repeat_request
|
||||
# ----------------------------------------------------------------------
|
||||
@strix_tool(timeout=120, strict_mode=False)
|
||||
@function_tool(timeout=120, strict_mode=False)
|
||||
async def repeat_request(
|
||||
ctx: RunContextWrapper,
|
||||
request_id: str,
|
||||
@@ -412,8 +435,10 @@ async def repeat_request(
|
||||
try:
|
||||
result = await client.request.get(request_id, RequestGetOptions(request_raw=True))
|
||||
if result is None or result.request.raw is None:
|
||||
return dump_tool_result(
|
||||
return json.dumps(
|
||||
{"success": False, "error": f"Request {request_id} not found"},
|
||||
ensure_ascii=False,
|
||||
default=str,
|
||||
)
|
||||
|
||||
original = result.request
|
||||
@@ -429,13 +454,17 @@ async def repeat_request(
|
||||
)
|
||||
return await _replay_send(client, raw=raw, connection=connection)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
return dump_tool_result({"success": False, "error": f"repeat_request failed: {exc}"})
|
||||
return json.dumps(
|
||||
{"success": False, "error": f"repeat_request failed: {exc}"},
|
||||
ensure_ascii=False,
|
||||
default=str,
|
||||
)
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# scope_rules
|
||||
# ----------------------------------------------------------------------
|
||||
@strix_tool(timeout=60)
|
||||
@function_tool(timeout=60)
|
||||
async def scope_rules(
|
||||
ctx: RunContextWrapper,
|
||||
action: ScopeAction,
|
||||
@@ -488,20 +517,28 @@ async def scope_rules(
|
||||
try:
|
||||
if action == "list":
|
||||
scopes = await client.scope.list()
|
||||
return dump_tool_result(
|
||||
return json.dumps(
|
||||
{"success": True, "scopes": [_serialize(s) for s in scopes]},
|
||||
ensure_ascii=False,
|
||||
default=str,
|
||||
)
|
||||
if action == "get":
|
||||
if not scope_id:
|
||||
return dump_tool_result(
|
||||
return json.dumps(
|
||||
{"success": False, "error": "scope_id required for get"},
|
||||
ensure_ascii=False,
|
||||
default=str,
|
||||
)
|
||||
scope = await client.scope.get(scope_id)
|
||||
return dump_tool_result({"success": True, "scope": _serialize(scope)})
|
||||
return json.dumps(
|
||||
{"success": True, "scope": _serialize(scope)}, ensure_ascii=False, default=str
|
||||
)
|
||||
if action == "create":
|
||||
if not scope_name:
|
||||
return dump_tool_result(
|
||||
return json.dumps(
|
||||
{"success": False, "error": "scope_name required for create"},
|
||||
ensure_ascii=False,
|
||||
default=str,
|
||||
)
|
||||
scope = await client.scope.create(
|
||||
CreateScopeOptions(
|
||||
@@ -510,14 +547,18 @@ async def scope_rules(
|
||||
denylist=list(denylist or []),
|
||||
),
|
||||
)
|
||||
return dump_tool_result({"success": True, "scope": _serialize(scope)})
|
||||
return json.dumps(
|
||||
{"success": True, "scope": _serialize(scope)}, ensure_ascii=False, default=str
|
||||
)
|
||||
if action == "update":
|
||||
if not scope_id or not scope_name:
|
||||
return dump_tool_result(
|
||||
return json.dumps(
|
||||
{
|
||||
"success": False,
|
||||
"error": "scope_id and scope_name required for update",
|
||||
},
|
||||
ensure_ascii=False,
|
||||
default=str,
|
||||
)
|
||||
scope = await client.scope.update(
|
||||
scope_id,
|
||||
@@ -527,16 +568,24 @@ async def scope_rules(
|
||||
denylist=list(denylist or []),
|
||||
),
|
||||
)
|
||||
return dump_tool_result({"success": True, "scope": _serialize(scope)})
|
||||
return json.dumps(
|
||||
{"success": True, "scope": _serialize(scope)}, ensure_ascii=False, default=str
|
||||
)
|
||||
# action == "delete" — exhaustive Literal
|
||||
if not scope_id:
|
||||
return dump_tool_result(
|
||||
return json.dumps(
|
||||
{"success": False, "error": "scope_id required for delete"},
|
||||
ensure_ascii=False,
|
||||
default=str,
|
||||
)
|
||||
await client.scope.delete(scope_id)
|
||||
return dump_tool_result({"success": True, "deleted": scope_id})
|
||||
return json.dumps({"success": True, "deleted": scope_id}, ensure_ascii=False, default=str)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
return dump_tool_result({"success": False, "error": f"scope_rules failed: {exc}"})
|
||||
return json.dumps(
|
||||
{"success": False, "error": f"scope_rules failed: {exc}"},
|
||||
ensure_ascii=False,
|
||||
default=str,
|
||||
)
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
@@ -670,7 +719,7 @@ async def _replay_send(
|
||||
"raw": response_raw.decode("utf-8", errors="replace"),
|
||||
}
|
||||
|
||||
return dump_tool_result(
|
||||
return json.dumps(
|
||||
{
|
||||
"success": result.status == "DONE",
|
||||
"status": result.status,
|
||||
@@ -679,4 +728,6 @@ async def _replay_send(
|
||||
"elapsed_ms": elapsed_ms,
|
||||
"response": response,
|
||||
},
|
||||
ensure_ascii=False,
|
||||
default=str,
|
||||
)
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
from .tool import create_vulnerability_report
|
||||
|
||||
|
||||
__all__ = ["create_vulnerability_report"]
|
||||
|
||||
@@ -9,9 +9,7 @@ import re
|
||||
from pathlib import PurePosixPath
|
||||
from typing import Any
|
||||
|
||||
from agents import RunContextWrapper
|
||||
|
||||
from strix.tools._decorator import strix_tool
|
||||
from agents import RunContextWrapper, function_tool
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -293,7 +291,7 @@ def _do_create( # noqa: PLR0912
|
||||
# large scans can have many existing reports to compare against.
|
||||
# strict_mode=False because cvss_breakdown is a dict[str, str] and
|
||||
# code_locations is list[dict] — both free-form for the strict schema.
|
||||
@strix_tool(timeout=180, strict_mode=False)
|
||||
@function_tool(timeout=180, strict_mode=False)
|
||||
async def create_vulnerability_report(
|
||||
ctx: RunContextWrapper,
|
||||
title: str,
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
from .tool import think
|
||||
|
||||
|
||||
__all__ = ["think"]
|
||||
|
||||
@@ -4,10 +4,10 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
from strix.tools._decorator import strix_tool
|
||||
from agents import function_tool
|
||||
|
||||
|
||||
@strix_tool(timeout=10)
|
||||
@function_tool(timeout=10)
|
||||
async def think(thought: str) -> str:
|
||||
"""Record a private chain-of-thought note. No side effects, no new info.
|
||||
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
from .tools import (
|
||||
create_todo,
|
||||
delete_todo,
|
||||
list_todos,
|
||||
mark_todo_done,
|
||||
mark_todo_pending,
|
||||
update_todo,
|
||||
)
|
||||
|
||||
|
||||
__all__ = [
|
||||
"create_todo",
|
||||
"delete_todo",
|
||||
"list_todos",
|
||||
"mark_todo_done",
|
||||
"mark_todo_pending",
|
||||
"update_todo",
|
||||
]
|
||||
|
||||
+37
-25
@@ -13,9 +13,7 @@ import uuid
|
||||
from datetime import UTC, datetime
|
||||
from typing import Any
|
||||
|
||||
from agents import RunContextWrapper
|
||||
|
||||
from strix.tools._decorator import dump_tool_result, strix_tool
|
||||
from agents import RunContextWrapper, function_tool
|
||||
|
||||
|
||||
VALID_PRIORITIES = ["low", "normal", "high", "critical"]
|
||||
@@ -198,7 +196,7 @@ def _apply_single_update(
|
||||
# --- public tools ---------------------------------------------------------
|
||||
|
||||
|
||||
@strix_tool(timeout=30)
|
||||
@function_tool(timeout=30)
|
||||
async def create_todo(
|
||||
ctx: RunContextWrapper,
|
||||
title: str | None = None,
|
||||
@@ -248,12 +246,14 @@ async def create_todo(
|
||||
},
|
||||
)
|
||||
if not tasks:
|
||||
return dump_tool_result(
|
||||
return json.dumps(
|
||||
{
|
||||
"success": False,
|
||||
"error": "Provide a title or 'todos' list to create.",
|
||||
"todo_id": None,
|
||||
},
|
||||
ensure_ascii=False,
|
||||
default=str,
|
||||
)
|
||||
|
||||
agent_todos = _get_agent_todos(agent_id)
|
||||
@@ -273,11 +273,13 @@ async def create_todo(
|
||||
}
|
||||
created.append({"todo_id": todo_id, "title": task["title"], "priority": task_priority})
|
||||
except (ValueError, TypeError) as e:
|
||||
return dump_tool_result(
|
||||
{"success": False, "error": f"Failed to create todo: {e}", "todo_id": None}
|
||||
return json.dumps(
|
||||
{"success": False, "error": f"Failed to create todo: {e}", "todo_id": None},
|
||||
ensure_ascii=False,
|
||||
default=str,
|
||||
)
|
||||
|
||||
return dump_tool_result(
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"created": created,
|
||||
@@ -285,10 +287,12 @@ async def create_todo(
|
||||
"todos": _sorted_todos(agent_id),
|
||||
"total_count": len(_get_agent_todos(agent_id)),
|
||||
},
|
||||
ensure_ascii=False,
|
||||
default=str,
|
||||
)
|
||||
|
||||
|
||||
@strix_tool(timeout=30)
|
||||
@function_tool(timeout=30)
|
||||
async def list_todos(
|
||||
ctx: RunContextWrapper,
|
||||
status: str | None = None,
|
||||
@@ -327,7 +331,7 @@ async def list_todos(
|
||||
sv = todo.get("status", "pending")
|
||||
summary[sv] = summary.get(sv, 0) + 1
|
||||
except (ValueError, TypeError) as e:
|
||||
return dump_tool_result(
|
||||
return json.dumps(
|
||||
{
|
||||
"success": False,
|
||||
"error": f"Failed to list todos: {e}",
|
||||
@@ -335,19 +339,23 @@ async def list_todos(
|
||||
"total_count": 0,
|
||||
"summary": {"pending": 0, "in_progress": 0, "done": 0},
|
||||
},
|
||||
ensure_ascii=False,
|
||||
default=str,
|
||||
)
|
||||
|
||||
return dump_tool_result(
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
"todos": todos_list,
|
||||
"total_count": len(todos_list),
|
||||
"summary": summary,
|
||||
},
|
||||
ensure_ascii=False,
|
||||
default=str,
|
||||
)
|
||||
|
||||
|
||||
@strix_tool(timeout=30)
|
||||
@function_tool(timeout=30)
|
||||
async def update_todo(
|
||||
ctx: RunContextWrapper,
|
||||
todo_id: str | None = None,
|
||||
@@ -387,8 +395,10 @@ async def update_todo(
|
||||
},
|
||||
)
|
||||
if not updates_to_apply:
|
||||
return dump_tool_result(
|
||||
return json.dumps(
|
||||
{"success": False, "error": "Provide todo_id or 'updates' list to update."},
|
||||
ensure_ascii=False,
|
||||
default=str,
|
||||
)
|
||||
|
||||
updated: list[str] = []
|
||||
@@ -407,7 +417,7 @@ async def update_todo(
|
||||
else:
|
||||
updated.append(upd["todo_id"])
|
||||
except (ValueError, TypeError) as e:
|
||||
return dump_tool_result({"success": False, "error": str(e)})
|
||||
return json.dumps({"success": False, "error": str(e)}, ensure_ascii=False, default=str)
|
||||
|
||||
response: dict[str, Any] = {
|
||||
"success": len(errors) == 0,
|
||||
@@ -418,7 +428,7 @@ async def update_todo(
|
||||
}
|
||||
if errors:
|
||||
response["errors"] = errors
|
||||
return dump_tool_result(response)
|
||||
return json.dumps(response, ensure_ascii=False, default=str)
|
||||
|
||||
|
||||
def _mark(
|
||||
@@ -437,7 +447,7 @@ def _mark(
|
||||
ids.append(todo_id)
|
||||
if not ids:
|
||||
msg = f"Provide todo_id or todo_ids to mark as {new_status}."
|
||||
return dump_tool_result({"success": False, "error": msg})
|
||||
return json.dumps({"success": False, "error": msg}, ensure_ascii=False, default=str)
|
||||
|
||||
marked: list[str] = []
|
||||
errors: list[dict[str, Any]] = []
|
||||
@@ -452,7 +462,7 @@ def _mark(
|
||||
todo["updated_at"] = timestamp
|
||||
marked.append(tid)
|
||||
except (ValueError, TypeError) as e:
|
||||
return dump_tool_result({"success": False, "error": str(e)})
|
||||
return json.dumps({"success": False, "error": str(e)}, ensure_ascii=False, default=str)
|
||||
|
||||
key = "marked_done" if new_status == "done" else "marked_pending"
|
||||
response: dict[str, Any] = {
|
||||
@@ -464,10 +474,10 @@ def _mark(
|
||||
}
|
||||
if errors:
|
||||
response["errors"] = errors
|
||||
return dump_tool_result(response)
|
||||
return json.dumps(response, ensure_ascii=False, default=str)
|
||||
|
||||
|
||||
@strix_tool(timeout=30)
|
||||
@function_tool(timeout=30)
|
||||
async def mark_todo_done(
|
||||
ctx: RunContextWrapper,
|
||||
todo_id: str | None = None,
|
||||
@@ -488,7 +498,7 @@ async def mark_todo_done(
|
||||
)
|
||||
|
||||
|
||||
@strix_tool(timeout=30)
|
||||
@function_tool(timeout=30)
|
||||
async def mark_todo_pending(
|
||||
ctx: RunContextWrapper,
|
||||
todo_id: str | None = None,
|
||||
@@ -508,7 +518,7 @@ async def mark_todo_pending(
|
||||
)
|
||||
|
||||
|
||||
@strix_tool(timeout=30)
|
||||
@function_tool(timeout=30)
|
||||
async def delete_todo(
|
||||
ctx: RunContextWrapper,
|
||||
todo_id: str | None = None,
|
||||
@@ -529,8 +539,10 @@ async def delete_todo(
|
||||
if todo_id is not None:
|
||||
ids.append(todo_id)
|
||||
if not ids:
|
||||
return dump_tool_result(
|
||||
{"success": False, "error": "Provide todo_id or todo_ids to delete."}
|
||||
return json.dumps(
|
||||
{"success": False, "error": "Provide todo_id or todo_ids to delete."},
|
||||
ensure_ascii=False,
|
||||
default=str,
|
||||
)
|
||||
|
||||
deleted: list[str] = []
|
||||
@@ -542,7 +554,7 @@ async def delete_todo(
|
||||
del agent_todos[tid]
|
||||
deleted.append(tid)
|
||||
except (ValueError, TypeError) as e:
|
||||
return dump_tool_result({"success": False, "error": str(e)})
|
||||
return json.dumps({"success": False, "error": str(e)}, ensure_ascii=False, default=str)
|
||||
|
||||
response: dict[str, Any] = {
|
||||
"success": len(errors) == 0,
|
||||
@@ -553,4 +565,4 @@ async def delete_todo(
|
||||
}
|
||||
if errors:
|
||||
response["errors"] = errors
|
||||
return dump_tool_result(response)
|
||||
return json.dumps(response, ensure_ascii=False, default=str)
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
from .tool import web_search
|
||||
|
||||
|
||||
__all__ = ["web_search"]
|
||||
|
||||
@@ -8,9 +8,7 @@ import os
|
||||
from typing import Any
|
||||
|
||||
import requests
|
||||
from agents import RunContextWrapper
|
||||
|
||||
from strix.tools._decorator import strix_tool
|
||||
from agents import RunContextWrapper, function_tool
|
||||
|
||||
|
||||
_SYSTEM_PROMPT = """You are assisting a cybersecurity agent specialized in vulnerability scanning
|
||||
@@ -84,7 +82,7 @@ def _do_search(query: str) -> dict[str, Any]:
|
||||
|
||||
# Perplexity request timeout is 300s; give the SDK a slightly larger
|
||||
# budget so the round-trip + JSON decode doesn't push us over.
|
||||
@strix_tool(timeout=330)
|
||||
@function_tool(timeout=330)
|
||||
async def web_search(ctx: RunContextWrapper, query: str) -> str:
|
||||
"""Real-time web search via Perplexity — your primary research tool.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user