forked from HAL9000/cleveragents-core
cd9cb9e889
Thread plan_env and project_env through the tool execution chain so the ExecutionEnvironmentResolver receives project-level execution environment values stored in ContextConfig.execution_environment. The resolver's precedence logic (tool > plan > project > default) was already correct, but callers never passed project_env — tools always fell through to the global HOST default, ignoring project-level overrides entirely. Changes: - PlanExecutionContext: add plan_env and project_env fields + properties - ToolCallRouter: accept plan_env/project_env in constructor, pass to runner.execute() in route() and route_streaming() - ToolCallingRuntime: accept plan_env/project_env, pass to runner.execute() in the direct-runner fallback path - Update monkey-patched execute stubs in tool_router_steps.py to accept explicit plan_env/project_env keyword arguments - Add 4 BDD scenarios proving plan_env/project_env reach the resolver via ToolCallRouter (project-only, plan-only, both, neither) Dependency: requires PR #1135 to be merged first (adds CLI flag and persistence for project-level execution-env-priority). Closes #1080 ISSUES CLOSED: #1080
130 lines
4.3 KiB
Python
130 lines
4.3 KiB
Python
"""Step definitions for exec_env_project_override.feature.
|
|
|
|
Verifies that plan_env and project_env are threaded from
|
|
ToolCallRouter through to the ExecutionEnvironmentResolver
|
|
inside ToolRunner.execute().
|
|
|
|
Issue #1080.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from behave import given, then, when
|
|
from behave.runner import Context
|
|
|
|
from cleveragents.tool.registry import ToolRegistry
|
|
from cleveragents.tool.router import ToolCallRouter
|
|
from cleveragents.tool.runner import ToolRunner
|
|
from cleveragents.tool.runtime import ToolSpec
|
|
|
|
__all__: list[str] = []
|
|
|
|
|
|
def _echo_tool(arguments: dict[str, Any]) -> dict[str, Any]:
|
|
"""Simple echo tool for testing."""
|
|
return {"echoed": arguments}
|
|
|
|
|
|
@given("ee1080- a tool registry with a registered echo tool")
|
|
def step_ee1080_registry(context: Context) -> None:
|
|
context.ee1080_registry = ToolRegistry()
|
|
spec = ToolSpec(
|
|
name="test/echo",
|
|
description="Echo tool for testing",
|
|
input_schema={"type": "object", "properties": {}},
|
|
handler=_echo_tool,
|
|
)
|
|
context.ee1080_registry.register(spec)
|
|
|
|
|
|
@given("ee1080- a tool runner and resolver spy")
|
|
def step_ee1080_runner(context: Context) -> None:
|
|
context.ee1080_runner = ToolRunner(context.ee1080_registry)
|
|
# Spy on _env_resolver.resolve to capture what it receives
|
|
context.ee1080_resolve_calls: list[dict[str, Any]] = [] # type: ignore[annotation-unchecked]
|
|
original_rv = context.ee1080_runner._env_resolver.resolve_and_validate
|
|
|
|
def spy_resolve_and_validate(**kwargs: Any) -> tuple[str, str | None]:
|
|
context.ee1080_resolve_calls.append(
|
|
{
|
|
"plan_env": kwargs.get("plan_env"),
|
|
"project_env": kwargs.get("project_env"),
|
|
}
|
|
)
|
|
return original_rv(**kwargs)
|
|
|
|
context.ee1080_runner._env_resolver.resolve_and_validate = spy_resolve_and_validate # type: ignore[method-assign]
|
|
|
|
|
|
@given('ee1080- a ToolCallRouter with project_env "{env}"')
|
|
def step_ee1080_router_project(context: Context, env: str) -> None:
|
|
context.ee1080_router = ToolCallRouter(
|
|
registry=context.ee1080_registry,
|
|
runner=context.ee1080_runner,
|
|
plan_id="ee1080-plan",
|
|
project_env=env,
|
|
)
|
|
|
|
|
|
@given('ee1080- a ToolCallRouter with plan_env "{env}"')
|
|
def step_ee1080_router_plan(context: Context, env: str) -> None:
|
|
context.ee1080_router = ToolCallRouter(
|
|
registry=context.ee1080_registry,
|
|
runner=context.ee1080_runner,
|
|
plan_id="ee1080-plan",
|
|
plan_env=env,
|
|
)
|
|
|
|
|
|
@given(
|
|
'ee1080- a ToolCallRouter with both plan_env "{plan}" and project_env "{project}"'
|
|
)
|
|
def step_ee1080_router_both(context: Context, plan: str, project: str) -> None:
|
|
context.ee1080_router = ToolCallRouter(
|
|
registry=context.ee1080_registry,
|
|
runner=context.ee1080_runner,
|
|
plan_id="ee1080-plan",
|
|
plan_env=plan,
|
|
project_env=project,
|
|
)
|
|
|
|
|
|
@given("ee1080- a ToolCallRouter without env params")
|
|
def step_ee1080_router_none(context: Context) -> None:
|
|
context.ee1080_router = ToolCallRouter(
|
|
registry=context.ee1080_registry,
|
|
runner=context.ee1080_runner,
|
|
plan_id="ee1080-plan",
|
|
)
|
|
|
|
|
|
@when("ee1080- I route a tool call through the router")
|
|
def step_ee1080_route(context: Context) -> None:
|
|
payload = {
|
|
"name": "test/echo",
|
|
"arguments": {"msg": "hello"},
|
|
}
|
|
context.ee1080_result = context.ee1080_router.route(payload)
|
|
|
|
|
|
@then('ee1080- the resolver should have received project_env "{expected}"')
|
|
def step_ee1080_check_project_env(context: Context, expected: str) -> None:
|
|
expected_val = None if expected == "None" else expected
|
|
calls = context.ee1080_resolve_calls
|
|
assert len(calls) > 0, "Resolver was never called"
|
|
actual = calls[-1]["project_env"]
|
|
assert actual == expected_val, (
|
|
f"Expected project_env={expected_val!r}, got {actual!r}"
|
|
)
|
|
|
|
|
|
@then('ee1080- the resolver should have received plan_env "{expected}"')
|
|
def step_ee1080_check_plan_env(context: Context, expected: str) -> None:
|
|
expected_val = None if expected == "None" else expected
|
|
calls = context.ee1080_resolve_calls
|
|
assert len(calls) > 0, "Resolver was never called"
|
|
actual = calls[-1]["plan_env"]
|
|
assert actual == expected_val, f"Expected plan_env={expected_val!r}, got {actual!r}"
|