114 lines
3.8 KiB
Python
114 lines
3.8 KiB
Python
"""Tests for ``_inject_mcp_schemas`` — the proxy_stream merge helper.
|
|
|
|
The runner injects MCP tool schemas into the harness request body
|
|
right before forwarding (designs/RUNNER_MCP.md §Schema injection).
|
|
The merge must append, not replace, so builtins / client-side tools
|
|
from the Omnigent server survive. A future refactor that swaps ``+`` for
|
|
``=`` would silently clobber those tools — these tests fail loudly
|
|
on that regression.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from omnigent.runner.app import _inject_mcp_schemas
|
|
|
|
|
|
def _schema(name: str) -> dict[str, Any]:
|
|
"""OpenAI function-tool schema dict for use in expected/actual."""
|
|
return {
|
|
"type": "function",
|
|
"name": name,
|
|
"description": "",
|
|
"parameters": {"type": "object", "properties": {}},
|
|
}
|
|
|
|
|
|
def test_inject_appends_after_existing_tools() -> None:
|
|
"""MCP schemas land AFTER the Omnigent server's tools, in order.
|
|
|
|
Order matters: the harness's tool list defines a deterministic
|
|
iteration order downstream; flipping it could change which tool
|
|
wins for ambiguous-prompt cases.
|
|
"""
|
|
body = {"tools": [_schema("sys_os_read"), _schema("sys_os_write")]}
|
|
mcp = [_schema("jira_search_issues"), _schema("jira_get_issue")]
|
|
|
|
_inject_mcp_schemas(body, mcp)
|
|
|
|
assert [t["name"] for t in body["tools"]] == [
|
|
"sys_os_read",
|
|
"sys_os_write",
|
|
"jira_search_issues",
|
|
"jira_get_issue",
|
|
]
|
|
|
|
|
|
def test_inject_creates_tools_when_missing() -> None:
|
|
"""A body with no ``tools`` key gets one populated from the MCP list."""
|
|
body: dict[str, Any] = {}
|
|
mcp = [_schema("jira_search_issues")]
|
|
|
|
_inject_mcp_schemas(body, mcp)
|
|
|
|
assert body["tools"] == mcp
|
|
|
|
|
|
def test_inject_treats_none_tools_as_empty() -> None:
|
|
"""``tools: None`` (vs missing) is also handled — the ``or []`` guard."""
|
|
body: dict[str, Any] = {"tools": None}
|
|
mcp = [_schema("jira_search_issues")]
|
|
|
|
_inject_mcp_schemas(body, mcp)
|
|
|
|
assert body["tools"] == mcp
|
|
|
|
|
|
def test_inject_noop_on_empty_mcp_schemas() -> None:
|
|
"""Empty MCP list leaves the body's tools list untouched (not even rewritten)."""
|
|
body = {"tools": [_schema("sys_os_read")]}
|
|
original_tools_id = id(body["tools"])
|
|
|
|
_inject_mcp_schemas(body, [])
|
|
|
|
assert body["tools"] == [_schema("sys_os_read")]
|
|
# Same list object: nothing copied/reassigned. Guards against a
|
|
# future refactor that always replaces the list, dropping any
|
|
# downstream references.
|
|
assert id(body["tools"]) == original_tools_id
|
|
|
|
|
|
def test_inject_does_not_share_list_with_caller() -> None:
|
|
"""The result list is independent of the mcp_schemas argument.
|
|
|
|
A later mutation to the mcp_schemas list (e.g. eviction-driven
|
|
cache rebuild) must not retroactively edit the in-flight request.
|
|
"""
|
|
body: dict[str, Any] = {"tools": [_schema("a")]}
|
|
mcp = [_schema("jira_search_issues")]
|
|
|
|
_inject_mcp_schemas(body, mcp)
|
|
# Mutate the source after injection.
|
|
mcp.append(_schema("jira_get_issue"))
|
|
|
|
assert [t["name"] for t in body["tools"]] == ["a", "jira_search_issues"], (
|
|
"in-flight request must not see post-injection mutation of mcp_schemas"
|
|
)
|
|
|
|
|
|
def test_inject_skips_mcp_already_present() -> None:
|
|
"""An MCP schema already in ``body["tools"]`` is not appended again.
|
|
|
|
Regression: the per-session tool cache already includes MCP
|
|
schemas, so re-injecting them sent duplicate tool names that codex rejects.
|
|
"""
|
|
body = {"tools": [_schema("sys_os_read"), _schema("confluence_get_service_info")]}
|
|
mcp = [_schema("confluence_get_service_info"), _schema("confluence_search_pages")]
|
|
|
|
_inject_mcp_schemas(body, mcp)
|
|
|
|
names = [t["name"] for t in body["tools"]]
|
|
assert names == ["sys_os_read", "confluence_get_service_info", "confluence_search_pages"]
|
|
assert names.count("confluence_get_service_info") == 1
|