-
Notifications
You must be signed in to change notification settings - Fork 0
revert(cli): undo PR #273 AI response parsing changes #275
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -102,11 +102,7 @@ func commandQuery(c *cli.Context) error { | |
| // Print newline after streaming | ||
| fmt.Println() | ||
|
|
||
| newCommand := sanitizeSuggestedCommand(result.String()) | ||
| if newCommand == "" { | ||
| color.Red.Println("AI returned an empty response. Try rephrasing your query.") | ||
| return fmt.Errorf("empty AI response") | ||
| } | ||
| newCommand := strings.TrimSpace(result.String()) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. By removing the |
||
|
|
||
| // Check auto-run configuration | ||
| if cfg.AI != nil && (cfg.AI.Agent.View || cfg.AI.Agent.Edit || cfg.AI.Agent.Delete) { | ||
|
|
@@ -192,39 +188,6 @@ func executeCommand(ctx context.Context, command string) error { | |
| return nil | ||
| } | ||
|
|
||
| // sanitizeSuggestedCommand normalizes raw AI output into an executable command. | ||
| // It strips triple-backtick fences (with optional language tag like bash, sh, | ||
| // zsh, fish, pwsh, powershell), strips surrounding single backticks when the | ||
| // result is a single line, and trims whitespace. Responses that start with `#` | ||
| // are treated as refusal comments and preserved verbatim so the caller can | ||
| // surface them to the user without attempting execution. | ||
| func sanitizeSuggestedCommand(raw string) string { | ||
| s := strings.TrimSpace(raw) | ||
| if s == "" { | ||
| return "" | ||
| } | ||
|
|
||
| if strings.HasPrefix(s, "```") { | ||
| s = strings.TrimPrefix(s, "```") | ||
| if nl := strings.IndexByte(s, '\n'); nl >= 0 { | ||
| switch strings.ToLower(strings.TrimSpace(s[:nl])) { | ||
| case "", "bash", "sh", "shell", "zsh", "fish", "pwsh", "powershell": | ||
| s = s[nl+1:] | ||
| } | ||
| } | ||
| s = strings.TrimRight(s, " \t\n") | ||
| s = strings.TrimSuffix(s, "```") | ||
| s = strings.TrimSpace(s) | ||
| } | ||
|
|
||
| if !strings.ContainsRune(s, '\n') && len(s) >= 2 && | ||
| strings.HasPrefix(s, "`") && strings.HasSuffix(s, "`") { | ||
| s = strings.TrimSpace(s[1 : len(s)-1]) | ||
| } | ||
|
|
||
| return s | ||
| } | ||
|
|
||
| func getSystemContext(query string, ai *model.AIConfig) (model.CommandSuggestVariables, error) { | ||
| // Get shell information | ||
| shell := os.Getenv("SHELL") | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -74,26 +74,24 @@ func (s *sseAIService) QueryCommandStream( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for scanner.Scan() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| line := scanner.Text() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if line == "" { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isError = false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if line == "event: error" { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isError = true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+77
to
80
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The string comparison |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if v, ok := stripSSEField(line, "event:"); ok { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if v == "error" { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isError = true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if strings.HasPrefix(line, "data:") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data := line[len("data:"):] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The stream parser takes Useful? React with 👍 / 👎. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if v, ok := stripSSEField(line, "data:"); ok { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if isError { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return fmt.Errorf("server error: %s", v) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return fmt.Errorf("server error: %s", data) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if v == "[DONE]" { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if data == "[DONE]" { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onToken(v) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onToken(data) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isError = false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+82
to
95
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This logic fails to handle the optional leading space in SSE
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -103,17 +101,3 @@ func (s *sseAIService) QueryCommandStream( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // stripSSEField returns the value after prefix, stripping one optional leading | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // space per the SSE specification (§9.2 "If value starts with a U+0020 SPACE | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // character, remove it from value"). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func stripSSEField(line, prefix string) (string, bool) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if !strings.HasPrefix(line, prefix) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return "", false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| v := line[len(prefix):] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if strings.HasPrefix(v, " ") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| v = v[1:] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return v, true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,7 +6,6 @@ import ( | |
| "fmt" | ||
| "net/http" | ||
| "net/http/httptest" | ||
| "strings" | ||
| "testing" | ||
| ) | ||
|
|
||
|
|
@@ -91,124 +90,3 @@ func TestQueryCommandStream_ErrorResponseBody(t *testing.T) { | |
| }) | ||
| } | ||
| } | ||
|
Comment on lines
90
to
92
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The removal of |
||
|
|
||
| func TestQueryCommandStream_SSEParsing(t *testing.T) { | ||
| tests := []struct { | ||
| name string | ||
| body string | ||
| wantErr bool | ||
| wantErrSubstr string | ||
| wantTokens []string | ||
| }{ | ||
| { | ||
| name: "data with space and [DONE] terminates cleanly", | ||
| body: "data: [DONE]\n\n", | ||
| wantTokens: nil, | ||
| }, | ||
| { | ||
| name: "data without space and [DONE] terminates cleanly", | ||
| body: "data:[DONE]\n\n", | ||
| wantTokens: nil, | ||
| }, | ||
| { | ||
| name: "single data token with leading space is stripped", | ||
| body: "data: hello\n\ndata: [DONE]\n\n", | ||
| wantTokens: []string{"hello"}, | ||
| }, | ||
| { | ||
| name: "single data token without leading space passes through", | ||
| body: "data:hello\n\ndata:[DONE]\n\n", | ||
| wantTokens: []string{"hello"}, | ||
| }, | ||
| { | ||
| name: "multi-token stream concatenates without spurious spaces", | ||
| body: "data: ls\n\ndata: -la\n\ndata: [DONE]\n\n", | ||
| wantTokens: []string{"ls", " -la"}, | ||
| }, | ||
| { | ||
| name: "event error with space", | ||
| body: "event: error\ndata: boom\n\n", | ||
| wantErr: true, | ||
| wantErrSubstr: "boom", | ||
| }, | ||
| { | ||
| name: "event error without space", | ||
| body: "event:error\ndata:boom\n\n", | ||
| wantErr: true, | ||
| wantErrSubstr: "boom", | ||
| }, | ||
| { | ||
| name: "blank line resets error state between events", | ||
| body: "event: error\n\ndata: hello\n\ndata: [DONE]\n\n", | ||
| wantTokens: []string{"hello"}, | ||
| }, | ||
| } | ||
|
|
||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
| w.Header().Set("Content-Type", "text/event-stream") | ||
| w.WriteHeader(http.StatusOK) | ||
| _, _ = w.Write([]byte(tt.body)) | ||
| })) | ||
| defer server.Close() | ||
|
|
||
| var got []string | ||
| svc := NewAIService() | ||
| err := svc.QueryCommandStream( | ||
| context.Background(), | ||
| CommandSuggestVariables{Shell: "bash", Os: "linux", Query: "test"}, | ||
| Endpoint{APIEndpoint: server.URL, Token: "test-token"}, | ||
| func(token string) { got = append(got, token) }, | ||
| ) | ||
|
|
||
| if tt.wantErr { | ||
| if err == nil { | ||
| t.Fatalf("expected error, got nil (tokens=%v)", got) | ||
| } | ||
| if !strings.Contains(err.Error(), tt.wantErrSubstr) { | ||
| t.Fatalf("expected error to contain %q, got %q", tt.wantErrSubstr, err.Error()) | ||
| } | ||
| return | ||
| } | ||
|
|
||
| if err != nil { | ||
| t.Fatalf("unexpected error: %v", err) | ||
| } | ||
| if len(got) != len(tt.wantTokens) { | ||
| t.Fatalf("token count mismatch: want %d %v, got %d %v", len(tt.wantTokens), tt.wantTokens, len(got), got) | ||
| } | ||
| for i, tok := range tt.wantTokens { | ||
| if got[i] != tok { | ||
| t.Errorf("token[%d] = %q, want %q", i, got[i], tok) | ||
| } | ||
| } | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| func TestStripSSEField(t *testing.T) { | ||
| tests := []struct { | ||
| name string | ||
| line string | ||
| prefix string | ||
| wantVal string | ||
| wantOk bool | ||
| }{ | ||
| {"no match", "foo:bar", "data:", "", false}, | ||
| {"match no space", "data:hello", "data:", "hello", true}, | ||
| {"match one space stripped", "data: hello", "data:", "hello", true}, | ||
| {"match two spaces preserves second", "data: hello", "data:", " hello", true}, | ||
| {"empty value no space", "data:", "data:", "", true}, | ||
| {"empty value one space", "data: ", "data:", "", true}, | ||
| {"event error with space", "event: error", "event:", "error", true}, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| v, ok := stripSSEField(tt.line, tt.prefix) | ||
| if ok != tt.wantOk || v != tt.wantVal { | ||
| t.Errorf("stripSSEField(%q, %q) = (%q, %v), want (%q, %v)", tt.line, tt.prefix, v, ok, tt.wantVal, tt.wantOk) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replacing
sanitizeSuggestedCommandwith a plainTrimSpacemeans markdown-formatted model output (for example fenced blocks or inline backticks) is now passed through unchanged. When auto-run is enabled,executeCommandreceives those backticks viashell -c, which can trigger shell parsing/command-substitution errors instead of executing the intended command; classification logic also runs on the malformed text.Useful? React with 👍 / 👎.