package services import ( "encoding/json" "strings" "testing" ) // TestAgentMessageRawRoundTrip confirma que un campo que el proveedor manda // y que nuestra struct no conoce (ej. thought_signature de Gemini) sobrevive // a un ciclo unmarshal->marshal en vez de perderse — es justo lo que hacía // que Gemini rechazara la segunda ronda de una tool call. func TestAgentMessageRawRoundTrip(t *testing.T) { original := `{"role":"assistant","content":null,"tool_calls":[{"id":"call_1","type":"function","function":{"name":"buscar_conocimiento","arguments":"{}"},"thought_signature":"opaco-123"}]}` var msg agentMessage if err := json.Unmarshal([]byte(original), &msg); err != nil { t.Fatalf("unmarshal: %v", err) } if len(msg.ToolCalls) != 1 || msg.ToolCalls[0].Function.Name != "buscar_conocimiento" { t.Fatalf("no se poblaron los campos tipados normalmente: %+v", msg) } out, err := json.Marshal(msg) if err != nil { t.Fatalf("marshal: %v", err) } if !strings.Contains(string(out), `"thought_signature":"opaco-123"`) { t.Errorf("se perdió el campo desconocido al reserializar: %s", out) } // Un mensaje armado por nosotros (no parseado) no debe llevar Raw ni // romperse por eso — tiene que serializar normal. propio := agentMessage{Role: "user", Content: "hola"} outPropio, err := json.Marshal(propio) if err != nil { t.Fatalf("marshal de mensaje propio: %v", err) } if !strings.Contains(string(outPropio), `"role":"user"`) || !strings.Contains(string(outPropio), `"content":"hola"`) { t.Errorf("mensaje propio no serializó bien: %s", outPropio) } }