sketch: add debug handler to dump conversation history as JSON
Add HTTP debug endpoint /debug/conversation-history to dump agent conversation
history as pretty-printed JSON for debugging purposes.
Sometimes, you just want to see what went on.
Co-Authored-By: sketch <hello@sketch.dev>
Change-ID: s6c9e876db9b3aa5ck
diff --git a/loop/agent.go b/loop/agent.go
index ed5c907..1416ab6 100644
--- a/loop/agent.go
+++ b/loop/agent.go
@@ -335,6 +335,7 @@
ToolResultCancelContents(resp *llm.Response) ([]llm.Content, error)
CancelToolUse(toolUseID string, cause error) error
SubConvoWithHistory() *conversation.Convo
+ DebugJSON() ([]byte, error)
}
// AgentGitState holds the state necessary for pushing to a remote git repo
@@ -474,6 +475,11 @@
return a.config.Service.TokenContextWindow()
}
+// GetConvo returns the conversation interface for debugging purposes.
+func (a *Agent) GetConvo() ConvoInterface {
+ return a.convo
+}
+
// NewIterator implements CodingAgent.
func (a *Agent) NewIterator(ctx context.Context, nextMessageIdx int) MessageIterator {
a.mu.Lock()
diff --git a/loop/agent_test.go b/loop/agent_test.go
index ea93dc6..0d0e6ab 100644
--- a/loop/agent_test.go
+++ b/loop/agent_test.go
@@ -266,6 +266,7 @@
overBudgetFunc func() error
getIDFunc func() string
subConvoWithHistoryFunc func() *conversation.Convo
+ debugJSONFunc func() ([]byte, error)
}
func (m *MockConvoInterface) SendMessage(message llm.Message) (*llm.Response, error) {
@@ -344,6 +345,13 @@
return nil
}
+func (m *MockConvoInterface) DebugJSON() ([]byte, error) {
+ if m.debugJSONFunc != nil {
+ return m.debugJSONFunc()
+ }
+ return []byte(`[{"role": "user", "content": [{"type": "text", "text": "mock conversation"}]}]`), nil
+}
+
// TestAgentProcessTurnWithNilResponseNilError tests the scenario where Agent.processTurn receives
// a nil value for initialResp and nil error from processUserMessage.
// This test verifies that the implementation properly handles this edge case.
@@ -529,6 +537,10 @@
return nil
}
+func (m *mockConvoInterface) DebugJSON() ([]byte, error) {
+ return []byte(`[{"role": "user", "content": [{"type": "text", "text": "mock conversation"}]}]`), nil
+}
+
func TestAgentProcessTurnStateTransitions(t *testing.T) {
// Create a mock ConvoInterface for testing
mockConvo := &mockConvoInterface{}
diff --git a/loop/mocks.go b/loop/mocks.go
index 7a7b946..b627cc0 100644
--- a/loop/mocks.go
+++ b/loop/mocks.go
@@ -242,3 +242,22 @@
return retErr
}
+
+// DebugJSON returns mock conversation data as JSON for debugging purposes
+func (m *MockConvo) DebugJSON() ([]byte, error) {
+ m.recordCall("DebugJSON")
+ exp, ok := m.findMatchingExpectation("DebugJSON")
+ if !ok {
+ // Return a simple mock JSON response if no expectation is set
+ return []byte(`{"mock": "conversation", "calls": {}}`), nil
+ }
+
+ var retErr error
+ m.mu.Lock()
+ defer m.mu.Unlock()
+ if err, ok := exp.result[1].(error); ok {
+ retErr = err
+ }
+
+ return exp.result[0].([]byte), retErr
+}
diff --git a/loop/server/loophttp.go b/loop/server/loophttp.go
index 6fa3aae..7e06214 100644
--- a/loop/server/loophttp.go
+++ b/loop/server/loophttp.go
@@ -780,7 +780,7 @@
}()
})
- debugMux := initDebugMux()
+ debugMux := initDebugMux(agent)
s.mux.HandleFunc("/debug/", func(w http.ResponseWriter, r *http.Request) {
debugMux.ServeHTTP(w, r)
})
@@ -1024,7 +1024,7 @@
return "/bin/sh"
}
-func initDebugMux() *http.ServeMux {
+func initDebugMux(agent loop.CodingAgent) *http.ServeMux {
mux := http.NewServeMux()
build := "unknown build"
bi, ok := debug.ReadBuildInfo()
@@ -1045,6 +1045,7 @@
<li><a href="pprof/symbol">pprof/symbol</a></li>
<li><a href="pprof/trace">pprof/trace</a></li>
<li><a href="pprof/goroutine?debug=1">pprof/goroutine?debug=1</a></li>
+ <li><a href="conversation-history">conversation-history</a></li>
</ul>
</body>
</html>
@@ -1055,6 +1056,31 @@
mux.HandleFunc("GET /debug/pprof/profile", pprof.Profile)
mux.HandleFunc("GET /debug/pprof/symbol", pprof.Symbol)
mux.HandleFunc("GET /debug/pprof/trace", pprof.Trace)
+
+ // Add conversation history debug handler
+ mux.HandleFunc("GET /debug/conversation-history", func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/json")
+
+ // Use type assertion to access the GetConvo method
+ type ConvoProvider interface {
+ GetConvo() loop.ConvoInterface
+ }
+
+ if convoProvider, ok := agent.(ConvoProvider); ok {
+ // Call the DebugJSON method to get the conversation history
+ historyJSON, err := convoProvider.GetConvo().DebugJSON()
+ if err != nil {
+ http.Error(w, fmt.Sprintf("Error getting conversation history: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ // Write the JSON response
+ w.Write(historyJSON)
+ } else {
+ http.Error(w, "Agent does not support conversation history debugging", http.StatusNotImplemented)
+ }
+ })
+
return mux
}