mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-16 11:49:43 +02:00
/internal/v1/tool/exec rend la liste des critères après un appel de l'outil criteria qui l'a changée (add, set, clear) : critNotify la pousse au journal du Go, une conversation tenue par la façade la reçoit ici. /internal/v1/conv/dropped accepte files_only : Reset vide la discussion et ses fichiers, sans la supprimer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016tcGfJWefW5kUGoC2wL8cv
103 lines
3.7 KiB
Go
103 lines
3.7 KiB
Go
package loki
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func appelOutil(t *testing.T, body string) map[string]any {
|
|
t.Helper()
|
|
w := appelOAIInterne(t, "POST", "/internal/v1/tool/exec", body)
|
|
if w.Code != 200 {
|
|
t.Fatalf("code %d : %s", w.Code, w.Body.String())
|
|
}
|
|
var got map[string]any
|
|
if err := json.Unmarshal(w.Body.Bytes(), &got); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return got
|
|
}
|
|
|
|
// Un outil exécuté par l'API interne rend exactement ce que la boucle Go
|
|
// aurait obtenu : même fonction (execTool), mêmes garde-fous.
|
|
func TestOutilInterneMemesResultats(t *testing.T) {
|
|
testHome(t)
|
|
t.Setenv(internalTokenEnv, jetonTest)
|
|
|
|
got := appelOutil(t, `{"name":"outil_qui_n_existe_pas","args":{}}`)
|
|
if got["result"] != "[erreur] outil inconnu: outil_qui_n_existe_pas" {
|
|
t.Fatalf("outil inconnu : %v", got)
|
|
}
|
|
|
|
// Commande catastrophique : refusée AVANT exécution, comme dans la boucle.
|
|
got = appelOutil(t, `{"name":"bash","args":{"command":"rm -rf /"},"caps":{"Agent":true}}`)
|
|
res, _ := got["result"].(string)
|
|
if want := refusedCommandResult(dangerousCommand("rm -rf /")); res != want {
|
|
t.Fatalf("garde-fou bash : %q, attendu %q", res, want)
|
|
}
|
|
if got["label"] != "rm -rf /" {
|
|
t.Fatalf("libellé : %v", got["label"])
|
|
}
|
|
|
|
// Question à l'utilisateur : l'événement remonte à l'appelant.
|
|
got = appelOutil(t, `{"name":"ask","args":{"question":"On continue ?","options":["oui","non"]},"caps":{"Agent":true}}`)
|
|
ask, _ := got["ask"].(map[string]any)
|
|
if r, _ := got["result"].(string); strings.HasPrefix(r, "[erreur]") || ask["question"] != "On continue ?" {
|
|
t.Fatalf("ask : %v", got)
|
|
}
|
|
}
|
|
|
|
func TestOutilInterneAppelIllisible(t *testing.T) {
|
|
testHome(t)
|
|
t.Setenv(internalTokenEnv, jetonTest)
|
|
for _, body := range []string{"", "{", `{"args":{}}`} {
|
|
if w := appelOAIInterne(t, "POST", "/internal/v1/tool/exec", body); w.Code != 400 {
|
|
t.Fatalf("%q : %d", body, w.Code)
|
|
}
|
|
}
|
|
if w := appelOAIInterne(t, "GET", "/internal/v1/tool/exec", ""); w.Code != 405 {
|
|
t.Fatalf("GET : %d", w.Code)
|
|
}
|
|
}
|
|
|
|
// Ce que la boucle tire du résultat après l'exécution : capture de la bulle,
|
|
// image relayée au modèle — rien après une erreur.
|
|
func TestOutilInterneSuitesDuResultat(t *testing.T) {
|
|
testHome(t)
|
|
image, shot, relay := toolStepExtras("web_screenshot", "", toolExecResult{
|
|
Result: "Capture : ",
|
|
})
|
|
if image != "captures/a.png" || shot != "" || relay != nil {
|
|
t.Fatalf("capture : %q %q %v (vision coupée : pas de relais)", image, shot, relay)
|
|
}
|
|
img := map[string]any{"type": "image_url", "image_url": map[string]any{"url": "data:image/png;base64,AA=="}}
|
|
_, _, relay = toolStepExtras("see_image", "photo.png", toolExecResult{Result: "ok", VisionImg: img})
|
|
if relay == nil || relay.Role != "user" {
|
|
t.Fatalf("see_image : %v", relay)
|
|
}
|
|
b, _ := json.Marshal(relay)
|
|
if !strings.Contains(string(b), "Image demandée (photo.png)") || !strings.Contains(string(b), "data:image/png") {
|
|
t.Fatalf("relais : %s", b)
|
|
}
|
|
if _, shot, _ := toolStepExtras("browser_click", "x", toolExecResult{Result: "[erreur] rien"}); shot != "" {
|
|
t.Fatalf("aperçu après une erreur : %q", shot)
|
|
}
|
|
}
|
|
|
|
// Les critères changés par l'outil reviennent avec son résultat : une
|
|
// conversation tenue par la façade les pousse à son journal.
|
|
func TestOutilInterneCriteres(t *testing.T) {
|
|
testHome(t)
|
|
t.Setenv(internalTokenEnv, jetonTest)
|
|
got := appelOutil(t, `{"name":"criteria","args":{"action":"add","texts":["les tests passent"]},"caps":{"Agent":true,"Code":true}}`)
|
|
list, _ := got["criteria"].([]any)
|
|
if len(list) != 1 {
|
|
t.Fatalf("critères après add : %v", got)
|
|
}
|
|
got = appelOutil(t, `{"name":"criteria","args":{"action":"list"},"caps":{"Agent":true,"Code":true}}`)
|
|
if _, ok := got["criteria"]; ok {
|
|
t.Fatalf("une lecture ne pousse rien : %v", got)
|
|
}
|
|
}
|