on main help sign in
Merge branch 'public_main' into config-provider-changes
commit6e25625f878a34c6f2b40c6cb13a51669b2d3bdd authorKujtim Hoxha <kujtimii.h@gmail.com> dateTue 1 Jul 2025, 12:05 (1 year ago) parent290f1f4f0571f5
6 files changed+167−9
internal/config/config.go+9 internal/fsext/fileutil.go+1−1 internal/llm/agent/agent.go+20−5 internal/llm/agent/mcp-tools.go+5−1 internal/logging/logger.go+131−2 internal/logging/writer.go+1
internal/config/config.go+9
@@ -223,6 +223,15 @@ func loadConfig(cwd string, debug bool) (*Config, error) {
223223 }
224224 }
225225
226+ messagesPath := fmt.Sprintf("%s/%s", cfg.Options.DataDirectory, "messages")
227+
228+ if _, err := os.Stat(messagesPath); os.IsNotExist(err) {
229+ if err := os.MkdirAll(messagesPath, 0o756); err != nil {
230+ return cfg, fmt.Errorf("failed to create directory: %w", err)
231+ }
232+ }
233+ logging.MessageDir = messagesPath
234+
226235 sloggingFileWriter, err := os.OpenFile(loggingFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o666)
227236 if err != nil {
228237 return cfg, fmt.Errorf("failed to open log file: %w", err)
internal/fsext/fileutil.go+1−1
@@ -55,7 +55,7 @@ func GetRgSearchCmd(pattern, path, include string) *exec.Cmd {
5555 return nil
5656 }
5757 // Use -n to show line numbers and include the matched line
58− args := []string{"-n", pattern}
58+ args := []string{"-H", "-n", pattern}
5959 if include != "" {
6060 args = append(args, "--glob", include)
6161 }
internal/llm/agent/agent.go+20−5
@@ -363,6 +363,7 @@ func (a *agent) Run(ctx context.Context, sessionID string, content string, attac
363363}
364364
365365func (a *agent) processGeneration(ctx context.Context, sessionID, content string, attachmentParts []message.ContentPart) AgentEvent {
366+ cfg := config.Get()
366367 // List existing messages; if none, start title generation asynchronously.
367368 msgs, err := a.messages.List(ctx, sessionID)
368369 if err != nil {
@@ -421,7 +422,13 @@ func (a *agent) processGeneration(ctx context.Context, sessionID, content string
421422 }
422423 return a.err(fmt.Errorf("failed to process events: %w", err))
423424 }
424− logging.Info("Result", "message", agentMessage.FinishReason(), "toolResults", toolResults)
425+ if cfg.Options.Debug {
426+ seqId := (len(msgHistory) + 1) / 2
427+ toolResultFilepath := logging.WriteToolResultsJson(sessionID, seqId, toolResults)
428+ logging.Info("Result", "message", agentMessage.FinishReason(), "toolResults", "{}", "filepath", toolResultFilepath)
429+ } else {
430+ logging.Info("Result", "message", agentMessage.FinishReason(), "toolResults", toolResults)
431+ }
425432 if (agentMessage.FinishReason() == message.FinishReasonToolUse) && toolResults != nil {
426433 // We are not done, we need to respond with the tool response
427434 msgHistory = append(msgHistory, agentMessage, *toolResults)
@@ -445,6 +452,7 @@ func (a *agent) createUserMessage(ctx context.Context, sessionID, content string
445452}
446453
447454func (a *agent) streamAndHandleEvents(ctx context.Context, sessionID string, msgHistory []message.Message) (message.Message, *message.Message, error) {
455+ ctx = context.WithValue(ctx, tools.SessionIDContextKey, sessionID)
448456 eventChan := a.provider.StreamResponse(ctx, msgHistory, a.tools)
449457
450458 assistantMsg, err := a.messages.Create(ctx, sessionID, message.CreateMessageParams{
@@ -459,7 +467,6 @@ func (a *agent) streamAndHandleEvents(ctx context.Context, sessionID string, msg
459467
460468 // Add the session and message ID into the context if needed by tools.
461469 ctx = context.WithValue(ctx, tools.MessageIDContextKey, assistantMsg.ID)
462− ctx = context.WithValue(ctx, tools.SessionIDContextKey, sessionID)
463470
464471 // Process each event in the stream.
465472 for event := range eventChan {
@@ -491,10 +498,17 @@ func (a *agent) streamAndHandleEvents(ctx context.Context, sessionID string, msg
491498 default:
492499 // Continue processing
493500 var tool tools.BaseTool
494− for _, availableTools := range a.tools {
495− if availableTools.Info().Name == toolCall.Name {
496− tool = availableTools
501+ for _, availableTool := range a.tools {
502+ if availableTool.Info().Name == toolCall.Name {
503+ tool = availableTool
504+ break
497505 }
506+ // Monkey patch for Copilot Sonnet-4 tool repetition obfuscation
507+ // if strings.HasPrefix(toolCall.Name, availableTool.Info().Name) &&
508+ // strings.HasPrefix(toolCall.Name, availableTool.Info().Name+availableTool.Info().Name) {
509+ // tool = availableTool
510+ // break
511+ // }
498512 }
499513
500514 // Tool not found
@@ -665,6 +679,7 @@ func (a *agent) Summarize(ctx context.Context, sessionID string) error {
665679 a.Publish(pubsub.CreatedEvent, event)
666680 return
667681 }
682+ summarizeCtx = context.WithValue(summarizeCtx, tools.SessionIDContextKey, sessionID)
668683
669684 if len(msgs) == 0 {
670685 event = AgentEvent{
internal/llm/agent/mcp-tools.go+5−1
@@ -37,11 +37,15 @@ func (b *mcpTool) Name() string {
3737}
3838
3939func (b *mcpTool) Info() tools.ToolInfo {
40+ required := b.tool.InputSchema.Required
41+ if required == nil {
42+ required = make([]string, 0)
43+ }
4044 return tools.ToolInfo{
4145 Name: fmt.Sprintf("%s_%s", b.mcpName, b.tool.Name),
4246 Description: b.tool.Description,
4347 Parameters: b.tool.InputSchema.Properties,
44− Required: b.tool.InputSchema.Required,
48+ Required: required,
4549 }
4650}
4751
internal/logging/logger.go+131−2
@@ -4,16 +4,33 @@ import (
44 "fmt"
55 "log/slog"
66 "os"
7+ // "path/filepath"
8+ "encoding/json"
9+ "runtime"
710 "runtime/debug"
11+ "sync"
812 "time"
913)
1014
15+func getCaller() string {
16+ var caller string
17+ if _, file, line, ok := runtime.Caller(2); ok {
18+ // caller = fmt.Sprintf("%s:%d", filepath.Base(file), line)
19+ caller = fmt.Sprintf("%s:%d", file, line)
20+ } else {
21+ caller = "unknown"
22+ }
23+ return caller
24+}
1125func Info(msg string, args ...any) {
12− slog.Info(msg, args...)
26+ source := getCaller()
27+ slog.Info(msg, append([]any{"source", source}, args...)...)
1328}
1429
1530func Debug(msg string, args ...any) {
16− slog.Debug(msg, args...)
31+ // slog.Debug(msg, args...)
32+ source := getCaller()
33+ slog.Debug(msg, append([]any{"source", source}, args...)...)
1734}
1835
1936func Warn(msg string, args ...any) {
@@ -76,3 +93,115 @@ func RecoverPanic(name string, cleanup func()) {
7693 }
7794 }
7895}
96+
97+// Message Logging for Debug
98+var MessageDir string
99+
100+func GetSessionPrefix(sessionId string) string {
101+ return sessionId[:8]
102+}
103+
104+var sessionLogMutex sync.Mutex
105+
106+func AppendToSessionLogFile(sessionId string, filename string, content string) string {
107+ if MessageDir == "" || sessionId == "" {
108+ return ""
109+ }
110+ sessionPrefix := GetSessionPrefix(sessionId)
111+
112+ sessionLogMutex.Lock()
113+ defer sessionLogMutex.Unlock()
114+
115+ sessionPath := fmt.Sprintf("%s/%s", MessageDir, sessionPrefix)
116+ if _, err := os.Stat(sessionPath); os.IsNotExist(err) {
117+ if err := os.MkdirAll(sessionPath, 0o766); err != nil {
118+ Error("Failed to create session directory", "dirpath", sessionPath, "error", err)
119+ return ""
120+ }
121+ }
122+
123+ filePath := fmt.Sprintf("%s/%s", sessionPath, filename)
124+
125+ f, err := os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
126+ if err != nil {
127+ Error("Failed to open session log file", "filepath", filePath, "error", err)
128+ return ""
129+ }
130+ defer f.Close()
131+
132+ // Append chunk to file
133+ _, err = f.WriteString(content)
134+ if err != nil {
135+ Error("Failed to write chunk to session log file", "filepath", filePath, "error", err)
136+ return ""
137+ }
138+ return filePath
139+}
140+
141+func WriteRequestMessageJson(sessionId string, requestSeqId int, message any) string {
142+ if MessageDir == "" || sessionId == "" || requestSeqId <= 0 {
143+ return ""
144+ }
145+ msgJson, err := json.Marshal(message)
146+ if err != nil {
147+ Error("Failed to marshal message", "session_id", sessionId, "request_seq_id", requestSeqId, "error", err)
148+ return ""
149+ }
150+ return WriteRequestMessage(sessionId, requestSeqId, string(msgJson))
151+}
152+
153+func WriteRequestMessage(sessionId string, requestSeqId int, message string) string {
154+ if MessageDir == "" || sessionId == "" || requestSeqId <= 0 {
155+ return ""
156+ }
157+ filename := fmt.Sprintf("%d_request.json", requestSeqId)
158+
159+ return AppendToSessionLogFile(sessionId, filename, message)
160+}
161+
162+func AppendToStreamSessionLogJson(sessionId string, requestSeqId int, jsonableChunk any) string {
163+ if MessageDir == "" || sessionId == "" || requestSeqId <= 0 {
164+ return ""
165+ }
166+ chunkJson, err := json.Marshal(jsonableChunk)
167+ if err != nil {
168+ Error("Failed to marshal message", "session_id", sessionId, "request_seq_id", requestSeqId, "error", err)
169+ return ""
170+ }
171+ return AppendToStreamSessionLog(sessionId, requestSeqId, string(chunkJson))
172+}
173+
174+func AppendToStreamSessionLog(sessionId string, requestSeqId int, chunk string) string {
175+ if MessageDir == "" || sessionId == "" || requestSeqId <= 0 {
176+ return ""
177+ }
178+ filename := fmt.Sprintf("%d_response_stream.log", requestSeqId)
179+ return AppendToSessionLogFile(sessionId, filename, chunk)
180+}
181+
182+func WriteChatResponseJson(sessionId string, requestSeqId int, response any) string {
183+ if MessageDir == "" || sessionId == "" || requestSeqId <= 0 {
184+ return ""
185+ }
186+ responseJson, err := json.Marshal(response)
187+ if err != nil {
188+ Error("Failed to marshal response", "session_id", sessionId, "request_seq_id", requestSeqId, "error", err)
189+ return ""
190+ }
191+ filename := fmt.Sprintf("%d_response.json", requestSeqId)
192+
193+ return AppendToSessionLogFile(sessionId, filename, string(responseJson))
194+}
195+
196+func WriteToolResultsJson(sessionId string, requestSeqId int, toolResults any) string {
197+ if MessageDir == "" || sessionId == "" || requestSeqId <= 0 {
198+ return ""
199+ }
200+ toolResultsJson, err := json.Marshal(toolResults)
201+ if err != nil {
202+ Error("Failed to marshal tool results", "session_id", sessionId, "request_seq_id", requestSeqId, "error", err)
203+ return ""
204+ }
205+ filename := fmt.Sprintf("%d_tool_results.json", requestSeqId)
206+ return AppendToSessionLogFile(sessionId, filename, string(toolResultsJson))
207+}
internal/logging/writer.go+1
@@ -45,6 +45,7 @@ type writer struct{}
4545
4646func (w *writer) Write(p []byte) (int, error) {
4747 d := logfmt.NewDecoder(bytes.NewReader(p))
48+
4849 for d.ScanRecord() {
4950 msg := LogMessage{
5051 ID: fmt.Sprintf("%d", time.Now().UnixNano()),