@@ -1908,3 +1908,48 @@ func TestEngine_InteractionModeDefault_ProducesRenderOutput(t *testing.T) {
19081908 }
19091909}
19101910
1911+
1912+ // ── Bug #8: Tool goroutines lack panic recovery ─────────────────────────
1913+ // If a tool.Call() panics, the agent should not crash.
1914+
1915+ type panicTool struct {
1916+ name string
1917+ }
1918+
1919+ func (p * panicTool ) Name () string { return p .name }
1920+ func (p * panicTool ) Description () string { return "panics on call" }
1921+ func (p * panicTool ) Schema () any { return map [string ]any {"type" : "object" } }
1922+ func (p * panicTool ) Call (args string ) (string , error ) {
1923+ panic ("deliberate panic from tool" )
1924+ }
1925+
1926+ // TestToolPanic_DoesNotKillAgent verifies that a panicking tool call
1927+ // does not crash the agent. The agent should recover and continue.
1928+ func TestToolPanic_DoesNotKillAgent (t * testing.T ) {
1929+ server := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
1930+ var body struct {
1931+ Messages []llm.Message `json:"messages"`
1932+ }
1933+ if err := json .NewDecoder (r .Body ).Decode (& body ); err != nil {
1934+ t .Fatal (err )
1935+ }
1936+ // First call: return tool calls with panic tool
1937+ if len (body .Messages ) == 2 {
1938+ fmt .Fprint (w , `{"choices":[{"index":0,"message":{"role":"assistant","content":"","tool_calls":[{"id":"call_1","type":"function","function":{"name":"panic_tool","arguments":"{}"}}]},"finish_reason":"tool_calls"}]}` )
1939+ return
1940+ }
1941+ // Second call (after tool panic): return content
1942+ fmt .Fprint (w , `{"choices":[{"index":0,"message":{"role":"assistant","content":"Agent survived the panic!"},"finish_reason":"stop"}]}` )
1943+ }))
1944+ defer server .Close ()
1945+
1946+ client := llm .New (server .URL , "sk-test" , "test-model" , "" , 0 )
1947+ engine := New (client , tool .NewRegistry ([]tool.Tool {& panicTool {name : "panic_tool" }}), 10 , "" , nil , 0 )
1948+ result , err := engine .Run (context .Background (), "test task" )
1949+ if err != nil {
1950+ t .Fatalf ("engine.Run should not crash on tool panic: %v" , err )
1951+ }
1952+ if ! strings .Contains (result , "Agent survived" ) {
1953+ t .Errorf ("agent result = %q, want 'Agent survived'" , result )
1954+ }
1955+ }
0 commit comments