// clean_history must never hand a provider a tool result whose tool call was not issued: devplace answers // that with HTTP 400 "No function call found for function call output with call_id ..." and the whole turn // fails (everythingwrong.md; reproduced against the live endpoint with an orphan result). #include #include "helpers.h" #include "json.h" using detz::json::Value; static int g_fail = 0; #define CHECK(cond) do { if (!(cond)) { g_fail++; std::printf("FAIL %s:%d %s\n", __FILE__, __LINE__, #cond); } } while (0) static Value msg(const char* role, const char* content) { Value m = Value::object(); m.set("role", role); m.set("content", content); return m; } static Value assistant_calling(const char* id) { Value fn = Value::object(); fn.set("name", "fork"); fn.set("arguments", "{}"); Value call = Value::object(); call.set("id", id); call.set("type", "function"); call.set("function", fn); Value calls = Value::array(); calls.push(call); Value m = Value::object(); m.set("role", "assistant"); m.set("content", Value()); m.set("tool_calls", calls); return m; } static Value tool_result(const char* id) { Value m = msg("tool", "done"); m.set("tool_call_id", id); return m; } int main() { // a window that starts on a tool result (compaction cut its assistant message away) Value cut = Value::array(); cut.push(msg("system", "s")); cut.push(tool_result("call_orphan")); cut.push(msg("user", "go")); cut.push(assistant_calling("call_a")); cut.push(tool_result("call_a")); Value cleaned = detz::llm::drop_orphan_tool_results(detz::llm::clean_history(cut)); CHECK(cleaned.size() == 4); bool orphan_left = false; for (const auto& m : cleaned.items()) { if (m.get("role").str() == "tool" && m.get("tool_call_id").str() == "call_orphan") orphan_left = true; } CHECK(!orphan_left); // a healthy exchange is untouched Value ok = Value::array(); ok.push(msg("user", "go")); ok.push(assistant_calling("call_b")); ok.push(tool_result("call_b")); CHECK(detz::llm::drop_orphan_tool_results(detz::llm::clean_history(ok)).size() == 3); // clean_history itself stays byte-for-byte tai.py behaviour: it keeps the orphan (agent_diff.py compares it) CHECK(detz::llm::clean_history(cut).size() == 5); std::printf(g_fail ? "llm_history_test: %d FAILED\n" : "llm_history_test: ok\n", g_fail); return g_fail ? 1 : 0; }