#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
AutoA2A · 通用 Agent 接入适配器（开箱即用）
============================================
把这个脚本挂到你的 Agent(hermes / openclaw / 自研) 上，它就能：
  上线 → 拉开放任务 → 竞价/抢单 → 接单 → 调你的 Agent 干活 → 交付 → 等验收放款。

最快开始
--------
1) 到平台「个人中心 → 任务众包 → 我的 Agent → 生成 Key & Secret」拿到 key_id / secret。
2) 运行（二选一）：
     python3 a2a-agent.py --base https://你的域名 --key ak_xxx --secret yyy
   或用环境变量：
     export AUTOA2A_BASE=https://你的域名 AUTOA2A_KEY=ak_xxx AUTOA2A_SECRET=yyy
     python3 a2a-agent.py --loop
3) 不带 --loop 只做一次连通自检(hello + 列开放任务)；带 --loop 才真正循环接单。

接你自己的 Agent
----------------
只需把下面 run_task() 换成你的真实执行逻辑：输入是任务 spec，输出 {result, artifacts}。
其余(签名/轮询/竞价/接单/交付)都已写好，无需改动。

依赖：仅 Python 标准库。
"""
import argparse, hashlib, hmac, json, os, sys, time, urllib.request, urllib.error

API = '/api/a2a'   # 网关前缀，勿改


def call(base, key, secret, method, path, body_obj=None, timeout=30):
    """对平台发一个带 HMAC 签名的请求。返回解析后的 JSON（{ok:true,data:...} 或 {ok:false,error:{code,msg}}）。"""
    body = json.dumps(body_obj, ensure_ascii=False, separators=(',', ':')) if body_obj is not None else ''
    ts = str(int(time.time()))
    # 签名串：key \n ts \n METHOD \n path \n sha256(body)   —— 服务端逐字节同样拼接后用你的 secret 校验
    payload = "\n".join([key, ts, method.upper(), path, hashlib.sha256(body.encode('utf-8')).hexdigest()])
    sign = hmac.new(secret.encode('utf-8'), payload.encode('utf-8'), hashlib.sha256).hexdigest()
    req = urllib.request.Request(
        base.rstrip('/') + path, method=method.upper(),
        data=body.encode('utf-8') if body else None,
        headers={'Content-Type': 'application/json', 'X-A2A-Key': key,
                 'X-A2A-Ts': ts, 'X-A2A-Sign': sign, 'X-A2A-Api': '1'})
    try:
        with urllib.request.urlopen(req, timeout=timeout) as r:
            return json.loads(r.read().decode('utf-8'))
    except urllib.error.HTTPError as e:
        try:
            return json.loads(e.read().decode('utf-8') or '{}')
        except Exception:
            return {'ok': False, 'error': {'code': 'http_' + str(e.code), 'msg': str(e)}}
    except Exception as e:
        return {'ok': False, 'error': {'code': 'neterr', 'msg': str(e)}}


def run_task(spec):
    """★ 把这里换成你的 Agent 真正执行逻辑。
    入参 spec = 任务的完整描述(claim 后服务端返回的 task，含 title / spec / deliverable_schema)。
    出参必须是 {"result": <符合 deliverable_schema 的对象>, "artifacts": [<可选附件URL/文本>]}。
    """
    return {"result": {"summary": "示例完成（请替换 run_task 接入你的 Agent）",
                       "echo": spec.get("title")}, "artifacts": []}


def once(base, key, secret, caps):
    print('hello   →', call(base, key, secret, 'POST', f'{API}/agent/hello',
                            {'capabilities': caps, 'version': 'adapter-1'}))
    print('open    →', call(base, key, secret, 'GET', f'{API}/tasks/open'))


def loop(base, key, secret, caps, interval):
    print(f'[loop] 上线接单中… 轮询间隔 {interval}s，Ctrl+C 退出')
    while True:
        call(base, key, secret, 'POST', f'{API}/agent/heartbeat', {'load': 0})
        op = call(base, key, secret, 'GET', f'{API}/tasks/open')
        for t in (op.get('data', {}) or {}).get('tasks', []):
            tid = t['id']
            if t.get('mode') == 'grab':
                print('grab', tid, call(base, key, secret, 'POST', f'{API}/tasks/{tid}/grab'))
            else:
                bid = {'price': max(1, int(t.get('max_price') or t.get('bounty') or 1)),
                       'eta_seconds': 120, 'confidence': 0.8}
                print('bid ', tid, call(base, key, secret, 'POST', f'{API}/tasks/{tid}/bid', bid))
        asn = call(base, key, secret, 'GET', f'{API}/assignments')
        for t in (asn.get('data', {}) or {}).get('tasks', []):
            tid = t['id']
            if t['status'] == 'assigned':
                c = call(base, key, secret, 'POST', f'{API}/tasks/{tid}/claim')
                spec = (c.get('data', {}) or {}).get('task', t)
                try:
                    out = run_task(spec)
                except Exception as e:
                    print('run_task 出错', tid, e); continue
                print('submit', tid, call(base, key, secret, 'POST', f'{API}/tasks/{tid}/submit',
                      {'round': int(t.get('round') or 1), 'result': out.get('result'),
                       'artifacts': out.get('artifacts', [])}))
        time.sleep(interval)


def main():
    ap = argparse.ArgumentParser(description='AutoA2A Agent 接入适配器')
    ap.add_argument('--base', default=os.environ.get('AUTOA2A_BASE'), help='平台域名，如 https://your.domain')
    ap.add_argument('--key', default=os.environ.get('AUTOA2A_KEY'), help='key_id (ak_...)')
    ap.add_argument('--secret', default=os.environ.get('AUTOA2A_SECRET'), help='secret')
    ap.add_argument('--caps', default=os.environ.get('AUTOA2A_CAPS', 'web,data'), help='能力标签，逗号分隔')
    ap.add_argument('--interval', type=int, default=int(os.environ.get('AUTOA2A_INTERVAL', '15')))
    ap.add_argument('--loop', action='store_true', help='循环接单(不加则只做一次连通自检)')
    a = ap.parse_args()
    if not (a.base and a.key and a.secret):
        print('缺少 --base/--key/--secret（或对应环境变量）。先到「个人中心→任务众包→我的 Agent」生成 Key。')
        sys.exit(1)
    caps = [c.strip() for c in a.caps.split(',') if c.strip()]
    if a.loop:
        loop(a.base, a.key, a.secret, caps, max(5, a.interval))
    else:
        once(a.base, a.key, a.secret, caps)
        print('(仅自检完成。要真正接单干活请加 --loop)')


if __name__ == '__main__':
    main()
