on main help sign in
Merge branch 'public_main' into config-provider-changes
6e25625Kujtim Hoxha
internal/config/config.go+9
@@ -223,6 +223,15 @@ func loadConfig(cwd string, debug bool) (*Config, error) {
223 }
223 }
224 }
224 }
225
225
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+
226 sloggingFileWriter, err := os.OpenFile(loggingFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o666)
235 sloggingFileWriter, err := os.OpenFile(loggingFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o666)
227 if err != nil {
236 if err != nil {
228 return cfg, fmt.Errorf("failed to open log file: %w", err)
237 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 {
55 return nil
55 return nil
56 }
56 }
57 // Use -n to show line numbers and include the matched line
57 // Use -n to show line numbers and include the matched line
58− args := []string{"-n", pattern}
58+ args := []string{"-H", "-n", pattern}
59 if include != "" {
59 if include != "" {
60 args = append(args, "--glob", include)
60 args = append(args, "--glob", include)
61 }
61 }
internal/llm/agent/agent.go+20−5
@@ -363,6 +363,7 @@ func (a *agent) Run(ctx context.Context, sessionID string, content string, attac
363}
363}
364
364
365func (a *agent) processGeneration(ctx context.Context, sessionID, content string, attachmentParts []message.ContentPart) AgentEvent {
365func (a *agent) processGeneration(ctx context.Context, sessionID, content string, attachmentParts []message.ContentPart) AgentEvent {
366+ cfg := config.Get()
366 // List existing messages; if none, start title generation asynchronously.
367 // List existing messages; if none, start title generation asynchronously.
367 msgs, err := a.messages.List(ctx, sessionID)
368 msgs, err := a.messages.List(ctx, sessionID)
368 if err != nil {
369 if err != nil {
@@ -421,7 +422,13 @@ func (a *agent) processGeneration(ctx context.Context, sessionID, content string
421 }
422 }
422 return a.err(fmt.Errorf("failed to process events: %w", err))
423 return a.err(fmt.Errorf("failed to process events: %w", err))
423 }
424 }
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 {
424− logging.Info("Result", "message", agentMessage.FinishReason(), "toolResults", toolResults)
430+ logging.Info("Result", "message", agentMessage.FinishReason(), "toolResults", toolResults)
431+ }
425 if (agentMessage.FinishReason() == message.FinishReasonToolUse) && toolResults != nil {
432 if (agentMessage.FinishReason() == message.FinishReasonToolUse) && toolResults != nil {
426 // We are not done, we need to respond with the tool response
433 // We are not done, we need to respond with the tool response
427 msgHistory = append(msgHistory, agentMessage, *toolResults)
434 msgHistory = append(msgHistory, agentMessage, *toolResults)
@@ -445,6 +452,7 @@ func (a *agent) createUserMessage(ctx context.Context, sessionID, content string
445}
452}
446
453
447func (a *agent) streamAndHandleEvents(ctx context.Context, sessionID string, msgHistory []message.Message) (message.Message, *message.Message, error) {
454func (a *agent) streamAndHandleEvents(ctx context.Context, sessionID string, msgHistory []message.Message) (message.Message, *message.Message, error) {
455+ ctx = context.WithValue(ctx, tools.SessionIDContextKey, sessionID)
448 eventChan := a.provider.StreamResponse(ctx, msgHistory, a.tools)
456 eventChan := a.provider.StreamResponse(ctx, msgHistory, a.tools)
449
457
450 assistantMsg, err := a.messages.Create(ctx, sessionID, message.CreateMessageParams{
458 assistantMsg, err := a.messages.Create(ctx, sessionID, message.CreateMessageParams{
@@ -459,7 +467,6 @@ func (a *agent) streamAndHandleEvents(ctx context.Context, sessionID string, msg
459
467
460 // Add the session and message ID into the context if needed by tools.
468 // Add the session and message ID into the context if needed by tools.
461 ctx = context.WithValue(ctx, tools.MessageIDContextKey, assistantMsg.ID)
469 ctx = context.WithValue(ctx, tools.MessageIDContextKey, assistantMsg.ID)
462− ctx = context.WithValue(ctx, tools.SessionIDContextKey, sessionID)
463
470
464 // Process each event in the stream.
471 // Process each event in the stream.
465 for event := range eventChan {
472 for event := range eventChan {
@@ -491,10 +498,17 @@ func (a *agent) streamAndHandleEvents(ctx context.Context, sessionID string, msg
491 default:
498 default:
492 // Continue processing
499 // Continue processing
493 var tool tools.BaseTool
500 var tool tools.BaseTool
494− for _, availableTools := range a.tools {
501+ for _, availableTool := range a.tools {
495− if availableTools.Info().Name == toolCall.Name {
502+ if availableTool.Info().Name == toolCall.Name {
496− tool = availableTools
503+ tool = availableTool
504+ break
497 }
505 }
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+ // }
498 }
512 }
499
513
500 // Tool not found
514 // Tool not found
@@ -665,6 +679,7 @@ func (a *agent) Summarize(ctx context.Context, sessionID string) error {
665 a.Publish(pubsub.CreatedEvent, event)
679 a.Publish(pubsub.CreatedEvent, event)
666 return
680 return
667 }
681 }
682+ summarizeCtx = context.WithValue(summarizeCtx, tools.SessionIDContextKey, sessionID)
668
683
669 if len(msgs) == 0 {
684 if len(msgs) == 0 {
670 event = AgentEvent{
685 event = AgentEvent{
internal/llm/agent/mcp-tools.go+5−1
@@ -37,11 +37,15 @@ func (b *mcpTool) Name() string {
37}
37}
38
38
39func (b *mcpTool) Info() tools.ToolInfo {
39func (b *mcpTool) Info() tools.ToolInfo {
40+ required := b.tool.InputSchema.Required
41+ if required == nil {
42+ required = make([]string, 0)
43+ }
40 return tools.ToolInfo{
44 return tools.ToolInfo{
41 Name: fmt.Sprintf("%s_%s", b.mcpName, b.tool.Name),
45 Name: fmt.Sprintf("%s_%s", b.mcpName, b.tool.Name),
42 Description: b.tool.Description,
46 Description: b.tool.Description,
43 Parameters: b.tool.InputSchema.Properties,
47 Parameters: b.tool.InputSchema.Properties,
44− Required: b.tool.InputSchema.Required,
48+ Required: required,
45 }
49 }
46}
50}
47
51
internal/logging/logger.go+131−2
@@ -4,16 +4,33 @@ import (
4 "fmt"
4 "fmt"
5 "log/slog"
5 "log/slog"
6 "os"
6 "os"
7+ // "path/filepath"
8+ "encoding/json"
9+ "runtime"
7 "runtime/debug"
10 "runtime/debug"
11+ "sync"
8 "time"
12 "time"
9)
13)
10
14
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+}
11func Info(msg string, args ...any) {
25func Info(msg string, args ...any) {
26+ source := getCaller()
12− slog.Info(msg, args...)
27+ slog.Info(msg, append([]any{"source", source}, args...)...)
13}
28}
14
29
15func Debug(msg string, args ...any) {
30func Debug(msg string, args ...any) {
31+ // slog.Debug(msg, args...)
32+ source := getCaller()
16− slog.Debug(msg, args...)
33+ slog.Debug(msg, append([]any{"source", source}, args...)...)
17}
34}
18
35
19func Warn(msg string, args ...any) {
36func Warn(msg string, args ...any) {
@@ -76,3 +93,115 @@ func RecoverPanic(name string, cleanup func()) {
76 }
93 }
77 }
94 }
78}
95}
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{}
45
45
46func (w *writer) Write(p []byte) (int, error) {
46func (w *writer) Write(p []byte) (int, error) {
47 d := logfmt.NewDecoder(bytes.NewReader(p))
47 d := logfmt.NewDecoder(bytes.NewReader(p))
48+
48 for d.ScanRecord() {
49 for d.ScanRecord() {
49 msg := LogMessage{
50 msg := LogMessage{
50 ID: fmt.Sprintf("%d", time.Now().UnixNano()),
51 ID: fmt.Sprintf("%d", time.Now().UnixNano()),