File size: 5,331 Bytes
2ffc435
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9e8fbdf
 
 
 
 
 
 
 
 
 
2ffc435
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ef2eee3
 
 
 
f695ef3
ef2eee3
 
2ffc435
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ef2eee3
 
2ffc435
 
 
ef2eee3
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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
"""Soumission automatique au LLMDrops JDM (Phase 12).

Poste un fichier de soumission consolidé au endpoint LLMDrops de JDM,
sous un nom standardisé qui trace quel LLM l'a produit et quand. Conçu
pour être appelé depuis `write_submission_file` ou directement.

Configuration (cascade) :
  - api_key      : arg `api_key` → env `JDM_DROPS_API_KEY`
  - endpoint_url : arg `endpoint_url` → env `JDM_DROPS_URL` → défaut
  - model_name   : arg `model_name` → env `LLM_MODEL` → "mcp_client"

Le fichier est posté sous `compute_submission_filename(model_name)`,
indépendant du nom local sur disque (le LLMDrops voit toujours un nom
auto-descriptif type `from_claude-sonnet-4-7_automatic_submission_14h32_27-05-26.enrich`).
"""
from __future__ import annotations

import os
from pathlib import Path
from typing import Optional

import httpx

# Charge le .env du projet pour que `JDM_DROPS_API_KEY`, `JDM_DROPS_URL` et
# `LLM_MODEL` soient disponibles même si l'appelant n'a pas importé d'autre
# composant qui charge dotenv (cas typique : `python -c "from jdm_agent.enrich
# import submit_to_jdm; ..."`).
try:
    from dotenv import load_dotenv  # type: ignore
    load_dotenv(override=False)
except Exception:
    pass


DEFAULT_ENDPOINT_URL = "http://jeuxdemots.org/LLMDrops.php"


def submit_to_jdm(
    path: str | Path,
    *,
    api_key: Optional[str] = None,
    model_name: Optional[str] = None,
    endpoint_url: Optional[str] = None,
    timeout: float = 30.0,
) -> dict:
    """POST le fichier de soumission au endpoint LLMDrops.

    Args:
        path: chemin local du fichier `.enrich` consolidé à uploader.
        api_key: clé API (override env `JDM_DROPS_API_KEY` si fournie).
        model_name: nom du LLM source (override env `LLM_MODEL`, fallback "mcp_client").
        endpoint_url: URL du endpoint (override env `JDM_DROPS_URL`, fallback défaut).
        timeout: timeout HTTP en secondes.

    Returns:
        {"ok": bool, "status_code": int, "response": dict | str,
         "uploaded_as": str, "endpoint": str, "error": str | None}

        Ne lève jamais d'exception réseau / HTTP — toute erreur est captée
        et reportée dans `error` avec `ok=False`. L'appelant décide quoi
        en faire.
    """
    # Préparation du fichier
    p = Path(path)
    if not p.exists():
        return {
            "ok": False, "status_code": 0,
            "response": None, "uploaded_as": "",
            "endpoint": endpoint_url or "",
            "error": f"Fichier introuvable : {p}",
        }

    # Résolution des paramètres (cascade)
    key = api_key or os.environ.get("JDM_DROPS_API_KEY") or ""
    url = endpoint_url or os.environ.get("JDM_DROPS_URL") or DEFAULT_ENDPOINT_URL
    resolved_model = model_name or os.environ.get("LLM_MODEL") or "mcp_client"
    # Extension dérivée du fichier source — préserve .enrich / .audit / .err
    # pour que le mainteneur JDM voie immédiatement le type du drop.
    # Fallback .enrich si le fichier n'a pas d'extension reconnue.
    src_ext = p.suffix.lower()
    if src_ext not in (".enrich", ".audit", ".err", ".stat"):
        src_ext = ".enrich"
    uploaded_name = compute_submission_filename(resolved_model, extension=src_ext)

    if not key:
        return {
            "ok": False, "status_code": 0,
            "response": None, "uploaded_as": uploaded_name,
            "endpoint": url,
            "error": (
                "Clé API manquante : passe `api_key=...` ou définis "
                "`JDM_DROPS_API_KEY` dans l'env. Le fichier local a été "
                "écrit mais l'upload a été sauté."
            ),
        }

    headers = {"X-API-Key": key}
    data = {"format": "json"}

    try:
        with p.open("rb") as f:
            files = {"file": (uploaded_name, f, "text/plain")}
            with httpx.Client(timeout=timeout) as client:
                resp = client.post(url, headers=headers, data=data, files=files)
    except httpx.HTTPError as e:
        return {
            "ok": False, "status_code": 0,
            "response": None, "uploaded_as": uploaded_name,
            "endpoint": url,
            "error": f"Erreur réseau / HTTP : {e}",
        }
    except OSError as e:
        return {
            "ok": False, "status_code": 0,
            "response": None, "uploaded_as": uploaded_name,
            "endpoint": url,
            "error": f"Erreur d'I/O sur le fichier : {e}",
        }

    # Parse la réponse — JSON si possible, brut sinon
    try:
        body: object = resp.json()
    except Exception:
        body = resp.text

    ok = 200 <= resp.status_code < 300
    return {
        "ok": ok,
        "status_code": resp.status_code,
        "response": body,
        "uploaded_as": uploaded_name,
        "endpoint": url,
        "error": None if ok else f"HTTP {resp.status_code}",
    }


# Re-export pour ergonomie depuis ce module
def compute_submission_filename(model_name: str, *, now=None,
                                extension: str = ".enrich") -> str:
    """Délègue à `jdm_agent.enrich.pipeline.compute_submission_filename`."""
    # Import local pour éviter une dépendance circulaire au load time
    from jdm_agent.enrich.pipeline import compute_submission_filename as _impl
    return _impl(model_name, now=now, extension=extension)