Initial project import
This commit is contained in:
@@ -10,6 +10,7 @@ RUN pip install --no-cache-dir pyyaml==6.0.2
|
||||
COPY scripts /app/scripts
|
||||
COPY plugins/1c /app/plugins/1c
|
||||
COPY config /app/config
|
||||
COPY core /app/core
|
||||
COPY registry /app/registry
|
||||
|
||||
EXPOSE 8090
|
||||
|
||||
@@ -1003,6 +1003,12 @@ ADAPTER_BASE_ID_REQUIRED_PREFIXES = (
|
||||
"storage.",
|
||||
"templates.",
|
||||
)
|
||||
AGENT_FORBIDDEN_TECHNICAL_SELECTOR_FIELDS = {
|
||||
"table", "file_name", "file_names", "module_ref", "module_id", "stream_index",
|
||||
"bsl_offset", "cas_key", "storage_key", "include_storage", "guid", "object_guid",
|
||||
"form_guid", "extension_guid",
|
||||
}
|
||||
AGENT_CONFIGURATION_METHOD_PREFIXES = ("metadata.", "modules.", "code.", "templates.", "extension.")
|
||||
|
||||
|
||||
def adapter_method_requires_base_id(method: str) -> bool:
|
||||
@@ -1018,29 +1024,99 @@ def validate_adapter_call(method: str, params: dict[str, Any] | None) -> None:
|
||||
if adapter_method_requires_base_id(method) and not str(params.get("base_id") or "").strip():
|
||||
raise ValueError(f"adapter method {method} requires params.base_id")
|
||||
|
||||
|
||||
def agent_technical_selector_fields(value: Any) -> list[str]:
|
||||
"""Reject storage coordinates even when a caller nests them in JSON."""
|
||||
found: set[str] = set()
|
||||
if isinstance(value, dict):
|
||||
for key, nested in value.items():
|
||||
if key in AGENT_FORBIDDEN_TECHNICAL_SELECTOR_FIELDS:
|
||||
found.add(key)
|
||||
found.update(agent_technical_selector_fields(nested))
|
||||
elif isinstance(value, list):
|
||||
for nested in value:
|
||||
found.update(agent_technical_selector_fields(nested))
|
||||
return sorted(found)
|
||||
|
||||
|
||||
def prepare_agent_adapter_call(method: str, params: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Keep the agent on public metadata selectors rather than SQL routes."""
|
||||
prepared = dict(params)
|
||||
diagnostic_allowed = str(os.environ.get("ONEC_AGENT_ALLOW_DIAGNOSTIC") or "").strip().casefold() in {"1", "true", "yes", "on"}
|
||||
technical = agent_technical_selector_fields(prepared)
|
||||
if technical and not diagnostic_allowed:
|
||||
raise ValueError(
|
||||
"agent adapter calls require public names/selectors; forbidden technical fields: " + ", ".join(technical)
|
||||
)
|
||||
if method.startswith(AGENT_CONFIGURATION_METHOD_PREFIXES):
|
||||
prepared.setdefault("configuration_view", "effective_working")
|
||||
prepared.setdefault("source_state", "working")
|
||||
return prepared
|
||||
|
||||
def call_adapter(method: str, params: dict[str, Any] | None, *, base_url: str | None = None) -> dict[str, Any]:
|
||||
params = params or {}
|
||||
"""Call the adapter through its public MCP boundary, never its SQL REST surface."""
|
||||
params = prepare_agent_adapter_call(method, params or {})
|
||||
validate_adapter_call(method, params)
|
||||
adapter_url = normalize_base_url(base_url or os.environ.get("ONEC_ADAPTER_URL", "http://docker-gpu.cin.su:8011"))
|
||||
headers = {"Content-Type": "application/json"}
|
||||
token = os.environ.get("ONEC_ADAPTER_TOKEN", "").strip()
|
||||
if token:
|
||||
headers["Authorization"] = f"Bearer {token}"
|
||||
request = Request(
|
||||
f"{adapter_url}/rpc",
|
||||
data=json.dumps({"method": method, "payload": params}, ensure_ascii=False).encode("utf-8"),
|
||||
mcp_url = normalize_base_url(base_url or os.environ.get("ONEC_MCP_URL", "http://docker.cin.su:8021"))
|
||||
headers = {"Content-Type": "application/json", "Accept": "application/json, text/event-stream"}
|
||||
initialize = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": f"onec-agent-init-{uuid.uuid4().hex}",
|
||||
"method": "initialize",
|
||||
"params": {
|
||||
"protocolVersion": "2025-06-18",
|
||||
"capabilities": {},
|
||||
"clientInfo": {"name": "onec-agent", "version": "1"},
|
||||
},
|
||||
}
|
||||
init_request = Request(
|
||||
f"{mcp_url}/mcp",
|
||||
data=json.dumps(initialize, ensure_ascii=False).encode("utf-8"),
|
||||
headers=headers,
|
||||
method="POST",
|
||||
)
|
||||
try:
|
||||
with urlopen(init_request, timeout=30) as response:
|
||||
init_raw = json.loads(response.read().decode("utf-8"))
|
||||
session_id = response.headers.get("Mcp-Session-Id")
|
||||
except urllib.error.HTTPError as exc:
|
||||
body = exc.read().decode("utf-8", errors="replace")
|
||||
raise ValueError(f"MCP initialize returned HTTP {exc.code}: {body}") from exc
|
||||
if not isinstance(init_raw, dict) or not isinstance(init_raw.get("result"), dict):
|
||||
raise ValueError("MCP initialize response is not JSON-RPC success")
|
||||
call_headers = dict(headers)
|
||||
if session_id:
|
||||
call_headers["Mcp-Session-Id"] = session_id
|
||||
call = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": f"onec-agent-call-{uuid.uuid4().hex}",
|
||||
"method": "tools/call",
|
||||
"params": {"name": "onec_request", "arguments": {"method": method, "payload": params}},
|
||||
}
|
||||
request = Request(
|
||||
f"{mcp_url}/mcp",
|
||||
data=json.dumps(call, ensure_ascii=False).encode("utf-8"),
|
||||
headers=call_headers,
|
||||
method="POST",
|
||||
)
|
||||
try:
|
||||
with urlopen(request, timeout=120) as response:
|
||||
raw = json.loads(response.read().decode("utf-8"))
|
||||
except urllib.error.HTTPError as exc:
|
||||
body = exc.read().decode("utf-8", errors="replace")
|
||||
raise ValueError(f"adapter returned HTTP {exc.code}: {body}") from exc
|
||||
if not isinstance(raw, dict):
|
||||
raise ValueError("adapter response is not JSON")
|
||||
return raw
|
||||
raise ValueError(f"MCP tool call returned HTTP {exc.code}: {body}") from exc
|
||||
result = raw.get("result") if isinstance(raw, dict) and isinstance(raw.get("result"), dict) else None
|
||||
content = result.get("content") if isinstance(result, dict) and isinstance(result.get("content"), list) else []
|
||||
text = content[0].get("text") if content and isinstance(content[0], dict) else None
|
||||
if not isinstance(text, str):
|
||||
raise ValueError("MCP tool response has no JSON text content")
|
||||
try:
|
||||
decoded = json.loads(text)
|
||||
except json.JSONDecodeError as exc:
|
||||
raise ValueError("MCP tool response text is not JSON") from exc
|
||||
if not isinstance(decoded, dict):
|
||||
raise ValueError("MCP tool response payload is not an object")
|
||||
return decoded
|
||||
|
||||
|
||||
class AgentHandler(BaseHTTPRequestHandler):
|
||||
|
||||
Reference in New Issue
Block a user