"""从损坏的 tool_call arguments 里抢救完好 JSON 的纯逻辑。

单独成模块(不 import litellm)是刻意的:core/loop.py 顶层 import litellm 在部分
开发机导入期会拉 GitHub cost map 卡死(见 memory),使 import core.loop 无法跑;而
本机制的正确性盲区(线上真垃圾前缀本机复现不出)恰恰只能靠单测兜底。把纯函数放这里,
tests 可无 litellm 依赖直接 import 校验守卫逻辑。

定层结论(见 project_deepseek_malformed_toolcall_salvage / DESIGN):畸形 arguments 是
provider 在 wire 上把别处正文字节错标成本 tool_call 的 arguments delta,粘在**开头**
(char-0 垃圾前缀),尾部真 JSON 完好并跑到串尾。
"""
from __future__ import annotations

import json
from typing import Optional


def salvage_tool_arguments(raw: str, allowed_keys: set) -> Optional[dict]:
    """从损坏的 arguments 里抢救出完好的 JSON 对象;抢不出返回 None。

    左→右扫每个 `{`,试 `json.loads(raw[i:])`——第一个满足三条的即采纳:
      (a) parse-to-end 成功(json.loads 要求整个后缀是合法 JSON,尾部有残渣就失败);
      (b) 结果是非空 dict;
      (c) 顶层 key ⊆ 该工具 schema 的参数名。
    双护栏缺一不可:(a) 挡尾部被截断的半个 JSON(截断则拒绝、回落既有重试,不冒险执行
    残缺写入);(c) 挡垃圾前缀里恰好自洽的旁支 JSON(如模型正文里引用的 `{...}` 片段)。
    都不满足返回 None。纯函数、无副作用,便于单测(线上真前缀样本当夹具)。
    """
    # 只可能出现在 '{' 处;非 '{' 直接跳过,省下大部分 json.loads 尝试。
    start = 0
    while True:
        i = raw.find("{", start)
        if i < 0:
            return None
        try:
            obj = json.loads(raw[i:])
        except (json.JSONDecodeError, ValueError):
            start = i + 1
            continue
        if isinstance(obj, dict) and obj and set(obj.keys()) <= allowed_keys:
            return obj
        # 解析成功但不是想要的 dict(如前缀里一段自洽 JSON):继续往后找,真 JSON 可能在更后面。
        start = i + 1
