File size: 1,939 Bytes
272a052
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
"""Shared helpers for Phase A2 tool implementations.

- ``deterministic_rng(seed, step, tool_name)``: reproducible RNG seeded from the
  episode seed, the step index, and the tool name. Use in any handler that
  needs randomness (e.g. log line shuffling) so the same episode always
  produces the same outputs.

- ``args_hash(args)``: short stable hash of an args dict, useful for keying
  tool-call repeats (the redundancy-penalty in the reward layer interprets
  identical hashes as the same call).

- ``SchemaValidatedHandler``: ``ToolHandler`` mixin that validates ``args`` via
  ``jsonschema`` against the frozen ``MCPToolDef.args_schema`` from
  ``schemas/tools.py``. Concrete handlers implement ``call``.
"""

from __future__ import annotations

import hashlib
import json
import random
from typing import ClassVar

import jsonschema

from ci_triage_env.env.tools.base import ToolHandler
from ci_triage_env.schemas.tools import ALL_TOOLS

_TOOL_DEFS = {t.name: t for t in ALL_TOOLS}


def deterministic_rng(seed: int, step: int, tool_name: str) -> random.Random:
    h = hashlib.sha256(f"{seed}:{step}:{tool_name}".encode()).digest()
    return random.Random(int.from_bytes(h[:8], "big"))


def args_hash(args: dict) -> str:
    return hashlib.sha1(
        json.dumps(args or {}, sort_keys=True, default=str).encode()
    ).hexdigest()[:12]


class SchemaValidatedHandler(ToolHandler):
    """Concrete-handler base that validates args against the MCP schema."""

    name: ClassVar[str] = ""
    cost_unit: ClassVar[float] = 0.0

    def validate_args(self, args: dict) -> None:
        spec = _TOOL_DEFS.get(self.name)
        if spec is None:
            raise ValueError(f"unknown tool: {self.name}")
        try:
            jsonschema.validate(instance=args or {}, schema=spec.args_schema)
        except jsonschema.ValidationError as exc:
            raise ValueError(f"invalid args for {self.name}: {exc.message}") from exc